Add junk map serialization and deserialization for Nintendo GameCube/Wii: implement functions for entry management and lazy initialization

This commit is contained in:
2026-03-16 18:39:08 +00:00
parent 5b93c56e7b
commit d5d6e036e4
2 changed files with 346 additions and 0 deletions

229
src/ngcw/ngcw_junk.c Normal file
View 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
View 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 */