From a8fda9770ccd485dcdd42c1d5cebc2d58487ddde Mon Sep 17 00:00:00 2001 From: CocoSimone Date: Tue, 18 Oct 2022 20:46:45 +0200 Subject: [PATCH] Delay SI DMAs, implement TAS movie replay (WIP), and fix DIV --- src/CMakeLists.txt | 4 + src/Scheduler.cpp | 20 ++++ src/Scheduler.hpp | 24 +++++ src/common.hpp | 2 + src/frontend/App.cpp | 5 +- src/main.cpp | 4 + src/n64/Core.cpp | 84 +++++++++------- src/n64/core/Cpu.cpp | 4 - src/n64/core/Cpu.hpp | 2 +- src/n64/core/MMIO.cpp | 4 +- src/n64/core/MMIO.hpp | 2 +- src/n64/core/Mem.cpp | 8 +- src/n64/core/Mem.hpp | 1 + src/n64/core/cpu/decode.cpp | 2 +- src/n64/core/cpu/instructions.cpp | 10 +- src/n64/core/mmio/PIF.cpp | 9 +- src/n64/core/mmio/PIF.hpp | 35 +++++-- src/n64/core/mmio/SI.cpp | 44 ++++---- src/n64/core/mmio/SI.hpp | 7 +- src/n64/core/rsp/decode.cpp | 2 +- src/n64/core/rsp/instructions.cpp | 2 + src/n64/m64.cpp | 162 ++++++++++++++++++++++++++++++ src/n64/m64.hpp | 7 ++ 23 files changed, 359 insertions(+), 85 deletions(-) create mode 100644 src/Scheduler.cpp create mode 100644 src/Scheduler.hpp create mode 100644 src/n64/m64.cpp create mode 100644 src/n64/m64.hpp diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 6bf1e814..f7866255 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -23,6 +23,10 @@ add_executable(natsukashii ${FRONTEND_SOURCES} ${FRONTEND_HEADERS} main.cpp + Scheduler.cpp + Scheduler.hpp + common.hpp + util.hpp ) target_include_directories(natsukashii PRIVATE diff --git a/src/Scheduler.cpp b/src/Scheduler.cpp new file mode 100644 index 00000000..4f53e42a --- /dev/null +++ b/src/Scheduler.cpp @@ -0,0 +1,20 @@ +#include +#include +#include +#include + +namespace n64 { +void Scheduler::enqueue(const Event &event) { + events.push_back({event.time + ticks, event.func}); +} + +void Scheduler::handleEvents(u64 tick, Mem &mem, Registers ®s) { + ticks += tick; + for (int i = 0; i < events.size(); i++) { + if (ticks >= events[i].time) { + events[i].func(mem, regs); + events.erase(events.begin() + i); + } + } +} +} \ No newline at end of file diff --git a/src/Scheduler.hpp b/src/Scheduler.hpp new file mode 100644 index 00000000..89bf7aac --- /dev/null +++ b/src/Scheduler.hpp @@ -0,0 +1,24 @@ +#pragma once +#include +#include + +namespace n64 { +struct Mem; +struct Registers; + +struct Event { + u64 time; + void (*func)(Mem&, Registers& regs); +}; + +enum { + SI_DMA_COMPLETE +}; + +struct Scheduler { + void enqueue(const Event&); + void handleEvents(u64 tick, Mem& mem, Registers& regs); + u64 ticks = 0; + std::vector events; +}; +} diff --git a/src/common.hpp b/src/common.hpp index db1e052e..d936ba38 100644 --- a/src/common.hpp +++ b/src/common.hpp @@ -38,6 +38,8 @@ using s128 = __int128_t; #define ELEMENT_INDEX(i) (7 - (i)) #define BYTE_INDEX(i) (15 - (i)) +#define SI_DMA_DELAY (65536 * 2) + enum TLBAccessType { LOAD, STORE diff --git a/src/frontend/App.cpp b/src/frontend/App.cpp index 31f8f240..988521f1 100644 --- a/src/frontend/App.cpp +++ b/src/frontend/App.cpp @@ -1,13 +1,16 @@ #include #include #include +#include "m64.hpp" void App::Run() { // Main loop const u8* state = SDL_GetKeyboardState(nullptr); SDL_EventState(SDL_DROPFILE, SDL_ENABLE); + static int count = 0; while (!core.done) { core.Run(window, window.volumeL, window.volumeR); + core.UpdateController(state); SDL_Event event; while (SDL_PollEvent(&event)) { @@ -48,8 +51,6 @@ void App::Run() { } } break; } - - core.UpdateController(state); } } } diff --git a/src/main.cpp b/src/main.cpp index d72f8d78..82f34ca0 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,4 +1,5 @@ #include +#include "m64.hpp" #ifdef _WIN32 #define main SDL_main @@ -7,6 +8,9 @@ int main(int argc, char** argv) { App* app = new App; if(argc > 1) { + if(argc > 2) { + LoadTAS(argv[2]); + } app->LoadROM(argv[1]); } diff --git a/src/n64/Core.cpp b/src/n64/Core.cpp index e76a4427..35a8bb7f 100644 --- a/src/n64/Core.cpp +++ b/src/n64/Core.cpp @@ -2,6 +2,7 @@ #include #include #include +#include "m64.hpp" namespace n64 { Core::Core() { @@ -61,6 +62,7 @@ void Core::Run(Window& window, float volumeL, float volumeR) { } mmio.ai.Step(mem, cpu.regs, 1, volumeL, volumeR); + mem.scheduler.handleEvents(1, mem, cpu.regs); } cycles -= mmio.vi.cyclesPerHalfline; @@ -87,7 +89,6 @@ void Core::Run(Window& window, float volumeL, float volumeR) { void Core::UpdateController(const u8* state) { Controller &controller = mem.mmio.si.controller; - controller.raw = 0; s8 xaxis = 0, yaxis = 0; if(gamepadConnected) { @@ -106,42 +107,49 @@ void Core::UpdateController(const u8* state) { bool CLEFT = GET_AXIS(gamepad, SDL_CONTROLLER_AXIS_RIGHTX) <= -128; bool CRIGHT = GET_AXIS(gamepad, SDL_CONTROLLER_AXIS_RIGHTX) >= 127; - controller.b1 = (A << 7) | (B << 6) | (Z << 5) | (START << 4) | - (DUP << 3) | (DDOWN << 2) | (DLEFT << 1) | DRIGHT; - - controller.b2 = ((START && L && R) << 7) | (0 << 6) | (L << 5) | (R << 4) | - (CUP << 3) | (CDOWN << 2) | (CLEFT << 1) | CRIGHT; + controller.a = A; + controller.b = B; + controller.z = Z; + controller.start = START; + controller.dp_up = DUP; + controller.dp_down = DDOWN; + controller.dp_left = DLEFT; + controller.dp_right = DRIGHT; + controller.joy_reset = L && R && START; + controller.l = L; + controller.r = R; + controller.c_up = CUP; + controller.c_down = CDOWN; + controller.c_left = CLEFT; + controller.c_right = CRIGHT; xaxis = (s8) std::clamp((GET_AXIS(gamepad, SDL_CONTROLLER_AXIS_LEFTX) >> 8), -86, 86); yaxis = (s8) std::clamp(-(GET_AXIS(gamepad, SDL_CONTROLLER_AXIS_LEFTY) >> 8), -86, 86); - controller.b3 = xaxis; - controller.b4 = yaxis; + controller.joy_x = xaxis; + controller.joy_y = yaxis; - if ((controller.b2 >> 7) & 1) { - controller.b1 &= ~0x10; - controller.b3 = 0; - controller.b4 = 0; + if (controller.joy_reset) { + controller.start = false; + controller.joy_x = 0; + controller.joy_y = 0; } } else { - controller.b1 = - (state[SDL_SCANCODE_X] << 7) | - (state[SDL_SCANCODE_C] << 6) | - (state[SDL_SCANCODE_Z] << 5) | - (state[SDL_SCANCODE_RETURN] << 4) | - (state[SDL_SCANCODE_KP_8] << 3) | - (state[SDL_SCANCODE_KP_5] << 2) | - (state[SDL_SCANCODE_KP_4] << 1) | - (state[SDL_SCANCODE_KP_6]); - controller.b2 = - ((state[SDL_SCANCODE_RETURN] && state[SDL_SCANCODE_A] && state[SDL_SCANCODE_S]) << 7) | - (0 << 6) | - (state[SDL_SCANCODE_A] << 5) | - (state[SDL_SCANCODE_S] << 4) | - (state[SDL_SCANCODE_I] << 3) | - (state[SDL_SCANCODE_J] << 2) | - (state[SDL_SCANCODE_K] << 1) | - (state[SDL_SCANCODE_L]); + controller.a = state[SDL_SCANCODE_X]; + controller.b = state[SDL_SCANCODE_C]; + controller.z = state[SDL_SCANCODE_Z]; + controller.start = state[SDL_SCANCODE_RETURN]; + controller.dp_up = state[SDL_SCANCODE_KP_8]; + controller.dp_down = state[SDL_SCANCODE_KP_5]; + controller.dp_left = state[SDL_SCANCODE_KP_4]; + controller.dp_right = state[SDL_SCANCODE_KP_6]; + controller.joy_reset = state[SDL_SCANCODE_RETURN] && state[SDL_SCANCODE_A] && state[SDL_SCANCODE_S]; + controller.l = state[SDL_SCANCODE_A]; + controller.r = state[SDL_SCANCODE_S]; + controller.c_up = state[SDL_SCANCODE_I]; + controller.c_down = state[SDL_SCANCODE_J]; + controller.c_left = state[SDL_SCANCODE_K]; + controller.c_right = state[SDL_SCANCODE_L]; if (state[SDL_SCANCODE_LEFT]) { xaxis = -86; @@ -155,14 +163,18 @@ void Core::UpdateController(const u8* state) { yaxis = 86; } - controller.b3 = xaxis; - controller.b4 = yaxis; + controller.joy_x = xaxis; + controller.joy_y = yaxis; - if ((controller.b2 >> 7) & 1) { - controller.b1 &= ~0x10; - controller.b3 = 0; - controller.b4 = 0; + if (controller.joy_reset) { + controller.start = false; + controller.joy_x = 0; + controller.joy_y = 0; } } + + if(tas_movie_loaded()) { + controller = tas_next_inputs(); + } } } diff --git a/src/n64/core/Cpu.cpp b/src/n64/core/Cpu.cpp index 3ed8c9c6..a18cb247 100644 --- a/src/n64/core/Cpu.cpp +++ b/src/n64/core/Cpu.cpp @@ -27,10 +27,6 @@ inline void CheckCompareInterrupt(MI& mi, Registers& regs) { } } -inline void HandleInterrupt(Registers& regs) { - -} - inline void Cpu::disassembly(u32 instr) { size_t count; cs_insn *insn; diff --git a/src/n64/core/Cpu.hpp b/src/n64/core/Cpu.hpp index a5ec8519..cf947175 100644 --- a/src/n64/core/Cpu.hpp +++ b/src/n64/core/Cpu.hpp @@ -47,7 +47,7 @@ private: void daddiu(u32); void ddiv(u32); void ddivu(u32); - void div_(u32); + void div(u32); void divu(u32); void dmult(u32); void dmultu(u32); diff --git a/src/n64/core/MMIO.cpp b/src/n64/core/MMIO.cpp index 2563e0e8..9ebbc9b2 100644 --- a/src/n64/core/MMIO.cpp +++ b/src/n64/core/MMIO.cpp @@ -34,7 +34,7 @@ u32 MMIO::Read(u32 addr) { } } -void MMIO::Write(Mem& mem, Registers& regs, u32 addr, u32 val) { +void MMIO::Write(Scheduler& scheduler, Mem& mem, Registers& regs, u32 addr, u32 val) { switch (addr) { case 0x04040000 ... 0x040FFFFF: rsp.Write(mem, regs, addr, val); break; case 0x04100000 ... 0x041FFFFF: rdp.Write(mi, regs, rsp, addr, val); break; @@ -43,7 +43,7 @@ void MMIO::Write(Mem& mem, Registers& regs, u32 addr, u32 val) { case 0x04500000 ... 0x045FFFFF: ai.Write(mem, regs, addr, val); break; case 0x04600000 ... 0x046FFFFF: pi.Write(mem, regs, addr, val); break; case 0x04700000 ... 0x047FFFFF: ri.Write(addr, val); break; - case 0x04800000 ... 0x048FFFFF: si.Write(mem, regs, addr, val); break; + case 0x04800000 ... 0x048FFFFF: si.Write(scheduler, mem, regs, addr, val); break; default: util::panic("Unhandled mmio write at addr {:08X} with val {:08X}\n", addr, val); } diff --git a/src/n64/core/MMIO.hpp b/src/n64/core/MMIO.hpp index f102b324..c8d20dbf 100644 --- a/src/n64/core/MMIO.hpp +++ b/src/n64/core/MMIO.hpp @@ -25,6 +25,6 @@ struct MMIO { RDP rdp; u32 Read(u32); - void Write(Mem&, Registers&, u32, u32); + void Write(Scheduler&, Mem&, Registers&, u32, u32); }; } diff --git a/src/n64/core/Mem.cpp b/src/n64/core/Mem.cpp index 71c7db11..e213afef 100644 --- a/src/n64/core/Mem.cpp +++ b/src/n64/core/Mem.cpp @@ -228,7 +228,7 @@ void Mem::Write8(Registers& regs, u64 vaddr, u32 val, s64 pc) { util::WriteAccess(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 0x04300000 ... 0x044FFFFF: case 0x04500000 ... 0x048FFFFF: mmio.Write(scheduler, *this, regs, paddr, val); break; case 0x10000000 ... 0x13FFFFFF: break; case 0x1FC007C0 ... 0x1FC007FF: val = val << (8 * (3 - (paddr & 3))); @@ -265,7 +265,7 @@ void Mem::Write16(Registers& regs, u64 vaddr, u32 val, s64 pc) { util::WriteAccess(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 0x04300000 ... 0x044FFFFF: case 0x04500000 ... 0x048FFFFF: mmio.Write(scheduler, *this, regs, paddr, val); break; case 0x10000000 ... 0x13FFFFFF: break; case 0x1FC007C0 ... 0x1FC007FF: val = val << (16 * !(paddr & 2)); @@ -299,7 +299,7 @@ void Mem::Write32(Registers& regs, u64 vaddr, u32 val, s64 pc) { util::WriteAccess(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 0x04300000 ... 0x044FFFFF: case 0x04500000 ... 0x048FFFFF: mmio.Write(scheduler, *this, regs, paddr, val); break; case 0x10000000 ... 0x13FF0013: break; case 0x13FF0014: { if(val < ISVIEWER_SIZE) { @@ -343,7 +343,7 @@ void Mem::Write64(Registers& regs, u64 vaddr, u64 val, s64 pc) { util::WriteAccess(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 0x04300000 ... 0x044FFFFF: case 0x04500000 ... 0x048FFFFF: mmio.Write(scheduler, *this, regs, paddr, val); break; case 0x10000000 ... 0x13FFFFFF: break; case 0x1FC007C0 ... 0x1FC007FF: util::WriteAccess(pifRam, paddr & PIF_RAM_DSIZE, htobe64(val)); diff --git a/src/n64/core/Mem.hpp b/src/n64/core/Mem.hpp index 10f1d0a5..ded7039b 100644 --- a/src/n64/core/Mem.hpp +++ b/src/n64/core/Mem.hpp @@ -42,6 +42,7 @@ struct Mem { MMIO mmio; u8 pifRam[PIF_RAM_SIZE]{}; + Scheduler scheduler; inline void DumpRDRAM() const { FILE *fp = fopen("rdram.dump", "wb"); diff --git a/src/n64/core/cpu/decode.cpp b/src/n64/core/cpu/decode.cpp index 6641edbd..577bf027 100644 --- a/src/n64/core/cpu/decode.cpp +++ b/src/n64/core/cpu/decode.cpp @@ -30,7 +30,7 @@ void Cpu::special(Mem& mem, u32 instr) { case 0x17: dsrav(instr); break; case 0x18: mult(instr); break; case 0x19: multu(instr); break; - case 0x1A: div_(instr); break; + case 0x1A: div(instr); break; case 0x1B: divu(instr); break; case 0x1C: dmult(instr); break; case 0x1D: dmultu(instr); break; diff --git a/src/n64/core/cpu/instructions.cpp b/src/n64/core/cpu/instructions.cpp index 8e386de0..b04f31c3 100644 --- a/src/n64/core/cpu/instructions.cpp +++ b/src/n64/core/cpu/instructions.cpp @@ -77,20 +77,20 @@ void Cpu::daddiu(u32 instr) { regs.gpr[RT(instr)] = rs + imm; } -void Cpu::div_(u32 instr) { +void Cpu::div(u32 instr) { s64 dividend = (s32)regs.gpr[RS(instr)]; s64 divisor = (s32)regs.gpr[RT(instr)]; if(divisor == 0) { regs.hi = dividend; if(dividend >= 0) { - regs.lo = -1; + regs.lo = s64(-1); } else { - regs.lo = 1; + regs.lo = s64(1); } } else { - s64 quotient = dividend / divisor; - s64 remainder = dividend % divisor; + s32 quotient = dividend / divisor; + s32 remainder = dividend % divisor; regs.lo = quotient; regs.hi = remainder; } diff --git a/src/n64/core/mmio/PIF.cpp b/src/n64/core/mmio/PIF.cpp index 95858897..668ba9b5 100644 --- a/src/n64/core/mmio/PIF.cpp +++ b/src/n64/core/mmio/PIF.cpp @@ -2,6 +2,7 @@ #include #include #include +#include "m64.hpp" namespace n64 { static int channel = 0; @@ -43,10 +44,10 @@ void ProcessPIFCommands(u8* pifRam, Controller& controller, Mem& mem) { res[2] = 0x01; break; case 1: - res[0] = controller.b1; - res[1] = controller.b2; - res[2] = controller.b3; - res[3] = controller.b4; + res[0] = controller.byte1; + res[1] = controller.byte2; + res[2] = controller.joy_x; + res[3] = controller.joy_y; break; case 2: case 3: res[0] = 0; break; default: util::panic("Unimplemented PIF command {}", cmd[2]); diff --git a/src/n64/core/mmio/PIF.hpp b/src/n64/core/mmio/PIF.hpp index 200492c7..0f0e5617 100644 --- a/src/n64/core/mmio/PIF.hpp +++ b/src/n64/core/mmio/PIF.hpp @@ -3,12 +3,35 @@ namespace n64 { -union Controller { - struct { - u8 b1, b2; - s8 b3, b4; - } __attribute__((__packed__)); - u32 raw; +struct Controller { + union { + u8 byte1; + struct { + bool dp_right:1; + bool dp_left:1; + bool dp_down:1; + bool dp_up:1; + bool start:1; + bool z:1; + bool b:1; + bool a:1; + }; + }; + union { + u8 byte2; + struct { + bool c_right:1; + bool c_left:1; + bool c_down:1; + bool c_up:1; + bool r:1; + bool l:1; + bool zero:1; + bool joy_reset:1; + }; + }; + s8 joy_x; + s8 joy_y; }; static_assert(sizeof(Controller) == 4); diff --git a/src/n64/core/mmio/SI.cpp b/src/n64/core/mmio/SI.cpp index a779a80a..a0074f66 100644 --- a/src/n64/core/mmio/SI.cpp +++ b/src/n64/core/mmio/SI.cpp @@ -9,7 +9,7 @@ SI::SI() { void SI::Reset() { status.raw = 0; dramAddr = 0; - controller.raw = 0; + memset(&controller, 0, sizeof(Controller)); } auto SI::Read(MI& mi, u32 addr) const -> u32 { @@ -21,7 +21,7 @@ auto SI::Read(MI& mi, u32 addr) const -> u32 { val |= status.dmaBusy; val |= (0 << 1); val |= (0 << 3); - val |= (status.intr << 12); + val |= (mi.miIntr.si << 12); return val; } default: @@ -29,33 +29,43 @@ auto SI::Read(MI& mi, u32 addr) const -> u32 { } } -void SI::Write(Mem& mem, Registers& regs, u32 addr, u32 val) { +void DMA(Mem& mem, Registers& regs) { + MMIO& mmio = mem.mmio; + SI& si = mmio.si; + si.status.dmaBusy = false; + 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]; + } + } else { + for(int i = 0; i < 64; i++) { + mem.pifRam[i] = mem.mmio.rdp.dram[BYTE_ADDRESS(si.dramAddr + i)]; + } + ProcessPIFCommands(mem.pifRam, si.controller, mem); + } + InterruptRaise(mem.mmio.mi, regs, Interrupt::SI); +} + +void SI::Write(Scheduler& scheduler, Mem& mem, Registers& regs, u32 addr, u32 val) { switch(addr) { case 0x04800000: dramAddr = val & RDRAM_DSIZE; break; case 0x04800004: { - ProcessPIFCommands(mem.pifRam, controller, mem); - - for(int i = 0; i < 64; i++) { - mem.mmio.rdp.dram[BYTE_ADDRESS(dramAddr + i)] = mem.pifRam[i]; - } - InterruptRaise(mem.mmio.mi, regs, Interrupt::SI); - status.intr = 1; + status.dmaBusy = true; + toDram = true; + scheduler.enqueue({SI_DMA_DELAY, DMA}); util::logdebug("SI DMA from PIF RAM to RDRAM ({:08X} to {:08X})\n", val & 0x1FFFFFFF, dramAddr); } break; case 0x04800010: { - for(int i = 0; i < 64; i++) { - mem.pifRam[i] = mem.mmio.rdp.dram[BYTE_ADDRESS(dramAddr + i)]; - } - ProcessPIFCommands(mem.pifRam, controller, mem); - InterruptRaise(mem.mmio.mi, regs, Interrupt::SI); - status.intr = 1; + status.dmaBusy = true; + toDram = false; + scheduler.enqueue({SI_DMA_DELAY, DMA}); util::logdebug("SI DMA from RDRAM to PIF RAM ({:08X} to {:08X})\n", dramAddr, val & 0x1FFFFFFF); } break; case 0x04800018: InterruptLower(mem.mmio.mi, regs, Interrupt::SI); - status.intr = 0; break; default: util::panic("Unhandled SI[%08X] write (%08X)\n", addr, val); diff --git a/src/n64/core/mmio/SI.hpp b/src/n64/core/mmio/SI.hpp index 1f05236e..6194d15b 100644 --- a/src/n64/core/mmio/SI.hpp +++ b/src/n64/core/mmio/SI.hpp @@ -3,6 +3,7 @@ #include #include #include +#include namespace n64 { @@ -27,7 +28,11 @@ struct SI { u32 dramAddr{}; Controller controller{}; + bool toDram = false; + auto Read(MI&, u32) const -> u32; - void Write(Mem&, Registers&, u32, u32); + void Write(Scheduler& scheduler, Mem&, Registers&, u32, u32); }; + +static void DMA(Mem& mem, Registers& regs); } \ No newline at end of file diff --git a/src/n64/core/rsp/decode.cpp b/src/n64/core/rsp/decode.cpp index 5964c0af..6518baf9 100644 --- a/src/n64/core/rsp/decode.cpp +++ b/src/n64/core/rsp/decode.cpp @@ -68,7 +68,7 @@ inline void lwc2(RSP& rsp, u32 instr) { case 0x05: rsp.lrv(instr); break; case 0x06: rsp.lpv(instr); break; case 0x07: rsp.luv(instr); break; - case 0x0A: printf("LWV\n"); break; + case 0x0A: break; case 0x0B: rsp.ltv(instr); break; default: util::panic("Unhandled RSP LWC2 {:05b}\n", mask); } diff --git a/src/n64/core/rsp/instructions.cpp b/src/n64/core/rsp/instructions.cpp index 49ae0b49..2589aeb0 100644 --- a/src/n64/core/rsp/instructions.cpp +++ b/src/n64/core/rsp/instructions.cpp @@ -68,6 +68,8 @@ inline void SetCop0Reg(Registers& regs, Mem& mem, u8 index, u32 val) { case 7: if(val == 0) { ReleaseSemaphore(rsp); + } else { + util::panic("Write with non-zero value to RSP_COP0_RESERVED ({})\n", val); } break; case 8: rdp.WriteStart(val); break; diff --git a/src/n64/m64.cpp b/src/n64/m64.cpp new file mode 100644 index 00000000..b3e59fac --- /dev/null +++ b/src/n64/m64.cpp @@ -0,0 +1,162 @@ +#include +#include + +struct TASMovieHeader { + u8 signature[4]; + u32 version; + u32 uid; + u32 numFrames; + u32 rerecords; + u8 fps; + u8 numControllers; + u8 reserved1; + u8 reserved2; + u32 numInputSamples; + uint16_t startType; + u8 reserved3; + u8 reserved4; + u32 controllerFlags; + u8 reserved5[160]; + char romName[32]; + u32 romCrc32; + uint16_t romCountryCode; + u8 reserved6[56]; + // 122 64-byte ASCII string: name of video plugin used when recording, directly from plugin + char video_plugin_name[64]; + // 162 64-byte ASCII string: name of sound plugin used when recording, directly from plugin + char audio_plugin_name[64]; + // 1A2 64-byte ASCII string: name of input plugin used when recording, directly from plugin + char input_plugin_name[64]; + // 1E2 64-byte ASCII string: name of rsp plugin used when recording, directly from plugin + char rsp_plugin_name[64]; + // 222 222-byte UTF-8 string: author name info + char author_name[222]; + // 300 256-byte UTF-8 string: author movie description info + char movie_description[256]; +} __attribute__((packed)); + +static_assert(sizeof(TASMovieHeader) == 1024); + +union TASMovieControllerData { + struct { + bool dpad_right: 1; + bool dpad_left: 1; + bool dpad_down: 1; + bool dpad_up: 1; + bool start: 1; + bool z: 1; + bool b: 1; + bool a: 1; + bool c_right: 1; + bool c_left: 1; + bool c_down: 1; + bool c_up: 1; + bool r: 1; + bool l: 1; + u8: 2; + s8 analog_x: 8; + s8 analog_y: 8; + }; + u32 raw; +} __attribute__((packed)); + +static_assert(sizeof(TASMovieControllerData) == 4); + +static u8* loaded_tas_movie = nullptr; +static size_t loaded_tas_movie_size = 0; +TASMovieHeader loaded_tas_movie_header; +uint32_t loaded_tas_movie_index = 0; + +void LoadTAS(const char* filename) { + FILE *fp = fopen(filename, "rb"); + + if (!fp) { + util::panic("Error opening the movie file {}! Are you sure it's a valid movie and that it exists?", filename); + } + + fseek(fp, 0, SEEK_END); + size_t size = ftell(fp); + + fseek(fp, 0, SEEK_SET); + u8 *buf = (u8*)malloc(size); + fread(buf, size, 1, fp); + + loaded_tas_movie = buf; + loaded_tas_movie_size = size; + + if (!loaded_tas_movie) { + util::panic("Error loading movie!"); + } + + memcpy(&loaded_tas_movie_header, buf, sizeof(TASMovieHeader)); + + if (loaded_tas_movie_header.signature[0] != 0x4D || loaded_tas_movie_header.signature[1] != 0x36 || loaded_tas_movie_header.signature[2] != 0x34 || loaded_tas_movie_header.signature[3] != 0x1A) { + util::panic("Failed to load movie: incorrect signature. Are you sure this is a valid movie?"); + } + + if (loaded_tas_movie_header.version != 3) { + util::panic("This movie is version {}: only version 3 is supported.", loaded_tas_movie_header.version); + } + + if (loaded_tas_movie_header.startType != 2) { + util::panic("Movie start type is {} - only movies with a start type of 2 are supported (start at power on)", loaded_tas_movie_header.startType); + } + + // TODO: check ROM CRC32 here + + util::print("Loaded movie '{}' ", loaded_tas_movie_header.movie_description); + util::print("by {}\n", loaded_tas_movie_header.author_name); + util::print("{} controller(s) connected\n", loaded_tas_movie_header.numControllers); + + if (loaded_tas_movie_header.numControllers != 1) { + util::panic("Currently, only movies with 1 controller connected are supported.\n"); + } + + loaded_tas_movie_index = sizeof(TASMovieHeader) - 4; // skip header +} + +bool tas_movie_loaded() { + return loaded_tas_movie != nullptr; +} + +n64::Controller tas_next_inputs() { + if (loaded_tas_movie_index + sizeof(TASMovieControllerData) > loaded_tas_movie_size) { + loaded_tas_movie = nullptr; + n64::Controller empty_controller{}; + memset(&empty_controller, 0, sizeof(n64::Controller)); + return empty_controller; + } + + TASMovieControllerData movie_cdata{}; + memcpy(&movie_cdata, loaded_tas_movie + loaded_tas_movie_index, sizeof(TASMovieControllerData)); + + loaded_tas_movie_index += sizeof(TASMovieControllerData); + + n64::Controller controller{}; + memset(&controller, 0, sizeof(controller)); + + controller.c_right = movie_cdata.c_right; + controller.c_left = movie_cdata.c_left; + controller.c_down = movie_cdata.c_down; + controller.c_up = movie_cdata.c_up; + controller.r = movie_cdata.r; + controller.l = movie_cdata.l; + + controller.dp_right = movie_cdata.dpad_right; + controller.dp_left = movie_cdata.dpad_left; + controller.dp_down = movie_cdata.dpad_down; + controller.dp_up = movie_cdata.dpad_up; + + controller.z = movie_cdata.z; + controller.b = movie_cdata.b; + controller.a = movie_cdata.a; + if(movie_cdata.start) { + printf("\n"); + } + controller.start = movie_cdata.start; + + controller.joy_x = movie_cdata.analog_x; + controller.joy_y = movie_cdata.analog_y; + + return controller; +} \ No newline at end of file diff --git a/src/n64/m64.hpp b/src/n64/m64.hpp new file mode 100644 index 00000000..62fa9b86 --- /dev/null +++ b/src/n64/m64.hpp @@ -0,0 +1,7 @@ +#pragma once +#include + +void LoadTAS(const char* filename); +void UnloadTAS(); +n64::Controller tas_next_inputs(); +bool tas_movie_loaded(); \ No newline at end of file