remove d3dx dependency, severely improve memory usage if necessary (#19)

* basic tga/hdr image loading

* bgr tga tex

* convert to dds during loading
doesn't work yet, fails to create textures from dds memory

* remove ext member from TextureFileStruct

* do not convert images, leads to weird loading problems...

* use regex matching for tpf loading

* convert non-RGBA images to RGBA

* remove d3dx (mostly)

* fix moving wrong image

* move TextureFunction to module

* move FileLoader to ModfileLoader module

* move TextureClient to module

* spaces

* ifdef debug

* 1.6.0

* compress images to dxt5 (bc3_unorm) to use less memory

* remove unnecessary directxtex source code patch

* don't move unnecessarily

* only compress if mods in filesystem use up more than 400mb

* use std::future to prepare to multithread image loading
fails when using std::launch::async though, maybe something in the directxtex library is not thread safe

* remove d3dx sdk from workflows

* remove extern "C"

* catch exception when saving texture

* make loading async (call CoInitializeEx)

* support DXGI_FORMAT_BC1_UNORM (DXT1) compressed textures without warning

* don't warn for BC2 BC4 or BC5 either

* update README.md
This commit is contained in:
DubbleClick
2023-12-04 19:50:56 +01:00
committed by GitHub
parent b49428bafd
commit 9e593e83ee
24 changed files with 881 additions and 895 deletions
-186
View File
@@ -1,186 +0,0 @@
#include <..\header\Main.h>
#include <filesystem>
#include "FileLoader.h"
#include "TpfReader.h"
FileLoader::FileLoader(const std::string& fileName)
{
file_name = std::filesystem::absolute(fileName).string();
}
std::vector<TextureFileStruct> FileLoader::GetContents()
{
try {
return file_name.ends_with(".tpf") ? GetTpfContents() : GetFileContents();
}
catch (const std::exception&) {
Warning("Failed to open mod file: %s\n", file_name.c_str());
}
return {};
}
std::vector<TextureFileStruct> FileLoader::GetTpfContents()
{
std::vector<TextureFileStruct> entries;
auto tpf_reader = TpfReader(file_name);
const auto buffer = tpf_reader.ReadToEnd();
const auto zip_archive = libzippp::ZipArchive::fromBuffer(buffer.data(), buffer.size(), false, TPF_PASSWORD);
if (!zip_archive) {
Warning("Failed to open tpf file: %s - %u bytes!", file_name.c_str(), buffer.size());
return {};
}
zip_archive->setErrorHandlerCallback(
[](const std::string& message, const std::string& strerror, int zip_error_code, int system_error_code) -> void {
Message("GetTpfContents: %s %s %d %d\n", message.c_str(), strerror.c_str(), zip_error_code, system_error_code);
});
zip_archive->open();
LoadEntries(*zip_archive, entries);
zip_archive->close();
libzippp::ZipArchive::free(zip_archive);
return entries;
}
std::vector<TextureFileStruct> FileLoader::GetFileContents()
{
std::vector<TextureFileStruct> entries;
libzippp::ZipArchive zip_archive(file_name);
zip_archive.open();
LoadEntries(zip_archive, entries);
zip_archive.close();
return entries;
}
void ParseSimpleArchive(const libzippp::ZipArchive& archive, std::vector<TextureFileStruct>& entries)
{
for (const auto& entry : archive.getEntries()) {
if (entry.isFile()) {
//TODO: #6 - Implement regex search
auto name = entry.getName();
// Remove the part before the last underscore (if any)
size_t firstIndex = name.find_last_of('_');
while (firstIndex != std::string::npos) {
if (firstIndex >= entry.getName().length() - 1) {
name = entry.getName();
}
else {
name = entry.getName().substr(firstIndex + 1);
}
firstIndex = name.find_last_of('_');
}
// Remove the file extension (if any)
size_t lastIndex = name.find_last_of('.');
if (lastIndex != std::string::npos) {
name = name.substr(0, lastIndex);
}
uint32_t crc_hash;
try {
crc_hash = std::stoul(name, nullptr, 16);
}
catch (const std::invalid_argument& e) {
Warning("Failed to parse %s as a hash", name.c_str());
continue;
}
catch (const std::out_of_range& e) {
Message("Out of range while parsing %s as a hash", name.c_str());
continue;
}
const auto data_ptr = static_cast<BYTE*>(entry.readAsBinary());
const auto size = entry.getSize();
std::vector vec(data_ptr, data_ptr + size);
std::filesystem::path tex_name(entry.getName());
entries.emplace_back(std::move(vec), crc_hash, tex_name.extension() != ".dds");
delete[] data_ptr;
}
}
}
void ParseTexmodArchive(std::vector<std::string>& lines, libzippp::ZipArchive& archive, std::vector<TextureFileStruct>& entries)
{
for (const auto& line : lines) {
std::istringstream iss(line);
std::string part;
std::vector<std::string> splits;
// Split the line by '|'
while (std::getline(iss, part, '|')) {
splits.push_back(part);
}
if (splits.size() != 2) {
continue;
}
std::string addrstr = splits[0];
std::string path = splits[1];
// Remove unwanted characters from the beginning of the path
while (!path.empty() && (path[0] == '.' && (path[1] == '/' || path[1] == '\\')) || path[0] == '/' || path[0] == '\\') {
path.erase(0, 1);
}
// Remove trailing newline and carriage return characters
if (const auto end_pos = path.find_last_not_of("\r\n"); end_pos != std::string::npos) {
path.erase(end_pos + 1);
}
else if (!path.empty()) {
path.clear();
}
const auto entry = archive.getEntry(path);
if (entry.isNull()) {
continue;
}
if (!entry.isFile()) {
continue;
}
uint32_t crc_hash{};
try {
crc_hash = std::stoul(addrstr, nullptr, 16);
}
catch (const std::invalid_argument& e) {
Warning("Failed to parse %s as a hash", addrstr.c_str());
continue;
}
catch (const std::out_of_range& e) {
Warning("Out of range while parsing %s as a hash", addrstr.c_str());
continue;
}
const auto data_ptr = static_cast<BYTE*>(entry.readAsBinary());
const auto size = static_cast<size_t>(entry.getSize());
std::vector vec(data_ptr, data_ptr + size);
const auto tex_name = std::filesystem::path(entry.getName());
entries.emplace_back(std::move(vec), crc_hash, tex_name.extension() != ".dds");
delete[] data_ptr;
}
}
void FileLoader::LoadEntries(libzippp::ZipArchive& archive, std::vector<TextureFileStruct>& entries)
{
const auto def_file = archive.getEntry("texmod.def");
if (def_file.isNull() || !def_file.isFile()) {
ParseSimpleArchive(archive, entries);
}
else {
const auto def = def_file.readAsText();
std::istringstream iss(def);
std::vector<std::string> lines;
std::string line;
while (std::getline(iss, line)) {
lines.push_back(line);
}
ParseTexmodArchive(lines, archive, entries);
}
}
-172
View File
@@ -1,172 +0,0 @@
#include <filesystem>
#include <fstream>
#include "Main.h"
TextureClient::TextureClient(IDirect3DDevice9* device)
{
Message("TextureClient::TextureClient(): %p\n", this);
D3D9Device = device;
void* cpy;
isDirectXExDevice = D3D9Device->QueryInterface(IID_IDirect3DTexture9, &cpy) == 0x01000001L;
mutex = CreateMutex(nullptr, false, nullptr);
}
TextureClient::~TextureClient()
{
Message("TextureClient::~TextureClient(): %p\n", this);
if (mutex != nullptr) {
CloseHandle(mutex);
}
for (const auto& it : modded_textures) {
delete it.second;
}
modded_textures.clear();
}
int TextureClient::MergeUpdate()
{
if (!should_update) return RETURN_OK;
should_update = false;
if (const int ret = LockMutex()) {
gl_ErrorState |= uMod_ERROR_TEXTURE;
return ret;
}
Message("MergeUpdate(): %p\n", this);
for (const auto pTexture : OriginalTextures) {
if (pTexture->CrossRef_D3Dtex == nullptr) {
LookUpToMod(pTexture);
}
}
for (const auto pTexture : OriginalVolumeTextures) {
if (pTexture->CrossRef_D3Dtex == nullptr) {
LookUpToMod(pTexture);
}
}
for (const auto pTexture : OriginalCubeTextures) {
if (pTexture->CrossRef_D3Dtex == nullptr) {
LookUpToMod(pTexture);
}
}
return UnlockMutex();
}
void TextureClient::SetLastCreatedTexture(uMod_IDirect3DTexture9* texture)
{
if (isDirectXExDevice)
return static_cast<uMod_IDirect3DDevice9Ex*>(D3D9Device)->SetLastCreatedTexture(texture);
return static_cast<uMod_IDirect3DDevice9*>(D3D9Device)->SetLastCreatedTexture(texture);
}
void TextureClient::SetLastCreatedVolumeTexture(uMod_IDirect3DVolumeTexture9* texture)
{
if (isDirectXExDevice)
return static_cast<uMod_IDirect3DDevice9Ex*>(D3D9Device)->SetLastCreatedVolumeTexture(texture);
return static_cast<uMod_IDirect3DDevice9*>(D3D9Device)->SetLastCreatedVolumeTexture(texture);
}
void TextureClient::SetLastCreatedCubeTexture(uMod_IDirect3DCubeTexture9* texture)
{
if (isDirectXExDevice)
return static_cast<uMod_IDirect3DDevice9Ex*>(D3D9Device)->SetLastCreatedCubeTexture(texture);
return static_cast<uMod_IDirect3DDevice9*>(D3D9Device)->SetLastCreatedCubeTexture(texture);
}
int TextureClient::LockMutex()
{
if ((gl_ErrorState & (uMod_ERROR_FATAL | uMod_ERROR_MUTEX))) {
return RETURN_NO_MUTEX;
}
if (WAIT_OBJECT_0 != WaitForSingleObject(mutex, 100)) {
return RETURN_MUTEX_LOCK; //waiting 100ms, to wait infinite pass INFINITE
}
return RETURN_OK;
}
int TextureClient::UnlockMutex()
{
if (ReleaseMutex(mutex) == 0) {
return RETURN_MUTEX_UNLOCK;
}
return RETURN_OK;
}
unsigned long TextureClient::AddFile(TextureFileStruct& entry)
{
if (modded_textures.contains(entry.crc_hash)) {
return 0;
}
TextureFileStruct* texture_file_struct = new TextureFileStruct();
texture_file_struct->data = std::move(entry.data);
texture_file_struct->crc_hash = entry.crc_hash;
texture_file_struct->is_wic_texture = entry.is_wic_texture;
modded_textures.emplace(entry.crc_hash, texture_file_struct);
should_update = true;
return texture_file_struct->data.size();
}
void TextureClient::LoadModsFromFile(const char* source)
{
static unsigned long loaded_size = 0;
Message("Initialize: searching in %s\n", source);
std::ifstream file(source);
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;
while (std::getline(file, line)) {
Message("Initialize: loading file %s... ", line.c_str());
// Remove newline character
line.erase(std::ranges::remove(line, '\n').begin(), line.end());
auto file_loader = FileLoader(line);
auto entries = file_loader.GetContents();
if (loaded_size > 1'500'000'000) {
Message("LoadModsFromFile: Loaded %d bytes, aborting!!!\n", loaded_size);
return;
}
if (entries.empty()) {
Message("No entries found.\n");
continue;
}
Message("%zu textures... ", entries.size());
unsigned long file_bytes_loaded = 0;
for (auto& tpf_entry : entries) {
file_bytes_loaded += AddFile(tpf_entry);
}
entries.clear();
Message("%d bytes loaded.\n", file_bytes_loaded);
loaded_size += file_bytes_loaded;
}
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");
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
char dllpath[MAX_PATH]{};
GetModuleFileName(gl_hThisInstance, dllpath, MAX_PATH); //ask for name and path of this dll
const auto exe = std::filesystem::path(gwpath).parent_path();
const auto dll = std::filesystem::path(dllpath).parent_path();
for (const auto& path : {exe, dll}) {
const auto modlist = path / "modlist.txt";
if (std::filesystem::exists(modlist)) {
Message("Initialize: found %s\n", modlist.string().c_str());
LoadModsFromFile(modlist.string().c_str());
}
}
Message("Initialize: end\n");
}
+24 -28
View File
@@ -1,15 +1,13 @@
#include "dll_main.h"
#include <Windows.h>
#include "Main.h"
#include <Psapi.h>
#include "MinHook.h"
#include <filesystem>
void ExitInstance();
void InitInstance(HINSTANCE hModule);
namespace {
#define DISABLE_HOOK(var) if(var) { MH_DisableHook(var);}
#define DISABLE_HOOK(var) if(var) { MH_DisableHook(var);}
using Direct3DCreate9_type = IDirect3D9* (APIENTRY*)(UINT);
using Direct3DCreate9Ex_type = HRESULT(APIENTRY*)(UINT SDKVersion, IDirect3D9Ex** ppD3D);
@@ -29,9 +27,8 @@ namespace {
HMODULE gMod_Loaded_d3d9_Module_Handle = nullptr;
HMODULE FindLoadedModuleByName(const char* name, bool include_this_module = false) {
HMODULE FindLoadedModuleByName(const char* name, bool include_this_module = false)
{
HMODULE hModules[1024];
HANDLE hProcess;
DWORD cbNeeded;
@@ -66,8 +63,6 @@ namespace {
const auto exe_path = std::filesystem::path(executable_path);
const auto gmod_path = std::filesystem::path(dll_path);
bool successful_load = false;
if (exe_path.parent_path() != gmod_path.parent_path()
|| gmod_path.filename() != "d3d9.dll") {
// Call basic LoadLibrary function; we're not in the same directory as the exe.
@@ -75,7 +70,7 @@ namespace {
}
if (!gMod_Loaded_d3d9_Module_Handle) {
// Tried resolving d3d9.dll locally, didn't work. Try system directory
char buffer[MAX_PATH] ;
char buffer[MAX_PATH];
ASSERT(GetSystemDirectory(buffer, _countof(buffer)) > 0); //get the system directory, we need to open the original d3d9.dll
// Append dll name
@@ -91,30 +86,32 @@ namespace {
DISABLE_HOOK(GetProcAddress_fn);
// GetProcAddress, hooked via OnGetProcAddress
Direct3DCreate9_ret = (Direct3DCreate9_type)GetProcAddress(found, "Direct3DCreate9");
Direct3DCreate9Ex_ret = (Direct3DCreate9Ex_type)GetProcAddress(found, "Direct3DCreate9Ex");
Direct3DCreate9_ret = reinterpret_cast<Direct3DCreate9_type>(GetProcAddress(found, "Direct3DCreate9"));
Direct3DCreate9Ex_ret = reinterpret_cast<Direct3DCreate9Ex_type>(GetProcAddress(found, "Direct3DCreate9Ex"));
return found;
}
FARPROC APIENTRY OnGetProcAddress(HMODULE hModule, LPCSTR lpProcName) {
FARPROC APIENTRY OnGetProcAddress(HMODULE hModule, LPCSTR lpProcName)
{
ASSERT(GetProcAddress_ret);
if ((int)lpProcName < 0xffff)
return GetProcAddress_ret(hModule, lpProcName); // lpProcName is ordinal offset, not string
if (strcmp(lpProcName, "Direct3DCreate9") == 0) {
Direct3DCreate9_ret = (Direct3DCreate9_type)GetProcAddress_ret(hModule, lpProcName);
return (FARPROC)Direct3DCreate9;
}
if (strcmp(lpProcName, "Direct3DCreate9Ex") == 0) {
Direct3DCreate9Ex_ret = (Direct3DCreate9Ex_type)GetProcAddress_ret(hModule, lpProcName);
return (FARPROC)Direct3DCreate9Ex;
}
return GetProcAddress_ret(hModule, lpProcName);
if (strcmp(lpProcName, "Direct3DCreate9") == 0) {
Direct3DCreate9_ret = reinterpret_cast<Direct3DCreate9_type>(GetProcAddress_ret(hModule, lpProcName));
return reinterpret_cast<FARPROC>(Direct3DCreate9);
}
if (strcmp(lpProcName, "Direct3DCreate9Ex") == 0) {
Direct3DCreate9Ex_ret = reinterpret_cast<Direct3DCreate9Ex_type>(GetProcAddress_ret(hModule, lpProcName));
return reinterpret_cast<FARPROC>(Direct3DCreate9Ex);
}
return GetProcAddress_ret(hModule, lpProcName);
}
// If the original d3d9 function is nullptr or points to gMod, load the actual d3d9 dll and redirect the addresses
void CheckLoadD3d9Dll() {
void CheckLoadD3d9Dll()
{
if (!(Direct3DCreate9_ret && Direct3DCreate9_ret != Direct3DCreate9)) {
ASSERT(LoadD3d9Dll());
ASSERT(Direct3DCreate9_ret && Direct3DCreate9_ret != Direct3DCreate9);
@@ -132,7 +129,7 @@ namespace {
unsigned int gl_ErrorState = 0;
HINSTANCE gl_hThisInstance = nullptr;
extern "C" IDirect3D9* APIENTRY Direct3DCreate9(UINT SDKVersion)
IDirect3D9* APIENTRY Direct3DCreate9(UINT SDKVersion)
{
Message("uMod_Direct3DCreate9: uMod %p\n", Direct3DCreate9);
@@ -150,7 +147,7 @@ extern "C" IDirect3D9* APIENTRY Direct3DCreate9(UINT SDKVersion)
return new uMod_IDirect3D9(pIDirect3D9_orig); //return our object instead of the "real one"
}
extern "C" HRESULT APIENTRY Direct3DCreate9Ex(UINT SDKVersion, IDirect3D9Ex** ppD3D)
HRESULT APIENTRY Direct3DCreate9Ex(UINT SDKVersion, IDirect3D9Ex** ppD3D)
{
Message("uMod_Direct3DCreate9Ex: uMod %p\n", Direct3DCreate9Ex);
@@ -247,4 +244,3 @@ void ExitInstance()
__except (EXCEPTION_CONTINUE_EXECUTION) { }
#endif
}
+5 -2
View File
@@ -1,5 +1,8 @@
#include "Main.h"
import TextureFunction;
import TextureClient;
//this function yields for the non switched texture object
HRESULT APIENTRY uMod_IDirect3DCubeTexture9::QueryInterface(REFIID riid, void** ppvObj)
{
@@ -260,8 +263,8 @@ HashType uMod_IDirect3DCubeTexture9::GetHash() const
}
}
const int size = (GetBitsFromFormat(desc.Format) * desc.Width * desc.Height) / 8;
const auto hash = GetCRC32(static_cast<char*>(d3dlr.pBits), size); //calculate the crc32 of the texture
const int size = (TextureFunction::GetBitsFromFormat(desc.Format) * desc.Width * desc.Height) / 8;
const auto hash = TextureFunction::GetCRC32(static_cast<char*>(d3dlr.pBits), size); //calculate the crc32 of the texture
// Only release surfaces after we're finished with d3dlr
if (pResolvedSurface != nullptr) {
+1
View File
@@ -1,4 +1,5 @@
#include "Main.h"
import TextureClient;
#ifndef RETURN_QueryInterface
#define RETURN_QueryInterface 0x01000000L
+5 -2
View File
@@ -1,5 +1,8 @@
#include "Main.h"
import TextureFunction;
import TextureClient;
//this function yields for the non switched texture object
HRESULT APIENTRY uMod_IDirect3DTexture9::QueryInterface(REFIID riid, void** ppvObj)
{
@@ -318,8 +321,8 @@ HashType uMod_IDirect3DTexture9::GetHash() const
}
}
const int size = (GetBitsFromFormat(desc.Format) * desc.Width * desc.Height) / 8;
const auto hash = GetCRC32(static_cast<char*>(d3dlr.pBits), size); //calculate the crc32 of the texture
const int size = (TextureFunction::GetBitsFromFormat(desc.Format) * desc.Width * desc.Height) / 8;
const auto hash = TextureFunction::GetCRC32(static_cast<char*>(d3dlr.pBits), size); //calculate the crc32 of the texture
// Only release surfaces after we're finished with d3dlr
if (pOffscreenSurface != nullptr) {
+4 -2
View File
@@ -1,4 +1,6 @@
#include "Main.h"
import TextureFunction;
import TextureClient;
HRESULT APIENTRY uMod_IDirect3DVolumeTexture9::QueryInterface(REFIID riid, void** ppvObj)
{
@@ -257,8 +259,8 @@ HashType uMod_IDirect3DVolumeTexture9::GetHash() const
}
}
const int size = (GetBitsFromFormat(desc.Format) * desc.Width * desc.Height * desc.Depth) / 8;
const auto hash = GetCRC32(static_cast<char*>(d3dlr.pBits), size); //calculate the crc32 of the texture
const int size = (TextureFunction::GetBitsFromFormat(desc.Format) * desc.Width * desc.Height * desc.Depth) / 8;
const auto hash = TextureFunction::GetCRC32(static_cast<char*>(d3dlr.pBits), size); //calculate the crc32 of the texture
// Only release surfaces after we're finished with d3dlr
if (pResolvedSurface != nullptr) {