Refactor Mupen Movies

This commit is contained in:
SimoneN64
2024-05-10 21:05:23 +02:00
committed by Simone
parent e940b6731a
commit 423647d69a
12 changed files with 205 additions and 184 deletions

View File

@@ -25,6 +25,10 @@ void Core::Stop() {
cpu->mem.Reset(); cpu->mem.Reset();
} }
bool Core::LoadTAS(const fs::path &path) {
return cpu->mem.mmio.si.pif.movie.Load(path);
}
void Core::LoadROM(const std::string& rom_) { void Core::LoadROM(const std::string& rom_) {
pause = true; pause = true;
rom = rom_; rom = rom_;

View File

@@ -11,6 +11,7 @@ struct Core {
Core(); Core();
void Stop(); void Stop();
void LoadROM(const std::string&); void LoadROM(const std::string&);
bool LoadTAS(const fs::path&);
void Run(float volumeL, float volumeR); void Run(float volumeL, float volumeR);
void Serialize(); void Serialize();
void Deserialize(); void Deserialize();

View File

@@ -4,6 +4,7 @@
#include <filesystem> #include <filesystem>
#include <mio/mmap.hpp> #include <mio/mmap.hpp>
#include <vector> #include <vector>
#include "MupenMovie.hpp"
namespace fs = std::filesystem; namespace fs = std::filesystem;
@@ -21,10 +22,10 @@ struct Controller {
union { union {
u8 byte1; u8 byte1;
struct { struct {
bool dp_right: 1; bool dpRight: 1;
bool dp_left: 1; bool dpLeft: 1;
bool dp_down: 1; bool dpDown: 1;
bool dp_up: 1; bool dpUp: 1;
bool start: 1; bool start: 1;
bool z: 1; bool z: 1;
bool b: 1; bool b: 1;
@@ -34,19 +35,19 @@ struct Controller {
union { union {
u8 byte2; u8 byte2;
struct { struct {
bool c_right: 1; bool cRight: 1;
bool c_left: 1; bool cLeft: 1;
bool c_down: 1; bool cDown: 1;
bool c_up: 1; bool cUp: 1;
bool r: 1; bool r: 1;
bool l: 1; bool l: 1;
bool zero: 1; bool zero: 1;
bool joy_reset: 1; bool joyReset: 1;
}; };
}; };
s8 joy_x; s8 joyX;
s8 joy_y; s8 joyY;
}; };
u32 raw; u32 raw;
@@ -54,8 +55,8 @@ struct Controller {
Controller& operator=(const Controller& other) { Controller& operator=(const Controller& other) {
byte1 = other.byte1; byte1 = other.byte1;
byte2 = other.byte2; byte2 = other.byte2;
joy_x = other.joy_x; joyX = other.joyX;
joy_y = other.joy_y; joyY = other.joyY;
return *this; return *this;
} }
@@ -73,14 +74,14 @@ struct Controller {
case B: b = state; break; case B: b = state; break;
case Z: z = state; break; case Z: z = state; break;
case Start: start = state; break; case Start: start = state; break;
case DUp: dp_up = state; break; case DUp: dpUp = state; break;
case DDown: dp_down = state; break; case DDown: dpDown = state; break;
case DLeft: dp_left = state; break; case DLeft: dpLeft = state; break;
case DRight: dp_right = state; break; case DRight: dpRight = state; break;
case CUp: c_up = state; break; case CUp: cUp = state; break;
case CDown: c_down = state; break; case CDown: cDown = state; break;
case CLeft: c_left = state; break; case CLeft: cLeft = state; break;
case CRight: c_right = state; break; case CRight: cRight = state; break;
case LT: l = state; break; case LT: l = state; break;
case RT: r = state; break; case RT: r = state; break;
} }
@@ -88,14 +89,14 @@ struct Controller {
void UpdateAxis(Axis a, s8 state) { void UpdateAxis(Axis a, s8 state) {
switch(a) { switch(a) {
case X: joy_x = state; break; case X: joyX = state; break;
case Y: joy_y = state; break; case Y: joyY = state; break;
} }
} }
Controller& operator=(u32 v) { Controller& operator=(u32 v) {
joy_y = v & 0xff; joyY = v & 0xff;
joy_x = v >> 8; joyX = v >> 8;
byte2 = v >> 16; byte2 = v >> 16;
byte1 = v >> 24; byte1 = v >> 24;
@@ -161,7 +162,7 @@ struct PIF {
void CICChallenge(); void CICChallenge();
static void ExecutePIF(Mem& mem, Registers& regs); static void ExecutePIF(Mem& mem, Registers& regs);
static void DoPIFHLE(Mem& mem, Registers& regs, bool pal, CICType cicType); static void DoPIFHLE(Mem& mem, Registers& regs, bool pal, CICType cicType);
bool ReadButtons(u8*) const; bool ReadButtons(u8*);
void ControllerID(u8*) const; void ControllerID(u8*) const;
void MempakRead(const u8*, u8*); void MempakRead(const u8*, u8*);
void MempakWrite(u8*, u8*); void MempakWrite(u8*, u8*);
@@ -176,6 +177,7 @@ struct PIF {
int channel = 0; int channel = 0;
std::string mempakPath{}, eepromPath{}; std::string mempakPath{}, eepromPath{};
size_t eepromSize{}; size_t eepromSize{};
MupenMovie movie;
FORCE_INLINE u8 Read(u32 addr) { FORCE_INLINE u8 Read(u32 addr) {
addr &= 0x7FF; addr &= 0x7FF;

View File

@@ -76,7 +76,7 @@ void PIF::ControllerID(u8 *res) const {
} }
} }
bool PIF::ReadButtons(u8* res) const { bool PIF::ReadButtons(u8* res) {
if(channel >= 6) { if(channel >= 6) {
res[0] = 0; res[0] = 0;
res[1] = 0; res[1] = 0;
@@ -95,17 +95,17 @@ bool PIF::ReadButtons(u8* res) const {
case JOYBUS_4KB_EEPROM: case JOYBUS_4KB_EEPROM:
case JOYBUS_16KB_EEPROM: case JOYBUS_16KB_EEPROM:
case JOYBUS_CONTROLLER: case JOYBUS_CONTROLLER:
if (TasMovieLoaded()) { if (movie.IsLoaded()) {
Controller controller = TasNextInputs(); Controller controller = movie.NextInputs();
res[0] = controller.byte1; res[0] = controller.byte1;
res[1] = controller.byte2; res[1] = controller.byte2;
res[2] = controller.joy_x; res[2] = controller.joyX;
res[3] = controller.joy_y; res[3] = controller.joyY;
} else { } else {
res[0] = joybusDevices[channel].controller.byte1; res[0] = joybusDevices[channel].controller.byte1;
res[1] = joybusDevices[channel].controller.byte2; res[1] = joybusDevices[channel].controller.byte2;
res[2] = joybusDevices[channel].controller.joy_x; res[2] = joybusDevices[channel].controller.joyX;
res[3] = joybusDevices[channel].controller.joy_y; res[3] = joybusDevices[channel].controller.joyY;
} }
return true; return true;
case JOYBUS_DANCEPAD: case JOYBUS_DANCEPAD:

View File

@@ -1,178 +1,128 @@
#include <PIF/MupenMovie.hpp> #include <PIF/MupenMovie.hpp>
#include <log.hpp> #include <log.hpp>
#include "File.hpp"
struct TASMovieHeader { #include "PIF.hpp"
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 { union TASMovieControllerData {
struct { struct {
bool dpad_right: 1; unsigned dpadRight: 1;
bool dpad_left: 1; unsigned dpadLeft: 1;
bool dpad_down: 1; unsigned dpadDown: 1;
bool dpad_up: 1; unsigned dpadUp: 1;
bool start: 1; unsigned start: 1;
bool z: 1; unsigned z: 1;
bool b: 1; unsigned b: 1;
bool a: 1; unsigned a: 1;
bool c_right: 1; unsigned cRight: 1;
bool c_left: 1; unsigned cLeft: 1;
bool c_down: 1; unsigned cDown: 1;
bool c_up: 1; unsigned cUp: 1;
bool r: 1; unsigned r: 1;
bool l: 1; unsigned l: 1;
u8: 2; unsigned : 2;
s8 analog_x: 8; signed analogX : 8;
s8 analog_y: 8; signed analogY : 8;
}; };
u32 raw; u32 raw;
} __attribute__((packed)); } __attribute__((packed));
static_assert(sizeof(TASMovieControllerData) == 4); static_assert(sizeof(TASMovieControllerData) == 4);
static u8* loaded_tas_movie = nullptr; bool MupenMovie::Load(const fs::path &path) {
static size_t loaded_tas_movie_size = 0; loadedTasMovie = Util::ReadFileBinary(path.string());
TASMovieHeader loaded_tas_movie_header; if(!IsLoaded()) {
uint32_t loaded_tas_movie_index = 0; Util::error("Error loading movie!");
return false;
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); memcpy(&loadedTasMovieHeader, loadedTasMovie.data(), loadedTasMovie.size());
size_t size = ftell(fp);
fseek(fp, 0, SEEK_SET); if (loadedTasMovieHeader.signature[0] != 0x4D || loadedTasMovieHeader.signature[1] != 0x36 || loadedTasMovieHeader.signature[2] != 0x34 || loadedTasMovieHeader.signature[3] != 0x1A) {
u8 *buf = (u8*)malloc(size); Util::error("Failed to load movie: incorrect signature. Are you sure this is a valid movie?");
fread(buf, size, 1, fp); return false;
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 (loadedTasMovieHeader.version != 3) {
Util::error("This movie is version {}: only version 3 is supported.", loadedTasMovieHeader.version);
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) { return false;
Util::panic("Failed to load movie: incorrect signature. Are you sure this is a valid movie?");
} }
if (loaded_tas_movie_header.version != 3) { if (loadedTasMovieHeader.startType != 2) {
Util::panic("This movie is version {}: only version 3 is supported.", loaded_tas_movie_header.version); Util::error("Movie start type is {} - only movies with a start type of 2 are supported (start at power on)", loadedTasMovieHeader.startType);
return false;
} }
if (loaded_tas_movie_header.startType != 2) { Util::info("Loaded movie '{}' ", loadedTasMovieHeader.movie_description);
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); Util::info("by {}", loadedTasMovieHeader.author_name);
Util::info("{} controller(s) connected", loadedTasMovieHeader.numControllers);
if (loadedTasMovieHeader.numControllers != 1) {
Util::error("Currently, only movies with 1 controller connected are supported.");
return false;
} }
// TODO: check ROM CRC32 here loadedTasMovieIndex = sizeof(TASMovieHeader) - 4; // skip header
return true;
Util::info("Loaded movie '{}' ", loaded_tas_movie_header.movie_description);
Util::info("by {}", loaded_tas_movie_header.author_name);
Util::info("{} controller(s) connected", loaded_tas_movie_header.numControllers);
if (loaded_tas_movie_header.numControllers != 1) {
Util::panic("Currently, only movies with 1 controller connected are supported.");
}
loaded_tas_movie_index = sizeof(TASMovieHeader) - 4; // skip header
} }
bool TasMovieLoaded() { MupenMovie::MupenMovie(const fs::path &path) {
return loaded_tas_movie != nullptr; if(!Load(path)) {
Util::panic("");
}
} }
FORCE_INLINE void LogController(const n64::Controller& controller) { FORCE_INLINE void LogController(const n64::Controller& controller) {
Util::debug("c_right: {}", controller.c_right); Util::debug("c_right: {}", controller.cRight);
Util::debug("c_left: {}", controller.c_left); Util::debug("c_left: {}", controller.cLeft);
Util::debug("c_down: {}", controller.c_down); Util::debug("c_down: {}", controller.cDown);
Util::debug("c_up: {}", controller.c_up); Util::debug("c_up: {}", controller.cUp);
Util::debug("r: {}", controller.r); Util::debug("r: {}", controller.r);
Util::debug("l: {}", controller.l); Util::debug("l: {}", controller.l);
Util::debug("dp_right: {}", controller.dp_right); Util::debug("dp_right: {}", controller.dpRight);
Util::debug("dp_left: {}", controller.dp_left); Util::debug("dp_left: {}", controller.dpLeft);
Util::debug("dp_down: {}", controller.dp_down); Util::debug("dp_down: {}", controller.dpDown);
Util::debug("dp_up: {}", controller.dp_up); Util::debug("dp_up: {}", controller.dpUp);
Util::debug("z: {}", controller.z); Util::debug("z: {}", controller.z);
Util::debug("b: {}", controller.b); Util::debug("b: {}", controller.b);
Util::debug("a: {}", controller.a); Util::debug("a: {}", controller.a);
Util::debug("start: {}", controller.start); Util::debug("start: {}", controller.start);
Util::debug("joy_x: {}", controller.joy_x); Util::debug("joy_x: {}", controller.joyX);
Util::debug("joy_y: {}", controller.joy_y); Util::debug("joy_y: {}", controller.joyY);
} }
n64::Controller TasNextInputs() { n64::Controller MupenMovie::NextInputs() {
if (loaded_tas_movie_index + sizeof(TASMovieControllerData) > loaded_tas_movie_size) { if (loadedTasMovieIndex + sizeof(TASMovieControllerData) > loadedTasMovie.size()) {
loaded_tas_movie = nullptr; loadedTasMovie.clear();
n64::Controller empty_controller{}; n64::Controller emptyController{};
memset(&empty_controller, 0, sizeof(n64::Controller)); return emptyController;
return empty_controller;
} }
TASMovieControllerData movie_cdata{}; TASMovieControllerData movieCData{};
memcpy(&movie_cdata, loaded_tas_movie + loaded_tas_movie_index, sizeof(TASMovieControllerData)); memcpy(&movieCData, &loadedTasMovie[loadedTasMovieIndex], sizeof(TASMovieControllerData));
loaded_tas_movie_index += sizeof(TASMovieControllerData); loadedTasMovieIndex += sizeof(TASMovieControllerData);
n64::Controller controller{}; n64::Controller controller{};
memset(&controller, 0, sizeof(controller));
controller.c_right = movie_cdata.c_right; controller.cRight = movieCData.cRight;
controller.c_left = movie_cdata.c_left; controller.cLeft = movieCData.cLeft;
controller.c_down = movie_cdata.c_down; controller.cDown = movieCData.cDown;
controller.c_up = movie_cdata.c_up; controller.cUp = movieCData.cUp;
controller.r = movie_cdata.r; controller.r = movieCData.r;
controller.l = movie_cdata.l; controller.l = movieCData.l;
controller.dp_right = movie_cdata.dpad_right; controller.dpRight = movieCData.dpadRight;
controller.dp_left = movie_cdata.dpad_left; controller.dpLeft = movieCData.dpadLeft;
controller.dp_down = movie_cdata.dpad_down; controller.dpDown = movieCData.dpadDown;
controller.dp_up = movie_cdata.dpad_up; controller.dpUp = movieCData.dpadUp;
controller.z = movie_cdata.z; controller.z = movieCData.z;
controller.b = movie_cdata.b; controller.b = movieCData.b;
controller.a = movie_cdata.a; controller.a = movieCData.a;
controller.start = movie_cdata.start; controller.start = movieCData.start;
controller.joy_x = movie_cdata.analog_x; controller.joyX = movieCData.analogX;
controller.joy_y = movie_cdata.analog_y; controller.joyY = movieCData.analogY;
LogController(controller); LogController(controller);

View File

@@ -1,6 +1,60 @@
#pragma once #pragma once
#include <backend/core/mmio/PIF.hpp> #include <common.hpp>
#include <filesystem>
#include <vector>
void LoadTAS(const char* filename); namespace fs = std::filesystem;
n64::Controller TasNextInputs();
bool TasMovieLoaded(); namespace n64 {
struct Controller;
}
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);
struct MupenMovie {
MupenMovie() = default;
MupenMovie(const fs::path&);
bool Load(const fs::path&);
n64::Controller NextInputs();
bool IsLoaded() const { return !loadedTasMovie.empty(); }
private:
std::string filename = "";
std::string game = "";
std::vector<u8> loadedTasMovie = {};
TASMovieHeader loadedTasMovieHeader = {};
uint32_t loadedTasMovieIndex = 0;
};

View File

@@ -254,7 +254,7 @@ void FireException(Registers& regs, ExceptionCode code, int cop, s64 pc) {
bool old_exl = regs.cop0.status.exl; bool old_exl = regs.cop0.status.exl;
if(!regs.cop0.status.exl) { if(!regs.cop0.status.exl) {
if(regs.cop0.cause.branchDelay = regs.prevDelaySlot) { if((regs.cop0.cause.branchDelay = regs.prevDelaySlot)) {
pc -= 4; pc -= 4;
} }

View File

@@ -24,6 +24,10 @@ public:
SettingsWindow* settings; SettingsWindow* settings;
bool running = false; bool running = false;
bool LoadTAS(const fs::path& path) {
return core->LoadTAS(path);
}
void TogglePause() void TogglePause()
{ {
running = !running; running = !running;

View File

@@ -21,20 +21,20 @@ static inline nlohmann::json JSONOpenOrCreate(const std::string& path) {
json["audio"]["lock"] = true; json["audio"]["lock"] = true;
json["cpu"]["type"] = "interpreter"; json["cpu"]["type"] = "interpreter";
json["input"] = { json["input"] = {
{"A", ""}, {"A", "X"},
{"B", ""}, {"B", "C"},
{"Z", ""}, {"Z", "Z"},
{"Start", ""}, {"Start", "Enter"},
{"L", ""}, {"L", "A"},
{"R", ""}, {"R", "S"},
{"Dpad Up", ""}, {"Dpad Up", ""},
{"Dpad Down", ""}, {"Dpad Down", ""},
{"Dpad Left", ""}, {"Dpad Left", ""},
{"Dpad Right", ""}, {"Dpad Right", ""},
{"C Up", ""}, {"C Up", "I"},
{"C Down", ""}, {"C Down", "K"},
{"C Left", ""}, {"C Left", "J"},
{"C Right", ""}, {"C Right", "L"},
{"Analog Up", ""}, {"Analog Up", ""},
{"Analog Down", ""}, {"Analog Down", ""},
{"Analog Left", ""}, {"Analog Left", ""},

View File

@@ -3,6 +3,9 @@
#include <QApplication> #include <QApplication>
#include <QDropEvent> #include <QDropEvent>
#include <QMimeData> #include <QMimeData>
#include <filesystem>
namespace fs = std::filesystem;
KaizenQt::KaizenQt() noexcept : QWidget(nullptr) { KaizenQt::KaizenQt() noexcept : QWidget(nullptr) {
mainWindow = new MainWindowController(); mainWindow = new MainWindowController();
@@ -52,9 +55,13 @@ void KaizenQt::dropEvent(QDropEvent* event) {
LoadROM(path); LoadROM(path);
} }
void KaizenQt::LoadROM(const QString& file_name) noexcept { void KaizenQt::LoadROM(const QString& fileName) noexcept {
emuThread->start(); emuThread->start();
emuThread->core->LoadROM(file_name.toStdString()); emuThread->core->LoadROM(fileName.toStdString());
}
void KaizenQt::LoadTAS(const QString& fileName) noexcept {
emuThread->core->LoadTAS(fileName.toStdString());
} }
void KaizenQt::keyPressEvent(QKeyEvent *e) { void KaizenQt::keyPressEvent(QKeyEvent *e) {

View File

@@ -27,6 +27,7 @@ class KaizenQt : public QWidget {
Q_OBJECT Q_OBJECT
public: public:
KaizenQt() noexcept; KaizenQt() noexcept;
void LoadTAS(const QString& path) noexcept;
void LoadROM(const QString& path) noexcept; void LoadROM(const QString& path) noexcept;
void dropEvent(QDropEvent*) override; void dropEvent(QDropEvent*) override;
void dragEnterEvent(QDragEnterEvent*) override; void dragEnterEvent(QDragEnterEvent*) override;

View File

@@ -1,8 +1,6 @@
#include <KaizenQt.hpp> #include <KaizenQt.hpp>
#include <QApplication> #include <QApplication>
#include <QCommandLineParser> #include <QCommandLineParser>
#include <QCommandLineOption>
#include <MupenMovie.hpp>
int main(int argc, char** argv) { int main(int argc, char** argv) {
QApplication app(argc, argv); QApplication app(argc, argv);
@@ -20,7 +18,7 @@ int main(int argc, char** argv) {
if (parser.positionalArguments().size() > 0) { if (parser.positionalArguments().size() > 0) {
kaizenQt.LoadROM(parser.positionalArguments().first()); kaizenQt.LoadROM(parser.positionalArguments().first());
if (parser.positionalArguments().size() > 1) { if (parser.positionalArguments().size() > 1) {
LoadTAS(parser.positionalArguments()[1].toStdString().c_str()); kaizenQt.LoadTAS(parser.positionalArguments()[1]);
} }
} }