sm64pc/tools/libsm64.c

440 lines
16 KiB
C

#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;
} else if (length > 8*MB) {
return ROM_SM64_BE_EXT;
}
}
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},
};
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]);
}