[Threading]: Initialize tls stuff, making va thread safe (#657)

This commit is contained in:
Edo 2022-12-18 22:47:59 +01:00 committed by GitHub
parent 24ccd9af3b
commit d8166a4166
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 230 additions and 129 deletions

View File

@ -246,13 +246,20 @@ namespace Components
if (ent->client->sess.sessionState != Game::SESS_STATE_PLAYING || !CheatsOk(ent))
return;
Scheduler::Once([ent]
{
auto** bgs = Game::Sys::GetTls<Game::bgs_t*>(Game::Sys::ThreadOffset::LEVEL_BGS);
assert(*bgs == nullptr);
*bgs = Game::level_bgs;
ent->flags &= ~(Game::FL_GODMODE | Game::FL_DEMI_GODMODE);
ent->health = 0;
ent->client->ps.stats[0] = 0;
Game::player_die(ent, ent, ent, 100000, Game::MOD_SUICIDE, 0, nullptr, Game::HITLOC_NONE, 0);
}, Scheduler::Pipeline::SERVER);
assert(*bgs == Game::level_bgs);
*bgs = nullptr;
});
}

View File

@ -7,39 +7,42 @@ namespace Components
std::thread Discovery::Thread;
std::string Discovery::Challenge;
Dvar::Var Discovery::NetDiscoveryPortRangeMin;
Dvar::Var Discovery::NetDiscoveryPortRangeMax;
void Discovery::Perform()
{
Discovery::IsPerforming = true;
IsPerforming = true;
}
Discovery::Discovery()
{
Dvar::Register<int>("net_discoveryPortRangeMin", 25000, 0, 65535, Game::DVAR_ARCHIVE, "Minimum scan range port for local server discovery");
Dvar::Register<int>("net_discoveryPortRangeMax", 35000, 1, 65536, Game::DVAR_ARCHIVE, "Maximum scan range port for local server discovery");
NetDiscoveryPortRangeMin = Dvar::Register<int>("net_discoveryPortRangeMin", 25000, 0, 65535, Game::DVAR_NONE, "Minimum scan range port for local server discovery");
NetDiscoveryPortRangeMax = Dvar::Register<int>("net_discoveryPortRangeMax", 35000, 1, 65536, Game::DVAR_NONE, "Maximum scan range port for local server discovery");
// An additional thread prevents lags
// Not sure if that's the best way though
Discovery::IsPerforming = false;
Discovery::IsTerminating = false;
Discovery::Thread = std::thread([]()
IsPerforming = false;
IsTerminating = false;
Thread = std::thread([]
{
while (!Discovery::IsTerminating)
while (!IsTerminating)
{
if (Discovery::IsPerforming)
if (IsPerforming)
{
int start = Game::Sys_Milliseconds();
const auto start = Game::Sys_Milliseconds();
Logger::Print("Starting local server discovery...\n");
Discovery::Challenge = Utils::Cryptography::Rand::GenerateChallenge();
Challenge = Utils::Cryptography::Rand::GenerateChallenge();
unsigned int minPort = Dvar::Var("net_discoveryPortRangeMin").get<unsigned int>();
unsigned int maxPort = Dvar::Var("net_discoveryPortRangeMax").get<unsigned int>();
Network::BroadcastRange(minPort, maxPort, Utils::String::VA("discovery %s", Discovery::Challenge.data()));
const auto minPort = NetDiscoveryPortRangeMin.get<unsigned int>();
const auto maxPort = NetDiscoveryPortRangeMax.get<unsigned int>();
Network::BroadcastRange(minPort, maxPort, std::format("discovery {}", Challenge));
Logger::Print("Discovery sent within {}ms, awaiting responses...\n", Game::Sys_Milliseconds() - start);
Discovery::IsPerforming = false;
IsPerforming = false;
}
std::this_thread::sleep_for(50ms);
@ -70,7 +73,7 @@ namespace Components
return;
}
if (Utils::ParseChallenge(data) != Discovery::Challenge)
if (Utils::ParseChallenge(data) != Challenge)
{
Logger::Print("Received discovery with invalid challenge from: {}\n", address.getString());
return;
@ -87,12 +90,12 @@ namespace Components
void Discovery::preDestroy()
{
Discovery::IsPerforming = false;
Discovery::IsTerminating = true;
IsPerforming = false;
IsTerminating = true;
if (Discovery::Thread.joinable())
if (Thread.joinable())
{
Discovery::Thread.join();
Thread.join();
}
}
}

View File

@ -16,5 +16,8 @@ namespace Components
static bool IsPerforming;
static std::thread Thread;
static std::string Challenge;
static Dvar::Var NetDiscoveryPortRangeMin;
static Dvar::Var NetDiscoveryPortRangeMax;
};
}

View File

@ -232,11 +232,11 @@ namespace Components
{
if (!download) download = &CLDownload;
auto host = "http://" + download->target.getString();
const auto host = "http://" + download->target.getString();
auto listUrl = host + (download->isMap ? "/map" : "/list") + (download->isPrivate ? ("?password=" + download->hashedPassword) : "");
const auto listUrl = host + (download->isMap ? "/map" : "/list") + (download->isPrivate ? ("?password=" + download->hashedPassword) : "");
auto list = Utils::WebIO("IW4x", listUrl).setTimeout(5000)->get();
const auto list = Utils::WebIO("IW4x", listUrl).setTimeout(5000)->get();
if (list.empty())
{
if (download->terminateThread) return;
@ -276,7 +276,7 @@ namespace Components
static std::string mod;
mod = download->mod;
for (unsigned int i = 0; i < download->files.size(); ++i)
for (std::size_t i = 0; i < download->files.size(); ++i)
{
if (download->terminateThread) return;
@ -319,7 +319,6 @@ namespace Components
Scheduler::Once([]
{
Game::Dvar_SetString(*Game::fs_gameDirVar, mod.data());
const_cast<Game::dvar_t*>(*Game::fs_gameDirVar)->modified = true;
mod.clear();
@ -673,6 +672,10 @@ namespace Components
Download::Download()
{
AssertSize(Game::va_info_t, 0x804);
AssertSize(jmp_buf, 0x40);
AssertSize(Game::TraceThreadInfo, 0x8);
if (Dedicated::IsEnabled())
{
mg_mgr_init(&Mgr);
@ -690,6 +693,8 @@ namespace Components
Terminate = false;
ServerThread = Utils::Thread::CreateNamedThread("Mongoose", []
{
Com_InitThreadData();
while (!Terminate)
{
mg_mgr_poll(&Mgr, 100);

View File

@ -13,12 +13,12 @@ namespace Components
{
bool result = true;
if (News::Thread.joinable())
if (Thread.joinable())
{
Logger::Debug("Awaiting thread termination...");
News::Thread.join();
Thread.join();
if (!strcmp(Localization::Get("MPUI_MOTD_TEXT"), NEWS_MOTD_DEFAULT))
if (!std::strcmp(Localization::Get("MPUI_MOTD_TEXT"), NEWS_MOTD_DEFAULT))
{
Logger::Print("Failed to fetch motd!\n");
result = false;
@ -43,14 +43,12 @@ namespace Components
Dvar::Register<bool>("g_firstLaunch", true, Game::DVAR_ARCHIVE, "");
Dvar::Register<int>("cl_updateoldversion", REVISION, REVISION, REVISION, Game::DVAR_INIT, "Current version number.");
UIScript::Add("checkFirstLaunch", []([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info)
{
if (Dvar::Var("g_firstLaunch").get<bool>())
{
Command::Execute("openmenu menu_first_launch", false);
//Dvar::Var("g_firstLaunch").set(false);
//Dvar::Var("g_firstLaunch").set(false); // The menus should set it
}
});
@ -67,15 +65,15 @@ namespace Components
// hook for getting the news ticker string
Utils::Hook::Nop(0x6388BB, 2); // skip the "if (item->text[0] == '@')" localize check
Utils::Hook(0x6388C1, News::GetNewsText, HOOK_CALL).install()->quick();
Utils::Hook(0x6388C1, GetNewsText, HOOK_CALL).install()->quick();
if (!Utils::IsWineEnvironment() && !Loader::IsPerformingUnitTests())
{
News::Terminate = false;
News::Thread = std::thread([]()
Terminate = false;
Thread = std::thread([]()
{
Changelog::LoadChangelog();
if (News::Terminate) return;
if (Terminate) return;
std::string data = Utils::Cache::GetFile("/iw4/motd.txt");
if (!data.empty())
@ -83,12 +81,12 @@ namespace Components
Localization::Set("MPUI_MOTD_TEXT", data);
}
if (!Loader::IsPerformingUnitTests() && !News::Terminate)
if (!Loader::IsPerformingUnitTests() && !Terminate)
{
while (!News::Terminate)
while (!Terminate)
{
// Sleep for 3 minutes
for (int i = 0; i < 180 && !News::Terminate; ++i)
for (int i = 0; i < 180 && !Terminate; ++i)
{
std::this_thread::sleep_for(1s);
}
@ -100,13 +98,11 @@ namespace Components
void News::preDestroy()
{
News::Terminate = true;
Terminate = true;
if (News::Thread.joinable())
if (Thread.joinable())
{
News::Thread.join();
Thread.join();
}
News::Thread = std::thread();
}
}

View File

@ -154,9 +154,6 @@ namespace Components
// dvar setting function, unknown stuff related to server thread sync
Utils::Hook::Set<std::uint8_t>(0x647781, 0xEB);
// make VA thread safe
Utils::Hook(0x4785B0, Utils::String::VA, HOOK_JUMP).install()->quick();
Utils::Hook(0x627695, 0x627040, HOOK_CALL).install()->quick();
Utils::Hook(0x43D1C7, PacketEventStub, HOOK_JUMP).install()->quick();
Utils::Hook(0x6272E3, FrameEpilogueStub, HOOK_JUMP).install()->quick();

View File

@ -11,7 +11,8 @@ namespace Components
bool ZoneBuilder::MainThreadInterrupted;
DWORD ZoneBuilder::InterruptingThreadId;
volatile bool ZoneBuilder::Terminate = false;
volatile bool ZoneBuilder::CommandThreadTerminate = false;
std::thread ZoneBuilder::CommandThread;
Dvar::Var ZoneBuilder::PreferDiskAssetsDvar;
@ -120,7 +121,7 @@ namespace Components
this->writeZone();
}
void ZoneBuilder::Zone::loadFastFiles()
void ZoneBuilder::Zone::loadFastFiles() const
{
Logger::Print("Loading required FastFiles...\n");
@ -793,7 +794,8 @@ namespace Components
return GetCurrentThreadId() == Utils::Hook::Get<DWORD>(0x1CDE7FC);
}
static Game::XZoneInfo baseZones_old[] = {
static Game::XZoneInfo baseZones_old[] =
{
{ "code_pre_gfx_mp", Game::DB_ZONE_CODE, 0 },
{ "localized_code_pre_gfx_mp", Game::DB_ZONE_CODE_LOC, 0 },
{ "code_post_gfx_mp", Game::DB_ZONE_CODE, 0 },
@ -805,7 +807,8 @@ namespace Components
};
static Game::XZoneInfo baseZones[] = {
static Game::XZoneInfo baseZones[] =
{
{ "defaults", Game::DB_ZONE_CODE, 0 },
{ "techsets", Game::DB_ZONE_CODE, 0 },
{ "common_mp", Game::DB_ZONE_COMMON, 0 },
@ -819,7 +822,20 @@ namespace Components
ExitProcess(0);
}
int APIENTRY ZoneBuilder::EntryPoint(HINSTANCE /*hInstance*/, HINSTANCE /*hPrevInstance*/, LPSTR /*lpCmdLine*/, int /*nShowCmd*/)
void ZoneBuilder::CommandThreadCallback()
{
Com_InitThreadData();
while (!ZoneBuilder::CommandThreadTerminate)
{
ZoneBuilder::AssumeMainThreadRole();
Utils::Hook::Call<void(int, int)>(0x4E2C80)(0, 0); // Cbuf_Execute
ZoneBuilder::ResetThreadRole();
std::this_thread::sleep_for(1ms);
}
}
BOOL APIENTRY ZoneBuilder::EntryPoint(HINSTANCE /*hInstance*/, HINSTANCE /*hPrevInstance*/, LPSTR /*lpCmdLine*/, int /*nShowCmd*/)
{
Utils::Hook::Call<void()>(0x42F0A0)(); // Com_InitCriticalSections
Utils::Hook::Call<void()>(0x4301B0)(); // Com_InitMainThread
@ -835,11 +851,14 @@ namespace Components
Utils::Hook::Call<void()>(0x4A62A0)(); // LargeLocalInit
Utils::Hook::Call<void()>(0x4DCC10)(); // Sys_InitCmdEvents
Utils::Hook::Call<void()>(0x64A020)(); // PMem_Init
if (!Flags::HasFlag("stdout"))
{
Console::ShowAsyncConsole();
Utils::Hook::Call<void()>(0x43D140)(); // Com_EventLoop
}
Utils::Hook::Call<void(unsigned int)>(0x502580)(static_cast<unsigned int>(__rdtsc())); // Netchan_Init
Utils::Hook::Call<void()>(0x429080)(); // FS_InitFileSystem
Utils::Hook::Call<void()>(0x4BFBE0)(); // Con_InitChannels
@ -848,21 +867,10 @@ namespace Components
Game::NET_Init();
Utils::Hook::Call<void()>(0x4F5090)(); // SND_InitDriver
Utils::Hook::Call<void()>(0x46A630)(); // SND_Init
//Utils::Hook::Call<void()>(0x4D3660)(); // SV_Init
//Utils::Hook::Call<void()>(0x4121E0)(); // SV_InitServerThread
//Utils::Hook::Call<void()>(0x464A90)(); // Com_ParseCommandLine
Utils::Hook::Call<void()>(0x43D140)(); // Com_EventLoop
ZoneBuilder::CommandThread = std::thread([]
{
while (!ZoneBuilder::Terminate)
{
ZoneBuilder::AssumeMainThreadRole();
Utils::Hook::Call<void(int, int)>(0x4E2C80)(0, 0); // Cbuf_Execute
ZoneBuilder::ResetThreadRole();
std::this_thread::sleep_for(1ms);
}
});
ZoneBuilder::CommandThread = Utils::Thread::CreateNamedThread("Command Thread", ZoneBuilder::CommandThreadCallback);
ZoneBuilder::CommandThread.detach();
Command::Add("quit", ZoneBuilder::Com_Quitf_t);
@ -873,8 +881,7 @@ namespace Components
}
else
{
Logger::Warning(Game::CON_CHANNEL_DONT_FILTER,
"Missing new init zones (defaults.ff & techsets.ff). You will need to load fastfiles to manually obtain techsets.\n");
Logger::Warning(Game::CON_CHANNEL_DONT_FILTER, "Missing new init zones (defaults.ff & techsets.ff). You will need to load fastfiles to manually obtain techsets.\n");
Game::DB_LoadXAssets(baseZones_old, ARRAYSIZE(baseZones_old), 0);
}
@ -908,7 +915,7 @@ namespace Components
}
Logger::Print(" --------------------------------------------------------------------------------\n");
Logger::Print(" IW4x ZoneBuilder (" VERSION ")\n");
Logger::Print(" IW4x ZoneBuilder ({})\n", VERSION);
Logger::Print(" Commands:\n");
Logger::Print("\t-buildzone [zone]: builds a zone from a csv located in zone_source\n");
Logger::Print("\t-buildall: builds all zones in zone_source\n");
@ -1006,8 +1013,8 @@ namespace Components
{
assert(data);
auto* sound = Utils::Hook::Get<Game::MssSound*>(0x112AE04);
auto length = sound->info.data_len;
auto allocatedSpace = Game::Z_Malloc(length);
const auto length = sound->info.data_len;
const auto allocatedSpace = Game::Z_Malloc(static_cast<int>(length));
memcpy_s(allocatedSpace, length, data, length);
data = allocatedSpace;
@ -1022,8 +1029,6 @@ namespace Components
AssertSize(Game::XFile, 40);
static_assert(Game::MAX_XFILE_COUNT == 8, "XFile block enum is invalid!");
ZoneBuilder::EndAssetTrace();
if (ZoneBuilder::IsEnabled())
{
// Prevent loading textures (preserves loaddef)
@ -1035,10 +1040,10 @@ namespace Components
// Release the loaddef
Game::DB_ReleaseXAssetHandlers[Game::XAssetType::ASSET_TYPE_IMAGE] = ZoneBuilder::ReleaseTexture;
//r_loadForrenderer = 0
// r_loadForrenderer = 0
Utils::Hook::Set<BYTE>(0x519DDF, 0);
//r_delayloadimage retn
// r_delayloadimage ret
Utils::Hook::Set<BYTE>(0x51F450, 0xC3);
// r_registerDvars hack
@ -1056,7 +1061,7 @@ namespace Components
// Don't mark assets
//Utils::Hook::Nop(0x5BB632, 5);
// Don't load sounds
// Load sounds
Utils::Hook(0x492EFC, ReallocateLoadedSounds, HOOK_CALL).install()->quick();
// Don't display errors when assets are missing (we might manually build those)
@ -1092,14 +1097,14 @@ namespace Components
Utils::Hook::Set<DWORD>(0x64A029, 0x38400000); // 900 MiB
Utils::Hook::Set<DWORD>(0x64A057, 0x38400000);
// change fs_game domain func
// change FS_GameDirDomainFunc
Utils::Hook::Set<int(*)(Game::dvar_t*, Game::DvarValue)>(0x643203, [](Game::dvar_t* dvar, Game::DvarValue value)
{
int result = Utils::Hook::Call<int(Game::dvar_t*, Game::DvarValue)>(0x642FC0)(dvar, value);
if (result)
{
if (std::string(value.string) != dvar->current.string)
if (std::strcmp(value.string, dvar->current.string) != 0)
{
dvar->current.string = value.string;
Game::FS_Restart(0, 0);
@ -1118,7 +1123,7 @@ namespace Components
// handle Com_error Calls
Utils::Hook(Game::Com_Error, ZoneBuilder::HandleError, HOOK_JUMP).install()->quick();
// thread fuckery hooks
// Sys_IsMainThread hook
Utils::Hook(0x4C37D0, ZoneBuilder::IsThreadMainThreadHook, HOOK_JUMP).install()->quick();
// Don't exec startup config in fs_restart
@ -1134,31 +1139,16 @@ namespace Components
{
if (!ZoneBuilder::TraceZone.empty() && ZoneBuilder::TraceZone == FastFiles::Current())
{
ZoneBuilder::TraceAssets.push_back({ type, name });
OutputDebugStringA((name + "\n").data());
ZoneBuilder::TraceAssets.emplace_back(std::make_pair(type, name));
#ifdef _DEBUG
OutputDebugStringA(Utils::String::Format("%s\n", name));
#endif
}
});
Command::Add("verifyzone", [](Command::Params* params)
{
if (params->size() < 2) return;
/*
Utils::Hook(0x4AE9C2, []
{
Game::WeaponCompleteDef** varPtr = (Game::WeaponCompleteDef**)0x112A9F4;
Game::WeaponCompleteDef* var = *varPtr;
OutputDebugStringA("");
Utils::Hook::Call<void()>(0x4D1D60)(); // DB_PopStreamPos
}, HOOK_JUMP).install()->quick();
Utils::Hook(0x4AE9B4, []
{
Game::WeaponCompleteDef** varPtr = (Game::WeaponCompleteDef**)0x112A9F4;
Game::WeaponCompleteDef* var = *varPtr;
OutputDebugStringA("");
Utils::Hook::Call<void()>(0x4D1D60)(); // DB_PopStreamPos
}, HOOK_JUMP).install()->quick();
*/
std::string zone = params->get(1);
@ -1601,7 +1591,7 @@ namespace Components
ZoneBuilder::~ZoneBuilder()
{
ZoneBuilder::Terminate = true;
ZoneBuilder::CommandThreadTerminate = true;
if (ZoneBuilder::CommandThread.joinable())
{
ZoneBuilder::CommandThread.join();

View File

@ -74,7 +74,7 @@ namespace Components
bool isPrimaryAsset() { return this->assetDepth <= 1; }
private:
void loadFastFiles();
void loadFastFiles() const;
bool loadAssets();
bool loadAssetByName(const std::string& type, std::string name, bool isSubAsset = true);
@ -140,7 +140,7 @@ namespace Components
static std::string FindMaterialByTechnique(const std::string& name);
static void ReallocateLoadedSounds(void*& data, void* a2);
static int __stdcall EntryPoint(HINSTANCE /*hInstance*/, HINSTANCE /*hPrevInstance*/, LPSTR /*lpCmdLine*/, int /*nShowCmd*/);
static BOOL APIENTRY EntryPoint(HINSTANCE /*hInstance*/, HINSTANCE /*hPrevInstance*/, LPSTR /*lpCmdLine*/, int /*nShowCmd*/);
static void HandleError(Game::errorParm_t code, const char* fmt, ...);
static void SoftErrorAssetOverflow();
@ -151,10 +151,12 @@ namespace Components
static void Com_Quitf_t();
static void CommandThreadCallback();
static bool MainThreadInterrupted;
static DWORD InterruptingThreadId;
static volatile bool Terminate;
static volatile bool CommandThreadTerminate;
static std::thread CommandThread;
};
}

View File

@ -76,3 +76,19 @@ namespace Game
extern const char* Com_LoadInfoString_FastFile(const char* fileName, const char* fileDesc, const char* ident, char* loadBuffer);
}
#define Com_InitThreadData() \
{ \
static Game::ProfileStack profile_stack{}; \
static Game::va_info_t va_info{}; \
static jmp_buf g_com_error{}; \
static Game::TraceThreadInfo g_trace_thread_info{}; \
static void* g_thread_values[Game::THREAD_VALUE_COUNT]{}; \
auto** thread_data = \
Game::Sys::GetTls<void*>(Game::Sys::ThreadOffset::THREAD_VALUES); \
*thread_data = g_thread_values; \
Game::Sys_SetValue(Game::THREAD_VALUE_PROF_STACK, &profile_stack); \
Game::Sys_SetValue(Game::THREAD_VALUE_VA, &va_info); \
Game::Sys_SetValue(Game::THREAD_VALUE_COM_ERROR, &g_com_error); \
Game::Sys_SetValue(Game::THREAD_VALUE_TRACE, &g_trace_thread_info); \
}

View File

@ -8,6 +8,7 @@ namespace Game
Cbuf_AddServerText_f_t Cbuf_AddServerText_f = Cbuf_AddServerText_f_t(0x4BB9B0);
Cbuf_AddText_t Cbuf_AddText = Cbuf_AddText_t(0x404B20);
Cbuf_InsertText_t Cbuf_InsertText = Cbuf_InsertText_t(0x4940B0);
Cbuf_Execute_t Cbuf_Execute = Cbuf_Execute_t(0x4E2C80);
CG_DrawDisconnect_t CG_DrawDisconnect = CG_DrawDisconnect_t(0x454A70);
CG_NextWeapon_f_t CG_NextWeapon_f = CG_NextWeapon_f_t(0x449DE0);
@ -381,7 +382,9 @@ namespace Game
Material** whiteMaterial = reinterpret_cast<Material**>(0x8EE4B8);
unsigned long* _tls_index = reinterpret_cast<unsigned long*>(0x66D94A8);
unsigned long* g_dwTlsIndex = reinterpret_cast<unsigned long*>(0x66D94A8);
bgs_t* level_bgs = reinterpret_cast<bgs_t*>(0x19BD680);
unsigned int* playerCardUIStringIndex = reinterpret_cast<unsigned int*>(0x62CD7A8);
char (*playerCardUIStringBuf)[PLAYER_CARD_UI_STRING_COUNT][38] = reinterpret_cast<char(*)[PLAYER_CARD_UI_STRING_COUNT][38]>(0x62CB4F8);

View File

@ -15,6 +15,9 @@ namespace Game
typedef void(*Cbuf_InsertText_t)(int localClientNum, const char* text);
extern Cbuf_InsertText_t Cbuf_InsertText;
typedef void(*Cbuf_Execute_t)(int localClientNum, int controllerIndex);
extern Cbuf_Execute_t Cbuf_Execute;
typedef void(*CG_DrawDisconnect_t)(int localClientNum);
extern CG_DrawDisconnect_t CG_DrawDisconnect;
@ -712,7 +715,9 @@ namespace Game
extern Material** whiteMaterial;
extern unsigned long* _tls_index;
extern unsigned long* g_dwTlsIndex;
extern bgs_t* level_bgs;
constexpr std::size_t PLAYER_CARD_UI_STRING_COUNT = 18;
extern unsigned int* playerCardUIStringIndex;

View File

@ -10333,6 +10333,59 @@ namespace Game
TraceCheckCount checkcount;
};
struct ProfileAtom
{
unsigned int value[1];
};
volatile struct ProfileReadable
{
unsigned int hits;
ProfileAtom total;
ProfileAtom self;
};
struct ProfileWritable
{
int nesting;
unsigned int hits;
ProfileAtom start[3];
ProfileAtom total;
ProfileAtom child;
};
struct profile_t
{
ProfileWritable write;
ProfileReadable read;
};
struct profile_guard_t
{
int id;
profile_t** ppStack;
};
struct ProfileStack
{
profile_t prof_root;
profile_t* prof_pStack[16384];
profile_t** prof_ppStack;
profile_t prof_array[443];
ProfileAtom prof_overhead_internal;
ProfileAtom prof_overhead_external;
profile_guard_t prof_guardstack[32];
int prof_guardpos;
float prof_timescale;
};
struct bgs_t
{
unsigned char __pad0[0x82950];
};
static_assert(sizeof(bgs_t) == 0x82950);
#pragma endregion
#ifndef IDA

View File

@ -21,6 +21,7 @@ namespace Game
Sys_SendPacket_t Sys_SendPacket = Sys_SendPacket_t(0x60FDC0);
Sys_ShowConsole_t Sys_ShowConsole = Sys_ShowConsole_t(0x4305E0);
Sys_SuspendOtherThreads_t Sys_SuspendOtherThreads = Sys_SuspendOtherThreads_t(0x45A190);
Sys_SetValue_t Sys_SetValue = Sys_SetValue_t(0x4B2F50);
char(*sys_exitCmdLine)[1024] = reinterpret_cast<char(*)[1024]>(0x649FB68);

View File

@ -2,7 +2,6 @@
namespace Game
{
typedef void(*Sys_Error_t)(const char* error, ...);
extern Sys_Error_t Sys_Error;
@ -60,6 +59,9 @@ namespace Game
typedef void(*Sys_SuspendOtherThreads_t)();
extern Sys_SuspendOtherThreads_t Sys_SuspendOtherThreads;
typedef void(*Sys_SetValue_t)(int valueIndex, void* data);
extern Sys_SetValue_t Sys_SetValue;
extern char(*sys_exitCmdLine)[1024];
extern RTL_CRITICAL_SECTION* s_criticalSection;
@ -68,4 +70,22 @@ namespace Game
extern void Sys_UnlockRead(FastCriticalSection* critSect);
extern bool Sys_TryEnterCriticalSection(CriticalSection critSect);
class Sys
{
public:
enum ThreadOffset : unsigned int
{
LEVEL_BGS = 0xC,
THREAD_VALUES = 0x14,
DVAR_MODIFIED_FLAGS = 0x18,
};
template <typename T>
static T* GetTls(ThreadOffset offset)
{
const auto* tls = reinterpret_cast<std::uintptr_t*>(__readfsdword(0x2C));
return reinterpret_cast<T*>(tls[*g_dwTlsIndex] + offset);
}
};
}