Added ExtraPatches section to ddraw.ini for setting multiple custom paths for game files (from Mr.Stalin, #206)

Refactored some code of DataLoadOrderPatch to work with new ExtraPatches.
Removed MultiPatches from ddraw.ini, now it's always enabled.
Minor edits to MiscPatches.cpp and Arrays.cpp.
Updated version number.
This commit is contained in:
NovaRain
2019-02-19 11:55:38 +08:00
parent fe5a148d7f
commit 8613a4fb8b
9 changed files with 122 additions and 83 deletions
+10 -5
View File
@@ -1,5 +1,5 @@
;sfall configuration settings ;sfall configuration settings
;v4.1.5 ;v4.1.6
[Main] [Main]
;Change to 1 if you want to use command line args to tell sfall to use another ini file. ;Change to 1 if you want to use command line args to tell sfall to use another ini file.
@@ -8,6 +8,14 @@ UseCommandLine=0
;Uncomment and point to a file to get alternate translations for some sfall messages ;Uncomment and point to a file to get alternate translations for some sfall messages
;TranslationsINI=./Translations.ini ;TranslationsINI=./Translations.ini
;XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
[ExtraPatches]
;This section allows you to set multiple paths to folders containing mods or patches that will be loaded by the game
;The priority of read files will be higher than the files in patchXXX.dat. If DataLoadOrderPatch is enabled, the data load order will be:
;master_patches > critter_patches > PatchFile99 - PatchFile0 > patchXXX.dat > ...
;Paths to folders and Fallout .dat files are supported. The available range for PatchFile option names is from 0 to 99
;PatchFile0=mods\RP_data
;XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX ;XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
[Sound] [Sound]
;Sets the number of allowed simultaneous sound effects ;Sets the number of allowed simultaneous sound effects
@@ -200,11 +208,8 @@ WorldMapSlots=0
UseFileSystemOverride=0 UseFileSystemOverride=0
;To use a patch file other than patch000.dat, uncomment the next line and add your new file name ;To use a patch file other than patch000.dat, uncomment the next line and add your new file name
;If you want to check for multiple patch files, you can include a %d in the file name (sprintf syntax) ;If you want to load multiple patch files (up to 1000) at once, you can include a %d in the file name (sprintf syntax)
;By default, only the first patch file found will be used.
;If you want to load multiple patch files (up to 1000) at once, uncomment the MultiPatches line and set it to 1
;PatchFile=patch%03d.dat ;PatchFile=patch%03d.dat
;MultiPatches=0
;Set to 1 to use the modified data load order for the engine to find game data ;Set to 1 to use the modified data load order for the engine to find game data
;Original: patchXXX.dat > critter_patches > critter_dat > f2_res_patches > f2_res_dat > master_patches > master_dat ;Original: patchXXX.dat > critter_patches > critter_dat > f2_res_patches > f2_res_dat > master_patches > master_dat
+4
View File
@@ -160,6 +160,10 @@ long __stdcall db_get_file_list(const char* searchMask, char* * *fileList) {
WRAP_WATCOM_CALL2(db_get_file_list_, searchMask, fileList) WRAP_WATCOM_CALL2(db_get_file_list_, searchMask, fileList)
} }
long __stdcall db_init(const char* path_dat, const char* path_patches) {
WRAP_WATCOM_CALL3(db_init_, path_dat, 0, path_patches)
}
// Check fallout paths for file // Check fallout paths for file
long __stdcall CheckFile(char *fileName, DWORD *sizeOut) { long __stdcall CheckFile(char *fileName, DWORD *sizeOut) {
WRAP_WATCOM_CALL2(db_dir_entry_, fileName, sizeOut) WRAP_WATCOM_CALL2(db_dir_entry_, fileName, sizeOut)
+2
View File
@@ -76,6 +76,8 @@ long __stdcall db_fwriteByteCount(DbFile* file, const BYTE* cptr, long count);
// returns number of elements in *fileList // returns number of elements in *fileList
long __stdcall db_get_file_list(const char* searchMask, char* * *fileList); long __stdcall db_get_file_list(const char* searchMask, char* * *fileList);
long __stdcall db_init(const char* path_dat, const char* path_patches);
// prints message to debug.log file // prints message to debug.log file
void __declspec() debug_printf(const char* fmt, ...); void __declspec() debug_printf(const char* fmt, ...);
+75 -52
View File
@@ -37,6 +37,8 @@ static bool femaleCheck = false; // flag for check female dialog file
static DWORD format; static DWORD format;
static bool cutsPatch = false; static bool cutsPatch = false;
static std::vector<std::string> patchFiles;
static void CheckPlayerGender() { static void CheckPlayerGender() {
isFemale = fo::HeroIsFemale(); isFemale = fo::HeroIsFemale();
@@ -99,81 +101,102 @@ static void __declspec(naked) gnw_main_hack() {
} }
} }
static void __declspec(naked) removeDatabase() { static fo::PathNode* __fastcall RemoveDatabase(const char* pathPatches) {
__asm { auto paths = fo::var::paths; // curr.node (beginning of the load order)
cmp eax, -1 auto _paths = paths; // prev.node
je end
mov ebx, ds:[FO_VAR_paths] while (paths) {
mov ecx, ebx if (_stricmp(paths->path, pathPatches) == 0) { // found path
nextPath: fo::PathNode* nextPaths = paths->next; // pointer to the node of the next path
mov edx, [esp+0x104+4+4] // path_patches // TODO: need to check if this condition is used correctly
mov eax, [ebx] // database.path if (paths != _paths)
call fo::funcoffs::stricmp_ _paths->next = nextPaths; // replace the pointer in the previous node, removing the current(found) path from the load order
test eax, eax // found path? else // if the current node is equal to the previous node
jz skip // Yes fo::var::paths = nextPaths; // set the next node at the beginning of the load order
mov ecx, ebx return paths; // return the pointer of the current removed node (save the pointer)
mov ebx, [ebx+0xC] // database.next
jmp nextPath
skip:
mov eax, [ebx+0xC] // database.next
mov [ecx+0xC], eax // database.next
xchg ebx, eax
cmp eax, ecx
jne end
mov ds:[FO_VAR_paths], ebx
end:
retn
} }
_paths = paths; // prev.node <- curr.node
paths = paths->next; // take a pointer to the next path from the current node
}
return nullptr; // it's possible that this will create an exceptional situation for the game, although such a situation should not arise
} }
// Remove master_patches from the load order
static void __declspec(naked) game_init_databases_hack1() { static void __declspec(naked) game_init_databases_hack1() {
__asm { __asm {
call removeDatabase cmp eax, -1;
mov ds:[FO_VAR_master_db_handle], eax je skip;
retn mov ecx, [esp + 0x104 + 4]; // path_patches
call RemoveDatabase;
skip:
mov ds:[FO_VAR_master_db_handle], eax; // the pointer of master_patches node will be saved here
retn;
} }
} }
// Remove critter_patches from the load order
static void __declspec(naked) game_init_databases_hack2() { static void __declspec(naked) game_init_databases_hack2() {
__asm { __asm {
cmp eax, -1 cmp eax, -1;
je end je end;
mov eax, ds:[FO_VAR_master_db_handle] mov eax, ds:[FO_VAR_master_db_handle]; // pointer to master_patches node
mov eax, [eax] // eax = master_patches.path mov eax, [eax]; // eax = master_patches.path
call fo::funcoffs::xremovepath_ call fo::funcoffs::xremovepath_;
dec eax // remove path (critter_patches == master_patches)? dec eax; // remove path (critter_patches == master_patches)?
jz end // Yes jz end; // Yes (jump if 0)
inc eax mov ecx, [esp + 0x104 + 4]; // path_patches
call removeDatabase call RemoveDatabase;
end: end:
mov ds:[FO_VAR_critter_db_handle], eax mov ds:[FO_VAR_critter_db_handle], eax; // the pointer of critter_patches node will be saved here
retn retn;
} }
} }
static void __declspec(naked) game_init_databases_hook() { static void __stdcall InitExtraPatches() {
// eax = _master_db_handle for (auto it = patchFiles.begin(); it != patchFiles.end(); it++) {
__asm { fo::func::db_init(it->c_str(), 0);
mov ecx, ds:[FO_VAR_critter_db_handle] }
mov edx, ds:[FO_VAR_paths] }
jecxz skip
mov [ecx+0xC], edx // critter_patches.next->_paths static void __fastcall game_init_databases_hook() {
mov edx, ecx fo::PathNode* master_patches;
skip: __asm mov master_patches, eax; // eax = _master_db_handle
mov [eax+0xC], edx // master_patches.next
mov ds:[FO_VAR_paths], eax if (!patchFiles.empty()) InitExtraPatches();
retn
fo::PathNode* critter_patches = (fo::PathNode*)fo::var::critter_db_handle;
fo::PathNode* paths = fo::var::paths; // beginning of the load order
// insert master_patches/critter_patches at the beginning of the load order
if (critter_patches) {
critter_patches->next = paths; // critter_patches.next -> paths
paths = critter_patches;
}
master_patches->next = paths; // master_patches.next -> paths
fo::var::paths = master_patches; // set master_patches node at the beginning of the load order
}
static void GetExtraPatches() {
char patchFile[12] = "PatchFile";
for (int i = 0; i < 100; i++) {
_itoa(i, &patchFile[9], 10);
auto patch = GetConfigString("ExtraPatches", patchFile, "", MAX_PATH);
if (patch.empty() || GetFileAttributes(patch.c_str()) == INVALID_FILE_ATTRIBUTES) continue;
patchFiles.push_back(patch);
} }
} }
void LoadOrder::init() { void LoadOrder::init() {
GetExtraPatches();
if (GetConfigInt("Misc", "DataLoadOrderPatch", 0)) { if (GetConfigInt("Misc", "DataLoadOrderPatch", 0)) {
dlog("Applying data load order patch.", DL_INIT); dlog("Applying data load order patch.", DL_INIT);
MakeCall(0x444259, game_init_databases_hack1); MakeCall(0x444259, game_init_databases_hack1);
MakeCall(0x4442F1, game_init_databases_hack2); MakeCall(0x4442F1, game_init_databases_hack2);
HookCall(0x44436D, &game_init_databases_hook); HookCall(0x44436D, game_init_databases_hook);
SafeWrite8(0x4DFAEC, 0x1D); // error correction SafeWrite8(0x4DFAEC, 0x1D); // error correction (ecx > ebx)
dlogr(" Done", DL_INIT); dlogr(" Done", DL_INIT);
} else if (!patchFiles.empty()) {
HookCall(0x44436D, InitExtraPatches);
} }
femaleMsgs = GetConfigInt("Misc", "FemaleDialogMsgs", 0); femaleMsgs = GetConfigInt("Misc", "FemaleDialogMsgs", 0);
+11 -9
View File
@@ -284,11 +284,11 @@ really_end:
static DWORD RetryCombatLastAP; static DWORD RetryCombatLastAP;
static DWORD RetryCombatMinAP; static DWORD RetryCombatMinAP;
static void __declspec(naked) RetryCombatHook() { static void __declspec(naked) RetryCombatHook() {
using namespace fo;
using namespace Fields;
__asm { __asm {
mov RetryCombatLastAP, 0; mov RetryCombatLastAP, 0;
retry: retry:
mov eax, esi;
xor edx, edx;
call fo::funcoffs::combat_ai_; call fo::funcoffs::combat_ai_;
process: process:
cmp dword ptr ds:[FO_VAR_combat_turn_running], 0; cmp dword ptr ds:[FO_VAR_combat_turn_running], 0;
@@ -296,12 +296,14 @@ process:
call fo::funcoffs::process_bk_; call fo::funcoffs::process_bk_;
jmp process; jmp process;
next: next:
mov eax, [esi+0x40]; mov eax, [esi + movePoints];
cmp eax, RetryCombatMinAP; cmp eax, RetryCombatMinAP;
jl end; jl end;
cmp eax, RetryCombatLastAP; cmp eax, RetryCombatLastAP;
je end; je end;
mov RetryCombatLastAP, eax; mov RetryCombatLastAP, eax;
mov eax, esi;
xor edx, edx;
jmp retry; jmp retry;
end: end:
retn; retn;
@@ -619,19 +621,19 @@ void DontTurnOffSneakIfYouRunPatch() {
void CombatProcFix() { void CombatProcFix() {
//Ray's combat_p_proc fix //Ray's combat_p_proc fix
dlog("Applying combat_p_proc fix.", DL_INIT); dlog("Applying combat_p_proc fix.", DL_INIT);
SafeWrite32(0x0425253, ((DWORD)&Combat_p_procFix) - 0x0425257); HookCall(0x425252, Combat_p_procFix);
SafeWrite8(0x0424dbc, 0xE9); SafeWrite8(0x424DBC, 0xE9);
SafeWrite32(0x0424dbd, 0x00000034); SafeWrite32(0x424DBD, 0x00000034);
dlogr(" Done", DL_INIT); dlogr(" Done", DL_INIT);
} }
void MultiPatchesPatch() { void MultiPatchesPatch() {
if (GetConfigInt("Misc", "MultiPatches", 0)) { //if (GetConfigInt("Misc", "MultiPatches", 0)) {
dlog("Applying load multiple patches patch.", DL_INIT); dlog("Applying load multiple patches patch.", DL_INIT);
SafeWrite8(0x444354, 0x90); // Change step from 2 to 1 SafeWrite8(0x444354, 0x90); // Change step from 2 to 1
SafeWrite8(0x44435C, 0xC4); // Disable check SafeWrite8(0x44435C, 0xC4); // Disable check
dlogr(" Done", DL_INIT); dlogr(" Done", DL_INIT);
} //}
} }
void PlayIdleAnimOnReloadPatch() { void PlayIdleAnimOnReloadPatch() {
@@ -655,7 +657,7 @@ void ApplyNpcExtraApPatch() {
RetryCombatMinAP = GetConfigInt("Misc", "NPCsTryToSpendExtraAP", 0); RetryCombatMinAP = GetConfigInt("Misc", "NPCsTryToSpendExtraAP", 0);
if (RetryCombatMinAP > 0) { if (RetryCombatMinAP > 0) {
dlog("Applying retry combat patch.", DL_INIT); dlog("Applying retry combat patch.", DL_INIT);
HookCall(0x422B94, &RetryCombatHook); HookCall(0x422B94, RetryCombatHook); // combat_turn_
dlogr(" Done", DL_INIT); dlogr(" Done", DL_INIT);
} }
} }
+1 -1
View File
@@ -33,7 +33,7 @@ long Objects::uniqueID = UniqueID::Start; // saving to sfallgv.sav
// Assigns a new unique identifier to an object if it has not been previously assigned // Assigns a new unique identifier to an object if it has not been previously assigned
// the identifier is saved with the object in the saved game and this can used in various script // the identifier is saved with the object in the saved game and this can used in various script
long Objects::SetObjectUniqueID(fo::GameObject* obj) { long Objects::SetObjectUniqueID(fo::GameObject* obj) {
if (obj->id > UniqueID::Start || obj == fo::var::obj_dude) return obj->id; // dude id = 1800. TODO: perhaps his id needs to be changed to 0x10000000 if (obj->id > UniqueID::Start || obj == fo::var::obj_dude) return obj->id; // dude id = 18000
if ((DWORD)uniqueID >= UniqueID::End) uniqueID = UniqueID::Start; if ((DWORD)uniqueID >= UniqueID::End) uniqueID = UniqueID::Start;
obj->id = ++uniqueID; obj->id = ++uniqueID;
+1 -1
View File
@@ -243,7 +243,7 @@ bool LoadArrays(HANDLE h) {
sArrayVar arrayVar; sArrayVar arrayVar;
for (DWORD i = 0; i < count; i++) { for (DWORD i = 0; i < count; i++) {
if (LoadArrayElement(&arrayVar.key, h)) return true; if (LoadArrayElement(&arrayVar.key, h)) return true;
if (static_cast<long>(arrayVar.key.type) > 4 || arrayVar.key.intVal == 0) { // partial compatibility with 3.4 if (arrayVar.key.intVal == 0 || static_cast<long>(arrayVar.key.type) >= 4) { // partial compatibility with 3.4
arrayVar.key.intVal = static_cast<long>(arrayVar.key.type); arrayVar.key.intVal = static_cast<long>(arrayVar.key.type);
arrayVar.key.type = DataType::INT; arrayVar.key.type = DataType::INT;
} }
+4 -1
View File
@@ -145,10 +145,13 @@ typedef ArraysMap::iterator array_itr;
typedef std::pair<DWORD, sArrayVar> array_pair; typedef std::pair<DWORD, sArrayVar> array_pair;
// auto-incremented ID // auto-incremented ID
extern DWORD nextarrayid; extern DWORD nextArrayID;
extern DWORD arraysBehavior; extern DWORD arraysBehavior;
// temp arrays: set of arrayId // temp arrays: set of arrayId
extern std::set<DWORD> tempArrays; extern std::set<DWORD> tempArrays;
// saved arrays: arrayKey => arrayId // saved arrays: arrayKey => arrayId
extern ArrayKeysMap savedArrays; extern ArrayKeysMap savedArrays;
+3 -3
View File
@@ -24,10 +24,10 @@
#define VERSION_MAJOR 4 #define VERSION_MAJOR 4
#define VERSION_MINOR 1 #define VERSION_MINOR 1
#define VERSION_BUILD 5 #define VERSION_BUILD 6
#define VERSION_REV 0 #define VERSION_REV 0
#define VERSION_STRING "4.1.5" #define VERSION_STRING "4.1.6"
#define CHECK_VAL (4) //#define CHECK_VAL (4)