Merge branch 'develop' into 'master'
Develop
This commit is contained in:
commit
ee76ec9340
7
.gitmodules
vendored
7
.gitmodules
vendored
@ -29,3 +29,10 @@
|
||||
[submodule "deps/Wink-Signals"]
|
||||
path = deps/Wink-Signals
|
||||
url = https://github.com/miguelmartin75/Wink-Signals.git
|
||||
[submodule "deps/bitmrc"]
|
||||
path = deps/bitmrc
|
||||
url = git@github.com:iw4x-dev-urandom/BitMRC.git
|
||||
ignore = dirty
|
||||
[submodule "deps/base128"]
|
||||
path = deps/base128
|
||||
url = https://github.com/seizu/base128.git
|
||||
|
1
deps/base128
vendored
Submodule
1
deps/base128
vendored
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit 64c8ab2755e14d316b18aff9746f0180f5fe301b
|
1
deps/bitmrc
vendored
Submodule
1
deps/bitmrc
vendored
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit 43f63eeb1f900fd03b192e7e577c033a6be072ab
|
2
deps/fmt
vendored
2
deps/fmt
vendored
@ -1 +1 @@
|
||||
Subproject commit 2ae6bca488795929a0207d109e135751f10c53d9
|
||||
Subproject commit 0d25f6fcbbf0a867b939a5501965ee4462b21ee6
|
2
deps/mongoose
vendored
2
deps/mongoose
vendored
@ -1 +1 @@
|
||||
Subproject commit b3fb21dacccfb08b046b73740eec52cd66e944de
|
||||
Subproject commit 2a541175b56a1aeecd2dc8474f981923ef580af6
|
2
deps/protobuf
vendored
2
deps/protobuf
vendored
@ -1 +1 @@
|
||||
Subproject commit 3d9d1a1255583bac550f7bf94f3016e8c238fa5e
|
||||
Subproject commit 14e74f6a21f2726d25e0e679c59d569f6bc8fe8e
|
54
premake/base128.lua
Normal file
54
premake/base128.lua
Normal 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
64
premake/bitmrc.lua
Normal 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
163
premake/libcryptopp.lua
Normal 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
54
premake/sqlite3.lua
Normal 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
|
183
premake5.lua
183
premake5.lua
@ -1,3 +1,5 @@
|
||||
gitVersioningCommand = "git describe --tags --dirty --always"
|
||||
|
||||
-- Quote the given string input as a C string
|
||||
function cstrquote(value)
|
||||
result = value:gsub("\\", "\\\\")
|
||||
@ -11,6 +13,19 @@ function cstrquote(value)
|
||||
return result
|
||||
end
|
||||
|
||||
-- Converts tags in "vX.X.X" format to X,X,X.
|
||||
-- In the case where the format does not work fall back to old 4,2,REVISION.
|
||||
function vertonum(value, vernumber)
|
||||
vernum = {}
|
||||
for num in string.gmatch(value, "%d+") do
|
||||
table.insert(vernum, num)
|
||||
end
|
||||
if #vernum < 3 then
|
||||
return "4,2," .. vernumber
|
||||
end
|
||||
return vernum[1] .. "," .. vernum[2] .. "," .. vernum[3]
|
||||
end
|
||||
|
||||
-- Option to allow copying the DLL file to a custom folder after build
|
||||
newoption {
|
||||
trigger = "copy-to",
|
||||
@ -43,16 +58,41 @@ newoption {
|
||||
description = "Always compile unit tests."
|
||||
}
|
||||
|
||||
newoption {
|
||||
trigger = "force-exception-handler",
|
||||
description = "Install custom unhandled exception handler even for Debug builds."
|
||||
}
|
||||
|
||||
newoption {
|
||||
trigger = "force-minidump-upload",
|
||||
description = "Upload minidumps even for Debug builds."
|
||||
}
|
||||
|
||||
newoption {
|
||||
trigger = "disable-bitmessage",
|
||||
description = "Disable use of BitMessage completely."
|
||||
}
|
||||
|
||||
newoption {
|
||||
trigger = "disable-node-log",
|
||||
description = "Disable debugging messages for Nodes in Debug builds."
|
||||
}
|
||||
|
||||
newoption {
|
||||
trigger = "disable-base128",
|
||||
description = "Disable debugging messages for Nodes in Debug builds."
|
||||
}
|
||||
|
||||
newaction {
|
||||
trigger = "version",
|
||||
description = "Returns the version string for the current commit of the source code.",
|
||||
onWorkspace = function(wks)
|
||||
-- get revision number via git
|
||||
local proc = assert(io.popen("git rev-list --count HEAD", "r"))
|
||||
local revNumber = assert(proc:read('*a')):gsub("%s+", "")
|
||||
-- get current version via git
|
||||
local proc = assert(io.popen(gitVersioningCommand, "r"))
|
||||
local gitDescribeOutput = assert(proc:read('*a')):gsub("%s+", "")
|
||||
proc:close()
|
||||
|
||||
print(revNumber)
|
||||
print(gitDescribeOutput)
|
||||
os.exit(0)
|
||||
end
|
||||
}
|
||||
@ -64,60 +104,73 @@ newaction {
|
||||
-- get revision number via git
|
||||
local proc = assert(io.popen("git rev-list --count HEAD", "r"))
|
||||
local revNumber = assert(proc:read('*a')):gsub("%s+", "")
|
||||
|
||||
-- get current version via git
|
||||
local proc = assert(io.popen(gitVersioningCommand, "r"))
|
||||
local gitDescribeOutput = assert(proc:read('*a')):gsub("%s+", "")
|
||||
proc:close()
|
||||
|
||||
-- get whether this is a clean revision (no uncommitted changes)
|
||||
proc = assert(io.popen("git status --porcelain", "r"))
|
||||
local revClean = 1
|
||||
local revCleanSuffix = ""
|
||||
if assert(proc:read('*a')) ~= "" then
|
||||
revClean = 0
|
||||
revCleanSuffix = " (unclean)"
|
||||
end
|
||||
local revDirty = (assert(proc:read('*a')) ~= "")
|
||||
if revDirty then revDirty = 1 else revDirty = 0 end
|
||||
proc:close()
|
||||
|
||||
-- get current tag name (aka milestone for now)
|
||||
proc = assert(io.popen("git tag"))
|
||||
proc = assert(io.popen("git describe --tags --abbrev=0"))
|
||||
local tagName = assert(proc:read('*l'))
|
||||
|
||||
-- get old version number from version.hpp if any
|
||||
local oldRevNumber = "(none)"
|
||||
local oldRevClean = 1
|
||||
local oldRevCleanSuffix = ""
|
||||
local oldVersionHeader = io.open(wks.location .. "/src/version.hpp", "r")
|
||||
if oldVersionHeader ~=nil then
|
||||
local oldVersionHeaderContent = assert(oldVersionHeader:read('*a'))
|
||||
oldRevNumber = string.match(oldVersionHeaderContent, "#define REVISION (%d+)")
|
||||
if oldRevNumber == nil then
|
||||
-- old version.hpp format?
|
||||
oldRevNumber = "(none)"
|
||||
local oldVersion = "(none)"
|
||||
local oldVersionHeader = io.open(wks.location .. "/src/version.h", "r")
|
||||
if oldVersionHeader ~= nil then
|
||||
local oldVersionHeaderContent = assert(oldVersionHeader:read('*l'))
|
||||
while oldVersionHeaderContent do
|
||||
m = string.match(oldVersionHeaderContent, "#define GIT_DESCRIBE (.+)%s*$")
|
||||
if m ~= nil then
|
||||
oldVersion = m
|
||||
end
|
||||
|
||||
oldVersionHeaderContent = oldVersionHeader:read('*l')
|
||||
end
|
||||
oldRevClean = string.match(oldVersionHeaderContent, "#define REVISION_CLEAN (%d+)")
|
||||
if oldRevClean == nil then
|
||||
-- old version.hpp format?
|
||||
oldRevClean = 1
|
||||
elseif oldRevClean ~= "1" then
|
||||
oldRevClean = 0
|
||||
else
|
||||
oldRevClean = 1
|
||||
end
|
||||
end
|
||||
if oldRevClean == 0 then
|
||||
oldRevCleanSuffix = " (unclean)"
|
||||
end
|
||||
|
||||
-- generate version.hpp with a revision number if not equal
|
||||
if oldRevNumber ~= revNumber or oldRevClean ~= revClean then
|
||||
print ("Update " .. oldRevNumber .. oldRevCleanSuffix .. " -> " .. revNumber .. revCleanSuffix)
|
||||
local versionHeader = assert(io.open(wks.location .. "/src/version.hpp", "w"))
|
||||
gitDescribeOutputQuoted = cstrquote(gitDescribeOutput)
|
||||
if oldVersion ~= gitDescribeOutputQuoted then
|
||||
print ("Update " .. oldVersion .. " -> " .. gitDescribeOutputQuoted)
|
||||
local versionHeader = assert(io.open(wks.location .. "/src/version.h", "w"))
|
||||
versionHeader:write("/*\n")
|
||||
versionHeader:write(" * Automatically generated by premake5.\n")
|
||||
versionHeader:write(" * Do not touch, you fucking moron!\n")
|
||||
versionHeader:write(" */\n")
|
||||
versionHeader:write("\n")
|
||||
versionHeader:write("#define GIT_DESCRIBE " .. gitDescribeOutputQuoted .. "\n")
|
||||
versionHeader:write("#define GIT_DIRTY " .. revDirty .. "\n")
|
||||
versionHeader:write("#define GIT_TAG " .. cstrquote(tagName) .. "\n")
|
||||
versionHeader:write("\n")
|
||||
versionHeader:write("// Legacy definitions (needed for update check)\n")
|
||||
versionHeader:write("#define REVISION " .. revNumber .. "\n")
|
||||
versionHeader:write("#define REVISION_CLEAN " .. revClean .. "\n")
|
||||
versionHeader:write("#define MILESTONE " .. cstrquote(tagName) .. "\n")
|
||||
versionHeader:write("\n")
|
||||
versionHeader:write("// Version transformed for RC files\n")
|
||||
versionHeader:write("#define VERSION_RC " .. vertonum(tagName, revNumber) .. "\n")
|
||||
versionHeader:write("\n")
|
||||
versionHeader:write("// Alias definitions\n")
|
||||
versionHeader:write("#define VERSION GIT_DESCRIBE\n")
|
||||
versionHeader:write("#define SHORTVERSION GIT_TAG\n")
|
||||
versionHeader:close()
|
||||
local versionHeader = assert(io.open(wks.location .. "/src/version.hpp", "w"))
|
||||
versionHeader:write("/*\n")
|
||||
versionHeader:write(" * Automatically generated by premake5.\n")
|
||||
versionHeader:write(" * Do not touch, you fucking moron!\n")
|
||||
versionHeader:write(" *\n")
|
||||
versionHeader:write(" * This file exists for reasons of complying with our coding standards.\n")
|
||||
versionHeader:write(" *\n")
|
||||
versionHeader:write(" * The Resource Compiler will ignore any content from C++ header files if they're not from STDInclude.hpp.\n")
|
||||
versionHeader:write(" * That's the reason why we now place all version info in version.h instead.\n")
|
||||
versionHeader:write(" */\n")
|
||||
versionHeader:write("\n")
|
||||
versionHeader:write("#include \".\\version.h\"\n")
|
||||
versionHeader:close()
|
||||
end
|
||||
end
|
||||
@ -125,16 +178,28 @@ newaction {
|
||||
|
||||
depsBasePath = "./deps"
|
||||
|
||||
require "premake/base128"
|
||||
require "premake/bitmrc"
|
||||
require "premake/fmt"
|
||||
require "premake/json11"
|
||||
require "premake/libcryptopp"
|
||||
require "premake/libtomcrypt"
|
||||
require "premake/libtommath"
|
||||
require "premake/mongoose"
|
||||
require "premake/pdcurses"
|
||||
require "premake/protobuf"
|
||||
require "premake/sqlite3"
|
||||
require "premake/winksignals"
|
||||
require "premake/zlib"
|
||||
|
||||
base128.setup
|
||||
{
|
||||
source = path.join(depsBasePath, "base128"),
|
||||
}
|
||||
bitmrc.setup
|
||||
{
|
||||
source = path.join(depsBasePath, "bitmrc"),
|
||||
}
|
||||
fmt.setup
|
||||
{
|
||||
source = path.join(depsBasePath, "fmt"),
|
||||
@ -143,6 +208,10 @@ json11.setup
|
||||
{
|
||||
source = path.join(depsBasePath, "json11"),
|
||||
}
|
||||
libcryptopp.setup
|
||||
{
|
||||
source = path.join(depsBasePath, "bitmrc/libcryptopp"),
|
||||
}
|
||||
libtomcrypt.setup
|
||||
{
|
||||
defines = {
|
||||
@ -171,6 +240,10 @@ protobuf.setup
|
||||
{
|
||||
source = path.join(depsBasePath, "protobuf"),
|
||||
}
|
||||
sqlite3.setup
|
||||
{
|
||||
source = path.join(depsBasePath, "bitmrc/windows/sqlite3"),
|
||||
}
|
||||
winksignals.setup
|
||||
{
|
||||
source = path.join(depsBasePath, "Wink-Signals"),
|
||||
@ -238,6 +311,24 @@ workspace "iw4x"
|
||||
if _OPTIONS["force-unit-tests"] then
|
||||
defines { "FORCE_UNIT_TESTS" }
|
||||
end
|
||||
if _OPTIONS["force-minidump-upload"] then
|
||||
defines { "FORCE_MINIDUMP_UPLOAD" }
|
||||
end
|
||||
if _OPTIONS["force-exception-handler"] then
|
||||
defines { "FORCE_EXCEPTION_HANDLER" }
|
||||
end
|
||||
if _OPTIONS["disable-bitmessage"] then
|
||||
defines { "DISABLE_BITMESSAGE" }
|
||||
removefiles {
|
||||
"./src/Components/Modules/BitMessage.*",
|
||||
}
|
||||
end
|
||||
if _OPTIONS["disable-node-log"] then
|
||||
defines { "DISABLE_NODE_LOG"}
|
||||
end
|
||||
if _OPTIONS["disable-base128"] then
|
||||
defines { "DISABLE_BASE128" }
|
||||
end
|
||||
|
||||
-- Pre-compiled header
|
||||
pchheader "STDInclude.hpp" -- must be exactly same as used in #include directives
|
||||
@ -245,6 +336,12 @@ workspace "iw4x"
|
||||
buildoptions { "/Zm200" }
|
||||
|
||||
-- Dependency libraries
|
||||
if not _OPTIONS["disable-bitmessage"] then
|
||||
bitmrc.import()
|
||||
end
|
||||
if not _OPTIONS["disable-base128"] then
|
||||
base128.import()
|
||||
end
|
||||
fmt.import()
|
||||
json11.import()
|
||||
libtomcrypt.import()
|
||||
@ -348,15 +445,23 @@ workspace "iw4x"
|
||||
}
|
||||
|
||||
group "External dependencies"
|
||||
if not _OPTIONS["disable-bitmessage"] then
|
||||
bitmrc.project()
|
||||
libcryptopp.project()
|
||||
sqlite3.project()
|
||||
end
|
||||
if not _OPTIONS["disable-base128"] then
|
||||
base128.project()
|
||||
end
|
||||
fmt.project()
|
||||
json11.project()
|
||||
libtomcrypt.project()
|
||||
libtommath.project()
|
||||
mongoose.project()
|
||||
pdcurses.project()
|
||||
protobuf.project()
|
||||
winksignals.project()
|
||||
zlib.project()
|
||||
protobuf.project()
|
||||
|
||||
rule "ProtobufCompiler"
|
||||
display "Protobuf compiler"
|
||||
|
@ -2,10 +2,18 @@
|
||||
|
||||
namespace Components
|
||||
{
|
||||
bool Loader::Pregame = true;
|
||||
std::vector<Component*> Loader::Components;
|
||||
|
||||
bool Loader::IsPregame()
|
||||
{
|
||||
return Loader::Pregame;
|
||||
}
|
||||
|
||||
void Loader::Initialize()
|
||||
{
|
||||
Loader::Pregame = true;
|
||||
|
||||
Loader::Register(new Flags());
|
||||
Loader::Register(new Singleton());
|
||||
|
||||
@ -44,7 +52,11 @@ namespace Components
|
||||
Loader::Register(new Exception());
|
||||
Loader::Register(new FastFiles());
|
||||
Loader::Register(new Materials());
|
||||
#ifndef DISABLE_BITMESSAGE
|
||||
Loader::Register(new BitMessage());
|
||||
#endif
|
||||
Loader::Register(new FileSystem());
|
||||
Loader::Register(new PlayerName());
|
||||
Loader::Register(new QuickPatch());
|
||||
Loader::Register(new ServerInfo());
|
||||
Loader::Register(new ServerList());
|
||||
@ -53,8 +65,11 @@ namespace Components
|
||||
Loader::Register(new AssetHandler());
|
||||
Loader::Register(new Localization());
|
||||
Loader::Register(new MusicalTalent());
|
||||
Loader::Register(new MinidumpUpload());
|
||||
Loader::Register(new StructuredData());
|
||||
Loader::Register(new ConnectProtocol());
|
||||
|
||||
Loader::Pregame = false;
|
||||
}
|
||||
|
||||
void Loader::Uninitialize()
|
||||
|
@ -22,7 +22,10 @@ namespace Components
|
||||
static bool PerformingUnitTests();
|
||||
static void Register(Component* component);
|
||||
|
||||
static bool IsPregame();
|
||||
|
||||
private:
|
||||
static bool Pregame;
|
||||
static std::vector<Component*> Components;
|
||||
};
|
||||
}
|
||||
@ -38,7 +41,6 @@ namespace Components
|
||||
#include "Modules\Toast.hpp"
|
||||
#include "Modules\Colors.hpp"
|
||||
#include "Modules\D3D9Ex.hpp"
|
||||
#include "Modules\Logger.hpp"
|
||||
#include "Modules\Script.hpp"
|
||||
#include "Modules\Weapon.hpp"
|
||||
#include "Modules\Window.hpp"
|
||||
@ -51,6 +53,7 @@ namespace Components
|
||||
#include "Modules\Node.hpp"
|
||||
#include "Modules\RCon.hpp"
|
||||
#include "Modules\Party.hpp" // Destroys the order, but requires network classes :D
|
||||
#include "Modules\Logger.hpp"
|
||||
#include "Modules\Download.hpp"
|
||||
#include "Modules\Playlist.hpp"
|
||||
#include "Modules\RawFiles.hpp"
|
||||
@ -64,7 +67,9 @@ namespace Components
|
||||
#include "Modules\FastFiles.hpp"
|
||||
#include "Modules\Materials.hpp"
|
||||
#include "Modules\Singleton.hpp"
|
||||
#include "Modules\BitMessage.hpp"
|
||||
#include "Modules\FileSystem.hpp"
|
||||
#include "Modules\PlayerName.hpp"
|
||||
#include "Modules\QuickPatch.hpp"
|
||||
#include "Modules\ServerInfo.hpp"
|
||||
#include "Modules\ServerList.hpp"
|
||||
@ -73,5 +78,6 @@ namespace Components
|
||||
#include "Modules\AssetHandler.hpp"
|
||||
#include "Modules\Localization.hpp"
|
||||
#include "Modules\MusicalTalent.hpp"
|
||||
#include "Modules\MinidumpUpload.hpp"
|
||||
#include "Modules\StructuredData.hpp"
|
||||
#include "Modules\ConnectProtocol.hpp"
|
||||
|
@ -136,18 +136,25 @@ namespace Components
|
||||
static uint8_t loadLibWStr[] = { 0xB3, 0x90, 0x9E, 0x9B, 0xB3, 0x96, 0x9D, 0x8D, 0x9E, 0x8D, 0x86, 0xA8 }; // LoadLibraryW
|
||||
|
||||
HMODULE kernel32 = GetModuleHandleA(Utils::String::XOR(std::string(reinterpret_cast<char*>(kernel32Str), sizeof kernel32Str), -1).data());
|
||||
FARPROC loadLibA = GetProcAddress(kernel32, Utils::String::XOR(std::string(reinterpret_cast<char*>(loadLibAStr), sizeof loadLibAStr), -1).data());
|
||||
FARPROC loadLibW = GetProcAddress(kernel32, Utils::String::XOR(std::string(reinterpret_cast<char*>(loadLibWStr), sizeof loadLibWStr), -1).data());
|
||||
|
||||
if (kernel32)
|
||||
{
|
||||
FARPROC loadLibA = GetProcAddress(kernel32, Utils::String::XOR(std::string(reinterpret_cast<char*>(loadLibAStr), sizeof loadLibAStr), -1).data());
|
||||
FARPROC loadLibW = GetProcAddress(kernel32, Utils::String::XOR(std::string(reinterpret_cast<char*>(loadLibWStr), sizeof loadLibWStr), -1).data());
|
||||
|
||||
if (loadLibA && loadLibW)
|
||||
{
|
||||
#ifdef DEBUG_LOAD_LIBRARY
|
||||
AntiCheat::LoadLibHook[0].Initialize(loadLibA, LoadLibaryAStub, HOOK_JUMP);
|
||||
AntiCheat::LoadLibHook[1].Initialize(loadLibW, LoadLibaryWStub, HOOK_JUMP);
|
||||
AntiCheat::LoadLibHook[0].Initialize(loadLibA, LoadLibaryAStub, HOOK_JUMP);
|
||||
AntiCheat::LoadLibHook[1].Initialize(loadLibW, LoadLibaryWStub, HOOK_JUMP);
|
||||
#else
|
||||
AntiCheat::LoadLibHook[0].Initialize(loadLibA, loadLibStub, HOOK_JUMP);
|
||||
AntiCheat::LoadLibHook[1].Initialize(loadLibW, loadLibStub, HOOK_JUMP);
|
||||
AntiCheat::LoadLibHook[0].Initialize(loadLibA, loadLibStub, HOOK_JUMP);
|
||||
AntiCheat::LoadLibHook[1].Initialize(loadLibW, loadLibStub, HOOK_JUMP);
|
||||
#endif
|
||||
//AntiCheat::LoadLibHook[2].Initialize(LoadLibraryExA, loadLibExStub, HOOK_JUMP);
|
||||
//AntiCheat::LoadLibHook[3].Initialize(LoadLibraryExW, loadLibExStub, HOOK_JUMP);
|
||||
//AntiCheat::LoadLibHook[2].Initialize(LoadLibraryExA, loadLibExStub, HOOK_JUMP);
|
||||
//AntiCheat::LoadLibHook[3].Initialize(LoadLibraryExW, loadLibExStub, HOOK_JUMP);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AntiCheat::ReadIntegrityCheck()
|
||||
@ -156,7 +163,6 @@ namespace Components
|
||||
|
||||
if ((Game::Sys_Milliseconds() - lastCheck) > 1000 * 70)
|
||||
{
|
||||
// TODO: Move that elsewhere
|
||||
if (HANDLE h = OpenProcess(PROCESS_VM_READ, TRUE, GetCurrentProcessId()))
|
||||
{
|
||||
#ifdef DEBUG_DETECTIONS
|
||||
@ -584,37 +590,35 @@ namespace Components
|
||||
if (NULL != pSecDesc)
|
||||
{
|
||||
HeapFree(GetProcessHeap(), 0, pSecDesc);
|
||||
pSecDesc = NULL;
|
||||
}
|
||||
if (NULL != pDacl)
|
||||
{
|
||||
HeapFree(GetProcessHeap(), 0, pDacl);
|
||||
pDacl = NULL;
|
||||
}
|
||||
|
||||
if (psidAdmins)
|
||||
{
|
||||
FreeSid(psidAdmins);
|
||||
psidAdmins = NULL;
|
||||
}
|
||||
|
||||
if (psidSystem)
|
||||
{
|
||||
FreeSid(psidSystem);
|
||||
psidSystem = NULL;
|
||||
}
|
||||
|
||||
if (psidEveryone)
|
||||
{
|
||||
FreeSid(psidEveryone);
|
||||
psidEveryone = NULL;
|
||||
}
|
||||
|
||||
if (NULL != pTokenInfo)
|
||||
{
|
||||
HeapFree(GetProcessHeap(), 0, pTokenInfo);
|
||||
pTokenInfo = NULL;
|
||||
}
|
||||
|
||||
if (NULL != hToken)
|
||||
{
|
||||
CloseHandle(hToken);
|
||||
hToken = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
@ -646,7 +650,10 @@ namespace Components
|
||||
Utils::Hook(0x56AC60, AntiCheat::AimTargetGetTagPosStub, HOOK_JUMP).Install()->Quick();
|
||||
|
||||
// TODO: Probably move that :P
|
||||
AntiCheat::InitLoadLibHook();
|
||||
if (!Dedicated::IsEnabled())
|
||||
{
|
||||
AntiCheat::InitLoadLibHook();
|
||||
}
|
||||
|
||||
// Prevent external processes from accessing our memory
|
||||
AntiCheat::ProtectProcess();
|
||||
|
@ -5,7 +5,7 @@ namespace Assets
|
||||
void IGfxImage::Load(Game::XAssetHeader* header, std::string name, Components::ZoneBuilder::Zone* builder)
|
||||
{
|
||||
Game::GfxImage* image = Game::DB_FindXAssetHeader(Game::XAssetType::ASSET_TYPE_IMAGE, name.data()).image;
|
||||
if (image) return; // TODO: Check for default?
|
||||
if (image) return;
|
||||
|
||||
image = builder->GetAllocator()->Allocate<Game::GfxImage>();
|
||||
if (!image)
|
||||
|
@ -13,8 +13,6 @@ namespace Assets
|
||||
|
||||
buffer->PushBlock(Game::XFILE_BLOCK_VIRTUAL);
|
||||
|
||||
// TODO: I think we have to write them, even if they are NULL
|
||||
|
||||
if (asset->name)
|
||||
{
|
||||
buffer->SaveString(builder->GetAssetName(this->GetType(), asset->name));
|
||||
|
@ -391,23 +391,26 @@ namespace Components
|
||||
Logger::Print("Your guid: %llX\n", Steam::SteamUser()->GetSteamID().Bits);
|
||||
});
|
||||
|
||||
Command::Add("securityLevel", [] (Command::Params params)
|
||||
if (!Dedicated::IsEnabled() && !ZoneBuilder::IsEnabled())
|
||||
{
|
||||
if (params.Length() < 2)
|
||||
Command::Add("securityLevel", [] (Command::Params params)
|
||||
{
|
||||
uint32_t level = Auth::GetZeroBits(Auth::GuidToken, Auth::GuidKey.GetPublicKey());
|
||||
Logger::Print("Your current security level is %d\n", level);
|
||||
Logger::Print("Your security token is: %s\n", Utils::String::DumpHex(Auth::GuidToken.ToString(), "").data());
|
||||
Logger::Print("Your computation token is: %s\n", Utils::String::DumpHex(Auth::ComputeToken.ToString(), "").data());
|
||||
if (params.Length() < 2)
|
||||
{
|
||||
uint32_t level = Auth::GetZeroBits(Auth::GuidToken, Auth::GuidKey.GetPublicKey());
|
||||
Logger::Print("Your current security level is %d\n", level);
|
||||
Logger::Print("Your security token is: %s\n", Utils::String::DumpHex(Auth::GuidToken.ToString(), "").data());
|
||||
Logger::Print("Your computation token is: %s\n", Utils::String::DumpHex(Auth::ComputeToken.ToString(), "").data());
|
||||
|
||||
Toast::Show("cardicon_locked", "^5Security Level", fmt::sprintf("Your security level is %d", level), 3000);
|
||||
}
|
||||
else
|
||||
{
|
||||
uint32_t level = static_cast<uint32_t>(atoi(params[1]));
|
||||
Auth::IncreaseSecurityLevel(level);
|
||||
}
|
||||
});
|
||||
Toast::Show("cardicon_locked", "^5Security Level", fmt::sprintf("Your security level is %d", level), 3000);
|
||||
}
|
||||
else
|
||||
{
|
||||
uint32_t level = static_cast<uint32_t>(atoi(params[1]));
|
||||
Auth::IncreaseSecurityLevel(level);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
UIScript::Add("security_increase_cancel", [] ()
|
||||
{
|
||||
|
@ -39,7 +39,7 @@ namespace Components
|
||||
Bans::BanList list;
|
||||
Bans::LoadBans(&list);
|
||||
|
||||
Bans::AccessMutex.lock();
|
||||
std::lock_guard<std::mutex> _(Bans::AccessMutex);
|
||||
|
||||
if (entry.first.Bits)
|
||||
{
|
||||
@ -102,15 +102,12 @@ namespace Components
|
||||
|
||||
FileSystem::FileWriter ban("bans.json");
|
||||
ban.Write(bans.dump());
|
||||
|
||||
Bans::AccessMutex.unlock();
|
||||
}
|
||||
|
||||
void Bans::LoadBans(Bans::BanList* list)
|
||||
{
|
||||
Bans::AccessMutex.lock();
|
||||
std::lock_guard<std::mutex> _(Bans::AccessMutex);
|
||||
|
||||
// TODO: Read bans
|
||||
FileSystem::File bans("bans.json");
|
||||
|
||||
if (bans.Exists())
|
||||
@ -162,8 +159,6 @@ namespace Components
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Bans::AccessMutex.unlock();
|
||||
}
|
||||
|
||||
void Bans::BanClientNum(int num, std::string reason)
|
||||
|
369
src/Components/Modules/BitMessage.cpp
Normal file
369
src/Components/Modules/BitMessage.cpp
Normal 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
|
36
src/Components/Modules/BitMessage.hpp
Normal file
36
src/Components/Modules/BitMessage.hpp
Normal 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
|
@ -46,6 +46,17 @@ namespace Components
|
||||
|
||||
void Command::AddSV(const char* name, Command::Callback* callback)
|
||||
{
|
||||
if (Loader::IsPregame())
|
||||
{
|
||||
MessageBoxA(0, "Registering server commands in pregamestate is illegal!", 0, MB_ICONERROR);
|
||||
|
||||
#ifdef DEBUG
|
||||
__debugbreak();
|
||||
#endif
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
std::string command = Utils::String::ToLower(name);
|
||||
|
||||
if (Command::FunctionMapSV.find(command) == Command::FunctionMapSV.end())
|
||||
@ -59,9 +70,9 @@ namespace Components
|
||||
Command::FunctionMapSV[command] = callback;
|
||||
}
|
||||
|
||||
void Command::AddRaw(const char* name, void(*callback)())
|
||||
void Command::AddRaw(const char* name, void(*callback)(), bool key)
|
||||
{
|
||||
Game::Cmd_AddCommand(name, callback, Command::Allocate(), 0);
|
||||
Game::Cmd_AddCommand(name, callback, Command::Allocate(), key);
|
||||
}
|
||||
|
||||
void Command::AddRawSV(const char* name, void(*callback)())
|
||||
@ -134,6 +145,8 @@ namespace Components
|
||||
|
||||
Command::Command()
|
||||
{
|
||||
Assert_Size(Game::cmd_function_t, 24);
|
||||
|
||||
// Disable native noclip command
|
||||
Utils::Hook::Nop(0x474846, 5);
|
||||
|
||||
|
@ -34,7 +34,7 @@ namespace Components
|
||||
|
||||
static void Add(const char* name, Callback* callback);
|
||||
static void AddSV(const char* name, Callback* callback);
|
||||
static void AddRaw(const char* name, void(*callback)());
|
||||
static void AddRaw(const char* name, void(*callback)(), bool key = false);
|
||||
static void AddRawSV(const char* name, void(*callback)());
|
||||
static void Execute(std::string command, bool sync = true);
|
||||
|
||||
|
@ -18,6 +18,7 @@ namespace Components
|
||||
int Console::LineBufferIndex = 0;
|
||||
|
||||
bool Console::HasConsole = false;
|
||||
bool Console::SkipShutdown = false;
|
||||
|
||||
std::thread Console::ConsoleThread;
|
||||
|
||||
@ -76,14 +77,14 @@ namespace Components
|
||||
}
|
||||
else if(IsWindow(*reinterpret_cast<HWND*>(0x64A3288)) != FALSE)
|
||||
{
|
||||
SetWindowTextA(*reinterpret_cast<HWND*>(0x64A3288), Utils::String::VA("IW4x(r" REVISION_STR REVISION_SUFFIX ") : %s", hostname.data()));
|
||||
SetWindowTextA(*reinterpret_cast<HWND*>(0x64A3288), Utils::String::VA("IW4x(" VERSION ") : %s", hostname.data()));
|
||||
}
|
||||
}
|
||||
|
||||
void Console::ShowPrompt()
|
||||
{
|
||||
wattron(Console::InputWindow, COLOR_PAIR(10) | A_BOLD);
|
||||
wprintw(Console::InputWindow, "%s> ", VERSION_STR);
|
||||
wprintw(Console::InputWindow, "%s> ", VERSION);
|
||||
}
|
||||
|
||||
void Console::RefreshOutput()
|
||||
@ -279,11 +280,12 @@ namespace Components
|
||||
raw();
|
||||
noecho();
|
||||
|
||||
Console::OutputWindow = newpad(OUTPUT_HEIGHT, Console::Width);
|
||||
Console::OutputWindow = newpad(Console::Height - 1, Console::Width);
|
||||
Console::InputWindow = newwin(1, Console::Width, Console::Height - 1, 0);
|
||||
Console::InfoWindow = newwin(1, Console::Width, 0, 0);
|
||||
|
||||
scrollok(Console::OutputWindow, true);
|
||||
idlok(Console::OutputWindow, true);
|
||||
scrollok(Console::InputWindow, true);
|
||||
nodelay(Console::InputWindow, true);
|
||||
keypad(Console::InputWindow, true);
|
||||
@ -343,11 +345,6 @@ namespace Components
|
||||
const char* p = message;
|
||||
while (*p != '\0')
|
||||
{
|
||||
if (*p == '\n')
|
||||
{
|
||||
Console::ScrollOutput(1);
|
||||
}
|
||||
|
||||
if (*p == '^')
|
||||
{
|
||||
char color;
|
||||
@ -387,6 +384,7 @@ namespace Components
|
||||
|
||||
void Console::ConsoleRunner()
|
||||
{
|
||||
Console::SkipShutdown = false;
|
||||
Game::Sys_ShowConsole();
|
||||
|
||||
MSG message;
|
||||
@ -396,6 +394,8 @@ namespace Components
|
||||
DispatchMessageA(&message);
|
||||
}
|
||||
|
||||
if (Console::SkipShutdown) return;
|
||||
|
||||
if (Game::Sys_Milliseconds() - Console::LastRefresh > 100 &&
|
||||
MessageBoxA(0, "The application is not responding anymore, do you want to force its termination?", "Application is not responding", MB_ICONEXCLAMATION | MB_YESNO) == IDYES)
|
||||
{
|
||||
@ -406,11 +406,11 @@ namespace Components
|
||||
// We can not force the termination in this thread
|
||||
// The destructor would be called in this thread
|
||||
// and would try to join this thread, which is impossible
|
||||
std::async([] ()
|
||||
std::thread([] ()
|
||||
{
|
||||
std::this_thread::sleep_for(200ms);
|
||||
ExitProcess(static_cast<uint32_t>(-1));
|
||||
});
|
||||
}).detach();
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -478,10 +478,23 @@ namespace Components
|
||||
*Game::safeArea = Console::OriginalSafeArea;
|
||||
}
|
||||
|
||||
void Console::SetSkipShutdown()
|
||||
{
|
||||
Console::SkipShutdown = true;
|
||||
}
|
||||
|
||||
void Console::FreeNativeConsole()
|
||||
{
|
||||
if (Flags::HasFlag("console") || ZoneBuilder::IsEnabled())
|
||||
{
|
||||
FreeConsole();
|
||||
}
|
||||
}
|
||||
|
||||
Console::Console()
|
||||
{
|
||||
// Console '%s: %s> ' string
|
||||
Utils::Hook::Set<char*>(0x5A44B4, "IW4x: r" REVISION_STR "> ");
|
||||
Utils::Hook::Set<char*>(0x5A44B4, "IW4x: " VERSION "> ");
|
||||
|
||||
// Internal console
|
||||
Utils::Hook(0x4F690C, Console::ToggleConsole, HOOK_CALL).Install()->Quick();
|
||||
@ -522,9 +535,11 @@ namespace Components
|
||||
}
|
||||
else if (Flags::HasFlag("console") || ZoneBuilder::IsEnabled()) // ZoneBuilder uses the game's console, until the native one is adapted.
|
||||
{
|
||||
FreeConsole();
|
||||
Utils::Hook::Nop(0x60BB58, 11);
|
||||
|
||||
// Redirect input (]command)
|
||||
Utils::Hook(0x47025A, 0x4F5770, HOOK_CALL).Install()->Quick();
|
||||
|
||||
Utils::Hook(0x60BB68, [] ()
|
||||
{
|
||||
Console::ConsoleThread = std::thread(Console::ConsoleRunner);
|
||||
|
@ -13,9 +13,9 @@ namespace Components
|
||||
const char* GetName() { return "Console"; };
|
||||
#endif
|
||||
|
||||
private:
|
||||
static void ToggleConsole();
|
||||
static char** GetAutoCompleteFileList(const char *path, const char *extension, Game::FsListBehavior_e behavior, int *numfiles, int allocTrackType);
|
||||
static void SetSkipShutdown();
|
||||
|
||||
static void FreeNativeConsole();
|
||||
|
||||
private:
|
||||
// Text-based console stuff
|
||||
@ -35,6 +35,7 @@ namespace Components
|
||||
static int LineBufferIndex;
|
||||
|
||||
static bool HasConsole;
|
||||
static bool SkipShutdown;
|
||||
|
||||
static std::thread ConsoleThread;
|
||||
|
||||
@ -59,5 +60,8 @@ namespace Components
|
||||
static void DrawSolidConsoleStub();
|
||||
static void StoreSafeArea();
|
||||
static void RestoreSafeArea();
|
||||
|
||||
static void ToggleConsole();
|
||||
static char** GetAutoCompleteFileList(const char *path, const char *extension, Game::FsListBehavior_e behavior, int *numfiles, int allocTrackType);
|
||||
};
|
||||
}
|
||||
|
@ -3,6 +3,268 @@
|
||||
namespace Components
|
||||
{
|
||||
mg_mgr Download::Mgr;
|
||||
Download::ClientDownload Download::CLDownload;
|
||||
|
||||
#pragma region Client
|
||||
|
||||
void Download::InitiateClientDownload(std::string mod)
|
||||
{
|
||||
if (Download::CLDownload.Running) return;
|
||||
|
||||
Localization::SetTemp("MPUI_EST_TIME_LEFT", Utils::String::FormatTimeSpan(0));
|
||||
Localization::SetTemp("MPUI_PROGRESS_DL", "(0/0) %");
|
||||
Localization::SetTemp("MPUI_TRANS_RATE", "0.0 MB/s");
|
||||
|
||||
Command::Execute("openmenu mod_download_popmenu", true);
|
||||
|
||||
Download::CLDownload.Running = true;
|
||||
Download::CLDownload.Mod = mod;
|
||||
Download::CLDownload.TerminateThread = false;
|
||||
Download::CLDownload.Target = Party::Target();
|
||||
Download::CLDownload.Thread = std::thread(Download::ModDownloader, &Download::CLDownload);
|
||||
}
|
||||
|
||||
bool Download::ParseModList(ClientDownload* download, std::string list)
|
||||
{
|
||||
if (!download) return false;
|
||||
download->Files.clear();
|
||||
|
||||
std::string error;
|
||||
json11::Json listData = json11::Json::parse(list, error);
|
||||
|
||||
|
||||
if (!error.empty() || !listData.is_array()) return false;
|
||||
|
||||
download->TotalBytes = 0;
|
||||
|
||||
for (auto& file : listData.array_items())
|
||||
{
|
||||
if (!file.is_object()) return false;
|
||||
|
||||
auto hash = file["hash"];
|
||||
auto name = file["name"];
|
||||
auto size = file["size"];
|
||||
|
||||
if (!hash.is_string() || !name.is_string() || !size.is_number()) return false;
|
||||
|
||||
Download::ClientDownload::File fileEntry;
|
||||
fileEntry.Name = name.string_value();
|
||||
fileEntry.Hash = hash.string_value();
|
||||
fileEntry.Size = static_cast<size_t>(size.number_value());
|
||||
|
||||
if (!fileEntry.Name.empty())
|
||||
{
|
||||
download->Files.push_back(fileEntry);
|
||||
download->TotalBytes += fileEntry.Size;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Download::DownloadHandler(mg_connection *nc, int ev, void* ev_data)
|
||||
{
|
||||
http_message* hm = reinterpret_cast<http_message*>(ev_data);
|
||||
Download::FileDownload* fDownload = reinterpret_cast<Download::FileDownload*>(nc->mgr->user_data);
|
||||
|
||||
if (ev == MG_EV_CONNECT)
|
||||
{
|
||||
if (hm->message.p)
|
||||
{
|
||||
fDownload->downloading = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (ev == MG_EV_RECV)
|
||||
{
|
||||
size_t bytes = static_cast<size_t>(*reinterpret_cast<int*>(ev_data));
|
||||
fDownload->receivedBytes += bytes;
|
||||
fDownload->download->DownBytes += bytes;
|
||||
fDownload->download->TimeStampBytes += bytes;
|
||||
|
||||
double progress = (100.0 / fDownload->download->TotalBytes) * fDownload->download->DownBytes;
|
||||
Localization::SetTemp("MPUI_PROGRESS_DL", fmt::sprintf("(%d/%d) %d%%", fDownload->index + 1, fDownload->download->Files.size(), static_cast<unsigned int>(progress)));
|
||||
|
||||
int delta = Game::Sys_Milliseconds() - fDownload->download->LastTimeStamp;
|
||||
if (delta > 300)
|
||||
{
|
||||
bool doFormat = fDownload->download->LastTimeStamp != 0;
|
||||
fDownload->download->LastTimeStamp = Game::Sys_Milliseconds();
|
||||
|
||||
size_t dataLeft = fDownload->download->TotalBytes - fDownload->download->DownBytes;
|
||||
double timeLeftD = ((1.0 * dataLeft) / fDownload->download->TimeStampBytes) * delta;
|
||||
int timeLeft = static_cast<int>(timeLeftD);
|
||||
|
||||
if (doFormat)
|
||||
{
|
||||
Localization::SetTemp("MPUI_EST_TIME_LEFT", Utils::String::FormatTimeSpan(timeLeft));
|
||||
Localization::SetTemp("MPUI_TRANS_RATE", Utils::String::FormatBandwidth(fDownload->download->TimeStampBytes, delta));
|
||||
}
|
||||
|
||||
fDownload->download->TimeStampBytes = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (ev == MG_EV_HTTP_REPLY)
|
||||
{
|
||||
nc->flags |= MG_F_CLOSE_IMMEDIATELY;
|
||||
fDownload->buffer = std::string(hm->body.p, hm->body.len);
|
||||
fDownload->downloading = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
bool Download::DownloadFile(ClientDownload* download, unsigned int index)
|
||||
{
|
||||
if (!download || download->Files.size() <= index) return false;
|
||||
|
||||
auto file = download->Files[index];
|
||||
|
||||
std::string path = download->Mod + "/" + file.Name;
|
||||
if (Utils::IO::FileExists(path))
|
||||
{
|
||||
std::string data = Utils::IO::ReadFile(path);
|
||||
|
||||
if (data.size() == file.Size && Utils::String::DumpHex(Utils::Cryptography::SHA256::Compute(data), "") == file.Hash)
|
||||
{
|
||||
download->TotalBytes += file.Size;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
std::string url = "http://" + download->Target.GetString() + "/file/" + file.Name;
|
||||
|
||||
Download::FileDownload fDownload;
|
||||
fDownload.file = file;
|
||||
fDownload.index = index;
|
||||
fDownload.download = download;
|
||||
fDownload.downloading = true;
|
||||
fDownload.receivedBytes = 0;
|
||||
|
||||
Utils::String::Replace(url, " ", "%20");
|
||||
|
||||
download->Valid = true;
|
||||
mg_mgr_init(&download->Mgr, &fDownload);
|
||||
mg_connect_http(&download->Mgr, Download::DownloadHandler, url.data(), NULL, NULL);
|
||||
|
||||
while (fDownload.downloading && !fDownload.download->TerminateThread)
|
||||
{
|
||||
mg_mgr_poll(&download->Mgr, 0);
|
||||
}
|
||||
|
||||
mg_mgr_free(&download->Mgr);
|
||||
download->Valid = false;
|
||||
|
||||
if (fDownload.buffer.size() != file.Size || Utils::String::DumpHex(Utils::Cryptography::SHA256::Compute(fDownload.buffer), "") != file.Hash)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Utils::IO::CreateDirectory(download->Mod);
|
||||
Utils::IO::WriteFile(path, fDownload.buffer);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Download::ModDownloader(ClientDownload* download)
|
||||
{
|
||||
if (!download) download = &Download::CLDownload;
|
||||
|
||||
std::string host = "http://" + download->Target.GetString();
|
||||
std::string list = Utils::WebIO("IW4x", host + "/list").SetTimeout(5000)->Get();
|
||||
|
||||
if (list.empty())
|
||||
{
|
||||
if (download->TerminateThread) return;
|
||||
|
||||
download->Thread.detach();
|
||||
download->Clear();
|
||||
|
||||
QuickPatch::Once([] ()
|
||||
{
|
||||
Party::ConnectError("Failed to download the modlist!");
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (download->TerminateThread) return;
|
||||
|
||||
if (!Download::ParseModList(download, list))
|
||||
{
|
||||
if (download->TerminateThread) return;
|
||||
|
||||
download->Thread.detach();
|
||||
download->Clear();
|
||||
|
||||
QuickPatch::Once([] ()
|
||||
{
|
||||
Party::ConnectError("Failed to parse the modlist!");
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (download->TerminateThread) return;
|
||||
|
||||
static std::string mod = download->Mod;
|
||||
|
||||
for (unsigned int i = 0; i < download->Files.size(); ++i)
|
||||
{
|
||||
if (download->TerminateThread) return;
|
||||
|
||||
if (!Download::DownloadFile(download, i))
|
||||
{
|
||||
if (download->TerminateThread) return;
|
||||
|
||||
mod = fmt::sprintf("Failed to download file: %s!", download->Files[i].Name.data());
|
||||
download->Thread.detach();
|
||||
download->Clear();
|
||||
|
||||
QuickPatch::Once([] ()
|
||||
{
|
||||
Dvar::Var("partyend_reason").Set(mod);
|
||||
mod.clear();
|
||||
|
||||
Localization::ClearTemp();
|
||||
Command::Execute("closemenu mod_download_popmenu");
|
||||
Command::Execute("openmenu menu_xboxlive_partyended");
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (download->TerminateThread) return;
|
||||
|
||||
download->Thread.detach();
|
||||
download->Clear();
|
||||
|
||||
// Run this on the main thread
|
||||
QuickPatch::Once([] ()
|
||||
{
|
||||
auto fsGame = Dvar::Var("fs_game");
|
||||
fsGame.Set(mod);
|
||||
fsGame.Get<Game::dvar_t*>()->modified = true;
|
||||
|
||||
mod.clear();
|
||||
|
||||
Localization::ClearTemp();
|
||||
Command::Execute("closemenu mod_download_popmenu", true);
|
||||
|
||||
if (Dvar::Var("cl_modVidRestart").Get<bool>())
|
||||
{
|
||||
Command::Execute("vid_restart", false);
|
||||
}
|
||||
|
||||
Command::Execute("reconnect", false);
|
||||
});
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region Server
|
||||
|
||||
bool Download::IsClient(mg_connection *nc)
|
||||
{
|
||||
@ -114,6 +376,8 @@ namespace Components
|
||||
Utils::String::Replace(url, "\\", "/");
|
||||
url = url.substr(6);
|
||||
|
||||
Utils::String::Replace(url, "%20", " ");
|
||||
|
||||
if (url.find_first_of("/") != std::string::npos || (!Utils::String::EndsWith(url, ".iwd") && url != "mod.ff") || strstr(url.data(), "_svr_") != NULL)
|
||||
{
|
||||
Download::Forbid(nc);
|
||||
@ -283,6 +547,8 @@ namespace Components
|
||||
nc->flags |= MG_F_SEND_AND_CLOSE;
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
Download::Download()
|
||||
{
|
||||
if (Dedicated::IsEnabled())
|
||||
@ -308,12 +574,10 @@ namespace Components
|
||||
}
|
||||
else
|
||||
{
|
||||
Utils::Hook(0x5AC6E9, [] ()
|
||||
UIScript::Add("mod_download_cancel", [] ()
|
||||
{
|
||||
// TODO: Perform moddownload here
|
||||
|
||||
Game::CL_DownloadsComplete(0);
|
||||
}, HOOK_CALL).Install()->Quick();
|
||||
Download::CLDownload.Clear();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -325,7 +589,7 @@ namespace Components
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
Download::CLDownload.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -10,16 +10,90 @@ namespace Components
|
||||
const char* GetName() { return "Download"; };
|
||||
#endif
|
||||
|
||||
static void InitiateClientDownload(std::string mod);
|
||||
|
||||
private:
|
||||
class ClientDownload
|
||||
{
|
||||
public:
|
||||
ClientDownload() : Valid(false), Running(false), TerminateThread(false), TotalBytes(0), DownBytes(0), LastTimeStamp(0), TimeStampBytes(0) {}
|
||||
~ClientDownload() { this->Clear(); }
|
||||
|
||||
bool Running;
|
||||
bool Valid;
|
||||
bool TerminateThread;
|
||||
mg_mgr Mgr;
|
||||
Network::Address Target;
|
||||
std::string Mod;
|
||||
std::thread Thread;
|
||||
|
||||
size_t TotalBytes;
|
||||
size_t DownBytes;
|
||||
|
||||
int LastTimeStamp;
|
||||
size_t TimeStampBytes;
|
||||
|
||||
class File
|
||||
{
|
||||
public:
|
||||
std::string Name;
|
||||
std::string Hash;
|
||||
size_t Size;
|
||||
};
|
||||
|
||||
std::vector<File> Files;
|
||||
|
||||
void Clear()
|
||||
{
|
||||
this->TerminateThread = true;
|
||||
|
||||
if (this->Thread.joinable())
|
||||
{
|
||||
this->Thread.join();
|
||||
}
|
||||
|
||||
this->Running = false;
|
||||
this->Mod.clear();
|
||||
this->Files.clear();
|
||||
|
||||
if (this->Valid)
|
||||
{
|
||||
this->Valid = false;
|
||||
mg_mgr_free(&(this->Mgr));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class FileDownload
|
||||
{
|
||||
public:
|
||||
ClientDownload* download;
|
||||
ClientDownload::File file;
|
||||
|
||||
int timestamp;
|
||||
bool downloading;
|
||||
unsigned int index;
|
||||
std::string buffer;
|
||||
size_t receivedBytes;
|
||||
|
||||
|
||||
};
|
||||
|
||||
static mg_mgr Mgr;
|
||||
static ClientDownload CLDownload;
|
||||
|
||||
static void EventHandler(mg_connection *nc, int ev, void *ev_data);
|
||||
static void ListHandler(mg_connection *nc, int ev, void *ev_data);
|
||||
static void FileHandler(mg_connection *nc, int ev, void *ev_data);
|
||||
static void InfoHandler(mg_connection *nc, int ev, void *ev_data);
|
||||
static void DownloadHandler(mg_connection *nc, int ev, void *ev_data);
|
||||
|
||||
static bool IsClient(mg_connection *nc);
|
||||
static Game::client_t* GetClient(mg_connection *nc);
|
||||
static void Forbid(mg_connection *nc);
|
||||
|
||||
static void ModDownloader(ClientDownload* download);
|
||||
static bool ParseModList(ClientDownload* download, std::string list);
|
||||
static bool DownloadFile(ClientDownload* download, unsigned int index);
|
||||
};
|
||||
}
|
||||
|
@ -9,92 +9,60 @@
|
||||
|
||||
namespace Components
|
||||
{
|
||||
bool Exception::UploadMinidump(std::string filename)
|
||||
{
|
||||
if (Utils::IO::FileExists(filename))
|
||||
{
|
||||
Utils::WebIO webio("Firefucks", "https://reich.io/upload.php");
|
||||
|
||||
std::string buffer = Utils::IO::ReadFile(filename);
|
||||
std::string result = webio.PostFile("minidump.dmp", "files[]", buffer);
|
||||
|
||||
std::string errors;
|
||||
json11::Json object = json11::Json::parse(result, errors);
|
||||
|
||||
if (!object.is_object()) return false;
|
||||
|
||||
json11::Json success = object["success"];
|
||||
|
||||
if (!success.is_bool() || !success.bool_value()) return false;
|
||||
|
||||
json11::Json files = object["files"];
|
||||
|
||||
if (!files.is_array()) return false;
|
||||
|
||||
for (auto file : files.array_items())
|
||||
{
|
||||
json11::Json url = file["url"];
|
||||
json11::Json hash = file["hash"];
|
||||
|
||||
if (hash.is_string() && url.is_string())
|
||||
{
|
||||
if (Utils::String::ToLower(Utils::Cryptography::SHA1::Compute(buffer, true)) == Utils::String::ToLower(hash.string_value()))
|
||||
{
|
||||
MessageBoxA(0, url.string_value().data(), 0, 0);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
Utils::Hook Exception::SetFilterHook;
|
||||
|
||||
LONG WINAPI Exception::ExceptionFilter(LPEXCEPTION_POINTERS ExceptionInfo)
|
||||
{
|
||||
char filename[MAX_PATH];
|
||||
__time64_t time;
|
||||
tm ltime;
|
||||
|
||||
_time64(&time);
|
||||
_localtime64_s(<ime, &time);
|
||||
strftime(filename, sizeof(filename) - 1, "iw4x-" VERSION_STR "-%Y%m%d%H%M%S.dmp", <ime);
|
||||
|
||||
HANDLE hFile = CreateFileA(filename, GENERIC_WRITE, FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
|
||||
|
||||
if (hFile && hFile != INVALID_HANDLE_VALUE)
|
||||
// Pass on harmless errors
|
||||
if (ExceptionInfo->ExceptionRecord->ExceptionCode == STATUS_INTEGER_OVERFLOW ||
|
||||
ExceptionInfo->ExceptionRecord->ExceptionCode == STATUS_FLOAT_OVERFLOW)
|
||||
{
|
||||
MINIDUMP_EXCEPTION_INFORMATION ex = { GetCurrentThreadId(), ExceptionInfo, FALSE };
|
||||
MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), hFile, MiniDumpNormal, &ex, NULL, NULL);
|
||||
CloseHandle(hFile);
|
||||
return EXCEPTION_CONTINUE_EXECUTION;
|
||||
}
|
||||
|
||||
//Exception::UploadMinidump(filename);
|
||||
auto minidump = MinidumpUpload::CreateQueuedMinidump(ExceptionInfo);
|
||||
if (!minidump)
|
||||
{
|
||||
OutputDebugStringA("Failed to create new minidump!");
|
||||
Utils::OutputDebugLastError();
|
||||
}
|
||||
else
|
||||
{
|
||||
delete minidump;
|
||||
}
|
||||
|
||||
if (ExceptionInfo->ExceptionRecord->ExceptionCode == EXCEPTION_STACK_OVERFLOW)
|
||||
{
|
||||
Logger::Error("Termination because of a stack overflow.\n");
|
||||
TerminateProcess(GetCurrentProcess(), EXCEPTION_STACK_OVERFLOW);
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger::Error("Fatal error (0x%08X) at 0x%08X.", ExceptionInfo->ExceptionRecord->ExceptionCode, ExceptionInfo->ExceptionRecord->ExceptionAddress);
|
||||
}
|
||||
|
||||
//TerminateProcess(GetCurrentProcess(), ExceptionInfo->ExceptionRecord->ExceptionCode);
|
||||
|
||||
return EXCEPTION_CONTINUE_SEARCH;
|
||||
}
|
||||
|
||||
LPTOP_LEVEL_EXCEPTION_FILTER WINAPI Exception::SetUnhandledExceptionFilterStub(LPTOP_LEVEL_EXCEPTION_FILTER lpTopLevelExceptionFilter)
|
||||
LPTOP_LEVEL_EXCEPTION_FILTER WINAPI Exception::SetUnhandledExceptionFilterStub(LPTOP_LEVEL_EXCEPTION_FILTER)
|
||||
{
|
||||
SetUnhandledExceptionFilter(&Exception::ExceptionFilter);
|
||||
return lpTopLevelExceptionFilter;
|
||||
Exception::SetFilterHook.Uninstall();
|
||||
LPTOP_LEVEL_EXCEPTION_FILTER retval = SetUnhandledExceptionFilter(&Exception::ExceptionFilter);
|
||||
Exception::SetFilterHook.Install();
|
||||
return retval;
|
||||
}
|
||||
|
||||
LPTOP_LEVEL_EXCEPTION_FILTER Exception::Hook()
|
||||
{
|
||||
return SetUnhandledExceptionFilter(&Exception::ExceptionFilter);
|
||||
}
|
||||
|
||||
Exception::Exception()
|
||||
{
|
||||
#ifdef DEBUG
|
||||
// Display DEBUG branding, so we know we're on a debug build
|
||||
Renderer::OnFrame([] ()
|
||||
Renderer::OnFrame([]()
|
||||
{
|
||||
Game::Font* font = Game::R_RegisterFont("fonts/normalFont");
|
||||
float color[4] = { 1.0f, 1.0f, 1.0f, 1.0f };
|
||||
@ -109,18 +77,21 @@ namespace Components
|
||||
|
||||
Game::R_AddCmdDrawText("DEBUG-BUILD", 0x7FFFFFFF, font, 15.0f, 10.0f + Game::R_TextHeight(font), 1.0f, 1.0f, 0.0f, color, Game::ITEM_TEXTSTYLE_SHADOWED);
|
||||
});
|
||||
#else
|
||||
Utils::Hook::Set(0x6D70AC, Exception::SetUnhandledExceptionFilterStub);
|
||||
#endif
|
||||
#if !defined(DEBUG) || defined(FORCE_EXCEPTION_HANDLER)
|
||||
Exception::SetFilterHook.Initialize(SetUnhandledExceptionFilter, Exception::SetUnhandledExceptionFilterStub, HOOK_JUMP);
|
||||
Exception::SetFilterHook.Install();
|
||||
|
||||
SetUnhandledExceptionFilter(&Exception::ExceptionFilter);
|
||||
#endif
|
||||
|
||||
Command::Add("mapTest", [] (Command::Params params)
|
||||
Command::Add("mapTest", [](Command::Params params)
|
||||
{
|
||||
std::string command;
|
||||
|
||||
int max = (params.Length() >= 2 ? atoi(params[1]) : 16), current = 0;
|
||||
|
||||
for (int i =0;;)
|
||||
for (int i = 0;;)
|
||||
{
|
||||
char* mapname = Game::mapnames[i];
|
||||
if (!*mapname)
|
||||
@ -129,7 +100,7 @@ namespace Components
|
||||
continue;
|
||||
}
|
||||
|
||||
if(!(i % 2)) command.append(fmt::sprintf("wait 250;disconnect;wait 750;", mapname)); // Test a disconnect
|
||||
if (!(i % 2)) command.append(fmt::sprintf("wait 250;disconnect;wait 750;", mapname)); // Test a disconnect
|
||||
else command.append(fmt::sprintf("wait 500;", mapname)); // Test direct map switch
|
||||
command.append(fmt::sprintf("map %s;", mapname));
|
||||
|
||||
@ -140,5 +111,63 @@ namespace Components
|
||||
|
||||
Command::Execute(command, false);
|
||||
});
|
||||
Command::Add("debug_exceptionhandler", [](Command::Params)
|
||||
{
|
||||
Logger::Print("Rerunning SetUnhandledExceptionHandler...\n");
|
||||
auto oldHandler = Exception::Hook();
|
||||
Logger::Print("Old exception handler was 0x%010X.\n", oldHandler);
|
||||
});
|
||||
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable:4740) // flow in or out of inline asm code suppresses global optimization
|
||||
Command::Add("debug_minidump", [](Command::Params)
|
||||
{
|
||||
// The following code was taken from VC++ 8.0 CRT (invarg.c: line 104)
|
||||
|
||||
CONTEXT ContextRecord;
|
||||
EXCEPTION_RECORD ExceptionRecord;
|
||||
ZeroMemory(&ContextRecord, sizeof(CONTEXT));
|
||||
|
||||
__asm
|
||||
{
|
||||
mov [ContextRecord.Eax], eax
|
||||
mov [ContextRecord.Ecx], ecx
|
||||
mov [ContextRecord.Edx], edx
|
||||
mov [ContextRecord.Ebx], ebx
|
||||
mov [ContextRecord.Esi], esi
|
||||
mov [ContextRecord.Edi], edi
|
||||
mov word ptr [ContextRecord.SegSs], ss
|
||||
mov word ptr [ContextRecord.SegCs], cs
|
||||
mov word ptr [ContextRecord.SegDs], ds
|
||||
mov word ptr [ContextRecord.SegEs], es
|
||||
mov word ptr [ContextRecord.SegFs], fs
|
||||
mov word ptr [ContextRecord.SegGs], gs
|
||||
|
||||
pushfd
|
||||
pop [ContextRecord.EFlags]
|
||||
}
|
||||
|
||||
ContextRecord.ContextFlags = CONTEXT_CONTROL;
|
||||
ContextRecord.Eip = reinterpret_cast<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();
|
||||
}
|
||||
}
|
||||
|
@ -1,18 +1,22 @@
|
||||
|
||||
|
||||
namespace Components
|
||||
{
|
||||
class Exception : public Component
|
||||
{
|
||||
public:
|
||||
Exception();
|
||||
~Exception();
|
||||
|
||||
#ifdef DEBUG
|
||||
const char* GetName() { return "Exception"; };
|
||||
#endif
|
||||
static LPTOP_LEVEL_EXCEPTION_FILTER Hook();
|
||||
|
||||
private:
|
||||
static LONG WINAPI ExceptionFilter(LPEXCEPTION_POINTERS ExceptionInfo);
|
||||
static LPTOP_LEVEL_EXCEPTION_FILTER WINAPI SetUnhandledExceptionFilterStub(LPTOP_LEVEL_EXCEPTION_FILTER lpTopLevelExceptionFilter);
|
||||
|
||||
static bool UploadMinidump(std::string filename);
|
||||
static Utils::Hook SetFilterHook;
|
||||
};
|
||||
}
|
||||
|
@ -58,11 +58,11 @@ namespace Components
|
||||
|
||||
Lean::Lean()
|
||||
{
|
||||
Game::Cmd_AddCommand("+leanleft", Lean::IN_LeanLeft_Down, Command::Allocate(), 1);
|
||||
Game::Cmd_AddCommand("-leanleft", Lean::IN_LeanLeft_Up, Command::Allocate(), 1);
|
||||
Command::AddRaw("+leanleft", Lean::IN_LeanLeft_Down, true);
|
||||
Command::AddRaw("-leanleft", Lean::IN_LeanLeft_Up, true);
|
||||
|
||||
Game::Cmd_AddCommand("+leanright", Lean::IN_LeanRight_Down, Command::Allocate(), 1);
|
||||
Game::Cmd_AddCommand("-leanright", Lean::IN_LeanRight_Up, Command::Allocate(), 1);
|
||||
Command::AddRaw("+leanright", Lean::IN_LeanRight_Down, true);
|
||||
Command::AddRaw("-leanright", Lean::IN_LeanRight_Up, true);
|
||||
|
||||
Utils::Hook(0x5A6D84, Lean::CL_CmdButtonsStub, HOOK_CALL).Install()->Quick();
|
||||
}
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
namespace Components
|
||||
{
|
||||
std::mutex Localization::LocalizeMutex;
|
||||
Dvar::Var Localization::UseLocalization;
|
||||
Utils::Memory::Allocator Localization::MemAllocator;
|
||||
std::map<std::string, Game::LocalizedEntry*> Localization::LocalizeMap;
|
||||
@ -9,6 +10,8 @@ namespace Components
|
||||
|
||||
void Localization::Set(std::string key, std::string value)
|
||||
{
|
||||
std::lock_guard<std::mutex> _(Localization::LocalizeMutex);
|
||||
|
||||
if (Localization::LocalizeMap.find(key) != Localization::LocalizeMap.end())
|
||||
{
|
||||
Game::LocalizedEntry* entry = Localization::LocalizeMap[key];
|
||||
@ -46,6 +49,7 @@ namespace Components
|
||||
if (!Localization::UseLocalization.Get<bool>()) return key;
|
||||
|
||||
Game::LocalizedEntry* entry = nullptr;
|
||||
std::lock_guard<std::mutex> _(Localization::LocalizeMutex);
|
||||
|
||||
if (Localization::TempLocalizeMap.find(key) != Localization::TempLocalizeMap.end())
|
||||
{
|
||||
@ -56,7 +60,12 @@ namespace Components
|
||||
entry = Localization::LocalizeMap[key];
|
||||
}
|
||||
|
||||
if (!entry || !entry->value) entry = Game::DB_FindXAssetHeader(Game::XAssetType::ASSET_TYPE_LOCALIZE, key).localize;
|
||||
if (!entry || !entry->value)
|
||||
{
|
||||
Localization::LocalizeMutex.unlock();
|
||||
entry = Game::DB_FindXAssetHeader(Game::XAssetType::ASSET_TYPE_LOCALIZE, key).localize;
|
||||
Localization::LocalizeMutex.lock();
|
||||
}
|
||||
|
||||
if (entry && entry->value)
|
||||
{
|
||||
@ -68,6 +77,8 @@ namespace Components
|
||||
|
||||
void Localization::SetTemp(std::string key, std::string value)
|
||||
{
|
||||
std::lock_guard<std::mutex> _(Localization::LocalizeMutex);
|
||||
|
||||
if (Localization::TempLocalizeMap.find(key) != Localization::TempLocalizeMap.end())
|
||||
{
|
||||
Game::LocalizedEntry* entry = Localization::TempLocalizeMap[key];
|
||||
@ -100,6 +111,8 @@ namespace Components
|
||||
|
||||
void Localization::ClearTemp()
|
||||
{
|
||||
std::lock_guard<std::mutex> _(Localization::LocalizeMutex);
|
||||
|
||||
for (auto i = Localization::TempLocalizeMap.begin(); i != Localization::TempLocalizeMap.end(); ++i)
|
||||
{
|
||||
if (i->second)
|
||||
@ -131,6 +144,7 @@ namespace Components
|
||||
AssetHandler::OnFind(Game::XAssetType::ASSET_TYPE_LOCALIZE, [] (Game::XAssetType, std::string filename)
|
||||
{
|
||||
Game::XAssetHeader header = { 0 };
|
||||
std::lock_guard<std::mutex> _(Localization::LocalizeMutex);
|
||||
|
||||
if (Localization::TempLocalizeMap.find(filename) != Localization::TempLocalizeMap.end())
|
||||
{
|
||||
|
@ -17,6 +17,7 @@ namespace Components
|
||||
static void ClearTemp();
|
||||
|
||||
private:
|
||||
static std::mutex LocalizeMutex;
|
||||
static Utils::Memory::Allocator MemAllocator;
|
||||
static std::map<std::string, Game::LocalizedEntry*> LocalizeMap;
|
||||
static std::map<std::string, Game::LocalizedEntry*> TempLocalizeMap;
|
||||
|
@ -4,6 +4,7 @@ namespace Components
|
||||
{
|
||||
std::mutex Logger::MessageMutex;
|
||||
std::vector<std::string> Logger::MessageQueue;
|
||||
std::vector<Network::Address> Logger::LoggingAddresses[2];
|
||||
void(*Logger::PipeCallback)(std::string) = nullptr;
|
||||
|
||||
bool Logger::IsConsoleReady()
|
||||
@ -79,7 +80,7 @@ namespace Components
|
||||
|
||||
void Logger::Frame()
|
||||
{
|
||||
Logger::MessageMutex.lock();
|
||||
std::lock_guard<std::mutex> _(Logger::MessageMutex);
|
||||
|
||||
for (unsigned int i = 0; i < Logger::MessageQueue.size(); ++i)
|
||||
{
|
||||
@ -92,7 +93,6 @@ namespace Components
|
||||
}
|
||||
|
||||
Logger::MessageQueue.clear();
|
||||
Logger::MessageMutex.unlock();
|
||||
}
|
||||
|
||||
void Logger::PipeOutput(void(*callback)(std::string))
|
||||
@ -108,6 +108,31 @@ namespace Components
|
||||
}
|
||||
}
|
||||
|
||||
void Logger::NetworkLog(const char* data, bool gLog)
|
||||
{
|
||||
if (!data) return;
|
||||
|
||||
std::string buffer(data);
|
||||
for (auto& addr : Logger::LoggingAddresses[gLog & 1])
|
||||
{
|
||||
Network::SendCommand(addr, "print", buffer);
|
||||
}
|
||||
}
|
||||
|
||||
__declspec(naked) void Logger::GameLogStub()
|
||||
{
|
||||
__asm
|
||||
{
|
||||
push 1
|
||||
push [esp + 8h]
|
||||
call Logger::NetworkLog
|
||||
add esp, 8h
|
||||
|
||||
mov eax, 4576C0h
|
||||
jmp eax
|
||||
}
|
||||
}
|
||||
|
||||
__declspec(naked) void Logger::PrintMessageStub()
|
||||
{
|
||||
__asm
|
||||
@ -122,6 +147,11 @@ namespace Components
|
||||
retn
|
||||
|
||||
returnPrint:
|
||||
push 0
|
||||
push [esp + 0Ch]
|
||||
call Logger::NetworkLog
|
||||
add esp, 8h
|
||||
|
||||
push esi
|
||||
mov esi, [esp + 0Ch]
|
||||
|
||||
@ -143,11 +173,120 @@ namespace Components
|
||||
|
||||
QuickPatch::OnFrame(Logger::Frame);
|
||||
|
||||
Utils::Hook(0x4B0218, Logger::GameLogStub, HOOK_CALL).Install()->Quick();
|
||||
Utils::Hook(Game::Com_PrintMessage, Logger::PrintMessageStub, HOOK_JUMP).Install()->Quick();
|
||||
|
||||
Dvar::OnInit([] ()
|
||||
{
|
||||
Command::AddSV("log_add", [] (Command::Params params)
|
||||
{
|
||||
if (params.Length() < 2) return;
|
||||
|
||||
Network::Address addr(params[1]);
|
||||
|
||||
if (std::find(Logger::LoggingAddresses[0].begin(), Logger::LoggingAddresses[0].end(), addr) == Logger::LoggingAddresses[0].end())
|
||||
{
|
||||
Logger::LoggingAddresses[0].push_back(addr);
|
||||
}
|
||||
});
|
||||
|
||||
Command::AddSV("log_del", [] (Command::Params params)
|
||||
{
|
||||
if (params.Length() < 2) return;
|
||||
|
||||
int num = atoi(params[1]);
|
||||
if (fmt::sprintf("%i", num) == params[1] && static_cast<unsigned int>(num) < Logger::LoggingAddresses[0].size())
|
||||
{
|
||||
auto addr = Logger::LoggingAddresses[0].begin() + num;
|
||||
Logger::Print("Address %s removed\n", addr->GetCString());
|
||||
Logger::LoggingAddresses[0].erase(addr);
|
||||
}
|
||||
else
|
||||
{
|
||||
Network::Address addr(params[1]);
|
||||
|
||||
auto i = std::find(Logger::LoggingAddresses[0].begin(), Logger::LoggingAddresses[0].end(), addr);
|
||||
if (i != Logger::LoggingAddresses[0].end())
|
||||
{
|
||||
Logger::LoggingAddresses[0].erase(i);
|
||||
Logger::Print("Address %s removed\n", addr.GetCString());
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger::Print("Address %s not found!\n", addr.GetCString());
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Command::AddSV("log_list", [] (Command::Params)
|
||||
{
|
||||
Logger::Print("# ID: Address\n");
|
||||
Logger::Print("-------------\n");
|
||||
|
||||
for (unsigned int i = 0; i < Logger::LoggingAddresses[0].size(); ++i)
|
||||
{
|
||||
Logger::Print("#%03d: %5s\n", i, Logger::LoggingAddresses[0][i].GetCString());
|
||||
}
|
||||
});
|
||||
|
||||
Command::AddSV("g_log_add", [] (Command::Params params)
|
||||
{
|
||||
if (params.Length() < 2) return;
|
||||
|
||||
Network::Address addr(params[1]);
|
||||
|
||||
if (std::find(Logger::LoggingAddresses[1].begin(), Logger::LoggingAddresses[1].end(), addr) == Logger::LoggingAddresses[1].end())
|
||||
{
|
||||
Logger::LoggingAddresses[1].push_back(addr);
|
||||
}
|
||||
});
|
||||
|
||||
Command::AddSV("g_log_del", [] (Command::Params params)
|
||||
{
|
||||
if (params.Length() < 2) return;
|
||||
|
||||
int num = atoi(params[1]);
|
||||
if (fmt::sprintf("%i", num) == params[1] && static_cast<unsigned int>(num) < Logger::LoggingAddresses[1].size())
|
||||
{
|
||||
auto addr = Logger::LoggingAddresses[1].begin() + num;
|
||||
Logger::Print("Address %s removed\n", addr->GetCString());
|
||||
Logger::LoggingAddresses[1].erase(addr);
|
||||
}
|
||||
else
|
||||
{
|
||||
Network::Address addr(params[1]);
|
||||
|
||||
auto i = std::find(Logger::LoggingAddresses[1].begin(), Logger::LoggingAddresses[1].end(), addr);
|
||||
if (i != Logger::LoggingAddresses[1].end())
|
||||
{
|
||||
Logger::LoggingAddresses[1].erase(i);
|
||||
Logger::Print("Address %s removed\n", addr.GetCString());
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger::Print("Address %s not found!\n", addr.GetCString());
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Command::AddSV("g_log_list", [] (Command::Params)
|
||||
{
|
||||
Logger::Print("# ID: Address\n");
|
||||
Logger::Print("-------------\n");
|
||||
|
||||
for (unsigned int i = 0; i < Logger::LoggingAddresses[1].size(); ++i)
|
||||
{
|
||||
Logger::Print("#%03d: %5s\n", i, Logger::LoggingAddresses[1][i].GetCString());
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Logger::~Logger()
|
||||
{
|
||||
Logger::LoggingAddresses[0].clear();
|
||||
Logger::LoggingAddresses[1].clear();
|
||||
|
||||
Logger::MessageMutex.lock();
|
||||
Logger::MessageQueue.clear();
|
||||
Logger::MessageMutex.unlock();
|
||||
|
@ -24,13 +24,17 @@ namespace Components
|
||||
private:
|
||||
static std::mutex MessageMutex;
|
||||
static std::vector<std::string> MessageQueue;
|
||||
static std::vector<Network::Address> LoggingAddresses[2];
|
||||
static void(*PipeCallback)(std::string);
|
||||
|
||||
static void Frame();
|
||||
static void GameLogStub();
|
||||
static void PrintMessageStub();
|
||||
static void PrintMessagePipe(const char* data);
|
||||
static void EnqueueMessage(std::string message);
|
||||
|
||||
static void NetworkLog(const char* data, bool gLog);
|
||||
|
||||
static std::string Format(const char** message);
|
||||
};
|
||||
}
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
namespace Components
|
||||
{
|
||||
int Materials::ImageNameLength;
|
||||
Utils::Hook Materials::ImageVersionCheckHook;
|
||||
|
||||
__declspec(naked) void Materials::ImageVersionCheck()
|
||||
@ -20,22 +21,41 @@ namespace Components
|
||||
}
|
||||
}
|
||||
|
||||
Game::Material* Materials::VerifyMaterial(Game::Material* material)
|
||||
Game::Material* Materials::ResolveMaterial(const char* stringPtr)
|
||||
{
|
||||
if (!IsBadReadPtr(material, 4) && !IsBadReadPtr(material->name, 1))
|
||||
const char* imagePtr = stringPtr + 4;
|
||||
unsigned int length = static_cast<unsigned int>(stringPtr[3] & 0xFF);
|
||||
|
||||
if (strlen(imagePtr) >= length)
|
||||
{
|
||||
return material;
|
||||
Materials::ImageNameLength = 4 + length;
|
||||
std::string image(imagePtr, length);
|
||||
|
||||
return Game::DB_FindXAssetHeader(Game::XAssetType::ASSET_TYPE_MATERIAL, image.data()).material;
|
||||
}
|
||||
|
||||
Materials::ImageNameLength = 4;
|
||||
return Game::DB_FindXAssetHeader(Game::XAssetType::ASSET_TYPE_MATERIAL, "default").material;
|
||||
}
|
||||
|
||||
__declspec(naked) void Materials::PostDrawMaterialStub()
|
||||
{
|
||||
__asm
|
||||
{
|
||||
mov eax, Materials::ImageNameLength
|
||||
add [esp + 30h], eax
|
||||
|
||||
mov eax, 5358FFh
|
||||
jmp eax
|
||||
}
|
||||
}
|
||||
|
||||
__declspec(naked) void Materials::DrawMaterialStub()
|
||||
{
|
||||
__asm
|
||||
{
|
||||
push eax
|
||||
call Materials::VerifyMaterial
|
||||
push ecx
|
||||
call Materials::ResolveMaterial
|
||||
add esp, 4h
|
||||
|
||||
mov edx, 5310F0h
|
||||
@ -43,13 +63,59 @@ namespace Components
|
||||
}
|
||||
}
|
||||
|
||||
int Materials::WriteDeathMessageIcon(char* string, int offset, Game::Material* material)
|
||||
{
|
||||
if (!material)
|
||||
{
|
||||
material = Game::DB_FindXAssetHeader(Game::XAssetType::ASSET_TYPE_MATERIAL, "default").material;
|
||||
}
|
||||
|
||||
int length = strlen(material->name);
|
||||
string[offset++] = static_cast<char>(length);
|
||||
|
||||
strncpy_s(string + offset, 1024 - offset, material->name, length);
|
||||
|
||||
return offset + length;
|
||||
}
|
||||
|
||||
__declspec(naked) void Materials::DeathMessageStub()
|
||||
{
|
||||
__asm
|
||||
{
|
||||
push edx // Material
|
||||
push eax // offset
|
||||
push ecx // String
|
||||
|
||||
call Materials::WriteDeathMessageIcon
|
||||
|
||||
add esp, 14h
|
||||
retn
|
||||
}
|
||||
}
|
||||
|
||||
Materials::Materials()
|
||||
{
|
||||
Materials::ImageNameLength = 7;
|
||||
|
||||
// Allow codo images
|
||||
Materials::ImageVersionCheckHook.Initialize(0x53A456, Materials::ImageVersionCheck, HOOK_CALL)->Install();
|
||||
|
||||
// Fix material pointer exploit
|
||||
Utils::Hook(0x534E0C, Materials::DrawMaterialStub, HOOK_CALL).Install()->Quick();
|
||||
|
||||
// Increment string pointer accordingly
|
||||
Utils::Hook(0x5358FA, Materials::PostDrawMaterialStub, HOOK_JUMP).Install()->Quick();
|
||||
|
||||
// Adapt death message to IW5 material format
|
||||
Utils::Hook(0x5A30D9, Materials::DeathMessageStub, HOOK_JUMP).Install()->Quick();
|
||||
|
||||
// Renderer::OnFrame([] ()
|
||||
// {
|
||||
// Game::Font* font = Game::R_RegisterFont("fonts/normalFont");
|
||||
// float color[4] = { 1.0f, 1.0f, 1.0f, 1.0f };
|
||||
//
|
||||
// Game::R_AddCmdDrawText("test^==preview_mp_rustzob", 0x7FFFFFFF, font, 500.0f, 150.0f, 1.0f, 1.0f, 0.0f, color, Game::ITEM_TEXTSTYLE_SHADOWED);
|
||||
// });
|
||||
}
|
||||
|
||||
Materials::~Materials()
|
||||
|
@ -11,10 +11,16 @@ namespace Components
|
||||
#endif
|
||||
|
||||
private:
|
||||
static int ImageNameLength;
|
||||
|
||||
static Utils::Hook ImageVersionCheckHook;
|
||||
static void ImageVersionCheck();
|
||||
|
||||
static Game::Material* VerifyMaterial(Game::Material* material);
|
||||
static Game::Material* ResolveMaterial(const char* stringPtr);
|
||||
static void DrawMaterialStub();
|
||||
static void PostDrawMaterialStub();
|
||||
|
||||
static int WriteDeathMessageIcon(char* string, int offset, Game::Material* material);
|
||||
static void DeathMessageStub();
|
||||
};
|
||||
}
|
||||
|
@ -674,6 +674,7 @@ namespace Components
|
||||
Menus::Add("ui_mp/stats_reset.menu");
|
||||
Menus::Add("ui_mp/stats_unlock.menu");
|
||||
Menus::Add("ui_mp/security_increase_popmenu.menu");
|
||||
Menus::Add("ui_mp/mod_download_popmenu.menu");
|
||||
}
|
||||
|
||||
Menus::~Menus()
|
||||
|
392
src/Components/Modules/MinidumpUpload.cpp
Normal file
392
src/Components/Modules/MinidumpUpload.cpp
Normal 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(<ime, &time);
|
||||
strftime(filenameFriendlyTime, sizeof(filenameFriendlyTime) - 1, "%Y%m%d%H%M%S", <ime);
|
||||
|
||||
// Combine with queuedMinidumpsFolder
|
||||
char filename[MAX_PATH];
|
||||
PathCombineA(filename, MinidumpUpload::queuedMinidumpsFolder.data(), Utils::String::VA("%s-" VERSION "-%s.dmp", exeFileName, filenameFriendlyTime));
|
||||
|
||||
// Generate the dump
|
||||
return Minidump::Create(filename, exceptionInfo, minidumpType);
|
||||
}
|
||||
|
||||
bool MinidumpUpload::UploadQueuedMinidumps()
|
||||
{
|
||||
#ifndef DISABLE_BITMESSAGE
|
||||
// Preload public key for our target that will receive minidumps
|
||||
Logger::Print("About to send request for public key for minidump upload address.\n");
|
||||
if (!BitMessage::RequestPublicKey(MinidumpUpload::targetAddress))
|
||||
{
|
||||
Logger::Error("Failed to request public key for minidump collection address.\n");
|
||||
}
|
||||
Logger::Print("Waiting for public key for minidump upload address.\n");
|
||||
if (!BitMessage::WaitForPublicKey(MinidumpUpload::targetAddress))
|
||||
{
|
||||
Logger::Error("Failed to fetch public key for minidump collection address.\n");
|
||||
}
|
||||
#endif
|
||||
|
||||
// Check if folder exists
|
||||
if (!PathIsDirectoryA(MinidumpUpload::queuedMinidumpsFolder.data()))
|
||||
{
|
||||
// Nothing to upload
|
||||
Logger::Print("No minidumps to upload.\n");
|
||||
return PathFileExistsA(MinidumpUpload::queuedMinidumpsFolder.data()) == FALSE;
|
||||
}
|
||||
|
||||
// Walk through directory and search for valid minidumps
|
||||
WIN32_FIND_DATAA ffd;
|
||||
HANDLE hFind = FindFirstFileA(Utils::String::VA("%s\\*.dmp", MinidumpUpload::queuedMinidumpsFolder), &ffd);
|
||||
if (hFind != INVALID_HANDLE_VALUE)
|
||||
{
|
||||
do
|
||||
{
|
||||
if ((ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) > 0)
|
||||
continue; // ignore directory
|
||||
|
||||
char fullPath[MAX_PATH_SIZE];
|
||||
PathCombineA(fullPath, MinidumpUpload::queuedMinidumpsFolder.data(), ffd.cFileName);
|
||||
|
||||
// Try to open this minidump
|
||||
Logger::Print("Trying to upload %s...\n", fullPath);
|
||||
auto minidump = Minidump::Open(fullPath);
|
||||
if (!minidump)
|
||||
{
|
||||
Logger::Print("Couldn't open minidump.\n");
|
||||
continue; // file can't be opened
|
||||
}
|
||||
|
||||
// Upload!
|
||||
if (!MinidumpUpload::Upload(minidump))
|
||||
{
|
||||
Logger::Print("Couldn't upload minidump.\n");
|
||||
continue; // couldn't upload that minidump, keep for another attempt
|
||||
}
|
||||
|
||||
delete minidump;
|
||||
|
||||
#ifndef KEEP_MINIDUMPS_AFTER_UPLOAD
|
||||
// Delete minidump if possible
|
||||
DeleteFileA(fullPath);
|
||||
#endif
|
||||
} while (FindNextFileA(hFind, &ffd) != 0);
|
||||
}
|
||||
|
||||
Logger::Print("All minidumps uploaded.\n");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MinidumpUpload::Upload(Minidump* minidump)
|
||||
{
|
||||
// Checking if we can extract any information from minidump first, just to be sure that this is valid.
|
||||
if (!minidump->Check())
|
||||
{
|
||||
Utils::OutputDebugLastError();
|
||||
return false;
|
||||
}
|
||||
|
||||
auto id = Utils::String::GenerateUUIDString();
|
||||
|
||||
std::map<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
|
||||
}
|
75
src/Components/Modules/MinidumpUpload.hpp
Normal file
75
src/Components/Modules/MinidumpUpload.hpp
Normal 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
|
||||
};
|
||||
}
|
@ -60,7 +60,7 @@ namespace Components
|
||||
{
|
||||
auto fsGame = Dvar::Var("fs_game");
|
||||
fsGame.Set("");
|
||||
fsGame.Get<Game::dvar_t*>()->pad2[0] = 1;
|
||||
fsGame.Get<Game::dvar_t*>()->modified = true;
|
||||
|
||||
if (Dvar::Var("cl_modVidRestart").Get<bool>())
|
||||
{
|
||||
@ -76,7 +76,7 @@ namespace Components
|
||||
{
|
||||
auto fsGame = Dvar::Var("fs_game");
|
||||
fsGame.Set(fmt::sprintf("mods/%s", mod.data()));
|
||||
fsGame.Get<Game::dvar_t*>()->pad2[0] = 1;
|
||||
fsGame.Get<Game::dvar_t*>()->modified = true;
|
||||
|
||||
if (Dvar::Var("cl_modVidRestart").Get<bool>())
|
||||
{
|
||||
|
@ -4,6 +4,7 @@
|
||||
|
||||
namespace Components
|
||||
{
|
||||
bool News::Terminate;
|
||||
std::thread News::Thread;
|
||||
|
||||
bool News::UnitTest()
|
||||
@ -39,11 +40,110 @@ namespace Components
|
||||
return result;
|
||||
}
|
||||
|
||||
void News::ExitProcessStub(unsigned int exitCode)
|
||||
{
|
||||
std::this_thread::sleep_for(10ms);
|
||||
|
||||
STARTUPINFOA sInfo;
|
||||
PROCESS_INFORMATION pInfo;
|
||||
|
||||
ZeroMemory(&sInfo, sizeof(sInfo));
|
||||
ZeroMemory(&pInfo, sizeof(pInfo));
|
||||
sInfo.cb = sizeof(sInfo);
|
||||
|
||||
CreateProcessA("updater.exe", NULL, NULL, NULL, false, CREATE_NO_WINDOW, NULL, NULL, &sInfo, &pInfo);
|
||||
|
||||
if (pInfo.hThread && pInfo.hThread != INVALID_HANDLE_VALUE)
|
||||
{
|
||||
CloseHandle(pInfo.hThread);
|
||||
}
|
||||
|
||||
if (pInfo.hProcess && pInfo.hProcess != INVALID_HANDLE_VALUE)
|
||||
{
|
||||
CloseHandle(pInfo.hProcess);
|
||||
}
|
||||
|
||||
TerminateProcess(GetCurrentProcess(), exitCode);
|
||||
}
|
||||
|
||||
void News::CheckForUpdate()
|
||||
{
|
||||
std::string caches = Utils::WebIO("IW4x", "https://iw4xcachep26muba.onion.to/iw4/caches.xml").SetTimeout(5000)->Get();
|
||||
|
||||
if (!caches.empty())
|
||||
{
|
||||
std::string str = "<Cache ID=\"game\" Version=\"";
|
||||
auto pos = caches.find(str);
|
||||
|
||||
if (pos != std::string::npos)
|
||||
{
|
||||
caches = caches.substr(pos + str.size());
|
||||
|
||||
pos = caches.find_first_of("\"");
|
||||
|
||||
if (pos != std::string::npos)
|
||||
{
|
||||
caches = caches.substr(0, pos);
|
||||
|
||||
int version = atoi(caches.data());
|
||||
|
||||
Dvar::Var("cl_updateversion").Get<Game::dvar_t*>()->current.integer = version;
|
||||
Dvar::Var("cl_updateavailable").Get<Game::dvar_t*>()->current.boolean = (version > REVISION);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
News::News()
|
||||
{
|
||||
Dvar::Register<int>("cl_updateoldversion", REVISION, REVISION, REVISION, Game::DVAR_FLAG_WRITEPROTECTED, "Current version number.");
|
||||
Dvar::Register<int>("cl_updateversion", 0, 0, -1, Game::DVAR_FLAG_WRITEPROTECTED, "New version number.");
|
||||
Dvar::Register<bool>("cl_updateavailable", 0, Game::DVAR_FLAG_WRITEPROTECTED, "New update is available.");
|
||||
|
||||
Localization::Set("MPUI_CHANGELOG_TEXT", "Loading...");
|
||||
Localization::Set("MPUI_MOTD_TEXT", NEWS_MOTD_DEFUALT);
|
||||
|
||||
// TODO: Probably remove that, if the updater is part of the repo?
|
||||
if (Utils::IO::FileExists("updater.exe"))
|
||||
{
|
||||
remove("updater.exe");
|
||||
}
|
||||
|
||||
Command::Add("checkforupdate", [] (Command::Params)
|
||||
{
|
||||
News::CheckForUpdate();
|
||||
});
|
||||
|
||||
Command::Add("getautoupdate", [] (Command::Params)
|
||||
{
|
||||
if (!Dvar::Var("cl_updateavailable").Get<Game::dvar_t*>()->current.boolean) return;
|
||||
|
||||
Localization::SetTemp("MENU_RECONNECTING_TO_PARTY", "Downloading updater");
|
||||
Command::Execute("openmenu popup_reconnectingtoparty", true);
|
||||
|
||||
// Run the updater on shutdown
|
||||
Utils::Hook::Set(0x6D72A0, News::ExitProcessStub);
|
||||
|
||||
std::thread([] ()
|
||||
{
|
||||
std::string data = Utils::WebIO("IW4x", "https://iw4xcachep26muba.onion.to/iw4/updater.exe").SetTimeout(5000)->Get();
|
||||
|
||||
if (data.empty())
|
||||
{
|
||||
Localization::ClearTemp();
|
||||
Command::Execute("closemenu popup_reconnectingtoparty", false);
|
||||
Game::MessageBox("Failed to download the updater!", "Error");
|
||||
}
|
||||
else
|
||||
{
|
||||
Console::SetSkipShutdown();
|
||||
Utils::IO::WriteFile("updater.exe", data);
|
||||
Command::Execute("wait 300; quit;", false);
|
||||
}
|
||||
}).detach();
|
||||
});
|
||||
|
||||
News::Terminate = false;
|
||||
News::Thread = std::thread([] ()
|
||||
{
|
||||
Localization::Set("MPUI_CHANGELOG_TEXT", Utils::WebIO("IW4x", "https://iw4xcachep26muba.onion.to/iw4/changelog.txt").SetTimeout(5000)->Get());
|
||||
@ -55,12 +155,26 @@ namespace Components
|
||||
Localization::Set("MPUI_MOTD_TEXT", data);
|
||||
}
|
||||
|
||||
// TODO: Implement update checks here!
|
||||
if (!Loader::PerformingUnitTests())
|
||||
{
|
||||
while (!News::Terminate)
|
||||
{
|
||||
News::CheckForUpdate();
|
||||
|
||||
// Sleep for 3 minutes
|
||||
for (int i = 0; i < 180 && !News::Terminate; ++i)
|
||||
{
|
||||
std::this_thread::sleep_for(1s);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
News::~News()
|
||||
{
|
||||
News::Terminate = true;
|
||||
|
||||
if (News::Thread.joinable())
|
||||
{
|
||||
News::Thread.join();
|
||||
|
@ -14,5 +14,9 @@ namespace Components
|
||||
|
||||
private:
|
||||
static std::thread Thread;
|
||||
static bool Terminate;
|
||||
|
||||
static void CheckForUpdate();
|
||||
static void ExitProcessStub(unsigned int exitCode);
|
||||
};
|
||||
}
|
||||
|
@ -8,19 +8,13 @@ namespace Components
|
||||
|
||||
void Node::LoadNodePreset()
|
||||
{
|
||||
Proto::Node::List list;
|
||||
FileSystem::File defaultNodes("nodes_default.dat");
|
||||
if (!defaultNodes.Exists()) return;
|
||||
if (!defaultNodes.Exists() || !list.ParseFromString(Utils::Compression::ZLib::Decompress(defaultNodes.GetBuffer()))) return;
|
||||
|
||||
auto buffer = defaultNodes.GetBuffer();
|
||||
Utils::String::Replace(buffer, "\r", "");
|
||||
|
||||
auto nodes = Utils::String::Explode(buffer, '\n');
|
||||
for (auto node : nodes)
|
||||
for (int i = 0; i < list.address_size(); ++i)
|
||||
{
|
||||
if (!node.empty())
|
||||
{
|
||||
Node::AddNode(node);
|
||||
}
|
||||
Node::AddNode(list.address(i));
|
||||
}
|
||||
}
|
||||
|
||||
@ -28,7 +22,7 @@ namespace Components
|
||||
{
|
||||
Proto::Node::List list;
|
||||
std::string nodes = Utils::IO::ReadFile("players/nodes.dat");
|
||||
if (nodes.empty() || !list.ParseFromString(nodes)) return;
|
||||
if (nodes.empty() || !list.ParseFromString(Utils::Compression::ZLib::Decompress(nodes))) return;
|
||||
|
||||
for (int i = 0; i < list.address_size(); ++i)
|
||||
{
|
||||
@ -60,7 +54,10 @@ namespace Components
|
||||
}
|
||||
|
||||
CreateDirectoryW(L"players", NULL);
|
||||
Utils::IO::WriteFile("players/nodes.dat", list.SerializeAsString());
|
||||
|
||||
|
||||
|
||||
Utils::IO::WriteFile("players/nodes.dat", Utils::Compression::ZLib::Compress(list.SerializeAsString()));
|
||||
}
|
||||
|
||||
Node::NodeEntry* Node::FindNode(Network::Address address)
|
||||
@ -130,7 +127,7 @@ namespace Components
|
||||
|
||||
Node::Nodes.push_back(entry);
|
||||
|
||||
#ifdef DEBUG
|
||||
#if defined(DEBUG) && !defined(DISABLE_NODE_LOG)
|
||||
Logger::Print("Adding node %s...\n", address.GetCString());
|
||||
#endif
|
||||
}
|
||||
@ -187,7 +184,7 @@ namespace Components
|
||||
{
|
||||
if (node.state == Node::STATE_INVALID && (Game::Sys_Milliseconds() - node.lastHeard) > NODE_INVALID_DELETE)
|
||||
{
|
||||
#ifdef DEBUG
|
||||
#if defined(DEBUG) && !defined(DISABLE_NODE_LOG)
|
||||
Logger::Print("Removing invalid node %s\n", node.address.GetCString());
|
||||
#endif
|
||||
}
|
||||
@ -231,14 +228,14 @@ namespace Components
|
||||
Proto::Node::Packet packet;
|
||||
packet.set_challenge(entry->challenge);
|
||||
|
||||
#ifdef DEBUG
|
||||
#if defined(DEBUG) && !defined(DISABLE_NODE_LOG)
|
||||
Logger::Print("Sending registration request to %s\n", entry->address.GetCString());
|
||||
#endif
|
||||
Network::SendCommand(entry->address, "nodeRegisterRequest", packet.SerializeAsString());
|
||||
}
|
||||
else
|
||||
{
|
||||
#ifdef DEBUG
|
||||
#if defined(DEBUG) && !defined(DISABLE_NODE_LOG)
|
||||
Logger::Print("Sending session request to %s\n", entry->address.GetCString());
|
||||
#endif
|
||||
Network::SendCommand(entry->address, "sessionRequest");
|
||||
@ -267,7 +264,7 @@ namespace Components
|
||||
node.lastHeard = Game::Sys_Milliseconds();
|
||||
node.lastTime = Game::Sys_Milliseconds();
|
||||
|
||||
#ifdef DEBUG
|
||||
#if defined(DEBUG) && !defined(DISABLE_NODE_LOG)
|
||||
Logger::Print("Node negotiation timed out. Invalidating %s\n", node.address.GetCString());
|
||||
#endif
|
||||
}
|
||||
@ -399,7 +396,7 @@ namespace Components
|
||||
if (!entry) return;
|
||||
}
|
||||
|
||||
#ifdef DEBUG
|
||||
#if defined(DEBUG) && !defined(DISABLE_NODE_LOG)
|
||||
Logger::Print("Received registration request from %s\n", address.GetCString());
|
||||
#endif
|
||||
|
||||
@ -440,7 +437,7 @@ namespace Components
|
||||
Node::NodeEntry* entry = Node::FindNode(address);
|
||||
if (!entry || entry->state != Node::STATE_NEGOTIATING) return;
|
||||
|
||||
#ifdef DEBUG
|
||||
#if defined(DEBUG) && !defined(DISABLE_NODE_LOG)
|
||||
Logger::Print("Received synchronization data for registration from %s!\n", address.GetCString());
|
||||
#endif
|
||||
|
||||
@ -467,7 +464,7 @@ namespace Components
|
||||
entry->state = Node::STATE_VALID;
|
||||
entry->registered = true;
|
||||
|
||||
#ifdef DEBUG
|
||||
#if defined(DEBUG) && !defined(DISABLE_NODE_LOG)
|
||||
Logger::Print("Signature from %s for challenge '%s' is valid!\n", address.GetCString(), entry->challenge.data());
|
||||
Logger::Print("Node %s registered\n", address.GetCString());
|
||||
#endif
|
||||
@ -491,7 +488,7 @@ namespace Components
|
||||
Node::NodeEntry* entry = Node::FindNode(address);
|
||||
if (!entry || entry->state != Node::STATE_NEGOTIATING) return;
|
||||
|
||||
#ifdef DEBUG
|
||||
#if defined(DEBUG) && !defined(DISABLE_NODE_LOG)
|
||||
Logger::Print("Received acknowledgment from %s\n", address.GetCString());
|
||||
#endif
|
||||
|
||||
@ -511,7 +508,7 @@ namespace Components
|
||||
entry->state = Node::STATE_VALID;
|
||||
entry->registered = true;
|
||||
|
||||
#ifdef DEBUG
|
||||
#if defined(DEBUG) && !defined(DISABLE_NODE_LOG)
|
||||
Logger::Print("Signature from %s for challenge '%s' is valid!\n", address.GetCString(), entry->challenge.data());
|
||||
Logger::Print("Node %s registered\n", address.GetCString());
|
||||
#endif
|
||||
@ -554,7 +551,7 @@ namespace Components
|
||||
}
|
||||
else
|
||||
{
|
||||
#ifdef DEBUG
|
||||
#if defined(DEBUG) && !defined(DISABLE_NODE_LOG)
|
||||
// Unallowed connection
|
||||
Logger::Print("Node list requested by %s, but no valid session was present!\n", address.GetCString());
|
||||
#endif
|
||||
@ -584,13 +581,13 @@ namespace Components
|
||||
entry->registered = false;
|
||||
entry->state = Node::STATE_INVALID;
|
||||
|
||||
#ifdef DEBUG
|
||||
#if defined(DEBUG) && !defined(DISABLE_NODE_LOG)
|
||||
Logger::Print("Node %s unregistered\n", address.GetCString());
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
#ifdef DEBUG
|
||||
#if defined(DEBUG) && !defined(DISABLE_NODE_LOG)
|
||||
Logger::Print("Node %s tried to unregister using an invalid signature!\n", address.GetCString());
|
||||
#endif
|
||||
}
|
||||
@ -612,7 +609,7 @@ namespace Components
|
||||
Node::ClientSession* session = Node::FindSession(address);
|
||||
if (!session) return; // Registering template session failed, odd...
|
||||
|
||||
#ifdef DEBUG
|
||||
#if defined(DEBUG) && !defined(DISABLE_NODE_LOG)
|
||||
Logger::Print("Client %s is requesting a new session\n", address.GetCString());
|
||||
#endif
|
||||
|
||||
@ -634,7 +631,7 @@ namespace Components
|
||||
|
||||
if (session->challenge == data)
|
||||
{
|
||||
#ifdef DEBUG
|
||||
#if defined(DEBUG) && !defined(DISABLE_NODE_LOG)
|
||||
Logger::Print("Session for %s validated.\n", address.GetCString());
|
||||
#endif
|
||||
session->valid = true;
|
||||
@ -643,7 +640,7 @@ namespace Components
|
||||
else
|
||||
{
|
||||
session->lastTime = -1;
|
||||
#ifdef DEBUG
|
||||
#if defined(DEBUG) && !defined(DISABLE_NODE_LOG)
|
||||
Logger::Print("Challenge mismatch. Validating session for %s failed.\n", address.GetCString());
|
||||
#endif
|
||||
}
|
||||
@ -656,7 +653,7 @@ namespace Components
|
||||
Node::NodeEntry* entry = Node::FindNode(address);
|
||||
if (!entry) return;
|
||||
|
||||
#ifdef DEBUG
|
||||
#if defined(DEBUG) && !defined(DISABLE_NODE_LOG)
|
||||
Logger::Print("Session initialization received from %s. Synchronizing...\n", address.GetCString());
|
||||
#endif
|
||||
|
||||
@ -673,7 +670,7 @@ namespace Components
|
||||
entry->registered = true;
|
||||
entry->lastTime = Game::Sys_Milliseconds();
|
||||
|
||||
#ifdef DEBUG
|
||||
#if defined(DEBUG) && !defined(DISABLE_NODE_LOG)
|
||||
Logger::Print("Session acknowledged by %s, synchronizing node list...\n", address.GetCString());
|
||||
#endif
|
||||
Network::SendCommand(address, "nodeListRequest");
|
||||
@ -687,7 +684,7 @@ namespace Components
|
||||
|
||||
if (data.empty() || !list.ParseFromString(data))
|
||||
{
|
||||
#ifdef DEBUG
|
||||
#if defined(DEBUG) && !defined(DISABLE_NODE_LOG)
|
||||
Logger::Print("Received invalid node list from %s!\n", address.GetCString());
|
||||
#endif
|
||||
return;
|
||||
@ -698,7 +695,7 @@ namespace Components
|
||||
{
|
||||
if (entry->registered)
|
||||
{
|
||||
#ifdef DEBUG
|
||||
#if defined(DEBUG) && !defined(DISABLE_NODE_LOG)
|
||||
Logger::Print("Received valid node list with %i entries from %s\n", list.address_size(), address.GetCString());
|
||||
#endif
|
||||
|
||||
@ -771,11 +768,6 @@ namespace Components
|
||||
Node::AddNode(address);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: Implement client handshake stuff
|
||||
// Nvm, clients can simply ignore that i guess
|
||||
}
|
||||
});
|
||||
|
||||
Command::Add("listnodes", [] (Command::Params)
|
||||
@ -907,6 +899,31 @@ namespace Components
|
||||
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::high_resolution_clock::now() - startTime).count();
|
||||
Logger::Print("took %llims\n", duration);
|
||||
|
||||
printf("Testing ZLib compression...");
|
||||
|
||||
std::string test = fmt::sprintf("%c", Utils::Cryptography::Rand::GenerateInt());
|
||||
|
||||
for (int i = 0; i < 21; ++i)
|
||||
{
|
||||
std::string compressed = Utils::Compression::ZLib::Compress(test);
|
||||
std::string decompressed = Utils::Compression::ZLib::Decompress(compressed);
|
||||
|
||||
if (test != decompressed)
|
||||
{
|
||||
printf("Error\n");
|
||||
printf("Compressing %d bytes and decompressing failed!\n", test.size());
|
||||
return false;
|
||||
}
|
||||
|
||||
auto size = test.size();
|
||||
for (unsigned int j = 0; j < size; ++j)
|
||||
{
|
||||
test.append(fmt::sprintf("%c", Utils::Cryptography::Rand::GenerateInt()));
|
||||
}
|
||||
}
|
||||
|
||||
printf("Success\n");
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -64,6 +64,7 @@ namespace Components
|
||||
|
||||
void Party::ConnectError(std::string message)
|
||||
{
|
||||
Localization::ClearTemp();
|
||||
Command::Execute("closemenu popup_reconnectingtoparty");
|
||||
Dvar::Var("partyend_reason").Set(message);
|
||||
Command::Execute("openmenu menu_xboxlive_partyended");
|
||||
@ -308,7 +309,7 @@ namespace Components
|
||||
info.Set("clients", fmt::sprintf("%i", clientCount));
|
||||
info.Set("sv_maxclients", fmt::sprintf("%i", maxclientCount));
|
||||
info.Set("protocol", fmt::sprintf("%i", PROTOCOL));
|
||||
info.Set("shortversion", VERSION_STR);
|
||||
info.Set("shortversion", SHORTVERSION);
|
||||
info.Set("checksum", fmt::sprintf("%d", Game::Sys_Milliseconds()));
|
||||
info.Set("mapname", Dvar::Var("mapname").Get<const char*>());
|
||||
info.Set("isPrivate", (Dvar::Var("g_password").Get<std::string>().size() ? "1" : "0"));
|
||||
@ -354,8 +355,9 @@ namespace Components
|
||||
{
|
||||
// Invalidate handler for future packets
|
||||
Party::Container.Valid = false;
|
||||
Party::Container.Info = info;
|
||||
|
||||
int matchType = atoi(info.Get("matchtype").data());
|
||||
Party::Container.MatchType = atoi(info.Get("matchtype").data());
|
||||
uint32_t securityLevel = static_cast<uint32_t>(atoi(info.Get("securityLevel").data()));
|
||||
|
||||
if (info.Get("challenge") != Party::Container.Challenge)
|
||||
@ -368,48 +370,66 @@ namespace Components
|
||||
Command::Execute("closemenu popup_reconnectingtoparty");
|
||||
Auth::IncreaseSecurityLevel(securityLevel, "reconnect");
|
||||
}
|
||||
else if (!matchType)
|
||||
else if (!Party::Container.MatchType)
|
||||
{
|
||||
Party::ConnectError("Server is not hosting a match.");
|
||||
}
|
||||
// Connect
|
||||
else if (matchType == 1) // Party
|
||||
else if(Party::Container.MatchType > 2 || Party::Container.MatchType < 0)
|
||||
{
|
||||
// Send playlist request
|
||||
Party::Container.RequestTime = Game::Sys_Milliseconds();
|
||||
Party::Container.AwaitingPlaylist = true;
|
||||
Network::SendCommand(address, "getplaylist");
|
||||
|
||||
// This is not a safe method
|
||||
// TODO: Fix actual error!
|
||||
if (Game::CL_IsCgameInitialized())
|
||||
{
|
||||
Command::Execute("disconnect", true);
|
||||
}
|
||||
Party::ConnectError("Invalid join response: Unknown matchtype");
|
||||
}
|
||||
else if (matchType == 2) // Match
|
||||
else if(!info.Get("fs_game").empty() && Dvar::Var("fs_game").Get<std::string>() != info.Get("fs_game"))
|
||||
{
|
||||
if (atoi(info.Get("clients").data()) >= atoi(info.Get("sv_maxclients").data()))
|
||||
{
|
||||
Party::ConnectError("@EXE_SERVERISFULL");
|
||||
}
|
||||
if (info.Get("mapname") == "" || info.Get("gametype") == "")
|
||||
{
|
||||
Party::ConnectError("Invalid map or gametype.");
|
||||
}
|
||||
else
|
||||
{
|
||||
Dvar::Var("xblive_privateserver").Set(true);
|
||||
Command::Execute("closemenu popup_reconnectingtoparty");
|
||||
Download::InitiateClientDownload(info.Get("fs_game"));
|
||||
}
|
||||
else if (!Dvar::Var("fs_game").Get<std::string>().empty() && info.Get("fs_game").empty())
|
||||
{
|
||||
Dvar::Var("fs_game").Set("");
|
||||
|
||||
Game::Menus_CloseAll(Game::uiContext);
|
||||
|
||||
Game::_XSESSION_INFO hostInfo;
|
||||
Game::CL_ConnectFromParty(0, &hostInfo, *address.Get(), 0, 0, info.Get("mapname").data(), info.Get("gametype").data());
|
||||
if (Dvar::Var("cl_modVidRestart").Get<bool>())
|
||||
{
|
||||
Command::Execute("vid_restart", false);
|
||||
}
|
||||
|
||||
Command::Execute("reconnect", false);
|
||||
}
|
||||
else
|
||||
{
|
||||
Party::ConnectError("Invalid join response: Unknown matchtype");
|
||||
if (Party::Container.MatchType == 1) // Party
|
||||
{
|
||||
// Send playlist request
|
||||
Party::Container.RequestTime = Game::Sys_Milliseconds();
|
||||
Party::Container.AwaitingPlaylist = true;
|
||||
Network::SendCommand(Party::Container.Target, "getplaylist");
|
||||
|
||||
// This is not a safe method
|
||||
// TODO: Fix actual error!
|
||||
if (Game::CL_IsCgameInitialized())
|
||||
{
|
||||
Command::Execute("disconnect", true);
|
||||
}
|
||||
}
|
||||
else if (Party::Container.MatchType == 2) // Match
|
||||
{
|
||||
if (atoi(Party::Container.Info.Get("clients").data()) >= atoi(Party::Container.Info.Get("sv_maxclients").data()))
|
||||
{
|
||||
Party::ConnectError("@EXE_SERVERISFULL");
|
||||
}
|
||||
if (Party::Container.Info.Get("mapname") == "" || Party::Container.Info.Get("gametype") == "")
|
||||
{
|
||||
Party::ConnectError("Invalid map or gametype.");
|
||||
}
|
||||
else
|
||||
{
|
||||
Dvar::Var("xblive_privateserver").Set(true);
|
||||
|
||||
Game::Menus_CloseAll(Game::uiContext);
|
||||
|
||||
Game::_XSESSION_INFO hostInfo;
|
||||
Game::CL_ConnectFromParty(0, &hostInfo, *Party::Container.Target.Get(), 0, 0, Party::Container.Info.Get("mapname").data(), Party::Container.Info.Get("gametype").data());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -19,6 +19,8 @@ namespace Components
|
||||
static void PlaylistContinue();
|
||||
static void PlaylistError(std::string error);
|
||||
|
||||
static void ConnectError(std::string message);
|
||||
|
||||
private:
|
||||
class JoinContainer
|
||||
{
|
||||
@ -27,6 +29,9 @@ namespace Components
|
||||
std::string Challenge;
|
||||
DWORD JoinTime;
|
||||
bool Valid;
|
||||
int MatchType;
|
||||
|
||||
Utils::InfoString Info;
|
||||
|
||||
// Party-specific stuff
|
||||
DWORD RequestTime;
|
||||
@ -40,8 +45,6 @@ namespace Components
|
||||
|
||||
static Game::dvar_t* RegisterMinPlayers(const char* name, int value, int min, int max, Game::dvar_flag flag, const char* description);
|
||||
|
||||
static void ConnectError(std::string message);
|
||||
|
||||
static DWORD UIDvarIntStub(char* dvar);
|
||||
};
|
||||
}
|
||||
|
33
src/Components/Modules/PlayerName.cpp
Normal file
33
src/Components/Modules/PlayerName.cpp
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
18
src/Components/Modules/PlayerName.hpp
Normal file
18
src/Components/Modules/PlayerName.hpp
Normal 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);
|
||||
};
|
||||
}
|
@ -173,26 +173,34 @@ namespace Components
|
||||
Utils::Hook::Set<char*>(0x6431D1, BASEGAME);
|
||||
|
||||
// UI version string
|
||||
Utils::Hook::Set<char*>(0x43F73B, "IW4x: r" REVISION_STR REVISION_SUFFIX "-" MILESTONE);
|
||||
Utils::Hook::Set<char*>(0x43F73B, "IW4x: " VERSION);
|
||||
|
||||
// console version string
|
||||
Utils::Hook::Set<char*>(0x4B12BB, "IW4x r" REVISION_STR REVISION_SUFFIX "-" MILESTONE " (built " __DATE__ " " __TIME__ ")");
|
||||
Utils::Hook::Set<char*>(0x4B12BB, "IW4x " VERSION " (built " __DATE__ " " __TIME__ ")");
|
||||
|
||||
// version string
|
||||
Utils::Hook::Set<char*>(0x60BD56, "IW4x (r" REVISION_STR REVISION_SUFFIX ")");
|
||||
Utils::Hook::Set<char*>(0x60BD56, "IW4x (" VERSION ")");
|
||||
|
||||
// Shift ui version string to the left (ui_buildlocation)
|
||||
Utils::Hook::Nop(0x6310A0, 5); // Don't register the initial dvar
|
||||
Utils::Hook::Nop(0x6310B8, 5); // Don't write the result
|
||||
Dvar::OnInit([] ()
|
||||
{
|
||||
*reinterpret_cast<Game::dvar_t**>(0x62E4B64) = Game::Dvar_RegisterVec2("ui_buildLocation", -80.0f, 15.0f, -10000.0, 10000.0, Game::DVAR_FLAG_READONLY, "Where to draw the build number");
|
||||
});
|
||||
|
||||
// console title
|
||||
if (ZoneBuilder::IsEnabled())
|
||||
{
|
||||
Utils::Hook::Set<char*>(0x4289E8, "IW4x (r" REVISION_STR REVISION_SUFFIX "): ZoneBuilder");
|
||||
Utils::Hook::Set<char*>(0x4289E8, "IW4x (" VERSION "): ZoneBuilder");
|
||||
}
|
||||
else if (Dedicated::IsEnabled())
|
||||
{
|
||||
Utils::Hook::Set<char*>(0x4289E8, "IW4x (r" REVISION_STR REVISION_SUFFIX "): Dedicated");
|
||||
Utils::Hook::Set<char*>(0x4289E8, "IW4x (r" VERSION "): Dedicated");
|
||||
}
|
||||
else
|
||||
{
|
||||
Utils::Hook::Set<char*>(0x4289E8, "IW4x (r" REVISION_STR REVISION_SUFFIX "): Console");
|
||||
Utils::Hook::Set<char*>(0x4289E8, "IW4x (r" VERSION "): Console");
|
||||
}
|
||||
|
||||
// window title
|
||||
@ -202,7 +210,7 @@ namespace Components
|
||||
Utils::Hook::Set<char*>(0x4D378B, "IW4Host");
|
||||
|
||||
// shortversion
|
||||
Utils::Hook::Set<char*>(0x60BD91, VERSION_STR);
|
||||
Utils::Hook::Set<char*>(0x60BD91, SHORTVERSION);
|
||||
|
||||
// console logo
|
||||
Utils::Hook::Set<char*>(0x428A66, BASEGAME "/images/logo.bmp");
|
||||
@ -236,6 +244,15 @@ namespace Components
|
||||
Utils::Hook::Set<BYTE>(0x6832BA, 0xEB);
|
||||
Utils::Hook::Set<BYTE>(0x4BD190, 0xC3);
|
||||
|
||||
//*(BYTE*)0x4BB250 = 0x33;
|
||||
//*(BYTE*)0x4BB251 = 0xC0;
|
||||
//*(DWORD*)0x4BB252 = 0xC3909090;
|
||||
|
||||
// remove 'impure stats' checking
|
||||
Utils::Hook::Set<BYTE>(0x4BB250, 0x33);
|
||||
Utils::Hook::Set<BYTE>(0x4BB251, 0xC0);
|
||||
Utils::Hook::Set<DWORD>(0x4BB252, 0xC3909090);
|
||||
|
||||
// default sv_pure to 0
|
||||
Utils::Hook::Set<BYTE>(0x4D3A74, 0);
|
||||
|
||||
|
@ -45,7 +45,6 @@ namespace Components
|
||||
}
|
||||
});
|
||||
|
||||
// TODO: Maybe execute that for clients as well, when we use triangular natting.
|
||||
if (!Dedicated::IsEnabled()) return;
|
||||
|
||||
// Load public key
|
||||
|
@ -115,7 +115,7 @@ namespace Components
|
||||
info.Set("gamename", "IW4");
|
||||
info.Set("sv_maxclients", fmt::sprintf("%i", maxclientCount));
|
||||
info.Set("protocol", fmt::sprintf("%i", PROTOCOL));
|
||||
info.Set("shortversion", VERSION_STR);
|
||||
info.Set("shortversion", SHORTVERSION);
|
||||
info.Set("mapname", Dvar::Var("mapname").Get<const char*>());
|
||||
info.Set("isPrivate", (Dvar::Var("g_password").Get<std::string>().empty() ? "0" : "1"));
|
||||
info.Set("checksum", fmt::sprintf("%X", Utils::Cryptography::JenkinsOneAtATime::Compute(fmt::sprintf("%u", Game::Sys_Milliseconds()))));
|
||||
|
@ -381,7 +381,7 @@ namespace Components
|
||||
|
||||
void ServerList::Insert(Network::Address address, Utils::InfoString info)
|
||||
{
|
||||
ServerList::RefreshContainer.Mutex.lock();
|
||||
std::lock_guard<std::mutex> _(ServerList::RefreshContainer.Mutex);
|
||||
|
||||
for (auto i = ServerList::RefreshContainer.Servers.begin(); i != ServerList::RefreshContainer.Servers.end();)
|
||||
{
|
||||
@ -449,7 +449,7 @@ namespace Components
|
||||
if (info.Get("gamename") == "IW4"
|
||||
&& server.MatchType
|
||||
#ifndef DEBUG
|
||||
&& server.Shortversion == VERSION_STR
|
||||
&& server.Shortversion == SHORTVERSION
|
||||
#endif
|
||||
)
|
||||
{
|
||||
@ -469,8 +469,6 @@ namespace Components
|
||||
++i;
|
||||
}
|
||||
}
|
||||
|
||||
ServerList::RefreshContainer.Mutex.unlock();
|
||||
}
|
||||
|
||||
ServerList::ServerInfo* ServerList::GetCurrentServer()
|
||||
@ -625,7 +623,7 @@ namespace Components
|
||||
|
||||
ServerList::RefreshContainer.AwatingList = false;
|
||||
|
||||
ServerList::RefreshContainer.Mutex.lock();
|
||||
std::lock_guard<std::mutex> _(ServerList::RefreshContainer.Mutex);
|
||||
|
||||
int offset = 0;
|
||||
int count = ServerList::RefreshContainer.Servers.size();
|
||||
@ -649,8 +647,6 @@ namespace Components
|
||||
}
|
||||
|
||||
Logger::Print("Parsed %d servers from master\n", ServerList::RefreshContainer.Servers.size() - count);
|
||||
|
||||
ServerList::RefreshContainer.Mutex.unlock();
|
||||
});
|
||||
|
||||
// Set default masterServerName + port and save it
|
||||
|
@ -13,10 +13,12 @@ namespace Components
|
||||
{
|
||||
if (Flags::HasFlag("version"))
|
||||
{
|
||||
printf("IW4x r" REVISION_STR "-" MILESTONE " (built " __DATE__ " " __TIME__ ")\n");
|
||||
printf("IW4x " VERSION " (built " __DATE__ " " __TIME__ ")\n");
|
||||
ExitProcess(0);
|
||||
}
|
||||
|
||||
Console::FreeNativeConsole();
|
||||
|
||||
if (Dedicated::IsEnabled() || ZoneBuilder::IsEnabled()) return;
|
||||
|
||||
Singleton::FirstInstance = (CreateMutexA(NULL, FALSE, "iw4x_mutex") && GetLastError() != ERROR_ALREADY_EXISTS);
|
||||
|
@ -21,6 +21,8 @@ namespace Components
|
||||
|
||||
Game::StringTable* StringTable::LoadObject(std::string filename)
|
||||
{
|
||||
filename = Utils::String::ToLower(filename);
|
||||
|
||||
Game::StringTable* table = nullptr;
|
||||
FileSystem::File rawTable(filename);
|
||||
|
||||
@ -73,6 +75,8 @@ namespace Components
|
||||
{
|
||||
Game::XAssetHeader header = { 0 };
|
||||
|
||||
filename = Utils::String::ToLower(filename);
|
||||
|
||||
if (StringTable::StringTableMap.find(filename) != StringTable::StringTableMap.end())
|
||||
{
|
||||
header.stringTable = StringTable::StringTableMap[filename];
|
||||
|
@ -103,7 +103,7 @@ namespace Components
|
||||
{
|
||||
if (Toast::Queue.empty()) return;
|
||||
|
||||
Toast::Mutex.lock();
|
||||
std::lock_guard<std::mutex> _(Toast::Mutex);
|
||||
|
||||
Toast::UIToast* toast = &Toast::Queue.front();
|
||||
|
||||
@ -121,8 +121,6 @@ namespace Components
|
||||
{
|
||||
Toast::Draw(toast);
|
||||
}
|
||||
|
||||
Toast::Mutex.unlock();
|
||||
}
|
||||
|
||||
Toast::Toast()
|
||||
|
@ -143,7 +143,6 @@ namespace Components
|
||||
|
||||
if (Window::CursorVisible)
|
||||
{
|
||||
// TODO: Apply custom cursor
|
||||
SetCursor(LoadCursor(NULL, IDC_ARROW));
|
||||
|
||||
while ((value = ShowCursor(TRUE)) < 0);
|
||||
|
@ -36,7 +36,7 @@ namespace Game
|
||||
typedef void(__cdecl * CL_SelectStringTableEntryInDvar_f_t)();
|
||||
extern CL_SelectStringTableEntryInDvar_f_t CL_SelectStringTableEntryInDvar_f;
|
||||
|
||||
typedef void(__cdecl * Cmd_AddCommand_t)(const char* cmdName, void(*function), cmd_function_t* allocedCmd, char);
|
||||
typedef void(__cdecl * Cmd_AddCommand_t)(const char* cmdName, void(*function), cmd_function_t* allocedCmd, bool isKey);
|
||||
extern Cmd_AddCommand_t Cmd_AddCommand;
|
||||
|
||||
typedef void(__cdecl * Cmd_AddServerCommand_t)(const char* name, void(*callback), cmd_function_t* data);
|
||||
|
@ -117,14 +117,14 @@ namespace Game
|
||||
typedef struct dvar_t
|
||||
{
|
||||
//startbyte:endbyte
|
||||
const char* name; //0:3
|
||||
const char* description; //4:7
|
||||
unsigned int flags; //8:11
|
||||
char type; //12:12
|
||||
char pad2[3]; //13:15
|
||||
dvar_value_t current; //16:31
|
||||
dvar_value_t latched; //32:47
|
||||
dvar_value_t _default; //48:64
|
||||
const char* name; //0:3
|
||||
const char* description; //4:7
|
||||
unsigned int flags; //8:11
|
||||
char type; //12:12
|
||||
bool modified; //13:15
|
||||
dvar_value_t current; //16:31
|
||||
dvar_value_t latched; //32:47
|
||||
dvar_value_t _default; //48:64
|
||||
dvar_maxmin_t min; //65:67
|
||||
dvar_maxmin_t max; //68:72 woooo
|
||||
} dvar_t;
|
||||
@ -136,7 +136,7 @@ namespace Game
|
||||
const char *autoCompleteDir;
|
||||
const char *autoCompleteExt;
|
||||
void(__cdecl *function)();
|
||||
int unknown;
|
||||
bool isKey; // Looks like this is true when the command is a key/button
|
||||
} cmd_function_t;
|
||||
|
||||
#pragma pack(push, 4)
|
||||
|
@ -2,7 +2,6 @@
|
||||
//
|
||||
#pragma code_page(65001)
|
||||
|
||||
#define RESOURCE_DATA
|
||||
#include "STDInclude.hpp"
|
||||
|
||||
#define APSTUDIO_READONLY_SYMBOLS
|
||||
@ -47,8 +46,8 @@ END
|
||||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION VERSION
|
||||
PRODUCTVERSION VERSION
|
||||
FILEVERSION VERSION_RC
|
||||
PRODUCTVERSION VERSION_RC
|
||||
FILEFLAGSMASK 0x3fL
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS 0x1L
|
||||
@ -69,12 +68,12 @@ BEGIN
|
||||
#else
|
||||
VALUE "FileDescription", "IW4 client modification"
|
||||
#endif
|
||||
VALUE "FileVersion", VERSION_STR
|
||||
VALUE "FileVersion", SHORTVERSION
|
||||
VALUE "InternalName", "iw4x"
|
||||
VALUE "LegalCopyright", "No rights reserved."
|
||||
VALUE "OriginalFilename", "iw4x.dll"
|
||||
VALUE "ProductName", "IW4x"
|
||||
VALUE "ProductVersion", VERSION_STR
|
||||
VALUE "ProductVersion", SHORTVERSION
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
@ -17,6 +17,10 @@ Assert_Size(DWORD, 4);
|
||||
Assert_Size(WORD, 2);
|
||||
Assert_Size(BYTE, 1);
|
||||
|
||||
// 128 bit integers (only x64)
|
||||
//Assert_Size(__int128, 16);
|
||||
//Assert_Size(unsigned __int128, 16);
|
||||
|
||||
// 64 bit integers
|
||||
Assert_Size(__int64, 8);
|
||||
Assert_Size(unsigned __int64, 8);
|
||||
@ -64,8 +68,8 @@ static_assert(sizeof(intptr_t) == 4 && sizeof(void*) == 4 && sizeof(size_t) == 4
|
||||
// Disable telemetry data logging
|
||||
extern "C"
|
||||
{
|
||||
void _cdecl __vcrt_initialize_telemetry_provider() {}
|
||||
void _cdecl __telemetry_main_invoke_trigger() {}
|
||||
void _cdecl __telemetry_main_return_trigger() {}
|
||||
void _cdecl __vcrt_uninitialize_telemetry_provider() {}
|
||||
void __cdecl __vcrt_initialize_telemetry_provider() {}
|
||||
void __cdecl __telemetry_main_invoke_trigger() {}
|
||||
void __cdecl __telemetry_main_return_trigger() {}
|
||||
void __cdecl __vcrt_uninitialize_telemetry_provider() {}
|
||||
};
|
||||
|
@ -1,9 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
// Version number
|
||||
#include <version.hpp>
|
||||
#include "version.h"
|
||||
|
||||
#ifndef RESOURCE_DATA
|
||||
#ifndef RC_INVOKED
|
||||
|
||||
#define VC_EXTRALEAN
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
@ -46,6 +46,7 @@
|
||||
#pragma warning(disable: 4100)
|
||||
#pragma warning(disable: 4389)
|
||||
#pragma warning(disable: 4702)
|
||||
#pragma warning(disable: 4996) // _CRT_SECURE_NO_WARNINGS
|
||||
#pragma warning(disable: 6001)
|
||||
#pragma warning(disable: 6011)
|
||||
#pragma warning(disable: 6031)
|
||||
@ -60,6 +61,9 @@
|
||||
#include <json11.hpp>
|
||||
#include <tomcrypt.h>
|
||||
#include <wink/signal.hpp>
|
||||
#ifndef DISABLE_BITMESSAGE
|
||||
#include <BitMRC.h>
|
||||
#endif
|
||||
|
||||
#ifdef max
|
||||
#undef max
|
||||
@ -109,28 +113,19 @@
|
||||
#pragma comment(lib, "shlwapi.lib")
|
||||
#pragma comment(lib, "Urlmon.lib")
|
||||
#pragma comment(lib, "Advapi32.lib")
|
||||
#pragma comment(lib, "rpcrt4.lib")
|
||||
|
||||
// Enable additional literals
|
||||
using namespace std::literals;
|
||||
|
||||
#endif
|
||||
|
||||
// Revision number
|
||||
#define STRINGIZE_(x) #x
|
||||
#define STRINGIZE(x) STRINGIZE_(x)
|
||||
|
||||
#define BASEGAME "iw4x"
|
||||
#define CLIENT_CONFIG "iw4x_config.cfg"
|
||||
|
||||
#define REVISION_STR STRINGIZE(REVISION)
|
||||
#if !REVISION_CLEAN
|
||||
#define REVISION_SUFFIX "*"
|
||||
#else
|
||||
#define REVISION_SUFFIX ""
|
||||
#endif
|
||||
#define VERSION 4,2,REVISION
|
||||
#define VERSION_STR "4.2." REVISION_STR
|
||||
|
||||
#define Assert_Size(x, size) static_assert(sizeof(x) == size, STRINGIZE(x) " structure has an invalid size.")
|
||||
|
||||
// Resource stuff
|
||||
|
@ -6,8 +6,13 @@ namespace Utils
|
||||
{
|
||||
std::string ZLib::Compress(std::string data)
|
||||
{
|
||||
Utils::Memory::Allocator allocator;
|
||||
unsigned long length = (data.size() * 2);
|
||||
char* buffer = Utils::Memory::AllocateArray<char>(length);
|
||||
|
||||
// Make sure the buffer is large enough
|
||||
if (length < 100) length *= 10;
|
||||
|
||||
char* buffer = allocator.AllocateArray<char>(length);
|
||||
|
||||
if (compress2(reinterpret_cast<Bytef*>(buffer), &length, reinterpret_cast<Bytef*>(const_cast<char*>(data.data())), data.size(), Z_BEST_COMPRESSION) != Z_OK)
|
||||
{
|
||||
@ -18,8 +23,6 @@ namespace Utils
|
||||
data.clear();
|
||||
data.append(buffer, length);
|
||||
|
||||
Utils::Memory::Free(buffer);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
@ -44,6 +47,7 @@ namespace Utils
|
||||
{
|
||||
stream.avail_in = std::min(static_cast<size_t>(CHUNK), data.size() - (dataPtr - data.data()));
|
||||
stream.next_in = reinterpret_cast<const uint8_t*>(dataPtr);
|
||||
dataPtr += stream.avail_in;
|
||||
|
||||
do
|
||||
{
|
||||
@ -51,7 +55,7 @@ namespace Utils
|
||||
stream.next_out = dest;
|
||||
|
||||
ret = inflate(&stream, Z_NO_FLUSH);
|
||||
if (ret == Z_STREAM_ERROR)
|
||||
if (ret != Z_OK && ret != Z_STREAM_END)
|
||||
{
|
||||
inflateEnd(&stream);
|
||||
return "";
|
||||
|
@ -20,7 +20,7 @@ namespace Utils
|
||||
}
|
||||
else
|
||||
{
|
||||
for (unsigned int i = (this->TokenString.size() - 1); i >= 0; i--)
|
||||
for (int i = static_cast<int>(this->TokenString.size() - 1); i >= 0; --i)
|
||||
{
|
||||
if (this->TokenString[i] == 0xFF)
|
||||
{
|
||||
|
@ -136,11 +136,10 @@ namespace Utils
|
||||
|
||||
Hook* Hook::Install(bool unprotect, bool keepUnportected)
|
||||
{
|
||||
Hook::StateMutex.lock();
|
||||
std::lock_guard<std::mutex> _(Hook::StateMutex);
|
||||
|
||||
if (!Hook::Initialized || Hook::Installed)
|
||||
{
|
||||
Hook::StateMutex.unlock();
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -159,8 +158,6 @@ namespace Utils
|
||||
|
||||
FlushInstructionCache(GetCurrentProcess(), Hook::Place, sizeof(Hook::Buffer));
|
||||
|
||||
Hook::StateMutex.unlock();
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
|
@ -47,5 +47,48 @@ namespace Utils
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
bool CreateDirectory(std::string dir)
|
||||
{
|
||||
char opath[MAX_PATH] = { 0 };
|
||||
char *p;
|
||||
size_t len;
|
||||
|
||||
strncpy_s(opath, dir.data(), sizeof(opath));
|
||||
len = strlen(opath);
|
||||
|
||||
if (opath[len - 1] == L'/')
|
||||
{
|
||||
opath[len - 1] = L'\0';
|
||||
}
|
||||
|
||||
for (p = opath; *p; p++)
|
||||
{
|
||||
if (*p == L'/' || *p == L'\\')
|
||||
{
|
||||
*p = L'\0';
|
||||
|
||||
if (_access(opath, 0))
|
||||
{
|
||||
if (_mkdir(opath) == -1)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
*p = L'\\';
|
||||
}
|
||||
}
|
||||
|
||||
if (_access(opath, 0))
|
||||
{
|
||||
if (_mkdir(opath) == -1)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5,5 +5,6 @@ namespace Utils
|
||||
bool FileExists(std::string file);
|
||||
void WriteFile(std::string file, std::string data);
|
||||
std::string ReadFile(std::string file);
|
||||
bool CreateDirectory(std::string dir);
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,7 @@
|
||||
#include "STDInclude.hpp"
|
||||
#ifndef DISABLE_BASE128
|
||||
#include "base128.h"
|
||||
#endif
|
||||
|
||||
namespace Utils
|
||||
{
|
||||
@ -115,5 +118,80 @@ namespace Utils
|
||||
|
||||
return fmt::sprintf("%02d:%02d:%02d", hoursTotal, minutes, seconds);
|
||||
}
|
||||
|
||||
std::string FormatBandwidth(size_t bytes, int milliseconds)
|
||||
{
|
||||
static char* sizes[] =
|
||||
{
|
||||
"B",
|
||||
"KB",
|
||||
"MB",
|
||||
"GB",
|
||||
"TB"
|
||||
};
|
||||
|
||||
double bytesPerSecond = (1000.0 / milliseconds) * bytes;
|
||||
|
||||
int i = 0;
|
||||
for (i = 0; bytesPerSecond > 1000 && i < ARRAY_SIZE(sizes); ++i) // 1024 or 1000?
|
||||
{
|
||||
bytesPerSecond /= 1000;
|
||||
}
|
||||
|
||||
return fmt::sprintf("%.2f %s/s", static_cast<float>(bytesPerSecond), sizes[i]);
|
||||
}
|
||||
|
||||
// Encodes a given string in Base64
|
||||
std::string EncodeBase64(const char* input, const unsigned long inputSize)
|
||||
{
|
||||
unsigned long outlen = long(inputSize + (inputSize / 3.0) + 16);
|
||||
unsigned char* outbuf = new unsigned char[outlen]; //Reserve output memory
|
||||
base64_encode(reinterpret_cast<unsigned char*>(const_cast<char*>(input)), inputSize, outbuf, &outlen);
|
||||
std::string ret((char*)outbuf, outlen);
|
||||
delete[] outbuf;
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Encodes a given string in Base64
|
||||
std::string EncodeBase64(const std::string& input)
|
||||
{
|
||||
return EncodeBase64(input.data(), input.size());
|
||||
}
|
||||
|
||||
#ifndef DISABLE_BASE128
|
||||
// Encodes a given string in Base128
|
||||
std::string EncodeBase128(const std::string& input)
|
||||
{
|
||||
base128 encoder;
|
||||
|
||||
void* inbuffer = const_cast<char*>(input.data());
|
||||
char* buffer = encoder.encode(inbuffer, input.size());
|
||||
/*
|
||||
Interesting to see that the buffer returned by the encoder is not a standalone string copy
|
||||
but instead is a pointer to the internal "encoded" field of the encoder. So if you deinitialize
|
||||
the encoder that string will probably become garbage.
|
||||
*/
|
||||
std::string retval(buffer);
|
||||
return retval;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Generates a UUID and returns the string representation of it
|
||||
std::string GenerateUUIDString()
|
||||
{
|
||||
// Generate UUID data
|
||||
UUID uuid;
|
||||
if (!UuidCreate(&uuid))
|
||||
{
|
||||
// Convert to string representation
|
||||
char* strdata = nullptr;
|
||||
if (!UuidToStringA(&uuid, reinterpret_cast<RPC_CSTR*>(&strdata)))
|
||||
{
|
||||
return std::string(strdata);
|
||||
}
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -35,9 +35,17 @@ namespace Utils
|
||||
std::string &Trim(std::string &s);
|
||||
|
||||
std::string FormatTimeSpan(int milliseconds);
|
||||
std::string FormatBandwidth(size_t bytes, int milliseconds);
|
||||
|
||||
std::string DumpHex(std::string data, std::string separator = " ");
|
||||
|
||||
std::string XOR(std::string str, char value);
|
||||
|
||||
std::string EncodeBase64(const char* input, const unsigned long inputSize);
|
||||
std::string EncodeBase64(const std::string& input);
|
||||
|
||||
std::string EncodeBase128(const std::string& input);
|
||||
|
||||
std::string GenerateUUIDString();
|
||||
}
|
||||
}
|
||||
|
@ -22,4 +22,17 @@ namespace Utils
|
||||
if (pos == std::string::npos) return data;
|
||||
return data.substr(0, pos).data();
|
||||
}
|
||||
|
||||
void OutputDebugLastError()
|
||||
{
|
||||
DWORD errorMessageID = ::GetLastError();
|
||||
LPSTR messageBuffer = nullptr;
|
||||
size_t size = FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
|
||||
NULL, errorMessageID, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)&messageBuffer, 0, NULL);
|
||||
std::string message(messageBuffer, size);
|
||||
|
||||
OutputDebugStringA(Utils::String::VA("Last error code: 0x%08X (%s)\n", errorMessageID, message));
|
||||
|
||||
LocalFree(messageBuffer);
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ namespace Utils
|
||||
{
|
||||
std::string GetMimeType(std::string url);
|
||||
std::string ParseChallenge(std::string data);
|
||||
void OutputDebugLastError();
|
||||
|
||||
template <typename T> void Merge(std::vector<T>* target, T* source, size_t length)
|
||||
{
|
||||
|
@ -104,10 +104,26 @@ namespace Utils
|
||||
WebIO::m_sUrl.document = server.substr(pos);
|
||||
}
|
||||
|
||||
WebIO::m_sUrl.port.clear();
|
||||
|
||||
pos = WebIO::m_sUrl.server.find(":");
|
||||
if (pos != std::string::npos)
|
||||
{
|
||||
WebIO::m_sUrl.port = WebIO::m_sUrl.server.substr(pos + 1);
|
||||
WebIO::m_sUrl.server = WebIO::m_sUrl.server.substr(0, pos);
|
||||
}
|
||||
|
||||
WebIO::m_sUrl.raw.clear();
|
||||
WebIO::m_sUrl.raw.append(WebIO::m_sUrl.protocol);
|
||||
WebIO::m_sUrl.raw.append("://");
|
||||
WebIO::m_sUrl.raw.append(WebIO::m_sUrl.server);
|
||||
|
||||
if (!WebIO::m_sUrl.port.empty())
|
||||
{
|
||||
WebIO::m_sUrl.raw.append(":");
|
||||
WebIO::m_sUrl.raw.append(WebIO::m_sUrl.port);
|
||||
}
|
||||
|
||||
WebIO::m_sUrl.raw.append(WebIO::m_sUrl.document);
|
||||
|
||||
WebIO::m_isFTP = (WebIO::m_sUrl.protocol == "ftp");
|
||||
@ -134,25 +150,33 @@ namespace Utils
|
||||
return body;
|
||||
}
|
||||
|
||||
std::string WebIO::PostFile(std::string url, std::string filename, std::string fieldname, std::string data)
|
||||
std::string WebIO::PostFile(std::string url, std::string data, std::string fieldName, std::string fileName)
|
||||
{
|
||||
WebIO::SetURL(url);
|
||||
return WebIO::PostFile(filename, fieldname, data);
|
||||
return WebIO::PostFile(data, fieldName, fileName);
|
||||
}
|
||||
|
||||
std::string WebIO::PostFile(std::string filename, std::string fieldname, std::string data)
|
||||
std::string WebIO::PostFile(std::string data, std::string fieldName, std::string fileName)
|
||||
{
|
||||
WebIO::Params headers;
|
||||
|
||||
std::string boundary = "----FormBoundary" + Utils::Cryptography::SHA256::Compute(fmt::sprintf("%d", timeGetTime()));
|
||||
std::string boundary = "----WebKitFormBoundaryHoLVocRsBxs71fU6";
|
||||
headers["Content-Type"] = "multipart/form-data, boundary=" + boundary;
|
||||
|
||||
std::string body;
|
||||
body.append("--" + boundary + "\r\n");
|
||||
body.append("Content-Disposition: form-data; name=\"" + fieldname + "\"; filename=\"" + filename + "\"\r\n");
|
||||
body.append("Content-Type: application/octet-stream\r\n\r\n");
|
||||
body.append(data + "\r\n");
|
||||
body.append("--" + boundary + "--\r\n");
|
||||
Utils::String::Replace(fieldName, "\"", "\\\"");
|
||||
Utils::String::Replace(fieldName, "\\", "\\\\");
|
||||
Utils::String::Replace(fileName, "\"", "\\\"");
|
||||
Utils::String::Replace(fileName, "\\", "\\\\");
|
||||
|
||||
std::string body = "--" + boundary + "\r\n";
|
||||
body += "Content-Disposition: form-data; name=\"";
|
||||
body += fieldName;
|
||||
body += "\"; filename=\"";
|
||||
body += fileName;
|
||||
body += "\"\r\n";
|
||||
body += "Content-Type: application/octet-stream\r\n\r\n";
|
||||
body += data + "\r\n";
|
||||
body += "--" + boundary + "--\r\n";
|
||||
|
||||
headers["Content-Length"] = fmt::sprintf("%u", body.size());
|
||||
|
||||
@ -209,6 +233,11 @@ namespace Utils
|
||||
wPort = INTERNET_DEFAULT_HTTPS_PORT;
|
||||
}
|
||||
|
||||
if (!WebIO::m_sUrl.port.empty())
|
||||
{
|
||||
wPort = static_cast<WORD>(atoi(WebIO::m_sUrl.port.data()));
|
||||
}
|
||||
|
||||
const char* username = (WebIO::m_username.size() ? WebIO::m_username.data() : NULL);
|
||||
const char* password = (WebIO::m_password.size() ? WebIO::m_password.data() : NULL);
|
||||
WebIO::m_hConnect = InternetConnectA(WebIO::m_hSession, WebIO::m_sUrl.server.data(), wPort, username, password, dwService, dwFlag, 0);
|
||||
|
@ -26,8 +26,8 @@ namespace Utils
|
||||
void SetURL(std::string url);
|
||||
void SetCredentials(std::string username, std::string password);
|
||||
|
||||
std::string PostFile(std::string url, std::string filename, std::string fieldname, std::string data);
|
||||
std::string PostFile(std::string filename, std::string fieldname, std::string data);
|
||||
std::string PostFile(std::string url, std::string data, std::string fieldName, std::string fileName);
|
||||
std::string PostFile(std::string data, std::string fieldName, std::string fileName);
|
||||
|
||||
std::string Post(std::string url, WebIO::Params params);
|
||||
std::string Post(std::string url, std::string body);
|
||||
@ -74,6 +74,7 @@ namespace Utils
|
||||
std::string protocol;
|
||||
std::string server;
|
||||
std::string document;
|
||||
std::string port;
|
||||
std::string raw;
|
||||
};
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user