libaaruformat 1.0
Aaru Data Preservation Suite - Format Library
Loading...
Searching...
No Matches
wiiu_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 * Wii U disc encryption: partition key map, encrypt/decrypt, serialization.
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 "wiiu_crypto.h"
29
30/* Read a little-endian uint32 from a byte buffer. */
31static uint32_t read_le32(const uint8_t *p)
32{ return (uint32_t)p[0] | ((uint32_t)p[1] << 8) | ((uint32_t)p[2] << 16) | ((uint32_t)p[3] << 24); }
33
34/* Write a little-endian uint32 to a byte buffer. */
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
43const uint8_t *wiiu_get_sector_key(const WiiuPartitionRegion *regions, uint32_t region_count, uint64_t logical_sector)
44{
45 if(regions == NULL || region_count == 0) return NULL;
46
47 /* Convert logical (2048-byte) sector to physical (0x8000-byte) sector */
48 uint64_t phys_sector = logical_sector / WIIU_LOGICAL_PER_PHYSICAL;
49
50 /* Disc header sectors (0-2) are always plaintext */
51 if(phys_sector < WIIU_HEADER_PHYSICAL_SECTORS) return NULL;
52
53 for(uint32_t i = 0; i < region_count; i++)
54 {
55 if(phys_sector >= regions[i].start_sector && phys_sector < regions[i].end_sector)
56 {
57 /* Partition header sector is plaintext */
58 if(phys_sector == regions[i].start_sector) return NULL;
59
60 /* Encrypted with this partition's key */
61 return regions[i].key;
62 }
63 }
64
65 /* Outside any known partition — treat as plaintext */
66 return NULL;
67}
68
69bool wiiu_is_sector_encrypted(const WiiuPartitionRegion *regions, uint32_t region_count, uint64_t logical_sector)
70{ return wiiu_get_sector_key(regions, region_count, logical_sector) != NULL; }
71
72void wiiu_encrypt_physical_sector(const uint8_t key[16], uint8_t *data, uint32_t length)
73{
74 uint8_t iv[16];
75 memset(iv, 0, sizeof(iv));
76 aes128_cbc_encrypt(key, iv, data, length);
77}
78
79void wiiu_decrypt_physical_sector(const uint8_t key[16], uint8_t *data, uint32_t length)
80{
81 uint8_t iv[16];
82 memset(iv, 0, sizeof(iv));
83 aes128_cbc_decrypt(key, iv, data, length);
84}
85
86int32_t wiiu_serialize_partition_key_map(const WiiuPartitionRegion *regions, uint32_t count, uint8_t **out_data,
87 uint32_t *out_len)
88{
89 if(out_data == NULL || out_len == NULL) return -1;
90 if(count > WIIU_MAX_PARTITIONS) return -3;
91
92 /* 4 bytes for count + 24 bytes per entry (4 start + 4 end + 16 key) */
93 uint32_t size = 4 + count * 24;
94 uint8_t *buf = (uint8_t *)malloc(size);
95 if(buf == NULL) return -4;
96
97 write_le32(buf, count);
98
99 for(uint32_t i = 0; i < count; i++)
100 {
101 uint32_t offset = 4 + i * 24;
102 write_le32(buf + offset, regions[i].start_sector);
103 write_le32(buf + offset + 4, regions[i].end_sector);
104 memcpy(buf + offset + 8, regions[i].key, 16);
105 }
106
107 *out_data = buf;
108 *out_len = size;
109 return 0;
110}
111
112int32_t wiiu_deserialize_partition_key_map(const uint8_t *data, uint32_t data_len, WiiuPartitionRegion **regions,
113 uint32_t *count)
114{
115 if(data == NULL || regions == NULL || count == NULL) return -1;
116 if(data_len < 4) return -2;
117
118 uint32_t region_count = read_le32(data);
119
120 if(region_count > WIIU_MAX_PARTITIONS) return -3;
121
122 if(region_count == 0)
123 {
124 *regions = NULL;
125 *count = 0;
126 return 0;
127 }
128
129 uint32_t required = 4 + region_count * 24;
130 if(data_len < required) return -2;
131
132 WiiuPartitionRegion *r = (WiiuPartitionRegion *)malloc(region_count * sizeof(WiiuPartitionRegion));
133 if(r == NULL) return -4;
134
135 for(uint32_t i = 0; i < region_count; i++)
136 {
137 uint32_t offset = 4 + i * 24;
138 r[i].start_sector = read_le32(data + offset);
139 r[i].end_sector = read_le32(data + offset + 4);
140 memcpy(r[i].key, data + offset + 8, 16);
141
142 if(r[i].start_sector >= r[i].end_sector)
143 {
144 memset(r, 0, region_count * sizeof(WiiuPartitionRegion));
145 free(r);
146 return -5;
147 }
148 }
149
150 *regions = r;
151 *count = region_count;
152 return 0;
153}
154
156{
157 if(ctx == NULL) return;
158
159 /* Read disc key from media tags */
160 if(ctx->wiiu_disc_key == NULL)
161 {
162 mediaTagEntry *item = NULL;
163 int32_t tag = kMediaTagWiiUDiscKey;
164 HASH_FIND_INT(ctx->mediaTags, &tag, item);
165
166 if(item != NULL && item->length == 16)
167 {
168 ctx->wiiu_disc_key = (uint8_t *)malloc(16);
169
170 if(ctx->wiiu_disc_key != NULL) memcpy(ctx->wiiu_disc_key, item->data, 16);
171 }
172 }
173
174 /* Read and deserialize partition key map from media tags */
175 if(ctx->wiiu_partition_regions == NULL)
176 {
177 mediaTagEntry *item = NULL;
178 int32_t tag = kMediaTagWiiUPartitionKeyMap;
179 HASH_FIND_INT(ctx->mediaTags, &tag, item);
180
181 if(item != NULL && item->length >= 4)
182 {
183 WiiuPartitionRegion *regions = NULL;
184 uint32_t count = 0;
185
186 if(wiiu_deserialize_partition_key_map(item->data, item->length, &regions, &count) == 0)
187 {
188 ctx->wiiu_partition_regions = regions;
189 ctx->wiiu_partition_region_count = count;
190 }
191 }
192 }
193
194 /* Allocate the encrypted block cache if needed */
195 if(ctx->wiiu_encrypted_block_cache == NULL)
196 {
197 ctx->wiiu_encrypted_block_cache = (uint8_t *)malloc(WIIU_CRYPTO_SECTOR_SIZE);
198 ctx->wiiu_cache_valid = false;
199 }
200}
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
@ kMediaTagWiiUDiscKey
Nintendo Wii U disc key (16 bytes, from non-readable disc area).
Definition aaru.h:1079
@ kMediaTagWiiUPartitionKeyMap
Nintendo Wii U partition-to-key mapping with regions.
Definition aaru.h:1085
A Wii U partition region entry (in-memory representation).
Definition wiiu_crypto.h:49
uint8_t key[16]
AES-128 key for encrypted sectors in this partition.
Definition wiiu_crypto.h:52
uint32_t start_sector
First physical sector of partition (plaintext header).
Definition wiiu_crypto.h:50
uint32_t end_sector
End physical sector (exclusive).
Definition wiiu_crypto.h:51
Master context representing an open or in‑creation Aaru image.
Definition context.h:175
void * wiiu_partition_regions
Parsed WiiuPartitionRegion array, NULL if not loaded.
Definition context.h:355
uint32_t wiiu_partition_region_count
Number of partition regions.
Definition context.h:356
uint8_t * wiiu_encrypted_block_cache
Cached re-encrypted 0x8000-byte physical sector.
Definition context.h:358
mediaTagEntry * mediaTags
Hash table of extra media tags (uthash root).
Definition context.h:267
uint8_t * wiiu_disc_key
Cached disc key (16 bytes), NULL if not loaded.
Definition context.h:354
bool wiiu_cache_valid
Whether the encrypted block cache is valid.
Definition context.h:360
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
static uint32_t read_le32(const uint8_t *p)
Definition wiiu_crypto.c:31
static void write_le32(uint8_t *p, uint32_t v)
Definition wiiu_crypto.c:35
void wiiu_encrypt_physical_sector(const uint8_t key[16], uint8_t *data, uint32_t length)
Encrypt a full 0x8000-byte physical sector in-place.
Definition wiiu_crypto.c:72
int32_t wiiu_deserialize_partition_key_map(const uint8_t *data, uint32_t data_len, WiiuPartitionRegion **regions, uint32_t *count)
Deserialize a partition key map from a media tag buffer.
void wiiu_lazy_init(aaruformat_context *ctx)
Lazy initialization: load disc key and partition key map from media tags.
void wiiu_decrypt_physical_sector(const uint8_t key[16], uint8_t *data, uint32_t length)
Decrypt a full 0x8000-byte physical sector in-place.
Definition wiiu_crypto.c:79
int32_t wiiu_serialize_partition_key_map(const WiiuPartitionRegion *regions, uint32_t count, uint8_t **out_data, uint32_t *out_len)
Serialize a partition key map for storage as a media tag.
Definition wiiu_crypto.c:86
const uint8_t * wiiu_get_sector_key(const WiiuPartitionRegion *regions, uint32_t region_count, uint64_t logical_sector)
Get the encryption key for a given logical sector (2048-byte).
Definition wiiu_crypto.c:43
bool wiiu_is_sector_encrypted(const WiiuPartitionRegion *regions, uint32_t region_count, uint64_t logical_sector)
Check if a logical sector (2048-byte) is in an encrypted region.
Definition wiiu_crypto.c:69
#define WIIU_CRYPTO_SECTOR_SIZE
Wii U physical sector size (32 KiB).
Definition wiiu_crypto.h:35
#define WIIU_HEADER_PHYSICAL_SECTORS
Disc header occupies physical sectors 0-2 (plaintext).
Definition wiiu_crypto.h:37
#define WIIU_LOGICAL_PER_PHYSICAL
Number of 2048-byte logical sectors per physical sector.
Definition wiiu_crypto.h:36
#define WIIU_MAX_PARTITIONS
Maximum number of partitions supported.
Definition wiiu_crypto.h:38