Sorta works

This commit is contained in:
irisz64
2025-08-03 20:06:16 +02:00
parent 010bb5e0bb
commit 3fb03c6aba
20 changed files with 563 additions and 406 deletions

View File

@@ -130,6 +130,7 @@ add_executable(kaizen
src/frontend/AudioSettings.cpp src/frontend/AudioSettings.cpp
src/frontend/NativeWindow.hpp src/frontend/NativeWindow.hpp
src/utils/Options.cpp src/utils/Options.cpp
src/utils/File.cpp
src/frontend/Debugger.hpp src/frontend/Debugger.hpp
src/frontend/Debugger.cpp) src/frontend/Debugger.cpp)

View File

@@ -29,10 +29,11 @@ void Core::Reset() {
regs.Reset(); regs.Reset();
mem->Reset(); mem->Reset();
cpu->Reset(); cpu->Reset();
mem->mmio.si.pif.Execute(); if(romLoaded)
mem->mmio.si.pif.Execute();
} }
bool Core::LoadTAS(const fs::path &path) const { return mem->mmio.si.pif.movie.Load(path); } void Core::LoadTAS(const fs::path &path) const { mem->mmio.si.pif.movie.Load(path); }
void Core::LoadROM(const std::string &rom_) { void Core::LoadROM(const std::string &rom_) {
Stop(); Stop();
@@ -75,6 +76,9 @@ void Core::Run(float volumeL, float volumeR) {
u32 taken = cpu->Step(); u32 taken = cpu->Step();
taken += regs.PopStalledCycles(); taken += regs.PopStalledCycles();
if(!Util::Error::IsHandled())
return;
regs.steps += taken; regs.steps += taken;
if (mmio.rsp.spStatus.halt) { if (mmio.rsp.spStatus.halt) {
regs.steps = 0; regs.steps = 0;

View File

@@ -31,7 +31,7 @@ struct Core {
void Stop(); void Stop();
void Reset(); void Reset();
void LoadROM(const std::string &); void LoadROM(const std::string &);
[[nodiscard]] bool LoadTAS(const fs::path &) const; void LoadTAS(const fs::path &) const;
void Run(float volumeL, float volumeR); void Run(float volumeL, float volumeR);
void TogglePause() { pause = !pause; } void TogglePause() { pause = !pause; }
inline void ToggleBreakpoint(s64 addr) { inline void ToggleBreakpoint(s64 addr) {

View File

@@ -13,7 +13,8 @@ FORCE_INLINE void SwapN64Rom(std::vector<u8> &rom, u32 endianness) {
if (endianness >> 24 != 0x80) { if (endianness >> 24 != 0x80) {
if ((endianness & 0xFF) != 0x80) { if ((endianness & 0xFF) != 0x80) {
if ((endianness >> 16 & 0xff) != 0x80) { if ((endianness >> 16 & 0xff) != 0x80) {
panic("TODO: Unrecognized rom endianness. Ideally, this should be more robust"); Error::GetInstance().Throw({Error::Severity::UNRECOVERABLE}, {Error::Type::ROM_LOAD_ERROR}, {}, {}, "Unrecognized rom endianness");
return;
} else { } else {
altByteShift = 12; altByteShift = 12;
} }
@@ -41,7 +42,7 @@ FORCE_INLINE void SwapN64Rom(std::vector<u8> &rom, u32 endianness) {
SwapBuffer<u32>(rom); SwapBuffer<u32>(rom);
break; break;
default: default:
panic("Unrecognized rom format! Make sure this is a valid Nintendo 64 ROM dump!"); 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 } // namespace Util

View File

@@ -40,9 +40,11 @@ void Scheduler::Tick(const u64 t) {
case NONE: case NONE:
break; break;
case IMPOSSIBLE: case IMPOSSIBLE:
panic("Congratulations on keeping the emulator on for about 5 billion years, I guess, nerd."); Util::Error::GetInstance().Throw({Util::Error::Severity::UNRECOVERABLE}, {Util::Error::Type::ROM_LOAD_ERROR}, {}, {}, "Unrecognized rom endianness");
return;
default: default:
panic("Unknown scheduler event type {}", static_cast<int>(type)); 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(); events.pop();
} }

View File

@@ -2,119 +2,6 @@
#include <Core.hpp> #include <Core.hpp>
#include <optional> #include <optional>
template <>
std::optional<u64> Disassembler::CapstoneToRegValue(mips_reg reg) const {
n64::Registers& regs = n64::Core::GetRegs();
u64 val = 0;
switch(reg) {
case MIPS_REG_ZERO: case MIPS_REG_ZERO_64: case MIPS_REG_ZERO_NM:
val = 0;
break;
case MIPS_REG_AT: case MIPS_REG_AT_64: case MIPS_REG_AT_NM:
val = regs.Read<u64>(1);
break;
case MIPS_REG_V0: case MIPS_REG_V0_64:
val = regs.Read<u64>(2);
break;
case MIPS_REG_V1: case MIPS_REG_V1_64:
val = regs.Read<u64>(3);
break;
case MIPS_REG_A0: case MIPS_REG_A0_64:
val = regs.Read<u64>(4);
break;
case MIPS_REG_A1: case MIPS_REG_A1_64:
val = regs.Read<u64>(5);
break;
case MIPS_REG_A2: case MIPS_REG_A2_64:
val = regs.Read<u64>(6);
break;
case MIPS_REG_A3: case MIPS_REG_A3_64:
val = regs.Read<u64>(7);
break;
case MIPS_REG_T0: case MIPS_REG_T0_64:
val = regs.Read<u64>(8);
break;
case MIPS_REG_T1: case MIPS_REG_T1_64:
val = regs.Read<u64>(9);
break;
case MIPS_REG_T2: case MIPS_REG_T2_64:
val = regs.Read<u64>(10);
break;
case MIPS_REG_T3: case MIPS_REG_T3_64:
val = regs.Read<u64>(11);
break;
case MIPS_REG_T4: case MIPS_REG_T4_64:
val = regs.Read<u64>(12);
break;
case MIPS_REG_T5: case MIPS_REG_T5_64:
val = regs.Read<u64>(13);
break;
case MIPS_REG_T6: case MIPS_REG_T6_64:
val = regs.Read<u64>(14);
break;
case MIPS_REG_T7: case MIPS_REG_T7_64:
val = regs.Read<u64>(15);
break;
case MIPS_REG_S0: case MIPS_REG_S0_64:
val = regs.Read<u64>(16);
break;
case MIPS_REG_S1: case MIPS_REG_S1_64:
val = regs.Read<u64>(17);
break;
case MIPS_REG_S2: case MIPS_REG_S2_64:
val = regs.Read<u64>(18);
break;
case MIPS_REG_S3: case MIPS_REG_S3_64:
val = regs.Read<u64>(19);
break;
case MIPS_REG_S4: case MIPS_REG_S4_64:
val = regs.Read<u64>(20);
break;
case MIPS_REG_S5: case MIPS_REG_S5_64:
val = regs.Read<u64>(21);
break;
case MIPS_REG_S6: case MIPS_REG_S6_64:
val = regs.Read<u64>(22);
break;
case MIPS_REG_S7: case MIPS_REG_S7_64:
val = regs.Read<u64>(23);
break;
case MIPS_REG_T8: case MIPS_REG_T8_64:
val = regs.Read<u64>(24);
break;
case MIPS_REG_T9: case MIPS_REG_T9_64:
val = regs.Read<u64>(25);
break;
case MIPS_REG_K0: case MIPS_REG_K0_64:
val = regs.Read<u64>(26);
break;
case MIPS_REG_K1: case MIPS_REG_K1_64:
val = regs.Read<u64>(27);
break;
case MIPS_REG_GP: case MIPS_REG_GP_64:
val = regs.Read<u64>(28);
break;
case MIPS_REG_SP: case MIPS_REG_SP_64:
val = regs.Read<u64>(29);
break;
case MIPS_REG_RA: case MIPS_REG_RA_64:
val = regs.Read<u64>(31);
break;
default:
return {};
}
return val;
}
template <>
std::string Disassembler::CapstoneToRegValue(mips_reg reg) const {
n64::Registers& regs = n64::Core::GetRegs();
auto val = CapstoneToRegValue<std::optional<u64>>(reg);
return std::format("{}", val.has_value() ? std::format("{} (= 0x{:016X})", cs_reg_name(handle, reg), val.value()) : "! Unknown !");
}
Disassembler::DisassemblyResult Disassembler::DisassembleSimple(const u32 address, const u32 instruction) const { Disassembler::DisassemblyResult Disassembler::DisassembleSimple(const u32 address, const u32 instruction) const {
cs_insn *insn; cs_insn *insn;
const auto bytes = Util::IntegralToBuffer(std::byteswap(instruction)); const auto bytes = Util::IntegralToBuffer(std::byteswap(instruction));
@@ -179,27 +66,8 @@ Disassembler::DisassemblyResult Disassembler::DisassembleDetailed(const u32 addr
} }
}; };
auto formatComment = [&](const cs_mips_op &operand) -> CommentPart {
switch (operand.type) {
case MIPS_OP_IMM:
return {std::format("#{:X}, ", operand.is_unsigned ? operand.uimm : operand.imm)};
case MIPS_OP_MEM: {
auto base = CapstoneToRegValue<std::optional<u64>>(operand.mem.base);
return {
std::format("{}(0x{:X}), ", CapstoneToRegValue<std::string>(operand.mem.base), operand.mem.disp),
base.has_value() ? base.value() + (s16)operand.mem.disp : 0xFFFF'FFFF'FFFF'FFFFull,
};
}
case MIPS_OP_REG:
return {std::format("{}, ", CapstoneToRegValue<std::string>(operand.reg))};
default:
return {"! Unknown !"};
}
};
for (u8 i = 0; i < details->mips.op_count && i < 3; i++) { for (u8 i = 0; i < details->mips.op_count && i < 3; i++) {
result.ops[i] = formatOperand(details->mips.operands[i]); result.ops[i] = formatOperand(details->mips.operands[i]);
result.comment[i] = formatComment(details->mips.operands[i]);
result.full += result.ops[i].str + "\t"; result.full += result.ops[i].str + "\t";
} }

View File

@@ -5,11 +5,6 @@
#include <array> #include <array>
struct Disassembler { struct Disassembler {
struct CommentPart {
std::string str;
u64 resolvedAddr = 0xFFFF'FFFF'FFFF'FFFFull;
};
struct DisassemblyResult { struct DisassemblyResult {
bool success = false; bool success = false;
std::string full; std::string full;
@@ -20,23 +15,6 @@ struct Disassembler {
std::string str; std::string str;
}; };
std::array<Operand, 3> ops{}; std::array<Operand, 3> ops{};
std::array<CommentPart, 3> comment{};
std::string GetFormattedComment() {
std::string res{};
for(auto& [str, _] : comment) {
res += str;
}
return res;
}
u64 GetResolvedAddressFromComment() {
auto it = std::ranges::find_if(comment, [](auto& part) {
return part.resolvedAddr != 0xFFFF'FFFF'FFFF'FFFFull;
});
if(it == comment.end()) return 0xFFFF'FFFF'FFFF'FFFFull;
return it->resolvedAddr;
}
}; };
~Disassembler() { cs_close(&handle); } ~Disassembler() { cs_close(&handle); }
@@ -50,8 +28,6 @@ struct Disassembler {
[[nodiscard]] DisassemblyResult DisassembleDetailed(u32 address, u32 instruction) const; [[nodiscard]] DisassemblyResult DisassembleDetailed(u32 address, u32 instruction) const;
[[nodiscard]] DisassemblyResult DisassembleSimple(u32 address, u32 instruction) const; [[nodiscard]] DisassemblyResult DisassembleSimple(u32 address, u32 instruction) const;
private: private:
template <typename T>
T CapstoneToRegValue(mips_reg reg) const;
explicit Disassembler(const bool rsp) : rsp(rsp) { 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) != 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) { CS_ERR_OK) {
@@ -59,7 +35,7 @@ private:
} }
if (cs_option(handle, CS_OPT_DETAIL, CS_OPT_ON) != CS_ERR_OK) { if (cs_option(handle, CS_OPT_DETAIL, CS_OPT_ON) != CS_ERR_OK) {
warn("Could not enable disassembler's details!"); Util::Error::GetInstance().Throw({Util::Error::Severity::WARN}, {Util::Error::Type::CAPSTONE_ERROR}, {}, {}, "Could not enable disassembler's details!");
details = false; details = false;
} }
} }

View File

@@ -46,17 +46,21 @@ u32 JIT::FetchInstruction() {
/*regs.cop0.HandleTLBException(blockPC); /*regs.cop0.HandleTLBException(blockPC);
regs.cop0.FireException(ExceptionCode::AddressErrorLoad, 0, blockPC); regs.cop0.FireException(ExceptionCode::AddressErrorLoad, 0, blockPC);
return 1;*/ return 1;*/
Util::Error::GetInstance().Throw(
panic("[JIT]: Unhandled exception ADL due to unaligned PC virtual value! (0x{:016X})", blockPC); {Util::Error::Severity::NON_FATAL}, {Util::Error::Type::UNHANDLED_EXCEPTION}, blockPC, {},
"[JIT]: Unhandled exception ADL due to unaligned PC virtual value!");
return 0;
} }
if (!regs.cop0.MapVAddr(Cop0::LOAD, blockPC, paddr)) { if (!regs.cop0.MapVAddr(Cop0::LOAD, blockPC, paddr)) {
/*regs.cop0.HandleTLBException(blockPC); /*regs.cop0.HandleTLBException(blockPC);
regs.cop0.FireException(Cop0::GetTLBExceptionCode(regs.cop0.tlbError, Cop0::LOAD), 0, blockPC); regs.cop0.FireException(Cop0::GetTLBExceptionCode(regs.cop0.tlbError, Cop0::LOAD), 0, blockPC);
return 1;*/ return 1;*/
panic( Util::Error::GetInstance().Throw(
"[JIT]: Unhandled exception TLB exception {} when retrieving PC physical address! (virtual: 0x{:016X})", {Util::Error::Severity::NON_FATAL}, {Util::Error::Type::UNHANDLED_EXCEPTION}, blockPC, {},
static_cast<int>(Cop0::GetTLBExceptionCode(regs.cop0.tlbError, Cop0::LOAD)), static_cast<u64>(blockPC)); "[JIT]: Unhandled exception TLB exception {} when retrieving PC physical address!",
static_cast<int>(Cop0::GetTLBExceptionCode(regs.cop0.tlbError, Cop0::LOAD)));
return 0;
} }
return Core::GetMem().Read<u32>(paddr); return Core::GetMem().Read<u32>(paddr);
@@ -107,8 +111,11 @@ int JIT::Step() {
/*regs.cop0.HandleTLBException(blockPC); /*regs.cop0.HandleTLBException(blockPC);
regs.cop0.FireException(Cop0::GetTLBExceptionCode(regs.cop0.tlbError, Cop0::LOAD), 0, blockPC); regs.cop0.FireException(Cop0::GetTLBExceptionCode(regs.cop0.tlbError, Cop0::LOAD), 0, blockPC);
return 1;*/ return 1;*/
panic("[JIT]: Unhandled exception TLB exception {} when retrieving PC physical address! (virtual: 0x{:016X})", Util::Error::GetInstance().Throw(
static_cast<int>(Cop0::GetTLBExceptionCode(regs.cop0.tlbError, Cop0::LOAD)), static_cast<u64>(blockPC)); {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;
} }
u32 upperIndex = paddr >> kUpperShift; u32 upperIndex = paddr >> kUpperShift;
@@ -149,17 +156,21 @@ int JIT::Step() {
/*regs.cop0.HandleTLBException(blockPC); /*regs.cop0.HandleTLBException(blockPC);
regs.cop0.FireException(ExceptionCode::AddressErrorLoad, 0, blockPC); regs.cop0.FireException(ExceptionCode::AddressErrorLoad, 0, blockPC);
return 1;*/ return 1;*/
Util::Error::GetInstance().Throw(
panic("[JIT]: Unhandled exception ADL due to unaligned PC virtual value! (0x{:016X})", blockPC); {Util::Error::Severity::NON_FATAL}, {Util::Error::Type::UNHANDLED_EXCEPTION}, blockPC, {},
"[JIT]: Unhandled exception ADL due to unaligned PC virtual value!");
return 0;
} }
if (!regs.cop0.MapVAddr(Cop0::LOAD, blockPC, paddr)) { if (!regs.cop0.MapVAddr(Cop0::LOAD, blockPC, paddr)) {
/*regs.cop0.HandleTLBException(blockPC); /*regs.cop0.HandleTLBException(blockPC);
regs.cop0.FireException(Cop0::GetTLBExceptionCode(regs.cop0.tlbError, Cop0::LOAD), 0, blockPC); regs.cop0.FireException(Cop0::GetTLBExceptionCode(regs.cop0.tlbError, Cop0::LOAD), 0, blockPC);
return 1;*/ return 1;*/
panic( Util::Error::GetInstance().Throw(
"[JIT]: Unhandled exception TLB exception {} when retrieving PC physical address! (virtual: 0x{:016X})", {Util::Error::Severity::NON_FATAL}, {Util::Error::Type::UNHANDLED_EXCEPTION}, blockPC, {},
static_cast<int>(Cop0::GetTLBExceptionCode(regs.cop0.tlbError, Cop0::LOAD)), static_cast<u64>(blockPC)); "[JIT]: Unhandled exception TLB exception {} when retrieving PC physical address!",
static_cast<int>(Cop0::GetTLBExceptionCode(regs.cop0.tlbError, Cop0::LOAD)));
return 0;
} }
instruction = Core::GetMem().Read<u32>(paddr); instruction = Core::GetMem().Read<u32>(paddr);
@@ -185,8 +196,12 @@ int JIT::Step() {
instructionsInBlock++; instructionsInBlock++;
const u32 delay_instruction = FetchInstruction(); const u32 delay_instruction = FetchInstruction();
instrEndsBlock = InstrEndsBlock(delay_instruction); instrEndsBlock = InstrEndsBlock(delay_instruction);
if(instrEndsBlock) if(instrEndsBlock) {
panic("Branch in delay slot - YOU SHOULD KILL YOURSELF, NOW!!!"); 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;
}
blockOldPC = blockPC; blockOldPC = blockPC;
blockPC = blockNextPC; blockPC = blockNextPC;

View File

@@ -58,8 +58,9 @@ private:
return code.qword[code.rbp + (reinterpret_cast<uintptr_t>(&regs.gpr[index]) - reinterpret_cast<uintptr_t>(this))]; return code.qword[code.rbp + (reinterpret_cast<uintptr_t>(&regs.gpr[index]) - reinterpret_cast<uintptr_t>(this))];
} }
panic("[JIT]: Invalid register addressing"); Util::Error::GetInstance().Throw(
// never actually hit, but just to silence the warning "not all control paths return a value" {Util::Error::Severity::UNRECOVERABLE}, {Util::Error::Type::JIT_INVALID_X86_REG_ADDRESSING},
blockPC, {}, "[JIT]: Invalid register addressing mode {}!", sizeof(T));
return Xbyak::Address{0}; return Xbyak::Address{0};
} }
@@ -188,17 +189,57 @@ private:
void mthi(u32); void mthi(u32);
void mtlo(u32); void mtlo(u32);
void nor(u32); void nor(u32);
void sb(u32) { panic("[JIT] Implement sb!"); } void sb(u32) {
void sc(u32) { panic("[JIT] Implement sc!"); } Util::Error::GetInstance().Throw(
void scd(u32) { panic("[JIT] Implement scd!"); } {Util::Error::Severity::NON_FATAL}, {Util::Error::Type::UNHANDLED_INSTRUCTION},
void sd(u32) { panic("[JIT] Implement sd!"); } blockPC, {}, "[JIT]: Unhandled 'sb'!");
void sdc1(u32) { panic("[JIT] Implement sdc1!"); } }
void sdl(u32) { panic("[JIT] Implement sdl!"); } void sc(u32) {
void sdr(u32) { panic("[JIT] Implement sdr!"); } Util::Error::GetInstance().Throw(
void sh(u32) { panic("[JIT] Implement sh!"); } {Util::Error::Severity::NON_FATAL}, {Util::Error::Type::UNHANDLED_INSTRUCTION},
blockPC, {}, "[JIT]: Unhandled 'sc'!");
}
void scd(u32) {
Util::Error::GetInstance().Throw(
{Util::Error::Severity::NON_FATAL}, {Util::Error::Type::UNHANDLED_INSTRUCTION},
blockPC, {}, "[JIT]: Unhandled 'scd'!");
}
void sd(u32) {
Util::Error::GetInstance().Throw(
{Util::Error::Severity::NON_FATAL}, {Util::Error::Type::UNHANDLED_INSTRUCTION},
blockPC, {}, "[JIT]: Unhandled 'sd'!");
}
void sdc1(u32) {
Util::Error::GetInstance().Throw(
{Util::Error::Severity::NON_FATAL}, {Util::Error::Type::UNHANDLED_INSTRUCTION},
blockPC, {}, "[JIT]: Unhandled 'sdc1'!");
}
void sdl(u32) {
Util::Error::GetInstance().Throw(
{Util::Error::Severity::NON_FATAL}, {Util::Error::Type::UNHANDLED_INSTRUCTION},
blockPC, {}, "[JIT]: Unhandled 'sdl'!");
}
void sdr(u32) {
Util::Error::GetInstance().Throw(
{Util::Error::Severity::NON_FATAL}, {Util::Error::Type::UNHANDLED_INSTRUCTION},
blockPC, {}, "[JIT]: Unhandled 'sdr'!");
}
void sh(u32) {
Util::Error::GetInstance().Throw(
{Util::Error::Severity::NON_FATAL}, {Util::Error::Type::UNHANDLED_INSTRUCTION},
blockPC, {}, "[JIT]: Unhandled 'sh'!");
}
void sw(u32); void sw(u32);
void swl(u32) { panic("[JIT] Implement swl!"); } void swl(u32) {
void swr(u32) { panic("[JIT] Implement swr!"); } Util::Error::GetInstance().Throw(
{Util::Error::Severity::NON_FATAL}, {Util::Error::Type::UNHANDLED_INSTRUCTION},
blockPC, {}, "[JIT]: Unhandled 'swl'!");
}
void swr(u32) {
Util::Error::GetInstance().Throw(
{Util::Error::Severity::NON_FATAL}, {Util::Error::Type::UNHANDLED_INSTRUCTION},
blockPC, {}, "[JIT]: Unhandled 'swr'!");
}
void slti(u32); void slti(u32);
void sltiu(u32); void sltiu(u32);
void slt(u32); void slt(u32);
@@ -207,12 +248,20 @@ private:
void sllv(u32); void sllv(u32);
void sub(u32); void sub(u32);
void subu(u32); void subu(u32);
void swc1(u32) { panic("[JIT] Implement swc1!"); } void swc1(u32) {
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(u32); void sra(u32);
void srav(u32); void srav(u32);
void srl(u32); void srl(u32);
void srlv(u32); void srlv(u32);
void trap(bool) { panic("[JIT] Implement trap!"); } 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_(u32); void or_(u32);
void ori(u32); void ori(u32);
void xor_(u32); void xor_(u32);

View File

@@ -18,7 +18,10 @@ void Mem::Reset() {
std::error_code error; std::error_code error;
saveData.sync(error); saveData.sync(error);
if (error) { if (error) {
panic("Could not sync save data"); 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(); saveData.unmap();
} }
@@ -36,7 +39,10 @@ void Mem::LoadSRAM(SaveType save_type, fs::path path) {
if (saveData.is_mapped()) { if (saveData.is_mapped()) {
saveData.sync(error); saveData.sync(error);
if (error) { if (error) {
panic("Could not sync {}", sramPath); 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(); saveData.unmap();
} }
@@ -48,12 +54,17 @@ void Mem::LoadSRAM(SaveType save_type, fs::path path) {
} }
if (sramVec.size() != SRAM_SIZE) { if (sramVec.size() != SRAM_SIZE) {
panic("Corrupt SRAM!"); 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); saveData = mio::make_mmap_sink(sramPath, 0, mio::map_entire_file, error);
if (error) { if (error) {
panic("Could not mmap {}", sramPath); 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);
} }
} }
} }
@@ -150,7 +161,12 @@ u8 Mem::Read(const u32 paddr) {
} }
if(Util::IsInsideRange(paddr, MMIO_REGION_START_1, MMIO_REGION_END_1) || 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 Read<u8>!"); 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_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, PIF_RAM_REGION_START, PIF_RAM_REGION_END)) return si.pif.ram[paddr - PIF_RAM_REGION_START];
@@ -159,7 +175,9 @@ u8 Mem::Read(const u32 paddr) {
Util::IsInsideRange(paddr, UNUSED_START_3, UNUSED_END_3) || Util::IsInsideRange(paddr, UNUSED_START_3, UNUSED_END_3) ||
Util::IsInsideRange(paddr, UNUSED_START_4, UNUSED_END_4)) return 0; Util::IsInsideRange(paddr, UNUSED_START_4, UNUSED_END_4)) return 0;
panic("Unimplemented 8-bit read at address {:08X} (PC = {:016X})", paddr, (u64)regs.pc); 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; return 0;
} }
@@ -184,7 +202,9 @@ u16 Mem::Read(const u32 paddr) {
Util::IsInsideRange(paddr, UNUSED_START_3, UNUSED_END_3) || Util::IsInsideRange(paddr, UNUSED_START_3, UNUSED_END_3) ||
Util::IsInsideRange(paddr, UNUSED_START_4, UNUSED_END_4)) return 0; Util::IsInsideRange(paddr, UNUSED_START_4, UNUSED_END_4)) return 0;
panic("Unimplemented 16-bit read at address {:08X} (PC = {:016X})", paddr, (u64)regs.pc); 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; return 0;
} }
@@ -210,7 +230,9 @@ u32 Mem::Read(const u32 paddr) {
Util::IsInsideRange(paddr, UNUSED_START_3, UNUSED_END_3) || Util::IsInsideRange(paddr, UNUSED_START_3, UNUSED_END_3) ||
Util::IsInsideRange(paddr, UNUSED_START_4, UNUSED_END_4)) return 0; Util::IsInsideRange(paddr, UNUSED_START_4, UNUSED_END_4)) return 0;
panic("Unimplemented 32-bit read at address {:08X} (PC = {:016X})", paddr, (u64)regs.pc); 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; return 0;
} }
@@ -236,7 +258,9 @@ u64 Mem::Read(const u32 paddr) {
Util::IsInsideRange(paddr, UNUSED_START_3, UNUSED_END_3) || Util::IsInsideRange(paddr, UNUSED_START_3, UNUSED_END_3) ||
Util::IsInsideRange(paddr, UNUSED_START_4, UNUSED_END_4)) return 0; Util::IsInsideRange(paddr, UNUSED_START_4, UNUSED_END_4)) return 0;
panic("Unimplemented 64-bit read at address {:08X} (PC = {:016X})", paddr, (u64)regs.pc); 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; return 0;
} }

