diff --git a/file_io.cpp b/file_io.cpp index dbc2c18..65069bf 100644 --- a/file_io.cpp +++ b/file_io.cpp @@ -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); diff --git a/file_io.h b/file_io.h index 6245478..cf9794d 100644 --- a/file_io.h +++ b/file_io.h @@ -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 diff --git a/support/n64/n64.cpp b/support/n64/n64.cpp index 772c39a..4ea34f3 100644 --- a/support/n64/n64.cpp +++ b/support/n64/n64.cpp @@ -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; } diff --git a/support/n64/n64.h b/support/n64/n64.h index 8b66bc6..b6f2c03 100644 --- a/support/n64/n64.h +++ b/support/n64/n64.h @@ -2,7 +2,10 @@ #define N64_H #include +#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 \ No newline at end of file diff --git a/support/n64/n64_cpak_header.h b/support/n64/n64_cpak_header.h new file mode 100644 index 0000000..5d7ee38 --- /dev/null +++ b/support/n64/n64_cpak_header.h @@ -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 } }; \ No newline at end of file diff --git a/support/n64/n64_joy_emu.cpp b/support/n64/n64_joy_emu.cpp index 5e3d9ad..0b940d9 100644 --- a/support/n64/n64_joy_emu.cpp +++ b/support/n64/n64_joy_emu.cpp @@ -3,81 +3,82 @@ #include #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 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 diff --git a/support/n64/n64_joy_emu.h b/support/n64/n64_joy_emu.h index bf9e20b..9e8403d 100644 --- a/support/n64/n64_joy_emu.h +++ b/support/n64/n64_joy_emu.h @@ -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); diff --git a/user_io.cpp b/user_io.cpp index 6623de6..f387b4f 100644 --- a/user_io.cpp +++ b/user_io.cpp @@ -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()) diff --git a/user_io.h b/user_io.h index 2f25d7e..730d278 100644 --- a/user_io.h +++ b/user_io.h @@ -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();