diff --git a/src/ngcw/ngcw_junk.c b/src/ngcw/ngcw_junk.c new file mode 100644 index 0000000..c8d3051 --- /dev/null +++ b/src/ngcw/ngcw_junk.c @@ -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 + * . + * + * Nintendo GameCube/Wii junk map: serialization, deserialization, regeneration. + */ + +#include +#include + +#include + +#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; + } +} diff --git a/src/ngcw/ngcw_junk.h b/src/ngcw/ngcw_junk.h new file mode 100644 index 0000000..0222e2f --- /dev/null +++ b/src/ngcw/ngcw_junk.h @@ -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 + * . + * + * Nintendo GameCube/Wii junk map: serialization, deserialization, regeneration. + */ + +#ifndef LIBAARUFORMAT_NGCW_JUNK_H +#define LIBAARUFORMAT_NGCW_JUNK_H + +#include +#include + +#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 */