This commit is contained in:
2026-03-23 12:11:07 +01:00
commit e64eb40b38
4573 changed files with 3117439 additions and 0 deletions
+7
View File
@@ -0,0 +1,7 @@
file(GLOB SOURCES *.cpp)
file(GLOB HEADERS *.hpp)
add_subdirectory(core)
add_library(backend ${SOURCES} ${HEADERS})
target_link_libraries(backend PRIVATE core)
+136
View File
@@ -0,0 +1,136 @@
#include <Core.hpp>
#include <ParallelRDPWrapper.hpp>
#include <Scheduler.hpp>
#include <Options.hpp>
namespace n64 {
Core::Core() {
const auto selectedCpu = Options::GetInstance().GetValue<std::string>("cpu", "type");
if (selectedCpu == "interpreter") {
cpuType = Interpreted;
cpu = std::make_unique<Interpreter>(*mem, regs);
} else if(selectedCpu == "jit") {
#ifndef __aarch64__
cpuType = DynamicRecompiler;
cpu = std::make_unique<JIT>(*mem, regs);
#else
panic("JIT currently unsupported on aarch64");
#endif
} else {
panic("Unimplemented CPU type");
}
}
void Core::Stop() {
pause = true;
romLoaded = false;
Reset();
}
void Core::Reset() {
regs.Reset();
mem->Reset();
cpu->Reset();
if(romLoaded)
mem->mmio.si.pif.Execute();
}
void Core::LoadTAS(const fs::path &path) const { mem->mmio.si.pif.movie.Load(path); }
void Core::LoadROM(const std::string &rom_) {
Stop();
rom = rom_;
std::string archive_types[] = {".zip", ".7z", ".rar", ".tar"};
auto extension = fs::path(rom).extension().string();
const bool isArchive = std::ranges::any_of(archive_types, [&extension](const auto &e) { return e == extension; });
mem->LoadROM(isArchive, rom);
GameDB::match();
if (mem->rom.gameNameDB.empty()) {
mem->rom.gameNameDB = fs::path(rom).stem().string();
}
mem->mmio.vi.isPal = mem->IsROMPAL();
mem->mmio.si.pif.InitDevices(mem->saveType);
mem->mmio.si.pif.mempakPath = rom;
mem->mmio.si.pif.LoadEeprom(mem->saveType, rom);
mem->flash.Load(mem->saveType, rom);
mem->LoadSRAM(mem->saveType, rom);
mem->mmio.si.pif.Execute();
pause = false;
romLoaded = true;
}
u32 Core::StepCPU() {
return cpu->Step() + regs.PopStalledCycles();
}
void Core::StepRSP(const u32 cpuCycles) {
MMIO &mmio = mem->mmio;
if (mmio.rsp.spStatus.halt) {
regs.steps = 0;
mmio.rsp.steps = 0;
return;
}
static constexpr u32 cpuRatio = 3, rspRatio = 2;
regs.steps += cpuCycles;
const auto sets = regs.steps / cpuRatio;
mmio.rsp.steps += sets * rspRatio;
regs.steps -= sets * cpuRatio;
while (mmio.rsp.steps > 0) {
mmio.rsp.steps--;
mmio.rsp.Step();
}
}
void Core::Run(const float volumeL, const float volumeR) {
MMIO &mmio = mem->mmio;
bool broken = false;
for (int field = 0; field < mmio.vi.numFields; field++) {
u32 frameCycles = 0;
for (int i = 0; i < mmio.vi.numHalflines; i++) {
mmio.vi.current = (i << 1) + field;
if ((mmio.vi.current & 0x3FE) == mmio.vi.intr) {
mmio.mi.InterruptRaise(MI::Interrupt::VI);
}
while(cycles < mem->mmio.vi.cyclesPerHalfline) {
const u32 taken = StepCPU();
cycles += taken;
if((broken = breakpoints.contains(regs.nextPC)))
break;
StepRSP(taken);
frameCycles += taken;
Scheduler::GetInstance().Tick(taken);
}
if(broken)
break;
cycles -= mmio.vi.cyclesPerHalfline;
}
if(broken)
break;
if ((mmio.vi.current & 0x3FE) == mmio.vi.intr) {
mmio.mi.InterruptRaise(MI::Interrupt::VI);
}
mmio.ai.Step(frameCycles, volumeL, volumeR);
Scheduler::GetInstance().Tick(frameCycles);
}
if(broken)
pause = true;
}
} // namespace n64
+62
View File
@@ -0,0 +1,62 @@
#pragma once
#include <ParallelRDPWrapper.hpp>
#include <backend/core/Interpreter.hpp>
#include <backend/core/JIT.hpp>
#include <string>
#include <set>
#include <variant>
namespace n64 {
struct Core {
enum CPUType {
Interpreted,
DynamicRecompiler,
CachedInterpreter
} cpuType = Interpreted;
explicit Core();
static Core& GetInstance() {
static Core instance;
return instance;
}
static Registers& GetRegs() {
return GetInstance().regs;
}
static Mem& GetMem() {
return *GetInstance().mem;
}
u32 StepCPU();
void StepRSP(u32 cpuCycles);
void Stop();
void Reset();
void LoadROM(const std::string &);
void LoadTAS(const fs::path &) const;
void Run(float volumeL, float volumeR);
void TogglePause() { pause = !pause; }
inline void ToggleBreakpoint(s64 addr) {
if(breakpoints.contains(addr)) {
breakpoints.erase(addr);
return;
}
breakpoints.insert(addr);
}
bool pause = true;
bool romLoaded = false;
int slot = 0;
u32 cycles = 0;
size_t memSize{}, cpuSize{}, verSize{};
std::string rom;
std::set<s64> breakpoints{};
std::unique_ptr<Mem> mem = std::make_unique<Mem>();
std::unique_ptr<BaseCPU> cpu;
Registers regs;
ParallelRDP parallel;
};
} // namespace n64
+39
View File
@@ -0,0 +1,39 @@
#include <GameDB.hpp>
#include <Core.hpp>
namespace n64 {
void GameDB::match() {
n64::Mem& mem = n64::Core::GetMem();
const ROM &rom = mem.rom;
for (const auto &[code, regions, saveType, name] : gamedb) {
const bool matches_code = code == rom.code;
bool matches_region = false;
for (int j = 0; j < regions.size() && !matches_region; j++) {
if (regions[j] == rom.header.countryCode[0]) {
matches_region = true;
}
}
if (matches_code) {
if (matches_region) {
mem.saveType = saveType;
mem.rom.gameNameDB = name;
return;
}
warn(
"Matched code for {}, but not region! Game supposedly exists in regions [{}] but this image has region {}",
name, regions, rom.header.countryCode[0]);
mem.saveType = saveType;
mem.rom.gameNameDB = name;
return;
}
}
warn("Did not match any Game DB entries. Code: {} Region: {}", mem.rom.code, mem.rom.header.countryCode[0]);
mem.rom.gameNameDB = "";
mem.saveType = SAVE_NONE;
}
} // namespace n64
+203
View File
@@ -0,0 +1,203 @@
#pragma once
#include <string>
namespace n64 {
enum SaveType { SAVE_NONE, SAVE_EEPROM_4k, SAVE_EEPROM_16k, SAVE_FLASH_1m, SAVE_SRAM_256k };
struct GameDBEntry {
std::string code;
std::string regions;
SaveType saveType;
const char *name;
};
namespace GameDB {
void match();
}
static const GameDBEntry gamedb[] = {
{"NNM", "E", SAVE_NONE, "Namco Museum 64"},
{"NDM", "E", SAVE_NONE, "Doom 64"},
{"NGN", "E", SAVE_EEPROM_4k, "GoldenEye 007"},
// Copied from CEN64 with small edits: https://github.com/n64dev/cen64/blob/master/device/cart_db.c
{"CFZ", "EJ", SAVE_SRAM_256k, "F-Zero X (NTSC)"},
{"CLB", "EJ", SAVE_EEPROM_4k, "Mario Party (NTSC)"},
{"CP2", "J", SAVE_FLASH_1m, "Pokémon Stadium 2 (Japan)"},
{"CPS", "J", SAVE_SRAM_256k, "Pokémon Stadium (Japan)"},
{"CZL", "EJ", SAVE_SRAM_256k, "Legend of Zelda: Ocarina of Time (NTSC)"},
{"N3D", "J", SAVE_EEPROM_16k, "Doraemon 3: Nobita no Machi SOS!"},
{"N3H", "J", SAVE_SRAM_256k, "Ganbare! Nippon! Olympics 2000"},
{"NA2", "J", SAVE_SRAM_256k, "Virtual Pro Wrestling 2"},
{"NAB", "JP", SAVE_EEPROM_4k, "Air Boarder 64"},
{"NAD", "E", SAVE_EEPROM_4k, "Worms Armageddon (USA)"},
{"NAF", "J", SAVE_FLASH_1m, "Doubutsu no Mori"},
{"NAG", "EJP", SAVE_EEPROM_4k, "AeroGauge"},
{"NAL", "EJPU", SAVE_SRAM_256k, "Super Smash Bros"},
{"NB5", "J", SAVE_SRAM_256k, "Biohazard 2"},
{"NB6", "J", SAVE_EEPROM_4k, "Super B-Daman: Battle Phoenix 64"},
{"NB7", "EJPU", SAVE_EEPROM_16k, "Banjo-Tooie"},
{"NBC", "EJP", SAVE_EEPROM_4k, "Blast Corps"},
{"NBD", "EJP", SAVE_EEPROM_4k, "Bomberman Hero"},
{"NBH", "EP", SAVE_EEPROM_4k, "Body Harvest"},
{"NBK", "EJP", SAVE_EEPROM_4k, "Banjo-Kazooie"},
{"NBM", "EJP", SAVE_EEPROM_4k, "Bomberman 64"},
{"NBN", "J", SAVE_EEPROM_4k, "Bakuretsu Muteki Bangaioh"},
{"NBV", "EJ", SAVE_EEPROM_4k, "Bomberman 64: The Second Attack!"},
{"NCC", "DEP", SAVE_FLASH_1m, "Command & Conquer"},
{"NCG", "J", SAVE_EEPROM_4k, "Choro Q 64 2: Hacha-Mecha Grand Prix Race"},
{"NCH", "EP", SAVE_EEPROM_4k, "Chopper Attack"},
{"NCK", "E", SAVE_FLASH_1m, "NBA Courtside 2"},
{"NCR", "EJP", SAVE_EEPROM_4k, "Penny Racers"},
{"NCT", "EJP", SAVE_EEPROM_4k, "Chameleon Twist"},
{"NCU", "EP", SAVE_EEPROM_4k, "Cruis'n USA"},
{"NCW", "EP", SAVE_EEPROM_16k, "Cruis'n World"},
{"NCX", "J", SAVE_EEPROM_4k, "Custom Robo"},
{"NCZ", "J", SAVE_EEPROM_16k, "Custom Robo V2"},
{"ND2", "J", SAVE_EEPROM_16k, "Doraemon 2: Nobita to Hikari no Shinden"},
{"ND3", "J", SAVE_EEPROM_16k, "Akumajou Dracula Mokushiroku"},
{"ND4", "J", SAVE_EEPROM_16k, "Akumajou Dracula Mokushiroku Gaiden: Legend of Cornell"},
{"ND6", "J", SAVE_EEPROM_16k, "Densha de Go! 64"},
{"NDA", "J", SAVE_FLASH_1m, "Derby Stallion 64"},
{"NDK", "J", SAVE_EEPROM_4k, "Space Dynamites"},
{"NDO", "EJP", SAVE_EEPROM_16k, "Donkey Kong 64"},
{"NDP", "E", SAVE_FLASH_1m, "Dinosaur Planet"},
{"NDR", "J", SAVE_EEPROM_4k, "Doraemon: Nobita to 3tsu no Seireiseki"},
{"NDU", "EP", SAVE_EEPROM_4k, "Duck Dodgers"},
{"NDY", "EJP", SAVE_EEPROM_4k, "Diddy Kong Racing"},
{"NEA", "EP", SAVE_EEPROM_4k, "PGA European Tour"},
{"NEP", "EJP", SAVE_EEPROM_16k, "Star Wars Episode I: Racer"},
{"NER", "E", SAVE_EEPROM_4k, "AeroFighters Assault (USA)"},
{"NEV", "J", SAVE_EEPROM_16k, "Neon Genesis Evangelion"},
{"NF2", "P", SAVE_EEPROM_4k, "F-1 World Grand Prix II"},
{"NFG", "E", SAVE_EEPROM_4k, "Fighter Destiny 2"},
{"NFH", "EP", SAVE_EEPROM_4k, "Bass Hunter 64"},
{"NFU", "EP", SAVE_EEPROM_16k, "Conker's Bad Fur Day"},
{"NFW", "DEFJP", SAVE_EEPROM_4k, "F-1 World Grand Prix"},
{"NFX", "EJPU", SAVE_EEPROM_4k, "Star Fox 64"},
{"NFY", "J", SAVE_EEPROM_4k, "Kakutou Denshou: F-Cup Maniax"},
{"NFZ", "P", SAVE_SRAM_256k, "F-Zero X (PAL)"},
{"NG6", "J", SAVE_SRAM_256k, "Ganbare Goemon: Dero Dero Douchuu Obake Tenkomori"},
{"NGC", "EP", SAVE_EEPROM_16k, "GT 64: Championship Edition"},
{"NGE", "EJP", SAVE_EEPROM_4k, "GoldenEye 007"},
{"NGL", "J", SAVE_EEPROM_4k, "Getter Love!!"},
{"NGP", "J", SAVE_SRAM_256k, "Goemon: Mononoke Sugoroku"},
{"NGT", "J", SAVE_EEPROM_16k, "City-Tour GP: Zen-Nihon GT Senshuken"},
{"NGU", "J", SAVE_EEPROM_4k, "Tsumi to Batsu: Hoshi no Keishousha"},
{"NGV", "EP", SAVE_EEPROM_4k, "Glover"},
{"NHA", "J", SAVE_EEPROM_4k, "Bomber Man 64 (Japan)"},
{"NHF", "J", SAVE_EEPROM_4k, "64 Hanafuda: Tenshi no Yakusoku"},
{"NHP", "J", SAVE_EEPROM_4k, "Heiwa Pachinko World 64"},
{"NHY", "J", SAVE_SRAM_256k, "Hybrid Heaven (Japan)"},
{"NIB", "J", SAVE_SRAM_256k, "Itoi Shigesato no Bass Tsuri No. 1 Kettei Ban!"},
{"NIC", "E", SAVE_EEPROM_4k, "Indy Racing 2000"},
{"NIJ", "EP", SAVE_EEPROM_4k, "Indiana Jones and the Infernal Machine"},
{"NIM", "J", SAVE_EEPROM_16k, "Ide Yosuke no Mahjong Juku"},
{"NIR", "J", SAVE_EEPROM_4k, "Utchan Nanchan no Hono no Challenger: Denryuu Ira Ira Bou"},
{"NJ5", "J", SAVE_SRAM_256k, "Jikkyou Powerful Pro Yakyuu 5"},
{"NJD", "E", SAVE_FLASH_1m, "Jet Force Gemini (Kiosk Demo)"},
{"NJF", "EJP", SAVE_FLASH_1m, "Jet Force Gemini"},
{"NJG", "J", SAVE_SRAM_256k, "Jinsei Game 64"},
{"NJM", "EP", SAVE_EEPROM_4k, "Earthworm Jim 3D"},
{"NK2", "EJP", SAVE_EEPROM_4k, "Snowboard Kids 2"},
{"NK4", "EJP", SAVE_EEPROM_16k, "Kirby 64: The Crystal Shards"},
{"NKA", "DEFJP", SAVE_EEPROM_4k, "Fighters Destiny"},
{"NKG", "EP", SAVE_SRAM_256k, "MLB featuring Ken Griffey Jr."},
{"NKI", "EP", SAVE_EEPROM_4k, "Killer Instinct Gold"},
{"NKJ", "E", SAVE_FLASH_1m, "Ken Griffey Jr.'s Slugfest"},
{"NKT", "EJP", SAVE_EEPROM_4k, "Mario Kart 64"},
{"NLB", "P", SAVE_EEPROM_4k, "Mario Party (PAL)"},
{"NLL", "J", SAVE_EEPROM_4k, "Last Legion UX"},
{"NLR", "EJP", SAVE_EEPROM_4k, "Lode Runner 3D"},
{"NM6", "E", SAVE_FLASH_1m, "Mega Man 64"},
{"NM8", "EJP", SAVE_EEPROM_16k, "Mario Tennis"},
{"NMF", "EJP", SAVE_SRAM_256k, "Mario Golf"},
{"NMG", "DEP", SAVE_EEPROM_4k, "Monaco Grand Prix"},
{"NMI", "DEFIPS", SAVE_EEPROM_4k, "Mission: Impossible"},
{"NML", "EJP", SAVE_EEPROM_4k, "Mickey's Speedway USA"},
{"NMO", "E", SAVE_EEPROM_4k, "Monopoly"},
{"NMQ", "EJP", SAVE_FLASH_1m, "Paper Mario"},
{"NMR", "EJP", SAVE_EEPROM_4k, "Multi Racing Championship"},
{"NMS", "J", SAVE_EEPROM_4k, "Morita Shougi 64"},
{"NMU", "E", SAVE_EEPROM_4k, "Big Mountain 2000"},
{"NMV", "EJP", SAVE_EEPROM_16k, "Mario Party 3"},
{"NMW", "EJP", SAVE_EEPROM_4k, "Mario Party 2"},
{"NMX", "EJP", SAVE_EEPROM_16k, "Excitebike 64"},
{"NN6", "E", SAVE_EEPROM_4k, "Dr. Mario 64"},
{"NNA", "EP", SAVE_EEPROM_4k, "Star Wars Episode I: Battle for Naboo"},
{"NNB", "EP", SAVE_EEPROM_16k, "Kobe Bryant in NBA Courtside"},
{"NOB", "EJ", SAVE_SRAM_256k, "Ogre Battle 64: Person of Lordly Caliber"},
{"NOS", "J", SAVE_EEPROM_4k, "64 Oozumou"},
{"NP2", "J", SAVE_EEPROM_4k, "Chou Kuukan Nighter Pro Yakyuu King 2"},
{"NP3", "DEFIJPS", SAVE_FLASH_1m, "Pokémon Stadium 2"},
{"NP6", "J", SAVE_SRAM_256k, "Jikkyou Powerful Pro Yakyuu 6"},
{"NPA", "J", SAVE_SRAM_256k, "Jikkyou Powerful Pro Yakyuu 2000"},
{"NPD", "EJP", SAVE_EEPROM_16k, "Perfect Dark"},
{"NPE", "J", SAVE_SRAM_256k, "Jikkyou Powerful Pro Yakyuu Basic Ban 2001"},
{"NPF", "DEFIJPSU", SAVE_FLASH_1m, "Pokémon Snap"},
{"NPG", "EJ", SAVE_EEPROM_4k, "Hey You, Pikachu!"},
{"NPH", "E", SAVE_FLASH_1m, "Pokémon Snap Station (Kiosk Demo)"},
{"NPM", "P", SAVE_SRAM_256k, "Premier Manager 64"},
{"NPN", "DEFP", SAVE_FLASH_1m, "Pokémon Puzzle League"},
{"NPO", "DEFIPS", SAVE_FLASH_1m, "Pokémon Stadium (USA, PAL)"},
{"NPP", "J", SAVE_EEPROM_16k, "Parlor! Pro 64: Pachinko Jikki Simulation Game"},
{"NPS", "J", SAVE_SRAM_256k, "Jikkyou J.League 1999: Perfect Striker 2"},
{"NPT", "J", SAVE_EEPROM_4k, "Puyo Puyon Party"},
{"NPW", "EJP", SAVE_EEPROM_4k, "Pilotwings 64"},
{"NPY", "J", SAVE_EEPROM_4k, "Puyo Puyo Sun 64"},
{"NR7", "J", SAVE_EEPROM_16k, "Robot Poncots 64: 7tsu no Umi no Caramel"},
{"NRA", "J", SAVE_EEPROM_4k, "Rally '99"},
{"NRC", "EJP", SAVE_EEPROM_4k, "Top Gear Overdrive"},
{"NRE", "EP", SAVE_SRAM_256k, "Resident Evil 2"},
{"NRH", "J", SAVE_FLASH_1m, "Rockman Dash"},
{"NRI", "EP", SAVE_SRAM_256k, "The New Tetris"},
{"NRS", "EJP", SAVE_EEPROM_4k, "Star Wars: Rogue Squadron"},
{"NRZ", "EP", SAVE_EEPROM_16k, "Ridge Racer 64"},
{"NS4", "J", SAVE_SRAM_256k, "Super Robot Taisen 64"},
{"NS6", "EJ", SAVE_EEPROM_4k, "Star Soldier: Vanishing Earth"},
{"NSA", "JP", SAVE_EEPROM_4k, "AeroFighters Assault (PAL, Japan)"},
{"NSC", "EP", SAVE_EEPROM_4k, "Starshot: Space Circus Fever"},
{"NSI", "J", SAVE_SRAM_256k, "Fushigi no Dungeon: Fuurai no Shiren 2"},
{"NSM", "EJP", SAVE_EEPROM_4k, "Super Mario 64"},
{"NSN", "J", SAVE_EEPROM_4k, "Snow Speeder"},
{"NSQ", "EP", SAVE_FLASH_1m, "StarCraft 64"},
{"NSS", "J", SAVE_EEPROM_4k, "Super Robot Spirits"},
{"NSU", "EP", SAVE_EEPROM_4k, "Rocket: Robot on Wheels"},
{"NSV", "EP", SAVE_EEPROM_4k, "SpaceStation Silicon Valley"},
{"NSW", "EJP", SAVE_EEPROM_4k, "Star Wars: Shadows of the Empire"},
{"NT3", "J", SAVE_SRAM_256k, "Toukon Road 2"},
{"NT6", "J", SAVE_EEPROM_4k, "Tetris 64"},
{"NT9", "EP", SAVE_FLASH_1m, "Tigger's Honey Hunt"},
{"NTB", "J", SAVE_EEPROM_4k, "Transformers: Beast Wars Metals 64"},
{"NTC", "J", SAVE_EEPROM_4k, "64 Trump Collection"},
{"NTE", "AP", SAVE_SRAM_256k, "1080 Snowboarding"},
{"NTJ", "EP", SAVE_EEPROM_4k, "Tom and Jerry in Fists of Furry"},
{"NTM", "EJP", SAVE_EEPROM_4k, "Mischief Makers"},
{"NTN", "EP", SAVE_EEPROM_4k, "All-Star Tennis 99"},
{"NTP", "EP", SAVE_EEPROM_4k, "Tetrisphere"},
{"NTR", "JP", SAVE_EEPROM_4k, "Top Gear Rally (PAL, Japan)"},
{"NTW", "J", SAVE_EEPROM_4k, "64 de Hakken!! Tamagotchi"},
{"NTX", "EP", SAVE_EEPROM_4k, "Taz Express"},
{"NUB", "J", SAVE_EEPROM_16k, "PD Ultraman Battle Collection 64"},
{"NUM", "J", SAVE_SRAM_256k, "Nushi Zuri 64: Shiokaze ni Notte"},
{"NUT", "J", SAVE_SRAM_256k, "Nushi Zuri 64"},
{"NVB", "J", SAVE_SRAM_256k, "Bass Rush: ECOGEAR PowerWorm Championship"},
{"NVL", "EP", SAVE_EEPROM_4k, "V-Rally 99 (USA, PAL)"},
{"NVP", "J", SAVE_SRAM_256k, "Virtual Pro Wrestling 64"},
{"NVY", "J", SAVE_EEPROM_4k, "V-Rally 99 (Japan)"},
{"NW2", "EP", SAVE_SRAM_256k, "WCW/nWo Revenge"},
{"NW4", "EP", SAVE_FLASH_1m, "WWF No Mercy"},
{"NWC", "J", SAVE_EEPROM_4k, "Wild Choppers"},
{"NWL", "EP", SAVE_SRAM_256k, "Waialae Country Club: True Golf Classics"},
{"NWQ", "E", SAVE_EEPROM_4k, "Rally Challenge 2000"},
{"NWR", "EJP", SAVE_EEPROM_4k, "Wave Race 64"},
{"NWT", "J", SAVE_EEPROM_4k, "Wetrix (Japan)"},
{"NWU", "P", SAVE_EEPROM_4k, "Worms Armageddon (PAL)"},
{"NWX", "EJP", SAVE_SRAM_256k, "WWF WrestleMania 2000"},
{"NXO", "E", SAVE_EEPROM_4k, "Cruis'n Exotica"},
{"NYK", "J", SAVE_EEPROM_4k, "Yakouchuu II: Satsujin Kouro"},
{"NYS", "EJP", SAVE_EEPROM_16k, "Yoshi's Story"},
{"NYW", "EJ", SAVE_SRAM_256k, "Harvest Moon 64"},
{"NZL", "P", SAVE_SRAM_256k, "Legend of Zelda: Ocarina of Time (PAL)"},
{"NZS", "EJP", SAVE_FLASH_1m, "Legend of Zelda: Majora's Mask"},
};
}
+137
View File
@@ -0,0 +1,137 @@
#pragma once
#include <common.hpp>
#define RDRAM_SIZE 0x800000
#define RDRAM_DSIZE (RDRAM_SIZE - 1)
#define SRAM_SIZE 256_kb
#define SRAM_DSIZE (SRAM_SIZE - 1)
#define DMEM_SIZE 0x1000
#define DMEM_DSIZE (DMEM_SIZE - 1)
#define IMEM_SIZE 0x1000
#define IMEM_DSIZE (IMEM_SIZE - 1)
#define PIF_RAM_SIZE 0x40
#define PIF_RAM_DSIZE (PIF_RAM_SIZE - 1)
#define PIF_BOOTROM_SIZE 0x7C0
#define PIF_BOOTROM_DSIZE (PIF_BOOTROM_SIZE - 1)
#define ISVIEWER_SIZE (0x13FFFFFF - 0x13FF0020)
#define ISVIEWER_DSIZE (ISVIEWER_SIZE - 1)
#define CART_SIZE 0xFC00000
#define CART_DSIZE (CART_REGION_SIZE - 1)
#define RDRAM_REGION_START 0
#define RDRAM_REGION_END RDRAM_DSIZE
#define DMEM_REGION_START 0x4000000
#define DMEM_REGION_END (DMEM_REGION_START + DMEM_DSIZE)
#define IMEM_REGION_START 0x4001000
#define IMEM_REGION_END (IMEM_REGION_START + IMEM_DSIZE)
#define PIF_ROM_REGION_START 0x1FC00000
#define PIF_ROM_REGION_END 0x1FC007BF
#define PIF_RAM_REGION_START 0x1FC007C0
#define PIF_RAM_REGION_END 0x1FC007FF
#define CART_REGION_START_1_1 0x06000000
#define CART_REGION_START_1_2 0x10000000
#define CART_REGION_START_2_1 0x05000000
#define CART_REGION_START_2_2 0x08000000
#define CART_REGION_END_1_1 0x07FFFFFF
#define CART_REGION_END_1_2 0x1FBFFFFF
#define CART_REGION_END_2_1 0x05FFFFFF
#define CART_REGION_END_2_2 0x0FFFFFFF
#define RSP_MEM_REGION_END 0x0403FFFF
#define AI_REGION_START 0x04500000
#define AI_REGION_END 0x045FFFFF
#define MMIO_REGION_START_1 0x04040000
#define MMIO_REGION_START_2 0x04300000
#define MMIO_REGION_END_1 0x041FFFFF
#define MMIO_REGION_END_2 0x048FFFFF
#define UNUSED_START_1 0x00800000
#define UNUSED_END_1 0x03EFFFFF
#define UNUSED_START_2 0x04200000
#define UNUSED_END_2 0x042FFFFF
#define UNUSED_START_3 0x04900000
#define UNUSED_END_3 0x04FFFFFF
#define UNUSED_START_4 0x1FC00800
#define UNUSED_END_4 0xFFFFFFFF
#define RDRAM_REGION RDRAM_REGION_START ... RDRAM_REGION_END
#define RSP_MEM_REGION DMEM_REGION_START ... RSP_MEM_REGION_END
#define MMIO_REGION MMIO_REGION_START_1 ... MMIO_REGION_END_1 : case MMIO_REGION_START_2 ... MMIO_REGION_END_2
#define SP_REGION 0x04040000 ... 0x040FFFFF
#define DP_CMD_REGION 0x04100000 ... 0x041FFFFF
#define RSP_REGION 0x04040000 ... 0x040FFFFF
#define RDP_REGION 0x04100000 ... 0x041FFFFF
#define MI_REGION 0x04300000 ... 0x043FFFFF
#define MI_REGION 0x04300000 ... 0x043FFFFF
#define VI_REGION 0x04400000 ... 0x044FFFFF
#define AI_REGION 0x04500000 ... 0x045FFFFF
#define PI_REGION 0x04600000 ... 0x046FFFFF
#define RI_REGION 0x04700000 ... 0x047FFFFF
#define SI_REGION 0x04800000 ... 0x048FFFFF
#define REGION_CART CART_REGION_START_2_1 ... CART_REGION_END_1_2
#define PIF_ROM_REGION PIF_ROM_REGION_START ... PIF_ROM_REGION_END
#define PIF_RAM_REGION PIF_RAM_REGION_START ... PIF_RAM_REGION_END
#define START_VREGION_KUSEG 0x00000000
#define START_VREGION_KSEG0 0x80000000
#define START_VREGION_KSEG1 0xA0000000
#define START_VREGION_KSSEG 0xC0000000
#define START_VREGION_KSEG3 0xE0000000
#define END_VREGION_KUSEG 0x7FFFFFFF
#define END_VREGION_KSEG0 0x9FFFFFFF
#define END_VREGION_KSEG1 0xBFFFFFFF
#define END_VREGION_KSSEG 0xDFFFFFFF
#define END_VREGION_KSEG3 0xFFFFFFFF
#define VREGION_KUSEG START_VREGION_KUSEG ... END_VREGION_KUSEG
#define VREGION_KSEG0 START_VREGION_KSEG0 ... END_VREGION_KSEG0
#define VREGION_KSEG1 START_VREGION_KSEG1 ... END_VREGION_KSEG1
#define VREGION_KSSEG START_VREGION_KSSEG ... END_VREGION_KSSEG
#define VREGION_KSEG3 START_VREGION_KSEG3 ... END_VREGION_KSEG3
#define DIRECT_MAP_MASK 0x1FFFFFFF
#define VREGION_XKUSEG 0x0000000000000000 ... 0x000000FFFFFFFFFF
#define VREGION_XBAD1 0x0000010000000000 ... 0x3FFFFFFFFFFFFFFF
#define VREGION_XKSSEG 0x4000000000000000 ... 0x400000FFFFFFFFFF
#define VREGION_XBAD2 0x4000010000000000 ... 0x7FFFFFFFFFFFFFFF
#define VREGION_XKPHYS 0x8000000000000000 ... 0xBFFFFFFFFFFFFFFF
#define VREGION_XKSEG 0xC000000000000000 ... 0xC00000FF7FFFFFFF
#define VREGION_XBAD3 0xC00000FF80000000 ... 0xFFFFFFFF7FFFFFFF
#define VREGION_CKSEG0 0xFFFFFFFF80000000 ... 0xFFFFFFFF9FFFFFFF
#define VREGION_CKSEG1 0xFFFFFFFFA0000000 ... 0xFFFFFFFFBFFFFFFF
#define VREGION_CKSSEG 0xFFFFFFFFC0000000 ... 0xFFFFFFFFDFFFFFFF
#define VREGION_CKSEG3 0xFFFFFFFFE0000000 ... 0xFFFFFFFFFFFFFFFF
#define SREGION_PI_UNKNOWN 0x00000000
#define SREGION_PI_64DD_REG 0x05000000
#define SREGION_PI_64DD_ROM 0x06000000
#define SREGION_PI_SRAM 0x08000000
#define SREGION_PI_ROM 0x10000000
#define EREGION_PI_UNKNOWN 0x04FFFFFF
#define EREGION_PI_64DD_REG 0x05FFFFFF
#define EREGION_PI_64DD_ROM 0x07FFFFFF
#define EREGION_PI_SRAM 0x0FFFFFFF
#define EREGION_PI_ROM 0xFFFFFFFF
#define REGION_PI_UNKNOWN SREGION_PI_UNKNOWN ... EREGION_PI_UNKNOWN
#define REGION_PI_64DD_REG SREGION_PI_64DD_REG ... EREGION_PI_64DD_REG
#define REGION_PI_64DD_ROM SREGION_PI_64DD_ROM ... EREGION_PI_64DD_ROM
#define REGION_PI_SRAM SREGION_PI_SRAM ... EREGION_PI_SRAM
#define REGION_PI_ROM SREGION_PI_ROM ... EREGION_PI_ROM
#define CART_ISVIEWER_FLUSH 0x13FF0014
#define SREGION_CART_ISVIEWER_BUFFER 0x13FF0020
#define EREGION_CART_ISVIEWER_BUFFER 0x13FFFFFF
#define CART_ISVIEWER_SIZE (EREGION_CART_ISVIEWER_BUFFER - SREGION_CART_ISVIEWER_BUFFER)
#define REGION_CART_ISVIEWER_BUFFER SREGION_CART_ISVIEWER_BUFFER ... EREGION_CART_ISVIEWER_BUFFER
constexpr u64 operator""_kb(unsigned long long int x) { return 1024ULL * x; }
constexpr u64 operator""_mb(unsigned long long int x) { return 1024_kb * x; }
constexpr u64 operator""_gb(unsigned long long int x) { return 1024_mb * x; }
#define ADDRESS_RANGE_SIZE 0x80000000ull
#define PAGE_SIZE 4_kb
#define PAGE_COUNT ((ADDRESS_RANGE_SIZE) / (PAGE_SIZE))
+6
View File
@@ -0,0 +1,6 @@
#include <Netplay.hpp>
#include <PIF.hpp>
#include <array>
#include <log.hpp>
namespace Netplay {}
+4
View File
@@ -0,0 +1,4 @@
#pragma once
#include <PIF.hpp>
namespace Netplay {}
+48
View File
@@ -0,0 +1,48 @@
#pragma once
#include <MemoryHelpers.hpp>
#include <log.hpp>
namespace Util {
#define Z64 0x80371200
#define N64 0x00123780
#define V64 0x37800012
template <bool toBE = false>
FORCE_INLINE void SwapN64Rom(std::vector<u8> &rom, u32 endianness) {
u8 altByteShift = 0;
if (endianness >> 24 != 0x80) {
if ((endianness & 0xFF) != 0x80) {
if ((endianness >> 16 & 0xff) != 0x80) {
Error::GetInstance().Throw({Error::Severity::UNRECOVERABLE}, {Error::Type::ROM_LOAD_ERROR}, {}, {}, "Unrecognized rom endianness");
return;
} else {
altByteShift = 12;
}
} else {
altByteShift = 24;
}
} else {
altByteShift = 0;
}
endianness &= ~(0xFF << altByteShift);
switch (endianness) {
case V64:
SwapBuffer<u16>(rom);
if constexpr (!toBE)
SwapBuffer<u32>(rom);
break;
case N64:
if constexpr (toBE)
SwapBuffer<u32>(rom);
break;
case Z64:
if constexpr (!toBE)
SwapBuffer<u32>(rom);
break;
default:
Error::GetInstance().Throw({Error::Severity::UNRECOVERABLE}, {Error::Type::ROM_LOAD_ERROR}, {}, {}, "Unrecognized rom format! Make sure this is a valid Nintendo 64 ROM dump!");
}
}
} // namespace Util
+51
View File
@@ -0,0 +1,51 @@
#include <Scheduler.hpp>
#include <Core.hpp>
void Scheduler::EnqueueRelative(const u64 t, const EventType type) { EnqueueAbsolute(t + ticks, type); }
void Scheduler::EnqueueAbsolute(const u64 t, const EventType type) { events.push({t, type}); }
u64 Scheduler::Remove(const EventType eventType) const {
for (auto &[time, type] : events) {
if (type == eventType) {
const u64 ret = time - ticks;
type = NONE;
time = ticks;
return ret;
}
}
return 0;
}
void Scheduler::Tick(const u64 t) {
n64::Mem& mem = n64::Core::GetMem();
ticks += t;
n64::MI &mi = mem.mmio.mi;
n64::SI &si = mem.mmio.si;
n64::PI &pi = mem.mmio.pi;
while (ticks >= events.top().time) {
switch (const auto type = events.top().type) {
case SI_DMA:
si.DMA();
break;
case PI_DMA_COMPLETE:
mi.InterruptRaise(n64::MI::Interrupt::PI);
pi.dmaBusy = false;
break;
case PI_BUS_WRITE_COMPLETE:
pi.ioBusy = false;
break;
case NONE:
break;
case IMPOSSIBLE:
Util::Error::GetInstance().Throw({Util::Error::Severity::UNRECOVERABLE}, {Util::Error::Type::ROM_LOAD_ERROR}, {}, {}, "Unrecognized rom endianness");
return;
default:
Util::Error::GetInstance().Throw({Util::Error::Severity::UNRECOVERABLE}, {Util::Error::Type::ROM_LOAD_ERROR}, {}, {}, "Unknown scheduler event type {}", static_cast<int>(type));
return;
}
events.pop();
}
}
+46
View File
@@ -0,0 +1,46 @@
#pragma once
#include <functional>
#include <log.hpp>
#include <queue>
enum EventType { NONE, PI_BUS_WRITE_COMPLETE, PI_DMA_COMPLETE, SI_DMA, IMPOSSIBLE };
struct Event {
u64 time;
EventType type;
friend bool operator<(const Event &rhs, const Event &lhs) { return rhs.time < lhs.time; }
friend bool operator>(const Event &rhs, const Event &lhs) { return rhs.time > lhs.time; }
friend bool operator>=(const Event &rhs, const Event &lhs) { return rhs.time >= lhs.time; }
};
struct IterableEvents {
std::priority_queue<Event, std::vector<Event>, std::greater<>> events;
explicit IterableEvents() = default;
[[nodiscard]] auto top() const { return events.top(); }
auto pop() { events.pop(); }
[[nodiscard]] auto begin() const { return const_cast<Event *>(&events.top()); }
[[nodiscard]] auto end() const { return begin() + events.size(); }
auto push(const Event e) { events.push(e); }
};
struct Scheduler {
Scheduler() { EnqueueAbsolute(std::numeric_limits<u64>::max(), IMPOSSIBLE); }
static Scheduler &GetInstance() {
static Scheduler instance;
return instance;
}
void EnqueueRelative(u64, EventType);
void EnqueueAbsolute(u64, EventType);
[[nodiscard]] u64 Remove(EventType) const;
void Tick(u64 t);
u8 index = 0;
u64 ticks = 0;
IterableEvents events{};
};
+12
View File
@@ -0,0 +1,12 @@
#pragma once
#include <Mem.hpp>
#include <Registers.hpp>
#include <Disassembler.hpp>
namespace n64 {
struct BaseCPU {
virtual ~BaseCPU() = default;
virtual u32 Step() = 0;
virtual void Reset() = 0;
};
} // namespace n64
+17
View File
@@ -0,0 +1,17 @@
file(GLOB SOURCES *.cpp)
file(GLOB HEADERS *.hpp)
add_subdirectory(interpreter)
if(NOT ARM64)
add_subdirectory(jit)
endif()
add_subdirectory(mem)
add_subdirectory(mmio)
add_subdirectory(registers)
add_subdirectory(rsp)
add_library(core ${SOURCES} ${HEADERS})
target_link_libraries(core PRIVATE interpreter mem mmio unarr registers rsp)
if(NOT ARM64)
target_link_libraries(core PRIVATE jit)
endif()
+77
View File
@@ -0,0 +1,77 @@
#include <Disassembler.hpp>
#include <Core.hpp>
#include <optional>
Disassembler::DisassemblyResult Disassembler::DisassembleSimple(const u32 address, const u32 instruction) const {
cs_insn *insn;
const auto bytes = Util::IntegralToBuffer(std::byteswap(instruction));
const auto count = cs_disasm(handle, bytes.data(), bytes.size(), address, 0, &insn);
if (count <= 0)
return {};
DisassemblyResult result{true, std::format("0x{:016X}:\t{}\t{}", insn[0].address, insn[0].mnemonic, insn[0].op_str)};
cs_free(insn, count);
return result;
}
[[nodiscard]] Disassembler::DisassemblyResult Disassembler::Disassemble(const u32 address) const {
u32 paddr;
if(!n64::Core::GetRegs().cop0.MapVAddr(n64::Cop0::TLBAccessType::LOAD, address, paddr))
return DisassemblyResult{false, ""};
u32 instruction = n64::Core::GetMem().Read<u32>(paddr);
return details ? DisassembleDetailed(address, instruction) : DisassembleSimple(address, instruction);
}
Disassembler::DisassemblyResult Disassembler::DisassembleDetailed(const u32 address, const u32 instruction) const {
n64::Core& core = n64::Core::GetInstance();
cs_insn *insn;
const auto bytes = Util::IntegralToBuffer(std::byteswap(instruction));
const auto count = cs_disasm(handle, bytes.data(), bytes.size(), address, 0, &insn);
if (count <= 0)
return {};
DisassemblyResult result{true};
result.address = insn[0].address;
result.mnemonic = insn[0].mnemonic;
result.full += std::format("0x{:016X}", result.address) + ":\t";
result.full += result.mnemonic + "\t";
const cs_detail *details = insn[0].detail;
auto formatOperand = [&](const cs_mips_op &operand) {
switch (operand.type) {
case MIPS_OP_IMM:
return DisassemblyResult::Operand{
0xffcbf1ae,
std::format("#{:X}", operand.is_unsigned ? operand.uimm : operand.imm)
};
case MIPS_OP_MEM:
return DisassemblyResult::Operand{
0xffaef1c3,
std::format("{}(0x{:X})", cs_reg_name(handle, operand.mem.base), operand.mem.disp)
};
case MIPS_OP_REG:
return DisassemblyResult::Operand{
0xffaef1eb,
std::format("{}", cs_reg_name(handle, operand.reg))
};
default:
return DisassemblyResult::Operand { 0xff808080, "" };
}
};
for (u8 i = 0; i < details->mips.op_count && i < 3; i++) {
result.ops[i] = formatOperand(details->mips.operands[i]);
result.full += result.ops[i].str + "\t";
}
cs_free(insn, count);
return result;
}
+46
View File
@@ -0,0 +1,46 @@
#pragma once
#include <capstone/capstone.h>
#include <utils/log.hpp>
#include <utils/MemoryHelpers.hpp>
#include <array>
struct Disassembler {
struct DisassemblyResult {
bool success = false;
std::string full;
u64 address;
std::string mnemonic;
struct Operand {
u32 color;
std::string str;
};
std::array<Operand, 3> ops{};
};
~Disassembler() { cs_close(&handle); }
static Disassembler &GetInstance(bool rsp = false) {
static Disassembler ret(rsp);
return ret;
}
[[nodiscard]] DisassemblyResult Disassemble(const u32 address) const;
[[nodiscard]] DisassemblyResult DisassembleDetailed(u32 address, u32 instruction) const;
[[nodiscard]] DisassemblyResult DisassembleSimple(u32 address, u32 instruction) const;
private:
explicit Disassembler(const bool rsp) : rsp(rsp) {
if (cs_open(CS_ARCH_MIPS, static_cast<cs_mode>((rsp ? CS_MODE_32 : CS_MODE_64) | CS_MODE_BIG_ENDIAN), &handle) !=
CS_ERR_OK) {
panic("Could not initialize {} disassembler!", rsp ? "RSP" : "CPU");
}
if (cs_option(handle, CS_OPT_DETAIL, CS_OPT_ON) != CS_ERR_OK) {
Util::Error::GetInstance().Throw({Util::Error::Severity::WARN}, {Util::Error::Type::CAPSTONE_ERROR}, {}, {}, "Could not enable disassembler's details!");
details = false;
}
}
bool rsp = false;
bool details = true;
csh handle{};
};
+58
View File
@@ -0,0 +1,58 @@
#include <Core.hpp>
namespace n64 {
Interpreter::Interpreter(Mem& mem, Registers& regs) : regs(regs), mem(mem) {}
bool Interpreter::ShouldServiceInterrupt() const {
const bool interrupts_pending = (regs.cop0.status.im & regs.cop0.cause.interruptPending) != 0;
const bool interrupts_enabled = regs.cop0.status.ie == 1;
const bool currently_handling_exception = regs.cop0.status.exl == 1;
const bool currently_handling_error = regs.cop0.status.erl == 1;
return interrupts_pending && interrupts_enabled && !currently_handling_exception && !currently_handling_error;
}
void Interpreter::CheckCompareInterrupt() const {
regs.cop0.count++;
regs.cop0.count &= 0x1FFFFFFFF;
if (regs.cop0.count == static_cast<u64>(regs.cop0.compare) << 1) {
regs.cop0.cause.ip7 = 1;
mem.mmio.mi.UpdateInterrupt();
}
}
u32 Interpreter::Step() {
CheckCompareInterrupt();
regs.prevDelaySlot = regs.delaySlot;
regs.delaySlot = false;
if (check_address_error(0b11, u64(regs.pc))) [[unlikely]] {
regs.cop0.HandleTLBException(regs.pc);
regs.cop0.FireException(ExceptionCode::AddressErrorLoad, 0, regs.pc);
return 1;
}
u32 paddr = 0;
if (!regs.cop0.MapVAddr(Cop0::LOAD, regs.pc, paddr)) {
regs.cop0.HandleTLBException(regs.pc);
regs.cop0.FireException(Cop0::GetTLBExceptionCode(regs.cop0.tlbError, Cop0::LOAD), 0, regs.pc);
return 1;
}
const u32 instruction = mem.Read<u32>(paddr);
if (ShouldServiceInterrupt()) {
regs.cop0.FireException(ExceptionCode::Interrupt, 0, regs.pc);
return 1;
}
regs.oldPC = regs.pc;
regs.pc = regs.nextPC;
regs.nextPC += 4;
Exec(instruction);
return 1;
}
} // namespace n64
+125
View File
@@ -0,0 +1,125 @@
#pragma once
#include <BaseCPU.hpp>
#include <Mem.hpp>
#include <vector>
namespace n64 {
struct Core;
struct Interpreter final : BaseCPU {
explicit Interpreter(Mem&, Registers&);
~Interpreter() override = default;
u32 Step() override;
void Reset() override {
cop2Latch = {};
}
private:
Registers& regs;
Mem& mem;
u64 cop2Latch{};
friend struct Cop1;
#define check_address_error(mask, vaddr) \
(((!regs.cop0.is64BitAddressing) && (s32)(vaddr) != (vaddr)) || (((vaddr) & (mask)) != 0))
[[nodiscard]] bool ShouldServiceInterrupt() const;
void CheckCompareInterrupt() const;
void cop2Decode(Instruction);
void special(Instruction);
void regimm(Instruction);
void Exec(Instruction);
void add(Instruction);
void addu(Instruction);
void addi(Instruction);
void addiu(Instruction);
void andi(Instruction);
void and_(Instruction);
void branch(bool, s64);
void branch_likely(bool, s64);
void b(Instruction, bool);
void blink(Instruction, bool);
void bl(Instruction, bool);
void bllink(Instruction, bool);
void dadd(Instruction);
void daddu(Instruction);
void daddi(Instruction);
void daddiu(Instruction);
void ddiv(Instruction);
void ddivu(Instruction);
void div(Instruction);
void divu(Instruction);
void dmult(Instruction);
void dmultu(Instruction);
void dsll(Instruction);
void dsllv(Instruction);
void dsll32(Instruction);
void dsra(Instruction);
void dsrav(Instruction);
void dsra32(Instruction);
void dsrl(Instruction);
void dsrlv(Instruction);
void dsrl32(Instruction);
void dsub(Instruction);
void dsubu(Instruction);
void j(Instruction);
void jr(Instruction);
void jal(Instruction);
void jalr(Instruction);
void lui(Instruction);
void lbu(Instruction);
void lb(Instruction);
void ld(Instruction);
void ldl(Instruction);
void ldr(Instruction);
void lh(Instruction);
void lhu(Instruction);
void ll(Instruction);
void lld(Instruction);
void lw(Instruction);
void lwl(Instruction);
void lwu(Instruction);
void lwr(Instruction);
void mfhi(Instruction);
void mflo(Instruction);
void mult(Instruction);
void multu(Instruction);
void mthi(Instruction);
void mtlo(Instruction);
void nor(Instruction);
void sb(Instruction);
void sc(Instruction);
void scd(Instruction);
void sd(Instruction);
void sdl(Instruction);
void sdr(Instruction);
void sh(Instruction);
void sw(Instruction);
void swl(Instruction);
void swr(Instruction);
void slti(Instruction);
void sltiu(Instruction);
void slt(Instruction);
void sltu(Instruction);
void sll(Instruction);
void sllv(Instruction);
void sub(Instruction);
void subu(Instruction);
void sra(Instruction);
void srav(Instruction);
void srl(Instruction);
void srlv(Instruction);
void trap(bool) const;
void or_(Instruction);
void ori(Instruction);
void xor_(Instruction);
void xori(Instruction);
void mtc2(Instruction);
void mfc2(Instruction);
void dmtc2(Instruction);
void dmfc2(Instruction);
void ctc2(Instruction);
void cfc2(Instruction);
};
} // namespace n64
+246
View File
@@ -0,0 +1,246 @@
#include <Core.hpp>
#include <jit/helpers.hpp>
namespace n64 {
#ifndef __aarch64__
JIT::JIT(Mem& mem, Registers& regs) : regs(regs), mem(mem) {
regs.SetJIT(this);
mem.SetJIT(this);
blockCache.resize(kUpperSize);
if (cs_open(CS_ARCH_MIPS, static_cast<cs_mode>(CS_MODE_MIPS64 | CS_MODE_BIG_ENDIAN), &disassemblerMips) !=
CS_ERR_OK) {
panic("Failed to initialize MIPS disassembler");
}
if (cs_open(CS_ARCH_X86, static_cast<cs_mode>(CS_MODE_64 | CS_MODE_LITTLE_ENDIAN), &disassemblerX86) != CS_ERR_OK) {
panic("Failed to initialize x86 disassembler");
}
}
bool JIT::ShouldServiceInterrupt() const {
const bool interrupts_pending = (regs.cop0.status.im & regs.cop0.cause.interruptPending) != 0;
const bool interrupts_enabled = regs.cop0.status.ie == 1;
const bool currently_handling_exception = regs.cop0.status.exl == 1;
const bool currently_handling_error = regs.cop0.status.erl == 1;
return interrupts_pending && interrupts_enabled && !currently_handling_exception && !currently_handling_error;
}
void JIT::CheckCompareInterrupt() const {
regs.cop0.count++;
regs.cop0.count &= 0x1FFFFFFFF;
if (regs.cop0.count == static_cast<u64>(regs.cop0.compare) << 1) {
regs.cop0.cause.ip7 = 1;
Core::GetMem().mmio.mi.UpdateInterrupt();
}
}
void JIT::InvalidateBlock(const u32 paddr) {
if (const u32 index = paddr >> kUpperShift; !blockCache[index].empty())
blockCache[index] = {};
}
std::optional<u32> JIT::FetchInstruction(s64 vaddr) {
u32 paddr = 0;
if (check_address_error(0b11, vaddr)) [[unlikely]] {
/*regs.cop0.HandleTLBException(blockPC);
regs.cop0.FireException(ExceptionCode::AddressErrorLoad, 0, blockPC);
return 1;*/
Util::Error::GetInstance().Throw(
{Util::Error::Severity::NON_FATAL}, {Util::Error::Type::UNHANDLED_EXCEPTION}, blockPC, {},
"[JIT]: Unhandled exception ADL due to unaligned PC virtual value!");
return std::nullopt;
}
if (!regs.cop0.MapVAddr(Cop0::LOAD, vaddr, paddr)) {
/*regs.cop0.HandleTLBException(blockPC);
regs.cop0.FireException(Cop0::GetTLBExceptionCode(regs.cop0.tlbError, Cop0::LOAD), 0, blockPC);
return 1;*/
Util::Error::GetInstance().Throw(
{Util::Error::Severity::NON_FATAL}, {Util::Error::Type::UNHANDLED_EXCEPTION}, blockPC, {},
"[JIT]: Unhandled exception TLB exception {} when retrieving PC physical address!",
static_cast<int>(Cop0::GetTLBExceptionCode(regs.cop0.tlbError, Cop0::LOAD)));
return std::nullopt;
}
const u32 instr = Core::GetMem().Read<u32>(paddr);
info("{}", Disassembler::GetInstance().DisassembleSimple(paddr, instr).full);
return instr;
}
void JIT::SetPC32(const s32 val) {
code.mov(code.SCR1, code.qword[code.rbp + PC_OFFSET]);
code.mov(code.qword[code.rbp + OLD_PC_OFFSET], code.SCR1);
code.mov(code.SCR1.cvt32(), val);
code.movsxd(code.SCR1.cvt64(), code.SCR1.cvt32());
code.mov(code.qword[code.rbp + PC_OFFSET], code.SCR1);
code.mov(code.SCR1.cvt32(), val + 4);
code.movsxd(code.SCR1.cvt64(), code.SCR1.cvt32());
code.mov(code.qword[code.rbp + NEXT_PC_OFFSET], code.SCR1);
}
void JIT::SetPC64(const s64 val) {
code.mov(code.SCR1, code.qword[code.rbp + PC_OFFSET]);
code.mov(code.qword[code.rbp + OLD_PC_OFFSET], code.SCR1);
code.mov(code.SCR1, val);
code.mov(code.qword[code.rbp + PC_OFFSET], code.SCR1);
code.mov(code.SCR1, val + 4);
code.mov(code.qword[code.rbp + NEXT_PC_OFFSET], code.SCR1);
}
void JIT::SetPC32(const Xbyak::Reg32& val) {
code.mov(code.SCR1, code.qword[code.rbp + PC_OFFSET]);
code.mov(code.qword[code.rbp + OLD_PC_OFFSET], code.SCR1);
code.movsxd(val.cvt64(), val);
code.mov(code.qword[code.rbp + PC_OFFSET], val);
code.add(val, 4);
code.mov(code.qword[code.rbp + NEXT_PC_OFFSET], val);
}
void JIT::SetPC64(const Xbyak::Reg64& val) {
code.mov(code.SCR1, code.qword[code.rbp + PC_OFFSET]);
code.mov(code.qword[code.rbp + OLD_PC_OFFSET], code.SCR1);
code.mov(code.qword[code.rbp + PC_OFFSET], val);
code.add(val, 4);
code.mov(code.qword[code.rbp + NEXT_PC_OFFSET], val);
}
u32 JIT::Step() {
blockOldPC = regs.oldPC;
blockPC = regs.pc;
blockNextPC = regs.nextPC;
u32 paddr = 0;
if (!regs.cop0.MapVAddr(Cop0::LOAD, blockPC, paddr)) {
/*regs.cop0.HandleTLBException(blockPC);
regs.cop0.FireException(Cop0::GetTLBExceptionCode(regs.cop0.tlbError, Cop0::LOAD), 0, blockPC);
return 1;*/
Util::Error::GetInstance().Throw({Util::Error::Severity::NON_FATAL}, {Util::Error::Type::UNHANDLED_EXCEPTION},
blockPC, {},
"[JIT]: Unhandled exception TLB exception {} when retrieving PC physical address!",
static_cast<int>(Cop0::GetTLBExceptionCode(regs.cop0.tlbError, Cop0::LOAD)));
return 0;
}
const u32 upperIndex = paddr >> kUpperShift;
const u32 lowerIndex = paddr & kLowerMask;
if (!blockCache[upperIndex].empty()) {
if (blockCache[upperIndex][lowerIndex]) {
// trace("[JIT]: Executing already compiled block @ 0x{:016X}", blockPC);
return blockCache[upperIndex][lowerIndex]();
}
} else {
blockCache[upperIndex].resize(kLowerSize);
}
info("[JIT]: Compiling block @ 0x{:016X}:", static_cast<u64>(blockPC));
const auto blockInfo = code.getCurr();
const auto block = code.getCurr<BlockFn>();
blockCache[upperIndex][lowerIndex] = block;
code.setProtectModeRW();
u32 instructionsInBlock = 0;
bool instrEndsBlock = false;
code.sub(code.rsp, 8);
code.push(code.rbp);
code.mov(code.rbp, reinterpret_cast<uintptr_t>(this)); // Load context pointer
cs_insn *insn;
info("\tMIPS code (guest PC = 0x{:016X}):", static_cast<u64>(blockPC));
emitMemberFunctionCall(&JIT::AdvanceDelaySlot, this);
while (true) {
paddr = 0;
auto instruction = FetchInstruction(blockPC);
if(!instruction)
return 0;
instructionsInBlock++;
blockOldPC = blockPC;
blockPC = blockNextPC;
blockNextPC += 4;
if(InstrEndsBlock(instruction.value())) {
const auto delay_instruction = FetchInstruction(blockPC); // get instruction in delay slot
if(!delay_instruction)
return 0;
if(InstrEndsBlock(delay_instruction.value())) {
Util::Error::GetInstance().Throw(
{Util::Error::Severity::NON_FATAL}, {Util::Error::Type::JIT_BRANCH_INSIDE_DELAY_SLOT},
blockPC, {}, "[JIT]: Unhandled case of branch from delay slot!");
return 0;
}
instructionsInBlock++;
blockOldPC = blockPC;
blockPC = blockNextPC;
blockNextPC += 4;
Emit(delay_instruction.value());
Emit(instruction.value());
if(!branch_taken) {
Xbyak::Label runtime_branch_taken;
code.mov(code.SCR1, code.byte[code.rbp + BRANCH_TAKEN_OFFSET]);
code.cmp(code.SCR1, 0);
code.jne(runtime_branch_taken);
code.mov(code.SCR1, blockOldPC);
code.mov(code.qword[code.rbp + OLD_PC_OFFSET], code.SCR1);
code.mov(code.SCR1, blockPC);
code.mov(code.qword[code.rbp + PC_OFFSET], code.SCR1);
code.mov(code.SCR1, blockNextPC);
code.mov(code.qword[code.rbp + NEXT_PC_OFFSET], code.SCR1);
code.L(runtime_branch_taken);
}
if(branch_taken) branch_taken = false;
emitMemberFunctionCall(&JIT::AdvanceDelaySlot, this);
break;
}
Emit(instruction.value());
emitMemberFunctionCall(&JIT::AdvanceDelaySlot, this);
}
code.mov(code.rax, instructionsInBlock);
code.pop(code.rbp);
code.add(code.rsp, 8);
code.ret();
code.setProtectModeRE();
static size_t blockInfoSize = 0;
blockInfoSize = code.getSize() - blockInfoSize;
info("\tX86 code (block address = 0x{:016X}):", reinterpret_cast<uintptr_t>(block));
const auto count = cs_disasm(disassemblerX86, blockInfo, blockInfoSize, reinterpret_cast<uintptr_t>(block), 0, &insn);
if (count > 0) {
for (size_t j = 0; j < count; j++) {
info("\t\t0x{:016X}:\t{}\t\t{}", insn[j].address, insn[j].mnemonic, insn[j].op_str);
}
cs_free(insn, count);
}
// panic("");
return block();
}
void JIT::DumpBlockCacheToDisk() const {
Util::WriteFileBinary(code.getCode<u8*>(), code.getSize(), "jit.dump");
}
#endif
} // namespace n64
+286
View File
@@ -0,0 +1,286 @@
#pragma once
#include <BaseCPU.hpp>
#include <Mem.hpp>
#include <vector>
#include <xbyak.h>
#include <jit/helpers.hpp>
#include <capstone/capstone.h>
namespace n64 {
struct Core;
static constexpr u64 kAddressSpaceSize = 0x8000'0000;
static constexpr u8 kUpperShift = 8;
static constexpr u8 kLowerMask = 0xff;
static constexpr u32 kUpperSize = kAddressSpaceSize >> kUpperShift; // 0x800000
static constexpr u32 kLowerSize = 0x100; // 0x80
static constexpr u32 kCodeCacheSize = 32_mb;
static constexpr u32 kCodeCacheAllocSize = kCodeCacheSize + 4_kb;
#define OLD_PC_OFFSET (reinterpret_cast<uintptr_t>(&regs.oldPC) - reinterpret_cast<uintptr_t>(this))
#define PC_OFFSET (reinterpret_cast<uintptr_t>(&regs.pc) - reinterpret_cast<uintptr_t>(this))
#define NEXT_PC_OFFSET (reinterpret_cast<uintptr_t>(&regs.nextPC) - reinterpret_cast<uintptr_t>(this))
#define GPR_OFFSET(x) (reinterpret_cast<uintptr_t>(&regs.gpr[(x)]) - reinterpret_cast<uintptr_t>(this))
#define BRANCH_TAKEN_OFFSET (reinterpret_cast<uintptr_t>(&branch_taken) - reinterpret_cast<uintptr_t>(this))
#define HI_OFFSET (reinterpret_cast<uintptr_t>(&regs.hi) - reinterpret_cast<uintptr_t>(this))
#define LO_OFFSET (reinterpret_cast<uintptr_t>(&regs.lo) - reinterpret_cast<uintptr_t>(this))
#ifdef __aarch64__
struct JIT : BaseCPU {};
#else
struct JIT final : BaseCPU {
explicit JIT(Mem&, Registers&);
~JIT() override = default;
u32 Step() override;
void Reset() override {
code.reset();
blockCache = {};
blockCache.resize(kUpperSize);
}
void DumpBlockCacheToDisk() const;
void AdvanceDelaySlot() {
regs.prevDelaySlot = regs.delaySlot;
regs.delaySlot = false;
}
void InvalidateBlock(u32);
private:
friend struct Cop1;
friend struct Registers;
using BlockFn = int (*)();
bool branch_taken;
Registers& regs;
Mem& mem;
u64 cop2Latch{};
s64 blockOldPC = 0, blockPC = 0, blockNextPC = 0;
Xbyak::CodeGenerator code{kCodeCacheAllocSize};
csh disassemblerMips{}, disassemblerX86{};
std::vector<std::vector<BlockFn>> blockCache;
template <typename T>
Xbyak::Address GPR(const size_t index) {
if constexpr (sizeof(T) == 1) {
return code.byte[code.rbp + GPR_OFFSET(index)];
} else if constexpr (sizeof(T) == 2) {
return code.word[code.rbp + GPR_OFFSET(index)];
} else if constexpr (sizeof(T) == 4) {
return code.dword[code.rbp + GPR_OFFSET(index)];
} else if constexpr (sizeof(T) == 8) {
return code.qword[code.rbp + GPR_OFFSET(index)];
}
Util::Error::GetInstance().Throw(
{Util::Error::Severity::UNRECOVERABLE}, {Util::Error::Type::JIT_INVALID_X86_REG_ADDRESSING},
blockPC, {}, "[JIT]: Invalid register addressing mode {}!", sizeof(T));
return Xbyak::Address{0};
}
// Thanks to https://github.com/grumpycoders/pcsx-redux
// Load a pointer to the JIT object in "reg"
template <typename T>
void emitMemberFunctionCall(T func, void *thisObject) {
uintptr_t functionPtr;
auto thisPtr = reinterpret_cast<uintptr_t>(thisObject);
#if defined(WIN32) || defined(_WIN32) || defined(__WIN32) && !defined(__CYGWIN__)
static_assert(sizeof(T) == 8, "[x64 JIT] Invalid size for member function pointer");
std::memcpy(&functionPtr, &func, sizeof(T));
#else
static_assert(sizeof(T) == 16, "[x64 JIT] Invalid size for member function pointer");
uintptr_t arr[2];
std::memcpy(arr, &func, sizeof(T));
// First 8 bytes correspond to the actual pointer to the function
functionPtr = reinterpret_cast<uintptr_t>(reinterpret_cast<void *>(arr[0]));
// Next 8 bytes correspond to the "this" pointer adjustment
thisPtr += arr[1];
#endif
code.mov(code.ARG1, thisPtr);
code.mov(code.rax, functionPtr);
code.sub(code.rsp, 8);
code.call(code.rax);
code.add(code.rsp, 8);
}
void SetPC32(s32 val);
void SetPC64(s64 val);
void SetPC32(const Xbyak::Reg32& val);
void SetPC64(const Xbyak::Reg64& val);
void BranchNotTaken();
void BranchTaken(s64 offs);
void BranchTaken(const Xbyak::Reg64 &offs);
void BranchAbsTaken(s64 addr);
void BranchAbsTaken(const Xbyak::Reg64 &addr);
#define check_address_error(mask, vaddr) \
(((!regs.cop0.is64BitAddressing) && (s32)(vaddr) != (vaddr)) || (((vaddr) & (mask)) != 0))
[[nodiscard]] bool ShouldServiceInterrupt() const;
void CheckCompareInterrupt() const;
std::optional<u32> FetchInstruction(s64);
void Emit(Instruction);
void special(Instruction);
void regimm(Instruction);
void add(Instruction);
void addu(Instruction);
void addi(Instruction);
void addiu(Instruction);
void andi(Instruction);
void and_(Instruction);
void branch_constant(bool cond, s64 offset);
void branch_likely_constant(bool cond, s64 offset);
void branch_abs_constant(bool cond, s64 address);
void bltz(Instruction);
void bgez(Instruction);
void bltzl(Instruction);
void bgezl(Instruction);
void bltzal(Instruction);
void bgezal(Instruction);
void bltzall(Instruction);
void bgezall(Instruction);
void beq(Instruction);
void beql(Instruction);
void bne(Instruction);
void bnel(Instruction);
void blez(Instruction);
void blezl(Instruction);
void bgtz(Instruction);
void bgtzl(Instruction);
void bfc1(Instruction);
void blfc1(Instruction);
void bfc0(Instruction);
void blfc0(Instruction);
void dadd(Instruction);
void daddu(Instruction);
void daddi(Instruction);
void daddiu(Instruction);
void ddiv(Instruction);
void ddivu(Instruction);
void div(Instruction);
void divu(Instruction);
void dmult(Instruction);
void dmultu(Instruction);
void dsll(Instruction);
void dsllv(Instruction);
void dsll32(Instruction);
void dsra(Instruction);
void dsrav(Instruction);
void dsra32(Instruction);
void dsrl(Instruction);
void dsrlv(Instruction);
void dsrl32(Instruction);
void dsub(Instruction);
void dsubu(Instruction);
void j(Instruction);
void jr(Instruction);
void jal(Instruction);
void jalr(Instruction);
void lui(Instruction);
void lbu(Instruction);
void lb(Instruction);
void ld(Instruction);
void ldc1(Instruction);
void ldl(Instruction);
void ldr(Instruction);
void lh(Instruction);
void lhu(Instruction);
void ll(Instruction);
void lld(Instruction);
void lw(Instruction);
void lwc1(Instruction);
void lwl(Instruction);
void lwu(Instruction);
void lwr(Instruction);
void mfhi(Instruction);
void mflo(Instruction);
void mult(Instruction);
void multu(Instruction);
void mthi(Instruction);
void mtlo(Instruction);
void nor(Instruction);
void sb(const Instruction) {
Util::Error::GetInstance().Throw(
{Util::Error::Severity::NON_FATAL}, {Util::Error::Type::UNHANDLED_INSTRUCTION},
blockPC, {}, "[JIT]: Unhandled 'sb'!");
}
void sc(const Instruction) {
Util::Error::GetInstance().Throw(
{Util::Error::Severity::NON_FATAL}, {Util::Error::Type::UNHANDLED_INSTRUCTION},
blockPC, {}, "[JIT]: Unhandled 'sc'!");
}
void scd(const Instruction) {
Util::Error::GetInstance().Throw(
{Util::Error::Severity::NON_FATAL}, {Util::Error::Type::UNHANDLED_INSTRUCTION},
blockPC, {}, "[JIT]: Unhandled 'scd'!");
}
void sd(const Instruction) {
Util::Error::GetInstance().Throw(
{Util::Error::Severity::NON_FATAL}, {Util::Error::Type::UNHANDLED_INSTRUCTION},
blockPC, {}, "[JIT]: Unhandled 'sd'!");
}
void sdc1(const Instruction) {
Util::Error::GetInstance().Throw(
{Util::Error::Severity::NON_FATAL}, {Util::Error::Type::UNHANDLED_INSTRUCTION},
blockPC, {}, "[JIT]: Unhandled 'sdc1'!");
}
void sdl(const Instruction) {
Util::Error::GetInstance().Throw(
{Util::Error::Severity::NON_FATAL}, {Util::Error::Type::UNHANDLED_INSTRUCTION},
blockPC, {}, "[JIT]: Unhandled 'sdl'!");
}
void sdr(const Instruction) {
Util::Error::GetInstance().Throw(
{Util::Error::Severity::NON_FATAL}, {Util::Error::Type::UNHANDLED_INSTRUCTION},
blockPC, {}, "[JIT]: Unhandled 'sdr'!");
}
void sh(const Instruction) {
Util::Error::GetInstance().Throw(
{Util::Error::Severity::NON_FATAL}, {Util::Error::Type::UNHANDLED_INSTRUCTION},
blockPC, {}, "[JIT]: Unhandled 'sh'!");
}
void sw(Instruction);
void swl(const Instruction) {
Util::Error::GetInstance().Throw(
{Util::Error::Severity::NON_FATAL}, {Util::Error::Type::UNHANDLED_INSTRUCTION},
blockPC, {}, "[JIT]: Unhandled 'swl'!");
}
void swr(const Instruction) {
Util::Error::GetInstance().Throw(
{Util::Error::Severity::NON_FATAL}, {Util::Error::Type::UNHANDLED_INSTRUCTION},
blockPC, {}, "[JIT]: Unhandled 'swr'!");
}
void slti(Instruction);
void sltiu(Instruction);
void slt(Instruction);
void sltu(Instruction);
void sll(Instruction);
void sllv(Instruction);
void sub(Instruction);
void subu(Instruction);
void swc1(const Instruction) {
Util::Error::GetInstance().Throw(
{Util::Error::Severity::NON_FATAL}, {Util::Error::Type::JIT_BRANCH_INSIDE_DELAY_SLOT},
blockPC, {}, "[JIT]: Unhandled case of branch from delay slot!");
}
void sra(Instruction);
void srav(Instruction);
void srl(Instruction);
void srlv(Instruction);
void trap(bool) {
Util::Error::GetInstance().Throw(
{Util::Error::Severity::NON_FATAL}, {Util::Error::Type::JIT_BRANCH_INSIDE_DELAY_SLOT},
blockPC, {}, "[JIT]: Unhandled case of branch from delay slot!");
}
void or_(Instruction);
void ori(Instruction);
void xor_(Instruction);
void xori(Instruction);
};
#endif
} // namespace n64
+69
View File
@@ -0,0 +1,69 @@
#include <core/MMIO.hpp>
#include <core/Mem.hpp>
namespace n64 {
void MMIO::Reset() {
rsp.Reset();
rdp.Reset();
mi.Reset();
vi.Reset();
ai.Reset();
pi.Reset();
ri.Reset();
si.Reset();
}
u32 MMIO::Read(u32 addr) {
switch (addr) {
case RSP_REGION:
return rsp.Read(addr);
case RDP_REGION:
return rdp.Read(addr);
case MI_REGION:
return mi.Read(addr);
case VI_REGION:
return vi.Read(addr);
case AI_REGION:
return ai.Read(addr);
case PI_REGION:
return pi.Read(addr);
case RI_REGION:
return ri.Read(addr);
case SI_REGION:
return si.Read(addr);
default:
panic("Unhandled mmio read at addr {:08X}", addr);
}
}
void MMIO::Write(const u32 addr, const u32 val) {
switch (addr) {
case RSP_REGION:
rsp.Write(addr, val);
break;
case RDP_REGION:
rdp.Write(addr, val);
break;
case MI_REGION:
mi.Write(addr, val);
break;
case VI_REGION:
vi.Write(addr, val);
break;
case AI_REGION:
ai.Write(addr, val);
break;
case PI_REGION:
pi.Write(addr, val);
break;
case RI_REGION:
ri.Write(addr, val);
break;
case SI_REGION:
si.Write(addr, val);
break;
default:
panic("Unhandled mmio write at addr {:08X} with val {:08X}", addr, val);
}
}
} // namespace n64
+33
View File
@@ -0,0 +1,33 @@
#pragma once
#include <core/RDP.hpp>
#include <core/RSP.hpp>
#include <core/mmio/AI.hpp>
#include <core/mmio/MI.hpp>
#include <core/mmio/PI.hpp>
#include <core/mmio/RI.hpp>
#include <core/mmio/SI.hpp>
#include <core/mmio/VI.hpp>
class ParallelRDP;
namespace n64 {
struct Mem;
struct Registers;
struct MMIO {
MMIO() { Reset(); }
void Reset();
VI vi;
MI mi;
AI ai;
PI pi;
RI ri;
SI si;
RSP rsp;
RDP rdp;
u32 Read(u32);
void Write(u32, u32);
};
} // namespace n64
+555
View File
@@ -0,0 +1,555 @@
#include <File.hpp>
#include <Mem.hpp>
#include <backend/RomHelpers.hpp>
#include <cassert>
#include <Core.hpp>
#include <Options.hpp>
namespace n64 {
Mem::Mem() : flash(saveData) {
rom.cart.resize(CART_SIZE);
std::ranges::fill(rom.cart, 0);
}
void Mem::Reset() {
std::ranges::fill(isviewer, 0);
flash.Reset();
if (saveData.is_mapped()) {
std::error_code error;
saveData.sync(error);
if (error) {
Util::Error::GetInstance().Throw(
{Util::Error::Severity::NON_FATAL}, {Util::Error::Type::COULD_NOT_SYNC_SAVE_DATA},
{}, {}, "[Mem]: Could not sync save data!");
return;
}
saveData.unmap();
}
mmio.Reset();
}
void Mem::LoadSRAM(SaveType save_type, fs::path path) {
if (save_type == SAVE_SRAM_256k) {
std::error_code error;
std::string savePath = Options::GetInstance().GetValue<std::string>("general", "savePath");
if (!savePath.empty()) {
path = savePath / path.filename();
}
sramPath = path.replace_extension(".sram").string();
if (saveData.is_mapped()) {
saveData.sync(error);
if (error) {
Util::Error::GetInstance().Throw(
{Util::Error::Severity::NON_FATAL}, {Util::Error::Type::COULD_NOT_SYNC_SAVE_DATA},
{}, {}, R"([Mem]: Could not sync save data stored @ "{}")", sramPath);
return;
}
saveData.unmap();
}
auto sramVec = Util::ReadFileBinary(sramPath);
if (sramVec.empty()) {
Util::WriteFileBinary(std::array<u8, SRAM_SIZE>{}, sramPath);
sramVec = Util::ReadFileBinary(sramPath);
}
if (sramVec.size() != SRAM_SIZE) {
Util::Error::GetInstance().Throw(
{Util::Error::Severity::NON_FATAL}, {Util::Error::Type::SAVE_DATA_IS_CORRUPT_OR_INVALID_SIZE},
{}, {}, "[Mem]: Save data is corrupt or has unexpected size! (it's {} KiB)", sramVec.size() / 1024);
return;
}
saveData = mio::make_mmap_sink(sramPath, 0, mio::map_entire_file, error);
if (error) {
Util::Error::GetInstance().Throw(
{Util::Error::Severity::NON_FATAL}, {Util::Error::Type::MMAP_MAKE_SINK_ERROR},
{}, {}, R"([Mem]: Could not create file sink for save data @ "{}")", sramPath);
}
}
}
FORCE_INLINE void SetROMCIC(u32 checksum, ROM &rom) {
switch (checksum) {
case 0xEC8B1325:
rom.cicType = CIC_NUS_7102;
break; // 7102
case 0x1DEB51A9:
rom.cicType = CIC_NUS_6101;
break; // 6101
case 0xC08E5BD6:
rom.cicType = CIC_NUS_6102_7101;
break;
case 0x03B8376A:
rom.cicType = CIC_NUS_6103_7103;
break;
case 0xCF7F41DC:
rom.cicType = CIC_NUS_6105_7105;
break;
case 0xD1059C6A:
rom.cicType = CIC_NUS_6106_7106;
break;
default:
warn("Could not determine CIC TYPE! Checksum: 0x{:08X} is unknown!", checksum);
rom.cicType = UNKNOWN_CIC_TYPE;
break;
}
}
void Mem::LoadROM(const bool isArchive, const std::string &filename) {
u32 endianness;
{
size_t sizeAdjusted;
std::vector<u8> buf{};
if (isArchive) {
buf = Util::OpenArchive(filename, sizeAdjusted);
} else {
buf = Util::OpenROM(filename, sizeAdjusted);
}
endianness = std::byteswap(Util::ReadAccess<u32>(buf, 0));
Util::SwapN64Rom<true>(buf, endianness);
std::ranges::copy(buf, rom.cart.begin());
rom.mask = sizeAdjusted - 1;
memcpy(&rom.header, buf.data(), sizeof(ROMHeader));
}
memcpy(rom.gameNameCart, rom.header.imageName, sizeof(rom.header.imageName));
rom.header.clockRate = std::byteswap(rom.header.clockRate);
rom.header.programCounter = std::byteswap(rom.header.programCounter);
rom.header.release = std::byteswap(rom.header.release);
rom.header.crc1 = std::byteswap(rom.header.crc1);
rom.header.crc2 = std::byteswap(rom.header.crc2);
rom.header.unknown = std::byteswap(rom.header.unknown);
rom.header.unknown2 = std::byteswap(rom.header.unknown2);
rom.header.manufacturerId = std::byteswap(rom.header.manufacturerId);
rom.header.cartridgeId = std::byteswap(rom.header.cartridgeId);
rom.code[0] = rom.header.manufacturerId & 0xFF;
rom.code[1] = (rom.header.cartridgeId >> 8) & 0xFF;
rom.code[2] = rom.header.cartridgeId & 0xFF;
rom.code[3] = '\0';
for (int i = sizeof(rom.header.imageName) - 1; rom.gameNameCart[i] == ' '; i--) {
rom.gameNameCart[i] = '\0';
}
const u32 checksum = Util::crc32(0, &rom.cart[0x40], 0x9c0);
SetROMCIC(checksum, rom);
endianness = std::byteswap(Util::ReadAccess<u32>(rom.cart, 0));
Util::SwapN64Rom(rom.cart, endianness);
rom.pal = IsROMPAL();
}
template <>
u8 Mem::Read(const u32 paddr) {
n64::Registers& regs = n64::Core::GetRegs();
const SI &si = mmio.si;
if(Util::IsInsideRange(paddr, RDRAM_REGION_START, RDRAM_REGION_END)) return mmio.rdp.ReadRDRAM<u8>(paddr);
if(Util::IsInsideRange(paddr, DMEM_REGION_START, RSP_MEM_REGION_END)) {
const auto &src = paddr & 0x1000 ? mmio.rsp.imem : mmio.rsp.dmem;
return src[BYTE_ADDRESS(paddr & 0xfff)];
}
if(Util::IsInsideRange(paddr, CART_REGION_START_2_1, CART_REGION_END_1_2)) return mmio.pi.BusRead<u8, false>(paddr);
if(Util::IsInsideRange(paddr, AI_REGION_START, AI_REGION_END)) {
const u32 w = mmio.ai.Read(paddr & ~3);
const int offs = 3 - (paddr & 3);
return w >> offs * 8 & 0xff;
}
if(Util::IsInsideRange(paddr, MMIO_REGION_START_1, MMIO_REGION_END_1) ||
Util::IsInsideRange(paddr, MMIO_REGION_START_2, MMIO_REGION_END_2)) {
Util::Error::GetInstance().Throw(
{Util::Error::Severity::NON_FATAL}, {Util::Error::Type::MEM_INVALID_ACCESS}, regs.pc,
Util::Error::MemoryAccess{false, Util::Error::MemoryAccess::BYTE, paddr, 0}, "8-bit read access from MMIO");
return 0;
}
if(Util::IsInsideRange(paddr, PIF_ROM_REGION_START, PIF_ROM_REGION_END)) return si.pif.bootrom[BYTE_ADDRESS(paddr) - PIF_ROM_REGION_START];
if(Util::IsInsideRange(paddr, PIF_RAM_REGION_START, PIF_RAM_REGION_END)) return si.pif.ram[paddr - PIF_RAM_REGION_START];
if(Util::IsInsideRange(paddr, UNUSED_START_1, UNUSED_END_1) || // unused
Util::IsInsideRange(paddr, UNUSED_START_2, UNUSED_END_2) ||
Util::IsInsideRange(paddr, UNUSED_START_3, UNUSED_END_3) ||
Util::IsInsideRange(paddr, UNUSED_START_4, UNUSED_END_4)) return 0;
Util::Error::GetInstance().Throw(
{Util::Error::Severity::NON_FATAL}, {Util::Error::Type::MEM_UNHANDLED_ACCESS}, regs.pc,
Util::Error::MemoryAccess{false, Util::Error::MemoryAccess::BYTE, paddr, 0}, "8-bit read access in unhandled region");
return 0;
}
template <>
u16 Mem::Read(const u32 paddr) {
n64::Registers& regs = n64::Core::GetRegs();
const SI &si = mmio.si;
if(Util::IsInsideRange(paddr, RDRAM_REGION_START, RDRAM_REGION_END)) return mmio.rdp.ReadRDRAM<u16>(paddr);
if(Util::IsInsideRange(paddr, DMEM_REGION_START, RSP_MEM_REGION_END)) {
const auto &src = paddr & 0x1000 ? mmio.rsp.imem : mmio.rsp.dmem;
return Util::ReadAccess<u16>(src, HALF_ADDRESS(paddr & 0xfff));
}
if(Util::IsInsideRange(paddr, CART_REGION_START_2_1, CART_REGION_END_1_2)) return mmio.pi.BusRead<u16, false>(paddr);
if(Util::IsInsideRange(paddr, MMIO_REGION_START_1, MMIO_REGION_END_1) ||
Util::IsInsideRange(paddr, MMIO_REGION_START_2, MMIO_REGION_END_2)) return mmio.Read(paddr);
if(Util::IsInsideRange(paddr, PIF_ROM_REGION_START, PIF_ROM_REGION_END)) return Util::ReadAccess<u16>(si.pif.bootrom, HALF_ADDRESS(paddr) - PIF_ROM_REGION_START);
if(Util::IsInsideRange(paddr, PIF_RAM_REGION_START, PIF_RAM_REGION_END)) return std::byteswap(Util::ReadAccess<u16>(si.pif.ram, paddr - PIF_RAM_REGION_START));
if(Util::IsInsideRange(paddr, UNUSED_START_1, UNUSED_END_1) || // unused
Util::IsInsideRange(paddr, UNUSED_START_2, UNUSED_END_2) ||
Util::IsInsideRange(paddr, UNUSED_START_3, UNUSED_END_3) ||
Util::IsInsideRange(paddr, UNUSED_START_4, UNUSED_END_4)) return 0;
Util::Error::GetInstance().Throw(
{Util::Error::Severity::NON_FATAL}, {Util::Error::Type::MEM_UNHANDLED_ACCESS}, regs.pc,
Util::Error::MemoryAccess{false, Util::Error::MemoryAccess::SHORT, paddr, 0}, "16-bit read access in unhandled region");
return 0;
}
template <>
u32 Mem::Read(const u32 paddr) {
n64::Registers& regs = n64::Core::GetRegs();
const SI &si = mmio.si;
if(Util::IsInsideRange(paddr, RDRAM_REGION_START, RDRAM_REGION_END)) return mmio.rdp.ReadRDRAM<u32>(paddr);
if(Util::IsInsideRange(paddr, DMEM_REGION_START, RSP_MEM_REGION_END)) {
const auto &src = paddr & 0x1000 ? mmio.rsp.imem : mmio.rsp.dmem;
return Util::ReadAccess<u32>(src, paddr & 0xfff);
}
if(Util::IsInsideRange(paddr, CART_REGION_START_2_1, CART_REGION_END_1_2)) return mmio.pi.BusRead<u32, false>(paddr);
if(Util::IsInsideRange(paddr, MMIO_REGION_START_1, MMIO_REGION_END_1) ||
Util::IsInsideRange(paddr, MMIO_REGION_START_2, MMIO_REGION_END_2)) return mmio.Read(paddr);
if(Util::IsInsideRange(paddr, PIF_ROM_REGION_START, PIF_ROM_REGION_END)) return Util::ReadAccess<u32>(si.pif.bootrom, paddr - PIF_ROM_REGION_START);
if(Util::IsInsideRange(paddr, PIF_RAM_REGION_START, PIF_RAM_REGION_END)) return std::byteswap(Util::ReadAccess<u32>(si.pif.ram, paddr - PIF_RAM_REGION_START));
if(Util::IsInsideRange(paddr, UNUSED_START_1, UNUSED_END_1) || // unused
Util::IsInsideRange(paddr, UNUSED_START_2, UNUSED_END_2) ||
Util::IsInsideRange(paddr, UNUSED_START_3, UNUSED_END_3) ||
Util::IsInsideRange(paddr, UNUSED_START_4, UNUSED_END_4)) return 0;
Util::Error::GetInstance().Throw(
{Util::Error::Severity::NON_FATAL}, {Util::Error::Type::MEM_UNHANDLED_ACCESS}, regs.pc,
Util::Error::MemoryAccess{false, Util::Error::MemoryAccess::WORD, paddr, 0}, "32-bit read access in unhandled region");
return 0;
}
template <>
u64 Mem::Read(const u32 paddr) {
n64::Registers& regs = n64::Core::GetRegs();
const SI &si = mmio.si;
if(Util::IsInsideRange(paddr, RDRAM_REGION_START, RDRAM_REGION_END)) return mmio.rdp.ReadRDRAM<u64>(paddr);
if(Util::IsInsideRange(paddr, DMEM_REGION_START, RSP_MEM_REGION_END)) {
const auto &src = paddr & 0x1000 ? mmio.rsp.imem : mmio.rsp.dmem;
return Util::ReadAccess<u64>(src, paddr & 0xfff);
}
if(Util::IsInsideRange(paddr, CART_REGION_START_2_1, CART_REGION_END_1_2)) return mmio.pi.BusRead<u64, false>(paddr);
if(Util::IsInsideRange(paddr, MMIO_REGION_START_1, MMIO_REGION_END_1) ||
Util::IsInsideRange(paddr, MMIO_REGION_START_2, MMIO_REGION_END_2)) return mmio.Read(paddr);
if(Util::IsInsideRange(paddr, PIF_ROM_REGION_START, PIF_ROM_REGION_END)) return Util::ReadAccess<u64>(si.pif.bootrom, paddr - PIF_ROM_REGION_START);
if(Util::IsInsideRange(paddr, PIF_RAM_REGION_START, PIF_RAM_REGION_END)) return std::byteswap(Util::ReadAccess<u64>(si.pif.ram, paddr - PIF_RAM_REGION_START));
if(Util::IsInsideRange(paddr, UNUSED_START_1, UNUSED_END_1) || // unused
Util::IsInsideRange(paddr, UNUSED_START_2, UNUSED_END_2) ||
Util::IsInsideRange(paddr, UNUSED_START_3, UNUSED_END_3) ||
Util::IsInsideRange(paddr, UNUSED_START_4, UNUSED_END_4)) return 0;
Util::Error::GetInstance().Throw(
{Util::Error::Severity::NON_FATAL}, {Util::Error::Type::MEM_UNHANDLED_ACCESS}, regs.pc,
Util::Error::MemoryAccess{false, Util::Error::MemoryAccess::DWORD, paddr, 0}, "64-bit read access in unhandled region");
return 0;
}
template <>
void Mem::WriteInterpreter<u8>(u32 paddr, u32 val) {
n64::Registers& regs = n64::Core::GetRegs();
SI &si = mmio.si;
if(Util::IsInsideRange(paddr, RDRAM_REGION_START, RDRAM_REGION_END)) { mmio.rdp.WriteRDRAM<u8>(paddr, val); return; }
if(Util::IsInsideRange(paddr, DMEM_REGION_START, RSP_MEM_REGION_END)) {
val = val << (8 * (3 - (paddr & 3)));
auto &dest = paddr & 0x1000 ? mmio.rsp.imem : mmio.rsp.dmem;
paddr = (paddr & 0xFFF) & ~3;
Util::WriteAccess<u32>(dest, paddr, val);
return;
}
if(Util::IsInsideRange(paddr, CART_REGION_START_2_1, CART_REGION_END_1_2)) {
trace("BusWrite<u8> @ {:08X} = {:02X}", paddr, val);
mmio.pi.BusWrite<u8, false>(paddr, val);
return;
}
if(Util::IsInsideRange(paddr, MMIO_REGION_START_1, MMIO_REGION_END_1) ||
Util::IsInsideRange(paddr, MMIO_REGION_START_2, MMIO_REGION_END_2)) panic("MMIO Write<u8>!");
if(Util::IsInsideRange(paddr, PIF_RAM_REGION_START, PIF_RAM_REGION_END)) {
val = val << (8 * (3 - (paddr & 3)));
paddr = (paddr - PIF_RAM_REGION_START) & ~3;
Util::WriteAccess<u32>(si.pif.ram, paddr, std::byteswap(val));
si.pif.ProcessCommands();
return;
}
if(Util::IsInsideRange(paddr, UNUSED_START_1, UNUSED_END_1) || // unused
Util::IsInsideRange(paddr, UNUSED_START_2, UNUSED_END_2) ||
Util::IsInsideRange(paddr, UNUSED_START_3, UNUSED_END_3) ||
Util::IsInsideRange(paddr, PIF_ROM_REGION_START, PIF_ROM_REGION_END) ||
Util::IsInsideRange(paddr, UNUSED_START_4, UNUSED_END_4)) return;
panic("Unimplemented 8-bit write at address {:08X} with value {:02X} (PC = {:016X})", paddr, val, (u64)regs.pc);
}
#ifndef __aarch64__
template <>
void Mem::WriteJIT<u8>(const u32 paddr, const u32 val) {
WriteInterpreter<u8>(paddr, val);
if (jit)
jit->InvalidateBlock(paddr);
}
#endif
template <>
void Mem::Write<u8>(const u32 paddr, const u32 val) {
WriteInterpreter<u8>(paddr, val);
}
template <>
void Mem::WriteInterpreter<u16>(u32 paddr, u32 val) {
n64::Registers& regs = n64::Core::GetRegs();
SI &si = mmio.si;
if(Util::IsInsideRange(paddr, RDRAM_REGION_START, RDRAM_REGION_END)) { mmio.rdp.WriteRDRAM<u16>(paddr, val); return; }
if(Util::IsInsideRange(paddr, DMEM_REGION_START, RSP_MEM_REGION_END)) {
val = val << (16 * !(paddr & 2));
auto &dest = paddr & 0x1000 ? mmio.rsp.imem : mmio.rsp.dmem;
paddr = (paddr & 0xFFF) & ~3;
Util::WriteAccess<u32>(dest, paddr, val);
return;
}
if(Util::IsInsideRange(paddr, CART_REGION_START_2_1, CART_REGION_END_1_2)) {
trace("BusWrite<u8> @ {:08X} = {:04X}", paddr, val);
mmio.pi.BusWrite<u16, false>(paddr, val);
return;
}
if(Util::IsInsideRange(paddr, MMIO_REGION_START_1, MMIO_REGION_END_1) ||
Util::IsInsideRange(paddr, MMIO_REGION_START_2, MMIO_REGION_END_2)) panic("MMIO Write<u16>!");
if(Util::IsInsideRange(paddr, PIF_RAM_REGION_START, PIF_RAM_REGION_END)) {
val = val << (16 * !(paddr & 2));
paddr &= ~3;
Util::WriteAccess<u32>(si.pif.ram, paddr - PIF_RAM_REGION_START, std::byteswap(val));
si.pif.ProcessCommands();
return;
}
if(Util::IsInsideRange(paddr, UNUSED_START_1, UNUSED_END_1) || // unused
Util::IsInsideRange(paddr, UNUSED_START_2, UNUSED_END_2) ||
Util::IsInsideRange(paddr, UNUSED_START_3, UNUSED_END_3) ||
Util::IsInsideRange(paddr, PIF_ROM_REGION_START, PIF_ROM_REGION_END) ||
Util::IsInsideRange(paddr, UNUSED_START_4, UNUSED_END_4)) return;
panic("Unimplemented 16-bit write at address {:08X} with value {:04X} (PC = {:016X})", paddr, val, (u64)regs.pc);
}
#ifndef __aarch64__
template <>
void Mem::WriteJIT<u16>(const u32 paddr, const u32 val) {
WriteInterpreter<u16>(paddr, val);
if (jit)
jit->InvalidateBlock(paddr);
}
#endif
template <>
void Mem::Write<u16>(const u32 paddr, const u32 val) {
WriteInterpreter<u16>(paddr, val);
}
template <>
void Mem::WriteInterpreter<u32>(const u32 paddr, const u32 val) {
n64::Registers& regs = n64::Core::GetRegs();
SI &si = mmio.si;
if(Util::IsInsideRange(paddr, RDRAM_REGION_START, RDRAM_REGION_END)) { mmio.rdp.WriteRDRAM<u32>(paddr, val); return; }
if(Util::IsInsideRange(paddr, DMEM_REGION_START, RSP_MEM_REGION_END)) {
auto &dest = paddr & 0x1000 ? mmio.rsp.imem : mmio.rsp.dmem;
Util::WriteAccess<u32>(dest, paddr & 0xfff, val);
return;
}
if(Util::IsInsideRange(paddr, CART_REGION_START_2_1, CART_REGION_END_1_2)) {
trace("BusWrite<u8> @ {:08X} = {:08X}", paddr, val);
mmio.pi.BusWrite<u32, false>(paddr, val);
return;
}
if(Util::IsInsideRange(paddr, MMIO_REGION_START_1, MMIO_REGION_END_1) ||
Util::IsInsideRange(paddr, MMIO_REGION_START_2, MMIO_REGION_END_2)) { mmio.Write(paddr, val); return; }
if(Util::IsInsideRange(paddr, PIF_RAM_REGION_START, PIF_RAM_REGION_END)) {
Util::WriteAccess<u32>(si.pif.ram, paddr - PIF_RAM_REGION_START, std::byteswap(val));
si.pif.ProcessCommands();
return;
}
if(Util::IsInsideRange(paddr, UNUSED_START_1, UNUSED_END_1) || // unused
Util::IsInsideRange(paddr, UNUSED_START_2, UNUSED_END_2) ||
Util::IsInsideRange(paddr, UNUSED_START_3, UNUSED_END_3) ||
Util::IsInsideRange(paddr, PIF_ROM_REGION_START, PIF_ROM_REGION_END) ||
Util::IsInsideRange(paddr, UNUSED_START_4, UNUSED_END_4)) return;
panic("Unimplemented 32-bit write at address {:08X} with value {:08X} (PC = {:016X})", paddr, val, (u64)regs.pc);
}
#ifndef __aarch64__
template <>
void Mem::WriteJIT<u32>(const u32 paddr, const u32 val) {
WriteInterpreter<u32>(paddr, val);
if (jit)
jit->InvalidateBlock(paddr);
}
#endif
template <>
void Mem::Write<u32>(const u32 paddr, const u32 val) {
WriteInterpreter<u32>(paddr, val);
}
#ifndef __aarch64__
void Mem::WriteJIT(const u32 paddr, const u64 val) {
WriteInterpreter(paddr, val);
if (jit)
jit->InvalidateBlock(paddr);
}
#endif
void Mem::Write(const u32 paddr, const u64 val) { WriteInterpreter(paddr, val); }
void Mem::WriteInterpreter(const u32 paddr, u64 val) {
n64::Registers& regs = n64::Core::GetRegs();
SI &si = mmio.si;
if(Util::IsInsideRange(paddr, RDRAM_REGION_START, RDRAM_REGION_END)) { mmio.rdp.WriteRDRAM<u64>(paddr, val); return; }
if(Util::IsInsideRange(paddr, DMEM_REGION_START, RSP_MEM_REGION_END)) {
auto &dest = paddr & 0x1000 ? mmio.rsp.imem : mmio.rsp.dmem;
val >>= 32;
Util::WriteAccess<u32>(dest, paddr & 0xfff, val);
return;
}
if(Util::IsInsideRange(paddr, CART_REGION_START_2_1, CART_REGION_END_1_2)) {
trace("BusWrite<u64> @ {:08X} = {:016X}", paddr, val);
mmio.pi.BusWrite<false>(paddr, val);
return;
}
if(Util::IsInsideRange(paddr, MMIO_REGION_START_1, MMIO_REGION_END_1) ||
Util::IsInsideRange(paddr, MMIO_REGION_START_2, MMIO_REGION_END_2)) panic("MMIO Write<u64>!");
if(Util::IsInsideRange(paddr, PIF_RAM_REGION_START, PIF_RAM_REGION_END)) {
Util::WriteAccess<u64>(si.pif.ram, paddr - PIF_RAM_REGION_START, std::byteswap(val));
si.pif.ProcessCommands();
return;
}
if(Util::IsInsideRange(paddr, UNUSED_START_1, UNUSED_END_1) || // unused
Util::IsInsideRange(paddr, UNUSED_START_2, UNUSED_END_2) ||
Util::IsInsideRange(paddr, UNUSED_START_3, UNUSED_END_3) ||
Util::IsInsideRange(paddr, PIF_ROM_REGION_START, PIF_ROM_REGION_END) ||
Util::IsInsideRange(paddr, UNUSED_START_4, UNUSED_END_4)) return;
panic("Unimplemented 64-bit write at address {:08X} with value {:016X} (PC = {:016X})", paddr, val, (u64)regs.pc);
}
template <>
u32 Mem::BackupRead<u32>(const u32 addr) {
switch (saveType) {
case SAVE_NONE:
return 0;
case SAVE_EEPROM_4k:
case SAVE_EEPROM_16k:
warn("Accessing cartridge backup type SAVE_EEPROM, returning 0 for word read");
return 0;
case SAVE_FLASH_1m:
return flash.Read<u32>(addr);
case SAVE_SRAM_256k:
return 0xFFFFFFFF;
default:
panic("Backup read word with unknown save type");
}
}
template <>
u8 Mem::BackupRead<u8>(const u32 addr) {
switch (saveType) {
case SAVE_NONE:
return 0;
case SAVE_EEPROM_4k:
case SAVE_EEPROM_16k:
warn("Accessing cartridge backup type SAVE_EEPROM, returning 0 for word read");
return 0;
case SAVE_FLASH_1m:
return flash.Read<u8>(addr);
case SAVE_SRAM_256k:
if (saveData.is_mapped()) {
assert(addr < saveData.size());
return saveData[addr];
} else {
panic("Invalid backup Read<u8> if save data is not initialized");
}
default:
panic("Backup read word with unknown save type");
}
}
template <>
void Mem::BackupWrite<u32>(const u32 addr, const u32 val) {
switch (saveType) {
case SAVE_NONE:
warn("Accessing cartridge with save type SAVE_NONE in write word");
break;
case SAVE_EEPROM_4k:
case SAVE_EEPROM_16k:
panic("Accessing cartridge with save type SAVE_EEPROM in write word");
case SAVE_FLASH_1m:
flash.Write<u32>(addr, val);
break;
case SAVE_SRAM_256k:
break;
default:
panic("Backup read word with unknown save type");
}
}
template <>
void Mem::BackupWrite<u8>(const u32 addr, const u8 val) {
switch (saveType) {
case SAVE_NONE:
warn("Accessing cartridge with save type SAVE_NONE in write word");
break;
case SAVE_EEPROM_4k:
case SAVE_EEPROM_16k:
panic("Accessing cartridge with save type SAVE_EEPROM in write word");
case SAVE_FLASH_1m:
flash.Write<u8>(addr, val);
break;
case SAVE_SRAM_256k:
if (saveData.is_mapped()) {
assert(addr < saveData.size());
saveData[addr] = val;
} else {
panic("Invalid backup Write<u8> if save data is not initialized");
}
break;
default:
panic("Backup read word with unknown save type");
}
}
} // namespace n64
+153
View File
@@ -0,0 +1,153 @@
#pragma once
#include <File.hpp>
#include <GameDB.hpp>
#include <backend/MemoryRegions.hpp>
#include <backend/core/MMIO.hpp>
#include <common.hpp>
#include <log.hpp>
#include <vector>
#include <algorithm>
#include <ranges>
namespace n64 {
struct ROMHeader {
u8 initialValues[4];
char imageName[20];
char countryCode[2];
u16 cartridgeId;
u32 clockRate;
u32 programCounter;
u32 release;
u32 crc1;
u32 crc2;
u32 unknown2;
u32 manufacturerId;
u64 unknown;
};
struct ROM {
bool pal;
char gameNameCart[20];
char code[4];
ROMHeader header;
size_t mask;
CICType cicType;
std::vector<u8> cart;
std::string gameNameDB;
};
enum class FlashState : u8 { Idle, Erase, Write, Read, Status };
struct Flash {
explicit Flash(mio::mmap_sink &);
~Flash() = default;
void Reset();
void Load(SaveType, const std::string &);
std::array<u8, 128> writeBuf{};
FlashState state{};
u64 status{};
size_t eraseOffs{};
size_t writeOffs{};
std::string flashPath{};
mio::mmap_sink &saveData;
enum FlashCommands : u8 {
FLASH_COMMAND_EXECUTE = 0xD2,
FLASH_COMMAND_STATUS = 0xE1,
FLASH_COMMAND_SET_ERASE_OFFSET = 0x4B,
FLASH_COMMAND_ERASE = 0x78,
FLASH_COMMAND_SET_WRITE_OFFSET = 0xA5,
FLASH_COMMAND_WRITE = 0xB4,
FLASH_COMMAND_READ = 0xF0,
};
void CommandExecute() const;
void CommandStatus();
void CommandSetEraseOffs(u32);
void CommandErase();
void CommandSetWriteOffs(u32);
void CommandWrite();
void CommandRead();
template <typename T>
void Write(u32 index, T val);
template <typename T>
T Read(u32 index) const;
};
struct JIT;
struct Mem {
~Mem() = default;
Mem();
void Reset();
void LoadSRAM(SaveType, fs::path);
void LoadROM(bool, const std::string &);
void SetJIT(JIT* jit) { this->jit = jit; }
[[nodiscard]] auto GetRDRAMPtr() -> u8 * { return mmio.rdp.rdram.data(); }
[[nodiscard]] auto GetRDRAM() -> std::vector<u8> & { return mmio.rdp.rdram; }
template <typename T>
T Read(u32);
template <typename T>
void Write(u32, u32);
void Write(u32, u64);
template <typename T>
T BackupRead(u32);
template <typename T>
void BackupWrite(u32, T);
FORCE_INLINE void DumpRDRAM() const {
std::vector<u8> temp{};
temp.resize(RDRAM_SIZE);
std::ranges::copy(mmio.rdp.rdram, temp.begin());
Util::SwapBuffer<u32>(temp);
Util::WriteFileBinary(temp, "rdram.bin");
}
FORCE_INLINE void DumpIMEM() const {
std::array<u8, IMEM_SIZE> temp{};
std::ranges::copy(mmio.rsp.imem, temp.begin());
Util::SwapBuffer<u32>(temp);
Util::WriteFileBinary(temp, "imem.bin");
}
FORCE_INLINE void DumpDMEM() const {
std::array<u8, DMEM_SIZE> temp{};
std::ranges::copy(mmio.rsp.dmem, temp.begin());
Util::SwapBuffer<u32>(temp);
Util::WriteFileBinary(temp, "dmem.bin");
}
MMIO mmio;
ROM rom;
SaveType saveType = SAVE_NONE;
Flash flash;
private:
friend struct SI;
friend struct PI;
friend struct AI;
friend struct RSP;
friend struct JIT;
friend struct Core;
template <typename T>
void WriteInterpreter(u32, u32);
void WriteInterpreter(u32, u64);
template <typename T>
void WriteJIT(u32, u32);
void WriteJIT(u32, u64);
std::array<u8, ISVIEWER_SIZE> isviewer{};
int mmioSize{}, flashSize{};
JIT *jit = nullptr;
std::string sramPath{};
mio::mmap_sink saveData{};
[[nodiscard]] FORCE_INLINE bool IsROMPAL() const {
static constexpr char pal_codes[] = {'D', 'F', 'I', 'P', 'S', 'U', 'X', 'Y'};
return std::ranges::any_of(pal_codes, [this](char a) { return rom.cart[0x3d] == a; });
}
};
} // namespace n64
+41
View File
@@ -0,0 +1,41 @@
#pragma once
#include <common.hpp>
static constexpr u16 rcpRom[] = {
0xffff, 0xff00, 0xfe01, 0xfd04, 0xfc07, 0xfb0c, 0xfa11, 0xf918, 0xf81f, 0xf727, 0xf631, 0xf53b, 0xf446, 0xf352,
0xf25f, 0xf16d, 0xf07c, 0xef8b, 0xee9c, 0xedae, 0xecc0, 0xebd3, 0xeae8, 0xe9fd, 0xe913, 0xe829, 0xe741, 0xe65a,
0xe573, 0xe48d, 0xe3a9, 0xe2c5, 0xe1e1, 0xe0ff, 0xe01e, 0xdf3d, 0xde5d, 0xdd7e, 0xdca0, 0xdbc2, 0xdae6, 0xda0a,
0xd92f, 0xd854, 0xd77b, 0xd6a2, 0xd5ca, 0xd4f3, 0xd41d, 0xd347, 0xd272, 0xd19e, 0xd0cb, 0xcff8, 0xcf26, 0xce55,
0xcd85, 0xccb5, 0xcbe6, 0xcb18, 0xca4b, 0xc97e, 0xc8b2, 0xc7e7, 0xc71c, 0xc652, 0xc589, 0xc4c0, 0xc3f8, 0xc331,
0xc26b, 0xc1a5, 0xc0e0, 0xc01c, 0xbf58, 0xbe95, 0xbdd2, 0xbd10, 0xbc4f, 0xbb8f, 0xbacf, 0xba10, 0xb951, 0xb894,
0xb7d6, 0xb71a, 0xb65e, 0xb5a2, 0xb4e8, 0xb42e, 0xb374, 0xb2bb, 0xb203, 0xb14b, 0xb094, 0xafde, 0xaf28, 0xae73,
0xadbe, 0xad0a, 0xac57, 0xaba4, 0xaaf1, 0xaa40, 0xa98e, 0xa8de, 0xa82e, 0xa77e, 0xa6d0, 0xa621, 0xa574, 0xa4c6,
0xa41a, 0xa36e, 0xa2c2, 0xa217, 0xa16d, 0xa0c3, 0xa01a, 0x9f71, 0x9ec8, 0x9e21, 0x9d79, 0x9cd3, 0x9c2d, 0x9b87,
0x9ae2, 0x9a3d, 0x9999, 0x98f6, 0x9852, 0x97b0, 0x970e, 0x966c, 0x95cb, 0x952b, 0x948b, 0x93eb, 0x934c, 0x92ad,
0x920f, 0x9172, 0x90d4, 0x9038, 0x8f9c, 0x8f00, 0x8e65, 0x8dca, 0x8d30, 0x8c96, 0x8bfc, 0x8b64, 0x8acb, 0x8a33,
0x899c, 0x8904, 0x886e, 0x87d8, 0x8742, 0x86ad, 0x8618, 0x8583, 0x84f0, 0x845c, 0x83c9, 0x8336, 0x82a4, 0x8212,
0x8181, 0x80f0, 0x8060, 0x7fd0, 0x7f40, 0x7eb1, 0x7e22, 0x7d93, 0x7d05, 0x7c78, 0x7beb, 0x7b5e, 0x7ad2, 0x7a46,
0x79ba, 0x792f, 0x78a4, 0x781a, 0x7790, 0x7706, 0x767d, 0x75f5, 0x756c, 0x74e4, 0x745d, 0x73d5, 0x734f, 0x72c8,
0x7242, 0x71bc, 0x7137, 0x70b2, 0x702e, 0x6fa9, 0x6f26, 0x6ea2, 0x6e1f, 0x6d9c, 0x6d1a, 0x6c98, 0x6c16, 0x6b95,
0x6b14, 0x6a94, 0x6a13, 0x6993, 0x6914, 0x6895, 0x6816, 0x6798, 0x6719, 0x669c, 0x661e, 0x65a1, 0x6524, 0x64a8,
0x642c, 0x63b0, 0x6335, 0x62ba, 0x623f, 0x61c5, 0x614b, 0x60d1, 0x6058, 0x5fdf, 0x5f66, 0x5eed, 0x5e75, 0x5dfd,
0x5d86, 0x5d0f, 0x5c98, 0x5c22, 0x5bab, 0x5b35, 0x5ac0, 0x5a4b, 0x59d6, 0x5961, 0x58ed, 0x5879, 0x5805, 0x5791,
0x571e, 0x56ac, 0x5639, 0x55c7, 0x5555, 0x54e3, 0x5472, 0x5401, 0x5390, 0x5320, 0x52af, 0x5240, 0x51d0, 0x5161,
0x50f2, 0x5083, 0x5015, 0x4fa6, 0x4f38, 0x4ecb, 0x4e5e, 0x4df1, 0x4d84, 0x4d17, 0x4cab, 0x4c3f, 0x4bd3, 0x4b68,
0x4afd, 0x4a92, 0x4a27, 0x49bd, 0x4953, 0x48e9, 0x4880, 0x4817, 0x47ae, 0x4745, 0x46dc, 0x4674, 0x460c, 0x45a5,
0x453d, 0x44d6, 0x446f, 0x4408, 0x43a2, 0x433c, 0x42d6, 0x4270, 0x420b, 0x41a6, 0x4141, 0x40dc, 0x4078, 0x4014,
0x3fb0, 0x3f4c, 0x3ee8, 0x3e85, 0x3e22, 0x3dc0, 0x3d5d, 0x3cfb, 0x3c99, 0x3c37, 0x3bd6, 0x3b74, 0x3b13, 0x3ab2,
0x3a52, 0x39f1, 0x3991, 0x3931, 0x38d2, 0x3872, 0x3813, 0x37b4, 0x3755, 0x36f7, 0x3698, 0x363a, 0x35dc, 0x357f,
0x3521, 0x34c4, 0x3467, 0x340a, 0x33ae, 0x3351, 0x32f5, 0x3299, 0x323e, 0x31e2, 0x3187, 0x312c, 0x30d1, 0x3076,
0x301c, 0x2fc2, 0x2f68, 0x2f0e, 0x2eb4, 0x2e5b, 0x2e02, 0x2da9, 0x2d50, 0x2cf8, 0x2c9f, 0x2c47, 0x2bef, 0x2b97,
0x2b40, 0x2ae8, 0x2a91, 0x2a3a, 0x29e4, 0x298d, 0x2937, 0x28e0, 0x288b, 0x2835, 0x27df, 0x278a, 0x2735, 0x26e0,
0x268b, 0x2636, 0x25e2, 0x258d, 0x2539, 0x24e5, 0x2492, 0x243e, 0x23eb, 0x2398, 0x2345, 0x22f2, 0x22a0, 0x224d,
0x21fb, 0x21a9, 0x2157, 0x2105, 0x20b4, 0x2063, 0x2012, 0x1fc1, 0x1f70, 0x1f1f, 0x1ecf, 0x1e7f, 0x1e2e, 0x1ddf,
0x1d8f, 0x1d3f, 0x1cf0, 0x1ca1, 0x1c52, 0x1c03, 0x1bb4, 0x1b66, 0x1b17, 0x1ac9, 0x1a7b, 0x1a2d, 0x19e0, 0x1992,
0x1945, 0x18f8, 0x18ab, 0x185e, 0x1811, 0x17c4, 0x1778, 0x172c, 0x16e0, 0x1694, 0x1648, 0x15fd, 0x15b1, 0x1566,
0x151b, 0x14d0, 0x1485, 0x143b, 0x13f0, 0x13a6, 0x135c, 0x1312, 0x12c8, 0x127f, 0x1235, 0x11ec, 0x11a3, 0x1159,
0x1111, 0x10c8, 0x107f, 0x1037, 0x0fef, 0x0fa6, 0x0f5e, 0x0f17, 0x0ecf, 0x0e87, 0x0e40, 0x0df9, 0x0db2, 0x0d6b,
0x0d24, 0x0cdd, 0x0c97, 0x0c50, 0x0c0a, 0x0bc4, 0x0b7e, 0x0b38, 0x0af2, 0x0aad, 0x0a68, 0x0a22, 0x09dd, 0x0998,
0x0953, 0x090f, 0x08ca, 0x0886, 0x0842, 0x07fd, 0x07b9, 0x0776, 0x0732, 0x06ee, 0x06ab, 0x0668, 0x0624, 0x05e1,
0x059e, 0x055c, 0x0519, 0x04d6, 0x0494, 0x0452, 0x0410, 0x03ce, 0x038c, 0x034a, 0x0309, 0x02c7, 0x0286, 0x0245,
0x0204, 0x01c3, 0x0182, 0x0141, 0x0101, 0x00c0, 0x0080, 0x0040};
+298
View File
@@ -0,0 +1,298 @@
#include <log.hpp>
#include <parallel-rdp/ParallelRDPWrapper.hpp>
#include <Core.hpp>
namespace n64 {
RDP::RDP() {
rdram.resize(RDRAM_SIZE);
Reset();
}
void RDP::Reset() {
dpc = {};
dpc.status.raw = 0x80;
std::ranges::fill(rdram, 0);
std::ranges::fill(cmd_buf, 0);
}
template <>
void RDP::WriteRDRAM<u8>(const size_t idx, const u8 v) {
if (const size_t real = BYTE_ADDRESS(idx); real < RDRAM_SIZE) [[likely]] {
rdram[real] = v;
}
}
template <>
void RDP::WriteRDRAM<u16>(const size_t idx, const u16 v) {
if (const size_t real = HALF_ADDRESS(idx); real < RDRAM_SIZE) [[likely]] {
Util::WriteAccess<u16>(rdram, real, v);
}
}
template <>
void RDP::WriteRDRAM<u32>(const size_t idx, const u32 v) {
if (idx < RDRAM_SIZE) [[likely]] {
Util::WriteAccess<u32>(rdram, idx, v);
}
}
template <>
void RDP::WriteRDRAM<u64>(const size_t idx, const u64 v) {
if (idx < RDRAM_SIZE) [[likely]] {
Util::WriteAccess<u64>(rdram, idx, v);
}
}
template <>
u8 RDP::ReadRDRAM<u8>(const size_t idx) {
if (const size_t real = BYTE_ADDRESS(idx); real < RDRAM_SIZE) [[likely]]
return rdram[real];
return 0;
}
template <>
u16 RDP::ReadRDRAM<u16>(const size_t idx) {
if (const size_t real = HALF_ADDRESS(idx); real < RDRAM_SIZE) [[likely]]
return Util::ReadAccess<u16>(rdram, real);
return 0;
}
template <>
u32 RDP::ReadRDRAM<u32>(const size_t idx) {
if (idx < RDRAM_SIZE) [[likely]]
return Util::ReadAccess<u32>(rdram, idx);
return 0;
}
template <>
u64 RDP::ReadRDRAM<u64>(const size_t idx) {
if (idx < RDRAM_SIZE) [[likely]]
return Util::ReadAccess<u64>(rdram, idx);
return 0;
}
static const int cmd_lens[64] = {2, 2, 2, 2, 2, 2, 2, 2, 8, 12, 24, 28, 24, 28, 40, 44, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 4, 4, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2};
auto RDP::Read(const u32 addr) const -> u32 {
switch (addr) {
case 0x04100000:
return dpc.start;
case 0x04100004:
return dpc.end;
case 0x04100008:
return dpc.current;
case 0x0410000C:
return dpc.status.raw;
case 0x04100010:
return dpc.clock;
case 0x04100014:
return dpc.status.cmdBusy;
case 0x04100018:
return dpc.status.pipeBusy;
case 0x0410001C:
return dpc.tmem;
default:
panic("Unhandled DP Command Registers read (addr: {:08X})", addr);
}
return 0;
}
void RDP::Write(const u32 addr, const u32 val) {
switch (addr) {
case 0x04100000:
WriteStart(val);
break;
case 0x04100004:
WriteEnd(val);
break;
case 0x0410000C:
WriteStatus(val);
break;
default:
panic("Unhandled DP Command Registers write (addr: {:08X}, val: {:08X})", addr, val);
}
}
void RDP::WriteStatus(const u32 val) {
DPCStatusWrite temp{};
temp.raw = val;
bool unfrozen = false;
#define CLEAR_SET(val, clear, set) \
do { \
if ((clear)) \
(val) = 0; \
if ((set)) \
(val) = 1; \
} \
while (0)
CLEAR_SET(dpc.status.xbusDmemDma, temp.clearXbusDmemDma, temp.setXbusDmemDma);
if (temp.clearFreeze) {
dpc.status.freeze = false;
unfrozen = true;
}
if (temp.setFreeze) {
dpc.status.freeze = true;
}
CLEAR_SET(dpc.status.flush, temp.clearFlush, temp.setFlush);
CLEAR_SET(dpc.status.tmemBusy, temp.clearTmem, false);
CLEAR_SET(dpc.status.pipeBusy, temp.clearPipe, false);
CLEAR_SET(dpc.status.cmdBusy, temp.clearCmd, false);
CLEAR_SET(dpc.clock, temp.clearClock, false);
if (!unfrozen) {
RunCommand();
}
}
/*
FORCE_INLINE void logCommand(u8 cmd) {
switch(cmd) {
case 0x08: debug("Fill triangle"); break;
case 0x09: debug("Fill, zbuf triangle"); break;
case 0x0a: debug("Texture triangle"); break;
case 0x0b: debug("Texture, zbuf triangle"); break;
case 0x0c: debug("Shade triangle"); break;
case 0x0d: debug("Shade, zbuf triangle"); break;
case 0x0e: debug("Shade, texture triangle"); break;
case 0x0f: debug("Shade, texture, zbuf triangle"); break;
case 0x24: debug("Texture rectangle"); break;
case 0x25: debug("Texture rectangle flip"); break;
case 0x26: debug("Sync load"); break;
case 0x27: debug("Sync pipe"); break;
case 0x28: debug("Sync tile"); break;
case 0x29: debug("Sync full"); break;
case 0x2a: debug("Set key gb"); break;
case 0x2b: debug("Set key r"); break;
case 0x2c: debug("Set convert"); break;
case 0x2d: debug("Set scissor"); break;
case 0x2e: debug("Set prim depth"); break;
case 0x2f: debug("Set other modes"); break;
case 0x30: debug("Load TLUT"); break;
case 0x32: debug("Set tile size"); break;
case 0x33: debug("Load block"); break;
case 0x34: debug("Load tile"); break;
case 0x35: debug("Set tile"); break;
case 0x36: debug("Fill rectangle"); break;
case 0x37: debug("Set fill color"); break;
case 0x38: debug("Set fog color"); break;
case 0x39: debug("Set blend color"); break;
case 0x3a: debug("Set prim color"); break;
case 0x3b: debug("Set env color"); break;
case 0x3c: debug("Set combine"); break;
case 0x3d: debug("Set texture image"); break;
case 0x3e: debug("Set mask image"); break;
case 0x3f: debug("Set color image"); break;
}
}
*/
void RDP::RunCommand() {
n64::Mem& mem = n64::Core::GetMem();
ParallelRDP& parallel = n64::Core::GetInstance().parallel;
if (dpc.status.freeze) {
return;
}
dpc.status.pipeBusy = true;
dpc.status.startGclk = true;
if (dpc.end > dpc.current) {
dpc.status.freeze = true;
static int remaining_cmds = 0;
const u32 current = dpc.current & 0xFFFFF8;
const u32 end = dpc.end & 0xFFFFF8;
const auto len = static_cast<s32>(end) - static_cast<s32>(current);
if (len <= 0)
return;
if (len + remaining_cmds * 4 > COMMAND_BUFFER_SIZE) {
panic("Too many RDP commands");
return;
}
if (dpc.status.xbusDmemDma) {
for (int i = 0; i < len; i += 4) {
const u32 cmd = Util::ReadAccess<u32>(mem.mmio.rsp.dmem, current + i & 0xFFF);
cmd_buf[remaining_cmds + (i >> 2)] = cmd;
}
} else {
if (end > 0x7FFFFFF || current > 0x7FFFFFF) { // if (end > RDRAM_DSIZE || current > RDRAM_DSIZE)
return;
}
for (int i = 0; i < len; i += 4) {
const u32 cmd = Util::ReadAccess<u32>(rdram, current + i);
cmd_buf[remaining_cmds + (i >> 2)] = cmd;
}
}
const int word_len = (len >> 2) + remaining_cmds;
int buf_index = 0;
bool processed_all = true;
while (buf_index < word_len) {
const u8 cmd = cmd_buf[buf_index] >> 24 & 0x3F;
const int cmd_len = cmd_lens[cmd];
if ((buf_index + cmd_len) * 4 > len + remaining_cmds * 4) {
remaining_cmds = word_len - buf_index;
u32 tmp[remaining_cmds];
for (int i = 0; i < remaining_cmds; i++) {
tmp[i] = cmd_buf[buf_index + i];
}
for (int i = 0; i < remaining_cmds; i++) {
cmd_buf[i] = tmp[i];
}
processed_all = false;
break;
}
if (cmd >= 8) {
parallel.EnqueueCommand(cmd_len, &cmd_buf[buf_index]);
}
if (cmd == 0x29) {
OnFullSync();
}
buf_index += cmd_len;
}
if (processed_all) {
remaining_cmds = 0;
}
dpc.current = end;
dpc.end = end;
dpc.status.freeze = false;
}
dpc.status.cbufReady = true;
}
void RDP::OnFullSync() {
n64::Mem& mem = n64::Core::GetMem();
ParallelRDP& parallel = n64::Core::GetInstance().parallel;
parallel.OnFullSync();
dpc.status.pipeBusy = false;
dpc.status.startGclk = false;
dpc.status.cbufReady = false;
mem.mmio.mi.InterruptRaise(MI::Interrupt::DP);
}
} // namespace n64
+92
View File
@@ -0,0 +1,92 @@
#pragma once
#include <array>
#include <vector>
#include <common.hpp>
namespace n64 {
union DPCStatusWrite {
u32 raw;
struct {
unsigned clearXbusDmemDma : 1;
unsigned setXbusDmemDma : 1;
unsigned clearFreeze : 1;
unsigned setFreeze : 1;
unsigned clearFlush : 1;
unsigned setFlush : 1;
unsigned clearTmem : 1;
unsigned clearPipe : 1;
unsigned clearCmd : 1;
unsigned clearClock : 1;
};
};
union DPCStatus {
struct {
unsigned xbusDmemDma : 1;
unsigned freeze : 1;
unsigned flush : 1;
unsigned startGclk : 1;
unsigned tmemBusy : 1;
unsigned pipeBusy : 1;
unsigned cmdBusy : 1;
unsigned cbufReady : 1;
unsigned dmaBusy : 1;
unsigned endValid : 1;
unsigned startValid : 1;
};
u32 raw;
};
struct DPC {
DPCStatus status;
u32 start;
u32 current;
u32 end;
u32 clock;
u32 tmem;
};
struct RDP {
static constexpr auto COMMAND_BUFFER_SIZE = 0xFFFFF;
RDP();
void Reset();
[[nodiscard]] auto Read(u32 addr) const -> u32;
void Write(u32 addr, u32 val);
void WriteStatus(u32 val);
void RunCommand();
void OnFullSync();
FORCE_INLINE void WriteStart(u32 val) {
if (!dpc.status.startValid) {
dpc.start = val & 0xFFFFF8;
}
dpc.status.startValid = true;
}
FORCE_INLINE void WriteEnd(u32 val) {
dpc.end = val & 0xFFFFF8;
if (dpc.status.startValid) {
dpc.current = dpc.start;
dpc.status.startValid = false;
}
RunCommand();
}
template <typename T>
void WriteRDRAM(size_t, T);
template <typename T>
T ReadRDRAM(size_t);
private:
friend struct Mem;
friend struct MMIO;
std::vector<u8> rdram{};
public:
DPC dpc{};
std::array<u32, COMMAND_BUFFER_SIZE> cmd_buf{};
};
} // namespace n64
+228
View File
@@ -0,0 +1,228 @@
#include <Core.hpp>
#include <log.hpp>
namespace n64 {
RSP::RSP() { Reset(); }
void RSP::Reset() {
lastSuccessfulSPAddr.raw = 0;
lastSuccessfulDRAMAddr.raw = 0;
spStatus.raw = 0;
spStatus.halt = true;
oldPC = 0;
pc = 0;
nextPC = 4;
spDMASPAddr.raw = 0;
spDMADRAMAddr.raw = 0;
spDMALen.raw = 0;
dmem = {};
imem = {};
memset(vpr, 0, 32 * sizeof(VPR));
memset(gpr, 0, 32 * sizeof(u32));
memset(&vce, 0, sizeof(VPR));
memset(&acc, 0, 3 * sizeof(VPR));
memset(&vcc, 0, 2 * sizeof(VPR));
memset(&vco, 0, 2 * sizeof(VPR));
semaphore = false;
divIn = 0;
divOut = 0;
divInLoaded = false;
steps = 0;
}
/*
FORCE_INLINE void logRSP(const RSP& rsp, const u32 instr) {
debug("{:04X} {:08X} ", rsp.oldPC, instr);
for (auto gpr : rsp.gpr) {
debug("{:08X} ", gpr);
}
for (auto vpr : rsp.vpr) {
for (int i = 0; i < 8; i++) {
debug("{:04X}", vpr.element[i]);
}
debug(" ");
}
for (int i = 0; i < 8; i++) {
debug("{:04X}", rsp.acc.h.element[i]);
}
debug(" ");
for (int i = 0; i < 8; i++) {
debug("{:04X}", rsp.acc.m.element[i]);
}
debug(" ");
for (int i = 0; i < 8; i++) {
debug("{:04X}", rsp.acc.l.element[i]);
}
debug(" {:04X} {:04X} {:02X}", rsp.GetVCC(), rsp.GetVCO(), rsp.GetVCE());
debug("DMEM: {:02X}{:02X}", rsp.dmem[0x3c4], rsp.dmem[0x3c5]);
}
*/
auto RSP::Read(const u32 addr) -> u32 {
switch (addr) {
case 0x04040000:
return lastSuccessfulSPAddr.raw & 0x1FF8;
case 0x04040004:
return lastSuccessfulDRAMAddr.raw & 0xFFFFF8;
case 0x04040008:
case 0x0404000C:
return spDMALen.raw;
case 0x04040010:
return spStatus.raw;
case 0x04040014:
return spStatus.dmaFull;
case 0x04040018:
return 0;
case 0x0404001C:
return AcquireSemaphore();
case 0x04080000:
return pc & 0xFFC;
default:
panic("Unimplemented SP register read {:08X}", addr);
}
}
void RSP::WriteStatus(const u32 value) {
Mem& mem = Core::GetMem();
Registers& regs = Core::GetRegs();
MI &mi = mem.mmio.mi;
const auto write = SPStatusWrite{.raw = value};
if (write.clearHalt && !write.setHalt) {
spStatus.halt = false;
}
if (write.setHalt && !write.clearHalt) {
regs.steps = 0;
spStatus.halt = true;
}
if (write.clearBroke)
spStatus.broke = false;
if (write.clearIntr && !write.setIntr)
mi.InterruptLower(MI::Interrupt::SP);
if (write.setIntr && !write.clearIntr)
mi.InterruptRaise(MI::Interrupt::SP);
#define CLEAR_SET(val, clear, set) \
do { \
if ((clear) && !(set)) \
(val) = 0; \
if ((set) && !(clear)) \
(val) = 1; \
} \
while (0)
CLEAR_SET(spStatus.singleStep, write.clearSstep, write.setSstep);
CLEAR_SET(spStatus.interruptOnBreak, write.clearIntrOnBreak, write.setIntrOnBreak);
CLEAR_SET(spStatus.signal0, write.clearSignal0, write.setSignal0);
CLEAR_SET(spStatus.signal1, write.clearSignal1, write.setSignal1);
CLEAR_SET(spStatus.signal2, write.clearSignal2, write.setSignal2);
CLEAR_SET(spStatus.signal3, write.clearSignal3, write.setSignal3);
CLEAR_SET(spStatus.signal4, write.clearSignal4, write.setSignal4);
CLEAR_SET(spStatus.signal5, write.clearSignal5, write.setSignal5);
CLEAR_SET(spStatus.signal6, write.clearSignal6, write.setSignal6);
CLEAR_SET(spStatus.signal7, write.clearSignal7, write.setSignal7);
#undef CLEAR_SET
}
template <>
void RSP::DMA<true>() {
Mem& mem = Core::GetMem();
u32 length = spDMALen.len + 1;
length = (length + 0x7) & ~0x7;
const auto &src = spDMASPAddr.bank ? imem : dmem;
u32 mem_address = spDMASPAddr.address & 0xFF8;
u32 dram_address = spDMADRAMAddr.address & 0xFFFFF8;
trace("SP DMA from RSP to RDRAM (size: {} B, {:08X} to {:08X})", length, mem_address, dram_address);
for (u32 i = 0; i < spDMALen.count + 1; i++) {
for (u32 j = 0; j < length; j++) {
mem.mmio.rdp.WriteRDRAM<u8>(BYTE_ADDRESS(dram_address + j), src[(mem_address + j) & DMEM_DSIZE]);
}
const int skip = i == spDMALen.count ? 0 : spDMALen.skip;
dram_address += (length + skip);
dram_address &= 0xFFFFF8;
mem_address += length;
mem_address &= 0xFF8;
}
trace("Addresses after: RSP: 0x{:08X}, Dram: 0x{:08X}", mem_address, dram_address);
lastSuccessfulSPAddr.address = mem_address;
lastSuccessfulSPAddr.bank = spDMASPAddr.bank;
lastSuccessfulDRAMAddr.address = dram_address;
spDMALen.raw = 0xFF8 | (spDMALen.skip << 20);
}
template <>
void RSP::DMA<false>() {
Mem& mem = Core::GetMem();
u32 length = spDMALen.len + 1;
length = (length + 0x7) & ~0x7;
auto &dst = spDMASPAddr.bank ? imem : dmem;
u32 mem_address = spDMASPAddr.address & 0xFF8;
u32 dram_address = spDMADRAMAddr.address & 0xFFFFF8;
trace("SP DMA from RDRAM to RSP (size: {} B, {:08X} to {:08X})", length, dram_address, mem_address);
for (u32 i = 0; i < spDMALen.count + 1; i++) {
for (u32 j = 0; j < length; j++) {
dst[(mem_address + j) & DMEM_DSIZE] = mem.mmio.rdp.ReadRDRAM<u8>(BYTE_ADDRESS(dram_address + j));
}
const int skip = i == spDMALen.count ? 0 : spDMALen.skip;
dram_address += (length + skip);
dram_address &= 0xFFFFF8;
mem_address += length;
mem_address &= 0xFF8;
}
trace("Addresses after: RSP: 0x{:08X}, Dram: 0x{:08X}", mem_address, dram_address);
lastSuccessfulSPAddr.address = mem_address;
lastSuccessfulSPAddr.bank = spDMASPAddr.bank;
lastSuccessfulDRAMAddr.address = dram_address;
spDMALen.raw = 0xFF8 | (spDMALen.skip << 20);
}
void RSP::Write(const u32 addr, const u32 val) {
switch (addr) {
case 0x04040000:
spDMASPAddr.raw = val & 0x1FF8;
break;
case 0x04040004:
spDMADRAMAddr.raw = val & 0xFFFFF8;
break;
case 0x04040008:
spDMALen.raw = val;
DMA<false>();
break;
case 0x0404000C:
spDMALen.raw = val;
DMA<true>();
break;
case 0x04040010:
WriteStatus(val);
break;
case 0x0404001C:
ReleaseSemaphore();
break;
case 0x04080000:
if (spStatus.halt) {
SetPC(val);
}
break;
default:
panic("Unimplemented SP register write {:08X}, val: {:08X}", addr, val);
}
}
} // namespace n64
+395
View File
@@ -0,0 +1,395 @@
#pragma once
#include <MemoryHelpers.hpp>
#include <MemoryRegions.hpp>
#include <array>
#include <core/RDP.hpp>
#include <core/mmio/MI.hpp>
#include <Instruction.hpp>
#define RSP_BYTE(addr) (dmem[BYTE_ADDRESS(addr) & 0xFFF])
#define GET_RSP_HALF(addr) ((RSP_BYTE(addr) << 8) | RSP_BYTE((addr) + 1))
#define SET_RSP_HALF(addr, value) \
do { \
RSP_BYTE(addr) = ((value) >> 8) & 0xFF; \
RSP_BYTE((addr) + 1) = (value) & 0xFF; \
} \
while (0)
#define GET_RSP_WORD(addr) ((GET_RSP_HALF(addr) << 16) | GET_RSP_HALF((addr) + 2))
#define SET_RSP_WORD(addr, value) \
do { \
SET_RSP_HALF(addr, ((value) >> 16) & 0xFFFF); \
SET_RSP_HALF((addr) + 2, (value) & 0xFFFF); \
} \
while (0)
namespace n64 {
union SPStatus {
u32 raw;
struct {
unsigned halt : 1;
unsigned broke : 1;
unsigned dmaBusy : 1;
unsigned dmaFull : 1;
unsigned ioFull : 1;
unsigned singleStep : 1;
unsigned interruptOnBreak : 1;
unsigned signal0 : 1;
unsigned signal1 : 1;
unsigned signal2 : 1;
unsigned signal3 : 1;
unsigned signal4 : 1;
unsigned signal5 : 1;
unsigned signal6 : 1;
unsigned signal7 : 1;
unsigned : 17;
};
};
union SPStatusWrite {
u32 raw;
struct {
unsigned clearHalt : 1;
unsigned setHalt : 1;
unsigned clearBroke : 1;
unsigned clearIntr : 1;
unsigned setIntr : 1;
unsigned clearSstep : 1;
unsigned setSstep : 1;
unsigned clearIntrOnBreak : 1;
unsigned setIntrOnBreak : 1;
unsigned clearSignal0 : 1;
unsigned setSignal0 : 1;
unsigned clearSignal1 : 1;
unsigned setSignal1 : 1;
unsigned clearSignal2 : 1;
unsigned setSignal2 : 1;
unsigned clearSignal3 : 1;
unsigned setSignal3 : 1;
unsigned clearSignal4 : 1;
unsigned setSignal4 : 1;
unsigned clearSignal5 : 1;
unsigned setSignal5 : 1;
unsigned clearSignal6 : 1;
unsigned setSignal6 : 1;
unsigned clearSignal7 : 1;
unsigned setSignal7 : 1;
unsigned : 7;
};
};
union SPDMALen {
struct {
unsigned len : 12;
unsigned count : 8;
unsigned skip : 12;
};
u32 raw;
};
union SPDMASPAddr {
struct {
unsigned address : 12;
unsigned bank : 1;
unsigned : 19;
};
u32 raw;
};
union SPDMADRAMAddr {
struct {
unsigned address : 24;
unsigned : 8;
};
u32 raw;
};
union VPR {
s16 selement[8];
u16 element[8];
u8 byte[16];
u32 word[4];
m128i single;
} __attribute__((packed));
static_assert(sizeof(VPR) == 16);
struct Mem;
struct Registers;
#define DE(x) (((x) >> 11) & 0x1F)
struct RSP {
bool divInLoaded = false;
bool semaphore = false;
std::array<u8, DMEM_SIZE> dmem{};
std::array<u8, IMEM_SIZE> imem{};
u16 oldPC{}, pc{}, nextPC{};
s16 divIn{}, divOut{};
u32 steps = 0;
SPStatus spStatus{};
SPDMASPAddr spDMASPAddr{};
SPDMADRAMAddr spDMADRAMAddr{};
SPDMASPAddr lastSuccessfulSPAddr{};
SPDMADRAMAddr lastSuccessfulDRAMAddr{};
SPDMALen spDMALen{};
s32 gpr[32]{};
VPR vpr[32]{};
VPR vte{};
VPR vce{};
struct {
VPR h{}, m{}, l{};
} acc;
struct {
VPR l{}, h{};
} vcc, vco;
RSP();
void Reset();
FORCE_INLINE void Step() {
gpr[0] = 0;
const u32 instr = Util::ReadAccess<u32>(imem, pc & IMEM_DSIZE);
oldPC = pc & 0xFFC;
pc = nextPC & 0xFFC;
nextPC += 4;
Exec(instr);
}
void SetVTE(const VPR &vt, u8 e);
auto Read(u32 addr) -> u32;
void Write(u32 addr, u32 val);
void Exec(Instruction instr);
FORCE_INLINE void SetPC(const u16 val) {
oldPC = pc & 0xFFC;
pc = val & 0xFFC;
nextPC = pc + 4;
}
[[nodiscard]] FORCE_INLINE s64 GetACC(const int e) const {
s64 val = u64(acc.h.element[e]) << 32;
val |= u64(acc.m.element[e]) << 16;
val |= u64(acc.l.element[e]) << 00;
if ((val & 0x0000800000000000) != 0) {
val |= 0xFFFF000000000000;
}
return val;
}
FORCE_INLINE void SetACC(const int e, const s64 val) {
acc.h.element[e] = val >> 32;
acc.m.element[e] = val >> 16;
acc.l.element[e] = val;
}
[[nodiscard]] FORCE_INLINE u16 GetVCO() const {
u16 value = 0;
for (int i = 0; i < 8; i++) {
const bool h = vco.h.element[7 - i] != 0;
const bool l = vco.l.element[7 - i] != 0;
const u32 mask = (l << i) | (h << (i + 8));
value |= mask;
}
return value;
}
[[nodiscard]] FORCE_INLINE u16 GetVCC() const {
u16 value = 0;
for (int i = 0; i < 8; i++) {
const bool h = vcc.h.element[7 - i] != 0;
const bool l = vcc.l.element[7 - i] != 0;
const u32 mask = (l << i) | (h << (i + 8));
value |= mask;
}
return value;
}
[[nodiscard]] FORCE_INLINE u8 GetVCE() const {
u8 value = 0;
for (int i = 0; i < 8; i++) {
const bool l = vce.element[ELEMENT_INDEX(i)] != 0;
value |= (l << i);
}
return value;
}
[[nodiscard]] FORCE_INLINE u32 ReadWord(u32 addr) const {
addr &= 0xfff;
return GET_RSP_WORD(addr);
}
FORCE_INLINE void WriteWord(u32 addr, const u32 val) {
addr &= 0xfff;
SET_RSP_WORD(addr, val);
}
[[nodiscard]] FORCE_INLINE u16 ReadHalf(u32 addr) const {
addr &= 0xfff;
return GET_RSP_HALF(addr);
}
FORCE_INLINE void WriteHalf(u32 addr, const u16 val) {
addr &= 0xfff;
SET_RSP_HALF(addr, val);
}
[[nodiscard]] FORCE_INLINE u8 ReadByte(u32 addr) const {
addr &= 0xfff;
return RSP_BYTE(addr);
}
FORCE_INLINE void WriteByte(u32 addr, const u8 val) {
addr &= 0xfff;
RSP_BYTE(addr) = val;
}
FORCE_INLINE bool AcquireSemaphore() {
if (semaphore) {
return true;
} else {
semaphore = true;
return false;
}
}
FORCE_INLINE void ReleaseSemaphore() { semaphore = false; }
void special(Instruction instr);
void regimm(Instruction instr);
void lwc2(Instruction instr);
void swc2(Instruction instr);
void cop2(Instruction instr);
void cop0(Instruction instr);
void add(Instruction instr);
void addi(Instruction instr);
void and_(Instruction instr);
void andi(Instruction instr);
void b(Instruction instr, bool cond);
void blink(Instruction instr, bool cond);
void cfc2(Instruction instr);
void ctc2(Instruction instr);
void lb(Instruction instr);
void lh(Instruction instr);
void lw(Instruction instr);
void lbu(Instruction instr);
void lhu(Instruction instr);
void lui(Instruction instr);
void luv(Instruction instr);
void lbv(Instruction instr);
void ldv(Instruction instr);
void lsv(Instruction instr);
void llv(Instruction instr);
void lrv(Instruction instr);
void lqv(Instruction instr);
void lfv(Instruction instr);
void lhv(Instruction instr);
void ltv(Instruction instr);
void lpv(Instruction instr);
void j(Instruction instr);
void jal(Instruction instr);
void jr(Instruction instr);
void jalr(Instruction instr);
void nor(Instruction instr);
void or_(Instruction instr);
void ori(Instruction instr);
void xor_(Instruction instr);
void xori(Instruction instr);
void sb(Instruction instr);
void sh(Instruction instr);
void sw(Instruction instr);
void swv(Instruction instr);
void sub(Instruction instr);
void sbv(Instruction instr);
void sdv(Instruction instr);
void stv(Instruction instr);
void sqv(Instruction instr);
void ssv(Instruction instr);
void suv(Instruction instr);
void slv(Instruction instr);
void shv(Instruction instr);
void sfv(Instruction instr);
void srv(Instruction instr);
void spv(Instruction instr);
void sllv(Instruction instr);
void srlv(Instruction instr);
void srav(Instruction instr);
void sll(Instruction instr);
void srl(Instruction instr);
void sra(Instruction instr);
void slt(Instruction instr);
void sltu(Instruction instr);
void slti(Instruction instr);
void sltiu(Instruction instr);
void vabs(Instruction instr);
void vadd(Instruction instr);
void vaddc(Instruction instr);
void vand(Instruction instr);
void vnand(Instruction instr);
void vch(Instruction instr);
void vcr(Instruction instr);
void vcl(Instruction instr);
void vmacf(Instruction instr);
void vmacu(Instruction instr);
void vmacq(Instruction instr);
void vmadh(Instruction instr);
void vmadl(Instruction instr);
void vmadm(Instruction instr);
void vmadn(Instruction instr);
void vmov(Instruction instr);
void vmulf(Instruction instr);
void vmulu(Instruction instr);
void vmulq(Instruction instr);
void vmudl(Instruction instr);
void vmudh(Instruction instr);
void vmudm(Instruction instr);
void vmudn(Instruction instr);
void vmrg(Instruction instr);
void vlt(Instruction instr);
void veq(Instruction instr);
void vne(Instruction instr);
void vge(Instruction instr);
void vrcp(Instruction instr);
void vrsq(Instruction instr);
void vrcpl(Instruction instr);
void vrsql(Instruction instr);
void vrndp(Instruction instr);
void vrndn(Instruction instr);
void vrcph(Instruction instr);
void vsar(Instruction instr);
void vsub(Instruction instr);
void vsubc(Instruction instr);
void vxor(Instruction instr);
void vnxor(Instruction instr);
void vor(Instruction instr);
void vnor(Instruction instr);
void vzero(Instruction instr);
void mfc0(const RDP &rdp, Instruction instr);
void mtc0(Instruction instr) const;
void mfc2(Instruction instr);
void mtc2(Instruction instr);
template <bool toRdram>
void DMA();
void WriteStatus(u32 value);
private:
FORCE_INLINE void branch(const u16 address, const bool cond) {
if (cond) {
nextPC = address & 0xFFC;
}
}
FORCE_INLINE void branch_likely(const u16 address, const bool cond) {
if (cond) {
nextPC = address & 0xFFC;
} else {
pc = nextPC & 0xFFC;
nextPC = pc + 4;
}
}
};
} // namespace n64
+41
View File
@@ -0,0 +1,41 @@
#pragma once
#include <common.hpp>
static constexpr u16 rsqRom[] = {
0xffff, 0xff00, 0xfe02, 0xfd06, 0xfc0b, 0xfb12, 0xfa1a, 0xf923, 0xf82e, 0xf73b, 0xf648, 0xf557, 0xf467, 0xf379,
0xf28c, 0xf1a0, 0xf0b6, 0xefcd, 0xeee5, 0xedff, 0xed19, 0xec35, 0xeb52, 0xea71, 0xe990, 0xe8b1, 0xe7d3, 0xe6f6,
0xe61b, 0xe540, 0xe467, 0xe38e, 0xe2b7, 0xe1e1, 0xe10d, 0xe039, 0xdf66, 0xde94, 0xddc4, 0xdcf4, 0xdc26, 0xdb59,
0xda8c, 0xd9c1, 0xd8f7, 0xd82d, 0xd765, 0xd69e, 0xd5d7, 0xd512, 0xd44e, 0xd38a, 0xd2c8, 0xd206, 0xd146, 0xd086,
0xcfc7, 0xcf0a, 0xce4d, 0xcd91, 0xccd6, 0xcc1b, 0xcb62, 0xcaa9, 0xc9f2, 0xc93b, 0xc885, 0xc7d0, 0xc71c, 0xc669,
0xc5b6, 0xc504, 0xc453, 0xc3a3, 0xc2f4, 0xc245, 0xc198, 0xc0eb, 0xc03f, 0xbf93, 0xbee9, 0xbe3f, 0xbd96, 0xbced,
0xbc46, 0xbb9f, 0xbaf8, 0xba53, 0xb9ae, 0xb90a, 0xb867, 0xb7c5, 0xb723, 0xb681, 0xb5e1, 0xb541, 0xb4a2, 0xb404,
0xb366, 0xb2c9, 0xb22c, 0xb191, 0xb0f5, 0xb05b, 0xafc1, 0xaf28, 0xae8f, 0xadf7, 0xad60, 0xacc9, 0xac33, 0xab9e,
0xab09, 0xaa75, 0xa9e1, 0xa94e, 0xa8bc, 0xa82a, 0xa799, 0xa708, 0xa678, 0xa5e8, 0xa559, 0xa4cb, 0xa43d, 0xa3b0,
0xa323, 0xa297, 0xa20b, 0xa180, 0xa0f6, 0xa06c, 0x9fe2, 0x9f59, 0x9ed1, 0x9e49, 0x9dc2, 0x9d3b, 0x9cb4, 0x9c2f,
0x9ba9, 0x9b25, 0x9aa0, 0x9a1c, 0x9999, 0x9916, 0x9894, 0x9812, 0x9791, 0x9710, 0x968f, 0x960f, 0x9590, 0x9511,
0x9492, 0x9414, 0x9397, 0x931a, 0x929d, 0x9221, 0x91a5, 0x9129, 0x90af, 0x9034, 0x8fba, 0x8f40, 0x8ec7, 0x8e4f,
0x8dd6, 0x8d5e, 0x8ce7, 0x8c70, 0x8bf9, 0x8b83, 0x8b0d, 0x8a98, 0x8a23, 0x89ae, 0x893a, 0x88c6, 0x8853, 0x87e0,
0x876d, 0x86fb, 0x8689, 0x8618, 0x85a7, 0x8536, 0x84c6, 0x8456, 0x83e7, 0x8377, 0x8309, 0x829a, 0x822c, 0x81bf,
0x8151, 0x80e4, 0x8078, 0x800c, 0x7fa0, 0x7f34, 0x7ec9, 0x7e5e, 0x7df4, 0x7d8a, 0x7d20, 0x7cb6, 0x7c4d, 0x7be5,
0x7b7c, 0x7b14, 0x7aac, 0x7a45, 0x79de, 0x7977, 0x7911, 0x78ab, 0x7845, 0x77df, 0x777a, 0x7715, 0x76b1, 0x764d,
0x75e9, 0x7585, 0x7522, 0x74bf, 0x745d, 0x73fa, 0x7398, 0x7337, 0x72d5, 0x7274, 0x7213, 0x71b3, 0x7152, 0x70f2,
0x7093, 0x7033, 0x6fd4, 0x6f76, 0x6f17, 0x6eb9, 0x6e5b, 0x6dfd, 0x6da0, 0x6d43, 0x6ce6, 0x6c8a, 0x6c2d, 0x6bd1,
0x6b76, 0x6b1a, 0x6abf, 0x6a64, 0x6a09, 0x6955, 0x68a1, 0x67ef, 0x673e, 0x668d, 0x65de, 0x6530, 0x6482, 0x63d6,
0x632b, 0x6280, 0x61d7, 0x612e, 0x6087, 0x5fe0, 0x5f3a, 0x5e95, 0x5df1, 0x5d4e, 0x5cac, 0x5c0b, 0x5b6b, 0x5acb,
0x5a2c, 0x598f, 0x58f2, 0x5855, 0x57ba, 0x5720, 0x5686, 0x55ed, 0x5555, 0x54be, 0x5427, 0x5391, 0x52fc, 0x5268,
0x51d5, 0x5142, 0x50b0, 0x501f, 0x4f8e, 0x4efe, 0x4e6f, 0x4de1, 0x4d53, 0x4cc6, 0x4c3a, 0x4baf, 0x4b24, 0x4a9a,
0x4a10, 0x4987, 0x48ff, 0x4878, 0x47f1, 0x476b, 0x46e5, 0x4660, 0x45dc, 0x4558, 0x44d5, 0x4453, 0x43d1, 0x434f,
0x42cf, 0x424f, 0x41cf, 0x4151, 0x40d2, 0x4055, 0x3fd8, 0x3f5b, 0x3edf, 0x3e64, 0x3de9, 0x3d6e, 0x3cf5, 0x3c7c,
0x3c03, 0x3b8b, 0x3b13, 0x3a9c, 0x3a26, 0x39b0, 0x393a, 0x38c5, 0x3851, 0x37dd, 0x3769, 0x36f6, 0x3684, 0x3612,
0x35a0, 0x352f, 0x34bf, 0x344f, 0x33df, 0x3370, 0x3302, 0x3293, 0x3226, 0x31b9, 0x314c, 0x30df, 0x3074, 0x3008,
0x2f9d, 0x2f33, 0x2ec8, 0x2e5f, 0x2df6, 0x2d8d, 0x2d24, 0x2cbc, 0x2c55, 0x2bee, 0x2b87, 0x2b21, 0x2abb, 0x2a55,
0x29f0, 0x298b, 0x2927, 0x28c3, 0x2860, 0x27fd, 0x279a, 0x2738, 0x26d6, 0x2674, 0x2613, 0x25b2, 0x2552, 0x24f2,
0x2492, 0x2432, 0x23d3, 0x2375, 0x2317, 0x22b9, 0x225b, 0x21fe, 0x21a1, 0x2145, 0x20e8, 0x208d, 0x2031, 0x1fd6,
0x1f7b, 0x1f21, 0x1ec7, 0x1e6d, 0x1e13, 0x1dba, 0x1d61, 0x1d09, 0x1cb1, 0x1c59, 0x1c01, 0x1baa, 0x1b53, 0x1afc,
0x1aa6, 0x1a50, 0x19fa, 0x19a5, 0x1950, 0x18fb, 0x18a7, 0x1853, 0x17ff, 0x17ab, 0x1758, 0x1705, 0x16b2, 0x1660,
0x160d, 0x15bc, 0x156a, 0x1519, 0x14c8, 0x1477, 0x1426, 0x13d6, 0x1386, 0x1337, 0x12e7, 0x1298, 0x1249, 0x11fb,
0x11ac, 0x115e, 0x1111, 0x10c3, 0x1076, 0x1029, 0x0fdc, 0x0f8f, 0x0f43, 0x0ef7, 0x0eab, 0x0e60, 0x0e15, 0x0dca,
0x0d7f, 0x0d34, 0x0cea, 0x0ca0, 0x0c56, 0x0c0c, 0x0bc3, 0x0b7a, 0x0b31, 0x0ae8, 0x0aa0, 0x0a58, 0x0a10, 0x09c8,
0x0981, 0x0939, 0x08f2, 0x08ab, 0x0865, 0x081e, 0x07d8, 0x0792, 0x074d, 0x0707, 0x06c2, 0x067d, 0x0638, 0x05f3,
0x05af, 0x056a, 0x0526, 0x04e2, 0x049f, 0x045b, 0x0418, 0x03d5, 0x0392, 0x0350, 0x030d, 0x02cb, 0x0289, 0x0247,
0x0206, 0x01c4, 0x0183, 0x0142, 0x0101, 0x00c0, 0x0080, 0x0040};
@@ -0,0 +1,4 @@
file(GLOB_RECURSE SOURCES *.cpp)
file(GLOB_RECURSE HEADERS *.hpp)
add_library(interpreter ${SOURCES} ${HEADERS})
@@ -0,0 +1,93 @@
#include <Core.hpp>
#include <log.hpp>
#include <ranges>
namespace n64 {
void Cop0::mtc0(const Instruction instr) {
Registers& regs = Core::GetRegs();
SetReg32(instr.rd(), regs.Read<u32>(instr.rt()));
}
void Cop0::dmtc0(const Instruction instr) {
Registers& regs = Core::GetRegs();
SetReg64(instr.rd(), regs.Read<u64>(instr.rt()));
}
void Cop0::mfc0(const Instruction instr) {
Registers& regs = Core::GetRegs();
regs.Write(instr.rt(), s32(GetReg32(instr.rd())));
}
void Cop0::dmfc0(const Instruction instr) const {
Registers& regs = Core::GetRegs();
regs.Write(instr.rt(), s64(GetReg64(instr.rd())));
}
void Cop0::eret() {
Registers& regs = Core::GetRegs();
if (!regs.cop0.kernelMode) {
FireException(ExceptionCode::CoprocessorUnusable, 0, regs.oldPC);
return;
}
if (status.erl) {
regs.SetPC64(ErrorEPC);
status.erl = false;
} else {
regs.SetPC64(EPC);
status.exl = false;
}
regs.cop0.Update();
llbit = false;
}
void Cop0::tlbr() {
if (index.i >= 32) {
panic("TLBR with TLB index {}", index.i);
}
const TLBEntry entry = tlb[index.i];
entryHi.raw = entry.entryHi.raw;
entryLo0.raw = entry.entryLo0.raw & 0x3FFFFFFF;
entryLo1.raw = entry.entryLo1.raw & 0x3FFFFFFF;
entryLo0.g = entry.global;
entryLo1.g = entry.global;
pageMask.raw = entry.pageMask.raw;
}
void Cop0::tlbw(const int index_) {
PageMask page_mask{};
page_mask = pageMask;
const u32 top = page_mask.mask & 0xAAA;
page_mask.mask = top | (top >> 1);
if (index_ >= 32) {
panic("TLBWI with TLB index {}", index_);
}
tlb[index_].entryHi.raw = entryHi.raw;
tlb[index_].entryHi.vpn2 &= ~page_mask.mask;
tlb[index_].entryLo0.raw = entryLo0.raw & 0x03FFFFFE;
tlb[index_].entryLo1.raw = entryLo1.raw & 0x03FFFFFE;
tlb[index_].pageMask.raw = page_mask.raw;
tlb[index_].global = entryLo0.g && entryLo1.g;
tlb[index_].initialized = true;
}
void Cop0::tlbp() {
int match = -1;
const TLBEntry *entry = TLBTryMatch(entryHi.raw, match);
if (match >= 0) {
index.raw = match;
return;
}
index.raw = 0;
index.p = 1;
}
} // namespace n64
File diff suppressed because it is too large Load Diff
+447
View File
@@ -0,0 +1,447 @@
#include <Core.hpp>
#include <log.hpp>
#include <Instruction.hpp>
namespace n64 {
void Interpreter::special(const Instruction instr) {
// 00rr_rccc
switch (instr.special()) {
case Instruction::SLL:
if (instr.instr.raw != 0) {
sll(instr);
}
break;
case Instruction::SRL:
srl(instr);
break;
case Instruction::SRA:
sra(instr);
break;
case Instruction::SLLV:
sllv(instr);
break;
case Instruction::SRLV:
srlv(instr);
break;
case Instruction::SRAV:
srav(instr);
break;
case Instruction::JR:
jr(instr);
break;
case Instruction::JALR:
jalr(instr);
break;
case Instruction::SYSCALL:
regs.cop0.FireException(ExceptionCode::Syscall, 0, regs.oldPC);
break;
case Instruction::BREAK:
regs.cop0.FireException(ExceptionCode::Breakpoint, 0, regs.oldPC);
break;
case Instruction::SYNC:
break; // SYNC
case Instruction::MFHI:
mfhi(instr);
break;
case Instruction::MTHI:
mthi(instr);
break;
case Instruction::MFLO:
mflo(instr);
break;
case Instruction::MTLO:
mtlo(instr);
break;
case Instruction::DSLLV:
dsllv(instr);
break;
case Instruction::DSRLV:
dsrlv(instr);
break;
case Instruction::DSRAV:
dsrav(instr);
break;
case Instruction::MULT:
mult(instr);
break;
case Instruction::MULTU:
multu(instr);
break;
case Instruction::DIV:
div(instr);
break;
case Instruction::DIVU:
divu(instr);
break;
case Instruction::DMULT:
dmult(instr);
break;
case Instruction::DMULTU:
dmultu(instr);
break;
case Instruction::DDIV:
ddiv(instr);
break;
case Instruction::DDIVU:
ddivu(instr);
break;
case Instruction::ADD:
add(instr);
break;
case Instruction::ADDU:
addu(instr);
break;
case Instruction::SUB:
sub(instr);
break;
case Instruction::SUBU:
subu(instr);
break;
case Instruction::AND:
and_(instr);
break;
case Instruction::OR:
or_(instr);
break;
case Instruction::XOR:
xor_(instr);
break;
case Instruction::NOR:
nor(instr);
break;
case Instruction::SLT:
slt(instr);
break;
case Instruction::SLTU:
sltu(instr);
break;
case Instruction::DADD:
dadd(instr);
break;
case Instruction::DADDU:
daddu(instr);
break;
case Instruction::DSUB:
dsub(instr);
break;
case Instruction::DSUBU:
dsubu(instr);
break;
case Instruction::TGE:
trap(regs.Read<s64>(instr.rs()) >= regs.Read<s64>(instr.rt()));
break;
case Instruction::TGEU:
trap(regs.Read<u64>(instr.rs()) >= regs.Read<u64>(instr.rt()));
break;
case Instruction::TLT:
trap(regs.Read<s64>(instr.rs()) < regs.Read<s64>(instr.rt()));
break;
case Instruction::TLTU:
trap(regs.Read<u64>(instr.rs()) < regs.Read<u64>(instr.rt()));
break;
case Instruction::TEQ:
trap(regs.Read<s64>(instr.rs()) == regs.Read<s64>(instr.rt()));
break;
case Instruction::TNE:
trap(regs.Read<s64>(instr.rs()) != regs.Read<s64>(instr.rt()));
break;
case Instruction::DSLL:
dsll(instr);
break;
case Instruction::DSRL:
dsrl(instr);
break;
case Instruction::DSRA:
dsra(instr);
break;
case Instruction::DSLL32:
dsll32(instr);
break;
case Instruction::DSRL32:
dsrl32(instr);
break;
case Instruction::DSRA32:
dsra32(instr);
break;
default:
panic("Unimplemented special {} {} ({:08X}) (pc: {:016X})", instr.instr.opcode.special_hi, instr.instr.opcode.special_lo, instr.instr.raw,
static_cast<u64>(regs.oldPC));
}
}
void Interpreter::regimm(const Instruction instr) {
// 000r_rccc
switch (instr.regimm()) {
case Instruction::BLTZ:
b(instr, regs.Read<s64>(instr.rs()) < 0);
break;
case Instruction::BGEZ:
b(instr, regs.Read<s64>(instr.rs()) >= 0);
break;
case Instruction::BLTZL:
bl(instr, regs.Read<s64>(instr.rs()) < 0);
break;
case Instruction::BGEZL:
bl(instr, regs.Read<s64>(instr.rs()) >= 0);
break;
case Instruction::TGEI:
trap(regs.Read<s64>(instr.rs()) >= static_cast<s64>(static_cast<s16>(instr.instr.itype.imm)));
break;
case Instruction::TGEIU:
trap(regs.Read<u64>(instr.rs()) >= static_cast<u64>(static_cast<s64>(static_cast<s16>(instr.instr.itype.imm))));
break;
case Instruction::TLTI:
trap(regs.Read<s64>(instr.rs()) < static_cast<s64>(static_cast<s16>(instr.instr.itype.imm)));
break;
case Instruction::TLTIU:
trap(regs.Read<u64>(instr.rs()) < static_cast<u64>(static_cast<s64>(static_cast<s16>(instr.instr.itype.imm))));
break;
case Instruction::TEQI:
trap(regs.Read<s64>(instr.rs()) == static_cast<s64>(static_cast<s16>(instr.instr.itype.imm)));
break;
case Instruction::TNEI:
trap(regs.Read<s64>(instr.rs()) != static_cast<s64>(static_cast<s16>(instr.instr.itype.imm)));
break;
case Instruction::BLTZAL:
blink(instr, regs.Read<s64>(instr.rs()) < 0);
break;
case Instruction::BGEZAL:
blink(instr, regs.Read<s64>(instr.rs()) >= 0);
break;
case Instruction::BLTZALL:
bllink(instr, regs.Read<s64>(instr.rs()) < 0);
break;
case Instruction::BGEZALL:
bllink(instr, regs.Read<s64>(instr.rs()) >= 0);
break;
default:
panic("Unimplemented regimm {} {} ({:08X}) (pc: {:016X})", instr.instr.opcode.regimm_hi,
instr.instr.opcode.regimm_lo, u32(instr), static_cast<u64>(regs.oldPC));
}
}
void Interpreter::cop2Decode(const Instruction instr) {
if (!regs.cop0.status.cu2) {
regs.cop0.FireException(ExceptionCode::CoprocessorUnusable, 2, regs.oldPC);
return;
}
switch (instr.rs()) {
case 0x00:
mfc2(instr);
break;
case 0x01:
dmfc2(instr);
break;
case 0x02:
cfc2(instr);
break;
case 0x04:
mtc2(instr);
break;
case 0x05:
dmtc2(instr);
break;
case 0x06:
ctc2(instr);
break;
default:
regs.cop0.FireException(ExceptionCode::ReservedInstruction, 2, regs.oldPC);
}
}
void Interpreter::Exec(const Instruction instr) {
// 00rr_rccc
switch (instr.opcode()) {
case Instruction::SPECIAL:
special(instr);
break;
case Instruction::REGIMM:
regimm(instr);
break;
case Instruction::J:
j(instr);
break;
case Instruction::JAL:
jal(instr);
break;
case Instruction::BEQ:
b(instr, regs.Read<s64>(instr.rs()) == regs.Read<s64>(instr.rt()));
break;
case Instruction::BNE:
b(instr, regs.Read<s64>(instr.rs()) != regs.Read<s64>(instr.rt()));
break;
case Instruction::BLEZ:
b(instr, regs.Read<s64>(instr.rs()) <= 0);
break;
case Instruction::BGTZ:
b(instr, regs.Read<s64>(instr.rs()) > 0);
break;
case Instruction::ADDI:
addi(instr);
break;
case Instruction::ADDIU:
addiu(instr);
break;
case Instruction::SLTI:
slti(instr);
break;
case Instruction::SLTIU:
sltiu(instr);
break;
case Instruction::ANDI:
andi(instr);
break;
case Instruction::ORI:
ori(instr);
break;
case Instruction::XORI:
xori(instr);
break;
case Instruction::LUI:
lui(instr);
break;
case Instruction::COP0:
regs.cop0.decode(instr);
break;
case Instruction::COP1:
if(instr.cop_rs() == 0x08) {
switch (instr.cop_rt()) {
case 0:
if (!regs.cop1.CheckFPUUsable())
return;
b(instr, !regs.cop1.fcr31.compare);
break;
case 1:
if (!regs.cop1.CheckFPUUsable())
return;
b(instr, regs.cop1.fcr31.compare);
break;
case 2:
if (!regs.cop1.CheckFPUUsable())
return;
bl(instr, !regs.cop1.fcr31.compare);
break;
case 3:
if (!regs.cop1.CheckFPUUsable())
return;
bl(instr, regs.cop1.fcr31.compare);
break;
default:
panic("Undefined BC COP1 {:02X}", instr.cop_rt());
}
return;
}
regs.cop1.decode(instr);
break;
case Instruction::COP2:
cop2Decode(instr);
break;
case Instruction::BEQL:
bl(instr, regs.Read<s64>(instr.rs()) == regs.Read<s64>(instr.rt()));
break;
case Instruction::BNEL:
bl(instr, regs.Read<s64>(instr.rs()) != regs.Read<s64>(instr.rt()));
break;
case Instruction::BLEZL:
bl(instr, regs.Read<s64>(instr.rs()) <= 0);
break;
case Instruction::BGTZL:
bl(instr, regs.Read<s64>(instr.rs()) > 0);
break;
case Instruction::DADDI:
daddi(instr);
break;
case Instruction::DADDIU:
daddiu(instr);
break;
case Instruction::LDL:
ldl(instr);
break;
case Instruction::LDR:
ldr(instr);
break;
case 0x1F:
regs.cop0.FireException(ExceptionCode::ReservedInstruction, 0, regs.oldPC);
break;
case Instruction::LB:
lb(instr);
break;
case Instruction::LH:
lh(instr);
break;
case Instruction::LWL:
lwl(instr);
break;
case Instruction::LW:
lw(instr);
break;
case Instruction::LBU:
lbu(instr);
break;
case Instruction::LHU:
lhu(instr);
break;
case Instruction::LWR:
lwr(instr);
break;
case Instruction::LWU:
lwu(instr);
break;
case Instruction::SB:
sb(instr);
break;
case Instruction::SH:
sh(instr);
break;
case Instruction::SWL:
swl(instr);
break;
case Instruction::SW:
sw(instr);
break;
case Instruction::SDL:
sdl(instr);
break;
case Instruction::SDR:
sdr(instr);
break;
case Instruction::SWR:
swr(instr);
break;
case Instruction::CACHE:
break; // CACHE
case Instruction::LL:
ll(instr);
break;
case Instruction::LWC1:
regs.cop1.lwc1(instr);
break;
case Instruction::LLD:
lld(instr);
break;
case Instruction::LDC1:
regs.cop1.ldc1(instr);
break;
case Instruction::LD:
ld(instr);
break;
case Instruction::SC:
sc(instr);
break;
case Instruction::SWC1:
regs.cop1.swc1(instr);
break;
case Instruction::SCD:
scd(instr);
break;
case Instruction::SDC1:
regs.cop1.sdc1(instr);
break;
case Instruction::SD:
sd(instr);
break;
default:
panic("Unimplemented instruction {:02X} ({:08X}) (pc: {:016X})", instr.instr.opcode.op, u32(instr), static_cast<u64>(regs.oldPC));
}
}
} // namespace n64
@@ -0,0 +1,847 @@
#include <Core.hpp>
#define check_signed_overflow(op1, op2, res) (((~((op1) ^ (op2)) & ((op1) ^ (res))) >> ((sizeof(res) * 8) - 1)) & 1)
#define check_signed_underflow(op1, op2, res) (((((op1) ^ (op2)) & ((op1) ^ (res))) >> ((sizeof(res) * 8) - 1)) & 1)
namespace n64 {
void Interpreter::add(const Instruction instr) {
const u32 rs = regs.Read<s32>(instr.rs());
const u32 rt = regs.Read<s32>(instr.rt());
if (const u32 result = rs + rt; check_signed_overflow(rs, rt, result)) {
regs.cop0.FireException(ExceptionCode::Overflow, 0, regs.oldPC);
} else {
regs.Write(instr.rd(), static_cast<s32>(result));
}
}
void Interpreter::addu(const Instruction instr) {
const s32 rs = regs.Read<s32>(instr.rs());
const s32 rt = regs.Read<s32>(instr.rt());
const s32 result = rs + rt;
regs.Write(instr.rd(), result);
}
void Interpreter::addi(const Instruction instr) {
const u32 rs = regs.Read<s64>(instr.rs());
const u32 imm = static_cast<s32>(static_cast<s16>(instr));
if (const u32 result = rs + imm; check_signed_overflow(rs, imm, result)) {
regs.cop0.FireException(ExceptionCode::Overflow, 0, regs.oldPC);
} else {
regs.Write(instr.rt(), static_cast<s32>(result));
}
}
void Interpreter::addiu(const Instruction instr) {
const s32 rs = regs.Read<s32>(instr.rs());
const s16 imm = static_cast<s16>(instr);
const s32 result = rs + imm;
regs.Write(instr.rt(), result);
}
void Interpreter::dadd(const Instruction instr) {
const u64 rs = regs.Read<s64>(instr.rs());
const u64 rt = regs.Read<s64>(instr.rt());
if (const u64 result = rt + rs; check_signed_overflow(rs, rt, result)) {
regs.cop0.FireException(ExceptionCode::Overflow, 0, regs.oldPC);
} else {
regs.Write(instr.rd(), result);
}
}
void Interpreter::daddu(const Instruction instr) {
const s64 rs = regs.Read<s64>(instr.rs());
const s64 rt = regs.Read<s64>(instr.rt());
regs.Write(instr.rd(), rs + rt);
}
void Interpreter::daddi(const Instruction instr) {
const u64 imm = s64(s16(instr));
const u64 rs = regs.Read<s64>(instr.rs());
if (const u64 result = imm + rs; check_signed_overflow(rs, imm, result)) {
regs.cop0.FireException(ExceptionCode::Overflow, 0, regs.oldPC);
} else {
regs.Write(instr.rt(), result);
}
}
void Interpreter::daddiu(const Instruction instr) {
const s16 imm = static_cast<s16>(instr);
const s64 rs = regs.Read<s64>(instr.rs());
regs.Write(instr.rt(), rs + imm);
}
void Interpreter::div(const Instruction instr) {
const s64 dividend = regs.Read<s32>(instr.rs());
if (const s64 divisor = regs.Read<s32>(instr.rt()); divisor == 0) {
regs.hi = dividend;
if (dividend >= 0) {
regs.lo = static_cast<s64>(-1);
} else {
regs.lo = static_cast<s64>(1);
}
} else {
const s32 quotient = dividend / divisor;
const s32 remainder = dividend % divisor;
regs.lo = quotient;
regs.hi = remainder;
}
}
void Interpreter::divu(const Instruction instr) {
const u32 dividend = regs.Read<s64>(instr.rs());
if (const u32 divisor = regs.Read<s64>(instr.rt()); divisor == 0) {
regs.lo = -1;
regs.hi = (s32)dividend;
} else {
const s32 quotient = (s32)(dividend / divisor);
const s32 remainder = (s32)(dividend % divisor);
regs.lo = quotient;
regs.hi = remainder;
}
}
void Interpreter::ddiv(const Instruction instr) {
const s64 dividend = regs.Read<s64>(instr.rs());
const s64 divisor = regs.Read<s64>(instr.rt());
if (dividend == 0x8000000000000000 && divisor == 0xFFFFFFFFFFFFFFFF) {
regs.lo = dividend;
regs.hi = 0;
} else if (divisor == 0) {
regs.hi = dividend;
if (dividend >= 0) {
regs.lo = -1;
} else {
regs.lo = 1;
}
} else {
const s64 quotient = dividend / divisor;
const s64 remainder = dividend % divisor;
regs.lo = quotient;
regs.hi = remainder;
}
}
void Interpreter::ddivu(const Instruction instr) {
const u64 dividend = regs.Read<s64>(instr.rs());
const u64 divisor = regs.Read<s64>(instr.rt());
if (divisor == 0) {
regs.lo = -1;
regs.hi = (s64)dividend;
} else {
const u64 quotient = dividend / divisor;
const u64 remainder = dividend % divisor;
regs.lo = (s64)quotient;
regs.hi = (s64)remainder;
}
}
void Interpreter::branch(const bool cond, const s64 address) {
regs.delaySlot = true;
if (cond) {
regs.nextPC = address;
}
}
void Interpreter::branch_likely(const bool cond, const s64 address) {
if (cond) {
regs.delaySlot = true;
regs.nextPC = address;
} else {
regs.SetPC64(regs.nextPC);
}
}
void Interpreter::b(const Instruction instr, const bool cond) {
const s16 imm = instr;
const s64 offset = u64((s64)imm) << 2;
const s64 address = regs.pc + offset;
branch(cond, address);
}
void Interpreter::blink(const Instruction instr, const bool cond) {
regs.Write(31, regs.nextPC);
const s16 imm = instr;
const s64 offset = u64((s64)imm) << 2;
const s64 address = regs.pc + offset;
branch(cond, address);
}
void Interpreter::bl(const Instruction instr, const bool cond) {
const s16 imm = instr;
const s64 offset = u64((s64)imm) << 2;
const s64 address = regs.pc + offset;
branch_likely(cond, address);
}
void Interpreter::bllink(const Instruction instr, const bool cond) {
regs.Write(31, regs.nextPC);
const s16 imm = instr;
const s64 offset = u64((s64)imm) << 2;
const s64 address = regs.pc + offset;
branch_likely(cond, address);
}
void Interpreter::lui(const Instruction instr) {
u64 val = s64((s16)instr);
val <<= 16;
regs.Write(instr.rt(), val);
}
void Interpreter::lb(const Instruction instr) {
const u64 address = regs.Read<s64>(instr.rs()) + (s16)instr;
if (u32 paddr = 0; !regs.cop0.MapVAddr(Cop0::LOAD, address, paddr)) {
regs.cop0.HandleTLBException(address);
regs.cop0.FireException(Cop0::GetTLBExceptionCode(regs.cop0.tlbError, Cop0::LOAD), 0, regs.oldPC);
} else {
regs.Write(instr.rt(), (s8)mem.Read<u8>(paddr));
}
}
void Interpreter::lh(const Instruction instr) {
const u64 address = regs.Read<s64>(instr.rs()) + (s16)instr;
if (check_address_error(0b1, address)) {
regs.cop0.HandleTLBException(address);
regs.cop0.FireException(ExceptionCode::AddressErrorLoad, 0, regs.oldPC);
return;
}
u32 paddr = 0;
if (!regs.cop0.MapVAddr(Cop0::LOAD, address, paddr)) {
regs.cop0.HandleTLBException(address);
regs.cop0.FireException(Cop0::GetTLBExceptionCode(regs.cop0.tlbError, Cop0::LOAD), 0, regs.oldPC);
} else {
regs.Write(instr.rt(), (s16)mem.Read<u16>(paddr));
}
}
void Interpreter::lw(const Instruction instr) {
const s16 offset = instr;
const u64 address = regs.Read<s64>(instr.rs()) + offset;
if (check_address_error(0b11, address)) {
regs.cop0.HandleTLBException(address);
regs.cop0.FireException(ExceptionCode::AddressErrorLoad, 0, regs.oldPC);
return;
}
u32 physical = 0;
if (!regs.cop0.MapVAddr(Cop0::LOAD, address, physical)) {
regs.cop0.HandleTLBException(address);
regs.cop0.FireException(Cop0::GetTLBExceptionCode(regs.cop0.tlbError, Cop0::LOAD), 0, regs.oldPC);
} else {
regs.Write(instr.rt(), (s32)mem.Read<u32>(physical));
}
}
void Interpreter::ll(const Instruction instr) {
const u64 address = regs.Read<s64>(instr.rs()) + (s16)instr;
u32 physical;
if (!regs.cop0.MapVAddr(Cop0::LOAD, address, physical)) {
regs.cop0.HandleTLBException(address);
regs.cop0.FireException(Cop0::GetTLBExceptionCode(regs.cop0.tlbError, Cop0::LOAD), 0, regs.oldPC);
} else {
const s32 result = mem.Read<u32>(physical);
if (check_address_error(0b11, address)) {
regs.cop0.FireException(ExceptionCode::AddressErrorLoad, 0, regs.oldPC);
return;
}
regs.Write(instr.rt(), result);
regs.cop0.llbit = true;
regs.cop0.LLAddr = physical >> 4;
}
}
void Interpreter::lwl(const Instruction instr) {
const u64 address = regs.Read<s64>(instr.rs()) + (s16)instr;
u32 paddr = 0;
if (!regs.cop0.MapVAddr(Cop0::LOAD, address, paddr)) {
regs.cop0.HandleTLBException(address);
regs.cop0.FireException(Cop0::GetTLBExceptionCode(regs.cop0.tlbError, Cop0::LOAD), 0, regs.oldPC);
} else {
const u32 shift = 8 * ((address ^ 0) & 3);
const u32 mask = 0xFFFFFFFF << shift;
const u32 data = mem.Read<u32>(paddr & ~3);
const s32 result = s32((regs.Read<s64>(instr.rt()) & ~mask) | (data << shift));
regs.Write(instr.rt(), result);
}
}
void Interpreter::lwr(const Instruction instr) {
const u64 address = regs.Read<s64>(instr.rs()) + (s16)instr;
u32 paddr = 0;
if (!regs.cop0.MapVAddr(Cop0::LOAD, address, paddr)) {
regs.cop0.HandleTLBException(address);
regs.cop0.FireException(Cop0::GetTLBExceptionCode(regs.cop0.tlbError, Cop0::LOAD), 0, regs.oldPC);
} else {
const u32 shift = 8 * ((address ^ 3) & 3);
const u32 mask = 0xFFFFFFFF >> shift;
const u32 data = mem.Read<u32>(paddr & ~3);
const s32 result = s32((regs.Read<s64>(instr.rt()) & ~mask) | (data >> shift));
regs.Write(instr.rt(), result);
}
}
void Interpreter::ld(const Instruction instr) {
const s64 address = regs.Read<s64>(instr.rs()) + (s16)instr;
if (check_address_error(0b111, address)) {
regs.cop0.HandleTLBException(address);
regs.cop0.FireException(ExceptionCode::AddressErrorLoad, 0, regs.oldPC);
return;
}
u32 paddr = 0;
if (!regs.cop0.MapVAddr(Cop0::LOAD, address, paddr)) {
regs.cop0.HandleTLBException(address);
regs.cop0.FireException(Cop0::GetTLBExceptionCode(regs.cop0.tlbError, Cop0::LOAD), 0, regs.oldPC);
} else {
const s64 value = mem.Read<u64>(paddr);
regs.Write(instr.rt(), value);
}
}
void Interpreter::lld(const Instruction instr) {
if (!regs.cop0.is64BitAddressing && !regs.cop0.kernelMode) {
regs.cop0.FireException(ExceptionCode::ReservedInstruction, 0, regs.oldPC);
return;
}
const u64 address = regs.Read<s64>(instr.rs()) + (s16)instr;
u32 paddr;
if (!regs.cop0.MapVAddr(Cop0::LOAD, address, paddr)) {
regs.cop0.HandleTLBException(address);
regs.cop0.FireException(Cop0::GetTLBExceptionCode(regs.cop0.tlbError, Cop0::LOAD), 0, regs.oldPC);
} else {
if (check_address_error(0b111, address)) {
regs.cop0.FireException(ExceptionCode::AddressErrorLoad, 0, regs.oldPC);
} else {
regs.Write(instr.rt(), mem.Read<u64>(paddr));
regs.cop0.llbit = true;
regs.cop0.LLAddr = paddr >> 4;
}
}
}
void Interpreter::ldl(const Instruction instr) {
const u64 address = regs.Read<s64>(instr.rs()) + (s16)instr;
u32 paddr = 0;
if (!regs.cop0.MapVAddr(Cop0::LOAD, address, paddr)) {
regs.cop0.HandleTLBException(address);
regs.cop0.FireException(Cop0::GetTLBExceptionCode(regs.cop0.tlbError, Cop0::LOAD), 0, regs.oldPC);
} else {
const s32 shift = 8 * ((address ^ 0) & 7);
const u64 mask = 0xFFFFFFFFFFFFFFFF << shift;
const u64 data = mem.Read<u64>(paddr & ~7);
const s64 result = (s64)((regs.Read<s64>(instr.rt()) & ~mask) | (data << shift));
regs.Write(instr.rt(), result);
}
}
void Interpreter::ldr(const Instruction instr) {
const u64 address = regs.Read<s64>(instr.rs()) + (s16)instr;
u32 paddr;
if (!regs.cop0.MapVAddr(Cop0::LOAD, address, paddr)) {
regs.cop0.HandleTLBException(address);
regs.cop0.FireException(Cop0::GetTLBExceptionCode(regs.cop0.tlbError, Cop0::LOAD), 0, regs.oldPC);
} else {
const s32 shift = 8 * ((address ^ 7) & 7);
const u64 mask = 0xFFFFFFFFFFFFFFFF >> shift;
const u64 data = mem.Read<u64>(paddr & ~7);
const s64 result = (s64)((regs.Read<s64>(instr.rt()) & ~mask) | (data >> shift));
regs.Write(instr.rt(), result);
}
}
void Interpreter::lbu(const Instruction instr) {
const u64 address = regs.Read<s64>(instr.rs()) + (s16)instr;
u32 paddr;
if (!regs.cop0.MapVAddr(Cop0::LOAD, address, paddr)) {
regs.cop0.HandleTLBException(address);
regs.cop0.FireException(Cop0::GetTLBExceptionCode(regs.cop0.tlbError, Cop0::LOAD), 0, regs.oldPC);
} else {
const u8 value = mem.Read<u8>(paddr);
regs.Write(instr.rt(), value);
}
}
void Interpreter::lhu(const Instruction instr) {
const s64 address = regs.Read<s64>(instr.rs()) + (s16)instr;
if (check_address_error(0b1, address)) {
regs.cop0.HandleTLBException(address);
regs.cop0.FireException(ExceptionCode::AddressErrorLoad, 0, regs.oldPC);
return;
}
u32 paddr;
if (!regs.cop0.MapVAddr(Cop0::LOAD, address, paddr)) {
regs.cop0.HandleTLBException(address);
regs.cop0.FireException(Cop0::GetTLBExceptionCode(regs.cop0.tlbError, Cop0::LOAD), 0, regs.oldPC);
} else {
const u16 value = mem.Read<u16>(paddr);
regs.Write(instr.rt(), value);
}
}
void Interpreter::lwu(const Instruction instr) {
const s64 address = regs.Read<s64>(instr.rs()) + (s16)instr;
if (check_address_error(0b11, address)) {
regs.cop0.HandleTLBException(address);
regs.cop0.FireException(ExceptionCode::AddressErrorLoad, 0, regs.oldPC);
return;
}
u32 paddr;
if (!regs.cop0.MapVAddr(Cop0::LOAD, address, paddr)) {
regs.cop0.HandleTLBException(address);
regs.cop0.FireException(Cop0::GetTLBExceptionCode(regs.cop0.tlbError, Cop0::LOAD), 0, regs.oldPC);
} else {
const u32 value = mem.Read<u32>(paddr);
regs.Write(instr.rt(), value);
}
}
void Interpreter::sb(const Instruction instr) {
const u64 address = regs.Read<s64>(instr.rs()) + (s16)instr;
u32 paddr;
if (!regs.cop0.MapVAddr(Cop0::STORE, address, paddr)) {
regs.cop0.HandleTLBException(address);
regs.cop0.FireException(Cop0::GetTLBExceptionCode(regs.cop0.tlbError, Cop0::STORE), 0, regs.oldPC);
} else {
mem.Write<u8>(paddr, regs.Read<s64>(instr.rt()));
}
}
void Interpreter::sc(const Instruction instr) {
const u64 address = regs.Read<s64>(instr.rs()) + (s16)instr;
if (regs.cop0.llbit) {
regs.cop0.llbit = false;
if (check_address_error(0b11, address)) {
regs.Write(instr.rt(), 0);
regs.cop0.HandleTLBException(address);
regs.cop0.FireException(ExceptionCode::AddressErrorStore, 0, regs.oldPC);
return;
}
u32 paddr = 0;
if (!regs.cop0.MapVAddr(Cop0::STORE, address, paddr)) {
regs.Write(instr.rt(), 0);
regs.cop0.HandleTLBException(address);
regs.cop0.FireException(Cop0::GetTLBExceptionCode(regs.cop0.tlbError, Cop0::STORE), 0, regs.oldPC);
} else {
mem.Write<u32>(paddr, regs.Read<s64>(instr.rt()));
regs.Write(instr.rt(), 1);
}
} else {
regs.Write(instr.rt(), 0);
}
}
void Interpreter::scd(const Instruction instr) {
if (!regs.cop0.is64BitAddressing && !regs.cop0.kernelMode) {
regs.cop0.FireException(ExceptionCode::ReservedInstruction, 0, regs.oldPC);
return;
}
const s64 address = regs.Read<s64>(instr.rs()) + (s16)instr;
if (regs.cop0.llbit) {
regs.cop0.llbit = false;
if (check_address_error(0b111, address)) {
regs.Write(instr.rt(), 0);
regs.cop0.HandleTLBException(address);
regs.cop0.FireException(ExceptionCode::AddressErrorStore, 0, regs.oldPC);
return;
}
u32 paddr = 0;
if (!regs.cop0.MapVAddr(Cop0::STORE, address, paddr)) {
regs.Write(instr.rt(), 0);
regs.cop0.HandleTLBException(address);
regs.cop0.FireException(Cop0::GetTLBExceptionCode(regs.cop0.tlbError, Cop0::STORE), 0, regs.oldPC);
} else {
mem.Write<u32>(paddr, regs.Read<s64>(instr.rt()));
regs.Write(instr.rt(), 1);
}
} else {
regs.Write(instr.rt(), 0);
}
}
void Interpreter::sh(const Instruction instr) {
const s64 address = regs.Read<s64>(instr.rs()) + (s16)instr;
u32 physical;
if (!regs.cop0.MapVAddr(Cop0::STORE, address, physical)) {
regs.cop0.HandleTLBException(address);
regs.cop0.FireException(Cop0::GetTLBExceptionCode(regs.cop0.tlbError, Cop0::STORE), 0, regs.oldPC);
} else {
mem.Write<u16>(physical, regs.Read<s64>(instr.rt()));
}
}
void Interpreter::sw(const Instruction instr) {
const s16 offset = instr;
const u64 address = regs.Read<s64>(instr.rs()) + offset;
if (check_address_error(0b11, address)) {
regs.cop0.HandleTLBException(address);
regs.cop0.FireException(ExceptionCode::AddressErrorStore, 0, regs.oldPC);
return;
}
u32 physical;
if (!regs.cop0.MapVAddr(Cop0::STORE, address, physical)) {
regs.cop0.HandleTLBException(address);
regs.cop0.FireException(Cop0::GetTLBExceptionCode(regs.cop0.tlbError, Cop0::STORE), 0, regs.oldPC);
} else {
mem.Write<u32>(physical, regs.Read<s64>(instr.rt()));
}
}
void Interpreter::sd(const Instruction instr) {
const s64 address = regs.Read<s64>(instr.rs()) + (s16)instr;
if (check_address_error(0b111, address)) {
regs.cop0.HandleTLBException(address);
regs.cop0.FireException(ExceptionCode::AddressErrorStore, 0, regs.oldPC);
return;
}
u32 physical;
if (!regs.cop0.MapVAddr(Cop0::STORE, address, physical)) {
regs.cop0.HandleTLBException(address);
regs.cop0.FireException(Cop0::GetTLBExceptionCode(regs.cop0.tlbError, Cop0::STORE), 0, regs.oldPC);
} else {
mem.Write(physical, regs.Read<s64>(instr.rt()));
}
}
void Interpreter::sdl(const Instruction instr) {
const u64 address = regs.Read<s64>(instr.rs()) + (s16)instr;
u32 paddr;
if (!regs.cop0.MapVAddr(Cop0::STORE, address, paddr)) {
regs.cop0.HandleTLBException(address);
regs.cop0.FireException(Cop0::GetTLBExceptionCode(regs.cop0.tlbError, Cop0::STORE), 0, regs.oldPC);
} else {
const s32 shift = 8 * ((address ^ 0) & 7);
const u64 mask = 0xFFFFFFFFFFFFFFFF >> shift;
const u64 data = mem.Read<u64>(paddr & ~7);
const u64 rt = regs.Read<s64>(instr.rt());
mem.Write(paddr & ~7, (data & ~mask) | (rt >> shift));
}
}
void Interpreter::sdr(const Instruction instr) {
const u64 address = regs.Read<s64>(instr.rs()) + (s16)instr;
u32 paddr;
if (!regs.cop0.MapVAddr(Cop0::STORE, address, paddr)) {
regs.cop0.HandleTLBException(address);
regs.cop0.FireException(Cop0::GetTLBExceptionCode(regs.cop0.tlbError, Cop0::STORE), 0, regs.oldPC);
} else {
const s32 shift = 8 * ((address ^ 7) & 7);
const u64 mask = 0xFFFFFFFFFFFFFFFF << shift;
const u64 data = mem.Read<u64>(paddr & ~7);
const u64 rt = regs.Read<s64>(instr.rt());
mem.Write(paddr & ~7, (data & ~mask) | (rt << shift));
}
}
void Interpreter::swl(const Instruction instr) {
const u64 address = regs.Read<s64>(instr.rs()) + (s16)instr;
u32 paddr;
if (!regs.cop0.MapVAddr(Cop0::STORE, address, paddr)) {
regs.cop0.HandleTLBException(address);
regs.cop0.FireException(Cop0::GetTLBExceptionCode(regs.cop0.tlbError, Cop0::STORE), 0, regs.oldPC);
} else {
const u32 shift = 8 * ((address ^ 0) & 3);
const u32 mask = 0xFFFFFFFF >> shift;
const u32 data = mem.Read<u32>(paddr & ~3);
const u32 rt = regs.Read<s64>(instr.rt());
mem.Write<u32>(paddr & ~3, (data & ~mask) | (rt >> shift));
}
}
void Interpreter::swr(const Instruction instr) {
const u64 address = regs.Read<s64>(instr.rs()) + (s16)instr;
u32 paddr;
if (!regs.cop0.MapVAddr(Cop0::STORE, address, paddr)) {
regs.cop0.HandleTLBException(address);
regs.cop0.FireException(Cop0::GetTLBExceptionCode(regs.cop0.tlbError, Cop0::STORE), 0, regs.oldPC);
} else {
const u32 shift = 8 * ((address ^ 3) & 3);
const u32 mask = 0xFFFFFFFF << shift;
const u32 data = mem.Read<u32>(paddr & ~3);
const u32 rt = regs.Read<s64>(instr.rt());
mem.Write<u32>(paddr & ~3, (data & ~mask) | (rt << shift));
}
}
void Interpreter::ori(const Instruction instr) {
const s64 imm = (u16)instr;
const s64 result = imm | regs.Read<s64>(instr.rs());
regs.Write(instr.rt(), result);
}
void Interpreter::or_(const Instruction instr) { regs.Write(instr.rd(), regs.Read<s64>(instr.rs()) | regs.Read<s64>(instr.rt())); }
void Interpreter::nor(const Instruction instr) {
regs.Write(instr.rd(), ~(regs.Read<s64>(instr.rs()) | regs.Read<s64>(instr.rt())));
}
void Interpreter::j(const Instruction instr) {
const s32 target = (instr & 0x3ffffff) << 2;
const s64 address = (regs.oldPC & ~0xfffffff) | target;
branch(true, address);
}
void Interpreter::jal(const Instruction instr) {
regs.Write(31, regs.nextPC);
j(instr);
}
void Interpreter::jalr(const Instruction instr) {
regs.Write(instr.rd(), regs.nextPC);
jr(instr);
}
void Interpreter::jr(const Instruction instr) {
const u64 address = regs.Read<s64>(instr.rs());
branch(true, address);
}
void Interpreter::slti(const Instruction instr) {
const s16 imm = instr;
regs.Write(instr.rt(), regs.Read<s64>(instr.rs()) < imm);
}
void Interpreter::sltiu(const Instruction instr) {
const s16 imm = instr;
regs.Write(instr.rt(), regs.Read<u64>(instr.rs()) < imm);
}
void Interpreter::slt(const Instruction instr) { regs.Write(instr.rd(), regs.Read<s64>(instr.rs()) < regs.Read<s64>(instr.rt())); }
void Interpreter::sltu(const Instruction instr) {
regs.Write(instr.rd(), regs.Read<u64>(instr.rs()) < regs.Read<u64>(instr.rt()));
}
void Interpreter::xori(const Instruction instr) {
const s64 imm = (u16)instr;
regs.Write(instr.rt(), regs.Read<s64>(instr.rs()) ^ imm);
}
void Interpreter::xor_(const Instruction instr) {
regs.Write(instr.rd(), regs.Read<s64>(instr.rt()) ^ regs.Read<s64>(instr.rs()));
}
void Interpreter::andi(const Instruction instr) {
const s64 imm = (u16)instr;
regs.Write(instr.rt(), regs.Read<s64>(instr.rs()) & imm);
}
void Interpreter::and_(const Instruction instr) {
regs.Write(instr.rd(), regs.Read<s64>(instr.rs()) & regs.Read<s64>(instr.rt()));
}
void Interpreter::sll(const Instruction instr) {
const u8 sa = ((instr >> 6) & 0x1f);
const s32 result = regs.Read<s64>(instr.rt()) << sa;
regs.Write(instr.rd(), (s64)result);
}
void Interpreter::sllv(const Instruction instr) {
const u8 sa = (regs.Read<s64>(instr.rs())) & 0x1F;
const u32 rt = regs.Read<s64>(instr.rt());
const s32 result = rt << sa;
regs.Write(instr.rd(), (s64)result);
}
void Interpreter::dsll32(const Instruction instr) {
const u8 sa = ((instr >> 6) & 0x1f);
const s64 result = regs.Read<s64>(instr.rt()) << (sa + 32);
regs.Write(instr.rd(), result);
}
void Interpreter::dsll(const Instruction instr) {
const u8 sa = ((instr >> 6) & 0x1f);
const s64 result = regs.Read<s64>(instr.rt()) << sa;
regs.Write(instr.rd(), result);
}
void Interpreter::dsllv(const Instruction instr) {
const s64 sa = regs.Read<s64>(instr.rs()) & 63;
const s64 result = regs.Read<s64>(instr.rt()) << sa;
regs.Write(instr.rd(), result);
}
void Interpreter::srl(const Instruction instr) {
const u32 rt = regs.Read<s64>(instr.rt());
const u8 sa = ((instr >> 6) & 0x1f);
const u32 result = rt >> sa;
regs.Write(instr.rd(), (s32)result);
}
void Interpreter::srlv(const Instruction instr) {
const u8 sa = (regs.Read<s64>(instr.rs()) & 0x1F);
const u32 rt = regs.Read<s64>(instr.rt());
const s32 result = rt >> sa;
regs.Write(instr.rd(), (s64)result);
}
void Interpreter::dsrl(const Instruction instr) {
const u64 rt = regs.Read<s64>(instr.rt());
const u8 sa = ((instr >> 6) & 0x1f);
const u64 result = rt >> sa;
regs.Write(instr.rd(), s64(result));
}
void Interpreter::dsrlv(const Instruction instr) {
const u8 amount = (regs.Read<s64>(instr.rs()) & 63);
const u64 rt = regs.Read<s64>(instr.rt());
const u64 result = rt >> amount;
regs.Write(instr.rd(), s64(result));
}
void Interpreter::dsrl32(const Instruction instr) {
const u64 rt = regs.Read<s64>(instr.rt());
const u8 sa = ((instr >> 6) & 0x1f);
const u64 result = rt >> (sa + 32);
regs.Write(instr.rd(), s64(result));
}
void Interpreter::sra(const Instruction instr) {
const s64 rt = regs.Read<s64>(instr.rt());
const u8 sa = ((instr >> 6) & 0x1f);
const s32 result = rt >> sa;
regs.Write(instr.rd(), result);
}
void Interpreter::srav(const Instruction instr) {
const s64 rs = regs.Read<s64>(instr.rs());
const s64 rt = regs.Read<s64>(instr.rt());
const u8 sa = rs & 0x1f;
const s32 result = rt >> sa;
regs.Write(instr.rd(), result);
}
void Interpreter::dsra(const Instruction instr) {
const s64 rt = regs.Read<s64>(instr.rt());
const u8 sa = ((instr >> 6) & 0x1f);
const s64 result = rt >> sa;
regs.Write(instr.rd(), result);
}
void Interpreter::dsrav(const Instruction instr) {
const s64 rt = regs.Read<s64>(instr.rt());
const s64 rs = regs.Read<s64>(instr.rs());
const s64 sa = rs & 63;
const s64 result = rt >> sa;
regs.Write(instr.rd(), result);
}
void Interpreter::dsra32(const Instruction instr) {
const s64 rt = regs.Read<s64>(instr.rt());
const u8 sa = ((instr >> 6) & 0x1f);
const s64 result = rt >> (sa + 32);
regs.Write(instr.rd(), result);
}
void Interpreter::dsub(const Instruction instr) {
const s64 rt = regs.Read<s64>(instr.rt());
const s64 rs = regs.Read<s64>(instr.rs());
if (const s64 result = rs - rt; check_signed_underflow(rs, rt, result)) {
regs.cop0.FireException(ExceptionCode::Overflow, 0, regs.oldPC);
} else {
regs.Write(instr.rd(), result);
}
}
void Interpreter::dsubu(const Instruction instr) {
const u64 rt = regs.Read<s64>(instr.rt());
const u64 rs = regs.Read<s64>(instr.rs());
const u64 result = rs - rt;
regs.Write(instr.rd(), s64(result));
}
void Interpreter::sub(const Instruction instr) {
const s32 rt = regs.Read<s64>(instr.rt());
const s32 rs = regs.Read<s64>(instr.rs());
const s32 result = rs - rt;
if (check_signed_underflow(rs, rt, result)) {
regs.cop0.FireException(ExceptionCode::Overflow, 0, regs.oldPC);
} else {
regs.Write(instr.rd(), result);
}
}
void Interpreter::subu(const Instruction instr) {
const u32 rt = regs.Read<s64>(instr.rt());
const u32 rs = regs.Read<s64>(instr.rs());
const u32 result = rs - rt;
regs.Write(instr.rd(), (s64)((s32)result));
}
void Interpreter::dmultu(const Instruction instr) {
const u64 rt = regs.Read<s64>(instr.rt());
const u64 rs = regs.Read<s64>(instr.rs());
const u128 result = (u128)rt * (u128)rs;
regs.lo = (s64)(result & 0xFFFFFFFFFFFFFFFF);
regs.hi = (s64)(result >> 64);
}
void Interpreter::dmult(const Instruction instr) {
const s64 rt = regs.Read<s64>(instr.rt());
const s64 rs = regs.Read<s64>(instr.rs());
const s128 result = (s128)rt * (s128)rs;
regs.lo = result & 0xFFFFFFFFFFFFFFFF;
regs.hi = result >> 64;
}
void Interpreter::multu(const Instruction instr) {
const u32 rt = regs.Read<s64>(instr.rt());
const u32 rs = regs.Read<s64>(instr.rs());
const u64 result = (u64)rt * (u64)rs;
regs.lo = (s64)((s32)result);
regs.hi = (s64)((s32)(result >> 32));
}
void Interpreter::mult(const Instruction instr) {
const s32 rt = regs.Read<s64>(instr.rt());
const s32 rs = regs.Read<s64>(instr.rs());
const s64 result = (s64)rt * (s64)rs;
regs.lo = (s64)((s32)result);
regs.hi = (s64)((s32)(result >> 32));
}
void Interpreter::mflo(const Instruction instr) { regs.Write(instr.rd(), regs.lo); }
void Interpreter::mfhi(const Instruction instr) { regs.Write(instr.rd(), regs.hi); }
void Interpreter::mtlo(const Instruction instr) { regs.lo = regs.Read<s64>(instr.rs()); }
void Interpreter::mthi(const Instruction instr) { regs.hi = regs.Read<s64>(instr.rs()); }
void Interpreter::trap(const bool cond) const {
Cop0& cop0 = Core::GetRegs().cop0;
if (cond) {
cop0.FireException(ExceptionCode::Trap, 0, regs.oldPC);
}
}
void Interpreter::mtc2(const Instruction instr) { cop2Latch = regs.Read<s64>(instr.rt()); }
void Interpreter::mfc2(const Instruction instr) {
const s32 value = cop2Latch;
regs.Write(instr.rt(), value);
}
void Interpreter::dmtc2(const Instruction instr) { cop2Latch = regs.Read<s64>(instr.rt()); }
void Interpreter::dmfc2(const Instruction instr) { regs.Write(instr.rt(), cop2Latch); }
void Interpreter::ctc2(const Instruction) {}
void Interpreter::cfc2(const Instruction) {}
} // namespace n64
+4
View File
@@ -0,0 +1,4 @@
file(GLOB_RECURSE SOURCES *.cpp)
file(GLOB_RECURSE HEADERS *.hpp)
add_library(jit ${SOURCES} ${HEADERS})
+465
View File
@@ -0,0 +1,465 @@
#include <JIT.hpp>
#include <Instruction.hpp>
namespace n64 {
void JIT::special(const Instruction instr) {
// 00rr_rccc
switch (instr.special()) {
case Instruction::SLL:
if (instr != 0) {
sll(instr);
}
break;
case Instruction::SRL:
srl(instr);
break;
case Instruction::SRA:
sra(instr);
break;
case Instruction::SLLV:
sllv(instr);
break;
case Instruction::SRLV:
srlv(instr);
break;
case Instruction::SRAV:
srav(instr);
break;
case Instruction::JR:
jr(instr);
break;
case Instruction::JALR:
jalr(instr);
break;
case Instruction::SYSCALL:
regs.cop0.FireException(ExceptionCode::Syscall, 0, regs.oldPC);
break;
case Instruction::BREAK:
regs.cop0.FireException(ExceptionCode::Breakpoint, 0, regs.oldPC);
break;
case Instruction::SYNC:
break; // SYNC
case Instruction::MFHI:
mfhi(instr);
break;
case Instruction::MTHI:
mthi(instr);
break;
case Instruction::MFLO:
mflo(instr);
break;
case Instruction::MTLO:
mtlo(instr);
break;
case Instruction::DSLLV:
dsllv(instr);
break;
case Instruction::DSRLV:
dsrlv(instr);
break;
case Instruction::DSRAV:
dsrav(instr);
break;
case Instruction::MULT:
mult(instr);
break;
case Instruction::MULTU:
multu(instr);
break;
case Instruction::DIV:
div(instr);
break;
case Instruction::DIVU:
divu(instr);
break;
case Instruction::DMULT:
dmult(instr);
break;
case Instruction::DMULTU:
dmultu(instr);
break;
case Instruction::DDIV:
ddiv(instr);
break;
case Instruction::DDIVU:
ddivu(instr);
break;
case Instruction::ADD:
add(instr);
break;
case Instruction::ADDU:
addu(instr);
break;
case Instruction::SUB:
sub(instr);
break;
case Instruction::SUBU:
subu(instr);
break;
case Instruction::AND:
and_(instr);
break;
case Instruction::OR:
or_(instr);
break;
case Instruction::XOR:
xor_(instr);
break;
case Instruction::NOR:
nor(instr);
break;
case Instruction::SLT:
slt(instr);
break;
case Instruction::SLTU:
sltu(instr);
break;
case Instruction::DADD:
dadd(instr);
break;
case Instruction::DADDU:
daddu(instr);
break;
case Instruction::DSUB:
dsub(instr);
break;
case Instruction::DSUBU:
dsubu(instr);
break;
case Instruction::TGE:
trap(regs.Read<s64>(instr.rs()) >= regs.Read<s64>(instr.rt()));
break;
case Instruction::TGEU:
trap(regs.Read<u64>(instr.rs()) >= regs.Read<u64>(instr.rt()));
break;
case Instruction::TLT:
trap(regs.Read<s64>(instr.rs()) < regs.Read<s64>(instr.rt()));
break;
case Instruction::TLTU:
trap(regs.Read<u64>(instr.rs()) < regs.Read<u64>(instr.rt()));
break;
case Instruction::TEQ:
trap(regs.Read<s64>(instr.rs()) == regs.Read<s64>(instr.rt()));
break;
case Instruction::TNE:
trap(regs.Read<s64>(instr.rs()) != regs.Read<s64>(instr.rt()));
break;
case Instruction::DSLL:
dsll(instr);
break;
case Instruction::DSRL:
dsrl(instr);
break;
case Instruction::DSRA:
dsra(instr);
break;
case Instruction::DSLL32:
dsll32(instr);
break;
case Instruction::DSRL32:
dsrl32(instr);
break;
case Instruction::DSRA32:
dsra32(instr);
break;
default:
panic("Unimplemented special {} ({:08X}) (pc: {:016X})", instr.special(), u32(instr),
static_cast<u64>(regs.oldPC));
}
}
void JIT::regimm(const Instruction instr) {
// 000r_rccc
switch (instr.regimm()) {
case Instruction::BLTZ:
bltz(instr);
break;
case Instruction::BGEZ:
bgez(instr);
break;
case Instruction::BLTZL:
bltzl(instr);
break;
case Instruction::BGEZL:
bgezl(instr);
break;
case Instruction::TGEI:
trap(regs.Read<s64>(instr.rs()) >= static_cast<s64>(static_cast<s16>(instr)));
break;
case Instruction::TGEIU:
trap(regs.Read<u64>(instr.rs()) >= static_cast<u64>(static_cast<s64>(static_cast<s16>(instr))));
break;
case Instruction::TLTI:
trap(regs.Read<s64>(instr.rs()) < static_cast<s64>(static_cast<s16>(instr)));
break;
case Instruction::TLTIU:
trap(regs.Read<u64>(instr.rs()) < static_cast<u64>(static_cast<s64>(static_cast<s16>(instr))));
break;
case Instruction::TEQI:
trap(regs.Read<s64>(instr.rs()) == static_cast<s64>(static_cast<s16>(instr)));
break;
case Instruction::TNEI:
trap(regs.Read<s64>(instr.rs()) != static_cast<s64>(static_cast<s16>(instr)));
break;
case Instruction::BLTZAL:
bltzal(instr);
break;
case Instruction::BGEZAL:
bgezal(instr);
break;
case Instruction::BLTZALL:
bltzall(instr);
break;
case Instruction::BGEZALL:
bgezall(instr);
break;
default:
panic("Unimplemented regimm {} ({:08X}) (pc: {:016X})", instr.regimm(), u32(instr),
static_cast<u64>(regs.oldPC));
}
}
void JIT::Emit(const Instruction instr) {
switch (instr.opcode()) {
case Instruction::SPECIAL:
special(instr);
break;
case Instruction::REGIMM:
regimm(instr);
break;
case Instruction::J:
j(instr);
break;
case Instruction::JAL:
jal(instr);
break;
case Instruction::BEQ:
beq(instr);
break;
case Instruction::BNE:
bne(instr);
break;
case Instruction::BLEZ:
blez(instr);
break;
case Instruction::BGTZ:
bgtz(instr);
break;
case Instruction::ADDI:
addi(instr);
break;
case Instruction::ADDIU:
addiu(instr);
break;
case Instruction::SLTI:
slti(instr);
break;
case Instruction::SLTIU:
sltiu(instr);
break;
case Instruction::ANDI:
andi(instr);
break;
case Instruction::ORI:
ori(instr);
break;
case Instruction::XORI:
xori(instr);
break;
case Instruction::LUI:
lui(instr);
break;
case Instruction::COP0:
switch (instr.cop_rs()) {
case 0x00:
code.mov(code.ARG2, instr);
emitMemberFunctionCall(&Cop0::mfc0, &regs.cop0);
break;
case 0x01:
code.mov(code.ARG2, instr);
emitMemberFunctionCall(&Cop0::dmfc0, &regs.cop0);
break;
case 0x04:
code.mov(code.ARG2, instr);
emitMemberFunctionCall(&Cop0::mtc0, &regs.cop0);
break;
case 0x05:
code.mov(code.ARG2, instr);
emitMemberFunctionCall(&Cop0::dmtc0, &regs.cop0);
break;
case 0x10 ... 0x1F:
switch (instr.cop_funct()) {
case 0x01:
emitMemberFunctionCall(&Cop0::tlbr, &regs.cop0);
break;
case 0x02:
code.mov(code.ARG2, COP0_REG_INDEX);
emitMemberFunctionCall(&Cop0::GetReg32, &regs.cop0);
code.mov(code.ARG2, code.rax);
code.and_(code.ARG2, 0x3F);
emitMemberFunctionCall(&Cop0::tlbw, &regs.cop0);
break;
case 0x06:
emitMemberFunctionCall(&Cop0::GetRandom, &regs.cop0);
code.mov(code.ARG2, code.rax);
emitMemberFunctionCall(&Cop0::tlbw, &regs.cop0);
break;
case 0x08:
emitMemberFunctionCall(&Cop0::tlbp, &regs.cop0);
break;
case 0x18:
emitMemberFunctionCall(&Cop0::eret, &regs.cop0);
break;
default:
panic("Unimplemented COP0 function {} ({:08X}) ({:016X})", instr.cop_funct(), u32(instr),
regs.oldPC);
}
break;
default:
panic("Unimplemented COP0 instruction {}", instr.cop_rs());
}
break;
case Instruction::COP1:
{
if (instr.cop_rs() == 0x08) {
switch (instr.cop_rt()) {
case 0:
// if (!regs.cop1.CheckFPUUsable())
// return;
bfc0(instr);
break;
case 1:
// if (!regs.cop1.CheckFPUUsable())
// return;
bfc1(instr);
break;
case 2:
// if (!regs.cop1.CheckFPUUsable())
// return;
blfc0(instr);
break;
case 3:
// if (!regs.cop1.CheckFPUUsable())
// return;
blfc1(instr);
break;
default:
panic("Undefined BC COP1 {:02X}", instr.cop_rt());
}
break;
}
regs.cop1.decode(instr);
}
break;
case Instruction::COP2:
break;
case Instruction::BEQL:
beql(instr);
break;
case Instruction::BNEL:
bnel(instr);
break;
case Instruction::BLEZL:
blezl(instr);
break;
case Instruction::BGTZL:
bgtzl(instr);
break;
case Instruction::DADDI:
daddi(instr);
break;
case Instruction::DADDIU:
daddiu(instr);
break;
case Instruction::LDL:
ldl(instr);
break;
case Instruction::LDR:
ldr(instr);
break;
case 0x1F:
regs.cop0.FireException(ExceptionCode::ReservedInstruction, 0, regs.oldPC);
break;
case Instruction::LB:
lb(instr);
break;
case Instruction::LH:
lh(instr);
break;
case Instruction::LWL:
lwl(instr);
break;
case Instruction::LW:
lw(instr);
break;
case Instruction::LBU:
lbu(instr);
break;
case Instruction::LHU:
lhu(instr);
break;
case Instruction::LWR:
lwr(instr);
break;
case Instruction::LWU:
lwu(instr);
break;
case Instruction::SB:
sb(instr);
break;
case Instruction::SH:
sh(instr);
break;
case Instruction::SWL:
swl(instr);
break;
case Instruction::SW:
sw(instr);
break;
case Instruction::SDL:
sdl(instr);
break;
case Instruction::SDR:
sdr(instr);
break;
case Instruction::SWR:
swr(instr);
break;
case Instruction::CACHE:
break; // CACHE
case Instruction::LL:
ll(instr);
break;
case Instruction::LWC1:
lwc1(instr);
break;
case Instruction::LLD:
lld(instr);
break;
case Instruction::LDC1:
ldc1(instr);
break;
case Instruction::LD:
ld(instr);
break;
case Instruction::SC:
sc(instr);
break;
case Instruction::SWC1:
swc1(instr);
break;
case Instruction::SCD:
scd(instr);
break;
case Instruction::SDC1:
sdc1(instr);
break;
case Instruction::SD:
sd(instr);
break;
default:
DumpBlockCacheToDisk();
panic("Unimplemented instruction {:02X} ({:08X}) (pc: {:016X})", instr.opcode(), u32(instr), static_cast<u64>(regs.oldPC));
}
}
} // namespace n64
+102
View File
@@ -0,0 +1,102 @@
#pragma once
#include <Instruction.hpp>
namespace n64 {
static bool SpecialEndsBlock(const Instruction instr) {
switch (instr.special()) {
case Instruction::JR:
case Instruction::JALR:
case Instruction::SYSCALL:
case Instruction::BREAK:
case Instruction::TGE:
case Instruction::TGEU:
case Instruction::TLT:
case Instruction::TLTU:
case Instruction::TEQ:
case Instruction::TNE:
return true;
default:
return false;
}
}
static bool InstrEndsBlock(const Instruction instr) {
switch (instr.opcode()) {
case Instruction::SPECIAL:
return SpecialEndsBlock(instr);
case Instruction::REGIMM:
case Instruction::J:
case Instruction::JAL:
case Instruction::BEQ:
case Instruction::BNE:
case Instruction::BLEZ:
case Instruction::BGTZ:
return true;
default:
return false;
}
}
static bool IsBranchLikely(const Instruction instr) {
switch (instr.opcode()) {
case Instruction::BEQL:
case Instruction::BNEL:
case Instruction::BLEZL:
case Instruction::BGTZL:
return true;
case Instruction::REGIMM:
switch (instr.regimm()) {
case Instruction::BLTZL:
case Instruction::BGEZL:
case Instruction::BLTZALL:
case Instruction::BGEZALL:
return true;
default:
return false;
}
case Instruction::COP1:
{
if (instr.cop_rs() == 0x08) {
if (instr.cop_rt() == 2 || instr.cop_rt() == 3)
return true;
return false;
}
return false;
}
default:
return false;
}
}
#ifdef _WIN32
#define ARG1 rcx
#define ARG2 rdx
#define ARG3 r8
#define ARG4 r9
#define SCR1 rax
#define SCR2 rcx
#define SCR3 rdx
#define SCR4 r8
#define SCR5 r9
#define SCR6 r10
#define SCR7 r11
#else
#define ARG1 rdi
#define ARG2 rsi
#define ARG3 rdx
#define ARG4 rcx
#define ARG5 r8
#define ARG6 r9
#define SCR1 rax
#define SCR2 rdi
#define SCR3 rsi
#define SCR4 rdx
#define SCR5 rcx
#define SCR6 r8
#define SCR7 r9
#define SCR8 r10
#define SCR9 r11
#endif
} // namespace n64
File diff suppressed because it is too large Load Diff
+4
View File
@@ -0,0 +1,4 @@
file(GLOB SOURCES *.cpp)
file(GLOB HEADERS *.hpp)
add_library(mem ${SOURCES} ${HEADERS})
+197
View File
@@ -0,0 +1,197 @@
#include <Mem.hpp>
#include <cassert>
#include <Options.hpp>
namespace n64 {
constexpr auto FLASH_SIZE = 1_mb;
Flash::Flash(mio::mmap_sink &saveData) : saveData(saveData) {}
void Flash::Reset() {
state = FlashState::Idle;
writeOffs = {};
state = {};
status = {};
eraseOffs = {};
writeBuf = {};
}
void Flash::Load(SaveType saveType, const std::string &path) {
if (saveType == SAVE_FLASH_1m) {
fs::path flashPath_ = path;
std::string savePath = Options::GetInstance().GetValue<std::string>("general", "savePath");
if (!savePath.empty()) {
flashPath_ = savePath / flashPath_.filename();
}
flashPath = flashPath_.replace_extension(".flash").string();
std::error_code error;
if (saveData.is_mapped()) {
saveData.sync(error);
if (error) {
panic("Could not sync {}", flashPath);
}
saveData.unmap();
}
auto flashVec = Util::ReadFileBinary(flashPath);
if (flashVec.empty()) {
std::vector<u8> dummy{};
dummy.resize(FLASH_SIZE);
Util::WriteFileBinary(dummy, flashPath);
flashVec = Util::ReadFileBinary(flashPath);
}
if (flashVec.size() != FLASH_SIZE) {
panic("Corrupt SRAM!");
}
saveData = mio::make_mmap_sink(flashPath, 0, mio::map_entire_file, error);
if (error) {
panic("Could not make mmap {}", flashPath);
}
}
}
void Flash::CommandExecute() const {
trace("Flash::CommandExecute");
switch (state) {
case FlashState::Idle:
break;
case FlashState::Erase:
if (saveData.is_mapped()) {
for (int i = 0; i < 128; i++) {
saveData[eraseOffs + i] = 0xFF;
}
} else {
panic("Accessing flash when not mapped!");
}
break;
case FlashState::Write:
if (saveData.is_mapped()) {
for (int i = 0; i < 128; i++) {
saveData[writeOffs + i] = writeBuf[i];
}
} else {
panic("Accessing flash when not mapped!");
}
break;
case FlashState::Read:
panic("Execute command when flash in read state");
break;
case FlashState::Status:
break;
}
}
void Flash::CommandStatus() {
state = FlashState::Status;
status = 0x1111800100C20000;
}
void Flash::CommandSetEraseOffs(u32 val) { eraseOffs = (val & 0xffff) << 7; }
void Flash::CommandErase() {
state = FlashState::Erase;
status = 0x1111800800C20000LL;
}
void Flash::CommandSetWriteOffs(u32 val) {
writeOffs = (val & 0xffff) << 7;
status = 0x1111800400C20000LL;
}
void Flash::CommandWrite() { state = FlashState::Write; }
void Flash::CommandRead() {
state = FlashState::Read;
status = 0x11118004F0000000;
}
template <>
void Flash::Write<u32>(u32 index, u32 val) {
if (index > 0) {
u8 cmd = val >> 24;
switch (cmd) {
case FLASH_COMMAND_EXECUTE:
CommandExecute();
break;
case FLASH_COMMAND_STATUS:
CommandStatus();
break;
case FLASH_COMMAND_SET_ERASE_OFFSET:
CommandSetEraseOffs(val);
break;
case FLASH_COMMAND_ERASE:
CommandErase();
break;
case FLASH_COMMAND_SET_WRITE_OFFSET:
CommandSetWriteOffs(val);
break;
case FLASH_COMMAND_WRITE:
CommandWrite();
break;
case FLASH_COMMAND_READ:
CommandRead();
break;
default:
warn("Invalid flash command: {:02X}", cmd);
}
} else {
warn("Flash Write of {:08X} @ {:08X}", val, index);
}
}
template <>
void Flash::Write<u8>(u32 index, u8 val) {
switch (state) {
case FlashState::Idle:
panic("Invalid FlashState::Idle with Write<u8>");
case FlashState::Status:
panic("Invalid FlashState::Status with Write<u8>");
case FlashState::Erase:
panic("Invalid FlashState::Erase with Write<u8>");
case FlashState::Read:
panic("Invalid FlashState::Read with Write<u8>");
case FlashState::Write:
assert(index <= 0x7F && "Out of range flash Write8");
writeBuf[index] = val;
break;
default:
warn("Invalid flash state on Write<u8>: {:02X}", static_cast<u8>(state));
}
}
template <>
u8 Flash::Read<u8>(const u32 index) const {
switch (state) {
case FlashState::Idle:
panic("Flash read byte while in state FLASH_STATE_IDLE");
case FlashState::Write:
panic("Flash read byte while in state FLASH_STATE_WRITE");
case FlashState::Read:
if (saveData.is_mapped()) {
const u8 value = saveData[index];
trace("Flash read byte in state read: index {:08X} = {:02X}", index, value);
return value;
}
panic("Accessing flash when not mapped!");
case FlashState::Status:
{
const u32 offset = (7 - (index % 8)) * 8;
const u8 value = (status >> offset) & 0xFF;
trace("Flash read byte in state status: index {:08X} = {:02X}", index, value);
return value;
}
default:
panic("Flash read byte while in unknown state");
return 0;
}
}
template <>
u32 Flash::Read<u32>(u32) const {
return status >> 32;
}
} // namespace n64
+119
View File
@@ -0,0 +1,119 @@
#include <Core.hpp>
#include <log.hpp>
namespace n64 {
AI::AI() { Reset(); }
void AI::Reset() {
dmaEnable = false;
dacRate = 0;
bitrate = 0;
dmaCount = 0;
dmaAddrCarry = false;
cycles = 0;
dmaLen = {};
dmaAddr = {};
dac = {44100, N64_CPU_FREQ / dac.freq, 16};
device.Reset();
}
// https://github.com/ares-emulator/ares/blob/master/ares/n64/ai/io.cpp
// https://github.com/ares-emulator/ares/blob/master/LICENSE
auto AI::Read(const u32 addr) const -> u32 {
if (addr == 0x0450000C) {
u32 val = 0;
val |= (dmaCount > 1);
val |= 1 << 20;
val |= 1 << 24;
val |= (dmaEnable << 25);
val |= (dmaCount > 0) << 30;
val |= (dmaCount > 1) << 31;
return val;
}
return dmaLen[0];
}
void AI::Write(const u32 addr, const u32 val) {
n64::Mem& mem = n64::Core::GetMem();
switch (addr) {
case 0x04500000:
if (dmaCount < 2) {
dmaAddr[dmaCount] = val & 0xFFFFFF & ~7;
}
break;
case 0x04500004:
{
const u32 len = (val & 0x3FFFF) & ~7;
if (dmaCount < 2) {
if (dmaCount == 0)
mem.mmio.mi.InterruptRaise(MI::Interrupt::AI);
dmaLen[dmaCount] = len;
dmaCount++;
}
}
break;
case 0x04500008:
dmaEnable = val & 1;
break;
case 0x0450000C:
mem.mmio.mi.InterruptLower(MI::Interrupt::AI);
break;
case 0x04500010:
{
const u32 oldDacFreq = dac.freq;
dacRate = val & 0x3FFF;
dac.freq = std::max(1.f, (float)GetVideoFrequency(mem.IsROMPAL()) / (dacRate + 1)) * 1.037;
dac.period = GetVideoFrequency(mem.IsROMPAL()) / dac.freq;
if (oldDacFreq != dac.freq) {
device.AdjustSampleRate(dac.freq);
}
}
break;
case 0x04500014:
bitrate = val & 0xF;
dac.precision = bitrate + 1;
break;
default:
panic("Unhandled AI write at addr {:08X} with val {:08X}", addr, val);
}
}
void AI::Step(const u32 cpuCycles, const float volumeL, const float volumeR) {
n64::Mem& mem = n64::Core::GetMem();
cycles += cpuCycles;
while (cycles > dac.period) {
if (dmaCount == 0) {
return;
}
if (dmaLen[0] && dmaEnable) {
const u32 addrHi = (dmaAddr[0] >> 13) + dmaAddrCarry & 0x7FF;
dmaAddr[0] = addrHi << 13 | dmaAddr[0] & 0x1FFF;
const u32 data = mem.mmio.rdp.ReadRDRAM<u32>(dmaAddr[0]);
const s16 l = s16(data >> 16);
const s16 r = s16(data);
if (volumeR > 0 && volumeL > 0) {
device.PushSample((float)l / std::numeric_limits<s16>::max(), volumeL, (float)r / std::numeric_limits<s16>::max(), volumeR);
}
const u32 addrLo = dmaAddr[0] + 4 & 0x1FFF;
dmaAddr[0] = dmaAddr[0] & ~0x1FFF | addrLo;
dmaAddrCarry = addrLo == 0;
dmaLen[0] -= 4;
}
if (!dmaLen[0]) {
if (--dmaCount > 0) {
mem.mmio.mi.InterruptRaise(MI::Interrupt::AI);
dmaAddr[0] = dmaAddr[1];
dmaLen[0] = dmaLen[1];
}
}
cycles -= dac.period;
}
}
} // namespace n64
+29
View File
@@ -0,0 +1,29 @@
#pragma once
#include <common.hpp>
#include <core/mmio/Audio.hpp>
namespace n64 {
struct AI {
AI();
void Reset();
auto Read(u32) const -> u32;
void Write(u32, u32);
void Step(u32, float, float);
bool dmaEnable{};
bool dmaAddrCarry{};
u8 bitrate{};
u16 dacRate{};
int dmaCount{};
u32 cycles{};
std::array<u32, 2> dmaLen{};
std::array<u32, 2> dmaAddr{};
struct {
u32 freq{44100};
u32 period{N64_CPU_FREQ / freq};
u32 precision{16};
} dac;
AudioDevice device;
};
} // namespace n64
+57
View File
@@ -0,0 +1,57 @@
#include <Audio.hpp>
#include <log.hpp>
#include <SDL3/SDL.h>
namespace n64 {
#define AUDIO_SAMPLE_RATE 44100
#define SYSTEM_SAMPLE_FORMAT SDL_AUDIO_F32
#define SYSTEM_SAMPLE_SIZE 4
#define BYTES_PER_HALF_SECOND (((float)AUDIO_SAMPLE_RATE / 2) * SYSTEM_SAMPLE_SIZE)
AudioDevice::AudioDevice() {
audioStreamMutex = SDL_CreateMutex();
if (!audioStreamMutex) {
panic("Unable to initialize audio mutex: {}", SDL_GetError());
}
SDL_InitSubSystem(SDL_INIT_AUDIO);
request = {SYSTEM_SAMPLE_FORMAT, 2, AUDIO_SAMPLE_RATE};
audioStream = SDL_OpenAudioDeviceStream(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, &request, nullptr, nullptr);
if (!audioStream) {
panic("Unable to create audio stream: {}", SDL_GetError());
}
}
void AudioDevice::PushSample(const float left, const float volumeL, const float right, const float volumeR) {
const float adjustedL = left * volumeL;
const float adjustedR = right * volumeR;
const float samples[]{adjustedL, adjustedR};
if (const auto availableBytes = static_cast<float>(SDL_GetAudioStreamAvailable(audioStream));
availableBytes <= BYTES_PER_HALF_SECOND) {
SDL_PutAudioStreamData(audioStream, samples, 2 * SYSTEM_SAMPLE_SIZE);
}
if (!running) {
SDL_ResumeAudioStreamDevice(audioStream);
running = true;
}
}
void AudioDevice::AdjustSampleRate(int sampleRate) {
LockMutex();
SDL_DestroyAudioStream(audioStream);
if (sampleRate < 4000) { // hack for Animal Forest. It requests a frequency of 3000-something. Weird asf
sampleRate *= 4000.f / static_cast<float>(sampleRate);
}
request = {SYSTEM_SAMPLE_FORMAT, 2, sampleRate};
audioStream = SDL_OpenAudioDeviceStream(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, &request, nullptr, nullptr);
if (!audioStream) {
panic("Unable to create audio stream: {}", SDL_GetError());
}
UnlockMutex();
}
} // namespace n64
+31
View File
@@ -0,0 +1,31 @@
#pragma once
#include <MemoryHelpers.hpp>
#include <SDL3/SDL.h>
namespace n64 {
struct AudioDevice {
AudioDevice();
void Reset() { running = false; }
void PushSample(float, float, float, float);
void AdjustSampleRate(int);
void LockMutex() const {
if (audioStreamMutex)
SDL_LockMutex(audioStreamMutex);
}
void UnlockMutex() const {
if (audioStreamMutex)
SDL_UnlockMutex(audioStreamMutex);
}
SDL_AudioStream *GetStream() const { return audioStream; }
private:
bool running = false;
SDL_AudioStream *audioStream;
SDL_Mutex *audioStreamMutex;
SDL_AudioSpec request{};
};
} // namespace n64
+4
View File
@@ -0,0 +1,4 @@
file(GLOB_RECURSE SOURCES *.cpp)
file(GLOB_RECURSE HEADERS *.hpp)
add_library(mmio ${SOURCES} ${HEADERS} ../../../../external/cic_nus_6105/n64_cic_nus_6105.cpp)
+59
View File
@@ -0,0 +1,59 @@
#include <Core.hpp>
namespace n64 {
void MI::InterruptRaise(const Interrupt intr) {
switch (intr) {
case Interrupt::VI:
miIntr.vi = true;
break;
case Interrupt::SI:
miIntr.si = true;
break;
case Interrupt::PI:
miIntr.pi = true;
break;
case Interrupt::AI:
miIntr.ai = true;
break;
case Interrupt::DP:
miIntr.dp = true;
break;
case Interrupt::SP:
miIntr.sp = true;
break;
}
UpdateInterrupt();
}
void MI::InterruptLower(const Interrupt intr) {
switch (intr) {
case Interrupt::VI:
miIntr.vi = false;
break;
case Interrupt::SI:
miIntr.si = false;
break;
case Interrupt::PI:
miIntr.pi = false;
break;
case Interrupt::AI:
miIntr.ai = false;
break;
case Interrupt::DP:
miIntr.dp = false;
break;
case Interrupt::SP:
miIntr.sp = false;
break;
}
UpdateInterrupt();
}
void MI::UpdateInterrupt() const {
n64::Registers& regs = n64::Core::GetRegs();
const bool interrupt = miIntr.raw & miIntrMask.raw;
regs.cop0.cause.ip2 = interrupt;
}
} // namespace n64
+87
View File
@@ -0,0 +1,87 @@
#include <core/mmio/MI.hpp>
#include <core/registers/Registers.hpp>
#include <log.hpp>
#define MI_VERSION_REG 0x02020102
namespace n64 {
MI::MI() { Reset(); }
void MI::Reset() {
miIntrMask.raw = 0;
miIntr.raw = 0;
miMode = 0;
}
auto MI::Read(u32 paddr) const -> u32 {
switch (paddr & 0xF) {
case 0x0:
return miMode & 0x3FF;
case 0x4:
return MI_VERSION_REG;
case 0x8:
return miIntr.raw & 0x3F;
case 0xC:
return miIntrMask.raw & 0x3F;
default:
panic("Unhandled MI[{:08X}] read", paddr);
}
}
void MI::Write(u32 paddr, u32 val) {
switch (paddr & 0xF) {
case 0x0:
miMode &= 0xFFFFFF80;
miMode |= val & 0x7F;
if (val & (1 << 7)) {
miMode &= ~(1 << 7);
}
if (val & (1 << 8)) {
miMode |= 1 << 7;
}
if (val & (1 << 9)) {
miMode &= ~(1 << 8);
}
if (val & (1 << 10)) {
miMode |= 1 << 8;
}
if (val & (1 << 11)) {
InterruptLower(Interrupt::DP);
}
if (val & (1 << 12)) {
miMode &= ~(1 << 9);
}
if (val & (1 << 13)) {
miMode |= 1 << 9;
}
break;
case 0x4:
case 0x8:
break;
case 0xC:
for (int bit = 0; bit < 6; bit++) {
const int clearbit = bit << 1;
const int setbit = (bit << 1) + 1;
if (val & (1 << clearbit)) {
miIntrMask.raw &= ~(1 << bit);
}
if (val & (1 << setbit)) {
miIntrMask.raw |= 1 << bit;
}
}
UpdateInterrupt();
break;
default:
panic("Unhandled MI write @ 0x{:08X} with value 0x{:08X}", paddr, val);
}
}
} // namespace n64
+33
View File
@@ -0,0 +1,33 @@
#pragma once
#include <common.hpp>
namespace n64 {
union MIIntr {
struct {
unsigned sp : 1;
unsigned si : 1;
unsigned ai : 1;
unsigned vi : 1;
unsigned pi : 1;
unsigned dp : 1;
unsigned : 26;
};
u32 raw;
};
struct MI {
enum class Interrupt : u8 { VI, SI, PI, AI, DP, SP };
explicit MI();
void Reset();
[[nodiscard]] auto Read(u32) const -> u32;
void Write(u32, u32);
void InterruptRaise(Interrupt intr);
void InterruptLower(Interrupt intr);
void UpdateInterrupt() const;
u32 miMode{};
MIIntr miIntr{}, miIntrMask{};
};
} // namespace n64
+601
View File
@@ -0,0 +1,601 @@
#include <Core.hpp>
#include <Scheduler.hpp>
#include <cmath>
#include <core/mmio/PI.hpp>
#include <log.hpp>
namespace n64 {
PI::PI() { Reset(); }
void PI::Reset() {
dmaBusy = false;
ioBusy = false;
latch = 0;
dramAddr = 0;
cartAddr = 0;
rdLen = 0;
wrLen = 0;
piBsdDom1Lat = 0;
piBsdDom2Lat = 0;
piBsdDom1Pwd = 0;
piBsdDom2Pwd = 0;
piBsdDom1Pgs = 0;
piBsdDom2Pgs = 0;
piBsdDom1Rls = 0;
piBsdDom2Rls = 0;
}
bool PI::WriteLatch(u32 value) {
if (ioBusy) {
return false;
} else {
ioBusy = true;
latch = value;
Scheduler::GetInstance().EnqueueRelative(100, PI_BUS_WRITE_COMPLETE);
return true;
}
}
bool PI::ReadLatch() {
n64::Registers& regs = n64::Core::GetRegs();
if (ioBusy) [[unlikely]] {
ioBusy = false;
regs.CpuStall(Scheduler::GetInstance().Remove(PI_BUS_WRITE_COMPLETE));
return false;
}
return true;
}
template <>
auto PI::BusRead<u8, true>(u32 addr) -> u8 {
n64::Mem& mem = n64::Core::GetMem();
switch (addr) {
case REGION_PI_UNKNOWN:
panic("Reading byte from address 0x{:08X} in unsupported region: REGION_PI_UNKNOWN - This is the N64DD, "
"returning FF because it is not emulated",
addr);
case REGION_PI_64DD_REG:
panic("Reading byte from address 0x{:08X} in unsupported region: REGION_PI_64DD_REG - This is the N64DD, "
"returning FF because it is not emulated",
addr);
case REGION_PI_64DD_ROM:
warn("Reading byte from address 0x{:08X} in unsupported region: REGION_PI_64DD_ROM - This is the N64DD, "
"returning FF because it is not emulated",
addr);
return 0xFF;
case REGION_PI_SRAM:
return mem.BackupRead<u8>(addr - SREGION_PI_SRAM);
case REGION_PI_ROM:
{
// round to nearest 4 byte boundary, keeping old LSB
const u32 index = BYTE_ADDRESS(addr) - SREGION_PI_ROM;
if (index >= mem.rom.cart.size()) {
warn("Address 0x{:08X} accessed an index {}/0x{:X} outside the bounds of the ROM! ({}/0x{:016X})", addr,
index, index, mem.rom.cart.size(), mem.rom.cart.size());
return 0xFF;
}
return mem.rom.cart[index];
}
default:
panic("Should never end up here! Access to address {:08X} which did not match any PI bus regions!", addr);
}
}
template <>
auto PI::BusRead<u8, false>(u32 addr) -> u8 {
n64::Mem& mem = n64::Core::GetMem();
if (!ReadLatch()) [[unlikely]] {
return latch >> 24;
}
switch (addr) {
case REGION_PI_UNKNOWN:
panic("Reading byte from address 0x{:08X} in unsupported region: REGION_PI_UNKNOWN - This is the N64DD, "
"returning FF because it is not emulated",
addr);
case REGION_PI_64DD_REG:
panic("Reading byte from address 0x{:08X} in unsupported region: REGION_PI_64DD_REG - This is the N64DD, "
"returning FF because it is not emulated",
addr);
case REGION_PI_64DD_ROM:
warn("Reading byte from address 0x{:08X} in unsupported region: REGION_PI_64DD_ROM - This is the N64DD, "
"returning FF because it is not emulated",
addr);
return 0xFF;
case REGION_PI_SRAM:
return mem.BackupRead<u8>(addr - SREGION_PI_SRAM);
case REGION_PI_ROM:
{
addr = (addr + 2) & ~2;
// round to nearest 4 byte boundary, keeping old LSB
const u32 index = BYTE_ADDRESS(addr) - SREGION_PI_ROM;
if (index >= mem.rom.cart.size()) {
warn("Address 0x{:08X} accessed an index {}/0x{:X} outside the bounds of the ROM! ({}/0x{:016X})", addr,
index, index, mem.rom.cart.size(), mem.rom.cart.size());
return 0xFF;
}
return mem.rom.cart[index];
}
default:
panic("Should never end up here! Access to address {:08X} which did not match any PI bus regions!", addr);
}
}
template <>
void PI::BusWrite<u8, true>(u32 addr, u32 val) {
n64::Mem& mem = n64::Core::GetMem();
switch (addr) {
case REGION_PI_UNKNOWN:
panic("Writing byte 0x{:02X} to address 0x{:08X} in unsupported region: REGION_PI_UNKNOWN", val, addr);
case REGION_PI_64DD_REG:
if (addr == 0x05000020) {
fprintf(stderr, "%c", val);
} else {
warn("Writing byte 0x{:02X} to address 0x{:08X} in region: REGION_PI_64DD_ROM, this is the 64DD, ignoring!",
val, addr);
}
break;
case REGION_PI_64DD_ROM:
panic("Writing byte 0x{:02X} to address 0x{:08X} in unsupported region: REGION_PI_64DD_ROM", val, addr);
case REGION_PI_SRAM:
mem.BackupWrite<u8>(addr - SREGION_PI_SRAM, val);
break;
case REGION_PI_ROM:
warn("Writing byte 0x{:02X} to address 0x{:08X} in unsupported region: REGION_PI_ROM", val, addr);
break;
default:
panic("Should never end up here! Access to address {:08X} which did not match any PI bus regions!", addr);
}
}
template <>
void PI::BusWrite<u8, false>(u32 addr, u32 val) {
u8 latch_shift = 24 - (addr & 1) * 8;
if (!WriteLatch(val << latch_shift) && addr != 0x05000020) [[unlikely]] {
return;
}
BusWrite<u8, true>(addr, val);
}
template <>
auto PI::BusRead<u16, false>(u32 addr) -> u16 {
n64::Mem& mem = n64::Core::GetMem();
if (!ReadLatch()) [[unlikely]] {
return latch >> 16;
}
switch (addr) {
case REGION_PI_UNKNOWN:
panic("Reading half from address 0x{:08X} in unsupported region: REGION_PI_UNKNOWN - This is the N64DD, "
"returning FF because it is not emulated",
addr);
case REGION_PI_64DD_REG:
panic("Reading half from address 0x{:08X} in unsupported region: REGION_PI_64DD_REG - This is the N64DD, "
"returning FF because it is not emulated",
addr);
case REGION_PI_64DD_ROM:
panic("Reading half from address 0x{:08X} in unsupported region: REGION_PI_64DD_ROM - This is the N64DD, "
"returning FF because it is not emulated",
addr);
case REGION_PI_SRAM:
panic("Reading half from address 0x{:08X} in unsupported region: REGION_PI_SRAM", addr);
case REGION_PI_ROM:
{
addr = (addr + 2) & ~3;
const u32 index = HALF_ADDRESS(addr) - SREGION_PI_ROM;
if (index > mem.rom.cart.size() - 1) {
panic("Address 0x{:08X} accessed an index {}/0x{:X} outside the bounds of the ROM!", addr, index, index);
}
return Util::ReadAccess<u16>(mem.rom.cart, index);
}
default:
panic("Should never end up here! Access to address {:08X} which did not match any PI bus regions!", addr);
}
}
template <>
auto PI::BusRead<u16, true>(u32 addr) -> u16 {
return BusRead<u16, false>(addr);
}
template <>
void PI::BusWrite<u16, false>(u32 addr, u32 val) {
if (!WriteLatch(val << 16)) [[unlikely]] {
return;
}
switch (addr) {
case REGION_PI_UNKNOWN:
panic("Writing half 0x{:04X} to address 0x{:08X} in unsupported region: REGION_PI_UNKNOWN", val, addr);
case REGION_PI_64DD_REG:
panic("Writing half 0x{:04X} to address 0x{:08X} in region: REGION_PI_64DD_ROM, this is the 64DD, ignoring!",
val, addr);
case REGION_PI_64DD_ROM:
panic("Writing half 0x{:04X} to address 0x{:08X} in unsupported region: REGION_PI_64DD_ROM", val, addr);
case REGION_PI_SRAM:
panic("Writing half 0x{:04X} to address 0x{:08X} in unsupported region: REGION_PI_SRAM", val, addr);
case REGION_PI_ROM:
warn("Writing half 0x{:04X} to address 0x{:08X} in unsupported region: REGION_PI_ROM", val, addr);
break;
default:
panic("Should never end up here! Access to address {:08X} which did not match any PI bus regions!", addr);
}
}
template <>
void PI::BusWrite<u16, true>(u32 addr, u32 val) {
BusWrite<u16, false>(addr, val);
}
template <>
auto PI::BusRead<u32, false>(u32 addr) -> u32 {
n64::Mem& mem = n64::Core::GetMem();
if (!ReadLatch()) [[unlikely]] {
return latch;
}
switch (addr) {
case REGION_PI_UNKNOWN:
warn("Reading word from address 0x{:08X} in unsupported region: REGION_PI_UNKNOWN - This is the N64DD, "
"returning FF because it is not emulated",
addr);
return 0xFF;
case REGION_PI_64DD_REG:
warn("Reading word from address 0x{:08X} in unsupported region: REGION_PI_64DD_REG - This is the N64DD, "
"returning FF because it is not emulated",
addr);
return 0xFF;
case REGION_PI_64DD_ROM:
warn("Reading word from address 0x{:08X} in unsupported region: REGION_PI_64DD_ROM - This is the N64DD, "
"returning FF because it is not emulated",
addr);
return 0xFF;
case REGION_PI_SRAM:
return mem.BackupRead<u32>(addr);
case REGION_PI_ROM:
{
const u32 index = addr - SREGION_PI_ROM;
if (index > mem.rom.cart.size() - 3) { // -3 because we're reading an entire word
switch (addr) {
case REGION_CART_ISVIEWER_BUFFER:
return std::byteswap<u32>(Util::ReadAccess<u32>(mem.isviewer, addr - SREGION_CART_ISVIEWER_BUFFER));
case CART_ISVIEWER_FLUSH:
panic("Read from ISViewer flush!");
default:
break;
}
warn("Address 0x{:08X} accessed an index {}/0x{:X} outside the bounds of the ROM!", addr, index, index);
return 0;
}
return Util::ReadAccess<u32>(mem.rom.cart, index);
}
default:
panic("Should never end up here! Access to address {:08X} which did not match any PI bus regions!", addr);
}
}
template <>
auto PI::BusRead<u32, true>(u32 addr) -> u32 {
return BusRead<u32, false>(addr);
}
template <>
void PI::BusWrite<u32, false>(u32 addr, u32 val) {
n64::Mem& mem = n64::Core::GetMem();
switch (addr) {
case REGION_PI_UNKNOWN:
if (!WriteLatch(val)) [[unlikely]] {
return;
}
warn("Writing word 0x{:08X} to address 0x{:08X} in unsupported region: REGION_PI_UNKNOWN", val, addr);
return;
case REGION_PI_64DD_REG:
if (!WriteLatch(val)) [[unlikely]] {
return;
}
warn("Writing word 0x{:08X} to address 0x{:08X} in region: REGION_PI_64DD_ROM, this is the 64DD, ignoring!",
val, addr);
return;
case REGION_PI_64DD_ROM:
if (!WriteLatch(val)) [[unlikely]] {
return;
}
warn("Writing word 0x{:08X} to address 0x{:08X} in unsupported region: REGION_PI_64DD_ROM", val, addr);
return;
case REGION_PI_SRAM:
if (!WriteLatch(val)) [[unlikely]] {
return;
}
mem.BackupWrite<u32>(addr - SREGION_PI_SRAM, val);
return;
case REGION_PI_ROM:
switch (addr) {
case REGION_CART_ISVIEWER_BUFFER:
Util::WriteAccess<u32>(mem.isviewer, addr - SREGION_CART_ISVIEWER_BUFFER, std::byteswap(val));
break;
case CART_ISVIEWER_FLUSH:
{
if (val < CART_ISVIEWER_SIZE) {
std::string message(val + 1, 0);
std::copy_n(mem.isviewer.begin(), val, message.begin());
always("{}", message);
} else {
panic("ISViewer buffer size is emulated at {} bytes, but received a flush command for {} bytes!",
CART_ISVIEWER_SIZE, val);
}
break;
}
default:
if (!WriteLatch(val)) [[unlikely]] {
warn("Couldn't latch PI bus, ignoring write to REGION_PI_ROM");
return;
}
warn("Writing word 0x{:08X} to address 0x{:08X} in unsupported region: REGION_PI_ROM", val, addr);
}
return;
default:
panic("Should never end up here! Access to address {:08X} which did not match any PI bus regions!", addr);
}
}
template <>
void PI::BusWrite<u32, true>(u32 addr, u32 val) {
BusWrite<u32, false>(addr, val);
}
template <>
auto PI::BusRead<u64, false>(u32 addr) -> u64 {
n64::Mem& mem = n64::Core::GetMem();
if (!ReadLatch()) [[unlikely]] {
return static_cast<u64>(latch) << 32;
}
switch (addr) {
case REGION_PI_UNKNOWN:
panic("Reading dword from address 0x{:08X} in unsupported region: REGION_PI_UNKNOWN", addr);
case REGION_PI_64DD_REG:
panic("Reading dword from address 0x{:08X} in unsupported region: REGION_PI_64DD_REG", addr);
case REGION_PI_64DD_ROM:
panic("Reading dword from address 0x{:08X} in unsupported region: REGION_PI_64DD_ROM", addr);
case REGION_PI_SRAM:
panic("Reading dword from address 0x{:08X} in unsupported region: REGION_PI_SRAM", addr);
case REGION_PI_ROM:
{
const u32 index = addr - SREGION_PI_ROM;
if (index > mem.rom.cart.size() - 7) { // -7 because we're reading an entire dword
panic("Address 0x{:08X} accessed an index {}/0x{:X} outside the bounds of the ROM!", addr, index, index);
}
return Util::ReadAccess<u64>(mem.rom.cart, index);
}
default:
panic("Should never end up here! Access to address {:08X} which did not match any PI bus regions!", addr);
}
}
template <>
auto PI::BusRead<u64, true>(u32 addr) -> u64 {
return BusRead<u64, false>(addr);
}
template <>
void PI::BusWrite<false>(u32 addr, u64 val) {
if (!WriteLatch(val >> 32)) [[unlikely]] {
return;
}
switch (addr) {
case REGION_PI_UNKNOWN:
panic("Writing dword 0x{:016X} to address 0x{:08X} in unsupported region: REGION_PI_UNKNOWN", val, addr);
case REGION_PI_64DD_REG:
panic("Writing dword 0x{:016X} to address 0x{:08X} in unsupported region: REGION_PI_64DD_REG", val, addr);
case REGION_PI_64DD_ROM:
panic("Writing dword 0x{:016X} to address 0x{:08X} in unsupported region: REGION_PI_64DD_ROM", val, addr);
case REGION_PI_SRAM:
panic("Writing dword 0x{:016X} to address 0x{:08X} in unsupported region: REGION_PI_SRAM", val, addr);
case REGION_PI_ROM:
warn("Writing dword 0x{:016X} to address 0x{:08X} in unsupported region: REGION_PI_ROM", val, addr);
break;
default:
panic("Should never end up here! Access to address {:08X} which did not match any PI bus regions!", addr);
}
}
template <>
void PI::BusWrite<true>(u32 addr, u64 val) {
BusWrite<false>(addr, val);
}
auto PI::Read(u32 addr) const -> u32 {
n64::Mem& mem = n64::Core::GetMem();
switch (addr) {
case 0x04600000:
return dramAddr & 0x00FFFFFE;
case 0x04600004:
return cartAddr & 0xFFFFFFFE;
case 0x04600008:
return rdLen;
case 0x0460000C:
return wrLen;
case 0x04600010:
{
u32 value = 0;
value |= (dmaBusy << 0); // Is PI DMA active? No, because it's instant
value |= (ioBusy << 1); // Is PI IO busy? No, because it's instant
value |= (0 << 2); // PI IO error?
value |= (mem.mmio.mi.miIntr.pi << 3); // PI interrupt?
return value;
}
case 0x04600014:
return piBsdDom1Lat;
case 0x04600018:
return piBsdDom1Pwd;
case 0x0460001C:
return piBsdDom1Pgs;
case 0x04600020:
return piBsdDom1Rls;
case 0x04600024:
return piBsdDom2Lat;
case 0x04600028:
return piBsdDom2Pwd;
case 0x0460002C:
return piBsdDom2Pgs;
case 0x04600030:
return piBsdDom2Rls;
default:
panic("Unhandled PI[{:08X}] read", addr);
}
}
u8 PI::GetDomain(const u32 address) {
switch (address) {
case REGION_PI_UNKNOWN:
case REGION_PI_64DD_ROM:
case REGION_PI_ROM:
return 1;
case REGION_PI_64DD_REG:
case REGION_PI_SRAM:
return 2;
default:
panic("Unknown PI domain for address {:08X}!", address);
}
}
u32 PI::AccessTiming(const u8 domain, const u32 length) const {
uint32_t cycles = 0;
uint32_t latency = 0;
uint32_t pulse_width = 0;
uint32_t release = 0;
uint32_t page_size = 0;
switch (domain) {
case 1:
latency = piBsdDom1Lat + 1;
pulse_width = piBsdDom1Pwd + 1;
release = piBsdDom1Rls + 1;
page_size = 1 << (piBsdDom1Pgs + 2);
break;
case 2:
latency = piBsdDom2Lat + 1;
pulse_width = piBsdDom2Pwd + 1;
release = piBsdDom2Rls + 1;
page_size = 1 << (piBsdDom2Pgs + 2);
break;
default:
panic("Unknown PI domain: {}\n", domain);
}
const uint32_t pages = static_cast<uint32_t>(ceil(static_cast<double>(length) / static_cast<double>(page_size)));
cycles += (14 + latency) * pages;
cycles += (pulse_width + release) * (length / 2);
cycles += 5 * pages;
return cycles * 1.5; // Converting RCP clock speed to CPU clock speed
}
// rdram -> cart
template <>
void PI::DMA<false>() {
n64::Mem& mem = n64::Core::GetMem();
const s32 len = rdLen + 1;
trace("PI DMA from RDRAM to CARTRIDGE (size: {} B, {:08X} to {:08X})", len, dramAddr, cartAddr);
if (mem.saveType == SAVE_FLASH_1m && cartAddr >= SREGION_PI_SRAM && cartAddr < (CART_REGION_START_2_2 + 1_mb)) {
cartAddr = SREGION_PI_SRAM | ((cartAddr & (1_mb-1)) << 1);
}
for (int i = 0; i < len; i++) {
BusWrite<u8, true>(cartAddr + i, mem.mmio.rdp.ReadRDRAM<u8>(dramAddr + i));
}
dramAddr += len;
dramAddr = (dramAddr + 7) & ~7;
cartAddr += len;
if (cartAddr & 1)
cartAddr += 1;
dmaBusy = true;
Scheduler::GetInstance().EnqueueRelative(AccessTiming(GetDomain(cartAddr), rdLen), PI_DMA_COMPLETE);
}
// cart -> rdram
template <>
void PI::DMA<true>() {
n64::Mem& mem = n64::Core::GetMem();
const s32 len = wrLen + 1;
trace("PI DMA from CARTRIDGE to RDRAM (size: {} B, {:08X} to {:08X})", len, cartAddr, dramAddr);
if (mem.saveType == SAVE_FLASH_1m && cartAddr >= SREGION_PI_SRAM && cartAddr < (CART_REGION_START_2_2 + 1_mb)) {
cartAddr = SREGION_PI_SRAM | ((cartAddr & (1_mb-1)) << 1);
}
for (u32 i = 0; i < len; i++) {
mem.mmio.rdp.WriteRDRAM<u8>(dramAddr + i, BusRead<u8, true>(cartAddr + i));
}
dramAddr += len;
dramAddr = (dramAddr + 7) & ~7;
cartAddr += len;
if (cartAddr & 1)
cartAddr += 1;
dmaBusy = true;
Scheduler::GetInstance().EnqueueRelative(AccessTiming(GetDomain(cartAddr), len), PI_DMA_COMPLETE);
}
void PI::Write(u32 addr, u32 val) {
n64::Mem& mem = n64::Core::GetMem();
MI &mi = mem.mmio.mi;
switch (addr) {
case 0x04600000:
dramAddr = val & 0x00FFFFFE;
break;
case 0x04600004:
cartAddr = val & 0xFFFFFFFE;
break;
case 0x04600008:
{
rdLen = val & 0x00FFFFFF;
DMA<false>();
}
break;
case 0x0460000C:
{
wrLen = val & 0x00FFFFFF;
DMA<true>();
}
break;
case 0x04600010:
if (val & 2) {
mi.InterruptLower(MI::Interrupt::PI);
}
break;
case 0x04600014:
piBsdDom1Lat = val & 0xff;
break;
case 0x04600018:
piBsdDom1Pwd = val & 0xff;
break;
case 0x0460001C:
piBsdDom1Pgs = val & 0xff;
break;
case 0x04600020:
piBsdDom1Rls = val & 0xff;
break;
case 0x04600024:
piBsdDom2Lat = val & 0xff;
break;
case 0x04600028:
piBsdDom2Pwd = val & 0xff;
break;
case 0x0460002C:
piBsdDom2Pgs = val & 0xff;
break;
case 0x04600030:
piBsdDom2Rls = val & 0xff;
break;
default:
panic("Unhandled PI[{:08X}] write ({:08X})", val, addr);
}
}
} // namespace n64
+37
View File
@@ -0,0 +1,37 @@
#pragma once
#include <common.hpp>
namespace n64 {
struct PI {
PI();
void Reset();
[[nodiscard]] auto Read(u32) const -> u32;
void Write(u32, u32);
template <typename T, bool isDma>
void BusWrite(u32, u32);
template <bool isDma>
void BusWrite(u32, u64);
template <typename T, bool isDma>
auto BusRead(u32) -> T;
bool ReadLatch();
bool WriteLatch(u32 val);
static u8 GetDomain(u32 address);
[[nodiscard]] u32 AccessTiming(u8 domain, u32 length) const;
bool dmaBusy{}, ioBusy{};
u32 latch{};
u32 dramAddr{}, cartAddr{};
u32 rdLen{}, wrLen{};
u32 piBsdDom1Lat{}, piBsdDom2Lat{};
u32 piBsdDom1Pwd{}, piBsdDom2Pwd{};
u32 piBsdDom1Pgs{}, piBsdDom2Pgs{};
u32 piBsdDom1Rls{}, piBsdDom2Rls{};
private:
template <bool toDram>
void DMA();
};
} // namespace n64
+654
View File
@@ -0,0 +1,654 @@
#include <Netplay.hpp>
#include <cassert>
#include <cic_nus_6105/n64_cic_nus_6105.hpp>
#include <Core.hpp>
#include <log.hpp>
#include <Options.hpp>
#define MEMPAK_SIZE 32768
namespace n64 {
void PIF::Reset() {
movie.Reset();
joybusDevices = {};
bootrom = {};
ram = {};
std::error_code error;
if (mempak.is_mapped()) {
mempak.sync(error);
if (error) {
panic("Could not sync {}", mempakPath);
}
mempak.unmap();
}
if (eeprom.is_mapped()) {
eeprom.sync(error);
if (error) {
panic("Could not sync {}", eepromPath);
}
eeprom.unmap();
}
mempakOpen = false;
channel = 0;
}
void PIF::MaybeLoadMempak() {
if (!mempakOpen) {
fs::path mempakPath_ = mempakPath;
std::string savePath = Options::GetInstance().GetValue<std::string>("general", "savePath");
if (!savePath.empty()) {
mempakPath_ = savePath / mempakPath_.filename();
}
mempakPath = mempakPath_.replace_extension(".mempak").string();
std::error_code error;
if (mempak.is_mapped()) {
mempak.sync(error);
if (error) {
panic("Could not sync {}", mempakPath);
}
mempak.unmap();
}
auto mempakVec = Util::ReadFileBinary(mempakPath);
if (mempak.empty()) {
Util::WriteFileBinary(std::array<u8, MEMPAK_SIZE>{}, mempakPath);
mempakVec = Util::ReadFileBinary(mempakPath);
}
if (mempakVec.size() != MEMPAK_SIZE) {
panic("Corrupt mempak!");
}
mempak = mio::make_mmap_sink(mempakPath, 0, mio::map_entire_file, error);
if (error) {
panic("Could not open {}", mempakPath);
}
mempakOpen = true;
}
}
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:
panic("Unknown save type!");
}
}
void PIF::LoadEeprom(const SaveType saveType, const std::string &path) {
if (saveType == SAVE_EEPROM_16k || saveType == SAVE_EEPROM_4k) {
fs::path eepromPath_ = path;
std::string savePath = Options::GetInstance().GetValue<std::string>("general", "savePath");
if (!savePath.empty()) {
eepromPath_ = savePath / eepromPath_.filename();
}
eepromPath = eepromPath_.replace_extension(".eeprom").string();
std::error_code error;
if (eeprom.is_mapped()) {
eeprom.sync(error);
if (error) {
panic("Could not sync {}", eepromPath);
}
eeprom.unmap();
}
eepromSize = GetSaveSize(saveType);
auto eepromVec = Util::ReadFileBinary(eepromPath);
if (eepromVec.empty()) {
std::vector<u8> dummy{};
dummy.resize(GetSaveSize(saveType));
Util::WriteFileBinary(dummy, eepromPath);
eepromVec = dummy;
}
if (eepromVec.size() != eepromSize) {
panic("Corrupt eeprom!");
}
eeprom = mio::make_mmap_sink(eepromPath, 0, mio::map_entire_file, error);
if (error) {
panic("Could not open {}. Reason {}", eepromPath, error.message());
}
}
}
enum CommandIndexes { COMMAND_LEN = 0, COMMAND_RESULT_LEN, COMMAND_INDEX, COMMAND_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(reinterpret_cast<char *>(challenge), reinterpret_cast<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 DataCRC(const u8 *data) {
u8 crc = 0;
for (int i = 0; i <= 32; i++) {
for (int j = 7; j >= 0; j--) {
const u8 xorVal = ((crc & 0x80) != 0) ? 0x85 : 0x00;
crc <<= 1;
if (i < 32) {
if ((data[i] & (1 << j)) != 0) {
crc |= 1;
}
}
crc ^= xorVal;
}
}
return crc;
}
#define BCD_ENCODE(x) (((x) / 10) << 4 | ((x) % 10))
#define BCD_DECODE(x) (((x) >> 4) * 10 + ((x) & 15))
void PIF::ConfigureJoyBusFrame() {
n64::Mem& mem = n64::Core::GetMem();
channel = 0;
int i = 0;
while (i < 63) {
u8 *packet = &ram[i++];
const u8 commandLength = packet[COMMAND_LEN] & 0x3F;
if (commandLength == 0) {
channel++;
} else if (commandLength == 0x3D) {
channel = 0;
channel++;
} else if (commandLength == 0x3E) {
break;
} else if (commandLength == 0x3F) {
continue;
} else {
const u8 r = ram[i++];
if (r == 0xFE) {
break;
}
const u8 reslen = r & 0x3F;
u8 *res = &ram[i + commandLength];
const u8 commandIndex = packet[COMMAND_INDEX];
switch (commandIndex) {
case 0:
case 0xff:
ControllerID(res);
channel++;
break;
case 1:
if (!ReadButtons(res)) {
packet[COMMAND_RESULT_LEN] |= 0x80;
}
channel++;
break;
case 2:
MempakRead(packet, res);
break;
case 3:
MempakWrite(packet, res);
break;
case 4:
EepromRead(packet, res);
break;
case 5:
EepromWrite(packet, res);
break;
case 6:
res[0] = 0x00;
res[1] = 0x10;
res[2] = 0x80;
break;
case 7: {
const u8 commandStart = packet[COMMAND_START];
switch (commandStart) {
case 0:
case 1:
case 3:
break;
case 2:
{
auto now = std::time(nullptr);
const 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;
res[8] = 0x80;
}
break;
default:
panic("Invalid read RTC block {}", commandStart);
}
} break;
case 8:
res[0] = 0x00;
break;
default:
panic("Invalid PIF command: {:X}", commandIndex);
}
i += commandLength + reslen;
}
}
}
void PIF::ProcessCommands() {
const u8 control = ram[63];
if (control & 1) {
ConfigureJoyBusFrame();
}
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) {
MaybeLoadMempak();
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] = DataCRC(res);
}
void PIF::MempakWrite(u8 *cmd, u8 *res) {
MaybeLoadMempak();
// 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] = DataCRC(&cmd[5]);
}
void PIF::EepromRead(const u8 *cmd, u8 *res) const {
n64::Mem& mem = n64::Core::GetMem();
assert(mem.saveType == SAVE_EEPROM_4k || mem.saveType == SAVE_EEPROM_16k);
if (channel == 4) {
const u8 offset = cmd[3];
if ((offset * 8) >= GetSaveSize(mem.saveType)) {
panic("Out of range EEPROM read! offset: {:02X}", offset);
}
std::copy_n(eeprom.begin() + offset * 8, 8, res);
} else {
panic("EEPROM read on bad channel {}", channel);
}
}
void PIF::EepromWrite(const u8 *cmd, u8 *res) {
n64::Mem& mem = n64::Core::GetMem();
assert(mem.saveType == SAVE_EEPROM_4k || mem.saveType == SAVE_EEPROM_16k);
if (channel == 4) {
const u8 offset = cmd[3];
if ((offset * 8) >= GetSaveSize(mem.saveType)) {
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 {
panic("EEPROM write on bad channel {}", channel);
}
}
void PIF::HLE(const bool pal, const CICType cicType) const {
n64::Mem& mem = n64::Core::GetMem();
n64::Registers& regs = n64::Core::GetRegs();
mem.Write<u32>(PIF_RAM_REGION_START + 0x24, cicSeeds[cicType]);
switch (cicType) {
case UNKNOWN_CIC_TYPE:
warn("Unknown CIC type!");
break;
case CIC_NUS_6101:
regs.Write<u64>(0, 0x0000000000000000);
regs.Write<u64>(1, 0x0000000000000000);
regs.Write<u64>(2, 0xFFFFFFFFDF6445CC);
regs.Write<u64>(3, 0xFFFFFFFFDF6445CC);
regs.Write<u64>(4, 0x00000000000045CC);
regs.Write<u64>(5, 0x0000000073EE317A);
regs.Write<u64>(6, 0xFFFFFFFFA4001F0C);
regs.Write<u64>(7, 0xFFFFFFFFA4001F08);
regs.Write<u64>(8, 0x00000000000000C0);
regs.Write<u64>(9, 0x0000000000000000);
regs.Write<u64>(10, 0x0000000000000040);
regs.Write<u64>(11, 0xFFFFFFFFA4000040);
regs.Write<u64>(12, 0xFFFFFFFFC7601FAC);
regs.Write<u64>(13, 0xFFFFFFFFC7601FAC);
regs.Write<u64>(14, 0xFFFFFFFFB48E2ED6);
regs.Write<u64>(15, 0xFFFFFFFFBA1A7D4B);
regs.Write<u64>(16, 0x0000000000000000);
regs.Write<u64>(17, 0x0000000000000000);
regs.Write<u64>(18, 0x0000000000000000);
regs.Write<u64>(19, 0x0000000000000000);
regs.Write<u64>(20, 0x0000000000000001);
regs.Write<u64>(21, 0x0000000000000000);
regs.Write<u64>(23, 0x0000000000000001);
regs.Write<u64>(24, 0x0000000000000002);
regs.Write<u64>(25, 0xFFFFFFFF905F4718);
regs.Write<u64>(26, 0x0000000000000000);
regs.Write<u64>(27, 0x0000000000000000);
regs.Write<u64>(28, 0x0000000000000000);
regs.Write<u64>(29, 0xFFFFFFFFA4001FF0);
regs.Write<u64>(30, 0x0000000000000000);
regs.Write<u64>(31, 0xFFFFFFFFA4001550);
regs.lo = 0xFFFFFFFFBA1A7D4Bll;
regs.hi = 0xFFFFFFFF997EC317ll;
break;
case CIC_NUS_7102:
regs.Write<u64>(0, 0x0000000000000000);
regs.Write<u64>(1, 0x0000000000000001);
regs.Write<u64>(2, 0x000000001E324416);
regs.Write<u64>(3, 0x000000001E324416);
regs.Write<u64>(4, 0x0000000000004416);
regs.Write<u64>(5, 0x000000000EC5D9AF);
regs.Write<u64>(6, 0xFFFFFFFFA4001F0C);
regs.Write<u64>(7, 0xFFFFFFFFA4001F08);
regs.Write<u64>(8, 0x00000000000000C0);
regs.Write<u64>(9, 0x0000000000000000);
regs.Write<u64>(10, 0x0000000000000040);
regs.Write<u64>(11, 0xFFFFFFFFA4000040);
regs.Write<u64>(12, 0x00000000495D3D7B);
regs.Write<u64>(13, 0xFFFFFFFF8B3DFA1E);
regs.Write<u64>(14, 0x000000004798E4D4);
regs.Write<u64>(15, 0xFFFFFFFFF1D30682);
regs.Write<u64>(16, 0x0000000000000000);
regs.Write<u64>(17, 0x0000000000000000);
regs.Write<u64>(18, 0x0000000000000000);
regs.Write<u64>(19, 0x0000000000000000);
regs.Write<u64>(20, 0x0000000000000000);
regs.Write<u64>(21, 0x0000000000000000);
regs.Write<u64>(22, 0x000000000000003F);
regs.Write<u64>(23, 0x0000000000000007);
regs.Write<u64>(24, 0x0000000000000000);
regs.Write<u64>(25, 0x0000000013D05CAB);
regs.Write<u64>(26, 0x0000000000000000);
regs.Write<u64>(27, 0x0000000000000000);
regs.Write<u64>(28, 0x0000000000000000);
regs.Write<u64>(29, 0xFFFFFFFFA4001FF0);
regs.Write<u64>(30, 0x0000000000000000);
regs.Write<u64>(31, 0xFFFFFFFFA4001554);
regs.lo = 0xFFFFFFFFF1D30682ll;
regs.hi = 0x0000000010054A98;
break;
case CIC_NUS_6102_7101:
regs.Write<u64>(0, 0x0000000000000000);
regs.Write<u64>(1, 0x0000000000000001);
regs.Write<u64>(2, 0x000000000EBDA536);
regs.Write<u64>(3, 0x000000000EBDA536);
regs.Write<u64>(4, 0x000000000000A536);
regs.Write<u64>(5, 0xFFFFFFFFC0F1D859);
regs.Write<u64>(6, 0xFFFFFFFFA4001F0C);
regs.Write<u64>(7, 0xFFFFFFFFA4001F08);
regs.Write<u64>(8, 0x00000000000000C0);
regs.Write<u64>(9, 0x0000000000000000);
regs.Write<u64>(10, 0x0000000000000040);
regs.Write<u64>(11, 0xFFFFFFFFA4000040);
regs.Write<u64>(12, 0xFFFFFFFFED10D0B3);
regs.Write<u64>(13, 0x000000001402A4CC);
regs.Write<u64>(14, 0x000000002DE108EA);
regs.Write<u64>(15, 0x000000003103E121);
regs.Write<u64>(16, 0x0000000000000000);
regs.Write<u64>(17, 0x0000000000000000);
regs.Write<u64>(18, 0x0000000000000000);
regs.Write<u64>(19, 0x0000000000000000);
regs.Write<u64>(20, 0x0000000000000001);
regs.Write<u64>(21, 0x0000000000000000);
regs.Write<u64>(23, 0x0000000000000000);
regs.Write<u64>(24, 0x0000000000000000);
regs.Write<u64>(25, 0xFFFFFFFF9DEBB54F);
regs.Write<u64>(26, 0x0000000000000000);
regs.Write<u64>(27, 0x0000000000000000);
regs.Write<u64>(28, 0x0000000000000000);
regs.Write<u64>(29, 0xFFFFFFFFA4001FF0);
regs.Write<u64>(30, 0x0000000000000000);
regs.Write<u64>(31, 0xFFFFFFFFA4001550);
regs.hi = 0x000000003FC18657;
regs.lo = 0x000000003103E121;
if (pal) {
regs.Write<u64>(20, 0x0000000000000000);
regs.Write<u64>(23, 0x0000000000000006);
regs.Write<u64>(31, 0xFFFFFFFFA4001554);
}
break;
case CIC_NUS_6103_7103:
regs.Write<u64>(0, 0x0000000000000000);
regs.Write<u64>(1, 0x0000000000000001);
regs.Write<u64>(2, 0x0000000049A5EE96);
regs.Write<u64>(3, 0x0000000049A5EE96);
regs.Write<u64>(4, 0x000000000000EE96);
regs.Write<u64>(5, 0xFFFFFFFFD4646273);
regs.Write<u64>(6, 0xFFFFFFFFA4001F0C);
regs.Write<u64>(7, 0xFFFFFFFFA4001F08);
regs.Write<u64>(8, 0x00000000000000C0);
regs.Write<u64>(9, 0x0000000000000000);
regs.Write<u64>(10, 0x0000000000000040);
regs.Write<u64>(11, 0xFFFFFFFFA4000040);
regs.Write<u64>(12, 0xFFFFFFFFCE9DFBF7);
regs.Write<u64>(13, 0xFFFFFFFFCE9DFBF7);
regs.Write<u64>(14, 0x000000001AF99984);
regs.Write<u64>(15, 0x0000000018B63D28);
regs.Write<u64>(16, 0x0000000000000000);
regs.Write<u64>(17, 0x0000000000000000);
regs.Write<u64>(18, 0x0000000000000000);
regs.Write<u64>(19, 0x0000000000000000);
regs.Write<u64>(20, 0x0000000000000001);
regs.Write<u64>(21, 0x0000000000000000);
regs.Write<u64>(23, 0x0000000000000000);
regs.Write<u64>(24, 0x0000000000000000);
regs.Write<u64>(25, 0xFFFFFFFF825B21C9);
regs.Write<u64>(26, 0x0000000000000000);
regs.Write<u64>(27, 0x0000000000000000);
regs.Write<u64>(28, 0x0000000000000000);
regs.Write<u64>(29, 0xFFFFFFFFA4001FF0);
regs.Write<u64>(30, 0x0000000000000000);
regs.Write<u64>(31, 0xFFFFFFFFA4001550);
regs.lo = 0x0000000018B63D28;
regs.hi = 0x00000000625C2BBE;
if (pal) {
regs.Write<u64>(20, 0x0000000000000000);
regs.Write<u64>(23, 0x0000000000000006);
regs.Write<u64>(31, 0xFFFFFFFFA4001554);
}
break;
case CIC_NUS_6105_7105:
regs.Write<u64>(0, 0x0000000000000000);
regs.Write<u64>(1, 0x0000000000000000);
regs.Write<u64>(2, 0xFFFFFFFFF58B0FBF);
regs.Write<u64>(3, 0xFFFFFFFFF58B0FBF);
regs.Write<u64>(4, 0x0000000000000FBF);
regs.Write<u64>(5, 0xFFFFFFFFDECAAAD1);
regs.Write<u64>(6, 0xFFFFFFFFA4001F0C);
regs.Write<u64>(7, 0xFFFFFFFFA4001F08);
regs.Write<u64>(8, 0x00000000000000C0);
regs.Write<u64>(9, 0x0000000000000000);
regs.Write<u64>(10, 0x0000000000000040);
regs.Write<u64>(11, 0xFFFFFFFFA4000040);
regs.Write<u64>(12, 0xFFFFFFFF9651F81E);
regs.Write<u64>(13, 0x000000002D42AAC5);
regs.Write<u64>(14, 0x00000000489B52CF);
regs.Write<u64>(15, 0x0000000056584D60);
regs.Write<u64>(16, 0x0000000000000000);
regs.Write<u64>(17, 0x0000000000000000);
regs.Write<u64>(18, 0x0000000000000000);
regs.Write<u64>(19, 0x0000000000000000);
regs.Write<u64>(20, 0x0000000000000001);
regs.Write<u64>(21, 0x0000000000000000);
regs.Write<u64>(23, 0x0000000000000000);
regs.Write<u64>(24, 0x0000000000000002);
regs.Write<u64>(25, 0xFFFFFFFFCDCE565F);
regs.Write<u64>(26, 0x0000000000000000);
regs.Write<u64>(27, 0x0000000000000000);
regs.Write<u64>(28, 0x0000000000000000);
regs.Write<u64>(29, 0xFFFFFFFFA4001FF0);
regs.Write<u64>(30, 0x0000000000000000);
regs.Write<u64>(31, 0xFFFFFFFFA4001550);
regs.lo = 0x0000000056584D60;
regs.hi = 0x000000004BE35D1F;
if (pal) {
regs.Write<u64>(20, 0x0000000000000000);
regs.Write<u64>(23, 0x0000000000000006);
regs.Write<u64>(31, 0xFFFFFFFFA4001554);
}
mem.Write<u32>(IMEM_REGION_START + 0x00, 0x3C0DBFC0);
mem.Write<u32>(IMEM_REGION_START + 0x04, 0x8DA807FC);
mem.Write<u32>(IMEM_REGION_START + 0x08, 0x25AD07C0);
mem.Write<u32>(IMEM_REGION_START + 0x0C, 0x31080080);
mem.Write<u32>(IMEM_REGION_START + 0x10, 0x5500FFFC);
mem.Write<u32>(IMEM_REGION_START + 0x14, 0x3C0DBFC0);
mem.Write<u32>(IMEM_REGION_START + 0x18, 0x8DA80024);
mem.Write<u32>(IMEM_REGION_START + 0x1C, 0x3C0BB000);
break;
case CIC_NUS_6106_7106:
regs.Write<u64>(0, 0x0000000000000000);
regs.Write<u64>(1, 0x0000000000000000);
regs.Write<u64>(2, 0xFFFFFFFFA95930A4);
regs.Write<u64>(3, 0xFFFFFFFFA95930A4);
regs.Write<u64>(4, 0x00000000000030A4);
regs.Write<u64>(5, 0xFFFFFFFFB04DC903);
regs.Write<u64>(6, 0xFFFFFFFFA4001F0C);
regs.Write<u64>(7, 0xFFFFFFFFA4001F08);
regs.Write<u64>(8, 0x00000000000000C0);
regs.Write<u64>(9, 0x0000000000000000);
regs.Write<u64>(10, 0x0000000000000040);
regs.Write<u64>(11, 0xFFFFFFFFA4000040);
regs.Write<u64>(12, 0xFFFFFFFFBCB59510);
regs.Write<u64>(13, 0xFFFFFFFFBCB59510);
regs.Write<u64>(14, 0x000000000CF85C13);
regs.Write<u64>(15, 0x000000007A3C07F4);
regs.Write<u64>(16, 0x0000000000000000);
regs.Write<u64>(17, 0x0000000000000000);
regs.Write<u64>(18, 0x0000000000000000);
regs.Write<u64>(19, 0x0000000000000000);
regs.Write<u64>(20, 0x0000000000000001);
regs.Write<u64>(21, 0x0000000000000000);
regs.Write<u64>(23, 0x0000000000000000);
regs.Write<u64>(24, 0x0000000000000002);
regs.Write<u64>(25, 0x00000000465E3F72);
regs.Write<u64>(26, 0x0000000000000000);
regs.Write<u64>(27, 0x0000000000000000);
regs.Write<u64>(28, 0x0000000000000000);
regs.Write<u64>(29, 0xFFFFFFFFA4001FF0);
regs.Write<u64>(30, 0x0000000000000000);
regs.Write<u64>(31, 0xFFFFFFFFA4001550);
regs.lo = 0x000000007A3C07F4;
regs.hi = 0x0000000023953898;
if (pal) {
regs.Write<u64>(20, 0x0000000000000000);
regs.Write<u64>(23, 0x0000000000000006);
regs.Write<u64>(31, 0xFFFFFFFFA4001554);
}
break;
}
regs.Write<u8>(22, (cicSeeds[cicType] >> 8) & 0xFF);
regs.cop0.Reset();
mem.Write<u32>(0x04300004, 0x01010101);
std::copy_n(mem.rom.cart.begin(), 0x1000, mem.mmio.rsp.dmem.begin());
regs.SetPC32(static_cast<s32>(0xA4000040));
}
void PIF::Execute() const {
n64::Mem& mem = n64::Core::GetMem();
const CICType cicType = mem.rom.cicType;
const bool pal = mem.rom.pal;
mem.Write<u32>(PIF_RAM_REGION_START + 0x24, cicSeeds[cicType]);
switch (cicType) {
case UNKNOWN_CIC_TYPE:
warn("Unknown CIC type!");
break;
case CIC_NUS_6101 ... CIC_NUS_6103_7103:
mem.Write<u32>(0x318, RDRAM_SIZE);
break;
case CIC_NUS_6105_7105:
mem.Write<u32>(0x3F0, RDRAM_SIZE);
break;
case CIC_NUS_6106_7106:
break;
}
HLE(pal, cicType);
}
} // namespace n64
+233
View File
@@ -0,0 +1,233 @@
#pragma once
#include <GameDB.hpp>
#include <MemoryRegions.hpp>
#include <array>
#include <filesystem>
#include <mio/mmap.hpp>
#include <vector>
#include <MupenMovie.hpp>
namespace fs = std::filesystem;
namespace n64 {
enum AccessoryType : u8 { ACCESSORY_NONE, ACCESSORY_MEMPACK, ACCESSORY_RUMBLE_PACK };
struct Controller {
union {
struct {
union {
u8 byte1;
struct {
bool dpRight : 1;
bool dpLeft : 1;
bool dpDown : 1;
bool dpUp : 1;
bool start : 1;
bool z : 1;
bool b : 1;
bool a : 1;
};
};
union {
u8 byte2;
struct {
bool cRight : 1;
bool cLeft : 1;
bool cDown : 1;
bool cUp : 1;
bool r : 1;
bool l : 1;
bool zero : 1;
bool joyReset : 1;
};
};
s8 joyX;
s8 joyY;
};
u32 raw;
};
Controller &operator=(const Controller &other) {
byte1 = other.byte1;
byte2 = other.byte2;
joyX = other.joyX;
joyY = other.joyY;
return *this;
}
enum Key { A, B, Z, Start, DUp, DDown, DLeft, DRight, CUp, CDown, CLeft, CRight, LT, RT };
enum Axis { X, Y };
Controller() = default;
void UpdateButton(Key k, bool state) {
switch (k) {
case A:
a = state;
break;
case B:
b = state;
break;
case Z:
z = state;
break;
case Start:
start = state;
break;
case DUp:
dpUp = state;
break;
case DDown:
dpDown = state;
break;
case DLeft:
dpLeft = state;
break;
case DRight:
dpRight = state;
break;
case CUp:
cUp = state;
break;
case CDown:
cDown = state;
break;
case CLeft:
cLeft = state;
break;
case CRight:
cRight = state;
break;
case LT:
l = state;
break;
case RT:
r = state;
break;
}
}
void UpdateAxis(Axis a, s8 state) {
switch (a) {
case X:
joyX = state;
break;
case Y:
joyY = state;
break;
}
}
Controller &operator=(u32 v) {
joyY = v & 0xff;
joyX = v >> 8;
byte2 = v >> 16;
byte1 = v >> 24;
return *this;
}
};
static_assert(sizeof(Controller) == 4);
enum JoybusType : u8 {
JOYBUS_NONE,
JOYBUS_CONTROLLER,
JOYBUS_DANCEPAD,
JOYBUS_VRU,
JOYBUS_MOUSE,
JOYBUS_RANDNET_KEYBOARD,
JOYBUS_DENSHA_DE_GO,
JOYBUS_4KB_EEPROM,
JOYBUS_16KB_EEPROM
};
struct JoybusDevice {
JoybusType type{};
AccessoryType accessoryType{};
Controller controller{};
JoybusDevice() = default;
};
// https://github.com/ares-emulator/ares/blob/master/ares/n64/cic/cic.cpp
// https://github.com/ares-emulator/ares/blob/master/LICENSE
constexpr u32 cicSeeds[] = {
0x0,
0x00043F3F, // CIC_NUS_6101
0x00043F3F, // CIC_NUS_7102
0x00043F3F, // CIC_NUS_6102_7101
0x00047878, // CIC_NUS_6103_7103
0x00049191, // CIC_NUS_6105_7105
0x00048585, // CIC_NUS_6106_7106
};
enum CICType {
UNKNOWN_CIC_TYPE,
CIC_NUS_6101,
CIC_NUS_7102,
CIC_NUS_6102_7101,
CIC_NUS_6103_7103,
CIC_NUS_6105_7105,
CIC_NUS_6106_7106
};
struct PIF {
void Reset();
void MaybeLoadMempak();
void LoadEeprom(SaveType, const std::string &);
void ProcessCommands();
void InitDevices(SaveType);
void CICChallenge();
void Execute() const;
void HLE(bool pal, CICType cicType) const;
bool ReadButtons(u8 *);
void ControllerID(u8 *) const;
void MempakRead(const u8 *, u8 *);
void MempakWrite(u8 *, u8 *);
void EepromRead(const u8 *, u8 *) const;
void EepromWrite(const u8 *, u8 *);
void UpdateButton(int index, Controller::Key k, bool state) {
joybusDevices[index].controller.UpdateButton(k, state);
}
void UpdateAxis(int index, Controller::Axis a, s8 state) { joybusDevices[index].controller.UpdateAxis(a, state); }
bool mempakOpen = false;
std::array<u8, PIF_BOOTROM_SIZE> bootrom{};
std::array<u8, PIF_RAM_SIZE> ram{};
int channel = 0;
std::array<JoybusDevice, 6> joybusDevices{};
mio::mmap_sink mempak, eeprom;
std::string mempakPath{}, eepromPath{};
size_t eepromSize{};
MupenMovie movie;
[[nodiscard]] FORCE_INLINE u8 Read(u32 addr) const {
addr &= 0x7FF;
if (addr < 0x7c0)
return bootrom[addr];
return ram[addr & PIF_RAM_DSIZE];
}
FORCE_INLINE void Write(u32 addr, const u8 val) {
addr &= 0x7FF;
if (addr < 0x7c0)
return;
ram[addr & PIF_RAM_DSIZE] = val;
}
[[nodiscard]] FORCE_INLINE AccessoryType GetAccessoryType() const {
if (channel >= 4 || joybusDevices[channel].type != JOYBUS_CONTROLLER) {
return ACCESSORY_NONE;
}
return joybusDevices[channel].accessoryType;
}
private:
void ConfigureJoyBusFrame();
};
} // namespace n64
+121
View File
@@ -0,0 +1,121 @@
#include <Netplay.hpp>
#include <PIF.hpp>
#include <PIF/MupenMovie.hpp>
#include <log.hpp>
namespace n64 {
void PIF::InitDevices(SaveType saveType) {
joybusDevices[0].type = JOYBUS_CONTROLLER;
joybusDevices[0].accessoryType = ACCESSORY_MEMPACK;
for (int i = 1; i < 4; i++) { // TODO: make this configurable
joybusDevices[i].type = JOYBUS_NONE;
joybusDevices[i].accessoryType = ACCESSORY_NONE;
}
if (saveType == SAVE_EEPROM_4k) {
joybusDevices[4].type = JOYBUS_4KB_EEPROM;
} else if (saveType == SAVE_EEPROM_16k) {
joybusDevices[4].type = JOYBUS_16KB_EEPROM;
} else {
joybusDevices[4].type = JOYBUS_NONE;
}
joybusDevices[5].type = JOYBUS_NONE;
}
void PIF::ControllerID(u8 *res) const {
if (channel < 6) {
switch (joybusDevices[channel].type) {
case JOYBUS_NONE:
res[0] = 0x00;
res[1] = 0x00;
res[2] = 0x00;
break;
case JOYBUS_CONTROLLER:
res[0] = 0x05;
res[1] = 0x00;
res[2] = joybusDevices[channel].accessoryType != ACCESSORY_NONE ? 0x01 : 0x02;
break;
case JOYBUS_DANCEPAD:
res[0] = 0x05;
res[1] = 0x00;
res[2] = 0x00;
break;
case JOYBUS_VRU:
res[0] = 0x00;
res[1] = 0x01;
res[2] = 0x00;
break;
case JOYBUS_MOUSE:
res[0] = 0x02;
res[1] = 0x00;
res[2] = 0x00;
break;
case JOYBUS_RANDNET_KEYBOARD:
res[0] = 0x00;
res[1] = 0x02;
res[2] = 0x00;
break;
case JOYBUS_DENSHA_DE_GO:
res[0] = 0x20;
res[1] = 0x04;
res[2] = 0x00;
break;
case JOYBUS_4KB_EEPROM:
res[0] = 0x00;
res[1] = 0x80;
res[2] = 0x00;
break;
case JOYBUS_16KB_EEPROM:
res[0] = 0x00;
res[1] = 0xC0;
res[2] = 0x00;
break;
}
} else {
panic("Device ID on unknown channel {}", channel);
}
}
bool PIF::ReadButtons(u8 *res) {
if (channel >= 6) {
res[0] = 0;
res[1] = 0;
res[2] = 0;
res[3] = 0;
return false;
}
switch (joybusDevices[channel].type) {
case JOYBUS_NONE:
res[0] = 0x00;
res[1] = 0x00;
res[2] = 0x00;
res[3] = 0x00;
return false; // Device not present
case JOYBUS_4KB_EEPROM:
case JOYBUS_16KB_EEPROM:
case JOYBUS_CONTROLLER:
if (movie.IsLoaded()) {
const Controller controller = movie.NextInputs();
res[0] = controller.byte1;
res[1] = controller.byte2;
res[2] = controller.joyX;
res[3] = controller.joyY;
} else {
res[0] = joybusDevices[channel].controller.byte1;
res[1] = joybusDevices[channel].controller.byte2;
res[2] = joybusDevices[channel].controller.joyX;
res[3] = joybusDevices[channel].controller.joyY;
}
return true;
case JOYBUS_DANCEPAD:
case JOYBUS_VRU:
case JOYBUS_MOUSE:
case JOYBUS_RANDNET_KEYBOARD:
case JOYBUS_DENSHA_DE_GO:
return false;
}
return true;
}
} // namespace n64
+138
View File
@@ -0,0 +1,138 @@
#include <cstring>
#include <PIF/MupenMovie.hpp>
#include <File.hpp>
#include <PIF.hpp>
union TASMovieControllerData {
struct {
unsigned dpadRight : 1;
unsigned dpadLeft : 1;
unsigned dpadDown : 1;
unsigned dpadUp : 1;
unsigned start : 1;
unsigned z : 1;
unsigned b : 1;
unsigned a : 1;
unsigned cRight : 1;
unsigned cLeft : 1;
unsigned cDown : 1;
unsigned cUp : 1;
unsigned r : 1;
unsigned l : 1;
unsigned : 2;
signed analogX : 8;
signed analogY : 8;
};
u32 raw;
} __attribute__((packed));
static_assert(sizeof(TASMovieControllerData) == 4);
void MupenMovie::Load(const fs::path &path) {
filename = path.stem().string();
loadedTasMovie = Util::ReadFileBinary(path.string());
if (!IsLoaded()) {
error("Error loading movie!");
return;
}
std::memcpy(&loadedTasMovieHeader, loadedTasMovie.data(), sizeof(TASMovieHeader));
if (loadedTasMovieHeader.signature[0] != 0x4D || loadedTasMovieHeader.signature[1] != 0x36 ||
loadedTasMovieHeader.signature[2] != 0x34 || loadedTasMovieHeader.signature[3] != 0x1A) {
error("Failed to load movie: incorrect signature. Are you sure this is a valid movie?");
return;
}
if (loadedTasMovieHeader.version != 3) {
error("This movie is version {}: only version 3 is supported.", loadedTasMovieHeader.version);
return;
}
if (loadedTasMovieHeader.startType != 2) {
error("Movie start type is {} - only movies with a start type of 2 are supported (start at power on)",
loadedTasMovieHeader.startType);
return;
}
info("Loaded movie '{}' ", loadedTasMovieHeader.movie_description);
info("by {}", loadedTasMovieHeader.author_name);
info("{} controller(s) connected", loadedTasMovieHeader.numControllers);
if (loadedTasMovieHeader.numControllers != 1) {
error("Currently, only movies with 1 controller connected are supported.");
return;
}
loadedTasMovieIndex = sizeof(TASMovieHeader) - 4; // skip header
}
MupenMovie::MupenMovie(const fs::path &path) {
Load(path);
}
void MupenMovie::Reset() {
if (!IsLoaded())
return;
loadedTasMovieIndex = sizeof(TASMovieHeader) - 4; // skip header
}
FORCE_INLINE void LogController(const n64::Controller &controller) {
debug("c_right: {}", controller.cRight);
debug("c_left: {}", controller.cLeft);
debug("c_down: {}", controller.cDown);
debug("c_up: {}", controller.cUp);
debug("r: {}", controller.r);
debug("l: {}", controller.l);
debug("dp_right: {}", controller.dpRight);
debug("dp_left: {}", controller.dpLeft);
debug("dp_down: {}", controller.dpDown);
debug("dp_up: {}", controller.dpUp);
debug("z: {}", controller.z);
debug("b: {}", controller.b);
debug("a: {}", controller.a);
debug("start: {}", controller.start);
debug("joy_x: {}", controller.joyX);
debug("joy_y: {}", controller.joyY);
}
n64::Controller MupenMovie::NextInputs() {
if (loadedTasMovieIndex + sizeof(TASMovieControllerData) > loadedTasMovie.size()) {
loadedTasMovie.clear();
n64::Controller emptyController{};
return emptyController;
}
TASMovieControllerData movieCData{};
memcpy(&movieCData, &loadedTasMovie[loadedTasMovieIndex], sizeof(TASMovieControllerData));
loadedTasMovieIndex += sizeof(TASMovieControllerData);
n64::Controller controller{};
controller.cRight = movieCData.cRight;
controller.cLeft = movieCData.cLeft;
controller.cDown = movieCData.cDown;
controller.cUp = movieCData.cUp;
controller.r = movieCData.r;
controller.l = movieCData.l;
controller.dpRight = movieCData.dpadRight;
controller.dpLeft = movieCData.dpadLeft;
controller.dpDown = movieCData.dpadDown;
controller.dpUp = movieCData.dpadUp;
controller.z = movieCData.z;
controller.b = movieCData.b;
controller.a = movieCData.a;
controller.start = movieCData.start;
controller.joyX = movieCData.analogX;
controller.joyY = movieCData.analogY;
LogController(controller);
return controller;
}
+62
View File
@@ -0,0 +1,62 @@
#pragma once
#include <common.hpp>
#include <filesystem>
#include <vector>
namespace fs = std::filesystem;
namespace n64 {
struct Controller;
}
struct TASMovieHeader {
u8 signature[4];
u32 version;
u32 uid;
u32 numFrames;
u32 rerecords;
u8 fps;
u8 numControllers;
u8 reserved1;
u8 reserved2;
u32 numInputSamples;
uint16_t startType;
u8 reserved3;
u8 reserved4;
u32 controllerFlags;
u8 reserved5[160];
char romName[32];
u32 romCrc32;
uint16_t romCountryCode;
u8 reserved6[56];
// 122 64-byte ASCII string: name of video plugin used when recording, directly from plugin
char video_plugin_name[64];
// 162 64-byte ASCII string: name of sound plugin used when recording, directly from plugin
char audio_plugin_name[64];
// 1A2 64-byte ASCII string: name of input plugin used when recording, directly from plugin
char input_plugin_name[64];
// 1E2 64-byte ASCII string: name of rsp plugin used when recording, directly from plugin
char rsp_plugin_name[64];
// 222 222-byte UTF-8 string: author name info
char author_name[222];
// 300 256-byte UTF-8 string: author movie description info
char movie_description[256];
} __attribute__((packed));
static_assert(sizeof(TASMovieHeader) == 1024);
struct MupenMovie {
MupenMovie() = default;
explicit MupenMovie(const fs::path &);
void Load(const fs::path &);
void Reset();
n64::Controller NextInputs();
[[nodiscard]] bool IsLoaded() const { return !loadedTasMovie.empty(); }
[[nodiscard]] const std::string &GetFilename() const { return filename; }
private:
std::string filename{};
std::vector<u8> loadedTasMovie = {};
TASMovieHeader loadedTasMovieHeader = {};
uint32_t loadedTasMovieIndex = 0;
};
+47
View File
@@ -0,0 +1,47 @@
#include <core/mmio/RI.hpp>
#include <log.hpp>
namespace n64 {
RI::RI() { Reset(); }
void RI::Reset() {
mode = 0xE;
config = 0x40;
select = 0x14;
refresh = 0x63634;
}
auto RI::Read(u32 addr) const -> u32 {
switch (addr) {
case 0x04700000:
return mode;
case 0x04700004:
return config;
case 0x0470000C:
return select;
case 0x04700010:
return refresh;
default:
panic("Unhandled RI[{:08X}] read", addr);
}
}
void RI::Write(u32 addr, u32 val) {
switch (addr) {
case 0x04700000:
mode = val;
break;
case 0x04700004:
config = val;
break;
case 0x0470000C:
select = val;
break;
case 0x04700010:
refresh = val;
break;
default:
panic("Unhandled RI[{:08X}] write with val {:08X}", addr, val);
}
}
} // namespace n64
+14
View File
@@ -0,0 +1,14 @@
#pragma once
#include <common.hpp>
namespace n64 {
struct RI {
RI();
void Reset();
auto Read(u32) const -> u32;
void Write(u32, u32);
u32 mode{0xE}, config{0x40}, select{0x14}, refresh{0x63634};
};
} // namespace n64
+95
View File
@@ -0,0 +1,95 @@
#include <Scheduler.hpp>
#include <Core.hpp>
namespace n64 {
SI::SI() { Reset(); }
void SI::Reset() {
status.raw = 0;
dramAddr = 0;
pifAddr = 0;
toDram = false;
pif.Reset();
}
auto SI::Read(u32 addr) const -> u32 {
n64::Mem& mem = n64::Core::GetMem();
switch (addr) {
case 0x04800000:
return dramAddr;
case 0x04800004:
case 0x04800010:
return pifAddr;
case 0x0480000C:
return 0;
case 0x04800018:
{
u32 val = 0;
val |= status.dmaBusy;
val |= (0 << 1);
val |= (0 << 3);
val |= (mem.mmio.mi.miIntr.si << 12);
return val;
}
default:
panic("Unhandled SI[{:08X}] read", addr);
}
}
// pif -> rdram
template <>
void SI::DMA<true>() {
n64::Mem& mem = n64::Core::GetMem();
pif.ProcessCommands();
for (int i = 0; i < 64; i++) {
mem.mmio.rdp.WriteRDRAM<u8>(dramAddr + i, pif.Read(pifAddr + i));
}
trace("SI DMA from PIF RAM to RDRAM ({:08X} to {:08X})", pifAddr, dramAddr);
}
// rdram -> pif
template <>
void SI::DMA<false>() {
n64::Mem& mem = n64::Core::GetMem();
for (int i = 0; i < 64; i++) {
pif.Write(pifAddr + i, mem.mmio.rdp.ReadRDRAM<u8>(dramAddr + i));
}
trace("SI DMA from RDRAM to PIF RAM ({:08X} to {:08X})", dramAddr, pifAddr);
}
void SI::DMA() {
n64::Mem& mem = n64::Core::GetMem();
status.dmaBusy = false;
if (toDram)
DMA<true>();
else
DMA<false>();
mem.mmio.mi.InterruptRaise(MI::Interrupt::SI);
}
void SI::Write(u32 addr, u32 val) {
n64::Mem& mem = n64::Core::GetMem();
switch (addr) {
case 0x04800000:
dramAddr = val & RDRAM_DSIZE;
break;
case 0x04800004:
pifAddr = val & 0x1FFFFFFF;
status.dmaBusy = true;
toDram = true;
Scheduler::GetInstance().EnqueueRelative(SI_DMA_DELAY, SI_DMA);
break;
case 0x04800010:
pifAddr = val & 0x1FFFFFFF;
status.dmaBusy = true;
toDram = false;
Scheduler::GetInstance().EnqueueRelative(SI_DMA_DELAY, SI_DMA);
break;
case 0x04800018:
mem.mmio.mi.InterruptLower(MI::Interrupt::SI);
break;
default:
panic("Unhandled SI[{:08X}] write ({:08X})", addr, val);
}
}
} // namespace n64
+37
View File
@@ -0,0 +1,37 @@
#pragma once
#include <common.hpp>
#include <core/mmio/MI.hpp>
#include <core/mmio/PIF.hpp>
namespace n64 {
union SIStatus {
u32 raw{};
struct {
unsigned dmaBusy : 1;
unsigned ioBusy : 1;
unsigned reserved : 1;
unsigned dmaErr : 1;
unsigned : 8;
unsigned intr : 1;
};
};
struct SI {
SI();
void Reset();
[[nodiscard]] auto Read(u32) const -> u32;
void Write(u32, u32);
template <bool toDram>
void DMA();
void DMA();
bool toDram = false;
SIStatus status{};
u32 dramAddr{};
u32 pifAddr{};
PIF pif;
};
#define SI_DMA_DELAY (65536 * 2)
} // namespace n64
+127
View File
@@ -0,0 +1,127 @@
#include <Core.hpp>
#include <log.hpp>
namespace n64 {
VI::VI() { Reset(); }
void VI::Reset() {
status.raw = 0xF;
intr = 256;
origin = 0;
width = 320;
current = 0;
vsync = 0;
hsync = 0;
numHalflines = 262;
numFields = 1;
cyclesPerHalfline = 1000;
xscale = {}, yscale = {};
hsyncLeap = {}, burst = {}, vburst = {};
hstart = {}, vstart = {};
isPal = false;
swaps = {};
}
u32 VI::Read(const u32 paddr) const {
switch (paddr) {
case 0x04400000:
return status.raw;
case 0x04400004:
return origin;
case 0x04400008:
return width;
case 0x0440000C:
return intr;
case 0x04400010:
return current << 1;
case 0x04400014:
return burst.raw;
case 0x04400018:
return vsync;
case 0x0440001C:
return hsync;
case 0x04400020:
return hsyncLeap.raw;
case 0x04400024:
return hstart.raw;
case 0x04400028:
return vstart.raw;
case 0x0440002C:
return vburst;
case 0x04400030:
return xscale.raw;
case 0x04400034:
return yscale.raw;
default: {
n64::Registers& regs = n64::Core::GetRegs();
Util::Error::GetInstance().Throw(
{Util::Error::Severity::NON_FATAL}, {Util::Error::Type::MEM_UNHANDLED_ACCESS}, regs.pc,
Util::Error::MemoryAccess{false, Util::Error::MemoryAccess::WORD, paddr, 0}, "32-bit read access on unhandled VI register");
return 0;
}
}
}
void VI::Write(const u32 paddr, const u32 val) {
n64::Mem& mem = n64::Core::GetMem();
switch (paddr) {
case 0x04400000:
status.raw = val;
numFields = status.serrate ? 2 : 1;
break;
case 0x04400004:
{
const u32 masked = val & 0xFFFFFF;
if (origin != masked) {
swaps++;
}
origin = masked;
}
break;
case 0x04400008:
width = val & 0x7FF;
break;
case 0x0440000C:
intr = val & 0x3FF;
break;
case 0x04400010:
mem.mmio.mi.InterruptLower(MI::Interrupt::VI);
break;
case 0x04400014:
burst.raw = val;
break;
case 0x04400018:
vsync = val & 0x3FF;
numHalflines = vsync >> 1;
cyclesPerHalfline = GetCyclesPerFrame(isPal) / numHalflines;
break;
case 0x0440001C:
hsync = val & 0x3FF;
break;
case 0x04400020:
hsyncLeap.raw = val;
break;
case 0x04400024:
hstart.raw = val;
break;
case 0x04400028:
vstart.raw = val;
break;
case 0x0440002C:
vburst = val;
break;
case 0x04400030:
xscale.raw = val;
break;
case 0x04400034:
yscale.raw = val;
break;
case 0x04400038:
break;
case 0x0440003C:
break;
default:
panic("Unimplemented VI[{:08X}] write ({:08X})", paddr, val);
}
}
} // namespace n64
+90
View File
@@ -0,0 +1,90 @@
#pragma once
#include <common.hpp>
namespace n64 {
union VIBurst {
/*struct {
unsigned hsyncW:8;
unsigned burstW:8;
unsigned vsyncW:4;
unsigned burstStart:10;
unsigned:2;
};*/
u32 raw;
};
union VIHsyncLeap {
/*struct {
unsigned leapB:10;
unsigned:6;
unsigned leapA:10;
unsigned:6;
};*/
u32 raw;
};
union AxisScale {
u32 raw;
struct {
unsigned scaleDecimal : 10;
unsigned scaleInteger : 2;
unsigned subpixelOffsetDecimal : 10;
unsigned subpixelOffsetInteger : 2;
unsigned : 4;
};
struct {
unsigned scale : 12;
unsigned subpixelOffset : 12;
unsigned : 4;
};
};
enum VIFormat { blank = 0, reserved = 1, f5553 = 2, f8888 = 3 };
union VIStatus {
struct {
u8 type : 2;
bool gamma_dither_enable : 1;
bool gamma_enable : 1;
bool divot_enable : 1;
bool reserved_always_off : 1;
bool serrate : 1;
bool reserved_diagnostics_only : 1;
unsigned antialias_mode : 3;
unsigned : 21;
};
u32 raw;
};
union AxisStart {
u32 raw;
struct {
unsigned end : 10;
unsigned : 6;
unsigned start : 10;
unsigned : 6;
};
};
struct VI {
VI();
void Reset();
[[nodiscard]] u32 Read(u32) const;
void Write(u32, u32);
bool isPal = false;
AxisScale xscale{}, yscale{};
VIHsyncLeap hsyncLeap{};
VIStatus status{};
VIBurst burst{};
u32 vburst{};
u32 origin{}, width{}, current{};
u32 vsync{}, hsync{}, intr{};
AxisStart hstart{}, vstart{};
int swaps{};
int numHalflines{};
int numFields{};
int cyclesPerHalfline{};
};
} // namespace n64
@@ -0,0 +1,5 @@
file(GLOB_RECURSE SOURCES *.cpp)
file(GLOB_RECURSE HEADERS *.cpp)
add_library(registers ${SOURCES} ${HEADERS})
target_link_libraries(registers PRIVATE interpreter)
+551
View File
@@ -0,0 +1,551 @@
#include <Core.hpp>
#include <log.hpp>
namespace n64 {
Cop0::Cop0() { Reset(); }
void Cop0::Reset() {
cause.raw = 0xB000007C;
status.raw = 0;
status.cu0 = 1;
status.cu1 = 1;
status.fr = 1;
PRId = 0x00000B22;
Config = 0x7006E463;
EPC = 0xFFFFFFFFFFFFFFFFll;
ErrorEPC = 0xFFFFFFFFFFFFFFFFll;
wired = 0;
index.raw = 63;
badVaddr = 0xFFFFFFFFFFFFFFFF;
kernelMode = {true};
supervisorMode = {false};
userMode = {false};
is64BitAddressing = {false};
llbit = {};
pageMask = {};
entryHi = {};
entryLo0 = {}, entryLo1 = {};
context = {};
wired = {}, r7 = {};
count = {};
compare = {};
LLAddr = {}, WatchLo = {}, WatchHi = {};
xcontext = {};
r21 = {}, r22 = {}, r23 = {}, r24 = {}, r25 = {};
ParityError = {}, CacheError = {}, TagLo = {}, TagHi = {};
ErrorEPC = {};
r31 = {};
memset(tlb, 0, sizeof(TLBEntry) * 32);
tlbError = NONE;
openbus = {};
}
u32 Cop0::GetReg32(const u8 addr) {
switch (addr) {
case COP0_REG_INDEX:
return index.raw & INDEX_MASK;
case COP0_REG_RANDOM:
return GetRandom();
case COP0_REG_ENTRYLO0:
return entryLo0.raw;
case COP0_REG_ENTRYLO1:
return entryLo1.raw;
case COP0_REG_CONTEXT:
return context.raw;
case COP0_REG_PAGEMASK:
return pageMask.raw;
case COP0_REG_WIRED:
return wired;
case COP0_REG_BADVADDR:
return badVaddr;
case COP0_REG_COUNT:
return GetCount();
case COP0_REG_ENTRYHI:
return entryHi.raw;
case COP0_REG_COMPARE:
return compare;
case COP0_REG_STATUS:
return status.raw;
case COP0_REG_CAUSE:
return cause.raw;
case COP0_REG_EPC:
return EPC;
case COP0_REG_PRID:
return PRId;
case COP0_REG_CONFIG:
return Config;
case COP0_REG_LLADDR:
return LLAddr;
case COP0_REG_WATCHLO:
return WatchLo;
case COP0_REG_WATCHHI:
return WatchHi;
case COP0_REG_XCONTEXT:
return xcontext.raw;
case COP0_REG_PARITY_ERR:
return ParityError;
case COP0_REG_CACHE_ERR:
return CacheError;
case COP0_REG_TAGLO:
return TagLo;
case COP0_REG_TAGHI:
return TagHi;
case COP0_REG_ERROREPC:
return ErrorEPC;
case 7:
case 21:
case 22:
case 23:
case 24:
case 25:
case 31:
return openbus;
default:
panic("Unsupported word read from COP0 register {}", addr);
}
}
u64 Cop0::GetReg64(const u8 addr) const {
switch (addr) {
case COP0_REG_ENTRYLO0:
return entryLo0.raw;
case COP0_REG_ENTRYLO1:
return entryLo1.raw;
case COP0_REG_CONTEXT:
return context.raw;
case COP0_REG_BADVADDR:
return badVaddr;
case COP0_REG_ENTRYHI:
return entryHi.raw;
case COP0_REG_STATUS:
return status.raw;
case COP0_REG_EPC:
return EPC;
case COP0_REG_PRID:
return PRId;
case COP0_REG_LLADDR:
return LLAddr;
case COP0_REG_XCONTEXT:
return xcontext.raw & 0xFFFFFFFFFFFFFFF0;
case COP0_REG_ERROREPC:
return ErrorEPC;
case 7:
case 21:
case 22:
case 23:
case 24:
case 25:
case 31:
return openbus;
default:
panic("Unsupported dword read from COP0 register {}", addr);
}
}
void Cop0::SetReg32(const u8 addr, const u32 value) {
openbus = value & 0xFFFFFFFF;
switch (addr) {
case COP0_REG_INDEX:
index.raw = value & INDEX_MASK;
break;
case COP0_REG_RANDOM:
break;
case COP0_REG_ENTRYLO0:
entryLo0.raw = value & ENTRY_LO_MASK;
break;
case COP0_REG_ENTRYLO1:
entryLo1.raw = value & ENTRY_LO_MASK;
break;
case COP0_REG_CONTEXT:
context.raw = (s64(s32(value)) & 0xFFFFFFFFFF800000) | (context.raw & 0x7FFFFF);
break;
case COP0_REG_PAGEMASK:
pageMask.raw = value & PAGEMASK_MASK;
break;
case COP0_REG_WIRED:
wired = value & 63;
break;
case COP0_REG_BADVADDR:
break;
case COP0_REG_COUNT:
count = (u64)value << 1;
break;
case COP0_REG_ENTRYHI:
entryHi.raw = s64(s32(value)) & ENTRY_HI_MASK;
break;
case COP0_REG_COMPARE:
compare = value;
cause.ip7 = false;
break;
case COP0_REG_STATUS:
status.raw &= ~STATUS_MASK;
status.raw |= (value & STATUS_MASK);
Update();
break;
case COP0_REG_CAUSE:
{
Cop0Cause tmp{};
tmp.raw = value;
cause.ip0 = tmp.ip0;
cause.ip1 = tmp.ip1;
}
break;
case COP0_REG_EPC:
EPC = s64(s32(value));
break;
case COP0_REG_PRID:
break;
case COP0_REG_CONFIG:
Config &= ~CONFIG_MASK;
Config |= (value & CONFIG_MASK);
break;
case COP0_REG_LLADDR:
LLAddr = value;
break;
case COP0_REG_WATCHLO:
WatchLo = value;
break;
case COP0_REG_WATCHHI:
WatchHi = value;
break;
case COP0_REG_XCONTEXT:
xcontext.raw = (s64(s32(value)) & 0xFFFFFFFE00000000) | (xcontext.raw & 0x1FFFFFFFF);
break;
case COP0_REG_PARITY_ERR:
ParityError = value & 0xff;
break;
case COP0_REG_CACHE_ERR:
break;
case COP0_REG_TAGLO:
TagLo = value;
break;
case COP0_REG_TAGHI:
TagHi = value;
break;
case COP0_REG_ERROREPC:
ErrorEPC = s64(s32(value));
break;
case 7:
case 21:
case 22:
case 23:
case 24:
case 25:
case 31:
break;
default:
panic("Unsupported word write from COP0 register {}", addr);
}
}
void Cop0::SetReg64(const u8 addr, const u64 value) {
openbus = value;
switch (addr) {
case COP0_REG_ENTRYLO0:
entryLo0.raw = value & ENTRY_LO_MASK;
break;
case COP0_REG_ENTRYLO1:
entryLo1.raw = value & ENTRY_LO_MASK;
break;
case COP0_REG_CONTEXT:
context.raw = (value & 0xFFFFFFFFFF800000) | (context.raw & 0x7FFFFF);
break;
case COP0_REG_XCONTEXT:
xcontext.raw = (value & 0xFFFFFFFE00000000) | (xcontext.raw & 0x1FFFFFFFF);
break;
case COP0_REG_ENTRYHI:
entryHi.raw = value & ENTRY_HI_MASK;
break;
case COP0_REG_STATUS:
status.raw = value;
break;
case COP0_REG_CAUSE:
{
Cop0Cause tmp{};
tmp.raw = value;
cause.ip0 = tmp.ip0;
cause.ip1 = tmp.ip1;
}
break;
case COP0_REG_BADVADDR:
break;
case COP0_REG_EPC:
EPC = (s64)value;
break;
case COP0_REG_LLADDR:
LLAddr = value;
break;
case COP0_REG_ERROREPC:
ErrorEPC = (s64)value;
break;
default:
panic("Unsupported dword write to COP0 register {}", addr);
}
}
static FORCE_INLINE u64 getVPN(const u64 addr, const u64 pageMask) {
const u64 mask = pageMask | 0x1fff;
const u64 vpn = addr & 0xFFFFFFFFFF | addr >> 22 & 0x30000000000;
return vpn & ~mask;
}
TLBEntry *Cop0::TLBTryMatch(const u64 vaddr, int &index) {
for (int i = 0; i < 32; i++) {
TLBEntry *entry = &tlb[i];
if (!entry->initialized)
continue;
const u64 entry_vpn = getVPN(entry->entryHi.raw, entry->pageMask.raw);
const u64 vaddr_vpn = getVPN(vaddr, entry->pageMask.raw);
const bool vpn_match = entry_vpn == vaddr_vpn;
const bool asid_match = entry->global || entryHi.asid == entry->entryHi.asid;
if(!vpn_match || !asid_match)
continue;
index = i;
return entry;
}
return nullptr;
}
TLBEntry *Cop0::TLBTryMatch(const u64 vaddr) {
for (auto &t : tlb) {
TLBEntry *entry = &t;
if (!entry->initialized)
continue;
const u64 entry_vpn = getVPN(entry->entryHi.raw, entry->pageMask.raw);
const u64 vaddr_vpn = getVPN(vaddr, entry->pageMask.raw);
const bool vpn_match = entry_vpn == vaddr_vpn;
const bool asid_match = entry->global || entryHi.asid == entry->entryHi.asid;
if (vpn_match && asid_match)
return entry;
}
return nullptr;
}
bool Cop0::ProbeTLB(const TLBAccessType accessType, const u64 vaddr, u32 &paddr) {
const TLBEntry *entry = TLBTryMatch(vaddr);
if (!entry) {
tlbError = MISS;
return false;
}
const u32 mask = entry->pageMask.mask << 12 | 0xFFF;
const u32 odd = vaddr & mask + 1;
const EntryLo entryLo = odd ? entry->entryLo1 : entry->entryLo0;
if (!entryLo.v) {
tlbError = INVALID;
return false;
}
if (accessType == STORE && !entryLo.d) {
tlbError = MODIFICATION;
return false;
}
paddr = entryLo.pfn << 12 | vaddr & mask;
return true;
}
void Cop0::FireException(const ExceptionCode code, const int cop, s64 pc) {
Registers& regs = Core::GetRegs();
u16 vectorOffset = 0x0180;
if(tlbError == MISS && (code == ExceptionCode::TLBLoad || code == ExceptionCode::TLBStore)) {
if(!status.exl) {
if(is64BitAddressing) vectorOffset = 0x0080;
else vectorOffset = 0x0000;
}
}
cause.copError = cop;
cause.exceptionCode = static_cast<u8>(code);
if (!status.exl) {
if ((cause.branchDelay = regs.prevDelaySlot)) {
pc -= 4;
}
status.exl = true;
EPC = pc;
}
if (status.bev) {
panic("BEV bit set!");
}
regs.SetPC32(s32(0x80000000 + vectorOffset));
Update();
}
void Cop0::HandleTLBException(const u64 vaddr) {
const u64 vpn2 = vaddr >> 13 & 0x7FFFF;
const u64 xvpn2 = vaddr >> 13 & 0x7FFFFFF;
badVaddr = vaddr;
context.badvpn2 = vpn2;
xcontext.badvpn2 = xvpn2;
xcontext.r = vaddr >> 62 & 3;
entryHi.vpn2 = xvpn2;
entryHi.r = vaddr >> 62 & 3;
}
ExceptionCode Cop0::GetTLBExceptionCode(const TLBError error, const TLBAccessType accessType) {
switch (error) {
case NONE:
panic("Getting TLB exception with error NONE");
case INVALID:
case MISS:
return accessType == LOAD ? ExceptionCode::TLBLoad : ExceptionCode::TLBStore;
case MODIFICATION:
return ExceptionCode::TLBModification;
case DISALLOWED_ADDRESS:
return accessType == LOAD ? ExceptionCode::AddressErrorLoad : ExceptionCode::AddressErrorStore;
default:
panic("Getting TLB exception for unknown error code! ({})", static_cast<u8>(error));
return {};
}
}
void Cop0::decode(const Instruction instr) {
Registers& regs = Core::GetRegs();
switch (instr.cop_rs()) {
case 0x00: mfc0(instr); break;
case 0x01: dmfc0(instr); break;
case 0x04: mtc0(instr); break;
case 0x05: dmtc0(instr); break;
case 0x10 ... 0x1F:
switch (instr.cop_funct()) {
case 0x01: tlbr(); break;
case 0x02: tlbw(index.i); break;
case 0x06: tlbw(GetRandom()); break;
case 0x08: tlbp(); break;
case 0x18: eret(); break;
default:
panic("Unimplemented COP0 function {} ({:08X}) ({:016X})", instr.cop_funct(), u32(instr),
regs.oldPC);
}
break;
default:
panic("Unimplemented COP0 instruction {}", instr.cop_rs());
}
}
template <>
bool Cop0::MapVirtualAddress<u32, true>(const TLBAccessType accessType, const u64 vaddr, u32 &paddr) {
if(Util::IsInsideRange(vaddr, START_VREGION_KUSEG, END_VREGION_KUSEG))
return ProbeTLB(accessType, s64(s32(vaddr)), paddr);
tlbError = DISALLOWED_ADDRESS;
return false;
}
template <>
bool Cop0::MapVirtualAddress<u32, false>(const TLBAccessType accessType, const u64 vaddr, u32 &paddr) {
u8 segment = static_cast<u32>(vaddr) >> 29 & 7;
if(Util::IsInsideRange(segment, 0, 3) || segment == 7)
return ProbeTLB(accessType, static_cast<s32>(vaddr), paddr);
if(Util::IsInsideRange(segment, 4, 5)) {
paddr = vaddr & 0x1FFFFFFF;
return true;
}
if(segment == 6)
panic("Unimplemented virtual mapping in KSSEG! ({:08X})", vaddr);
panic("Should never end up in base case in MapVirtualAddress! ({:08X})", vaddr);
return false;
}
template <>
bool Cop0::MapVirtualAddress<u64, true>(const TLBAccessType accessType, const u64 vaddr, u32 &paddr) {
if(Util::IsInsideRange(vaddr, 0x0000000000000000, 0x000000FFFFFFFFFF))
return ProbeTLB(accessType, vaddr, paddr);
tlbError = DISALLOWED_ADDRESS;
return false;
}
template <>
bool Cop0::MapVirtualAddress<u64, false>(const TLBAccessType accessType, const u64 vaddr, u32 &paddr) {
if(Util::IsInsideRange(vaddr, 0x0000000000000000, 0x000000FFFFFFFFFF) || // VREGION_XKUSEG
Util::IsInsideRange(vaddr, 0x4000000000000000, 0x400000FFFFFFFFFF) || // VREGION_XKSSEG
Util::IsInsideRange(vaddr, 0xC000000000000000, 0xC00000FF7FFFFFFF) || // VREGION_XKSEG
Util::IsInsideRange(vaddr, 0xFFFFFFFFE0000000, 0xFFFFFFFFFFFFFFFF)) // VREGION_CKSEG3
return ProbeTLB(accessType, vaddr, paddr);
if(Util::IsInsideRange(vaddr, 0x8000000000000000, 0xBFFFFFFFFFFFFFFF)) { // VREGION_XKPHYS
if (!kernelMode)
panic("Access to XKPHYS address 0x{:016X} when outside kernel mode!", vaddr);
const u8 high_two_bits = (vaddr >> 62) & 0b11;
if (high_two_bits != 0b10)
panic("Access to XKPHYS address 0x{:016X} with high two bits != 0b10!", vaddr);
const u8 subsegment = (vaddr >> 59) & 0b11;
bool cached = subsegment != 2; // do something with this eventually
// If any bits in the range of 58:32 are set, the address is invalid.
const bool valid = (vaddr & 0x07FFFFFF00000000) == 0;
if (!valid) {
tlbError = DISALLOWED_ADDRESS;
return false;
}
paddr = vaddr & 0xFFFFFFFF;
return true;
}
if(Util::IsInsideRange(vaddr, 0xFFFFFFFF80000000, 0xFFFFFFFF9FFFFFFF) || // VREGION_CKSEG0
Util::IsInsideRange(vaddr, 0xFFFFFFFFA0000000, 0xFFFFFFFFBFFFFFFF)) { // VREGION_CKSEG1
u32 cut = u32(vaddr) >> 28;
u32 num = cut == 0xA;
// Identical to ksegX in 32 bit mode.
// Unmapped translation. Subtract the base address of the space to get the physical address.
paddr = vaddr - (cut << 28); // Implies cutting off the high 32 bits
trace("CKSEG{}: Translated 0x{:016X} to 0x{:08X}", num, vaddr, paddr);
return true;
}
if(Util::IsInsideRange(vaddr, 0x0000010000000000, 0x3FFFFFFFFFFFFFFF) || // VREGION_XBAD1
Util::IsInsideRange(vaddr, 0x4000010000000000, 0x7FFFFFFFFFFFFFFF) || // VREGION_XBAD2
Util::IsInsideRange(vaddr, 0xC00000FF80000000, 0xFFFFFFFF7FFFFFFF)) { // VREGION_XBAD3
tlbError = DISALLOWED_ADDRESS;
return false;
}
panic("Resolving virtual address 0x{:016X} in 64 bit mode", vaddr);
return false; // just to silence warning
}
bool Cop0::MapVAddr(const TLBAccessType accessType, const u64 vaddr, u32 &paddr) {
if(supervisorMode)
panic("Supervisor mode memory access");
if (is64BitAddressing) [[unlikely]] {
if (kernelMode) [[likely]] return MapVirtualAddress<u64, false>(accessType, vaddr, paddr);
if (userMode) return MapVirtualAddress<u64, true>(accessType, vaddr, paddr);
panic("Unknown mode! This should never happen!");
}
if (kernelMode) [[likely]] return MapVirtualAddress<u32, false>(accessType, vaddr, paddr);
if (userMode) return MapVirtualAddress<u32, true>(accessType, vaddr, paddr);
panic("Unknown mode! This should never happen!");
}
} // namespace n64
+285
View File
@@ -0,0 +1,285 @@
#pragma once
#include <common.hpp>
#include <log.hpp>
#include <unordered_map>
#include <Instruction.hpp>
namespace n64 {
#define STATUS_MASK 0xFF57FFFF
#define CONFIG_MASK 0x0F00800F
#define INDEX_MASK 0x8000003F
#define COP0_REG_INDEX 0
#define COP0_REG_RANDOM 1
#define COP0_REG_ENTRYLO0 2
#define COP0_REG_ENTRYLO1 3
#define COP0_REG_CONTEXT 4
#define COP0_REG_PAGEMASK 5
#define COP0_REG_WIRED 6
#define COP0_REG_BADVADDR 8
#define COP0_REG_COUNT 9
#define COP0_REG_ENTRYHI 10
#define COP0_REG_COMPARE 11
#define COP0_REG_STATUS 12
#define COP0_REG_CAUSE 13
#define COP0_REG_EPC 14
#define COP0_REG_PRID 15
#define COP0_REG_CONFIG 16
#define COP0_REG_LLADDR 17
#define COP0_REG_WATCHLO 18
#define COP0_REG_WATCHHI 19
#define COP0_REG_XCONTEXT 20
#define COP0_REG_PARITY_ERR 26
#define COP0_REG_CACHE_ERR 27
#define COP0_REG_TAGLO 28
#define COP0_REG_TAGHI 29
#define COP0_REG_ERROREPC 30
#define ENTRY_LO_MASK 0x3FFFFFFF
#define ENTRY_HI_MASK 0xC00000FFFFFFE0FF
#define PAGEMASK_MASK 0x1FFE000
union Cop0Cause {
u32 raw;
struct {
unsigned : 8;
unsigned interruptPending : 8;
unsigned : 16;
} __attribute__((__packed__));
struct {
unsigned : 2;
unsigned exceptionCode : 5;
unsigned : 1;
unsigned ip0 : 1;
unsigned ip1 : 1;
unsigned ip2 : 1;
unsigned ip3 : 1;
unsigned ip4 : 1;
unsigned ip5 : 1;
unsigned ip6 : 1;
unsigned ip7 : 1;
unsigned : 12;
unsigned copError : 2;
unsigned : 1;
unsigned branchDelay : 1;
} __attribute__((__packed__));
};
union Cop0Status {
struct {
unsigned ie : 1;
unsigned exl : 1;
unsigned erl : 1;
unsigned ksu : 2;
unsigned ux : 1;
unsigned sx : 1;
unsigned kx : 1;
unsigned im : 8;
unsigned ds : 9;
unsigned re : 1;
unsigned fr : 1;
unsigned rp : 1;
unsigned cu0 : 1;
unsigned cu1 : 1;
unsigned cu2 : 1;
unsigned cu3 : 1;
} __attribute__((__packed__));
struct {
unsigned : 16;
unsigned de : 1;
unsigned ce : 1;
unsigned ch : 1;
unsigned : 1;
unsigned sr : 1;
unsigned ts : 1;
unsigned bev : 1;
unsigned : 1;
unsigned its : 1;
unsigned : 7;
} __attribute__((__packed__));
u32 raw;
} __attribute__((__packed__));
union EntryLo {
u32 raw;
struct {
unsigned g : 1;
unsigned v : 1;
unsigned d : 1;
unsigned c : 3;
unsigned pfn : 20;
unsigned : 6;
};
};
union EntryHi {
u64 raw;
struct {
u64 asid : 8;
u64 : 5;
u64 vpn2 : 27;
u64 fill : 22;
u64 r : 2;
} __attribute__((__packed__));
};
union PageMask {
u32 raw;
struct {
unsigned : 13;
unsigned mask : 12;
unsigned : 7;
};
};
union Index {
u32 raw;
struct {
unsigned i : 6;
unsigned : 25;
unsigned p : 1;
};
};
struct TLBEntry {
bool initialized;
EntryLo entryLo0, entryLo1;
EntryHi entryHi;
PageMask pageMask;
bool global;
};
enum TLBError : u8 { NONE, MISS, INVALID, MODIFICATION, DISALLOWED_ADDRESS };
enum class ExceptionCode : u8 {
Interrupt = 0,
TLBModification = 1,
TLBLoad = 2,
TLBStore = 3,
AddressErrorLoad = 4,
AddressErrorStore = 5,
InstructionBusError = 6,
DataBusError = 7,
Syscall = 8,
Breakpoint = 9,
ReservedInstruction = 10,
CoprocessorUnusable = 11,
Overflow = 12,
Trap = 13,
FloatingPointError = 15,
Watch = 23
};
union Cop0Context {
u64 raw;
struct {
u64 : 4;
u64 badvpn2 : 19;
u64 ptebase : 41;
};
};
union Cop0XContext {
u64 raw;
struct {
u64 : 4;
u64 badvpn2 : 27;
u64 r : 2;
u64 ptebase : 31;
} __attribute__((__packed__));
};
struct Cop0 {
Cop0();
bool kernelMode{true};
bool supervisorMode{false};
bool userMode{false};
bool is64BitAddressing{false};
bool llbit{};
TLBError tlbError = NONE;
PageMask pageMask{};
EntryHi entryHi{};
EntryLo entryLo0{}, entryLo1{};
Index index{};
Cop0Context context{};
u32 wired{}, r7{};
u32 compare{};
Cop0Status status{};
Cop0Cause cause{};
u32 PRId{}, Config{}, LLAddr{}, WatchLo{}, WatchHi{};
u32 r21{}, r22{}, r23{}, r24{}, r25{}, ParityError{}, CacheError{}, TagLo{}, TagHi{};
u32 r31{};
Cop0XContext xcontext{};
u64 badVaddr{}, count{};
s64 EPC{};
s64 ErrorEPC{};
s64 openbus{};
TLBEntry tlb[32]{};
enum TLBAccessType { LOAD, STORE };
u32 GetReg32(u8);
[[nodiscard]] u64 GetReg64(u8) const;
void SetReg32(u8, u32);
void SetReg64(u8, u64);
void Reset();
bool ProbeTLB(TLBAccessType accessType, u64 vaddr, u32 &paddr);
void FireException(ExceptionCode code, int cop, s64 pc);
bool MapVAddr(TLBAccessType accessType, u64 vaddr, u32 &paddr);
TLBEntry *TLBTryMatch(u64 vaddr, int &index);
TLBEntry *TLBTryMatch(u64 vaddr);
void HandleTLBException(u64 vaddr);
static ExceptionCode GetTLBExceptionCode(TLBError error, TLBAccessType accessType);
void decode(const Instruction);
[[nodiscard]] FORCE_INLINE u32 GetRandom() const {
u32 val = rand();
const auto wired_ = GetWired();
u32 lower, upper;
if (wired_ > 31) {
lower = 0;
upper = 64;
} else {
lower = wired_;
upper = 32 - wired_;
}
val = (val % upper) + lower;
return val;
}
FORCE_INLINE void Update() {
const bool exception = status.exl || status.erl;
kernelMode = exception || status.ksu == 0;
supervisorMode = !exception && status.ksu == 1;
userMode = !exception && status.ksu == 2;
is64BitAddressing = (kernelMode && status.kx) || (supervisorMode && status.sx) || (userMode && status.ux);
}
private:
friend struct JIT;
[[nodiscard]] FORCE_INLINE u32 GetWired() const { return wired & 0x3F; }
[[nodiscard]] FORCE_INLINE u32 GetCount() const { return u32(u64(count >> 1)); }
void mtc0(const Instruction);
void dmtc0(const Instruction);
void mfc0(const Instruction);
void dmfc0(const Instruction) const;
void eret();
void tlbr();
void tlbw(int);
void tlbp();
template <typename T, bool User>
bool MapVirtualAddress(TLBAccessType accessType, u64 vaddr, u32 &paddr);
};
} // namespace n64
+290
View File
@@ -0,0 +1,290 @@
#include <Core.hpp>
#include <log.hpp>
namespace n64 {
Cop1::Cop1() { Reset(); }
void Cop1::Reset() {
fcr0 = 0xa00;
fcr31.write(0x01000800);
memset(fgr, 0, 32 * sizeof(FloatingPointReg));
}
void Cop1::decode(const Instruction instr) {
switch (instr.cop_rs()) {
// 000r_rccc
case 0x00:
mfc1(instr);
break;
case 0x01:
dmfc1(instr);
break;
case 0x02:
cfc1(instr);
break;
case 0x03:
unimplemented();
break;
case 0x04:
mtc1(instr);
break;
case 0x05:
dmtc1(instr);
break;
case 0x06:
ctc1(instr);
break;
case 0x07:
unimplemented();
break;
case 0x10: // s
switch (instr.cop_funct()) {
case 0x00:
adds(instr);
break;
case 0x01:
subs(instr);
break;
case 0x02:
muls(instr);
break;
case 0x03:
divs(instr);
break;
case 0x04:
sqrts(instr);
break;
case 0x05:
abss(instr);
break;
case 0x06:
movs(instr);
break;
case 0x07:
negs(instr);
break;
case 0x08:
roundls(instr);
break;
case 0x09:
truncls(instr);
break;
case 0x0A:
ceills(instr);
break;
case 0x0B:
floorls(instr);
break;
case 0x0C:
roundws(instr);
break;
case 0x0D:
truncws(instr);
break;
case 0x0E:
ceilws(instr);
break;
case 0x0F:
floorws(instr);
break;
case 0x21:
cvtds(instr);
break;
case 0x24:
cvtws(instr);
break;
case 0x25:
cvtls(instr);
break;
case 0x30:
cf<float>(instr);
break;
case 0x31:
cun<float>(instr);
break;
case 0x32:
ceq<float>(instr);
break;
case 0x33:
cueq<float>(instr);
break;
case 0x34:
colt<float>(instr);
break;
case 0x35:
cult<float>(instr);
break;
case 0x36:
cole<float>(instr);
break;
case 0x37:
cule<float>(instr);
break;
case 0x38:
csf<float>(instr);
break;
case 0x39:
cngle<float>(instr);
break;
case 0x3A:
cseq<float>(instr);
break;
case 0x3B:
cngl<float>(instr);
break;
case 0x3C:
clt<float>(instr);
break;
case 0x3D:
cnge<float>(instr);
break;
case 0x3E:
cle<float>(instr);
break;
case 0x3F:
cngt<float>(instr);
break;
default:
unimplemented();
}
break;
case 0x11: // d
switch (instr.cop_funct()) {
case 0x00:
addd(instr);
break;
case 0x01:
subd(instr);
break;
case 0x02:
muld(instr);
break;
case 0x03:
divd(instr);
break;
case 0x04:
sqrtd(instr);
break;
case 0x05:
absd(instr);
break;
case 0x06:
movd(instr);
break;
case 0x07:
negd(instr);
break;
case 0x08:
roundld(instr);
break;
case 0x09:
truncld(instr);
break;
case 0x0A:
ceilld(instr);
break;
case 0x0B:
floorld(instr);
break;
case 0x0C:
roundwd(instr);
break;
case 0x0D:
truncwd(instr);
break;
case 0x0E:
ceilwd(instr);
break;
case 0x0F:
floorwd(instr);
break;
case 0x20:
cvtsd(instr);
break;
case 0x24:
cvtwd(instr);
break;
case 0x25:
cvtld(instr);
break;
case 0x30:
cf<double>(instr);
break;
case 0x31:
cun<double>(instr);
break;
case 0x32:
ceq<double>(instr);
break;
case 0x33:
cueq<double>(instr);
break;
case 0x34:
colt<double>(instr);
break;
case 0x35:
cult<double>(instr);
break;
case 0x36:
cole<double>(instr);
break;
case 0x37:
cule<double>(instr);
break;
case 0x38:
csf<double>(instr);
break;
case 0x39:
cngle<double>(instr);
break;
case 0x3A:
cseq<double>(instr);
break;
case 0x3B:
cngl<double>(instr);
break;
case 0x3C:
clt<double>(instr);
break;
case 0x3D:
cnge<double>(instr);
break;
case 0x3E:
cle<double>(instr);
break;
case 0x3F:
cngt<double>(instr);
break;
default:
unimplemented();
}
break;
case 0x14: // w
switch (instr.cop_funct()) {
case 0x20:
cvtsw(instr);
break;
case 0x21:
cvtdw(instr);
break;
default:
unimplemented();
}
break;
case 0x15: // l
switch (instr.cop_funct()) {
case 0x20:
cvtsl(instr);
break;
case 0x21:
cvtdl(instr);
break;
default:
unimplemented();
}
break;
default:
panic("Unimplemented COP1 instruction {}", instr.cop_rs());
}
}
} // namespace n64
+247
View File
@@ -0,0 +1,247 @@
#pragma once
#include <core/registers/Cop0.hpp>
#include <cstring>
#include <Instruction.hpp>
namespace n64 {
struct Cop1;
union FCR31 {
FCR31() = default;
struct {
unsigned rounding_mode : 2;
struct {
unsigned inexact_operation : 1;
unsigned underflow : 1;
unsigned overflow : 1;
unsigned division_by_zero : 1;
unsigned invalid_operation : 1;
} flag;
struct {
unsigned inexact_operation : 1;
unsigned underflow : 1;
unsigned overflow : 1;
unsigned division_by_zero : 1;
unsigned invalid_operation : 1;
} enable;
struct {
unsigned inexact_operation : 1;
unsigned underflow : 1;
unsigned overflow : 1;
unsigned division_by_zero : 1;
unsigned invalid_operation : 1;
unsigned unimplemented_operation : 1;
} cause;
unsigned : 5;
unsigned compare : 1;
unsigned fs : 1;
unsigned : 7;
} __attribute__((__packed__));
[[nodiscard]] u32 read() const {
u32 ret = 0;
ret |= (u32(fs) << 24);
ret |= (u32(compare) << 23);
ret |= (u32(cause.unimplemented_operation) << 17);
ret |= (u32(cause.invalid_operation) << 16);
ret |= (u32(cause.division_by_zero) << 15);
ret |= (u32(cause.overflow) << 14);
ret |= (u32(cause.underflow) << 13);
ret |= (u32(cause.inexact_operation) << 12);
ret |= (u32(enable.invalid_operation) << 11);
ret |= (u32(enable.division_by_zero) << 10);
ret |= (u32(enable.overflow) << 9);
ret |= (u32(enable.underflow) << 8);
ret |= (u32(enable.inexact_operation) << 7);
ret |= (u32(flag.invalid_operation) << 6);
ret |= (u32(flag.division_by_zero) << 5);
ret |= (u32(flag.overflow) << 4);
ret |= (u32(flag.underflow) << 3);
ret |= (u32(flag.inexact_operation) << 2);
ret |= (u32(rounding_mode) & 3);
return ret;
}
void write(u32 val) {
fs = val >> 24;
compare = val >> 23;
cause.unimplemented_operation = val >> 17;
cause.invalid_operation = val >> 16;
cause.division_by_zero = val >> 15;
cause.overflow = val >> 14;
cause.underflow = val >> 13;
cause.inexact_operation = val >> 12;
enable.invalid_operation = val >> 11;
enable.division_by_zero = val >> 10;
enable.overflow = val >> 9;
enable.underflow = val >> 8;
enable.inexact_operation = val >> 7;
flag.invalid_operation = val >> 6;
flag.division_by_zero = val >> 5;
flag.overflow = val >> 4;
flag.underflow = val >> 3;
flag.inexact_operation = val >> 2;
rounding_mode = val & 3;
}
};
union FloatingPointReg {
struct {
s32 int32;
s32 int32h;
};
struct {
u32 uint32;
u32 uint32h;
};
struct {
s64 int64;
};
struct {
u64 uint64;
};
struct {
float float32;
float float32h;
};
struct {
double float64;
};
};
struct Cop1 {
explicit Cop1();
bool fgrIsConstant[32]{};
u32 fcr0{};
FCR31 fcr31{};
FloatingPointReg fgr[32]{};
void Reset();
void decode(const Instruction);
friend struct Interpreter;
friend struct JIT;
template <bool preserveCause = false>
bool CheckFPUUsable();
template <typename T>
bool CheckResult(T &);
template <typename T>
bool CheckArg(T);
template <typename T>
bool CheckArgs(T, T);
template <typename T>
bool isqnan(T);
template <typename T, bool quiet, bool cf>
bool XORDERED(T fs, T ft);
template <typename T>
bool CheckCVTArg(float f);
template <typename T>
bool CheckCVTArg(double f);
template <bool cvt = false>
bool TestExceptions();
void SetCauseUnimplemented();
bool SetCauseUnderflow();
bool SetCauseInexact();
bool SetCauseDivisionByZero();
bool SetCauseOverflow();
bool SetCauseInvalid();
private:
template <typename T>
auto FGR_T(const Cop0Status &, u32) -> T &;
template <typename T>
auto FGR_S(const Cop0Status &, u32) -> T &;
template <typename T>
auto FGR_D(const Cop0Status &, u32) -> T &;
void absd(const Instruction instr);
void abss(const Instruction instr);
void adds(const Instruction instr);
void addd(const Instruction instr);
void subs(const Instruction instr);
void subd(const Instruction instr);
void ceills(const Instruction instr);
void ceilws(const Instruction instr);
void ceilld(const Instruction instr);
void ceilwd(const Instruction instr);
void cfc1(const Instruction instr);
void ctc1(const Instruction instr);
void unimplemented();
void roundls(const Instruction instr);
void roundld(const Instruction instr);
void roundws(const Instruction instr);
void roundwd(const Instruction instr);
void floorls(const Instruction instr);
void floorld(const Instruction instr);
void floorws(const Instruction instr);
void floorwd(const Instruction instr);
void cvtls(const Instruction instr);
void cvtws(const Instruction instr);
void cvtds(const Instruction instr);
void cvtsw(const Instruction instr);
void cvtdw(const Instruction instr);
void cvtsd(const Instruction instr);
void cvtwd(const Instruction instr);
void cvtld(const Instruction instr);
void cvtdl(const Instruction instr);
void cvtsl(const Instruction instr);
template <typename T>
void cf(const Instruction instr);
template <typename T>
void cun(const Instruction instr);
template <typename T>
void ceq(const Instruction instr);
template <typename T>
void cueq(const Instruction instr);
template <typename T>
void colt(const Instruction instr);
template <typename T>
void cult(const Instruction instr);
template <typename T>
void cole(const Instruction instr);
template <typename T>
void cule(const Instruction instr);
template <typename T>
void csf(const Instruction instr);
template <typename T>
void cngle(const Instruction instr);
template <typename T>
void cseq(const Instruction instr);
template <typename T>
void cngl(const Instruction instr);
template <typename T>
void clt(const Instruction instr);
template <typename T>
void cnge(const Instruction instr);
template <typename T>
void cle(const Instruction instr);
template <typename T>
void cngt(const Instruction instr);
void divs(const Instruction instr);
void divd(const Instruction instr);
void muls(const Instruction instr);
void muld(const Instruction instr);
void movs(const Instruction instr);
void movd(const Instruction instr);
void negs(const Instruction instr);
void negd(const Instruction instr);
void sqrts(const Instruction instr);
void sqrtd(const Instruction instr);
void lwc1(const Instruction instr);
void swc1(const Instruction instr);
void ldc1(const Instruction instr);
void sdc1(const Instruction instr);
void mfc1(const Instruction instr);
void dmfc1(const Instruction instr);
void mtc1(const Instruction instr);
void dmtc1(const Instruction instr);
void truncws(const Instruction instr);
void truncwd(const Instruction instr);
void truncls(const Instruction instr);
void truncld(const Instruction instr);
};
} // namespace n64
+363
View File
@@ -0,0 +1,363 @@
#include <jit/helpers.hpp>
#include <core/registers/Registers.hpp>
#include <core/JIT.hpp>
namespace n64 {
Registers::Registers() { Reset(); }
void Registers::Reset() {
hi = 0;
lo = 0;
delaySlot = false;
prevDelaySlot = false;
gpr.fill(0);
regIsConstant = 1; // first bit is true indicating $zero is constant which yes it is always
cop0.Reset();
cop1.Reset();
steps = 0;
extraCycles = 0;
}
void Registers::SetPC64(s64 val) {
oldPC = pc;
pc = val;
nextPC = pc + 4;
}
void Registers::SetPC32(s32 val) {
oldPC = pc;
pc = s64(val);
nextPC = pc + 4;
}
template <>
u64 Registers::Read<u64>(size_t idx) {
return gpr[idx];
}
template <>
s64 Registers::Read<s64>(const size_t idx) {
return static_cast<s64>(Read<u64>(idx));
}
template <>
u32 Registers::Read<u32>(size_t idx) {
return gpr[idx];
}
template <>
s32 Registers::Read<s32>(size_t idx) {
return static_cast<s32>(Read<u32>(idx));
}
template <>
u16 Registers::Read<u16>(size_t idx) {
return gpr[idx];
}
template <>
s16 Registers::Read<s16>(size_t idx) {
return static_cast<s16>(Read<u16>(idx));
}
template <>
u8 Registers::Read<u8>(size_t idx) {
return gpr[idx];
}
template <>
s8 Registers::Read<s8>(size_t idx) {
return static_cast<s8>(Read<u8>(idx));
}
#ifndef __aarch64__
template <>
void Registers::Read<u64>(size_t idx, Xbyak::Reg reg) {
if(IsRegConstant(idx)) {
jit->code.mov(reg.cvt64(), Read<u64>(idx));
return;
}
jit->code.mov(reg.cvt64(), jit->GPR<u64>(idx));
}
template <>
void Registers::Read<s64>(size_t idx, Xbyak::Reg reg) {
if(IsRegConstant(idx)) {
jit->code.mov(reg.cvt64(), Read<s64>(idx));
return;
}
jit->code.mov(reg.cvt64(), jit->GPR<u64>(idx));
}
template <>
void Registers::Read<u32>(size_t idx, Xbyak::Reg reg) {
if(IsRegConstant(idx)) {
jit->code.mov(reg.cvt32(), Read<u32>(idx));
return;
}
jit->code.mov(reg.cvt32(), jit->GPR<u32>(idx));
}
template <>
void Registers::Read<s32>(size_t idx, Xbyak::Reg reg) {
if(IsRegConstant(idx)) {
jit->code.mov(reg.cvt32(), Read<s32>(idx));
return;
}
jit->code.mov(reg.cvt32(), jit->GPR<s32>(idx));
}
template <>
void Registers::Read<u16>(size_t idx, Xbyak::Reg reg) {
if(IsRegConstant(idx)) {
jit->code.mov(reg.cvt16(), Read<u16>(idx));
return;
}
jit->code.mov(reg.cvt16(), jit->GPR<u16>(idx));
}
template <>
void Registers::Read<s16>(size_t idx, Xbyak::Reg reg) {
if(IsRegConstant(idx)) {
jit->code.mov(reg.cvt16(), Read<s16>(idx));
return;
}
jit->code.mov(reg.cvt16(), jit->GPR<u16>(idx));
}
template <>
void Registers::Read<u8>(size_t idx, Xbyak::Reg reg) {
if(IsRegConstant(idx)) {
jit->code.mov(reg.cvt8(), Read<u8>(idx));
return;
}
jit->code.mov(reg.cvt8(), jit->GPR<u8>(idx));
}
template <>
void Registers::Read<s8>(size_t idx, Xbyak::Reg reg) {
if(IsRegConstant(idx)) {
jit->code.mov(reg.cvt8(), Read<s8>(idx));
return;
}
jit->code.mov(reg.cvt8(), jit->GPR<s8>(idx));
}
#endif
template <>
void Registers::Write<bool>(size_t idx, bool v) {
if (idx == 0)
return;
if (jit) [[unlikely]]
regIsConstant |= (1 << idx);
gpr[idx] = v;
}
template <>
void Registers::Write<u64>(size_t idx, u64 v) {
if (idx == 0)
return;
if (jit) [[unlikely]]
regIsConstant |= (1 << idx);
gpr[idx] = v;
}
template <>
void Registers::Write<s64>(size_t idx, s64 v) {
Write<u64>(idx, v);
}
template <>
void Registers::Write<u32>(size_t idx, u32 v) {
if (idx == 0)
return;
if (jit) [[unlikely]]
regIsConstant |= (1 << idx);
gpr[idx] = v;
}
template <>
void Registers::Write<s32>(size_t idx, s32 v) {
if (idx == 0)
return;
if (jit) [[unlikely]]
regIsConstant |= (1 << idx);
gpr[idx] = v;
}
template <>
void Registers::Write<u16>(size_t idx, u16 v) {
if (idx == 0)
return;
if (jit) [[unlikely]]
regIsConstant |= (1 << idx);
gpr[idx] = v;
}
template <>
void Registers::Write<s16>(size_t idx, s16 v) {
if (idx == 0)
return;
if (jit) [[unlikely]]
regIsConstant |= (1 << idx);
gpr[idx] = v;
}
template <>
void Registers::Write<u8>(size_t idx, u8 v) {
if (idx == 0)
return;
if (jit) [[unlikely]]
regIsConstant |= (1 << idx);
gpr[idx] = v;
}
template <>
void Registers::Write<s8>(size_t idx, s8 v) {
if (idx == 0)
return;
if (jit) [[unlikely]]
regIsConstant |= (1 << idx);
gpr[idx] = v;
}
#ifndef __aarch64__
template <>
void Registers::Write<bool>(size_t idx, Xbyak::Reg v) {
if (idx == 0)
return;
if (!jit)
panic("Did you try to call Registers::Write(size_t, *Xbyak::Reg*) from the interpreter?");
regIsConstant &= ~(1 << idx);
jit->code.movsx(v.cvt64(), v.cvt8());
jit->code.mov(jit->GPR<u64>(idx), v);
}
template <>
void Registers::Write<s8>(size_t idx, Xbyak::Reg v) {
if (idx == 0)
return;
if (!jit)
panic("Did you try to call Registers::Write(size_t, *Xbyak::Reg*) from the interpreter?");
regIsConstant &= ~(1 << idx);
jit->code.movsx(v.cvt64(), v.cvt8());
jit->code.mov(jit->GPR<u64>(idx), v);
}
template <>
void Registers::Write<u8>(size_t idx, Xbyak::Reg v) {
if (idx == 0)
return;
if (!jit)
panic("Did you try to call Registers::Write(size_t, *Xbyak::Reg*) from the interpreter?");
regIsConstant &= ~(1 << idx);
jit->code.movzx(v.cvt64(), v.cvt8());
jit->code.mov(jit->GPR<u64>(idx), v.cvt64());
}
template <>
void Registers::Write<s16>(size_t idx, Xbyak::Reg v) {
if (idx == 0)
return;
if (!jit)
panic("Did you try to call Registers::Write(size_t, *Xbyak::Reg*) from the interpreter?");
regIsConstant &= ~(1 << idx);
jit->code.movsx(v.cvt64(), v.cvt16());
jit->code.mov(jit->GPR<u64>(idx), v.cvt64());
}
template <>
void Registers::Write<u16>(size_t idx, Xbyak::Reg v) {
if (idx == 0)
return;
if (!jit)
panic("Did you try to call Registers::Write(size_t, *Xbyak::Reg*) from the interpreter?");
regIsConstant &= ~(1 << idx);
jit->code.movzx(v.cvt64(), v.cvt16());
jit->code.mov(jit->GPR<u64>(idx), v.cvt64());
}
template <>
void Registers::Write<s32>(size_t idx, Xbyak::Reg v) {
if (idx == 0)
return;
if (!jit)
panic("Did you try to call Registers::Write(size_t, *Xbyak::Reg*) from the interpreter?");
regIsConstant &= ~(1 << idx);
jit->code.movsxd(v.cvt64(), v.cvt32());
jit->code.mov(jit->GPR<u64>(idx), v.cvt64());
}
template <>
void Registers::Write<u32>(size_t idx, Xbyak::Reg v) {
if (idx == 0)
return;
if (!jit)
panic("Did you try to call Registers::Write(size_t, *Xbyak::Reg*) from the interpreter?");
regIsConstant &= ~(1 << idx);
jit->code.movzx(v.cvt64(), v.cvt32());
jit->code.mov(jit->GPR<u64>(idx), v.cvt64());
}
template <>
void Registers::Write<u64>(size_t idx, Xbyak::Reg v) {
if (idx == 0)
return;
if (!jit)
panic("Did you try to call Registers::Write(size_t, *Xbyak::Reg*) from the interpreter?");
regIsConstant &= ~(1 << idx);
jit->code.mov(jit->GPR<u64>(idx), v.cvt64());
}
template <>
void Registers::Write<s64>(size_t idx, Xbyak::Reg v) {
Write<u64>(idx, v);
}
#endif
} // namespace n64
+80
View File
@@ -0,0 +1,80 @@
#pragma once
#include <array>
#include <xbyak.h>
#include <backend/core/registers/Cop1.hpp>
namespace n64 {
struct JIT;
struct Registers {
Registers();
void Reset();
void SetPC64(s64);
void SetPC32(s32);
void SetJIT(JIT* jit) { this->jit = jit; }
[[nodiscard]] bool IsRegConstant(const u32 index) const {
if (index == 0)
return true;
return regIsConstant & (1 << index);
}
[[nodiscard]] bool IsRegConstant(const u32 index1, const u32 index2) const {
return IsRegConstant(index1) && IsRegConstant(index2);
}
bool GetLOConstant() {
return regIsConstant & (1ull << 32);
}
bool GetHIConstant() {
return regIsConstant & (1ull << 33);
}
void SetLOConstant() {
regIsConstant |= (1ull << 32);
}
void SetHIConstant() {
regIsConstant |= (1ull << 33);
}
void UnsetLOConstant() {
regIsConstant &= ~(1ull << 32);
}
void UnsetHIConstant() {
regIsConstant &= ~(1ull << 33);
}
JIT *jit = nullptr;
uint64_t regIsConstant = 0;
bool prevDelaySlot{}, delaySlot{};
u32 steps = 0;
u32 extraCycles = 0;
s64 oldPC{}, pc{}, nextPC{};
s64 hi{}, lo{};
Cop0 cop0;
Cop1 cop1;
void CpuStall(u32 cycles) { extraCycles += cycles; }
u32 PopStalledCycles() {
u32 ret = extraCycles;
extraCycles = 0;
return ret;
}
template <typename T>
T Read(size_t);
template <typename T>
void Read(size_t, Xbyak::Reg);
template <typename T>
void Write(size_t, T);
template <typename T>
void Write(size_t, Xbyak::Reg);
std::array<s64, 32> gpr{};
};
} // namespace n64
+1
View File
@@ -0,0 +1 @@
add_library(rsp decode.cpp instructions.cpp)
+456
View File
@@ -0,0 +1,456 @@
#include <Core.hpp>
#include <log.hpp>
namespace n64 {
void RSP::special(const Instruction instr) {
MI& mi = Core::GetMem().mmio.mi;
switch (instr.cop_funct()) {
case 0x00:
if (instr != 0) {
sll(instr);
}
break;
case 0x02:
srl(instr);
break;
case 0x03:
sra(instr);
break;
case 0x04:
sllv(instr);
break;
case 0x06:
srlv(instr);
break;
case 0x07:
srav(instr);
break;
case 0x08:
jr(instr);
break;
case 0x09:
jalr(instr);
break;
case 0x0D:
spStatus.halt = true;
steps = 0;
spStatus.broke = true;
if (spStatus.interruptOnBreak) {
mi.InterruptRaise(MI::Interrupt::SP);
}
break;
case 0x20:
case 0x21:
add(instr);
break;
case 0x22:
case 0x23:
sub(instr);
break;
case 0x24:
and_(instr);
break;
case 0x25:
or_(instr);
break;
case 0x26:
xor_(instr);
break;
case 0x27:
nor(instr);
break;
case 0x2A:
slt(instr);
break;
case 0x2B:
sltu(instr);
break;
default:
panic("Unhandled RSP special instruction ({:06b})", instr.cop_funct());
}
}
void RSP::regimm(const Instruction instr) {
switch (instr.cop_rt()) {
case 0x00:
b(instr, gpr[instr.rs()] < 0);
break;
case 0x01:
b(instr, gpr[instr.rs()] >= 0);
break;
case 0x10:
blink(instr, gpr[instr.rs()] < 0);
break;
case 0x11:
blink(instr, gpr[instr.rs()] >= 0);
break;
default:
panic("Unhandled RSP regimm instruction ({:05b})", instr.cop_rt());
}
}
void RSP::lwc2(const Instruction instr) {
switch (instr.rd()) {
case 0x00:
lbv(instr);
break;
case 0x01:
lsv(instr);
break;
case 0x02:
llv(instr);
break;
case 0x03:
ldv(instr);
break;
case 0x04:
lqv(instr);
break;
case 0x05:
lrv(instr);
break;
case 0x06:
lpv(instr);
break;
case 0x07:
luv(instr);
break;
case 0x08:
lhv(instr);
break;
case 0x09:
lfv(instr);
break;
case 0x0A:
break;
case 0x0B:
ltv(instr);
break;
default:
panic("Unhandled RSP LWC2 {:05b}", instr.rd());
}
}
void RSP::swc2(const Instruction instr) {
switch (instr.rd()) {
case 0x00:
sbv(instr);
break;
case 0x01:
ssv(instr);
break;
case 0x02:
slv(instr);
break;
case 0x03:
sdv(instr);
break;
case 0x04:
sqv(instr);
break;
case 0x05:
srv(instr);
break;
case 0x06:
spv(instr);
break;
case 0x07:
suv(instr);
break;
case 0x08:
shv(instr);
break;
case 0x09:
sfv(instr);
break;
case 0x0A:
swv(instr);
break;
case 0x0B:
stv(instr);
break;
default:
panic("Unhandled RSP SWC2 {:05b}", instr.rd());
}
}
void RSP::cop2(const Instruction instr) {
switch (instr.cop_funct()) {
case 0x00:
if (instr >> 25 & 1) {
vmulf(instr);
} else {
switch (instr.cop_rs()) {
case 0x00:
mfc2(instr);
break;
case 0x02:
cfc2(instr);
break;
case 0x04:
mtc2(instr);
break;
case 0x06:
ctc2(instr);
break;
default:
panic("Unhandled RSP COP2 sub ({:05b})", instr.cop_rs());
}
}
break;
case 0x01:
vmulu(instr);
break;
case 0x02:
vrndp(instr);
break;
case 0x03:
vmulq(instr);
break;
case 0x04:
vmudl(instr);
break;
case 0x05:
vmudm(instr);
break;
case 0x06:
vmudn(instr);
break;
case 0x07:
vmudh(instr);
break;
case 0x08:
vmacf(instr);
break;
case 0x09:
vmacu(instr);
break;
case 0x0A:
vrndn(instr);
break;
case 0x0B:
vmacq(instr);
break;
case 0x0C:
vmadl(instr);
break;
case 0x0D:
vmadm(instr);
break;
case 0x0E:
vmadn(instr);
break;
case 0x0F:
vmadh(instr);
break;
case 0x10:
vadd(instr);
break;
case 0x11:
vsub(instr);
break;
case 0x12:
vzero(instr);
break;
case 0x13:
vabs(instr);
break;
case 0x14:
vaddc(instr);
break;
case 0x15:
vsubc(instr);
break;
case 0x16 ... 0x1C:
case 0x1E:
case 0x1F:
case 0x2E:
case 0x2F:
vzero(instr);
break;
case 0x1D:
vsar(instr);
break;
case 0x20:
vlt(instr);
break;
case 0x21:
veq(instr);
break;
case 0x22:
vne(instr);
break;
case 0x23:
vge(instr);
break;
case 0x24:
vcl(instr);
break;
case 0x25:
vch(instr);
break;
case 0x26:
vcr(instr);
break;
case 0x27:
vmrg(instr);
break;
case 0x28:
vand(instr);
break;
case 0x29:
vnand(instr);
break;
case 0x2A:
vor(instr);
break;
case 0x2B:
vnor(instr);
break;
case 0x2C:
vxor(instr);
break;
case 0x2D:
vnxor(instr);
break;
case 0x31:
vrcpl(instr);
break;
case 0x35:
vrsql(instr);
break;
case 0x32:
case 0x36:
vrcph(instr);
break;
case 0x30:
vrcp(instr);
break;
case 0x33:
vmov(instr);
break;
case 0x34:
vrsq(instr);
break;
case 0x38 ... 0x3E:
vzero(instr);
break;
case 0x37:
case 0x3F:
break;
default:
panic("Unhandled RSP COP2 ({:06b})", instr.cop_funct());
}
}
void RSP::cop0(const Instruction instr) {
if ((instr & 0x7FF) == 0) {
switch (instr.cop_rs()) {
case 0x00:
mfc0(Core::GetMem().mmio.rdp, instr);
break;
case 0x04:
mtc0(instr);
break;
default:
panic("Unhandled RSP COP0 ({:05b})", instr.cop_rs());
}
} else {
panic("RSP COP0 unknown {:08X}", u32(instr));
}
}
void RSP::Exec(const Instruction instr) {
Mem& mem = Core::GetMem();
MMIO &mmio = mem.mmio;
MI &mi = mmio.mi;
switch (instr.opcode()) {
case 0x00:
special(instr);
break;
case 0x01:
regimm(instr);
break;
case 0x02:
j(instr);
break;
case 0x03:
jal(instr);
break;
case 0x04:
b(instr, gpr[instr.rt()] == gpr[instr.rs()]);
break;
case 0x05:
b(instr, gpr[instr.rt()] != gpr[instr.rs()]);
break;
case 0x06:
b(instr, gpr[instr.rs()] <= 0);
break;
case 0x07:
b(instr, gpr[instr.rs()] > 0);
break;
case 0x08:
case 0x09:
addi(instr);
break;
case 0x0A:
slti(instr);
break;
case 0x0B:
sltiu(instr);
break;
case 0x0C:
andi(instr);
break;
case 0x0D:
ori(instr);
break;
case 0x0E:
xori(instr);
break;
case 0x0F:
lui(instr);
break;
case 0x10:
cop0(instr);
break;
case 0x12:
cop2(instr);
break;
case 0x20:
lb(instr);
break;
case 0x21:
lh(instr);
break;
case 0x23:
case 0x27:
lw(instr);
break;
case 0x24:
lbu(instr);
break;
case 0x25:
lhu(instr);
break;
case 0x28:
sb(instr);
break;
case 0x29:
sh(instr);
break;
case 0x2B:
sw(instr);
break;
case 0x32:
lwc2(instr);
break;
case 0x3A:
swc2(instr);
break;
default:
mem.DumpIMEM();
panic("Unhandled RSP instruction ({:06b}, {:04X})", instr.opcode(), oldPC);
}
}
} // namespace n64
File diff suppressed because it is too large Load Diff
+45
View File
@@ -0,0 +1,45 @@
#pragma once
#include <types.hpp>
#include <ErrorData.hpp>
#define FORCE_INLINE inline __attribute__((always_inline))
constexpr u32 N64_CPU_FREQ = 93750000;
#ifdef KAIZEN_USE_HASH
#include <resources/version.hpp>
#define KAIZEN_VERSION_STR KAIZEN_GIT_COMMIT_HASH
#else
#define KAIZEN_VERSION_YEAR 2026
#define KAIZEN_VERSION_MONTH 1
#define STR_HELPER(x) #x
#define STR(x) STR_HELPER(x)
#define KAIZEN_VERSION_STR STR(KAIZEN_VERSION_YEAR) "." STR(KAIZEN_VERSION_MONTH)
#endif
static FORCE_INLINE constexpr u32 GetCyclesPerFrame(bool pal) {
if (pal) {
return N64_CPU_FREQ / 50;
} else {
return N64_CPU_FREQ / 60;
}
}
static FORCE_INLINE constexpr u32 GetVideoFrequency(bool pal) {
if (pal) {
return 49'656'530;
} else {
return 48'681'812;
}
}
#define HALF_ADDRESS(addr) ((addr) ^ 2)
#define BYTE_ADDRESS(addr) ((addr) ^ 3)
#define ELEMENT_INDEX(i) (7 - (i))
#define BYTE_INDEX(i) (15 - (i))
#if defined(WIN32) || defined(_WIN32) || defined(__WIN32) && !defined(__CYGWIN__)
#define ABI_WINDOWS
#else
#define ABI_UNIX
#endif
+249
View File
@@ -0,0 +1,249 @@
#include <Debugger.hpp>
#include <imgui.h>
char const* regNames[] = {
"zero", "at", "v0", "v1",
"a0", "a1", "a2", "a3",
"t0", "t1", "t2", "t3",
"t4", "t5", "t6", "t7",
"s0", "s1", "s2", "s3",
"s4", "s5", "s6", "s7",
"t8", "t9", "k0", "k1",
"gp", "sp", "s8", "ra",
};
void BreakpointFunc(s64 addr, Disassembler::DisassemblyResult&) {
n64::Core& core = n64::Core::GetInstance();
bool isBroken = core.breakpoints.contains(addr);
ImGui::PushStyleColor(ImGuiCol_CheckMark, 0xff0000ff);
ImGui::PushStyleColor(ImGuiCol_FrameBg, 0);
ImGui::PushStyleColor(ImGuiCol_FrameBgActive, 0);
ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, 0x800000ff);
ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, 0.5f);
if(ImGui::Checkbox(std::format("##toggleBreakpoint{}", addr).c_str(), &isBroken)) {
core.ToggleBreakpoint(addr);
}
ImGui::PopStyleVar();
ImGui::PopStyleColor();
ImGui::PopStyleColor();
ImGui::PopStyleColor();
ImGui::PopStyleColor();
}
void AddressFunc(s64, Disassembler::DisassemblyResult& disasm) {
if(!disasm.success) {
ImGui::TextColored(ImColor(0xffeaefb6), "????????????????");
return;
}
ImGui::TextColored(ImColor(0xffeaefb6), "%s", std::format("{:016X}:", disasm.address).c_str());
}
void InstructionFunc(s64, Disassembler::DisassemblyResult& disasm) {
if(!disasm.success) {
ImGui::TextColored(ImColor(0xffcbf1ae), "Disassembly unsuccessful...");
return;
}
ImGui::TextColored(ImColor(0xffcbf1ae), "%s", std::format("{} ", disasm.mnemonic).c_str());
ImGui::SameLine(0, 0);
for(int i = 0; i < 3; i++) {
if(disasm.ops[i].str.empty())
continue;
if(i >= 2) {
ImGui::TextColored(ImColor(disasm.ops[i].color), "%s", disasm.ops[i].str.c_str());
ImGui::SameLine(0, 0);
continue;
}
std::string op_str = disasm.ops[i].str;
if(!disasm.ops[i+1].str.empty())
op_str += ", ";
ImGui::TextColored(ImColor(disasm.ops[i].color), "%s", op_str.c_str());
ImGui::SameLine(0, 0);
}
}
void Debugger::RegisterView() {
if(!ImGui::BeginTabItem("Registers"))
return;
if(!ImGui::BeginTable("##regs", 4, ImGuiTableFlags_SizingStretchSame | ImGuiTableFlags_Resizable | ImGuiTableFlags_BordersOuter | ImGuiTableFlags_BordersV | ImGuiTableFlags_ContextMenuInBody))
return;
ImGui::TableSetupColumn("Name");
ImGui::TableSetupColumn("Value");
ImGui::TableSetupColumn("Name");
ImGui::TableSetupColumn("Value");
ImGui::TableHeadersRow();
auto renderMemoryTable = [&](u64 vaddr) {
if(!ImGui::IsItemHovered(ImGuiHoveredFlags_DelayNormal | ImGuiHoveredFlags_ForTooltip))
return;
if(!ImGui::BeginTooltip())
return;
ImGui::Text("%s", std::format("Memory contents @ 0x{:016X}", vaddr).c_str());
if(!ImGui::BeginTable("##memoryContents", 16))
return;
for(u32 col = 0; col < 16; col++)
ImGui::TableSetupColumn(std::format("##hexCol{}", col).c_str());
ImGui::TableHeadersRow();
for(u32 row = 0; row < 16; row++) {
ImGui::TableNextRow();
for(u32 col = 0; col < 16; col+=4) {
u32 paddr;
if (!n64::Core::GetRegs().cop0.MapVAddr(n64::Cop0::LOAD, vaddr + row * 0x10 + col, paddr))
continue;
const u32 val = n64::Core::GetMem().Read<u32>(paddr);
ImGui::TableSetColumnIndex(col+0);
ImGui::Text("%02X", (val >> 24) & 0xff);
ImGui::TableSetColumnIndex(col+1);
ImGui::Text("%02X", (val >> 16) & 0xff);
ImGui::TableSetColumnIndex(col+2);
ImGui::Text("%02X", (val >> 8) & 0xff);
ImGui::TableSetColumnIndex(col+3);
ImGui::Text("%02X", (val >> 0) & 0xff);
}
}
ImGui::EndTable();
ImGui::EndTooltip();
};
n64::Registers& regs = n64::Core::GetRegs();
for(int i = 0; i < 32; i+=2) {
ImGui::TableNextRow();
ImGui::TableSetColumnIndex(0);
ImGui::Text("%s", regNames[i]);
ImGui::TableSetColumnIndex(1);
auto value = regs.Read<u64>(i);
ImGui::Text("%s", std::format("{:016X}", value).c_str());
renderMemoryTable(value);
ImGui::TableSetColumnIndex(2);
ImGui::Text("%s", regNames[i+1]);
ImGui::TableSetColumnIndex(3);
value = regs.Read<u64>(i+1);
ImGui::Text("%s", std::format("{:016X}", value).c_str());
renderMemoryTable(value);
}
ImGui::EndTable();
ImGui::EndTabItem();
}
bool Debugger::render() {
n64::Core &core = n64::Core::GetInstance();
const n64::Registers& regs = n64::Core::GetRegs();
if(!enabled)
return false;
static s64 startAddr = 0xFFFF'FFFF'8000'0000;
constexpr int step = 4;
constexpr int stepFast = 256;
if(!ImGui::Begin("Debugger", &enabled)) {
ImGui::End();
return false;
}
ImGui::BeginDisabled(followPC);
ImGui::InputScalar("Address", ImGuiDataType_S64, &startAddr, &step, &stepFast, "%016lX", ImGuiInputTextFlags_CharsHexadecimal);
ImGui::EndDisabled();
ImGui::Text("Follow program counter:");
ImGui::SameLine(0,0);
ImGui::Checkbox("##followPC", &followPC);
ImGui::SameLine(0,0);
ImGui::Text("Add a breakpoint");
ImGui::SameLine(0,0);
if(followPC)
startAddr = regs.pc - 256; // TODO: arbitrary???
if (ImGui::Button(core.breakpoints.contains(startAddr) ? "-" : "+")) {
core.ToggleBreakpoint(startAddr);
}
if(!ImGui::BeginTabBar("##debuggerTabs")) {
ImGui::EndTabBar();
ImGui::End();
return false;
}
RegisterView();
if(!ImGui::BeginTabItem("MIPS R4300i code view")) {
ImGui::EndTabBar();
ImGui::End();
return false;
}
constexpr auto disasmTableFlags = ImGuiTableFlags_SizingFixedSame | ImGuiTableFlags_Resizable | ImGuiTableFlags_BordersOuter |
ImGuiTableFlags_BordersV | ImGuiTableFlags_ContextMenuInBody;
if(!ImGui::BeginTable("Disassembly", columns.size(), disasmTableFlags)) {
ImGui::EndTabBar();
ImGui::End();
return false;
}
for(auto &[name, _] : columns)
ImGui::TableSetupColumn(name);
ImGui::TableHeadersRow();
for(auto addr = startAddr; addr < startAddr + MAX_LINES_OF_DISASM * sizeof(u32); addr += sizeof(u32)) {
auto disasm = Disassembler::GetInstance().Disassemble(addr);
const auto addrIsCurrent = addr == regs.nextPC;
const auto addrIsBreakpoint = core.breakpoints.contains(addr);
ImColor colorChoice = ImGui::GetStyle().Colors[ImGuiCol_TableRowBg];
ImColor colorChoiceAlt = ImGui::GetStyle().Colors[ImGuiCol_TableRowBgAlt];
if(addrIsCurrent) {
colorChoice = 0x80e27fbc;
colorChoiceAlt = 0x80e27fbc;
}
if(addrIsBreakpoint) {
colorChoice = 0x800000ff;
colorChoiceAlt = 0x800000ff;
}
ImGui::PushStyleColor(ImGuiCol_TableRowBg, colorChoice.Value);
ImGui::PushStyleColor(ImGuiCol_TableRowBgAlt, colorChoiceAlt.Value);
ImGui::TableNextRow();
for(int i = 0; auto &[_, func] : columns) {
ImGui::TableSetColumnIndex(i++);
func(addr, disasm);
}
ImGui::PopStyleColor();
ImGui::PopStyleColor();
}
ImGui::EndTable();
ImGui::EndTabItem();
ImGui::EndTabBar();
ImGui::End();
return true;
}
+28
View File
@@ -0,0 +1,28 @@
#pragma once
#include <backend/Core.hpp>
void BreakpointFunc(s64, Disassembler::DisassemblyResult&);
void AddressFunc(s64, Disassembler::DisassemblyResult&);
void InstructionFunc(s64, Disassembler::DisassemblyResult&);
class Debugger final {
bool enabled = false;
static constexpr auto MAX_LINES_OF_DISASM = 150;
struct Column {
const char* name = nullptr;
void (*func)(s64, Disassembler::DisassemblyResult&) = nullptr;
};
std::array<Column, 3> columns = {
Column{"##BreakpointColumn", &BreakpointFunc},
Column{"Address", &AddressFunc},
Column{"Instruction", &InstructionFunc},
};
public:
static void RegisterView();
bool followPC = true;
void Open(bool wantFollowPC = true) { enabled = true; followPC = wantFollowPC; }
void Close() { enabled = false; }
bool render();
};
+53
View File
@@ -0,0 +1,53 @@
#include <Core.hpp>
#include <EmuThread.hpp>
#include <KaizenGui.hpp>
EmuThread::EmuThread(double &fps, SettingsWindow &settings) noexcept : settings(settings), fps(fps) {}
void EmuThread::run() const noexcept {
n64::Core& core = n64::Core::GetInstance();
if(!core.romLoaded) return;
auto lastSample = std::chrono::high_resolution_clock::now();
auto avgFps = 16.667;
auto sampledFps = 0;
static bool oneSecondPassed = false;
fps = 1000.0 / avgFps;
const auto startFrameTime = std::chrono::high_resolution_clock::now();
if (!core.pause) {
core.Run(settings.getVolumeL(), settings.getVolumeR());
}
const auto endFrameTime = std::chrono::high_resolution_clock::now();
using namespace std::chrono_literals;
const auto frameTimeMs = std::chrono::duration<double>(endFrameTime - startFrameTime) / 1ms;
avgFps += frameTimeMs;
sampledFps++;
if (const auto elapsedSinceLastSample = std::chrono::duration<double>(endFrameTime - lastSample) / 1s;
elapsedSinceLastSample >= 1.0) {
if (!oneSecondPassed) {
oneSecondPassed = true;
return;
}
avgFps /= sampledFps;
fps = 1000.0 / avgFps;
}
}
void EmuThread::TogglePause() const noexcept {
n64::Core::GetInstance().TogglePause();
}
void EmuThread::Reset() const noexcept {
n64::Core::GetInstance().Reset();
}
void EmuThread::Stop() const noexcept {
n64::Core& core = n64::Core::GetInstance();
core.Stop();
core.rom = {};
}
+23
View File
@@ -0,0 +1,23 @@
#pragma once
#include <RenderWidget.hpp>
#include <SettingsWindow.hpp>
#include <memory>
namespace n64 {
struct Core;
}
class EmuThread final {
bool started = false;
public:
explicit EmuThread(double &, SettingsWindow &) noexcept;
~EmuThread() = default;
void run() const noexcept;
void TogglePause() const noexcept;
void Reset() const noexcept;
void Stop() const noexcept;
bool interruptionRequested = false, parallelRDPInitialized = false;
SettingsWindow &settings;
double& fps;
};
+157
View File
@@ -0,0 +1,157 @@
#pragma once
#define IMGUI_IMPL_VULKAN_NO_PROTOTYPES
#include <imgui.h>
#include <imgui_impl_sdl3.h>
#include <imgui_impl_vulkan.h>
#include <utils/log.hpp>
#include <memory>
namespace gui {
static VkAllocationCallbacks *g_Allocator = NULL;
static VkInstance g_Instance = VK_NULL_HANDLE;
static VkPhysicalDevice g_PhysicalDevice = VK_NULL_HANDLE;
static VkDevice g_Device = VK_NULL_HANDLE;
static uint32_t g_QueueFamily = (uint32_t)-1;
static VkQueue g_Queue = VK_NULL_HANDLE;
static VkPipelineCache g_PipelineCache = VK_NULL_HANDLE;
static VkDescriptorPool g_DescriptorPool = VK_NULL_HANDLE;
static ImGui_ImplVulkanH_Window g_MainWindowData;
static uint32_t g_MinImageCount = 2;
static void CheckVkResult(VkResult err) {
if (err == VK_SUCCESS)
return;
if (err < VK_SUCCESS)
panic("[vulkan] VkResult = {}", (int)err);
warn("[vulkan] VkResult = {}", (int)err);
}
inline void Initialize(const std::shared_ptr<Vulkan::WSI> &wsi, SDL_Window *nativeWindow) {
VkResult err;
// Setup Dear ImGui context
IMGUI_CHECKVERSION();
ImGui::CreateContext();
ImGuiIO &io = ImGui::GetIO();
(void)io;
io.ConfigFlags |= ImGuiConfigFlags_DockingEnable;
// io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls
// io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; // Enable Gamepad Controls
// Setup Dear ImGui style
ImGui::StyleColorsDark();
// ImGui::StyleColorsClassic();
g_Instance = wsi->get_context().get_instance();
g_PhysicalDevice = wsi->get_device().get_physical_device();
g_Device = wsi->get_device().get_device();
g_QueueFamily = wsi->get_context().get_queue_info().family_indices[Vulkan::QUEUE_INDEX_GRAPHICS];
g_Queue = wsi->get_context().get_queue_info().queues[Vulkan::QUEUE_INDEX_GRAPHICS];
g_PipelineCache = nullptr;
g_DescriptorPool = nullptr;
g_Allocator = nullptr;
g_MinImageCount = 2;
// Create Descriptor Pool
{
VkDescriptorPoolSize pool_sizes[] = {{VK_DESCRIPTOR_TYPE_SAMPLER, 1000},
{VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1000},
{VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, 1000},
{VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1000},
{VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER, 1000},
{VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER, 1000},
{VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1000},
{VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1000},
{VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, 1000},
{VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC, 1000},
{VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT, 1000}};
VkDescriptorPoolCreateInfo pool_info = {};
pool_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
pool_info.flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT;
pool_info.maxSets = 1000 * IM_ARRAYSIZE(pool_sizes);
pool_info.poolSizeCount = (uint32_t)IM_ARRAYSIZE(pool_sizes);
pool_info.pPoolSizes = pool_sizes;
err = vkCreateDescriptorPool(g_Device, &pool_info, g_Allocator, &g_DescriptorPool);
CheckVkResult(err);
}
// Create the Render Pass
VkRenderPass renderPass;
{
VkAttachmentDescription attachment = {};
attachment.format = wsi->get_device().get_swapchain_view().get_format();
attachment.samples = VK_SAMPLE_COUNT_1_BIT;
attachment.loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
attachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
attachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
attachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
attachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
attachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
VkAttachmentReference color_attachment = {};
color_attachment.attachment = 0;
color_attachment.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
VkSubpassDescription subpass = {};
subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
subpass.colorAttachmentCount = 1;
subpass.pColorAttachments = &color_attachment;
VkSubpassDependency dependency = {};
dependency.srcSubpass = VK_SUBPASS_EXTERNAL;
dependency.dstSubpass = 0;
dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
dependency.srcAccessMask = 0;
dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
VkRenderPassCreateInfo info = {};
info.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
info.attachmentCount = 1;
info.pAttachments = &attachment;
info.subpassCount = 1;
info.pSubpasses = &subpass;
info.dependencyCount = 1;
info.pDependencies = &dependency;
err = vkCreateRenderPass(g_Device, &info, g_Allocator, &renderPass);
CheckVkResult(err);
}
// Setup Platform/Renderer backends
ImGui_ImplSDL3_InitForVulkan(nativeWindow);
ImGui_ImplVulkan_InitInfo init_info = {};
init_info.Instance = g_Instance;
init_info.PhysicalDevice = g_PhysicalDevice;
init_info.Device = g_Device;
init_info.QueueFamily = g_QueueFamily;
init_info.Queue = g_Queue;
init_info.PipelineCache = g_PipelineCache;
init_info.DescriptorPool = g_DescriptorPool;
init_info.Allocator = g_Allocator;
init_info.MinImageCount = g_MinImageCount;
init_info.ImageCount = 2;
init_info.CheckVkResultFn = CheckVkResult;
init_info.RenderPass = renderPass;
init_info.ApiVersion = VK_API_VERSION_1_3;
ImGui_ImplVulkan_LoadFunctions(
VK_API_VERSION_1_3,
[](const char *function_name, void *vulkan_instance) {
return vkGetInstanceProcAddr((reinterpret_cast<VkInstance>(vulkan_instance)), function_name);
},
g_Instance);
if (!ImGui_ImplVulkan_Init(&init_info))
panic("Failed to initialize ImGui!");
}
inline void StartFrame() {
ImGui_ImplVulkan_NewFrame();
ImGui_ImplSDL3_NewFrame();
ImGui::NewFrame();
}
inline void Cleanup() {
ImGui_ImplVulkan_Shutdown();
ImGui_ImplSDL3_Shutdown();
ImGui::DestroyContext();
}
} // namespace gui
@@ -0,0 +1,43 @@
#pragma once
#include <imgui.h>
#include <imgui_internal.h>
namespace ImGui {
inline bool Spinner(const char* label, const float radius, const int thickness, const ImU32& color) {
ImGuiWindow* window = GetCurrentWindow();
if (window->SkipItems)
return false;
const ImGuiContext & g = *GImGui;
const ImGuiStyle& style = g.Style;
const ImGuiID id = window->GetID(label);
const ImVec2 pos = window->DC.CursorPos;
const ImVec2 size(radius*2, (radius + style.FramePadding.y)*2);
const ImRect bb(pos, ImVec2(pos.x + size.x, pos.y + size.y));
ItemSize(bb, style.FramePadding.y);
if (!ItemAdd(bb, id))
return false;
// Render
window->DrawList->PathClear();
constexpr int num_segments = 30;
const int start = abs(ImSin(g.Time*1.8f)*(num_segments-5));
const float a_min = IM_PI * 2.0f * static_cast<float>(start) / static_cast<float>(num_segments);
constexpr float a_max = IM_PI*2.0f * (static_cast<float>(num_segments) -3) / static_cast<float>(num_segments);
const auto centre = ImVec2(pos.x+radius, pos.y+radius+style.FramePadding.y);
for (int i = 0; i < num_segments; i++) {
const float a = a_min + static_cast<float>(i) / static_cast<float>(num_segments) * (a_max - a_min);
window->DrawList->PathLineTo(ImVec2(centre.x + ImCos(a+g.Time*8) * radius,
centre.y + ImSin(a+g.Time*8) * radius));
}
window->DrawList->PathStroke(color, false, thickness);
return true;
}
}
+8
View File
@@ -0,0 +1,8 @@
#pragma once
struct SettingsTab {
virtual ~SettingsTab() = default;
virtual void render() = 0;
bool modified = false;
};
+42
View File
@@ -0,0 +1,42 @@
#pragma once
#include <imgui.h>
#include <imgui_internal.h>
namespace ImGui {
inline bool BeginMainStatusBar()
{
ImGuiContext& g = *GetCurrentContext();
ImGuiViewportP* viewport = (ImGuiViewportP*)(void*)GetMainViewport();
// Notify of viewport change so GetFrameHeight() can be accurate in case of DPI change
SetCurrentViewport(NULL, viewport);
// For the main menu bar, which cannot be moved, we honor g.Style.DisplaySafeAreaPadding to ensure text can be visible on a TV set.
// FIXME: This could be generalized as an opt-in way to clamp window->DC.CursorStartPos to avoid SafeArea?
// FIXME: Consider removing support for safe area down the line... it's messy. Nowadays consoles have support for TV calibration in OS settings.
g.NextWindowData.MenuBarOffsetMinVal = ImVec2(g.Style.DisplaySafeAreaPadding.x, ImMax(g.Style.DisplaySafeAreaPadding.y - g.Style.FramePadding.y, 0.0f));
ImGuiWindowFlags window_flags = ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_MenuBar;
float height = GetFrameHeight();
bool is_open = BeginViewportSideBar("##MainStatusBar", viewport, ImGuiDir_Down, height, window_flags);
g.NextWindowData.MenuBarOffsetMinVal = ImVec2(0.0f, 0.0f);
if (is_open)
BeginMenuBar();
else
End();
return is_open;
}
inline void EndMainStatusBar()
{
EndMenuBar();
// When the user has left the menu layer (typically: closed menus through activation of an item), we restore focus to the previous window
// FIXME: With this strategy we won't be able to restore a NULL focus.
ImGuiContext& g = *GImGui;
if (g.CurrentWindow == g.NavWindow && g.NavLayer == ImGuiNavLayer_Main && !g.NavAnyRequest)
FocusTopMostWindowUnderOne(g.NavWindow, NULL, NULL, ImGuiFocusRequestFlags_UnlessBelowModal | ImGuiFocusRequestFlags_RestoreFocusedChild);
End();
}
}
+460
View File
@@ -0,0 +1,460 @@
#include <KaizenGui.hpp>
#include <backend/Core.hpp>
#include <ImGuiImpl/GUI.hpp>
#include <ImGuiImpl/ProgressIndicators.hpp>
#include <ImGuiImpl/StatusBar.hpp>
#include <resources/gamecontrollerdb.h>
KaizenGui::KaizenGui() noexcept : window("Kaizen " KAIZEN_VERSION_STR, 1280, 720), settingsWindow(window), vulkanWidget(window.getHandle()), emuThread(fpsCounter, settingsWindow) {
gui::Initialize(n64::Core::GetInstance().parallel.wsi, window.getHandle());
SDL_InitSubSystem(SDL_INIT_GAMEPAD);
SDL_AddGamepadMapping(gamecontrollerdb_str);
}
KaizenGui::~KaizenGui() {
gui::Cleanup();
SDL_Quit();
}
void KaizenGui::QueryDevices(const SDL_Event &event) {
switch (event.type) {
case SDL_EVENT_GAMEPAD_ADDED:
if (!gamepad) {
const auto index = event.gdevice.which;
gamepad = SDL_OpenGamepad(index);
info("Found controller!");
info("Name: {}", SDL_GetGamepadName(gamepad));
info("Vendor: {}", SDL_GetGamepadVendor(gamepad));
}
break;
case SDL_EVENT_GAMEPAD_REMOVED:
if (gamepad)
SDL_CloseGamepad(gamepad);
break;
default: break;
}
}
void KaizenGui::HandleInput(const SDL_Event &event) {
const n64::Core& core = n64::Core::GetInstance();
n64::PIF &pif = n64::Core::GetMem().mmio.si.pif;
switch(event.type) {
case SDL_EVENT_GAMEPAD_AXIS_MOTION:
if(!gamepad)
break;
{
pif.UpdateButton(0, n64::Controller::Key::Z, SDL_GetGamepadAxis(gamepad, SDL_GAMEPAD_AXIS_LEFT_TRIGGER) == SDL_JOYSTICK_AXIS_MAX);
pif.UpdateButton(0, n64::Controller::Key::CUp, SDL_GetGamepadAxis(gamepad, SDL_GAMEPAD_AXIS_RIGHTY) <= -127);
pif.UpdateButton(0, n64::Controller::Key::CDown, SDL_GetGamepadAxis(gamepad, SDL_GAMEPAD_AXIS_RIGHTY) >= 127);
pif.UpdateButton(0, n64::Controller::Key::CLeft, SDL_GetGamepadAxis(gamepad, SDL_GAMEPAD_AXIS_RIGHTX) <= -127);
pif.UpdateButton(0, n64::Controller::Key::CRight, SDL_GetGamepadAxis(gamepad, SDL_GAMEPAD_AXIS_RIGHTX) >= 127);
float xclamped = SDL_GetGamepadAxis(gamepad, SDL_GAMEPAD_AXIS_LEFTX);
if (xclamped < 0) {
xclamped /= static_cast<float>(std::abs(SDL_JOYSTICK_AXIS_MAX));
} else {
xclamped /= SDL_JOYSTICK_AXIS_MAX;
}
xclamped *= 86;
float yclamped = SDL_GetGamepadAxis(gamepad, SDL_GAMEPAD_AXIS_LEFTY);
if (yclamped < 0) {
yclamped /= static_cast<float>(std::abs(SDL_JOYSTICK_AXIS_MIN));
} else {
yclamped /= SDL_JOYSTICK_AXIS_MAX;
}
yclamped *= 86;
pif.UpdateAxis(0, n64::Controller::Axis::Y, static_cast<s8>(-yclamped));
pif.UpdateAxis(0, n64::Controller::Axis::X, static_cast<s8>( xclamped));
}
break;
case SDL_EVENT_GAMEPAD_BUTTON_DOWN:
case SDL_EVENT_GAMEPAD_BUTTON_UP:
if(!gamepad)
break;
pif.UpdateButton(0, n64::Controller::Key::A, SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_SOUTH));
pif.UpdateButton(0, n64::Controller::Key::B, SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_WEST));
pif.UpdateButton(0, n64::Controller::Key::Start, SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_START));
pif.UpdateButton(0, n64::Controller::Key::DUp, SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_DPAD_UP));
pif.UpdateButton(0, n64::Controller::Key::DDown, SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_DPAD_DOWN));
pif.UpdateButton(0, n64::Controller::Key::DLeft, SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_DPAD_LEFT));
pif.UpdateButton(0, n64::Controller::Key::DRight, SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_DPAD_RIGHT));
pif.UpdateButton(0, n64::Controller::Key::LT, SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER));
pif.UpdateButton(0, n64::Controller::Key::RT, SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER));
break;
case SDL_EVENT_KEY_DOWN:
case SDL_EVENT_KEY_UP:
{
const auto keys = SDL_GetKeyboardState(nullptr);
if((keys[SDL_SCANCODE_LCTRL] || keys[SDL_SCANCODE_RCTRL]) && keys[SDL_SCANCODE_O]) {
fileDialogOpen = true;
}
fastForward = keys[SDL_SCANCODE_SPACE];
if(!unlockFramerate)
core.parallel.SetFramerateUnlocked(fastForward);
if(core.romLoaded) {
if(keys[SDL_SCANCODE_P]) {
emuThread.TogglePause();
}
if(keys[SDL_SCANCODE_R]) {
emuThread.Reset();
}
if(keys[SDL_SCANCODE_Q]) {
emuThread.Stop();
}
}
if(gamepad)
break;
pif.UpdateButton(0, n64::Controller::Key::Z, keys[SDL_SCANCODE_Z]);
pif.UpdateButton(0, n64::Controller::Key::CUp, keys[SDL_SCANCODE_HOME]);
pif.UpdateButton(0, n64::Controller::Key::CDown, keys[SDL_SCANCODE_END]);
pif.UpdateButton(0, n64::Controller::Key::CLeft, keys[SDL_SCANCODE_DELETE]);
pif.UpdateButton(0, n64::Controller::Key::CRight, keys[SDL_SCANCODE_PAGEDOWN]);
pif.UpdateButton(0, n64::Controller::Key::A, keys[SDL_SCANCODE_X]);
pif.UpdateButton(0, n64::Controller::Key::B, keys[SDL_SCANCODE_C]);
pif.UpdateButton(0, n64::Controller::Key::Start, keys[SDL_SCANCODE_RETURN]);
pif.UpdateButton(0, n64::Controller::Key::DUp, keys[SDL_SCANCODE_I]);
pif.UpdateButton(0, n64::Controller::Key::DDown, keys[SDL_SCANCODE_K]);
pif.UpdateButton(0, n64::Controller::Key::DLeft, keys[SDL_SCANCODE_J]);
pif.UpdateButton(0, n64::Controller::Key::DRight, keys[SDL_SCANCODE_L]);
pif.UpdateButton(0, n64::Controller::Key::LT, keys[SDL_SCANCODE_A]);
pif.UpdateButton(0, n64::Controller::Key::RT, keys[SDL_SCANCODE_S]);
float x = 0, y = 0;
if (keys[SDL_SCANCODE_UP]) y = 86;
if (keys[SDL_SCANCODE_DOWN]) y = -86;
if (keys[SDL_SCANCODE_LEFT]) x = -86;
if (keys[SDL_SCANCODE_RIGHT]) x = 86;
pif.UpdateAxis(0, n64::Controller::Axis::X, x);
pif.UpdateAxis(0, n64::Controller::Axis::Y, y);
}
break;
default: break;
}
}
std::pair<std::optional<s64>, std::optional<Util::Error::MemoryAccess>> RenderErrorMessageDetails() {
auto lastPC = Util::Error::GetLastPC();
if(lastPC.has_value()) {
ImGui::Text("%s", std::format("Occurred @ PC = {:016X}", Util::Error::GetLastPC().value()).c_str());
}
auto memoryAccess = Util::Error::GetMemoryAccess();
if(memoryAccess.has_value()) {
const auto [is_write, size, address, written_val] = memoryAccess.value();
ImGui::Text("%s", std::format("{} {}-bit value @ {:08X}{}", is_write ? "Writing" : "Reading",
static_cast<u8>(size), address,
is_write ? std::format(" (value = 0x{:X})", written_val) : "")
.c_str());
}
return {lastPC, memoryAccess};
}
void KaizenGui::RenderUI() {
n64::Core& core = n64::Core::GetInstance();
gui::StartFrame();
if(ImGui::BeginMainMenuBar()) {
if(ImGui::BeginMenu("File")) {
if(ImGui::MenuItem("Open", "Ctrl-O")) {
fileDialogOpen = true;
}
if(ImGui::MenuItem("Exit")) {
quit = true;
emuThread.Stop();
}
ImGui::EndMenu();
}
if(ImGui::BeginMenu("Emulation")) {
ImGui::BeginDisabled(!core.romLoaded);
if(ImGui::MenuItem(core.pause ? "Resume" : "Pause", "P")) {
emuThread.TogglePause();
}
if(ImGui::MenuItem("Reset", "R")) {
emuThread.Reset();
}
if(ImGui::MenuItem("Stop", "Q")) {
emuThread.Stop();
core.romLoaded = false;
}
if(ImGui::Checkbox("Unlock framerate", &unlockFramerate)) {
core.parallel.SetFramerateUnlocked(unlockFramerate);
}
if(ImGui::MenuItem("Open Debugger")) {
debugger.Open();
}
ImGui::EndDisabled();
if(ImGui::MenuItem("Options")) {
settingsWindow.isOpen = true;
}
ImGui::EndMenu();
}
if(ImGui::BeginMenu("Help")) {
if(ImGui::MenuItem("About")) {
aboutOpen = true;
}
ImGui::EndMenu();
}
ImGui::EndMainMenuBar();
}
if(!Util::Error::IsHandled()) {
ImGui::OpenPopup(Util::Error::GetSeverity().as_c_str());
}
if(settingsWindow.isOpen) {
ImGui::OpenPopup("Settings", ImGuiPopupFlags_None);
}
if(aboutOpen) {
ImGui::OpenPopup("About Kaizen");
}
settingsWindow.render();
debugger.render();
const ImVec2 center = ImGui::GetMainViewport()->GetCenter();
ImGui::SetNextWindowPos(center, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f));
if (ImGui::BeginPopupModal("About Kaizen", &aboutOpen, ImGuiWindowFlags_AlwaysAutoResize)) {
ImGui::Text("Kaizen is a Nintendo 64 emulator that strives");
ImGui::Text("to offer a friendly user experience and compatibility.");
ImGui::Text("Kaizen is licensed under the BSD 3-clause license.");
ImGui::Text("Nintendo 64 is a registered trademark of Nintendo Co., Ltd.");
ImGui::Separator();
ImGui::Text("Kaizen %s%s", KAIZEN_USE_HASH ? "dev build " : "", KAIZEN_VERSION_STR);
ImGui::Separator();
if(ImGui::Button("OK")) {
aboutOpen = false;
ImGui::CloseCurrentPopup();
}
ImGui::EndPopup();
}
ImGui::SetNextWindowPos(center, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f));
if (ImGui::BeginPopupModal(Util::Error::GetSeverity().as_c_str(), nullptr, ImGuiWindowFlags_AlwaysAutoResize)) {
emuThread.TogglePause();
switch(Util::Error::GetSeverity().as_enum) {
case Util::Error::Severity::WARN: {
ImGui::PushStyleColor(ImGuiCol_TitleBg, 0x8054eae5);
ImGui::PushStyleColor(ImGuiCol_Text, 0xff7be4e1);
ImGui::Text("Warning of type: %s", Util::Error::GetType().as_c_str());
ImGui::PopStyleColor();
ImGui::PopStyleColor();
ImGui::Text(R"(Warning message: "%s")", Util::Error::GetError().c_str());
RenderErrorMessageDetails();
if(n64::Core::GetInstance().romLoaded && !n64::Core::GetInstance().pause) {
const bool ignore = ImGui::Button("Try continuing"); ImGui::SameLine();
const bool stop = ImGui::Button("Stop emulation"); ImGui::SameLine();
const bool chooseAnother = ImGui::Button("Choose another ROM");
if(ignore || stop || chooseAnother) {
Util::Error::SetHandled();
ImGui::CloseCurrentPopup();
}
if(ignore) {
emuThread.TogglePause();
}
if(stop || chooseAnother) {
emuThread.Stop();
}
if(chooseAnother) {
fileDialogOpen = true;
}
break;
}
if(ImGui::Button("OK"))
ImGui::CloseCurrentPopup();
} break;
case Util::Error::Severity::UNRECOVERABLE: {
emuThread.Stop();
ImGui::PushStyleColor(ImGuiCol_TitleBg, 0x800000ff);
ImGui::PushStyleColor(ImGuiCol_Text, 0xff3b3bbf);
ImGui::Text("An unrecoverable error has occurred! Emulation has been stopped...");
ImGui::Text("Error of type: %s", Util::Error::GetType().as_c_str());
ImGui::PopStyleColor();
ImGui::PopStyleColor();
ImGui::Text(R"(Error message: "%s")", Util::Error::GetError().c_str());
RenderErrorMessageDetails();
if(ImGui::Button("OK"))
ImGui::CloseCurrentPopup();
} break;
case Util::Error::Severity::NON_FATAL: {
ImGui::PushStyleColor(ImGuiCol_TitleBg, 0x800000ff);
ImGui::PushStyleColor(ImGuiCol_Text, 0xff3b3bbf);
ImGui::Text("An error has occurred!");
ImGui::Text("Error of type: %s", Util::Error::GetType().as_c_str());
ImGui::PopStyleColor();
ImGui::PopStyleColor();
ImGui::Text(R"(Error message: "%s")", Util::Error::GetError().c_str());
auto [lastPC, memoryAccess] = RenderErrorMessageDetails();
const bool ignore = ImGui::Button("Try continuing"); ImGui::SameLine();
const bool stop = ImGui::Button("Stop emulation"); ImGui::SameLine();
const bool chooseAnother = ImGui::Button("Choose another ROM");
const bool openInDebugger = lastPC.has_value() ? ImGui::Button("Add breakpoint at this PC and open the debugger") : false;
if(ignore || stop || chooseAnother || openInDebugger) {
Util::Error::SetHandled();
ImGui::CloseCurrentPopup();
}
if(ignore) {
emuThread.TogglePause();
}
if(stop || chooseAnother) {
emuThread.Stop();
}
if(chooseAnother) {
fileDialogOpen = true;
}
if(openInDebugger) {
if(!n64::Core::GetInstance().breakpoints.contains(lastPC.value()))
n64::Core::GetInstance().ToggleBreakpoint(lastPC.value());
debugger.Open();
emuThread.Reset();
}
} break;
default: break;
}
ImGui::EndPopup();
}
if(ImGui::BeginMainStatusBar()) {
ImGui::Text("FPS: %.2f", ImGui::GetIO().Framerate);
ImGui::EndMainStatusBar();
}
if (shouldDisplaySpinner) {
ImGui::SetNextWindowPos({static_cast<float>(width) * 0.5f, static_cast<float>(height) * 0.5f}, 0, ImVec2(0.5f, 0.5f));
ImGui::PushStyleColor(ImGuiCol_WindowBg, IM_COL32_BLACK_TRANS);
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f);
ImGui::Begin("##spinnerContainer", nullptr, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoDecoration);
ImGui::Spinner("##spinner", 10.f, 4.f, ImGui::GetColorU32(ImGui::GetStyle().Colors[ImGuiCol_TitleBgActive]));
ImGui::SameLine();
ImGui::PushFont(nullptr, ImGui::GetStyle().FontSizeBase * 2.f);
ImGui::Text("Loading \"%s\"...", fs::path(fileToLoad).filename().string().c_str());
ImGui::PopFont();
ImGui::End();
ImGui::PopStyleVar();
ImGui::PopStyleColor();
}
ImGui::Render();
if (ImGui::GetIO().ConfigFlags & ImGuiConfigFlags_ViewportsEnable) {
ImGui::UpdatePlatformWindows();
ImGui::RenderPlatformWindowsDefault();
}
if(fileDialogOpen) {
fileDialogOpen = false;
constexpr SDL_DialogFileFilter filters[] = {{"All files", "*"}, {"Nintendo 64 executable", "n64;z64;v64"}, {"Nintendo 64 executable archive", "rar;tar;zip;7z"}};
SDL_ShowOpenFileDialog([](void *userdata, const char * const *filelist, int) {
auto kaizen = static_cast<KaizenGui*>(userdata);
if (!filelist) {
panic("An error occured: {}", SDL_GetError());
}
if (!*filelist) {
warn("The user did not select any file.");
warn("Most likely, the dialog was canceled.");
return;
}
kaizen->fileToLoad = *filelist;
kaizen->shouldDisplaySpinner = true;
std::thread fileWorker(&KaizenGui::FileWorker, kaizen);
fileWorker.detach();
}, this, window.getHandle(), filters, 3, nullptr, false);
}
if(minimized)
return;
if(core.romLoaded) {
core.parallel.UpdateScreen<true>();
return;
}
core.parallel.UpdateScreen<false>();
}
void KaizenGui::LoadROM(const std::string &path) noexcept {
n64::Core& core = n64::Core::GetInstance();
core.LoadROM(path);
const auto gameNameDB = n64::Core::GetMem().rom.gameNameDB;
SDL_SetWindowTitle(window.getHandle(), ("Kaizen " KAIZEN_VERSION_STR " - " + gameNameDB).c_str());
}
void KaizenGui::run() {
while(!quit) {
SDL_Event e;
while (SDL_PollEvent(&e)) {
ImGui_ImplSDL3_ProcessEvent(&e);
switch(e.type) {
case SDL_EVENT_QUIT:
quit = true;
emuThread.Stop();
break;
case SDL_EVENT_WINDOW_MINIMIZED:
minimized = true;
break;
case SDL_EVENT_WINDOW_RESTORED:
minimized = false;
break;
default:
}
QueryDevices(e);
HandleInput(e);
}
SDL_GetWindowSize(window.getHandle(), &width, &height);
emuThread.run();
RenderUI();
}
}
void KaizenGui::LoadTAS(const std::string &path) noexcept {
n64::Core::GetInstance().LoadTAS(fs::path(path));
}
+49
View File
@@ -0,0 +1,49 @@
#pragma once
#include <RenderWidget.hpp>
#include <NativeWindow.hpp>
#include <Debugger.hpp>
#include <EmuThread.hpp>
#include <SDL3/SDL_gamepad.h>
class KaizenGui final {
gui::NativeWindow window;
public:
explicit KaizenGui() noexcept;
~KaizenGui();
double fpsCounter = -1.0;
bool fastForward = false;
bool unlockFramerate = false;
bool minimized = false;
SettingsWindow settingsWindow;
RenderWidget vulkanWidget;
EmuThread emuThread;
Debugger debugger;
SDL_Gamepad* gamepad = nullptr;
void run();
static void LoadTAS(const std::string &path) noexcept;
void LoadROM(const std::string &path) noexcept;
private:
int width{}, height{};
bool aboutOpen = false;
bool fileDialogOpen = false;
bool quit = false;
bool shouldDisplaySpinner = false;
std::string fileToLoad = "";
void RenderUI();
void HandleInput(const SDL_Event &event);
void QueryDevices(const SDL_Event &event);
[[noreturn]] void FileWorker() {
while (true) {
if (!fileToLoad.empty()) {
LoadROM(fileToLoad);
shouldDisplaySpinner = false;
fileToLoad = "";
}
}
}
};
+28
View File
@@ -0,0 +1,28 @@
#pragma once
#include <SDL3/SDL.h>
#include <string>
#include <memory>
#include <volk.h>
#include <utils/log.hpp>
namespace gui {
struct NativeWindow {
NativeWindow(const std::string& title, int w, int h, int posX = SDL_WINDOWPOS_CENTERED, int posY = SDL_WINDOWPOS_CENTERED) {
SDL_Init(SDL_INIT_VIDEO);
float scale = SDL_GetDisplayContentScale(SDL_GetPrimaryDisplay());
window = SDL_CreateWindow(title.c_str(), w * scale, h * scale, SDL_WINDOW_VULKAN | SDL_WINDOW_RESIZABLE | SDL_WINDOW_HIGH_PIXEL_DENSITY);
if(volkInitialize() != VK_SUCCESS) {
panic("Failed to initialize Volk!");
}
}
~NativeWindow() {
SDL_DestroyWindow(window);
}
SDL_Window* getHandle() { return window; }
private:
SDL_Window* window;
};
}
+12
View File
@@ -0,0 +1,12 @@
#include <Core.hpp>
#include <KaizenGui.hpp>
#include <RenderWidget.hpp>
#include <SDL3/SDL.h>
#include <imgui_impl_sdl3.h>
RenderWidget::RenderWidget(SDL_Window* window) {
wsiPlatform = std::make_shared<SDLWSIPlatform>(window);
windowInfo = std::make_shared<SDLParallelRdpWindowInfo>(window);
n64::Core& core = n64::Core::GetInstance();
core.parallel.Init(wsiPlatform, windowInfo, core.GetMem().GetRDRAMPtr());
}
+79
View File
@@ -0,0 +1,79 @@
#pragma once
#include <ParallelRDPWrapper.hpp>
#include <SDL3/SDL.h>
#include <SDL3/SDL_vulkan.h>
struct InputSettings;
namespace n64 {
struct Core;
}
class SDLParallelRdpWindowInfo final : public ParallelRDP::WindowInfo {
public:
explicit SDLParallelRdpWindowInfo(SDL_Window* window) : window(window) {}
CoordinatePair get_window_size() override {
int w,h;
SDL_GetWindowSizeInPixels(window, &w, &h);
return CoordinatePair{static_cast<float>(w), static_cast<float>(h)};
}
private:
SDL_Window* window{};
};
class SDLWSIPlatform final : public Vulkan::WSIPlatform {
public:
explicit SDLWSIPlatform(SDL_Window* window) : window(window) {}
~SDLWSIPlatform() = default;
std::vector<const char *> get_instance_extensions() override {
auto vec = std::vector<const char *>();
u32 extCount;
const auto &extensions = SDL_Vulkan_GetInstanceExtensions(&extCount);
vec.resize(extCount);
for (u32 i = 0; i < extCount; i++) {
vec[i] = extensions[i];
}
return vec;
}
VkSurfaceKHR create_surface(VkInstance instance, VkPhysicalDevice pDevice) override {
SDL_Vulkan_CreateSurface(window, instance, nullptr, &surface);
return surface;
}
void destroy_surface(VkInstance instance, VkSurfaceKHR surface) override {
SDL_Vulkan_DestroySurface(instance, surface, nullptr);
}
uint32_t get_surface_width() override { return 640; }
uint32_t get_surface_height() override { return 480; }
bool alive(Vulkan::WSI &) override { return true; }
void poll_input() override {}
void poll_input_async(Granite::InputTrackerHandler *handler) override {}
void event_frame_tick(double frame, double elapsed) override {}
const VkApplicationInfo *get_application_info() override { return &appInfo; }
VkApplicationInfo appInfo{.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO, .apiVersion = VK_API_VERSION_1_3};
SDL_Window* window{};
VkSurfaceKHR surface;
private:
bool gamepadConnected = false;
};
class RenderWidget final {
public:
explicit RenderWidget(SDL_Window*);
std::shared_ptr<ParallelRDP::WindowInfo> windowInfo;
std::shared_ptr<SDLWSIPlatform> wsiPlatform;
};
+38
View File
@@ -0,0 +1,38 @@
#include <AudioSettings.hpp>
#include <imgui.h>
#include <Options.hpp>
AudioSettings::AudioSettings() {
lockChannels = Options::GetInstance().GetValue<bool>("audio", "lock");
volumeL = Options::GetInstance().GetValue<float>("audio", "volumeL") * 100;
volumeR = Options::GetInstance().GetValue<float>("audio", "volumeR") * 100;
}
void AudioSettings::render() {
if(ImGui::Checkbox("Lock channels:", &lockChannels)) {
Options::GetInstance().SetValue("audio", "lock", lockChannels);
if(lockChannels) {
volumeR = volumeL;
Options::GetInstance().SetValue("audio", "volumeR", volumeR / 100.f);
}
modified = true;
}
if(ImGui::SliderFloat("Volume L", &volumeL, 0.f, 100.f, "%.2f")) {
Options::GetInstance().SetValue("audio", "volumeL", volumeL / 100.f);
if (lockChannels) {
volumeR = volumeL;
Options::GetInstance().SetValue("audio", "volumeR", volumeR / 100.f);
}
modified = true;
}
ImGui::BeginDisabled(lockChannels);
if(ImGui::SliderFloat("Volume R", &volumeR, 0.f, 100.f, "%.2f")) {
Options::GetInstance().SetValue("audio", "volumeR", volumeR / 100.f);
modified = true;
}
ImGui::EndDisabled();
}
+11
View File
@@ -0,0 +1,11 @@
#pragma once
#include <SettingsTab.hpp>
struct AudioSettings final : SettingsTab {
bool lockChannels = false;
float volumeL{};
float volumeR{};
explicit AudioSettings();
void render() override;
};
+43
View File
@@ -0,0 +1,43 @@
#include <CPUSettings.hpp>
#include <Options.hpp>
#include <log.hpp>
#include <imgui.h>
CPUSettings::CPUSettings() {
if (Options::GetInstance().GetValue<std::string>("cpu", "type") == "jit") {
selectedCpuTypeIndex = 1;
} else {
selectedCpuTypeIndex = 0;
}
}
void CPUSettings::render() {
const char* items[] = {
"Interpreter",
"Dynamic Recompiler"
};
const char* combo_preview_value = items[selectedCpuTypeIndex];
if (ImGui::BeginCombo("CPU Type", combo_preview_value)) {
for (int n = 0; n < IM_ARRAYSIZE(items); n++) {
const bool is_selected = (selectedCpuTypeIndex == n);
if (ImGui::Selectable(items[n], is_selected)) {
selectedCpuTypeIndex = n;
modified = true;
}
// Set the initial focus when opening the combo (scrolling + keyboard navigation focus)
if (is_selected)
ImGui::SetItemDefaultFocus();
}
ImGui::EndCombo();
}
if(modified) {
if(selectedCpuTypeIndex == 0) {
Options::GetInstance().SetValue<std::string>("cpu", "type", "interpreter");
} else {
Options::GetInstance().SetValue<std::string>("cpu", "type", "jit");
}
}
}
+8
View File
@@ -0,0 +1,8 @@
#pragma once
#include <SettingsTab.hpp>
struct CPUSettings final : SettingsTab {
int selectedCpuTypeIndex = 0;
void render() override;
explicit CPUSettings();
};
+35
View File
@@ -0,0 +1,35 @@
#include <GeneralSettings.hpp>
#include <Options.hpp>
#include <imgui.h>
#include <log.hpp>
GeneralSettings::GeneralSettings(gui::NativeWindow& window) : window(window) {
savesPath = Options::GetInstance().GetValue<std::string>("general", "savePath");
}
void GeneralSettings::render() {
if(ImGui::Button("Pick...")) {
SDL_ShowOpenFolderDialog([](void *userdata, const char * const *filelist, int _) {
auto* general = static_cast<GeneralSettings*>(userdata);
if (!filelist) {
panic("An error occurred: {}", SDL_GetError());
}
if (!*filelist) {
warn("The user did not select any file.");
warn("Most likely, the dialog was canceled.");
general->modified = false;
return;
}
general->savesPath = fs::absolute(*filelist).string();
Options::GetInstance().SetValue<std::string>("general", "savePath", general->savesPath);
general->modified = true;
}, this, window.getHandle(), nullptr, false);
}
ImGui::SameLine();
ImGui::BeginDisabled();
ImGui::InputText("Save Path", const_cast<char*>(savesPath.c_str()), savesPath.length());
ImGui::EndDisabled();
}
+11
View File
@@ -0,0 +1,11 @@
#pragma once
#include <SettingsTab.hpp>
#include <NativeWindow.hpp>
struct GeneralSettings final : SettingsTab {
void render() override;
explicit GeneralSettings(gui::NativeWindow&);
private:
gui::NativeWindow& window;
std::string savesPath;
};
+48
View File
@@ -0,0 +1,48 @@
#include <SettingsWindow.hpp>
#include <Options.hpp>
#include <imgui.h>
#include <ranges>
bool SettingsWindow::render() {
const ImVec2 center = ImGui::GetMainViewport()->GetCenter();
ImGui::SetNextWindowPos(center, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f));
if(!ImGui::BeginPopupModal("Settings", &isOpen, ImGuiWindowFlags_AlwaysAutoResize))
return false;
if(!ImGui::BeginTabBar("SettingsTabBar"))
return false;
for (auto& [name, tab] : tabs) {
if (ImGui::BeginTabItem(name.c_str())) {
tab->render();
if (tab->modified && !applyEnabled)
applyEnabled = true;
ImGui::EndTabItem();
}
}
ImGui::EndTabBar();
ImGui::BeginDisabled(!applyEnabled);
if(ImGui::Button("Apply")) {
applyEnabled = false;
Options::GetInstance().Apply();
for (const auto &tab : tabs | std::views::values) {
tab->modified = false;
}
}
ImGui::EndDisabled();
ImGui::SameLine();
if(ImGui::Button("Cancel")) {
isOpen = false;
ImGui::CloseCurrentPopup();
}
ImGui::EndPopup();
return true;
}
+26
View File
@@ -0,0 +1,26 @@
#pragma once
#include <AudioSettings.hpp>
#include <CPUSettings.hpp>
#include <GeneralSettings.hpp>
#include <NativeWindow.hpp>
#include <vector>
class SettingsWindow final {
gui::NativeWindow& window;
GeneralSettings generalSettings;
CPUSettings cpuSettings;
AudioSettings audioSettings;
bool applyEnabled = false;
std::vector<std::pair<std::string, SettingsTab*>> tabs = {
{ "General", &generalSettings },
{ "CPU", &cpuSettings },
{ "Audio", &audioSettings },
};
public:
bool isOpen = false;
bool render();
explicit SettingsWindow(gui::NativeWindow& window) : window(window), generalSettings(window) {}
[[nodiscard]] float getVolumeL() const { return audioSettings.volumeL / 100.f; }
[[nodiscard]] float getVolumeR() const { return audioSettings.volumeR / 100.f; }
};
+17
View File
@@ -0,0 +1,17 @@
#include <KaizenGui.hpp>
#include <cflags.hpp>
int main(const int argc, char **argv) {
KaizenGui kaizenGui;
cflags::cflags flags;
flags.add_string_callback('\0', "rom", [&kaizenGui](const std::string& v) { kaizenGui.LoadROM(v); }, "Rom to launch from command-line");
flags.add_string_callback('\0', "movie", [](const std::string& v) { KaizenGui::LoadTAS(v); }, "Mupen Movie to replay");
if(!flags.parse(argc, argv)) {
return -1;
}
kaizenGui.run();
return 0;
}
+20
View File
@@ -0,0 +1,20 @@
#pragma once
#include <cstdint>
#ifdef USE_NEON
#include <sse2neon.h>
#else
#include <emmintrin.h>
#include <smmintrin.h>
#endif
using u8 = uint8_t;
using u16 = uint16_t;
using u32 = uint32_t;
using u64 = uint64_t;
using s8 = int8_t;
using s16 = int16_t;
using s32 = int32_t;
using s64 = int64_t;
using u128 = __uint128_t;
using s128 = __int128_t;
using m128i = __m128i;
+149
View File
@@ -0,0 +1,149 @@
#pragma once
#include <string>
#include <types.hpp>
#include <format>
#include <optional>
namespace Util {
struct Error {
struct Severity {
enum {
NONE,
WARN,
NON_FATAL,
UNRECOVERABLE,
} as_enum;
[[nodiscard]] const char* as_c_str() const {
switch(as_enum) {
case NONE: return "";
case WARN: return "Warning";
case NON_FATAL: return "Error";
case UNRECOVERABLE: return "Unrecoverable Error";
}
return "Unknown";
}
};
struct MemoryAccess {
bool is_write;
enum Size {
BYTE = 8, SHORT = 16, WORD = 32, DWORD = 64
} size;
u32 address;
u64 written_val;
};
struct Type {
enum {
SCHEDULER_EOL,
SCHEDULER_UNKNOWN,
UNHANDLED_EXCEPTION,
UNHANDLED_INSTRUCTION,
INVALID_INSTRUCTION_FORMAT,
TLB_LIMIT_EXCEEDED,
TLB_INVALID_ERROR,
TLB_UNHANDLED_ERROR,
TLB_UNHANDLED_MAPPING,
JIT_BRANCH_INSIDE_DELAY_SLOT,
JIT_INVALID_X86_REG_ADDRESSING,
COULD_NOT_SYNC_SAVE_DATA,
SAVE_DATA_IS_CORRUPT_OR_INVALID_SIZE,
MMAP_MAKE_SINK_ERROR,
MEM_INVALID_ACCESS,
MEM_UNHANDLED_ACCESS,
RDP_LIMIT_EXCEEDED,
FLASH_EXECUTE_COMMAND,
PIF_UNHANDLED_CHANNEL,
UNHANDLED_COP0_STATUS_BIT,
COP0_INVALID_ACCESS,
COP0_UNHANDLED_ACCESS,
SYSTEM_DIALOG_ERROR,
TAS_LOAD_ERROR,
ROM_LOAD_ERROR,
SAVE_OPTIONS_ERROR,
DIALOG_CANCELED,
UNKNOWN_CIC_TYPE,
GAME_DB_NOT_MATCHED,
CAPSTONE_ERROR,
} as_enum;
[[nodiscard]] const char* as_c_str() const {
switch(as_enum) {
case SCHEDULER_EOL: return "SCHEDULER_EOL";
case SCHEDULER_UNKNOWN: return "SCHEDULER_UNKNOWN";
case UNHANDLED_EXCEPTION: return "UNHANDLED_EXCEPTION";
case UNHANDLED_INSTRUCTION: return "UNHANDLED_INSTRUCTION";
case INVALID_INSTRUCTION_FORMAT: return "INVALID_INSTRUCTION_FORMAT";
case TLB_LIMIT_EXCEEDED: return "TLB_LIMIT_EXCEEDED";
case TLB_INVALID_ERROR: return "TLB_INVALID_ERROR";
case TLB_UNHANDLED_ERROR: return "TLB_UNHANDLED_ERROR";
case TLB_UNHANDLED_MAPPING: return "TLB_UNHANDLED_MAPPING";
case JIT_BRANCH_INSIDE_DELAY_SLOT: return "JIT_BRANCH_INSIDE_DELAY_SLOT";
case JIT_INVALID_X86_REG_ADDRESSING: return "JIT_INVALID_X86_REG_ADDRESSING";
case COULD_NOT_SYNC_SAVE_DATA: return "COULD_NOT_SYNC_SAVE_DATA";
case SAVE_DATA_IS_CORRUPT_OR_INVALID_SIZE: return "SAVE_DATA_IS_CORRUPT_OR_INVALID_SIZE";
case MMAP_MAKE_SINK_ERROR: return "MMAP_MAKE_SINK_ERROR";
case MEM_INVALID_ACCESS: return "MEM_INVALID_ACCESS";
case MEM_UNHANDLED_ACCESS: return "MEM_UNHANDLED_ACCESS";
case RDP_LIMIT_EXCEEDED: return "RDP_LIMIT_EXCEEDED";
case FLASH_EXECUTE_COMMAND: return "FLASH_EXECUTE_COMMAND";
case PIF_UNHANDLED_CHANNEL: return "PIF_UNHANDLED_CHANNEL";
case UNHANDLED_COP0_STATUS_BIT: return "UNHANDLED_COP0_STATUS_BIT";
case COP0_INVALID_ACCESS: return "COP0_INVALID_ACCESS";
case COP0_UNHANDLED_ACCESS: return "COP0_UNHANDLED_ACCESS";
case SYSTEM_DIALOG_ERROR: return "SYSTEM_DIALOG_ERROR";
case TAS_LOAD_ERROR: return "TAS_LOAD_ERROR";
case ROM_LOAD_ERROR: return "ROM_LOAD_ERROR";
case SAVE_OPTIONS_ERROR: return "SAVE_OPTIONS_ERROR";
case DIALOG_CANCELED: return "DIALOG_CANCELED";
case UNKNOWN_CIC_TYPE: return "UNKNOWN_CIC_TYPE";
case GAME_DB_NOT_MATCHED: return "GAME_DB_NOT_MATCHED";
case CAPSTONE_ERROR: return "CAPSTONE_ERROR";
default: return "Unknown";
}
}
};
template <class... Args>
void Throw (const Severity severity, const Type type, const std::optional<u64> lastPC,
const std::optional<MemoryAccess> memoryAccess,
const std::format_string<Args...> fmt, Args... args) {
this->severity = severity;
this->lastPC = lastPC;
this->memoryAccess = memoryAccess;
this->type = type;
err = std::format(fmt, std::forward<Args>(args)...);
}
static Error& GetInstance() {
static Error instance;
return instance;
}
static std::string& GetError() { return GetInstance().err; }
static Severity& GetSeverity() { return GetInstance().severity; }
static Type& GetType() { return GetInstance().type; }
static std::optional<s64>& GetLastPC() { return GetInstance().lastPC; }
static std::optional<MemoryAccess>& GetMemoryAccess() { return GetInstance().memoryAccess; }
static bool IsHandled() {
return GetSeverity().as_enum == Severity::NONE;
}
static void SetHandled() {
GetSeverity() = {};
GetError() = "";
GetType() = {};
GetLastPC() = {};
GetMemoryAccess() = {};
}
private:
std::string err;
Severity severity = {};
Type type = {};
std::optional<s64> lastPC = {};
std::optional<MemoryAccess> memoryAccess = {};
};
}
+58
View File
@@ -0,0 +1,58 @@
#include <File.hpp>
#include <algorithm>
#include <unarr.h>
namespace Util {
std::vector<u8> OpenROM(const std::string &filename, size_t &sizeAdjusted) {
auto buf = ReadFileBinary(filename);
sizeAdjusted = NextPow2(buf.size());
return buf;
}
std::vector<u8> OpenArchive(const std::string &path, size_t &sizeAdjusted) {
const auto stream = ar_open_file(fs::path(path).string().c_str());
if (!stream) {
panic("Could not open archive! Are you sure it's an archive?");
}
ar_archive *archive = ar_open_zip_archive(stream, false);
if (!archive)
archive = ar_open_rar_archive(stream);
if (!archive)
archive = ar_open_7z_archive(stream);
if (!archive)
archive = ar_open_tar_archive(stream);
if (!archive) {
ar_close(stream);
panic("Could not open archive! Are you sure it's a supported archive? (7z, zip, rar and tar are supported)");
}
std::vector<u8> buf{};
std::vector<std::string> rom_exts{".n64", ".z64", ".v64", ".N64", ".Z64", ".V64"};
while (ar_parse_entry(archive)) {
auto filename = ar_entry_get_name(archive);
auto extension = fs::path(filename).extension();
if (std::ranges::any_of(rom_exts, [&](const auto &x) { return extension == x; })) {
const auto size = ar_entry_get_size(archive);
sizeAdjusted = NextPow2(size);
buf.resize(sizeAdjusted);
ar_entry_uncompress(archive, buf.data(), size);
break;
}
ar_close_archive(archive);
ar_close(stream);
panic("Could not find any rom image in the archive!");
}
ar_close_archive(archive);
ar_close(stream);
return buf;
}
}
+46
View File
@@ -0,0 +1,46 @@
#pragma once
#include <fstream>
#include <log.hpp>
#include <vector>
#include <filesystem>
namespace fs = std::filesystem;
namespace Util {
FORCE_INLINE std::vector<u8> ReadFileBinary(const std::string &path) {
std::ifstream file(path, std::ios::binary);
return {std::istreambuf_iterator{file}, {}};
}
FORCE_INLINE void WriteFileBinary(const std::vector<u8> &data, const std::string &path) {
std::ofstream file(path, std::ios::binary);
std::copy(data.begin(), data.end(), std::ostreambuf_iterator{file});
}
FORCE_INLINE void WriteFileBinary(const u8 *data, const size_t size, const std::string &path) {
FILE *out = fopen(path.c_str(), "wb");
fwrite(data, size, 1, out);
fclose(out);
}
template <size_t Size>
FORCE_INLINE void WriteFileBinary(const std::array<u8, Size> &data, const std::string &path) {
std::ofstream file(path, std::ios::binary);
std::copy(data.begin(), data.end(), std::ostreambuf_iterator{file});
}
FORCE_INLINE size_t NextPow2(size_t num) {
// Taken from "Bit Twiddling Hacks" by Sean Anderson:
// https://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2
--num;
num |= num >> 1;
num |= num >> 2;
num |= num >> 4;
num |= num >> 8;
num |= num >> 16;
return num + 1;
}
std::vector<u8> OpenROM(const std::string &filename, size_t &sizeAdjusted);
std::vector<u8> OpenArchive(const std::string &path, size_t &sizeAdjusted);
} // namespace Util
+106
View File
@@ -0,0 +1,106 @@
#pragma once
#include <cmath>
#include <common.hpp>
namespace Util {
static FORCE_INLINE auto roundCeil(float f) {
#ifdef SIMD_SUPPORT
__m128 t = _mm_set_ss(f);
t = _mm_round_ss(t, t, _MM_FROUND_TO_POS_INF);
return _mm_cvtss_f32(t);
#else
return ceilf(f);
#endif
}
static FORCE_INLINE auto roundCeil(double f) {
#ifdef SIMD_SUPPORT
__m128d t = _mm_set_sd(f);
t = _mm_round_sd(t, t, _MM_FROUND_TO_POS_INF);
return _mm_cvtsd_f64(t);
#else
return ceil(f);
#endif
}
static FORCE_INLINE auto roundNearest(float f) {
#ifdef SIMD_SUPPORT
__m128 t = _mm_set_ss(f);
t = _mm_round_ss(t, t, _MM_FROUND_TO_NEAREST_INT);
return _mm_cvtss_f32(t);
#else
return roundf(f);
#endif
}
static FORCE_INLINE auto roundNearest(double f) {
#ifdef SIMD_SUPPORT
__m128d t = _mm_set_sd(f);
t = _mm_round_sd(t, t, _MM_FROUND_TO_NEAREST_INT);
return _mm_cvtsd_f64(t);
#else
return round(f);
#endif
}
static FORCE_INLINE auto roundCurrent(float f) {
#ifdef SIMD_SUPPORT
auto t = _mm_set_ss(f);
t = _mm_round_ss(t, t, _MM_FROUND_CUR_DIRECTION);
return _mm_cvtss_f32(t);
#else
return rint(f);
#endif
}
static FORCE_INLINE auto roundCurrent(double f) {
#ifdef SIMD_SUPPORT
auto t = _mm_set_sd(f);
t = _mm_round_sd(t, t, _MM_FROUND_CUR_DIRECTION);
return _mm_cvtsd_f64(t);
#else
return rint(f);
#endif
}
static FORCE_INLINE auto roundFloor(float f) {
#ifdef SIMD_SUPPORT
__m128 t = _mm_set_ss(f);
t = _mm_round_ss(t, t, _MM_FROUND_TO_NEG_INF);
return _mm_cvtss_f32(t);
#else
return floor(f);
#endif
}
static FORCE_INLINE auto roundFloor(double f) {
#ifdef SIMD_SUPPORT
__m128d t = _mm_set_sd(f);
t = _mm_round_sd(t, t, _MM_FROUND_TO_NEG_INF);
return _mm_cvtsd_f64(t);
#else
return floor(f);
#endif
}
static FORCE_INLINE auto roundTrunc(float f) {
#ifdef SIMD_SUPPORT
__m128 t = _mm_set_ss(f);
t = _mm_round_ss(t, t, _MM_FROUND_TO_ZERO);
return _mm_cvtss_f32(t);
#else
return trunc(f);
#endif
}
static FORCE_INLINE auto roundTrunc(double f) {
#ifdef SIMD_SUPPORT
__m128d t = _mm_set_sd(f);
t = _mm_round_sd(t, t, _MM_FROUND_TO_ZERO);
return _mm_cvtsd_f64(t);
#else
return trunc(f);
#endif
}
} // namespace Util
+224
View File
@@ -0,0 +1,224 @@
#pragma once
#include <types.hpp>
#include <common.hpp>
namespace n64 {
struct Instruction {
Instruction(u32 v) { instr.raw = v; }
void operator=(u32 v) { instr.raw = v; }
operator u32() const { return instr.raw; }
inline u8 rs() const { return instr.rtype.rs; }
inline u8 rt() const { return instr.rtype.rt; }
inline u8 rd() const { return instr.rtype.rd; }
inline u8 sa() const { return instr.rtype.sa; }
inline u8 fs() const { return rd(); }
inline u8 ft() const { return rt(); }
inline u8 fd() const { return sa(); }
inline u8 base() const { return rs(); }
inline u8 vt() const { return rt(); }
inline u8 vs() const { return rd(); }
inline u8 vd() const { return fd(); }
inline u8 e1() const { return (instr.raw >> 7) & 0x0f; }
inline u8 e2() const { return rs() & 0x0f; }
inline u16 imm() const { return instr.itype.imm; }
inline u32 target() const { return instr.jtype.target; }
inline u8 opcode() const { return instr.opcode.op; }
inline u8 special() const { return instr.opcode.special; }
inline u8 regimm() const { return instr.opcode.regimm; }
inline u8 cop_rs() const { return instr.opcode.cop_rs; }
inline u8 cop_rt() const { return instr.opcode.cop_rt; }
inline u8 cop_funct() const { return instr.opcode.funct; }
union {
struct {
unsigned imm:16;
unsigned rt:5;
unsigned rs:5;
unsigned op:6;
} itype;
struct {
unsigned target:26;
unsigned op:6;
} jtype;
struct {
unsigned funct:6;
unsigned sa:5;
unsigned rd:5;
unsigned rt:5;
unsigned rs:5;
unsigned op:6;
} rtype;
union {
struct {
unsigned special_lo:3;
unsigned special_hi:3;
unsigned:26;
};
struct {
unsigned special:6;
unsigned:26;
};
struct {
unsigned:16;
unsigned regimm_lo:3;
unsigned regimm_hi:2;
unsigned:11;
};
struct {
unsigned:16;
unsigned regimm:5;
unsigned:11;
};
struct {
unsigned:26;
unsigned op:6;
};
struct {
unsigned funct:6;
unsigned:10;
unsigned cop_rt:5;
unsigned cop_rs:5;
unsigned:6;
};
u32 raw;
} opcode;
u32 raw;
} instr{};
static constexpr u8 SPECIAL = 0b000000;
static constexpr u8 REGIMM = 0b000001;
static constexpr u8 J = 0b000010;
static constexpr u8 JAL = 0b000011;
static constexpr u8 BEQ = 0b000100;
static constexpr u8 BNE = 0b000101;
static constexpr u8 BLEZ = 0b000110;
static constexpr u8 BGTZ = 0b000111;
static constexpr u8 ADDI = 0b001000;
static constexpr u8 ADDIU = 0b001001;
static constexpr u8 SLTI = 0b001010;
static constexpr u8 SLTIU = 0b001011;
static constexpr u8 ANDI = 0b001100;
static constexpr u8 ORI = 0b001101;
static constexpr u8 XORI = 0b001110;
static constexpr u8 LUI = 0b001111;
static constexpr u8 COP0 = 0b010000;
static constexpr u8 COP1 = 0b010001;
static constexpr u8 COP2 = 0b010010;
static constexpr u8 BEQL = 0b010100;
static constexpr u8 BNEL = 0b010101;
static constexpr u8 BLEZL = 0b010110;
static constexpr u8 BGTZL = 0b010111;
static constexpr u8 DADDI = 0b011000;
static constexpr u8 DADDIU = 0b011001;
static constexpr u8 LDL = 0b011010;
static constexpr u8 LDR = 0b011011;
static constexpr u8 LB = 0b100000;
static constexpr u8 LH = 0b100001;
static constexpr u8 LWL = 0b100010;
static constexpr u8 LW = 0b100011;
static constexpr u8 LBU = 0b100100;
static constexpr u8 LHU = 0b100101;
static constexpr u8 LWR = 0b100110;
static constexpr u8 LWU = 0b100111;
static constexpr u8 SB = 0b101000;
static constexpr u8 SH = 0b101001;
static constexpr u8 SWL = 0b101010;
static constexpr u8 SW = 0b101011;
static constexpr u8 SDL = 0b101100;
static constexpr u8 SDR = 0b101101;
static constexpr u8 SWR = 0b101110;
static constexpr u8 CACHE = 0b101111;
static constexpr u8 LL = 0b110000;
static constexpr u8 LWC1 = 0b110001;
static constexpr u8 LWC2 = 0b110010;
static constexpr u8 LLD = 0b110100;
static constexpr u8 LDC1 = 0b110101;
static constexpr u8 LDC2 = 0b110110;
static constexpr u8 LD = 0b110111;
static constexpr u8 SC = 0b111000;
static constexpr u8 SWC1 = 0b111001;
static constexpr u8 SWC2 = 0b111010;
static constexpr u8 SCD = 0b111100;
static constexpr u8 SDC1 = 0b111101;
static constexpr u8 SDC2 = 0b111110;
static constexpr u8 SD = 0b111111;
// special
static constexpr u8 SLL = 0b000000;
static constexpr u8 SRL = 0b000010;
static constexpr u8 SRA = 0b000011;
static constexpr u8 SLLV = 0b000100;
static constexpr u8 SRLV = 0b000110;
static constexpr u8 SRAV = 0b000111;
static constexpr u8 JR = 0b001000;
static constexpr u8 JALR = 0b001001;
static constexpr u8 SYSCALL = 0b001100;
static constexpr u8 BREAK = 0b001101;
static constexpr u8 SYNC = 0b001111;
static constexpr u8 MFHI = 0b010000;
static constexpr u8 MTHI = 0b010001;
static constexpr u8 MFLO = 0b010010;
static constexpr u8 MTLO = 0b010011;
static constexpr u8 DSLLV = 0b010100;
static constexpr u8 DSRLV = 0b010110;
static constexpr u8 DSRAV = 0b010111;
static constexpr u8 MULT = 0b011000;
static constexpr u8 MULTU = 0b011001;
static constexpr u8 DIV = 0b011010;
static constexpr u8 DIVU = 0b011011;
static constexpr u8 DMULT = 0b011100;
static constexpr u8 DMULTU = 0b011101;
static constexpr u8 DDIV = 0b011110;
static constexpr u8 DDIVU = 0b011111;
static constexpr u8 ADD = 0b100000;
static constexpr u8 ADDU = 0b100001;
static constexpr u8 SUB = 0b100010;
static constexpr u8 SUBU = 0b100011;
static constexpr u8 AND = 0b100100;
static constexpr u8 OR = 0b100101;
static constexpr u8 XOR = 0b100110;
static constexpr u8 NOR = 0b100111;
static constexpr u8 SLT = 0b101010;
static constexpr u8 SLTU = 0b101011;
static constexpr u8 DADD = 0b101100;
static constexpr u8 DADDU = 0b101101;
static constexpr u8 DSUB = 0b101110;
static constexpr u8 DSUBU = 0b101111;
static constexpr u8 TGE = 0b110000;
static constexpr u8 TGEU = 0b110001;
static constexpr u8 TLT = 0b110010;
static constexpr u8 TLTU = 0b110011;
static constexpr u8 TEQ = 0b110100;
static constexpr u8 TNE = 0b110110;
static constexpr u8 DSLL = 0b111000;
static constexpr u8 DSRL = 0b111010;
static constexpr u8 DSRA = 0b111011;
static constexpr u8 DSLL32 = 0b111100;
static constexpr u8 DSRL32 = 0b111110;
static constexpr u8 DSRA32 = 0b111111;
// regimm
static constexpr u8 BLTZ = 0b00000;
static constexpr u8 BGEZ = 0b00001;
static constexpr u8 BLTZL = 0b00010;
static constexpr u8 BGEZL = 0b00011;
static constexpr u8 TGEI = 0b01000;
static constexpr u8 TGEIU = 0b01001;
static constexpr u8 TLTI = 0b01010;
static constexpr u8 TLTIU = 0b01011;
static constexpr u8 TEQI = 0b01100;
static constexpr u8 TNEI = 0b01110;
static constexpr u8 BLTZAL = 0b10000;
static constexpr u8 BGEZAL = 0b10001;
static constexpr u8 BLTZALL = 0b10010;
static constexpr u8 BGEZALL = 0b10011;
};
}

Some files were not shown because too many files have changed in this diff Show More