2022-02-27 12:53:44 +00:00
|
|
|
#include <STDInclude.hpp>
|
2017-01-19 22:23:59 +01:00
|
|
|
|
|
|
|
namespace Components
|
|
|
|
{
|
|
|
|
std::string Script::ScriptName;
|
|
|
|
std::vector<int> Script::ScriptHandles;
|
2022-02-23 11:38:37 +00:00
|
|
|
std::unordered_map<std::string, Game::BuiltinFunctionDef> Script::CustomScrFunctions;
|
|
|
|
std::unordered_map<std::string, Game::BuiltinMethodDef> Script::CustomScrMethods;
|
2017-01-19 22:23:59 +01:00
|
|
|
std::vector<std::string> Script::ScriptNameStack;
|
|
|
|
unsigned short Script::FunctionName;
|
2020-12-04 16:42:47 -06:00
|
|
|
std::unordered_map<std::string, std::string> Script::ScriptStorage;
|
2020-12-08 16:14:47 -06:00
|
|
|
std::unordered_map<int, std::string> Script::ScriptBaseProgramNum;
|
2021-11-13 13:15:27 +00:00
|
|
|
std::unordered_map<const char*, const char*> Script::ReplacedFunctions;
|
2022-01-24 03:15:05 +00:00
|
|
|
const char* Script::ReplacedPos = nullptr;
|
2020-12-19 16:50:51 -06:00
|
|
|
int Script::LastFrameTime = -1;
|
2017-01-19 22:23:59 +01:00
|
|
|
|
2017-05-31 15:45:12 +02:00
|
|
|
Utils::Signal<Scheduler::Callback> Script::VMShutdownSignal;
|
2017-05-14 20:14:52 +02:00
|
|
|
|
2017-01-19 22:23:59 +01:00
|
|
|
void Script::FunctionError()
|
|
|
|
{
|
2022-04-12 14:34:51 +02:00
|
|
|
const auto* funcName = Game::SL_ConvertToString(Script::FunctionName);
|
2017-01-19 22:23:59 +01:00
|
|
|
|
|
|
|
Game::Scr_ShutdownAllocNode();
|
|
|
|
|
|
|
|
Logger::Print(23, "\n");
|
|
|
|
Logger::Print(23, "******* script compile error *******\n");
|
2022-04-12 14:34:51 +02:00
|
|
|
Logger::Print(23, "Error: unknown function %s in %s\n", funcName, Script::ScriptName.data());
|
2017-01-19 22:23:59 +01:00
|
|
|
Logger::Print(23, "************************************\n");
|
|
|
|
|
2022-04-12 14:34:51 +02:00
|
|
|
Logger::Error(Game::ERR_SCRIPT_DROP, "script compile error\nunknown function %s\n%s\n\n", funcName, Script::ScriptName.data());
|
2017-01-19 22:23:59 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
__declspec(naked) void Script::StoreFunctionNameStub()
|
|
|
|
{
|
|
|
|
__asm
|
|
|
|
{
|
|
|
|
mov eax, [esp - 8h]
|
|
|
|
mov Script::FunctionName, ax
|
|
|
|
|
|
|
|
sub esp, 0Ch
|
|
|
|
push 0
|
|
|
|
push edi
|
|
|
|
|
|
|
|
mov eax, 612DB6h
|
|
|
|
jmp eax
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-16 17:25:51 +00:00
|
|
|
void Script::RuntimeError(const char* codePos, unsigned int index, const char* msg, const char* dialogMessage)
|
|
|
|
{
|
|
|
|
const auto developer = Dvar::Var("developer").get<int>();
|
|
|
|
|
|
|
|
// Allow error messages to be printed if developer mode is on
|
2022-01-23 19:32:20 +00:00
|
|
|
// Should check scrVarPub.developer but it's absent
|
|
|
|
// in this version of the game so let's check the dvar
|
2022-01-16 17:25:51 +00:00
|
|
|
if (!Game::scrVmPub->terminal_error && !developer)
|
|
|
|
return;
|
|
|
|
|
2022-01-23 19:32:20 +00:00
|
|
|
// If were are developing let's call RuntimeErrorInternal
|
2022-01-18 00:21:25 +00:00
|
|
|
// scrVmPub.debugCode seems to be always false
|
2022-01-16 17:25:51 +00:00
|
|
|
if (Game::scrVmPub->debugCode || Game::scrVarPub->developer_script)
|
|
|
|
{
|
2022-01-23 19:32:20 +00:00
|
|
|
Game::RuntimeErrorInternal(23, codePos, index, msg);
|
2022-01-16 17:25:51 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2022-01-23 19:32:20 +00:00
|
|
|
Logger::Print(23, "%s\n", msg);
|
2022-01-16 17:25:51 +00:00
|
|
|
}
|
|
|
|
|
2022-03-15 22:49:58 +00:00
|
|
|
// Let's not throw error unless we have to
|
2022-04-09 16:29:58 +02:00
|
|
|
if (Game::scrVmPub->terminal_error)
|
|
|
|
{
|
|
|
|
if (dialogMessage == nullptr)
|
|
|
|
dialogMessage = "";
|
2022-01-16 17:25:51 +00:00
|
|
|
|
2022-04-09 16:29:58 +02:00
|
|
|
Logger::Error(Game::ERR_SCRIPT_DROP, "\x15script runtime error\n(see console for details)\n%s\n%s", msg, dialogMessage);
|
|
|
|
}
|
2022-01-16 17:25:51 +00:00
|
|
|
}
|
|
|
|
|
2017-01-19 22:23:59 +01:00
|
|
|
void Script::StoreScriptName(const char* name)
|
|
|
|
{
|
|
|
|
Script::ScriptNameStack.push_back(Script::ScriptName);
|
|
|
|
Script::ScriptName = name;
|
|
|
|
|
|
|
|
if (!Utils::String::EndsWith(Script::ScriptName, ".gsc"))
|
|
|
|
{
|
|
|
|
Script::ScriptName.append(".gsc");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
__declspec(naked) void Script::StoreScriptNameStub()
|
|
|
|
{
|
|
|
|
__asm
|
|
|
|
{
|
2017-02-01 13:44:25 +01:00
|
|
|
pushad
|
|
|
|
|
|
|
|
lea ecx, [esp + 30h]
|
2017-01-19 22:23:59 +01:00
|
|
|
push ecx
|
|
|
|
|
|
|
|
call Script::StoreScriptName
|
|
|
|
add esp, 4h
|
|
|
|
|
2017-02-01 13:44:25 +01:00
|
|
|
popad
|
|
|
|
|
2017-01-19 22:23:59 +01:00
|
|
|
push ebp
|
|
|
|
mov ebp, ds:1CDEAA8h
|
2017-02-01 13:44:25 +01:00
|
|
|
|
|
|
|
push 427DC3h
|
|
|
|
retn
|
2017-01-19 22:23:59 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void Script::RestoreScriptName()
|
|
|
|
{
|
|
|
|
Script::ScriptName = Script::ScriptNameStack.back();
|
|
|
|
Script::ScriptNameStack.pop_back();
|
|
|
|
}
|
|
|
|
|
|
|
|
__declspec(naked) void Script::RestoreScriptNameStub()
|
|
|
|
{
|
|
|
|
__asm
|
|
|
|
{
|
2017-02-01 13:44:25 +01:00
|
|
|
pushad
|
2017-01-19 22:23:59 +01:00
|
|
|
call Script::RestoreScriptName
|
2017-02-01 13:44:25 +01:00
|
|
|
popad
|
2017-01-19 22:23:59 +01:00
|
|
|
|
|
|
|
mov ds:1CDEAA8h, ebp
|
|
|
|
|
2017-02-01 13:44:25 +01:00
|
|
|
push 427E77h
|
|
|
|
retn
|
2017-01-19 22:23:59 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void Script::PrintSourcePos(const char* filename, unsigned int offset)
|
|
|
|
{
|
|
|
|
FileSystem::File script(filename);
|
|
|
|
|
|
|
|
if (script.exists())
|
|
|
|
{
|
|
|
|
std::string buffer = script.getBuffer();
|
|
|
|
Utils::String::Replace(buffer, "\t", " ");
|
|
|
|
|
2022-04-10 14:13:32 +02:00
|
|
|
auto line = 1, lineOffset = 0, inlineOffset = 0;
|
2017-01-19 22:23:59 +01:00
|
|
|
|
2022-04-10 14:13:32 +02:00
|
|
|
for (size_t i = 0; i < buffer.size(); ++i)
|
2017-01-19 22:23:59 +01:00
|
|
|
{
|
|
|
|
// Terminate line
|
|
|
|
if (i == offset)
|
|
|
|
{
|
|
|
|
while (buffer[i] != '\r' && buffer[i] != '\n' && buffer[i] != '\0')
|
|
|
|
{
|
|
|
|
++i;
|
|
|
|
}
|
|
|
|
|
|
|
|
buffer[i] = '\0';
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (buffer[i] == '\n')
|
|
|
|
{
|
|
|
|
++line;
|
2022-04-10 14:13:32 +02:00
|
|
|
lineOffset = static_cast<int>(i); // Includes the line break!
|
2017-01-19 22:23:59 +01:00
|
|
|
inlineOffset = 0;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
++inlineOffset;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Logger::Print(23, "in file %s, line %d:", filename, line);
|
|
|
|
Logger::Print(23, "%s\n", buffer.data() + lineOffset);
|
|
|
|
|
2022-04-10 14:13:32 +02:00
|
|
|
for (auto i = 0; i < (inlineOffset - 1); ++i)
|
2017-01-19 22:23:59 +01:00
|
|
|
{
|
|
|
|
Logger::Print(23, " ");
|
|
|
|
}
|
|
|
|
|
|
|
|
Logger::Print(23, "*\n");
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
Logger::Print(23, "in file %s, offset %d\n", filename, offset);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void Script::CompileError(unsigned int offset, const char* message, ...)
|
|
|
|
{
|
2022-02-01 21:51:17 +01:00
|
|
|
char msgbuf[1024] = {0};
|
|
|
|
va_list va;
|
|
|
|
va_start(va, message);
|
2022-03-02 10:10:03 +00:00
|
|
|
_vsnprintf_s(msgbuf, _TRUNCATE, message, va);
|
2022-02-01 21:51:17 +01:00
|
|
|
va_end(va);
|
2017-01-19 22:23:59 +01:00
|
|
|
|
|
|
|
Game::Scr_ShutdownAllocNode();
|
|
|
|
|
|
|
|
Logger::Print(23, "\n");
|
|
|
|
Logger::Print(23, "******* script compile error *******\n");
|
|
|
|
Logger::Print(23, "Error: %s ", msgbuf);
|
|
|
|
Script::PrintSourcePos(Script::ScriptName.data(), offset);
|
|
|
|
Logger::Print(23, "************************************\n\n");
|
|
|
|
|
2021-11-16 16:56:13 +00:00
|
|
|
Logger::Error(Game::ERR_SCRIPT_DROP, "script compile error\n%s\n%s\n(see console for actual details)\n", msgbuf, Script::ScriptName.data());
|
2017-01-19 22:23:59 +01:00
|
|
|
}
|
|
|
|
|
2018-12-17 14:29:18 +01:00
|
|
|
int Script::LoadScriptAndLabel(const std::string& script, const std::string& label)
|
2017-01-19 22:23:59 +01:00
|
|
|
{
|
|
|
|
Logger::Print("Loading script %s.gsc...\n", script.data());
|
|
|
|
|
|
|
|
if (!Game::Scr_LoadScript(script.data()))
|
|
|
|
{
|
|
|
|
Logger::Print("Script %s encountered an error while loading. (doesn't exist?)", script.data());
|
2022-02-01 21:51:17 +01:00
|
|
|
Logger::Error(Game::ERR_DROP, reinterpret_cast<const char*>(0x70B810), script.data());
|
2017-01-19 22:23:59 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
Logger::Print("Script %s.gsc loaded successfully.\n", script.data());
|
|
|
|
}
|
|
|
|
|
|
|
|
Logger::Print("Finding script handle %s::%s...\n", script.data(), label.data());
|
2022-03-15 22:49:58 +00:00
|
|
|
const auto handle = Game::Scr_GetFunctionHandle(script.data(), label.data());
|
2017-01-19 22:23:59 +01:00
|
|
|
if (handle)
|
|
|
|
{
|
|
|
|
Logger::Print("Script handle %s::%s loaded successfully.\n", script.data(), label.data());
|
|
|
|
return handle;
|
|
|
|
}
|
|
|
|
|
|
|
|
Logger::Print("Script handle %s::%s couldn't be loaded. (file with no entry point?)\n", script.data(), label.data());
|
|
|
|
return handle;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Script::LoadGameType()
|
|
|
|
{
|
2022-03-15 22:49:58 +00:00
|
|
|
for (const auto& handle : Script::ScriptHandles)
|
2017-01-19 22:23:59 +01:00
|
|
|
{
|
|
|
|
Game::Scr_FreeThread(Game::Scr_ExecThread(handle, 0));
|
|
|
|
}
|
|
|
|
|
|
|
|
Game::Scr_LoadGameType();
|
|
|
|
}
|
|
|
|
|
|
|
|
void Script::LoadGameTypeScript()
|
|
|
|
{
|
|
|
|
Script::ScriptHandles.clear();
|
|
|
|
|
2022-03-15 22:49:58 +00:00
|
|
|
const auto list = FileSystem::GetFileList("scripts/", "gsc");
|
2017-01-19 22:23:59 +01:00
|
|
|
|
|
|
|
for (auto file : list)
|
|
|
|
{
|
2022-04-10 14:13:32 +02:00
|
|
|
file.insert(0, "scripts/");
|
2017-01-19 22:23:59 +01:00
|
|
|
|
|
|
|
if (Utils::String::EndsWith(file, ".gsc"))
|
|
|
|
{
|
|
|
|
file = file.substr(0, file.size() - 4);
|
|
|
|
}
|
|
|
|
|
2022-03-15 22:49:58 +00:00
|
|
|
auto handle = Script::LoadScriptAndLabel(file, "init");
|
|
|
|
|
|
|
|
if (handle)
|
|
|
|
{
|
|
|
|
Script::ScriptHandles.push_back(handle);
|
|
|
|
}
|
2017-05-13 12:09:58 +02:00
|
|
|
else
|
2017-01-19 22:23:59 +01:00
|
|
|
{
|
2017-05-13 12:09:58 +02:00
|
|
|
handle = Script::LoadScriptAndLabel(file, "main");
|
|
|
|
if (handle) Script::ScriptHandles.push_back(handle);
|
2017-01-19 22:23:59 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Game::GScr_LoadGameTypeScript();
|
|
|
|
}
|
|
|
|
|
2022-04-12 14:34:51 +02:00
|
|
|
void Script::AddFunction(const char* name, Game::BuiltinFunction func, int type)
|
2017-05-13 12:09:58 +02:00
|
|
|
{
|
2022-02-23 11:38:37 +00:00
|
|
|
Game::BuiltinFunctionDef toAdd;
|
|
|
|
toAdd.actionString = name;
|
|
|
|
toAdd.actionFunc = func;
|
|
|
|
toAdd.type = type;
|
|
|
|
|
2022-04-12 14:34:51 +02:00
|
|
|
CustomScrFunctions.insert_or_assign(Utils::String::ToLower(name), toAdd);
|
2022-02-23 11:38:37 +00:00
|
|
|
}
|
|
|
|
|
2022-04-12 14:34:51 +02:00
|
|
|
void Script::AddMethod(const char* name, Game::BuiltinMethod func, int type)
|
2022-02-23 11:38:37 +00:00
|
|
|
{
|
|
|
|
Game::BuiltinMethodDef toAdd;
|
|
|
|
toAdd.actionString = name;
|
|
|
|
toAdd.actionFunc = func;
|
|
|
|
toAdd.type = type;
|
|
|
|
|
2022-04-12 14:34:51 +02:00
|
|
|
CustomScrMethods.insert_or_assign(Utils::String::ToLower(name), toAdd);
|
2022-02-23 11:38:37 +00:00
|
|
|
}
|
|
|
|
|
2022-04-12 14:34:51 +02:00
|
|
|
Game::BuiltinFunction Script::BuiltIn_GetFunctionStub(const char** pName, int* type)
|
2017-05-14 20:14:52 +02:00
|
|
|
{
|
2022-04-10 14:13:32 +02:00
|
|
|
if (pName != nullptr)
|
2017-05-13 12:09:58 +02:00
|
|
|
{
|
2022-02-25 12:37:01 +00:00
|
|
|
const auto got = Script::CustomScrFunctions.find(Utils::String::ToLower(*pName));
|
2022-02-23 11:38:37 +00:00
|
|
|
|
|
|
|
// If no function was found let's call game's function
|
|
|
|
if (got != Script::CustomScrFunctions.end())
|
2017-05-13 12:09:58 +02:00
|
|
|
{
|
2022-02-23 11:38:37 +00:00
|
|
|
*type = got->second.type;
|
|
|
|
return got->second.actionFunc;
|
|
|
|
}
|
2017-05-13 12:09:58 +02:00
|
|
|
}
|
2022-04-10 14:00:55 +02:00
|
|
|
else
|
|
|
|
{
|
|
|
|
for (const auto& [name, builtin] : Script::CustomScrFunctions)
|
|
|
|
{
|
|
|
|
Game::Scr_RegisterFunction(reinterpret_cast<int>(builtin.actionFunc), name.data());
|
|
|
|
}
|
|
|
|
}
|
2017-05-13 12:09:58 +02:00
|
|
|
|
2022-04-12 14:34:51 +02:00
|
|
|
return Utils::Hook::Call<Game::BuiltinFunction(const char**, int*)>(0x5FA2B0)(pName, type); // BuiltIn_GetFunction
|
2017-05-13 12:09:58 +02:00
|
|
|
}
|
|
|
|
|
2022-04-12 14:34:51 +02:00
|
|
|
Game::BuiltinMethod Script::BuiltIn_GetMethod(const char** pName, int* type)
|
2017-05-13 12:09:58 +02:00
|
|
|
{
|
2022-04-10 14:13:32 +02:00
|
|
|
if (pName != nullptr)
|
2017-05-13 12:09:58 +02:00
|
|
|
{
|
2022-02-25 12:37:01 +00:00
|
|
|
const auto got = Script::CustomScrMethods.find(Utils::String::ToLower(*pName));
|
2017-05-13 12:09:58 +02:00
|
|
|
|
2022-02-23 11:38:37 +00:00
|
|
|
// If no method was found let's call game's function
|
|
|
|
if (got != Script::CustomScrMethods.end())
|
|
|
|
{
|
2022-04-10 14:00:55 +02:00
|
|
|
*type = got->second.type;
|
2022-02-23 11:38:37 +00:00
|
|
|
return got->second.actionFunc;
|
|
|
|
}
|
2020-12-08 16:14:47 -06:00
|
|
|
}
|
2022-04-10 14:00:55 +02:00
|
|
|
else
|
|
|
|
{
|
|
|
|
for (const auto& [name, builtin] : Script::CustomScrMethods)
|
|
|
|
{
|
|
|
|
Game::Scr_RegisterFunction(reinterpret_cast<int>(builtin.actionFunc), name.data());
|
|
|
|
}
|
|
|
|
}
|
2022-02-23 11:38:37 +00:00
|
|
|
|
2022-04-12 14:34:51 +02:00
|
|
|
return Utils::Hook::Call<Game::BuiltinMethod(const char**, int*)>(0x5FA360)(pName, type); // Player_GetMethod
|
2020-12-08 16:14:47 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
void Script::StoreScriptBaseProgramNum()
|
|
|
|
{
|
|
|
|
Script::ScriptBaseProgramNum.insert_or_assign(Utils::Hook::Get<int>(0x1CFEEF8), Script::ScriptName);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Script::Scr_PrintPrevCodePos(int scriptPos)
|
|
|
|
{
|
2022-02-04 15:08:43 +01:00
|
|
|
auto bestCodePos = -1, nextCodePos = -1, offset = -1;
|
2020-12-08 16:14:47 -06:00
|
|
|
std::string file;
|
|
|
|
|
2022-02-01 21:51:17 +01:00
|
|
|
for (const auto& [key, value] : Script::ScriptBaseProgramNum)
|
2020-12-08 16:14:47 -06:00
|
|
|
{
|
2022-03-15 22:49:58 +00:00
|
|
|
const auto codePos = key;
|
2020-12-08 16:14:47 -06:00
|
|
|
|
|
|
|
if (codePos > scriptPos)
|
|
|
|
{
|
|
|
|
if (nextCodePos == -1 || codePos < nextCodePos)
|
|
|
|
nextCodePos = codePos;
|
|
|
|
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (codePos < bestCodePos)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
bestCodePos = codePos;
|
|
|
|
|
2022-02-01 21:51:17 +01:00
|
|
|
file = value;
|
2020-12-08 16:14:47 -06:00
|
|
|
offset = scriptPos - bestCodePos;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (bestCodePos == -1)
|
|
|
|
return;
|
|
|
|
|
|
|
|
Logger::Print(23, "\n@ %d (%d - %d)\n", scriptPos, bestCodePos, nextCodePos);
|
2022-02-14 16:43:26 +00:00
|
|
|
Logger::Print(23, "in %s (%.1f%% through the source)\n\n", file.data(), ((offset * 100.0f) / (nextCodePos - bestCodePos)));
|
2020-12-08 16:14:47 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
__declspec(naked) void Script::Scr_PrintPrevCodePosStub()
|
|
|
|
{
|
|
|
|
__asm
|
|
|
|
{
|
|
|
|
push esi
|
|
|
|
call Script::Scr_PrintPrevCodePos
|
|
|
|
add esp, 4h
|
|
|
|
|
2017-05-14 20:14:52 +02:00
|
|
|
pop esi
|
2017-05-13 12:09:58 +02:00
|
|
|
retn
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-12-08 16:14:47 -06:00
|
|
|
__declspec(naked) void Script::StoreScriptBaseProgramNumStub()
|
|
|
|
{
|
|
|
|
__asm
|
|
|
|
{
|
|
|
|
// execute our hook
|
|
|
|
pushad
|
|
|
|
|
|
|
|
call Script::StoreScriptBaseProgramNum
|
|
|
|
|
|
|
|
popad
|
|
|
|
|
|
|
|
// execute overwritten code caused by the jump hook
|
|
|
|
sub eax, ds:201A460h // gScrVarPub_programBuffer
|
|
|
|
add esp, 0Ch
|
|
|
|
mov ds : 1CFEEF8h, eax // gScrCompilePub_programLen
|
|
|
|
|
|
|
|
// jump back to the original code
|
|
|
|
push 426C3Bh
|
|
|
|
retn
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-07-23 06:36:35 +03:00
|
|
|
void Script::OnVMShutdown(Utils::Slot<Scheduler::Callback> callback)
|
|
|
|
{
|
2020-12-08 16:14:47 -06:00
|
|
|
Script::ScriptBaseProgramNum.clear();
|
2022-03-15 22:49:58 +00:00
|
|
|
Script::VMShutdownSignal.connect(std::move(callback));
|
2020-07-23 06:36:35 +03:00
|
|
|
}
|
|
|
|
|
2022-04-09 16:29:58 +02:00
|
|
|
void Script::ScrShutdownSystemStub(unsigned char sys)
|
2017-05-14 20:14:52 +02:00
|
|
|
{
|
|
|
|
Script::VMShutdownSignal();
|
|
|
|
|
|
|
|
// Scr_ShutdownSystem
|
2022-04-09 16:29:58 +02:00
|
|
|
Utils::Hook::Call<void(unsigned char)>(0x421EE0)(sys);
|
2017-05-14 20:14:52 +02:00
|
|
|
}
|
|
|
|
|
2021-11-13 13:22:06 +00:00
|
|
|
unsigned int Script::SetExpFogStub()
|
2017-04-29 23:08:41 +02:00
|
|
|
{
|
2021-11-13 13:22:06 +00:00
|
|
|
if (Game::Scr_GetNumParam() == 6u)
|
2017-04-29 23:08:41 +02:00
|
|
|
{
|
2021-11-13 18:51:37 +00:00
|
|
|
std::memmove(&Game::scrVmPub->top[-4], &Game::scrVmPub->top[-5], sizeof(Game::VariableValue) * 6);
|
|
|
|
Game::scrVmPub->top += 1;
|
2022-05-04 12:44:45 +01:00
|
|
|
Game::scrVmPub->top[-6].type = Game::scrParamType_t::VAR_FLOAT;
|
2021-11-15 17:44:23 +00:00
|
|
|
Game::scrVmPub->top[-6].u.floatValue = 0.0f;
|
2017-05-07 19:04:57 +02:00
|
|
|
|
2021-11-13 18:51:37 +00:00
|
|
|
++Game::scrVmPub->outparamcount;
|
2017-04-29 23:08:41 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return Game::Scr_GetNumParam();
|
|
|
|
}
|
|
|
|
|
2021-11-13 20:46:58 +00:00
|
|
|
const char* Script::GetCodePosForParam(int index)
|
2021-11-13 13:15:27 +00:00
|
|
|
{
|
2021-11-13 20:55:22 +00:00
|
|
|
if (static_cast<unsigned int>(index) >= Game::scrVmPub->outparamcount)
|
2021-11-13 18:51:37 +00:00
|
|
|
{
|
2022-01-16 13:45:18 +00:00
|
|
|
Game::Scr_ParamError(static_cast<unsigned int>(index), "^1GetCodePosForParam: Index is out of range!\n");
|
2021-11-13 18:51:37 +00:00
|
|
|
return "";
|
|
|
|
}
|
|
|
|
|
2021-11-13 20:46:58 +00:00
|
|
|
const auto value = &Game::scrVmPub->top[-index];
|
2021-11-13 18:51:37 +00:00
|
|
|
|
2022-05-04 12:44:45 +01:00
|
|
|
if (value->type != Game::scrParamType_t::VAR_FUNCTION)
|
2021-11-13 18:51:37 +00:00
|
|
|
{
|
2022-01-16 13:45:18 +00:00
|
|
|
Game::Scr_ParamError(static_cast<unsigned int>(index), "^1GetCodePosForParam: Expects a function as parameter!\n");
|
2021-11-13 18:51:37 +00:00
|
|
|
return "";
|
|
|
|
}
|
|
|
|
|
|
|
|
return value->u.codePosValue;
|
2021-11-13 13:15:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void Script::GetReplacedPos(const char* pos)
|
|
|
|
{
|
2022-05-31 18:38:09 +02:00
|
|
|
if (Script::ReplacedFunctions.contains(pos))
|
2021-11-13 13:15:27 +00:00
|
|
|
{
|
2021-11-13 13:22:06 +00:00
|
|
|
Script::ReplacedPos = Script::ReplacedFunctions[pos];
|
2021-11-13 13:15:27 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void Script::SetReplacedPos(const char* what, const char* with)
|
|
|
|
{
|
2021-11-13 18:51:37 +00:00
|
|
|
if (what[0] == '\0' || with[0] == '\0')
|
|
|
|
{
|
2022-03-15 22:49:58 +00:00
|
|
|
Logger::Print("Warning: Invalid parameters passed to ReplacedFunctions\n");
|
2021-11-13 18:51:37 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2022-05-31 18:38:09 +02:00
|
|
|
if (Script::ReplacedFunctions.contains(what))
|
2021-11-13 13:15:27 +00:00
|
|
|
{
|
2021-11-13 18:51:37 +00:00
|
|
|
Logger::Print("Warning: ReplacedFunctions already contains codePosValue for a function\n");
|
2021-11-13 13:15:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
Script::ReplacedFunctions[what] = with;
|
|
|
|
}
|
|
|
|
|
|
|
|
__declspec(naked) void Script::VMExecuteInternalStub()
|
|
|
|
{
|
|
|
|
__asm
|
|
|
|
{
|
|
|
|
pushad
|
|
|
|
|
|
|
|
push edx
|
|
|
|
call Script::GetReplacedPos
|
|
|
|
|
|
|
|
pop edx
|
|
|
|
popad
|
|
|
|
|
|
|
|
cmp Script::ReplacedPos, 0
|
|
|
|
jne SetPos
|
|
|
|
|
|
|
|
movzx eax, byte ptr [edx]
|
|
|
|
inc edx
|
|
|
|
|
|
|
|
Loc1:
|
|
|
|
cmp eax, 0x8B
|
|
|
|
|
|
|
|
push ecx
|
|
|
|
|
|
|
|
mov ecx, 0x2045094
|
|
|
|
mov [ecx], eax
|
|
|
|
|
|
|
|
mov ecx, 0x2040CD4
|
|
|
|
mov [ecx], edx
|
|
|
|
|
|
|
|
pop ecx
|
|
|
|
|
2021-11-13 18:51:37 +00:00
|
|
|
push 0x61E944
|
2021-11-13 13:15:27 +00:00
|
|
|
retn
|
|
|
|
|
|
|
|
SetPos:
|
|
|
|
mov edx, Script::ReplacedPos
|
|
|
|
mov Script::ReplacedPos, 0
|
|
|
|
|
|
|
|
movzx eax, byte ptr [edx]
|
|
|
|
inc edx
|
|
|
|
|
|
|
|
jmp Loc1
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-02-25 12:17:57 +00:00
|
|
|
Game::client_t* Script::GetClient(const Game::gentity_t* ent)
|
2020-07-23 06:36:35 +03:00
|
|
|
{
|
2022-02-25 12:17:57 +00:00
|
|
|
assert(ent != nullptr);
|
2020-07-23 06:36:35 +03:00
|
|
|
|
2022-02-25 12:17:57 +00:00
|
|
|
if (ent->client == nullptr)
|
2020-07-23 06:36:35 +03:00
|
|
|
{
|
2022-02-25 12:17:57 +00:00
|
|
|
Game::Scr_ObjectError(Utils::String::VA("Entity %i is not a player", ent->s.number));
|
2022-01-07 22:00:44 +01:00
|
|
|
return nullptr;
|
2020-07-23 06:36:35 +03:00
|
|
|
}
|
2022-01-07 22:00:44 +01:00
|
|
|
|
2022-04-12 14:34:51 +02:00
|
|
|
if (ent->s.number >= *Game::svs_clientCount)
|
2022-01-23 19:32:20 +00:00
|
|
|
{
|
2022-02-25 12:17:57 +00:00
|
|
|
Game::Scr_ObjectError(Utils::String::VA("Entity %i is out of bounds", ent->s.number));
|
2022-01-23 19:32:20 +00:00
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
2022-02-25 12:17:57 +00:00
|
|
|
return &Game::svs_clients[ent->s.number];
|
2020-07-23 06:36:35 +03:00
|
|
|
}
|
|
|
|
|
2020-12-04 16:42:47 -06:00
|
|
|
void Script::AddFunctions()
|
|
|
|
{
|
2022-05-04 12:44:45 +01:00
|
|
|
Script::AddFunction("ReplaceFunc", [] // gsc: ReplaceFunc(<function>, <function>)
|
2021-11-13 13:15:27 +00:00
|
|
|
{
|
2021-11-13 13:22:06 +00:00
|
|
|
if (Game::Scr_GetNumParam() != 2u)
|
2021-11-13 13:15:27 +00:00
|
|
|
{
|
|
|
|
Game::Scr_Error("^1ReplaceFunc: Needs two parameters!\n");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const auto what = Script::GetCodePosForParam(0);
|
2021-11-13 18:51:37 +00:00
|
|
|
const auto with = Script::GetCodePosForParam(1);
|
2021-11-13 13:15:27 +00:00
|
|
|
|
|
|
|
Script::SetReplacedPos(what, with);
|
|
|
|
});
|
|
|
|
|
2020-12-04 16:42:47 -06:00
|
|
|
// System time
|
2022-05-04 12:44:45 +01:00
|
|
|
Script::AddFunction("GetSystemMilliseconds", [] // gsc: GetSystemMilliseconds()
|
2020-12-08 16:18:09 -06:00
|
|
|
{
|
|
|
|
SYSTEMTIME time;
|
|
|
|
GetSystemTime(&time);
|
2020-12-04 16:42:47 -06:00
|
|
|
|
2020-12-08 16:18:09 -06:00
|
|
|
Game::Scr_AddInt(time.wMilliseconds);
|
|
|
|
});
|
2020-12-04 16:42:47 -06:00
|
|
|
|
|
|
|
// Executes command to the console
|
2022-05-04 12:44:45 +01:00
|
|
|
Script::AddFunction("Exec", [] // gsc: Exec(<string>)
|
2020-12-08 16:18:09 -06:00
|
|
|
{
|
2022-01-07 22:00:44 +01:00
|
|
|
const auto str = Game::Scr_GetString(0);
|
2022-01-23 19:32:20 +00:00
|
|
|
|
|
|
|
if (str == nullptr)
|
|
|
|
{
|
2022-02-01 21:51:17 +01:00
|
|
|
Game::Scr_ParamError(0, "^1Exec: Illegal parameter!\n");
|
2022-01-23 19:32:20 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2020-12-08 16:18:09 -06:00
|
|
|
Command::Execute(str, false);
|
|
|
|
});
|
2020-12-04 16:42:47 -06:00
|
|
|
|
2022-01-24 03:15:05 +00:00
|
|
|
// Allow printing to the console even when developer is 0
|
2022-05-04 12:44:45 +01:00
|
|
|
Script::AddFunction("PrintConsole", [] // gsc: PrintConsole(<string>)
|
2022-01-24 03:15:05 +00:00
|
|
|
{
|
|
|
|
for (auto i = 0u; i < Game::Scr_GetNumParam(); i++)
|
|
|
|
{
|
|
|
|
const auto str = Game::Scr_GetString(i);
|
|
|
|
|
|
|
|
if (str == nullptr)
|
|
|
|
{
|
2022-02-01 21:51:17 +01:00
|
|
|
Game::Scr_ParamError(i, "^1PrintConsole: Illegal parameter!\n");
|
2022-01-24 03:15:05 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2022-04-15 12:16:12 +02:00
|
|
|
Logger::Print(Game::level->scriptPrintChannel, "%s", str);
|
2022-01-24 03:15:05 +00:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2022-03-15 22:49:58 +00:00
|
|
|
// Script Storage Functions
|
2022-05-04 12:44:45 +01:00
|
|
|
Script::AddFunction("StorageSet", [] // gsc: StorageSet(<str key>, <str data>);
|
2020-12-08 16:18:09 -06:00
|
|
|
{
|
2022-02-01 21:51:17 +01:00
|
|
|
const auto* key = Game::Scr_GetString(0);
|
|
|
|
const auto* value = Game::Scr_GetString(1);
|
2020-12-04 16:42:47 -06:00
|
|
|
|
2022-02-01 21:51:17 +01:00
|
|
|
if (key == nullptr || value == nullptr)
|
|
|
|
{
|
|
|
|
Game::Scr_Error("^1StorageSet: Illegal parameters!\n");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
Script::ScriptStorage.insert_or_assign(key, value);
|
2020-12-08 16:18:09 -06:00
|
|
|
});
|
2020-12-04 16:42:47 -06:00
|
|
|
|
2022-05-04 12:44:45 +01:00
|
|
|
Script::AddFunction("StorageRemove", [] // gsc: StorageRemove(<str key>);
|
2020-12-08 16:18:09 -06:00
|
|
|
{
|
2022-02-01 21:51:17 +01:00
|
|
|
const auto* key = Game::Scr_GetString(0);
|
|
|
|
|
|
|
|
if (key == nullptr)
|
|
|
|
{
|
|
|
|
Game::Scr_Error("^1StorageRemove: Illegal parameter!\n");
|
|
|
|
return;
|
|
|
|
}
|
2020-12-04 16:42:47 -06:00
|
|
|
|
2020-12-09 11:06:26 -06:00
|
|
|
if (!Script::ScriptStorage.count(key))
|
2020-12-08 16:18:09 -06:00
|
|
|
{
|
2022-02-01 21:51:17 +01:00
|
|
|
Game::Scr_Error(Utils::String::VA("^1StorageRemove: Store does not have key '%s'!\n", key));
|
2020-12-08 16:18:09 -06:00
|
|
|
return;
|
|
|
|
}
|
2020-12-04 16:42:47 -06:00
|
|
|
|
2020-12-08 16:18:09 -06:00
|
|
|
Script::ScriptStorage.erase(key);
|
|
|
|
});
|
2020-12-04 16:42:47 -06:00
|
|
|
|
2022-05-04 12:44:45 +01:00
|
|
|
Script::AddFunction("StorageGet", [] // gsc: StorageGet(<str key>);
|
2020-12-08 16:18:09 -06:00
|
|
|
{
|
2022-02-01 21:51:17 +01:00
|
|
|
const auto* key = Game::Scr_GetString(0);
|
|
|
|
|
|
|
|
if (key == nullptr)
|
|
|
|
{
|
|
|
|
Game::Scr_Error("^1StorageGet: Illegal parameter!\n");
|
|
|
|
return;
|
|
|
|
}
|
2020-12-04 16:42:47 -06:00
|
|
|
|
2020-12-09 11:06:26 -06:00
|
|
|
if (!Script::ScriptStorage.count(key))
|
2020-12-08 16:18:09 -06:00
|
|
|
{
|
2022-02-01 21:51:17 +01:00
|
|
|
Game::Scr_Error(Utils::String::VA("^1StorageGet: Store does not have key '%s'!\n", key));
|
2020-12-08 16:18:09 -06:00
|
|
|
return;
|
|
|
|
}
|
2020-12-04 16:42:47 -06:00
|
|
|
|
2022-01-16 13:45:18 +00:00
|
|
|
const auto& data = Script::ScriptStorage.at(key);
|
|
|
|
Game::Scr_AddString(data.data());
|
2020-12-08 16:18:09 -06:00
|
|
|
});
|
2020-12-04 16:42:47 -06:00
|
|
|
|
2022-05-04 12:44:45 +01:00
|
|
|
Script::AddFunction("StorageHas", [] // gsc: StorageHas(<str key>);
|
2020-12-08 16:18:09 -06:00
|
|
|
{
|
2022-02-01 21:51:17 +01:00
|
|
|
const auto* key = Game::Scr_GetString(0);
|
|
|
|
|
|
|
|
if (key == nullptr)
|
|
|
|
{
|
|
|
|
Game::Scr_Error("^1StorageHas: Illegal parameter!\n");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2022-05-31 18:38:09 +02:00
|
|
|
Game::Scr_AddInt(static_cast<int>(Script::ScriptStorage.count(key)));
|
2020-12-08 16:18:09 -06:00
|
|
|
});
|
2020-12-04 16:42:47 -06:00
|
|
|
|
2022-05-04 12:44:45 +01:00
|
|
|
Script::AddFunction("StorageClear", [] // gsc: StorageClear();
|
2020-12-08 16:18:09 -06:00
|
|
|
{
|
|
|
|
Script::ScriptStorage.clear();
|
|
|
|
});
|
2022-01-08 12:52:05 +01:00
|
|
|
|
2022-01-15 13:49:33 +00:00
|
|
|
// PlayerCmd_AreControlsFrozen GSC function from Black Ops 2
|
2022-02-23 11:38:37 +00:00
|
|
|
Script::AddMethod("AreControlsFrozen", [](Game::scr_entref_t entref) // Usage: self AreControlsFrozen();
|
2022-01-14 00:21:14 +00:00
|
|
|
{
|
2022-02-25 12:17:57 +00:00
|
|
|
const auto* ent = Game::GetPlayerEntity(entref);
|
2022-01-14 00:21:14 +00:00
|
|
|
|
2022-01-15 19:18:15 +00:00
|
|
|
Game::Scr_AddBool((ent->client->flags & Game::PLAYER_FLAG_FROZEN) != 0);
|
2022-01-14 00:21:14 +00:00
|
|
|
});
|
2020-12-04 16:42:47 -06:00
|
|
|
}
|
|
|
|
|
2017-01-19 22:23:59 +01:00
|
|
|
Script::Script()
|
|
|
|
{
|
|
|
|
Utils::Hook(0x612DB0, Script::StoreFunctionNameStub, HOOK_JUMP).install()->quick();
|
|
|
|
Utils::Hook(0x427E71, Script::RestoreScriptNameStub, HOOK_JUMP).install()->quick();
|
|
|
|
Utils::Hook(0x427DBC, Script::StoreScriptNameStub, HOOK_JUMP).install()->quick();
|
2020-12-08 16:14:47 -06:00
|
|
|
Utils::Hook(0x426C2D, Script::StoreScriptBaseProgramNumStub, HOOK_JUMP).install()->quick();
|
|
|
|
Utils::Hook(0x42281B, Script::Scr_PrintPrevCodePosStub, HOOK_JUMP).install()->quick();
|
|
|
|
|
2022-01-16 17:25:51 +00:00
|
|
|
Utils::Hook(0x61E3AD, Script::RuntimeError, HOOK_CALL).install()->quick();
|
|
|
|
Utils::Hook(0x621976, Script::RuntimeError, HOOK_CALL).install()->quick();
|
|
|
|
Utils::Hook(0x62246E, Script::RuntimeError, HOOK_CALL).install()->quick();
|
2022-02-09 23:40:27 +00:00
|
|
|
// Skip check in GScr_CheckAllowedToSetPersistentData to prevent log spam in RuntimeError.
|
|
|
|
// On IW5 the function is entirely nullsubbed
|
|
|
|
Utils::Hook::Set<BYTE>(0x5F8DBF, 0xEB);
|
2017-01-19 22:23:59 +01:00
|
|
|
|
|
|
|
Utils::Hook(0x612E8D, Script::FunctionError, HOOK_CALL).install()->quick();
|
|
|
|
Utils::Hook(0x612EA2, Script::FunctionError, HOOK_CALL).install()->quick();
|
|
|
|
Utils::Hook(0x434260, Script::CompileError, HOOK_JUMP).install()->quick();
|
|
|
|
|
|
|
|
Utils::Hook(0x48EFFE, Script::LoadGameType, HOOK_CALL).install()->quick();
|
|
|
|
Utils::Hook(0x45D44A, Script::LoadGameTypeScript, HOOK_CALL).install()->quick();
|
2017-04-29 23:08:41 +02:00
|
|
|
|
2022-02-23 11:38:37 +00:00
|
|
|
// Fetch custom functions
|
|
|
|
Utils::Hook(0x44E72E, Script::BuiltIn_GetFunctionStub, HOOK_CALL).install()->quick(); // Scr_GetFunction
|
2022-04-10 14:00:55 +02:00
|
|
|
Utils::Hook(0x4EC8DD, Script::BuiltIn_GetMethod, HOOK_CALL).install()->quick(); // Scr_GetMethod
|
2017-05-13 12:09:58 +02:00
|
|
|
|
2017-04-29 23:08:41 +02:00
|
|
|
Utils::Hook(0x5F41A3, Script::SetExpFogStub, HOOK_CALL).install()->quick();
|
2017-05-14 20:14:52 +02:00
|
|
|
|
2021-11-13 13:15:27 +00:00
|
|
|
Utils::Hook(0x61E92E, Script::VMExecuteInternalStub, HOOK_JUMP).install()->quick();
|
|
|
|
Utils::Hook::Nop(0x61E933, 1);
|
|
|
|
|
2022-04-09 16:29:58 +02:00
|
|
|
Utils::Hook(0x47548B, Script::ScrShutdownSystemStub, HOOK_CALL).install()->quick(); // G_LoadGame
|
|
|
|
Utils::Hook(0x4D06BA, Script::ScrShutdownSystemStub, HOOK_CALL).install()->quick(); // G_ShutdownGame
|
2017-05-14 20:14:52 +02:00
|
|
|
|
2020-12-19 16:50:51 -06:00
|
|
|
Scheduler::OnFrame([]()
|
|
|
|
{
|
|
|
|
if (!Game::SV_Loaded())
|
|
|
|
return;
|
|
|
|
|
2022-03-15 22:49:58 +00:00
|
|
|
const auto nowMs = Game::Sys_Milliseconds();
|
2020-12-19 16:50:51 -06:00
|
|
|
|
|
|
|
if (Script::LastFrameTime != -1)
|
|
|
|
{
|
2022-03-15 22:49:58 +00:00
|
|
|
const auto timeScale = Dvar::Var("timescale").get<float>();
|
|
|
|
const auto timeTaken = static_cast<int>((nowMs - Script::LastFrameTime) * timeScale);
|
2020-12-19 16:50:51 -06:00
|
|
|
|
|
|
|
if (timeTaken >= 500)
|
|
|
|
Logger::Print(23, "Hitch warning: %i msec frame time\n", timeTaken);
|
|
|
|
}
|
|
|
|
|
|
|
|
Script::LastFrameTime = nowMs;
|
|
|
|
});
|
|
|
|
|
2022-04-09 16:29:58 +02:00
|
|
|
#ifdef _DEBUG
|
2022-05-04 12:44:45 +01:00
|
|
|
Script::AddFunction("DebugBox", []
|
2020-12-08 16:18:09 -06:00
|
|
|
{
|
2022-02-22 17:16:12 +00:00
|
|
|
const auto* message = Game::Scr_GetString(0);
|
|
|
|
|
|
|
|
if (message == nullptr)
|
|
|
|
{
|
|
|
|
Game::Scr_Error("^1DebugBox: Illegal parameter!\n");
|
|
|
|
}
|
|
|
|
|
|
|
|
MessageBoxA(nullptr, message, "DEBUG", MB_OK);
|
2022-02-25 11:58:11 +00:00
|
|
|
}, 1);
|
2022-04-09 16:29:58 +02:00
|
|
|
#endif
|
2018-07-17 14:30:29 +02:00
|
|
|
|
2020-12-04 16:42:47 -06:00
|
|
|
Script::AddFunctions();
|
|
|
|
|
2021-11-13 18:51:37 +00:00
|
|
|
Script::OnVMShutdown([]
|
|
|
|
{
|
|
|
|
Script::ReplacedFunctions.clear();
|
|
|
|
});
|
2017-01-19 22:23:59 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
Script::~Script()
|
|
|
|
{
|
2017-05-14 20:14:52 +02:00
|
|
|
Script::VMShutdownSignal.clear();
|
2017-01-19 22:23:59 +01:00
|
|
|
}
|
|
|
|
}
|