mirror of
https://github.com/aaru-dps/libaaruformat.git
synced 2026-09-19 13:24:16 +00:00
Add junk map serialization and deserialization for Nintendo GameCube/Wii: implement functions for entry management and lazy initialization
This commit is contained in:
229
src/ngcw/ngcw_junk.c
Normal file
229
src/ngcw/ngcw_junk.c
Normal file
@@ -0,0 +1,229 @@
|
||||
/*
|
||||
* This file is part of the Aaru Data Preservation Suite.
|
||||
* Copyright (c) 2019-2026 Natalia Portillo.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as
|
||||
* published by the Free Software Foundation; version 2.1 of the License.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, see
|
||||
* <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Nintendo GameCube/Wii junk map: serialization, deserialization, regeneration.
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <aaruformat.h>
|
||||
|
||||
#include "ngcw_junk.h"
|
||||
|
||||
/* ---- Little-endian helpers ---- */
|
||||
|
||||
static uint16_t read_le16(const uint8_t *p) { return (uint16_t)((uint16_t)p[0] | ((uint16_t)p[1] << 8)); }
|
||||
|
||||
static uint32_t read_le32(const uint8_t *p)
|
||||
{ return (uint32_t)p[0] | ((uint32_t)p[1] << 8) | ((uint32_t)p[2] << 16) | ((uint32_t)p[3] << 24); }
|
||||
|
||||
static uint64_t read_le64(const uint8_t *p) { return (uint64_t)read_le32(p) | ((uint64_t)read_le32(p + 4) << 32); }
|
||||
|
||||
static void write_le16(uint8_t *p, uint16_t v)
|
||||
{
|
||||
p[0] = (uint8_t)(v & 0xFF);
|
||||
p[1] = (uint8_t)((v >> 8) & 0xFF);
|
||||
}
|
||||
|
||||
static void write_le32(uint8_t *p, uint32_t v)
|
||||
{
|
||||
p[0] = (uint8_t)(v & 0xFF);
|
||||
p[1] = (uint8_t)((v >> 8) & 0xFF);
|
||||
p[2] = (uint8_t)((v >> 16) & 0xFF);
|
||||
p[3] = (uint8_t)((v >> 24) & 0xFF);
|
||||
}
|
||||
|
||||
static void write_le64(uint8_t *p, uint64_t v)
|
||||
{
|
||||
write_le32(p, (uint32_t)(v & 0xFFFFFFFF));
|
||||
write_le32(p + 4, (uint32_t)(v >> 32));
|
||||
}
|
||||
|
||||
/* ---- Serialization ---- */
|
||||
|
||||
/*
|
||||
* Format:
|
||||
* [2] version (uint16 LE)
|
||||
* [4] entry_count (uint32 LE)
|
||||
* [2] seed_size (uint16 LE) — NGC_LFG_SEED_SIZE (17)
|
||||
* For each entry:
|
||||
* [8] offset (uint64 LE)
|
||||
* [8] length (uint64 LE)
|
||||
* [2] partition_index (uint16 LE)
|
||||
* [seed_size * 4] seed (raw bytes)
|
||||
*
|
||||
* Header = 8 bytes. Entry = 18 + seed_size * 4 = 86 bytes (when seed_size=17).
|
||||
*/
|
||||
|
||||
#define JUNK_MAP_HEADER_SIZE 8
|
||||
|
||||
int32_t ngcw_serialize_junk_map(const NgcwJunkEntry *entries, uint32_t count, uint8_t **out_data, uint32_t *out_len)
|
||||
{
|
||||
if(out_data == NULL || out_len == NULL) return -1;
|
||||
|
||||
uint32_t entry_size = 18 + NGC_LFG_SEED_SIZE * 4;
|
||||
uint32_t size = JUNK_MAP_HEADER_SIZE + count * entry_size;
|
||||
uint8_t *buf = (uint8_t *)malloc(size);
|
||||
|
||||
if(buf == NULL) return -4;
|
||||
|
||||
write_le16(buf, NGCW_JUNK_MAP_VERSION);
|
||||
write_le32(buf + 2, count);
|
||||
write_le16(buf + 6, NGC_LFG_SEED_SIZE);
|
||||
|
||||
for(uint32_t i = 0; i < count; i++)
|
||||
{
|
||||
uint8_t *p = buf + JUNK_MAP_HEADER_SIZE + i * entry_size;
|
||||
|
||||
write_le64(p, entries[i].offset);
|
||||
write_le64(p + 8, entries[i].length);
|
||||
write_le16(p + 16, entries[i].partition_index);
|
||||
memcpy(p + 18, entries[i].seed, NGC_LFG_SEED_SIZE * sizeof(uint32_t));
|
||||
}
|
||||
|
||||
*out_data = buf;
|
||||
*out_len = size;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int32_t ngcw_deserialize_junk_map(const uint8_t *data, uint32_t data_len, NgcwJunkEntry **entries, uint32_t *count,
|
||||
uint16_t *seed_size)
|
||||
{
|
||||
if(data == NULL || entries == NULL || count == NULL || seed_size == NULL) return -1;
|
||||
|
||||
if(data_len < JUNK_MAP_HEADER_SIZE) return -2;
|
||||
|
||||
uint16_t version = read_le16(data);
|
||||
uint32_t entry_cnt = read_le32(data + 2);
|
||||
uint16_t ss = read_le16(data + 6);
|
||||
|
||||
if(version != NGCW_JUNK_MAP_VERSION) return -3;
|
||||
|
||||
if(ss != NGC_LFG_SEED_SIZE) return -3;
|
||||
|
||||
if(entry_cnt == 0)
|
||||
{
|
||||
*entries = NULL;
|
||||
*count = 0;
|
||||
*seed_size = ss;
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint32_t entry_size = 18 + (uint32_t)ss * 4;
|
||||
uint32_t required = JUNK_MAP_HEADER_SIZE + entry_cnt * entry_size;
|
||||
|
||||
if(data_len < required) return -2;
|
||||
|
||||
NgcwJunkEntry *e = (NgcwJunkEntry *)calloc(entry_cnt, sizeof(NgcwJunkEntry));
|
||||
|
||||
if(e == NULL) return -4;
|
||||
|
||||
for(uint32_t i = 0; i < entry_cnt; i++)
|
||||
{
|
||||
const uint8_t *p = data + JUNK_MAP_HEADER_SIZE + i * entry_size;
|
||||
|
||||
e[i].offset = read_le64(p);
|
||||
e[i].length = read_le64(p + 8);
|
||||
e[i].partition_index = read_le16(p + 16);
|
||||
memcpy(e[i].seed, p + 18, ss * sizeof(uint32_t));
|
||||
}
|
||||
|
||||
*entries = e;
|
||||
*count = entry_cnt;
|
||||
*seed_size = ss;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ---- Junk regeneration ---- */
|
||||
|
||||
int ngcw_regenerate_junk_sector(const NgcwJunkEntry *entries, uint32_t entry_count, uint64_t disc_offset,
|
||||
uint8_t *output, uint32_t length)
|
||||
{
|
||||
if(entries == NULL || entry_count == 0 || output == NULL) return -1;
|
||||
|
||||
/* Binary search for the entry containing disc_offset */
|
||||
int lo = 0;
|
||||
int hi = (int)entry_count - 1;
|
||||
|
||||
while(lo <= hi)
|
||||
{
|
||||
int mid = lo + (hi - lo) / 2;
|
||||
uint64_t entry_end = entries[mid].offset + entries[mid].length;
|
||||
|
||||
if(disc_offset >= entry_end)
|
||||
lo = mid + 1;
|
||||
else if(disc_offset < entries[mid].offset)
|
||||
hi = mid - 1;
|
||||
else
|
||||
{
|
||||
/* Found: disc_offset is within entries[mid] */
|
||||
uint64_t stream_pos = disc_offset - entries[mid].offset;
|
||||
|
||||
struct ngc_lfg_ctx lfg;
|
||||
uint32_t seed_copy[NGC_LFG_SEED_SIZE];
|
||||
memcpy(seed_copy, entries[mid].seed, sizeof(seed_copy));
|
||||
ngc_lfg_set_seed(&lfg, seed_copy);
|
||||
|
||||
/* Advance LFG to the correct stream position */
|
||||
if(stream_pos > 0)
|
||||
{
|
||||
uint8_t discard[4096];
|
||||
size_t rem = (size_t)stream_pos;
|
||||
|
||||
while(rem > 0)
|
||||
{
|
||||
size_t step = rem > sizeof(discard) ? sizeof(discard) : rem;
|
||||
ngc_lfg_get_bytes(&lfg, discard, step);
|
||||
rem -= step;
|
||||
}
|
||||
}
|
||||
|
||||
/* Generate the requested bytes */
|
||||
ngc_lfg_get_bytes(&lfg, output, length);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return -1; /* Not found */
|
||||
}
|
||||
|
||||
/* ---- Lazy initialization ---- */
|
||||
|
||||
void ngcw_junk_lazy_init(aaruformat_context *ctx)
|
||||
{
|
||||
if(ctx == NULL) return;
|
||||
|
||||
if(ctx->ngcw_junk_entries != NULL) return;
|
||||
|
||||
mediaTagEntry *item = NULL;
|
||||
int32_t tag = kMediaTagNgcwJunkMap;
|
||||
HASH_FIND_INT(ctx->mediaTags, &tag, item);
|
||||
|
||||
if(item == NULL || item->length < JUNK_MAP_HEADER_SIZE) return;
|
||||
|
||||
NgcwJunkEntry *entries = NULL;
|
||||
uint32_t count = 0;
|
||||
uint16_t seed_size = 0;
|
||||
|
||||
if(ngcw_deserialize_junk_map(item->data, item->length, &entries, &count, &seed_size) == 0)
|
||||
{
|
||||
ctx->ngcw_junk_entries = entries;
|
||||
ctx->ngcw_junk_entry_count = count;
|
||||
ctx->ngcw_junk_seed_size = seed_size;
|
||||
}
|
||||
}
|
||||
117
src/ngcw/ngcw_junk.h
Normal file
117
src/ngcw/ngcw_junk.h
Normal file
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
* This file is part of the Aaru Data Preservation Suite.
|
||||
* Copyright (c) 2019-2026 Natalia Portillo.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as
|
||||
* published by the Free Software Foundation; version 2.1 of the License.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, see
|
||||
* <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Nintendo GameCube/Wii junk map: serialization, deserialization, regeneration.
|
||||
*/
|
||||
|
||||
#ifndef LIBAARUFORMAT_NGCW_JUNK_H
|
||||
#define LIBAARUFORMAT_NGCW_JUNK_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "lfg.h"
|
||||
|
||||
/* Forward declaration */
|
||||
typedef struct aaruformat_context aaruformat_context;
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C"
|
||||
{
|
||||
#endif
|
||||
|
||||
#define NGCW_JUNK_MAP_VERSION 1 /**< Current junk map serialization version. */
|
||||
|
||||
/**
|
||||
* @brief In-memory junk map entry.
|
||||
*
|
||||
* Each entry describes a contiguous region of LFG-generated junk on disc.
|
||||
*/
|
||||
typedef struct NgcwJunkEntry
|
||||
{
|
||||
uint64_t offset; /**< Disc byte offset where junk starts. */
|
||||
uint64_t length; /**< Length of junk region in bytes. */
|
||||
uint16_t partition_index; /**< Partition index (0xFFFF for GC / inter-partition). */
|
||||
uint32_t seed[NGC_LFG_SEED_SIZE]; /**< LFG seed (17 words, big-endian). */
|
||||
} NgcwJunkEntry;
|
||||
|
||||
/**
|
||||
* @brief Serialize a junk map for storage as a media tag.
|
||||
*
|
||||
* Format (all little-endian):
|
||||
* [2 bytes] version (uint16)
|
||||
* [4 bytes] entry_count (uint32)
|
||||
* [2 bytes] seed_size (uint16) — NGC_LFG_SEED_SIZE
|
||||
* For each entry (86 bytes when seed_size=17):
|
||||
* [8 bytes] offset (uint64)
|
||||
* [8 bytes] length (uint64)
|
||||
* [2 bytes] partition_index (uint16)
|
||||
* [seed_size * 4 bytes] seed (raw bytes, big-endian uint32 words)
|
||||
*
|
||||
* @param entries Array of junk entries.
|
||||
* @param count Number of entries.
|
||||
* @param out_data Output: malloc'd buffer (caller must free).
|
||||
* @param out_len Output: length of the buffer.
|
||||
* @return 0 on success, negative error code on failure.
|
||||
*/
|
||||
int32_t ngcw_serialize_junk_map(const NgcwJunkEntry *entries, uint32_t count, uint8_t **out_data,
|
||||
uint32_t *out_len);
|
||||
|
||||
/**
|
||||
* @brief Deserialize a junk map from a media tag buffer.
|
||||
*
|
||||
* @param data Serialized data buffer.
|
||||
* @param data_len Length of the data buffer.
|
||||
* @param entries Output: malloc'd array of NgcwJunkEntry (caller must free).
|
||||
* @param count Output: number of entries.
|
||||
* @param seed_size Output: seed size in uint32 words.
|
||||
* @return 0 on success, negative error code on failure.
|
||||
*/
|
||||
int32_t ngcw_deserialize_junk_map(const uint8_t *data, uint32_t data_len, NgcwJunkEntry **entries, uint32_t *count,
|
||||
uint16_t *seed_size);
|
||||
|
||||
/**
|
||||
* @brief Regenerate a junk sector from the junk map.
|
||||
*
|
||||
* Looks up the given disc byte offset in the junk map, initializes the LFG
|
||||
* with the matching entry's seed, advances to the correct stream position,
|
||||
* and generates the requested number of bytes.
|
||||
*
|
||||
* @param entries Array of junk entries (sorted by offset).
|
||||
* @param entry_count Number of entries.
|
||||
* @param disc_offset Disc byte offset of the sector to regenerate.
|
||||
* @param output Output buffer to fill.
|
||||
* @param length Number of bytes to generate.
|
||||
* @return 0 on success, -1 if the offset is not in the junk map.
|
||||
*/
|
||||
int ngcw_regenerate_junk_sector(const NgcwJunkEntry *entries, uint32_t entry_count, uint64_t disc_offset,
|
||||
uint8_t *output, uint32_t length);
|
||||
|
||||
/**
|
||||
* @brief Lazy initialization: load junk map from media tags.
|
||||
*
|
||||
* Populates ctx->ngcw_junk_entries from the kMediaTagNgcwJunkMap media tag.
|
||||
*
|
||||
* @param ctx AaruFormat context.
|
||||
*/
|
||||
void ngcw_junk_lazy_init(aaruformat_context *ctx);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* LIBAARUFORMAT_NGCW_JUNK_H */
|
||||
Reference in New Issue
Block a user