N64 improvements, save files (#859)

N64 improvements. Split N64 save files into eep, sra, fla, mpk and tpk files.
Add "wide" tag detection to database, auto-switches to widescreen if found.
Prioritize N64-database_user.txt, so that people can override games already in the regular database file.
Honor order that rpak, cpak and tpak are written in database, for p1 controller.
This commit is contained in:
Rikard Bengtsson
2023-12-05 13:55:12 +01:00
committed by GitHub
parent 542b6f4d73
commit e9340593f4
9 changed files with 805 additions and 215 deletions

View File

@@ -814,7 +814,7 @@ int FileCanWrite(const char *name)
return ((st.st_mode & S_IWUSR) != 0);
}
static void create_path(const char *base_dir, const char* sub_dir)
void create_path(const char *base_dir, const char* sub_dir)
{
make_fullpath(base_dir);
mkdir(full_path, S_IRWXU | S_IRWXG | S_IRWXO);

View File

@@ -148,4 +148,6 @@ const char* FileReadLine(fileTextReader *reader);
#define CIFS_DIR "cifs"
#define DOCS_DIR "docs"
void create_path(const char *base_dir, const char* sub_dir);
#endif

View File

@@ -1,4 +1,5 @@
#include "n64.h"
#include "n64_cpak_header.h"
#include "../../menu.h"
#include "../../user_io.h"
@@ -9,8 +10,8 @@
#include "lib/md5/md5.h"
static constexpr size_t CARTID_LENGTH = 6; // Ex: NSME00
static constexpr size_t MD5_LENGTH = 16;
static constexpr auto CARTID_LENGTH = 6U; // Ex: NSME00
static constexpr auto MD5_LENGTH = 16U;
static constexpr auto CARTID_PREFIX = "ID:";
static constexpr auto AR_TYPE_OPT = "[48:47]";
static constexpr auto AUTODETECT_OPT = "[64]";
@@ -29,7 +30,7 @@ static constexpr const char *const CONTROLLER_OPTS[] = { "[51:49]", "[54:52]", "
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;
if (s) while (uint8_t a = *(s++)) h = (h ^ ((a >= 'A') && (a <= 'Z') ? a + ('a' - 'A') : a)) * FNV_PRIME;
return h;
}
@@ -70,7 +71,7 @@ enum class SystemType : uint32_t {
UNKNOWN = ~0U
};
enum class RomFormat : uint32_t {
enum class DataFormat : uint32_t {
BIG_ENDIAN = 0,
BYTE_SWAPPED,
LITTLE_ENDIAN,
@@ -92,14 +93,26 @@ enum class AutoDetect : uint32_t {
OFF
};
enum class AspectRatio : uint32_t {
ORIGINAL = 0,
FULL,
CUSTOM_1,
CUSTOM_2,
UNKNOWN = ~0U
};
static AspectRatio old_ar = AspectRatio::UNKNOWN;
static const char* stringify(MemoryType v) {
switch (v) {
default: return "(none)";
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)";
}
}
@@ -151,7 +164,7 @@ static const char* stringify(bool v) {
: "No";
}
static RomFormat detectRomFormat(const uint8_t *data) {
static DataFormat detectRomFormat(const uint8_t *data) {
// Data should be aligned
const uint32_t val = *(uint32_t *)data;
@@ -162,15 +175,15 @@ static RomFormat detectRomFormat(const uint8_t *data) {
case UINT32_C(0x40123780):
case UINT32_C(0x40072780):
case UINT32_C(0x41123780):
return RomFormat::BIG_ENDIAN;
return DataFormat::BIG_ENDIAN;
case UINT32_C(0x12408037):
case UINT32_C(0x07408027):
case UINT32_C(0x12418037):
return RomFormat::BYTE_SWAPPED;
return DataFormat::BYTE_SWAPPED;
case UINT32_C(0x80371240):
case UINT32_C(0x80270740):
case UINT32_C(0x80371241):
return RomFormat::LITTLE_ENDIAN;
return DataFormat::LITTLE_ENDIAN;
default:
break;
}
@@ -178,22 +191,21 @@ static RomFormat detectRomFormat(const uint8_t *data) {
// Endianness could not be determined, use just first byte.
switch (val & 0xff) {
case 0x80:
return RomFormat::BIG_ENDIAN;
return DataFormat::BIG_ENDIAN;
case 0x37:
case 0x27:
return RomFormat::BYTE_SWAPPED;
return DataFormat::BYTE_SWAPPED;
case 0x40:
case 0x41:
return RomFormat::LITTLE_ENDIAN;
return DataFormat::LITTLE_ENDIAN;
default:
return RomFormat::UNKNOWN;
return DataFormat::UNKNOWN;
}
}
static void normalizeData(uint8_t* data, size_t size, RomFormat format) {
static void normalizeData(uint8_t* data, size_t size, DataFormat format) {
switch (format) {
case RomFormat::BYTE_SWAPPED:
case DataFormat::BYTE_SWAPPED:
size &= ~1U;
for (size_t i = 0; i < size; i += 2) {
auto c0 = data[0];
@@ -203,7 +215,7 @@ static void normalizeData(uint8_t* data, size_t size, RomFormat format) {
data += 2;
}
break;
case RomFormat::LITTLE_ENDIAN:
case DataFormat::LITTLE_ENDIAN:
size &= ~3U;
for (size_t i = 0; i < size; i += 4) {
auto c0 = data[0];
@@ -223,25 +235,154 @@ static void normalizeData(uint8_t* data, size_t size, RomFormat format) {
}
}
static MemoryType get_save_type() {
auto tmp = (MemoryType)user_io_status_get(SAVE_TYPE_OPT);
return (get_save_size(tmp) ? tmp : MemoryType::NONE);
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_save_type(MemoryType v) {
user_io_status_set(SAVE_TYPE_OPT,
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_file_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 size_t create_file(const char* filename, const uint8_t* data, size_t sz) {
sprintf(full_path, "%s/%s", getRootDir(), filename);
printf("Open file %s\n", full_path);
FILE* fp = fopen(full_path, "w");
if (!fp) {
return 0;
}
sz = fwrite(data, sizeof(uint8_t), sz, fp);
printf("Wrote %u bytes\n", sz);
fclose(fp);
return sz;
}
static size_t read_file(const char* filename, uint8_t* data, uint32_t offset, size_t sz) {
sprintf(full_path, "%s/%s", getRootDir(), filename);
printf("Open file %s\n", full_path);
FILE* fp = fopen(full_path, "r");
if (!fp) {
return 0;
}
fseek(fp, 0L, SEEK_END);
size_t fs = ftell(fp);
printf("File is %u bytes\n", fs);
if (offset > fs) {
return 0;
}
if (offset + sz > fs) {
sz = fs - offset;
}
fseek(fp, offset, SEEK_SET);
printf("Reading %u to %u\n", offset, offset + sz);
sz = fread(data, sizeof(uint8_t), sz, fp);
printf("Read %u bytes\n", sz);
fclose(fp);
return sz;
}
static uint8_t mounted_save_files;
static uint8_t save_file_buf[0x20000]; // Largest save size
struct SaveFile {
int idx = -1;
MemoryType type;
const char* get_path() {
if (idx < 0) {
return nullptr;
}
auto file = get_image(idx);
if (!file) {
return nullptr;
}
return file->name;
}
size_t get_size() {
return get_save_size(type);
}
void mount(const char* path) {
idx = mounted_save_files++;
user_io_file_mount(path, idx, 1);
}
// Return "true" if created
bool create_if_missing(const char* path, const char* old_path) {
if (FileExists(path, 0)) {
return false;
}
auto sz = get_size();
memset(save_file_buf, 0, sz);
bool found_old_data = false;
if (sz && FileExists(old_path, 0)) {
uint32_t off = get_save_file_offset((idx < 0) ? mounted_save_files : 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 ((type == MemoryType::CPAK) || (type == MemoryType::TPAK)) {
normalizeData(save_file_buf, sz, DataFormat::LITTLE_ENDIAN);
}
}
}
if (!found_old_data && (type == MemoryType::CPAK)) {
memcpy(save_file_buf, cpak_header[((idx < 0) ? mounted_save_files : idx) % (sizeof(cpak_header) / sizeof(*cpak_header))], sizeof(*cpak_header));
}
if (!create_file(path, save_file_buf, sz)) {
return false;
}
printf("Created file: %s (%u bytes)\n", path, sz);
return true;
}
SaveFile(MemoryType type) {
this->type = type;
}
SaveFile() {
this->type = MemoryType::NONE;
}
};
static SaveFile save_files[8]{};
static bool is_auto() {
return (AutoDetect)user_io_status_get(AUTODETECT_OPT) == AutoDetect::ON;
}
static int8_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 -1;
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 len, const char* str)
@@ -279,11 +420,7 @@ static void trim(char* out, size_t len, const char* str)
out[out_size] = '\0';
// Obfuscate illegal characters
for (size_t i = 0; i < out_size; i++) {
if (out[i] == '\0') {
break;
}
// Valid characters
for (size_t i = 0; (i < out_size) && out[i]; i++) {
if ((out[i] >= 0x20) && (out[i] < 0xa0)) {
continue;
}
@@ -306,37 +443,39 @@ static bool parse_and_apply_db_tags(char *tags) {
bool rtc = false;
bool wide = false;
PadType prefered_pad = PadType::N64_PAD;
for (auto tag = strtok(tags, separator); tag; tag = strtok(nullptr, separator)) {
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; break;
case fnv_hash("rpak"): rpak = true; break;
case fnv_hash("tpak"): tpak = true; 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("wide"): wide = true; 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;
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("wide"): wide = true; 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;
}
}
@@ -369,17 +508,23 @@ static bool parse_and_apply_db_tags(char *tags) {
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_save_type(save_type);
set_cart_save_type(save_type);
if ((PadType)user_io_status_get(CONTROLLER_OPTS[0]) != PadType::SNAC) {
user_io_status_set(CONTROLLER_OPTS[0], (uint32_t)(
tpak ? PadType::N64_PAD_WITH_TPAK :
cpak ? PadType::N64_PAD_WITH_CPAK :
rpak ? PadType::N64_PAD_WITH_RPAK :
PadType::N64_PAD));
user_io_status_set(CONTROLLER_OPTS[0], (uint32_t)prefered_pad);
}
if (wide) user_io_status_set(AR_TYPE_OPT, 1);
auto current_ar = (AspectRatio)user_io_status_get(AR_TYPE_OPT);
if (wide) {
if (current_ar != AspectRatio::FULL) {
old_ar = current_ar;
}
user_io_status_set(AR_TYPE_OPT, (uint32_t)AspectRatio::FULL);
}
else if ((current_ar == AspectRatio::FULL) && (old_ar != AspectRatio::UNKNOWN)) {
user_io_status_set(AR_TYPE_OPT, (uint32_t)old_ar);
old_ar = AspectRatio::UNKNOWN;
}
return (system_type != SystemType::UNKNOWN && cic_type != CIC::UNKNOWN);
}
@@ -396,7 +541,7 @@ static bool md5_matches(const char* line, const char* md5) {
}
// Returns numbers of matching characters if match, otherwize 0
static size_t cart_id_matches(const char* line, const char* cart_id) {
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:"
@@ -407,12 +552,12 @@ static size_t cart_id_matches(const char* line, const char* cart_id) {
// 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 (*lp != '_' && *lp != cart_id[i]) {
return 0; // Character didn't match pattern
if (i && isspace(*lp)) {
return i; // Early termination
}
if (isspace(*lp)) {
return i; // Early termination
if (*lp != '_' && *lp != cart_id[i]) {
return 0; // Character didn't match pattern
}
}
@@ -467,7 +612,7 @@ static uint8_t detect_rom_settings_in_db_with_cartid(const char* cart_id, const
while (const char* line = FileReadLine(&reader)) {
// Skip lines that doesn't start with our ID
if (!(i = cart_id_matches(line, cart_id))) continue;
if (!(i = cart_id_is_match(line, cart_id))) continue;
auto s = line + strlen(CARTID_PREFIX) + i;
auto tags = new char[strlen(s) + 1];
@@ -486,26 +631,26 @@ static uint8_t detect_rom_settings_in_db_with_cartid(const char* cart_id, const
}
static const char* DB_FILE_NAMES[] = {
"N64-database.txt",
"N64-database_user.txt",
"N64-database.txt"
};
static uint8_t detect_rom_settings_in_dbs_with_md5(const char* lookup_hash) {
for (const char* db_file_name : DB_FILE_NAMES) {
const auto detected = detect_rom_settings_in_db(lookup_hash, db_file_name);
if (detected) {
return detected;
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 0;
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 (isupper(c) || isdigit(c)) {
if (isalnum(c)) {
continue;
}
@@ -513,18 +658,19 @@ static uint8_t detect_rom_settings_in_dbs_with_cartid(const char* lookup_id) {
return 0;
}
for (const char* db_file_name : DB_FILE_NAMES) {
const auto detected = detect_rom_settings_in_db_with_cartid(lookup_id, db_file_name);
if (detected) {
return detected;
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 0;
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')) {
if ((cart_id[1] != 'E') || (cart_id[2] != 'D')) {
return false;
}
@@ -535,10 +681,33 @@ static bool detect_homebrew_header(const uint8_t* controller_settings, const cha
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");
set_save_type((MemoryType)hex_to_dec(cart_id[4]));
user_io_status_set(RTC_OPT, (uint32_t)(hex_to_dec(cart_id[5]) & 1));
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 ||
@@ -558,7 +727,7 @@ static bool detect_homebrew_header(const uint8_t* controller_settings, const cha
(controller_settings[2] == 0x03) ||
(controller_settings[3] == 0x03) ? 1 : 0)); // Transfer Pak
uint32_t c_idx = 0;
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) {
@@ -584,17 +753,18 @@ static bool detect_rom_settings_from_first_chunk(const char region_code, const u
bool is_known_cic = true;
switch (region_code) {
case 'D': // Germany
case 'F': // France
case 'H': // Netherlands (Dutch)
case 'I': // Italy
case 'L': // Gateway 64
case 'P': // Europe
case 'S': // Spain
case 'U': // Australia
case 'W': // Scandinavia
case 'X': // Europe
case 'Y': // Europe
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;
default:
system_type = SystemType::NTSC; break;
@@ -604,7 +774,6 @@ static bool detect_rom_settings_from_first_chunk(const char region_code, const u
size_t n = 2;
if (n) do {
// The following checks assume we're on a little-endian platform
switch (*signatures) {
default:
if (--n) {
@@ -615,40 +784,41 @@ static bool detect_rom_settings_from_first_chunk(const char region_code, const u
printf("Unknown CIC (Signature: 0x%016llx), uses default.\n", *signatures);
is_known_cic = false;
// Fall through
case UINT64_C(0x000000a316adc55a):
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(0x0000009acc31e644): // Unknown. Used in some betas and homebrew. Dev boot code?
case UINT64_C(0x0000009474732e6b): // iQue hacks
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):
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):
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):
case UINT64_C(0x000000a9229d7c45): // CIC-x103 IPL3
case UINT64_C(0x000000a9199c8c1b): // NOP:ed out CRC check
case UINT64_C(0x000000271316d406): // iQue hacks
cic = (system_type == SystemType::NTSC) ? CIC::CIC_NUS_6103 : CIC::CIC_NUS_7103; break;
case UINT64_C(0x000000f8b860ed00):
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::NTSC) ? CIC::CIC_NUS_6105 : CIC::CIC_NUS_7105; break;
case UINT64_C(0x000000ba5ba4b8cd):
cic = (system_type == SystemType::NTSC) ? CIC::CIC_NUS_6106 : CIC::CIC_NUS_7106; break;
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):
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("System: %s, CIC: %s\n", stringify(system_type), stringify(cic));
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");
@@ -671,8 +841,7 @@ static void md5_to_hex(uint8_t* md5, char* out) {
}
}
static void calc_bootcode_checksums(uint64_t bootcode_sums[2], const uint8_t* buf)
{
static void calc_bootcode_checksums(uint64_t bootcode_sums[2], const uint8_t* buf) {
size_t i;
uint64_t sum = 0;
@@ -691,6 +860,189 @@ static void calc_bootcode_checksums(uint64_t bootcode_sums[2], const uint8_t* bu
bootcode_sums[0] = sum;
}
// Mount Save File. Return "true" if save file was created
static bool mount_save_file(const char *name, MemoryType type, const char *old_path) {
char save_path[1024];
create_path(SAVE_DIR, CoreName);
sprintf(save_path, "%s/%s/", SAVE_DIR, CoreName);
char* fname = save_path + strlen(save_path);
const char* p = strrchr(name, '/');
if (p) {
strcat(fname, p + 1);
}
else {
strcat(fname, name);
}
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;
}
char* e = strrchr(fname, '.');
if (e) {
strcpy(e, ext);
}
else {
strcat(fname, ext);
}
auto save_file = new SaveFile(type);
auto is_new = save_file->create_if_missing(save_path, old_path);
save_file->mount(save_path);
save_files[save_file->idx] = *save_file;
return is_new;
}
static void get_old_save_path(char* save_path, const char *name) {
constexpr auto ext = ".sav";
create_path(SAVE_DIR, CoreName);
sprintf(save_path, "%s/%s/", SAVE_DIR, CoreName);
char* fname = save_path + strlen(save_path);
const char* p = strrchr(name, '/');
if (p) {
strcat(fname, p + 1);
}
else {
strcat(fname, name);
}
char* e = strrchr(fname, '.');
if (e) {
strcpy(e, 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 fio_size = user_io_get_width();
buffer_lba = -1;
int done = 0;
int invalid = 0;
unsigned char file_idx = 0;
int64_t pos = lba * blksz;
while (pos >= get_save_file_offset(file_idx + 1)) {
file_idx++;
if (file_idx >= mounted_save_files) {
invalid = 1;
buffer_lba = lba;
break;
}
}
if (!invalid) {
fileTYPE* file = get_image(file_idx);
pos -= get_save_file_offset(file_idx);
if (file->size)
{
diskled_on();
if (FileSeek(file, pos, SEEK_SET) && FileReadAdv(file, buffer, buffer_size)) {
// printf("Loaded save data, %u bytes from %s (%lld)\n", sz, file->name, pos);
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);
}
else if ((save_files[file_idx].type == MemoryType::CPAK) || (save_files[file_idx].type == MemoryType::TPAK)) {
normalizeData(buffer, sz, DataFormat::LITTLE_ENDIAN);
}
// data is now stored in buffer. send it to fpga
EnableIO();
spi_w(UIO_SECTOR_RD | ack);
spi_block_write(buffer, fio_size, sz);
DisableIO();
}
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();
int fio_size = user_io_get_width();
buffer_lba = -1;
unsigned char file_idx = 0;
int64_t pos = lba * blksz;
int invalid = 0;
while (pos >= get_save_file_offset(file_idx + 1)) {
file_idx++;
if (file_idx >= mounted_save_files) {
invalid = 1;
break;
}
}
// Fetch sector data from FPGA ...
EnableIO();
spi_w(UIO_SECTOR_WR | ack);
spi_block_read(buffer, fio_size, sz);
DisableIO();
if (invalid) {
return;
}
auto file = get_image(file_idx);
pos -= get_save_file_offset(file_idx);
if (sz) {
diskled_on();
if (FileSeek(file, pos, SEEK_SET)) {
if ((save_files[file_idx].type == MemoryType::CPAK) || (save_files[file_idx].type == MemoryType::TPAK)) {
normalizeData(buffer, sz, DataFormat::LITTLE_ENDIAN);
}
if (!FileWriteAdv(file, buffer, sz)) {
printf("Failed to write save data! (%u bytes to %s at %lld)\n", sz, file->name, pos);
}
}
}
}
static void unmount_all_save_files()
{
fileTYPE *file;
for (auto i = 0U; i < (sizeof(save_files) / sizeof(SaveFile)); i++) {
if ((file = get_image(i)) && file->opened()) {
FileClose(file);
}
save_files[i] = {};
}
mounted_save_files = 0;
}
int n64_rom_tx(const char *name, unsigned char idx) {
static uint8_t buf[4096];
fileTYPE f;
@@ -709,7 +1061,7 @@ int n64_rom_tx(const char *name, unsigned char idx) {
user_io_set_download(1);
const int use_progress = 1;
if (use_progress) ProgressMessage();
if (use_progress) ProgressMessage(0, 0, 0, 0);
if ((idx & 0x3f) == 2) {
// Handle non-N64 files (GameBoy)
@@ -736,12 +1088,14 @@ int n64_rom_tx(const char *name, unsigned char idx) {
// save state processing
process_ss(name);
unmount_all_save_files();
/* 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;
RomFormat rom_format;
DataFormat rom_format;
uint8_t md5[MD5_LENGTH];
char md5_hex[MD5_LENGTH * 2 + 1];
uint64_t bootcode_sums[2] = { };
@@ -763,6 +1117,10 @@ int n64_rom_tx(const char *name, unsigned char idx) {
if (chunk < 4096) {
printf("Failed to load ROM: must be at least 4096 bytes\n");
Info("Invalid ROM!");
// Signal end of transmission
user_io_set_download(0);
ProgressMessage(0, 0, 0, 0);
return 0;
}
@@ -795,9 +1153,9 @@ int n64_rom_tx(const char *name, unsigned char idx) {
calc_bootcode_checksums(bootcode_sums, buf);
/* The first byte (starting at 0x3b) indicates the type of ROM
'N' = cartridge
'N' = Cartridge
'D' = 64DD disk
'C' = cartridge part of expandable game
'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
@@ -830,8 +1188,6 @@ int n64_rom_tx(const char *name, unsigned char idx) {
is_first_chunk = false;
}
MD5Final(md5, &ctx);
md5_to_hex(md5, md5_hex);
printf("File MD5: %s\n", md5_hex);
@@ -846,7 +1202,6 @@ int n64_rom_tx(const char *name, unsigned char idx) {
printf("No ROM information found for file hash: %s\n", md5_hex);
rom_settings_detected = detect_rom_settings_in_dbs_with_cartid(cart_id);
if (rom_settings_detected == 0) {
// "Advanced" Homebrew ROM Header https://n64brew.dev/wiki/ROM_Header
if (detect_homebrew_header(controller_settings, cart_id)) {
rom_settings_detected = 2;
}
@@ -854,14 +1209,16 @@ int n64_rom_tx(const char *name, unsigned char idx) {
printf("No ROM information found for Cart ID: %s\n", cart_id);
if (is_auto()) {
// Defaulting misc. System Settings
// auto is_wide = user_io_status_get(AR_OPT) == 1;
// if (is_wide) user_io_status_set(AR_TYPE_OPT, 0); // Resetting Aspect Ratio to Original
if (old_ar != AspectRatio::UNKNOWN) {
user_io_status_set(AR_TYPE_OPT, (uint32_t)old_ar); // Resetting Aspect Ratio to Original
old_ar = AspectRatio::UNKNOWN;
}
user_io_status_set(NO_EPAK_OPT, 0); // Enable Expansion Pak
user_io_status_set(CPAK_OPT, 1); // Enable Controller 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_save_type(MemoryType::EEPROM_512); // Use the most common save type (?)
set_cart_save_type(MemoryType::NONE);
}
}
}
@@ -881,16 +1238,43 @@ int n64_rom_tx(const char *name, unsigned char idx) {
printf("Done.\n");
FileClose(&f);
// Mount save state
char file_path[1024];
FileGenerateSavePath(name, file_path);
user_io_file_mount(file_path, 0, 1);
auto save_type = get_cart_save_type();
char old_save_path[1024];
get_old_save_path(old_save_path, name);
bool do_reset = false;
if (save_type != MemoryType::NONE) {
do_reset |= mount_save_file(name, save_type, old_save_path);
}
auto use_cpak = (bool)user_io_status_get(CPAK_OPT);
auto use_tpak = (bool)user_io_status_get(TPAK_OPT);
// First controller can be either tpak or cpak. Tpak is prioritized.
if (use_tpak || use_cpak) {
do_reset |= mount_save_file(name,
(use_tpak ? MemoryType::TPAK : MemoryType::CPAK),
old_save_path);
}
if (use_cpak) {
do_reset |= mount_save_file(name, MemoryType::CPAK, old_save_path);
do_reset |= mount_save_file(name, MemoryType::CPAK, old_save_path);
do_reset |= mount_save_file(name, MemoryType::CPAK, old_save_path);
}
// Signal end of transmission
user_io_set_download(0);
ProgressMessage(0, 0, 0, 0);
// reset if new save files were
if (do_reset) {
user_io_status_set("[0]", 1);
usleep(100000);
user_io_status_set("[0]", 0);
}
if (!is_auto()) {
return 1;
}
@@ -900,8 +1284,8 @@ int n64_rom_tx(const char *name, unsigned char idx) {
char info[256];
size_t len = sprintf(info, "Auto-detect:");
if (strlen(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 (strlen(internal_name)) len += sprintf(info + len, "\n\"%s\"", internal_name);
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) == 0) {
len += sprintf(info + len, "\nUnknown Region/CIC");
}
@@ -917,24 +1301,21 @@ int n64_rom_tx(const char *name, unsigned char idx) {
Info(info, 4000);
}
else {
auto save_type = get_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 tpak = (bool)user_io_status_get(TPAK_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 (tpak) len += sprintf(info + len, "\nTransfer Pak \x96");
if (rtc) len += sprintf(info + len, "\nRTC \x96");
if (no_epak) sprintf(info + len, "\nDisable Expansion Pak \x96");
if (no_epak) sprintf(info + len, "\nDisable Exp. Pak \x96");
Info(info, 6000);
}
printf("%s\n", info);
return 1;
}

View File

@@ -2,7 +2,10 @@
#define N64_H
#include <stdint.h>
#include "../../file_io.h"
int n64_rom_tx(const char* name, unsigned char index);
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);
void n64_save_savedata(uint64_t lba, int ack, uint64_t& buffer_lba, uint8_t* buffer, uint32_t blksz, uint32_t sz);
#endif

View File

@@ -0,0 +1,194 @@
static const uint8_t cpak_header[4][768] = { {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x07, 0x00, 0x00, 0x00, 0x07, 0xc2, 0x1e, 0x03, 0x11, 0x8a, 0x6a, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x50, 0xa8, 0xaf, 0x4a,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x07, 0x00, 0x00, 0x00, 0x07, 0xc2, 0x1e, 0x03, 0x11, 0x8a, 0x6a, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x50, 0xa8, 0xaf, 0x4a,
0x00, 0x07, 0x00, 0x00, 0x00, 0x07, 0xc2, 0x1e, 0x03, 0x11, 0x8a, 0x6a, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x50, 0xa8, 0xaf, 0x4a,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x07, 0x00, 0x00, 0x00, 0x07, 0xc2, 0x1e, 0x03, 0x11, 0x8a, 0x6a, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x50, 0xa8, 0xaf, 0x4a,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x71, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03,
0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03,
0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03,
0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03,
0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03,
0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03,
0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03,
0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03,
0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03,
0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03,
0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03,
0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03,
0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03,
0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03,
0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03,
0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03,
0x00, 0x71, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03,
0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03,
0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03,
0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03,
0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03,
0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03,
0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03,
0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03,
0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03,
0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03,
0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03,
0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03,
0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03,
0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03,
0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03,
0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03 }, {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x1a, 0x00, 0x00, 0x00, 0x02, 0xaa, 0x23, 0x00, 0x7b, 0x3c, 0x56, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0xe8, 0x11, 0x17, 0xe1,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x1a, 0x00, 0x00, 0x00, 0x02, 0xaa, 0x23, 0x00, 0x7b, 0x3c, 0x56, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0xe8, 0x11, 0x17, 0xe1,
0x00, 0x1a, 0x00, 0x00, 0x00, 0x02, 0xaa, 0x23, 0x00, 0x7b, 0x3c, 0x56, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0xe8, 0x11, 0x17, 0xe1,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x1a, 0x00, 0x00, 0x00, 0x02, 0xaa, 0x23, 0x00, 0x7b, 0x3c, 0x56, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0xe8, 0x11, 0x17, 0xe1,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x71, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03,
0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03,
0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03,
0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03,
0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03,
0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03,
0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03,
0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03,
0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03,
0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03,
0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03,
0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03,
0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03,
0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03,
0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03,
0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03,
0x00, 0x71, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03,
0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03,
0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03,
0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03,
0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03,
0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03,
0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03,
0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03,
0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03,
0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03,
0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03,
0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03,
0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03,
0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03,
0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03,
0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03 }, {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x26, 0x00, 0x00, 0x00, 0x01, 0x5e, 0xd9, 0x04, 0x95, 0x99, 0xfe, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0xfe, 0x94, 0x01, 0x5e,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x26, 0x00, 0x00, 0x00, 0x01, 0x5e, 0xd9, 0x04, 0x95, 0x99, 0xfe, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0xfe, 0x94, 0x01, 0x5e,
0x00, 0x26, 0x00, 0x00, 0x00, 0x01, 0x5e, 0xd9, 0x04, 0x95, 0x99, 0xfe, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0xfe, 0x94, 0x01, 0x5e,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x26, 0x00, 0x00, 0x00, 0x01, 0x5e, 0xd9, 0x04, 0x95, 0x99, 0xfe, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0xfe, 0x94, 0x01, 0x5e,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x71, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03,
0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03,
0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03,
0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03,
0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03,
0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03,
0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03,
0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03,
0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03,
0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03,
0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03,
0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03,
0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03,
0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03,
0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03,
0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03,
0x00, 0x71, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03,
0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03,
0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03,
0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03,
0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03,
0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03,
0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03,
0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03,
0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03,
0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03,
0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03,
0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03,
0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03,
0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03,
0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03,
0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03 }, {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x1f, 0x00, 0x00, 0x00, 0x04, 0xf6, 0x56, 0x04, 0x06, 0x30, 0xc4, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x2c, 0x44, 0xd3, 0xae,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x1f, 0x00, 0x00, 0x00, 0x04, 0xf6, 0x56, 0x04, 0x06, 0x30, 0xc4, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x2c, 0x44, 0xd3, 0xae,
0x00, 0x1f, 0x00, 0x00, 0x00, 0x04, 0xf6, 0x56, 0x04, 0x06, 0x30, 0xc4, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x2c, 0x44, 0xd3, 0xae,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x1f, 0x00, 0x00, 0x00, 0x04, 0xf6, 0x56, 0x04, 0x06, 0x30, 0xc4, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x2c, 0x44, 0xd3, 0xae,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x71, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03,
0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03,
0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03,
0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03,
0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03,
0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03,
0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03,
0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03,
0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03,
0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03,
0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03,
0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03,
0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03,
0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03,
0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03,
0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03,
0x00, 0x71, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03,
0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03,
0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03,
0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03,
0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03,
0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03,
0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03,
0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03,
0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03,
0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03,
0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03,
0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03,
0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03,
0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03,
0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03,
0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03 } };

View File

@@ -3,81 +3,82 @@
#include <math.h>
#include "../../user_io.h"
#define N64_MAX_DIAG 69
#define N64_MAX_DIST sqrt(N64_MAX_DIAG * N64_MAX_DIAG * 2)
#define N64_MAX_CARDINAL 85
#define OUTER_DEADZONE 2.0f
#define WEDGE_BOUNDARY (N64_MAX_CARDINAL - 69.0f) / 69.0f
static constexpr int MAX_DIAG = 69;
static constexpr int MAX_CARDINAL = 85;
static constexpr float OUTER_DEADZONE = 2.0f;
static constexpr float WEDGE_BOUNDARY = (float)(MAX_CARDINAL - MAX_DIAG) / MAX_DIAG;
static constexpr float MAX_DIST = hypotf(MAX_DIAG, MAX_DIAG);
void stick_swap(int num, int stick, int *num2, int *stick2)
void stick_swap(int num, int stick, int* num2, int* stick2)
{
int get=user_io_status_get("TV",1);
int p2=get%2;
int p3=get&2;
int swap=get&4;
if(swap) //reverse sticks
{
stick=1-stick;
int get = user_io_status_get("TV", 1);
int p2 = get & 1;
int p3 = get & 2;
int swap = get & 4;
//reverse sticks
if (swap) {
stick = stick ? 0 : 1;
}
if(p3) //p1 right stick -> p3
{
if (stick && num<2)
{
num+=2;
stick=0;
//p1 right stick -> p3
if (p3) {
if (stick && (num < 2)) {
num += 2;
stick = 0;
}
else if(!stick && 2<num && num<5) //swap sticks to minimize conflict
{
num-=2;
stick=1;
//swap sticks to minimize conflict
else if (!stick && (2 < num) && (num < 5)) {
num -= 2;
stick = 1;
}
}
if(p2) //p1 right stick -> p2
{
if (stick && ( (num==0) | (num==2)))
{
//p1 right stick -> p2
if (p2) {
if (stick && ((num == 0) || (num == 2))) {
num++;
stick=0;
stick = 0;
}
else if(!stick && num%2==1)
{
else if (!stick && (num % 2 == 1)) {
num--;
stick=1;
stick = 1;
}
}
*num2=num;
*stick2=stick;
*num2 = num;
*stick2 = stick;
}
void n64_joy_emu(int x, int y, int* x2, int* y2, int max_cardinal, float max_range)
{
void n64_joy_emu(const int x, const int y, int* x2, int* y2, int max_cardinal, float max_range) {
// Move to top right quadrant to standardize solutions
const int x_flip = x < 0 ? -1 : 1;
const int y_flip = y < 0 ? -1 : 1;
const int abs_x = x * x_flip;
const int abs_y = y * y_flip;
const float abs_x = x * x_flip;
const float abs_y = y * y_flip;
// Either reduce range to radius 97.5807358037f ((69,69) diagonal of original controller)
// Either reduce range to radius 97.5807358037f ((69, 69) diagonal of original controller)
// or reduce cardinals to 85, whichever is less aggressive (smaller reduction in scaling)
// (subtracts 2 from each to allow for minor outer deadzone)
// assumes the max range is at least 85 (max cardinal of original controller)
if (max_cardinal < N64_MAX_CARDINAL) max_cardinal = N64_MAX_CARDINAL;
if (max_range < N64_MAX_DIST) max_range = N64_MAX_DIST;
float scale_cardinal = N64_MAX_CARDINAL / (max_cardinal - OUTER_DEADZONE);
float scale_range = N64_MAX_DIST / (max_range - OUTER_DEADZONE);
float scale = scale_cardinal > scale_range ? scale_cardinal : scale_range;
float scaled_x = abs_x * scale;
float scaled_y = abs_y * scale;
if (max_cardinal < MAX_CARDINAL) max_cardinal = MAX_CARDINAL;
if (max_range < MAX_DIST) max_range = MAX_DIST;
const float scale_cardinal = MAX_CARDINAL / (max_cardinal - OUTER_DEADZONE);
const float scale_range = MAX_DIST / (max_range - OUTER_DEADZONE);
const float scale = scale_cardinal > scale_range ? scale_cardinal : scale_range;
const float scaled_x = abs_x * scale;
const float scaled_y = abs_y * scale;
// Move to octagon's lower wedge in top right quadrant to further standardize solution
float scaled_max;
float scaled_min;
if (abs_x > abs_y) {
scaled_max = scaled_x;
scaled_min = scaled_y;
} else {
}
else {
scaled_max = scaled_y;
scaled_min = scaled_x;
}
@@ -85,28 +86,23 @@ void n64_joy_emu(int x, int y, int* x2, int* y2, int max_cardinal, float max_ran
// Clamp scaled_min and scaled_max
// Note: wedge boundary is given by x = 85 - y * ((85 - 69) / 69)
// If x + y * (16 / 69) > 85, coordinates exceed boundary and need clamped
float boundary = scaled_max + scaled_min * WEDGE_BOUNDARY;
if (boundary > N64_MAX_CARDINAL) {
const float boundary = scaled_max + scaled_min * WEDGE_BOUNDARY;
if (boundary > MAX_CARDINAL) {
// We know target value is on:
// 1) Boundary line: x = 85 - y * (16 / 69)
// 2) Observed slope line: y = (scaled_max / scaled_min) * x
// Solving system of equations yields:
scaled_min = N64_MAX_CARDINAL * scaled_min / boundary;
scaled_max = N64_MAX_CARDINAL - scaled_min * WEDGE_BOUNDARY; // Boundary line
scaled_min = MAX_CARDINAL * scaled_min / boundary;
scaled_max = MAX_CARDINAL - scaled_min * WEDGE_BOUNDARY; // Boundary line
}
// Move back from wedge to actual coordinates
if (abs_x > abs_y) {
*x2 = x_flip * scaled_max;
*y2 = y_flip * scaled_min;
} else {
*x2 = x_flip * scaled_min;
*y2 = y_flip * scaled_max;
*x2 = nearbyintf(scaled_max * x_flip);
*y2 = nearbyintf(scaled_min * y_flip);
}
else {
*x2 = nearbyintf(scaled_min * x_flip);
*y2 = nearbyintf(scaled_max * y_flip);
}
}
#undef N64_MAX_DIAG
#undef N64_MAX_DIST
#undef N64_MAX_CARDINAL
#undef OUTER_DEADZONE
#undef WEDGE_BOUNDARY

View File

@@ -1,2 +1,2 @@
void stick_swap(int num, int stick, int *num2, int *stick2);
void n64_joy_emu(int x, int y, int* x2, int* y2, int max_cardinal, float max_range);
void n64_joy_emu(const int x, const int y, int* x2, int* y2, int max_cardinal, float max_range);

View File

@@ -96,6 +96,11 @@ const char *get_image_name(int i)
return p;
}
fileTYPE *get_image(int i)
{
return &sd_image[i];
}
static uint32_t uart_mode;
uint32_t user_io_get_uart_mode()
{
@@ -1050,7 +1055,7 @@ const char* get_rbf_path()
return core_path;
}
void MakeFile(const char * filename, const char * data)
void MakeFile(const char *filename, const char *data)
{
FILE * file;
file = fopen(filename, "w");
@@ -1230,14 +1235,14 @@ uint16_t sdram_sz(int sz)
{
*par++ = 0x12;
*par++ = 0x57;
*par++ = (uint8_t)(sz>>8);
*par++ = (uint8_t)(sz >> 8);
*par++ = (uint8_t)sz;
}
else
{
if ((par[0] == 0x12) && (par[1] == 0x57))
{
res = 0x8000 | (par[2]<<8) | par[3];
res = 0x8000 | (par[2] << 8) | par[3];
if(res & 0x4000) printf("*** Debug phase: %d\n", (res & 0x100) ? (res & 0xFF) : -(res & 0xFF));
else printf("*** Found SDRAM config: %d\n", res & 7);
}
@@ -1651,7 +1656,7 @@ void user_io_l_analog_joystick(unsigned char joystick, char valueX, char valueY)
if (core_type == CORE_TYPE_8BIT)
{
spi_uio_cmd8_cont(UIO_ASTICK, joy);
if(io_ver) spi_w((valueY<<8) | (uint8_t)(valueX));
if(io_ver) spi_w((valueY << 8) | (uint8_t)(valueX));
else
{
spi8(valueX);
@@ -1687,7 +1692,7 @@ void user_io_digital_joystick(unsigned char joystick, uint32_t map, int newdir)
spi_uio_cmd_cont((joy < 2) ? (UIO_JOYSTICK0 + joy) : (UIO_JOYSTICK2 + joy - 2));
spi_w(map);
if(use32) spi_w(map>>16);
if(use32) spi_w(map >> 16);
DisableIO();
if (!is_minimig() && joy_transl == 1 && newdir)
@@ -2095,12 +2100,12 @@ int user_io_file_mount(const char *name, unsigned char index, char pre, int pre_
else
{
spi32_b(size);
spi32_b(size>>32);
spi32_b(size >> 32);
}
DisableIO();
// notify core of possible sd image change
spi_uio_cmd8(UIO_SET_SDSTAT, (1<< index) | (writable ? 0 : 0x80));
spi_uio_cmd8(UIO_SET_SDSTAT, (1 << index) | (writable ? 0 : 0x80));
return ret ? 1 : 0;
}
@@ -3051,9 +3056,13 @@ void user_io_poll()
else if (op & 1) c64_readGCR(disk, lba, blks-1);
else break;
}
else if (op == 2 && is_n64() && use_save)
{
n64_save_savedata(lba, ack, buffer_lba[disk], buffer[disk], blksz, sz);
}
else if (op == 2)
{
//printf("SD WR %d on %d\n", lba, disk);
//printf("SD WR %llu on %d\n", lba, disk);
if (use_save) menu_process_save();
@@ -3101,6 +3110,10 @@ void user_io_poll()
}
}
}
else if ((op & 1) && is_n64() && use_save)
{
n64_load_savedata(lba, ack, buffer_lba[disk], buffer[disk], sizeof(*buffer), blksz, sz);
}
else if (op & 1)
{
uint32_t buf_n = sizeof(buffer[0]) / blksz;
@@ -3109,7 +3122,7 @@ void user_io_poll()
int done = 0;
uint32_t offset;
if ((buffer_lba[disk] == (uint64_t)-1) || lba < buffer_lba[disk] || (lba + blks - buffer_lba[disk]) > buf_n)
if ((buffer_lba[disk] == -1LLU) || lba < buffer_lba[disk] || (lba + blks - buffer_lba[disk]) > buf_n)
{
buffer_lba[disk] = -1;
if (blksz == 2352 && is_psx())

View File

@@ -192,6 +192,7 @@ void user_io_name_override(const char* name);
char has_menu();
const char *get_image_name(int i);
fileTYPE *get_image(int i);
int user_io_get_kbdemu();
uint32_t user_io_get_uart_mode();