Skip to content

Commit

Permalink
Preliminary support for Aleck64 (#1758)
Browse files Browse the repository at this point in the history
In its current form, all games that use standard Aleck64 hardware should
function correctly, using the current MAME romset at the time of writing
(0.273)

The following games are known to be working:

- Eleven Beat
- Hanabi de Doon! - Don-chan Puzzle
- Hi Pai Paradise
- Hi Pai Paradise 2
- Kurukuru Fever
- Mayjinsen 3
- Star Soldier: Vanishing Earth
- Super Real Mahjong VS
- Super Real Mahjong VS (Rev A)
- Tower & Shaft
- Vivid Dolls

The following games are known to be non-working:

- Magical Tetris Challenge (981009 Japan): Requires custom video
hardware: boots but unplayable.

Known Issues: 

- Games using EEPROM do not currently save their settings between
executions.|

---------

Co-authored-by: Darren Thompson <[email protected]>
  • Loading branch information
LukeUsher and DiscoStarslayer authored Jan 28, 2025
1 parent 76a5f8e commit 6946437
Show file tree
Hide file tree
Showing 44 changed files with 1,970 additions and 35 deletions.
32 changes: 32 additions & 0 deletions ares/n64/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ ares_add_sources(
cpu/cpu.cpp
rsp/rsp.cpp
rdp/rdp.cpp
aleck64/aleck64.cpp
)

target_enable_feature(ares "N64 Vulkan rendering with paraLLEl-RDP" VULKAN)
Expand Down Expand Up @@ -54,6 +55,29 @@ ares_add_sources(
ai/serialization.cpp
)

ares_add_sources(
CORE #
n64
INCLUDED #
aleck64/aleck64.hpp
aleck64/controls.cpp
aleck64/debugger.cpp
aleck64/serialization.cpp
aleck64/io.cpp
aleck64/vdp.cpp
aleck64/game-config/11beat.hpp
aleck64/game-config/doncdoon.hpp
aleck64/game-config/kurufev.hpp
aleck64/game-config/mayjin3.hpp
aleck64/game-config/starsldr.hpp
aleck64/game-config/twrshaft.hpp
aleck64/game-config/vivdolls.hpp
aleck64/game-config/hipai.hpp
aleck64/game-config/hipai2.hpp
aleck64/game-config/srmvs.hpp
aleck64/game-config/mtetrisc.hpp
)

ares_add_sources(
CORE #
n64
Expand Down Expand Up @@ -88,6 +112,14 @@ ares_add_sources(
controller/port.hpp
)

ares_add_sources(
CORE #
n64
INCLUDED #
controller/aleck64/aleck64.cpp
controller/aleck64/aleck64.hpp
)

ares_add_sources(
CORE #
n64
Expand Down
2 changes: 2 additions & 0 deletions ares/n64/GNUmakefile
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ ares.objects += ares-n64-rdram
ares.objects += ares-n64-cpu
ares.objects += ares-n64-rsp
ares.objects += ares-n64-rdp
ares.objects += ares-n64-aleck64

$(object.path)/ares-n64-memory.o: $(ares.path)/n64/memory/memory.cpp
$(object.path)/ares-n64-system.o: $(ares.path)/n64/system/system.cpp
Expand All @@ -37,6 +38,7 @@ $(object.path)/ares-n64-rdram.o: $(ares.path)/n64/rdram/rdram.cpp
$(object.path)/ares-n64-cpu.o: $(ares.path)/n64/cpu/cpu.cpp
$(object.path)/ares-n64-rsp.o: $(ares.path)/n64/rsp/rsp.cpp
$(object.path)/ares-n64-rdp.o: $(ares.path)/n64/rdp/rdp.cpp
$(object.path)/ares-n64-aleck64.o: $(ares.path)/n64/aleck64/aleck64.cpp

ifeq ($(vulkan),true)
ifeq ($(platform),macos)
Expand Down
73 changes: 73 additions & 0 deletions ares/n64/aleck64/aleck64.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
#include <n64/n64.hpp>

namespace ares::Nintendo64 {
Aleck64 aleck64;

#include "io.cpp"
#include "controls.cpp"
#include "debugger.cpp"
#include "vdp.cpp"
#include "serialization.cpp"

#include "game-config/11beat.hpp"
#include "game-config/starsldr.hpp"
#include "game-config/doncdoon.hpp"
#include "game-config/kurufev.hpp"
#include "game-config/mayjin3.hpp"
#include "game-config/vivdolls.hpp"
#include "game-config/twrshaft.hpp"
#include "game-config/hipai.hpp"
#include "game-config/hipai2.hpp"
#include "game-config/srmvs.hpp"
#include "game-config/mtetrisc.hpp"

auto Aleck64::load(Node::Object parent) -> void {
sdram.allocate(4_MiB);
vram.allocate(4_KiB);
pram.allocate(4_KiB);
controls.load(parent);
gameConfig.reset();
dipSwitchNode = parent->append<Node::Object>("DIP Switches");

debugger.load(parent);
}

auto Aleck64::unload() -> void {
sdram.reset();
debugger.unload();
}

auto Aleck64::save() -> void {

}

auto Aleck64::power(bool reset) -> void {
if(!reset) {
dipSwitch[0] = 0xffff'ffff;
dipSwitch[1] = 0xffff'ffff;

//NOTE: We can't do this at 'load' time because cartridges are not attached yet...
auto name = cartridge.pak->attribute("name");

if(name == "11beat" ) gameConfig = new _11beat();
if(name == "starsldr") gameConfig = new starsldr();
if(name == "doncdoon") gameConfig = new doncdoon();
if(name == "kurufev" ) gameConfig = new kurufev();
if(name == "mayjin3" ) gameConfig = new mayjin3();
if(name == "vivdolls") gameConfig = new vivdolls();
if(name == "twrshaft") gameConfig = new twrshaft();
if(name == "hipai" ) gameConfig = new hipai();
if(name == "hipai2" ) gameConfig = new hipai2();
if(name == "srmvs" ) gameConfig = new srmvs();
if(name == "srmvsa" ) gameConfig = new srmvs();
if(name == "mtetrisc") gameConfig = new mtetrisc();
if(!gameConfig) gameConfig = new GameConfig(); //Fallback to default implementation

gameConfig->dipSwitches(dipSwitchNode);

sdram.fill();
vram.fill();
pram.fill();
}
}
}
241 changes: 241 additions & 0 deletions ares/n64/aleck64/aleck64.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,241 @@
struct Aleck64 {
Node::Object dipSwitchNode;
enum BoardType { E90, E92 };

struct Writable : public Memory::Writable {
Aleck64& self;

Writable(Aleck64& self) : self(self) {}

template<u32 Size>
auto writeBurst(u32 address, u32 *value, const char *peripheral) -> void {
address = address & 0x00ff'ffff;
if(address >= size) return;
Memory::Writable::write<Word>(address | 0x00, value[0]);
Memory::Writable::write<Word>(address | 0x04, value[1]);
Memory::Writable::write<Word>(address | 0x08, value[2]);
Memory::Writable::write<Word>(address | 0x0c, value[3]);
if(Size == ICache) {
Memory::Writable::write<Word>(address | 0x10, value[4]);
Memory::Writable::write<Word>(address | 0x14, value[5]);
Memory::Writable::write<Word>(address | 0x18, value[6]);
Memory::Writable::write<Word>(address | 0x1c, value[7]);
}
}

template<u32 Size>
auto readBurst(u32 address, u32 *value, const char *peripheral) -> void {
address = address & 0x00ff'ffff;
if(address >= size) {
value[0] = value[1] = value[2] = value[3] = 0;
if(Size == ICache)
value[4] = value[5] = value[6] = value[7] = 0;
return;
}
value[0] = Memory::Writable::read<Word>(address | 0x00);
value[1] = Memory::Writable::read<Word>(address | 0x04);
value[2] = Memory::Writable::read<Word>(address | 0x08);
value[3] = Memory::Writable::read<Word>(address | 0x0c);
if(Size == ICache) {
value[4] = Memory::Writable::read<Word>(address | 0x10);
value[5] = Memory::Writable::read<Word>(address | 0x14);
value[6] = Memory::Writable::read<Word>(address | 0x18);
value[7] = Memory::Writable::read<Word>(address | 0x1c);
}
}

} sdram{*this};

Memory::Writable vram;
Memory::Writable pram;

struct Debugger {
Node::Object parent;
//debugger.cpp
auto load(Node::Object) -> void;
auto unload() -> void;

struct Memory {
Node::Debugger::Memory sdram;
Node::Debugger::Memory vram;
Node::Debugger::Memory pram;
} memory;
} debugger;

auto load(Node::Object) -> void;
auto unload() -> void;
auto power(bool reset) -> void;
auto save() -> void;

template<u32 Size>
auto formatRead(u32 address, u32 data) -> u32 {
if constexpr(Size == Byte) {;
switch(address & 3) {
case 0: return data >> 24;
case 1: return data >> 16;
case 2: return data >> 8;
case 3: return data >> 0;
}
}
if constexpr(Size == Half) {
switch(address & 2) {
case 0: return data >> 16;
case 2: return data >> 0;
}
}
if constexpr(Size == Word) {
return data;
}

unreachable;
}

template<u32 Size> auto read(u32 address, Thread& thread) -> u32 {
if(address <= 0xc07f'ffff) {
return sdram.read<Size>(address & 0x00ff'ffff);
}

controls.poll();
if(address <= 0xc080'0fff) {
n32 value = 0xffff'ffff;

switch (address & 0xffff'fffc) {
case 0xc080'0000: return formatRead<Size>(address, readPort1());
case 0xc080'0004: return formatRead<Size>(address, readPort2());
case 0xc080'0008: return formatRead<Size>(address, readPort3());
case 0xc080'0100: return formatRead<Size>(address, readPort4());
}
}

if(gameConfig->type() == BoardType::E90) {
if(address >= 0xd000'0000 && address <= 0xd000'0fff) return vram.read<Size>(address & 0x0000'0fff);
if(address >= 0xd001'0000 && address <= 0xd001'0fff) return pram.read<Size>(address & 0x0000'0fff);
if(address >= 0xd003'0000 && address <= 0xd003'001f) return formatRead<Size>(address, vdp.readWord(address & 0x0000'001f));
} else if(gameConfig->type() == BoardType::E92) {
if(address >= 0xd080'0000 && address <= 0xd080'0fff) return vram.read<Size>(address & 0x0000'0fff);
if(address >= 0xd080'1000 && address <= 0xd080'1fff) return pram.read<Size>(address & 0x0000'0fff);
if(address >= 0xd080'2000 && address <= 0xd080'201f) return formatRead<Size>(address, vdp.readWord(address & 0x0000'001f));
}

debug(unusual, "[Aleck64::read] Unmapped address: 0x", hex(address, 8L));
return 0xffffffff;
}

template<u32 Size> auto write(u32 address, u32 data, Thread& thread) -> void {
if(address <= 0xc07f'ffff) {
return sdram.write<Size>(address & 0x00ff'ffff, data);
}

if(address <= 0xc080'0fff) {
switch (address & 0xffff'fffc) {
case 0xc080'0008: return writePort3(data);
case 0xc080'0100: return writePort4(data);
}
}

if(gameConfig->type() == BoardType::E90) {
if(address >= 0xd000'0000 && address <= 0xd000'0fff) return vram.write<Size>(address & 0x0000'0fff, data);
if(address >= 0xd001'0000 && address <= 0xd001'0fff) return pram.write<Size>(address & 0x0000'0fff, data);
if(address >= 0xd003'0000 && address <= 0xd003'001f) return vdp.writeWord (address & 0x0000'001f, data);
} else if(gameConfig->type() == BoardType::E92) {
if(address >= 0xd080'0000 && address <= 0xd080'0fff) return vram.write<Size>(address & 0x0000'0fff, data);
if(address >= 0xd080'1000 && address <= 0xd080'1fff) return pram.write<Size>(address & 0x0000'0fff, data);
if(address >= 0xd080'2000 && address <= 0xd080'201f) return vdp.writeWord (address & 0x0000'001f, data);
}

debug(unusual, "[Aleck64::write] ", hex(address, 8L), " = ", hex(data, 8L));
}

//io.cpp
auto readPort1() -> u32;
auto readPort2() -> u32;
auto readPort3() -> u32;
auto readPort4() -> u32;
auto writePort3(n32 data) -> void;
auto writePort4(n32 data) -> void;

//serialization.cpp
auto serialize(serializer&) -> void;

struct Controls {
Aleck64& self;
Node::Object node;

Node::Input::Button service;
Node::Input::Button test;

Node::Input::Axis p1x;
Node::Input::Axis p1y;
Node::Input::Button p1up;
Node::Input::Button p1down;
Node::Input::Button p1left;
Node::Input::Button p1right;
Node::Input::Button p1[9];
Node::Input::Button p1start;
Node::Input::Button p1coin;

Node::Input::Axis p2x;
Node::Input::Axis p2y;
Node::Input::Button p2up;
Node::Input::Button p2down;
Node::Input::Button p2left;
Node::Input::Button p2right;
Node::Input::Button p2[9];
Node::Input::Button p2start;
Node::Input::Button p2coin;

Node::Input::Button mahjongA;
Node::Input::Button mahjongB;
Node::Input::Button mahjongC;
Node::Input::Button mahjongD;
Node::Input::Button mahjongE;
Node::Input::Button mahjongF;
Node::Input::Button mahjongG;
Node::Input::Button mahjongH;
Node::Input::Button mahjongI;
Node::Input::Button mahjongJ;
Node::Input::Button mahjongK;
Node::Input::Button mahjongL;
Node::Input::Button mahjongM;
Node::Input::Button mahjongN;
Node::Input::Button mahjongKan;
Node::Input::Button mahjongPon;
Node::Input::Button mahjongChi;
Node::Input::Button mahjongReach;
Node::Input::Button mahjongRon;

//controls.cpp
auto load(Node::Object) -> void;
auto poll() -> void;

auto controllerButton(int playerIndex, string button) -> bool;
auto controllerAxis(int playerIndex, string axis) -> s64;
auto mahjong(n8 row) -> n8;

} controls{*this};

struct VDP {
auto readWord(u32 address) -> u32;
auto writeWord(u32 address, n32 data) -> void;
auto render(Node::Video::Screen screen) -> void;

struct IO {
n1 enable;
} io;
} vdp;

n8 dipSwitch[2];

struct GameConfig {
virtual ~GameConfig() = default;
virtual auto type() -> BoardType { return BoardType::E92; }
virtual auto dpadDisabled() -> n1 { return 0; }
virtual auto dipSwitches(Node::Object parent) -> void {};
virtual auto readExpansionPort() -> u32 { return 0xffff'ffff; }
virtual auto writeExpansionPort(n32 data) -> void {};
};

shared_pointer<GameConfig> gameConfig;
};

extern Aleck64 aleck64;
Loading

0 comments on commit 6946437

Please sign in to comment.