diff --git a/CMakeLists.txt b/CMakeLists.txt index fb19eecb28..29f59a844f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,6 +7,7 @@ if(CCACHE_PROGRAM) endif() set(SENTRY_UPLOAD_URL "" CACHE STRING "Sentry upload URL") +set(GDXSV_TEXTURE_PACK_PASS "" CACHE STRING "Gdxsv texture pack password") set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/shell/cmake") @@ -254,6 +255,10 @@ if(NOT "${SENTRY_UPLOAD_URL}" STREQUAL "") target_compile_definitions(${PROJECT_NAME} PRIVATE SENTRY_UPLOAD="${SENTRY_UPLOAD_URL}") endif() +if(NOT "${GDXSV_TEXTURE_PACK_PASS}" STREQUAL "") + target_compile_definitions(${PROJECT_NAME} PRIVATE GDXSV_TEXTURE_PACK_PASS="${GDXSV_TEXTURE_PACK_PASS}") +endif() + target_include_directories(${PROJECT_NAME} PRIVATE core core/deps core/deps/stb core/deps/khronos core/deps/json) if(LIBRETRO) target_include_directories(${PROJECT_NAME} PRIVATE shell/libretro) @@ -2075,8 +2080,8 @@ target_sources(${PROJECT_NAME} PRIVATE core/gdxsv/gdxsv.h core/gdxsv/gdxsv.pb.cc core/gdxsv/gdxsv.pb.h - core/gdxsv/gdxsv_CustomTexture.cpp - core/gdxsv/gdxsv_CustomTexture.h + core/gdxsv/gdxsv_custom_texture_source.cpp + core/gdxsv/gdxsv_custom_texture_source.h core/gdxsv/gdxsv_backend_replay.cpp core/gdxsv/gdxsv_backend_replay.h core/gdxsv/gdxsv_backend_rollback.cpp diff --git a/core/cfg/option.cpp b/core/cfg/option.cpp index a9cbf77f8b..08f2d42589 100644 --- a/core/cfg/option.cpp +++ b/core/cfg/option.cpp @@ -141,6 +141,7 @@ Option ProfilerFrameWarningTime("Profiler.FrameWarningTime", 1.0f / 55.0f Option GdxLobbyServer("server", "zdxsv.net", "gdxsv"); Option GdxLoginKey("loginkey", "", "gdxsv"); Option GdxLanguage("language", -1, "gdxsv"); +Option GdxUseTexturePack("UseTexturePack", false, "gdxsv"); Option GdxLocalPort("LocalPort", 0, "gdxsv"); Option GdxMinDelay("MinDelay", 2, "gdxsv"); Option GdxSaveReplay("SaveReplay", true, "gdxsv"); diff --git a/core/cfg/option.h b/core/cfg/option.h index c955fc1d73..fdd25bfa3f 100644 --- a/core/cfg/option.h +++ b/core/cfg/option.h @@ -499,6 +499,7 @@ extern Option ProfilerFrameWarningTime; extern Option GdxLobbyServer; extern Option GdxLoginKey; extern Option GdxLanguage; +extern Option GdxUseTexturePack; extern Option GdxLocalPort; extern Option GdxMinDelay; extern Option GdxSaveReplay; diff --git a/core/emulator.cpp b/core/emulator.cpp index 2fc95d930a..f2e344c0a9 100644 --- a/core/emulator.cpp +++ b/core/emulator.cpp @@ -46,7 +46,6 @@ #include #include "gdxsv/gdxsv_emu_hooks.h" -#include "gdxsv/gdxsv_CustomTexture.h" settings_t settings; constexpr float WINCE_DEPTH_SCALE = 0.01f; @@ -702,9 +701,6 @@ void Emulator::unloadGame() state = Init; EventManager::event(Event::Terminate); } -#if defined(__APPLE__) || defined(_WIN32) - gdx_custom_texture.Terminate(); -#endif } void Emulator::term() @@ -715,9 +711,6 @@ void Emulator::term() debugger::term(); sh4_cpu.Term(); custom_texture.Terminate(); // lr: avoid deadlock on exit (win32) -#if defined(__APPLE__) || defined(_WIN32) - gdx_custom_texture.Terminate(); -#endif reios_term(); aica::term(); pvr::term(); @@ -815,9 +808,6 @@ void Emulator::stepRange(u32 from, u32 to) void dc_loadstate(Deserializer& deser) { custom_texture.Terminate(); -#if defined(__APPLE__) || defined(_WIN32) - gdx_custom_texture.Terminate(); -#endif #if FEAT_AREC == DYNAREC_JIT aica::arm::recompiler::flush(); #endif diff --git a/core/gdxsv/gdxsv_CustomTexture.cpp b/core/gdxsv/gdxsv_CustomTexture.cpp deleted file mode 100644 index 6ca97e8ab9..0000000000 --- a/core/gdxsv/gdxsv_CustomTexture.cpp +++ /dev/null @@ -1,163 +0,0 @@ -// -// gdxsv_CustomTexture.cpp -// gdxsv -// -// Created by Edward Li on 3/6/2021. -// Copyright 2021 flycast. All rights reserved. -// -#ifdef _WIN32 -#define _AMD64_ // Fixing GitHub runner's winnt.h error -#endif - -#include "gdxsv_CustomTexture.h" - -#include - -#include "gdxsv_translation.h" - -#ifdef __APPLE__ -#include - -#include - -#include "oslib/directory.h" -#elif _WIN32 -#include -#include -#include -#endif - -GdxsvCustomTexture gdx_custom_texture; - -bool GdxsvCustomTexture::Init() { - if (!initialized) { - initialized = true; - std::string game_id = GetGameId(); - if (GetGameId() == "T13306M") { -#ifdef __APPLE__ - uint32_t bufSize = PATH_MAX + 1; - char result[bufSize]; - if (_NSGetExecutablePath(result, &bufSize) == 0) { - textures_path = std::string(result); - textures_path.replace(textures_path.find("MacOS/Flycast"), sizeof("MacOS/Flycast") - 1, "Resources/Textures/"); - textures_path += GdxsvLanguage::TextureDirectoryName(); - } - - DIR* dir = flycast::opendir(textures_path.c_str()); - if (dir != nullptr) { - INFO_LOG(RENDERER, "Found custom textures directory: %s", textures_path.c_str()); - custom_textures_available = true; - closedir(dir); - loader_thread.Start(); - } -#elif _WIN32 - custom_textures_available = true; - loader_thread.Start(); -#endif - } - } - return custom_textures_available; -} - -u8* GdxsvCustomTexture::LoadExtraTexture(const char* name, bool v_flip, int& width, int& height) { - if (!custom_textures_available) { - return nullptr; - } - -#ifdef _WIN32 - u8* imgData = nullptr; - std::string upper_name = name; - std::transform(upper_name.begin(), upper_name.end(), upper_name.begin(), ::toupper); - if (upper_name.find_last_of("PNG") != std::string::npos) { - upper_name = upper_name.substr(0, upper_name.find_last_of('.')); - } - HRSRC source = FindResourceA(GetModuleHandle(NULL), ("EXTRA_" + upper_name).c_str(), "GDXSV_TEXTURE"); - if (source != NULL) { - unsigned int size = SizeofResource(NULL, source); - HGLOBAL memory = LoadResource(NULL, source); - - if (memory != NULL) { - void* data = LockResource(memory); - - int n; - stbi_set_flip_vertically_on_load((int)v_flip); - imgData = stbi_load_from_memory(static_cast(data), size, &width, &height, &n, STBI_rgb_alpha); - - FreeResource(memory); - } - } - return imgData; - -#else - - auto textures_base_path = textures_path.substr(0, textures_path.find_last_of("/")); - FILE* file = nowide::fopen((textures_base_path + "/Extra/" + name).c_str(), "rb"); - if (file == nullptr) return nullptr; - int n; - stbi_set_flip_vertically_on_load((int)v_flip); - u8* imgData = stbi_load_from_file(file, &width, &height, &n, STBI_rgb_alpha); - std::fclose(file); - return imgData; - -#endif -} - -#ifdef _WIN32 - -static BOOL CALLBACK StaticEnumRCNamesFunc(HMODULE hModule, LPCTSTR lpType, LPTSTR lpName, LONG_PTR lParam) { - // Only add target language's hash & resource index into texture_map - auto mapping = reinterpret_cast*>(lParam); - const auto name = std::string(lpName); - auto lang_dir = GdxsvLanguage::TextureDirectoryName(); - std::transform(lang_dir.begin(), lang_dir.end(), lang_dir.begin(), ::toupper); - - if (name.find(lang_dir) == 0 || name.find("COMMON") == 0) { - auto tex_hash = name.substr(name.find_last_of('_') + 1); - u32 hash = strtoul(tex_hash.c_str(), NULL, 16); - mapping->emplace(hash, name); - } - - return true; -} - -void GdxsvCustomTexture::LoadMap() { - texture_map.clear(); - std::map mapping; - - auto ret = EnumResourceNames(GetModuleHandle(NULL), "GDXSV_TEXTURE", (ENUMRESNAMEPROC)&StaticEnumRCNamesFunc, - reinterpret_cast(&mapping)); - if (!ret) { - ERROR_LOG(COMMON, "EnumResourceNames error:%d", GetLastError()); - } - - texture_map = mapping; - custom_textures_available = !texture_map.empty(); -} - -u8* GdxsvCustomTexture::LoadCustomTexture(u32 hash, int& width, int& height) { - auto it = texture_map.find(hash); - if (it == texture_map.end()) return nullptr; - - u8* imgData = nullptr; - - // Load PNG data from resource - HRSRC source = FindResourceA(GetModuleHandle(NULL), it->second.c_str(), "GDXSV_TEXTURE"); - if (source != NULL) { - unsigned int size = SizeofResource(NULL, source); - HGLOBAL memory = LoadResource(NULL, source); - - if (memory != NULL) { - void* data = LockResource(memory); - - int n; - stbi_set_flip_vertically_on_load(1); - imgData = stbi_load_from_memory(static_cast(data), size, &width, &height, &n, STBI_rgb_alpha); - - FreeResource(memory); - } - } - - return imgData; -} - -#endif diff --git a/core/gdxsv/gdxsv_CustomTexture.h b/core/gdxsv/gdxsv_CustomTexture.h deleted file mode 100644 index 78f9db45ee..0000000000 --- a/core/gdxsv/gdxsv_CustomTexture.h +++ /dev/null @@ -1,27 +0,0 @@ -// -// gdxsv_CustomTexture.h -// gdxsv -// -// Created by Edward Li on 3/6/2021. -// Copyright 2021 flycast. All rights reserved. -// -#pragma once - -// clang-format off -#include "../rend/TexCache.h" -#include "../rend/CustomTexture.h" -// clang-format on - -class GdxsvCustomTexture : public CustomTexture { - public: - bool Init() override; - u8* LoadExtraTexture(const char* name, bool v_flip, int& width, int& height); - -#ifdef _WIN32 - u8* LoadCustomTexture(u32 hash, int& width, int& height) override; - - protected: - void LoadMap() override; -#endif -}; -extern GdxsvCustomTexture gdx_custom_texture; diff --git a/core/gdxsv/gdxsv_custom_texture_source.cpp b/core/gdxsv/gdxsv_custom_texture_source.cpp new file mode 100644 index 0000000000..ae7c5cf267 --- /dev/null +++ b/core/gdxsv/gdxsv_custom_texture_source.cpp @@ -0,0 +1,364 @@ +#ifdef _WIN32 +#define _AMD64_ // Fixing GitHub runner's winnt.h error +#endif + +#include "gdxsv_custom_texture_source.h" + +#include +#include + +#include "gdxsv_translation.h" +#include "oslib/storage.h" + +#ifdef __APPLE__ +#include + +#include + +#include "oslib/directory.h" +#elif _WIN32 +#include +#include +#include +#endif + +GdxsvTexturePackSource gdxsv_texture_pack_source; +GdxsvEmbedTextureSource gdxsv_embed_texture_source; + +static std::string get_game_id() { + std::string game_id(settings.content.gameId); + const size_t str_end = game_id.find_last_not_of(' '); + if (str_end == std::string::npos) return ""; + game_id = game_id.substr(0, str_end + 1); + std::replace(game_id.begin(), game_id.end(), ' ', '_'); + return game_id; +} + +GdxsvEmbedTextureSource ::~GdxsvEmbedTextureSource() { texture_map.clear(); } + +bool GdxsvEmbedTextureSource ::Init() { + if (GdxsvLanguage::Language() == GdxsvLanguage::Lang::Disabled) return false; + + if (!initialized) { + initialized = true; + std::string game_id = get_game_id(); + if (game_id == "T13306M") { +#ifdef __APPLE__ + uint32_t bufSize = PATH_MAX + 1; + char result[bufSize]; + if (_NSGetExecutablePath(result, &bufSize) == 0) { + textures_path = std::string(result); + textures_path.replace(textures_path.find("MacOS/Flycast"), sizeof("MacOS/Flycast") - 1, "Resources/Textures/"); + } + + DIR* dir = flycast::opendir(textures_path.c_str()); + if (dir != nullptr) { + INFO_LOG(RENDERER, "Found custom textures directory: %s", textures_path.c_str()); + custom_textures_available = true; + closedir(dir); + } +#elif _WIN32 + custom_textures_available = true; +#endif + // TODO: Linux + } + } + + return custom_textures_available; +} + +void GdxsvEmbedTextureSource ::Terminate() { + initialized = false; + custom_textures_available = false; + textures_path.clear(); + texture_map.clear(); +} + +#ifdef _WIN32 +static BOOL CALLBACK StaticEnumRCNamesFunc(HMODULE hModule, LPCTSTR lpType, LPTSTR lpName, LONG_PTR lParam) { + // Only add target language's hash & resource index into texture_map + auto mapping = reinterpret_cast*>(lParam); + const auto name = std::string(lpName); + auto lang_dir = GdxsvLanguage::TextureDirectoryName(); + std::transform(lang_dir.begin(), lang_dir.end(), lang_dir.begin(), ::toupper); + + if (name.find(lang_dir) == 0 || name.find("COMMON") == 0) { + auto tex_hash = name.substr(name.find_last_of('_') + 1); + u32 hash = strtoul(tex_hash.c_str(), NULL, 16); + mapping->emplace(hash, name); + } + + return true; +} +#endif + +bool GdxsvEmbedTextureSource ::LoadMap() { + std::map mapping; + + if (GdxsvLanguage::Language() != GdxsvLanguage::Lang::Disabled) { + // Normal image files by language + if (!textures_path.empty()) { + hostfs::DirectoryTree tree(textures_path + GdxsvLanguage::TextureDirectoryName()); + for (const hostfs::FileInfo& item : tree) { + std::string extension = get_file_extension(item.name); + if (extension != "jpg" && extension != "jpeg" && extension != "png") continue; + std::string::size_type dotpos = item.name.find_last_of('.'); + std::string basename = item.name.substr(0, dotpos); + char* endptr; + u32 hash = (u32)strtoll(basename.c_str(), &endptr, 16); + if (endptr - basename.c_str() < (ptrdiff_t)basename.length()) { + INFO_LOG(RENDERER, "Invalid hash %s", basename.c_str()); + continue; + } + mapping.emplace(hash, item.path); + } + } + +#if _WIN32 + // Embed textures (mainly localization) + const auto ret = EnumResourceNames(GetModuleHandle(NULL), "GDXSV_TEXTURE", (ENUMRESNAMEPROC)&StaticEnumRCNamesFunc, + reinterpret_cast(&mapping)); + if (!ret) { + ERROR_LOG(COMMON, "EnumResourceNames error:%d", GetLastError()); + } +#endif + } + + texture_map = mapping; + return custom_textures_available = !texture_map.empty(); +} + +u8* GdxsvEmbedTextureSource ::LoadCustomTexture(u32 hash, int& width, int& height) { + const auto it = texture_map.find(hash); + if (it == texture_map.end()) return nullptr; + +#if _WIN32 + // Load texture from EnumResource + HRSRC source = FindResourceA(GetModuleHandle(NULL), it->second.c_str(), "GDXSV_TEXTURE"); + if (source != NULL) { + unsigned int size = SizeofResource(NULL, source); + HGLOBAL memory = LoadResource(NULL, source); + + if (memory != NULL) { + void* data = LockResource(memory); + + int n; + stbi_set_flip_vertically_on_load(1); + u8* imgData = stbi_load_from_memory(static_cast(data), size, &width, &height, &n, STBI_rgb_alpha); + + FreeResource(memory); + return imgData; + } + } + return nullptr; + +#else + // Load texture from file + FILE* file = nowide::fopen(it->second.c_str(), "rb"); + if (file == nullptr) return nullptr; + int n; + stbi_set_flip_vertically_on_load(1); + u8* imgData = stbi_load_from_file(file, &width, &height, &n, STBI_rgb_alpha); + std::fclose(file); + return imgData; +#endif +} + +u8* GdxsvEmbedTextureSource ::LoadExtraTexture(const char* name, bool v_flip, int& width, int& height) const { +#ifdef _WIN32 + u8* imgData = nullptr; + std::string upper_name = name; + std::transform(upper_name.begin(), upper_name.end(), upper_name.begin(), ::toupper); + if (upper_name.find_last_of("PNG") != std::string::npos) { + upper_name = upper_name.substr(0, upper_name.find_last_of('.')); + } + HRSRC source = FindResourceA(GetModuleHandle(NULL), ("EXTRA_" + upper_name).c_str(), "GDXSV_TEXTURE"); + if (source != NULL) { + unsigned int size = SizeofResource(NULL, source); + HGLOBAL memory = LoadResource(NULL, source); + + if (memory != NULL) { + void* data = LockResource(memory); + + int n; + stbi_set_flip_vertically_on_load((int)v_flip); + imgData = stbi_load_from_memory(static_cast(data), size, &width, &height, &n, STBI_rgb_alpha); + + FreeResource(memory); + } + } + return imgData; + +#else + + FILE* file = nowide::fopen((textures_path + "/Extra/" + name).c_str(), "rb"); + if (file == nullptr) return nullptr; + int n; + stbi_set_flip_vertically_on_load((int)v_flip); + u8* imgData = stbi_load_from_file(file, &width, &height, &n, STBI_rgb_alpha); + std::fclose(file); + return imgData; + +#endif +} + +GdxsvTexturePackSource::~GdxsvTexturePackSource() { + if (texp_zip != nullptr) zip_close(texp_zip); + if (texp_file != nullptr) std::fclose(texp_file); + texp_file = nullptr; + texp_zip = nullptr; + texp_zip_source = nullptr; +} + +bool GdxsvTexturePackSource::Init() { + if (!config::GdxUseTexturePack) return false; + + if (!initialized) { + initialized = true; + std::string game_id = get_game_id(); + if (game_id == "T13306M") { + custom_textures_available = true; + } + } + + return custom_textures_available; +} + +void GdxsvTexturePackSource::Terminate() { + if (texp_zip != nullptr) zip_close(texp_zip); + if (texp_file != nullptr) std::fclose(texp_file); + initialized = false; + custom_textures_available = false; + texture_map.clear(); + texp_file = nullptr; + texp_zip = nullptr; + texp_zip_source = nullptr; +} + +bool GdxsvTexturePackSource::LoadMap() { + if (texp_zip != nullptr) zip_close(texp_zip); + if (texp_file != nullptr) std::fclose(texp_file); + texp_file = nullptr; + texp_zip = nullptr; + texp_zip_source = nullptr; + + std::map mapping; + + // Zipped texture pack + const auto texture_pack_path = get_writable_data_path(get_game_id() + ".texp"); + if (config::GdxUseTexturePack && file_exists(texture_pack_path)) do { + auto zip_file = nowide::fopen(texture_pack_path.c_str(), "rb"); + if (zip_file == nullptr) { + ERROR_LOG(COMMON, "LoadMap: fopen zip_path failure"); + break; + } + + zip_error_t error; + zip_source_t* source = zip_source_filep_create(zip_file, 0, -1, &error); + if (source == nullptr) { + ERROR_LOG(COMMON, "LoadMap: zip_source_filep_create failure: %s", error.str); + std::fclose(zip_file); + break; + } + + const auto zip = zip_open_from_source(source, ZIP_RDONLY, &error); + if (zip == nullptr) { + ERROR_LOG(COMMON, "LoadMap: zip_open_from_source failure: %s", error.str); + std::fclose(zip_file); + zip_source_free(source); + break; + } + +#ifdef GDXSV_TEXTURE_PACK_PASS + zip_set_default_password(zip, GDXSV_TEXTURE_PACK_PASS); +#endif +# + const auto num_entries = zip_get_num_entries(zip, 0); + if (num_entries < 0) { + ERROR_LOG(COMMON, "LoadMap: zip_get_num_entries failure"); + std::fclose(zip_file); + zip_source_free(source); + break; + } + + this->texp_file = zip_file; + this->texp_zip = zip; + this->texp_zip_source = source; + + for (int i = 0; i < num_entries; i++) { + std::string name(zip_get_name(zip, i, 0)); + if (name.empty()) { + ERROR_LOG(COMMON, "LoadMap: zip_get_name"); + break; + } + + std::string extension = get_file_extension(name); + if (extension != "jpg" && extension != "jpeg" && extension != "png") continue; + std::string::size_type dotpos = name.find_last_of('.'); + std::string::size_type slashpos = name.find_last_of('/'); + if (slashpos == std::string::npos) slashpos = 0; + std::string basename = name.substr(slashpos + 1, dotpos - (slashpos + 1)); + char* endptr; + u32 hash = (u32)strtoll(basename.c_str(), &endptr, 16); + if (endptr - basename.c_str() < (ptrdiff_t)basename.length()) { + INFO_LOG(COMMON, "Invalid hash %s", basename.c_str()); + continue; + } + mapping.emplace(hash, i); + } + } while (false); + + texture_map = mapping; + custom_textures_available = !texture_map.empty(); + return custom_textures_available; +} + +u8* GdxsvTexturePackSource::LoadCustomTexture(u32 hash, int& width, int& height) { + const auto it = texture_map.find(hash); + if (it == texture_map.end()) return nullptr; + + // Load texture from texture pack file + auto zfp = zip_fopen_index(texp_zip, it->second, 0); + if (zfp == nullptr) { + ERROR_LOG(COMMON, "LoadCustomTexture: zip_fopen_index failure"); + } else { + struct UserData { + zip_file_t* zfp; + bool eof; + }; + + stbi_io_callbacks cbk{}; + cbk.read = [](void* user, char* data, int size) -> int { + const auto u = static_cast(user); + const int n = static_cast(zip_fread(u->zfp, data, size)); + u->eof |= size != 0 && n == 0; + return n; + }; + cbk.skip = [](void* user, int skip) { + const auto u = static_cast(user); + while (0 < skip) { + char buf[1024]; + const int size = std::min(sizeof(buf), skip); + const int n = static_cast(zip_fread(u->zfp, buf, size)); + u->eof |= size != 0 && n == 0; + skip -= n; + } + }; + cbk.eof = [](void* user) -> int { + const auto u = static_cast(user); + return u->eof; + }; + + UserData u{}; + u.zfp = zfp; + u.eof = false; + int n; + stbi_set_flip_vertically_on_load(1); + u8* imgData = stbi_load_from_callbacks(&cbk, &u, &width, &height, &n, STBI_rgb_alpha); + + zip_fclose(zfp); + return imgData; + } + + return nullptr; +} diff --git a/core/gdxsv/gdxsv_custom_texture_source.h b/core/gdxsv/gdxsv_custom_texture_source.h new file mode 100644 index 0000000000..f6f966dc37 --- /dev/null +++ b/core/gdxsv/gdxsv_custom_texture_source.h @@ -0,0 +1,45 @@ +#pragma once + +#include "zip.h" + +// clang-format off +#include "../rend/TexCache.h" +#include "../rend/CustomTexture.h" +// clang-format on + +class GdxsvEmbedTextureSource : public ICustomTextureSource { + public: + ~GdxsvEmbedTextureSource() override; + bool Init() override; + bool LoadMap() override; + void Terminate() override; + u8* LoadCustomTexture(u32 hash, int& width, int& height) override; + u8* LoadExtraTexture(const char* name, bool v_flip, int& width, int& height) const; + + private: + bool initialized = false; + bool custom_textures_available = false; + std::string textures_path; + std::map texture_map; +}; + +class GdxsvTexturePackSource : public ICustomTextureSource { + public: + ~GdxsvTexturePackSource() override; + bool Init() override; + bool LoadMap() override; + void Terminate() override; + u8* LoadCustomTexture(u32 hash, int& width, int& height) override; + u8* LoadExtraTexture(const char* name, bool v_flip, int& width, int& height) const; + + private: + bool initialized = false; + bool custom_textures_available = false; + std::map texture_map; + FILE* texp_file = nullptr; + zip_t* texp_zip = nullptr; + zip_source_t* texp_zip_source = nullptr; +}; + +extern GdxsvTexturePackSource gdxsv_texture_pack_source; +extern GdxsvEmbedTextureSource gdxsv_embed_texture_source; diff --git a/core/gdxsv/gdxsv_emu_hooks.cpp b/core/gdxsv/gdxsv_emu_hooks.cpp index a981281652..5fb48e48ce 100644 --- a/core/gdxsv/gdxsv_emu_hooks.cpp +++ b/core/gdxsv/gdxsv_emu_hooks.cpp @@ -5,14 +5,13 @@ #include "cfg/cfg.h" #include "gdxsv.h" +#include "gdxsv_custom_texture_source.h" #include "gdxsv_gui_settings.h" #include "gdxsv_replay_util.h" #include "gdxsv_update.h" -#include "hw/maple/maple_if.h" #include "imgui/imgui.h" #include "imgui/imgui_internal.h" #include "json.hpp" -#include "libs.h" #include "nowide/fstream.hpp" #include "oslib/directory.h" #include "oslib/oslib.h" @@ -34,7 +33,11 @@ bool gdxsv_is_online() { return gdxsv.IsOnline(); } bool gdxsv_is_savestate_allowed() { return gdxsv.IsSaveStateAllowed(); } -void gdxsv_emu_flycast_init() { config::GGPOEnable = false; } +void gdxsv_emu_flycast_init() { + config::GGPOEnable = false; + custom_texture.AddTextureSource(&gdxsv_embed_texture_source); + custom_texture.AddTextureSource(&gdxsv_texture_pack_source); +} void gdxsv_emu_start() { gdxsv.Reset(); diff --git a/core/gdxsv/gdxsv_gui_settings.cpp b/core/gdxsv/gdxsv_gui_settings.cpp index 66d42fb4bd..29ccff4721 100644 --- a/core/gdxsv/gdxsv_gui_settings.cpp +++ b/core/gdxsv/gdxsv_gui_settings.cpp @@ -4,7 +4,6 @@ #include "gdxsv_network.h" #include "hw/maple/maple_if.h" #include "imgui.h" -#include "input/gamepad_device.h" #include "libs.h" #include "rend/gui_util.h" @@ -32,17 +31,16 @@ static const char* t(std::initializer_list texts) { return *(texts.begin() + index); } -void gdxsv_apply_base_settings() -{ +void gdxsv_apply_base_settings() { // Frame Limit config::LimitFPS = true; config::FixedFrequency = 2; - + // Controls config::MapleMainDevices[0].set(MapleDeviceType::MDT_SegaController); config::MapleExpansionDevices[0][0].set(MDT_SegaVMU); config::Sh4Clock = 200; - + // Video config::PerStripSorting = false; config::DelayFrameSwapping = false; @@ -55,23 +53,23 @@ void gdxsv_apply_base_settings() config::SkipFrame = 0; config::AutoSkipFrame = 0; config::TextureUpscale = 1; - + // Audio config::DSPEnabled = false; config::AudioVolume.set(50); config::AudioVolume.calcDbPower(); config::AudioBufferSize = 706 * 4; - + // Others config::DynarecEnabled = true; - + // Network config::EnableUPnP = true; if (config::GdxLocalPort == 0) { config::GdxLocalPort = get_random_port_number(); } config::GdxMinDelay = 2; - + maple_ReconnectDevices(); } @@ -102,7 +100,7 @@ void gdxsv_gui_settings_tab() ImGui::NextColumn(); OptionRadioButton("Disabled", config::GdxLanguage, 3); ImGui::Columns(1, nullptr, false); - + auto settings_to_be_changed = []() -> std::string { std::string str = t({ "Settings to be changed:\n\n", u8"設定を変更する:\n\n", u8"將會被更改的設定:\n\n" }); // Frame Limit @@ -244,6 +242,11 @@ u8"使用開發者建議嘅偏好設定 (其他版嘅設定都會改埋)\n\n\ 最爽亦係最高負荷嘅設定" })); + OptionCheckbox(t({ "UseTexturePack", u8"高解像度テクスチャを使用" }), config::GdxUseTexturePack, t({ +"Use up-scaled textures.", +u8"ゲーム内で高品質なテクスチャを使用します. ", +})); + OptionCheckbox(t({ "Multi-threaded emulation", u8"マルチスレッドエミュレーション" }), config::ThreadedRendering, t({ R"(Run the emulated CPU and GPU on different threads. Can reduce the loading, but it may introduce a delay of 1 frame for input. @@ -374,7 +377,4 @@ u8"網絡對戰時顯示連線状態。\n按下Menu button可以切換顯示或 } // clang-format on -const char* gdxsv_gui_settings_text_for_preparing_font() -{ - return u8"檔嘅黑吓增開另幫你"; -} +const char* gdxsv_gui_settings_text_for_preparing_font() { return u8"檔嘅黑吓增開另幫你"; } diff --git a/core/gdxsv/gdxsv_key_display.cpp b/core/gdxsv/gdxsv_key_display.cpp index 2582c7b965..db9bb42737 100644 --- a/core/gdxsv/gdxsv_key_display.cpp +++ b/core/gdxsv/gdxsv_key_display.cpp @@ -1,7 +1,7 @@ #include "gdxsv_key_display.h" #include "gdxsv.h" -#include "gdxsv_CustomTexture.h" +#include "gdxsv_custom_texture_source.h" #include "imgui/imgui.h" #include "rend/imgui_driver.h" #include "rend/transform_matrix.h" @@ -40,7 +40,7 @@ void GdxsvKeyDisplay::DisplayOSD() { if (buttonsTexture == ImTextureID{} || buttonsTexture != imguiDriver->getTexture(ButtonsTextureName)) { int w, h; - u8* imgData = gdx_custom_texture.LoadExtraTexture(ButtonsTextureName, false, w, h); + u8* imgData = gdxsv_embed_texture_source.LoadExtraTexture(ButtonsTextureName, false, w, h); if (imgData) { buttonsTexture = imguiDriver->updateTexture(ButtonsTextureName, imgData, w, h); @@ -49,7 +49,7 @@ void GdxsvKeyDisplay::DisplayOSD() { if (arrowsTexture == ImTextureID{} || arrowsTexture != imguiDriver->getTexture(ArrowsTextureName)) { int w, h; - u8* imgData = gdx_custom_texture.LoadExtraTexture(ArrowsTextureName, false, w, h); + u8* imgData = gdxsv_embed_texture_source.LoadExtraTexture(ArrowsTextureName, false, w, h); if (imgData) { arrowsTexture = imguiDriver->updateTexture(ArrowsTextureName, imgData, w, h); diff --git a/core/rend/CustomTexture.cpp b/core/rend/CustomTexture.cpp index 263a38a822..acf5a0cc15 100644 --- a/core/rend/CustomTexture.cpp +++ b/core/rend/CustomTexture.cpp @@ -32,6 +32,92 @@ #include CustomTexture custom_texture; +CustomTextureFolderSource custom_texture_folder_source; + +bool CustomTextureFolderSource::Init() +{ + if (!config::CustomTextures) + return false; + + if (!initialized) + { + initialized = true; + + std::string game_id = custom_texture.GetGameId(); + if (game_id.length() > 0) + { + textures_path = hostfs::getTextureLoadPath(game_id); + + if (!textures_path.empty()) + { + DIR *dir = flycast::opendir(textures_path.c_str()); + if (dir != nullptr) + { + NOTICE_LOG(RENDERER, "Found custom textures directory: %s", textures_path.c_str()); + flycast::closedir(dir); + } + else + { + textures_path.clear(); + } + } + } + } + + return !textures_path.empty(); +} + +bool CustomTextureFolderSource::LoadMap() +{ + texture_map.clear(); + if (!textures_path.empty()) + { + hostfs::DirectoryTree tree(textures_path); + for (const hostfs::FileInfo& item : tree) + { + std::string extension = get_file_extension(item.name); + if (extension != "jpg" && extension != "jpeg" && extension != "png") + continue; + std::string::size_type dotpos = item.name.find_last_of('.'); + std::string basename = item.name.substr(0, dotpos); + char *endptr; + u32 hash = (u32)strtoll(basename.c_str(), &endptr, 16); + if (endptr - basename.c_str() < (ptrdiff_t)basename.length()) + { + INFO_LOG(RENDERER, "Invalid hash %s", basename.c_str()); + continue; + } + texture_map[hash] = item.path; + } + } + + return !texture_map.empty(); +} + +u8* CustomTextureFolderSource::LoadCustomTexture(u32 hash, int& width, int& height) +{ + const auto it = texture_map.find(hash); + if (it == texture_map.end()) + return nullptr; + + FILE *file = nowide::fopen(it->second.c_str(), "rb"); + if (file == nullptr) + return nullptr; + + int n; + stbi_set_flip_vertically_on_load(1); + u8 *imgData = stbi_load_from_file(file, &width, &height, &n, STBI_rgb_alpha); + std::fclose(file); + + return imgData; +} + +void CustomTextureFolderSource::Terminate() +{ + initialized = false; + textures_path.clear(); + texture_map.clear(); +} void CustomTexture::LoaderThread() { @@ -100,24 +186,17 @@ bool CustomTexture::Init() if (!initialized) { initialized = true; - std::string game_id = GetGameId(); - if (game_id.length() > 0) - { - textures_path = hostfs::getTextureLoadPath(game_id); + custom_textures_available = false; - if (!textures_path.empty()) - { - DIR *dir = flycast::opendir(textures_path.c_str()); - if (dir != nullptr) - { - NOTICE_LOG(RENDERER, "Found custom textures directory: %s", textures_path.c_str()); - custom_textures_available = true; - flycast::closedir(dir); - loader_thread.Start(); - } - } + for (const auto source : sources) { + custom_textures_available |= source->Init(); + } + + if (custom_textures_available) { + loader_thread.Start(); } } + return custom_textures_available; } @@ -132,24 +211,21 @@ void CustomTexture::Terminate() } wakeup_thread.Set(); loader_thread.WaitToEnd(); - texture_map.clear(); + + for (const auto source : sources) { + source->Terminate(); + } } } u8* CustomTexture::LoadCustomTexture(u32 hash, int& width, int& height) { - auto it = texture_map.find(hash); - if (it == texture_map.end()) - return nullptr; - - FILE *file = nowide::fopen(it->second.c_str(), "rb"); - if (file == nullptr) - return nullptr; - int n; - stbi_set_flip_vertically_on_load(1); - u8 *imgData = stbi_load_from_file(file, &width, &height, &n, STBI_rgb_alpha); - std::fclose(file); - return imgData; + for (const auto source : sources) { + u8* imgData = source->LoadCustomTexture(hash, width, height); + if (imgData != nullptr) + return imgData; + } + return nullptr; } void CustomTexture::LoadCustomTextureAsync(BaseTextureCacheData *texture_data) @@ -287,23 +363,11 @@ void CustomTexture::DumpTexture(u32 hash, int w, int h, TextureType textype, voi void CustomTexture::LoadMap() { - texture_map.clear(); - hostfs::DirectoryTree tree(textures_path); - for (const hostfs::FileInfo& item : tree) - { - std::string extension = get_file_extension(item.name); - if (extension != "jpg" && extension != "jpeg" && extension != "png") - continue; - std::string::size_type dotpos = item.name.find_last_of('.'); - std::string basename = item.name.substr(0, dotpos); - char *endptr; - u32 hash = (u32)strtoll(basename.c_str(), &endptr, 16); - if (endptr - basename.c_str() < (ptrdiff_t)basename.length()) - { - INFO_LOG(RENDERER, "Invalid hash %s", basename.c_str()); - continue; - } - texture_map[hash] = item.path; + bool loaded = false; + + for (const auto source : sources) { + loaded |= source->LoadMap(); } - custom_textures_available = !texture_map.empty(); + + custom_textures_available = loaded; } diff --git a/core/rend/CustomTexture.h b/core/rend/CustomTexture.h index dcc069e692..9879db8b08 100644 --- a/core/rend/CustomTexture.h +++ b/core/rend/CustomTexture.h @@ -26,31 +26,57 @@ #include #include +class ICustomTextureSource { +public: + virtual ~ICustomTextureSource() = default; + virtual bool Init() = 0; + virtual bool LoadMap() = 0; + virtual u8* LoadCustomTexture(u32 hash, int& width, int& height) = 0; + virtual void Terminate() = 0; +}; + +class CustomTextureFolderSource : public ICustomTextureSource { +public: + bool Init() override; + bool LoadMap() override; + u8* LoadCustomTexture(u32 hash, int& width, int& height) override; + void Terminate() override; + +private: + bool initialized = false; + std::string textures_path; + std::map texture_map; +}; + +extern CustomTextureFolderSource custom_texture_folder_source; + class CustomTexture { public: - CustomTexture() : loader_thread(loader_thread_func, this) {} + CustomTexture() : loader_thread(loader_thread_func, this) { + sources.push_back(&custom_texture_folder_source); + } ~CustomTexture() { Terminate(); } - virtual u8* LoadCustomTexture(u32 hash, int& width, int& height); + u8* LoadCustomTexture(u32 hash, int& width, int& height); void LoadCustomTextureAsync(BaseTextureCacheData *texture_data); void DumpTexture(u32 hash, int w, int h, TextureType textype, void *src_buffer); void Terminate(); - -protected: - virtual bool Init(); - virtual void LoaderThread(); + void AddTextureSource(ICustomTextureSource* source) { sources.push_back(source); } std::string GetGameId(); - virtual void LoadMap(); + +private: + bool Init(); + void LoaderThread(); + void LoadMap(); static void *loader_thread_func(void *param) { ((CustomTexture *)param)->LoaderThread(); return NULL; } bool initialized = false; bool custom_textures_available = false; - std::string textures_path; cThread loader_thread; cResetEvent wakeup_thread; std::vector work_queue; std::mutex work_queue_mutex; - std::map texture_map; + std::vector sources; }; extern CustomTexture custom_texture; diff --git a/core/rend/TexCache.cpp b/core/rend/TexCache.cpp index 4ce95b9903..31d57ba2aa 100644 --- a/core/rend/TexCache.cpp +++ b/core/rend/TexCache.cpp @@ -1,6 +1,5 @@ #include "TexCache.h" #include "CustomTexture.h" -#include "../gdxsv/gdxsv_CustomTexture.h" #include "deps/xbrz/xbrz.h" #include "hw/pvr/pvr_mem.h" #include "hw/mem/addrspace.h" @@ -643,11 +642,7 @@ bool BaseTextureCacheData::Update() return false; } } -#if defined(__APPLE__) || defined(_WIN32) - if (config::GdxLanguage != 3) // 3 = Disabled - gdx_custom_texture.LoadCustomTextureAsync(this); -#endif - if (config::CustomTextures) + if (config::CustomTextures || settings.gdxsv.disk) custom_texture.LoadCustomTextureAsync(this); void *temp_tex_buffer = NULL;