diff --git a/.gitmodules b/.gitmodules index dc5c9e50..d9402510 100644 --- a/.gitmodules +++ b/.gitmodules @@ -23,9 +23,16 @@ 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 -[submodule "deps/Wink-Signals"] - path = deps/Wink-Signals - url = https://github.com/miguelmartin75/Wink-Signals.git +[submodule "deps/fmt"] + path = deps/fmt + url = https://github.com/fmtlib/fmt.git +[submodule "deps/Wink-Signals"] + path = deps/Wink-Signals + url = https://github.com/miguelmartin75/Wink-Signals.git +[submodule "deps/bitmrc"] + path = deps/bitmrc + url = git@github.com:iw4x-dev-urandom/BitMRC.git + ignore = dirty +[submodule "deps/base128"] + path = deps/base128 + url = https://github.com/seizu/base128.git diff --git a/deps/base128 b/deps/base128 new file mode 160000 index 00000000..64c8ab27 --- /dev/null +++ b/deps/base128 @@ -0,0 +1 @@ +Subproject commit 64c8ab2755e14d316b18aff9746f0180f5fe301b diff --git a/deps/bitmrc b/deps/bitmrc new file mode 160000 index 00000000..43f63eeb --- /dev/null +++ b/deps/bitmrc @@ -0,0 +1 @@ +Subproject commit 43f63eeb1f900fd03b192e7e577c033a6be072ab diff --git a/deps/fmt b/deps/fmt index 2ae6bca4..0d25f6fc 160000 --- a/deps/fmt +++ b/deps/fmt @@ -1 +1 @@ -Subproject commit 2ae6bca488795929a0207d109e135751f10c53d9 +Subproject commit 0d25f6fcbbf0a867b939a5501965ee4462b21ee6 diff --git a/deps/mongoose b/deps/mongoose index b3fb21da..2a541175 160000 --- a/deps/mongoose +++ b/deps/mongoose @@ -1 +1 @@ -Subproject commit b3fb21dacccfb08b046b73740eec52cd66e944de +Subproject commit 2a541175b56a1aeecd2dc8474f981923ef580af6 diff --git a/deps/protobuf b/deps/protobuf index 3d9d1a12..14e74f6a 160000 --- a/deps/protobuf +++ b/deps/protobuf @@ -1 +1 @@ -Subproject commit 3d9d1a1255583bac550f7bf94f3016e8c238fa5e +Subproject commit 14e74f6a21f2726d25e0e679c59d569f6bc8fe8e diff --git a/premake/base128.lua b/premake/base128.lua new file mode 100644 index 00000000..bd8a7db3 --- /dev/null +++ b/premake/base128.lua @@ -0,0 +1,54 @@ +base128 = { + settings = nil +} + +function base128.setup(settings) + if not settings.source then error("Missing source.") end + + base128.settings = settings +end + +function base128.import() + if not base128.settings then error("Run base128.setup first") end + + base128.links() + base128.includes() +end + +function base128.links() + if not base128.settings then error("Run base128.setup first") end + + links { "base128" } +end + +function base128.includes() + if not base128.settings then error("Run base128.setup first") end + + includedirs { path.join(base128.settings.source, "cpp") } +end + +function base128.project() + if not base128.settings then error("Run base128.setup first") end + + project "base128" + language "C++" + + base128.includes() + + files + { + path.join(base128.settings.source, "cpp/*.cpp"), + path.join(base128.settings.source, "cpp/*.h"), + } + removefiles + { + "**/demo.*", + } + + -- not our code, ignore POSIX usage warnings for now + warnings "Off" + + defines { "_LIB" } + removedefines { "_USRDLL", "_DLL" } + kind "StaticLib" +end diff --git a/premake/bitmrc.lua b/premake/bitmrc.lua new file mode 100644 index 00000000..77db6a4e --- /dev/null +++ b/premake/bitmrc.lua @@ -0,0 +1,64 @@ +bitmrc = { + settings = nil, +} + +function bitmrc.setup(settings) + if not settings.source then error("Missing source.") end + + bitmrc.settings = settings +end + +function bitmrc.import() + if not bitmrc.settings then error("Run bitmrc.setup first") end + + sqlite3.links() + libcryptopp.links() + bitmrc.links() + bitmrc.includes() +end + +function bitmrc.links() + links { "bitmrc" } +end + +function bitmrc.includes() + if not bitmrc.settings then error("Run bitmrc.setup first") end + + includedirs { path.join(bitmrc.settings.source, "BitMRC/include") } +end + +function bitmrc.project() + if not bitmrc.settings then error("Run bitmrc.setup first") end + + project "bitmrc" + language "C++" + + includedirs + { + path.join(bitmrc.settings.source, "BitMRC/include"), + path.join(bitmrc.settings.source, "BitMRC/Storage/include"), + } + files + { + path.join(bitmrc.settings.source, "BitMRC/*.cpp"), + path.join(bitmrc.settings.source, "BitMRC/Storage/*.cpp"), + } + removefiles + { + -- path.join(bitmrc.settings.source, "src/**/*test.cc"), + path.join(bitmrc.settings.source, "BitMRC/main.*"), + path.join(bitmrc.settings.source, "BitMRC/class.*"), + path.join(bitmrc.settings.source, "BitMRC/tests/**"), + + path.join(bitmrc.settings.source, "BitMRC/Storage/Storable.cpp"), + } + + -- dependencies + sqlite3.import() + libcryptopp.import() + + defines { "_SCL_SECURE_NO_WARNINGS" } + warnings "Off" + + kind "StaticLib" +end diff --git a/premake/libcryptopp.lua b/premake/libcryptopp.lua new file mode 100644 index 00000000..c2116497 --- /dev/null +++ b/premake/libcryptopp.lua @@ -0,0 +1,163 @@ +libcryptopp = { + settings = nil, +} + +function libcryptopp.setup(settings) + if not settings.source then error("Missing source.") end + + libcryptopp.settings = settings +end + +function libcryptopp.import() + if not libcryptopp.settings then error("Run libcryptopp.setup first") end + + libcryptopp.links() + libcryptopp.includes() +end + +function libcryptopp.links() + links { "libcryptopp" } +end + +function libcryptopp.includes() + if not libcryptopp.settings then error("Run libcryptopp.setup first") end + + --defines { "CRYPTOPP_IMPORTS" } + + --filter "*Static" + -- removedefines { "CRYPTOPP_IMPORTS" } + + filter "Debug*" + defines { "_DEBUG" } + + filter "Release*" + defines { "NDEBUG" } + + filter "system:windows" + defines { "_WINDOWS", "WIN32" } + filter {} + + includedirs { libcryptopp.settings.source } +end + +function libcryptopp.project() + if not libcryptopp.settings then error("Run libcryptopp.setup first") end + + rule "MASM_dummy" + location "./build" + fileextension "" + filename "masm_dummy" + + externalrule "MASM" + filename "masm_dummy" + location "./build" + buildmessage "Building and assembling %(Identity)..." + propertydefinition { + name = "PreprocessorDefinitions", + kind = "string", + value = "", + switch = "/D", + } + propertydefinition { + name = "UseSafeExceptionHandlers", + kind = "boolean", + value = false, + switch = "/safeseh", + } + + --[[ + rule "CustomProtoBuildTool" + display "C++ prototype copy" + location "./build" + fileExtension ".proto" + buildmessage "Preparing %(Identity)..." + buildcommands { + 'if not exist "$(ProjectDir)\\src\\%(Filename)" copy "%(Identity)" "$(ProjectDir)\\src\\%(Filename)"', + 'echo: >> "src\\%(Filename).copied"', + } + buildoutputs { + '$(ProjectDir)\\src\\%(Filename)', + } + ]] + + project "libcryptopp" + language "C++" + characterset "MBCS" + + defines { + "USE_PRECOMPILED_HEADERS" + } + includedirs + { + libcryptopp.settings.source, + } + files + { + path.join(libcryptopp.settings.source, "*.cpp"), + --path.join(libcryptopp.settings.source, "*.cpp.proto"), + path.join(libcryptopp.settings.source, "*.h"), + path.join(libcryptopp.settings.source, "*.txt"), + } + + removefiles { + path.join(libcryptopp.settings.source, "eccrypto.cpp"), + path.join(libcryptopp.settings.source, "eprecomp.cpp"), + path.join(libcryptopp.settings.source, "bench*"), + path.join(libcryptopp.settings.source, "*test.*"), + path.join(libcryptopp.settings.source, "fipsalgt.*"), + path.join(libcryptopp.settings.source, "cryptlib_bds.*"), + path.join(libcryptopp.settings.source, "validat*.*"), + + -- Remove linker warnings + path.join(libcryptopp.settings.source, "strciphr.cpp"), + path.join(libcryptopp.settings.source, "simple.cpp"), + path.join(libcryptopp.settings.source, "polynomi.cpp"), + path.join(libcryptopp.settings.source, "algebra.cpp"), + } + + -- Pre-compiled header + pchheader "pch.h" -- must be exactly same as used in #include directives + pchsource(path.join(libcryptopp.settings.source, "pch.cpp")) -- real path + + defines { "_SCL_SECURE_NO_WARNINGS" } + warnings "Off" + + vectorextensions "SSE" + + rules { + "MASM", + --"CustomProtoBuildTool", + } + + -- SharedLib needs that + --links { "Ws2_32" } + + --kind "SharedLib" + --filter "*Static" + kind "StaticLib" + + filter "kind:SharedLib" + defines { "CRYPTOPP_EXPORTS" } + + filter "architecture:x86" + exceptionhandling "SEH" + masmVars { + UseSafeExceptionHandlers = true, + PreprocessorDefinitions = "_M_X86", + } + filter "architecture:x64" + files { + path.join(libcryptopp.settings.source, "x64masm.asm"), + } + masmVars { + PreprocessorDefinitions = "_M_X64", + } + filter { "architecture:x64", "kind:SharedLib" } + files { + path.join(libcryptopp.settings.source, "x64dll.asm"), + } + + filter("files:" .. path.join(libcryptopp.settings.source, "dll.cpp") + .. " or files:" .. path.join(libcryptopp.settings.source, "iterhash.cpp")) + flags { "NoPCH" } +end diff --git a/premake/sqlite3.lua b/premake/sqlite3.lua new file mode 100644 index 00000000..a3d12e4f --- /dev/null +++ b/premake/sqlite3.lua @@ -0,0 +1,54 @@ +sqlite3 = { + settings = nil, +} + +function sqlite3.setup(settings) + if not settings.source then error("Missing source.") end + + sqlite3.settings = settings +end + +function sqlite3.import() + if not sqlite3.settings then error("Run sqlite3.setup first") end + + sqlite3.includes() + sqlite3.links() +end + +function sqlite3.links() + links { "sqlite3" } +end + +function sqlite3.includes() + if not sqlite3.settings then error("Run sqlite3.setup first") end + + includedirs { sqlite3.settings.source } +end + +function sqlite3.project() + if not sqlite3.settings then error("Run sqlite3.setup first") end + + project "sqlite3" + language "C++" + + includedirs + { + sqlite3.settings.source, + } + + files + { + path.join(sqlite3.settings.source, "sqlite3*.c"), + path.join(sqlite3.settings.source, "sqlite3*.h"), + } + + -- not our code, ignore POSIX usage warnings for now + warnings "Off" + + --kind "SharedLib" + --filter "*Static" + kind "StaticLib" + --filter "kind:StaticLib" + -- defines { "_LIB" } + -- removedefines { "_USRDLL", "_DLL" } +end diff --git a/premake5.lua b/premake5.lua index 67a4739a..476a647a 100644 --- a/premake5.lua +++ b/premake5.lua @@ -1,3 +1,5 @@ +gitVersioningCommand = "git describe --tags --dirty --always" + -- Quote the given string input as a C string function cstrquote(value) result = value:gsub("\\", "\\\\") @@ -11,6 +13,19 @@ function cstrquote(value) return result end +-- Converts tags in "vX.X.X" format to X,X,X. +-- In the case where the format does not work fall back to old 4,2,REVISION. +function vertonum(value, vernumber) + vernum = {} + for num in string.gmatch(value, "%d+") do + table.insert(vernum, num) + end + if #vernum < 3 then + return "4,2," .. vernumber + end + return vernum[1] .. "," .. vernum[2] .. "," .. vernum[3] +end + -- Option to allow copying the DLL file to a custom folder after build newoption { trigger = "copy-to", @@ -43,16 +58,41 @@ newoption { description = "Always compile unit tests." } +newoption { + trigger = "force-exception-handler", + description = "Install custom unhandled exception handler even for Debug builds." +} + +newoption { + trigger = "force-minidump-upload", + description = "Upload minidumps even for Debug builds." +} + +newoption { + trigger = "disable-bitmessage", + description = "Disable use of BitMessage completely." +} + +newoption { + trigger = "disable-node-log", + description = "Disable debugging messages for Nodes in Debug builds." +} + +newoption { + trigger = "disable-base128", + description = "Disable debugging messages for Nodes in Debug builds." +} + 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+", "") + -- get current version via git + local proc = assert(io.popen(gitVersioningCommand, "r")) + local gitDescribeOutput = assert(proc:read('*a')):gsub("%s+", "") proc:close() - print(revNumber) + print(gitDescribeOutput) os.exit(0) end } @@ -64,60 +104,73 @@ newaction { -- get revision number via git local proc = assert(io.popen("git rev-list --count HEAD", "r")) local revNumber = assert(proc:read('*a')):gsub("%s+", "") + + -- get current version via git + local proc = assert(io.popen(gitVersioningCommand, "r")) + local gitDescribeOutput = assert(proc:read('*a')):gsub("%s+", "") proc:close() -- get whether this is a clean revision (no uncommitted changes) proc = assert(io.popen("git status --porcelain", "r")) - local revClean = 1 - local revCleanSuffix = "" - if assert(proc:read('*a')) ~= "" then - revClean = 0 - revCleanSuffix = " (unclean)" - end + local revDirty = (assert(proc:read('*a')) ~= "") + if revDirty then revDirty = 1 else revDirty = 0 end proc:close() -- get current tag name (aka milestone for now) - proc = assert(io.popen("git tag")) + proc = assert(io.popen("git describe --tags --abbrev=0")) local tagName = assert(proc:read('*l')) -- 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)" + local oldVersion = "(none)" + local oldVersionHeader = io.open(wks.location .. "/src/version.h", "r") + if oldVersionHeader ~= nil then + local oldVersionHeaderContent = assert(oldVersionHeader:read('*l')) + while oldVersionHeaderContent do + m = string.match(oldVersionHeaderContent, "#define GIT_DESCRIBE (.+)%s*$") + if m ~= nil then + oldVersion = m + end + + oldVersionHeaderContent = oldVersionHeader:read('*l') 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")) + gitDescribeOutputQuoted = cstrquote(gitDescribeOutput) + if oldVersion ~= gitDescribeOutputQuoted then + print ("Update " .. oldVersion .. " -> " .. gitDescribeOutputQuoted) + local versionHeader = assert(io.open(wks.location .. "/src/version.h", "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 GIT_DESCRIBE " .. gitDescribeOutputQuoted .. "\n") + versionHeader:write("#define GIT_DIRTY " .. revDirty .. "\n") + versionHeader:write("#define GIT_TAG " .. cstrquote(tagName) .. "\n") + versionHeader:write("\n") + versionHeader:write("// Legacy definitions (needed for update check)\n") versionHeader:write("#define REVISION " .. revNumber .. "\n") - versionHeader:write("#define REVISION_CLEAN " .. revClean .. "\n") - versionHeader:write("#define MILESTONE " .. cstrquote(tagName) .. "\n") + versionHeader:write("\n") + versionHeader:write("// Version transformed for RC files\n") + versionHeader:write("#define VERSION_RC " .. vertonum(tagName, revNumber) .. "\n") + versionHeader:write("\n") + versionHeader:write("// Alias definitions\n") + versionHeader:write("#define VERSION GIT_DESCRIBE\n") + versionHeader:write("#define SHORTVERSION GIT_TAG\n") + versionHeader:close() + 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(" * This file exists for reasons of complying with our coding standards.\n") + versionHeader:write(" *\n") + versionHeader:write(" * The Resource Compiler will ignore any content from C++ header files if they're not from STDInclude.hpp.\n") + versionHeader:write(" * That's the reason why we now place all version info in version.h instead.\n") + versionHeader:write(" */\n") + versionHeader:write("\n") + versionHeader:write("#include \".\\version.h\"\n") versionHeader:close() end end @@ -125,16 +178,28 @@ newaction { depsBasePath = "./deps" +require "premake/base128" +require "premake/bitmrc" require "premake/fmt" require "premake/json11" +require "premake/libcryptopp" require "premake/libtomcrypt" require "premake/libtommath" require "premake/mongoose" require "premake/pdcurses" require "premake/protobuf" +require "premake/sqlite3" require "premake/winksignals" require "premake/zlib" +base128.setup +{ + source = path.join(depsBasePath, "base128"), +} +bitmrc.setup +{ + source = path.join(depsBasePath, "bitmrc"), +} fmt.setup { source = path.join(depsBasePath, "fmt"), @@ -143,6 +208,10 @@ json11.setup { source = path.join(depsBasePath, "json11"), } +libcryptopp.setup +{ + source = path.join(depsBasePath, "bitmrc/libcryptopp"), +} libtomcrypt.setup { defines = { @@ -171,6 +240,10 @@ protobuf.setup { source = path.join(depsBasePath, "protobuf"), } +sqlite3.setup +{ + source = path.join(depsBasePath, "bitmrc/windows/sqlite3"), +} winksignals.setup { source = path.join(depsBasePath, "Wink-Signals"), @@ -238,6 +311,24 @@ workspace "iw4x" if _OPTIONS["force-unit-tests"] then defines { "FORCE_UNIT_TESTS" } end + if _OPTIONS["force-minidump-upload"] then + defines { "FORCE_MINIDUMP_UPLOAD" } + end + if _OPTIONS["force-exception-handler"] then + defines { "FORCE_EXCEPTION_HANDLER" } + end + if _OPTIONS["disable-bitmessage"] then + defines { "DISABLE_BITMESSAGE" } + removefiles { + "./src/Components/Modules/BitMessage.*", + } + end + if _OPTIONS["disable-node-log"] then + defines { "DISABLE_NODE_LOG"} + end + if _OPTIONS["disable-base128"] then + defines { "DISABLE_BASE128" } + end -- Pre-compiled header pchheader "STDInclude.hpp" -- must be exactly same as used in #include directives @@ -245,6 +336,12 @@ workspace "iw4x" buildoptions { "/Zm200" } -- Dependency libraries + if not _OPTIONS["disable-bitmessage"] then + bitmrc.import() + end + if not _OPTIONS["disable-base128"] then + base128.import() + end fmt.import() json11.import() libtomcrypt.import() @@ -348,15 +445,23 @@ workspace "iw4x" } group "External dependencies" + if not _OPTIONS["disable-bitmessage"] then + bitmrc.project() + libcryptopp.project() + sqlite3.project() + end + if not _OPTIONS["disable-base128"] then + base128.project() + end fmt.project() json11.project() libtomcrypt.project() libtommath.project() mongoose.project() pdcurses.project() + protobuf.project() winksignals.project() zlib.project() - protobuf.project() rule "ProtobufCompiler" display "Protobuf compiler" diff --git a/src/Components/Loader.cpp b/src/Components/Loader.cpp index e6284704..90e40d33 100644 --- a/src/Components/Loader.cpp +++ b/src/Components/Loader.cpp @@ -2,10 +2,18 @@ namespace Components { + bool Loader::Pregame = true; std::vector Loader::Components; + bool Loader::IsPregame() + { + return Loader::Pregame; + } + void Loader::Initialize() { + Loader::Pregame = true; + Loader::Register(new Flags()); Loader::Register(new Singleton()); @@ -44,7 +52,11 @@ namespace Components Loader::Register(new Exception()); Loader::Register(new FastFiles()); Loader::Register(new Materials()); +#ifndef DISABLE_BITMESSAGE + Loader::Register(new BitMessage()); +#endif Loader::Register(new FileSystem()); + Loader::Register(new PlayerName()); Loader::Register(new QuickPatch()); Loader::Register(new ServerInfo()); Loader::Register(new ServerList()); @@ -53,8 +65,11 @@ namespace Components Loader::Register(new AssetHandler()); Loader::Register(new Localization()); Loader::Register(new MusicalTalent()); + Loader::Register(new MinidumpUpload()); Loader::Register(new StructuredData()); Loader::Register(new ConnectProtocol()); + + Loader::Pregame = false; } void Loader::Uninitialize() diff --git a/src/Components/Loader.hpp b/src/Components/Loader.hpp index 4681166e..00883999 100644 --- a/src/Components/Loader.hpp +++ b/src/Components/Loader.hpp @@ -22,7 +22,10 @@ namespace Components static bool PerformingUnitTests(); static void Register(Component* component); + static bool IsPregame(); + private: + static bool Pregame; static std::vector Components; }; } @@ -38,7 +41,6 @@ namespace Components #include "Modules\Toast.hpp" #include "Modules\Colors.hpp" #include "Modules\D3D9Ex.hpp" -#include "Modules\Logger.hpp" #include "Modules\Script.hpp" #include "Modules\Weapon.hpp" #include "Modules\Window.hpp" @@ -51,6 +53,7 @@ namespace Components #include "Modules\Node.hpp" #include "Modules\RCon.hpp" #include "Modules\Party.hpp" // Destroys the order, but requires network classes :D +#include "Modules\Logger.hpp" #include "Modules\Download.hpp" #include "Modules\Playlist.hpp" #include "Modules\RawFiles.hpp" @@ -64,7 +67,9 @@ namespace Components #include "Modules\FastFiles.hpp" #include "Modules\Materials.hpp" #include "Modules\Singleton.hpp" +#include "Modules\BitMessage.hpp" #include "Modules\FileSystem.hpp" +#include "Modules\PlayerName.hpp" #include "Modules\QuickPatch.hpp" #include "Modules\ServerInfo.hpp" #include "Modules\ServerList.hpp" @@ -73,5 +78,6 @@ namespace Components #include "Modules\AssetHandler.hpp" #include "Modules\Localization.hpp" #include "Modules\MusicalTalent.hpp" +#include "Modules\MinidumpUpload.hpp" #include "Modules\StructuredData.hpp" #include "Modules\ConnectProtocol.hpp" diff --git a/src/Components/Modules/AntiCheat.cpp b/src/Components/Modules/AntiCheat.cpp index dd68d9c5..1c2616ab 100644 --- a/src/Components/Modules/AntiCheat.cpp +++ b/src/Components/Modules/AntiCheat.cpp @@ -136,18 +136,25 @@ namespace Components static uint8_t loadLibWStr[] = { 0xB3, 0x90, 0x9E, 0x9B, 0xB3, 0x96, 0x9D, 0x8D, 0x9E, 0x8D, 0x86, 0xA8 }; // LoadLibraryW HMODULE kernel32 = GetModuleHandleA(Utils::String::XOR(std::string(reinterpret_cast(kernel32Str), sizeof kernel32Str), -1).data()); - FARPROC loadLibA = GetProcAddress(kernel32, Utils::String::XOR(std::string(reinterpret_cast(loadLibAStr), sizeof loadLibAStr), -1).data()); - FARPROC loadLibW = GetProcAddress(kernel32, Utils::String::XOR(std::string(reinterpret_cast(loadLibWStr), sizeof loadLibWStr), -1).data()); + if (kernel32) + { + FARPROC loadLibA = GetProcAddress(kernel32, Utils::String::XOR(std::string(reinterpret_cast(loadLibAStr), sizeof loadLibAStr), -1).data()); + FARPROC loadLibW = GetProcAddress(kernel32, Utils::String::XOR(std::string(reinterpret_cast(loadLibWStr), sizeof loadLibWStr), -1).data()); + + if (loadLibA && loadLibW) + { #ifdef DEBUG_LOAD_LIBRARY - AntiCheat::LoadLibHook[0].Initialize(loadLibA, LoadLibaryAStub, HOOK_JUMP); - AntiCheat::LoadLibHook[1].Initialize(loadLibW, LoadLibaryWStub, HOOK_JUMP); + AntiCheat::LoadLibHook[0].Initialize(loadLibA, LoadLibaryAStub, HOOK_JUMP); + AntiCheat::LoadLibHook[1].Initialize(loadLibW, LoadLibaryWStub, HOOK_JUMP); #else - AntiCheat::LoadLibHook[0].Initialize(loadLibA, loadLibStub, HOOK_JUMP); - AntiCheat::LoadLibHook[1].Initialize(loadLibW, loadLibStub, HOOK_JUMP); + AntiCheat::LoadLibHook[0].Initialize(loadLibA, loadLibStub, HOOK_JUMP); + AntiCheat::LoadLibHook[1].Initialize(loadLibW, loadLibStub, HOOK_JUMP); #endif - //AntiCheat::LoadLibHook[2].Initialize(LoadLibraryExA, loadLibExStub, HOOK_JUMP); - //AntiCheat::LoadLibHook[3].Initialize(LoadLibraryExW, loadLibExStub, HOOK_JUMP); + //AntiCheat::LoadLibHook[2].Initialize(LoadLibraryExA, loadLibExStub, HOOK_JUMP); + //AntiCheat::LoadLibHook[3].Initialize(LoadLibraryExW, loadLibExStub, HOOK_JUMP); + } + } } void AntiCheat::ReadIntegrityCheck() @@ -156,7 +163,6 @@ namespace Components if ((Game::Sys_Milliseconds() - lastCheck) > 1000 * 70) { - // TODO: Move that elsewhere if (HANDLE h = OpenProcess(PROCESS_VM_READ, TRUE, GetCurrentProcessId())) { #ifdef DEBUG_DETECTIONS @@ -584,37 +590,35 @@ namespace Components if (NULL != pSecDesc) { HeapFree(GetProcessHeap(), 0, pSecDesc); - pSecDesc = NULL; } if (NULL != pDacl) { HeapFree(GetProcessHeap(), 0, pDacl); - pDacl = NULL; } + if (psidAdmins) { FreeSid(psidAdmins); - psidAdmins = NULL; } + if (psidSystem) { FreeSid(psidSystem); - psidSystem = NULL; } + if (psidEveryone) { FreeSid(psidEveryone); - psidEveryone = NULL; } + if (NULL != pTokenInfo) { HeapFree(GetProcessHeap(), 0, pTokenInfo); - pTokenInfo = NULL; } + if (NULL != hToken) { CloseHandle(hToken); - hToken = NULL; } } @@ -646,7 +650,10 @@ namespace Components Utils::Hook(0x56AC60, AntiCheat::AimTargetGetTagPosStub, HOOK_JUMP).Install()->Quick(); // TODO: Probably move that :P - AntiCheat::InitLoadLibHook(); + if (!Dedicated::IsEnabled()) + { + AntiCheat::InitLoadLibHook(); + } // Prevent external processes from accessing our memory AntiCheat::ProtectProcess(); diff --git a/src/Components/Modules/AssetInterfaces/IGfxImage.cpp b/src/Components/Modules/AssetInterfaces/IGfxImage.cpp index feb08f60..862da050 100644 --- a/src/Components/Modules/AssetInterfaces/IGfxImage.cpp +++ b/src/Components/Modules/AssetInterfaces/IGfxImage.cpp @@ -5,7 +5,7 @@ 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? + if (image) return; image = builder->GetAllocator()->Allocate(); if (!image) diff --git a/src/Components/Modules/AssetInterfaces/IPhysPreset.cpp b/src/Components/Modules/AssetInterfaces/IPhysPreset.cpp index 304cb60b..763cc95e 100644 --- a/src/Components/Modules/AssetInterfaces/IPhysPreset.cpp +++ b/src/Components/Modules/AssetInterfaces/IPhysPreset.cpp @@ -13,8 +13,6 @@ namespace Assets buffer->PushBlock(Game::XFILE_BLOCK_VIRTUAL); - // TODO: I think we have to write them, even if they are NULL - if (asset->name) { buffer->SaveString(builder->GetAssetName(this->GetType(), asset->name)); diff --git a/src/Components/Modules/Auth.cpp b/src/Components/Modules/Auth.cpp index cebec5ec..ed33eff8 100644 --- a/src/Components/Modules/Auth.cpp +++ b/src/Components/Modules/Auth.cpp @@ -391,23 +391,26 @@ namespace Components Logger::Print("Your guid: %llX\n", Steam::SteamUser()->GetSteamID().Bits); }); - Command::Add("securityLevel", [] (Command::Params params) + if (!Dedicated::IsEnabled() && !ZoneBuilder::IsEnabled()) { - if (params.Length() < 2) + Command::Add("securityLevel", [] (Command::Params params) { - 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()); + 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); - } - }); + 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", [] () { diff --git a/src/Components/Modules/Bans.cpp b/src/Components/Modules/Bans.cpp index d1bdd371..3f6cbc23 100644 --- a/src/Components/Modules/Bans.cpp +++ b/src/Components/Modules/Bans.cpp @@ -39,7 +39,7 @@ namespace Components Bans::BanList list; Bans::LoadBans(&list); - Bans::AccessMutex.lock(); + std::lock_guard _(Bans::AccessMutex); if (entry.first.Bits) { @@ -102,15 +102,12 @@ namespace Components FileSystem::FileWriter ban("bans.json"); ban.Write(bans.dump()); - - Bans::AccessMutex.unlock(); } void Bans::LoadBans(Bans::BanList* list) { - Bans::AccessMutex.lock(); + std::lock_guard _(Bans::AccessMutex); - // TODO: Read bans FileSystem::File bans("bans.json"); if (bans.Exists()) @@ -162,8 +159,6 @@ namespace Components } } } - - Bans::AccessMutex.unlock(); } void Bans::BanClientNum(int num, std::string reason) diff --git a/src/Components/Modules/BitMessage.cpp b/src/Components/Modules/BitMessage.cpp new file mode 100644 index 00000000..7a2e6cb4 --- /dev/null +++ b/src/Components/Modules/BitMessage.cpp @@ -0,0 +1,369 @@ +#include "STDInclude.hpp" + +#ifndef DISABLE_BITMESSAGE + +#include + +using namespace Utils; + +namespace Components +{ + BitMRC* BitMessage::BMClient; + + BitMessage::BitMessage() + { +#ifdef DEBUG +Logger::Print("Initializing BitMessage...\n"); +#endif // DEBUG + + + + BitMessage::BMClient = new BitMRC(BITMESSAGE_OBJECT_STORAGE_FILENAME, BITMESSAGE_KEYS_FILENAME); + BitMessage::BMClient->init(); + BitMessage::BMClient->defaultTTL = 1 * 60 * 60; // 1 hour + + if (BitMessage::BMClient->PrivAddresses.empty()) + { + if (!this->InitAddr()) + { + // Generate a random address ready to use + throw std::runtime_error("Failed to prepare source address for exception handling"); + } + + BitMessage::BMClient->save(); + } + + BitMessage::BMClient->start(); + +#ifdef DEBUG + Command::Add("bm_send", [](Command::Params params) + { + if (params.Length() < 3) return; + + ustring pubAddrString; + pubAddrString.fromString(params[1]); + + PubAddr pubAddr; + if (pubAddr.loadAddr(pubAddrString)) + { + ustring msg; + msg.fromString(params.Join(2)); + + Logger::Print("Sending message (this may take a while)...\n"); + BitMessage::BMClient->sendMessage(msg, pubAddr, BitMessage::BMClient->PrivAddresses[0]); + Logger::Print("Message sent.\n"); + } + else + { + Logger::Print("Address not correct!\n"); + } + }); + + Command::Add("bm_sendb", [](Command::Params params) + { + if (params.Length() < 2) return; + + ustring msg; + msg.appendVarString(params.Join(1)); + Logger::Print("Sending broadcast...\n"); + BitMessage::BMClient->sendBroadcast(msg, BitMessage::BMClient->PrivAddresses[0]); + Logger::Print("Broadcast done.\n"); + }); + + Command::Add("bm_check_messages", [](Command::Params) + { + while (BitMessage::BMClient->new_messages.size() > 0) + { + auto msg = BitMessage::BMClient->new_messages.pop(); + Logger::Print("New message:\nFrom: %s\nTo: %s\nMessage:\n%s\n", msg.from.data(), msg.to.data(), msg.info.data()); + } + }); + + Command::Add("bm_check_connections", [](Command::Params) + { + std::shared_lock mlock(BitMessage::BMClient->mutex_nodes); + + for (auto& node : BitMessage::BMClient->Nodes) + { + switch (node->state) { + case 0: // Not connected + Logger::Print("%s: Disconnected\n", node->Ip.data()); + break; + case 1: // Connecting + Logger::Print("%s: Connecting\n", node->Ip.data()); + break; + case 2: // Connected + Logger::Print("%s: Connected\n", node->Ip.data()); + break; + case 3: // Reconnecting + Logger::Print("%s: Reconnecting\n", node->Ip.data()); + break; + } + } + + mlock.unlock(); + }); + + Command::Add("bm_check_privatekey", [](Command::Params) + { + std::shared_lock mlock(BitMessage::BMClient->mutex_priv); + + if (BitMessage::BMClient->PrivAddresses.empty()) + { + Logger::Print("No private key\n"); + } + else + { + for (auto& addr : BitMessage::BMClient->PrivAddresses) + { + Logger::Print("%s\n", addr.getAddress().data()); + } + } + + mlock.unlock(); + }); + + Command::Add("bm_check_publickey", [](Command::Params) + { + std::shared_lock mlock(BitMessage::BMClient->mutex_pub); + + if (BitMessage::BMClient->PubAddresses.empty()) + { + Logger::Print("No public key\n"); + } + else + for (auto& addr : BitMessage::BMClient->PubAddresses) + { + Logger::Print("%s (waiting for public key: %s)\n", addr.getAddress().data(), addr.waitingPubKey() ? "yes" : "no"); + } + + mlock.unlock(); + }); + + Command::Add("bm_save", [](Command::Params) + { + BitMessage::Save(); + }); + + Command::Add("bm_address_public", [](Command::Params params) + { + if (params.Length() < 2) return; + + ustring addre; + addre.fromString(params.Join(1)); + + PubAddr address; + if (address.loadAddr(addre)) + { + Logger::Print("Asking public key!\n"); + BitMessage::BMClient->getPubKey(address); + Logger::Print("Asked! check publickey for news on that address!\n"); + } + else + { + Logger::Print("Address not correct!\n"); + } + }); + + Command::Add("bm_address_broadcast", [](Command::Params params) + { + if (params.Length() < 2) return; + + ustring addre; + addre.fromString(params.Join(1)); + PubAddr address; + if (address.loadAddr(addre)) + { + Logger::Print("Adding subscription!\n"); + BitMessage::BMClient->addSubscription(address); + } + else + { + Logger::Print("Address not correct!\n"); + } + }); +#endif + } + + BitMessage::~BitMessage() + { + delete BitMessage::BMClient; + BitMessage::BMClient = nullptr; + } + + void BitMessage::SetDefaultTTL(time_t ttl) + { + BitMessage::BMClient->defaultTTL = ttl; + } + + bool BitMessage::RequestPublicKey(std::string targetAddress) + { + // Convert to ustring + ustring targetAddressU; + targetAddressU.fromString(targetAddress); + + // Convert to PubAddr + PubAddr pubAddr; + if (!pubAddr.loadAddr(targetAddressU)) + { + return false; + } + + // Request public key! + BitMessage::BMClient->getPubKey(pubAddr); + return true; + } + + PubAddr* BitMessage::FindPublicKey(PubAddr address) + { + std::shared_lock mlock(BitMessage::BMClient->mutex_pub); + + PubAddr* retval = nullptr; + + for (auto& pubKey : BitMessage::BMClient->PubAddresses) + { + if (pubKey.getVersion() == address.getVersion()) //check same version + { + if ((address.getVersion() >= 4 && pubKey.getTag() == address.getTag()) // version 4+ equality check + || (pubKey.getRipe() == address.getRipe())) // version 3- equality check + { + retval = &pubKey; + break; + } + } + } + + mlock.unlock(); + return retval; + } + + bool BitMessage::WaitForPublicKey(std::string targetAddress) + { + // Convert to ustring + ustring targetAddressU; + targetAddressU.fromString(targetAddress); + + // Convert to PubAddr + PubAddr address; + if (!address.loadAddr(targetAddressU)) + { + return false; + } + + // Resolve our own copy to the registered PubAddr copy in BitMRC if possible + auto resolvedAddress = BitMessage::FindPublicKey(address); + if (resolvedAddress != nullptr && + !resolvedAddress->waitingPubKey() && !resolvedAddress->getPubEncryptionKey().empty()) + return true; + + if (resolvedAddress == nullptr || + (!resolvedAddress->waitingPubKey() && resolvedAddress->getPubEncryptionKey().empty())) + { + // Request public key + BitMessage::BMClient->getPubKey(address); + resolvedAddress = BitMessage::FindPublicKey(address); + } + + BitMessage::Save(); + + // TODO: Wait for public key by using signaling in BitMRC, needs to be done directly in the fork. + while (resolvedAddress->waitingPubKey()) + { + std::this_thread::sleep_for(1500ms); + } + + BitMessage::Save(); + + return true; + } + + bool BitMessage::Subscribe(std::string targetAddress) + { + // Convert to ustring + ustring targetAddressU; + targetAddressU.fromString(targetAddress); + + // Convert to PubAddr + PubAddr pubAddr; + if (!pubAddr.loadAddr(targetAddressU)) + { + return false; + } + + // Subscribe! + BitMessage::BMClient->addSubscription(pubAddr); + return true; + } + + bool BitMessage::SendMsg(std::string targetAddress, std::string message, time_t ttl) + { + // Convert target address to ustring + ustring targetAddressU; + targetAddressU.fromString(targetAddress); + + // Convert target address to PubAddr + PubAddr pubAddr; + if (!pubAddr.loadAddr(targetAddressU)) + { + return false; + } + + // Convert message to ustring + ustring messageU; + messageU.fromString(message); + + // Send the message + // TODO - Set mutex on priv when accessing first private address + if (ttl > 0) + { + BitMessage::BMClient->sendMessage(messageU, pubAddr, BitMessage::BMClient->PrivAddresses[0], ttl); + } + else + { + BitMessage::BMClient->sendMessage(messageU, pubAddr, BitMessage::BMClient->PrivAddresses[0]); + } + + return true; + } + + bool BitMessage::SendBroadcast(std::string message, time_t ttl) + { + // Convert message to ustring + ustring messageU; + messageU.fromString(message); + + // TODO - Set mutex on priv when accessing first private address + if (ttl > 0) + { + BitMessage::BMClient->sendBroadcast(messageU, BitMessage::BMClient->PrivAddresses[0], ttl); + } + else + { + BitMessage::BMClient->sendBroadcast(messageU, BitMessage::BMClient->PrivAddresses[0]); + } + + return true; + } + + bool BitMessage::InitAddr() + { +#ifdef DEBUG + Logger::Print("Generating BM address...\n"); +#endif + Addr myAddress; + if (!myAddress.generateRandom()) + { + return false; + } + + BitMessage::BMClient->addAddr(myAddress); + return true; + } + + void BitMessage::Save() + { + BitMessage::BMClient->save(); + } +} + +#endif \ No newline at end of file diff --git a/src/Components/Modules/BitMessage.hpp b/src/Components/Modules/BitMessage.hpp new file mode 100644 index 00000000..fd586ebb --- /dev/null +++ b/src/Components/Modules/BitMessage.hpp @@ -0,0 +1,36 @@ +#pragma once + +#ifndef DISABLE_BITMESSAGE + +#define BITMESSAGE_KEYS_FILENAME std::string("players/bmk.dat") +#define BITMESSAGE_OBJECT_STORAGE_FILENAME std::string("players/storage.dat") + +namespace Components +{ + class BitMessage : public Component + { + public: + BitMessage(); + ~BitMessage(); + +#ifdef DEBUG + const char* GetName() { return "BitMessage"; }; +#endif + + static void SetDefaultTTL(time_t ttl); + static bool RequestPublicKey(std::string targetAddress); + static bool WaitForPublicKey(std::string targetAddress); + static bool Subscribe(std::string targetAddress); + static bool SendMsg(std::string targetAddress, std::string message, time_t ttl = 0); + static bool SendBroadcast(std::string message, time_t ttl = 0); + static void Save(); + + static BitMRC* BMClient; + + private: + static PubAddr* FindPublicKey(PubAddr addr); + static bool InitAddr(); + }; +} + +#endif \ No newline at end of file diff --git a/src/Components/Modules/Command.cpp b/src/Components/Modules/Command.cpp index d510f023..b09b5da1 100644 --- a/src/Components/Modules/Command.cpp +++ b/src/Components/Modules/Command.cpp @@ -46,6 +46,17 @@ namespace Components void Command::AddSV(const char* name, Command::Callback* callback) { + if (Loader::IsPregame()) + { + MessageBoxA(0, "Registering server commands in pregamestate is illegal!", 0, MB_ICONERROR); + +#ifdef DEBUG + __debugbreak(); +#endif + + return; + } + std::string command = Utils::String::ToLower(name); if (Command::FunctionMapSV.find(command) == Command::FunctionMapSV.end()) @@ -59,9 +70,9 @@ namespace Components Command::FunctionMapSV[command] = callback; } - void Command::AddRaw(const char* name, void(*callback)()) + void Command::AddRaw(const char* name, void(*callback)(), bool key) { - Game::Cmd_AddCommand(name, callback, Command::Allocate(), 0); + Game::Cmd_AddCommand(name, callback, Command::Allocate(), key); } void Command::AddRawSV(const char* name, void(*callback)()) @@ -134,6 +145,8 @@ namespace Components Command::Command() { + Assert_Size(Game::cmd_function_t, 24); + // Disable native noclip command Utils::Hook::Nop(0x474846, 5); diff --git a/src/Components/Modules/Command.hpp b/src/Components/Modules/Command.hpp index 7a4401d8..b429ec6c 100644 --- a/src/Components/Modules/Command.hpp +++ b/src/Components/Modules/Command.hpp @@ -34,7 +34,7 @@ namespace Components static void Add(const char* name, Callback* callback); static void AddSV(const char* name, Callback* callback); - static void AddRaw(const char* name, void(*callback)()); + static void AddRaw(const char* name, void(*callback)(), bool key = false); static void AddRawSV(const char* name, void(*callback)()); static void Execute(std::string command, bool sync = true); diff --git a/src/Components/Modules/Console.cpp b/src/Components/Modules/Console.cpp index 5b0da036..55f3fd26 100644 --- a/src/Components/Modules/Console.cpp +++ b/src/Components/Modules/Console.cpp @@ -18,6 +18,7 @@ namespace Components int Console::LineBufferIndex = 0; bool Console::HasConsole = false; + bool Console::SkipShutdown = false; std::thread Console::ConsoleThread; @@ -76,14 +77,14 @@ namespace Components } else if(IsWindow(*reinterpret_cast(0x64A3288)) != FALSE) { - SetWindowTextA(*reinterpret_cast(0x64A3288), Utils::String::VA("IW4x(r" REVISION_STR REVISION_SUFFIX ") : %s", hostname.data())); + SetWindowTextA(*reinterpret_cast(0x64A3288), Utils::String::VA("IW4x(" VERSION ") : %s", hostname.data())); } } void Console::ShowPrompt() { wattron(Console::InputWindow, COLOR_PAIR(10) | A_BOLD); - wprintw(Console::InputWindow, "%s> ", VERSION_STR); + wprintw(Console::InputWindow, "%s> ", VERSION); } void Console::RefreshOutput() @@ -279,11 +280,12 @@ namespace Components raw(); noecho(); - Console::OutputWindow = newpad(OUTPUT_HEIGHT, Console::Width); + Console::OutputWindow = newpad(Console::Height - 1, Console::Width); Console::InputWindow = newwin(1, Console::Width, Console::Height - 1, 0); Console::InfoWindow = newwin(1, Console::Width, 0, 0); scrollok(Console::OutputWindow, true); + idlok(Console::OutputWindow, true); scrollok(Console::InputWindow, true); nodelay(Console::InputWindow, true); keypad(Console::InputWindow, true); @@ -343,11 +345,6 @@ namespace Components const char* p = message; while (*p != '\0') { - if (*p == '\n') - { - Console::ScrollOutput(1); - } - if (*p == '^') { char color; @@ -387,6 +384,7 @@ namespace Components void Console::ConsoleRunner() { + Console::SkipShutdown = false; Game::Sys_ShowConsole(); MSG message; @@ -395,6 +393,8 @@ namespace Components TranslateMessage(&message); DispatchMessageA(&message); } + + if (Console::SkipShutdown) return; if (Game::Sys_Milliseconds() - Console::LastRefresh > 100 && MessageBoxA(0, "The application is not responding anymore, do you want to force its termination?", "Application is not responding", MB_ICONEXCLAMATION | MB_YESNO) == IDYES) @@ -406,11 +406,11 @@ namespace Components // We can not force the termination in this thread // The destructor would be called in this thread // and would try to join this thread, which is impossible - std::async([] () + std::thread([] () { std::this_thread::sleep_for(200ms); ExitProcess(static_cast(-1)); - }); + }).detach(); } else { @@ -477,11 +477,24 @@ namespace Components // Restore the initial safe area *Game::safeArea = Console::OriginalSafeArea; } + + void Console::SetSkipShutdown() + { + Console::SkipShutdown = true; + } + + void Console::FreeNativeConsole() + { + if (Flags::HasFlag("console") || ZoneBuilder::IsEnabled()) + { + FreeConsole(); + } + } Console::Console() { // Console '%s: %s> ' string - Utils::Hook::Set(0x5A44B4, "IW4x: r" REVISION_STR "> "); + Utils::Hook::Set(0x5A44B4, "IW4x: " VERSION "> "); // Internal console Utils::Hook(0x4F690C, Console::ToggleConsole, HOOK_CALL).Install()->Quick(); @@ -522,9 +535,11 @@ namespace Components } else if (Flags::HasFlag("console") || ZoneBuilder::IsEnabled()) // ZoneBuilder uses the game's console, until the native one is adapted. { - FreeConsole(); Utils::Hook::Nop(0x60BB58, 11); + // Redirect input (]command) + Utils::Hook(0x47025A, 0x4F5770, HOOK_CALL).Install()->Quick(); + Utils::Hook(0x60BB68, [] () { Console::ConsoleThread = std::thread(Console::ConsoleRunner); diff --git a/src/Components/Modules/Console.hpp b/src/Components/Modules/Console.hpp index 429e8a74..49d7e1e4 100644 --- a/src/Components/Modules/Console.hpp +++ b/src/Components/Modules/Console.hpp @@ -1,63 +1,67 @@ -#define OUTPUT_HEIGHT 250 -#define OUTPUT_MAX_TOP (OUTPUT_HEIGHT - (Console::Height - 2)) - -namespace Components -{ - class Console : public Component - { - public: - Console(); +#define OUTPUT_HEIGHT 250 +#define OUTPUT_MAX_TOP (OUTPUT_HEIGHT - (Console::Height - 2)) + +namespace Components +{ + class Console : public Component + { + public: + Console(); ~Console(); -#ifdef DEBUG - const char* GetName() { return "Console"; }; -#endif - - private: - static void ToggleConsole(); - static char** GetAutoCompleteFileList(const char *path, const char *extension, Game::FsListBehavior_e behavior, int *numfiles, int allocTrackType); - - private: - // Text-based console stuff - static WINDOW* OutputWindow; - static WINDOW* InputWindow; - static WINDOW* InfoWindow; - - static int Width; - static int Height; - - static int OutputTop; - static int OutBuffer; - static int LastRefresh; - - static char LineBuffer[1024]; - static char LineBuffer2[1024]; - static int LineBufferIndex; - - static bool HasConsole; - - static std::thread ConsoleThread; - - static Game::SafeArea OriginalSafeArea; - - static void ShowPrompt(); - static void RefreshStatus(); - static void RefreshOutput(); - static void ScrollOutput(int amount); - - static const char* Input(); - static void Print(const char* message); - static void Error(const char* format, ...); - static void Create(); - static void Destroy(); - - static void StdOutPrint(const char* message); - static void StdOutError(const char* format, ...); - - static void ConsoleRunner(); - - static void DrawSolidConsoleStub(); - static void StoreSafeArea(); - static void RestoreSafeArea(); - }; -} +#ifdef DEBUG + const char* GetName() { return "Console"; }; +#endif + + static void SetSkipShutdown(); + + static void FreeNativeConsole(); + + private: + // Text-based console stuff + static WINDOW* OutputWindow; + static WINDOW* InputWindow; + static WINDOW* InfoWindow; + + static int Width; + static int Height; + + static int OutputTop; + static int OutBuffer; + static int LastRefresh; + + static char LineBuffer[1024]; + static char LineBuffer2[1024]; + static int LineBufferIndex; + + static bool HasConsole; + static bool SkipShutdown; + + static std::thread ConsoleThread; + + static Game::SafeArea OriginalSafeArea; + + static void ShowPrompt(); + static void RefreshStatus(); + static void RefreshOutput(); + static void ScrollOutput(int amount); + + static const char* Input(); + static void Print(const char* message); + static void Error(const char* format, ...); + static void Create(); + static void Destroy(); + + static void StdOutPrint(const char* message); + static void StdOutError(const char* format, ...); + + static void ConsoleRunner(); + + static void DrawSolidConsoleStub(); + static void StoreSafeArea(); + static void RestoreSafeArea(); + + static void ToggleConsole(); + static char** GetAutoCompleteFileList(const char *path, const char *extension, Game::FsListBehavior_e behavior, int *numfiles, int allocTrackType); + }; +} diff --git a/src/Components/Modules/Download.cpp b/src/Components/Modules/Download.cpp index f8d25832..5e819aeb 100644 --- a/src/Components/Modules/Download.cpp +++ b/src/Components/Modules/Download.cpp @@ -3,6 +3,268 @@ namespace Components { mg_mgr Download::Mgr; + Download::ClientDownload Download::CLDownload; + +#pragma region Client + + void Download::InitiateClientDownload(std::string mod) + { + if (Download::CLDownload.Running) return; + + Localization::SetTemp("MPUI_EST_TIME_LEFT", Utils::String::FormatTimeSpan(0)); + Localization::SetTemp("MPUI_PROGRESS_DL", "(0/0) %"); + Localization::SetTemp("MPUI_TRANS_RATE", "0.0 MB/s"); + + Command::Execute("openmenu mod_download_popmenu", true); + + Download::CLDownload.Running = true; + Download::CLDownload.Mod = mod; + Download::CLDownload.TerminateThread = false; + Download::CLDownload.Target = Party::Target(); + Download::CLDownload.Thread = std::thread(Download::ModDownloader, &Download::CLDownload); + } + + bool Download::ParseModList(ClientDownload* download, std::string list) + { + if (!download) return false; + download->Files.clear(); + + std::string error; + json11::Json listData = json11::Json::parse(list, error); + + + if (!error.empty() || !listData.is_array()) return false; + + download->TotalBytes = 0; + + for (auto& file : listData.array_items()) + { + if (!file.is_object()) return false; + + auto hash = file["hash"]; + auto name = file["name"]; + auto size = file["size"]; + + if (!hash.is_string() || !name.is_string() || !size.is_number()) return false; + + Download::ClientDownload::File fileEntry; + fileEntry.Name = name.string_value(); + fileEntry.Hash = hash.string_value(); + fileEntry.Size = static_cast(size.number_value()); + + if (!fileEntry.Name.empty()) + { + download->Files.push_back(fileEntry); + download->TotalBytes += fileEntry.Size; + } + } + + return true; + } + + void Download::DownloadHandler(mg_connection *nc, int ev, void* ev_data) + { + http_message* hm = reinterpret_cast(ev_data); + Download::FileDownload* fDownload = reinterpret_cast(nc->mgr->user_data); + + if (ev == MG_EV_CONNECT) + { + if (hm->message.p) + { + fDownload->downloading = false; + return; + } + } + + if (ev == MG_EV_RECV) + { + size_t bytes = static_cast(*reinterpret_cast(ev_data)); + fDownload->receivedBytes += bytes; + fDownload->download->DownBytes += bytes; + fDownload->download->TimeStampBytes += bytes; + + double progress = (100.0 / fDownload->download->TotalBytes) * fDownload->download->DownBytes; + Localization::SetTemp("MPUI_PROGRESS_DL", fmt::sprintf("(%d/%d) %d%%", fDownload->index + 1, fDownload->download->Files.size(), static_cast(progress))); + + int delta = Game::Sys_Milliseconds() - fDownload->download->LastTimeStamp; + if (delta > 300) + { + bool doFormat = fDownload->download->LastTimeStamp != 0; + fDownload->download->LastTimeStamp = Game::Sys_Milliseconds(); + + size_t dataLeft = fDownload->download->TotalBytes - fDownload->download->DownBytes; + double timeLeftD = ((1.0 * dataLeft) / fDownload->download->TimeStampBytes) * delta; + int timeLeft = static_cast(timeLeftD); + + if (doFormat) + { + Localization::SetTemp("MPUI_EST_TIME_LEFT", Utils::String::FormatTimeSpan(timeLeft)); + Localization::SetTemp("MPUI_TRANS_RATE", Utils::String::FormatBandwidth(fDownload->download->TimeStampBytes, delta)); + } + + fDownload->download->TimeStampBytes = 0; + } + } + + if (ev == MG_EV_HTTP_REPLY) + { + nc->flags |= MG_F_CLOSE_IMMEDIATELY; + fDownload->buffer = std::string(hm->body.p, hm->body.len); + fDownload->downloading = false; + return; + } + } + + bool Download::DownloadFile(ClientDownload* download, unsigned int index) + { + if (!download || download->Files.size() <= index) return false; + + auto file = download->Files[index]; + + std::string path = download->Mod + "/" + file.Name; + if (Utils::IO::FileExists(path)) + { + std::string data = Utils::IO::ReadFile(path); + + if (data.size() == file.Size && Utils::String::DumpHex(Utils::Cryptography::SHA256::Compute(data), "") == file.Hash) + { + download->TotalBytes += file.Size; + return true; + } + } + + std::string url = "http://" + download->Target.GetString() + "/file/" + file.Name; + + Download::FileDownload fDownload; + fDownload.file = file; + fDownload.index = index; + fDownload.download = download; + fDownload.downloading = true; + fDownload.receivedBytes = 0; + + Utils::String::Replace(url, " ", "%20"); + + download->Valid = true; + mg_mgr_init(&download->Mgr, &fDownload); + mg_connect_http(&download->Mgr, Download::DownloadHandler, url.data(), NULL, NULL); + + while (fDownload.downloading && !fDownload.download->TerminateThread) + { + mg_mgr_poll(&download->Mgr, 0); + } + + mg_mgr_free(&download->Mgr); + download->Valid = false; + + if (fDownload.buffer.size() != file.Size || Utils::String::DumpHex(Utils::Cryptography::SHA256::Compute(fDownload.buffer), "") != file.Hash) + { + return false; + } + + Utils::IO::CreateDirectory(download->Mod); + Utils::IO::WriteFile(path, fDownload.buffer); + + return true; + } + + void Download::ModDownloader(ClientDownload* download) + { + if (!download) download = &Download::CLDownload; + + std::string host = "http://" + download->Target.GetString(); + std::string list = Utils::WebIO("IW4x", host + "/list").SetTimeout(5000)->Get(); + + if (list.empty()) + { + if (download->TerminateThread) return; + + download->Thread.detach(); + download->Clear(); + + QuickPatch::Once([] () + { + Party::ConnectError("Failed to download the modlist!"); + }); + + return; + } + + if (download->TerminateThread) return; + + if (!Download::ParseModList(download, list)) + { + if (download->TerminateThread) return; + + download->Thread.detach(); + download->Clear(); + + QuickPatch::Once([] () + { + Party::ConnectError("Failed to parse the modlist!"); + }); + + return; + } + + if (download->TerminateThread) return; + + static std::string mod = download->Mod; + + for (unsigned int i = 0; i < download->Files.size(); ++i) + { + if (download->TerminateThread) return; + + if (!Download::DownloadFile(download, i)) + { + if (download->TerminateThread) return; + + mod = fmt::sprintf("Failed to download file: %s!", download->Files[i].Name.data()); + download->Thread.detach(); + download->Clear(); + + QuickPatch::Once([] () + { + Dvar::Var("partyend_reason").Set(mod); + mod.clear(); + + Localization::ClearTemp(); + Command::Execute("closemenu mod_download_popmenu"); + Command::Execute("openmenu menu_xboxlive_partyended"); + }); + + return; + } + } + + if (download->TerminateThread) return; + + download->Thread.detach(); + download->Clear(); + + // Run this on the main thread + QuickPatch::Once([] () + { + auto fsGame = Dvar::Var("fs_game"); + fsGame.Set(mod); + fsGame.Get()->modified = true; + + mod.clear(); + + Localization::ClearTemp(); + Command::Execute("closemenu mod_download_popmenu", true); + + if (Dvar::Var("cl_modVidRestart").Get()) + { + Command::Execute("vid_restart", false); + } + + Command::Execute("reconnect", false); + }); + } + +#pragma endregion + +#pragma region Server bool Download::IsClient(mg_connection *nc) { @@ -114,6 +376,8 @@ namespace Components Utils::String::Replace(url, "\\", "/"); url = url.substr(6); + Utils::String::Replace(url, "%20", " "); + if (url.find_first_of("/") != std::string::npos || (!Utils::String::EndsWith(url, ".iwd") && url != "mod.ff") || strstr(url.data(), "_svr_") != NULL) { Download::Forbid(nc); @@ -283,6 +547,8 @@ namespace Components nc->flags |= MG_F_SEND_AND_CLOSE; } +#pragma endregion + Download::Download() { if (Dedicated::IsEnabled()) @@ -308,12 +574,10 @@ namespace Components } else { - Utils::Hook(0x5AC6E9, [] () + UIScript::Add("mod_download_cancel", [] () { - // TODO: Perform moddownload here - - Game::CL_DownloadsComplete(0); - }, HOOK_CALL).Install()->Quick(); + Download::CLDownload.Clear(); + }); } } @@ -325,7 +589,7 @@ namespace Components } else { - + Download::CLDownload.Clear(); } } } diff --git a/src/Components/Modules/Download.hpp b/src/Components/Modules/Download.hpp index ea56ba4a..7a32e4c0 100644 --- a/src/Components/Modules/Download.hpp +++ b/src/Components/Modules/Download.hpp @@ -10,16 +10,90 @@ namespace Components const char* GetName() { return "Download"; }; #endif + static void InitiateClientDownload(std::string mod); + private: + class ClientDownload + { + public: + ClientDownload() : Valid(false), Running(false), TerminateThread(false), TotalBytes(0), DownBytes(0), LastTimeStamp(0), TimeStampBytes(0) {} + ~ClientDownload() { this->Clear(); } + + bool Running; + bool Valid; + bool TerminateThread; + mg_mgr Mgr; + Network::Address Target; + std::string Mod; + std::thread Thread; + + size_t TotalBytes; + size_t DownBytes; + + int LastTimeStamp; + size_t TimeStampBytes; + + class File + { + public: + std::string Name; + std::string Hash; + size_t Size; + }; + + std::vector Files; + + void Clear() + { + this->TerminateThread = true; + + if (this->Thread.joinable()) + { + this->Thread.join(); + } + + this->Running = false; + this->Mod.clear(); + this->Files.clear(); + + if (this->Valid) + { + this->Valid = false; + mg_mgr_free(&(this->Mgr)); + } + } + }; + + class FileDownload + { + public: + ClientDownload* download; + ClientDownload::File file; + + int timestamp; + bool downloading; + unsigned int index; + std::string buffer; + size_t receivedBytes; + + + }; + static mg_mgr Mgr; + static ClientDownload CLDownload; static void EventHandler(mg_connection *nc, int ev, void *ev_data); static void ListHandler(mg_connection *nc, int ev, void *ev_data); static void FileHandler(mg_connection *nc, int ev, void *ev_data); static void InfoHandler(mg_connection *nc, int ev, void *ev_data); + static void DownloadHandler(mg_connection *nc, int ev, void *ev_data); static bool IsClient(mg_connection *nc); static Game::client_t* GetClient(mg_connection *nc); static void Forbid(mg_connection *nc); + + static void ModDownloader(ClientDownload* download); + static bool ParseModList(ClientDownload* download, std::string list); + static bool DownloadFile(ClientDownload* download, unsigned int index); }; } diff --git a/src/Components/Modules/Exception.cpp b/src/Components/Modules/Exception.cpp index bab77313..1822e1d7 100644 --- a/src/Components/Modules/Exception.cpp +++ b/src/Components/Modules/Exception.cpp @@ -9,92 +9,60 @@ namespace Components { - bool Exception::UploadMinidump(std::string filename) - { - if (Utils::IO::FileExists(filename)) - { - Utils::WebIO webio("Firefucks", "https://reich.io/upload.php"); - - std::string buffer = Utils::IO::ReadFile(filename); - std::string result = webio.PostFile("minidump.dmp", "files[]", buffer); - - std::string errors; - json11::Json object = json11::Json::parse(result, errors); - - if (!object.is_object()) return false; - - json11::Json success = object["success"]; - - if (!success.is_bool() || !success.bool_value()) return false; - - json11::Json files = object["files"]; - - if (!files.is_array()) return false; - - for (auto file : files.array_items()) - { - json11::Json url = file["url"]; - json11::Json hash = file["hash"]; - - if (hash.is_string() && url.is_string()) - { - if (Utils::String::ToLower(Utils::Cryptography::SHA1::Compute(buffer, true)) == Utils::String::ToLower(hash.string_value())) - { - MessageBoxA(0, url.string_value().data(), 0, 0); - return true; - } - } - } - } - - return false; - } + Utils::Hook Exception::SetFilterHook; LONG WINAPI Exception::ExceptionFilter(LPEXCEPTION_POINTERS ExceptionInfo) { - char filename[MAX_PATH]; - __time64_t time; - tm ltime; - - _time64(&time); - _localtime64_s(<ime, &time); - strftime(filename, sizeof(filename) - 1, "iw4x-" VERSION_STR "-%Y%m%d%H%M%S.dmp", <ime); - - HANDLE hFile = CreateFileA(filename, GENERIC_WRITE, FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); - - if (hFile && hFile != INVALID_HANDLE_VALUE) + // Pass on harmless errors + if (ExceptionInfo->ExceptionRecord->ExceptionCode == STATUS_INTEGER_OVERFLOW || + ExceptionInfo->ExceptionRecord->ExceptionCode == STATUS_FLOAT_OVERFLOW) { - MINIDUMP_EXCEPTION_INFORMATION ex = { GetCurrentThreadId(), ExceptionInfo, FALSE }; - MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), hFile, MiniDumpNormal, &ex, NULL, NULL); - CloseHandle(hFile); + return EXCEPTION_CONTINUE_EXECUTION; } - //Exception::UploadMinidump(filename); + auto minidump = MinidumpUpload::CreateQueuedMinidump(ExceptionInfo); + if (!minidump) + { + OutputDebugStringA("Failed to create new minidump!"); + Utils::OutputDebugLastError(); + } + else + { + delete minidump; + } 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); } + //TerminateProcess(GetCurrentProcess(), ExceptionInfo->ExceptionRecord->ExceptionCode); + return EXCEPTION_CONTINUE_SEARCH; } - LPTOP_LEVEL_EXCEPTION_FILTER WINAPI Exception::SetUnhandledExceptionFilterStub(LPTOP_LEVEL_EXCEPTION_FILTER lpTopLevelExceptionFilter) + LPTOP_LEVEL_EXCEPTION_FILTER WINAPI Exception::SetUnhandledExceptionFilterStub(LPTOP_LEVEL_EXCEPTION_FILTER) { - SetUnhandledExceptionFilter(&Exception::ExceptionFilter); - return lpTopLevelExceptionFilter; + Exception::SetFilterHook.Uninstall(); + LPTOP_LEVEL_EXCEPTION_FILTER retval = SetUnhandledExceptionFilter(&Exception::ExceptionFilter); + Exception::SetFilterHook.Install(); + return retval; + } + + LPTOP_LEVEL_EXCEPTION_FILTER Exception::Hook() + { + return SetUnhandledExceptionFilter(&Exception::ExceptionFilter); } Exception::Exception() { #ifdef DEBUG // Display DEBUG branding, so we know we're on a debug build - Renderer::OnFrame([] () + Renderer::OnFrame([]() { Game::Font* font = Game::R_RegisterFont("fonts/normalFont"); float color[4] = { 1.0f, 1.0f, 1.0f, 1.0f }; @@ -109,18 +77,21 @@ namespace Components 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); +#endif +#if !defined(DEBUG) || defined(FORCE_EXCEPTION_HANDLER) + Exception::SetFilterHook.Initialize(SetUnhandledExceptionFilter, Exception::SetUnhandledExceptionFilterStub, HOOK_JUMP); + Exception::SetFilterHook.Install(); + SetUnhandledExceptionFilter(&Exception::ExceptionFilter); #endif - Command::Add("mapTest", [] (Command::Params params) + Command::Add("mapTest", [](Command::Params params) { std::string command; int max = (params.Length() >= 2 ? atoi(params[1]) : 16), current = 0; - for (int i =0;;) + for (int i = 0;;) { char* mapname = Game::mapnames[i]; if (!*mapname) @@ -129,7 +100,7 @@ namespace Components continue; } - if(!(i % 2)) command.append(fmt::sprintf("wait 250;disconnect;wait 750;", mapname)); // Test a disconnect + 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)); @@ -140,5 +111,63 @@ namespace Components Command::Execute(command, false); }); + Command::Add("debug_exceptionhandler", [](Command::Params) + { + Logger::Print("Rerunning SetUnhandledExceptionHandler...\n"); + auto oldHandler = Exception::Hook(); + Logger::Print("Old exception handler was 0x%010X.\n", oldHandler); + }); + +#pragma warning(push) +#pragma warning(disable:4740) // flow in or out of inline asm code suppresses global optimization + Command::Add("debug_minidump", [](Command::Params) + { + // The following code was taken from VC++ 8.0 CRT (invarg.c: line 104) + + CONTEXT ContextRecord; + EXCEPTION_RECORD ExceptionRecord; + ZeroMemory(&ContextRecord, sizeof(CONTEXT)); + + __asm + { + mov [ContextRecord.Eax], eax + mov [ContextRecord.Ecx], ecx + mov [ContextRecord.Edx], edx + mov [ContextRecord.Ebx], ebx + mov [ContextRecord.Esi], esi + mov [ContextRecord.Edi], edi + mov word ptr [ContextRecord.SegSs], ss + mov word ptr [ContextRecord.SegCs], cs + mov word ptr [ContextRecord.SegDs], ds + mov word ptr [ContextRecord.SegEs], es + mov word ptr [ContextRecord.SegFs], fs + mov word ptr [ContextRecord.SegGs], gs + + pushfd + pop [ContextRecord.EFlags] + } + + ContextRecord.ContextFlags = CONTEXT_CONTROL; + ContextRecord.Eip = reinterpret_cast(_ReturnAddress()); + ContextRecord.Esp = reinterpret_cast(_AddressOfReturnAddress()); + ContextRecord.Ebp = *reinterpret_cast(_AddressOfReturnAddress()) - 1; + + ZeroMemory(&ExceptionRecord, sizeof(EXCEPTION_RECORD)); + + ExceptionRecord.ExceptionCode = EXCEPTION_BREAKPOINT; + ExceptionRecord.ExceptionAddress = _ReturnAddress(); + + EXCEPTION_POINTERS eptr; + eptr.ExceptionRecord = &ExceptionRecord; + eptr.ContextRecord = &ContextRecord; + + Exception::ExceptionFilter(&eptr); + }); +#pragma warning(pop) + } + + Exception::~Exception() + { + Exception::SetFilterHook.Uninstall(); } } diff --git a/src/Components/Modules/Exception.hpp b/src/Components/Modules/Exception.hpp index 8e70ffea..9418fe13 100644 --- a/src/Components/Modules/Exception.hpp +++ b/src/Components/Modules/Exception.hpp @@ -1,18 +1,22 @@ + + namespace Components { class Exception : public Component { public: Exception(); + ~Exception(); #ifdef DEBUG - const char* GetName() { return "Exception"; }; + const char* GetName() { return "Exception"; }; #endif + static LPTOP_LEVEL_EXCEPTION_FILTER Hook(); private: static LONG WINAPI ExceptionFilter(LPEXCEPTION_POINTERS ExceptionInfo); static LPTOP_LEVEL_EXCEPTION_FILTER WINAPI SetUnhandledExceptionFilterStub(LPTOP_LEVEL_EXCEPTION_FILTER lpTopLevelExceptionFilter); - static bool UploadMinidump(std::string filename); + static Utils::Hook SetFilterHook; }; } diff --git a/src/Components/Modules/Lean.cpp b/src/Components/Modules/Lean.cpp index a0830b3e..7d794284 100644 --- a/src/Components/Modules/Lean.cpp +++ b/src/Components/Modules/Lean.cpp @@ -58,11 +58,11 @@ namespace Components Lean::Lean() { - Game::Cmd_AddCommand("+leanleft", Lean::IN_LeanLeft_Down, Command::Allocate(), 1); - Game::Cmd_AddCommand("-leanleft", Lean::IN_LeanLeft_Up, Command::Allocate(), 1); + Command::AddRaw("+leanleft", Lean::IN_LeanLeft_Down, true); + Command::AddRaw("-leanleft", Lean::IN_LeanLeft_Up, true); - Game::Cmd_AddCommand("+leanright", Lean::IN_LeanRight_Down, Command::Allocate(), 1); - Game::Cmd_AddCommand("-leanright", Lean::IN_LeanRight_Up, Command::Allocate(), 1); + Command::AddRaw("+leanright", Lean::IN_LeanRight_Down, true); + Command::AddRaw("-leanright", Lean::IN_LeanRight_Up, true); Utils::Hook(0x5A6D84, Lean::CL_CmdButtonsStub, HOOK_CALL).Install()->Quick(); } diff --git a/src/Components/Modules/Localization.cpp b/src/Components/Modules/Localization.cpp index f9d9115d..52361182 100644 --- a/src/Components/Modules/Localization.cpp +++ b/src/Components/Modules/Localization.cpp @@ -2,6 +2,7 @@ namespace Components { + std::mutex Localization::LocalizeMutex; Dvar::Var Localization::UseLocalization; Utils::Memory::Allocator Localization::MemAllocator; std::map Localization::LocalizeMap; @@ -9,6 +10,8 @@ namespace Components void Localization::Set(std::string key, std::string value) { + std::lock_guard _(Localization::LocalizeMutex); + if (Localization::LocalizeMap.find(key) != Localization::LocalizeMap.end()) { Game::LocalizedEntry* entry = Localization::LocalizeMap[key]; @@ -46,6 +49,7 @@ namespace Components if (!Localization::UseLocalization.Get()) return key; Game::LocalizedEntry* entry = nullptr; + std::lock_guard _(Localization::LocalizeMutex); if (Localization::TempLocalizeMap.find(key) != Localization::TempLocalizeMap.end()) { @@ -56,7 +60,12 @@ namespace Components entry = Localization::LocalizeMap[key]; } - if (!entry || !entry->value) entry = Game::DB_FindXAssetHeader(Game::XAssetType::ASSET_TYPE_LOCALIZE, key).localize; + if (!entry || !entry->value) + { + Localization::LocalizeMutex.unlock(); + entry = Game::DB_FindXAssetHeader(Game::XAssetType::ASSET_TYPE_LOCALIZE, key).localize; + Localization::LocalizeMutex.lock(); + } if (entry && entry->value) { @@ -68,6 +77,8 @@ namespace Components void Localization::SetTemp(std::string key, std::string value) { + std::lock_guard _(Localization::LocalizeMutex); + if (Localization::TempLocalizeMap.find(key) != Localization::TempLocalizeMap.end()) { Game::LocalizedEntry* entry = Localization::TempLocalizeMap[key]; @@ -100,6 +111,8 @@ namespace Components void Localization::ClearTemp() { + std::lock_guard _(Localization::LocalizeMutex); + for (auto i = Localization::TempLocalizeMap.begin(); i != Localization::TempLocalizeMap.end(); ++i) { if (i->second) @@ -131,6 +144,7 @@ namespace Components AssetHandler::OnFind(Game::XAssetType::ASSET_TYPE_LOCALIZE, [] (Game::XAssetType, std::string filename) { Game::XAssetHeader header = { 0 }; + std::lock_guard _(Localization::LocalizeMutex); if (Localization::TempLocalizeMap.find(filename) != Localization::TempLocalizeMap.end()) { diff --git a/src/Components/Modules/Localization.hpp b/src/Components/Modules/Localization.hpp index 3880f048..a7a27bba 100644 --- a/src/Components/Modules/Localization.hpp +++ b/src/Components/Modules/Localization.hpp @@ -17,6 +17,7 @@ namespace Components static void ClearTemp(); private: + static std::mutex LocalizeMutex; static Utils::Memory::Allocator MemAllocator; static std::map LocalizeMap; static std::map TempLocalizeMap; diff --git a/src/Components/Modules/Logger.cpp b/src/Components/Modules/Logger.cpp index c2b4d5a8..7b0a66a3 100644 --- a/src/Components/Modules/Logger.cpp +++ b/src/Components/Modules/Logger.cpp @@ -4,6 +4,7 @@ namespace Components { std::mutex Logger::MessageMutex; std::vector Logger::MessageQueue; + std::vector Logger::LoggingAddresses[2]; void(*Logger::PipeCallback)(std::string) = nullptr; bool Logger::IsConsoleReady() @@ -79,7 +80,7 @@ namespace Components void Logger::Frame() { - Logger::MessageMutex.lock(); + std::lock_guard _(Logger::MessageMutex); for (unsigned int i = 0; i < Logger::MessageQueue.size(); ++i) { @@ -92,7 +93,6 @@ namespace Components } Logger::MessageQueue.clear(); - Logger::MessageMutex.unlock(); } void Logger::PipeOutput(void(*callback)(std::string)) @@ -108,6 +108,31 @@ namespace Components } } + void Logger::NetworkLog(const char* data, bool gLog) + { + if (!data) return; + + std::string buffer(data); + for (auto& addr : Logger::LoggingAddresses[gLog & 1]) + { + Network::SendCommand(addr, "print", buffer); + } + } + + __declspec(naked) void Logger::GameLogStub() + { + __asm + { + push 1 + push [esp + 8h] + call Logger::NetworkLog + add esp, 8h + + mov eax, 4576C0h + jmp eax + } + } + __declspec(naked) void Logger::PrintMessageStub() { __asm @@ -122,6 +147,11 @@ namespace Components retn returnPrint: + push 0 + push [esp + 0Ch] + call Logger::NetworkLog + add esp, 8h + push esi mov esi, [esp + 0Ch] @@ -143,11 +173,120 @@ namespace Components QuickPatch::OnFrame(Logger::Frame); + Utils::Hook(0x4B0218, Logger::GameLogStub, HOOK_CALL).Install()->Quick(); Utils::Hook(Game::Com_PrintMessage, Logger::PrintMessageStub, HOOK_JUMP).Install()->Quick(); + + Dvar::OnInit([] () + { + Command::AddSV("log_add", [] (Command::Params params) + { + if (params.Length() < 2) return; + + Network::Address addr(params[1]); + + if (std::find(Logger::LoggingAddresses[0].begin(), Logger::LoggingAddresses[0].end(), addr) == Logger::LoggingAddresses[0].end()) + { + Logger::LoggingAddresses[0].push_back(addr); + } + }); + + Command::AddSV("log_del", [] (Command::Params params) + { + if (params.Length() < 2) return; + + int num = atoi(params[1]); + if (fmt::sprintf("%i", num) == params[1] && static_cast(num) < Logger::LoggingAddresses[0].size()) + { + auto addr = Logger::LoggingAddresses[0].begin() + num; + Logger::Print("Address %s removed\n", addr->GetCString()); + Logger::LoggingAddresses[0].erase(addr); + } + else + { + Network::Address addr(params[1]); + + auto i = std::find(Logger::LoggingAddresses[0].begin(), Logger::LoggingAddresses[0].end(), addr); + if (i != Logger::LoggingAddresses[0].end()) + { + Logger::LoggingAddresses[0].erase(i); + Logger::Print("Address %s removed\n", addr.GetCString()); + } + else + { + Logger::Print("Address %s not found!\n", addr.GetCString()); + } + } + }); + + Command::AddSV("log_list", [] (Command::Params) + { + Logger::Print("# ID: Address\n"); + Logger::Print("-------------\n"); + + for (unsigned int i = 0; i < Logger::LoggingAddresses[0].size(); ++i) + { + Logger::Print("#%03d: %5s\n", i, Logger::LoggingAddresses[0][i].GetCString()); + } + }); + + Command::AddSV("g_log_add", [] (Command::Params params) + { + if (params.Length() < 2) return; + + Network::Address addr(params[1]); + + if (std::find(Logger::LoggingAddresses[1].begin(), Logger::LoggingAddresses[1].end(), addr) == Logger::LoggingAddresses[1].end()) + { + Logger::LoggingAddresses[1].push_back(addr); + } + }); + + Command::AddSV("g_log_del", [] (Command::Params params) + { + if (params.Length() < 2) return; + + int num = atoi(params[1]); + if (fmt::sprintf("%i", num) == params[1] && static_cast(num) < Logger::LoggingAddresses[1].size()) + { + auto addr = Logger::LoggingAddresses[1].begin() + num; + Logger::Print("Address %s removed\n", addr->GetCString()); + Logger::LoggingAddresses[1].erase(addr); + } + else + { + Network::Address addr(params[1]); + + auto i = std::find(Logger::LoggingAddresses[1].begin(), Logger::LoggingAddresses[1].end(), addr); + if (i != Logger::LoggingAddresses[1].end()) + { + Logger::LoggingAddresses[1].erase(i); + Logger::Print("Address %s removed\n", addr.GetCString()); + } + else + { + Logger::Print("Address %s not found!\n", addr.GetCString()); + } + } + }); + + Command::AddSV("g_log_list", [] (Command::Params) + { + Logger::Print("# ID: Address\n"); + Logger::Print("-------------\n"); + + for (unsigned int i = 0; i < Logger::LoggingAddresses[1].size(); ++i) + { + Logger::Print("#%03d: %5s\n", i, Logger::LoggingAddresses[1][i].GetCString()); + } + }); + }); } Logger::~Logger() { + Logger::LoggingAddresses[0].clear(); + Logger::LoggingAddresses[1].clear(); + Logger::MessageMutex.lock(); Logger::MessageQueue.clear(); Logger::MessageMutex.unlock(); diff --git a/src/Components/Modules/Logger.hpp b/src/Components/Modules/Logger.hpp index 504038e4..8597cddc 100644 --- a/src/Components/Modules/Logger.hpp +++ b/src/Components/Modules/Logger.hpp @@ -24,13 +24,17 @@ namespace Components private: static std::mutex MessageMutex; static std::vector MessageQueue; + static std::vector LoggingAddresses[2]; static void(*PipeCallback)(std::string); static void Frame(); + static void GameLogStub(); static void PrintMessageStub(); static void PrintMessagePipe(const char* data); static void EnqueueMessage(std::string message); + static void NetworkLog(const char* data, bool gLog); + static std::string Format(const char** message); }; } diff --git a/src/Components/Modules/Materials.cpp b/src/Components/Modules/Materials.cpp index dd9ca412..f257f8d5 100644 --- a/src/Components/Modules/Materials.cpp +++ b/src/Components/Modules/Materials.cpp @@ -2,6 +2,7 @@ namespace Components { + int Materials::ImageNameLength; Utils::Hook Materials::ImageVersionCheckHook; __declspec(naked) void Materials::ImageVersionCheck() @@ -20,22 +21,41 @@ namespace Components } } - Game::Material* Materials::VerifyMaterial(Game::Material* material) + Game::Material* Materials::ResolveMaterial(const char* stringPtr) { - if (!IsBadReadPtr(material, 4) && !IsBadReadPtr(material->name, 1)) + const char* imagePtr = stringPtr + 4; + unsigned int length = static_cast(stringPtr[3] & 0xFF); + + if (strlen(imagePtr) >= length) { - return material; + Materials::ImageNameLength = 4 + length; + std::string image(imagePtr, length); + + return Game::DB_FindXAssetHeader(Game::XAssetType::ASSET_TYPE_MATERIAL, image.data()).material; } + Materials::ImageNameLength = 4; return Game::DB_FindXAssetHeader(Game::XAssetType::ASSET_TYPE_MATERIAL, "default").material; } + __declspec(naked) void Materials::PostDrawMaterialStub() + { + __asm + { + mov eax, Materials::ImageNameLength + add [esp + 30h], eax + + mov eax, 5358FFh + jmp eax + } + } + __declspec(naked) void Materials::DrawMaterialStub() { __asm { - push eax - call Materials::VerifyMaterial + push ecx + call Materials::ResolveMaterial add esp, 4h mov edx, 5310F0h @@ -43,13 +63,59 @@ namespace Components } } + int Materials::WriteDeathMessageIcon(char* string, int offset, Game::Material* material) + { + if (!material) + { + material = Game::DB_FindXAssetHeader(Game::XAssetType::ASSET_TYPE_MATERIAL, "default").material; + } + + int length = strlen(material->name); + string[offset++] = static_cast(length); + + strncpy_s(string + offset, 1024 - offset, material->name, length); + + return offset + length; + } + + __declspec(naked) void Materials::DeathMessageStub() + { + __asm + { + push edx // Material + push eax // offset + push ecx // String + + call Materials::WriteDeathMessageIcon + + add esp, 14h + retn + } + } + Materials::Materials() { + Materials::ImageNameLength = 7; + // Allow codo images Materials::ImageVersionCheckHook.Initialize(0x53A456, Materials::ImageVersionCheck, HOOK_CALL)->Install(); // Fix material pointer exploit Utils::Hook(0x534E0C, Materials::DrawMaterialStub, HOOK_CALL).Install()->Quick(); + + // Increment string pointer accordingly + Utils::Hook(0x5358FA, Materials::PostDrawMaterialStub, HOOK_JUMP).Install()->Quick(); + + // Adapt death message to IW5 material format + Utils::Hook(0x5A30D9, Materials::DeathMessageStub, HOOK_JUMP).Install()->Quick(); + +// Renderer::OnFrame([] () +// { +// Game::Font* font = Game::R_RegisterFont("fonts/normalFont"); +// float color[4] = { 1.0f, 1.0f, 1.0f, 1.0f }; +// +// Game::R_AddCmdDrawText("test^==preview_mp_rustzob", 0x7FFFFFFF, font, 500.0f, 150.0f, 1.0f, 1.0f, 0.0f, color, Game::ITEM_TEXTSTYLE_SHADOWED); +// }); } Materials::~Materials() diff --git a/src/Components/Modules/Materials.hpp b/src/Components/Modules/Materials.hpp index 34bbb505..2066130f 100644 --- a/src/Components/Modules/Materials.hpp +++ b/src/Components/Modules/Materials.hpp @@ -11,10 +11,16 @@ namespace Components #endif private: + static int ImageNameLength; + static Utils::Hook ImageVersionCheckHook; static void ImageVersionCheck(); - static Game::Material* VerifyMaterial(Game::Material* material); + static Game::Material* ResolveMaterial(const char* stringPtr); static void DrawMaterialStub(); + static void PostDrawMaterialStub(); + + static int WriteDeathMessageIcon(char* string, int offset, Game::Material* material); + static void DeathMessageStub(); }; } diff --git a/src/Components/Modules/Menus.cpp b/src/Components/Modules/Menus.cpp index db2b2a1b..6259bc30 100644 --- a/src/Components/Modules/Menus.cpp +++ b/src/Components/Modules/Menus.cpp @@ -674,6 +674,7 @@ namespace Components Menus::Add("ui_mp/stats_reset.menu"); Menus::Add("ui_mp/stats_unlock.menu"); Menus::Add("ui_mp/security_increase_popmenu.menu"); + Menus::Add("ui_mp/mod_download_popmenu.menu"); } Menus::~Menus() diff --git a/src/Components/Modules/MinidumpUpload.cpp b/src/Components/Modules/MinidumpUpload.cpp new file mode 100644 index 00000000..89da69a8 --- /dev/null +++ b/src/Components/Modules/MinidumpUpload.cpp @@ -0,0 +1,392 @@ +#include "STDInclude.hpp" +#include "Shlwapi.h" + +const int MiniDumpTiny = MiniDumpFilterMemory | MiniDumpWithoutAuxiliaryState | MiniDumpWithoutOptionalData | MiniDumpFilterModulePaths | MiniDumpIgnoreInaccessibleMemory; + +namespace Components +{ + +#pragma region Minidump class implementation + Minidump::Minidump() + { + this->fileHandle = this->mapFileHandle = INVALID_HANDLE_VALUE; + } + + Minidump::~Minidump() + { + if (this->mapFileHandle != NULL && this->mapFileHandle != INVALID_HANDLE_VALUE) + { + CloseHandle(this->mapFileHandle); + this->mapFileHandle = INVALID_HANDLE_VALUE; + } + + if (this->fileHandle != NULL && this->fileHandle != INVALID_HANDLE_VALUE) + { + CloseHandle(this->fileHandle); + this->fileHandle = INVALID_HANDLE_VALUE; + } + } + + std::string Minidump::ToString() + { + if (!this->EnsureFileMapping()) return false; + + auto pBuf = MapViewOfFile(this->mapFileHandle, FILE_MAP_READ, 0, 0, 0); + if (!pBuf) + { + Utils::OutputDebugLastError(); + throw new std::runtime_error("Could not read minidump."); + } + + size_t fileSize; + DWORD fileSizeHi; + fileSize = GetFileSize(this->fileHandle, &fileSizeHi); +#ifdef _WIN64 + fileSize |= ((size_t)fileSizeHi << 32); +#endif + std::string retval = std::string((const char*)pBuf, fileSize * sizeof(const char)); + UnmapViewOfFile(pBuf); + return retval; + } + + bool Minidump::GetStream(MINIDUMP_STREAM_TYPE type, PMINIDUMP_DIRECTORY* directoryPtr, PVOID* streamBeginningPtr, ULONG* streamSizePtr) + { + if (!this->EnsureFileMapping()) return false; + + auto pBuf = MapViewOfFile(this->mapFileHandle, FILE_MAP_READ, 0, 0, 0); + if (!pBuf) + { + Utils::OutputDebugLastError(); + throw new std::runtime_error("Could not read minidump."); + } + + BOOL success = MiniDumpReadDumpStream(pBuf, type, directoryPtr, streamBeginningPtr, streamSizePtr); + UnmapViewOfFile(pBuf); + if (success != TRUE) + return false; + + return true; + } + + inline bool Minidump::Check() + { + /*PMINIDUMP_DIRECTORY directory; + PVOID stream; + ULONG streamSize; + return Minidump::GetStream(ExceptionStream, &directory, &stream, &streamSize);*/ + return Minidump::GetStream(ExceptionStream, NULL, NULL, NULL); + } + + Minidump* Minidump::Create(std::string path, LPEXCEPTION_POINTERS exceptionInfo, int type) + { + Minidump* minidump = Minidump::Initialize(path); + if (!minidump) return minidump; + + // Do the dump generation + MINIDUMP_EXCEPTION_INFORMATION ex = { GetCurrentThreadId(), exceptionInfo, FALSE }; + if (!MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), minidump->fileHandle, (MINIDUMP_TYPE)type, &ex, NULL, NULL)) + { + Utils::OutputDebugLastError(); + delete minidump; + return nullptr; + } + + if (SetFilePointer(minidump->fileHandle, 0, NULL, FILE_BEGIN) == INVALID_SET_FILE_POINTER) + { + Utils::OutputDebugLastError(); + delete minidump; + DeleteFileA(path.data()); + return nullptr; + } + + return minidump; + } + + Minidump* Minidump::Open(std::string path) + { + return Minidump::Initialize(path, FILE_SHARE_READ); + } + + bool Minidump::EnsureFileMapping() + { + if (this->mapFileHandle == NULL || this->mapFileHandle == INVALID_HANDLE_VALUE) + { + this->mapFileHandle = CreateFileMappingA(this->fileHandle, NULL, PAGE_READONLY, 0, 0, NULL); + if (this->mapFileHandle == NULL || this->mapFileHandle == INVALID_HANDLE_VALUE) + { + Utils::OutputDebugLastError(); + return false; + } + } + return true; + } + + Minidump* Minidump::Initialize(std::string path, DWORD fileShare) + { + Minidump* minidump = new Minidump(); + + minidump->fileHandle = CreateFileA(path.data(), + GENERIC_WRITE | GENERIC_READ, fileShare, + NULL, (fileShare & FILE_SHARE_WRITE) > 0 ? OPEN_ALWAYS : OPEN_EXISTING, NULL, NULL); + + if (minidump->fileHandle == NULL || minidump->fileHandle == INVALID_HANDLE_VALUE) + { + Utils::OutputDebugLastError(); + delete minidump; + return nullptr; + } + + return minidump; + } +#pragma endregion + +#pragma region Minidump uploader class implementation + const std::string MinidumpUpload::queuedMinidumpsFolder = "minidumps\\"; + +#ifdef DISABLE_BITMESSAGE + const std::vector MinidumpUpload::targetUrls = + { + "https://reich.io/upload.php", + "https://hitlers.kz/upload.php" + }; +#else + const std::string MinidumpUpload::targetAddress = "BM-2cSksR7gyyFcNK7MaFoxGCjRJWxtoGckdj"; + const unsigned int MinidumpUpload::maxSegmentSize = 200 * 1024; // 200 kB +#endif + + MinidumpUpload::MinidumpUpload() + { +#if !defined(DEBUG) || defined(FORCE_MINIDUMP_UPLOAD) + this->uploadThread = std::thread([&]() { this->UploadQueuedMinidumps(); }); +#endif + } + + MinidumpUpload::~MinidumpUpload() + { + if (this->uploadThread.joinable()) + { + this->uploadThread.join(); + } + } + + bool MinidumpUpload::EnsureQueuedMinidumpsFolderExists() + { + BOOL success = CreateDirectoryA(MinidumpUpload::queuedMinidumpsFolder.data(), NULL); + + if (success != TRUE) + { + success = (GetLastError() == ERROR_ALREADY_EXISTS); + } + + return (success == TRUE); + } + + Minidump* MinidumpUpload::CreateQueuedMinidump(LPEXCEPTION_POINTERS exceptionInfo, int minidumpType) + { + // Note that most of the Path* functions are DEPRECATED and they have been replaced by Cch variants that only work on Windows 8+. + // If you plan to drop support for Windows 7, please upgrade these calls to prevent accidental buffer overflows! + + if (!EnsureQueuedMinidumpsFolderExists()) return NULL; + + // Current executable name + char exeFileName[MAX_PATH]; + GetModuleFileNameA(NULL, exeFileName, MAX_PATH); + PathStripPathA(exeFileName); + PathRemoveExtensionA(exeFileName); + + // Generate filename + char filenameFriendlyTime[MAX_PATH]; + __time64_t time; + tm ltime; + _time64(&time); + _localtime64_s(<ime, &time); + strftime(filenameFriendlyTime, sizeof(filenameFriendlyTime) - 1, "%Y%m%d%H%M%S", <ime); + + // Combine with queuedMinidumpsFolder + char filename[MAX_PATH]; + PathCombineA(filename, MinidumpUpload::queuedMinidumpsFolder.data(), Utils::String::VA("%s-" VERSION "-%s.dmp", exeFileName, filenameFriendlyTime)); + + // Generate the dump + return Minidump::Create(filename, exceptionInfo, minidumpType); + } + + bool MinidumpUpload::UploadQueuedMinidumps() + { +#ifndef DISABLE_BITMESSAGE + // Preload public key for our target that will receive minidumps + Logger::Print("About to send request for public key for minidump upload address.\n"); + if (!BitMessage::RequestPublicKey(MinidumpUpload::targetAddress)) + { + Logger::Error("Failed to request public key for minidump collection address.\n"); + } + Logger::Print("Waiting for public key for minidump upload address.\n"); + if (!BitMessage::WaitForPublicKey(MinidumpUpload::targetAddress)) + { + Logger::Error("Failed to fetch public key for minidump collection address.\n"); + } +#endif + + // Check if folder exists + if (!PathIsDirectoryA(MinidumpUpload::queuedMinidumpsFolder.data())) + { + // Nothing to upload + Logger::Print("No minidumps to upload.\n"); + return PathFileExistsA(MinidumpUpload::queuedMinidumpsFolder.data()) == FALSE; + } + + // Walk through directory and search for valid minidumps + WIN32_FIND_DATAA ffd; + HANDLE hFind = FindFirstFileA(Utils::String::VA("%s\\*.dmp", MinidumpUpload::queuedMinidumpsFolder), &ffd); + if (hFind != INVALID_HANDLE_VALUE) + { + do + { + if ((ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) > 0) + continue; // ignore directory + + char fullPath[MAX_PATH_SIZE]; + PathCombineA(fullPath, MinidumpUpload::queuedMinidumpsFolder.data(), ffd.cFileName); + + // Try to open this minidump + Logger::Print("Trying to upload %s...\n", fullPath); + auto minidump = Minidump::Open(fullPath); + if (!minidump) + { + Logger::Print("Couldn't open minidump.\n"); + continue; // file can't be opened + } + + // Upload! + if (!MinidumpUpload::Upload(minidump)) + { + Logger::Print("Couldn't upload minidump.\n"); + continue; // couldn't upload that minidump, keep for another attempt + } + + delete minidump; + +#ifndef KEEP_MINIDUMPS_AFTER_UPLOAD + // Delete minidump if possible + DeleteFileA(fullPath); +#endif + } while (FindNextFileA(hFind, &ffd) != 0); + } + + Logger::Print("All minidumps uploaded.\n"); + return true; + } + + bool MinidumpUpload::Upload(Minidump* minidump) + { + // Checking if we can extract any information from minidump first, just to be sure that this is valid. + if (!minidump->Check()) + { + Utils::OutputDebugLastError(); + return false; + } + + auto id = Utils::String::GenerateUUIDString(); + + std::map extraHeaders = { + {"Hash-SHA512", Utils::Cryptography::SHA512::Compute(minidump->ToString(), true)}, + {"Compression", "deflate"}, + {"ID", id}, + }; + + std::string compressedMinidump = minidump->ToString(); + + Logger::Print("Compressing minidump %s (currently %d bytes)...\n", id.data(), compressedMinidump.size()); + compressedMinidump = Utils::Compression::ZLib::Compress(compressedMinidump); + +#ifndef DISABLE_BASE128 + Logger::Print("Encoding minidump %s (currently %d bytes)...\n", id.data(), compressedMinidump.size()); + extraHeaders["Encoding"] = "base128"; + compressedMinidump = Utils::String::EncodeBase128(compressedMinidump); +#endif + + Logger::Print("Minidump %s now prepared for uploading (currently %d bytes)...\n", id.data(), compressedMinidump.size()); + +#ifdef DISABLE_BITMESSAGE + for (auto& targetUrl : targetUrls) + { + Utils::WebIO webio("Firefucks", targetUrl); + + std::string buffer = MinidumpUpload::Encode(compressedMinidump, extraHeaders); + std::string result = webio.PostFile(buffer, "files[]", "minidump.dmpx"); + + std::string errors; + json11::Json object = json11::Json::parse(result, errors); + + if (!object.is_object()) continue; + + json11::Json success = object["success"]; + + if (!success.is_bool() || !success.bool_value()) return false; + + json11::Json files = object["files"]; + + if (!files.is_array()) continue; + + for (auto file : files.array_items()) + { + json11::Json url = file["url"]; + json11::Json hash = file["hash"]; + + if (hash.is_string() && url.is_string()) + { + if (Utils::String::ToLower(Utils::Cryptography::SHA1::Compute(buffer, true)) == Utils::String::ToLower(hash.string_value())) + { + MessageBoxA(0, url.string_value().data(), 0, 0); + return true; + } + } + } + } + + return false; +#else + // BitMessage has a max msg size that is somewhere around 220 KB, split it up as necessary! + auto totalParts = compressedMinidump.size() / MinidumpUpload::maxSegmentSize + 1; + extraHeaders.insert({ "Parts", Utils::String::VA("%d",totalParts) }); + + for (size_t offset = 0; offset < compressedMinidump.size(); offset += MinidumpUpload::maxSegmentSize) + { + auto extraPartHeaders = extraHeaders; + + auto part = compressedMinidump.substr(offset, std::min(MinidumpUpload::maxSegmentSize, compressedMinidump.size() - offset)); + auto partNum = offset / MinidumpUpload::maxSegmentSize + 1; + extraPartHeaders.insert({ "Part", Utils::String::VA("%d", partNum) }); + + Logger::Print("Uploading minidump %s (part %d out of %d, %d bytes)...\n", id.data(), partNum, totalParts, part.size()); + BitMessage::SendMsg(MinidumpUpload::targetAddress, MinidumpUpload::Encode(part, extraPartHeaders)); + } + + return true; +#endif + } + + std::string MinidumpUpload::Encode(std::string data, std::map extraHeaders) + { + std::string marker = "MINIDUMP"; + std::stringstream output; + + if (extraHeaders.find("Encoding") == extraHeaders.end()) + extraHeaders["Encoding"] = "raw"; + extraHeaders["Version"] = VERSION; + + output << "-----BEGIN " << marker << "-----\n"; + + // Insert extra headers + for (auto& header : extraHeaders) + { + output << header.first << ": " << header.second << "\n"; + } + + output << "\n" + << data << "\n" + << "-----END " << marker << "-----\n"; + + return output.str(); + } +#pragma endregion +} \ No newline at end of file diff --git a/src/Components/Modules/MinidumpUpload.hpp b/src/Components/Modules/MinidumpUpload.hpp new file mode 100644 index 00000000..6c686953 --- /dev/null +++ b/src/Components/Modules/MinidumpUpload.hpp @@ -0,0 +1,75 @@ +#pragma once + +#pragma warning(push) +#pragma warning(disable: 4091) +#include +#pragma comment(lib, "dbghelp.lib") +#pragma warning(pop) + +extern const int MiniDumpTiny; + +namespace Components +{ + // This class holds a Minidump and allows easy access to its aspects. + class Minidump + { + public: + ~Minidump(); + + static Minidump* Create(std::string path, LPEXCEPTION_POINTERS exceptionInfo, int type = MiniDumpTiny); + static Minidump* Open(std::string path); + bool GetStream(MINIDUMP_STREAM_TYPE type, PMINIDUMP_DIRECTORY* directoryPtr, PVOID* streamPtr, ULONG* streamSizePtr); + bool Check(); + std::string ToString(); + + private: + Minidump(); + + bool EnsureFileMapping(); + + static Minidump* Initialize(std::string path, DWORD fileShare = FILE_SHARE_READ | FILE_SHARE_WRITE); + + HANDLE fileHandle; + HANDLE mapFileHandle; + }; + + class MinidumpUpload : public Component + { + public: +#ifdef DEBUG + const char* GetName() { return "MinidumpUpload"; }; +#endif + MinidumpUpload(); + ~MinidumpUpload(); + + // Uploads the given minidump. + static bool Upload(Minidump* minidump); + + // Generates a new minidump and saves it to the folder for queued minidumps. + static Minidump* CreateQueuedMinidump(LPEXCEPTION_POINTERS exceptionInfo, int minidumpType = MiniDumpTiny); + + // Browses the folder for queued minidumps and uploads each queued minidump. + // On Release builds this will also delete every successfully uploaded minidump. + static bool UploadQueuedMinidumps(); + + private: + std::thread uploadThread; + + // Encodes the given minidump so that it can be uploaded in a proper format. + // Internally, this will compress the minidump and decorate it with proper markers and first-look headers. + static std::string Encode(std::string data, std::map extraHeaders = {}); + + // Ensures the queued minidumps folder exists. Will return false if the directory can't be created and does not exist. + static bool EnsureQueuedMinidumpsFolderExists(); + + // Contains the path to the minidumps folder. + static const std::string queuedMinidumpsFolder; + +#ifdef DISABLE_BITMESSAGE + static const std::vector targetUrls; +#else + static const std::string targetAddress; + static const unsigned int maxSegmentSize; +#endif + }; +} diff --git a/src/Components/Modules/ModList.cpp b/src/Components/Modules/ModList.cpp index 2fc9b170..4491c4cc 100644 --- a/src/Components/Modules/ModList.cpp +++ b/src/Components/Modules/ModList.cpp @@ -60,7 +60,7 @@ namespace Components { auto fsGame = Dvar::Var("fs_game"); fsGame.Set(""); - fsGame.Get()->pad2[0] = 1; + fsGame.Get()->modified = true; if (Dvar::Var("cl_modVidRestart").Get()) { @@ -76,7 +76,7 @@ namespace Components { auto fsGame = Dvar::Var("fs_game"); fsGame.Set(fmt::sprintf("mods/%s", mod.data())); - fsGame.Get()->pad2[0] = 1; + fsGame.Get()->modified = true; if (Dvar::Var("cl_modVidRestart").Get()) { diff --git a/src/Components/Modules/News.cpp b/src/Components/Modules/News.cpp index ae929e55..627d26ac 100644 --- a/src/Components/Modules/News.cpp +++ b/src/Components/Modules/News.cpp @@ -4,6 +4,7 @@ namespace Components { + bool News::Terminate; std::thread News::Thread; bool News::UnitTest() @@ -39,11 +40,110 @@ namespace Components return result; } + void News::ExitProcessStub(unsigned int exitCode) + { + std::this_thread::sleep_for(10ms); + + STARTUPINFOA sInfo; + PROCESS_INFORMATION pInfo; + + ZeroMemory(&sInfo, sizeof(sInfo)); + ZeroMemory(&pInfo, sizeof(pInfo)); + sInfo.cb = sizeof(sInfo); + + CreateProcessA("updater.exe", NULL, NULL, NULL, false, CREATE_NO_WINDOW, NULL, NULL, &sInfo, &pInfo); + + if (pInfo.hThread && pInfo.hThread != INVALID_HANDLE_VALUE) + { + CloseHandle(pInfo.hThread); + } + + if (pInfo.hProcess && pInfo.hProcess != INVALID_HANDLE_VALUE) + { + CloseHandle(pInfo.hProcess); + } + + TerminateProcess(GetCurrentProcess(), exitCode); + } + + void News::CheckForUpdate() + { + std::string caches = Utils::WebIO("IW4x", "https://iw4xcachep26muba.onion.to/iw4/caches.xml").SetTimeout(5000)->Get(); + + if (!caches.empty()) + { + std::string str = "()->current.integer = version; + Dvar::Var("cl_updateavailable").Get()->current.boolean = (version > REVISION); + } + } + } + } + News::News() { + Dvar::Register("cl_updateoldversion", REVISION, REVISION, REVISION, Game::DVAR_FLAG_WRITEPROTECTED, "Current version number."); + Dvar::Register("cl_updateversion", 0, 0, -1, Game::DVAR_FLAG_WRITEPROTECTED, "New version number."); + Dvar::Register("cl_updateavailable", 0, Game::DVAR_FLAG_WRITEPROTECTED, "New update is available."); + Localization::Set("MPUI_CHANGELOG_TEXT", "Loading..."); Localization::Set("MPUI_MOTD_TEXT", NEWS_MOTD_DEFUALT); + // TODO: Probably remove that, if the updater is part of the repo? + if (Utils::IO::FileExists("updater.exe")) + { + remove("updater.exe"); + } + + Command::Add("checkforupdate", [] (Command::Params) + { + News::CheckForUpdate(); + }); + + Command::Add("getautoupdate", [] (Command::Params) + { + if (!Dvar::Var("cl_updateavailable").Get()->current.boolean) return; + + Localization::SetTemp("MENU_RECONNECTING_TO_PARTY", "Downloading updater"); + Command::Execute("openmenu popup_reconnectingtoparty", true); + + // Run the updater on shutdown + Utils::Hook::Set(0x6D72A0, News::ExitProcessStub); + + std::thread([] () + { + std::string data = Utils::WebIO("IW4x", "https://iw4xcachep26muba.onion.to/iw4/updater.exe").SetTimeout(5000)->Get(); + + if (data.empty()) + { + Localization::ClearTemp(); + Command::Execute("closemenu popup_reconnectingtoparty", false); + Game::MessageBox("Failed to download the updater!", "Error"); + } + else + { + Console::SetSkipShutdown(); + Utils::IO::WriteFile("updater.exe", data); + Command::Execute("wait 300; quit;", false); + } + }).detach(); + }); + + News::Terminate = false; News::Thread = std::thread([] () { Localization::Set("MPUI_CHANGELOG_TEXT", Utils::WebIO("IW4x", "https://iw4xcachep26muba.onion.to/iw4/changelog.txt").SetTimeout(5000)->Get()); @@ -55,12 +155,26 @@ namespace Components Localization::Set("MPUI_MOTD_TEXT", data); } - // TODO: Implement update checks here! + if (!Loader::PerformingUnitTests()) + { + while (!News::Terminate) + { + News::CheckForUpdate(); + + // Sleep for 3 minutes + for (int i = 0; i < 180 && !News::Terminate; ++i) + { + std::this_thread::sleep_for(1s); + } + } + } }); } News::~News() { + News::Terminate = true; + if (News::Thread.joinable()) { News::Thread.join(); diff --git a/src/Components/Modules/News.hpp b/src/Components/Modules/News.hpp index 0299f16b..924e89c4 100644 --- a/src/Components/Modules/News.hpp +++ b/src/Components/Modules/News.hpp @@ -14,5 +14,9 @@ namespace Components private: static std::thread Thread; + static bool Terminate; + + static void CheckForUpdate(); + static void ExitProcessStub(unsigned int exitCode); }; } diff --git a/src/Components/Modules/Node.cpp b/src/Components/Modules/Node.cpp index 50c3e977..b2a0fc33 100644 --- a/src/Components/Modules/Node.cpp +++ b/src/Components/Modules/Node.cpp @@ -8,19 +8,13 @@ namespace Components void Node::LoadNodePreset() { + Proto::Node::List list; FileSystem::File defaultNodes("nodes_default.dat"); - if (!defaultNodes.Exists()) return; + if (!defaultNodes.Exists() || !list.ParseFromString(Utils::Compression::ZLib::Decompress(defaultNodes.GetBuffer()))) return; - auto buffer = defaultNodes.GetBuffer(); - Utils::String::Replace(buffer, "\r", ""); - - auto nodes = Utils::String::Explode(buffer, '\n'); - for (auto node : nodes) + for (int i = 0; i < list.address_size(); ++i) { - if (!node.empty()) - { - Node::AddNode(node); - } + Node::AddNode(list.address(i)); } } @@ -28,7 +22,7 @@ namespace Components { Proto::Node::List list; std::string nodes = Utils::IO::ReadFile("players/nodes.dat"); - if (nodes.empty() || !list.ParseFromString(nodes)) return; + if (nodes.empty() || !list.ParseFromString(Utils::Compression::ZLib::Decompress(nodes))) return; for (int i = 0; i < list.address_size(); ++i) { @@ -60,7 +54,10 @@ namespace Components } CreateDirectoryW(L"players", NULL); - Utils::IO::WriteFile("players/nodes.dat", list.SerializeAsString()); + + + + Utils::IO::WriteFile("players/nodes.dat", Utils::Compression::ZLib::Compress(list.SerializeAsString())); } Node::NodeEntry* Node::FindNode(Network::Address address) @@ -130,7 +127,7 @@ namespace Components Node::Nodes.push_back(entry); -#ifdef DEBUG +#if defined(DEBUG) && !defined(DISABLE_NODE_LOG) Logger::Print("Adding node %s...\n", address.GetCString()); #endif } @@ -187,7 +184,7 @@ namespace Components { if (node.state == Node::STATE_INVALID && (Game::Sys_Milliseconds() - node.lastHeard) > NODE_INVALID_DELETE) { -#ifdef DEBUG +#if defined(DEBUG) && !defined(DISABLE_NODE_LOG) Logger::Print("Removing invalid node %s\n", node.address.GetCString()); #endif } @@ -231,14 +228,14 @@ namespace Components Proto::Node::Packet packet; packet.set_challenge(entry->challenge); -#ifdef DEBUG +#if defined(DEBUG) && !defined(DISABLE_NODE_LOG) Logger::Print("Sending registration request to %s\n", entry->address.GetCString()); #endif Network::SendCommand(entry->address, "nodeRegisterRequest", packet.SerializeAsString()); } else { -#ifdef DEBUG +#if defined(DEBUG) && !defined(DISABLE_NODE_LOG) Logger::Print("Sending session request to %s\n", entry->address.GetCString()); #endif Network::SendCommand(entry->address, "sessionRequest"); @@ -267,7 +264,7 @@ namespace Components node.lastHeard = Game::Sys_Milliseconds(); node.lastTime = Game::Sys_Milliseconds(); -#ifdef DEBUG +#if defined(DEBUG) && !defined(DISABLE_NODE_LOG) Logger::Print("Node negotiation timed out. Invalidating %s\n", node.address.GetCString()); #endif } @@ -399,7 +396,7 @@ namespace Components if (!entry) return; } -#ifdef DEBUG +#if defined(DEBUG) && !defined(DISABLE_NODE_LOG) Logger::Print("Received registration request from %s\n", address.GetCString()); #endif @@ -440,7 +437,7 @@ namespace Components Node::NodeEntry* entry = Node::FindNode(address); if (!entry || entry->state != Node::STATE_NEGOTIATING) return; -#ifdef DEBUG +#if defined(DEBUG) && !defined(DISABLE_NODE_LOG) Logger::Print("Received synchronization data for registration from %s!\n", address.GetCString()); #endif @@ -467,7 +464,7 @@ namespace Components entry->state = Node::STATE_VALID; entry->registered = true; -#ifdef DEBUG +#if defined(DEBUG) && !defined(DISABLE_NODE_LOG) 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 @@ -491,7 +488,7 @@ namespace Components Node::NodeEntry* entry = Node::FindNode(address); if (!entry || entry->state != Node::STATE_NEGOTIATING) return; -#ifdef DEBUG +#if defined(DEBUG) && !defined(DISABLE_NODE_LOG) Logger::Print("Received acknowledgment from %s\n", address.GetCString()); #endif @@ -511,7 +508,7 @@ namespace Components entry->state = Node::STATE_VALID; entry->registered = true; -#ifdef DEBUG +#if defined(DEBUG) && !defined(DISABLE_NODE_LOG) 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 @@ -554,7 +551,7 @@ namespace Components } else { -#ifdef DEBUG +#if defined(DEBUG) && !defined(DISABLE_NODE_LOG) // Unallowed connection Logger::Print("Node list requested by %s, but no valid session was present!\n", address.GetCString()); #endif @@ -584,13 +581,13 @@ namespace Components entry->registered = false; entry->state = Node::STATE_INVALID; -#ifdef DEBUG +#if defined(DEBUG) && !defined(DISABLE_NODE_LOG) Logger::Print("Node %s unregistered\n", address.GetCString()); #endif } else { -#ifdef DEBUG +#if defined(DEBUG) && !defined(DISABLE_NODE_LOG) Logger::Print("Node %s tried to unregister using an invalid signature!\n", address.GetCString()); #endif } @@ -612,7 +609,7 @@ namespace Components Node::ClientSession* session = Node::FindSession(address); if (!session) return; // Registering template session failed, odd... -#ifdef DEBUG +#if defined(DEBUG) && !defined(DISABLE_NODE_LOG) Logger::Print("Client %s is requesting a new session\n", address.GetCString()); #endif @@ -634,7 +631,7 @@ namespace Components if (session->challenge == data) { -#ifdef DEBUG +#if defined(DEBUG) && !defined(DISABLE_NODE_LOG) Logger::Print("Session for %s validated.\n", address.GetCString()); #endif session->valid = true; @@ -643,7 +640,7 @@ namespace Components else { session->lastTime = -1; -#ifdef DEBUG +#if defined(DEBUG) && !defined(DISABLE_NODE_LOG) Logger::Print("Challenge mismatch. Validating session for %s failed.\n", address.GetCString()); #endif } @@ -656,7 +653,7 @@ namespace Components Node::NodeEntry* entry = Node::FindNode(address); if (!entry) return; -#ifdef DEBUG +#if defined(DEBUG) && !defined(DISABLE_NODE_LOG) Logger::Print("Session initialization received from %s. Synchronizing...\n", address.GetCString()); #endif @@ -673,7 +670,7 @@ namespace Components entry->registered = true; entry->lastTime = Game::Sys_Milliseconds(); -#ifdef DEBUG +#if defined(DEBUG) && !defined(DISABLE_NODE_LOG) Logger::Print("Session acknowledged by %s, synchronizing node list...\n", address.GetCString()); #endif Network::SendCommand(address, "nodeListRequest"); @@ -687,7 +684,7 @@ namespace Components if (data.empty() || !list.ParseFromString(data)) { -#ifdef DEBUG +#if defined(DEBUG) && !defined(DISABLE_NODE_LOG) Logger::Print("Received invalid node list from %s!\n", address.GetCString()); #endif return; @@ -698,7 +695,7 @@ namespace Components { if (entry->registered) { -#ifdef DEBUG +#if defined(DEBUG) && !defined(DISABLE_NODE_LOG) Logger::Print("Received valid node list with %i entries from %s\n", list.address_size(), address.GetCString()); #endif @@ -771,11 +768,6 @@ namespace Components Node::AddNode(address); } } - else - { - // TODO: Implement client handshake stuff - // Nvm, clients can simply ignore that i guess - } }); Command::Add("listnodes", [] (Command::Params) @@ -907,6 +899,31 @@ namespace Components auto duration = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - startTime).count(); Logger::Print("took %llims\n", duration); + printf("Testing ZLib compression..."); + + std::string test = fmt::sprintf("%c", Utils::Cryptography::Rand::GenerateInt()); + + for (int i = 0; i < 21; ++i) + { + std::string compressed = Utils::Compression::ZLib::Compress(test); + std::string decompressed = Utils::Compression::ZLib::Decompress(compressed); + + if (test != decompressed) + { + printf("Error\n"); + printf("Compressing %d bytes and decompressing failed!\n", test.size()); + return false; + } + + auto size = test.size(); + for (unsigned int j = 0; j < size; ++j) + { + test.append(fmt::sprintf("%c", Utils::Cryptography::Rand::GenerateInt())); + } + } + + printf("Success\n"); + return true; } } diff --git a/src/Components/Modules/Party.cpp b/src/Components/Modules/Party.cpp index 7922d1c8..ed693242 100644 --- a/src/Components/Modules/Party.cpp +++ b/src/Components/Modules/Party.cpp @@ -64,6 +64,7 @@ namespace Components void Party::ConnectError(std::string message) { + Localization::ClearTemp(); Command::Execute("closemenu popup_reconnectingtoparty"); Dvar::Var("partyend_reason").Set(message); Command::Execute("openmenu menu_xboxlive_partyended"); @@ -308,7 +309,7 @@ namespace Components 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("shortversion", SHORTVERSION); 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")); @@ -354,8 +355,9 @@ namespace Components { // Invalidate handler for future packets Party::Container.Valid = false; + Party::Container.Info = info; - int matchType = atoi(info.Get("matchtype").data()); + Party::Container.MatchType = atoi(info.Get("matchtype").data()); uint32_t securityLevel = static_cast(atoi(info.Get("securityLevel").data())); if (info.Get("challenge") != Party::Container.Challenge) @@ -368,48 +370,66 @@ namespace Components Command::Execute("closemenu popup_reconnectingtoparty"); Auth::IncreaseSecurityLevel(securityLevel, "reconnect"); } - else if (!matchType) + else if (!Party::Container.MatchType) { Party::ConnectError("Server is not hosting a match."); } - // Connect - else if (matchType == 1) // Party + else if(Party::Container.MatchType > 2 || Party::Container.MatchType < 0) { - // 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); - } + Party::ConnectError("Invalid join response: Unknown matchtype"); } - else if (matchType == 2) // Match + else if(!info.Get("fs_game").empty() && Dvar::Var("fs_game").Get() != info.Get("fs_game")) { - 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); + Command::Execute("closemenu popup_reconnectingtoparty"); + Download::InitiateClientDownload(info.Get("fs_game")); + } + else if (!Dvar::Var("fs_game").Get().empty() && info.Get("fs_game").empty()) + { + Dvar::Var("fs_game").Set(""); - 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()); + if (Dvar::Var("cl_modVidRestart").Get()) + { + Command::Execute("vid_restart", false); } + + Command::Execute("reconnect", false); } else { - Party::ConnectError("Invalid join response: Unknown matchtype"); + if (Party::Container.MatchType == 1) // Party + { + // Send playlist request + Party::Container.RequestTime = Game::Sys_Milliseconds(); + Party::Container.AwaitingPlaylist = true; + Network::SendCommand(Party::Container.Target, "getplaylist"); + + // This is not a safe method + // TODO: Fix actual error! + if (Game::CL_IsCgameInitialized()) + { + Command::Execute("disconnect", true); + } + } + else if (Party::Container.MatchType == 2) // Match + { + if (atoi(Party::Container.Info.Get("clients").data()) >= atoi(Party::Container.Info.Get("sv_maxclients").data())) + { + Party::ConnectError("@EXE_SERVERISFULL"); + } + if (Party::Container.Info.Get("mapname") == "" || Party::Container.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, *Party::Container.Target.Get(), 0, 0, Party::Container.Info.Get("mapname").data(), Party::Container.Info.Get("gametype").data()); + } + } } } } diff --git a/src/Components/Modules/Party.hpp b/src/Components/Modules/Party.hpp index 5e7e73b6..ca2e033e 100644 --- a/src/Components/Modules/Party.hpp +++ b/src/Components/Modules/Party.hpp @@ -19,6 +19,8 @@ namespace Components static void PlaylistContinue(); static void PlaylistError(std::string error); + static void ConnectError(std::string message); + private: class JoinContainer { @@ -27,10 +29,13 @@ namespace Components std::string Challenge; DWORD JoinTime; bool Valid; + int MatchType; + + Utils::InfoString Info; // Party-specific stuff DWORD RequestTime; - bool AwaitingPlaylist; + bool AwaitingPlaylist; }; static JoinContainer Container; @@ -40,8 +45,6 @@ namespace Components static Game::dvar_t* RegisterMinPlayers(const char* name, int value, int min, int max, Game::dvar_flag flag, const char* description); - static void ConnectError(std::string message); - static DWORD UIDvarIntStub(char* dvar); }; } diff --git a/src/Components/Modules/PlayerName.cpp b/src/Components/Modules/PlayerName.cpp new file mode 100644 index 00000000..9e5595a3 --- /dev/null +++ b/src/Components/Modules/PlayerName.cpp @@ -0,0 +1,33 @@ +#include "STDInclude.hpp" + +namespace Components +{ + std::string PlayerName::PlayerNames[18]; + + int PlayerName::GetClientName(int /*localClientNum*/, int index, char *buf, int size) + { + if (index < 0 || index >= 18) return 0; + return strncpy_s(buf, size, PlayerName::PlayerNames[index].data(), PlayerName::PlayerNames[index].size()) == 0; + } + + PlayerName::PlayerName() + { + if (0) // Disabled for now (comment out that line to enable it) + { + for (int i = 0; i < ARRAY_SIZE(PlayerName::PlayerNames); ++i) + { + PlayerName::PlayerNames[i] = "mumu"; + } + + Utils::Hook(Game::CL_GetClientName, PlayerName::GetClientName, HOOK_JUMP).Install()->Quick(); + } + } + + PlayerName::~PlayerName() + { + for (int i = 0; i < ARRAY_SIZE(PlayerName::PlayerNames); ++i) + { + PlayerName::PlayerNames[i].clear(); + } + } +} diff --git a/src/Components/Modules/PlayerName.hpp b/src/Components/Modules/PlayerName.hpp new file mode 100644 index 00000000..d9442ab3 --- /dev/null +++ b/src/Components/Modules/PlayerName.hpp @@ -0,0 +1,18 @@ +namespace Components +{ + class PlayerName : public Component + { + public: + PlayerName(); + ~PlayerName(); + +#ifdef DEBUG + const char* GetName() { return "PlayerName"; }; +#endif + + private: + static std::string PlayerNames[18]; + + static int GetClientName(int localClientNum, int index, char *buf, int size); + }; +} diff --git a/src/Components/Modules/QuickPatch.cpp b/src/Components/Modules/QuickPatch.cpp index a8c94bdc..779d2fed 100644 --- a/src/Components/Modules/QuickPatch.cpp +++ b/src/Components/Modules/QuickPatch.cpp @@ -173,26 +173,34 @@ namespace Components Utils::Hook::Set(0x6431D1, BASEGAME); // UI version string - Utils::Hook::Set(0x43F73B, "IW4x: r" REVISION_STR REVISION_SUFFIX "-" MILESTONE); + Utils::Hook::Set(0x43F73B, "IW4x: " VERSION); // console version string - Utils::Hook::Set(0x4B12BB, "IW4x r" REVISION_STR REVISION_SUFFIX "-" MILESTONE " (built " __DATE__ " " __TIME__ ")"); + Utils::Hook::Set(0x4B12BB, "IW4x " VERSION " (built " __DATE__ " " __TIME__ ")"); // version string - Utils::Hook::Set(0x60BD56, "IW4x (r" REVISION_STR REVISION_SUFFIX ")"); + Utils::Hook::Set(0x60BD56, "IW4x (" VERSION ")"); + + // Shift ui version string to the left (ui_buildlocation) + Utils::Hook::Nop(0x6310A0, 5); // Don't register the initial dvar + Utils::Hook::Nop(0x6310B8, 5); // Don't write the result + Dvar::OnInit([] () + { + *reinterpret_cast(0x62E4B64) = Game::Dvar_RegisterVec2("ui_buildLocation", -80.0f, 15.0f, -10000.0, 10000.0, Game::DVAR_FLAG_READONLY, "Where to draw the build number"); + }); // console title if (ZoneBuilder::IsEnabled()) { - Utils::Hook::Set(0x4289E8, "IW4x (r" REVISION_STR REVISION_SUFFIX "): ZoneBuilder"); + Utils::Hook::Set(0x4289E8, "IW4x (" VERSION "): ZoneBuilder"); } else if (Dedicated::IsEnabled()) { - Utils::Hook::Set(0x4289E8, "IW4x (r" REVISION_STR REVISION_SUFFIX "): Dedicated"); + Utils::Hook::Set(0x4289E8, "IW4x (r" VERSION "): Dedicated"); } else { - Utils::Hook::Set(0x4289E8, "IW4x (r" REVISION_STR REVISION_SUFFIX "): Console"); + Utils::Hook::Set(0x4289E8, "IW4x (r" VERSION "): Console"); } // window title @@ -202,7 +210,7 @@ namespace Components Utils::Hook::Set(0x4D378B, "IW4Host"); // shortversion - Utils::Hook::Set(0x60BD91, VERSION_STR); + Utils::Hook::Set(0x60BD91, SHORTVERSION); // console logo Utils::Hook::Set(0x428A66, BASEGAME "/images/logo.bmp"); @@ -236,6 +244,15 @@ namespace Components Utils::Hook::Set(0x6832BA, 0xEB); Utils::Hook::Set(0x4BD190, 0xC3); + //*(BYTE*)0x4BB250 = 0x33; + //*(BYTE*)0x4BB251 = 0xC0; + //*(DWORD*)0x4BB252 = 0xC3909090; + + // remove 'impure stats' checking + Utils::Hook::Set(0x4BB250, 0x33); + Utils::Hook::Set(0x4BB251, 0xC0); + Utils::Hook::Set(0x4BB252, 0xC3909090); + // default sv_pure to 0 Utils::Hook::Set(0x4D3A74, 0); diff --git a/src/Components/Modules/RCon.cpp b/src/Components/Modules/RCon.cpp index 450caae0..04eeddce 100644 --- a/src/Components/Modules/RCon.cpp +++ b/src/Components/Modules/RCon.cpp @@ -45,7 +45,6 @@ namespace Components } }); - // TODO: Maybe execute that for clients as well, when we use triangular natting. if (!Dedicated::IsEnabled()) return; // Load public key diff --git a/src/Components/Modules/ServerInfo.cpp b/src/Components/Modules/ServerInfo.cpp index 8a3151e7..695d4470 100644 --- a/src/Components/Modules/ServerInfo.cpp +++ b/src/Components/Modules/ServerInfo.cpp @@ -115,7 +115,7 @@ namespace Components 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("shortversion", SHORTVERSION); 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())))); diff --git a/src/Components/Modules/ServerList.cpp b/src/Components/Modules/ServerList.cpp index 9cff72d7..f234e760 100644 --- a/src/Components/Modules/ServerList.cpp +++ b/src/Components/Modules/ServerList.cpp @@ -381,7 +381,7 @@ namespace Components void ServerList::Insert(Network::Address address, Utils::InfoString info) { - ServerList::RefreshContainer.Mutex.lock(); + std::lock_guard _(ServerList::RefreshContainer.Mutex); for (auto i = ServerList::RefreshContainer.Servers.begin(); i != ServerList::RefreshContainer.Servers.end();) { @@ -449,7 +449,7 @@ namespace Components if (info.Get("gamename") == "IW4" && server.MatchType #ifndef DEBUG - && server.Shortversion == VERSION_STR + && server.Shortversion == SHORTVERSION #endif ) { @@ -469,8 +469,6 @@ namespace Components ++i; } } - - ServerList::RefreshContainer.Mutex.unlock(); } ServerList::ServerInfo* ServerList::GetCurrentServer() @@ -625,7 +623,7 @@ namespace Components ServerList::RefreshContainer.AwatingList = false; - ServerList::RefreshContainer.Mutex.lock(); + std::lock_guard _(ServerList::RefreshContainer.Mutex); int offset = 0; int count = ServerList::RefreshContainer.Servers.size(); @@ -649,8 +647,6 @@ namespace Components } Logger::Print("Parsed %d servers from master\n", ServerList::RefreshContainer.Servers.size() - count); - - ServerList::RefreshContainer.Mutex.unlock(); }); // Set default masterServerName + port and save it diff --git a/src/Components/Modules/Singleton.cpp b/src/Components/Modules/Singleton.cpp index 7c8d5956..1a50fa5b 100644 --- a/src/Components/Modules/Singleton.cpp +++ b/src/Components/Modules/Singleton.cpp @@ -13,10 +13,12 @@ namespace Components { if (Flags::HasFlag("version")) { - printf("IW4x r" REVISION_STR "-" MILESTONE " (built " __DATE__ " " __TIME__ ")\n"); + printf("IW4x " VERSION " (built " __DATE__ " " __TIME__ ")\n"); ExitProcess(0); } + Console::FreeNativeConsole(); + if (Dedicated::IsEnabled() || ZoneBuilder::IsEnabled()) return; Singleton::FirstInstance = (CreateMutexA(NULL, FALSE, "iw4x_mutex") && GetLastError() != ERROR_ALREADY_EXISTS); diff --git a/src/Components/Modules/StringTable.cpp b/src/Components/Modules/StringTable.cpp index 7bf4f0b4..3d0738f7 100644 --- a/src/Components/Modules/StringTable.cpp +++ b/src/Components/Modules/StringTable.cpp @@ -21,6 +21,8 @@ namespace Components Game::StringTable* StringTable::LoadObject(std::string filename) { + filename = Utils::String::ToLower(filename); + Game::StringTable* table = nullptr; FileSystem::File rawTable(filename); @@ -73,6 +75,8 @@ namespace Components { Game::XAssetHeader header = { 0 }; + filename = Utils::String::ToLower(filename); + if (StringTable::StringTableMap.find(filename) != StringTable::StringTableMap.end()) { header.stringTable = StringTable::StringTableMap[filename]; diff --git a/src/Components/Modules/Toast.cpp b/src/Components/Modules/Toast.cpp index 2279de8d..f60f1f2e 100644 --- a/src/Components/Modules/Toast.cpp +++ b/src/Components/Modules/Toast.cpp @@ -103,7 +103,7 @@ namespace Components { if (Toast::Queue.empty()) return; - Toast::Mutex.lock(); + std::lock_guard _(Toast::Mutex); Toast::UIToast* toast = &Toast::Queue.front(); @@ -121,8 +121,6 @@ namespace Components { Toast::Draw(toast); } - - Toast::Mutex.unlock(); } Toast::Toast() diff --git a/src/Components/Modules/Window.cpp b/src/Components/Modules/Window.cpp index e1f30658..4a755f37 100644 --- a/src/Components/Modules/Window.cpp +++ b/src/Components/Modules/Window.cpp @@ -143,7 +143,6 @@ namespace Components if (Window::CursorVisible) { - // TODO: Apply custom cursor SetCursor(LoadCursor(NULL, IDC_ARROW)); while ((value = ShowCursor(TRUE)) < 0); diff --git a/src/Game/Functions.hpp b/src/Game/Functions.hpp index c3f29b05..a5adfcab 100644 --- a/src/Game/Functions.hpp +++ b/src/Game/Functions.hpp @@ -36,7 +36,7 @@ namespace Game typedef void(__cdecl * CL_SelectStringTableEntryInDvar_f_t)(); extern CL_SelectStringTableEntryInDvar_f_t CL_SelectStringTableEntryInDvar_f; - typedef void(__cdecl * Cmd_AddCommand_t)(const char* cmdName, void(*function), cmd_function_t* allocedCmd, char); + typedef void(__cdecl * Cmd_AddCommand_t)(const char* cmdName, void(*function), cmd_function_t* allocedCmd, bool isKey); extern Cmd_AddCommand_t Cmd_AddCommand; typedef void(__cdecl * Cmd_AddServerCommand_t)(const char* name, void(*callback), cmd_function_t* data); diff --git a/src/Game/Structs.hpp b/src/Game/Structs.hpp index fb163030..ea949aeb 100644 --- a/src/Game/Structs.hpp +++ b/src/Game/Structs.hpp @@ -117,14 +117,14 @@ namespace Game typedef struct dvar_t { //startbyte:endbyte - const char* name; //0:3 - const char* description; //4:7 - unsigned int flags; //8:11 - char type; //12:12 - char pad2[3]; //13:15 - dvar_value_t current; //16:31 - dvar_value_t latched; //32:47 - dvar_value_t _default; //48:64 + const char* name; //0:3 + const char* description; //4:7 + unsigned int flags; //8:11 + char type; //12:12 + bool modified; //13:15 + dvar_value_t current; //16:31 + dvar_value_t latched; //32:47 + dvar_value_t _default; //48:64 dvar_maxmin_t min; //65:67 dvar_maxmin_t max; //68:72 woooo } dvar_t; @@ -136,7 +136,7 @@ namespace Game const char *autoCompleteDir; const char *autoCompleteExt; void(__cdecl *function)(); - int unknown; + bool isKey; // Looks like this is true when the command is a key/button } cmd_function_t; #pragma pack(push, 4) diff --git a/src/Resource.rc b/src/Resource.rc index 44f73f4e..1a74c1b4 100644 --- a/src/Resource.rc +++ b/src/Resource.rc @@ -2,7 +2,6 @@ // #pragma code_page(65001) -#define RESOURCE_DATA #include "STDInclude.hpp" #define APSTUDIO_READONLY_SYMBOLS @@ -47,8 +46,8 @@ END // VS_VERSION_INFO VERSIONINFO - FILEVERSION VERSION - PRODUCTVERSION VERSION + FILEVERSION VERSION_RC + PRODUCTVERSION VERSION_RC FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -69,12 +68,12 @@ BEGIN #else VALUE "FileDescription", "IW4 client modification" #endif - VALUE "FileVersion", VERSION_STR + VALUE "FileVersion", SHORTVERSION VALUE "InternalName", "iw4x" VALUE "LegalCopyright", "No rights reserved." VALUE "OriginalFilename", "iw4x.dll" VALUE "ProductName", "IW4x" - VALUE "ProductVersion", VERSION_STR + VALUE "ProductVersion", SHORTVERSION END END BLOCK "VarFileInfo" diff --git a/src/STDInclude.cpp b/src/STDInclude.cpp index 3c2ddf00..037fe84b 100644 --- a/src/STDInclude.cpp +++ b/src/STDInclude.cpp @@ -17,6 +17,10 @@ Assert_Size(DWORD, 4); Assert_Size(WORD, 2); Assert_Size(BYTE, 1); +// 128 bit integers (only x64) +//Assert_Size(__int128, 16); +//Assert_Size(unsigned __int128, 16); + // 64 bit integers Assert_Size(__int64, 8); Assert_Size(unsigned __int64, 8); @@ -64,8 +68,8 @@ static_assert(sizeof(intptr_t) == 4 && sizeof(void*) == 4 && sizeof(size_t) == 4 // Disable telemetry data logging extern "C" { - void _cdecl __vcrt_initialize_telemetry_provider() {} - void _cdecl __telemetry_main_invoke_trigger() {} - void _cdecl __telemetry_main_return_trigger() {} - void _cdecl __vcrt_uninitialize_telemetry_provider() {} + void __cdecl __vcrt_initialize_telemetry_provider() {} + void __cdecl __telemetry_main_invoke_trigger() {} + void __cdecl __telemetry_main_return_trigger() {} + void __cdecl __vcrt_uninitialize_telemetry_provider() {} }; diff --git a/src/STDInclude.hpp b/src/STDInclude.hpp index 73032776..35936c14 100644 --- a/src/STDInclude.hpp +++ b/src/STDInclude.hpp @@ -1,9 +1,9 @@ #pragma once // Version number -#include +#include "version.h" -#ifndef RESOURCE_DATA +#ifndef RC_INVOKED #define VC_EXTRALEAN #define WIN32_LEAN_AND_MEAN @@ -46,6 +46,7 @@ #pragma warning(disable: 4100) #pragma warning(disable: 4389) #pragma warning(disable: 4702) +#pragma warning(disable: 4996) // _CRT_SECURE_NO_WARNINGS #pragma warning(disable: 6001) #pragma warning(disable: 6011) #pragma warning(disable: 6031) @@ -60,6 +61,9 @@ #include #include #include +#ifndef DISABLE_BITMESSAGE +#include +#endif #ifdef max #undef max @@ -109,28 +113,19 @@ #pragma comment(lib, "shlwapi.lib") #pragma comment(lib, "Urlmon.lib") #pragma comment(lib, "Advapi32.lib") +#pragma comment(lib, "rpcrt4.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 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.") // Resource stuff diff --git a/src/Utils/Compression.cpp b/src/Utils/Compression.cpp index 02c85351..4eec358d 100644 --- a/src/Utils/Compression.cpp +++ b/src/Utils/Compression.cpp @@ -6,8 +6,13 @@ namespace Utils { std::string ZLib::Compress(std::string data) { + Utils::Memory::Allocator allocator; unsigned long length = (data.size() * 2); - char* buffer = Utils::Memory::AllocateArray(length); + + // Make sure the buffer is large enough + if (length < 100) length *= 10; + + char* buffer = allocator.AllocateArray(length); if (compress2(reinterpret_cast(buffer), &length, reinterpret_cast(const_cast(data.data())), data.size(), Z_BEST_COMPRESSION) != Z_OK) { @@ -18,8 +23,6 @@ namespace Utils data.clear(); data.append(buffer, length); - Utils::Memory::Free(buffer); - return data; } @@ -44,6 +47,7 @@ namespace Utils { stream.avail_in = std::min(static_cast(CHUNK), data.size() - (dataPtr - data.data())); stream.next_in = reinterpret_cast(dataPtr); + dataPtr += stream.avail_in; do { @@ -51,7 +55,7 @@ namespace Utils stream.next_out = dest; ret = inflate(&stream, Z_NO_FLUSH); - if (ret == Z_STREAM_ERROR) + if (ret != Z_OK && ret != Z_STREAM_END) { inflateEnd(&stream); return ""; diff --git a/src/Utils/Cryptography.hpp b/src/Utils/Cryptography.hpp index 4adda3e7..0cb67589 100644 --- a/src/Utils/Cryptography.hpp +++ b/src/Utils/Cryptography.hpp @@ -20,7 +20,7 @@ namespace Utils } else { - for (unsigned int i = (this->TokenString.size() - 1); i >= 0; i--) + for (int i = static_cast(this->TokenString.size() - 1); i >= 0; --i) { if (this->TokenString[i] == 0xFF) { diff --git a/src/Utils/Hooking.cpp b/src/Utils/Hooking.cpp index d8928327..3ab1c563 100644 --- a/src/Utils/Hooking.cpp +++ b/src/Utils/Hooking.cpp @@ -136,11 +136,10 @@ namespace Utils Hook* Hook::Install(bool unprotect, bool keepUnportected) { - Hook::StateMutex.lock(); + std::lock_guard _(Hook::StateMutex); if (!Hook::Initialized || Hook::Installed) { - Hook::StateMutex.unlock(); return this; } @@ -159,8 +158,6 @@ namespace Utils FlushInstructionCache(GetCurrentProcess(), Hook::Place, sizeof(Hook::Buffer)); - Hook::StateMutex.unlock(); - return this; } diff --git a/src/Utils/IO.cpp b/src/Utils/IO.cpp index c29a39f5..5fdf05a4 100644 --- a/src/Utils/IO.cpp +++ b/src/Utils/IO.cpp @@ -47,5 +47,48 @@ namespace Utils return buffer; } + + bool CreateDirectory(std::string dir) + { + char opath[MAX_PATH] = { 0 }; + char *p; + size_t len; + + strncpy_s(opath, dir.data(), sizeof(opath)); + len = strlen(opath); + + if (opath[len - 1] == L'/') + { + opath[len - 1] = L'\0'; + } + + for (p = opath; *p; p++) + { + if (*p == L'/' || *p == L'\\') + { + *p = L'\0'; + + if (_access(opath, 0)) + { + if (_mkdir(opath) == -1) + { + return false; + } + } + + *p = L'\\'; + } + } + + if (_access(opath, 0)) + { + if (_mkdir(opath) == -1) + { + return false; + } + } + + return true; + } } } diff --git a/src/Utils/IO.hpp b/src/Utils/IO.hpp index 82ef4769..817eb7d4 100644 --- a/src/Utils/IO.hpp +++ b/src/Utils/IO.hpp @@ -5,5 +5,6 @@ namespace Utils bool FileExists(std::string file); void WriteFile(std::string file, std::string data); std::string ReadFile(std::string file); + bool CreateDirectory(std::string dir); } } diff --git a/src/Utils/String.cpp b/src/Utils/String.cpp index 44157047..0e2da5f8 100644 --- a/src/Utils/String.cpp +++ b/src/Utils/String.cpp @@ -1,4 +1,7 @@ #include "STDInclude.hpp" +#ifndef DISABLE_BASE128 +#include "base128.h" +#endif namespace Utils { @@ -115,5 +118,80 @@ namespace Utils return fmt::sprintf("%02d:%02d:%02d", hoursTotal, minutes, seconds); } + + std::string FormatBandwidth(size_t bytes, int milliseconds) + { + static char* sizes[] = + { + "B", + "KB", + "MB", + "GB", + "TB" + }; + + double bytesPerSecond = (1000.0 / milliseconds) * bytes; + + int i = 0; + for (i = 0; bytesPerSecond > 1000 && i < ARRAY_SIZE(sizes); ++i) // 1024 or 1000? + { + bytesPerSecond /= 1000; + } + + return fmt::sprintf("%.2f %s/s", static_cast(bytesPerSecond), sizes[i]); + } + + // Encodes a given string in Base64 + std::string EncodeBase64(const char* input, const unsigned long inputSize) + { + unsigned long outlen = long(inputSize + (inputSize / 3.0) + 16); + unsigned char* outbuf = new unsigned char[outlen]; //Reserve output memory + base64_encode(reinterpret_cast(const_cast(input)), inputSize, outbuf, &outlen); + std::string ret((char*)outbuf, outlen); + delete[] outbuf; + return ret; + } + + // Encodes a given string in Base64 + std::string EncodeBase64(const std::string& input) + { + return EncodeBase64(input.data(), input.size()); + } + +#ifndef DISABLE_BASE128 + // Encodes a given string in Base128 + std::string EncodeBase128(const std::string& input) + { + base128 encoder; + + void* inbuffer = const_cast(input.data()); + char* buffer = encoder.encode(inbuffer, input.size()); + /* + Interesting to see that the buffer returned by the encoder is not a standalone string copy + but instead is a pointer to the internal "encoded" field of the encoder. So if you deinitialize + the encoder that string will probably become garbage. + */ + std::string retval(buffer); + return retval; + } +#endif + + // Generates a UUID and returns the string representation of it + std::string GenerateUUIDString() + { + // Generate UUID data + UUID uuid; + if (!UuidCreate(&uuid)) + { + // Convert to string representation + char* strdata = nullptr; + if (!UuidToStringA(&uuid, reinterpret_cast(&strdata))) + { + return std::string(strdata); + } + } + + return ""; + } } } diff --git a/src/Utils/String.hpp b/src/Utils/String.hpp index 0a877ebd..33312035 100644 --- a/src/Utils/String.hpp +++ b/src/Utils/String.hpp @@ -35,9 +35,17 @@ namespace Utils std::string &Trim(std::string &s); std::string FormatTimeSpan(int milliseconds); + std::string FormatBandwidth(size_t bytes, int milliseconds); std::string DumpHex(std::string data, std::string separator = " "); std::string XOR(std::string str, char value); + + std::string EncodeBase64(const char* input, const unsigned long inputSize); + std::string EncodeBase64(const std::string& input); + + std::string EncodeBase128(const std::string& input); + + std::string GenerateUUIDString(); } } diff --git a/src/Utils/Utils.cpp b/src/Utils/Utils.cpp index 02c080ec..f35d1e79 100644 --- a/src/Utils/Utils.cpp +++ b/src/Utils/Utils.cpp @@ -22,4 +22,17 @@ namespace Utils if (pos == std::string::npos) return data; return data.substr(0, pos).data(); } + + void OutputDebugLastError() + { + DWORD errorMessageID = ::GetLastError(); + LPSTR messageBuffer = nullptr; + size_t size = FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, errorMessageID, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)&messageBuffer, 0, NULL); + std::string message(messageBuffer, size); + + OutputDebugStringA(Utils::String::VA("Last error code: 0x%08X (%s)\n", errorMessageID, message)); + + LocalFree(messageBuffer); + } } diff --git a/src/Utils/Utils.hpp b/src/Utils/Utils.hpp index 99a187a7..399b34d7 100644 --- a/src/Utils/Utils.hpp +++ b/src/Utils/Utils.hpp @@ -2,6 +2,7 @@ namespace Utils { std::string GetMimeType(std::string url); std::string ParseChallenge(std::string data); + void OutputDebugLastError(); template void Merge(std::vector* target, T* source, size_t length) { diff --git a/src/Utils/WebIO.cpp b/src/Utils/WebIO.cpp index e7e6ca05..c88e43c2 100644 --- a/src/Utils/WebIO.cpp +++ b/src/Utils/WebIO.cpp @@ -104,10 +104,26 @@ namespace Utils WebIO::m_sUrl.document = server.substr(pos); } + WebIO::m_sUrl.port.clear(); + + pos = WebIO::m_sUrl.server.find(":"); + if (pos != std::string::npos) + { + WebIO::m_sUrl.port = WebIO::m_sUrl.server.substr(pos + 1); + WebIO::m_sUrl.server = WebIO::m_sUrl.server.substr(0, pos); + } + WebIO::m_sUrl.raw.clear(); WebIO::m_sUrl.raw.append(WebIO::m_sUrl.protocol); WebIO::m_sUrl.raw.append("://"); WebIO::m_sUrl.raw.append(WebIO::m_sUrl.server); + + if (!WebIO::m_sUrl.port.empty()) + { + WebIO::m_sUrl.raw.append(":"); + WebIO::m_sUrl.raw.append(WebIO::m_sUrl.port); + } + WebIO::m_sUrl.raw.append(WebIO::m_sUrl.document); WebIO::m_isFTP = (WebIO::m_sUrl.protocol == "ftp"); @@ -134,25 +150,33 @@ namespace Utils return body; } - std::string WebIO::PostFile(std::string url, std::string filename, std::string fieldname, std::string data) + std::string WebIO::PostFile(std::string url, std::string data, std::string fieldName, std::string fileName) { WebIO::SetURL(url); - return WebIO::PostFile(filename, fieldname, data); + return WebIO::PostFile(data, fieldName, fileName); } - std::string WebIO::PostFile(std::string filename, std::string fieldname, std::string data) + std::string WebIO::PostFile(std::string data, std::string fieldName, std::string fileName) { WebIO::Params headers; - std::string boundary = "----FormBoundary" + Utils::Cryptography::SHA256::Compute(fmt::sprintf("%d", timeGetTime())); + std::string boundary = "----WebKitFormBoundaryHoLVocRsBxs71fU6"; headers["Content-Type"] = "multipart/form-data, boundary=" + boundary; - std::string body; - body.append("--" + boundary + "\r\n"); - body.append("Content-Disposition: form-data; name=\"" + fieldname + "\"; filename=\"" + filename + "\"\r\n"); - body.append("Content-Type: application/octet-stream\r\n\r\n"); - body.append(data + "\r\n"); - body.append("--" + boundary + "--\r\n"); + Utils::String::Replace(fieldName, "\"", "\\\""); + Utils::String::Replace(fieldName, "\\", "\\\\"); + Utils::String::Replace(fileName, "\"", "\\\""); + Utils::String::Replace(fileName, "\\", "\\\\"); + + std::string body = "--" + boundary + "\r\n"; + body += "Content-Disposition: form-data; name=\""; + body += fieldName; + body += "\"; filename=\""; + body += fileName; + body += "\"\r\n"; + body += "Content-Type: application/octet-stream\r\n\r\n"; + body += data + "\r\n"; + body += "--" + boundary + "--\r\n"; headers["Content-Length"] = fmt::sprintf("%u", body.size()); @@ -209,6 +233,11 @@ namespace Utils wPort = INTERNET_DEFAULT_HTTPS_PORT; } + if (!WebIO::m_sUrl.port.empty()) + { + wPort = static_cast(atoi(WebIO::m_sUrl.port.data())); + } + const char* username = (WebIO::m_username.size() ? WebIO::m_username.data() : NULL); const char* password = (WebIO::m_password.size() ? WebIO::m_password.data() : NULL); WebIO::m_hConnect = InternetConnectA(WebIO::m_hSession, WebIO::m_sUrl.server.data(), wPort, username, password, dwService, dwFlag, 0); diff --git a/src/Utils/WebIO.hpp b/src/Utils/WebIO.hpp index f5ee3b1d..ffa7ca3c 100644 --- a/src/Utils/WebIO.hpp +++ b/src/Utils/WebIO.hpp @@ -26,8 +26,8 @@ namespace Utils void SetURL(std::string url); void SetCredentials(std::string username, std::string password); - std::string PostFile(std::string url, std::string filename, std::string fieldname, std::string data); - std::string PostFile(std::string filename, std::string fieldname, std::string data); + std::string PostFile(std::string url, std::string data, std::string fieldName, std::string fileName); + std::string PostFile(std::string data, std::string fieldName, std::string fileName); std::string Post(std::string url, WebIO::Params params); std::string Post(std::string url, std::string body); @@ -74,6 +74,7 @@ namespace Utils std::string protocol; std::string server; std::string document; + std::string port; std::string raw; };