Fuck git
This commit is contained in:
@@ -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)
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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"},
|
||||
};
|
||||
}
|
||||
@@ -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))
|
||||
@@ -0,0 +1,6 @@
|
||||
#include <Netplay.hpp>
|
||||
#include <PIF.hpp>
|
||||
#include <array>
|
||||
#include <log.hpp>
|
||||
|
||||
namespace Netplay {}
|
||||
@@ -0,0 +1,4 @@
|
||||
#pragma once
|
||||
#include <PIF.hpp>
|
||||
|
||||
namespace Netplay {}
|
||||
@@ -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
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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{};
|
||||
};
|
||||
@@ -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
|
||||
@@ -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()
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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{};
|
||||
};
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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>(®s.oldPC) - reinterpret_cast<uintptr_t>(this))
|
||||
#define PC_OFFSET (reinterpret_cast<uintptr_t>(®s.pc) - reinterpret_cast<uintptr_t>(this))
|
||||
#define NEXT_PC_OFFSET (reinterpret_cast<uintptr_t>(®s.nextPC) - reinterpret_cast<uintptr_t>(this))
|
||||
#define GPR_OFFSET(x) (reinterpret_cast<uintptr_t>(®s.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>(®s.hi) - reinterpret_cast<uintptr_t>(this))
|
||||
#define LO_OFFSET (reinterpret_cast<uintptr_t>(®s.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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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};
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
@@ -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
|
||||
@@ -0,0 +1,4 @@
|
||||
file(GLOB_RECURSE SOURCES *.cpp)
|
||||
file(GLOB_RECURSE HEADERS *.hpp)
|
||||
|
||||
add_library(jit ${SOURCES} ${HEADERS})
|
||||
@@ -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, ®s.cop0);
|
||||
break;
|
||||
case 0x01:
|
||||
code.mov(code.ARG2, instr);
|
||||
emitMemberFunctionCall(&Cop0::dmfc0, ®s.cop0);
|
||||
break;
|
||||
case 0x04:
|
||||
code.mov(code.ARG2, instr);
|
||||
emitMemberFunctionCall(&Cop0::mtc0, ®s.cop0);
|
||||
break;
|
||||
case 0x05:
|
||||
code.mov(code.ARG2, instr);
|
||||
emitMemberFunctionCall(&Cop0::dmtc0, ®s.cop0);
|
||||
break;
|
||||
case 0x10 ... 0x1F:
|
||||
switch (instr.cop_funct()) {
|
||||
case 0x01:
|
||||
emitMemberFunctionCall(&Cop0::tlbr, ®s.cop0);
|
||||
break;
|
||||
case 0x02:
|
||||
code.mov(code.ARG2, COP0_REG_INDEX);
|
||||
emitMemberFunctionCall(&Cop0::GetReg32, ®s.cop0);
|
||||
code.mov(code.ARG2, code.rax);
|
||||
code.and_(code.ARG2, 0x3F);
|
||||
emitMemberFunctionCall(&Cop0::tlbw, ®s.cop0);
|
||||
break;
|
||||
case 0x06:
|
||||
emitMemberFunctionCall(&Cop0::GetRandom, ®s.cop0);
|
||||
code.mov(code.ARG2, code.rax);
|
||||
emitMemberFunctionCall(&Cop0::tlbw, ®s.cop0);
|
||||
break;
|
||||
case 0x08:
|
||||
emitMemberFunctionCall(&Cop0::tlbp, ®s.cop0);
|
||||
break;
|
||||
case 0x18:
|
||||
emitMemberFunctionCall(&Cop0::eret, ®s.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
|
||||
@@ -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
@@ -0,0 +1,4 @@
|
||||
file(GLOB SOURCES *.cpp)
|
||||
file(GLOB HEADERS *.hpp)
|
||||
|
||||
add_library(mem ${SOURCES} ${HEADERS})
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -0,0 +1 @@
|
||||
add_library(rsp decode.cpp instructions.cpp)
|
||||
@@ -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
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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();
|
||||
};
|
||||
@@ -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 = {};
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
struct SettingsTab {
|
||||
virtual ~SettingsTab() = default;
|
||||
virtual void render() = 0;
|
||||
|
||||
bool modified = false;
|
||||
};
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
@@ -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 = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -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;
|
||||
};
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
#pragma once
|
||||
#include <SettingsTab.hpp>
|
||||
|
||||
struct CPUSettings final : SettingsTab {
|
||||
int selectedCpuTypeIndex = 0;
|
||||
void render() override;
|
||||
explicit CPUSettings();
|
||||
};
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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; }
|
||||
};
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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 = {};
|
||||
};
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
Reference in New Issue
Block a user