Merge branch 'develop' into 'master'

Develop
This commit is contained in:
/dev/full 2016-09-13 19:52:11 +02:00
commit ee76ec9340
70 changed files with 2777 additions and 401 deletions

7
.gitmodules vendored
View File

@ -29,3 +29,10 @@
[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

1
deps/base128 vendored Submodule

@ -0,0 +1 @@
Subproject commit 64c8ab2755e14d316b18aff9746f0180f5fe301b

1
deps/bitmrc vendored Submodule

@ -0,0 +1 @@
Subproject commit 43f63eeb1f900fd03b192e7e577c033a6be072ab

2
deps/fmt vendored

@ -1 +1 @@
Subproject commit 2ae6bca488795929a0207d109e135751f10c53d9
Subproject commit 0d25f6fcbbf0a867b939a5501965ee4462b21ee6

2
deps/mongoose vendored

@ -1 +1 @@
Subproject commit b3fb21dacccfb08b046b73740eec52cd66e944de
Subproject commit 2a541175b56a1aeecd2dc8474f981923ef580af6

2
deps/protobuf vendored

@ -1 +1 @@
Subproject commit 3d9d1a1255583bac550f7bf94f3016e8c238fa5e
Subproject commit 14e74f6a21f2726d25e0e679c59d569f6bc8fe8e

54
premake/base128.lua Normal file
View File

@ -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

64
premake/bitmrc.lua Normal file
View File

@ -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

163
premake/libcryptopp.lua Normal file
View File

@ -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

54
premake/sqlite3.lua Normal file
View File

@ -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

View File

@ -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"

View File

@ -2,10 +2,18 @@
namespace Components
{
bool Loader::Pregame = true;
std::vector<Component*> 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()

View File

@ -22,7 +22,10 @@ namespace Components
static bool PerformingUnitTests();
static void Register(Component* component);
static bool IsPregame();
private:
static bool Pregame;
static std::vector<Component*> 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"

View File

@ -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<char*>(kernel32Str), sizeof kernel32Str), -1).data());
FARPROC loadLibA = GetProcAddress(kernel32, Utils::String::XOR(std::string(reinterpret_cast<char*>(loadLibAStr), sizeof loadLibAStr), -1).data());
FARPROC loadLibW = GetProcAddress(kernel32, Utils::String::XOR(std::string(reinterpret_cast<char*>(loadLibWStr), sizeof loadLibWStr), -1).data());
if (kernel32)
{
FARPROC loadLibA = GetProcAddress(kernel32, Utils::String::XOR(std::string(reinterpret_cast<char*>(loadLibAStr), sizeof loadLibAStr), -1).data());
FARPROC loadLibW = GetProcAddress(kernel32, Utils::String::XOR(std::string(reinterpret_cast<char*>(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();

View File

@ -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<Game::GfxImage>();
if (!image)

View File

@ -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));

View File

@ -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<uint32_t>(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<uint32_t>(atoi(params[1]));
Auth::IncreaseSecurityLevel(level);
}
});
}
UIScript::Add("security_increase_cancel", [] ()
{

View File

@ -39,7 +39,7 @@ namespace Components
Bans::BanList list;
Bans::LoadBans(&list);
Bans::AccessMutex.lock();
std::lock_guard<std::mutex> _(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<std::mutex> _(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)

View File

@ -0,0 +1,369 @@
#include "STDInclude.hpp"
#ifndef DISABLE_BITMESSAGE
#include <Shlwapi.h>
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<std::shared_timed_mutex> 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<std::shared_timed_mutex> 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<std::shared_timed_mutex> 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<std::shared_timed_mutex> 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

View File

@ -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

View File

@ -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);

View File

@ -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);

View File

@ -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<HWND*>(0x64A3288)) != FALSE)
{
SetWindowTextA(*reinterpret_cast<HWND*>(0x64A3288), Utils::String::VA("IW4x(r" REVISION_STR REVISION_SUFFIX ") : %s", hostname.data()));
SetWindowTextA(*reinterpret_cast<HWND*>(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;
@ -396,6 +394,8 @@ namespace Components
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<uint32_t>(-1));
});
}).detach();
}
else
{
@ -478,10 +478,23 @@ namespace Components
*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<char*>(0x5A44B4, "IW4x: r" REVISION_STR "> ");
Utils::Hook::Set<char*>(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);

View File

@ -13,9 +13,9 @@ namespace Components
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);
static void SetSkipShutdown();
static void FreeNativeConsole();
private:
// Text-based console stuff
@ -35,6 +35,7 @@ namespace Components
static int LineBufferIndex;
static bool HasConsole;
static bool SkipShutdown;
static std::thread ConsoleThread;
@ -59,5 +60,8 @@ namespace Components
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);
};
}

