153 lines
4.3 KiB
C
153 lines
4.3 KiB
C
#include <arpa/inet.h>
|
|
#include <memory.h>
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include "mkrom.h"
|
|
|
|
extern struct state state;
|
|
|
|
/**
|
|
* This file handles creation of the gamezips segment.
|
|
*
|
|
* Before mkrom is called, the game segment (uncompressed) is placed past the
|
|
* end of the ROM by ld. This segment must be split into 4KB chunks. Each chunk
|
|
* is zipped and then placed in its final location within the ROM.
|
|
*
|
|
* The format of the gamezips segment is:
|
|
* - Array of offsets to each chunk, where each offset is 4 bytes and relative
|
|
* to the start of the gamezips segment.
|
|
* - A final offset that points to the end of the final chunk.
|
|
* - Chunk data, where each chunk consists of:
|
|
* - A 2 byte checksum of the uncompressed chunk.
|
|
* - Zip data (starting with 0x1173001000).
|
|
* - Optional single byte to align it to the next 2 byte boundary.
|
|
* The added byte is data from the previous chunk.
|
|
*/
|
|
|
|
/**
|
|
* Calculate the checksum of this chunk's raw data.
|
|
*
|
|
* The game never verifies this, but it exists in the ROM so it has to be
|
|
* calculated by mkrom.
|
|
*
|
|
* It's a simple sum of each word, but then it gets stored as a short so the
|
|
* upper half is lost.
|
|
*/
|
|
static uint32_t crc(uint8_t *buffer, size_t len)
|
|
{
|
|
uint32_t sum = 0;
|
|
uint32_t offset;
|
|
|
|
for (offset = 0; offset < len; offset += 4) {
|
|
sum += ntohl(*(uint32_t *) &buffer[offset]);
|
|
}
|
|
|
|
return sum;
|
|
}
|
|
|
|
/**
|
|
* Create a game chunk. We just calculate and prepend the checksum, then call
|
|
* rarezip() which does the zipping and adding of the 0x1173 header.
|
|
*/
|
|
static void create_chunk(uint8_t *outbuf, size_t *outlen, uint8_t *inbuf, size_t inlen)
|
|
{
|
|
uint32_t sum = crc(inbuf, inlen);
|
|
|
|
outbuf[0] = (sum >> 8) & 0xff;
|
|
outbuf[1] = sum & 0xff;
|
|
|
|
rarezip(&outbuf[2], outlen, inbuf, inlen, state.zipmagic);
|
|
|
|
*outlen += 2;
|
|
}
|
|
|
|
/**
|
|
* Generate the gamezips segment.
|
|
*
|
|
* This segment starts with an offset table followed by the chunk data.
|
|
* However, we need to keep the offset table zeroed here and build the table
|
|
* in a different allocation because the ROM is packed with a duplicate of
|
|
* this segment which has an empty offset table.
|
|
*
|
|
* So this function creates:
|
|
* - state.gamezips, which is big enough to hold the offset table plus zips,
|
|
* but has the offset table zeroed
|
|
* - state.gametable, which is just the table and has the entries populated.
|
|
*
|
|
* Each chunk is aligned to an even byte. If alignment needs to occur, the extra
|
|
* byte is taken from the same offset in the previous chunk. In other words, the
|
|
* same output buffer is used for every chunk and it's not cleared between uses.
|
|
*/
|
|
void game_zip(void)
|
|
{
|
|
uint32_t end;
|
|
size_t len;
|
|
size_t num_chunks;
|
|
uint32_t tableoffset;
|
|
uint32_t dataoffset;
|
|
size_t len_remaining;
|
|
uint8_t outscratch[0x1100];
|
|
uint32_t offset;
|
|
uint32_t tablelen;
|
|
|
|
memset(outscratch, 0, 0x1000);
|
|
|
|
// Find the game's position in the ROM and calculate the number of chunks
|
|
map_get_segment_rompos("game", &offset, &end);
|
|
|
|
len = end - offset;
|
|
num_chunks = len / 0x1000;
|
|
|
|
if (len % 0x1000) {
|
|
num_chunks++;
|
|
}
|
|
|
|
// Allocate buffers
|
|
tablelen = num_chunks * 4 + 4;
|
|
|
|
state.gamezips = malloc(tablelen + len);
|
|
state.gametable = malloc(tablelen);
|
|
state.gametablelen = tablelen;
|
|
|
|
memset(state.gamezips, 0, tablelen);
|
|
|
|
tableoffset = 0;
|
|
dataoffset = tablelen;
|
|
|
|
len_remaining = len;
|
|
|
|
// Generate the chunks
|
|
while (offset < end) {
|
|
size_t chunkoriglen = len_remaining >= 0x1000 ? 0x1000 : len_remaining;
|
|
size_t chunklen;
|
|
|
|
// Write the table entry
|
|
state.gametable[tableoffset + 0] = (dataoffset >> 24) & 0xff;
|
|
state.gametable[tableoffset + 1] = (dataoffset >> 16) & 0xff;
|
|
state.gametable[tableoffset + 2] = (dataoffset >> 8) & 0xff;
|
|
state.gametable[tableoffset + 3] = dataoffset & 0xff;
|
|
tableoffset += 4;
|
|
|
|
// Write the data
|
|
create_chunk(outscratch, &chunklen, &state.rom[offset], chunkoriglen);
|
|
|
|
chunklen += chunklen % 2;
|
|
|
|
memcpy(&state.gamezips[dataoffset], outscratch, chunklen);
|
|
|
|
dataoffset += chunklen;
|
|
|
|
len_remaining -= 0x1000;
|
|
offset += 0x1000;
|
|
}
|
|
|
|
// The table contains an additional pointer to the end of the final chunk
|
|
state.gametable[tableoffset + 0] = (dataoffset >> 24) & 0xff;
|
|
state.gametable[tableoffset + 1] = (dataoffset >> 16) & 0xff;
|
|
state.gametable[tableoffset + 2] = (dataoffset >> 8) & 0xff;
|
|
state.gametable[tableoffset + 3] = dataoffset & 0xff;
|
|
|
|
state.gamezipslen = dataoffset;
|
|
}
|