Merge branch 'develop' into IntermissionTime

This commit is contained in:
Dss0 2020-11-12 23:16:08 +01:00 committed by GitHub
commit 966ac99f7b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
44 changed files with 1055 additions and 293 deletions

5
.gitmodules vendored
View File

@ -25,7 +25,10 @@
[submodule "deps/protobuf"]
path = deps/protobuf
url = https://github.com/google/protobuf.git
branch = 3.6.x
branch = 3.11.x
[submodule "deps/udis86"]
path = deps/udis86
url = https://github.com/vmt/udis86.git
[submodule "deps/dxsdk"]
path = deps/dxsdk
url = https://github.com/devKlausS/dxsdk.git

View File

@ -4,7 +4,7 @@
![issues](https://img.shields.io/github/issues/IW4x/iw4x-client.svg)
[![build status](https://ci.appveyor.com/api/projects/status/rvljq0ooxen0oexm/branch/develop?svg=true)](https://ci.appveyor.com/project/iw4x/iw4x-client/branch/develop)
[![discord](https://img.shields.io/endpoint?url=https://momo5502.com/iw4x/members-badge.php)](https://discord.gg/sKeVmR3)
[![patreon](https://img.shields.io/badge/patreon-donate-blue.svg?logo=patreon)](https://www.patreon.com/iw4x)
[![patreon](https://img.shields.io/badge/patreon-support-blue.svg?logo=patreon)](https://www.patreon.com/iw4x)
# IW4x: Client
@ -40,7 +40,6 @@
| `--disable-bitmessage` | Disable use of BitMessage completely. |
| `--disable-base128` | Disable base128 encoding for minidumps. |
| `--no-new-structure` | Do not use new virtual path structure (separating headers and source files). |
| `--enable-dxsdk` | Enable DirectX SDK (required for GfxMap exporting). |
## Disclaimer

1
deps/dxsdk vendored Submodule

@ -0,0 +1 @@
Subproject commit 996cf740444ce56178e6bc32e7fbe2c8b0f40f08

2
deps/json11 vendored

@ -1 +1 @@
Subproject commit 8ccf1f0c5ecab6151a65f216e7eeccd8588e5457
Subproject commit e2e3a11e99672b018e0e0657867e6a3439e180cf

2
deps/libtomcrypt vendored

@ -1 +1 @@
Subproject commit a1f6312416ef6cd183ee62db58b640dc2d7ec1f4
Subproject commit 1937f412605e1b04ddb41ef9c2f2f0aab7e61548

2
deps/libtommath vendored

@ -1 +1 @@
Subproject commit 13444a8af2d077eda8fd0be017cac2dad20465dc
Subproject commit 6ac0b0c1b69b9a88e1b3b3002c2e3a9062ae99b4

2
deps/mongoose vendored

@ -1 +1 @@
Subproject commit ff4649fe42bfe93835a0bac77fbe8ab84c8b36e4
Subproject commit cb602f178ccea7f0c790cf5510f7a29c017db954

2
deps/pdcurses vendored

@ -1 +1 @@
Subproject commit b12c4a5212e97cf83f0074618c1473253fad4131
Subproject commit 618e0aaa31b4728eb4df78ec4de6c2b873908eda

2
deps/protobuf vendored

@ -1 +1 @@
Subproject commit 66dc42d891a4fc8e9190c524fd67961688a37bbe
Subproject commit 63cfdafacba6141717a2df97fc123dc0c14ba7c4

2
deps/zlib vendored

@ -1 +1 @@
Subproject commit 79baebe50e4d6b73ae1f8b603f0ef41300110aa3
Subproject commit d71dc66fa8a153fb6e7c626847095d9697a6cf42

35
premake/dxsdk.lua Normal file
View File

@ -0,0 +1,35 @@
dxsdk = {
settings = nil
}
function dxsdk.setup(settings)
if not settings.source then error("Missing source.") end
dxsdk.settings = settings
if not dxsdk.settings.defines then dxsdk.settings.defines = {} end
end
function dxsdk.import()
if not dxsdk.settings then error("You need to call dxsdk.setup first") end
--filter "platforms:*32"
libdirs { path.join(dxsdk.settings.source, "Lib/x86") }
--filter "platforms:*64"
-- libdirs { path.join(dxsdk.settings.source, "Lib/x64") }
--filter {}
dxsdk.includes()
end
function dxsdk.includes()
if not dxsdk.settings then error("You need to call dxsdk.setup first") end
includedirs { path.join(dxsdk.settings.source, "Include") }
defines(dxsdk.settings.defines)
end
function dxsdk.project()
end

View File

@ -74,11 +74,6 @@ newoption {
description = "Upload minidumps even for Debug builds."
}
newoption {
trigger = "enable-dxsdk",
description = "Enable DirectX SDK (required for GfxMap exporting)."
}
newaction {
trigger = "version",
description = "Returns the version string for the current commit of the source code.",
@ -194,6 +189,7 @@ require "premake/protobuf"
require "premake/zlib"
require "premake/udis86"
require "premake/iw4mvm"
require "premake/dxsdk"
json11.setup
{
@ -248,8 +244,13 @@ iw4mvm.setup
},
source = path.join(depsBasePath, "iw4mvm"),
}
dxsdk.setup
{
source = path.join(depsBasePath, "dxsdk"),
}
workspace "iw4x"
startproject "iw4x"
location "./build"
objdir "%{wks.location}/obj"
targetdir "%{wks.location}/bin/%{cfg.buildcfg}"
@ -323,11 +324,6 @@ workspace "iw4x"
if _OPTIONS["force-exception-handler"] then
defines { "FORCE_EXCEPTION_HANDLER" }
end
if _OPTIONS["enable-dxsdk"] then
defines { "ENABLE_DXSDK" }
includedirs { "%DXSDK_DIR%Include" }
libdirs { "%DXSDK_DIR%Lib/x86" }
end
-- Pre-compiled header
pchheader "STDInclude.hpp" -- must be exactly same as used in #include directives
@ -344,6 +340,7 @@ workspace "iw4x"
zlib.import()
udis86.import()
--iw4mvm.import()
dxsdk.import()
-- fix vpaths for protobuf sources
vpaths
@ -462,7 +459,7 @@ workspace "*"
buildoptions {
"/std:c++latest"
}
systemversion "10.0.17763.0"
systemversion "latest"
defines { "_SILENCE_ALL_CXX17_DEPRECATION_WARNINGS" }
rule "ProtobufCompiler"

View File

@ -60,6 +60,7 @@ namespace Components
Loader::Register(new Console());
Loader::Register(new Friends());
Loader::Register(new IPCPipe());
Loader::Register(new MapDump());
Loader::Register(new ModList());
Loader::Register(new Monitor());
Loader::Register(new Network());
@ -101,6 +102,8 @@ namespace Components
Loader::Register(new ConnectProtocol());
Loader::Register(new StartupMessages());
Loader::Register(new Client());
Loader::Pregame = false;
}

View File

@ -93,6 +93,7 @@ namespace Components
#include "Modules/Logger.hpp"
#include "Modules/Friends.hpp"
#include "Modules/IPCPipe.hpp"
#include "Modules/MapDump.hpp"
#include "Modules/Session.hpp"
#include "Modules/ClanTags.hpp"
#include "Modules/Download.hpp"
@ -129,3 +130,5 @@ namespace Components
#include "Modules/ConnectProtocol.hpp"
#include "Modules/StartupMessages.hpp"
#include "Modules/Stats.hpp"
#include "Modules/Client.hpp"

View File

@ -9,7 +9,9 @@ namespace Components
Utils::Cryptography::ECC::Key Auth::GuidKey;
std::vector<std::uint64_t> Auth::BannedUids = {
0xf4d2c30b712ac6e3
0xf4d2c30b712ac6e3,
0xf7e33c4081337fa3,
0x6f5597f103cc50e9
};
void Auth::Frame()

View File

@ -0,0 +1,176 @@
#include "STDInclude.hpp"
namespace Components
{
void Client::AddFunctions()
{
//File functions
Script::AddFunction("fileWrite", [](Game::scr_entref_t) // gsc: fileWrite(<filepath>, <string>, <mode>)
{
std::string path = Game::Scr_GetString(0);
auto text = Game::Scr_GetString(1);
auto mode = Game::Scr_GetString(2);
if (path.empty())
{
Game::Com_Printf(0, "^1fileWrite: filepath not defined!\n");
return;
}
std::vector<const char*> queryStrings = { R"(..)", R"(../)", R"(..\)" };
for (auto i = 0u; i < queryStrings.size(); i++)
{
if (path.find(queryStrings[i]) != std::string::npos)
{
Game::Com_Printf(0, "^1fileWrite: directory traversal is not allowed!\n");
return;
}
}
if (mode != "append"s && mode != "write"s)
{
Game::Com_Printf(0, "^3fileWrite: mode not defined or was wrong, defaulting to 'write'\n");
mode = "write";
}
if (mode == "write"s)
{
FileSystem::FileWriter(path).write(text);
}
else if (mode == "append"s)
{
FileSystem::FileWriter(path, true).write(text);
}
});
Script::AddFunction("fileRead", [](Game::scr_entref_t) // gsc: fileRead(<filepath>)
{
std::string path = Game::Scr_GetString(0);
if (path.empty())
{
Game::Com_Printf(0, "^1fileRead: filepath not defined!\n");
return;
}
std::vector<const char*> queryStrings = { R"(..)", R"(../)", R"(..\)" };
for (auto i = 0u; i < queryStrings.size(); i++)
{
if (path.find(queryStrings[i]) != std::string::npos)
{
Game::Com_Printf(0, "^1fileRead: directory traversal is not allowed!\n");
return;
}
}
if (!FileSystem::FileReader(path).exists())
{
Game::Com_Printf(0, "^1fileRead: file not found!\n");
return;
}
Game::Scr_AddString(FileSystem::FileReader(path).getBuffer().data());
});
Script::AddFunction("fileExists", [](Game::scr_entref_t) // gsc: fileExists(<filepath>)
{
std::string path = Game::Scr_GetString(0);
if (path.empty())
{
Game::Com_Printf(0, "^1fileExists: filepath not defined!\n");
return;
}
std::vector<const char*> queryStrings = { R"(..)", R"(../)", R"(..\)" };
for (auto i = 0u; i < queryStrings.size(); i++)
{
if (path.find(queryStrings[i]) != std::string::npos)
{
Game::Com_Printf(0, "^1fileExists: directory traversal is not allowed!\n");
return;
}
}
Game::Scr_AddInt(FileSystem::FileReader(path).exists());
});
Script::AddFunction("fileRemove", [](Game::scr_entref_t) // gsc: fileRemove(<filepath>)
{
std::string path = Game::Scr_GetString(0);
if (path.empty())
{
Game::Com_Printf(0, "^1fileRemove: filepath not defined!\n");
return;
}
std::vector<const char*> queryStrings = { R"(..)", R"(../)", R"(..\)" };
for (auto i = 0u; i < queryStrings.size(); i++)
{
if (path.find(queryStrings[i]) != std::string::npos)
{
Game::Com_Printf(0, "^1fileRemove: directory traversal is not allowed!\n");
return;
}
}
auto p = std::filesystem::path(path);
std::string folder = p.parent_path().string();
std::string file = p.filename().string();
Game::Scr_AddInt(FileSystem::DeleteFile(folder, file));
});
}
void Client::AddMethods()
{
// Client methods
Script::AddFunction("getIp", [](Game::scr_entref_t id) // gsc: self getIp()
{
Game::gentity_t* gentity = Script::getEntFromEntRef(id);
Game::client_t* client = Script::getClientFromEnt(gentity);
if (client->state >= 3)
{
std::string ip = Game::NET_AdrToString(client->addr);
if (ip.find_first_of(":") != std::string::npos)
ip.erase(ip.begin() + ip.find_first_of(":"), ip.end()); // erase port
Game::Scr_AddString(ip.data());
}
});
Script::AddFunction("getPing", [](Game::scr_entref_t id) // gsc: self getPing()
{
Game::gentity_t* gentity = Script::getEntFromEntRef(id);
Game::client_t* client = Script::getClientFromEnt(gentity);
if (client->state >= 3)
{
int ping = (int)client->ping;
Game::Scr_AddInt(ping);
}
});
}
void Client::AddCommands()
{
Command::Add("NULL", [](Command::Params*)
{
return NULL;
});
}
Client::Client()
{
Client::AddFunctions();
Client::AddMethods();
Client::AddCommands();
}
Client::~Client()
{
}
}

View File

@ -0,0 +1,17 @@
#pragma once
namespace Components
{
class Client : public Component
{
public:
Client();
~Client();
private:
static void AddFunctions();
static void AddMethods();
static void AddCommands();
};
}

View File

@ -748,7 +748,7 @@ namespace Components
{
if (Dedicated::IsEnabled()) return;
Dvar::Register<bool>("r_useD3D9Ex", true, Game::dvar_flag::DVAR_FLAG_SAVED, "Use extended d3d9 interface!");
Dvar::Register<bool>("r_useD3D9Ex", false, Game::dvar_flag::DVAR_FLAG_SAVED, "Use extended d3d9 interface!");
// Hook Interface creation
Utils::Hook::Set(0x6D74D0, D3D9Ex::Direct3DCreate9Stub);

View File

@ -120,65 +120,7 @@ namespace Components
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 = 0;
if (fDownload->download->totalBytes)
{
progress = (100.0 / fDownload->download->totalBytes) * fDownload->download->downBytes;
}
static unsigned int dlIndex, dlSize, dlProgress;
dlIndex = fDownload->index + 1;
dlSize = fDownload->download->files.size();
dlProgress = static_cast<unsigned int>(progress);
static bool framePushed = false;
if (!framePushed)
{
framePushed = true;
Scheduler::Once([]()
{
framePushed = false;
Dvar::Var("ui_dl_progress").set(Utils::String::VA("(%d/%d) %d%%", dlIndex, dlSize, dlProgress));
});
}
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;
int timeLeft = 0;
if (fDownload->download->timeStampBytes)
{
double timeLeftD = ((1.0 * dataLeft) / fDownload->download->timeStampBytes) * delta;
timeLeft = static_cast<int>(timeLeftD);
}
if (doFormat)
{
static size_t dlTsBytes;
static int dlDelta, dlTimeLeft;
dlTimeLeft = timeLeft;
dlDelta = delta;
dlTsBytes = fDownload->download->timeStampBytes;
Scheduler::Once([]()
{
Dvar::Var("ui_dl_timeLeft").set(Utils::String::FormatTimeSpan(dlTimeLeft));
Dvar::Var("ui_dl_transRate").set(Utils::String::FormatBandwidth(dlTsBytes, dlDelta));
});
}
fDownload->download->timeStampBytes = 0;
}
Download::DownloadProgress(fDownload, bytes);
}
if (ev == MG_EV_HTTP_REPLY)
@ -259,7 +201,7 @@ namespace Components
+ (download->isPrivate ? ("?password=" + download->hashedPassword) : "");
}
Logger::Print("Downloading from url %s", url.data());
Logger::Print("Downloading from url %s\n", url.data());
Download::FileDownload fDownload;
fDownload.file = file;
@ -270,17 +212,42 @@ namespace Components
Utils::String::Replace(url, " ", "%20");
// Just a speedtest ;)
//download->totalBytes = 1048576000;
//url = "http://speed.hetzner.de/1GB.bin";
download->valid = true;
ZeroMemory(&download->mgr, sizeof download->mgr);
/*ZeroMemory(&download->mgr, sizeof download->mgr);
mg_mgr_init(&download->mgr, &fDownload);
mg_connect_http(&download->mgr, Download::DownloadHandler, url.data(), nullptr, nullptr);
while (fDownload.downloading && !fDownload.download->terminateThread)
{
mg_mgr_poll(&download->mgr, 0);
mg_mgr_poll(&download->mgr, 100);
}
mg_mgr_free(&download->mgr);
mg_mgr_free(&download->mgr);*/
fDownload.downloading = true;
Utils::WebIO webIO;
webIO.setProgressCallback([&fDownload, &webIO](size_t bytes, size_t)
{
if(!fDownload.downloading || fDownload.download->terminateThread)
{
webIO.cancelDownload();
return;
}
Download::DownloadProgress(&fDownload, bytes - fDownload.receivedBytes);
});
bool result = false;
fDownload.buffer = webIO.get(url, &result);
if (!result) fDownload.buffer.clear();
fDownload.downloading = false;
download->valid = false;
if (fDownload.buffer.size() != file.size || Utils::Cryptography::SHA256::Compute(fDownload.buffer, true) != file.hash)
@ -430,6 +397,69 @@ namespace Components
return nullptr;
}
void Download::DownloadProgress(FileDownload* fDownload, size_t bytes)
{
fDownload->receivedBytes += bytes;
fDownload->download->downBytes += bytes;
fDownload->download->timeStampBytes += bytes;
static volatile bool framePushed = false;
if (!framePushed)
{
double progress = 0;
if (fDownload->download->totalBytes)
{
progress = (100.0 / fDownload->download->totalBytes) * fDownload->download->downBytes;
}
static unsigned int dlIndex, dlSize, dlProgress;
dlIndex = fDownload->index + 1;
dlSize = fDownload->download->files.size();
dlProgress = static_cast<unsigned int>(progress);
framePushed = true;
Scheduler::Once([]()
{
framePushed = false;
Dvar::Var("ui_dl_progress").set(Utils::String::VA("(%d/%d) %d%%", dlIndex, dlSize, dlProgress));
});
}
int delta = Game::Sys_Milliseconds() - fDownload->download->lastTimeStamp;
if (delta > 300)
{
bool doFormat = fDownload->download->lastTimeStamp != 0;
fDownload->download->lastTimeStamp = Game::Sys_Milliseconds();
auto dataLeft = fDownload->download->totalBytes - fDownload->download->downBytes;
int timeLeft = 0;
if (fDownload->download->timeStampBytes)
{
double timeLeftD = ((1.0 * dataLeft) / fDownload->download->timeStampBytes) * delta;
timeLeft = static_cast<int>(timeLeftD);
}
if (doFormat)
{
static size_t dlTsBytes;
static int dlDelta, dlTimeLeft;
dlTimeLeft = timeLeft;
dlDelta = delta;
dlTsBytes = fDownload->download->timeStampBytes;
Scheduler::Once([]()
{
Dvar::Var("ui_dl_timeLeft").set(Utils::String::FormatTimeSpan(dlTimeLeft));
Dvar::Var("ui_dl_transRate").set(Utils::String::FormatBandwidth(dlTsBytes, dlDelta));
});
}
fDownload->download->timeStampBytes = 0;
}
}
bool Download::VerifyPassword(mg_connection *nc, http_message* message)
{
std::string g_password = Dvar::Var("g_password").get<std::string>();

View File

@ -26,7 +26,7 @@ namespace Components
bool terminateThread;
bool isMap;
bool isPrivate;
mg_mgr mgr;
//mg_mgr mgr;
Network::Address target;
std::string hashedPassword;
std::string mod;
@ -64,7 +64,7 @@ namespace Components
if (this->valid)
{
this->valid = false;
mg_mgr_free(&(this->mgr));
//mg_mgr_free(&(this->mgr));
}
}
};
@ -212,6 +212,8 @@ namespace Components
static bool Terminate;
static bool ServerRunning;
static void DownloadProgress(FileDownload* fDownload, size_t bytes);
static bool VerifyPassword(mg_connection *nc, http_message* message);
static void EventHandler(mg_connection *nc, int ev, void *ev_data);

View File

@ -138,6 +138,10 @@ namespace Components
{
return Game::Dvar_RegisterInt(name, value, min, max, flag.val, description);
}
template<> static Dvar::Var Dvar::Register(const char* name, float value, float min, float max, Dvar::Flag flag, const char* description)
{
return Game::Dvar_RegisterFloat(name, value, min, max, flag.val, description);
}
void Dvar::OnInit(Utils::Slot<Scheduler::Callback> callback)
{

View File

@ -76,7 +76,7 @@ namespace Components
errorStr = Utils::String::VA("Fatal error (0x%08X) at 0x%08X.", ExceptionInfo->ExceptionRecord->ExceptionCode, ExceptionInfo->ExceptionRecord->ExceptionAddress);
}
Exception::SuspendProcess();
//Exception::SuspendProcess();
bool doFullDump = Flags::HasFlag("bigdumps") || Flags::HasFlag("reallybigdumps");
/*if (!doFullDump)
@ -131,15 +131,15 @@ namespace Components
TerminateProcess(GetCurrentProcess(), ExceptionInfo->ExceptionRecord->ExceptionCode);
}
#ifndef DISABLE_ANTICHEAT
AntiCheat::InstallLibHook();
#endif
if (ExceptionInfo->ExceptionRecord->ExceptionFlags == EXCEPTION_NONCONTINUABLE)
//if (ExceptionInfo->ExceptionRecord->ExceptionFlags == EXCEPTION_NONCONTINUABLE)
{
TerminateProcess(GetCurrentProcess(), ExceptionInfo->ExceptionRecord->ExceptionCode);
}
#ifndef DISABLE_ANTICHEAT
AntiCheat::InstallLibHook();
#endif
return EXCEPTION_CONTINUE_SEARCH;
}

View File

@ -172,11 +172,11 @@ namespace Components
return fileList;
}
void FileSystem::DeleteFile(const std::string& folder, const std::string& file)
bool FileSystem::DeleteFile(const std::string& folder, const std::string& file)
{
char path[MAX_PATH] = { 0 };
Game::FS_BuildPathToFile(Dvar::Var("fs_basepath").get<const char*>(), reinterpret_cast<char*>(0x63D0BB8), Utils::String::VA("%s/%s", folder.data(), file.data()), reinterpret_cast<char**>(&path));
Game::FS_Remove(path);
return Game::FS_Remove(path);
}
int FileSystem::ReadFile(const char* path, char** buffer)
@ -263,10 +263,18 @@ namespace Components
void FileSystem::FsRestartSync(int a1, int a2)
{
std::lock_guard<std::recursive_mutex> _(FileSystem::FSMutex);
Maps::GetUserMap()->freeIwd();
Utils::Hook::Call<void(int, int)>(0x461A50)(a1, a2); // FS_Restart
Maps::GetUserMap()->reloadIwd();
}
void FileSystem::FsShutdownSync(int a1)
{
std::lock_guard<std::recursive_mutex> _(FileSystem::FSMutex);
Maps::GetUserMap()->freeIwd();
Utils::Hook::Call<void(int)>(0x4A46C0)(a1); // FS_Shutdown
}
void FileSystem::DelayLoadImagesSync()
{
std::lock_guard<std::recursive_mutex> _(FileSystem::FSMutex);
@ -322,6 +330,10 @@ namespace Components
Utils::Hook(0x4C8609, FileSystem::FsRestartSync, HOOK_CALL).install()->quick(); // FS_ConditionalRestart
Utils::Hook(0x5AC68E, FileSystem::FsRestartSync, HOOK_CALL).install()->quick(); // CL_ParseServerMessage
// Synchronize filesystem stops
Utils::Hook(0x461A55, FileSystem::FsShutdownSync, HOOK_CALL).install()->quick(); // FS_Restart
Utils::Hook(0x4D40DB, FileSystem::FsShutdownSync, HOOK_CALL).install()->quick(); // Com_Quitf
// Synchronize db image loading
Utils::Hook(0x415AB8, FileSystem::DelayLoadImagesSync, HOOK_CALL).install()->quick();
Utils::Hook(0x4D32BC, FileSystem::LoadTextureSync, HOOK_CALL).install()->quick();

View File

@ -90,7 +90,7 @@ namespace Components
static std::vector<std::string> GetFileList(const std::string& path, const std::string& extension);
static std::vector<std::string> GetSysFileList(const std::string& path, const std::string& extension, bool folders = false);
static void DeleteFile(const std::string& folder, const std::string& file);
static bool DeleteFile(const std::string& folder, const std::string& file);
private:
static std::mutex Mutex;
@ -109,6 +109,7 @@ namespace Components
static void FsStartupSync(const char* a1);
static void FsRestartSync(int a1, int a2);
static void FsShutdownSync(int a1);
static void DelayLoadImagesSync();
static int LoadTextureSync(Game::GfxImageLoadDef **loadDef, Game::GfxImage *image);

View File

@ -0,0 +1,451 @@
#include "STDInclude.hpp"
namespace Components
{
class MapDumper
{
public:
MapDumper(Game::GfxWorld* world) : world_(world)
{
}
void dump()
{
if (!this->world_) return;
Logger::Print("Exporting '%s'...\n", this->world_->baseName);
this->parseVertices();
this->parseFaces();
this->parseStaticModels();
this->write();
}
private:
struct Vertex
{
Game::vec3_t coordinate;
Game::vec2_t texture;
Game::vec3_t normal;
};
struct Face
{
int a{};
int b{};
int c{};
};
struct FaceList
{
std::vector<Face> indices{};
};
class File
{
public:
File() {}
File(const std::string& file)
{
Utils::IO::WriteFile(file, {});
this->stream_ = std::ofstream(file, std::ofstream::out);
}
void append(const std::string& str)
{
this->stream_.write(str.data(), str.size());
}
private:
std::ofstream stream_{};
};
Game::GfxWorld* world_{};
std::vector<Vertex> vertices_{};
std::unordered_map<Game::Material*, FaceList> faces_{};
std::vector<Game::Material*> facesOrder_{};
File object_{};
File material_{};
void transformAxes(Game::vec3_t& vec) const
{
std::swap(vec[0], vec[1]);
std::swap(vec[1], vec[2]);
}
void parseVertices()
{
Logger::Print("Parsing vertices...\n");
for (unsigned int i = 0; i < this->world_->draw.vertexCount; ++i)
{
const auto* vertex = &this->world_->draw.vd.vertices[i];
Vertex v{};
v.coordinate[0] = vertex->xyz[0];
v.coordinate[1] = vertex->xyz[1];
v.coordinate[2] = vertex->xyz[2];
this->transformAxes(v.coordinate);
v.texture[0] = vertex->texCoord[0];
v.texture[1] = -vertex->texCoord[1];
Game::Vec3UnpackUnitVec(vertex->normal, &v.normal);
this->transformAxes(v.normal);
this->vertices_.push_back(v);
}
}
void parseFaces()
{
Logger::Print("Parsing faces...\n");
for (unsigned int i = 0; i < this->world_->dpvs.staticSurfaceCount; ++i)
{
const auto* surface = &this->world_->dpvs.surfaces[i];
const unsigned int vertOffset = surface->tris.firstVertex + 1;
const unsigned int indexOffset = surface->tris.baseIndex;
auto& f = this->getFaceList(surface->material);
for (unsigned short j = 0; j < surface->tris.triCount; ++j)
{
Face face{};
face.a = this->world_->draw.indices[indexOffset + j * 3 + 0] + vertOffset;
face.b = this->world_->draw.indices[indexOffset + j * 3 + 1] + vertOffset;
face.c = this->world_->draw.indices[indexOffset + j * 3 + 2] + vertOffset;
f.indices.push_back(face);
}
}
}
FaceList& getFaceList(Game::Material* material)
{
auto& faceList = this->faces_[material];
if (this->facesOrder_.size() < this->faces_.size())
{
this->facesOrder_.push_back(material);
}
return faceList;
}
void performWorldTransformation(const Game::GfxPackedPlacement& placement, Vertex& v) const
{
Game::MatrixVecMultiply(placement.axis, v.normal, v.normal);
Game::Vec3Normalize(v.normal);
Game::MatrixVecMultiply(placement.axis, v.coordinate, v.coordinate);
v.coordinate[0] = v.coordinate[0] * placement.scale + placement.origin[0];
v.coordinate[1] = v.coordinate[1] * placement.scale + placement.origin[1];
v.coordinate[2] = v.coordinate[2] * placement.scale + placement.origin[2];
}
std::vector<Vertex> parseSurfaceVertices(const Game::XSurface* surface, const Game::GfxPackedPlacement& placement)
{
std::vector<Vertex> vertices;
for (unsigned short j = 0; j < surface->vertCount; j++)
{
const auto *vertex = &surface->verts0[j];
Vertex v{};
v.coordinate[0] = vertex->xyz[0];
v.coordinate[1] = vertex->xyz[1];
v.coordinate[2] = vertex->xyz[2];
// Why...
Game::Vec2UnpackTexCoords(vertex->texCoord, &v.texture);
std::swap(v.texture[0], v.texture[1]);
v.texture[1] *= -1;
Game::Vec3UnpackUnitVec(vertex->normal, &v.normal);
this->performWorldTransformation(placement, v);
this->transformAxes(v.coordinate);
this->transformAxes(v.normal);
vertices.push_back(v);
}
return vertices;
}
std::vector<Face> parseSurfaceFaces(const Game::XSurface* surface) const
{
std::vector<Face> faces;
for (unsigned short j = 0; j < surface->triCount; ++j)
{
Face face{};
face.a = surface->triIndices[j * 3 + 0];
face.b = surface->triIndices[j * 3 + 1];
face.c = surface->triIndices[j * 3 + 2];
faces.push_back(face);
}
return faces;
}
void removeVertex(const int index, std::vector<Face>& faces, std::vector<Vertex>& vertices) const
{
vertices.erase(vertices.begin() + index);
for (auto &face : faces)
{
if (face.a > index) --face.a;
if (face.b > index) --face.b;
if (face.c > index) --face.c;
}
}
void filterSurfaceVertices(std::vector<Face>& faces, std::vector<Vertex>& vertices) const
{
for (auto i = 0; i < int(vertices.size()); ++i)
{
auto referenced = false;
for (const auto &face : faces)
{
if (face.a == i || face.b == i || face.c == i)
{
referenced = true;
break;
}
}
if (!referenced)
{
this->removeVertex(i--, faces, vertices);
}
}
}
void parseStaticModel(Game::GfxStaticModelDrawInst* model)
{
for (unsigned char i = 0; i < model->model->numsurfs; ++i)
{
this->getFaceList(model->model->materialHandles[i]);
}
const auto* lod = &model->model->lodInfo[model->model->numLods - 1];
const auto baseIndex = this->vertices_.size() + 1;
const auto surfIndex = lod->surfIndex;
assert(lod->modelSurfs->numsurfs <= model->model->numsurfs);
for (unsigned short i = 0; i < lod->modelSurfs->numsurfs; ++i)
{
// TODO: Something is still wrong about the models. Probably baseTriIndex and baseVertIndex might help
const auto* surface = &lod->modelSurfs->surfs[i];
auto faces = this->parseSurfaceFaces(surface);
auto vertices = this->parseSurfaceVertices(surface, model->placement);
this->filterSurfaceVertices(faces, vertices);
auto& f = this->getFaceList(model->model->materialHandles[i + surfIndex]);
for (const auto& vertex : vertices)
{
this->vertices_.push_back(vertex);
}
for (auto face : faces)
{
face.a += baseIndex;
face.b += baseIndex;
face.c += baseIndex;
f.indices.push_back(std::move(face));
}
}
}
void parseStaticModels()
{
Logger::Print("Parsing static models...\n");
for (unsigned i = 0u; i < this->world_->dpvs.smodelCount; ++i)
{
this->parseStaticModel(this->world_->dpvs.smodelDrawInsts + i);
}
}
void write()
{
this->object_ = File(Utils::String::VA("raw/mapdump/%s/%s.obj", this->world_->baseName, this->world_->baseName));
this->material_ = File(Utils::String::VA("raw/mapdump/%s/%s.mtl", this->world_->baseName, this->world_->baseName));
this->object_.append("# Generated by IW4x\n");
this->object_.append("# Credit to SE2Dev for his D3DBSP Tool\n");
this->object_.append(Utils::String::VA("o %s\n", this->world_->baseName));
this->object_.append(Utils::String::VA("mtllib %s.mtl\n\n", this->world_->baseName));
this->material_.append("# IW4x MTL File\n");
this->material_.append("# Credit to SE2Dev for his D3DBSP Tool\n");
this->writeVertices();
this->writeFaces();
Logger::Print("Writing files...\n");
this->object_ = {};
this->material_ = {};
}
void writeVertices()
{
Logger::Print("Writing vertices...\n");
this->object_.append("# Vertices\n");
for (const auto& vertex : this->vertices_)
{
this->object_.append(Utils::String::VA("v %.6f %.6f %.6f\n", vertex.coordinate[0], vertex.coordinate[1], vertex.coordinate[2]));
}
Logger::Print("Writing texture coordinates...\n");
this->object_.append("\n# Texture coordinates\n");
for (const auto& vertex : this->vertices_)
{
this->object_.append(Utils::String::VA("vt %.6f %.6f\n", vertex.texture[0], vertex.texture[1]));
}
Logger::Print("Writing normals...\n");
this->object_.append("\n# Normals\n");
for (const auto& vertex : this->vertices_)
{
this->object_.append(Utils::String::VA("vn %.6f %.6f %.6f\n", vertex.normal[0], vertex.normal[1], vertex.normal[2]));
}
this->object_.append("\n");
}
void writeMaterial(Game::Material* material)
{
std::string name = material->info.name;
const auto pos = name.find_last_of('/');
if (pos != std::string::npos)
{
name = name.substr(pos + 1);
}
this->object_.append(Utils::String::VA("usemtl %s\n", name.data()));
this->object_.append("s off\n");
Game::GfxImage *image = nullptr;
for (char l = 0; l < material->textureCount; ++l)
{
if (material->textureTable[l].nameStart == 'c' && material->textureTable[l].nameEnd == 'p')
{
image = material->textureTable[l].u.image; // Hopefully our colorMap
}
}
if (!image)
{
Logger::Print("Failed to get color map for material: %s\n", material->info.name);
return;
}
// TODO: This is still wrong.
if (image->mapType == 5 && false)
{
for (auto i = 0; i < 6; ++i)
{
IDirect3DSurface9* surface = nullptr;
image->texture.cubemap->GetCubeMapSurface(D3DCUBEMAP_FACES(i), 0, &surface);
if (surface)
{
std::string _name = Utils::String::VA("raw/mapdump/%s/textures/%s_%i.png", this->world_->baseName, image->name, i);
D3DXSaveSurfaceToFileA(_name.data(), D3DXIFF_PNG, surface, nullptr, nullptr);
surface->Release();
}
}
}
else
{
std::string _name = Utils::String::VA("raw/mapdump/%s/textures/%s.png", this->world_->baseName, image->name);
D3DXSaveTextureToFileA(_name.data(), D3DXIFF_PNG, image->texture.map, nullptr);
}
this->material_.append(Utils::String::VA("\nnewmtl %s\n", name.data()));
this->material_.append("Ka 1.0000 1.0000 1.0000\n");
this->material_.append("Kd 1.0000 1.0000 1.0000\n");
this->material_.append("illum 1\n");
this->material_.append(Utils::String::VA("map_Ka textures/%s.png\n", image->name));
this->material_.append(Utils::String::VA("map_Kd textures/%s.png\n", image->name));
}
void writeFaces()
{
Logger::Print("Writing faces...\n");
Utils::IO::CreateDir(Utils::String::VA("raw/mapdump/%s/textures", this->world_->baseName));
this->material_.append(Utils::String::VA("# Material count: %d\n", this->faces_.size()));
this->object_.append("# Faces\n");
for (const auto& material : this->facesOrder_)
{
this->writeMaterial(material);
const auto& faces = this->getFaceList(material);
for (const auto& index : faces.indices)
{
const int a = index.a;
const int b = index.b;
const int c = index.c;
this->object_.append(Utils::String::VA("f %d/%d/%d %d/%d/%d %d/%d/%d\n", a, a, a, b, b, b, c, c, c));
}
this->object_.append("\n");
}
}
};
MapDump::MapDump()
{
Command::Add("dumpmap", [](Command::Params*)
{
if (Dedicated::IsEnabled() || ZoneBuilder::IsEnabled())
{
Logger::Print("DirectX needs to be enabled, please start a client to use this command!\n");
return;
}
Game::GfxWorld* world = nullptr;
Game::DB_EnumXAssets(Game::XAssetType::ASSET_TYPE_GFXWORLD, [](Game::XAssetHeader header, void* world)
{
*reinterpret_cast<Game::GfxWorld**>(world) = header.gfxWorld;
}, &world, false);
if (world)
{
MapDumper dumper(world);
dumper.dump();
Logger::Print("Map '%s' exported!\n", world->baseName);
}
else
{
Logger::Print("No map loaded, unable to dump anything!\n");
}
});
}
}

View File

@ -0,0 +1,10 @@
#pragma once
namespace Components
{
class MapDump : public Component
{
public:
MapDump();
};
}

View File

@ -524,140 +524,6 @@ namespace Components
}
}
#if defined(DEBUG) && defined(ENABLE_DXSDK)
// Credit to SE2Dev, as we shouldn't share the code, keep that in debug mode!
void Maps::ExportMap(Game::GfxWorld* world)
{
Utils::Memory::Allocator allocator;
if (!world) return;
Logger::Print("Exporting '%s'...\n", world->baseName);
std::string mtl;
mtl.append("# IW4x MTL File\n");
mtl.append("# Credit to SE2Dev for his D3DBSP Tool\n");
std::string map;
map.append("# Generated by IW4x\n");
map.append("# Credit to SE2Dev for his D3DBSP Tool\n");
map.append(Utils::String::VA("o %s\n", world->baseName));
map.append(Utils::String::VA("mtllib %s.mtl\n\n", world->baseName));
Logger::Print("Writing vertices...\n");
for (unsigned int i = 0; i < world->draw.vertexCount; ++i)
{
float x = world->draw.vd.vertices[i].xyz[1];
float y = world->draw.vd.vertices[i].xyz[2];
float z = world->draw.vd.vertices[i].xyz[0];
map.append(Utils::String::VA("v %.6f %.6f %.6f\n", x, y, z));
}
map.append("\n");
Logger::Print("Writing texture coordinates...\n");
for (unsigned int i = 0; i < world->draw.vertexCount; ++i)
{
map.append(Utils::String::VA("vt %.6f %.6f\n", world->draw.vd.vertices[i].texCoord[0], -world->draw.vd.vertices[i].texCoord[1]));
}
Logger::Print("Writing normals...\n");
for (unsigned int i = 0; i < world->draw.vertexCount; ++i)
{
Game::vec3_t normal;
Game::Vec3UnpackUnitVec(world->draw.vd.vertices[i].normal, &normal);
map.append(Utils::String::VA("vn %.6f %.6f %.6f\n", normal[0], normal[1], normal[2]));
}
map.append("\n");
Logger::Print("Searching materials...\n");
int materialCount = 0;
Game::Material** materials = allocator.allocateArray<Game::Material*>(world->dpvs.staticSurfaceCount);
for (unsigned int i = 0; i < world->dpvs.staticSurfaceCount; ++i)
{
bool isNewMat = true;
for (int j = 0; j < materialCount; ++j)
{
if (world->dpvs.surfaces[i].material == materials[j])
{
isNewMat = false;
break;
}
}
if (isNewMat)
{
materials[materialCount++] = world->dpvs.surfaces[i].material;
}
}
Utils::IO::CreateDir(Utils::String::VA("raw/mapdump/%s/textures", world->baseName));
mtl.append(Utils::String::VA("# Material Count: %d\n", materialCount));
Logger::Print("Exporting materials and faces...\n");
for (int m = 0; m < materialCount; ++m)
{
std::string name = materials[m]->info.name;
auto pos = name.find_last_of("/");
if (pos != std::string::npos)
{
name = name.substr(pos + 1);
}
map.append(Utils::String::VA("\nusemtl %s\n", name.data()));
map.append("s off\n");
Game::GfxImage* image = materials[m]->textureTable[0].u.image;
for (char l = 0; l < materials[m]->textureCount; ++l)
{
if (materials[m]->textureTable[l].nameStart == 'c')
{
if (materials[m]->textureTable[l].nameEnd == 'p')
{
image = materials[m]->textureTable[l].u.image; // Hopefully our colorMap
}
}
}
std::string _name = Utils::String::VA("raw/mapdump/%s/textures/%s.png", world->baseName, image->name);
D3DXSaveTextureToFile(std::wstring(_name.begin(), _name.end()).data(), D3DXIFF_PNG, image->texture.map, NULL);
mtl.append(Utils::String::VA("\nnewmtl %s\n", name.data()));
mtl.append("Ka 1.0000 1.0000 1.0000\n");
mtl.append("Kd 1.0000 1.0000 1.0000\n");
mtl.append("illum 1\n");
mtl.append(Utils::String::VA("map_Ka textures/%s.png\n", image->name));
mtl.append(Utils::String::VA("map_Kd textures/%s.png\n", image->name));
for (unsigned int i = 0; i < world->dpvs.staticSurfaceCount; ++i)
{
if (world->dpvs.surfaces[i].material != materials[m])
continue;
int vertOffset = world->dpvs.surfaces[i].tris.firstVertex + 1;//+1 cus obj starts at 1
int indexOffset = world->dpvs.surfaces[i].tris.baseIndex;
for (unsigned short j = 0; j < world->dpvs.surfaces[i].tris.triCount; ++j)
{
int a = world->draw.indices[indexOffset + j * 3 + 0] + vertOffset;
int b = world->draw.indices[indexOffset + j * 3 + 1] + vertOffset;
int c = world->draw.indices[indexOffset + j * 3 + 2] + vertOffset;
map.append(Utils::String::VA("f %d/%d/%d %d/%d/%d %d/%d/%d\n", a, a, a, b, b, b, c, c, c));
}
}
}
Logger::Print("Writing final files...\n");
Utils::IO::WriteFile(Utils::String::VA("raw/mapdump/%s/%s.mtl", world->baseName, world->baseName), mtl);
Utils::IO::WriteFile(Utils::String::VA("raw/mapdump/%s/%s.obj", world->baseName, world->baseName), map);
}
#endif
void Maps::AddDlc(Maps::DLC dlc)
{
for (auto& pack : Maps::DlcPacks)
@ -816,12 +682,16 @@ namespace Components
Game::dvar_t* Maps::GetSpecularDvar()
{
Game::dvar_t*& r_specular = *reinterpret_cast<Game::dvar_t**>(0x69F0D94);
static Game::dvar_t* r_specularCustomMaps = Game::Dvar_RegisterBool("r_specularCustomMaps", false, Game::DVAR_FLAG_SAVED, "Allows shaders to use phong specular lighting on custom maps");
if (Maps::IsCustomMap())
{
static Game::dvar_t noSpecular;
ZeroMemory(&noSpecular, sizeof noSpecular);
return &noSpecular;
if (!r_specularCustomMaps->current.enabled)
{
static Game::dvar_t noSpecular;
ZeroMemory(&noSpecular, sizeof noSpecular);
return &noSpecular;
}
}
return r_specular;
@ -1001,33 +871,6 @@ namespace Components
//Maps::AddDependency("co_hunted", "mp_storm");
//Maps::AddDependency("mp_shipment", "mp_shipment_long");
#if defined(DEBUG) && defined(ENABLE_DXSDK)
Command::Add("dumpmap", [](Command::Params*)
{
if (Dedicated::IsEnabled() || ZoneBuilder::IsEnabled())
{
Logger::Print("DirectX needs to be enabled, please start a client to use this command!\n");
return;
}
Game::GfxWorld* world = nullptr;
Game::DB_EnumXAssets(Game::XAssetType::ASSET_TYPE_GFXWORLD, [](Game::XAssetHeader header, void* world)
{
*reinterpret_cast<Game::GfxWorld**>(world) = header.gfxWorld;
}, &world, false);
if (world)
{
Maps::ExportMap(world);
Logger::Print("Map '%s' exported!\n", world->baseName);
}
else
{
Logger::Print("No map loaded, unable to dump anything!\n");
}
});
#endif
// Allow hiding specific smodels
Utils::Hook(0x50E67C, Maps::HideModelStub, HOOK_CALL).install()->quick();

View File

@ -100,10 +100,6 @@ namespace Components
static void AddDlc(DLC dlc);
static void UpdateDlcStatus();
#if defined(DEBUG) && defined(ENABLE_DXSDK)
static void ExportMap(Game::GfxWorld* world);
#endif
static void PrepareUsermap(const char* mapname);
static void SpawnServerStub();
static void LoadMapLoadscreenStub();

View File

@ -209,7 +209,8 @@ namespace Components
UIScript::Add("visitWiki", [](UIScript::Token)
{
Utils::OpenUrl(Utils::Cache::GetStaticUrl("/wiki/"));
//Utils::OpenUrl(Utils::Cache::GetStaticUrl("/wiki/"));
Utils::OpenUrl("https://github.com/Jawesome99/IW4x/wiki");
});
UIScript::Add("visitDiscord", [](UIScript::Token)

View File

@ -119,6 +119,26 @@ namespace Components
}
}
__declspec(naked) int QuickPatch::SVGameClientNum()
{
_asm
{
mov eax, [esp + 4] // index
mov ecx, 1A831A8h
mov ecx, [ecx]
imul eax, 366Ch
lea edx, [eax + ecx + 3134h]
cmp edx, 0
jnz valid_player_state
mov eax, 0
ret
valid_player_state:
mov eax, [eax + ecx + 3134h]
ret
}
}
bool QuickPatch::InvalidNameCheck(char *dest, char *source, int size)
{
strncpy(dest, source, size - 1);
@ -248,6 +268,69 @@ namespace Components
}
}
Game::dvar_t* QuickPatch::g_playerCollision;
__declspec(naked) void QuickPatch::PlayerCollisionStub()
{
__asm
{
// check the value of g_playerCollision
push eax;
mov eax, g_playerCollision;
cmp byte ptr[eax + 16], 0;
pop eax;
// dont collide if g_playerCollision is set to 0
je dontcollide;
// original code
mov eax, dword ptr[esp + 0xa0];
jmp collide;
collide:
push 0x00478376;
retn;
dontcollide:
mov eax, dword ptr[esp + 0xa0];
mov ecx, dword ptr[esp + 9ch];
push eax;
push ecx;
lea edx, [esp + 48h];
push edx;
mov eax, esi;
push 0x0047838b;
retn;
}
}
Game::dvar_t* QuickPatch::g_playerEjection;
__declspec(naked) void QuickPatch::PlayerEjectionStub()
{
__asm
{
// check the value of g_playerEjection
push eax;
mov eax, g_playerEjection;
cmp byte ptr[eax + 16], 0;
pop eax;
// dont eject if g_playerEjection is set to 0
je donteject;
// original code
cmp dword ptr[ebx + 19ch], edi;
jle eject;
eject:
push 0x005d8152;
retn;
donteject:
push 0x005d815b;
retn;
}
}
template <typename T> std::function < T > ImportFunction(const std::string& dll, const std::string& function)
{
auto dllHandle = GetModuleHandleA(&dll[0]);
@ -306,6 +389,14 @@ namespace Components
// Intermission time dvar
Game::Dvar_RegisterFloat("scr_intermissionTime", 10, 0, 120, Game::DVAR_FLAG_REPLICATED | Game::DVAR_FLAG_DEDISAVED, "Time in seconds before match server loads the next map");
// Player Collision dvar
g_playerCollision = Game::Dvar_RegisterBool("g_playerCollision", true, Game::DVAR_FLAG_REPLICATED, "Flag whether player collision is on or off");
Utils::Hook(0x47836F, QuickPatch::PlayerCollisionStub, HOOK_JUMP).install()->quick();
// Player Ejection dvar
g_playerEjection = Game::Dvar_RegisterBool("g_playerEjection", true, Game::DVAR_FLAG_REPLICATED, "Flag whether player ejection is on or off");
Utils::Hook(0x5D814A, QuickPatch::PlayerEjectionStub, HOOK_JUMP).install()->quick();
// Disallow invalid player names
Utils::Hook(0x401983, QuickPatch::InvalidNameStub, HOOK_JUMP).install()->quick();
@ -589,6 +680,12 @@ namespace Components
// Patch selectStringTableEntryInDvar
Utils::Hook::Set(0x405959, QuickPatch::SelectStringTableEntryInDvarStub);
// Patch SV_GameClientNum for edge case generating status
Utils::Hook(0x624DE2, QuickPatch::SVGameClientNum, HOOK_CALL).install()->quick();
// Ignore call to print 'Offhand class mismatch when giving weapon...'
Utils::Hook(0x5D9047, 0x4BB9B0, HOOK_CALL).install()->quick();
Command::Add("unlockstats", [](Command::Params*)
{
QuickPatch::UnlockStats();

View File

@ -19,6 +19,7 @@ namespace Components
static void SelectStringTableEntryInDvarStub();
static int SVCanReplaceServerCommand(Game::client_t *client, const char *cmd);
static int SVGameClientNum();
static int MsgReadBitsCompressCheckSV(const char *from, char *to, int size);
static int MsgReadBitsCompressCheckCL(const char *from, char *to, int size);
@ -37,5 +38,10 @@ namespace Components
static Game::dvar_t* Dvar_RegisterAspectRatioDvar(const char* name, char** enumValues, int defaultVal, int flags, const char* description);
static void SetAspectRatioStub();
static void SetAspectRatio();
static Game::dvar_t* g_playerCollision;
static void PlayerCollisionStub();
static Game::dvar_t* g_playerEjection;
static void PlayerEjectionStub();
};
}

View File

@ -247,11 +247,6 @@ namespace Components
Script::ScriptFunctions.push_back({ name, function, isDev });
}
void Script::OnVMShutdown(Utils::Slot<Scheduler::Callback> callback)
{
Script::VMShutdownSignal.connect(callback);
}
Game::scr_function_t Script::GetFunction(void* caller, const char** name, int* isDev)
{
for (auto& function : Script::ScriptFunctions)
@ -293,6 +288,11 @@ namespace Components
}
}
void Script::OnVMShutdown(Utils::Slot<Scheduler::Callback> callback)
{
Script::VMShutdownSignal.connect(callback);
}
void Script::ScrShutdownSystemStub(int num)
{
Script::VMShutdownSignal();
@ -316,6 +316,21 @@ namespace Components
return Game::Scr_GetNumParam();
}
Game::gentity_t* Script::getEntFromEntRef(Game::scr_entref_t entref)
{
Game::gentity_t* gentity = &Game::g_entities[entref];
return gentity;
}
Game::client_t* Script::getClientFromEnt(Game::gentity_t* gentity)
{
if (!gentity->client)
{
Logger::Error(5, "Entity: %i is not a client", gentity);
}
return &Game::svs_clients[gentity->number];
}
Script::Script()
{
Utils::Hook(0x612DB0, Script::StoreFunctionNameStub, HOOK_JUMP).install()->quick();
@ -330,7 +345,7 @@ namespace Components
Utils::Hook(0x45D44A, Script::LoadGameTypeScript, HOOK_CALL).install()->quick();
Utils::Hook(0x44E736, Script::GetFunctionStub, HOOK_JUMP).install()->quick(); // Scr_GetFunction
//Utils::Hook(0x4EC8E5, Script::GetFunctionStub, HOOK_JUMP).install()->quick(); // Scr_GetMethod
Utils::Hook(0x4EC8E5, Script::GetFunctionStub, HOOK_JUMP).install()->quick(); // Scr_GetMethod
Utils::Hook(0x5F41A3, Script::SetExpFogStub, HOOK_CALL).install()->quick();

View File

@ -29,6 +29,9 @@ namespace Components
static void OnVMShutdown(Utils::Slot<Scheduler::Callback> callback);
static Game::gentity_t* getEntFromEntRef(Game::scr_entref_t entref);
static Game::client_t* getClientFromEnt(Game::gentity_t* gentity);
private:
static std::string ScriptName;
static std::vector<int> ScriptHandles;

View File

@ -66,6 +66,7 @@ namespace Components
int Stats::SaveStats(char* dest, const char* folder, const char* buffer, size_t length)
{
const auto fs_game = Game::Dvar_FindVar("fs_game");
if (fs_game && fs_game->current.string && strlen(fs_game->current.string) && !strncmp(fs_game->current.string, "mods/", 5))
{
folder = fs_game->current.string;

View File

@ -2908,23 +2908,14 @@ namespace Components
// check if file should be skipped
auto skipFile = false;
if (!strncmp(&file[strlen(file) - 4], ".iwi", 4))
{
if (readSize > 3 && !memcmp(&fileBuffer[0], "IWi", 3))
{
skipFile = true;
}
}
else if (strstr(file, "weapons"))
if (strlen(file) > 5 && ((strncmp(&file[strlen(file) - 4], ".iwi", 4) != 0)))
{
skipFile = true;
}
else
else if (readSize >= 3 && (!memcmp(&fileBuffer[0], "IWi", 3)))
{
if (readSize > 8 && *reinterpret_cast<std::uint32_t*>(&fileBuffer[4]) == 0xe9c9c447)
{
skipFile = true;
}
skipFile = true;
}
// if the header seems encrypted...
@ -3409,9 +3400,11 @@ namespace Components
Utils::Hook(0x45A806, RelocateFileCount, HOOK_CALL).install()->quick();
Utils::Hook(0x45A6A0, RelocateFileCount, HOOK_CALL).install()->quick();
#ifndef DEBUG
// Ignore missing soundaliases for now
// TODO: Include them in the dependency zone!
Utils::Hook::Nop(0x644207, 5);
#endif
// Block Mark_pathnode_constant_t
Utils::Hook::Set<BYTE>(0x4F74B0, 0xC3);
@ -3511,4 +3504,4 @@ namespace Components
}
}
#pragma optimize( "", on )
#pragma optimize( "", on )

View File

@ -683,6 +683,43 @@ namespace Game
return atoi(StringTable_Lookup(rankTable, 0, maxrank, 7));
}
void Vec3Normalize(vec3_t& vec)
{
const auto length = std::sqrt(std::pow(vec[0], 2) + std::pow(vec[1], 2) + std::pow(vec[2], 2));
vec[0] /= length;
vec[1] /= length;
vec[2] /= length;
}
void Vec2UnpackTexCoords(const PackedTexCoords in, vec2_t* out)
{
unsigned int v3; // xmm1_4
if (LOWORD(in.packed))
v3 = ((in.packed & 0x8000) << 16) | (((((in.packed & 0x3FFF) << 14) - (~(LOWORD(in.packed) << 14) & 0x10000000)) ^ 0x80000001) >> 1);
else
v3 = 0;
(*out)[0] = *reinterpret_cast<float*>(&v3);
if (HIWORD(in.packed))
v3 = ((HIWORD(in.packed) & 0x8000) << 16) | (((((HIWORD(in.packed) & 0x3FFF) << 14)
- (~(HIWORD(in.packed) << 14) & 0x10000000)) ^ 0x80000001) >> 1);
else
v3 = 0;
(*out)[1] = *reinterpret_cast<float*>(&v3);
}
void MatrixVecMultiply(const float (& mulMat)[3][3], const vec3_t& mulVec, vec3_t& solution)
{
vec3_t res;
res[0] = mulMat[0][0] * mulVec[0] + mulMat[1][0] * mulVec[1] + mulMat[2][0] * mulVec[2];
res[1] = mulMat[0][1] * mulVec[0] + mulMat[1][1] * mulVec[1] + mulMat[2][1] * mulVec[2];
res[2] = mulMat[0][2] * mulVec[0] + mulMat[1][2] * mulVec[1] + mulMat[2][2] * mulVec[2];
std::memmove(&solution[0], &res[0], sizeof(res));
}
void SortWorldSurfaces(GfxWorld* world)
{
DWORD* specular1 = reinterpret_cast<DWORD*>(0x69F105C);

View File

@ -885,6 +885,10 @@ namespace Game
void Image_Setup(GfxImage* image, unsigned int width, unsigned int height, unsigned int depth, unsigned int flags, _D3DFORMAT format);
void Vec3Normalize(vec3_t& vec);
void Vec2UnpackTexCoords(const PackedTexCoords in, vec2_t* out);
void MatrixVecMultiply(const float(&mulMat)[3][3], const vec3_t& mulVec, vec3_t& solution);
void SortWorldSurfaces(GfxWorld* world);
void R_AddDebugLine(float* color, float* v1, float* v2);
void R_AddDebugString(float *color, float *pos, float scale, const char *str);

View File

@ -91,4 +91,25 @@ extern "C"
// Enable 'High Performance Graphics'
__declspec(dllexport) DWORD NvOptimusEnablement = 0x00000001;
// Tommath fixes
int s_read_arc4random(void*, size_t)
{
return -1;
}
int s_read_getrandom(void*, size_t)
{
return -1;
}
int s_read_urandom(void*, size_t)
{
return -1;
}
int s_read_ltm_rng(void*, size_t)
{
return -1;
}
};

View File

@ -45,10 +45,8 @@
#pragma warning(pop)
#ifdef ENABLE_DXSDK
#include <d3dx9tex.h>
#pragma comment(lib, "D3dx9.lib")
#endif
// Usefull for debugging
template <size_t S> class Sizer { };

View File

@ -175,11 +175,11 @@ namespace Steam
size_t expectedParams = Proxy::ClientUser.paramSize("SpawnProcess");
if (expectedParams == 40) // Release
{
Proxy::ClientUser.invoke<bool>("SpawnProcess", ourPath, cmdline.data(), 0, ourDirectory, gameID.bits, mod.data(), Proxy::AppId, 0, 0);
Proxy::ClientUser.invoke<bool>("SpawnProcess", ourPath, cmdline.data(), ourDirectory, gameID.bits, mod.data(), Proxy::AppId, 0, 0);
}
else if (expectedParams == 36) // Beta
{
Proxy::ClientUser.invoke<bool>("SpawnProcess", ourPath, cmdline.data(), 0, ourDirectory, gameID.bits, mod.data(), 0, 0);
Proxy::ClientUser.invoke<bool>("SpawnProcess", ourPath, cmdline.data(), ourDirectory, gameID.bits, mod.data(), Proxy::AppId, 0, 0);
}
else if (expectedParams == 48) // Legacy, expects VAC blob
{

View File

@ -4,7 +4,6 @@ namespace Utils
{
CSV::CSV(const std::string& file, bool isFile, bool allowComments)
{
this->valid = false;
this->parse(file, isFile, allowComments);
}
@ -111,7 +110,7 @@ namespace Utils
//++i;
continue;
}
else if (!isString && row[i] == '#' && allowComments) // Skip comments. I know CSVs usually don't have comments, but in this case it's useful
else if (!isString && (row[i] == '#' || (row[i] == '/' && (i + 1) < row.size() && row[i + 1] == '/') ) && allowComments) // Skip comments. I know CSVs usually don't have comments, but in this case it's useful
{
return;
}

View File

@ -18,7 +18,7 @@ namespace Utils
bool isValid() { return this->valid; }
private:
bool valid;
bool valid = false;
std::vector<std::vector<std::string>> dataMap;
void parse(const std::string& file, bool isFile = true, bool allowComments = true);

View File

@ -4,6 +4,8 @@ namespace Utils
{
const char* Cache::Urls[] =
{
"https://iw4x.org",
"https://iw4xcachep26muba.onion.to",
"https://iw4xcachep26muba.tor2web.xyz",
"https://iw4xcachep26muba.onion.ws",

Binary file not shown.