mirror of
https://github.com/sfall-team/sfall.git
synced 2026-07-27 16:52:34 -07:00
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:
+10
-5
@@ -1,5 +1,5 @@
|
||||
;sfall configuration settings
|
||||
;v4.1.5
|
||||
;v4.1.6
|
||||
|
||||
[Main]
|
||||
;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
|
||||
;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
|
||||
[Sound]
|
||||
;Sets the number of allowed simultaneous sound effects
|
||||
@@ -200,11 +208,8 @@ WorldMapSlots=0
|
||||
UseFileSystemOverride=0
|
||||
|
||||
;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)
|
||||
;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
|
||||
;If you want to load multiple patch files (up to 1000) at once, you can include a %d in the file name (sprintf syntax)
|
||||
;PatchFile=patch%03d.dat
|
||||
;MultiPatches=0
|
||||
|
||||
;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
|
||||
|
||||
@@ -160,6 +160,10 @@ long __stdcall db_get_file_list(const char* searchMask, char* * *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
|
||||
long __stdcall CheckFile(char *fileName, DWORD *sizeOut) {
|
||||
WRAP_WATCOM_CALL2(db_dir_entry_, fileName, sizeOut)
|
||||
|
||||
@@ -76,6 +76,8 @@ long __stdcall db_fwriteByteCount(DbFile* file, const BYTE* cptr, long count);
|
||||
// returns number of elements in *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
|
||||
void __declspec() debug_printf(const char* fmt, ...);
|
||||
|
||||
|
||||
+75
-52
@@ -37,6 +37,8 @@ static bool femaleCheck = false; // flag for check female dialog file
|
||||
static DWORD format;
|
||||
static bool cutsPatch = false;
|
||||
|
||||
static std::vector<std::string> patchFiles;
|
||||
|
||||
static void CheckPlayerGender() {
|
||||
isFemale = fo::HeroIsFemale();
|
||||
|
||||
@@ -99,81 +101,102 @@ static void __declspec(naked) gnw_main_hack() {
|
||||
}
|
||||
}
|
||||
|
||||
static void __declspec(naked) removeDatabase() {
|
||||
__asm {
|
||||
cmp eax, -1
|
||||
je end
|
||||
mov ebx, ds:[FO_VAR_paths]
|
||||
mov ecx, ebx
|
||||
nextPath:
|
||||
mov edx, [esp+0x104+4+4] // path_patches
|
||||
mov eax, [ebx] // database.path
|
||||
call fo::funcoffs::stricmp_
|
||||
test eax, eax // found path?
|
||||
jz skip // Yes
|
||||
mov ecx, ebx
|
||||
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
|
||||
static fo::PathNode* __fastcall RemoveDatabase(const char* pathPatches) {
|
||||
auto paths = fo::var::paths; // curr.node (beginning of the load order)
|
||||
auto _paths = paths; // prev.node
|
||||
|
||||
while (paths) {
|
||||
if (_stricmp(paths->path, pathPatches) == 0) { // found path
|
||||
fo::PathNode* nextPaths = paths->next; // pointer to the node of the next path
|
||||
// TODO: need to check if this condition is used correctly
|
||||
if (paths != _paths)
|
||||
_paths->next = nextPaths; // replace the pointer in the previous node, removing the current(found) path from the load order
|
||||
else // if the current node is equal to the previous node
|
||||
fo::var::paths = nextPaths; // set the next node at the beginning of the load order
|
||||
return paths; // return the pointer of the current removed node (save the pointer)
|
||||
}
|
||||
_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() {
|
||||
__asm {
|
||||
call removeDatabase
|
||||
mov ds:[FO_VAR_master_db_handle], eax
|
||||
retn
|
||||
cmp eax, -1;
|
||||
je skip;
|
||||
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() {
|
||||
__asm {
|
||||
cmp eax, -1
|
||||
je end
|
||||
mov eax, ds:[FO_VAR_master_db_handle]
|
||||
mov eax, [eax] // eax = master_patches.path
|
||||
call fo::funcoffs::xremovepath_
|
||||
dec eax // remove path (critter_patches == master_patches)?
|
||||
jz end // Yes
|
||||
inc eax
|
||||
call removeDatabase
|
||||
cmp eax, -1;
|
||||
je end;
|
||||
mov eax, ds:[FO_VAR_master_db_handle]; // pointer to master_patches node
|
||||
mov eax, [eax]; // eax = master_patches.path
|
||||
call fo::funcoffs::xremovepath_;
|
||||
dec eax; // remove path (critter_patches == master_patches)?
|
||||
jz end; // Yes (jump if 0)
|
||||
mov ecx, [esp + 0x104 + 4]; // path_patches
|
||||
call RemoveDatabase;
|
||||
end:
|
||||
mov ds:[FO_VAR_critter_db_handle], eax
|
||||
retn
|
||||
mov ds:[FO_VAR_critter_db_handle], eax; // the pointer of critter_patches node will be saved here
|
||||
retn;
|
||||
}
|
||||
}
|
||||
|
||||
static void __declspec(naked) game_init_databases_hook() {
|
||||
// eax = _master_db_handle
|
||||
__asm {
|
||||
mov ecx, ds:[FO_VAR_critter_db_handle]
|
||||
mov edx, ds:[FO_VAR_paths]
|
||||
jecxz skip
|
||||
mov [ecx+0xC], edx // critter_patches.next->_paths
|
||||
mov edx, ecx
|
||||
skip:
|
||||
mov [eax+0xC], edx // master_patches.next
|
||||
mov ds:[FO_VAR_paths], eax
|
||||
retn
|
||||
static void __stdcall InitExtraPatches() {
|
||||
for (auto it = patchFiles.begin(); it != patchFiles.end(); it++) {
|
||||
fo::func::db_init(it->c_str(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
static void __fastcall game_init_databases_hook() {
|
||||
fo::PathNode* master_patches;
|
||||
__asm mov master_patches, eax; // eax = _master_db_handle
|
||||
|
||||
if (!patchFiles.empty()) InitExtraPatches();
|
||||
|
||||
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() {
|
||||
GetExtraPatches();
|
||||
|
||||
if (GetConfigInt("Misc", "DataLoadOrderPatch", 0)) {
|
||||
dlog("Applying data load order patch.", DL_INIT);
|
||||
MakeCall(0x444259, game_init_databases_hack1);
|
||||
MakeCall(0x4442F1, game_init_databases_hack2);
|
||||
HookCall(0x44436D, &game_init_databases_hook);
|
||||
SafeWrite8(0x4DFAEC, 0x1D); // error correction
|
||||
HookCall(0x44436D, game_init_databases_hook);
|
||||
SafeWrite8(0x4DFAEC, 0x1D); // error correction (ecx > ebx)
|
||||
dlogr(" Done", DL_INIT);
|
||||
} else if (!patchFiles.empty()) {
|
||||
HookCall(0x44436D, InitExtraPatches);
|
||||
}
|
||||
|
||||
femaleMsgs = GetConfigInt("Misc", "FemaleDialogMsgs", 0);
|
||||
|
||||
@@ -284,11 +284,11 @@ really_end:
|
||||
static DWORD RetryCombatLastAP;
|
||||
static DWORD RetryCombatMinAP;
|
||||
static void __declspec(naked) RetryCombatHook() {
|
||||
using namespace fo;
|
||||
using namespace Fields;
|
||||
__asm {
|
||||
mov RetryCombatLastAP, 0;
|
||||
retry:
|
||||
mov eax, esi;
|
||||
xor edx, edx;
|
||||
call fo::funcoffs::combat_ai_;
|
||||
process:
|
||||
cmp dword ptr ds:[FO_VAR_combat_turn_running], 0;
|
||||
@@ -296,12 +296,14 @@ process:
|
||||
call fo::funcoffs::process_bk_;
|
||||
jmp process;
|
||||
next:
|
||||
mov eax, [esi+0x40];
|
||||
mov eax, [esi + movePoints];
|
||||
cmp eax, RetryCombatMinAP;
|
||||
jl end;
|
||||
cmp eax, RetryCombatLastAP;
|
||||
je end;
|
||||
mov RetryCombatLastAP, eax;
|
||||
mov eax, esi;
|
||||
xor edx, edx;
|
||||
jmp retry;
|
||||
end:
|
||||
retn;
|
||||
@@ -619,19 +621,19 @@ void DontTurnOffSneakIfYouRunPatch() {
|
||||
void CombatProcFix() {
|
||||
//Ray's combat_p_proc fix
|
||||
dlog("Applying combat_p_proc fix.", DL_INIT);
|
||||
SafeWrite32(0x0425253, ((DWORD)&Combat_p_procFix) - 0x0425257);
|
||||
SafeWrite8(0x0424dbc, 0xE9);
|
||||
SafeWrite32(0x0424dbd, 0x00000034);
|
||||
HookCall(0x425252, Combat_p_procFix);
|
||||
SafeWrite8(0x424DBC, 0xE9);
|
||||
SafeWrite32(0x424DBD, 0x00000034);
|
||||
dlogr(" Done", DL_INIT);
|
||||
}
|
||||
|
||||
void MultiPatchesPatch() {
|
||||
if (GetConfigInt("Misc", "MultiPatches", 0)) {
|
||||
//if (GetConfigInt("Misc", "MultiPatches", 0)) {
|
||||
dlog("Applying load multiple patches patch.", DL_INIT);
|
||||
SafeWrite8(0x444354, 0x90); // Change step from 2 to 1
|
||||
SafeWrite8(0x44435C, 0xC4); // Disable check
|
||||
dlogr(" Done", DL_INIT);
|
||||
}
|
||||
//}
|
||||
}
|
||||
|
||||
void PlayIdleAnimOnReloadPatch() {
|
||||
@@ -655,7 +657,7 @@ void ApplyNpcExtraApPatch() {
|
||||
RetryCombatMinAP = GetConfigInt("Misc", "NPCsTryToSpendExtraAP", 0);
|
||||
if (RetryCombatMinAP > 0) {
|
||||
dlog("Applying retry combat patch.", DL_INIT);
|
||||
HookCall(0x422B94, &RetryCombatHook);
|
||||
HookCall(0x422B94, RetryCombatHook); // combat_turn_
|
||||
dlogr(" Done", DL_INIT);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
// the identifier is saved with the object in the saved game and this can used in various script
|
||||
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;
|
||||
obj->id = ++uniqueID;
|
||||
|
||||
@@ -243,7 +243,7 @@ bool LoadArrays(HANDLE h) {
|
||||
sArrayVar arrayVar;
|
||||
for (DWORD i = 0; i < count; i++) {
|
||||
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.type = DataType::INT;
|
||||
}
|
||||
|
||||
@@ -145,10 +145,13 @@ typedef ArraysMap::iterator array_itr;
|
||||
typedef std::pair<DWORD, sArrayVar> array_pair;
|
||||
|
||||
// auto-incremented ID
|
||||
extern DWORD nextarrayid;
|
||||
extern DWORD nextArrayID;
|
||||
|
||||
extern DWORD arraysBehavior;
|
||||
|
||||
// temp arrays: set of arrayId
|
||||
extern std::set<DWORD> tempArrays;
|
||||
|
||||
// saved arrays: arrayKey => arrayId
|
||||
extern ArrayKeysMap savedArrays;
|
||||
|
||||
|
||||
+3
-3
@@ -24,10 +24,10 @@
|
||||
|
||||
#define VERSION_MAJOR 4
|
||||
#define VERSION_MINOR 1
|
||||
#define VERSION_BUILD 5
|
||||
#define VERSION_BUILD 6
|
||||
#define VERSION_REV 0
|
||||
|
||||
#define VERSION_STRING "4.1.5"
|
||||
#define VERSION_STRING "4.1.6"
|
||||
|
||||
#define CHECK_VAL (4)
|
||||
//#define CHECK_VAL (4)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user