mirror of
https://github.com/sfall-team/sfall.git
synced 2026-07-27 16:52:34 -07:00
933 lines
22 KiB
C++
933 lines
22 KiB
C++
/*
|
|
* sfall
|
|
* Copyright (C) 2008-2023 The sfall team
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "..\main.h"
|
|
#include "..\FalloutEngine\Fallout2.h"
|
|
#include "..\InputFuncs.h"
|
|
#include "..\Logging.h"
|
|
#include "..\Translate.h"
|
|
#include "..\version.h"
|
|
#include "AI.h"
|
|
#include "Animations.h"
|
|
#include "BugFixes.h"
|
|
#include "BarBoxes.h"
|
|
#include "Combat.h"
|
|
#include "Console.h"
|
|
#include "Criticals.h"
|
|
#include "DebugEditor.h"
|
|
#include "Explosions.h"
|
|
#include "ExtraSaveSlots.h"
|
|
#include "FileSystem.h"
|
|
#include "Graphics.h"
|
|
#include "HeroAppearance.h"
|
|
#include "HookScripts\Common.h"
|
|
#include "HookScripts\MiscHs.h"
|
|
#include "Interface.h"
|
|
#include "Inventory.h"
|
|
#include "LoadOrder.h"
|
|
#include "Message.h"
|
|
#include "MetaruleExtender.h"
|
|
#include "MiscPatches.h"
|
|
#include "Movies.h"
|
|
#include "Objects.h"
|
|
#include "PartyControl.h"
|
|
#include "Perks.h"
|
|
#include "QuestList.h"
|
|
#include "ScriptExtender.h"
|
|
#include "Scripting\Arrays.h"
|
|
#include "ScriptShaders.h"
|
|
#include "Skills.h"
|
|
#include "Sound.h"
|
|
#include "Stats.h"
|
|
#include "SubModules\ObjectName.h"
|
|
#include "TalkingHeads.h"
|
|
#include "Worldmap.h"
|
|
|
|
#include "LoadGameHook.h"
|
|
|
|
namespace sfall
|
|
{
|
|
|
|
#define _InLoop2(type, flag) __asm { \
|
|
__asm push flag \
|
|
__asm push type \
|
|
__asm call SetInLoop \
|
|
}
|
|
#define _InLoop(type, flag) __asm { \
|
|
pushadc \
|
|
_InLoop2(type, flag) \
|
|
popadc \
|
|
}
|
|
|
|
static DWORD inLoop = 0;
|
|
static DWORD saveInCombatFix;
|
|
static bool disableHorrigan = false;
|
|
static bool pipBoyAvailableAtGameStart = false;
|
|
static bool gameLoaded = false;
|
|
static bool onLoadingMap = false;
|
|
|
|
char LoadGameHook::mapLoadingName[16]; // current loading/loaded map name
|
|
|
|
long LoadGameHook::interfaceWID = -1;
|
|
|
|
bool LoadGameHook::IsMapLoading() {
|
|
return onLoadingMap;
|
|
}
|
|
|
|
// True if game was started, false when on the main menu
|
|
bool IsGameLoaded() {
|
|
return gameLoaded;
|
|
}
|
|
|
|
DWORD InWorldMap() {
|
|
return (inLoop & WORLDMAP) ? 1 : 0;
|
|
}
|
|
|
|
DWORD InCombat() {
|
|
return (inLoop & COMBAT) ? 1 : 0;
|
|
}
|
|
|
|
DWORD InDialog() {
|
|
return (inLoop & DIALOG) ? 1 : 0;
|
|
}
|
|
|
|
DWORD GetLoopFlags() {
|
|
return inLoop;
|
|
}
|
|
|
|
void SetLoopFlag(LoopFlag flag) {
|
|
inLoop |= flag;
|
|
}
|
|
|
|
void ClearLoopFlag(LoopFlag flag) {
|
|
inLoop &= ~flag;
|
|
}
|
|
|
|
static void __stdcall GameModeChange(DWORD state) { // OnGameModeChange
|
|
HookCommon::GameModeChangeHook(state);
|
|
}
|
|
|
|
void __stdcall SetInLoop(DWORD mode, LoopFlag flag) {
|
|
unsigned long _inLoop = inLoop;
|
|
if (mode) {
|
|
SetLoopFlag(flag);
|
|
} else {
|
|
ClearLoopFlag(flag);
|
|
}
|
|
if (_inLoop ^ inLoop) GameModeChange(0);
|
|
}
|
|
|
|
static void __stdcall RunOnBeforeGameStart() {
|
|
Criticals::ApplyCritTable();
|
|
ReadExtraGameMsgFiles();
|
|
if (pipBoyAvailableAtGameStart) fo::ptr::gmovie_played_list[3] = true; // PipBoy aquiring video
|
|
Combat::OnBeforeGameStart();
|
|
LoadGlobalScripts(); // loading sfall scripts
|
|
}
|
|
|
|
static void __stdcall RunOnAfterGameStarted() {
|
|
if (femaleMsgs) CheckPlayerGender();
|
|
if (disableHorrigan) *fo::ptr::Meet_Frank_Horrigan = true;
|
|
SourceUseSkillOnInit();
|
|
InitGlobalScripts(); // running sfall scripts
|
|
}
|
|
|
|
void GetSavePath(char* buf, char* ftype) {
|
|
sprintf(buf, "%s\\savegame\\slot%.2d\\sfall%s.sav", *fo::ptr::patches, ExtraSaveSlots::GetSaveSlot() + 1, ftype); //add SuperSave Page offset
|
|
}
|
|
|
|
static void __stdcall SaveGame2() {
|
|
char buf[MAX_PATH];
|
|
GetSavePath(buf, "gv");
|
|
|
|
dlog_f("Saving game: %s\n", DL_MAIN, buf);
|
|
|
|
DWORD size, data;
|
|
HANDLE h = CreateFileA(buf, GENERIC_WRITE, 0, 0, CREATE_ALWAYS, 0, 0);
|
|
if (h != INVALID_HANDLE_VALUE) {
|
|
SaveGlobals(h);
|
|
WriteFile(h, &Objects::uniqueID, 4, &size, 0); // save unique id counter
|
|
data = Worldmap::GetAddedYears(false) << 16; // save to high bytes (short)
|
|
WriteFile(h, &data, 4, &size, 0);
|
|
Perks::Save(h);
|
|
script::SaveArrays(h);
|
|
BugFixes::DrugsSaveFix(h);
|
|
CloseHandle(h);
|
|
} else {
|
|
goto errorSave;
|
|
}
|
|
|
|
GetSavePath(buf, "db");
|
|
h = CreateFileA(buf, GENERIC_WRITE, 0, 0, CREATE_ALWAYS, 0, 0);
|
|
if (h != INVALID_HANDLE_VALUE) {
|
|
Worldmap::SaveData(h);
|
|
data = 0;
|
|
WriteFile(h, &data, 8, &size, 0); // padding bytes for compatibility with 4.x
|
|
|
|
// last marker
|
|
data = 0xCC | VERSION_MAJOR << 8 | VERSION_MINOR << 16 | VERSION_BUILD << 24;
|
|
WriteFile(h, &data, 4, &size, 0);
|
|
CloseHandle(h);
|
|
} else {
|
|
goto errorSave;
|
|
}
|
|
|
|
if (FileSystem::IsEmpty()) return;
|
|
GetSavePath(buf, "fs");
|
|
h = CreateFileA(buf, GENERIC_WRITE, 0, 0, CREATE_ALWAYS, 0, 0);
|
|
if (h != INVALID_HANDLE_VALUE) {
|
|
FileSystem::Save(h);
|
|
CloseHandle(h);
|
|
} else {
|
|
goto errorSave;
|
|
}
|
|
return;
|
|
|
|
/////////////////////////////////////////////////
|
|
errorSave:
|
|
dlog_f("ERROR creating: %s\n", DL_MAIN, buf);
|
|
fo::func::display_print(Translate::SfallSaveDataFailure());
|
|
fo::func::gsound_play_sfx_file("IISXXXX1");
|
|
}
|
|
|
|
static DWORD __stdcall CombatSaveTest() {
|
|
if (!saveInCombatFix && !PartyControl::IsNpcControlled()) return 1;
|
|
if (inLoop & COMBAT) {
|
|
if (saveInCombatFix == 2 || PartyControl::IsNpcControlled() || !(inLoop & PCOMBAT)) {
|
|
fo::func::display_print(Translate::CombatSaveBlockMessage());
|
|
return 0;
|
|
}
|
|
int ap = fo::func::stat_level(*fo::ptr::obj_dude, fo::STAT_max_move_points);
|
|
int bonusmove = fo::func::perk_level(*fo::ptr::obj_dude, fo::PERK_bonus_move);
|
|
if ((*fo::ptr::obj_dude)->critter.movePoints != ap || bonusmove * 2 != *fo::ptr::combat_free_move) {
|
|
fo::func::display_print(Translate::CombatSaveBlockMessage());
|
|
return 0;
|
|
}
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
static void __declspec(naked) SaveGame_hook() {
|
|
__asm {
|
|
push ecx;
|
|
push edx;
|
|
push eax; // save Mode parameter
|
|
call CombatSaveTest;
|
|
test eax, eax;
|
|
pop edx; // recall Mode parameter (pop eax)
|
|
jz end;
|
|
push edx;
|
|
_InLoop2(1, SAVEGAME);
|
|
pop eax;
|
|
call fo::funcoffs::SaveGame_;
|
|
push eax;
|
|
_InLoop2(0, SAVEGAME);
|
|
pop eax;
|
|
cmp eax, 1;
|
|
jne end;
|
|
call SaveGame2; // save sfall.sav
|
|
mov eax, 1;
|
|
end:
|
|
pop edx;
|
|
pop ecx;
|
|
retn;
|
|
}
|
|
}
|
|
|
|
// Called right before savegame slot is being loaded
|
|
static bool LoadGame_Before() {
|
|
RunOnBeforeGameStart();
|
|
|
|
char buf[MAX_PATH];
|
|
GetSavePath(buf, "gv");
|
|
|
|
dlog_f("Loading save game: %s\n", DL_MAIN, buf);
|
|
|
|
HANDLE h = CreateFileA(buf, GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, 0, 0);
|
|
if (h != INVALID_HANDLE_VALUE) {
|
|
DWORD size, data;
|
|
if (LoadGlobals(h)) goto errorLoad;
|
|
long uID = 0;
|
|
ReadFile(h, &uID, 4, &size, 0);
|
|
if (uID > UniqueID::Start) Objects::uniqueID = uID;
|
|
ReadFile(h, &data, 4, &size, 0);
|
|
Worldmap::SetAddedYears(data >> 16);
|
|
if (size != 4 || !Perks::Load(h)) goto errorLoad;
|
|
long result = script::LoadArrays(h); // 1 - old save, -1 - broken save
|
|
if (result == -1 || (!result && BugFixes::DrugsLoadFix(h))) goto errorLoad;
|
|
CloseHandle(h);
|
|
} else {
|
|
dlogr("Cannot open sfallgv.sav - assuming non-sfall save.", DL_MAIN);
|
|
}
|
|
|
|
GetSavePath(buf, "db");
|
|
dlogr("Loading data from sfalldb.sav...", DL_MAIN);
|
|
h = CreateFileA(buf, GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, 0, 0);
|
|
if (h != INVALID_HANDLE_VALUE) {
|
|
if (Worldmap::LoadData(h)) goto errorLoad;
|
|
CloseHandle(h);
|
|
} else {
|
|
dlogr("Cannot open sfalldb.sav.", DL_MAIN);
|
|
}
|
|
return false;
|
|
|
|
/////////////////////////////////////////////////
|
|
errorLoad:
|
|
CloseHandle(h);
|
|
dlog_f("ERROR reading data: %s\n", DL_MAIN, buf);
|
|
fo::func::debug_printf("\n[SFALL] ERROR reading data: %s", buf);
|
|
return (true & !isDebug);
|
|
}
|
|
|
|
// called whenever game is being reset (prior to loading a save or when returning to main menu)
|
|
static bool __stdcall GameReset(DWORD isGameLoad) {
|
|
if (gameLoaded) { // prevent resetting when a new game has not been started (loading saved game from main menu)
|
|
// OnGameReset
|
|
BugFixes::OnGameLoad();
|
|
if (Graphics::mode >= 4) {
|
|
Graphics::ForceGraphicsRefresh(0); // disable refresh
|
|
ScriptShaders::OnGameLoad();
|
|
}
|
|
FileSystem::Reset();
|
|
LoadOrder::OnGameLoad();
|
|
Animations::OnGameLoad();
|
|
BarBoxes::OnGameLoad();
|
|
Explosions::OnGameLoad();
|
|
ClearScriptAddedExtraGameMsg();
|
|
Interface::OnGameLoad();
|
|
Worldmap::OnGameLoad();
|
|
Sound::OnGameLoad();
|
|
MiscPatches::OnGameLoad();
|
|
Inventory::Reset();
|
|
Objects::OnGameLoad();
|
|
Stats::OnGameLoad();
|
|
Perks::Reset();
|
|
Skills::OnGameLoad();
|
|
PartyControl::OnGameLoad();
|
|
Combat::OnGameLoad();
|
|
QuestList::ResetQuests();
|
|
Console::OnGameLoad();
|
|
MetaruleExtender::Reset();
|
|
ScriptExtender::OnGameLoad();
|
|
if (isDebug) {
|
|
char* str = (isGameLoad) ? "on Load" : "on Exit";
|
|
fo::func::debug_printf("\nSFALL: [State reset %s]\n", str);
|
|
}
|
|
}
|
|
inLoop = 0;
|
|
gameLoaded = false;
|
|
|
|
return (isGameLoad) ? LoadGame_Before() : false;
|
|
}
|
|
|
|
// Called after game was loaded from a save
|
|
static void __stdcall LoadGame_After() {
|
|
RunOnAfterGameStarted();
|
|
gameLoaded = true;
|
|
}
|
|
|
|
static void __declspec(naked) LoadGame_hook() {
|
|
__asm {
|
|
_InLoop(1, LOADGAME);
|
|
call fo::funcoffs::LoadGame_;
|
|
_InLoop(0, LOADGAME);
|
|
cmp eax, 1;
|
|
jne end;
|
|
// Invoked
|
|
push ecx;
|
|
push edx;
|
|
call LoadGame_After;
|
|
mov eax, 1;
|
|
pop edx;
|
|
pop ecx;
|
|
end:
|
|
retn;
|
|
}
|
|
}
|
|
|
|
static void __declspec(naked) EndLoadHook() {
|
|
__asm {
|
|
call fo::funcoffs::EndLoad_;
|
|
pushadc;
|
|
call LoadHeroAppearance;
|
|
popadc;
|
|
retn;
|
|
}
|
|
}
|
|
|
|
static void __stdcall NewGame_Before() {
|
|
RunOnBeforeGameStart();
|
|
}
|
|
|
|
static void __stdcall NewGame_After() {
|
|
// OnAfterNewGame
|
|
if (HeroAppearance::appModEnabled) {
|
|
SetNewCharAppearanceGlobals();
|
|
LoadHeroAppearance();
|
|
}
|
|
RunOnAfterGameStarted();
|
|
gameLoaded = true;
|
|
dlogr("New Game started.", DL_MAIN);
|
|
}
|
|
|
|
static void __declspec(naked) main_load_new_hook() {
|
|
__asm {
|
|
push eax;
|
|
call NewGame_Before;
|
|
pop eax;
|
|
call fo::funcoffs::main_load_new_;
|
|
jmp NewGame_After;
|
|
}
|
|
}
|
|
|
|
static void __stdcall GameInitialization() { // OnBeforeGameInit
|
|
BugFixes::OnBeforeGameInit();
|
|
}
|
|
|
|
static void __stdcall game_init_hook() { // OnGameInit
|
|
FallbackEnglishLoadMsgFiles();
|
|
}
|
|
|
|
static void __stdcall GameInitialized(int initResult) { // OnAfterGameInit
|
|
#ifdef NDEBUG
|
|
if (!initResult) {
|
|
MessageBoxA(0, "Game initialization failed!", 0, MB_TASKMODAL | MB_ICONERROR);
|
|
return;
|
|
}
|
|
#endif
|
|
BugFixes::OnAfterGameInit();
|
|
LoadOrder::OnAfterGameInit();
|
|
BarBoxes::OnAfterGameInit();
|
|
Sound::OnAfterGameInit();
|
|
if (TalkingHeads::Use32Bit) TalkingHeads::OnAfterGameInit();
|
|
BuildSortedIndexList();
|
|
DebugEditor::OnAfterGameInit();
|
|
}
|
|
|
|
static void __stdcall GameExit() { // OnGameExit
|
|
if (femaleMsgs > 1) PlayerGenderCutsRestore();
|
|
}
|
|
|
|
static void __stdcall GameClose() { // OnBeforeGameClose
|
|
ClearReadExtraGameMsgFiles();
|
|
Sound::OnBeforeGameClose();
|
|
}
|
|
|
|
static void __stdcall MapLoadHook() { // OnBeforeMapLoad
|
|
ObjectName::Reset();
|
|
}
|
|
|
|
static void __declspec(naked) main_init_system_hook() {
|
|
__asm {
|
|
pushadc;
|
|
call GameInitialization;
|
|
popadc;
|
|
call fo::funcoffs::main_init_system_;
|
|
pushadc;
|
|
push eax;
|
|
call GameInitialized;
|
|
popadc;
|
|
retn;
|
|
}
|
|
}
|
|
|
|
static void __declspec(naked) game_reset_hook() {
|
|
__asm {
|
|
pushadc;
|
|
push 0;
|
|
call GameReset; // reset all sfall modules before resetting the game data
|
|
popadc;
|
|
jmp fo::funcoffs::game_reset_;
|
|
}
|
|
}
|
|
|
|
static void __declspec(naked) game_reset_on_load_hook() {
|
|
__asm {
|
|
pushadc;
|
|
push 1;
|
|
call GameReset; // reset all sfall modules before resetting the game data
|
|
test al, al;
|
|
popadc;
|
|
jnz errorLoad;
|
|
jmp fo::funcoffs::game_reset_;
|
|
errorLoad:
|
|
mov eax, -1;
|
|
add esp, 4;
|
|
pop edx;
|
|
retn;
|
|
}
|
|
}
|
|
|
|
static void __declspec(naked) before_game_exit_hook() {
|
|
__asm {
|
|
pushadc;
|
|
push 1;
|
|
call GameModeChange;
|
|
popadc;
|
|
jmp fo::funcoffs::map_exit_;
|
|
}
|
|
}
|
|
|
|
static void __declspec(naked) after_game_exit_hook() {
|
|
__asm {
|
|
pushadc;
|
|
call GameExit;
|
|
popadc;
|
|
jmp fo::funcoffs::main_menu_create_;
|
|
}
|
|
}
|
|
|
|
static void __declspec(naked) game_close_hook() {
|
|
__asm {
|
|
pushadc;
|
|
call GameClose;
|
|
popadc;
|
|
jmp fo::funcoffs::game_exit_;
|
|
}
|
|
}
|
|
|
|
|
|
static void __declspec(naked) map_load_hook() {
|
|
__asm {
|
|
mov esi, ebx;
|
|
lea edi, LoadGameHook::mapLoadingName;
|
|
mov ecx, 4;
|
|
rep movsd; // copy the name of the loaded map to mapLoadingName
|
|
mov onLoadingMap, 1;
|
|
push eax;
|
|
push edx;
|
|
call MapLoadHook;
|
|
pop edx;
|
|
pop eax;
|
|
call fo::funcoffs::map_load_file_;
|
|
mov onLoadingMap, 0;
|
|
retn;
|
|
}
|
|
}
|
|
|
|
static void __declspec(naked) WorldMapHook_Start() {
|
|
__asm {
|
|
call fo::funcoffs::wmInterfaceInit_;
|
|
test eax, eax;
|
|
jl skip;
|
|
push eax;
|
|
_InLoop2(1, WORLDMAP);
|
|
pop eax;
|
|
skip:
|
|
retn;
|
|
}
|
|
}
|
|
|
|
static void __declspec(naked) WorldMapHook_End() {
|
|
__asm {
|
|
push eax;
|
|
_InLoop2(0, WORLDMAP);
|
|
pop eax;
|
|
jmp fo::funcoffs::remove_bk_process_;
|
|
}
|
|
}
|
|
|
|
static void __fastcall CombatInternal(fo::CombatGcsd* gcsd) {
|
|
// OnCombatStart
|
|
AI::AICombatClear();
|
|
SetInLoop(1, COMBAT);
|
|
|
|
__asm mov eax, gcsd;
|
|
__asm call fo::funcoffs::combat_;
|
|
|
|
// OnCombatEnd
|
|
AI::AICombatClear();
|
|
PartyControl::OnCombatEnd();
|
|
SetInLoop(0, COMBAT);
|
|
}
|
|
|
|
static void __declspec(naked) CombatHook() {
|
|
__asm {
|
|
push ecx;
|
|
push edx;
|
|
mov ecx, eax;
|
|
call CombatInternal;
|
|
pop edx;
|
|
pop ecx;
|
|
retn;
|
|
}
|
|
}
|
|
|
|
static void __declspec(naked) PlayerCombatHook() {
|
|
__asm {
|
|
_InLoop(1, PCOMBAT);
|
|
call fo::funcoffs::combat_input_;
|
|
_InLoop(0, PCOMBAT);
|
|
retn;
|
|
}
|
|
}
|
|
|
|
static void __declspec(naked) EscMenuHook() {
|
|
__asm {
|
|
_InLoop(1, ESCMENU);
|
|
call fo::funcoffs::do_optionsFunc_;
|
|
_InLoop(0, ESCMENU);
|
|
retn;
|
|
}
|
|
}
|
|
|
|
static void __declspec(naked) EscMenuHook2() {
|
|
__asm {
|
|
_InLoop(1, ESCMENU);
|
|
call fo::funcoffs::do_options_;
|
|
_InLoop(0, ESCMENU);
|
|
retn;
|
|
}
|
|
}
|
|
|
|
static void __declspec(naked) OptionsMenuHook() {
|
|
__asm {
|
|
_InLoop(1, OPTIONS);
|
|
call fo::funcoffs::do_prefscreen_;
|
|
_InLoop(0, OPTIONS);
|
|
retn;
|
|
}
|
|
}
|
|
|
|
static void __declspec(naked) HelpMenuHook() {
|
|
__asm {
|
|
_InLoop(1, HELP);
|
|
call fo::funcoffs::game_help_;
|
|
_InLoop(0, HELP);
|
|
retn;
|
|
}
|
|
}
|
|
|
|
static void __declspec(naked) CharacterHook() {
|
|
__asm {
|
|
push edx;
|
|
_InLoop2(1, CHARSCREEN);
|
|
call PerksEnterCharScreen;
|
|
xor eax, eax;
|
|
call fo::funcoffs::editor_design_;
|
|
test eax, eax;
|
|
jz success;
|
|
call PerksCancelCharScreen;
|
|
jmp end;
|
|
success:
|
|
call PerksAcceptCharScreen;
|
|
end:
|
|
_InLoop2(0, CHARSCREEN);
|
|
mov tagSkill4LevelBase, -1; // for fixing Tag! perk exploit
|
|
pop edx;
|
|
retn;
|
|
}
|
|
}
|
|
|
|
static void __declspec(naked) DialogHook_Start() {
|
|
__asm {
|
|
_InLoop2(1, DIALOG);
|
|
mov ebx, 1;
|
|
retn;
|
|
}
|
|
}
|
|
|
|
static void __declspec(naked) DialogHook_End() {
|
|
__asm {
|
|
and inLoop, ~DIALOG; // unset flag
|
|
_InLoop2(1, SPECIAL); // set the flag before animating the panel when exiting the dialog
|
|
call fo::funcoffs::gdDestroyHeadWindow_;
|
|
_InLoop2(0, SPECIAL);
|
|
retn;
|
|
}
|
|
}
|
|
|
|
static void __declspec(naked) PipboyHook_Start() {
|
|
__asm {
|
|
push eax;
|
|
_InLoop2(1, PIPBOY);
|
|
pop eax;
|
|
jmp fo::funcoffs::win_draw_;
|
|
}
|
|
}
|
|
|
|
static void __declspec(naked) PipboyHook_End() {
|
|
__asm {
|
|
push eax;
|
|
_InLoop2(0, PIPBOY);
|
|
pop eax;
|
|
jmp fo::funcoffs::win_delete_;
|
|
}
|
|
}
|
|
|
|
static void __declspec(naked) SkilldexHook() {
|
|
__asm {
|
|
_InLoop(1, SKILLDEX);
|
|
call fo::funcoffs::skilldex_select_;
|
|
_InLoop(0, SKILLDEX);
|
|
retn;
|
|
}
|
|
}
|
|
|
|
static void __declspec(naked) HandleInventoryHook_Start() {
|
|
__asm {
|
|
_InLoop2(1, INVENTORY);
|
|
xor eax, eax;
|
|
jmp fo::funcoffs::inven_set_mouse_;
|
|
}
|
|
}
|
|
|
|
static void __declspec(naked) HandleInventoryHook_End() {
|
|
__asm {
|
|
_InLoop2(0, INVENTORY);
|
|
mov eax, esi;
|
|
jmp fo::funcoffs::exit_inventory_;
|
|
}
|
|
}
|
|
|
|
static void __declspec(naked) UseInventoryOnHook_Start() {
|
|
__asm {
|
|
_InLoop2(1, INTFACEUSE);
|
|
xor eax, eax;
|
|
jmp fo::funcoffs::inven_set_mouse_;
|
|
}
|
|
}
|
|
|
|
static void __declspec(naked) UseInventoryOnHook_End() {
|
|
__asm {
|
|
_InLoop2(0, INTFACEUSE);
|
|
mov eax, edi;
|
|
jmp fo::funcoffs::exit_inventory_;
|
|
}
|
|
}
|
|
|
|
static void __declspec(naked) LootContainerHook_Start() {
|
|
__asm {
|
|
_InLoop2(1, INTFACELOOT);
|
|
xor eax, eax;
|
|
jmp fo::funcoffs::inven_set_mouse_;
|
|
}
|
|
}
|
|
|
|
static void __declspec(naked) LootContainerHook_End() {
|
|
__asm {
|
|
cmp dword ptr [esp + 0x150 - 0x58 + 4], 0; // JESSE_CONTAINER
|
|
jz skip; // container is not created
|
|
_InLoop2(0, INTFACELOOT);
|
|
xor eax, eax;
|
|
skip:
|
|
call ResetBodyState; // reset object pointer used in calculating the weight/size of equipped and hidden items on NPCs after exiting loot/barter screens
|
|
retn 0x13C;
|
|
}
|
|
}
|
|
|
|
static void __declspec(naked) BarterInventoryHook() {
|
|
__asm {
|
|
and inLoop, ~SPECIAL; // unset flag after animating the dialog panel
|
|
_InLoop(1, BARTER);
|
|
push [esp + 4];
|
|
call fo::funcoffs::barter_inventory_;
|
|
_InLoop(0, BARTER);
|
|
call ResetBodyState;
|
|
retn 4;
|
|
}
|
|
}
|
|
|
|
static void __declspec(naked) AutomapHook_Start() {
|
|
__asm {
|
|
call fo::funcoffs::gmouse_set_cursor_;
|
|
test edx, edx;
|
|
jnz skip;
|
|
mov LoadGameHook::interfaceWID, ebp;
|
|
_InLoop(1, AUTOMAP);
|
|
skip:
|
|
retn;
|
|
}
|
|
}
|
|
|
|
static void __declspec(naked) AutomapHook_End() {
|
|
__asm {
|
|
_InLoop(0, AUTOMAP);
|
|
mov LoadGameHook::interfaceWID, -1
|
|
jmp fo::funcoffs::win_delete_;
|
|
}
|
|
}
|
|
|
|
static void __declspec(naked) DialogReviewInitHook() {
|
|
__asm {
|
|
call fo::funcoffs::gdReviewInit_;
|
|
test eax, eax;
|
|
jnz error;
|
|
push ecx;
|
|
_InLoop2(1, DIALOGVIEW);
|
|
pop ecx;
|
|
xor eax, eax;
|
|
error:
|
|
retn;
|
|
}
|
|
}
|
|
|
|
static void __declspec(naked) DialogReviewExitHook() {
|
|
__asm {
|
|
push ecx;
|
|
push eax;
|
|
_InLoop2(0, DIALOGVIEW);
|
|
pop eax;
|
|
pop ecx;
|
|
jmp fo::funcoffs::gdReviewExit_;
|
|
}
|
|
}
|
|
|
|
static void __declspec(naked) setup_move_timer_win_Hook() {
|
|
__asm {
|
|
_InLoop2(1, COUNTERWIN);
|
|
jmp fo::funcoffs::text_curr_;
|
|
}
|
|
}
|
|
|
|
static void __declspec(naked) exit_move_timer_win_Hook() {
|
|
__asm {
|
|
push eax;
|
|
_InLoop2(0, COUNTERWIN);
|
|
pop eax;
|
|
jmp fo::funcoffs::win_delete_;
|
|
}
|
|
}
|
|
|
|
static void __declspec(naked) gdialog_bk_hook() {
|
|
__asm {
|
|
_InLoop2(1, SPECIAL); // set the flag before switching from dialog mode to barter
|
|
jmp fo::funcoffs::gdialog_window_destroy_;
|
|
}
|
|
}
|
|
|
|
static void __declspec(naked) gdialogUpdatePartyStatus_hook1() {
|
|
__asm {
|
|
push edx;
|
|
_InLoop2(1, SPECIAL); // set the flag before animating the dialog panel when a party member joins/leaves
|
|
pop edx;
|
|
jmp fo::funcoffs::gdialog_window_destroy_;
|
|
}
|
|
}
|
|
|
|
static void __declspec(naked) gdialogUpdatePartyStatus_hook0() {
|
|
__asm {
|
|
call fo::funcoffs::gdialog_window_create_;
|
|
_InLoop2(0, SPECIAL); // unset the flag when entering the party member control panel
|
|
retn;
|
|
}
|
|
}
|
|
|
|
void LoadGameHook::init() {
|
|
saveInCombatFix = IniReader::GetConfigInt("Misc", "SaveInCombatFix", 1);
|
|
if (saveInCombatFix > 2) saveInCombatFix = 0;
|
|
|
|
switch (IniReader::GetConfigInt("Misc", "PipBoyAvailableAtGameStart", 0)) {
|
|
case 1:
|
|
pipBoyAvailableAtGameStart = true;
|
|
break;
|
|
case 2:
|
|
SafeWrite8(0x497011, CodeType::JumpShort); // skip the vault suit movie check
|
|
break;
|
|
}
|
|
|
|
if (IniReader::GetConfigInt("Misc", "DisableHorrigan", 0)) {
|
|
disableHorrigan = true;
|
|
}
|
|
|
|
HookCall(0x482AEC, map_load_hook);
|
|
HookCall(0x4809BA, main_init_system_hook);
|
|
HookCall(0x4426A6, game_init_hook);
|
|
HookCall(0x480AAE, main_load_new_hook);
|
|
|
|
const DWORD loadGameAddr[] = {0x443AE4, 0x443B89, 0x480B77, 0x48FD35};
|
|
HookCalls(LoadGame_hook, loadGameAddr);
|
|
SafeWrite32(0x5194C0, (DWORD)&EndLoadHook);
|
|
const DWORD saveGameAddr[] = {0x443AAC, 0x443B1C, 0x48FCFF};
|
|
HookCalls(SaveGame_hook, saveGameAddr);
|
|
|
|
const DWORD gameResetAddr[] = {
|
|
0x47DD6B, // LoadSlot_ (on load error)
|
|
0x47DDF3, // LoadSlot_ (on load error)
|
|
//0x480708, // RestoreLoad_ (never called)
|
|
0x480AD3, // gnw_main_ (game ended after playing via New Game)
|
|
0x480BCC, // gnw_main_ (game ended after playing via Load Game)
|
|
//0x480D0C, // main_reset_system_ (never called)
|
|
0x481028, // main_selfrun_record_
|
|
0x481062, // main_selfrun_record_
|
|
0x48110B, // main_selfrun_play_
|
|
0x481145 // main_selfrun_play_
|
|
};
|
|
HookCalls(game_reset_hook, gameResetAddr);
|
|
HookCall(0x47F491, game_reset_on_load_hook); // PrepLoad_ (the very first step during save game loading)
|
|
|
|
const DWORD beforeGameExitAddr[] = {0x480ACE, 0x480BC7}; // gnw_main_
|
|
HookCalls(before_game_exit_hook, beforeGameExitAddr);
|
|
const DWORD afterGameExitAddr[] = {0x480AEB, 0x480BE4}; // gnw_main_
|
|
HookCalls(after_game_exit_hook, afterGameExitAddr);
|
|
HookCall(0x480CA7, game_close_hook); // gnw_main_
|
|
//HookCall(0x480D45, game_close_hook); // main_exit_system_ (never called)
|
|
|
|
/////////////// GAME MODES ///////////////
|
|
HookCall(0x4BFE33, WorldMapHook_Start); // wmTownMap_ (old 0x483668, 0x4A4073)
|
|
HookCall(0x4C2E4F, WorldMapHook_End); // wmInterfaceExit_ (old 0x4C4855)
|
|
|
|
const DWORD combatHkAddr[] = {0x426A29, 0x4432BE, 0x45F6D2, 0x4A4020, 0x4A403D};
|
|
HookCalls(CombatHook, combatHkAddr);
|
|
HookCall(0x422B09, PlayerCombatHook);
|
|
|
|
HookCall(0x480C16, EscMenuHook); // gnw_main_
|
|
HookCall(0x4433BE, EscMenuHook2); // game_handle_input_
|
|
const DWORD optionsMenuHkAddr[] = {0x48FCE4, 0x48FD17, 0x48FD4D, 0x48FD6A, 0x48FD87, 0x48FDB3};
|
|
HookCalls(OptionsMenuHook, optionsMenuHkAddr);
|
|
HookCall(0x443A50, HelpMenuHook); // game_handle_input_
|
|
HookCall(0x443320, CharacterHook); // 0x4A73EB, 0x4A740A for character creation
|
|
|
|
MakeCall(0x445285, DialogHook_Start); // gdialogInitFromScript_ (old 0x445748)
|
|
HookCall(0x445317, DialogHook_End); // gdialogExitFromScript_
|
|
|
|
const DWORD pipboyHkStartAddr[] = {0x49767F, 0x4977EF, 0x49780D}; // StartPipboy_ (old 0x443463, 0x443605)
|
|
HookCalls(PipboyHook_Start, pipboyHkStartAddr);
|
|
HookCall(0x497868, PipboyHook_End); // EndPipboy_
|
|
|
|
const DWORD skilldexHkAddr[] = {0x4434AC, 0x44C7BD};
|
|
HookCalls(SkilldexHook, skilldexHkAddr);
|
|
|
|
HookCall(0x46E8F3, HandleInventoryHook_Start); // handle_inventory_ (old 0x443357)
|
|
HookCall(0x46EC2D, HandleInventoryHook_End);
|
|
|
|
HookCall(0x471823, UseInventoryOnHook_Start); // use_inventory_on_ (old 0x44C6FB)
|
|
HookCall(0x471B2C, UseInventoryOnHook_End);
|
|
|
|
HookCall(0x473E0D, LootContainerHook_Start); // loot_container_ (old 0x4746EC, 0x4A4369, 0x4A4565)
|
|
MakeCall(0x474692, LootContainerHook_End, 1);
|
|
|
|
HookCall(0x4466C7, BarterInventoryHook); // gdProcess_
|
|
|
|
HookCall(0x41BAB6, AutomapHook_Start); // automap_
|
|
HookCall(0x41BCDB, AutomapHook_End); // automap_
|
|
|
|
HookCall(0x445CA7, DialogReviewInitHook);
|
|
HookCall(0x445D30, DialogReviewExitHook);
|
|
|
|
HookCall(0x476AC6, setup_move_timer_win_Hook); // before init win
|
|
HookCall(0x477067, exit_move_timer_win_Hook);
|
|
|
|
// Set and unset the Special flag of game mode when animating the dialog interface panel
|
|
HookCall(0x447A7E, gdialog_bk_hook); // set when switching from dialog mode to barter mode (unset when entering barter)
|
|
HookCall(0x4457B1, gdialogUpdatePartyStatus_hook1); // set when a party member joins/leaves
|
|
HookCall(0x4457BC, gdialogUpdatePartyStatus_hook0); // unset
|
|
}
|
|
|
|
}
|