diff --git a/res/extra_thorns.png b/res/extra_thorns.png new file mode 100644 index 0000000..62dc2b5 Binary files /dev/null and b/res/extra_thorns.png differ diff --git a/res/playlunky64.rc b/res/playlunky64.rc index 096b779..11d0fd7 100644 --- a/res/playlunky64.rc +++ b/res/playlunky64.rc @@ -3,3 +3,4 @@ ENTITIES_JSON JSON_FILE "entities.json" TEXTURES_JSON JSON_FILE "textures.json" PET_HEADS PNG_FILE "pet_heads.png" +EXTRA_THORNS PNG_FILE "extra_thorns.png" diff --git a/res/resource_playlunky64.h b/res/resource_playlunky64.h index 5ae34f4..b091107 100644 --- a/res/resource_playlunky64.h +++ b/res/resource_playlunky64.h @@ -19,3 +19,4 @@ #define TEXTURES_JSON 103 #define PNG_FILE 104 #define PET_HEADS 105 +#define EXTRA_THORNS 106 diff --git a/source/playlunky/mod/bug_fixes.cpp b/source/playlunky/mod/bug_fixes.cpp new file mode 100644 index 0000000..a594880 --- /dev/null +++ b/source/playlunky/mod/bug_fixes.cpp @@ -0,0 +1,137 @@ +#include "bug_fixes.h" + +#include "../../res/resource_playlunky64.h" +#include "dds_conversion.h" +#include "log.h" +#include "playlunky_settings.h" +#include "util/image.h" +#include "util/on_scope_exit.h" +#include "virtual_filesystem.h" + +#include "detour/sigscan.h" + +#include +#include +#include + +std::int64_t g_ExtraThornsTextureId{}; +using SetupThorns = void(Entity*); +SetupThorns* g_SetupThornsTrampoline{ nullptr }; +using GetNeighbouringThorns = uint32_t(Entity* thorns, float offset_x, float offset_y); +GetNeighbouringThorns* g_GetNeighbouringThorns{ nullptr }; + +bool InitBugFixes(VirtualFilesystem& /*vfs*/, + const PlaylunkySettings& settings, + const std::filesystem::path& db_folder, + const std::filesystem::path& original_data_folder) +{ + namespace fs = std::filesystem; + + // Extract extra thorns + if (settings.GetBool("bug_fixes", "missing_thorns", true)) + { + const auto extra_thorns_path = original_data_folder / "Data/Textures/extra_thorns.png"; + if (!fs::exists(extra_thorns_path)) + { + auto acquire_png_resource = [](LPSTR resource) + { + HMODULE this_module = GetModuleHandle("playlunky64.dll"); + if (HRSRC png_resource = FindResource(this_module, resource, MAKEINTRESOURCE(PNG_FILE))) + { + if (HGLOBAL png_data = LoadResource(this_module, png_resource)) + { + DWORD png_size = SizeofResource(this_module, png_resource); + return std::pair{ png_data, std::span{ (std::uint8_t*)LockResource(png_data), png_size } }; + } + } + return std::pair{ HGLOBAL{ NULL }, std::span{} }; + }; + auto [extra_thorns_res, extra_thorns_png] = acquire_png_resource(MAKEINTRESOURCE(EXTRA_THORNS)); + OnScopeExit release_resources{ [extra_thorns_res] + { + UnlockResource(extra_thorns_res); + } }; + + Image extra_thorns; + extra_thorns.Load(extra_thorns_png); + extra_thorns.Write(extra_thorns_path); + extra_thorns.Write("Mods/Extracted/Data/Textures/extra_thorns.png"); + + const auto extra_thorns_dds_path = db_folder / "Data/Textures/extra_thorns.DDS"; + if (!ConvertRBGAToDds(extra_thorns.GetData(), extra_thorns.GetWidth(), extra_thorns.GetHeight(), extra_thorns_dds_path)) + { + return false; + } + } + + Spelunky_TextureDefinition extra_thorns_texture_def{ + "Data/Textures/extra_thorns.DDS", + 128 * 3, + 128 * 2, + 128, + 128, + }; + g_ExtraThornsTextureId = Spelunky_DefineTexture(extra_thorns_texture_def); + + static constexpr auto decode_call = [](void* addr) -> void* { + return (char*)addr + (*(int32_t*)((char*)addr + 1)) + 5; + }; + + constexpr std::string_view g_SetupThornsFuncPattern{ "\x34\x01\xc0\xe0\x03\x08\xc8" }; + g_SetupThornsTrampoline = static_cast(SigScan::FindFunctionStart(SigScan::FindPattern("Spel2.exe", g_SetupThornsFuncPattern, true))); + g_GetNeighbouringThorns = static_cast(decode_call(SigScan::FindPattern("\xe8", g_SetupThornsTrampoline, (char*)g_SetupThornsTrampoline + 0x1000))); + + DetourTransactionBegin(); + DetourUpdateThread(GetCurrentThread()); + + static constexpr auto c_SetupMissingThorns = [](Entity* thorns) + { + g_SetupThornsTrampoline(thorns); + const auto current_texture_tile = SpelunkyEntity_GetTextureTile(thorns); + if (current_texture_tile == 127) + { + const auto left = g_GetNeighbouringThorns(thorns, 0.0f, 1.0f) & 0x1; + const auto right = g_GetNeighbouringThorns(thorns, 0.0f, -1.0f) & 0x1; + const auto top = g_GetNeighbouringThorns(thorns, 1.0f, 0.0f) & 0x1; + const auto bottom = g_GetNeighbouringThorns(thorns, -1.0f, 0.0f) & 0x1; + //const auto mask = top | (bottom << 1) | (left << 2) | (right << 3); + const auto mask = left | (right << 1) | (top << 2) | (bottom << 3); + const auto texture_tile = [mask]() -> uint16_t + { + switch (mask) + { + case 0b0000: + return 0; + case 0b1111: + return 3; + case 0b0111: + return 1; + case 0b1011: + return 2; + case 0b1101: + return 4; + case 0b1110: + return 5; + default: + LogError("Thorns have missing textures but valid setup... why?"); + return 127; + } + }(); + if (texture_tile != 127) + { + SpelunkyEntity_SetTexture(thorns, g_ExtraThornsTextureId); + SpelunkyEntity_SetTextureTile(thorns, texture_tile); + } + } + }; + DetourAttach((void**)&g_SetupThornsTrampoline, (SetupThorns*)c_SetupMissingThorns); + + const LONG error = DetourTransactionCommit(); + if (error != NO_ERROR) + { + LogError("Could not fix thorns textures: {}", error); + } + } + + return true; +} diff --git a/source/playlunky/mod/bug_fixes.h b/source/playlunky/mod/bug_fixes.h new file mode 100644 index 0000000..198fec7 --- /dev/null +++ b/source/playlunky/mod/bug_fixes.h @@ -0,0 +1,12 @@ +#pragma once + +#include +#include + +class PlaylunkySettings; +class VirtualFilesystem; + +bool InitBugFixes(VirtualFilesystem& vfs, + const PlaylunkySettings& settings, + const std::filesystem::path& db_folder, + const std::filesystem::path& original_data_folder); diff --git a/source/playlunky/mod/mod_manager.cpp b/source/playlunky/mod/mod_manager.cpp index 947df26..3f0f5a6 100644 --- a/source/playlunky/mod/mod_manager.cpp +++ b/source/playlunky/mod/mod_manager.cpp @@ -1,5 +1,6 @@ #include "mod_manager.h" +#include "bug_fixes.h" #include "cache_audio_file.h" #include "dds_conversion.h" #include "decode_audio_file.h" @@ -44,7 +45,7 @@ static constexpr ctll::fixed_string s_StringModFileRule{ "strings([0-9]{2})_mod\ ModManager::ModManager(std::string_view mods_root, const PlaylunkySettings& settings, VirtualFilesystem& vfs) : mSpriteSheetMerger{ new SpriteSheetMerger{ settings } } - , mVfs{ &vfs } + , mVfs{ vfs } , mModsRoot{ mods_root } , mDeveloperMode{ settings.GetBool("settings", "enable_developer_mode", false) || settings.GetBool("script_settings", "enable_developer_mode", false) } , mConsoleMode{ settings.GetBool("script_settings", "enable_developer_console", false) } @@ -689,12 +690,9 @@ ModManager::ModManager(std::string_view mods_root, const PlaylunkySettings& sett Spelunky_RegisterOnLoadFileFunc(FunctionPointer( [this](const char* file_path, SpelunkyAllocFun alloc_fun) -> SpelunkyFileInfo* { - if (mVfs) + if (auto* file_info = mVfs.LoadFile(file_path, alloc_fun)) { - if (auto* file_info = mVfs->LoadFile(file_path, alloc_fun)) - { - return file_info; - } + return file_info; } return nullptr; })); @@ -814,15 +812,18 @@ ModManager::~ModManager() void ModManager::PostGameInit(const class PlaylunkySettings& settings) { + const auto db_folder = mModsRoot / ".db"; + const auto db_original_folder = db_folder / "Original"; + if (mSpritePainter) { LogInfo("Setting up sprite painting..."); - const auto db_folder = mModsRoot / ".db"; - const auto db_original_folder = db_folder / "Original"; mSpritePainter->FinalizeSetup(db_original_folder, db_folder); } - PatchCharacterDefinitions(*mVfs, settings); + PatchCharacterDefinitions(mVfs, settings); + + InitBugFixes(mVfs, settings, db_folder, db_original_folder); Spelunky_InitSoundManager([](const char* file_path) { @@ -884,7 +885,7 @@ bool ModManager::OnInput(std::uint32_t msg, std::uint64_t w_param, std::int64_t } void ModManager::Update() { - if (mSpritePainter || (mSpriteHotLoader && mVfs)) + if (mSpritePainter || mSpriteHotLoader) { const auto db_folder = mModsRoot / ".db"; const auto db_original_folder = db_folder / "Original"; @@ -893,9 +894,9 @@ void ModManager::Update() mSpritePainter->Update(db_original_folder, db_folder); } - if (mSpriteHotLoader && mVfs) + if (mSpriteHotLoader) { - mSpriteHotLoader->Update(db_original_folder, db_folder, *mVfs); + mSpriteHotLoader->Update(db_original_folder, db_folder, mVfs); } } diff --git a/source/playlunky/mod/mod_manager.h b/source/playlunky/mod/mod_manager.h index 8e8b6df..232a62d 100644 --- a/source/playlunky/mod/mod_manager.h +++ b/source/playlunky/mod/mod_manager.h @@ -36,7 +36,7 @@ class ModManager std::unique_ptr mSpritePainter; std::unique_ptr mSpriteSheetMerger; ScriptManager mScriptManager; - VirtualFilesystem* mVfs; + VirtualFilesystem& mVfs; std::filesystem::path mModsRoot; diff --git a/source/playlunky/playlunky_settings.cpp b/source/playlunky/playlunky_settings.cpp index d94446e..c762790 100644 --- a/source/playlunky/playlunky_settings.cpp +++ b/source/playlunky/playlunky_settings.cpp @@ -60,6 +60,9 @@ PlaylunkySettings::PlaylunkySettings(std::string settings_file) KnownSetting{ .Name{ "enable_customizable_sheets" }, .DefaultValue{ "on" }, .Comment{ "Enables the customizable sprite sheets feature, does not work in speedrun mode" } }, KnownSetting{ .Name{ "enable_luminance_scaling" }, .DefaultValue{ "on" }, .Comment{ "Scales luminance of customized images based on the color" } }, } }, + KnownCategory{ { "bug_fixes" }, { + KnownSetting{ .Name{ "missing_thorns" }, .DefaultValue{ "on" }, .Comment{ "Adds textures for the missing jungle thorns configurations" } }, + } }, KnownCategory{ { "key_bindings" }, { KnownSetting{ .Name{ "console" }, .DefaultValue{ "0xc0" }, .Comment{ "Default 0xc0 == ~ for US" } }, KnownSetting{ .Name{ "console_alt" }, .DefaultValue{ "0xdc" }, .Comment{ "Default 0xdc == \\ for US" } }, diff --git a/submodules/overlunky b/submodules/overlunky index 4551073..089926f 160000 --- a/submodules/overlunky +++ b/submodules/overlunky @@ -1 +1 @@ -Subproject commit 455107314311b5b0c499aca26b51e9df035bfe1c +Subproject commit 089926fedf5812f1e0c4adddb075ffe36c2481ba