Delay SI DMAs, implement TAS movie replay (WIP), and fix DIV

This commit is contained in:
CocoSimone
2022-10-18 20:46:45 +02:00
parent 6a42a212c4
commit a8fda9770c
23 changed files with 359 additions and 85 deletions

View File

@@ -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

20
src/Scheduler.cpp Normal file
View 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 &regs) {
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
View 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;
};
}

View File

@@ -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

View File

@@ -1,13 +1,16 @@
#include <App.hpp>
#include <parallel-rdp/ParallelRDPWrapper.hpp>
#include <nfd.hpp>
#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);
}
}
}

View File

@@ -1,4 +1,5 @@
#include <frontend/App.hpp>
#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]);
}

View File

@@ -2,6 +2,7 @@
#include <ParallelRDPWrapper.hpp>
#include <Window.hpp>
#include <algorithm>
#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();
}
}
}

View File

@@ -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;

View File

@@ -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);

View File

@@ -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);
}

View File

@@ -25,6 +25,6 @@ struct MMIO {
RDP rdp;
u32 Read(u32);
void Write(Mem&, Registers&, u32, u32);
void Write(Scheduler&, Mem&, Registers&, u32, u32);
};
}

View File

@@ -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);
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<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 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<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 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<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 0x04300000 ... 0x044FFFFF: case 0x04500000 ... 0x048FFFFF: mmio.Write(scheduler, *this, regs, paddr, val); break;
case 0x10000000 ... 0x13FFFFFF: break;
case 0x1FC007C0 ... 0x1FC007FF:
util::WriteAccess<u64>(pifRam, paddr & PIF_RAM_DSIZE, htobe64(val));

View File

@@ -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");

View File

@@ -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;

View File

@@ -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;
}

View File

@@ -2,6 +2,7 @@
#include <n64/core/Mem.hpp>
#include <n64/core/cpu/Registers.hpp>
#include <util.hpp>
#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]);

View File

@@ -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);

View File

@@ -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);

View File

@@ -3,6 +3,7 @@
#include <n64/core/mmio/Interrupt.hpp>
#include <n64/core/mmio/MI.hpp>
#include <n64/core/mmio/PIF.hpp>
#include <Scheduler.hpp>
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);
}

View File

@@ -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);
}

View File

@@ -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;

162
src/n64/m64.cpp Normal file
View 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
View 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();