View File

@@ -29,12 +29,12 @@ union TASMovieControllerData {
static_assert(sizeof(TASMovieControllerData) == 4); static_assert(sizeof(TASMovieControllerData) == 4);
bool MupenMovie::Load(const fs::path &path) { void MupenMovie::Load(const fs::path &path) {
filename = path.stem().string(); filename = path.stem().string();
loadedTasMovie = Util::ReadFileBinary(path.string()); loadedTasMovie = Util::ReadFileBinary(path.string());
if (!IsLoaded()) { if (!IsLoaded()) {
error("Error loading movie!"); error("Error loading movie!");
return false; return;
} }
std::memcpy(&loadedTasMovieHeader, loadedTasMovie.data(), sizeof(TASMovieHeader)); std::memcpy(&loadedTasMovieHeader, loadedTasMovie.data(), sizeof(TASMovieHeader));
@@ -42,18 +42,18 @@ bool MupenMovie::Load(const fs::path &path) {
if (loadedTasMovieHeader.signature[0] != 0x4D || loadedTasMovieHeader.signature[1] != 0x36 || if (loadedTasMovieHeader.signature[0] != 0x4D || loadedTasMovieHeader.signature[1] != 0x36 ||
loadedTasMovieHeader.signature[2] != 0x34 || loadedTasMovieHeader.signature[3] != 0x1A) { loadedTasMovieHeader.signature[2] != 0x34 || loadedTasMovieHeader.signature[3] != 0x1A) {
error("Failed to load movie: incorrect signature. Are you sure this is a valid movie?"); error("Failed to load movie: incorrect signature. Are you sure this is a valid movie?");
return false; return;
} }
if (loadedTasMovieHeader.version != 3) { if (loadedTasMovieHeader.version != 3) {
error("This movie is version {}: only version 3 is supported.", loadedTasMovieHeader.version); error("This movie is version {}: only version 3 is supported.", loadedTasMovieHeader.version);
return false; return;
} }
if (loadedTasMovieHeader.startType != 2) { if (loadedTasMovieHeader.startType != 2) {
error("Movie start type is {} - only movies with a start type of 2 are supported (start at power on)", error("Movie start type is {} - only movies with a start type of 2 are supported (start at power on)",
loadedTasMovieHeader.startType); loadedTasMovieHeader.startType);
return false; return;
} }
info("Loaded movie '{}' ", loadedTasMovieHeader.movie_description); info("Loaded movie '{}' ", loadedTasMovieHeader.movie_description);
@@ -62,11 +62,10 @@ bool MupenMovie::Load(const fs::path &path) {
if (loadedTasMovieHeader.numControllers != 1) { if (loadedTasMovieHeader.numControllers != 1) {
error("Currently, only movies with 1 controller connected are supported."); error("Currently, only movies with 1 controller connected are supported.");
return false; return;
} }
loadedTasMovieIndex = sizeof(TASMovieHeader) - 4; // skip header loadedTasMovieIndex = sizeof(TASMovieHeader) - 4; // skip header
return true;
} }
MupenMovie::MupenMovie(const fs::path &path) { MupenMovie::MupenMovie(const fs::path &path) {

View File

@@ -48,7 +48,7 @@ static_assert(sizeof(TASMovieHeader) == 1024);
struct MupenMovie { struct MupenMovie {
MupenMovie() = default; MupenMovie() = default;
explicit MupenMovie(const fs::path &); explicit MupenMovie(const fs::path &);
bool Load(const fs::path &); void Load(const fs::path &);
void Reset(); void Reset();
n64::Controller NextInputs(); n64::Controller NextInputs();
[[nodiscard]] bool IsLoaded() const { return !loadedTasMovie.empty(); } [[nodiscard]] bool IsLoaded() const { return !loadedTasMovie.empty(); }

View File

@@ -24,40 +24,41 @@ void VI::Reset() {
u32 VI::Read(const u32 paddr) const { u32 VI::Read(const u32 paddr) const {
switch (paddr) { switch (paddr) {
case 0x04400000: case 0x04400000:
return status.raw; return status.raw;
case 0x04400004: case 0x04400004:
return origin; return origin;
case 0x04400008: case 0x04400008:
return width; return width;
case 0x0440000C: case 0x0440000C:
return intr; return intr;
case 0x04400010: case 0x04400010:
return current << 1; return current << 1;
case 0x04400014: case 0x04400014:
return burst.raw; return burst.raw;
case 0x04400018: case 0x04400018:
return vsync; return vsync;
case 0x0440001C: case 0x0440001C:
return hsync; return hsync;
case 0x04400020: case 0x04400020:
return hsyncLeap.raw; return hsyncLeap.raw;
case 0x04400024: case 0x04400024:
return hstart.raw; return hstart.raw;
case 0x04400028: case 0x04400028:
return vstart.raw; return vstart.raw;
case 0x0440002C: case 0x0440002C:
return vburst; return vburst;
case 0x04400030: case 0x04400030:
return xscale.raw; return xscale.raw;
case 0x04400034: case 0x04400034:
return yscale.raw; return yscale.raw;
case 0x04400038: default: {
return 0; n64::Registers& regs = n64::Core::GetRegs();
case 0x0440003C: Util::Error::GetInstance().Throw(
return 0; {Util::Error::Severity::NON_FATAL}, {Util::Error::Type::MEM_UNHANDLED_ACCESS}, regs.pc,
default: Util::Error::MemoryAccess{false, Util::Error::MemoryAccess::WORD, paddr, 0}, "32-bit read access on unhandled VI register");
panic("Unimplemented VI[{:08X}] read", paddr); return 0;
}
} }
} }

View File

@@ -2,6 +2,17 @@
#include <imgui.h> #include <imgui.h>
#include <execution> #include <execution>
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, s64 startAddr, Disassembler::DisassemblyResult&) { void BreakpointFunc(s64 addr, s64 startAddr, Disassembler::DisassemblyResult&) {
n64::Core& core = n64::Core::GetInstance(); n64::Core& core = n64::Core::GetInstance();
bool isBroken = core.breakpoints.contains(addr + 4); bool isBroken = core.breakpoints.contains(addr + 4);
@@ -56,58 +67,80 @@ void InstructionFunc(s64, s64, Disassembler::DisassemblyResult& disasm) {
} }
} }
void CommentFunc(s64, s64, Disassembler::DisassemblyResult& disasm) { void Debugger::RegisterView() {
n64::Core& core = n64::Core::GetInstance(); if(!ImGui::BeginTable("Registers", 4, ImGuiTableFlags_SizingStretchSame | ImGuiTableFlags_Resizable | ImGuiTableFlags_BordersOuter | ImGuiTableFlags_BordersV | ImGuiTableFlags_ContextMenuInBody))
if(!disasm.success) {
ImGui::TextColored(ImColor(0xff71efe5), "");
return;
}
ImGui::TextColored(ImColor(0xff71efe5), "%s", std::format("{}", disasm.GetFormattedComment()).c_str());
u64 vaddr = disasm.GetResolvedAddressFromComment();
if(vaddr == 0xFFFF'FFFF'FFFF'FFFFull)
return; return;
if(!ImGui::IsItemHovered(ImGuiHoveredFlags_DelayNormal | ImGuiHoveredFlags_ForTooltip)) ImGui::TableSetupColumn("Name");
return; ImGui::TableSetupColumn("Value");
ImGui::TableSetupColumn("Name");
if(!ImGui::BeginTooltip()) ImGui::TableSetupColumn("Value");
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(); ImGui::TableHeadersRow();
for(u32 row = 0; row < 16; row++) { auto renderMemoryTable = [&](u64 vaddr) {
ImGui::TableNextRow(); if(!ImGui::IsItemHovered(ImGuiHoveredFlags_DelayNormal | ImGuiHoveredFlags_ForTooltip))
for(u32 col = 0; col < 16; col+=4) { return;
u32 paddr;
if(!n64::Core::GetRegs().cop0.MapVAddr(n64::Cop0::LOAD, vaddr+row*0x10+col, paddr))
continue;
u32 val = n64::Core::GetMem().Read<u32>(paddr);
ImGui::TableSetColumnIndex(col+0); if(!ImGui::BeginTooltip())
ImGui::Text("%02X", (val >> 24) & 0xff); return;
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::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;
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::EndTable();
ImGui::EndTooltip();
} }
bool Debugger::render() { bool Debugger::render() {
@@ -119,7 +152,6 @@ bool Debugger::render() {
static s64 startAddr = 0xFFFF'FFFF'8000'0000; static s64 startAddr = 0xFFFF'FFFF'8000'0000;
int step = 4, stepFast = 256; int step = 4, stepFast = 256;
static bool followPC = true;
if(!ImGui::Begin("Debugger", &enabled)) if(!ImGui::Begin("Debugger", &enabled))
return false; return false;
@@ -144,7 +176,7 @@ bool Debugger::render() {
core.ToggleBreakpoint(startAddr); core.ToggleBreakpoint(startAddr);
} }
if(!ImGui::BeginTable("Disassembly", columns.size(), ImGuiTableFlags_SizingStretchSame | ImGuiTableFlags_Resizable | ImGuiTableFlags_BordersOuter | ImGuiTableFlags_BordersV | ImGuiTableFlags_ContextMenuInBody)) if(!ImGui::BeginTable("Disassembly", columns.size(), ImGuiTableFlags_SizingFixedSame | ImGuiTableFlags_Resizable | ImGuiTableFlags_BordersOuter | ImGuiTableFlags_BordersV | ImGuiTableFlags_ContextMenuInBody))
return false; return false;
for(int i = 0; i < columns.size(); i++) for(int i = 0; i < columns.size(); i++)
@@ -183,6 +215,8 @@ bool Debugger::render() {
ImGui::EndTable(); ImGui::EndTable();
RegisterView();
ImGui::End(); ImGui::End();
return true; return true;

View File

@@ -4,7 +4,6 @@
void BreakpointFunc(s64, s64, Disassembler::DisassemblyResult&); void BreakpointFunc(s64, s64, Disassembler::DisassemblyResult&);
void AddressFunc(s64, s64, Disassembler::DisassemblyResult&); void AddressFunc(s64, s64, Disassembler::DisassemblyResult&);
void InstructionFunc(s64, s64, Disassembler::DisassemblyResult&); void InstructionFunc(s64, s64, Disassembler::DisassemblyResult&);
void CommentFunc(s64, s64, Disassembler::DisassemblyResult&);
class Debugger final { class Debugger final {
bool enabled = false; bool enabled = false;
@@ -15,14 +14,15 @@ class Debugger final {
void (*func)(s64, s64, Disassembler::DisassemblyResult&) = nullptr; void (*func)(s64, s64, Disassembler::DisassemblyResult&) = nullptr;
}; };
std::array<Column, 4> columns = { std::array<Column, 3> columns = {
Column{"##BreakpointColumn", &BreakpointFunc}, Column{"##BreakpointColumn", &BreakpointFunc},
Column{"Address", &AddressFunc}, Column{"Address", &AddressFunc},
Column{"Instruction", &InstructionFunc}, Column{"Instruction", &InstructionFunc},
Column{"Comment", &CommentFunc},
}; };
public: public:
void Open() { enabled = true; } void RegisterView();
bool followPC = true;
void Open(bool wantFollowPC = true) { enabled = true; followPC = wantFollowPC; }
void Close() { enabled = false; } void Close() { enabled = false; }
bool render(); bool render();
}; };

View File

@@ -22,11 +22,13 @@ namespace gui {
inline std::shared_ptr<Vulkan::WSI> g_Wsi; inline std::shared_ptr<Vulkan::WSI> g_Wsi;
static void CheckVkResult(VkResult err) { static void CheckVkResult(VkResult err) {
if (err == 0) if (err == VK_SUCCESS)
return; return;
error("[vulkan] VkResult = {}", (int) err);
if (err < 0) if (err < VK_SUCCESS)
abort(); panic("[vulkan] VkResult = {}", (int) err);
warn("[vulkan] VkResult = {}", (int) err);
} }
inline void Initialize(const std::shared_ptr<Vulkan::WSI>& wsi, SDL_Window* nativeWindow) { inline void Initialize(const std::shared_ptr<Vulkan::WSI>& wsi, SDL_Window* nativeWindow) {

View File

@@ -134,6 +134,24 @@ void KaizenGui::HandleInput(SDL_Event event) {
} }
} }
std::tuple<std::optional<u64>, 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()) {
auto memoryAccessV = memoryAccess.value();
ImGui::Text("%s", std::format("{} {}-bit value @ {:08X}{}", memoryAccessV.is_write ? "Writing" : "Reading",
(u8)memoryAccessV.size, memoryAccessV.address,
memoryAccessV.is_write ? std::format(" (value = 0x{:X})", memoryAccessV.written_val) : "")
.c_str());
}
return {lastPC, memoryAccess};
}
void KaizenGui::RenderUI() { void KaizenGui::RenderUI() {
n64::Core& core = n64::Core::GetInstance(); n64::Core& core = n64::Core::GetInstance();
gui::StartFrame(); gui::StartFrame();
@@ -189,6 +207,10 @@ void KaizenGui::RenderUI() {
} }
ImGui::EndMainMenuBar(); ImGui::EndMainMenuBar();
} }
if(!Util::Error::GetInstance().IsHandled()) {
ImGui::OpenPopup(Util::Error::GetSeverity().as_c_str());
}
if(settingsWindow.isOpen) { if(settingsWindow.isOpen) {
ImGui::OpenPopup("Settings", ImGuiPopupFlags_None); ImGui::OpenPopup("Settings", ImGuiPopupFlags_None);
@@ -216,6 +238,103 @@ void KaizenGui::RenderUI() {
ImGui::EndPopup(); 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) {
bool ignore = ImGui::Button("Try continuing"); ImGui::SameLine();
bool stop = ImGui::Button("Stop emulation"); ImGui::SameLine();
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();
bool ignore = ImGui::Button("Try continuing"); ImGui::SameLine();
bool stop = ImGui::Button("Stop emulation"); ImGui::SameLine();
bool chooseAnother = ImGui::Button("Choose another ROM");
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();
}
} break;
default: break;
}
ImGui::EndPopup();
}
if(ImGui::BeginMainStatusBar()) { if(ImGui::BeginMainStatusBar()) {
ImGui::Text("FPS: %.2f", ImGui::GetIO().Framerate); ImGui::Text("FPS: %.2f", ImGui::GetIO().Framerate);
@@ -292,7 +411,5 @@ void KaizenGui::run() {
} }
void KaizenGui::LoadTAS(const std::string &path) const noexcept { void KaizenGui::LoadTAS(const std::string &path) const noexcept {
if (!n64::Core::GetInstance().LoadTAS(fs::path(path))) { n64::Core::GetInstance().LoadTAS(fs::path(path));
panic("Could not load TAS movie {}!", path);
}
} }

View File

@@ -6,51 +6,103 @@
namespace Util { namespace Util {
struct Error { struct Error {
enum Severity { struct Severity {
NONE, enum {
WARN, NONE,
ERROR, WARN,
FATAL, NON_FATAL,
UNRECOVERABLE,
} as_enum;
const char* as_c_str() {
switch(as_enum) {
case NONE: return "";
case WARN: return "Warning";
case NON_FATAL: return "Error";
case UNRECOVERABLE: return "Unrecoverable Error";
}
}
}; };
union MemoryAccess { struct MemoryAccess {
struct { bool is_write;
unsigned : 3; enum Size {
unsigned is_write : 1; BYTE = 8, SHORT = 16, WORD = 32, DWORD = 64
unsigned is_fatal : 1; } size;
unsigned size : 3; u32 address;
}; u64 written_val;
u8 raw;
}; };
enum Type { struct Type {
SCHEDULER_EOL, enum {
SCHEDULER_UNKNOWN, SCHEDULER_EOL,
UNHANDLED_EXCEPTION, SCHEDULER_UNKNOWN,
UNHANDLED_INSTRUCTION, UNHANDLED_EXCEPTION,
INVALID_INSTRUCTION_FORMAT, UNHANDLED_INSTRUCTION,
TLB_LIMIT_EXCEEDED, INVALID_INSTRUCTION_FORMAT,
TLB_INVALID_ERROR, TLB_LIMIT_EXCEEDED,
TLB_UNHANDLED_ERROR, TLB_INVALID_ERROR,
TLB_UNHANDLED_MAPPING, TLB_UNHANDLED_ERROR,
JIT_BRANCH_INSIDE_DELAY_SLOT, TLB_UNHANDLED_MAPPING,
JIT_INVALID_X86_REG_ADDRESSING, JIT_BRANCH_INSIDE_DELAY_SLOT,
COULD_NOT_SYNC_SAVE_DATA, JIT_INVALID_X86_REG_ADDRESSING,
SAVE_DATA_IS_CORRUPT_OR_INVALID_SIZE, COULD_NOT_SYNC_SAVE_DATA,
MMAP_MAKE_SINK_ERROR, SAVE_DATA_IS_CORRUPT_OR_INVALID_SIZE,
MEM_INVALID_ACCESS, MMAP_MAKE_SINK_ERROR,
MEM_UNHANDLED_ACCESS, MEM_INVALID_ACCESS,
RDP_LIMIT_EXCEEDED, MEM_UNHANDLED_ACCESS,
FLASH_EXECUTE_COMMAND_ERROR, RDP_LIMIT_EXCEEDED,
PIF_UNHANDLED_CHANNEL, FLASH_EXECUTE_COMMAND,
UNHANDLED_COP0_STATUS_BIT, PIF_UNHANDLED_CHANNEL,
COP0_INVALID_ACCESS, UNHANDLED_COP0_STATUS_BIT,
COP0_UNHANDLED_ACCESS, COP0_INVALID_ACCESS,
SYSTEM_DIALOG_ERROR, COP0_UNHANDLED_ACCESS,
TAS_LOAD_ERROR, SYSTEM_DIALOG_ERROR,
ROM_LOAD_ERROR, TAS_LOAD_ERROR,
SAVE_OPTIONS_ERROR, ROM_LOAD_ERROR,
SAVE_OPTIONS_ERROR,
DIALOG_CANCELED,
UNKNOWN_CIC_TYPE,
GAME_DB_NOT_MATCHED,
CAPSTONE_ERROR,
} as_enum;
const char* as_c_str() {
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> template <class... Args>
@@ -63,15 +115,7 @@ struct Error {
this->lastPC = lastPC; this->lastPC = lastPC;
this->memoryAccess = memoryAccess; this->memoryAccess = memoryAccess;
this->type = type; this->type = type;
return std::format(fmt, std::forward<Args>(args)...); err = std::format(fmt, std::forward<Args>(args)...);
}
void SetHandled() {
severity = NONE;
}
bool IsHandled() {
return severity == NONE;
} }
static Error& GetInstance() { static Error& GetInstance() {
@@ -79,12 +123,25 @@ struct Error {
return instance; return instance;
} }
static std::string Get() { static std::string GetError() { return GetInstance().err; }
return GetInstance().err; static Severity GetSeverity() { return GetInstance().severity; }
static Type GetType() { return GetInstance().type; }
static std::optional<u64> 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: private:
std::string err; std::string err;
Severity severity = NONE; Severity severity = {};
Type type = {}; Type type = {};
std::optional<u64> lastPC = {}; std::optional<u64> lastPC = {};
std::optional<MemoryAccess> memoryAccess = {}; std::optional<MemoryAccess> memoryAccess = {};

58
src/utils/File.cpp Normal file
View File

@@ -0,0 +1,58 @@
#include <File.hpp>
#include <algorithm>
#include <unarr.h>
namespace Util {
std::vector<u8> OpenROM(const std::string &filename, size_t &sizeAdjusted) {
auto buf = ReadFileBinary(filename);
sizeAdjusted = NextPow2(buf.size());
return buf;
}
std::vector<u8> OpenArchive(const std::string &path, size_t &sizeAdjusted) {
const auto stream = ar_open_file(fs::path(path).string().c_str());
if (!stream) {
panic("Could not open archive! Are you sure it's an archive?");
}
ar_archive *archive = ar_open_zip_archive(stream, false);
if (!archive)
archive = ar_open_rar_archive(stream);
if (!archive)
archive = ar_open_7z_archive(stream);
if (!archive)
archive = ar_open_tar_archive(stream);
if (!archive) {
ar_close(stream);
panic("Could not open archive! Are you sure it's a supported archive? (7z, zip, rar and tar are supported)");
}
std::vector<u8> buf{};
std::vector<std::string> rom_exts{".n64", ".z64", ".v64", ".N64", ".Z64", ".V64"};
while (ar_parse_entry(archive)) {
auto filename = ar_entry_get_name(archive);
auto extension = fs::path(filename).extension();
if (std::ranges::any_of(rom_exts, [&](const auto &x) { return extension == x; })) {
const auto size = ar_entry_get_size(archive);
sizeAdjusted = NextPow2(size);
buf.resize(sizeAdjusted);
ar_entry_uncompress(archive, buf.data(), size);
break;
}
ar_close_archive(archive);
ar_close(stream);
panic("Could not find any rom image in the archive!");
}
ar_close_archive(archive);
ar_close(stream);
return buf;
}
}

View File

@@ -2,7 +2,6 @@
#include <fstream> #include <fstream>
#include <log.hpp> #include <log.hpp>
#include <vector> #include <vector>
#include <unarr.h>
#include <filesystem> #include <filesystem>
namespace fs = std::filesystem; namespace fs = std::filesystem;
@@ -42,56 +41,6 @@ FORCE_INLINE size_t NextPow2(size_t num) {
return num + 1; return num + 1;
} }
FORCE_INLINE std::vector<u8> OpenROM(const std::string &filename, size_t &sizeAdjusted) { std::vector<u8> OpenROM(const std::string &filename, size_t &sizeAdjusted);
auto buf = Util::ReadFileBinary(filename); std::vector<u8> OpenArchive(const std::string &path, size_t &sizeAdjusted);
sizeAdjusted = Util::NextPow2(buf.size());
return buf;
}
FORCE_INLINE 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 = Util::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;
}
} // namespace Util } // namespace Util