forked from HarbourMasters/Shipwright
-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
13 changed files
with
764 additions
and
15 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 }); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
13
soh/soh/Enhancements/scripting-layer/exceptions/host-api.h
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | ||
} | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
}; |
Oops, something went wrong.