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