Merge branch 'master' into dynarec
# Conflicts: # .github/workflows/build.yml # src/CMakeLists.txt # src/frontend/imgui/Window.cpp # src/n64/Core.cpp # src/n64/core/interpreter/cop/cop1instructions.cpp # src/n64/core/interpreter/decode.cpp # src/n64/core/registers/Cop1.cpp
This commit is contained in:
@@ -13,10 +13,15 @@ find_package(fmt REQUIRED)
|
||||
find_package(SDL2 REQUIRED)
|
||||
find_package(nlohmann_json REQUIRED)
|
||||
|
||||
option(RAPIDJSON_BUILD_DOC "Build rapidjson documentation." OFF)
|
||||
option(RAPIDJSON_BUILD_EXAMPLES "Build rapidjson examples." OFF)
|
||||
option(RAPIDJSON_BUILD_TESTS "Build rapidjson perftests and unittests." OFF)
|
||||
|
||||
add_subdirectory(../external/capstone capstone)
|
||||
add_subdirectory(../external/parallel-rdp prdp)
|
||||
add_subdirectory(../external/imgui imgui)
|
||||
add_subdirectory(../external/nativefiledialog-extended nfd)
|
||||
add_subdirectory(../external/discord-rpc discord-rpc)
|
||||
|
||||
add_executable(gadolinium
|
||||
${CORE_SOURCES}
|
||||
@@ -25,7 +30,9 @@ add_executable(gadolinium
|
||||
${FRONTEND_HEADERS}
|
||||
main.cpp
|
||||
common.hpp
|
||||
util.hpp)
|
||||
util.hpp
|
||||
n64/Scheduler.cpp
|
||||
n64/Scheduler.hpp)
|
||||
|
||||
target_include_directories(gadolinium PRIVATE
|
||||
.
|
||||
@@ -43,6 +50,7 @@ target_include_directories(gadolinium PRIVATE
|
||||
../external/parallel-rdp/
|
||||
../external/nativefiledialog-extended/src/include
|
||||
../external/capstone/include
|
||||
../external/discord-rpc/include
|
||||
${SDL2_INCLUDE_DIR}
|
||||
)
|
||||
|
||||
@@ -57,4 +65,21 @@ if(WIN32)
|
||||
target_compile_options(gadolinium PUBLIC /EHa)
|
||||
endif()
|
||||
|
||||
target_link_libraries(gadolinium PRIVATE SDL2::SDL2main SDL2::SDL2 capstone-static nfd parallel-rdp fmt::fmt imgui nlohmann_json::nlohmann_json)
|
||||
if(${CMAKE_BUILD_TYPE} MATCHES Release)
|
||||
set_property(TARGET gadolinium PROPERTY INTERPROCEDURAL_OPTIMIZATION TRUE)
|
||||
if(WIN32)
|
||||
add_compile_options(/O2)
|
||||
else()
|
||||
add_compile_options(-O3)
|
||||
endif()
|
||||
else()
|
||||
if(WIN32)
|
||||
add_compile_options(/Od)
|
||||
else()
|
||||
add_compile_options(-fsanitize=address)
|
||||
add_link_options(-fsanitize=address)
|
||||
add_compile_options(-g)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
target_link_libraries(gadolinium PRIVATE discord-rpc SDL2::SDL2main SDL2::SDL2 capstone-static nfd parallel-rdp fmt::fmt imgui nlohmann_json::nlohmann_json)
|
||||
@@ -1,6 +1,12 @@
|
||||
#include <App.hpp>
|
||||
#include <nfd.hpp>
|
||||
|
||||
App::App() : window(core) {
|
||||
DiscordEventHandlers handlers{};
|
||||
Discord_Initialize("1049669178124148806", &handlers, 1, nullptr);
|
||||
util::UpdateRPC(util::Idling);
|
||||
}
|
||||
|
||||
void App::Run() {
|
||||
const u8* state = SDL_GetKeyboardState(nullptr);
|
||||
SDL_EventState(SDL_DROPFILE, SDL_ENABLE);
|
||||
@@ -50,4 +56,4 @@ void App::Run() {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,10 @@
|
||||
#pragma once
|
||||
#include <imgui/Window.hpp>
|
||||
#include <util.hpp>
|
||||
|
||||
struct App {
|
||||
App() : window(core) {};
|
||||
~App() = default;
|
||||
App();
|
||||
~App() { util::ClearRPC(); }
|
||||
void Run();
|
||||
inline void LoadROM(const std::string& path) {
|
||||
window.LoadROM(core, path);
|
||||
|
||||
@@ -2,10 +2,12 @@
|
||||
#include <nfd.hpp>
|
||||
#include <Core.hpp>
|
||||
#include <Audio.hpp>
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <filesystem>
|
||||
#include <SDL.h>
|
||||
|
||||
VkInstance instance{};
|
||||
using json = nlohmann::json;
|
||||
|
||||
Window::Window(n64::Core& core) {
|
||||
InitSDL();
|
||||
@@ -57,7 +59,7 @@ void Window::InitSDL() {
|
||||
"Gadolinium",
|
||||
SDL_WINDOWPOS_CENTERED,
|
||||
SDL_WINDOWPOS_CENTERED,
|
||||
800, 600,
|
||||
1024, 768,
|
||||
SDL_WINDOW_VULKAN | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI
|
||||
);
|
||||
|
||||
@@ -143,6 +145,17 @@ void Window::InitImgui() {
|
||||
uiFont = io.Fonts->AddFontFromFileTTF("resources/OpenSans.ttf", 15.f);
|
||||
codeFont = io.Fonts->AddFontFromFileTTF("resources/Sweet16.ttf", 15.f);
|
||||
|
||||
int displayIndex = SDL_GetWindowDisplayIndex(window);
|
||||
float ddpi, hdpi, vdpi;
|
||||
SDL_GetDisplayDPI(displayIndex, &ddpi, &hdpi, &vdpi);
|
||||
|
||||
ddpi /= 96.f;
|
||||
|
||||
uiFont = io.Fonts->AddFontFromFileTTF("resources/OpenSans.ttf", 16.f * ddpi);
|
||||
codeFont = io.Fonts->AddFontFromFileTTF("resources/Sweet16.ttf", 16.f * ddpi);
|
||||
|
||||
ImGui::GetStyle().ScaleAllSizes(ddpi);
|
||||
|
||||
{
|
||||
VkCommandBuffer commandBuffer = GetVkCommandBuffer();
|
||||
ImGui_ImplVulkan_CreateFontsTexture(commandBuffer);
|
||||
@@ -178,14 +191,14 @@ void Window::LoadROM(n64::Core& core, const std::string &path) {
|
||||
std::ifstream gameDbFile("resources/game_db.json");
|
||||
json gameDb = json::parse(gameDbFile);
|
||||
auto entry = gameDb[fmt::format("{:08x}", cartInfo.crc)]["name"];
|
||||
std::string name{};
|
||||
|
||||
if(!entry.empty()) {
|
||||
name = entry.get<std::string>();
|
||||
gameName = entry.get<std::string>();
|
||||
} else {
|
||||
name = std::filesystem::path(path).stem().string();
|
||||
gameName = std::filesystem::path(path).stem().string();
|
||||
}
|
||||
|
||||
windowTitle = "Gadolinium - " + name;
|
||||
windowTitle = "Gadolinium - " + gameName;
|
||||
shadowWindowTitle = windowTitle;
|
||||
|
||||
SDL_SetWindowTitle(window, windowTitle.c_str());
|
||||
@@ -215,6 +228,7 @@ void Window::Render(n64::Core& core) {
|
||||
nfdresult_t result = NFD_OpenDialog(&outpath, &filter, 1, nullptr);
|
||||
if (result == NFD_OKAY) {
|
||||
LoadROM(core, outpath);
|
||||
util::UpdateRPC(util::Playing, gameName);
|
||||
NFD_FreePath(outpath);
|
||||
}
|
||||
}
|
||||
@@ -238,6 +252,8 @@ void Window::Render(n64::Core& core) {
|
||||
}
|
||||
if (ImGui::MenuItem("Stop")) {
|
||||
windowTitle = "Gadolinium";
|
||||
core.rom.clear();
|
||||
util::UpdateRPC(util::Idling);
|
||||
SDL_SetWindowTitle(window, windowTitle.c_str());
|
||||
core.Stop();
|
||||
}
|
||||
@@ -246,8 +262,10 @@ void Window::Render(n64::Core& core) {
|
||||
if(core.pause) {
|
||||
shadowWindowTitle = windowTitle;
|
||||
windowTitle += " | Paused";
|
||||
util::UpdateRPC(util::Paused, gameName);
|
||||
} else {
|
||||
windowTitle = shadowWindowTitle;
|
||||
util::UpdateRPC(util::Playing, gameName);
|
||||
}
|
||||
SDL_SetWindowTitle(window, windowTitle.c_str());
|
||||
}
|
||||
|
||||
@@ -18,15 +18,16 @@ struct Window {
|
||||
[[nodiscard]] bool gotClosed(SDL_Event event);
|
||||
ImFont *uiFont{}, *codeFont{};
|
||||
u32 windowID{};
|
||||
float volumeL = 0.1, volumeR = 0.1;
|
||||
float volumeL = 0.0, volumeR = 0.0;
|
||||
void LoadROM(n64::Core& core, const std::string& path);
|
||||
private:
|
||||
json settings;
|
||||
std::fstream settingsFile;
|
||||
bool lockVolume = true;
|
||||
SDL_Window* window{};
|
||||
std::string windowTitle;
|
||||
std::string shadowWindowTitle;
|
||||
std::string windowTitle{"Gadolinium"};
|
||||
std::string shadowWindowTitle{windowTitle};
|
||||
std::string gameName{};
|
||||
void InitSDL();
|
||||
void InitImgui();
|
||||
void Render(n64::Core& core);
|
||||
|
||||
@@ -33,7 +33,7 @@ CartInfo Core::LoadROM(const std::string& rom_) {
|
||||
void Core::Run(Window& window, float volumeL, float volumeR) {
|
||||
MMIO& mmio = mem.mmio;
|
||||
Controller& controller = mmio.si.controller;
|
||||
int cpuSteps = 0;
|
||||
|
||||
for(int field = 0; field < mmio.vi.numFields; field++) {
|
||||
int frameCycles = 0;
|
||||
if(!pause && romLoaded) {
|
||||
@@ -46,14 +46,11 @@ void Core::Run(Window& window, float volumeL, float volumeR) {
|
||||
|
||||
for(;cycles <= mmio.vi.cyclesPerHalfline; cycles++, frameCycles++) {
|
||||
CpuStep(mem);
|
||||
cpuSteps++;
|
||||
if(mmio.rsp.spStatus.halt) {
|
||||
mmio.rsp.steps = 0;
|
||||
cpuSteps = 0;
|
||||
} else {
|
||||
if(cpuSteps > 2) {
|
||||
if(!mmio.rsp.spStatus.halt) {
|
||||
cpu.regs.steps++;
|
||||
if(cpu.regs.steps > 2) {
|
||||
mmio.rsp.steps += 2;
|
||||
cpuSteps -= 3;
|
||||
cpu.regs.steps -= 3;
|
||||
}
|
||||
|
||||
while(mmio.rsp.steps > 0) {
|
||||
|
||||
@@ -20,7 +20,7 @@ void Scheduler::enqueueAbsolute(const Event& event) {
|
||||
|
||||
void Scheduler::tick(u64 t, n64::Mem& mem, n64::Registers& regs) {
|
||||
ticks += t;
|
||||
if(ticks >= events.top().time) {
|
||||
while(ticks >= events.top().time) {
|
||||
events.top().event_cb(mem, regs);
|
||||
events.pop();
|
||||
}
|
||||
|
||||
@@ -53,13 +53,13 @@ void Interpreter::Step(Mem& mem) {
|
||||
regs.prevDelaySlot = regs.delaySlot;
|
||||
regs.delaySlot = false;
|
||||
|
||||
u32 instruction = mem.Read32(regs, regs.pc, regs.pc);
|
||||
|
||||
if(ShouldServiceInterrupt(regs)) {
|
||||
FireException(regs, ExceptionCode::Interrupt, 0, regs.pc);
|
||||
return;
|
||||
}
|
||||
|
||||
u32 instruction = mem.Read32(regs, regs.pc, regs.pc);
|
||||
|
||||
regs.oldPC = regs.pc;
|
||||
regs.pc = regs.nextPC;
|
||||
regs.nextPC += 4;
|
||||
|
||||
@@ -20,11 +20,13 @@ struct Interpreter {
|
||||
void Step(Mem&);
|
||||
Registers regs;
|
||||
private:
|
||||
u64 cop2Latch{};
|
||||
csh handle;
|
||||
|
||||
void disassembly(u32 instr);
|
||||
friend struct Cop1;
|
||||
|
||||
void cop2Decode(u32);
|
||||
void special(Mem&, u32);
|
||||
void regimm(u32);
|
||||
void Exec(Mem&, u32);
|
||||
@@ -113,5 +115,12 @@ private:
|
||||
void ori(u32);
|
||||
void xor_(u32);
|
||||
void xori(u32);
|
||||
|
||||
void mtc2(u32);
|
||||
void mfc2(u32);
|
||||
void dmtc2(u32);
|
||||
void dmfc2(u32);
|
||||
void ctc2(u32);
|
||||
void cfc2(u32);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -10,6 +10,23 @@ Mem::Mem() {
|
||||
}
|
||||
|
||||
void Mem::Reset() {
|
||||
readPages.resize(PAGE_COUNT);
|
||||
writePages.resize(PAGE_COUNT);
|
||||
std::fill(readPages.begin(), readPages.end(), 0);
|
||||
std::fill(writePages.begin(), writePages.end(), 0);
|
||||
|
||||
for(int i = 0; i < 2048; i++) {
|
||||
const auto addr = (i * PAGE_SIZE) & RDRAM_DSIZE;
|
||||
const auto pointer = (uintptr_t) &mmio.rdp.rdram[addr];
|
||||
readPages[i] = pointer;
|
||||
writePages[i] = pointer;
|
||||
}
|
||||
|
||||
readPages[0x4000] = (uintptr_t) &mmio.rsp.dmem[0];
|
||||
readPages[0x4001] = (uintptr_t) &mmio.rsp.imem[0];
|
||||
writePages[0x4000] = (uintptr_t) &mmio.rsp.dmem[0];
|
||||
writePages[0x4001] = (uintptr_t) &mmio.rsp.imem[0];
|
||||
|
||||
sram.resize(SRAM_SIZE);
|
||||
std::fill(sram.begin(), sram.end(), 0);
|
||||
romMask = 0;
|
||||
@@ -73,132 +90,182 @@ template bool MapVAddr<false>(Registers& regs, TLBAccessType accessType, u64 vad
|
||||
template <bool tlb>
|
||||
u8 Mem::Read8(n64::Registers ®s, u64 vaddr, s64 pc) {
|
||||
u32 paddr = vaddr;
|
||||
if(!MapVAddr<tlb>(regs, LOAD, vaddr, paddr)) {
|
||||
if (!MapVAddr<tlb>(regs, LOAD, vaddr, paddr)) {
|
||||
HandleTLBException(regs, vaddr);
|
||||
FireException(regs, GetTLBExceptionCode(regs.cop0.tlbError, LOAD), 0, pc);
|
||||
}
|
||||
|
||||
switch(paddr) {
|
||||
case 0x00000000 ... 0x007FFFFF:
|
||||
return mmio.rdp.dram[BYTE_ADDRESS(paddr)];
|
||||
case 0x04000000 ... 0x0403FFFF:
|
||||
if((paddr >> 12) & 1)
|
||||
return mmio.rsp.imem[BYTE_ADDRESS(paddr) & IMEM_DSIZE];
|
||||
else
|
||||
return mmio.rsp.dmem[BYTE_ADDRESS(paddr) & DMEM_DSIZE];
|
||||
case 0x04040000 ... 0x040FFFFF: case 0x04100000 ... 0x041FFFFF:
|
||||
case 0x04600000 ... 0x048FFFFF: case 0x04300000 ... 0x044FFFFF: return 0xff;
|
||||
case 0x04500000 ... 0x045FFFFF: {
|
||||
u32 w = mmio.ai.Read(paddr & ~3);
|
||||
int offs = 3 - (paddr & 3);
|
||||
return (w >> (offs * 8)) & 0xff;
|
||||
const auto page = paddr >> 12;
|
||||
const auto offset = paddr & 0xFFF;
|
||||
const auto pointer = readPages[page];
|
||||
|
||||
if(pointer) {
|
||||
return ((u8*)pointer)[BYTE_ADDRESS(offset)];
|
||||
} else {
|
||||
switch (paddr) {
|
||||
case 0x00000000 ... 0x007FFFFF:
|
||||
return mmio.rdp.rdram[BYTE_ADDRESS(paddr)];
|
||||
case 0x04000000 ... 0x0403FFFF:
|
||||
if ((paddr >> 12) & 1)
|
||||
return mmio.rsp.imem[BYTE_ADDRESS(paddr) & IMEM_DSIZE];
|
||||
else
|
||||
return mmio.rsp.dmem[BYTE_ADDRESS(paddr) & DMEM_DSIZE];
|
||||
case 0x04040000 ... 0x040FFFFF:
|
||||
case 0x04100000 ... 0x041FFFFF:
|
||||
case 0x04600000 ... 0x048FFFFF:
|
||||
case 0x04300000 ... 0x044FFFFF:
|
||||
return 0xff;
|
||||
case 0x04500000 ... 0x045FFFFF: {
|
||||
u32 w = mmio.ai.Read(paddr & ~3);
|
||||
int offs = 3 - (paddr & 3);
|
||||
return (w >> (offs * 8)) & 0xff;
|
||||
}
|
||||
case 0x10000000 ... 0x1FBFFFFF:
|
||||
paddr = (paddr + 2) & ~2;
|
||||
return cart[BYTE_ADDRESS(paddr) & romMask];
|
||||
case 0x1FC00000 ... 0x1FC007BF:
|
||||
return pifBootrom[BYTE_ADDRESS(paddr) - 0x1FC00000];
|
||||
case 0x1FC007C0 ... 0x1FC007FF:
|
||||
return pifRam[paddr - 0x1FC007C0];
|
||||
case 0x00800000 ... 0x03FFFFFF:
|
||||
case 0x04200000 ... 0x042FFFFF:
|
||||
case 0x04900000 ... 0x0FFFFFFF:
|
||||
case 0x1FC00800 ... 0xFFFFFFFF:
|
||||
return 0;
|
||||
default:
|
||||
util::panic("Unimplemented 8-bit read at address {:08X} (PC = {:016X})\n", paddr, (u64) regs.pc);
|
||||
}
|
||||
case 0x10000000 ... 0x1FBFFFFF:
|
||||
paddr = (paddr + 2) & ~2;
|
||||
return cart[BYTE_ADDRESS(paddr) & romMask];
|
||||
case 0x1FC00000 ... 0x1FC007BF:
|
||||
return pifBootrom[BYTE_ADDRESS(paddr) - 0x1FC00000];
|
||||
case 0x1FC007C0 ... 0x1FC007FF:
|
||||
return pifRam[paddr - 0x1FC007C0];
|
||||
case 0x00800000 ... 0x03FFFFFF: case 0x04200000 ... 0x042FFFFF:
|
||||
case 0x04900000 ... 0x0FFFFFFF: case 0x1FC00800 ... 0xFFFFFFFF: return 0;
|
||||
default:
|
||||
util::panic("Unimplemented 8-bit read at address {:08X} (PC = {:016X})\n", paddr, (u64)regs.pc);
|
||||
}
|
||||
}
|
||||
|
||||
template <bool tlb>
|
||||
u16 Mem::Read16(n64::Registers ®s, u64 vaddr, s64 pc) {
|
||||
u32 paddr = vaddr;
|
||||
if(!MapVAddr<tlb>(regs, LOAD, vaddr, paddr)) {
|
||||
if (!MapVAddr<tlb>(regs, LOAD, vaddr, paddr)) {
|
||||
HandleTLBException(regs, vaddr);
|
||||
FireException(regs, GetTLBExceptionCode(regs.cop0.tlbError, LOAD), 0, pc);
|
||||
}
|
||||
|
||||
switch(paddr) {
|
||||
case 0x00000000 ... 0x007FFFFF:
|
||||
return util::ReadAccess<u16>(mmio.rdp.dram.data(), HALF_ADDRESS(paddr));
|
||||
case 0x04000000 ... 0x0403FFFF:
|
||||
if((paddr >> 12) & 1)
|
||||
return util::ReadAccess<u16>(mmio.rsp.imem, HALF_ADDRESS(paddr) & IMEM_DSIZE);
|
||||
else
|
||||
return util::ReadAccess<u16>(mmio.rsp.dmem, HALF_ADDRESS(paddr) & DMEM_DSIZE);
|
||||
case 0x04040000 ... 0x040FFFFF: case 0x04100000 ... 0x041FFFFF:
|
||||
case 0x04300000 ... 0x044FFFFF: case 0x04500000 ... 0x048FFFFF:
|
||||
return mmio.Read(paddr);
|
||||
case 0x10000000 ... 0x1FBFFFFF:
|
||||
paddr = (paddr + 2) & ~3;
|
||||
return util::ReadAccess<u16>(cart.data(), HALF_ADDRESS(paddr) & romMask);
|
||||
case 0x1FC00000 ... 0x1FC007BF:
|
||||
return util::ReadAccess<u16>(pifBootrom, HALF_ADDRESS(paddr) - 0x1FC00000);
|
||||
case 0x1FC007C0 ... 0x1FC007FF:
|
||||
return be16toh(util::ReadAccess<u16>(pifRam, paddr - 0x1FC007C0));
|
||||
case 0x00800000 ... 0x03FFFFFF: case 0x04200000 ... 0x042FFFFF:
|
||||
case 0x04900000 ... 0x0FFFFFFF: case 0x1FC00800 ... 0xFFFFFFFF: return 0;
|
||||
default: util::panic("Unimplemented 16-bit read at address {:08X} (PC = {:016X})\n", paddr, (u64)regs.pc);
|
||||
const auto page = paddr >> 12;
|
||||
const auto offset = paddr & 0xFFF;
|
||||
const auto pointer = readPages[page];
|
||||
|
||||
if(pointer) {
|
||||
return util::ReadAccess<u16>((u8*)pointer, HALF_ADDRESS(offset));
|
||||
} else {
|
||||
switch (paddr) {
|
||||
case 0x00000000 ... 0x007FFFFF:
|
||||
return util::ReadAccess<u16>(mmio.rdp.rdram.data(), HALF_ADDRESS(paddr));
|
||||
case 0x04000000 ... 0x0403FFFF:
|
||||
if ((paddr >> 12) & 1)
|
||||
return util::ReadAccess<u16>(mmio.rsp.imem, HALF_ADDRESS(paddr) & IMEM_DSIZE);
|
||||
else
|
||||
return util::ReadAccess<u16>(mmio.rsp.dmem, HALF_ADDRESS(paddr) & DMEM_DSIZE);
|
||||
case 0x04040000 ... 0x040FFFFF:
|
||||
case 0x04100000 ... 0x041FFFFF:
|
||||
case 0x04300000 ... 0x044FFFFF:
|
||||
case 0x04500000 ... 0x048FFFFF:
|
||||
return mmio.Read(paddr);
|
||||
case 0x10000000 ... 0x1FBFFFFF:
|
||||
paddr = (paddr + 2) & ~3;
|
||||
return util::ReadAccess<u16>(cart.data(), HALF_ADDRESS(paddr) & romMask);
|
||||
case 0x1FC00000 ... 0x1FC007BF:
|
||||
return util::ReadAccess<u16>(pifBootrom, HALF_ADDRESS(paddr) - 0x1FC00000);
|
||||
case 0x1FC007C0 ... 0x1FC007FF:
|
||||
return be16toh(util::ReadAccess<u16>(pifRam, paddr - 0x1FC007C0));
|
||||
case 0x00800000 ... 0x03FFFFFF:
|
||||
case 0x04200000 ... 0x042FFFFF:
|
||||
case 0x04900000 ... 0x0FFFFFFF:
|
||||
case 0x1FC00800 ... 0xFFFFFFFF:
|
||||
return 0;
|
||||
default:
|
||||
util::panic("Unimplemented 16-bit read at address {:08X} (PC = {:016X})\n", paddr, (u64) regs.pc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <bool tlb>
|
||||
u32 Mem::Read32(n64::Registers ®s, u64 vaddr, s64 pc) {
|
||||
u32 paddr = vaddr;
|
||||
if(!MapVAddr<tlb>(regs, LOAD, vaddr, paddr)) {
|
||||
if (!MapVAddr<tlb>(regs, LOAD, vaddr, paddr)) {
|
||||
HandleTLBException(regs, vaddr);
|
||||
FireException(regs, GetTLBExceptionCode(regs.cop0.tlbError, LOAD), 0, pc);
|
||||
}
|
||||
|
||||
switch(paddr) {
|
||||
case 0x00000000 ... 0x007FFFFF:
|
||||
return util::ReadAccess<u32>(mmio.rdp.dram.data(), paddr);
|
||||
case 0x04000000 ... 0x0403FFFF:
|
||||
if((paddr >> 12) & 1)
|
||||
return util::ReadAccess<u32>(mmio.rsp.imem, paddr & IMEM_DSIZE);
|
||||
else
|
||||
return util::ReadAccess<u32>(mmio.rsp.dmem, paddr & DMEM_DSIZE);
|
||||
case 0x04040000 ... 0x040FFFFF: case 0x04100000 ... 0x041FFFFF:
|
||||
case 0x04300000 ... 0x044FFFFF: case 0x04500000 ... 0x048FFFFF:
|
||||
return mmio.Read(paddr);
|
||||
case 0x10000000 ... 0x1FBFFFFF:
|
||||
return util::ReadAccess<u32>(cart.data(), paddr & romMask);
|
||||
case 0x1FC00000 ... 0x1FC007BF:
|
||||
return util::ReadAccess<u32>(pifBootrom, paddr - 0x1FC00000);
|
||||
case 0x1FC007C0 ... 0x1FC007FF:
|
||||
return be32toh(util::ReadAccess<u32>(pifRam, paddr - 0x1FC007C0));
|
||||
case 0x00800000 ... 0x03FFFFFF: case 0x04200000 ... 0x042FFFFF:
|
||||
case 0x04900000 ... 0x0FFFFFFF: case 0x1FC00800 ... 0xFFFFFFFF: return 0;
|
||||
default:
|
||||
util::panic("Unimplemented 32-bit read at address {:08X} (PC = {:016X})\n", paddr, (u64) regs.pc);
|
||||
const auto page = paddr >> 12;
|
||||
const auto offset = paddr & 0xFFF;
|
||||
const auto pointer = readPages[page];
|
||||
|
||||
if(pointer) {
|
||||
return util::ReadAccess<u32>((u8*)pointer, offset);
|
||||
} else {
|
||||
switch(paddr) {
|
||||
case 0x00000000 ... 0x007FFFFF:
|
||||
return util::ReadAccess<u32>(mmio.rdp.rdram.data(), paddr);
|
||||
case 0x04000000 ... 0x0403FFFF:
|
||||
if((paddr >> 12) & 1)
|
||||
return util::ReadAccess<u32>(mmio.rsp.imem, paddr & IMEM_DSIZE);
|
||||
else
|
||||
return util::ReadAccess<u32>(mmio.rsp.dmem, paddr & DMEM_DSIZE);
|
||||
case 0x04040000 ... 0x040FFFFF: case 0x04100000 ... 0x041FFFFF:
|
||||
case 0x04300000 ... 0x044FFFFF: case 0x04500000 ... 0x048FFFFF:
|
||||
return mmio.Read(paddr);
|
||||
case 0x10000000 ... 0x1FBFFFFF:
|
||||
return util::ReadAccess<u32>(cart.data(), paddr & romMask);
|
||||
case 0x1FC00000 ... 0x1FC007BF:
|
||||
return util::ReadAccess<u32>(pifBootrom, paddr - 0x1FC00000);
|
||||
case 0x1FC007C0 ... 0x1FC007FF:
|
||||
return be32toh(util::ReadAccess<u32>(pifRam, paddr - 0x1FC007C0));
|
||||
case 0x00800000 ... 0x03FFFFFF: case 0x04200000 ... 0x042FFFFF:
|
||||
case 0x04900000 ... 0x0FFFFFFF: case 0x1FC00800 ... 0xFFFFFFFF: return 0;
|
||||
default:
|
||||
util::panic("Unimplemented 32-bit read at address {:08X} (PC = {:016X})\n", paddr, (u64) regs.pc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <bool tlb>
|
||||
u64 Mem::Read64(n64::Registers ®s, u64 vaddr, s64 pc) {
|
||||
u32 paddr = vaddr;
|
||||
if(!MapVAddr<tlb>(regs, LOAD, vaddr, paddr)) {
|
||||
if (!MapVAddr<tlb>(regs, LOAD, vaddr, paddr)) {
|
||||
HandleTLBException(regs, vaddr);
|
||||
FireException(regs, GetTLBExceptionCode(regs.cop0.tlbError, LOAD), 0, pc);
|
||||
}
|
||||
|
||||
switch(paddr) {
|
||||
case 0x00000000 ... 0x007FFFFF:
|
||||
return util::ReadAccess<u64>(mmio.rdp.dram.data(), paddr);
|
||||
case 0x04000000 ... 0x0403FFFF:
|
||||
if((paddr >> 12) & 1)
|
||||
return util::ReadAccess<u64>(mmio.rsp.imem, paddr & IMEM_DSIZE);
|
||||
else
|
||||
return util::ReadAccess<u64>(mmio.rsp.dmem, paddr & DMEM_DSIZE);
|
||||
case 0x04040000 ... 0x040FFFFF: case 0x04100000 ... 0x041FFFFF:
|
||||
case 0x04300000 ... 0x044FFFFF: case 0x04500000 ... 0x048FFFFF:
|
||||
return mmio.Read(paddr);
|
||||
case 0x10000000 ... 0x1FBFFFFF:
|
||||
return util::ReadAccess<u64>(cart.data(), paddr & romMask);
|
||||
case 0x1FC00000 ... 0x1FC007BF:
|
||||
return util::ReadAccess<u64>(pifBootrom, paddr - 0x1FC00000);
|
||||
case 0x1FC007C0 ... 0x1FC007FF:
|
||||
return be64toh(util::ReadAccess<u64>(pifRam, paddr - 0x1FC007C0));
|
||||
case 0x00800000 ... 0x03FFFFFF: case 0x04200000 ... 0x042FFFFF:
|
||||
case 0x04900000 ... 0x0FFFFFFF: case 0x1FC00800 ... 0xFFFFFFFF: return 0;
|
||||
default: util::panic("Unimplemented 32-bit read at address {:08X} (PC = {:016X})\n", paddr, (u64)regs.pc);
|
||||
const auto page = paddr >> 12;
|
||||
const auto offset = paddr & 0xFFF;
|
||||
const auto pointer = readPages[page];
|
||||
|
||||
if(pointer) {
|
||||
return util::ReadAccess<u64>((u8*)pointer, offset);
|
||||
} else {
|
||||
switch (paddr) {
|
||||
case 0x00000000 ... 0x007FFFFF:
|
||||
return util::ReadAccess<u64>(mmio.rdp.rdram.data(), paddr);
|
||||
case 0x04000000 ... 0x0403FFFF:
|
||||
if ((paddr >> 12) & 1)
|
||||
return util::ReadAccess<u64>(mmio.rsp.imem, paddr & IMEM_DSIZE);
|
||||
else
|
||||
return util::ReadAccess<u64>(mmio.rsp.dmem, paddr & DMEM_DSIZE);
|
||||
case 0x04040000 ... 0x040FFFFF:
|
||||
case 0x04100000 ... 0x041FFFFF:
|
||||
case 0x04300000 ... 0x044FFFFF:
|
||||
case 0x04500000 ... 0x048FFFFF:
|
||||
return mmio.Read(paddr);
|
||||
case 0x10000000 ... 0x1FBFFFFF:
|
||||
return util::ReadAccess<u64>(cart.data(), paddr & romMask);
|
||||
case 0x1FC00000 ... 0x1FC007BF:
|
||||
return util::ReadAccess<u64>(pifBootrom, paddr - 0x1FC00000);
|
||||
case 0x1FC007C0 ... 0x1FC007FF:
|
||||
return be64toh(util::ReadAccess<u64>(pifRam, paddr - 0x1FC007C0));
|
||||
case 0x00800000 ... 0x03FFFFFF:
|
||||
case 0x04200000 ... 0x042FFFFF:
|
||||
case 0x04900000 ... 0x0FFFFFFF:
|
||||
case 0x1FC00800 ... 0xFFFFFFFF:
|
||||
return 0;
|
||||
default:
|
||||
util::panic("Unimplemented 32-bit read at address {:08X} (PC = {:016X})\n", paddr, (u64) regs.pc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -214,73 +281,118 @@ template u64 Mem::Read64<true>(n64::Registers ®s, u64 vaddr, s64 pc);
|
||||
template <bool tlb>
|
||||
void Mem::Write8(Registers& regs, u64 vaddr, u32 val, s64 pc) {
|
||||
u32 paddr = vaddr;
|
||||
if(!MapVAddr<tlb>(regs, STORE, vaddr, paddr)) {
|
||||
if (!MapVAddr<tlb>(regs, LOAD, vaddr, paddr)) {
|
||||
HandleTLBException(regs, vaddr);
|
||||
FireException(regs, GetTLBExceptionCode(regs.cop0.tlbError, STORE), 0, pc);
|
||||
FireException(regs, GetTLBExceptionCode(regs.cop0.tlbError, LOAD), 0, pc);
|
||||
}
|
||||
|
||||
switch(paddr) {
|
||||
case 0x00000000 ... 0x007FFFFF:
|
||||
mmio.rdp.dram[BYTE_ADDRESS(paddr)] = val;
|
||||
break;
|
||||
case 0x04000000 ... 0x0403FFFF:
|
||||
const auto page = paddr >> 12;
|
||||
auto offset = paddr & 0xFFF;
|
||||
const auto pointer = readPages[page];
|
||||
|
||||
if(pointer) {
|
||||
if(paddr >= 0x04000000 && paddr <= 0x0403FFFF) {
|
||||
val = val << (8 * (3 - (paddr & 3)));
|
||||
paddr = (paddr & DMEM_DSIZE) & ~3;
|
||||
if(paddr & 0x1000)
|
||||
util::WriteAccess<u32>(mmio.rsp.imem, paddr & IMEM_DSIZE, val);
|
||||
else
|
||||
util::WriteAccess<u32>(mmio.rsp.dmem, paddr & DMEM_DSIZE, val);
|
||||
break;
|
||||
case 0x04040000 ... 0x040FFFFF: case 0x04100000 ... 0x041FFFFF:
|
||||
case 0x04300000 ... 0x044FFFFF: case 0x04500000 ... 0x048FFFFF: util::panic("MMIO Write8!\n");
|
||||
case 0x10000000 ... 0x13FFFFFF: break;
|
||||
case 0x1FC007C0 ... 0x1FC007FF:
|
||||
val = val << (8 * (3 - (paddr & 3)));
|
||||
paddr = (paddr - 0x1FC007C0) & ~3;
|
||||
util::WriteAccess<u32>(pifRam, paddr, htobe32(val));
|
||||
ProcessPIFCommands(pifRam, mmio.si.controller, *this);
|
||||
break;
|
||||
case 0x00800000 ... 0x03FFFFFF: case 0x04200000 ... 0x042FFFFF:
|
||||
case 0x08000000 ... 0x0FFFFFFF: case 0x04900000 ... 0x07FFFFFF:
|
||||
case 0x1FC00800 ... 0x7FFFFFFF: case 0x80000000 ... 0xFFFFFFFF: break;
|
||||
default:
|
||||
util::panic("Unimplemented 8-bit write at address {:08X} with value {:0X} (PC = {:016X})\n", paddr, val, (u64)regs.pc);
|
||||
offset = (offset & DMEM_DSIZE) & ~3;
|
||||
}
|
||||
|
||||
((u8*)pointer)[BYTE_ADDRESS(offset)] = val;
|
||||
} else {
|
||||
switch (paddr) {
|
||||
case 0x00000000 ... 0x007FFFFF:
|
||||
mmio.rdp.rdram[BYTE_ADDRESS(paddr)] = val;
|
||||
break;
|
||||
case 0x04000000 ... 0x0403FFFF:
|
||||
val = val << (8 * (3 - (paddr & 3)));
|
||||
paddr = (paddr & DMEM_DSIZE) & ~3;
|
||||
if (paddr & 0x1000)
|
||||
util::WriteAccess<u32>(mmio.rsp.imem, paddr & IMEM_DSIZE, val);
|
||||
else
|
||||
util::WriteAccess<u32>(mmio.rsp.dmem, paddr & DMEM_DSIZE, val);
|
||||
break;
|
||||
case 0x04040000 ... 0x040FFFFF:
|
||||
case 0x04100000 ... 0x041FFFFF:
|
||||
case 0x04300000 ... 0x044FFFFF:
|
||||
case 0x04500000 ... 0x048FFFFF:
|
||||
util::panic("MMIO Write8!\n");
|
||||
case 0x10000000 ... 0x13FFFFFF:
|
||||
break;
|
||||
case 0x1FC007C0 ... 0x1FC007FF:
|
||||
val = val << (8 * (3 - (paddr & 3)));
|
||||
paddr = (paddr - 0x1FC007C0) & ~3;
|
||||
util::WriteAccess<u32>(pifRam, paddr, htobe32(val));
|
||||
ProcessPIFCommands(pifRam, mmio.si.controller, *this);
|
||||
break;
|
||||
case 0x00800000 ... 0x03FFFFFF:
|
||||
case 0x04200000 ... 0x042FFFFF:
|
||||
case 0x08000000 ... 0x0FFFFFFF:
|
||||
case 0x04900000 ... 0x07FFFFFF:
|
||||
case 0x1FC00800 ... 0x7FFFFFFF:
|
||||
case 0x80000000 ... 0xFFFFFFFF:
|
||||
break;
|
||||
default:
|
||||
util::panic("Unimplemented 8-bit write at address {:08X} with value {:0X} (PC = {:016X})\n", paddr, val,
|
||||
(u64) regs.pc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <bool tlb>
|
||||
void Mem::Write16(Registers& regs, u64 vaddr, u32 val, s64 pc) {
|
||||
u32 paddr = vaddr;
|
||||
if(!MapVAddr<tlb>(regs, STORE, vaddr, paddr)) {
|
||||
if (!MapVAddr<tlb>(regs, STORE, vaddr, paddr)) {
|
||||
HandleTLBException(regs, vaddr);
|
||||
FireException(regs, GetTLBExceptionCode(regs.cop0.tlbError, STORE), 0, pc);
|
||||
}
|
||||
|
||||
switch(paddr) {
|
||||
case 0x00000000 ... 0x007FFFFF:
|
||||
util::WriteAccess<u16>(mmio.rdp.dram.data(), HALF_ADDRESS(paddr), val);
|
||||
break;
|
||||
case 0x04000000 ... 0x0403FFFF:
|
||||
const auto page = paddr >> 12;
|
||||
auto offset = paddr & 0xFFF;
|
||||
const auto pointer = readPages[page];
|
||||
|
||||
if(pointer) {
|
||||
if(paddr >= 0x04000000 && paddr <= 0x0403FFFF) {
|
||||
val = val << (16 * !(paddr & 2));
|
||||
paddr &= ~3;
|
||||
if(paddr & 0x1000)
|
||||
util::WriteAccess<u32>(mmio.rsp.imem, paddr & IMEM_DSIZE, val);
|
||||
else
|
||||
util::WriteAccess<u32>(mmio.rsp.dmem, paddr & DMEM_DSIZE, val);
|
||||
break;
|
||||
case 0x04040000 ... 0x040FFFFF: case 0x04100000 ... 0x041FFFFF:
|
||||
case 0x04300000 ... 0x044FFFFF: case 0x04500000 ... 0x048FFFFF: util::panic("MMIO Write16!\n");
|
||||
case 0x10000000 ... 0x13FFFFFF: break;
|
||||
case 0x1FC007C0 ... 0x1FC007FF:
|
||||
val = val << (16 * !(paddr & 2));
|
||||
paddr &= ~3;
|
||||
util::WriteAccess<u32>(pifRam, paddr - 0x1FC007C0, htobe32(val));
|
||||
ProcessPIFCommands(pifRam, mmio.si.controller, *this);
|
||||
break;
|
||||
case 0x00800000 ... 0x03FFFFFF: case 0x04200000 ... 0x042FFFFF:
|
||||
case 0x08000000 ... 0x0FFFFFFF: case 0x04900000 ... 0x07FFFFFF:
|
||||
case 0x1FC00800 ... 0x7FFFFFFF: case 0x80000000 ... 0xFFFFFFFF: break;
|
||||
default: util::panic("Unimplemented 16-bit write at address {:08X} with value {:0X} (PC = {:016X})\n", paddr, val, (u64)regs.pc);
|
||||
offset &= ~3;
|
||||
}
|
||||
|
||||
util::WriteAccess<u16>((u8*)pointer, HALF_ADDRESS(offset), val);
|
||||
} else {
|
||||
switch (paddr) {
|
||||
case 0x00000000 ... 0x007FFFFF:
|
||||
util::WriteAccess<u16>(mmio.rdp.rdram.data(), HALF_ADDRESS(paddr), val);
|
||||
break;
|
||||
case 0x04000000 ... 0x0403FFFF:
|
||||
val = val << (16 * !(paddr & 2));
|
||||
paddr &= ~3;
|
||||
if (paddr & 0x1000)
|
||||
util::WriteAccess<u32>(mmio.rsp.imem, paddr & IMEM_DSIZE, val);
|
||||
else
|
||||
util::WriteAccess<u32>(mmio.rsp.dmem, paddr & DMEM_DSIZE, val);
|
||||
break;
|
||||
case 0x04040000 ... 0x040FFFFF:
|
||||
case 0x04100000 ... 0x041FFFFF:
|
||||
case 0x04300000 ... 0x044FFFFF:
|
||||
case 0x04500000 ... 0x048FFFFF:
|
||||
util::panic("MMIO Write16!\n");
|
||||
case 0x10000000 ... 0x13FFFFFF:
|
||||
break;
|
||||
case 0x1FC007C0 ... 0x1FC007FF:
|
||||
val = val << (16 * !(paddr & 2));
|
||||
paddr &= ~3;
|
||||
util::WriteAccess<u32>(pifRam, paddr - 0x1FC007C0, htobe32(val));
|
||||
ProcessPIFCommands(pifRam, mmio.si.controller, *this);
|
||||
break;
|
||||
case 0x00800000 ... 0x03FFFFFF:
|
||||
case 0x04200000 ... 0x042FFFFF:
|
||||
case 0x08000000 ... 0x0FFFFFFF:
|
||||
case 0x04900000 ... 0x07FFFFFF:
|
||||
case 0x1FC00800 ... 0x7FFFFFFF:
|
||||
case 0x80000000 ... 0xFFFFFFFF:
|
||||
break;
|
||||
default:
|
||||
util::panic("Unimplemented 16-bit write at address {:08X} with value {:0X} (PC = {:016X})\n", paddr, val,
|
||||
(u64) regs.pc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -292,38 +404,46 @@ void Mem::Write32(Registers& regs, u64 vaddr, u32 val, s64 pc) {
|
||||
FireException(regs, GetTLBExceptionCode(regs.cop0.tlbError, STORE), 0, pc);
|
||||
}
|
||||
|
||||
switch(paddr) {
|
||||
case 0x00000000 ... 0x007FFFFF:
|
||||
util::WriteAccess<u32>(mmio.rdp.dram.data(), paddr, val);
|
||||
break;
|
||||
case 0x04000000 ... 0x0403FFFF:
|
||||
if(paddr & 0x1000)
|
||||
util::WriteAccess<u32>(mmio.rsp.imem, paddr & IMEM_DSIZE, val);
|
||||
else
|
||||
util::WriteAccess<u32>(mmio.rsp.dmem, paddr & DMEM_DSIZE, val);
|
||||
break;
|
||||
case 0x04040000 ... 0x040FFFFF: case 0x04100000 ... 0x041FFFFF:
|
||||
case 0x04300000 ... 0x044FFFFF: case 0x04500000 ... 0x048FFFFF: mmio.Write(*this, regs, paddr, val); break;
|
||||
case 0x10000000 ... 0x13FF0013: break;
|
||||
case 0x13FF0014: {
|
||||
if(val < ISVIEWER_SIZE) {
|
||||
char* message = (char*)calloc(val + 1, 1);
|
||||
memcpy(message, isviewer, val);
|
||||
fmt::print("{}", message);
|
||||
free(message);
|
||||
}
|
||||
} break;
|
||||
case 0x13FF0020 ... 0x13FFFFFF:
|
||||
util::WriteAccess<u32>(isviewer, paddr - 0x13FF0020, htobe32(val));
|
||||
break;
|
||||
case 0x1FC007C0 ... 0x1FC007FF:
|
||||
util::WriteAccess<u32>(pifRam, paddr - 0x1FC007C0, htobe32(val));
|
||||
ProcessPIFCommands(pifRam, mmio.si.controller, *this);
|
||||
break;
|
||||
case 0x00800000 ... 0x03FFFFFF: case 0x04200000 ... 0x042FFFFF:
|
||||
case 0x08000000 ... 0x0FFFFFFF: case 0x04900000 ... 0x07FFFFFF:
|
||||
case 0x1FC00800 ... 0x7FFFFFFF: case 0x80000000 ... 0xFFFFFFFF: break;
|
||||
default: util::panic("Unimplemented 32-bit write at address {:08X} with value {:0X} (PC = {:016X})\n", paddr, val, (u64)regs.pc);
|
||||
const auto page = paddr >> 12;
|
||||
auto offset = paddr & 0xFFF;
|
||||
const auto pointer = readPages[page];
|
||||
|
||||
if(pointer) {
|
||||
util::WriteAccess<u32>((u8*)pointer, offset, val);
|
||||
} else {
|
||||
switch(paddr) {
|
||||
case 0x00000000 ... 0x007FFFFF:
|
||||
util::WriteAccess<u32>(mmio.rdp.rdram.data(), paddr, val);
|
||||
break;
|
||||
case 0x04000000 ... 0x0403FFFF:
|
||||
if(paddr & 0x1000)
|
||||
util::WriteAccess<u32>(mmio.rsp.imem, paddr & IMEM_DSIZE, val);
|
||||
else
|
||||
util::WriteAccess<u32>(mmio.rsp.dmem, paddr & DMEM_DSIZE, val);
|
||||
break;
|
||||
case 0x04040000 ... 0x040FFFFF: case 0x04100000 ... 0x041FFFFF:
|
||||
case 0x04300000 ... 0x044FFFFF: case 0x04500000 ... 0x048FFFFF: mmio.Write(*this, regs, paddr, val); break;
|
||||
case 0x10000000 ... 0x13FF0013: break;
|
||||
case 0x13FF0014: {
|
||||
if(val < ISVIEWER_SIZE) {
|
||||
char* message = (char*)calloc(val + 1, 1);
|
||||
memcpy(message, isviewer, val);
|
||||
fmt::print("{}", message);
|
||||
free(message);
|
||||
}
|
||||
} break;
|
||||
case 0x13FF0020 ... 0x13FFFFFF:
|
||||
util::WriteAccess<u32>(isviewer, paddr - 0x13FF0020, htobe32(val));
|
||||
break;
|
||||
case 0x1FC007C0 ... 0x1FC007FF:
|
||||
util::WriteAccess<u32>(pifRam, paddr - 0x1FC007C0, htobe32(val));
|
||||
ProcessPIFCommands(pifRam, mmio.si.controller, *this);
|
||||
break;
|
||||
case 0x00800000 ... 0x03FFFFFF: case 0x04200000 ... 0x042FFFFF:
|
||||
case 0x08000000 ... 0x0FFFFFFF: case 0x04900000 ... 0x07FFFFFF:
|
||||
case 0x1FC00800 ... 0x7FFFFFFF: case 0x80000000 ... 0xFFFFFFFF: break;
|
||||
default: util::panic("Unimplemented 32-bit write at address {:08X} with value {:0X} (PC = {:016X})\n", paddr, val, (u64)regs.pc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -335,28 +455,49 @@ void Mem::Write64(Registers& regs, u64 vaddr, u64 val, s64 pc) {
|
||||
FireException(regs, GetTLBExceptionCode(regs.cop0.tlbError, STORE), 0, pc);
|
||||
}
|
||||
|
||||
switch(paddr) {
|
||||
case 0x00000000 ... 0x007FFFFF:
|
||||
util::WriteAccess<u64>(mmio.rdp.dram.data(), paddr, val);
|
||||
break;
|
||||
case 0x04000000 ... 0x0403FFFF:
|
||||
const auto page = paddr >> 12;
|
||||
auto offset = paddr & 0xFFF;
|
||||
const auto pointer = readPages[page];
|
||||
|
||||
if(pointer) {
|
||||
if(paddr >= 0x04000000 && paddr <= 0x0403FFFF) {
|
||||
val >>= 32;
|
||||
if(paddr & 0x1000)
|
||||
util::WriteAccess<u32>(mmio.rsp.imem, paddr & IMEM_DSIZE, val);
|
||||
else
|
||||
util::WriteAccess<u32>(mmio.rsp.dmem, paddr & DMEM_DSIZE, val);
|
||||
break;
|
||||
case 0x04040000 ... 0x040FFFFF: case 0x04100000 ... 0x041FFFFF:
|
||||
case 0x04300000 ... 0x044FFFFF: case 0x04500000 ... 0x048FFFFF: util::panic("MMIO Write64!\n");
|
||||
case 0x10000000 ... 0x13FFFFFF: break;
|
||||
case 0x1FC007C0 ... 0x1FC007FF:
|
||||
util::WriteAccess<u64>(pifRam, paddr - 0x1FC007C0, htobe64(val));
|
||||
ProcessPIFCommands(pifRam, mmio.si.controller, *this);
|
||||
break;
|
||||
case 0x00800000 ... 0x03FFFFFF: case 0x04200000 ... 0x042FFFFF:
|
||||
case 0x08000000 ... 0x0FFFFFFF: case 0x04900000 ... 0x07FFFFFF:
|
||||
case 0x1FC00800 ... 0x7FFFFFFF: case 0x80000000 ... 0xFFFFFFFF: break;
|
||||
default: util::panic("Unimplemented 64-bit write at address {:08X} with value {:0X} (PC = {:016X})\n", paddr, val, (u64)regs.pc);
|
||||
}
|
||||
util::WriteAccess<u64>((u8*)pointer, offset, val);
|
||||
} else {
|
||||
switch (paddr) {
|
||||
case 0x00000000 ... 0x007FFFFF:
|
||||
util::WriteAccess<u64>(mmio.rdp.rdram.data(), paddr, val);
|
||||
break;
|
||||
case 0x04000000 ... 0x0403FFFF:
|
||||
val >>= 32;
|
||||
if (paddr & 0x1000)
|
||||
util::WriteAccess<u32>(mmio.rsp.imem, paddr & IMEM_DSIZE, val);
|
||||
else
|
||||
util::WriteAccess<u32>(mmio.rsp.dmem, paddr & DMEM_DSIZE, val);
|
||||
break;
|
||||
case 0x04040000 ... 0x040FFFFF:
|
||||
case 0x04100000 ... 0x041FFFFF:
|
||||
case 0x04300000 ... 0x044FFFFF:
|
||||
case 0x04500000 ... 0x048FFFFF:
|
||||
util::panic("MMIO Write64!\n");
|
||||
case 0x10000000 ... 0x13FFFFFF:
|
||||
break;
|
||||
case 0x1FC007C0 ... 0x1FC007FF:
|
||||
util::WriteAccess<u64>(pifRam, paddr - 0x1FC007C0, htobe64(val));
|
||||
ProcessPIFCommands(pifRam, mmio.si.controller, *this);
|
||||
break;
|
||||
case 0x00800000 ... 0x03FFFFFF:
|
||||
case 0x04200000 ... 0x042FFFFF:
|
||||
case 0x08000000 ... 0x0FFFFFFF:
|
||||
case 0x04900000 ... 0x07FFFFFF:
|
||||
case 0x1FC00800 ... 0x7FFFFFFF:
|
||||
case 0x80000000 ... 0xFFFFFFFF:
|
||||
break;
|
||||
default:
|
||||
util::panic("Unimplemented 64-bit write at address {:08X} with value {:0X} (PC = {:016X})\n", paddr, val,
|
||||
(u64) regs.pc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ struct Mem {
|
||||
void Reset();
|
||||
CartInfo LoadROM(const std::string&);
|
||||
[[nodiscard]] auto GetRDRAM() -> u8* {
|
||||
return mmio.rdp.dram.data();
|
||||
return mmio.rdp.rdram.data();
|
||||
}
|
||||
|
||||
template <bool tlb = true>
|
||||
@@ -46,7 +46,7 @@ struct Mem {
|
||||
inline void DumpRDRAM() const {
|
||||
FILE *fp = fopen("rdram.dump", "wb");
|
||||
u8 *temp = (u8*)calloc(RDRAM_SIZE, 1);
|
||||
memcpy(temp, mmio.rdp.dram.data(), RDRAM_SIZE);
|
||||
memcpy(temp, mmio.rdp.rdram.data(), RDRAM_SIZE);
|
||||
util::SwapBuffer32(RDRAM_SIZE, temp);
|
||||
fwrite(temp, 1, RDRAM_SIZE, fp);
|
||||
free(temp);
|
||||
@@ -72,6 +72,7 @@ struct Mem {
|
||||
free(temp);
|
||||
fclose(fp);
|
||||
}
|
||||
std::vector<uintptr_t> writePages, readPages;
|
||||
private:
|
||||
friend struct SI;
|
||||
friend struct PI;
|
||||
|
||||
@@ -11,8 +11,8 @@ RDP::RDP() {
|
||||
|
||||
void RDP::Reset() {
|
||||
dpc.status.raw = 0x80;
|
||||
dram.resize(RDRAM_SIZE);
|
||||
std::fill(dram.begin(), dram.end(), 0);
|
||||
rdram.resize(RDRAM_SIZE);
|
||||
std::fill(rdram.begin(), rdram.end(), 0);
|
||||
memset(cmd_buf, 0, 0x100000);
|
||||
}
|
||||
|
||||
@@ -144,7 +144,7 @@ void RDP::RunCommand(MI& mi, Registers& regs, RSP& rsp) {
|
||||
return;
|
||||
}
|
||||
for (int i = 0; i < len; i += 4) {
|
||||
u32 cmd = util::ReadAccess<u32>(dram.data(), current + i);
|
||||
u32 cmd = util::ReadAccess<u32>(rdram.data(), current + i);
|
||||
cmd_buf[remaining_cmds + (i >> 2)] = cmd;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,7 +57,7 @@ struct RDP {
|
||||
RDP();
|
||||
void Reset();
|
||||
|
||||
std::vector<u8> dram;
|
||||
std::vector<u8> rdram;
|
||||
[[nodiscard]] auto Read(u32 addr) const -> u32;
|
||||
void Write(MI& mi, Registers& regs, RSP& rsp, u32 addr, u32 val);
|
||||
void WriteStatus(MI& mi, Registers& regs, RSP& rsp, u32 val);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include <n64/core/RSP.hpp>
|
||||
#include <util.hpp>
|
||||
#include <n64/core/Mem.hpp>
|
||||
#include <n64/core/cpu/Registers.hpp>
|
||||
|
||||
namespace n64 {
|
||||
RSP::RSP() {
|
||||
@@ -90,6 +91,32 @@ auto RSP::Read(u32 addr) -> u32{
|
||||
}
|
||||
}
|
||||
|
||||
void RSP::WriteStatus(MI& mi, Registers& regs, u32 value) {
|
||||
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)
|
||||
InterruptLower(mi, regs, Interrupt::SP);
|
||||
if(write.setIntr && !write.clearIntr)
|
||||
InterruptRaise(mi, regs, Interrupt::SP);
|
||||
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);
|
||||
}
|
||||
|
||||
void RSP::Write(Mem& mem, Registers& regs, u32 addr, u32 value) {
|
||||
MI& mi = mem.mmio.mi;
|
||||
switch (addr) {
|
||||
|
||||
@@ -194,31 +194,6 @@ struct RSP {
|
||||
return value;
|
||||
}
|
||||
|
||||
inline void WriteStatus(MI& mi, Registers& regs, u32 value) {
|
||||
auto write = SPStatusWrite{.raw = value};
|
||||
if(write.clearHalt && !write.setHalt) {
|
||||
spStatus.halt = false;
|
||||
}
|
||||
if(write.setHalt && !write.clearHalt) {
|
||||
spStatus.halt = true;
|
||||
}
|
||||
if(write.clearBroke) spStatus.broke = false;
|
||||
if(write.clearIntr && !write.setIntr)
|
||||
InterruptLower(mi, regs, Interrupt::SP);
|
||||
if(write.setIntr && !write.clearIntr)
|
||||
InterruptRaise(mi, regs, Interrupt::SP);
|
||||
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);
|
||||
}
|
||||
|
||||
inline u32 ReadWord(u32 addr) {
|
||||
addr &= 0xfff;
|
||||
return GET_RSP_WORD(addr);
|
||||
@@ -400,6 +375,8 @@ struct RSP {
|
||||
rsp.lastSuccessfulDRAMAddr.address = dram_address;
|
||||
rsp.spDMALen.raw = 0xFF8 | (rsp.spDMALen.skip << 20);
|
||||
}
|
||||
|
||||
void WriteStatus(MI& mi, Registers& regs, u32 value);
|
||||
private:
|
||||
inline void branch(u16 address, bool cond) {
|
||||
if(cond) {
|
||||
|
||||
@@ -90,6 +90,23 @@ void Interpreter::regimm(u32 instr) {
|
||||
}
|
||||
}
|
||||
|
||||
void Interpreter::cop2Decode(u32 instr) {
|
||||
if(!regs.cop0.status.cu2) {
|
||||
FireException(regs, ExceptionCode::CoprocessorUnusable, 2, regs.oldPC);
|
||||
return;
|
||||
}
|
||||
switch(RS(instr)) {
|
||||
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:
|
||||
FireException(regs, ExceptionCode::ReservedInstruction, 2, regs.oldPC);
|
||||
}
|
||||
}
|
||||
|
||||
void Interpreter::Exec(Mem& mem, u32 instr) {
|
||||
u8 mask = (instr >> 26) & 0x3f;
|
||||
// 00rr_rccc
|
||||
@@ -115,9 +132,7 @@ void Interpreter::Exec(Mem& mem, u32 instr) {
|
||||
case 0x0F: lui(instr); break;
|
||||
case 0x10: regs.cop0.decode(regs, mem, instr); break;
|
||||
case 0x11: regs.cop1.decode(*this, instr); break;
|
||||
case 0x12:
|
||||
FireException(regs, ExceptionCode::CoprocessorUnusable, 2, regs.oldPC);
|
||||
break;
|
||||
case 0x12: cop2Decode(instr); break;
|
||||
case 0x14: bl(instr, regs.gpr[RS(instr)] == regs.gpr[RT(instr)]); break;
|
||||
case 0x15: bl(instr, regs.gpr[RS(instr)] != regs.gpr[RT(instr)]); break;
|
||||
case 0x16: bl(instr, regs.gpr[RS(instr)] <= 0); break;
|
||||
|
||||
@@ -777,4 +777,30 @@ void Interpreter::trap(bool cond) {
|
||||
FireException(regs, ExceptionCode::Trap, 0, regs.oldPC);
|
||||
}
|
||||
}
|
||||
|
||||
void Cpu::mtc2(u32 instr) {
|
||||
cop2Latch = regs.gpr[RT(instr)];
|
||||
}
|
||||
|
||||
void Cpu::mfc2(u32 instr) {
|
||||
s32 value = cop2Latch;
|
||||
regs.gpr[RT(instr)] = value;
|
||||
}
|
||||
|
||||
void Cpu::dmtc2(u32 instr) {
|
||||
cop2Latch = regs.gpr[RT(instr)];
|
||||
}
|
||||
|
||||
void Cpu::dmfc2(u32 instr) {
|
||||
regs.gpr[RT(instr)] = cop2Latch;
|
||||
}
|
||||
|
||||
void Cpu::ctc2(u32) {
|
||||
|
||||
}
|
||||
|
||||
void Cpu::cfc2(u32) {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -44,7 +44,7 @@ void AI::Write(Mem& mem, Registers& regs, u32 addr, u32 val) {
|
||||
case 0x04500004: {
|
||||
u32 len = (val & 0x3FFFF) & ~7;
|
||||
if((dmaCount < 2) && len) {
|
||||
if(dmaCount == 0) InterruptRaise(mem.mmio.mi, regs, Interrupt::AI);
|
||||
// if(dmaCount == 0) InterruptRaise(mem.mmio.mi, regs, Interrupt::AI);
|
||||
dmaLen[dmaCount] = len;
|
||||
dmaCount++;
|
||||
}
|
||||
@@ -81,17 +81,19 @@ void AI::Step(Mem& mem, Registers& regs, int cpuCycles, float volumeL, float vol
|
||||
}
|
||||
|
||||
if(dmaLen[0] && dmaEnable) {
|
||||
u32 addrHi = ((dmaAddr[0] >> 13) + dmaAddrCarry) & 0x7FF;
|
||||
dmaAddr[0] = (addrHi << 13) | (dmaAddr[0] & 0x1FFF);
|
||||
u32 data = util::ReadAccess<u32>(mem.mmio.rdp.dram.data(), dmaAddr[0] & RDRAM_DSIZE);
|
||||
s16 l = s16(data >> 16);
|
||||
s16 r = s16(data);
|
||||
if(volumeR > 0 && volumeL > 0) {
|
||||
u32 addrHi = ((dmaAddr[0] >> 13) + dmaAddrCarry) & 0x7FF;
|
||||
dmaAddr[0] = (addrHi << 13) | (dmaAddr[0] & 0x1FFF);
|
||||
u32 data = util::ReadAccess<u32>(mem.mmio.rdp.rdram.data(), dmaAddr[0] & RDRAM_DSIZE);
|
||||
s16 l = s16(data >> 16);
|
||||
s16 r = s16(data);
|
||||
|
||||
PushSample((float)l / INT16_MAX, volumeL, (float)r / INT16_MAX, volumeR);
|
||||
PushSample((float) l / INT16_MAX, volumeL, (float) r / INT16_MAX, volumeR);
|
||||
|
||||
u32 addrLo = (dmaAddr[0] + 4) & 0x1FFF;
|
||||
dmaAddr[0] = (dmaAddr[0] & ~0x1FFF) | addrLo;
|
||||
dmaAddrCarry = addrLo == 0;
|
||||
u32 addrLo = (dmaAddr[0] + 4) & 0x1FFF;
|
||||
dmaAddr[0] = (dmaAddr[0] & ~0x1FFF) | addrLo;
|
||||
dmaAddrCarry = addrLo == 0;
|
||||
}
|
||||
dmaLen[0] -= 4;
|
||||
}
|
||||
|
||||
|
||||
@@ -52,7 +52,7 @@ void PI::Write(Mem& mem, Registers& regs, u32 addr, u32 val) {
|
||||
}
|
||||
rdLen = len;
|
||||
for(int i = 0; i < len; i++) {
|
||||
mem.cart[BYTE_ADDRESS(cart_addr + i) & mem.romMask] = mem.mmio.rdp.dram[BYTE_ADDRESS(dram_addr + i) & RDRAM_DSIZE];
|
||||
mem.cart[BYTE_ADDRESS(cart_addr + i) & mem.romMask] = mem.mmio.rdp.rdram[BYTE_ADDRESS(dram_addr + i) & RDRAM_DSIZE];
|
||||
}
|
||||
dramAddr = dram_addr + len;
|
||||
cartAddr = cart_addr + len;
|
||||
@@ -68,7 +68,7 @@ void PI::Write(Mem& mem, Registers& regs, u32 addr, u32 val) {
|
||||
}
|
||||
wrLen = len;
|
||||
for(int i = 0; i < len; i++) {
|
||||
mem.mmio.rdp.dram[BYTE_ADDRESS(dram_addr + i) & RDRAM_DSIZE] = mem.cart[BYTE_ADDRESS(cart_addr + i) & mem.romMask];
|
||||
mem.mmio.rdp.rdram[BYTE_ADDRESS(dram_addr + i) & RDRAM_DSIZE] = mem.cart[BYTE_ADDRESS(cart_addr + i) & mem.romMask];
|
||||
}
|
||||
dramAddr = dram_addr + len;
|
||||
cartAddr = cart_addr + len;
|
||||
|
||||
@@ -36,17 +36,14 @@ void DMA(Mem& mem, Registers& regs) {
|
||||
MMIO& mmio = mem.mmio;
|
||||
SI& si = mmio.si;
|
||||
si.status.dmaBusy = false;
|
||||
if(si.status.dmaBusy) {
|
||||
return;
|
||||
}
|
||||
if(si.toDram) {
|
||||
ProcessPIFCommands(mem.pifRam, si.controller, mem);
|
||||
for(int i = 0; i < 64; i++) {
|
||||
mem.mmio.rdp.dram[BYTE_ADDRESS(si.dramAddr + i)] = mem.pifRam[i];
|
||||
mem.mmio.rdp.rdram[BYTE_ADDRESS(si.dramAddr + i)] = mem.pifRam[i];
|
||||
}
|
||||
} else {
|
||||
for(int i = 0; i < 64; i++) {
|
||||
mem.pifRam[i] = mem.mmio.rdp.dram[BYTE_ADDRESS(si.dramAddr + i)];
|
||||
mem.pifRam[i] = mem.mmio.rdp.rdram[BYTE_ADDRESS(si.dramAddr + i)];
|
||||
}
|
||||
util::logdebug("SI DMA from PIF RAM to RDRAM ({:08X} to {:08X})\n", si.pifAddr, si.dramAddr);
|
||||
ProcessPIFCommands(mem.pifRam, si.controller, mem);
|
||||
|
||||
@@ -9,7 +9,7 @@ Cop1::Cop1() {
|
||||
}
|
||||
|
||||
void Cop1::Reset() {
|
||||
fcr0 = 0xa00;
|
||||
fcr0 = 0;
|
||||
fcr31.raw = 0;
|
||||
memset(fgr, 0, 32 * sizeof(FGR));
|
||||
}
|
||||
|
||||
@@ -13,5 +13,6 @@ struct Registers {
|
||||
s64 oldPC, pc, nextPC;
|
||||
s64 hi, lo;
|
||||
bool prevDelaySlot, delaySlot;
|
||||
int steps = 0;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -215,7 +215,7 @@ void RSP::Exec(Registers ®s, Mem& mem, u32 instr) {
|
||||
fwrite(temp, 1, IMEM_SIZE, fp);
|
||||
free(temp);
|
||||
fclose(fp);
|
||||
util::panic("Unhandled RSP instruction ({:06b})\n", mask);
|
||||
util::panic("Unhandled RSP instruction ({:06b}, {:04X})\n", mask, oldPC);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -29,4 +29,20 @@
|
||||
#define SRAM_REGION 0x08000000 ... 0x0FFFFFFF
|
||||
#define CART_REGION 0x10000000 ... 0x1FBFFFFF
|
||||
#define PIF_ROM_REGION 0x1FC00000 ... 0x1FC007BF
|
||||
#define PIF_RAM_REGION 0x1FC007C0 ... 0x1FC007FF
|
||||
#define PIF_RAM_REGION 0x1FC007C0 ... 0x1FC007FF
|
||||
|
||||
constexpr size_t operator""_kb(unsigned long long int x) {
|
||||
return 1024ULL * x;
|
||||
}
|
||||
|
||||
constexpr size_t operator""_mb(unsigned long long int x) {
|
||||
return 1024_kb * x;
|
||||
}
|
||||
|
||||
constexpr size_t operator""_gb(unsigned long long int x) {
|
||||
return 1024_mb * x;
|
||||
}
|
||||
|
||||
#define ADDRESS_RANGE_SIZE 4_gb
|
||||
#define PAGE_SIZE 4_kb
|
||||
#define PAGE_COUNT ((ADDRESS_RANGE_SIZE) / (PAGE_SIZE))
|
||||
39
src/util.hpp
39
src/util.hpp
@@ -8,6 +8,7 @@
|
||||
#include <array>
|
||||
#include <chrono>
|
||||
#include <functional>
|
||||
#include <discord_rpc.h>
|
||||
|
||||
namespace util {
|
||||
using SteadyClock = std::chrono::steady_clock;
|
||||
@@ -20,7 +21,7 @@ enum MessageType : u8 {
|
||||
|
||||
template <MessageType messageType = Info, typename ...Args>
|
||||
constexpr void print(const std::string& fmt, Args... args) {
|
||||
#ifndef __WIN32
|
||||
#ifndef _WIN32
|
||||
if constexpr(messageType == Error) {
|
||||
fmt::print(fmt::emphasis::bold | fg(fmt::color::red), fmt, args...);
|
||||
exit(-1);
|
||||
@@ -265,4 +266,40 @@ inline auto ReadFileBinary(const std::string& path, u32** buf) {
|
||||
file.close();
|
||||
return size;
|
||||
}
|
||||
|
||||
enum State {
|
||||
Idling,
|
||||
Playing,
|
||||
Paused
|
||||
};
|
||||
|
||||
inline void UpdateRPC(State state, const std::string& game = "") {
|
||||
DiscordRichPresence presence{};
|
||||
std::string textState, textDetails;
|
||||
|
||||
switch(state) {
|
||||
case Idling:
|
||||
textDetails = "Idling";
|
||||
break;
|
||||
case Playing:
|
||||
textDetails = "In-game";
|
||||
textState = "Playing \"" + game + "\"";
|
||||
break;
|
||||
case Paused:
|
||||
textDetails = "In-game";
|
||||
textState = "Playing \"" + game + "\" (Paused)";
|
||||
break;
|
||||
}
|
||||
|
||||
presence.details = textDetails.c_str();
|
||||
presence.state = textState.c_str();
|
||||
presence.startTimestamp = time(nullptr);
|
||||
presence.largeImageText = "Gadolinium";
|
||||
presence.largeImageKey = "logo";
|
||||
Discord_UpdatePresence(&presence);
|
||||
}
|
||||
|
||||
inline void ClearRPC() {
|
||||
Discord_ClearPresence();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user