libaaruformat 1.0
Aaru Data Preservation Suite - Format Library
Loading...
Searching...
No Matches
ngcw_junk.c
Go to the documentation of this file.
1/*
2 * This file is part of the Aaru Data Preservation Suite.
3 * Copyright (c) 2019-2026 Natalia Portillo.
4 *
5 * This library is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU Lesser General Public License as
7 * published by the Free Software Foundation; version 2.1 of the License.
8 *
9 * This library is distributed in the hope that it will be useful, but
10 * WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library; if not, see
16 * <https://www.gnu.org/licenses/>.
17 *
18 * Nintendo GameCube/Wii junk map: serialization, deserialization, regeneration.
19 */
20
21#include <stdlib.h>
22#include <string.h>
23
24#include <aaruformat.h>
25
26#include "ngcw_junk.h"
27
28/* ---- Little-endian helpers ---- */
29
30static uint16_t read_le16(const uint8_t *p) { return (uint16_t)((uint16_t)p[0] | ((uint16_t)p[1] << 8)); }
31
32static uint32_t read_le32(const uint8_t *p)
33{ return (uint32_t)p[0] | ((uint32_t)p[1] << 8) | ((uint32_t)p[2] << 16) | ((uint32_t)p[3] << 24); }
34
35static uint64_t read_le64(const uint8_t *p) { return (uint64_t)read_le32(p) | ((uint64_t)read_le32(p + 4) << 32); }
36
37static void write_le16(uint8_t *p, uint16_t v)
38{
39 p[0] = (uint8_t)(v & 0xFF);
40 p[1] = (uint8_t)((v >> 8) & 0xFF);
41}
42
43static void write_le32(uint8_t *p, uint32_t v)
44{
45 p[0] = (uint8_t)(v & 0xFF);
46 p[1] = (uint8_t)((v >> 8) & 0xFF);
47 p[2] = (uint8_t)((v >> 16) & 0xFF);
48 p[3] = (uint8_t)((v >> 24) & 0xFF);
49}
50
51static void write_le64(uint8_t *p, uint64_t v)
52{
53 write_le32(p, (uint32_t)(v & 0xFFFFFFFF));
54 write_le32(p + 4, (uint32_t)(v >> 32));
55}
56
57/* ---- Serialization ---- */
58
59/*
60 * Format:
61 * [2] version (uint16 LE)
62 * [4] entry_count (uint32 LE)
63 * [2] seed_size (uint16 LE) — NGC_LFG_SEED_SIZE (17)
64 * For each entry:
65 * [8] offset (uint64 LE)
66 * [8] length (uint64 LE)
67 * [2] partition_index (uint16 LE)
68 * [seed_size * 4] seed (raw bytes)
69 *
70 * Header = 8 bytes. Entry = 18 + seed_size * 4 = 86 bytes (when seed_size=17).
71 */
72
73#define JUNK_MAP_HEADER_SIZE 8
74
75int32_t ngcw_serialize_junk_map(const NgcwJunkEntry *entries, uint32_t count, uint8_t **out_data, uint32_t *out_len)
76{
77 if(out_data == NULL || out_len == NULL) return -1;
78
79 uint32_t entry_size = 18 + NGC_LFG_SEED_SIZE * 4;
80 uint32_t size = JUNK_MAP_HEADER_SIZE + count * entry_size;
81 uint8_t *buf = (uint8_t *)malloc(size);
82
83 if(buf == NULL) return -4;
84
86 write_le32(buf + 2, count);
88
89 for(uint32_t i = 0; i < count; i++)
90 {
91 uint8_t *p = buf + JUNK_MAP_HEADER_SIZE + i * entry_size;
92
93 write_le64(p, entries[i].offset);
94 write_le64(p + 8, entries[i].length);
95 write_le16(p + 16, entries[i].partition_index);
96 memcpy(p + 18, entries[i].seed, NGC_LFG_SEED_SIZE * sizeof(uint32_t));
97 }
98
99 *out_data = buf;
100 *out_len = size;
101 return 0;
102}
103
104int32_t ngcw_deserialize_junk_map(const uint8_t *data, uint32_t data_len, NgcwJunkEntry **entries, uint32_t *count,
105 uint16_t *seed_size)
106{
107 if(data == NULL || entries == NULL || count == NULL || seed_size == NULL) return -1;
108
109 if(data_len < JUNK_MAP_HEADER_SIZE) return -2;
110
111 uint16_t version = read_le16(data);
112 uint32_t entry_cnt = read_le32(data + 2);
113 uint16_t ss = read_le16(data + 6);
114
115 if(version != NGCW_JUNK_MAP_VERSION) return -3;
116
117 if(ss != NGC_LFG_SEED_SIZE) return -3;
118
119 if(entry_cnt == 0)
120 {
121 *entries = NULL;
122 *count = 0;
123 *seed_size = ss;
124 return 0;
125 }
126
127 uint32_t entry_size = 18 + (uint32_t)ss * 4;
128 uint32_t required = JUNK_MAP_HEADER_SIZE + entry_cnt * entry_size;
129
130 if(data_len < required) return -2;
131
132 NgcwJunkEntry *e = (NgcwJunkEntry *)calloc(entry_cnt, sizeof(NgcwJunkEntry));
133
134 if(e == NULL) return -4;
135
136 for(uint32_t i = 0; i < entry_cnt; i++)
137 {
138 const uint8_t *p = data + JUNK_MAP_HEADER_SIZE + i * entry_size;
139
140 e[i].offset = read_le64(p);
141 e[i].length = read_le64(p + 8);
142 e[i].partition_index = read_le16(p + 16);
143 memcpy(e[i].seed, p + 18, ss * sizeof(uint32_t));
144 }
145
146 *entries = e;
147 *count = entry_cnt;
148 *seed_size = ss;
149 return 0;
150}
151
152/* ---- Junk regeneration ---- */
153
154int ngcw_regenerate_junk_sector(const NgcwJunkEntry *entries, uint32_t entry_count, uint64_t disc_offset,
155 uint8_t *output, uint32_t length)
156{
157 if(entries == NULL || entry_count == 0 || output == NULL) return -1;
158
159 /* Binary search for the entry containing disc_offset */
160 int lo = 0;
161 int hi = (int)entry_count - 1;
162
163 while(lo <= hi)
164 {
165 int mid = lo + (hi - lo) / 2;
166 uint64_t entry_end = entries[mid].offset + entries[mid].length;
167
168 if(disc_offset >= entry_end)
169 lo = mid + 1;
170 else if(disc_offset < entries[mid].offset)
171 hi = mid - 1;
172 else
173 {
174 /* Found: disc_offset is within entries[mid].
175 * The seed's position 0 corresponds to the start of the 0x8000-aligned
176 * block, not entries[mid].offset (which is the start of the junk region).
177 * Compute advance from the block start. */
178 uint64_t block_start = entries[mid].offset & ~(uint64_t)0x7FFF;
179 uint64_t stream_pos = disc_offset - block_start;
180
181 struct ngc_lfg_ctx lfg;
182 uint32_t seed_copy[NGC_LFG_SEED_SIZE];
183 memcpy(seed_copy, entries[mid].seed, sizeof(seed_copy));
184 ngc_lfg_set_seed(&lfg, seed_copy);
185
186 /* Advance LFG to the correct stream position */
187 if(stream_pos > 0)
188 {
189 uint8_t discard[4096];
190 size_t rem = (size_t)stream_pos;
191
192 while(rem > 0)
193 {
194 size_t step = rem > sizeof(discard) ? sizeof(discard) : rem;
195 ngc_lfg_get_bytes(&lfg, discard, step);
196 rem -= step;
197 }
198 }
199
200 /* Generate the requested bytes */
201 ngc_lfg_get_bytes(&lfg, output, length);
202 return 0;
203 }
204 }
205
206 return -1; /* Not found */
207}
208
209/* ---- Lazy initialization ---- */
210
212{
213 if(ctx == NULL) return;
214
215 if(ctx->ngcw_junk_entries != NULL) return;
216
217 mediaTagEntry *item = NULL;
218 int32_t tag = kMediaTagNgcwJunkMap;
219 HASH_FIND_INT(ctx->mediaTags, &tag, item);
220
221 if(item == NULL || item->length < JUNK_MAP_HEADER_SIZE) return;
222
223 NgcwJunkEntry *entries = NULL;
224 uint32_t count = 0;
225 uint16_t seed_size = 0;
226
227 if(ngcw_deserialize_junk_map(item->data, item->length, &entries, &count, &seed_size) == 0)
228 {
229 ctx->ngcw_junk_entries = entries;
230 ctx->ngcw_junk_entry_count = count;
231 ctx->ngcw_junk_seed_size = seed_size;
232 }
233}
@ kMediaTagNgcwJunkMap
Nintendo GameCube/Wii junk region map with LFG seeds.
Definition aaru.h:1087
void ngc_lfg_get_bytes(struct ngc_lfg_ctx *ctx, uint8_t *out, size_t count)
Generate count bytes of junk data into out.
Definition lfg.c:82
void ngc_lfg_set_seed(struct ngc_lfg_ctx *ctx, const uint32_t seed[17])
Initialize the LFG from a 17-word big-endian seed.
Definition lfg.c:73
#define NGC_LFG_SEED_SIZE
Number of uint32 words needed to seed the LFG.
Definition lfg.h:36
int32_t ngcw_serialize_junk_map(const NgcwJunkEntry *entries, uint32_t count, uint8_t **out_data, uint32_t *out_len)
Serialize a junk map for storage as a media tag.
Definition ngcw_junk.c:75
static uint64_t read_le64(const uint8_t *p)
Definition ngcw_junk.c:35
static uint32_t read_le32(const uint8_t *p)
Definition ngcw_junk.c:32
int32_t ngcw_deserialize_junk_map(const uint8_t *data, uint32_t data_len, NgcwJunkEntry **entries, uint32_t *count, uint16_t *seed_size)
Deserialize a junk map from a media tag buffer.
Definition ngcw_junk.c:104
static void write_le32(uint8_t *p, uint32_t v)
Definition ngcw_junk.c:43
static void write_le16(uint8_t *p, uint16_t v)
Definition ngcw_junk.c:37
static void write_le64(uint8_t *p, uint64_t v)
Definition ngcw_junk.c:51
#define JUNK_MAP_HEADER_SIZE
Definition ngcw_junk.c:73
void ngcw_junk_lazy_init(aaruformat_context *ctx)
Lazy initialization: load junk map from media tags.
Definition ngcw_junk.c:211
static uint16_t read_le16(const uint8_t *p)
Definition ngcw_junk.c:30
int ngcw_regenerate_junk_sector(const NgcwJunkEntry *entries, uint32_t entry_count, uint64_t disc_offset, uint8_t *output, uint32_t length)
Regenerate a junk sector from the junk map.
Definition ngcw_junk.c:154
#define NGCW_JUNK_MAP_VERSION
Current junk map serialization version.
Definition ngcw_junk.h:40
In-memory junk map entry.
Definition ngcw_junk.h:48
uint64_t length
Length of junk region in bytes.
Definition ngcw_junk.h:50
uint16_t partition_index
Partition index (0xFFFF for GC / inter-partition).
Definition ngcw_junk.h:51
uint64_t offset
Disc byte offset where junk starts.
Definition ngcw_junk.h:49
Master context representing an open or in‑creation Aaru image.
Definition context.h:175
uint16_t ngcw_junk_seed_size
LFG seed size in uint32 words (expected: 17).
Definition context.h:366
void * ngcw_junk_entries
Parsed NgcwJunkEntry array, NULL if not loaded.
Definition context.h:364
mediaTagEntry * mediaTags
Hash table of extra media tags (uthash root).
Definition context.h:267
uint32_t ngcw_junk_entry_count
Number of junk entries.
Definition context.h:365
Hash table entry for an arbitrary media tag (e.g., proprietary drive/medium descriptor).
Definition context.h:122
uint8_t * data
Tag data blob (opaque to library core); length bytes long.
Definition context.h:123
uint32_t length
Length in bytes of data.
Definition context.h:125
LFG context holding the 521-word circular buffer and byte position.
Definition lfg.h:42