View File

@ -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_t>(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<http_message*>(ev_data);
Download::FileDownload* fDownload = reinterpret_cast<Download::FileDownload*>(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<size_t>(*reinterpret_cast<int*>(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<unsigned int>(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<int>(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<Game::dvar_t*>()->modified = true;
mod.clear();
Localization::ClearTemp();
Command::Execute("closemenu mod_download_popmenu", true);
if (Dvar::Var("cl_modVidRestart").Get<bool>())
{
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();
}
}
}

View File

@ -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<File> 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);
};
}

View File

@ -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(&ltime, &time);
strftime(filename, sizeof(filename) - 1, "iw4x-" VERSION_STR "-%Y%m%d%H%M%S.dmp", &ltime);
HANDLE hFile = CreateFileA(filename, GENERIC_WRITE, FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
if (hFile && hFile != INVALID_HANDLE_VALUE)
// 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<DWORD>(_ReturnAddress());
ContextRecord.Esp = reinterpret_cast<DWORD>(_AddressOfReturnAddress());
ContextRecord.Ebp = *reinterpret_cast<DWORD*>(_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();
}
}

View File

@ -1,18 +1,22 @@
namespace Components
{
class Exception : public Component
{
public:
Exception();
~Exception();
#ifdef DEBUG
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;
};
}

View File

@ -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();
}

View File

@ -2,6 +2,7 @@
namespace Components
{
std::mutex Localization::LocalizeMutex;
Dvar::Var Localization::UseLocalization;
Utils::Memory::Allocator Localization::MemAllocator;
std::map<std::string, Game::LocalizedEntry*> Localization::LocalizeMap;
@ -9,6 +10,8 @@ namespace Components
void Localization::Set(std::string key, std::string value)
{
std::lock_guard<std::mutex> _(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<bool>()) return key;
Game::LocalizedEntry* entry = nullptr;
std::lock_guard<std::mutex> _(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<std::mutex> _(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<std::mutex> _(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<std::mutex> _(Localization::LocalizeMutex);
if (Localization::TempLocalizeMap.find(filename) != Localization::TempLocalizeMap.end())
{

View File

@ -17,6 +17,7 @@ namespace Components
static void ClearTemp();
private:
static std::mutex LocalizeMutex;
static Utils::Memory::Allocator MemAllocator;
static std::map<std::string, Game::LocalizedEntry*> LocalizeMap;
static std::map<std::string, Game::LocalizedEntry*> TempLocalizeMap;

View File

@ -4,6 +4,7 @@ namespace Components
{
std::mutex Logger::MessageMutex;
std::vector<std::string> Logger::MessageQueue;
std::vector<Network::Address> 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<std::mutex> _(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<unsigned int>(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<unsigned int>(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();

View File

@ -24,13 +24,17 @@ namespace Components
private:
static std::mutex MessageMutex;
static std::vector<std::string> MessageQueue;
static std::vector<Network::Address> 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);
};
}

View File

@ -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<unsigned int>(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<char>(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()

View File

@ -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();
};
}

View File

@ -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()

View File

@ -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<std::string> 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(&ltime, &time);
strftime(filenameFriendlyTime, sizeof(filenameFriendlyTime) - 1, "%Y%m%d%H%M%S", &ltime);
// 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<std::string, std::string> 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<std::string, std::string> 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
}

View File

@ -0,0 +1,75 @@
#pragma once
#pragma warning(push)
#pragma warning(disable: 4091)
#include <dbghelp.h>
#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<std::string, std::string> 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<std::string> targetUrls;
#else
static const std::string targetAddress;
static const unsigned int maxSegmentSize;
#endif
};
}

View File

@ -60,7 +60,7 @@ namespace Components
{
auto fsGame = Dvar::Var("fs_game");
fsGame.Set("");
fsGame.Get<Game::dvar_t*>()->pad2[0] = 1;
fsGame.Get<Game::dvar_t*>()->modified = true;
if (Dvar::Var("cl_modVidRestart").Get<bool>())
{
@ -76,7 +76,7 @@ namespace Components
{
auto fsGame = Dvar::Var("fs_game");
fsGame.Set(fmt::sprintf("mods/%s", mod.data()));
fsGame.Get<Game::dvar_t*>()->pad2[0] = 1;
fsGame.Get<Game::dvar_t*>()->modified = true;
if (Dvar::Var("cl_modVidRestart").Get<bool>())
{

View File

@ -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 = "<Cache ID=\"game\" Version=\"";
auto pos = caches.find(str);
if (pos != std::string::npos)
{
caches = caches.substr(pos + str.size());
pos = caches.find_first_of("\"");
if (pos != std::string::npos)
{
caches = caches.substr(0, pos);
int version = atoi(caches.data());
Dvar::Var("cl_updateversion").Get<Game::dvar_t*>()->current.integer = version;
Dvar::Var("cl_updateavailable").Get<Game::dvar_t*>()->current.boolean = (version > REVISION);
}
}
}
}
News::News()
{
Dvar::Register<int>("cl_updateoldversion", REVISION, REVISION, REVISION, Game::DVAR_FLAG_WRITEPROTECTED, "Current version number.");
Dvar::Register<int>("cl_updateversion", 0, 0, -1, Game::DVAR_FLAG_WRITEPROTECTED, "New version number.");
Dvar::Register<bool>("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<Game::dvar_t*>()->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();

View File

@ -14,5 +14,9 @@ namespace Components
private:
static std::thread Thread;
static bool Terminate;
static void CheckForUpdate();
static void ExitProcessStub(unsigned int exitCode);
};
}

View File

@ -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::milliseconds>(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;
}
}

View File

@ -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<const char*>());
info.Set("isPrivate", (Dvar::Var("g_password").Get<std::string>().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<uint32_t>(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<std::string>() != 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<std::string>().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<bool>())
{
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());
}
}
}
}
}

View File

@ -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,6 +29,9 @@ namespace Components
std::string Challenge;
DWORD JoinTime;
bool Valid;
int MatchType;
Utils::InfoString Info;
// Party-specific stuff
DWORD RequestTime;
@ -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);
};
}

