Delay SI DMAs, implement TAS movie replay (WIP), and fix DIV
This commit is contained in:
@@ -23,6 +23,10 @@ add_executable(natsukashii
|
|||||||
${FRONTEND_SOURCES}
|
${FRONTEND_SOURCES}
|
||||||
${FRONTEND_HEADERS}
|
${FRONTEND_HEADERS}
|
||||||
main.cpp
|
main.cpp
|
||||||
|
Scheduler.cpp
|
||||||
|
Scheduler.hpp
|
||||||
|
common.hpp
|
||||||
|
util.hpp
|
||||||
)
|
)
|
||||||
|
|
||||||
target_include_directories(natsukashii PRIVATE
|
target_include_directories(natsukashii PRIVATE
|
||||||
|
|||||||
20
src/Scheduler.cpp
Normal file
20
src/Scheduler.cpp
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
#include <Scheduler.hpp>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <Mem.hpp>
|
||||||
|
#include <Registers.hpp>
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
24
src/Scheduler.hpp
Normal file
24
src/Scheduler.hpp
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <vector>
|
||||||
|
#include <common.hpp>
|
||||||
|
|
||||||
|
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<Event> events;
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -38,6 +38,8 @@ using s128 = __int128_t;
|
|||||||
#define ELEMENT_INDEX(i) (7 - (i))
|
#define ELEMENT_INDEX(i) (7 - (i))
|
||||||
#define BYTE_INDEX(i) (15 - (i))
|
#define BYTE_INDEX(i) (15 - (i))
|
||||||
|
|
||||||
|
#define SI_DMA_DELAY (65536 * 2)
|
||||||
|
|
||||||
|
|
||||||
enum TLBAccessType {
|
enum TLBAccessType {
|
||||||
LOAD, STORE
|
LOAD, STORE
|
||||||
|
|||||||
@@ -1,13 +1,16 @@
|
|||||||
#include <App.hpp>
|
#include <App.hpp>
|
||||||
#include <parallel-rdp/ParallelRDPWrapper.hpp>
|
#include <parallel-rdp/ParallelRDPWrapper.hpp>
|
||||||
#include <nfd.hpp>
|
#include <nfd.hpp>
|
||||||
|
#include "m64.hpp"
|
||||||
|
|
||||||
void App::Run() {
|
void App::Run() {
|
||||||
// Main loop
|
// Main loop
|
||||||
const u8* state = SDL_GetKeyboardState(nullptr);
|
const u8* state = SDL_GetKeyboardState(nullptr);
|
||||||
SDL_EventState(SDL_DROPFILE, SDL_ENABLE);
|
SDL_EventState(SDL_DROPFILE, SDL_ENABLE);
|
||||||
|
static int count = 0;
|
||||||
while (!core.done) {
|
while (!core.done) {
|
||||||
core.Run(window, window.volumeL, window.volumeR);
|
core.Run(window, window.volumeL, window.volumeR);
|
||||||
|
core.UpdateController(state);
|
||||||
|
|
||||||
SDL_Event event;
|
SDL_Event event;
|
||||||
while (SDL_PollEvent(&event)) {
|
while (SDL_PollEvent(&event)) {
|
||||||
@@ -48,8 +51,6 @@ void App::Run() {
|
|||||||
}
|
}
|
||||||
} break;
|
} break;
|
||||||
}
|
}
|
||||||
|
|
||||||
core.UpdateController(state);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
#include <frontend/App.hpp>
|
#include <frontend/App.hpp>
|
||||||
|
#include "m64.hpp"
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
#define main SDL_main
|
#define main SDL_main
|
||||||
@@ -7,6 +8,9 @@
|
|||||||
int main(int argc, char** argv) {
|
int main(int argc, char** argv) {
|
||||||
App* app = new App;
|
App* app = new App;
|
||||||
if(argc > 1) {
|
if(argc > 1) {
|
||||||
|
if(argc > 2) {
|
||||||
|
LoadTAS(argv[2]);
|
||||||
|
}
|
||||||
app->LoadROM(argv[1]);
|
app->LoadROM(argv[1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
#include <ParallelRDPWrapper.hpp>
|
#include <ParallelRDPWrapper.hpp>
|
||||||
#include <Window.hpp>
|
#include <Window.hpp>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include "m64.hpp"
|
||||||
|
|
||||||
namespace n64 {
|
namespace n64 {
|
||||||
Core::Core() {
|
Core::Core() {
|
||||||
@@ -61,6 +62,7 @@ void Core::Run(Window& window, float volumeL, float volumeR) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
mmio.ai.Step(mem, cpu.regs, 1, volumeL, volumeR);
|
mmio.ai.Step(mem, cpu.regs, 1, volumeL, volumeR);
|
||||||
|
mem.scheduler.handleEvents(1, mem, cpu.regs);
|
||||||
}
|
}
|
||||||
|
|
||||||
cycles -= mmio.vi.cyclesPerHalfline;
|
cycles -= mmio.vi.cyclesPerHalfline;
|
||||||
@@ -87,7 +89,6 @@ void Core::Run(Window& window, float volumeL, float volumeR) {
|
|||||||
|
|
||||||
void Core::UpdateController(const u8* state) {
|
void Core::UpdateController(const u8* state) {
|
||||||
Controller &controller = mem.mmio.si.controller;
|
Controller &controller = mem.mmio.si.controller;
|
||||||
controller.raw = 0;
|
|
||||||
s8 xaxis = 0, yaxis = 0;
|
s8 xaxis = 0, yaxis = 0;
|
||||||
|
|
||||||
if(gamepadConnected) {
|
if(gamepadConnected) {
|
||||||
@@ -106,42 +107,49 @@ void Core::UpdateController(const u8* state) {
|
|||||||
bool CLEFT = GET_AXIS(gamepad, SDL_CONTROLLER_AXIS_RIGHTX) <= -128;
|
bool CLEFT = GET_AXIS(gamepad, SDL_CONTROLLER_AXIS_RIGHTX) <= -128;
|
||||||
bool CRIGHT = GET_AXIS(gamepad, SDL_CONTROLLER_AXIS_RIGHTX) >= 127;
|
bool CRIGHT = GET_AXIS(gamepad, SDL_CONTROLLER_AXIS_RIGHTX) >= 127;
|
||||||
|
|
||||||
controller.b1 = (A << 7) | (B << 6) | (Z << 5) | (START << 4) |
|
controller.a = A;
|
||||||
(DUP << 3) | (DDOWN << 2) | (DLEFT << 1) | DRIGHT;
|
controller.b = B;
|
||||||
|
controller.z = Z;
|
||||||
controller.b2 = ((START && L && R) << 7) | (0 << 6) | (L << 5) | (R << 4) |
|
controller.start = START;
|
||||||
(CUP << 3) | (CDOWN << 2) | (CLEFT << 1) | CRIGHT;
|
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);
|
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);
|
yaxis = (s8) std::clamp(-(GET_AXIS(gamepad, SDL_CONTROLLER_AXIS_LEFTY) >> 8), -86, 86);
|
||||||
|
|
||||||
controller.b3 = xaxis;
|
controller.joy_x = xaxis;
|
||||||
controller.b4 = yaxis;
|
controller.joy_y = yaxis;
|
||||||
|
|
||||||
if ((controller.b2 >> 7) & 1) {
|
if (controller.joy_reset) {
|
||||||
controller.b1 &= ~0x10;
|
controller.start = false;
|
||||||
controller.b3 = 0;
|
controller.joy_x = 0;
|
||||||
controller.b4 = 0;
|
controller.joy_y = 0;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
controller.b1 =
|
controller.a = state[SDL_SCANCODE_X];
|
||||||
(state[SDL_SCANCODE_X] << 7) |
|
controller.b = state[SDL_SCANCODE_C];
|
||||||
(state[SDL_SCANCODE_C] << 6) |
|
controller.z = state[SDL_SCANCODE_Z];
|
||||||
(state[SDL_SCANCODE_Z] << 5) |
|
controller.start = state[SDL_SCANCODE_RETURN];
|
||||||
(state[SDL_SCANCODE_RETURN] << 4) |
|
controller.dp_up = state[SDL_SCANCODE_KP_8];
|
||||||
(state[SDL_SCANCODE_KP_8] << 3) |
|
controller.dp_down = state[SDL_SCANCODE_KP_5];
|
||||||
(state[SDL_SCANCODE_KP_5] << 2) |
|
controller.dp_left = state[SDL_SCANCODE_KP_4];
|
||||||
(state[SDL_SCANCODE_KP_4] << 1) |
|
controller.dp_right = state[SDL_SCANCODE_KP_6];
|
||||||
(state[SDL_SCANCODE_KP_6]);
|
controller.joy_reset = state[SDL_SCANCODE_RETURN] && state[SDL_SCANCODE_A] && state[SDL_SCANCODE_S];
|
||||||
controller.b2 =
|
controller.l = state[SDL_SCANCODE_A];
|
||||||
((state[SDL_SCANCODE_RETURN] && state[SDL_SCANCODE_A] && state[SDL_SCANCODE_S]) << 7) |
|
controller.r = state[SDL_SCANCODE_S];
|
||||||
(0 << 6) |
|
controller.c_up = state[SDL_SCANCODE_I];
|
||||||
(state[SDL_SCANCODE_A] << 5) |
|
controller.c_down = state[SDL_SCANCODE_J];
|
||||||
(state[SDL_SCANCODE_S] << 4) |
|
controller.c_left = state[SDL_SCANCODE_K];
|
||||||
(state[SDL_SCANCODE_I] << 3) |
|
controller.c_right = state[SDL_SCANCODE_L];
|
||||||
(state[SDL_SCANCODE_J] << 2) |
|
|
||||||
(state[SDL_SCANCODE_K] << 1) |
|
|
||||||
(state[SDL_SCANCODE_L]);
|
|
||||||
|
|
||||||
if (state[SDL_SCANCODE_LEFT]) {
|
if (state[SDL_SCANCODE_LEFT]) {
|
||||||
xaxis = -86;
|
xaxis = -86;
|
||||||
@@ -155,14 +163,18 @@ void Core::UpdateController(const u8* state) {
|
|||||||
yaxis = 86;
|
yaxis = 86;
|
||||||
}
|
}
|
||||||
|
|
||||||
controller.b3 = xaxis;
|
controller.joy_x = xaxis;
|
||||||
controller.b4 = yaxis;
|
controller.joy_y = yaxis;
|
||||||
|
|
||||||
if ((controller.b2 >> 7) & 1) {
|
if (controller.joy_reset) {
|
||||||
controller.b1 &= ~0x10;
|
controller.start = false;
|
||||||
controller.b3 = 0;
|
controller.joy_x = 0;
|
||||||
controller.b4 = 0;
|
controller.joy_y = 0;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(tas_movie_loaded()) {
|
||||||
|
controller = tas_next_inputs();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,10 +27,6 @@ inline void CheckCompareInterrupt(MI& mi, Registers& regs) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void HandleInterrupt(Registers& regs) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
inline void Cpu::disassembly(u32 instr) {
|
inline void Cpu::disassembly(u32 instr) {
|
||||||
size_t count;
|
size_t count;
|
||||||
cs_insn *insn;
|
cs_insn *insn;
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ private:
|
|||||||
void daddiu(u32);
|
void daddiu(u32);
|
||||||
void ddiv(u32);
|
void ddiv(u32);
|
||||||
void ddivu(u32);
|
void ddivu(u32);
|
||||||
void div_(u32);
|
void div(u32);
|
||||||
void divu(u32);
|
void divu(u32);
|
||||||
void dmult(u32);
|
void dmult(u32);
|
||||||
void dmultu(u32);
|
void dmultu(u32);
|
||||||
|
|||||||
@@ -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) {
|
switch (addr) {
|
||||||
case 0x04040000 ... 0x040FFFFF: rsp.Write(mem, regs, addr, val); break;
|
case 0x04040000 ... 0x040FFFFF: rsp.Write(mem, regs, addr, val); break;
|
||||||
case 0x04100000 ... 0x041FFFFF: rdp.Write(mi, regs, rsp, 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 0x04500000 ... 0x045FFFFF: ai.Write(mem, regs, addr, val); break;
|
||||||
case 0x04600000 ... 0x046FFFFF: pi.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 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:
|
default:
|
||||||
util::panic("Unhandled mmio write at addr {:08X} with val {:08X}\n", addr, val);
|
util::panic("Unhandled mmio write at addr {:08X} with val {:08X}\n", addr, val);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,6 +25,6 @@ struct MMIO {
|
|||||||
RDP rdp;
|
RDP rdp;
|
||||||
|
|
||||||
u32 Read(u32);
|
u32 Read(u32);
|
||||||
void Write(Mem&, Registers&, u32, u32);
|
void Write(Scheduler&, Mem&, Registers&, u32, u32);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -228,7 +228,7 @@ void Mem::Write8(Registers& regs, u64 vaddr, u32 val, s64 pc) {
|
|||||||
util::WriteAccess<u32>(mmio.rsp.dmem, paddr & DMEM_DSIZE, val);
|
util::WriteAccess<u32>(mmio.rsp.dmem, paddr & DMEM_DSIZE, val);
|
||||||
break;
|
break;
|
||||||
case 0x04040000 ... 0x040FFFFF: case 0x04100000 ... 0x041FFFFF:
|
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 0x10000000 ... 0x13FFFFFF: break;
|
||||||
case 0x1FC007C0 ... 0x1FC007FF:
|
case 0x1FC007C0 ... 0x1FC007FF:
|
||||||
val = val << (8 * (3 - (paddr & 3)));
|
val = val << (8 * (3 - (paddr & 3)));
|
||||||
@@ -265,7 +265,7 @@ void Mem::Write16(Registers& regs, u64 vaddr, u32 val, s64 pc) {
|
|||||||
util::WriteAccess<u32>(mmio.rsp.dmem, paddr & DMEM_DSIZE, val);
|
util::WriteAccess<u32>(mmio.rsp.dmem, paddr & DMEM_DSIZE, val);
|
||||||
break;
|
break;
|
||||||
case 0x04040000 ... 0x040FFFFF: case 0x04100000 ... 0x041FFFFF:
|
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 0x10000000 ... 0x13FFFFFF: break;
|
||||||
case 0x1FC007C0 ... 0x1FC007FF:
|
case 0x1FC007C0 ... 0x1FC007FF:
|
||||||
val = val << (16 * !(paddr & 2));
|
val = val << (16 * !(paddr & 2));
|
||||||
@@ -299,7 +299,7 @@ void Mem::Write32(Registers& regs, u64 vaddr, u32 val, s64 pc) {
|
|||||||
util::WriteAccess<u32>(mmio.rsp.dmem, paddr & DMEM_DSIZE, val);
|
util::WriteAccess<u32>(mmio.rsp.dmem, paddr & DMEM_DSIZE, val);
|
||||||
break;
|
break;
|
||||||
case 0x04040000 ... 0x040FFFFF: case 0x04100000 ... 0x041FFFFF:
|
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 0x10000000 ... 0x13FF0013: break;
|
||||||
case 0x13FF0014: {
|
case 0x13FF0014: {
|
||||||
if(val < ISVIEWER_SIZE) {
|
if(val < ISVIEWER_SIZE) {
|
||||||
@@ -343,7 +343,7 @@ void Mem::Write64(Registers& regs, u64 vaddr, u64 val, s64 pc) {
|
|||||||
util::WriteAccess<u32>(mmio.rsp.dmem, paddr & DMEM_DSIZE, val);
|
util::WriteAccess<u32>(mmio.rsp.dmem, paddr & DMEM_DSIZE, val);
|
||||||
break;
|
break;
|
||||||
case 0x04040000 ... 0x040FFFFF: case 0x04100000 ... 0x041FFFFF:
|
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 0x10000000 ... 0x13FFFFFF: break;
|
||||||
case 0x1FC007C0 ... 0x1FC007FF:
|
case 0x1FC007C0 ... 0x1FC007FF:
|
||||||
util::WriteAccess<u64>(pifRam, paddr & PIF_RAM_DSIZE, htobe64(val));
|
util::WriteAccess<u64>(pifRam, paddr & PIF_RAM_DSIZE, htobe64(val));
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ struct Mem {
|
|||||||
|
|
||||||
MMIO mmio;
|
MMIO mmio;
|
||||||
u8 pifRam[PIF_RAM_SIZE]{};
|
u8 pifRam[PIF_RAM_SIZE]{};
|
||||||
|
Scheduler scheduler;
|
||||||
|
|
||||||
inline void DumpRDRAM() const {
|
inline void DumpRDRAM() const {
|
||||||
FILE *fp = fopen("rdram.dump", "wb");
|
FILE *fp = fopen("rdram.dump", "wb");
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ void Cpu::special(Mem& mem, u32 instr) {
|
|||||||
case 0x17: dsrav(instr); break;
|
case 0x17: dsrav(instr); break;
|
||||||
case 0x18: mult(instr); break;
|
case 0x18: mult(instr); break;
|
||||||
case 0x19: multu(instr); break;
|
case 0x19: multu(instr); break;
|
||||||
case 0x1A: div_(instr); break;
|
case 0x1A: div(instr); break;
|
||||||
case 0x1B: divu(instr); break;
|
case 0x1B: divu(instr); break;
|
||||||
case 0x1C: dmult(instr); break;
|
case 0x1C: dmult(instr); break;
|
||||||
case 0x1D: dmultu(instr); break;
|
case 0x1D: dmultu(instr); break;
|
||||||
|
|||||||
@@ -77,20 +77,20 @@ void Cpu::daddiu(u32 instr) {
|
|||||||
regs.gpr[RT(instr)] = rs + imm;
|
regs.gpr[RT(instr)] = rs + imm;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Cpu::div_(u32 instr) {
|
void Cpu::div(u32 instr) {
|
||||||
s64 dividend = (s32)regs.gpr[RS(instr)];
|
s64 dividend = (s32)regs.gpr[RS(instr)];
|
||||||
s64 divisor = (s32)regs.gpr[RT(instr)];
|
s64 divisor = (s32)regs.gpr[RT(instr)];
|
||||||
|
|
||||||
if(divisor == 0) {
|
if(divisor == 0) {
|
||||||
regs.hi = dividend;
|
regs.hi = dividend;
|
||||||
if(dividend >= 0) {
|
if(dividend >= 0) {
|
||||||
regs.lo = -1;
|
regs.lo = s64(-1);
|
||||||
} else {
|
} else {
|
||||||
regs.lo = 1;
|
regs.lo = s64(1);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
s64 quotient = dividend / divisor;
|
s32 quotient = dividend / divisor;
|
||||||
s64 remainder = dividend % divisor;
|
s32 remainder = dividend % divisor;
|
||||||
regs.lo = quotient;
|
regs.lo = quotient;
|
||||||
regs.hi = remainder;
|
regs.hi = remainder;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
#include <n64/core/Mem.hpp>
|
#include <n64/core/Mem.hpp>
|
||||||
#include <n64/core/cpu/Registers.hpp>
|
#include <n64/core/cpu/Registers.hpp>
|
||||||
#include <util.hpp>
|
#include <util.hpp>
|
||||||
|
#include "m64.hpp"
|
||||||
|
|
||||||
namespace n64 {
|
namespace n64 {
|
||||||
static int channel = 0;
|
static int channel = 0;
|
||||||
@@ -43,10 +44,10 @@ void ProcessPIFCommands(u8* pifRam, Controller& controller, Mem& mem) {
|
|||||||
res[2] = 0x01;
|
res[2] = 0x01;
|
||||||
break;
|
break;
|
||||||
case 1:
|
case 1:
|
||||||
res[0] = controller.b1;
|
res[0] = controller.byte1;
|
||||||
res[1] = controller.b2;
|
res[1] = controller.byte2;
|
||||||
res[2] = controller.b3;
|
res[2] = controller.joy_x;
|
||||||
res[3] = controller.b4;
|
res[3] = controller.joy_y;
|
||||||
break;
|
break;
|
||||||
case 2: case 3: res[0] = 0; break;
|
case 2: case 3: res[0] = 0; break;
|
||||||
default: util::panic("Unimplemented PIF command {}", cmd[2]);
|
default: util::panic("Unimplemented PIF command {}", cmd[2]);
|
||||||
|
|||||||
@@ -3,12 +3,35 @@
|
|||||||
|
|
||||||
namespace n64 {
|
namespace n64 {
|
||||||
|
|
||||||
union Controller {
|
struct Controller {
|
||||||
|
union {
|
||||||
|
u8 byte1;
|
||||||
struct {
|
struct {
|
||||||
u8 b1, b2;
|
bool dp_right:1;
|
||||||
s8 b3, b4;
|
bool dp_left:1;
|
||||||
} __attribute__((__packed__));
|
bool dp_down:1;
|
||||||
u32 raw;
|
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);
|
static_assert(sizeof(Controller) == 4);
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ SI::SI() {
|
|||||||
void SI::Reset() {
|
void SI::Reset() {
|
||||||
status.raw = 0;
|
status.raw = 0;
|
||||||
dramAddr = 0;
|
dramAddr = 0;
|
||||||
controller.raw = 0;
|
memset(&controller, 0, sizeof(Controller));
|
||||||
}
|
}
|
||||||
|
|
||||||
auto SI::Read(MI& mi, u32 addr) const -> u32 {
|
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 |= status.dmaBusy;
|
||||||
val |= (0 << 1);
|
val |= (0 << 1);
|
||||||
val |= (0 << 3);
|
val |= (0 << 3);
|
||||||
val |= (status.intr << 12);
|
val |= (mi.miIntr.si << 12);
|
||||||
return val;
|
return val;
|
||||||
}
|
}
|
||||||
default:
|
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) {
|
switch(addr) {
|
||||||
case 0x04800000:
|
case 0x04800000:
|
||||||
dramAddr = val & RDRAM_DSIZE;
|
dramAddr = val & RDRAM_DSIZE;
|
||||||
break;
|
break;
|
||||||
case 0x04800004: {
|
case 0x04800004: {
|
||||||
ProcessPIFCommands(mem.pifRam, controller, mem);
|
status.dmaBusy = true;
|
||||||
|
toDram = true;
|
||||||
for(int i = 0; i < 64; i++) {
|
scheduler.enqueue({SI_DMA_DELAY, DMA});
|
||||||
mem.mmio.rdp.dram[BYTE_ADDRESS(dramAddr + i)] = mem.pifRam[i];
|
|
||||||
}
|
|
||||||
InterruptRaise(mem.mmio.mi, regs, Interrupt::SI);
|
|
||||||
status.intr = 1;
|
|
||||||
util::logdebug("SI DMA from PIF RAM to RDRAM ({:08X} to {:08X})\n", val & 0x1FFFFFFF, dramAddr);
|
util::logdebug("SI DMA from PIF RAM to RDRAM ({:08X} to {:08X})\n", val & 0x1FFFFFFF, dramAddr);
|
||||||
} break;
|
} break;
|
||||||
case 0x04800010: {
|
case 0x04800010: {
|
||||||
for(int i = 0; i < 64; i++) {
|
status.dmaBusy = true;
|
||||||
mem.pifRam[i] = mem.mmio.rdp.dram[BYTE_ADDRESS(dramAddr + i)];
|
toDram = false;
|
||||||
}
|
scheduler.enqueue({SI_DMA_DELAY, DMA});
|
||||||
ProcessPIFCommands(mem.pifRam, controller, mem);
|
|
||||||
InterruptRaise(mem.mmio.mi, regs, Interrupt::SI);
|
|
||||||
status.intr = 1;
|
|
||||||
util::logdebug("SI DMA from RDRAM to PIF RAM ({:08X} to {:08X})\n", dramAddr, val & 0x1FFFFFFF);
|
util::logdebug("SI DMA from RDRAM to PIF RAM ({:08X} to {:08X})\n", dramAddr, val & 0x1FFFFFFF);
|
||||||
} break;
|
} break;
|
||||||
case 0x04800018:
|
case 0x04800018:
|
||||||
InterruptLower(mem.mmio.mi, regs, Interrupt::SI);
|
InterruptLower(mem.mmio.mi, regs, Interrupt::SI);
|
||||||
status.intr = 0;
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
util::panic("Unhandled SI[%08X] write (%08X)\n", addr, val);
|
util::panic("Unhandled SI[%08X] write (%08X)\n", addr, val);
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
#include <n64/core/mmio/Interrupt.hpp>
|
#include <n64/core/mmio/Interrupt.hpp>
|
||||||
#include <n64/core/mmio/MI.hpp>
|
#include <n64/core/mmio/MI.hpp>
|
||||||
#include <n64/core/mmio/PIF.hpp>
|
#include <n64/core/mmio/PIF.hpp>
|
||||||
|
#include <Scheduler.hpp>
|
||||||
|
|
||||||
namespace n64 {
|
namespace n64 {
|
||||||
|
|
||||||
@@ -27,7 +28,11 @@ struct SI {
|
|||||||
u32 dramAddr{};
|
u32 dramAddr{};
|
||||||
Controller controller{};
|
Controller controller{};
|
||||||
|
|
||||||
|
bool toDram = false;
|
||||||
|
|
||||||
auto Read(MI&, u32) const -> u32;
|
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);
|
||||||
}
|
}
|
||||||
@@ -68,7 +68,7 @@ inline void lwc2(RSP& rsp, u32 instr) {
|
|||||||
case 0x05: rsp.lrv(instr); break;
|
case 0x05: rsp.lrv(instr); break;
|
||||||
case 0x06: rsp.lpv(instr); break;
|
case 0x06: rsp.lpv(instr); break;
|
||||||
case 0x07: rsp.luv(instr); break;
|
case 0x07: rsp.luv(instr); break;
|
||||||
case 0x0A: printf("LWV\n"); break;
|
case 0x0A: break;
|
||||||
case 0x0B: rsp.ltv(instr); break;
|
case 0x0B: rsp.ltv(instr); break;
|
||||||
default: util::panic("Unhandled RSP LWC2 {:05b}\n", mask);
|
default: util::panic("Unhandled RSP LWC2 {:05b}\n", mask);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -68,6 +68,8 @@ inline void SetCop0Reg(Registers& regs, Mem& mem, u8 index, u32 val) {
|
|||||||
case 7:
|
case 7:
|
||||||
if(val == 0) {
|
if(val == 0) {
|
||||||
ReleaseSemaphore(rsp);
|
ReleaseSemaphore(rsp);
|
||||||
|
} else {
|
||||||
|
util::panic("Write with non-zero value to RSP_COP0_RESERVED ({})\n", val);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 8: rdp.WriteStart(val); break;
|
case 8: rdp.WriteStart(val); break;
|
||||||
|
|||||||
162
src/n64/m64.cpp
Normal file
162
src/n64/m64.cpp
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
#include <m64.hpp>
|
||||||
|
#include <util.hpp>
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
7
src/n64/m64.hpp
Normal file
7
src/n64/m64.hpp
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <PIF.hpp>
|
||||||
|
|
||||||
|
void LoadTAS(const char* filename);
|
||||||
|
void UnloadTAS();
|
||||||
|
n64::Controller tas_next_inputs();
|
||||||
|
bool tas_movie_loaded();
|
||||||
Reference in New Issue
Block a user