/* Copyright (C) 2014-2015 Ben Kurtovic Released under the terms of the MIT License. See LICENSE for details. */ #include #include #include #include #include #include #include #include "rom.h" #include "logging.h" #include "util.h" #define NUM_LOCATIONS 3 #define MAGIC_LEN 8 #define HEADER_SIZE 16 static size_t header_locations[NUM_LOCATIONS] = {0x7FF0, 0x1FF0, 0x3FF0}; static const char header_magic[MAGIC_LEN + 1] = "TMR SEGA"; /* Return whether or not the given ROM image size is valid. */ static bool validate_size(off_t size) { if (size & (size - 1)) return false; // Ensure size is a power of two off_t kbytes = size >> 10; return kbytes >= 8 && kbytes <= 1024; } #ifdef DEBUG_MODE /* DEBUG FUNCTION: Print out the header to stdout. */ static void print_header(const uint8_t *header) { char header_hex[3 * HEADER_SIZE], header_chr[3 * HEADER_SIZE]; for (int i = 0; i < HEADER_SIZE; i++) { snprintf(&header_hex[3 * i], 3, "%02X", header[i]); if (isprint(header[i])) snprintf(&header_chr[3 * i], 3, "%2c", header[i]); else { header_chr[3 * i] = ' '; header_chr[3 * i + 1] = '?'; } header_hex[3 * i + 2] = header_chr[3 * i + 2] = ' '; } header_hex[3 * HEADER_SIZE - 1] = header_chr[3 * HEADER_SIZE - 1] = '\0'; DEBUG("- header dump (hex): %s", header_hex) DEBUG("- header dump (chr): %s", header_chr) } #endif /* Parse a ROM image's header, and return whether or not it is valid. The header is 16 bytes long, consisting of: byte 0: magic ('T') byte 1: magic ('M') byte 2: magic ('R') byte 3: magic (' ') byte 4: magic ('S') byte 5: magic ('E') byte 6: magic ('G') byte 7: magic ('A') byte 8: unused byte 9: unused byte A: checksum (LSB) byte B: checksum (MSB) byte C: product code (LSB) byte D: product code (middle byte) byte E (hi nibble): product code (most-significant nibble) byte E (lo nibble): version byte F (hi nibble): region code byte F (lo nibble): ROM size (Based on: http://www.smspower.org/Development/ROMHeader) */ static bool parse_header(ROM *rom, const uint8_t *header) { #ifdef DEBUG_MODE print_header(header); #endif rom->checksum = header[0xA] + (header[0xB] << 8); rom->product_code = bcd_decode(header[0xC]) + (bcd_decode(header[0xD]) * 100) + ((header[0xE] >> 4) * 10000); rom->version = header[0xE] & 0x0F; rom->region_code = header[0xF] >> 4; const char* region = rom_region(rom); DEBUG("- header info:") DEBUG(" - checksum: 0x%04X", rom->checksum) DEBUG(" - product code: %u", rom->product_code) DEBUG(" - version: %u", rom->version) DEBUG(" - region code: %u (%s)", rom->region_code, region ? region : "unknown") return true; } /* Find and read a ROM image's header, and return whether or not it is valid. */ static bool find_and_read_header(ROM *rom) { size_t location, i; const uint8_t *header; DEBUG("- looking for header:") for (i = 0; i < NUM_LOCATIONS; i++) { location = header_locations[i]; if (location + HEADER_SIZE > rom->size) { DEBUG(" - skipping location 0x%zX, out of range", location) continue; } DEBUG(" - trying location 0x%zX:", location) header = &rom->data[location]; if (memcmp(header, header_magic, MAGIC_LEN)) { DEBUG(" - magic not present") } else { DEBUG(" - magic found") return parse_header(rom, header); } } DEBUG(" - could not find header") return false; } /* Create and load a ROM image located at the given path. rom_ptr will point to the new object if created successfully, and NULL will be returned. Otherwise, rom_ptr will not be modified and an error string will be returned. The error string should not be freed. */ const char* rom_open(ROM **rom_ptr, const char *path) { ROM *rom; FILE* fp; struct stat st; if (!(fp = fopen(path, "r"))) return strerror(errno); if (fstat(fileno(fp), &st)) { fclose(fp); return strerror(errno); } if (!(st.st_mode & S_IFREG)) { fclose(fp); return (st.st_mode & S_IFDIR) ? rom_err_isdir : rom_err_notfile; } if (!(rom = malloc(sizeof(ROM)))) OUT_OF_MEMORY() // Set defaults: rom->name = NULL; rom->data = NULL; rom->size = 0; rom->checksum = 0; rom->product_code = 0; rom->version = 0; rom->region_code = 0; // Set rom->name: if (!(rom->name = malloc(sizeof(char) * (strlen(path) + 1)))) OUT_OF_MEMORY() strcpy(rom->name, path); DEBUG("Loading ROM %s:", rom->name) // Set rom->size: DEBUG("- size: %lld", st.st_size) if (!validate_size(st.st_size)) { rom_close(rom); fclose(fp); return rom_err_badsize; } rom->size = st.st_size; // Set rom->data: if (!(rom->data = malloc(sizeof(uint8_t) * st.st_size))) OUT_OF_MEMORY() if (!(fread(rom->data, st.st_size, 1, fp))) { rom_close(rom); fclose(fp); return rom_err_badread; } fclose(fp); // Parse the header: if (!find_and_read_header(rom)) { rom_close(rom); return rom_err_badheader; } *rom_ptr = rom; return NULL; } /* Free a ROM object previously created with rom_open(). */ void rom_close(ROM *rom) { if (rom->name) free(rom->name); if (rom->data) free(rom->data); free(rom); } /* Return the region this ROM was intended for, based on header information. NULL is returned if the region code is invalid. Region code information is taken from http://www.smspower.org/Development/ROMHeader. */ const char* rom_region(const ROM *rom) { switch (rom->region_code) { case 3: return "SMS Japan"; case 4: return "SMS Export"; case 5: return "GG Japan"; case 6: return "GG Export"; case 7: return "GG International"; default: return NULL; } }