Update README.md
[sm64pc.git] / tools / libsm64.c
blob575b8c7f6cbc2608652cb9bf6ce10b833e3a8d62
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <string.h>
5 #include "libmio0.h"
6 #include "libsm64.h"
7 #include "utils.h"
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)
18 typedef struct
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)
27 } ptr_t;
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)
36 int i;
37 for (i = 0; i < count; i++) {
38 if (ptr == table[i].old) {
39 return i;
42 return -1;
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[])
52 unsigned int addr;
53 int count = 0;
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;
59 count++;
62 return count;
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)
72 unsigned int addr;
73 unsigned int ptr;
74 int idx;
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);
80 if (idx >= 0) {
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) {
95 addr_high--;
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
114 unsigned int addr;
115 unsigned int ptr;
116 unsigned int end;
117 int idx;
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) {
122 a1_addiu = 0xc;
123 } else if (OPCODE(&buf[addr+0x10]) == 0x24) {
124 a1_addiu = 0x10;
126 if (a1_addiu) {
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);
132 if (idx >= 0) {
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)
152 unsigned int addr;
153 unsigned int old_ptr;
154 int idx;
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);
159 if (idx >= 0) {
160 INFO("Old pointer at %X = ", addr);
161 INFO_HEX(&buf[addr], 12);
162 INFO("\n");
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);
170 INFO("\n");
176 // adjust 'pointer' encoded in ASM LUI and ADDIU instructions
177 static void sm64_adjust_asm(unsigned char *buf, ptr_t table[], int count)
179 unsigned int addr;
180 int i;
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);
187 INFO("\n");
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) {
192 addr_high++;
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) {
200 addr_high++;
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;
217 unsigned int s0, s6;
218 unsigned int a0, a1, a2, a3, at;
219 unsigned int lo;
220 unsigned int v0, v1;
221 unsigned int ra;
223 // derived from the SM64 boot code
224 s6 = 0x3f;
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
245 do {
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
266 } else {
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
279 cksum[0] = a3;
280 cksum[1] = s0;
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)) {
289 return ROM_SM64_BS;
291 if (!memcmp(buf, bs, sizeof(le)) && length == (8*MB)) {
292 return ROM_SM64_LE;
294 if (!memcmp(buf, be, sizeof(be))) {
295 if (length == 8*MB) {
296 return ROM_SM64_BE;
297 } else if (length > 8*MB) {
298 return ROM_SM64_BE_EXT;
301 return ROM_INVALID;
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)
327 #define MAX_PTRS 128
328 #define COMPRESSED_LENGTH 2
329 mio0_header_t head;
330 int bit_length;
331 int move_offset;
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];
337 int ptr_count;
338 int i;
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)) {
349 unsigned int end;
350 int length;
351 int is_mio0 = 0;
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);
355 if (length > 0) {
356 // dump MIO0 data and decompressed data to file
357 if (config->dump) {
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;
377 is_mio0 = 1;
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" : "");
389 if (config->fill) {
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;
397 } else {
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];
415 int i;
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]) {
429 INFO("(Good)\n");
430 } else {
431 INFO("(Bad)\n");
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]);