Skip to content

Commit

Permalink
Ported old scripting PR
Browse files Browse the repository at this point in the history
  • Loading branch information
KiritoDv committed Jun 1, 2024
1 parent 53efc22 commit c3ea6a9
Show file tree
Hide file tree
Showing 13 changed files with 764 additions and 15 deletions.
27 changes: 25 additions & 2 deletions soh/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
cmake_minimum_required(VERSION 3.16.0 FATAL_ERROR)
include(FetchContent)

set(CMAKE_SYSTEM_VERSION 10.0 CACHE STRING "" FORCE)

Expand Down Expand Up @@ -177,6 +178,7 @@ source_group("soh\\Enhancements\\randomizer\\3drando" REGULAR_EXPRESSION "soh/En
source_group("soh\\Enhancements\\randomizer\\3drando\\hint_list" REGULAR_EXPRESSION "soh/Enhancements/randomizer/3drando/hint_list/*")
source_group("soh\\Enhancements\\randomizer\\3drando\\location_access" REGULAR_EXPRESSION "soh/Enhancements/randomizer/3drando/location_access/*")
source_group("soh\\Enhancements\\speechsynthesizer" REGULAR_EXPRESSION "soh/Enhancements/speechsynthesizer/*")
source_group("soh\\Enhancements\\scripting-layer" REGULAR_EXPRESSION "soh/Enhancements/scripting-layer/*")
source_group("soh\\Enhancements\\tts" REGULAR_EXPRESSION "soh/Enhancements/tts/*")

if (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
Expand Down Expand Up @@ -318,7 +320,6 @@ endif()
################################################################################
# Find/download Boost
################################################################################
include(FetchContent)
FetchContent_Declare(
Boost
URL https://archives.boost.io/release/1.81.0/source/boost_1_81_0.tar.gz
Expand Down Expand Up @@ -665,8 +666,25 @@ endif()
################################################################################
# Dependencies
################################################################################

FetchContent_Declare(
lua
GIT_REPOSITORY https://github.com/lua/lua
GIT_TAG 814213b65fa4ab2b1a7216d06f68a6f3df89efcd
)
FetchContent_MakeAvailable(lua)
add_library(lua STATIC)
file(GLOB lua_src ${FETCHCONTENT_BASE_DIR}/lua-src
"${FETCHCONTENT_BASE_DIR}/lua-src/*.c"
)
list(REMOVE_ITEM lua_src "${FETCHCONTENT_BASE_DIR}/lua-src/onelua.c")
list(REMOVE_ITEM lua_src "${FETCHCONTENT_BASE_DIR}/lua-src/lua.c")
list(REMOVE_ITEM lua_src "${FETCHCONTENT_BASE_DIR}/lua-src/ltests.c")
target_sources(lua PRIVATE ${lua_src})
target_include_directories(${PROJECT_NAME} PRIVATE ${FETCHCONTENT_BASE_DIR}/lua-src)

add_dependencies(${PROJECT_NAME}
libultraship
libultraship lua
)
if(NOT CMAKE_SYSTEM_NAME MATCHES "NintendoSwitch|CafeOS")
add_dependencies(${PROJECT_NAME}
Expand All @@ -681,6 +699,7 @@ if (CMAKE_SYSTEM_NAME STREQUAL "Windows")
"libultraship;"
"ZAPDLib;"
"glu32;"
"lua;"
"SDL2::SDL2;"
"SDL2::SDL2main;"
"$<$<BOOL:${BUILD_REMOTE_CONTROL}>:SDL2_net::SDL2_net-static>"
Expand All @@ -695,6 +714,7 @@ if (CMAKE_SYSTEM_NAME STREQUAL "Windows")
"libultraship;"
"ZAPDLib;"
"glu32;"
"lua;"
"SDL2::SDL2;"
"SDL2::SDL2main;"
"glfw;"
Expand All @@ -710,6 +730,7 @@ elseif(CMAKE_SYSTEM_NAME STREQUAL "NintendoSwitch")
find_package(Threads REQUIRED)
set(ADDITIONAL_LIBRARY_DEPENDENCIES
"libultraship;"
"lua;"
SDL2::SDL2
-lglad
Threads::Threads
Expand All @@ -718,6 +739,7 @@ elseif(CMAKE_SYSTEM_NAME STREQUAL "CafeOS")
find_package(SDL2 REQUIRED)
set(ADDITIONAL_LIBRARY_DEPENDENCIES
"libultraship;"
"lua;"
SDL2::SDL2-static

"$<$<CONFIG:Debug>:-Wl,--wrap=abort>"
Expand All @@ -731,6 +753,7 @@ else()
find_package(Threads REQUIRED)
set(ADDITIONAL_LIBRARY_DEPENDENCIES
"libultraship;"
"lua;"
"ZAPDLib;"
SDL2::SDL2
"$<$<BOOL:${BUILD_REMOTE_CONTROL}>:SDL2_net::SDL2_net>"
Expand Down
199 changes: 199 additions & 0 deletions soh/soh/Enhancements/scripting-layer/bridge.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@

#include "bridge.h"
#include "hosts/lua.h"
#include <Console.h>
#include <filesystem>
#include <fstream>
#include <sstream>
#include <string>
#include "soh/Enhancements/scripting-layer/exceptions/bridge.h"
#include "soh/Enhancements/scripting-layer/types/hostfunction.h"
#include "soh/Enhancements/game-interactor/GameInteractor.h"

#include <libultraship/bridge.h>
#include <libultraship/libultraship.h>
#include "exceptions/host-api.h"

#define CMD_REGISTER Ship::Context::GetInstance()->GetConsole()->AddCommand
#define ERROR_MESSAGE std::reinterpret_pointer_cast<Ship::ConsoleWindow>(Ship::Context::GetInstance()->GetWindow()->GetGui()->GetGuiWindow("Console"))->SendErrorMessage
#define INFO_MESSAGE std::reinterpret_pointer_cast<Ship::ConsoleWindow>(Ship::Context::GetInstance()->GetWindow()->GetGui()->GetGuiWindow("Console"))->SendInfoMessage

static bool RunScript(const std::shared_ptr<Ship::Console>&, const std::vector<std::string>& args, std::string*) {
if (args.size() > 1) {
auto path = std::filesystem::path("scripts") / args[1];
auto ext = path.extension().string().substr(1);

if(!exists(path)) {
ERROR_MESSAGE("Script not found: %s", args[1].c_str());
return true;
}

if(ext == "moon"){
ext = "lua";
}

std::ifstream file(path);
std::string content((std::istreambuf_iterator(file)), std::istreambuf_iterator<char>());

if(content.empty()) {
ERROR_MESSAGE("Script is empty: %s", args[1].c_str());
return true;
}

try {
uint16_t pid = GameBridge::Instance->Execute(content, ext);
INFO_MESSAGE("Script %s started with pid %d", args[1].c_str(), pid);
} catch (GameBridgeException& e) {
ERROR_MESSAGE("Error while executing script: %s", e.what());
return true;
} catch (HostAPIException& e) {
ERROR_MESSAGE("Error while executing script: %s", e.what());
return true;
}
} else {
ERROR_MESSAGE("Usage: run <path>");
return true;
}
return false;
}

static bool KillScript(const std::shared_ptr<Ship::Console>&, const std::vector<std::string>& args, std::string*) {
if (args.size() > 1) {
try {
const uint16_t pid = std::stoi(args[1]);
GameBridge::Instance->Kill(pid);
INFO_MESSAGE("Script with pid %d killed", pid);
} catch (std::invalid_argument&) {
ERROR_MESSAGE("Invalid pid: %s", args[1].c_str());
return true;
}
} else {
ERROR_MESSAGE("Usage: kill <pid>");
return true;
}
return false;
}

void GameBridge::Initialize() {
this->RegisterHost("lua", std::make_shared<LuaHost>());
// GameInteractorBridge::Initialize();
// N64Bridge::Initialize();
// ImGuiBridge::Initialize();
// SOHBridge::Initialize();

this->BindFunction("print", [](uintptr_t, MethodCall* method) {
const size_t count = method->ArgumentCount();
std::stringstream message;
for(size_t i = 0; i < count; i++) {
std::any value = method->RawArgument(static_cast<int>(i));
if (IS_TYPE(std::string, value)) {
message << std::any_cast<std::string>(value);
} else if (IS_TYPE(bool, value)) {
message << (std::any_cast<bool>(value) ? "true" : "false");
} else if (IS_TYPE(int, value)) {
message << std::any_cast<int>(value);
} else if (IS_TYPE(double, value)) {
message << std::any_cast<double>(value);
} else if (IS_TYPE(std::monostate, value) || IS_TYPE(nullptr, value)) {
message << "null";
} else {
message << "[" << value.type().name() << "]";
}
if(i < count - 1){
message << " ";
}
}
INFO_MESSAGE(message.str().c_str());
method->success();
});
// TODO: Find a way to automatically bind all hooks
this->BindFunction("hook", [](uintptr_t, MethodCall* method) {
const auto mode = method->GetArgument<std::string>(0);
const auto hook = method->GetArgument<std::string>(1);
if(mode == "add"){
auto function = method->GetArgument<HostFunction*>(2);
if(hook == "update"){
const size_t idx = GameInteractor::Instance->RegisterGameHook<GameInteractor::OnGameFrameUpdate>([function]() {
try {
function->execute();
} catch (HostAPIException& e) {
ERROR_MESSAGE("Error while executing script: %s", e.what());
}
});
method->success(static_cast<int>(idx));
return;
}
method->error("Unknown hook: " + hook);
return;
}
if(mode == "remove"){
const auto idx = method->GetArgument<int>(2);
if(hook == "update"){
GameInteractor::Instance->UnregisterGameHook<GameInteractor::OnGameFrameUpdate>(idx);
method->success();
return;
}
method->error("Unknown hook: " + hook);
return;
}
method->error("Unknown mode: " + mode);
}, "Game");

for (const auto& [fst, snd] : this->hosts) {
if(!snd->Initialize()){
throw GameBridgeException("Failed to initialize host api: " + fst);
}
}

CMD_REGISTER("run", { RunScript, "Tries to execute an script", {
{ "path", Ship::ArgumentType::TEXT, true }
}});
CMD_REGISTER("stop", { KillScript, "Tries to kill a program with its pid", {
{ "pid", Ship::ArgumentType::NUMBER, true }
}});
}

uint16_t GameBridge::Execute(const std::string& script, const std::string& hostname){
const auto host = this->hosts.find(hostname);
if(host == this->hosts.end()){
throw GameBridgeException("Host api not found: " + hostname);
}

const uint16_t pid = host->second->Execute(script);
this->pids[pid] = host->second;

return pid;
}

void GameBridge::Kill(const uint16_t pid) const {
if(!this->pids.contains(pid)){
return;
}

const auto host = this->pids.find(pid);
return host->second->Kill(pid);
}

void GameBridge::RegisterHost(const std::string& name, std::shared_ptr<HostAPI> host){
this->hosts[name] = std::move(host);
}

void GameBridge::Register(const std::vector<NamedEntry>& entries, const std::string& mod_name) const {
for (const auto& [fst, snd] : this->hosts) {
for (auto& [name, link] : entries) {
snd->Bind(name, { static_cast<BindingType>(link.index()), link, mod_name });
}
}
}

void GameBridge::BindField(const std::string& name, const std::any& field, const std::string& mod_name) const {
for(const auto& [fst, snd] : this->hosts){
snd->Bind(name, { BindingType::KField, field, mod_name });
}
}

void GameBridge::BindFunction(const std::string& name, FunctionPtr function, const std::string& mod_name) const {
for(const auto& [fst, snd] : this->hosts){
snd->Bind(name, { BindingType::KFunction, function, mod_name });
}
}
31 changes: 31 additions & 0 deletions soh/soh/Enhancements/scripting-layer/bridge.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#pragma once

#include "./hosts/hostapi.h"
#include "types/methodcall.h"
#include <unordered_map>
#include <string>

#define BIND_FUNCTION(name, ptr) NamedEntry { name, (FunctionPtr)ptr }
#define BIND_FIELD(name, field) NamedEntry { name, field }
#define AUTOBIND_FIELD(field) NamedEntry { #field, field }

struct NamedEntry {
const std::string& name;
const std::variant<FunctionPtr, std::any> link;
};

class GameBridge {
std::unordered_map<std::string, std::shared_ptr<HostAPI>> hosts;
std::unordered_map<uint16_t, std::shared_ptr<HostAPI>> pids;
void RegisterHost(const std::string& name, std::shared_ptr<HostAPI> host);
void BindField(const std::string& name, const std::any& field, const std::string& mod_name = ROOT_MODULE) const;
void BindFunction(const std::string& name, FunctionPtr function, const std::string& mod_name = ROOT_MODULE) const;
public:
static GameBridge* Instance;
void Initialize();
uint16_t Execute(const std::string& script, const std::string& hostname);

// Static Methods
void Register(const std::vector<NamedEntry>& entries, const std::string& mod_name = ROOT_MODULE) const;
void Kill(uint16_t pid) const;
};
13 changes: 13 additions & 0 deletions soh/soh/Enhancements/scripting-layer/exceptions/bridge.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#pragma once

#include <exception>
#include <string>

class GameBridgeException : public std::exception {
std::string mMessage;
public:
explicit GameBridgeException(std::string message) : mMessage(std::move(message)) {}
[[nodiscard]] const char* what() const noexcept override {
return mMessage.c_str();
}
};
13 changes: 13 additions & 0 deletions soh/soh/Enhancements/scripting-layer/exceptions/host-api.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#pragma once

#include <exception>
#include <string>

class HostAPIException : public std::exception {
std::string mMessage;
public:
explicit HostAPIException(std::string message) : mMessage(std::move(message)) {}
[[nodiscard]] const char* what() const noexcept override {
return mMessage.c_str();
}
};
20 changes: 20 additions & 0 deletions soh/soh/Enhancements/scripting-layer/hosts/hostapi.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#pragma once

#include <any>
#include <vector>

struct GameBinding;

#define IS_TYPE(t, value) value.type() == typeid(t)
#define ROOT_MODULE "root"

class HostAPI {
public:
virtual bool Initialize() = 0;
virtual void Bind(std::string name, GameBinding binding) = 0;
virtual void Call(uintptr_t context, uintptr_t function, const std::vector<std::any>& arguments) = 0;
virtual std::any GetArgument(int index, uintptr_t context) = 0;
virtual size_t GetArgumentCount(uintptr_t context) = 0;
virtual uint16_t Execute(const std::string& script) = 0;
virtual void Kill(uint16_t pid) = 0;
};
Loading

0 comments on commit c3ea6a9

Please sign in to comment.