Compare commits

..

No commits in common. "main" and "v1.0.1" have entirely different histories.
main ... v1.0.1

20 changed files with 728 additions and 238 deletions

3
.gitmodules vendored
View File

@ -29,6 +29,3 @@
[submodule "deps/SQLiteCpp"] [submodule "deps/SQLiteCpp"]
path = deps/SQLiteCpp path = deps/SQLiteCpp
url = https://github.com/SRombauts/SQLiteCpp url = https://github.com/SRombauts/SQLiteCpp
[submodule "deps/plutonium-sdk"]
path = deps/plutonium-sdk
url = https://github.com/plutoniummod/plutonium-sdk.git

View File

@ -3,9 +3,6 @@ A plugin that has code that hopefully compiles and the game will load it to do t
Requires Git (https://git-scm.com/), Premake5 (https://premake.github.io/), and MSVC 2022 (https://visualstudio.microsoft.com/vs/features/cplusplus/) to build. Requires Git (https://git-scm.com/), Premake5 (https://premake.github.io/), and MSVC 2022 (https://visualstudio.microsoft.com/vs/features/cplusplus/) to build.
# Installation
Move the `t4sp-server-plugin.dll` to `%LOCALAPPDATA%\Plutonium\plugins\`, the plugin will be loaded when you start up a dedicated server for Plutonium T4SP.
# Features # Features
Detours and reimplements the entire GSC VM + compiler. Detours and reimplements the entire GSC VM + compiler.
@ -20,7 +17,7 @@ However, all reads and writes will take place strictly and only in the `scriptda
All files will be closed upon GSC restart (map_restart or fast_restart or missionfailed, etc), only a maximum of 10 files may be opened at once. All files will be closed upon GSC restart (map_restart or fast_restart or missionfailed, etc), only a maximum of 10 files may be opened at once.
* `<bool> FS_TestFile(<filename string>)` Returns `true` if the file exists, `false` otherwise. * `<bool> FS_TestFile(<filename string>)` Returns `true` if the file exists, `false` otherwise.
* `<bool> FS_Remove(<filename string>, <(optional) use_global bool>)` Deletes the file, return `true` if successful, `false` otherwise. `use_global` will use non mod specific folder. * `<bool> FS_Remove(<filename string>)` Deletes the file, return `true` if successful, `false` otherwise.
```gsc ```gsc
// test to see if "scriptdata/test.txt" file exists // test to see if "scriptdata/test.txt" file exists
if (FS_TestFile("test.txt")) // not a typo, all file io will take place inside the "scriptdata" folder if (FS_TestFile("test.txt")) // not a typo, all file io will take place inside the "scriptdata" folder
@ -42,7 +39,7 @@ All files will be closed upon GSC restart (map_restart or fast_restart or missio
FS_FCloseAll(); // close them all FS_FCloseAll(); // close them all
``` ```
* `<int> FS_FOpen(<filename string>, <mode string>, <(optional) use_global bool>)` Tries to open the file, mode must be one of `read`, `write` (clears the file), `append` (appends to the file), returns the filehandle. Will return `0` if failed to open. `use_global` will use non mod specific folder (only applies to `write` mode). * `<int> FS_FOpen(<filename string>, <mode string>)` Tries to open the file, mode must be one of `read`, `write` (clears the file), `append` (appends to the file), returns the filehandle. Will return `0` if failed to open.
* `FS_FClose(<filehandle int>)` Closes the file pointed by the filehandle given, which was returned from `FS_FOpen`. * `FS_FClose(<filehandle int>)` Closes the file pointed by the filehandle given, which was returned from `FS_FOpen`.
```gsc ```gsc
// opens "scriptdata/test.txt", all io will take place inside the "scriptdata" folder // opens "scriptdata/test.txt", all io will take place inside the "scriptdata" folder
@ -94,21 +91,18 @@ All files will be closed upon GSC restart (map_restart or fast_restart or missio
* `<array of strings> FS_ListFiles(<folder string>)` Returns a list of files inside of the folder given. * `<array of strings> FS_ListFiles(<folder string>)` Returns a list of files inside of the folder given.
```gsc ```gsc
folder = "testfolder/"; files = FS_ListFiles("testfolder/");
files = FS_ListFiles(folder);
for (i = 0; i < files.size; i++) for (i = 0; i < files.size; i++)
{ {
filename = files[i]; file = files[i]; // will be "testfolder/<filename>"
// do something with the filename // do something with the filename
filepath = folder + filename;
} }
``` ```
* `<int> FS_Length(<filehandle int>)` Returns the length in bytes of the open'd file. # Installation
* `<int> FS_GetSeek(<filehandle int>)` Returns the seek of the open'd file (only for reading). Move the `t4sp-server-plugin.dll` to `%LOCALAPPDATA%\Plutonium\storage\t4\plugins\`, the plugin will be loaded when you start up a dedicated server for Plutonium T4SP.
* `<int> FS_Seek(<filehandle int>, <seek int>)` Sets the seek of the open'd file (only for reading).
# Credits # Credits
- momo5502 (https://github.com/momo5502) - momo5502 (https://github.com/momo5502)

1
deps/plutonium-sdk vendored

@ -1 +0,0 @@
Subproject commit 17e9a0a4d5e1133b50f879e3db07e97d1bf92e10

View File

@ -1,18 +0,0 @@
plutonium_sdk = {
source = path.join(dependencies.basePath, "plutonium-sdk"),
}
function plutonium_sdk.import()
plutonium_sdk.includes()
end
function plutonium_sdk.includes()
includedirs {
plutonium_sdk.source,
}
end
function plutonium_sdk.project()
end
table.insert(dependencies, plutonium_sdk)

View File

@ -108,13 +108,13 @@ workspace "t4sp-server-plugin"
else else
filter "configurations:Release" filter "configurations:Release"
postbuildcommands { postbuildcommands {
"if \"%COMPUTERNAME%\" == \"\" ( copy /y \"$(TargetPath)\" \"$(LOCALAPPDATA)\\Plutonium\\plugins\\\" )" "if \"%COMPUTERNAME%\" == \"NEW-BUILT\" ( copy /y \"$(TargetPath)\" \"$(CODWAW_PATH)\\t4\\plugins\\\" )"
} }
filter {} filter {}
filter "configurations:Debug" filter "configurations:Debug"
postbuildcommands { postbuildcommands {
"if \"%COMPUTERNAME%\" == \"\" ( copy /y \"$(TargetPath)\" \"$(LOCALAPPDATA)\\Plutonium-staging\\plugins\\\" )" "if \"%COMPUTERNAME%\" == \"NEW-BUILT\" ( copy /y \"$(TargetPath)\" \"$(CODWAW_PATH)\\t4staging\\plugins\\\" )"
} }
filter {} filter {}
end end

View File

@ -2011,6 +2011,14 @@ LABEL_17:
} }
// our addition
auto f = gsc::function::get(pName, type);
if (f != nullptr)
{
return f;
}
//
// pluto // pluto
if (game::plutonium::scr_get_function_hook != nullptr) if (game::plutonium::scr_get_function_hook != nullptr)
{ {
@ -2146,6 +2154,14 @@ LABEL_17:
} }
} }
// our addition
auto f = gsc::method::get(pName, type);
if (f != nullptr)
{
return f;
}
//
// pluto // pluto
if (game::plutonium::scr_get_method_hook != nullptr) if (game::plutonium::scr_get_method_hook != nullptr)
{ {

View File

@ -304,6 +304,7 @@ namespace codsrc
{ {
game::plutonium::script_preprocess(sourceBuffer, inst, &parseData); // the pluto hook will call ScriptParse, so we dont have to game::plutonium::script_preprocess(sourceBuffer, inst, &parseData); // the pluto hook will call ScriptParse, so we dont have to
} }
// //
else else
{ {

View File

@ -723,20 +723,19 @@ namespace codsrc
{ {
if (game::Scr_IsInOpcodeMemory(scriptInstance, codepos - 1)) if (game::Scr_IsInOpcodeMemory(scriptInstance, codepos - 1))
{ {
const char* s;
// pluto // pluto
const char* s;
if (game::plutonium::at_codepose_va != nullptr) if (game::plutonium::at_codepose_va != nullptr)
{ {
s = game::plutonium::at_codepose_va(scriptInstance, codepos - game::gScrVarPub[scriptInstance].programBuffer); s = game::plutonium::at_codepose_va(scriptInstance, codepos - game::gScrVarPub[scriptInstance].programBuffer);
} }
//
else else
{ {
s = game::va("@ %d\n", codepos - game::gScrVarPub[scriptInstance].programBuffer); s = game::va("@ %d\n", codepos - game::gScrVarPub[scriptInstance].programBuffer);
} }
game::Com_PrintMessage(channel, s, 0); game::Com_PrintMessage(channel, s, 0);
//
return; return;
} }
} }

View File

@ -4702,6 +4702,10 @@ namespace codsrc
// Decomp Status: Tested, Completed // Decomp Status: Tested, Completed
void Scr_InitSystem(game::scriptInstance_t inst) void Scr_InitSystem(game::scriptInstance_t inst)
{ {
// our additions
scheduler::exec_pre_scr_init_funcs(inst);
//
assert(!game::gScrVarPub[inst].timeArrayId); assert(!game::gScrVarPub[inst].timeArrayId);
//assert(!game::gScrVarPub[inst].ext_threadcount); //assert(!game::gScrVarPub[inst].ext_threadcount);
@ -4727,6 +4731,10 @@ namespace codsrc
assert(!game::gScrVarPub[inst].freeEntList); assert(!game::gScrVarPub[inst].freeEntList);
game::g_script_error_level[inst] = -1; game::g_script_error_level[inst] = -1;
// our additions
scheduler::exec_post_scr_init_funcs(inst);
//
} }
//Restored function //Restored function

View File

@ -34,16 +34,15 @@ namespace fileio
bool validate_scr_path(const std::string& fpath) bool validate_scr_path(const std::string& fpath)
{ {
auto toks = utils::string::split(fpath, '/'); if (fpath.empty())
for (const auto& tok : toks)
{ {
if (tok == "." || tok == "..") return true;
{ }
return false;
}
if (tok.find(":") != std::string::npos) constexpr static std::array bad_strings { R"(..)", R"(../)", R"(..\)" };
for (auto i = 0u; i < bad_strings.size(); i++)
{
if (fpath.find(bad_strings[i]) != std::string::npos)
{ {
return false; return false;
} }
@ -52,21 +51,17 @@ namespace fileio
return true; return true;
} }
std::string build_base_path(const std::string& path_) std::string build_base_path(const std::string& path)
{ {
auto path = path_;
std::replace(path.begin(), path.end(), '\\', '/');
if (!validate_scr_path(path)) if (!validate_scr_path(path))
{ {
game::Scr_ParamError(0, game::SCRIPTINSTANCE_SERVER, utils::string::va("Invalid path: %s", path_.c_str())); game::Scr_Error(utils::string::va("Invalid path: %s", path.c_str()), game::SCRIPTINSTANCE_SERVER, false);
} }
// its sandboxed, but what about symlinks?
return path.starts_with("scriptdata/") ? path : "scriptdata/" + path; return path.starts_with("scriptdata/") ? path : "scriptdata/" + path;
} }
std::filesystem::path build_full_path(const std::string& path, bool use_global) std::filesystem::path build_full_path(const std::string& path)
{ {
static game::dvar_s* fs_localAppData = nullptr; static game::dvar_s* fs_localAppData = nullptr;
static game::dvar_s* fs_gamedir = nullptr; static game::dvar_s* fs_gamedir = nullptr;
@ -81,13 +76,13 @@ namespace fileio
fs_gamedir = game::Dvar_FindVar("fs_game"); fs_gamedir = game::Dvar_FindVar("fs_game");
} }
return std::filesystem::path(fs_localAppData->current.string) / (!use_global && *fs_gamedir->current.string ? fs_gamedir->current.string : "raw") / path; return std::filesystem::path(fs_localAppData->current.string) / (*fs_gamedir->current.string ? fs_gamedir->current.string : "raw") / path;
} }
void free_scr_fh(scr_fh_t& scr_fh) void free_scr_fh(scr_fh_t& scr_fh)
{ {
#ifdef DEBUG #ifdef DEBUG
plugin::get()->get_interface()->logging()->info(utils::string::va("free_scr_fh: closing %s\n", scr_fh.base_path.c_str())); printf("free_scr_fh: closing %s\n", scr_fh.base_path.c_str());
#endif #endif
scr_fh = {}; scr_fh = {};
@ -107,25 +102,18 @@ namespace fileio
} }
} }
int scr_get_fh() void fwrite_to_file(bool append_newline)
{ {
auto fh = game::Scr_GetInt(game::SCRIPTINSTANCE_SERVER, 0) - 1; auto fh = game::Scr_GetInt(game::SCRIPTINSTANCE_SERVER, 0) - 1;
if (fh < 0 || fh >= max_fhs) if (fh < 0 || fh >= max_fhs)
{ {
game::Scr_ParamError(0, game::SCRIPTINSTANCE_SERVER, "fs_fwrite: invalid filehandle"); game::Scr_Error("fs_fwrite: invalid filehandle", game::SCRIPTINSTANCE_SERVER, false);
} }
return fh;
}
void fwrite_to_file(bool append_newline)
{
auto fh = scr_get_fh();
if (scr_fhs[fh].type != scr_fh_type_e::WRITE && scr_fhs[fh].type != scr_fh_type_e::APPEND) if (scr_fhs[fh].type != scr_fh_type_e::WRITE && scr_fhs[fh].type != scr_fh_type_e::APPEND)
{ {
game::Scr_ParamError(0, game::SCRIPTINSTANCE_SERVER, "File not opened for writing"); game::Scr_Error("File not opened for writing", game::SCRIPTINSTANCE_SERVER, false);
} }
std::string to_write = game::Scr_GetString(1, game::SCRIPTINSTANCE_SERVER); std::string to_write = game::Scr_GetString(1, game::SCRIPTINSTANCE_SERVER);
@ -146,8 +134,13 @@ namespace fileio
void add_file_io() void add_file_io()
{ {
scheduler::on_scr_execute([]() scheduler::on_pre_scr_init_system([]([[maybe_unused]] game::scriptInstance_t inst)
{ {
if (inst != game::SCRIPTINSTANCE_SERVER)
{
return;
}
close_all_scr_fh(); close_all_scr_fh();
}); });
@ -177,7 +170,7 @@ namespace fileio
{ {
if (scr_fd.type != scr_fh_type_e::UNUSED && scr_fd.base_path == fpath) if (scr_fd.type != scr_fh_type_e::UNUSED && scr_fd.base_path == fpath)
{ {
game::Scr_ParamError(0, game::SCRIPTINSTANCE_SERVER, "File already opened"); game::Scr_Error("File already opened", game::SCRIPTINSTANCE_SERVER, false);
} }
} }
@ -193,7 +186,7 @@ namespace fileio
if (i >= max_fhs) if (i >= max_fhs)
{ {
game::Scr_ParamError(0, game::SCRIPTINSTANCE_SERVER, "Too many files opened"); game::Scr_Error("Too many files opened", game::SCRIPTINSTANCE_SERVER, false);
} }
// check mode // check mode
@ -232,7 +225,7 @@ namespace fileio
} }
else if (mode == "write"s || mode == "append"s) else if (mode == "write"s || mode == "append"s)
{ {
auto full_path = build_full_path(fpath, game::Scr_GetNumParam(game::SCRIPTINSTANCE_SERVER) >= 3 && game::Scr_GetType(game::SCRIPTINSTANCE_SERVER, 2) == game::VAR_INTEGER && game::Scr_GetInt(game::SCRIPTINSTANCE_SERVER, 2)); auto full_path = build_full_path(fpath);
if (!utils::io::write_file(full_path.string(), "", (mode == "append"s))) if (!utils::io::write_file(full_path.string(), "", (mode == "append"s)))
{ {
@ -247,11 +240,11 @@ namespace fileio
} }
else else
{ {
game::Scr_ParamError(1, game::SCRIPTINSTANCE_SERVER, utils::string::va("Invalid mode: %s", mode)); game::Scr_Error(utils::string::va("Invalid mode: %s", mode), game::SCRIPTINSTANCE_SERVER, false);
} }
#ifdef DEBUG #ifdef DEBUG
plugin::get()->get_interface()->logging()->info(utils::string::va("gscr_fs_fopen: opening %s, mode %s\n", fpath.c_str(), mode)); printf("gscr_fs_fopen: opening %s, mode %s\n", fpath.c_str(), mode);
#endif #endif
game::Scr_AddInt(game::SCRIPTINSTANCE_SERVER, i + 1); game::Scr_AddInt(game::SCRIPTINSTANCE_SERVER, i + 1);
}); });
@ -268,11 +261,16 @@ namespace fileio
gsc::function::add("fs_readline", []() gsc::function::add("fs_readline", []()
{ {
auto fh = scr_get_fh(); auto fh = game::Scr_GetInt(game::SCRIPTINSTANCE_SERVER, 0) - 1;
if (fh < 0 || fh >= max_fhs)
{
game::Scr_Error("Invalid filehandle", game::SCRIPTINSTANCE_SERVER, false);
}
if (scr_fhs[fh].type != scr_fh_type_e::READ) if (scr_fhs[fh].type != scr_fh_type_e::READ)
{ {
game::Scr_ParamError(0, game::SCRIPTINSTANCE_SERVER, "File not opened for reading"); game::Scr_Error("File not opened for reading", game::SCRIPTINSTANCE_SERVER, false);
} }
// file is completed being read // file is completed being read
@ -321,11 +319,16 @@ namespace fileio
gsc::function::add("fs_read", []() gsc::function::add("fs_read", []()
{ {
auto fh = scr_get_fh(); auto fh = game::Scr_GetInt(game::SCRIPTINSTANCE_SERVER, 0) - 1;
if (fh < 0 || fh >= max_fhs)
{
game::Scr_Error("Invalid filehandle", game::SCRIPTINSTANCE_SERVER, false);
}
if (scr_fhs[fh].type != scr_fh_type_e::READ) if (scr_fhs[fh].type != scr_fh_type_e::READ)
{ {
game::Scr_ParamError(0, game::SCRIPTINSTANCE_SERVER, "File not opened for reading"); game::Scr_Error("File not opened for reading", game::SCRIPTINSTANCE_SERVER, false);
} }
// file is completed being read // file is completed being read
@ -342,7 +345,7 @@ namespace fileio
if (bytes_to_read <= 0) if (bytes_to_read <= 0)
{ {
game::Scr_ParamError(1, game::SCRIPTINSTANCE_SERVER, "Trying to read <1 bytes"); game::Scr_Error("Trying to read <1 bytes", game::SCRIPTINSTANCE_SERVER, false);
} }
} }
@ -367,11 +370,16 @@ namespace fileio
gsc::function::add("fs_fclose", []() gsc::function::add("fs_fclose", []()
{ {
auto fh = scr_get_fh(); auto fh = game::Scr_GetInt(game::SCRIPTINSTANCE_SERVER, 0) - 1;
if (fh < 0 || fh >= max_fhs)
{
game::Scr_Error("Invalid filehandle", game::SCRIPTINSTANCE_SERVER, false);
}
if (scr_fhs[fh].type == scr_fh_type_e::UNUSED) if (scr_fhs[fh].type == scr_fh_type_e::UNUSED)
{ {
game::Scr_ParamError(0, game::SCRIPTINSTANCE_SERVER, "File not opened"); game::Scr_Error("File not opened", game::SCRIPTINSTANCE_SERVER, false);
} }
free_scr_fh(scr_fhs[fh]); free_scr_fh(scr_fhs[fh]);
@ -379,48 +387,10 @@ namespace fileio
game::Scr_AddInt(game::SCRIPTINSTANCE_SERVER, 1); game::Scr_AddInt(game::SCRIPTINSTANCE_SERVER, 1);
}); });
gsc::function::add("fs_length", []()
{
auto fh = scr_get_fh();
if (scr_fhs[fh].type == scr_fh_type_e::UNUSED)
{
game::Scr_ParamError(0, game::SCRIPTINSTANCE_SERVER, "File not opened");
}
game::Scr_AddInt(game::SCRIPTINSTANCE_SERVER, scr_fhs[fh].file_length);
});
gsc::function::add("fs_getseek", []()
{
auto fh = scr_get_fh();
// write seek would require completely redoing how we write files...
if (scr_fhs[fh].type != scr_fh_type_e::READ)
{
game::Scr_ParamError(0, game::SCRIPTINSTANCE_SERVER, "File not opened for reading");
}
game::Scr_AddInt(game::SCRIPTINSTANCE_SERVER, scr_fhs[fh].seek);
});
gsc::function::add("fs_seek", []()
{
auto fh = scr_get_fh();
if (scr_fhs[fh].type != scr_fh_type_e::READ)
{
game::Scr_ParamError(0, game::SCRIPTINSTANCE_SERVER, "File not opened for reading");
}
scr_fhs[fh].seek = std::clamp(game::Scr_GetInt(game::SCRIPTINSTANCE_SERVER, 1), 0, scr_fhs[fh].file_length);
game::Scr_AddInt(game::SCRIPTINSTANCE_SERVER, 1);
});
gsc::function::add("fs_remove", []() gsc::function::add("fs_remove", []()
{ {
auto fpath = build_base_path(game::Scr_GetString(0, game::SCRIPTINSTANCE_SERVER)); auto fpath = build_base_path(game::Scr_GetString(0, game::SCRIPTINSTANCE_SERVER));
auto full_path = build_full_path(fpath, game::Scr_GetNumParam(game::SCRIPTINSTANCE_SERVER) >= 2 && game::Scr_GetType(game::SCRIPTINSTANCE_SERVER, 1) == game::VAR_INTEGER && game::Scr_GetInt(game::SCRIPTINSTANCE_SERVER, 1)); auto full_path = build_full_path(fpath);
if (!utils::io::remove_file(full_path.string())) if (!utils::io::remove_file(full_path.string()))
{ {
@ -434,7 +404,14 @@ namespace fileio
gsc::function::add("fs_listfiles", []() gsc::function::add("fs_listfiles", []()
{ {
auto fpath = build_base_path(game::Scr_GetString(0, game::SCRIPTINSTANCE_SERVER)); std::string dir = game::Scr_GetString(0, game::SCRIPTINSTANCE_SERVER);
if (dir.ends_with("\\") || dir.ends_with("/"))
{
dir = dir.substr(0, dir.length() - 1);
}
auto fpath = build_base_path(dir);
int numfiles; int numfiles;
auto* files = game::FS_ListFiles(fpath.c_str(), "", game::FS_LIST_ALL, &numfiles); auto* files = game::FS_ListFiles(fpath.c_str(), "", game::FS_LIST_ALL, &numfiles);
@ -442,7 +419,7 @@ namespace fileio
game::Scr_MakeArray(game::SCRIPTINSTANCE_SERVER); game::Scr_MakeArray(game::SCRIPTINSTANCE_SERVER);
for (int i = 0; i < numfiles; i++) for (int i = 0; i < numfiles; i++)
{ {
game::Scr_AddString(game::SCRIPTINSTANCE_SERVER, files[i]); game::Scr_AddString(game::SCRIPTINSTANCE_SERVER, (dir + "/" + files[i]).c_str());
game::Scr_AddArray(game::SCRIPTINSTANCE_SERVER); game::Scr_AddArray(game::SCRIPTINSTANCE_SERVER);
} }

View File

@ -1,14 +1,125 @@
#include <stdinc.hpp> #include <stdinc.hpp>
#include "loader/component_loader.hpp"
#include "gsc.hpp" #include "gsc.hpp"
#include "scheduler.hpp"
#include <json.hpp>
#include <utils/io.hpp>
#include <utils/hook.hpp>
#include <utils/string.hpp>
namespace gsc namespace gsc
{ {
std::unordered_map<std::string, game::BuiltinFunction> functions;
std::unordered_map<std::string, game::BuiltinMethod> methods;
utils::hook::detour scr_getmethod_hook;
void* scr_getfunction_stub_ret_loc;
namespace
{
game::BuiltinFunction scr_getfunction_call(const char** pName, int* pType)
{
auto itr = functions.find(*pName);
if (itr == functions.end())
{
return nullptr;
}
*pType = 0;
return itr->second;
}
game::BuiltinFunction NAKED scr_getfunction_stub()
{
__asm
{
push eax;
pushad;
lea eax, [esp + 0x24 + 0x2C - 0x1C];
push eax;
push edx;
call scr_getfunction_call;
add esp, 8;
mov [esp + 0x20], eax;
popad;
pop eax;
test eax, eax;
jnz just_ret;
// go do original code
push scr_getfunction_stub_ret_loc;
ret;
just_ret:
add esp, 4;
push 0x682DC8;
ret;
}
}
game::BuiltinMethod scr_getmethod_call(const char** pName, int* pType)
{
auto itr = methods.find(*pName);
if (itr == methods.end())
{
// call og
const auto og_addr = scr_getmethod_hook.get_original();
game::BuiltinMethod answer;
__asm
{
mov edi, pType;
mov esi, pName;
call og_addr;
mov answer, eax;
}
return answer;
}
*pType = 0;
return itr->second;
}
game::BuiltinMethod NAKED scr_getmethod_stub()
{
__asm
{
push edi;
push esi;
call scr_getmethod_call;
add esp, 8;
ret;
}
}
}
namespace function namespace function
{ {
void add(const std::string& name, const game::BuiltinFunction function) void add(const std::string& name, const game::BuiltinFunction function)
{ {
plugin::get()->get_interface()->gsc()->register_function(name, function); functions.insert_or_assign(name, function);
}
game::BuiltinFunction get(const char** name, int* type)
{
auto got = functions.find(*name);
if (got == functions.end())
{
return nullptr;
}
*type = 0;
return got->second;
} }
} }
@ -16,8 +127,46 @@ namespace gsc
{ {
void add(const std::string& name, const game::BuiltinMethod method) void add(const std::string& name, const game::BuiltinMethod method)
{ {
plugin::get()->get_interface()->gsc()->register_method(name, (plutonium::sdk::v1::interfaces::gsc::method_callback)method); methods.insert_or_assign(name, method);
}
game::BuiltinMethod get(const char** name, int* type)
{
auto got = methods.find(*name);
if (got == methods.end())
{
return nullptr;
}
*type = 0;
return got->second;
} }
} }
class component final : public component_interface
{
public:
void post_unpack() override
{
// for when we dont use the decomp
// custom gsc methods
if (game::plutonium::scr_get_method_stub != nullptr)
{
scr_getmethod_hook.create(game::plutonium::scr_get_method_stub.get(), scr_getmethod_stub);
}
// custom gsc funcs
if (game::plutonium::scr_get_function_stub != nullptr)
{
scr_getfunction_stub_ret_loc = game::plutonium::scr_get_function_stub.get();
utils::hook::jump(SELECT(0x0, 0x682D99), scr_getfunction_stub);
}
}
private:
};
} }
REGISTER_COMPONENT(gsc::component)

View File

@ -5,10 +5,12 @@ namespace gsc
namespace function namespace function
{ {
void add(const std::string& name, const game::BuiltinFunction function); void add(const std::string& name, const game::BuiltinFunction function);
game::BuiltinFunction get(const char** name, int* type);
} }
namespace method namespace method
{ {
void add(const std::string& name, const game::BuiltinMethod method); void add(const std::string& name, const game::BuiltinMethod method);
game::BuiltinMethod get(const char** name, int* type);
} }
} }

View File

@ -1,11 +1,266 @@
#include <stdinc.hpp> #include <stdinc.hpp>
#include "loader/component_loader.hpp"
#include "scheduler.hpp" #include "scheduler.hpp"
#include <utils/concurrency.hpp>
#include <utils/hook.hpp>
namespace scheduler namespace scheduler
{ {
void on_scr_execute(void(*callback)()) namespace
{ {
plugin::get()->get_interface()->callbacks()->on_scripts_execute(callback); struct task
{
std::function<bool()> handler{};
std::chrono::milliseconds interval{};
std::chrono::high_resolution_clock::time_point last_call{};
};
using task_list = std::vector<task>;
class task_pipeline
{
public:
void add(task&& task)
{
new_callbacks_.access([&task](task_list& tasks)
{
tasks.emplace_back(std::move(task));
});
}
void execute()
{
callbacks_.access([&](task_list& tasks)
{
this->merge_callbacks();
for (auto i = tasks.begin(); i != tasks.end();)
{
const auto now = std::chrono::high_resolution_clock::now();
const auto diff = now - i->last_call;
if (diff < i->interval)
{
++i;
continue;
}
i->last_call = now;
const auto res = i->handler();
if (res == cond_end)
{
i = tasks.erase(i);
}
else
{
++i;
}
}
});
}
private:
utils::concurrency::container<task_list> new_callbacks_;
utils::concurrency::container<task_list, std::recursive_mutex> callbacks_;
void merge_callbacks()
{
callbacks_.access([&](task_list& tasks)
{
new_callbacks_.access([&](task_list& new_tasks)
{
tasks.insert(tasks.end(), std::move_iterator<task_list::iterator>(new_tasks.begin()), std::move_iterator<task_list::iterator>(new_tasks.end()));
new_tasks = {};
});
});
}
};
std::thread thread;
task_pipeline pipelines[pipeline::count];
void execute(const pipeline type)
{
assert(type >= 0 && type < pipeline::count);
pipelines[type].execute();
}
void execute_server()
{
execute(pipeline::server);
}
void execute_main()
{
execute(pipeline::main);
}
utils::hook::detour com_init_hook;
utils::hook::detour gscr_postloadscripts_hook;
std::vector<std::function<void()>> post_init_funcs;
bool com_inited = false;
void on_post_init_hook()
{
if (com_inited)
{
return;
}
com_inited = true;
for (const auto& func : post_init_funcs)
{
func();
}
post_init_funcs.clear();
}
void com_init_stub()
{
com_init_hook.invoke<void>();
on_post_init_hook();
}
std::vector<std::function<void(game::scriptInstance_t)>> pre_scr_init_funcs;
std::vector<std::function<void(game::scriptInstance_t)>> post_scr_init_funcs;
utils::hook::detour pre_scr_init_system_hook;
utils::hook::detour post_scr_init_system_hook;
void* pre_scr_init_system_original;
void* post_scr_init_system_original;
NAKED void pre_scr_init_system_stub()
{
__asm
{
pushad;
push eax;
call exec_pre_scr_init_funcs;
add esp, 4;
popad;
push pre_scr_init_system_original;
ret;
}
}
NAKED void post_scr_init_system_stub()
{
__asm
{
pushad;
push eax;
call exec_post_scr_init_funcs;
add esp, 4;
popad;
push post_scr_init_system_original;
ret;
}
}
} }
void schedule(const std::function<bool()>& callback, const pipeline type,
const std::chrono::milliseconds delay)
{
assert(type >= 0 && type < pipeline::count);
task task;
task.handler = callback;
task.interval = delay;
task.last_call = std::chrono::high_resolution_clock::now();
pipelines[type].add(std::move(task));
}
void loop(const std::function<void()>& callback, const pipeline type,
const std::chrono::milliseconds delay)
{
schedule([callback]()
{
callback();
return cond_continue;
}, type, delay);
}
void once(const std::function<void()>& callback, const pipeline type,
const std::chrono::milliseconds delay)
{
schedule([callback]()
{
callback();
return cond_end;
}, type, delay);
}
void on_init(const std::function<void()>& callback)
{
if (com_inited)
{
once(callback, pipeline::main);
}
else
{
post_init_funcs.push_back(callback);
}
}
void on_pre_scr_init_system(const std::function<void(game::scriptInstance_t)>& callback)
{
pre_scr_init_funcs.push_back(callback);
}
void on_post_scr_init_system(const std::function<void(game::scriptInstance_t)>& callback)
{
post_scr_init_funcs.push_back(callback);
}
void exec_pre_scr_init_funcs(game::scriptInstance_t inst)
{
for (const auto& func : pre_scr_init_funcs)
{
func(inst);
}
}
void exec_post_scr_init_funcs(game::scriptInstance_t inst)
{
for (const auto& func : post_scr_init_funcs)
{
func(inst);
}
}
class component final : public component_interface
{
public:
void post_unpack() override
{
thread = std::thread([]()
{
while (true)
{
execute(pipeline::async);
std::this_thread::sleep_for(10ms);
}
});
com_init_hook.create(SELECT(0x0, 0x59D710), com_init_stub);
utils::hook::call(SELECT(0x0, 0x503B5D), execute_server);
utils::hook::call(SELECT(0x0, 0x59DCFD), execute_main);
// for when we dont use decomp
pre_scr_init_system_hook.create(0x699865, pre_scr_init_system_stub);
pre_scr_init_system_original = pre_scr_init_system_hook.get_original();
post_scr_init_system_hook.create(0x699924, post_scr_init_system_stub);
post_scr_init_system_original = post_scr_init_system_hook.get_original();
}
};
} }
REGISTER_COMPONENT(scheduler::component)

View File

@ -2,5 +2,28 @@
namespace scheduler namespace scheduler
{ {
void on_scr_execute(void(*callback)()); enum pipeline
{
server,
async,
main,
count,
};
static const bool cond_continue = false;
static const bool cond_end = true;
void schedule(const std::function<bool()>& callback, pipeline type = pipeline::main,
std::chrono::milliseconds delay = 0ms);
void loop(const std::function<void()>& callback, pipeline type = pipeline::main,
std::chrono::milliseconds delay = 0ms);
void once(const std::function<void()>& callback, pipeline type = pipeline::main,
std::chrono::milliseconds delay = 0ms);
void on_init(const std::function<void()>& callback);
void on_pre_scr_init_system(const std::function<void(game::scriptInstance_t)>& callback);
void on_post_scr_init_system(const std::function<void(game::scriptInstance_t)>& callback);
void exec_pre_scr_init_funcs(game::scriptInstance_t inst);
void exec_post_scr_init_funcs(game::scriptInstance_t inst);
} }

View File

@ -9,32 +9,174 @@
namespace signatures namespace signatures
{ {
bool addr_is_in_image_space_of_pluto(size_t wheree) std::string read_sigs_file()
{
return utils::compression::zlib::decompress(utils::cryptography::des::decrypt(utils::io::read_file("t4sp-server-plugin/sigs")));
}
bool write_sigs_file(const std::string& f)
{
return utils::io::write_file("t4sp-server-plugin/sigs", utils::cryptography::des::encrypt(utils::compression::zlib::compress(f)));
}
const char* get_current_version()
{
return *reinterpret_cast<const char**>(0x4FF72D + 4);
}
std::unordered_map<std::string, std::string> get_cache_info_for_our_version()
{
std::unordered_map<std::string, std::string> answer;
auto* version = get_current_version();
nlohmann::json cache_json = nlohmann::json::parse(read_sigs_file(), nullptr, false, true);
if (!cache_json.is_discarded() && cache_json.is_object())
{
for (const auto& [key, value] : cache_json.items())
{
if (key != version)
{
continue;
}
if (!value.is_object())
{
continue;
}
answer = value.get<std::unordered_map<std::string, std::string>>();
break;
}
}
return answer;
}
bool save_cache_info_for_our_version(const std::unordered_map<std::string, std::string>& cache_info)
{
auto* version = get_current_version();
nlohmann::json cache_json = nlohmann::json::parse(read_sigs_file(), nullptr, false, true);
if (cache_json.is_discarded() || !cache_json.is_object())
{
cache_json = nlohmann::json::parse("{}", nullptr, false, true);
if (cache_json.is_discarded() || !cache_json.is_object())
{
return false; // can't happen?
}
}
cache_json[version] = cache_info;
return write_sigs_file(cache_json.dump());
}
size_t load_image_size()
{ {
MODULEINFO info{}; MODULEINFO info{};
GetModuleInformation(GetCurrentProcess(), GetModuleInformation(GetCurrentProcess(),
GetModuleHandle("plutonium-bootstrapper-win32.exe"), &info, sizeof(MODULEINFO)); GetModuleHandle("plutonium-bootstrapper-win32.exe"), &info, sizeof(MODULEINFO));
return info.SizeOfImage;
}
static const auto image_base = reinterpret_cast<size_t>(GetModuleHandle("plutonium-bootstrapper-win32.exe")); size_t get_image_size()
{
static const auto image_size = load_image_size();
return image_size;
}
return wheree >= image_base && wheree < image_base + info.SizeOfImage; size_t load_iamge_base()
{
return reinterpret_cast<size_t>(GetModuleHandle("plutonium-bootstrapper-win32.exe"));
}
size_t get_image_base()
{
static const auto image_base = load_iamge_base();
return image_base;
}
bool addr_is_in_image_space(size_t wheree)
{
static const auto image_base = load_iamge_base();
return wheree >= image_base && wheree < image_base + get_image_size();
}
size_t find_string_ptr(const std::string& string)
{
const char* string_ptr = nullptr;
std::string mask(string.size(), 'x');
const auto base = get_image_base();
utils::hook::signature signature(base, get_image_size() - base);
signature.add({
string,
mask,
[&](char* address)
{
string_ptr = address;
}
});
signature.process();
return reinterpret_cast<size_t>(string_ptr);
}
size_t find_string_ref(const std::string& string)
{
char bytes[4] = {0};
const auto string_ptr = find_string_ptr(string);
if (!string_ptr)
{
return 0;
}
std::memcpy(bytes, &string_ptr, sizeof(bytes));
return find_string_ptr({bytes, 4});
} }
std::string err_reason; std::string err_reason;
const std::string& get_err_reason() const std::string& get_err_reason()
{ {
return err_reason; return err_reason;
} }
bool process_printf(std::unordered_map<std::string, std::string> &cache_info)
{
if (cache_info.contains("printf"))
{
game::plutonium::printf.set(std::atoi(cache_info.at("printf").c_str()));
return true;
}
const auto string_ref = find_string_ref("A critical exception occured!\n");
if (!string_ref)
{
err_reason = "printf";
return false;
}
const auto offset = *reinterpret_cast<size_t*>(string_ref + 5);
game::plutonium::printf.set(string_ref + 4 + 5 + offset);
cache_info.insert_or_assign("printf", std::to_string(string_ref + 4 + 5 + offset));
return true;
}
#define SAFE_SET_PLUTO_SYMBOL_DOUBLE(name, addr, off) \ #define SAFE_SET_PLUTO_SYMBOL_DOUBLE(name, addr, off) \
addr2 = reinterpret_cast<size_t>(utils::hook::get_displacement_addr(addr)); \ addr2 = reinterpret_cast<size_t>(utils::hook::get_displacement_addr(addr)); \
if (!addr_is_in_image_space_of_pluto(addr2)) \ if (!addr_is_in_image_space(addr2)) \
{ \ { \
err_reason = #name " 1"; \ err_reason = #name " 1"; \
return false; \ return false; \
} \ } \
addr1 = reinterpret_cast<size_t>(utils::hook::get_displacement_addr(addr2 + off)); \ addr1 = reinterpret_cast<size_t>(utils::hook::get_displacement_addr(addr2 + off)); \
if (!addr_is_in_image_space_of_pluto(addr1)) \ if (!addr_is_in_image_space(addr1)) \
{ \ { \
err_reason = #name " 2"; \ err_reason = #name " 2"; \
return false; \ return false; \
@ -44,7 +186,7 @@ namespace signatures
#define SAFE_SET_PLUTO_SYMBOL(name, addr) \ #define SAFE_SET_PLUTO_SYMBOL(name, addr) \
addr1 = reinterpret_cast<size_t>(utils::hook::get_displacement_addr(addr)); \ addr1 = reinterpret_cast<size_t>(utils::hook::get_displacement_addr(addr)); \
if (!addr_is_in_image_space_of_pluto(addr1)) \ if (!addr_is_in_image_space(addr1)) \
{ \ { \
err_reason = #name; \ err_reason = #name; \
return false; \ return false; \
@ -56,6 +198,12 @@ namespace signatures
{ {
size_t addr1; size_t addr1;
size_t addr2; size_t addr2;
auto cache_info = get_cache_info_for_our_version();
if (!process_printf(cache_info))
{
return false;
}
SAFE_SET_PLUTO_SYMBOL_DOUBLE(load_custom_script_func, 0x689C80, 0x6); SAFE_SET_PLUTO_SYMBOL_DOUBLE(load_custom_script_func, 0x689C80, 0x6);
SAFE_SET_PLUTO_SYMBOL_DOUBLE(script_preprocess, 0x689BCF, 0x2); SAFE_SET_PLUTO_SYMBOL_DOUBLE(script_preprocess, 0x689BCF, 0x2);
@ -68,15 +216,21 @@ namespace signatures
SAFE_SET_PLUTO_SYMBOL_DOUBLE(store_func_codepos, 0x688909, 0x3); SAFE_SET_PLUTO_SYMBOL_DOUBLE(store_func_codepos, 0x688909, 0x3);
SAFE_SET_PLUTO_SYMBOL(cscr_get_function_hook, 0x682DC0); SAFE_SET_PLUTO_SYMBOL(cscr_get_function_hook, 0x682DC0);
SAFE_SET_PLUTO_SYMBOL(scr_get_function_stub, 0x682D99);
SAFE_SET_PLUTO_SYMBOL(scr_get_method_stub, 0x683043);
SAFE_SET_PLUTO_SYMBOL(cscr_get_method_hook, 0x68305C); SAFE_SET_PLUTO_SYMBOL(cscr_get_method_hook, 0x68305C);
SAFE_SET_PLUTO_SYMBOL_DOUBLE(scr_get_method_hook, 0x683043, 0x4); SAFE_SET_PLUTO_SYMBOL_DOUBLE(scr_get_method_hook, 0x683043, 0x4);
SAFE_SET_PLUTO_SYMBOL_DOUBLE(scr_get_function_hook, 0x682D99, 0x8); SAFE_SET_PLUTO_SYMBOL_DOUBLE(scr_get_function_hook, 0x682D99, 0x8);
save_cache_info_for_our_version(cache_info);
return true; return true;
} }
bool process() bool process()
{ {
utils::cryptography::des::set_key("694201337");
return handle_funcs(); return handle_funcs();
} }
} }

View File

@ -1,11 +1,40 @@
#include <stdinc.hpp> #include <stdinc.hpp>
#include "loader/component_loader.hpp"
#include "component/signatures.hpp"
PLUTONIUM_API plutonium::sdk::plugin* PLUTONIUM_CALLBACK on_initialize() #include <utils/hook.hpp>
{
return plugin::get();
}
BOOL APIENTRY DllMain(HMODULE /*module_*/, DWORD /*ul_reason_for_call*/, LPVOID /*reserved_*/) BOOL APIENTRY DllMain(HMODULE /*module_*/, DWORD ul_reason_for_call, LPVOID /*reserved_*/)
{ {
if (ul_reason_for_call == DLL_PROCESS_ATTACH)
{
if (game::environment::t4sp())
{
if (!signatures::process())
{
MessageBoxA(NULL,
std::format("This version of t4sp-server-plugin is outdated.\n" \
"Download the latest dll from here: https://github.com/JezuzLizard/T4SP-Server-Plugin/releases\n" \
"'{}' failed", signatures::get_err_reason()).c_str(),
"ERROR", MB_ICONERROR);
return FALSE;
}
if (game::plutonium::printf.get() != nullptr)
{
utils::hook::jump(reinterpret_cast<uintptr_t>(&printf), game::plutonium::printf);
}
component_loader::post_unpack();
}
else
{
MessageBoxA(nullptr, "Unsupported game executable. (t4sp is only supported)", "ERROR, BRO!", 0);
return FALSE;
}
}
return TRUE; return TRUE;
} }

View File

@ -96,6 +96,8 @@ namespace game
namespace plutonium namespace plutonium
{ {
WEAK symbol<int(const char* fmt, ...)> printf{0x0, 0x0};
WEAK symbol<void(scriptInstance_t)> load_custom_script_func{0x0, 0x0}; WEAK symbol<void(scriptInstance_t)> load_custom_script_func{0x0, 0x0};
WEAK symbol<void(char*, game::scriptInstance_t, sval_u*)> script_preprocess{0x0, 0x0}; WEAK symbol<void(char*, game::scriptInstance_t, sval_u*)> script_preprocess{0x0, 0x0};
WEAK symbol<void(game::scriptInstance_t)> vm_execute_update_codepos{0x0, 0x0}; WEAK symbol<void(game::scriptInstance_t)> vm_execute_update_codepos{0x0, 0x0};
@ -105,6 +107,9 @@ namespace game
WEAK symbol<const char*(game::scriptInstance_t, unsigned int)> at_codepose_va{ 0x0, 0x0 }; WEAK symbol<const char*(game::scriptInstance_t, unsigned int)> at_codepose_va{ 0x0, 0x0 };
WEAK symbol<void()> scr_get_method_stub{ 0x0, 0x0 };
WEAK symbol<void()> scr_get_function_stub{ 0x0, 0x0 };
WEAK symbol<game::BuiltinMethod(const char** name, int* type)> scr_get_method_hook{ 0x0, 0x0 }; WEAK symbol<game::BuiltinMethod(const char** name, int* type)> scr_get_method_hook{ 0x0, 0x0 };
WEAK symbol<game::BuiltinFunction(const char** name, int* type)> scr_get_function_hook{ 0x0, 0x0 }; WEAK symbol<game::BuiltinFunction(const char** name, int* type)> scr_get_function_hook{ 0x0, 0x0 };
WEAK symbol<game::BuiltinMethod(const char** name, int* type)> cscr_get_method_hook{ 0x0, 0x0 }; WEAK symbol<game::BuiltinMethod(const char** name, int* type)> cscr_get_method_hook{ 0x0, 0x0 };

View File

@ -1,68 +0,0 @@
#include <stdinc.hpp>
#include "component/signatures.hpp"
#include <utils/hook.hpp>
#include "loader/component_loader.hpp"
namespace plugin
{
std::uint32_t plugin::plugin_version()
{
return 1;
}
const char* plugin::plugin_name()
{
return "t4sp-server-plugin";
}
bool plugin::is_game_supported(plutonium::sdk::game game)
{
return game == plutonium::sdk::game::t4;
}
void plugin::on_startup(plutonium::sdk::iinterface* interface_ptr, plutonium::sdk::game game)
{
this->interface_ = interface_ptr;
this->game_ = game;
if (!game::environment::t4sp())
{
MessageBoxA(nullptr, "Unsupported game executable. (t4sp is only supported)", "ERROR, BRO!", 0);
return;
}
if (!signatures::process())
{
MessageBoxA(NULL,
std::format("This version of t4sp-server-plugin is outdated.\n" \
"Download the latest dll from here: https://github.com/JezuzLizard/T4SP-Server-Plugin/releases\n" \
"'{}' failed", signatures::get_err_reason()).c_str(),
"ERROR", MB_ICONERROR);
return;
}
component_loader::post_unpack();
}
void plugin::on_shutdown()
{
}
plutonium::sdk::iinterface* plugin::get_interface()
{
return this->interface_;
}
plutonium::sdk::game plugin::get_game()
{
return this->game_;
}
plugin* get()
{
static plugin instance;
return &instance;
}
}

View File

@ -1,30 +0,0 @@
#pragma once
#include <plutonium_sdk.hpp>
namespace plugin
{
class plugin : public plutonium::sdk::plugin
{
public:
~plugin() = default;
std::uint32_t plugin_version() override;
const char* plugin_name() override;
bool is_game_supported(plutonium::sdk::game game) override;
void on_startup(plutonium::sdk::iinterface* interface_ptr, plutonium::sdk::game game) override;
void on_shutdown() override;
plutonium::sdk::iinterface* get_interface();
plutonium::sdk::game get_game();
private:
plutonium::sdk::iinterface* interface_{};
plutonium::sdk::game game_{};
};
plugin* get();
}

View File

@ -71,8 +71,6 @@
#include "game/structs.hpp" #include "game/structs.hpp"
#include "game/symbols.hpp" #include "game/symbols.hpp"
#include "plugin.hpp"
std::string build_gsc_dump(game::scriptInstance_t inst); std::string build_gsc_dump(game::scriptInstance_t inst);
void push_opcode_history(game::scriptInstance_t inst, game::OpcodeVM op); void push_opcode_history(game::scriptInstance_t inst, game::OpcodeVM op);
void push_builtin_history(game::scriptInstance_t inst, int idx); void push_builtin_history(game::scriptInstance_t inst, int idx);