#include #include #include #include #include #include #include #include #define MEMPAK_SIZE 32768 namespace n64 { void PIF::Reset() { memset(joybusDevices, 0, sizeof(JoybusDevice) * 6); memset(bootrom, 0, PIF_BOOTROM_SIZE); memset(ram, 0, PIF_RAM_SIZE); std::error_code error; if(mempak.is_mapped()) { mempak.sync(error); if (error) { Util::panic("Could not sync {}", mempakPath); } mempak.unmap(); } if(eeprom.is_mapped()) { eeprom.sync(error); if (error) { Util::panic("Could not sync {}", eepromPath); } eeprom.unmap(); } } void PIF::LoadMempak(const std::string& path) { mempakPath = fs::path(path).replace_extension(".mempak").string(); std::error_code error; if (mempak.is_mapped()) { mempak.sync(error); if (error) { Util::panic("Could not sync {}", mempakPath); } mempak.unmap(); } FILE* f = fopen(mempakPath.c_str(), "rb"); if (!f) { f = fopen(mempakPath.c_str(), "wb"); u8* dummy = (u8*)calloc(MEMPAK_SIZE, 1); fwrite(dummy, 1, MEMPAK_SIZE, f); free(dummy); } fseek(f, 0, SEEK_END); size_t actualSize = ftell(f); fseek(f, 0, SEEK_SET); if (actualSize != MEMPAK_SIZE) { Util::panic("Corrupt mempak!"); } fclose(f); mempak = mio::make_mmap_sink( mempakPath, 0, mio::map_entire_file, error); if (error) { Util::panic("Could not open {}", mempakPath); } } FORCE_INLINE size_t getSaveSize(SaveType saveType) { switch (saveType) { case SAVE_NONE: return 0; case SAVE_EEPROM_4k: return 512; case SAVE_EEPROM_16k: return 2048; case SAVE_SRAM_256k: return 32768; case SAVE_FLASH_1m: return 131072; default: Util::panic("Unknown save type!"); } } void PIF::LoadEeprom(SaveType saveType, const std::string& path) { if(saveType == SAVE_EEPROM_16k || saveType == SAVE_EEPROM_4k) { eepromPath = fs::path(path).replace_extension(".eeprom").string(); std::error_code error; if (eeprom.is_mapped()) { eeprom.sync(error); if (error) { Util::panic("Could not sync {}", eepromPath); } eeprom.unmap(); } eepromSize = getSaveSize(saveType); FILE *f = fopen(eepromPath.c_str(), "rb"); if (!f) { f = fopen(eepromPath.c_str(), "wb"); u8* dummy = (u8*)calloc(eepromSize, 1); fwrite(dummy, 1, eepromSize, f); } fseek(f, 0, SEEK_END); size_t actualSize = ftell(f); fseek(f, 0, SEEK_SET); if (actualSize != eepromSize) { Util::panic("Corrupt eeprom!"); } fclose(f); eeprom = mio::make_mmap_sink( eepromPath, 0, mio::map_entire_file, error); if (error) { Util::panic("Could not open {}", eepromPath); } } } enum CMDIndexes { CMD_LEN = 0, CMD_RES_LEN, CMD_IDX, CMD_START }; void PIF::CICChallenge() { u8 challenge[30]; u8 response[30]; // Split 15 bytes into 30 nibbles for (int i = 0; i < 15; i++) { challenge[i * 2 + 0] = (ram[0x30 + i] >> 4) & 0x0F; challenge[i * 2 + 1] = (ram[0x30 + i] >> 0) & 0x0F; } n64_cic_nus_6105((char*)challenge, (char*)response, CHL_LEN - 2); for (int i = 0; i < 15; i++) { ram[0x30 + i] = (response[i * 2] << 4) + response[i * 2 + 1]; } } FORCE_INLINE u8 data_crc(const u8* data) { u8 crc = 0; for (int i = 0; i <= 32; i++) { for (int j = 7; j >= 0; j--) { u8 xor_val = ((crc & 0x80) != 0) ? 0x85 : 0x00; crc <<= 1; if (i < 32) { if ((data[i] & (1 << j)) != 0) { crc |= 1; } } crc ^= xor_val; } } return crc; } #define BCD_ENCODE(x) (((x) / 10) << 4 | ((x) % 10)) #define BCD_DECODE(x) (((x) >> 4) * 10 + ((x) & 15)) void PIF::ProcessCommands(Mem &mem) { u8 control = ram[63]; if (control & 1) { channel = 0; int i = 0; while (i < 63) { u8* cmd = &ram[i++]; u8 cmdlen = cmd[CMD_LEN] & 0x3F; if (cmdlen == 0 || cmdlen == 0x3D) { channel++; } else if (cmdlen == 0x3E) { break; } else if (cmdlen == 0x3F) { continue; } else { u8 r = ram[i++]; if (r == 0xFE) { break; } u8 reslen = r & 0x3F; u8* res = &ram[i + cmdlen]; switch (cmd[CMD_IDX]) { case 0: case 0xff: ControllerID(res); channel++; break; case 1: UpdateController(); if(!ReadButtons(res)) { cmd[1] |= 0x80; } channel++; break; case 2: MempakRead(cmd, res); break; case 3: MempakWrite(cmd, res); break; case 4: EepromRead(cmd, res, mem); break; case 5: EepromWrite(cmd, res, mem); break; case 6: res[0] = 0x00; res[1] = 0x10; res[2] = 0x80; break; case 7: switch(cmd[CMD_START]) { case 0: case 1: case 3: break; case 2: { auto now = std::time(nullptr); auto* gmtm = gmtime(&now); res[0] = BCD_ENCODE(gmtm->tm_sec); res[1] = BCD_ENCODE(gmtm->tm_min); res[2] = BCD_ENCODE(gmtm->tm_hour) + 0x80; res[3] = BCD_ENCODE(gmtm->tm_mday); res[4] = BCD_ENCODE(gmtm->tm_wday); res[5] = BCD_ENCODE(gmtm->tm_mon); res[6] = BCD_ENCODE(gmtm->tm_year); res[7] = (gmtm->tm_year - 1900) >= 100 ? 1 : 0; } break; default: Util::panic("Invalid read RTC block {}", cmd[CMD_START]); } break; case 8: break; default: Util::panic("Invalid PIF command: {:X}", cmd[2]); } i += cmdlen + reslen; } } } if (control & 0x02) { CICChallenge(); ram[63] &= ~2; } if (control & 0x08) { ram[63] &= ~8; } if (control & 0x30) { ram[63] = 0x80; } } void PIF::MempakRead(const u8* cmd, u8* res) const { u16 offset = cmd[3] << 8; offset |= cmd[4]; // low 5 bits are the CRC //byte crc = offset & 0x1F; // offset must be 32-byte aligned offset &= ~0x1F; switch (getAccessoryType()) { case ACCESSORY_NONE: break; case ACCESSORY_MEMPACK: if (offset <= MEMPAK_SIZE - 0x20) { std::copy_n(mempak.begin() + offset, 32, res); } break; case ACCESSORY_RUMBLE_PACK: memset(res, 0x80, 32); break; } // CRC byte res[32] = data_crc(res); } void PIF::MempakWrite(u8* cmd, u8* res) { // First two bytes in the command are the offset u16 offset = cmd[3] << 8; offset |= cmd[4]; // low 5 bits are the CRC //byte crc = offset & 0x1F; // offset must be 32-byte aligned offset &= ~0x1F; switch (getAccessoryType()) { case ACCESSORY_NONE: break; case ACCESSORY_MEMPACK: if (offset <= MEMPAK_SIZE - 0x20) { std::copy_n(cmd + 5, 32, mempak.begin() + offset); } break; case ACCESSORY_RUMBLE_PACK: break; } // CRC byte res[0] = data_crc(&cmd[5]); } void PIF::EepromRead(const u8* cmd, u8* res, const Mem& mem) const { assert(mem.saveType == SAVE_EEPROM_4k || mem.saveType == SAVE_EEPROM_16k); if (channel == 4) { u8 offset = cmd[3]; if ((offset * 8) >= getSaveSize(mem.saveType)) { Util::panic("Out of range EEPROM read! offset: {:02X}", offset); } std::copy_n(eeprom.begin() + offset * 8, 8, res); } else { Util::panic("EEPROM read on bad channel {}", channel); } } void PIF::EepromWrite(const u8* cmd, u8* res, const Mem& mem) { assert(mem.saveType == SAVE_EEPROM_4k || mem.saveType == SAVE_EEPROM_16k); if (channel == 4) { u8 offset = cmd[3]; if ((offset * 8) >= getSaveSize(mem.saveType)) { Util::panic("Out of range EEPROM write! offset: {:02X}", offset); } std::copy_n(cmd + 4, 8, eeprom.begin() + offset * 8); res[0] = 0; // Error byte, I guess it always succeeds? } else { Util::panic("EEPROM write on bad channel {}", channel); } } void PIF::UpdateController() { //if(Netplay::connected) { // Util::panic("Netplay!!!"); //} else { PollController(); //} } void PIF::DoPIFHLE(Mem& mem, Registers& regs, bool pal, CICType cicType) { mem.Write32(regs, PIF_RAM_REGION_START + 0x24, cicSeeds[cicType]); switch(cicType) { case UNKNOWN_CIC_TYPE: Util::warn("Unknown CIC type!"); break; case CIC_NUS_6101: regs.gpr[0] = 0x0000000000000000; regs.gpr[1] = 0x0000000000000000; regs.gpr[2] = 0xFFFFFFFFDF6445CCll; regs.gpr[3] = 0xFFFFFFFFDF6445CCll; regs.gpr[4] = 0x00000000000045CC; regs.gpr[5] = 0x0000000073EE317A; regs.gpr[6] = 0xFFFFFFFFA4001F0Cll; regs.gpr[7] = 0xFFFFFFFFA4001F08ll; regs.gpr[8] = 0x00000000000000C0; regs.gpr[9] = 0x0000000000000000; regs.gpr[10] = 0x0000000000000040; regs.gpr[11] = 0xFFFFFFFFA4000040ll; regs.gpr[12] = 0xFFFFFFFFC7601FACll; regs.gpr[13] = 0xFFFFFFFFC7601FACll; regs.gpr[14] = 0xFFFFFFFFB48E2ED6ll; regs.gpr[15] = 0xFFFFFFFFBA1A7D4Bll; regs.gpr[16] = 0x0000000000000000; regs.gpr[17] = 0x0000000000000000; regs.gpr[18] = 0x0000000000000000; regs.gpr[19] = 0x0000000000000000; regs.gpr[20] = 0x0000000000000001; regs.gpr[21] = 0x0000000000000000; regs.gpr[23] = 0x0000000000000001; regs.gpr[24] = 0x0000000000000002; regs.gpr[25] = 0xFFFFFFFF905F4718ll; regs.gpr[26] = 0x0000000000000000; regs.gpr[27] = 0x0000000000000000; regs.gpr[28] = 0x0000000000000000; regs.gpr[29] = 0xFFFFFFFFA4001FF0ll; regs.gpr[30] = 0x0000000000000000; regs.gpr[31] = 0xFFFFFFFFA4001550ll; regs.lo = 0xFFFFFFFFBA1A7D4Bll; regs.hi = 0xFFFFFFFF997EC317ll; break; case CIC_NUS_7102: regs.gpr[0] = 0x0000000000000000; regs.gpr[1] = 0x0000000000000001; regs.gpr[2] = 0x000000001E324416; regs.gpr[3] = 0x000000001E324416; regs.gpr[4] = 0x0000000000004416; regs.gpr[5] = 0x000000000EC5D9AF; regs.gpr[6] = 0xFFFFFFFFA4001F0Cll; regs.gpr[7] = 0xFFFFFFFFA4001F08ll; regs.gpr[8] = 0x00000000000000C0; regs.gpr[9] = 0x0000000000000000; regs.gpr[10] = 0x0000000000000040; regs.gpr[11] = 0xFFFFFFFFA4000040ll; regs.gpr[12] = 0x00000000495D3D7B; regs.gpr[13] = 0xFFFFFFFF8B3DFA1Ell; regs.gpr[14] = 0x000000004798E4D4; regs.gpr[15] = 0xFFFFFFFFF1D30682ll; regs.gpr[16] = 0x0000000000000000; regs.gpr[17] = 0x0000000000000000; regs.gpr[18] = 0x0000000000000000; regs.gpr[19] = 0x0000000000000000; regs.gpr[20] = 0x0000000000000000; regs.gpr[21] = 0x0000000000000000; regs.gpr[22] = 0x000000000000003F; regs.gpr[23] = 0x0000000000000007; regs.gpr[24] = 0x0000000000000000; regs.gpr[25] = 0x0000000013D05CAB; regs.gpr[26] = 0x0000000000000000; regs.gpr[27] = 0x0000000000000000; regs.gpr[28] = 0x0000000000000000; regs.gpr[29] = 0xFFFFFFFFA4001FF0ll; regs.gpr[30] = 0x0000000000000000; regs.gpr[31] = 0xFFFFFFFFA4001554ll; regs.lo = 0xFFFFFFFFF1D30682ll; regs.hi = 0x0000000010054A98; break; case CIC_NUS_6102_7101: regs.gpr[0] = 0x0000000000000000; regs.gpr[1] = 0x0000000000000001; regs.gpr[2] = 0x000000000EBDA536; regs.gpr[3] = 0x000000000EBDA536; regs.gpr[4] = 0x000000000000A536; regs.gpr[5] = 0xFFFFFFFFC0F1D859ll; regs.gpr[6] = 0xFFFFFFFFA4001F0Cll; regs.gpr[7] = 0xFFFFFFFFA4001F08ll; regs.gpr[8] = 0x00000000000000C0; regs.gpr[9] = 0x0000000000000000; regs.gpr[10] = 0x0000000000000040; regs.gpr[11] = 0xFFFFFFFFA4000040ll; regs.gpr[12] = 0xFFFFFFFFED10D0B3ll; regs.gpr[13] = 0x000000001402A4CC; regs.gpr[14] = 0x000000002DE108EA; regs.gpr[15] = 0x000000003103E121; regs.gpr[16] = 0x0000000000000000; regs.gpr[17] = 0x0000000000000000; regs.gpr[18] = 0x0000000000000000; regs.gpr[19] = 0x0000000000000000; regs.gpr[20] = 0x0000000000000001; regs.gpr[21] = 0x0000000000000000; regs.gpr[23] = 0x0000000000000000; regs.gpr[24] = 0x0000000000000000; regs.gpr[25] = 0xFFFFFFFF9DEBB54Fll; regs.gpr[26] = 0x0000000000000000; regs.gpr[27] = 0x0000000000000000; regs.gpr[28] = 0x0000000000000000; regs.gpr[29] = 0xFFFFFFFFA4001FF0ll; regs.gpr[30] = 0x0000000000000000; regs.gpr[31] = 0xFFFFFFFFA4001550ll; regs.hi = 0x000000003FC18657; regs.lo = 0x000000003103E121; if (pal) { regs.gpr[20] = 0x0000000000000000; regs.gpr[23] = 0x0000000000000006; regs.gpr[31] = 0xFFFFFFFFA4001554ll; } break; case CIC_NUS_6103_7103: regs.gpr[0] = 0x0000000000000000; regs.gpr[1] = 0x0000000000000001; regs.gpr[2] = 0x0000000049A5EE96; regs.gpr[3] = 0x0000000049A5EE96; regs.gpr[4] = 0x000000000000EE96; regs.gpr[5] = 0xFFFFFFFFD4646273ll; regs.gpr[6] = 0xFFFFFFFFA4001F0Cll; regs.gpr[7] = 0xFFFFFFFFA4001F08ll; regs.gpr[8] = 0x00000000000000C0; regs.gpr[9] = 0x0000000000000000; regs.gpr[10] = 0x0000000000000040; regs.gpr[11] = 0xFFFFFFFFA4000040ll; regs.gpr[12] = 0xFFFFFFFFCE9DFBF7ll; regs.gpr[13] = 0xFFFFFFFFCE9DFBF7ll; regs.gpr[14] = 0x000000001AF99984; regs.gpr[15] = 0x0000000018B63D28; regs.gpr[16] = 0x0000000000000000; regs.gpr[17] = 0x0000000000000000; regs.gpr[18] = 0x0000000000000000; regs.gpr[19] = 0x0000000000000000; regs.gpr[20] = 0x0000000000000001; regs.gpr[21] = 0x0000000000000000; regs.gpr[23] = 0x0000000000000000; regs.gpr[24] = 0x0000000000000000; regs.gpr[25] = 0xFFFFFFFF825B21C9ll; regs.gpr[26] = 0x0000000000000000; regs.gpr[27] = 0x0000000000000000; regs.gpr[28] = 0x0000000000000000; regs.gpr[29] = 0xFFFFFFFFA4001FF0ll; regs.gpr[30] = 0x0000000000000000; regs.gpr[31] = 0xFFFFFFFFA4001550ll; regs.lo = 0x0000000018B63D28; regs.hi = 0x00000000625C2BBE; if (pal) { regs.gpr[20] = 0x0000000000000000; regs.gpr[23] = 0x0000000000000006; regs.gpr[31] = 0xFFFFFFFFA4001554ll; } break; case CIC_NUS_6105_7105: regs.gpr[0] = 0x0000000000000000; regs.gpr[1] = 0x0000000000000000; regs.gpr[2] = 0xFFFFFFFFF58B0FBFll; regs.gpr[3] = 0xFFFFFFFFF58B0FBFll; regs.gpr[4] = 0x0000000000000FBF; regs.gpr[5] = 0xFFFFFFFFDECAAAD1ll; regs.gpr[6] = 0xFFFFFFFFA4001F0Cll; regs.gpr[7] = 0xFFFFFFFFA4001F08ll; regs.gpr[8] = 0x00000000000000C0; regs.gpr[9] = 0x0000000000000000; regs.gpr[10] = 0x0000000000000040; regs.gpr[11] = 0xFFFFFFFFA4000040ll; regs.gpr[12] = 0xFFFFFFFF9651F81Ell; regs.gpr[13] = 0x000000002D42AAC5; regs.gpr[14] = 0x00000000489B52CF; regs.gpr[15] = 0x0000000056584D60; regs.gpr[16] = 0x0000000000000000; regs.gpr[17] = 0x0000000000000000; regs.gpr[18] = 0x0000000000000000; regs.gpr[19] = 0x0000000000000000; regs.gpr[20] = 0x0000000000000001; regs.gpr[21] = 0x0000000000000000; regs.gpr[23] = 0x0000000000000000; regs.gpr[24] = 0x0000000000000002; regs.gpr[25] = 0xFFFFFFFFCDCE565Fll; regs.gpr[26] = 0x0000000000000000; regs.gpr[27] = 0x0000000000000000; regs.gpr[28] = 0x0000000000000000; regs.gpr[29] = 0xFFFFFFFFA4001FF0ll; regs.gpr[30] = 0x0000000000000000; regs.gpr[31] = 0xFFFFFFFFA4001550ll; regs.lo = 0x0000000056584D60; regs.hi = 0x000000004BE35D1F; if (pal) { regs.gpr[20] = 0x0000000000000000; regs.gpr[23] = 0x0000000000000006; regs.gpr[31] = 0xFFFFFFFFA4001554ll; } mem.Write32(regs, IMEM_REGION_START + 0x00, 0x3C0DBFC0); mem.Write32(regs, IMEM_REGION_START + 0x04, 0x8DA807FC); mem.Write32(regs, IMEM_REGION_START + 0x08, 0x25AD07C0); mem.Write32(regs, IMEM_REGION_START + 0x0C, 0x31080080); mem.Write32(regs, IMEM_REGION_START + 0x10, 0x5500FFFC); mem.Write32(regs, IMEM_REGION_START + 0x14, 0x3C0DBFC0); mem.Write32(regs, IMEM_REGION_START + 0x18, 0x8DA80024); mem.Write32(regs, IMEM_REGION_START + 0x1C, 0x3C0BB000); break; case CIC_NUS_6106_7106: regs.gpr[0] = 0x0000000000000000; regs.gpr[1] = 0x0000000000000000; regs.gpr[2] = 0xFFFFFFFFA95930A4ll; regs.gpr[3] = 0xFFFFFFFFA95930A4ll; regs.gpr[4] = 0x00000000000030A4; regs.gpr[5] = 0xFFFFFFFFB04DC903ll; regs.gpr[6] = 0xFFFFFFFFA4001F0Cll; regs.gpr[7] = 0xFFFFFFFFA4001F08ll; regs.gpr[8] = 0x00000000000000C0; regs.gpr[9] = 0x0000000000000000; regs.gpr[10] = 0x0000000000000040; regs.gpr[11] = 0xFFFFFFFFA4000040ll; regs.gpr[12] = 0xFFFFFFFFBCB59510ll; regs.gpr[13] = 0xFFFFFFFFBCB59510ll; regs.gpr[14] = 0x000000000CF85C13; regs.gpr[15] = 0x000000007A3C07F4; regs.gpr[16] = 0x0000000000000000; regs.gpr[17] = 0x0000000000000000; regs.gpr[18] = 0x0000000000000000; regs.gpr[19] = 0x0000000000000000; regs.gpr[20] = 0x0000000000000001; regs.gpr[21] = 0x0000000000000000; regs.gpr[23] = 0x0000000000000000; regs.gpr[24] = 0x0000000000000002; regs.gpr[25] = 0x00000000465E3F72; regs.gpr[26] = 0x0000000000000000; regs.gpr[27] = 0x0000000000000000; regs.gpr[28] = 0x0000000000000000; regs.gpr[29] = 0xFFFFFFFFA4001FF0ll; regs.gpr[30] = 0x0000000000000000; regs.gpr[31] = 0xFFFFFFFFA4001550ll; regs.lo = 0x000000007A3C07F4; regs.hi = 0x0000000023953898; if (pal) { regs.gpr[20] = 0x0000000000000000; regs.gpr[23] = 0x0000000000000006; regs.gpr[31] = 0xFFFFFFFFA4001554ll; } break; } regs.gpr[22] = (cicSeeds[cicType] >> 8) & 0xFF; regs.cop0.Reset(); mem.Write32(regs, 0x04300004, 0x01010101); memcpy(mem.mmio.rsp.dmem, mem.rom.cart, 0x1000); regs.SetPC32(s32(0xA4000040)); } void PIF::ExecutePIF(Mem& mem, Registers& regs) { CICType cicType = mem.rom.cicType; bool pal = mem.rom.pal; mem.Write32(regs, PIF_RAM_REGION_START + 0x24, cicSeeds[cicType]); switch(cicType) { case UNKNOWN_CIC_TYPE: Util::warn("Unknown CIC type!"); break; case CIC_NUS_6101 ... CIC_NUS_6103_7103: mem.Write32(regs, 0x318, RDRAM_SIZE); break; case CIC_NUS_6105_7105: mem.Write32(regs, 0x3F0, RDRAM_SIZE); break; case CIC_NUS_6106_7106: break; } DoPIFHLE(mem, regs, pal, cicType); } }