diff --git a/flightrec/decoder/src/main/java/org/rehlds/flightrec/decoders/rehlds/AllocEntPrivateDataV2Decoder.java b/flightrec/decoder/src/main/java/org/rehlds/flightrec/decoders/rehlds/AllocEntPrivateDataV2Decoder.java
new file mode 100644
index 0000000..42f89dd
--- /dev/null
+++ b/flightrec/decoder/src/main/java/org/rehlds/flightrec/decoders/rehlds/AllocEntPrivateDataV2Decoder.java
@@ -0,0 +1,22 @@
+package org.rehlds.flightrec.decoders.rehlds;
+
+import org.rehlds.flightrec.api.DecodedExtraData;
+import org.rehlds.flightrec.api.FlightrecMessage;
+import org.rehlds.flightrec.api.FlightrecMessageType;
+import org.rehlds.flightrec.api.MessageDecoder;
+import org.rehlds.flightrec.api.util.UtilSizeBuf;
+
+public class AllocEntPrivateDataV2Decoder implements MessageDecoder {
+ @Override
+ public FlightrecMessageType getMessageType() {
+ return new FlightrecMessageType("rehlds", "AllocEntPrivateData", 2, false);
+ }
+
+ @Override
+ public DecodedExtraData decode(FlightrecMessage msg) {
+ UtilSizeBuf sb = msg.getDataSizebuf();
+ long ptr = sb.readUInt32();
+ long size = sb.readUInt32();
+ return DecodedExtraData.create("pPrivData", "0x" + Long.toHexString(ptr), "size", "" + size);
+ }
+}
diff --git a/flightrec/decoder/src/main/java/org/rehlds/flightrec/decoders/rehlds/LogV1Decoder.java b/flightrec/decoder/src/main/java/org/rehlds/flightrec/decoders/rehlds/LogV1Decoder.java
new file mode 100644
index 0000000..8925cd2
--- /dev/null
+++ b/flightrec/decoder/src/main/java/org/rehlds/flightrec/decoders/rehlds/LogV1Decoder.java
@@ -0,0 +1,22 @@
+package org.rehlds.flightrec.decoders.rehlds;
+
+import org.rehlds.flightrec.api.DecodedExtraData;
+import org.rehlds.flightrec.api.FlightrecMessage;
+import org.rehlds.flightrec.api.FlightrecMessageType;
+import org.rehlds.flightrec.api.MessageDecoder;
+import org.rehlds.flightrec.api.util.UtilSizeBuf;
+
+public class LogV1Decoder implements MessageDecoder {
+ @Override
+ public FlightrecMessageType getMessageType() {
+ return new FlightrecMessageType("rehlds", "Log", 1, false);
+ }
+
+ @Override
+ public DecodedExtraData decode(FlightrecMessage msg) {
+ UtilSizeBuf sb = msg.getDataSizebuf();
+ String prefix = sb.readString();
+ String message = sb.readString();
+ return DecodedExtraData.create("prefix", prefix, "message", message);
+ }
+}
diff --git a/flightrec/decoder/src/main/java/org/rehlds/flightrec/decoders/rehlds/RehldsDecodersModule.java b/flightrec/decoder/src/main/java/org/rehlds/flightrec/decoders/rehlds/RehldsDecodersModule.java
index e143e20..2292281 100644
--- a/flightrec/decoder/src/main/java/org/rehlds/flightrec/decoders/rehlds/RehldsDecodersModule.java
+++ b/flightrec/decoder/src/main/java/org/rehlds/flightrec/decoders/rehlds/RehldsDecodersModule.java
@@ -11,5 +11,8 @@ public class RehldsDecodersModule extends SimpleDecoderModule {
registerDecoder(new AllocEntPrivateDataV1Decoder());
registerDecoder(new FrameV2Decoder());
+
+ registerDecoder(new LogV1Decoder());
+ registerDecoder(new AllocEntPrivateDataV2Decoder());
}
}
diff --git a/rehlds/build.gradle b/rehlds/build.gradle
index feab1ae..3c4a9fa 100644
--- a/rehlds/build.gradle
+++ b/rehlds/build.gradle
@@ -162,6 +162,10 @@ void setupToolchain(NativeBinarySpec b) {
if (!unitTestExecutable && !swdsLib) {
cfg.singleDefines 'HOOK_ENGINE'
}
+
+ if (unitTestExecutable) {
+ cfg.singleDefines 'REHLDS_UNIT_TESTS'
+ }
if (rehldsFixes) {
cfg.singleDefines 'REHLDS_FIXES', 'REHLDS_CHECKS'
diff --git a/rehlds/engine/sv_log.cpp b/rehlds/engine/sv_log.cpp
index 3164b7a..59993c0 100644
--- a/rehlds/engine/sv_log.cpp
+++ b/rehlds/engine/sv_log.cpp
@@ -72,6 +72,10 @@ void Log_Printf(const char *fmt, ...)
Q_vsnprintf(&string[Q_strlen(string)], sizeof(string) - Q_strlen(string), fmt, argptr);
va_end(argptr);
+#ifdef REHLDS_FLIGHT_REC
+ FR_Log("REHLDS_LOG", string);
+#endif
+
if (g_psvs.log.net_log_ || firstLog != NULL)
{
if (g_psvs.log.net_log_)
diff --git a/rehlds/engine/sys_dll.cpp b/rehlds/engine/sys_dll.cpp
index dd0401b..57c91b7 100644
--- a/rehlds/engine/sys_dll.cpp
+++ b/rehlds/engine/sys_dll.cpp
@@ -1336,6 +1336,10 @@ void Con_Printf(const char *fmt, ...)
Q_vsnprintf(Dest, sizeof(Dest), fmt, va);
va_end(va);
+#ifdef REHLDS_FLIGHT_REC
+ FR_Log("REHLDS_CON", Dest);
+#endif
+
Sys_Printf("%s", Dest);
if (sv_redirected)
{
@@ -1381,6 +1385,34 @@ void Con_SafePrintf(const char *fmt, ...)
}
/* <8e00b> ../engine/sys_dll.c:2459 */
+#if defined(REHLDS_FIXES) && defined(REHLDS_FLIGHT_REC)
+// Always print debug logs to the flight recorder
+void Con_DPrintf(const char *fmt, ...)
+{
+ va_list argptr;
+
+ va_start(argptr, fmt);
+ char Dest[4096];
+ Q_vsnprintf(Dest, sizeof(Dest), fmt, argptr);
+ va_end(argptr);
+
+ FR_Log("REHLDS_CONDBG", Dest);
+
+ if (developer.value != 0.0f)
+ {
+#ifdef _WIN32
+ OutputDebugStringA(Dest);
+ if (con_debuglog)
+ Con_DebugLog("qconsole.log", "%s", Dest);
+#else
+ vfprintf(stdout, "%s", Dest);
+ fflush(stdout);
+#endif // _WIN32
+ }
+}
+
+#else //defined(REHLDS_FIXES) and defined(REHLDS_FLIGHT_REC)
+
void Con_DPrintf(const char *fmt, ...)
{
va_list argptr;
@@ -1391,6 +1423,7 @@ void Con_DPrintf(const char *fmt, ...)
#ifdef _WIN32
char Dest[4096];
Q_vsnprintf(Dest, sizeof(Dest), fmt, argptr);
+
OutputDebugStringA(Dest);
if (con_debuglog)
Con_DebugLog("qconsole.log", "%s", Dest);
@@ -1401,4 +1434,4 @@ void Con_DPrintf(const char *fmt, ...)
}
va_end(argptr);
}
-
+#endif //defined(REHLDS_FIXES) and defined(REHLDS_FLIGHT_REC)
diff --git a/rehlds/msvc/ReHLDS.vcxproj b/rehlds/msvc/ReHLDS.vcxproj
index fe328e0..89fe96c 100644
--- a/rehlds/msvc/ReHLDS.vcxproj
+++ b/rehlds/msvc/ReHLDS.vcxproj
@@ -268,6 +268,7 @@
true
true
+
true
true
@@ -546,6 +547,7 @@
+
@@ -904,7 +906,7 @@
Level3
Disabled
true
- REHLDS_FLIGHT_REC;REHLDS_OPT_PEDANTIC;REHLDS_SELF;_BUILD_FROM_IDE;USE_BREAKPAD_HANDLER;DEDICATED;SWDS;_CRT_SECURE_NO_WARNINGS;_DEBUG;%(PreprocessorDefinitions)
+ REHLDS_FLIGHT_REC;REHLDS_OPT_PEDANTIC;REHLDS_SELF;REHLDS_UNIT_TESTS;_BUILD_FROM_IDE;USE_BREAKPAD_HANDLER;DEDICATED;SWDS;_CRT_SECURE_NO_WARNINGS;_DEBUG;%(PreprocessorDefinitions)
MultiThreadedDebug
Use
precompiled.h
@@ -940,7 +942,7 @@
Level3
Disabled
true
- REHLDS_FLIGHT_REC;REHLDS_OPT_PEDANTIC;REHLDS_FIXES;REHLDS_SELF;_BUILD_FROM_IDE;USE_BREAKPAD_HANDLER;DEDICATED;SWDS;_CRT_SECURE_NO_WARNINGS;_DEBUG;%(PreprocessorDefinitions)
+ REHLDS_FLIGHT_REC;REHLDS_OPT_PEDANTIC;REHLDS_FIXES;REHLDS_SELF;REHLDS_UNIT_TESTS;_BUILD_FROM_IDE;USE_BREAKPAD_HANDLER;DEDICATED;SWDS;_CRT_SECURE_NO_WARNINGS;_DEBUG;%(PreprocessorDefinitions)
MultiThreadedDebug
Use
precompiled.h
diff --git a/rehlds/msvc/ReHLDS.vcxproj.filters b/rehlds/msvc/ReHLDS.vcxproj.filters
index 95f298c..31093ab 100644
--- a/rehlds/msvc/ReHLDS.vcxproj.filters
+++ b/rehlds/msvc/ReHLDS.vcxproj.filters
@@ -340,6 +340,9 @@
unittests
+
+ unittests
+
@@ -1053,6 +1056,9 @@
public
+
+ unittests
+
diff --git a/rehlds/public/rehlds/osconfig.h b/rehlds/public/rehlds/osconfig.h
index c8471c0..4457930 100644
--- a/rehlds/public/rehlds/osconfig.h
+++ b/rehlds/public/rehlds/osconfig.h
@@ -116,6 +116,10 @@
inline void* sys_allocmem(unsigned int size) {
return VirtualAlloc(NULL, size, MEM_COMMIT, PAGE_READWRITE);
}
+
+ inline void sys_freemem(void* ptr, unsigned int size) {
+ VirtualFree(ptr, 0, MEM_RELEASE);
+ }
#else // _WIN32
#ifndef PAGESIZE
#define PAGESIZE 4096
@@ -153,6 +157,9 @@
inline void* sys_allocmem(unsigned int size) {
return mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
}
+ inline void sys_freemem(void* ptr, unsigned int size) {
+ munmap(ptr, size);
+ }
#define WSAENOPROTOOPT ENOPROTOOPT
diff --git a/rehlds/public/rehlds/rehlds_api.h b/rehlds/public/rehlds/rehlds_api.h
index 7fb3781..f795c77 100644
--- a/rehlds/public/rehlds/rehlds_api.h
+++ b/rehlds/public/rehlds/rehlds_api.h
@@ -35,7 +35,7 @@
#include "model.h"
#define REHLDS_API_VERSION_MAJOR 1
-#define REHLDS_API_VERSION_MINOR 1
+#define REHLDS_API_VERSION_MINOR 2
//Steam_NotifyClientConnect hook
typedef IHookChain IRehldsHook_Steam_NotifyClientConnect;
@@ -165,6 +165,7 @@ struct RehldsFuncs_t {
double(*GetRealTime)();
int*(*GetMsgBadRead)();
cmd_source_t*(*GetCmdSource)();
+ void(*Log)(const char* prefix, const char* msg);
};
class IRehldsApi {
diff --git a/rehlds/rehlds/FlightRecorderImpl.cpp b/rehlds/rehlds/FlightRecorderImpl.cpp
index 2c8cca4..672a005 100644
--- a/rehlds/rehlds/FlightRecorderImpl.cpp
+++ b/rehlds/rehlds/FlightRecorderImpl.cpp
@@ -57,6 +57,11 @@ CRehldsFlightRecorder::CRehldsFlightRecorder() {
m_DataRegionPtr = m_DataRegion + DATA_REGION_HEADER;
}
+CRehldsFlightRecorder::~CRehldsFlightRecorder() {
+ sys_freemem(m_MetaRegion, META_REGION_SIZE);
+ sys_freemem(m_DataRegion, DATA_REGION_SIZE);
+}
+
void CRehldsFlightRecorder::InitHeadersContent() {
m_pMetaHeader->version = FLIGHT_RECORDER_VERSION;
m_pMetaHeader->regionSize = META_REGION_SIZE;
diff --git a/rehlds/rehlds/FlightRecorderImpl.h b/rehlds/rehlds/FlightRecorderImpl.h
index b503030..0116872 100644
--- a/rehlds/rehlds/FlightRecorderImpl.h
+++ b/rehlds/rehlds/FlightRecorderImpl.h
@@ -96,6 +96,7 @@ private:
public:
CRehldsFlightRecorder();
+ virtual ~CRehldsFlightRecorder();
void dump(const char* fname);
virtual void StartMessage(uint16 msg, bool entrance);
diff --git a/rehlds/rehlds/flight_recorder.cpp b/rehlds/rehlds/flight_recorder.cpp
index 4a12fff..50dc8ba 100644
--- a/rehlds/rehlds/flight_recorder.cpp
+++ b/rehlds/rehlds/flight_recorder.cpp
@@ -23,18 +23,27 @@ void FR_Init() {
g_FlightRecorder = new CRehldsFlightRecorder();
}
+void FR_Shutdown() {
+ if (g_FlightRecorder) {
+ delete g_FlightRecorder;
+ g_FlightRecorder = NULL;
+ }
+}
+
#ifdef REHLDS_FLIGHT_REC
uint16 g_FRMsg_Frame;
uint16 g_FRMsg_FreeEntPrivateData;
uint16 g_FRMsg_AllocEntPrivateData;
+uint16 g_FRMsg_Log;
cvar_t rehlds_flrec_frame = { "rehlds_flrec_frame", "1", 0, 1.0f, NULL };
cvar_t rehlds_flrec_pvdata = { "rehlds_flrec_privdata", "1", 0, 1.0f, NULL };
void FR_CheckInit() {
-#ifdef HOOK_ENGINE
- if (!g_FlightRecorder)
+#if defined(HOOK_ENGINE)
+ if (!g_FlightRecorder) {
FR_Init();
+ }
#endif
}
@@ -57,6 +66,7 @@ void FR_Rehlds_Init() {
g_FRMsg_Frame = g_FlightRecorder->RegisterMessage("rehlds", "Frame", 2, true);
g_FRMsg_FreeEntPrivateData = g_FlightRecorder->RegisterMessage("rehlds", "FreeEntPrivateData", 1, false);
g_FRMsg_AllocEntPrivateData = g_FlightRecorder->RegisterMessage("rehlds", "AllocEntPrivateData", 2, false);
+ g_FRMsg_Log = g_FlightRecorder->RegisterMessage("rehlds", "Log", 1, false);
}
void FR_StartFrame(long frameCounter) {
@@ -89,4 +99,15 @@ void FR_FreeEntPrivateData(void* data) {
g_FlightRecorder->EndMessage(g_FRMsg_FreeEntPrivateData, true);
}
+void FR_Log(const char* prefix, const char* msg) {
+ FR_CheckInit();
+ if (g_FlightRecorder == NULL) {
+ return; //During server initialization some messages could be written in console/log before flightrecorder is initialized
+ }
+ g_FlightRecorder->StartMessage(g_FRMsg_Log, true);
+ g_FlightRecorder->WriteString(prefix);
+ g_FlightRecorder->WriteString(msg);
+ g_FlightRecorder->EndMessage(g_FRMsg_Log, true);
+}
+
#endif //REHLDS_FLIGHT_REC
diff --git a/rehlds/rehlds/flight_recorder.h b/rehlds/rehlds/flight_recorder.h
index 9b80f43..5230e73 100644
--- a/rehlds/rehlds/flight_recorder.h
+++ b/rehlds/rehlds/flight_recorder.h
@@ -22,6 +22,7 @@
extern CRehldsFlightRecorder* g_FlightRecorder;
extern void FR_Init();
+extern void FR_Shutdown();
#ifdef REHLDS_FLIGHT_REC
@@ -40,5 +41,6 @@ extern void FR_StartFrame(long frameCounter);
extern void FR_EndFrame(long frameCounter);
extern void FR_FreeEntPrivateData(void* data);
extern void FR_AllocEntPrivateData(void* res, int size);
+extern void FR_Log(const char* prefix, const char* msg);
#endif //REHLDS_FLIGHT_REC
diff --git a/rehlds/rehlds/rehlds_api_impl.cpp b/rehlds/rehlds/rehlds_api_impl.cpp
index 86b60ce..684f9b0 100644
--- a/rehlds/rehlds/rehlds_api_impl.cpp
+++ b/rehlds/rehlds/rehlds_api_impl.cpp
@@ -55,6 +55,12 @@ cmd_source_t* GetCmdSource_api() {
return &cmd_source;
}
+void Log_api(const char* prefix, const char* msg) {
+#ifdef REHLDS_FLIGHT_REC
+ FR_Log(prefix, msg);
+#endif
+}
+
CRehldsServerStatic g_RehldsServerStatic;
CRehldsServerData g_RehldsServerData;
CRehldsHookchains g_RehldsHookchains;
@@ -80,7 +86,8 @@ RehldsFuncs_t g_RehldsApiFuncs =
&GetBuildNumber_api,
&GetRealTime_api,
&GetMsgBadRead_api,
- &GetCmdSource_api
+ &GetCmdSource_api,
+ &Log_api
};
sizebuf_t* GetNetMessage_api()
diff --git a/rehlds/unittests/TestRunner.cpp b/rehlds/unittests/TestRunner.cpp
index a5b2045..cb9fa86 100644
--- a/rehlds/unittests/TestRunner.cpp
+++ b/rehlds/unittests/TestRunner.cpp
@@ -1,4 +1,5 @@
#include "precompiled.h"
+#include "rehlds_tests_shared.h"
#include "cppunitlite/GradleAdapter.h"
int main(int argc, char* argv[]) {
diff --git a/rehlds/unittests/common_tests.cpp b/rehlds/unittests/common_tests.cpp
index 9d86158..0170930 100644
--- a/rehlds/unittests/common_tests.cpp
+++ b/rehlds/unittests/common_tests.cpp
@@ -1,4 +1,5 @@
#include "precompiled.h"
+#include "rehlds_tests_shared.h"
#include "cppunitlite/TestHarness.h"
TEST(BitsWritingReading, MSG, 1000)
diff --git a/rehlds/unittests/crc32c_tests.cpp b/rehlds/unittests/crc32c_tests.cpp
index 5f5f575..4a4f5f7 100644
--- a/rehlds/unittests/crc32c_tests.cpp
+++ b/rehlds/unittests/crc32c_tests.cpp
@@ -1,7 +1,9 @@
#include "precompiled.h"
+#include "rehlds_tests_shared.h"
#include "cppunitlite/TestHarness.h"
TEST(CRC32C_Hash, CRC32C, 1000) {
+
Sys_CheckCpuInstructionsSupport();
CHECK("SSE4.2 Support", cpuinfo.sse4_2);
diff --git a/rehlds/unittests/delta_tests.cpp b/rehlds/unittests/delta_tests.cpp
index cd4e0e5..500265f 100644
--- a/rehlds/unittests/delta_tests.cpp
+++ b/rehlds/unittests/delta_tests.cpp
@@ -1,4 +1,5 @@
#include "precompiled.h"
+#include "rehlds_tests_shared.h"
#include "cppunitlite/TestHarness.h"
#pragma pack(push, 1)
@@ -239,6 +240,8 @@ void _DeltaSimpleTests(delta_t* delta, delta_simpletest_data_t* tests, int tests
TEST(MarkFieldsTest_Simple_Primitives, Delta, 1000) {
+ EngineInitializer engInitGuard;
+
delta_t* delta = _CreateTestDeltaDesc();
delta_simpletest_data_t testdata[] = {
@@ -254,11 +257,11 @@ TEST(MarkFieldsTest_Simple_Primitives, Delta, 1000) {
{ "Float_1BlockCheck", 0x71, 0x71, true, "f_18", []() { dst2.f_18 = 0; } },
};
_DeltaSimpleTests(delta, testdata, ARRAYSIZE(testdata));
-
- SV_Shutdown();
}
TEST(MarkFieldsTest_InterBlock, Delta, 1000) {
+ EngineInitializer engInitGuard;
+
delta_t* delta = _CreateTestDeltaDesc();
delta_simpletest_data_t testdata[] = {
@@ -279,11 +282,11 @@ TEST(MarkFieldsTest_InterBlock, Delta, 1000) {
{ "Interblock2_val_3b", 0x71, 0x71, true, "i_5D", []() { dst2.i_5D &= 0x00FFFFFF; } },
};
_DeltaSimpleTests(delta, testdata, ARRAYSIZE(testdata));
-
- SV_Shutdown();
}
TEST(MarkFieldsTest_Strings, Delta, 1000) {
+ EngineInitializer engInitGuard;
+
delta_t* delta = _CreateTestDeltaDesc();
delta_simpletest_data_t testdata[] = {
@@ -296,11 +299,11 @@ TEST(MarkFieldsTest_Strings, Delta, 1000) {
{ "Str_case_check", 'c', 'c', true, "", []() { dst1.s_24[0] = 'C'; } },
};
_DeltaSimpleTests(delta, testdata, ARRAYSIZE(testdata));
-
- SV_Shutdown();
}
TEST(MarkFieldsTest_TimeWindow, Delta, 1000) {
+ EngineInitializer engInitGuard;
+
#ifdef REHLDS_FIXES
bool rehlds_fixes = true;
#else
@@ -315,12 +318,11 @@ TEST(MarkFieldsTest_TimeWindow, Delta, 1000) {
{ "TimeWindow_Above_Precision2", 'c', 'c', true, "w8_1C", []() { dst2.w8_1C = 0.1f; dst1.w8_1C = 0.12f; } },
};
_DeltaSimpleTests(delta, testdata, ARRAYSIZE(testdata));
-
- SV_Shutdown();
}
-TEST(TestDelta_Test, Delta, 1000)
-{
+TEST(TestDelta_Test, Delta, 1000) {
+ EngineInitializer engInitGuard;
+
delta_t* delta = _CreateTestDeltaDesc();
delta_test_struct_t testdata[4], from;
@@ -363,6 +365,4 @@ TEST(TestDelta_Test, Delta, 1000)
if (tested != result[i])
rehlds_syserror("TestDelta_Test: returned bitcount %i is not equal to true value %i", tested, result[i]);
}
-
- SV_Shutdown();
}
diff --git a/rehlds/unittests/info_tests.cpp b/rehlds/unittests/info_tests.cpp
index 56cc5ca..164be30 100644
--- a/rehlds/unittests/info_tests.cpp
+++ b/rehlds/unittests/info_tests.cpp
@@ -1,7 +1,10 @@
#include "precompiled.h"
+#include "rehlds_tests_shared.h"
#include "cppunitlite/TestHarness.h"
TEST(PrefixedKeysRemove, Info, 1000) {
+ EngineInitializer engInitGuard;
+
struct testdata_t {
const char* inData;
const char* outData;
@@ -31,6 +34,8 @@ TEST(PrefixedKeysRemove, Info, 1000) {
}
TEST(SetValueForStarKey, Info, 1000) {
+ EngineInitializer engInitGuard;
+
struct testdata_t {
const char* initialInfo;
const char* key;
@@ -91,6 +96,8 @@ TEST(SetValueForStarKey, Info, 1000) {
}
TEST(RemoveKeyValue, Info, 1000) {
+ EngineInitializer engInitGuard;
+
struct testdata_t {
const char* initialInfo;
const char* key;
@@ -124,6 +131,8 @@ TEST(RemoveKeyValue, Info, 1000) {
}
TEST(GetKeyValue, Info, 1000) {
+ EngineInitializer engInitGuard;
+
struct testdata_t {
const char* info;
const char* key;
diff --git a/rehlds/unittests/mathlib_tests.cpp b/rehlds/unittests/mathlib_tests.cpp
index 335caac..73450ce 100644
--- a/rehlds/unittests/mathlib_tests.cpp
+++ b/rehlds/unittests/mathlib_tests.cpp
@@ -1,4 +1,5 @@
#include "precompiled.h"
+#include "rehlds_tests_shared.h"
#include "cppunitlite/TestHarness.h"
TEST(AngleVectorsTest, MathLib, 1000) {
diff --git a/rehlds/unittests/rehlds_tests_shared.cpp b/rehlds/unittests/rehlds_tests_shared.cpp
new file mode 100644
index 0000000..40bb8c5
--- /dev/null
+++ b/rehlds/unittests/rehlds_tests_shared.cpp
@@ -0,0 +1,32 @@
+#include "precompiled.h"
+#include "rehlds_tests_shared.h"
+
+static uint8 g_TestMemoryBuf[1024 * 1024 * 4];
+
+void Tests_InitEngine() {
+ Memory_Init(g_TestMemoryBuf, sizeof(g_TestMemoryBuf));
+
+ FR_Init();
+
+#ifdef REHLDS_FLIGHT_REC
+ FR_Rehlds_Init();
+#endif //REHLDS_FLIGHT_REC
+
+ Cbuf_Init();
+ Cmd_Init();
+ Cvar_Init();
+ Cvar_CmdInit();
+}
+
+void Tests_ShutdownEngine() {
+ Cvar_Shutdown();
+ Cmd_Shutdown();
+
+ mainzone = NULL;
+ hunk_base = NULL;
+ hunk_size = 0;
+
+ FR_Shutdown();
+
+ SV_Shutdown();
+}
diff --git a/rehlds/unittests/rehlds_tests_shared.h b/rehlds/unittests/rehlds_tests_shared.h
new file mode 100644
index 0000000..7a3afcd
--- /dev/null
+++ b/rehlds/unittests/rehlds_tests_shared.h
@@ -0,0 +1,18 @@
+#pragma once
+
+#include "osconfig.h"
+#include "engine.h"
+
+extern void Tests_InitEngine();
+extern void Tests_ShutdownEngine();
+
+class EngineInitializer {
+public:
+ EngineInitializer() {
+ Tests_InitEngine();
+ }
+
+ ~EngineInitializer() {
+ Tests_ShutdownEngine();
+ }
+};
\ No newline at end of file
diff --git a/rehlds/unittests/struct_offsets_tests.cpp b/rehlds/unittests/struct_offsets_tests.cpp
index 3bb81fb..6c1f59a 100644
--- a/rehlds/unittests/struct_offsets_tests.cpp
+++ b/rehlds/unittests/struct_offsets_tests.cpp
@@ -1,4 +1,5 @@
#include "precompiled.h"
+#include "rehlds_tests_shared.h"
#include "cppunitlite/TestHarness.h"
#pragma warning(push)
diff --git a/rehlds/unittests/tmessage_tests.cpp b/rehlds/unittests/tmessage_tests.cpp
index b24ed64..a2ce942 100644
--- a/rehlds/unittests/tmessage_tests.cpp
+++ b/rehlds/unittests/tmessage_tests.cpp
@@ -1,4 +1,5 @@
#include "precompiled.h"
+#include "rehlds_tests_shared.h"
#include "cppunitlite/TestHarness.h"
diff --git a/rehlds/unittests/unicode_tests.cpp b/rehlds/unittests/unicode_tests.cpp
index 02429a0..9b8e158 100644
--- a/rehlds/unittests/unicode_tests.cpp
+++ b/rehlds/unittests/unicode_tests.cpp
@@ -1,4 +1,5 @@
#include "precompiled.h"
+#include "rehlds_tests_shared.h"
#include "cppunitlite/TestHarness.h"
TEST(UTF8ToFromUChar32, UNICODE_STRTOOLS, 1000)