diff --git a/.gitmodules b/.gitmodules index aa8ed2b2..c0543d8e 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,28 +1,31 @@ -[submodule "deps/zlib"] - path = deps/zlib - url = https://github.com/madler/zlib.git - branch = master -[submodule "deps/json11"] - path = deps/json11 - url = https://github.com/dropbox/json11.git -[submodule "deps/libtommath"] - path = deps/libtommath - url = https://github.com/libtom/libtommath.git - branch = develop -[submodule "deps/protobuf"] - path = deps/protobuf - url = https://github.com/google/protobuf.git -[submodule "deps/Wink-Signals"] - path = deps/Wink-Signals - url = https://github.com/momo5502/Wink-Signals.git -[submodule "deps/libtomcrypt"] - path = deps/libtomcrypt - url = https://github.com/libtom/libtomcrypt.git - branch = develop -[submodule "deps/pdcurses"] - path = deps/pdcurses - url = https://github.com/wmcbrine/PDCurses.git -[submodule "deps/mongoose"] - path = deps/mongoose - url = https://github.com/cesanta/mongoose.git - ignore = dirty +[submodule "deps/zlib"] + path = deps/zlib + url = https://github.com/madler/zlib.git + branch = master +[submodule "deps/json11"] + path = deps/json11 + url = https://github.com/dropbox/json11.git +[submodule "deps/libtommath"] + path = deps/libtommath + url = https://github.com/libtom/libtommath.git + branch = develop +[submodule "deps/protobuf"] + path = deps/protobuf + url = https://github.com/google/protobuf.git +[submodule "deps/Wink-Signals"] + path = deps/Wink-Signals + url = https://github.com/momo5502/Wink-Signals.git +[submodule "deps/libtomcrypt"] + path = deps/libtomcrypt + url = https://github.com/libtom/libtomcrypt.git + branch = develop +[submodule "deps/pdcurses"] + path = deps/pdcurses + url = https://github.com/wmcbrine/PDCurses.git +[submodule "deps/mongoose"] + path = deps/mongoose + url = https://github.com/cesanta/mongoose.git + ignore = dirty +[submodule "deps/fmt"] + path = deps/fmt + url = https://github.com/fmtlib/fmt.git diff --git a/deps/fmt b/deps/fmt new file mode 160000 index 00000000..4133e501 --- /dev/null +++ b/deps/fmt @@ -0,0 +1 @@ +Subproject commit 4133e501f3277aeef530b75de2e7bfceca93e5d2 diff --git a/deps/protobuf b/deps/protobuf index 8779cba3..790e6afb 160000 --- a/deps/protobuf +++ b/deps/protobuf @@ -1 +1 @@ -Subproject commit 8779cba302f330f478ce10c4e58a34e7b6360471 +Subproject commit 790e6afb72ed4ad952d9c74e73ae53a01443fe97 diff --git a/premake5.lua b/premake5.lua index dfbcfc90..f05ef97c 100644 --- a/premake5.lua +++ b/premake5.lua @@ -1,390 +1,412 @@ --- Option to allow copying the DLL file to a custom folder after build -newoption { - trigger = "copy-to", - description = "Optional, copy the DLL to a custom folder after build, define the path here if wanted.", - value = "PATH" -} - -newoption { - trigger = "no-new-structure", - description = "Do not use new virtual path structure (separating headers and source files)." -} - -newaction { - trigger = "version", - description = "Returns the version string for the current commit of the source code.", - onWorkspace = function(wks) - -- get revision number via git - local proc = assert(io.popen("git rev-list --count HEAD", "r")) - local revNumber = assert(proc:read('*a')):gsub("%s+", "") - proc:close() - - print(revNumber) - os.exit(0) - end -} - -newaction { - trigger = "generate-buildinfo", - description = "Sets up build information file like version.h.", - onWorkspace = function(wks) - -- get revision number via git - local proc = assert(io.popen("git rev-list --count HEAD", "r")) - local revNumber = assert(proc:read('*a')):gsub("%s+", "") - proc:close() - - -- get whether this is a clean revision (no uncommitted changes) - local proc = assert(io.popen("git status --porcelain", "r")) - local revClean = 1 - local revCleanSuffix = "" - if assert(proc:read('*a')) ~= "" then - revClean = 0 - revCleanSuffix = " (unclean)" - end - proc:close() - - -- get old version number from version.hpp if any - local oldRevNumber = "(none)" - local oldRevClean = 1 - local oldRevCleanSuffix = "" - local oldVersionHeader = io.open(wks.location .. "/src/version.hpp", "r") - if oldVersionHeader ~=nil then - local oldVersionHeaderContent = assert(oldVersionHeader:read('*a')) - oldRevNumber = string.match(oldVersionHeaderContent, "#define REVISION (%d+)") - if oldRevNumber == nil then - -- old version.hpp format? - oldRevNumber = "(none)" - end - oldRevClean = string.match(oldVersionHeaderContent, "#define REVISION_CLEAN (%d+)") - if oldRevClean == nil then - -- old version.hpp format? - oldRevClean = 1 - elseif oldRevClean ~= "1" then - oldRevClean = 0 - else - oldRevClean = 1 - end - end - if oldRevClean == 0 then - oldRevCleanSuffix = " (unclean)" - end - - -- generate version.hpp with a revision number if not equal - if oldRevNumber ~= revNumber or oldRevClean ~= revClean then - print ("Update " .. oldRevNumber .. oldRevCleanSuffix .. " -> " .. revNumber .. revCleanSuffix) - local versionHeader = assert(io.open(wks.location .. "/src/version.hpp", "w")) - versionHeader:write("/*\n") - versionHeader:write(" * Automatically generated by premake5.\n") - versionHeader:write(" * Do not touch, you fucking moron!\n") - versionHeader:write(" */\n") - versionHeader:write("\n") - versionHeader:write("#define REVISION " .. revNumber .. "\n") - versionHeader:write("#define REVISION_CLEAN " .. revClean .. "\n") - versionHeader:close() - end - end -} - -workspace "iw4x" - location "./build" - objdir "%{wks.location}/obj" - targetdir "%{wks.location}/bin/%{cfg.buildcfg}" - configurations { "Debug", "DebugStatic", "Release", "ReleaseStatic" } - architecture "x32" - platforms "x86" - - -- VS 2015 toolset only - toolset "msc-140" - - configuration "windows" - defines { "_WINDOWS", "WIN32" } - - configuration "Release*" - defines { "NDEBUG" } - flags { "MultiProcessorCompile", "Symbols", "LinkTimeOptimization", "No64BitChecks" } - optimize "Full" - - configuration "Debug*" - defines { "DEBUG", "_DEBUG" } - flags { "MultiProcessorCompile", "Symbols", "No64BitChecks" } - optimize "Debug" - - configuration "*Static" - flags { "StaticRuntime" } - - project "iw4x" - kind "SharedLib" - language "C++" - files { - "./src/**.rc", - "./src/**.hpp", - "./src/**.cpp", - "./src/**.proto", - } - includedirs { - "%{prj.location}/src", - "./src" - } - resincludedirs { - "$(ProjectDir)src" -- fix for VS IDE - } - - -- Pre-compiled header - pchheader "STDInclude.hpp" -- must be exactly same as used in #include directives - pchsource "src/STDInclude.cpp" -- real path - buildoptions { "/Zm200" } - filter "files:**.pb.*" - flags { - "NoPCH", - } - buildoptions { - "/wd4100", -- "Unused formal parameter" - "/wd4389", -- "Signed/Unsigned mismatch" - "/wd6011", -- "Dereferencing NULL pointer" - "/wd4125", -- "Decimal digit terminates octal escape sequence" - } - defines { - "_SCL_SECURE_NO_WARNINGS", - } - filter {} - - -- Dependency libraries - links { "zlib", "json11", "pdcurses", "libtomcrypt", "libtommath", "protobuf", "mongoose" } - includedirs - { - "./deps/zlib", - "./deps/json11", - "./deps/pdcurses", - "./deps/mongoose", - "./deps/libtomcrypt/src/headers", - "./deps/libtommath", - "./deps/protobuf/src", - "./deps/Wink-Signals", - } - - -- fix vpaths for protobuf sources - vpaths { - ["*"] = { "./src/**" }, - ["Proto/Generated"] = { "**.pb.*" }, -- meh. - } - - -- Virtual paths - if not _OPTIONS["no-new-structure"] then - vpaths { - ["Headers/*"] = { "./src/**.hpp" }, - ["Sources/*"] = { "./src/**.cpp" }, - ["Resource/*"] = { "./src/**.rc" }, - ["Proto/Definitions/*"] = { "./src/Proto/**.proto" }, - ["Proto/Generated/*"] = { "**.pb.*" }, -- meh. - } - end - - vpaths { - ["Docs/*"] = { "**.txt","**.md" }, - } - - -- Pre-build - prebuildcommands { - "cd %{_MAIN_SCRIPT_DIR}", - "tools\\premake5 generate-buildinfo" - } - - -- Post-build - if _OPTIONS["copy-to"] then - saneCopyToPath = string.gsub(_OPTIONS["copy-to"] .. "\\", "\\\\", "\\") - postbuildcommands { - "copy /y \"$(TargetDir)*.dll\" \"" .. saneCopyToPath .. "\"" - } - end - - -- Specific configurations - flags { "UndefinedIdentifiers", "ExtraWarnings" } - - configuration "Release*" - flags { "FatalCompileWarnings" } - configuration {} - - -- Generate source code from protobuf definitions - rules { "ProtobufCompiler" } - - -- Workaround: Consume protobuf generated source files - matches = os.matchfiles(path.join("src/Proto/**.proto")) - for i, srcPath in ipairs(matches) do - basename = path.getbasename(srcPath) - files { - string.format("%%{prj.location}/src/proto/%s.pb.h", basename), - string.format("%%{prj.location}/src/proto/%s.pb.cc", basename), - } - end - includedirs { - "%{prj.location}/src/proto" - } - - group "External dependencies" - - -- zlib - project "zlib" - language "C" - defines { "ZLIB_DLL", "_CRT_SECURE_NO_DEPRECATE" } - - files - { - "./deps/zlib/*.h", - "./deps/zlib/*.c" - } - - -- not our code, ignore POSIX usage warnings for now - warnings "Off" - - kind "SharedLib" - configuration "*Static" - kind "StaticLib" - removedefines { "ZLIB_DLL" } - - -- json11 - project "json11" - language "C++" - - files - { - "./deps/json11/*.cpp", - "./deps/json11/*.hpp" - } - - -- remove dropbox's testing code - removefiles { "./deps/json11/test.cpp" } - - -- not our code, ignore POSIX usage warnings for now - warnings "Off" - - -- always build as static lib, as json11 doesn't export anything - kind "StaticLib" - - -- mongoose - project "mongoose" - language "C" - - files - { - "./deps/mongoose/*.c", - "./deps/mongoose/*.h" - } - - -- not our code, ignore POSIX usage warnings for now - warnings "Off" - - -- always build as static lib, as json11 doesn't export anything - kind "StaticLib" - - - -- pdcurses - project "pdcurses" - language "C" - includedirs { "./deps/pdcurses/" } - - files - { - "./deps/pdcurses/pdcurses/*.c", - "./deps/pdcurses/win32/*.c" - } - - -- not our code, ignore POSIX usage warnings for now - warnings "Off" - - -- always build as static lib, as pdcurses doesn't export anything - kind "StaticLib" - - -- libtomcrypt - project "libtomcrypt" - language "C" - defines { "_LIB", "LTC_SOURCE", "LTC_NO_FAST", "LTC_NO_RSA_BLINDING", "LTM_DESC", "USE_LTM", "WIN32" } - - links { "libtommath" } - includedirs { "./deps/libtomcrypt/src/headers" } - includedirs { "./deps/libtommath" } - - files { "./deps/libtomcrypt/src/**.c" } - - -- seems like tab stuff can be omitted - removefiles { "./deps/libtomcrypt/src/**/*tab.c" } - - -- remove incorrect files - -- for some reason, they lack the necessary header files - -- i might have to open a pull request which includes them - removefiles - { - "./deps/libtomcrypt/src/pk/dh/dh_sys.c", - "./deps/libtomcrypt/src/hashes/sha2/sha224.c", - "./deps/libtomcrypt/src/hashes/sha2/sha384.c", - "./deps/libtomcrypt/src/encauth/ocb3/**.c", - } - - -- not our code, ignore POSIX usage warnings for now - warnings "Off" - - -- always build as static lib, as libtomcrypt doesn't export anything - kind "StaticLib" - - -- libtommath - project "libtommath" - language "C" - defines { "_LIB" } - includedirs { "./deps/libtommath" } - - files { "./deps/libtommath/*.c" } - - -- not our code, ignore POSIX usage warnings for now - warnings "Off" - - -- always build as static lib, as libtommath doesn't export anything - kind "StaticLib" - - -- protobuf - project "protobuf" - language "C++" - links { "zlib" } - defines { "_SCL_SECURE_NO_WARNINGS" } - includedirs - { - "./deps/zlib", - "./deps/protobuf/src", - } - - -- default protobuf sources - files { "./deps/protobuf/src/**.cc" } - - -- remove unnecessary sources - removefiles - { - "./deps/protobuf/src/**/*test.cc", - "./deps/protobuf/src/google/protobuf/*test*.cc", - - "./deps/protobuf/src/google/protobuf/testing/**.cc", - "./deps/protobuf/src/google/protobuf/compiler/**.cc", - - "./deps/protobuf/src/google/protobuf/arena_nc.cc", - "./deps/protobuf/src/google/protobuf/util/internal/error_listener.cc", - "./deps/protobuf/src/google/protobuf/stubs/atomicops_internals_x86_gcc.cc", - } - - -- not our code, ignore POSIX usage warnings for now - warnings "Off" - - -- always build as static lib, as we include our custom classes and therefore can't perform shared linking - kind "StaticLib" - -rule "ProtobufCompiler" - display "Protobuf compiler" - location "./build" - fileExtension ".proto" - buildmessage "Compiling %(Identity) with protoc..." - buildcommands { - '@echo off', - 'path "$(SolutionDir)\\..\\tools"', - 'if not exist "$(ProjectDir)\\src\\proto" mkdir "$(ProjectDir)\\src\\proto"', - 'protoc --error_format=msvs -I=%(RelativeDir) --cpp_out=src\\proto %(Identity)', - } - buildoutputs { - '$(ProjectDir)\\src\\proto\\%(Filename).pb.cc', - '$(ProjectDir)\\src\\proto\\%(Filename).pb.h', - } +-- Option to allow copying the DLL file to a custom folder after build +newoption { + trigger = "copy-to", + description = "Optional, copy the DLL to a custom folder after build, define the path here if wanted.", + value = "PATH" +} + +newoption { + trigger = "no-new-structure", + description = "Do not use new virtual path structure (separating headers and source files)." +} + +newaction { + trigger = "version", + description = "Returns the version string for the current commit of the source code.", + onWorkspace = function(wks) + -- get revision number via git + local proc = assert(io.popen("git rev-list --count HEAD", "r")) + local revNumber = assert(proc:read('*a')):gsub("%s+", "") + proc:close() + + print(revNumber) + os.exit(0) + end +} + +newaction { + trigger = "generate-buildinfo", + description = "Sets up build information file like version.h.", + onWorkspace = function(wks) + -- get revision number via git + local proc = assert(io.popen("git rev-list --count HEAD", "r")) + local revNumber = assert(proc:read('*a')):gsub("%s+", "") + proc:close() + + -- get whether this is a clean revision (no uncommitted changes) + local proc = assert(io.popen("git status --porcelain", "r")) + local revClean = 1 + local revCleanSuffix = "" + if assert(proc:read('*a')) ~= "" then + revClean = 0 + revCleanSuffix = " (unclean)" + end + proc:close() + + -- get old version number from version.hpp if any + local oldRevNumber = "(none)" + local oldRevClean = 1 + local oldRevCleanSuffix = "" + local oldVersionHeader = io.open(wks.location .. "/src/version.hpp", "r") + if oldVersionHeader ~=nil then + local oldVersionHeaderContent = assert(oldVersionHeader:read('*a')) + oldRevNumber = string.match(oldVersionHeaderContent, "#define REVISION (%d+)") + if oldRevNumber == nil then + -- old version.hpp format? + oldRevNumber = "(none)" + end + oldRevClean = string.match(oldVersionHeaderContent, "#define REVISION_CLEAN (%d+)") + if oldRevClean == nil then + -- old version.hpp format? + oldRevClean = 1 + elseif oldRevClean ~= "1" then + oldRevClean = 0 + else + oldRevClean = 1 + end + end + if oldRevClean == 0 then + oldRevCleanSuffix = " (unclean)" + end + + -- generate version.hpp with a revision number if not equal + if oldRevNumber ~= revNumber or oldRevClean ~= revClean then + print ("Update " .. oldRevNumber .. oldRevCleanSuffix .. " -> " .. revNumber .. revCleanSuffix) + local versionHeader = assert(io.open(wks.location .. "/src/version.hpp", "w")) + versionHeader:write("/*\n") + versionHeader:write(" * Automatically generated by premake5.\n") + versionHeader:write(" * Do not touch, you fucking moron!\n") + versionHeader:write(" */\n") + versionHeader:write("\n") + versionHeader:write("#define REVISION " .. revNumber .. "\n") + versionHeader:write("#define REVISION_CLEAN " .. revClean .. "\n") + versionHeader:close() + end + end +} + +workspace "iw4x" + location "./build" + objdir "%{wks.location}/obj" + targetdir "%{wks.location}/bin/%{cfg.buildcfg}" + configurations { "Debug", "DebugStatic", "Release", "ReleaseStatic" } + architecture "x32" + platforms "x86" + + -- VS 2015 toolset only + toolset "msc-140" + + configuration "windows" + defines { "_WINDOWS", "WIN32" } + + configuration "Release*" + defines { "NDEBUG" } + flags { "MultiProcessorCompile", "Symbols", "LinkTimeOptimization", "No64BitChecks" } + optimize "Full" + + configuration "Debug*" + defines { "DEBUG", "_DEBUG" } + flags { "MultiProcessorCompile", "Symbols", "No64BitChecks" } + optimize "Debug" + + configuration "*Static" + flags { "StaticRuntime" } + + project "iw4x" + kind "SharedLib" + language "C++" + files { + "./src/**.rc", + "./src/**.hpp", + "./src/**.cpp", + "./src/**.proto", + } + includedirs { + "%{prj.location}/src", + "./src" + } + resincludedirs { + "$(ProjectDir)src" -- fix for VS IDE + } + + -- Pre-compiled header + pchheader "STDInclude.hpp" -- must be exactly same as used in #include directives + pchsource "src/STDInclude.cpp" -- real path + buildoptions { "/Zm200" } + filter "files:**.pb.*" + flags { + "NoPCH", + } + buildoptions { + "/wd4100", -- "Unused formal parameter" + "/wd4389", -- "Signed/Unsigned mismatch" + "/wd6011", -- "Dereferencing NULL pointer" + "/wd4125", -- "Decimal digit terminates octal escape sequence" + } + defines { + "_SCL_SECURE_NO_WARNINGS", + } + filter {} + + -- Dependency libraries + links { "zlib", "fmt", "json11", "pdcurses", "libtomcrypt", "libtommath", "protobuf", "mongoose" } + includedirs + { + "./deps/fmt", + "./deps/zlib", + "./deps/json11", + "./deps/pdcurses", + "./deps/mongoose", + "./deps/libtomcrypt/src/headers", + "./deps/libtommath", + "./deps/protobuf/src", + "./deps/Wink-Signals", + } + + -- fix vpaths for protobuf sources + vpaths + { + ["*"] = { "./src/**" }, + ["Proto/Generated"] = { "**.pb.*" }, -- meh. + } + + -- Virtual paths + if not _OPTIONS["no-new-structure"] then + vpaths + { + ["Headers/*"] = { "./src/**.hpp" }, + ["Sources/*"] = { "./src/**.cpp" }, + ["Resource/*"] = { "./src/**.rc" }, + ["Proto/Definitions/*"] = { "./src/Proto/**.proto" }, + ["Proto/Generated/*"] = { "**.pb.*" }, -- meh. + } + end + + vpaths { + ["Docs/*"] = { "**.txt","**.md" }, + } + + -- Pre-build + prebuildcommands + { + "cd %{_MAIN_SCRIPT_DIR}", + "tools\\premake5 generate-buildinfo" + } + + -- Post-build + if _OPTIONS["copy-to"] then + saneCopyToPath = string.gsub(_OPTIONS["copy-to"] .. "\\", "\\\\", "\\") + postbuildcommands { + "copy /y \"$(TargetDir)*.dll\" \"" .. saneCopyToPath .. "\"" + } + end + + -- Specific configurations + flags { "UndefinedIdentifiers", "ExtraWarnings" } + + configuration "Release*" + flags { "FatalCompileWarnings" } + configuration {} + + -- Generate source code from protobuf definitions + rules { "ProtobufCompiler" } + + -- Workaround: Consume protobuf generated source files + matches = os.matchfiles(path.join("src/Proto/**.proto")) + for i, srcPath in ipairs(matches) do + basename = path.getbasename(srcPath) + files + { + string.format("%%{prj.location}/src/proto/%s.pb.h", basename), + string.format("%%{prj.location}/src/proto/%s.pb.cc", basename), + } + end + includedirs { + "%{prj.location}/src/proto" + } + + group "External dependencies" + + -- zlib + project "zlib" + language "C" + defines { "ZLIB_DLL", "_CRT_SECURE_NO_DEPRECATE" } + + files + { + "./deps/zlib/*.h", + "./deps/zlib/*.c" + } + + -- not our code, ignore POSIX usage warnings for now + warnings "Off" + + kind "SharedLib" + configuration "*Static" + kind "StaticLib" + removedefines { "ZLIB_DLL" } + + -- json11 + project "json11" + language "C++" + + files + { + "./deps/json11/*.cpp", + "./deps/json11/*.hpp" + } + + -- remove dropbox's testing code + removefiles { "./deps/json11/test.cpp" } + + -- not our code, ignore POSIX usage warnings for now + warnings "Off" + + -- always build as static lib, as json11 doesn't export anything + kind "StaticLib" + + -- fmt + project "fmt" + language "C++" + + includedirs { "./deps/fmt" } + files + { + "./deps/fmt/fmt/*.cc", + "./deps/fmt/fmt/*.h" + } + + -- not our code, ignore POSIX usage warnings for now + warnings "Off" + + -- always build as static lib, as fmt doesn't export anything + kind "StaticLib" + + -- mongoose + project "mongoose" + language "C" + + files + { + "./deps/mongoose/*.c", + "./deps/mongoose/*.h" + } + + -- not our code, ignore POSIX usage warnings for now + warnings "Off" + + -- always build as static lib, as mongoose doesn't export anything + kind "StaticLib" + + + -- pdcurses + project "pdcurses" + language "C" + includedirs { "./deps/pdcurses/" } + + files + { + "./deps/pdcurses/pdcurses/*.c", + "./deps/pdcurses/win32/*.c" + } + + -- not our code, ignore POSIX usage warnings for now + warnings "Off" + + -- always build as static lib, as pdcurses doesn't export anything + kind "StaticLib" + + -- libtomcrypt + project "libtomcrypt" + language "C" + defines { "_LIB", "LTC_SOURCE", "LTC_NO_FAST", "LTC_NO_RSA_BLINDING", "LTM_DESC", "USE_LTM", "WIN32" } + + links { "libtommath" } + includedirs { "./deps/libtomcrypt/src/headers" } + includedirs { "./deps/libtommath" } + + files { "./deps/libtomcrypt/src/**.c" } + + -- seems like tab stuff can be omitted + removefiles { "./deps/libtomcrypt/src/**/*tab.c" } + + -- remove incorrect files + -- for some reason, they lack the necessary header files + -- i might have to open a pull request which includes them + removefiles + { + "./deps/libtomcrypt/src/pk/dh/dh_sys.c", + "./deps/libtomcrypt/src/hashes/sha2/sha224.c", + "./deps/libtomcrypt/src/hashes/sha2/sha384.c", + "./deps/libtomcrypt/src/encauth/ocb3/**.c", + } + + -- not our code, ignore POSIX usage warnings for now + warnings "Off" + + -- always build as static lib, as libtomcrypt doesn't export anything + kind "StaticLib" + + -- libtommath + project "libtommath" + language "C" + defines { "_LIB" } + includedirs { "./deps/libtommath" } + + files { "./deps/libtommath/*.c" } + + -- not our code, ignore POSIX usage warnings for now + warnings "Off" + + -- always build as static lib, as libtommath doesn't export anything + kind "StaticLib" + + -- protobuf + project "protobuf" + language "C++" + links { "zlib" } + defines { "_SCL_SECURE_NO_WARNINGS" } + includedirs + { + "./deps/zlib", + "./deps/protobuf/src", + } + + -- default protobuf sources + files { "./deps/protobuf/src/**.cc" } + + -- remove unnecessary sources + removefiles + { + "./deps/protobuf/src/**/*test.cc", + "./deps/protobuf/src/google/protobuf/*test*.cc", + + "./deps/protobuf/src/google/protobuf/testing/**.cc", + "./deps/protobuf/src/google/protobuf/compiler/**.cc", + + "./deps/protobuf/src/google/protobuf/arena_nc.cc", + "./deps/protobuf/src/google/protobuf/util/internal/error_listener.cc", + "./deps/protobuf/src/google/protobuf/stubs/atomicops_internals_x86_gcc.cc", + } + + -- not our code, ignore POSIX usage warnings for now + warnings "Off" + + -- always build as static lib, as we include our custom classes and therefore can't perform shared linking + kind "StaticLib" + +rule "ProtobufCompiler" + display "Protobuf compiler" + location "./build" + fileExtension ".proto" + buildmessage "Compiling %(Identity) with protoc..." + buildcommands { + '@echo off', + 'path "$(SolutionDir)\\..\\tools"', + 'if not exist "$(ProjectDir)\\src\\proto" mkdir "$(ProjectDir)\\src\\proto"', + 'protoc --error_format=msvs -I=%(RelativeDir) --cpp_out=src\\proto %(Identity)', + } + buildoutputs { + '$(ProjectDir)\\src\\proto\\%(Filename).pb.cc', + '$(ProjectDir)\\src\\proto\\%(Filename).pb.h', + } diff --git a/src/Components/Modules/AntiCheat.cpp b/src/Components/Modules/AntiCheat.cpp index 56e2ef97..3c824204 100644 --- a/src/Components/Modules/AntiCheat.cpp +++ b/src/Components/Modules/AntiCheat.cpp @@ -1,233 +1,233 @@ -#include "STDInclude.hpp" - -namespace Components -{ - int AntiCheat::LastCheck; - std::string AntiCheat::Hash; - Utils::Hook AntiCheat::LoadLibHook[4]; - Utils::Hook AntiCheat::VirtualProtectHook; - - // This function does nothing, it only adds the two passed variables and returns the value - // The only important thing it does is to clean the first parameter, and then return - // By returning, the crash procedure will be called, as it hasn't been cleaned from the stack - void __declspec(naked) AntiCheat::NullSub() - { - __asm - { - push ebp - push ecx - mov ebp, esp - - xor eax, eax - mov eax, [ebp + 8h] - mov ecx, [ebp + 0Ch] - add eax, ecx - - pop ecx - pop ebp - retn 4 - } - } - - void __declspec(naked) AntiCheat::CrashClient() - { - static uint8_t crashProcedure[] = - { - // Variable space - 0xDC, 0xC1, 0xDC, 0x05, - - // Uninstall minidump handler - // This doesn't work anymore, due to the SetUnhandledExceptionFilter hook, but that's not important - //0xB8, 0x63, 0xE7, 0x2F, 0x00, // mov eax, 2FE763h - //0x05, 0xAD, 0xAD, 0x3C, 0x00, // add eax, 3CADADh - //0x6A, 0x58, // push 88 - //0x8B, 0x80, 0xEA, 0x01, 0x00, 0x00, // mov eax, [eax + 1EAh] - //0xFF, 0x10, // call dword ptr [eax] - - // Crash me. - 0xB8, 0x4F, 0x91, 0x27, 0x00, // mov eax, 27914Fh - 0x05, 0xDD, 0x28, 0x1A, 0x00, // add eax, 1A28DDh - 0x80, 0x00, 0x68, // add byte ptr [eax], 68h - 0xC3, // retn - - // Random stuff - 0xBE, 0xFF, 0xC2, 0xF4, 0x3A, - }; - - __asm - { - // This does absolutely nothing :P - xor eax, eax - mov ebx, [esp + 4h] - shl ebx, 4h - setz bl - - // Push the fake var onto the stack - push ebx - - // Save the address to our crash procedure - mov eax, offset crashProcedure - push eax - - // Unprotect the .text segment - push eax - push 40h - push 2D5FFFh - push 401001h - call VirtualProtect - - // Increment to our crash procedure - // Skip variable space - add dword ptr [esp], 4h - - // This basically removes the pushed ebx value from the stack, so returning results in a call to the procedure - jmp AntiCheat::NullSub - } - } - - // This has to be called when doing .text changes during runtime - void AntiCheat::EmptyHash() - { - AntiCheat::LastCheck = 0; - AntiCheat::Hash.clear(); - } - - void AntiCheat::InitLoadLibHook() - { - static uint8_t loadLibStub[] = { 0x33, 0xC0, 0xC2, 0x04, 0x00 }; // xor eax, eax; retn 04h - static uint8_t loadLibExStub[] = { 0x33, 0xC0, 0xC2, 0x0C, 0x00 }; // xor eax, eax; retn 0Ch - - static uint8_t kernel32Str[] = { 0xB4, 0x9A, 0x8D, 0xB1, 0x9A, 0x93, 0xCC, 0xCD, 0xD1, 0x9B, 0x93, 0x93 }; // KerNel32.dll - static uint8_t loadLibAStr[] = { 0xB3, 0x90, 0x9E, 0x9B, 0xB3, 0x96, 0x9D, 0x8D, 0x9E, 0x8D, 0x86, 0xBE }; // LoadLibraryA - static uint8_t loadLibWStr[] = { 0xB3, 0x90, 0x9E, 0x9B, 0xB3, 0x96, 0x9D, 0x8D, 0x9E, 0x8D, 0x86, 0xA8 }; // LoadLibraryW - - HMODULE kernel32 = GetModuleHandleA(Utils::XORString(std::string(reinterpret_cast(kernel32Str), sizeof kernel32Str), -1).data()); - FARPROC loadLibA = GetProcAddress(kernel32, Utils::XORString(std::string(reinterpret_cast(loadLibAStr), sizeof loadLibAStr), -1).data()); - FARPROC loadLibW = GetProcAddress(kernel32, Utils::XORString(std::string(reinterpret_cast(loadLibWStr), sizeof loadLibWStr), -1).data()); - - AntiCheat::LoadLibHook[0].Initialize(loadLibA, loadLibStub, HOOK_JUMP); - AntiCheat::LoadLibHook[1].Initialize(loadLibW, loadLibStub, HOOK_JUMP); - //AntiCheat::LoadLibHook[2].Initialize(LoadLibraryExA, loadLibExStub, HOOK_JUMP); - //AntiCheat::LoadLibHook[3].Initialize(LoadLibraryExW, loadLibExStub, HOOK_JUMP); - } - - void AntiCheat::PerformCheck() - { - // Hash .text segment - // Add 1 to each value, so searching in memory doesn't reveal anything - size_t textSize = 0x2D6001; - uint8_t* textBase = reinterpret_cast(0x401001); - std::string hash = Utils::Cryptography::SHA512::Compute(textBase - 1, textSize - 1, false); - - // Set the hash, if none is set - if (AntiCheat::Hash.empty()) - { - AntiCheat::Hash = hash; - } - // Crash if the hashes don't match - else if (AntiCheat::Hash != hash) - { - AntiCheat::CrashClient(); - } - } - - void AntiCheat::Frame() - { - // Perform check only every 30 seconds - if (AntiCheat::LastCheck && (Game::Sys_Milliseconds() - AntiCheat::LastCheck) < 1000 * 30) return; - AntiCheat::LastCheck = Game::Sys_Milliseconds(); - - AntiCheat::PerformCheck(); - } - - void AntiCheat::UninstallLibHook() - { - for (int i = 0; i < ARRAYSIZE(AntiCheat::LoadLibHook); ++i) - { - AntiCheat::LoadLibHook[i].Uninstall(); - } - } - - void AntiCheat::InstallLibHook() - { - AntiCheat::LoadLibHook[0].Install(); - AntiCheat::LoadLibHook[1].Install(); - //AntiCheat::LoadLibHook[2].Install(); - //AntiCheat::LoadLibHook[3].Install(); - } - - void AntiCheat::PatchWinAPI() - { - AntiCheat::UninstallLibHook(); - - // Initialize directx :P - Utils::Hook::Call(0x5078C0)(); - - AntiCheat::InstallLibHook(); - } - - void AntiCheat::SoundInitStub() - { - AntiCheat::UninstallLibHook(); - - Game::SND_InitDriver(); - - AntiCheat::InstallLibHook(); - } - -// BOOL WINAPI AntiCheat::VirtualProtectStub(LPVOID lpAddress, SIZE_T dwSize, DWORD flNewProtect, PDWORD lpflOldProtect) -// { -// AntiCheat::VirtualProtectHook.Uninstall(false); -// -// if (flNewProtect == PAGE_WRITECOPY || flNewProtect == PAGE_READWRITE || flNewProtect == PAGE_EXECUTE_READWRITE || flNewProtect == PAGE_WRITECOMBINE) -// { -// DWORD addr = (DWORD)lpAddress; -// DWORD start = 0x401000; -// DWORD end = start + 0x2D6000; -// -// if (addr > start && addr < end) -// { -// OutputDebugStringA(Utils::VA("Write access to address %X", lpAddress)); -// } -// } -// -// BOOL retVal = VirtualProtect(lpAddress, dwSize, flNewProtect, lpflOldProtect); -// AntiCheat::VirtualProtectHook.Install(false); -// return retVal; -// } - - AntiCheat::AntiCheat() - { - // This is required for debugging...in release mode :P - //AntiCheat::VirtualProtectHook.Initialize(VirtualProtect, VirtualProtectStub, HOOK_JUMP); - //AntiCheat::VirtualProtectHook.Install(true, true); - - AntiCheat::EmptyHash(); - -#ifdef DEBUG - Command::Add("penis", [] (Command::Params) - { - AntiCheat::CrashClient(); - }); -#else - Utils::Hook(0x60BE8E, AntiCheat::SoundInitStub, HOOK_CALL).Install()->Quick(); - Utils::Hook(0x418204, AntiCheat::SoundInitStub, HOOK_CALL).Install()->Quick(); - Utils::Hook(0x507BD5, AntiCheat::PatchWinAPI, HOOK_CALL).Install()->Quick(); - QuickPatch::OnFrame(AntiCheat::Frame); - - // TODO: Probably move that :P - AntiCheat::InitLoadLibHook(); -#endif - } - - AntiCheat::~AntiCheat() - { - AntiCheat::EmptyHash(); - - AntiCheat::VirtualProtectHook.Uninstall(false); - for (int i = 0; i < ARRAYSIZE(AntiCheat::LoadLibHook); ++i) - { - AntiCheat::LoadLibHook[i].Uninstall(); - } - } -} +#include "STDInclude.hpp" + +namespace Components +{ + int AntiCheat::LastCheck; + std::string AntiCheat::Hash; + Utils::Hook AntiCheat::LoadLibHook[4]; + Utils::Hook AntiCheat::VirtualProtectHook; + + // This function does nothing, it only adds the two passed variables and returns the value + // The only important thing it does is to clean the first parameter, and then return + // By returning, the crash procedure will be called, as it hasn't been cleaned from the stack + void __declspec(naked) AntiCheat::NullSub() + { + __asm + { + push ebp + push ecx + mov ebp, esp + + xor eax, eax + mov eax, [ebp + 8h] + mov ecx, [ebp + 0Ch] + add eax, ecx + + pop ecx + pop ebp + retn 4 + } + } + + void __declspec(naked) AntiCheat::CrashClient() + { + static uint8_t crashProcedure[] = + { + // Variable space + 0xDC, 0xC1, 0xDC, 0x05, + + // Uninstall minidump handler + // This doesn't work anymore, due to the SetUnhandledExceptionFilter hook, but that's not important + //0xB8, 0x63, 0xE7, 0x2F, 0x00, // mov eax, 2FE763h + //0x05, 0xAD, 0xAD, 0x3C, 0x00, // add eax, 3CADADh + //0x6A, 0x58, // push 88 + //0x8B, 0x80, 0xEA, 0x01, 0x00, 0x00, // mov eax, [eax + 1EAh] + //0xFF, 0x10, // call dword ptr [eax] + + // Crash me. + 0xB8, 0x4F, 0x91, 0x27, 0x00, // mov eax, 27914Fh + 0x05, 0xDD, 0x28, 0x1A, 0x00, // add eax, 1A28DDh + 0x80, 0x00, 0x68, // add byte ptr [eax], 68h + 0xC3, // retn + + // Random stuff + 0xBE, 0xFF, 0xC2, 0xF4, 0x3A, + }; + + __asm + { + // This does absolutely nothing :P + xor eax, eax + mov ebx, [esp + 4h] + shl ebx, 4h + setz bl + + // Push the fake var onto the stack + push ebx + + // Save the address to our crash procedure + mov eax, offset crashProcedure + push eax + + // Unprotect the .text segment + push eax + push 40h + push 2D5FFFh + push 401001h + call VirtualProtect + + // Increment to our crash procedure + // Skip variable space + add dword ptr [esp], 4h + + // This basically removes the pushed ebx value from the stack, so returning results in a call to the procedure + jmp AntiCheat::NullSub + } + } + + // This has to be called when doing .text changes during runtime + void AntiCheat::EmptyHash() + { + AntiCheat::LastCheck = 0; + AntiCheat::Hash.clear(); + } + + void AntiCheat::InitLoadLibHook() + { + static uint8_t loadLibStub[] = { 0x33, 0xC0, 0xC2, 0x04, 0x00 }; // xor eax, eax; retn 04h + static uint8_t loadLibExStub[] = { 0x33, 0xC0, 0xC2, 0x0C, 0x00 }; // xor eax, eax; retn 0Ch + + static uint8_t kernel32Str[] = { 0xB4, 0x9A, 0x8D, 0xB1, 0x9A, 0x93, 0xCC, 0xCD, 0xD1, 0x9B, 0x93, 0x93 }; // KerNel32.dll + static uint8_t loadLibAStr[] = { 0xB3, 0x90, 0x9E, 0x9B, 0xB3, 0x96, 0x9D, 0x8D, 0x9E, 0x8D, 0x86, 0xBE }; // LoadLibraryA + static uint8_t loadLibWStr[] = { 0xB3, 0x90, 0x9E, 0x9B, 0xB3, 0x96, 0x9D, 0x8D, 0x9E, 0x8D, 0x86, 0xA8 }; // LoadLibraryW + + HMODULE kernel32 = GetModuleHandleA(Utils::String::XORString(std::string(reinterpret_cast(kernel32Str), sizeof kernel32Str), -1).data()); + FARPROC loadLibA = GetProcAddress(kernel32, Utils::String::XORString(std::string(reinterpret_cast(loadLibAStr), sizeof loadLibAStr), -1).data()); + FARPROC loadLibW = GetProcAddress(kernel32, Utils::String::XORString(std::string(reinterpret_cast(loadLibWStr), sizeof loadLibWStr), -1).data()); + + AntiCheat::LoadLibHook[0].Initialize(loadLibA, loadLibStub, HOOK_JUMP); + AntiCheat::LoadLibHook[1].Initialize(loadLibW, loadLibStub, HOOK_JUMP); + //AntiCheat::LoadLibHook[2].Initialize(LoadLibraryExA, loadLibExStub, HOOK_JUMP); + //AntiCheat::LoadLibHook[3].Initialize(LoadLibraryExW, loadLibExStub, HOOK_JUMP); + } + + void AntiCheat::PerformCheck() + { + // Hash .text segment + // Add 1 to each value, so searching in memory doesn't reveal anything + size_t textSize = 0x2D6001; + uint8_t* textBase = reinterpret_cast(0x401001); + std::string hash = Utils::Cryptography::SHA512::Compute(textBase - 1, textSize - 1, false); + + // Set the hash, if none is set + if (AntiCheat::Hash.empty()) + { + AntiCheat::Hash = hash; + } + // Crash if the hashes don't match + else if (AntiCheat::Hash != hash) + { + AntiCheat::CrashClient(); + } + } + + void AntiCheat::Frame() + { + // Perform check only every 30 seconds + if (AntiCheat::LastCheck && (Game::Sys_Milliseconds() - AntiCheat::LastCheck) < 1000 * 30) return; + AntiCheat::LastCheck = Game::Sys_Milliseconds(); + + AntiCheat::PerformCheck(); + } + + void AntiCheat::UninstallLibHook() + { + for (int i = 0; i < ARRAYSIZE(AntiCheat::LoadLibHook); ++i) + { + AntiCheat::LoadLibHook[i].Uninstall(); + } + } + + void AntiCheat::InstallLibHook() + { + AntiCheat::LoadLibHook[0].Install(); + AntiCheat::LoadLibHook[1].Install(); + //AntiCheat::LoadLibHook[2].Install(); + //AntiCheat::LoadLibHook[3].Install(); + } + + void AntiCheat::PatchWinAPI() + { + AntiCheat::UninstallLibHook(); + + // Initialize directx :P + Utils::Hook::Call(0x5078C0)(); + + AntiCheat::InstallLibHook(); + } + + void AntiCheat::SoundInitStub() + { + AntiCheat::UninstallLibHook(); + + Game::SND_InitDriver(); + + AntiCheat::InstallLibHook(); + } + +// BOOL WINAPI AntiCheat::VirtualProtectStub(LPVOID lpAddress, SIZE_T dwSize, DWORD flNewProtect, PDWORD lpflOldProtect) +// { +// AntiCheat::VirtualProtectHook.Uninstall(false); +// +// if (flNewProtect == PAGE_WRITECOPY || flNewProtect == PAGE_READWRITE || flNewProtect == PAGE_EXECUTE_READWRITE || flNewProtect == PAGE_WRITECOMBINE) +// { +// DWORD addr = (DWORD)lpAddress; +// DWORD start = 0x401000; +// DWORD end = start + 0x2D6000; +// +// if (addr > start && addr < end) +// { +// OutputDebugStringA(Utils::VA("Write access to address %X", lpAddress)); +// } +// } +// +// BOOL retVal = VirtualProtect(lpAddress, dwSize, flNewProtect, lpflOldProtect); +// AntiCheat::VirtualProtectHook.Install(false); +// return retVal; +// } + + AntiCheat::AntiCheat() + { + // This is required for debugging...in release mode :P + //AntiCheat::VirtualProtectHook.Initialize(VirtualProtect, VirtualProtectStub, HOOK_JUMP); + //AntiCheat::VirtualProtectHook.Install(true, true); + + AntiCheat::EmptyHash(); + +#ifdef DEBUG + Command::Add("penis", [] (Command::Params) + { + AntiCheat::CrashClient(); + }); +#else + Utils::Hook(0x60BE8E, AntiCheat::SoundInitStub, HOOK_CALL).Install()->Quick(); + Utils::Hook(0x418204, AntiCheat::SoundInitStub, HOOK_CALL).Install()->Quick(); + Utils::Hook(0x507BD5, AntiCheat::PatchWinAPI, HOOK_CALL).Install()->Quick(); + QuickPatch::OnFrame(AntiCheat::Frame); + + // TODO: Probably move that :P + AntiCheat::InitLoadLibHook(); +#endif + } + + AntiCheat::~AntiCheat() + { + AntiCheat::EmptyHash(); + + AntiCheat::VirtualProtectHook.Uninstall(false); + for (int i = 0; i < ARRAYSIZE(AntiCheat::LoadLibHook); ++i) + { + AntiCheat::LoadLibHook[i].Uninstall(); + } + } +} diff --git a/src/Components/Modules/AssetInterfaces/IGfxImage.cpp b/src/Components/Modules/AssetInterfaces/IGfxImage.cpp index b6db58b8..e43e9453 100644 --- a/src/Components/Modules/AssetInterfaces/IGfxImage.cpp +++ b/src/Components/Modules/AssetInterfaces/IGfxImage.cpp @@ -1,124 +1,124 @@ -#include - -namespace Assets -{ - void IGfxImage::Load(Game::XAssetHeader* header, std::string name, Components::ZoneBuilder::Zone* builder) - { - Game::GfxImage* image = Game::DB_FindXAssetHeader(Game::XAssetType::ASSET_TYPE_IMAGE, name.data()).image; - if (image) return; // TODO: Check for default? - - image = builder->GetAllocator()->AllocateArray(); - if (!image) - { - Components::Logger::Error("Failed to allocate GfxImage structure!"); - return; - } - - image->name = builder->GetAllocator()->DuplicateString(name); - image->semantic = 2; - image->category = 0; - image->cardMemory = 0; - - image->texture = builder->GetAllocator()->AllocateArray(); - if (!image->texture) - { - Components::Logger::Error("Failed to allocate GfxImageLoadDef structure!"); - return; - } - - Components::FileSystem::File iwi(Utils::VA("images/%s.iwi", name.data())); - - if (!iwi.Exists()) - { - Components::Logger::Error("Loading image '%s' failed!", iwi.GetName().data()); - return; - } - - const Game::GfxImageFileHeader* iwiHeader = reinterpret_cast(iwi.GetBuffer().data()); - - image->mapType = 3; - image->dataLen1 = iwiHeader->fileSizeForPicmip[0] - 32; - image->dataLen2 = iwiHeader->fileSizeForPicmip[0] - 32; - - if (std::memcmp(iwiHeader->tag, "IWi", 3)) - { - Components::Logger::Error("Image is not a valid IWi!"); - return; - } - - std::memcpy(image->texture->dimensions, iwiHeader->dimensions, 6); - image->texture->flags = 0; - image->texture->mipLevels = 0; - - switch (iwiHeader->format) - { - case Game::IWI_COMPRESSION::IWI_ARGB: - { - image->texture->format = 21; - break; - } - - case Game::IWI_COMPRESSION::IWI_RGB8: - { - image->texture->format = 20; - break; - } - - case Game::IWI_COMPRESSION::IWI_DXT1: - { - image->texture->format = 0x31545844; - break; - } - - case Game::IWI_COMPRESSION::IWI_DXT3: - { - image->texture->format = 0x33545844; - break; - } - - case Game::IWI_COMPRESSION::IWI_DXT5: - { - image->texture->format = 0x35545844; - break; - } - } - - header->image = image; - } - - void IGfxImage::Save(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder) - { - Assert_Size(Game::GfxImage, 32); - - Utils::Stream* buffer = builder->GetBuffer(); - Game::GfxImage* asset = header.image; - Game::GfxImage* dest = buffer->Dest(); - buffer->Save(asset); - - buffer->PushBlock(Game::XFILE_BLOCK_VIRTUAL); - - if (asset->name) - { - buffer->SaveString(builder->GetAssetName(this->GetType(), asset->name)); - Utils::Stream::ClearPointer(&dest->name); - } - - buffer->PushBlock(Game::XFILE_BLOCK_TEMP); - - if (asset->texture) - { - buffer->Align(Utils::Stream::ALIGN_4); - - Game::GfxImageLoadDef* destTexture = buffer->Dest(); - buffer->Save(asset->texture, 16); - - // Zero the size! - destTexture->dataSize = 0; - - Utils::Stream::ClearPointer(&dest->texture); - } - - buffer->PopBlock(); - buffer->PopBlock(); - } -} +#include + +namespace Assets +{ + void IGfxImage::Load(Game::XAssetHeader* header, std::string name, Components::ZoneBuilder::Zone* builder) + { + Game::GfxImage* image = Game::DB_FindXAssetHeader(Game::XAssetType::ASSET_TYPE_IMAGE, name.data()).image; + if (image) return; // TODO: Check for default? + + image = builder->GetAllocator()->AllocateArray(); + if (!image) + { + Components::Logger::Error("Failed to allocate GfxImage structure!"); + return; + } + + image->name = builder->GetAllocator()->DuplicateString(name); + image->semantic = 2; + image->category = 0; + image->cardMemory = 0; + + image->texture = builder->GetAllocator()->AllocateArray(); + if (!image->texture) + { + Components::Logger::Error("Failed to allocate GfxImageLoadDef structure!"); + return; + } + + Components::FileSystem::File iwi(fmt::sprintf("images/%s.iwi", name.data())); + + if (!iwi.Exists()) + { + Components::Logger::Error("Loading image '%s' failed!", iwi.GetName().data()); + return; + } + + const Game::GfxImageFileHeader* iwiHeader = reinterpret_cast(iwi.GetBuffer().data()); + + image->mapType = 3; + image->dataLen1 = iwiHeader->fileSizeForPicmip[0] - 32; + image->dataLen2 = iwiHeader->fileSizeForPicmip[0] - 32; + + if (std::memcmp(iwiHeader->tag, "IWi", 3)) + { + Components::Logger::Error("Image is not a valid IWi!"); + return; + } + + std::memcpy(image->texture->dimensions, iwiHeader->dimensions, 6); + image->texture->flags = 0; + image->texture->mipLevels = 0; + + switch (iwiHeader->format) + { + case Game::IWI_COMPRESSION::IWI_ARGB: + { + image->texture->format = 21; + break; + } + + case Game::IWI_COMPRESSION::IWI_RGB8: + { + image->texture->format = 20; + break; + } + + case Game::IWI_COMPRESSION::IWI_DXT1: + { + image->texture->format = 0x31545844; + break; + } + + case Game::IWI_COMPRESSION::IWI_DXT3: + { + image->texture->format = 0x33545844; + break; + } + + case Game::IWI_COMPRESSION::IWI_DXT5: + { + image->texture->format = 0x35545844; + break; + } + } + + header->image = image; + } + + void IGfxImage::Save(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder) + { + Assert_Size(Game::GfxImage, 32); + + Utils::Stream* buffer = builder->GetBuffer(); + Game::GfxImage* asset = header.image; + Game::GfxImage* dest = buffer->Dest(); + buffer->Save(asset); + + buffer->PushBlock(Game::XFILE_BLOCK_VIRTUAL); + + if (asset->name) + { + buffer->SaveString(builder->GetAssetName(this->GetType(), asset->name)); + Utils::Stream::ClearPointer(&dest->name); + } + + buffer->PushBlock(Game::XFILE_BLOCK_TEMP); + + if (asset->texture) + { + buffer->Align(Utils::Stream::ALIGN_4); + + Game::GfxImageLoadDef* destTexture = buffer->Dest(); + buffer->Save(asset->texture, 16); + + // Zero the size! + destTexture->dataSize = 0; + + Utils::Stream::ClearPointer(&dest->texture); + } + + buffer->PopBlock(); + buffer->PopBlock(); + } +} diff --git a/src/Components/Modules/AssetInterfaces/IMaterial.cpp b/src/Components/Modules/AssetInterfaces/IMaterial.cpp index 60993acc..28e5a763 100644 --- a/src/Components/Modules/AssetInterfaces/IMaterial.cpp +++ b/src/Components/Modules/AssetInterfaces/IMaterial.cpp @@ -1,320 +1,320 @@ -#include - -namespace Assets -{ - void IMaterial::Load(Game::XAssetHeader* header, std::string name, Components::ZoneBuilder::Zone* builder) - { - Components::FileSystem::File materialInfo(Utils::VA("materials/%s.json", name.data())); - - if (!materialInfo.Exists()) return; - - std::string errors; - json11::Json infoData = json11::Json::parse(materialInfo.GetBuffer(), errors); - - if (!infoData.is_object()) - { - Components::Logger::Error("Failed to load material information for %s!", name.data()); - return; - } - - auto base = infoData["base"]; - - if (!base.is_string()) - { - Components::Logger::Error("No valid material base provided for %s!", name.data()); - return; - } - - Game::Material* baseMaterial = Game::DB_FindXAssetHeader(Game::XAssetType::ASSET_TYPE_MATERIAL, base.string_value().data()).material; - - if (!baseMaterial) // TODO: Maybe check if default asset? Maybe not? You could still want to use the default one as base!? - { - Components::Logger::Error("Basematerial '%s' not found for %s!", base.string_value().data(), name.data()); - return; - } - - Game::Material* material = builder->GetAllocator()->AllocateArray(); - - if (!material) - { - Components::Logger::Error("Failed to allocate material structure!"); - return; - } - - // Copy base material to our structure - std::memcpy(material, baseMaterial, sizeof(Game::Material)); - material->name = builder->GetAllocator()->DuplicateString(name); - - material->textureAtlasRowCount = 1; - material->textureAtlasColumnCount = 1; - - // Load animation frames - auto anims = infoData["anims"]; - if (anims.is_array()) - { - auto animCoords = anims.array_items(); - - if (animCoords.size() >= 2) - { - auto animCoordX = animCoords[0]; - auto animCoordY = animCoords[1]; - - if (animCoordX.is_number()) - { - material->textureAtlasColumnCount = static_cast(animCoordX.number_value()) & 0xFF; - } - - if (animCoordY.is_number()) - { - material->textureAtlasRowCount = static_cast(animCoordY.number_value()) & 0xFF; - } - } - } - - // Model surface textures are special, they need a special order and whatnot - bool replaceTexture = Utils::StartsWith(name, "mc/"); - if (replaceTexture) - { - Game::MaterialTextureDef* textureTable = builder->GetAllocator()->AllocateArray(baseMaterial->textureCount); - std::memcpy(textureTable, baseMaterial->textureTable, sizeof(Game::MaterialTextureDef) * baseMaterial->textureCount); - material->textureTable = textureTable; - material->textureCount = baseMaterial->textureCount; - } - - // Load referenced textures - auto textures = infoData["textures"]; - if (textures.is_array()) - { - std::vector textureList; - - for (auto texture : textures.array_items()) - { - if (!texture.is_array()) continue; - if (textureList.size() >= 0xFF) break; - - auto textureInfo = texture.array_items(); - if (textureInfo.size() < 2) continue; - - auto map = textureInfo[0]; - auto image = textureInfo[1]; - if (!map.is_string() || !image.is_string()) continue; - - Game::MaterialTextureDef textureDef; - - textureDef.semantic = 0; // No water image - textureDef.sampleState = -30; - textureDef.nameEnd = map.string_value().back(); - textureDef.nameStart = map.string_value().front(); - textureDef.nameHash = Game::R_HashString(map.string_value().data()); - - textureDef.info.image = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_IMAGE, image.string_value(), builder).image; - - if (replaceTexture) - { - bool applied = false; - - for (char i = 0; i < baseMaterial->textureCount; ++i) - { - if (material->textureTable[i].nameHash == textureDef.nameHash) - { - applied = true; - material->textureTable[i].info.image = textureDef.info.image; - break; - } - } - - if (!applied) - { - Components::Logger::Error(0, "Unable to find texture for map '%s' in %s!", map.string_value().data(), baseMaterial->name); - } - } - else - { - textureList.push_back(textureDef); - } - } - - if(!replaceTexture) - { - if (!textureList.empty()) - { - Game::MaterialTextureDef* textureTable = builder->GetAllocator()->AllocateArray(textureList.size()); - - if (!textureTable) - { - Components::Logger::Error("Failed to allocate texture table!"); - return; - } - - std::memcpy(textureTable, textureList.data(), sizeof(Game::MaterialTextureDef) * textureList.size()); - - material->textureTable = textureTable; - } - else - { - material->textureTable = 0; - } - - material->textureCount = static_cast(textureList.size()) & 0xFF; - } - } - - header->material = material; - } - - void IMaterial::Mark(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder) - { - Game::Material* asset = header.material; - - if (asset->techniqueSet) - { - builder->LoadAsset(Game::XAssetType::ASSET_TYPE_TECHSET, asset->techniqueSet->name); - } - - if (asset->textureTable) - { - for (char i = 0; i < asset->textureCount; ++i) - { - if (asset->textureTable[i].info.image) - { - if (asset->textureTable[i].semantic == SEMANTIC_WATER_MAP) - { - if (asset->textureTable[i].info.water->image) - { - builder->LoadAsset(Game::XAssetType::ASSET_TYPE_IMAGE, asset->textureTable[i].info.water->image->name); - } - } - else - { - builder->LoadAsset(Game::XAssetType::ASSET_TYPE_IMAGE, asset->textureTable[i].info.image->name); - } - } - } - } - } - - void IMaterial::Save(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder) - { - Assert_Size(Game::Material, 96); - - Utils::Stream* buffer = builder->GetBuffer(); - Game::Material* asset = header.material; - Game::Material* dest = buffer->Dest(); - buffer->Save(asset); - - buffer->PushBlock(Game::XFILE_BLOCK_VIRTUAL); - - if (asset->name) - { - buffer->SaveString(builder->GetAssetName(this->GetType(), asset->name)); - Utils::Stream::ClearPointer(&dest->name); - } - - if (asset->techniqueSet) - { - dest->techniqueSet = builder->RequireAsset(Game::XAssetType::ASSET_TYPE_TECHSET, asset->techniqueSet->name).materialTechset; - } - - if (asset->textureTable) - { - Assert_Size(Game::MaterialTextureDef, 12); - - // Pointer/Offset insertion is untested, but it worked in T6, so I think it's fine - if (builder->HasPointer(asset->textureTable)) - { - dest->textureTable = builder->GetPointer(asset->textureTable); - } - else - { - buffer->Align(Utils::Stream::ALIGN_4); - builder->StorePointer(asset->textureTable); - - Game::MaterialTextureDef* destTextureTable = buffer->Dest(); - buffer->SaveArray(asset->textureTable, asset->textureCount); - - for (char i = 0; i < asset->textureCount; ++i) - { - Game::MaterialTextureDef* destTextureDef = &destTextureTable[i]; - Game::MaterialTextureDef* textureDef = &asset->textureTable[i]; - - if (textureDef->semantic == SEMANTIC_WATER_MAP) - { - Assert_Size(Game::water_t, 68); - - Game::water_t* destWater = buffer->Dest(); - Game::water_t* water = textureDef->info.water; - - if (water) - { - buffer->Align(Utils::Stream::ALIGN_4); - buffer->Save(water); - Utils::Stream::ClearPointer(&destTextureDef->info.water); - - // Save_water_t - if (water->H0X) - { - buffer->Align(Utils::Stream::ALIGN_4); - buffer->Save(water->H0X, 8, water->M * water->N); - Utils::Stream::ClearPointer(&destWater->H0X); - } - - if (water->H0Y) - { - buffer->Align(Utils::Stream::ALIGN_4); - buffer->Save(water->H0Y, 4, water->M * water->N); - Utils::Stream::ClearPointer(&destWater->H0Y); - } - - if (water->image) - { - destWater->image = builder->RequireAsset(Game::XAssetType::ASSET_TYPE_IMAGE, water->image->name).image; - } - } - } - else if (textureDef->info.image) - { - destTextureDef->info.image = builder->RequireAsset(Game::XAssetType::ASSET_TYPE_IMAGE, textureDef->info.image->name).image; - } - } - - Utils::Stream::ClearPointer(&dest->textureTable); - } - } - - if (asset->constantTable) - { - Assert_Size(Game::MaterialConstantDef, 32); - - if (builder->HasPointer(asset->constantTable)) - { - dest->constantTable = builder->GetPointer(asset->constantTable); - } - else - { - buffer->Align(Utils::Stream::ALIGN_16); - builder->StorePointer(asset->constantTable); - - buffer->SaveArray(asset->constantTable, asset->constantCount); - Utils::Stream::ClearPointer(&dest->constantTable); - } - } - - if (asset->stateBitTable) - { - if (builder->HasPointer(asset->stateBitTable)) - { - dest->stateBitTable = builder->GetPointer(asset->stateBitTable); - } - else - { - buffer->Align(Utils::Stream::ALIGN_4); - builder->StorePointer(asset->stateBitTable); - - buffer->Save(asset->stateBitTable, 8, asset->stateBitsCount); - Utils::Stream::ClearPointer(&dest->stateBitTable); - } - } - - buffer->PopBlock(); - } -} +#include + +namespace Assets +{ + void IMaterial::Load(Game::XAssetHeader* header, std::string name, Components::ZoneBuilder::Zone* builder) + { + Components::FileSystem::File materialInfo(fmt::sprintf("materials/%s.json", name.data())); + + if (!materialInfo.Exists()) return; + + std::string errors; + json11::Json infoData = json11::Json::parse(materialInfo.GetBuffer(), errors); + + if (!infoData.is_object()) + { + Components::Logger::Error("Failed to load material information for %s!", name.data()); + return; + } + + auto base = infoData["base"]; + + if (!base.is_string()) + { + Components::Logger::Error("No valid material base provided for %s!", name.data()); + return; + } + + Game::Material* baseMaterial = Game::DB_FindXAssetHeader(Game::XAssetType::ASSET_TYPE_MATERIAL, base.string_value().data()).material; + + if (!baseMaterial) // TODO: Maybe check if default asset? Maybe not? You could still want to use the default one as base!? + { + Components::Logger::Error("Basematerial '%s' not found for %s!", base.string_value().data(), name.data()); + return; + } + + Game::Material* material = builder->GetAllocator()->AllocateArray(); + + if (!material) + { + Components::Logger::Error("Failed to allocate material structure!"); + return; + } + + // Copy base material to our structure + std::memcpy(material, baseMaterial, sizeof(Game::Material)); + material->name = builder->GetAllocator()->DuplicateString(name); + + material->textureAtlasRowCount = 1; + material->textureAtlasColumnCount = 1; + + // Load animation frames + auto anims = infoData["anims"]; + if (anims.is_array()) + { + auto animCoords = anims.array_items(); + + if (animCoords.size() >= 2) + { + auto animCoordX = animCoords[0]; + auto animCoordY = animCoords[1]; + + if (animCoordX.is_number()) + { + material->textureAtlasColumnCount = static_cast(animCoordX.number_value()) & 0xFF; + } + + if (animCoordY.is_number()) + { + material->textureAtlasRowCount = static_cast(animCoordY.number_value()) & 0xFF; + } + } + } + + // Model surface textures are special, they need a special order and whatnot + bool replaceTexture = Utils::String::StartsWith(name, "mc/"); + if (replaceTexture) + { + Game::MaterialTextureDef* textureTable = builder->GetAllocator()->AllocateArray(baseMaterial->textureCount); + std::memcpy(textureTable, baseMaterial->textureTable, sizeof(Game::MaterialTextureDef) * baseMaterial->textureCount); + material->textureTable = textureTable; + material->textureCount = baseMaterial->textureCount; + } + + // Load referenced textures + auto textures = infoData["textures"]; + if (textures.is_array()) + { + std::vector textureList; + + for (auto texture : textures.array_items()) + { + if (!texture.is_array()) continue; + if (textureList.size() >= 0xFF) break; + + auto textureInfo = texture.array_items(); + if (textureInfo.size() < 2) continue; + + auto map = textureInfo[0]; + auto image = textureInfo[1]; + if (!map.is_string() || !image.is_string()) continue; + + Game::MaterialTextureDef textureDef; + + textureDef.semantic = 0; // No water image + textureDef.sampleState = -30; + textureDef.nameEnd = map.string_value().back(); + textureDef.nameStart = map.string_value().front(); + textureDef.nameHash = Game::R_HashString(map.string_value().data()); + + textureDef.info.image = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_IMAGE, image.string_value(), builder).image; + + if (replaceTexture) + { + bool applied = false; + + for (char i = 0; i < baseMaterial->textureCount; ++i) + { + if (material->textureTable[i].nameHash == textureDef.nameHash) + { + applied = true; + material->textureTable[i].info.image = textureDef.info.image; + break; + } + } + + if (!applied) + { + Components::Logger::Error(0, "Unable to find texture for map '%s' in %s!", map.string_value().data(), baseMaterial->name); + } + } + else + { + textureList.push_back(textureDef); + } + } + + if(!replaceTexture) + { + if (!textureList.empty()) + { + Game::MaterialTextureDef* textureTable = builder->GetAllocator()->AllocateArray(textureList.size()); + + if (!textureTable) + { + Components::Logger::Error("Failed to allocate texture table!"); + return; + } + + std::memcpy(textureTable, textureList.data(), sizeof(Game::MaterialTextureDef) * textureList.size()); + + material->textureTable = textureTable; + } + else + { + material->textureTable = 0; + } + + material->textureCount = static_cast(textureList.size()) & 0xFF; + } + } + + header->material = material; + } + + void IMaterial::Mark(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder) + { + Game::Material* asset = header.material; + + if (asset->techniqueSet) + { + builder->LoadAsset(Game::XAssetType::ASSET_TYPE_TECHSET, asset->techniqueSet->name); + } + + if (asset->textureTable) + { + for (char i = 0; i < asset->textureCount; ++i) + { + if (asset->textureTable[i].info.image) + { + if (asset->textureTable[i].semantic == SEMANTIC_WATER_MAP) + { + if (asset->textureTable[i].info.water->image) + { + builder->LoadAsset(Game::XAssetType::ASSET_TYPE_IMAGE, asset->textureTable[i].info.water->image->name); + } + } + else + { + builder->LoadAsset(Game::XAssetType::ASSET_TYPE_IMAGE, asset->textureTable[i].info.image->name); + } + } + } + } + } + + void IMaterial::Save(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder) + { + Assert_Size(Game::Material, 96); + + Utils::Stream* buffer = builder->GetBuffer(); + Game::Material* asset = header.material; + Game::Material* dest = buffer->Dest(); + buffer->Save(asset); + + buffer->PushBlock(Game::XFILE_BLOCK_VIRTUAL); + + if (asset->name) + { + buffer->SaveString(builder->GetAssetName(this->GetType(), asset->name)); + Utils::Stream::ClearPointer(&dest->name); + } + + if (asset->techniqueSet) + { + dest->techniqueSet = builder->RequireAsset(Game::XAssetType::ASSET_TYPE_TECHSET, asset->techniqueSet->name).materialTechset; + } + + if (asset->textureTable) + { + Assert_Size(Game::MaterialTextureDef, 12); + + // Pointer/Offset insertion is untested, but it worked in T6, so I think it's fine + if (builder->HasPointer(asset->textureTable)) + { + dest->textureTable = builder->GetPointer(asset->textureTable); + } + else + { + buffer->Align(Utils::Stream::ALIGN_4); + builder->StorePointer(asset->textureTable); + + Game::MaterialTextureDef* destTextureTable = buffer->Dest(); + buffer->SaveArray(asset->textureTable, asset->textureCount); + + for (char i = 0; i < asset->textureCount; ++i) + { + Game::MaterialTextureDef* destTextureDef = &destTextureTable[i]; + Game::MaterialTextureDef* textureDef = &asset->textureTable[i]; + + if (textureDef->semantic == SEMANTIC_WATER_MAP) + { + Assert_Size(Game::water_t, 68); + + Game::water_t* destWater = buffer->Dest(); + Game::water_t* water = textureDef->info.water; + + if (water) + { + buffer->Align(Utils::Stream::ALIGN_4); + buffer->Save(water); + Utils::Stream::ClearPointer(&destTextureDef->info.water); + + // Save_water_t + if (water->H0X) + { + buffer->Align(Utils::Stream::ALIGN_4); + buffer->Save(water->H0X, 8, water->M * water->N); + Utils::Stream::ClearPointer(&destWater->H0X); + } + + if (water->H0Y) + { + buffer->Align(Utils::Stream::ALIGN_4); + buffer->Save(water->H0Y, 4, water->M * water->N); + Utils::Stream::ClearPointer(&destWater->H0Y); + } + + if (water->image) + { + destWater->image = builder->RequireAsset(Game::XAssetType::ASSET_TYPE_IMAGE, water->image->name).image; + } + } + } + else if (textureDef->info.image) + { + destTextureDef->info.image = builder->RequireAsset(Game::XAssetType::ASSET_TYPE_IMAGE, textureDef->info.image->name).image; + } + } + + Utils::Stream::ClearPointer(&dest->textureTable); + } + } + + if (asset->constantTable) + { + Assert_Size(Game::MaterialConstantDef, 32); + + if (builder->HasPointer(asset->constantTable)) + { + dest->constantTable = builder->GetPointer(asset->constantTable); + } + else + { + buffer->Align(Utils::Stream::ALIGN_16); + builder->StorePointer(asset->constantTable); + + buffer->SaveArray(asset->constantTable, asset->constantCount); + Utils::Stream::ClearPointer(&dest->constantTable); + } + } + + if (asset->stateBitTable) + { + if (builder->HasPointer(asset->stateBitTable)) + { + dest->stateBitTable = builder->GetPointer(asset->stateBitTable); + } + else + { + buffer->Align(Utils::Stream::ALIGN_4); + builder->StorePointer(asset->stateBitTable); + + buffer->Save(asset->stateBitTable, 8, asset->stateBitsCount); + Utils::Stream::ClearPointer(&dest->stateBitTable); + } + } + + buffer->PopBlock(); + } +} diff --git a/src/Components/Modules/AssetInterfaces/IXAnimParts.cpp b/src/Components/Modules/AssetInterfaces/IXAnimParts.cpp index 52ffa889..103aeed9 100644 --- a/src/Components/Modules/AssetInterfaces/IXAnimParts.cpp +++ b/src/Components/Modules/AssetInterfaces/IXAnimParts.cpp @@ -1,341 +1,341 @@ -#include - -namespace Assets -{ - void IXAnimParts::Load(Game::XAssetHeader* header, std::string name, Components::ZoneBuilder::Zone* builder) - { - Components::FileSystem::File animFile(Utils::VA("xanim/%s.iw4xAnim", name.data())); - - if (animFile.Exists()) - { - Utils::Stream::Reader reader(builder->GetAllocator(), animFile.GetBuffer()); - - Game::XAnimParts* xanim = reader.ReadArray(); - - if (xanim) - { - if (xanim->name) - { - xanim->name = reader.ReadCString(); - } - - if (xanim->tagnames) - { - xanim->tagnames = builder->GetAllocator()->AllocateArray(xanim->boneCount[Game::XAnimPartType::PART_TYPE_ALL]); - for (int i = 0; i < xanim->boneCount[Game::XAnimPartType::PART_TYPE_ALL]; ++i) - { - xanim->tagnames[i] = Game::SL_GetString(reader.ReadCString(), 0); - } - } - - if (xanim->notetracks) - { - xanim->notetracks = reader.ReadArray(xanim->notetrackCount); - - for (int i = 0; i < xanim->notetrackCount; ++i) - { - xanim->notetracks[i].name = Game::SL_GetString(reader.ReadCString(), 0); - } - } - - if (xanim->dataByte) - { - xanim->dataByte = reader.ReadArray(xanim->dataByteCount); - } - - if (xanim->dataShort) - { - xanim->dataShort = reader.ReadArray(xanim->dataShortCount); - } - - if (xanim->dataInt) - { - xanim->dataInt = reader.ReadArray(xanim->dataIntCount); - } - - if (xanim->randomDataByte) - { - xanim->randomDataByte = reader.ReadArray(xanim->randomDataByteCount); - } - - if (xanim->randomDataShort) - { - xanim->randomDataShort = reader.ReadArray(xanim->randomDataShortCount); - } - - if (xanim->randomDataInt) - { - xanim->randomDataInt = reader.ReadArray(xanim->randomDataIntCount); - } - - if (xanim->indices.data) - { - if (xanim->framecount < 256) - { - xanim->indices._1 = reader.ReadArray(xanim->indexcount); - } - else - { - xanim->indices._2 = reader.ReadArray(xanim->indexcount); - } - } - - if (!reader.End()) - { - Components::Logger::Error(0, "Reading animation '%s' failed, remaining raw data found!", name.data()); - } - - header->xanim = xanim; - } - } - } - - void IXAnimParts::Mark(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder) - { - Game::XAnimParts* asset = header.xanim; - - if (asset->tagnames) - { - for (char i = 0; i < asset->boneCount[Game::XAnimPartType::PART_TYPE_ALL]; ++i) - { - builder->AddScriptString(asset->tagnames[i]); - } - } - - if (asset->notetracks) - { - for (char i = 0; i < asset->notetrackCount; ++i) - { - builder->AddScriptString(asset->notetracks[i].name); - } - } - } - - void IXAnimParts::Save_XAnimDeltaPart(Game::XAnimDeltaPart* delta, unsigned short framecount, Components::ZoneBuilder::Zone* builder) - { - Assert_Size(Game::XAnimDeltaPart, 12); - - Utils::Stream* buffer = builder->GetBuffer(); - Game::XAnimDeltaPart* destDelta = buffer->Dest(); - buffer->Save(delta); - - if (delta->trans) - { - buffer->Align(Utils::Stream::ALIGN_4); - buffer->Save(delta->trans, 4); - - if (delta->trans->size) - { - buffer->Save(&delta->trans->u.frames, 28); - - if (framecount > 0xFF) - { - buffer->SaveArray(delta->trans->u.frames.indices._2, delta->trans->size + 1); - } - else - { - buffer->SaveArray(delta->trans->u.frames.indices._1, delta->trans->size + 1); - } - - if (delta->trans->u.frames.frames._1) - { - if (delta->trans->smallTrans) - { - buffer->Save(delta->trans->u.frames.frames._1, 3, delta->trans->size + 1); - } - else - { - buffer->Align(Utils::Stream::ALIGN_4); - buffer->Save(delta->trans->u.frames.frames._1, 6, delta->trans->size + 1); - } - } - } - else - { - buffer->Save(delta->trans->u.frame0, 12); - } - - Utils::Stream::ClearPointer(&destDelta->trans); - } - - if (delta->quat2) - { - buffer->Align(Utils::Stream::ALIGN_4); - buffer->Save(delta->quat2, 4); - - if (delta->quat2->size) - { - buffer->Save(&delta->quat2->u.frames, 4); - - if (framecount > 0xFF) - { - buffer->Save(delta->quat2->u.frames.indices, 2, delta->quat2->size + 1); - } - else - { - buffer->Save(delta->quat2->u.frames.indices, 1, delta->quat2->size + 1); - } - - if (delta->quat2->u.frames.frames) - { - buffer->Align(Utils::Stream::ALIGN_4); - buffer->Save(delta->quat2->u.frames.frames, 4, delta->quat2->size + 1); - } - } - else - { - buffer->Save(delta->quat2->u.frame0, 4); - } - - Utils::Stream::ClearPointer(&destDelta->quat2); - } - - if (delta->quat) - { - buffer->Align(Utils::Stream::ALIGN_4); - buffer->Save(delta->quat, 4); - - if (delta->quat->size) - { - buffer->Save(&delta->quat->u.frames, 4); - - if (framecount > 0xFF) - { - buffer->Save(delta->quat->u.frames.indices, 2, delta->quat->size + 1); - } - else - { - buffer->Save(delta->quat->u.frames.indices, 1, delta->quat->size + 1); - } - - if (delta->quat->u.frames.frames) - { - buffer->Align(Utils::Stream::ALIGN_4); - buffer->Save(delta->quat->u.frames.frames, 4, delta->quat->size + 1); - } - } - else - { - buffer->Save(delta->quat->u.frame0, 4); - } - - Utils::Stream::ClearPointer(&destDelta->quat); - } - } - - void IXAnimParts::Save(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder) - { - Assert_Size(Game::XAnimParts, 88); - - Utils::Stream* buffer = builder->GetBuffer(); - Game::XAnimParts* asset = header.xanim; - Game::XAnimParts* dest = buffer->Dest(); - buffer->Save(asset, sizeof(Game::XAnimParts)); - - buffer->PushBlock(Game::XFILE_BLOCK_VIRTUAL); - - if (asset->name) - { - buffer->SaveString(builder->GetAssetName(this->GetType(), asset->name)); - Utils::Stream::ClearPointer(&dest->name); - } - - if (asset->tagnames) - { - buffer->Align(Utils::Stream::ALIGN_2); - - unsigned short* destTagnames = buffer->Dest(); - buffer->SaveArray(asset->tagnames, asset->boneCount[Game::XAnimPartType::PART_TYPE_ALL]); - - for (char i = 0; i < asset->boneCount[Game::XAnimPartType::PART_TYPE_ALL]; ++i) - { - builder->MapScriptString(&destTagnames[i]); - } - - Utils::Stream::ClearPointer(&dest->tagnames); - } - - if (asset->notetracks) - { - Assert_Size(Game::XAnimNotifyInfo, 8); - buffer->Align(Utils::Stream::ALIGN_4); - - Game::XAnimNotifyInfo* destNotetracks = buffer->Dest(); - buffer->SaveArray(asset->notetracks, asset->notetrackCount); - - for (char i = 0; i < asset->notetrackCount; ++i) - { - builder->MapScriptString(&destNotetracks[i].name); - } - - Utils::Stream::ClearPointer(&dest->notetracks); - } - - if (asset->delta) - { - Assert_Size(Game::XAnimDeltaPart, 12); - buffer->Align(Utils::Stream::ALIGN_4); - - IXAnimParts::Save_XAnimDeltaPart(asset->delta, asset->framecount, builder); - - Utils::Stream::ClearPointer(&dest->delta); - } - - if (asset->dataByte) - { - buffer->SaveArray(asset->dataByte, asset->dataByteCount); - Utils::Stream::ClearPointer(&dest->dataByte); - } - - if (asset->dataShort) - { - buffer->Align(Utils::Stream::ALIGN_2); - buffer->SaveArray(asset->dataShort, asset->dataShortCount); - Utils::Stream::ClearPointer(&dest->dataShort); - } - - if (asset->dataInt) - { - buffer->Align(Utils::Stream::ALIGN_4); - buffer->SaveArray(asset->dataInt, asset->dataIntCount); - Utils::Stream::ClearPointer(&dest->dataInt); - } - - if (asset->randomDataShort) - { - buffer->Align(Utils::Stream::ALIGN_2); - buffer->SaveArray(asset->randomDataShort, asset->randomDataShortCount); - Utils::Stream::ClearPointer(&dest->randomDataShort); - } - - if (asset->randomDataByte) - { - buffer->SaveArray(asset->randomDataByte, asset->randomDataByteCount); - Utils::Stream::ClearPointer(&dest->randomDataByte); - } - - if (asset->randomDataInt) - { - buffer->Align(Utils::Stream::ALIGN_4); - buffer->SaveArray(asset->randomDataInt, asset->randomDataIntCount); - Utils::Stream::ClearPointer(&dest->randomDataInt); - } - - if (asset->indices.data) - { - if (asset->framecount > 0xFF) - { - buffer->Align(Utils::Stream::ALIGN_2); - buffer->SaveArray(asset->indices._2, asset->indexcount); - } - else - { - buffer->SaveArray(asset->indices._1, asset->indexcount); - } - - Utils::Stream::ClearPointer(&dest->indices.data); - } - - buffer->PopBlock(); - } -} +#include + +namespace Assets +{ + void IXAnimParts::Load(Game::XAssetHeader* header, std::string name, Components::ZoneBuilder::Zone* builder) + { + Components::FileSystem::File animFile(fmt::sprintf("xanim/%s.iw4xAnim", name.data())); + + if (animFile.Exists()) + { + Utils::Stream::Reader reader(builder->GetAllocator(), animFile.GetBuffer()); + + Game::XAnimParts* xanim = reader.ReadArray(); + + if (xanim) + { + if (xanim->name) + { + xanim->name = reader.ReadCString(); + } + + if (xanim->tagnames) + { + xanim->tagnames = builder->GetAllocator()->AllocateArray(xanim->boneCount[Game::XAnimPartType::PART_TYPE_ALL]); + for (int i = 0; i < xanim->boneCount[Game::XAnimPartType::PART_TYPE_ALL]; ++i) + { + xanim->tagnames[i] = Game::SL_GetString(reader.ReadCString(), 0); + } + } + + if (xanim->notetracks) + { + xanim->notetracks = reader.ReadArray(xanim->notetrackCount); + + for (int i = 0; i < xanim->notetrackCount; ++i) + { + xanim->notetracks[i].name = Game::SL_GetString(reader.ReadCString(), 0); + } + } + + if (xanim->dataByte) + { + xanim->dataByte = reader.ReadArray(xanim->dataByteCount); + } + + if (xanim->dataShort) + { + xanim->dataShort = reader.ReadArray(xanim->dataShortCount); + } + + if (xanim->dataInt) + { + xanim->dataInt = reader.ReadArray(xanim->dataIntCount); + } + + if (xanim->randomDataByte) + { + xanim->randomDataByte = reader.ReadArray(xanim->randomDataByteCount); + } + + if (xanim->randomDataShort) + { + xanim->randomDataShort = reader.ReadArray(xanim->randomDataShortCount); + } + + if (xanim->randomDataInt) + { + xanim->randomDataInt = reader.ReadArray(xanim->randomDataIntCount); + } + + if (xanim->indices.data) + { + if (xanim->framecount < 256) + { + xanim->indices._1 = reader.ReadArray(xanim->indexcount); + } + else + { + xanim->indices._2 = reader.ReadArray(xanim->indexcount); + } + } + + if (!reader.End()) + { + Components::Logger::Error(0, "Reading animation '%s' failed, remaining raw data found!", name.data()); + } + + header->xanim = xanim; + } + } + } + + void IXAnimParts::Mark(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder) + { + Game::XAnimParts* asset = header.xanim; + + if (asset->tagnames) + { + for (char i = 0; i < asset->boneCount[Game::XAnimPartType::PART_TYPE_ALL]; ++i) + { + builder->AddScriptString(asset->tagnames[i]); + } + } + + if (asset->notetracks) + { + for (char i = 0; i < asset->notetrackCount; ++i) + { + builder->AddScriptString(asset->notetracks[i].name); + } + } + } + + void IXAnimParts::Save_XAnimDeltaPart(Game::XAnimDeltaPart* delta, unsigned short framecount, Components::ZoneBuilder::Zone* builder) + { + Assert_Size(Game::XAnimDeltaPart, 12); + + Utils::Stream* buffer = builder->GetBuffer(); + Game::XAnimDeltaPart* destDelta = buffer->Dest(); + buffer->Save(delta); + + if (delta->trans) + { + buffer->Align(Utils::Stream::ALIGN_4); + buffer->Save(delta->trans, 4); + + if (delta->trans->size) + { + buffer->Save(&delta->trans->u.frames, 28); + + if (framecount > 0xFF) + { + buffer->SaveArray(delta->trans->u.frames.indices._2, delta->trans->size + 1); + } + else + { + buffer->SaveArray(delta->trans->u.frames.indices._1, delta->trans->size + 1); + } + + if (delta->trans->u.frames.frames._1) + { + if (delta->trans->smallTrans) + { + buffer->Save(delta->trans->u.frames.frames._1, 3, delta->trans->size + 1); + } + else + { + buffer->Align(Utils::Stream::ALIGN_4); + buffer->Save(delta->trans->u.frames.frames._1, 6, delta->trans->size + 1); + } + } + } + else + { + buffer->Save(delta->trans->u.frame0, 12); + } + + Utils::Stream::ClearPointer(&destDelta->trans); + } + + if (delta->quat2) + { + buffer->Align(Utils::Stream::ALIGN_4); + buffer->Save(delta->quat2, 4); + + if (delta->quat2->size) + { + buffer->Save(&delta->quat2->u.frames, 4); + + if (framecount > 0xFF) + { + buffer->Save(delta->quat2->u.frames.indices, 2, delta->quat2->size + 1); + } + else + { + buffer->Save(delta->quat2->u.frames.indices, 1, delta->quat2->size + 1); + } + + if (delta->quat2->u.frames.frames) + { + buffer->Align(Utils::Stream::ALIGN_4); + buffer->Save(delta->quat2->u.frames.frames, 4, delta->quat2->size + 1); + } + } + else + { + buffer->Save(delta->quat2->u.frame0, 4); + } + + Utils::Stream::ClearPointer(&destDelta->quat2); + } + + if (delta->quat) + { + buffer->Align(Utils::Stream::ALIGN_4); + buffer->Save(delta->quat, 4); + + if (delta->quat->size) + { + buffer->Save(&delta->quat->u.frames, 4); + + if (framecount > 0xFF) + { + buffer->Save(delta->quat->u.frames.indices, 2, delta->quat->size + 1); + } + else + { + buffer->Save(delta->quat->u.frames.indices, 1, delta->quat->size + 1); + } + + if (delta->quat->u.frames.frames) + { + buffer->Align(Utils::Stream::ALIGN_4); + buffer->Save(delta->quat->u.frames.frames, 4, delta->quat->size + 1); + } + } + else + { + buffer->Save(delta->quat->u.frame0, 4); + } + + Utils::Stream::ClearPointer(&destDelta->quat); + } + } + + void IXAnimParts::Save(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder) + { + Assert_Size(Game::XAnimParts, 88); + + Utils::Stream* buffer = builder->GetBuffer(); + Game::XAnimParts* asset = header.xanim; + Game::XAnimParts* dest = buffer->Dest(); + buffer->Save(asset, sizeof(Game::XAnimParts)); + + buffer->PushBlock(Game::XFILE_BLOCK_VIRTUAL); + + if (asset->name) + { + buffer->SaveString(builder->GetAssetName(this->GetType(), asset->name)); + Utils::Stream::ClearPointer(&dest->name); + } + + if (asset->tagnames) + { + buffer->Align(Utils::Stream::ALIGN_2); + + unsigned short* destTagnames = buffer->Dest(); + buffer->SaveArray(asset->tagnames, asset->boneCount[Game::XAnimPartType::PART_TYPE_ALL]); + + for (char i = 0; i < asset->boneCount[Game::XAnimPartType::PART_TYPE_ALL]; ++i) + { + builder->MapScriptString(&destTagnames[i]); + } + + Utils::Stream::ClearPointer(&dest->tagnames); + } + + if (asset->notetracks) + { + Assert_Size(Game::XAnimNotifyInfo, 8); + buffer->Align(Utils::Stream::ALIGN_4); + + Game::XAnimNotifyInfo* destNotetracks = buffer->Dest(); + buffer->SaveArray(asset->notetracks, asset->notetrackCount); + + for (char i = 0; i < asset->notetrackCount; ++i) + { + builder->MapScriptString(&destNotetracks[i].name); + } + + Utils::Stream::ClearPointer(&dest->notetracks); + } + + if (asset->delta) + { + Assert_Size(Game::XAnimDeltaPart, 12); + buffer->Align(Utils::Stream::ALIGN_4); + + IXAnimParts::Save_XAnimDeltaPart(asset->delta, asset->framecount, builder); + + Utils::Stream::ClearPointer(&dest->delta); + } + + if (asset->dataByte) + { + buffer->SaveArray(asset->dataByte, asset->dataByteCount); + Utils::Stream::ClearPointer(&dest->dataByte); + } + + if (asset->dataShort) + { + buffer->Align(Utils::Stream::ALIGN_2); + buffer->SaveArray(asset->dataShort, asset->dataShortCount); + Utils::Stream::ClearPointer(&dest->dataShort); + } + + if (asset->dataInt) + { + buffer->Align(Utils::Stream::ALIGN_4); + buffer->SaveArray(asset->dataInt, asset->dataIntCount); + Utils::Stream::ClearPointer(&dest->dataInt); + } + + if (asset->randomDataShort) + { + buffer->Align(Utils::Stream::ALIGN_2); + buffer->SaveArray(asset->randomDataShort, asset->randomDataShortCount); + Utils::Stream::ClearPointer(&dest->randomDataShort); + } + + if (asset->randomDataByte) + { + buffer->SaveArray(asset->randomDataByte, asset->randomDataByteCount); + Utils::Stream::ClearPointer(&dest->randomDataByte); + } + + if (asset->randomDataInt) + { + buffer->Align(Utils::Stream::ALIGN_4); + buffer->SaveArray(asset->randomDataInt, asset->randomDataIntCount); + Utils::Stream::ClearPointer(&dest->randomDataInt); + } + + if (asset->indices.data) + { + if (asset->framecount > 0xFF) + { + buffer->Align(Utils::Stream::ALIGN_2); + buffer->SaveArray(asset->indices._2, asset->indexcount); + } + else + { + buffer->SaveArray(asset->indices._1, asset->indexcount); + } + + Utils::Stream::ClearPointer(&dest->indices.data); + } + + buffer->PopBlock(); + } +} diff --git a/src/Components/Modules/AssetInterfaces/IXModel.cpp b/src/Components/Modules/AssetInterfaces/IXModel.cpp index a3164b33..3c0b6a4e 100644 --- a/src/Components/Modules/AssetInterfaces/IXModel.cpp +++ b/src/Components/Modules/AssetInterfaces/IXModel.cpp @@ -1,354 +1,354 @@ -#include - -namespace Assets -{ - void IXModel::Load(Game::XAssetHeader* header, std::string name, Components::ZoneBuilder::Zone* builder) - { - Components::FileSystem::File modelFile(Utils::VA("xmodel/%s.iw4xModel", name.data())); - - if (modelFile.Exists()) - { - Game::XModel* baseModel = Components::AssetHandler::FindOriginalAsset(Game::XAssetType::ASSET_TYPE_XMODEL, "viewmodel_mp5k").model; - - // Allocate new model and copy the base data to it - Game::XModel* model = builder->GetAllocator()->AllocateArray(); - std::memcpy(model, baseModel, sizeof(Game::XModel)); - - Utils::Stream::Reader reader(builder->GetAllocator(), modelFile.GetBuffer()); - - model->name = reader.ReadCString(); - model->numBones = reader.ReadByte(); - model->numRootBones = reader.ReadByte(); - model->numSurfaces = reader.ReadByte(); - model->numColSurfs = reader.Read(); - - // Read bone names - model->boneNames = builder->GetAllocator()->AllocateArray(model->numBones); - for (int i = 0; i < model->numBones; ++i) - { - model->boneNames[i] = Game::SL_GetString(reader.ReadCString(), 0); - } - - // Bone count - int boneCount = (model->numBones - model->numRootBones); - - // Read bone data - model->parentList = reader.ReadArray(boneCount); - model->tagAngles = reader.ReadArray(boneCount); - model->tagPositions = reader.ReadArray(boneCount); - model->partClassification = reader.ReadArray(boneCount); - model->animMatrix = reader.ReadArray(boneCount); - - // Prepare surfaces - Game::XSurface* baseSurface = &baseModel->lods[0].surfaces[0].surfaces[0]; - Game::XModelSurfs* surf = builder->GetAllocator()->AllocateArray(); - - std::memcpy(surf, baseModel->lods[0].surfaces, sizeof(Game::XModelSurfs)); - surf->name = builder->GetAllocator()->DuplicateString(Utils::VA("%s1", model->name)); - surf->surfaces = builder->GetAllocator()->AllocateArray(model->numSurfaces); - surf->numSurfaces = model->numSurfaces; - - model->lods[0].numSurfs = model->numSurfaces; - model->lods[0].surfaces = surf; - - // Reset surfaces in remaining lods - for (unsigned int i = 1; i < 4; ++i) - { - model->lods[i].numSurfs = 0; - model->lods[i].surfaces = nullptr; - } - - // Read surfaces - for (int i = 0; i < surf->numSurfaces; ++i) - { - Game::XSurface* surface = &surf->surfaces[i]; - std::memcpy(surface, baseSurface, sizeof(Game::XSurface)); - - surface->streamHandle = reader.Read(); - surface->something = reader.Read(); - surface->something2 = reader.Read(); - - surface->numVertices = reader.Read(); - surface->numPrimitives = reader.Read(); - surface->numCT = reader.Read(); - - surface->blendNum1 = reader.Read(); - surface->blendNum2 = reader.Read(); - surface->blendNum3 = reader.Read(); - surface->blendNum4 = reader.Read(); - - surface->blendInfo = reinterpret_cast(reader.Read(2, surface->blendNum1 + (3 * surface->blendNum2) + (5 * surface->blendNum3) + (7 * surface->blendNum4))); - - surface->vertexBuffer = reader.ReadArray(surface->numVertices); - surface->indexBuffer = reader.ReadArray(surface->numPrimitives); - - // Read vert list - if (reader.ReadByte()) - { - surface->ct = reader.ReadArray(surface->numCT); - - for (int j = 0; j < surface->numCT; ++j) - { - Game::XRigidVertList* vertList = &surface->ct[j]; - - vertList->entry = reader.ReadArray(); - vertList->entry->node = reinterpret_cast(reader.Read(16, vertList->entry->numNode)); - vertList->entry->leaf = reader.ReadArray(vertList->entry->numLeaf); - } - } - else - { - surface->ct = nullptr; - } - } - - // Read materials - model->materials = builder->GetAllocator()->AllocateArray(model->numSurfaces); - for (char i = 0; i < model->numSurfaces; ++i) - { - model->materials[i] = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_MATERIAL, reader.ReadString(), builder).material; - } - - // Read collision surfaces - if (reader.ReadByte()) - { - model->colSurf = reader.ReadArray(model->numColSurfs); - - for (int i = 0; i < model->numColSurfs; ++i) - { - if (model->colSurf[i].tris) - { - model->colSurf[i].tris = reader.Read(48, model->colSurf[i].count); - } - } - } - else - { - model->colSurf = nullptr; - } - - // Read bone info - if (reader.ReadByte()) - { - model->boneInfo = reader.ReadArray(model->numBones); - } - else - { - model->boneInfo = nullptr; - } - - if (!reader.End()) - { - Components::Logger::Error(0, "Reading model '%s' failed, remaining raw data found!", name.data()); - } - - header->model = model; - } - } - - void IXModel::Mark(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder) - { - Game::XModel* asset = header.model; - - if (asset->boneNames) - { - for (char i = 0; i < asset->numBones; ++i) - { - builder->AddScriptString(asset->boneNames[i]); - } - } - - if (asset->materials) - { - for (char i = 0; i < asset->numSurfaces; ++i) - { - if (asset->materials[i]) - { - builder->LoadAsset(Game::XAssetType::ASSET_TYPE_MATERIAL, asset->materials[i]->name); - } - } - } - - for (int i = 0; i < 4; ++i) - { - if (asset->lods[i].surfaces) - { - // We're not supposed to include xmodelsurfs as standalone asset - //builder->LoadAsset(Game::XAssetType::ASSET_TYPE_XMODELSURFS, asset->lods[i].surfaces->name); - - IXModelSurfs().Mark({ asset->lods[i].surfaces }, builder); - } - } - - if (asset->physPreset) - { - builder->LoadAsset(Game::XAssetType::ASSET_TYPE_PHYSPRESET, asset->physPreset->name); - } - - if (asset->physCollmap) - { - builder->LoadAsset(Game::XAssetType::ASSET_TYPE_PHYS_COLLMAP, asset->physCollmap->name); - } - } - - void IXModel::Save(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder) - { - Assert_Size(Game::XModel, 304); - - Utils::Stream* buffer = builder->GetBuffer(); - Game::XModel* asset = header.model; - Game::XModel* dest = buffer->Dest(); - buffer->Save(asset); - - buffer->PushBlock(Game::XFILE_BLOCK_VIRTUAL); - - if (asset->name) - { - buffer->SaveString(builder->GetAssetName(this->GetType(), asset->name)); - Utils::Stream::ClearPointer(&dest->name); - } - - if (asset->boneNames) - { - buffer->Align(Utils::Stream::ALIGN_2); - - unsigned short* destBoneNames = buffer->Dest(); - buffer->SaveArray(asset->boneNames, asset->numBones); - - for (char i = 0; i < asset->numBones; ++i) - { - builder->MapScriptString(&destBoneNames[i]); - } - - Utils::Stream::ClearPointer(&dest->boneNames); - } - - if (asset->parentList) - { - buffer->Save(asset->parentList, asset->numBones - asset->numRootBones); - Utils::Stream::ClearPointer(&dest->parentList); - } - - if (asset->tagAngles) - { - Assert_Size(Game::XModelAngle, 8); - - buffer->Align(Utils::Stream::ALIGN_2); - buffer->SaveArray(asset->tagAngles, asset->numBones - asset->numRootBones); - Utils::Stream::ClearPointer(&dest->tagAngles); - } - - if (asset->tagPositions) - { - Assert_Size(Game::XModelTagPos, 12); - - buffer->Align(Utils::Stream::ALIGN_4); - buffer->SaveArray(asset->tagPositions, asset->numBones - asset->numRootBones); - Utils::Stream::ClearPointer(&dest->tagPositions); - } - - if (asset->partClassification) - { - buffer->Save(asset->partClassification, asset->numBones); - Utils::Stream::ClearPointer(&dest->partClassification); - } - - if (asset->animMatrix) - { - Assert_Size(Game::DObjAnimMat, 32); - - buffer->Align(Utils::Stream::ALIGN_4); - buffer->SaveArray(asset->animMatrix, asset->numBones); - Utils::Stream::ClearPointer(&dest->animMatrix); - } - - if (asset->materials) - { - buffer->Align(Utils::Stream::ALIGN_4); - - Game::Material** destMaterials = buffer->Dest(); - buffer->SaveArray(asset->materials, asset->numSurfaces); - - for (char i = 0; i < asset->numSurfaces; ++i) - { - if (asset->materials[i]) - { - destMaterials[i] = builder->RequireAsset(Game::XAssetType::ASSET_TYPE_MATERIAL, asset->materials[i]->name).material; - } - } - - Utils::Stream::ClearPointer(&dest->materials); - } - - // Save_XModelLodInfoArray - { - Assert_Size(Game::XModelLodInfo, 44); - - for (int i = 0; i < 4; ++i) - { - if (asset->lods[i].surfaces) - { - // Requiring this asset is not possible, it has to be loaded as part of the model - //dest->lods[i].surfaces = builder->RequireAsset(Game::XAssetType::ASSET_TYPE_XMODELSURFS, asset->lods[i].surfaces->name).surfaces; - - buffer->PushBlock(Game::XFILE_BLOCK_TEMP); - buffer->Align(Utils::Stream::ALIGN_4); - - IXModelSurfs().Save({ asset->lods[i].surfaces }, builder); - Utils::Stream::ClearPointer(&dest->lods[i].surfaces); - - buffer->PopBlock(); - } - } - } - - // Save_XModelCollSurfArray - if (asset->colSurf) - { - Assert_Size(Game::XModelCollSurf, 44); - - buffer->Align(Utils::Stream::ALIGN_4); - - Game::XModelCollSurf* destColSurfs = buffer->Dest(); - buffer->SaveArray(asset->colSurf, asset->numColSurfs); - - for (int i = 0; i < asset->numColSurfs; ++i) - { - Game::XModelCollSurf* destColSurf = &destColSurfs[i]; - Game::XModelCollSurf* colSurf = &asset->colSurf[i]; - - if (colSurf->tris) - { - buffer->Align(Utils::Stream::ALIGN_4); - - buffer->Save(colSurf->tris, 48, colSurf->count); - Utils::Stream::ClearPointer(&destColSurf->tris); - } - } - - Utils::Stream::ClearPointer(&dest->colSurf); - } - - if (asset->boneInfo) - { - Assert_Size(Game::XBoneInfo, 28); - - buffer->Align(Utils::Stream::ALIGN_4); - - buffer->SaveArray(asset->boneInfo, asset->numBones); - Utils::Stream::ClearPointer(&dest->boneInfo); - } - - if (asset->physPreset) - { - dest->physPreset = builder->RequireAsset(Game::XAssetType::ASSET_TYPE_PHYSPRESET, asset->physPreset->name).physPreset; - } - - if (asset->physCollmap) - { - dest->physCollmap = builder->RequireAsset(Game::XAssetType::ASSET_TYPE_PHYS_COLLMAP, asset->physCollmap->name).physCollmap; - } - - buffer->PopBlock(); - } -} +#include + +namespace Assets +{ + void IXModel::Load(Game::XAssetHeader* header, std::string name, Components::ZoneBuilder::Zone* builder) + { + Components::FileSystem::File modelFile(fmt::sprintf("xmodel/%s.iw4xModel", name.data())); + + if (modelFile.Exists()) + { + Game::XModel* baseModel = Components::AssetHandler::FindOriginalAsset(Game::XAssetType::ASSET_TYPE_XMODEL, "viewmodel_mp5k").model; + + // Allocate new model and copy the base data to it + Game::XModel* model = builder->GetAllocator()->AllocateArray(); + std::memcpy(model, baseModel, sizeof(Game::XModel)); + + Utils::Stream::Reader reader(builder->GetAllocator(), modelFile.GetBuffer()); + + model->name = reader.ReadCString(); + model->numBones = reader.ReadByte(); + model->numRootBones = reader.ReadByte(); + model->numSurfaces = reader.ReadByte(); + model->numColSurfs = reader.Read(); + + // Read bone names + model->boneNames = builder->GetAllocator()->AllocateArray(model->numBones); + for (int i = 0; i < model->numBones; ++i) + { + model->boneNames[i] = Game::SL_GetString(reader.ReadCString(), 0); + } + + // Bone count + int boneCount = (model->numBones - model->numRootBones); + + // Read bone data + model->parentList = reader.ReadArray(boneCount); + model->tagAngles = reader.ReadArray(boneCount); + model->tagPositions = reader.ReadArray(boneCount); + model->partClassification = reader.ReadArray(boneCount); + model->animMatrix = reader.ReadArray(boneCount); + + // Prepare surfaces + Game::XSurface* baseSurface = &baseModel->lods[0].surfaces[0].surfaces[0]; + Game::XModelSurfs* surf = builder->GetAllocator()->AllocateArray(); + + std::memcpy(surf, baseModel->lods[0].surfaces, sizeof(Game::XModelSurfs)); + surf->name = builder->GetAllocator()->DuplicateString(fmt::sprintf("%s1", model->name)); + surf->surfaces = builder->GetAllocator()->AllocateArray(model->numSurfaces); + surf->numSurfaces = model->numSurfaces; + + model->lods[0].numSurfs = model->numSurfaces; + model->lods[0].surfaces = surf; + + // Reset surfaces in remaining lods + for (unsigned int i = 1; i < 4; ++i) + { + model->lods[i].numSurfs = 0; + model->lods[i].surfaces = nullptr; + } + + // Read surfaces + for (int i = 0; i < surf->numSurfaces; ++i) + { + Game::XSurface* surface = &surf->surfaces[i]; + std::memcpy(surface, baseSurface, sizeof(Game::XSurface)); + + surface->streamHandle = reader.Read(); + surface->something = reader.Read(); + surface->something2 = reader.Read(); + + surface->numVertices = reader.Read(); + surface->numPrimitives = reader.Read(); + surface->numCT = reader.Read(); + + surface->blendNum1 = reader.Read(); + surface->blendNum2 = reader.Read(); + surface->blendNum3 = reader.Read(); + surface->blendNum4 = reader.Read(); + + surface->blendInfo = reinterpret_cast(reader.Read(2, surface->blendNum1 + (3 * surface->blendNum2) + (5 * surface->blendNum3) + (7 * surface->blendNum4))); + + surface->vertexBuffer = reader.ReadArray(surface->numVertices); + surface->indexBuffer = reader.ReadArray(surface->numPrimitives); + + // Read vert list + if (reader.ReadByte()) + { + surface->ct = reader.ReadArray(surface->numCT); + + for (int j = 0; j < surface->numCT; ++j) + { + Game::XRigidVertList* vertList = &surface->ct[j]; + + vertList->entry = reader.ReadArray(); + vertList->entry->node = reinterpret_cast(reader.Read(16, vertList->entry->numNode)); + vertList->entry->leaf = reader.ReadArray(vertList->entry->numLeaf); + } + } + else + { + surface->ct = nullptr; + } + } + + // Read materials + model->materials = builder->GetAllocator()->AllocateArray(model->numSurfaces); + for (char i = 0; i < model->numSurfaces; ++i) + { + model->materials[i] = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_MATERIAL, reader.ReadString(), builder).material; + } + + // Read collision surfaces + if (reader.ReadByte()) + { + model->colSurf = reader.ReadArray(model->numColSurfs); + + for (int i = 0; i < model->numColSurfs; ++i) + { + if (model->colSurf[i].tris) + { + model->colSurf[i].tris = reader.Read(48, model->colSurf[i].count); + } + } + } + else + { + model->colSurf = nullptr; + } + + // Read bone info + if (reader.ReadByte()) + { + model->boneInfo = reader.ReadArray(model->numBones); + } + else + { + model->boneInfo = nullptr; + } + + if (!reader.End()) + { + Components::Logger::Error(0, "Reading model '%s' failed, remaining raw data found!", name.data()); + } + + header->model = model; + } + } + + void IXModel::Mark(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder) + { + Game::XModel* asset = header.model; + + if (asset->boneNames) + { + for (char i = 0; i < asset->numBones; ++i) + { + builder->AddScriptString(asset->boneNames[i]); + } + } + + if (asset->materials) + { + for (char i = 0; i < asset->numSurfaces; ++i) + { + if (asset->materials[i]) + { + builder->LoadAsset(Game::XAssetType::ASSET_TYPE_MATERIAL, asset->materials[i]->name); + } + } + } + + for (int i = 0; i < 4; ++i) + { + if (asset->lods[i].surfaces) + { + // We're not supposed to include xmodelsurfs as standalone asset + //builder->LoadAsset(Game::XAssetType::ASSET_TYPE_XMODELSURFS, asset->lods[i].surfaces->name); + + IXModelSurfs().Mark({ asset->lods[i].surfaces }, builder); + } + } + + if (asset->physPreset) + { + builder->LoadAsset(Game::XAssetType::ASSET_TYPE_PHYSPRESET, asset->physPreset->name); + } + + if (asset->physCollmap) + { + builder->LoadAsset(Game::XAssetType::ASSET_TYPE_PHYS_COLLMAP, asset->physCollmap->name); + } + } + + void IXModel::Save(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder) + { + Assert_Size(Game::XModel, 304); + + Utils::Stream* buffer = builder->GetBuffer(); + Game::XModel* asset = header.model; + Game::XModel* dest = buffer->Dest(); + buffer->Save(asset); + + buffer->PushBlock(Game::XFILE_BLOCK_VIRTUAL); + + if (asset->name) + { + buffer->SaveString(builder->GetAssetName(this->GetType(), asset->name)); + Utils::Stream::ClearPointer(&dest->name); + } + + if (asset->boneNames) + { + buffer->Align(Utils::Stream::ALIGN_2); + + unsigned short* destBoneNames = buffer->Dest(); + buffer->SaveArray(asset->boneNames, asset->numBones); + + for (char i = 0; i < asset->numBones; ++i) + { + builder->MapScriptString(&destBoneNames[i]); + } + + Utils::Stream::ClearPointer(&dest->boneNames); + } + + if (asset->parentList) + { + buffer->Save(asset->parentList, asset->numBones - asset->numRootBones); + Utils::Stream::ClearPointer(&dest->parentList); + } + + if (asset->tagAngles) + { + Assert_Size(Game::XModelAngle, 8); + + buffer->Align(Utils::Stream::ALIGN_2); + buffer->SaveArray(asset->tagAngles, asset->numBones - asset->numRootBones); + Utils::Stream::ClearPointer(&dest->tagAngles); + } + + if (asset->tagPositions) + { + Assert_Size(Game::XModelTagPos, 12); + + buffer->Align(Utils::Stream::ALIGN_4); + buffer->SaveArray(asset->tagPositions, asset->numBones - asset->numRootBones); + Utils::Stream::ClearPointer(&dest->tagPositions); + } + + if (asset->partClassification) + { + buffer->Save(asset->partClassification, asset->numBones); + Utils::Stream::ClearPointer(&dest->partClassification); + } + + if (asset->animMatrix) + { + Assert_Size(Game::DObjAnimMat, 32); + + buffer->Align(Utils::Stream::ALIGN_4); + buffer->SaveArray(asset->animMatrix, asset->numBones); + Utils::Stream::ClearPointer(&dest->animMatrix); + } + + if (asset->materials) + { + buffer->Align(Utils::Stream::ALIGN_4); + + Game::Material** destMaterials = buffer->Dest(); + buffer->SaveArray(asset->materials, asset->numSurfaces); + + for (char i = 0; i < asset->numSurfaces; ++i) + { + if (asset->materials[i]) + { + destMaterials[i] = builder->RequireAsset(Game::XAssetType::ASSET_TYPE_MATERIAL, asset->materials[i]->name).material; + } + } + + Utils::Stream::ClearPointer(&dest->materials); + } + + // Save_XModelLodInfoArray + { + Assert_Size(Game::XModelLodInfo, 44); + + for (int i = 0; i < 4; ++i) + { + if (asset->lods[i].surfaces) + { + // Requiring this asset is not possible, it has to be loaded as part of the model + //dest->lods[i].surfaces = builder->RequireAsset(Game::XAssetType::ASSET_TYPE_XMODELSURFS, asset->lods[i].surfaces->name).surfaces; + + buffer->PushBlock(Game::XFILE_BLOCK_TEMP); + buffer->Align(Utils::Stream::ALIGN_4); + + IXModelSurfs().Save({ asset->lods[i].surfaces }, builder); + Utils::Stream::ClearPointer(&dest->lods[i].surfaces); + + buffer->PopBlock(); + } + } + } + + // Save_XModelCollSurfArray + if (asset->colSurf) + { + Assert_Size(Game::XModelCollSurf, 44); + + buffer->Align(Utils::Stream::ALIGN_4); + + Game::XModelCollSurf* destColSurfs = buffer->Dest(); + buffer->SaveArray(asset->colSurf, asset->numColSurfs); + + for (int i = 0; i < asset->numColSurfs; ++i) + { + Game::XModelCollSurf* destColSurf = &destColSurfs[i]; + Game::XModelCollSurf* colSurf = &asset->colSurf[i]; + + if (colSurf->tris) + { + buffer->Align(Utils::Stream::ALIGN_4); + + buffer->Save(colSurf->tris, 48, colSurf->count); + Utils::Stream::ClearPointer(&destColSurf->tris); + } + } + + Utils::Stream::ClearPointer(&dest->colSurf); + } + + if (asset->boneInfo) + { + Assert_Size(Game::XBoneInfo, 28); + + buffer->Align(Utils::Stream::ALIGN_4); + + buffer->SaveArray(asset->boneInfo, asset->numBones); + Utils::Stream::ClearPointer(&dest->boneInfo); + } + + if (asset->physPreset) + { + dest->physPreset = builder->RequireAsset(Game::XAssetType::ASSET_TYPE_PHYSPRESET, asset->physPreset->name).physPreset; + } + + if (asset->physCollmap) + { + dest->physCollmap = builder->RequireAsset(Game::XAssetType::ASSET_TYPE_PHYS_COLLMAP, asset->physCollmap->name).physCollmap; + } + + buffer->PopBlock(); + } +} diff --git a/src/Components/Modules/Auth.cpp b/src/Components/Modules/Auth.cpp index 32224e7d..b1b7421c 100644 --- a/src/Components/Modules/Auth.cpp +++ b/src/Components/Modules/Auth.cpp @@ -1,494 +1,494 @@ -#include "STDInclude.hpp" - -namespace Components -{ - Auth::AuthInfo Auth::ClientAuthInfo[18]; - Auth::TokenIncrementing Auth::TokenContainer; - - Utils::Cryptography::Token Auth::GuidToken; - Utils::Cryptography::Token Auth::ComputeToken; - Utils::Cryptography::ECC::Key Auth::GuidKey; - - void Auth::Frame() - { -#ifndef DEBUG - for (int i = 0; i < *Game::svs_numclients; i++) - { - Game::client_t* client = &Game::svs_clients[i]; - Auth::AuthInfo* info = &Auth::ClientAuthInfo[i]; - - // State must be 5 or greater here, as otherwise the client will crash when being kicked. - // That's due to the hunk being freed by that time, but it hasn't been reallocated, therefore all future allocations will cause a crash. - // Additionally, the game won't catch the errors and simply lose the connection, so we even have to add a delay to send the data. - - // Not sure if that's potentially unsafe, though. - // Players faking their GUID will be connected for 5 seconds, which allows them to fuck up everything. - // I think we have to perform the verification when clients are still in state 3, but for now it works. - - // I think we even have to lock the client into state 3 until the verification is done. - // Intercepting the entire connection process to perform the authentication within state 3 solely is necessary, due to having a timeout. - // Not sending a response might allow the player to connect for a few seconds (<= 5) until the timeout is reached. - if (client->state >= 5) - { - if (info->state == Auth::STATE_NEGOTIATING && (Game::Sys_Milliseconds() - info->time) > 1000 * 5) - { - info->state = Auth::STATE_INVALID; - info->time = Game::Sys_Milliseconds(); - Game::SV_KickClientError(client, "XUID verification timed out!"); - } - else if (info->state == Auth::STATE_UNKNOWN && info->time && (Game::Sys_Milliseconds() - info->time) > 1000 * 5) // Wait 5 seconds (error delay) - { - if ((client->steamid & 0xFFFFFFFF00000000) != 0x110000100000000) - { - info->state = Auth::STATE_INVALID; - info->time = Game::Sys_Milliseconds(); - Game::SV_KickClientError(client, "Your XUID is invalid!"); - } - else - { - Logger::Print("Sending XUID authentication request to %s\n", Network::Address(client->addr).GetCString()); - - info->state = Auth::STATE_NEGOTIATING; - info->time = Game::Sys_Milliseconds(); - info->challenge = Utils::VA("%X", Utils::Cryptography::Rand::GenerateInt()); - Network::SendCommand(client->addr, "xuidAuthReq", info->challenge); - } - } - else if (info->state == Auth::STATE_UNKNOWN && !info->time) - { - info->time = Game::Sys_Milliseconds(); - } - } - } -#endif - - if (Auth::TokenContainer.generating) - { - static int lastCalc = 0; - static double mseconds = 0; - - if (!lastCalc || (Game::Sys_Milliseconds() - lastCalc) > 500) - { - lastCalc = Game::Sys_Milliseconds(); - - int diff = Game::Sys_Milliseconds() - Auth::TokenContainer.startTime; - double hashPMS = (Auth::TokenContainer.hashes * 1.0) / diff; - double requiredHashes = std::pow(2, Auth::TokenContainer.targetLevel + 1) - Auth::TokenContainer.hashes; - mseconds = requiredHashes / hashPMS; - if (mseconds < 0) mseconds = 0; - } - - Localization::Set("MPUI_SECURITY_INCREASE_MESSAGE", Utils::VA("Increasing security level from %d to %d (est. %s)", Auth::GetSecurityLevel(), Auth::TokenContainer.targetLevel, Utils::FormatTimeSpan(static_cast(mseconds)).data())); - } - else if(Auth::TokenContainer.thread.joinable()) - { - Auth::TokenContainer.thread.join(); - Auth::TokenContainer.generating = false; - - Auth::StoreKey(); - Logger::Print("Security level is %d\n", Auth::GetSecurityLevel()); - Command::Execute("closemenu security_increase_popmenu", false); - - if (!Auth::TokenContainer.cancel) - { - if (Auth::TokenContainer.command.empty()) - { - Game::MessageBox(Utils::VA("Your new security level is %d", Auth::GetSecurityLevel()), "Success"); - } - else - { - Command::Execute(Auth::TokenContainer.command, false); - } - } - - Auth::TokenContainer.cancel = false; - } - } - - void Auth::RegisterClient(int clientNum) - { - if (clientNum >= 18) return; - - Network::Address address(Game::svs_clients[clientNum].addr); - - if (address.GetType() == Game::netadrtype_t::NA_BOT) - { - Auth::ClientAuthInfo[clientNum].state = Auth::STATE_VALID; - } - else - { - Logger::Print("Registering client %s\n", address.GetCString()); - Auth::ClientAuthInfo[clientNum].time = 0; - Auth::ClientAuthInfo[clientNum].state = Auth::STATE_UNKNOWN; - } - } - - void __declspec(naked) Auth::RegisterClientStub() - { - __asm - { - push esi - call Auth::RegisterClient - pop esi - - imul esi, 366Ch - mov eax, 478A18h - jmp eax - } - } - - unsigned int Auth::GetKeyHash() - { - Auth::LoadKey(); - return (Utils::Cryptography::JenkinsOneAtATime::Compute(Auth::GuidKey.GetPublicKey())); - } - - void Auth::StoreKey() - { - if (!Dedicated::IsDedicated() && !ZoneBuilder::IsEnabled()) - { - Proto::Auth::Certificate cert; - cert.set_token(Auth::GuidToken.ToString()); - cert.set_ctoken(Auth::ComputeToken.ToString()); - cert.set_privatekey(Auth::GuidKey.Export(PK_PRIVATE)); - - Utils::WriteFile("players/guid.dat", cert.SerializeAsString()); - } - } - - void Auth::LoadKey(bool force) - { - if (Dedicated::IsDedicated() || ZoneBuilder::IsEnabled()) return; - if (!force && Auth::GuidKey.IsValid()) return; - - Proto::Auth::Certificate cert; - if (cert.ParseFromString(::Utils::ReadFile("players/guid.dat"))) - { - Auth::GuidKey.Import(cert.privatekey(), PK_PRIVATE); - Auth::GuidToken = cert.token(); - Auth::ComputeToken = cert.ctoken(); - } - else - { - Auth::GuidKey.Free(); - } - - if (!Auth::GuidKey.IsValid()) - { - Auth::GuidToken.Clear(); - Auth::ComputeToken.Clear(); - Auth::GuidKey = Utils::Cryptography::ECC::GenerateKey(512); - Auth::StoreKey(); - } - } - - uint32_t Auth::GetSecurityLevel() - { - return Auth::GetZeroBits(Auth::GuidToken, Auth::GuidKey.GetPublicKey()); - } - - void Auth::IncreaseSecurityLevel(uint32_t level, std::string command) - { - if (Auth::GetSecurityLevel() >= level) return; - - if (!Auth::TokenContainer.generating) - { - Auth::TokenContainer.cancel = false; - Auth::TokenContainer.targetLevel = level; - Auth::TokenContainer.command = command; - - // Open menu - Command::Execute("openmenu security_increase_popmenu", true); - - // Start thread - Auth::TokenContainer.thread = std::thread([&level] () - { - Auth::TokenContainer.generating = true; - Auth::TokenContainer.hashes = 0; - Auth::TokenContainer.startTime = Game::Sys_Milliseconds(); - Auth::IncrementToken(Auth::GuidToken, Auth::ComputeToken, Auth::GuidKey.GetPublicKey(), Auth::TokenContainer.targetLevel, &Auth::TokenContainer.cancel, &Auth::TokenContainer.hashes); - Auth::TokenContainer.generating = false; - - if (Auth::TokenContainer.cancel) - { - Logger::Print("Token incrementation thread terminated\n"); - } - }); - } - } - - uint32_t Auth::GetZeroBits(Utils::Cryptography::Token token, std::string publicKey) - { - std::string message = publicKey + token.ToString(); - std::string hash = Utils::Cryptography::SHA512::Compute(message, false); - - uint32_t bits = 0; - - for (unsigned int i = 0; i < hash.size(); ++i) - { - if (hash[i] == '\0') - { - bits += 8; - continue; - } - - uint8_t value = static_cast(hash[i]); - for (int j = 7; j >= 0; --j) - { - if ((value >> j) & 1) - { - return bits; - } - - ++bits; - } - } - - return bits; - } - - void Auth::IncrementToken(Utils::Cryptography::Token& token, Utils::Cryptography::Token& computeToken, std::string publicKey, uint32_t zeroBits, bool* cancel, uint64_t* count) - { - if (zeroBits > 512) return; // Not possible, due to SHA512 - - if (computeToken < token) - { - computeToken = token; - } - - // Check if we already have the desired security level - uint32_t lastLevel = Auth::GetZeroBits(token, publicKey); - uint32_t level = lastLevel; - if (level >= zeroBits) return; - - do - { - ++computeToken; - if (count) ++(*count); - level = Auth::GetZeroBits(computeToken, publicKey); - - // Store level if higher than the last one - if (level >= lastLevel) - { - token = computeToken; - lastLevel = level; - } - - // Allow canceling that shit - if (cancel && *cancel) return; - } - while (level < zeroBits); - - token = computeToken; - } - - Auth::Auth() - { - Auth::TokenContainer.cancel = false; - Auth::TokenContainer.generating = false; - - Localization::Set("MPUI_SECURITY_INCREASE_MESSAGE", ""); - - Auth::LoadKey(true); - - // Only clients receive the auth request - if (!Dedicated::IsDedicated()) - { - Network::Handle("xuidAuthReq", [] (Network::Address address, std::string data) - { - Logger::Print("Received XUID authentication request from %s\n", address.GetCString()); - - // Only accept requests from the server we're connected to - if (address != *Game::connectedHost) return; - - // Ensure our certificate is loaded - Steam::SteamUser()->GetSteamID(); - if (!Auth::GuidKey.IsValid()) return; - - Proto::Auth::Response response; - response.set_token(Auth::GuidToken.ToString()); - response.set_publickey(Auth::GuidKey.GetPublicKey()); - response.set_signature(Utils::Cryptography::ECC::SignMessage(Auth::GuidKey, data)); - - Network::SendCommand(address, "xuidAuthResp", response.SerializeAsString()); - }); - } - - Network::Handle("xuidAuthResp", [] (Network::Address address, std::string data) - { - Logger::Print("Received XUID authentication response from %s\n", address.GetCString()); - - for (int i = 0; i < *Game::svs_numclients; i++) - { - Game::client_t* client = &Game::svs_clients[i]; - Auth::AuthInfo* info = &Auth::ClientAuthInfo[i]; - - if (client->state >= 3 && address == client->addr && info->state == Auth::STATE_NEGOTIATING) - { - Proto::Auth::Response response; - unsigned int id = static_cast(~0x110000100000000 & client->steamid); - - // Check if response is valid - if (!response.ParseFromString(data) || response.signature().empty() || response.publickey().empty() || response.token().empty()) - { - info->state = Auth::STATE_INVALID; - Game::SV_KickClientError(client, "XUID authentication response was invalid!"); - } - - // Check if guid matches the certificate - else if (id != (Utils::Cryptography::JenkinsOneAtATime::Compute(response.publickey()) & ~0x80000000)) - { - info->state = Auth::STATE_INVALID; - Game::SV_KickClientError(client, "XUID doesn't match the certificate!"); - } - - // Verify GUID using the signature and certificate - else - { - info->publicKey.Set(response.publickey()); - - if (Utils::Cryptography::ECC::VerifyMessage(info->publicKey, info->challenge, response.signature())) - { - uint32_t ourLevel = static_cast(Dvar::Var("sv_securityLevel").Get()); - uint32_t userLevel = Auth::GetZeroBits(response.token(), response.publickey()); - - if (userLevel >= ourLevel) - { - info->state = Auth::STATE_VALID; - Logger::Print("Verified XUID %llX (%d) from %s\n", client->steamid, userLevel, address.GetCString()); - } - else - { - info->state = Auth::STATE_INVALID; - Game::SV_KickClientError(client, Utils::VA("Your security level (%d) is lower than the server's security level (%d)", userLevel, ourLevel)); - } - } - else - { - info->state = Auth::STATE_INVALID; - Game::SV_KickClientError(client, "Challenge signature was invalid!"); - } - } - - break; - } - } - }); - - // Install frame handlers - QuickPatch::OnFrame(Auth::Frame); - - // Register dvar - Dvar::Register("sv_securityLevel", 23, 0, 512, Game::dvar_flag::DVAR_FLAG_SERVERINFO, "Security level for GUID certificates (POW)"); - -#ifndef DEBUG - // Install registration hook - Utils::Hook(0x478A12, Auth::RegisterClientStub, HOOK_JUMP).Install()->Quick(); -#endif - - // Guid command - Command::Add("guid", [] (Command::Params params) - { - Logger::Print("Your guid: %llX\n", Steam::SteamUser()->GetSteamID().Bits); - }); - - Command::Add("securityLevel", [] (Command::Params params) - { - if (params.Length() < 2) - { - uint32_t level = Auth::GetZeroBits(Auth::GuidToken, Auth::GuidKey.GetPublicKey()); - Logger::Print("Your current security level is %d\n", level); - Logger::Print("Your security token is: %s\n", Utils::DumpHex(Auth::GuidToken.ToString(), "").data()); - Logger::Print("Your computation token is: %s\n", Utils::DumpHex(Auth::ComputeToken.ToString(), "").data()); - - Toast::Show("cardicon_locked", "^5Security Level", Utils::VA("Your security level is %d", level), 3000); - } - else - { - uint32_t level = static_cast(atoi(params[1])); - Auth::IncreaseSecurityLevel(level); - } - }); - - UIScript::Add("security_increase_cancel", [] () - { - Auth::TokenContainer.cancel = true; - Logger::Print("Token incrementation process canceled!\n"); - }); - } - - Auth::~Auth() - { - Auth::TokenContainer.cancel = true; - Auth::TokenContainer.generating = false; - - // Terminate thread - if (Auth::TokenContainer.thread.joinable()) - { - Auth::TokenContainer.thread.join(); - } - - Auth::StoreKey(); - } - - bool Auth::UnitTest() - { - bool success = true; - - printf("Testing logical token operators:\n"); - - Utils::Cryptography::Token token1; - Utils::Cryptography::Token token2; - ++token1, token2++; // Test incrementation operator - - printf("Operator == : "); - if (token1 == token2 && !(++token1 == token2)) printf("Success\n"); - else - { - printf("Error\n"); - success = false; - } - - printf("Operator != : "); - if (token1 != token2 && !(++token2 != token1)) printf("Success\n"); - else - { - printf("Error\n"); - success = false; - } - - printf("Operator >= : "); - if (token1 >= token2 && ++token1 >= token2) printf("Success\n"); - else - { - printf("Error\n"); - success = false; - } - - printf("Operator > : "); - if (token1 > token2) printf("Success\n"); - else - { - printf("Error\n"); - success = false; - } - - printf("Operator <= : "); - if (token1 <= ++token2 && token1 <= ++token2) printf("Success\n"); - else - { - printf("Error\n"); - success = false; - } - - printf("Operator < : "); - if (token1 < token2) printf("Success\n"); - else - { - printf("Error\n"); - success = false; - } - - return success; - } -} +#include "STDInclude.hpp" + +namespace Components +{ + Auth::AuthInfo Auth::ClientAuthInfo[18]; + Auth::TokenIncrementing Auth::TokenContainer; + + Utils::Cryptography::Token Auth::GuidToken; + Utils::Cryptography::Token Auth::ComputeToken; + Utils::Cryptography::ECC::Key Auth::GuidKey; + + void Auth::Frame() + { +#ifndef DEBUG + for (int i = 0; i < *Game::svs_numclients; i++) + { + Game::client_t* client = &Game::svs_clients[i]; + Auth::AuthInfo* info = &Auth::ClientAuthInfo[i]; + + // State must be 5 or greater here, as otherwise the client will crash when being kicked. + // That's due to the hunk being freed by that time, but it hasn't been reallocated, therefore all future allocations will cause a crash. + // Additionally, the game won't catch the errors and simply lose the connection, so we even have to add a delay to send the data. + + // Not sure if that's potentially unsafe, though. + // Players faking their GUID will be connected for 5 seconds, which allows them to fuck up everything. + // I think we have to perform the verification when clients are still in state 3, but for now it works. + + // I think we even have to lock the client into state 3 until the verification is done. + // Intercepting the entire connection process to perform the authentication within state 3 solely is necessary, due to having a timeout. + // Not sending a response might allow the player to connect for a few seconds (<= 5) until the timeout is reached. + if (client->state >= 5) + { + if (info->state == Auth::STATE_NEGOTIATING && (Game::Sys_Milliseconds() - info->time) > 1000 * 5) + { + info->state = Auth::STATE_INVALID; + info->time = Game::Sys_Milliseconds(); + Game::SV_KickClientError(client, "XUID verification timed out!"); + } + else if (info->state == Auth::STATE_UNKNOWN && info->time && (Game::Sys_Milliseconds() - info->time) > 1000 * 5) // Wait 5 seconds (error delay) + { + if ((client->steamid & 0xFFFFFFFF00000000) != 0x110000100000000) + { + info->state = Auth::STATE_INVALID; + info->time = Game::Sys_Milliseconds(); + Game::SV_KickClientError(client, "Your XUID is invalid!"); + } + else + { + Logger::Print("Sending XUID authentication request to %s\n", Network::Address(client->addr).GetCString()); + + info->state = Auth::STATE_NEGOTIATING; + info->time = Game::Sys_Milliseconds(); + info->challenge = fmt::sprintf("%X", Utils::Cryptography::Rand::GenerateInt()); + Network::SendCommand(client->addr, "xuidAuthReq", info->challenge); + } + } + else if (info->state == Auth::STATE_UNKNOWN && !info->time) + { + info->time = Game::Sys_Milliseconds(); + } + } + } +#endif + + if (Auth::TokenContainer.generating) + { + static int lastCalc = 0; + static double mseconds = 0; + + if (!lastCalc || (Game::Sys_Milliseconds() - lastCalc) > 500) + { + lastCalc = Game::Sys_Milliseconds(); + + int diff = Game::Sys_Milliseconds() - Auth::TokenContainer.startTime; + double hashPMS = (Auth::TokenContainer.hashes * 1.0) / diff; + double requiredHashes = std::pow(2, Auth::TokenContainer.targetLevel + 1) - Auth::TokenContainer.hashes; + mseconds = requiredHashes / hashPMS; + if (mseconds < 0) mseconds = 0; + } + + Localization::Set("MPUI_SECURITY_INCREASE_MESSAGE", fmt::sprintf("Increasing security level from %d to %d (est. %s)", Auth::GetSecurityLevel(), Auth::TokenContainer.targetLevel, Utils::String::FormatTimeSpan(static_cast(mseconds)).data())); + } + else if(Auth::TokenContainer.thread.joinable()) + { + Auth::TokenContainer.thread.join(); + Auth::TokenContainer.generating = false; + + Auth::StoreKey(); + Logger::Print("Security level is %d\n", Auth::GetSecurityLevel()); + Command::Execute("closemenu security_increase_popmenu", false); + + if (!Auth::TokenContainer.cancel) + { + if (Auth::TokenContainer.command.empty()) + { + Game::MessageBox(fmt::sprintf("Your new security level is %d", Auth::GetSecurityLevel()), "Success"); + } + else + { + Command::Execute(Auth::TokenContainer.command, false); + } + } + + Auth::TokenContainer.cancel = false; + } + } + + void Auth::RegisterClient(int clientNum) + { + if (clientNum >= 18) return; + + Network::Address address(Game::svs_clients[clientNum].addr); + + if (address.GetType() == Game::netadrtype_t::NA_BOT) + { + Auth::ClientAuthInfo[clientNum].state = Auth::STATE_VALID; + } + else + { + Logger::Print("Registering client %s\n", address.GetCString()); + Auth::ClientAuthInfo[clientNum].time = 0; + Auth::ClientAuthInfo[clientNum].state = Auth::STATE_UNKNOWN; + } + } + + void __declspec(naked) Auth::RegisterClientStub() + { + __asm + { + push esi + call Auth::RegisterClient + pop esi + + imul esi, 366Ch + mov eax, 478A18h + jmp eax + } + } + + unsigned int Auth::GetKeyHash() + { + Auth::LoadKey(); + return (Utils::Cryptography::JenkinsOneAtATime::Compute(Auth::GuidKey.GetPublicKey())); + } + + void Auth::StoreKey() + { + if (!Dedicated::IsDedicated() && !ZoneBuilder::IsEnabled()) + { + Proto::Auth::Certificate cert; + cert.set_token(Auth::GuidToken.ToString()); + cert.set_ctoken(Auth::ComputeToken.ToString()); + cert.set_privatekey(Auth::GuidKey.Export(PK_PRIVATE)); + + Utils::IO::WriteFile("players/guid.dat", cert.SerializeAsString()); + } + } + + void Auth::LoadKey(bool force) + { + if (Dedicated::IsDedicated() || ZoneBuilder::IsEnabled()) return; + if (!force && Auth::GuidKey.IsValid()) return; + + Proto::Auth::Certificate cert; + if (cert.ParseFromString(::Utils::IO::ReadFile("players/guid.dat"))) + { + Auth::GuidKey.Import(cert.privatekey(), PK_PRIVATE); + Auth::GuidToken = cert.token(); + Auth::ComputeToken = cert.ctoken(); + } + else + { + Auth::GuidKey.Free(); + } + + if (!Auth::GuidKey.IsValid()) + { + Auth::GuidToken.Clear(); + Auth::ComputeToken.Clear(); + Auth::GuidKey = Utils::Cryptography::ECC::GenerateKey(512); + Auth::StoreKey(); + } + } + + uint32_t Auth::GetSecurityLevel() + { + return Auth::GetZeroBits(Auth::GuidToken, Auth::GuidKey.GetPublicKey()); + } + + void Auth::IncreaseSecurityLevel(uint32_t level, std::string command) + { + if (Auth::GetSecurityLevel() >= level) return; + + if (!Auth::TokenContainer.generating) + { + Auth::TokenContainer.cancel = false; + Auth::TokenContainer.targetLevel = level; + Auth::TokenContainer.command = command; + + // Open menu + Command::Execute("openmenu security_increase_popmenu", true); + + // Start thread + Auth::TokenContainer.thread = std::thread([&level] () + { + Auth::TokenContainer.generating = true; + Auth::TokenContainer.hashes = 0; + Auth::TokenContainer.startTime = Game::Sys_Milliseconds(); + Auth::IncrementToken(Auth::GuidToken, Auth::ComputeToken, Auth::GuidKey.GetPublicKey(), Auth::TokenContainer.targetLevel, &Auth::TokenContainer.cancel, &Auth::TokenContainer.hashes); + Auth::TokenContainer.generating = false; + + if (Auth::TokenContainer.cancel) + { + Logger::Print("Token incrementation thread terminated\n"); + } + }); + } + } + + uint32_t Auth::GetZeroBits(Utils::Cryptography::Token token, std::string publicKey) + { + std::string message = publicKey + token.ToString(); + std::string hash = Utils::Cryptography::SHA512::Compute(message, false); + + uint32_t bits = 0; + + for (unsigned int i = 0; i < hash.size(); ++i) + { + if (hash[i] == '\0') + { + bits += 8; + continue; + } + + uint8_t value = static_cast(hash[i]); + for (int j = 7; j >= 0; --j) + { + if ((value >> j) & 1) + { + return bits; + } + + ++bits; + } + } + + return bits; + } + + void Auth::IncrementToken(Utils::Cryptography::Token& token, Utils::Cryptography::Token& computeToken, std::string publicKey, uint32_t zeroBits, bool* cancel, uint64_t* count) + { + if (zeroBits > 512) return; // Not possible, due to SHA512 + + if (computeToken < token) + { + computeToken = token; + } + + // Check if we already have the desired security level + uint32_t lastLevel = Auth::GetZeroBits(token, publicKey); + uint32_t level = lastLevel; + if (level >= zeroBits) return; + + do + { + ++computeToken; + if (count) ++(*count); + level = Auth::GetZeroBits(computeToken, publicKey); + + // Store level if higher than the last one + if (level >= lastLevel) + { + token = computeToken; + lastLevel = level; + } + + // Allow canceling that shit + if (cancel && *cancel) return; + } + while (level < zeroBits); + + token = computeToken; + } + + Auth::Auth() + { + Auth::TokenContainer.cancel = false; + Auth::TokenContainer.generating = false; + + Localization::Set("MPUI_SECURITY_INCREASE_MESSAGE", ""); + + Auth::LoadKey(true); + + // Only clients receive the auth request + if (!Dedicated::IsDedicated()) + { + Network::Handle("xuidAuthReq", [] (Network::Address address, std::string data) + { + Logger::Print("Received XUID authentication request from %s\n", address.GetCString()); + + // Only accept requests from the server we're connected to + if (address != *Game::connectedHost) return; + + // Ensure our certificate is loaded + Steam::SteamUser()->GetSteamID(); + if (!Auth::GuidKey.IsValid()) return; + + Proto::Auth::Response response; + response.set_token(Auth::GuidToken.ToString()); + response.set_publickey(Auth::GuidKey.GetPublicKey()); + response.set_signature(Utils::Cryptography::ECC::SignMessage(Auth::GuidKey, data)); + + Network::SendCommand(address, "xuidAuthResp", response.SerializeAsString()); + }); + } + + Network::Handle("xuidAuthResp", [] (Network::Address address, std::string data) + { + Logger::Print("Received XUID authentication response from %s\n", address.GetCString()); + + for (int i = 0; i < *Game::svs_numclients; i++) + { + Game::client_t* client = &Game::svs_clients[i]; + Auth::AuthInfo* info = &Auth::ClientAuthInfo[i]; + + if (client->state >= 3 && address == client->addr && info->state == Auth::STATE_NEGOTIATING) + { + Proto::Auth::Response response; + unsigned int id = static_cast(~0x110000100000000 & client->steamid); + + // Check if response is valid + if (!response.ParseFromString(data) || response.signature().empty() || response.publickey().empty() || response.token().empty()) + { + info->state = Auth::STATE_INVALID; + Game::SV_KickClientError(client, "XUID authentication response was invalid!"); + } + + // Check if guid matches the certificate + else if (id != (Utils::Cryptography::JenkinsOneAtATime::Compute(response.publickey()) & ~0x80000000)) + { + info->state = Auth::STATE_INVALID; + Game::SV_KickClientError(client, "XUID doesn't match the certificate!"); + } + + // Verify GUID using the signature and certificate + else + { + info->publicKey.Set(response.publickey()); + + if (Utils::Cryptography::ECC::VerifyMessage(info->publicKey, info->challenge, response.signature())) + { + uint32_t ourLevel = static_cast(Dvar::Var("sv_securityLevel").Get()); + uint32_t userLevel = Auth::GetZeroBits(response.token(), response.publickey()); + + if (userLevel >= ourLevel) + { + info->state = Auth::STATE_VALID; + Logger::Print("Verified XUID %llX (%d) from %s\n", client->steamid, userLevel, address.GetCString()); + } + else + { + info->state = Auth::STATE_INVALID; + Game::SV_KickClientError(client, fmt::sprintf("Your security level (%d) is lower than the server's security level (%d)", userLevel, ourLevel)); + } + } + else + { + info->state = Auth::STATE_INVALID; + Game::SV_KickClientError(client, "Challenge signature was invalid!"); + } + } + + break; + } + } + }); + + // Install frame handlers + QuickPatch::OnFrame(Auth::Frame); + + // Register dvar + Dvar::Register("sv_securityLevel", 23, 0, 512, Game::dvar_flag::DVAR_FLAG_SERVERINFO, "Security level for GUID certificates (POW)"); + +#ifndef DEBUG + // Install registration hook + Utils::Hook(0x478A12, Auth::RegisterClientStub, HOOK_JUMP).Install()->Quick(); +#endif + + // Guid command + Command::Add("guid", [] (Command::Params params) + { + Logger::Print("Your guid: %llX\n", Steam::SteamUser()->GetSteamID().Bits); + }); + + Command::Add("securityLevel", [] (Command::Params params) + { + if (params.Length() < 2) + { + uint32_t level = Auth::GetZeroBits(Auth::GuidToken, Auth::GuidKey.GetPublicKey()); + Logger::Print("Your current security level is %d\n", level); + Logger::Print("Your security token is: %s\n", Utils::String::DumpHex(Auth::GuidToken.ToString(), "").data()); + Logger::Print("Your computation token is: %s\n", Utils::String::DumpHex(Auth::ComputeToken.ToString(), "").data()); + + Toast::Show("cardicon_locked", "^5Security Level", fmt::sprintf("Your security level is %d", level), 3000); + } + else + { + uint32_t level = static_cast(atoi(params[1])); + Auth::IncreaseSecurityLevel(level); + } + }); + + UIScript::Add("security_increase_cancel", [] () + { + Auth::TokenContainer.cancel = true; + Logger::Print("Token incrementation process canceled!\n"); + }); + } + + Auth::~Auth() + { + Auth::TokenContainer.cancel = true; + Auth::TokenContainer.generating = false; + + // Terminate thread + if (Auth::TokenContainer.thread.joinable()) + { + Auth::TokenContainer.thread.join(); + } + + Auth::StoreKey(); + } + + bool Auth::UnitTest() + { + bool success = true; + + printf("Testing logical token operators:\n"); + + Utils::Cryptography::Token token1; + Utils::Cryptography::Token token2; + ++token1, token2++; // Test incrementation operator + + printf("Operator == : "); + if (token1 == token2 && !(++token1 == token2)) printf("Success\n"); + else + { + printf("Error\n"); + success = false; + } + + printf("Operator != : "); + if (token1 != token2 && !(++token2 != token1)) printf("Success\n"); + else + { + printf("Error\n"); + success = false; + } + + printf("Operator >= : "); + if (token1 >= token2 && ++token1 >= token2) printf("Success\n"); + else + { + printf("Error\n"); + success = false; + } + + printf("Operator > : "); + if (token1 > token2) printf("Success\n"); + else + { + printf("Error\n"); + success = false; + } + + printf("Operator <= : "); + if (token1 <= ++token2 && token1 <= ++token2) printf("Success\n"); + else + { + printf("Error\n"); + success = false; + } + + printf("Operator < : "); + if (token1 < token2) printf("Success\n"); + else + { + printf("Error\n"); + success = false; + } + + return success; + } +} diff --git a/src/Components/Modules/Command.cpp b/src/Components/Modules/Command.cpp index 0cfb62c5..4e0130f9 100644 --- a/src/Components/Modules/Command.cpp +++ b/src/Components/Modules/Command.cpp @@ -1,137 +1,137 @@ -#include "STDInclude.hpp" - -namespace Components -{ - std::vector Command::Functions; - std::map> Command::FunctionMap; - std::map> Command::FunctionMapSV; - - char* Command::Params::operator[](size_t index) - { - if (index >= this->Length()) return ""; - if (this->IsSV) return Game::cmd_argv_sv[this->CommandId][index]; - else return Game::cmd_argv[this->CommandId][index]; - } - - size_t Command::Params::Length() - { - if (this->IsSV) return Game::cmd_argc_sv[this->CommandId]; - else return Game::cmd_argc[this->CommandId]; - } - - std::string Command::Params::Join(size_t startIndex) - { - std::string result; - - for (size_t i = startIndex; i < this->Length(); ++i) - { - if (i > startIndex) result.append(" "); - result.append(this->operator[](i)); - } - - return result; - } - - void Command::Add(const char* name, Command::Callback* callback) - { - std::string command = Utils::StrToLower(name); - - if (Command::FunctionMap.find(command) == Command::FunctionMap.end()) - { - Command::AddRaw(name, Command::MainCallback); - } - - Command::FunctionMap[command] = callback; - } - - void Command::AddSV(const char* name, Command::Callback* callback) - { - std::string command = Utils::StrToLower(name); - - if (Command::FunctionMapSV.find(command) == Command::FunctionMapSV.end()) - { - Command::AddRawSV(name, Command::MainCallbackSV); - - // If the main command is registered as Cbuf_AddServerText, the command will be redirected to the SV handler - Command::AddRaw(name, Game::Cbuf_AddServerText); - } - - Command::FunctionMapSV[command] = callback; - } - - void Command::AddRaw(const char* name, void(*callback)()) - { - Game::Cmd_AddCommand(name, callback, Command::Allocate(), 0); - } - - void Command::AddRawSV(const char* name, void(*callback)()) - { - Game::Cmd_AddServerCommand(name, callback, Command::Allocate()); - - // If the main command is registered as Cbuf_AddServerText, the command will be redirected to the SV handler - Command::AddRaw(name, Game::Cbuf_AddServerText); - } - - void Command::Execute(std::string command, bool sync) - { - command.append("\n"); // Make sure it's terminated - - if (sync) - { - Game::Cmd_ExecuteSingleCommand(0, 0, command.data()); - } - else - { - Game::Cbuf_AddText(0, command.data()); - } - } - - Game::cmd_function_t* Command::Allocate() - { - Game::cmd_function_t* cmd = new Game::cmd_function_t; - Command::Functions.push_back(cmd); - - return cmd; - } - - void Command::MainCallback() - { - Command::Params params(false, *Game::cmd_id); - - std::string command = Utils::StrToLower(params[0]); - - if (Command::FunctionMap.find(command) != Command::FunctionMap.end()) - { - Command::FunctionMap[command](params); - } - } - - void Command::MainCallbackSV() - { - Command::Params params(true, *Game::cmd_id_sv); - - std::string command = Utils::StrToLower(params[0]); - - if (Command::FunctionMapSV.find(command) != Command::FunctionMapSV.end()) - { - Command::FunctionMapSV[command](params); - } - } - - Command::Command() - { - // TODO: Add commands here? - } - - Command::~Command() - { - for (auto command : Command::Functions) - { - delete command; - } - - Command::Functions.clear(); - Command::FunctionMap.clear(); - Command::FunctionMapSV.clear(); - } -} +#include "STDInclude.hpp" + +namespace Components +{ + std::vector Command::Functions; + std::map> Command::FunctionMap; + std::map> Command::FunctionMapSV; + + char* Command::Params::operator[](size_t index) + { + if (index >= this->Length()) return ""; + if (this->IsSV) return Game::cmd_argv_sv[this->CommandId][index]; + else return Game::cmd_argv[this->CommandId][index]; + } + + size_t Command::Params::Length() + { + if (this->IsSV) return Game::cmd_argc_sv[this->CommandId]; + else return Game::cmd_argc[this->CommandId]; + } + + std::string Command::Params::Join(size_t startIndex) + { + std::string result; + + for (size_t i = startIndex; i < this->Length(); ++i) + { + if (i > startIndex) result.append(" "); + result.append(this->operator[](i)); + } + + return result; + } + + void Command::Add(const char* name, Command::Callback* callback) + { + std::string command = Utils::String::StrToLower(name); + + if (Command::FunctionMap.find(command) == Command::FunctionMap.end()) + { + Command::AddRaw(name, Command::MainCallback); + } + + Command::FunctionMap[command] = callback; + } + + void Command::AddSV(const char* name, Command::Callback* callback) + { + std::string command = Utils::String::StrToLower(name); + + if (Command::FunctionMapSV.find(command) == Command::FunctionMapSV.end()) + { + Command::AddRawSV(name, Command::MainCallbackSV); + + // If the main command is registered as Cbuf_AddServerText, the command will be redirected to the SV handler + Command::AddRaw(name, Game::Cbuf_AddServerText); + } + + Command::FunctionMapSV[command] = callback; + } + + void Command::AddRaw(const char* name, void(*callback)()) + { + Game::Cmd_AddCommand(name, callback, Command::Allocate(), 0); + } + + void Command::AddRawSV(const char* name, void(*callback)()) + { + Game::Cmd_AddServerCommand(name, callback, Command::Allocate()); + + // If the main command is registered as Cbuf_AddServerText, the command will be redirected to the SV handler + Command::AddRaw(name, Game::Cbuf_AddServerText); + } + + void Command::Execute(std::string command, bool sync) + { + command.append("\n"); // Make sure it's terminated + + if (sync) + { + Game::Cmd_ExecuteSingleCommand(0, 0, command.data()); + } + else + { + Game::Cbuf_AddText(0, command.data()); + } + } + + Game::cmd_function_t* Command::Allocate() + { + Game::cmd_function_t* cmd = new Game::cmd_function_t; + Command::Functions.push_back(cmd); + + return cmd; + } + + void Command::MainCallback() + { + Command::Params params(false, *Game::cmd_id); + + std::string command = Utils::String::StrToLower(params[0]); + + if (Command::FunctionMap.find(command) != Command::FunctionMap.end()) + { + Command::FunctionMap[command](params); + } + } + + void Command::MainCallbackSV() + { + Command::Params params(true, *Game::cmd_id_sv); + + std::string command = Utils::String::StrToLower(params[0]); + + if (Command::FunctionMapSV.find(command) != Command::FunctionMapSV.end()) + { + Command::FunctionMapSV[command](params); + } + } + + Command::Command() + { + // TODO: Add commands here? + } + + Command::~Command() + { + for (auto command : Command::Functions) + { + delete command; + } + + Command::Functions.clear(); + Command::FunctionMap.clear(); + Command::FunctionMapSV.clear(); + } +} diff --git a/src/Components/Modules/ConnectProtocol.cpp b/src/Components/Modules/ConnectProtocol.cpp index 5eddf37e..a3757d53 100644 --- a/src/Components/Modules/ConnectProtocol.cpp +++ b/src/Components/Modules/ConnectProtocol.cpp @@ -1,234 +1,234 @@ -#include "STDInclude.hpp" - -namespace Components -{ - ConnectProtocol::Container ConnectProtocol::ConnectContainer = { false, false, "" }; - - bool ConnectProtocol::Evaluated() - { - return ConnectProtocol::ConnectContainer.Evaluated; - } - - bool ConnectProtocol::Used() - { - if (!ConnectProtocol::Evaluated()) - { - ConnectProtocol::EvaluateProtocol(); - } - - return (ConnectProtocol::ConnectContainer.ConnectString.size() > 0); - } - - bool ConnectProtocol::InstallProtocol() - { - HKEY hKey = NULL; - std::string data; - - char ownPth[MAX_PATH] = { 0 }; - char workdir[MAX_PATH] = { 0 }; - - DWORD dwsize = MAX_PATH; - HMODULE hModule = GetModuleHandle(NULL); - - if (hModule != NULL) - { - if (GetModuleFileNameA(hModule, ownPth, MAX_PATH) == ERROR) - { - return false; - } - - if (GetModuleFileNameA(hModule, workdir, MAX_PATH) == ERROR) - { - return false; - } - else - { - char* endPtr = strstr(workdir, "iw4x.exe"); - if (endPtr != NULL) - { - *endPtr = 0; - } - else - { - return false; - } - } - } - else - { - return false; - } - - SetCurrentDirectoryA(workdir); - - LONG openRes = RegOpenKeyExA(HKEY_CURRENT_USER, "SOFTWARE\\Classes\\iw4x\\shell\\open\\command", 0, KEY_ALL_ACCESS, &hKey); - if (openRes == ERROR_SUCCESS) - { - char regred[MAX_PATH] = { 0 }; - - // Check if the game has been moved. - openRes = RegQueryValueExA(hKey, 0, 0, 0, reinterpret_cast(regred), &dwsize); - if (openRes == ERROR_SUCCESS) - { - char* endPtr = strstr(regred, "\" \"%1\""); - if (endPtr != NULL) - { - *endPtr = 0; - } - else - { - return false; - } - - RegCloseKey(hKey); - if (strcmp(regred + 1, ownPth)) - { - openRes = RegDeleteKeyA(HKEY_CURRENT_USER, "SOFTWARE\\Classes\\iw4x"); - } - else - { - return true; - } - } - else - { - openRes = RegDeleteKeyA(HKEY_CURRENT_USER, "SOFTWARE\\Classes\\iw4x"); - } - } - else - { - openRes = RegDeleteKeyA(HKEY_CURRENT_USER, "SOFTWARE\\Classes\\iw4x"); - } - - // Open SOFTWARE\\Classes - openRes = RegOpenKeyExA(HKEY_CURRENT_USER, "SOFTWARE\\Classes", 0, KEY_ALL_ACCESS, &hKey); - - if (openRes != ERROR_SUCCESS) - { - return false; - } - - // Create SOFTWARE\\Classes\\iw4x - openRes = RegCreateKeyExA(hKey, "iw4x", 0, 0, REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, 0, &hKey, 0); - - if (openRes != ERROR_SUCCESS) - { - return false; - } - - // Write URL:IW4x Protocol - data = "URL:IW4x Protocol"; - openRes = RegSetValueExA(hKey, "URL Protocol", 0, REG_SZ, reinterpret_cast(data.data()), data.size() + 1); - - if (openRes != ERROR_SUCCESS) - { - RegCloseKey(hKey); - return false; - } - - // Create SOFTWARE\\Classes\\iw4x\\DefaultIcon - openRes = RegCreateKeyExA(hKey, "DefaultIcon", 0, 0, REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, 0, &hKey, 0); - - if (openRes != ERROR_SUCCESS) - { - return false; - } - - data = Utils::VA("%s,1", ownPth); - openRes = RegSetValueExA(hKey, 0, 0, REG_SZ, reinterpret_cast(data.data()), data.size() + 1); - RegCloseKey(hKey); - - if (openRes != ERROR_SUCCESS) - { - RegCloseKey(hKey); - return false; - } - - openRes = RegOpenKeyExA(HKEY_CURRENT_USER, "SOFTWARE\\Classes\\iw4x", 0, KEY_ALL_ACCESS, &hKey); - - if (openRes != ERROR_SUCCESS) - { - return false; - } - - openRes = RegCreateKeyExA(hKey, "shell\\open\\command", 0, 0, REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, 0, &hKey, 0); - - if (openRes != ERROR_SUCCESS) - { - return false; - } - - data = Utils::VA("\"%s\" \"%s\"", ownPth, "%1"); - openRes = RegSetValueExA(hKey, 0, 0, REG_SZ, reinterpret_cast(data.data()), data.size() + 1); - RegCloseKey(hKey); - - if (openRes != ERROR_SUCCESS) - { - return false; - } - - return true; - } - - void ConnectProtocol::EvaluateProtocol() - { - if (ConnectProtocol::ConnectContainer.Evaluated) return; - ConnectProtocol::ConnectContainer.Evaluated = true; - - std::string cmdLine = GetCommandLineA(); - - auto pos = cmdLine.find("iw4x://"); - - if (pos != std::string::npos) - { - cmdLine = cmdLine.substr(pos + 7); - pos = cmdLine.find_first_of("/"); - - if (pos != std::string::npos) - { - cmdLine = cmdLine.substr(0, pos); - } - - ConnectProtocol::ConnectContainer.ConnectString = cmdLine; - } - } - - ConnectProtocol::ConnectProtocol() - { - // IPC handler - IPCPipe::On("connect", [] (std::string data) - { - Command::Execute(Utils::VA("connect %s", data.data()), false); - }); - - // Invocation handler - // TODO: Don't call it every frame, once is enough! - Renderer::OnFrame([] () - { - if (!ConnectProtocol::ConnectContainer.Invoked && ConnectProtocol::Used()) - { - ConnectProtocol::ConnectContainer.Invoked = true; - Command::Execute(Utils::VA("connect %s", ConnectProtocol::ConnectContainer.ConnectString.data()), false); - } - }); - - ConnectProtocol::InstallProtocol(); - ConnectProtocol::EvaluateProtocol(); - - // Fire protocol handlers - // Make sure this happens after the pipe-initialization! - if (ConnectProtocol::Used()) - { - if (!Singleton::IsFirstInstance()) - { - IPCPipe::Write("connect", ConnectProtocol::ConnectContainer.ConnectString); - ExitProcess(0); - } - else - { - // Only skip intro here, invocation will be done later. - Utils::Hook::Set(0x60BECF, 0xEB); - } - } - } -} +#include "STDInclude.hpp" + +namespace Components +{ + ConnectProtocol::Container ConnectProtocol::ConnectContainer = { false, false, "" }; + + bool ConnectProtocol::Evaluated() + { + return ConnectProtocol::ConnectContainer.Evaluated; + } + + bool ConnectProtocol::Used() + { + if (!ConnectProtocol::Evaluated()) + { + ConnectProtocol::EvaluateProtocol(); + } + + return (ConnectProtocol::ConnectContainer.ConnectString.size() > 0); + } + + bool ConnectProtocol::InstallProtocol() + { + HKEY hKey = NULL; + std::string data; + + char ownPth[MAX_PATH] = { 0 }; + char workdir[MAX_PATH] = { 0 }; + + DWORD dwsize = MAX_PATH; + HMODULE hModule = GetModuleHandle(NULL); + + if (hModule != NULL) + { + if (GetModuleFileNameA(hModule, ownPth, MAX_PATH) == ERROR) + { + return false; + } + + if (GetModuleFileNameA(hModule, workdir, MAX_PATH) == ERROR) + { + return false; + } + else + { + char* endPtr = strstr(workdir, "iw4x.exe"); + if (endPtr != NULL) + { + *endPtr = 0; + } + else + { + return false; + } + } + } + else + { + return false; + } + + SetCurrentDirectoryA(workdir); + + LONG openRes = RegOpenKeyExA(HKEY_CURRENT_USER, "SOFTWARE\\Classes\\iw4x\\shell\\open\\command", 0, KEY_ALL_ACCESS, &hKey); + if (openRes == ERROR_SUCCESS) + { + char regred[MAX_PATH] = { 0 }; + + // Check if the game has been moved. + openRes = RegQueryValueExA(hKey, 0, 0, 0, reinterpret_cast(regred), &dwsize); + if (openRes == ERROR_SUCCESS) + { + char* endPtr = strstr(regred, "\" \"%1\""); + if (endPtr != NULL) + { + *endPtr = 0; + } + else + { + return false; + } + + RegCloseKey(hKey); + if (strcmp(regred + 1, ownPth)) + { + openRes = RegDeleteKeyA(HKEY_CURRENT_USER, "SOFTWARE\\Classes\\iw4x"); + } + else + { + return true; + } + } + else + { + openRes = RegDeleteKeyA(HKEY_CURRENT_USER, "SOFTWARE\\Classes\\iw4x"); + } + } + else + { + openRes = RegDeleteKeyA(HKEY_CURRENT_USER, "SOFTWARE\\Classes\\iw4x"); + } + + // Open SOFTWARE\\Classes + openRes = RegOpenKeyExA(HKEY_CURRENT_USER, "SOFTWARE\\Classes", 0, KEY_ALL_ACCESS, &hKey); + + if (openRes != ERROR_SUCCESS) + { + return false; + } + + // Create SOFTWARE\\Classes\\iw4x + openRes = RegCreateKeyExA(hKey, "iw4x", 0, 0, REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, 0, &hKey, 0); + + if (openRes != ERROR_SUCCESS) + { + return false; + } + + // Write URL:IW4x Protocol + data = "URL:IW4x Protocol"; + openRes = RegSetValueExA(hKey, "URL Protocol", 0, REG_SZ, reinterpret_cast(data.data()), data.size() + 1); + + if (openRes != ERROR_SUCCESS) + { + RegCloseKey(hKey); + return false; + } + + // Create SOFTWARE\\Classes\\iw4x\\DefaultIcon + openRes = RegCreateKeyExA(hKey, "DefaultIcon", 0, 0, REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, 0, &hKey, 0); + + if (openRes != ERROR_SUCCESS) + { + return false; + } + + data = fmt::sprintf("%s,1", ownPth); + openRes = RegSetValueExA(hKey, 0, 0, REG_SZ, reinterpret_cast(data.data()), data.size() + 1); + RegCloseKey(hKey); + + if (openRes != ERROR_SUCCESS) + { + RegCloseKey(hKey); + return false; + } + + openRes = RegOpenKeyExA(HKEY_CURRENT_USER, "SOFTWARE\\Classes\\iw4x", 0, KEY_ALL_ACCESS, &hKey); + + if (openRes != ERROR_SUCCESS) + { + return false; + } + + openRes = RegCreateKeyExA(hKey, "shell\\open\\command", 0, 0, REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, 0, &hKey, 0); + + if (openRes != ERROR_SUCCESS) + { + return false; + } + + data = fmt::sprintf("\"%s\" \"%s\"", ownPth, "%1"); + openRes = RegSetValueExA(hKey, 0, 0, REG_SZ, reinterpret_cast(data.data()), data.size() + 1); + RegCloseKey(hKey); + + if (openRes != ERROR_SUCCESS) + { + return false; + } + + return true; + } + + void ConnectProtocol::EvaluateProtocol() + { + if (ConnectProtocol::ConnectContainer.Evaluated) return; + ConnectProtocol::ConnectContainer.Evaluated = true; + + std::string cmdLine = GetCommandLineA(); + + auto pos = cmdLine.find("iw4x://"); + + if (pos != std::string::npos) + { + cmdLine = cmdLine.substr(pos + 7); + pos = cmdLine.find_first_of("/"); + + if (pos != std::string::npos) + { + cmdLine = cmdLine.substr(0, pos); + } + + ConnectProtocol::ConnectContainer.ConnectString = cmdLine; + } + } + + ConnectProtocol::ConnectProtocol() + { + // IPC handler + IPCPipe::On("connect", [] (std::string data) + { + Command::Execute(fmt::sprintf("connect %s", data.data()), false); + }); + + // Invocation handler + // TODO: Don't call it every frame, once is enough! + Renderer::OnFrame([] () + { + if (!ConnectProtocol::ConnectContainer.Invoked && ConnectProtocol::Used()) + { + ConnectProtocol::ConnectContainer.Invoked = true; + Command::Execute(fmt::sprintf("connect %s", ConnectProtocol::ConnectContainer.ConnectString.data()), false); + } + }); + + ConnectProtocol::InstallProtocol(); + ConnectProtocol::EvaluateProtocol(); + + // Fire protocol handlers + // Make sure this happens after the pipe-initialization! + if (ConnectProtocol::Used()) + { + if (!Singleton::IsFirstInstance()) + { + IPCPipe::Write("connect", ConnectProtocol::ConnectContainer.ConnectString); + ExitProcess(0); + } + else + { + // Only skip intro here, invocation will be done later. + Utils::Hook::Set(0x60BECF, 0xEB); + } + } + } +} diff --git a/src/Components/Modules/Dedicated.cpp b/src/Components/Modules/Dedicated.cpp index fdd59d91..246bcaca 100644 --- a/src/Components/Modules/Dedicated.cpp +++ b/src/Components/Modules/Dedicated.cpp @@ -1,411 +1,411 @@ -#include "STDInclude.hpp" - -namespace Components -{ - wink::signal> Dedicated::FrameSignal; - wink::signal> Dedicated::FrameOnceSignal; - - bool Dedicated::SendChat; - - bool Dedicated::IsDedicated() - { - return Flags::HasFlag("dedicated"); - } - - void Dedicated::InitDedicatedServer() - { - static const char* fastfiles[7] = - { - "code_post_gfx_mp", - "localized_code_post_gfx_mp", - "ui_mp", - "localized_ui_mp", - "common_mp", - "localized_common_mp", - "patch_mp" - }; - - std::memcpy(reinterpret_cast(0x66E1CB0), &fastfiles, sizeof(fastfiles)); - Game::R_LoadGraphicsAssets(); - - Utils::Hook::Call(0x4F84C0)(); - } - - void Dedicated::PostInitialization() - { - Command::Execute("exec autoexec.cfg"); - Command::Execute("onlinegame 1"); - Command::Execute("exec default_xboxlive.cfg"); - Command::Execute("xblive_rankedmatch 1"); - Command::Execute("xblive_privatematch 1"); - Command::Execute("xblive_privateserver 0"); - Command::Execute("xstartprivatematch"); - //Command::Execute("xstartlobby"); - Command::Execute("sv_network_fps 1000"); - Command::Execute("com_maxfps 125"); - - // Process command line? - Utils::Hook::Call(0x60C3D0)(); - } - - void __declspec(naked) Dedicated::PostInitializationStub() - { - __asm - { - call Dedicated::PostInitialization - - // Start Com_EvenLoop - mov eax, 43D140h - jmp eax - } - } - - const char* Dedicated::EvaluateSay(char* text) - { - Dedicated::SendChat = true; - - if (text[1] == '/') - { - Dedicated::SendChat = false; - text[1] = text[0]; - ++text; - } - - return text; - } - - void __declspec(naked) Dedicated::PreSayStub() - { - __asm - { - mov eax, [esp + 100h + 10h] - - push eax - call EvaluateSay - add esp, 4h - - mov [esp + 100h + 10h], eax - - jmp Colors::CleanStrStub - } - } - - void __declspec(naked) Dedicated::PostSayStub() - { - __asm - { - // eax is used by the callee - push eax - - xor eax, eax - mov al, Dedicated::SendChat - - test al, al - jnz return - - // Don't send the chat - pop eax - retn - - return: - pop eax - - // Jump to the target - push 5DF620h - retn - } - } - - void Dedicated::MapRotate() - { - if (!Dedicated::IsDedicated() && Dvar::Var("sv_dontrotate").Get()) - { - Dvar::Var("sv_dontrotate").SetRaw(0); - return; - } - - if (Dvar::Var("party_enable").Get() && Dvar::Var("party_host").Get()) - { - Logger::Print("Not performing map rotation as we are hosting a party!\n"); - return; - } - - Logger::Print("Rotating map...\n"); - - // if nothing, just restart - if (Dvar::Var("sv_mapRotation").Get().empty()) - { - Logger::Print("No rotation defined, restarting map.\n"); - Command::Execute(Utils::VA("map %s", Dvar::Var("mapname").Get()), true); - return; - } - - // first, check if the string contains nothing - if (Dvar::Var("sv_mapRotationCurrent").Get().empty()) - { - Logger::Print("Current map rotation has finished, reloading...\n"); - Dvar::Var("sv_mapRotationCurrent").Set(Dvar::Var("sv_mapRotation").Get()); - } - - std::string rotation = Dvar::Var("sv_mapRotationCurrent").Get(); - - auto tokens = Utils::Explode(rotation, ' '); - - for (unsigned int i = 0; i < (tokens.size() - 1); i += 2) - { - if (i + 1 >= tokens.size()) - { - Dvar::Var("sv_mapRotationCurrent").Set(""); - Command::Execute("map_rotate", true); - return; - } - - std::string key = tokens[i]; - std::string value = tokens[i + 1]; - - if (key == "map") - { - // Rebuild map rotation string - rotation.clear(); - for (unsigned int j = (i + 2); j < tokens.size(); ++j) - { - if (j != (i + 2)) rotation += " "; - rotation += tokens[j]; - } - - Dvar::Var("sv_mapRotationCurrent").Set(rotation); - - Logger::Print("Loading new map: %s\n", value.data()); - Command::Execute(Utils::VA("map %s", value.data()), true); - break; - } - else if (key == "gametype") - { - Logger::Print("Applying new gametype: %s\n", value.data()); - Dvar::Var("g_gametype").Set(value); - } - else - { - Logger::Print("Unsupported maprotation key '%s', motherfucker!\n", key.data()); - } - } - } - - void Dedicated::Heartbeat() - { - int masterPort = Dvar::Var("masterPort").Get(); - const char* masterServerName = Dvar::Var("masterServerName").Get(); - - Network::Address master(Utils::VA("%s:%u", masterServerName, masterPort)); - - Logger::Print("Sending heartbeat to master: %s:%u\n", masterServerName, masterPort); - Network::SendCommand(master, "heartbeat", "IW4"); - } - - void Dedicated::Once(Dedicated::Callback* callback) - { - Dedicated::FrameOnceSignal.connect(callback); - } - - void Dedicated::OnFrame(Dedicated::Callback* callback) - { - Dedicated::FrameSignal.connect(callback); - } - - void Dedicated::FrameStub() - { - Dedicated::FrameSignal(); - Dedicated::FrameOnceSignal(); - Dedicated::FrameOnceSignal.clear(); - Utils::Hook::Call(0x5A8E80)(); - } - - Dedicated::Dedicated() - { - // Map rotation - Utils::Hook::Set(0x4152E8, Dedicated::MapRotate); - Dvar::Register("sv_dontrotate", false, Game::dvar_flag::DVAR_FLAG_CHEAT, ""); - - if (Dedicated::IsDedicated() || ZoneBuilder::IsEnabled()) // Run zonebuilder as dedi :P - { - Dvar::Register("sv_lanOnly", false, Game::dvar_flag::DVAR_FLAG_NONE, "Don't act as node"); - - Utils::Hook(0x60BE98, Dedicated::InitDedicatedServer, HOOK_CALL).Install()->Quick(); - - Utils::Hook::Set(0x683370, 0xC3); // steam sometimes doesn't like the server - - Utils::Hook::Set(0x5B4FF0, 0xC3); // self-registration on party - Utils::Hook::Set(0x426130, 0xC3); // other party stuff? - - Utils::Hook::Set(0x4D7030, 0xC3); // upnp stuff - - Utils::Hook::Set(0x4B0FC3, 0x04); // make CL_Frame do client packets, even for game state 9 - Utils::Hook::Set(0x4F5090, 0xC3); // init sound system (1) - Utils::Hook::Set(0x507B80, 0xC3); // start render thread - Utils::Hook::Set(0x4F84C0, 0xC3); // R_Init caller - Utils::Hook::Set(0x46A630, 0xC3); // init sound system (2) - Utils::Hook::Set(0x41FDE0, 0xC3); // Com_Frame audio processor? - Utils::Hook::Set(0x41B9F0, 0xC3); // called from Com_Frame, seems to do renderer stuff - Utils::Hook::Set(0x41D010, 0xC3); // CL_CheckForResend, which tries to connect to the local server constantly - Utils::Hook::Set(0x62B6C0, 0xC3); // UI expression 'DebugPrint', mainly to prevent some console spam - - Utils::Hook::Set(0x468960, 0xC3); // some mixer-related function called on shutdown - Utils::Hook::Set(0x60AD90, 0); // masterServerName flags - - Utils::Hook::Nop(0x4DCEC9, 2); // some check preventing proper game functioning - Utils::Hook::Nop(0x507C79, 6); // another similar bsp check - Utils::Hook::Nop(0x414E4D, 6); // unknown check in SV_ExecuteClientMessage (0x20F0890 == 0, related to client->f_40) - Utils::Hook::Nop(0x4DCEE9, 5); // some deinit renderer function - Utils::Hook::Nop(0x59A896, 5); // warning message on a removed subsystem - Utils::Hook::Nop(0x4B4EEF, 5); // same as above - Utils::Hook::Nop(0x64CF77, 5); // function detecting video card, causes Direct3DCreate9 to be called - Utils::Hook::Nop(0x60BC52, 0x15); // recommended settings check - - Utils::Hook::Nop(0x45148B, 5); // Disable splash screen - - // isHost script call return 0 - Utils::Hook::Set(0x5DEC04, 0); - - // sv_network_fps max 1000, and uncheat - Utils::Hook::Set(0x4D3C67, 0); // ? - Utils::Hook::Set(0x4D3C69, 1000); - - // r_loadForRenderer default to 0 - Utils::Hook::Set(0x519DDF, 0); - - // disable cheat protection on onlinegame - Utils::Hook::Set(0x404CF7, 0x80); - - // some d3d9 call on error - Utils::Hook::Set(0x508470, 0xC3); - - // stop saving a config_mp.cfg - Utils::Hook::Set(0x60B240, 0xC3); - - // don't load the config - Utils::Hook::Set(0x4B4D19, 0xEB); - - // Dedicated frame handler - Utils::Hook(0x4B0F81, Dedicated::FrameStub, HOOK_CALL).Install()->Quick(); - - // Intercept chat sending - Utils::Hook(0x4D000B, Dedicated::PreSayStub, HOOK_CALL).Install()->Quick(); - Utils::Hook(0x4D00D4, Dedicated::PostSayStub, HOOK_CALL).Install()->Quick(); - Utils::Hook(0x4D0110, Dedicated::PostSayStub, HOOK_CALL).Install()->Quick(); - - if (!ZoneBuilder::IsEnabled()) - { - // Post initialization point - Utils::Hook(0x60BFBF, Dedicated::PostInitializationStub, HOOK_JUMP).Install()->Quick(); - -#ifdef USE_LEGACY_SERVER_LIST - - // Heartbeats - Dedicated::OnFrame([] () - { - static int LastHeartbeat = 0; - - if (Dvar::Var("sv_maxclients").Get() > 0 && !LastHeartbeat || (Game::Com_Milliseconds() - LastHeartbeat) > 120 * 1000) - { - LastHeartbeat = Game::Com_Milliseconds(); - Dedicated::Heartbeat(); - } - }); -#endif - } - - Dvar::OnInit([] () - { - Dvar::Register("sv_sayName", "^7Console", Game::dvar_flag::DVAR_FLAG_NONE, "The name to pose as for 'say' commands"); - - // Say command - Command::AddSV("say", [] (Command::Params params) - { - if (params.Length() < 2) return; - - std::string message = params.Join(1); - std::string name = Dvar::Var("sv_sayName").Get(); - - if (!name.empty()) - { - Game::SV_GameSendServerCommand(-1, 0, Utils::VA("%c \"%s: %s\"", 104, name.data(), message.data())); - Game::Com_Printf(15, "%s: %s\n", name.data(), message.data()); - } - else - { - Game::SV_GameSendServerCommand(-1, 0, Utils::VA("%c \"Console: %s\"", 104, message.data())); - Game::Com_Printf(15, "Console: %s\n", message.data()); - } - }); - - // Tell command - Command::AddSV("tell", [] (Command::Params params) - { - if (params.Length() < 3) return; - - int client = atoi(params[1]); - std::string message = params.Join(2); - std::string name = Dvar::Var("sv_sayName").Get(); - - if (!name.empty()) - { - Game::SV_GameSendServerCommand(client, 0, Utils::VA("%c \"%s: %s\"", 104, name.data(), message.data())); - Game::Com_Printf(15, "%s -> %i: %s\n", name.data(), client, message.data()); - } - else - { - Game::SV_GameSendServerCommand(client, 0, Utils::VA("%c \"Console: %s\"", 104, message.data())); - Game::Com_Printf(15, "Console -> %i: %s\n", client, message.data()); - } - }); - - // Sayraw command - Command::AddSV("sayraw", [] (Command::Params params) - { - if (params.Length() < 2) return; - - std::string message = params.Join(1); - Game::SV_GameSendServerCommand(-1, 0, Utils::VA("%c \"%s\"", 104, message.data())); - Game::Com_Printf(15, "Raw: %s\n", message.data()); - }); - - // Tellraw command - Command::AddSV("tellraw", [] (Command::Params params) - { - if (params.Length() < 3) return; - - int client = atoi(params[1]); - std::string message = params.Join(2); - Game::SV_GameSendServerCommand(client, 0, Utils::VA("%c \"%s\"", 104, message.data())); - Game::Com_Printf(15, "Raw -> %i: %s\n", client, message.data()); - }); - - // ! command - Command::AddSV("!", [] (Command::Params params) - { - if (params.Length() != 2) return; - - int client = -1; - if (params[1] != "all"s) - { - client = atoi(params[1]); - - if (client >= *reinterpret_cast(0x31D938C)) - { - Game::Com_Printf(0, "Invalid player.\n"); - return; - } - } - - Game::SV_GameSendServerCommand(client, 0, Utils::VA("%c \"\"", 106)); - }); - }); - } - } - - Dedicated::~Dedicated() - { - Dedicated::FrameOnceSignal.clear(); - Dedicated::FrameSignal.clear(); - } -} +#include "STDInclude.hpp" + +namespace Components +{ + wink::signal> Dedicated::FrameSignal; + wink::signal> Dedicated::FrameOnceSignal; + + bool Dedicated::SendChat; + + bool Dedicated::IsDedicated() + { + return Flags::HasFlag("dedicated"); + } + + void Dedicated::InitDedicatedServer() + { + static const char* fastfiles[7] = + { + "code_post_gfx_mp", + "localized_code_post_gfx_mp", + "ui_mp", + "localized_ui_mp", + "common_mp", + "localized_common_mp", + "patch_mp" + }; + + std::memcpy(reinterpret_cast(0x66E1CB0), &fastfiles, sizeof(fastfiles)); + Game::R_LoadGraphicsAssets(); + + Utils::Hook::Call(0x4F84C0)(); + } + + void Dedicated::PostInitialization() + { + Command::Execute("exec autoexec.cfg"); + Command::Execute("onlinegame 1"); + Command::Execute("exec default_xboxlive.cfg"); + Command::Execute("xblive_rankedmatch 1"); + Command::Execute("xblive_privatematch 1"); + Command::Execute("xblive_privateserver 0"); + Command::Execute("xstartprivatematch"); + //Command::Execute("xstartlobby"); + Command::Execute("sv_network_fps 1000"); + Command::Execute("com_maxfps 125"); + + // Process command line? + Utils::Hook::Call(0x60C3D0)(); + } + + void __declspec(naked) Dedicated::PostInitializationStub() + { + __asm + { + call Dedicated::PostInitialization + + // Start Com_EvenLoop + mov eax, 43D140h + jmp eax + } + } + + const char* Dedicated::EvaluateSay(char* text) + { + Dedicated::SendChat = true; + + if (text[1] == '/') + { + Dedicated::SendChat = false; + text[1] = text[0]; + ++text; + } + + return text; + } + + void __declspec(naked) Dedicated::PreSayStub() + { + __asm + { + mov eax, [esp + 100h + 10h] + + push eax + call EvaluateSay + add esp, 4h + + mov [esp + 100h + 10h], eax + + jmp Colors::CleanStrStub + } + } + + void __declspec(naked) Dedicated::PostSayStub() + { + __asm + { + // eax is used by the callee + push eax + + xor eax, eax + mov al, Dedicated::SendChat + + test al, al + jnz return + + // Don't send the chat + pop eax + retn + + return: + pop eax + + // Jump to the target + push 5DF620h + retn + } + } + + void Dedicated::MapRotate() + { + if (!Dedicated::IsDedicated() && Dvar::Var("sv_dontrotate").Get()) + { + Dvar::Var("sv_dontrotate").SetRaw(0); + return; + } + + if (Dvar::Var("party_enable").Get() && Dvar::Var("party_host").Get()) + { + Logger::Print("Not performing map rotation as we are hosting a party!\n"); + return; + } + + Logger::Print("Rotating map...\n"); + + // if nothing, just restart + if (Dvar::Var("sv_mapRotation").Get().empty()) + { + Logger::Print("No rotation defined, restarting map.\n"); + Command::Execute(fmt::sprintf("map %s", Dvar::Var("mapname").Get()), true); + return; + } + + // first, check if the string contains nothing + if (Dvar::Var("sv_mapRotationCurrent").Get().empty()) + { + Logger::Print("Current map rotation has finished, reloading...\n"); + Dvar::Var("sv_mapRotationCurrent").Set(Dvar::Var("sv_mapRotation").Get()); + } + + std::string rotation = Dvar::Var("sv_mapRotationCurrent").Get(); + + auto tokens = Utils::String::Explode(rotation, ' '); + + for (unsigned int i = 0; i < (tokens.size() - 1); i += 2) + { + if (i + 1 >= tokens.size()) + { + Dvar::Var("sv_mapRotationCurrent").Set(""); + Command::Execute("map_rotate", true); + return; + } + + std::string key = tokens[i]; + std::string value = tokens[i + 1]; + + if (key == "map") + { + // Rebuild map rotation string + rotation.clear(); + for (unsigned int j = (i + 2); j < tokens.size(); ++j) + { + if (j != (i + 2)) rotation += " "; + rotation += tokens[j]; + } + + Dvar::Var("sv_mapRotationCurrent").Set(rotation); + + Logger::Print("Loading new map: %s\n", value.data()); + Command::Execute(fmt::sprintf("map %s", value.data()), true); + break; + } + else if (key == "gametype") + { + Logger::Print("Applying new gametype: %s\n", value.data()); + Dvar::Var("g_gametype").Set(value); + } + else + { + Logger::Print("Unsupported maprotation key '%s', motherfucker!\n", key.data()); + } + } + } + + void Dedicated::Heartbeat() + { + int masterPort = Dvar::Var("masterPort").Get(); + const char* masterServerName = Dvar::Var("masterServerName").Get(); + + Network::Address master(fmt::sprintf("%s:%u", masterServerName, masterPort)); + + Logger::Print("Sending heartbeat to master: %s:%u\n", masterServerName, masterPort); + Network::SendCommand(master, "heartbeat", "IW4"); + } + + void Dedicated::Once(Dedicated::Callback* callback) + { + Dedicated::FrameOnceSignal.connect(callback); + } + + void Dedicated::OnFrame(Dedicated::Callback* callback) + { + Dedicated::FrameSignal.connect(callback); + } + + void Dedicated::FrameStub() + { + Dedicated::FrameSignal(); + Dedicated::FrameOnceSignal(); + Dedicated::FrameOnceSignal.clear(); + Utils::Hook::Call(0x5A8E80)(); + } + + Dedicated::Dedicated() + { + // Map rotation + Utils::Hook::Set(0x4152E8, Dedicated::MapRotate); + Dvar::Register("sv_dontrotate", false, Game::dvar_flag::DVAR_FLAG_CHEAT, ""); + + if (Dedicated::IsDedicated() || ZoneBuilder::IsEnabled()) // Run zonebuilder as dedi :P + { + Dvar::Register("sv_lanOnly", false, Game::dvar_flag::DVAR_FLAG_NONE, "Don't act as node"); + + Utils::Hook(0x60BE98, Dedicated::InitDedicatedServer, HOOK_CALL).Install()->Quick(); + + Utils::Hook::Set(0x683370, 0xC3); // steam sometimes doesn't like the server + + Utils::Hook::Set(0x5B4FF0, 0xC3); // self-registration on party + Utils::Hook::Set(0x426130, 0xC3); // other party stuff? + + Utils::Hook::Set(0x4D7030, 0xC3); // upnp stuff + + Utils::Hook::Set(0x4B0FC3, 0x04); // make CL_Frame do client packets, even for game state 9 + Utils::Hook::Set(0x4F5090, 0xC3); // init sound system (1) + Utils::Hook::Set(0x507B80, 0xC3); // start render thread + Utils::Hook::Set(0x4F84C0, 0xC3); // R_Init caller + Utils::Hook::Set(0x46A630, 0xC3); // init sound system (2) + Utils::Hook::Set(0x41FDE0, 0xC3); // Com_Frame audio processor? + Utils::Hook::Set(0x41B9F0, 0xC3); // called from Com_Frame, seems to do renderer stuff + Utils::Hook::Set(0x41D010, 0xC3); // CL_CheckForResend, which tries to connect to the local server constantly + Utils::Hook::Set(0x62B6C0, 0xC3); // UI expression 'DebugPrint', mainly to prevent some console spam + + Utils::Hook::Set(0x468960, 0xC3); // some mixer-related function called on shutdown + Utils::Hook::Set(0x60AD90, 0); // masterServerName flags + + Utils::Hook::Nop(0x4DCEC9, 2); // some check preventing proper game functioning + Utils::Hook::Nop(0x507C79, 6); // another similar bsp check + Utils::Hook::Nop(0x414E4D, 6); // unknown check in SV_ExecuteClientMessage (0x20F0890 == 0, related to client->f_40) + Utils::Hook::Nop(0x4DCEE9, 5); // some deinit renderer function + Utils::Hook::Nop(0x59A896, 5); // warning message on a removed subsystem + Utils::Hook::Nop(0x4B4EEF, 5); // same as above + Utils::Hook::Nop(0x64CF77, 5); // function detecting video card, causes Direct3DCreate9 to be called + Utils::Hook::Nop(0x60BC52, 0x15); // recommended settings check + + Utils::Hook::Nop(0x45148B, 5); // Disable splash screen + + // isHost script call return 0 + Utils::Hook::Set(0x5DEC04, 0); + + // sv_network_fps max 1000, and uncheat + Utils::Hook::Set(0x4D3C67, 0); // ? + Utils::Hook::Set(0x4D3C69, 1000); + + // r_loadForRenderer default to 0 + Utils::Hook::Set(0x519DDF, 0); + + // disable cheat protection on onlinegame + Utils::Hook::Set(0x404CF7, 0x80); + + // some d3d9 call on error + Utils::Hook::Set(0x508470, 0xC3); + + // stop saving a config_mp.cfg + Utils::Hook::Set(0x60B240, 0xC3); + + // don't load the config + Utils::Hook::Set(0x4B4D19, 0xEB); + + // Dedicated frame handler + Utils::Hook(0x4B0F81, Dedicated::FrameStub, HOOK_CALL).Install()->Quick(); + + // Intercept chat sending + Utils::Hook(0x4D000B, Dedicated::PreSayStub, HOOK_CALL).Install()->Quick(); + Utils::Hook(0x4D00D4, Dedicated::PostSayStub, HOOK_CALL).Install()->Quick(); + Utils::Hook(0x4D0110, Dedicated::PostSayStub, HOOK_CALL).Install()->Quick(); + + if (!ZoneBuilder::IsEnabled()) + { + // Post initialization point + Utils::Hook(0x60BFBF, Dedicated::PostInitializationStub, HOOK_JUMP).Install()->Quick(); + +#ifdef USE_LEGACY_SERVER_LIST + + // Heartbeats + Dedicated::OnFrame([] () + { + static int LastHeartbeat = 0; + + if (Dvar::Var("sv_maxclients").Get() > 0 && !LastHeartbeat || (Game::Com_Milliseconds() - LastHeartbeat) > 120 * 1000) + { + LastHeartbeat = Game::Com_Milliseconds(); + Dedicated::Heartbeat(); + } + }); +#endif + } + + Dvar::OnInit([] () + { + Dvar::Register("sv_sayName", "^7Console", Game::dvar_flag::DVAR_FLAG_NONE, "The name to pose as for 'say' commands"); + + // Say command + Command::AddSV("say", [] (Command::Params params) + { + if (params.Length() < 2) return; + + std::string message = params.Join(1); + std::string name = Dvar::Var("sv_sayName").Get(); + + if (!name.empty()) + { + Game::SV_GameSendServerCommand(-1, 0, Utils::String::VA("%c \"%s: %s\"", 104, name.data(), message.data())); + Game::Com_Printf(15, "%s: %s\n", name.data(), message.data()); + } + else + { + Game::SV_GameSendServerCommand(-1, 0, Utils::String::VA("%c \"Console: %s\"", 104, message.data())); + Game::Com_Printf(15, "Console: %s\n", message.data()); + } + }); + + // Tell command + Command::AddSV("tell", [] (Command::Params params) + { + if (params.Length() < 3) return; + + int client = atoi(params[1]); + std::string message = params.Join(2); + std::string name = Dvar::Var("sv_sayName").Get(); + + if (!name.empty()) + { + Game::SV_GameSendServerCommand(client, 0, Utils::String::VA("%c \"%s: %s\"", 104, name.data(), message.data())); + Game::Com_Printf(15, "%s -> %i: %s\n", name.data(), client, message.data()); + } + else + { + Game::SV_GameSendServerCommand(client, 0, Utils::String::VA("%c \"Console: %s\"", 104, message.data())); + Game::Com_Printf(15, "Console -> %i: %s\n", client, message.data()); + } + }); + + // Sayraw command + Command::AddSV("sayraw", [] (Command::Params params) + { + if (params.Length() < 2) return; + + std::string message = params.Join(1); + Game::SV_GameSendServerCommand(-1, 0, Utils::String::VA("%c \"%s\"", 104, message.data())); + Game::Com_Printf(15, "Raw: %s\n", message.data()); + }); + + // Tellraw command + Command::AddSV("tellraw", [] (Command::Params params) + { + if (params.Length() < 3) return; + + int client = atoi(params[1]); + std::string message = params.Join(2); + Game::SV_GameSendServerCommand(client, 0, Utils::String::VA("%c \"%s\"", 104, message.data())); + Game::Com_Printf(15, "Raw -> %i: %s\n", client, message.data()); + }); + + // ! command + Command::AddSV("!", [] (Command::Params params) + { + if (params.Length() != 2) return; + + int client = -1; + if (params[1] != "all"s) + { + client = atoi(params[1]); + + if (client >= *reinterpret_cast(0x31D938C)) + { + Game::Com_Printf(0, "Invalid player.\n"); + return; + } + } + + Game::SV_GameSendServerCommand(client, 0, Utils::String::VA("%c \"\"", 106)); + }); + }); + } + } + + Dedicated::~Dedicated() + { + Dedicated::FrameOnceSignal.clear(); + Dedicated::FrameSignal.clear(); + } +} diff --git a/src/Components/Modules/Discovery.cpp b/src/Components/Modules/Discovery.cpp index da4739bd..1f889a59 100644 --- a/src/Components/Modules/Discovery.cpp +++ b/src/Components/Modules/Discovery.cpp @@ -1,93 +1,93 @@ -#include "STDInclude.hpp" - -namespace Components -{ - Discovery::Container Discovery::DiscoveryContainer = { false, false, std::thread() }; - - void Discovery::Perform() - { - Discovery::DiscoveryContainer.Perform = true; - } - - Discovery::Discovery() - { - Dvar::Register("net_discoveryPortRangeMin", 25000, 0, 65535, Game::dvar_flag::DVAR_FLAG_SAVED, "Minimum scan range port for local server discovery"); - Dvar::Register("net_discoveryPortRangeMax", 35000, 1, 65536, Game::dvar_flag::DVAR_FLAG_SAVED, "Maximum scan range port for local server discovery"); - - Discovery::DiscoveryContainer.Perform = false; - Discovery::DiscoveryContainer.Terminate = false; - Discovery::DiscoveryContainer.Thread = std::thread([] () - { - while (!Discovery::DiscoveryContainer.Terminate) - { - if (Discovery::DiscoveryContainer.Perform) - { - int start = Game::Sys_Milliseconds(); - - Logger::Print("Starting local server discovery...\n"); - - Discovery::DiscoveryContainer.Challenge = Utils::VA("%X", Utils::Cryptography::Rand::GenerateInt()); - - unsigned int minPort = Dvar::Var("net_discoveryPortRangeMin").Get(); - unsigned int maxPort = Dvar::Var("net_discoveryPortRangeMax").Get(); - Network::BroadcastRange(minPort, maxPort, Utils::VA("discovery %s", Discovery::DiscoveryContainer.Challenge.data())); - - Logger::Print("Discovery sent within %dms, awaiting responses...\n", Game::Sys_Milliseconds() - start); - - Discovery::DiscoveryContainer.Perform = false; - } - - std::this_thread::sleep_for(50ms); - } - }); - - Network::Handle("discovery", [] (Network::Address address, std::string data) - { - if (address.IsSelf()) return; - - if (!address.IsLocal()) - { - Logger::Print("Received discovery request from non-local address: %s\n", address.GetCString()); - return; - } - - Logger::Print("Received discovery request from %s\n", address.GetCString()); - Network::SendCommand(address, "discoveryResponse", data); - }); - - Network::Handle("discoveryResponse", [] (Network::Address address, std::string data) - { - if (address.IsSelf()) return; - - if (!address.IsLocal()) - { - Logger::Print("Received discovery response from non-local address: %s\n", address.GetCString()); - return; - } - - if (Utils::ParseChallenge(data) != Discovery::DiscoveryContainer.Challenge) - { - Logger::Print("Received discovery with invalid challenge from: %s\n", address.GetCString()); - return; - } - - Logger::Print("Received discovery response from: %s\n", address.GetCString()); - - if (ServerList::IsOfflineList()) - { - ServerList::InsertRequest(address, true); - } - }); - } - - Discovery::~Discovery() - { - Discovery::DiscoveryContainer.Perform = false; - Discovery::DiscoveryContainer.Terminate = true; - - if (Discovery::DiscoveryContainer.Thread.joinable()) - { - Discovery::DiscoveryContainer.Thread.join(); - } - } -} +#include "STDInclude.hpp" + +namespace Components +{ + Discovery::Container Discovery::DiscoveryContainer = { false, false, std::thread() }; + + void Discovery::Perform() + { + Discovery::DiscoveryContainer.Perform = true; + } + + Discovery::Discovery() + { + Dvar::Register("net_discoveryPortRangeMin", 25000, 0, 65535, Game::dvar_flag::DVAR_FLAG_SAVED, "Minimum scan range port for local server discovery"); + Dvar::Register("net_discoveryPortRangeMax", 35000, 1, 65536, Game::dvar_flag::DVAR_FLAG_SAVED, "Maximum scan range port for local server discovery"); + + Discovery::DiscoveryContainer.Perform = false; + Discovery::DiscoveryContainer.Terminate = false; + Discovery::DiscoveryContainer.Thread = std::thread([] () + { + while (!Discovery::DiscoveryContainer.Terminate) + { + if (Discovery::DiscoveryContainer.Perform) + { + int start = Game::Sys_Milliseconds(); + + Logger::Print("Starting local server discovery...\n"); + + Discovery::DiscoveryContainer.Challenge = fmt::sprintf("%X", Utils::Cryptography::Rand::GenerateInt()); + + unsigned int minPort = Dvar::Var("net_discoveryPortRangeMin").Get(); + unsigned int maxPort = Dvar::Var("net_discoveryPortRangeMax").Get(); + Network::BroadcastRange(minPort, maxPort, fmt::sprintf("discovery %s", Discovery::DiscoveryContainer.Challenge.data())); + + Logger::Print("Discovery sent within %dms, awaiting responses...\n", Game::Sys_Milliseconds() - start); + + Discovery::DiscoveryContainer.Perform = false; + } + + std::this_thread::sleep_for(50ms); + } + }); + + Network::Handle("discovery", [] (Network::Address address, std::string data) + { + if (address.IsSelf()) return; + + if (!address.IsLocal()) + { + Logger::Print("Received discovery request from non-local address: %s\n", address.GetCString()); + return; + } + + Logger::Print("Received discovery request from %s\n", address.GetCString()); + Network::SendCommand(address, "discoveryResponse", data); + }); + + Network::Handle("discoveryResponse", [] (Network::Address address, std::string data) + { + if (address.IsSelf()) return; + + if (!address.IsLocal()) + { + Logger::Print("Received discovery response from non-local address: %s\n", address.GetCString()); + return; + } + + if (Utils::ParseChallenge(data) != Discovery::DiscoveryContainer.Challenge) + { + Logger::Print("Received discovery with invalid challenge from: %s\n", address.GetCString()); + return; + } + + Logger::Print("Received discovery response from: %s\n", address.GetCString()); + + if (ServerList::IsOfflineList()) + { + ServerList::InsertRequest(address, true); + } + }); + } + + Discovery::~Discovery() + { + Discovery::DiscoveryContainer.Perform = false; + Discovery::DiscoveryContainer.Terminate = true; + + if (Discovery::DiscoveryContainer.Thread.joinable()) + { + Discovery::DiscoveryContainer.Thread.join(); + } + } +} diff --git a/src/Components/Modules/Download.cpp b/src/Components/Modules/Download.cpp index 0f7fb0fc..de125036 100644 --- a/src/Components/Modules/Download.cpp +++ b/src/Components/Modules/Download.cpp @@ -1,331 +1,331 @@ -#include "STDInclude.hpp" - -namespace Components -{ - mg_mgr Download::Mgr; - - bool Download::IsClient(mg_connection *nc) - { - return (Download::GetClient(nc) != nullptr); - } - - Game::client_t* Download::GetClient(mg_connection *nc) - { - Network::Address address(nc->sa.sa); - - for (int i = 0; i < *Game::svs_numclients; ++i) - { - Game::client_t* client = &Game::svs_clients[i]; - - if (client->state >= 3) - { - if (address.GetIP().full == Network::Address(client->addr).GetIP().full) - { - return client; - } - } - } - - return nullptr; - } - - void Download::Forbid(mg_connection *nc) - { - mg_printf(nc, "HTTP/1.1 403 Forbidden\r\n" - "Content-Type: text/html\r\n" - "Connection: close\r\n" - "\r\n" - "403 - Forbidden"); - - nc->flags |= MG_F_SEND_AND_CLOSE; - } - - void Download::ListHandler(mg_connection *nc, int ev, void *ev_data) - { - // Only handle http requests - if (ev != MG_EV_HTTP_REQUEST) return; - -// if (!Download::IsClient(nc)) -// { -// Download::Forbid(nc); -// } -// else - { - static std::string fsGamePre; - static json11::Json jsonList; - - std::string fsGame = Dvar::Var("fs_game").Get(); - - if (!fsGame.empty() && fsGame != fsGamePre) - { - std::vector fileList; - - fsGamePre = fsGame; - - std::string path = Dvar::Var("fs_basepath").Get() + "\\" + fsGame; - auto list = FileSystem::GetSysFileList(path, "iwd", false); - - list.push_back("mod.ff"); - - for (auto i = list.begin(); i != list.end(); ++i) - { - std::string filename = path + "\\" + *i; - if (strstr(i->data(), "_svr_") == NULL && Utils::FileExists(filename)) - { - std::map file; - std::string fileBuffer = Utils::ReadFile(path + "\\" + *i); - - file["name"] = *i; - file["size"] = static_cast(fileBuffer.size()); - file["hash"] = Utils::Cryptography::SHA256::Compute(fileBuffer, true); - - fileList.push_back(file); - } - } - - jsonList = fileList; - } - - mg_printf(nc, - "HTTP/1.1 200 OK\r\n" - "Content-Type: application/json\r\n" - "Connection: close\r\n" - "\r\n" - "%s", jsonList.dump().data()); - - nc->flags |= MG_F_SEND_AND_CLOSE; - } - } - - void Download::FileHandler(mg_connection *nc, int ev, void *ev_data) - { - // Only handle http requests - if (ev != MG_EV_HTTP_REQUEST) return; - - http_message* message = reinterpret_cast(ev_data); - -// if (!Download::IsClient(nc)) -// { -// Download::Forbid(nc); -// } -// else - { - std::string url(message->uri.p, message->uri.len); - Utils::Replace(url, "\\", "/"); - url = url.substr(6); - - if (url.find_first_of("/") != std::string::npos || (!Utils::EndsWith(url, ".iwd") && url != "mod.ff") || strstr(url.data(), "_svr_") != NULL) - { - Download::Forbid(nc); - return; - } - - std::string fsGame = Dvar::Var("fs_game").Get(); - std::string path = Dvar::Var("fs_basepath").Get() + "\\" + fsGame + "\\" + url; - - if (fsGame.empty() || !Utils::FileExists(path)) - { - mg_printf(nc, - "HTTP/1.1 404 Not Found\r\n" - "Content-Type: text/html\r\n" - "Connection: close\r\n" - "\r\n" - "404 - Not Found %s", path.data()); - } - else - { - std::string file = Utils::ReadFile(path); - - mg_printf(nc, - "HTTP/1.1 200 OK\r\n" - "Content-Type: application/octet-stream\r\n" - "Content-Length: %d\r\n" - "Connection: close\r\n" - "\r\n", file.size()); - - mg_send(nc, file.data(), static_cast(file.size())); - } - - nc->flags |= MG_F_SEND_AND_CLOSE; - } - } - - void Download::InfoHandler(mg_connection *nc, int ev, void *ev_data) - { - // Only handle http requests - if (ev != MG_EV_HTTP_REQUEST) return; - - //http_message* message = reinterpret_cast(ev_data); - - Utils::InfoString status = ServerInfo::GetInfo(); - - std::map info; - info["status"] = status.to_json(); - - std::vector players; - - // Build player list - for (int i = 0; i < atoi(status.Get("sv_maxclients").data()); ++i) // Maybe choose 18 here? - { - std::map playerInfo; - playerInfo["score"] = 0; - playerInfo["ping"] = 0; - playerInfo["name"] = ""; - - if (Dvar::Var("sv_running").Get()) - { - if (Game::svs_clients[i].state < 3) continue; - - playerInfo["score"] = Game::SV_GameClientNum_Score(i); - playerInfo["ping"] = Game::svs_clients[i].ping; - playerInfo["name"] = Game::svs_clients[i].name; - } - else - { - // Score and ping are irrelevant - const char* namePtr = Game::PartyHost_GetMemberName(reinterpret_cast(0x1081C00), i); - if (!namePtr || !namePtr[0]) continue; - - playerInfo["name"] = namePtr; - } - - players.push_back(playerInfo); - } - - info["players"] = players; - - mg_printf(nc, - "HTTP/1.1 200 OK\r\n" - "Content-Type: application/json\r\n" - "Connection: close\r\n" - "Access-Control-Allow-Origin: *\r\n" - "\r\n" - "%s", json11::Json(info).dump().data()); - - nc->flags |= MG_F_SEND_AND_CLOSE; - } - - void Download::EventHandler(mg_connection *nc, int ev, void *ev_data) - { - // Only handle http requests - if (ev != MG_EV_HTTP_REQUEST) return; - - http_message* message = reinterpret_cast(ev_data); - -// if (message->uri.p, message->uri.len == "/"s) -// { -// mg_printf(nc, -// "HTTP/1.1 200 OK\r\n" -// "Content-Type: text/html\r\n" -// "Connection: close\r\n" -// "\r\n" -// "Hi fella!
You are%s connected to this server!", (Download::IsClient(nc) ? " " : " not")); -// -// Game::client_t* client = Download::GetClient(nc); -// -// if (client) -// { -// mg_printf(nc, "
Hello %s!", client->name); -// } -// } -// else - { - //std::string path = (Dvar::Var("fs_basepath").Get() + "\\" BASEGAME "\\html"); - //mg_serve_http_opts opts = { 0 }; - //opts.document_root = path.data(); - //mg_serve_http(nc, message, opts); - - FileSystem::File file; - std::string url = "html" + std::string(message->uri.p, message->uri.len); - - if (Utils::EndsWith(url, "/")) - { - url.append("index.html"); - file = FileSystem::File(url); - } - else - { - file = FileSystem::File(url); - - if (!file.Exists()) - { - url.append("/index.html"); - file = FileSystem::File(url); - } - } - - std::string mimeType = Utils::GetMimeType(url); - - if (file.Exists()) - { - std::string& buffer = file.GetBuffer(); - - mg_printf(nc, - "HTTP/1.1 200 OK\r\n" - "Content-Type: %s\r\n" - "Content-Length: %d\r\n" - "Connection: close\r\n" - "\r\n", mimeType.data(), buffer.size()); - - mg_send(nc, buffer.data(), static_cast(buffer.size())); - } - else - { - mg_printf(nc, - "HTTP/1.1 404 Not Found\r\n" - "Content-Type: text/html\r\n" - "Connection: close\r\n" - "\r\n" - "404 - Not Found"); - } - } - - nc->flags |= MG_F_SEND_AND_CLOSE; - } - - Download::Download() - { - if (Dedicated::IsDedicated()) - { - mg_mgr_init(&Download::Mgr, NULL); - - Network::OnStart([] () - { - mg_connection* nc = mg_bind(&Download::Mgr, Utils::VA("%hu", (Dvar::Var("net_port").Get() & 0xFFFF)), Download::EventHandler); - - // Handle special requests - mg_register_http_endpoint(nc, "/info", Download::InfoHandler); - mg_register_http_endpoint(nc, "/list", Download::ListHandler); - mg_register_http_endpoint(nc, "/file", Download::FileHandler); - - mg_set_protocol_http_websocket(nc); - }); - - QuickPatch::OnFrame([] - { - mg_mgr_poll(&Download::Mgr, 0); - }); - } - else - { - Utils::Hook(0x5AC6E9, [] () - { - // TODO: Perform moddownload here - - Game::CL_DownloadsComplete(0); - }, HOOK_CALL).Install()->Quick(); - } - } - - Download::~Download() - { - if (Dedicated::IsDedicated()) - { - mg_mgr_free(&Download::Mgr); - } - else - { - - } - } -} +#include "STDInclude.hpp" + +namespace Components +{ + mg_mgr Download::Mgr; + + bool Download::IsClient(mg_connection *nc) + { + return (Download::GetClient(nc) != nullptr); + } + + Game::client_t* Download::GetClient(mg_connection *nc) + { + Network::Address address(nc->sa.sa); + + for (int i = 0; i < *Game::svs_numclients; ++i) + { + Game::client_t* client = &Game::svs_clients[i]; + + if (client->state >= 3) + { + if (address.GetIP().full == Network::Address(client->addr).GetIP().full) + { + return client; + } + } + } + + return nullptr; + } + + void Download::Forbid(mg_connection *nc) + { + mg_printf(nc, "HTTP/1.1 403 Forbidden\r\n" + "Content-Type: text/html\r\n" + "Connection: close\r\n" + "\r\n" + "403 - Forbidden"); + + nc->flags |= MG_F_SEND_AND_CLOSE; + } + + void Download::ListHandler(mg_connection *nc, int ev, void *ev_data) + { + // Only handle http requests + if (ev != MG_EV_HTTP_REQUEST) return; + +// if (!Download::IsClient(nc)) +// { +// Download::Forbid(nc); +// } +// else + { + static std::string fsGamePre; + static json11::Json jsonList; + + std::string fsGame = Dvar::Var("fs_game").Get(); + + if (!fsGame.empty() && fsGame != fsGamePre) + { + std::vector fileList; + + fsGamePre = fsGame; + + std::string path = Dvar::Var("fs_basepath").Get() + "\\" + fsGame; + auto list = FileSystem::GetSysFileList(path, "iwd", false); + + list.push_back("mod.ff"); + + for (auto i = list.begin(); i != list.end(); ++i) + { + std::string filename = path + "\\" + *i; + if (strstr(i->data(), "_svr_") == NULL && Utils::IO::FileExists(filename)) + { + std::map file; + std::string fileBuffer = Utils::IO::ReadFile(path + "\\" + *i); + + file["name"] = *i; + file["size"] = static_cast(fileBuffer.size()); + file["hash"] = Utils::Cryptography::SHA256::Compute(fileBuffer, true); + + fileList.push_back(file); + } + } + + jsonList = fileList; + } + + mg_printf(nc, + "HTTP/1.1 200 OK\r\n" + "Content-Type: application/json\r\n" + "Connection: close\r\n" + "\r\n" + "%s", jsonList.dump().data()); + + nc->flags |= MG_F_SEND_AND_CLOSE; + } + } + + void Download::FileHandler(mg_connection *nc, int ev, void *ev_data) + { + // Only handle http requests + if (ev != MG_EV_HTTP_REQUEST) return; + + http_message* message = reinterpret_cast(ev_data); + +// if (!Download::IsClient(nc)) +// { +// Download::Forbid(nc); +// } +// else + { + std::string url(message->uri.p, message->uri.len); + Utils::String::Replace(url, "\\", "/"); + url = url.substr(6); + + if (url.find_first_of("/") != std::string::npos || (!Utils::String::EndsWith(url, ".iwd") && url != "mod.ff") || strstr(url.data(), "_svr_") != NULL) + { + Download::Forbid(nc); + return; + } + + std::string fsGame = Dvar::Var("fs_game").Get(); + std::string path = Dvar::Var("fs_basepath").Get() + "\\" + fsGame + "\\" + url; + + if (fsGame.empty() || !Utils::IO::FileExists(path)) + { + mg_printf(nc, + "HTTP/1.1 404 Not Found\r\n" + "Content-Type: text/html\r\n" + "Connection: close\r\n" + "\r\n" + "404 - Not Found %s", path.data()); + } + else + { + std::string file = Utils::IO::ReadFile(path); + + mg_printf(nc, + "HTTP/1.1 200 OK\r\n" + "Content-Type: application/octet-stream\r\n" + "Content-Length: %d\r\n" + "Connection: close\r\n" + "\r\n", file.size()); + + mg_send(nc, file.data(), static_cast(file.size())); + } + + nc->flags |= MG_F_SEND_AND_CLOSE; + } + } + + void Download::InfoHandler(mg_connection *nc, int ev, void *ev_data) + { + // Only handle http requests + if (ev != MG_EV_HTTP_REQUEST) return; + + //http_message* message = reinterpret_cast(ev_data); + + Utils::InfoString status = ServerInfo::GetInfo(); + + std::map info; + info["status"] = status.to_json(); + + std::vector players; + + // Build player list + for (int i = 0; i < atoi(status.Get("sv_maxclients").data()); ++i) // Maybe choose 18 here? + { + std::map playerInfo; + playerInfo["score"] = 0; + playerInfo["ping"] = 0; + playerInfo["name"] = ""; + + if (Dvar::Var("sv_running").Get()) + { + if (Game::svs_clients[i].state < 3) continue; + + playerInfo["score"] = Game::SV_GameClientNum_Score(i); + playerInfo["ping"] = Game::svs_clients[i].ping; + playerInfo["name"] = Game::svs_clients[i].name; + } + else + { + // Score and ping are irrelevant + const char* namePtr = Game::PartyHost_GetMemberName(reinterpret_cast(0x1081C00), i); + if (!namePtr || !namePtr[0]) continue; + + playerInfo["name"] = namePtr; + } + + players.push_back(playerInfo); + } + + info["players"] = players; + + mg_printf(nc, + "HTTP/1.1 200 OK\r\n" + "Content-Type: application/json\r\n" + "Connection: close\r\n" + "Access-Control-Allow-Origin: *\r\n" + "\r\n" + "%s", json11::Json(info).dump().data()); + + nc->flags |= MG_F_SEND_AND_CLOSE; + } + + void Download::EventHandler(mg_connection *nc, int ev, void *ev_data) + { + // Only handle http requests + if (ev != MG_EV_HTTP_REQUEST) return; + + http_message* message = reinterpret_cast(ev_data); + +// if (message->uri.p, message->uri.len == "/"s) +// { +// mg_printf(nc, +// "HTTP/1.1 200 OK\r\n" +// "Content-Type: text/html\r\n" +// "Connection: close\r\n" +// "\r\n" +// "Hi fella!
You are%s connected to this server!", (Download::IsClient(nc) ? " " : " not")); +// +// Game::client_t* client = Download::GetClient(nc); +// +// if (client) +// { +// mg_printf(nc, "
Hello %s!", client->name); +// } +// } +// else + { + //std::string path = (Dvar::Var("fs_basepath").Get() + "\\" BASEGAME "\\html"); + //mg_serve_http_opts opts = { 0 }; + //opts.document_root = path.data(); + //mg_serve_http(nc, message, opts); + + FileSystem::File file; + std::string url = "html" + std::string(message->uri.p, message->uri.len); + + if (Utils::String::EndsWith(url, "/")) + { + url.append("index.html"); + file = FileSystem::File(url); + } + else + { + file = FileSystem::File(url); + + if (!file.Exists()) + { + url.append("/index.html"); + file = FileSystem::File(url); + } + } + + std::string mimeType = Utils::GetMimeType(url); + + if (file.Exists()) + { + std::string& buffer = file.GetBuffer(); + + mg_printf(nc, + "HTTP/1.1 200 OK\r\n" + "Content-Type: %s\r\n" + "Content-Length: %d\r\n" + "Connection: close\r\n" + "\r\n", mimeType.data(), buffer.size()); + + mg_send(nc, buffer.data(), static_cast(buffer.size())); + } + else + { + mg_printf(nc, + "HTTP/1.1 404 Not Found\r\n" + "Content-Type: text/html\r\n" + "Connection: close\r\n" + "\r\n" + "404 - Not Found"); + } + } + + nc->flags |= MG_F_SEND_AND_CLOSE; + } + + Download::Download() + { + if (Dedicated::IsDedicated()) + { + mg_mgr_init(&Download::Mgr, NULL); + + Network::OnStart([] () + { + mg_connection* nc = mg_bind(&Download::Mgr, Utils::String::VA("%hu", (Dvar::Var("net_port").Get() & 0xFFFF)), Download::EventHandler); + + // Handle special requests + mg_register_http_endpoint(nc, "/info", Download::InfoHandler); + mg_register_http_endpoint(nc, "/list", Download::ListHandler); + mg_register_http_endpoint(nc, "/file", Download::FileHandler); + + mg_set_protocol_http_websocket(nc); + }); + + QuickPatch::OnFrame([] + { + mg_mgr_poll(&Download::Mgr, 0); + }); + } + else + { + Utils::Hook(0x5AC6E9, [] () + { + // TODO: Perform moddownload here + + Game::CL_DownloadsComplete(0); + }, HOOK_CALL).Install()->Quick(); + } + } + + Download::~Download() + { + if (Dedicated::IsDedicated()) + { + mg_mgr_free(&Download::Mgr); + } + else + { + + } + } +} diff --git a/src/Components/Modules/Dvar.cpp b/src/Components/Modules/Dvar.cpp index b9ab7982..4e53f1a7 100644 --- a/src/Components/Modules/Dvar.cpp +++ b/src/Components/Modules/Dvar.cpp @@ -1,207 +1,207 @@ -#include "STDInclude.hpp" - -namespace Components -{ - wink::signal> Dvar::RegistrationSignal; - - Dvar::Var::Var(std::string dvarName) : Var() - { - this->dvar = Game::Dvar_FindVar(dvarName.data()); - - if (!this->dvar) - { - // Quick-register the dvar - Game::Dvar_SetStringByName(dvarName.data(), ""); - this->dvar = Game::Dvar_FindVar(dvarName.data()); - } - } - - template <> Game::dvar_t* Dvar::Var::Get() - { - return this->dvar; - } - template <> char* Dvar::Var::Get() - { - if (this->dvar && this->dvar->type == Game::dvar_type::DVAR_TYPE_STRING && this->dvar->current.string) - { - return this->dvar->current.string; - } - - return ""; - } - template <> const char* Dvar::Var::Get() - { - return this->Get(); - } - template <> int Dvar::Var::Get() - { - if (this->dvar && this->dvar->type == Game::dvar_type::DVAR_TYPE_INT) - { - return this->dvar->current.integer; - } - - return 0; - } - template <> unsigned int Dvar::Var::Get() - { - return static_cast(this->Get()); - } - template <> float Dvar::Var::Get() - { - if (this->dvar && this->dvar->type == Game::dvar_type::DVAR_TYPE_FLOAT) - { - return this->dvar->current.value; - } - - return 0; - } - template <> float* Dvar::Var::Get() - { - static float val[4] = { 0 }; - - if (this->dvar && (this->dvar->type == Game::dvar_type::DVAR_TYPE_FLOAT_2 || this->dvar->type == Game::dvar_type::DVAR_TYPE_FLOAT_3 || this->dvar->type == Game::dvar_type::DVAR_TYPE_FLOAT_4)) - { - return this->dvar->current.vec4; - } - - return val; - } - template <> bool Dvar::Var::Get() - { - if (this->dvar && this->dvar->type == Game::dvar_type::DVAR_TYPE_BOOL) - { - return this->dvar->current.boolean; - } - - return false; - } - template <> std::string Dvar::Var::Get() - { - return this->Get(); - } - - void Dvar::Var::Set(char* string) - { - this->Set(const_cast(string)); - } - void Dvar::Var::Set(const char* string) - { - if (this->dvar && this->dvar->name) - { - Game::Dvar_SetCommand(this->dvar->name, string); - } - } - void Dvar::Var::Set(std::string string) - { - this->Set(string.data()); - } - void Dvar::Var::Set(int integer) - { - if (this->dvar && this->dvar->name) - { - Game::Dvar_SetCommand(this->dvar->name, Utils::VA("%i", integer)); - } - } - void Dvar::Var::Set(float value) - { - if (this->dvar && this->dvar->name) - { - Game::Dvar_SetCommand(this->dvar->name, Utils::VA("%f", value)); - } - } - - void Dvar::Var::SetRaw(int integer) - { - if (this->dvar) - { - this->dvar->current.integer = integer; - } - } - - template<> static Dvar::Var Dvar::Register(const char* name, bool value, Dvar::Flag flag, const char* description) - { - return Game::Dvar_RegisterBool(name, value, flag.val, description); - } - template<> static Dvar::Var Dvar::Register(const char* name, const char* value, Dvar::Flag flag, const char* description) - { - return Game::Dvar_RegisterString(name, value, flag.val, description); - } - template<> static Dvar::Var Dvar::Register(const char* name, int value, int min, int max, Dvar::Flag flag, const char* description) - { - return Game::Dvar_RegisterInt(name, value, min, max, flag.val, description); - } - - void Dvar::OnInit(Dvar::Callback* callback) - { - Dvar::RegistrationSignal.connect(callback); - } - - Game::dvar_t* Dvar::RegisterName(const char* name, const char* default, Game::dvar_flag flag, const char* description) - { - // Run callbacks - Dvar::RegistrationSignal(); - - // Name watcher - Renderer::OnFrame([] () - { - static std::string lastValidName = "Unknown Soldier"; - std::string name = Dvar::Var("name").Get(); - - // Don't perform any checks if name didn't change - if (name == lastValidName) return; - - std::string saneName = Colors::Strip(Utils::Trim(name)); - if (saneName.size() < 3 || (saneName[0] == '[' && saneName[1] == '{')) - { - Logger::Print("Username '%s' is invalid. It must at least be 3 characters long and not appear empty!\n", name.data()); - Dvar::Var("name").Set(lastValidName); - } - else - { - lastValidName = name; - } - }); - - return Dvar::Register(name, "Unknown Soldier", Dvar::Flag(flag | Game::dvar_flag::DVAR_FLAG_SAVED).val, description).Get(); - } - - Dvar::Dvar() - { - // set flags of cg_drawFPS to archive - Utils::Hook::Or(0x4F8F69, Game::dvar_flag::DVAR_FLAG_SAVED); - - // un-cheat cg_fov and add archive flags - Utils::Hook::Xor(0x4F8E35, Game::dvar_flag::DVAR_FLAG_CHEAT | Game::dvar_flag::DVAR_FLAG_SAVED); - - // un-cheat cg_debugInfoCornerOffset and add archive flags - Utils::Hook::Xor(0x4F8FC2, Game::dvar_flag::DVAR_FLAG_CHEAT | Game::dvar_flag::DVAR_FLAG_SAVED); - - // remove archive flags for cg_hudchatposition - Utils::Hook::Xor(0x4F9992, Game::dvar_flag::DVAR_FLAG_SAVED); - - // remove write protection from fs_game - Utils::Hook::Xor(0x6431EA, Game::dvar_flag::DVAR_FLAG_WRITEPROTECTED); - - // set cg_fov max to 90.0 - static float cgFov90 = 90.0f; - Utils::Hook::Set(0x4F8E28, &cgFov90); - - // set max volume to 1 - static float volume = 1.0f; - Utils::Hook::Set(0x408078, &volume); - - // Uncheat ui_showList - Utils::Hook::Xor(0x6310DC, Game::dvar_flag::DVAR_FLAG_CHEAT); - - // Uncheat ui_debugMode - Utils::Hook::Xor(0x6312DE, Game::dvar_flag::DVAR_FLAG_CHEAT); - - // Hook dvar 'name' registration - Utils::Hook(0x40531C, Dvar::RegisterName, HOOK_CALL).Install()->Quick(); - } - - Dvar::~Dvar() - { - Dvar::RegistrationSignal.clear(); - } -} +#include "STDInclude.hpp" + +namespace Components +{ + wink::signal> Dvar::RegistrationSignal; + + Dvar::Var::Var(std::string dvarName) : Var() + { + this->dvar = Game::Dvar_FindVar(dvarName.data()); + + if (!this->dvar) + { + // Quick-register the dvar + Game::Dvar_SetStringByName(dvarName.data(), ""); + this->dvar = Game::Dvar_FindVar(dvarName.data()); + } + } + + template <> Game::dvar_t* Dvar::Var::Get() + { + return this->dvar; + } + template <> char* Dvar::Var::Get() + { + if (this->dvar && this->dvar->type == Game::dvar_type::DVAR_TYPE_STRING && this->dvar->current.string) + { + return this->dvar->current.string; + } + + return ""; + } + template <> const char* Dvar::Var::Get() + { + return this->Get(); + } + template <> int Dvar::Var::Get() + { + if (this->dvar && this->dvar->type == Game::dvar_type::DVAR_TYPE_INT) + { + return this->dvar->current.integer; + } + + return 0; + } + template <> unsigned int Dvar::Var::Get() + { + return static_cast(this->Get()); + } + template <> float Dvar::Var::Get() + { + if (this->dvar && this->dvar->type == Game::dvar_type::DVAR_TYPE_FLOAT) + { + return this->dvar->current.value; + } + + return 0; + } + template <> float* Dvar::Var::Get() + { + static float val[4] = { 0 }; + + if (this->dvar && (this->dvar->type == Game::dvar_type::DVAR_TYPE_FLOAT_2 || this->dvar->type == Game::dvar_type::DVAR_TYPE_FLOAT_3 || this->dvar->type == Game::dvar_type::DVAR_TYPE_FLOAT_4)) + { + return this->dvar->current.vec4; + } + + return val; + } + template <> bool Dvar::Var::Get() + { + if (this->dvar && this->dvar->type == Game::dvar_type::DVAR_TYPE_BOOL) + { + return this->dvar->current.boolean; + } + + return false; + } + template <> std::string Dvar::Var::Get() + { + return this->Get(); + } + + void Dvar::Var::Set(char* string) + { + this->Set(const_cast(string)); + } + void Dvar::Var::Set(const char* string) + { + if (this->dvar && this->dvar->name) + { + Game::Dvar_SetCommand(this->dvar->name, string); + } + } + void Dvar::Var::Set(std::string string) + { + this->Set(string.data()); + } + void Dvar::Var::Set(int integer) + { + if (this->dvar && this->dvar->name) + { + Game::Dvar_SetCommand(this->dvar->name, Utils::String::VA("%i", integer)); + } + } + void Dvar::Var::Set(float value) + { + if (this->dvar && this->dvar->name) + { + Game::Dvar_SetCommand(this->dvar->name, Utils::String::VA("%f", value)); + } + } + + void Dvar::Var::SetRaw(int integer) + { + if (this->dvar) + { + this->dvar->current.integer = integer; + } + } + + template<> static Dvar::Var Dvar::Register(const char* name, bool value, Dvar::Flag flag, const char* description) + { + return Game::Dvar_RegisterBool(name, value, flag.val, description); + } + template<> static Dvar::Var Dvar::Register(const char* name, const char* value, Dvar::Flag flag, const char* description) + { + return Game::Dvar_RegisterString(name, value, flag.val, description); + } + template<> static Dvar::Var Dvar::Register(const char* name, int value, int min, int max, Dvar::Flag flag, const char* description) + { + return Game::Dvar_RegisterInt(name, value, min, max, flag.val, description); + } + + void Dvar::OnInit(Dvar::Callback* callback) + { + Dvar::RegistrationSignal.connect(callback); + } + + Game::dvar_t* Dvar::RegisterName(const char* name, const char* default, Game::dvar_flag flag, const char* description) + { + // Run callbacks + Dvar::RegistrationSignal(); + + // Name watcher + Renderer::OnFrame([] () + { + static std::string lastValidName = "Unknown Soldier"; + std::string name = Dvar::Var("name").Get(); + + // Don't perform any checks if name didn't change + if (name == lastValidName) return; + + std::string saneName = Colors::Strip(Utils::String::Trim(name)); + if (saneName.size() < 3 || (saneName[0] == '[' && saneName[1] == '{')) + { + Logger::Print("Username '%s' is invalid. It must at least be 3 characters long and not appear empty!\n", name.data()); + Dvar::Var("name").Set(lastValidName); + } + else + { + lastValidName = name; + } + }); + + return Dvar::Register(name, "Unknown Soldier", Dvar::Flag(flag | Game::dvar_flag::DVAR_FLAG_SAVED).val, description).Get(); + } + + Dvar::Dvar() + { + // set flags of cg_drawFPS to archive + Utils::Hook::Or(0x4F8F69, Game::dvar_flag::DVAR_FLAG_SAVED); + + // un-cheat cg_fov and add archive flags + Utils::Hook::Xor(0x4F8E35, Game::dvar_flag::DVAR_FLAG_CHEAT | Game::dvar_flag::DVAR_FLAG_SAVED); + + // un-cheat cg_debugInfoCornerOffset and add archive flags + Utils::Hook::Xor(0x4F8FC2, Game::dvar_flag::DVAR_FLAG_CHEAT | Game::dvar_flag::DVAR_FLAG_SAVED); + + // remove archive flags for cg_hudchatposition + Utils::Hook::Xor(0x4F9992, Game::dvar_flag::DVAR_FLAG_SAVED); + + // remove write protection from fs_game + Utils::Hook::Xor(0x6431EA, Game::dvar_flag::DVAR_FLAG_WRITEPROTECTED); + + // set cg_fov max to 90.0 + static float cgFov90 = 90.0f; + Utils::Hook::Set(0x4F8E28, &cgFov90); + + // set max volume to 1 + static float volume = 1.0f; + Utils::Hook::Set(0x408078, &volume); + + // Uncheat ui_showList + Utils::Hook::Xor(0x6310DC, Game::dvar_flag::DVAR_FLAG_CHEAT); + + // Uncheat ui_debugMode + Utils::Hook::Xor(0x6312DE, Game::dvar_flag::DVAR_FLAG_CHEAT); + + // Hook dvar 'name' registration + Utils::Hook(0x40531C, Dvar::RegisterName, HOOK_CALL).Install()->Quick(); + } + + Dvar::~Dvar() + { + Dvar::RegistrationSignal.clear(); + } +} diff --git a/src/Components/Modules/Exception.cpp b/src/Components/Modules/Exception.cpp index 232bbe86..b317cef7 100644 --- a/src/Components/Modules/Exception.cpp +++ b/src/Components/Modules/Exception.cpp @@ -1,101 +1,101 @@ -#include "STDInclude.hpp" - -// Stuff causes warnings -#pragma warning(push) -#pragma warning(disable: 4091) -#include -#pragma comment(lib, "dbghelp.lib") -#pragma warning(pop) - -namespace Components -{ - LONG WINAPI Exception::ExceptionFilter(LPEXCEPTION_POINTERS ExceptionInfo) - { - char filename[MAX_PATH]; - __time64_t time; - tm* ltime; - - _time64(&time); - ltime = _localtime64(&time); - strftime(filename, sizeof(filename) - 1, "iw4x-" VERSION_STR "-%Y%m%d%H%M%S.dmp", ltime); - - HANDLE hFile = CreateFileA(filename, GENERIC_WRITE, FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); - - if (hFile && hFile != INVALID_HANDLE_VALUE) - { - MINIDUMP_EXCEPTION_INFORMATION ex = { GetCurrentThreadId(),ExceptionInfo, FALSE }; - MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), hFile, MiniDumpNormal, &ex, NULL, NULL); - CloseHandle(hFile); - } - - if (ExceptionInfo->ExceptionRecord->ExceptionCode == EXCEPTION_STACK_OVERFLOW) - { - Logger::Error("Termination because of a stack overflow.\n"); - TerminateProcess(GetCurrentProcess(), EXCEPTION_STACK_OVERFLOW); - } - else - { - Logger::Error("Fatal error (0x%08x) at 0x%08x.", ExceptionInfo->ExceptionRecord->ExceptionCode, ExceptionInfo->ExceptionRecord->ExceptionAddress); - } - - return EXCEPTION_CONTINUE_SEARCH; - } - - LPTOP_LEVEL_EXCEPTION_FILTER WINAPI Exception::SetUnhandledExceptionFilterStub(LPTOP_LEVEL_EXCEPTION_FILTER lpTopLevelExceptionFilter) - { - SetUnhandledExceptionFilter(&Exception::ExceptionFilter); - return lpTopLevelExceptionFilter; - } - - Exception::Exception() - { -#ifdef DEBUG - // Display DEBUG branding, so we know we're on a debug build - Renderer::OnFrame([] () - { - Game::Font* font = Game::R_RegisterFont("fonts/normalFont"); - float color[4] = { 1.0f, 1.0f, 1.0f, 1.0f }; - - // Change the color when attaching a debugger - if (IsDebuggerPresent()) - { - color[0] = 0.6588f; - color[1] = 1.0000f; - color[2] = 0.0000f; - } - - Game::R_AddCmdDrawText("DEBUG-BUILD", 0x7FFFFFFF, font, 15.0f, 10.0f + Game::R_TextHeight(font), 1.0f, 1.0f, 0.0f, color, Game::ITEM_TEXTSTYLE_SHADOWED); - }); -#else - Utils::Hook::Set(0x6D70AC, Exception::SetUnhandledExceptionFilterStub); - SetUnhandledExceptionFilter(&Exception::ExceptionFilter); -#endif - - Command::Add("mapTest", [] (Command::Params params) - { - std::string command; - - int max = (params.Length() >= 2 ? atoi(params[1]) : 16), current = 0; - - for (int i =0;;) - { - char* mapname = reinterpret_cast(0x7471D0) + 40 * i; - if (!*mapname) - { - i = 0; - continue; - } - - if(!(i % 2)) command.append(Utils::VA("wait 250;disconnect;wait 750;", mapname)); // Test a disconnect - else command.append(Utils::VA("wait 500;", mapname)); // Test direct map switch - command.append(Utils::VA("map %s;", mapname)); - - ++i, ++current; - - if (current >= max) break; - } - - Command::Execute(command, false); - }); - } -} +#include "STDInclude.hpp" + +// Stuff causes warnings +#pragma warning(push) +#pragma warning(disable: 4091) +#include +#pragma comment(lib, "dbghelp.lib") +#pragma warning(pop) + +namespace Components +{ + LONG WINAPI Exception::ExceptionFilter(LPEXCEPTION_POINTERS ExceptionInfo) + { + char filename[MAX_PATH]; + __time64_t time; + tm* ltime; + + _time64(&time); + ltime = _localtime64(&time); + strftime(filename, sizeof(filename) - 1, "iw4x-" VERSION_STR "-%Y%m%d%H%M%S.dmp", ltime); + + HANDLE hFile = CreateFileA(filename, GENERIC_WRITE, FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + + if (hFile && hFile != INVALID_HANDLE_VALUE) + { + MINIDUMP_EXCEPTION_INFORMATION ex = { GetCurrentThreadId(),ExceptionInfo, FALSE }; + MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), hFile, MiniDumpNormal, &ex, NULL, NULL); + CloseHandle(hFile); + } + + if (ExceptionInfo->ExceptionRecord->ExceptionCode == EXCEPTION_STACK_OVERFLOW) + { + Logger::Error("Termination because of a stack overflow.\n"); + TerminateProcess(GetCurrentProcess(), EXCEPTION_STACK_OVERFLOW); + } + else + { + Logger::Error("Fatal error (0x%08x) at 0x%08x.", ExceptionInfo->ExceptionRecord->ExceptionCode, ExceptionInfo->ExceptionRecord->ExceptionAddress); + } + + return EXCEPTION_CONTINUE_SEARCH; + } + + LPTOP_LEVEL_EXCEPTION_FILTER WINAPI Exception::SetUnhandledExceptionFilterStub(LPTOP_LEVEL_EXCEPTION_FILTER lpTopLevelExceptionFilter) + { + SetUnhandledExceptionFilter(&Exception::ExceptionFilter); + return lpTopLevelExceptionFilter; + } + + Exception::Exception() + { +#ifdef DEBUG + // Display DEBUG branding, so we know we're on a debug build + Renderer::OnFrame([] () + { + Game::Font* font = Game::R_RegisterFont("fonts/normalFont"); + float color[4] = { 1.0f, 1.0f, 1.0f, 1.0f }; + + // Change the color when attaching a debugger + if (IsDebuggerPresent()) + { + color[0] = 0.6588f; + color[1] = 1.0000f; + color[2] = 0.0000f; + } + + Game::R_AddCmdDrawText("DEBUG-BUILD", 0x7FFFFFFF, font, 15.0f, 10.0f + Game::R_TextHeight(font), 1.0f, 1.0f, 0.0f, color, Game::ITEM_TEXTSTYLE_SHADOWED); + }); +#else + Utils::Hook::Set(0x6D70AC, Exception::SetUnhandledExceptionFilterStub); + SetUnhandledExceptionFilter(&Exception::ExceptionFilter); +#endif + + Command::Add("mapTest", [] (Command::Params params) + { + std::string command; + + int max = (params.Length() >= 2 ? atoi(params[1]) : 16), current = 0; + + for (int i =0;;) + { + char* mapname = reinterpret_cast(0x7471D0) + 40 * i; + if (!*mapname) + { + i = 0; + continue; + } + + if(!(i % 2)) command.append(fmt::sprintf("wait 250;disconnect;wait 750;", mapname)); // Test a disconnect + else command.append(fmt::sprintf("wait 500;", mapname)); // Test direct map switch + command.append(fmt::sprintf("map %s;", mapname)); + + ++i, ++current; + + if (current >= max) break; + } + + Command::Execute(command, false); + }); + } +} diff --git a/src/Components/Modules/FastFiles.cpp b/src/Components/Modules/FastFiles.cpp index addc4047..a9c93fb6 100644 --- a/src/Components/Modules/FastFiles.cpp +++ b/src/Components/Modules/FastFiles.cpp @@ -1,226 +1,226 @@ -#include "STDInclude.hpp" - -namespace Components -{ - std::vector FastFiles::ZonePaths; - - // This has to be called only once, when the game starts - void FastFiles::LoadInitialZones(Game::XZoneInfo *zoneInfo, unsigned int zoneCount, int sync) - { - std::vector data; - Utils::Merge(&data, zoneInfo, zoneCount); - - if (FastFiles::Exists("iw4x_patch_mp")) - { - data.push_back({ "iw4x_patch_mp", 1, 0 }); - } - - // Load custom weapons, if present (force that later on) - if (FastFiles::Exists("iw4x_weapons_mp")) - { - data.push_back({ "iw4x_weapons_mp", 1, 0 }); - } - - return FastFiles::LoadDLCUIZones(data.data(), data.size(), sync); - } - - // This has to be called every time the cgame is reinitialized - void FastFiles::LoadDLCUIZones(Game::XZoneInfo *zoneInfo, unsigned int zoneCount, int sync) - { - std::vector data; - Utils::Merge(&data, zoneInfo, zoneCount); - - Game::XZoneInfo info = { nullptr, 2, 0 }; - - // Custom ui stuff - if (FastFiles::Exists("iw4x_ui_mp")) - { - info.name = "iw4x_ui_mp"; - data.push_back(info); - } - else // Fallback - { - info.name = "dlc1_ui_mp"; - data.push_back(info); - - info.name = "dlc2_ui_mp"; - data.push_back(info); - } - - return FastFiles::LoadLocalizeZones(data.data(), data.size(), sync); - } - - void FastFiles::LoadGfxZones(Game::XZoneInfo *zoneInfo, unsigned int zoneCount, int sync) - { - std::vector data; - Utils::Merge(&data, zoneInfo, zoneCount); - - if (FastFiles::Exists("iw4x_code_post_gfx_mp")) - { - data.push_back({ "iw4x_code_post_gfx_mp", zoneInfo->allocFlags, zoneInfo->freeFlags }); - } - - Game::DB_LoadXAssets(data.data(), data.size(), sync); - } - - // This has to be called every time fastfiles are loaded :D - void FastFiles::LoadLocalizeZones(Game::XZoneInfo *zoneInfo, unsigned int zoneCount, int sync) - { - std::vector data; - Utils::Merge(&data, zoneInfo, zoneCount); - - Game::XZoneInfo info = { nullptr, 4, 0 }; - - // Not sure how they should be loaded :S - std::string langZone = Utils::VA("iw4x_localized_%s", Game::Win_GetLanguage()); - - if (FastFiles::Exists(langZone)) - { - info.name = langZone.data(); - } - else if (FastFiles::Exists("iw4x_localized_english")) // Fallback - { - info.name = "iw4x_localized_english"; - } - - data.push_back(info); - - Game::DB_LoadXAssets(data.data(), data.size(), sync); - } - - // Name is a bit weird, due to FasFileS and ExistS :P - bool FastFiles::Exists(std::string file) - { - std::string path = FastFiles::GetZoneLocation(file.data()); - path.append(file); - - if (!Utils::EndsWith(path.data(), ".ff")) - { - path.append(".ff"); - } - - return std::ifstream(path.data()).good(); - } - - const char* FastFiles::GetZoneLocation(const char* file) - { - const char* dir = Dvar::Var("fs_basepath").Get(); - - for (auto &path : FastFiles::ZonePaths) - { - std::string absoluteFile = Utils::VA("%s\\%s%s", dir, path.data(), file); - - // No ".ff" appended, append it manually - if (!Utils::EndsWith(absoluteFile.data(), ".ff")) - { - absoluteFile.append(".ff"); - } - - // Check if FastFile exists - if (std::ifstream(absoluteFile.data()).good()) - { - return Utils::VA("%s", path.data()); - } - } - - return Utils::VA("zone\\%s\\", Game::Win_GetLanguage()); - } - - void FastFiles::AddZonePath(std::string path) - { - FastFiles::ZonePaths.push_back(path); - } - - std::string FastFiles::Current() - { - const char* file = (Utils::Hook::Get(0x112A680) + 4); - - if (file == reinterpret_cast(4)) - { - return ""; - } - - return file; - } - - void FastFiles::ReadVersionStub(unsigned int* version, int size) - { - Game::DB_ReadXFileUncompressed(version, size); - - // Allow loading out custom version - if (*version == XFILE_VERSION_IW4X) - { - *version = XFILE_VERSION; - } - } - - FastFiles::FastFiles() - { - Dvar::Register("ui_zoneDebug", false, Game::dvar_flag::DVAR_FLAG_SAVED, "Display current loaded zone."); - - // Redirect zone paths - Utils::Hook(0x44DA90, FastFiles::GetZoneLocation, HOOK_JUMP).Install()->Quick(); - - // Allow loading 'newer' zones - Utils::Hook(0x4158E7, FastFiles::ReadVersionStub, HOOK_CALL).Install()->Quick(); - - // Allow custom zone loading - if (!ZoneBuilder::IsEnabled()) - { - Utils::Hook(0x506BC7, FastFiles::LoadInitialZones, HOOK_CALL).Install()->Quick(); - Utils::Hook(0x60B4AC, FastFiles::LoadDLCUIZones, HOOK_CALL).Install()->Quick(); - Utils::Hook(0x506B25, FastFiles::LoadGfxZones, HOOK_CALL).Install()->Quick(); - } - - // basic checks (hash jumps, both normal and playlist) - Utils::Hook::Nop(0x5B97A3, 2); - Utils::Hook::Nop(0x5BA493, 2); - - Utils::Hook::Nop(0x5B991C, 2); - Utils::Hook::Nop(0x5BA60C, 2); - - Utils::Hook::Nop(0x5B97B4, 2); - Utils::Hook::Nop(0x5BA4A4, 2); - - // allow loading of IWffu (unsigned) files - Utils::Hook::Set(0x4158D9, 0xEB); // main function - Utils::Hook::Nop(0x4A1D97, 2); // DB_AuthLoad_InflateInit - - // some other, unknown, check - Utils::Hook::Set(0x5B9912, 0xB8); - Utils::Hook::Set(0x5B9913, 1); - - Utils::Hook::Set(0x5BA602, 0xB8); - Utils::Hook::Set(0x5BA603, 1); - - // Add custom zone paths - FastFiles::AddZonePath("zone\\patch\\"); - FastFiles::AddZonePath("zone\\dlc\\"); - - Renderer::OnFrame([] () - { - if (FastFiles::Current().empty() || !Dvar::Var("ui_zoneDebug").Get()) return; - - Game::Font* font = Game::R_RegisterFont("fonts/consoleFont"); // Inlining that seems to skip xpos, no idea why xD - float color[4] = { 1.0f, 1.0f, 1.0f, (Game::CL_IsCgameInitialized() ? 0.3f : 1.0f) }; - Game::R_AddCmdDrawText(Utils::VA("Loading FastFile: %s", FastFiles::Current().data()), 0x7FFFFFFF, font, 5.0f, static_cast(Renderer::Height() - 5), 1.0f, 1.0f, 0.0f, color, Game::ITEM_TEXTSTYLE_NORMAL); - }); - - Command::Add("loadzone", [] (Command::Params params) - { - if (params.Length() < 2) return; - - Game::XZoneInfo info; - info.name = params[1]; - info.allocFlags = 1;//0x01000000; - info.freeFlags = 0; - - Game::DB_LoadXAssets(&info, 1, true); - }); - } - - FastFiles::~FastFiles() - { - FastFiles::ZonePaths.clear(); - } -} +#include "STDInclude.hpp" + +namespace Components +{ + std::vector FastFiles::ZonePaths; + + // This has to be called only once, when the game starts + void FastFiles::LoadInitialZones(Game::XZoneInfo *zoneInfo, unsigned int zoneCount, int sync) + { + std::vector data; + Utils::Merge(&data, zoneInfo, zoneCount); + + if (FastFiles::Exists("iw4x_patch_mp")) + { + data.push_back({ "iw4x_patch_mp", 1, 0 }); + } + + // Load custom weapons, if present (force that later on) + if (FastFiles::Exists("iw4x_weapons_mp")) + { + data.push_back({ "iw4x_weapons_mp", 1, 0 }); + } + + return FastFiles::LoadDLCUIZones(data.data(), data.size(), sync); + } + + // This has to be called every time the cgame is reinitialized + void FastFiles::LoadDLCUIZones(Game::XZoneInfo *zoneInfo, unsigned int zoneCount, int sync) + { + std::vector data; + Utils::Merge(&data, zoneInfo, zoneCount); + + Game::XZoneInfo info = { nullptr, 2, 0 }; + + // Custom ui stuff + if (FastFiles::Exists("iw4x_ui_mp")) + { + info.name = "iw4x_ui_mp"; + data.push_back(info); + } + else // Fallback + { + info.name = "dlc1_ui_mp"; + data.push_back(info); + + info.name = "dlc2_ui_mp"; + data.push_back(info); + } + + return FastFiles::LoadLocalizeZones(data.data(), data.size(), sync); + } + + void FastFiles::LoadGfxZones(Game::XZoneInfo *zoneInfo, unsigned int zoneCount, int sync) + { + std::vector data; + Utils::Merge(&data, zoneInfo, zoneCount); + + if (FastFiles::Exists("iw4x_code_post_gfx_mp")) + { + data.push_back({ "iw4x_code_post_gfx_mp", zoneInfo->allocFlags, zoneInfo->freeFlags }); + } + + Game::DB_LoadXAssets(data.data(), data.size(), sync); + } + + // This has to be called every time fastfiles are loaded :D + void FastFiles::LoadLocalizeZones(Game::XZoneInfo *zoneInfo, unsigned int zoneCount, int sync) + { + std::vector data; + Utils::Merge(&data, zoneInfo, zoneCount); + + Game::XZoneInfo info = { nullptr, 4, 0 }; + + // Not sure how they should be loaded :S + std::string langZone = fmt::sprintf("iw4x_localized_%s", Game::Win_GetLanguage()); + + if (FastFiles::Exists(langZone)) + { + info.name = langZone.data(); + } + else if (FastFiles::Exists("iw4x_localized_english")) // Fallback + { + info.name = "iw4x_localized_english"; + } + + data.push_back(info); + + Game::DB_LoadXAssets(data.data(), data.size(), sync); + } + + // Name is a bit weird, due to FasFileS and ExistS :P + bool FastFiles::Exists(std::string file) + { + std::string path = FastFiles::GetZoneLocation(file.data()); + path.append(file); + + if (!Utils::String::EndsWith(path.data(), ".ff")) + { + path.append(".ff"); + } + + return std::ifstream(path.data()).good(); + } + + const char* FastFiles::GetZoneLocation(const char* file) + { + const char* dir = Dvar::Var("fs_basepath").Get(); + + for (auto &path : FastFiles::ZonePaths) + { + std::string absoluteFile = fmt::sprintf("%s\\%s%s", dir, path.data(), file); + + // No ".ff" appended, append it manually + if (!Utils::String::EndsWith(absoluteFile, ".ff")) + { + absoluteFile.append(".ff"); + } + + // Check if FastFile exists + if (Utils::IO::FileExists(absoluteFile)) + { + return Utils::String::VA("%s", path.data()); + } + } + + return Utils::String::VA("zone\\%s\\", Game::Win_GetLanguage()); + } + + void FastFiles::AddZonePath(std::string path) + { + FastFiles::ZonePaths.push_back(path); + } + + std::string FastFiles::Current() + { + const char* file = (Utils::Hook::Get(0x112A680) + 4); + + if (file == reinterpret_cast(4)) + { + return ""; + } + + return file; + } + + void FastFiles::ReadVersionStub(unsigned int* version, int size) + { + Game::DB_ReadXFileUncompressed(version, size); + + // Allow loading out custom version + if (*version == XFILE_VERSION_IW4X) + { + *version = XFILE_VERSION; + } + } + + FastFiles::FastFiles() + { + Dvar::Register("ui_zoneDebug", false, Game::dvar_flag::DVAR_FLAG_SAVED, "Display current loaded zone."); + + // Redirect zone paths + Utils::Hook(0x44DA90, FastFiles::GetZoneLocation, HOOK_JUMP).Install()->Quick(); + + // Allow loading 'newer' zones + Utils::Hook(0x4158E7, FastFiles::ReadVersionStub, HOOK_CALL).Install()->Quick(); + + // Allow custom zone loading + if (!ZoneBuilder::IsEnabled()) + { + Utils::Hook(0x506BC7, FastFiles::LoadInitialZones, HOOK_CALL).Install()->Quick(); + Utils::Hook(0x60B4AC, FastFiles::LoadDLCUIZones, HOOK_CALL).Install()->Quick(); + Utils::Hook(0x506B25, FastFiles::LoadGfxZones, HOOK_CALL).Install()->Quick(); + } + + // basic checks (hash jumps, both normal and playlist) + Utils::Hook::Nop(0x5B97A3, 2); + Utils::Hook::Nop(0x5BA493, 2); + + Utils::Hook::Nop(0x5B991C, 2); + Utils::Hook::Nop(0x5BA60C, 2); + + Utils::Hook::Nop(0x5B97B4, 2); + Utils::Hook::Nop(0x5BA4A4, 2); + + // allow loading of IWffu (unsigned) files + Utils::Hook::Set(0x4158D9, 0xEB); // main function + Utils::Hook::Nop(0x4A1D97, 2); // DB_AuthLoad_InflateInit + + // some other, unknown, check + Utils::Hook::Set(0x5B9912, 0xB8); + Utils::Hook::Set(0x5B9913, 1); + + Utils::Hook::Set(0x5BA602, 0xB8); + Utils::Hook::Set(0x5BA603, 1); + + // Add custom zone paths + FastFiles::AddZonePath("zone\\patch\\"); + FastFiles::AddZonePath("zone\\dlc\\"); + + Renderer::OnFrame([] () + { + if (FastFiles::Current().empty() || !Dvar::Var("ui_zoneDebug").Get()) return; + + Game::Font* font = Game::R_RegisterFont("fonts/consoleFont"); // Inlining that seems to skip xpos, no idea why xD + float color[4] = { 1.0f, 1.0f, 1.0f, (Game::CL_IsCgameInitialized() ? 0.3f : 1.0f) }; + Game::R_AddCmdDrawText(Utils::String::VA("Loading FastFile: %s", FastFiles::Current().data()), 0x7FFFFFFF, font, 5.0f, static_cast(Renderer::Height() - 5), 1.0f, 1.0f, 0.0f, color, Game::ITEM_TEXTSTYLE_NORMAL); + }); + + Command::Add("loadzone", [] (Command::Params params) + { + if (params.Length() < 2) return; + + Game::XZoneInfo info; + info.name = params[1]; + info.allocFlags = 1;//0x01000000; + info.freeFlags = 0; + + Game::DB_LoadXAssets(&info, 1, true); + }); + } + + FastFiles::~FastFiles() + { + FastFiles::ZonePaths.clear(); + } +} diff --git a/src/Components/Modules/FileSystem.cpp b/src/Components/Modules/FileSystem.cpp index bd58b85f..c1879f00 100644 --- a/src/Components/Modules/FileSystem.cpp +++ b/src/Components/Modules/FileSystem.cpp @@ -1,154 +1,154 @@ -#include "STDInclude.hpp" - -namespace Components -{ - void FileSystem::File::Read() - { - char* buffer = nullptr; - int size = Game::FS_ReadFile(this->FilePath.data(), &buffer); - - this->Buffer.clear(); - - if (size < 0) - { - if (buffer) - { - Game::FS_FreeFile(buffer); - } - } - else - { - this->Buffer.append(buffer, size); - Game::FS_FreeFile(buffer); - } - } - - void FileSystem::FileWriter::Write(std::string data) - { - if (this->Handle) - { - Game::FS_Write(data.data(), data.size(), this->Handle); - } - } - - void FileSystem::FileWriter::Open() - { - this->Handle = Game::FS_FOpenFileWrite(this->FilePath.data()); - } - - void FileSystem::FileWriter::Close() - { - if (this->Handle) - { - Game::FS_FCloseFile(this->Handle); - } - } - - std::vector FileSystem::GetFileList(std::string path, std::string extension) - { - std::vector fileList; - - int numFiles = 0; - char** files = Game::FS_GetFileList(path.data(), extension.data(), Game::FS_LIST_PURE_ONLY, &numFiles, 0); - - if (files) - { - for (int i = 0; i < numFiles; ++i) - { - if (files[i]) - { - fileList.push_back(files[i]); - } - } - - Game::FS_FreeFileList(files); - } - - return fileList; - } - - std::vector FileSystem::GetSysFileList(std::string path, std::string extension, bool folders) - { - std::vector fileList; - - int numFiles = 0; - char** files = Game::Sys_ListFiles(path.data(), extension.data(), NULL, &numFiles, folders); - - if (files) - { - for (int i = 0; i < numFiles; ++i) - { - if (files[i]) - { - fileList.push_back(files[i]); - } - } - - Game::Sys_FreeFileList(files); - } - - return fileList; - } - - void FileSystem::DeleteFile(std::string folder, std::string file) - { - char path[MAX_PATH] = { 0 }; - Game::FS_BuildPathToFile(Dvar::Var("fs_basepath").Get(), reinterpret_cast(0x63D0BB8), Utils::VA("%s/%s", folder.data(), file.data()), reinterpret_cast(&path)); - Game::FS_Remove(path); - } - - void FileSystem::RegisterFolder(const char* folder) - { - std::string fs_cdpath = Dvar::Var("fs_cdpath").Get(); - std::string fs_basepath = Dvar::Var("fs_basepath").Get(); - std::string fs_homepath = Dvar::Var("fs_homepath").Get(); - - if (!fs_cdpath.empty()) Game::FS_AddLocalizedGameDirectory(fs_cdpath.data(), folder); - if (!fs_basepath.empty()) Game::FS_AddLocalizedGameDirectory(fs_basepath.data(), folder); - if (!fs_homepath.empty()) Game::FS_AddLocalizedGameDirectory(fs_homepath.data(), folder); - } - - void FileSystem::RegisterFolders() - { - FileSystem::RegisterFolder("userraw"); - } - - void __declspec(naked) FileSystem::StartupStub() - { - __asm - { - push esi - call FileSystem::RegisterFolders - pop esi - - mov edx, ds:63D0CC0h - - mov eax, 48264Dh - jmp eax - } - } - - int FileSystem::ExecIsFSStub(const char* execFilename) - { - return !File(execFilename).Exists(); - } - - FileSystem::FileSystem() - { - // Filesystem config checks - Utils::Hook(0x6098FD, FileSystem::ExecIsFSStub, HOOK_CALL).Install()->Quick(); - - // Register additional folders - Utils::Hook(0x482647, FileSystem::StartupStub, HOOK_JUMP).Install()->Quick(); - - // exec whitelist removal (YAYFINITY WARD) - Utils::Hook::Nop(0x609685, 5); - Utils::Hook::Nop(0x60968C, 2); - - // ignore 'no iwd files found in main' - Utils::Hook::Nop(0x642A4B, 5); - - // Ignore bad magic, when trying to free hunk when it's already cleared - Utils::Hook::Set(0x49AACE, 0xC35E); - } -} +#include "STDInclude.hpp" + +namespace Components +{ + void FileSystem::File::Read() + { + char* buffer = nullptr; + int size = Game::FS_ReadFile(this->FilePath.data(), &buffer); + + this->Buffer.clear(); + + if (size < 0) + { + if (buffer) + { + Game::FS_FreeFile(buffer); + } + } + else + { + this->Buffer.append(buffer, size); + Game::FS_FreeFile(buffer); + } + } + + void FileSystem::FileWriter::Write(std::string data) + { + if (this->Handle) + { + Game::FS_Write(data.data(), data.size(), this->Handle); + } + } + + void FileSystem::FileWriter::Open() + { + this->Handle = Game::FS_FOpenFileWrite(this->FilePath.data()); + } + + void FileSystem::FileWriter::Close() + { + if (this->Handle) + { + Game::FS_FCloseFile(this->Handle); + } + } + + std::vector FileSystem::GetFileList(std::string path, std::string extension) + { + std::vector fileList; + + int numFiles = 0; + char** files = Game::FS_GetFileList(path.data(), extension.data(), Game::FS_LIST_PURE_ONLY, &numFiles, 0); + + if (files) + { + for (int i = 0; i < numFiles; ++i) + { + if (files[i]) + { + fileList.push_back(files[i]); + } + } + + Game::FS_FreeFileList(files); + } + + return fileList; + } + + std::vector FileSystem::GetSysFileList(std::string path, std::string extension, bool folders) + { + std::vector fileList; + + int numFiles = 0; + char** files = Game::Sys_ListFiles(path.data(), extension.data(), NULL, &numFiles, folders); + + if (files) + { + for (int i = 0; i < numFiles; ++i) + { + if (files[i]) + { + fileList.push_back(files[i]); + } + } + + Game::Sys_FreeFileList(files); + } + + return fileList; + } + + void FileSystem::DeleteFile(std::string folder, std::string file) + { + char path[MAX_PATH] = { 0 }; + Game::FS_BuildPathToFile(Dvar::Var("fs_basepath").Get(), reinterpret_cast(0x63D0BB8), Utils::String::VA("%s/%s", folder.data(), file.data()), reinterpret_cast(&path)); + Game::FS_Remove(path); + } + + void FileSystem::RegisterFolder(const char* folder) + { + std::string fs_cdpath = Dvar::Var("fs_cdpath").Get(); + std::string fs_basepath = Dvar::Var("fs_basepath").Get(); + std::string fs_homepath = Dvar::Var("fs_homepath").Get(); + + if (!fs_cdpath.empty()) Game::FS_AddLocalizedGameDirectory(fs_cdpath.data(), folder); + if (!fs_basepath.empty()) Game::FS_AddLocalizedGameDirectory(fs_basepath.data(), folder); + if (!fs_homepath.empty()) Game::FS_AddLocalizedGameDirectory(fs_homepath.data(), folder); + } + + void FileSystem::RegisterFolders() + { + FileSystem::RegisterFolder("userraw"); + } + + void __declspec(naked) FileSystem::StartupStub() + { + __asm + { + push esi + call FileSystem::RegisterFolders + pop esi + + mov edx, ds:63D0CC0h + + mov eax, 48264Dh + jmp eax + } + } + + int FileSystem::ExecIsFSStub(const char* execFilename) + { + return !File(execFilename).Exists(); + } + + FileSystem::FileSystem() + { + // Filesystem config checks + Utils::Hook(0x6098FD, FileSystem::ExecIsFSStub, HOOK_CALL).Install()->Quick(); + + // Register additional folders + Utils::Hook(0x482647, FileSystem::StartupStub, HOOK_JUMP).Install()->Quick(); + + // exec whitelist removal (YAYFINITY WARD) + Utils::Hook::Nop(0x609685, 5); + Utils::Hook::Nop(0x60968C, 2); + + // ignore 'no iwd files found in main' + Utils::Hook::Nop(0x642A4B, 5); + + // Ignore bad magic, when trying to free hunk when it's already cleared + Utils::Hook::Set(0x49AACE, 0xC35E); + } +} diff --git a/src/Components/Modules/Flags.cpp b/src/Components/Modules/Flags.cpp index 05182ef5..f598053c 100644 --- a/src/Components/Modules/Flags.cpp +++ b/src/Components/Modules/Flags.cpp @@ -1,49 +1,49 @@ -#include "STDInclude.hpp" - -namespace Components -{ - std::vector Flags::EnabledFlags; - - bool Flags::HasFlag(std::string flag) - { - for (auto entry : Flags::EnabledFlags) - { - if (Utils::StrToLower(entry) == Utils::StrToLower(flag)) - { - return true; - } - } - - return false; - } - - void Flags::ParseFlags() - { - int numArgs; - LPWSTR* argv = CommandLineToArgvW(GetCommandLineW(), &numArgs); - - if (argv) - { - for (int i = 0; i < numArgs; ++i) - { - std::wstring wFlag(argv[i]); - if (wFlag[0] == L'-') - { - Flags::EnabledFlags.push_back(std::string(++wFlag.begin(), wFlag.end())); - } - } - - LocalFree(argv); - } - } - - Flags::Flags() - { - Flags::ParseFlags(); - } - - Flags::~Flags() - { - Flags::EnabledFlags.clear(); - } -} +#include "STDInclude.hpp" + +namespace Components +{ + std::vector Flags::EnabledFlags; + + bool Flags::HasFlag(std::string flag) + { + for (auto entry : Flags::EnabledFlags) + { + if (Utils::String::StrToLower(entry) == Utils::String::StrToLower(flag)) + { + return true; + } + } + + return false; + } + + void Flags::ParseFlags() + { + int numArgs; + LPWSTR* argv = CommandLineToArgvW(GetCommandLineW(), &numArgs); + + if (argv) + { + for (int i = 0; i < numArgs; ++i) + { + std::wstring wFlag(argv[i]); + if (wFlag[0] == L'-') + { + Flags::EnabledFlags.push_back(std::string(++wFlag.begin(), wFlag.end())); + } + } + + LocalFree(argv); + } + } + + Flags::Flags() + { + Flags::ParseFlags(); + } + + Flags::~Flags() + { + Flags::EnabledFlags.clear(); + } +} diff --git a/src/Components/Modules/Localization.cpp b/src/Components/Modules/Localization.cpp index 277ba93c..6a2793e1 100644 --- a/src/Components/Modules/Localization.cpp +++ b/src/Components/Modules/Localization.cpp @@ -1,201 +1,201 @@ -#include "STDInclude.hpp" - -namespace Components -{ - Dvar::Var Localization::UseLocalization; - std::map Localization::LocalizeMap; - std::map Localization::TempLocalizeMap; - - void Localization::Set(const char* key, const char* value) - { - if (Localization::LocalizeMap.find(key) != Localization::LocalizeMap.end()) - { - Game::LocalizedEntry* entry = Localization::LocalizeMap[key]; - - char* newStaticValue = Utils::Memory::DuplicateString(value); - if (!newStaticValue) return; - if (entry->value) Utils::Memory::Free(entry->value); - entry->value = newStaticValue; - return; - } - - Game::LocalizedEntry* entry = Utils::Memory::AllocateArray(1); - if (!entry) return; - - entry->name = Utils::Memory::DuplicateString(key); - if (!entry->name) - { - Utils::Memory::Free(entry); - return; - } - - entry->value = Utils::Memory::DuplicateString(value); - if (!entry->value) - { - Utils::Memory::Free(entry->name); - Utils::Memory::Free(entry); - return; - } - - Localization::LocalizeMap[key] = entry; - } - - const char* Localization::Get(const char* key) - { - if (!Localization::UseLocalization.Get()) return key; - - Game::LocalizedEntry* entry = nullptr; - - if (Localization::TempLocalizeMap.find(key) != Localization::TempLocalizeMap.end()) - { - entry = Localization::TempLocalizeMap[key]; - } - else if (Localization::LocalizeMap.find(key) != Localization::LocalizeMap.end()) - { - entry = Localization::LocalizeMap[key]; - } - - if (!entry || !entry->value) entry = Game::DB_FindXAssetHeader(Game::XAssetType::ASSET_TYPE_LOCALIZE, key).localize; - - if (entry && entry->value) - { - return entry->value; - } - - return key; - } - - void Localization::SetTemp(std::string key, std::string value) - { - if (Localization::TempLocalizeMap.find(key) != Localization::TempLocalizeMap.end()) - { - Game::LocalizedEntry* entry = Localization::TempLocalizeMap[key]; - if(entry->value) Utils::Memory::Free(entry->value); - entry->value = Utils::Memory::DuplicateString(value); - } - else - { - Game::LocalizedEntry* entry = Utils::Memory::AllocateArray(1); - if (!entry) return; - - entry->name = Utils::Memory::DuplicateString(key); - if (!entry->name) - { - Utils::Memory::Free(entry); - return; - } - - entry->value = Utils::Memory::DuplicateString(value); - if (!entry->value) - { - Utils::Memory::Free(entry->name); - Utils::Memory::Free(entry); - return; - } - - Localization::TempLocalizeMap[key] = entry; - } - } - - void Localization::ClearTemp() - { - for (auto i = Localization::TempLocalizeMap.begin(); i != Localization::TempLocalizeMap.end(); ++i) - { - if (i->second) - { - if (i->second->name) Utils::Memory::Free(i->second->name); - if (i->second->value) Utils::Memory::Free(i->second->value); - Utils::Memory::Free(i->second); - } - } - - Localization::TempLocalizeMap.clear(); - } - - void __stdcall Localization::SetStringStub(const char* key, const char* value, bool isEnglish) - { - Localization::Set(key, value); - } - - DWORD Localization::SELoadLanguageStub() - { - //'official' iw4m localized strings - Game::SE_Load("localizedstrings/iw4m.str", 0); - - return Utils::Hook::Call(0x629E20)(); - } - - Localization::Localization() - { - AssetHandler::OnFind(Game::XAssetType::ASSET_TYPE_LOCALIZE, [] (Game::XAssetType, std::string filename) - { - Game::XAssetHeader header = { 0 }; - - if (Localization::TempLocalizeMap.find(filename) != Localization::TempLocalizeMap.end()) - { - header.localize = Localization::TempLocalizeMap[filename]; - } - else if (Localization::LocalizeMap.find(filename) != Localization::LocalizeMap.end()) - { - header.localize = Localization::LocalizeMap[filename]; - } - - return header; - }); - - // Resolving hook - Utils::Hook(0x629B90, Localization::Get, HOOK_JUMP).Install()->Quick(); - - // Set loading entry point - Utils::Hook(0x41D859, Localization::SELoadLanguageStub, HOOK_CALL).Install()->Quick(); - - // Overwrite SetString - Utils::Hook(0x4CE5EE, Localization::SetStringStub, HOOK_CALL).Install()->Quick(); - - // TODO: Get rid of those! - Localization::Set("MENU_SEARCHINGFORGAMES_100MS", ""); - Localization::Set("MP_SEARCHING_FOR_PLAYER", "Waiting"); - Localization::Set("MENU_WAITING_FOR_MORE_PLAYERS_TEAMS", "Waiting for more players to balance teams"); - Localization::Set("MENU_MOTD", "News"); - Localization::Set("MENU_MOTD_CAPS", "NEWS"); - Localization::Set("MENU_MODS", "Mods"); - Localization::Set("MENU_MODS_CAPS", "MODS"); - Localization::Set("MPUI_DESC_MODS", "Browse your Mods."); - Localization::Set("MENU_THEATER", "Theater"); - Localization::Set("MENU_THEATER_CAPS", "THEATER"); - Localization::Set("MPUI_DESC_THEATER", "View your played matches."); - Localization::Set("MENU_FOV", "Field of View"); - Localization::Set("MENU_NOBORDER", "Disable Window Border"); - Localization::Set("MENU_NATIVECURSOR", "Display native cursor"); - Localization::Set("MENU_MAXPACKETS", "Max. Packets per frame"); - Localization::Set("MENU_SNAPS", "Snapshot rate"); - Localization::Set("MENU_LAGOMETER", "Show Lagometer"); - Localization::Set("MENU_DRAWFPS", "Show FPS"); - Localization::Set("MENU_FPSLABELS", "Show FPS Labels"); - Localization::Set("MENU_NEWCOLORS", "Use new color codes"); - Localization::Set("MPUI_DESC_OPTIONS", "Set your game options."); - Localization::Set("MPUI_DESC_QUIT", "Quit the game."); - - Localization::Set("PLATFORM_REFRESH_LIST", "Refresh List ^0- ^3F5"); - Localization::Set("PLATFORM_REFRESH_LIST_CAPS", "REFRESH LIST ^0- ^3F5"); - - Localization::UseLocalization = Dvar::Register("ui_localize", true, Game::dvar_flag::DVAR_FLAG_NONE, "Use localization strings"); - } - - Localization::~Localization() - { - Localization::ClearTemp(); - - for (auto i = Localization::LocalizeMap.begin(); i != Localization::LocalizeMap.end(); ++i) - { - if (i->second) - { - if (i->second->name) Utils::Memory::Free(i->second->name); - if (i->second->value) Utils::Memory::Free(i->second->value); - Utils::Memory::Free(i->second); - } - } - - Localization::LocalizeMap.clear(); - } -} +#include "STDInclude.hpp" + +namespace Components +{ + Dvar::Var Localization::UseLocalization; + std::map Localization::LocalizeMap; + std::map Localization::TempLocalizeMap; + + void Localization::Set(std::string key, std::string value) + { + if (Localization::LocalizeMap.find(key) != Localization::LocalizeMap.end()) + { + Game::LocalizedEntry* entry = Localization::LocalizeMap[key]; + + char* newStaticValue = Utils::Memory::DuplicateString(value); + if (!newStaticValue) return; + if (entry->value) Utils::Memory::Free(entry->value); + entry->value = newStaticValue; + return; + } + + Game::LocalizedEntry* entry = Utils::Memory::AllocateArray(1); + if (!entry) return; + + entry->name = Utils::Memory::DuplicateString(key); + if (!entry->name) + { + Utils::Memory::Free(entry); + return; + } + + entry->value = Utils::Memory::DuplicateString(value); + if (!entry->value) + { + Utils::Memory::Free(entry->name); + Utils::Memory::Free(entry); + return; + } + + Localization::LocalizeMap[key] = entry; + } + + const char* Localization::Get(const char* key) + { + if (!Localization::UseLocalization.Get()) return key; + + Game::LocalizedEntry* entry = nullptr; + + if (Localization::TempLocalizeMap.find(key) != Localization::TempLocalizeMap.end()) + { + entry = Localization::TempLocalizeMap[key]; + } + else if (Localization::LocalizeMap.find(key) != Localization::LocalizeMap.end()) + { + entry = Localization::LocalizeMap[key]; + } + + if (!entry || !entry->value) entry = Game::DB_FindXAssetHeader(Game::XAssetType::ASSET_TYPE_LOCALIZE, key).localize; + + if (entry && entry->value) + { + return entry->value; + } + + return key; + } + + void Localization::SetTemp(std::string key, std::string value) + { + if (Localization::TempLocalizeMap.find(key) != Localization::TempLocalizeMap.end()) + { + Game::LocalizedEntry* entry = Localization::TempLocalizeMap[key]; + if(entry->value) Utils::Memory::Free(entry->value); + entry->value = Utils::Memory::DuplicateString(value); + } + else + { + Game::LocalizedEntry* entry = Utils::Memory::AllocateArray(1); + if (!entry) return; + + entry->name = Utils::Memory::DuplicateString(key); + if (!entry->name) + { + Utils::Memory::Free(entry); + return; + } + + entry->value = Utils::Memory::DuplicateString(value); + if (!entry->value) + { + Utils::Memory::Free(entry->name); + Utils::Memory::Free(entry); + return; + } + + Localization::TempLocalizeMap[key] = entry; + } + } + + void Localization::ClearTemp() + { + for (auto i = Localization::TempLocalizeMap.begin(); i != Localization::TempLocalizeMap.end(); ++i) + { + if (i->second) + { + if (i->second->name) Utils::Memory::Free(i->second->name); + if (i->second->value) Utils::Memory::Free(i->second->value); + Utils::Memory::Free(i->second); + } + } + + Localization::TempLocalizeMap.clear(); + } + + void __stdcall Localization::SetStringStub(const char* key, const char* value, bool isEnglish) + { + Localization::Set(key, value); + } + + DWORD Localization::SELoadLanguageStub() + { + //'official' iw4x localized strings + Game::SE_Load("localizedstrings/iw4x.str", 0); + + return Utils::Hook::Call(0x629E20)(); + } + + Localization::Localization() + { + AssetHandler::OnFind(Game::XAssetType::ASSET_TYPE_LOCALIZE, [] (Game::XAssetType, std::string filename) + { + Game::XAssetHeader header = { 0 }; + + if (Localization::TempLocalizeMap.find(filename) != Localization::TempLocalizeMap.end()) + { + header.localize = Localization::TempLocalizeMap[filename]; + } + else if (Localization::LocalizeMap.find(filename) != Localization::LocalizeMap.end()) + { + header.localize = Localization::LocalizeMap[filename]; + } + + return header; + }); + + // Resolving hook + Utils::Hook(0x629B90, Localization::Get, HOOK_JUMP).Install()->Quick(); + + // Set loading entry point + Utils::Hook(0x41D859, Localization::SELoadLanguageStub, HOOK_CALL).Install()->Quick(); + + // Overwrite SetString + Utils::Hook(0x4CE5EE, Localization::SetStringStub, HOOK_CALL).Install()->Quick(); + + // TODO: Get rid of those! + Localization::Set("MENU_SEARCHINGFORGAMES_100MS", ""); + Localization::Set("MP_SEARCHING_FOR_PLAYER", "Waiting"); + Localization::Set("MENU_WAITING_FOR_MORE_PLAYERS_TEAMS", "Waiting for more players to balance teams"); + Localization::Set("MENU_MOTD", "News"); + Localization::Set("MENU_MOTD_CAPS", "NEWS"); + Localization::Set("MENU_MODS", "Mods"); + Localization::Set("MENU_MODS_CAPS", "MODS"); + Localization::Set("MPUI_DESC_MODS", "Browse your Mods."); + Localization::Set("MENU_THEATER", "Theater"); + Localization::Set("MENU_THEATER_CAPS", "THEATER"); + Localization::Set("MPUI_DESC_THEATER", "View your played matches."); + Localization::Set("MENU_FOV", "Field of View"); + Localization::Set("MENU_NOBORDER", "Disable Window Border"); + Localization::Set("MENU_NATIVECURSOR", "Display native cursor"); + Localization::Set("MENU_MAXPACKETS", "Max. Packets per frame"); + Localization::Set("MENU_SNAPS", "Snapshot rate"); + Localization::Set("MENU_LAGOMETER", "Show Lagometer"); + Localization::Set("MENU_DRAWFPS", "Show FPS"); + Localization::Set("MENU_FPSLABELS", "Show FPS Labels"); + Localization::Set("MENU_NEWCOLORS", "Use new color codes"); + Localization::Set("MPUI_DESC_OPTIONS", "Set your game options."); + Localization::Set("MPUI_DESC_QUIT", "Quit the game."); + + Localization::Set("PLATFORM_REFRESH_LIST", "Refresh List ^0- ^3F5"); + Localization::Set("PLATFORM_REFRESH_LIST_CAPS", "REFRESH LIST ^0- ^3F5"); + + Localization::UseLocalization = Dvar::Register("ui_localize", true, Game::dvar_flag::DVAR_FLAG_NONE, "Use localization strings"); + } + + Localization::~Localization() + { + Localization::ClearTemp(); + + for (auto i = Localization::LocalizeMap.begin(); i != Localization::LocalizeMap.end(); ++i) + { + if (i->second) + { + if (i->second->name) Utils::Memory::Free(i->second->name); + if (i->second->value) Utils::Memory::Free(i->second->value); + Utils::Memory::Free(i->second); + } + } + + Localization::LocalizeMap.clear(); + } +} diff --git a/src/Components/Modules/Localization.hpp b/src/Components/Modules/Localization.hpp index 5c7dc597..fb34f73d 100644 --- a/src/Components/Modules/Localization.hpp +++ b/src/Components/Modules/Localization.hpp @@ -1,24 +1,24 @@ -namespace Components -{ - class Localization : public Component - { - public: - Localization(); - ~Localization(); - const char* GetName() { return "Localization"; }; - - static void Set(const char* key, const char* value); - static const char* Get(const char* key); - - static void SetTemp(std::string key, std::string value); - static void ClearTemp(); - - private: - static std::map LocalizeMap; - static std::map TempLocalizeMap; - static Dvar::Var UseLocalization; - - static void __stdcall SetStringStub(const char* key, const char* value, bool isEnglish); - static DWORD SELoadLanguageStub(); - }; -} +namespace Components +{ + class Localization : public Component + { + public: + Localization(); + ~Localization(); + const char* GetName() { return "Localization"; }; + + static void Set(std::string key, std::string value); + static const char* Get(const char* key); + + static void SetTemp(std::string key, std::string value); + static void ClearTemp(); + + private: + static std::map LocalizeMap; + static std::map TempLocalizeMap; + static Dvar::Var UseLocalization; + + static void __stdcall SetStringStub(const char* key, const char* value, bool isEnglish); + static DWORD SELoadLanguageStub(); + }; +} diff --git a/src/Components/Modules/Maps.cpp b/src/Components/Modules/Maps.cpp index bf47e059..a0f0b5c1 100644 --- a/src/Components/Modules/Maps.cpp +++ b/src/Components/Modules/Maps.cpp @@ -1,223 +1,223 @@ -#include "STDInclude.hpp" - -namespace Components -{ - std::map Maps::DependencyList; - std::vector Maps::CurrentDependencies; - - std::vector Maps::EntryPool; - - void Maps::LoadMapZones(Game::XZoneInfo *zoneInfo, unsigned int zoneCount, int sync) - { - if (!zoneInfo) return; - - Maps::CurrentDependencies.clear(); - for (auto i = Maps::DependencyList.begin(); i != Maps::DependencyList.end(); ++i) - { - if (std::regex_match(zoneInfo->name, std::regex(i->first))) - { - if (std::find(Maps::CurrentDependencies.begin(), Maps::CurrentDependencies.end(), i->second) == Maps::CurrentDependencies.end()) - { - Maps::CurrentDependencies.push_back(i->second); - } - } - } - - std::vector data; - Utils::Merge(&data, zoneInfo, zoneCount); - - for (unsigned int i = 0; i < Maps::CurrentDependencies.size(); ++i) - { - Game::XZoneInfo info; - - info.name = (&Maps::CurrentDependencies[i])->data(); - info.allocFlags = zoneInfo->allocFlags; - info.freeFlags = zoneInfo->freeFlags; - - data.push_back(info); - } - - // Load patch files - std::string patchZone = Utils::VA("patch_%s", zoneInfo->name); - if (FastFiles::Exists(patchZone)) - { - data.push_back({ patchZone.data(), zoneInfo->allocFlags, zoneInfo->freeFlags }); - } - - return FastFiles::LoadLocalizeZones(data.data(), data.size(), sync); - } - - void Maps::OverrideMapEnts(Game::MapEnts* ents) - { - auto callback = [] (Game::XAssetHeader header, void* ents) - { - Game::MapEnts* mapEnts = reinterpret_cast(ents); - Game::clipMap_t* clipMap = header.clipMap; - - if (clipMap && mapEnts && !_stricmp(mapEnts->name, clipMap->name)) - { - clipMap->mapEnts = mapEnts; - //*Game::marMapEntsPtr = mapEnts; - //Game::G_SpawnEntitiesFromString(); - } - }; - - // Internal doesn't lock the thread, as locking is impossible, due to executing this in the thread that holds the current lock - Game::DB_EnumXAssets_Internal(Game::XAssetType::ASSET_TYPE_COL_MAP_MP, callback, ents, true); - Game::DB_EnumXAssets_Internal(Game::XAssetType::ASSET_TYPE_COL_MAP_SP, callback, ents, true); - } - - void Maps::LoadAssetRestrict(Game::XAssetType type, Game::XAssetHeader asset, std::string name, bool* restrict) - { - if (std::find(Maps::CurrentDependencies.begin(), Maps::CurrentDependencies.end(), FastFiles::Current()) != Maps::CurrentDependencies.end()) - { - if (type == Game::XAssetType::ASSET_TYPE_GAME_MAP_MP || type == Game::XAssetType::ASSET_TYPE_COL_MAP_MP || type == Game::XAssetType::ASSET_TYPE_GFX_MAP || type == Game::XAssetType::ASSET_TYPE_MAP_ENTS || type == Game::XAssetType::ASSET_TYPE_COM_MAP || type == Game::XAssetType::ASSET_TYPE_FX_MAP) - { - *restrict = true; - return; - } - } - - if (type == Game::XAssetType::ASSET_TYPE_ADDON_MAP_ENTS) - { - *restrict = true; - return; - } - - if (type == Game::XAssetType::ASSET_TYPE_MAP_ENTS) - { - static std::string mapEntities; - FileSystem::File ents(name + ".ents"); - if (ents.Exists()) - { - mapEntities = ents.GetBuffer(); - asset.mapEnts->entityString = const_cast(mapEntities.data()); - asset.mapEnts->numEntityChars = mapEntities.size() + 1; - } - - // Apply new mapEnts - // This doesn't work, entities are spawned before the patch file is loaded - //Maps::OverrideMapEnts(asset.mapEnts); - } - } - - void Maps::GetBSPName(char* buffer, size_t size, const char* format, const char* mapname) - { - if (_strnicmp("mp_", mapname, 3)) - { - format = "maps/%s.d3dbsp"; - - // Adjust pointer to GameMap_Data - Utils::Hook::Set(0x4D90B7, &(Game::DB_XAssetPool[Game::XAssetType::ASSET_TYPE_GAME_MAP_SP].gameMapSP[0].data)); - } - else - { - // Adjust pointer to GameMap_Data - Utils::Hook::Set(0x4D90B7, &(Game::DB_XAssetPool[Game::XAssetType::ASSET_TYPE_GAME_MAP_MP].gameMapMP[0].data)); - } - - AntiCheat::EmptyHash(); - - _snprintf(buffer, size, format, mapname); - } - - void Maps::AddDependency(std::string expression, std::string zone) - { - // Test expression before adding it - try - { - std::regex _(expression); - } - catch (const std::exception e) - { - MessageBoxA(0, Utils::VA("Invalid regular expression: %s", expression.data()), "Warning", MB_ICONEXCLAMATION); - return; - } - - Maps::DependencyList[expression] = zone; - } - - void Maps::ReallocateEntryPool() - { - Assert_Size(Game::XAssetEntry, 16); - - Maps::EntryPool.clear(); - Maps::EntryPool.resize(789312); - - // Apply new size - Utils::Hook::Set(0x5BAEB0, Maps::EntryPool.size()); - - // Apply new pool - Utils::Hook::Set(0x48E6F4, Maps::EntryPool.data()); - Utils::Hook::Set(0x4C67E4, Maps::EntryPool.data()); - Utils::Hook::Set(0x4C8584, Maps::EntryPool.data()); - Utils::Hook::Set(0x5BAEA8, Maps::EntryPool.data()); - Utils::Hook::Set(0x5BB0C4, Maps::EntryPool.data()); - Utils::Hook::Set(0x5BB0F5, Maps::EntryPool.data()); - Utils::Hook::Set(0x5BB1D4, Maps::EntryPool.data()); - Utils::Hook::Set(0x5BB235, Maps::EntryPool.data()); - Utils::Hook::Set(0x5BB278, Maps::EntryPool.data()); - Utils::Hook::Set(0x5BB34C, Maps::EntryPool.data()); - Utils::Hook::Set(0x5BB484, Maps::EntryPool.data()); - Utils::Hook::Set(0x5BB570, Maps::EntryPool.data()); - Utils::Hook::Set(0x5BB6B7, Maps::EntryPool.data()); - Utils::Hook::Set(0x5BB844, Maps::EntryPool.data()); - Utils::Hook::Set(0x5BB98D, Maps::EntryPool.data()); - Utils::Hook::Set(0x5BBA66, Maps::EntryPool.data()); - Utils::Hook::Set(0x5BBB8D, Maps::EntryPool.data()); - Utils::Hook::Set(0x5BBCB1, Maps::EntryPool.data()); - Utils::Hook::Set(0x5BBD9B, Maps::EntryPool.data()); - Utils::Hook::Set(0x5BBE4C, Maps::EntryPool.data()); - Utils::Hook::Set(0x5BBF14, Maps::EntryPool.data()); - Utils::Hook::Set(0x5BBF54, Maps::EntryPool.data()); - Utils::Hook::Set(0x5BBFB8, Maps::EntryPool.data()); - - Utils::Hook::Set(0x5BAE91, Maps::EntryPool.data() + 1); - Utils::Hook::Set(0x5BAEA2, Maps::EntryPool.data() + 1); - } - - Maps::Maps() - { - // Restrict asset loading - AssetHandler::OnLoad(Maps::LoadAssetRestrict); - - // hunk size (was 300 MiB) - Utils::Hook::Set(0x64A029, 0x1C200000); // 450 MiB - Utils::Hook::Set(0x64A057, 0x1C200000); - - // Intercept BSP name resolving - Utils::Hook(0x4C5979, Maps::GetBSPName, HOOK_CALL).Install()->Quick(); - - // Intercept map zone loading - Utils::Hook(0x42C2AF, Maps::LoadMapZones, HOOK_CALL).Install()->Quick(); - - Game::ReallocateAssetPool(Game::XAssetType::ASSET_TYPE_GAME_MAP_SP, 1); - Game::ReallocateAssetPool(Game::XAssetType::ASSET_TYPE_IMAGE, 7168); - Game::ReallocateAssetPool(Game::XAssetType::ASSET_TYPE_LOADED_SOUND, 2700); - Game::ReallocateAssetPool(Game::XAssetType::ASSET_TYPE_FX, 1200); - Game::ReallocateAssetPool(Game::XAssetType::ASSET_TYPE_LOCALIZE, 14000); - Game::ReallocateAssetPool(Game::XAssetType::ASSET_TYPE_XANIM, 8192); - Game::ReallocateAssetPool(Game::XAssetType::ASSET_TYPE_XMODEL, 5125); - Game::ReallocateAssetPool(Game::XAssetType::ASSET_TYPE_PHYSPRESET, 128); - Game::ReallocateAssetPool(Game::XAssetType::ASSET_TYPE_PIXELSHADER, 10000); - Game::ReallocateAssetPool(Game::XAssetType::ASSET_TYPE_VERTEXSHADER, 3072); - Game::ReallocateAssetPool(Game::XAssetType::ASSET_TYPE_MATERIAL, 8192); - Game::ReallocateAssetPool(Game::XAssetType::ASSET_TYPE_VERTEXDECL, 196); - Game::ReallocateAssetPool(Game::XAssetType::ASSET_TYPE_WEAPON, 2400); - Game::ReallocateAssetPool(Game::XAssetType::ASSET_TYPE_STRINGTABLE, 800); - - Maps::ReallocateEntryPool(); - - // Dependencies - //Maps::AddDependency("oilrig", "mp_subbase"); - //Maps::AddDependency("gulag", "mp_subbase"); - //Maps::AddDependency("invasion", "mp_rust"); - Maps::AddDependency("co_hunted", "mp_storm"); - Maps::AddDependency("^(?!mp_).*", "iw4x_dependencies_mp"); // All maps not starting with "mp_" - } - - Maps::~Maps() - { - Maps::EntryPool.clear(); - } -} +#include "STDInclude.hpp" + +namespace Components +{ + std::map Maps::DependencyList; + std::vector Maps::CurrentDependencies; + + std::vector Maps::EntryPool; + + void Maps::LoadMapZones(Game::XZoneInfo *zoneInfo, unsigned int zoneCount, int sync) + { + if (!zoneInfo) return; + + Maps::CurrentDependencies.clear(); + for (auto i = Maps::DependencyList.begin(); i != Maps::DependencyList.end(); ++i) + { + if (std::regex_match(zoneInfo->name, std::regex(i->first))) + { + if (std::find(Maps::CurrentDependencies.begin(), Maps::CurrentDependencies.end(), i->second) == Maps::CurrentDependencies.end()) + { + Maps::CurrentDependencies.push_back(i->second); + } + } + } + + std::vector data; + Utils::Merge(&data, zoneInfo, zoneCount); + + for (unsigned int i = 0; i < Maps::CurrentDependencies.size(); ++i) + { + Game::XZoneInfo info; + + info.name = (&Maps::CurrentDependencies[i])->data(); + info.allocFlags = zoneInfo->allocFlags; + info.freeFlags = zoneInfo->freeFlags; + + data.push_back(info); + } + + // Load patch files + std::string patchZone = fmt::sprintf("patch_%s", zoneInfo->name); + if (FastFiles::Exists(patchZone)) + { + data.push_back({ patchZone.data(), zoneInfo->allocFlags, zoneInfo->freeFlags }); + } + + return FastFiles::LoadLocalizeZones(data.data(), data.size(), sync); + } + + void Maps::OverrideMapEnts(Game::MapEnts* ents) + { + auto callback = [] (Game::XAssetHeader header, void* ents) + { + Game::MapEnts* mapEnts = reinterpret_cast(ents); + Game::clipMap_t* clipMap = header.clipMap; + + if (clipMap && mapEnts && !_stricmp(mapEnts->name, clipMap->name)) + { + clipMap->mapEnts = mapEnts; + //*Game::marMapEntsPtr = mapEnts; + //Game::G_SpawnEntitiesFromString(); + } + }; + + // Internal doesn't lock the thread, as locking is impossible, due to executing this in the thread that holds the current lock + Game::DB_EnumXAssets_Internal(Game::XAssetType::ASSET_TYPE_COL_MAP_MP, callback, ents, true); + Game::DB_EnumXAssets_Internal(Game::XAssetType::ASSET_TYPE_COL_MAP_SP, callback, ents, true); + } + + void Maps::LoadAssetRestrict(Game::XAssetType type, Game::XAssetHeader asset, std::string name, bool* restrict) + { + if (std::find(Maps::CurrentDependencies.begin(), Maps::CurrentDependencies.end(), FastFiles::Current()) != Maps::CurrentDependencies.end()) + { + if (type == Game::XAssetType::ASSET_TYPE_GAME_MAP_MP || type == Game::XAssetType::ASSET_TYPE_COL_MAP_MP || type == Game::XAssetType::ASSET_TYPE_GFX_MAP || type == Game::XAssetType::ASSET_TYPE_MAP_ENTS || type == Game::XAssetType::ASSET_TYPE_COM_MAP || type == Game::XAssetType::ASSET_TYPE_FX_MAP) + { + *restrict = true; + return; + } + } + + if (type == Game::XAssetType::ASSET_TYPE_ADDON_MAP_ENTS) + { + *restrict = true; + return; + } + + if (type == Game::XAssetType::ASSET_TYPE_MAP_ENTS) + { + static std::string mapEntities; + FileSystem::File ents(name + ".ents"); + if (ents.Exists()) + { + mapEntities = ents.GetBuffer(); + asset.mapEnts->entityString = const_cast(mapEntities.data()); + asset.mapEnts->numEntityChars = mapEntities.size() + 1; + } + + // Apply new mapEnts + // This doesn't work, entities are spawned before the patch file is loaded + //Maps::OverrideMapEnts(asset.mapEnts); + } + } + + void Maps::GetBSPName(char* buffer, size_t size, const char* format, const char* mapname) + { + if (_strnicmp("mp_", mapname, 3)) + { + format = "maps/%s.d3dbsp"; + + // Adjust pointer to GameMap_Data + Utils::Hook::Set(0x4D90B7, &(Game::DB_XAssetPool[Game::XAssetType::ASSET_TYPE_GAME_MAP_SP].gameMapSP[0].data)); + } + else + { + // Adjust pointer to GameMap_Data + Utils::Hook::Set(0x4D90B7, &(Game::DB_XAssetPool[Game::XAssetType::ASSET_TYPE_GAME_MAP_MP].gameMapMP[0].data)); + } + + AntiCheat::EmptyHash(); + + _snprintf(buffer, size, format, mapname); + } + + void Maps::AddDependency(std::string expression, std::string zone) + { + // Test expression before adding it + try + { + std::regex _(expression); + } + catch (const std::exception e) + { + MessageBoxA(0, Utils::String::VA("Invalid regular expression: %s", expression.data()), "Warning", MB_ICONEXCLAMATION); + return; + } + + Maps::DependencyList[expression] = zone; + } + + void Maps::ReallocateEntryPool() + { + Assert_Size(Game::XAssetEntry, 16); + + Maps::EntryPool.clear(); + Maps::EntryPool.resize(789312); + + // Apply new size + Utils::Hook::Set(0x5BAEB0, Maps::EntryPool.size()); + + // Apply new pool + Utils::Hook::Set(0x48E6F4, Maps::EntryPool.data()); + Utils::Hook::Set(0x4C67E4, Maps::EntryPool.data()); + Utils::Hook::Set(0x4C8584, Maps::EntryPool.data()); + Utils::Hook::Set(0x5BAEA8, Maps::EntryPool.data()); + Utils::Hook::Set(0x5BB0C4, Maps::EntryPool.data()); + Utils::Hook::Set(0x5BB0F5, Maps::EntryPool.data()); + Utils::Hook::Set(0x5BB1D4, Maps::EntryPool.data()); + Utils::Hook::Set(0x5BB235, Maps::EntryPool.data()); + Utils::Hook::Set(0x5BB278, Maps::EntryPool.data()); + Utils::Hook::Set(0x5BB34C, Maps::EntryPool.data()); + Utils::Hook::Set(0x5BB484, Maps::EntryPool.data()); + Utils::Hook::Set(0x5BB570, Maps::EntryPool.data()); + Utils::Hook::Set(0x5BB6B7, Maps::EntryPool.data()); + Utils::Hook::Set(0x5BB844, Maps::EntryPool.data()); + Utils::Hook::Set(0x5BB98D, Maps::EntryPool.data()); + Utils::Hook::Set(0x5BBA66, Maps::EntryPool.data()); + Utils::Hook::Set(0x5BBB8D, Maps::EntryPool.data()); + Utils::Hook::Set(0x5BBCB1, Maps::EntryPool.data()); + Utils::Hook::Set(0x5BBD9B, Maps::EntryPool.data()); + Utils::Hook::Set(0x5BBE4C, Maps::EntryPool.data()); + Utils::Hook::Set(0x5BBF14, Maps::EntryPool.data()); + Utils::Hook::Set(0x5BBF54, Maps::EntryPool.data()); + Utils::Hook::Set(0x5BBFB8, Maps::EntryPool.data()); + + Utils::Hook::Set(0x5BAE91, Maps::EntryPool.data() + 1); + Utils::Hook::Set(0x5BAEA2, Maps::EntryPool.data() + 1); + } + + Maps::Maps() + { + // Restrict asset loading + AssetHandler::OnLoad(Maps::LoadAssetRestrict); + + // hunk size (was 300 MiB) + Utils::Hook::Set(0x64A029, 0x1C200000); // 450 MiB + Utils::Hook::Set(0x64A057, 0x1C200000); + + // Intercept BSP name resolving + Utils::Hook(0x4C5979, Maps::GetBSPName, HOOK_CALL).Install()->Quick(); + + // Intercept map zone loading + Utils::Hook(0x42C2AF, Maps::LoadMapZones, HOOK_CALL).Install()->Quick(); + + Game::ReallocateAssetPool(Game::XAssetType::ASSET_TYPE_GAME_MAP_SP, 1); + Game::ReallocateAssetPool(Game::XAssetType::ASSET_TYPE_IMAGE, 7168); + Game::ReallocateAssetPool(Game::XAssetType::ASSET_TYPE_LOADED_SOUND, 2700); + Game::ReallocateAssetPool(Game::XAssetType::ASSET_TYPE_FX, 1200); + Game::ReallocateAssetPool(Game::XAssetType::ASSET_TYPE_LOCALIZE, 14000); + Game::ReallocateAssetPool(Game::XAssetType::ASSET_TYPE_XANIM, 8192); + Game::ReallocateAssetPool(Game::XAssetType::ASSET_TYPE_XMODEL, 5125); + Game::ReallocateAssetPool(Game::XAssetType::ASSET_TYPE_PHYSPRESET, 128); + Game::ReallocateAssetPool(Game::XAssetType::ASSET_TYPE_PIXELSHADER, 10000); + Game::ReallocateAssetPool(Game::XAssetType::ASSET_TYPE_VERTEXSHADER, 3072); + Game::ReallocateAssetPool(Game::XAssetType::ASSET_TYPE_MATERIAL, 8192); + Game::ReallocateAssetPool(Game::XAssetType::ASSET_TYPE_VERTEXDECL, 196); + Game::ReallocateAssetPool(Game::XAssetType::ASSET_TYPE_WEAPON, 2400); + Game::ReallocateAssetPool(Game::XAssetType::ASSET_TYPE_STRINGTABLE, 800); + + Maps::ReallocateEntryPool(); + + // Dependencies + //Maps::AddDependency("oilrig", "mp_subbase"); + //Maps::AddDependency("gulag", "mp_subbase"); + //Maps::AddDependency("invasion", "mp_rust"); + Maps::AddDependency("co_hunted", "mp_storm"); + Maps::AddDependency("^(?!mp_).*", "iw4x_dependencies_mp"); // All maps not starting with "mp_" + } + + Maps::~Maps() + { + Maps::EntryPool.clear(); + } +} diff --git a/src/Components/Modules/Menus.cpp b/src/Components/Modules/Menus.cpp index 83ecea63..896b8ba4 100644 --- a/src/Components/Modules/Menus.cpp +++ b/src/Components/Modules/Menus.cpp @@ -1,684 +1,684 @@ -#include "STDInclude.hpp" - -namespace Components -{ - std::vector Menus::CustomMenus; - std::map Menus::MenuList; - std::map Menus::MenuListList; - - int Menus::ReserveSourceHandle() - { - // Check if a free slot is available - int i = 1; - for (; i < MAX_SOURCEFILES; ++i) - { - if (!Game::sourceFiles[i]) - break; - } - - if (i >= MAX_SOURCEFILES) - return 0; - - // Reserve it, if yes - Game::sourceFiles[i] = (Game::source_t*)1; - - return i; - } - - Game::script_t* Menus::LoadMenuScript(std::string name, std::string& buffer) - { - Game::script_t* script = Game::Script_Alloc(sizeof(Game::script_t) + 1 + buffer.length()); - - strcpy_s(script->filename, sizeof(script->filename), name.data()); - script->buffer = reinterpret_cast(script + 1); - - *(script->buffer + buffer.length()) = '\0'; - - script->script_p = script->buffer; - script->lastscript_p = script->buffer; - script->length = buffer.length(); - script->end_p = &script->buffer[buffer.length()]; - script->line = 1; - script->lastline = 1; - script->tokenavailable = 0; - - Game::Script_SetupTokens(script, reinterpret_cast(0x797F80)); - script->punctuations = reinterpret_cast(0x797F80); - - strcpy(script->buffer, buffer.data()); - - script->length = Game::Script_CleanString(script->buffer); - - return script; - } - - int Menus::LoadMenuSource(std::string name, std::string& buffer) - { - int handle = Menus::ReserveSourceHandle(); - if (!Menus::IsValidSourceHandle(handle)) return 0; // No free source slot! - - Game::source_t *source = nullptr; - Game::script_t *script = Menus::LoadMenuScript(name, buffer); - - if (!script) - { - Game::sourceFiles[handle] = nullptr; // Free reserved slot - return 0; - } - - script->next = NULL; - - source = Utils::Memory::AllocateArray(1); - if (!source) - { - Game::FreeMemory(script); - return 0; - } - - strncpy(source->filename, "string", 64); - source->scriptstack = script; - source->tokens = NULL; - source->defines = NULL; - source->indentstack = NULL; - source->skip = 0; - source->definehash = (Game::define_t**)Utils::Memory::Allocate(4096); - - Game::sourceFiles[handle] = source; - - return handle; - } - - bool Menus::IsValidSourceHandle(int handle) - { - return (handle > 0 && handle < MAX_SOURCEFILES && Game::sourceFiles[handle]); - } - - int Menus::KeywordHash(char* key) - { - int hash = 0; - - if (*key) - { - int sub = 3523 - reinterpret_cast(key); - do - { - char _chr = *key; - hash += reinterpret_cast(&(key++)[sub]) * tolower(_chr); - } while (*key); - } - - return (static_cast(hash) + static_cast(hash >> 8)) & 0x7F; - } - - Game::menuDef_t* Menus::ParseMenu(int handle) - { - Game::menuDef_t* menu = Utils::Memory::AllocateArray(1); - if (!menu) return nullptr; - - menu->items = Utils::Memory::AllocateArray(512); - if (!menu->items) - { - Utils::Memory::Free(menu); - return nullptr; - } - - Game::pc_token_t token; - if (!Game::PC_ReadTokenHandle(handle, &token) || token.string[0] != '{') - { - Utils::Memory::Free(menu->items); - Utils::Memory::Free(menu); - return nullptr; - } - - while (true) - { - ZeroMemory(&token, sizeof(token)); - - if (!Game::PC_ReadTokenHandle(handle, &token)) - { - Game::PC_SourceError(handle, "end of file inside menu\n"); - break; // Fail - } - - if (*token.string == '}') - { - break; // Success - } - - int idx = Menus::KeywordHash(token.string); - - Game::keywordHash_t* key = Game::menuParseKeywordHash[idx]; - - if (!key) - { - Game::PC_SourceError(handle, "unknown menu keyword %s", token.string); - continue; - } - - if (!key->func(menu, handle)) - { - Game::PC_SourceError(handle, "couldn't parse menu keyword %s", token.string); - break; // Fail - } - } - - Menus::OverrideMenu(menu); - Menus::RemoveMenu(menu->window.name); - Menus::MenuList[menu->window.name] = menu; - - return menu; - } - - std::vector Menus::LoadMenu(std::string menu) - { - std::vector menus; - FileSystem::File menuFile(menu); - - if (menuFile.Exists()) - { - Game::pc_token_t token; - int handle = Menus::LoadMenuSource(menu, menuFile.GetBuffer()); - - if (Menus::IsValidSourceHandle(handle)) - { - while (true) - { - ZeroMemory(&token, sizeof(token)); - - if (!Game::PC_ReadTokenHandle(handle, &token) || token.string[0] == '}') - { - break; - } - - if (!_stricmp(token.string, "loadmenu")) - { - Game::PC_ReadTokenHandle(handle, &token); - - Utils::Merge(&menus, Menus::LoadMenu(Utils::VA("ui_mp\\%s.menu", token.string))); - } - - if (!_stricmp(token.string, "menudef")) - { - Game::menuDef_t* menudef = Menus::ParseMenu(handle); - if (menudef) menus.push_back(menudef); - } - } - - Menus::FreeMenuSource(handle); - } - } - - return menus; - } - - std::vector Menus::LoadMenu(Game::menuDef_t* menudef) - { - std::vector menus = Menus::LoadMenu(Utils::VA("ui_mp\\%s.menu", menudef->window.name)); - - if (menus.empty()) - { - // Try loading the original menu, if we can't load our custom one - Game::menuDef_t* originalMenu = AssetHandler::FindOriginalAsset(Game::XAssetType::ASSET_TYPE_MENU, menudef->window.name).menu; - - if (originalMenu) - { - menus.push_back(originalMenu); - } - else - { - menus.push_back(menudef); - } - } - - return menus; - } - - Game::MenuList* Menus::LoadScriptMenu(const char* menu) - { - std::vector menus = Menus::LoadMenu(menu); - if (menus.empty()) return nullptr; - - // Allocate new menu list - Game::MenuList* newList = Utils::Memory::AllocateArray(1); - if (!newList) return nullptr; - - newList->menus = Utils::Memory::AllocateArray(menus.size()); - if (!newList->menus) - { - Utils::Memory::Free(newList); - return nullptr; - } - - newList->name = Utils::Memory::DuplicateString(menu); - newList->menuCount = menus.size(); - - // Copy new menus - std::memcpy(newList->menus, menus.data(), menus.size() * sizeof(Game::menuDef_t *)); - - Menus::RemoveMenuList(newList->name); - Menus::MenuListList[newList->name] = newList; - - return newList; - } - - Game::MenuList* Menus::LoadMenuList(Game::MenuList* menuList) - { - std::vector menus; - - for (int i = 0; i < menuList->menuCount; ++i) - { - if (!menuList->menus[i]) - { - continue; - } - - Utils::Merge(&menus, Menus::LoadMenu(menuList->menus[i])); - } - - // Load custom menus - if (menuList->name == "ui_mp/code.txt"s) // Should be menus, but code is loaded ingame - { - for (auto menu : Menus::CustomMenus) - { - Utils::Merge(&menus, Menus::LoadMenu(menu)); - } - } - - // Allocate new menu list - Game::MenuList* newList = Utils::Memory::AllocateArray(1); - if (!newList) return menuList; - - size_t size = menus.size(); - newList->menus = Utils::Memory::AllocateArray(size); - if (!newList->menus) - { - Utils::Memory::Free(newList); - return menuList; - } - - newList->name = Utils::Memory::DuplicateString(menuList->name); - newList->menuCount = size; - - // Copy new menus - std::memcpy(newList->menus, menus.data(), size * sizeof(Game::menuDef_t *)); - - Menus::RemoveMenuList(newList->name); - Menus::MenuListList[newList->name] = newList; - - return newList; - } - - void Menus::FreeMenuSource(int handle) - { - if (!Menus::IsValidSourceHandle(handle)) return; - - Game::source_t *source = Game::sourceFiles[handle]; - - while (source->scriptstack) - { - Game::script_t* script = source->scriptstack; - source->scriptstack = source->scriptstack->next; - Game::FreeMemory(script); - } - - while (source->tokens) - { - Game::token_t* token = source->tokens; - source->tokens = source->tokens->next; - Game::FreeMemory(token); - } - - while (source->defines) - { - Game::define_t* define = source->defines; - source->defines = source->defines->next; - Game::FreeMemory(define); - } - - while (source->indentstack) - { - Game::indent_t* indent = source->indentstack; - source->indentstack = source->indentstack->next; - Utils::Memory::Free(indent); - } - - if (source->definehash) Utils::Memory::Free(source->definehash); - - Utils::Memory::Free(source); - - Game::sourceFiles[handle] = nullptr; - } - - void Menus::FreeMenu(Game::menuDef_t* menudef) - { - // Do i need to free expressions and strings? - // Or does the game take care of it? - // Seems like it does... - - if (menudef->items) - { - // Seems like this is obsolete as well, - // as the game handles the memory - - //for (int i = 0; i < menudef->itemCount; ++i) - //{ - // Game::Menu_FreeItemMemory(menudef->items[i]); - //} - - Utils::Memory::Free(menudef->items); - } - - Utils::Memory::Free(menudef); - } - - void Menus::FreeMenuList(Game::MenuList* menuList) - { - if (!menuList) return; - - // Keep our compiler happy - Game::MenuList list = { menuList->name, menuList->menuCount, menuList->menus }; - - if (list.name) - { - Utils::Memory::Free(list.name); - } - - if (list.menus) - { - Utils::Memory::Free(list.menus); - } - - Utils::Memory::Free(menuList); - } - - void Menus::RemoveMenu(std::string menu) - { - auto i = Menus::MenuList.find(menu); - if(i != Menus::MenuList.end()) - { - if (i->second) Menus::FreeMenu(i->second); - i = Menus::MenuList.erase(i); - } - } - - void Menus::RemoveMenu(Game::menuDef_t* menudef) - { - for (auto i = Menus::MenuList.begin(); i != Menus::MenuList.end();) - { - if (i->second == menudef) - { - Menus::FreeMenu(menudef); - i = Menus::MenuList.erase(i); - } - else - { - ++i; - } - } - } - - void Menus::RemoveMenuList(std::string menuList) - { - auto i = Menus::MenuListList.find(menuList); - if (i != Menus::MenuListList.end()) - { - if (i->second) - { - for (auto j = 0; j < i->second->menuCount; ++j) - { - Menus::RemoveMenu(i->second->menus[j]); - } - - Menus::FreeMenuList(i->second); - } - - i = Menus::MenuListList.erase(i); - } - } - - // This is actually a really important function - // It checks if we have already loaded the menu we passed and replaces its instances in memory - // Due to deallocating the old menu, the game might crash on not being able to handle its old instance - // So we need to override it in our menu lists and the game's ui context - // EDIT: We might also remove the old instances inside RemoveMenu - // EDIT2: Removing old instances without having a menu to replace them with might leave a nullptr - // EDIT3: Wouldn't it be better to check if the new menu we're trying to load has already been loaded and not was not deallocated and return that one instead of loading a new one? - void Menus::OverrideMenu(Game::menuDef_t *menu) - { - if (!menu || !menu->window.name) return; - std::string name = menu->window.name; - - // Find the old menu - auto i = Menus::MenuList.find(name); - if (i != Menus::MenuList.end()) - { - // We have found it, *yay* - Game::menuDef_t* oldMenu = i->second; - - // Replace every old instance with our new one in the ui context - for (int j = 0; j < Game::uiContext->menuCount; ++j) - { - if (Game::uiContext->menus[j] == oldMenu) - { - Game::uiContext->menus[j] = menu; - } - } - - // Replace every old instance with our new one in our menu lists - for (auto j = Menus::MenuListList.begin(); j != Menus::MenuListList.end(); ++j) - { - Game::MenuList* list = j->second; - - if (list && list->menus) - { - for (int k = 0; k < list->menuCount; ++k) - { - if (list->menus[k] == oldMenu) - { - list->menus[k] = menu; - } - } - } - } - } - } - - void Menus::RemoveMenuList(Game::MenuList* menuList) - { - if (!menuList || !menuList->name) return; - Menus::RemoveMenuList(menuList->name); - } - - void Menus::FreeEverything() - { - for (auto i = Menus::MenuListList.begin(); i != Menus::MenuListList.end(); ++i) - { - Menus::FreeMenuList(i->second); - } - - Menus::MenuListList.clear(); - - for (auto i = Menus::MenuList.begin(); i != Menus::MenuList.end(); ++i) - { - Menus::FreeMenu(i->second); - } - - Menus::MenuList.clear(); - } - - Game::XAssetHeader Menus::MenuLoad(Game::XAssetType type, std::string filename) - { - return { Game::Menus_FindByName(Game::uiContext, filename.data()) }; - } - - Game::XAssetHeader Menus::MenuFileLoad(Game::XAssetType type, std::string filename) - { - Game::XAssetHeader header = { 0 }; - - Game::MenuList* menuList = Game::DB_FindXAssetHeader(type, filename.data()).menuList; - header.menuList = menuList; - - // Free the last menulist and ui context, as we have to rebuild it with the new menus - if (Menus::MenuListList.find(filename) != Menus::MenuListList.end()) - { - Game::MenuList* list = Menus::MenuListList[filename]; - - for (int i = 0; list && list->menus && i < list->menuCount; ++i) - { - Menus::RemoveMenuFromContext(Game::uiContext, list->menus[i]); - } - - Menus::RemoveMenuList(filename); - } - - if (menuList) - { - // Parse scriptmenus! - if (menuList->menus[0]->window.name == "default_menu"s || Utils::EndsWith(filename, ".menu")) - { - if (FileSystem::File(filename).Exists()) - { - header.menuList = Menus::LoadScriptMenu(filename.data()); - - // Reset, if we didn't find scriptmenus - if (!header.menuList) - { - header.menuList = menuList; - } - } - } - else - { - header.menuList = Menus::LoadMenuList(menuList); - } - } - - return header; - } - - bool Menus::IsMenuVisible(Game::UiContext *dc, Game::menuDef_t *menu) - { - if (menu && menu->window.name) - { - if (menu->window.name == "connect"s) // Check if we're supposed to draw the loadscreen - { - Game::menuDef_t* originalConnect = AssetHandler::FindOriginalAsset(Game::XAssetType::ASSET_TYPE_MENU, "connect").menu; - - if (originalConnect == menu) // Check if we draw the original loadscreen - { - if (Menus::MenuList.find("connect") != Menus::MenuList.end()) // Check if we have a custom loadscreen, to prevent drawing the original one ontop - { - return false; - } - } - } - } - - return Game::Menu_IsVisible(dc, menu); - } - - void Menus::RemoveMenuFromContext(Game::UiContext *dc, Game::menuDef_t *menu) - { - // Search menu in context - int i = 0; - for (; i < dc->menuCount; ++i) - { - if (dc->menus[i] == menu) - { - break; - } - } - - // Remove from stack - if (i < dc->menuCount) - { - for (; i < dc->menuCount - 1; ++i) - { - dc->menus[i] = dc->menus[i + 1]; - } - - // Clear last menu - dc->menus[--dc->menuCount] = 0; - } - } - - void Menus::Add(std::string menu) - { - Menus::CustomMenus.push_back(menu); - } - - Menus::Menus() - { - if (Dedicated::IsDedicated()) return; - - // Ensure everything is zero'ed - Menus::FreeEverything(); - - // Intercept asset finding - AssetHandler::OnFind(Game::XAssetType::ASSET_TYPE_MENU, Menus::MenuLoad); - AssetHandler::OnFind(Game::XAssetType::ASSET_TYPE_MENUFILE, Menus::MenuFileLoad); - - // Don't open connect menu - Utils::Hook::Nop(0x428E48, 5); - - // Intercept menu painting - Utils::Hook(0x4FFBDF, Menus::IsMenuVisible, HOOK_CALL).Install()->Quick(); - - // disable the 2 new tokens in ItemParse_rect - Utils::Hook::Set(0x640693, 0xEB); - - // don't load ASSET_TYPE_MENU assets for every menu (might cause patch menus to fail) - Utils::Hook::Nop(0x453406, 5); - - //make Com_Error and similar go back to main_text instead of menu_xboxlive. - Utils::Hook::SetString(0x6FC790, "main_text"); - - Command::Add("openmenu", [] (Command::Params params) - { - if (params.Length() != 2) - { - Logger::Print("USAGE: openmenu \n"); - return; - } - - Game::Menus_OpenByName(Game::uiContext, params[1]); - }); - - Command::Add("reloadmenus", [] (Command::Params params) - { - // Close all menus - Game::Menus_CloseAll(Game::uiContext); - - // Free custom menus - Menus::FreeEverything(); - - // Only disconnect if in-game, context is updated automatically! - if (Game::CL_IsCgameInitialized()) - { - Game::Cbuf_AddText(0, "disconnect\n"); - } - else - { - // Reinitialize ui context - Utils::Hook::Call(0x401700)(); - - // Reopen main menu - Game::Menus_OpenByName(Game::uiContext, "main_text"); - } - }); - - // Define custom menus here - Menus::Add("ui_mp/theater_menu.menu"); - Menus::Add("ui_mp/pc_options_multi.menu"); - Menus::Add("ui_mp/pc_options_game.menu"); - Menus::Add("ui_mp/stats_reset.menu"); - Menus::Add("ui_mp/stats_unlock.menu"); - Menus::Add("ui_mp/security_increase_popmenu.menu"); - } - - Menus::~Menus() - { - Menus::CustomMenus.clear(); - Menus::FreeEverything(); - } -} +#include "STDInclude.hpp" + +namespace Components +{ + std::vector Menus::CustomMenus; + std::map Menus::MenuList; + std::map Menus::MenuListList; + + int Menus::ReserveSourceHandle() + { + // Check if a free slot is available + int i = 1; + for (; i < MAX_SOURCEFILES; ++i) + { + if (!Game::sourceFiles[i]) + break; + } + + if (i >= MAX_SOURCEFILES) + return 0; + + // Reserve it, if yes + Game::sourceFiles[i] = (Game::source_t*)1; + + return i; + } + + Game::script_t* Menus::LoadMenuScript(std::string name, std::string& buffer) + { + Game::script_t* script = Game::Script_Alloc(sizeof(Game::script_t) + 1 + buffer.length()); + + strcpy_s(script->filename, sizeof(script->filename), name.data()); + script->buffer = reinterpret_cast(script + 1); + + *(script->buffer + buffer.length()) = '\0'; + + script->script_p = script->buffer; + script->lastscript_p = script->buffer; + script->length = buffer.length(); + script->end_p = &script->buffer[buffer.length()]; + script->line = 1; + script->lastline = 1; + script->tokenavailable = 0; + + Game::Script_SetupTokens(script, reinterpret_cast(0x797F80)); + script->punctuations = reinterpret_cast(0x797F80); + + strcpy(script->buffer, buffer.data()); + + script->length = Game::Script_CleanString(script->buffer); + + return script; + } + + int Menus::LoadMenuSource(std::string name, std::string& buffer) + { + int handle = Menus::ReserveSourceHandle(); + if (!Menus::IsValidSourceHandle(handle)) return 0; // No free source slot! + + Game::source_t *source = nullptr; + Game::script_t *script = Menus::LoadMenuScript(name, buffer); + + if (!script) + { + Game::sourceFiles[handle] = nullptr; // Free reserved slot + return 0; + } + + script->next = NULL; + + source = Utils::Memory::AllocateArray(1); + if (!source) + { + Game::FreeMemory(script); + return 0; + } + + strncpy(source->filename, "string", 64); + source->scriptstack = script; + source->tokens = NULL; + source->defines = NULL; + source->indentstack = NULL; + source->skip = 0; + source->definehash = (Game::define_t**)Utils::Memory::Allocate(4096); + + Game::sourceFiles[handle] = source; + + return handle; + } + + bool Menus::IsValidSourceHandle(int handle) + { + return (handle > 0 && handle < MAX_SOURCEFILES && Game::sourceFiles[handle]); + } + + int Menus::KeywordHash(char* key) + { + int hash = 0; + + if (*key) + { + int sub = 3523 - reinterpret_cast(key); + do + { + char _chr = *key; + hash += reinterpret_cast(&(key++)[sub]) * tolower(_chr); + } while (*key); + } + + return (static_cast(hash) + static_cast(hash >> 8)) & 0x7F; + } + + Game::menuDef_t* Menus::ParseMenu(int handle) + { + Game::menuDef_t* menu = Utils::Memory::AllocateArray(1); + if (!menu) return nullptr; + + menu->items = Utils::Memory::AllocateArray(512); + if (!menu->items) + { + Utils::Memory::Free(menu); + return nullptr; + } + + Game::pc_token_t token; + if (!Game::PC_ReadTokenHandle(handle, &token) || token.string[0] != '{') + { + Utils::Memory::Free(menu->items); + Utils::Memory::Free(menu); + return nullptr; + } + + while (true) + { + ZeroMemory(&token, sizeof(token)); + + if (!Game::PC_ReadTokenHandle(handle, &token)) + { + Game::PC_SourceError(handle, "end of file inside menu\n"); + break; // Fail + } + + if (*token.string == '}') + { + break; // Success + } + + int idx = Menus::KeywordHash(token.string); + + Game::keywordHash_t* key = Game::menuParseKeywordHash[idx]; + + if (!key) + { + Game::PC_SourceError(handle, "unknown menu keyword %s", token.string); + continue; + } + + if (!key->func(menu, handle)) + { + Game::PC_SourceError(handle, "couldn't parse menu keyword %s", token.string); + break; // Fail + } + } + + Menus::OverrideMenu(menu); + Menus::RemoveMenu(menu->window.name); + Menus::MenuList[menu->window.name] = menu; + + return menu; + } + + std::vector Menus::LoadMenu(std::string menu) + { + std::vector menus; + FileSystem::File menuFile(menu); + + if (menuFile.Exists()) + { + Game::pc_token_t token; + int handle = Menus::LoadMenuSource(menu, menuFile.GetBuffer()); + + if (Menus::IsValidSourceHandle(handle)) + { + while (true) + { + ZeroMemory(&token, sizeof(token)); + + if (!Game::PC_ReadTokenHandle(handle, &token) || token.string[0] == '}') + { + break; + } + + if (!_stricmp(token.string, "loadmenu")) + { + Game::PC_ReadTokenHandle(handle, &token); + + Utils::Merge(&menus, Menus::LoadMenu(fmt::sprintf("ui_mp\\%s.menu", token.string))); + } + + if (!_stricmp(token.string, "menudef")) + { + Game::menuDef_t* menudef = Menus::ParseMenu(handle); + if (menudef) menus.push_back(menudef); + } + } + + Menus::FreeMenuSource(handle); + } + } + + return menus; + } + + std::vector Menus::LoadMenu(Game::menuDef_t* menudef) + { + std::vector menus = Menus::LoadMenu(fmt::sprintf("ui_mp\\%s.menu", menudef->window.name)); + + if (menus.empty()) + { + // Try loading the original menu, if we can't load our custom one + Game::menuDef_t* originalMenu = AssetHandler::FindOriginalAsset(Game::XAssetType::ASSET_TYPE_MENU, menudef->window.name).menu; + + if (originalMenu) + { + menus.push_back(originalMenu); + } + else + { + menus.push_back(menudef); + } + } + + return menus; + } + + Game::MenuList* Menus::LoadScriptMenu(const char* menu) + { + std::vector menus = Menus::LoadMenu(menu); + if (menus.empty()) return nullptr; + + // Allocate new menu list + Game::MenuList* newList = Utils::Memory::AllocateArray(1); + if (!newList) return nullptr; + + newList->menus = Utils::Memory::AllocateArray(menus.size()); + if (!newList->menus) + { + Utils::Memory::Free(newList); + return nullptr; + } + + newList->name = Utils::Memory::DuplicateString(menu); + newList->menuCount = menus.size(); + + // Copy new menus + std::memcpy(newList->menus, menus.data(), menus.size() * sizeof(Game::menuDef_t *)); + + Menus::RemoveMenuList(newList->name); + Menus::MenuListList[newList->name] = newList; + + return newList; + } + + Game::MenuList* Menus::LoadMenuList(Game::MenuList* menuList) + { + std::vector menus; + + for (int i = 0; i < menuList->menuCount; ++i) + { + if (!menuList->menus[i]) + { + continue; + } + + Utils::Merge(&menus, Menus::LoadMenu(menuList->menus[i])); + } + + // Load custom menus + if (menuList->name == "ui_mp/code.txt"s) // Should be menus, but code is loaded ingame + { + for (auto menu : Menus::CustomMenus) + { + Utils::Merge(&menus, Menus::LoadMenu(menu)); + } + } + + // Allocate new menu list + Game::MenuList* newList = Utils::Memory::AllocateArray(1); + if (!newList) return menuList; + + size_t size = menus.size(); + newList->menus = Utils::Memory::AllocateArray(size); + if (!newList->menus) + { + Utils::Memory::Free(newList); + return menuList; + } + + newList->name = Utils::Memory::DuplicateString(menuList->name); + newList->menuCount = size; + + // Copy new menus + std::memcpy(newList->menus, menus.data(), size * sizeof(Game::menuDef_t *)); + + Menus::RemoveMenuList(newList->name); + Menus::MenuListList[newList->name] = newList; + + return newList; + } + + void Menus::FreeMenuSource(int handle) + { + if (!Menus::IsValidSourceHandle(handle)) return; + + Game::source_t *source = Game::sourceFiles[handle]; + + while (source->scriptstack) + { + Game::script_t* script = source->scriptstack; + source->scriptstack = source->scriptstack->next; + Game::FreeMemory(script); + } + + while (source->tokens) + { + Game::token_t* token = source->tokens; + source->tokens = source->tokens->next; + Game::FreeMemory(token); + } + + while (source->defines) + { + Game::define_t* define = source->defines; + source->defines = source->defines->next; + Game::FreeMemory(define); + } + + while (source->indentstack) + { + Game::indent_t* indent = source->indentstack; + source->indentstack = source->indentstack->next; + Utils::Memory::Free(indent); + } + + if (source->definehash) Utils::Memory::Free(source->definehash); + + Utils::Memory::Free(source); + + Game::sourceFiles[handle] = nullptr; + } + + void Menus::FreeMenu(Game::menuDef_t* menudef) + { + // Do i need to free expressions and strings? + // Or does the game take care of it? + // Seems like it does... + + if (menudef->items) + { + // Seems like this is obsolete as well, + // as the game handles the memory + + //for (int i = 0; i < menudef->itemCount; ++i) + //{ + // Game::Menu_FreeItemMemory(menudef->items[i]); + //} + + Utils::Memory::Free(menudef->items); + } + + Utils::Memory::Free(menudef); + } + + void Menus::FreeMenuList(Game::MenuList* menuList) + { + if (!menuList) return; + + // Keep our compiler happy + Game::MenuList list = { menuList->name, menuList->menuCount, menuList->menus }; + + if (list.name) + { + Utils::Memory::Free(list.name); + } + + if (list.menus) + { + Utils::Memory::Free(list.menus); + } + + Utils::Memory::Free(menuList); + } + + void Menus::RemoveMenu(std::string menu) + { + auto i = Menus::MenuList.find(menu); + if(i != Menus::MenuList.end()) + { + if (i->second) Menus::FreeMenu(i->second); + i = Menus::MenuList.erase(i); + } + } + + void Menus::RemoveMenu(Game::menuDef_t* menudef) + { + for (auto i = Menus::MenuList.begin(); i != Menus::MenuList.end();) + { + if (i->second == menudef) + { + Menus::FreeMenu(menudef); + i = Menus::MenuList.erase(i); + } + else + { + ++i; + } + } + } + + void Menus::RemoveMenuList(std::string menuList) + { + auto i = Menus::MenuListList.find(menuList); + if (i != Menus::MenuListList.end()) + { + if (i->second) + { + for (auto j = 0; j < i->second->menuCount; ++j) + { + Menus::RemoveMenu(i->second->menus[j]); + } + + Menus::FreeMenuList(i->second); + } + + i = Menus::MenuListList.erase(i); + } + } + + // This is actually a really important function + // It checks if we have already loaded the menu we passed and replaces its instances in memory + // Due to deallocating the old menu, the game might crash on not being able to handle its old instance + // So we need to override it in our menu lists and the game's ui context + // EDIT: We might also remove the old instances inside RemoveMenu + // EDIT2: Removing old instances without having a menu to replace them with might leave a nullptr + // EDIT3: Wouldn't it be better to check if the new menu we're trying to load has already been loaded and not was not deallocated and return that one instead of loading a new one? + void Menus::OverrideMenu(Game::menuDef_t *menu) + { + if (!menu || !menu->window.name) return; + std::string name = menu->window.name; + + // Find the old menu + auto i = Menus::MenuList.find(name); + if (i != Menus::MenuList.end()) + { + // We have found it, *yay* + Game::menuDef_t* oldMenu = i->second; + + // Replace every old instance with our new one in the ui context + for (int j = 0; j < Game::uiContext->menuCount; ++j) + { + if (Game::uiContext->menus[j] == oldMenu) + { + Game::uiContext->menus[j] = menu; + } + } + + // Replace every old instance with our new one in our menu lists + for (auto j = Menus::MenuListList.begin(); j != Menus::MenuListList.end(); ++j) + { + Game::MenuList* list = j->second; + + if (list && list->menus) + { + for (int k = 0; k < list->menuCount; ++k) + { + if (list->menus[k] == oldMenu) + { + list->menus[k] = menu; + } + } + } + } + } + } + + void Menus::RemoveMenuList(Game::MenuList* menuList) + { + if (!menuList || !menuList->name) return; + Menus::RemoveMenuList(menuList->name); + } + + void Menus::FreeEverything() + { + for (auto i = Menus::MenuListList.begin(); i != Menus::MenuListList.end(); ++i) + { + Menus::FreeMenuList(i->second); + } + + Menus::MenuListList.clear(); + + for (auto i = Menus::MenuList.begin(); i != Menus::MenuList.end(); ++i) + { + Menus::FreeMenu(i->second); + } + + Menus::MenuList.clear(); + } + + Game::XAssetHeader Menus::MenuLoad(Game::XAssetType type, std::string filename) + { + return { Game::Menus_FindByName(Game::uiContext, filename.data()) }; + } + + Game::XAssetHeader Menus::MenuFileLoad(Game::XAssetType type, std::string filename) + { + Game::XAssetHeader header = { 0 }; + + Game::MenuList* menuList = Game::DB_FindXAssetHeader(type, filename.data()).menuList; + header.menuList = menuList; + + // Free the last menulist and ui context, as we have to rebuild it with the new menus + if (Menus::MenuListList.find(filename) != Menus::MenuListList.end()) + { + Game::MenuList* list = Menus::MenuListList[filename]; + + for (int i = 0; list && list->menus && i < list->menuCount; ++i) + { + Menus::RemoveMenuFromContext(Game::uiContext, list->menus[i]); + } + + Menus::RemoveMenuList(filename); + } + + if (menuList) + { + // Parse scriptmenus! + if (menuList->menus[0]->window.name == "default_menu"s || Utils::String::EndsWith(filename, ".menu")) + { + if (FileSystem::File(filename).Exists()) + { + header.menuList = Menus::LoadScriptMenu(filename.data()); + + // Reset, if we didn't find scriptmenus + if (!header.menuList) + { + header.menuList = menuList; + } + } + } + else + { + header.menuList = Menus::LoadMenuList(menuList); + } + } + + return header; + } + + bool Menus::IsMenuVisible(Game::UiContext *dc, Game::menuDef_t *menu) + { + if (menu && menu->window.name) + { + if (menu->window.name == "connect"s) // Check if we're supposed to draw the loadscreen + { + Game::menuDef_t* originalConnect = AssetHandler::FindOriginalAsset(Game::XAssetType::ASSET_TYPE_MENU, "connect").menu; + + if (originalConnect == menu) // Check if we draw the original loadscreen + { + if (Menus::MenuList.find("connect") != Menus::MenuList.end()) // Check if we have a custom loadscreen, to prevent drawing the original one ontop + { + return false; + } + } + } + } + + return Game::Menu_IsVisible(dc, menu); + } + + void Menus::RemoveMenuFromContext(Game::UiContext *dc, Game::menuDef_t *menu) + { + // Search menu in context + int i = 0; + for (; i < dc->menuCount; ++i) + { + if (dc->menus[i] == menu) + { + break; + } + } + + // Remove from stack + if (i < dc->menuCount) + { + for (; i < dc->menuCount - 1; ++i) + { + dc->menus[i] = dc->menus[i + 1]; + } + + // Clear last menu + dc->menus[--dc->menuCount] = 0; + } + } + + void Menus::Add(std::string menu) + { + Menus::CustomMenus.push_back(menu); + } + + Menus::Menus() + { + if (Dedicated::IsDedicated()) return; + + // Ensure everything is zero'ed + Menus::FreeEverything(); + + // Intercept asset finding + AssetHandler::OnFind(Game::XAssetType::ASSET_TYPE_MENU, Menus::MenuLoad); + AssetHandler::OnFind(Game::XAssetType::ASSET_TYPE_MENUFILE, Menus::MenuFileLoad); + + // Don't open connect menu + Utils::Hook::Nop(0x428E48, 5); + + // Intercept menu painting + Utils::Hook(0x4FFBDF, Menus::IsMenuVisible, HOOK_CALL).Install()->Quick(); + + // disable the 2 new tokens in ItemParse_rect + Utils::Hook::Set(0x640693, 0xEB); + + // don't load ASSET_TYPE_MENU assets for every menu (might cause patch menus to fail) + Utils::Hook::Nop(0x453406, 5); + + //make Com_Error and similar go back to main_text instead of menu_xboxlive. + Utils::Hook::SetString(0x6FC790, "main_text"); + + Command::Add("openmenu", [] (Command::Params params) + { + if (params.Length() != 2) + { + Logger::Print("USAGE: openmenu \n"); + return; + } + + Game::Menus_OpenByName(Game::uiContext, params[1]); + }); + + Command::Add("reloadmenus", [] (Command::Params params) + { + // Close all menus + Game::Menus_CloseAll(Game::uiContext); + + // Free custom menus + Menus::FreeEverything(); + + // Only disconnect if in-game, context is updated automatically! + if (Game::CL_IsCgameInitialized()) + { + Game::Cbuf_AddText(0, "disconnect\n"); + } + else + { + // Reinitialize ui context + Utils::Hook::Call(0x401700)(); + + // Reopen main menu + Game::Menus_OpenByName(Game::uiContext, "main_text"); + } + }); + + // Define custom menus here + Menus::Add("ui_mp/theater_menu.menu"); + Menus::Add("ui_mp/pc_options_multi.menu"); + Menus::Add("ui_mp/pc_options_game.menu"); + Menus::Add("ui_mp/stats_reset.menu"); + Menus::Add("ui_mp/stats_unlock.menu"); + Menus::Add("ui_mp/security_increase_popmenu.menu"); + } + + Menus::~Menus() + { + Menus::CustomMenus.clear(); + Menus::FreeEverything(); + } +} diff --git a/src/Components/Modules/ModList.cpp b/src/Components/Modules/ModList.cpp index 2856dbef..090727a4 100644 --- a/src/Components/Modules/ModList.cpp +++ b/src/Components/Modules/ModList.cpp @@ -1,102 +1,102 @@ -#include "STDInclude.hpp" - -namespace Components -{ - std::vector ModList::Mods; - unsigned int ModList::CurrentMod; - - bool ModList::HasMod(std::string modName) - { - auto list = FileSystem::GetSysFileList(Dvar::Var("fs_basepath").Get() + "\\mods", "", true); - - for (auto mod : list) - { - if (mod == modName) - { - return true; - } - } - - return false; - } - - unsigned int ModList::GetItemCount() - { - return ModList::Mods.size(); - } - - const char* ModList::GetItemText(unsigned int index, int column) - { - if (index < ModList::Mods.size()) - { - return ModList::Mods[index].data(); - } - - return "..."; - } - - void ModList::Select(unsigned int index) - { - ModList::CurrentMod = index; - } - - void ModList::UIScript_LoadMods() - { - auto folder = Dvar::Var("fs_basepath").Get() + "\\mods"; - Game::Com_Printf(0, "Searching for mods in %s...\n", folder.data()); - ModList::Mods = FileSystem::GetSysFileList(folder, "", true); - Game::Com_Printf(0, "Found %i mods!\n", ModList::Mods.size()); - } - - void ModList::UIScript_RunMod() - { - if (ModList::CurrentMod < ModList::Mods.size()) - { - auto fsGame = Dvar::Var("fs_game"); - fsGame.Set(Utils::VA("mods/%s", ModList::Mods[ModList::CurrentMod].data())); - fsGame.Get()->pad2[0] = 1; - - if (Dvar::Var("cl_modVidRestart").Get()) - { - Command::Execute("vid_restart", false); - } - else - { - Command::Execute("closemenu mods_menu", false); - } - } - } - - void ModList::UIScript_ClearMods() - { - auto fsGame = Dvar::Var("fs_game"); - fsGame.Set(""); - fsGame.Get()->pad2[0] = 1; - - if (Dvar::Var("cl_modVidRestart").Get()) - { - Game::Cmd_ExecuteSingleCommand(0, 0, "vid_restart"); - } - else - { - Game::Cmd_ExecuteSingleCommand(0, 0, "closemenu mods_menu"); - } - } - - ModList::ModList() - { - ModList::CurrentMod = 0; - Dvar::Register("cl_modVidRestart", true, Game::dvar_flag::DVAR_FLAG_SAVED, "Perform a vid_restart when loading a mod."); - - UIScript::Add("LoadMods", ModList::UIScript_LoadMods); - UIScript::Add("RunMod", ModList::UIScript_RunMod); - UIScript::Add("ClearMods", ModList::UIScript_ClearMods); - - UIFeeder::Add(9.0f, ModList::GetItemCount, ModList::GetItemText, ModList::Select); - } - - ModList::~ModList() - { - ModList::Mods.clear(); - } +#include "STDInclude.hpp" + +namespace Components +{ + std::vector ModList::Mods; + unsigned int ModList::CurrentMod; + + bool ModList::HasMod(std::string modName) + { + auto list = FileSystem::GetSysFileList(Dvar::Var("fs_basepath").Get() + "\\mods", "", true); + + for (auto mod : list) + { + if (mod == modName) + { + return true; + } + } + + return false; + } + + unsigned int ModList::GetItemCount() + { + return ModList::Mods.size(); + } + + const char* ModList::GetItemText(unsigned int index, int column) + { + if (index < ModList::Mods.size()) + { + return ModList::Mods[index].data(); + } + + return "..."; + } + + void ModList::Select(unsigned int index) + { + ModList::CurrentMod = index; + } + + void ModList::UIScript_LoadMods() + { + auto folder = Dvar::Var("fs_basepath").Get() + "\\mods"; + Game::Com_Printf(0, "Searching for mods in %s...\n", folder.data()); + ModList::Mods = FileSystem::GetSysFileList(folder, "", true); + Game::Com_Printf(0, "Found %i mods!\n", ModList::Mods.size()); + } + + void ModList::UIScript_RunMod() + { + if (ModList::CurrentMod < ModList::Mods.size()) + { + auto fsGame = Dvar::Var("fs_game"); + fsGame.Set(fmt::sprintf("mods/%s", ModList::Mods[ModList::CurrentMod].data())); + fsGame.Get()->pad2[0] = 1; + + if (Dvar::Var("cl_modVidRestart").Get()) + { + Command::Execute("vid_restart", false); + } + else + { + Command::Execute("closemenu mods_menu", false); + } + } + } + + void ModList::UIScript_ClearMods() + { + auto fsGame = Dvar::Var("fs_game"); + fsGame.Set(""); + fsGame.Get()->pad2[0] = 1; + + if (Dvar::Var("cl_modVidRestart").Get()) + { + Game::Cmd_ExecuteSingleCommand(0, 0, "vid_restart"); + } + else + { + Game::Cmd_ExecuteSingleCommand(0, 0, "closemenu mods_menu"); + } + } + + ModList::ModList() + { + ModList::CurrentMod = 0; + Dvar::Register("cl_modVidRestart", true, Game::dvar_flag::DVAR_FLAG_SAVED, "Perform a vid_restart when loading a mod."); + + UIScript::Add("LoadMods", ModList::UIScript_LoadMods); + UIScript::Add("RunMod", ModList::UIScript_RunMod); + UIScript::Add("ClearMods", ModList::UIScript_ClearMods); + + UIFeeder::Add(9.0f, ModList::GetItemCount, ModList::GetItemText, ModList::Select); + } + + ModList::~ModList() + { + ModList::Mods.clear(); + } } \ No newline at end of file diff --git a/src/Components/Modules/MusicalTalent.cpp b/src/Components/Modules/MusicalTalent.cpp index 21dce32b..8c91598d 100644 --- a/src/Components/Modules/MusicalTalent.cpp +++ b/src/Components/Modules/MusicalTalent.cpp @@ -1,45 +1,45 @@ -#include "STDInclude.hpp" - -namespace Components -{ - std::map MusicalTalent::SoundAliasList; - - void MusicalTalent::Replace(std::string sound, const char* file) - { - MusicalTalent::SoundAliasList[Utils::StrToLower(sound)] = file; - } - - Game::XAssetHeader MusicalTalent::ModifyAliases(Game::XAssetType type, std::string filename) - { - Game::XAssetHeader header = { 0 }; - - if (MusicalTalent::SoundAliasList.find(Utils::StrToLower(filename)) != MusicalTalent::SoundAliasList.end()) - { - Game::snd_alias_list_t* aliases = Game::DB_FindXAssetHeader(type, filename.data()).aliasList; - - if (aliases) - { - if (aliases->aliases->stream->type == 2) - { - aliases->aliases->stream->file = MusicalTalent::SoundAliasList[Utils::StrToLower(filename)]; - } - - header.aliasList = aliases; - } - } - - return header; - } - - MusicalTalent::MusicalTalent() - { - AssetHandler::OnFind(Game::XAssetType::ASSET_TYPE_SOUND, MusicalTalent::ModifyAliases); - - MusicalTalent::Replace("music_mainmenu_mp", "hz_t_menumusic.mp3"); - } - - MusicalTalent::~MusicalTalent() - { - MusicalTalent::SoundAliasList.clear(); - } -} +#include "STDInclude.hpp" + +namespace Components +{ + std::map MusicalTalent::SoundAliasList; + + void MusicalTalent::Replace(std::string sound, const char* file) + { + MusicalTalent::SoundAliasList[Utils::String::StrToLower(sound)] = file; + } + + Game::XAssetHeader MusicalTalent::ModifyAliases(Game::XAssetType type, std::string filename) + { + Game::XAssetHeader header = { 0 }; + + if (MusicalTalent::SoundAliasList.find(Utils::String::StrToLower(filename)) != MusicalTalent::SoundAliasList.end()) + { + Game::snd_alias_list_t* aliases = Game::DB_FindXAssetHeader(type, filename.data()).aliasList; + + if (aliases) + { + if (aliases->aliases->stream->type == 2) + { + aliases->aliases->stream->file = MusicalTalent::SoundAliasList[Utils::String::StrToLower(filename)]; + } + + header.aliasList = aliases; + } + } + + return header; + } + + MusicalTalent::MusicalTalent() + { + AssetHandler::OnFind(Game::XAssetType::ASSET_TYPE_SOUND, MusicalTalent::ModifyAliases); + + MusicalTalent::Replace("music_mainmenu_mp", "hz_t_menumusic.mp3"); + } + + MusicalTalent::~MusicalTalent() + { + MusicalTalent::SoundAliasList.clear(); + } +} diff --git a/src/Components/Modules/Network.cpp b/src/Components/Modules/Network.cpp index 54ed98db..bd54dae7 100644 --- a/src/Components/Modules/Network.cpp +++ b/src/Components/Modules/Network.cpp @@ -1,342 +1,342 @@ -#include "STDInclude.hpp" - -namespace Components -{ - std::string Network::SelectedPacket; - wink::signal> Network::StartupSignal; - std::map> Network::PacketHandlers; - - Network::Address::Address(std::string addrString) - { - Game::NET_StringToAdr(addrString.data(), &this->address); - } - Network::Address::Address(sockaddr* addr) - { - Game::SockadrToNetadr(addr, &this->address); - } - bool Network::Address::operator==(const Network::Address &obj) - { - return Game::NET_CompareAdr(this->address, obj.address); - } - void Network::Address::SetPort(unsigned short port) - { - this->address.port = htons(port); - } - unsigned short Network::Address::GetPort() - { - return ntohs(this->address.port); - } - void Network::Address::SetIP(DWORD ip) - { - this->address.ip.full = ip; - } - void Network::Address::SetIP(Game::netIP_t ip) - { - this->address.ip = ip; - } - Game::netIP_t Network::Address::GetIP() - { - return this->address.ip; - } - void Network::Address::SetType(Game::netadrtype_t type) - { - this->address.type = type; - } - Game::netadrtype_t Network::Address::GetType() - { - return this->address.type; - } - sockaddr Network::Address::GetSockAddr() - { - sockaddr addr; - this->ToSockAddr(&addr); - return addr; - } - void Network::Address::ToSockAddr(sockaddr* addr) - { - if (addr) - { - Game::NetadrToSockadr(&this->address, addr); - } - } - void Network::Address::ToSockAddr(sockaddr_in* addr) - { - this->ToSockAddr(reinterpret_cast(addr)); - } - Game::netadr_t* Network::Address::Get() - { - return &this->address; - } - const char* Network::Address::GetCString() - { - return Game::NET_AdrToString(this->address); - } - std::string Network::Address::GetString() - { - return this->GetCString(); - } - bool Network::Address::IsLocal() - { - // According to: https://en.wikipedia.org/wiki/Private_network - - // 10.X.X.X - if (this->GetIP().bytes[0] == 10) return true; - - // 192.168.X.X - if (this->GetIP().bytes[0] == 192 && this->GetIP().bytes[1] == 168) return true; - - // 172.16.X.X - 172.31.X.X - if (this->GetIP().bytes[0] == 172 && (this->GetIP().bytes[1] >= 16) && (this->GetIP().bytes[1] < 32)) return true; - - // TODO: Maybe check for matching localIPs and subnet mask - - return false; - } - bool Network::Address::IsSelf() - { - if (Game::NET_IsLocalAddress(this->address)) return true; // Loopback - if (this->GetPort() != (Dvar::Var("net_port").Get() & 0xFFFF)) return false; // Port not equal - - for (int i = 0; i < *Game::numIP; ++i) - { - if (this->GetIP().full == Game::localIP[i].full) - { - return true; - } - } - - return false; - } - bool Network::Address::IsValid() - { - return (this->GetType() != Game::netadrtype_t::NA_BAD); - } - void Network::Address::Serialize(Proto::Network::Address* protoAddress) - { - protoAddress->set_ip(this->GetIP().full); - protoAddress->set_port(this->GetPort() & 0xFFFF); - } - void Network::Address::Deserialize(const Proto::Network::Address& protoAddress) - { - this->SetIP(protoAddress.ip()); - this->SetPort(static_cast(protoAddress.port())); - this->SetType(Game::netadrtype_t::NA_IP); - } - - void Network::Handle(std::string packet, Network::Callback* callback) - { - Network::PacketHandlers[Utils::StrToLower(packet)] = callback; - } - - void Network::OnStart(Network::CallbackRaw* callback) - { - Network::StartupSignal.connect(callback); - } - - void Network::Send(Game::netsrc_t type, Network::Address target, std::string data) - { - // NET_OutOfBandPrint only supports non-binary data! - //Game::NET_OutOfBandPrint(type, *target.Get(), data.data()); - - std::string rawData; - rawData.append("\xFF\xFF\xFF\xFF", 4); - rawData.append(data); - //rawData.append("\0", 1); - - Network::SendRaw(type, target, rawData); - } - - void Network::Send(Network::Address target, std::string data) - { - Network::Send(Game::netsrc_t::NS_CLIENT, target, data); - } - - void Network::SendRaw(Game::netsrc_t type, Network::Address target, std::string data) - { - // NET_OutOfBandData doesn't seem to work properly - //Game::NET_OutOfBandData(type, *target.Get(), data.data(), data.size()); - Game::Sys_SendPacket(type, data.size(), data.data(), *target.Get()); - } - - void Network::SendRaw(Network::Address target, std::string data) - { - Network::SendRaw(Game::netsrc_t::NS_CLIENT, target, data); - } - - void Network::SendCommand(Game::netsrc_t type, Network::Address target, std::string command, std::string data) - { - // Use space as separator (possible separators are '\n', ' '). - // Though, our handler only needs exactly 1 char as separator and doesn't which char it is - std::string packet; - packet.append(command); - packet.append(" ", 1); - packet.append(data); - - Network::Send(type, target, packet); - } - - void Network::SendCommand(Network::Address target, std::string command, std::string data) - { - Network::SendCommand(Game::netsrc_t::NS_CLIENT, target, command, data); - } - - void Network::Broadcast(unsigned short port, std::string data) - { - Address target; - - target.SetPort(port); - target.SetIP(INADDR_BROADCAST); - target.SetType(Game::netadrtype_t::NA_BROADCAST); - - Network::Send(Game::netsrc_t::NS_CLIENT, target, data); - } - - void Network::BroadcastRange(unsigned int min, unsigned int max, std::string data) - { - for (unsigned int i = min; i < max; ++i) - { - Network::Broadcast(static_cast(i & 0xFFFF), data); - } - } - - void Network::BroadcastAll(std::string data) - { - Network::BroadcastRange(100, 65536, data); - } - - int Network::PacketInterceptionHandler(const char* packet) - { - // Packet rate limit. - static uint32_t packets = 0; - static int lastClean = 0; - - if ((Game::Sys_Milliseconds() - lastClean) > 1'000) - { - packets = 0; - lastClean = Game::Sys_Milliseconds(); - } - - if ((++packets) > NETWORK_MAX_PACKETS_PER_SECOND) - { - return 1; - } - - std::string packetCommand = packet; - auto pos = packetCommand.find_first_of("\\\n "); - if (pos != std::string::npos) - { - packetCommand = packetCommand.substr(0, pos); - } - - packetCommand = Utils::StrToLower(packetCommand); - - // Check if custom handler exists - for (auto i = Network::PacketHandlers.begin(); i != Network::PacketHandlers.end(); ++i) - { - if (Utils::StrToLower(i->first) == packetCommand) - { - Network::SelectedPacket = i->first; - return 0; - } - } - - // No interception - return 1; - } - - void Network::DeployPacket(Game::netadr_t* from, Game::msg_t* msg) - { - if (Network::PacketHandlers.find(Network::SelectedPacket) != Network::PacketHandlers.end()) - { - std::string data; - - size_t offset = Network::SelectedPacket.size() + 4 + 1; - - if (static_cast(msg->cursize) > offset) - { - data.append(msg->data + offset, msg->cursize - offset); - } - - // Remove trailing 0x00 byte - // Actually, don't remove it, it might be part of the packet. Send correctly formatted packets instead! - //if (data.size() && !data[data.size() - 1]) data.pop_back(); - - Network::PacketHandlers[Network::SelectedPacket](from, data); - } - else - { - Logger::Print("Error: Network packet intercepted, but handler is missing!\n"); - } - } - - void Network::NetworkStart() - { - Network::StartupSignal(); - } - - void __declspec(naked) Network::NetworkStartStub() - { - __asm - { - mov eax, 64D900h - call eax - jmp Network::NetworkStart - } - } - - void __declspec(naked) Network::DeployPacketStub() - { - __asm - { - lea eax, [esp + 0C54h] - push ebp // Command - push eax // Address pointer - call Network::DeployPacket - add esp, 8h - mov al, 1 - pop edi - pop esi - pop ebp - pop ebx - add esp, 0C40h - retn - } - } - - Network::Network() - { - Assert_Size(Game::netadr_t, 20); - - // maximum size in NET_OutOfBandPrint - Utils::Hook::Set(0x4AEF08, 0x1FFFC); - Utils::Hook::Set(0x4AEFA3, 0x1FFFC); - - // increase max port binding attempts from 10 to 100 - Utils::Hook::Set(0x4FD48A, 100); - - // Parse port as short in Net_AddrToString - Utils::Hook::Set(0x4698E3, "%u.%u.%u.%u:%hu"); - - // Install startup handler - Utils::Hook(0x4FD4D4, Network::NetworkStartStub, HOOK_JUMP).Install()->Quick(); - - // Install interception handler - Utils::Hook(0x5AA709, Network::PacketInterceptionHandler, HOOK_CALL).Install()->Quick(); - - // Install packet deploy hook - Utils::Hook::RedirectJump(0x5AA713, Network::DeployPacketStub); - - // For /dev/urandom :P - Network::Handle("resolveAddress", [] (Address address, std::string data) - { - Network::SendRaw(address, address.GetString()); - }); - } - - Network::~Network() - { - Network::SelectedPacket.clear(); - Network::PacketHandlers.clear(); - Network::StartupSignal.clear(); - } -} +#include "STDInclude.hpp" + +namespace Components +{ + std::string Network::SelectedPacket; + wink::signal> Network::StartupSignal; + std::map> Network::PacketHandlers; + + Network::Address::Address(std::string addrString) + { + Game::NET_StringToAdr(addrString.data(), &this->address); + } + Network::Address::Address(sockaddr* addr) + { + Game::SockadrToNetadr(addr, &this->address); + } + bool Network::Address::operator==(const Network::Address &obj) + { + return Game::NET_CompareAdr(this->address, obj.address); + } + void Network::Address::SetPort(unsigned short port) + { + this->address.port = htons(port); + } + unsigned short Network::Address::GetPort() + { + return ntohs(this->address.port); + } + void Network::Address::SetIP(DWORD ip) + { + this->address.ip.full = ip; + } + void Network::Address::SetIP(Game::netIP_t ip) + { + this->address.ip = ip; + } + Game::netIP_t Network::Address::GetIP() + { + return this->address.ip; + } + void Network::Address::SetType(Game::netadrtype_t type) + { + this->address.type = type; + } + Game::netadrtype_t Network::Address::GetType() + { + return this->address.type; + } + sockaddr Network::Address::GetSockAddr() + { + sockaddr addr; + this->ToSockAddr(&addr); + return addr; + } + void Network::Address::ToSockAddr(sockaddr* addr) + { + if (addr) + { + Game::NetadrToSockadr(&this->address, addr); + } + } + void Network::Address::ToSockAddr(sockaddr_in* addr) + { + this->ToSockAddr(reinterpret_cast(addr)); + } + Game::netadr_t* Network::Address::Get() + { + return &this->address; + } + const char* Network::Address::GetCString() + { + return Game::NET_AdrToString(this->address); + } + std::string Network::Address::GetString() + { + return this->GetCString(); + } + bool Network::Address::IsLocal() + { + // According to: https://en.wikipedia.org/wiki/Private_network + + // 10.X.X.X + if (this->GetIP().bytes[0] == 10) return true; + + // 192.168.X.X + if (this->GetIP().bytes[0] == 192 && this->GetIP().bytes[1] == 168) return true; + + // 172.16.X.X - 172.31.X.X + if (this->GetIP().bytes[0] == 172 && (this->GetIP().bytes[1] >= 16) && (this->GetIP().bytes[1] < 32)) return true; + + // TODO: Maybe check for matching localIPs and subnet mask + + return false; + } + bool Network::Address::IsSelf() + { + if (Game::NET_IsLocalAddress(this->address)) return true; // Loopback + if (this->GetPort() != (Dvar::Var("net_port").Get() & 0xFFFF)) return false; // Port not equal + + for (int i = 0; i < *Game::numIP; ++i) + { + if (this->GetIP().full == Game::localIP[i].full) + { + return true; + } + } + + return false; + } + bool Network::Address::IsValid() + { + return (this->GetType() != Game::netadrtype_t::NA_BAD); + } + void Network::Address::Serialize(Proto::Network::Address* protoAddress) + { + protoAddress->set_ip(this->GetIP().full); + protoAddress->set_port(this->GetPort() & 0xFFFF); + } + void Network::Address::Deserialize(const Proto::Network::Address& protoAddress) + { + this->SetIP(protoAddress.ip()); + this->SetPort(static_cast(protoAddress.port())); + this->SetType(Game::netadrtype_t::NA_IP); + } + + void Network::Handle(std::string packet, Network::Callback* callback) + { + Network::PacketHandlers[Utils::String::StrToLower(packet)] = callback; + } + + void Network::OnStart(Network::CallbackRaw* callback) + { + Network::StartupSignal.connect(callback); + } + + void Network::Send(Game::netsrc_t type, Network::Address target, std::string data) + { + // NET_OutOfBandPrint only supports non-binary data! + //Game::NET_OutOfBandPrint(type, *target.Get(), data.data()); + + std::string rawData; + rawData.append("\xFF\xFF\xFF\xFF", 4); + rawData.append(data); + //rawData.append("\0", 1); + + Network::SendRaw(type, target, rawData); + } + + void Network::Send(Network::Address target, std::string data) + { + Network::Send(Game::netsrc_t::NS_CLIENT, target, data); + } + + void Network::SendRaw(Game::netsrc_t type, Network::Address target, std::string data) + { + // NET_OutOfBandData doesn't seem to work properly + //Game::NET_OutOfBandData(type, *target.Get(), data.data(), data.size()); + Game::Sys_SendPacket(type, data.size(), data.data(), *target.Get()); + } + + void Network::SendRaw(Network::Address target, std::string data) + { + Network::SendRaw(Game::netsrc_t::NS_CLIENT, target, data); + } + + void Network::SendCommand(Game::netsrc_t type, Network::Address target, std::string command, std::string data) + { + // Use space as separator (possible separators are '\n', ' '). + // Though, our handler only needs exactly 1 char as separator and doesn't which char it is + std::string packet; + packet.append(command); + packet.append(" ", 1); + packet.append(data); + + Network::Send(type, target, packet); + } + + void Network::SendCommand(Network::Address target, std::string command, std::string data) + { + Network::SendCommand(Game::netsrc_t::NS_CLIENT, target, command, data); + } + + void Network::Broadcast(unsigned short port, std::string data) + { + Address target; + + target.SetPort(port); + target.SetIP(INADDR_BROADCAST); + target.SetType(Game::netadrtype_t::NA_BROADCAST); + + Network::Send(Game::netsrc_t::NS_CLIENT, target, data); + } + + void Network::BroadcastRange(unsigned int min, unsigned int max, std::string data) + { + for (unsigned int i = min; i < max; ++i) + { + Network::Broadcast(static_cast(i & 0xFFFF), data); + } + } + + void Network::BroadcastAll(std::string data) + { + Network::BroadcastRange(100, 65536, data); + } + + int Network::PacketInterceptionHandler(const char* packet) + { + // Packet rate limit. + static uint32_t packets = 0; + static int lastClean = 0; + + if ((Game::Sys_Milliseconds() - lastClean) > 1'000) + { + packets = 0; + lastClean = Game::Sys_Milliseconds(); + } + + if ((++packets) > NETWORK_MAX_PACKETS_PER_SECOND) + { + return 1; + } + + std::string packetCommand = packet; + auto pos = packetCommand.find_first_of("\\\n "); + if (pos != std::string::npos) + { + packetCommand = packetCommand.substr(0, pos); + } + + packetCommand = Utils::String::StrToLower(packetCommand); + + // Check if custom handler exists + for (auto i = Network::PacketHandlers.begin(); i != Network::PacketHandlers.end(); ++i) + { + if (Utils::String::StrToLower(i->first) == packetCommand) + { + Network::SelectedPacket = i->first; + return 0; + } + } + + // No interception + return 1; + } + + void Network::DeployPacket(Game::netadr_t* from, Game::msg_t* msg) + { + if (Network::PacketHandlers.find(Network::SelectedPacket) != Network::PacketHandlers.end()) + { + std::string data; + + size_t offset = Network::SelectedPacket.size() + 4 + 1; + + if (static_cast(msg->cursize) > offset) + { + data.append(msg->data + offset, msg->cursize - offset); + } + + // Remove trailing 0x00 byte + // Actually, don't remove it, it might be part of the packet. Send correctly formatted packets instead! + //if (data.size() && !data[data.size() - 1]) data.pop_back(); + + Network::PacketHandlers[Network::SelectedPacket](from, data); + } + else + { + Logger::Print("Error: Network packet intercepted, but handler is missing!\n"); + } + } + + void Network::NetworkStart() + { + Network::StartupSignal(); + } + + void __declspec(naked) Network::NetworkStartStub() + { + __asm + { + mov eax, 64D900h + call eax + jmp Network::NetworkStart + } + } + + void __declspec(naked) Network::DeployPacketStub() + { + __asm + { + lea eax, [esp + 0C54h] + push ebp // Command + push eax // Address pointer + call Network::DeployPacket + add esp, 8h + mov al, 1 + pop edi + pop esi + pop ebp + pop ebx + add esp, 0C40h + retn + } + } + + Network::Network() + { + Assert_Size(Game::netadr_t, 20); + + // maximum size in NET_OutOfBandPrint + Utils::Hook::Set(0x4AEF08, 0x1FFFC); + Utils::Hook::Set(0x4AEFA3, 0x1FFFC); + + // increase max port binding attempts from 10 to 100 + Utils::Hook::Set(0x4FD48A, 100); + + // Parse port as short in Net_AddrToString + Utils::Hook::Set(0x4698E3, "%u.%u.%u.%u:%hu"); + + // Install startup handler + Utils::Hook(0x4FD4D4, Network::NetworkStartStub, HOOK_JUMP).Install()->Quick(); + + // Install interception handler + Utils::Hook(0x5AA709, Network::PacketInterceptionHandler, HOOK_CALL).Install()->Quick(); + + // Install packet deploy hook + Utils::Hook::RedirectJump(0x5AA713, Network::DeployPacketStub); + + // For /dev/urandom :P + Network::Handle("resolveAddress", [] (Address address, std::string data) + { + Network::SendRaw(address, address.GetString()); + }); + } + + Network::~Network() + { + Network::SelectedPacket.clear(); + Network::PacketHandlers.clear(); + Network::StartupSignal.clear(); + } +} diff --git a/src/Components/Modules/News.cpp b/src/Components/Modules/News.cpp index 5ee25cb7..ae929e55 100644 --- a/src/Components/Modules/News.cpp +++ b/src/Components/Modules/News.cpp @@ -1,69 +1,69 @@ -#include "STDInclude.hpp" - -#define NEWS_MOTD_DEFUALT "Welcome to IW4x Multiplayer!" - -namespace Components -{ - std::thread News::Thread; - - bool News::UnitTest() - { - bool result = true; - - if (News::Thread.joinable()) - { - Logger::Print("Awaiting thread termination...\n"); - News::Thread.join(); - - if (!strlen(Localization::Get("MPUI_CHANGELOG_TEXT")) || Localization::Get("MPUI_CHANGELOG_TEXT") == "Loading..."s) - { - Logger::Print("Failed to fetch changelog!\n"); - result = false; - } - else - { - Logger::Print("Successfully fetched changelog.\n"); - } - - if (!strcmp(Localization::Get("MPUI_MOTD_TEXT"), NEWS_MOTD_DEFUALT)) - { - Logger::Print("Failed to fetch motd!\n"); - result = false; - } - else - { - Logger::Print("Successfully fetched motd.\n"); - } - } - - return result; - } - - News::News() - { - Localization::Set("MPUI_CHANGELOG_TEXT", "Loading..."); - Localization::Set("MPUI_MOTD_TEXT", NEWS_MOTD_DEFUALT); - - News::Thread = std::thread([] () - { - Localization::Set("MPUI_CHANGELOG_TEXT", Utils::WebIO("IW4x", "https://iw4xcachep26muba.onion.to/iw4/changelog.txt").SetTimeout(5000)->Get().data()); - - std::string data = Utils::WebIO("IW4x", "https://iw4xcachep26muba.onion.to/iw4/motd.txt").SetTimeout(5000)->Get(); - - if (!data.empty()) - { - Localization::Set("MPUI_MOTD_TEXT", data.data()); - } - - // TODO: Implement update checks here! - }); - } - - News::~News() - { - if (News::Thread.joinable()) - { - News::Thread.join(); - } - } -} +#include "STDInclude.hpp" + +#define NEWS_MOTD_DEFUALT "Welcome to IW4x Multiplayer!" + +namespace Components +{ + std::thread News::Thread; + + bool News::UnitTest() + { + bool result = true; + + if (News::Thread.joinable()) + { + Logger::Print("Awaiting thread termination...\n"); + News::Thread.join(); + + if (!strlen(Localization::Get("MPUI_CHANGELOG_TEXT")) || Localization::Get("MPUI_CHANGELOG_TEXT") == "Loading..."s) + { + Logger::Print("Failed to fetch changelog!\n"); + result = false; + } + else + { + Logger::Print("Successfully fetched changelog.\n"); + } + + if (!strcmp(Localization::Get("MPUI_MOTD_TEXT"), NEWS_MOTD_DEFUALT)) + { + Logger::Print("Failed to fetch motd!\n"); + result = false; + } + else + { + Logger::Print("Successfully fetched motd.\n"); + } + } + + return result; + } + + News::News() + { + Localization::Set("MPUI_CHANGELOG_TEXT", "Loading..."); + Localization::Set("MPUI_MOTD_TEXT", NEWS_MOTD_DEFUALT); + + News::Thread = std::thread([] () + { + Localization::Set("MPUI_CHANGELOG_TEXT", Utils::WebIO("IW4x", "https://iw4xcachep26muba.onion.to/iw4/changelog.txt").SetTimeout(5000)->Get()); + + std::string data = Utils::WebIO("IW4x", "https://iw4xcachep26muba.onion.to/iw4/motd.txt").SetTimeout(5000)->Get(); + + if (!data.empty()) + { + Localization::Set("MPUI_MOTD_TEXT", data); + } + + // TODO: Implement update checks here! + }); + } + + News::~News() + { + if (News::Thread.joinable()) + { + News::Thread.join(); + } + } +} diff --git a/src/Components/Modules/Node.cpp b/src/Components/Modules/Node.cpp index 442134fd..23ade51d 100644 --- a/src/Components/Modules/Node.cpp +++ b/src/Components/Modules/Node.cpp @@ -1,896 +1,896 @@ -#include "STDInclude.hpp" - -namespace Components -{ - Utils::Cryptography::ECC::Key Node::SignatureKey; - std::vector Node::Nodes; - std::vector Node::Sessions; - - void Node::LoadNodePreset() - { - FileSystem::File defaultNodes("nodes_default.dat"); - if (!defaultNodes.Exists()) return; - - auto buffer = defaultNodes.GetBuffer(); - Utils::Replace(buffer, "\r", ""); - - auto nodes = Utils::Explode(buffer, '\n'); - for (auto node : nodes) - { - if (!node.empty()) - { - Node::AddNode(node); - } - } - } - - void Node::LoadNodes() - { - Proto::Node::List list; - std::string nodes = Utils::ReadFile("players/nodes.dat"); - if (nodes.empty() || !list.ParseFromString(nodes)) return; - - for (int i = 0; i < list.address_size(); ++i) - { - Node::AddNode(list.address(i)); - } - } - void Node::StoreNodes(bool force) - { - if (Dedicated::IsDedicated() && Dvar::Var("sv_lanOnly").Get()) return; - - static int lastStorage = 0; - - // Don't store nodes if the delta is too small and were not forcing it - if (((Game::Sys_Milliseconds() - lastStorage) < NODE_STORE_INTERVAL && !force) || !Node::GetValidNodeCount()) return; - lastStorage = Game::Sys_Milliseconds(); - - Proto::Node::List list; - - // This is obsolete when storing to file. - // However, defining another proto message due to this would be redundant. - //list.set_is_dedi(Dedicated::IsDedicated()); - - for (auto node : Node::Nodes) - { - if (node.state == Node::STATE_VALID && node.registered) - { - node.address.Serialize(list.add_address()); - } - } - - CreateDirectoryW(L"players", NULL); - Utils::WriteFile("players/nodes.dat", list.SerializeAsString()); - } - - Node::NodeEntry* Node::FindNode(Network::Address address) - { - for (auto i = Node::Nodes.begin(); i != Node::Nodes.end(); ++i) - { - if (i->address == address) - { - return &(*i); - } - } - - return nullptr; - } - Node::ClientSession* Node::FindSession(Network::Address address) - { - for (auto i = Node::Sessions.begin(); i != Node::Sessions.end(); ++i) - { - if (i->address == address) - { - return &(*i); - } - } - - return nullptr; - } - - unsigned int Node::GetValidNodeCount() - { - unsigned int count = 0; - - for (auto node : Node::Nodes) - { - if (node.state == Node::STATE_VALID) - { - ++count; - } - } - - return count; - } - - void Node::AddNode(Network::Address address) - { -#ifdef DEBUG - if (!address.IsValid() || address.IsSelf()) return; -#else - if (!address.IsValid() || address.IsLocal() || address.IsSelf()) return; -#endif - - Node::NodeEntry* existingEntry = Node::FindNode(address); - if (existingEntry) - { - existingEntry->lastHeard = Game::Sys_Milliseconds(); - } - else - { - Node::NodeEntry entry; - - entry.lastHeard = Game::Sys_Milliseconds(); - entry.lastTime = 0; - entry.lastListQuery = 0; - entry.registered = false; - entry.state = Node::STATE_UNKNOWN; - entry.address = address; - entry.challenge.clear(); - - Node::Nodes.push_back(entry); - -#ifdef DEBUG - Logger::Print("Adding node %s...\n", address.GetCString()); -#endif - } - } - - void Node::SendNodeList(Network::Address address) - { - if (address.IsSelf()) return; - - Proto::Node::List list; - list.set_is_dedi(Dedicated::IsDedicated()); - list.set_protocol(PROTOCOL); - list.set_version(NODE_VERSION); - - for (auto node : Node::Nodes) - { - if (node.state == Node::STATE_VALID && node.registered) - { - node.address.Serialize(list.add_address()); - } - - if (list.address_size() >= NODE_PACKET_LIMIT) - { - Network::SendCommand(address, "nodeListResponse", list.SerializeAsString()); - list.clear_address(); - } - } - - // Even if we send an empty list, we have to tell the client about our dedi-status - // If the amount of servers we have modulo the NODE_PACKET_LIMIT equals 0, we will send this request without any servers, so it's obsolete, but meh... - Network::SendCommand(address, "nodeListResponse", list.SerializeAsString()); - } - - void Node::DeleteInvalidSessions() - { - for (auto i = Node::Sessions.begin(); i != Node::Sessions.end();) - { - if (i->lastTime <= 0 || (Game::Sys_Milliseconds() - i->lastTime) > SESSION_TIMEOUT) - { - i = Node::Sessions.erase(i); - } - else - { - ++i; - } - } - } - - void Node::DeleteInvalidNodes() - { - std::vector cleanNodes; - - for (auto node : Node::Nodes) - { - if (node.state == Node::STATE_INVALID && (Game::Sys_Milliseconds() - node.lastHeard) > NODE_INVALID_DELETE) - { - Logger::Print("Removing invalid node %s\n", node.address.GetCString()); - } - else - { - cleanNodes.push_back(node); - } - } - - if (cleanNodes.size() != Node::Nodes.size()) - { - //Node::Nodes.clear(); - //Utils::Merge(&Node::Nodes, cleanNodes); - Node::Nodes = cleanNodes; - } - } - - void Node::SyncNodeList() - { - for (auto& node : Node::Nodes) - { - if (node.state == Node::STATE_VALID && node.registered) - { - node.state = Node::STATE_UNKNOWN; - node.registered = false; - } - } - } - - void Node::PerformRegistration(Network::Address address) - { - Node::NodeEntry* entry = Node::FindNode(address); - if (!entry) return; - - entry->lastTime = Game::Sys_Milliseconds(); - - if (Dedicated::IsDedicated()) - { - entry->challenge = Utils::VA("%X", Utils::Cryptography::Rand::GenerateInt()); - - Proto::Node::Packet packet; - packet.set_challenge(entry->challenge); - -#ifdef DEBUG - Logger::Print("Sending registration request to %s\n", entry->address.GetCString()); -#endif - Network::SendCommand(entry->address, "nodeRegisterRequest", packet.SerializeAsString()); - } - else - { -#ifdef DEBUG - Logger::Print("Sending session request to %s\n", entry->address.GetCString()); -#endif - Network::SendCommand(entry->address, "sessionRequest"); - } - } - - void Node::FrameHandler() - { - if (Dedicated::IsDedicated() && Dvar::Var("sv_lanOnly").Get()) return; - - // Frame limit - static int lastFrame = 0; - if ((Game::Sys_Milliseconds() - lastFrame) < (1000 / NODE_FRAME_LOCK) || Game::Sys_Milliseconds() < 5000) return; - lastFrame = Game::Sys_Milliseconds(); - - int registerCount = 0; - int listQueryCount = 0; - - for (auto &node : Node::Nodes) - { - // TODO: Decide how to handle nodes that were already registered, but timed out re-registering. - if (node.state == STATE_NEGOTIATING && (Game::Sys_Milliseconds() - node.lastTime) > (NODE_QUERY_TIMEOUT)) - { - node.registered = false; // Definitely unregister here! - node.state = Node::STATE_INVALID; - node.lastHeard = Game::Sys_Milliseconds(); - node.lastTime = Game::Sys_Milliseconds(); - - Logger::Print("Node negotiation timed out. Invalidating %s\n", node.address.GetCString()); - } - - if (registerCount < NODE_FRAME_QUERY_LIMIT) - { - // Register when unregistered and in UNKNOWN state (I doubt it's possible to be unregistered and in VALID state) - if (!node.registered && (node.state != Node::STATE_NEGOTIATING && node.state != Node::STATE_INVALID)) - { - ++registerCount; - node.state = Node::STATE_NEGOTIATING; - Node::PerformRegistration(node.address); - } - // Requery invalid nodes within the NODE_QUERY_INTERVAL - // This is required, as a node might crash, which causes it to be invalid. - // If it's restarted though, we wouldn't query it again. - - // But wouldn't it send a registration request to us? - // Not sure if the code below is necessary... - // Well, it might be possible that this node doesn't know use anymore. Anyways, just keep that code here... - - // Nvm, this is required for clients, as nodes don't send registration requests to clients. - else if (node.state == STATE_INVALID && (Game::Sys_Milliseconds() - node.lastTime) > NODE_QUERY_INTERVAL) - { - ++registerCount; - Node::PerformRegistration(node.address); - } - } - - if (listQueryCount < NODE_FRAME_QUERY_LIMIT) - { - if (node.registered && node.state == Node::STATE_VALID && (!node.lastListQuery || (Game::Sys_Milliseconds() - node.lastListQuery) > NODE_QUERY_INTERVAL)) - { - ++listQueryCount; - node.state = Node::STATE_NEGOTIATING; - node.lastTime = Game::Sys_Milliseconds(); - node.lastListQuery = Game::Sys_Milliseconds(); - - if (Dedicated::IsDedicated()) - { - Network::SendCommand(node.address, "nodeListRequest"); - } - else - { - Network::SendCommand(node.address, "sessionRequest"); - } - } - } - } - - static int lastCheck = 0; - if ((Game::Sys_Milliseconds() - lastCheck) < 1000) return; - lastCheck = Game::Sys_Milliseconds(); - - Node::DeleteInvalidSessions(); - Node::DeleteInvalidNodes(); - Node::StoreNodes(false); - } - - const char* Node::GetStateName(EntryState state) - { - switch (state) - { - case Node::STATE_UNKNOWN: - return "Unknown"; - - case Node::STATE_NEGOTIATING: - return "Negotiating"; - - case Node::STATE_INVALID: - return "Invalid"; - - case Node::STATE_VALID: - return "Valid"; - } - - return ""; - } - - Node::Node() - { - Node::Nodes.clear(); - - // ZoneBuilder doesn't require node stuff - if (ZoneBuilder::IsEnabled()) return; - - // Generate our ECDSA key - Node::SignatureKey = Utils::Cryptography::ECC::GenerateKey(512); - - // Load stored nodes - Dvar::OnInit([] () - { - Node::LoadNodePreset(); - Node::LoadNodes(); - }); - - // Send deadline when shutting down - if (Dedicated::IsDedicated()) - { - QuickPatch::OnShutdown([] () - { - if (Dvar::Var("sv_lanOnly").Get()) return; - - std::string challenge = Utils::VA("%X", Utils::Cryptography::Rand::GenerateInt()); - - Proto::Node::Packet packet; - packet.set_challenge(challenge); - packet.set_signature(Utils::Cryptography::ECC::SignMessage(Node::SignatureKey, challenge)); - - for (auto node : Node::Nodes) - { - Network::SendCommand(node.address, "nodeDeregister", packet.SerializeAsString()); - } - }); - - // This is the handler that accepts registration requests from other nodes - // If you want to get accepted as node, you have to send a request to this handler - Network::Handle("nodeRegisterRequest", [] (Network::Address address, std::string data) - { - if (Dvar::Var("sv_lanOnly").Get()) return; - - Node::NodeEntry* entry = Node::FindNode(address); - - // Create a new entry, if we don't already know it - if (!entry) - { - Node::AddNode(address); - entry = Node::FindNode(address); - if (!entry) return; - } - -#ifdef DEBUG - Logger::Print("Received registration request from %s\n", address.GetCString()); -#endif - - Proto::Node::Packet packet; - if (!packet.ParseFromString(data)) return; - if (packet.challenge().empty()) return; - - std::string signature = Utils::Cryptography::ECC::SignMessage(Node::SignatureKey, packet.challenge()); - std::string challenge = Utils::VA("%X", Utils::Cryptography::Rand::GenerateInt()); - - // The challenge this client sent is exactly the challenge we stored for this client - // That means this is us, so we're going to ignore us :P - if (packet.challenge() == entry->challenge) - { - entry->lastHeard = Game::Sys_Milliseconds(); - entry->lastTime = Game::Sys_Milliseconds(); - entry->registered = false; - entry->state = Node::STATE_INVALID; - return; - } - - packet.Clear(); - packet.set_challenge(challenge); - packet.set_signature(signature); - packet.set_publickey(Node::SignatureKey.GetPublicKey()); - - entry->lastTime = Game::Sys_Milliseconds(); - entry->challenge = challenge; - entry->state = Node::STATE_NEGOTIATING; - - Network::SendCommand(address, "nodeRegisterSynchronize", packet.SerializeAsString()); - }); - - Network::Handle("nodeRegisterSynchronize", [] (Network::Address address, std::string data) - { - if (Dvar::Var("sv_lanOnly").Get()) return; - - Node::NodeEntry* entry = Node::FindNode(address); - if (!entry || entry->state != Node::STATE_NEGOTIATING) return; - -#ifdef DEBUG - Logger::Print("Received synchronization data for registration from %s!\n", address.GetCString()); -#endif - - Proto::Node::Packet packet; - if (!packet.ParseFromString(data)) return; - if (packet.challenge().empty()) return; - if (packet.publickey().empty()) return; - if (packet.signature().empty()) return; - - std::string challenge = packet.challenge(); - std::string publicKey = packet.publickey(); - std::string signature = packet.signature(); - - // Verify signature - entry->publicKey.Set(publicKey); - if (!Utils::Cryptography::ECC::VerifyMessage(entry->publicKey, entry->challenge, signature)) - { - Logger::Print("Signature from %s for challenge '%s' is invalid!\n", address.GetCString(), entry->challenge.data()); - return; - } - - // Mark as registered - entry->lastTime = Game::Sys_Milliseconds(); - entry->state = Node::STATE_VALID; - entry->registered = true; - -#ifdef DEBUG - Logger::Print("Signature from %s for challenge '%s' is valid!\n", address.GetCString(), entry->challenge.data()); - Logger::Print("Node %s registered\n", address.GetCString()); -#endif - - // Build response - publicKey = Node::SignatureKey.GetPublicKey(); - signature = Utils::Cryptography::ECC::SignMessage(Node::SignatureKey, challenge); - - packet.Clear(); - packet.set_signature(signature); - packet.set_publickey(publicKey); - - Network::SendCommand(address, "nodeRegisterAcknowledge", packet.SerializeAsString()); - }); - - Network::Handle("nodeRegisterAcknowledge", [] (Network::Address address, std::string data) - { - if (Dvar::Var("sv_lanOnly").Get()) return; - - // Ignore requests from nodes we don't know - Node::NodeEntry* entry = Node::FindNode(address); - if (!entry || entry->state != Node::STATE_NEGOTIATING) return; - -#ifdef DEBUG - Logger::Print("Received acknowledgment from %s\n", address.GetCString()); -#endif - - Proto::Node::Packet packet; - if (!packet.ParseFromString(data)) return; - if (packet.signature().empty()) return; - if (packet.publickey().empty()) return; - - std::string publicKey = packet.publickey(); - std::string signature = packet.signature(); - - entry->publicKey.Set(publicKey); - - if (Utils::Cryptography::ECC::VerifyMessage(entry->publicKey, entry->challenge, signature)) - { - entry->lastTime = Game::Sys_Milliseconds(); - entry->state = Node::STATE_VALID; - entry->registered = true; - -#ifdef DEBUG - Logger::Print("Signature from %s for challenge '%s' is valid!\n", address.GetCString(), entry->challenge.data()); - Logger::Print("Node %s registered\n", address.GetCString()); -#endif - } - else - { - Logger::Print("Signature from %s for challenge '%s' is invalid!\n", address.GetCString(), entry->challenge.data()); - } - }); - - Network::Handle("nodeListRequest", [] (Network::Address address, std::string data) - { - if (Dvar::Var("sv_lanOnly").Get()) return; - - // Check if this is a registered node - bool allowed = false; - Node::NodeEntry* entry = Node::FindNode(address); - if (entry && entry->registered) - { - entry->lastTime = Game::Sys_Milliseconds(); - allowed = true; - } - - // Check if there is any open session - if (!allowed) - { - Node::ClientSession* session = Node::FindSession(address); - if (session) - { - session->lastTime = Game::Sys_Milliseconds(); - allowed = session->valid; - } - } - - if (allowed) - { - Node::SendNodeList(address); - } - else - { - // Unallowed connection - Logger::Print("Node list requested by %s, but no valid session was present!\n", address.GetCString()); - Network::SendCommand(address, "nodeListError"); - } - }); - - Network::Handle("nodeDeregister", [] (Network::Address address, std::string data) - { - if (Dvar::Var("sv_lanOnly").Get()) return; - - Node::NodeEntry* entry = Node::FindNode(address); - if (!entry || !entry->registered) return; - - Proto::Node::Packet packet; - if (!packet.ParseFromString(data)) return; - if (packet.challenge().empty()) return; - if (packet.signature().empty()) return; - - std::string challenge = packet.challenge(); - std::string signature = packet.signature(); - - if (Utils::Cryptography::ECC::VerifyMessage(entry->publicKey, challenge, signature)) - { - entry->lastHeard = Game::Sys_Milliseconds(); - entry->lastTime = Game::Sys_Milliseconds(); - entry->registered = false; - entry->state = Node::STATE_INVALID; - -#ifdef DEBUG - Logger::Print("Node %s unregistered\n", address.GetCString()); -#endif - } - else - { - Logger::Print("Node %s tried to unregister using an invalid signature!\n", address.GetCString()); - } - }); - - Network::Handle("sessionRequest", [] (Network::Address address, std::string data) - { - if (Dvar::Var("sv_lanOnly").Get()) return; - - // Search an active session, if we haven't found one, register a template - if (!Node::FindSession(address)) - { - Node::ClientSession templateSession; - templateSession.address = address; - Node::Sessions.push_back(templateSession); - } - - // Search our target session (this should not fail!) - Node::ClientSession* session = Node::FindSession(address); - if (!session) return; // Registering template session failed, odd... - -#ifdef DEBUG - Logger::Print("Client %s is requesting a new session\n", address.GetCString()); -#endif - - // Initialize session data - session->challenge = Utils::VA("%X", Utils::Cryptography::Rand::GenerateInt()); - session->lastTime = Game::Sys_Milliseconds(); - session->valid = false; - - Network::SendCommand(address, "sessionInitialize", session->challenge); - }); - - Network::Handle("sessionSynchronize", [] (Network::Address address, std::string data) - { - if (Dvar::Var("sv_lanOnly").Get()) return; - - // Return if we don't have a session for this address - Node::ClientSession* session = Node::FindSession(address); - if (!session || session->valid) return; - - if (session->challenge == data) - { -#ifdef DEBUG - Logger::Print("Session for %s validated.\n", address.GetCString()); -#endif - session->valid = true; - Network::SendCommand(address, "sessionAcknowledge"); - } - else - { - session->lastTime = -1; - Logger::Print("Challenge mismatch. Validating session for %s failed.\n", address.GetCString()); - } - }); - } - else - { - Network::Handle("sessionInitialize", [] (Network::Address address, std::string data) - { - Node::NodeEntry* entry = Node::FindNode(address); - if (!entry) return; - -#ifdef DEBUG - Logger::Print("Session initialization received from %s. Synchronizing...\n", address.GetCString()); -#endif - - entry->lastTime = Game::Sys_Milliseconds(); - Network::SendCommand(address, "sessionSynchronize", data); - }); - - Network::Handle("sessionAcknowledge", [] (Network::Address address, std::string data) - { - Node::NodeEntry* entry = Node::FindNode(address); - if (!entry) return; - - entry->state = Node::STATE_VALID; - entry->registered = true; - entry->lastTime = Game::Sys_Milliseconds(); - -#ifdef DEBUG - Logger::Print("Session acknowledged by %s, synchronizing node list...\n", address.GetCString()); -#endif - Network::SendCommand(address, "nodeListRequest"); - Node::SendNodeList(address); - }); - } - - Network::Handle("nodeListResponse", [] (Network::Address address, std::string data) - { - Proto::Node::List list; - - if (data.empty() || !list.ParseFromString(data)) - { - Logger::Print("Received invalid node list from %s!\n", address.GetCString()); - return; - } - - Node::NodeEntry* entry = Node::FindNode(address); - if (entry) - { - if (entry->registered) - { - Logger::Print("Received valid node list with %i entries from %s\n", list.address_size(), address.GetCString()); - - entry->isDedi = list.is_dedi(); - entry->protocol = list.protocol(); - entry->version = list.version(); - entry->state = Node::STATE_VALID; - entry->lastTime = Game::Sys_Milliseconds(); - - if (!Dedicated::IsDedicated() && entry->isDedi && ServerList::IsOnlineList() && entry->protocol == PROTOCOL) - { - ServerList::InsertRequest(entry->address, true); - } - - for (int i = 0; i < list.address_size(); ++i) - { - Network::Address _addr(list.address(i)); - - // Version 0 sends port in the wrong byte order! - if (entry->version == 0) - { - _addr.SetPort(ntohs(_addr.GetPort())); - } - -// if (!Node::FindNode(_addr) && _addr.GetPort() >= 1024 && _addr.GetPort() - 20 < 1024) -// { -// std::string a1 = _addr.GetString(); -// std::string a2 = address.GetString(); -// Logger::Print("Received weird address %s from %s\n", a1.data(), a2.data()); -// } - - Node::AddNode(_addr); - } - } - } - else - { - //Node::AddNode(address); - - Node::ClientSession* session = Node::FindSession(address); - if (session && session->valid) - { - session->lastTime = Game::Sys_Milliseconds(); - - for (int i = 0; i < list.address_size(); ++i) - { - Node::AddNode(list.address(i)); - } - } - } - }); - - // If we receive that response, our request was not permitted - // So we either have to register as node, or register a remote session - Network::Handle("nodeListError", [] (Network::Address address, std::string data) - { - if (Dedicated::IsDedicated()) - { - Node::NodeEntry* entry = Node::FindNode(address); - if (entry) - { - // Set to unregistered to perform registration later on - entry->lastTime = Game::Sys_Milliseconds(); - entry->registered = false; - entry->state = Node::STATE_UNKNOWN; - } - else - { - // Add as new entry to perform registration - Node::AddNode(address); - } - } - else - { - // TODO: Implement client handshake stuff - // Nvm, clients can simply ignore that i guess - } - }); - - Command::Add("listnodes", [] (Command::Params params) - { - Logger::Print("Nodes: %d (%d)\n", Node::Nodes.size(), Node::GetValidNodeCount()); - - for (auto node : Node::Nodes) - { - Logger::Print("%s\t(%s)\n", node.address.GetCString(), Node::GetStateName(node.state)); - } - }); - - Command::Add("addnode", [] (Command::Params params) - { - if (params.Length() < 2) return; - - Network::Address address(params[1]); - Node::AddNode(address); - - Node::NodeEntry* entry = Node::FindNode(address); - if (entry) - { - entry->state = Node::STATE_UNKNOWN; - entry->registered = false; - } - }); - - Command::Add("syncnodes", [] (Command::Params params) - { - Logger::Print("Re-Synchronizing nodes...\n"); - - for (auto& node : Node::Nodes) - { - node.state = Node::STATE_UNKNOWN; - node.registered = false; - node.lastTime = 0; - node.lastListQuery = 0; - } - }); - - // Install frame handlers - QuickPatch::OnFrame(Node::FrameHandler); - } - - Node::~Node() - { - Node::SignatureKey.Free(); - - Node::StoreNodes(true); - Node::Nodes.clear(); - Node::Sessions.clear(); - } - - bool Node::UnitTest() - { - printf("Testing ECDSA key..."); - - if (!Node::SignatureKey.IsValid()) - { - printf("Error\n"); - printf("ECDSA key seems invalid!\n"); - return false; - } - - printf("Success\n"); - printf("Testing 10 valid signatures..."); - - for (int i = 0; i < 10; ++i) - { - std::string message = Utils::VA("%X", Utils::Cryptography::Rand::GenerateInt()); - std::string signature = Utils::Cryptography::ECC::SignMessage(Node::SignatureKey, message); - - if (!Utils::Cryptography::ECC::VerifyMessage(Node::SignatureKey, message, signature)) - { - printf("Error\n"); - printf("Signature for '%s' (%d) was invalid!\n", message.data(), i); - return false; - } - } - - printf("Success\n"); - printf("Testing 10 invalid signatures..."); - - for (int i = 0; i < 10; ++i) - { - std::string message = Utils::VA("%X", Utils::Cryptography::Rand::GenerateInt()); - std::string signature = Utils::Cryptography::ECC::SignMessage(Node::SignatureKey, message); - - // Invalidate the message... - ++message[Utils::Cryptography::Rand::GenerateInt() % message.size()]; - - if (Utils::Cryptography::ECC::VerifyMessage(Node::SignatureKey, message, signature)) - { - printf("Error\n"); - printf("Signature for '%s' (%d) was valid? What the fuck? That is absolutely impossible...\n", message.data(), i); - return false; - } - } - - printf("Success\n"); - printf("Testing ECDSA key import..."); - - std::string pubKey = Node::SignatureKey.GetPublicKey(); - std::string message = Utils::VA("%X", Utils::Cryptography::Rand::GenerateInt()); - std::string signature = Utils::Cryptography::ECC::SignMessage(Node::SignatureKey, message); - - Utils::Cryptography::ECC::Key testKey; - testKey.Set(pubKey); - - if (!Utils::Cryptography::ECC::VerifyMessage(Node::SignatureKey, message, signature)) - { - printf("Error\n"); - printf("Verifying signature for message '%s' using imported keys failed!\n", message.data()); - return false; - } - - printf("Success\n"); - - uint32_t randIntCount = 4'000'000; - printf("Generating %d random integers...", randIntCount); - - auto startTime = std::chrono::high_resolution_clock::now(); - - for (uint32_t i = 0; i < randIntCount; ++i) - { - Utils::Cryptography::Rand::GenerateInt(); - } - - auto duration = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - startTime).count(); - Logger::Print("took %llims\n", duration); - - return true; - } -} +#include "STDInclude.hpp" + +namespace Components +{ + Utils::Cryptography::ECC::Key Node::SignatureKey; + std::vector Node::Nodes; + std::vector Node::Sessions; + + void Node::LoadNodePreset() + { + FileSystem::File defaultNodes("nodes_default.dat"); + if (!defaultNodes.Exists()) return; + + auto buffer = defaultNodes.GetBuffer(); + Utils::String::Replace(buffer, "\r", ""); + + auto nodes = Utils::String::Explode(buffer, '\n'); + for (auto node : nodes) + { + if (!node.empty()) + { + Node::AddNode(node); + } + } + } + + void Node::LoadNodes() + { + Proto::Node::List list; + std::string nodes = Utils::IO::ReadFile("players/nodes.dat"); + if (nodes.empty() || !list.ParseFromString(nodes)) return; + + for (int i = 0; i < list.address_size(); ++i) + { + Node::AddNode(list.address(i)); + } + } + void Node::StoreNodes(bool force) + { + if (Dedicated::IsDedicated() && Dvar::Var("sv_lanOnly").Get()) return; + + static int lastStorage = 0; + + // Don't store nodes if the delta is too small and were not forcing it + if (((Game::Sys_Milliseconds() - lastStorage) < NODE_STORE_INTERVAL && !force) || !Node::GetValidNodeCount()) return; + lastStorage = Game::Sys_Milliseconds(); + + Proto::Node::List list; + + // This is obsolete when storing to file. + // However, defining another proto message due to this would be redundant. + //list.set_is_dedi(Dedicated::IsDedicated()); + + for (auto node : Node::Nodes) + { + if (node.state == Node::STATE_VALID && node.registered) + { + node.address.Serialize(list.add_address()); + } + } + + CreateDirectoryW(L"players", NULL); + Utils::IO::WriteFile("players/nodes.dat", list.SerializeAsString()); + } + + Node::NodeEntry* Node::FindNode(Network::Address address) + { + for (auto i = Node::Nodes.begin(); i != Node::Nodes.end(); ++i) + { + if (i->address == address) + { + return &(*i); + } + } + + return nullptr; + } + Node::ClientSession* Node::FindSession(Network::Address address) + { + for (auto i = Node::Sessions.begin(); i != Node::Sessions.end(); ++i) + { + if (i->address == address) + { + return &(*i); + } + } + + return nullptr; + } + + unsigned int Node::GetValidNodeCount() + { + unsigned int count = 0; + + for (auto node : Node::Nodes) + { + if (node.state == Node::STATE_VALID) + { + ++count; + } + } + + return count; + } + + void Node::AddNode(Network::Address address) + { +#ifdef DEBUG + if (!address.IsValid() || address.IsSelf()) return; +#else + if (!address.IsValid() || address.IsLocal() || address.IsSelf()) return; +#endif + + Node::NodeEntry* existingEntry = Node::FindNode(address); + if (existingEntry) + { + existingEntry->lastHeard = Game::Sys_Milliseconds(); + } + else + { + Node::NodeEntry entry; + + entry.lastHeard = Game::Sys_Milliseconds(); + entry.lastTime = 0; + entry.lastListQuery = 0; + entry.registered = false; + entry.state = Node::STATE_UNKNOWN; + entry.address = address; + entry.challenge.clear(); + + Node::Nodes.push_back(entry); + +#ifdef DEBUG + Logger::Print("Adding node %s...\n", address.GetCString()); +#endif + } + } + + void Node::SendNodeList(Network::Address address) + { + if (address.IsSelf()) return; + + Proto::Node::List list; + list.set_is_dedi(Dedicated::IsDedicated()); + list.set_protocol(PROTOCOL); + list.set_version(NODE_VERSION); + + for (auto node : Node::Nodes) + { + if (node.state == Node::STATE_VALID && node.registered) + { + node.address.Serialize(list.add_address()); + } + + if (list.address_size() >= NODE_PACKET_LIMIT) + { + Network::SendCommand(address, "nodeListResponse", list.SerializeAsString()); + list.clear_address(); + } + } + + // Even if we send an empty list, we have to tell the client about our dedi-status + // If the amount of servers we have modulo the NODE_PACKET_LIMIT equals 0, we will send this request without any servers, so it's obsolete, but meh... + Network::SendCommand(address, "nodeListResponse", list.SerializeAsString()); + } + + void Node::DeleteInvalidSessions() + { + for (auto i = Node::Sessions.begin(); i != Node::Sessions.end();) + { + if (i->lastTime <= 0 || (Game::Sys_Milliseconds() - i->lastTime) > SESSION_TIMEOUT) + { + i = Node::Sessions.erase(i); + } + else + { + ++i; + } + } + } + + void Node::DeleteInvalidNodes() + { + std::vector cleanNodes; + + for (auto node : Node::Nodes) + { + if (node.state == Node::STATE_INVALID && (Game::Sys_Milliseconds() - node.lastHeard) > NODE_INVALID_DELETE) + { + Logger::Print("Removing invalid node %s\n", node.address.GetCString()); + } + else + { + cleanNodes.push_back(node); + } + } + + if (cleanNodes.size() != Node::Nodes.size()) + { + //Node::Nodes.clear(); + //Utils::Merge(&Node::Nodes, cleanNodes); + Node::Nodes = cleanNodes; + } + } + + void Node::SyncNodeList() + { + for (auto& node : Node::Nodes) + { + if (node.state == Node::STATE_VALID && node.registered) + { + node.state = Node::STATE_UNKNOWN; + node.registered = false; + } + } + } + + void Node::PerformRegistration(Network::Address address) + { + Node::NodeEntry* entry = Node::FindNode(address); + if (!entry) return; + + entry->lastTime = Game::Sys_Milliseconds(); + + if (Dedicated::IsDedicated()) + { + entry->challenge = fmt::sprintf("%X", Utils::Cryptography::Rand::GenerateInt()); + + Proto::Node::Packet packet; + packet.set_challenge(entry->challenge); + +#ifdef DEBUG + Logger::Print("Sending registration request to %s\n", entry->address.GetCString()); +#endif + Network::SendCommand(entry->address, "nodeRegisterRequest", packet.SerializeAsString()); + } + else + { +#ifdef DEBUG + Logger::Print("Sending session request to %s\n", entry->address.GetCString()); +#endif + Network::SendCommand(entry->address, "sessionRequest"); + } + } + + void Node::FrameHandler() + { + if (Dedicated::IsDedicated() && Dvar::Var("sv_lanOnly").Get()) return; + + // Frame limit + static int lastFrame = 0; + if ((Game::Sys_Milliseconds() - lastFrame) < (1000 / NODE_FRAME_LOCK) || Game::Sys_Milliseconds() < 5000) return; + lastFrame = Game::Sys_Milliseconds(); + + int registerCount = 0; + int listQueryCount = 0; + + for (auto &node : Node::Nodes) + { + // TODO: Decide how to handle nodes that were already registered, but timed out re-registering. + if (node.state == STATE_NEGOTIATING && (Game::Sys_Milliseconds() - node.lastTime) > (NODE_QUERY_TIMEOUT)) + { + node.registered = false; // Definitely unregister here! + node.state = Node::STATE_INVALID; + node.lastHeard = Game::Sys_Milliseconds(); + node.lastTime = Game::Sys_Milliseconds(); + + Logger::Print("Node negotiation timed out. Invalidating %s\n", node.address.GetCString()); + } + + if (registerCount < NODE_FRAME_QUERY_LIMIT) + { + // Register when unregistered and in UNKNOWN state (I doubt it's possible to be unregistered and in VALID state) + if (!node.registered && (node.state != Node::STATE_NEGOTIATING && node.state != Node::STATE_INVALID)) + { + ++registerCount; + node.state = Node::STATE_NEGOTIATING; + Node::PerformRegistration(node.address); + } + // Requery invalid nodes within the NODE_QUERY_INTERVAL + // This is required, as a node might crash, which causes it to be invalid. + // If it's restarted though, we wouldn't query it again. + + // But wouldn't it send a registration request to us? + // Not sure if the code below is necessary... + // Well, it might be possible that this node doesn't know use anymore. Anyways, just keep that code here... + + // Nvm, this is required for clients, as nodes don't send registration requests to clients. + else if (node.state == STATE_INVALID && (Game::Sys_Milliseconds() - node.lastTime) > NODE_QUERY_INTERVAL) + { + ++registerCount; + Node::PerformRegistration(node.address); + } + } + + if (listQueryCount < NODE_FRAME_QUERY_LIMIT) + { + if (node.registered && node.state == Node::STATE_VALID && (!node.lastListQuery || (Game::Sys_Milliseconds() - node.lastListQuery) > NODE_QUERY_INTERVAL)) + { + ++listQueryCount; + node.state = Node::STATE_NEGOTIATING; + node.lastTime = Game::Sys_Milliseconds(); + node.lastListQuery = Game::Sys_Milliseconds(); + + if (Dedicated::IsDedicated()) + { + Network::SendCommand(node.address, "nodeListRequest"); + } + else + { + Network::SendCommand(node.address, "sessionRequest"); + } + } + } + } + + static int lastCheck = 0; + if ((Game::Sys_Milliseconds() - lastCheck) < 1000) return; + lastCheck = Game::Sys_Milliseconds(); + + Node::DeleteInvalidSessions(); + Node::DeleteInvalidNodes(); + Node::StoreNodes(false); + } + + const char* Node::GetStateName(EntryState state) + { + switch (state) + { + case Node::STATE_UNKNOWN: + return "Unknown"; + + case Node::STATE_NEGOTIATING: + return "Negotiating"; + + case Node::STATE_INVALID: + return "Invalid"; + + case Node::STATE_VALID: + return "Valid"; + } + + return ""; + } + + Node::Node() + { + Node::Nodes.clear(); + + // ZoneBuilder doesn't require node stuff + if (ZoneBuilder::IsEnabled()) return; + + // Generate our ECDSA key + Node::SignatureKey = Utils::Cryptography::ECC::GenerateKey(512); + + // Load stored nodes + Dvar::OnInit([] () + { + Node::LoadNodePreset(); + Node::LoadNodes(); + }); + + // Send deadline when shutting down + if (Dedicated::IsDedicated()) + { + QuickPatch::OnShutdown([] () + { + if (Dvar::Var("sv_lanOnly").Get()) return; + + std::string challenge = fmt::sprintf("%X", Utils::Cryptography::Rand::GenerateInt()); + + Proto::Node::Packet packet; + packet.set_challenge(challenge); + packet.set_signature(Utils::Cryptography::ECC::SignMessage(Node::SignatureKey, challenge)); + + for (auto node : Node::Nodes) + { + Network::SendCommand(node.address, "nodeDeregister", packet.SerializeAsString()); + } + }); + + // This is the handler that accepts registration requests from other nodes + // If you want to get accepted as node, you have to send a request to this handler + Network::Handle("nodeRegisterRequest", [] (Network::Address address, std::string data) + { + if (Dvar::Var("sv_lanOnly").Get()) return; + + Node::NodeEntry* entry = Node::FindNode(address); + + // Create a new entry, if we don't already know it + if (!entry) + { + Node::AddNode(address); + entry = Node::FindNode(address); + if (!entry) return; + } + +#ifdef DEBUG + Logger::Print("Received registration request from %s\n", address.GetCString()); +#endif + + Proto::Node::Packet packet; + if (!packet.ParseFromString(data)) return; + if (packet.challenge().empty()) return; + + std::string signature = Utils::Cryptography::ECC::SignMessage(Node::SignatureKey, packet.challenge()); + std::string challenge = fmt::sprintf("%X", Utils::Cryptography::Rand::GenerateInt()); + + // The challenge this client sent is exactly the challenge we stored for this client + // That means this is us, so we're going to ignore us :P + if (packet.challenge() == entry->challenge) + { + entry->lastHeard = Game::Sys_Milliseconds(); + entry->lastTime = Game::Sys_Milliseconds(); + entry->registered = false; + entry->state = Node::STATE_INVALID; + return; + } + + packet.Clear(); + packet.set_challenge(challenge); + packet.set_signature(signature); + packet.set_publickey(Node::SignatureKey.GetPublicKey()); + + entry->lastTime = Game::Sys_Milliseconds(); + entry->challenge = challenge; + entry->state = Node::STATE_NEGOTIATING; + + Network::SendCommand(address, "nodeRegisterSynchronize", packet.SerializeAsString()); + }); + + Network::Handle("nodeRegisterSynchronize", [] (Network::Address address, std::string data) + { + if (Dvar::Var("sv_lanOnly").Get()) return; + + Node::NodeEntry* entry = Node::FindNode(address); + if (!entry || entry->state != Node::STATE_NEGOTIATING) return; + +#ifdef DEBUG + Logger::Print("Received synchronization data for registration from %s!\n", address.GetCString()); +#endif + + Proto::Node::Packet packet; + if (!packet.ParseFromString(data)) return; + if (packet.challenge().empty()) return; + if (packet.publickey().empty()) return; + if (packet.signature().empty()) return; + + std::string challenge = packet.challenge(); + std::string publicKey = packet.publickey(); + std::string signature = packet.signature(); + + // Verify signature + entry->publicKey.Set(publicKey); + if (!Utils::Cryptography::ECC::VerifyMessage(entry->publicKey, entry->challenge, signature)) + { + Logger::Print("Signature from %s for challenge '%s' is invalid!\n", address.GetCString(), entry->challenge.data()); + return; + } + + // Mark as registered + entry->lastTime = Game::Sys_Milliseconds(); + entry->state = Node::STATE_VALID; + entry->registered = true; + +#ifdef DEBUG + Logger::Print("Signature from %s for challenge '%s' is valid!\n", address.GetCString(), entry->challenge.data()); + Logger::Print("Node %s registered\n", address.GetCString()); +#endif + + // Build response + publicKey = Node::SignatureKey.GetPublicKey(); + signature = Utils::Cryptography::ECC::SignMessage(Node::SignatureKey, challenge); + + packet.Clear(); + packet.set_signature(signature); + packet.set_publickey(publicKey); + + Network::SendCommand(address, "nodeRegisterAcknowledge", packet.SerializeAsString()); + }); + + Network::Handle("nodeRegisterAcknowledge", [] (Network::Address address, std::string data) + { + if (Dvar::Var("sv_lanOnly").Get()) return; + + // Ignore requests from nodes we don't know + Node::NodeEntry* entry = Node::FindNode(address); + if (!entry || entry->state != Node::STATE_NEGOTIATING) return; + +#ifdef DEBUG + Logger::Print("Received acknowledgment from %s\n", address.GetCString()); +#endif + + Proto::Node::Packet packet; + if (!packet.ParseFromString(data)) return; + if (packet.signature().empty()) return; + if (packet.publickey().empty()) return; + + std::string publicKey = packet.publickey(); + std::string signature = packet.signature(); + + entry->publicKey.Set(publicKey); + + if (Utils::Cryptography::ECC::VerifyMessage(entry->publicKey, entry->challenge, signature)) + { + entry->lastTime = Game::Sys_Milliseconds(); + entry->state = Node::STATE_VALID; + entry->registered = true; + +#ifdef DEBUG + Logger::Print("Signature from %s for challenge '%s' is valid!\n", address.GetCString(), entry->challenge.data()); + Logger::Print("Node %s registered\n", address.GetCString()); +#endif + } + else + { + Logger::Print("Signature from %s for challenge '%s' is invalid!\n", address.GetCString(), entry->challenge.data()); + } + }); + + Network::Handle("nodeListRequest", [] (Network::Address address, std::string data) + { + if (Dvar::Var("sv_lanOnly").Get()) return; + + // Check if this is a registered node + bool allowed = false; + Node::NodeEntry* entry = Node::FindNode(address); + if (entry && entry->registered) + { + entry->lastTime = Game::Sys_Milliseconds(); + allowed = true; + } + + // Check if there is any open session + if (!allowed) + { + Node::ClientSession* session = Node::FindSession(address); + if (session) + { + session->lastTime = Game::Sys_Milliseconds(); + allowed = session->valid; + } + } + + if (allowed) + { + Node::SendNodeList(address); + } + else + { + // Unallowed connection + Logger::Print("Node list requested by %s, but no valid session was present!\n", address.GetCString()); + Network::SendCommand(address, "nodeListError"); + } + }); + + Network::Handle("nodeDeregister", [] (Network::Address address, std::string data) + { + if (Dvar::Var("sv_lanOnly").Get()) return; + + Node::NodeEntry* entry = Node::FindNode(address); + if (!entry || !entry->registered) return; + + Proto::Node::Packet packet; + if (!packet.ParseFromString(data)) return; + if (packet.challenge().empty()) return; + if (packet.signature().empty()) return; + + std::string challenge = packet.challenge(); + std::string signature = packet.signature(); + + if (Utils::Cryptography::ECC::VerifyMessage(entry->publicKey, challenge, signature)) + { + entry->lastHeard = Game::Sys_Milliseconds(); + entry->lastTime = Game::Sys_Milliseconds(); + entry->registered = false; + entry->state = Node::STATE_INVALID; + +#ifdef DEBUG + Logger::Print("Node %s unregistered\n", address.GetCString()); +#endif + } + else + { + Logger::Print("Node %s tried to unregister using an invalid signature!\n", address.GetCString()); + } + }); + + Network::Handle("sessionRequest", [] (Network::Address address, std::string data) + { + if (Dvar::Var("sv_lanOnly").Get()) return; + + // Search an active session, if we haven't found one, register a template + if (!Node::FindSession(address)) + { + Node::ClientSession templateSession; + templateSession.address = address; + Node::Sessions.push_back(templateSession); + } + + // Search our target session (this should not fail!) + Node::ClientSession* session = Node::FindSession(address); + if (!session) return; // Registering template session failed, odd... + +#ifdef DEBUG + Logger::Print("Client %s is requesting a new session\n", address.GetCString()); +#endif + + // Initialize session data + session->challenge = fmt::sprintf("%X", Utils::Cryptography::Rand::GenerateInt()); + session->lastTime = Game::Sys_Milliseconds(); + session->valid = false; + + Network::SendCommand(address, "sessionInitialize", session->challenge); + }); + + Network::Handle("sessionSynchronize", [] (Network::Address address, std::string data) + { + if (Dvar::Var("sv_lanOnly").Get()) return; + + // Return if we don't have a session for this address + Node::ClientSession* session = Node::FindSession(address); + if (!session || session->valid) return; + + if (session->challenge == data) + { +#ifdef DEBUG + Logger::Print("Session for %s validated.\n", address.GetCString()); +#endif + session->valid = true; + Network::SendCommand(address, "sessionAcknowledge"); + } + else + { + session->lastTime = -1; + Logger::Print("Challenge mismatch. Validating session for %s failed.\n", address.GetCString()); + } + }); + } + else + { + Network::Handle("sessionInitialize", [] (Network::Address address, std::string data) + { + Node::NodeEntry* entry = Node::FindNode(address); + if (!entry) return; + +#ifdef DEBUG + Logger::Print("Session initialization received from %s. Synchronizing...\n", address.GetCString()); +#endif + + entry->lastTime = Game::Sys_Milliseconds(); + Network::SendCommand(address, "sessionSynchronize", data); + }); + + Network::Handle("sessionAcknowledge", [] (Network::Address address, std::string data) + { + Node::NodeEntry* entry = Node::FindNode(address); + if (!entry) return; + + entry->state = Node::STATE_VALID; + entry->registered = true; + entry->lastTime = Game::Sys_Milliseconds(); + +#ifdef DEBUG + Logger::Print("Session acknowledged by %s, synchronizing node list...\n", address.GetCString()); +#endif + Network::SendCommand(address, "nodeListRequest"); + Node::SendNodeList(address); + }); + } + + Network::Handle("nodeListResponse", [] (Network::Address address, std::string data) + { + Proto::Node::List list; + + if (data.empty() || !list.ParseFromString(data)) + { + Logger::Print("Received invalid node list from %s!\n", address.GetCString()); + return; + } + + Node::NodeEntry* entry = Node::FindNode(address); + if (entry) + { + if (entry->registered) + { + Logger::Print("Received valid node list with %i entries from %s\n", list.address_size(), address.GetCString()); + + entry->isDedi = list.is_dedi(); + entry->protocol = list.protocol(); + entry->version = list.version(); + entry->state = Node::STATE_VALID; + entry->lastTime = Game::Sys_Milliseconds(); + + if (!Dedicated::IsDedicated() && entry->isDedi && ServerList::IsOnlineList() && entry->protocol == PROTOCOL) + { + ServerList::InsertRequest(entry->address, true); + } + + for (int i = 0; i < list.address_size(); ++i) + { + Network::Address _addr(list.address(i)); + + // Version 0 sends port in the wrong byte order! + if (entry->version == 0) + { + _addr.SetPort(ntohs(_addr.GetPort())); + } + +// if (!Node::FindNode(_addr) && _addr.GetPort() >= 1024 && _addr.GetPort() - 20 < 1024) +// { +// std::string a1 = _addr.GetString(); +// std::string a2 = address.GetString(); +// Logger::Print("Received weird address %s from %s\n", a1.data(), a2.data()); +// } + + Node::AddNode(_addr); + } + } + } + else + { + //Node::AddNode(address); + + Node::ClientSession* session = Node::FindSession(address); + if (session && session->valid) + { + session->lastTime = Game::Sys_Milliseconds(); + + for (int i = 0; i < list.address_size(); ++i) + { + Node::AddNode(list.address(i)); + } + } + } + }); + + // If we receive that response, our request was not permitted + // So we either have to register as node, or register a remote session + Network::Handle("nodeListError", [] (Network::Address address, std::string data) + { + if (Dedicated::IsDedicated()) + { + Node::NodeEntry* entry = Node::FindNode(address); + if (entry) + { + // Set to unregistered to perform registration later on + entry->lastTime = Game::Sys_Milliseconds(); + entry->registered = false; + entry->state = Node::STATE_UNKNOWN; + } + else + { + // Add as new entry to perform registration + Node::AddNode(address); + } + } + else + { + // TODO: Implement client handshake stuff + // Nvm, clients can simply ignore that i guess + } + }); + + Command::Add("listnodes", [] (Command::Params params) + { + Logger::Print("Nodes: %d (%d)\n", Node::Nodes.size(), Node::GetValidNodeCount()); + + for (auto node : Node::Nodes) + { + Logger::Print("%s\t(%s)\n", node.address.GetCString(), Node::GetStateName(node.state)); + } + }); + + Command::Add("addnode", [] (Command::Params params) + { + if (params.Length() < 2) return; + + Network::Address address(params[1]); + Node::AddNode(address); + + Node::NodeEntry* entry = Node::FindNode(address); + if (entry) + { + entry->state = Node::STATE_UNKNOWN; + entry->registered = false; + } + }); + + Command::Add("syncnodes", [] (Command::Params params) + { + Logger::Print("Re-Synchronizing nodes...\n"); + + for (auto& node : Node::Nodes) + { + node.state = Node::STATE_UNKNOWN; + node.registered = false; + node.lastTime = 0; + node.lastListQuery = 0; + } + }); + + // Install frame handlers + QuickPatch::OnFrame(Node::FrameHandler); + } + + Node::~Node() + { + Node::SignatureKey.Free(); + + Node::StoreNodes(true); + Node::Nodes.clear(); + Node::Sessions.clear(); + } + + bool Node::UnitTest() + { + printf("Testing ECDSA key..."); + + if (!Node::SignatureKey.IsValid()) + { + printf("Error\n"); + printf("ECDSA key seems invalid!\n"); + return false; + } + + printf("Success\n"); + printf("Testing 10 valid signatures..."); + + for (int i = 0; i < 10; ++i) + { + std::string message = fmt::sprintf("%X", Utils::Cryptography::Rand::GenerateInt()); + std::string signature = Utils::Cryptography::ECC::SignMessage(Node::SignatureKey, message); + + if (!Utils::Cryptography::ECC::VerifyMessage(Node::SignatureKey, message, signature)) + { + printf("Error\n"); + printf("Signature for '%s' (%d) was invalid!\n", message.data(), i); + return false; + } + } + + printf("Success\n"); + printf("Testing 10 invalid signatures..."); + + for (int i = 0; i < 10; ++i) + { + std::string message = fmt::sprintf("%X", Utils::Cryptography::Rand::GenerateInt()); + std::string signature = Utils::Cryptography::ECC::SignMessage(Node::SignatureKey, message); + + // Invalidate the message... + ++message[Utils::Cryptography::Rand::GenerateInt() % message.size()]; + + if (Utils::Cryptography::ECC::VerifyMessage(Node::SignatureKey, message, signature)) + { + printf("Error\n"); + printf("Signature for '%s' (%d) was valid? What the fuck? That is absolutely impossible...\n", message.data(), i); + return false; + } + } + + printf("Success\n"); + printf("Testing ECDSA key import..."); + + std::string pubKey = Node::SignatureKey.GetPublicKey(); + std::string message = fmt::sprintf("%X", Utils::Cryptography::Rand::GenerateInt()); + std::string signature = Utils::Cryptography::ECC::SignMessage(Node::SignatureKey, message); + + Utils::Cryptography::ECC::Key testKey; + testKey.Set(pubKey); + + if (!Utils::Cryptography::ECC::VerifyMessage(Node::SignatureKey, message, signature)) + { + printf("Error\n"); + printf("Verifying signature for message '%s' using imported keys failed!\n", message.data()); + return false; + } + + printf("Success\n"); + + uint32_t randIntCount = 4'000'000; + printf("Generating %d random integers...", randIntCount); + + auto startTime = std::chrono::high_resolution_clock::now(); + + for (uint32_t i = 0; i < randIntCount; ++i) + { + Utils::Cryptography::Rand::GenerateInt(); + } + + auto duration = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - startTime).count(); + Logger::Print("took %llims\n", duration); + + return true; + } +} diff --git a/src/Components/Modules/Party.cpp b/src/Components/Modules/Party.cpp index eaf396e6..40f78594 100644 --- a/src/Components/Modules/Party.cpp +++ b/src/Components/Modules/Party.cpp @@ -1,404 +1,404 @@ -#include "STDInclude.hpp" - -namespace Components -{ - Party::JoinContainer Party::Container; - std::map Party::LobbyMap; - - SteamID Party::GenerateLobbyId() - { - SteamID id; - - id.AccountID = Game::Sys_Milliseconds(); - id.Universe = 1; - id.AccountType = 8; - id.AccountInstance = 0x40000; - - return id; - } - - Network::Address Party::Target() - { - return Party::Container.Target; - } - - void Party::Connect(Network::Address target) - { - Party::Container.Valid = true; - Party::Container.AwaitingPlaylist = false; - Party::Container.JoinTime = Game::Sys_Milliseconds(); - Party::Container.Target = target; - Party::Container.Challenge = Utils::VA("%X", Utils::Cryptography::Rand::GenerateInt()); - - Network::SendCommand(Party::Container.Target, "getinfo", Party::Container.Challenge); - - Command::Execute("openmenu popup_reconnectingtoparty"); - } - - const char* Party::GetLobbyInfo(SteamID lobby, std::string key) - { - if (Party::LobbyMap.find(lobby.Bits) != Party::LobbyMap.end()) - { - Network::Address address = Party::LobbyMap[lobby.Bits]; - - if (key == "addr") - { - return Utils::VA("%d", address.GetIP().full); - } - else if (key =="port") - { - return Utils::VA("%d", address.GetPort()); - } - } - - return "212"; - } - - void Party::RemoveLobby(SteamID lobby) - { - if (Party::LobbyMap.find(lobby.Bits) != Party::LobbyMap.end()) - { - Party::LobbyMap.erase(Party::LobbyMap.find(lobby.Bits)); - } - } - - void Party::ConnectError(std::string message) - { - Command::Execute("closemenu popup_reconnectingtoparty"); - Dvar::Var("partyend_reason").Set(message); - Command::Execute("openmenu menu_xboxlive_partyended"); - } - - Game::dvar_t* Party::RegisterMinPlayers(const char* name, int value, int min, int max, Game::dvar_flag flag, const char* description) - { - return Dvar::Register(name, 1, 1, max, Game::dvar_flag::DVAR_FLAG_WRITEPROTECTED | flag, description).Get(); - } - - bool Party::PlaylistAwaiting() - { - return Party::Container.AwaitingPlaylist; - } - - void Party::PlaylistContinue() - { - Dvar::Var("xblive_privateserver").Set(false); - - // Ensure we can join - *Game::g_lobbyCreateInProgress = false; - - Party::Container.AwaitingPlaylist = false; - - SteamID id = Party::GenerateLobbyId(); - - Party::LobbyMap[id.Bits] = Party::Container.Target; - - Game::Steam_JoinLobby(id, 0); - } - - void Party::PlaylistError(std::string error) - { - Party::Container.Valid = false; - Party::Container.AwaitingPlaylist = false; - - Party::ConnectError(error); - } - - DWORD Party::UIDvarIntStub(char* dvar) - { - if (!_stricmp(dvar, "onlinegame")) - { - return 0x649E660; - } - - return Utils::Hook::Call(0x4D5390)(dvar); - } - - Party::Party() - { - static Game::dvar_t* partyEnable = Dvar::Register("party_enable", Dedicated::IsDedicated(), Game::dvar_flag::DVAR_FLAG_NONE, "Enable party system").Get(); - Dvar::Register("xblive_privatematch", true, Game::dvar_flag::DVAR_FLAG_WRITEPROTECTED, "").Get(); - - // various changes to SV_DirectConnect-y stuff to allow non-party joinees - Utils::Hook::Set(0x460D96, 0x90E9); - Utils::Hook::Set(0x460F0A, 0xEB); - Utils::Hook::Set(0x401CA4, 0xEB); - Utils::Hook::Set(0x401C15, 0xEB); - - // disable configstring checksum matching (it's unreliable at most) - Utils::Hook::Set(0x4A75A7, 0xEB); // SV_SpawnServer - Utils::Hook::Set(0x5AC2CF, 0xEB); // CL_ParseGamestate - Utils::Hook::Set(0x5AC2C3, 0xEB); // CL_ParseGamestate - - // AnonymousAddRequest - Utils::Hook::Set(0x5B5E18, 0xEB); - Utils::Hook::Set(0x5B5E64, 0xEB); - Utils::Hook::Nop(0x5B5E5C, 2); - - // HandleClientHandshake - Utils::Hook::Set(0x5B6EA5, 0xEB); - Utils::Hook::Set(0x5B6EF3, 0xEB); - Utils::Hook::Nop(0x5B6EEB, 2); - - // Allow local connections - Utils::Hook::Set(0x4D43DA, 0xEB); - - // LobbyID mismatch - Utils::Hook::Nop(0x4E50D6, 2); - Utils::Hook::Set(0x4E50DA, 0xEB); - - // causes 'does current Steam lobby match' calls in Steam_JoinLobby to be ignored - Utils::Hook::Set(0x49D007, 0xEB); - - // functions checking party heartbeat timeouts, cause random issues - Utils::Hook::Nop(0x4E532D, 5); - - // Steam_JoinLobby call causes migration - Utils::Hook::Nop(0x5AF851, 5); - Utils::Hook::Set(0x5AF85B, 0xEB); - - // Allow xpartygo in public lobbies - Utils::Hook::Set(0x5A969E, 0xEB); - Utils::Hook::Nop(0x5A96BE, 2); - - // Always open lobby menu when connecting - // It's not possible to entirely patch it via code - //Utils::Hook::Set(0x5B1698, 0xEB); - //Utils::Hook::Nop(0x5029F2, 6); - //Utils::Hook::SetString(0x70573C, "menu_xboxlive_lobby"); - - // Disallow selecting team in private match - //Utils::Hook::Nop(0x5B2BD8, 6); - - // Force teams, even if not private match - Utils::Hook::Set(0x487BB2, 0xEB); - - // Force xblive_privatematch 0 and rename it - //Utils::Hook::Set(0x420A6A, 4); - Utils::Hook::Set(0x420A6C, 0); - Utils::Hook::Set(0x420A6E, "xblive_privateserver"); - - // Remove migration shutdown, it causes crashes and will be destroyed when erroring anyways - Utils::Hook::Nop(0x5A8E1C, 12); - Utils::Hook::Nop(0x5A8E33, 11); - - // Enable XP Bar - Utils::Hook(0x62A2A7, Party::UIDvarIntStub, HOOK_CALL).Install()->Quick(); - - // Set NAT to open - Utils::Hook::Set(0x79D898, 1); - - // Disable host migration - Utils::Hook::Set(0x5B58B2, 0xEB); - Utils::Hook::Set(0x4D6171, 0); - - // Patch playlist stuff for non-party behavior - Utils::Hook::Set(0x4A4093, &partyEnable); - Utils::Hook::Set(0x4573F1, &partyEnable); - Utils::Hook::Set(0x5B1A0C, &partyEnable); - - // Invert corresponding jumps - Utils::Hook::Xor(0x4A409B, 1); - Utils::Hook::Xor(0x4573FA, 1); - Utils::Hook::Xor(0x5B1A17, 1); - - // Fix xstartlobby - //Utils::Hook::Set(0x5B71CD, 0xEB); - - // Patch party_minplayers to 1 and protect it - //Utils::Hook(0x4D5D51, Party::RegisterMinPlayers, HOOK_CALL).Install()->Quick(); - - // Set ui_maxclients to sv_maxclients - Utils::Hook::Set(0x42618F, "sv_maxclients"); - Utils::Hook::Set(0x4D3756, "sv_maxclients"); - Utils::Hook::Set(0x5E3772, "sv_maxclients"); - - // Unlatch maxclient dvars - Utils::Hook::Xor(0x426187, Game::dvar_flag::DVAR_FLAG_LATCHED); - Utils::Hook::Xor(0x4D374E, Game::dvar_flag::DVAR_FLAG_LATCHED); - Utils::Hook::Xor(0x5E376A, Game::dvar_flag::DVAR_FLAG_LATCHED); - Utils::Hook::Xor(0x4261A1, Game::dvar_flag::DVAR_FLAG_LATCHED); - Utils::Hook::Xor(0x4D376D, Game::dvar_flag::DVAR_FLAG_LATCHED); - Utils::Hook::Xor(0x5E3789, Game::dvar_flag::DVAR_FLAG_LATCHED); - - Command::Add("connect", [] (Command::Params params) - { - if (params.Length() < 2) - { - return; - } - - Party::Connect(Network::Address(params[1])); - }); - Command::Add("reconnect", [] (Command::Params params) - { - Party::Connect(Party::Container.Target); - }); - - Renderer::OnFrame([] () - { - if (Party::Container.Valid) - { - if ((Game::Sys_Milliseconds() - Party::Container.JoinTime) > 5000) - { - Party::Container.Valid = false; - Party::ConnectError("Server connection timed out."); - } - } - - if (Party::Container.AwaitingPlaylist) - { - if ((Game::Sys_Milliseconds() - Party::Container.RequestTime) > 5000) - { - Party::Container.AwaitingPlaylist = false; - Party::ConnectError("Playlist request timed out."); - } - } - }); - - // Basic info handler - Network::Handle("getInfo", [] (Network::Address address, std::string data) - { - int clientCount = 0; - int maxclientCount = *Game::svs_numclients; - - if (maxclientCount) - { - for (int i = 0; i < maxclientCount; ++i) - { - if (Game::svs_clients[i].state >= 3) - { - ++clientCount; - } - } - } - else - { - //maxclientCount = Dvar::Var("sv_maxclients").Get(); - maxclientCount = Game::Party_GetMaxPlayers(*Game::partyIngame); - clientCount = Game::PartyHost_CountMembers(reinterpret_cast(0x1081C00)); - } - - Utils::InfoString info; - info.Set("challenge", Utils::ParseChallenge(data)); - info.Set("gamename", "IW4"); - info.Set("hostname", Dvar::Var("sv_hostname").Get()); - info.Set("gametype", Dvar::Var("g_gametype").Get()); - info.Set("fs_game", Dvar::Var("fs_game").Get()); - info.Set("xuid", Utils::VA("%llX", Steam::SteamUser()->GetSteamID().Bits)); - info.Set("clients", Utils::VA("%i", clientCount)); - info.Set("sv_maxclients", Utils::VA("%i", maxclientCount)); - info.Set("protocol", Utils::VA("%i", PROTOCOL)); - info.Set("shortversion", VERSION_STR); - info.Set("checksum", Utils::VA("%d", Game::Sys_Milliseconds())); - info.Set("mapname", Dvar::Var("mapname").Get()); - info.Set("isPrivate", (Dvar::Var("g_password").Get().size() ? "1" : "0")); - info.Set("hc", (Dvar::Var("g_hardcore").Get() ? "1" : "0")); - info.Set("securityLevel", Utils::VA("%i", Dvar::Var("sv_securityLevel").Get())); - - // Ensure mapname is set - if (info.Get("mapname").empty()) - { - info.Set("mapname", Dvar::Var("ui_mapname").Get()); - } - - // Set matchtype - // 0 - No match, connecting not possible - // 1 - Party, use Steam_JoinLobby to connect - // 2 - Match, use CL_ConnectFromParty to connect - - if (Dvar::Var("party_enable").Get() && Dvar::Var("party_host").Get()) // Party hosting - { - info.Set("matchtype", "1"); - } - else if (Dvar::Var("sv_running").Get()) // Match hosting - { - info.Set("matchtype", "2"); - } - else - { - info.Set("matchtype", "0"); - } - - Network::SendCommand(address, "infoResponse", "\\" + info.Build()); - }); - - Network::Handle("infoResponse", [] (Network::Address address, std::string data) - { - Utils::InfoString info(data); - - // Handle connection - if (Party::Container.Valid) - { - if (Party::Container.Target == address) - { - // Invalidate handler for future packets - Party::Container.Valid = false; - - int matchType = atoi(info.Get("matchtype").data()); - uint32_t securityLevel = static_cast(atoi(info.Get("securityLevel").data())); - - if (info.Get("challenge") != Party::Container.Challenge) - { - Party::ConnectError("Invalid join response: Challenge mismatch."); - } - else if (securityLevel > Auth::GetSecurityLevel()) - { - //Party::ConnectError(Utils::VA("Your security level (%d) is lower than the server's (%d)", Auth::GetSecurityLevel(), securityLevel)); - Command::Execute("closemenu popup_reconnectingtoparty"); - Auth::IncreaseSecurityLevel(securityLevel, "reconnect"); - } - else if (!matchType) - { - Party::ConnectError("Server is not hosting a match."); - } - // Connect - else if (matchType == 1) // Party - { - // Send playlist request - Party::Container.RequestTime = Game::Sys_Milliseconds(); - Party::Container.AwaitingPlaylist = true; - Network::SendCommand(address, "getplaylist"); - - // This is not a safe method - // TODO: Fix actual error! - if (Game::CL_IsCgameInitialized()) - { - Command::Execute("disconnect", true); - } - } - else if (matchType == 2) // Match - { - if (atoi(info.Get("clients").data()) >= atoi(info.Get("sv_maxclients").data())) - { - Party::ConnectError("@EXE_SERVERISFULL"); - } - if (info.Get("mapname") == "" || info.Get("gametype") == "") - { - Party::ConnectError("Invalid map or gametype."); - } - else - { - Dvar::Var("xblive_privateserver").Set(true); - - Game::Menus_CloseAll(Game::uiContext); - - Game::_XSESSION_INFO hostInfo; - Game::CL_ConnectFromParty(0, &hostInfo, *address.Get(), 0, 0, info.Get("mapname").data(), info.Get("gametype").data()); - } - } - else - { - Party::ConnectError("Invalid join response: Unknown matchtype"); - } - } - } - - ServerList::Insert(address, info); - }); - } - - Party::~Party() - { - Party::LobbyMap.clear(); - } -} +#include "STDInclude.hpp" + +namespace Components +{ + Party::JoinContainer Party::Container; + std::map Party::LobbyMap; + + SteamID Party::GenerateLobbyId() + { + SteamID id; + + id.AccountID = Game::Sys_Milliseconds(); + id.Universe = 1; + id.AccountType = 8; + id.AccountInstance = 0x40000; + + return id; + } + + Network::Address Party::Target() + { + return Party::Container.Target; + } + + void Party::Connect(Network::Address target) + { + Party::Container.Valid = true; + Party::Container.AwaitingPlaylist = false; + Party::Container.JoinTime = Game::Sys_Milliseconds(); + Party::Container.Target = target; + Party::Container.Challenge = fmt::sprintf("%X", Utils::Cryptography::Rand::GenerateInt()); + + Network::SendCommand(Party::Container.Target, "getinfo", Party::Container.Challenge); + + Command::Execute("openmenu popup_reconnectingtoparty"); + } + + const char* Party::GetLobbyInfo(SteamID lobby, std::string key) + { + if (Party::LobbyMap.find(lobby.Bits) != Party::LobbyMap.end()) + { + Network::Address address = Party::LobbyMap[lobby.Bits]; + + if (key == "addr") + { + return Utils::String::VA("%d", address.GetIP().full); + } + else if (key =="port") + { + return Utils::String::VA("%d", address.GetPort()); + } + } + + return "212"; + } + + void Party::RemoveLobby(SteamID lobby) + { + if (Party::LobbyMap.find(lobby.Bits) != Party::LobbyMap.end()) + { + Party::LobbyMap.erase(Party::LobbyMap.find(lobby.Bits)); + } + } + + void Party::ConnectError(std::string message) + { + Command::Execute("closemenu popup_reconnectingtoparty"); + Dvar::Var("partyend_reason").Set(message); + Command::Execute("openmenu menu_xboxlive_partyended"); + } + + Game::dvar_t* Party::RegisterMinPlayers(const char* name, int value, int min, int max, Game::dvar_flag flag, const char* description) + { + return Dvar::Register(name, 1, 1, max, Game::dvar_flag::DVAR_FLAG_WRITEPROTECTED | flag, description).Get(); + } + + bool Party::PlaylistAwaiting() + { + return Party::Container.AwaitingPlaylist; + } + + void Party::PlaylistContinue() + { + Dvar::Var("xblive_privateserver").Set(false); + + // Ensure we can join + *Game::g_lobbyCreateInProgress = false; + + Party::Container.AwaitingPlaylist = false; + + SteamID id = Party::GenerateLobbyId(); + + Party::LobbyMap[id.Bits] = Party::Container.Target; + + Game::Steam_JoinLobby(id, 0); + } + + void Party::PlaylistError(std::string error) + { + Party::Container.Valid = false; + Party::Container.AwaitingPlaylist = false; + + Party::ConnectError(error); + } + + DWORD Party::UIDvarIntStub(char* dvar) + { + if (!_stricmp(dvar, "onlinegame")) + { + return 0x649E660; + } + + return Utils::Hook::Call(0x4D5390)(dvar); + } + + Party::Party() + { + static Game::dvar_t* partyEnable = Dvar::Register("party_enable", Dedicated::IsDedicated(), Game::dvar_flag::DVAR_FLAG_NONE, "Enable party system").Get(); + Dvar::Register("xblive_privatematch", true, Game::dvar_flag::DVAR_FLAG_WRITEPROTECTED, "").Get(); + + // various changes to SV_DirectConnect-y stuff to allow non-party joinees + Utils::Hook::Set(0x460D96, 0x90E9); + Utils::Hook::Set(0x460F0A, 0xEB); + Utils::Hook::Set(0x401CA4, 0xEB); + Utils::Hook::Set(0x401C15, 0xEB); + + // disable configstring checksum matching (it's unreliable at most) + Utils::Hook::Set(0x4A75A7, 0xEB); // SV_SpawnServer + Utils::Hook::Set(0x5AC2CF, 0xEB); // CL_ParseGamestate + Utils::Hook::Set(0x5AC2C3, 0xEB); // CL_ParseGamestate + + // AnonymousAddRequest + Utils::Hook::Set(0x5B5E18, 0xEB); + Utils::Hook::Set(0x5B5E64, 0xEB); + Utils::Hook::Nop(0x5B5E5C, 2); + + // HandleClientHandshake + Utils::Hook::Set(0x5B6EA5, 0xEB); + Utils::Hook::Set(0x5B6EF3, 0xEB); + Utils::Hook::Nop(0x5B6EEB, 2); + + // Allow local connections + Utils::Hook::Set(0x4D43DA, 0xEB); + + // LobbyID mismatch + Utils::Hook::Nop(0x4E50D6, 2); + Utils::Hook::Set(0x4E50DA, 0xEB); + + // causes 'does current Steam lobby match' calls in Steam_JoinLobby to be ignored + Utils::Hook::Set(0x49D007, 0xEB); + + // functions checking party heartbeat timeouts, cause random issues + Utils::Hook::Nop(0x4E532D, 5); + + // Steam_JoinLobby call causes migration + Utils::Hook::Nop(0x5AF851, 5); + Utils::Hook::Set(0x5AF85B, 0xEB); + + // Allow xpartygo in public lobbies + Utils::Hook::Set(0x5A969E, 0xEB); + Utils::Hook::Nop(0x5A96BE, 2); + + // Always open lobby menu when connecting + // It's not possible to entirely patch it via code + //Utils::Hook::Set(0x5B1698, 0xEB); + //Utils::Hook::Nop(0x5029F2, 6); + //Utils::Hook::SetString(0x70573C, "menu_xboxlive_lobby"); + + // Disallow selecting team in private match + //Utils::Hook::Nop(0x5B2BD8, 6); + + // Force teams, even if not private match + Utils::Hook::Set(0x487BB2, 0xEB); + + // Force xblive_privatematch 0 and rename it + //Utils::Hook::Set(0x420A6A, 4); + Utils::Hook::Set(0x420A6C, 0); + Utils::Hook::Set(0x420A6E, "xblive_privateserver"); + + // Remove migration shutdown, it causes crashes and will be destroyed when erroring anyways + Utils::Hook::Nop(0x5A8E1C, 12); + Utils::Hook::Nop(0x5A8E33, 11); + + // Enable XP Bar + Utils::Hook(0x62A2A7, Party::UIDvarIntStub, HOOK_CALL).Install()->Quick(); + + // Set NAT to open + Utils::Hook::Set(0x79D898, 1); + + // Disable host migration + Utils::Hook::Set(0x5B58B2, 0xEB); + Utils::Hook::Set(0x4D6171, 0); + + // Patch playlist stuff for non-party behavior + Utils::Hook::Set(0x4A4093, &partyEnable); + Utils::Hook::Set(0x4573F1, &partyEnable); + Utils::Hook::Set(0x5B1A0C, &partyEnable); + + // Invert corresponding jumps + Utils::Hook::Xor(0x4A409B, 1); + Utils::Hook::Xor(0x4573FA, 1); + Utils::Hook::Xor(0x5B1A17, 1); + + // Fix xstartlobby + //Utils::Hook::Set(0x5B71CD, 0xEB); + + // Patch party_minplayers to 1 and protect it + //Utils::Hook(0x4D5D51, Party::RegisterMinPlayers, HOOK_CALL).Install()->Quick(); + + // Set ui_maxclients to sv_maxclients + Utils::Hook::Set(0x42618F, "sv_maxclients"); + Utils::Hook::Set(0x4D3756, "sv_maxclients"); + Utils::Hook::Set(0x5E3772, "sv_maxclients"); + + // Unlatch maxclient dvars + Utils::Hook::Xor(0x426187, Game::dvar_flag::DVAR_FLAG_LATCHED); + Utils::Hook::Xor(0x4D374E, Game::dvar_flag::DVAR_FLAG_LATCHED); + Utils::Hook::Xor(0x5E376A, Game::dvar_flag::DVAR_FLAG_LATCHED); + Utils::Hook::Xor(0x4261A1, Game::dvar_flag::DVAR_FLAG_LATCHED); + Utils::Hook::Xor(0x4D376D, Game::dvar_flag::DVAR_FLAG_LATCHED); + Utils::Hook::Xor(0x5E3789, Game::dvar_flag::DVAR_FLAG_LATCHED); + + Command::Add("connect", [] (Command::Params params) + { + if (params.Length() < 2) + { + return; + } + + Party::Connect(Network::Address(params[1])); + }); + Command::Add("reconnect", [] (Command::Params params) + { + Party::Connect(Party::Container.Target); + }); + + Renderer::OnFrame([] () + { + if (Party::Container.Valid) + { + if ((Game::Sys_Milliseconds() - Party::Container.JoinTime) > 5000) + { + Party::Container.Valid = false; + Party::ConnectError("Server connection timed out."); + } + } + + if (Party::Container.AwaitingPlaylist) + { + if ((Game::Sys_Milliseconds() - Party::Container.RequestTime) > 5000) + { + Party::Container.AwaitingPlaylist = false; + Party::ConnectError("Playlist request timed out."); + } + } + }); + + // Basic info handler + Network::Handle("getInfo", [] (Network::Address address, std::string data) + { + int clientCount = 0; + int maxclientCount = *Game::svs_numclients; + + if (maxclientCount) + { + for (int i = 0; i < maxclientCount; ++i) + { + if (Game::svs_clients[i].state >= 3) + { + ++clientCount; + } + } + } + else + { + //maxclientCount = Dvar::Var("sv_maxclients").Get(); + maxclientCount = Game::Party_GetMaxPlayers(*Game::partyIngame); + clientCount = Game::PartyHost_CountMembers(reinterpret_cast(0x1081C00)); + } + + Utils::InfoString info; + info.Set("challenge", Utils::ParseChallenge(data)); + info.Set("gamename", "IW4"); + info.Set("hostname", Dvar::Var("sv_hostname").Get()); + info.Set("gametype", Dvar::Var("g_gametype").Get()); + info.Set("fs_game", Dvar::Var("fs_game").Get()); + info.Set("xuid", fmt::sprintf("%llX", Steam::SteamUser()->GetSteamID().Bits)); + info.Set("clients", fmt::sprintf("%i", clientCount)); + info.Set("sv_maxclients", fmt::sprintf("%i", maxclientCount)); + info.Set("protocol", fmt::sprintf("%i", PROTOCOL)); + info.Set("shortversion", VERSION_STR); + info.Set("checksum", fmt::sprintf("%d", Game::Sys_Milliseconds())); + info.Set("mapname", Dvar::Var("mapname").Get()); + info.Set("isPrivate", (Dvar::Var("g_password").Get().size() ? "1" : "0")); + info.Set("hc", (Dvar::Var("g_hardcore").Get() ? "1" : "0")); + info.Set("securityLevel", fmt::sprintf("%i", Dvar::Var("sv_securityLevel").Get())); + + // Ensure mapname is set + if (info.Get("mapname").empty()) + { + info.Set("mapname", Dvar::Var("ui_mapname").Get()); + } + + // Set matchtype + // 0 - No match, connecting not possible + // 1 - Party, use Steam_JoinLobby to connect + // 2 - Match, use CL_ConnectFromParty to connect + + if (Dvar::Var("party_enable").Get() && Dvar::Var("party_host").Get()) // Party hosting + { + info.Set("matchtype", "1"); + } + else if (Dvar::Var("sv_running").Get()) // Match hosting + { + info.Set("matchtype", "2"); + } + else + { + info.Set("matchtype", "0"); + } + + Network::SendCommand(address, "infoResponse", "\\" + info.Build()); + }); + + Network::Handle("infoResponse", [] (Network::Address address, std::string data) + { + Utils::InfoString info(data); + + // Handle connection + if (Party::Container.Valid) + { + if (Party::Container.Target == address) + { + // Invalidate handler for future packets + Party::Container.Valid = false; + + int matchType = atoi(info.Get("matchtype").data()); + uint32_t securityLevel = static_cast(atoi(info.Get("securityLevel").data())); + + if (info.Get("challenge") != Party::Container.Challenge) + { + Party::ConnectError("Invalid join response: Challenge mismatch."); + } + else if (securityLevel > Auth::GetSecurityLevel()) + { + //Party::ConnectError(Utils::VA("Your security level (%d) is lower than the server's (%d)", Auth::GetSecurityLevel(), securityLevel)); + Command::Execute("closemenu popup_reconnectingtoparty"); + Auth::IncreaseSecurityLevel(securityLevel, "reconnect"); + } + else if (!matchType) + { + Party::ConnectError("Server is not hosting a match."); + } + // Connect + else if (matchType == 1) // Party + { + // Send playlist request + Party::Container.RequestTime = Game::Sys_Milliseconds(); + Party::Container.AwaitingPlaylist = true; + Network::SendCommand(address, "getplaylist"); + + // This is not a safe method + // TODO: Fix actual error! + if (Game::CL_IsCgameInitialized()) + { + Command::Execute("disconnect", true); + } + } + else if (matchType == 2) // Match + { + if (atoi(info.Get("clients").data()) >= atoi(info.Get("sv_maxclients").data())) + { + Party::ConnectError("@EXE_SERVERISFULL"); + } + if (info.Get("mapname") == "" || info.Get("gametype") == "") + { + Party::ConnectError("Invalid map or gametype."); + } + else + { + Dvar::Var("xblive_privateserver").Set(true); + + Game::Menus_CloseAll(Game::uiContext); + + Game::_XSESSION_INFO hostInfo; + Game::CL_ConnectFromParty(0, &hostInfo, *address.Get(), 0, 0, info.Get("mapname").data(), info.Get("gametype").data()); + } + } + else + { + Party::ConnectError("Invalid join response: Unknown matchtype"); + } + } + } + + ServerList::Insert(address, info); + }); + } + + Party::~Party() + { + Party::LobbyMap.clear(); + } +} diff --git a/src/Components/Modules/Playlist.cpp b/src/Components/Modules/Playlist.cpp index 0db1f466..09affde5 100644 --- a/src/Components/Modules/Playlist.cpp +++ b/src/Components/Modules/Playlist.cpp @@ -1,149 +1,149 @@ -#include "STDInclude.hpp" - -namespace Components -{ - std::string Playlist::CurrentPlaylistBuffer; - std::string Playlist::ReceivedPlaylistBuffer; - - void Playlist::LoadPlaylist() - { - // Check if playlist already loaded - if (Utils::Hook::Get(0x1AD3680)) return; - - // Don't load playlists when dedi and no party - if (Dedicated::IsDedicated() && !Dvar::Var("party_enable").Get()) - { - Utils::Hook::Set(0x1AD3680, true); // Set received to true - Dvar::Var("xblive_privateserver").Set(true); - return; - } - - Dvar::Var("xblive_privateserver").Set(false); - - std::string playlistFilename = Dvar::Var("playlistFilename").Get(); - FileSystem::File playlist(playlistFilename); - - if (playlist.Exists()) - { - Logger::Print("Parsing playlist '%s'...\n", playlist.GetName().data()); - Game::Live_ParsePlaylists(playlist.GetBuffer().data()); - Utils::Hook::Set(0x1AD3680, true); // Playlist loaded - } - else - { - Logger::Print("Unable to load playlist '%s'!\n", playlist.GetName().data()); - } - } - - DWORD Playlist::StorePlaylistStub(const char** buffer) - { - Playlist::CurrentPlaylistBuffer = *buffer; - return Utils::Hook::Call(0x4C0350)(buffer); - } - - void Playlist::PlaylistRequest(Network::Address address, std::string data) - { - Logger::Print("Received playlist request, sending currently stored buffer.\n"); - - std::string compressedList = Utils::Compression::ZLib::Compress(Playlist::CurrentPlaylistBuffer); - - Proto::Party::Playlist list; - list.set_hash(Utils::Cryptography::JenkinsOneAtATime::Compute(compressedList)); - list.set_buffer(compressedList); - - Network::SendCommand(address, "playlistResponse", list.SerializeAsString()); - } - - void Playlist::PlaylistReponse(Network::Address address, std::string data) - { - if (Party::PlaylistAwaiting()) - { - if (address == Party::Target()) - { - Proto::Party::Playlist list; - - if (!list.ParseFromString(data)) - { - Party::PlaylistError(Utils::VA("Received playlist response from %s, but it is invalid.", address.GetCString())); - Playlist::ReceivedPlaylistBuffer.clear(); - return; - } - else - { - // Generate buffer and hash - std::string compressedData(list.buffer()); - unsigned int hash = Utils::Cryptography::JenkinsOneAtATime::Compute(compressedData); - - //Validate hashes - if (hash != list.hash()) - { - Party::PlaylistError(Utils::VA("Received playlist response from %s, but the checksum did not match (%X != %X).", address.GetCString(), list.hash(), hash)); - Playlist::ReceivedPlaylistBuffer.clear(); - return; - } - - // Decompress buffer - Playlist::ReceivedPlaylistBuffer = Utils::Compression::ZLib::Decompress(compressedData); - - // Load and continue connection - Logger::Print("Received playlist, loading and continuing connection...\n"); - Game::Live_ParsePlaylists(Playlist::ReceivedPlaylistBuffer.data()); - Party::PlaylistContinue(); - } - } - else - { - Logger::Print("Received playlist from someone else than our target host, ignoring it.\n"); - } - } - else - { - Logger::Print("Received stray playlist response, ignoring it.\n"); - } - } - - Playlist::Playlist() - { - // Default playlists - Utils::Hook::Set(0x60B06E, "playlists_default.info"); - - // disable playlist download function - Utils::Hook::Set(0x4D4790, 0xC3); - - // Load playlist, but don't delete it - Utils::Hook::Nop(0x4D6EBB, 5); - Utils::Hook::Nop(0x4D6E67, 5); - Utils::Hook::Nop(0x4D6E71, 2); - - // playlist dvar 'validity check' - Utils::Hook::Set(0x4B1170, 0xC3); - - // disable playlist checking - Utils::Hook::Set(0x5B69E9, 0xEB); // too new - Utils::Hook::Set(0x5B696E, 0xEB); // too old - - //Got playlists is true - //Utils::Hook::Set(0x1AD3680, true); - - // Store playlist buffer on load - Utils::Hook(0x42961C, Playlist::StorePlaylistStub, HOOK_CALL).Install()->Quick(); - - //if (Dedicated::IsDedicated()) - { - // Custom playlist loading - Utils::Hook(0x420B5A, Playlist::LoadPlaylist, HOOK_JUMP).Install()->Quick(); - - // disable playlist.ff loading function - Utils::Hook::Set(0x4D6E60, 0xC3); - } - - Network::Handle("getPlaylist", PlaylistRequest); - Network::Handle("playlistResponse", PlaylistReponse); - } - - Playlist::~Playlist() - { - Playlist::CurrentPlaylistBuffer.clear(); - Playlist::ReceivedPlaylistBuffer.clear(); - } -} +#include "STDInclude.hpp" + +namespace Components +{ + std::string Playlist::CurrentPlaylistBuffer; + std::string Playlist::ReceivedPlaylistBuffer; + + void Playlist::LoadPlaylist() + { + // Check if playlist already loaded + if (Utils::Hook::Get(0x1AD3680)) return; + + // Don't load playlists when dedi and no party + if (Dedicated::IsDedicated() && !Dvar::Var("party_enable").Get()) + { + Utils::Hook::Set(0x1AD3680, true); // Set received to true + Dvar::Var("xblive_privateserver").Set(true); + return; + } + + Dvar::Var("xblive_privateserver").Set(false); + + std::string playlistFilename = Dvar::Var("playlistFilename").Get(); + FileSystem::File playlist(playlistFilename); + + if (playlist.Exists()) + { + Logger::Print("Parsing playlist '%s'...\n", playlist.GetName().data()); + Game::Live_ParsePlaylists(playlist.GetBuffer().data()); + Utils::Hook::Set(0x1AD3680, true); // Playlist loaded + } + else + { + Logger::Print("Unable to load playlist '%s'!\n", playlist.GetName().data()); + } + } + + DWORD Playlist::StorePlaylistStub(const char** buffer) + { + Playlist::CurrentPlaylistBuffer = *buffer; + return Utils::Hook::Call(0x4C0350)(buffer); + } + + void Playlist::PlaylistRequest(Network::Address address, std::string data) + { + Logger::Print("Received playlist request, sending currently stored buffer.\n"); + + std::string compressedList = Utils::Compression::ZLib::Compress(Playlist::CurrentPlaylistBuffer); + + Proto::Party::Playlist list; + list.set_hash(Utils::Cryptography::JenkinsOneAtATime::Compute(compressedList)); + list.set_buffer(compressedList); + + Network::SendCommand(address, "playlistResponse", list.SerializeAsString()); + } + + void Playlist::PlaylistReponse(Network::Address address, std::string data) + { + if (Party::PlaylistAwaiting()) + { + if (address == Party::Target()) + { + Proto::Party::Playlist list; + + if (!list.ParseFromString(data)) + { + Party::PlaylistError(fmt::sprintf("Received playlist response from %s, but it is invalid.", address.GetCString())); + Playlist::ReceivedPlaylistBuffer.clear(); + return; + } + else + { + // Generate buffer and hash + std::string compressedData(list.buffer()); + unsigned int hash = Utils::Cryptography::JenkinsOneAtATime::Compute(compressedData); + + //Validate hashes + if (hash != list.hash()) + { + Party::PlaylistError(fmt::sprintf("Received playlist response from %s, but the checksum did not match (%X != %X).", address.GetCString(), list.hash(), hash)); + Playlist::ReceivedPlaylistBuffer.clear(); + return; + } + + // Decompress buffer + Playlist::ReceivedPlaylistBuffer = Utils::Compression::ZLib::Decompress(compressedData); + + // Load and continue connection + Logger::Print("Received playlist, loading and continuing connection...\n"); + Game::Live_ParsePlaylists(Playlist::ReceivedPlaylistBuffer.data()); + Party::PlaylistContinue(); + } + } + else + { + Logger::Print("Received playlist from someone else than our target host, ignoring it.\n"); + } + } + else + { + Logger::Print("Received stray playlist response, ignoring it.\n"); + } + } + + Playlist::Playlist() + { + // Default playlists + Utils::Hook::Set(0x60B06E, "playlists_default.info"); + + // disable playlist download function + Utils::Hook::Set(0x4D4790, 0xC3); + + // Load playlist, but don't delete it + Utils::Hook::Nop(0x4D6EBB, 5); + Utils::Hook::Nop(0x4D6E67, 5); + Utils::Hook::Nop(0x4D6E71, 2); + + // playlist dvar 'validity check' + Utils::Hook::Set(0x4B1170, 0xC3); + + // disable playlist checking + Utils::Hook::Set(0x5B69E9, 0xEB); // too new + Utils::Hook::Set(0x5B696E, 0xEB); // too old + + //Got playlists is true + //Utils::Hook::Set(0x1AD3680, true); + + // Store playlist buffer on load + Utils::Hook(0x42961C, Playlist::StorePlaylistStub, HOOK_CALL).Install()->Quick(); + + //if (Dedicated::IsDedicated()) + { + // Custom playlist loading + Utils::Hook(0x420B5A, Playlist::LoadPlaylist, HOOK_JUMP).Install()->Quick(); + + // disable playlist.ff loading function + Utils::Hook::Set(0x4D6E60, 0xC3); + } + + Network::Handle("getPlaylist", PlaylistRequest); + Network::Handle("playlistResponse", PlaylistReponse); + } + + Playlist::~Playlist() + { + Playlist::CurrentPlaylistBuffer.clear(); + Playlist::ReceivedPlaylistBuffer.clear(); + } +} diff --git a/src/Components/Modules/QuickPatch.cpp b/src/Components/Modules/QuickPatch.cpp index 76877017..05aeb6b2 100644 --- a/src/Components/Modules/QuickPatch.cpp +++ b/src/Components/Modules/QuickPatch.cpp @@ -1,354 +1,354 @@ -#include "STDInclude.hpp" - -namespace Components -{ - wink::signal> QuickPatch::ShutdownSignal; - - int64_t* QuickPatch::GetStatsID() - { - static int64_t id = 0x110000100001337; - return &id; - } - - void QuickPatch::OnShutdown(QuickPatch::Callback callback) - { - QuickPatch::ShutdownSignal.connect(callback); - } - - void QuickPatch::ShutdownStub(int channel, const char* message) - { - Game::Com_Printf(0, message); - QuickPatch::ShutdownSignal(); - } - - void QuickPatch::OnFrame(QuickPatch::Callback* callback) - { - if (Dedicated::IsDedicated() || ZoneBuilder::IsEnabled()) - { - Dedicated::OnFrame(callback); - } - else - { - Renderer::OnFrame(callback); - } - } - - void QuickPatch::Once(QuickPatch::Callback* callback) - { - if (Dedicated::IsDedicated() || ZoneBuilder::IsEnabled()) - { - Dedicated::Once(callback); - } - else - { - Renderer::Once(callback); - } - } - - void QuickPatch::UnlockStats() - { - Command::Execute("setPlayerData prestige 10"); - Command::Execute("setPlayerData experience 2516000"); - Command::Execute("setPlayerData iconUnlocked cardicon_prestige10_02 1"); - - // Unlock challenges - Game::StringTable* challengeTable = Game::DB_FindXAssetHeader(Game::XAssetType::ASSET_TYPE_STRINGTABLE, "mp/allchallengestable.csv").stringTable; - - if (challengeTable) - { - for (int i = 0; i < challengeTable->rowCount; ++i) - { - // Find challenge - const char* challenge = Game::TableLookup(challengeTable, i, 0); - - int maxState = 0; - int maxProgress = 0; - - // Find correct tier and progress - for (int j = 0; j < 10; ++j) - { - int progress = atoi(Game::TableLookup(challengeTable, i, 6 + j * 2)); - if (!progress) break; - - maxState = j + 2; - maxProgress = progress; - } - - Command::Execute(Utils::VA("setPlayerData challengeState %s %d", challenge, maxState)); - Command::Execute(Utils::VA("setPlayerData challengeProgress %s %d", challenge, maxProgress)); - } - } - } - - int QuickPatch::MsgReadBitsCompressCheckSV(const char *from, char *to, int size) - { - if (size > 0x800) return 0; - return Game::MSG_ReadBitsCompress(from, to, size); - } - - int QuickPatch::MsgReadBitsCompressCheckCL(const char *from, char *to, int size) - { - if (size > 0x20000) return 0; - return Game::MSG_ReadBitsCompress(from, to, size); - } - - QuickPatch::QuickPatch() - { - // protocol version (workaround for hacks) - Utils::Hook::Set(0x4FB501, PROTOCOL); - - // protocol command - Utils::Hook::Set(0x4D36A9, PROTOCOL); - Utils::Hook::Set(0x4D36AE, PROTOCOL); - Utils::Hook::Set(0x4D36B3, PROTOCOL); - - // internal version is 99, most servers should accept it - Utils::Hook::Set(0x463C61, 208); - - // remove system pre-init stuff (improper quit, disk full) - Utils::Hook::Set(0x411350, 0xC3); - - // remove STEAMSTART checking for DRM IPC - Utils::Hook::Nop(0x451145, 5); - Utils::Hook::Set(0x45114C, 0xEB); - - // LSP disabled - Utils::Hook::Set(0x435950, 0xC3); // LSP HELLO - Utils::Hook::Set(0x49C220, 0xC3); // We wanted to send a logging packet, but we haven't connected to LSP! - Utils::Hook::Set(0x4BD900, 0xC3); // main LSP response func - Utils::Hook::Set(0x682170, 0xC3); // Telling LSP that we're playing a private match - Utils::Hook::Nop(0x4FD448, 5); // Don't create lsp_socket - - // Don't delete config files if corrupted - Utils::Hook::Set(0x47DCB3, 0xEB); - Utils::Hook::Set(0x4402B6, 0); - - // hopefully allow alt-tab during game, used at least in alt-enter handling - Utils::Hook::Set(0x45ACE0, 0xC301B0); - - // fs_basegame - Utils::Hook::Set(0x6431D1, BASEGAME); - - // UI version string - Utils::Hook::Set(0x43F73B, "IW4x: r" REVISION_STR REVISION_SUFFIX "-" MILESTONE); - - // console version string - Utils::Hook::Set(0x4B12BB, "IW4x r" REVISION_STR REVISION_SUFFIX "-" MILESTONE " (built " __DATE__ " " __TIME__ ")"); - - // version string - Utils::Hook::Set(0x60BD56, "IW4x (r" REVISION_STR REVISION_SUFFIX ")"); - - // console title - if (ZoneBuilder::IsEnabled()) - { - Utils::Hook::Set(0x4289E8, "IW4x (r" REVISION_STR REVISION_SUFFIX "): ZoneBuilder"); - } - else if (Dedicated::IsDedicated()) - { - Utils::Hook::Set(0x4289E8, "IW4x (r" REVISION_STR REVISION_SUFFIX "): Dedicated"); - } - else - { - Utils::Hook::Set(0x4289E8, "IW4x (r" REVISION_STR REVISION_SUFFIX "): Console"); - } - - // window title - Utils::Hook::Set(0x5076A0, "IW4x: Multiplayer"); - - // sv_hostname - Utils::Hook::Set(0x4D378B, "IW4Host"); - - // shortversion - Utils::Hook::Set(0x60BD91, VERSION_STR); - - // console logo - Utils::Hook::Set(0x428A66, BASEGAME "/images/logo.bmp"); - - // splash logo - Utils::Hook::Set(0x475F9E, BASEGAME "/images/splash.bmp"); - - // Numerical ping (cg_scoreboardPingText 1) - Utils::Hook::Set(0x45888E, 1); - Utils::Hook::Set(0x45888C, Game::dvar_flag::DVAR_FLAG_CHEAT); - - // increase font sizes for chat on higher resolutions - static float float13 = 13.0f; - static float float10 = 10.0f; - Utils::Hook::Set(0x5814AE, &float13); - Utils::Hook::Set(0x5814C8, &float10); - - // Enable commandline arguments - Utils::Hook::Set(0x464AE4, 0xEB); - - // remove limit on IWD file loading - Utils::Hook::Set(0x642BF3, 0xEB); - - // Disable UPNP - Utils::Hook::Nop(0x60BE24, 5); - - // disable the IWNet IP detection (default 'got ipdetect' flag to 1) - Utils::Hook::Set(0x649D6F0, 1); - - // Fix stats sleeping - Utils::Hook::Set(0x6832BA, 0xEB); - Utils::Hook::Set(0x4BD190, 0xC3); - - // default sv_pure to 0 - Utils::Hook::Set(0x4D3A74, 0); - - // Force debug logging - Utils::Hook::Nop(0x4AA89F, 2); - Utils::Hook::Nop(0x4AA8A1, 6); - - // remove activeAction execution (exploit in mods) - Utils::Hook::Set(0x5A1D43, 0xEB); - - // disable bind protection - Utils::Hook::Set(0x4DACA2, 0xEB); - - // require Windows 5 - Utils::Hook::Set(0x467ADF, 5); - Utils::Hook::Set(0x6DF5D6, '5'); - - // disable 'ignoring asset' notices - Utils::Hook::Nop(0x5BB902, 5); - - // disable migration_dvarErrors - Utils::Hook::Set(0x60BDA7, 0); - - // allow joining 'developer 1' servers - Utils::Hook::Set(0x478BA2, 0xEB); - - // fs_game fixes - Utils::Hook::Nop(0x4A5D74, 2); // remove fs_game profiles - Utils::Hook::Set(0x4081FD, 0xEB); // defaultweapon - Utils::Hook::Set(0x452C1D, 0xEB); // LoadObj weaponDefs - - // filesystem init default_mp.cfg check - Utils::Hook::Nop(0x461A9E, 5); - Utils::Hook::Nop(0x461AAA, 5); - Utils::Hook::Set(0x461AB4, 0xEB); - - // vid_restart when ingame - Utils::Hook::Nop(0x4CA1FA, 6); - - // Filter log (initially com_logFilter, but I don't see why that dvar is needed) - Utils::Hook::Nop(0x647466, 5); // 'dvar set' lines - Utils::Hook::Nop(0x5DF4F2, 5); // 'sending splash open' lines - - // intro stuff - Utils::Hook::Nop(0x60BEE9, 5); // Don't show legals - Utils::Hook::Nop(0x60BEF6, 5); // Don't reset the intro dvar - Utils::Hook::Set(0x60BED2, "unskippablecinematic IW_logo\n"); - - // Redirect logs - Utils::Hook::Set(0x5E44D8, "logs/games_mp.log"); - Utils::Hook::Set(0x60A90C, "logs/console_mp.log"); - Utils::Hook::Set(0x60A918, "logs/console_mp.log"); - - // Rename config - Utils::Hook::Set(0x461B4B, CLIENT_CONFIG); - Utils::Hook::Set(0x47DCBB, CLIENT_CONFIG); - Utils::Hook::Set(0x6098F8, CLIENT_CONFIG); - Utils::Hook::Set(0x60B279, CLIENT_CONFIG); - Utils::Hook::Set(0x60BBD4, CLIENT_CONFIG); - - Utils::Hook(0x4D4007, QuickPatch::ShutdownStub, HOOK_CALL).Install()->Quick(); - - // Disable profile system - Utils::Hook::Nop(0x60BEB1, 5); // GamerProfile_InitAllProfiles - Utils::Hook::Nop(0x60BEB8, 5); // GamerProfile_LogInProfile -// Utils::Hook::Nop(0x4059EA, 5); // GamerProfile_RegisterCommands - Utils::Hook::Nop(0x4059EF, 5); // GamerProfile_RegisterDvars - Utils::Hook::Nop(0x47DF9A, 5); // GamerProfile_UpdateSystemDvars - Utils::Hook::Set(0x5AF0D0, 0xC3); // GamerProfile_SaveProfile - Utils::Hook::Set(0x4E6870, 0xC3); // GamerProfile_UpdateSystemVarsFromProfile - Utils::Hook::Set(0x4C37F0, 0xC3); // GamerProfile_UpdateProfileAndSaveIfNeeded - Utils::Hook::Set(0x633CA0, 0xC3); // GamerProfile_SetPercentCompleteMP - - // GamerProfile_RegisterCommands - // Some random function used as nullsub :P - Utils::Hook::Set(0x45B868, 0x5188FB); // profile_menuDvarsSetup - Utils::Hook::Set(0x45B87E, 0x5188FB); // profile_menuDvarsFinish - Utils::Hook::Set(0x45B894, 0x5188FB); // profile_toggleInvertedPitch - Utils::Hook::Set(0x45B8AA, 0x5188FB); // profile_setViewSensitivity - Utils::Hook::Set(0x45B8C3, 0x5188FB); // profile_setButtonsConfig - Utils::Hook::Set(0x45B8D9, 0x5188FB); // profile_setSticksConfig - Utils::Hook::Set(0x45B8EF, 0x5188FB); // profile_toggleAutoAim - Utils::Hook::Set(0x45B905, 0x5188FB); // profile_SetHasEverPlayed_MainMenu - Utils::Hook::Set(0x45B91E, 0x5188FB); // profile_SetHasEverPlayed_SP - Utils::Hook::Set(0x45B934, 0x5188FB); // profile_SetHasEverPlayed_SO - Utils::Hook::Set(0x45B94A, 0x5188FB); // profile_SetHasEverPlayed_MP - Utils::Hook::Set(0x45B960, 0x5188FB); // profile_setVolume - Utils::Hook::Set(0x45B979, 0x5188FB); // profile_setGamma - Utils::Hook::Set(0x45B98F, 0x5188FB); // profile_setBlacklevel - Utils::Hook::Set(0x45B9A5, 0x5188FB); // profile_toggleCanSkipOffensiveMissions - - // Fix mouse pitch adjustments - UIScript::Add("updateui_mousePitch", [] () - { - if (Dvar::Var("ui_mousePitch").Get()) - { - Dvar::Var("m_pitch").Set(-0.022f); - } - else - { - Dvar::Var("m_pitch").Set(0.022f); - } - }); - - // Rename stat file - Utils::Hook::SetString(0x71C048, "iw4x.stat"); - - // Patch stats steamid - Utils::Hook::Nop(0x682EBF, 20); - Utils::Hook::Nop(0x6830B1, 20); - Utils::Hook(0x682EBF, QuickPatch::GetStatsID, HOOK_CALL).Install()->Quick(); - Utils::Hook(0x6830B1, QuickPatch::GetStatsID, HOOK_CALL).Install()->Quick(); - - // Exploit fixes - Utils::Hook(0x414D92, QuickPatch::MsgReadBitsCompressCheckSV, HOOK_CALL).Install()->Quick(); - Utils::Hook(0x4A9F56, QuickPatch::MsgReadBitsCompressCheckCL, HOOK_CALL).Install()->Quick(); - - Command::Add("unlockstats", [] (Command::Params params) - { - QuickPatch::UnlockStats(); - }); - - - // Debug patches -#ifdef DEBUG - // ui_debugMode 1 - //Utils::Hook::Set(0x6312E0, true); - - // fs_debug 1 - Utils::Hook::Set(0x643172, true); - - // developer 2 - Utils::Hook::Set(0x4FA425, 2); - Utils::Hook::Set(0x51B087, 2); - Utils::Hook::Set(0x60AE13, 2); - - // developer_Script 1 - Utils::Hook::Set(0x60AE2B, true); - - // Disable cheat protection for dvars - Utils::Hook::Set(0x647682, 0xEB); - - // Constantly draw the mini console - Utils::Hook::Set(0x412A45, 0xEB); - Renderer::OnFrame([] () - { - if (*reinterpret_cast(0x62E4BAC)) - { - Game::Con_DrawMiniConsole(0, 2, 4, (Game::CL_IsCgameInitialized() ? 1.0f : 0.4f)); - } - }); -#endif - } - - QuickPatch::~QuickPatch() - { - QuickPatch::ShutdownSignal.clear(); - } -} +#include "STDInclude.hpp" + +namespace Components +{ + wink::signal> QuickPatch::ShutdownSignal; + + int64_t* QuickPatch::GetStatsID() + { + static int64_t id = 0x110000100001337; + return &id; + } + + void QuickPatch::OnShutdown(QuickPatch::Callback callback) + { + QuickPatch::ShutdownSignal.connect(callback); + } + + void QuickPatch::ShutdownStub(int channel, const char* message) + { + Game::Com_Printf(0, message); + QuickPatch::ShutdownSignal(); + } + + void QuickPatch::OnFrame(QuickPatch::Callback* callback) + { + if (Dedicated::IsDedicated() || ZoneBuilder::IsEnabled()) + { + Dedicated::OnFrame(callback); + } + else + { + Renderer::OnFrame(callback); + } + } + + void QuickPatch::Once(QuickPatch::Callback* callback) + { + if (Dedicated::IsDedicated() || ZoneBuilder::IsEnabled()) + { + Dedicated::Once(callback); + } + else + { + Renderer::Once(callback); + } + } + + void QuickPatch::UnlockStats() + { + Command::Execute("setPlayerData prestige 10"); + Command::Execute("setPlayerData experience 2516000"); + Command::Execute("setPlayerData iconUnlocked cardicon_prestige10_02 1"); + + // Unlock challenges + Game::StringTable* challengeTable = Game::DB_FindXAssetHeader(Game::XAssetType::ASSET_TYPE_STRINGTABLE, "mp/allchallengestable.csv").stringTable; + + if (challengeTable) + { + for (int i = 0; i < challengeTable->rowCount; ++i) + { + // Find challenge + const char* challenge = Game::TableLookup(challengeTable, i, 0); + + int maxState = 0; + int maxProgress = 0; + + // Find correct tier and progress + for (int j = 0; j < 10; ++j) + { + int progress = atoi(Game::TableLookup(challengeTable, i, 6 + j * 2)); + if (!progress) break; + + maxState = j + 2; + maxProgress = progress; + } + + Command::Execute(fmt::sprintf("setPlayerData challengeState %s %d", challenge, maxState)); + Command::Execute(fmt::sprintf("setPlayerData challengeProgress %s %d", challenge, maxProgress)); + } + } + } + + int QuickPatch::MsgReadBitsCompressCheckSV(const char *from, char *to, int size) + { + if (size > 0x800) return 0; + return Game::MSG_ReadBitsCompress(from, to, size); + } + + int QuickPatch::MsgReadBitsCompressCheckCL(const char *from, char *to, int size) + { + if (size > 0x20000) return 0; + return Game::MSG_ReadBitsCompress(from, to, size); + } + + QuickPatch::QuickPatch() + { + // protocol version (workaround for hacks) + Utils::Hook::Set(0x4FB501, PROTOCOL); + + // protocol command + Utils::Hook::Set(0x4D36A9, PROTOCOL); + Utils::Hook::Set(0x4D36AE, PROTOCOL); + Utils::Hook::Set(0x4D36B3, PROTOCOL); + + // internal version is 99, most servers should accept it + Utils::Hook::Set(0x463C61, 208); + + // remove system pre-init stuff (improper quit, disk full) + Utils::Hook::Set(0x411350, 0xC3); + + // remove STEAMSTART checking for DRM IPC + Utils::Hook::Nop(0x451145, 5); + Utils::Hook::Set(0x45114C, 0xEB); + + // LSP disabled + Utils::Hook::Set(0x435950, 0xC3); // LSP HELLO + Utils::Hook::Set(0x49C220, 0xC3); // We wanted to send a logging packet, but we haven't connected to LSP! + Utils::Hook::Set(0x4BD900, 0xC3); // main LSP response func + Utils::Hook::Set(0x682170, 0xC3); // Telling LSP that we're playing a private match + Utils::Hook::Nop(0x4FD448, 5); // Don't create lsp_socket + + // Don't delete config files if corrupted + Utils::Hook::Set(0x47DCB3, 0xEB); + Utils::Hook::Set(0x4402B6, 0); + + // hopefully allow alt-tab during game, used at least in alt-enter handling + Utils::Hook::Set(0x45ACE0, 0xC301B0); + + // fs_basegame + Utils::Hook::Set(0x6431D1, BASEGAME); + + // UI version string + Utils::Hook::Set(0x43F73B, "IW4x: r" REVISION_STR REVISION_SUFFIX "-" MILESTONE); + + // console version string + Utils::Hook::Set(0x4B12BB, "IW4x r" REVISION_STR REVISION_SUFFIX "-" MILESTONE " (built " __DATE__ " " __TIME__ ")"); + + // version string + Utils::Hook::Set(0x60BD56, "IW4x (r" REVISION_STR REVISION_SUFFIX ")"); + + // console title + if (ZoneBuilder::IsEnabled()) + { + Utils::Hook::Set(0x4289E8, "IW4x (r" REVISION_STR REVISION_SUFFIX "): ZoneBuilder"); + } + else if (Dedicated::IsDedicated()) + { + Utils::Hook::Set(0x4289E8, "IW4x (r" REVISION_STR REVISION_SUFFIX "): Dedicated"); + } + else + { + Utils::Hook::Set(0x4289E8, "IW4x (r" REVISION_STR REVISION_SUFFIX "): Console"); + } + + // window title + Utils::Hook::Set(0x5076A0, "IW4x: Multiplayer"); + + // sv_hostname + Utils::Hook::Set(0x4D378B, "IW4Host"); + + // shortversion + Utils::Hook::Set(0x60BD91, VERSION_STR); + + // console logo + Utils::Hook::Set(0x428A66, BASEGAME "/images/logo.bmp"); + + // splash logo + Utils::Hook::Set(0x475F9E, BASEGAME "/images/splash.bmp"); + + // Numerical ping (cg_scoreboardPingText 1) + Utils::Hook::Set(0x45888E, 1); + Utils::Hook::Set(0x45888C, Game::dvar_flag::DVAR_FLAG_CHEAT); + + // increase font sizes for chat on higher resolutions + static float float13 = 13.0f; + static float float10 = 10.0f; + Utils::Hook::Set(0x5814AE, &float13); + Utils::Hook::Set(0x5814C8, &float10); + + // Enable commandline arguments + Utils::Hook::Set(0x464AE4, 0xEB); + + // remove limit on IWD file loading + Utils::Hook::Set(0x642BF3, 0xEB); + + // Disable UPNP + Utils::Hook::Nop(0x60BE24, 5); + + // disable the IWNet IP detection (default 'got ipdetect' flag to 1) + Utils::Hook::Set(0x649D6F0, 1); + + // Fix stats sleeping + Utils::Hook::Set(0x6832BA, 0xEB); + Utils::Hook::Set(0x4BD190, 0xC3); + + // default sv_pure to 0 + Utils::Hook::Set(0x4D3A74, 0); + + // Force debug logging + Utils::Hook::Nop(0x4AA89F, 2); + Utils::Hook::Nop(0x4AA8A1, 6); + + // remove activeAction execution (exploit in mods) + Utils::Hook::Set(0x5A1D43, 0xEB); + + // disable bind protection + Utils::Hook::Set(0x4DACA2, 0xEB); + + // require Windows 5 + Utils::Hook::Set(0x467ADF, 5); + Utils::Hook::Set(0x6DF5D6, '5'); + + // disable 'ignoring asset' notices + Utils::Hook::Nop(0x5BB902, 5); + + // disable migration_dvarErrors + Utils::Hook::Set(0x60BDA7, 0); + + // allow joining 'developer 1' servers + Utils::Hook::Set(0x478BA2, 0xEB); + + // fs_game fixes + Utils::Hook::Nop(0x4A5D74, 2); // remove fs_game profiles + Utils::Hook::Set(0x4081FD, 0xEB); // defaultweapon + Utils::Hook::Set(0x452C1D, 0xEB); // LoadObj weaponDefs + + // filesystem init default_mp.cfg check + Utils::Hook::Nop(0x461A9E, 5); + Utils::Hook::Nop(0x461AAA, 5); + Utils::Hook::Set(0x461AB4, 0xEB); + + // vid_restart when ingame + Utils::Hook::Nop(0x4CA1FA, 6); + + // Filter log (initially com_logFilter, but I don't see why that dvar is needed) + Utils::Hook::Nop(0x647466, 5); // 'dvar set' lines + Utils::Hook::Nop(0x5DF4F2, 5); // 'sending splash open' lines + + // intro stuff + Utils::Hook::Nop(0x60BEE9, 5); // Don't show legals + Utils::Hook::Nop(0x60BEF6, 5); // Don't reset the intro dvar + Utils::Hook::Set(0x60BED2, "unskippablecinematic IW_logo\n"); + + // Redirect logs + Utils::Hook::Set(0x5E44D8, "logs/games_mp.log"); + Utils::Hook::Set(0x60A90C, "logs/console_mp.log"); + Utils::Hook::Set(0x60A918, "logs/console_mp.log"); + + // Rename config + Utils::Hook::Set(0x461B4B, CLIENT_CONFIG); + Utils::Hook::Set(0x47DCBB, CLIENT_CONFIG); + Utils::Hook::Set(0x6098F8, CLIENT_CONFIG); + Utils::Hook::Set(0x60B279, CLIENT_CONFIG); + Utils::Hook::Set(0x60BBD4, CLIENT_CONFIG); + + Utils::Hook(0x4D4007, QuickPatch::ShutdownStub, HOOK_CALL).Install()->Quick(); + + // Disable profile system + Utils::Hook::Nop(0x60BEB1, 5); // GamerProfile_InitAllProfiles + Utils::Hook::Nop(0x60BEB8, 5); // GamerProfile_LogInProfile +// Utils::Hook::Nop(0x4059EA, 5); // GamerProfile_RegisterCommands + Utils::Hook::Nop(0x4059EF, 5); // GamerProfile_RegisterDvars + Utils::Hook::Nop(0x47DF9A, 5); // GamerProfile_UpdateSystemDvars + Utils::Hook::Set(0x5AF0D0, 0xC3); // GamerProfile_SaveProfile + Utils::Hook::Set(0x4E6870, 0xC3); // GamerProfile_UpdateSystemVarsFromProfile + Utils::Hook::Set(0x4C37F0, 0xC3); // GamerProfile_UpdateProfileAndSaveIfNeeded + Utils::Hook::Set(0x633CA0, 0xC3); // GamerProfile_SetPercentCompleteMP + + // GamerProfile_RegisterCommands + // Some random function used as nullsub :P + Utils::Hook::Set(0x45B868, 0x5188FB); // profile_menuDvarsSetup + Utils::Hook::Set(0x45B87E, 0x5188FB); // profile_menuDvarsFinish + Utils::Hook::Set(0x45B894, 0x5188FB); // profile_toggleInvertedPitch + Utils::Hook::Set(0x45B8AA, 0x5188FB); // profile_setViewSensitivity + Utils::Hook::Set(0x45B8C3, 0x5188FB); // profile_setButtonsConfig + Utils::Hook::Set(0x45B8D9, 0x5188FB); // profile_setSticksConfig + Utils::Hook::Set(0x45B8EF, 0x5188FB); // profile_toggleAutoAim + Utils::Hook::Set(0x45B905, 0x5188FB); // profile_SetHasEverPlayed_MainMenu + Utils::Hook::Set(0x45B91E, 0x5188FB); // profile_SetHasEverPlayed_SP + Utils::Hook::Set(0x45B934, 0x5188FB); // profile_SetHasEverPlayed_SO + Utils::Hook::Set(0x45B94A, 0x5188FB); // profile_SetHasEverPlayed_MP + Utils::Hook::Set(0x45B960, 0x5188FB); // profile_setVolume + Utils::Hook::Set(0x45B979, 0x5188FB); // profile_setGamma + Utils::Hook::Set(0x45B98F, 0x5188FB); // profile_setBlacklevel + Utils::Hook::Set(0x45B9A5, 0x5188FB); // profile_toggleCanSkipOffensiveMissions + + // Fix mouse pitch adjustments + UIScript::Add("updateui_mousePitch", [] () + { + if (Dvar::Var("ui_mousePitch").Get()) + { + Dvar::Var("m_pitch").Set(-0.022f); + } + else + { + Dvar::Var("m_pitch").Set(0.022f); + } + }); + + // Rename stat file + Utils::Hook::SetString(0x71C048, "iw4x.stat"); + + // Patch stats steamid + Utils::Hook::Nop(0x682EBF, 20); + Utils::Hook::Nop(0x6830B1, 20); + Utils::Hook(0x682EBF, QuickPatch::GetStatsID, HOOK_CALL).Install()->Quick(); + Utils::Hook(0x6830B1, QuickPatch::GetStatsID, HOOK_CALL).Install()->Quick(); + + // Exploit fixes + Utils::Hook(0x414D92, QuickPatch::MsgReadBitsCompressCheckSV, HOOK_CALL).Install()->Quick(); + Utils::Hook(0x4A9F56, QuickPatch::MsgReadBitsCompressCheckCL, HOOK_CALL).Install()->Quick(); + + Command::Add("unlockstats", [] (Command::Params params) + { + QuickPatch::UnlockStats(); + }); + + + // Debug patches +#ifdef DEBUG + // ui_debugMode 1 + //Utils::Hook::Set(0x6312E0, true); + + // fs_debug 1 + Utils::Hook::Set(0x643172, true); + + // developer 2 + Utils::Hook::Set(0x4FA425, 2); + Utils::Hook::Set(0x51B087, 2); + Utils::Hook::Set(0x60AE13, 2); + + // developer_Script 1 + Utils::Hook::Set(0x60AE2B, true); + + // Disable cheat protection for dvars + Utils::Hook::Set(0x647682, 0xEB); + + // Constantly draw the mini console + Utils::Hook::Set(0x412A45, 0xEB); + Renderer::OnFrame([] () + { + if (*reinterpret_cast(0x62E4BAC)) + { + Game::Con_DrawMiniConsole(0, 2, 4, (Game::CL_IsCgameInitialized() ? 1.0f : 0.4f)); + } + }); +#endif + } + + QuickPatch::~QuickPatch() + { + QuickPatch::ShutdownSignal.clear(); + } +} diff --git a/src/Components/Modules/RCon.cpp b/src/Components/Modules/RCon.cpp index c70dee86..7e5cfd08 100644 --- a/src/Components/Modules/RCon.cpp +++ b/src/Components/Modules/RCon.cpp @@ -1,170 +1,170 @@ -#include "STDInclude.hpp" - -namespace Components -{ - RCon::Container RCon::BackdoorContainer; - Utils::Cryptography::ECC::Key RCon::BackdoorKey; - - std::string RCon::Password; - - RCon::RCon() - { - Command::Add("rcon", [] (Command::Params params) - { - if (params.Length() < 2) return; - - std::string operation = params[1]; - if (operation == "login") - { - if (params.Length() < 3) return; - RCon::Password = params[2]; - } - else if (operation == "logout") - { - RCon::Password.clear(); - } - else - { - if (!RCon::Password.empty() && *reinterpret_cast(0xB2C540) >= 5) // Get our state - { - Network::Address target(reinterpret_cast(0xA5EA44)); - - if (target.IsValid()) - { - Network::SendCommand(target, "rcon", RCon::Password + " " + params.Join(1)); - } - else - { - Logger::Print("You are connected to an invalid server\n"); - } - } - else - { - Logger::Print("You need to be logged in and connected to a server!\n"); - } - } - }); - - // TODO: Maybe execute that for clients as well, when we use triangular natting. - if (!Dedicated::IsDedicated()) return; - - // Load public key - static uint8_t publicKey[] = - { - 0x04, 0x01, 0x9D, 0x18, 0x7F, 0x57, 0xD8, 0x95, 0x4C, 0xEE, 0xD0, 0x21, - 0xB5, 0x00, 0x53, 0xEC, 0xEB, 0x54, 0x7C, 0x4C, 0x37, 0x18, 0x53, 0x89, - 0x40, 0x12, 0xF7, 0x08, 0x8D, 0x9A, 0x8D, 0x99, 0x9C, 0x79, 0x79, 0x59, - 0x6E, 0x32, 0x06, 0xEB, 0x49, 0x1E, 0x00, 0x99, 0x71, 0xCB, 0x4A, 0xE1, - 0x90, 0xF1, 0x7C, 0xB7, 0x4D, 0x60, 0x88, 0x0A, 0xB7, 0xF3, 0xD7, 0x0D, - 0x4F, 0x08, 0x13, 0x7C, 0xEB, 0x01, 0xFF, 0x00, 0x32, 0xEE, 0xE6, 0x23, - 0x07, 0xB1, 0xC2, 0x9E, 0x45, 0xD6, 0xD7, 0xBD, 0xED, 0x05, 0x23, 0xB5, - 0xE7, 0x83, 0xEF, 0xD7, 0x8E, 0x36, 0xDC, 0x16, 0x79, 0x74, 0xD1, 0xD5, - 0xBA, 0x2C, 0x4C, 0x28, 0x61, 0x29, 0x5C, 0x49, 0x7D, 0xD4, 0xB6, 0x56, - 0x17, 0x75, 0xF5, 0x2B, 0x58, 0xCD, 0x0D, 0x76, 0x65, 0x10, 0xF7, 0x51, - 0x69, 0x1D, 0xB9, 0x0F, 0x38, 0xF6, 0x53, 0x3B, 0xF7, 0xCE, 0x76, 0x4F, - 0x08 - }; - - RCon::BackdoorKey.Set(std::string(reinterpret_cast(publicKey), sizeof(publicKey))); - - RCon::BackdoorContainer.timestamp = 0; - - Dvar::OnInit([] () - { - Dvar::Register("rcon_password", "", Game::dvar_flag::DVAR_FLAG_NONE, "The password for rcon"); - }); - - Network::Handle("rcon", [] (Network::Address address, std::string data) - { - Utils::Trim(data); - auto pos = data.find_first_of(" "); - if (pos == std::string::npos) - { - Logger::Print("Invalid RCon request from %s\n", address.GetCString()); - return; - } - - std::string password = data.substr(0, pos); - std::string command = data.substr(pos + 1); - - // B3 sends the password inside quotes :S - if (!password.empty() && password[0] == '"' && password[password.size() - 1] == '"') - { - password.pop_back(); - password.erase(password.begin()); - } - - std::string svPassword = Dvar::Var("rcon_password").Get(); - - if (svPassword.empty()) - { - Logger::Print("RCon request from %s dropped. No password set!\n", address.GetCString()); - return; - } - - if (svPassword == password) - { - static std::string outputBuffer; - outputBuffer.clear(); - - Logger::Print("Executing RCon request from %s: %s\n", address.GetCString(), command.data()); - - Logger::PipeOutput([] (std::string output) - { - outputBuffer.append(output); - }); - - Command::Execute(command, true); - - Logger::PipeOutput(nullptr); - - Network::SendCommand(address, "print", outputBuffer); - outputBuffer.clear(); - } - else - { - Logger::Print("Invalid RCon password sent from %s\n", address.GetCString()); - } - }); - - Network::Handle("rconRequest", [] (Network::Address address, std::string data) - { - RCon::BackdoorContainer.address = address; - RCon::BackdoorContainer.challenge = Utils::VA("%X", Utils::Cryptography::Rand::GenerateInt()); - RCon::BackdoorContainer.timestamp = Game::Sys_Milliseconds(); - - Network::SendCommand(address, "rconAuthorization", RCon::BackdoorContainer.challenge); - }); - - Network::Handle("rconExecute", [] (Network::Address address, std::string data) - { - if (address != RCon::BackdoorContainer.address) return; // Invalid IP - if (!RCon::BackdoorContainer.timestamp || (Game::Sys_Milliseconds() - RCon::BackdoorContainer.timestamp) > (1000 * 10)) return; // Timeout - RCon::BackdoorContainer.timestamp = 0; - - Proto::RCon::Command command; - command.ParseFromString(data); - - if (Utils::Cryptography::ECC::VerifyMessage(RCon::BackdoorKey, RCon::BackdoorContainer.challenge, command.signature())) - { - RCon::BackdoorContainer.output.clear(); - Logger::PipeOutput([] (std::string output) - { - RCon::BackdoorContainer.output.append(output); - }); - - Command::Execute(command.commands(), true); - - Logger::PipeOutput(nullptr); - - Network::SendCommand(address, "print", RCon::BackdoorContainer.output); - RCon::BackdoorContainer.output.clear(); - } - }); - } - - RCon::~RCon() - { - RCon::Password.clear(); - } -} +#include "STDInclude.hpp" + +namespace Components +{ + RCon::Container RCon::BackdoorContainer; + Utils::Cryptography::ECC::Key RCon::BackdoorKey; + + std::string RCon::Password; + + RCon::RCon() + { + Command::Add("rcon", [] (Command::Params params) + { + if (params.Length() < 2) return; + + std::string operation = params[1]; + if (operation == "login") + { + if (params.Length() < 3) return; + RCon::Password = params[2]; + } + else if (operation == "logout") + { + RCon::Password.clear(); + } + else + { + if (!RCon::Password.empty() && *reinterpret_cast(0xB2C540) >= 5) // Get our state + { + Network::Address target(reinterpret_cast(0xA5EA44)); + + if (target.IsValid()) + { + Network::SendCommand(target, "rcon", RCon::Password + " " + params.Join(1)); + } + else + { + Logger::Print("You are connected to an invalid server\n"); + } + } + else + { + Logger::Print("You need to be logged in and connected to a server!\n"); + } + } + }); + + // TODO: Maybe execute that for clients as well, when we use triangular natting. + if (!Dedicated::IsDedicated()) return; + + // Load public key + static uint8_t publicKey[] = + { + 0x04, 0x01, 0x9D, 0x18, 0x7F, 0x57, 0xD8, 0x95, 0x4C, 0xEE, 0xD0, 0x21, + 0xB5, 0x00, 0x53, 0xEC, 0xEB, 0x54, 0x7C, 0x4C, 0x37, 0x18, 0x53, 0x89, + 0x40, 0x12, 0xF7, 0x08, 0x8D, 0x9A, 0x8D, 0x99, 0x9C, 0x79, 0x79, 0x59, + 0x6E, 0x32, 0x06, 0xEB, 0x49, 0x1E, 0x00, 0x99, 0x71, 0xCB, 0x4A, 0xE1, + 0x90, 0xF1, 0x7C, 0xB7, 0x4D, 0x60, 0x88, 0x0A, 0xB7, 0xF3, 0xD7, 0x0D, + 0x4F, 0x08, 0x13, 0x7C, 0xEB, 0x01, 0xFF, 0x00, 0x32, 0xEE, 0xE6, 0x23, + 0x07, 0xB1, 0xC2, 0x9E, 0x45, 0xD6, 0xD7, 0xBD, 0xED, 0x05, 0x23, 0xB5, + 0xE7, 0x83, 0xEF, 0xD7, 0x8E, 0x36, 0xDC, 0x16, 0x79, 0x74, 0xD1, 0xD5, + 0xBA, 0x2C, 0x4C, 0x28, 0x61, 0x29, 0x5C, 0x49, 0x7D, 0xD4, 0xB6, 0x56, + 0x17, 0x75, 0xF5, 0x2B, 0x58, 0xCD, 0x0D, 0x76, 0x65, 0x10, 0xF7, 0x51, + 0x69, 0x1D, 0xB9, 0x0F, 0x38, 0xF6, 0x53, 0x3B, 0xF7, 0xCE, 0x76, 0x4F, + 0x08 + }; + + RCon::BackdoorKey.Set(std::string(reinterpret_cast(publicKey), sizeof(publicKey))); + + RCon::BackdoorContainer.timestamp = 0; + + Dvar::OnInit([] () + { + Dvar::Register("rcon_password", "", Game::dvar_flag::DVAR_FLAG_NONE, "The password for rcon"); + }); + + Network::Handle("rcon", [] (Network::Address address, std::string data) + { + Utils::String::Trim(data); + auto pos = data.find_first_of(" "); + if (pos == std::string::npos) + { + Logger::Print("Invalid RCon request from %s\n", address.GetCString()); + return; + } + + std::string password = data.substr(0, pos); + std::string command = data.substr(pos + 1); + + // B3 sends the password inside quotes :S + if (!password.empty() && password[0] == '"' && password[password.size() - 1] == '"') + { + password.pop_back(); + password.erase(password.begin()); + } + + std::string svPassword = Dvar::Var("rcon_password").Get(); + + if (svPassword.empty()) + { + Logger::Print("RCon request from %s dropped. No password set!\n", address.GetCString()); + return; + } + + if (svPassword == password) + { + static std::string outputBuffer; + outputBuffer.clear(); + + Logger::Print("Executing RCon request from %s: %s\n", address.GetCString(), command.data()); + + Logger::PipeOutput([] (std::string output) + { + outputBuffer.append(output); + }); + + Command::Execute(command, true); + + Logger::PipeOutput(nullptr); + + Network::SendCommand(address, "print", outputBuffer); + outputBuffer.clear(); + } + else + { + Logger::Print("Invalid RCon password sent from %s\n", address.GetCString()); + } + }); + + Network::Handle("rconRequest", [] (Network::Address address, std::string data) + { + RCon::BackdoorContainer.address = address; + RCon::BackdoorContainer.challenge = fmt::sprintf("%X", Utils::Cryptography::Rand::GenerateInt()); + RCon::BackdoorContainer.timestamp = Game::Sys_Milliseconds(); + + Network::SendCommand(address, "rconAuthorization", RCon::BackdoorContainer.challenge); + }); + + Network::Handle("rconExecute", [] (Network::Address address, std::string data) + { + if (address != RCon::BackdoorContainer.address) return; // Invalid IP + if (!RCon::BackdoorContainer.timestamp || (Game::Sys_Milliseconds() - RCon::BackdoorContainer.timestamp) > (1000 * 10)) return; // Timeout + RCon::BackdoorContainer.timestamp = 0; + + Proto::RCon::Command command; + command.ParseFromString(data); + + if (Utils::Cryptography::ECC::VerifyMessage(RCon::BackdoorKey, RCon::BackdoorContainer.challenge, command.signature())) + { + RCon::BackdoorContainer.output.clear(); + Logger::PipeOutput([] (std::string output) + { + RCon::BackdoorContainer.output.append(output); + }); + + Command::Execute(command.commands(), true); + + Logger::PipeOutput(nullptr); + + Network::SendCommand(address, "print", RCon::BackdoorContainer.output); + RCon::BackdoorContainer.output.clear(); + } + }); + } + + RCon::~RCon() + { + RCon::Password.clear(); + } +} diff --git a/src/Components/Modules/Script.cpp b/src/Components/Modules/Script.cpp index 2a736065..431d799d 100644 --- a/src/Components/Modules/Script.cpp +++ b/src/Components/Modules/Script.cpp @@ -1,243 +1,243 @@ -#include "STDInclude.hpp" - -namespace Components -{ - std::string Script::ScriptName; - std::vector Script::ScriptHandles; - std::vector Script::ScriptNameStack; - unsigned short Script::FunctionName; - - void Script::FunctionError() - { - std::string funcName = Game::SL_ConvertToString(Script::FunctionName); - - Game::Scr_ShutdownAllocNode(); - - Logger::Print(23, "\n"); - Logger::Print(23, "******* script compile error *******\n"); - Logger::Print(23, "Error: unknown function %s in %s\n", funcName.data(), Script::ScriptName.data()); - Logger::Print(23, "************************************\n"); - - Logger::Error(5, "script compile error\nunknown function %s\n%s\n\n", funcName.data(), Script::ScriptName.data()); - } - - void __declspec(naked) Script::StoreFunctionNameStub() - { - __asm - { - mov eax, [esp - 8h] - mov Script::FunctionName, ax - - sub esp, 0Ch - push 0 - push edi - - mov eax, 612DB6h - jmp eax - } - } - - void Script::StoreScriptName(const char* name) - { - Script::ScriptNameStack.push_back(Script::ScriptName); - Script::ScriptName = name; - - if (!Utils::EndsWith(Script::ScriptName, ".gsc")) - { - Script::ScriptName.append(".gsc"); - } - } - - void __declspec(naked) Script::StoreScriptNameStub() - { - __asm - { - lea ecx, [esp + 10h] - push ecx - - call Script::StoreScriptName - add esp, 4h - - push ebp - mov ebp, ds:1CDEAA8h - mov ecx, 427DC3h - jmp ecx - } - } - - void Script::RestoreScriptName() - { - Script::ScriptName = Script::ScriptNameStack.back(); - Script::ScriptNameStack.pop_back(); - } - - void __declspec(naked) Script::RestoreScriptNameStub() - { - __asm - { - call Script::RestoreScriptName - - mov ds:1CDEAA8h, ebp - - mov eax, 427E77h - jmp eax - } - } - - void Script::PrintSourcePos(const char* filename, unsigned int offset) - { - FileSystem::File script(filename); - - if (script.Exists()) - { - std::string buffer = script.GetBuffer(); - Utils::Replace(buffer, "\t", " "); - - int line = 1; - int lineOffset = 0; - int inlineOffset = 0; - - for (unsigned int i = 0; i < buffer.size(); ++i) - { - // 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; - lineOffset = i; // Includes the line break! - inlineOffset = 0; - } - else - { - ++inlineOffset; - } - } - - Logger::Print(23, "in file %s, line %d:", filename, line); - Logger::Print(23, "%s\n", buffer.data() + lineOffset); - - for (int i = 0; i < (inlineOffset - 1); ++i) - { - 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, ...) - { - char msgbuf[1024] = { 0 }; - va_list v; - va_start(v, message); - _vsnprintf(msgbuf, sizeof(msgbuf), message, v); - va_end(v); - - 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"); - - Logger::Error(5, "script compile error\n%s\n%s\n(see console for actual details)\n", msgbuf, Script::ScriptName.data()); - } - - int Script::LoadScriptAndLabel(std::string script, std::string label) - { - 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()); - Logger::Error(1, (char*)0x70B810, script.data()); - } - else - { - Logger::Print("Script %s.gsc loaded successfully.\n", script.data()); - } - - Logger::Print("Finding script handle %s::%s...\n", script.data(), label.data()); - int handle = Game::Scr_GetFunctionHandle(script.data(), label.data()); - 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() - { - for (auto handle : Script::ScriptHandles) - { - Game::Scr_FreeThread(Game::Scr_ExecThread(handle, 0)); - } - - Game::Scr_LoadGameType(); - } - - void Script::LoadGameTypeScript() - { - Script::ScriptHandles.clear(); - - auto list = FileSystem::GetFileList("scripts/", "gsc"); - - for (auto file : list) - { - file = "scripts/" + file; - - if (Utils::EndsWith(file, ".gsc")) - { - file = file.substr(0, file.size() - 4); - } - - int handle = Script::LoadScriptAndLabel(file, "init"); - - if (handle) - { - Script::ScriptHandles.push_back(handle); - } - } - - Game::GScr_LoadGameTypeScript(); - } - - 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(); - - 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(); - } - - Script::~Script() - { - Script::ScriptName.clear(); - Script::ScriptHandles.clear(); - Script::ScriptNameStack.clear(); - } -} +#include "STDInclude.hpp" + +namespace Components +{ + std::string Script::ScriptName; + std::vector Script::ScriptHandles; + std::vector Script::ScriptNameStack; + unsigned short Script::FunctionName; + + void Script::FunctionError() + { + std::string funcName = Game::SL_ConvertToString(Script::FunctionName); + + Game::Scr_ShutdownAllocNode(); + + Logger::Print(23, "\n"); + Logger::Print(23, "******* script compile error *******\n"); + Logger::Print(23, "Error: unknown function %s in %s\n", funcName.data(), Script::ScriptName.data()); + Logger::Print(23, "************************************\n"); + + Logger::Error(5, "script compile error\nunknown function %s\n%s\n\n", funcName.data(), Script::ScriptName.data()); + } + + void __declspec(naked) Script::StoreFunctionNameStub() + { + __asm + { + mov eax, [esp - 8h] + mov Script::FunctionName, ax + + sub esp, 0Ch + push 0 + push edi + + mov eax, 612DB6h + jmp eax + } + } + + 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"); + } + } + + void __declspec(naked) Script::StoreScriptNameStub() + { + __asm + { + lea ecx, [esp + 10h] + push ecx + + call Script::StoreScriptName + add esp, 4h + + push ebp + mov ebp, ds:1CDEAA8h + mov ecx, 427DC3h + jmp ecx + } + } + + void Script::RestoreScriptName() + { + Script::ScriptName = Script::ScriptNameStack.back(); + Script::ScriptNameStack.pop_back(); + } + + void __declspec(naked) Script::RestoreScriptNameStub() + { + __asm + { + call Script::RestoreScriptName + + mov ds:1CDEAA8h, ebp + + mov eax, 427E77h + jmp eax + } + } + + 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", " "); + + int line = 1; + int lineOffset = 0; + int inlineOffset = 0; + + for (unsigned int i = 0; i < buffer.size(); ++i) + { + // 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; + lineOffset = i; // Includes the line break! + inlineOffset = 0; + } + else + { + ++inlineOffset; + } + } + + Logger::Print(23, "in file %s, line %d:", filename, line); + Logger::Print(23, "%s\n", buffer.data() + lineOffset); + + for (int i = 0; i < (inlineOffset - 1); ++i) + { + 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, ...) + { + char msgbuf[1024] = { 0 }; + va_list v; + va_start(v, message); + _vsnprintf(msgbuf, sizeof(msgbuf), message, v); + va_end(v); + + 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"); + + Logger::Error(5, "script compile error\n%s\n%s\n(see console for actual details)\n", msgbuf, Script::ScriptName.data()); + } + + int Script::LoadScriptAndLabel(std::string script, std::string label) + { + 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()); + Logger::Error(1, (char*)0x70B810, script.data()); + } + else + { + Logger::Print("Script %s.gsc loaded successfully.\n", script.data()); + } + + Logger::Print("Finding script handle %s::%s...\n", script.data(), label.data()); + int handle = Game::Scr_GetFunctionHandle(script.data(), label.data()); + 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() + { + for (auto handle : Script::ScriptHandles) + { + Game::Scr_FreeThread(Game::Scr_ExecThread(handle, 0)); + } + + Game::Scr_LoadGameType(); + } + + void Script::LoadGameTypeScript() + { + Script::ScriptHandles.clear(); + + auto list = FileSystem::GetFileList("scripts/", "gsc"); + + for (auto file : list) + { + file = "scripts/" + file; + + if (Utils::String::EndsWith(file, ".gsc")) + { + file = file.substr(0, file.size() - 4); + } + + int handle = Script::LoadScriptAndLabel(file, "init"); + + if (handle) + { + Script::ScriptHandles.push_back(handle); + } + } + + Game::GScr_LoadGameTypeScript(); + } + + 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(); + + 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(); + } + + Script::~Script() + { + Script::ScriptName.clear(); + Script::ScriptHandles.clear(); + Script::ScriptNameStack.clear(); + } +} diff --git a/src/Components/Modules/ServerInfo.cpp b/src/Components/Modules/ServerInfo.cpp index 1941ec34..c91e7b80 100644 --- a/src/Components/Modules/ServerInfo.cpp +++ b/src/Components/Modules/ServerInfo.cpp @@ -1,289 +1,289 @@ -#include "STDInclude.hpp" - -namespace Components -{ - ServerInfo::Container ServerInfo::PlayerContainer; - - unsigned int ServerInfo::GetPlayerCount() - { - return ServerInfo::PlayerContainer.PlayerList.size(); - } - - const char* ServerInfo::GetPlayerText(unsigned int index, int column) - { - if (index < ServerInfo::PlayerContainer.PlayerList.size()) - { - switch (column) - { - case 0: - return Utils::VA("%d", index); - - case 1: - return ServerInfo::PlayerContainer.PlayerList[index].Name.data(); - - case 2: - return Utils::VA("%d", ServerInfo::PlayerContainer.PlayerList[index].Score); - - case 3: - return Utils::VA("%d", ServerInfo::PlayerContainer.PlayerList[index].Ping); - } - } - - return ""; - } - - void ServerInfo::SelectPlayer(unsigned int index) - { - ServerInfo::PlayerContainer.CurrentPlayer = index; - } - - void ServerInfo::ServerStatus() - { - ServerInfo::PlayerContainer.CurrentPlayer = 0; - ServerInfo::PlayerContainer.PlayerList.clear(); - - ServerList::ServerInfo* info = ServerList::GetCurrentServer(); - - if(info) - { - Dvar::Var("uiSi_ServerName").Set(info->Hostname); - Dvar::Var("uiSi_MaxClients").Set(info->Clients); - Dvar::Var("uiSi_Version").Set(info->Shortversion); - Dvar::Var("uiSi_isPrivate").Set(info->Password ? "@MENU_YES" : "@MENU_NO"); - Dvar::Var("uiSi_Hardcore").Set(info->Hardcore ? "@MENU_ENABLED" : "@MENU_DISABLED"); - Dvar::Var("uiSi_KillCam").Set("@MENU_NO"); - Dvar::Var("uiSi_ffType").Set("@MENU_DISABLED"); - Dvar::Var("uiSi_MapName").Set(info->Mapname); - Dvar::Var("uiSi_MapNameLoc").Set(Game::UI_LocalizeMapName(info->Mapname.data())); - Dvar::Var("uiSi_GameType").Set(Game::UI_LocalizeGameType(info->Gametype.data())); - Dvar::Var("uiSi_ModName").Set(""); - - if (info->Mod.size() > 5) - { - Dvar::Var("uiSi_ModName").Set(info->Mod.data() + 5); - } - - ServerInfo::PlayerContainer.Target = info->Addr; - Network::SendCommand(ServerInfo::PlayerContainer.Target, "getstatus"); - } - } - - void ServerInfo::DrawScoreboardInfo(void* a1) - { - Game::Font* font = Game::R_RegisterFont("fonts/bigfont"); - void* cxt = Game::UI_GetContext(a1); - - std::string addressText = Network::Address(*Game::connectedHost).GetString(); - if (addressText == "0.0.0.0:0" || addressText == "loopback") addressText = "Listen Server"; - - // get x positions - float fontSize = 0.35f; - float y = (480.0f - Dvar::Var("cg_scoreboardHeight").Get()) * 0.5f; - y += Dvar::Var("cg_scoreboardHeight").Get() + 6.0f; - - float x = 320.0f - Dvar::Var("cg_scoreboardWidth").Get() * 0.5f; - float x2 = 320.0f + Dvar::Var("cg_scoreboardWidth").Get() * 0.5f; - - Game::UI_DrawText(cxt, reinterpret_cast(0x7ED3F8), 0x7FFFFFFF, font, x, y, 0, 0, fontSize, reinterpret_cast(0x747F34), 3); - Game::UI_DrawText(cxt, addressText.data(), 0x7FFFFFFF, font, x2 - Game::UI_TextWidth(addressText.data(), 0, font, fontSize), y, 0, 0, fontSize, reinterpret_cast(0x747F34), 3); - } - - void __declspec(naked) ServerInfo::DrawScoreboardStub() - { - __asm - { - push eax - call ServerInfo::DrawScoreboardInfo - pop eax - mov ecx, 591B70h - jmp ecx - } - } - - Utils::InfoString ServerInfo::GetInfo() - { - int maxclientCount = *Game::svs_numclients; - - if (!maxclientCount) - { - //maxclientCount = Dvar::Var("sv_maxclients").Get(); - maxclientCount = Game::Party_GetMaxPlayers(*Game::partyIngame); - } - - Utils::InfoString info(Game::Dvar_InfoString_Big(1024)); - info.Set("gamename", "IW4"); - info.Set("sv_maxclients", Utils::VA("%i", maxclientCount)); - info.Set("protocol", Utils::VA("%i", PROTOCOL)); - info.Set("shortversion", VERSION_STR); - info.Set("mapname", Dvar::Var("mapname").Get()); - info.Set("isPrivate", (Dvar::Var("g_password").Get().empty() ? "0" : "1")); - info.Set("checksum", Utils::VA("%X", Utils::Cryptography::JenkinsOneAtATime::Compute(Utils::VA("%u", Game::Sys_Milliseconds())))); - - // Ensure mapname is set - if (info.Get("mapname").empty()) - { - info.Set("mapname", Dvar::Var("ui_mapname").Get()); - } - - // Set matchtype - // 0 - No match, connecting not possible - // 1 - Party, use Steam_JoinLobby to connect - // 2 - Match, use CL_ConnectFromParty to connect - - if (Dvar::Var("party_enable").Get() && Dvar::Var("party_host").Get()) // Party hosting - { - info.Set("matchtype", "1"); - } - else if (Dvar::Var("sv_running").Get()) // Match hosting - { - info.Set("matchtype", "2"); - } - else - { - info.Set("matchtype", "0"); - } - - return info; - } - - ServerInfo::ServerInfo() - { - ServerInfo::PlayerContainer.CurrentPlayer = 0; - ServerInfo::PlayerContainer.PlayerList.clear(); - - // Draw IP and hostname on the scoreboard - Utils::Hook(0x4FC6EA, ServerInfo::DrawScoreboardStub, HOOK_CALL).Install()->Quick(); - - // Ignore native getStatus implementation - Utils::Hook::Nop(0x62654E, 6); - - // Add uiscript - UIScript::Add("ServerStatus", ServerInfo::ServerStatus); - - // Add uifeeder - UIFeeder::Add(13.0f, ServerInfo::GetPlayerCount, ServerInfo::GetPlayerText, ServerInfo::SelectPlayer); - - Network::Handle("getStatus", [] (Network::Address address, std::string data) - { - std::string playerList; - - Utils::InfoString info = ServerInfo::GetInfo(); - info.Set("challenge", Utils::ParseChallenge(data)); - - for (int i = 0; i < atoi(info.Get("sv_maxclients").data()); ++i) // Maybe choose 18 here? - { - int score = 0; - int ping = 0; - std::string name; - - if (Dvar::Var("sv_running").Get()) - { - if (Game::svs_clients[i].state < 3) continue; - - score = Game::SV_GameClientNum_Score(i); - ping = Game::svs_clients[i].ping; - name = Game::svs_clients[i].name; - } - else - { - // Score and ping are irrelevant - const char* namePtr = Game::PartyHost_GetMemberName(reinterpret_cast(0x1081C00), i); - if (!namePtr || !namePtr[0]) continue; - - name = namePtr; - } - - playerList.append(Utils::VA("%i %i \"%s\"\n", score, ping, name.data())); - } - - Network::SendCommand(address, "statusResponse", "\\" + info.Build() + "\n" + playerList + "\n"); - }); - - Network::Handle("statusResponse", [] (Network::Address address, std::string data) - { - if (ServerInfo::PlayerContainer.Target == address) - { - Utils::InfoString info(data.substr(0, data.find_first_of("\n"))); - - Dvar::Var("uiSi_ServerName").Set(info.Get("sv_hostname")); - Dvar::Var("uiSi_MaxClients").Set(info.Get("sv_maxclients")); - Dvar::Var("uiSi_Version").Set(info.Get("shortversion")); - Dvar::Var("uiSi_isPrivate").Set(info.Get("isPrivate") == "0" ? "@MENU_NO" : "@MENU_YES"); - Dvar::Var("uiSi_Hardcore").Set(info.Get("g_hardcore") == "0" ? "@MENU_DISABLED" : "@MENU_ENABLED"); - Dvar::Var("uiSi_KillCam").Set(info.Get("scr_game_allowkillcam") == "0" ? "@MENU_NO" : "@MENU_YES"); - Dvar::Var("uiSi_MapName").Set(info.Get("mapname")); - Dvar::Var("uiSi_MapNameLoc").Set(Game::UI_LocalizeMapName(info.Get("mapname").data())); - Dvar::Var("uiSi_GameType").Set(Game::UI_LocalizeGameType(info.Get("g_gametype").data())); - Dvar::Var("uiSi_ModName").Set(""); - - switch (atoi(info.Get("scr_team_fftype").data())) - { - default: - Dvar::Var("uiSi_ffType").Set("@MENU_DISABLED"); - break; - - case 1: - Dvar::Var("uiSi_ffType").Set("@MENU_ENABLED"); - break; - - case 2: - Dvar::Var("uiSi_ffType").Set("@MPUI_RULES_REFLECT"); - break; - - case 3: - Dvar::Var("uiSi_ffType").Set("@MPUI_RULES_SHARED"); - break; - } - - if (info.Get("fs_game").size() > 5) - { - Dvar::Var("uiSi_ModName").Set(info.Get("fs_game").data() + 5); - } - - auto lines = Utils::Explode(data, '\n'); - - if (lines.size() <= 1) return; - - for (unsigned int i = 1; i < lines.size(); ++i) - { - ServerInfo::Container::Player player; - - std::string currentData = lines[i]; - - if (currentData.size() < 3) continue; - - // Insert score - player.Score = atoi(currentData.substr(0, currentData.find_first_of(" ")).data()); - - // Remove score - currentData = currentData.substr(currentData.find_first_of(" ") + 1); - - // Insert ping - player.Ping = atoi(currentData.substr(0, currentData.find_first_of(" ")).data()); - - // Remove ping - currentData = currentData.substr(currentData.find_first_of(" ") + 1); - - if (currentData[0] == '\"') - { - currentData = currentData.substr(1); - } - - if (currentData[currentData.size() - 1] == '\"') - { - currentData.pop_back(); - } - - player.Name = currentData; - - ServerInfo::PlayerContainer.PlayerList.push_back(player); - } - } - }); - } - - ServerInfo::~ServerInfo() - { - ServerInfo::PlayerContainer.PlayerList.clear(); - } -} +#include "STDInclude.hpp" + +namespace Components +{ + ServerInfo::Container ServerInfo::PlayerContainer; + + unsigned int ServerInfo::GetPlayerCount() + { + return ServerInfo::PlayerContainer.PlayerList.size(); + } + + const char* ServerInfo::GetPlayerText(unsigned int index, int column) + { + if (index < ServerInfo::PlayerContainer.PlayerList.size()) + { + switch (column) + { + case 0: + return Utils::String::VA("%d", index); + + case 1: + return ServerInfo::PlayerContainer.PlayerList[index].Name.data(); + + case 2: + return Utils::String::VA("%d", ServerInfo::PlayerContainer.PlayerList[index].Score); + + case 3: + return Utils::String::VA("%d", ServerInfo::PlayerContainer.PlayerList[index].Ping); + } + } + + return ""; + } + + void ServerInfo::SelectPlayer(unsigned int index) + { + ServerInfo::PlayerContainer.CurrentPlayer = index; + } + + void ServerInfo::ServerStatus() + { + ServerInfo::PlayerContainer.CurrentPlayer = 0; + ServerInfo::PlayerContainer.PlayerList.clear(); + + ServerList::ServerInfo* info = ServerList::GetCurrentServer(); + + if(info) + { + Dvar::Var("uiSi_ServerName").Set(info->Hostname); + Dvar::Var("uiSi_MaxClients").Set(info->Clients); + Dvar::Var("uiSi_Version").Set(info->Shortversion); + Dvar::Var("uiSi_isPrivate").Set(info->Password ? "@MENU_YES" : "@MENU_NO"); + Dvar::Var("uiSi_Hardcore").Set(info->Hardcore ? "@MENU_ENABLED" : "@MENU_DISABLED"); + Dvar::Var("uiSi_KillCam").Set("@MENU_NO"); + Dvar::Var("uiSi_ffType").Set("@MENU_DISABLED"); + Dvar::Var("uiSi_MapName").Set(info->Mapname); + Dvar::Var("uiSi_MapNameLoc").Set(Game::UI_LocalizeMapName(info->Mapname.data())); + Dvar::Var("uiSi_GameType").Set(Game::UI_LocalizeGameType(info->Gametype.data())); + Dvar::Var("uiSi_ModName").Set(""); + + if (info->Mod.size() > 5) + { + Dvar::Var("uiSi_ModName").Set(info->Mod.data() + 5); + } + + ServerInfo::PlayerContainer.Target = info->Addr; + Network::SendCommand(ServerInfo::PlayerContainer.Target, "getstatus"); + } + } + + void ServerInfo::DrawScoreboardInfo(void* a1) + { + Game::Font* font = Game::R_RegisterFont("fonts/bigfont"); + void* cxt = Game::UI_GetContext(a1); + + std::string addressText = Network::Address(*Game::connectedHost).GetString(); + if (addressText == "0.0.0.0:0" || addressText == "loopback") addressText = "Listen Server"; + + // get x positions + float fontSize = 0.35f; + float y = (480.0f - Dvar::Var("cg_scoreboardHeight").Get()) * 0.5f; + y += Dvar::Var("cg_scoreboardHeight").Get() + 6.0f; + + float x = 320.0f - Dvar::Var("cg_scoreboardWidth").Get() * 0.5f; + float x2 = 320.0f + Dvar::Var("cg_scoreboardWidth").Get() * 0.5f; + + Game::UI_DrawText(cxt, reinterpret_cast(0x7ED3F8), 0x7FFFFFFF, font, x, y, 0, 0, fontSize, reinterpret_cast(0x747F34), 3); + Game::UI_DrawText(cxt, addressText.data(), 0x7FFFFFFF, font, x2 - Game::UI_TextWidth(addressText.data(), 0, font, fontSize), y, 0, 0, fontSize, reinterpret_cast(0x747F34), 3); + } + + void __declspec(naked) ServerInfo::DrawScoreboardStub() + { + __asm + { + push eax + call ServerInfo::DrawScoreboardInfo + pop eax + mov ecx, 591B70h + jmp ecx + } + } + + Utils::InfoString ServerInfo::GetInfo() + { + int maxclientCount = *Game::svs_numclients; + + if (!maxclientCount) + { + //maxclientCount = Dvar::Var("sv_maxclients").Get(); + maxclientCount = Game::Party_GetMaxPlayers(*Game::partyIngame); + } + + Utils::InfoString info(Game::Dvar_InfoString_Big(1024)); + info.Set("gamename", "IW4"); + info.Set("sv_maxclients", fmt::sprintf("%i", maxclientCount)); + info.Set("protocol", fmt::sprintf("%i", PROTOCOL)); + info.Set("shortversion", VERSION_STR); + info.Set("mapname", Dvar::Var("mapname").Get()); + info.Set("isPrivate", (Dvar::Var("g_password").Get().empty() ? "0" : "1")); + info.Set("checksum", fmt::sprintf("%X", Utils::Cryptography::JenkinsOneAtATime::Compute(fmt::sprintf("%u", Game::Sys_Milliseconds())))); + + // Ensure mapname is set + if (info.Get("mapname").empty()) + { + info.Set("mapname", Dvar::Var("ui_mapname").Get()); + } + + // Set matchtype + // 0 - No match, connecting not possible + // 1 - Party, use Steam_JoinLobby to connect + // 2 - Match, use CL_ConnectFromParty to connect + + if (Dvar::Var("party_enable").Get() && Dvar::Var("party_host").Get()) // Party hosting + { + info.Set("matchtype", "1"); + } + else if (Dvar::Var("sv_running").Get()) // Match hosting + { + info.Set("matchtype", "2"); + } + else + { + info.Set("matchtype", "0"); + } + + return info; + } + + ServerInfo::ServerInfo() + { + ServerInfo::PlayerContainer.CurrentPlayer = 0; + ServerInfo::PlayerContainer.PlayerList.clear(); + + // Draw IP and hostname on the scoreboard + Utils::Hook(0x4FC6EA, ServerInfo::DrawScoreboardStub, HOOK_CALL).Install()->Quick(); + + // Ignore native getStatus implementation + Utils::Hook::Nop(0x62654E, 6); + + // Add uiscript + UIScript::Add("ServerStatus", ServerInfo::ServerStatus); + + // Add uifeeder + UIFeeder::Add(13.0f, ServerInfo::GetPlayerCount, ServerInfo::GetPlayerText, ServerInfo::SelectPlayer); + + Network::Handle("getStatus", [] (Network::Address address, std::string data) + { + std::string playerList; + + Utils::InfoString info = ServerInfo::GetInfo(); + info.Set("challenge", Utils::ParseChallenge(data)); + + for (int i = 0; i < atoi(info.Get("sv_maxclients").data()); ++i) // Maybe choose 18 here? + { + int score = 0; + int ping = 0; + std::string name; + + if (Dvar::Var("sv_running").Get()) + { + if (Game::svs_clients[i].state < 3) continue; + + score = Game::SV_GameClientNum_Score(i); + ping = Game::svs_clients[i].ping; + name = Game::svs_clients[i].name; + } + else + { + // Score and ping are irrelevant + const char* namePtr = Game::PartyHost_GetMemberName(reinterpret_cast(0x1081C00), i); + if (!namePtr || !namePtr[0]) continue; + + name = namePtr; + } + + playerList.append(fmt::sprintf("%i %i \"%s\"\n", score, ping, name.data())); + } + + Network::SendCommand(address, "statusResponse", "\\" + info.Build() + "\n" + playerList + "\n"); + }); + + Network::Handle("statusResponse", [] (Network::Address address, std::string data) + { + if (ServerInfo::PlayerContainer.Target == address) + { + Utils::InfoString info(data.substr(0, data.find_first_of("\n"))); + + Dvar::Var("uiSi_ServerName").Set(info.Get("sv_hostname")); + Dvar::Var("uiSi_MaxClients").Set(info.Get("sv_maxclients")); + Dvar::Var("uiSi_Version").Set(info.Get("shortversion")); + Dvar::Var("uiSi_isPrivate").Set(info.Get("isPrivate") == "0" ? "@MENU_NO" : "@MENU_YES"); + Dvar::Var("uiSi_Hardcore").Set(info.Get("g_hardcore") == "0" ? "@MENU_DISABLED" : "@MENU_ENABLED"); + Dvar::Var("uiSi_KillCam").Set(info.Get("scr_game_allowkillcam") == "0" ? "@MENU_NO" : "@MENU_YES"); + Dvar::Var("uiSi_MapName").Set(info.Get("mapname")); + Dvar::Var("uiSi_MapNameLoc").Set(Game::UI_LocalizeMapName(info.Get("mapname").data())); + Dvar::Var("uiSi_GameType").Set(Game::UI_LocalizeGameType(info.Get("g_gametype").data())); + Dvar::Var("uiSi_ModName").Set(""); + + switch (atoi(info.Get("scr_team_fftype").data())) + { + default: + Dvar::Var("uiSi_ffType").Set("@MENU_DISABLED"); + break; + + case 1: + Dvar::Var("uiSi_ffType").Set("@MENU_ENABLED"); + break; + + case 2: + Dvar::Var("uiSi_ffType").Set("@MPUI_RULES_REFLECT"); + break; + + case 3: + Dvar::Var("uiSi_ffType").Set("@MPUI_RULES_SHARED"); + break; + } + + if (info.Get("fs_game").size() > 5) + { + Dvar::Var("uiSi_ModName").Set(info.Get("fs_game").data() + 5); + } + + auto lines = Utils::String::Explode(data, '\n'); + + if (lines.size() <= 1) return; + + for (unsigned int i = 1; i < lines.size(); ++i) + { + ServerInfo::Container::Player player; + + std::string currentData = lines[i]; + + if (currentData.size() < 3) continue; + + // Insert score + player.Score = atoi(currentData.substr(0, currentData.find_first_of(" ")).data()); + + // Remove score + currentData = currentData.substr(currentData.find_first_of(" ") + 1); + + // Insert ping + player.Ping = atoi(currentData.substr(0, currentData.find_first_of(" ")).data()); + + // Remove ping + currentData = currentData.substr(currentData.find_first_of(" ") + 1); + + if (currentData[0] == '\"') + { + currentData = currentData.substr(1); + } + + if (currentData[currentData.size() - 1] == '\"') + { + currentData.pop_back(); + } + + player.Name = currentData; + + ServerInfo::PlayerContainer.PlayerList.push_back(player); + } + } + }); + } + + ServerInfo::~ServerInfo() + { + ServerInfo::PlayerContainer.PlayerList.clear(); + } +} diff --git a/src/Components/Modules/ServerList.cpp b/src/Components/Modules/ServerList.cpp index 93055f9c..0193cf37 100644 --- a/src/Components/Modules/ServerList.cpp +++ b/src/Components/Modules/ServerList.cpp @@ -1,733 +1,733 @@ -#include "STDInclude.hpp" - -namespace Components -{ - bool ServerList::SortAsc = true; - int ServerList::SortKey = ServerList::Column::Ping; - - unsigned int ServerList::CurrentServer = 0; - ServerList::Container ServerList::RefreshContainer; - - std::vector ServerList::OnlineList; - std::vector ServerList::OfflineList; - std::vector ServerList::FavouriteList; - - std::vector ServerList::VisibleList; - - std::vector* ServerList::GetList() - { - if (ServerList::IsOnlineList()) - { - return &ServerList::OnlineList; - } - else if (ServerList::IsOfflineList()) - { - return &ServerList::OfflineList; - } - else if (ServerList::IsFavouriteList()) - { - return &ServerList::FavouriteList; - } - - return nullptr; - } - - bool ServerList::IsFavouriteList() - { - return (Dvar::Var("ui_netSource").Get() == 2); - } - - bool ServerList::IsOfflineList() - { - return (Dvar::Var("ui_netSource").Get() == 0); - } - - bool ServerList::IsOnlineList() - { - return (Dvar::Var("ui_netSource").Get() == 1); - } - - unsigned int ServerList::GetServerCount() - { - return ServerList::VisibleList.size(); - } - - const char* ServerList::GetServerText(unsigned int index, int column) - { - ServerList::ServerInfo* info = ServerList::GetServer(index); - - if (info) - { - return ServerList::GetServerText(info, column); - } - - return ""; - } - - const char* ServerList::GetServerText(ServerList::ServerInfo* server, int column) - { - if (!server) return ""; - - switch (column) - { - case Column::Password: - { - return (server->Password ? "X" : ""); - } - - case Column::Matchtype: - { - return ((server->MatchType == 1) ? "P" : "M"); - } - - case Column::Hostname: - { - return server->Hostname.data(); - } - - case Column::Mapname: - { - return Game::UI_LocalizeMapName(server->Mapname.data()); - } - - case Column::Players: - { - return Utils::VA("%i (%i)", server->Clients, server->MaxClients); - } - - case Column::Gametype: - { - return Game::UI_LocalizeGameType(server->Gametype.data()); - } - - case Column::Mod: - { - if (server->Mod != "") - { - return (server->Mod.data() + 5); - } - - return ""; - } - - case Column::Ping: - { - return Utils::VA("%i", server->Ping); - } - } - - return ""; - } - - void ServerList::SelectServer(unsigned int index) - { - ServerList::CurrentServer = index; - - ServerList::ServerInfo* info = ServerList::GetCurrentServer(); - - if (info) - { - Dvar::Var("ui_serverSelected").Set(true); - Dvar::Var("ui_serverSelectedMap").Set(info->Mapname); - } - else - { - Dvar::Var("ui_serverSelected").Set(false); - } - } - - void ServerList::UpdateVisibleList() - { - auto list = ServerList::GetList(); - if (!list) return; - - std::vector tempList(*list); - - if (tempList.empty()) - { - ServerList::Refresh(); - } - else - { - list->clear(); - - ServerList::RefreshContainer.Mutex.lock(); - - ServerList::RefreshContainer.SendCount = 0; - ServerList::RefreshContainer.SentCount = 0; - - for (auto server : tempList) - { - ServerList::InsertRequest(server.Addr, false); - } - - ServerList::RefreshContainer.Mutex.unlock(); - } - } - - void ServerList::RefreshVisibleList() - { - Dvar::Var("ui_serverSelected").Set(false); - - ServerList::VisibleList.clear(); - - auto list = ServerList::GetList(); - if (!list) return; - - // Refresh entirely, if there is no entry in the list - if (list->empty()) - { - ServerList::Refresh(); - return; - } - - bool ui_browserShowFull = Dvar::Var("ui_browserShowFull").Get(); - bool ui_browserShowEmpty = Dvar::Var("ui_browserShowEmpty").Get(); - int ui_browserShowHardcore = Dvar::Var("ui_browserKillcam").Get(); - int ui_browserShowPassword = Dvar::Var("ui_browserShowPassword").Get(); - int ui_browserMod = Dvar::Var("ui_browserMod").Get(); - int ui_joinGametype = Dvar::Var("ui_joinGametype").Get(); - - for (unsigned int i = 0; i < list->size(); ++i) - { - ServerList::ServerInfo* info = &(*list)[i]; - - // Filter full servers - if (!ui_browserShowFull && info->Clients >= info->MaxClients) continue; - - // Filter empty servers - if (!ui_browserShowEmpty && info->Clients <= 0) continue; - - // Filter hardcore servers - if ((ui_browserShowHardcore == 0 && info->Hardcore) || (ui_browserShowHardcore == 1 && !info->Hardcore)) continue; - - // Filter servers with password - if ((ui_browserShowPassword == 0 && info->Password) || (ui_browserShowPassword == 1 && !info->Password)) continue; - - // Don't show modded servers - if ((ui_browserMod == 0 && info->Mod.size()) || (ui_browserMod == 1 && !info->Mod.size())) continue; - - // Filter by gametype - if (ui_joinGametype > 0 && (ui_joinGametype -1) < *Game::gameTypeCount && Game::gameTypes[(ui_joinGametype - 1)].gameType != info->Gametype) continue; - - ServerList::VisibleList.push_back(i); - } - - ServerList::SortList(); - } - - void ServerList::Refresh() - { - Dvar::Var("ui_serverSelected").Set(false); - Localization::Set("MPUI_SERVERQUERIED", "Sent requests: 0/0"); - -// ServerList::OnlineList.clear(); -// ServerList::OfflineList.clear(); -// ServerList::FavouriteList.clear(); - - auto list = ServerList::GetList(); - if (list) list->clear(); - - ServerList::VisibleList.clear(); - ServerList::RefreshContainer.Mutex.lock(); - ServerList::RefreshContainer.Servers.clear(); - ServerList::RefreshContainer.SendCount = 0; - ServerList::RefreshContainer.SentCount = 0; - ServerList::RefreshContainer.Mutex.unlock(); - - if (ServerList::IsOfflineList()) - { - Discovery::Perform(); - } - else if (ServerList::IsOnlineList()) - { -#ifdef USE_LEGACY_SERVER_LIST - ServerList::RefreshContainer.AwatingList = true; - ServerList::RefreshContainer.AwaitTime = Game::Com_Milliseconds(); - - int masterPort = Dvar::Var("masterPort").Get(); - const char* masterServerName = Dvar::Var("masterServerName").Get(); - - ServerList::RefreshContainer.Host = Network::Address(Utils::VA("%s:%u", masterServerName, masterPort)); - - Logger::Print("Sending serverlist request to master: %s:%u\n", masterServerName, masterPort); - - Network::SendCommand(ServerList::RefreshContainer.Host, "getservers", Utils::VA("IW4 %i full empty", PROTOCOL)); - //Network::SendCommand(ServerList::RefreshContainer.Host, "getservers", "0 full empty"); -#else - Node::SyncNodeList(); -#endif - } - else if (ServerList::IsFavouriteList()) - { - ServerList::LoadFavourties(); - } - } - - void ServerList::StoreFavourite(std::string server) - { - //json11::Json::parse() - std::vector servers; - - if (Utils::FileExists("players/favourites.json")) - { - std::string data = Utils::ReadFile("players/favourites.json"); - json11::Json object = json11::Json::parse(data, data); - - if (!object.is_array()) - { - Logger::Print("Favourites storage file is invalid!\n"); - Game::MessageBox("Favourites storage file is invalid!", "Error"); - return; - } - - auto storedServers = object.array_items(); - - for (unsigned int i = 0; i < storedServers.size(); ++i) - { - if (!storedServers[i].is_string()) continue; - if (storedServers[i].string_value() == server) - { - Game::MessageBox("Server already marked as favourite.", "Error"); - return; - } - - servers.push_back(storedServers[i].string_value()); - } - } - - servers.push_back(server); - - json11::Json data = json11::Json(servers); - Utils::WriteFile("players/favourites.json", data.dump()); - Game::MessageBox("Server added to favourites.", "Success"); - } - - void ServerList::LoadFavourties() - { - if (ServerList::IsFavouriteList() && Utils::FileExists("players/favourites.json")) - { - auto list = ServerList::GetList(); - if (list) list->clear(); - - std::string data = Utils::ReadFile("players/favourites.json"); - json11::Json object = json11::Json::parse(data, data); - - if (!object.is_array()) - { - Logger::Print("Favourites storage file is invalid!\n"); - Game::MessageBox("Favourites storage file is invalid!", "Error"); - return; - } - - auto servers = object.array_items(); - - for (unsigned int i = 0; i < servers.size(); ++i) - { - if(!servers[i].is_string()) continue; - ServerList::InsertRequest(servers[i].string_value(), true); - } - } - } - - void ServerList::InsertRequest(Network::Address address, bool acquireMutex) - { - if (acquireMutex) ServerList::RefreshContainer.Mutex.lock(); - - ServerList::Container::ServerContainer container; - container.Sent = false; - container.Target = address; - - bool alreadyInserted = false; - for (auto &server : ServerList::RefreshContainer.Servers) - { - if (server.Target == container.Target) - { - alreadyInserted = true; - break; - } - } - - if (!alreadyInserted) - { - ServerList::RefreshContainer.Servers.push_back(container); - - auto list = ServerList::GetList(); - if (list) - { - for (auto server : *list) - { - if (server.Addr == container.Target) - { - --ServerList::RefreshContainer.SendCount; - --ServerList::RefreshContainer.SentCount; - break; - } - } - } - - ++ServerList::RefreshContainer.SendCount; - } - - if (acquireMutex) ServerList::RefreshContainer.Mutex.unlock(); - } - - void ServerList::Insert(Network::Address address, Utils::InfoString info) - { - ServerList::RefreshContainer.Mutex.lock(); - - for (auto i = ServerList::RefreshContainer.Servers.begin(); i != ServerList::RefreshContainer.Servers.end();) - { - // Our desired server - if (i->Target == address && i->Sent) - { - // Challenge did not match - if (i->Challenge != info.Get("challenge")) - { - // Shall we remove the server from the queue? - // Better not, it might send a second response with the correct challenge. - // This might happen when users refresh twice (or more often) in a short period of time - break; - } - - ServerInfo server; - server.Hostname = info.Get("hostname"); - server.Mapname = info.Get("mapname"); - server.Gametype = info.Get("gametype"); - server.Shortversion = info.Get("shortversion"); - server.Mod = info.Get("fs_game"); - server.MatchType = atoi(info.Get("matchtype").data()); - server.Clients = atoi(info.Get("clients").data()); - server.MaxClients = atoi(info.Get("sv_maxclients").data()); - server.Password = (atoi(info.Get("isPrivate").data()) != 0); - server.Hardcore = (atoi(info.Get("hc").data()) != 0); - server.Ping = (Game::Sys_Milliseconds() - i->SendTime); - server.Addr = address; - - // Remove server from queue - i = ServerList::RefreshContainer.Servers.erase(i); - - // Check if already inserted and remove - auto list = ServerList::GetList(); - if (!list) return; - - unsigned int k = 0; - for (auto j = list->begin(); j != list->end(); ++k) - { - if (j->Addr == address) - { - j = list->erase(j); - } - else - { - ++j; - } - } - - // Also remove from visible list - for (auto j = ServerList::VisibleList.begin(); j != ServerList::VisibleList.end();) - { - if (*j == k) - { - j = ServerList::VisibleList.erase(j); - } - else - { - ++j; - } - } - - if (info.Get("gamename") == "IW4" - && server.MatchType -#ifndef DEBUG - && server.Shortversion == VERSION_STR -#endif - ) - { - auto lList = ServerList::GetList(); - - if (lList) - { - lList->push_back(server); - ServerList::RefreshVisibleList(); - } - } - - break; - } - else - { - ++i; - } - } - - ServerList::RefreshContainer.Mutex.unlock(); - } - - ServerList::ServerInfo* ServerList::GetCurrentServer() - { - return ServerList::GetServer(ServerList::CurrentServer); - } - - void ServerList::SortList() - { - qsort(ServerList::VisibleList.data(), ServerList::VisibleList.size(), sizeof(int), [] (const void* first, const void* second) - { - const unsigned int server1 = *static_cast(first); - const unsigned int server2 = *static_cast(second); - - ServerInfo* info1 = nullptr; - ServerInfo* info2 = nullptr; - - auto list = ServerList::GetList(); - if (!list) return 0; - - if (list->size() > server1) info1 = &(*list)[server1]; - if (list->size() > server2) info2 = &(*list)[server2]; - - if (!info1) return 1; - if (!info2) return -1; - - // Numerical comparisons - if (ServerList::SortKey == ServerList::Column::Ping) - { - return ((info1->Ping - info2->Ping) * (ServerList::SortAsc ? 1 : -1)); - } - else if (ServerList::SortKey == ServerList::Column::Players) - { - return ((info1->Clients - info2->Clients) * (ServerList::SortAsc ? 1 : -1)); - } - - std::string text1 = Colors::Strip(ServerList::GetServerText(info1, ServerList::SortKey)); - std::string text2 = Colors::Strip(ServerList::GetServerText(info2, ServerList::SortKey)); - - // ASCII-based comparison - return (text1.compare(text2) * (ServerList::SortAsc ? 1 : -1)); - }); - } - - ServerList::ServerInfo* ServerList::GetServer(unsigned int index) - { - if (ServerList::VisibleList.size() > index) - { - auto list = ServerList::GetList(); - if (!list) return nullptr; - - if (list->size() > ServerList::VisibleList[index]) - { - return &(*list)[ServerList::VisibleList[index]]; - } - } - - return nullptr; - } - - void ServerList::Frame() - { - if (!ServerList::RefreshContainer.Mutex.try_lock()) return; - - if (ServerList::RefreshContainer.AwatingList) - { - // Check if we haven't got a response within 10 seconds - if (Game::Sys_Milliseconds() - ServerList::RefreshContainer.AwaitTime > 5000) - { - ServerList::RefreshContainer.AwatingList = false; - - Logger::Print("We haven't received a response from the master within %d seconds!\n", (Game::Sys_Milliseconds() - ServerList::RefreshContainer.AwaitTime) / 1000); - } - } - - // Send requests to 10 servers each frame - int SendServers = 10; - - for (unsigned int i = 0; i < ServerList::RefreshContainer.Servers.size(); ++i) - { - ServerList::Container::ServerContainer* server = &ServerList::RefreshContainer.Servers[i]; - if (server->Sent) continue; - - // Found server we can send a request to - server->Sent = true; - SendServers--; - - server->SendTime = Game::Sys_Milliseconds(); - server->Challenge = Utils::VA("%X", Utils::Cryptography::Rand::GenerateInt()); - - ++ServerList::RefreshContainer.SentCount; - - Network::SendCommand(server->Target, "getinfo", server->Challenge); - - // Display in the menu, like in COD4 - Localization::Set("MPUI_SERVERQUERIED", Utils::VA("Sent requests: %d/%d", ServerList::RefreshContainer.SentCount, ServerList::RefreshContainer.SendCount)); - - if (SendServers <= 0) break; - } - - ServerList::RefreshContainer.Mutex.unlock(); - } - - void ServerList::UpdateSource() - { - Dvar::Var netSource("ui_netSource"); - - int source = netSource.Get(); - - if (++source > netSource.Get()->max.i) - { - source = 0; - } - - netSource.Set(source); - - ServerList::RefreshVisibleList(); - } - - void ServerList::UpdateGameType() - { - Dvar::Var joinGametype("ui_joinGametype"); - - int gametype = joinGametype.Get(); - - if (++gametype > *Game::gameTypeCount) - { - gametype = 0; - } - - joinGametype.Set(gametype); - - ServerList::RefreshVisibleList(); - } - - ServerList::ServerList() - { - ServerList::OnlineList.clear(); - ServerList::VisibleList.clear(); - - Dvar::OnInit([] () - { - Dvar::Register("ui_serverSelected", false, Game::dvar_flag::DVAR_FLAG_NONE, "Whether a server has been selected in the serverlist"); - Dvar::Register("ui_serverSelectedMap", "mp_afghan", Game::dvar_flag::DVAR_FLAG_NONE, "Map of the selected server"); - }); - - Localization::Set("MPUI_SERVERQUERIED", "Sent requests: 0/0"); - - Network::Handle("getServersResponse", [] (Network::Address address, std::string data) - { - if (ServerList::RefreshContainer.Host != address) return; // Only parse from host we sent to - - ServerList::RefreshContainer.AwatingList = false; - - ServerList::RefreshContainer.Mutex.lock(); - - int offset = 0; - int count = ServerList::RefreshContainer.Servers.size(); - ServerList::MasterEntry* entry = nullptr; - - // Find first entry - do - { - entry = reinterpret_cast(const_cast(data.data()) + offset++); - } - while (!entry->HasSeparator() && !entry->IsEndToken()); - - for (int i = 0; !entry[i].IsEndToken() && entry[i].HasSeparator(); ++i) - { - Network::Address serverAddr = address; - serverAddr.SetIP(entry[i].IP); - serverAddr.SetPort(ntohs(entry[i].Port)); - serverAddr.SetType(Game::NA_IP); - - ServerList::InsertRequest(serverAddr, false); - } - - Logger::Print("Parsed %d servers from master\n", ServerList::RefreshContainer.Servers.size() - count); - - ServerList::RefreshContainer.Mutex.unlock(); - }); - - // Set default masterServerName + port and save it -#ifdef USE_LEGACY_SERVER_LIST - Utils::Hook::Set(0x60AD92, "localhost"); - Utils::Hook::Set(0x60AD90, Game::dvar_flag::DVAR_FLAG_SAVED); // masterServerName - Utils::Hook::Set(0x60ADC6, Game::dvar_flag::DVAR_FLAG_SAVED); // masterPort -#endif - - // Add server list feeder - UIFeeder::Add(2.0f, ServerList::GetServerCount, ServerList::GetServerText, ServerList::SelectServer); - - // Add required UIScripts - UIScript::Add("UpdateFilter", ServerList::RefreshVisibleList); - UIScript::Add("RefreshFilter", ServerList::UpdateVisibleList); - - UIScript::Add("RefreshServers", ServerList::Refresh); - UIScript::Add("JoinServer", [] () - { - ServerList::ServerInfo* info = ServerList::GetServer(ServerList::CurrentServer); - - if (info) - { - Party::Connect(info->Addr); - } - }); - UIScript::Add("ServerSort", [] (UIScript::Token token) - { - int key = token.Get(); - - if (ServerList::SortKey == key) - { - ServerList::SortAsc = !ServerList::SortAsc; - } - else - { - ServerList::SortKey = key; - ServerList::SortAsc = true; - } - - Logger::Print("Sorting server list by token: %d\n", ServerList::SortKey); - ServerList::SortList(); - }); - UIScript::Add("CreateListFavorite", [] () - { - ServerList::ServerInfo* info = ServerList::GetCurrentServer(); - - if (info) - { - ServerList::StoreFavourite(info->Addr.GetString()); - } - }); - UIScript::Add("CreateFavorite", [] () - { - ServerList::StoreFavourite(Dvar::Var("ui_favoriteAddress").Get()); - }); - UIScript::Add("CreateCurrentServerFavorite", []() - { - if (Dvar::Var("cl_ingame").Get()) - { - std::string addressText = Network::Address(*Game::connectedHost).GetString(); - if (addressText != "0.0.0.0:0" && addressText != "loopback") - { - ServerList::StoreFavourite(addressText); - } - } - }); - - // Add required ownerDraws - UIScript::AddOwnerDraw(220, ServerList::UpdateSource); - UIScript::AddOwnerDraw(253, ServerList::UpdateGameType); - - // Add frame callback - Renderer::OnFrame(ServerList::Frame); - } - - ServerList::~ServerList() - { - ServerList::OnlineList.clear(); - ServerList::OfflineList.clear(); - ServerList::FavouriteList.clear(); - ServerList::VisibleList.clear(); - - ServerList::RefreshContainer.Mutex.lock(); - ServerList::RefreshContainer.AwatingList = false; - ServerList::RefreshContainer.Servers.clear(); - ServerList::RefreshContainer.Mutex.unlock(); - } -} +#include "STDInclude.hpp" + +namespace Components +{ + bool ServerList::SortAsc = true; + int ServerList::SortKey = ServerList::Column::Ping; + + unsigned int ServerList::CurrentServer = 0; + ServerList::Container ServerList::RefreshContainer; + + std::vector ServerList::OnlineList; + std::vector ServerList::OfflineList; + std::vector ServerList::FavouriteList; + + std::vector ServerList::VisibleList; + + std::vector* ServerList::GetList() + { + if (ServerList::IsOnlineList()) + { + return &ServerList::OnlineList; + } + else if (ServerList::IsOfflineList()) + { + return &ServerList::OfflineList; + } + else if (ServerList::IsFavouriteList()) + { + return &ServerList::FavouriteList; + } + + return nullptr; + } + + bool ServerList::IsFavouriteList() + { + return (Dvar::Var("ui_netSource").Get() == 2); + } + + bool ServerList::IsOfflineList() + { + return (Dvar::Var("ui_netSource").Get() == 0); + } + + bool ServerList::IsOnlineList() + { + return (Dvar::Var("ui_netSource").Get() == 1); + } + + unsigned int ServerList::GetServerCount() + { + return ServerList::VisibleList.size(); + } + + const char* ServerList::GetServerText(unsigned int index, int column) + { + ServerList::ServerInfo* info = ServerList::GetServer(index); + + if (info) + { + return ServerList::GetServerText(info, column); + } + + return ""; + } + + const char* ServerList::GetServerText(ServerList::ServerInfo* server, int column) + { + if (!server) return ""; + + switch (column) + { + case Column::Password: + { + return (server->Password ? "X" : ""); + } + + case Column::Matchtype: + { + return ((server->MatchType == 1) ? "P" : "M"); + } + + case Column::Hostname: + { + return server->Hostname.data(); + } + + case Column::Mapname: + { + return Game::UI_LocalizeMapName(server->Mapname.data()); + } + + case Column::Players: + { + return Utils::String::VA("%i (%i)", server->Clients, server->MaxClients); + } + + case Column::Gametype: + { + return Game::UI_LocalizeGameType(server->Gametype.data()); + } + + case Column::Mod: + { + if (server->Mod != "") + { + return (server->Mod.data() + 5); + } + + return ""; + } + + case Column::Ping: + { + return Utils::String::VA("%i", server->Ping); + } + } + + return ""; + } + + void ServerList::SelectServer(unsigned int index) + { + ServerList::CurrentServer = index; + + ServerList::ServerInfo* info = ServerList::GetCurrentServer(); + + if (info) + { + Dvar::Var("ui_serverSelected").Set(true); + Dvar::Var("ui_serverSelectedMap").Set(info->Mapname); + } + else + { + Dvar::Var("ui_serverSelected").Set(false); + } + } + + void ServerList::UpdateVisibleList() + { + auto list = ServerList::GetList(); + if (!list) return; + + std::vector tempList(*list); + + if (tempList.empty()) + { + ServerList::Refresh(); + } + else + { + list->clear(); + + ServerList::RefreshContainer.Mutex.lock(); + + ServerList::RefreshContainer.SendCount = 0; + ServerList::RefreshContainer.SentCount = 0; + + for (auto server : tempList) + { + ServerList::InsertRequest(server.Addr, false); + } + + ServerList::RefreshContainer.Mutex.unlock(); + } + } + + void ServerList::RefreshVisibleList() + { + Dvar::Var("ui_serverSelected").Set(false); + + ServerList::VisibleList.clear(); + + auto list = ServerList::GetList(); + if (!list) return; + + // Refresh entirely, if there is no entry in the list + if (list->empty()) + { + ServerList::Refresh(); + return; + } + + bool ui_browserShowFull = Dvar::Var("ui_browserShowFull").Get(); + bool ui_browserShowEmpty = Dvar::Var("ui_browserShowEmpty").Get(); + int ui_browserShowHardcore = Dvar::Var("ui_browserKillcam").Get(); + int ui_browserShowPassword = Dvar::Var("ui_browserShowPassword").Get(); + int ui_browserMod = Dvar::Var("ui_browserMod").Get(); + int ui_joinGametype = Dvar::Var("ui_joinGametype").Get(); + + for (unsigned int i = 0; i < list->size(); ++i) + { + ServerList::ServerInfo* info = &(*list)[i]; + + // Filter full servers + if (!ui_browserShowFull && info->Clients >= info->MaxClients) continue; + + // Filter empty servers + if (!ui_browserShowEmpty && info->Clients <= 0) continue; + + // Filter hardcore servers + if ((ui_browserShowHardcore == 0 && info->Hardcore) || (ui_browserShowHardcore == 1 && !info->Hardcore)) continue; + + // Filter servers with password + if ((ui_browserShowPassword == 0 && info->Password) || (ui_browserShowPassword == 1 && !info->Password)) continue; + + // Don't show modded servers + if ((ui_browserMod == 0 && info->Mod.size()) || (ui_browserMod == 1 && !info->Mod.size())) continue; + + // Filter by gametype + if (ui_joinGametype > 0 && (ui_joinGametype -1) < *Game::gameTypeCount && Game::gameTypes[(ui_joinGametype - 1)].gameType != info->Gametype) continue; + + ServerList::VisibleList.push_back(i); + } + + ServerList::SortList(); + } + + void ServerList::Refresh() + { + Dvar::Var("ui_serverSelected").Set(false); + Localization::Set("MPUI_SERVERQUERIED", "Sent requests: 0/0"); + +// ServerList::OnlineList.clear(); +// ServerList::OfflineList.clear(); +// ServerList::FavouriteList.clear(); + + auto list = ServerList::GetList(); + if (list) list->clear(); + + ServerList::VisibleList.clear(); + ServerList::RefreshContainer.Mutex.lock(); + ServerList::RefreshContainer.Servers.clear(); + ServerList::RefreshContainer.SendCount = 0; + ServerList::RefreshContainer.SentCount = 0; + ServerList::RefreshContainer.Mutex.unlock(); + + if (ServerList::IsOfflineList()) + { + Discovery::Perform(); + } + else if (ServerList::IsOnlineList()) + { +#ifdef USE_LEGACY_SERVER_LIST + ServerList::RefreshContainer.AwatingList = true; + ServerList::RefreshContainer.AwaitTime = Game::Com_Milliseconds(); + + int masterPort = Dvar::Var("masterPort").Get(); + const char* masterServerName = Dvar::Var("masterServerName").Get(); + + ServerList::RefreshContainer.Host = Network::Address(Utils::VA("%s:%u", masterServerName, masterPort)); + + Logger::Print("Sending serverlist request to master: %s:%u\n", masterServerName, masterPort); + + Network::SendCommand(ServerList::RefreshContainer.Host, "getservers", Utils::VA("IW4 %i full empty", PROTOCOL)); + //Network::SendCommand(ServerList::RefreshContainer.Host, "getservers", "0 full empty"); +#else + Node::SyncNodeList(); +#endif + } + else if (ServerList::IsFavouriteList()) + { + ServerList::LoadFavourties(); + } + } + + void ServerList::StoreFavourite(std::string server) + { + //json11::Json::parse() + std::vector servers; + + if (Utils::IO::FileExists("players/favourites.json")) + { + std::string data = Utils::IO::ReadFile("players/favourites.json"); + json11::Json object = json11::Json::parse(data, data); + + if (!object.is_array()) + { + Logger::Print("Favourites storage file is invalid!\n"); + Game::MessageBox("Favourites storage file is invalid!", "Error"); + return; + } + + auto storedServers = object.array_items(); + + for (unsigned int i = 0; i < storedServers.size(); ++i) + { + if (!storedServers[i].is_string()) continue; + if (storedServers[i].string_value() == server) + { + Game::MessageBox("Server already marked as favourite.", "Error"); + return; + } + + servers.push_back(storedServers[i].string_value()); + } + } + + servers.push_back(server); + + json11::Json data = json11::Json(servers); + Utils::IO::WriteFile("players/favourites.json", data.dump()); + Game::MessageBox("Server added to favourites.", "Success"); + } + + void ServerList::LoadFavourties() + { + if (ServerList::IsFavouriteList() && Utils::IO::FileExists("players/favourites.json")) + { + auto list = ServerList::GetList(); + if (list) list->clear(); + + std::string data = Utils::IO::ReadFile("players/favourites.json"); + json11::Json object = json11::Json::parse(data, data); + + if (!object.is_array()) + { + Logger::Print("Favourites storage file is invalid!\n"); + Game::MessageBox("Favourites storage file is invalid!", "Error"); + return; + } + + auto servers = object.array_items(); + + for (unsigned int i = 0; i < servers.size(); ++i) + { + if(!servers[i].is_string()) continue; + ServerList::InsertRequest(servers[i].string_value(), true); + } + } + } + + void ServerList::InsertRequest(Network::Address address, bool acquireMutex) + { + if (acquireMutex) ServerList::RefreshContainer.Mutex.lock(); + + ServerList::Container::ServerContainer container; + container.Sent = false; + container.Target = address; + + bool alreadyInserted = false; + for (auto &server : ServerList::RefreshContainer.Servers) + { + if (server.Target == container.Target) + { + alreadyInserted = true; + break; + } + } + + if (!alreadyInserted) + { + ServerList::RefreshContainer.Servers.push_back(container); + + auto list = ServerList::GetList(); + if (list) + { + for (auto server : *list) + { + if (server.Addr == container.Target) + { + --ServerList::RefreshContainer.SendCount; + --ServerList::RefreshContainer.SentCount; + break; + } + } + } + + ++ServerList::RefreshContainer.SendCount; + } + + if (acquireMutex) ServerList::RefreshContainer.Mutex.unlock(); + } + + void ServerList::Insert(Network::Address address, Utils::InfoString info) + { + ServerList::RefreshContainer.Mutex.lock(); + + for (auto i = ServerList::RefreshContainer.Servers.begin(); i != ServerList::RefreshContainer.Servers.end();) + { + // Our desired server + if (i->Target == address && i->Sent) + { + // Challenge did not match + if (i->Challenge != info.Get("challenge")) + { + // Shall we remove the server from the queue? + // Better not, it might send a second response with the correct challenge. + // This might happen when users refresh twice (or more often) in a short period of time + break; + } + + ServerInfo server; + server.Hostname = info.Get("hostname"); + server.Mapname = info.Get("mapname"); + server.Gametype = info.Get("gametype"); + server.Shortversion = info.Get("shortversion"); + server.Mod = info.Get("fs_game"); + server.MatchType = atoi(info.Get("matchtype").data()); + server.Clients = atoi(info.Get("clients").data()); + server.MaxClients = atoi(info.Get("sv_maxclients").data()); + server.Password = (atoi(info.Get("isPrivate").data()) != 0); + server.Hardcore = (atoi(info.Get("hc").data()) != 0); + server.Ping = (Game::Sys_Milliseconds() - i->SendTime); + server.Addr = address; + + // Remove server from queue + i = ServerList::RefreshContainer.Servers.erase(i); + + // Check if already inserted and remove + auto list = ServerList::GetList(); + if (!list) return; + + unsigned int k = 0; + for (auto j = list->begin(); j != list->end(); ++k) + { + if (j->Addr == address) + { + j = list->erase(j); + } + else + { + ++j; + } + } + + // Also remove from visible list + for (auto j = ServerList::VisibleList.begin(); j != ServerList::VisibleList.end();) + { + if (*j == k) + { + j = ServerList::VisibleList.erase(j); + } + else + { + ++j; + } + } + + if (info.Get("gamename") == "IW4" + && server.MatchType +#ifndef DEBUG + && server.Shortversion == VERSION_STR +#endif + ) + { + auto lList = ServerList::GetList(); + + if (lList) + { + lList->push_back(server); + ServerList::RefreshVisibleList(); + } + } + + break; + } + else + { + ++i; + } + } + + ServerList::RefreshContainer.Mutex.unlock(); + } + + ServerList::ServerInfo* ServerList::GetCurrentServer() + { + return ServerList::GetServer(ServerList::CurrentServer); + } + + void ServerList::SortList() + { + qsort(ServerList::VisibleList.data(), ServerList::VisibleList.size(), sizeof(int), [] (const void* first, const void* second) + { + const unsigned int server1 = *static_cast(first); + const unsigned int server2 = *static_cast(second); + + ServerInfo* info1 = nullptr; + ServerInfo* info2 = nullptr; + + auto list = ServerList::GetList(); + if (!list) return 0; + + if (list->size() > server1) info1 = &(*list)[server1]; + if (list->size() > server2) info2 = &(*list)[server2]; + + if (!info1) return 1; + if (!info2) return -1; + + // Numerical comparisons + if (ServerList::SortKey == ServerList::Column::Ping) + { + return ((info1->Ping - info2->Ping) * (ServerList::SortAsc ? 1 : -1)); + } + else if (ServerList::SortKey == ServerList::Column::Players) + { + return ((info1->Clients - info2->Clients) * (ServerList::SortAsc ? 1 : -1)); + } + + std::string text1 = Colors::Strip(ServerList::GetServerText(info1, ServerList::SortKey)); + std::string text2 = Colors::Strip(ServerList::GetServerText(info2, ServerList::SortKey)); + + // ASCII-based comparison + return (text1.compare(text2) * (ServerList::SortAsc ? 1 : -1)); + }); + } + + ServerList::ServerInfo* ServerList::GetServer(unsigned int index) + { + if (ServerList::VisibleList.size() > index) + { + auto list = ServerList::GetList(); + if (!list) return nullptr; + + if (list->size() > ServerList::VisibleList[index]) + { + return &(*list)[ServerList::VisibleList[index]]; + } + } + + return nullptr; + } + + void ServerList::Frame() + { + if (!ServerList::RefreshContainer.Mutex.try_lock()) return; + + if (ServerList::RefreshContainer.AwatingList) + { + // Check if we haven't got a response within 10 seconds + if (Game::Sys_Milliseconds() - ServerList::RefreshContainer.AwaitTime > 5000) + { + ServerList::RefreshContainer.AwatingList = false; + + Logger::Print("We haven't received a response from the master within %d seconds!\n", (Game::Sys_Milliseconds() - ServerList::RefreshContainer.AwaitTime) / 1000); + } + } + + // Send requests to 10 servers each frame + int SendServers = 10; + + for (unsigned int i = 0; i < ServerList::RefreshContainer.Servers.size(); ++i) + { + ServerList::Container::ServerContainer* server = &ServerList::RefreshContainer.Servers[i]; + if (server->Sent) continue; + + // Found server we can send a request to + server->Sent = true; + SendServers--; + + server->SendTime = Game::Sys_Milliseconds(); + server->Challenge = fmt::sprintf("%X", Utils::Cryptography::Rand::GenerateInt()); + + ++ServerList::RefreshContainer.SentCount; + + Network::SendCommand(server->Target, "getinfo", server->Challenge); + + // Display in the menu, like in COD4 + Localization::Set("MPUI_SERVERQUERIED", fmt::sprintf("Sent requests: %d/%d", ServerList::RefreshContainer.SentCount, ServerList::RefreshContainer.SendCount)); + + if (SendServers <= 0) break; + } + + ServerList::RefreshContainer.Mutex.unlock(); + } + + void ServerList::UpdateSource() + { + Dvar::Var netSource("ui_netSource"); + + int source = netSource.Get(); + + if (++source > netSource.Get()->max.i) + { + source = 0; + } + + netSource.Set(source); + + ServerList::RefreshVisibleList(); + } + + void ServerList::UpdateGameType() + { + Dvar::Var joinGametype("ui_joinGametype"); + + int gametype = joinGametype.Get(); + + if (++gametype > *Game::gameTypeCount) + { + gametype = 0; + } + + joinGametype.Set(gametype); + + ServerList::RefreshVisibleList(); + } + + ServerList::ServerList() + { + ServerList::OnlineList.clear(); + ServerList::VisibleList.clear(); + + Dvar::OnInit([] () + { + Dvar::Register("ui_serverSelected", false, Game::dvar_flag::DVAR_FLAG_NONE, "Whether a server has been selected in the serverlist"); + Dvar::Register("ui_serverSelectedMap", "mp_afghan", Game::dvar_flag::DVAR_FLAG_NONE, "Map of the selected server"); + }); + + Localization::Set("MPUI_SERVERQUERIED", "Sent requests: 0/0"); + + Network::Handle("getServersResponse", [] (Network::Address address, std::string data) + { + if (ServerList::RefreshContainer.Host != address) return; // Only parse from host we sent to + + ServerList::RefreshContainer.AwatingList = false; + + ServerList::RefreshContainer.Mutex.lock(); + + int offset = 0; + int count = ServerList::RefreshContainer.Servers.size(); + ServerList::MasterEntry* entry = nullptr; + + // Find first entry + do + { + entry = reinterpret_cast(const_cast(data.data()) + offset++); + } + while (!entry->HasSeparator() && !entry->IsEndToken()); + + for (int i = 0; !entry[i].IsEndToken() && entry[i].HasSeparator(); ++i) + { + Network::Address serverAddr = address; + serverAddr.SetIP(entry[i].IP); + serverAddr.SetPort(ntohs(entry[i].Port)); + serverAddr.SetType(Game::NA_IP); + + ServerList::InsertRequest(serverAddr, false); + } + + Logger::Print("Parsed %d servers from master\n", ServerList::RefreshContainer.Servers.size() - count); + + ServerList::RefreshContainer.Mutex.unlock(); + }); + + // Set default masterServerName + port and save it +#ifdef USE_LEGACY_SERVER_LIST + Utils::Hook::Set(0x60AD92, "localhost"); + Utils::Hook::Set(0x60AD90, Game::dvar_flag::DVAR_FLAG_SAVED); // masterServerName + Utils::Hook::Set(0x60ADC6, Game::dvar_flag::DVAR_FLAG_SAVED); // masterPort +#endif + + // Add server list feeder + UIFeeder::Add(2.0f, ServerList::GetServerCount, ServerList::GetServerText, ServerList::SelectServer); + + // Add required UIScripts + UIScript::Add("UpdateFilter", ServerList::RefreshVisibleList); + UIScript::Add("RefreshFilter", ServerList::UpdateVisibleList); + + UIScript::Add("RefreshServers", ServerList::Refresh); + UIScript::Add("JoinServer", [] () + { + ServerList::ServerInfo* info = ServerList::GetServer(ServerList::CurrentServer); + + if (info) + { + Party::Connect(info->Addr); + } + }); + UIScript::Add("ServerSort", [] (UIScript::Token token) + { + int key = token.Get(); + + if (ServerList::SortKey == key) + { + ServerList::SortAsc = !ServerList::SortAsc; + } + else + { + ServerList::SortKey = key; + ServerList::SortAsc = true; + } + + Logger::Print("Sorting server list by token: %d\n", ServerList::SortKey); + ServerList::SortList(); + }); + UIScript::Add("CreateListFavorite", [] () + { + ServerList::ServerInfo* info = ServerList::GetCurrentServer(); + + if (info) + { + ServerList::StoreFavourite(info->Addr.GetString()); + } + }); + UIScript::Add("CreateFavorite", [] () + { + ServerList::StoreFavourite(Dvar::Var("ui_favoriteAddress").Get()); + }); + UIScript::Add("CreateCurrentServerFavorite", []() + { + if (Dvar::Var("cl_ingame").Get()) + { + std::string addressText = Network::Address(*Game::connectedHost).GetString(); + if (addressText != "0.0.0.0:0" && addressText != "loopback") + { + ServerList::StoreFavourite(addressText); + } + } + }); + + // Add required ownerDraws + UIScript::AddOwnerDraw(220, ServerList::UpdateSource); + UIScript::AddOwnerDraw(253, ServerList::UpdateGameType); + + // Add frame callback + Renderer::OnFrame(ServerList::Frame); + } + + ServerList::~ServerList() + { + ServerList::OnlineList.clear(); + ServerList::OfflineList.clear(); + ServerList::FavouriteList.clear(); + ServerList::VisibleList.clear(); + + ServerList::RefreshContainer.Mutex.lock(); + ServerList::RefreshContainer.AwatingList = false; + ServerList::RefreshContainer.Servers.clear(); + ServerList::RefreshContainer.Mutex.unlock(); + } +} diff --git a/src/Components/Modules/StructuredData.cpp b/src/Components/Modules/StructuredData.cpp index f8ac4dc8..0542720d 100644 --- a/src/Components/Modules/StructuredData.cpp +++ b/src/Components/Modules/StructuredData.cpp @@ -1,213 +1,213 @@ -#include "STDInclude.hpp" - -namespace Components -{ - Utils::Memory::Allocator StructuredData::MemAllocator; - - const char* StructuredData::EnumTranslation[ENUM_MAX] = - { - "features", - "weapons", - "attachements", - "challenges", - "camos", - "perks", - "killstreaks", - "accolades", - "cardicons", - "cardtitles", - "cardnameplates", - "teams", - "gametypes" - }; - - void StructuredData::PatchPlayerDataEnum(Game::StructuredDataDef* data, StructuredData::PlayerDataType type, std::vector& entries) - { - if (!data || type >= StructuredData::PlayerDataType::ENUM_MAX) return; - - Game::StructuredDataEnum* dataEnum = &data->enums[type]; - - // Find last index so we can add our offset to it. - // This whole procedure is potentially unsafe. - // If any index changes, everything gets shifted and the stats are fucked. - int lastIndex = 0; - for (int i = 0; i < dataEnum->numIndices; ++i) - { - if (dataEnum->indices[i].index > lastIndex) - { - lastIndex = dataEnum->indices[i].index; - } - } - - // Calculate new count - unsigned int indexCount = dataEnum->numIndices + entries.size(); - - // Allocate new entries - Game::StructuredDataEnumEntry* indices = StructuredData::MemAllocator.AllocateArray(indexCount); - std::memcpy(indices, dataEnum->indices, sizeof(Game::StructuredDataEnumEntry) * dataEnum->numIndices); - - for (unsigned int i = 0; i < entries.size(); ++i) - { - unsigned int pos = 0; - - for (; pos < (dataEnum->numIndices + i); pos++) - { - if (indices[pos].key == entries[i]) - { - Logger::Error("Duplicate playerdatadef entry found: %s", entries[i].data()); - } - - // We found our position - if (entries[i] < indices[pos].key) - { - break; - } - } - - // TODO directly shift the data using memmove - for (unsigned int j = dataEnum->numIndices + i; j > pos && j < indexCount; j--) - { - std::memcpy(&indices[j], &indices[j - 1], sizeof(Game::StructuredDataEnumEntry)); - } - - indices[pos].index = i + lastIndex; - indices[pos].key = StructuredData::MemAllocator.DuplicateString(entries[i]); - } - - // Apply our patches - dataEnum->numIndices = indexCount; - dataEnum->indices = indices; - } - - StructuredData::StructuredData() - { - // Only execute this when building zones - if (!ZoneBuilder::IsEnabled()) return; - - AssetHandler::OnLoad([] (Game::XAssetType type, Game::XAssetHeader asset, std::string filename, bool* restrict) - { - // Only intercept playerdatadef loading - if (filename != "mp/playerdata.def" || type != Game::XAssetType::ASSET_TYPE_STRUCTUREDDATADEF) return; - - // Store asset - Game::StructuredDataDefSet* data = asset.structuredData; - if (!data) return; - - if (data->count != 1) - { - Logger::Error("PlayerDataDefSet contains more than 1 definition!"); - return; - } - - if (data->data[0].version != 155) - { - Logger::Error("Initial PlayerDataDef is not version 155, patching not possible!"); - return; - } - - std::map>> patchDefinitions; - - // First check if all versions are present - for (int i = 156;; ++i) - { - FileSystem::File definition(Utils::VA("%s/%d.json", filename.data(), i)); - if (!definition.Exists()) break; - - std::vector> enumContainer; - - std::string errors; - json11::Json defData = json11::Json::parse(definition.GetBuffer(), errors); - - if (!errors.empty()) - { - Logger::Error("Parsing patch file '%s' for PlayerDataDef version %d failed: %s", definition.GetName().data(), i, errors.data()); - return; - } - - if (!defData.is_object()) - { - Logger::Error("PlayerDataDef patch for version %d is invalid!", i); - return; - } - - for (unsigned int pType = 0; pType < StructuredData::PlayerDataType::ENUM_MAX; ++pType) - { - auto enumData = defData[StructuredData::EnumTranslation[pType]]; - - std::vector entryData; - - if (enumData.is_array()) - { - for (auto rawEntry : enumData.array_items()) - { - if (rawEntry.is_string()) - { - entryData.push_back(rawEntry.string_value()); - } - } - } - - enumContainer.push_back(entryData); - } - - patchDefinitions[i] = enumContainer; - } - - // Nothing to patch - if (patchDefinitions.empty()) return; - - // Reallocate the definition - Game::StructuredDataDef* newData = StructuredData::MemAllocator.AllocateArray(data->count + patchDefinitions.size()); - std::memcpy(&newData[patchDefinitions.size()], data->data, sizeof Game::StructuredDataDef * data->count); - - // Prepare the buffers - for (unsigned int i = 0; i < patchDefinitions.size(); ++i) - { - std::memcpy(&newData[i], data->data, sizeof Game::StructuredDataDef); - newData[i].version = (patchDefinitions.size() - i) + 155; - - // Reallocate the enum array - Game::StructuredDataEnum* newEnums = StructuredData::MemAllocator.AllocateArray(data->data->numEnums); - std::memcpy(newEnums, data->data->enums, sizeof Game::StructuredDataEnum * data->data->numEnums); - newData[i].enums = newEnums; - } - - // Apply new data - data->data = newData; - data->count += patchDefinitions.size(); - - // Patch the definition - for (int i = 0; i < data->count; ++i) - { - // No need to patch version 155 - if (newData[i].version == 155) continue; - - if(patchDefinitions.find(newData[i].version) != patchDefinitions.end()) - { - auto patchData = patchDefinitions[newData[i].version]; - - // Invalid patch data - if (patchData.size() != StructuredData::PlayerDataType::ENUM_MAX) - { - Logger::Error("PlayerDataDef patch for version %d wasn't parsed correctly!", newData[i].version); - continue; - } - - // Apply the patch data - for (unsigned int pType = 0; pType < StructuredData::PlayerDataType::ENUM_MAX; ++pType) - { - if (!patchData[pType].empty()) - { - StructuredData::PatchPlayerDataEnum(&newData[i], static_cast(pType), patchData[pType]); - } - } - } - } - }); - } - - StructuredData::~StructuredData() - { - StructuredData::MemAllocator.Free(); - } -} +#include "STDInclude.hpp" + +namespace Components +{ + Utils::Memory::Allocator StructuredData::MemAllocator; + + const char* StructuredData::EnumTranslation[ENUM_MAX] = + { + "features", + "weapons", + "attachements", + "challenges", + "camos", + "perks", + "killstreaks", + "accolades", + "cardicons", + "cardtitles", + "cardnameplates", + "teams", + "gametypes" + }; + + void StructuredData::PatchPlayerDataEnum(Game::StructuredDataDef* data, StructuredData::PlayerDataType type, std::vector& entries) + { + if (!data || type >= StructuredData::PlayerDataType::ENUM_MAX) return; + + Game::StructuredDataEnum* dataEnum = &data->enums[type]; + + // Find last index so we can add our offset to it. + // This whole procedure is potentially unsafe. + // If any index changes, everything gets shifted and the stats are fucked. + int lastIndex = 0; + for (int i = 0; i < dataEnum->numIndices; ++i) + { + if (dataEnum->indices[i].index > lastIndex) + { + lastIndex = dataEnum->indices[i].index; + } + } + + // Calculate new count + unsigned int indexCount = dataEnum->numIndices + entries.size(); + + // Allocate new entries + Game::StructuredDataEnumEntry* indices = StructuredData::MemAllocator.AllocateArray(indexCount); + std::memcpy(indices, dataEnum->indices, sizeof(Game::StructuredDataEnumEntry) * dataEnum->numIndices); + + for (unsigned int i = 0; i < entries.size(); ++i) + { + unsigned int pos = 0; + + for (; pos < (dataEnum->numIndices + i); pos++) + { + if (indices[pos].key == entries[i]) + { + Logger::Error("Duplicate playerdatadef entry found: %s", entries[i].data()); + } + + // We found our position + if (entries[i] < indices[pos].key) + { + break; + } + } + + // TODO directly shift the data using memmove + for (unsigned int j = dataEnum->numIndices + i; j > pos && j < indexCount; j--) + { + std::memcpy(&indices[j], &indices[j - 1], sizeof(Game::StructuredDataEnumEntry)); + } + + indices[pos].index = i + lastIndex; + indices[pos].key = StructuredData::MemAllocator.DuplicateString(entries[i]); + } + + // Apply our patches + dataEnum->numIndices = indexCount; + dataEnum->indices = indices; + } + + StructuredData::StructuredData() + { + // Only execute this when building zones + if (!ZoneBuilder::IsEnabled()) return; + + AssetHandler::OnLoad([] (Game::XAssetType type, Game::XAssetHeader asset, std::string filename, bool* restrict) + { + // Only intercept playerdatadef loading + if (filename != "mp/playerdata.def" || type != Game::XAssetType::ASSET_TYPE_STRUCTUREDDATADEF) return; + + // Store asset + Game::StructuredDataDefSet* data = asset.structuredData; + if (!data) return; + + if (data->count != 1) + { + Logger::Error("PlayerDataDefSet contains more than 1 definition!"); + return; + } + + if (data->data[0].version != 155) + { + Logger::Error("Initial PlayerDataDef is not version 155, patching not possible!"); + return; + } + + std::map>> patchDefinitions; + + // First check if all versions are present + for (int i = 156;; ++i) + { + FileSystem::File definition(fmt::sprintf("%s/%d.json", filename.data(), i)); + if (!definition.Exists()) break; + + std::vector> enumContainer; + + std::string errors; + json11::Json defData = json11::Json::parse(definition.GetBuffer(), errors); + + if (!errors.empty()) + { + Logger::Error("Parsing patch file '%s' for PlayerDataDef version %d failed: %s", definition.GetName().data(), i, errors.data()); + return; + } + + if (!defData.is_object()) + { + Logger::Error("PlayerDataDef patch for version %d is invalid!", i); + return; + } + + for (unsigned int pType = 0; pType < StructuredData::PlayerDataType::ENUM_MAX; ++pType) + { + auto enumData = defData[StructuredData::EnumTranslation[pType]]; + + std::vector entryData; + + if (enumData.is_array()) + { + for (auto rawEntry : enumData.array_items()) + { + if (rawEntry.is_string()) + { + entryData.push_back(rawEntry.string_value()); + } + } + } + + enumContainer.push_back(entryData); + } + + patchDefinitions[i] = enumContainer; + } + + // Nothing to patch + if (patchDefinitions.empty()) return; + + // Reallocate the definition + Game::StructuredDataDef* newData = StructuredData::MemAllocator.AllocateArray(data->count + patchDefinitions.size()); + std::memcpy(&newData[patchDefinitions.size()], data->data, sizeof Game::StructuredDataDef * data->count); + + // Prepare the buffers + for (unsigned int i = 0; i < patchDefinitions.size(); ++i) + { + std::memcpy(&newData[i], data->data, sizeof Game::StructuredDataDef); + newData[i].version = (patchDefinitions.size() - i) + 155; + + // Reallocate the enum array + Game::StructuredDataEnum* newEnums = StructuredData::MemAllocator.AllocateArray(data->data->numEnums); + std::memcpy(newEnums, data->data->enums, sizeof Game::StructuredDataEnum * data->data->numEnums); + newData[i].enums = newEnums; + } + + // Apply new data + data->data = newData; + data->count += patchDefinitions.size(); + + // Patch the definition + for (int i = 0; i < data->count; ++i) + { + // No need to patch version 155 + if (newData[i].version == 155) continue; + + if(patchDefinitions.find(newData[i].version) != patchDefinitions.end()) + { + auto patchData = patchDefinitions[newData[i].version]; + + // Invalid patch data + if (patchData.size() != StructuredData::PlayerDataType::ENUM_MAX) + { + Logger::Error("PlayerDataDef patch for version %d wasn't parsed correctly!", newData[i].version); + continue; + } + + // Apply the patch data + for (unsigned int pType = 0; pType < StructuredData::PlayerDataType::ENUM_MAX; ++pType) + { + if (!patchData[pType].empty()) + { + StructuredData::PatchPlayerDataEnum(&newData[i], static_cast(pType), patchData[pType]); + } + } + } + } + }); + } + + StructuredData::~StructuredData() + { + StructuredData::MemAllocator.Free(); + } +} diff --git a/src/Components/Modules/Theatre.cpp b/src/Components/Modules/Theatre.cpp index b86f2e00..7f18439b 100644 --- a/src/Components/Modules/Theatre.cpp +++ b/src/Components/Modules/Theatre.cpp @@ -1,376 +1,376 @@ -#include "STDInclude.hpp" - -namespace Components -{ - Theatre::Container Theatre::DemoContainer; - - char Theatre::BaselineSnapshot[131072] = { 0 }; - int Theatre::BaselineSnapshotMsgLen; - int Theatre::BaselineSnapshotMsgOff; - - void Theatre::GamestateWriteStub(Game::msg_t* msg, char byte) - { - Game::MSG_WriteLong(msg, 0); - Game::MSG_WriteByte(msg, byte); - } - - void Theatre::RecordGamestateStub() - { - int sequence = (*Game::serverMessageSequence - 1); - Game::FS_Write(&sequence, 4, *Game::demoFile); - } - - void Theatre::StoreBaseline(PBYTE snapshotMsg) - { - // Store offset and length - Theatre::BaselineSnapshotMsgLen = *reinterpret_cast(snapshotMsg + 20); - Theatre::BaselineSnapshotMsgOff = *reinterpret_cast(snapshotMsg + 28) - 7; - - // Copy to our snapshot buffer - std::memcpy(Theatre::BaselineSnapshot, *reinterpret_cast(snapshotMsg + 8), *reinterpret_cast(snapshotMsg + 20)); - } - - void __declspec(naked) Theatre::BaselineStoreStub() - { - _asm - { - push edi - call Theatre::StoreBaseline - pop edi - - mov edx, 5ABEF5h - jmp edx - } - } - - void Theatre::WriteBaseline() - { - static char bufData[131072]; - static char cmpData[131072]; - - Game::msg_t buf; - - Game::MSG_Init(&buf, bufData, 131072); - Game::MSG_WriteData(&buf, &Theatre::BaselineSnapshot[Theatre::BaselineSnapshotMsgOff], Theatre::BaselineSnapshotMsgLen - Theatre::BaselineSnapshotMsgOff); - Game::MSG_WriteByte(&buf, 6); - - int compressedSize = Game::MSG_WriteBitsCompress(false, buf.data, cmpData, buf.cursize); - int fileCompressedSize = compressedSize + 4; - - int byte8 = 8; - char byte0 = 0; - - Game::FS_Write(&byte0, 1, *Game::demoFile); - Game::FS_Write(Game::serverMessageSequence, 4, *Game::demoFile); - Game::FS_Write(&fileCompressedSize, 4, *Game::demoFile); - Game::FS_Write(&byte8, 4, *Game::demoFile); - - for (int i = 0; i < compressedSize; i += 1024) - { - int size = min(compressedSize - i, 1024); - - if (i + size >= sizeof(cmpData)) - { - Logger::Print("Error: Writing compressed demo baseline exceeded buffer\n"); - break; - } - - Game::FS_Write(&cmpData[i], size, *Game::demoFile); - } - } - - void __declspec(naked) Theatre::BaselineToFileStub() - { - __asm - { - call Theatre::WriteBaseline - - // Restore overwritten operation - mov ecx, 0A5E9C4h - mov [ecx], 0 - - // Return to original code - mov ecx, 5A863Ah - jmp ecx - } - } - - void __declspec(naked) Theatre::AdjustTimeDeltaStub() - { - __asm - { - mov eax, Game::demoPlaying - mov eax, [eax] - test al, al - jz continue - - // delta doesn't drift for demos - retn - - continue: - mov eax, 5A1AD0h - jmp eax - } - } - - void __declspec(naked) Theatre::ServerTimedOutStub() - { - __asm - { - mov eax, Game::demoPlaying - mov eax, [eax] - test al, al - jz continue - - mov eax, 5A8E70h - jmp eax - - continue: - mov eax, 0B2BB90h - mov esi, 5A8E08h - jmp esi - } - } - - void __declspec(naked) Theatre::UISetActiveMenuStub() - { - __asm - { - mov eax, Game::demoPlaying - mov eax, [eax] - test al, al - jz continue - - mov eax, 4CB49Ch - jmp eax - - continue: - mov ecx, [esp + 10h] - push 10h - push ecx - mov eax, 4CB3F6h - jmp eax - } - } - - void Theatre::RecordStub(int channel, char* message, char* file) - { - Game::Com_Printf(channel, message, file); - - Theatre::DemoContainer.CurrentInfo.Name = file; - Theatre::DemoContainer.CurrentInfo.Mapname = Dvar::Var("mapname").Get(); - Theatre::DemoContainer.CurrentInfo.Gametype = Dvar::Var("g_gametype").Get(); - Theatre::DemoContainer.CurrentInfo.Author = Steam::SteamFriends()->GetPersonaName(); - Theatre::DemoContainer.CurrentInfo.Length = Game::Sys_Milliseconds(); - std::time(&Theatre::DemoContainer.CurrentInfo.TimeStamp); - } - - void Theatre::StopRecordStub(int channel, char* message) - { - Game::Com_Printf(channel, message); - - // Store correct length - Theatre::DemoContainer.CurrentInfo.Length = Game::Sys_Milliseconds() - Theatre::DemoContainer.CurrentInfo.Length; - - // Write metadata - FileSystem::FileWriter meta(Utils::VA("%s.json", Theatre::DemoContainer.CurrentInfo.Name.data())); - meta.Write(json11::Json(Theatre::DemoContainer.CurrentInfo).dump()); - } - - void Theatre::LoadDemos() - { - Theatre::DemoContainer.CurrentSelection = 0; - Theatre::DemoContainer.Demos.clear(); - - auto demos = FileSystem::GetFileList("demos/", "dm_13"); - - for (auto demo : demos) - { - FileSystem::File meta(Utils::VA("demos/%s.json", demo.data())); - - if (meta.Exists()) - { - std::string error; - json11::Json metaObject = json11::Json::parse(meta.GetBuffer(), error); - - if (metaObject.is_object()) - { - Theatre::Container::DemoInfo info; - - info.Name = demo.substr(0, demo.find_last_of(".")); - info.Author = metaObject["author"].string_value(); - info.Gametype = metaObject["gametype"].string_value(); - info.Mapname = metaObject["mapname"].string_value(); - info.Length = (int)metaObject["length"].number_value(); - info.TimeStamp = _atoi64(metaObject["timestamp"].string_value().data()); - - Theatre::DemoContainer.Demos.push_back(info); - } - } - } - - // Reverse, latest demo first! - std::reverse(Theatre::DemoContainer.Demos.begin(), Theatre::DemoContainer.Demos.end()); - } - - void Theatre::DeleteDemo() - { - if (Theatre::DemoContainer.CurrentSelection < Theatre::DemoContainer.Demos.size()) - { - Theatre::Container::DemoInfo info = Theatre::DemoContainer.Demos[Theatre::DemoContainer.CurrentSelection]; - - Logger::Print("Deleting demo %s...\n", info.Name.data()); - - FileSystem::DeleteFile("demos", info.Name + ".dm_13"); - FileSystem::DeleteFile("demos", info.Name + ".dm_13.json"); - - // Reset our ui_demo_* dvars here, because the theater menu needs it. - Dvar::Var("ui_demo_mapname").Set(""); - Dvar::Var("ui_demo_mapname_localized").Set(""); - Dvar::Var("ui_demo_gametype").Set(""); - Dvar::Var("ui_demo_length").Set(""); - Dvar::Var("ui_demo_author").Set(""); - Dvar::Var("ui_demo_date").Set(""); - - // Reload demos - Theatre::LoadDemos(); - } - } - - void Theatre::PlayDemo() - { - if (Theatre::DemoContainer.CurrentSelection < Theatre::DemoContainer.Demos.size()) - { - Command::Execute(Utils::VA("demo %s", Theatre::DemoContainer.Demos[Theatre::DemoContainer.CurrentSelection].Name.data()), true); - Command::Execute("demoback", false); - } - } - - unsigned int Theatre::GetDemoCount() - { - return Theatre::DemoContainer.Demos.size(); - } - - // Omit column here - const char* Theatre::GetDemoText(unsigned int item, int column) - { - if (item < Theatre::DemoContainer.Demos.size()) - { - Theatre::Container::DemoInfo info = Theatre::DemoContainer.Demos[item]; - - return Utils::VA("%s on %s", Game::UI_LocalizeGameType(info.Gametype.data()), Game::UI_LocalizeMapName(info.Mapname.data())); - } - - return ""; - } - - void Theatre::SelectDemo(unsigned int index) - { - if (index < Theatre::DemoContainer.Demos.size()) - { - Theatre::DemoContainer.CurrentSelection = index; - Theatre::Container::DemoInfo info = Theatre::DemoContainer.Demos[index]; - - Dvar::Var("ui_demo_mapname").Set(info.Mapname); - Dvar::Var("ui_demo_mapname_localized").Set(Game::UI_LocalizeMapName(info.Mapname.data())); - Dvar::Var("ui_demo_gametype").Set(Game::UI_LocalizeGameType(info.Gametype.data())); - Dvar::Var("ui_demo_length").Set(Utils::FormatTimeSpan(info.Length)); - Dvar::Var("ui_demo_author").Set(info.Author); - Dvar::Var("ui_demo_date").Set(std::asctime(std::localtime(&info.TimeStamp))); - } - } - - uint32_t Theatre::InitCGameStub() - { - if (Dvar::Var("cl_autoRecord").Get() && !*Game::demoPlaying) - { - std::vector files; - std::vector demos = FileSystem::GetFileList("demos/", "dm_13"); - - for (auto demo : demos) - { - if (Utils::StartsWith(demo, "auto_")) - { - files.push_back(demo); - } - } - - int numDel = files.size() - Dvar::Var("cl_demosKeep").Get(); - - for (int i = 0; i < numDel; ++i) - { - Logger::Print("Deleting old demo %s\n", files[i].data()); - FileSystem::DeleteFile("demos", files[i].data()); - FileSystem::DeleteFile("demos", Utils::VA("%s.json", files[i].data())); - } - - Command::Execute(Utils::VA("record auto_%I64d", time(0)), true); - } - - return Utils::Hook::Call(0x42BBB0)(); - } - - void Theatre::MapChangeStub() - { - if (*Game::demoRecording) - { - Command::Execute("stoprecord", true); - } - - Utils::Hook::Call(0x464A60)(); - } - - void Theatre::MapChangeSVStub(char* a1, char* a2) - { - if (*Game::demoRecording) - { - Command::Execute("stoprecord", true); - } - - Utils::Hook::Call(0x487C50)(a1, a2); - } - - Theatre::Theatre() - { - Dvar::Register("cl_autoRecord", true, Game::dvar_flag::DVAR_FLAG_SAVED, "Automatically record games."); - Dvar::Register("cl_demosKeep", 30, 1, 999, Game::dvar_flag::DVAR_FLAG_SAVED, "How many demos to keep with autorecord."); - - Utils::Hook(0x5A8370, Theatre::GamestateWriteStub, HOOK_CALL).Install()->Quick(); - Utils::Hook(0x5A85D2, Theatre::RecordGamestateStub, HOOK_CALL).Install()->Quick(); - Utils::Hook(0x5ABE36, Theatre::BaselineStoreStub, HOOK_JUMP).Install()->Quick(); - Utils::Hook(0x5A8630, Theatre::BaselineToFileStub, HOOK_JUMP).Install()->Quick(); - Utils::Hook(0x4CB3EF, Theatre::UISetActiveMenuStub, HOOK_JUMP).Install()->Quick(); - Utils::Hook(0x50320E, Theatre::AdjustTimeDeltaStub, HOOK_CALL).Install()->Quick(); - Utils::Hook(0x5A8E03, Theatre::ServerTimedOutStub, HOOK_JUMP).Install()->Quick(); - - // Hook commands to enforce metadata generation - Utils::Hook(0x5A82AE, Theatre::RecordStub, HOOK_CALL).Install()->Quick(); - Utils::Hook(0x5A8156, Theatre::StopRecordStub, HOOK_CALL).Install()->Quick(); - - // Autorecording - Utils::Hook(0x5A1D6A, Theatre::InitCGameStub, HOOK_CALL).Install()->Quick(); - Utils::Hook(0x4A712A, Theatre::MapChangeStub, HOOK_CALL).Install()->Quick(); - Utils::Hook(0x5AA91C, Theatre::MapChangeSVStub, HOOK_CALL).Install()->Quick(); - - // UIScripts - UIScript::Add("loadDemos", Theatre::LoadDemos); - UIScript::Add("launchDemo", Theatre::PlayDemo); - UIScript::Add("deleteDemo", Theatre::DeleteDemo); - - // Feeder - UIFeeder::Add(10.0f, Theatre::GetDemoCount, Theatre::GetDemoText, Theatre::SelectDemo); - - // set the configstrings stuff to load the default (empty) string table; this should allow demo recording on all gametypes/maps - if(!Dedicated::IsDedicated()) Utils::Hook::Set(0x47440B, "mp/defaultStringTable.csv"); - - // Change font size - Utils::Hook::Set(0x5AC854, 2); - Utils::Hook::Set(0x5AC85A, 2); - -// Command::Add("democycle", [] (Command::Params params) -// { -// // Cmd_FollowCycle_f -// Utils::Hook::Call(0x458ED0)(Game::g_entities, -1); -// }); - } -} +#include "STDInclude.hpp" + +namespace Components +{ + Theatre::Container Theatre::DemoContainer; + + char Theatre::BaselineSnapshot[131072] = { 0 }; + int Theatre::BaselineSnapshotMsgLen; + int Theatre::BaselineSnapshotMsgOff; + + void Theatre::GamestateWriteStub(Game::msg_t* msg, char byte) + { + Game::MSG_WriteLong(msg, 0); + Game::MSG_WriteByte(msg, byte); + } + + void Theatre::RecordGamestateStub() + { + int sequence = (*Game::serverMessageSequence - 1); + Game::FS_Write(&sequence, 4, *Game::demoFile); + } + + void Theatre::StoreBaseline(PBYTE snapshotMsg) + { + // Store offset and length + Theatre::BaselineSnapshotMsgLen = *reinterpret_cast(snapshotMsg + 20); + Theatre::BaselineSnapshotMsgOff = *reinterpret_cast(snapshotMsg + 28) - 7; + + // Copy to our snapshot buffer + std::memcpy(Theatre::BaselineSnapshot, *reinterpret_cast(snapshotMsg + 8), *reinterpret_cast(snapshotMsg + 20)); + } + + void __declspec(naked) Theatre::BaselineStoreStub() + { + _asm + { + push edi + call Theatre::StoreBaseline + pop edi + + mov edx, 5ABEF5h + jmp edx + } + } + + void Theatre::WriteBaseline() + { + static char bufData[131072]; + static char cmpData[131072]; + + Game::msg_t buf; + + Game::MSG_Init(&buf, bufData, 131072); + Game::MSG_WriteData(&buf, &Theatre::BaselineSnapshot[Theatre::BaselineSnapshotMsgOff], Theatre::BaselineSnapshotMsgLen - Theatre::BaselineSnapshotMsgOff); + Game::MSG_WriteByte(&buf, 6); + + int compressedSize = Game::MSG_WriteBitsCompress(false, buf.data, cmpData, buf.cursize); + int fileCompressedSize = compressedSize + 4; + + int byte8 = 8; + char byte0 = 0; + + Game::FS_Write(&byte0, 1, *Game::demoFile); + Game::FS_Write(Game::serverMessageSequence, 4, *Game::demoFile); + Game::FS_Write(&fileCompressedSize, 4, *Game::demoFile); + Game::FS_Write(&byte8, 4, *Game::demoFile); + + for (int i = 0; i < compressedSize; i += 1024) + { + int size = std::min(compressedSize - i, 1024); + + if (i + size >= sizeof(cmpData)) + { + Logger::Print("Error: Writing compressed demo baseline exceeded buffer\n"); + break; + } + + Game::FS_Write(&cmpData[i], size, *Game::demoFile); + } + } + + void __declspec(naked) Theatre::BaselineToFileStub() + { + __asm + { + call Theatre::WriteBaseline + + // Restore overwritten operation + mov ecx, 0A5E9C4h + mov [ecx], 0 + + // Return to original code + mov ecx, 5A863Ah + jmp ecx + } + } + + void __declspec(naked) Theatre::AdjustTimeDeltaStub() + { + __asm + { + mov eax, Game::demoPlaying + mov eax, [eax] + test al, al + jz continue + + // delta doesn't drift for demos + retn + + continue: + mov eax, 5A1AD0h + jmp eax + } + } + + void __declspec(naked) Theatre::ServerTimedOutStub() + { + __asm + { + mov eax, Game::demoPlaying + mov eax, [eax] + test al, al + jz continue + + mov eax, 5A8E70h + jmp eax + + continue: + mov eax, 0B2BB90h + mov esi, 5A8E08h + jmp esi + } + } + + void __declspec(naked) Theatre::UISetActiveMenuStub() + { + __asm + { + mov eax, Game::demoPlaying + mov eax, [eax] + test al, al + jz continue + + mov eax, 4CB49Ch + jmp eax + + continue: + mov ecx, [esp + 10h] + push 10h + push ecx + mov eax, 4CB3F6h + jmp eax + } + } + + void Theatre::RecordStub(int channel, char* message, char* file) + { + Game::Com_Printf(channel, message, file); + + Theatre::DemoContainer.CurrentInfo.Name = file; + Theatre::DemoContainer.CurrentInfo.Mapname = Dvar::Var("mapname").Get(); + Theatre::DemoContainer.CurrentInfo.Gametype = Dvar::Var("g_gametype").Get(); + Theatre::DemoContainer.CurrentInfo.Author = Steam::SteamFriends()->GetPersonaName(); + Theatre::DemoContainer.CurrentInfo.Length = Game::Sys_Milliseconds(); + std::time(&Theatre::DemoContainer.CurrentInfo.TimeStamp); + } + + void Theatre::StopRecordStub(int channel, char* message) + { + Game::Com_Printf(channel, message); + + // Store correct length + Theatre::DemoContainer.CurrentInfo.Length = Game::Sys_Milliseconds() - Theatre::DemoContainer.CurrentInfo.Length; + + // Write metadata + FileSystem::FileWriter meta(fmt::sprintf("%s.json", Theatre::DemoContainer.CurrentInfo.Name.data())); + meta.Write(json11::Json(Theatre::DemoContainer.CurrentInfo).dump()); + } + + void Theatre::LoadDemos() + { + Theatre::DemoContainer.CurrentSelection = 0; + Theatre::DemoContainer.Demos.clear(); + + auto demos = FileSystem::GetFileList("demos/", "dm_13"); + + for (auto demo : demos) + { + FileSystem::File meta(fmt::sprintf("demos/%s.json", demo.data())); + + if (meta.Exists()) + { + std::string error; + json11::Json metaObject = json11::Json::parse(meta.GetBuffer(), error); + + if (metaObject.is_object()) + { + Theatre::Container::DemoInfo info; + + info.Name = demo.substr(0, demo.find_last_of(".")); + info.Author = metaObject["author"].string_value(); + info.Gametype = metaObject["gametype"].string_value(); + info.Mapname = metaObject["mapname"].string_value(); + info.Length = (int)metaObject["length"].number_value(); + info.TimeStamp = _atoi64(metaObject["timestamp"].string_value().data()); + + Theatre::DemoContainer.Demos.push_back(info); + } + } + } + + // Reverse, latest demo first! + std::reverse(Theatre::DemoContainer.Demos.begin(), Theatre::DemoContainer.Demos.end()); + } + + void Theatre::DeleteDemo() + { + if (Theatre::DemoContainer.CurrentSelection < Theatre::DemoContainer.Demos.size()) + { + Theatre::Container::DemoInfo info = Theatre::DemoContainer.Demos[Theatre::DemoContainer.CurrentSelection]; + + Logger::Print("Deleting demo %s...\n", info.Name.data()); + + FileSystem::DeleteFile("demos", info.Name + ".dm_13"); + FileSystem::DeleteFile("demos", info.Name + ".dm_13.json"); + + // Reset our ui_demo_* dvars here, because the theater menu needs it. + Dvar::Var("ui_demo_mapname").Set(""); + Dvar::Var("ui_demo_mapname_localized").Set(""); + Dvar::Var("ui_demo_gametype").Set(""); + Dvar::Var("ui_demo_length").Set(""); + Dvar::Var("ui_demo_author").Set(""); + Dvar::Var("ui_demo_date").Set(""); + + // Reload demos + Theatre::LoadDemos(); + } + } + + void Theatre::PlayDemo() + { + if (Theatre::DemoContainer.CurrentSelection < Theatre::DemoContainer.Demos.size()) + { + Command::Execute(fmt::sprintf("demo %s", Theatre::DemoContainer.Demos[Theatre::DemoContainer.CurrentSelection].Name.data()), true); + Command::Execute("demoback", false); + } + } + + unsigned int Theatre::GetDemoCount() + { + return Theatre::DemoContainer.Demos.size(); + } + + // Omit column here + const char* Theatre::GetDemoText(unsigned int item, int column) + { + if (item < Theatre::DemoContainer.Demos.size()) + { + Theatre::Container::DemoInfo info = Theatre::DemoContainer.Demos[item]; + + return Utils::String::VA("%s on %s", Game::UI_LocalizeGameType(info.Gametype.data()), Game::UI_LocalizeMapName(info.Mapname.data())); + } + + return ""; + } + + void Theatre::SelectDemo(unsigned int index) + { + if (index < Theatre::DemoContainer.Demos.size()) + { + Theatre::DemoContainer.CurrentSelection = index; + Theatre::Container::DemoInfo info = Theatre::DemoContainer.Demos[index]; + + Dvar::Var("ui_demo_mapname").Set(info.Mapname); + Dvar::Var("ui_demo_mapname_localized").Set(Game::UI_LocalizeMapName(info.Mapname.data())); + Dvar::Var("ui_demo_gametype").Set(Game::UI_LocalizeGameType(info.Gametype.data())); + Dvar::Var("ui_demo_length").Set(Utils::String::FormatTimeSpan(info.Length)); + Dvar::Var("ui_demo_author").Set(info.Author); + Dvar::Var("ui_demo_date").Set(std::asctime(std::localtime(&info.TimeStamp))); + } + } + + uint32_t Theatre::InitCGameStub() + { + if (Dvar::Var("cl_autoRecord").Get() && !*Game::demoPlaying) + { + std::vector files; + std::vector demos = FileSystem::GetFileList("demos/", "dm_13"); + + for (auto demo : demos) + { + if (Utils::String::StartsWith(demo, "auto_")) + { + files.push_back(demo); + } + } + + int numDel = files.size() - Dvar::Var("cl_demosKeep").Get(); + + for (int i = 0; i < numDel; ++i) + { + Logger::Print("Deleting old demo %s\n", files[i].data()); + FileSystem::DeleteFile("demos", files[i].data()); + FileSystem::DeleteFile("demos", fmt::sprintf("%s.json", files[i].data())); + } + + Command::Execute(fmt::sprintf("record auto_%I64d", time(0)), true); + } + + return Utils::Hook::Call(0x42BBB0)(); + } + + void Theatre::MapChangeStub() + { + if (*Game::demoRecording) + { + Command::Execute("stoprecord", true); + } + + Utils::Hook::Call(0x464A60)(); + } + + void Theatre::MapChangeSVStub(char* a1, char* a2) + { + if (*Game::demoRecording) + { + Command::Execute("stoprecord", true); + } + + Utils::Hook::Call(0x487C50)(a1, a2); + } + + Theatre::Theatre() + { + Dvar::Register("cl_autoRecord", true, Game::dvar_flag::DVAR_FLAG_SAVED, "Automatically record games."); + Dvar::Register("cl_demosKeep", 30, 1, 999, Game::dvar_flag::DVAR_FLAG_SAVED, "How many demos to keep with autorecord."); + + Utils::Hook(0x5A8370, Theatre::GamestateWriteStub, HOOK_CALL).Install()->Quick(); + Utils::Hook(0x5A85D2, Theatre::RecordGamestateStub, HOOK_CALL).Install()->Quick(); + Utils::Hook(0x5ABE36, Theatre::BaselineStoreStub, HOOK_JUMP).Install()->Quick(); + Utils::Hook(0x5A8630, Theatre::BaselineToFileStub, HOOK_JUMP).Install()->Quick(); + Utils::Hook(0x4CB3EF, Theatre::UISetActiveMenuStub, HOOK_JUMP).Install()->Quick(); + Utils::Hook(0x50320E, Theatre::AdjustTimeDeltaStub, HOOK_CALL).Install()->Quick(); + Utils::Hook(0x5A8E03, Theatre::ServerTimedOutStub, HOOK_JUMP).Install()->Quick(); + + // Hook commands to enforce metadata generation + Utils::Hook(0x5A82AE, Theatre::RecordStub, HOOK_CALL).Install()->Quick(); + Utils::Hook(0x5A8156, Theatre::StopRecordStub, HOOK_CALL).Install()->Quick(); + + // Autorecording + Utils::Hook(0x5A1D6A, Theatre::InitCGameStub, HOOK_CALL).Install()->Quick(); + Utils::Hook(0x4A712A, Theatre::MapChangeStub, HOOK_CALL).Install()->Quick(); + Utils::Hook(0x5AA91C, Theatre::MapChangeSVStub, HOOK_CALL).Install()->Quick(); + + // UIScripts + UIScript::Add("loadDemos", Theatre::LoadDemos); + UIScript::Add("launchDemo", Theatre::PlayDemo); + UIScript::Add("deleteDemo", Theatre::DeleteDemo); + + // Feeder + UIFeeder::Add(10.0f, Theatre::GetDemoCount, Theatre::GetDemoText, Theatre::SelectDemo); + + // set the configstrings stuff to load the default (empty) string table; this should allow demo recording on all gametypes/maps + if(!Dedicated::IsDedicated()) Utils::Hook::Set(0x47440B, "mp/defaultStringTable.csv"); + + // Change font size + Utils::Hook::Set(0x5AC854, 2); + Utils::Hook::Set(0x5AC85A, 2); + +// Command::Add("democycle", [] (Command::Params params) +// { +// // Cmd_FollowCycle_f +// Utils::Hook::Call(0x458ED0)(Game::g_entities, -1); +// }); + } +} diff --git a/src/Components/Modules/Theatre.hpp b/src/Components/Modules/Theatre.hpp index 91ce2cbf..68a66027 100644 --- a/src/Components/Modules/Theatre.hpp +++ b/src/Components/Modules/Theatre.hpp @@ -1,73 +1,73 @@ -namespace Components -{ - class Theatre : public Component - { - public: - Theatre(); - const char* GetName() { return "Theatre"; }; - - private: - class Container - { - public: - class DemoInfo - { - public: - std::string Name; - std::string Mapname; - std::string Gametype; - std::string Author; - int Length; - std::time_t TimeStamp; - - json11::Json to_json() const - { - return json11::Json::object - { - { "mapname", Mapname }, - { "gametype", Gametype }, - { "author", Author }, - { "length", Length }, - { "timestamp", Utils::VA("%lld", TimeStamp) } //Ugly, but prevents information loss - }; - } - }; - - DemoInfo CurrentInfo; - unsigned int CurrentSelection; - std::vector Demos; - }; - - static Container DemoContainer; - - static char BaselineSnapshot[131072]; - static int BaselineSnapshotMsgLen; - static int BaselineSnapshotMsgOff; - - static void WriteBaseline(); - static void StoreBaseline(PBYTE snapshotMsg); - - static void LoadDemos(); - static void DeleteDemo(); - static void PlayDemo(); - - static unsigned int GetDemoCount(); - static const char* GetDemoText(unsigned int item, int column); - static void SelectDemo(unsigned int index); - - static void GamestateWriteStub(Game::msg_t* msg, char byte); - static void RecordGamestateStub(); - static void BaselineStoreStub(); - static void BaselineToFileStub(); - static void AdjustTimeDeltaStub(); - static void ServerTimedOutStub(); - static void UISetActiveMenuStub(); - - static uint32_t InitCGameStub(); - static void MapChangeStub(); - static void MapChangeSVStub(char* a1, char* a2); - - static void RecordStub(int channel, char* message, char* file); - static void StopRecordStub(int channel, char* message); - }; -} +namespace Components +{ + class Theatre : public Component + { + public: + Theatre(); + const char* GetName() { return "Theatre"; }; + + private: + class Container + { + public: + class DemoInfo + { + public: + std::string Name; + std::string Mapname; + std::string Gametype; + std::string Author; + int Length; + std::time_t TimeStamp; + + json11::Json to_json() const + { + return json11::Json::object + { + { "mapname", Mapname }, + { "gametype", Gametype }, + { "author", Author }, + { "length", Length }, + { "timestamp", fmt::sprintf("%lld", TimeStamp) } //Ugly, but prevents information loss + }; + } + }; + + DemoInfo CurrentInfo; + unsigned int CurrentSelection; + std::vector Demos; + }; + + static Container DemoContainer; + + static char BaselineSnapshot[131072]; + static int BaselineSnapshotMsgLen; + static int BaselineSnapshotMsgOff; + + static void WriteBaseline(); + static void StoreBaseline(PBYTE snapshotMsg); + + static void LoadDemos(); + static void DeleteDemo(); + static void PlayDemo(); + + static unsigned int GetDemoCount(); + static const char* GetDemoText(unsigned int item, int column); + static void SelectDemo(unsigned int index); + + static void GamestateWriteStub(Game::msg_t* msg, char byte); + static void RecordGamestateStub(); + static void BaselineStoreStub(); + static void BaselineToFileStub(); + static void AdjustTimeDeltaStub(); + static void ServerTimedOutStub(); + static void UISetActiveMenuStub(); + + static uint32_t InitCGameStub(); + static void MapChangeStub(); + static void MapChangeSVStub(char* a1, char* a2); + + static void RecordStub(int channel, char* message, char* file); + static void StopRecordStub(int channel, char* message); + }; +} diff --git a/src/Components/Modules/Toast.cpp b/src/Components/Modules/Toast.cpp index 4455f760..2ccd714f 100644 --- a/src/Components/Modules/Toast.cpp +++ b/src/Components/Modules/Toast.cpp @@ -1,142 +1,142 @@ -#include "STDInclude.hpp" - -namespace Components -{ - std::queue Toast::Queue; - std::mutex Toast::Mutex; - - void Toast::Show(const char* image, const char* title, const char* description, int length) - { - Toast::Mutex.lock(); - Toast::Queue.push({ image, title, description, length, 0 }); - Toast::Mutex.unlock(); - } - - void Toast::Draw(UIToast* toast) - { - if (!toast) return; - - int width = Renderer::Width(); - int height = Renderer::Height(); - int slideTime = 100; - - int duration = toast->Length; - int startTime = toast->Start; - - int border = 1; - int cornerSize = 15; - int bHeight = 74; - - int imgDim = 60; - - float fontSize = 0.9f; - float descSize = 0.9f; - - Game::Material* white = Game::DB_FindXAssetHeader(Game::XAssetType::ASSET_TYPE_MATERIAL, "white").material; - Game::Material* image = Game::DB_FindXAssetHeader(Game::XAssetType::ASSET_TYPE_MATERIAL, toast->Image.data()).material; - Game::Font* font = Game::DB_FindXAssetHeader(Game::XAssetType::ASSET_TYPE_FONT, "fonts/objectiveFont").font; - Game::Font* descfont = Game::DB_FindXAssetHeader(Game::XAssetType::ASSET_TYPE_FONT, "fonts/normalFont").font; - Game::vec4_t wColor = { 1.0f, 1.0f, 1.0f, 1.0f }; - Game::vec4_t bgColor = { 0.0f, 0.0f, 0.0f, 0.5f }; - Game::vec4_t borderColor = { 1.0f, 1.0f, 1.0f, 0.2f }; - - height /= 5; - height *= 4; - - if (Game::Sys_Milliseconds() < startTime || (startTime + duration) < Game::Sys_Milliseconds()) return; - - // Fadein stuff - else if (Game::Sys_Milliseconds() - startTime < slideTime) - { - int diffH = Renderer::Height() / 5; - int diff = Game::Sys_Milliseconds() - startTime; - double scale = 1.0 - ((1.0 * diff) / (1.0 * slideTime)); - diffH = static_cast(diffH * scale); - height += diffH; - } - - // Fadeout stuff - else if (Game::Sys_Milliseconds() - startTime > (duration - slideTime)) - { - int diffH = Renderer::Height() / 5; - int diff = (startTime + duration) - Game::Sys_Milliseconds(); - double scale = 1.0 - ((1.0 * diff) / (1.0 * slideTime)); - diffH = static_cast(diffH * scale); - height += diffH; - } - - height += bHeight / 2 - cornerSize; - - // Calculate width data - int iOffset = (bHeight - imgDim) / 2; - int iOffsetLeft = iOffset * 2; - float titleSize = Game::R_TextWidth(toast->Title.data(), 0x7FFFFFFF, font) * fontSize; - float descrSize = Game::R_TextWidth(toast->Desc.data(), 0x7FFFFFFF, descfont) * descSize; - float bWidth = iOffsetLeft * 3 + imgDim + std::max(titleSize, descrSize); - - // Make stuff divisible by 2 - // Otherwise there are overlapping images - // and I'm too lazy to figure out the actual problem :P - bWidth = (static_cast(bWidth) + (static_cast(bWidth) % 2)) * 1.0f; - bHeight += (bHeight % 2); - - // Background - Game::CL_DrawStretchPicPhysical(static_cast(width / 2 - bWidth / 2), static_cast(height - bHeight / 2), bWidth * 1.0f, bHeight * 1.0f, 0, 0, 1.0f, 1.0f, bgColor, white); - - // Border - Game::CL_DrawStretchPicPhysical(static_cast(width / 2 - bWidth / 2 - border), static_cast(height - bHeight / 2 - border), border * 1.0f, bHeight + (border * 2.0f), 0, 0, 1.0f, 1.0f, borderColor, white); // Left - Game::CL_DrawStretchPicPhysical(static_cast(width / 2 - bWidth / 2 + bWidth), static_cast(height - bHeight / 2 - border), border * 1.0f, bHeight + (border * 2.0f), 0, 0, 1.0f, 1.0f, borderColor, white); // Right - Game::CL_DrawStretchPicPhysical(static_cast(width / 2 - bWidth / 2), static_cast(height - bHeight / 2 - border), bWidth * 1.0f, border * 1.0f, 0, 0, 1.0f, 1.0f, borderColor, white); // Top - Game::CL_DrawStretchPicPhysical(static_cast(width / 2 - bWidth / 2), static_cast(height + bHeight / 2), bWidth * 1.0f, border * 1.0f, 0, 0, 1.0f, 1.0f, borderColor, white); // Bottom - - // Image - Game::CL_DrawStretchPicPhysical(static_cast(width / 2 - bWidth / 2 + iOffsetLeft), static_cast(height - bHeight / 2 + iOffset), imgDim * 1.0f, imgDim * 1.0f, 0, 0, 1.0f, 1.0f, wColor, image); - - // Text - float leftText = width / 2 - bWidth / 2 - cornerSize + iOffsetLeft * 2 + imgDim; - float rightText = width / 2 + bWidth / 2 - cornerSize - iOffsetLeft; - Game::R_AddCmdDrawText(Utils::StrToUpper(toast->Title).data(), 0x7FFFFFFF, font, static_cast(leftText + (rightText - leftText) / 2 - titleSize / 2 + cornerSize), static_cast(height - bHeight / 2 + cornerSize * 2 + 7), fontSize, fontSize, 0, wColor, Game::ITEM_TEXTSTYLE_SHADOWED); // Title - Game::R_AddCmdDrawText(toast->Desc.data(), 0x7FFFFFFF, descfont, leftText + (rightText - leftText) / 2 - descrSize / 2 + cornerSize, static_cast(height - bHeight / 2 + cornerSize * 2 + 33), descSize, descSize, 0, wColor, Game::ITEM_TEXTSTYLE_SHADOWED); // Description - } - - void Toast::Handler() - { - if (Toast::Queue.empty()) return; - - Toast::Mutex.lock(); - - Toast::UIToast* toast = &Toast::Queue.front(); - - // Set start time - if (!toast->Start) - { - toast->Start = Game::Sys_Milliseconds(); - } - - if ((toast->Start + toast->Length) < Game::Sys_Milliseconds()) - { - Toast::Queue.pop(); - } - else - { - Toast::Draw(toast); - } - - Toast::Mutex.unlock(); - } - - Toast::Toast() - { - Renderer::OnFrame(Toast::Handler); - - Command::Add("testtoast", [] (Command::Params) - { - Toast::Show("cardicon_prestige10", "Test", "This is a test toast", 3000); - }); - } - - Toast::~Toast() - { - Toast::Queue = std::queue(); - } -} +#include "STDInclude.hpp" + +namespace Components +{ + std::queue Toast::Queue; + std::mutex Toast::Mutex; + + void Toast::Show(std::string image, std::string title, std::string description, int length) + { + Toast::Mutex.lock(); + Toast::Queue.push({ image, title, description, length, 0 }); + Toast::Mutex.unlock(); + } + + void Toast::Draw(UIToast* toast) + { + if (!toast) return; + + int width = Renderer::Width(); + int height = Renderer::Height(); + int slideTime = 100; + + int duration = toast->Length; + int startTime = toast->Start; + + int border = 1; + int cornerSize = 15; + int bHeight = 74; + + int imgDim = 60; + + float fontSize = 0.9f; + float descSize = 0.9f; + + Game::Material* white = Game::DB_FindXAssetHeader(Game::XAssetType::ASSET_TYPE_MATERIAL, "white").material; + Game::Material* image = Game::DB_FindXAssetHeader(Game::XAssetType::ASSET_TYPE_MATERIAL, toast->Image.data()).material; + Game::Font* font = Game::DB_FindXAssetHeader(Game::XAssetType::ASSET_TYPE_FONT, "fonts/objectiveFont").font; + Game::Font* descfont = Game::DB_FindXAssetHeader(Game::XAssetType::ASSET_TYPE_FONT, "fonts/normalFont").font; + Game::vec4_t wColor = { 1.0f, 1.0f, 1.0f, 1.0f }; + Game::vec4_t bgColor = { 0.0f, 0.0f, 0.0f, 0.5f }; + Game::vec4_t borderColor = { 1.0f, 1.0f, 1.0f, 0.2f }; + + height /= 5; + height *= 4; + + if (Game::Sys_Milliseconds() < startTime || (startTime + duration) < Game::Sys_Milliseconds()) return; + + // Fadein stuff + else if (Game::Sys_Milliseconds() - startTime < slideTime) + { + int diffH = Renderer::Height() / 5; + int diff = Game::Sys_Milliseconds() - startTime; + double scale = 1.0 - ((1.0 * diff) / (1.0 * slideTime)); + diffH = static_cast(diffH * scale); + height += diffH; + } + + // Fadeout stuff + else if (Game::Sys_Milliseconds() - startTime > (duration - slideTime)) + { + int diffH = Renderer::Height() / 5; + int diff = (startTime + duration) - Game::Sys_Milliseconds(); + double scale = 1.0 - ((1.0 * diff) / (1.0 * slideTime)); + diffH = static_cast(diffH * scale); + height += diffH; + } + + height += bHeight / 2 - cornerSize; + + // Calculate width data + int iOffset = (bHeight - imgDim) / 2; + int iOffsetLeft = iOffset * 2; + float titleSize = Game::R_TextWidth(toast->Title.data(), 0x7FFFFFFF, font) * fontSize; + float descrSize = Game::R_TextWidth(toast->Desc.data(), 0x7FFFFFFF, descfont) * descSize; + float bWidth = iOffsetLeft * 3 + imgDim + std::max(titleSize, descrSize); + + // Make stuff divisible by 2 + // Otherwise there are overlapping images + // and I'm too lazy to figure out the actual problem :P + bWidth = (static_cast(bWidth) + (static_cast(bWidth) % 2)) * 1.0f; + bHeight += (bHeight % 2); + + // Background + Game::CL_DrawStretchPicPhysical(static_cast(width / 2 - bWidth / 2), static_cast(height - bHeight / 2), bWidth * 1.0f, bHeight * 1.0f, 0, 0, 1.0f, 1.0f, bgColor, white); + + // Border + Game::CL_DrawStretchPicPhysical(static_cast(width / 2 - bWidth / 2 - border), static_cast(height - bHeight / 2 - border), border * 1.0f, bHeight + (border * 2.0f), 0, 0, 1.0f, 1.0f, borderColor, white); // Left + Game::CL_DrawStretchPicPhysical(static_cast(width / 2 - bWidth / 2 + bWidth), static_cast(height - bHeight / 2 - border), border * 1.0f, bHeight + (border * 2.0f), 0, 0, 1.0f, 1.0f, borderColor, white); // Right + Game::CL_DrawStretchPicPhysical(static_cast(width / 2 - bWidth / 2), static_cast(height - bHeight / 2 - border), bWidth * 1.0f, border * 1.0f, 0, 0, 1.0f, 1.0f, borderColor, white); // Top + Game::CL_DrawStretchPicPhysical(static_cast(width / 2 - bWidth / 2), static_cast(height + bHeight / 2), bWidth * 1.0f, border * 1.0f, 0, 0, 1.0f, 1.0f, borderColor, white); // Bottom + + // Image + Game::CL_DrawStretchPicPhysical(static_cast(width / 2 - bWidth / 2 + iOffsetLeft), static_cast(height - bHeight / 2 + iOffset), imgDim * 1.0f, imgDim * 1.0f, 0, 0, 1.0f, 1.0f, wColor, image); + + // Text + float leftText = width / 2 - bWidth / 2 - cornerSize + iOffsetLeft * 2 + imgDim; + float rightText = width / 2 + bWidth / 2 - cornerSize - iOffsetLeft; + Game::R_AddCmdDrawText(Utils::String::StrToUpper(toast->Title).data(), 0x7FFFFFFF, font, static_cast(leftText + (rightText - leftText) / 2 - titleSize / 2 + cornerSize), static_cast(height - bHeight / 2 + cornerSize * 2 + 7), fontSize, fontSize, 0, wColor, Game::ITEM_TEXTSTYLE_SHADOWED); // Title + Game::R_AddCmdDrawText(toast->Desc.data(), 0x7FFFFFFF, descfont, leftText + (rightText - leftText) / 2 - descrSize / 2 + cornerSize, static_cast(height - bHeight / 2 + cornerSize * 2 + 33), descSize, descSize, 0, wColor, Game::ITEM_TEXTSTYLE_SHADOWED); // Description + } + + void Toast::Handler() + { + if (Toast::Queue.empty()) return; + + Toast::Mutex.lock(); + + Toast::UIToast* toast = &Toast::Queue.front(); + + // Set start time + if (!toast->Start) + { + toast->Start = Game::Sys_Milliseconds(); + } + + if ((toast->Start + toast->Length) < Game::Sys_Milliseconds()) + { + Toast::Queue.pop(); + } + else + { + Toast::Draw(toast); + } + + Toast::Mutex.unlock(); + } + + Toast::Toast() + { + Renderer::OnFrame(Toast::Handler); + + Command::Add("testtoast", [] (Command::Params) + { + Toast::Show("cardicon_prestige10", "Test", "This is a test toast", 3000); + }); + } + + Toast::~Toast() + { + Toast::Queue = std::queue(); + } +} diff --git a/src/Components/Modules/Toast.hpp b/src/Components/Modules/Toast.hpp index cd02d9d4..23a4b1e9 100644 --- a/src/Components/Modules/Toast.hpp +++ b/src/Components/Modules/Toast.hpp @@ -1,29 +1,29 @@ -namespace Components -{ - class Toast : public Component - { - public: - Toast(); - ~Toast(); - const char* GetName() { return "Toast"; }; - - static void Show(const char* image, const char* title, const char* description, int length); - - private: - class UIToast - { - public: - std::string Image; - std::string Title; - std::string Desc; - int Length; - int Start; - }; - - static void Handler(); - static void Draw(UIToast* toast); - - static std::queue Queue; - static std::mutex Mutex; - }; -} +namespace Components +{ + class Toast : public Component + { + public: + Toast(); + ~Toast(); + const char* GetName() { return "Toast"; }; + + static void Show(std::string image, std::string title, std::string description, int length); + + private: + class UIToast + { + public: + std::string Image; + std::string Title; + std::string Desc; + int Length; + int Start; + }; + + static void Handler(); + static void Draw(UIToast* toast); + + static std::queue Queue; + static std::mutex Mutex; + }; +} diff --git a/src/Components/Modules/Weapon.cpp b/src/Components/Modules/Weapon.cpp index 00b98e9f..aaaaf435 100644 --- a/src/Components/Modules/Weapon.cpp +++ b/src/Components/Modules/Weapon.cpp @@ -1,31 +1,31 @@ -#include "STDInclude.hpp" - -namespace Components -{ - Game::XAssetHeader Weapon::WeaponFileLoad(Game::XAssetType type, std::string filename) - { - Game::XAssetHeader header = { 0 }; - - // Try loading raw weapon - if (FileSystem::File(Utils::VA("weapons/mp/%s", filename.data())).Exists()) - { - header.data = Game::BG_LoadWeaponDef_LoadObj(filename.data()); - } - - return header; - } - - Weapon::Weapon() - { - // Intercept weapon loading - AssetHandler::OnFind(Game::XAssetType::ASSET_TYPE_WEAPON, Weapon::WeaponFileLoad); - - // weapon asset existence check - Utils::Hook::Nop(0x408228, 5); // find asset header - Utils::Hook::Nop(0x408230, 5); // is asset default - Utils::Hook::Nop(0x40823A, 2); // jump - - // Skip double loading for fs_game - Utils::Hook::Set(0x4081FD, 0xEB); - } -} +#include "STDInclude.hpp" + +namespace Components +{ + Game::XAssetHeader Weapon::WeaponFileLoad(Game::XAssetType type, std::string filename) + { + Game::XAssetHeader header = { 0 }; + + // Try loading raw weapon + if (FileSystem::File(fmt::sprintf("weapons/mp/%s", filename.data())).Exists()) + { + header.data = Game::BG_LoadWeaponDef_LoadObj(filename.data()); + } + + return header; + } + + Weapon::Weapon() + { + // Intercept weapon loading + AssetHandler::OnFind(Game::XAssetType::ASSET_TYPE_WEAPON, Weapon::WeaponFileLoad); + + // weapon asset existence check + Utils::Hook::Nop(0x408228, 5); // find asset header + Utils::Hook::Nop(0x408230, 5); // is asset default + Utils::Hook::Nop(0x40823A, 2); // jump + + // Skip double loading for fs_game + Utils::Hook::Set(0x4081FD, 0xEB); + } +} diff --git a/src/Components/Modules/ZoneBuilder.cpp b/src/Components/Modules/ZoneBuilder.cpp index 4d66333a..3c627bcb 100644 --- a/src/Components/Modules/ZoneBuilder.cpp +++ b/src/Components/Modules/ZoneBuilder.cpp @@ -1,576 +1,576 @@ -#include "STDInclude.hpp" - -namespace Components -{ - std::string ZoneBuilder::TraceZone; - std::vector> ZoneBuilder::TraceAssets; - - ZoneBuilder::Zone::Zone(std::string name) : DataMap("zone_source/" + name + ".csv"), ZoneName(name), IndexStart(0), Branding { 0 }, - - // Reserve 100MB by default. - // That's totally fine, as the dedi doesn't load images and therefore doesn't need much memory. - // That way we can be sure it won't need to reallocate memory. - // Side note: if you need a fastfile larger than 100MB, you're doing it wrong- - // Well, decompressed maps can get way larger than 100MB, so let's increase that. - Buffer(0xC800000) - {} - - ZoneBuilder::Zone::~Zone() - { - // Unload our fastfiles - Game::XZoneInfo info; - info.name = nullptr; - info.allocFlags = 0; - info.freeFlags = 0x01000000; - - Game::DB_LoadXAssets(&info, 1, true); - - AssetHandler::ClearTemporaryAssets(); - Localization::ClearTemp(); - - ZoneBuilder::Zone::LoadedAssets.clear(); - ZoneBuilder::Zone::ScriptStrings.clear(); - ZoneBuilder::Zone::ScriptStringMap.clear(); - } - - Utils::Stream* ZoneBuilder::Zone::GetBuffer() - { - return &Buffer; - } - - Utils::Memory::Allocator* ZoneBuilder::Zone::GetAllocator() - { - return &MemAllocator; - } - - void ZoneBuilder::Zone::Zone::Build() - { - ZoneBuilder::Zone::LoadFastFiles(); - - Logger::Print("Linking assets...\n"); - if (!ZoneBuilder::Zone::LoadAssets()) return; - - ZoneBuilder::Zone::AddBranding(); - - Logger::Print("Saving...\n"); - ZoneBuilder::Zone::SaveData(); - - Logger::Print("Compressing...\n"); - ZoneBuilder::Zone::WriteZone(); - } - - void ZoneBuilder::Zone::LoadFastFiles() - { - Logger::Print("Loading required FastFiles...\n"); - - for (int i = 0; i < DataMap.GetRows(); ++i) - { - if (DataMap.GetElementAt(i, 0) == "require") - { - std::string fastfile = DataMap.GetElementAt(i, 1); - - if (!Game::DB_IsZoneLoaded(fastfile.data())) - { - Game::XZoneInfo info; - info.name = fastfile.data(); - info.allocFlags = 0x01000000; - info.freeFlags = 0; - - Game::DB_LoadXAssets(&info, 1, true); - } - else - { - Logger::Print("Zone '%s' already loaded\n", fastfile.data()); - } - } - } - } - - bool ZoneBuilder::Zone::LoadAssets() - { - for (int i = 0; i < DataMap.GetRows(); ++i) - { - if (DataMap.GetElementAt(i, 0) != "require") - { - if (DataMap.GetColumns(i) > 2) - { - if (DataMap.GetElementAt(i, 0) == "localize") - { - std::string stringOverride = DataMap.GetElementAt(i, 2); - Utils::Replace(stringOverride, "\\n", "\n"); - - Localization::SetTemp(DataMap.GetElementAt(i, 1), stringOverride); - } - else - { - ZoneBuilder::Zone::RenameAsset(Game::DB_GetXAssetNameType(DataMap.GetElementAt(i, 0).data()), DataMap.GetElementAt(i, 1), DataMap.GetElementAt(i, 2)); - } - } - - if (!ZoneBuilder::Zone::LoadAsset(DataMap.GetElementAt(i, 0), DataMap.GetElementAt(i, 1))) - { - return false; - } - } - } - - return true; - } - - bool ZoneBuilder::Zone::LoadAsset(Game::XAssetType type, std::string name) - { - return ZoneBuilder::Zone::LoadAsset(Game::DB_GetXAssetTypeName(type), name); - } - - bool ZoneBuilder::Zone::LoadAsset(std::string typeName, std::string name) - { - Game::XAssetType type = Game::DB_GetXAssetNameType(typeName.data()); - - if (ZoneBuilder::Zone::FindAsset(type, name) != -1) return true; - - if (type == Game::XAssetType::ASSET_TYPE_INVALID || type >= Game::XAssetType::ASSET_TYPE_COUNT) - { - Logger::Error("Error: Invalid asset type '%s'\n", typeName.data()); - return false; - } - - Game::XAssetHeader assetHeader = AssetHandler::FindAssetForZone(type, name, this); - - if (!assetHeader.data) - { - Logger::Error("Error: Missing asset '%s' of type '%s'\n", name.data(), Game::DB_GetXAssetTypeName(type)); - return false; - } - - Game::XAsset asset; - asset.type = type; - asset.header = assetHeader; - - // Handle script strings and referenced assets - AssetHandler::ZoneMark(asset, this); - - ZoneBuilder::Zone::LoadedAssets.push_back(asset); - return true; - } - - int ZoneBuilder::Zone::FindAsset(Game::XAssetType type, std::string name) - { - for (unsigned int i = 0; i < ZoneBuilder::Zone::LoadedAssets.size(); ++i) - { - Game::XAsset* asset = &ZoneBuilder::Zone::LoadedAssets[i]; - - if (asset->type != type) continue; - - const char* assetName = DB_GetXAssetName(asset); - - if (name == assetName) - { - return i; - } - } - - return -1; - } - - Game::XAsset* ZoneBuilder::Zone::GetAsset(int index) - { - if ((uint32_t)index < ZoneBuilder::Zone::LoadedAssets.size()) - { - return &ZoneBuilder::Zone::LoadedAssets[index]; - } - - return nullptr; - } - - uint32_t ZoneBuilder::Zone::GetAssetOffset(int index) - { - Utils::Stream::Offset offset; - offset.block = Game::XFILE_BLOCK_VIRTUAL; - offset.offset = (ZoneBuilder::Zone::IndexStart + (index * sizeof(Game::XAsset)) + 4); - return offset.GetPackedOffset(); - } - - Game::XAssetHeader ZoneBuilder::Zone::RequireAsset(Game::XAssetType type, const char* name) - { - Game::XAssetHeader header; - Utils::Stream::ClearPointer(&header.data); - - int assetIndex = ZoneBuilder::Zone::FindAsset(type, name); - - if (assetIndex != -1) - { - header.data = reinterpret_cast(ZoneBuilder::Zone::GetAssetOffset(assetIndex)); - } - else - { - Logger::Error("Missing required asset '%s' (%s). Export failed!", name, Game::DB_GetXAssetTypeName(type)); - } - - return header; - } - - void ZoneBuilder::Zone::WriteZone() - { - FILETIME fileTime; - GetSystemTimeAsFileTime(&fileTime); - - Game::XFileHeader header = { XFILE_MAGIC_UNSIGNED, XFILE_VERSION_IW4X, Game::XFileLanguage::XLANG_NONE, fileTime.dwHighDateTime, fileTime.dwLowDateTime }; - - std::string outBuffer; - outBuffer.append(reinterpret_cast(&header), sizeof(header)); - - std::string zoneBuffer = ZoneBuilder::Zone::Buffer.ToBuffer(); - zoneBuffer = Utils::Compression::ZLib::Compress(zoneBuffer); - outBuffer.append(zoneBuffer); - - std::string outFile = "zone/" + ZoneBuilder::Zone::ZoneName + ".ff"; - Utils::WriteFile(outFile, outBuffer); - - Logger::Print("done.\n"); - Logger::Print("Zone '%s' written with %d assets\n", outFile.data(), ZoneBuilder::Zone::LoadedAssets.size()); - } - - void ZoneBuilder::Zone::SaveData() - { - // Add header - Game::ZoneHeader zoneHeader = { 0 }; - zoneHeader.assetList.assetCount = ZoneBuilder::Zone::LoadedAssets.size(); - Utils::Stream::ClearPointer(&zoneHeader.assetList.assets); - - // Increment ScriptStrings count (for empty script string) if available - if (!ZoneBuilder::Zone::ScriptStrings.empty()) - { - zoneHeader.assetList.stringList.count = ZoneBuilder::Zone::ScriptStrings.size() + 1; - Utils::Stream::ClearPointer(&zoneHeader.assetList.stringList.strings); - } - - // Write header - ZoneBuilder::Zone::Buffer.Save(&zoneHeader, sizeof(Game::ZoneHeader)); - ZoneBuilder::Zone::Buffer.PushBlock(Game::XFILE_BLOCK_VIRTUAL); // Push main stream onto the stream stack - - // Write ScriptStrings, if available - if (!ZoneBuilder::Zone::ScriptStrings.empty()) - { - ZoneBuilder::Zone::Buffer.SaveNull(4); // Empty script string? - // This actually represents a NULL string, but as scriptString. - // So scriptString loading for NULL scriptStrings from fastfile results in a NULL scriptString. - // That's the reason why the count is incremented by 1, if scriptStrings are available. - - // Write ScriptString pointer table - for (size_t i = 0; i < ZoneBuilder::Zone::ScriptStrings.size(); ++i) - { - ZoneBuilder::Zone::Buffer.SaveMax(4); - } - - ZoneBuilder::Zone::Buffer.Align(Utils::Stream::ALIGN_4); - - // Write ScriptStrings - for (auto ScriptString : ZoneBuilder::Zone::ScriptStrings) - { - ZoneBuilder::Zone::Buffer.SaveString(ScriptString.data()); - } - } - - // Align buffer (4 bytes) to get correct offsets for pointers - ZoneBuilder::Zone::Buffer.Align(Utils::Stream::ALIGN_4); - ZoneBuilder::Zone::IndexStart = ZoneBuilder::Zone::Buffer.GetBlockSize(Game::XFILE_BLOCK_VIRTUAL); // Mark AssetTable offset - - // AssetTable - for (auto asset : ZoneBuilder::Zone::LoadedAssets) - { - Game::XAsset entry = { asset.type, 0 }; - Utils::Stream::ClearPointer(&entry.header.data); - - ZoneBuilder::Zone::Buffer.Save(&entry); - } - - // Assets - for (auto asset : ZoneBuilder::Zone::LoadedAssets) - { - ZoneBuilder::Zone::Buffer.PushBlock(Game::XFILE_BLOCK_TEMP); - ZoneBuilder::Zone::Buffer.Align(Utils::Stream::ALIGN_4); - -#ifdef DEBUG - Components::Logger::Print("Saving (%s): %s\n", Game::DB_GetXAssetTypeName(asset.type), Game::DB_GetXAssetName(&asset)); -#endif - - ZoneBuilder::Zone::Store(asset.header); - AssetHandler::ZoneSave(asset, this); - - ZoneBuilder::Zone::Buffer.PopBlock(); - } - - // Adapt header - ZoneBuilder::Zone::Buffer.EnterCriticalSection(); - Game::XFile* header = reinterpret_cast(ZoneBuilder::Zone::Buffer.Data()); - header->size = ZoneBuilder::Zone::Buffer.Length() - sizeof(Game::XFile); // Write correct data size - header->externalSize = 0; // ? - - // Write stream sizes - for (int i = 0; i < Game::MAX_XFILE_COUNT; ++i) - { - header->blockSize[i] = ZoneBuilder::Zone::Buffer.GetBlockSize(static_cast(i)); - } - - ZoneBuilder::Zone::Buffer.LeaveCriticalSection(); - ZoneBuilder::Zone::Buffer.PopBlock(); - } - - // Add branding asset - void ZoneBuilder::Zone::AddBranding() - { - char* data = "FastFile built using IW4x ZoneTool!"; - ZoneBuilder::Zone::Branding = { ZoneBuilder::Zone::ZoneName.data(), (int)strlen(data), 0, data }; - - if (ZoneBuilder::Zone::FindAsset(Game::XAssetType::ASSET_TYPE_RAWFILE, ZoneBuilder::Zone::Branding.name) != -1) - { - Logger::Error("Unable to add branding. Asset '%s' already exists!", ZoneBuilder::Zone::Branding.name); - } - - Game::XAssetHeader header = { &Branding }; - Game::XAsset brandingAsset = { Game::XAssetType::ASSET_TYPE_RAWFILE, header }; - ZoneBuilder::Zone::LoadedAssets.push_back(brandingAsset); - } - - // Check if the given pointer has already been mapped - bool ZoneBuilder::Zone::HasPointer(const void* pointer) - { - return (ZoneBuilder::Zone::PointerMap.find(pointer) != ZoneBuilder::Zone::PointerMap.end()); - } - - // Get stored offset for given file pointer - uint32_t ZoneBuilder::Zone::SafeGetPointer(const void* pointer) - { - if (ZoneBuilder::Zone::HasPointer(pointer)) - { - return ZoneBuilder::Zone::PointerMap[pointer]; - } - - return NULL; - } - - void ZoneBuilder::Zone::StorePointer(const void* pointer) - { - ZoneBuilder::Zone::PointerMap[pointer] = ZoneBuilder::Zone::Buffer.GetPackedOffset(); - } - - int ZoneBuilder::Zone::AddScriptString(std::string str) - { - return ZoneBuilder::Zone::AddScriptString(Game::SL_GetString(str.data(), 0)); - } - - // Mark a scriptString for writing and map it. - int ZoneBuilder::Zone::AddScriptString(unsigned short gameIndex) - { - // Handle NULL scriptStrings - // Might optimize that later - if (!gameIndex) - { - if (ZoneBuilder::Zone::ScriptStrings.empty()) - { - ZoneBuilder::Zone::ScriptStrings.push_back(""); - } - - return 0; - } - - std::string str = Game::SL_ConvertToString(gameIndex); - int prev = ZoneBuilder::Zone::FindScriptString(str); - - if (prev > 0) - { - ZoneBuilder::Zone::ScriptStringMap[gameIndex] = prev; - return prev; - } - - ZoneBuilder::Zone::ScriptStrings.push_back(str); - ZoneBuilder::Zone::ScriptStringMap[gameIndex] = ZoneBuilder::Zone::ScriptStrings.size(); - return ZoneBuilder::Zone::ScriptStrings.size(); - } - - // Find a local scriptString - int ZoneBuilder::Zone::FindScriptString(std::string str) - { - for (unsigned int i = 0; i < ZoneBuilder::Zone::ScriptStrings.size(); ++i) - { - if (ZoneBuilder::Zone::ScriptStrings[i] == str) - { - return (i + 1); - } - } - - return -1; - } - - // Remap a scriptString to it's corresponding value in the local scriptString table. - void ZoneBuilder::Zone::MapScriptString(unsigned short* gameIndex) - { - *gameIndex = 0xFFFF & ZoneBuilder::Zone::ScriptStringMap[*gameIndex]; - } - - // Store a new name for a given asset - void ZoneBuilder::Zone::RenameAsset(Game::XAssetType type, std::string asset, std::string newName) - { - if (type < Game::XAssetType::ASSET_TYPE_COUNT) - { - ZoneBuilder::Zone::RenameMap[type][asset] = newName; - } - } - - // Return the new name for a given asset - std::string ZoneBuilder::Zone::GetAssetName(Game::XAssetType type, std::string asset) - { - if (type < Game::XAssetType::ASSET_TYPE_COUNT) - { - if (ZoneBuilder::Zone::RenameMap[type].find(asset) != ZoneBuilder::Zone::RenameMap[type].end()) - { - return ZoneBuilder::Zone::RenameMap[type][asset]; - } - } - - return asset; - } - - void ZoneBuilder::Zone::Store(Game::XAssetHeader header) - { - if (!ZoneBuilder::Zone::HasPointer(header.data)) // We should never have to restore a pointer, so this expression should always resolve into false - { - ZoneBuilder::Zone::StorePointer(header.data); - } - } - - bool ZoneBuilder::IsEnabled() - { - return (Flags::HasFlag("zonebuilder") && !Dedicated::IsDedicated()); - } - - void ZoneBuilder::BeginAssetTrace(std::string zone) - { - ZoneBuilder::TraceZone = zone; - } - - std::vector> ZoneBuilder::EndAssetTrace() - { - ZoneBuilder::TraceZone.clear(); - - std::vector> AssetTrace; - Utils::Merge(&AssetTrace, ZoneBuilder::TraceAssets); - - ZoneBuilder::TraceAssets.clear(); - - return AssetTrace; - } - - ZoneBuilder::ZoneBuilder() - { - Assert_Size(Game::XFileHeader, 21); - Assert_Size(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) - Utils::Hook::Set(0x51F4E0, 0xC3); - - //r_loadForrenderer = 0 - Utils::Hook::Set(0x519DDF, 0); - - //r_delayloadimage retn - Utils::Hook::Set(0x51F450, 0xC3); - - // r_registerDvars hack - Utils::Hook::Set(0x51B1CD, 0xC3); - - // Prevent destroying textures - Utils::Hook::Set(0x51F03D, 0xEB); - - // Don't create default assets - Utils::Hook::Set(0x407BAA, 0xEB); - - // Don't display errors when assets are missing (we might manually build those) - Utils::Hook::Nop(0x5BB3F2, 5); - Utils::Hook::Nop(0x5BB422, 5); - Utils::Hook::Nop(0x5BB43A, 5); - // Increase asset pools - Game::ReallocateAssetPool(Game::XAssetType::ASSET_TYPE_MAP_ENTS, 10); - - // hunk size (was 300 MiB) - Utils::Hook::Set(0x64A029, 0x38400000); // 900 MiB - Utils::Hook::Set(0x64A057, 0x38400000); - - AssetHandler::OnLoad([](Game::XAssetType type, Game::XAssetHeader asset, std::string name, bool* restrict) - { - if (!ZoneBuilder::TraceZone.empty() && ZoneBuilder::TraceZone == FastFiles::Current()) - { - ZoneBuilder::TraceAssets.push_back({ type, name }); - } - }); - - Command::Add("verifyzone", [] (Command::Params params) - { - if (params.Length() < 2) return; - - static std::string zone = params[1]; - - ZoneBuilder::BeginAssetTrace(zone); - - Game::XZoneInfo info; - info.name = zone.data(); - info.allocFlags = 0x01000000; - info.freeFlags = 0; - - Logger::Print("Loading zone '%s'...\n", zone.data()); - - Game::DB_LoadXAssets(&info, 1, true); - AssetHandler::FindOriginalAsset(Game::XAssetType::ASSET_TYPE_RAWFILE, zone.data()); // Lock until zone is loaded - - auto assets = ZoneBuilder::EndAssetTrace(); - - Logger::Print("Unloading zone '%s'...\n", zone.data()); - info.freeFlags = 0x01000000; - info.allocFlags = 0; - info.name = nullptr; - - Game::DB_LoadXAssets(&info, 1, true); - AssetHandler::FindOriginalAsset(Game::XAssetType::ASSET_TYPE_RAWFILE, "default"); // Lock until zone is unloaded - - Logger::Print("Zone '%s' loaded with %d assets:\n", zone.data(), assets.size()); - - int count = 0; - for (auto i = assets.begin(); i != assets.end(); ++i, ++count) - { - Logger::Print(" %d: %s: %s\n", count, Game::DB_GetXAssetTypeName(i->first), i->second.data()); - } - - Logger::Print("\n"); - }); - - Command::Add("buildzone", [] (Command::Params params) - { - if (params.Length() < 2) return; - - std::string zoneName = params[1]; - Logger::Print("Building zone '%s'...\n", zoneName.data()); - - Zone(zoneName).Build(); - }); - - Command::Add("listassets", [] (Command::Params params) - { - if (params.Length() < 2) return; - Game::XAssetType type = Game::DB_GetXAssetNameType(params[1]); - - if (type != Game::XAssetType::ASSET_TYPE_INVALID) - { - Game::DB_EnumXAssets(type, [] (Game::XAssetHeader header, void* data) - { - Game::XAsset asset = { *reinterpret_cast(data), header }; - Logger::Print("%s\n", Game::DB_GetXAssetName(&asset)); - }, &type, false); - } - }); - } - } -} +#include "STDInclude.hpp" + +namespace Components +{ + std::string ZoneBuilder::TraceZone; + std::vector> ZoneBuilder::TraceAssets; + + ZoneBuilder::Zone::Zone(std::string name) : DataMap("zone_source/" + name + ".csv"), ZoneName(name), IndexStart(0), Branding { 0 }, + + // Reserve 100MB by default. + // That's totally fine, as the dedi doesn't load images and therefore doesn't need much memory. + // That way we can be sure it won't need to reallocate memory. + // Side note: if you need a fastfile larger than 100MB, you're doing it wrong- + // Well, decompressed maps can get way larger than 100MB, so let's increase that. + Buffer(0xC800000) + {} + + ZoneBuilder::Zone::~Zone() + { + // Unload our fastfiles + Game::XZoneInfo info; + info.name = nullptr; + info.allocFlags = 0; + info.freeFlags = 0x01000000; + + Game::DB_LoadXAssets(&info, 1, true); + + AssetHandler::ClearTemporaryAssets(); + Localization::ClearTemp(); + + ZoneBuilder::Zone::LoadedAssets.clear(); + ZoneBuilder::Zone::ScriptStrings.clear(); + ZoneBuilder::Zone::ScriptStringMap.clear(); + } + + Utils::Stream* ZoneBuilder::Zone::GetBuffer() + { + return &Buffer; + } + + Utils::Memory::Allocator* ZoneBuilder::Zone::GetAllocator() + { + return &MemAllocator; + } + + void ZoneBuilder::Zone::Zone::Build() + { + ZoneBuilder::Zone::LoadFastFiles(); + + Logger::Print("Linking assets...\n"); + if (!ZoneBuilder::Zone::LoadAssets()) return; + + ZoneBuilder::Zone::AddBranding(); + + Logger::Print("Saving...\n"); + ZoneBuilder::Zone::SaveData(); + + Logger::Print("Compressing...\n"); + ZoneBuilder::Zone::WriteZone(); + } + + void ZoneBuilder::Zone::LoadFastFiles() + { + Logger::Print("Loading required FastFiles...\n"); + + for (int i = 0; i < DataMap.GetRows(); ++i) + { + if (DataMap.GetElementAt(i, 0) == "require") + { + std::string fastfile = DataMap.GetElementAt(i, 1); + + if (!Game::DB_IsZoneLoaded(fastfile.data())) + { + Game::XZoneInfo info; + info.name = fastfile.data(); + info.allocFlags = 0x01000000; + info.freeFlags = 0; + + Game::DB_LoadXAssets(&info, 1, true); + } + else + { + Logger::Print("Zone '%s' already loaded\n", fastfile.data()); + } + } + } + } + + bool ZoneBuilder::Zone::LoadAssets() + { + for (int i = 0; i < DataMap.GetRows(); ++i) + { + if (DataMap.GetElementAt(i, 0) != "require") + { + if (DataMap.GetColumns(i) > 2) + { + if (DataMap.GetElementAt(i, 0) == "localize") + { + std::string stringOverride = DataMap.GetElementAt(i, 2); + Utils::String::Replace(stringOverride, "\\n", "\n"); + + Localization::SetTemp(DataMap.GetElementAt(i, 1), stringOverride); + } + else + { + ZoneBuilder::Zone::RenameAsset(Game::DB_GetXAssetNameType(DataMap.GetElementAt(i, 0).data()), DataMap.GetElementAt(i, 1), DataMap.GetElementAt(i, 2)); + } + } + + if (!ZoneBuilder::Zone::LoadAsset(DataMap.GetElementAt(i, 0), DataMap.GetElementAt(i, 1))) + { + return false; + } + } + } + + return true; + } + + bool ZoneBuilder::Zone::LoadAsset(Game::XAssetType type, std::string name) + { + return ZoneBuilder::Zone::LoadAsset(Game::DB_GetXAssetTypeName(type), name); + } + + bool ZoneBuilder::Zone::LoadAsset(std::string typeName, std::string name) + { + Game::XAssetType type = Game::DB_GetXAssetNameType(typeName.data()); + + if (ZoneBuilder::Zone::FindAsset(type, name) != -1) return true; + + if (type == Game::XAssetType::ASSET_TYPE_INVALID || type >= Game::XAssetType::ASSET_TYPE_COUNT) + { + Logger::Error("Error: Invalid asset type '%s'\n", typeName.data()); + return false; + } + + Game::XAssetHeader assetHeader = AssetHandler::FindAssetForZone(type, name, this); + + if (!assetHeader.data) + { + Logger::Error("Error: Missing asset '%s' of type '%s'\n", name.data(), Game::DB_GetXAssetTypeName(type)); + return false; + } + + Game::XAsset asset; + asset.type = type; + asset.header = assetHeader; + + // Handle script strings and referenced assets + AssetHandler::ZoneMark(asset, this); + + ZoneBuilder::Zone::LoadedAssets.push_back(asset); + return true; + } + + int ZoneBuilder::Zone::FindAsset(Game::XAssetType type, std::string name) + { + for (unsigned int i = 0; i < ZoneBuilder::Zone::LoadedAssets.size(); ++i) + { + Game::XAsset* asset = &ZoneBuilder::Zone::LoadedAssets[i]; + + if (asset->type != type) continue; + + const char* assetName = DB_GetXAssetName(asset); + + if (name == assetName) + { + return i; + } + } + + return -1; + } + + Game::XAsset* ZoneBuilder::Zone::GetAsset(int index) + { + if ((uint32_t)index < ZoneBuilder::Zone::LoadedAssets.size()) + { + return &ZoneBuilder::Zone::LoadedAssets[index]; + } + + return nullptr; + } + + uint32_t ZoneBuilder::Zone::GetAssetOffset(int index) + { + Utils::Stream::Offset offset; + offset.block = Game::XFILE_BLOCK_VIRTUAL; + offset.offset = (ZoneBuilder::Zone::IndexStart + (index * sizeof(Game::XAsset)) + 4); + return offset.GetPackedOffset(); + } + + Game::XAssetHeader ZoneBuilder::Zone::RequireAsset(Game::XAssetType type, const char* name) + { + Game::XAssetHeader header; + Utils::Stream::ClearPointer(&header.data); + + int assetIndex = ZoneBuilder::Zone::FindAsset(type, name); + + if (assetIndex != -1) + { + header.data = reinterpret_cast(ZoneBuilder::Zone::GetAssetOffset(assetIndex)); + } + else + { + Logger::Error("Missing required asset '%s' (%s). Export failed!", name, Game::DB_GetXAssetTypeName(type)); + } + + return header; + } + + void ZoneBuilder::Zone::WriteZone() + { + FILETIME fileTime; + GetSystemTimeAsFileTime(&fileTime); + + Game::XFileHeader header = { XFILE_MAGIC_UNSIGNED, XFILE_VERSION_IW4X, Game::XFileLanguage::XLANG_NONE, fileTime.dwHighDateTime, fileTime.dwLowDateTime }; + + std::string outBuffer; + outBuffer.append(reinterpret_cast(&header), sizeof(header)); + + std::string zoneBuffer = ZoneBuilder::Zone::Buffer.ToBuffer(); + zoneBuffer = Utils::Compression::ZLib::Compress(zoneBuffer); + outBuffer.append(zoneBuffer); + + std::string outFile = "zone/" + ZoneBuilder::Zone::ZoneName + ".ff"; + Utils::IO::WriteFile(outFile, outBuffer); + + Logger::Print("done.\n"); + Logger::Print("Zone '%s' written with %d assets\n", outFile.data(), ZoneBuilder::Zone::LoadedAssets.size()); + } + + void ZoneBuilder::Zone::SaveData() + { + // Add header + Game::ZoneHeader zoneHeader = { 0 }; + zoneHeader.assetList.assetCount = ZoneBuilder::Zone::LoadedAssets.size(); + Utils::Stream::ClearPointer(&zoneHeader.assetList.assets); + + // Increment ScriptStrings count (for empty script string) if available + if (!ZoneBuilder::Zone::ScriptStrings.empty()) + { + zoneHeader.assetList.stringList.count = ZoneBuilder::Zone::ScriptStrings.size() + 1; + Utils::Stream::ClearPointer(&zoneHeader.assetList.stringList.strings); + } + + // Write header + ZoneBuilder::Zone::Buffer.Save(&zoneHeader, sizeof(Game::ZoneHeader)); + ZoneBuilder::Zone::Buffer.PushBlock(Game::XFILE_BLOCK_VIRTUAL); // Push main stream onto the stream stack + + // Write ScriptStrings, if available + if (!ZoneBuilder::Zone::ScriptStrings.empty()) + { + ZoneBuilder::Zone::Buffer.SaveNull(4); // Empty script string? + // This actually represents a NULL string, but as scriptString. + // So scriptString loading for NULL scriptStrings from fastfile results in a NULL scriptString. + // That's the reason why the count is incremented by 1, if scriptStrings are available. + + // Write ScriptString pointer table + for (size_t i = 0; i < ZoneBuilder::Zone::ScriptStrings.size(); ++i) + { + ZoneBuilder::Zone::Buffer.SaveMax(4); + } + + ZoneBuilder::Zone::Buffer.Align(Utils::Stream::ALIGN_4); + + // Write ScriptStrings + for (auto ScriptString : ZoneBuilder::Zone::ScriptStrings) + { + ZoneBuilder::Zone::Buffer.SaveString(ScriptString.data()); + } + } + + // Align buffer (4 bytes) to get correct offsets for pointers + ZoneBuilder::Zone::Buffer.Align(Utils::Stream::ALIGN_4); + ZoneBuilder::Zone::IndexStart = ZoneBuilder::Zone::Buffer.GetBlockSize(Game::XFILE_BLOCK_VIRTUAL); // Mark AssetTable offset + + // AssetTable + for (auto asset : ZoneBuilder::Zone::LoadedAssets) + { + Game::XAsset entry = { asset.type, 0 }; + Utils::Stream::ClearPointer(&entry.header.data); + + ZoneBuilder::Zone::Buffer.Save(&entry); + } + + // Assets + for (auto asset : ZoneBuilder::Zone::LoadedAssets) + { + ZoneBuilder::Zone::Buffer.PushBlock(Game::XFILE_BLOCK_TEMP); + ZoneBuilder::Zone::Buffer.Align(Utils::Stream::ALIGN_4); + +#ifdef DEBUG + Components::Logger::Print("Saving (%s): %s\n", Game::DB_GetXAssetTypeName(asset.type), Game::DB_GetXAssetName(&asset)); +#endif + + ZoneBuilder::Zone::Store(asset.header); + AssetHandler::ZoneSave(asset, this); + + ZoneBuilder::Zone::Buffer.PopBlock(); + } + + // Adapt header + ZoneBuilder::Zone::Buffer.EnterCriticalSection(); + Game::XFile* header = reinterpret_cast(ZoneBuilder::Zone::Buffer.Data()); + header->size = ZoneBuilder::Zone::Buffer.Length() - sizeof(Game::XFile); // Write correct data size + header->externalSize = 0; // ? + + // Write stream sizes + for (int i = 0; i < Game::MAX_XFILE_COUNT; ++i) + { + header->blockSize[i] = ZoneBuilder::Zone::Buffer.GetBlockSize(static_cast(i)); + } + + ZoneBuilder::Zone::Buffer.LeaveCriticalSection(); + ZoneBuilder::Zone::Buffer.PopBlock(); + } + + // Add branding asset + void ZoneBuilder::Zone::AddBranding() + { + char* data = "FastFile built using IW4x ZoneTool!"; + ZoneBuilder::Zone::Branding = { ZoneBuilder::Zone::ZoneName.data(), (int)strlen(data), 0, data }; + + if (ZoneBuilder::Zone::FindAsset(Game::XAssetType::ASSET_TYPE_RAWFILE, ZoneBuilder::Zone::Branding.name) != -1) + { + Logger::Error("Unable to add branding. Asset '%s' already exists!", ZoneBuilder::Zone::Branding.name); + } + + Game::XAssetHeader header = { &Branding }; + Game::XAsset brandingAsset = { Game::XAssetType::ASSET_TYPE_RAWFILE, header }; + ZoneBuilder::Zone::LoadedAssets.push_back(brandingAsset); + } + + // Check if the given pointer has already been mapped + bool ZoneBuilder::Zone::HasPointer(const void* pointer) + { + return (ZoneBuilder::Zone::PointerMap.find(pointer) != ZoneBuilder::Zone::PointerMap.end()); + } + + // Get stored offset for given file pointer + uint32_t ZoneBuilder::Zone::SafeGetPointer(const void* pointer) + { + if (ZoneBuilder::Zone::HasPointer(pointer)) + { + return ZoneBuilder::Zone::PointerMap[pointer]; + } + + return NULL; + } + + void ZoneBuilder::Zone::StorePointer(const void* pointer) + { + ZoneBuilder::Zone::PointerMap[pointer] = ZoneBuilder::Zone::Buffer.GetPackedOffset(); + } + + int ZoneBuilder::Zone::AddScriptString(std::string str) + { + return ZoneBuilder::Zone::AddScriptString(Game::SL_GetString(str.data(), 0)); + } + + // Mark a scriptString for writing and map it. + int ZoneBuilder::Zone::AddScriptString(unsigned short gameIndex) + { + // Handle NULL scriptStrings + // Might optimize that later + if (!gameIndex) + { + if (ZoneBuilder::Zone::ScriptStrings.empty()) + { + ZoneBuilder::Zone::ScriptStrings.push_back(""); + } + + return 0; + } + + std::string str = Game::SL_ConvertToString(gameIndex); + int prev = ZoneBuilder::Zone::FindScriptString(str); + + if (prev > 0) + { + ZoneBuilder::Zone::ScriptStringMap[gameIndex] = prev; + return prev; + } + + ZoneBuilder::Zone::ScriptStrings.push_back(str); + ZoneBuilder::Zone::ScriptStringMap[gameIndex] = ZoneBuilder::Zone::ScriptStrings.size(); + return ZoneBuilder::Zone::ScriptStrings.size(); + } + + // Find a local scriptString + int ZoneBuilder::Zone::FindScriptString(std::string str) + { + for (unsigned int i = 0; i < ZoneBuilder::Zone::ScriptStrings.size(); ++i) + { + if (ZoneBuilder::Zone::ScriptStrings[i] == str) + { + return (i + 1); + } + } + + return -1; + } + + // Remap a scriptString to it's corresponding value in the local scriptString table. + void ZoneBuilder::Zone::MapScriptString(unsigned short* gameIndex) + { + *gameIndex = 0xFFFF & ZoneBuilder::Zone::ScriptStringMap[*gameIndex]; + } + + // Store a new name for a given asset + void ZoneBuilder::Zone::RenameAsset(Game::XAssetType type, std::string asset, std::string newName) + { + if (type < Game::XAssetType::ASSET_TYPE_COUNT) + { + ZoneBuilder::Zone::RenameMap[type][asset] = newName; + } + } + + // Return the new name for a given asset + std::string ZoneBuilder::Zone::GetAssetName(Game::XAssetType type, std::string asset) + { + if (type < Game::XAssetType::ASSET_TYPE_COUNT) + { + if (ZoneBuilder::Zone::RenameMap[type].find(asset) != ZoneBuilder::Zone::RenameMap[type].end()) + { + return ZoneBuilder::Zone::RenameMap[type][asset]; + } + } + + return asset; + } + + void ZoneBuilder::Zone::Store(Game::XAssetHeader header) + { + if (!ZoneBuilder::Zone::HasPointer(header.data)) // We should never have to restore a pointer, so this expression should always resolve into false + { + ZoneBuilder::Zone::StorePointer(header.data); + } + } + + bool ZoneBuilder::IsEnabled() + { + return (Flags::HasFlag("zonebuilder") && !Dedicated::IsDedicated()); + } + + void ZoneBuilder::BeginAssetTrace(std::string zone) + { + ZoneBuilder::TraceZone = zone; + } + + std::vector> ZoneBuilder::EndAssetTrace() + { + ZoneBuilder::TraceZone.clear(); + + std::vector> AssetTrace; + Utils::Merge(&AssetTrace, ZoneBuilder::TraceAssets); + + ZoneBuilder::TraceAssets.clear(); + + return AssetTrace; + } + + ZoneBuilder::ZoneBuilder() + { + Assert_Size(Game::XFileHeader, 21); + Assert_Size(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) + Utils::Hook::Set(0x51F4E0, 0xC3); + + //r_loadForrenderer = 0 + Utils::Hook::Set(0x519DDF, 0); + + //r_delayloadimage retn + Utils::Hook::Set(0x51F450, 0xC3); + + // r_registerDvars hack + Utils::Hook::Set(0x51B1CD, 0xC3); + + // Prevent destroying textures + Utils::Hook::Set(0x51F03D, 0xEB); + + // Don't create default assets + Utils::Hook::Set(0x407BAA, 0xEB); + + // Don't display errors when assets are missing (we might manually build those) + Utils::Hook::Nop(0x5BB3F2, 5); + Utils::Hook::Nop(0x5BB422, 5); + Utils::Hook::Nop(0x5BB43A, 5); + // Increase asset pools + Game::ReallocateAssetPool(Game::XAssetType::ASSET_TYPE_MAP_ENTS, 10); + + // hunk size (was 300 MiB) + Utils::Hook::Set(0x64A029, 0x38400000); // 900 MiB + Utils::Hook::Set(0x64A057, 0x38400000); + + AssetHandler::OnLoad([](Game::XAssetType type, Game::XAssetHeader asset, std::string name, bool* restrict) + { + if (!ZoneBuilder::TraceZone.empty() && ZoneBuilder::TraceZone == FastFiles::Current()) + { + ZoneBuilder::TraceAssets.push_back({ type, name }); + } + }); + + Command::Add("verifyzone", [] (Command::Params params) + { + if (params.Length() < 2) return; + + static std::string zone = params[1]; + + ZoneBuilder::BeginAssetTrace(zone); + + Game::XZoneInfo info; + info.name = zone.data(); + info.allocFlags = 0x01000000; + info.freeFlags = 0; + + Logger::Print("Loading zone '%s'...\n", zone.data()); + + Game::DB_LoadXAssets(&info, 1, true); + AssetHandler::FindOriginalAsset(Game::XAssetType::ASSET_TYPE_RAWFILE, zone.data()); // Lock until zone is loaded + + auto assets = ZoneBuilder::EndAssetTrace(); + + Logger::Print("Unloading zone '%s'...\n", zone.data()); + info.freeFlags = 0x01000000; + info.allocFlags = 0; + info.name = nullptr; + + Game::DB_LoadXAssets(&info, 1, true); + AssetHandler::FindOriginalAsset(Game::XAssetType::ASSET_TYPE_RAWFILE, "default"); // Lock until zone is unloaded + + Logger::Print("Zone '%s' loaded with %d assets:\n", zone.data(), assets.size()); + + int count = 0; + for (auto i = assets.begin(); i != assets.end(); ++i, ++count) + { + Logger::Print(" %d: %s: %s\n", count, Game::DB_GetXAssetTypeName(i->first), i->second.data()); + } + + Logger::Print("\n"); + }); + + Command::Add("buildzone", [] (Command::Params params) + { + if (params.Length() < 2) return; + + std::string zoneName = params[1]; + Logger::Print("Building zone '%s'...\n", zoneName.data()); + + Zone(zoneName).Build(); + }); + + Command::Add("listassets", [] (Command::Params params) + { + if (params.Length() < 2) return; + Game::XAssetType type = Game::DB_GetXAssetNameType(params[1]); + + if (type != Game::XAssetType::ASSET_TYPE_INVALID) + { + Game::DB_EnumXAssets(type, [] (Game::XAssetHeader header, void* data) + { + Game::XAsset asset = { *reinterpret_cast(data), header }; + Logger::Print("%s\n", Game::DB_GetXAssetName(&asset)); + }, &type, false); + } + }); + } + } +} diff --git a/src/Game/Functions.cpp b/src/Game/Functions.cpp index 4bec29e1..571fb1b0 100644 --- a/src/Game/Functions.cpp +++ b/src/Game/Functions.cpp @@ -1,407 +1,407 @@ -#include "STDInclude.hpp" - -namespace Game -{ - // C-Style casts are fine here, that's where we're doing our dirty stuff anyways... - BG_LoadWeaponDef_LoadObj_t BG_LoadWeaponDef_LoadObj = (BG_LoadWeaponDef_LoadObj_t)0x57B5F0; - - Cbuf_AddServerText_t Cbuf_AddServerText = (Cbuf_AddServerText_t)0x4BB9B0; - Cbuf_AddText_t Cbuf_AddText = (Cbuf_AddText_t)0x404B20; - - CL_GetClientName_t CL_GetClientName = (CL_GetClientName_t)0x4563D0; - CL_IsCgameInitialized_t CL_IsCgameInitialized = (CL_IsCgameInitialized_t)0x43EB20; - CL_ConnectFromParty_t CL_ConnectFromParty = (CL_ConnectFromParty_t)0x433D30; - CL_DownloadsComplete_t CL_DownloadsComplete = (CL_DownloadsComplete_t)0x42CE90; - CL_DrawStretchPicPhysical_t CL_DrawStretchPicPhysical = (CL_DrawStretchPicPhysical_t)0x4FC120; - CL_ResetViewport_t CL_ResetViewport = (CL_ResetViewport_t)0x4A8830; - - Cmd_AddCommand_t Cmd_AddCommand = (Cmd_AddCommand_t)0x470090; - Cmd_AddServerCommand_t Cmd_AddServerCommand = (Cmd_AddServerCommand_t)0x4DCE00; - Cmd_ExecuteSingleCommand_t Cmd_ExecuteSingleCommand = (Cmd_ExecuteSingleCommand_t)0x609540; - - Com_Error_t Com_Error = (Com_Error_t)0x4B22D0; - Com_Printf_t Com_Printf = (Com_Printf_t)0x402500; - Com_PrintMessage_t Com_PrintMessage = (Com_PrintMessage_t)0x4AA830; - Com_ParseExt_t Com_ParseExt = (Com_ParseExt_t)0x474D60; - - Con_DrawMiniConsole_t Con_DrawMiniConsole = (Con_DrawMiniConsole_t)0x464F30; - Con_DrawSolidConsole_t Con_DrawSolidConsole = (Con_DrawSolidConsole_t)0x5A5040; - - DB_EnumXAssets_t DB_EnumXAssets = (DB_EnumXAssets_t)0x4B76D0; - DB_EnumXAssets_Internal_t DB_EnumXAssets_Internal = (DB_EnumXAssets_Internal_t)0x5BB0A0; - DB_FindXAssetHeader_t DB_FindXAssetHeader = (DB_FindXAssetHeader_t)0x407930; - DB_GetXAssetNameHandler_t* DB_GetXAssetNameHandlers = (DB_GetXAssetNameHandler_t*)0x799328; - DB_GetXAssetSizeHandler_t* DB_GetXAssetSizeHandlers = (DB_GetXAssetSizeHandler_t*)0x799488; - DB_GetXAssetTypeName_t DB_GetXAssetTypeName = (DB_GetXAssetTypeName_t)0x4CFCF0; - DB_IsXAssetDefault_t DB_IsXAssetDefault = (DB_IsXAssetDefault_t)0x48E6A0; - DB_LoadXAssets_t DB_LoadXAssets = (DB_LoadXAssets_t)0x4E5930; - DB_ReadXFileUncompressed_t DB_ReadXFileUncompressed = (DB_ReadXFileUncompressed_t)0x4705E0; - - Dvar_RegisterBool_t Dvar_RegisterBool = (Dvar_RegisterBool_t)0x4CE1A0; - Dvar_RegisterFloat_t Dvar_RegisterFloat = (Dvar_RegisterFloat_t)0x648440; - Dvar_RegisterVec2_t Dvar_RegisterVec2 = (Dvar_RegisterVec2_t)0x4F6070; - Dvar_RegisterVec3_t Dvar_RegisterVec3 = (Dvar_RegisterVec3_t)0x4EF8E0; - Dvar_RegisterVec4_t Dvar_RegisterVec4 = (Dvar_RegisterVec4_t)0x4F28E0; - Dvar_RegisterInt_t Dvar_RegisterInt = (Dvar_RegisterInt_t)0x479830; - Dvar_RegisterEnum_t Dvar_RegisterEnum = (Dvar_RegisterEnum_t)0x412E40; - Dvar_RegisterString_t Dvar_RegisterString = (Dvar_RegisterString_t)0x4FC7E0; - Dvar_RegisterColor_t Dvar_RegisterColor = (Dvar_RegisterColor_t)0x4F28E0;//0x471500; - - Dvar_GetUnpackedColorByName_t Dvar_GetUnpackedColorByName = (Dvar_GetUnpackedColorByName_t)0x406530; - Dvar_FindVar_t Dvar_FindVar = (Dvar_FindVar_t)0x4D5390; - Dvar_InfoString_Big_t Dvar_InfoString_Big = (Dvar_InfoString_Big_t)0x4D98A0; - Dvar_SetCommand_t Dvar_SetCommand = (Dvar_SetCommand_t)0x4EE430; - - Field_Clear_t Field_Clear = (Field_Clear_t)0x437EB0; - - FreeMemory_t FreeMemory = (FreeMemory_t)0x4D6640; - - FS_FileExists_t FS_FileExists = (FS_FileExists_t)0x4DEFA0; - FS_FreeFile_t FS_FreeFile = (FS_FreeFile_t)0x4416B0; - FS_ReadFile_t FS_ReadFile = (FS_ReadFile_t)0x4F4B90; - FS_GetFileList_t FS_GetFileList = (FS_GetFileList_t)0x441BB0; - FS_FreeFileList_t FS_FreeFileList = (FS_FreeFileList_t)0x4A5DE0; - FS_FOpenFileAppend_t FS_FOpenFileAppend = (FS_FOpenFileAppend_t)0x410BB0; - FS_FOpenFileAppend_t FS_FOpenFileWrite = (FS_FOpenFileAppend_t)0x4BA530; - FS_FOpenFileRead_t FS_FOpenFileRead = (FS_FOpenFileRead_t)0x46CBF0; - FS_FOpenFileReadForThread_t FS_FOpenFileReadForThread = (FS_FOpenFileReadForThread_t)0x643270; - FS_FCloseFile_t FS_FCloseFile = (FS_FCloseFile_t)0x462000; - FS_WriteFile_t FS_WriteFile = (FS_WriteFile_t)0x426450; - FS_Write_t FS_Write = (FS_Write_t)0x4C06E0; - FS_Read_t FS_Read = (FS_Read_t)0x4A04C0; - FS_Seek_t FS_Seek = (FS_Seek_t)0x4A63D0; - FS_FTell_t FS_FTell = (FS_FTell_t)0x4E6760; - FS_Remove_t FS_Remove = (FS_Remove_t)0x4660F0; - FS_Restart_t FS_Restart = (FS_Restart_t)0x461A50; - FS_BuildPathToFile_t FS_BuildPathToFile = (FS_BuildPathToFile_t)0x4702C0; - - G_SpawnEntitiesFromString_t G_SpawnEntitiesFromString = (G_SpawnEntitiesFromString_t)0x4D8840; - - GScr_LoadGameTypeScript_t GScr_LoadGameTypeScript = (GScr_LoadGameTypeScript_t)0x4ED9A0; - - Image_LoadFromFileWithReader_t Image_LoadFromFileWithReader = (Image_LoadFromFileWithReader_t)0x53ABF0; - Image_Release_t Image_Release = (Image_Release_t)0x51F010; - - Menus_CloseAll_t Menus_CloseAll = (Menus_CloseAll_t)0x4BA5B0; - Menus_OpenByName_t Menus_OpenByName = (Menus_OpenByName_t)0x4CCE60; - Menus_FindByName_t Menus_FindByName = (Menus_FindByName_t)0x487240; - Menu_IsVisible_t Menu_IsVisible = (Menu_IsVisible_t)0x4D77D0; - Menus_MenuIsInStack_t Menus_MenuIsInStack = (Menus_MenuIsInStack_t)0x47ACB0; - - MSG_Init_t MSG_Init = (MSG_Init_t)0x45FCA0; - MSG_ReadData_t MSG_ReadData = (MSG_ReadData_t)0x4527C0; - MSG_ReadLong_t MSG_ReadLong = (MSG_ReadLong_t)0x4C9550; - MSG_ReadShort_t MSG_ReadShort = (MSG_ReadShort_t)0x40BDD0; - MSG_ReadInt64_t MSG_ReadInt64 = (MSG_ReadInt64_t)0x4F1850; - MSG_ReadString_t MSG_ReadString = (MSG_ReadString_t)0x60E2B0; - MSG_WriteByte_t MSG_WriteByte = (MSG_WriteByte_t)0x48C520; - MSG_WriteData_t MSG_WriteData = (MSG_WriteData_t)0x4F4120; - MSG_WriteLong_t MSG_WriteLong = (MSG_WriteLong_t)0x41CA20; - MSG_WriteBitsCompress_t MSG_WriteBitsCompress = (MSG_WriteBitsCompress_t)0x4319D0; - MSG_ReadByte_t MSG_ReadByte = (MSG_ReadByte_t)0x4C1C20; - MSG_ReadBitsCompress_t MSG_ReadBitsCompress = (MSG_ReadBitsCompress_t)0x4DCC30; - - NetadrToSockadr_t NetadrToSockadr = (NetadrToSockadr_t)0x4B4B40; - - NET_AdrToString_t NET_AdrToString = (NET_AdrToString_t)0x469880; - NET_CompareAdr_t NET_CompareAdr = (NET_CompareAdr_t)0x4D0AA0; - NET_IsLocalAddress_t NET_IsLocalAddress = (NET_IsLocalAddress_t)0x402BD0; - NET_StringToAdr_t NET_StringToAdr = (NET_StringToAdr_t)0x409010; - NET_OutOfBandPrint_t NET_OutOfBandPrint = (NET_OutOfBandPrint_t)0x4AEF00; - NET_OutOfBandData_t NET_OutOfBandData = (NET_OutOfBandData_t)0x49C7E0; - - Live_MPAcceptInvite_t Live_MPAcceptInvite = (Live_MPAcceptInvite_t)0x420A6D; - Live_ParsePlaylists_t Live_ParsePlaylists = (Live_ParsePlaylists_t)0x4295A0; - - LoadModdableRawfile_t LoadModdableRawfile = (LoadModdableRawfile_t)0x61ABC0; - - LocalizeString_t LocalizeString = (LocalizeString_t)0x4FB010; - LocalizeMapString_t LocalizeMapString = (LocalizeMapString_t)0x44BB30; - - PC_ReadToken_t PC_ReadToken = (PC_ReadToken_t)0x4ACCD0; - PC_ReadTokenHandle_t PC_ReadTokenHandle = (PC_ReadTokenHandle_t)0x4D2060; - PC_SourceError_t PC_SourceError = (PC_SourceError_t)0x467A00; - - Party_GetMaxPlayers_t Party_GetMaxPlayers = (Party_GetMaxPlayers_t)0x4F5D60; - PartyHost_CountMembers_t PartyHost_CountMembers = (PartyHost_CountMembers_t)0x497330; - PartyHost_GetMemberAddressBySlot_t PartyHost_GetMemberAddressBySlot = (PartyHost_GetMemberAddressBySlot_t)0x44E100; - PartyHost_GetMemberName_t PartyHost_GetMemberName = (PartyHost_GetMemberName_t)0x44BE90; - - R_AddCmdDrawStretchPic_t R_AddCmdDrawStretchPic = (R_AddCmdDrawStretchPic_t)0x509770; - R_RegisterFont_t R_RegisterFont = (R_RegisterFont_t)0x505670; - R_AddCmdDrawText_t R_AddCmdDrawText = (R_AddCmdDrawText_t)0x509D80; - R_LoadGraphicsAssets_t R_LoadGraphicsAssets = (R_LoadGraphicsAssets_t)0x506AC0; - R_TextWidth_t R_TextWidth = (R_TextWidth_t)0x5056C0; - R_TextHeight_t R_TextHeight = (R_TextHeight_t)0x505770; - - Scr_LoadGameType_t Scr_LoadGameType = (Scr_LoadGameType_t)0x4D9520; - - Scr_LoadScript_t Scr_LoadScript = (Scr_LoadScript_t)0x45D940; - Scr_GetFunctionHandle_t Scr_GetFunctionHandle = (Scr_GetFunctionHandle_t)0x4234F0; - - Scr_ExecThread_t Scr_ExecThread = (Scr_ExecThread_t)0x4AD0B0; - Scr_FreeThread_t Scr_FreeThread = (Scr_FreeThread_t)0x4BD320; - - Scr_ShutdownAllocNode_t Scr_ShutdownAllocNode = (Scr_ShutdownAllocNode_t)0x441650; - - Script_Alloc_t Script_Alloc = (Script_Alloc_t)0x422E70; - Script_SetupTokens_t Script_SetupTokens = (Script_SetupTokens_t)0x4E6950; - Script_CleanString_t Script_CleanString = (Script_CleanString_t)0x498220; - - SE_Load_t SE_Load = (SE_Load_t)0x502A30; - - Dvar_SetStringByName_t Dvar_SetStringByName = (Dvar_SetStringByName_t)0x44F060; - - SL_ConvertToString_t SL_ConvertToString = (SL_ConvertToString_t)0x4EC1D0; - SL_GetString_t SL_GetString = (SL_GetString_t)0x4CDC10; - - SND_InitDriver_t SND_InitDriver = (SND_InitDriver_t)0x4F5090; - - SockadrToNetadr_t SockadrToNetadr = (SockadrToNetadr_t)0x4F8460; - - Steam_JoinLobby_t Steam_JoinLobby = (Steam_JoinLobby_t)0x49CF70; - - SV_GameClientNum_Score_t SV_GameClientNum_Score = (SV_GameClientNum_Score_t)0x469AC0; - SV_GameSendServerCommand_t SV_GameSendServerCommand = (SV_GameSendServerCommand_t)0x4BC3A0; - - Sys_FreeFileList_t Sys_FreeFileList = (Sys_FreeFileList_t)0x4D8580; - Sys_IsMainThread_t Sys_IsMainThread = (Sys_IsMainThread_t)0x4C37D0; - Sys_SendPacket_t Sys_SendPacket = (Sys_SendPacket_t)0x60FDC0; - Sys_ShowConsole_t Sys_ShowConsole = (Sys_ShowConsole_t)0x4305E0; - Sys_ListFiles_t Sys_ListFiles = (Sys_ListFiles_t)0x45A660; - Sys_Milliseconds_t Sys_Milliseconds = (Sys_Milliseconds_t)0x42A660; - - UI_AddMenuList_t UI_AddMenuList = (UI_AddMenuList_t)0x4533C0; - UI_LoadMenus_t UI_LoadMenus = (UI_LoadMenus_t)0x641460; - UI_DrawHandlePic_t UI_DrawHandlePic = (UI_DrawHandlePic_t)0x4D0EA0; - UI_GetContext_t UI_GetContext = (UI_GetContext_t)0x4F8940; - UI_TextWidth_t UI_TextWidth = (UI_TextWidth_t)0x6315C0; - UI_DrawText_t UI_DrawText = (UI_DrawText_t)0x49C0D0; - - Win_GetLanguage_t Win_GetLanguage = (Win_GetLanguage_t)0x45CBA0; - - XAssetHeader* DB_XAssetPool = (XAssetHeader*)0x7998A8; - unsigned int* g_poolSize = (unsigned int*)0x7995E8; - - DWORD* cmd_id = (DWORD*)0x1AAC5D0; - DWORD* cmd_argc = (DWORD*)0x1AAC614; - char*** cmd_argv = (char***)0x1AAC634; - - DWORD* cmd_id_sv = (DWORD*)0x1ACF8A0; - DWORD* cmd_argc_sv = (DWORD*)0x1ACF8E4; - char*** cmd_argv_sv = (char***)0x1ACF904; - - source_t **sourceFiles = (source_t **)0x7C4A98; - keywordHash_t **menuParseKeywordHash = (keywordHash_t **)0x63AE928; - - int* svs_numclients = (int*)0x31D938C; - client_t* svs_clients = (client_t*)0x31D9390; - - UiContext *uiContext = (UiContext *)0x62E2858; - - int* arenaCount = (int*)0x62E6930; - mapArena_t* arenas = (mapArena_t*)0x62E6934; - - int* gameTypeCount = (int*)0x62E50A0; - gameTypeName_t* gameTypes = (gameTypeName_t*)0x62E50A4; - - XBlock** g_streamBlocks = (XBlock**)0x16E554C; - - bool* g_lobbyCreateInProgress = (bool*)0x66C9BC2; - party_t** partyIngame = (party_t**)0x1081C00; - PartyData_s** partyData = (PartyData_s**)0x107E500; - - int* numIP = (int*)0x64A1E68; - netIP_t* localIP = (netIP_t*)0x64A1E28; - - int* demoFile = (int*)0xA5EA1C; - int* demoPlaying = (int*)0xA5EA0C; - int* demoRecording = (int*)0xA5EA08; - int* serverMessageSequence = (int*)0xA3E9B4; - - gentity_t* g_entities = (gentity_t*)0x18835D8; - - netadr_t* connectedHost = (netadr_t*)0xA1E888; - - SOCKET* ip_socket = (SOCKET*)0x64A3008; - - SafeArea* safeArea = (SafeArea*)0xA15F3C; - - SpawnVar* spawnVars = (SpawnVar*)0x1A83DE8; - MapEnts** marMapEntsPtr = (MapEnts**)0x112AD34; - - XAssetHeader ReallocateAssetPool(XAssetType type, unsigned int newSize) - { - int elSize = DB_GetXAssetSizeHandlers[type](); - XAssetHeader poolEntry = { Utils::Memory::Allocate(newSize * elSize) }; - DB_XAssetPool[type] = poolEntry; - g_poolSize[type] = newSize; - return poolEntry; - } - - void Menu_FreeItemMemory(Game::itemDef_t* item) - { - __asm - { - mov edi, item - mov eax, 63D880h - call eax - } - } - - const char* TableLookup(StringTable* stringtable, int row, int column) - { - if (!stringtable || !stringtable->values || row >= stringtable->rowCount || column >= stringtable->columnCount) return ""; - - const char* value = stringtable->values[row * stringtable->columnCount + column].string; - if (!value) value = ""; - - return value; - } - - const char* UI_LocalizeMapName(const char* mapName) - { - for (int i = 0; i < *arenaCount; ++i) - { - if (!_stricmp(arenas[i].mapName, mapName)) - { - char* uiName = &arenas[i].uiName[0]; - if ((uiName[0] == 'M' && uiName[1] == 'P') || (uiName[0] == 'P' && uiName[1] == 'A')) // MPUI/PATCH - { - return LocalizeMapString(uiName); - } - - return uiName; - } - } - - return mapName; - } - - const char* UI_LocalizeGameType(const char* gameType) - { - if (!gameType || !*gameType) - { - return ""; - } - - for (int i = 0; i < *gameTypeCount; ++i) - { - if (!_stricmp(gameTypes[i].gameType, gameType)) - { - return LocalizeMapString(gameTypes[i].uiName); - } - } - - return gameType; - } - - float UI_GetScoreboardLeft(void* a1) - { - static int func = 0x590390; - __asm - { - mov eax, [esp + 4h] - call func - } - } - - const char *DB_GetXAssetName(XAsset *asset) - { - if (!asset) return ""; - return DB_GetXAssetNameHandlers[asset->type](&asset->header); - } - - XAssetType DB_GetXAssetNameType(const char* name) - { - for (int i = 0; i < ASSET_TYPE_COUNT; ++i) - { - XAssetType type = static_cast(i); - if (!_stricmp(DB_GetXAssetTypeName(type), name)) - { - return type; - } - } - - return ASSET_TYPE_INVALID; - } - - bool DB_IsZoneLoaded(const char* zone) - { - int zoneCount = Utils::Hook::Get(0x1261BCC); - char* zoneIndices = reinterpret_cast(0x16B8A34); - char* zoneData = reinterpret_cast(0x14C0F80); - - for (int i = 0; i < zoneCount; ++i) - { - std::string name = zoneData + 4 + 0xA4 * (zoneIndices[i] & 0xFF); - - if (name == zone) - { - return true; - } - } - - return false; - } - - void FS_AddLocalizedGameDirectory(const char *path, const char *dir) - { - __asm - { - mov ebx, path - mov eax, dir - mov ecx, 642EF0h - call ecx - } - } - - void MessageBox(std::string message, std::string title) - { - Dvar_SetStringByName("com_errorMessage", message.data()); - Dvar_SetStringByName("com_errorTitle", title.data()); - Cbuf_AddText(0, "openmenu error_popmenu_lobby"); - } - - unsigned int R_HashString(const char* string) - { - unsigned int hash = 0; - - while (*string) - { - hash = (*string | 0x20) ^ (33 * hash); - ++string; - } - - return hash; - } - - void SV_KickClient(client_t* client, const char* reason) - { - __asm - { - push edi - push esi - mov edi, 0 - mov esi, client - push reason - push 0 - push 0 - mov eax, 6249A0h - call eax - add esp, 0Ch - pop esi - pop edi - } - } - - void SV_KickClientError(client_t* client, const char* reason) - { - if (client->state < 5) - { - Components::Network::Send(client->addr, Utils::VA("error\n%s", reason)); - } - - SV_KickClient(client, reason); - } -} +#include "STDInclude.hpp" + +namespace Game +{ + // C-Style casts are fine here, that's where we're doing our dirty stuff anyways... + BG_LoadWeaponDef_LoadObj_t BG_LoadWeaponDef_LoadObj = (BG_LoadWeaponDef_LoadObj_t)0x57B5F0; + + Cbuf_AddServerText_t Cbuf_AddServerText = (Cbuf_AddServerText_t)0x4BB9B0; + Cbuf_AddText_t Cbuf_AddText = (Cbuf_AddText_t)0x404B20; + + CL_GetClientName_t CL_GetClientName = (CL_GetClientName_t)0x4563D0; + CL_IsCgameInitialized_t CL_IsCgameInitialized = (CL_IsCgameInitialized_t)0x43EB20; + CL_ConnectFromParty_t CL_ConnectFromParty = (CL_ConnectFromParty_t)0x433D30; + CL_DownloadsComplete_t CL_DownloadsComplete = (CL_DownloadsComplete_t)0x42CE90; + CL_DrawStretchPicPhysical_t CL_DrawStretchPicPhysical = (CL_DrawStretchPicPhysical_t)0x4FC120; + CL_ResetViewport_t CL_ResetViewport = (CL_ResetViewport_t)0x4A8830; + + Cmd_AddCommand_t Cmd_AddCommand = (Cmd_AddCommand_t)0x470090; + Cmd_AddServerCommand_t Cmd_AddServerCommand = (Cmd_AddServerCommand_t)0x4DCE00; + Cmd_ExecuteSingleCommand_t Cmd_ExecuteSingleCommand = (Cmd_ExecuteSingleCommand_t)0x609540; + + Com_Error_t Com_Error = (Com_Error_t)0x4B22D0; + Com_Printf_t Com_Printf = (Com_Printf_t)0x402500; + Com_PrintMessage_t Com_PrintMessage = (Com_PrintMessage_t)0x4AA830; + Com_ParseExt_t Com_ParseExt = (Com_ParseExt_t)0x474D60; + + Con_DrawMiniConsole_t Con_DrawMiniConsole = (Con_DrawMiniConsole_t)0x464F30; + Con_DrawSolidConsole_t Con_DrawSolidConsole = (Con_DrawSolidConsole_t)0x5A5040; + + DB_EnumXAssets_t DB_EnumXAssets = (DB_EnumXAssets_t)0x4B76D0; + DB_EnumXAssets_Internal_t DB_EnumXAssets_Internal = (DB_EnumXAssets_Internal_t)0x5BB0A0; + DB_FindXAssetHeader_t DB_FindXAssetHeader = (DB_FindXAssetHeader_t)0x407930; + DB_GetXAssetNameHandler_t* DB_GetXAssetNameHandlers = (DB_GetXAssetNameHandler_t*)0x799328; + DB_GetXAssetSizeHandler_t* DB_GetXAssetSizeHandlers = (DB_GetXAssetSizeHandler_t*)0x799488; + DB_GetXAssetTypeName_t DB_GetXAssetTypeName = (DB_GetXAssetTypeName_t)0x4CFCF0; + DB_IsXAssetDefault_t DB_IsXAssetDefault = (DB_IsXAssetDefault_t)0x48E6A0; + DB_LoadXAssets_t DB_LoadXAssets = (DB_LoadXAssets_t)0x4E5930; + DB_ReadXFileUncompressed_t DB_ReadXFileUncompressed = (DB_ReadXFileUncompressed_t)0x4705E0; + + Dvar_RegisterBool_t Dvar_RegisterBool = (Dvar_RegisterBool_t)0x4CE1A0; + Dvar_RegisterFloat_t Dvar_RegisterFloat = (Dvar_RegisterFloat_t)0x648440; + Dvar_RegisterVec2_t Dvar_RegisterVec2 = (Dvar_RegisterVec2_t)0x4F6070; + Dvar_RegisterVec3_t Dvar_RegisterVec3 = (Dvar_RegisterVec3_t)0x4EF8E0; + Dvar_RegisterVec4_t Dvar_RegisterVec4 = (Dvar_RegisterVec4_t)0x4F28E0; + Dvar_RegisterInt_t Dvar_RegisterInt = (Dvar_RegisterInt_t)0x479830; + Dvar_RegisterEnum_t Dvar_RegisterEnum = (Dvar_RegisterEnum_t)0x412E40; + Dvar_RegisterString_t Dvar_RegisterString = (Dvar_RegisterString_t)0x4FC7E0; + Dvar_RegisterColor_t Dvar_RegisterColor = (Dvar_RegisterColor_t)0x4F28E0;//0x471500; + + Dvar_GetUnpackedColorByName_t Dvar_GetUnpackedColorByName = (Dvar_GetUnpackedColorByName_t)0x406530; + Dvar_FindVar_t Dvar_FindVar = (Dvar_FindVar_t)0x4D5390; + Dvar_InfoString_Big_t Dvar_InfoString_Big = (Dvar_InfoString_Big_t)0x4D98A0; + Dvar_SetCommand_t Dvar_SetCommand = (Dvar_SetCommand_t)0x4EE430; + + Field_Clear_t Field_Clear = (Field_Clear_t)0x437EB0; + + FreeMemory_t FreeMemory = (FreeMemory_t)0x4D6640; + + FS_FileExists_t FS_FileExists = (FS_FileExists_t)0x4DEFA0; + FS_FreeFile_t FS_FreeFile = (FS_FreeFile_t)0x4416B0; + FS_ReadFile_t FS_ReadFile = (FS_ReadFile_t)0x4F4B90; + FS_GetFileList_t FS_GetFileList = (FS_GetFileList_t)0x441BB0; + FS_FreeFileList_t FS_FreeFileList = (FS_FreeFileList_t)0x4A5DE0; + FS_FOpenFileAppend_t FS_FOpenFileAppend = (FS_FOpenFileAppend_t)0x410BB0; + FS_FOpenFileAppend_t FS_FOpenFileWrite = (FS_FOpenFileAppend_t)0x4BA530; + FS_FOpenFileRead_t FS_FOpenFileRead = (FS_FOpenFileRead_t)0x46CBF0; + FS_FOpenFileReadForThread_t FS_FOpenFileReadForThread = (FS_FOpenFileReadForThread_t)0x643270; + FS_FCloseFile_t FS_FCloseFile = (FS_FCloseFile_t)0x462000; + FS_WriteFile_t FS_WriteFile = (FS_WriteFile_t)0x426450; + FS_Write_t FS_Write = (FS_Write_t)0x4C06E0; + FS_Read_t FS_Read = (FS_Read_t)0x4A04C0; + FS_Seek_t FS_Seek = (FS_Seek_t)0x4A63D0; + FS_FTell_t FS_FTell = (FS_FTell_t)0x4E6760; + FS_Remove_t FS_Remove = (FS_Remove_t)0x4660F0; + FS_Restart_t FS_Restart = (FS_Restart_t)0x461A50; + FS_BuildPathToFile_t FS_BuildPathToFile = (FS_BuildPathToFile_t)0x4702C0; + + G_SpawnEntitiesFromString_t G_SpawnEntitiesFromString = (G_SpawnEntitiesFromString_t)0x4D8840; + + GScr_LoadGameTypeScript_t GScr_LoadGameTypeScript = (GScr_LoadGameTypeScript_t)0x4ED9A0; + + Image_LoadFromFileWithReader_t Image_LoadFromFileWithReader = (Image_LoadFromFileWithReader_t)0x53ABF0; + Image_Release_t Image_Release = (Image_Release_t)0x51F010; + + Menus_CloseAll_t Menus_CloseAll = (Menus_CloseAll_t)0x4BA5B0; + Menus_OpenByName_t Menus_OpenByName = (Menus_OpenByName_t)0x4CCE60; + Menus_FindByName_t Menus_FindByName = (Menus_FindByName_t)0x487240; + Menu_IsVisible_t Menu_IsVisible = (Menu_IsVisible_t)0x4D77D0; + Menus_MenuIsInStack_t Menus_MenuIsInStack = (Menus_MenuIsInStack_t)0x47ACB0; + + MSG_Init_t MSG_Init = (MSG_Init_t)0x45FCA0; + MSG_ReadData_t MSG_ReadData = (MSG_ReadData_t)0x4527C0; + MSG_ReadLong_t MSG_ReadLong = (MSG_ReadLong_t)0x4C9550; + MSG_ReadShort_t MSG_ReadShort = (MSG_ReadShort_t)0x40BDD0; + MSG_ReadInt64_t MSG_ReadInt64 = (MSG_ReadInt64_t)0x4F1850; + MSG_ReadString_t MSG_ReadString = (MSG_ReadString_t)0x60E2B0; + MSG_WriteByte_t MSG_WriteByte = (MSG_WriteByte_t)0x48C520; + MSG_WriteData_t MSG_WriteData = (MSG_WriteData_t)0x4F4120; + MSG_WriteLong_t MSG_WriteLong = (MSG_WriteLong_t)0x41CA20; + MSG_WriteBitsCompress_t MSG_WriteBitsCompress = (MSG_WriteBitsCompress_t)0x4319D0; + MSG_ReadByte_t MSG_ReadByte = (MSG_ReadByte_t)0x4C1C20; + MSG_ReadBitsCompress_t MSG_ReadBitsCompress = (MSG_ReadBitsCompress_t)0x4DCC30; + + NetadrToSockadr_t NetadrToSockadr = (NetadrToSockadr_t)0x4B4B40; + + NET_AdrToString_t NET_AdrToString = (NET_AdrToString_t)0x469880; + NET_CompareAdr_t NET_CompareAdr = (NET_CompareAdr_t)0x4D0AA0; + NET_IsLocalAddress_t NET_IsLocalAddress = (NET_IsLocalAddress_t)0x402BD0; + NET_StringToAdr_t NET_StringToAdr = (NET_StringToAdr_t)0x409010; + NET_OutOfBandPrint_t NET_OutOfBandPrint = (NET_OutOfBandPrint_t)0x4AEF00; + NET_OutOfBandData_t NET_OutOfBandData = (NET_OutOfBandData_t)0x49C7E0; + + Live_MPAcceptInvite_t Live_MPAcceptInvite = (Live_MPAcceptInvite_t)0x420A6D; + Live_ParsePlaylists_t Live_ParsePlaylists = (Live_ParsePlaylists_t)0x4295A0; + + LoadModdableRawfile_t LoadModdableRawfile = (LoadModdableRawfile_t)0x61ABC0; + + LocalizeString_t LocalizeString = (LocalizeString_t)0x4FB010; + LocalizeMapString_t LocalizeMapString = (LocalizeMapString_t)0x44BB30; + + PC_ReadToken_t PC_ReadToken = (PC_ReadToken_t)0x4ACCD0; + PC_ReadTokenHandle_t PC_ReadTokenHandle = (PC_ReadTokenHandle_t)0x4D2060; + PC_SourceError_t PC_SourceError = (PC_SourceError_t)0x467A00; + + Party_GetMaxPlayers_t Party_GetMaxPlayers = (Party_GetMaxPlayers_t)0x4F5D60; + PartyHost_CountMembers_t PartyHost_CountMembers = (PartyHost_CountMembers_t)0x497330; + PartyHost_GetMemberAddressBySlot_t PartyHost_GetMemberAddressBySlot = (PartyHost_GetMemberAddressBySlot_t)0x44E100; + PartyHost_GetMemberName_t PartyHost_GetMemberName = (PartyHost_GetMemberName_t)0x44BE90; + + R_AddCmdDrawStretchPic_t R_AddCmdDrawStretchPic = (R_AddCmdDrawStretchPic_t)0x509770; + R_RegisterFont_t R_RegisterFont = (R_RegisterFont_t)0x505670; + R_AddCmdDrawText_t R_AddCmdDrawText = (R_AddCmdDrawText_t)0x509D80; + R_LoadGraphicsAssets_t R_LoadGraphicsAssets = (R_LoadGraphicsAssets_t)0x506AC0; + R_TextWidth_t R_TextWidth = (R_TextWidth_t)0x5056C0; + R_TextHeight_t R_TextHeight = (R_TextHeight_t)0x505770; + + Scr_LoadGameType_t Scr_LoadGameType = (Scr_LoadGameType_t)0x4D9520; + + Scr_LoadScript_t Scr_LoadScript = (Scr_LoadScript_t)0x45D940; + Scr_GetFunctionHandle_t Scr_GetFunctionHandle = (Scr_GetFunctionHandle_t)0x4234F0; + + Scr_ExecThread_t Scr_ExecThread = (Scr_ExecThread_t)0x4AD0B0; + Scr_FreeThread_t Scr_FreeThread = (Scr_FreeThread_t)0x4BD320; + + Scr_ShutdownAllocNode_t Scr_ShutdownAllocNode = (Scr_ShutdownAllocNode_t)0x441650; + + Script_Alloc_t Script_Alloc = (Script_Alloc_t)0x422E70; + Script_SetupTokens_t Script_SetupTokens = (Script_SetupTokens_t)0x4E6950; + Script_CleanString_t Script_CleanString = (Script_CleanString_t)0x498220; + + SE_Load_t SE_Load = (SE_Load_t)0x502A30; + + Dvar_SetStringByName_t Dvar_SetStringByName = (Dvar_SetStringByName_t)0x44F060; + + SL_ConvertToString_t SL_ConvertToString = (SL_ConvertToString_t)0x4EC1D0; + SL_GetString_t SL_GetString = (SL_GetString_t)0x4CDC10; + + SND_InitDriver_t SND_InitDriver = (SND_InitDriver_t)0x4F5090; + + SockadrToNetadr_t SockadrToNetadr = (SockadrToNetadr_t)0x4F8460; + + Steam_JoinLobby_t Steam_JoinLobby = (Steam_JoinLobby_t)0x49CF70; + + SV_GameClientNum_Score_t SV_GameClientNum_Score = (SV_GameClientNum_Score_t)0x469AC0; + SV_GameSendServerCommand_t SV_GameSendServerCommand = (SV_GameSendServerCommand_t)0x4BC3A0; + + Sys_FreeFileList_t Sys_FreeFileList = (Sys_FreeFileList_t)0x4D8580; + Sys_IsMainThread_t Sys_IsMainThread = (Sys_IsMainThread_t)0x4C37D0; + Sys_SendPacket_t Sys_SendPacket = (Sys_SendPacket_t)0x60FDC0; + Sys_ShowConsole_t Sys_ShowConsole = (Sys_ShowConsole_t)0x4305E0; + Sys_ListFiles_t Sys_ListFiles = (Sys_ListFiles_t)0x45A660; + Sys_Milliseconds_t Sys_Milliseconds = (Sys_Milliseconds_t)0x42A660; + + UI_AddMenuList_t UI_AddMenuList = (UI_AddMenuList_t)0x4533C0; + UI_LoadMenus_t UI_LoadMenus = (UI_LoadMenus_t)0x641460; + UI_DrawHandlePic_t UI_DrawHandlePic = (UI_DrawHandlePic_t)0x4D0EA0; + UI_GetContext_t UI_GetContext = (UI_GetContext_t)0x4F8940; + UI_TextWidth_t UI_TextWidth = (UI_TextWidth_t)0x6315C0; + UI_DrawText_t UI_DrawText = (UI_DrawText_t)0x49C0D0; + + Win_GetLanguage_t Win_GetLanguage = (Win_GetLanguage_t)0x45CBA0; + + XAssetHeader* DB_XAssetPool = (XAssetHeader*)0x7998A8; + unsigned int* g_poolSize = (unsigned int*)0x7995E8; + + DWORD* cmd_id = (DWORD*)0x1AAC5D0; + DWORD* cmd_argc = (DWORD*)0x1AAC614; + char*** cmd_argv = (char***)0x1AAC634; + + DWORD* cmd_id_sv = (DWORD*)0x1ACF8A0; + DWORD* cmd_argc_sv = (DWORD*)0x1ACF8E4; + char*** cmd_argv_sv = (char***)0x1ACF904; + + source_t **sourceFiles = (source_t **)0x7C4A98; + keywordHash_t **menuParseKeywordHash = (keywordHash_t **)0x63AE928; + + int* svs_numclients = (int*)0x31D938C; + client_t* svs_clients = (client_t*)0x31D9390; + + UiContext *uiContext = (UiContext *)0x62E2858; + + int* arenaCount = (int*)0x62E6930; + mapArena_t* arenas = (mapArena_t*)0x62E6934; + + int* gameTypeCount = (int*)0x62E50A0; + gameTypeName_t* gameTypes = (gameTypeName_t*)0x62E50A4; + + XBlock** g_streamBlocks = (XBlock**)0x16E554C; + + bool* g_lobbyCreateInProgress = (bool*)0x66C9BC2; + party_t** partyIngame = (party_t**)0x1081C00; + PartyData_s** partyData = (PartyData_s**)0x107E500; + + int* numIP = (int*)0x64A1E68; + netIP_t* localIP = (netIP_t*)0x64A1E28; + + int* demoFile = (int*)0xA5EA1C; + int* demoPlaying = (int*)0xA5EA0C; + int* demoRecording = (int*)0xA5EA08; + int* serverMessageSequence = (int*)0xA3E9B4; + + gentity_t* g_entities = (gentity_t*)0x18835D8; + + netadr_t* connectedHost = (netadr_t*)0xA1E888; + + SOCKET* ip_socket = (SOCKET*)0x64A3008; + + SafeArea* safeArea = (SafeArea*)0xA15F3C; + + SpawnVar* spawnVars = (SpawnVar*)0x1A83DE8; + MapEnts** marMapEntsPtr = (MapEnts**)0x112AD34; + + XAssetHeader ReallocateAssetPool(XAssetType type, unsigned int newSize) + { + int elSize = DB_GetXAssetSizeHandlers[type](); + XAssetHeader poolEntry = { Utils::Memory::Allocate(newSize * elSize) }; + DB_XAssetPool[type] = poolEntry; + g_poolSize[type] = newSize; + return poolEntry; + } + + void Menu_FreeItemMemory(Game::itemDef_t* item) + { + __asm + { + mov edi, item + mov eax, 63D880h + call eax + } + } + + const char* TableLookup(StringTable* stringtable, int row, int column) + { + if (!stringtable || !stringtable->values || row >= stringtable->rowCount || column >= stringtable->columnCount) return ""; + + const char* value = stringtable->values[row * stringtable->columnCount + column].string; + if (!value) value = ""; + + return value; + } + + const char* UI_LocalizeMapName(const char* mapName) + { + for (int i = 0; i < *arenaCount; ++i) + { + if (!_stricmp(arenas[i].mapName, mapName)) + { + char* uiName = &arenas[i].uiName[0]; + if ((uiName[0] == 'M' && uiName[1] == 'P') || (uiName[0] == 'P' && uiName[1] == 'A')) // MPUI/PATCH + { + return LocalizeMapString(uiName); + } + + return uiName; + } + } + + return mapName; + } + + const char* UI_LocalizeGameType(const char* gameType) + { + if (!gameType || !*gameType) + { + return ""; + } + + for (int i = 0; i < *gameTypeCount; ++i) + { + if (!_stricmp(gameTypes[i].gameType, gameType)) + { + return LocalizeMapString(gameTypes[i].uiName); + } + } + + return gameType; + } + + float UI_GetScoreboardLeft(void* a1) + { + static int func = 0x590390; + __asm + { + mov eax, [esp + 4h] + call func + } + } + + const char *DB_GetXAssetName(XAsset *asset) + { + if (!asset) return ""; + return DB_GetXAssetNameHandlers[asset->type](&asset->header); + } + + XAssetType DB_GetXAssetNameType(const char* name) + { + for (int i = 0; i < ASSET_TYPE_COUNT; ++i) + { + XAssetType type = static_cast(i); + if (!_stricmp(DB_GetXAssetTypeName(type), name)) + { + return type; + } + } + + return ASSET_TYPE_INVALID; + } + + bool DB_IsZoneLoaded(const char* zone) + { + int zoneCount = Utils::Hook::Get(0x1261BCC); + char* zoneIndices = reinterpret_cast(0x16B8A34); + char* zoneData = reinterpret_cast(0x14C0F80); + + for (int i = 0; i < zoneCount; ++i) + { + std::string name = zoneData + 4 + 0xA4 * (zoneIndices[i] & 0xFF); + + if (name == zone) + { + return true; + } + } + + return false; + } + + void FS_AddLocalizedGameDirectory(const char *path, const char *dir) + { + __asm + { + mov ebx, path + mov eax, dir + mov ecx, 642EF0h + call ecx + } + } + + void MessageBox(std::string message, std::string title) + { + Dvar_SetStringByName("com_errorMessage", message.data()); + Dvar_SetStringByName("com_errorTitle", title.data()); + Cbuf_AddText(0, "openmenu error_popmenu_lobby"); + } + + unsigned int R_HashString(const char* string) + { + unsigned int hash = 0; + + while (*string) + { + hash = (*string | 0x20) ^ (33 * hash); + ++string; + } + + return hash; + } + + void SV_KickClient(client_t* client, const char* reason) + { + __asm + { + push edi + push esi + mov edi, 0 + mov esi, client + push reason + push 0 + push 0 + mov eax, 6249A0h + call eax + add esp, 0Ch + pop esi + pop edi + } + } + + void SV_KickClientError(client_t* client, std::string reason) + { + if (client->state < 5) + { + Components::Network::Send(client->addr, fmt::sprintf("error\n%s", reason.data())); + } + + SV_KickClient(client, reason.data()); + } +} diff --git a/src/Game/Functions.hpp b/src/Game/Functions.hpp index 6d942d49..30fc95cd 100644 --- a/src/Game/Functions.hpp +++ b/src/Game/Functions.hpp @@ -1,481 +1,481 @@ -namespace Game -{ - typedef void*(__cdecl * BG_LoadWeaponDef_LoadObj_t)(const char* filename); - extern BG_LoadWeaponDef_LoadObj_t BG_LoadWeaponDef_LoadObj; - - typedef void(__cdecl * Cbuf_AddServerText_t)(); - extern Cbuf_AddServerText_t Cbuf_AddServerText; - - typedef void(__cdecl * Cbuf_AddText_t)(int localClientNum, const char *text); - extern Cbuf_AddText_t Cbuf_AddText; - - typedef char*(__cdecl * CL_GetClientName_t)(int localClientNum, int index, char *buf, size_t size); - extern CL_GetClientName_t CL_GetClientName; - - typedef int(__cdecl * CL_IsCgameInitialized_t)(); - extern CL_IsCgameInitialized_t CL_IsCgameInitialized; - - typedef void(__cdecl * CL_ConnectFromParty_t)(int controllerIndex, _XSESSION_INFO *hostInfo, netadr_t addr, int numPublicSlots, int numPrivateSlots, const char *mapname, const char *gametype); - extern CL_ConnectFromParty_t CL_ConnectFromParty; - - typedef void(__cdecl * CL_DownloadsComplete_t)(int controller); - extern CL_DownloadsComplete_t CL_DownloadsComplete; - - typedef void(_cdecl * CL_DrawStretchPicPhysical_t)(float x, float y, float w, float h, float xScale, float yScale, float xay, float yay, const float *color, Game::Material* material); - extern CL_DrawStretchPicPhysical_t CL_DrawStretchPicPhysical; - - typedef void(__cdecl * CL_ResetViewport_t)(); - extern CL_ResetViewport_t CL_ResetViewport; - - typedef void(__cdecl * Cmd_AddCommand_t)(const char* name, void(*callback), cmd_function_t* data, char); - extern Cmd_AddCommand_t Cmd_AddCommand; - - typedef void(__cdecl * Cmd_AddServerCommand_t)(const char* name, void(*callback), cmd_function_t* data); - extern Cmd_AddServerCommand_t Cmd_AddServerCommand; - - typedef void(__cdecl * Cmd_ExecuteSingleCommand_t)(int localClientNum, int controllerIndex, const char* cmd); - extern Cmd_ExecuteSingleCommand_t Cmd_ExecuteSingleCommand; - - typedef void(__cdecl * Com_Error_t)(int type, char* message, ...); - extern Com_Error_t Com_Error; - - typedef void(__cdecl * Com_Printf_t)(int channel, const char *fmt, ...); - extern Com_Printf_t Com_Printf; - - typedef void(__cdecl * Com_PrintMessage_t)(int channel, const char *msg, int error); - extern Com_PrintMessage_t Com_PrintMessage; - - typedef char* (__cdecl * Com_ParseExt_t)(const char **data_p); - extern Com_ParseExt_t Com_ParseExt; - - typedef char* (__cdecl * Con_DrawMiniConsole_t)(int localClientNum, int xPos, int yPos, float alpha); - extern Con_DrawMiniConsole_t Con_DrawMiniConsole; - - typedef void (__cdecl * Con_DrawSolidConsole_t)(); - extern Con_DrawSolidConsole_t Con_DrawSolidConsole; - - typedef void(__cdecl * DB_EnumXAssets_t)(XAssetType type, void(*)(XAssetHeader, void *), void* userdata, bool overrides); - extern DB_EnumXAssets_t DB_EnumXAssets; - - typedef void(__cdecl * DB_EnumXAssets_Internal_t)(XAssetType type, void(*)(XAssetHeader, void *), void* userdata, bool overrides); - extern DB_EnumXAssets_Internal_t DB_EnumXAssets_Internal; - - typedef XAssetHeader (__cdecl * DB_FindXAssetHeader_t)(XAssetType type, const char* name); - extern DB_FindXAssetHeader_t DB_FindXAssetHeader; - - typedef const char* (__cdecl * DB_GetXAssetNameHandler_t)(XAssetHeader* asset); - extern DB_GetXAssetNameHandler_t* DB_GetXAssetNameHandlers; - - typedef int(__cdecl * DB_GetXAssetSizeHandler_t)(); - extern DB_GetXAssetSizeHandler_t* DB_GetXAssetSizeHandlers; - - typedef const char *(__cdecl * DB_GetXAssetTypeName_t)(XAssetType type); - extern DB_GetXAssetTypeName_t DB_GetXAssetTypeName; - - typedef const char *(__cdecl * DB_IsXAssetDefault_t)(XAssetType type, const char* name); - extern DB_IsXAssetDefault_t DB_IsXAssetDefault; - - typedef void(*DB_LoadXAssets_t)(XZoneInfo *zoneInfo, unsigned int zoneCount, int sync); - extern DB_LoadXAssets_t DB_LoadXAssets; - - typedef void(__cdecl * DB_ReadXFileUncompressed_t)(void* buffer, int size); - extern DB_ReadXFileUncompressed_t DB_ReadXFileUncompressed; - - typedef dvar_t* (__cdecl * Dvar_RegisterBool_t)(const char* name, bool default, int flags, const char* description); - extern Dvar_RegisterBool_t Dvar_RegisterBool; - - typedef dvar_t* (__cdecl * Dvar_RegisterFloat_t)(const char* name, float default, float min, float max, int flags, const char* description); - extern Dvar_RegisterFloat_t Dvar_RegisterFloat; - - typedef dvar_t* (__cdecl * Dvar_RegisterVec2_t)(const char* name, float defx, float defy, float min, float max, int flags, const char* description); - extern Dvar_RegisterVec2_t Dvar_RegisterVec2; - - typedef dvar_t* (__cdecl * Dvar_RegisterVec3_t)(const char* name, float defx, float defy, float defz, float min, float max, int flags, const char* description); - extern Dvar_RegisterVec3_t Dvar_RegisterVec3; - - typedef dvar_t* (__cdecl * Dvar_RegisterVec4_t)(const char* name, float defx, float defy, float defz, float defw, float min, float max, int flags, const char* description); - extern Dvar_RegisterVec4_t Dvar_RegisterVec4; - - typedef dvar_t* (__cdecl * Dvar_RegisterInt_t)(const char* name, int default, int min, int max, int flags, const char* description); - extern Dvar_RegisterInt_t Dvar_RegisterInt; - - typedef dvar_t* (__cdecl * Dvar_RegisterEnum_t)(const char* name, char** enumValues, int default, int flags, const char* description); - extern Dvar_RegisterEnum_t Dvar_RegisterEnum; - - typedef dvar_t* (__cdecl * Dvar_RegisterString_t)(const char* name, const char* default, int, const char*); - extern Dvar_RegisterString_t Dvar_RegisterString; - - typedef dvar_t* (__cdecl * Dvar_RegisterColor_t)(const char* name, float r, float g, float b, float a, int flags, const char* description); - extern Dvar_RegisterColor_t Dvar_RegisterColor; - - typedef void(__cdecl * Dvar_GetUnpackedColorByName_t)(const char* name, float* color); - extern Dvar_GetUnpackedColorByName_t Dvar_GetUnpackedColorByName; - - typedef dvar_t* (__cdecl * Dvar_FindVar_t)(const char *dvarName); - extern Dvar_FindVar_t Dvar_FindVar; - - typedef char* (__cdecl* Dvar_InfoString_Big_t)(int typeMask); - extern Dvar_InfoString_Big_t Dvar_InfoString_Big; - - typedef dvar_t* (__cdecl * Dvar_SetCommand_t)(const char* name, const char* value); - extern Dvar_SetCommand_t Dvar_SetCommand; - - typedef void(__cdecl * Field_Clear_t)(void* field); - extern Field_Clear_t Field_Clear; - - typedef void(__cdecl * FreeMemory_t)(void* buffer); - extern FreeMemory_t FreeMemory; - - typedef void(__cdecl * FS_FreeFile_t)(void* buffer); - extern FS_FreeFile_t FS_FreeFile; - - typedef int(__cdecl * FS_ReadFile_t)(const char* path, char** buffer); - extern FS_ReadFile_t FS_ReadFile; - - typedef char** (__cdecl * FS_GetFileList_t)(const char *path, const char *extension, FsListBehavior_e behavior, int *numfiles, int allocTrackType); - extern FS_GetFileList_t FS_GetFileList; - - typedef void(__cdecl * FS_FreeFileList_t)(char** list); - extern FS_FreeFileList_t FS_FreeFileList; - - typedef int(__cdecl * FS_FOpenFileAppend_t)(const char* file); - extern FS_FOpenFileAppend_t FS_FOpenFileAppend; - extern FS_FOpenFileAppend_t FS_FOpenFileWrite; - - typedef int(__cdecl * FS_FOpenFileRead_t)(const char* file, int* fh, int uniqueFile); - extern FS_FOpenFileRead_t FS_FOpenFileRead; - - typedef int(__cdecl * FS_FOpenFileReadForThread_t)(const char *filename, int *file, int thread); - extern FS_FOpenFileReadForThread_t FS_FOpenFileReadForThread; - - typedef int(__cdecl * FS_FCloseFile_t)(int fh); - extern FS_FCloseFile_t FS_FCloseFile; - - typedef bool(__cdecl * FS_FileExists_t)(const char* file); - extern FS_FileExists_t FS_FileExists; - - typedef bool(__cdecl * FS_WriteFile_t)(char* filename, char* folder, void* buffer, int size); - extern FS_WriteFile_t FS_WriteFile; - - typedef int(__cdecl * FS_Write_t)(const void* buffer, size_t size, int file); - extern FS_Write_t FS_Write; - - typedef int(__cdecl * FS_Read_t)(void* buffer, size_t size, int file); - extern FS_Read_t FS_Read; - - typedef int(__cdecl * FS_Seek_t)(int fileHandle, int seekPosition, int seekOrigin); - extern FS_Seek_t FS_Seek; - - typedef int(__cdecl * FS_FTell_t)(int fileHandle); - extern FS_FTell_t FS_FTell; - - typedef int(__cdecl * FS_Remove_t)(char *); - extern FS_Remove_t FS_Remove; - - typedef int(__cdecl * FS_Restart_t)(int localClientNum, int checksumFeed); - extern FS_Restart_t FS_Restart; - - typedef int(__cdecl * FS_BuildPathToFile_t)(const char*, const char*, const char*, char**); - extern FS_BuildPathToFile_t FS_BuildPathToFile; - - typedef void(__cdecl* G_SpawnEntitiesFromString_t)(); - extern G_SpawnEntitiesFromString_t G_SpawnEntitiesFromString; - - typedef void(__cdecl * GScr_LoadGameTypeScript_t)(); - extern GScr_LoadGameTypeScript_t GScr_LoadGameTypeScript; - - typedef int(__cdecl * Reader_t)(char const*, int *); - typedef bool(__cdecl * Image_LoadFromFileWithReader_t)(GfxImage* image, Reader_t reader); - extern Image_LoadFromFileWithReader_t Image_LoadFromFileWithReader; - - typedef void(__cdecl * Image_Release_t)(GfxImage* image); - extern Image_Release_t Image_Release; - - typedef void(__cdecl * Menus_CloseAll_t)(UiContext *dc); - extern Menus_CloseAll_t Menus_CloseAll; - - typedef int(__cdecl * Menus_OpenByName_t)(UiContext *dc, const char *p); - extern Menus_OpenByName_t Menus_OpenByName; - - typedef menuDef_t *(__cdecl * Menus_FindByName_t)(UiContext *dc, const char *name); - extern Menus_FindByName_t Menus_FindByName; - - typedef bool(__cdecl * Menu_IsVisible_t)(UiContext *dc, menuDef_t *menu); - extern Menu_IsVisible_t Menu_IsVisible; - - typedef bool(__cdecl * Menus_MenuIsInStack_t)(UiContext *dc, menuDef_t *menu); - extern Menus_MenuIsInStack_t Menus_MenuIsInStack; - - typedef void(__cdecl * MSG_Init_t)(msg_t *buf, char *data, int length); - extern MSG_Init_t MSG_Init; - - typedef void(__cdecl * MSG_ReadData_t)(msg_t *msg, void *data, int len); - extern MSG_ReadData_t MSG_ReadData; - - typedef int(__cdecl * MSG_ReadLong_t)(msg_t* msg); - extern MSG_ReadLong_t MSG_ReadLong; - - typedef short(__cdecl * MSG_ReadShort_t)(msg_t* msg); - extern MSG_ReadShort_t MSG_ReadShort; - - typedef __int64(__cdecl * MSG_ReadInt64_t)(msg_t* msg); - extern MSG_ReadInt64_t MSG_ReadInt64; - - typedef char* (__cdecl * MSG_ReadString_t)(msg_t* msg); - extern MSG_ReadString_t MSG_ReadString; - - typedef int(__cdecl * MSG_ReadByte_t)(msg_t* msg); - extern MSG_ReadByte_t MSG_ReadByte; - - typedef int(__cdecl * MSG_ReadBitsCompress_t)(const char *from, char *to, int size); - extern MSG_ReadBitsCompress_t MSG_ReadBitsCompress; - - typedef void(__cdecl * MSG_WriteByte_t)(msg_t* msg, unsigned char c); - extern MSG_WriteByte_t MSG_WriteByte; - - typedef void(__cdecl * MSG_WriteData_t)(msg_t *buf, const void *data, int length); - extern MSG_WriteData_t MSG_WriteData; - - typedef void(__cdecl * MSG_WriteLong_t)(msg_t *msg, int c); - extern MSG_WriteLong_t MSG_WriteLong; - - typedef int(__cdecl * MSG_WriteBitsCompress_t)(bool trainHuffman, const char *from, char *to, int size); - extern MSG_WriteBitsCompress_t MSG_WriteBitsCompress; - - typedef void(__cdecl * NetadrToSockadr_t)(netadr_t *a, sockaddr *s); - extern NetadrToSockadr_t NetadrToSockadr; - - typedef const char* (__cdecl * NET_AdrToString_t)(netadr_t adr); - extern NET_AdrToString_t NET_AdrToString; - - typedef bool(__cdecl * NET_CompareAdr_t)(netadr_t a, netadr_t b); - extern NET_CompareAdr_t NET_CompareAdr; - - typedef bool(__cdecl * NET_IsLocalAddress_t)(netadr_t adr); - extern NET_IsLocalAddress_t NET_IsLocalAddress; - - typedef bool(__cdecl * NET_StringToAdr_t)(const char *s, netadr_t *a); - extern NET_StringToAdr_t NET_StringToAdr; - - typedef void(__cdecl* NET_OutOfBandPrint_t)(netsrc_t sock, netadr_t adr, const char *data); - extern NET_OutOfBandPrint_t NET_OutOfBandPrint; - - typedef void(__cdecl* NET_OutOfBandData_t)(netsrc_t sock, netadr_t adr, const char *format, int len); - extern NET_OutOfBandData_t NET_OutOfBandData; - - typedef void(__cdecl * Live_MPAcceptInvite_t)(_XSESSION_INFO *hostInfo, const int controllerIndex, bool fromGameInvite); - extern Live_MPAcceptInvite_t Live_MPAcceptInvite; - - typedef void(__cdecl * Live_ParsePlaylists_t)(const char* data); - extern Live_ParsePlaylists_t Live_ParsePlaylists; - - typedef void* (__cdecl * LoadModdableRawfile_t)(int a1, const char* filename); - extern LoadModdableRawfile_t LoadModdableRawfile; - - typedef char* (__cdecl * LocalizeString_t)(char*, char*); - extern LocalizeString_t LocalizeString; - - typedef char* (__cdecl * LocalizeMapString_t)(char*); - extern LocalizeMapString_t LocalizeMapString; - - typedef int(__cdecl * PC_ReadToken_t)(source_t*, token_t*); - extern PC_ReadToken_t PC_ReadToken; - - typedef int(__cdecl * PC_ReadTokenHandle_t)(int handle, pc_token_s *pc_token); - extern PC_ReadTokenHandle_t PC_ReadTokenHandle; - - typedef void(__cdecl * PC_SourceError_t)(int, const char*, ...); - extern PC_SourceError_t PC_SourceError; - - typedef int(__cdecl * Party_GetMaxPlayers_t)(party_s* party); - extern Party_GetMaxPlayers_t Party_GetMaxPlayers; - - typedef int(__cdecl * PartyHost_CountMembers_t)(PartyData_s* party); - extern PartyHost_CountMembers_t PartyHost_CountMembers; - - typedef netadr_t *(__cdecl * PartyHost_GetMemberAddressBySlot_t)(int unk, void *party, const int slot); - extern PartyHost_GetMemberAddressBySlot_t PartyHost_GetMemberAddressBySlot; - - typedef const char *(__cdecl * PartyHost_GetMemberName_t)(PartyData_s* party, const int clientNum); - extern PartyHost_GetMemberName_t PartyHost_GetMemberName; - - typedef Font* (__cdecl * R_RegisterFont_t)(const char* asset); - extern R_RegisterFont_t R_RegisterFont; - - typedef void(__cdecl * R_AddCmdDrawText_t)(const char *text, int maxChars, Font *font, float x, float y, float xScale, float yScale, float rotation, const float *color, int style); - extern R_AddCmdDrawText_t R_AddCmdDrawText; - - typedef void(_cdecl * R_AddCmdDrawStretchPic_t)(float x, float y, float w, float h, float xScale, float yScale, float xay, float yay, const float *color, Game::Material* material); - extern R_AddCmdDrawStretchPic_t R_AddCmdDrawStretchPic; - - typedef void(__cdecl * R_LoadGraphicsAssets_t)(); - extern R_LoadGraphicsAssets_t R_LoadGraphicsAssets; - - typedef int(__cdecl * R_TextWidth_t)(const char* text, int maxlength, Font* font); - extern R_TextWidth_t R_TextWidth; - - typedef int(__cdecl * R_TextHeight_t)(Font* font); - extern R_TextHeight_t R_TextHeight; - - typedef void(__cdecl * Scr_ShutdownAllocNode_t)(); - extern Scr_ShutdownAllocNode_t Scr_ShutdownAllocNode; - - typedef int(__cdecl * Scr_LoadGameType_t)(); - extern Scr_LoadGameType_t Scr_LoadGameType; - - typedef int(__cdecl * Scr_LoadScript_t)(const char*); - extern Scr_LoadScript_t Scr_LoadScript; - - typedef int(__cdecl * Scr_GetFunctionHandle_t)(const char*, const char*); - extern Scr_GetFunctionHandle_t Scr_GetFunctionHandle; - - typedef int(__cdecl * Scr_ExecThread_t)(int, int); - extern Scr_ExecThread_t Scr_ExecThread; - - typedef int(__cdecl * Scr_FreeThread_t)(int); - extern Scr_FreeThread_t Scr_FreeThread; - - typedef script_t* (__cdecl * Script_Alloc_t)(int length); - extern Script_Alloc_t Script_Alloc; - - typedef void(__cdecl * Script_SetupTokens_t)(script_t* script, void* tokens); - extern Script_SetupTokens_t Script_SetupTokens; - - typedef int(__cdecl * Script_CleanString_t)(char* buffer); - extern Script_CleanString_t Script_CleanString; - - typedef char* (__cdecl * SE_Load_t)(char* file, int Unk); - extern SE_Load_t SE_Load; - - typedef void(__cdecl * Dvar_SetStringByName_t)(const char* cvar, const char* value); - extern Dvar_SetStringByName_t Dvar_SetStringByName; - - typedef char* (__cdecl * SL_ConvertToString_t)(unsigned short stringValue); - extern SL_ConvertToString_t SL_ConvertToString; - - typedef short(__cdecl * SL_GetString_t)(const char *str, unsigned int user); - extern SL_GetString_t SL_GetString; - - typedef void(__cdecl * SND_InitDriver_t)(); - extern SND_InitDriver_t SND_InitDriver; - - typedef void(__cdecl * SockadrToNetadr_t)(sockaddr *s, netadr_t *a); - extern SockadrToNetadr_t SockadrToNetadr; - - typedef void(__cdecl * Steam_JoinLobby_t)(SteamID, char); - extern Steam_JoinLobby_t Steam_JoinLobby; - - typedef int(__cdecl* SV_GameClientNum_Score_t)(int clientID); - extern SV_GameClientNum_Score_t SV_GameClientNum_Score; - - typedef void(__cdecl * SV_GameSendServerCommand_t)(int clientNum, /*svscmd_type*/int type, const char* text); - extern SV_GameSendServerCommand_t SV_GameSendServerCommand; - - typedef FS_FreeFileList_t Sys_FreeFileList_t; - extern Sys_FreeFileList_t Sys_FreeFileList; - - typedef bool(__cdecl * Sys_IsMainThread_t)(); - extern Sys_IsMainThread_t Sys_IsMainThread; - - typedef char** (__cdecl * Sys_ListFiles_t)(const char *directory, const char *extension, const char *filter, int *numfiles, int wantsubs); - extern Sys_ListFiles_t Sys_ListFiles; - - typedef int(__cdecl * Sys_Milliseconds_t)(); - extern Sys_Milliseconds_t Sys_Milliseconds; - - typedef bool(__cdecl * Sys_SendPacket_t)(netsrc_t sock, size_t len, const char *format, netadr_t adr); - extern Sys_SendPacket_t Sys_SendPacket; - - typedef void(__cdecl * Sys_ShowConsole_t)(); - extern Sys_ShowConsole_t Sys_ShowConsole; - - typedef void(__cdecl * UI_AddMenuList_t)(UiContext *dc, MenuList *menuList, int close); - extern UI_AddMenuList_t UI_AddMenuList; - - typedef MenuList *(__cdecl * UI_LoadMenus_t)(const char *menuFile, int imageTrack); - extern UI_LoadMenus_t UI_LoadMenus; - - typedef void(__cdecl * UI_DrawHandlePic_t)(/*ScreenPlacement*/void *scrPlace, float x, float y, float w, float h, int horzAlign, int vertAlign, const float *color, Material *material); - extern UI_DrawHandlePic_t UI_DrawHandlePic; - - typedef void* (__cdecl * UI_GetContext_t)(void*); - extern UI_GetContext_t UI_GetContext; - - typedef int(__cdecl * UI_TextWidth_t)(const char *text, int maxChars, Font *font, float scale); - extern UI_TextWidth_t UI_TextWidth; - - typedef void(__cdecl * UI_DrawText_t)(void* scrPlace, const char *text, int maxChars, Font *font, float x, float y, int horzAlign, int vertAlign, float scale, const float *color, int style); - extern UI_DrawText_t UI_DrawText; - - typedef const char * (__cdecl * Win_GetLanguage_t)(); - extern Win_GetLanguage_t Win_GetLanguage; - - extern XAssetHeader* DB_XAssetPool; - extern unsigned int* g_poolSize; - - extern DWORD* cmd_id; - extern DWORD* cmd_argc; - extern char*** cmd_argv; - - extern DWORD* cmd_id_sv; - extern DWORD* cmd_argc_sv; - extern char*** cmd_argv_sv; - - extern int* svs_numclients; - extern client_t* svs_clients; - - extern source_t **sourceFiles; - extern keywordHash_t **menuParseKeywordHash; - - extern UiContext *uiContext; - - extern int* arenaCount; - extern mapArena_t* arenas; - - extern int* gameTypeCount; - extern gameTypeName_t* gameTypes; - - extern XBlock** g_streamBlocks; - - extern bool* g_lobbyCreateInProgress; - extern party_t** partyIngame; - extern PartyData_s** partyData; - - extern int* numIP; - extern netIP_t* localIP; - - extern int* demoFile; - extern int* demoPlaying; - extern int* demoRecording; - extern int* serverMessageSequence; - - extern gentity_t* g_entities; - - extern netadr_t* connectedHost; - extern SOCKET* ip_socket; - - extern SafeArea* safeArea; - - extern SpawnVar* spawnVars; - extern MapEnts** marMapEntsPtr; - - XAssetHeader ReallocateAssetPool(XAssetType type, unsigned int newSize); - void Menu_FreeItemMemory(Game::itemDef_t* item); - const char* TableLookup(StringTable* stringtable, int row, int column); - const char* UI_LocalizeMapName(const char* mapName); - const char* UI_LocalizeGameType(const char* gameType); - float UI_GetScoreboardLeft(void*); - - const char *DB_GetXAssetName(XAsset *asset); - XAssetType DB_GetXAssetNameType(const char* name); - bool DB_IsZoneLoaded(const char* zone); - - void FS_AddLocalizedGameDirectory(const char *path, const char *dir); - - void MessageBox(std::string message, std::string title); - - unsigned int R_HashString(const char* string); - - void SV_KickClient(client_t* client, const char* reason); - void SV_KickClientError(client_t* client, const char* reason); -} +namespace Game +{ + typedef void*(__cdecl * BG_LoadWeaponDef_LoadObj_t)(const char* filename); + extern BG_LoadWeaponDef_LoadObj_t BG_LoadWeaponDef_LoadObj; + + typedef void(__cdecl * Cbuf_AddServerText_t)(); + extern Cbuf_AddServerText_t Cbuf_AddServerText; + + typedef void(__cdecl * Cbuf_AddText_t)(int localClientNum, const char *text); + extern Cbuf_AddText_t Cbuf_AddText; + + typedef char*(__cdecl * CL_GetClientName_t)(int localClientNum, int index, char *buf, size_t size); + extern CL_GetClientName_t CL_GetClientName; + + typedef int(__cdecl * CL_IsCgameInitialized_t)(); + extern CL_IsCgameInitialized_t CL_IsCgameInitialized; + + typedef void(__cdecl * CL_ConnectFromParty_t)(int controllerIndex, _XSESSION_INFO *hostInfo, netadr_t addr, int numPublicSlots, int numPrivateSlots, const char *mapname, const char *gametype); + extern CL_ConnectFromParty_t CL_ConnectFromParty; + + typedef void(__cdecl * CL_DownloadsComplete_t)(int controller); + extern CL_DownloadsComplete_t CL_DownloadsComplete; + + typedef void(_cdecl * CL_DrawStretchPicPhysical_t)(float x, float y, float w, float h, float xScale, float yScale, float xay, float yay, const float *color, Game::Material* material); + extern CL_DrawStretchPicPhysical_t CL_DrawStretchPicPhysical; + + typedef void(__cdecl * CL_ResetViewport_t)(); + extern CL_ResetViewport_t CL_ResetViewport; + + typedef void(__cdecl * Cmd_AddCommand_t)(const char* name, void(*callback), cmd_function_t* data, char); + extern Cmd_AddCommand_t Cmd_AddCommand; + + typedef void(__cdecl * Cmd_AddServerCommand_t)(const char* name, void(*callback), cmd_function_t* data); + extern Cmd_AddServerCommand_t Cmd_AddServerCommand; + + typedef void(__cdecl * Cmd_ExecuteSingleCommand_t)(int localClientNum, int controllerIndex, const char* cmd); + extern Cmd_ExecuteSingleCommand_t Cmd_ExecuteSingleCommand; + + typedef void(__cdecl * Com_Error_t)(int type, char* message, ...); + extern Com_Error_t Com_Error; + + typedef void(__cdecl * Com_Printf_t)(int channel, const char *fmt, ...); + extern Com_Printf_t Com_Printf; + + typedef void(__cdecl * Com_PrintMessage_t)(int channel, const char *msg, int error); + extern Com_PrintMessage_t Com_PrintMessage; + + typedef char* (__cdecl * Com_ParseExt_t)(const char **data_p); + extern Com_ParseExt_t Com_ParseExt; + + typedef char* (__cdecl * Con_DrawMiniConsole_t)(int localClientNum, int xPos, int yPos, float alpha); + extern Con_DrawMiniConsole_t Con_DrawMiniConsole; + + typedef void (__cdecl * Con_DrawSolidConsole_t)(); + extern Con_DrawSolidConsole_t Con_DrawSolidConsole; + + typedef void(__cdecl * DB_EnumXAssets_t)(XAssetType type, void(*)(XAssetHeader, void *), void* userdata, bool overrides); + extern DB_EnumXAssets_t DB_EnumXAssets; + + typedef void(__cdecl * DB_EnumXAssets_Internal_t)(XAssetType type, void(*)(XAssetHeader, void *), void* userdata, bool overrides); + extern DB_EnumXAssets_Internal_t DB_EnumXAssets_Internal; + + typedef XAssetHeader (__cdecl * DB_FindXAssetHeader_t)(XAssetType type, const char* name); + extern DB_FindXAssetHeader_t DB_FindXAssetHeader; + + typedef const char* (__cdecl * DB_GetXAssetNameHandler_t)(XAssetHeader* asset); + extern DB_GetXAssetNameHandler_t* DB_GetXAssetNameHandlers; + + typedef int(__cdecl * DB_GetXAssetSizeHandler_t)(); + extern DB_GetXAssetSizeHandler_t* DB_GetXAssetSizeHandlers; + + typedef const char *(__cdecl * DB_GetXAssetTypeName_t)(XAssetType type); + extern DB_GetXAssetTypeName_t DB_GetXAssetTypeName; + + typedef const char *(__cdecl * DB_IsXAssetDefault_t)(XAssetType type, const char* name); + extern DB_IsXAssetDefault_t DB_IsXAssetDefault; + + typedef void(*DB_LoadXAssets_t)(XZoneInfo *zoneInfo, unsigned int zoneCount, int sync); + extern DB_LoadXAssets_t DB_LoadXAssets; + + typedef void(__cdecl * DB_ReadXFileUncompressed_t)(void* buffer, int size); + extern DB_ReadXFileUncompressed_t DB_ReadXFileUncompressed; + + typedef dvar_t* (__cdecl * Dvar_RegisterBool_t)(const char* name, bool default, int flags, const char* description); + extern Dvar_RegisterBool_t Dvar_RegisterBool; + + typedef dvar_t* (__cdecl * Dvar_RegisterFloat_t)(const char* name, float default, float min, float max, int flags, const char* description); + extern Dvar_RegisterFloat_t Dvar_RegisterFloat; + + typedef dvar_t* (__cdecl * Dvar_RegisterVec2_t)(const char* name, float defx, float defy, float min, float max, int flags, const char* description); + extern Dvar_RegisterVec2_t Dvar_RegisterVec2; + + typedef dvar_t* (__cdecl * Dvar_RegisterVec3_t)(const char* name, float defx, float defy, float defz, float min, float max, int flags, const char* description); + extern Dvar_RegisterVec3_t Dvar_RegisterVec3; + + typedef dvar_t* (__cdecl * Dvar_RegisterVec4_t)(const char* name, float defx, float defy, float defz, float defw, float min, float max, int flags, const char* description); + extern Dvar_RegisterVec4_t Dvar_RegisterVec4; + + typedef dvar_t* (__cdecl * Dvar_RegisterInt_t)(const char* name, int default, int min, int max, int flags, const char* description); + extern Dvar_RegisterInt_t Dvar_RegisterInt; + + typedef dvar_t* (__cdecl * Dvar_RegisterEnum_t)(const char* name, char** enumValues, int default, int flags, const char* description); + extern Dvar_RegisterEnum_t Dvar_RegisterEnum; + + typedef dvar_t* (__cdecl * Dvar_RegisterString_t)(const char* name, const char* default, int, const char*); + extern Dvar_RegisterString_t Dvar_RegisterString; + + typedef dvar_t* (__cdecl * Dvar_RegisterColor_t)(const char* name, float r, float g, float b, float a, int flags, const char* description); + extern Dvar_RegisterColor_t Dvar_RegisterColor; + + typedef void(__cdecl * Dvar_GetUnpackedColorByName_t)(const char* name, float* color); + extern Dvar_GetUnpackedColorByName_t Dvar_GetUnpackedColorByName; + + typedef dvar_t* (__cdecl * Dvar_FindVar_t)(const char *dvarName); + extern Dvar_FindVar_t Dvar_FindVar; + + typedef char* (__cdecl* Dvar_InfoString_Big_t)(int typeMask); + extern Dvar_InfoString_Big_t Dvar_InfoString_Big; + + typedef dvar_t* (__cdecl * Dvar_SetCommand_t)(const char* name, const char* value); + extern Dvar_SetCommand_t Dvar_SetCommand; + + typedef void(__cdecl * Field_Clear_t)(void* field); + extern Field_Clear_t Field_Clear; + + typedef void(__cdecl * FreeMemory_t)(void* buffer); + extern FreeMemory_t FreeMemory; + + typedef void(__cdecl * FS_FreeFile_t)(void* buffer); + extern FS_FreeFile_t FS_FreeFile; + + typedef int(__cdecl * FS_ReadFile_t)(const char* path, char** buffer); + extern FS_ReadFile_t FS_ReadFile; + + typedef char** (__cdecl * FS_GetFileList_t)(const char *path, const char *extension, FsListBehavior_e behavior, int *numfiles, int allocTrackType); + extern FS_GetFileList_t FS_GetFileList; + + typedef void(__cdecl * FS_FreeFileList_t)(char** list); + extern FS_FreeFileList_t FS_FreeFileList; + + typedef int(__cdecl * FS_FOpenFileAppend_t)(const char* file); + extern FS_FOpenFileAppend_t FS_FOpenFileAppend; + extern FS_FOpenFileAppend_t FS_FOpenFileWrite; + + typedef int(__cdecl * FS_FOpenFileRead_t)(const char* file, int* fh, int uniqueFile); + extern FS_FOpenFileRead_t FS_FOpenFileRead; + + typedef int(__cdecl * FS_FOpenFileReadForThread_t)(const char *filename, int *file, int thread); + extern FS_FOpenFileReadForThread_t FS_FOpenFileReadForThread; + + typedef int(__cdecl * FS_FCloseFile_t)(int fh); + extern FS_FCloseFile_t FS_FCloseFile; + + typedef bool(__cdecl * FS_FileExists_t)(const char* file); + extern FS_FileExists_t FS_FileExists; + + typedef bool(__cdecl * FS_WriteFile_t)(char* filename, char* folder, void* buffer, int size); + extern FS_WriteFile_t FS_WriteFile; + + typedef int(__cdecl * FS_Write_t)(const void* buffer, size_t size, int file); + extern FS_Write_t FS_Write; + + typedef int(__cdecl * FS_Read_t)(void* buffer, size_t size, int file); + extern FS_Read_t FS_Read; + + typedef int(__cdecl * FS_Seek_t)(int fileHandle, int seekPosition, int seekOrigin); + extern FS_Seek_t FS_Seek; + + typedef int(__cdecl * FS_FTell_t)(int fileHandle); + extern FS_FTell_t FS_FTell; + + typedef int(__cdecl * FS_Remove_t)(char *); + extern FS_Remove_t FS_Remove; + + typedef int(__cdecl * FS_Restart_t)(int localClientNum, int checksumFeed); + extern FS_Restart_t FS_Restart; + + typedef int(__cdecl * FS_BuildPathToFile_t)(const char*, const char*, const char*, char**); + extern FS_BuildPathToFile_t FS_BuildPathToFile; + + typedef void(__cdecl* G_SpawnEntitiesFromString_t)(); + extern G_SpawnEntitiesFromString_t G_SpawnEntitiesFromString; + + typedef void(__cdecl * GScr_LoadGameTypeScript_t)(); + extern GScr_LoadGameTypeScript_t GScr_LoadGameTypeScript; + + typedef int(__cdecl * Reader_t)(char const*, int *); + typedef bool(__cdecl * Image_LoadFromFileWithReader_t)(GfxImage* image, Reader_t reader); + extern Image_LoadFromFileWithReader_t Image_LoadFromFileWithReader; + + typedef void(__cdecl * Image_Release_t)(GfxImage* image); + extern Image_Release_t Image_Release; + + typedef void(__cdecl * Menus_CloseAll_t)(UiContext *dc); + extern Menus_CloseAll_t Menus_CloseAll; + + typedef int(__cdecl * Menus_OpenByName_t)(UiContext *dc, const char *p); + extern Menus_OpenByName_t Menus_OpenByName; + + typedef menuDef_t *(__cdecl * Menus_FindByName_t)(UiContext *dc, const char *name); + extern Menus_FindByName_t Menus_FindByName; + + typedef bool(__cdecl * Menu_IsVisible_t)(UiContext *dc, menuDef_t *menu); + extern Menu_IsVisible_t Menu_IsVisible; + + typedef bool(__cdecl * Menus_MenuIsInStack_t)(UiContext *dc, menuDef_t *menu); + extern Menus_MenuIsInStack_t Menus_MenuIsInStack; + + typedef void(__cdecl * MSG_Init_t)(msg_t *buf, char *data, int length); + extern MSG_Init_t MSG_Init; + + typedef void(__cdecl * MSG_ReadData_t)(msg_t *msg, void *data, int len); + extern MSG_ReadData_t MSG_ReadData; + + typedef int(__cdecl * MSG_ReadLong_t)(msg_t* msg); + extern MSG_ReadLong_t MSG_ReadLong; + + typedef short(__cdecl * MSG_ReadShort_t)(msg_t* msg); + extern MSG_ReadShort_t MSG_ReadShort; + + typedef __int64(__cdecl * MSG_ReadInt64_t)(msg_t* msg); + extern MSG_ReadInt64_t MSG_ReadInt64; + + typedef char* (__cdecl * MSG_ReadString_t)(msg_t* msg); + extern MSG_ReadString_t MSG_ReadString; + + typedef int(__cdecl * MSG_ReadByte_t)(msg_t* msg); + extern MSG_ReadByte_t MSG_ReadByte; + + typedef int(__cdecl * MSG_ReadBitsCompress_t)(const char *from, char *to, int size); + extern MSG_ReadBitsCompress_t MSG_ReadBitsCompress; + + typedef void(__cdecl * MSG_WriteByte_t)(msg_t* msg, unsigned char c); + extern MSG_WriteByte_t MSG_WriteByte; + + typedef void(__cdecl * MSG_WriteData_t)(msg_t *buf, const void *data, int length); + extern MSG_WriteData_t MSG_WriteData; + + typedef void(__cdecl * MSG_WriteLong_t)(msg_t *msg, int c); + extern MSG_WriteLong_t MSG_WriteLong; + + typedef int(__cdecl * MSG_WriteBitsCompress_t)(bool trainHuffman, const char *from, char *to, int size); + extern MSG_WriteBitsCompress_t MSG_WriteBitsCompress; + + typedef void(__cdecl * NetadrToSockadr_t)(netadr_t *a, sockaddr *s); + extern NetadrToSockadr_t NetadrToSockadr; + + typedef const char* (__cdecl * NET_AdrToString_t)(netadr_t adr); + extern NET_AdrToString_t NET_AdrToString; + + typedef bool(__cdecl * NET_CompareAdr_t)(netadr_t a, netadr_t b); + extern NET_CompareAdr_t NET_CompareAdr; + + typedef bool(__cdecl * NET_IsLocalAddress_t)(netadr_t adr); + extern NET_IsLocalAddress_t NET_IsLocalAddress; + + typedef bool(__cdecl * NET_StringToAdr_t)(const char *s, netadr_t *a); + extern NET_StringToAdr_t NET_StringToAdr; + + typedef void(__cdecl* NET_OutOfBandPrint_t)(netsrc_t sock, netadr_t adr, const char *data); + extern NET_OutOfBandPrint_t NET_OutOfBandPrint; + + typedef void(__cdecl* NET_OutOfBandData_t)(netsrc_t sock, netadr_t adr, const char *format, int len); + extern NET_OutOfBandData_t NET_OutOfBandData; + + typedef void(__cdecl * Live_MPAcceptInvite_t)(_XSESSION_INFO *hostInfo, const int controllerIndex, bool fromGameInvite); + extern Live_MPAcceptInvite_t Live_MPAcceptInvite; + + typedef void(__cdecl * Live_ParsePlaylists_t)(const char* data); + extern Live_ParsePlaylists_t Live_ParsePlaylists; + + typedef void* (__cdecl * LoadModdableRawfile_t)(int a1, const char* filename); + extern LoadModdableRawfile_t LoadModdableRawfile; + + typedef char* (__cdecl * LocalizeString_t)(char*, char*); + extern LocalizeString_t LocalizeString; + + typedef char* (__cdecl * LocalizeMapString_t)(char*); + extern LocalizeMapString_t LocalizeMapString; + + typedef int(__cdecl * PC_ReadToken_t)(source_t*, token_t*); + extern PC_ReadToken_t PC_ReadToken; + + typedef int(__cdecl * PC_ReadTokenHandle_t)(int handle, pc_token_s *pc_token); + extern PC_ReadTokenHandle_t PC_ReadTokenHandle; + + typedef void(__cdecl * PC_SourceError_t)(int, const char*, ...); + extern PC_SourceError_t PC_SourceError; + + typedef int(__cdecl * Party_GetMaxPlayers_t)(party_s* party); + extern Party_GetMaxPlayers_t Party_GetMaxPlayers; + + typedef int(__cdecl * PartyHost_CountMembers_t)(PartyData_s* party); + extern PartyHost_CountMembers_t PartyHost_CountMembers; + + typedef netadr_t *(__cdecl * PartyHost_GetMemberAddressBySlot_t)(int unk, void *party, const int slot); + extern PartyHost_GetMemberAddressBySlot_t PartyHost_GetMemberAddressBySlot; + + typedef const char *(__cdecl * PartyHost_GetMemberName_t)(PartyData_s* party, const int clientNum); + extern PartyHost_GetMemberName_t PartyHost_GetMemberName; + + typedef Font* (__cdecl * R_RegisterFont_t)(const char* asset); + extern R_RegisterFont_t R_RegisterFont; + + typedef void(__cdecl * R_AddCmdDrawText_t)(const char *text, int maxChars, Font *font, float x, float y, float xScale, float yScale, float rotation, const float *color, int style); + extern R_AddCmdDrawText_t R_AddCmdDrawText; + + typedef void(_cdecl * R_AddCmdDrawStretchPic_t)(float x, float y, float w, float h, float xScale, float yScale, float xay, float yay, const float *color, Game::Material* material); + extern R_AddCmdDrawStretchPic_t R_AddCmdDrawStretchPic; + + typedef void(__cdecl * R_LoadGraphicsAssets_t)(); + extern R_LoadGraphicsAssets_t R_LoadGraphicsAssets; + + typedef int(__cdecl * R_TextWidth_t)(const char* text, int maxlength, Font* font); + extern R_TextWidth_t R_TextWidth; + + typedef int(__cdecl * R_TextHeight_t)(Font* font); + extern R_TextHeight_t R_TextHeight; + + typedef void(__cdecl * Scr_ShutdownAllocNode_t)(); + extern Scr_ShutdownAllocNode_t Scr_ShutdownAllocNode; + + typedef int(__cdecl * Scr_LoadGameType_t)(); + extern Scr_LoadGameType_t Scr_LoadGameType; + + typedef int(__cdecl * Scr_LoadScript_t)(const char*); + extern Scr_LoadScript_t Scr_LoadScript; + + typedef int(__cdecl * Scr_GetFunctionHandle_t)(const char*, const char*); + extern Scr_GetFunctionHandle_t Scr_GetFunctionHandle; + + typedef int(__cdecl * Scr_ExecThread_t)(int, int); + extern Scr_ExecThread_t Scr_ExecThread; + + typedef int(__cdecl * Scr_FreeThread_t)(int); + extern Scr_FreeThread_t Scr_FreeThread; + + typedef script_t* (__cdecl * Script_Alloc_t)(int length); + extern Script_Alloc_t Script_Alloc; + + typedef void(__cdecl * Script_SetupTokens_t)(script_t* script, void* tokens); + extern Script_SetupTokens_t Script_SetupTokens; + + typedef int(__cdecl * Script_CleanString_t)(char* buffer); + extern Script_CleanString_t Script_CleanString; + + typedef char* (__cdecl * SE_Load_t)(char* file, int Unk); + extern SE_Load_t SE_Load; + + typedef void(__cdecl * Dvar_SetStringByName_t)(const char* cvar, const char* value); + extern Dvar_SetStringByName_t Dvar_SetStringByName; + + typedef char* (__cdecl * SL_ConvertToString_t)(unsigned short stringValue); + extern SL_ConvertToString_t SL_ConvertToString; + + typedef short(__cdecl * SL_GetString_t)(const char *str, unsigned int user); + extern SL_GetString_t SL_GetString; + + typedef void(__cdecl * SND_InitDriver_t)(); + extern SND_InitDriver_t SND_InitDriver; + + typedef void(__cdecl * SockadrToNetadr_t)(sockaddr *s, netadr_t *a); + extern SockadrToNetadr_t SockadrToNetadr; + + typedef void(__cdecl * Steam_JoinLobby_t)(SteamID, char); + extern Steam_JoinLobby_t Steam_JoinLobby; + + typedef int(__cdecl* SV_GameClientNum_Score_t)(int clientID); + extern SV_GameClientNum_Score_t SV_GameClientNum_Score; + + typedef void(__cdecl * SV_GameSendServerCommand_t)(int clientNum, /*svscmd_type*/int type, const char* text); + extern SV_GameSendServerCommand_t SV_GameSendServerCommand; + + typedef FS_FreeFileList_t Sys_FreeFileList_t; + extern Sys_FreeFileList_t Sys_FreeFileList; + + typedef bool(__cdecl * Sys_IsMainThread_t)(); + extern Sys_IsMainThread_t Sys_IsMainThread; + + typedef char** (__cdecl * Sys_ListFiles_t)(const char *directory, const char *extension, const char *filter, int *numfiles, int wantsubs); + extern Sys_ListFiles_t Sys_ListFiles; + + typedef int(__cdecl * Sys_Milliseconds_t)(); + extern Sys_Milliseconds_t Sys_Milliseconds; + + typedef bool(__cdecl * Sys_SendPacket_t)(netsrc_t sock, size_t len, const char *format, netadr_t adr); + extern Sys_SendPacket_t Sys_SendPacket; + + typedef void(__cdecl * Sys_ShowConsole_t)(); + extern Sys_ShowConsole_t Sys_ShowConsole; + + typedef void(__cdecl * UI_AddMenuList_t)(UiContext *dc, MenuList *menuList, int close); + extern UI_AddMenuList_t UI_AddMenuList; + + typedef MenuList *(__cdecl * UI_LoadMenus_t)(const char *menuFile, int imageTrack); + extern UI_LoadMenus_t UI_LoadMenus; + + typedef void(__cdecl * UI_DrawHandlePic_t)(/*ScreenPlacement*/void *scrPlace, float x, float y, float w, float h, int horzAlign, int vertAlign, const float *color, Material *material); + extern UI_DrawHandlePic_t UI_DrawHandlePic; + + typedef void* (__cdecl * UI_GetContext_t)(void*); + extern UI_GetContext_t UI_GetContext; + + typedef int(__cdecl * UI_TextWidth_t)(const char *text, int maxChars, Font *font, float scale); + extern UI_TextWidth_t UI_TextWidth; + + typedef void(__cdecl * UI_DrawText_t)(void* scrPlace, const char *text, int maxChars, Font *font, float x, float y, int horzAlign, int vertAlign, float scale, const float *color, int style); + extern UI_DrawText_t UI_DrawText; + + typedef const char * (__cdecl * Win_GetLanguage_t)(); + extern Win_GetLanguage_t Win_GetLanguage; + + extern XAssetHeader* DB_XAssetPool; + extern unsigned int* g_poolSize; + + extern DWORD* cmd_id; + extern DWORD* cmd_argc; + extern char*** cmd_argv; + + extern DWORD* cmd_id_sv; + extern DWORD* cmd_argc_sv; + extern char*** cmd_argv_sv; + + extern int* svs_numclients; + extern client_t* svs_clients; + + extern source_t **sourceFiles; + extern keywordHash_t **menuParseKeywordHash; + + extern UiContext *uiContext; + + extern int* arenaCount; + extern mapArena_t* arenas; + + extern int* gameTypeCount; + extern gameTypeName_t* gameTypes; + + extern XBlock** g_streamBlocks; + + extern bool* g_lobbyCreateInProgress; + extern party_t** partyIngame; + extern PartyData_s** partyData; + + extern int* numIP; + extern netIP_t* localIP; + + extern int* demoFile; + extern int* demoPlaying; + extern int* demoRecording; + extern int* serverMessageSequence; + + extern gentity_t* g_entities; + + extern netadr_t* connectedHost; + extern SOCKET* ip_socket; + + extern SafeArea* safeArea; + + extern SpawnVar* spawnVars; + extern MapEnts** marMapEntsPtr; + + XAssetHeader ReallocateAssetPool(XAssetType type, unsigned int newSize); + void Menu_FreeItemMemory(Game::itemDef_t* item); + const char* TableLookup(StringTable* stringtable, int row, int column); + const char* UI_LocalizeMapName(const char* mapName); + const char* UI_LocalizeGameType(const char* gameType); + float UI_GetScoreboardLeft(void*); + + const char *DB_GetXAssetName(XAsset *asset); + XAssetType DB_GetXAssetNameType(const char* name); + bool DB_IsZoneLoaded(const char* zone); + + void FS_AddLocalizedGameDirectory(const char *path, const char *dir); + + void MessageBox(std::string message, std::string title); + + unsigned int R_HashString(const char* string); + + void SV_KickClient(client_t* client, const char* reason); + void SV_KickClientError(client_t* client, std::string reason); +} diff --git a/src/STDInclude.hpp b/src/STDInclude.hpp index 803ecc3f..aa8f515f 100644 --- a/src/STDInclude.hpp +++ b/src/STDInclude.hpp @@ -1,143 +1,157 @@ -#pragma once - -// Version number -#include - -#ifndef RESOURCE_DATA - -// Disable irrelevant warnings -#pragma warning(disable: 4100) // Unreferenced parameter (steam has to have them and other stubs as well, due to their calling convention) - -#define _CRT_SECURE_NO_WARNINGS -#define WIN32_LEAN_AND_MEAN - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -// Submodules -// Ignore the warnings, it's no our code! -#pragma warning(push) -#pragma warning(disable: 4005) -#pragma warning(disable: 4389) -#pragma warning(disable: 6001) -#pragma warning(disable: 6011) -#pragma warning(disable: 6031) -#pragma warning(disable: 6255) -#pragma warning(disable: 6258) -#pragma warning(disable: 6386) -#pragma warning(disable: 6387) - -#define ZLIB_CONST - -#define USE_LTM -#define LTM_DESC -#define LTC_NO_FAST -#define LTC_NO_PROTOTYPES -#define LTC_NO_RSA_BLINDING - -#include -#include -#include -#include -#include -#include - -// Protobuf -#include "proto/network.pb.h" -#include "proto/party.pb.h" -#include "proto/auth.pb.h" -#include "proto/node.pb.h" -#include "proto/rcon.pb.h" - -#pragma warning(pop) - -#include "Utils\CSV.hpp" -#include "Utils\Utils.hpp" -#include "Utils\WebIO.hpp" -#include "Utils\Memory.hpp" -#include "Utils\Hooking.hpp" -#include "Utils\Compression.hpp" -#include "Utils\Cryptography.hpp" - -#include "Steam\Steam.hpp" - -#include "Game\Structs.hpp" -#include "Game\Functions.hpp" - -#include "Utils\Stream.hpp" - -#include "Components\Loader.hpp" - -// Libraries -#pragma comment(lib, "Winmm.lib") -#pragma comment(lib, "Crypt32.lib") -#pragma comment(lib, "Ws2_32.lib") -#pragma comment(lib, "d3d9.lib") -#pragma comment(lib, "Wininet.lib") -#pragma comment(lib, "shlwapi.lib") -#pragma comment(lib, "Urlmon.lib") - -// Enable additional literals -using namespace std::literals; - -#endif - -// Revision number -#define STRINGIZE_(x) #x -#define STRINGIZE(x) STRINGIZE_(x) - -#define BASEGAME "iw4x" -#define CLIENT_CONFIG "iw4x_config.cfg" -#define MILESTONE "beta" - -#define REVISION_STR STRINGIZE(REVISION) -#if !REVISION_CLEAN -#define REVISION_SUFFIX "*" -#else -#define REVISION_SUFFIX "" -#endif -#define VERSION 4,2,REVISION -#define VERSION_STR "4.2." REVISION_STR - -#define Assert_Size(x, size) static_assert(sizeof(x) == size, STRINGIZE(x) " structure has an invalid size.") - -// Enable unit-test flag for release builds -//#define FORCE_UNIT_TESTS - -// Resource stuff -#ifdef APSTUDIO_INVOKED -#ifndef APSTUDIO_READONLY_SYMBOLS -// Defines below make accessing the resources from the code easier. -#define _APS_NEXT_RESOURCE_VALUE 102 -#define _APS_NEXT_COMMAND_VALUE 40001 -#define _APS_NEXT_CONTROL_VALUE 1001 -#define _APS_NEXT_SYMED_VALUE 101 -#endif -#endif +#pragma once + +// Version number +#include + +#ifndef RESOURCE_DATA + +// Disable irrelevant warnings +#pragma warning(disable: 4100) // Unreferenced parameter (steam has to have them and other stubs as well, due to their calling convention) + +#define _CRT_SECURE_NO_WARNINGS +#define WIN32_LEAN_AND_MEAN + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Submodules +// Ignore the warnings, it's no our code! +#pragma warning(push) +#pragma warning(disable: 4005) +#pragma warning(disable: 4389) +#pragma warning(disable: 4702) +#pragma warning(disable: 6001) +#pragma warning(disable: 6011) +#pragma warning(disable: 6031) +#pragma warning(disable: 6255) +#pragma warning(disable: 6258) +#pragma warning(disable: 6386) +#pragma warning(disable: 6387) + +#define ZLIB_CONST + +#define USE_LTM +#define LTM_DESC +#define LTC_NO_FAST +#define LTC_NO_PROTOTYPES +#define LTC_NO_RSA_BLINDING + +#include +#include +#include +#include +#include +#include + +#ifdef max +#undef max +#endif + +#ifdef min +#undef min +#endif + +#include + +// Protobuf +#include "proto/network.pb.h" +#include "proto/party.pb.h" +#include "proto/auth.pb.h" +#include "proto/node.pb.h" +#include "proto/rcon.pb.h" + +#pragma warning(pop) + +#include "Utils\IO.hpp" +#include "Utils\CSV.hpp" +#include "Utils\Utils.hpp" +#include "Utils\WebIO.hpp" +#include "Utils\Memory.hpp" +#include "Utils\String.hpp" +#include "Utils\Hooking.hpp" +#include "Utils\InfoString.hpp" +#include "Utils\Compression.hpp" +#include "Utils\Cryptography.hpp" + +#include "Steam\Steam.hpp" + +#include "Game\Structs.hpp" +#include "Game\Functions.hpp" + +#include "Utils\Stream.hpp" + +#include "Components\Loader.hpp" + +// Libraries +#pragma comment(lib, "Winmm.lib") +#pragma comment(lib, "Crypt32.lib") +#pragma comment(lib, "Ws2_32.lib") +#pragma comment(lib, "d3d9.lib") +#pragma comment(lib, "Wininet.lib") +#pragma comment(lib, "shlwapi.lib") +#pragma comment(lib, "Urlmon.lib") + +// Enable additional literals +using namespace std::literals; + +#endif + +// Revision number +#define STRINGIZE_(x) #x +#define STRINGIZE(x) STRINGIZE_(x) + +#define BASEGAME "iw4x" +#define CLIENT_CONFIG "iw4x_config.cfg" +#define MILESTONE "beta" + +#define REVISION_STR STRINGIZE(REVISION) +#if !REVISION_CLEAN +#define REVISION_SUFFIX "*" +#else +#define REVISION_SUFFIX "" +#endif +#define VERSION 4,2,REVISION +#define VERSION_STR "4.2." REVISION_STR + +#define Assert_Size(x, size) static_assert(sizeof(x) == size, STRINGIZE(x) " structure has an invalid size.") + +// Enable unit-test flag for release builds +//#define FORCE_UNIT_TESTS + +// Resource stuff +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +// Defines below make accessing the resources from the code easier. +#define _APS_NEXT_RESOURCE_VALUE 102 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/src/Steam/Steam.cpp b/src/Steam/Steam.cpp index 626a48d9..55aed87b 100644 --- a/src/Steam/Steam.cpp +++ b/src/Steam/Steam.cpp @@ -1,185 +1,185 @@ -#include "STDInclude.hpp" - -namespace Steam -{ - HMODULE Overlay = 0; - - uint64_t Callbacks::CallID = 0; - std::map Callbacks::Calls; - std::map Callbacks::ResultHandlers; - std::vector Callbacks::Results; - std::vector Callbacks::CallbackList; - - uint64_t Callbacks::RegisterCall() - { - Callbacks::Calls[++Callbacks::CallID] = false; - return Callbacks::CallID; - } - - void Callbacks::RegisterCallback(Callbacks::Base* handler, int callback) - { - handler->SetICallback(callback); - Callbacks::CallbackList.push_back(handler); - } - - void Callbacks::RegisterCallResult(uint64_t call, Callbacks::Base* result) - { - Callbacks::ResultHandlers[call] = result; - } - - void Callbacks::ReturnCall(void* data, int size, int type, uint64_t call) - { - Callbacks::Result result; - - Callbacks::Calls[call] = true; - - result.call = call; - result.data = data; - result.size = size; - result.type = type; - - Callbacks::Results.push_back(result); - } - - void Callbacks::RunCallbacks() - { - for (auto result : Callbacks::Results) - { - if (Callbacks::ResultHandlers.find(result.call) != Callbacks::ResultHandlers.end()) - { - Callbacks::ResultHandlers[result.call]->Run(result.data, false, result.call); - } - - for (auto callback : Callbacks::CallbackList) - { - if (callback && callback->GetICallback() == result.type) - { - callback->Run(result.data, false, 0); - } - } - - if (result.data) - { - ::Utils::Memory::Free(result.data); - } - } - - Callbacks::Results.clear(); - } - - extern "C" - { - bool SteamAPI_Init() - { - Overlay = GetModuleHandleA("gameoverlayrenderer.dll"); - - if (!Overlay) - { - HKEY hRegKey; - if (RegOpenKeyExA(HKEY_LOCAL_MACHINE, "Software\\Valve\\Steam", 0, KEY_QUERY_VALUE, &hRegKey) == ERROR_SUCCESS) - { - char steamPath[MAX_PATH] = { 0 }; - DWORD dwLength = sizeof(steamPath); - RegQueryValueExA(hRegKey, "InstallPath", NULL, NULL, reinterpret_cast(steamPath), &dwLength); - RegCloseKey(hRegKey); - - SetDllDirectoryA(steamPath); - - Overlay = LoadLibraryA(::Utils::VA("%s\\%s", steamPath, "gameoverlayrenderer.dll")); - } - } - - return true; - } - - void SteamAPI_RegisterCallResult(Callbacks::Base* result, uint64_t call) - { - Callbacks::RegisterCallResult(call, result); - } - - void SteamAPI_RegisterCallback(Callbacks::Base* handler, int callback) - { - Callbacks::RegisterCallback(handler, callback); - } - - void SteamAPI_RunCallbacks() - { - Callbacks::RunCallbacks(); - } - - void SteamAPI_Shutdown() - { - } - - void SteamAPI_UnregisterCallResult() - { - } - - void SteamAPI_UnregisterCallback() - { - } - - - bool SteamGameServer_Init() - { - return true; - } - - void SteamGameServer_RunCallbacks() - { - } - - void SteamGameServer_Shutdown() - { - } - - - Steam::Friends* SteamFriends() - { - static Steam::Friends iFriends; - return &iFriends; - } - - Steam::Matchmaking* SteamMatchmaking() - { - static Steam::Matchmaking iMatchmaking; - return &iMatchmaking; - } - - Steam::GameServer* SteamGameServer() - { - static Steam::GameServer iGameServer; - return &iGameServer; - } - - Steam::MasterServerUpdater* SteamMasterServerUpdater() - { - static Steam::MasterServerUpdater iMasterServerUpdater; - return &iMasterServerUpdater; - } - - Steam::Networking* SteamNetworking() - { - static Steam::Networking iNetworking; - return &iNetworking; - } - - Steam::RemoteStorage* SteamRemoteStorage() - { - static Steam::RemoteStorage iRemoteStorage; - return &iRemoteStorage; - } - - Steam::User* SteamUser() - { - static Steam::User iUser; - return &iUser; - } - - Steam::Utils* SteamUtils() - { - static Steam::Utils iUtils; - return &iUtils; - } - } -} +#include "STDInclude.hpp" + +namespace Steam +{ + HMODULE Overlay = 0; + + uint64_t Callbacks::CallID = 0; + std::map Callbacks::Calls; + std::map Callbacks::ResultHandlers; + std::vector Callbacks::Results; + std::vector Callbacks::CallbackList; + + uint64_t Callbacks::RegisterCall() + { + Callbacks::Calls[++Callbacks::CallID] = false; + return Callbacks::CallID; + } + + void Callbacks::RegisterCallback(Callbacks::Base* handler, int callback) + { + handler->SetICallback(callback); + Callbacks::CallbackList.push_back(handler); + } + + void Callbacks::RegisterCallResult(uint64_t call, Callbacks::Base* result) + { + Callbacks::ResultHandlers[call] = result; + } + + void Callbacks::ReturnCall(void* data, int size, int type, uint64_t call) + { + Callbacks::Result result; + + Callbacks::Calls[call] = true; + + result.call = call; + result.data = data; + result.size = size; + result.type = type; + + Callbacks::Results.push_back(result); + } + + void Callbacks::RunCallbacks() + { + for (auto result : Callbacks::Results) + { + if (Callbacks::ResultHandlers.find(result.call) != Callbacks::ResultHandlers.end()) + { + Callbacks::ResultHandlers[result.call]->Run(result.data, false, result.call); + } + + for (auto callback : Callbacks::CallbackList) + { + if (callback && callback->GetICallback() == result.type) + { + callback->Run(result.data, false, 0); + } + } + + if (result.data) + { + ::Utils::Memory::Free(result.data); + } + } + + Callbacks::Results.clear(); + } + + extern "C" + { + bool SteamAPI_Init() + { + Overlay = GetModuleHandleA("gameoverlayrenderer.dll"); + + if (!Overlay) + { + HKEY hRegKey; + if (RegOpenKeyExA(HKEY_LOCAL_MACHINE, "Software\\Valve\\Steam", 0, KEY_QUERY_VALUE, &hRegKey) == ERROR_SUCCESS) + { + char steamPath[MAX_PATH] = { 0 }; + DWORD dwLength = sizeof(steamPath); + RegQueryValueExA(hRegKey, "InstallPath", NULL, NULL, reinterpret_cast(steamPath), &dwLength); + RegCloseKey(hRegKey); + + SetDllDirectoryA(steamPath); + + Overlay = LoadLibraryA(::Utils::String::VA("%s\\%s", steamPath, "gameoverlayrenderer.dll")); + } + } + + return true; + } + + void SteamAPI_RegisterCallResult(Callbacks::Base* result, uint64_t call) + { + Callbacks::RegisterCallResult(call, result); + } + + void SteamAPI_RegisterCallback(Callbacks::Base* handler, int callback) + { + Callbacks::RegisterCallback(handler, callback); + } + + void SteamAPI_RunCallbacks() + { + Callbacks::RunCallbacks(); + } + + void SteamAPI_Shutdown() + { + } + + void SteamAPI_UnregisterCallResult() + { + } + + void SteamAPI_UnregisterCallback() + { + } + + + bool SteamGameServer_Init() + { + return true; + } + + void SteamGameServer_RunCallbacks() + { + } + + void SteamGameServer_Shutdown() + { + } + + + Steam::Friends* SteamFriends() + { + static Steam::Friends iFriends; + return &iFriends; + } + + Steam::Matchmaking* SteamMatchmaking() + { + static Steam::Matchmaking iMatchmaking; + return &iMatchmaking; + } + + Steam::GameServer* SteamGameServer() + { + static Steam::GameServer iGameServer; + return &iGameServer; + } + + Steam::MasterServerUpdater* SteamMasterServerUpdater() + { + static Steam::MasterServerUpdater iMasterServerUpdater; + return &iMasterServerUpdater; + } + + Steam::Networking* SteamNetworking() + { + static Steam::Networking iNetworking; + return &iNetworking; + } + + Steam::RemoteStorage* SteamRemoteStorage() + { + static Steam::RemoteStorage iRemoteStorage; + return &iRemoteStorage; + } + + Steam::User* SteamUser() + { + static Steam::User iUser; + return &iUser; + } + + Steam::Utils* SteamUtils() + { + static Steam::Utils iUtils; + return &iUtils; + } + } +} diff --git a/src/Utils/CSV.cpp b/src/Utils/CSV.cpp index 628380f2..3e9df921 100644 --- a/src/Utils/CSV.cpp +++ b/src/Utils/CSV.cpp @@ -1,144 +1,144 @@ -#include "STDInclude.hpp" - -namespace Utils -{ - CSV::CSV(std::string file, bool isFile, bool allowComments) - { - CSV::Parse(file, isFile, allowComments); - } - - CSV::~CSV() - { - for (auto row : CSV::DataMap) - { - for (auto entry : row) - { - entry.clear(); - } - - row.clear(); - } - - CSV::DataMap.clear(); - } - - int CSV::GetRows() - { - return CSV::DataMap.size(); - } - - int CSV::GetColumns(size_t row) - { - if (CSV::DataMap.size() > row) - { - return CSV::DataMap[row].size(); - } - - return 0; - } - - int CSV::GetColumns() - { - int count = 0; - - for (int i = 0; i < CSV::GetRows(); ++i) - { - count = std::max(CSV::GetColumns(i), count); - } - - return count; - } - - std::string CSV::GetElementAt(size_t row, size_t column) - { - if (CSV::DataMap.size() > row) - { - auto _row = CSV::DataMap[row]; - - if (_row.size() > column) - { - return _row[column]; - } - } - - return ""; - } - - void CSV::Parse(std::string file, bool isFile, bool allowComments) - { - std::string buffer; - - if (isFile) - { - if (!Utils::FileExists(file)) return; - buffer = Utils::ReadFile(file); - } - else - { - buffer = file; - } - - if (!buffer.empty()) - { - auto rows = Utils::Explode(buffer, '\n'); - - for (auto row : rows) - { - CSV::ParseRow(row, allowComments); - } - } - } - - void CSV::ParseRow(std::string row, bool allowComments) - { - bool isString = false; - std::string element; - std::vector _row; - char tempStr = 0; - - for (unsigned int i = 0; i < row.size(); ++i) - { - if (row[i] == ',' && !isString) // FLush entry - { - _row.push_back(element); - element.clear(); - continue; - } - else if (row[i] == '"') // Start/Terminate string - { - isString = !isString; - continue; - } - else if (i < (row.size() - 1) && row[i] == '\\' &&row[i + 1] == '"' && isString) // Handle quotes in strings as \" - { - tempStr = '"'; - ++i; - } - else if (!isString && (row[i] == '\n' || row[i] == '\x0D' || row[i] == '\x0A' || row[i] == '\t')) - { - //++i; - continue; - } - else if (!isString && row[i] == '#' && allowComments) // Skip comments. I know CSVs usually don't have comments, but in this case it's useful - { - return; - } - else - { - tempStr = row[i]; - } - - element.append(&tempStr, 1); - } - - // Push last element - _row.push_back(element); - - if (_row.size() == 0 || (_row.size() == 1 && !_row[0].size())) // Skip empty rows - { - return; - } - - DataMap.push_back(_row); - } -} +#include "STDInclude.hpp" + +namespace Utils +{ + CSV::CSV(std::string file, bool isFile, bool allowComments) + { + CSV::Parse(file, isFile, allowComments); + } + + CSV::~CSV() + { + for (auto row : CSV::DataMap) + { + for (auto entry : row) + { + entry.clear(); + } + + row.clear(); + } + + CSV::DataMap.clear(); + } + + int CSV::GetRows() + { + return CSV::DataMap.size(); + } + + int CSV::GetColumns(size_t row) + { + if (CSV::DataMap.size() > row) + { + return CSV::DataMap[row].size(); + } + + return 0; + } + + int CSV::GetColumns() + { + int count = 0; + + for (int i = 0; i < CSV::GetRows(); ++i) + { + count = std::max(CSV::GetColumns(i), count); + } + + return count; + } + + std::string CSV::GetElementAt(size_t row, size_t column) + { + if (CSV::DataMap.size() > row) + { + auto _row = CSV::DataMap[row]; + + if (_row.size() > column) + { + return _row[column]; + } + } + + return ""; + } + + void CSV::Parse(std::string file, bool isFile, bool allowComments) + { + std::string buffer; + + if (isFile) + { + if (!Utils::IO::FileExists(file)) return; + buffer = Utils::IO::ReadFile(file); + } + else + { + buffer = file; + } + + if (!buffer.empty()) + { + auto rows = Utils::String::Explode(buffer, '\n'); + + for (auto row : rows) + { + CSV::ParseRow(row, allowComments); + } + } + } + + void CSV::ParseRow(std::string row, bool allowComments) + { + bool isString = false; + std::string element; + std::vector _row; + char tempStr = 0; + + for (unsigned int i = 0; i < row.size(); ++i) + { + if (row[i] == ',' && !isString) // FLush entry + { + _row.push_back(element); + element.clear(); + continue; + } + else if (row[i] == '"') // Start/Terminate string + { + isString = !isString; + continue; + } + else if (i < (row.size() - 1) && row[i] == '\\' &&row[i + 1] == '"' && isString) // Handle quotes in strings as \" + { + tempStr = '"'; + ++i; + } + else if (!isString && (row[i] == '\n' || row[i] == '\x0D' || row[i] == '\x0A' || row[i] == '\t')) + { + //++i; + continue; + } + else if (!isString && row[i] == '#' && allowComments) // Skip comments. I know CSVs usually don't have comments, but in this case it's useful + { + return; + } + else + { + tempStr = row[i]; + } + + element.append(&tempStr, 1); + } + + // Push last element + _row.push_back(element); + + if (_row.size() == 0 || (_row.size() == 1 && !_row[0].size())) // Skip empty rows + { + return; + } + + DataMap.push_back(_row); + } +} diff --git a/src/Utils/Compression.cpp b/src/Utils/Compression.cpp index dc8a8bb5..56be5ed3 100644 --- a/src/Utils/Compression.cpp +++ b/src/Utils/Compression.cpp @@ -1,72 +1,72 @@ -#include "STDInclude.hpp" - -namespace Utils -{ - namespace Compression - { - std::string ZLib::Compress(std::string data) - { - unsigned long length = (data.size() * 2); - char* buffer = Utils::Memory::AllocateArray(length); - - if (compress2(reinterpret_cast(buffer), &length, reinterpret_cast(const_cast(data.data())), data.size(), Z_BEST_COMPRESSION) != Z_OK) - { - Utils::Memory::Free(buffer); - return ""; - } - - data.clear(); - data.append(buffer, length); - - Utils::Memory::Free(buffer); - - return data; - } - - std::string ZLib::Decompress(std::string data) - { - z_stream stream; - ZeroMemory(&stream, sizeof(stream)); - std::string buffer; - - if (inflateInit(&stream) != Z_OK) - { - return ""; - } - - int ret = 0; - uint8_t* dest = Utils::Memory::AllocateArray(CHUNK); - const char* dataPtr = data.data(); - - do - { - stream.avail_in = min(CHUNK, data.size() - (dataPtr - data.data())); - stream.next_in = reinterpret_cast(dataPtr); - - do - { - stream.avail_out = CHUNK; - stream.next_out = dest; - - ret = inflate(&stream, Z_NO_FLUSH); - if (ret == Z_STREAM_ERROR) - { - inflateEnd(&stream); - Utils::Memory::Free(dest); - return ""; - } - - buffer.append(reinterpret_cast(dest), CHUNK - stream.avail_out); - - } while (stream.avail_out == 0); - - } while (ret != Z_STREAM_END); - - inflateEnd(&stream); - - Utils::Memory::Free(dest); - - return buffer; - } - }; -} +#include "STDInclude.hpp" + +namespace Utils +{ + namespace Compression + { + std::string ZLib::Compress(std::string data) + { + unsigned long length = (data.size() * 2); + char* buffer = Utils::Memory::AllocateArray(length); + + if (compress2(reinterpret_cast(buffer), &length, reinterpret_cast(const_cast(data.data())), data.size(), Z_BEST_COMPRESSION) != Z_OK) + { + Utils::Memory::Free(buffer); + return ""; + } + + data.clear(); + data.append(buffer, length); + + Utils::Memory::Free(buffer); + + return data; + } + + std::string ZLib::Decompress(std::string data) + { + z_stream stream; + ZeroMemory(&stream, sizeof(stream)); + std::string buffer; + + if (inflateInit(&stream) != Z_OK) + { + return ""; + } + + int ret = 0; + uint8_t* dest = Utils::Memory::AllocateArray(CHUNK); + const char* dataPtr = data.data(); + + do + { + stream.avail_in = std::min(static_cast(CHUNK), data.size() - (dataPtr - data.data())); + stream.next_in = reinterpret_cast(dataPtr); + + do + { + stream.avail_out = CHUNK; + stream.next_out = dest; + + ret = inflate(&stream, Z_NO_FLUSH); + if (ret == Z_STREAM_ERROR) + { + inflateEnd(&stream); + Utils::Memory::Free(dest); + return ""; + } + + buffer.append(reinterpret_cast(dest), CHUNK - stream.avail_out); + + } while (stream.avail_out == 0); + + } while (ret != Z_STREAM_END); + + inflateEnd(&stream); + + Utils::Memory::Free(dest); + + return buffer; + } + }; +} diff --git a/src/Utils/Cryptography.cpp b/src/Utils/Cryptography.cpp index d256d532..68856132 100644 --- a/src/Utils/Cryptography.cpp +++ b/src/Utils/Cryptography.cpp @@ -1,261 +1,261 @@ -#include "STDInclude.hpp" - -/// http://www.opensource.apple.com/source/CommonCrypto/CommonCrypto-55010/Source/libtomcrypt/doc/libTomCryptDoc.pdf - -namespace Utils -{ - namespace Cryptography - { - void Initialize() - { - TDES::Initialize(); - Rand::Initialize(); - } - -#pragma region Rand - - prng_state Rand::State; - - uint32_t Rand::GenerateInt() - { - uint32_t number = 0; - fortuna_read(reinterpret_cast(&number), sizeof(number), &Rand::State); - return number; - } - - void Rand::Initialize() - { - ltc_mp = ltm_desc; - register_prng(&fortuna_desc); - rng_make_prng(128, find_prng("fortuna"), &Rand::State, NULL); - } - -#pragma endregion - -#pragma region ECC - - ECC::Key ECC::GenerateKey(int bits) - { - ECC::Key key; - - register_prng(&sprng_desc); - - ltc_mp = ltm_desc; - - ecc_make_key(NULL, find_prng("sprng"), bits / 8, key.GetKeyPtr()); - - return key; - } - - std::string ECC::SignMessage(Key key, std::string message) - { - if (!key.IsValid()) return ""; - - uint8_t buffer[512]; - DWORD length = sizeof(buffer); - - register_prng(&sprng_desc); - - ltc_mp = ltm_desc; - - ecc_sign_hash(reinterpret_cast(message.data()), message.size(), buffer, &length, NULL, find_prng("sprng"), key.GetKeyPtr()); - - return std::string(reinterpret_cast(buffer), length); - } - - bool ECC::VerifyMessage(Key key, std::string message, std::string signature) - { - if (!key.IsValid()) return false; - - ltc_mp = ltm_desc; - - int result = 0; - return (ecc_verify_hash(reinterpret_cast(signature.data()), signature.size(), reinterpret_cast(message.data()), message.size(), &result, key.GetKeyPtr()) == CRYPT_OK && result != 0); - } - -#pragma endregion - -#pragma region RSA - - RSA::Key RSA::GenerateKey(int bits) - { - RSA::Key key; - - register_prng(&sprng_desc); - register_hash(&sha1_desc); - - ltc_mp = ltm_desc; - - rsa_make_key(NULL, find_prng("sprng"), bits / 8, 65537, key.GetKeyPtr()); - - return key; - } - - std::string RSA::SignMessage(RSA::Key key, std::string message) - { - if (!key.IsValid()) return ""; - - uint8_t buffer[512]; - DWORD length = sizeof(buffer); - - register_prng(&sprng_desc); - register_hash(&sha1_desc); - - ltc_mp = ltm_desc; - - rsa_sign_hash(reinterpret_cast(message.data()), message.size(), buffer, &length, NULL, find_prng("sprng"), find_hash("sha1"), 0, key.GetKeyPtr()); - - return std::string(reinterpret_cast(buffer), length); - } - - bool RSA::VerifyMessage(Key key, std::string message, std::string signature) - { - if (!key.IsValid()) return false; - - register_hash(&sha1_desc); - - ltc_mp = ltm_desc; - - int result = 0; - return (rsa_verify_hash(reinterpret_cast(signature.data()), signature.size(), reinterpret_cast(message.data()), message.size(), find_hash("sha1"), 0, &result, key.GetKeyPtr()) == CRYPT_OK && result != 0); - } - -#pragma endregion - -#pragma region TDES - - void TDES::Initialize() - { - register_cipher(&des3_desc); - } - - std::string TDES::Encrypt(std::string text, std::string iv, std::string key) - { - std::string encData; - encData.resize(text.size()); - - symmetric_CBC cbc; - int des3 = find_cipher("3des"); - - cbc_start(des3, reinterpret_cast(iv.data()), reinterpret_cast(key.data()), key.size(), 0, &cbc); - cbc_encrypt(reinterpret_cast(text.data()), reinterpret_cast(const_cast(encData.data())), text.size(), &cbc); - cbc_done(&cbc); - - return encData; - } - - std::string TDES::Decrpyt(std::string data, std::string iv, std::string key) - { - std::string decData; - decData.resize(data.size()); - - symmetric_CBC cbc; - int des3 = find_cipher("3des"); - - cbc_start(des3, reinterpret_cast(iv.data()), reinterpret_cast(key.data()), key.size(), 0, &cbc); - cbc_decrypt(reinterpret_cast(data.data()), reinterpret_cast(const_cast(decData.data())), data.size(), &cbc); - cbc_done(&cbc); - - return decData; - } - -#pragma endregion - -#pragma region Tiger - - std::string Tiger::Compute(std::string data, bool hex) - { - return Tiger::Compute(reinterpret_cast(data.data()), data.size(), hex); - } - - std::string Tiger::Compute(const uint8_t* data, size_t length, bool hex) - { - uint8_t buffer[24] = { 0 }; - - hash_state state; - tiger_init(&state); - tiger_process(&state, data, length); - tiger_done(&state, buffer); - - std::string hash(reinterpret_cast(buffer), sizeof(buffer)); - if (!hex) return hash; - - return Utils::DumpHex(hash, ""); - } - -#pragma endregion - -#pragma region SHA256 - - std::string SHA256::Compute(std::string data, bool hex) - { - return SHA256::Compute(reinterpret_cast(data.data()), data.size(), hex); - } - - std::string SHA256::Compute(const uint8_t* data, size_t length, bool hex) - { - uint8_t buffer[32] = { 0 }; - - hash_state state; - sha256_init(&state); - sha256_process(&state, data, length); - sha256_done(&state, buffer); - - std::string hash(reinterpret_cast(buffer), sizeof(buffer)); - if (!hex) return hash; - - return Utils::DumpHex(hash, ""); - } - -#pragma endregion - -#pragma region SHA512 - - std::string SHA512::Compute(std::string data, bool hex) - { - return SHA512::Compute(reinterpret_cast(data.data()), data.size(), hex); - } - - std::string SHA512::Compute(const uint8_t* data, size_t length, bool hex) - { - uint8_t buffer[64] = { 0 }; - - hash_state state; - sha512_init(&state); - sha512_process(&state, data, length); - sha512_done(&state, buffer); - - std::string hash(reinterpret_cast(buffer), sizeof(buffer)); - if (!hex) return hash; - - return Utils::DumpHex(hash, ""); - } - -#pragma endregion - -#pragma region JenkinsOneAtATime - - unsigned int JenkinsOneAtATime::Compute(std::string data) - { - return JenkinsOneAtATime::Compute(data.data(), data.size()); - } - - unsigned int JenkinsOneAtATime::Compute(const char *key, size_t len) - { - unsigned int hash, i; - for (hash = i = 0; i < len; ++i) - { - hash += key[i]; - hash += (hash << 10); - hash ^= (hash >> 6); - } - hash += (hash << 3); - hash ^= (hash >> 11); - hash += (hash << 15); - return hash; - } - -#pragma endregion - - } -} +#include "STDInclude.hpp" + +/// http://www.opensource.apple.com/source/CommonCrypto/CommonCrypto-55010/Source/libtomcrypt/doc/libTomCryptDoc.pdf + +namespace Utils +{ + namespace Cryptography + { + void Initialize() + { + TDES::Initialize(); + Rand::Initialize(); + } + +#pragma region Rand + + prng_state Rand::State; + + uint32_t Rand::GenerateInt() + { + uint32_t number = 0; + fortuna_read(reinterpret_cast(&number), sizeof(number), &Rand::State); + return number; + } + + void Rand::Initialize() + { + ltc_mp = ltm_desc; + register_prng(&fortuna_desc); + rng_make_prng(128, find_prng("fortuna"), &Rand::State, NULL); + } + +#pragma endregion + +#pragma region ECC + + ECC::Key ECC::GenerateKey(int bits) + { + ECC::Key key; + + register_prng(&sprng_desc); + + ltc_mp = ltm_desc; + + ecc_make_key(NULL, find_prng("sprng"), bits / 8, key.GetKeyPtr()); + + return key; + } + + std::string ECC::SignMessage(Key key, std::string message) + { + if (!key.IsValid()) return ""; + + uint8_t buffer[512]; + DWORD length = sizeof(buffer); + + register_prng(&sprng_desc); + + ltc_mp = ltm_desc; + + ecc_sign_hash(reinterpret_cast(message.data()), message.size(), buffer, &length, NULL, find_prng("sprng"), key.GetKeyPtr()); + + return std::string(reinterpret_cast(buffer), length); + } + + bool ECC::VerifyMessage(Key key, std::string message, std::string signature) + { + if (!key.IsValid()) return false; + + ltc_mp = ltm_desc; + + int result = 0; + return (ecc_verify_hash(reinterpret_cast(signature.data()), signature.size(), reinterpret_cast(message.data()), message.size(), &result, key.GetKeyPtr()) == CRYPT_OK && result != 0); + } + +#pragma endregion + +#pragma region RSA + + RSA::Key RSA::GenerateKey(int bits) + { + RSA::Key key; + + register_prng(&sprng_desc); + register_hash(&sha1_desc); + + ltc_mp = ltm_desc; + + rsa_make_key(NULL, find_prng("sprng"), bits / 8, 65537, key.GetKeyPtr()); + + return key; + } + + std::string RSA::SignMessage(RSA::Key key, std::string message) + { + if (!key.IsValid()) return ""; + + uint8_t buffer[512]; + DWORD length = sizeof(buffer); + + register_prng(&sprng_desc); + register_hash(&sha1_desc); + + ltc_mp = ltm_desc; + + rsa_sign_hash(reinterpret_cast(message.data()), message.size(), buffer, &length, NULL, find_prng("sprng"), find_hash("sha1"), 0, key.GetKeyPtr()); + + return std::string(reinterpret_cast(buffer), length); + } + + bool RSA::VerifyMessage(Key key, std::string message, std::string signature) + { + if (!key.IsValid()) return false; + + register_hash(&sha1_desc); + + ltc_mp = ltm_desc; + + int result = 0; + return (rsa_verify_hash(reinterpret_cast(signature.data()), signature.size(), reinterpret_cast(message.data()), message.size(), find_hash("sha1"), 0, &result, key.GetKeyPtr()) == CRYPT_OK && result != 0); + } + +#pragma endregion + +#pragma region TDES + + void TDES::Initialize() + { + register_cipher(&des3_desc); + } + + std::string TDES::Encrypt(std::string text, std::string iv, std::string key) + { + std::string encData; + encData.resize(text.size()); + + symmetric_CBC cbc; + int des3 = find_cipher("3des"); + + cbc_start(des3, reinterpret_cast(iv.data()), reinterpret_cast(key.data()), key.size(), 0, &cbc); + cbc_encrypt(reinterpret_cast(text.data()), reinterpret_cast(const_cast(encData.data())), text.size(), &cbc); + cbc_done(&cbc); + + return encData; + } + + std::string TDES::Decrpyt(std::string data, std::string iv, std::string key) + { + std::string decData; + decData.resize(data.size()); + + symmetric_CBC cbc; + int des3 = find_cipher("3des"); + + cbc_start(des3, reinterpret_cast(iv.data()), reinterpret_cast(key.data()), key.size(), 0, &cbc); + cbc_decrypt(reinterpret_cast(data.data()), reinterpret_cast(const_cast(decData.data())), data.size(), &cbc); + cbc_done(&cbc); + + return decData; + } + +#pragma endregion + +#pragma region Tiger + + std::string Tiger::Compute(std::string data, bool hex) + { + return Tiger::Compute(reinterpret_cast(data.data()), data.size(), hex); + } + + std::string Tiger::Compute(const uint8_t* data, size_t length, bool hex) + { + uint8_t buffer[24] = { 0 }; + + hash_state state; + tiger_init(&state); + tiger_process(&state, data, length); + tiger_done(&state, buffer); + + std::string hash(reinterpret_cast(buffer), sizeof(buffer)); + if (!hex) return hash; + + return Utils::String::DumpHex(hash, ""); + } + +#pragma endregion + +#pragma region SHA256 + + std::string SHA256::Compute(std::string data, bool hex) + { + return SHA256::Compute(reinterpret_cast(data.data()), data.size(), hex); + } + + std::string SHA256::Compute(const uint8_t* data, size_t length, bool hex) + { + uint8_t buffer[32] = { 0 }; + + hash_state state; + sha256_init(&state); + sha256_process(&state, data, length); + sha256_done(&state, buffer); + + std::string hash(reinterpret_cast(buffer), sizeof(buffer)); + if (!hex) return hash; + + return Utils::String::DumpHex(hash, ""); + } + +#pragma endregion + +#pragma region SHA512 + + std::string SHA512::Compute(std::string data, bool hex) + { + return SHA512::Compute(reinterpret_cast(data.data()), data.size(), hex); + } + + std::string SHA512::Compute(const uint8_t* data, size_t length, bool hex) + { + uint8_t buffer[64] = { 0 }; + + hash_state state; + sha512_init(&state); + sha512_process(&state, data, length); + sha512_done(&state, buffer); + + std::string hash(reinterpret_cast(buffer), sizeof(buffer)); + if (!hex) return hash; + + return Utils::String::DumpHex(hash, ""); + } + +#pragma endregion + +#pragma region JenkinsOneAtATime + + unsigned int JenkinsOneAtATime::Compute(std::string data) + { + return JenkinsOneAtATime::Compute(data.data(), data.size()); + } + + unsigned int JenkinsOneAtATime::Compute(const char *key, size_t len) + { + unsigned int hash, i; + for (hash = i = 0; i < len; ++i) + { + hash += key[i]; + hash += (hash << 10); + hash ^= (hash >> 6); + } + hash += (hash << 3); + hash ^= (hash >> 11); + hash += (hash << 15); + return hash; + } + +#pragma endregion + + } +} diff --git a/src/Utils/IO.cpp b/src/Utils/IO.cpp new file mode 100644 index 00000000..c29a39f5 --- /dev/null +++ b/src/Utils/IO.cpp @@ -0,0 +1,51 @@ +#include "STDInclude.hpp" + +namespace Utils +{ + namespace IO + { + bool FileExists(std::string file) + { + return std::ifstream(file).good(); + } + + void WriteFile(std::string file, std::string data) + { + std::ofstream stream(file, std::ios::binary); + + if (stream.is_open()) + { + stream.write(data.data(), data.size()); + stream.close(); + } + } + + std::string ReadFile(std::string file) + { + std::string buffer; + + if (FileExists(file)) + { + std::streamsize size = 0; + std::ifstream stream(file, std::ios::binary); + if (!stream.is_open()) return buffer; + + stream.seekg(0, std::ios::end); + size = stream.tellg(); + stream.seekg(0, std::ios::beg); + + if (size > -1) + { + buffer.clear(); + buffer.resize((uint32_t)size); + + stream.read(const_cast(buffer.data()), size); + } + + stream.close(); + } + + return buffer; + } + } +} diff --git a/src/Utils/IO.hpp b/src/Utils/IO.hpp new file mode 100644 index 00000000..82ef4769 --- /dev/null +++ b/src/Utils/IO.hpp @@ -0,0 +1,9 @@ +namespace Utils +{ + namespace IO + { + bool FileExists(std::string file); + void WriteFile(std::string file, std::string data); + std::string ReadFile(std::string file); + } +} diff --git a/src/Utils/InfoString.cpp b/src/Utils/InfoString.cpp new file mode 100644 index 00000000..2bc02285 --- /dev/null +++ b/src/Utils/InfoString.cpp @@ -0,0 +1,67 @@ +#include "STDInclude.hpp" + +namespace Utils +{ + // Infostring class + void InfoString::Set(std::string key, std::string value) + { + this->KeyValuePairs[key] = value; + } + + std::string InfoString::Get(std::string key) + { + if (this->KeyValuePairs.find(key) != this->KeyValuePairs.end()) + { + return this->KeyValuePairs[key]; + } + + return ""; + } + + void InfoString::Parse(std::string buffer) + { + if (buffer[0] == '\\') + { + buffer = buffer.substr(1); + } + + std::vector KeyValues = Utils::String::Explode(buffer, '\\'); + + for (unsigned int i = 0; i < (KeyValues.size() - 1); i += 2) + { + this->KeyValuePairs[KeyValues[i]] = KeyValues[i + 1]; + } + } + + std::string InfoString::Build() + { + std::string infoString; + + bool first = true; + + for (auto i = this->KeyValuePairs.begin(); i != this->KeyValuePairs.end(); ++i) + { + if (first) first = false; + else infoString.append("\\"); + + infoString.append(i->first); // Key + infoString.append("\\"); + infoString.append(i->second); // Value + } + + return infoString; + } + + void InfoString::Dump() + { + for (auto i = this->KeyValuePairs.begin(); i != this->KeyValuePairs.end(); ++i) + { + OutputDebugStringA(Utils::String::VA("%s: %s", i->first.data(), i->second.data())); + } + } + + json11::Json InfoString::to_json() + { + return this->KeyValuePairs; + } +} diff --git a/src/Utils/InfoString.hpp b/src/Utils/InfoString.hpp new file mode 100644 index 00000000..78323e92 --- /dev/null +++ b/src/Utils/InfoString.hpp @@ -0,0 +1,25 @@ +#define ARR_SIZE(x) (sizeof(x) / sizeof(x[0])) + +namespace Utils +{ + class InfoString + { + public: + InfoString() {}; + InfoString(std::string buffer) : InfoString() { this->Parse(buffer); }; + InfoString(const InfoString &obj) : KeyValuePairs(obj.KeyValuePairs) {}; + + void Set(std::string key, std::string value); + std::string Get(std::string key); + + std::string Build(); + + void Dump(); + + json11::Json to_json(); + + private: + std::map KeyValuePairs; + void Parse(std::string buffer); + }; +} diff --git a/src/Utils/Stream.cpp b/src/Utils/Stream.cpp index db7f5475..2d9bb4bf 100644 --- a/src/Utils/Stream.cpp +++ b/src/Utils/Stream.cpp @@ -1,306 +1,306 @@ -#include "STDInclude.hpp" - -namespace Utils -{ - std::string Stream::Reader::ReadString() - { - std::string str; - - while (char byte = Stream::Reader::ReadByte()) - { - str.append(&byte, 1); - } - - return str; - } - - const char* Stream::Reader::ReadCString() - { - return Stream::Reader::Allocator->DuplicateString(Stream::Reader::ReadString()); - } - - char Stream::Reader::ReadByte() - { - if ((Stream::Reader::Position + 1) <= Stream::Reader::Buffer.size()) - { - return Stream::Reader::Buffer[Stream::Reader::Position++]; - } - - return 0; - } - - void* Stream::Reader::Read(size_t size, size_t count) - { - size_t bytes = size * count; - - if ((Stream::Reader::Position + bytes) <= Stream::Reader::Buffer.size()) - { - void* buffer = Stream::Reader::Allocator->Allocate(bytes); - - std::memcpy(buffer, Stream::Reader::Buffer.data() + Stream::Reader::Position, bytes); - Stream::Reader::Position += bytes; - - return buffer; - } - - return nullptr; - } - - bool Stream::Reader::End() - { - return (Stream::Reader::Buffer.size() == Stream::Reader::Position); - } - - void Stream::Reader::Seek(unsigned int position) - { - if (Stream::Reader::Buffer.size() >= position) - { - Stream::Reader::Position = position; - } - } - - Stream::Stream() : CriticalSectionState(0) - { - memset(Stream::BlockSize, 0, sizeof(Stream::BlockSize)); - } - - Stream::Stream(size_t size) : Stream() - { - Stream::Buffer.reserve(size); - } - - Stream::~Stream() - { - Stream::Buffer.clear(); - - if (Stream::CriticalSectionState != 0) - { - MessageBoxA(0, Utils::VA("Invalid critical section state '%i' for stream destruction!", Stream::CriticalSectionState), "WARNING", MB_ICONEXCLAMATION); - } - }; - - size_t Stream::Length() - { - return Stream::Buffer.length(); - } - - size_t Stream::Capacity() - { - return Stream::Buffer.capacity(); - } - - char* Stream::Save(const void* _str, size_t size, size_t count) - { - return Stream::Save(Stream::GetCurrentBlock(), _str, size, count); - } - - char* Stream::Save(Game::XFILE_BLOCK_TYPES stream, const void * _str, size_t size, size_t count) - { - //if (stream == XFILE_BLOCK_TEMP || stream == XFILE_BLOCK_VIRTUAL || stream == XFILE_BLOCK_PHYSICAL) // Only those seem to actually write data. - // As I'm not sure though, I'll still write the data - // Use IncreaseBlockSize to fill virtual streams - auto data = Stream::Data(); - - if (Stream::IsCriticalSection() && Stream::Length() + (size * count) > Stream::Capacity()) - { - MessageBoxA(0, Utils::VA("Potential stream reallocation during critical operation detected! Writing data of the length 0x%X exceeds the allocated stream size of 0x%X\n", (size * count), Stream::Capacity()), "ERROR", MB_ICONERROR); - __debugbreak(); - } - - Stream::Buffer.append(static_cast(_str), size * count); - - if (Stream::Data() != data && Stream::IsCriticalSection()) - { - MessageBoxA(0, "Stream reallocation during critical operations not permitted!\nPlease increase the initial memory size or reallocate memory during non-critical sections!", "ERROR", MB_ICONERROR); - __debugbreak(); - } - - Stream::IncreaseBlockSize(stream, size * count); - - return Stream::At() - (size * count); - } - - char* Stream::Save(Game::XFILE_BLOCK_TYPES stream, int value, size_t count) - { - auto ret = Stream::Length(); - - for (size_t i = 0; i < count; ++i) - { - Stream::Save(stream, &value, 4, 1); - } - - return Stream::Data() + ret; - } - - char* Stream::SaveString(std::string string) - { - return Stream::SaveString(string.data()/*, string.size()*/); - } - - char* Stream::SaveString(const char* string) - { - return Stream::SaveString(string, strlen(string)); - } - - char* Stream::SaveString(const char* string, size_t len) - { - auto ret = Stream::Length(); - - if (string) - { - Stream::Save(string, len); - } - - Stream::SaveNull(); - - return Stream::Data() + ret; - } - - char* Stream::SaveText(std::string string) - { - return Stream::Save(string.data(), string.length()); - } - - char* Stream::SaveByte(unsigned char byte, size_t count) - { - auto ret = Stream::Length(); - - for (size_t i = 0; i < count; ++i) - { - Stream::Save(&byte, 1); - } - - return Stream::Data() + ret; - } - - char* Stream::SaveNull(size_t count) - { - return Stream::SaveByte(0, count); - } - - char* Stream::SaveMax(size_t count) - { - return Stream::SaveByte(static_cast(-1), count); - } - - void Stream::Align(Stream::Alignment align) - { - uint32_t size = 2 << align; - - // Not power of 2! - if (!size || (size & (size - 1))) return; - --size; - - Game::XFILE_BLOCK_TYPES stream = Stream::GetCurrentBlock(); - Stream::BlockSize[stream] = ~size & (Stream::GetBlockSize(stream) + size); - } - - bool Stream::PushBlock(Game::XFILE_BLOCK_TYPES stream) - { - Stream::StreamStack.push_back(stream); - return Stream::IsValidBlock(stream); - } - - bool Stream::PopBlock() - { - if (!Stream::StreamStack.empty()) - { - Stream::StreamStack.pop_back(); - return true; - } - - return false; - } - - bool Stream::IsValidBlock(Game::XFILE_BLOCK_TYPES stream) - { - return (stream < Game::MAX_XFILE_COUNT && stream >= Game::XFILE_BLOCK_TEMP); - } - - void Stream::IncreaseBlockSize(Game::XFILE_BLOCK_TYPES stream, unsigned int size) - { - if (Stream::IsValidBlock(stream)) - { - Stream::BlockSize[stream] += size; - } - } - - void Stream::IncreaseBlockSize(unsigned int size) - { - return IncreaseBlockSize(Stream::GetCurrentBlock(), size); - } - - Game::XFILE_BLOCK_TYPES Stream::GetCurrentBlock() - { - if (!Stream::StreamStack.empty()) - { - return Stream::StreamStack.back(); - } - - return Game::XFILE_BLOCK_INVALID; - } - - char* Stream::At() - { - return reinterpret_cast(Stream::Data() + Stream::Length()); - } - - char* Stream::Data() - { - return const_cast(Stream::Buffer.data()); - } - - unsigned int Stream::GetBlockSize(Game::XFILE_BLOCK_TYPES stream) - { - if (Stream::IsValidBlock(stream)) - { - return Stream::BlockSize[stream]; - } - - return 0; - } - - DWORD Stream::GetPackedOffset() - { - Game::XFILE_BLOCK_TYPES block = Stream::GetCurrentBlock(); - - Stream::Offset offset; - offset.block = block; - offset.offset = Stream::GetBlockSize(block); - return offset.GetPackedOffset(); - } - - void Stream::ToBuffer(std::string& outBuffer) - { - outBuffer.clear(); - outBuffer.append(Stream::Data(), Stream::Length()); - } - - std::string Stream::ToBuffer() - { - std::string buffer; - Stream::ToBuffer(buffer); - return buffer; - } - - void Stream::EnterCriticalSection() - { - ++Stream::CriticalSectionState; - } - - void Stream::LeaveCriticalSection() - { - --Stream::CriticalSectionState; - } - - bool Stream::IsCriticalSection() - { - if (Stream::CriticalSectionState < 0) - { - MessageBoxA(0, "CriticalSectionState in stream has been overrun!", "ERROR", MB_ICONERROR); - __debugbreak(); - } - - return (Stream::CriticalSectionState != 0); - } -} +#include "STDInclude.hpp" + +namespace Utils +{ + std::string Stream::Reader::ReadString() + { + std::string str; + + while (char byte = Stream::Reader::ReadByte()) + { + str.append(&byte, 1); + } + + return str; + } + + const char* Stream::Reader::ReadCString() + { + return Stream::Reader::Allocator->DuplicateString(Stream::Reader::ReadString()); + } + + char Stream::Reader::ReadByte() + { + if ((Stream::Reader::Position + 1) <= Stream::Reader::Buffer.size()) + { + return Stream::Reader::Buffer[Stream::Reader::Position++]; + } + + return 0; + } + + void* Stream::Reader::Read(size_t size, size_t count) + { + size_t bytes = size * count; + + if ((Stream::Reader::Position + bytes) <= Stream::Reader::Buffer.size()) + { + void* buffer = Stream::Reader::Allocator->Allocate(bytes); + + std::memcpy(buffer, Stream::Reader::Buffer.data() + Stream::Reader::Position, bytes); + Stream::Reader::Position += bytes; + + return buffer; + } + + return nullptr; + } + + bool Stream::Reader::End() + { + return (Stream::Reader::Buffer.size() == Stream::Reader::Position); + } + + void Stream::Reader::Seek(unsigned int position) + { + if (Stream::Reader::Buffer.size() >= position) + { + Stream::Reader::Position = position; + } + } + + Stream::Stream() : CriticalSectionState(0) + { + memset(Stream::BlockSize, 0, sizeof(Stream::BlockSize)); + } + + Stream::Stream(size_t size) : Stream() + { + Stream::Buffer.reserve(size); + } + + Stream::~Stream() + { + Stream::Buffer.clear(); + + if (Stream::CriticalSectionState != 0) + { + MessageBoxA(0, Utils::String::VA("Invalid critical section state '%i' for stream destruction!", Stream::CriticalSectionState), "WARNING", MB_ICONEXCLAMATION); + } + }; + + size_t Stream::Length() + { + return Stream::Buffer.length(); + } + + size_t Stream::Capacity() + { + return Stream::Buffer.capacity(); + } + + char* Stream::Save(const void* _str, size_t size, size_t count) + { + return Stream::Save(Stream::GetCurrentBlock(), _str, size, count); + } + + char* Stream::Save(Game::XFILE_BLOCK_TYPES stream, const void * _str, size_t size, size_t count) + { + //if (stream == XFILE_BLOCK_TEMP || stream == XFILE_BLOCK_VIRTUAL || stream == XFILE_BLOCK_PHYSICAL) // Only those seem to actually write data. + // As I'm not sure though, I'll still write the data + // Use IncreaseBlockSize to fill virtual streams + auto data = Stream::Data(); + + if (Stream::IsCriticalSection() && Stream::Length() + (size * count) > Stream::Capacity()) + { + MessageBoxA(0, Utils::String::VA("Potential stream reallocation during critical operation detected! Writing data of the length 0x%X exceeds the allocated stream size of 0x%X\n", (size * count), Stream::Capacity()), "ERROR", MB_ICONERROR); + __debugbreak(); + } + + Stream::Buffer.append(static_cast(_str), size * count); + + if (Stream::Data() != data && Stream::IsCriticalSection()) + { + MessageBoxA(0, "Stream reallocation during critical operations not permitted!\nPlease increase the initial memory size or reallocate memory during non-critical sections!", "ERROR", MB_ICONERROR); + __debugbreak(); + } + + Stream::IncreaseBlockSize(stream, size * count); + + return Stream::At() - (size * count); + } + + char* Stream::Save(Game::XFILE_BLOCK_TYPES stream, int value, size_t count) + { + auto ret = Stream::Length(); + + for (size_t i = 0; i < count; ++i) + { + Stream::Save(stream, &value, 4, 1); + } + + return Stream::Data() + ret; + } + + char* Stream::SaveString(std::string string) + { + return Stream::SaveString(string.data()/*, string.size()*/); + } + + char* Stream::SaveString(const char* string) + { + return Stream::SaveString(string, strlen(string)); + } + + char* Stream::SaveString(const char* string, size_t len) + { + auto ret = Stream::Length(); + + if (string) + { + Stream::Save(string, len); + } + + Stream::SaveNull(); + + return Stream::Data() + ret; + } + + char* Stream::SaveText(std::string string) + { + return Stream::Save(string.data(), string.length()); + } + + char* Stream::SaveByte(unsigned char byte, size_t count) + { + auto ret = Stream::Length(); + + for (size_t i = 0; i < count; ++i) + { + Stream::Save(&byte, 1); + } + + return Stream::Data() + ret; + } + + char* Stream::SaveNull(size_t count) + { + return Stream::SaveByte(0, count); + } + + char* Stream::SaveMax(size_t count) + { + return Stream::SaveByte(static_cast(-1), count); + } + + void Stream::Align(Stream::Alignment align) + { + uint32_t size = 2 << align; + + // Not power of 2! + if (!size || (size & (size - 1))) return; + --size; + + Game::XFILE_BLOCK_TYPES stream = Stream::GetCurrentBlock(); + Stream::BlockSize[stream] = ~size & (Stream::GetBlockSize(stream) + size); + } + + bool Stream::PushBlock(Game::XFILE_BLOCK_TYPES stream) + { + Stream::StreamStack.push_back(stream); + return Stream::IsValidBlock(stream); + } + + bool Stream::PopBlock() + { + if (!Stream::StreamStack.empty()) + { + Stream::StreamStack.pop_back(); + return true; + } + + return false; + } + + bool Stream::IsValidBlock(Game::XFILE_BLOCK_TYPES stream) + { + return (stream < Game::MAX_XFILE_COUNT && stream >= Game::XFILE_BLOCK_TEMP); + } + + void Stream::IncreaseBlockSize(Game::XFILE_BLOCK_TYPES stream, unsigned int size) + { + if (Stream::IsValidBlock(stream)) + { + Stream::BlockSize[stream] += size; + } + } + + void Stream::IncreaseBlockSize(unsigned int size) + { + return IncreaseBlockSize(Stream::GetCurrentBlock(), size); + } + + Game::XFILE_BLOCK_TYPES Stream::GetCurrentBlock() + { + if (!Stream::StreamStack.empty()) + { + return Stream::StreamStack.back(); + } + + return Game::XFILE_BLOCK_INVALID; + } + + char* Stream::At() + { + return reinterpret_cast(Stream::Data() + Stream::Length()); + } + + char* Stream::Data() + { + return const_cast(Stream::Buffer.data()); + } + + unsigned int Stream::GetBlockSize(Game::XFILE_BLOCK_TYPES stream) + { + if (Stream::IsValidBlock(stream)) + { + return Stream::BlockSize[stream]; + } + + return 0; + } + + DWORD Stream::GetPackedOffset() + { + Game::XFILE_BLOCK_TYPES block = Stream::GetCurrentBlock(); + + Stream::Offset offset; + offset.block = block; + offset.offset = Stream::GetBlockSize(block); + return offset.GetPackedOffset(); + } + + void Stream::ToBuffer(std::string& outBuffer) + { + outBuffer.clear(); + outBuffer.append(Stream::Data(), Stream::Length()); + } + + std::string Stream::ToBuffer() + { + std::string buffer; + Stream::ToBuffer(buffer); + return buffer; + } + + void Stream::EnterCriticalSection() + { + ++Stream::CriticalSectionState; + } + + void Stream::LeaveCriticalSection() + { + --Stream::CriticalSectionState; + } + + bool Stream::IsCriticalSection() + { + if (Stream::CriticalSectionState < 0) + { + MessageBoxA(0, "CriticalSectionState in stream has been overrun!", "ERROR", MB_ICONERROR); + __debugbreak(); + } + + return (Stream::CriticalSectionState != 0); + } +} diff --git a/src/Utils/String.cpp b/src/Utils/String.cpp new file mode 100644 index 00000000..6c0b1b21 --- /dev/null +++ b/src/Utils/String.cpp @@ -0,0 +1,119 @@ +#include "STDInclude.hpp" + +namespace Utils +{ + namespace String + { + std::string StrToLower(std::string input) + { + std::transform(input.begin(), input.end(), input.begin(), ::tolower); + return input; + } + + std::string StrToUpper(std::string input) + { + std::transform(input.begin(), input.end(), input.begin(), ::toupper); + return input; + } + + bool EndsWith(std::string haystack, std::string needle) + { + return (strstr(haystack.data(), needle.data()) == (haystack.data() + haystack.size() - needle.size())); + } + + std::string DumpHex(std::string data, std::string separator) + { + std::string result; + + for (unsigned int i = 0; i < data.size(); ++i) + { + if (i > 0) + { + result.append(separator); + } + + result.append(fmt::sprintf("%02X", data[i] & 0xFF)); + } + + return result; + } + + std::string XORString(std::string str, char value) + { + for (unsigned int i = 0; i < str.size(); ++i) + { + str[i] ^= value; + } + + return str; + } + + std::vector Explode(const std::string& str, char delim) + { + std::vector result; + std::istringstream iss(str); + + for (std::string token; std::getline(iss, token, delim);) + { + std::string _entry = std::move(token); + + // Remove trailing 0x0 bytes + while (_entry.size() && !_entry[_entry.size() - 1]) + { + _entry = _entry.substr(0, _entry.size() - 1); + } + + result.push_back(_entry); + } + + return result; + } + + void Replace(std::string &string, std::string find, std::string replace) + { + size_t nPos = 0; + + while ((nPos = string.find(find, nPos)) != std::string::npos) + { + string = string.replace(nPos, find.length(), replace); + nPos += replace.length(); + } + } + + bool StartsWith(std::string haystack, std::string needle) + { + return (haystack.size() >= needle.size() && !strncmp(needle.data(), haystack.data(), needle.size())); + } + + // trim from start + std::string <rim(std::string &s) + { + s.erase(s.begin(), std::find_if(s.begin(), s.end(), std::not1(std::ptr_fun(std::isspace)))); + return s; + } + + // trim from end + std::string &RTrim(std::string &s) + { + s.erase(std::find_if(s.rbegin(), s.rend(), std::not1(std::ptr_fun(std::isspace))).base(), s.end()); + return s; + } + + // trim from both ends + std::string &Trim(std::string &s) + { + return LTrim(RTrim(s)); + } + + std::string FormatTimeSpan(int milliseconds) + { + int secondsTotal = milliseconds / 1000; + int seconds = secondsTotal % 60; + int minutesTotal = secondsTotal / 60; + int minutes = minutesTotal % 60; + int hoursTotal = minutesTotal / 60; + + return fmt::sprintf("%02d:%02d:%02d", hoursTotal, minutes, seconds); + } + } +} diff --git a/src/Utils/String.hpp b/src/Utils/String.hpp new file mode 100644 index 00000000..5feed183 --- /dev/null +++ b/src/Utils/String.hpp @@ -0,0 +1,38 @@ +namespace Utils +{ + namespace String + { + template + const char* VA(std::string message, Args && ...args) + { + #define VA_BUFFER_COUNT 4 + #define VA_BUFFER_SIZE 65536 + + static char g_vaBuffer[VA_BUFFER_COUNT][VA_BUFFER_SIZE]; + static int g_vaNextBufferIndex = 0; + + char* buffer = g_vaBuffer[g_vaNextBufferIndex]; + std::string str = fmt::sprintf(message, std::forward(args)...); + strncpy_s(g_vaBuffer[g_vaNextBufferIndex], str.data(), VA_BUFFER_SIZE); + g_vaNextBufferIndex = (g_vaNextBufferIndex + 1) % VA_BUFFER_COUNT; + + return buffer; + } + + std::string StrToLower(std::string input); + std::string StrToUpper(std::string input); + bool EndsWith(std::string haystack, std::string needle); + std::vector Explode(const std::string& str, char delim); + void Replace(std::string &string, std::string find, std::string replace); + bool StartsWith(std::string haystack, std::string needle); + std::string <rim(std::string &s); + std::string &RTrim(std::string &s); + std::string &Trim(std::string &s); + + std::string FormatTimeSpan(int milliseconds); + + std::string DumpHex(std::string data, std::string separator = " "); + + std::string XORString(std::string str, char value); + } +} diff --git a/src/Utils/Utils.cpp b/src/Utils/Utils.cpp index e65e991d..ba31c55c 100644 --- a/src/Utils/Utils.cpp +++ b/src/Utils/Utils.cpp @@ -1,287 +1,41 @@ -#include "STDInclude.hpp" - -#define VA_BUFFER_COUNT 4 -#define VA_BUFFER_SIZE 65536 - -namespace Utils -{ - std::string GetMimeType(std::string url) - { - wchar_t* mimeType = nullptr; - FindMimeFromData(NULL, std::wstring(url.begin(), url.end()).data(), NULL, 0, NULL, 0, &mimeType, 0); - - if (mimeType) - { - std::wstring wMimeType(mimeType); - return std::string(wMimeType.begin(), wMimeType.end()); - } - - return "application/octet-stream"; - } - - const char *VA(const char *fmt, ...) - { - static char g_vaBuffer[VA_BUFFER_COUNT][VA_BUFFER_SIZE]; - static int g_vaNextBufferIndex = 0; - - va_list ap; - va_start(ap, fmt); - char* dest = g_vaBuffer[g_vaNextBufferIndex]; - vsnprintf(g_vaBuffer[g_vaNextBufferIndex], VA_BUFFER_SIZE, fmt, ap); - g_vaNextBufferIndex = (g_vaNextBufferIndex + 1) % VA_BUFFER_COUNT; - va_end(ap); - return dest; - } - - std::string StrToLower(std::string input) - { - std::transform(input.begin(), input.end(), input.begin(), ::tolower); - return input; - } - - std::string StrToUpper(std::string input) - { - std::transform(input.begin(), input.end(), input.begin(), ::toupper); - return input; - } - - bool EndsWith(std::string haystack, std::string needle) - { - return (strstr(haystack.data(), needle.data()) == (haystack.data() + haystack.size() - needle.size())); - } - - std::string DumpHex(std::string data, std::string separator) - { - std::string result; - - for (unsigned int i = 0; i < data.size(); ++i) - { - if (i > 0) - { - result.append(separator); - } - - result.append(Utils::VA("%02X", data[i] & 0xFF)); - } - - return result; - } - - std::string XORString(std::string str, char value) - { - for (unsigned int i = 0; i < str.size(); ++i) - { - str[i] ^= value; - } - - return str; - } - - // Complementary function for memset, which checks if a memory is set - bool MemIsSet(void* mem, char chr, size_t length) - { - char* memArr = reinterpret_cast(mem); - - for (size_t i = 0; i < length; ++i) - { - if (memArr[i] != chr) - { - return false; - } - } - - return true; - } - - std::vector Explode(const std::string& str, char delim) - { - std::vector result; - std::istringstream iss(str); - - for (std::string token; std::getline(iss, token, delim);) - { - std::string _entry = std::move(token); - - // Remove trailing 0x0 bytes - while (_entry.size() && !_entry[_entry.size() - 1]) - { - _entry = _entry.substr(0, _entry.size() - 1); - } - - result.push_back(_entry); - } - - return result; - } - - void Replace(std::string &string, std::string find, std::string replace) - { - size_t nPos = 0; - - while ((nPos = string.find(find, nPos)) != std::string::npos) - { - string = string.replace(nPos, find.length(), replace); - nPos += replace.length(); - } - } - - bool StartsWith(std::string haystack, std::string needle) - { - return (haystack.size() >= needle.size() && !strncmp(needle.data(), haystack.data(), needle.size())); - } - - // trim from start - std::string <rim(std::string &s) - { - s.erase(s.begin(), std::find_if(s.begin(), s.end(), std::not1(std::ptr_fun(std::isspace)))); - return s; - } - - // trim from end - std::string &RTrim(std::string &s) - { - s.erase(std::find_if(s.rbegin(), s.rend(), std::not1(std::ptr_fun(std::isspace))).base(), s.end()); - return s; - } - - // trim from both ends - std::string &Trim(std::string &s) - { - return LTrim(RTrim(s)); - } - - std::string FormatTimeSpan(int milliseconds) - { - int secondsTotal = milliseconds / 1000; - int seconds = secondsTotal % 60; - int minutesTotal = secondsTotal / 60; - int minutes = minutesTotal % 60; - int hoursTotal = minutesTotal / 60; - - return Utils::VA("%02d:%02d:%02d", hoursTotal, minutes, seconds); - } - - std::string ParseChallenge(std::string data) - { - auto pos = data.find_first_of("\n "); - if (pos == std::string::npos) return data; - return data.substr(0, pos).data(); - } - - // TODO: Use modern file reading methods - bool FileExists(std::string file) - { - FILE* fp; - fopen_s(&fp, file.data(), "r"); - - if (fp) - { - fclose(fp); - return true; - } - - return false; - } - - void WriteFile(std::string file, std::string data) - { - std::ofstream stream(file, std::ios::binary); - - if (stream.is_open()) - { - stream.write(data.data(), data.size()); - stream.close(); - } - } - - std::string ReadFile(std::string file) - { - std::string buffer; - - if (FileExists(file)) - { - std::streamsize size = 0; - std::ifstream stream(file, std::ios::binary); - if (!stream.is_open()) return buffer; - - stream.seekg(0, std::ios::end); - size = stream.tellg(); - stream.seekg(0, std::ios::beg); - - if (size > -1) - { - buffer.clear(); - buffer.resize((uint32_t)size); - - stream.read(const_cast(buffer.data()), size); - } - - stream.close(); - } - - return buffer; - } - - // Infostring class - void InfoString::Set(std::string key, std::string value) - { - this->KeyValuePairs[key] = value; - } - - std::string InfoString::Get(std::string key) - { - if (this->KeyValuePairs.find(key) != this->KeyValuePairs.end()) - { - return this->KeyValuePairs[key]; - } - - return ""; - } - - std::string InfoString::Build() - { - std::string infoString; - - bool first = true; - - for (auto i = this->KeyValuePairs.begin(); i != this->KeyValuePairs.end(); ++i) - { - if (first) first = false; - else infoString.append("\\"); - - infoString.append(i->first); // Key - infoString.append("\\"); - infoString.append(i->second); // Value - } - - return infoString; - } - - void InfoString::Dump() - { - for (auto i = this->KeyValuePairs.begin(); i != this->KeyValuePairs.end(); ++i) - { - OutputDebugStringA(Utils::VA("%s: %s", i->first.data(), i->second.data())); - } - } - - json11::Json InfoString::to_json() - { - return this->KeyValuePairs; - } - - void InfoString::Parse(std::string buffer) - { - if (buffer[0] == '\\') - { - buffer = buffer.substr(1); - } - - std::vector KeyValues = Utils::Explode(buffer, '\\'); - - for (unsigned int i = 0; i < (KeyValues.size() - 1); i+=2) - { - this->KeyValuePairs[KeyValues[i]] = KeyValues[i + 1]; - } - } -} +#include "STDInclude.hpp" + +namespace Utils +{ + std::string GetMimeType(std::string url) + { + wchar_t* mimeType = nullptr; + FindMimeFromData(NULL, std::wstring(url.begin(), url.end()).data(), NULL, 0, NULL, 0, &mimeType, 0); + + if (mimeType) + { + std::wstring wMimeType(mimeType); + return std::string(wMimeType.begin(), wMimeType.end()); + } + + return "application/octet-stream"; + } + + // Complementary function for memset, which checks if a memory is set + bool MemIsSet(void* mem, char chr, size_t length) + { + char* memArr = reinterpret_cast(mem); + + for (size_t i = 0; i < length; ++i) + { + if (memArr[i] != chr) + { + return false; + } + } + + return true; + } + + std::string ParseChallenge(std::string data) + { + auto pos = data.find_first_of("\n "); + if (pos == std::string::npos) return data; + return data.substr(0, pos).data(); + } +} diff --git a/src/Utils/Utils.hpp b/src/Utils/Utils.hpp index b1662d2b..8479e31c 100644 --- a/src/Utils/Utils.hpp +++ b/src/Utils/Utils.hpp @@ -1,69 +1,25 @@ -#define ARR_SIZE(x) (sizeof(x) / sizeof(x[0])) - -namespace Utils -{ - std::string GetMimeType(std::string url); - const char *VA(const char *fmt, ...); - std::string StrToLower(std::string input); - std::string StrToUpper(std::string input); - bool EndsWith(std::string haystack, std::string needle); - std::vector Explode(const std::string& str, char delim); - void Replace(std::string &string, std::string find, std::string replace); - bool StartsWith(std::string haystack, std::string needle); - std::string <rim(std::string &s); - std::string &RTrim(std::string &s); - std::string &Trim(std::string &s); - - std::string FormatTimeSpan(int milliseconds); - std::string ParseChallenge(std::string data); - - bool FileExists(std::string file); - void WriteFile(std::string file, std::string data); - std::string ReadFile(std::string file); - - std::string DumpHex(std::string data, std::string separator = " "); - - std::string XORString(std::string str, char value); - - bool MemIsSet(void* mem, char chr, size_t length); - - class InfoString - { - public: - InfoString() {}; - InfoString(std::string buffer) : InfoString() { this->Parse(buffer); }; - InfoString(const InfoString &obj) : KeyValuePairs(obj.KeyValuePairs) {}; - - void Set(std::string key, std::string value); - std::string Get(std::string key); - - std::string Build(); - - void Dump(); - - json11::Json to_json(); - - private: - std::map KeyValuePairs; - void Parse(std::string buffer); - }; - - template void Merge(std::vector* target, T* source, size_t length) - { - if (source) - { - for (size_t i = 0; i < length; ++i) - { - target->push_back(source[i]); - } - } - } - - template void Merge(std::vector* target, std::vector source) - { - for (auto &entry : source) - { - target->push_back(entry); - } - } -} +namespace Utils +{ + std::string GetMimeType(std::string url); + std::string ParseChallenge(std::string data); + bool MemIsSet(void* mem, char chr, size_t length); + + template void Merge(std::vector* target, T* source, size_t length) + { + if (source) + { + for (size_t i = 0; i < length; ++i) + { + target->push_back(source[i]); + } + } + } + + template void Merge(std::vector* target, std::vector source) + { + for (auto &entry : source) + { + target->push_back(entry); + } + } +}