mirror of
https://github.com/aaru-dps/libaaruformat.git
synced 2026-09-17 04:14:15 +00:00
Add Lagged Fibonacci Generator for Nintendo GameCube/Wii: implement LFG context and functions for junk data generation
This commit is contained in:
194
src/ngcw/lfg.c
Normal file
194
src/ngcw/lfg.c
Normal file
@@ -0,0 +1,194 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*
|
||||
* Lagged Fibonacci Generator for Nintendo GameCube/Wii junk fill.
|
||||
* Based on Dolphin emulator's LaggedFibonacciGenerator (CC0 licensed).
|
||||
*/
|
||||
|
||||
#include "lfg.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
static inline uint32_t swap32(uint32_t x)
|
||||
{ return ((x >> 24) & 0xFF) | ((x >> 8) & 0xFF00) | ((x << 8) & 0xFF0000) | ((x << 24) & 0xFF000000); }
|
||||
|
||||
static void lfg_forward(struct ngc_lfg_ctx *ctx)
|
||||
{
|
||||
for(size_t i = 0; i < NGC_LFG_J; i++) ctx->buffer[i] ^= ctx->buffer[i + NGC_LFG_K - NGC_LFG_J];
|
||||
|
||||
for(size_t i = NGC_LFG_J; i < NGC_LFG_K; i++) ctx->buffer[i] ^= ctx->buffer[i - NGC_LFG_J];
|
||||
}
|
||||
|
||||
static void lfg_backward(struct ngc_lfg_ctx *ctx, size_t start_word, size_t end_word)
|
||||
{
|
||||
size_t loop_end = NGC_LFG_J > start_word ? NGC_LFG_J : start_word;
|
||||
size_t upper = end_word < NGC_LFG_K ? end_word : NGC_LFG_K;
|
||||
|
||||
for(size_t i = upper; i > loop_end; --i) ctx->buffer[i - 1] ^= ctx->buffer[i - 1 - NGC_LFG_J];
|
||||
|
||||
size_t upper2 = end_word < NGC_LFG_J ? end_word : NGC_LFG_J;
|
||||
|
||||
for(size_t i = upper2; i > start_word; --i) ctx->buffer[i - 1] ^= ctx->buffer[i - 1 + NGC_LFG_K - NGC_LFG_J];
|
||||
}
|
||||
|
||||
static bool lfg_initialize(struct ngc_lfg_ctx *ctx, bool check_existing)
|
||||
{
|
||||
for(size_t i = NGC_LFG_SEED_SIZE; i < NGC_LFG_K; i++)
|
||||
{
|
||||
uint32_t calculated = (ctx->buffer[i - 17] << 23) ^ (ctx->buffer[i - 16] >> 9) ^ ctx->buffer[i - 1];
|
||||
|
||||
if(check_existing)
|
||||
{
|
||||
uint32_t actual = (ctx->buffer[i] & 0xFF00FFFF) | (ctx->buffer[i] << 2 & 0x00FC0000);
|
||||
|
||||
if((calculated & 0xFFFCFFFF) != actual) return false;
|
||||
}
|
||||
|
||||
ctx->buffer[i] = calculated;
|
||||
}
|
||||
|
||||
/* Apply the shift-by-18-instead-of-16 quirk + byteswap */
|
||||
for(size_t i = 0; i < NGC_LFG_K; i++)
|
||||
ctx->buffer[i] = swap32((ctx->buffer[i] & 0xFF00FFFF) | ((ctx->buffer[i] >> 2) & 0x00FF0000));
|
||||
|
||||
for(int i = 0; i < 4; i++) lfg_forward(ctx);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void ngc_lfg_set_seed(struct ngc_lfg_ctx *ctx, const uint32_t seed[NGC_LFG_SEED_SIZE])
|
||||
{
|
||||
ctx->position_bytes = 0;
|
||||
|
||||
for(size_t i = 0; i < NGC_LFG_SEED_SIZE; i++) ctx->buffer[i] = swap32(seed[i]);
|
||||
|
||||
lfg_initialize(ctx, false);
|
||||
}
|
||||
|
||||
void ngc_lfg_get_bytes(struct ngc_lfg_ctx *ctx, uint8_t *out, size_t count)
|
||||
{
|
||||
while(count > 0)
|
||||
{
|
||||
size_t avail = NGC_LFG_K * sizeof(uint32_t) - ctx->position_bytes;
|
||||
size_t chunk = count < avail ? count : avail;
|
||||
|
||||
memcpy(out, (uint8_t *)ctx->buffer + ctx->position_bytes, chunk);
|
||||
|
||||
ctx->position_bytes += chunk;
|
||||
count -= chunk;
|
||||
out += chunk;
|
||||
|
||||
if(ctx->position_bytes == NGC_LFG_K * sizeof(uint32_t))
|
||||
{
|
||||
lfg_forward(ctx);
|
||||
ctx->position_bytes = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool lfg_reinitialize(struct ngc_lfg_ctx *ctx, uint32_t seed_out[NGC_LFG_SEED_SIZE])
|
||||
{
|
||||
for(int i = 0; i < 4; i++) lfg_backward(ctx, 0, NGC_LFG_K);
|
||||
|
||||
for(size_t i = 0; i < NGC_LFG_K; i++) ctx->buffer[i] = swap32(ctx->buffer[i]);
|
||||
|
||||
/* Reconstruct bits lost by the shift-by-18-instead-of-16 quirk */
|
||||
for(size_t i = 0; i < NGC_LFG_SEED_SIZE; i++)
|
||||
{
|
||||
ctx->buffer[i] = (ctx->buffer[i] & 0xFF00FFFF) | (ctx->buffer[i] << 2 & 0x00FC0000) |
|
||||
((ctx->buffer[i + 16] ^ ctx->buffer[i + 15]) << 9 & 0x00030000);
|
||||
}
|
||||
|
||||
for(size_t i = 0; i < NGC_LFG_SEED_SIZE; i++) seed_out[i] = swap32(ctx->buffer[i]);
|
||||
|
||||
return lfg_initialize(ctx, true);
|
||||
}
|
||||
|
||||
size_t ngc_lfg_get_seed(const uint8_t *data, size_t size, size_t data_offset, uint32_t seed_out[NGC_LFG_SEED_SIZE])
|
||||
{
|
||||
/* Alignment: data - data_offset must be 4-byte aligned */
|
||||
if(((uintptr_t)data - data_offset) % sizeof(uint32_t) != 0) return 0;
|
||||
|
||||
/* Work on whole u32 words */
|
||||
size_t bytes_to_skip = ((data_offset + 3) & ~(size_t)3) - data_offset;
|
||||
|
||||
if(bytes_to_skip > size) return 0;
|
||||
|
||||
const uint32_t *u32_data = (const uint32_t *)(data + bytes_to_skip);
|
||||
size_t u32_size = (size - bytes_to_skip) / sizeof(uint32_t);
|
||||
size_t u32_data_offset = (data_offset + bytes_to_skip) / sizeof(uint32_t);
|
||||
|
||||
if(u32_size < NGC_LFG_K) return 0;
|
||||
|
||||
/* Quick sanity check: the top bits have a specific pattern from the shift quirk */
|
||||
for(size_t i = 0; i < NGC_LFG_K; i++)
|
||||
{
|
||||
uint32_t x = swap32(u32_data[i]);
|
||||
|
||||
if((x & 0x00C00000) != (x >> 2 & 0x00C00000)) return 0;
|
||||
}
|
||||
|
||||
struct ngc_lfg_ctx lfg;
|
||||
size_t data_offset_mod_k = u32_data_offset % NGC_LFG_K;
|
||||
size_t data_offset_div_k = u32_data_offset / NGC_LFG_K;
|
||||
|
||||
/* Place the data into the buffer at the correct position.
|
||||
* Copy raw native-endian u32 values — NO byte-swapping here.
|
||||
* The swap happens later inside lfg_reinitialize/lfg_initialize. */
|
||||
size_t first_part = NGC_LFG_K - data_offset_mod_k;
|
||||
|
||||
if(first_part > NGC_LFG_K) first_part = NGC_LFG_K;
|
||||
|
||||
for(size_t i = 0; i < first_part && i < NGC_LFG_K; i++) lfg.buffer[data_offset_mod_k + i] = u32_data[i];
|
||||
|
||||
for(size_t i = 0; i < data_offset_mod_k; i++) lfg.buffer[i] = u32_data[first_part + i];
|
||||
|
||||
lfg_backward(&lfg, 0, data_offset_mod_k);
|
||||
|
||||
for(size_t i = 0; i < data_offset_div_k; i++) lfg_backward(&lfg, 0, NGC_LFG_K);
|
||||
|
||||
if(!lfg_reinitialize(&lfg, seed_out)) return 0;
|
||||
|
||||
lfg.position_bytes = data_offset % (NGC_LFG_K * sizeof(uint32_t));
|
||||
|
||||
/* Advance the LFG forward to match the data_offset position. */
|
||||
for(size_t i = 0; i < data_offset_div_k; i++) lfg_forward(&lfg);
|
||||
|
||||
/* Count how many bytes from data match the LFG output */
|
||||
size_t result = 0;
|
||||
const uint8_t *p = data;
|
||||
const uint8_t *end = data + size;
|
||||
|
||||
while(p < end)
|
||||
{
|
||||
uint8_t expected = ((uint8_t *)lfg.buffer)[lfg.position_bytes];
|
||||
|
||||
if(*p != expected) break;
|
||||
|
||||
result++;
|
||||
p++;
|
||||
lfg.position_bytes++;
|
||||
|
||||
if(lfg.position_bytes == NGC_LFG_K * sizeof(uint32_t))
|
||||
{
|
||||
lfg_forward(&lfg);
|
||||
lfg.position_bytes = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
86
src/ngcw/lfg.h
Normal file
86
src/ngcw/lfg.h
Normal file
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*
|
||||
* Lagged Fibonacci Generator for Nintendo GameCube/Wii junk fill.
|
||||
* Based on Dolphin emulator's LaggedFibonacciGenerator (CC0 licensed).
|
||||
*/
|
||||
|
||||
#ifndef LIBAARUFORMAT_NGCW_LFG_H
|
||||
#define LIBAARUFORMAT_NGCW_LFG_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C"
|
||||
{
|
||||
#endif
|
||||
|
||||
#define NGC_LFG_K 521 /**< LFG buffer size (number of uint32 words in state). */
|
||||
#define NGC_LFG_J 32 /**< LFG second tap. */
|
||||
#define NGC_LFG_SEED_SIZE 17 /**< Number of uint32 words needed to seed the LFG. */
|
||||
|
||||
/**
|
||||
* @brief LFG context holding the 521-word circular buffer and byte position.
|
||||
*/
|
||||
struct ngc_lfg_ctx
|
||||
{
|
||||
uint32_t buffer[NGC_LFG_K];
|
||||
size_t position_bytes;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Initialize the LFG from a 17-word big-endian seed.
|
||||
*
|
||||
* @param ctx LFG context to initialize.
|
||||
* @param seed Array of 17 uint32 seed words (big-endian).
|
||||
*/
|
||||
void ngc_lfg_set_seed(struct ngc_lfg_ctx *ctx, const uint32_t seed[NGC_LFG_SEED_SIZE]);
|
||||
|
||||
/**
|
||||
* @brief Generate count bytes of junk data into out.
|
||||
*
|
||||
* @param ctx LFG context (must be initialized via ngc_lfg_set_seed).
|
||||
* @param out Output buffer to fill.
|
||||
* @param count Number of bytes to generate.
|
||||
*/
|
||||
void ngc_lfg_get_bytes(struct ngc_lfg_ctx *ctx, uint8_t *out, size_t count);
|
||||
|
||||
/**
|
||||
* @brief Try to extract the LFG seed from a chunk of data.
|
||||
*
|
||||
* Attempts to reverse-engineer the 17-word seed from a buffer of suspected
|
||||
* PRNG output. Requires at least NGC_LFG_K * 4 = 2084 bytes of contiguous
|
||||
* PRNG output aligned to uint32 boundaries.
|
||||
*
|
||||
* @param data Pointer to the data to test.
|
||||
* @param size Number of bytes available.
|
||||
* @param data_offset Byte offset of data[0] within its LFG block
|
||||
* (0x8000-aligned for Wii, arbitrary for GC).
|
||||
* @param seed_out If successful, receives the 17-word seed (big-endian).
|
||||
*
|
||||
* @return The number of consecutive bytes from data[0] that match the LFG.
|
||||
* Returns 0 if the data does not look like junk.
|
||||
*/
|
||||
size_t ngc_lfg_get_seed(const uint8_t *data, size_t size, size_t data_offset, uint32_t seed_out[NGC_LFG_SEED_SIZE]);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* LIBAARUFORMAT_NGCW_LFG_H */
|
||||
Reference in New Issue
Block a user