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
|
;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
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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
@@ -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);
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
@@ -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)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user