diff --git a/AMBuildScript b/AMBuildScript index 70c6792..2c8712c 100644 --- a/AMBuildScript +++ b/AMBuildScript @@ -368,6 +368,7 @@ BuildScripts = [ if getattr(builder.options, 'enable_tests', False): BuildScripts += [ 'core/sourcehook/test/AMBuilder', + 'loader/test/AMBuilder', ] if builder.backend == 'amb2': diff --git a/README.md b/README.md index 6836536..5f81b4f 100644 --- a/README.md +++ b/README.md @@ -55,3 +55,18 @@ General documentation: Development: + + +Unit testing: +``` +mkdir build +cd build +python ../configure.py --sdks episode1 --enable-tests +ambuild +``` + +To test the loader code on Windows: +``` +loader/test/test_loader/windows-x86/test_loader.exe +loader/test/test_loader/windows-x86_64/test_loader.exe +``` diff --git a/core/sourcehook/test/AMBuilder b/core/sourcehook/test/AMBuilder index 6edca31..2b96087 100644 --- a/core/sourcehook/test/AMBuilder +++ b/core/sourcehook/test/AMBuilder @@ -4,6 +4,8 @@ import os for cxx in MMS.all_targets: name = 'test_sourcehook' binary = MMS.Program(cxx, name) + if binary.compiler.target.platform == 'windows': + continue if binary.compiler.target.arch == 'x86_64' and binary.compiler.target.platform == 'linux': continue @@ -12,6 +14,7 @@ for cxx in MMS.all_targets: ] binary.compiler.cxxincludes += [ + os.path.join(builder.sourcePath, 'core'), os.path.join(builder.sourcePath, 'core', 'sourcehook'), ] if binary.compiler.version >= 'gcc-4.9': diff --git a/core/sourcehook/test/version.rc b/core/sourcehook/test/version.rc new file mode 100644 index 0000000..e69de29 diff --git a/loader/test/AMBuilder b/loader/test/AMBuilder new file mode 100644 index 0000000..2009730 --- /dev/null +++ b/loader/test/AMBuilder @@ -0,0 +1,35 @@ +# vim: set sts=2 ts=8 sw=2 tw=99 et ft=python: +import os + +for cxx in MMS.all_targets: + name = 'test_loader' + binary = MMS.Program(cxx, name) + + if binary.compiler.target.platform == 'windows' and binary.compiler.target.arch == 'x86_64': + binary.compiler.defines += ['WIN64'] + + # TODO: hack - something makes it try and compile with /SUBSYSTEM:WINDOWS which then causes it to complain that WinMain is missing + if binary.compiler.target.platform == 'windows': + binary.compiler.linkflags = ['Advapi32.lib', 'kernel32.lib', 'shell32.lib'] + + binary.compiler.cxxincludes += [ + os.path.join(builder.sourcePath, 'core'), + os.path.join(builder.sourcePath, 'core', 'sourcehook'), + os.path.join(builder.sourcePath, 'loader'), + ] + + if binary.compiler.version >= 'gcc-4.9': + binary.compiler.cxxflags += ['-fno-devirtualize'] + if binary.compiler.version >= 'clang-2.9' or binary.compiler.version >= 'apple-clang-3.0': + binary.compiler.cxxflags += ['-Wno-null-dereference'] + + binary.sources += [ + 'main.cpp', + '../gamedll.cpp', + '../loader.cpp', + '../serverplugin.cpp', + '../utility.cpp', + 'testdeterminebackends1.cpp', + ] + + builder.Add(binary) diff --git a/loader/test/main.cpp b/loader/test/main.cpp new file mode 100644 index 0000000..844a395 --- /dev/null +++ b/loader/test/main.cpp @@ -0,0 +1,44 @@ +#include +#include +#include +#include +#include + +using namespace std; +bool g_Verbose; + +#define DECL_TEST(x) bool Test##x(std::string &error); + +#define DO_TEST(x) \ + error.clear(); \ + if (Test##x(error)) \ + { \ + ++passed; \ + cout << "Test" << #x << " passed" << endl; \ + } \ + else \ + { \ + ++failed; \ + cout << "Test" << #x << " FAILED: " << error << endl; \ + } \ + + +DECL_TEST(DetermineBackendS1); + +int main(int argc, char *argv[]) +{ + std::string error; + + g_Verbose = argc > 1 && strcmp(argv[1], "-v") == 0; + + int passed = 0, failed = 0; + + DO_TEST(DetermineBackendS1); + + cout << endl << "----" << endl << "Passed: " << passed << endl << "Failed: " << failed << endl; + cout << "Total: " << passed + failed << endl; + + if (failed) + return 1; + return 0; +} diff --git a/loader/test/testdeterminebackends1.cpp b/loader/test/testdeterminebackends1.cpp new file mode 100644 index 0000000..1066607 --- /dev/null +++ b/loader/test/testdeterminebackends1.cpp @@ -0,0 +1,222 @@ +#include +#include +#include +#ifdef _WIN32 +#include +#endif // _WIN32 +#include "loader.h" +#include "utility.h" + +#ifdef _WIN64 +bool g_64Bit = true; +#else +bool g_64Bit = false; +#endif + +struct GameAttributes +{ + // none of these will really be PLATFORM_MAX_PATH but they're paths, so make them long enough + char gamePath[PLATFORM_MAX_PATH]; + char gameDirectory[PLATFORM_MAX_PATH]; + char binPath[PLATFORM_MAX_PATH]; + MetamodBackend expectedBackend; + bool is64Bit; +}; + +std::string GetSteamInstallationPath() +{ +#ifdef _WIN32 + char pathBuffer[512]; + HKEY hKey; + ULONG statusCode; + DWORD bufferSize = sizeof(pathBuffer); + + LONG result = RegOpenKeyExA(HKEY_LOCAL_MACHINE, "SOFTWARE\\Wow6432Node\\Valve\\Steam", 0, KEY_READ, &hKey); + + // if it's not find, try the 32-bit path + if (result == ERROR_FILE_NOT_FOUND) + { + result = RegOpenKeyExA(HKEY_LOCAL_MACHINE, "SOFTWARE\\Valve\\Steam", 0, KEY_READ, &hKey); + } + + statusCode = RegQueryValueExA(hKey, "InstallPath", 0, NULL, (LPBYTE)pathBuffer, &bufferSize); + + if (statusCode == ERROR_SUCCESS) + { + return pathBuffer; + } + else + { + std::cout << "Error: RegQueryValueExA returned " << statusCode << std::endl; + + return ""; + } +#else + return ""; +#endif +} + +bool TestDetermineBackendS1(std::string &error) +{ + std::string steamInstallationPath = GetSteamInstallationPath(); + std::map games; + char errorBuffer[128]; + char currentDirectoryBuffer[PLATFORM_MAX_PATH]; + char directoryBuffer[PLATFORM_MAX_PATH]; + void *serverLib = nullptr; + void *engineLib = nullptr; + QueryValveInterface serverQvi; + QueryValveInterface engineQvi; + + if (steamInstallationPath == "") + { + std::cout << "Warning: exiting early as Steam installation not found" << std::endl; + + return true; + } + + // TODO: when C++20 is supported, use designated initialisers + games["Black Mesa"] = { + /*.gamePath = */"steamapps\\common\\Black Mesa\\", + /*.gameDirectory = */"bms", + /*.binPath = */"bin", + /*.expectedBackend = */MMBackend_BMS, + /*.is64Bit = */false + }; + games["Bloody Good Time"] = { + /*.gamePath = */"steamapps\\common\\Bloody Good Time\\", + /*.gameDirectory = */"pm", + /*.binPath = */"bin", + /*.expectedBackend = */MMBackend_BloodyGoodTime, + /*.is64Bit = */false + }; + games["Bloody Good Time Dedicated Server"] = { + /*.gamePath = */"steamapps\\common\\Bloody Good Time Dedicated Server\\", + /*.gameDirectory = */"pm", + /*.binPath = */"bin", + /*.expectedBackend = */MMBackend_BloodyGoodTime, + /*.is64Bit = */false + }; + games["Counter-Strike: Source"] = { + /*.gamePath = */"steamapps\\common\\Counter-Strike Source\\", + /*.gameDirectory = */"cstrike", + /*.binPath = */"bin\\x64", + /*.expectedBackend = */MMBackend_CSS, + /*.is64Bit = */true + }; + games["Counter-Strike: Global Offensive"] = { + /*.gamePath = */"steamapps\\common\\Counter-Strike Global Offensive\\", + /*.gameDirectory = */"csgo", + /*.binPath = */"bin", + /*.expectedBackend = */MMBackend_CSGO, + /*.is64Bit = */false + }; + games["Half-Life 2: Deathmatch"] = { + /*.gamePath = */"steamapps\\common\\Half-Life 2 Deathmatch\\", + /*.gameDirectory = */"hl2mp", + /*.binPath = */"bin\\x64", + /*.expectedBackend = */MMBackend_HL2DM, + /*.is64Bit = */true + }; + games["Military Conflict: Vietnam"] = { + /*.gamePath = */"steamapps\\common\\Military Conflict - Vietnam\\", + /*.gameDirectory = */"vietnam", + /*.binPath = */"bin\\win64", + /*.expectedBackend = */MMBackend_MCV, + /*.is64Bit = */true + }; + games["Portal 2"] = { + /*.gamePath = */"steamapps\\common\\Portal 2\\", + /*.gameDirectory = */"portal2", + /*.binPath = */"bin", + /*.expectedBackend = */MMBackend_Portal2, + /*.is64Bit = */false + }; + // TODO: skip for now as sourcetest is still 32-bit but the engine is 64-bit + // games["Source SDK Base 2013 Multiplayer"] = { + // /*.gamePath = */"steamapps\\common\\Source SDK Base 2013 Multiplayer\\", + // /*.gameDirectory = */"sourcetest", + // /*.binPath = */"bin", + // /*.expectedBackend = */MMBackend_HL2DM, + // /*.is64Bit = */true + // }; + games["Team Fortress 2"] = { + /*.gamePath = */"steamapps\\common\\Team Fortress 2\\", + /*.gameDirectory = */"tf", + /*.binPath = */"bin\\x64", + /*.expectedBackend = */MMBackend_TF2, + /*.is64Bit = */true + }; + + GetCurrentDirectory(PLATFORM_MAX_PATH, currentDirectoryBuffer); + + for (const auto& [gameName, gameAttributes] : games) + { + std::cout << "Checking " << gameName << std::endl; + + if (gameAttributes.is64Bit && !g_64Bit) + { + std::cout << "Skipping in 32-bit build" << std::endl << std::endl; + continue; + } + else if (!gameAttributes.is64Bit && g_64Bit) + { + std::cout << "Skipping in 64-bit build" << std::endl << std::endl; + continue; + } + + sprintf(directoryBuffer, "%s\\%s\\%s\\", steamInstallationPath.c_str(), gameAttributes.gamePath, gameAttributes.binPath); + SetCurrentDirectory(directoryBuffer); + + // get the server library + sprintf(directoryBuffer, "%s\\%s\\%s\\%s\\server.dll", steamInstallationPath.c_str(), gameAttributes.gamePath, gameAttributes.gameDirectory, gameAttributes.binPath); + serverLib = mm_LoadLibrary(directoryBuffer, errorBuffer, 128); + + if (serverLib == nullptr) + { + std::cout << "Error: mm_LoadLibrary server library " << errorBuffer << std::endl; + continue; + } + + // get the engine library + sprintf(directoryBuffer, "%s\\%s\\%s\\engine.dll", steamInstallationPath.c_str(), gameAttributes.gamePath, gameAttributes.binPath); + engineLib = mm_LoadLibrary(directoryBuffer, errorBuffer, 128); + + if (engineLib == nullptr) + { + std::cout << "Error: mm_LoadLibrary engine library " << errorBuffer << std::endl; + continue; + } + + serverQvi = (QueryValveInterface)mm_GetLibAddress(serverLib, "CreateInterface"); + + if (serverQvi == nullptr) + { + std::cout << "Error: Failed to find CreateInterface in server" << std::endl; + continue; + } + + engineQvi = (QueryValveInterface)mm_GetLibAddress(engineLib, "CreateInterface"); + + if (engineQvi == nullptr) + { + std::cout << "Error: Failed to find CreateInterface in engine" << std::endl; + continue; + } + + MetamodBackend actualBackend = mm_DetermineBackendS1(engineQvi, serverQvi, gameAttributes.gameDirectory); + + if (actualBackend != gameAttributes.expectedBackend) + { + std::cout << "Got MetamodBackend " << actualBackend << " instead of expected " << gameAttributes.expectedBackend << std::endl << std::endl; + } + else + { + std::cout << "Got MetamodBackend " << gameAttributes.expectedBackend << std::endl << std::endl; + } + } + + SetCurrentDirectory(currentDirectoryBuffer); + + return true; +} diff --git a/loader/test/version.rc b/loader/test/version.rc new file mode 100644 index 0000000..e69de29 diff --git a/loader/utility.cpp b/loader/utility.cpp index 053afae..f7a5f76 100644 --- a/loader/utility.cpp +++ b/loader/utility.cpp @@ -615,7 +615,7 @@ void *mm_FindPattern(const void *libPtr, const char *pattern, size_t len) while (ptr < end) { found = true; - for (register size_t i = 0; i < len; i++) + for (size_t i = 0; i < len; i++) { if (pattern[i] != '\x2A' && pattern[i] != ptr[i]) {