mirror of
https://github.com/gwdevhub/gMod.git
synced 2026-07-22 02:19:31 +00:00
Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 21c20c80e5 | |||
| 1692a5526c | |||
| f9d69fc91e | |||
| 7dbbdc480e |
+4
-1
@@ -17,7 +17,7 @@ endif()
|
||||
|
||||
set(VERSION_MAJOR 1)
|
||||
set(VERSION_MINOR 6)
|
||||
set(VERSION_PATCH 0)
|
||||
set(VERSION_PATCH 3)
|
||||
set(VERSION_TWEAK 0)
|
||||
|
||||
set(VERSION_RC "${CMAKE_CURRENT_BINARY_DIR}/version.rc")
|
||||
@@ -35,6 +35,7 @@ list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake")
|
||||
include(libzippp)
|
||||
include(minhook)
|
||||
include(dxtk)
|
||||
include(msgsl)
|
||||
|
||||
add_library(gMod SHARED)
|
||||
|
||||
@@ -58,9 +59,11 @@ target_link_libraries(gMod PRIVATE
|
||||
psapi
|
||||
minhook
|
||||
directxtex
|
||||
Microsoft.GSL::GSL
|
||||
)
|
||||
target_link_options(gMod PRIVATE
|
||||
"$<$<CONFIG:DEBUG>:/NODEFAULTLIB:LIBCMT>"
|
||||
"/LARGEADDRESSAWARE"
|
||||
)
|
||||
source_group(TREE "${CMAKE_CURRENT_SOURCE_DIR}" FILES ${SOURCES})
|
||||
target_sources(gMod PRIVATE ${SOURCES})
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
Requirements:
|
||||
- DirectX SDK (June 2010)
|
||||
- Visual Studio 2022
|
||||
- CMake 3.16+, integrated into the Developer Powershell for VS 2022
|
||||
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
cmake_minimum_required(VERSION 3.14)
|
||||
|
||||
include(FetchContent)
|
||||
|
||||
FetchContent_Declare(GSL
|
||||
GIT_REPOSITORY "https://github.com/microsoft/GSL"
|
||||
GIT_TAG "v4.0.0"
|
||||
GIT_SHALLOW ON
|
||||
)
|
||||
|
||||
FetchContent_MakeAvailable(GSL)
|
||||
@@ -26,9 +26,23 @@ inline void Message(const char* format, ...)
|
||||
#endif
|
||||
}
|
||||
|
||||
inline void Info(const char* format, ...)
|
||||
{
|
||||
#ifdef _DEBUG
|
||||
const HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
|
||||
[[maybe_unused]] const auto success = SetConsoleTextAttribute(hConsole, 0); // white
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
vprintf(format, args);
|
||||
va_end(args);
|
||||
#endif
|
||||
}
|
||||
|
||||
inline void Warning(const char* format, ...)
|
||||
{
|
||||
#ifdef _DEBUG
|
||||
const HANDLE hConsole = GetStdHandle(STD_ERROR_HANDLE);
|
||||
[[maybe_unused]] const auto success = SetConsoleTextAttribute(hConsole, 6); // yellow
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
vfprintf(stderr, format, args);
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include "Utils.h"
|
||||
|
||||
#include <d3d9.h>
|
||||
#include <gsl/gsl>
|
||||
|
||||
#include "Defines.h"
|
||||
#include "Error.h"
|
||||
|
||||
@@ -12,4 +12,17 @@ namespace utils
|
||||
vec.erase(found);
|
||||
}
|
||||
}
|
||||
|
||||
inline std::wstring utf8_to_wstring(const std::string& utf8str) {
|
||||
if (utf8str.empty()) return {};
|
||||
|
||||
// Calculate the number of wide characters needed for the conversion
|
||||
const int count = MultiByteToWideChar(CP_UTF8, 0, utf8str.c_str(), -1, nullptr, 0);
|
||||
if (count == 0) throw std::runtime_error("Failed to convert UTF-8 to UTF-16");
|
||||
|
||||
std::wstring wstr(count, 0);
|
||||
MultiByteToWideChar(CP_UTF8, 0, utf8str.c_str(), -1, wstr.data(), count);
|
||||
|
||||
return wstr;
|
||||
}
|
||||
}
|
||||
|
||||
+11
-11
@@ -15,7 +15,7 @@ import <filesystem>;
|
||||
import ModfileLoader.TpfReader;
|
||||
|
||||
export class ModfileLoader {
|
||||
std::string file_name;
|
||||
std::filesystem::path file_name;
|
||||
const std::string TPF_PASSWORD{
|
||||
0x73, 0x2A, 0x63, 0x7D, 0x5F, 0x0A, static_cast<char>(0xA6), static_cast<char>(0xBD),
|
||||
0x7D, 0x65, 0x7E, 0x67, 0x61, 0x2A, 0x7F, 0x7F,
|
||||
@@ -26,7 +26,7 @@ export class ModfileLoader {
|
||||
};
|
||||
|
||||
public:
|
||||
ModfileLoader(const std::string& fileName);
|
||||
ModfileLoader(const std::filesystem::path& fileName);
|
||||
|
||||
std::vector<TexEntry> GetContents();
|
||||
|
||||
@@ -39,15 +39,15 @@ private:
|
||||
void LoadEntries(libzippp::ZipArchive& archive, std::vector<TexEntry>& entries);
|
||||
};
|
||||
|
||||
ModfileLoader::ModfileLoader(const std::string& fileName)
|
||||
ModfileLoader::ModfileLoader(const std::filesystem::path& fileName)
|
||||
{
|
||||
file_name = std::filesystem::absolute(fileName).string();
|
||||
file_name = std::filesystem::absolute(fileName);
|
||||
}
|
||||
|
||||
std::vector<TexEntry> ModfileLoader::GetContents()
|
||||
{
|
||||
try {
|
||||
return file_name.ends_with(".tpf") ? GetTpfContents() : GetFileContents();
|
||||
return file_name.wstring().ends_with(L".tpf") ? GetTpfContents() : GetFileContents();
|
||||
}
|
||||
catch (const std::exception&) {
|
||||
Warning("Failed to open mod file: %s\n", file_name.c_str());
|
||||
@@ -81,7 +81,7 @@ std::vector<TexEntry> ModfileLoader::GetFileContents()
|
||||
{
|
||||
std::vector<TexEntry> entries;
|
||||
|
||||
libzippp::ZipArchive zip_archive(file_name);
|
||||
libzippp::ZipArchive zip_archive(file_name.string());
|
||||
zip_archive.open();
|
||||
LoadEntries(zip_archive, entries);
|
||||
zip_archive.close();
|
||||
@@ -96,7 +96,7 @@ void ParseSimpleArchive(const libzippp::ZipArchive& archive, std::vector<TexEntr
|
||||
//TODO: #6 - Implement regex search
|
||||
auto name = entry.getName();
|
||||
|
||||
const static std::regex re(R"(0x[0-9a-f]{8})", std::regex::optimize | std::regex::icase);
|
||||
const static std::regex re(R"(0x[0-9a-f]{4,8})", std::regex::optimize | std::regex::icase);
|
||||
std::smatch match;
|
||||
if (!std::regex_search(name, match, re)) {
|
||||
continue;
|
||||
@@ -106,11 +106,11 @@ void ParseSimpleArchive(const libzippp::ZipArchive& archive, std::vector<TexEntr
|
||||
try {
|
||||
crc_hash = std::stoul(match.str(), nullptr, 16);
|
||||
}
|
||||
catch (const std::invalid_argument& e) {
|
||||
catch (const std::invalid_argument&) {
|
||||
Warning("Failed to parse %s as a hash", name.c_str());
|
||||
continue;
|
||||
}
|
||||
catch (const std::out_of_range& e) {
|
||||
catch (const std::out_of_range&) {
|
||||
Message("Out of range while parsing %s as a hash", name.c_str());
|
||||
continue;
|
||||
}
|
||||
@@ -147,11 +147,11 @@ void ParseTexmodArchive(std::vector<std::string>& lines, libzippp::ZipArchive& a
|
||||
try {
|
||||
crc_hash = std::stoul(address_string, nullptr, 16);
|
||||
}
|
||||
catch (const std::invalid_argument& e) {
|
||||
catch (const std::invalid_argument&) {
|
||||
Warning("Failed to parse %s as a hash", address_string.c_str());
|
||||
continue;
|
||||
}
|
||||
catch (const std::out_of_range& e) {
|
||||
catch (const std::out_of_range&) {
|
||||
Warning("Out of range while parsing %s as a hash", address_string.c_str());
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
export module ModfileLoader.TpfReader;
|
||||
|
||||
import <filesystem>;
|
||||
import <string>;
|
||||
import <fstream>;
|
||||
import <vector>;
|
||||
|
||||
export class TpfReader {
|
||||
public:
|
||||
TpfReader(const std::string& path)
|
||||
TpfReader(const std::filesystem::path& path)
|
||||
{
|
||||
file_stream = std::ifstream(path, std::ios::binary);
|
||||
if (!file_stream.seekg(0, std::ios::end).good() || !file_stream.seekg(0, std::ios::beg).good()) {
|
||||
|
||||
+46
-36
@@ -27,9 +27,6 @@ public:
|
||||
int MergeUpdate(); //called from uMod_IDirect3DDevice9::BeginScene()
|
||||
void Initialize();
|
||||
|
||||
// Add TextureFileStruct data, return size of data added. 0 on failure.
|
||||
unsigned long AddFile(TexEntry& entry, bool compress = false);
|
||||
|
||||
std::vector<uMod_IDirect3DTexture9*> OriginalTextures;
|
||||
// stores the pointer to the uMod_IDirect3DTexture9 objects created by the game
|
||||
std::vector<uMod_IDirect3DVolumeTexture9*> OriginalVolumeTextures;
|
||||
@@ -53,9 +50,8 @@ private:
|
||||
int LockMutex();
|
||||
int UnlockMutex();
|
||||
HANDLE hMutex;
|
||||
std::mutex mutex;
|
||||
|
||||
std::unordered_map<HashType, TextureFileStruct*> modded_textures;
|
||||
std::unordered_map<HashType, gsl::owner<TextureFileStruct*>> modded_textures;
|
||||
// array which stores the file in memory and the hash of each texture to be modded
|
||||
|
||||
// called if a target texture is found
|
||||
@@ -83,8 +79,8 @@ TextureClient::~TextureClient()
|
||||
if (hMutex != nullptr) {
|
||||
CloseHandle(hMutex);
|
||||
}
|
||||
for (const auto& it : modded_textures) {
|
||||
delete it.second;
|
||||
for (const auto texture_file_struct : modded_textures | std::views::values) {
|
||||
delete texture_file_struct;
|
||||
}
|
||||
modded_textures.clear();
|
||||
}
|
||||
@@ -159,65 +155,66 @@ int TextureClient::UnlockMutex()
|
||||
return RETURN_OK;
|
||||
}
|
||||
|
||||
unsigned long TextureClient::AddFile(TexEntry& entry, const bool compress)
|
||||
gsl::owner<TextureFileStruct*> AddFile(TexEntry& entry, const bool compress, const std::filesystem::path& dll_path)
|
||||
{
|
||||
if (modded_textures.contains(entry.crc_hash)) {
|
||||
return 0;
|
||||
}
|
||||
auto texture_file_struct = new TextureFileStruct();
|
||||
const auto texture_file_struct = new TextureFileStruct();
|
||||
texture_file_struct->crc_hash = entry.crc_hash;
|
||||
should_update = true;
|
||||
const auto dds_blob = TextureFunction::ConvertToCompressedDDS(entry, compress, dll_path);
|
||||
texture_file_struct->data.assign(static_cast<BYTE*>(dds_blob.GetBufferPointer()), static_cast<BYTE*>(dds_blob.GetBufferPointer()) + dds_blob.GetBufferSize());
|
||||
std::lock_guard lock(mutex);
|
||||
modded_textures.emplace(entry.crc_hash, texture_file_struct);
|
||||
return texture_file_struct->data.size();
|
||||
return texture_file_struct;
|
||||
}
|
||||
|
||||
unsigned ProcessModfile(TextureClient& client, const std::string& modfile, const bool compress)
|
||||
std::vector<gsl::owner<TextureFileStruct*>> ProcessModfile(const std::filesystem::path& modfile, const std::filesystem::path& dll_path, const bool compress)
|
||||
{
|
||||
const auto hr = CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
|
||||
if (FAILED(hr)) return 0;
|
||||
if (FAILED(hr)) return {};
|
||||
Message("Initialize: loading file %s... ", modfile.c_str());
|
||||
auto file_loader = ModfileLoader(modfile);
|
||||
auto entries = file_loader.GetContents();
|
||||
if (entries.empty()) {
|
||||
Message("No entries found.\n");
|
||||
return 0;
|
||||
return {};
|
||||
}
|
||||
Message("%zu textures... ", entries.size());
|
||||
unsigned long file_bytes_loaded = 0;
|
||||
std::vector<gsl::owner<TextureFileStruct*>> texture_file_structs;
|
||||
texture_file_structs.reserve(entries.size());
|
||||
unsigned file_bytes_loaded = 0;
|
||||
for (auto& tpf_entry : entries) {
|
||||
file_bytes_loaded += client.AddFile(tpf_entry, compress);
|
||||
const auto tex_file_struct = AddFile(tpf_entry, compress, dll_path);
|
||||
texture_file_structs.push_back(tex_file_struct);
|
||||
file_bytes_loaded += texture_file_structs.back()->data.size();
|
||||
}
|
||||
entries.clear();
|
||||
Message("%d bytes loaded.\n", file_bytes_loaded);
|
||||
CoUninitialize();
|
||||
return file_bytes_loaded;
|
||||
return texture_file_structs;
|
||||
}
|
||||
|
||||
void TextureClient::LoadModsFromFile(const char* source)
|
||||
{
|
||||
static std::vector<std::string> loaded_modfiles{};
|
||||
static unsigned long loaded_size = 0;
|
||||
static std::vector<std::filesystem::path> loaded_modfiles{};
|
||||
Message("Initialize: searching in %s\n", source);
|
||||
|
||||
std::ifstream file(source);
|
||||
std::locale::global(std::locale(""));
|
||||
std::ifstream file(source, std::ios::binary);
|
||||
if (!file.is_open()) {
|
||||
Warning("LoadModsFromFile: failed to open modlist.txt for reading; aborting!!!");
|
||||
return;
|
||||
}
|
||||
Message("Initialize: found modlist.txt. Reading\n");
|
||||
std::string line;
|
||||
std::vector<std::string> modfiles;
|
||||
std::vector<std::filesystem::path> modfiles;
|
||||
while (std::getline(file, line)) {
|
||||
// Remove newline character
|
||||
line.erase(std::ranges::remove(line, '\r').begin(), line.end());
|
||||
line.erase(std::ranges::remove(line, '\n').begin(), line.end());
|
||||
|
||||
if (!std::ranges::contains(loaded_modfiles, line)) {
|
||||
modfiles.push_back(line);
|
||||
loaded_modfiles.push_back(line);
|
||||
const auto wline = utils::utf8_to_wstring(line);
|
||||
const auto fsline = std::filesystem::path(wline);
|
||||
|
||||
if (!std::ranges::contains(loaded_modfiles, fsline)) {
|
||||
modfiles.push_back(fsline);
|
||||
loaded_modfiles.push_back(fsline);
|
||||
}
|
||||
}
|
||||
auto files_size = 0u;
|
||||
@@ -226,20 +223,32 @@ void TextureClient::LoadModsFromFile(const char* source)
|
||||
files_size += std::filesystem::file_size(modfile);
|
||||
}
|
||||
}
|
||||
std::vector<std::future<unsigned>> futures;
|
||||
std::vector<std::future<std::vector<gsl::owner<TextureFileStruct*>>>> futures;
|
||||
for (const auto modfile : modfiles) {
|
||||
futures.emplace_back(std::async(std::launch::async, ProcessModfile, std::ref(*this), modfile, files_size > 400'000'000));
|
||||
futures.emplace_back(std::async(std::launch::async, ProcessModfile, modfile, dll_path, files_size > 400'000'000));
|
||||
}
|
||||
// Join all threads
|
||||
auto loaded_size = 0u;
|
||||
for (auto& future : futures) {
|
||||
loaded_size += future.get();
|
||||
const auto texture_file_structs = future.get();
|
||||
for (const auto texture_file_struct : texture_file_structs) {
|
||||
if (!texture_file_struct->crc_hash) continue;
|
||||
if (!modded_textures.contains(texture_file_struct->crc_hash)) {
|
||||
modded_textures.emplace(texture_file_struct->crc_hash, texture_file_struct);
|
||||
loaded_size += texture_file_struct->data.size();
|
||||
}
|
||||
else {
|
||||
delete texture_file_struct;
|
||||
}
|
||||
}
|
||||
should_update = true;
|
||||
}
|
||||
Message("Finished loading mods from %s: Loaded %u bytes (%u mb)", source, loaded_size, loaded_size / 1024 / 1024);
|
||||
}
|
||||
|
||||
void TextureClient::Initialize()
|
||||
{
|
||||
Message("Initialize: begin\n");
|
||||
const auto t1 = std::chrono::high_resolution_clock::now();
|
||||
Info("Initialize: begin\n");
|
||||
Message("Initialize: searching for modlist.txt\n");
|
||||
char gwpath[MAX_PATH]{};
|
||||
GetModuleFileName(GetModuleHandle(nullptr), gwpath, MAX_PATH); //ask for name and path of this executable
|
||||
@@ -254,8 +263,9 @@ void TextureClient::Initialize()
|
||||
LoadModsFromFile(modlist.string().c_str());
|
||||
}
|
||||
}
|
||||
|
||||
Message("Initialize: end\n");
|
||||
const auto t2 = std::chrono::high_resolution_clock::now();
|
||||
const auto ms = duration_cast<std::chrono::milliseconds>(t2 - t1);
|
||||
Info("Initialize: end, took %d ms\n", ms);
|
||||
}
|
||||
|
||||
int TextureClient::AddTexture(uModTexturePtr auto pTexture)
|
||||
|
||||
Reference in New Issue
Block a user