Initial commit

This commit is contained in:
Federico Cecchetto 2021-05-29 22:07:08 +02:00
parent daf26438b0
commit dc150bb940
55 changed files with 11496 additions and 1 deletions

63
.gitattributes vendored
View File

@ -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
View 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
View 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
View 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

@ -0,0 +1 @@
Subproject commit c1cbb41b428f15e53454682a45f03ea31f1da0a7

1
deps/minhook vendored Submodule

@ -0,0 +1 @@
Subproject commit 423d1e45af2ed2719a5c31e990e935ef301ed9c3

19
deps/premake/gsl.lua vendored Normal file
View 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
View 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
View File

@ -0,0 +1,2 @@
@echo off
tools\windows\premake5.exe vs2019

90
premake5.lua Normal file
View 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
View 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)

View 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)

View File

@ -0,0 +1,5 @@
#pragma once
namespace notifies
{
}

View 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)

View 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
View 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)

View 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
View 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
View File

@ -0,0 +1,6 @@
#include <stdinc.hpp>
namespace game
{
}

34
src/game/game.hpp Normal file
View 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"

View 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);
}
}

View 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>();
}
}

View 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;
};
}

View 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 {};
}
}

View 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);
}

File diff suppressed because it is too large Load Diff

View 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);
}
}

View 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);
}

View 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)

View 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);
}

View 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();
}
}

View 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_{};
};
}

View 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_;
}
}

View 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_;
};
}

View 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;
}
}
}

View 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};
};
}

View 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;
}
}

View 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
View 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
View 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};
}
}

View 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;
}
};

View 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;
}

View 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
View File

@ -0,0 +1 @@
#include <stdinc.hpp>

41
src/stdinc.hpp Normal file
View 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"

View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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

Binary file not shown.