mirror of
https://github.com/fedddddd/iw5-gsc-utils.git
synced 2025-04-20 21:05:44 +00:00
Initial commit
This commit is contained in:
parent
daf26438b0
commit
dc150bb940
63
.gitattributes
vendored
63
.gitattributes
vendored
@ -1,2 +1,63 @@
|
||||
# Auto detect text files and perform LF normalization
|
||||
###############################################################################
|
||||
# Set default behavior to automatically normalize line endings.
|
||||
###############################################################################
|
||||
* text=auto
|
||||
|
||||
###############################################################################
|
||||
# Set default behavior for command prompt diff.
|
||||
#
|
||||
# This is need for earlier builds of msysgit that does not have it on by
|
||||
# default for csharp files.
|
||||
# Note: This is only used by command line
|
||||
###############################################################################
|
||||
#*.cs diff=csharp
|
||||
|
||||
###############################################################################
|
||||
# Set the merge driver for project and solution files
|
||||
#
|
||||
# Merging from the command prompt will add diff markers to the files if there
|
||||
# are conflicts (Merging from VS is not affected by the settings below, in VS
|
||||
# the diff markers are never inserted). Diff markers may cause the following
|
||||
# file extensions to fail to load in VS. An alternative would be to treat
|
||||
# these files as binary and thus will always conflict and require user
|
||||
# intervention with every merge. To do so, just uncomment the entries below
|
||||
###############################################################################
|
||||
#*.sln merge=binary
|
||||
#*.csproj merge=binary
|
||||
#*.vbproj merge=binary
|
||||
#*.vcxproj merge=binary
|
||||
#*.vcproj merge=binary
|
||||
#*.dbproj merge=binary
|
||||
#*.fsproj merge=binary
|
||||
#*.lsproj merge=binary
|
||||
#*.wixproj merge=binary
|
||||
#*.modelproj merge=binary
|
||||
#*.sqlproj merge=binary
|
||||
#*.wwaproj merge=binary
|
||||
|
||||
###############################################################################
|
||||
# behavior for image files
|
||||
#
|
||||
# image files are treated as binary by default.
|
||||
###############################################################################
|
||||
#*.jpg binary
|
||||
#*.png binary
|
||||
#*.gif binary
|
||||
|
||||
###############################################################################
|
||||
# diff behavior for common document formats
|
||||
#
|
||||
# Convert binary document formats to text before diffing them. This feature
|
||||
# is only available from the command line. Turn it on by uncommenting the
|
||||
# entries below.
|
||||
###############################################################################
|
||||
#*.doc diff=astextplain
|
||||
#*.DOC diff=astextplain
|
||||
#*.docx diff=astextplain
|
||||
#*.DOCX diff=astextplain
|
||||
#*.dot diff=astextplain
|
||||
#*.DOT diff=astextplain
|
||||
#*.pdf diff=astextplain
|
||||
#*.PDF diff=astextplain
|
||||
#*.rtf diff=astextplain
|
||||
#*.RTF diff=astextplain
|
||||
|
150
.gitignore
vendored
Normal file
150
.gitignore
vendored
Normal file
@ -0,0 +1,150 @@
|
||||
### Windows
|
||||
|
||||
# Windows image file caches
|
||||
Thumbs.db
|
||||
ehthumbs.db
|
||||
|
||||
# Folder config file
|
||||
Desktop.ini
|
||||
|
||||
# Recycle Bin used on file shares
|
||||
$RECYCLE.BIN/
|
||||
|
||||
# Windows Installer files
|
||||
*.cab
|
||||
*.msi
|
||||
*.msm
|
||||
*.msp
|
||||
|
||||
# Shortcuts
|
||||
*.lnk
|
||||
|
||||
### OSX
|
||||
|
||||
.DS_Store
|
||||
.AppleDouble
|
||||
.LSOverride
|
||||
|
||||
# Icon must end with two \r
|
||||
Icon
|
||||
|
||||
# Thumbnails
|
||||
._*
|
||||
|
||||
# Files that might appear on external disk
|
||||
.Spotlight-V100
|
||||
.Trashes
|
||||
|
||||
# Directories potentially created on remote AFP share
|
||||
.AppleDB
|
||||
.AppleDesktop
|
||||
Network Trash Folder
|
||||
Temporary Items
|
||||
.apdisk
|
||||
|
||||
### Visual Studio
|
||||
|
||||
# User-specific files
|
||||
*.suo
|
||||
*.user
|
||||
*.userosscache
|
||||
*.sln.docstates
|
||||
|
||||
# User-specific files (MonoDevelop/Xamarin Studio)
|
||||
*.userprefs
|
||||
|
||||
# Build results
|
||||
build
|
||||
|
||||
# Visual Studio 2015 cache/options directory
|
||||
.vs/
|
||||
|
||||
# MSTest test Results
|
||||
[Tt]est[Rr]esult*/
|
||||
[Bb]uild[Ll]og.*
|
||||
|
||||
*_i.c
|
||||
*_p.c
|
||||
*_i.h
|
||||
*.ilk
|
||||
*.meta
|
||||
*.obj
|
||||
*.pch
|
||||
*.pdb
|
||||
*.pgc
|
||||
*.pgd
|
||||
*.rsp
|
||||
*.sbr
|
||||
*.tlb
|
||||
*.tli
|
||||
*.tlh
|
||||
*.tmp
|
||||
*.tmp_proj
|
||||
*.log
|
||||
*.vspscc
|
||||
*.vssscc
|
||||
.builds
|
||||
*.pidb
|
||||
*.svclog
|
||||
*.scc
|
||||
|
||||
# Visual C++ cache files
|
||||
ipch/
|
||||
*.aps
|
||||
*.ncb
|
||||
*.opendb
|
||||
*.opensdf
|
||||
*.sdf
|
||||
*.cachefile
|
||||
|
||||
# Visual Studio profiler
|
||||
*.psess
|
||||
*.vsp
|
||||
*.vspx
|
||||
*.sap
|
||||
|
||||
# TFS 2012 Local Workspace
|
||||
$tf/
|
||||
|
||||
# Guidance Automation Toolkit
|
||||
*.gpState
|
||||
|
||||
# Visual Studio cache files
|
||||
# files ending in .cache can be ignored
|
||||
*.[Cc]ache
|
||||
# but keep track of directories ending in .cache
|
||||
!*.[Cc]ache/
|
||||
|
||||
# Others
|
||||
~$*
|
||||
*~
|
||||
*.dbmdl
|
||||
*.dbproj.schemaview
|
||||
*.pfx
|
||||
*.publishsettings
|
||||
|
||||
# Backup & report files from converting an old project file
|
||||
# to a newer Visual Studio version. Backup files are not needed,
|
||||
# because we have git ;-)
|
||||
_UpgradeReport_Files/
|
||||
Backup*/
|
||||
UpgradeLog*.XML
|
||||
UpgradeLog*.htm
|
||||
|
||||
# SQL Server files
|
||||
*.mdf
|
||||
*.ldf
|
||||
|
||||
### IDA
|
||||
*.id0
|
||||
*.id1
|
||||
*.id2
|
||||
*.nam
|
||||
*.til
|
||||
|
||||
### Custom user files
|
||||
# User scripts
|
||||
user*.bat
|
||||
|
||||
# Premake binary
|
||||
#premake5.exe
|
12
.gitmodules
vendored
Normal file
12
.gitmodules
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
[submodule "deps/minhook"]
|
||||
path = deps/minhook
|
||||
url = https://github.com/TsudaKageyu/minhook.git
|
||||
[submodule "deps/GSL"]
|
||||
path = deps/GSL
|
||||
url = https://github.com/microsoft/GSL.git
|
||||
[submodule "deps/sol2"]
|
||||
path = deps/sol2
|
||||
url = https://github.com/ThePhD/sol2.git
|
||||
[submodule "deps/lua"]
|
||||
path = deps/lua
|
||||
url = https://github.com/lua/lua.git
|
111
README.md
Normal file
111
README.md
Normal file
@ -0,0 +1,111 @@
|
||||
# iw5-script
|
||||
|
||||
Lua scripting support for Plutonium IW5
|
||||
|
||||
Works the same as it does in [IW6x](https://github.com/XLabsProject/iw6x-client/wiki/Scripting)
|
||||
|
||||
# How
|
||||
|
||||
* Download the latest version from the Releases tab
|
||||
* Copy it to `%localappdata%/Plutonium/storage/iw5/plugins/`
|
||||
* Create a `__init__.lua` file in a folder with a name of your choice in `%localappdata%/Plutonium/storage/iw5/scripts/`
|
||||
* Example `%localappdata%/Plutonium/storage/iw5/scripts/myscript/__init__.lua`
|
||||
* Run the server (preferably with the `-no-scripting` flag to disable ChaiScript)
|
||||
|
||||
Below are some features that are not available or documented in IW6x
|
||||
|
||||
# Chat notifies
|
||||
```lua
|
||||
level:onnotify("say", function(player, message)
|
||||
print(player.name .. " said: " .. message)
|
||||
end)
|
||||
```
|
||||
or
|
||||
```lua
|
||||
level:onnotify("connected", function(player)
|
||||
player:onnotify("say", function(message)
|
||||
print(player.name .. " said: " .. message)
|
||||
end)
|
||||
end)
|
||||
```
|
||||
|
||||
# Player damage/killed callbacks
|
||||
|
||||
Callbacks can be added using the `game:onplayerkilled` or `game:onplayerdamage` functions:
|
||||
|
||||
Damage can be changed by returning it
|
||||
|
||||
Returning anything other than a number will not do anything (must be an integer)
|
||||
|
||||
```lua
|
||||
game:onplayerdamage(function(_self, inflictor, attacker, damage, dflags, mod, weapon, point, dir, hitloc)
|
||||
damage = 0
|
||||
|
||||
return damage
|
||||
end)
|
||||
```
|
||||
```lua
|
||||
game:onplayerkilled(function(_self, inflictor, attacker, damage, mod, weapon, dir, hitloc, timeoffset, deathanimduration)
|
||||
print(attacker.name .. " killed " .. _self.name)
|
||||
end)
|
||||
```
|
||||
|
||||
# Arrays
|
||||
GSC arrays are supported and can be accessed similarly to gsc:
|
||||
```lua
|
||||
local ents = game:getentarray()
|
||||
|
||||
for i = 1, #ents do
|
||||
print(ents[i])
|
||||
end
|
||||
|
||||
```
|
||||
|
||||
# Structs
|
||||
GSC structs are also supported similarly as the arrays.
|
||||
|
||||
To get an entity's struct use the `getstruct` method:
|
||||
```lua
|
||||
local levelstruct = level:getstruct()
|
||||
|
||||
levelstruct.inGracePeriod = 10000
|
||||
```
|
||||
Structs in other variables like arrays are automatically converted:
|
||||
```lua
|
||||
level:onnotify("connected", function(player)
|
||||
player:onnotify("spawned_player", function()
|
||||
player.pers.killstreaks[1].streakName = "ac130"
|
||||
player.pers.killstreaks[1].available = 1
|
||||
end)
|
||||
end)
|
||||
```
|
||||
|
||||
Note: you cannot create new struct fields but only modify or read existing ones, same thing for arrays
|
||||
|
||||
# Functions
|
||||
|
||||
You can call (will not work for every function) functions and methods within the game's gsc scripts using the
|
||||
|
||||
`scriptcall(filename, function, ...)` method:
|
||||
```lua
|
||||
level:onnotify("connected", function(player)
|
||||
player:onnotify("spawned_player", function()
|
||||
local hudelem = player:scriptcall("maps/mp/gametypes/_hud_utils", "createFontString", 1)
|
||||
|
||||
hudelem:scriptcall("maps/mp/gametypes/_hud_util", "setPoint", "CENTER", nil, 100, 100)
|
||||
hudelem.label = "&Hello world"
|
||||
end)
|
||||
end)
|
||||
```
|
||||
Functions in variables such as structs or arrays will be automatically converted to a lua function.
|
||||
|
||||
The first argument must always be the entity to call the function on (level, player...)
|
||||
```lua
|
||||
local levelstruct = level:getstruct()
|
||||
|
||||
level:onnotify("connected", function(player)
|
||||
player:onnotify("spawned_player", function()
|
||||
levelstruct.killstreakFuncs["ac130"](player)
|
||||
end)
|
||||
end)
|
||||
```
|
1
deps/GSL
vendored
Submodule
1
deps/GSL
vendored
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit c1cbb41b428f15e53454682a45f03ea31f1da0a7
|
1
deps/minhook
vendored
Submodule
1
deps/minhook
vendored
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit 423d1e45af2ed2719a5c31e990e935ef301ed9c3
|
19
deps/premake/gsl.lua
vendored
Normal file
19
deps/premake/gsl.lua
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
gsl = {
|
||||
source = path.join(dependencies.basePath, "GSL"),
|
||||
}
|
||||
|
||||
function gsl.import()
|
||||
gsl.includes()
|
||||
end
|
||||
|
||||
function gsl.includes()
|
||||
includedirs {
|
||||
path.join(gsl.source, "include")
|
||||
}
|
||||
end
|
||||
|
||||
function gsl.project()
|
||||
|
||||
end
|
||||
|
||||
table.insert(dependencies, gsl)
|
31
deps/premake/minhook.lua
vendored
Normal file
31
deps/premake/minhook.lua
vendored
Normal file
@ -0,0 +1,31 @@
|
||||
minhook = {
|
||||
source = path.join(dependencies.basePath, "minhook"),
|
||||
}
|
||||
|
||||
function minhook.import()
|
||||
links { "minhook" }
|
||||
minhook.includes()
|
||||
end
|
||||
|
||||
function minhook.includes()
|
||||
includedirs {
|
||||
path.join(minhook.source, "include")
|
||||
}
|
||||
end
|
||||
|
||||
function minhook.project()
|
||||
project "minhook"
|
||||
language "C"
|
||||
|
||||
minhook.includes()
|
||||
|
||||
files {
|
||||
path.join(minhook.source, "src/**.h"),
|
||||
path.join(minhook.source, "src/**.c"),
|
||||
}
|
||||
|
||||
warnings "Off"
|
||||
kind "StaticLib"
|
||||
end
|
||||
|
||||
table.insert(dependencies, minhook)
|
2
generate.bat
Normal file
2
generate.bat
Normal file
@ -0,0 +1,2 @@
|
||||
@echo off
|
||||
tools\windows\premake5.exe vs2019
|
90
premake5.lua
Normal file
90
premake5.lua
Normal file
@ -0,0 +1,90 @@
|
||||
dependencies = {
|
||||
basePath = "./deps"
|
||||
}
|
||||
|
||||
function dependencies.load()
|
||||
dir = path.join(dependencies.basePath, "premake/*.lua")
|
||||
deps = os.matchfiles(dir)
|
||||
|
||||
for i, dep in pairs(deps) do
|
||||
dep = dep:gsub(".lua", "")
|
||||
require(dep)
|
||||
end
|
||||
end
|
||||
|
||||
function dependencies.imports()
|
||||
for i, proj in pairs(dependencies) do
|
||||
if type(i) == 'number' then
|
||||
proj.import()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function dependencies.projects()
|
||||
for i, proj in pairs(dependencies) do
|
||||
if type(i) == 'number' then
|
||||
proj.project()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
dependencies.load()
|
||||
|
||||
workspace "iw5-gsc-utils"
|
||||
location "./build"
|
||||
objdir "%{wks.location}/obj/%{cfg.buildcfg}"
|
||||
targetdir "%{wks.location}/bin/%{cfg.buildcfg}"
|
||||
targetname "%{prj.name}"
|
||||
|
||||
language "C++"
|
||||
|
||||
architecture "x86"
|
||||
|
||||
buildoptions "/std:c++latest"
|
||||
systemversion "latest"
|
||||
|
||||
flags
|
||||
{
|
||||
"NoIncrementalLink",
|
||||
"MultiProcessorCompile",
|
||||
}
|
||||
|
||||
configurations { "Debug", "Release", }
|
||||
|
||||
symbols "On"
|
||||
|
||||
configuration "Release"
|
||||
optimize "Full"
|
||||
defines { "NDEBUG" }
|
||||
configuration{}
|
||||
|
||||
configuration "Debug"
|
||||
optimize "Debug"
|
||||
defines { "DEBUG", "_DEBUG" }
|
||||
configuration {}
|
||||
|
||||
startproject "iw5-gsc-utils"
|
||||
|
||||
project "iw5-gsc-utils"
|
||||
kind "SharedLib"
|
||||
language "C++"
|
||||
|
||||
pchheader "stdinc.hpp"
|
||||
pchsource "src/stdinc.cpp"
|
||||
|
||||
includedirs
|
||||
{
|
||||
"src"
|
||||
}
|
||||
|
||||
files
|
||||
{
|
||||
"src/**.h",
|
||||
"src/**.hpp",
|
||||
"src/**.cpp"
|
||||
}
|
||||
|
||||
dependencies.imports()
|
||||
|
||||
group "Dependencies"
|
||||
dependencies.projects()
|
244
src/component/gsc.cpp
Normal file
244
src/component/gsc.cpp
Normal file
@ -0,0 +1,244 @@
|
||||
#include <stdinc.hpp>
|
||||
#include "loader/component_loader.hpp"
|
||||
#include "scheduler.hpp"
|
||||
|
||||
#include "game/scripting/event.hpp"
|
||||
#include "game/scripting/execution.hpp"
|
||||
#include "game/scripting/functions.hpp"
|
||||
|
||||
namespace gsc
|
||||
{
|
||||
using function_args = std::vector<scripting::script_value>;
|
||||
|
||||
using builtin_function = void(*)();
|
||||
using builtin_method = void(*)(game::scr_entref_t);
|
||||
|
||||
using script_function = std::function<scripting::script_value(function_args)>;
|
||||
using script_method = std::function<scripting::script_value(game::scr_entref_t, function_args)>;
|
||||
|
||||
std::unordered_map<unsigned, script_function> functions;
|
||||
std::unordered_map<unsigned, script_method> methods;
|
||||
|
||||
namespace
|
||||
{
|
||||
void gsc_function_stub(game::scr_entref_t ent)
|
||||
{
|
||||
/*const auto function = scripting::script_value(*game::scr_VmPub->top);
|
||||
|
||||
if (!function.is<std::string>())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const auto function_str = function.as<std::string>();
|
||||
|
||||
if (gsc_functions.find(function_str) != gsc_functions.end())
|
||||
{
|
||||
std::vector<scripting::script_value> arguments;
|
||||
|
||||
for (auto i = 1; i < game::scr_VmPub->outparamcount; i++)
|
||||
{
|
||||
const auto value = game::scr_VmPub->top[-i];
|
||||
|
||||
arguments.emplace_back(value);
|
||||
}
|
||||
|
||||
const auto value = gsc_functions[function_str](ent, arguments);
|
||||
if (*reinterpret_cast<const int*>(&value))
|
||||
{
|
||||
game::Scr_ClearOutParams();
|
||||
scripting::push_value(value);
|
||||
}
|
||||
}*/
|
||||
}
|
||||
|
||||
std::string method_name(unsigned int id)
|
||||
{
|
||||
const auto map = *game::plutonium::method_map_rev;
|
||||
|
||||
for (const auto& function : map)
|
||||
{
|
||||
if (function.second == id)
|
||||
{
|
||||
return function.first;
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
std::string function_name(unsigned int id)
|
||||
{
|
||||
const auto map = *game::plutonium::function_map_rev;
|
||||
|
||||
for (const auto& function : map)
|
||||
{
|
||||
if (function.second == id)
|
||||
{
|
||||
return function.first;
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
function_args get_arguments()
|
||||
{
|
||||
function_args args;
|
||||
|
||||
const auto top = game::scr_VmPub->top;
|
||||
|
||||
for (auto i = 0; i < game::scr_VmPub->outparamcount; i++)
|
||||
{
|
||||
const auto value = game::scr_VmPub->top[i];
|
||||
args.push_back(value);
|
||||
}
|
||||
|
||||
return args;
|
||||
}
|
||||
|
||||
auto function_map_start = 0x200;
|
||||
auto method_map_start = 0x8400;
|
||||
|
||||
void call_function(unsigned int id)
|
||||
{
|
||||
if (id >= 0x200)
|
||||
{
|
||||
try
|
||||
{
|
||||
const auto result = functions[id](get_arguments());
|
||||
scripting::push_value(result);
|
||||
}
|
||||
catch (std::exception e)
|
||||
{
|
||||
printf("************** Script execution error **************\n");
|
||||
printf("Error executing function %s\n", function_name(id).data());
|
||||
printf("%s\n", e.what());
|
||||
printf("****************************************************\n");
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
reinterpret_cast<builtin_function*>(0x1D6EB34)[id]();
|
||||
}
|
||||
}
|
||||
|
||||
void call_method(game::scr_entref_t ent, unsigned int id)
|
||||
{
|
||||
if (id >= 0x8400)
|
||||
{
|
||||
try
|
||||
{
|
||||
const auto result = methods[id](ent, get_arguments());
|
||||
scripting::push_value(result);
|
||||
}
|
||||
catch (std::exception e)
|
||||
{
|
||||
printf("************** Script execution error **************\n");
|
||||
printf("Error executing method %s\n", method_name(id).data());
|
||||
printf("%s\n", e.what());
|
||||
printf("****************************************************\n");
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
reinterpret_cast<builtin_method*>(0x1D4F258)[id](ent);
|
||||
}
|
||||
}
|
||||
|
||||
__declspec(naked) void call_builtin_stub()
|
||||
{
|
||||
__asm
|
||||
{
|
||||
push eax
|
||||
|
||||
mov eax, 0x20B4A5C
|
||||
mov [eax], esi
|
||||
|
||||
mov eax, 0x20B4A90
|
||||
mov [eax], edx
|
||||
|
||||
pop eax
|
||||
|
||||
pushad
|
||||
push eax
|
||||
call call_function
|
||||
pop eax
|
||||
popad
|
||||
|
||||
push 0x56C900
|
||||
retn
|
||||
}
|
||||
}
|
||||
|
||||
__declspec(naked) void call_builtin_method_stub()
|
||||
{
|
||||
__asm
|
||||
{
|
||||
pushad
|
||||
push ecx
|
||||
push ebx
|
||||
call call_method
|
||||
pop ebx
|
||||
pop ecx
|
||||
popad
|
||||
|
||||
push ebx
|
||||
add esp, 0xC
|
||||
|
||||
push 0x56CBE9
|
||||
retn
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace function
|
||||
{
|
||||
void add(const std::string& name, const script_function& func)
|
||||
{
|
||||
const auto index = function_map_start++;
|
||||
|
||||
functions[index] = func;
|
||||
(*game::plutonium::function_map_rev)[name] = index;
|
||||
}
|
||||
}
|
||||
|
||||
namespace method
|
||||
{
|
||||
void add(const std::string& name, const script_method& func)
|
||||
{
|
||||
const auto index = method_map_start++;
|
||||
|
||||
methods[index] = func;
|
||||
(*game::plutonium::method_map_rev)[name] = index;
|
||||
}
|
||||
}
|
||||
|
||||
class component final : public component_interface
|
||||
{
|
||||
public:
|
||||
void post_unpack() override
|
||||
{
|
||||
function::add("lol", [](function_args args) -> scripting::script_value
|
||||
{
|
||||
const auto str = args[0].as<std::string>();
|
||||
|
||||
return str;
|
||||
});
|
||||
|
||||
method::add("test", [](game::scr_entref_t, function_args args) -> scripting::script_value
|
||||
{
|
||||
printf("here\n");
|
||||
|
||||
return {};
|
||||
});
|
||||
|
||||
utils::hook::jump(0x56C8EB, call_builtin_stub);
|
||||
utils::hook::jump(0x56CBDC, call_builtin_method_stub);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
REGISTER_COMPONENT(gsc::component)
|
58
src/component/notifies.cpp
Normal file
58
src/component/notifies.cpp
Normal file
@ -0,0 +1,58 @@
|
||||
#include <stdinc.hpp>
|
||||
#include "loader/component_loader.hpp"
|
||||
#include "scheduler.hpp"
|
||||
|
||||
#include "game/scripting/entity.hpp"
|
||||
#include "game/scripting/execution.hpp"
|
||||
#include "notifies.hpp"
|
||||
|
||||
namespace notifies
|
||||
{
|
||||
namespace
|
||||
{
|
||||
utils::hook::detour client_command_hook;
|
||||
|
||||
utils::hook::detour scr_player_killed_hook;
|
||||
utils::hook::detour scr_player_damage_hook;
|
||||
|
||||
void client_command_stub(int clientNum)
|
||||
{
|
||||
char cmd[1024] = { 0 };
|
||||
|
||||
game::SV_Cmd_ArgvBuffer(0, cmd, 1024);
|
||||
|
||||
if (cmd == "say"s)
|
||||
{
|
||||
std::string message = game::ConcatArgs(1);
|
||||
message.erase(0, 1);
|
||||
|
||||
scheduler::once([message, clientNum]()
|
||||
{
|
||||
const scripting::entity level{*game::levelEntityId};
|
||||
const auto _player = scripting::call("getEntByNum", {clientNum});
|
||||
|
||||
if (_player.get_raw().type == game::SCRIPT_OBJECT)
|
||||
{
|
||||
const auto player = _player.as<scripting::entity>();
|
||||
|
||||
scripting::notify(level, "say", {player, message});
|
||||
scripting::notify(player, "say", {message});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return client_command_hook.invoke<void>(clientNum);
|
||||
}
|
||||
}
|
||||
|
||||
class component final : public component_interface
|
||||
{
|
||||
public:
|
||||
void post_unpack() override
|
||||
{
|
||||
client_command_hook.create(0x502CB0, client_command_stub);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
REGISTER_COMPONENT(notifies::component)
|
5
src/component/notifies.hpp
Normal file
5
src/component/notifies.hpp
Normal file
@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
namespace notifies
|
||||
{
|
||||
}
|
83
src/component/scheduler.cpp
Normal file
83
src/component/scheduler.cpp
Normal file
@ -0,0 +1,83 @@
|
||||
#include <stdinc.hpp>
|
||||
#include "loader/component_loader.hpp"
|
||||
|
||||
namespace scheduler
|
||||
{
|
||||
namespace
|
||||
{
|
||||
std::queue<std::function<void()>> tasks;
|
||||
|
||||
struct task
|
||||
{
|
||||
std::function<bool()> handler;
|
||||
std::chrono::milliseconds interval{};
|
||||
std::chrono::high_resolution_clock::time_point last_call{};
|
||||
};
|
||||
|
||||
utils::concurrent_list<task> callbacks;
|
||||
|
||||
void execute()
|
||||
{
|
||||
for (auto callback : callbacks)
|
||||
{
|
||||
const auto now = std::chrono::high_resolution_clock::now();
|
||||
const auto diff = now - callback->last_call;
|
||||
|
||||
if (diff < callback->interval) continue;
|
||||
|
||||
callback->last_call = now;
|
||||
|
||||
const auto res = callback->handler();
|
||||
if (res)
|
||||
{
|
||||
callbacks.remove(callback);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void server_frame()
|
||||
{
|
||||
execute();
|
||||
reinterpret_cast<void (*)()>(0x50C1E0)();
|
||||
}
|
||||
}
|
||||
|
||||
void schedule(const std::function<bool()>& callback, const std::chrono::milliseconds delay)
|
||||
{
|
||||
task task;
|
||||
task.handler = callback;
|
||||
task.interval = delay;
|
||||
task.last_call = std::chrono::high_resolution_clock::now();
|
||||
|
||||
callbacks.add(task);
|
||||
}
|
||||
|
||||
void loop(const std::function<void()>& callback, const std::chrono::milliseconds delay)
|
||||
{
|
||||
schedule([callback]()
|
||||
{
|
||||
callback();
|
||||
return false;
|
||||
}, delay);
|
||||
}
|
||||
|
||||
void once(const std::function<void()>& callback, const std::chrono::milliseconds delay)
|
||||
{
|
||||
schedule([callback]()
|
||||
{
|
||||
callback();
|
||||
return true;
|
||||
}, delay);
|
||||
}
|
||||
|
||||
class component final : public component_interface
|
||||
{
|
||||
public:
|
||||
void post_unpack() override
|
||||
{
|
||||
utils::hook::call(0x50CEDC, server_frame);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
REGISTER_COMPONENT(scheduler::component)
|
10
src/component/scheduler.hpp
Normal file
10
src/component/scheduler.hpp
Normal file
@ -0,0 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
namespace scheduler
|
||||
{
|
||||
void schedule(const std::function<bool()>& callback, std::chrono::milliseconds delay = 0ms);
|
||||
void loop(const std::function<void()>& callback, std::chrono::milliseconds delay = 0ms);
|
||||
void once(const std::function<void()>& callback, std::chrono::milliseconds delay = 0ms);
|
||||
|
||||
void init();
|
||||
}
|
139
src/component/scripting.cpp
Normal file
139
src/component/scripting.cpp
Normal file
@ -0,0 +1,139 @@
|
||||
#include <stdinc.hpp>
|
||||
#include "loader/component_loader.hpp"
|
||||
|
||||
#include "scheduler.hpp"
|
||||
|
||||
#include "game/scripting/event.hpp"
|
||||
#include "game/scripting/execution.hpp"
|
||||
#include "game/scripting/functions.hpp"
|
||||
|
||||
namespace scripting
|
||||
{
|
||||
std::unordered_map<int, std::unordered_map<std::string, int>> fields_table;
|
||||
std::unordered_map<std::string, std::unordered_map<std::string, char*>> script_function_table;
|
||||
|
||||
namespace
|
||||
{
|
||||
utils::hook::detour vm_notify_hook;
|
||||
utils::hook::detour scr_add_class_field_hook;
|
||||
|
||||
utils::hook::detour scr_load_level_hook;
|
||||
utils::hook::detour g_shutdown_game_hook;
|
||||
|
||||
utils::hook::detour scr_emit_function_hook;
|
||||
utils::hook::detour scr_end_load_scripts_hook;
|
||||
|
||||
void vm_notify_stub(const unsigned int notify_list_owner_id, const unsigned int string_value,
|
||||
game::VariableValue* top)
|
||||
{
|
||||
const auto* name = game::SL_ConvertToString(string_value);
|
||||
|
||||
if (name)
|
||||
{
|
||||
event e;
|
||||
e.name = name;
|
||||
e.entity = notify_list_owner_id;
|
||||
|
||||
for (auto* value = top; value->type != game::SCRIPT_END; --value)
|
||||
{
|
||||
e.arguments.emplace_back(*value);
|
||||
}
|
||||
|
||||
if (e.name == "connected")
|
||||
{
|
||||
scripting::clear_entity_fields(e.entity);
|
||||
}
|
||||
}
|
||||
|
||||
vm_notify_hook.invoke<void>(notify_list_owner_id, string_value, top);
|
||||
}
|
||||
|
||||
void scr_add_class_field_stub(unsigned int classnum, unsigned int _name, unsigned int canonicalString, unsigned int offset)
|
||||
{
|
||||
const auto name = game::SL_ConvertToString(_name);
|
||||
|
||||
if (fields_table[classnum].find(name) == fields_table[classnum].end())
|
||||
{
|
||||
fields_table[classnum][name] = offset;
|
||||
}
|
||||
|
||||
scr_add_class_field_hook.invoke<void>(classnum, _name, canonicalString, offset);
|
||||
}
|
||||
|
||||
void scr_load_level_stub()
|
||||
{
|
||||
scr_load_level_hook.invoke<void>();
|
||||
}
|
||||
|
||||
void g_shutdown_game_stub(const int free_scripts)
|
||||
{
|
||||
g_shutdown_game_hook.invoke<void>(free_scripts);
|
||||
}
|
||||
|
||||
char* function_pos(unsigned int filename, unsigned int name)
|
||||
{
|
||||
const auto scripts_pos = *reinterpret_cast<int*>(0x1D6EB14);
|
||||
|
||||
const auto v2 = game::FindVariable(scripts_pos, filename);
|
||||
|
||||
const auto v3 = game::FindObject(scripts_pos, v2);
|
||||
const auto v4 = game::FindVariable(v3, name);
|
||||
|
||||
if (!v2 || !v3 || !v4)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return utils::hook::invoke<char*>(0x5659C0, v3, v4);
|
||||
}
|
||||
|
||||
void scr_emit_function_stub(unsigned int filename, unsigned int threadName, char* codePos)
|
||||
{
|
||||
const auto* name = game::SL_ConvertToString(filename);
|
||||
const auto filename_id = atoi(name);
|
||||
|
||||
for (const auto& entry : scripting::file_list)
|
||||
{
|
||||
if (entry.first == filename_id)
|
||||
{
|
||||
if (script_function_table.find(entry.second) == script_function_table.end())
|
||||
{
|
||||
script_function_table[entry.second] = {};
|
||||
}
|
||||
|
||||
for (const auto& token : scripting::token_map)
|
||||
{
|
||||
if (token.second == threadName)
|
||||
{
|
||||
const auto pos = function_pos(filename, threadName);
|
||||
|
||||
if (pos)
|
||||
{
|
||||
script_function_table[entry.second][token.first] = pos;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
scr_emit_function_hook.invoke<void>(filename, threadName, codePos);
|
||||
}
|
||||
}
|
||||
|
||||
class component final : public component_interface
|
||||
{
|
||||
public:
|
||||
void post_unpack() override
|
||||
{
|
||||
scr_load_level_hook.create(0x527AF0, scr_load_level_stub);
|
||||
g_shutdown_game_hook.create(0x50C100, g_shutdown_game_stub);
|
||||
|
||||
scr_add_class_field_hook.create(0x567CD0, scr_add_class_field_stub);
|
||||
//vm_notify_hook.create(0x569720, vm_notify_stub);
|
||||
|
||||
//scr_emit_function_hook.create(0x561400, scr_emit_function_stub);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
REGISTER_COMPONENT(scripting::component)
|
7
src/component/scripting.hpp
Normal file
7
src/component/scripting.hpp
Normal file
@ -0,0 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
namespace scripting
|
||||
{
|
||||
extern std::unordered_map<int, std::unordered_map<std::string, int>> fields_table;
|
||||
extern std::unordered_map<std::string, std::unordered_map<std::string, char*>> script_function_table;
|
||||
}
|
12
src/dllmain.cpp
Normal file
12
src/dllmain.cpp
Normal file
@ -0,0 +1,12 @@
|
||||
#include <stdinc.hpp>
|
||||
#include "loader/component_loader.hpp"
|
||||
|
||||
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
|
||||
{
|
||||
if (ul_reason_for_call == DLL_PROCESS_ATTACH)
|
||||
{
|
||||
component_loader::post_unpack();
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
6
src/game/game.cpp
Normal file
6
src/game/game.cpp
Normal file
@ -0,0 +1,6 @@
|
||||
#include <stdinc.hpp>
|
||||
|
||||
namespace game
|
||||
{
|
||||
|
||||
}
|
34
src/game/game.hpp
Normal file
34
src/game/game.hpp
Normal file
@ -0,0 +1,34 @@
|
||||
#pragma once
|
||||
|
||||
namespace game
|
||||
{
|
||||
template <typename T>
|
||||
class symbol
|
||||
{
|
||||
public:
|
||||
symbol(const size_t dedi)
|
||||
: dedi_(reinterpret_cast<T*>(dedi))
|
||||
{
|
||||
}
|
||||
|
||||
T* get() const
|
||||
{
|
||||
return dedi_;
|
||||
}
|
||||
|
||||
operator T* () const
|
||||
{
|
||||
return this->get();
|
||||
}
|
||||
|
||||
T* operator->() const
|
||||
{
|
||||
return this->get();
|
||||
}
|
||||
|
||||
private:
|
||||
T* dedi_;
|
||||
};
|
||||
}
|
||||
|
||||
#include "symbols.hpp"
|
115
src/game/scripting/entity.cpp
Normal file
115
src/game/scripting/entity.cpp
Normal file
@ -0,0 +1,115 @@
|
||||
#include <stdinc.hpp>
|
||||
#include "entity.hpp"
|
||||
#include "script_value.hpp"
|
||||
#include "execution.hpp"
|
||||
|
||||
namespace scripting
|
||||
{
|
||||
entity::entity()
|
||||
: entity(0)
|
||||
{
|
||||
}
|
||||
|
||||
entity::entity(const entity& other) : entity(other.entity_id_)
|
||||
{
|
||||
}
|
||||
|
||||
entity::entity(entity&& other) noexcept
|
||||
{
|
||||
this->entity_id_ = other.entity_id_;
|
||||
other.entity_id_ = 0;
|
||||
}
|
||||
|
||||
entity::entity(const unsigned int entity_id)
|
||||
: entity_id_(entity_id)
|
||||
{
|
||||
this->add();
|
||||
}
|
||||
|
||||
entity::~entity()
|
||||
{
|
||||
this->release();
|
||||
}
|
||||
|
||||
entity& entity::operator=(const entity& other)
|
||||
{
|
||||
if (&other != this)
|
||||
{
|
||||
this->release();
|
||||
this->entity_id_ = other.entity_id_;
|
||||
this->add();
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
entity& entity::operator=(entity&& other) noexcept
|
||||
{
|
||||
if (&other != this)
|
||||
{
|
||||
this->release();
|
||||
this->entity_id_ = other.entity_id_;
|
||||
other.entity_id_ = 0;
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
unsigned int entity::get_entity_id() const
|
||||
{
|
||||
return this->entity_id_;
|
||||
}
|
||||
|
||||
game::scr_entref_t entity::get_entity_reference() const
|
||||
{
|
||||
if (!this->entity_id_)
|
||||
{
|
||||
const auto not_null = static_cast<uint16_t>(~0ui16);
|
||||
return game::scr_entref_t{not_null, not_null};
|
||||
}
|
||||
|
||||
return game::Scr_GetEntityIdRef(this->get_entity_id());
|
||||
}
|
||||
|
||||
bool entity::operator==(const entity& other) const noexcept
|
||||
{
|
||||
return this->get_entity_id() == other.get_entity_id();
|
||||
}
|
||||
|
||||
bool entity::operator!=(const entity& other) const noexcept
|
||||
{
|
||||
return !this->operator==(other);
|
||||
}
|
||||
|
||||
void entity::add() const
|
||||
{
|
||||
if (this->entity_id_)
|
||||
{
|
||||
game::AddRefToValue(game::SCRIPT_OBJECT, {static_cast<int>(this->entity_id_)});
|
||||
}
|
||||
}
|
||||
|
||||
void entity::release() const
|
||||
{
|
||||
if (this->entity_id_)
|
||||
{
|
||||
game::RemoveRefToValue(game::SCRIPT_OBJECT, {static_cast<int>(this->entity_id_)});
|
||||
}
|
||||
}
|
||||
|
||||
void entity::set(const std::string& field, const script_value& value) const
|
||||
{
|
||||
set_entity_field(*this, field, value);
|
||||
}
|
||||
|
||||
template <>
|
||||
script_value entity::get<script_value>(const std::string& field) const
|
||||
{
|
||||
return get_entity_field(*this, field);
|
||||
}
|
||||
|
||||
script_value entity::call(const std::string& name, const std::vector<script_value>& arguments) const
|
||||
{
|
||||
return call_function(name, *this, arguments);
|
||||
}
|
||||
}
|
49
src/game/scripting/entity.hpp
Normal file
49
src/game/scripting/entity.hpp
Normal file
@ -0,0 +1,49 @@
|
||||
#pragma once
|
||||
#include "game/game.hpp"
|
||||
#include "script_value.hpp"
|
||||
|
||||
namespace scripting
|
||||
{
|
||||
class entity final
|
||||
{
|
||||
public:
|
||||
entity();
|
||||
entity(unsigned int entity_id);
|
||||
|
||||
entity(const entity& other);
|
||||
entity(entity&& other) noexcept;
|
||||
|
||||
~entity();
|
||||
|
||||
entity& operator=(const entity& other);
|
||||
entity& operator=(entity&& other) noexcept;
|
||||
|
||||
void set(const std::string& field, const script_value& value) const;
|
||||
|
||||
template <typename T = script_value>
|
||||
T get(const std::string& field) const;
|
||||
|
||||
script_value call(const std::string& name, const std::vector<script_value>& arguments = {}) const;
|
||||
|
||||
unsigned int get_entity_id() const;
|
||||
game::scr_entref_t get_entity_reference() const;
|
||||
|
||||
bool operator ==(const entity& other) const noexcept;
|
||||
bool operator !=(const entity& other) const noexcept;
|
||||
|
||||
private:
|
||||
unsigned int entity_id_;
|
||||
|
||||
void add() const;
|
||||
void release() const;
|
||||
};
|
||||
|
||||
template <>
|
||||
script_value entity::get(const std::string& field) const;
|
||||
|
||||
template <typename T>
|
||||
T entity::get(const std::string& field) const
|
||||
{
|
||||
return this->get<script_value>(field).as<T>();
|
||||
}
|
||||
}
|
13
src/game/scripting/event.hpp
Normal file
13
src/game/scripting/event.hpp
Normal file
@ -0,0 +1,13 @@
|
||||
#pragma once
|
||||
#include "script_value.hpp"
|
||||
#include "entity.hpp"
|
||||
|
||||
namespace scripting
|
||||
{
|
||||
struct event
|
||||
{
|
||||
std::string name;
|
||||
entity entity{};
|
||||
std::vector<script_value> arguments;
|
||||
};
|
||||
}
|
278
src/game/scripting/execution.cpp
Normal file
278
src/game/scripting/execution.cpp
Normal file
@ -0,0 +1,278 @@
|
||||
#include <stdinc.hpp>
|
||||
#include "execution.hpp"
|
||||
#include "safe_execution.hpp"
|
||||
#include "stack_isolation.hpp"
|
||||
#include "event.hpp"
|
||||
|
||||
#include "component/scripting.hpp"
|
||||
#include "component/scheduler.hpp"
|
||||
|
||||
namespace scripting
|
||||
{
|
||||
namespace
|
||||
{
|
||||
game::VariableValue* allocate_argument()
|
||||
{
|
||||
game::VariableValue* value_ptr = ++game::scr_VmPub->top;
|
||||
++game::scr_VmPub->inparamcount;
|
||||
return value_ptr;
|
||||
}
|
||||
|
||||
script_value get_return_value()
|
||||
{
|
||||
if (game::scr_VmPub->inparamcount == 0)
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
game::Scr_ClearOutParams();
|
||||
game::scr_VmPub->outparamcount = game::scr_VmPub->inparamcount;
|
||||
game::scr_VmPub->inparamcount = 0;
|
||||
|
||||
return script_value(game::scr_VmPub->top[1 - game::scr_VmPub->outparamcount]);
|
||||
}
|
||||
|
||||
int get_field_id(const int classnum, const std::string& field)
|
||||
{
|
||||
if (scripting::fields_table[classnum].find(field) != scripting::fields_table[classnum].end())
|
||||
{
|
||||
return scripting::fields_table[classnum][field];
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
void scr_notify_id(int id, unsigned int stringValue, unsigned int paramcount)
|
||||
{
|
||||
if (game::scr_VmPub->outparamcount)
|
||||
{
|
||||
game::Scr_ClearOutParams();
|
||||
}
|
||||
|
||||
auto v6 = game::scr_VmPub->top;
|
||||
auto v7 = game::scr_VmPub->inparamcount - paramcount;
|
||||
auto v8 = &game::scr_VmPub->top[-paramcount];
|
||||
|
||||
if (id)
|
||||
{
|
||||
const auto v9 = v8->type;
|
||||
v8->type = game::scriptType_e::SCRIPT_END;
|
||||
|
||||
game::scr_VmPub->inparamcount = 0;
|
||||
game::VM_Notify(id, stringValue, game::scr_VmPub->top);
|
||||
|
||||
v8->type = v9;
|
||||
v6 = game::scr_VmPub->top;
|
||||
}
|
||||
|
||||
for (; v6 != v8; game::scr_VmPub->top = v6)
|
||||
{
|
||||
game::RemoveRefToValue(v6->type, v6->u);
|
||||
v6 = game::scr_VmPub->top - 1;
|
||||
}
|
||||
|
||||
game::scr_VmPub->inparamcount = v7;
|
||||
}
|
||||
}
|
||||
|
||||
void push_value(const script_value& value)
|
||||
{
|
||||
auto* value_ptr = allocate_argument();
|
||||
*value_ptr = value.get_raw();
|
||||
|
||||
game::AddRefToValue(value_ptr->type, value_ptr->u);
|
||||
}
|
||||
|
||||
void notify(const entity& entity, const std::string& event, const std::vector<script_value>& arguments)
|
||||
{
|
||||
stack_isolation _;
|
||||
for (auto i = arguments.rbegin(); i != arguments.rend(); ++i)
|
||||
{
|
||||
push_value(*i);
|
||||
}
|
||||
|
||||
const auto event_id = game::SL_GetString(event.data(), 0);
|
||||
scr_notify_id(entity.get_entity_id(), event_id, game::scr_VmPub->inparamcount);
|
||||
}
|
||||
|
||||
script_value call_function(const std::string& name, const entity& entity,
|
||||
const std::vector<script_value>& arguments)
|
||||
{
|
||||
const auto entref = entity.get_entity_reference();
|
||||
|
||||
const auto is_method_call = *reinterpret_cast<const int*>(&entref) != -1;
|
||||
const auto function = find_function(name, !is_method_call);
|
||||
if (!function)
|
||||
{
|
||||
throw std::runtime_error("Unknown function '" + name + "'");
|
||||
}
|
||||
|
||||
stack_isolation _;
|
||||
|
||||
for (auto i = arguments.rbegin(); i != arguments.rend(); ++i)
|
||||
{
|
||||
push_value(*i);
|
||||
}
|
||||
|
||||
game::scr_VmPub->outparamcount = game::scr_VmPub->inparamcount;
|
||||
game::scr_VmPub->inparamcount = 0;
|
||||
|
||||
if (!safe_execution::call(function, entref))
|
||||
{
|
||||
throw std::runtime_error("Error executing function '" + name + "'");
|
||||
}
|
||||
|
||||
return get_return_value();
|
||||
}
|
||||
|
||||
script_value call_function(const std::string& name, const std::vector<script_value>& arguments)
|
||||
{
|
||||
return call_function(name, entity(), arguments);
|
||||
}
|
||||
|
||||
template <>
|
||||
script_value call(const std::string& name, const std::vector<script_value>& arguments)
|
||||
{
|
||||
return call_function(name, arguments);
|
||||
}
|
||||
|
||||
script_value exec_ent_thread(const entity& entity, const char* pos, const std::vector<script_value>& arguments)
|
||||
{
|
||||
const auto id = entity.get_entity_id();
|
||||
|
||||
for (auto i = arguments.rbegin(); i != arguments.rend(); ++i)
|
||||
{
|
||||
push_value(*i);
|
||||
}
|
||||
|
||||
game::AddReftoObject(id);
|
||||
|
||||
const auto local_id = game::AllocThread(id);
|
||||
const auto result = game::VM_Execute(local_id, pos, arguments.size());
|
||||
|
||||
const auto value = get_return_value();
|
||||
|
||||
game::RemoveRefToValue(game::scr_VmPub->top->type, game::scr_VmPub->top->u);
|
||||
game::scr_VmPub->top->type = (game::scriptType_e)0;
|
||||
|
||||
--game::scr_VmPub->top;
|
||||
--game::scr_VmPub->inparamcount;
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
script_value call_script_function(const entity& entity, const std::string& filename,
|
||||
const std::string& function, const std::vector<script_value>& arguments)
|
||||
{
|
||||
if (scripting::script_function_table.find(filename) == scripting::script_function_table.end())
|
||||
{
|
||||
throw std::runtime_error("File '" + filename + "' not found");
|
||||
};
|
||||
|
||||
const auto functions = scripting::script_function_table[filename];
|
||||
|
||||
if (functions.find(function) == functions.end())
|
||||
{
|
||||
throw std::runtime_error("Function '" + function + "' in file '" + filename + "' not found");
|
||||
}
|
||||
|
||||
const auto pos = functions.at(function);
|
||||
|
||||
return exec_ent_thread(entity, pos, arguments);
|
||||
}
|
||||
|
||||
static std::unordered_map<unsigned int, std::unordered_map<std::string, script_value>> custom_fields;
|
||||
|
||||
script_value get_custom_field(const entity& entity, const std::string& field)
|
||||
{
|
||||
auto fields = custom_fields[entity.get_entity_id()];
|
||||
const auto _field = fields.find(field);
|
||||
if (_field != fields.end())
|
||||
{
|
||||
return _field->second;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
void set_custom_field(const entity& entity, const std::string& field, const script_value& value)
|
||||
{
|
||||
const auto id = entity.get_entity_id();
|
||||
|
||||
if (custom_fields[id].find(field) != custom_fields[id].end())
|
||||
{
|
||||
custom_fields[id][field] = value;
|
||||
return;
|
||||
}
|
||||
|
||||
custom_fields[id].insert(std::make_pair(field, value));
|
||||
}
|
||||
|
||||
void clear_entity_fields(const entity& entity)
|
||||
{
|
||||
const auto id = entity.get_entity_id();
|
||||
|
||||
if (custom_fields.find(id) != custom_fields.end())
|
||||
{
|
||||
custom_fields[id].clear();
|
||||
}
|
||||
}
|
||||
|
||||
void clear_custom_fields()
|
||||
{
|
||||
custom_fields.clear();
|
||||
}
|
||||
|
||||
void set_entity_field(const entity& entity, const std::string& field, const script_value& value)
|
||||
{
|
||||
const auto entref = entity.get_entity_reference();
|
||||
const int id = get_field_id(entref.classnum, field);
|
||||
|
||||
if (id != -1)
|
||||
{
|
||||
stack_isolation _;
|
||||
push_value(value);
|
||||
|
||||
game::scr_VmPub->outparamcount = game::scr_VmPub->inparamcount;
|
||||
game::scr_VmPub->inparamcount = 0;
|
||||
|
||||
if (!safe_execution::set_entity_field(entref, id))
|
||||
{
|
||||
throw std::runtime_error("Failed to set value for field '" + field + "'");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
set_custom_field(entity, field, value);
|
||||
}
|
||||
}
|
||||
|
||||
script_value get_entity_field(const entity& entity, const std::string& field)
|
||||
{
|
||||
const auto entref = entity.get_entity_reference();
|
||||
const auto id = get_field_id(entref.classnum, field);
|
||||
|
||||
if (id != -1)
|
||||
{
|
||||
stack_isolation _;
|
||||
|
||||
game::VariableValue value{};
|
||||
if (!safe_execution::get_entity_field(entref, id, &value))
|
||||
{
|
||||
throw std::runtime_error("Failed to get value for field '" + field + "'");
|
||||
}
|
||||
|
||||
const auto __ = gsl::finally([value]()
|
||||
{
|
||||
game::RemoveRefToValue(value.type, value.u);
|
||||
});
|
||||
|
||||
return value;
|
||||
}
|
||||
else
|
||||
{
|
||||
return get_custom_field(entity, field);
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
}
|
37
src/game/scripting/execution.hpp
Normal file
37
src/game/scripting/execution.hpp
Normal file
@ -0,0 +1,37 @@
|
||||
#pragma once
|
||||
#include "game/game.hpp"
|
||||
#include "entity.hpp"
|
||||
#include "script_value.hpp"
|
||||
|
||||
namespace scripting
|
||||
{
|
||||
void push_value(const script_value& value);
|
||||
|
||||
script_value call_function(const std::string& name, const std::vector<script_value>& arguments);
|
||||
script_value call_function(const std::string& name, const entity& entity,
|
||||
const std::vector<script_value>& arguments);
|
||||
|
||||
template <typename T = script_value>
|
||||
T call(const std::string& name, const std::vector<script_value>& arguments = {});
|
||||
|
||||
template <>
|
||||
script_value call(const std::string& name, const std::vector<script_value>& arguments);
|
||||
|
||||
template <typename T>
|
||||
T call(const std::string& name, const std::vector<script_value>& arguments)
|
||||
{
|
||||
return call<script_value>(name, arguments).as<T>();
|
||||
}
|
||||
|
||||
script_value exec_ent_thread(const entity& entity, const char* pos, const std::vector<script_value>& arguments);
|
||||
script_value call_script_function(const entity& entity, const std::string& filename,
|
||||
const std::string& function, const std::vector<script_value>& arguments);
|
||||
|
||||
void clear_entity_fields(const entity& entity);
|
||||
void clear_custom_fields();
|
||||
|
||||
void set_entity_field(const entity& entity, const std::string& field, const script_value& value);
|
||||
script_value get_entity_field(const entity& entity, const std::string& field);
|
||||
|
||||
void notify(const entity& entity, const std::string& event, const std::vector<script_value>& arguments);
|
||||
}
|
7483
src/game/scripting/function_tables.cpp
Normal file
7483
src/game/scripting/function_tables.cpp
Normal file
File diff suppressed because it is too large
Load Diff
93
src/game/scripting/functions.cpp
Normal file
93
src/game/scripting/functions.cpp
Normal file
@ -0,0 +1,93 @@
|
||||
#include <stdinc.hpp>
|
||||
#include "functions.hpp"
|
||||
|
||||
#include <utils/string.hpp>
|
||||
|
||||
namespace scripting
|
||||
{
|
||||
namespace
|
||||
{
|
||||
std::unordered_map<std::string, unsigned> lowercase_map(
|
||||
const std::unordered_map<std::string, unsigned>& old_map)
|
||||
{
|
||||
std::unordered_map<std::string, unsigned> new_map{};
|
||||
for (auto& entry : old_map)
|
||||
{
|
||||
new_map[utils::string::to_lower(entry.first)] = entry.second;
|
||||
}
|
||||
|
||||
return new_map;
|
||||
}
|
||||
|
||||
const std::unordered_map<std::string, unsigned>& get_methods()
|
||||
{
|
||||
static auto methods = lowercase_map(method_map);
|
||||
return methods;
|
||||
}
|
||||
|
||||
const std::unordered_map<std::string, unsigned>& get_functions()
|
||||
{
|
||||
static auto function = lowercase_map(function_map);
|
||||
return function;
|
||||
}
|
||||
|
||||
int find_function_index(const std::string& name, const bool prefer_global)
|
||||
{
|
||||
const auto target = utils::string::to_lower(name);
|
||||
|
||||
const auto& primary_map = prefer_global
|
||||
? get_functions()
|
||||
: get_methods();
|
||||
const auto& secondary_map = !prefer_global
|
||||
? get_functions()
|
||||
: get_methods();
|
||||
|
||||
auto function_entry = primary_map.find(target);
|
||||
if (function_entry != primary_map.end())
|
||||
{
|
||||
return function_entry->second;
|
||||
}
|
||||
|
||||
function_entry = secondary_map.find(target);
|
||||
if (function_entry != secondary_map.end())
|
||||
{
|
||||
return function_entry->second;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
script_function get_function_by_index(const unsigned index)
|
||||
{
|
||||
static const auto function_table = 0x1D6EB34;
|
||||
static const auto method_table = 0x1D4F258;
|
||||
|
||||
if (index < 0x1C7)
|
||||
{
|
||||
return reinterpret_cast<script_function*>(function_table)[index];
|
||||
}
|
||||
|
||||
return reinterpret_cast<script_function*>(method_table)[index];
|
||||
}
|
||||
}
|
||||
|
||||
int find_token_id(const std::string& name)
|
||||
{
|
||||
const auto result = token_map.find(name);
|
||||
|
||||
if (result != token_map.end())
|
||||
{
|
||||
return result->second;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
script_function find_function(const std::string& name, const bool prefer_global)
|
||||
{
|
||||
const auto index = find_function_index(name, prefer_global);
|
||||
if (index < 0) return nullptr;
|
||||
|
||||
return get_function_by_index(index);
|
||||
}
|
||||
}
|
15
src/game/scripting/functions.hpp
Normal file
15
src/game/scripting/functions.hpp
Normal file
@ -0,0 +1,15 @@
|
||||
#pragma once
|
||||
#include "game/game.hpp"
|
||||
|
||||
namespace scripting
|
||||
{
|
||||
extern std::unordered_map<std::string, unsigned> method_map;
|
||||
extern std::unordered_map<std::string, unsigned> function_map;
|
||||
extern std::unordered_map<std::string, unsigned> token_map;
|
||||
extern std::unordered_map<unsigned, std::string> file_list;
|
||||
|
||||
using script_function = void(*)(game::scr_entref_t);
|
||||
|
||||
script_function find_function(const std::string& name, const bool prefer_global);
|
||||
int find_token_id(const std::string& name);
|
||||
}
|
74
src/game/scripting/safe_execution.cpp
Normal file
74
src/game/scripting/safe_execution.cpp
Normal file
@ -0,0 +1,74 @@
|
||||
#include <stdinc.hpp>
|
||||
#include "safe_execution.hpp"
|
||||
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable: 4611)
|
||||
|
||||
namespace scripting::safe_execution
|
||||
{
|
||||
namespace
|
||||
{
|
||||
bool execute_with_seh(const script_function function, const game::scr_entref_t& entref)
|
||||
{
|
||||
__try
|
||||
{
|
||||
function(entref);
|
||||
return true;
|
||||
}
|
||||
__except (EXCEPTION_EXECUTE_HANDLER)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool call(const script_function function, const game::scr_entref_t& entref)
|
||||
{
|
||||
*game::g_script_error_level += 1;
|
||||
if (game::_setjmp(&game::g_script_error[*game::g_script_error_level]))
|
||||
{
|
||||
*game::g_script_error_level -= 1;
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto result = execute_with_seh(function, entref);
|
||||
*game::g_script_error_level -= 1;
|
||||
return result;
|
||||
}
|
||||
|
||||
bool set_entity_field(const game::scr_entref_t& entref, const int offset)
|
||||
{
|
||||
*game::g_script_error_level += 1;
|
||||
if (game::_setjmp(&game::g_script_error[*game::g_script_error_level]))
|
||||
{
|
||||
*game::g_script_error_level -= 1;
|
||||
return false;
|
||||
}
|
||||
|
||||
game::Scr_SetObjectField(entref.classnum, entref.entnum, offset);
|
||||
|
||||
*game::g_script_error_level -= 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool get_entity_field(const game::scr_entref_t& entref, const int offset, game::VariableValue* value)
|
||||
{
|
||||
*game::g_script_error_level += 1;
|
||||
if (game::_setjmp(&game::g_script_error[*game::g_script_error_level]))
|
||||
{
|
||||
value->type = game::SCRIPT_NONE;
|
||||
value->u.intValue = 0;
|
||||
*game::g_script_error_level -= 1;
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto _value = game::GetEntityFieldValue(entref.classnum, entref.entnum, offset);
|
||||
value->type = _value.type;
|
||||
value->u = _value.u;
|
||||
|
||||
*game::g_script_error_level -= 1;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
#pragma warning(pop)
|
10
src/game/scripting/safe_execution.hpp
Normal file
10
src/game/scripting/safe_execution.hpp
Normal file
@ -0,0 +1,10 @@
|
||||
#pragma once
|
||||
#include "functions.hpp"
|
||||
|
||||
namespace scripting::safe_execution
|
||||
{
|
||||
bool call(script_function function, const game::scr_entref_t& entref);
|
||||
|
||||
bool set_entity_field(const game::scr_entref_t& entref, int offset);
|
||||
bool get_entity_field(const game::scr_entref_t& entref, int offset, game::VariableValue* value);
|
||||
}
|
287
src/game/scripting/script_value.cpp
Normal file
287
src/game/scripting/script_value.cpp
Normal file
@ -0,0 +1,287 @@
|
||||
#include <stdinc.hpp>
|
||||
#include "script_value.hpp"
|
||||
#include "entity.hpp"
|
||||
|
||||
|
||||
namespace scripting
|
||||
{
|
||||
/***************************************************************
|
||||
* Constructors
|
||||
**************************************************************/
|
||||
|
||||
script_value::script_value(const game::VariableValue& value)
|
||||
: value_(value)
|
||||
{
|
||||
}
|
||||
|
||||
script_value::script_value(void* value)
|
||||
{
|
||||
game::VariableValue variable{};
|
||||
variable.type = game::SCRIPT_INTEGER;
|
||||
variable.u.intValue = reinterpret_cast<uintptr_t>(value);
|
||||
|
||||
this->value_ = variable;
|
||||
}
|
||||
|
||||
script_value::script_value(const int value)
|
||||
{
|
||||
game::VariableValue variable{};
|
||||
variable.type = game::SCRIPT_INTEGER;
|
||||
variable.u.intValue = value;
|
||||
|
||||
this->value_ = variable;
|
||||
}
|
||||
|
||||
script_value::script_value(const unsigned int value)
|
||||
{
|
||||
game::VariableValue variable{};
|
||||
variable.type = game::SCRIPT_INTEGER;
|
||||
variable.u.uintValue = value;
|
||||
|
||||
this->value_ = variable;
|
||||
}
|
||||
|
||||
script_value::script_value(const bool value)
|
||||
: script_value(static_cast<unsigned>(value))
|
||||
{
|
||||
}
|
||||
|
||||
script_value::script_value(const float value)
|
||||
{
|
||||
game::VariableValue variable{};
|
||||
variable.type = game::SCRIPT_FLOAT;
|
||||
variable.u.floatValue = value;
|
||||
|
||||
this->value_ = variable;
|
||||
}
|
||||
|
||||
script_value::script_value(const double value)
|
||||
: script_value(static_cast<float>(value))
|
||||
{
|
||||
}
|
||||
|
||||
script_value::script_value(const char* value)
|
||||
{
|
||||
game::VariableValue variable{};
|
||||
variable.type = game::SCRIPT_STRING;
|
||||
variable.u.stringValue = game::SL_GetString(value, 0);
|
||||
|
||||
const auto _ = gsl::finally([&variable]()
|
||||
{
|
||||
game::RemoveRefToValue(variable.type, variable.u);
|
||||
});
|
||||
|
||||
this->value_ = variable;
|
||||
}
|
||||
|
||||
script_value::script_value(const std::string& value)
|
||||
: script_value(value.data())
|
||||
{
|
||||
}
|
||||
|
||||
script_value::script_value(const entity& value)
|
||||
{
|
||||
game::VariableValue variable{};
|
||||
variable.type = game::SCRIPT_OBJECT;
|
||||
variable.u.pointerValue = value.get_entity_id();
|
||||
|
||||
this->value_ = variable;
|
||||
}
|
||||
|
||||
script_value::script_value(const vector& value)
|
||||
{
|
||||
game::VariableValue variable{};
|
||||
variable.type = game::SCRIPT_VECTOR;
|
||||
variable.u.vectorValue = game::Scr_AllocVector(value);
|
||||
|
||||
const auto _ = gsl::finally([&variable]()
|
||||
{
|
||||
game::RemoveRefToValue(variable.type, variable.u);
|
||||
});
|
||||
|
||||
this->value_ = variable;
|
||||
}
|
||||
|
||||
/***************************************************************
|
||||
* Integer
|
||||
**************************************************************/
|
||||
|
||||
template <>
|
||||
bool script_value::is<int>() const
|
||||
{
|
||||
return this->get_raw().type == game::SCRIPT_INTEGER;
|
||||
}
|
||||
|
||||
template <>
|
||||
bool script_value::is<unsigned int>() const
|
||||
{
|
||||
return this->is<int>();
|
||||
}
|
||||
|
||||
template <>
|
||||
bool script_value::is<bool>() const
|
||||
{
|
||||
return this->is<int>();
|
||||
}
|
||||
|
||||
template <>
|
||||
int script_value::get() const
|
||||
{
|
||||
return this->get_raw().u.intValue;
|
||||
}
|
||||
|
||||
template <>
|
||||
unsigned int script_value::get() const
|
||||
{
|
||||
return this->get_raw().u.uintValue;
|
||||
}
|
||||
|
||||
template <>
|
||||
bool script_value::get() const
|
||||
{
|
||||
return this->get_raw().u.uintValue != 0;
|
||||
}
|
||||
|
||||
/***************************************************************
|
||||
* Float
|
||||
**************************************************************/
|
||||
|
||||
template <>
|
||||
bool script_value::is<float>() const
|
||||
{
|
||||
return this->get_raw().type == game::SCRIPT_FLOAT;
|
||||
}
|
||||
|
||||
template <>
|
||||
bool script_value::is<double>() const
|
||||
{
|
||||
return this->is<float>();
|
||||
}
|
||||
|
||||
template <>
|
||||
float script_value::get() const
|
||||
{
|
||||
return this->get_raw().u.floatValue;
|
||||
}
|
||||
|
||||
template <>
|
||||
double script_value::get() const
|
||||
{
|
||||
return static_cast<double>(this->get_raw().u.floatValue);
|
||||
}
|
||||
|
||||
/***************************************************************
|
||||
* String
|
||||
**************************************************************/
|
||||
|
||||
template <>
|
||||
bool script_value::is<const char*>() const
|
||||
{
|
||||
return this->get_raw().type == game::SCRIPT_STRING;
|
||||
}
|
||||
|
||||
template <>
|
||||
bool script_value::is<std::string>() const
|
||||
{
|
||||
return this->is<const char*>();
|
||||
}
|
||||
|
||||
template <>
|
||||
const char* script_value::get() const
|
||||
{
|
||||
return game::SL_ConvertToString(static_cast<unsigned int>(this->get_raw().u.stringValue));
|
||||
}
|
||||
|
||||
template <>
|
||||
std::string script_value::get() const
|
||||
{
|
||||
return this->get<const char*>();
|
||||
}
|
||||
|
||||
/***************************************************************
|
||||
* Entity
|
||||
**************************************************************/
|
||||
|
||||
template <>
|
||||
bool script_value::is<entity>() const
|
||||
{
|
||||
return this->get_raw().type == game::SCRIPT_OBJECT;
|
||||
}
|
||||
|
||||
template <>
|
||||
entity script_value::get() const
|
||||
{
|
||||
return entity(this->get_raw().u.pointerValue);
|
||||
}
|
||||
|
||||
/***************************************************************
|
||||
* Array
|
||||
**************************************************************/
|
||||
|
||||
template <>
|
||||
bool script_value::is<std::vector<script_value>>() const
|
||||
{
|
||||
if (this->get_raw().type != game::SCRIPT_OBJECT)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto id = this->get_raw().u.uintValue;
|
||||
const auto type = game::scr_VarGlob->objectVariableValue[id].w.type;
|
||||
|
||||
return type == game::SCRIPT_ARRAY;
|
||||
}
|
||||
|
||||
/***************************************************************
|
||||
* Struct
|
||||
**************************************************************/
|
||||
|
||||
template <>
|
||||
bool script_value::is<std::map<std::string, script_value>>() const
|
||||
{
|
||||
if (this->get_raw().type != game::SCRIPT_OBJECT)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto id = this->get_raw().u.uintValue;
|
||||
const auto type = game::scr_VarGlob->objectVariableValue[id].w.type;
|
||||
|
||||
return type == game::SCRIPT_STRUCT;
|
||||
}
|
||||
|
||||
/***************************************************************
|
||||
* Function
|
||||
**************************************************************/
|
||||
|
||||
template <>
|
||||
bool script_value::is<std::function<void()>>() const
|
||||
{
|
||||
return this->get_raw().type == game::SCRIPT_FUNCTION;
|
||||
}
|
||||
|
||||
/***************************************************************
|
||||
* Vector
|
||||
**************************************************************/
|
||||
|
||||
template <>
|
||||
bool script_value::is<vector>() const
|
||||
{
|
||||
return this->get_raw().type == game::SCRIPT_VECTOR;
|
||||
}
|
||||
|
||||
template <>
|
||||
vector script_value::get() const
|
||||
{
|
||||
return this->get_raw().u.vectorValue;
|
||||
}
|
||||
|
||||
/***************************************************************
|
||||
*
|
||||
**************************************************************/
|
||||
|
||||
const game::VariableValue& script_value::get_raw() const
|
||||
{
|
||||
return this->value_.get();
|
||||
}
|
||||
}
|
65
src/game/scripting/script_value.hpp
Normal file
65
src/game/scripting/script_value.hpp
Normal file
@ -0,0 +1,65 @@
|
||||
#pragma once
|
||||
#include "game/game.hpp"
|
||||
#include "variable_value.hpp"
|
||||
#include "vector.hpp"
|
||||
|
||||
namespace scripting
|
||||
{
|
||||
class entity;
|
||||
|
||||
class script_value
|
||||
{
|
||||
public:
|
||||
script_value() = default;
|
||||
script_value(const game::VariableValue& value);
|
||||
|
||||
script_value(void* value);
|
||||
|
||||
script_value(int value);
|
||||
script_value(unsigned int value);
|
||||
script_value(bool value);
|
||||
|
||||
script_value(float value);
|
||||
script_value(double value);
|
||||
|
||||
script_value(const char* value);
|
||||
script_value(const std::string& value);
|
||||
|
||||
script_value(const entity& value);
|
||||
|
||||
script_value(const vector& value);
|
||||
|
||||
template <typename T>
|
||||
bool is() const;
|
||||
|
||||
template <typename T>
|
||||
T as() const
|
||||
{
|
||||
if (!this->is<T>())
|
||||
{
|
||||
throw std::runtime_error("Invalid type");
|
||||
}
|
||||
|
||||
return get<T>();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T* as_ptr()
|
||||
{
|
||||
if (!this->is<int>())
|
||||
{
|
||||
throw std::runtime_error("Invalid type");
|
||||
}
|
||||
|
||||
return reinterpret_cast<T*>(this->get<int>());
|
||||
}
|
||||
|
||||
const game::VariableValue& get_raw() const;
|
||||
|
||||
private:
|
||||
template <typename T>
|
||||
T get() const;
|
||||
|
||||
variable_value value_{};
|
||||
};
|
||||
}
|
27
src/game/scripting/stack_isolation.cpp
Normal file
27
src/game/scripting/stack_isolation.cpp
Normal file
@ -0,0 +1,27 @@
|
||||
#include <stdinc.hpp>
|
||||
#include "stack_isolation.hpp"
|
||||
|
||||
namespace scripting
|
||||
{
|
||||
stack_isolation::stack_isolation()
|
||||
{
|
||||
this->in_param_count_ = game::scr_VmPub->inparamcount;
|
||||
this->out_param_count_ = game::scr_VmPub->outparamcount;
|
||||
this->top_ = game::scr_VmPub->top;
|
||||
this->max_stack_ = game::scr_VmPub->maxstack;
|
||||
|
||||
game::scr_VmPub->top = this->stack_;
|
||||
game::scr_VmPub->maxstack = &this->stack_[ARRAYSIZE(this->stack_) - 1];
|
||||
game::scr_VmPub->inparamcount = 0;
|
||||
game::scr_VmPub->outparamcount = 0;
|
||||
}
|
||||
|
||||
stack_isolation::~stack_isolation()
|
||||
{
|
||||
game::Scr_ClearOutParams();
|
||||
game::scr_VmPub->inparamcount = this->in_param_count_;
|
||||
game::scr_VmPub->outparamcount = this->out_param_count_;
|
||||
game::scr_VmPub->top = this->top_;
|
||||
game::scr_VmPub->maxstack = this->max_stack_;
|
||||
}
|
||||
}
|
25
src/game/scripting/stack_isolation.hpp
Normal file
25
src/game/scripting/stack_isolation.hpp
Normal file
@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
#include "game/game.hpp"
|
||||
|
||||
namespace scripting
|
||||
{
|
||||
class stack_isolation final
|
||||
{
|
||||
public:
|
||||
stack_isolation();
|
||||
~stack_isolation();
|
||||
|
||||
stack_isolation(stack_isolation&&) = delete;
|
||||
stack_isolation(const stack_isolation&) = delete;
|
||||
stack_isolation& operator=(stack_isolation&&) = delete;
|
||||
stack_isolation& operator=(const stack_isolation&) = delete;
|
||||
|
||||
private:
|
||||
game::VariableValue stack_[512]{};
|
||||
|
||||
game::VariableValue* max_stack_;
|
||||
game::VariableValue* top_;
|
||||
unsigned int in_param_count_;
|
||||
unsigned int out_param_count_;
|
||||
};
|
||||
}
|
68
src/game/scripting/variable_value.cpp
Normal file
68
src/game/scripting/variable_value.cpp
Normal file
@ -0,0 +1,68 @@
|
||||
#include <stdinc.hpp>
|
||||
#include "variable_value.hpp"
|
||||
|
||||
namespace scripting
|
||||
{
|
||||
variable_value::variable_value(const game::VariableValue& value)
|
||||
{
|
||||
this->assign(value);
|
||||
}
|
||||
|
||||
variable_value::variable_value(const variable_value& other) noexcept
|
||||
{
|
||||
this->operator=(other);
|
||||
}
|
||||
|
||||
variable_value::variable_value(variable_value&& other) noexcept
|
||||
{
|
||||
this->operator=(std::move(other));
|
||||
}
|
||||
|
||||
variable_value& variable_value::operator=(const variable_value& other) noexcept
|
||||
{
|
||||
if (this != &other)
|
||||
{
|
||||
this->release();
|
||||
this->assign(other.value_);
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
variable_value& variable_value::operator=(variable_value&& other) noexcept
|
||||
{
|
||||
if (this != &other)
|
||||
{
|
||||
this->release();
|
||||
this->value_ = other.value_;
|
||||
other.value_.type = game::SCRIPT_NONE;
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
variable_value::~variable_value()
|
||||
{
|
||||
this->release();
|
||||
}
|
||||
|
||||
const game::VariableValue& variable_value::get() const
|
||||
{
|
||||
return this->value_;
|
||||
}
|
||||
|
||||
void variable_value::assign(const game::VariableValue& value)
|
||||
{
|
||||
this->value_ = value;
|
||||
game::AddRefToValue(this->value_.type, this->value_.u);
|
||||
}
|
||||
|
||||
void variable_value::release()
|
||||
{
|
||||
if (this->value_.type != game::SCRIPT_NONE)
|
||||
{
|
||||
game::RemoveRefToValue(this->value_.type, this->value_.u);
|
||||
this->value_.type = game::SCRIPT_NONE;
|
||||
}
|
||||
}
|
||||
}
|
27
src/game/scripting/variable_value.hpp
Normal file
27
src/game/scripting/variable_value.hpp
Normal file
@ -0,0 +1,27 @@
|
||||
#pragma once
|
||||
#include "game/game.hpp"
|
||||
|
||||
namespace scripting
|
||||
{
|
||||
class variable_value
|
||||
{
|
||||
public:
|
||||
variable_value() = default;
|
||||
variable_value(const game::VariableValue& value);
|
||||
variable_value(const variable_value& other) noexcept;
|
||||
variable_value(variable_value&& other) noexcept;
|
||||
|
||||
variable_value& operator=(const variable_value& other) noexcept;
|
||||
variable_value& operator=(variable_value&& other) noexcept;
|
||||
|
||||
~variable_value();
|
||||
|
||||
const game::VariableValue& get() const;
|
||||
|
||||
private:
|
||||
void assign(const game::VariableValue& value);
|
||||
void release();
|
||||
|
||||
game::VariableValue value_{{0}, game::SCRIPT_NONE};
|
||||
};
|
||||
}
|
85
src/game/scripting/vector.cpp
Normal file
85
src/game/scripting/vector.cpp
Normal file
@ -0,0 +1,85 @@
|
||||
#include <stdinc.hpp>
|
||||
#include "vector.hpp"
|
||||
|
||||
namespace scripting
|
||||
{
|
||||
vector::vector(const float* value)
|
||||
{
|
||||
for (auto i = 0; i < 3; ++i)
|
||||
{
|
||||
this->value_[i] = value[i];
|
||||
}
|
||||
}
|
||||
|
||||
vector::vector(const game::vec3_t& value)
|
||||
: vector(&value[0])
|
||||
{
|
||||
}
|
||||
|
||||
vector::vector(const float x, const float y, const float z)
|
||||
{
|
||||
this->value_[0] = x;
|
||||
this->value_[1] = y;
|
||||
this->value_[2] = z;
|
||||
}
|
||||
|
||||
vector::operator game::vec3_t& ()
|
||||
{
|
||||
return this->value_;
|
||||
}
|
||||
|
||||
vector::operator const game::vec3_t& () const
|
||||
{
|
||||
return this->value_;
|
||||
}
|
||||
|
||||
game::vec_t& vector::operator[](const size_t i)
|
||||
{
|
||||
if (i >= 3)
|
||||
{
|
||||
throw std::runtime_error("Out of bounds.");
|
||||
}
|
||||
|
||||
return this->value_[i];
|
||||
}
|
||||
|
||||
const game::vec_t& vector::operator[](const size_t i) const
|
||||
{
|
||||
if (i >= 3)
|
||||
{
|
||||
throw std::runtime_error("Out of bounds.");
|
||||
}
|
||||
|
||||
return this->value_[i];
|
||||
}
|
||||
|
||||
float vector::get_x() const
|
||||
{
|
||||
return this->operator[](0);
|
||||
}
|
||||
|
||||
float vector::get_y() const
|
||||
{
|
||||
return this->operator[](1);
|
||||
}
|
||||
|
||||
float vector::get_z() const
|
||||
{
|
||||
return this->operator[](2);
|
||||
}
|
||||
|
||||
void vector::set_x(const float value)
|
||||
{
|
||||
this->operator[](0) = value;
|
||||
}
|
||||
|
||||
void vector::set_y(const float value)
|
||||
{
|
||||
this->operator[](1) = value;
|
||||
}
|
||||
|
||||
void vector::set_z(const float value)
|
||||
{
|
||||
this->operator[](2) = value;
|
||||
}
|
||||
}
|
31
src/game/scripting/vector.hpp
Normal file
31
src/game/scripting/vector.hpp
Normal file
@ -0,0 +1,31 @@
|
||||
#pragma once
|
||||
#include "game/game.hpp"
|
||||
|
||||
namespace scripting
|
||||
{
|
||||
class vector final
|
||||
{
|
||||
public:
|
||||
vector() = default;
|
||||
vector(const float* value);
|
||||
vector(const game::vec3_t& value);
|
||||
vector(float x, float y, float z);
|
||||
|
||||
operator game::vec3_t& ();
|
||||
operator const game::vec3_t& () const;
|
||||
|
||||
game::vec_t& operator[](size_t i);
|
||||
const game::vec_t& operator[](size_t i) const;
|
||||
|
||||
float get_x() const;
|
||||
float get_y() const;
|
||||
float get_z() const;
|
||||
|
||||
void set_x(float value);
|
||||
void set_y(float value);
|
||||
void set_z(float value);
|
||||
|
||||
private:
|
||||
game::vec3_t value_{ 0 };
|
||||
};
|
||||
}
|
301
src/game/structs.hpp
Normal file
301
src/game/structs.hpp
Normal file
@ -0,0 +1,301 @@
|
||||
#pragma once
|
||||
|
||||
namespace game
|
||||
{
|
||||
typedef float vec_t;
|
||||
typedef vec_t vec2_t[2];
|
||||
typedef vec_t vec3_t[3];
|
||||
typedef vec_t vec4_t[4];
|
||||
|
||||
struct cmd_function_t
|
||||
{
|
||||
cmd_function_t* next;
|
||||
const char* name;
|
||||
const char* autoCompleteDir;
|
||||
const char* autoCompleteExt;
|
||||
void(__cdecl* function)();
|
||||
int flags;
|
||||
};
|
||||
|
||||
struct msg_t
|
||||
{
|
||||
int overflowed;
|
||||
int readOnly;
|
||||
char* data;
|
||||
char* splitData;
|
||||
int maxsize;
|
||||
int cursize;
|
||||
int splitSize;
|
||||
int readcount;
|
||||
int bit;
|
||||
int lastEntityRef;
|
||||
};
|
||||
|
||||
struct XZoneInfo
|
||||
{
|
||||
const char* name;
|
||||
int allocFlags;
|
||||
int freeFlags;
|
||||
};
|
||||
|
||||
struct scr_entref_t
|
||||
{
|
||||
unsigned __int16 entnum;
|
||||
unsigned __int16 classnum;
|
||||
};
|
||||
|
||||
typedef void(__cdecl* scr_call_t)(int entref);
|
||||
|
||||
enum MeansOfDeath
|
||||
{
|
||||
MOD_UNKNOWN = 0,
|
||||
MOD_PISTOL_BULLET = 1,
|
||||
MOD_RIFLE_BULLET = 2,
|
||||
MOD_EXPLOSIVE_BULLET = 3,
|
||||
MOD_GRENADE = 4,
|
||||
MOD_GRENADE_SPLASH = 5,
|
||||
MOD_PROJECTILE = 6,
|
||||
MOD_PROJECTILE_SPLASH = 7,
|
||||
MOD_MELEE = 8,
|
||||
MOD_HEAD_SHOT = 9,
|
||||
MOD_CRUSH = 10,
|
||||
MOD_FALLING = 11,
|
||||
MOD_SUICIDE = 12,
|
||||
MOD_TRIGGER_HURT = 13,
|
||||
MOD_EXPLOSIVE = 14,
|
||||
MOD_IMPACT = 15,
|
||||
MOD_NUM = 16
|
||||
};
|
||||
|
||||
enum scriptType_e
|
||||
{
|
||||
SCRIPT_NONE = 0,
|
||||
SCRIPT_OBJECT = 1,
|
||||
SCRIPT_STRING = 2,
|
||||
SCRIPT_ISTRING = 3,
|
||||
SCRIPT_VECTOR = 4,
|
||||
SCRIPT_FLOAT = 5,
|
||||
SCRIPT_INTEGER = 6,
|
||||
SCRIPT_END = 8,
|
||||
SCRIPT_FUNCTION = 9,
|
||||
SCRIPT_STRUCT = 19,
|
||||
SCRIPT_ARRAY = 22,
|
||||
};
|
||||
|
||||
struct VariableStackBuffer
|
||||
{
|
||||
const char* pos;
|
||||
unsigned __int16 size;
|
||||
unsigned __int16 bufLen;
|
||||
unsigned __int16 localId;
|
||||
char time;
|
||||
char buf[1];
|
||||
};
|
||||
|
||||
union VariableUnion
|
||||
{
|
||||
int intValue;
|
||||
float floatValue;
|
||||
unsigned int stringValue;
|
||||
const float* vectorValue;
|
||||
const char* codePosValue;
|
||||
unsigned int pointerValue;
|
||||
VariableStackBuffer* stackValue;
|
||||
unsigned int entityId;
|
||||
unsigned int uintValue;
|
||||
};
|
||||
|
||||
struct VariableValue
|
||||
{
|
||||
VariableUnion u;
|
||||
scriptType_e type;
|
||||
};
|
||||
|
||||
struct function_stack_t
|
||||
{
|
||||
const char* pos;
|
||||
unsigned int localId;
|
||||
unsigned int localVarCount;
|
||||
VariableValue* top;
|
||||
VariableValue* startTop;
|
||||
};
|
||||
|
||||
struct function_frame_t
|
||||
{
|
||||
function_stack_t fs;
|
||||
int topType;
|
||||
};
|
||||
|
||||
struct scrVmPub_t
|
||||
{
|
||||
unsigned int* localVars;
|
||||
VariableValue* maxstack;
|
||||
int function_count;
|
||||
function_frame_t* function_frame;
|
||||
VariableValue* top;
|
||||
/*bool debugCode;
|
||||
bool abort_on_error;
|
||||
bool terminal_error;
|
||||
bool block_execution;*/
|
||||
unsigned int inparamcount;
|
||||
unsigned int outparamcount;
|
||||
unsigned int breakpointOutparamcount;
|
||||
bool showError;
|
||||
function_frame_t function_frame_start[32];
|
||||
VariableValue stack[2048];
|
||||
};
|
||||
|
||||
struct scr_classStruct_t
|
||||
{
|
||||
unsigned __int16 id;
|
||||
unsigned __int16 entArrayId;
|
||||
char charId;
|
||||
const char* name;
|
||||
};
|
||||
|
||||
struct ObjectVariableChildren
|
||||
{
|
||||
unsigned __int16 firstChild;
|
||||
unsigned __int16 lastChild;
|
||||
};
|
||||
|
||||
struct ObjectVariableValue_u_f
|
||||
{
|
||||
unsigned __int16 prev;
|
||||
unsigned __int16 next;
|
||||
};
|
||||
|
||||
union ObjectVariableValue_u_o_u
|
||||
{
|
||||
unsigned __int16 size;
|
||||
unsigned __int16 entnum;
|
||||
unsigned __int16 nextEntId;
|
||||
unsigned __int16 self;
|
||||
};
|
||||
|
||||
struct ObjectVariableValue_u_o
|
||||
{
|
||||
unsigned __int16 refCount;
|
||||
ObjectVariableValue_u_o_u u;
|
||||
};
|
||||
|
||||
union ObjectVariableValue_w
|
||||
{
|
||||
unsigned int type;
|
||||
unsigned int classnum;
|
||||
unsigned int notifyName;
|
||||
unsigned int waitTime;
|
||||
unsigned int parentLocalId;
|
||||
};
|
||||
|
||||
struct ChildVariableValue_u_f
|
||||
{
|
||||
unsigned __int16 prev;
|
||||
unsigned __int16 next;
|
||||
};
|
||||
|
||||
union ChildVariableValue_u
|
||||
{
|
||||
ChildVariableValue_u_f f;
|
||||
VariableUnion u;
|
||||
};
|
||||
|
||||
struct ChildBucketMatchKeys_keys
|
||||
{
|
||||
unsigned __int16 name_hi;
|
||||
unsigned __int16 parentId;
|
||||
};
|
||||
|
||||
union ChildBucketMatchKeys
|
||||
{
|
||||
ChildBucketMatchKeys_keys keys;
|
||||
unsigned int match;
|
||||
};
|
||||
|
||||
struct ChildVariableValue
|
||||
{
|
||||
ChildVariableValue_u u;
|
||||
unsigned __int16 next;
|
||||
char type;
|
||||
char name_lo;
|
||||
ChildBucketMatchKeys k;
|
||||
unsigned __int16 nextSibling;
|
||||
unsigned __int16 prevSibling;
|
||||
};
|
||||
|
||||
union ObjectVariableValue_u
|
||||
{
|
||||
ObjectVariableValue_u_f f;
|
||||
ObjectVariableValue_u_o o;
|
||||
};
|
||||
|
||||
struct ObjectVariableValue
|
||||
{
|
||||
ObjectVariableValue_u u;
|
||||
ObjectVariableValue_w w;
|
||||
};
|
||||
|
||||
struct scrVarGlob_t
|
||||
{
|
||||
ObjectVariableValue objectVariableValue[36864];
|
||||
ObjectVariableChildren objectVariableChildren[36864];
|
||||
unsigned __int16 childVariableBucket[65536];
|
||||
ChildVariableValue childVariableValue[102400];
|
||||
};
|
||||
|
||||
union DvarValue
|
||||
{
|
||||
bool enabled;
|
||||
int integer;
|
||||
unsigned int unsignedInt;
|
||||
float value;
|
||||
float vector[4];
|
||||
const char* string;
|
||||
char color[4];
|
||||
};
|
||||
|
||||
struct enum_limit
|
||||
{
|
||||
int stringCount;
|
||||
const char** strings;
|
||||
};
|
||||
|
||||
struct int_limit
|
||||
{
|
||||
int min;
|
||||
int max;
|
||||
};
|
||||
|
||||
struct float_limit
|
||||
{
|
||||
float min;
|
||||
float max;
|
||||
};
|
||||
|
||||
union DvarLimits
|
||||
{
|
||||
enum_limit enumeration;
|
||||
int_limit integer;
|
||||
float_limit value;
|
||||
float_limit vector;
|
||||
};
|
||||
|
||||
struct dvar_t
|
||||
{
|
||||
const char* name;
|
||||
unsigned int flags;
|
||||
char type;
|
||||
bool modified;
|
||||
DvarValue current;
|
||||
DvarValue latched;
|
||||
DvarValue reset;
|
||||
DvarLimits domain;
|
||||
bool(__cdecl* domainFunc)(dvar_t*, DvarValue);
|
||||
dvar_t* hashNext;
|
||||
};
|
||||
|
||||
struct gentity_s
|
||||
{
|
||||
int entnum;
|
||||
};
|
||||
}
|
69
src/game/symbols.hpp
Normal file
69
src/game/symbols.hpp
Normal file
@ -0,0 +1,69 @@
|
||||
#pragma once
|
||||
|
||||
#define WEAK __declspec(selectany)
|
||||
|
||||
namespace game
|
||||
{
|
||||
// Functions
|
||||
|
||||
WEAK symbol<void(int type, VariableUnion u)> AddRefToValue{0x5656E0};
|
||||
WEAK symbol<void(unsigned int id)> AddReftoObject{0x5655F0};
|
||||
WEAK symbol<unsigned int(unsigned int id)> AllocThread{0x565580};
|
||||
WEAK symbol<unsigned int()> AllocObject{0x565530};
|
||||
WEAK symbol<void(int type, VariableUnion u)> RemoveRefToValue{0x565730};
|
||||
|
||||
WEAK symbol<void(unsigned int weapon, bool isAlternate, char* output, unsigned int maxStringLen)> BG_GetWeaponNameComplete{0x42F760};
|
||||
|
||||
WEAK symbol<const char*(int index)> ConcatArgs{0x502150};
|
||||
WEAK symbol<void(int localClientNum, const char* text)> Cbuf_AddText{0x545680};
|
||||
WEAK symbol<void(const char* cmdName, void(), cmd_function_t* allocedCmd)> Cmd_AddCommandInternal{0x0};
|
||||
WEAK symbol<const char*(int index)> Cmd_Argv{0x0};
|
||||
|
||||
WEAK symbol<const dvar_t*(const char*)> Dvar_FindVar{0x5BDCC0};
|
||||
|
||||
WEAK symbol<char*(const char*)> I_CleanStr{0x0};
|
||||
|
||||
WEAK symbol<VariableValue(unsigned int classnum, int entnum, int offset)> GetEntityFieldValue{0x56AF20};
|
||||
WEAK symbol<unsigned int(unsigned int parentId, unsigned int name)> FindVariable{0x5651F0};
|
||||
WEAK symbol<unsigned int(unsigned int parentId, unsigned int name)> FindObject{0x565BD0};
|
||||
WEAK symbol<unsigned int(unsigned int parentId, unsigned int name)> GetVariable{0x5663E0};
|
||||
|
||||
WEAK symbol<const float* (const float* v)> Scr_AllocVector{0x565680};
|
||||
WEAK symbol<void()> Scr_ClearOutParams{0x569010};
|
||||
WEAK symbol<scr_entref_t(unsigned int entId)> Scr_GetEntityIdRef{0x565F60};
|
||||
WEAK symbol<void(unsigned int classnum, int entnum, int offset)> Scr_SetObjectField{0x52BCC0};
|
||||
WEAK symbol<void(int id, unsigned int stringValue, unsigned int paramcount)> Scr_NotifyId{0x56B5E0};
|
||||
WEAK symbol<int(const char* filename, unsigned int str)> Scr_GetFunctionHandle{0x5618A0};
|
||||
WEAK symbol<unsigned int(int handle, unsigned int objId, unsigned int paramcount)> Scr_ExecThreadInternal{0x56E1C0};
|
||||
|
||||
WEAK symbol<unsigned int(const char* str, unsigned int user)> SL_GetString{0x5649E0};
|
||||
WEAK symbol<const char*(unsigned int stringValue)> SL_ConvertToString{0x564270};
|
||||
|
||||
WEAK symbol<void(int clientNum, int type, const char* command)> SV_GameSendServerCommand{0x573220};
|
||||
WEAK symbol<void(int arg, char* buffer, int bufferLength)> SV_Cmd_ArgvBuffer{0x5459F0};
|
||||
|
||||
WEAK symbol<void(unsigned int notifyListOwnerId, unsigned int stringValue, VariableValue* top)> VM_Notify{0x569720};
|
||||
WEAK symbol<unsigned int(unsigned int localId, const char* pos, unsigned int paramcount)> VM_Execute{0x56DFE0};
|
||||
|
||||
WEAK symbol<void* (jmp_buf* Buf, int Value)> longjmp{0x7363BC};
|
||||
WEAK symbol<int(jmp_buf* Buf)> _setjmp{0x734CF8};
|
||||
|
||||
// Variables
|
||||
|
||||
WEAK symbol<int> g_script_error_level{0x20B21FC};
|
||||
WEAK symbol<jmp_buf> g_script_error{0x20B4218};
|
||||
|
||||
WEAK symbol<scrVmPub_t> scr_VmPub{0x20B4A80};
|
||||
WEAK symbol<scrVarGlob_t> scr_VarGlob{0x1E72180};
|
||||
|
||||
WEAK symbol<scr_classStruct_t*> g_classMap{0x8B4300};
|
||||
|
||||
WEAK symbol<gentity_s> g_entities{0x0};
|
||||
WEAK symbol<unsigned int> levelEntityId{0x208E1A4};
|
||||
|
||||
namespace plutonium
|
||||
{
|
||||
WEAK symbol<std::unordered_map<std::string, std::uint16_t>> function_map_rev{0x205862C0};
|
||||
WEAK symbol<std::unordered_map<std::string, std::uint16_t>> method_map_rev{0x205862E0};
|
||||
}
|
||||
}
|
35
src/loader/component_interface.hpp
Normal file
35
src/loader/component_interface.hpp
Normal file
@ -0,0 +1,35 @@
|
||||
#pragma once
|
||||
|
||||
class component_interface
|
||||
{
|
||||
public:
|
||||
virtual ~component_interface()
|
||||
{
|
||||
}
|
||||
|
||||
virtual void post_start()
|
||||
{
|
||||
}
|
||||
|
||||
virtual void post_load()
|
||||
{
|
||||
}
|
||||
|
||||
virtual void pre_destroy()
|
||||
{
|
||||
}
|
||||
|
||||
virtual void post_unpack()
|
||||
{
|
||||
}
|
||||
|
||||
virtual void* load_import([[maybe_unused]] const std::string& library, [[maybe_unused]] const std::string& function)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
virtual bool is_supported()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
};
|
127
src/loader/component_loader.cpp
Normal file
127
src/loader/component_loader.cpp
Normal file
@ -0,0 +1,127 @@
|
||||
#include <stdinc.hpp>
|
||||
#include "component_loader.hpp"
|
||||
|
||||
void component_loader::register_component(std::unique_ptr<component_interface>&& component_)
|
||||
{
|
||||
get_components().push_back(std::move(component_));
|
||||
}
|
||||
|
||||
bool component_loader::post_start()
|
||||
{
|
||||
static auto handled = false;
|
||||
if (handled) return true;
|
||||
handled = true;
|
||||
|
||||
try
|
||||
{
|
||||
for (const auto& component_ : get_components())
|
||||
{
|
||||
component_->post_start();
|
||||
}
|
||||
}
|
||||
catch (premature_shutdown_trigger&)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool component_loader::post_load()
|
||||
{
|
||||
static auto handled = false;
|
||||
if (handled) return true;
|
||||
handled = true;
|
||||
|
||||
clean();
|
||||
|
||||
try
|
||||
{
|
||||
for (const auto& component_ : get_components())
|
||||
{
|
||||
component_->post_load();
|
||||
}
|
||||
}
|
||||
catch (premature_shutdown_trigger&)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void component_loader::post_unpack()
|
||||
{
|
||||
static auto handled = false;
|
||||
if (handled) return;
|
||||
handled = true;
|
||||
|
||||
for (const auto& component_ : get_components())
|
||||
{
|
||||
component_->post_unpack();
|
||||
}
|
||||
}
|
||||
|
||||
void component_loader::pre_destroy()
|
||||
{
|
||||
static auto handled = false;
|
||||
if (handled) return;
|
||||
handled = true;
|
||||
|
||||
for (const auto& component_ : get_components())
|
||||
{
|
||||
component_->pre_destroy();
|
||||
}
|
||||
}
|
||||
|
||||
void component_loader::clean()
|
||||
{
|
||||
auto& components = get_components();
|
||||
for (auto i = components.begin(); i != components.end();)
|
||||
{
|
||||
if (!(*i)->is_supported())
|
||||
{
|
||||
(*i)->pre_destroy();
|
||||
i = components.erase(i);
|
||||
}
|
||||
else
|
||||
{
|
||||
++i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void* component_loader::load_import(const std::string& library, const std::string& function)
|
||||
{
|
||||
void* function_ptr = nullptr;
|
||||
|
||||
for (const auto& component_ : get_components())
|
||||
{
|
||||
auto* const component_function_ptr = component_->load_import(library, function);
|
||||
if (component_function_ptr)
|
||||
{
|
||||
function_ptr = component_function_ptr;
|
||||
}
|
||||
}
|
||||
|
||||
return function_ptr;
|
||||
}
|
||||
|
||||
void component_loader::trigger_premature_shutdown()
|
||||
{
|
||||
throw premature_shutdown_trigger();
|
||||
}
|
||||
|
||||
std::vector<std::unique_ptr<component_interface>>& component_loader::get_components()
|
||||
{
|
||||
using component_vector = std::vector<std::unique_ptr<component_interface>>;
|
||||
using component_vector_container = std::unique_ptr<component_vector, std::function<void(component_vector*)>>;
|
||||
|
||||
static component_vector_container components(new component_vector, [](component_vector* component_vector)
|
||||
{
|
||||
pre_destroy();
|
||||
delete component_vector;
|
||||
});
|
||||
|
||||
return *components;
|
||||
}
|
61
src/loader/component_loader.hpp
Normal file
61
src/loader/component_loader.hpp
Normal file
@ -0,0 +1,61 @@
|
||||
#pragma once
|
||||
#include "component_interface.hpp"
|
||||
|
||||
class component_loader final
|
||||
{
|
||||
public:
|
||||
class premature_shutdown_trigger final : public std::exception
|
||||
{
|
||||
[[nodiscard]] const char* what() const noexcept override
|
||||
{
|
||||
return "Premature shutdown requested";
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
class installer final
|
||||
{
|
||||
static_assert(std::is_base_of<component_interface, T>::value, "component has invalid base class");
|
||||
|
||||
public:
|
||||
installer()
|
||||
{
|
||||
register_component(std::make_unique<T>());
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
static T* get()
|
||||
{
|
||||
for (const auto& component_ : get_components())
|
||||
{
|
||||
if (typeid(*component_.get()) == typeid(T))
|
||||
{
|
||||
return reinterpret_cast<T*>(component_.get());
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static void register_component(std::unique_ptr<component_interface>&& component);
|
||||
|
||||
static bool post_start();
|
||||
static bool post_load();
|
||||
static void post_unpack();
|
||||
static void pre_destroy();
|
||||
static void clean();
|
||||
|
||||
static void* load_import(const std::string& library, const std::string& function);
|
||||
|
||||
static void trigger_premature_shutdown();
|
||||
|
||||
private:
|
||||
static std::vector<std::unique_ptr<component_interface>>& get_components();
|
||||
};
|
||||
|
||||
#define REGISTER_COMPONENT(name) \
|
||||
namespace \
|
||||
{ \
|
||||
static component_loader::installer<name> __component; \
|
||||
}
|
1
src/stdinc.cpp
Normal file
1
src/stdinc.cpp
Normal file
@ -0,0 +1 @@
|
||||
#include <stdinc.hpp>
|
41
src/stdinc.hpp
Normal file
41
src/stdinc.hpp
Normal file
@ -0,0 +1,41 @@
|
||||
#pragma once
|
||||
|
||||
#pragma warning(disable: 4018)
|
||||
#pragma warning(disable: 4146)
|
||||
#pragma warning(disable: 4244)
|
||||
#pragma warning(disable: 4267)
|
||||
#pragma warning(disable: 26812)
|
||||
|
||||
#define DLL_EXPORT extern "C" __declspec(dllexport)
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
|
||||
#include <vector>
|
||||
#include <cassert>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <fstream>
|
||||
#include <algorithm>
|
||||
#include <functional>
|
||||
#include <regex>
|
||||
#include <queue>
|
||||
#include <unordered_set>
|
||||
#include <filesystem>
|
||||
#include <map>
|
||||
#include <csetjmp>
|
||||
|
||||
using namespace std::literals;
|
||||
|
||||
#include <gsl/gsl>
|
||||
#include <MinHook.h>
|
||||
|
||||
#include "utils/memory.hpp"
|
||||
#include "utils/string.hpp"
|
||||
#include "utils/hook.hpp"
|
||||
#include "utils/concurrent_list.hpp"
|
||||
#include "utils/io.hpp"
|
||||
|
||||
#include "game/structs.hpp"
|
||||
#include "game/game.hpp"
|
144
src/utils/concurrent_list.hpp
Normal file
144
src/utils/concurrent_list.hpp
Normal file
@ -0,0 +1,144 @@
|
||||
#pragma once
|
||||
|
||||
#include <mutex>
|
||||
#include <memory>
|
||||
|
||||
// This class is trash. Need to get rid of it.
|
||||
|
||||
namespace utils
|
||||
{
|
||||
template <typename T>
|
||||
class concurrent_list final
|
||||
{
|
||||
public:
|
||||
class element final
|
||||
{
|
||||
public:
|
||||
explicit element(std::recursive_mutex* mutex, std::shared_ptr<T> entry = {},
|
||||
std::shared_ptr<element> next = {}) :
|
||||
mutex_(mutex),
|
||||
entry_(std::move(entry)),
|
||||
next_(std::move(next))
|
||||
{
|
||||
}
|
||||
|
||||
void remove(const std::shared_ptr<T>& element)
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> _(*this->mutex_);
|
||||
if (!this->next_) return;
|
||||
|
||||
if (this->next_->entry_.get() == element.get())
|
||||
{
|
||||
this->next_ = this->next_->next_;
|
||||
}
|
||||
else
|
||||
{
|
||||
this->next_->remove(element);
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] std::shared_ptr<element> get_next() const
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> _(*this->mutex_);
|
||||
return this->next_;
|
||||
}
|
||||
|
||||
std::shared_ptr<T> operator*() const
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> _(*this->mutex_);
|
||||
return this->entry_;
|
||||
}
|
||||
|
||||
element& operator++()
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> _(*this->mutex_);
|
||||
*this = this->next_ ? *this->next_ : element(this->mutex_);
|
||||
return *this;
|
||||
}
|
||||
|
||||
element operator++(int)
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> _(*this->mutex_);
|
||||
auto result = *this;
|
||||
this->operator++();
|
||||
return result;
|
||||
}
|
||||
|
||||
bool operator==(const element& other)
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> _(*this->mutex_);
|
||||
return this->entry_.get() == other.entry_.get();
|
||||
}
|
||||
|
||||
bool operator!=(const element& other)
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> _(*this->mutex_);
|
||||
return !(*this == other);
|
||||
}
|
||||
|
||||
private:
|
||||
std::recursive_mutex* mutex_;
|
||||
std::shared_ptr<T> entry_;
|
||||
std::shared_ptr<element> next_;
|
||||
};
|
||||
|
||||
element begin()
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> _(this->mutex_);
|
||||
return this->entry_ ? *this->entry_ : this->end();
|
||||
}
|
||||
|
||||
element end()
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> _(this->mutex_);
|
||||
return element(&this->mutex_);
|
||||
}
|
||||
|
||||
void remove(const element& entry)
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> _(this->mutex_);
|
||||
this->remove(*entry);
|
||||
}
|
||||
|
||||
void remove(const std::shared_ptr<T>& element)
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> _(this->mutex_);
|
||||
if (!this->entry_) return;
|
||||
|
||||
if ((**this->entry_).get() == element.get())
|
||||
{
|
||||
this->entry_ = this->entry_->get_next();
|
||||
}
|
||||
else
|
||||
{
|
||||
this->entry_->remove(element);
|
||||
}
|
||||
}
|
||||
|
||||
void add(const T& object)
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> _(this->mutex_);
|
||||
|
||||
const auto object_ptr = std::make_shared<T>(object);
|
||||
this->entry_ = std::make_shared<element>(&this->mutex_, object_ptr, this->entry_);
|
||||
}
|
||||
|
||||
void add(T&& object)
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> _(this->mutex_);
|
||||
|
||||
const auto object_ptr = std::make_shared<T>(std::move(object));
|
||||
this->entry_ = std::make_shared<element>(&this->mutex_, object_ptr, this->entry_);
|
||||
}
|
||||
|
||||
void clear()
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> _(this->mutex_);
|
||||
this->entry_ = {};
|
||||
}
|
||||
|
||||
private:
|
||||
std::recursive_mutex mutex_;
|
||||
std::shared_ptr<element> entry_;
|
||||
};
|
||||
}
|
180
src/utils/hook.cpp
Normal file
180
src/utils/hook.cpp
Normal file
@ -0,0 +1,180 @@
|
||||
#include <stdinc.hpp>
|
||||
|
||||
// iw6x-client
|
||||
|
||||
namespace utils::hook
|
||||
{
|
||||
namespace
|
||||
{
|
||||
[[maybe_unused]] class _
|
||||
{
|
||||
public:
|
||||
_()
|
||||
{
|
||||
if (MH_Initialize() != MH_OK)
|
||||
{
|
||||
throw std::runtime_error("Failed to initialize MinHook");
|
||||
}
|
||||
}
|
||||
|
||||
~_()
|
||||
{
|
||||
MH_Uninitialize();
|
||||
}
|
||||
} __;
|
||||
}
|
||||
|
||||
detour::detour(const size_t place, void* target) : detour(reinterpret_cast<void*>(place), target)
|
||||
{
|
||||
}
|
||||
|
||||
detour::detour(void* place, void* target)
|
||||
{
|
||||
this->create(place, target);
|
||||
}
|
||||
|
||||
detour::~detour()
|
||||
{
|
||||
this->clear();
|
||||
}
|
||||
|
||||
void detour::enable() const
|
||||
{
|
||||
MH_EnableHook(this->place_);
|
||||
}
|
||||
|
||||
void detour::disable() const
|
||||
{
|
||||
MH_DisableHook(this->place_);
|
||||
}
|
||||
|
||||
void detour::create(void* place, void* target)
|
||||
{
|
||||
this->clear();
|
||||
this->place_ = place;
|
||||
|
||||
if (MH_CreateHook(this->place_, target, &this->original_) != MH_OK)
|
||||
{
|
||||
throw std::runtime_error(string::va("Unable to create hook at location: %p", this->place_));
|
||||
}
|
||||
|
||||
this->enable();
|
||||
}
|
||||
|
||||
void detour::create(const size_t place, void* target)
|
||||
{
|
||||
this->create(reinterpret_cast<void*>(place), target);
|
||||
}
|
||||
|
||||
void detour::clear()
|
||||
{
|
||||
if (this->place_)
|
||||
{
|
||||
MH_RemoveHook(this->place_);
|
||||
}
|
||||
|
||||
this->place_ = nullptr;
|
||||
this->original_ = nullptr;
|
||||
}
|
||||
|
||||
void* detour::get_original() const
|
||||
{
|
||||
return this->original_;
|
||||
}
|
||||
|
||||
void nop(void* place, const size_t length)
|
||||
{
|
||||
DWORD old_protect{};
|
||||
VirtualProtect(place, length, PAGE_EXECUTE_READWRITE, &old_protect);
|
||||
|
||||
std::memset(place, 0x90, length);
|
||||
|
||||
VirtualProtect(place, length, old_protect, &old_protect);
|
||||
FlushInstructionCache(GetCurrentProcess(), place, length);
|
||||
}
|
||||
|
||||
void nop(const size_t place, const size_t length)
|
||||
{
|
||||
nop(reinterpret_cast<void*>(place), length);
|
||||
}
|
||||
|
||||
void copy(void* place, const void* data, const size_t length)
|
||||
{
|
||||
DWORD old_protect{};
|
||||
VirtualProtect(place, length, PAGE_EXECUTE_READWRITE, &old_protect);
|
||||
|
||||
std::memmove(place, data, length);
|
||||
|
||||
VirtualProtect(place, length, old_protect, &old_protect);
|
||||
FlushInstructionCache(GetCurrentProcess(), place, length);
|
||||
}
|
||||
|
||||
void copy(const size_t place, const void* data, const size_t length)
|
||||
{
|
||||
copy(reinterpret_cast<void*>(place), data, length);
|
||||
}
|
||||
|
||||
bool is_relatively_far(const void* pointer, const void* data, int offset)
|
||||
{
|
||||
const int64_t diff = size_t(data) - (size_t(pointer) + offset);
|
||||
const auto small_diff = int32_t(diff);
|
||||
return diff != int64_t(small_diff);
|
||||
}
|
||||
|
||||
void call(void* pointer, void* data)
|
||||
{
|
||||
if (is_relatively_far(pointer, data))
|
||||
{
|
||||
throw std::runtime_error("Too far away to create 32bit relative branch");
|
||||
}
|
||||
|
||||
auto* patch_pointer = PBYTE(pointer);
|
||||
set<uint8_t>(patch_pointer, 0xE8);
|
||||
set<int32_t>(patch_pointer + 1, int32_t(size_t(data) - (size_t(pointer) + 5)));
|
||||
}
|
||||
|
||||
void call(const size_t pointer, void* data)
|
||||
{
|
||||
return call(reinterpret_cast<void*>(pointer), data);
|
||||
}
|
||||
|
||||
void call(const size_t pointer, const size_t data)
|
||||
{
|
||||
return call(pointer, reinterpret_cast<void*>(data));
|
||||
}
|
||||
|
||||
void set(std::uintptr_t address, std::vector<std::uint8_t>&& bytes)
|
||||
{
|
||||
DWORD oldProtect = 0;
|
||||
|
||||
auto* place = reinterpret_cast<void*>(address);
|
||||
VirtualProtect(place, bytes.size(), PAGE_EXECUTE_READWRITE, &oldProtect);
|
||||
memcpy(place, bytes.data(), bytes.size());
|
||||
VirtualProtect(place, bytes.size(), oldProtect, &oldProtect);
|
||||
FlushInstructionCache(GetCurrentProcess(), place, bytes.size());
|
||||
}
|
||||
|
||||
void set(std::uintptr_t address, void* buffer, size_t size)
|
||||
{
|
||||
DWORD oldProtect = 0;
|
||||
|
||||
auto* place = reinterpret_cast<void*>(address);
|
||||
VirtualProtect(place, size, PAGE_EXECUTE_READWRITE, &oldProtect);
|
||||
memcpy(place, buffer, size);
|
||||
VirtualProtect(place, size, oldProtect, &oldProtect);
|
||||
FlushInstructionCache(GetCurrentProcess(), place, size);
|
||||
}
|
||||
|
||||
void jump(std::uintptr_t address, void* destination)
|
||||
{
|
||||
if (!address) return;
|
||||
|
||||
std::uint8_t* bytes = new std::uint8_t[5];
|
||||
*bytes = 0xE9;
|
||||
*reinterpret_cast<std::uint32_t*>(bytes + 1) = CalculateRelativeJMPAddress(address, destination);
|
||||
|
||||
set(address, bytes, 5);
|
||||
|
||||
delete[] bytes;
|
||||
}
|
||||
}
|
116
src/utils/hook.hpp
Normal file
116
src/utils/hook.hpp
Normal file
@ -0,0 +1,116 @@
|
||||
#pragma once
|
||||
|
||||
#define CalculateRelativeJMPAddress(X, Y) (((std::uintptr_t)Y - (std::uintptr_t)X) - 5)
|
||||
|
||||
namespace utils::hook
|
||||
{
|
||||
class detour
|
||||
{
|
||||
public:
|
||||
detour() = default;
|
||||
detour(void* place, void* target);
|
||||
detour(size_t place, void* target);
|
||||
~detour();
|
||||
|
||||
detour(detour&& other) noexcept
|
||||
{
|
||||
this->operator=(std::move(other));
|
||||
}
|
||||
|
||||
detour& operator= (detour&& other) noexcept
|
||||
{
|
||||
if (this != &other)
|
||||
{
|
||||
this->~detour();
|
||||
|
||||
this->place_ = other.place_;
|
||||
this->original_ = other.original_;
|
||||
|
||||
other.place_ = nullptr;
|
||||
other.original_ = nullptr;
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
detour(const detour&) = delete;
|
||||
detour& operator= (const detour&) = delete;
|
||||
|
||||
void enable() const;
|
||||
void disable() const;
|
||||
|
||||
void create(void* place, void* target);
|
||||
void create(size_t place, void* target);
|
||||
void clear();
|
||||
|
||||
template <typename T>
|
||||
T* get() const
|
||||
{
|
||||
return static_cast<T*>(this->get_original());
|
||||
}
|
||||
|
||||
template <typename T, typename... Args>
|
||||
T invoke(Args... args)
|
||||
{
|
||||
return static_cast<T(*)(Args ...)>(this->get_original())(args...);
|
||||
}
|
||||
|
||||
[[nodiscard]] void* get_original() const;
|
||||
|
||||
private:
|
||||
void* place_{};
|
||||
void* original_{};
|
||||
};
|
||||
|
||||
void nop(void* place, size_t length);
|
||||
void nop(size_t place, size_t length);
|
||||
|
||||
void copy(void* place, const void* data, size_t length);
|
||||
void copy(size_t place, const void* data, size_t length);
|
||||
|
||||
bool is_relatively_far(const void* pointer, const void* data, int offset = 5);
|
||||
|
||||
void call(void* pointer, void* data);
|
||||
void call(size_t pointer, void* data);
|
||||
void call(size_t pointer, size_t data);
|
||||
|
||||
void jump(std::uintptr_t address, void* destination);
|
||||
|
||||
template <typename T>
|
||||
T extract(void* address)
|
||||
{
|
||||
const auto data = static_cast<uint8_t*>(address);
|
||||
const auto offset = *reinterpret_cast<int32_t*>(data);
|
||||
return reinterpret_cast<T>(data + offset + 4);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static void set(void* place, T value)
|
||||
{
|
||||
DWORD old_protect;
|
||||
VirtualProtect(place, sizeof(T), PAGE_EXECUTE_READWRITE, &old_protect);
|
||||
|
||||
*static_cast<T*>(place) = value;
|
||||
|
||||
VirtualProtect(place, sizeof(T), old_protect, &old_protect);
|
||||
FlushInstructionCache(GetCurrentProcess(), place, sizeof(T));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static void set(const size_t place, T value)
|
||||
{
|
||||
return set<T>(reinterpret_cast<void*>(place), value);
|
||||
}
|
||||
|
||||
template <typename T, typename... Args>
|
||||
static T invoke(size_t func, Args... args)
|
||||
{
|
||||
return reinterpret_cast<T(*)(Args ...)>(func)(args...);
|
||||
}
|
||||
|
||||
template <typename T, typename... Args>
|
||||
static T invoke(void* func, Args... args)
|
||||
{
|
||||
return static_cast<T(*)(Args ...)>(func)(args...);
|
||||
}
|
||||
}
|
112
src/utils/io.cpp
Normal file
112
src/utils/io.cpp
Normal file
@ -0,0 +1,112 @@
|
||||
#include <stdinc.hpp>
|
||||
#include <fstream>
|
||||
|
||||
namespace utils::io
|
||||
{
|
||||
bool file_exists(const std::string& file)
|
||||
{
|
||||
return std::ifstream(file).good();
|
||||
}
|
||||
|
||||
bool write_file(const std::string& file, const std::string& data, const bool append)
|
||||
{
|
||||
const auto pos = file.find_last_of("/\\");
|
||||
if (pos != std::string::npos)
|
||||
{
|
||||
create_directory(file.substr(0, pos));
|
||||
}
|
||||
|
||||
std::ofstream stream(
|
||||
file, std::ios::binary | std::ofstream::out | (append ? std::ofstream::app : 0));
|
||||
|
||||
if (stream.is_open())
|
||||
{
|
||||
stream.write(data.data(), data.size());
|
||||
stream.close();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string read_file(const std::string& file)
|
||||
{
|
||||
std::string data;
|
||||
read_file(file, &data);
|
||||
return data;
|
||||
}
|
||||
|
||||
bool read_file(const std::string& file, std::string* data)
|
||||
{
|
||||
if (!data) return false;
|
||||
data->clear();
|
||||
|
||||
if (file_exists(file))
|
||||
{
|
||||
std::ifstream stream(file, std::ios::binary);
|
||||
if (!stream.is_open()) return false;
|
||||
|
||||
stream.seekg(0, std::ios::end);
|
||||
const std::streamsize size = stream.tellg();
|
||||
stream.seekg(0, std::ios::beg);
|
||||
|
||||
if (size > -1)
|
||||
{
|
||||
data->resize(static_cast<uint32_t>(size));
|
||||
stream.read(const_cast<char*>(data->data()), size);
|
||||
stream.close();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t file_size(const std::string& file)
|
||||
{
|
||||
if (file_exists(file))
|
||||
{
|
||||
std::ifstream stream(file, std::ios::binary);
|
||||
|
||||
if (stream.good())
|
||||
{
|
||||
stream.seekg(0, std::ios::end);
|
||||
return static_cast<size_t>(stream.tellg());
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool create_directory(const std::string& directory)
|
||||
{
|
||||
return std::filesystem::create_directories(directory);
|
||||
}
|
||||
|
||||
bool directory_exists(const std::string& directory)
|
||||
{
|
||||
return std::filesystem::is_directory(directory);
|
||||
}
|
||||
|
||||
bool directory_is_empty(const std::string& directory)
|
||||
{
|
||||
return std::filesystem::is_empty(directory);
|
||||
}
|
||||
|
||||
std::vector<std::string> list_files(const std::string& directory)
|
||||
{
|
||||
std::vector<std::string> files;
|
||||
|
||||
for (auto& file : std::filesystem::directory_iterator(directory))
|
||||
{
|
||||
files.push_back(file.path().generic_string());
|
||||
}
|
||||
|
||||
return files;
|
||||
}
|
||||
|
||||
void copy_folder(const std::filesystem::path& src, const std::filesystem::path& target)
|
||||
{
|
||||
std::filesystem::copy(src, target, std::filesystem::copy_options::overwrite_existing | std::filesystem::copy_options::recursive);
|
||||
}
|
||||
}
|
19
src/utils/io.hpp
Normal file
19
src/utils/io.hpp
Normal file
@ -0,0 +1,19 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <filesystem>
|
||||
|
||||
namespace utils::io
|
||||
{
|
||||
bool file_exists(const std::string& file);
|
||||
bool write_file(const std::string& file, const std::string& data, bool append = false);
|
||||
bool read_file(const std::string& file, std::string* data);
|
||||
std::string read_file(const std::string& file);
|
||||
size_t file_size(const std::string& file);
|
||||
bool create_directory(const std::string& directory);
|
||||
bool directory_exists(const std::string& directory);
|
||||
bool directory_is_empty(const std::string& directory);
|
||||
std::vector<std::string> list_files(const std::string& directory);
|
||||
void copy_folder(const std::filesystem::path& src, const std::filesystem::path& target);
|
||||
}
|
112
src/utils/memory.cpp
Normal file
112
src/utils/memory.cpp
Normal file
@ -0,0 +1,112 @@
|
||||
// https://github.com/momo5502/open-iw5
|
||||
|
||||
#include <stdinc.hpp>
|
||||
|
||||
namespace utils
|
||||
{
|
||||
memory::allocator memory::mem_allocator_;
|
||||
|
||||
memory::allocator::~allocator()
|
||||
{
|
||||
this->clear();
|
||||
}
|
||||
|
||||
void memory::allocator::clear()
|
||||
{
|
||||
std::lock_guard _(this->mutex_);
|
||||
|
||||
for (auto& data : this->pool_)
|
||||
{
|
||||
memory::free(data);
|
||||
}
|
||||
|
||||
this->pool_.clear();
|
||||
}
|
||||
|
||||
void memory::allocator::free(void* data)
|
||||
{
|
||||
std::lock_guard _(this->mutex_);
|
||||
|
||||
const auto j = std::find(this->pool_.begin(), this->pool_.end(), data);
|
||||
if (j != this->pool_.end())
|
||||
{
|
||||
memory::free(data);
|
||||
this->pool_.erase(j);
|
||||
}
|
||||
}
|
||||
|
||||
void memory::allocator::free(const void* data)
|
||||
{
|
||||
this->free(const_cast<void*>(data));
|
||||
}
|
||||
|
||||
void* memory::allocator::allocate(const size_t length)
|
||||
{
|
||||
std::lock_guard _(this->mutex_);
|
||||
|
||||
const auto data = memory::allocate(length);
|
||||
this->pool_.push_back(data);
|
||||
return data;
|
||||
}
|
||||
|
||||
bool memory::allocator::empty() const
|
||||
{
|
||||
return this->pool_.empty();
|
||||
}
|
||||
|
||||
/*char* memory::allocator::duplicate_string(const std::string& string)
|
||||
{
|
||||
std::lock_guard _(this->mutex_);
|
||||
|
||||
const auto data = memory::duplicate_string(string);
|
||||
this->pool_.push_back(data);
|
||||
return data;
|
||||
}*/
|
||||
|
||||
void* memory::allocate(const size_t length)
|
||||
{
|
||||
const auto data = calloc(length, 1);
|
||||
assert(data != nullptr);
|
||||
return data;
|
||||
}
|
||||
|
||||
/*char* memory::duplicate_string(const std::string& string)
|
||||
{
|
||||
const auto new_string = allocate_array<char>(string.size() + 1);
|
||||
std::memcpy(new_string, string.data(), string.size());
|
||||
return new_string;
|
||||
}*/
|
||||
|
||||
void memory::free(void* data)
|
||||
{
|
||||
if (data)
|
||||
{
|
||||
::free(data);
|
||||
}
|
||||
}
|
||||
|
||||
void memory::free(const void* data)
|
||||
{
|
||||
free(const_cast<void*>(data));
|
||||
}
|
||||
|
||||
bool memory::is_set(const void* mem, const char chr, const size_t length)
|
||||
{
|
||||
const auto mem_arr = static_cast<const char*>(mem);
|
||||
|
||||
for (size_t i = 0; i < length; ++i)
|
||||
{
|
||||
if (mem_arr[i] != chr)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
memory::allocator* memory::get_allocator()
|
||||
{
|
||||
return &memory::mem_allocator_;
|
||||
}
|
||||
}
|
70
src/utils/memory.hpp
Normal file
70
src/utils/memory.hpp
Normal file
@ -0,0 +1,70 @@
|
||||
// https://github.com/momo5502/open-iw5
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace utils
|
||||
{
|
||||
class memory final
|
||||
{
|
||||
public:
|
||||
class allocator final
|
||||
{
|
||||
public:
|
||||
~allocator();
|
||||
|
||||
void clear();
|
||||
|
||||
void free(void* data);
|
||||
|
||||
void free(const void* data);
|
||||
|
||||
void* allocate(size_t length);
|
||||
|
||||
template <typename T>
|
||||
T* allocate()
|
||||
{
|
||||
return this->allocate_array<T>(1);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T* allocate_array(const size_t count = 1)
|
||||
{
|
||||
return static_cast<T*>(this->allocate(count * sizeof(T)));
|
||||
}
|
||||
|
||||
bool empty() const;
|
||||
|
||||
//char* duplicate_string(const std::string& string);
|
||||
|
||||
private:
|
||||
std::mutex mutex_;
|
||||
std::vector<void*> pool_;
|
||||
};
|
||||
|
||||
static void* allocate(size_t length);
|
||||
|
||||
template <typename T>
|
||||
static inline T* allocate()
|
||||
{
|
||||
return allocate_array<T>(1);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static inline T* allocate_array(const size_t count = 1)
|
||||
{
|
||||
return static_cast<T*>(allocate(count * sizeof(T)));
|
||||
}
|
||||
|
||||
//static char* duplicate_string(const std::string& string);
|
||||
|
||||
static void free(void* data);
|
||||
static void free(const void* data);
|
||||
|
||||
static bool is_set(const void* mem, char chr, size_t length);
|
||||
|
||||
static allocator* get_allocator();
|
||||
|
||||
private:
|
||||
static allocator mem_allocator_;
|
||||
};
|
||||
}
|
151
src/utils/string.cpp
Normal file
151
src/utils/string.cpp
Normal file
@ -0,0 +1,151 @@
|
||||
#include <stdinc.hpp>
|
||||
|
||||
namespace utils::string
|
||||
{
|
||||
const char* va(const char* fmt, ...)
|
||||
{
|
||||
static thread_local va_provider<8, 256> provider;
|
||||
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
|
||||
const char* result = provider.get(fmt, ap);
|
||||
|
||||
va_end(ap);
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<std::string> split(const std::string& s, const char delim)
|
||||
{
|
||||
std::stringstream ss(s);
|
||||
std::string item;
|
||||
std::vector<std::string> elems;
|
||||
|
||||
while (std::getline(ss, item, delim))
|
||||
{
|
||||
elems.push_back(item); // elems.push_back(std::move(item)); // if C++11 (based on comment from @mchiasson)
|
||||
}
|
||||
|
||||
return elems;
|
||||
}
|
||||
|
||||
std::string to_lower(std::string text)
|
||||
{
|
||||
std::transform(text.begin(), text.end(), text.begin(), [](const char input)
|
||||
{
|
||||
return static_cast<char>(tolower(input));
|
||||
});
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
std::string to_upper(std::string text)
|
||||
{
|
||||
std::transform(text.begin(), text.end(), text.begin(), [](const char input)
|
||||
{
|
||||
return static_cast<char>(toupper(input));
|
||||
});
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
bool starts_with(const std::string& text, const std::string& substring)
|
||||
{
|
||||
return text.find(substring) == 0;
|
||||
}
|
||||
|
||||
bool ends_with(const std::string& text, const std::string& substring)
|
||||
{
|
||||
if (substring.size() > text.size()) return false;
|
||||
return std::equal(substring.rbegin(), substring.rend(), text.rbegin());
|
||||
}
|
||||
|
||||
std::string dump_hex(const std::string& data, const std::string& separator)
|
||||
{
|
||||
std::string result;
|
||||
|
||||
for (unsigned int i = 0; i < data.size(); ++i)
|
||||
{
|
||||
if (i > 0)
|
||||
{
|
||||
result.append(separator);
|
||||
}
|
||||
|
||||
result.append(va("%02X", data[i] & 0xFF));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void strip(const char* in, char* out, int max)
|
||||
{
|
||||
if (!in || !out) return;
|
||||
|
||||
max--;
|
||||
auto current = 0;
|
||||
while (*in != 0 && current < max)
|
||||
{
|
||||
const auto color_index = (*(in + 1) - 48) >= 0xC ? 7 : (*(in + 1) - 48);
|
||||
|
||||
if (*in == '^' && (color_index != 7 || *(in + 1) == '7'))
|
||||
{
|
||||
++in;
|
||||
}
|
||||
else
|
||||
{
|
||||
*out = *in;
|
||||
++out;
|
||||
++current;
|
||||
}
|
||||
|
||||
++in;
|
||||
}
|
||||
*out = '\0';
|
||||
}
|
||||
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable: 4100)
|
||||
std::string convert(const std::wstring& wstr)
|
||||
{
|
||||
std::string result;
|
||||
result.reserve(wstr.size());
|
||||
|
||||
for (const auto& chr : wstr)
|
||||
{
|
||||
result.push_back(static_cast<char>(chr));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::wstring convert(const std::string& str)
|
||||
{
|
||||
std::wstring result;
|
||||
result.reserve(str.size());
|
||||
|
||||
for (const auto& chr : str)
|
||||
{
|
||||
result.push_back(static_cast<wchar_t>(chr));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
#pragma warning(pop)
|
||||
|
||||
std::string replace(std::string str, const std::string& from, const std::string& to)
|
||||
{
|
||||
if (from.empty())
|
||||
{
|
||||
return str;
|
||||
}
|
||||
|
||||
size_t start_pos = 0;
|
||||
while ((start_pos = str.find(from, start_pos)) != std::string::npos)
|
||||
{
|
||||
str.replace(start_pos, from.length(), to);
|
||||
start_pos += to.length();
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
}
|
98
src/utils/string.hpp
Normal file
98
src/utils/string.hpp
Normal file
@ -0,0 +1,98 @@
|
||||
#pragma once
|
||||
#include "memory.hpp"
|
||||
#include <cstdint>
|
||||
|
||||
#ifndef ARRAYSIZE
|
||||
template <class Type, size_t n>
|
||||
size_t ARRAYSIZE(Type(&)[n]) { return n; }
|
||||
#endif
|
||||
|
||||
namespace utils::string
|
||||
{
|
||||
template <size_t Buffers, size_t MinBufferSize>
|
||||
class va_provider final
|
||||
{
|
||||
public:
|
||||
static_assert(Buffers != 0 && MinBufferSize != 0, "Buffers and MinBufferSize mustn't be 0");
|
||||
|
||||
va_provider() : current_buffer_(0)
|
||||
{
|
||||
}
|
||||
|
||||
char* get(const char* format, const va_list ap)
|
||||
{
|
||||
++this->current_buffer_ %= ARRAYSIZE(this->string_pool_);
|
||||
auto entry = &this->string_pool_[this->current_buffer_];
|
||||
|
||||
if (!entry->size || !entry->buffer)
|
||||
{
|
||||
throw std::runtime_error("String pool not initialized");
|
||||
}
|
||||
|
||||
while (true)
|
||||
{
|
||||
const int res = vsnprintf_s(entry->buffer, entry->size, _TRUNCATE, format, ap);
|
||||
if (res > 0) break; // Success
|
||||
if (res == 0) return nullptr; // Error
|
||||
|
||||
entry->double_size();
|
||||
}
|
||||
|
||||
return entry->buffer;
|
||||
}
|
||||
|
||||
private:
|
||||
class entry final
|
||||
{
|
||||
public:
|
||||
explicit entry(const size_t _size = MinBufferSize) : size(_size), buffer(nullptr)
|
||||
{
|
||||
if (this->size < MinBufferSize) this->size = MinBufferSize;
|
||||
this->allocate();
|
||||
}
|
||||
|
||||
~entry()
|
||||
{
|
||||
if (this->buffer) memory::get_allocator()->free(this->buffer);
|
||||
this->size = 0;
|
||||
this->buffer = nullptr;
|
||||
}
|
||||
|
||||
void allocate()
|
||||
{
|
||||
if (this->buffer) memory::get_allocator()->free(this->buffer);
|
||||
this->buffer = memory::get_allocator()->allocate_array<char>(this->size + 1);
|
||||
}
|
||||
|
||||
void double_size()
|
||||
{
|
||||
this->size *= 2;
|
||||
this->allocate();
|
||||
}
|
||||
|
||||
size_t size;
|
||||
char* buffer;
|
||||
};
|
||||
|
||||
size_t current_buffer_;
|
||||
entry string_pool_[Buffers];
|
||||
};
|
||||
|
||||
const char* va(const char* fmt, ...);
|
||||
|
||||
std::vector<std::string> split(const std::string& s, char delim);
|
||||
|
||||
std::string to_lower(std::string text);
|
||||
std::string to_upper(std::string text);
|
||||
bool starts_with(const std::string& text, const std::string& substring);
|
||||
bool ends_with(const std::string& text, const std::string& substring);
|
||||
|
||||
std::string dump_hex(const std::string& data, const std::string& separator = " ");
|
||||
|
||||
void strip(const char* in, char* out, int max);
|
||||
|
||||
std::string convert(const std::wstring& wstr);
|
||||
std::wstring convert(const std::string& str);
|
||||
|
||||
std::string replace(std::string str, const std::string& from, const std::string& to);
|
||||
}
|
BIN
tools/windows/premake5.exe
Normal file
BIN
tools/windows/premake5.exe
Normal file
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user