mirror of
https://github.com/aaru-dps/libaaruformat.git
synced 2026-07-09 02:16:24 +00:00
Add Wii disc encryption support: implement partition key map and group encrypt/decrypt functions
This commit is contained in:
211
src/ngcw/wii_crypto.c
Normal file
211
src/ngcw/wii_crypto.c
Normal file
@@ -0,0 +1,211 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*
|
||||
* Nintendo Wii disc encryption: partition key map, group encrypt/decrypt.
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <aaruformat.h>
|
||||
|
||||
#include "../lib/aes128.h"
|
||||
#include "wii_crypto.h"
|
||||
|
||||
/* ---- Little-endian helpers ---- */
|
||||
|
||||
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 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);
|
||||
}
|
||||
|
||||
/* ---- Key lookup ---- */
|
||||
|
||||
const uint8_t *wii_get_sector_key(const WiiPartitionRegion *regions, uint32_t region_count, uint64_t logical_sector)
|
||||
{
|
||||
if(regions == NULL || region_count == 0) return NULL;
|
||||
|
||||
/* Convert logical (2048-byte) sector to physical (0x8000-byte) group */
|
||||
uint64_t phys_group = logical_sector / WII_LOGICAL_PER_GROUP;
|
||||
|
||||
for(uint32_t i = 0; i < region_count; i++)
|
||||
{
|
||||
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;
|
||||
|
||||
return regions[i].key;
|
||||
}
|
||||
}
|
||||
|
||||
/* Outside any known partition — plaintext */
|
||||
return NULL;
|
||||
}
|
||||
|
||||
bool wii_is_sector_encrypted(const WiiPartitionRegion *regions, uint32_t region_count, uint64_t logical_sector)
|
||||
{ return wii_get_sector_key(regions, region_count, logical_sector) != NULL; }
|
||||
|
||||
/* ---- Group encrypt/decrypt ---- */
|
||||
|
||||
void wii_encrypt_group(const uint8_t key[16], const uint8_t *hash_block, const uint8_t *data_in, uint8_t *out)
|
||||
{
|
||||
/* Hash block: first 0x400 bytes, IV = all zeros */
|
||||
uint8_t iv[16];
|
||||
memset(iv, 0, sizeof(iv));
|
||||
memcpy(out, hash_block, WII_GROUP_HASH_SIZE);
|
||||
aes128_cbc_encrypt(key, iv, out, WII_GROUP_HASH_SIZE);
|
||||
|
||||
/* Data block: next 0x7C00 bytes.
|
||||
* IV = bytes 0x3D0..0x3DF of the ENCRYPTED hash output (just written to out). */
|
||||
uint8_t data_iv[16];
|
||||
memcpy(data_iv, out + 0x3D0, 16);
|
||||
memcpy(out + WII_GROUP_HASH_SIZE, data_in, WII_GROUP_DATA_SIZE);
|
||||
aes128_cbc_encrypt(key, data_iv, out + WII_GROUP_HASH_SIZE, WII_GROUP_DATA_SIZE);
|
||||
}
|
||||
|
||||
void wii_decrypt_group(const uint8_t key[16], const uint8_t *in, uint8_t *hash_block, uint8_t *data_out)
|
||||
{
|
||||
/* Hash block: first 0x400 bytes, IV = all zeros */
|
||||
uint8_t iv[16];
|
||||
memset(iv, 0, sizeof(iv));
|
||||
memcpy(hash_block, in, WII_GROUP_HASH_SIZE);
|
||||
aes128_cbc_decrypt(key, iv, hash_block, WII_GROUP_HASH_SIZE);
|
||||
|
||||
/* Data block: next 0x7C00 bytes.
|
||||
* IV = bytes 0x3D0..0x3DF of the ENCRYPTED input (not the decrypted hash block). */
|
||||
uint8_t data_iv[16];
|
||||
memcpy(data_iv, in + 0x3D0, 16);
|
||||
memcpy(data_out, in + WII_GROUP_HASH_SIZE, WII_GROUP_DATA_SIZE);
|
||||
aes128_cbc_decrypt(key, data_iv, data_out, WII_GROUP_DATA_SIZE);
|
||||
}
|
||||
|
||||
/* ---- Serialization (same format as Wii U) ---- */
|
||||
|
||||
int32_t wii_serialize_partition_key_map(const WiiPartitionRegion *regions, uint32_t count, uint8_t **out_data,
|
||||
uint32_t *out_len)
|
||||
{
|
||||
if(out_data == NULL || out_len == NULL) return -1;
|
||||
|
||||
if(count > WII_MAX_PARTITIONS) return -3;
|
||||
|
||||
uint32_t size = 4 + count * 24;
|
||||
uint8_t *buf = (uint8_t *)malloc(size);
|
||||
|
||||
if(buf == NULL) return -4;
|
||||
|
||||
write_le32(buf, count);
|
||||
|
||||
for(uint32_t i = 0; i < count; i++)
|
||||
{
|
||||
uint32_t offset = 4 + i * 24;
|
||||
write_le32(buf + offset, regions[i].start_sector);
|
||||
write_le32(buf + offset + 4, regions[i].end_sector);
|
||||
memcpy(buf + offset + 8, regions[i].key, 16);
|
||||
}
|
||||
|
||||
*out_data = buf;
|
||||
*out_len = size;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int32_t wii_deserialize_partition_key_map(const uint8_t *data, uint32_t data_len, WiiPartitionRegion **regions,
|
||||
uint32_t *count)
|
||||
{
|
||||
if(data == NULL || regions == NULL || count == NULL) return -1;
|
||||
|
||||
if(data_len < 4) return -2;
|
||||
|
||||
uint32_t region_count = read_le32(data);
|
||||
|
||||
if(region_count > WII_MAX_PARTITIONS) return -3;
|
||||
|
||||
if(region_count == 0)
|
||||
{
|
||||
*regions = NULL;
|
||||
*count = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint32_t required = 4 + region_count * 24;
|
||||
|
||||
if(data_len < required) return -2;
|
||||
|
||||
WiiPartitionRegion *r = (WiiPartitionRegion *)malloc(region_count * sizeof(WiiPartitionRegion));
|
||||
|
||||
if(r == NULL) return -4;
|
||||
|
||||
for(uint32_t i = 0; i < region_count; i++)
|
||||
{
|
||||
uint32_t offset = 4 + i * 24;
|
||||
r[i].start_sector = read_le32(data + offset);
|
||||
r[i].end_sector = read_le32(data + offset + 4);
|
||||
memcpy(r[i].key, data + offset + 8, 16);
|
||||
|
||||
if(r[i].start_sector >= r[i].end_sector)
|
||||
{
|
||||
memset(r, 0, region_count * sizeof(WiiPartitionRegion));
|
||||
free(r);
|
||||
return -5;
|
||||
}
|
||||
}
|
||||
|
||||
*regions = r;
|
||||
*count = region_count;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ---- Lazy initialization ---- */
|
||||
|
||||
void wii_lazy_init(aaruformat_context *ctx)
|
||||
{
|
||||
if(ctx == NULL) return;
|
||||
|
||||
/* Read and deserialize partition key map from media tags */
|
||||
if(ctx->wii_partition_regions == NULL)
|
||||
{
|
||||
mediaTagEntry *item = NULL;
|
||||
int32_t tag = kMediaTagWiiPartitionKeyMap;
|
||||
HASH_FIND_INT(ctx->mediaTags, &tag, item);
|
||||
|
||||
if(item != NULL && item->length >= 4)
|
||||
{
|
||||
WiiPartitionRegion *regions = NULL;
|
||||
uint32_t count = 0;
|
||||
|
||||
if(wii_deserialize_partition_key_map(item->data, item->length, ®ions, &count) == 0)
|
||||
{
|
||||
ctx->wii_partition_regions = regions;
|
||||
ctx->wii_partition_region_count = count;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Allocate the encrypted group cache if needed */
|
||||
if(ctx->wii_encrypted_group_cache == NULL)
|
||||
{
|
||||
ctx->wii_encrypted_group_cache = (uint8_t *)malloc(WII_GROUP_SIZE);
|
||||
ctx->wii_cache_valid = false;
|
||||
}
|
||||
}
|
||||
125
src/ngcw/wii_crypto.h
Normal file
125
src/ngcw/wii_crypto.h
Normal file
@@ -0,0 +1,125 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*
|
||||
* Nintendo Wii disc encryption: partition key map, group encrypt/decrypt.
|
||||
*/
|
||||
|
||||
#ifndef LIBAARUFORMAT_NGCW_WII_CRYPTO_H
|
||||
#define LIBAARUFORMAT_NGCW_WII_CRYPTO_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
/* Forward declaration */
|
||||
typedef struct aaruformat_context aaruformat_context;
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C"
|
||||
{
|
||||
#endif
|
||||
|
||||
#define WII_GROUP_SIZE 0x8000 /**< Wii physical group size (32 KiB). */
|
||||
#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_MAX_PARTITIONS 32 /**< Maximum number of partitions supported. */
|
||||
|
||||
/**
|
||||
* @brief A Wii partition region entry (in-memory representation).
|
||||
*
|
||||
* Stores the physical sector range and AES-128 key for one partition.
|
||||
* Physical sector numbers are in 0x8000-byte units.
|
||||
*/
|
||||
typedef struct WiiPartitionRegion
|
||||
{
|
||||
uint32_t start_sector; /**< First physical sector of partition. */
|
||||
uint32_t end_sector; /**< End physical sector (exclusive). */
|
||||
uint8_t key[16]; /**< AES-128 partition key. */
|
||||
} WiiPartitionRegion;
|
||||
|
||||
/**
|
||||
* @brief Get the encryption key for a given logical sector (2048-byte).
|
||||
*
|
||||
* Converts the logical sector address to a physical sector address,
|
||||
* then looks up which partition it belongs to and whether it is encrypted.
|
||||
*
|
||||
* @param regions Array of partition regions.
|
||||
* @param region_count Number of partition regions.
|
||||
* @param logical_sector Logical sector address (2048-byte units).
|
||||
* @return Pointer to the 16-byte key if encrypted, NULL if plaintext.
|
||||
*/
|
||||
const uint8_t *wii_get_sector_key(const WiiPartitionRegion *regions, uint32_t region_count,
|
||||
uint64_t logical_sector);
|
||||
|
||||
/**
|
||||
* @brief Check if a logical sector is in an encrypted region.
|
||||
*/
|
||||
bool wii_is_sector_encrypted(const WiiPartitionRegion *regions, uint32_t region_count, uint64_t logical_sector);
|
||||
|
||||
/**
|
||||
* @brief Encrypt a Wii group (0x8000 bytes) from separate hash_block + data.
|
||||
*
|
||||
* The hash block IV is all zeros. The data IV is bytes 0x3D0..0x3DF of the
|
||||
* encrypted hash block output (matching Dolphin's VolumeWii::EncryptBlock).
|
||||
*
|
||||
* @param key 16-byte AES-128 partition key.
|
||||
* @param hash_block 0x400-byte hash block (plaintext input).
|
||||
* @param data_in 0x7C00-byte user data (plaintext input).
|
||||
* @param out 0x8000-byte output buffer (encrypted).
|
||||
*/
|
||||
void wii_encrypt_group(const uint8_t key[16], const uint8_t *hash_block, const uint8_t *data_in, uint8_t *out);
|
||||
|
||||
/**
|
||||
* @brief Decrypt a Wii group (0x8000 bytes) into separate hash_block + data.
|
||||
*
|
||||
* @param key 16-byte AES-128 partition key.
|
||||
* @param in 0x8000-byte encrypted input.
|
||||
* @param hash_block 0x400-byte output for hash block.
|
||||
* @param data_out 0x7C00-byte output for user data.
|
||||
*/
|
||||
void wii_decrypt_group(const uint8_t key[16], const uint8_t *in, uint8_t *hash_block, uint8_t *data_out);
|
||||
|
||||
/**
|
||||
* @brief Serialize a Wii partition key map for storage as a media tag.
|
||||
*
|
||||
* Same format as Wii U partition key map:
|
||||
* [4 bytes] partition_count (uint32 LE)
|
||||
* Per partition (24 bytes): start_sector (LE), end_sector (LE), key[16]
|
||||
*/
|
||||
int32_t wii_serialize_partition_key_map(const WiiPartitionRegion *regions, uint32_t count, uint8_t **out_data,
|
||||
uint32_t *out_len);
|
||||
|
||||
/**
|
||||
* @brief Deserialize a Wii partition key map from a media tag buffer.
|
||||
*/
|
||||
int32_t wii_deserialize_partition_key_map(const uint8_t *data, uint32_t data_len, WiiPartitionRegion **regions,
|
||||
uint32_t *count);
|
||||
|
||||
/**
|
||||
* @brief Lazy initialization: load partition key map from media tags.
|
||||
*
|
||||
* Populates ctx->wii_partition_regions and allocates the group cache.
|
||||
*
|
||||
* @param ctx AaruFormat context.
|
||||
*/
|
||||
void wii_lazy_init(aaruformat_context *ctx);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* LIBAARUFORMAT_NGCW_WII_CRYPTO_H */
|
||||
Reference in New Issue
Block a user