View File

@ -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();
}
}
}

View File

@ -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);
};
}

View File

@ -173,26 +173,34 @@ namespace Components
Utils::Hook::Set<char*>(0x6431D1, BASEGAME);
// UI version string
Utils::Hook::Set<char*>(0x43F73B, "IW4x: r" REVISION_STR REVISION_SUFFIX "-" MILESTONE);
Utils::Hook::Set<char*>(0x43F73B, "IW4x: " VERSION);
// console version string
Utils::Hook::Set<char*>(0x4B12BB, "IW4x r" REVISION_STR REVISION_SUFFIX "-" MILESTONE " (built " __DATE__ " " __TIME__ ")");
Utils::Hook::Set<char*>(0x4B12BB, "IW4x " VERSION " (built " __DATE__ " " __TIME__ ")");
// version string
Utils::Hook::Set<char*>(0x60BD56, "IW4x (r" REVISION_STR REVISION_SUFFIX ")");
Utils::Hook::Set<char*>(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<Game::dvar_t**>(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<char*>(0x4289E8, "IW4x (r" REVISION_STR REVISION_SUFFIX "): ZoneBuilder");
Utils::Hook::Set<char*>(0x4289E8, "IW4x (" VERSION "): ZoneBuilder");
}
else if (Dedicated::IsEnabled())
{
Utils::Hook::Set<char*>(0x4289E8, "IW4x (r" REVISION_STR REVISION_SUFFIX "): Dedicated");
Utils::Hook::Set<char*>(0x4289E8, "IW4x (r" VERSION "): Dedicated");
}
else
{
Utils::Hook::Set<char*>(0x4289E8, "IW4x (r" REVISION_STR REVISION_SUFFIX "): Console");
Utils::Hook::Set<char*>(0x4289E8, "IW4x (r" VERSION "): Console");
}
// window title
@ -202,7 +210,7 @@ namespace Components
Utils::Hook::Set<char*>(0x4D378B, "IW4Host");
// shortversion
Utils::Hook::Set<char*>(0x60BD91, VERSION_STR);
Utils::Hook::Set<char*>(0x60BD91, SHORTVERSION);
// console logo
Utils::Hook::Set<char*>(0x428A66, BASEGAME "/images/logo.bmp");
@ -236,6 +244,15 @@ namespace Components
Utils::Hook::Set<BYTE>(0x6832BA, 0xEB);
Utils::Hook::Set<BYTE>(0x4BD190, 0xC3);
//*(BYTE*)0x4BB250 = 0x33;
//*(BYTE*)0x4BB251 = 0xC0;
//*(DWORD*)0x4BB252 = 0xC3909090;
// remove 'impure stats' checking
Utils::Hook::Set<BYTE>(0x4BB250, 0x33);
Utils::Hook::Set<BYTE>(0x4BB251, 0xC0);
Utils::Hook::Set<DWORD>(0x4BB252, 0xC3909090);
// default sv_pure to 0
Utils::Hook::Set<BYTE>(0x4D3A74, 0);

View File

@ -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

View File

@ -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<const char*>());
info.Set("isPrivate", (Dvar::Var("g_password").Get<std::string>().empty() ? "0" : "1"));
info.Set("checksum", fmt::sprintf("%X", Utils::Cryptography::JenkinsOneAtATime::Compute(fmt::sprintf("%u", Game::Sys_Milliseconds()))));

