Files
Main_MiSTer/support/n64/n64.cpp

1764 lines
49 KiB
C++

#include <ctype.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include "../../hardware.h"
#include "../../menu.h"
#include "../../shmem.h"
#include "../../lib/md5/md5.h"
#include "miniz.h"
#include "n64.h"
#include "n64_cpak_header.h"
#pragma push_macro("NONE")
#pragma push_macro("BIG_ENDIAN")
#pragma push_macro("LITTLE_ENDIAN")
#undef NONE
#undef BIG_ENDIAN
#undef LITTLE_ENDIAN
static constexpr auto RAM_SIZE = 0x800000U;
static constexpr auto CARTID_LENGTH = 6U; // Ex: NSME00
static constexpr auto MD5_LENGTH = 16U;
static constexpr auto CARTID_PREFIX = "ID:";
static constexpr auto RESET_OPT = "[0]";
static constexpr auto RELOAD_SAVE_OPT = "[40]";
static constexpr auto AUTODETECT_OPT = "[64]";
static constexpr auto CIC_TYPE_OPT = "[68:65]";
static constexpr auto NO_EPAK_OPT = "[70]";
static constexpr auto CPAK_OPT = "[71]";
static constexpr auto RPAK_OPT = "[72]";
static constexpr auto TPAK_OPT = "[73]";
static constexpr auto RTC_OPT = "[74]";
static constexpr auto SAVE_TYPE_OPT = "[77:75]";
static constexpr auto SYS_TYPE_OPT = "[80:79]";
static constexpr auto AUTOPAK_OPT = "[81]";
static constexpr auto PATCHES_OPT = "[90]";
static constexpr auto CHEATS_OPT = "[103]";
static constexpr const char* const CONTROLLER_OPTS[] = { "[51:49]", "[54:52]", "[57:55]", "[60:58]" };
// Simple hash function, see: https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function
// (Modified to make it case-insensitive)
static constexpr uint64_t FNV_PRIME = 0x100000001b3;
static constexpr uint64_t FNV_OFFSET_BASIS = 0xcbf29ce484222325;
static constexpr uint64_t fnv_hash(const char* s, uint64_t h = FNV_OFFSET_BASIS) {
if (s) while (uint8_t a = *(s++)) h = (h ^ ((a >= 'A') && (a <= 'Z') ? a + ('a' - 'A') : a)) * FNV_PRIME;
return h;
}
enum class MemoryType : uint32_t {
NONE = 0,
EEPROM_512,
EEPROM_2k,
SRAM_32k,
SRAM_96k,
FLASH_128k,
CPAK = ~2U,
TPAK = ~1U,
UNKNOWN = ~0U
};
enum class CIC : uint32_t {
CIC_NUS_6101 = 0,
CIC_NUS_6102,
CIC_NUS_7101,
CIC_NUS_7102,
CIC_NUS_6103,
CIC_NUS_7103,
CIC_NUS_6105,
CIC_NUS_7105,
CIC_NUS_6106,
CIC_NUS_7106,
CIC_NUS_8303,
CIC_NUS_8401,
CIC_NUS_5167,
CIC_NUS_DDUS,
CIC_NUS_5101,
UNKNOWN = ~0U
};
enum class SystemType : uint32_t {
NTSC = 0,
PAL,
UNKNOWN = ~0U
};
enum class ByteOrder : uint32_t {
BIG_ENDIAN = 0,
BYTE_SWAPPED,
LITTLE_ENDIAN,
UNKNOWN = ~0U
};
enum class PadType : uint32_t {
N64_PAD = 0,
UNPLUGGED,
N64_PAD_WITH_CPAK,
N64_PAD_WITH_RPAK,
SNAC,
N64_PAD_WITH_TPAK,
UNKNOWN = ~0U
};
enum class AutoDetect : uint32_t {
ON = 0,
OFF
};
enum class ComparisionType : uint8_t {
OPTYPE_ALWAYS = 0,
OPTYPE_EQUALS = 0x1,
OPTYPE_GREATER = 0x2,
OPTYPE_LESS = 0x3,
OPTYPE_GREATER_EQ = 0x4,
OPTYPE_LESS_EQ = 0x5,
OPTYPE_NOT_EQ = 0x6,
OPTYPE_EMPTY = 0xf
};
struct cheat_code_flags {
enum ComparisionType cmp_type : 4;
uint8_t cmp_mask : 4;
bool is_boot_code : 1;
bool is_boot_code_executed : 1;
bool is_gs_button_code : 1;
bool is_gs_button_pressed : 1;
uint32_t _unused : 20;
} __attribute__((packed));
struct cheat_code {
struct cheat_code_flags flags;
uint32_t address;
uint32_t compare;
uint32_t replace;
};
struct patch_data {
uint32_t address;
uint32_t diff;
bool turbo_core_only;
};
static uint8_t loaded = 0;
static char current_rom_path[1024] = { '\0' };
static char current_rom_path_gb[1024] = { '\0' };
static char old_save_path[1024];
static void* rdram_ptr = nullptr;
static cheat_code* cheat_codes = nullptr;
static uint32_t cheat_codes_count = 0;
static patch_data patches[256];
static int is_turbo_core_type = 0;
static const char* stringify(MemoryType v) {
switch (v) {
case MemoryType::EEPROM_512: return "4K EEPROM";
case MemoryType::EEPROM_2k: return "16K EEPROM";
case MemoryType::SRAM_32k: return "256K SRAM";
case MemoryType::SRAM_96k: return "768K SRAM";
case MemoryType::FLASH_128k: return "Flash RAM";
case MemoryType::CPAK: return "CPAK DATA";
case MemoryType::TPAK: return "TPAK DATA";
default: return "(none)";
}
}
static size_t get_save_size(MemoryType v) {
switch (v) {
case MemoryType::EEPROM_512: return 0x200;
case MemoryType::EEPROM_2k: return 0x800;
case MemoryType::SRAM_32k: return 0x8000;
case MemoryType::SRAM_96k: return 0x18000;
case MemoryType::FLASH_128k: return 0x20000;
case MemoryType::CPAK:
case MemoryType::TPAK: return 0x8000; // 32 KiByte
default: return 0;
}
}
static const char* stringify(CIC v) {
switch (v) {
case CIC::CIC_NUS_6101: return "6101";
case CIC::CIC_NUS_6102: return "6102";
case CIC::CIC_NUS_7101: return "7101";
case CIC::CIC_NUS_7102: return "7102";
case CIC::CIC_NUS_6103: return "6103";
case CIC::CIC_NUS_7103: return "7103";
case CIC::CIC_NUS_6105: return "6105";
case CIC::CIC_NUS_7105: return "7105";
case CIC::CIC_NUS_6106: return "6106";
case CIC::CIC_NUS_7106: return "7106";
case CIC::CIC_NUS_8303: return "8303";
case CIC::CIC_NUS_8401: return "8401";
case CIC::CIC_NUS_5167: return "5167";
case CIC::CIC_NUS_DDUS: return "DDUS";
case CIC::CIC_NUS_5101: return "5101";
default: return "Unknown";
}
}
static const char* stringify(SystemType v) {
switch (v) {
case SystemType::NTSC: return "NTSC";
case SystemType::PAL: return "PAL";
default: return "Unknown";
}
}
static const char* stringify(bool v) {
return v
? "Yes"
: "No";
}
static ByteOrder detect_rom_endianness(const uint8_t* data) {
// Data should be aligned
const uint32_t val = *(uint32_t*)data;
/* The following checks assume we're on a little-endian platform.
For each check, the first value is for regular ROMs, the 2nd is for 64DD images and
the third is a malformed "word" used in some homebrew(?) */
switch (val) {
case UINT32_C(0x40123780):
case UINT32_C(0x40072780):
case UINT32_C(0x41123780):
return ByteOrder::BIG_ENDIAN;
case UINT32_C(0x12408037):
case UINT32_C(0x07408027):
case UINT32_C(0x12418037):
return ByteOrder::BYTE_SWAPPED;
case UINT32_C(0x80371240):
case UINT32_C(0x80270740):
case UINT32_C(0x80371241):
return ByteOrder::LITTLE_ENDIAN;
default:
break;
}
// Endianness could not be determined, use just first byte and hope for the best.
switch (val & 0xff) {
case 0x80:
return ByteOrder::BIG_ENDIAN;
case 0x37:
case 0x27:
return ByteOrder::BYTE_SWAPPED;
case 0x40:
case 0x41:
return ByteOrder::LITTLE_ENDIAN;
default:
return ByteOrder::UNKNOWN;
}
}
static void normalize_data(uint8_t* data, size_t size, ByteOrder endianness) {
static uint8_t temp0, temp1, temp2;
switch (endianness) {
case ByteOrder::BYTE_SWAPPED:
for (size_t i = 0; i < (size & ~1U); i += 2) {
temp0 = data[0];
data[0] = data[1];
data[1] = temp0;
data += 2;
}
break;
case ByteOrder::LITTLE_ENDIAN:
for (size_t i = 0; i < (size & ~3U); i += 4) {
temp0 = data[0];
temp1 = data[1];
temp2 = data[2];
data[0] = data[3];
data[1] = temp2;
data[2] = temp1;
data[3] = temp0;
data += 4;
}
break;
default:
// Do nothing
break;
}
}
static MemoryType get_cart_save_type() {
auto v = (MemoryType)user_io_status_get(SAVE_TYPE_OPT);
return (get_save_size(v) ? v : MemoryType::NONE);
}
static void set_cart_save_type(MemoryType v) {
user_io_status_set(SAVE_TYPE_OPT,
(uint32_t)(get_save_size(v) ? v : MemoryType::NONE));
}
static uint32_t get_save_offset(unsigned char idx) {
uint32_t offset = 0;
MemoryType save_type = get_cart_save_type();
if (idx && (save_type != MemoryType::NONE)) {
offset += get_save_size(save_type);
idx--;
}
if (idx && (bool)user_io_status_get(TPAK_OPT)) {
offset += get_save_size(MemoryType::TPAK);
idx--;
}
offset += get_save_size(MemoryType::CPAK) * idx;
return offset;
}
static char full_path[1024];
static uint8_t save_file_buf[0x20000]; // Largest save size
static uint8_t mounted_save_files = 0;
static size_t create_file(const char* filename, const uint8_t* data, size_t sz) {
snprintf(full_path, sizeof(full_path), "%s/%s", getRootDir(), filename);
FILE* fp = fopen(full_path, "w");
if (!fp) return 0;
sz = fwrite(data, 1, sz, fp);
fclose(fp);
return sz;
}
static size_t read_file(const char* filename, uint8_t* data, uint32_t offset, size_t sz) {
snprintf(full_path, sizeof(full_path), "%s/%s", getRootDir(), filename);
FILE* fp = fopen(full_path, "r");
if (!fp) return 0;
fseek(fp, 0L, SEEK_END);
size_t fs = ftell(fp);
if (fs > sizeof(save_file_buf)) {
fs = sizeof(save_file_buf);
}
if (offset >= fs) {
fclose(fp);
return 0;
}
if (offset + sz > fs) {
sz = fs - offset;
}
fseek(fp, offset, SEEK_SET);
sz = fread(data, 1, sz, fp);
fclose(fp);
return sz;
}
static bool is_empty(uint8_t* arr, size_t sz) {
for (; sz-- > 0; arr++) {
if (*arr) return false;
}
return true;
}
// Shouldn't be possible to allocate more than 5 save files right now...
static N64SaveFile* save_files[8] = { nullptr };
struct N64SaveFile {
int idx;
MemoryType type;
fileTYPE* get_image() const {
return ((this->idx >= 0) && (this->idx < (int)(sizeof(save_files) / sizeof(*save_files)))) ? (fileTYPE*)::get_image(this->idx) : nullptr;
}
bool is_mounted() const {
fileTYPE* image;
return (image = this->get_image()) && image->filp;
}
size_t get_size() const {
return is_mounted() ? (size_t)this->get_image()->size : get_save_size(this->type);
}
// Return "true" if target is fine
bool copy_file(const char* path, const char* to_path, bool overwrite, bool resize = false) const {
if (!FileExists(path, 0)) {
return false;
}
size_t expected_sz = get_save_size(this->type);
size_t sz = read_file(path, save_file_buf, 0, resize ? expected_sz : sizeof(save_file_buf));
if (FileExists(to_path, 0) && (!overwrite || is_empty(save_file_buf, sz))) {
return true;
}
if (resize && (sz < expected_sz)) {
// Pad file
memset(save_file_buf + sz, 0, expected_sz - sz);
sz = expected_sz;
}
if (!create_file(to_path, save_file_buf, sz)) {
return false;
}
printf("Copied save data \"%s\" to \"%s\". (%u bytes)\n", path, to_path, sz);
return true;
}
// Return "true" if created
bool create_if_missing(const char* path, const char* old_path) {
if (FileExists(path, 0)) {
return false;
}
auto sz = this->get_size();
memset(save_file_buf, 0, sz);
bool found_old_data = false;
if (sz && FileExists(old_path, 0)) {
uint32_t off = get_save_offset((this->idx < 0) ? mounted_save_files : this->idx);
if (read_file(old_path, save_file_buf, off, sz)) {
printf("Found old save data \"%s\", converting to %s.\n", old_path, stringify(type));
found_old_data = true;
if ((this->type == MemoryType::CPAK) || (this->type == MemoryType::TPAK)) {
normalize_data(save_file_buf, sz, ByteOrder::LITTLE_ENDIAN);
}
}
}
if (!found_old_data && (this->type == MemoryType::CPAK)) {
memcpy(save_file_buf, cpak_header[((this->idx < 0) ? mounted_save_files : idx) % (sizeof(cpak_header) / sizeof(*cpak_header))], sizeof(*cpak_header));
}
return create_file(path, save_file_buf, sz);
}
void mount(const char* path) {
int pre_size = get_save_size(this->type);
if (!pre_size) return;
uint8_t idx = mounted_save_files++;
if (idx >= (sizeof(save_files) / sizeof(*save_files))) return;
if (save_files[idx] && save_files[idx]->is_mounted()) {
save_files[idx]->unmount();
delete save_files[idx];
save_files[idx] = nullptr;
}
user_io_file_mount(path, idx, 1, pre_size);
this->idx = idx;
save_files[idx] = this;
}
void mount(const char* path, const char* old_path) {
this->create_if_missing(path, old_path);
this->mount(path);
}
void unmount() {
if (!this->is_mounted()) return;
printf("Unmounting %s save file at %d slot.\n", stringify(this->type), this->idx);
user_io_file_mount("", this->idx);
}
N64SaveFile(MemoryType type) {
this->idx = -1; // Unmounted
this->type = type;
}
};
static bool is_auto() {
return (AutoDetect)user_io_status_get(AUTODETECT_OPT) == AutoDetect::ON;
}
static bool is_autopak() {
return !user_io_status_get(AUTOPAK_OPT);
}
static bool is_turbo_core() {
if (!is_turbo_core_type) {
is_turbo_core_type = 2;
user_io_read_confstr();
for (size_t i = 2; ; i++) {
char *p = user_io_get_confstr(i);
if (!p) break;
if (strncasecmp(p, "TURBO", 5) == 0) {
is_turbo_core_type = 1;
break;
}
}
}
return is_turbo_core_type == 1;
}
static bool cheats_enabled() {
return !user_io_status_get(CHEATS_OPT);
}
static void cheats_disable() {
user_io_status_set(CHEATS_OPT, 1);
}
static bool patches_enabled() {
return !user_io_status_get(PATCHES_OPT);
}
static uint8_t hex_to_dec(const char x) {
if (x >= '0' && x <= '9') return (x - '0');
if (x >= 'A' && x <= 'F') return (x - 'A') + 10;
if (x >= 'a' && x <= 'f') return (x - 'a') + 10;
return 0;
}
static void trim(char* out, size_t max_len, const char* str)
{
if (!*str || !max_len) {
*out = '\0';
return;
}
const char* end;
size_t out_size = max_len;
// Trim leading space
while (isspace(*str)) {
if (!(--out_size)) {
*out = '\0';
return;
}
str++;
}
// All spaces?
if (!*str) {
*out = '\0';
return;
}
// Trim trailing space
end = str + strnlen(str, max_len) - 1;
while (end > str && isspace(*end)) {
end--;
}
end++;
// Set output size to minimum of trimmed string length and buffer size minus 1
out_size = (size_t)(end - str) < (max_len - 1) ? (end - str) : max_len - 1;
// Copy trimmed string and add null terminator
memcpy(out, str, out_size);
out[out_size] = '\0';
// Obfuscate illegal characters
for (size_t i = 0; (i < out_size) && out[i]; i++) {
if ((out[i] >= 0x20) && (out[i] < 0xa0)) {
continue;
}
out[i] = '?';
}
}
// Returns true if CIC and System Region is detected, or if auto-detection is turned off
static bool parse_and_apply_db_tags(char* tags) {
if (!tags) return false;
const char* separator = "| ";
MemoryType save_type = MemoryType::NONE;
SystemType system_type = SystemType::UNKNOWN;
CIC cic_type = CIC::UNKNOWN;
bool no_epak = false;
bool cpak = false;
bool rpak = false;
bool tpak = false;
bool rtc = false;
int p_match;
uint32_t p_addr, p_diff;
char p_tag;
PadType prefered_pad = PadType::N64_PAD;
patch_data* patch = patches;
for (char* tag = strtok(tags, separator); tag; tag = strtok(nullptr, separator)) {
// Patch tag
if ((p_match = sscanf(tag, "%*[Pp]%8x:%8x%*[:]%c", &p_addr, &p_diff, &p_tag)) >= 2) {
printf("Patch: 0x%08x:0x%08x", p_addr, p_diff);
if (p_match >= 3) printf(":%c", p_tag);
printf("\n");
patch->address = p_addr;
patch->diff = p_diff;
patch->turbo_core_only = p_match >= 3 && (p_tag == 'T' || p_tag == 't');
patch++;
continue;
}
switch (fnv_hash(tag)) {
case fnv_hash("eeprom512"): save_type = MemoryType::EEPROM_512; break;
case fnv_hash("eeprom2k"): save_type = MemoryType::EEPROM_2k; break;
case fnv_hash("sram32k"): save_type = MemoryType::SRAM_32k; break;
case fnv_hash("sram96k"): save_type = MemoryType::SRAM_96k; break;
case fnv_hash("flash128k"): save_type = MemoryType::FLASH_128k; break;
case fnv_hash("noepak"): no_epak = true; break;
case fnv_hash("cpak"): cpak = true; if (prefered_pad == PadType::N64_PAD) prefered_pad = PadType::N64_PAD_WITH_CPAK; break;
case fnv_hash("rpak"): rpak = true; if (prefered_pad == PadType::N64_PAD) prefered_pad = PadType::N64_PAD_WITH_RPAK; break;
case fnv_hash("tpak"): tpak = true; if (prefered_pad == PadType::N64_PAD) prefered_pad = PadType::N64_PAD_WITH_TPAK; break;
case fnv_hash("rtc"): rtc = true; break;
case fnv_hash("ntsc"): ntsc: system_type = SystemType::NTSC; break;
case fnv_hash("pal"): pal: system_type = SystemType::PAL; break;
case fnv_hash("cic6101"): cic_type = CIC::CIC_NUS_6101; goto ntsc;
case fnv_hash("cic6102"): cic_type = CIC::CIC_NUS_6102; goto ntsc;
case fnv_hash("cic6103"): cic_type = CIC::CIC_NUS_6103; goto ntsc;
case fnv_hash("cic6105"): cic_type = CIC::CIC_NUS_6105; goto ntsc;
case fnv_hash("cic6106"): cic_type = CIC::CIC_NUS_6106; goto ntsc;
case fnv_hash("cic7101"): cic_type = CIC::CIC_NUS_7101; goto pal;
case fnv_hash("cic7102"): cic_type = CIC::CIC_NUS_7102; goto pal;
case fnv_hash("cic7103"): cic_type = CIC::CIC_NUS_7103; goto pal;
case fnv_hash("cic7105"): cic_type = CIC::CIC_NUS_7105; goto pal;
case fnv_hash("cic7106"): cic_type = CIC::CIC_NUS_7106; goto pal;
case fnv_hash("cic8303"): cic_type = CIC::CIC_NUS_8303; break;
case fnv_hash("cic8401"): cic_type = CIC::CIC_NUS_8401; break;
case fnv_hash("cic5167"): cic_type = CIC::CIC_NUS_5167; break;
case fnv_hash("cicddus"): cic_type = CIC::CIC_NUS_DDUS; break;
case fnv_hash("cic5101"): cic_type = CIC::CIC_NUS_5101; break;
default: printf("Unknown tag: [%s] (skipping)\n", tag); break;
}
}
if (system_type == SystemType::UNKNOWN && cic_type != CIC::UNKNOWN) {
system_type = SystemType::NTSC;
}
printf("Region: %s, Save Type: %s, CIC: %s, CPak: %s, RPak: %s, TPak %s, RTC: %s, Mem: %uMiB\n",
stringify(system_type),
stringify(save_type),
stringify(cic_type),
stringify(cpak),
stringify(rpak),
stringify(tpak),
stringify(rtc),
(no_epak ? 4 : 8));
if (!is_auto()) {
printf("Auto-detect is OFF, not updating OSD settings.\n");
return true;
}
printf("Auto-detect is ON, updating OSD settings.\n");
if (system_type != SystemType::UNKNOWN) user_io_status_set(SYS_TYPE_OPT, (uint32_t)system_type);
if (cic_type != CIC::UNKNOWN) user_io_status_set(CIC_TYPE_OPT, (uint32_t)cic_type);
user_io_status_set(NO_EPAK_OPT, (uint32_t)no_epak);
user_io_status_set(CPAK_OPT, (uint32_t)cpak);
user_io_status_set(RPAK_OPT, (uint32_t)rpak);
user_io_status_set(TPAK_OPT, (uint32_t)tpak);
user_io_status_set(RTC_OPT, (uint32_t)rtc);
set_cart_save_type(save_type);
if (is_autopak() && ((PadType)user_io_status_get(CONTROLLER_OPTS[0]) != PadType::SNAC)) {
user_io_status_set(CONTROLLER_OPTS[0], (uint32_t)prefered_pad);
}
return (system_type != SystemType::UNKNOWN && cic_type != CIC::UNKNOWN);
}
static bool md5_matches(const char* line, const char* md5) {
for (size_t i = MD5_LENGTH * 2; i > 0; --i, line++, md5++) {
int c = *line;
if (!c || *md5 != tolower(c)) {
return false;
}
}
return true;
}
// Returns numbers of matching characters if match, otherwize 0
static size_t cart_id_is_match(const char* line, const char* cart_id) {
const auto prefix_len = strlen(CARTID_PREFIX);
// A valid ID line should start with "ID:"
if (strncmp(line, CARTID_PREFIX, prefix_len)) {
return 0;
}
// Skip the line if it doesn't match our cart_id, '_' = don't care
const char* lp = line + prefix_len;
for (size_t i = 0; i < CARTID_LENGTH && *lp; i++, lp++) {
if (i && isspace(*lp)) {
return i; // Early termination
}
if (*lp != '_' && *lp != cart_id[i]) {
return 0; // Character didn't match pattern
}
}
return CARTID_LENGTH;
}
static uint8_t detect_rom_settings_in_db(const char* lookup_hash, const char* db_file_name) {
fileTextReader reader = {};
snprintf(full_path, sizeof(full_path), "%s/%s", HomeDir(), db_file_name);
if (!FileOpenTextReader(&reader, full_path)) {
printf("Failed to open N64 data file \"%s\".\n", db_file_name);
return 0;
}
while (const char* line = FileReadLine(&reader)) {
// Skip the line if it doesn't start with our hash
if (!md5_matches(line, lookup_hash)) continue;
const char* s = line + (MD5_LENGTH * 2);
char* tags = new char[strlen(s) + 1];
if (sscanf(s, "%*[ \t]%[^#;]", tags) <= 0) {
printf("Found ROM entry for MD5 %s, but the tag was malformed! (%s)\n", lookup_hash, s);
return 2;
}
printf("Found ROM entry for MD5 %s: [%s]\n", lookup_hash, tags);
// 2 = System region and/or CIC wasn't in DB, will need further detection
return parse_and_apply_db_tags(tags) ? 3 : 2;
}
return 0;
}
static uint8_t detect_rom_settings_in_db_with_cartid(const char* cart_id, const char* db_file_name) {
fileTextReader reader = {};
snprintf(full_path, sizeof(full_path), "%s/%s", HomeDir(), db_file_name);
if (!FileOpenTextReader(&reader, full_path)) {
printf("Failed to open N64 data file \"%s\".\n", db_file_name);
return 0;
}
while (const char* line = FileReadLine(&reader)) {
// Skip lines that doesn't start with our ID
size_t i;
if (!(i = cart_id_is_match(line, cart_id))) continue;
auto s = line + strlen(CARTID_PREFIX) + i;
auto tags = new char[strlen(s) + 1];
if (sscanf(s, "%*[ \t]%[^#;]", tags) <= 0) {
printf("Found ROM entry for ID [%s], but the tag was malformed! \"%s\".\n", cart_id, s);
return 2;
}
printf("Found ROM entry for ID [%s]: \"%s\".\n", cart_id, tags);
// 2 = System region and/or CIC wasn't in DB, will need further detection
return parse_and_apply_db_tags(tags) ? 3 : 2;
}
return 0;
}
static const char* DB_FILE_NAMES[] = {
"N64-database_user.txt",
"N64-database.txt"
};
static uint8_t detect_rom_settings_in_dbs_with_md5(const char* lookup_hash) {
uint8_t detected = 0;
for (auto i = 0U; i < (sizeof(DB_FILE_NAMES) / sizeof(*DB_FILE_NAMES)); i++) {
if ((detected = detect_rom_settings_in_db(lookup_hash, DB_FILE_NAMES[i]))) {
break;
}
}
return detected;
}
static uint8_t detect_rom_settings_in_dbs_with_cartid(const char* lookup_id) {
// Check if all characters in the lookup are valid
for (size_t i = 0; i < CARTID_LENGTH; i++) {
auto c = lookup_id[i];
if (isalnum(c)) {
continue;
}
printf("Not a valid Cart ID: [%s]!\n", lookup_id);
return 0;
}
uint8_t detected = 0;
for (auto i = 0U; i < (sizeof(DB_FILE_NAMES) / sizeof(*DB_FILE_NAMES)); i++) {
if ((detected = detect_rom_settings_in_db_with_cartid(lookup_id, DB_FILE_NAMES[i]))) {
break;
}
}
return detected;
}
// "Advanced" Homebrew ROM Header https://n64brew.dev/wiki/ROM_Header
static bool detect_homebrew_header(const uint8_t* controller_settings, const char* cart_id) {
if ((cart_id[1] != 'E') || (cart_id[2] != 'D')) return false;
printf("Detected Advanced Homebrew ROM Header, how fancy!\n");
if (!is_auto()) {
printf("Auto-detect is OFF, not updating OSD settings.\n");
return false;
}
switch (hex_to_dec(cart_id[4])) {
case 1:
set_cart_save_type(MemoryType::EEPROM_512);
break;
case 2:
set_cart_save_type(MemoryType::EEPROM_2k);
break;
case 3:
set_cart_save_type(MemoryType::SRAM_32k);
break;
case 4:
set_cart_save_type(MemoryType::SRAM_96k);
break;
case 5:
set_cart_save_type(MemoryType::FLASH_128k);
break;
//case 6:
// set_cart_save_type(MemoryType::SRAM_128k);
// break;
default:
set_cart_save_type(MemoryType::NONE);
break;
}
printf("Auto-detect is ON, updating OSD settings.\n");
user_io_status_set(RTC_OPT, (uint32_t)(hex_to_dec(cart_id[5]) & 1)); // RTC
user_io_status_set(RPAK_OPT, (uint32_t)(
(controller_settings[0] == 0x01) ||
(controller_settings[1] == 0x01) ||
(controller_settings[2] == 0x01) ||
(controller_settings[3] == 0x01) ? 1 : 0)); // Rumble Pak
user_io_status_set(CPAK_OPT, (uint32_t)(
(controller_settings[0] == 0x02) ||
(controller_settings[1] == 0x02) ||
(controller_settings[2] == 0x02) ||
(controller_settings[3] == 0x02) ? 1 : 0)); // Controller Pak
user_io_status_set(TPAK_OPT, (uint32_t)(
(controller_settings[0] == 0x03) ||
(controller_settings[1] == 0x03) ||
(controller_settings[2] == 0x03) ||
(controller_settings[3] == 0x03) ? 1 : 0)); // Transfer Pak
if (!is_autopak()) return true;
size_t c_idx = 0;
for (auto c_opt : CONTROLLER_OPTS) {
if (controller_settings[c_idx] && ((PadType)user_io_status_get(c_opt) != PadType::SNAC)) {
if (controller_settings[c_idx] < 0x80) {
user_io_status_set(c_opt, (uint32_t)(
(controller_settings[c_idx] == 0x01) ? PadType::N64_PAD_WITH_RPAK :
(controller_settings[c_idx] == 0x02) ? PadType::N64_PAD_WITH_CPAK :
(controller_settings[c_idx] == 0x03) && (c_idx == 0) ? PadType::N64_PAD_WITH_TPAK :
PadType::N64_PAD));
}
else if (controller_settings[c_idx] == 0xff) {
user_io_status_set(c_opt, (uint32_t)PadType::UNPLUGGED);
}
}
c_idx++;
}
return true;
}
static bool detect_rom_settings_from_first_chunk(const char region_code, const uint64_t* signatures, size_t sig_len) {
SystemType system_type;
CIC cic = CIC::UNKNOWN;
bool is_known_signature = true;
switch (region_code) {
case 'D': // Germany
case 'F': // France
case 'H': // Netherlands
case 'I': // Italy
case 'L': // Gateway 64 (PAL)
case 'P': // Europe
case 'S': // Spain
case 'U': // Australia
case 'W': // Scandinavia
case 'X': // Europe
case 'Y': // Europe
case 'Z': // Europe
system_type = SystemType::PAL; break;
case 'A': // Asia (NTSC, used for 1080 US/JP)
case 'B': // Brazil
case 'C': // China
case 'E': // North America
case 'G': // Gateway 64 (NTSC)
case 'J': // Japan
case 'K': // Korea
case 'N': // Canada
default:
system_type = SystemType::NTSC; break;
}
if (sig_len) do {
switch (*signatures) {
default:
if (--sig_len) {
signatures++;
break;
}
printf("Unknown CIC signature: 0x%016llx, uses default.\n", *signatures);
is_known_signature = false;
// Fall through
case UINT64_C(0x000000a316adc55a): // CIC-6102/7101 IPL3
case UINT64_C(0x000000a30dacd530): // NOP:ed out CRC check
case UINT64_C(0x000000039c981107): // hcs64's CIC-6102 IPL3 replacement
case UINT64_C(0x000000d2828281b0): // Unknown. Used in some homebrew
case UINT64_C(0x000000d2531456f6): // Unknown. Used in some homebrew
case UINT64_C(0x000000d28bc5f6ae): // Unknown. Used in some homebrew
case UINT64_C(0x000000d2be3c4486): // Xeno Crisis custom IPL3
case UINT64_C(0x0000009acc31e644): // HW1 IPL3 (Turok E3 prototype)
case UINT64_C(0x0000009474732e6b): // IPL3 re-assembled with the GNU assembler (iQue)
cic = (system_type != SystemType::PAL) ? CIC::CIC_NUS_6102 : CIC::CIC_NUS_7101; break;
case UINT64_C(0x000000a405397b05): // CIC-7102 IPL3
case UINT64_C(0x000000a3fc388adb): // NOP:ed out CRC check
system_type = SystemType::PAL; cic = CIC::CIC_NUS_7102; break;
case UINT64_C(0x000000a0f26f62fe): // CIC-6101 IPL3
case UINT64_C(0x000000a0e96e72d4): // NOP:ed out CRC check
system_type = SystemType::NTSC; cic = CIC::CIC_NUS_6101; break;
case UINT64_C(0x000000a9229d7c45): // CIC-x103 IPL3
case UINT64_C(0x000000a9199c8c1b): // NOP:ed out CRC check
case UINT64_C(0x000000271316d406): // All zeros bar font (iQue Paper Mario)
cic = (system_type != SystemType::PAL) ? CIC::CIC_NUS_6103 : CIC::CIC_NUS_7103; break;
case UINT64_C(0x000000f8b860ed00): // CIC-x105 IPL3
case UINT64_C(0x000000f8af5ffcd6): // NOP:ed out CRC check
cic = (system_type != SystemType::PAL) ? CIC::CIC_NUS_6105 : CIC::CIC_NUS_7105; break;
case UINT64_C(0x000000ba5ba4b8cd): // CIC-x106 IPL3
cic = (system_type != SystemType::PAL) ? CIC::CIC_NUS_6106 : CIC::CIC_NUS_7106; break;
case UINT64_C(0x0000012daafc8aab): cic = CIC::CIC_NUS_5167; break;
case UINT64_C(0x000000a9df4b39e1): cic = CIC::CIC_NUS_8303; break;
case UINT64_C(0x000000aa764e39e1): cic = CIC::CIC_NUS_8401; break;
case UINT64_C(0x000000abb0b739e1): cic = CIC::CIC_NUS_DDUS; break;
case UINT64_C(0x00000081ce470326): // CIC-5101 IPL3
case UINT64_C(0x000000827a47195a): // Kuru Kuru Fever
case UINT64_C(0x00000082551e4848): // Tower & Shaft
cic = CIC::CIC_NUS_5101; break;
}
} while (cic == CIC::UNKNOWN);
printf("Region: %s, CIC: CIC-NUS-%s\n", stringify(system_type), stringify(cic));
if (!is_auto()) {
printf("Auto-detect is OFF, not updating OSD settings.\n");
return true;
}
printf("Auto-detect is ON, updating OSD settings.\n");
user_io_status_set(SYS_TYPE_OPT, (uint32_t)system_type);
user_io_status_set(CIC_TYPE_OPT, (uint32_t)cic);
return is_known_signature;
}
// Creates a lower-case hex string out of the MD5 buffer
static void md5_to_hex(uint8_t* md5, char* out) {
for (size_t i = 0; i < MD5_LENGTH; i++, md5++, out += 2)
sprintf(out, "%02x", *md5);
}
static void calc_bootcode_checksums(uint64_t bootcode_sums[2], const uint8_t* buf) {
size_t i;
uint64_t sum = 0;
// Calculate boot code checksum for bytes 0x40 - 0xc00 (Aleck64)
for (i = 0x40 / sizeof(uint32_t); i < 0xc00 / sizeof(uint32_t); i++) {
sum += ((uint32_t*)buf)[i];
}
bootcode_sums[1] = sum;
// Calculate boot code checksum for bytes 0x40 - 0x1000
for (; i < 0x1000 / sizeof(uint32_t); i++) {
sum += ((uint32_t*)buf)[i];
}
bootcode_sums[0] = sum;
}
static void create_save_path(char* save_path, const MemoryType type) {
create_path(SAVE_DIR, CoreName2);
sprintf(save_path, SAVE_DIR"/%s/", CoreName2);
char* fname = save_path + strlen(save_path);
char* p = strrchr(current_rom_path, '/');
if (p) {
strcat(fname, p + 1);
}
else {
strcat(fname, current_rom_path);
}
char ext[16];
switch (type) {
case MemoryType::EEPROM_512:
case MemoryType::EEPROM_2k:
strcpy(ext, ".eep");
break;
case MemoryType::SRAM_32k:
case MemoryType::SRAM_96k:
strcpy(ext, ".sra");
break;
case MemoryType::FLASH_128k:
strcpy(ext, ".fla");
break;
case MemoryType::CPAK:
case MemoryType::TPAK:
sprintf(ext, "_%u%s",
(mounted_save_files + ((get_cart_save_type() == MemoryType::NONE) ? 1 : 0)),
((type == MemoryType::CPAK) ? ".cpk" : ".tpk"));
break;
default:
strcpy(ext, ".sav");
break;
}
p = strrchr(fname, '.');
if (p) {
strcpy(p, ext);
}
else {
strcat(fname, ext);
}
}
static void mount_save_core(const char* save_path, const MemoryType type, const char* old_path) {
auto save_file = new N64SaveFile(type);
save_file->mount(save_path, old_path);
}
static void mount_save(const MemoryType type, const char* old_path) {
static char save_path[1024];
create_save_path(save_path, type);
mount_save_core(save_path, type, old_path);
}
static void unmount_all_saves() {
for (size_t i = 0; i < (sizeof(save_files) / sizeof(*save_files)); i++) {
if (save_files[i]) {
save_files[i]->unmount();
delete save_files[i];
save_files[i] = nullptr;
}
}
mounted_save_files = 0;
}
static void mount_save_gb(const char* old_path) {
static const size_t games_path_len = strlen(GAMES_DIR"/");
static const char* ext_gb_save = ".sav";
static const char* backup_suffix = "_bak";
static char save_path[1024];
static char tpak_path[1024];
static char backup_path[1024];
static char core_name[32] = "GAMEBOY";
create_save_path(tpak_path, MemoryType::TPAK);
char* p;
if (!*current_rom_path_gb) {
printf("No Game Boy game mounted. Will use regular .tpk save file.\n");
mount_save_core(tpak_path, MemoryType::TPAK, old_path);
return;
}
if (strncmp(current_rom_path_gb, GAMES_DIR"/", games_path_len) ||
!(p = strchr(current_rom_path_gb + games_path_len, '/'))) {
printf("Unrecognized Game Boy game save path. Will use \"/" SAVE_DIR"/%s/\".\n", core_name);
}
else {
const uint32_t core_name_len = p - (current_rom_path_gb + games_path_len);
if (!strncmp(current_rom_path_gb + games_path_len, CoreName2, core_name_len)) {
printf("Game Boy game loaded from \"/" GAMES_DIR"/%s/\". Will use \"/" SAVE_DIR"/%s/\".\n", CoreName2, core_name);
}
else {
memcpy(core_name, current_rom_path_gb + games_path_len, core_name_len);
core_name[core_name_len] = '\0';
printf("Game Boy core name is: %s\n", core_name);
}
}
sprintf(save_path, SAVE_DIR"/%s/", core_name);
char* fname = save_path + strlen(save_path);
p = strrchr(current_rom_path_gb, '/');
if (p) {
strcat(fname, p + 1);
}
else {
strcat(fname, current_rom_path_gb);
}
p = strrchr(fname, '.');
if (p) {
strcpy(p, ext_gb_save);
}
else {
strcat(fname, ext_gb_save);
}
auto save_file = new N64SaveFile(MemoryType::TPAK);
if (!FileExists(save_path)) {
create_path(SAVE_DIR, core_name);
if (!save_file->copy_file(tpak_path, save_path, true, true)) {
save_file->create_if_missing(save_path, old_path);
printf("Created new Game Boy save file \"/%s/\".\n", save_path);
}
else {
printf("Copied old .tpk save file to \"/%s/\".\n", save_path);
}
}
else {
save_file->copy_file(save_path, tpak_path, false, true);
strcpy(backup_path, save_path);
p = backup_path + strlen(save_path) - strlen(ext_gb_save);
strcpy(p, backup_suffix);
strcpy(p + strlen(backup_suffix), ext_gb_save);
if (!save_file->copy_file(save_path, backup_path, false)) {
printf("Failed to create a backup of \"/%s\"! Will use regular .tpk save file.\n", save_path);
save_file->mount(tpak_path, old_path);
return;
}
else {
printf("Created a backup of \"/%s\" at \"/%s\".\n", save_path, backup_path);
}
}
save_file->mount(save_path);
}
static void get_old_save_path(char* save_path) {
static const char* ext = ".sav";
create_path(SAVE_DIR, CoreName2);
sprintf(save_path, SAVE_DIR"/%s/", CoreName2);
char* fname = save_path + strlen(save_path);
char* p = strrchr(current_rom_path, '/');
if (p) {
strcat(fname, p + 1);
}
else {
strcat(fname, current_rom_path);
}
p = strrchr(fname, '.');
if (p) {
strcpy(p, ext);
}
else {
strcat(fname, ext);
}
}
void n64_load_savedata(uint64_t lba, int ack, uint64_t& buffer_lba, uint8_t* buffer, uint32_t buffer_size, uint32_t blksz, uint32_t sz) {
int invalid = 0;
int done = 0;
unsigned char file_idx = 0;
int64_t pos = lba * blksz;
N64SaveFile* save_file;
do {
if ((file_idx >= mounted_save_files) || !(save_file = save_files[file_idx]) || !save_file->is_mounted()) {
buffer_lba = -1;
invalid = 1;
break;
}
if (pos < get_save_offset(file_idx + 1)) {
break;
}
file_idx++;
} while (1);
fileTYPE* image;
if (!invalid && (image = save_file->get_image()) && image->size) {
diskled_on();
pos -= get_save_offset(file_idx);
uint32_t read_sz;
if (FileSeek(image, pos, SEEK_SET) && (read_sz = FileReadAdv(image, buffer, sz))) {
if ((save_file->type == MemoryType::CPAK) || (save_file->type == MemoryType::TPAK)) {
normalize_data(buffer, read_sz, ByteOrder::LITTLE_ENDIAN);
}
if (read_sz < sz) {
// Pad block that wasn't filled completely
memset(buffer + read_sz, 0, sz - read_sz);
}
done = 1;
buffer_lba = lba;
}
}
// Even after error we have to provide the block to the core
// Give an empty block.
if (!done || invalid) {
memset(buffer, 0, buffer_size);
}
// Data is now stored in buffer. Send it to fpga
EnableIO();
spi_w(UIO_SECTOR_RD | ack);
spi_block_write(buffer, user_io_get_width(), sz);
DisableIO();
if (done && ((pos + blksz) >= image->size)) {
printf("Loaded save data from \"%s\". (%lld bytes)\n", get_image_name(file_idx), image->size);
}
}
void n64_save_savedata(uint64_t lba, int ack, uint64_t& buffer_lba, uint8_t* buffer, uint32_t blksz, uint32_t sz) {
menu_process_save();
buffer_lba = -1;
int invalid = 0;
int done = 0;
unsigned char file_idx = 0;
int64_t pos = lba * blksz;
N64SaveFile* save_file;
do {
if ((file_idx >= mounted_save_files) || !(save_file = save_files[file_idx]) || !save_file->is_mounted()) {
invalid = 1;
break;
}
if (pos < get_save_offset(file_idx + 1)) {
break;
}
file_idx++;
} while (1);
// Fetch sector data from FPGA ...
EnableIO();
spi_w(UIO_SECTOR_WR | ack);
spi_block_read(buffer, user_io_get_width(), sz);
DisableIO();
if (invalid) {
return;
}
pos -= get_save_offset(file_idx);
fileTYPE* image;
if (!sz || !(image = save_file->get_image()) || (pos >= image->size)) {
return;
}
if (pos + sz > image->size) {
sz = image->size - pos;
}
diskled_on();
if (FileSeek(image, pos, SEEK_SET)) {
if ((save_file->type == MemoryType::CPAK) || (save_file->type == MemoryType::TPAK)) {
normalize_data(buffer, sz, ByteOrder::LITTLE_ENDIAN);
}
done = FileWriteAdv(image, buffer, sz, -1) >= 0;
}
if (done && ((pos + blksz) >= image->size)) {
printf("Saved save data to \"%s\". (%lld bytes)\n", get_image_name(file_idx), image->size);
}
}
static void mount_all_saves() {
get_old_save_path(old_save_path);
auto cart_save_type = get_cart_save_type();
if (cart_save_type != MemoryType::NONE) {
mount_save(cart_save_type, old_save_path);
}
auto use_cpak = (bool)user_io_status_get(CPAK_OPT);
// First controller can be either tpak or cpak. Tpak is prioritized.
if ((bool)user_io_status_get(TPAK_OPT)) {
mount_save_gb(old_save_path);
}
else if (use_cpak) {
mount_save(MemoryType::CPAK, old_save_path);
}
if (use_cpak) {
mount_save(MemoryType::CPAK, old_save_path);
mount_save(MemoryType::CPAK, old_save_path);
mount_save(MemoryType::CPAK, old_save_path);
}
}
static int cheat_compare(const volatile uint8_t* mem, const int32_t new_val, const cheat_code_flags flags) {
if (flags.cmp_type == ComparisionType::OPTYPE_ALWAYS)
return 1;
uint32_t old_val_masked = 0, new_val_masked = 0;
// Mask and reverse
for (size_t i = 0; flags.cmp_mask >> i; i++) {
old_val_masked <<= 8;
new_val_masked <<= 8;
if (flags.cmp_mask & (0x1 << i)) {
old_val_masked |= mem[i];
new_val_masked |= (new_val >> (i * 8)) & 0xff;
}
}
// Return "0" if not equal according to comparision type, so next code will get skipped
switch (flags.cmp_type) {
case ComparisionType::OPTYPE_EQUALS:
return new_val_masked == old_val_masked;
case ComparisionType::OPTYPE_GREATER:
return new_val_masked > old_val_masked;
case ComparisionType::OPTYPE_LESS:
return new_val_masked < old_val_masked;
case ComparisionType::OPTYPE_GREATER_EQ:
return new_val_masked >= old_val_masked;
case ComparisionType::OPTYPE_LESS_EQ:
return new_val_masked <= old_val_masked;
case ComparisionType::OPTYPE_NOT_EQ:
return new_val_masked != old_val_masked;
default:
// Unknown comparision type
return -1;
}
}
static int cheat_execute(cheat_code* code) {
if ((code->address >= RAM_SIZE) || !code->flags.cmp_mask)
return -1;
// Boot code, run only once. Skip if already executed.
if (code->flags.is_boot_code) {
if (code->flags.is_boot_code_executed)
return 1;
code->flags.is_boot_code_executed = true;
}
// Game Shark button code. Only run if certain button is pressed. Hard-coded to F5 right now.
if (code->flags.is_gs_button_code && !is_key_pressed(63))
return 1;
volatile uint8_t* mem = ((volatile uint8_t*)rdram_ptr) + code->address;
int result = cheat_compare(mem, code->replace, code->flags);
if (result != 1) return result;
if ((code->flags.cmp_mask == 0xf) && !(code->address & 0x3)) {
// 32-bit aligned write
*(uint32_t*)mem = code->replace;
}
else if ((code->flags.cmp_mask == 0x3) && !(code->address & 0x1)) {
// 16-bit aligned write
*(uint16_t*)mem = code->replace & 0xffff;
}
else {
// Write byte-by-byte to memory in big-endian order
if (code->flags.cmp_mask & (0x1 << 0)) mem[0] = (code->replace >> (8 * 0)) & 0xff;
if (code->flags.cmp_mask & (0x1 << 1)) mem[1] = (code->replace >> (8 * 1)) & 0xff;
if (code->flags.cmp_mask & (0x1 << 2)) mem[2] = (code->replace >> (8 * 2)) & 0xff;
if (code->flags.cmp_mask & (0x1 << 3)) mem[3] = (code->replace >> (8 * 3)) & 0xff;
}
return 1;
}
void n64_cheats_send(const void* buf_ptr, const uint32_t size) {
cheat_codes = (cheat_code*)buf_ptr;
cheat_codes_count = size;
}
static unsigned long poll_timer = 0;
void n64_reset() {
printf("Resetting N64...\n");
if (cheat_codes && cheats_loaded() && cheats_enabled()) {
for (uint32_t i = 0; i < cheat_codes_count; i++) {
cheat_codes[i].flags.is_boot_code_executed = false;
}
poll_timer = GetTimer(2500);
}
}
void n64_poll() {
static uint8_t adj = 0;
if (!poll_timer || CheckTimer(poll_timer)) {
if (!(loaded && is_fpga_ready(0))) {
poll_timer = GetTimer(1000);
printf("Waiting for N64 game to be loaded...\n");
return;
}
auto system_type = (SystemType)user_io_status_get(SYS_TYPE_OPT);
// 17, 17, 16, 17, 17, 16... Repeated, to achieve (1 / .01667 ms) = 60 Hz.
poll_timer = GetTimer((system_type == SystemType::PAL) ? 20 : ((adj > 1) ? 17 : 16));
if (adj > 3 || --adj == 0) adj = 3;
if (cheats_loaded()) {
if (rdram_ptr == (void*)-1) return;
if (!rdram_ptr) {
if (!(rdram_ptr = shmem_map(0x30000000, RAM_SIZE))) {
rdram_ptr = (void*)-1;
printf("Failed to map RDRAM!\n");
Info("Failed to initialize cheat engine!", 2000);
}
else {
printf("Mapped RDRAM at 0x%" PRIXPTR ".\n", (uintptr_t)rdram_ptr);
}
}
else if (cheat_codes && cheats_enabled()) {
for (uint32_t i = 0; i < cheat_codes_count; i++) {
switch (cheat_execute(&cheat_codes[i])) {
case 0:
// Unfullfilled conditional code (DXXXXXXX), skip the next flagged code(s)
while ((i < (cheat_codes_count - 1)) && (cheat_codes[i + 1].compare & 0x1)) {
i++;
}
// Fall through
case 1:
// Normal code (8XXXXXXX)
continue;
}
// Invalid or unhandled code.
printf("Invalid cheat code: %08x\t%08x\t%08x\t%08x !\n",
cheat_codes[i].address,
cheat_codes[i].compare,
cheat_codes[i].replace,
*(uint32_t*)&cheat_codes[i].flags);
Info("Invalid cheat code! Disabling cheats.", 1500);
cheats_disable();
break;
}
}
}
}
}
int n64_rom_tx(const char* name, unsigned char idx, uint32_t load_addr, uint32_t& file_crc) {
static uint8_t buf[4096];
fileTYPE f;
if (!FileOpen(&f, name, 1)) {
return 0;
}
uint32_t data_size = f.size;
uint32_t data_left = data_size;
printf("N64 file \"%s\" with %u bytes to send for index %02x.\n", name, data_size, idx);
unmount_all_saves();
// Set index byte
user_io_set_index(idx);
if (((idx & 0x3f) == 2) && data_size) {
// Handle non-N64 files (Game Boy)
bool should_reset = false;
if (!(*current_rom_path_gb) || strcmp(current_rom_path_gb, name)) {
strcpy(current_rom_path_gb, name);
if (*current_rom_path && user_io_status_get(TPAK_OPT)) {
mount_all_saves();
// Force reload of backup RAM
user_io_status_set(RELOAD_SAVE_OPT, 1);
usleep(100000);
user_io_status_set(RELOAD_SAVE_OPT, 0);
// Hold reset
user_io_status_set(RESET_OPT, 1);
should_reset = true;
}
}
// Prepare transmission of Game Boy file
user_io_set_download(1);
ProgressMessage();
while (data_left) {
uint32_t chunk = (data_left > sizeof(buf)) ? sizeof(buf) : data_left;
FileReadAdv(&f, buf, chunk);
user_io_file_tx_data(buf, chunk);
ProgressMessage("Loading", f.name, data_size - data_left, data_size);
data_left -= chunk;
}
printf("Done loading Game Boy ROM.\n");
FileClose(&f);
// Signal end of transmission
user_io_set_download(0);
ProgressMessage();
if (should_reset) {
// Release reset
user_io_status_set(RESET_OPT, 0);
}
return 1;
}
loaded = 0;
if (rdram_ptr && rdram_ptr != (void*)-1) {
shmem_unmap(rdram_ptr, RAM_SIZE);
rdram_ptr = nullptr;
}
// save state processing
process_ss(name);
/* 0 = Nothing detected
1 = System region and CIC detected
2 = Found some ROM info in DB (Save type etc.), but System region and/or CIC has not been determined
3 = Has detected everything, System type, CIC, Save type etc. */
uint8_t rom_settings_detected = 0;
ByteOrder rom_endianness;
uint8_t md5[MD5_LENGTH];
char md5_hex[MD5_LENGTH * 2 + 1];
uint64_t bootcode_sums[2] = { };
uint8_t controller_settings[4] = { };
bool is_first_chunk = true;
char cart_id[CARTID_LENGTH + 1] = { };
char internal_name[20 + 1];
memset(patches, 0, sizeof(patches));
// CRC32 is used for cheat look-up
file_crc = 0;
void* mem = load_addr ? (uint8_t*)shmem_map(fpga_mem(load_addr), data_size) : nullptr;
uint8_t* write_ptr = (uint8_t*)mem;
MD5Context ctx;
MD5Init(&ctx);
// prepare transmission of new file
user_io_set_download(1, load_addr ? data_size : 0);
ProgressMessage();
while (data_left) {
size_t chunk = (data_left > sizeof(buf)) ? sizeof(buf) : data_left;
FileReadAdv(&f, buf, chunk);
// Perform sanity checks and detect ROM endianness
if (is_first_chunk) {
if (chunk < 4096) {
// Signal end of transmission
user_io_set_download(0);
*current_rom_path = '\0';
printf("Failed to load ROM: must be at least 4096 bytes.\n");
return 0;
}
rom_endianness = detect_rom_endianness(buf);
}
// Normalize data to big-endian format, if needed
normalize_data(buf, chunk, rom_endianness);
MD5Update(&ctx, buf, chunk);
if (is_first_chunk) {
/* Try to detect ROM settings based on header MD5 hash.
For calculating the MD5 hash of the header, we need to make a
copy of the context before calling MD5Final, otherwise the file
hash will be incorrect later on. */
MD5Context ctx_header;
memcpy(&ctx_header, &ctx, sizeof(struct MD5Context));
MD5Final(md5, &ctx_header);
md5_to_hex(md5, md5_hex);
printf("Header MD5 hash: %s\n", md5_hex);
trim(internal_name, 20, (char*)&buf[0x20]);
rom_settings_detected = detect_rom_settings_in_dbs_with_md5(md5_hex);
memcpy(controller_settings, &buf[0x34], sizeof(controller_settings));
calc_bootcode_checksums(bootcode_sums, buf);
/* The first byte (starting at 0x3b) indicates the type of ROM
'N' = Cartridge
'D' = 64DD disk
'C' = Cartridge part of expandable game
'E' = 64DD expansion for cart
'Z' = Aleck64 cart
The 2nd and 3rd byte form a 2-letter ID for the game
The 4th byte indicates the region and language for the game
The 5th byte indicates the revision of the game */
auto p_cid = (char*)&buf[0x3b];
for (auto i = 0; i < 4; i++, p_cid++) {
if (isalnum(*p_cid)) {
cart_id[i] = *p_cid;
}
else {
cart_id[i] = '?';
}
}
if (strncmp(cart_id, "????", 4)) {
sprintf(cart_id + 4, "%02X", buf[0x3f]);
printf("Cartridge ID: %s\n", cart_id);
}
else {
memset(cart_id, '\0', CARTID_LENGTH);
}
}
// Copy to DDR memory for fast ROM loading
if (mem) {
memcpy(write_ptr, buf, chunk);
write_ptr += chunk;
}
else {
// Fallback to normal (slow) loading
user_io_file_tx_data(buf, chunk);
}
ProgressMessage("Loading", f.name, data_size - data_left, data_size);
data_left -= chunk;
is_first_chunk = false;
// CRC32 is used for cheat look-up. Cheat files from gamehacking.org use byte swapped CRC32 for some reason...
normalize_data(buf, chunk, ByteOrder::BYTE_SWAPPED);
file_crc = crc32(file_crc, buf, chunk);
}
MD5Final(md5, &ctx);
md5_to_hex(md5, md5_hex);
printf("File MD5: %s\n", md5_hex);
// Try to detect ROM settings from full file MD5 if they're are not detected yet
if (!rom_settings_detected) {
printf("No ROM information found for header hash.\n");
rom_settings_detected = detect_rom_settings_in_dbs_with_md5(md5_hex);
// Try to detect ROM settings by cart ID if they're are not detected yet
if (!rom_settings_detected) {
printf("No ROM information found for file hash.\n");
rom_settings_detected = detect_rom_settings_in_dbs_with_cartid(cart_id);
if (!rom_settings_detected) {
if (detect_homebrew_header(controller_settings, cart_id)) {
rom_settings_detected = 2;
}
else {
printf("No ROM information found for Cart ID.\n");
if (is_auto()) {
// Defaulting misc. System Settings, everything OFF
user_io_status_set(NO_EPAK_OPT, 0); // Enable Expansion Pak
user_io_status_set(CPAK_OPT, 0); // Disable Controller Pak
user_io_status_set(RPAK_OPT, 0); // Disable Rumble Pak
user_io_status_set(TPAK_OPT, 0); // Disable Transfer Pak
user_io_status_set(RTC_OPT, 0); // Disable RTC
set_cart_save_type(MemoryType::NONE); // Disable Save
}
}
}
}
}
if (!(rom_settings_detected & 1) &&
detect_rom_settings_from_first_chunk(cart_id[3], bootcode_sums, sizeof(bootcode_sums) / sizeof(*bootcode_sums))) {
// Try detect (partial) ROM settings by analyzing the ROM itself. (System region and CIC)
rom_settings_detected |= 1;
}
printf("Done loading N64 ROM.\n");
FileClose(&f);
bool is_patched = false;
if (mem) {
if (patches_enabled()) {
for (auto patch = patches; patch->address * 4 < data_size && patch->diff; patch++) {
if (patch->turbo_core_only && !is_turbo_core()) continue;
((uint32_t*)mem)[patch->address] ^= patch->diff;
is_patched = true;
}
}
shmem_unmap(mem, data_size);
}
strcpy(current_rom_path, name);
mount_all_saves();
// Signal end of transmission
user_io_set_download(0);
ProgressMessage();
loaded = 1;
if (!cfg.controller_info || !is_auto()) {
return 1;
}
auto system_type = SystemType::UNKNOWN;
auto cic = CIC::UNKNOWN;
char info[256];
size_t len = sprintf(info, "Auto-detect:");
if (*cart_id && ((cart_id[1] != 'E') || (cart_id[2] != 'D'))) len += sprintf(info + len, "\n[%.4s] v.%u.%u", cart_id, hex_to_dec(cart_id[4]) + 1, hex_to_dec(cart_id[5]));
if (*internal_name) len += sprintf(info + len, "\n\"%s\"", internal_name);
if (!(rom_settings_detected & 1)) {
len += sprintf(info + len, "\nUnknown Region/CIC");
}
else {
system_type = (SystemType)user_io_status_get(SYS_TYPE_OPT);
cic = (CIC)user_io_status_get(CIC_TYPE_OPT);
len += sprintf(info + len, "\nRegion: %s (%s)", stringify(system_type), stringify(cic));
}
if (!(rom_settings_detected & 2)) {
sprintf(info + len, "\nROM missing from database.\nYou might not be able to save.");
Info(info, cfg.controller_info * 1000);
}
else {
auto save_type = get_cart_save_type();
auto no_epak = (bool)user_io_status_get(NO_EPAK_OPT);
auto tpak = (bool)user_io_status_get(TPAK_OPT);
auto cpak = (bool)user_io_status_get(CPAK_OPT);
auto rpak = (bool)user_io_status_get(RPAK_OPT);
auto rtc = (bool)user_io_status_get(RTC_OPT);
if (save_type != MemoryType::NONE) len += sprintf(info + len, "\nSave Type: %s", stringify(save_type));
if (tpak) len += sprintf(info + len, "\nTransfer Pak \x96");
if (cpak) len += sprintf(info + len, "\nController Pak \x96");
if (rpak) len += sprintf(info + len, "\nRumble Pak \x96");
if (rtc) len += sprintf(info + len, "\nRTC \x96");
if (no_epak) len += sprintf(info + len, "\nDisable Exp. Pak \x96");
if (is_patched) sprintf(info + len, "\nPatched \x96");
Info(info, cfg.controller_info * 1000);
}
return 1;
}
#pragma pop_macro("NONE")
#pragma pop_macro("BIG_ENDIAN")
#pragma pop_macro("LITTLE_ENDIAN")