libaaruformat 1.0
Aaru Data Preservation Suite - Format Library
Loading...
Searching...
No Matches
wii_crypto.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 Wii disc encryption: partition key map, group encrypt/decrypt.
19 */
20
21#include <stdint.h>
22#include <stdlib.h>
23#include <string.h>
24
25#include <aaruformat.h>
26
27#include "../lib/aes128.h"
28#include "wii_crypto.h"
29
30/* ---- Little-endian helpers ---- */
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 void write_le32(uint8_t *p, uint32_t v)
36{
37 p[0] = (uint8_t)(v & 0xFF);
38 p[1] = (uint8_t)((v >> 8) & 0xFF);
39 p[2] = (uint8_t)((v >> 16) & 0xFF);
40 p[3] = (uint8_t)((v >> 24) & 0xFF);
41}
42
43/* ---- Key lookup ---- */
44
45const uint8_t *wii_get_sector_key(const WiiPartitionRegion *regions, uint32_t region_count, uint64_t logical_sector)
46{
47 if(regions == NULL || region_count == 0) return NULL;
48
49 /* Convert logical (2048-byte) sector to physical (0x8000-byte) group */
50 uint64_t phys_group = logical_sector / WII_LOGICAL_PER_GROUP;
51
52 for(uint32_t i = 0; i < region_count; i++)
53 {
54 if(phys_group >= regions[i].start_sector && phys_group < regions[i].end_sector)
55 {
56 /* For Wii, data_offset already skips the partition header.
57 * ALL groups in [start_sector, end_sector) are encrypted. */
58 return regions[i].key;
59 }
60 }
61
62 /* Outside any known partition — plaintext */
63 return NULL;
64}
65
66bool wii_is_sector_encrypted(const WiiPartitionRegion *regions, uint32_t region_count, uint64_t logical_sector)
67{ return wii_get_sector_key(regions, region_count, logical_sector) != NULL; }
68
69/* ---- Group encrypt/decrypt ---- */
70
71void wii_encrypt_group(const uint8_t key[16], const uint8_t *hash_block, const uint8_t *data_in, uint8_t *out)
72{
73 /* Hash block: first 0x400 bytes, IV = all zeros */
74 uint8_t iv[16];
75 memset(iv, 0, sizeof(iv));
76 memcpy(out, hash_block, WII_GROUP_HASH_SIZE);
78
79 /* Data block: next 0x7C00 bytes.
80 * IV = bytes 0x3D0..0x3DF of the ENCRYPTED hash output (just written to out). */
81 uint8_t data_iv[16];
82 memcpy(data_iv, out + 0x3D0, 16);
83 memcpy(out + WII_GROUP_HASH_SIZE, data_in, WII_GROUP_DATA_SIZE);
85}
86
87void wii_decrypt_group(const uint8_t key[16], const uint8_t *in, uint8_t *hash_block, uint8_t *data_out)
88{
89 /* Hash block: first 0x400 bytes, IV = all zeros */
90 uint8_t iv[16];
91 memset(iv, 0, sizeof(iv));
92 memcpy(hash_block, in, WII_GROUP_HASH_SIZE);
93 aes128_cbc_decrypt(key, iv, hash_block, WII_GROUP_HASH_SIZE);
94
95 /* Data block: next 0x7C00 bytes.
96 * IV = bytes 0x3D0..0x3DF of the ENCRYPTED input (not the decrypted hash block). */
97 uint8_t data_iv[16];
98 memcpy(data_iv, in + 0x3D0, 16);
99 memcpy(data_out, in + WII_GROUP_HASH_SIZE, WII_GROUP_DATA_SIZE);
100 aes128_cbc_decrypt(key, data_iv, data_out, WII_GROUP_DATA_SIZE);
101}
102
103/* ---- Serialization (same format as Wii U) ---- */
104
105int32_t wii_serialize_partition_key_map(const WiiPartitionRegion *regions, uint32_t count, uint8_t **out_data,
106 uint32_t *out_len)
107{
108 if(out_data == NULL || out_len == NULL) return -1;
109
110 if(count > WII_MAX_PARTITIONS) return -3;
111
112 uint32_t size = 4 + count * 24;
113 uint8_t *buf = (uint8_t *)malloc(size);
114
115 if(buf == NULL) return -4;
116
117 write_le32(buf, count);
118
119 for(uint32_t i = 0; i < count; i++)
120 {
121 uint32_t offset = 4 + i * 24;
122 write_le32(buf + offset, regions[i].start_sector);
123 write_le32(buf + offset + 4, regions[i].end_sector);
124 memcpy(buf + offset + 8, regions[i].key, 16);
125 }
126
127 *out_data = buf;
128 *out_len = size;
129 return 0;
130}
131
132int32_t wii_deserialize_partition_key_map(const uint8_t *data, uint32_t data_len, WiiPartitionRegion **regions,
133 uint32_t *count)
134{
135 if(data == NULL || regions == NULL || count == NULL) return -1;
136
137 if(data_len < 4) return -2;
138
139 uint32_t region_count = read_le32(data);
140
141 if(region_count > WII_MAX_PARTITIONS) return -3;
142
143 if(region_count == 0)
144 {
145 *regions = NULL;
146 *count = 0;
147 return 0;
148 }
149
150 uint32_t required = 4 + region_count * 24;
151
152 if(data_len < required) return -2;
153
154 WiiPartitionRegion *r = (WiiPartitionRegion *)malloc(region_count * sizeof(WiiPartitionRegion));
155
156 if(r == NULL) return -4;
157
158 for(uint32_t i = 0; i < region_count; i++)
159 {
160 uint32_t offset = 4 + i * 24;
161 r[i].start_sector = read_le32(data + offset);
162 r[i].end_sector = read_le32(data + offset + 4);
163 memcpy(r[i].key, data + offset + 8, 16);
164
165 if(r[i].start_sector >= r[i].end_sector)
166 {
167 memset(r, 0, region_count * sizeof(WiiPartitionRegion));
168 free(r);
169 return -5;
170 }
171 }
172
173 *regions = r;
174 *count = region_count;
175 return 0;
176}
177
178/* ---- Lazy initialization ---- */
179
181{
182 if(ctx == NULL) return;
183
184 /* Read and deserialize partition key map from media tags */
185 if(ctx->wii_partition_regions == NULL)
186 {
187 mediaTagEntry *item = NULL;
188 int32_t tag = kMediaTagWiiPartitionKeyMap;
189 HASH_FIND_INT(ctx->mediaTags, &tag, item);
190
191 if(item != NULL && item->length >= 4)
192 {
193 WiiPartitionRegion *regions = NULL;
194 uint32_t count = 0;
195
196 if(wii_deserialize_partition_key_map(item->data, item->length, &regions, &count) == 0)
197 {
198 ctx->wii_partition_regions = regions;
199 ctx->wii_partition_region_count = count;
200 }
201 }
202 }
203
204 /* Allocate the encrypted group cache if needed */
205 if(ctx->wii_encrypted_group_cache == NULL)
206 {
207 ctx->wii_encrypted_group_cache = (uint8_t *)malloc(WII_GROUP_SIZE);
208 ctx->wii_cache_valid = false;
209 }
210}
void aes128_cbc_decrypt(const uint8_t key[16], const uint8_t iv[16], uint8_t *data, uint32_t length)
AES-128 CBC decrypt data in-place.
Definition aes128.c:290
void aes128_cbc_encrypt(const uint8_t key[16], const uint8_t iv[16], uint8_t *data, uint32_t length)
AES-128 CBC encrypt data in-place.
Definition aes128.c:274
@ kMediaTagWiiPartitionKeyMap
Nintendo Wii partition-to-key mapping with regions.
Definition aaru.h:1086
A Wii partition region entry (in-memory representation).
Definition wii_crypto.h:52
uint32_t start_sector
First physical sector of partition.
Definition wii_crypto.h:53
uint8_t key[16]
AES-128 partition key.
Definition wii_crypto.h:55
uint32_t end_sector
End physical sector (exclusive).
Definition wii_crypto.h:54
Master context representing an open or in‑creation Aaru image.
Definition context.h:175
mediaTagEntry * mediaTags
Hash table of extra media tags (uthash root).
Definition context.h:267
uint32_t wii_partition_region_count
Number of partition regions.
Definition context.h:371
void * wii_partition_regions
Parsed WiiPartitionRegion array, NULL if not loaded.
Definition context.h:370
bool wii_cache_valid
Whether the encrypted group cache is valid.
Definition context.h:375
uint8_t * wii_encrypted_group_cache
Cached re-encrypted 0x8000-byte group.
Definition context.h:373
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
const uint8_t * wii_get_sector_key(const WiiPartitionRegion *regions, uint32_t region_count, uint64_t logical_sector)
Get the encryption key for a given logical sector (2048-byte).
Definition wii_crypto.c:45
int32_t wii_deserialize_partition_key_map(const uint8_t *data, uint32_t data_len, WiiPartitionRegion **regions, uint32_t *count)
Deserialize a Wii partition key map from a media tag buffer.
Definition wii_crypto.c:132
static uint32_t read_le32(const uint8_t *p)
Definition wii_crypto.c:32
static void write_le32(uint8_t *p, uint32_t v)
Definition wii_crypto.c:35
int32_t wii_serialize_partition_key_map(const WiiPartitionRegion *regions, uint32_t count, uint8_t **out_data, uint32_t *out_len)
Serialize a Wii partition key map for storage as a media tag.
Definition wii_crypto.c:105
void wii_encrypt_group(const uint8_t key[16], const uint8_t *hash_block, const uint8_t *data_in, uint8_t *out)
Encrypt a Wii group (0x8000 bytes) from separate hash_block + data.
Definition wii_crypto.c:71
bool wii_is_sector_encrypted(const WiiPartitionRegion *regions, uint32_t region_count, uint64_t logical_sector)
Check if a logical sector is in an encrypted region.
Definition wii_crypto.c:66
void wii_decrypt_group(const uint8_t key[16], const uint8_t *in, uint8_t *hash_block, uint8_t *data_out)
Decrypt a Wii group (0x8000 bytes) into separate hash_block + data.
Definition wii_crypto.c:87
void wii_lazy_init(aaruformat_context *ctx)
Lazy initialization: load partition key map from media tags.
Definition wii_crypto.c:180
#define WII_GROUP_SIZE
Wii physical group size (32 KiB).
Definition wii_crypto.h:38
#define WII_MAX_PARTITIONS
Maximum number of partitions supported.
Definition wii_crypto.h:43
#define WII_GROUP_DATA_SIZE
User data size within a group (31 KiB).
Definition wii_crypto.h:40
#define WII_GROUP_HASH_SIZE
Hash block size within a group (1 KiB).
Definition wii_crypto.h:39
#define WII_LOGICAL_PER_GROUP
Number of 2048-byte logical sectors per group.
Definition wii_crypto.h:41