View File

@ -381,7 +381,7 @@ namespace Components
void ServerList::Insert(Network::Address address, Utils::InfoString info)
{
ServerList::RefreshContainer.Mutex.lock();
std::lock_guard<std::mutex> _(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<std::mutex> _(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

View File

@ -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);

View File

@ -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];

View File

@ -103,7 +103,7 @@ namespace Components
{
if (Toast::Queue.empty()) return;
Toast::Mutex.lock();
std::lock_guard<std::mutex> _(Toast::Mutex);
Toast::UIToast* toast = &Toast::Queue.front();
@ -121,8 +121,6 @@ namespace Components
{
Toast::Draw(toast);
}
Toast::Mutex.unlock();
}
Toast::Toast()

View File

@ -143,7 +143,6 @@ namespace Components
if (Window::CursorVisible)
{
// TODO: Apply custom cursor
SetCursor(LoadCursor(NULL, IDC_ARROW));
while ((value = ShowCursor(TRUE)) < 0);

View File

@ -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);

View File

@ -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)

View File

@ -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"

View File

@ -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() {}
};

View File

@ -1,9 +1,9 @@
#pragma once
// Version number
#include <version.hpp>
#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 <json11.hpp>
#include <tomcrypt.h>
#include <wink/signal.hpp>
#ifndef DISABLE_BITMESSAGE
#include <BitMRC.h>
#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

View File

@ -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<char>(length);
// Make sure the buffer is large enough
if (length < 100) length *= 10;
char* buffer = allocator.AllocateArray<char>(length);
if (compress2(reinterpret_cast<Bytef*>(buffer), &length, reinterpret_cast<Bytef*>(const_cast<char*>(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<size_t>(CHUNK), data.size() - (dataPtr - data.data()));
stream.next_in = reinterpret_cast<const uint8_t*>(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 "";

View File

@ -20,7 +20,7 @@ namespace Utils
}
else
{
for (unsigned int i = (this->TokenString.size() - 1); i >= 0; i--)
for (int i = static_cast<int>(this->TokenString.size() - 1); i >= 0; --i)
{
if (this->TokenString[i] == 0xFF)
{

View File

@ -136,11 +136,10 @@ namespace Utils
Hook* Hook::Install(bool unprotect, bool keepUnportected)
{
Hook::StateMutex.lock();
std::lock_guard<std::mutex> _(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;
}

View File

@ -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;
}
}
}

View File

@ -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);
}
}

View File

@ -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<float>(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<unsigned char*>(const_cast<char*>(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<char*>(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<RPC_CSTR*>(&strdata)))
{
return std::string(strdata);
}
}
return "";
}
}
}

View File

@ -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();
}
}

View File

@ -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);
}
}

View File

@ -2,6 +2,7 @@ namespace Utils
{
std::string GetMimeType(std::string url);
std::string ParseChallenge(std::string data);
void OutputDebugLastError();
template <typename T> void Merge(std::vector<T>* target, T* source, size_t length)
{

View File

@ -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<WORD>(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);

View File

@ -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;
};