diff --git a/artifacts/ddraw.ini b/artifacts/ddraw.ini index 0620d724..e667ae17 100644 --- a/artifacts/ddraw.ini +++ b/artifacts/ddraw.ini @@ -110,7 +110,7 @@ FadeMultiplier=100 ;XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX [Interface] ;Set to 1 to expand the number of action points displayed on the interface bar -;Requires new IFACE_E.frm and HR_IFACE_E.frm files in art\intrface\ (included in sfall.dat) to display correctly +;Requires new iface_apbar_e.frm in art\intrface\ (included in sfall.dat) to display correctly ;The minimum supported version of the hi-res patch is v4.1.8 ActionPointsBar=0 diff --git a/artifacts/sfall_dat/dat_en/art/intrface/iface_apbar_e.frm b/artifacts/sfall_dat/dat_en/art/intrface/iface_apbar_e.frm new file mode 100644 index 00000000..43da42de Binary files /dev/null and b/artifacts/sfall_dat/dat_en/art/intrface/iface_apbar_e.frm differ diff --git a/sfall/FalloutEngine/VariableOffsets.h b/sfall/FalloutEngine/VariableOffsets.h index b1f8d8ff..c08d4477 100644 --- a/sfall/FalloutEngine/VariableOffsets.h +++ b/sfall/FalloutEngine/VariableOffsets.h @@ -165,6 +165,7 @@ #define FO_VAR_idle_func 0x51E234 #define FO_VAR_In_WorldMap 0x672E1C #define FO_VAR_info_line 0x5707D0 +#define FO_VAR_interfaceBuffer 0x59D3F4 #define FO_VAR_interfaceWindow 0x519024 #define FO_VAR_intface_full_width 0x56FB4C #define FO_VAR_intfaceEnabled 0x518F10 diff --git a/sfall/FalloutEngine/Variables_def.h b/sfall/FalloutEngine/Variables_def.h index 823da7df..9ebfb074 100644 --- a/sfall/FalloutEngine/Variables_def.h +++ b/sfall/FalloutEngine/Variables_def.h @@ -102,6 +102,7 @@ VAR_(i_worn, fo::GameObject*) VAR_(idle_func, void*) VAR_(In_WorldMap, DWORD) // moving on WorldMap VAR_(info_line, DWORD) +VAR_(interfaceBuffer, BYTE*) VARC(interfaceWindow, DWORD) VAR_(intfaceEnabled, DWORD) VAR_(intotal, DWORD) diff --git a/sfall/HRP/InterfaceBar.cpp b/sfall/HRP/InterfaceBar.cpp index 8e1f2bba..acdd9b51 100644 --- a/sfall/HRP/InterfaceBar.cpp +++ b/sfall/HRP/InterfaceBar.cpp @@ -36,7 +36,6 @@ static long yPosition; static long xOffset; static long xyOffsetCBtn; static long xyOffsetAP; -static bool expandAPBar = false; long IFaceBar::display_width = 0; // width of the area for text output char* IFaceBar::display_string_buf = (char*)FO_VAR_display_string_buf; @@ -202,7 +201,7 @@ static long __cdecl InterfaceArt(BYTE* scr, long w, long h, long srcWidth, BYTE* fo::var::itemButtonRect.offx += xOffset; char file[33]; - std::sprintf(file, "HR_IFACE_%i%s.frm", IFaceBar::IFACE_BAR_WIDTH, ((expandAPBar) ? "E" : "")); + std::sprintf(file, "HR_IFACE_%i.frm", IFaceBar::IFACE_BAR_WIDTH); fo::FrmFile* frm = sf::LoadUnlistedFrmCached(file, fo::ArtType::OBJ_TYPE_INTRFACE); if (frm != nullptr && frm->frameData[0].width == IFaceBar::IFACE_BAR_WIDTH) { @@ -214,7 +213,7 @@ static long __cdecl InterfaceArt(BYTE* scr, long w, long h, long srcWidth, BYTE* } // no required file, use the default one provided by HRP - if (frm == nullptr) frm = sf::LoadUnlistedFrmCached(((expandAPBar) ? "HR_IFACE_800E.frm" : "HR_IFACE_800.frm"), fo::ArtType::OBJ_TYPE_INTRFACE); + if (frm == nullptr) frm = sf::LoadUnlistedFrmCached("HR_IFACE_800.frm", fo::ArtType::OBJ_TYPE_INTRFACE); if (frm != nullptr) { // scale the 800px wide interface to the width of IFACE_BAR_WIDTH @@ -466,10 +465,6 @@ static void __declspec(naked) intface_update_ammo_lights_hook() { //////////////////////////////////////////////////////////////////////////////// -void IFaceBar::SetExpandAPBar() { - expandAPBar = true; -} - void IFaceBar::Hide() { InterfaceHide(fo::var::getInt(FO_VAR_interfaceWindow)); } diff --git a/sfall/HRP/InterfaceBar.h b/sfall/HRP/InterfaceBar.h index 095231b8..591f8459 100644 --- a/sfall/HRP/InterfaceBar.h +++ b/sfall/HRP/InterfaceBar.h @@ -25,8 +25,6 @@ public: static long display_width; static char* display_string_buf; - static void SetExpandAPBar(); - static void Hide(); static void Show(); }; diff --git a/sfall/Modules/ExtraArt.cpp b/sfall/Modules/ExtraArt.cpp index 20024b88..4ad22d1f 100644 --- a/sfall/Modules/ExtraArt.cpp +++ b/sfall/Modules/ExtraArt.cpp @@ -41,15 +41,25 @@ static PcxFile LoadPcxFile(const char* file) { return pcx; } +static fo::FrmFile* LoadFrmFile(const char* file) { + fo::FrmFile* frmPtr = nullptr; + if (fo::func::load_frame(file, &frmPtr)) { + frmPtr = nullptr; + } + return frmPtr; +} + +void UnloadFrmFile(fo::FrmFile* frm) { + fo::func::mem_free(frm); +} + fo::FrmFile* LoadFrmFileCached(const char* file) { fo::FrmFile* frmPtr = nullptr; auto cacheHit = frmFileCache.find(file); if (cacheHit != frmFileCache.end()) { frmPtr = cacheHit->second; } else { - if (fo::func::load_frame(file, &frmPtr)) { - frmPtr = nullptr; - } + frmPtr = LoadFrmFile(file); frmFileCache.emplace(file, frmPtr); } return frmPtr; @@ -64,7 +74,6 @@ PcxFile LoadPcxFileCached(const char* file) { } static void GetUnlistedFrmPath(const char* frmName, unsigned int folderRef, bool useLanguage, char* pathBuf) { - const char* artfolder = fo::var::art[folderRef].path; // address of art type name if (useLanguage) { sprintf_s(pathBuf, MAX_PATH, "art\\%s\\%s\\%s", (const char*)fo::var::language, artfolder, frmName); @@ -73,10 +82,9 @@ static void GetUnlistedFrmPath(const char* frmName, unsigned int folderRef, bool } } -bool UnlistedFrmExists(const char* frmName, unsigned int folderRef) { +static bool CheckUnlistedFrm(const char* frmName, unsigned int folderRef, char* frmPath) { if (folderRef > fo::OBJ_TYPE_SKILLDEX) return nullptr; - char frmPath[MAX_PATH]; GetUnlistedFrmPath(frmName, folderRef, fo::var::use_language != 0, frmPath); bool exists = fo::func::db_access(frmPath); @@ -87,19 +95,21 @@ bool UnlistedFrmExists(const char* frmName, unsigned int folderRef) { return exists; } -fo::FrmFile* LoadUnlistedFrmCached(const char* frmName, unsigned int folderRef) { - if (folderRef > fo::OBJ_TYPE_SKILLDEX) return nullptr; - +bool UnlistedFrmExists(const char* frmName, unsigned int folderRef) { char frmPath[MAX_PATH]; + return CheckUnlistedFrm(frmName, folderRef, frmPath); +} - GetUnlistedFrmPath(frmName, folderRef, fo::var::use_language != 0, frmPath); +fo::FrmFile* LoadUnlistedFrm(const char* frmName, unsigned int folderRef) { + char frmPath[MAX_PATH]; + if (!CheckUnlistedFrm(frmName, folderRef, frmPath)) return nullptr; + return LoadFrmFile(frmPath); +} - fo::FrmFile* frm = LoadFrmFileCached(frmPath); - if (frm == nullptr && fo::var::use_language) { - GetUnlistedFrmPath(frmName, folderRef, false, frmPath); - frm = LoadFrmFileCached(frmPath); - } - return frm; +fo::FrmFile* LoadUnlistedFrmCached(const char* frmName, unsigned int folderRef) { + char frmPath[MAX_PATH]; + if (!CheckUnlistedFrm(frmName, folderRef, frmPath)) return nullptr; + return LoadFrmFileCached(frmPath); } static void ClearInterfaceArtCache() { @@ -109,11 +119,33 @@ static void ClearInterfaceArtCache() { pcxFileCache.clear(); for (auto &pair : frmFileCache) { - fo::func::mem_free(pair.second); + UnloadFrmFile(pair.second); } frmFileCache.clear(); } +TempFrmHandle::TempFrmHandle(fo::FrmFile* frm) : _frm(frm) { +} + +TempFrmHandle::TempFrmHandle(TempFrmHandle&& other) : _frm(other._frm) { + other._frm = nullptr; +} + +TempFrmHandle::~TempFrmHandle() { + if (_frm == nullptr) return; + UnloadFrmFile(_frm); + _frm = nullptr; +} + +bool TempFrmHandle::IsValid() { + return _frm != nullptr; +} + +const fo::FrmFile& TempFrmHandle::Frm() const { + assert(_frm != nullptr); + return *_frm; +} + void ExtraArt::init() { LoadGameHook::OnGameReset() += []() { ClearInterfaceArtCache(); diff --git a/sfall/Modules/ExtraArt.h b/sfall/Modules/ExtraArt.h index 6f6a68c7..1f81b23c 100644 --- a/sfall/Modules/ExtraArt.h +++ b/sfall/Modules/ExtraArt.h @@ -36,16 +36,33 @@ struct PcxFile { PcxFile() : pixelData(nullptr), width(0), height(0) {} }; +class TempFrmHandle { +public: + TempFrmHandle(fo::FrmFile* frm); + TempFrmHandle(const TempFrmHandle&) = delete; + TempFrmHandle(TempFrmHandle&&); + TempFrmHandle& operator=(TempFrmHandle) = delete; + ~TempFrmHandle(); + + bool IsValid(); + const fo::FrmFile& Frm() const; +private: + fo::FrmFile* _frm; +}; + class ExtraArt : public Module { public: const char* name() { return "ExtraArt"; } void init(); }; +// TODO: more robust caching, similar to how art_ptr_lock works. fo::FrmFile* LoadFrmFileCached(const char* file); PcxFile LoadPcxFileCached(const char* file); bool UnlistedFrmExists(const char* frmName, unsigned int folderRef); +fo::FrmFile* LoadUnlistedFrm(const char* frmName, unsigned int folderRef); fo::FrmFile* LoadUnlistedFrmCached(const char* file, unsigned int folderRef); +void UnloadFrmFile(fo::FrmFile* frm); } diff --git a/sfall/Modules/Interface.cpp b/sfall/Modules/Interface.cpp index 551bb60a..51e95f4e 100644 --- a/sfall/Modules/Interface.cpp +++ b/sfall/Modules/Interface.cpp @@ -126,25 +126,29 @@ struct InterfaceCustomFrm { }; static BYTE movePointBackground[16 * 9 * 5]; -static InterfaceCustomFrm ifaceFrm{ "IFACE_E.frm" }; -static void* LoadIfaceFrm() { - return ifaceFrm.LoadFrmData(); +static void DrawExtendedApBar() { + const char* const ifaceApBarFrm = "iface_apbar_e.frm"; // 183x13 at 266,10 (x = width - 374) + TempFrmHandle frmHandle{ LoadUnlistedFrm(ifaceApBarFrm, fo::ArtType::OBJ_TYPE_INTRFACE) }; + if (!frmHandle.IsValid()) return; + + DWORD ifaceWin = fo::var::interfaceWindow; + fo::Window* win = fo::func::GNW_find(ifaceWin); + if (win == nullptr) return; + + constexpr int destOffsetRight = 374; + constexpr int destOffsetTop = 10; + const fo::FrmFrameData& srcFrame = frmHandle.Frm().frameData[0]; + BYTE* dest = fo::var::interfaceBuffer + (win->width * (destOffsetTop + 1)) - destOffsetRight; + fo::func::buf_to_buf((BYTE*)srcFrame.data, srcFrame.width, srcFrame.height, srcFrame.width, dest, win->width); } -static void __declspec(naked) intface_init_hook_lock() { +static void __declspec(naked) intface_init_hook_unlock_iface_frm() { __asm { pushadc; - call LoadIfaceFrm; - test eax, eax; - jz skip; - pop ecx; - add esp, 8; - mov dword ptr [ecx], 0; - retn; -skip: + call DrawExtendedApBar; popadc; - jmp fo::funcoffs::art_ptr_lock_data_; + jmp fo::funcoffs::art_ptr_unlock_; } } @@ -175,14 +179,10 @@ static void APBarRectPatch() { } static void ActionPointsBarPatch() { - HRP::IFaceBar::SetExpandAPBar(); - dlog("Applying expanded action points bar patch.", DL_INIT); if (HRP::Setting::ExternalEnabled()) { // check valid data - if (HRP::Setting::VersionIsValid && !_stricmp((const char*)HRP::Setting::GetAddress(HRP_VAR_HR_IFACE_FRM_STR), "HR_IFACE_%i.frm")) { - SafeWriteStr(HRP::Setting::GetAddress(HRP_VAR_HR_IFACE_FRM_STR + 11), "E.frm"); // patching HRP - } else { + if (!HRP::Setting::VersionIsValid) { dlogr(" Incorrect HRP version!", DL_INIT); return; } @@ -190,13 +190,14 @@ static void ActionPointsBarPatch() { } else { APBarRectPatch(); } + // intface_init_ SafeWriteBatch((DWORD)&movePointBackground, {0x45E343, 0x45EE3F}); // intface_update_move_points_ SafeWriteBatch(16, {0x45EE55, 0x45EE7B, 0x45EE82, 0x45EE9C, 0x45EEA0}); SafeWriteBatch(9276 - (54 / 2), {0x45EE33, 0x45EEC8, 0x45EF16}); - HookCall(0x45D918, intface_init_hook_lock); + HookCall(0x45D962, intface_init_hook_unlock_iface_frm); MakeCall(0x45E356, intface_init_hack); MakeJump(0x45EE38, intface_update_move_points_hack, 1); dlogr(" Done", DL_INIT); @@ -1412,7 +1413,6 @@ void Interface::init() { ExpandedInventoryPatch(); }; LoadGameHook::OnGameReset() += []() { - ifaceFrm.Reset(); barterTallFrm.Reset(); tradeTallFrm.Reset(); for (size_t i = 0; i < inventoryTallFrms.size(); ++i) {