-
Notifications
You must be signed in to change notification settings - Fork 33
/
Copy pathlibsm64.c
444 lines (413 loc) · 15.9 KB
/
libsm64.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "libmio0.h"
#include "libsm64.h"
#include "utils.h"
// TODO: make these configurable
#define IN_START_ADDR 0x000D0000
#define OUT_START_ADDR 0x00800000
// MIPS instruction decoding
#define OPCODE(IBUF_) ((IBUF_)[0] & 0xFC)
#define RS(IBUF_) ( (((IBUF_)[0] & 0x3) < 3) | (((IBUF_)[1] & 0xE0) > 5) )
#define RT(IBUF_) ((IBUF_)[1] & 0x1F)
typedef struct
{
unsigned int old; // MIO0 address in original ROM
unsigned int old_end; // ending MIO0 address in original ROM
unsigned int new; // starting MIO0 address in extended ROM
unsigned int new_end; // ending MIO0 address in extended ROM
unsigned int addr; // ASM address for referenced pointer
unsigned int a1_addiu; // ASM offset for ADDIU for A1
unsigned char command; // command type: 0x1A or 0x18 (or 0xFF for ASM)
} ptr_t;
// find a pointer in the list and return index
// ptr: address to find in table old values
// table: list of addresses to MIO0 data
// count: number of addresses in table
// returns index in table if found, -1 otherwise
static int find_ptr(unsigned int ptr, ptr_t table[], int count)
{
int i;
for (i = 0; i < count; i++) {
if (ptr == table[i].old) {
return i;
}
}
return -1;
}
// find locations of existing MIO0 data
// buf: buffer containing SM64 data
// length: length of buf
// table: table to store MIO0 addresses in
// returns number of MIO0 files stored in table old values
static int find_mio0(unsigned char *buf, unsigned int length, ptr_t table[])
{
unsigned int addr;
int count = 0;
// MIO0 data is on 16-byte boundaries
for (addr = IN_START_ADDR; addr < length; addr += 16) {
if (!memcmp(&buf[addr], "MIO0", 4)) {
table[count].old = addr;
count++;
}
}
return count;
}
// find pointers to MIO0 files and stores command type
// buf: buffer containing SM64 data
// length: length of buf
// table: list of addresses to MIO0 data
// count: number of addresses in table
static void find_pointers(unsigned char *buf, unsigned int length, ptr_t table[], int count)
{
unsigned int addr;
unsigned int ptr;
int idx;
for (addr = IN_START_ADDR; addr < length; addr += 4) {
if ((buf[addr] == 0x18 || buf[addr] == 0x1A) && buf[addr+1] == 0x0C && buf[addr+2] == 0x00) {
ptr = read_u32_be(&buf[addr+4]);
idx = find_ptr(ptr, table, count);
if (idx >= 0) {
table[idx].command = buf[addr];
table[idx].old_end = read_u32_be(&buf[addr+8]);
}
}
}
}
static unsigned int la2int(unsigned char *buf, unsigned int lui, unsigned int addiu)
{
unsigned short addr_low, addr_high;
addr_high = read_u16_be(&buf[lui + 0x2]);
addr_low = read_u16_be(&buf[addiu + 0x2]);
// ADDIU sign extends which causes the encoded high val to be +1 if low MSb is set
if (addr_low & 0x8000) {
addr_high--;
}
return (addr_high << 16) | addr_low;
}
// find references to the MIO0 blocks in ASM and store type
// buf: buffer containing SM64 data
// length: length of buf
// table: list of addresses to MIO0 data
// count: number of addresses in table
static void find_asm_pointers(unsigned char *buf, ptr_t table[], int count)
{
// find the ASM references
// looking for some code that follows one of the below patterns:
// lui a1, start_upper lui a1, start_upper
// lui a2, end_upper lui a2, end_upper
// addiu a2, a2, end_lower addiu a2, a2, end_lower
// addiu a1, a1, start_lower jal function
// jal function addiu a1, a1, start_lower
unsigned int addr;
unsigned int ptr;
unsigned int end;
int idx;
for (addr = 0; addr < IN_START_ADDR; addr += 4) {
if (OPCODE(&buf[addr]) == 0x3C && OPCODE(&buf[addr+4]) == 0x3C && OPCODE(&buf[addr+8]) == 0x24) {
unsigned int a1_addiu = 0;
if (OPCODE(&buf[addr+0xc]) == 0x24) {
a1_addiu = 0xc;
} else if (OPCODE(&buf[addr+0x10]) == 0x24) {
a1_addiu = 0x10;
}
if (a1_addiu) {
if ( (RT(&buf[addr]) == RT(&buf[addr+a1_addiu]))
&& (RT(&buf[addr+4]) == RT(&buf[addr+8])) ) {
ptr = la2int(buf, addr, addr + a1_addiu);
end = la2int(buf, addr + 4, addr + 0x8);
idx = find_ptr(ptr, table, count);
if (idx >= 0) {
INFO("Found ASM reference to %X at %X\n", ptr, addr);
table[idx].command = 0xFF;
table[idx].addr = addr;
table[idx].new_end = end;
table[idx].a1_addiu = a1_addiu;
}
}
}
}
}
}
// adjust pointers to from old to new locations
// buf: buffer containing SM64 data
// length: length of buf
// table: list of addresses to MIO0 data
// count: number of addresses in table
static void sm64_adjust_pointers(unsigned char *buf, unsigned int length, ptr_t table[], int count)
{
unsigned int addr;
unsigned int old_ptr;
int idx;
for (addr = IN_START_ADDR; addr < length; addr += 4) {
if ((buf[addr] == 0x17 || buf[addr] == 0x18 || buf[addr] == 0x1A) && buf[addr+1] == 0x0C && buf[addr+2] < 0x02) {
old_ptr = read_u32_be(&buf[addr+4]);
idx = find_ptr(old_ptr, table, count);
if (idx >= 0) {
INFO("Old pointer at %X = ", addr);
INFO_HEX(&buf[addr], 12);
INFO("\n");
write_u32_be(&buf[addr+4], table[idx].new);
write_u32_be(&buf[addr+8], table[idx].new_end);
if (buf[addr] != table[idx].command) {
buf[addr] = table[idx].command;
}
INFO("NEW pointer at %X = ", addr);
INFO_HEX(&buf[addr], 12);
INFO("\n");
}
}
}
}
// adjust 'pointer' encoded in ASM LUI and ADDIU instructions
static void sm64_adjust_asm(unsigned char *buf, ptr_t table[], int count)
{
unsigned int addr;
int i;
unsigned short addr_low, addr_high;
for (i = 0; i < count; i++) {
if (table[i].command == 0xFF) {
addr = table[i].addr;
INFO("Old ASM reference at %X = ", addr);
INFO_HEX(&buf[addr], 0x14);
INFO("\n");
addr_low = table[i].new & 0xFFFF;
addr_high = (table[i].new >> 16) & 0xFFFF;
// ADDIU sign extends which causes the summed high to be 1 less if low MSb is set
if (addr_low & 0x8000) {
addr_high++;
}
write_u16_be(&buf[addr + 0x2], addr_high);
write_u16_be(&buf[addr + table[i].a1_addiu+2], addr_low);
addr_low = table[i].new_end & 0xFFFF;
addr_high = (table[i].new_end >> 16) & 0xFFFF;
if (addr_low & 0x8000) {
addr_high++;
}
write_u16_be(&buf[addr + 0x6], addr_high);
write_u16_be(&buf[addr + 0xa], addr_low);
INFO("NEW ASM reference at %X = ", addr);
INFO_HEX(&buf[addr], 0x14);
INFO(" [%06X - %06X]\n", table[i].new, table[i].new_end);
}
}
}
// compute N64 ROM checksums
// buf: buffer with extended SM64 data
// cksum: two element array to write CRC1 and CRC2 to
// TODO: this could be hand optimized
static void sm64_calc_checksums(unsigned char *buf, unsigned int cksum[]) {
unsigned int t0, t1, t2, t3, t4, t5, t6, t7, t8, t9;
unsigned int s0, s6;
unsigned int a0, a1, a2, a3, at;
unsigned int lo;
unsigned int v0, v1;
unsigned int ra;
// derived from the SM64 boot code
s6 = 0x3f;
a0 = 0x1000; // 59c: 8d640008 lw a0,8(t3)
a1 = s6; // 5a0: 02c02825 move a1,s6
at = 0x5d588b65; // 5a4: 3c015d58 lui at,0x5d58
// 5a8: 34218b65 ori at,at,0x8b65
lo = a1 * at; // 5ac: 00a10019 multu a1,at 16 F8CA 4DDB
ra = 0x100000; // 5bc: 3c1f0010 lui ra,0x10
v1 = 0; // 5c0: 00001825 move v1,zero
t0 = 0; // 5c4: 00004025 move t0,zero
t1 = a0; // 5c8: 00804825 move t1,a0
t5 = 32; // 5cc: 240d0020 li t5,32
v0 = lo; // 5d0: 00001012 mflo v0
v0++; // 5d4: 24420001 addiu v0,v0,1
a3 = v0; // 5d8: 00403825 move a3,v0
t2 = v0; // 5dc: 00405025 move t2,v0
t3 = v0; // 5e0: 00405825 move t3,v0
s0 = v0; // 5e4: 00408025 move s0,v0
a2 = v0; // 5e8: 00403025 move a2,v0
t4 = v0; // 5ec: 00406025 move t4,v0
do {
v0 = read_u32_be(&buf[t1]); // 5f0: 8d220000 lw v0,0(t1)
v1 = a3 + v0; // 5f4: 00e21821 addu v1,a3,v0
at = (v1 < a3); // 5f8: 0067082b sltu at,v1,a3
a1 = v1; // 600: 00602825 move a1,v1 branch delay slot
if (at) { // 5fc: 10200002 beqz at,0x608
t2++; // 604: 254a0001 addiu t2,t2,1
}
v1 = v0 & 0x1F; // 608: 3043001f andi v1,v0,0x1f
t7 = t5 - v1; // 60c: 01a37823 subu t7,t5,v1
t8 = v0 >> t7; // 610: 01e2c006 srlv t8,v0,t7
t6 = v0 << v1; // 614: 00627004 sllv t6,v0,v1
a0 = t6 | t8; // 618: 01d82025 or a0,t6,t8
at = (a2 < v0); // 61c: 00c2082b sltu at,a2,v0
a3 = a1; // 620: 00a03825 move a3,a1
t3 ^= v0; // 624: 01625826 xor t3,t3,v0
s0 += a0; // 62c: 02048021 addu s0,s0,a0 branch delay slot
if (at) { // 628: 10200004 beqz at,0x63c
t9 = a3 ^ v0; // 630: 00e2c826 xor t9,a3,v0
// 634: 10000002 b 0x640
a2 ^= t9; // 638: 03263026 xor a2,t9,a2 branch delay
} else {
a2 ^= a0; // 63c: 00c43026 xor a2,a2,a0
}
t0 += 4; // 640: 25080004 addiu t0,t0,4
t7 = v0 ^ s0; // 644: 00507826 xor t7,v0,s0
t1 += 4; // 648: 25290004 addiu t1,t1,4
t4 += t7; // 650: 01ec6021 addu t4,t7,t4 branch delay
} while (t0 != ra); // 64c: 151fffe8 bne t0,ra,0x5f0
t6 = a3 ^ t2; // 654: 00ea7026 xor t6,a3,t2
a3 = t6 ^ t3; // 658: 01cb3826 xor a3,t6,t3
t8 = s0 ^ a2; // 65c: 0206c026 xor t8,s0,a2
s0 = t8 ^ t4; // 660: 030c8026 xor s0,t8,t4
cksum[0] = a3;
cksum[1] = s0;
}
rom_type sm64_rom_type(unsigned char *buf, unsigned int length)
{
const unsigned char bs[] = {0x37, 0x80, 0x40, 0x12};
const unsigned char be[] = {0x80, 0x37, 0x12, 0x40};
const unsigned char le[] = {0x40, 0x12, 0x37, 0x80};
if (!memcmp(buf, bs, sizeof(bs)) && length == (8*MB)) {
return ROM_SM64_BS;
}
if (!memcmp(buf, bs, sizeof(le)) && length == (8*MB)) {
return ROM_SM64_LE;
}
if (!memcmp(buf, be, sizeof(be))) {
if (length > 8*MB) {
return ROM_SM64_BE_EXT;
} else if (length > 7*MB) {
return ROM_SM64_BE;
}
}
return ROM_INVALID;
}
rom_version sm64_rom_version(unsigned char *buf)
{
typedef struct
{
const unsigned char cksum1[4];
const rom_version version;
} version_entry;
const version_entry version_table[] =
{
{ {0x63, 0x5a, 0x2b, 0xff}, VERSION_SM64_U},
{ {0xa0, 0x3c, 0xf0, 0x36}, VERSION_SM64_E},
{ {0x4e, 0xaa, 0x3d, 0x0e}, VERSION_SM64_J},
{ {0xd6, 0xfb, 0xa4, 0xa8}, VERSION_SM64_SHINDOU},
{ {0x00, 0x00, 0x00, 0x00}, VERSION_SM64_IQUE},
};
for (unsigned int i = 0; i < DIM(version_table); i++) {
if (!memcmp(&buf[0x10], version_table[i].cksum1, 4)) {
return version_table[i].version;
}
}
return VERSION_UNKNOWN;
}
void sm64_decompress_mio0(const sm64_config *config,
unsigned char *in_buf,
unsigned int in_length,
unsigned char *out_buf)
{
#define MAX_PTRS 128
#define COMPRESSED_LENGTH 2
mio0_header_t head;
int bit_length;
int move_offset;
unsigned int in_addr;
unsigned int out_addr = OUT_START_ADDR;
unsigned int align_add = config->alignment - 1;
unsigned int align_mask = ~align_add;
ptr_t ptr_table[MAX_PTRS];
int ptr_count;
int i;
// find MIO0 locations and pointers
ptr_count = find_mio0(in_buf, in_length, ptr_table);
find_pointers(in_buf, in_length, ptr_table, ptr_count);
find_asm_pointers(in_buf, ptr_table, ptr_count);
// extract each MIO0 block and prepend fake MIO0 header for 0x1A command and ASM references
for (i = 0; i < ptr_count; i++) {
in_addr = ptr_table[i].old;
if (!memcmp(&in_buf[in_addr], "MIO0", 4)) {
unsigned int end;
int length;
int is_mio0 = 0;
// align output address
out_addr = (out_addr + align_add) & align_mask;
length = mio0_decode(&in_buf[in_addr], &out_buf[out_addr], &end);
if (length > 0) {
// dump MIO0 data and decompressed data to file
if (config->dump) {
char filename[FILENAME_MAX];
sprintf(filename, MIO0_DIR "/%08X.mio", in_addr);
write_file(filename, &in_buf[in_addr], end);
sprintf(filename, MIO0_DIR "/%08X", in_addr);
write_file(filename, &out_buf[out_addr], length);
}
// 0x1A commands and ASM references need fake MIO0 header
// relocate data and add MIO0 header with all uncompressed data
if (ptr_table[i].command == 0x1A || ptr_table[i].command == 0xFF) {
bit_length = (length + 7) / 8 + 2;
move_offset = MIO0_HEADER_LENGTH + bit_length + COMPRESSED_LENGTH;
memmove(&out_buf[out_addr + move_offset], &out_buf[out_addr], length);
head.dest_size = length;
head.comp_offset = move_offset - COMPRESSED_LENGTH;
head.uncomp_offset = move_offset;
mio0_encode_header(&out_buf[out_addr], &head);
memset(&out_buf[out_addr + MIO0_HEADER_LENGTH], 0xFF, head.comp_offset - MIO0_HEADER_LENGTH);
memset(&out_buf[out_addr + head.comp_offset], 0x0, 2);
length += head.uncomp_offset;
is_mio0 = 1;
} else if (ptr_table[i].command == 0x18) {
// 0x18 commands become 0x17
ptr_table[i].command = 0x17;
}
// use output from decoder to find end of ASM referenced MIO0 blocks
if (ptr_table[i].old_end == 0x00) {
ptr_table[i].old_end = in_addr + end;
}
INFO("MIO0 file %08X-%08X decompressed to %08X-%08X as raw data%s\n",
in_addr, ptr_table[i].old_end, out_addr, out_addr + length,
is_mio0 ? " with a MIO0 header" : "");
if (config->fill) {
INFO("Filling old MIO0 with 0x01 from %X length %X\n", in_addr, end);
memset(&out_buf[in_addr], 0x01, end);
}
// keep track of new pointers
ptr_table[i].new = out_addr;
ptr_table[i].new_end = out_addr + length;
out_addr += length + config->padding;
} else {
ERROR("Error decoding MIO0 block at %X\n", in_addr);
}
}
}
INFO("Ending offset: %X\n", out_addr);
// adjust pointers and ASM pointers to new values
sm64_adjust_pointers(out_buf, in_length, ptr_table, ptr_count);
sm64_adjust_asm(out_buf, ptr_table, ptr_count);
}
void sm64_update_checksums(unsigned char *buf)
{
unsigned int cksum_offsets[] = {0x10, 0x14};
unsigned int read_cksum[2];
unsigned int calc_cksum[2];
int i;
// assume CIC-NUS-6102
INFO("BootChip: CIC-NUS-6102\n");
// calculate new N64 header checksum
sm64_calc_checksums(buf, calc_cksum);
// mimic the n64sums output
for (i = 0; i < 2; i++) {
read_cksum[i] = read_u32_be(&buf[cksum_offsets[i]]);
INFO("CRC%d: 0x%08X ", i+1, read_cksum[i]);
INFO("Calculated: 0x%08X ", calc_cksum[i]);
if (calc_cksum[i] == read_cksum[i]) {
INFO("(Good)\n");
} else {
INFO("(Bad)\n");
}
}
// write checksums into header
INFO("Writing back calculated Checksum\n");
write_u32_be(&buf[cksum_offsets[0]], calc_cksum[0]);
write_u32_be(&buf[cksum_offsets[1]], calc_cksum[1]);
}