libaaruformat 1.0
Aaru Data Preservation Suite - Format Library
Loading...
Searching...
No Matches
lfg.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 * Lagged Fibonacci Generator for Nintendo GameCube/Wii junk fill.
19 * Based on Dolphin emulator's LaggedFibonacciGenerator (CC0 licensed).
20 */
21
22#include "lfg.h"
23
24#include <string.h>
25
26static inline uint32_t swap32(uint32_t x)
27{ return ((x >> 24) & 0xFF) | ((x >> 8) & 0xFF00) | ((x << 8) & 0xFF0000) | ((x << 24) & 0xFF000000); }
28
29static void lfg_forward(struct ngc_lfg_ctx *ctx)
30{
31 for(size_t i = 0; i < NGC_LFG_J; i++) ctx->buffer[i] ^= ctx->buffer[i + NGC_LFG_K - NGC_LFG_J];
32
33 for(size_t i = NGC_LFG_J; i < NGC_LFG_K; i++) ctx->buffer[i] ^= ctx->buffer[i - NGC_LFG_J];
34}
35
36static void lfg_backward(struct ngc_lfg_ctx *ctx, size_t start_word, size_t end_word)
37{
38 size_t loop_end = NGC_LFG_J > start_word ? NGC_LFG_J : start_word;
39 size_t upper = end_word < NGC_LFG_K ? end_word : NGC_LFG_K;
40
41 for(size_t i = upper; i > loop_end; --i) ctx->buffer[i - 1] ^= ctx->buffer[i - 1 - NGC_LFG_J];
42
43 size_t upper2 = end_word < NGC_LFG_J ? end_word : NGC_LFG_J;
44
45 for(size_t i = upper2; i > start_word; --i) ctx->buffer[i - 1] ^= ctx->buffer[i - 1 + NGC_LFG_K - NGC_LFG_J];
46}
47
48static bool lfg_initialize(struct ngc_lfg_ctx *ctx, bool check_existing)
49{
50 for(size_t i = NGC_LFG_SEED_SIZE; i < NGC_LFG_K; i++)
51 {
52 uint32_t calculated = (ctx->buffer[i - 17] << 23) ^ (ctx->buffer[i - 16] >> 9) ^ ctx->buffer[i - 1];
53
54 if(check_existing)
55 {
56 uint32_t actual = (ctx->buffer[i] & 0xFF00FFFF) | (ctx->buffer[i] << 2 & 0x00FC0000);
57
58 if((calculated & 0xFFFCFFFF) != actual) return false;
59 }
60
61 ctx->buffer[i] = calculated;
62 }
63
64 /* Apply the shift-by-18-instead-of-16 quirk + byteswap */
65 for(size_t i = 0; i < NGC_LFG_K; i++)
66 ctx->buffer[i] = swap32((ctx->buffer[i] & 0xFF00FFFF) | ((ctx->buffer[i] >> 2) & 0x00FF0000));
67
68 for(int i = 0; i < 4; i++) lfg_forward(ctx);
69
70 return true;
71}
72
73void ngc_lfg_set_seed(struct ngc_lfg_ctx *ctx, const uint32_t seed[NGC_LFG_SEED_SIZE])
74{
75 ctx->position_bytes = 0;
76
77 for(size_t i = 0; i < NGC_LFG_SEED_SIZE; i++) ctx->buffer[i] = swap32(seed[i]);
78
79 lfg_initialize(ctx, false);
80}
81
82void ngc_lfg_get_bytes(struct ngc_lfg_ctx *ctx, uint8_t *out, size_t count)
83{
84 while(count > 0)
85 {
86 size_t avail = NGC_LFG_K * sizeof(uint32_t) - ctx->position_bytes;
87 size_t chunk = count < avail ? count : avail;
88
89 memcpy(out, (uint8_t *)ctx->buffer + ctx->position_bytes, chunk);
90
91 ctx->position_bytes += chunk;
92 count -= chunk;
93 out += chunk;
94
95 if(ctx->position_bytes == NGC_LFG_K * sizeof(uint32_t))
96 {
97 lfg_forward(ctx);
98 ctx->position_bytes = 0;
99 }
100 }
101}
102
103static bool lfg_reinitialize(struct ngc_lfg_ctx *ctx, uint32_t seed_out[NGC_LFG_SEED_SIZE])
104{
105 for(int i = 0; i < 4; i++) lfg_backward(ctx, 0, NGC_LFG_K);
106
107 for(size_t i = 0; i < NGC_LFG_K; i++) ctx->buffer[i] = swap32(ctx->buffer[i]);
108
109 /* Reconstruct bits lost by the shift-by-18-instead-of-16 quirk */
110 for(size_t i = 0; i < NGC_LFG_SEED_SIZE; i++)
111 {
112 ctx->buffer[i] = (ctx->buffer[i] & 0xFF00FFFF) | (ctx->buffer[i] << 2 & 0x00FC0000) |
113 ((ctx->buffer[i + 16] ^ ctx->buffer[i + 15]) << 9 & 0x00030000);
114 }
115
116 for(size_t i = 0; i < NGC_LFG_SEED_SIZE; i++) seed_out[i] = swap32(ctx->buffer[i]);
117
118 return lfg_initialize(ctx, true);
119}
120
121size_t ngc_lfg_get_seed(const uint8_t *data, size_t size, size_t data_offset, uint32_t seed_out[NGC_LFG_SEED_SIZE])
122{
123 /* Alignment: data - data_offset must be 4-byte aligned */
124 if(((uintptr_t)data - data_offset) % sizeof(uint32_t) != 0) return 0;
125
126 /* Work on whole u32 words */
127 size_t bytes_to_skip = ((data_offset + 3) & ~(size_t)3) - data_offset;
128
129 if(bytes_to_skip > size) return 0;
130
131 const uint32_t *u32_data = (const uint32_t *)(data + bytes_to_skip);
132 size_t u32_size = (size - bytes_to_skip) / sizeof(uint32_t);
133 size_t u32_data_offset = (data_offset + bytes_to_skip) / sizeof(uint32_t);
134
135 if(u32_size < NGC_LFG_K) return 0;
136
137 /* Quick sanity check: the top bits have a specific pattern from the shift quirk */
138 for(size_t i = 0; i < NGC_LFG_K; i++)
139 {
140 uint32_t x = swap32(u32_data[i]);
141
142 if((x & 0x00C00000) != (x >> 2 & 0x00C00000)) return 0;
143 }
144
145 struct ngc_lfg_ctx lfg;
146 size_t data_offset_mod_k = u32_data_offset % NGC_LFG_K;
147 size_t data_offset_div_k = u32_data_offset / NGC_LFG_K;
148
149 /* Place the data into the buffer at the correct position.
150 * Copy raw native-endian u32 values — NO byte-swapping here.
151 * The swap happens later inside lfg_reinitialize/lfg_initialize. */
152 size_t first_part = NGC_LFG_K - data_offset_mod_k;
153
154 if(first_part > NGC_LFG_K) first_part = NGC_LFG_K;
155
156 for(size_t i = 0; i < first_part && i < NGC_LFG_K; i++) lfg.buffer[data_offset_mod_k + i] = u32_data[i];
157
158 for(size_t i = 0; i < data_offset_mod_k; i++) lfg.buffer[i] = u32_data[first_part + i];
159
160 lfg_backward(&lfg, 0, data_offset_mod_k);
161
162 for(size_t i = 0; i < data_offset_div_k; i++) lfg_backward(&lfg, 0, NGC_LFG_K);
163
164 if(!lfg_reinitialize(&lfg, seed_out)) return 0;
165
166 lfg.position_bytes = data_offset % (NGC_LFG_K * sizeof(uint32_t));
167
168 /* Advance the LFG forward to match the data_offset position. */
169 for(size_t i = 0; i < data_offset_div_k; i++) lfg_forward(&lfg);
170
171 /* Count how many bytes from data match the LFG output */
172 size_t result = 0;
173 const uint8_t *p = data;
174 const uint8_t *end = data + size;
175
176 while(p < end)
177 {
178 uint8_t expected = ((uint8_t *)lfg.buffer)[lfg.position_bytes];
179
180 if(*p != expected) break;
181
182 result++;
183 p++;
184 lfg.position_bytes++;
185
186 if(lfg.position_bytes == NGC_LFG_K * sizeof(uint32_t))
187 {
188 lfg_forward(&lfg);
189 lfg.position_bytes = 0;
190 }
191 }
192
193 return result;
194}
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
static uint32_t swap32(uint32_t x)
Definition lfg.c:26
static void lfg_backward(struct ngc_lfg_ctx *ctx, size_t start_word, size_t end_word)
Definition lfg.c:36
static bool lfg_initialize(struct ngc_lfg_ctx *ctx, bool check_existing)
Definition lfg.c:48
static void lfg_forward(struct ngc_lfg_ctx *ctx)
Definition lfg.c:29
static bool lfg_reinitialize(struct ngc_lfg_ctx *ctx, uint32_t seed_out[17])
Definition lfg.c:103
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
size_t ngc_lfg_get_seed(const uint8_t *data, size_t size, size_t data_offset, uint32_t seed_out[17])
Try to extract the LFG seed from a chunk of data.
Definition lfg.c:121
#define NGC_LFG_K
LFG buffer size (number of uint32 words in state).
Definition lfg.h:34
#define NGC_LFG_SEED_SIZE
Number of uint32 words needed to seed the LFG.
Definition lfg.h:36
#define NGC_LFG_J
LFG second tap.
Definition lfg.h:35
LFG context holding the 521-word circular buffer and byte position.
Definition lfg.h:42
size_t position_bytes
Definition lfg.h:44
uint32_t buffer[521]
Definition lfg.h:43