From 31b100894f0fc5a57cfeba23df598a3e256908f7 Mon Sep 17 00:00:00 2001 From: "Skyth (Asilkan)" <19259897+blueskythlikesclouds@users.noreply.github.com> Date: Sat, 7 Dec 2024 16:50:07 +0300 Subject: [PATCH] Option for PS controller icons. (#21) * Initial PS button implementation. * Add controller buttons as config option & handle loading screen. * Rename "Controller Buttons" to "Controller Icons". --- UnleashedRecomp/CMakeLists.txt | 3 + UnleashedRecomp/gpu/video.cpp | 46 +++++- UnleashedRecomp/gpu/video.h | 1 + UnleashedRecomp/locale/config_locale.h | 16 +++ UnleashedRecomp/patches/resident_patches.cpp | 17 +++ UnleashedRecomp/ui/options_menu.cpp | 1 + UnleashedRecomp/user/config.h | 1 + UnleashedRecomp/user/config_detail.h | 12 ++ UnleashedRecompLib/config/SWA.toml | 6 +- UnleashedRecompResources | 2 +- tools/CMakeLists.txt | 1 + tools/bc_diff/CMakeLists.txt | 10 ++ tools/bc_diff/bc_diff.cpp | 142 +++++++++++++++++++ tools/bc_diff/bc_diff.h | 23 +++ 14 files changed, 277 insertions(+), 4 deletions(-) create mode 100644 tools/bc_diff/CMakeLists.txt create mode 100644 tools/bc_diff/bc_diff.cpp create mode 100644 tools/bc_diff/bc_diff.h diff --git a/UnleashedRecomp/CMakeLists.txt b/UnleashedRecomp/CMakeLists.txt index d547896..23c75bb 100644 --- a/UnleashedRecomp/CMakeLists.txt +++ b/UnleashedRecomp/CMakeLists.txt @@ -146,6 +146,7 @@ set(LIBMSPACK_C_SOURCES set_source_files_properties(${LIBMSPACK_C_SOURCES} PROPERTIES SKIP_PRECOMPILE_HEADERS ON) set(SMOLV_SOURCE_DIR "${SWA_THIRDPARTY_ROOT}/ShaderRecomp/thirdparty/smol-v/source") +set(BC_DIFF_SOURCE_DIR "${SWA_TOOLS_ROOT}/bc_diff") set(SWA_USER_CXX_SOURCES "user/achievement_data.cpp" @@ -253,6 +254,7 @@ target_include_directories(UnleashedRecomp PRIVATE ${Stb_INCLUDE_DIR} ${SMOLV_SOURCE_DIR} ${MINIAUDIO_INCLUDE_DIRS} + ${BC_DIFF_SOURCE_DIR} ) target_precompile_headers(UnleashedRecomp PUBLIC ${SWA_PRECOMPILED_HEADERS}) @@ -323,6 +325,7 @@ generate_aggregate_header( set(RESOURCES_SOURCE_PATH "${PROJECT_SOURCE_DIR}/../UnleashedRecompResources") set(RESOURCES_OUTPUT_PATH "${PROJECT_SOURCE_DIR}/res") +BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/bc_diff/button_bc_diff.bin" DEST_FILE "${RESOURCES_OUTPUT_PATH}/bc_diff/button_bc_diff.bin" ARRAY_NAME "g_button_bc_diff" COMPRESSION_TYPE "zstd") BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/font/im_font_atlas.bin" DEST_FILE "${RESOURCES_OUTPUT_PATH}/font/im_font_atlas.bin" ARRAY_NAME "g_im_font_atlas" COMPRESSION_TYPE "zstd") BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/font/im_font_atlas.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/font/im_font_atlas.dds" ARRAY_NAME "g_im_font_atlas_texture" COMPRESSION_TYPE "zstd") BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/achievements_menu/trophy.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/achievements_menu/trophy.dds" ARRAY_NAME "g_trophy" COMPRESSION_TYPE "zstd") diff --git a/UnleashedRecomp/gpu/video.cpp b/UnleashedRecomp/gpu/video.cpp index 24b786f..24970e2 100644 --- a/UnleashedRecomp/gpu/video.cpp +++ b/UnleashedRecomp/gpu/video.cpp @@ -25,7 +25,9 @@ #include #include +#include #include +#include #include @@ -482,6 +484,9 @@ static void DestructTempResources() g_textureDescriptorAllocator.free(texture->descriptorIndex); + if (texture->patchedTexture != nullptr) + g_textureDescriptorAllocator.free(texture->patchedTexture->descriptorIndex); + texture->~GuestTexture(); break; } @@ -559,8 +564,9 @@ static void FlushBarriers() } static std::unique_ptr g_shaderCache; +static std::unique_ptr g_buttonBcDiff; -static void LoadShaderCache() +static void LoadEmbeddedResources() { const size_t decompressedSize = g_vulkan ? g_spirvCacheDecompressedSize : g_dxilCacheDecompressedSize; g_shaderCache = std::make_unique(decompressedSize); @@ -569,6 +575,8 @@ static void LoadShaderCache() decompressedSize, g_vulkan ? g_compressedSpirvCache : g_compressedDxilCache, g_vulkan ? g_spirvCacheCompressedSize : g_dxilCacheCompressedSize); + + g_buttonBcDiff = decompressZstd(g_button_bc_diff, g_button_bc_diff_uncompressed_size); } enum class RenderCommandType @@ -1243,7 +1251,7 @@ void Video::CreateHostDevice() g_vulkan = DetectWine() || Config::GraphicsAPI == EGraphicsAPI::Vulkan; - LoadShaderCache(); + LoadEmbeddedResources(); g_interface = g_vulkan ? CreateVulkanInterface() : CreateD3D12Interface(); g_device = g_interface->createDevice(); @@ -2606,6 +2614,9 @@ static void ProcSetViewport(const RenderCommand& cmd) static void SetTexture(GuestDevice* device, uint32_t index, GuestTexture* texture) { + if (Config::ControllerIcons == EControllerIcons::PlayStation && texture != nullptr && texture->patchedTexture != nullptr) + texture = texture->patchedTexture.get(); + RenderCommand cmd; cmd.type = RenderCommandType::SetTexture; cmd.setTexture.index = index; @@ -4380,6 +4391,34 @@ std::unique_ptr LoadTexture(const uint8_t* data, size_t dataSize, return nullptr; } +static void DiffPatchTexture(GuestTexture& texture, uint8_t* data, uint32_t dataSize) +{ + auto header = reinterpret_cast(g_buttonBcDiff.get()); + auto entries = reinterpret_cast(g_buttonBcDiff.get() + header->entriesOffset); + auto end = entries + header->entryCount; + + XXH64_hash_t hash = XXH3_64bits(data, dataSize); + auto findResult = std::lower_bound(entries, end, hash, [](BlockCompressionDiffPatchEntry& lhs, XXH64_hash_t rhs) + { + return lhs.hash < rhs; + }); + + if (findResult != end && findResult->hash == hash) + { + auto patch = reinterpret_cast(g_buttonBcDiff.get() + findResult->patchesOffset); + for (size_t i = 0; i < findResult->patchCount; i++) + { + assert(patch->destinationOffset + patch->patchBytesSize <= dataSize); + memcpy(data + patch->destinationOffset, g_buttonBcDiff.get() + patch->patchBytesOffset, patch->patchBytesSize); + ++patch; + } + + GuestTexture patchedTexture(ResourceType::Texture); + if (LoadTexture(patchedTexture, data, dataSize, {})) + texture.patchedTexture = std::make_unique(std::move(patchedTexture)); + } +} + static void MakePictureData(GuestPictureData* pictureData, uint8_t* data, uint32_t dataSize) { if ((pictureData->flags & 0x1) == 0 && data != nullptr) @@ -4391,6 +4430,9 @@ static void MakePictureData(GuestPictureData* pictureData, uint8_t* data, uint32 #ifdef _DEBUG texture.texture->setName(reinterpret_cast(g_memory.Translate(pictureData->name + 2))); #endif + + DiffPatchTexture(texture, data, dataSize); + pictureData->texture = g_memory.MapVirtual(g_userHeap.AllocPhysical(std::move(texture))); pictureData->type = 0; } diff --git a/UnleashedRecomp/gpu/video.h b/UnleashedRecomp/gpu/video.h index bb636b2..0c3f564 100644 --- a/UnleashedRecomp/gpu/video.h +++ b/UnleashedRecomp/gpu/video.h @@ -145,6 +145,7 @@ struct GuestTexture : GuestBaseTexture RenderTextureViewDimension viewDimension = RenderTextureViewDimension::UNKNOWN; void* mappedMemory = nullptr; std::unique_ptr framebuffer; + std::unique_ptr patchedTexture; }; struct GuestLockedRect diff --git a/UnleashedRecomp/locale/config_locale.h b/UnleashedRecomp/locale/config_locale.h index 5ac29f1..86fced1 100644 --- a/UnleashedRecomp/locale/config_locale.h +++ b/UnleashedRecomp/locale/config_locale.h @@ -131,6 +131,22 @@ CONFIG_DEFINE_ENUM_LOCALE(ETimeOfDayTransition) } }; +CONFIG_DEFINE_LOCALE(ControllerIcons) +{ + { ELanguage::English, { "Controller Icons", "[PLACEHOLDER]" } } +}; + +CONFIG_DEFINE_ENUM_LOCALE(EControllerIcons) +{ + { + ELanguage::English, + { + { EControllerIcons::Xbox, { "XBOX", "[PLACEHOLDER]" } }, + { EControllerIcons::PlayStation, { "PLAYSTATION", "[PLACEHOLDER]" } } + } + } +}; + CONFIG_DEFINE_LOCALE(SkipIntroLogos) { { ELanguage::English, { "Skip Intro Logos", "Skip the logos during the game's boot sequence.\n\n[TO BE REMOVED]" } } diff --git a/UnleashedRecomp/patches/resident_patches.cpp b/UnleashedRecomp/patches/resident_patches.cpp index 94efda9..52f09a6 100644 --- a/UnleashedRecomp/patches/resident_patches.cpp +++ b/UnleashedRecomp/patches/resident_patches.cpp @@ -101,3 +101,20 @@ PPC_FUNC(sub_82BD06C8) { ctx.r3.u64 = 0; } + +void LoadingScreenControllerMidAsmHook() +{ + static constexpr size_t STR_ADDRESSES[] = + { + 0x820301AC, // 360_sonic1 + 0x820301B8, // 360_sonic2 + 0x820301C4, // 360_sonic3 + 0x820301D0, // 360_evil + 0x820301DC, // 360_robo + 0x820301E8, // 360_super + }; + + const char* prefix = Config::ControllerIcons == EControllerIcons::PlayStation ? "ps3" : "360"; + for (auto address : STR_ADDRESSES) + memcpy(g_memory.Translate(address), prefix, 3); +} diff --git a/UnleashedRecomp/ui/options_menu.cpp b/UnleashedRecomp/ui/options_menu.cpp index 198805b..8c7799d 100644 --- a/UnleashedRecomp/ui/options_menu.cpp +++ b/UnleashedRecomp/ui/options_menu.cpp @@ -790,6 +790,7 @@ static void DrawConfigOptions() DrawConfigOption(rowCount++, yOffset, &Config::InvertCameraX, true); DrawConfigOption(rowCount++, yOffset, &Config::InvertCameraY, true); DrawConfigOption(rowCount++, yOffset, &Config::AllowBackgroundInput, true); + DrawConfigOption(rowCount++, yOffset, &Config::ControllerIcons, true); break; case 2: // AUDIO DrawConfigOption(rowCount++, yOffset, &Config::MusicVolume, true); diff --git a/UnleashedRecomp/user/config.h b/UnleashedRecomp/user/config.h index 5757da3..44a14e6 100644 --- a/UnleashedRecomp/user/config.h +++ b/UnleashedRecomp/user/config.h @@ -24,6 +24,7 @@ public: CONFIG_DEFINE_LOCALISED("Input", bool, XButtonHoming, true); CONFIG_DEFINE_LOCALISED("Input", bool, AllowCancellingUnleash, false); CONFIG_DEFINE_LOCALISED("Input", bool, AllowBackgroundInput, false); + CONFIG_DEFINE_ENUM_LOCALISED("Input", EControllerIcons, ControllerIcons, EControllerIcons::Xbox); CONFIG_DEFINE_LOCALISED("Audio", float, MusicVolume, 1.0f); CONFIG_DEFINE_LOCALISED("Audio", float, EffectsVolume, 1.0f); diff --git a/UnleashedRecomp/user/config_detail.h b/UnleashedRecomp/user/config_detail.h index 9cff098..4966f68 100644 --- a/UnleashedRecomp/user/config_detail.h +++ b/UnleashedRecomp/user/config_detail.h @@ -67,6 +67,18 @@ CONFIG_DEFINE_ENUM_TEMPLATE(ETimeOfDayTransition) { "PlayStation", ETimeOfDayTransition::PlayStation } }; +enum class EControllerIcons : uint32_t +{ + Xbox, + PlayStation +}; + +CONFIG_DEFINE_ENUM_TEMPLATE(EControllerIcons) +{ + { "Xbox", EControllerIcons::Xbox }, + { "PlayStation", EControllerIcons::PlayStation } +}; + enum class EVoiceLanguage : uint32_t { English, diff --git a/UnleashedRecompLib/config/SWA.toml b/UnleashedRecompLib/config/SWA.toml index 74203c0..99b60fc 100644 --- a/UnleashedRecompLib/config/SWA.toml +++ b/UnleashedRecompLib/config/SWA.toml @@ -553,4 +553,8 @@ jump_address = 0x825854FC name = "TitleMenuAddInstallOptionMidAsmHook" address = 0x8258547C registers = ["r3"] -jump_address = 0x82585480 \ No newline at end of file +jump_address = 0x82585480 + +[[midasm_hook]] +name = "LoadingScreenControllerMidAsmHook" +address = 0x824DC9D4 diff --git a/UnleashedRecompResources b/UnleashedRecompResources index d45116b..bcad34e 160000 --- a/UnleashedRecompResources +++ b/UnleashedRecompResources @@ -1 +1 @@ -Subproject commit d45116bb0b5c5075da9b3c166fdf73af2415e880 +Subproject commit bcad34ee648f69c448b204cb38337e7ef2e0aa18 diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt index d732343..20168ed 100644 --- a/tools/CMakeLists.txt +++ b/tools/CMakeLists.txt @@ -1 +1,2 @@ add_subdirectory(${SWA_TOOLS_ROOT}/file_to_c) +add_subdirectory(${SWA_TOOLS_ROOT}/bc_diff) diff --git a/tools/bc_diff/CMakeLists.txt b/tools/bc_diff/CMakeLists.txt new file mode 100644 index 0000000..5bba302 --- /dev/null +++ b/tools/bc_diff/CMakeLists.txt @@ -0,0 +1,10 @@ +cmake_minimum_required(VERSION 3.20) + +project("bc_diff") +set(CMAKE_CXX_STANDARD 17) + +add_executable(bc_diff "bc_diff.cpp") +add_compile_definitions(bc_diff PRIVATE _CRT_SECURE_NO_WARNINGS) + +find_package(xxHash CONFIG REQUIRED) +target_link_libraries(bc_diff PRIVATE xxHash::xxhash) diff --git a/tools/bc_diff/bc_diff.cpp b/tools/bc_diff/bc_diff.cpp new file mode 100644 index 0000000..f6e6753 --- /dev/null +++ b/tools/bc_diff/bc_diff.cpp @@ -0,0 +1,142 @@ +#include "bc_diff.h" +#include +#include +#include +#include + +static std::vector readAllBytes(const char* filePath) +{ + FILE* file = fopen(filePath, "rb"); + + if (!file) + return {}; + + fseek(file, 0, SEEK_END); + + long fileSize = ftell(file); + fseek(file, 0, SEEK_SET); + + std::vector data(fileSize); + fread(data.data(), 1, fileSize, file); + + fclose(file); + + return data; +} + +int main(int argc, char** argv) +{ + if (argc != 4) + { + printf("Usage: %s [old directory] [new directory] [destination file]", argv[0]); + return 0; + } + + // Debug configuration doesn't compile without this??? + assert(argc == 4); + + std::filesystem::path oldDirectoryPath = argv[1]; + std::filesystem::path newDirectoryPath = argv[2]; + + std::vector entries; + std::vector patches; + std::vector patchBytes; + + for (auto& oldFile : std::filesystem::recursive_directory_iterator(oldDirectoryPath)) + { + auto newFile = newDirectoryPath / std::filesystem::relative(oldFile, oldDirectoryPath); + if (!std::filesystem::exists(newFile)) + { + fprintf(stderr, "Cannot locate %s\n", newFile.string().c_str()); + continue; + } + + auto oldFileData = readAllBytes(oldFile.path().string().c_str()); + auto newFileData = readAllBytes(newFile.string().c_str()); + + constexpr size_t BC_STRIDE = 8; + + if (oldFileData.size() != newFileData.size()) + { + fprintf(stderr, "%s does not match %s in file size\n", oldFile.path().string().c_str(), newFile.string().c_str()); + continue; + } + + if ((oldFileData.size() % BC_STRIDE) != 0) + { + fprintf(stderr, "%s is not aligned to %d bytes\n", oldFile.path().string().c_str(), BC_STRIDE); + continue; + } + + if (oldFileData.size() >= BC_STRIDE) + { + size_t patchIndex = patches.size(); + + for (size_t i = 0; i < oldFileData.size() - BC_STRIDE + 1; i += BC_STRIDE) + { + if (memcmp(&oldFileData[i], &newFileData[i], BC_STRIDE) == 0) + continue; + + size_t patchBytesOffset = patchBytes.size(); + patchBytes.insert(patchBytes.end(), newFileData.begin() + i, newFileData.begin() + i + BC_STRIDE); + + if (patchIndex >= patches.size() || ((patches.back().destinationOffset + patches.back().patchBytesSize) != i)) + { + auto& patch = patches.emplace_back(); + patch.destinationOffset = i; + patch.patchBytesOffset = patchBytesOffset; + patch.patchBytesSize = BC_STRIDE; + } + else + { + patches.back().patchBytesSize += BC_STRIDE; + } + } + + size_t patchCount = patches.size() - patchIndex; + if (patchCount != 0) + { + auto& entry = entries.emplace_back(); + entry.hash = XXH3_64bits(oldFileData.data(), oldFileData.size()); + entry.patchesOffset = patchIndex * sizeof(BlockCompressionDiffPatch); + entry.patchCount = patchCount; + + printf("Generated BC patch for %s\n", oldFile.path().string().c_str()); + } + else + { + printf("Skipping %s, files are identical\n", oldFile.path().string().c_str()); + } + } + } + + std::sort(entries.begin(), entries.end(), [](auto& lhs, auto& rhs) { return lhs.hash < rhs.hash; }); + + BlockCompressionDiffPatchHeader header; + header.entriesOffset = sizeof(BlockCompressionDiffPatchHeader); + header.entryCount = entries.size(); + + size_t patchesOffset = header.entriesOffset + sizeof(BlockCompressionDiffPatchEntry) * entries.size(); + size_t patchBytesOffset = patchesOffset + sizeof(BlockCompressionDiffPatch) * patches.size(); + + for (auto& entry : entries) + entry.patchesOffset += patchesOffset; + + for (auto& patch : patches) + patch.patchBytesOffset += patchBytesOffset; + + FILE* file = fopen(argv[3], "wb"); + if (!file) + { + fprintf(stderr, "Cannot open %s for writing\n", argv[3]); + return 1; + } + + fwrite(&header, sizeof(header), 1, file); + fwrite(entries.data(), sizeof(BlockCompressionDiffPatchEntry), entries.size(), file); + fwrite(patches.data(), sizeof(BlockCompressionDiffPatch), patches.size(), file); + fwrite(patchBytes.data(), 1, patchBytes.size(), file); + fclose(file); + + return 0; +} diff --git a/tools/bc_diff/bc_diff.h b/tools/bc_diff/bc_diff.h new file mode 100644 index 0000000..6130ad9 --- /dev/null +++ b/tools/bc_diff/bc_diff.h @@ -0,0 +1,23 @@ +#pragma once + +#include + +struct BlockCompressionDiffPatch +{ + uint32_t destinationOffset; + uint32_t patchBytesOffset; + uint32_t patchBytesSize; +}; + +struct BlockCompressionDiffPatchEntry +{ + uint64_t hash; + uint32_t patchesOffset; + uint32_t patchCount; +}; + +struct BlockCompressionDiffPatchHeader +{ + uint32_t entriesOffset; + uint32_t entryCount; +};