diff --git a/src/gui/gui.cc b/src/gui/gui.cc index fcf4f2c07..925b3c162 100644 --- a/src/gui/gui.cc +++ b/src/gui/gui.cc @@ -74,6 +74,7 @@ extern "C" { #include "imgui_internal.h" #include "imgui_stdlib.h" #include "json.hpp" +#include "lua/extra.h" #include "lua/glffi.h" #include "lua/luafile.h" #include "lua/luawrapper.h" @@ -427,7 +428,7 @@ void PCSX::GUI::init(std::function applyArguments) { m_luaConsole.setCmdExec([this, luaStdout](const std::string& cmd) { ScopedOnlyLog scopedOnlyLog(this); try { - g_emulator->m_lua->load(cmd, "console", false); + g_emulator->m_lua->load(cmd, "console:", false); g_emulator->m_lua->pcall(); for (const auto& error : m_glErrors) { m_luaConsole.addError(error); @@ -1047,6 +1048,7 @@ void PCSX::GUI::endFrame() { bool showOpenIsoFileDialog = false; bool showOpenBinaryDialog = false; + bool showOpenArchiveDialog = false; if (m_showMenu || !m_fullWindowRender || !PCSX::g_system->running()) { if (ImGui::BeginMainMenuBar()) { @@ -1059,6 +1061,9 @@ void PCSX::GUI::endFrame() { if (ImGui::MenuItem(_("Load binary"))) { showOpenBinaryDialog = true; } + if (ImGui::MenuItem(_("Add Lua archive"))) { + showOpenArchiveDialog = true; + } ImGui::Separator(); if (ImGui::MenuItem(_("Dump save state proto schema"))) { std::ofstream schema("sstate.proto"); @@ -1378,6 +1383,29 @@ in Configuration->Emulation, restart PCSX-Redux, then try again.)")); } } + if (showOpenArchiveDialog) { + if (!isoPath.empty()) { + m_openArchiveDialog.m_currentPath = isoPath.value; + } + m_openArchiveDialog.openDialog(); + } + if (m_openArchiveDialog.draw()) { + isoPath.value = m_openArchiveDialog.m_currentPath; + changed = true; + std::vector fileToOpen = m_openArchiveDialog.selected(); + if (!fileToOpen.empty()) { + IO file = new PosixFile(fileToOpen[0]); + try { + auto& archive = LuaFFI::addArchive(*g_emulator->m_lua, file); + if (!archive.failed()) { + g_system->log(LogClass::UI, "Added %s to our list of loaded archives.\n", + reinterpret_cast(fileToOpen[0].c_str())); + } + } catch (...) { + } + } + } + if (m_showDemo) ImGui::ShowDemoWindow(); ImGui::SetNextWindowPos(ImVec2(10, 20), ImGuiCond_FirstUseEver); @@ -2449,6 +2477,26 @@ void PCSX::GUI::magicOpen(const char* pathStr) { } } + // Maybe it's a zip file to add to our list of Lua archive ? + { + uint32_t signature = file->readAt(0); + try { + switch (signature) { + case 0x02014b50: + case 0x04034b50: + case 0x06054b50: { + auto& archive = LuaFFI::addArchive(*g_emulator->m_lua, file); + if (!archive.failed()) { + g_system->log(LogClass::UI, "Added %s to our list of loaded archives.\n", path.string()); + return; + } + break; + } + } + } catch (...) { + } + } + // Iso loader is last because its detection is the most broken at the moment. g_emulator->m_cdrom->setIso(new CDRIso(path)); g_emulator->m_cdrom->check(); @@ -2471,9 +2519,7 @@ std::string PCSX::GUI::getSaveStatePrefix(bool includeSeparator) { } } -std::string PCSX::GUI::getSaveStatePostfix() { - return ".sstate"; -} +std::string PCSX::GUI::getSaveStatePostfix() { return ".sstate"; } std::string PCSX::GUI::buildSaveStateFilename(int i) { return fmt::format("{}{}{}", getSaveStatePrefix(false), getSaveStatePostfix(), i); diff --git a/src/gui/gui.h b/src/gui/gui.h index ef4e9226c..79ffb94d3 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -375,6 +375,7 @@ class GUI final : public UI { Widgets::Disassembly m_disassembly = {settings.get().value}; Widgets::FileDialog<> m_openIsoFileDialog = {[]() { return _("Open Disk Image"); }}; Widgets::FileDialog<> m_openBinaryDialog = {[]() { return _("Open Binary"); }}; + Widgets::FileDialog<> m_openArchiveDialog = {[]() { return _("Open Archive"); }}; Widgets::FileDialog<> m_selectBiosDialog = {[]() { return _("Select BIOS"); }}; Widgets::FileDialog<> m_selectEXP1Dialog = {[]() { return _("Select EXP1"); }}; Widgets::NamedSaveStates m_namedSaveStates = {settings.get().value}; diff --git a/src/lua/extra.cc b/src/lua/extra.cc index f9bd0e69f..df428bae7 100644 --- a/src/lua/extra.cc +++ b/src/lua/extra.cc @@ -19,8 +19,83 @@ #include "lua/extra.h" +#include +#include +#include + #include "lua-protobuf/pb.h" +#include "lua/luafile.h" #include "lua/luawrapper.h" +#include "support/file.h" +#include "support/strings-helpers.h" +#include "support/zip.h" + +namespace { + +std::vector s_archives; +PCSX::File* load(std::string_view name, std::string_view from, bool inArchives = true) { + bool doRelative = false; + if (!from.empty() && (from[0] == '@')) { + from = from.substr(1); + doRelative = true; + } + std::filesystem::path fromPath(from); + std::filesystem::path absolutePath(name); + std::filesystem::path relativePath(fromPath.parent_path() / name); + relativePath = std::filesystem::weakly_canonical(relativePath); + + PCSX::File* file = nullptr; + + if (inArchives) { + for (auto archivei = s_archives.rbegin(); archivei != s_archives.rend(); archivei++) { + auto& archive = *archivei; + if (doRelative) { + file = archive.openFile(relativePath.string()); + if (!file->failed()) return file; + delete file; + } + file = archive.openFile(absolutePath.string()); + if (!file->failed()) return file; + delete file; + } + } else { + for (auto archivei = s_archives.rbegin(); archivei != s_archives.rend(); archivei++) { + auto& archive = *archivei; + std::filesystem::path fromPath(from); + std::filesystem::path relativePath(archive.archiveFilename().parent_path() / name); + relativePath = std::filesystem::weakly_canonical(relativePath); + file = archive.openFile(relativePath.string()); + if (!file->failed()) return file; + delete file; + file = archive.openFile(absolutePath.string()); + if (!file->failed()) return file; + delete file; + } + } + + if (doRelative) { + file = new PCSX::PosixFile(relativePath); + if (!file->failed()) return file; + delete file; + } + return new PCSX::PosixFile(absolutePath); +} + +} // namespace + +PCSX::ZipArchive& PCSX::LuaFFI::addArchive(Lua L, IO file) { + auto& newArchive = s_archives.emplace_back(file); + if (newArchive.failed()) { + s_archives.pop_back(); + throw std::runtime_error("Invalid zip file"); + } + IO autoexec = newArchive.openFile("autoexec.lua"); + if (!autoexec->failed()) { + std::string code = autoexec->readString(autoexec->size()); + L.load(code, fmt::format("{}:@autoexec.lua", file->filename().string()).c_str()); + } + return newArchive; +} void PCSX::LuaFFI::open_extra(Lua L) { L.getfieldtable("_LOADED", LUA_REGISTRYINDEX); @@ -76,5 +151,67 @@ void PCSX::LuaFFI::open_extra(Lua L) { L.load(protoc, "internal:lua-protobuf/protoc.lua"); L.setfield("protoc"); + L.load(R"( +Support.extra = { + +loadfile = function(name) + return loadstring(Support._internal.loadfile(name), '@' .. name) +end, + +dofile = function(name) + local func, msg = loadstring(Support._internal.loadfile(name), '@' .. name) + if func then return func() end + error(msg) +end, +} + +Support.extra.open = function(name) + return Support.File._createFileWrapper(ffi.cast('LuaFile*', Support._internal.open(name))) +end +)", + "internal:extra.lua"); + + L.getfieldtable("Support", LUA_GLOBALSINDEX); + L.getfieldtable("extra"); + L.declareFunc( + "addArchive", + [](lua_State* L_) -> int { + Lua L(L_); + auto ar = L.getinfo("S"); + auto name = L.tostring(); + IO file = load(name, ar.has_value() ? ar->source : ""); + if (file->failed()) return L.error("Unable to locate archive file"); + addArchive(L, file); + return 0; + }, + -1); + L.pop(); + L.getfieldtable("_internal"); + L.declareFunc( + "open", + [](lua_State* L_) -> int { + Lua L(L_); + auto ar = L.getinfo("S", 1); + auto name = L.tostring(); + IO file = load(name, ar.has_value() ? ar->source : ""); + L.push(new LuaFile(file)); + return 1; + }, + -1); + L.declareFunc( + "loadfile", + [](lua_State* L_) -> int { + Lua L(L_); + auto ar = L.getinfo("S", 2); + auto name = L.tostring(); + IO file = load(name, ar.has_value() ? ar->source : ""); + if (file->failed()) return L.error("Unable to locate file"); + L.push(file->readString(file->size())); + return 1; + }, + -1); + L.pop(); + L.pop(); + L.pop(); } diff --git a/src/lua/extra.h b/src/lua/extra.h index 16975c6f3..662466580 100644 --- a/src/lua/extra.h +++ b/src/lua/extra.h @@ -20,11 +20,14 @@ #pragma once #include "lua/luawrapper.h" +#include "support/file.h" +#include "support/zip.h" namespace PCSX { namespace LuaFFI { void open_extra(Lua); -} +ZipArchive& addArchive(Lua, IO); +} // namespace LuaFFI } // namespace PCSX diff --git a/src/lua/luawrapper.h b/src/lua/luawrapper.h index 78adf3ce7..e18210142 100644 --- a/src/lua/luawrapper.h +++ b/src/lua/luawrapper.h @@ -21,6 +21,7 @@ #include #include +#include #include #include @@ -234,6 +235,15 @@ class Lua { bool newmetatable(const char* name) { return luaL_newmetatable(L, name) != 0; } + std::optional getinfo(const char* what, int level = 1) { + lua_Debug ar; + int r = lua_getstack(L, level, &ar); + if (!r) return std::nullopt; + r = lua_getinfo(L, what, &ar); + if (!r) return std::nullopt; + return ar; + } + private: lua_State* L; }; diff --git a/src/main/main.cc b/src/main/main.cc index d47f71ba4..ecf20b4b1 100644 --- a/src/main/main.cc +++ b/src/main/main.cc @@ -33,6 +33,7 @@ #include "flags.h" #include "fmt/chrono.h" #include "gui/gui.h" +#include "lua/extra.h" #include "lua/luawrapper.h" #include "main/textui.h" #include "spu/interface.h" @@ -373,25 +374,30 @@ int pcsxMain(int argc, char **argv) { PCSX::g_system = nullptr; }); try { + auto &L = emulator->m_lua; // Before going into the main loop, let's first load all of the Lua files // from the command-line specified using the -dofile switch. + auto archives = args.values("archive"); + for (auto &archive : archives) { + PCSX::IO file = new PCSX::PosixFile(archive); + if (file->failed()) { + throw std::runtime_error(fmt::format("Couldn't load file {}", archive)); + } + PCSX::LuaFFI::addArchive(*L, file); + } auto dofiles = args.values("dofile"); + L->load("return function(name) Support.extra.dofile(name) end", "internal:"); for (auto &dofile : dofiles) { - std::string name = std::string(dofile); - std::ifstream in(name, std::ifstream::in); - if (!in) { - throw std::runtime_error("Couldn't load file " + name); - } - std::ostringstream code; - code << in.rdbuf(); - in.close(); - emulator->m_lua->load(code.str(), name.c_str()); + L->copy(-1); + L->push(dofile); + L->pcall(1); } + L->pop(); // Then run all of the Lua "exec" commands. auto luaexecs = args.values("exec"); for (auto &luaexec : luaexecs) { - emulator->m_lua->load(luaexec.data(), "cmdline"); + L->load(luaexec.data(), "cmdline:"); } system->m_inStartup = false; diff --git a/src/support/version-linux.cc b/src/support/version-linux.cc index e5b43ea7d..d130c951a 100644 --- a/src/support/version-linux.cc +++ b/src/support/version-linux.cc @@ -43,9 +43,9 @@ bool PCSX::Update::applyUpdate(const std::filesystem::path& binDir) { std::string filename; - zip.listAllFiles([&zip, &filename, &tmp](const std::string_view& name) { + zip.listAllFiles([&zip, &filename, &tmp](std::string_view name) { IO out(new UvFile(tmp / name, FileOps::TRUNCATE)); - IO in(zip.openFile(name)); + IO in(zip.openFile(std::string(name))); Slice data = in->read(in->size()); out->write(std::move(data)); filename = out->filename(); diff --git a/src/support/version-windows.cc b/src/support/version-windows.cc index 613033030..3427c5f19 100644 --- a/src/support/version-windows.cc +++ b/src/support/version-windows.cc @@ -74,10 +74,10 @@ bool PCSX::Update::applyUpdate(const std::filesystem::path& binDir) { script->writeString("$failed = $False\n"); unsigned count = 0; - zip.listAllFiles([&zip, &script, &tmp, &binDir, &count](const std::string_view& name) { + zip.listAllFiles([&zip, &script, &tmp, &binDir, &count](std::string_view name) { auto filename = tmp / ("pcsx-update-file-" + std::to_string(count++) + ".tmp"); IO out(new UvFile(filename, FileOps::TRUNCATE)); - IO in(zip.openFile(name)); + IO in(zip.openFile(std::string(name))); Slice data = in->read(in->size()); uint8_t digest[16]; MD5 md5; diff --git a/src/support/zip.cc b/src/support/zip.cc index 7644b6f47..fa0dbab02 100644 --- a/src/support/zip.cc +++ b/src/support/zip.cc @@ -110,7 +110,7 @@ PCSX::ZipArchive::ZipArchive(IO file) : m_file(file) { } } -void PCSX::ZipArchive::listFiles(std::function walker) { +void PCSX::ZipArchive::listFiles(std::function walker) { for (auto& file : m_files) { if (!file.isDirectory()) { if (!walker(file.name)) return; @@ -118,7 +118,7 @@ void PCSX::ZipArchive::listFiles(std::function wa } } -void PCSX::ZipArchive::listDirectories(std::function walker) { +void PCSX::ZipArchive::listDirectories(std::function walker) { for (auto& file : m_files) { if (file.isDirectory()) { if (!walker(std::string_view(file.name.c_str(), file.name.length() - 1))) return; @@ -126,7 +126,10 @@ void PCSX::ZipArchive::listDirectories(std::function file); bool failed() { return m_failed; } - void listAllFiles(std::function walker) { - listFiles([walker](const std::string_view &name) -> bool { + void listAllFiles(std::function walker) { + listFiles([walker](std::string_view name) -> bool { walker(name); return true; }); } - void listAllDirectories(std::function walker) { - listDirectories([walker](const std::string_view &name) -> bool { + void listAllDirectories(std::function walker) { + listDirectories([walker](std::string_view name) -> bool { walker(name); return true; }); } - void listFiles(std::function walker); - void listDirectories(std::function walker); - File *openFile(const std::string_view &path); + void listFiles(std::function walker); + void listDirectories(std::function walker); + File *openFile(std::string path); + + std::filesystem::path archiveFilename() { return m_file->filename(); } private: IO m_file;