Skip to content

Commit

Permalink
Support dynamic bank counts for N64 Contorller Paks
Browse files Browse the repository at this point in the history
  • Loading branch information
Cuyler36 committed Oct 10, 2024
1 parent bc2a548 commit 1006d6e
Show file tree
Hide file tree
Showing 9 changed files with 205 additions and 24 deletions.
153 changes: 131 additions & 22 deletions ares/n64/controller/gamepad/gamepad.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<Node::Input::Axis> ("X-Axis");
y = node->append<Node::Input::Axis> ("Y-Axis");
up = node->append<Node::Input::Button>("Up");
Expand Down Expand 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<u8>{&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"));
}
}
}
Expand Down Expand Up @@ -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<Byte>(address);
// read into current bank
if(address <= 0x7FFF) output[index] = ram.read<Byte>(bank * 32_KiB + address);
else output[index] = 0;
address++;
}
Expand Down Expand Up @@ -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<Byte>(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<Byte>(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;
}
}

Expand Down Expand Up @@ -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<Byte>(0x20 + 0x1a) * 2 : 1; //first bank has 3 + bank * 2 system pages, other banks have 127.

for (i; i < 0x100; i++) {
checksum += ram.read<Byte>((1 + bank) * 0x100) + ram.read<Byte>((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);

Expand All @@ -325,12 +425,12 @@ auto Gamepad::formatControllerPak() -> void {
n19 fieldB = random();
n27 fieldC = random();
for(u32 area : array<u8[4]>{1,3,4,6}) {
ram.write<Byte>(area * 0x20 + 0x01, fieldA); //unknown
ram.write<Word>(area * 0x20 + 0x04, fieldB); //serial# hi
ram.write<Word>(area * 0x20 + 0x08, fieldC); //serial# lo
ram.write<Half>(area * 0x20 + 0x18, 0x0001); //device ID
ram.write<Byte>(area * 0x20 + 0x1a, 0x01); //banks (0x01 = 32KB)
ram.write<Byte>(area * 0x20 + 0x1b, 0x00); //version#
ram.write<Byte>(area * 0x20 + 0x01, fieldA); //unknown
ram.write<Word>(area * 0x20 + 0x04, fieldB); //serial# hi
ram.write<Word>(area * 0x20 + 0x08, fieldC); //serial# lo
ram.write<Half>(area * 0x20 + 0x18, 0x0001); //device ID
ram.write<Byte>(area * 0x20 + 0x1a, system.controllerPakBankCount); //banks (0x01 = 32KB), (62 = max banks)
ram.write<Byte>(area * 0x20 + 0x1b, 0x00); //version#
u16 checksum = 0;
u16 inverted = 0;
for(u32 half : range(14)) {
Expand All @@ -342,16 +442,25 @@ auto Gamepad::formatControllerPak() -> void {
ram.write<Half>(area * 0x20 + 0x1e, inverted);
}

//pages 1+2 (inode table)
for(u32 page : array<u8[2]>{1,2}) {
ram.write<Byte>(0x100 * page + 0x01, 0x71); //unknown
for(u32 slot : range(5,128)) {
ram.write<Byte>(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<Byte>(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<u32[2]>{inodeTablePage + bank, inodeTableCopyPage + bank}) {
for(u32 slot : range(firstDataPage,128)) {
ram.write<Byte>(0x100 * page + slot * 2 + 0x01, 0x03); //0x01 = stop, 0x03 = empty
}
ram.write<Byte>(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 {
Expand Down
2 changes: 2 additions & 0 deletions ares/n64/controller/gamepad/gamepad.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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;

Expand Down
15 changes: 15 additions & 0 deletions ares/n64/system/system.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down
2 changes: 2 additions & 0 deletions ares/n64/system/system.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 };

Expand Down
5 changes: 4 additions & 1 deletion desktop-ui/emulator/nintendo-64.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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();
Expand Down
5 changes: 4 additions & 1 deletion desktop-ui/emulator/nintendo-64dd.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -98,7 +99,9 @@ auto Nintendo64DD::load() -> bool {
if(auto port = peripheral->find<ares::Node::Port>("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();
Expand Down
38 changes: 38 additions & 0 deletions desktop-ui/settings/options.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<string[4]>{"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([&] {
Expand Down
1 change: 1 addition & 0 deletions desktop-ui/settings/settings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
8 changes: 8 additions & 0 deletions desktop-ui/settings/settings.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,8 @@ struct Settings : Markup::Node {

struct Nintendo64 {
bool expansionPak = true;
u8 controllerPakBankCount = 1;
string controllerPakBankString = "32KiB (Default)";
} nintendo64;

struct MegaDrive {
Expand Down Expand Up @@ -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};
Expand Down

0 comments on commit 1006d6e

Please sign in to comment.