mirror of
https://github.com/aaru-dps/libaaruformat.git
synced 2026-04-05 21:51:03 +00:00
Enhance Wii junk data handling: reconstruct junk in user data area during read and write operations
This commit is contained in:
@@ -371,6 +371,10 @@ typedef struct aaruformat_context
|
||||
bool wii_building_crypto_block; ///< True while gathering sectors for re-encryption (suppresses recursion)
|
||||
} aaruformat_context;
|
||||
|
||||
#ifndef AARUFORMAT_CONTEXT_DECLARED
|
||||
#define AARUFORMAT_CONTEXT_DECLARED
|
||||
#endif
|
||||
|
||||
/** \struct DumpHardwareEntriesWithData
|
||||
* \brief In-memory representation of a dump hardware entry plus decoded variable-length fields & extents.
|
||||
*
|
||||
|
||||
@@ -26,8 +26,11 @@
|
||||
|
||||
#include "lfg.h"
|
||||
|
||||
/* Forward declaration */
|
||||
/* Forward declaration — guard against redefinition when aaruformat.h is also included */
|
||||
#ifndef AARUFORMAT_CONTEXT_DECLARED
|
||||
#define AARUFORMAT_CONTEXT_DECLARED
|
||||
typedef struct aaruformat_context aaruformat_context;
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C"
|
||||
|
||||
@@ -53,9 +53,8 @@ const uint8_t *wii_get_sector_key(const WiiPartitionRegion *regions, uint32_t re
|
||||
{
|
||||
if(phys_group >= regions[i].start_sector && phys_group < regions[i].end_sector)
|
||||
{
|
||||
/* Partition header group is plaintext */
|
||||
if(phys_group == regions[i].start_sector) return NULL;
|
||||
|
||||
/* For Wii, data_offset already skips the partition header.
|
||||
* ALL groups in [start_sector, end_sector) are encrypted. */
|
||||
return regions[i].key;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,8 +24,11 @@
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
/* Forward declaration */
|
||||
/* Forward declaration — guard against redefinition when aaruformat.h is also included */
|
||||
#ifndef AARUFORMAT_CONTEXT_DECLARED
|
||||
#define AARUFORMAT_CONTEXT_DECLARED
|
||||
typedef struct aaruformat_context aaruformat_context;
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C"
|
||||
@@ -36,6 +39,7 @@ extern "C"
|
||||
#define WII_GROUP_HASH_SIZE 0x0400 /**< Hash block size within a group (1 KiB). */
|
||||
#define WII_GROUP_DATA_SIZE 0x7C00 /**< User data size within a group (31 KiB). */
|
||||
#define WII_LOGICAL_PER_GROUP 16 /**< Number of 2048-byte logical sectors per group. */
|
||||
#define WII_SECTOR_SIZE 2048 /**< Logical sector size in bytes. */
|
||||
#define WII_MAX_PARTITIONS 32 /**< Maximum number of partitions supported. */
|
||||
|
||||
/**
|
||||
|
||||
246
src/read.c
246
src/read.c
@@ -23,12 +23,105 @@
|
||||
|
||||
#include "internal.h"
|
||||
#include "log.h"
|
||||
#include "ngcw/lfg.h"
|
||||
#include "ngcw/ngcw_junk.h"
|
||||
#include "ngcw/wii_crypto.h"
|
||||
#include "ps3/ps3_crypto.h"
|
||||
#include "ps3/ps3_encryption_map.h"
|
||||
#include "wiiu/wiiu_crypto.h"
|
||||
|
||||
/**
|
||||
* @brief Reconstruct junk in a Wii group's user data area before re-encryption.
|
||||
*
|
||||
* The LFG stream for Wii operates in a virtual "user data stream" where each
|
||||
* group contributes WII_GROUP_DATA_SIZE (0x7C00) bytes, NOT the full group size.
|
||||
* stream_pos = group_idx * WII_GROUP_DATA_SIZE + offset_within_user_data
|
||||
* advance = stream_pos % WII_GROUP_SIZE (the 0x8000-aligned block boundary)
|
||||
*
|
||||
* This matches OBMAFS's reconstruct_group() logic.
|
||||
*/
|
||||
static void wii_reconstruct_group_junk(aaruformat_context *ctx, uint64_t phys_group, uint8_t *group_cache)
|
||||
{
|
||||
if(ctx->ngcw_junk_entries == NULL || ctx->ngcw_junk_entry_count == 0) return;
|
||||
|
||||
if(ctx->wii_partition_regions == NULL) return;
|
||||
|
||||
/* Find which partition this group belongs to */
|
||||
const WiiPartitionRegion *regions = (const WiiPartitionRegion *)ctx->wii_partition_regions;
|
||||
int in_part = -1;
|
||||
|
||||
for(uint32_t p = 0; p < ctx->wii_partition_region_count; p++)
|
||||
{
|
||||
if(phys_group >= regions[p].start_sector && phys_group < regions[p].end_sector)
|
||||
{
|
||||
in_part = (int)p;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(in_part < 0) return; /* Outside partition — no Wii junk reconstruction needed */
|
||||
|
||||
uint64_t group_idx = phys_group - regions[in_part].start_sector;
|
||||
uint64_t group_disc_off = phys_group * WII_GROUP_SIZE;
|
||||
|
||||
for(uint64_t off = 0; off < WII_GROUP_DATA_SIZE; off += WII_SECTOR_SIZE)
|
||||
{
|
||||
uint64_t disc_off = group_disc_off + WII_GROUP_HASH_SIZE + off;
|
||||
|
||||
/* Look up this user data sector in the junk map */
|
||||
const NgcwJunkEntry *entries = (const NgcwJunkEntry *)ctx->ngcw_junk_entries;
|
||||
int lo = 0;
|
||||
int hi = (int)ctx->ngcw_junk_entry_count - 1;
|
||||
int found = -1;
|
||||
|
||||
while(lo <= hi)
|
||||
{
|
||||
int mid = lo + (hi - lo) / 2;
|
||||
uint64_t entry_end = entries[mid].offset + entries[mid].length;
|
||||
|
||||
if(disc_off >= entry_end)
|
||||
lo = mid + 1;
|
||||
else if(disc_off < entries[mid].offset)
|
||||
hi = mid - 1;
|
||||
else
|
||||
{
|
||||
found = mid;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(found < 0) continue; /* Not junk — leave stored data */
|
||||
|
||||
/* Junk found — regenerate using OBMAFS stream formula */
|
||||
struct ngc_lfg_ctx lfg;
|
||||
uint32_t seed_copy[NGC_LFG_SEED_SIZE];
|
||||
memcpy(seed_copy, entries[found].seed, sizeof(seed_copy));
|
||||
ngc_lfg_set_seed(&lfg, seed_copy);
|
||||
|
||||
uint64_t stream_pos = group_idx * WII_GROUP_DATA_SIZE + off;
|
||||
size_t advance = (size_t)(stream_pos % WII_GROUP_SIZE);
|
||||
|
||||
if(advance > 0)
|
||||
{
|
||||
uint8_t discard[4096];
|
||||
size_t adv = advance;
|
||||
|
||||
while(adv > 0)
|
||||
{
|
||||
size_t step = adv > sizeof(discard) ? sizeof(discard) : adv;
|
||||
ngc_lfg_get_bytes(&lfg, discard, step);
|
||||
adv -= step;
|
||||
}
|
||||
}
|
||||
|
||||
size_t chunk = WII_GROUP_DATA_SIZE - (size_t)off;
|
||||
|
||||
if(chunk > WII_SECTOR_SIZE) chunk = WII_SECTOR_SIZE;
|
||||
|
||||
ngc_lfg_get_bytes(&lfg, group_cache + WII_GROUP_HASH_SIZE + off, chunk);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Reads a media tag from the AaruFormat image.
|
||||
*
|
||||
@@ -366,8 +459,6 @@ AARU_EXPORT int32_t AARU_CALL aaruf_read_sector(void *context, const uint64_t se
|
||||
if(ngcw_regenerate_junk_sector((const NgcwJunkEntry *)ctx->ngcw_junk_entries, ctx->ngcw_junk_entry_count,
|
||||
disc_offset, data, *length) == 0)
|
||||
{
|
||||
// For WOD: the regenerated junk may need re-encryption below
|
||||
// For GOD: return as-is
|
||||
if(ctx->header.mediaType == GOD)
|
||||
{
|
||||
*sector_status = SectorStatusDumped;
|
||||
@@ -375,9 +466,71 @@ AARU_EXPORT int32_t AARU_CALL aaruf_read_sector(void *context, const uint64_t se
|
||||
return AARUF_STATUS_OK;
|
||||
}
|
||||
|
||||
// WOD: fall through to Wii re-encryption below
|
||||
// Change status so the re-encryption logic picks it up
|
||||
*sector_status = SectorStatusUnencrypted;
|
||||
// WOD: regenerated junk needs Wii re-encryption.
|
||||
// We have the plaintext in the data buffer — do re-encryption now and return.
|
||||
if(!ctx->wii_encryption_initialized)
|
||||
{
|
||||
wii_lazy_init(ctx);
|
||||
ctx->wii_encryption_initialized = true;
|
||||
}
|
||||
|
||||
if(ctx->wii_partition_regions != NULL && ctx->wii_encrypted_group_cache != NULL &&
|
||||
!ctx->wii_building_crypto_block)
|
||||
{
|
||||
const uint8_t *part_key = wii_get_sector_key((const WiiPartitionRegion *)ctx->wii_partition_regions,
|
||||
ctx->wii_partition_region_count, sector_address);
|
||||
|
||||
if(part_key != NULL)
|
||||
{
|
||||
uint64_t phys_group = sector_address / WII_LOGICAL_PER_GROUP;
|
||||
uint32_t slice_index = (uint32_t)(sector_address % WII_LOGICAL_PER_GROUP);
|
||||
|
||||
if(!ctx->wii_cache_valid || ctx->wii_cached_physical_group != phys_group)
|
||||
{
|
||||
uint64_t first_logical = phys_group * WII_LOGICAL_PER_GROUP;
|
||||
|
||||
ctx->wii_building_crypto_block = true;
|
||||
|
||||
for(uint32_t s = 0; s < WII_LOGICAL_PER_GROUP; s++)
|
||||
{
|
||||
uint64_t logical = first_logical + s;
|
||||
|
||||
if(logical == sector_address)
|
||||
{
|
||||
memcpy(ctx->wii_encrypted_group_cache + s * (*length), data, *length);
|
||||
}
|
||||
else
|
||||
{
|
||||
uint32_t s_len = *length;
|
||||
uint8_t s_status = 0;
|
||||
int32_t s_ret = aaruf_read_sector(context, logical, false,
|
||||
ctx->wii_encrypted_group_cache + s * s_len,
|
||||
&s_len, &s_status);
|
||||
|
||||
if(s_ret != AARUF_STATUS_OK)
|
||||
memset(ctx->wii_encrypted_group_cache + s * (*length), 0, *length);
|
||||
}
|
||||
}
|
||||
|
||||
ctx->wii_building_crypto_block = false;
|
||||
|
||||
// Reconstruct junk in user data area
|
||||
wii_reconstruct_group_junk(ctx, phys_group, ctx->wii_encrypted_group_cache);
|
||||
|
||||
wii_encrypt_group(part_key, ctx->wii_encrypted_group_cache,
|
||||
ctx->wii_encrypted_group_cache + WII_GROUP_HASH_SIZE,
|
||||
ctx->wii_encrypted_group_cache);
|
||||
ctx->wii_cached_physical_group = phys_group;
|
||||
ctx->wii_cache_valid = true;
|
||||
}
|
||||
|
||||
memcpy(data, ctx->wii_encrypted_group_cache + slice_index * (*length), *length);
|
||||
}
|
||||
}
|
||||
|
||||
*sector_status = SectorStatusDumped;
|
||||
TRACE("Exiting aaruf_read_sector() = AARUF_STATUS_OK (WOD generable re-encrypted)");
|
||||
return AARUF_STATUS_OK;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -398,71 +551,6 @@ AARU_EXPORT int32_t AARU_CALL aaruf_read_sector(void *context, const uint64_t se
|
||||
}
|
||||
}
|
||||
|
||||
// If we fell through from SectorStatusGenerable on WOD, we need Wii re-encryption
|
||||
// but we already have the data in the buffer. Handle it here before block lookup.
|
||||
if(*sector_status == SectorStatusUnencrypted && ctx->header.mediaType == WOD && !ctx->wii_building_crypto_block)
|
||||
{
|
||||
if(!ctx->wii_encryption_initialized)
|
||||
{
|
||||
wii_lazy_init(ctx);
|
||||
ctx->wii_encryption_initialized = true;
|
||||
}
|
||||
|
||||
if(ctx->wii_partition_regions != NULL && ctx->wii_encrypted_group_cache != NULL)
|
||||
{
|
||||
const uint8_t *part_key = wii_get_sector_key((const WiiPartitionRegion *)ctx->wii_partition_regions,
|
||||
ctx->wii_partition_region_count, sector_address);
|
||||
|
||||
if(part_key != NULL)
|
||||
{
|
||||
uint64_t phys_group = sector_address / WII_LOGICAL_PER_GROUP;
|
||||
uint32_t slice_index = (uint32_t)(sector_address % WII_LOGICAL_PER_GROUP);
|
||||
|
||||
if(!ctx->wii_cache_valid || ctx->wii_cached_physical_group != phys_group)
|
||||
{
|
||||
uint64_t first_logical = phys_group * WII_LOGICAL_PER_GROUP;
|
||||
|
||||
ctx->wii_building_crypto_block = true;
|
||||
|
||||
for(uint32_t s = 0; s < WII_LOGICAL_PER_GROUP; s++)
|
||||
{
|
||||
uint64_t logical = first_logical + s;
|
||||
|
||||
if(logical == sector_address)
|
||||
{
|
||||
memcpy(ctx->wii_encrypted_group_cache + s * (*length), data, *length);
|
||||
}
|
||||
else
|
||||
{
|
||||
uint32_t s_len = *length;
|
||||
uint8_t s_status = 0;
|
||||
int32_t s_ret = aaruf_read_sector(
|
||||
context, logical, false, ctx->wii_encrypted_group_cache + s * s_len, &s_len, &s_status);
|
||||
|
||||
if(s_ret != AARUF_STATUS_OK)
|
||||
memset(ctx->wii_encrypted_group_cache + s * (*length), 0, *length);
|
||||
}
|
||||
}
|
||||
|
||||
ctx->wii_building_crypto_block = false;
|
||||
|
||||
// Split into hash_block + data, then encrypt
|
||||
wii_encrypt_group(part_key, ctx->wii_encrypted_group_cache,
|
||||
ctx->wii_encrypted_group_cache + WII_GROUP_HASH_SIZE,
|
||||
ctx->wii_encrypted_group_cache);
|
||||
ctx->wii_cached_physical_group = phys_group;
|
||||
ctx->wii_cache_valid = true;
|
||||
}
|
||||
|
||||
memcpy(data, ctx->wii_encrypted_group_cache + slice_index * (*length), *length);
|
||||
}
|
||||
}
|
||||
|
||||
*sector_status = SectorStatusDumped;
|
||||
TRACE("Exiting aaruf_read_sector() = AARUF_STATUS_OK (WOD re-encrypted)");
|
||||
return AARUF_STATUS_OK;
|
||||
}
|
||||
|
||||
// Check if block header is cached
|
||||
TRACE("Checking if block header is cached");
|
||||
block_header = find_in_cache_uint64(&ctx->block_header_cache, block_offset);
|
||||
@@ -624,6 +712,12 @@ AARU_EXPORT int32_t AARU_CALL aaruf_read_sector(void *context, const uint64_t se
|
||||
ctx->wii_encryption_initialized = true;
|
||||
}
|
||||
|
||||
if(!ctx->ngcw_junk_initialized)
|
||||
{
|
||||
ngcw_junk_lazy_init(ctx);
|
||||
ctx->ngcw_junk_initialized = true;
|
||||
}
|
||||
|
||||
if(ctx->wii_partition_regions != NULL && ctx->wii_encrypted_group_cache != NULL)
|
||||
{
|
||||
const uint8_t *part_key = wii_get_sector_key((const WiiPartitionRegion *)ctx->wii_partition_regions,
|
||||
@@ -663,6 +757,9 @@ AARU_EXPORT int32_t AARU_CALL aaruf_read_sector(void *context, const uint64_t se
|
||||
|
||||
ctx->wii_building_crypto_block = false;
|
||||
|
||||
// Reconstruct junk in user data area
|
||||
wii_reconstruct_group_junk(ctx, phys_group, ctx->wii_encrypted_group_cache);
|
||||
|
||||
wii_encrypt_group(part_key, ctx->wii_encrypted_group_cache,
|
||||
ctx->wii_encrypted_group_cache + WII_GROUP_HASH_SIZE,
|
||||
ctx->wii_encrypted_group_cache);
|
||||
@@ -947,6 +1044,12 @@ AARU_EXPORT int32_t AARU_CALL aaruf_read_sector(void *context, const uint64_t se
|
||||
ctx->wii_encryption_initialized = true;
|
||||
}
|
||||
|
||||
if(!ctx->ngcw_junk_initialized)
|
||||
{
|
||||
ngcw_junk_lazy_init(ctx);
|
||||
ctx->ngcw_junk_initialized = true;
|
||||
}
|
||||
|
||||
if(ctx->wii_partition_regions != NULL && ctx->wii_encrypted_group_cache != NULL)
|
||||
{
|
||||
const uint8_t *part_key = wii_get_sector_key((const WiiPartitionRegion *)ctx->wii_partition_regions,
|
||||
@@ -985,6 +1088,9 @@ AARU_EXPORT int32_t AARU_CALL aaruf_read_sector(void *context, const uint64_t se
|
||||
|
||||
ctx->wii_building_crypto_block = false;
|
||||
|
||||
// Reconstruct junk in user data area
|
||||
wii_reconstruct_group_junk(ctx, phys_group, ctx->wii_encrypted_group_cache);
|
||||
|
||||
wii_encrypt_group(part_key, ctx->wii_encrypted_group_cache,
|
||||
ctx->wii_encrypted_group_cache + WII_GROUP_HASH_SIZE,
|
||||
ctx->wii_encrypted_group_cache);
|
||||
|
||||
@@ -1260,6 +1260,74 @@ int convert_ngcw(const char *input_path, const char *output_path)
|
||||
else
|
||||
{
|
||||
/* ---- Wii pipeline ---- */
|
||||
|
||||
/* Build FST data maps for each partition */
|
||||
NgcwDataMap *part_maps = calloc(part_count > 0 ? part_count : 1, sizeof(NgcwDataMap));
|
||||
uint64_t *part_sys_end = calloc(part_count > 0 ? part_count : 1, sizeof(uint64_t));
|
||||
|
||||
if(part_maps != NULL && part_sys_end != NULL && is_raw)
|
||||
{
|
||||
for(int p = 0; p < part_count; p++)
|
||||
{
|
||||
/* Read and decrypt first group to get boot block */
|
||||
uint8_t enc_grp0[WII_GROUP_SIZE];
|
||||
|
||||
if(fseek(iso_file, (long)parts[p].data_offset, SEEK_SET) != 0 ||
|
||||
fread(enc_grp0, 1, WII_GROUP_SIZE, iso_file) != WII_GROUP_SIZE)
|
||||
continue;
|
||||
|
||||
uint8_t hb0[WII_GROUP_HASH_SIZE];
|
||||
uint8_t gd0[WII_GROUP_DATA_SIZE];
|
||||
wii_decrypt_group(parts[p].title_key, enc_grp0, hb0, gd0);
|
||||
|
||||
uint32_t fst_offset_p = read_be32(gd0 + 0x424) << 2;
|
||||
uint32_t fst_size_p = read_be32(gd0 + 0x428) << 2;
|
||||
|
||||
part_sys_end[p] = (uint64_t)fst_offset_p + fst_size_p;
|
||||
|
||||
if(fst_size_p > 0 && fst_size_p < 64 * 1024 * 1024)
|
||||
{
|
||||
uint8_t *fst_p = malloc(fst_size_p);
|
||||
|
||||
if(fst_p)
|
||||
{
|
||||
uint64_t fst_read = 0;
|
||||
int fst_ok = 1;
|
||||
|
||||
while(fst_read < fst_size_p)
|
||||
{
|
||||
uint64_t logical_off = fst_offset_p + fst_read;
|
||||
uint64_t grp_idx = logical_off / WII_GROUP_DATA_SIZE;
|
||||
uint64_t grp_off = logical_off % WII_GROUP_DATA_SIZE;
|
||||
uint64_t disc_off = parts[p].data_offset + grp_idx * WII_GROUP_SIZE;
|
||||
|
||||
uint8_t enc_g[WII_GROUP_SIZE];
|
||||
|
||||
if(fseek(iso_file, (long)disc_off, SEEK_SET) != 0 ||
|
||||
fread(enc_g, 1, WII_GROUP_SIZE, iso_file) != WII_GROUP_SIZE)
|
||||
{
|
||||
fst_ok = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
uint8_t hb[WII_GROUP_HASH_SIZE];
|
||||
uint8_t gd[WII_GROUP_DATA_SIZE];
|
||||
wii_decrypt_group(parts[p].title_key, enc_g, hb, gd);
|
||||
|
||||
uint64_t avail = WII_GROUP_DATA_SIZE - grp_off;
|
||||
uint64_t chunk = (fst_size_p - fst_read < avail) ? fst_size_p - fst_read : avail;
|
||||
memcpy(fst_p + fst_read, gd + grp_off, chunk);
|
||||
fst_read += chunk;
|
||||
}
|
||||
|
||||
if(fst_ok) build_data_map_from_fst(fst_p, fst_size_p, 0, 2, &part_maps[p]);
|
||||
|
||||
free(fst_p);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for(uint64_t offset = 0; offset < disc_size;)
|
||||
{
|
||||
if((offset & 0x7FFFF) == 0)
|
||||
@@ -1305,7 +1373,7 @@ int convert_ngcw(const char *input_path, const char *output_path)
|
||||
|
||||
if(in_part >= 0 && is_raw)
|
||||
{
|
||||
/* Inside partition — read, decrypt, detect junk, write */
|
||||
/* Inside partition — read, decrypt, detect junk in user data, write */
|
||||
uint64_t group_disc_off = parts[in_part].data_offset +
|
||||
((offset - parts[in_part].data_offset) / WII_GROUP_SIZE) * WII_GROUP_SIZE;
|
||||
|
||||
@@ -1324,17 +1392,154 @@ int convert_ngcw(const char *input_path, const char *output_path)
|
||||
uint8_t group_data[WII_GROUP_DATA_SIZE];
|
||||
wii_decrypt_group(parts[in_part].title_key, enc_grp, hash_block, group_data);
|
||||
|
||||
/* Reassemble: hash_block + group_data → decrypted_group */
|
||||
/* Classify each user data sector using the partition's FST */
|
||||
uint64_t group_num = (group_disc_off - parts[in_part].data_offset) / WII_GROUP_SIZE;
|
||||
uint64_t logical_offset = group_num * WII_GROUP_DATA_SIZE;
|
||||
|
||||
int sector_is_data[16];
|
||||
int num_ud_sectors = 0;
|
||||
|
||||
for(uint64_t off = 0; off < WII_GROUP_DATA_SIZE; off += NGC_SECTOR_SIZE)
|
||||
{
|
||||
uint64_t chunk = WII_GROUP_DATA_SIZE - off;
|
||||
|
||||
if(chunk > NGC_SECTOR_SIZE) chunk = NGC_SECTOR_SIZE;
|
||||
|
||||
if(logical_offset + off < part_sys_end[in_part])
|
||||
sector_is_data[num_ud_sectors] = 1;
|
||||
else if(part_maps[in_part].count > 0)
|
||||
sector_is_data[num_ud_sectors] =
|
||||
is_data_region(&part_maps[in_part], logical_offset + off, chunk) ? 1 : 0;
|
||||
else
|
||||
sector_is_data[num_ud_sectors] = 1;
|
||||
|
||||
num_ud_sectors++;
|
||||
}
|
||||
|
||||
/* Extract LFG seeds from user data (up to 2 per group for block boundaries) */
|
||||
uint64_t block_phase = logical_offset % WII_GROUP_SIZE;
|
||||
uint64_t block2_start = (block_phase > 0) ? (WII_GROUP_SIZE - block_phase) : WII_GROUP_DATA_SIZE;
|
||||
|
||||
if(block2_start > WII_GROUP_DATA_SIZE) block2_start = WII_GROUP_DATA_SIZE;
|
||||
|
||||
int have_seed1 = 0;
|
||||
uint32_t seed1[NGC_LFG_SEED_SIZE];
|
||||
int have_seed2 = 0;
|
||||
uint32_t seed2[NGC_LFG_SEED_SIZE];
|
||||
|
||||
for(int s = 0; s < num_ud_sectors; s++)
|
||||
{
|
||||
if(sector_is_data[s]) continue;
|
||||
|
||||
uint64_t soff = (uint64_t)s * NGC_SECTOR_SIZE;
|
||||
int in_block2 = (soff >= block2_start);
|
||||
|
||||
if(in_block2 && have_seed2) continue;
|
||||
|
||||
if(!in_block2 && have_seed1) continue;
|
||||
|
||||
size_t avail = (size_t)(WII_GROUP_DATA_SIZE - soff);
|
||||
size_t doff = (size_t)((logical_offset + soff) % WII_GROUP_SIZE);
|
||||
|
||||
if(avail < NGC_LFG_K * sizeof(uint32_t)) continue;
|
||||
|
||||
uint32_t *dst = in_block2 ? seed2 : seed1;
|
||||
size_t m = ngc_lfg_get_seed(group_data + soff, avail, doff, dst);
|
||||
|
||||
if(m > 0)
|
||||
{
|
||||
if(in_block2)
|
||||
have_seed2 = 1;
|
||||
else
|
||||
have_seed1 = 1;
|
||||
}
|
||||
|
||||
if(have_seed1 && have_seed2) break;
|
||||
}
|
||||
|
||||
/* Build output: hash_block + user_data, detect and zero junk.
|
||||
* Due to the 1024-byte hash block offset, junk detection sectors
|
||||
* (2048-byte chunks within group_data) don't align with AaruFormat
|
||||
* sectors (2048-byte chunks within the full 0x8000 group). So we
|
||||
* can't use SectorStatusGenerable per-sector for Wii partitions.
|
||||
* Instead: detect junk, zero it, record in junk map, write ALL
|
||||
* sectors as SectorStatusUnencrypted. Dedup handles the zeros.
|
||||
* On readback, junk is reconstructed at group level before re-encryption. */
|
||||
uint8_t decrypted_group[WII_GROUP_SIZE];
|
||||
memcpy(decrypted_group, hash_block, WII_GROUP_HASH_SIZE);
|
||||
memcpy(decrypted_group + WII_GROUP_HASH_SIZE, group_data, WII_GROUP_DATA_SIZE);
|
||||
|
||||
for(int s = 0; s < num_ud_sectors; s++)
|
||||
{
|
||||
uint64_t off = (uint64_t)s * NGC_SECTOR_SIZE;
|
||||
uint64_t chunk = WII_GROUP_DATA_SIZE - off;
|
||||
uint64_t out_off = WII_GROUP_HASH_SIZE + off;
|
||||
|
||||
if(chunk > NGC_SECTOR_SIZE) chunk = NGC_SECTOR_SIZE;
|
||||
|
||||
if(sector_is_data[s])
|
||||
{
|
||||
memcpy(decrypted_group + out_off, group_data + off, chunk);
|
||||
data_sectors++;
|
||||
continue;
|
||||
}
|
||||
|
||||
int in_block2 = (off >= block2_start);
|
||||
int have_seed = in_block2 ? have_seed2 : have_seed1;
|
||||
uint32_t *the_seed = in_block2 ? seed2 : seed1;
|
||||
|
||||
if(!have_seed)
|
||||
{
|
||||
memcpy(decrypted_group + out_off, group_data + off, chunk);
|
||||
data_sectors++;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Verify sector against LFG */
|
||||
struct ngc_lfg_ctx lfg;
|
||||
uint32_t sc[NGC_LFG_SEED_SIZE];
|
||||
memcpy(sc, the_seed, sizeof(sc));
|
||||
ngc_lfg_set_seed(&lfg, sc);
|
||||
|
||||
size_t adv = (size_t)((logical_offset + off) % WII_GROUP_SIZE);
|
||||
|
||||
if(adv > 0)
|
||||
{
|
||||
uint8_t discard[4096];
|
||||
size_t rem = adv;
|
||||
|
||||
while(rem > 0)
|
||||
{
|
||||
size_t step = rem > sizeof(discard) ? sizeof(discard) : rem;
|
||||
ngc_lfg_get_bytes(&lfg, discard, step);
|
||||
rem -= step;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t expected[NGC_SECTOR_SIZE];
|
||||
ngc_lfg_get_bytes(&lfg, expected, chunk);
|
||||
|
||||
if(memcmp(group_data + off, expected, chunk) == 0)
|
||||
{
|
||||
/* Junk — zero it out, record in junk map */
|
||||
memset(decrypted_group + out_off, 0, chunk);
|
||||
junk_collector_add(&jc, group_disc_off + WII_GROUP_HASH_SIZE + off, chunk, (uint16_t)in_part,
|
||||
the_seed);
|
||||
junk_sectors++;
|
||||
}
|
||||
else
|
||||
{
|
||||
memcpy(decrypted_group + out_off, group_data + off, chunk);
|
||||
data_sectors++;
|
||||
}
|
||||
}
|
||||
|
||||
/* Write all 16 sectors as SectorStatusUnencrypted */
|
||||
for(uint32_t s = 0; s < SECTORS_PER_BLOCK; s++)
|
||||
{
|
||||
uint64_t sector = group_disc_off / NGC_SECTOR_SIZE + s;
|
||||
int32_t wret = aaruf_write_sector(output_ctx, sector, false, decrypted_group + s * NGC_SECTOR_SIZE,
|
||||
SectorStatusUnencrypted, NGC_SECTOR_SIZE);
|
||||
|
||||
int32_t wret = aaruf_write_sector(output_ctx, sector, false, decrypted_group + s * NGC_SECTOR_SIZE,
|
||||
SectorStatusUnencrypted, NGC_SECTOR_SIZE);
|
||||
|
||||
if(wret != AARUF_STATUS_OK)
|
||||
{
|
||||
@@ -1345,8 +1550,6 @@ int convert_ngcw(const char *input_path, const char *output_path)
|
||||
result = -1;
|
||||
break;
|
||||
}
|
||||
|
||||
data_sectors++;
|
||||
}
|
||||
|
||||
if(result != 0) break;
|
||||
@@ -1420,6 +1623,16 @@ int convert_ngcw(const char *input_path, const char *output_path)
|
||||
bytes_processed += block_bytes;
|
||||
}
|
||||
}
|
||||
|
||||
/* Free partition data maps */
|
||||
if(part_maps != NULL)
|
||||
{
|
||||
for(int p = 0; p < part_count; p++) data_map_free(&part_maps[p]);
|
||||
|
||||
free(part_maps);
|
||||
}
|
||||
|
||||
free(part_sys_end);
|
||||
}
|
||||
|
||||
printf("\n\n");
|
||||
|
||||
Reference in New Issue
Block a user