mirror of
https://github.com/gwdevhub/gMod.git
synced 2026-07-15 15:09:30 +00:00
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:
@@ -0,0 +1,186 @@
|
||||
module;
|
||||
|
||||
#include "Main.h"
|
||||
#include "Defines.h"
|
||||
|
||||
export module ModfileLoader;
|
||||
|
||||
import <intsafe.h>;
|
||||
import <vector>;
|
||||
import <string>;
|
||||
import <libzippp.h>;
|
||||
import <regex>;
|
||||
import <algorithm>;
|
||||
import <filesystem>;
|
||||
import ModfileLoader.TpfReader;
|
||||
|
||||
export class ModfileLoader {
|
||||
std::string 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,
|
||||
0x74, 0x61, 0x67, 0x5B, 0x60, 0x70, 0x45, 0x74,
|
||||
0x5C, 0x22, 0x74, 0x5D, 0x6E, 0x6A, 0x73, 0x41,
|
||||
0x77, 0x6E, 0x46, 0x47, 0x77, 0x49, 0x0C, 0x4B,
|
||||
0x46, 0x6F
|
||||
};
|
||||
|
||||
public:
|
||||
ModfileLoader(const std::string& fileName);
|
||||
|
||||
std::vector<TexEntry> GetContents();
|
||||
|
||||
private:
|
||||
|
||||
std::vector<TexEntry> GetTpfContents();
|
||||
|
||||
std::vector<TexEntry> GetFileContents();
|
||||
|
||||
void LoadEntries(libzippp::ZipArchive& archive, std::vector<TexEntry>& entries);
|
||||
};
|
||||
|
||||
ModfileLoader::ModfileLoader(const std::string& fileName)
|
||||
{
|
||||
file_name = std::filesystem::absolute(fileName).string();
|
||||
}
|
||||
|
||||
std::vector<TexEntry> ModfileLoader::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<TexEntry> ModfileLoader::GetTpfContents()
|
||||
{
|
||||
std::vector<TexEntry> 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<TexEntry> ModfileLoader::GetFileContents()
|
||||
{
|
||||
std::vector<TexEntry> 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<TexEntry>& entries)
|
||||
{
|
||||
for (const auto& entry : archive.getEntries()) {
|
||||
if (entry.isFile()) {
|
||||
//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);
|
||||
std::smatch match;
|
||||
if (!std::regex_search(name, match, re)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
uint32_t crc_hash;
|
||||
try {
|
||||
crc_hash = std::stoul(match.str(), 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().string());
|
||||
delete[] data_ptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ParseTexmodArchive(std::vector<std::string>& lines, libzippp::ZipArchive& archive, std::vector<TexEntry>& entries)
|
||||
{
|
||||
for (const auto& line : lines) {
|
||||
// 0xC57D73F7|GW.EXE_0xC57D73F7.tga\r\n
|
||||
// match[1] | match[2]
|
||||
const static auto address_file_regex = std::regex(R"(^[\\/.]*([^|]+)\|([^\r\n]+))", std::regex::optimize);
|
||||
std::smatch match;
|
||||
std::regex_search(line, match, address_file_regex);
|
||||
if (match.size() != 3u)
|
||||
continue;
|
||||
const auto address_string = match[1].str();
|
||||
const auto file_path = match[2].str();
|
||||
|
||||
const auto entry = archive.getEntry(file_path);
|
||||
if (entry.isNull() || !entry.isFile()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
uint32_t crc_hash{};
|
||||
try {
|
||||
crc_hash = std::stoul(address_string, nullptr, 16);
|
||||
}
|
||||
catch (const std::invalid_argument& e) {
|
||||
Warning("Failed to parse %s as a hash", address_string.c_str());
|
||||
continue;
|
||||
}
|
||||
catch (const std::out_of_range& e) {
|
||||
Warning("Out of range while parsing %s as a hash", address_string.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().string());
|
||||
delete[] data_ptr;
|
||||
}
|
||||
}
|
||||
|
||||
void ModfileLoader::LoadEntries(libzippp::ZipArchive& archive, std::vector<TexEntry>& 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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
export module ModfileLoader.TpfReader;
|
||||
|
||||
import <string>;
|
||||
import <fstream>;
|
||||
import <vector>;
|
||||
|
||||
export class TpfReader {
|
||||
public:
|
||||
TpfReader(const std::string& 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()) {
|
||||
throw std::invalid_argument("Provided stream needs to have SEEK set to True");
|
||||
}
|
||||
}
|
||||
|
||||
~TpfReader()
|
||||
{
|
||||
file_stream.close();
|
||||
}
|
||||
|
||||
std::vector<char> ReadToEnd()
|
||||
{
|
||||
file_stream.seekg(0, std::ios::end);
|
||||
line_length = file_stream.tellg();
|
||||
file_stream.seekg(0, std::ios::beg); // Go to the beginning of the stream
|
||||
|
||||
std::vector<char> data(line_length);
|
||||
file_stream.read(data.data(), line_length);
|
||||
for (auto i = 0; i < data.size(); i++) {
|
||||
data[i] = XOR(data[i], i);
|
||||
}
|
||||
|
||||
for (int i = data.size() - 1; i > 0 && data[i] != 0; i--) {
|
||||
data[i] = 0;
|
||||
}
|
||||
|
||||
// in the other zip libraries, these had to be cut off, with libzip we need to zero them out
|
||||
// cutting them off makes the archive invalid
|
||||
//data.resize(last_zero);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
private:
|
||||
std::ifstream file_stream;
|
||||
long line_length;
|
||||
|
||||
[[nodiscard]] char XOR(const char b, const long position) const
|
||||
{
|
||||
if (position > line_length - 4) {
|
||||
return b ^ TPF_XOREven;
|
||||
}
|
||||
|
||||
return position % 2 == 0 ? b ^ TPF_XOREven : b ^ TPF_XOROdd;
|
||||
}
|
||||
|
||||
static constexpr char TPF_XOROdd = 0x3F;
|
||||
static constexpr char TPF_XOREven = static_cast<char>(0xA4);
|
||||
};
|
||||
@@ -0,0 +1,380 @@
|
||||
module;
|
||||
|
||||
#include "Main.h"
|
||||
#include "Error.h"
|
||||
#include "uMod_IDirect3DTexture9.h"
|
||||
#include <DDSTextureLoader/DDSTextureLoader9.h>
|
||||
#include <DirectXTex/DirectXTex.h>
|
||||
|
||||
export module TextureClient;
|
||||
import TextureFunction;
|
||||
import ModfileLoader;
|
||||
|
||||
/*
|
||||
* An object of this class is owned by each d3d9 device.
|
||||
* All other functions are called from the render thread instance of the game itself.
|
||||
*/
|
||||
export class TextureClient {
|
||||
public:
|
||||
TextureClient(IDirect3DDevice9* device);
|
||||
~TextureClient();
|
||||
|
||||
int AddTexture(uModTexturePtr auto pTexture);
|
||||
int RemoveTexture(uModTexturePtr auto pTexture); //called from uMod_IDirect3DTexture9::Release()
|
||||
int LookUpToMod(uModTexturePtr auto pTexture);
|
||||
// called at the end AddTexture(...) and from Device->UpdateTexture(...)
|
||||
|
||||
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;
|
||||
// stores the pointer to the uMod_IDirect3DVolumeTexture9 objects created by the game
|
||||
std::vector<uMod_IDirect3DCubeTexture9*> OriginalCubeTextures;
|
||||
// stores the pointer to the uMod_IDirect3DCubeTexture9 objects created by the game
|
||||
|
||||
|
||||
private:
|
||||
IDirect3DDevice9* D3D9Device;
|
||||
// Cached info about whether this id a dx9ex device or not; used for proxy functions
|
||||
bool isDirectXExDevice = false;
|
||||
|
||||
// DX9 proxy functions
|
||||
void SetLastCreatedTexture(uMod_IDirect3DTexture9*);
|
||||
void SetLastCreatedVolumeTexture(uMod_IDirect3DVolumeTexture9*);
|
||||
void SetLastCreatedCubeTexture(uMod_IDirect3DCubeTexture9*);
|
||||
|
||||
bool should_update = false;
|
||||
|
||||
int LockMutex();
|
||||
int UnlockMutex();
|
||||
HANDLE hMutex;
|
||||
std::mutex mutex;
|
||||
|
||||
std::unordered_map<HashType, 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
|
||||
int LoadTexture(TextureFileStruct* file_in_memory, uModTexturePtrPtr auto ppTexture);
|
||||
|
||||
void LoadModsFromFile(const char* source);
|
||||
std::filesystem::path exe_path; // path to gw.exe
|
||||
std::filesystem::path dll_path; // path to gmod dll
|
||||
};
|
||||
|
||||
TextureClient::TextureClient(IDirect3DDevice9* device)
|
||||
{
|
||||
Message("TextureClient::TextureClient(): %p\n", this);
|
||||
D3D9Device = device;
|
||||
|
||||
void* cpy;
|
||||
isDirectXExDevice = D3D9Device->QueryInterface(IID_IDirect3DTexture9, &cpy) == 0x01000001L;
|
||||
|
||||
hMutex = CreateMutex(nullptr, false, nullptr);
|
||||
}
|
||||
|
||||
TextureClient::~TextureClient()
|
||||
{
|
||||
Message("TextureClient::~TextureClient(): %p\n", this);
|
||||
if (hMutex != nullptr) {
|
||||
CloseHandle(hMutex);
|
||||
}
|
||||
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(hMutex, 100)) {
|
||||
return RETURN_MUTEX_LOCK; //waiting 100ms, to wait infinite pass INFINITE
|
||||
}
|
||||
return RETURN_OK;
|
||||
}
|
||||
|
||||
int TextureClient::UnlockMutex()
|
||||
{
|
||||
if (ReleaseMutex(hMutex) == 0) {
|
||||
return RETURN_MUTEX_UNLOCK;
|
||||
}
|
||||
return RETURN_OK;
|
||||
}
|
||||
|
||||
unsigned long TextureClient::AddFile(TexEntry& entry, const bool compress)
|
||||
{
|
||||
if (modded_textures.contains(entry.crc_hash)) {
|
||||
return 0;
|
||||
}
|
||||
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();
|
||||
}
|
||||
|
||||
unsigned ProcessModfile(TextureClient& client, const std::string& modfile, const bool compress)
|
||||
{
|
||||
const auto hr = CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
|
||||
if (FAILED(hr)) return 0;
|
||||
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;
|
||||
}
|
||||
Message("%zu textures... ", entries.size());
|
||||
unsigned long file_bytes_loaded = 0;
|
||||
for (auto& tpf_entry : entries) {
|
||||
file_bytes_loaded += client.AddFile(tpf_entry, compress);
|
||||
}
|
||||
entries.clear();
|
||||
Message("%d bytes loaded.\n", file_bytes_loaded);
|
||||
CoUninitialize();
|
||||
return file_bytes_loaded;
|
||||
}
|
||||
|
||||
void TextureClient::LoadModsFromFile(const char* source)
|
||||
{
|
||||
static std::vector<std::string> loaded_modfiles{};
|
||||
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;
|
||||
std::vector<std::string> 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);
|
||||
}
|
||||
}
|
||||
auto files_size = 0u;
|
||||
for (const auto modfile : modfiles) {
|
||||
if (std::filesystem::exists(modfile)) {
|
||||
files_size += std::filesystem::file_size(modfile);
|
||||
}
|
||||
}
|
||||
std::vector<std::future<unsigned>> futures;
|
||||
for (const auto modfile : modfiles) {
|
||||
futures.emplace_back(std::async(std::launch::async, ProcessModfile, std::ref(*this), modfile, files_size > 400'000'000));
|
||||
}
|
||||
// Join all threads
|
||||
for (auto& future : futures) {
|
||||
loaded_size += future.get();
|
||||
}
|
||||
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
|
||||
exe_path = std::filesystem::path(gwpath).parent_path();
|
||||
dll_path = std::filesystem::path(dllpath).parent_path();
|
||||
for (const auto& path : {exe_path, dll_path}) {
|
||||
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");
|
||||
}
|
||||
|
||||
int TextureClient::AddTexture(uModTexturePtr auto pTexture)
|
||||
{
|
||||
if constexpr (std::same_as<decltype(pTexture), uMod_IDirect3DTexture9*>) {
|
||||
SetLastCreatedTexture(nullptr);
|
||||
}
|
||||
else if constexpr (std::same_as<decltype(pTexture), uMod_IDirect3DVolumeTexture9*>) {
|
||||
SetLastCreatedVolumeTexture(nullptr);
|
||||
}
|
||||
else if constexpr (std::same_as<decltype(pTexture), uMod_IDirect3DCubeTexture9*>) {
|
||||
SetLastCreatedCubeTexture(nullptr);
|
||||
}
|
||||
|
||||
if (pTexture->FAKE) {
|
||||
return RETURN_OK; // this is a fake texture
|
||||
}
|
||||
|
||||
if (gl_ErrorState & uMod_ERROR_FATAL) {
|
||||
return RETURN_FATAL_ERROR;
|
||||
}
|
||||
|
||||
Message("TextureClient::AddTexture( Cube: %p): %p (thread: %u)\n", pTexture, this, GetCurrentThreadId());
|
||||
|
||||
pTexture->Hash = pTexture->GetHash();
|
||||
if (!pTexture->Hash) {
|
||||
return RETURN_FATAL_ERROR;
|
||||
}
|
||||
|
||||
if constexpr (std::same_as<decltype(pTexture), uMod_IDirect3DTexture9*>) {
|
||||
OriginalTextures.push_back(pTexture);
|
||||
}
|
||||
else if constexpr (std::same_as<decltype(pTexture), uMod_IDirect3DVolumeTexture9*>) {
|
||||
OriginalVolumeTextures.push_back(pTexture);
|
||||
}
|
||||
else if constexpr (std::same_as<decltype(pTexture), uMod_IDirect3DCubeTexture9*>) {
|
||||
OriginalCubeTextures.push_back(pTexture);
|
||||
}
|
||||
|
||||
return LookUpToMod(pTexture); // check if this texture should be modded
|
||||
}
|
||||
|
||||
int TextureClient::RemoveTexture(uModTexturePtr auto pTexture)
|
||||
{
|
||||
Message("TextureClient::RemoveTexture( %p, %#lX): %p\n", pTexture, pTexture->Hash, this);
|
||||
|
||||
if (gl_ErrorState & uMod_ERROR_FATAL) {
|
||||
return RETURN_FATAL_ERROR;
|
||||
}
|
||||
|
||||
if (!pTexture->FAKE) {
|
||||
if constexpr (std::same_as<decltype(pTexture), uMod_IDirect3DTexture9*>) {
|
||||
utils::erase_first(OriginalTextures, pTexture);
|
||||
}
|
||||
else if constexpr (std::same_as<decltype(pTexture), uMod_IDirect3DVolumeTexture9*>) {
|
||||
utils::erase_first(OriginalVolumeTextures, pTexture);
|
||||
}
|
||||
else if constexpr (std::same_as<decltype(pTexture), uMod_IDirect3DCubeTexture9*>) {
|
||||
utils::erase_first(OriginalCubeTextures, pTexture);
|
||||
}
|
||||
}
|
||||
if (!pTexture->Reference)
|
||||
return RETURN_OK; // Should this ever happen?
|
||||
return RETURN_OK;
|
||||
}
|
||||
|
||||
int TextureClient::LookUpToMod(uModTexturePtr auto pTexture)
|
||||
{
|
||||
Message("TextureClient::LookUpToMod( %p): hash: %#lX, %p\n", pTexture, pTexture->Hash, this);
|
||||
int ret = RETURN_OK;
|
||||
|
||||
if (pTexture->CrossRef_D3Dtex != nullptr)
|
||||
return ret; // bug, this texture is already switched
|
||||
|
||||
const auto found = modded_textures.find(pTexture->Hash);
|
||||
if (found == modded_textures.end())
|
||||
return ret;
|
||||
|
||||
const auto textureFileStruct = found->second;
|
||||
|
||||
decltype(pTexture) fake_Texture;
|
||||
ret = LoadTexture(textureFileStruct, &fake_Texture);
|
||||
if (ret != RETURN_OK)
|
||||
return ret;
|
||||
|
||||
ret = SwitchTextures(fake_Texture, pTexture);
|
||||
if (ret != RETURN_OK) {
|
||||
Message("TextureClient::LookUpToMod(): textures not switched %#lX\n", textureFileStruct->crc_hash);
|
||||
fake_Texture->Release();
|
||||
return ret;
|
||||
}
|
||||
fake_Texture->Reference = textureFileStruct;
|
||||
return ret;
|
||||
}
|
||||
|
||||
int TextureClient::LoadTexture(TextureFileStruct* file_in_memory, uModTexturePtrPtr auto ppTexture)
|
||||
{
|
||||
Message("LoadTexture( %p, %p, %#lX): %p\n", file_in_memory, ppTexture, file_in_memory->crc_hash, this);
|
||||
if (const auto ret = DirectX::CreateDDSTextureFromMemoryEx(
|
||||
D3D9Device,
|
||||
file_in_memory->data.data(),
|
||||
file_in_memory->data.size(),
|
||||
0, D3DPOOL_MANAGED, false,
|
||||
reinterpret_cast<LPDIRECT3DTEXTURE9*>(ppTexture)); ret != D3D_OK) {
|
||||
*ppTexture = nullptr;
|
||||
Warning("LoadDDSTexture (%p, %#lX): FAILED ret: \n", file_in_memory->data.data(), file_in_memory->crc_hash, ret);
|
||||
return RETURN_TEXTURE_NOT_LOADED;
|
||||
}
|
||||
if constexpr (std::same_as<decltype(ppTexture), uMod_IDirect3DTexture9**>) {
|
||||
SetLastCreatedTexture(nullptr);
|
||||
}
|
||||
else if constexpr (std::same_as<decltype(ppTexture), uMod_IDirect3DVolumeTexture9**>) {
|
||||
SetLastCreatedVolumeTexture(nullptr);
|
||||
}
|
||||
else if constexpr (std::same_as<decltype(ppTexture), uMod_IDirect3DCubeTexture9**>) {
|
||||
SetLastCreatedCubeTexture(nullptr);
|
||||
}
|
||||
(*ppTexture)->FAKE = true;
|
||||
|
||||
Message("LoadTexture (%p, %#lX): DONE\n", *ppTexture, file_in_memory->crc_hash);
|
||||
return RETURN_OK;
|
||||
}
|
||||
@@ -0,0 +1,321 @@
|
||||
module;
|
||||
|
||||
#include "Main.h"
|
||||
#include <d3d9types.h>
|
||||
#include <DirectXTex/DirectXTex.h>
|
||||
|
||||
export module TextureFunction;
|
||||
|
||||
export template <typename T>
|
||||
concept uModTexturePtr = requires(T a)
|
||||
{
|
||||
std::same_as<uMod_IDirect3DTexture9*, T> ||
|
||||
std::same_as<uMod_IDirect3DVolumeTexture9*, T> ||
|
||||
std::same_as<uMod_IDirect3DCubeTexture9*, T>;
|
||||
};
|
||||
|
||||
export template <typename T>
|
||||
concept uModTexturePtrPtr = uModTexturePtr<std::remove_pointer_t<T>>;
|
||||
|
||||
export template <typename T> requires uModTexturePtr<T>
|
||||
void UnswitchTextures(T pTexture)
|
||||
{
|
||||
decltype(pTexture) CrossRef = pTexture->CrossRef_D3Dtex;
|
||||
if (CrossRef != nullptr) {
|
||||
std::swap(pTexture->m_D3Dtex, CrossRef->m_D3Dtex);
|
||||
|
||||
// cancel the link
|
||||
CrossRef->CrossRef_D3Dtex = nullptr;
|
||||
pTexture->CrossRef_D3Dtex = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
export template <typename T> requires uModTexturePtr<T>
|
||||
int SwitchTextures(T pTexture1, T pTexture2)
|
||||
{
|
||||
if (pTexture1->m_D3Ddev == pTexture2->m_D3Ddev && pTexture1->CrossRef_D3Dtex == nullptr && pTexture2->CrossRef_D3Dtex == nullptr) {
|
||||
// make cross reference
|
||||
pTexture1->CrossRef_D3Dtex = pTexture2;
|
||||
pTexture2->CrossRef_D3Dtex = pTexture1;
|
||||
|
||||
// switch textures
|
||||
std::swap(pTexture1->m_D3Dtex, pTexture2->m_D3Dtex);
|
||||
return RETURN_OK;
|
||||
}
|
||||
return RETURN_TEXTURE_NOT_SWITCHED;
|
||||
}
|
||||
|
||||
export namespace TextureFunction {
|
||||
|
||||
unsigned int GetCRC32(char* pcDatabuf, unsigned int ulDatalen)
|
||||
{
|
||||
constexpr static auto crc32_poly = 0xEDB88320u;
|
||||
constexpr static auto ul_crc_in = 0xffffffff;
|
||||
unsigned int crc = ul_crc_in;
|
||||
for (unsigned int idx = 0u; idx < ulDatalen; idx++) {
|
||||
unsigned int data = *pcDatabuf++;
|
||||
for (unsigned int bit = 0u; bit < 8u; bit++, data >>= 1) {
|
||||
crc = crc >> 1 ^ ((crc ^ data) & 1 ? crc32_poly : 0);
|
||||
}
|
||||
}
|
||||
return crc;
|
||||
}
|
||||
|
||||
int GetBitsFromFormat(D3DFORMAT format)
|
||||
{
|
||||
switch (format) //switch trough the formats to calculate the size of the raw data
|
||||
{
|
||||
case D3DFMT_A1: // 1-bit monochrome.
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
case D3DFMT_R3G3B2: // 8-bit RGB texture format using 3 bits for red, 3 bits for green, and 2 bits for blue.
|
||||
case D3DFMT_A8: // 8-bit alpha only.
|
||||
case D3DFMT_A8P8: // 8-bit color indexed with 8 bits of alpha.
|
||||
case D3DFMT_P8: // 8-bit color indexed.
|
||||
case D3DFMT_L8: // 8-bit luminance only.
|
||||
case D3DFMT_A4L4: // 8-bit using 4 bits each for alpha and luminance.
|
||||
case D3DFMT_FORCE_DWORD:
|
||||
case D3DFMT_S8_LOCKABLE: // A lockable 8-bit stencil buffer.
|
||||
{
|
||||
return 8;
|
||||
}
|
||||
|
||||
case D3DFMT_D16_LOCKABLE: //16-bit z-buffer bit depth.
|
||||
case D3DFMT_D15S1: // 16-bit z-buffer bit depth where 15 bits are reserved for the depth channel and 1 bit is reserved for the stencil channel.
|
||||
case D3DFMT_L6V5U5: // 16-bit bump-map format with luminance using 6 bits for luminance, and 5 bits each for v and u.
|
||||
case D3DFMT_V8U8: // 16-bit bump-map format using 8 bits each for u and v data.
|
||||
case D3DFMT_CxV8U8: // 16-bit normal compression format. The texture sampler computes the C channel from: C = sqrt(1 - U2 - V2).
|
||||
case D3DFMT_R5G6B5: // 16-bit RGB pixel format with 5 bits for red, 6 bits for green, and 5 bits for blue.
|
||||
case D3DFMT_X1R5G5B5: // 16-bit pixel format where 5 bits are reserved for each color.
|
||||
case D3DFMT_A1R5G5B5: // 16-bit pixel format where 5 bits are reserved for each color and 1 bit is reserved for alpha.
|
||||
case D3DFMT_A4R4G4B4: // 16-bit ARGB pixel format with 4 bits for each channel.
|
||||
case D3DFMT_A8R3G3B2: // 16-bit ARGB texture format using 8 bits for alpha, 3 bits each for red and green, and 2 bits for blue.
|
||||
case D3DFMT_X4R4G4B4: // 16-bit RGB pixel format using 4 bits for each color.
|
||||
case D3DFMT_L16: // 16-bit luminance only.
|
||||
case D3DFMT_R16F: // 16-bit float format using 16 bits for the red channel.
|
||||
case D3DFMT_A8L8: // 16-bit using 8 bits each for alpha and luminance.
|
||||
case D3DFMT_D16: // 16-bit z-buffer bit depth.
|
||||
case D3DFMT_INDEX16: // 16-bit index buffer bit depth.
|
||||
case D3DFMT_G8R8_G8B8: // ??
|
||||
case D3DFMT_R8G8_B8G8: // ??
|
||||
case D3DFMT_UYVY: // ??
|
||||
case D3DFMT_YUY2: // ??
|
||||
{
|
||||
return 16;
|
||||
}
|
||||
|
||||
|
||||
case D3DFMT_R8G8B8: //24-bit RGB pixel format with 8 bits per channel.
|
||||
{
|
||||
return 24;
|
||||
}
|
||||
|
||||
case D3DFMT_R32F: // 32-bit float format using 32 bits for the red channel.
|
||||
case D3DFMT_X8L8V8U8: // 32-bit bump-map format with luminance using 8 bits for each channel.
|
||||
case D3DFMT_A2W10V10U10: // 32-bit bump-map format using 2 bits for alpha and 10 bits each for w, v, and u.
|
||||
case D3DFMT_Q8W8V8U8: // 32-bit bump-map format using 8 bits for each channel.
|
||||
case D3DFMT_V16U16: // 32-bit bump-map format using 16 bits for each channel.
|
||||
case D3DFMT_A8R8G8B8: // 32-bit ARGB pixel format with alpha, using 8 bits per channel.
|
||||
case D3DFMT_X8R8G8B8: // 32-bit RGB pixel format, where 8 bits are reserved for each color.
|
||||
case D3DFMT_A2B10G10R10: // 32-bit pixel format using 10 bits for each color and 2 bits for alpha.
|
||||
case D3DFMT_A8B8G8R8: // 32-bit ARGB pixel format with alpha, using 8 bits per channel.
|
||||
case D3DFMT_X8B8G8R8: // 32-bit RGB pixel format, where 8 bits are reserved for each color.
|
||||
case D3DFMT_G16R16: // 32-bit pixel format using 16 bits each for green and red.
|
||||
case D3DFMT_G16R16F: // 32-bit float format using 16 bits for the red channel and 16 bits for the green channel.
|
||||
case D3DFMT_A2R10G10B10: // 32-bit pixel format using 10 bits each for red, green, and blue, and 2 bits for alpha.
|
||||
case D3DFMT_D32: // 32-bit z-buffer bit depth.
|
||||
case D3DFMT_D24S8: // 32-bit z-buffer bit depth using 24 bits for the depth channel and 8 bits for the stencil channel.
|
||||
case D3DFMT_D24X8: //32-bit z-buffer bit depth using 24 bits for the depth channel.
|
||||
case D3DFMT_D24X4S4: // 32-bit z-buffer bit depth using 24 bits for the depth channel and 4 bits for the stencil channel.
|
||||
case D3DFMT_D32F_LOCKABLE: // A lockable format where the depth value is represented as a standard IEEE floating-point number.
|
||||
case D3DFMT_D24FS8: // A non-lockable format that contains 24 bits of depth (in a 24-bit floating point format - 20e4) and 8 bits of stencil.
|
||||
case D3DFMT_D32_LOCKABLE: // A lockable 32-bit depth buffer.
|
||||
case D3DFMT_INDEX32: // 32-bit index buffer bit depth.
|
||||
{
|
||||
return 32;
|
||||
}
|
||||
|
||||
case D3DFMT_G32R32F: // 64-bit float format using 32 bits for the red channel and 32 bits for the green channel.
|
||||
case D3DFMT_Q16W16V16U16: // 64-bit bump-map format using 16 bits for each component.
|
||||
case D3DFMT_A16B16G16R16: // 64-bit pixel format using 16 bits for each component.
|
||||
case D3DFMT_A16B16G16R16F: // 64-bit float format using 16 bits for the each channel (alpha, blue, green, red).
|
||||
{
|
||||
return 64;
|
||||
}
|
||||
|
||||
case D3DFMT_A32B32G32R32F: // 128-bit float format using 32 bits for the each channel (alpha, blue, green, red).
|
||||
{
|
||||
return 128;
|
||||
}
|
||||
case D3DFMT_DXT2:
|
||||
case D3DFMT_DXT3:
|
||||
case D3DFMT_DXT4:
|
||||
case D3DFMT_DXT5: {
|
||||
return 8;
|
||||
}
|
||||
case D3DFMT_DXT1: {
|
||||
return 4;
|
||||
}
|
||||
default: //compressed formats
|
||||
{
|
||||
return 4;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DirectX::ScratchImage ImageConvertToBGRA(DirectX::ScratchImage& image, const TexEntry& entry)
|
||||
{
|
||||
if (image.GetMetadata().format == DXGI_FORMAT_B8G8R8A8_UNORM ||
|
||||
image.GetMetadata().format == DXGI_FORMAT_BC1_UNORM ||
|
||||
image.GetMetadata().format == DXGI_FORMAT_BC2_UNORM ||
|
||||
image.GetMetadata().format == DXGI_FORMAT_BC3_UNORM ||
|
||||
image.GetMetadata().format == DXGI_FORMAT_BC4_UNORM ||
|
||||
image.GetMetadata().format == DXGI_FORMAT_BC5_UNORM) {
|
||||
return std::move(image);
|
||||
}
|
||||
DirectX::ScratchImage bgra_image;
|
||||
const HRESULT hr = DirectX::Convert(
|
||||
image.GetImages(),
|
||||
image.GetImageCount(),
|
||||
image.GetMetadata(),
|
||||
DXGI_FORMAT_B8G8R8A8_UNORM,
|
||||
DirectX::TEX_FILTER_DEFAULT,
|
||||
DirectX::TEX_THRESHOLD_DEFAULT,
|
||||
bgra_image);
|
||||
if (FAILED(hr)) {
|
||||
Warning("ImageConvertToBGRA (%#lX%s): FAILED\n", entry.crc_hash, entry.ext.c_str());
|
||||
bgra_image = std::move(image);
|
||||
}
|
||||
image.Release();
|
||||
return bgra_image;
|
||||
}
|
||||
|
||||
DirectX::ScratchImage ImageGenerateMipMaps(DirectX::ScratchImage& image, const TexEntry& entry)
|
||||
{
|
||||
if (entry.ext == ".dds") {
|
||||
return std::move(image);
|
||||
}
|
||||
DirectX::ScratchImage mipmapped_image;
|
||||
const auto hr = DirectX::GenerateMipMaps(
|
||||
image.GetImages(),
|
||||
image.GetImageCount(),
|
||||
image.GetMetadata(),
|
||||
DirectX::TEX_FILTER_DEFAULT,
|
||||
0,
|
||||
mipmapped_image);
|
||||
if (FAILED(hr)) {
|
||||
Warning("GenerateMipMaps (%#lX%s): FAILED\n", entry.crc_hash, entry.ext.c_str());
|
||||
mipmapped_image = std::move(image);
|
||||
}
|
||||
image.Release();
|
||||
return mipmapped_image;
|
||||
}
|
||||
|
||||
DirectX::ScratchImage ImageCompress(DirectX::ScratchImage& image, const TexEntry& entry)
|
||||
{
|
||||
if (image.GetMetadata().format == DXGI_FORMAT_BC1_UNORM ||
|
||||
image.GetMetadata().format == DXGI_FORMAT_BC2_UNORM ||
|
||||
image.GetMetadata().format == DXGI_FORMAT_BC3_UNORM ||
|
||||
image.GetMetadata().format == DXGI_FORMAT_BC4_UNORM ||
|
||||
image.GetMetadata().format == DXGI_FORMAT_BC5_UNORM) {
|
||||
return std::move(image);
|
||||
}
|
||||
DirectX::ScratchImage compressed_image;
|
||||
const auto hr = DirectX::Compress(
|
||||
image.GetImages(),
|
||||
image.GetImageCount(),
|
||||
image.GetMetadata(),
|
||||
DXGI_FORMAT_BC3_UNORM,
|
||||
DirectX::TEX_COMPRESS_DEFAULT,
|
||||
DirectX::TEX_THRESHOLD_DEFAULT,
|
||||
compressed_image);
|
||||
if (FAILED(hr)) {
|
||||
Warning("ImageCompress (%#lX%s): FAILED\n", entry.crc_hash, entry.ext.c_str());
|
||||
compressed_image = std::move(image);
|
||||
}
|
||||
image.Release();
|
||||
return compressed_image;
|
||||
}
|
||||
|
||||
void ImageSave(const DirectX::ScratchImage& image, const TexEntry& entry, const std::filesystem::path& dll_path)
|
||||
{
|
||||
const auto file_name = std::format("0x{:x}.dds", entry.crc_hash);
|
||||
const auto file_out = dll_path / "textures" / file_name;
|
||||
try {
|
||||
if (std::filesystem::exists(file_out)) {
|
||||
return;
|
||||
}
|
||||
if (!std::filesystem::exists(file_out.parent_path())) {
|
||||
std::filesystem::create_directory(file_out.parent_path());
|
||||
}
|
||||
const auto hr = DirectX::SaveToDDSFile(
|
||||
image.GetImages(),
|
||||
image.GetImageCount(),
|
||||
image.GetMetadata(),
|
||||
DirectX::DDS_FLAGS_NONE,
|
||||
file_out.c_str());
|
||||
if (FAILED(hr)) {
|
||||
Warning("SaveDDSImageToDisk (%#lX%s): FAILED\n", entry.crc_hash, entry.ext.c_str());
|
||||
}
|
||||
}
|
||||
catch (const std::exception& e) {
|
||||
Warning("SaveDDSImageToDisk (%#lX%s): %s\n", entry.crc_hash, entry.ext.c_str(), e.what());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
DirectX::Blob ConvertToCompressedDDS(TexEntry& entry, const bool compress, const std::filesystem::path& dll_path)
|
||||
{
|
||||
DirectX::ScratchImage image;
|
||||
HRESULT hr = 0;
|
||||
|
||||
if (entry.ext == ".dds") {
|
||||
hr = DirectX::LoadFromDDSMemory(entry.data.data(), entry.data.size(), DirectX::DDS_FLAGS_NONE, nullptr, image);
|
||||
}
|
||||
else if (entry.ext == ".tga") {
|
||||
hr = DirectX::LoadFromTGAMemory(entry.data.data(), entry.data.size(), DirectX::TGA_FLAGS_BGR, nullptr, image);
|
||||
}
|
||||
else if (entry.ext == ".hdr") {
|
||||
hr = DirectX::LoadFromHDRMemory(entry.data.data(), entry.data.size(), nullptr, image);
|
||||
}
|
||||
else {
|
||||
hr = DirectX::LoadFromWICMemory(entry.data.data(), entry.data.size(), DirectX::WIC_FLAGS_NONE, nullptr, image);
|
||||
if (image.GetMetadata().format == DXGI_FORMAT_B8G8R8X8_UNORM) {
|
||||
// todo: this is undefined behaviour, but we must force them to be interpreted as BGRA instead of BGRX
|
||||
const_cast<DXGI_FORMAT&>(image.GetMetadata().format) = DXGI_FORMAT_B8G8R8A8_UNORM;
|
||||
const auto images = image.GetImages();
|
||||
for (int i = 0; i < image.GetImageCount(); ++i) {
|
||||
const_cast<DXGI_FORMAT&>(images[i].format) = DXGI_FORMAT_B8G8R8A8_UNORM;
|
||||
}
|
||||
}
|
||||
}
|
||||
entry.data.clear();
|
||||
if (FAILED(hr)) {
|
||||
Warning("LoadImageFromMemory (%#lX%s): FAILED\n", entry.crc_hash, entry.ext.c_str());
|
||||
return {};
|
||||
}
|
||||
|
||||
auto bgra_image = ImageConvertToBGRA(image, entry);
|
||||
auto mipmapped_image = ImageGenerateMipMaps(bgra_image, entry);
|
||||
const auto compressed_image = compress ? ImageCompress(mipmapped_image, entry) : std::move(mipmapped_image);
|
||||
|
||||
DirectX::Blob dds_blob;
|
||||
hr = DirectX::SaveToDDSMemory(
|
||||
compressed_image.GetImages(),
|
||||
compressed_image.GetImageCount(),
|
||||
compressed_image.GetMetadata(),
|
||||
DirectX::DDS_FLAGS_NONE,
|
||||
dds_blob);
|
||||
if (FAILED(hr)) {
|
||||
Warning("SaveDDSImageToMemory (%#lX%s): FAILED\n", entry.crc_hash, entry.ext.c_str());
|
||||
return {};
|
||||
}
|
||||
|
||||
#ifdef _DEBUG
|
||||
ImageSave(compressed_image, entry, dll_path);
|
||||
#endif
|
||||
return dds_blob;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user