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

19
.gitmodules vendored
View File

@ -23,9 +23,16 @@
path = deps/mongoose path = deps/mongoose
url = https://github.com/cesanta/mongoose.git url = https://github.com/cesanta/mongoose.git
ignore = dirty ignore = dirty
[submodule "deps/fmt"] [submodule "deps/fmt"]
path = deps/fmt path = deps/fmt
url = https://github.com/fmtlib/fmt.git url = https://github.com/fmtlib/fmt.git
[submodule "deps/Wink-Signals"] [submodule "deps/Wink-Signals"]
path = deps/Wink-Signals path = deps/Wink-Signals
url = https://github.com/miguelmartin75/Wink-Signals.git 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 -- Quote the given string input as a C string
function cstrquote(value) function cstrquote(value)
result = value:gsub("\\", "\\\\") result = value:gsub("\\", "\\\\")
@ -11,6 +13,19 @@ function cstrquote(value)
return result return result
end 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 -- Option to allow copying the DLL file to a custom folder after build
newoption { newoption {
trigger = "copy-to", trigger = "copy-to",
@ -43,16 +58,41 @@ newoption {
description = "Always compile unit tests." 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 { newaction {
trigger = "version", trigger = "version",
description = "Returns the version string for the current commit of the source code.", description = "Returns the version string for the current commit of the source code.",
onWorkspace = function(wks) onWorkspace = function(wks)
-- get revision number via git -- get current version via git
local proc = assert(io.popen("git rev-list --count HEAD", "r")) local proc = assert(io.popen(gitVersioningCommand, "r"))
local revNumber = assert(proc:read('*a')):gsub("%s+", "") local gitDescribeOutput = assert(proc:read('*a')):gsub("%s+", "")
proc:close() proc:close()
print(revNumber) print(gitDescribeOutput)
os.exit(0) os.exit(0)
end end
} }
@ -64,60 +104,73 @@ newaction {
-- get revision number via git -- get revision number via git
local proc = assert(io.popen("git rev-list --count HEAD", "r")) local proc = assert(io.popen("git rev-list --count HEAD", "r"))
local revNumber = assert(proc:read('*a')):gsub("%s+", "") 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() proc:close()
-- get whether this is a clean revision (no uncommitted changes) -- get whether this is a clean revision (no uncommitted changes)
proc = assert(io.popen("git status --porcelain", "r")) proc = assert(io.popen("git status --porcelain", "r"))
local revClean = 1 local revDirty = (assert(proc:read('*a')) ~= "")
local revCleanSuffix = "" if revDirty then revDirty = 1 else revDirty = 0 end
if assert(proc:read('*a')) ~= "" then
revClean = 0
revCleanSuffix = " (unclean)"
end
proc:close() proc:close()
-- get current tag name (aka milestone for now) -- 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')) local tagName = assert(proc:read('*l'))
-- get old version number from version.hpp if any -- get old version number from version.hpp if any
local oldRevNumber = "(none)" local oldVersion = "(none)"
local oldRevClean = 1 local oldVersionHeader = io.open(wks.location .. "/src/version.h", "r")
local oldRevCleanSuffix = "" if oldVersionHeader ~= nil then
local oldVersionHeader = io.open(wks.location .. "/src/version.hpp", "r") local oldVersionHeaderContent = assert(oldVersionHeader:read('*l'))
if oldVersionHeader ~=nil then while oldVersionHeaderContent do
local oldVersionHeaderContent = assert(oldVersionHeader:read('*a')) m = string.match(oldVersionHeaderContent, "#define GIT_DESCRIBE (.+)%s*$")
oldRevNumber = string.match(oldVersionHeaderContent, "#define REVISION (%d+)") if m ~= nil then
if oldRevNumber == nil then oldVersion = m
-- old version.hpp format? end
oldRevNumber = "(none)"
oldVersionHeaderContent = oldVersionHeader:read('*l')
end 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 end
-- generate version.hpp with a revision number if not equal -- generate version.hpp with a revision number if not equal
if oldRevNumber ~= revNumber or oldRevClean ~= revClean then gitDescribeOutputQuoted = cstrquote(gitDescribeOutput)
print ("Update " .. oldRevNumber .. oldRevCleanSuffix .. " -> " .. revNumber .. revCleanSuffix) if oldVersion ~= gitDescribeOutputQuoted then
local versionHeader = assert(io.open(wks.location .. "/src/version.hpp", "w")) print ("Update " .. oldVersion .. " -> " .. gitDescribeOutputQuoted)
local versionHeader = assert(io.open(wks.location .. "/src/version.h", "w"))
versionHeader:write("/*\n") versionHeader:write("/*\n")
versionHeader:write(" * Automatically generated by premake5.\n") versionHeader:write(" * Automatically generated by premake5.\n")
versionHeader:write(" * Do not touch, you fucking moron!\n") versionHeader:write(" * Do not touch, you fucking moron!\n")
versionHeader:write(" */\n") versionHeader:write(" */\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 " .. revNumber .. "\n")
versionHeader:write("#define REVISION_CLEAN " .. revClean .. "\n") versionHeader:write("\n")
versionHeader:write("#define MILESTONE " .. cstrquote(tagName) .. "\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() versionHeader:close()
end end
end end
@ -125,16 +178,28 @@ newaction {
depsBasePath = "./deps" depsBasePath = "./deps"
require "premake/base128"
require "premake/bitmrc"
require "premake/fmt" require "premake/fmt"
require "premake/json11" require "premake/json11"
require "premake/libcryptopp"
require "premake/libtomcrypt" require "premake/libtomcrypt"
require "premake/libtommath" require "premake/libtommath"
require "premake/mongoose" require "premake/mongoose"
require "premake/pdcurses" require "premake/pdcurses"
require "premake/protobuf" require "premake/protobuf"
require "premake/sqlite3"
require "premake/winksignals" require "premake/winksignals"
require "premake/zlib" require "premake/zlib"
base128.setup
{
source = path.join(depsBasePath, "base128"),
}
bitmrc.setup
{
source = path.join(depsBasePath, "bitmrc"),
}
fmt.setup fmt.setup
{ {
source = path.join(depsBasePath, "fmt"), source = path.join(depsBasePath, "fmt"),
@ -143,6 +208,10 @@ json11.setup
{ {
source = path.join(depsBasePath, "json11"), source = path.join(depsBasePath, "json11"),
} }
libcryptopp.setup
{
source = path.join(depsBasePath, "bitmrc/libcryptopp"),
}
libtomcrypt.setup libtomcrypt.setup
{ {
defines = { defines = {
@ -171,6 +240,10 @@ protobuf.setup
{ {
source = path.join(depsBasePath, "protobuf"), source = path.join(depsBasePath, "protobuf"),
} }
sqlite3.setup
{
source = path.join(depsBasePath, "bitmrc/windows/sqlite3"),
}
winksignals.setup winksignals.setup
{ {
source = path.join(depsBasePath, "Wink-Signals"), source = path.join(depsBasePath, "Wink-Signals"),
@ -238,6 +311,24 @@ workspace "iw4x"
if _OPTIONS["force-unit-tests"] then if _OPTIONS["force-unit-tests"] then
defines { "FORCE_UNIT_TESTS" } defines { "FORCE_UNIT_TESTS" }
end 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 -- Pre-compiled header
pchheader "STDInclude.hpp" -- must be exactly same as used in #include directives pchheader "STDInclude.hpp" -- must be exactly same as used in #include directives
@ -245,6 +336,12 @@ workspace "iw4x"
buildoptions { "/Zm200" } buildoptions { "/Zm200" }
-- Dependency libraries -- Dependency libraries
if not _OPTIONS["disable-bitmessage"] then
bitmrc.import()
end
if not _OPTIONS["disable-base128"] then
base128.import()
end
fmt.import() fmt.import()
json11.import() json11.import()
libtomcrypt.import() libtomcrypt.import()
@ -348,15 +445,23 @@ workspace "iw4x"
} }
group "External dependencies" 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() fmt.project()
json11.project() json11.project()
libtomcrypt.project() libtomcrypt.project()
libtommath.project() libtommath.project()
mongoose.project() mongoose.project()
pdcurses.project() pdcurses.project()
protobuf.project()
winksignals.project() winksignals.project()
zlib.project() zlib.project()
protobuf.project()
rule "ProtobufCompiler" rule "ProtobufCompiler"
display "Protobuf compiler" display "Protobuf compiler"

View File

@ -2,10 +2,18 @@
namespace Components namespace Components
{ {
bool Loader::Pregame = true;
std::vector<Component*> Loader::Components; std::vector<Component*> Loader::Components;
bool Loader::IsPregame()
{
return Loader::Pregame;
}
void Loader::Initialize() void Loader::Initialize()
{ {
Loader::Pregame = true;
Loader::Register(new Flags()); Loader::Register(new Flags());
Loader::Register(new Singleton()); Loader::Register(new Singleton());
@ -44,7 +52,11 @@ namespace Components
Loader::Register(new Exception()); Loader::Register(new Exception());
Loader::Register(new FastFiles()); Loader::Register(new FastFiles());
Loader::Register(new Materials()); Loader::Register(new Materials());
#ifndef DISABLE_BITMESSAGE
Loader::Register(new BitMessage());
#endif
Loader::Register(new FileSystem()); Loader::Register(new FileSystem());
Loader::Register(new PlayerName());
Loader::Register(new QuickPatch()); Loader::Register(new QuickPatch());
Loader::Register(new ServerInfo()); Loader::Register(new ServerInfo());
Loader::Register(new ServerList()); Loader::Register(new ServerList());
@ -53,8 +65,11 @@ namespace Components
Loader::Register(new AssetHandler()); Loader::Register(new AssetHandler());
Loader::Register(new Localization()); Loader::Register(new Localization());
Loader::Register(new MusicalTalent()); Loader::Register(new MusicalTalent());
Loader::Register(new MinidumpUpload());
Loader::Register(new StructuredData()); Loader::Register(new StructuredData());
Loader::Register(new ConnectProtocol()); Loader::Register(new ConnectProtocol());
Loader::Pregame = false;
} }
void Loader::Uninitialize() void Loader::Uninitialize()

View File

@ -22,7 +22,10 @@ namespace Components
static bool PerformingUnitTests(); static bool PerformingUnitTests();
static void Register(Component* component); static void Register(Component* component);
static bool IsPregame();
private: private:
static bool Pregame;
static std::vector<Component*> Components; static std::vector<Component*> Components;
}; };
} }
@ -38,7 +41,6 @@ namespace Components
#include "Modules\Toast.hpp" #include "Modules\Toast.hpp"
#include "Modules\Colors.hpp" #include "Modules\Colors.hpp"
#include "Modules\D3D9Ex.hpp" #include "Modules\D3D9Ex.hpp"
#include "Modules\Logger.hpp"
#include "Modules\Script.hpp" #include "Modules\Script.hpp"
#include "Modules\Weapon.hpp" #include "Modules\Weapon.hpp"
#include "Modules\Window.hpp" #include "Modules\Window.hpp"
@ -51,6 +53,7 @@ namespace Components
#include "Modules\Node.hpp" #include "Modules\Node.hpp"
#include "Modules\RCon.hpp" #include "Modules\RCon.hpp"
#include "Modules\Party.hpp" // Destroys the order, but requires network classes :D #include "Modules\Party.hpp" // Destroys the order, but requires network classes :D
#include "Modules\Logger.hpp"
#include "Modules\Download.hpp" #include "Modules\Download.hpp"
#include "Modules\Playlist.hpp" #include "Modules\Playlist.hpp"
#include "Modules\RawFiles.hpp" #include "Modules\RawFiles.hpp"
@ -64,7 +67,9 @@ namespace Components
#include "Modules\FastFiles.hpp" #include "Modules\FastFiles.hpp"
#include "Modules\Materials.hpp" #include "Modules\Materials.hpp"
#include "Modules\Singleton.hpp" #include "Modules\Singleton.hpp"
#include "Modules\BitMessage.hpp"
#include "Modules\FileSystem.hpp" #include "Modules\FileSystem.hpp"
#include "Modules\PlayerName.hpp"
#include "Modules\QuickPatch.hpp" #include "Modules\QuickPatch.hpp"
#include "Modules\ServerInfo.hpp" #include "Modules\ServerInfo.hpp"
#include "Modules\ServerList.hpp" #include "Modules\ServerList.hpp"
@ -73,5 +78,6 @@ namespace Components
#include "Modules\AssetHandler.hpp" #include "Modules\AssetHandler.hpp"
#include "Modules\Localization.hpp" #include "Modules\Localization.hpp"
#include "Modules\MusicalTalent.hpp" #include "Modules\MusicalTalent.hpp"
#include "Modules\MinidumpUpload.hpp"
#include "Modules\StructuredData.hpp" #include "Modules\StructuredData.hpp"
#include "Modules\ConnectProtocol.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 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()); 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 #ifdef DEBUG_LOAD_LIBRARY
AntiCheat::LoadLibHook[0].Initialize(loadLibA, LoadLibaryAStub, HOOK_JUMP); AntiCheat::LoadLibHook[0].Initialize(loadLibA, LoadLibaryAStub, HOOK_JUMP);
AntiCheat::LoadLibHook[1].Initialize(loadLibW, LoadLibaryWStub, HOOK_JUMP); AntiCheat::LoadLibHook[1].Initialize(loadLibW, LoadLibaryWStub, HOOK_JUMP);
#else #else
AntiCheat::LoadLibHook[0].Initialize(loadLibA, loadLibStub, HOOK_JUMP); AntiCheat::LoadLibHook[0].Initialize(loadLibA, loadLibStub, HOOK_JUMP);
AntiCheat::LoadLibHook[1].Initialize(loadLibW, loadLibStub, HOOK_JUMP); AntiCheat::LoadLibHook[1].Initialize(loadLibW, loadLibStub, HOOK_JUMP);
#endif #endif
//AntiCheat::LoadLibHook[2].Initialize(LoadLibraryExA, loadLibExStub, HOOK_JUMP); //AntiCheat::LoadLibHook[2].Initialize(LoadLibraryExA, loadLibExStub, HOOK_JUMP);
//AntiCheat::LoadLibHook[3].Initialize(LoadLibraryExW, loadLibExStub, HOOK_JUMP); //AntiCheat::LoadLibHook[3].Initialize(LoadLibraryExW, loadLibExStub, HOOK_JUMP);
}
}
} }
void AntiCheat::ReadIntegrityCheck() void AntiCheat::ReadIntegrityCheck()
@ -156,7 +163,6 @@ namespace Components
if ((Game::Sys_Milliseconds() - lastCheck) > 1000 * 70) if ((Game::Sys_Milliseconds() - lastCheck) > 1000 * 70)
{ {
// TODO: Move that elsewhere
if (HANDLE h = OpenProcess(PROCESS_VM_READ, TRUE, GetCurrentProcessId())) if (HANDLE h = OpenProcess(PROCESS_VM_READ, TRUE, GetCurrentProcessId()))
{ {
#ifdef DEBUG_DETECTIONS #ifdef DEBUG_DETECTIONS
@ -584,37 +590,35 @@ namespace Components
if (NULL != pSecDesc) if (NULL != pSecDesc)
{ {
HeapFree(GetProcessHeap(), 0, pSecDesc); HeapFree(GetProcessHeap(), 0, pSecDesc);
pSecDesc = NULL;
} }
if (NULL != pDacl) if (NULL != pDacl)
{ {
HeapFree(GetProcessHeap(), 0, pDacl); HeapFree(GetProcessHeap(), 0, pDacl);
pDacl = NULL;
} }
if (psidAdmins) if (psidAdmins)
{ {
FreeSid(psidAdmins); FreeSid(psidAdmins);
psidAdmins = NULL;
} }
if (psidSystem) if (psidSystem)
{ {
FreeSid(psidSystem); FreeSid(psidSystem);
psidSystem = NULL;
} }
if (psidEveryone) if (psidEveryone)
{ {
FreeSid(psidEveryone); FreeSid(psidEveryone);
psidEveryone = NULL;
} }
if (NULL != pTokenInfo) if (NULL != pTokenInfo)
{ {
HeapFree(GetProcessHeap(), 0, pTokenInfo); HeapFree(GetProcessHeap(), 0, pTokenInfo);
pTokenInfo = NULL;
} }
if (NULL != hToken) if (NULL != hToken)
{ {
CloseHandle(hToken); CloseHandle(hToken);
hToken = NULL;
} }
} }
@ -646,7 +650,10 @@ namespace Components
Utils::Hook(0x56AC60, AntiCheat::AimTargetGetTagPosStub, HOOK_JUMP).Install()->Quick(); Utils::Hook(0x56AC60, AntiCheat::AimTargetGetTagPosStub, HOOK_JUMP).Install()->Quick();
// TODO: Probably move that :P // TODO: Probably move that :P
AntiCheat::InitLoadLibHook(); if (!Dedicated::IsEnabled())
{
AntiCheat::InitLoadLibHook();
}
// Prevent external processes from accessing our memory // Prevent external processes from accessing our memory
AntiCheat::ProtectProcess(); AntiCheat::ProtectProcess();

View File

@ -5,7 +5,7 @@ namespace Assets
void IGfxImage::Load(Game::XAssetHeader* header, std::string name, Components::ZoneBuilder::Zone* builder) 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; 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>(); image = builder->GetAllocator()->Allocate<Game::GfxImage>();
if (!image) if (!image)

View File

@ -13,8 +13,6 @@ namespace Assets
buffer->PushBlock(Game::XFILE_BLOCK_VIRTUAL); buffer->PushBlock(Game::XFILE_BLOCK_VIRTUAL);
// TODO: I think we have to write them, even if they are NULL
if (asset->name) if (asset->name)
{ {
buffer->SaveString(builder->GetAssetName(this->GetType(), 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); 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()); if (params.Length() < 2)
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()); uint32_t level = Auth::GetZeroBits(Auth::GuidToken, Auth::GuidKey.GetPublicKey());
Logger::Print("Your computation token is: %s\n", Utils::String::DumpHex(Auth::ComputeToken.ToString(), "").data()); 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); Toast::Show("cardicon_locked", "^5Security Level", fmt::sprintf("Your security level is %d", level), 3000);
} }
else else
{ {
uint32_t level = static_cast<uint32_t>(atoi(params[1])); uint32_t level = static_cast<uint32_t>(atoi(params[1]));
Auth::IncreaseSecurityLevel(level); Auth::IncreaseSecurityLevel(level);
} }
}); });
}
UIScript::Add("security_increase_cancel", [] () UIScript::Add("security_increase_cancel", [] ()
{ {

View File

@ -39,7 +39,7 @@ namespace Components
Bans::BanList list; Bans::BanList list;
Bans::LoadBans(&list); Bans::LoadBans(&list);
Bans::AccessMutex.lock(); std::lock_guard<std::mutex> _(Bans::AccessMutex);
if (entry.first.Bits) if (entry.first.Bits)
{ {
@ -102,15 +102,12 @@ namespace Components
FileSystem::FileWriter ban("bans.json"); FileSystem::FileWriter ban("bans.json");
ban.Write(bans.dump()); ban.Write(bans.dump());
Bans::AccessMutex.unlock();
} }
void Bans::LoadBans(Bans::BanList* list) void Bans::LoadBans(Bans::BanList* list)
{ {
Bans::AccessMutex.lock(); std::lock_guard<std::mutex> _(Bans::AccessMutex);
// TODO: Read bans
FileSystem::File bans("bans.json"); FileSystem::File bans("bans.json");
if (bans.Exists()) if (bans.Exists())
@ -162,8 +159,6 @@ namespace Components
} }
} }
} }
Bans::AccessMutex.unlock();
} }
void Bans::BanClientNum(int num, std::string reason) 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) 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); std::string command = Utils::String::ToLower(name);
if (Command::FunctionMapSV.find(command) == Command::FunctionMapSV.end()) if (Command::FunctionMapSV.find(command) == Command::FunctionMapSV.end())
@ -59,9 +70,9 @@ namespace Components
Command::FunctionMapSV[command] = callback; 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)()) void Command::AddRawSV(const char* name, void(*callback)())
@ -134,6 +145,8 @@ namespace Components
Command::Command() Command::Command()
{ {
Assert_Size(Game::cmd_function_t, 24);
// Disable native noclip command // Disable native noclip command
Utils::Hook::Nop(0x474846, 5); Utils::Hook::Nop(0x474846, 5);

View File

@ -34,7 +34,7 @@ namespace Components
static void Add(const char* name, Callback* callback); static void Add(const char* name, Callback* callback);
static void AddSV(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 AddRawSV(const char* name, void(*callback)());
static void Execute(std::string command, bool sync = true); static void Execute(std::string command, bool sync = true);

View File

@ -18,6 +18,7 @@ namespace Components
int Console::LineBufferIndex = 0; int Console::LineBufferIndex = 0;
bool Console::HasConsole = false; bool Console::HasConsole = false;
bool Console::SkipShutdown = false;
std::thread Console::ConsoleThread; std::thread Console::ConsoleThread;
@ -76,14 +77,14 @@ namespace Components
} }
else if(IsWindow(*reinterpret_cast<HWND*>(0x64A3288)) != FALSE) 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() void Console::ShowPrompt()
{ {
wattron(Console::InputWindow, COLOR_PAIR(10) | A_BOLD); wattron(Console::InputWindow, COLOR_PAIR(10) | A_BOLD);
wprintw(Console::InputWindow, "%s> ", VERSION_STR); wprintw(Console::InputWindow, "%s> ", VERSION);
} }
void Console::RefreshOutput() void Console::RefreshOutput()
@ -279,11 +280,12 @@ namespace Components
raw(); raw();
noecho(); 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::InputWindow = newwin(1, Console::Width, Console::Height - 1, 0);
Console::InfoWindow = newwin(1, Console::Width, 0, 0); Console::InfoWindow = newwin(1, Console::Width, 0, 0);
scrollok(Console::OutputWindow, true); scrollok(Console::OutputWindow, true);
idlok(Console::OutputWindow, true);
scrollok(Console::InputWindow, true); scrollok(Console::InputWindow, true);
nodelay(Console::InputWindow, true); nodelay(Console::InputWindow, true);
keypad(Console::InputWindow, true); keypad(Console::InputWindow, true);
@ -343,11 +345,6 @@ namespace Components
const char* p = message; const char* p = message;
while (*p != '\0') while (*p != '\0')
{ {
if (*p == '\n')
{
Console::ScrollOutput(1);
}
if (*p == '^') if (*p == '^')
{ {
char color; char color;
@ -387,6 +384,7 @@ namespace Components
void Console::ConsoleRunner() void Console::ConsoleRunner()
{ {
Console::SkipShutdown = false;
Game::Sys_ShowConsole(); Game::Sys_ShowConsole();
MSG message; MSG message;
@ -395,6 +393,8 @@ namespace Components
TranslateMessage(&message); TranslateMessage(&message);
DispatchMessageA(&message); DispatchMessageA(&message);
} }
if (Console::SkipShutdown) return;
if (Game::Sys_Milliseconds() - Console::LastRefresh > 100 && 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) 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 // We can not force the termination in this thread
// The destructor would be called in this thread // The destructor would be called in this thread
// and would try to join this thread, which is impossible // and would try to join this thread, which is impossible
std::async([] () std::thread([] ()
{ {
std::this_thread::sleep_for(200ms); std::this_thread::sleep_for(200ms);
ExitProcess(static_cast<uint32_t>(-1)); ExitProcess(static_cast<uint32_t>(-1));
}); }).detach();
} }
else else
{ {
@ -477,11 +477,24 @@ namespace Components
// Restore the initial safe area // Restore the initial safe area
*Game::safeArea = Console::OriginalSafeArea; *Game::safeArea = Console::OriginalSafeArea;
} }
void Console::SetSkipShutdown()
{
Console::SkipShutdown = true;
}
void Console::FreeNativeConsole()
{
if (Flags::HasFlag("console") || ZoneBuilder::IsEnabled())
{
FreeConsole();
}
}
Console::Console() Console::Console()
{ {
// Console '%s: %s> ' string // Console '%s: %s> ' string
Utils::Hook::Set<char*>(0x5A44B4, "IW4x: r" REVISION_STR "> "); Utils::Hook::Set<char*>(0x5A44B4, "IW4x: " VERSION "> ");
// Internal console // Internal console
Utils::Hook(0x4F690C, Console::ToggleConsole, HOOK_CALL).Install()->Quick(); 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. 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); Utils::Hook::Nop(0x60BB58, 11);
// Redirect input (]command)
Utils::Hook(0x47025A, 0x4F5770, HOOK_CALL).Install()->Quick();
Utils::Hook(0x60BB68, [] () Utils::Hook(0x60BB68, [] ()
{ {
Console::ConsoleThread = std::thread(Console::ConsoleRunner); Console::ConsoleThread = std::thread(Console::ConsoleRunner);

View File

@ -1,63 +1,67 @@
#define OUTPUT_HEIGHT 250 #define OUTPUT_HEIGHT 250
#define OUTPUT_MAX_TOP (OUTPUT_HEIGHT - (Console::Height - 2)) #define OUTPUT_MAX_TOP (OUTPUT_HEIGHT - (Console::Height - 2))
namespace Components namespace Components
{ {
class Console : public Component class Console : public Component
{ {
public: public:
Console(); Console();
~Console(); ~Console();
#ifdef DEBUG #ifdef DEBUG
const char* GetName() { return "Console"; }; const char* GetName() { return "Console"; };
#endif #endif
private: static void SetSkipShutdown();
static void ToggleConsole();
static char** GetAutoCompleteFileList(const char *path, const char *extension, Game::FsListBehavior_e behavior, int *numfiles, int allocTrackType); static void FreeNativeConsole();
private: private:
// Text-based console stuff // Text-based console stuff
static WINDOW* OutputWindow; static WINDOW* OutputWindow;
static WINDOW* InputWindow; static WINDOW* InputWindow;
static WINDOW* InfoWindow; static WINDOW* InfoWindow;
static int Width; static int Width;
static int Height; static int Height;
static int OutputTop; static int OutputTop;
static int OutBuffer; static int OutBuffer;
static int LastRefresh; static int LastRefresh;
static char LineBuffer[1024]; static char LineBuffer[1024];
static char LineBuffer2[1024]; static char LineBuffer2[1024];
static int LineBufferIndex; static int LineBufferIndex;
static bool HasConsole; static bool HasConsole;
static bool SkipShutdown;
static std::thread ConsoleThread;
static std::thread ConsoleThread;
static Game::SafeArea OriginalSafeArea;
static Game::SafeArea OriginalSafeArea;
static void ShowPrompt();
static void RefreshStatus(); static void ShowPrompt();
static void RefreshOutput(); static void RefreshStatus();
static void ScrollOutput(int amount); static void RefreshOutput();
static void ScrollOutput(int amount);
static const char* Input();
static void Print(const char* message); static const char* Input();
static void Error(const char* format, ...); static void Print(const char* message);
static void Create(); static void Error(const char* format, ...);
static void Destroy(); static void Create();
static void Destroy();
static void StdOutPrint(const char* message);
static void StdOutError(const char* format, ...); static void StdOutPrint(const char* message);
static void StdOutError(const char* format, ...);
static void ConsoleRunner();
static void ConsoleRunner();
static void DrawSolidConsoleStub();
static void StoreSafeArea(); static void DrawSolidConsoleStub();
static void RestoreSafeArea(); 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 namespace Components
{ {
mg_mgr Download::Mgr; 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) bool Download::IsClient(mg_connection *nc)
{ {
@ -114,6 +376,8 @@ namespace Components
Utils::String::Replace(url, "\\", "/"); Utils::String::Replace(url, "\\", "/");
url = url.substr(6); 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) if (url.find_first_of("/") != std::string::npos || (!Utils::String::EndsWith(url, ".iwd") && url != "mod.ff") || strstr(url.data(), "_svr_") != NULL)
{ {
Download::Forbid(nc); Download::Forbid(nc);
@ -283,6 +547,8 @@ namespace Components
nc->flags |= MG_F_SEND_AND_CLOSE; nc->flags |= MG_F_SEND_AND_CLOSE;
} }
#pragma endregion
Download::Download() Download::Download()
{ {
if (Dedicated::IsEnabled()) if (Dedicated::IsEnabled())
@ -308,12 +574,10 @@ namespace Components
} }
else else
{ {
Utils::Hook(0x5AC6E9, [] () UIScript::Add("mod_download_cancel", [] ()
{ {
// TODO: Perform moddownload here Download::CLDownload.Clear();
});
Game::CL_DownloadsComplete(0);
}, HOOK_CALL).Install()->Quick();
} }
} }
@ -325,7 +589,7 @@ namespace Components
} }
else else
{ {
Download::CLDownload.Clear();
} }
} }
} }

View File

@ -10,16 +10,90 @@ namespace Components
const char* GetName() { return "Download"; }; const char* GetName() { return "Download"; };
#endif #endif
static void InitiateClientDownload(std::string mod);
private: 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 mg_mgr Mgr;
static ClientDownload CLDownload;
static void EventHandler(mg_connection *nc, int ev, void *ev_data); static void EventHandler(mg_connection *nc, int ev, void *ev_data);
static void ListHandler(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 FileHandler(mg_connection *nc, int ev, void *ev_data);
static void InfoHandler(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 bool IsClient(mg_connection *nc);
static Game::client_t* GetClient(mg_connection *nc); static Game::client_t* GetClient(mg_connection *nc);
static void Forbid(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 namespace Components
{ {
bool Exception::UploadMinidump(std::string filename) Utils::Hook Exception::SetFilterHook;
{
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;
}
LONG WINAPI Exception::ExceptionFilter(LPEXCEPTION_POINTERS ExceptionInfo) LONG WINAPI Exception::ExceptionFilter(LPEXCEPTION_POINTERS ExceptionInfo)
{ {
char filename[MAX_PATH]; // Pass on harmless errors
__time64_t time; if (ExceptionInfo->ExceptionRecord->ExceptionCode == STATUS_INTEGER_OVERFLOW ||
tm ltime; ExceptionInfo->ExceptionRecord->ExceptionCode == STATUS_FLOAT_OVERFLOW)
_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)
{ {
MINIDUMP_EXCEPTION_INFORMATION ex = { GetCurrentThreadId(), ExceptionInfo, FALSE }; return EXCEPTION_CONTINUE_EXECUTION;
MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), hFile, MiniDumpNormal, &ex, NULL, NULL);
CloseHandle(hFile);
} }
//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) if (ExceptionInfo->ExceptionRecord->ExceptionCode == EXCEPTION_STACK_OVERFLOW)
{ {
Logger::Error("Termination because of a stack overflow.\n"); Logger::Error("Termination because of a stack overflow.\n");
TerminateProcess(GetCurrentProcess(), EXCEPTION_STACK_OVERFLOW);
} }
else else
{ {
Logger::Error("Fatal error (0x%08X) at 0x%08X.", ExceptionInfo->ExceptionRecord->ExceptionCode, ExceptionInfo->ExceptionRecord->ExceptionAddress); Logger::Error("Fatal error (0x%08X) at 0x%08X.", ExceptionInfo->ExceptionRecord->ExceptionCode, ExceptionInfo->ExceptionRecord->ExceptionAddress);
} }
//TerminateProcess(GetCurrentProcess(), ExceptionInfo->ExceptionRecord->ExceptionCode);
return EXCEPTION_CONTINUE_SEARCH; 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); Exception::SetFilterHook.Uninstall();
return lpTopLevelExceptionFilter; 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() Exception::Exception()
{ {
#ifdef DEBUG #ifdef DEBUG
// Display DEBUG branding, so we know we're on a debug build // Display DEBUG branding, so we know we're on a debug build
Renderer::OnFrame([] () Renderer::OnFrame([]()
{ {
Game::Font* font = Game::R_RegisterFont("fonts/normalFont"); Game::Font* font = Game::R_RegisterFont("fonts/normalFont");
float color[4] = { 1.0f, 1.0f, 1.0f, 1.0f }; 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); 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 #endif
Utils::Hook::Set(0x6D70AC, Exception::SetUnhandledExceptionFilterStub); #if !defined(DEBUG) || defined(FORCE_EXCEPTION_HANDLER)
Exception::SetFilterHook.Initialize(SetUnhandledExceptionFilter, Exception::SetUnhandledExceptionFilterStub, HOOK_JUMP);
Exception::SetFilterHook.Install();
SetUnhandledExceptionFilter(&Exception::ExceptionFilter); SetUnhandledExceptionFilter(&Exception::ExceptionFilter);
#endif #endif
Command::Add("mapTest", [] (Command::Params params) Command::Add("mapTest", [](Command::Params params)
{ {
std::string command; std::string command;
int max = (params.Length() >= 2 ? atoi(params[1]) : 16), current = 0; int max = (params.Length() >= 2 ? atoi(params[1]) : 16), current = 0;
for (int i =0;;) for (int i = 0;;)
{ {
char* mapname = Game::mapnames[i]; char* mapname = Game::mapnames[i];
if (!*mapname) if (!*mapname)
@ -129,7 +100,7 @@ namespace Components
continue; 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 else command.append(fmt::sprintf("wait 500;", mapname)); // Test direct map switch
command.append(fmt::sprintf("map %s;", mapname)); command.append(fmt::sprintf("map %s;", mapname));
@ -140,5 +111,63 @@ namespace Components
Command::Execute(command, false); 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 namespace Components
{ {
class Exception : public Component class Exception : public Component
{ {
public: public:
Exception(); Exception();
~Exception();
#ifdef DEBUG #ifdef DEBUG
const char* GetName() { return "Exception"; }; const char* GetName() { return "Exception"; };
#endif #endif
static LPTOP_LEVEL_EXCEPTION_FILTER Hook();
private: private:
static LONG WINAPI ExceptionFilter(LPEXCEPTION_POINTERS ExceptionInfo); static LONG WINAPI ExceptionFilter(LPEXCEPTION_POINTERS ExceptionInfo);
static LPTOP_LEVEL_EXCEPTION_FILTER WINAPI SetUnhandledExceptionFilterStub(LPTOP_LEVEL_EXCEPTION_FILTER lpTopLevelExceptionFilter); 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() Lean::Lean()
{ {
Game::Cmd_AddCommand("+leanleft", Lean::IN_LeanLeft_Down, Command::Allocate(), 1); Command::AddRaw("+leanleft", Lean::IN_LeanLeft_Down, true);
Game::Cmd_AddCommand("-leanleft", Lean::IN_LeanLeft_Up, Command::Allocate(), 1); Command::AddRaw("-leanleft", Lean::IN_LeanLeft_Up, true);
Game::Cmd_AddCommand("+leanright", Lean::IN_LeanRight_Down, Command::Allocate(), 1); Command::AddRaw("+leanright", Lean::IN_LeanRight_Down, true);
Game::Cmd_AddCommand("-leanright", Lean::IN_LeanRight_Up, Command::Allocate(), 1); Command::AddRaw("-leanright", Lean::IN_LeanRight_Up, true);
Utils::Hook(0x5A6D84, Lean::CL_CmdButtonsStub, HOOK_CALL).Install()->Quick(); Utils::Hook(0x5A6D84, Lean::CL_CmdButtonsStub, HOOK_CALL).Install()->Quick();
} }

View File

@ -2,6 +2,7 @@
namespace Components namespace Components
{ {
std::mutex Localization::LocalizeMutex;
Dvar::Var Localization::UseLocalization; Dvar::Var Localization::UseLocalization;
Utils::Memory::Allocator Localization::MemAllocator; Utils::Memory::Allocator Localization::MemAllocator;
std::map<std::string, Game::LocalizedEntry*> Localization::LocalizeMap; std::map<std::string, Game::LocalizedEntry*> Localization::LocalizeMap;
@ -9,6 +10,8 @@ namespace Components
void Localization::Set(std::string key, std::string value) void Localization::Set(std::string key, std::string value)
{ {
std::lock_guard<std::mutex> _(Localization::LocalizeMutex);
if (Localization::LocalizeMap.find(key) != Localization::LocalizeMap.end()) if (Localization::LocalizeMap.find(key) != Localization::LocalizeMap.end())
{ {
Game::LocalizedEntry* entry = Localization::LocalizeMap[key]; Game::LocalizedEntry* entry = Localization::LocalizeMap[key];
@ -46,6 +49,7 @@ namespace Components
if (!Localization::UseLocalization.Get<bool>()) return key; if (!Localization::UseLocalization.Get<bool>()) return key;
Game::LocalizedEntry* entry = nullptr; Game::LocalizedEntry* entry = nullptr;
std::lock_guard<std::mutex> _(Localization::LocalizeMutex);
if (Localization::TempLocalizeMap.find(key) != Localization::TempLocalizeMap.end()) if (Localization::TempLocalizeMap.find(key) != Localization::TempLocalizeMap.end())
{ {
@ -56,7 +60,12 @@ namespace Components
entry = Localization::LocalizeMap[key]; 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) if (entry && entry->value)
{ {
@ -68,6 +77,8 @@ namespace Components
void Localization::SetTemp(std::string key, std::string value) void Localization::SetTemp(std::string key, std::string value)
{ {
std::lock_guard<std::mutex> _(Localization::LocalizeMutex);
if (Localization::TempLocalizeMap.find(key) != Localization::TempLocalizeMap.end()) if (Localization::TempLocalizeMap.find(key) != Localization::TempLocalizeMap.end())
{ {
Game::LocalizedEntry* entry = Localization::TempLocalizeMap[key]; Game::LocalizedEntry* entry = Localization::TempLocalizeMap[key];
@ -100,6 +111,8 @@ namespace Components
void Localization::ClearTemp() void Localization::ClearTemp()
{ {
std::lock_guard<std::mutex> _(Localization::LocalizeMutex);
for (auto i = Localization::TempLocalizeMap.begin(); i != Localization::TempLocalizeMap.end(); ++i) for (auto i = Localization::TempLocalizeMap.begin(); i != Localization::TempLocalizeMap.end(); ++i)
{ {
if (i->second) if (i->second)
@ -131,6 +144,7 @@ namespace Components
AssetHandler::OnFind(Game::XAssetType::ASSET_TYPE_LOCALIZE, [] (Game::XAssetType, std::string filename) AssetHandler::OnFind(Game::XAssetType::ASSET_TYPE_LOCALIZE, [] (Game::XAssetType, std::string filename)
{ {
Game::XAssetHeader header = { 0 }; Game::XAssetHeader header = { 0 };
std::lock_guard<std::mutex> _(Localization::LocalizeMutex);
if (Localization::TempLocalizeMap.find(filename) != Localization::TempLocalizeMap.end()) if (Localization::TempLocalizeMap.find(filename) != Localization::TempLocalizeMap.end())
{ {

View File

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

View File

@ -4,6 +4,7 @@ namespace Components
{ {
std::mutex Logger::MessageMutex; std::mutex Logger::MessageMutex;
std::vector<std::string> Logger::MessageQueue; std::vector<std::string> Logger::MessageQueue;
std::vector<Network::Address> Logger::LoggingAddresses[2];
void(*Logger::PipeCallback)(std::string) = nullptr; void(*Logger::PipeCallback)(std::string) = nullptr;
bool Logger::IsConsoleReady() bool Logger::IsConsoleReady()
@ -79,7 +80,7 @@ namespace Components
void Logger::Frame() void Logger::Frame()
{ {
Logger::MessageMutex.lock(); std::lock_guard<std::mutex> _(Logger::MessageMutex);
for (unsigned int i = 0; i < Logger::MessageQueue.size(); ++i) for (unsigned int i = 0; i < Logger::MessageQueue.size(); ++i)
{ {
@ -92,7 +93,6 @@ namespace Components
} }
Logger::MessageQueue.clear(); Logger::MessageQueue.clear();
Logger::MessageMutex.unlock();
} }
void Logger::PipeOutput(void(*callback)(std::string)) 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() __declspec(naked) void Logger::PrintMessageStub()
{ {
__asm __asm
@ -122,6 +147,11 @@ namespace Components
retn retn
returnPrint: returnPrint:
push 0
push [esp + 0Ch]
call Logger::NetworkLog
add esp, 8h
push esi push esi
mov esi, [esp + 0Ch] mov esi, [esp + 0Ch]
@ -143,11 +173,120 @@ namespace Components
QuickPatch::OnFrame(Logger::Frame); QuickPatch::OnFrame(Logger::Frame);
Utils::Hook(0x4B0218, Logger::GameLogStub, HOOK_CALL).Install()->Quick();
Utils::Hook(Game::Com_PrintMessage, Logger::PrintMessageStub, HOOK_JUMP).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::~Logger()
{ {
Logger::LoggingAddresses[0].clear();
Logger::LoggingAddresses[1].clear();
Logger::MessageMutex.lock(); Logger::MessageMutex.lock();
Logger::MessageQueue.clear(); Logger::MessageQueue.clear();
Logger::MessageMutex.unlock(); Logger::MessageMutex.unlock();

View File

@ -24,13 +24,17 @@ namespace Components
private: private:
static std::mutex MessageMutex; static std::mutex MessageMutex;
static std::vector<std::string> MessageQueue; static std::vector<std::string> MessageQueue;
static std::vector<Network::Address> LoggingAddresses[2];
static void(*PipeCallback)(std::string); static void(*PipeCallback)(std::string);
static void Frame(); static void Frame();
static void GameLogStub();
static void PrintMessageStub(); static void PrintMessageStub();
static void PrintMessagePipe(const char* data); static void PrintMessagePipe(const char* data);
static void EnqueueMessage(std::string message); static void EnqueueMessage(std::string message);
static void NetworkLog(const char* data, bool gLog);
static std::string Format(const char** message); static std::string Format(const char** message);
}; };
} }

View File

@ -2,6 +2,7 @@
namespace Components namespace Components
{ {
int Materials::ImageNameLength;
Utils::Hook Materials::ImageVersionCheckHook; Utils::Hook Materials::ImageVersionCheckHook;
__declspec(naked) void Materials::ImageVersionCheck() __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; 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() __declspec(naked) void Materials::DrawMaterialStub()
{ {
__asm __asm
{ {
push eax push ecx
call Materials::VerifyMaterial call Materials::ResolveMaterial
add esp, 4h add esp, 4h
mov edx, 5310F0h 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::Materials()
{ {
Materials::ImageNameLength = 7;
// Allow codo images // Allow codo images
Materials::ImageVersionCheckHook.Initialize(0x53A456, Materials::ImageVersionCheck, HOOK_CALL)->Install(); Materials::ImageVersionCheckHook.Initialize(0x53A456, Materials::ImageVersionCheck, HOOK_CALL)->Install();
// Fix material pointer exploit // Fix material pointer exploit
Utils::Hook(0x534E0C, Materials::DrawMaterialStub, HOOK_CALL).Install()->Quick(); 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() Materials::~Materials()

View File

@ -11,10 +11,16 @@ namespace Components
#endif #endif
private: private:
static int ImageNameLength;
static Utils::Hook ImageVersionCheckHook; static Utils::Hook ImageVersionCheckHook;
static void ImageVersionCheck(); static void ImageVersionCheck();
static Game::Material* VerifyMaterial(Game::Material* material); static Game::Material* ResolveMaterial(const char* stringPtr);
static void DrawMaterialStub(); 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_reset.menu");
Menus::Add("ui_mp/stats_unlock.menu"); Menus::Add("ui_mp/stats_unlock.menu");
Menus::Add("ui_mp/security_increase_popmenu.menu"); Menus::Add("ui_mp/security_increase_popmenu.menu");
Menus::Add("ui_mp/mod_download_popmenu.menu");
} }
Menus::~Menus() 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"); auto fsGame = Dvar::Var("fs_game");
fsGame.Set(""); fsGame.Set("");
fsGame.Get<Game::dvar_t*>()->pad2[0] = 1; fsGame.Get<Game::dvar_t*>()->modified = true;
if (Dvar::Var("cl_modVidRestart").Get<bool>()) if (Dvar::Var("cl_modVidRestart").Get<bool>())
{ {
@ -76,7 +76,7 @@ namespace Components
{ {
auto fsGame = Dvar::Var("fs_game"); auto fsGame = Dvar::Var("fs_game");
fsGame.Set(fmt::sprintf("mods/%s", mod.data())); 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>()) if (Dvar::Var("cl_modVidRestart").Get<bool>())
{ {

View File

@ -4,6 +4,7 @@
namespace Components namespace Components
{ {
bool News::Terminate;
std::thread News::Thread; std::thread News::Thread;
bool News::UnitTest() bool News::UnitTest()
@ -39,11 +40,110 @@ namespace Components
return result; 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() 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_CHANGELOG_TEXT", "Loading...");
Localization::Set("MPUI_MOTD_TEXT", NEWS_MOTD_DEFUALT); 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([] () News::Thread = std::thread([] ()
{ {
Localization::Set("MPUI_CHANGELOG_TEXT", Utils::WebIO("IW4x", "https://iw4xcachep26muba.onion.to/iw4/changelog.txt").SetTimeout(5000)->Get()); 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); 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::~News()
{ {
News::Terminate = true;
if (News::Thread.joinable()) if (News::Thread.joinable())
{ {
News::Thread.join(); News::Thread.join();

View File

@ -14,5 +14,9 @@ namespace Components
private: private:
static std::thread Thread; 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() void Node::LoadNodePreset()
{ {
Proto::Node::List list;
FileSystem::File defaultNodes("nodes_default.dat"); 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(); for (int i = 0; i < list.address_size(); ++i)
Utils::String::Replace(buffer, "\r", "");
auto nodes = Utils::String::Explode(buffer, '\n');
for (auto node : nodes)
{ {
if (!node.empty()) Node::AddNode(list.address(i));
{
Node::AddNode(node);
}
} }
} }
@ -28,7 +22,7 @@ namespace Components
{ {
Proto::Node::List list; Proto::Node::List list;
std::string nodes = Utils::IO::ReadFile("players/nodes.dat"); 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) for (int i = 0; i < list.address_size(); ++i)
{ {
@ -60,7 +54,10 @@ namespace Components
} }
CreateDirectoryW(L"players", NULL); 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) Node::NodeEntry* Node::FindNode(Network::Address address)
@ -130,7 +127,7 @@ namespace Components
Node::Nodes.push_back(entry); Node::Nodes.push_back(entry);
#ifdef DEBUG #if defined(DEBUG) && !defined(DISABLE_NODE_LOG)
Logger::Print("Adding node %s...\n", address.GetCString()); Logger::Print("Adding node %s...\n", address.GetCString());
#endif #endif
} }
@ -187,7 +184,7 @@ namespace Components
{ {
if (node.state == Node::STATE_INVALID && (Game::Sys_Milliseconds() - node.lastHeard) > NODE_INVALID_DELETE) 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()); Logger::Print("Removing invalid node %s\n", node.address.GetCString());
#endif #endif
} }
@ -231,14 +228,14 @@ namespace Components
Proto::Node::Packet packet; Proto::Node::Packet packet;
packet.set_challenge(entry->challenge); 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()); Logger::Print("Sending registration request to %s\n", entry->address.GetCString());
#endif #endif
Network::SendCommand(entry->address, "nodeRegisterRequest", packet.SerializeAsString()); Network::SendCommand(entry->address, "nodeRegisterRequest", packet.SerializeAsString());
} }
else else
{ {
#ifdef DEBUG #if defined(DEBUG) && !defined(DISABLE_NODE_LOG)
Logger::Print("Sending session request to %s\n", entry->address.GetCString()); Logger::Print("Sending session request to %s\n", entry->address.GetCString());
#endif #endif
Network::SendCommand(entry->address, "sessionRequest"); Network::SendCommand(entry->address, "sessionRequest");
@ -267,7 +264,7 @@ namespace Components
node.lastHeard = Game::Sys_Milliseconds(); node.lastHeard = Game::Sys_Milliseconds();
node.lastTime = 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()); Logger::Print("Node negotiation timed out. Invalidating %s\n", node.address.GetCString());
#endif #endif
} }
@ -399,7 +396,7 @@ namespace Components
if (!entry) return; if (!entry) return;
} }
#ifdef DEBUG #if defined(DEBUG) && !defined(DISABLE_NODE_LOG)
Logger::Print("Received registration request from %s\n", address.GetCString()); Logger::Print("Received registration request from %s\n", address.GetCString());
#endif #endif
@ -440,7 +437,7 @@ namespace Components
Node::NodeEntry* entry = Node::FindNode(address); Node::NodeEntry* entry = Node::FindNode(address);
if (!entry || entry->state != Node::STATE_NEGOTIATING) return; 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()); Logger::Print("Received synchronization data for registration from %s!\n", address.GetCString());
#endif #endif
@ -467,7 +464,7 @@ namespace Components
entry->state = Node::STATE_VALID; entry->state = Node::STATE_VALID;
entry->registered = true; 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("Signature from %s for challenge '%s' is valid!\n", address.GetCString(), entry->challenge.data());
Logger::Print("Node %s registered\n", address.GetCString()); Logger::Print("Node %s registered\n", address.GetCString());
#endif #endif
@ -491,7 +488,7 @@ namespace Components
Node::NodeEntry* entry = Node::FindNode(address); Node::NodeEntry* entry = Node::FindNode(address);
if (!entry || entry->state != Node::STATE_NEGOTIATING) return; 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()); Logger::Print("Received acknowledgment from %s\n", address.GetCString());
#endif #endif
@ -511,7 +508,7 @@ namespace Components
entry->state = Node::STATE_VALID; entry->state = Node::STATE_VALID;
entry->registered = true; 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("Signature from %s for challenge '%s' is valid!\n", address.GetCString(), entry->challenge.data());
Logger::Print("Node %s registered\n", address.GetCString()); Logger::Print("Node %s registered\n", address.GetCString());
#endif #endif
@ -554,7 +551,7 @@ namespace Components
} }
else else
{ {
#ifdef DEBUG #if defined(DEBUG) && !defined(DISABLE_NODE_LOG)
// Unallowed connection // Unallowed connection
Logger::Print("Node list requested by %s, but no valid session was present!\n", address.GetCString()); Logger::Print("Node list requested by %s, but no valid session was present!\n", address.GetCString());
#endif #endif
@ -584,13 +581,13 @@ namespace Components
entry->registered = false; entry->registered = false;
entry->state = Node::STATE_INVALID; entry->state = Node::STATE_INVALID;
#ifdef DEBUG #if defined(DEBUG) && !defined(DISABLE_NODE_LOG)
Logger::Print("Node %s unregistered\n", address.GetCString()); Logger::Print("Node %s unregistered\n", address.GetCString());
#endif #endif
} }
else else
{ {
#ifdef DEBUG #if defined(DEBUG) && !defined(DISABLE_NODE_LOG)
Logger::Print("Node %s tried to unregister using an invalid signature!\n", address.GetCString()); Logger::Print("Node %s tried to unregister using an invalid signature!\n", address.GetCString());
#endif #endif
} }
@ -612,7 +609,7 @@ namespace Components
Node::ClientSession* session = Node::FindSession(address); Node::ClientSession* session = Node::FindSession(address);
if (!session) return; // Registering template session failed, odd... 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()); Logger::Print("Client %s is requesting a new session\n", address.GetCString());
#endif #endif
@ -634,7 +631,7 @@ namespace Components
if (session->challenge == data) if (session->challenge == data)
{ {
#ifdef DEBUG #if defined(DEBUG) && !defined(DISABLE_NODE_LOG)
Logger::Print("Session for %s validated.\n", address.GetCString()); Logger::Print("Session for %s validated.\n", address.GetCString());
#endif #endif
session->valid = true; session->valid = true;
@ -643,7 +640,7 @@ namespace Components
else else
{ {
session->lastTime = -1; session->lastTime = -1;
#ifdef DEBUG #if defined(DEBUG) && !defined(DISABLE_NODE_LOG)
Logger::Print("Challenge mismatch. Validating session for %s failed.\n", address.GetCString()); Logger::Print("Challenge mismatch. Validating session for %s failed.\n", address.GetCString());
#endif #endif
} }
@ -656,7 +653,7 @@ namespace Components
Node::NodeEntry* entry = Node::FindNode(address); Node::NodeEntry* entry = Node::FindNode(address);
if (!entry) return; if (!entry) return;
#ifdef DEBUG #if defined(DEBUG) && !defined(DISABLE_NODE_LOG)
Logger::Print("Session initialization received from %s. Synchronizing...\n", address.GetCString()); Logger::Print("Session initialization received from %s. Synchronizing...\n", address.GetCString());
#endif #endif
@ -673,7 +670,7 @@ namespace Components
entry->registered = true; entry->registered = true;
entry->lastTime = Game::Sys_Milliseconds(); 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()); Logger::Print("Session acknowledged by %s, synchronizing node list...\n", address.GetCString());
#endif #endif
Network::SendCommand(address, "nodeListRequest"); Network::SendCommand(address, "nodeListRequest");
@ -687,7 +684,7 @@ namespace Components
if (data.empty() || !list.ParseFromString(data)) 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()); Logger::Print("Received invalid node list from %s!\n", address.GetCString());
#endif #endif
return; return;
@ -698,7 +695,7 @@ namespace Components
{ {
if (entry->registered) 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()); Logger::Print("Received valid node list with %i entries from %s\n", list.address_size(), address.GetCString());
#endif #endif
@ -771,11 +768,6 @@ namespace Components
Node::AddNode(address); Node::AddNode(address);
} }
} }
else
{
// TODO: Implement client handshake stuff
// Nvm, clients can simply ignore that i guess
}
}); });
Command::Add("listnodes", [] (Command::Params) 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(); auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::high_resolution_clock::now() - startTime).count();
Logger::Print("took %llims\n", duration); 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; return true;
} }
} }

View File

@ -64,6 +64,7 @@ namespace Components
void Party::ConnectError(std::string message) void Party::ConnectError(std::string message)
{ {
Localization::ClearTemp();
Command::Execute("closemenu popup_reconnectingtoparty"); Command::Execute("closemenu popup_reconnectingtoparty");
Dvar::Var("partyend_reason").Set(message); Dvar::Var("partyend_reason").Set(message);
Command::Execute("openmenu menu_xboxlive_partyended"); Command::Execute("openmenu menu_xboxlive_partyended");
@ -308,7 +309,7 @@ namespace Components
info.Set("clients", fmt::sprintf("%i", clientCount)); info.Set("clients", fmt::sprintf("%i", clientCount));
info.Set("sv_maxclients", fmt::sprintf("%i", maxclientCount)); info.Set("sv_maxclients", fmt::sprintf("%i", maxclientCount));
info.Set("protocol", fmt::sprintf("%i", PROTOCOL)); 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("checksum", fmt::sprintf("%d", Game::Sys_Milliseconds()));
info.Set("mapname", Dvar::Var("mapname").Get<const char*>()); info.Set("mapname", Dvar::Var("mapname").Get<const char*>());
info.Set("isPrivate", (Dvar::Var("g_password").Get<std::string>().size() ? "1" : "0")); info.Set("isPrivate", (Dvar::Var("g_password").Get<std::string>().size() ? "1" : "0"));
@ -354,8 +355,9 @@ namespace Components
{ {
// Invalidate handler for future packets // Invalidate handler for future packets
Party::Container.Valid = false; 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())); uint32_t securityLevel = static_cast<uint32_t>(atoi(info.Get("securityLevel").data()));
if (info.Get("challenge") != Party::Container.Challenge) if (info.Get("challenge") != Party::Container.Challenge)
@ -368,48 +370,66 @@ namespace Components
Command::Execute("closemenu popup_reconnectingtoparty"); Command::Execute("closemenu popup_reconnectingtoparty");
Auth::IncreaseSecurityLevel(securityLevel, "reconnect"); Auth::IncreaseSecurityLevel(securityLevel, "reconnect");
} }
else if (!matchType) else if (!Party::Container.MatchType)
{ {
Party::ConnectError("Server is not hosting a match."); Party::ConnectError("Server is not hosting a match.");
} }
// Connect else if(Party::Container.MatchType > 2 || Party::Container.MatchType < 0)
else if (matchType == 1) // Party
{ {
// Send playlist request Party::ConnectError("Invalid join response: Unknown matchtype");
Party::Container.RequestTime = Game::Sys_Milliseconds();
Party::Container.AwaitingPlaylist = true;
Network::SendCommand(address, "getplaylist");
// This is not a safe method
// TODO: Fix actual error!
if (Game::CL_IsCgameInitialized())
{
Command::Execute("disconnect", true);
}
} }
else if (matchType == 2) // Match 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())) Command::Execute("closemenu popup_reconnectingtoparty");
{ Download::InitiateClientDownload(info.Get("fs_game"));
Party::ConnectError("@EXE_SERVERISFULL"); }
} else if (!Dvar::Var("fs_game").Get<std::string>().empty() && info.Get("fs_game").empty())
if (info.Get("mapname") == "" || info.Get("gametype") == "") {
{ Dvar::Var("fs_game").Set("");
Party::ConnectError("Invalid map or gametype.");
}
else
{
Dvar::Var("xblive_privateserver").Set(true);
Game::Menus_CloseAll(Game::uiContext); if (Dvar::Var("cl_modVidRestart").Get<bool>())
{
Game::_XSESSION_INFO hostInfo; Command::Execute("vid_restart", false);
Game::CL_ConnectFromParty(0, &hostInfo, *address.Get(), 0, 0, info.Get("mapname").data(), info.Get("gametype").data());
} }
Command::Execute("reconnect", false);
} }
else 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 PlaylistContinue();
static void PlaylistError(std::string error); static void PlaylistError(std::string error);
static void ConnectError(std::string message);
private: private:
class JoinContainer class JoinContainer
{ {
@ -27,10 +29,13 @@ namespace Components
std::string Challenge; std::string Challenge;
DWORD JoinTime; DWORD JoinTime;
bool Valid; bool Valid;
int MatchType;
Utils::InfoString Info;
// Party-specific stuff // Party-specific stuff
DWORD RequestTime; DWORD RequestTime;
bool AwaitingPlaylist; bool AwaitingPlaylist;
}; };
static JoinContainer Container; static JoinContainer Container;
@ -40,8 +45,6 @@ namespace Components
static Game::dvar_t* RegisterMinPlayers(const char* name, int value, int min, int max, Game::dvar_flag flag, const char* description); static 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); 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); Utils::Hook::Set<char*>(0x6431D1, BASEGAME);
// UI version string // 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 // 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 // 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 // console title
if (ZoneBuilder::IsEnabled()) 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()) 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 else
{ {
Utils::Hook::Set<char*>(0x4289E8, "IW4x (r" REVISION_STR REVISION_SUFFIX "): Console"); Utils::Hook::Set<char*>(0x4289E8, "IW4x (r" VERSION "): Console");
} }
// window title // window title
@ -202,7 +210,7 @@ namespace Components
Utils::Hook::Set<char*>(0x4D378B, "IW4Host"); Utils::Hook::Set<char*>(0x4D378B, "IW4Host");
// shortversion // shortversion
Utils::Hook::Set<char*>(0x60BD91, VERSION_STR); Utils::Hook::Set<char*>(0x60BD91, SHORTVERSION);
// console logo // console logo
Utils::Hook::Set<char*>(0x428A66, BASEGAME "/images/logo.bmp"); 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>(0x6832BA, 0xEB);
Utils::Hook::Set<BYTE>(0x4BD190, 0xC3); 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 // default sv_pure to 0
Utils::Hook::Set<BYTE>(0x4D3A74, 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; if (!Dedicated::IsEnabled()) return;
// Load public key // Load public key

View File

@ -115,7 +115,7 @@ namespace Components
info.Set("gamename", "IW4"); info.Set("gamename", "IW4");
info.Set("sv_maxclients", fmt::sprintf("%i", maxclientCount)); info.Set("sv_maxclients", fmt::sprintf("%i", maxclientCount));
info.Set("protocol", fmt::sprintf("%i", PROTOCOL)); 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("mapname", Dvar::Var("mapname").Get<const char*>());
info.Set("isPrivate", (Dvar::Var("g_password").Get<std::string>().empty() ? "0" : "1")); 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())))); 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) 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();) for (auto i = ServerList::RefreshContainer.Servers.begin(); i != ServerList::RefreshContainer.Servers.end();)
{ {
@ -449,7 +449,7 @@ namespace Components
if (info.Get("gamename") == "IW4" if (info.Get("gamename") == "IW4"
&& server.MatchType && server.MatchType
#ifndef DEBUG #ifndef DEBUG
&& server.Shortversion == VERSION_STR && server.Shortversion == SHORTVERSION
#endif #endif
) )
{ {
@ -469,8 +469,6 @@ namespace Components
++i; ++i;
} }
} }
ServerList::RefreshContainer.Mutex.unlock();
} }
ServerList::ServerInfo* ServerList::GetCurrentServer() ServerList::ServerInfo* ServerList::GetCurrentServer()
@ -625,7 +623,7 @@ namespace Components
ServerList::RefreshContainer.AwatingList = false; ServerList::RefreshContainer.AwatingList = false;
ServerList::RefreshContainer.Mutex.lock(); std::lock_guard<std::mutex> _(ServerList::RefreshContainer.Mutex);
int offset = 0; int offset = 0;
int count = ServerList::RefreshContainer.Servers.size(); 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); Logger::Print("Parsed %d servers from master\n", ServerList::RefreshContainer.Servers.size() - count);
ServerList::RefreshContainer.Mutex.unlock();
}); });
// Set default masterServerName + port and save it // Set default masterServerName + port and save it

View File

@ -13,10 +13,12 @@ namespace Components
{ {
if (Flags::HasFlag("version")) if (Flags::HasFlag("version"))
{ {
printf("IW4x r" REVISION_STR "-" MILESTONE " (built " __DATE__ " " __TIME__ ")\n"); printf("IW4x " VERSION " (built " __DATE__ " " __TIME__ ")\n");
ExitProcess(0); ExitProcess(0);
} }
Console::FreeNativeConsole();
if (Dedicated::IsEnabled() || ZoneBuilder::IsEnabled()) return; if (Dedicated::IsEnabled() || ZoneBuilder::IsEnabled()) return;
Singleton::FirstInstance = (CreateMutexA(NULL, FALSE, "iw4x_mutex") && GetLastError() != ERROR_ALREADY_EXISTS); 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) Game::StringTable* StringTable::LoadObject(std::string filename)
{ {
filename = Utils::String::ToLower(filename);
Game::StringTable* table = nullptr; Game::StringTable* table = nullptr;
FileSystem::File rawTable(filename); FileSystem::File rawTable(filename);
@ -73,6 +75,8 @@ namespace Components
{ {
Game::XAssetHeader header = { 0 }; Game::XAssetHeader header = { 0 };
filename = Utils::String::ToLower(filename);
if (StringTable::StringTableMap.find(filename) != StringTable::StringTableMap.end()) if (StringTable::StringTableMap.find(filename) != StringTable::StringTableMap.end())
{ {
header.stringTable = StringTable::StringTableMap[filename]; header.stringTable = StringTable::StringTableMap[filename];

View File

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

View File

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

View File

@ -36,7 +36,7 @@ namespace Game
typedef void(__cdecl * CL_SelectStringTableEntryInDvar_f_t)(); typedef void(__cdecl * CL_SelectStringTableEntryInDvar_f_t)();
extern CL_SelectStringTableEntryInDvar_f_t CL_SelectStringTableEntryInDvar_f; 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; extern Cmd_AddCommand_t Cmd_AddCommand;
typedef void(__cdecl * Cmd_AddServerCommand_t)(const char* name, void(*callback), cmd_function_t* data); 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 typedef struct dvar_t
{ {
//startbyte:endbyte //startbyte:endbyte
const char* name; //0:3 const char* name; //0:3
const char* description; //4:7 const char* description; //4:7
unsigned int flags; //8:11 unsigned int flags; //8:11
char type; //12:12 char type; //12:12
char pad2[3]; //13:15 bool modified; //13:15
dvar_value_t current; //16:31 dvar_value_t current; //16:31
dvar_value_t latched; //32:47 dvar_value_t latched; //32:47
dvar_value_t _default; //48:64 dvar_value_t _default; //48:64
dvar_maxmin_t min; //65:67 dvar_maxmin_t min; //65:67
dvar_maxmin_t max; //68:72 woooo dvar_maxmin_t max; //68:72 woooo
} dvar_t; } dvar_t;
@ -136,7 +136,7 @@ namespace Game
const char *autoCompleteDir; const char *autoCompleteDir;
const char *autoCompleteExt; const char *autoCompleteExt;
void(__cdecl *function)(); void(__cdecl *function)();
int unknown; bool isKey; // Looks like this is true when the command is a key/button
} cmd_function_t; } cmd_function_t;
#pragma pack(push, 4) #pragma pack(push, 4)

View File

@ -2,7 +2,6 @@
// //
#pragma code_page(65001) #pragma code_page(65001)
#define RESOURCE_DATA
#include "STDInclude.hpp" #include "STDInclude.hpp"
#define APSTUDIO_READONLY_SYMBOLS #define APSTUDIO_READONLY_SYMBOLS
@ -47,8 +46,8 @@ END
// //
VS_VERSION_INFO VERSIONINFO VS_VERSION_INFO VERSIONINFO
FILEVERSION VERSION FILEVERSION VERSION_RC
PRODUCTVERSION VERSION PRODUCTVERSION VERSION_RC
FILEFLAGSMASK 0x3fL FILEFLAGSMASK 0x3fL
#ifdef _DEBUG #ifdef _DEBUG
FILEFLAGS 0x1L FILEFLAGS 0x1L
@ -69,12 +68,12 @@ BEGIN
#else #else
VALUE "FileDescription", "IW4 client modification" VALUE "FileDescription", "IW4 client modification"
#endif #endif
VALUE "FileVersion", VERSION_STR VALUE "FileVersion", SHORTVERSION
VALUE "InternalName", "iw4x" VALUE "InternalName", "iw4x"
VALUE "LegalCopyright", "No rights reserved." VALUE "LegalCopyright", "No rights reserved."
VALUE "OriginalFilename", "iw4x.dll" VALUE "OriginalFilename", "iw4x.dll"
VALUE "ProductName", "IW4x" VALUE "ProductName", "IW4x"
VALUE "ProductVersion", VERSION_STR VALUE "ProductVersion", SHORTVERSION
END END
END END
BLOCK "VarFileInfo" BLOCK "VarFileInfo"

View File

@ -17,6 +17,10 @@ Assert_Size(DWORD, 4);
Assert_Size(WORD, 2); Assert_Size(WORD, 2);
Assert_Size(BYTE, 1); Assert_Size(BYTE, 1);
// 128 bit integers (only x64)
//Assert_Size(__int128, 16);
//Assert_Size(unsigned __int128, 16);
// 64 bit integers // 64 bit integers
Assert_Size(__int64, 8); Assert_Size(__int64, 8);
Assert_Size(unsigned __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 // Disable telemetry data logging
extern "C" extern "C"
{ {
void _cdecl __vcrt_initialize_telemetry_provider() {} void __cdecl __vcrt_initialize_telemetry_provider() {}
void _cdecl __telemetry_main_invoke_trigger() {} void __cdecl __telemetry_main_invoke_trigger() {}
void _cdecl __telemetry_main_return_trigger() {} void __cdecl __telemetry_main_return_trigger() {}
void _cdecl __vcrt_uninitialize_telemetry_provider() {} void __cdecl __vcrt_uninitialize_telemetry_provider() {}
}; };

View File

@ -1,9 +1,9 @@
#pragma once #pragma once
// Version number // Version number
#include <version.hpp> #include "version.h"
#ifndef RESOURCE_DATA #ifndef RC_INVOKED
#define VC_EXTRALEAN #define VC_EXTRALEAN
#define WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN
@ -46,6 +46,7 @@
#pragma warning(disable: 4100) #pragma warning(disable: 4100)
#pragma warning(disable: 4389) #pragma warning(disable: 4389)
#pragma warning(disable: 4702) #pragma warning(disable: 4702)
#pragma warning(disable: 4996) // _CRT_SECURE_NO_WARNINGS
#pragma warning(disable: 6001) #pragma warning(disable: 6001)
#pragma warning(disable: 6011) #pragma warning(disable: 6011)
#pragma warning(disable: 6031) #pragma warning(disable: 6031)
@ -60,6 +61,9 @@
#include <json11.hpp> #include <json11.hpp>
#include <tomcrypt.h> #include <tomcrypt.h>
#include <wink/signal.hpp> #include <wink/signal.hpp>
#ifndef DISABLE_BITMESSAGE
#include <BitMRC.h>
#endif
#ifdef max #ifdef max
#undef max #undef max
@ -109,28 +113,19 @@
#pragma comment(lib, "shlwapi.lib") #pragma comment(lib, "shlwapi.lib")
#pragma comment(lib, "Urlmon.lib") #pragma comment(lib, "Urlmon.lib")
#pragma comment(lib, "Advapi32.lib") #pragma comment(lib, "Advapi32.lib")
#pragma comment(lib, "rpcrt4.lib")
// Enable additional literals // Enable additional literals
using namespace std::literals; using namespace std::literals;
#endif #endif
// Revision number
#define STRINGIZE_(x) #x #define STRINGIZE_(x) #x
#define STRINGIZE(x) STRINGIZE_(x) #define STRINGIZE(x) STRINGIZE_(x)
#define BASEGAME "iw4x" #define BASEGAME "iw4x"
#define CLIENT_CONFIG "iw4x_config.cfg" #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.") #define Assert_Size(x, size) static_assert(sizeof(x) == size, STRINGIZE(x) " structure has an invalid size.")
// Resource stuff // Resource stuff

View File

@ -6,8 +6,13 @@ namespace Utils
{ {
std::string ZLib::Compress(std::string data) std::string ZLib::Compress(std::string data)
{ {
Utils::Memory::Allocator allocator;
unsigned long length = (data.size() * 2); 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) 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.clear();
data.append(buffer, length); data.append(buffer, length);
Utils::Memory::Free(buffer);
return data; return data;
} }
@ -44,6 +47,7 @@ namespace Utils
{ {
stream.avail_in = std::min(static_cast<size_t>(CHUNK), data.size() - (dataPtr - data.data())); stream.avail_in = std::min(static_cast<size_t>(CHUNK), data.size() - (dataPtr - data.data()));
stream.next_in = reinterpret_cast<const uint8_t*>(dataPtr); stream.next_in = reinterpret_cast<const uint8_t*>(dataPtr);
dataPtr += stream.avail_in;
do do
{ {
@ -51,7 +55,7 @@ namespace Utils
stream.next_out = dest; stream.next_out = dest;
ret = inflate(&stream, Z_NO_FLUSH); ret = inflate(&stream, Z_NO_FLUSH);
if (ret == Z_STREAM_ERROR) if (ret != Z_OK && ret != Z_STREAM_END)
{ {
inflateEnd(&stream); inflateEnd(&stream);
return ""; return "";

View File

@ -20,7 +20,7 @@ namespace Utils
} }
else 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) if (this->TokenString[i] == 0xFF)
{ {

View File

@ -136,11 +136,10 @@ namespace Utils
Hook* Hook::Install(bool unprotect, bool keepUnportected) Hook* Hook::Install(bool unprotect, bool keepUnportected)
{ {
Hook::StateMutex.lock(); std::lock_guard<std::mutex> _(Hook::StateMutex);
if (!Hook::Initialized || Hook::Installed) if (!Hook::Initialized || Hook::Installed)
{ {
Hook::StateMutex.unlock();
return this; return this;
} }
@ -159,8 +158,6 @@ namespace Utils
FlushInstructionCache(GetCurrentProcess(), Hook::Place, sizeof(Hook::Buffer)); FlushInstructionCache(GetCurrentProcess(), Hook::Place, sizeof(Hook::Buffer));
Hook::StateMutex.unlock();
return this; return this;
} }

View File

@ -47,5 +47,48 @@ namespace Utils
return buffer; 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); bool FileExists(std::string file);
void WriteFile(std::string file, std::string data); void WriteFile(std::string file, std::string data);
std::string ReadFile(std::string file); std::string ReadFile(std::string file);
bool CreateDirectory(std::string dir);
} }
} }

View File

@ -1,4 +1,7 @@
#include "STDInclude.hpp" #include "STDInclude.hpp"
#ifndef DISABLE_BASE128
#include "base128.h"
#endif
namespace Utils namespace Utils
{ {
@ -115,5 +118,80 @@ namespace Utils
return fmt::sprintf("%02d:%02d:%02d", hoursTotal, minutes, seconds); 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 &Trim(std::string &s);
std::string FormatTimeSpan(int milliseconds); std::string FormatTimeSpan(int milliseconds);
std::string FormatBandwidth(size_t bytes, int milliseconds);
std::string DumpHex(std::string data, std::string separator = " "); std::string DumpHex(std::string data, std::string separator = " ");
std::string XOR(std::string str, char value); 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; if (pos == std::string::npos) return data;
return data.substr(0, pos).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 GetMimeType(std::string url);
std::string ParseChallenge(std::string data); std::string ParseChallenge(std::string data);
void OutputDebugLastError();
template <typename T> void Merge(std::vector<T>* target, T* source, size_t length) 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.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.clear();
WebIO::m_sUrl.raw.append(WebIO::m_sUrl.protocol); WebIO::m_sUrl.raw.append(WebIO::m_sUrl.protocol);
WebIO::m_sUrl.raw.append("://"); WebIO::m_sUrl.raw.append("://");
WebIO::m_sUrl.raw.append(WebIO::m_sUrl.server); 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_sUrl.raw.append(WebIO::m_sUrl.document);
WebIO::m_isFTP = (WebIO::m_sUrl.protocol == "ftp"); WebIO::m_isFTP = (WebIO::m_sUrl.protocol == "ftp");
@ -134,25 +150,33 @@ namespace Utils
return body; 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); 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; 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; headers["Content-Type"] = "multipart/form-data, boundary=" + boundary;
std::string body; Utils::String::Replace(fieldName, "\"", "\\\"");
body.append("--" + boundary + "\r\n"); Utils::String::Replace(fieldName, "\\", "\\\\");
body.append("Content-Disposition: form-data; name=\"" + fieldname + "\"; filename=\"" + filename + "\"\r\n"); Utils::String::Replace(fileName, "\"", "\\\"");
body.append("Content-Type: application/octet-stream\r\n\r\n"); Utils::String::Replace(fileName, "\\", "\\\\");
body.append(data + "\r\n");
body.append("--" + boundary + "--\r\n"); 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()); headers["Content-Length"] = fmt::sprintf("%u", body.size());
@ -209,6 +233,11 @@ namespace Utils
wPort = INTERNET_DEFAULT_HTTPS_PORT; 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* username = (WebIO::m_username.size() ? WebIO::m_username.data() : NULL);
const char* password = (WebIO::m_password.size() ? WebIO::m_password.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); 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 SetURL(std::string url);
void SetCredentials(std::string username, std::string password); 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 url, std::string data, std::string fieldName, std::string fileName);
std::string PostFile(std::string filename, std::string fieldname, std::string data); 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, WebIO::Params params);
std::string Post(std::string url, std::string body); std::string Post(std::string url, std::string body);
@ -74,6 +74,7 @@ namespace Utils
std::string protocol; std::string protocol;
std::string server; std::string server;
std::string document; std::string document;
std::string port;
std::string raw; std::string raw;
}; };