From 1006d6e92501d892796a818b54f405862aa117a5 Mon Sep 17 00:00:00 2001 From: Cuyler36 Date: Mon, 7 Oct 2024 11:09:54 -0400 Subject: [PATCH] Support dynamic bank counts for N64 Contorller Paks --- ares/n64/controller/gamepad/gamepad.cpp | 153 ++++++++++++++++++++---- ares/n64/controller/gamepad/gamepad.hpp | 2 + ares/n64/system/system.cpp | 15 +++ ares/n64/system/system.hpp | 2 + desktop-ui/emulator/nintendo-64.cpp | 5 +- desktop-ui/emulator/nintendo-64dd.cpp | 5 +- desktop-ui/settings/options.cpp | 38 ++++++ desktop-ui/settings/settings.cpp | 1 + desktop-ui/settings/settings.hpp | 8 ++ 9 files changed, 205 insertions(+), 24 deletions(-) diff --git a/ares/n64/controller/gamepad/gamepad.cpp b/ares/n64/controller/gamepad/gamepad.cpp index bfa76b2a95..74494dfc0e 100644 --- a/ares/n64/controller/gamepad/gamepad.cpp +++ b/ares/n64/controller/gamepad/gamepad.cpp @@ -12,6 +12,8 @@ Gamepad::Gamepad(Node::Port parent) { port->setDisconnect([&] { return disconnect(); }); port->setSupported({"Controller Pak", "Rumble Pak", "Transfer Pak"}); + bank = 0; + x = node->append ("X-Axis"); y = node->append ("Y-Axis"); up = node->append("Up"); @@ -54,12 +56,56 @@ auto Gamepad::allocate(string name) -> Node::Peripheral { auto Gamepad::connect() -> void { if(!slot) return; if(slot->name() == "Controller Pak") { + bool create = true; + node->setPak(pak = platform->pak(node)); - ram.allocate(32_KiB); + system.controllerPakBankCount = system.configuredControllerPakBankCount; //reset controller bank count + ram.allocate(system.controllerPakBankCount * 32_KiB); //allocate N banks * 32KiB, max # of banks allowed is 62 + bank = 0; formatControllerPak(); if(auto fp = pak->read("save.pak")) { if(fp->attribute("loaded").boolean()) { + //read the bank count + u8 banks; + u32 bank_size; + + fp->seek(0x20 + 0x1A); + fp->read(array_span{&banks, sizeof(banks)}); + fp->seek(0); + + if (banks < 1) { + banks = 1; + } else if (banks > 62) { + banks = 62; + } + + bank_size = 32_KiB * banks; + + if (bank_size != ram.size) { + ram.allocate(bank_size); + + //update the system controller bank count + system.controllerPakBankCount = banks; + } ram.load(pak->read("save.pak")); + + if (fp->size() != bank_size) { + //reallocate vfs node + pak->remove(fp); + pak->append("save.pak", bank_size); + ram.save(pak->write("save.pak")); //write data back to filesystem + } + + create = false; + } + } + + if (create) { + //we need to create a controller pak file, so reallocate the vfs file to configured size + if (auto fp = pak->read("save.pak")) { + pak->remove(fp); + pak->append("save.pak", system.controllerPakBankCount * 32_KiB); + ram.save(pak->write("save.pak")); } } } @@ -132,7 +178,8 @@ auto Gamepad::comm(n8 send, n8 recv, n8 input[], n8 output[]) -> n2 { u16 address = (input[1] << 8 | input[2] << 0) & ~31; if(pif.addressCRC(address) == (n5)input[2]) { for(u32 index : range(recv - 1)) { - if(address <= 0x7FFF) output[index] = ram.read(address); + // read into current bank + if(address <= 0x7FFF) output[index] = ram.read(bank * 32_KiB + address); else output[index] = 0; address++; } @@ -173,12 +220,49 @@ auto Gamepad::comm(n8 send, n8 recv, n8 input[], n8 output[]) -> n2 { if(ram) { u16 address = (input[1] << 8 | input[2] << 0) & ~31; if(pif.addressCRC(address) == (n5)input[2]) { - for(u32 index : range(send - 3)) { - if(address <= 0x7FFF) ram.write(address, input[3 + index]); - address++; + //check if address is bank switch command + if (address == 0x8000) { + bool tValid = true; + if (send >= 4) { + u8 reqBank = input[3]; + if (reqBank < system.controllerPakBankCount) { + bank = reqBank; + } else { + tValid = false; + } + } else { + if (system.homebrewMode) { + tValid = false; + } + bank = 0; + } + + if (tValid && system.homebrewMode) { + //Verify we have 32 bytes (1 block) input and each value is the same bank + if (send == 35) { + u8 bank = input[3]; + for (u32 i = 4; i < 35; i++) { + if (input[i] != bank) { + tValid = false; + break; + } + } + } else { + tValid = false; + } + } + + + output[0] = pif.dataCRC({&input[3], send - 3u}); + valid = tValid ? 1 : 0; + } else { + for(u32 index : range(send - 3)) { + if(address <= 0x7FFF) ram.write(bank * 32_KiB + address, input[3 + index]); + address++; + } + output[0] = pif.dataCRC({&input[3], send - 3u}); + valid = 1; } - output[0] = pif.dataCRC({&input[3], send - 3u}); - valid = 1; } } @@ -315,8 +399,24 @@ auto Gamepad::read() -> n32 { return data; } -//controller paks contain 32KB of SRAM split into 128 pages of 256 bytes each. -//the first 5 pages are for storing system data, and the remaining 123 for game data. +auto Gamepad::getInodeChecksum(u8 bank) -> u8 { + if (bank < 62) { + u32 checksum = 0; + u32 i = bank == 0 ? 3 + ram.read(0x20 + 0x1a) * 2 : 1; //first bank has 3 + bank * 2 system pages, other banks have 127. + + for (i; i < 0x100; i++) { + checksum += ram.read((1 + bank) * 0x100) + ram.read((1 + bank) * 0x100 + 0x01); + } + + return checksum; + } + + return 0; +} + +//controller paks contain 32KB * nBanks of SRAM split into 128 pages of 256 bytes each. +//the first 3 + nBanks * 2 pages of bank 0 are for storing system data, and the remaining 123 for game data. +//the remaining banks page 0 is unused and the remaining 127 are for game data. auto Gamepad::formatControllerPak() -> void { ram.fill(0x00); @@ -325,12 +425,12 @@ auto Gamepad::formatControllerPak() -> void { n19 fieldB = random(); n27 fieldC = random(); for(u32 area : array{1,3,4,6}) { - ram.write(area * 0x20 + 0x01, fieldA); //unknown - ram.write(area * 0x20 + 0x04, fieldB); //serial# hi - ram.write(area * 0x20 + 0x08, fieldC); //serial# lo - ram.write(area * 0x20 + 0x18, 0x0001); //device ID - ram.write(area * 0x20 + 0x1a, 0x01); //banks (0x01 = 32KB) - ram.write(area * 0x20 + 0x1b, 0x00); //version# + ram.write(area * 0x20 + 0x01, fieldA); //unknown + ram.write(area * 0x20 + 0x04, fieldB); //serial# hi + ram.write(area * 0x20 + 0x08, fieldC); //serial# lo + ram.write(area * 0x20 + 0x18, 0x0001); //device ID + ram.write(area * 0x20 + 0x1a, system.controllerPakBankCount); //banks (0x01 = 32KB), (62 = max banks) + ram.write(area * 0x20 + 0x1b, 0x00); //version# u16 checksum = 0; u16 inverted = 0; for(u32 half : range(14)) { @@ -342,16 +442,25 @@ auto Gamepad::formatControllerPak() -> void { ram.write(area * 0x20 + 0x1e, inverted); } - //pages 1+2 (inode table) - for(u32 page : array{1,2}) { - ram.write(0x100 * page + 0x01, 0x71); //unknown - for(u32 slot : range(5,128)) { - ram.write(0x100 * page + slot * 2 + 0x01, 0x03); //0x01 = stop, 0x03 = empty + //pages 1 thru nBanks, nBanks+1 thru (nBanks*2) (inode table, inode table copy) + u8 nBanks = ram.read(0x20 + 0x1a); + u32 inodeTablePage = 1; + u32 inodeTableCopyPage = 1 + nBanks * 2; + for(u32 bank : range(0,nBanks)) { + u32 firstDataPage = bank == 0 ? (3 + nBanks * 2) : 1; //first bank has 3 + bank * 2 system pages, other banks have 127. + for(u32 page : array{inodeTablePage + bank, inodeTableCopyPage + bank}) { + for(u32 slot : range(firstDataPage,128)) { + ram.write(0x100 * page + slot * 2 + 0x01, 0x03); //0x01 = stop, 0x03 = empty + } + ram.write(0x100 * page + 0x01, getInodeChecksum(bank)); //checksum } } - //pages 3+4 (note table) - //pages 5-127 (game saves) + //page 1 is pak info and serial + //pages 2-nBanks are for the inode table + //pages at nBanks+1,2*nBanks are for the inode table backup + //pages at 2*nBanks+1, 2*nBanks+2 are for note table + //pages 3 + 2*nBanks are for save data } auto Gamepad::serialize(serializer& s) -> void { diff --git a/ares/n64/controller/gamepad/gamepad.hpp b/ares/n64/controller/gamepad/gamepad.hpp index 19801f5bd0..172440be9f 100644 --- a/ares/n64/controller/gamepad/gamepad.hpp +++ b/ares/n64/controller/gamepad/gamepad.hpp @@ -2,6 +2,7 @@ struct Gamepad : Controller { Node::Port port; Node::Peripheral slot; VFS::Pak pak; + u8 bank; Memory::Writable ram; //Toshiba TC55257DFL-85V Node::Input::Rumble motor; @@ -31,6 +32,7 @@ struct Gamepad : Controller { auto rumble(bool enable) -> void; auto comm(n8 send, n8 recv, n8 input[], n8 output[]) -> n2 override; auto read() -> n32 override; + auto getInodeChecksum(u8 bank) -> u8; auto formatControllerPak() -> void; auto serialize(serializer&) -> void override; diff --git a/ares/n64/system/system.cpp b/ares/n64/system/system.cpp index 92b1bf748e..6592e1e43c 100644 --- a/ares/n64/system/system.cpp +++ b/ares/n64/system/system.cpp @@ -41,6 +41,21 @@ auto option(string name, string value) -> bool { } } if(name == "Expansion Pak") system.expansionPak = value.boolean(); + if(name == "Controller Pak Banks") { + if (value == "32KiB (Default)") { + system.configuredControllerPakBankCount = 1; + system.controllerPakBankCount = 1; + } else if (value == "128KiB (Datel 1Meg)") { + system.configuredControllerPakBankCount = 4; + system.controllerPakBankCount = 4; + } else if (value == "512KiB (Datel 4Meg)") { + system.configuredControllerPakBankCount = 16; + system.controllerPakBankCount = 16; + } else if (value == "1984KiB (Maximum)") { + system.configuredControllerPakBankCount = 62; + system.controllerPakBankCount = 62; + } + } return true; } diff --git a/ares/n64/system/system.hpp b/ares/n64/system/system.hpp index 956b42ee8a..ee2386bde9 100644 --- a/ares/n64/system/system.hpp +++ b/ares/n64/system/system.hpp @@ -3,6 +3,8 @@ struct System { VFS::Pak pak; bool homebrewMode = false; bool expansionPak = true; + u8 configuredControllerPakBankCount = 1; + u8 controllerPakBankCount = 1; enum class Region : u32 { NTSC, PAL }; diff --git a/desktop-ui/emulator/nintendo-64.cpp b/desktop-ui/emulator/nintendo-64.cpp index 0119a9663e..fc46093d49 100644 --- a/desktop-ui/emulator/nintendo-64.cpp +++ b/desktop-ui/emulator/nintendo-64.cpp @@ -105,6 +105,7 @@ auto Nintendo64::load() -> bool { ares::Nintendo64::option("Homebrew Mode", settings.general.homebrewMode); ares::Nintendo64::option("Recompiler", !settings.general.forceInterpreter); ares::Nintendo64::option("Expansion Pak", settings.nintendo64.expansionPak); + ares::Nintendo64::option("Controller Pak Banks", settings.nintendo64.controllerPakBankString); if(!ares::Nintendo64::load(root, {"[Nintendo] ", name, " (", region, ")"})) return false; @@ -151,7 +152,9 @@ auto Nintendo64::load() -> bool { if(!transferPakConnected) { if(id == 0 && game->pak->attribute("cpak").boolean()) { gamepad = mia::Pak::create("Nintendo 64"); - gamepad->pak->append("save.pak", 32_KiB); + + //create maximum sized controller pak, file is resized later + gamepad->pak->append("save.pak", 1984_KiB); gamepad->load("save.pak", ".pak", game->location); port->allocate("Controller Pak"); port->connect(); diff --git a/desktop-ui/emulator/nintendo-64dd.cpp b/desktop-ui/emulator/nintendo-64dd.cpp index eac30875d3..2673d59c53 100644 --- a/desktop-ui/emulator/nintendo-64dd.cpp +++ b/desktop-ui/emulator/nintendo-64dd.cpp @@ -82,6 +82,7 @@ auto Nintendo64DD::load() -> bool { ares::Nintendo64::option("Homebrew Mode", settings.general.homebrewMode); ares::Nintendo64::option("Recompiler", !settings.general.forceInterpreter); ares::Nintendo64::option("Expansion Pak", settings.nintendo64.expansionPak); + ares::Nintendo64::option("Controller Pak Banks", settings.nintendo64.controllerPakBankString); if(!ares::Nintendo64::load(root, {"[Nintendo] Nintendo 64DD (", region, ")"})) return false; @@ -98,7 +99,9 @@ auto Nintendo64DD::load() -> bool { if(auto port = peripheral->find("Pak")) { if(id == 0 && game->pak->attribute("cpak").boolean()) { gamepad = mia::Pak::create("Nintendo 64"); - gamepad->pak->append("save.pak", 32_KiB); + + //create maximum sized controller pak, file is resized later + gamepad->pak->append("save.pak", 1984_KiB); gamepad->load("save.pak", ".pak", game->location); port->allocate("Controller Pak"); port->connect(); diff --git a/desktop-ui/settings/options.cpp b/desktop-ui/settings/options.cpp index 5292deafce..5cf9f43736 100644 --- a/desktop-ui/settings/options.cpp +++ b/desktop-ui/settings/options.cpp @@ -44,6 +44,44 @@ auto OptionSettings::construct() -> void { nintendo64ExpansionPakLayout.setAlignment(1).setPadding(12_sx, 0); nintendo64ExpansionPakHint.setText("Enable/Disable the 4MB Expansion Pak").setFont(Font().setSize(7.0)).setForegroundColor(SystemColor::Sublabel); + for (auto& opt : array{"32KiB (Default)", "128KiB (Datel 1Meg)", "512KiB (Datel 4Meg)", "1984KiB (Maximum)"}) { + ComboButtonItem item{&nintendo64ControllerPakBankOption}; + item.setText(opt); + if (opt == settings.nintendo64.controllerPakBankString) { + item.setSelected(); + + if (opt == "32KiB (Default)") { + settings.nintendo64.controllerPakBankCount = 1; + } else if (opt == "128KiB (Datel 1Meg)") { + settings.nintendo64.controllerPakBankCount = 4; + } else if (opt == "512KiB (Datel 4Meg)") { + settings.nintendo64.controllerPakBankCount = 16; + } else if (opt == "1984KiB (Maximum)") { + settings.nintendo64.controllerPakBankCount = 62; + } + } + } + nintendo64ControllerPakBankOption.onChange([&] { + auto idx = nintendo64ControllerPakBankOption.selected(); + auto value = idx.text(); + if (value != settings.nintendo64.controllerPakBankString) { + settings.nintendo64.controllerPakBankString = value; + + if (value == "32KiB (Default)") { + settings.nintendo64.controllerPakBankCount = 1; + } else if (value == "128KiB (Datel 1Meg)") { + settings.nintendo64.controllerPakBankCount = 4; + } else if (value == "512KiB (Datel 4Meg)") { + settings.nintendo64.controllerPakBankCount = 16; + } else if (value == "1984KiB (Maximum)") { + settings.nintendo64.controllerPakBankCount = 62; + } + } + }); + nintendo64ControllerPakBankLayout.setAlignment(1).setPadding(12_sx, 0); + nintendo64ControllerPakBankLabel.setText("Controller Pak Size:"); + nintendo64ControllerPakBankHint.setText("Sets the size of a newly created Controller Pak's available memory").setFont(Font().setSize(7.0)).setForegroundColor(SystemColor::Sublabel); + megaDriveSettingsLabel.setText("Mega Drive Settings").setFont(Font().setBold()); megaDriveTmssOption.setText("TMSS Boot Rom").setChecked(settings.megadrive.tmss).onToggle([&] { diff --git a/desktop-ui/settings/settings.cpp b/desktop-ui/settings/settings.cpp index 5b04caf2cc..d6ba1fd302 100644 --- a/desktop-ui/settings/settings.cpp +++ b/desktop-ui/settings/settings.cpp @@ -123,6 +123,7 @@ auto Settings::process(bool load) -> void { bind(boolean, "DebugServer/UseIPv4", debugServer.useIPv4); bind(boolean, "Nintendo64/ExpansionPak", nintendo64.expansionPak); + bind(string, "Nintendo64/ControllerPakBankString", nintendo64.controllerPakBankString); bind(boolean, "MegaDrive/TMSS", megadrive.tmss); diff --git a/desktop-ui/settings/settings.hpp b/desktop-ui/settings/settings.hpp index d20e9ee777..fb77853c55 100644 --- a/desktop-ui/settings/settings.hpp +++ b/desktop-ui/settings/settings.hpp @@ -103,6 +103,8 @@ struct Settings : Markup::Node { struct Nintendo64 { bool expansionPak = true; + u8 controllerPakBankCount = 1; + string controllerPakBankString = "32KiB (Default)"; } nintendo64; struct MegaDrive { @@ -262,6 +264,12 @@ struct OptionSettings : VerticalLayout { HorizontalLayout nintendo64ExpansionPakLayout{this, Size{~0, 0}, 5}; CheckLabel nintendo64ExpansionPakOption{&nintendo64ExpansionPakLayout, Size{0, 0}, 5}; Label nintendo64ExpansionPakHint{&nintendo64ExpansionPakLayout, Size{0, 0}}; + HorizontalLayout nintendo64ControllerPakBankLayout{this, Size{~0, 0}, 5}; + Label nintendo64ControllerPakBankLabel{&nintendo64ControllerPakBankLayout, Size{0, 0}}; + ComboButton nintendo64ControllerPakBankOption{&nintendo64ControllerPakBankLayout, Size{0, 0}}; + // LineEdit nintendo64ControllerPakBankOption{&nintendo64ControllerPakBankLayout, Size{40, 0}}; + Label nintendo64ControllerPakBankHint{&nintendo64ControllerPakBankLayout, Size{0, 0}}; + Label megaDriveSettingsLabel{this, Size{~0, 0}, 5}; HorizontalLayout megaDriveTmssLayout{this, Size{~0, 0}, 5}; CheckLabel megaDriveTmssOption{&megaDriveTmssLayout, Size{0, 0}, 5};