diff --git a/core/hw/naomi/m4cartridge.cpp b/core/hw/naomi/m4cartridge.cpp index d80baf61a..d04c7e443 100644 --- a/core/hw/naomi/m4cartridge.cpp +++ b/core/hw/naomi/m4cartridge.cpp @@ -290,7 +290,9 @@ bool M4Cartridge::GetBootId(RomBootID *bootId) if (RomSize < sizeof(RomBootID)) return false; RomBootID *pBootId = (RomBootID *)RomPtr; - if (memcmp(pBootId->boardName, "NAOMI", 5)) + if (memcmp(pBootId->boardName, "NAOMI", 5) + && memcmp(pBootId->boardName, "Naomi2", 6) + && memcmp(pBootId->boardName, "SystemSP", 8)) { rom_cur_address = 0; enc_reset(); diff --git a/core/hw/naomi/m4cartridge.h b/core/hw/naomi/m4cartridge.h index 9a74c54ca..7f21974a0 100644 --- a/core/hw/naomi/m4cartridge.h +++ b/core/hw/naomi/m4cartridge.h @@ -55,6 +55,7 @@ protected: void DmaOffsetChanged(u32 dma_offset) override; void PioOffsetChanged(u32 pio_offset) override; u16 decrypt(u16 w); + void enc_reset(); private: void device_start(); @@ -77,7 +78,6 @@ private: bool xfer_ready; void enc_init(); - void enc_reset(); void enc_fill(); u16 decrypt_one_round(u16 word, u16 subkey); }; diff --git a/core/hw/naomi/naomi_cart.cpp b/core/hw/naomi/naomi_cart.cpp index 0bda0b6a5..f8817247c 100644 --- a/core/hw/naomi/naomi_cart.cpp +++ b/core/hw/naomi/naomi_cart.cpp @@ -668,7 +668,7 @@ void naomi_cart_LoadRom(const std::string& path, const std::string& fileName, Lo } else if (gameId.substr(0, 8) == "MKG TKOB" || gameId.substr(0, 9) == "MUSHIKING" - || gameId == "DINOSAUR KING" + || gameId.substr(0, 13) == "DINOSAUR KING" || gameId == "INW PUPPY 2008 VER1.001" // SystemSP isshoni || gameId.substr(0, 14) == "LOVE AND BERRY") { @@ -726,9 +726,6 @@ void naomi_cart_LoadRom(const std::string& path, const std::string& fileName, Lo else { NOTICE_LOG(NAOMI, "NAOMI GAME ID [%s]", settings.content.gameId.c_str()); - if (settings.content.gameId.substr(0, 6) == "dinoki" - || settings.content.gameId.substr(0, 7) == "loveber") - card_reader::barcodeInit(); } } diff --git a/core/hw/naomi/systemsp.cpp b/core/hw/naomi/systemsp.cpp index 305d6a73f..4ac2bd832 100644 --- a/core/hw/naomi/systemsp.cpp +++ b/core/hw/naomi/systemsp.cpp @@ -97,14 +97,14 @@ constexpr u8 LOVEBERRY_CHIP_DATA[128] = { // class RfidReaderWriter : public SerialPort::Pipe { - enum Responses : u8 { + enum Response : u8 { OK = 0xa, RETRY = 0xca, NG = 0x3a, NOT = 0x6a, }; - enum Commands : u8 { + enum Command : u8 { REQ = 0x4e, SEL = 0x2e, READ = 0xe, @@ -143,169 +143,157 @@ public: { SERIAL_LOG("UART%d write data out: %02x", index, v); recvBuffer.push_back(v); - if (recvBuffer.size() == expectedBytes) + if (recvBuffer.size() < expectedBytes) + return; + + switch (recvBuffer[0]) { - switch (recvBuffer[0]) + case TEST: + if (recvBuffer[1] == RF_OFF) { - case TEST: - if (recvBuffer[1] == RF_OFF) { - state = Off; - toSend.push_back(OK); - SERIAL_LOG("UART%d state Off", index); - } - else if (state == Off) { - state = Connecting; - toSend.push_back(NOT); - SERIAL_LOG("UART%d state Connecting", index); - } - else - toSend.push_back(OK); - break; - case SEL: - if ((state == Connected || state == FullRead) - && (recvBuffer[5] != 0 || recvBuffer[6] != 0 || recvBuffer[7] != 0 || recvBuffer[8] != 0)) - state = FullRead; - else - state = Connected; + state = Off; toSend.push_back(OK); - break; - case COUNT: + SERIAL_LOG("UART%d state Off", index); + } + else if (state == Off) + { + state = Connecting; + toSend.push_back(NOT); + SERIAL_LOG("UART%d state Connecting", index); + } + else + toSend.push_back(OK); + break; + case SEL: + if ((state == Connected || state == FullRead) + && (recvBuffer[5] != 0 || recvBuffer[6] != 0 || recvBuffer[7] != 0 || recvBuffer[8] != 0)) + state = FullRead; + else + state = Connected; + toSend.push_back(OK); + break; + case COUNT: + { + u8 newVal = 0; + switch (recvBuffer[1]) { - u8 newVal = 0; - switch (recvBuffer[1]) - { - case 0xc0: - newVal = --cardData[16]; - break; - case 0x30: - newVal = --cardData[17]; - break; - case 0x0c: - newVal = --cardData[18]; - break; - case 0x03: - newVal = --cardData[19]; - if (newVal == 0) - makeNewCard(); - break; - } - toSend.push_back(OK); - toSend.push_back(newVal); - saveData(); - SERIAL_LOG("UART%d COUNT %x -> %x", index, recvBuffer[1], newVal); + case 0xc0: + newVal = --cardData[16]; + break; + case 0x30: + newVal = --cardData[17]; + break; + case 0x0c: + newVal = --cardData[18]; + break; + case 0x03: + newVal = --cardData[19]; + if (newVal == 0) + makeNewCard(); break; } - case WRITE: - memcpy(&cardData[4 * rowCounter], &recvBuffer[1], 4); + toSend.push_back(OK); + toSend.push_back(newVal); saveData(); - SERIAL_LOG("UART%d card written @ row %d", index, rowCounter); - toSend.push_back(OK); - break; - case WRITE_NOP: - case WRITE_ST: - case CHANGE: - case HALT: - toSend.push_back(OK); - break; - default: - SERIAL_LOG("UART%d unhandled cmd %x (len %d)", index, recvBuffer[0], expectedBytes); + SERIAL_LOG("UART%d COUNT %x -> %x", index, recvBuffer[1], newVal); break; } - port->updateStatus(); - expectedBytes = 0; - } - return; - } - - switch (v) - { - case TEST: - SERIAL_LOG("UART%d cmd TEST", index); - expectedBytes = 2 + 1; - recvBuffer.clear(); - recvBuffer.push_back(v); - break; - case CHANGE: // change baud rate - SERIAL_LOG("UART%d cmd CHANGE", index); - expectedBytes = 1 + 1; - recvBuffer.clear(); - recvBuffer.push_back(v); - break; - case RESET: - SERIAL_LOG("UART%d cmd RESET", index); - state = Off; - toSend.push_back(OK); - break; - case HALT: - SERIAL_LOG("UART%d cmd HALT", index); - expectedBytes = 4 + 1; // ser0 - recvBuffer.clear(); - recvBuffer.push_back(v); - break; - case READ: - SERIAL_LOG("UART%d cmd READ", index); - toSend.push_back(OK); - if (state == FullRead) { - toSend.insert(toSend.end(), cardData.begin(), cardData.end()); - } - else - { - toSend.insert(toSend.end(), cardData.begin(), cardData.begin() + 8); - static const u8 maskedData[120] = { 0, 0, 0, 0, 0, 0, 0, 0xaa }; - toSend.insert(toSend.end(), std::begin(maskedData), std::end(maskedData)); - } - break; - case SEL: - SERIAL_LOG("UART%d cmd SEL", index); - expectedBytes = 8 + 1; // ser0 key (key is calc'ed from ser1) - recvBuffer.clear(); - recvBuffer.push_back(v); - break; - case REQ: - SERIAL_LOG("UART%d cmd REQ", index); - if (state == Connecting) - { - state = Connected; - toSend.push_back(RETRY); - SERIAL_LOG("UART%d state Connected", index); - } - else if (state == Connected || state == FullRead) - { + case WRITE: + memcpy(&cardData[4 * rowCounter], &recvBuffer[1], 4); + saveData(); + SERIAL_LOG("UART%d card written @ row %d", index, rowCounter); toSend.push_back(OK); - toSend.insert(toSend.end(), &cardData[0], &cardData[4]); // ser0 - SERIAL_LOG("UART%d exec REQ", index); + break; + case WRITE_NOP: + case WRITE_ST: + case CHANGE: + case HALT: + toSend.push_back(OK); + break; + default: + SERIAL_LOG("UART%d unhandled cmd %x (len %d)", index, recvBuffer[0], expectedBytes); + break; } - else - toSend.push_back(RETRY); - break; - case COUNT: - SERIAL_LOG("UART%d cmd COUNT", index); - expectedBytes = 1 + 1; - recvBuffer.clear(); - recvBuffer.push_back(v); - break; - case WRITE: - SERIAL_LOG("UART%d cmd WRITE", index); - rowCounter++; - expectedBytes = 4 + 1; - recvBuffer.clear(); - recvBuffer.push_back(0xce); - break; - case WRITE_ST: - case WRITE_NOP: - SERIAL_LOG("UART%d cmd %s", index, v == WRITE_ST ? "WRITE_ST" : "WRITE_NOP"); - if (v == WRITE_ST) - rowCounter = 0; - else + expectedBytes = 0; + } + else + { + switch (v) + { + case TEST: + SERIAL_LOG("UART%d cmd TEST", index); + expect(v, 2); + break; + case CHANGE: // change baud rate + SERIAL_LOG("UART%d cmd CHANGE", index); + expect(v, 1); + break; + case RESET: + SERIAL_LOG("UART%d cmd RESET", index); + state = Off; + toSend.push_back(OK); + break; + case HALT: + SERIAL_LOG("UART%d cmd HALT", index); + expect(v, 4); // ser0 + break; + case READ: + SERIAL_LOG("UART%d cmd READ", index); + toSend.push_back(OK); + if (state == FullRead) { + toSend.insert(toSend.end(), cardData.begin(), cardData.end()); + } + else + { + toSend.insert(toSend.end(), cardData.begin(), cardData.begin() + 8); + static const u8 maskedData[120] = { 0, 0, 0, 0, 0, 0, 0, 0xaa }; + toSend.insert(toSend.end(), std::begin(maskedData), std::end(maskedData)); + } + break; + case SEL: + SERIAL_LOG("UART%d cmd SEL", index); + expect(v, 8); // ser0 key (key is calc'ed from ser1) + break; + case REQ: + SERIAL_LOG("UART%d cmd REQ", index); + if (state == Connecting) + { + state = Connected; + toSend.push_back(RETRY); + SERIAL_LOG("UART%d state Connected", index); + } + else if (state == Connected || state == FullRead) + { + toSend.push_back(OK); + toSend.insert(toSend.end(), &cardData[0], &cardData[4]); // ser0 + SERIAL_LOG("UART%d exec REQ", index); + } + else + toSend.push_back(RETRY); + break; + case COUNT: + SERIAL_LOG("UART%d cmd COUNT", index); + expect(v, 1); + break; + case WRITE: + SERIAL_LOG("UART%d cmd WRITE", index); rowCounter++; - expectedBytes = 4 + 1; - recvBuffer.clear(); - recvBuffer.push_back(v); - break; - default: - INFO_LOG(NAOMI, "UART%d write data out: unknown cmd %x", index, v); - toSend.push_back(NG); - break; + expect(v, 4); + break; + case WRITE_ST: + case WRITE_NOP: + SERIAL_LOG("UART%d cmd %s", index, v == WRITE_ST ? "WRITE_ST" : "WRITE_NOP"); + if (v == WRITE_ST) + rowCounter = 0; + else + rowCounter++; + expect(v, 4); + break; + default: + INFO_LOG(NAOMI, "UART%d write data out: unknown cmd %x", index, v); + toSend.push_back(NG); + break; + } } port->updateStatus(); } @@ -365,6 +353,13 @@ public: } private: + void expect(u8 cmd, int bytes) + { + expectedBytes = bytes + 1; + recvBuffer.clear(); + recvBuffer.push_back(cmd); + } + void makeNewCard() { INFO_LOG(NAOMI, "Creating new RFID card"); @@ -1500,10 +1495,208 @@ std::string SystemSpCart::getEepromPath() const return path; } +class BootIdLoader +{ + static constexpr u32 SECSIZE = 512; + +public: + BootIdLoader(SystemSpCart& cart) : cart(cart) + { + const chd_header* head = chd_get_header(cart.chd); + + hunkbytes = head->hunkbytes; + hunkmem = std::make_unique(hunkbytes); + } + + RomBootID *load() + { + // Read master boot record + if (chd_read(cart.chd, 0, &hunkmem[0]) != CHDERR_NONE) + return nullptr; + // Check boot signature + if (hunkmem[510] != 0x55 || hunkmem[511] != 0xaa) + return nullptr; + // Get partition 1 info + if (hunkmem[446 + 4] != 6) + // Not a FAT16B partition + return nullptr; + u32 start = *(u32 *)&hunkmem[446 + 8]; + + // Read partition Bios Parameter Block + if (chd_read(cart.chd, start * SECSIZE / hunkbytes, &hunkmem[0]) != CHDERR_NONE) + return nullptr; + int offset = (start * SECSIZE) % hunkbytes; + // Partition attributes + sectorsPerCluster = hunkmem[offset + 13]; + u16 resSectors = *(u16 *)&hunkmem[offset + 14]; + u8 fatCount = hunkmem[offset + 16]; + rootFolderSize = *(u16 *)&hunkmem[offset + 17]; // in entries + u16 fatSize = *(u16 *)&hunkmem[offset + 22]; // in sectors + // FATs start after reserved sectors + fatOffset = start + resSectors; + rootOffset = fatOffset + fatSize * fatCount; + dataStartOffset = rootOffset + rootFolderSize * sizeof(DirEntry) / SECSIZE; + + if (!openFile("1STREAD.BIN")) + return nullptr; + std::unique_ptr buffer = std::make_unique(file.size); + cart.enc_reset(); + for (u32 i = 0; i < file.size; i += 2) + *(u16 *)&buffer[i] = cart.decrypt(*(u16 *)&hunkmem[file.offset + i]); + if (memcmp(&buffer[0], "SPCF", 4)) + // wrong signature + return nullptr; + std::string imageName((const char *)&buffer[0x80]); + if (imageName.length() > 12) + // make short 8.3 name + imageName = imageName.substr(0, 6) + "~1" + imageName.substr(imageName.find('.')); + string_toupper(imageName); + + if (!openFile(imageName)) + return nullptr; + buffer = std::make_unique(sizeof(RomBootID)); + cart.enc_reset(); + for (size_t i = 0; i < sizeof(RomBootID); i += 2) + { + if (file.offset + i >= hunkbytes) + { + u32 fileOffset = dataStartOffset + (file.cluster - 2) * sectorsPerCluster + i / SECSIZE; + if (chd_read(cart.chd, fileOffset * SECSIZE / hunkbytes, &hunkmem[0]) != CHDERR_NONE) + return nullptr; + file.offset = (fileOffset * SECSIZE) % hunkbytes - i; + file.size -= i; + } + *(u16 *)&buffer[i] = cart.decrypt(*(u16 *)&hunkmem[file.offset + i]); + } + if (memcmp(&buffer[0], "SystemSP", 8)) + { + // Yes, it is encrypted twice (except tetgiano) + cart.enc_reset(); + for (size_t i = 0; i < sizeof(RomBootID); i += 2) + *(u16 *)&buffer[i] = cart.decrypt(*(u16 *)&buffer[i]); + } + RomBootID *bootId = new RomBootID(); + memcpy(bootId, &buffer[0], sizeof(RomBootID)); + + return bootId; + } + +private: + struct DirEntry + { + char name[8]; + char ext[3]; + enum Attribute : u8 { + ReadOnly = 0x01, + Hidden = 0x02, + System = 0x04, + VolumeName = 0x8, + Directory = 0x10, + Archive = 0x20, + LongName = 0xf, + }; + Attribute attributes; + u8 _reserved; + u8 creationTimeMs; + u16 creationTime; + u16 creationDate; + u16 accessDate; + u16 clusterHigh; + u16 updateTime; + u16 updateDate; + u16 cluster; + u32 size; + }; + + bool openFile(const std::string& fileName) + { + u32 curRootOffset = rootOffset; + if (chd_read(cart.chd, curRootOffset * SECSIZE / hunkbytes, &hunkmem[0]) != CHDERR_NONE) + return false; + int offset = (curRootOffset * SECSIZE) % hunkbytes; + for (int i = 0; i < rootFolderSize; i++) + { + if (offset + i * sizeof(DirEntry) >= hunkbytes) + { + ++curRootOffset; + if (chd_read(cart.chd, curRootOffset * SECSIZE / hunkbytes, &hunkmem[0]) != CHDERR_NONE) + return false; + offset = (curRootOffset * SECSIZE) % hunkbytes - i * sizeof(DirEntry); + } + const DirEntry *entry = (const DirEntry *)&hunkmem[offset + i * sizeof(DirEntry)]; + u8 b = (u8)entry->name[0]; + if (b == 0) + // end of directory + return false; + if (b == 0xe5 || b == 0x2e) + // unused or ./.. entry + continue; + if (entry->attributes == DirEntry::LongName || (entry->attributes & (DirEntry::VolumeName | DirEntry::System))) + // long name or volume name/system + continue; + std::string name(std::begin(entry->name), std::end(entry->name)); + name = trim_trailing_ws(name); + std::string ext(std::begin(entry->ext), std::end(entry->ext)); + ext = trim_trailing_ws(ext); + if (!ext.empty()) + name += "." + ext; + //printf("%s%c\t%d\n", name.c_str(), entry->attributes & DirEntry::Directory ? '/' : ' ', entry->size); + if (name != fileName) + continue; + file.cluster = entry->cluster; + file.size = entry->size; + u32 fileOffset = dataStartOffset + (file.cluster - 2) * sectorsPerCluster; + if (chd_read(cart.chd, fileOffset * SECSIZE / hunkbytes, &hunkmem[0]) != CHDERR_NONE) + return false; + file.offset = (fileOffset * SECSIZE) % hunkbytes; + + return true; + } + return false; + } + + SystemSpCart& cart; + u32 hunkbytes; + std::unique_ptr hunkmem; + u8 sectorsPerCluster = 0; + u32 fatOffset = 0; + u16 rootFolderSize = 0; + u32 rootOffset = 0; + u32 dataStartOffset = 0; + struct { + u32 cluster; + u32 size; + int offset; + } file; +}; + void SystemSpCart::Init(LoadProgress *progress, std::vector *digest) { M4Cartridge::Init(progress, digest); + if (mediaName != nullptr) + { + std::string parent = hostfs::storage().getParentPath(settings.content.path); + std::string gdrom_path = get_file_basename(settings.content.fileName) + "/" + std::string(mediaName) + ".chd"; + gdrom_path = hostfs::storage().getSubPath(parent, gdrom_path); + chd = openChd(gdrom_path); + if (parentName != nullptr && chd == nullptr) + { + std::string gdrom_parent_path = hostfs::storage().getSubPath(parent, std::string(parentName) + "/" + std::string(mediaName) + ".chd"); + chd = openChd(gdrom_parent_path); + } + if (chd == nullptr) + throw NaomiCartException("SystemSP: Cannot open CompactFlash file " + gdrom_path); + + BootIdLoader loader(*this); + romBootId.reset(loader.load()); + } + else + { + ata.status.rdy = 0; + ata.status.df = 1; + } + RomBootID bootId; if (GetBootId(&bootId) && bootId.country != 0 && (bootId.country & (2 << config::Region)) == 0) @@ -1524,6 +1717,7 @@ void SystemSpCart::Init(LoadProgress *progress, std::vector *digest) config::Region.override(0); } // Force BIOS reload now to get the default eeprom for the correct region + naomi_default_eeprom = nullptr; naomi_cart_LoadBios(settings.content.fileName.c_str()); } region = config::Region; @@ -1531,25 +1725,6 @@ void SystemSpCart::Init(LoadProgress *progress, std::vector *digest) if (!eeprom.Load(getEepromPath()) && naomi_default_eeprom != nullptr) memcpy(eeprom.data, naomi_default_eeprom, 128); - if (mediaName != nullptr) - { - std::string parent = hostfs::storage().getParentPath(settings.content.path); - std::string gdrom_path = get_file_basename(settings.content.fileName) + "/" + std::string(mediaName) + ".chd"; - gdrom_path = hostfs::storage().getSubPath(parent, gdrom_path); - chd = openChd(gdrom_path); - if (parentName != nullptr && chd == nullptr) - { - std::string gdrom_parent_path = hostfs::storage().getSubPath(parent, std::string(parentName) + "/" + std::string(mediaName) + ".chd"); - chd = openChd(gdrom_parent_path); - } - if (chd == nullptr) - throw NaomiCartException("SystemSP: Cannot open CompactFlash file " + gdrom_path); - } - else - { - ata.status.rdy = 0; - ata.status.df = 1; - } // dinoki4 doesn't use rfid chips. dinokich uses a different reader/writer protocol if ((!strncmp(game->name, "dinoki", 6) && strcmp(game->name, "dinoki4") != 0 && strcmp(game->name, "dinokich") != 0) || !strncmp(game->name, "loveber", 7)) diff --git a/core/hw/naomi/systemsp.h b/core/hw/naomi/systemsp.h index 23525c12f..76ea664b1 100644 --- a/core/hw/naomi/systemsp.h +++ b/core/hw/naomi/systemsp.h @@ -162,9 +162,10 @@ public: { if (mediaName == nullptr) return M4Cartridge::GetBootId(bootId); - else - // TODO + if (!romBootId) return false; + memcpy(bootId, romBootId.get(), sizeof(RomBootID)); + return true; } void updateInterrupt(u32 mask = 0); @@ -189,6 +190,7 @@ private: u32 hunkbytes = 0; std::unique_ptr hunkmem; u32 hunknum = ~0; + std::unique_ptr romBootId; AT93C46SerialEeprom eeprom; SerialPort uart1; @@ -238,6 +240,8 @@ public: static constexpr u32 INT_ATA = 0x10; static SystemSpCart *Instance; + + friend class BootIdLoader; }; } diff --git a/core/stdclass.h b/core/stdclass.h index 1373c04a7..d67901efe 100644 --- a/core/stdclass.h +++ b/core/stdclass.h @@ -106,6 +106,11 @@ static inline void string_tolower(std::string& s) std::transform(s.begin(), s.end(), s.begin(), [](unsigned char c){ return std::tolower(c); }); } +static inline void string_toupper(std::string& s) +{ + std::transform(s.begin(), s.end(), s.begin(), [](unsigned char c){ return std::toupper(c); }); +} + static inline std::string get_file_extension(const std::string& s) { size_t dot = s.find_last_of('.');