diff --git a/artifacts/ddraw.ini b/artifacts/ddraw.ini index 98ca7920..99fd71ae 100644 --- a/artifacts/ddraw.ini +++ b/artifacts/ddraw.ini @@ -500,12 +500,12 @@ UseScrollingQuestsList=1 ExtraSaveSlots=0 ;To use more than one save slot for quick saving (F6 key) without picking a slot beforehand, set the next two lines -;AutoQuickSave sets how many save slots on a page you want to use for quick saving (valid range: 1..10) -;AutoQuickSavePage is the page number to use (valid range: 1..1000) if ExtraSaveSlots is enabled -;The quick saves will be rotated from the first slot on the page to the n-th slot +;AutoQuickSave sets how many pages you want to use for quick saving (valid range: 1..10) +;AutoQuickSavePage is the page number to start at (valid range: 1..1000, if ExtraSaveSlots is enabled) +;The quick saves will be rotated from the first slot on the page to the last slot on the n-th page ;Set to 0 to disable. AutoQuickSave will use the current selected page if AutoQuickSavePage is disabled AutoQuickSave=0 -AutoQuickSavePage=0 +AutoQuickSavePage=1 ;Set to 1 to speed up the HP/AC counter animations ;Set to 2 to update the HP/AC counters instantly diff --git a/artifacts/scripting/headers/sfall.h b/artifacts/scripting/headers/sfall.h index 7c0a0023..db1d9066 100644 --- a/artifacts/scripting/headers/sfall.h +++ b/artifacts/scripting/headers/sfall.h @@ -283,16 +283,10 @@ if (get_flags(obj1) bwand FLAG_MULTIHEX) then distance--; \ if (get_flags(obj2) bwand FLAG_MULTIHEX) then distance-- - -/* sfall metarule3 function macros */ -// sets the number of days (range 1...127) for the Frank Horrigan encounter, or disable the encounter if days is set to 0 -#define set_horrigan_days(day) metarule3(200, day, 0, 0) -// clears the keyboard input buffer, use it in the HOOK_KEYPRESS hook to clear keyboard events before calling functions that are waiting for keyboard input -#define clear_keyboard_buffer metarule3(201, 0, 0, 0) - // checks if the specified PID number exists in the list of registered protos #define check_pid(pid) (get_proto_data(pid, 0) != -1) + /* sfall_funcX macros */ #define add_extra_msg_file(name) sfall_func1("add_extra_msg_file", name) #define add_global_timer_event(time, fixedParam) sfall_func2("add_g_timer_event", time, fixedParam) @@ -407,4 +401,19 @@ #define set_fake_trait_npc(npc, trait, active, image, desc) sfall_func5("set_fake_trait_npc", npc, trait, active, image, desc) #define set_selectable_perk_npc(npc, perk, active, image, desc) sfall_func5("set_selectable_perk_npc", npc, perk, active, image, desc) + +/* sfall metarule3 function macros */ +// sets the number of days (range 1...127) for the Frank Horrigan encounter, or disable the encounter if days is set to 0 +#define set_horrigan_days(day) metarule3(200, day, 0, 0) +// clears the keyboard input buffer, use it in the HOOK_KEYPRESS hook to clear keyboard events before calling functions that are waiting for keyboard input +#define clear_keyboard_buffer metarule3(201, 0, 0, 0) + +// functions to control the save slot +// Note: page and slot here are 0-indexed instead of 1-indexed displayed in game +#define get_current_save_slot metarule3(1000, 0, 0, 0) // returns the amount: page + slot +#define set_current_save_slot(page, slot) metarule3(1001, page, slot, 0) +#define get_current_quick_save_page metarule3(1002, 0, 0, 0) +#define get_current_quick_save_slot metarule3(1003, 0, 0, 0) +#define set_current_quick_save_slot(page, slot, check) metarule3(1004, page, slot, check) // check: 1 - don't check slot when saving + #endif diff --git a/sfall/Modules/ExtraSaveSlots.cpp b/sfall/Modules/ExtraSaveSlots.cpp index 6553d8c9..57d0ad98 100644 --- a/sfall/Modules/ExtraSaveSlots.cpp +++ b/sfall/Modules/ExtraSaveSlots.cpp @@ -26,14 +26,23 @@ namespace sfall { -DWORD LSPageOffset = 0; - +static long LSPageOffset = 0; static long LSButtDN = 0; static BYTE* SaveLoadSurface = nullptr; static const char* filename = "%s\\savegame\\slotdat.ini"; -static void SavePageOffsets() { +long ExtraSaveSlots::GetSaveSlot() { + return LSPageOffset + fo::var::slot_cursor; +} + +void ExtraSaveSlots::SetSaveSlot(long page, long slot) { + if (GetQuickSavePage() >= 0 && page >= 0 && page <= 9990) LSPageOffset = page - (page % 10);; + if (slot >= 0 && slot < 10) fo::var::slot_cursor = slot; +} + +// save last slot position values to file +static long save_page_offsets() { char SavePath[MAX_PATH], buffer[6]; sprintf_s(SavePath, MAX_PATH, filename, fo::var::patches); @@ -43,16 +52,8 @@ static void SavePageOffsets() { _itoa_s(LSPageOffset, buffer, 10); WritePrivateProfileStringA("POSITION", "PageOffset", buffer, SavePath); -} -static void __declspec(naked) save_page_offsets(void) { - __asm { - // save last slot position values to file - call SavePageOffsets; - // restore original code - mov eax, dword ptr ds:[FO_VAR_lsgwin]; - retn; - } + return fo::var::lsgwin; // restore original code } static void LoadPageOffsets() { @@ -74,13 +75,12 @@ static void __declspec(naked) load_page_offsets(void) { __asm { // load last slot position values from file call LoadPageOffsets; - // restore original code - mov edx, 0x50A480; // ASCII "SAV" + mov edx, 0x50A480; // ASCII "SAV" (restore original code) retn; } } -static void CreateButtons() { +static long create_page_buttons() { DWORD winRef = fo::var::lsgwin; // left button -10 | X | Y | W | H |HOn |HOff |BDown |BUp |PicUp |PicDown |? |ButType @@ -93,14 +93,8 @@ static void CreateButtons() { fo::func::win_register_button(winRef, 248, 60, 24, 20, -1, 0x500, 0x551, 0x151, 0, 0, 0, 32); // Set Number button fo::func::win_register_button(winRef, 140, 60, 60, 20, -1, -1, 'p', -1, 0, 0, 0, 32); -} -static void __declspec(naked) create_page_buttons(void) { - __asm { - call CreateButtons; - mov eax, 0x65; // restore original code - retn; - } + return 101; // restore original value } static void SetPageNum() { @@ -215,12 +209,12 @@ static long __fastcall CheckPage(long button) { break; case 0x14D: // right button LSPageOffset += 10; - if (LSPageOffset > 9990) LSPageOffset -= 10000; // to the first page + if (LSPageOffset >= 10000) LSPageOffset -= 10000; // to the first page __asm call fo::funcoffs::gsound_red_butt_press_; break; case 0x151: // fast right PGDN button LSPageOffset += 100; - if (LSPageOffset > 9990) LSPageOffset -= 10000; + if (LSPageOffset >= 10000) LSPageOffset -= 10000; __asm call fo::funcoffs::gsound_red_butt_press_; break; case 'p': // p/P button pressed - start SetPageNum func @@ -237,15 +231,17 @@ static long __fastcall CheckPage(long button) { static void __declspec(naked) check_page_buttons(void) { __asm { - pushad; + push eax; + push ecx; mov ecx, eax; call CheckPage; test eax, eax; - popad; - jnz CheckUp; + pop ecx; + pop eax; + jnz checkUp; add dword ptr ds:[esp], 26; // set return to button pressed code jmp fo::funcoffs::GetSlotList_; // reset page save list func -CheckUp: +checkUp: // restore original code cmp eax, 0x148; // up button retn; @@ -326,25 +322,25 @@ static void DrawPageText() { static void __declspec(naked) draw_page_text(void) { __asm { - pushad; + push eax; call DrawPageText; - popad; - mov ebp, 0x57; // restore original code + pop eax; + mov ebp, 87; // restore original code retn; } } // add page num offset when reading and writing various save data files -static void __declspec(naked) AddPageOffset01(void) { +static void __declspec(naked) add_page_offset_hack1(void) { __asm { mov eax, dword ptr ds:[FO_VAR_slot_cursor]; // list position 0-9 - add eax, LSPageOffset; // add page num offset + add eax, LSPageOffset; // add page num offset retn; } } // getting info for the 10 currently displayed save slots from save.dats -static void __declspec(naked) AddPageOffset02(void) { +static void __declspec(naked) add_page_offset_hack2(void) { __asm { push 0x50A514; // ASCII "SAVE.DAT" lea eax, [ebx + 1]; @@ -355,7 +351,7 @@ static void __declspec(naked) AddPageOffset02(void) { } // printing current 10 slot numbers -static void __declspec(naked) AddPageOffset03(void) { +static void __declspec(naked) add_page_offset_hack3(void) { __asm { inc eax; add eax, LSPageOffset; // add page num offset @@ -366,32 +362,36 @@ static void __declspec(naked) AddPageOffset03(void) { static void EnableSuperSaving() { // save/load button setup func - MakeCalls(create_page_buttons, {0x47D80D}); + MakeCall(0x47D80D, create_page_buttons); // LSGameStart_ // Draw button text - MakeCalls(draw_page_text, {0x47E6E8}); + MakeCall(0x47E6E8, draw_page_text); // ShowSlotList_ // check save/load buttons - MakeCalls(check_page_buttons, {0x47BD49, 0x47CB1C}); + MakeCalls(check_page_buttons, {0x47BD49, 0x47CB1C}); // SaveGame_, LoadGame_ // save current page and list positions to file on load/save scrn exit - MakeCalls(save_page_offsets, {0x47D828}); + MakeCall(0x47D828, save_page_offsets); // LSGameEnd_ // load saved page and list positions from file - MakeCalls(load_page_offsets, {0x47B82B}); + MakeCall(0x47B82B, load_page_offsets); // InitLoadSave_ // Add Load/Save page offset to Load/Save folder number - MakeCalls(AddPageOffset01, { - 0x47B929, 0x47D8DB, 0x47D9B0, 0x47DA34, 0x47DABF, 0x47DB58, 0x47DBE9, - 0x47DC9C, 0x47EC77, 0x47F5AB, 0x47F694, 0x47F6EB, 0x47F7FB, 0x47F892, - 0x47FB86, 0x47FC3A, 0x47FCF2, 0x480117, 0x4801CF, 0x480234, 0x480310, - 0x4803F3, 0x48049F, 0x480512, 0x4805F2, 0x480767, 0x4807E6, 0x480839, - 0x4808D3 + MakeCalls(add_page_offset_hack1, { + 0x47B929, // SaveGame_ + 0x47D8DB, 0x47D9B0, 0x47DA34, 0x47DABF, 0x47DB58, 0x47DBE9, // SaveSlot_ + 0x47DC9C, // LoadSlot_ + 0x47EC77, // LoadTumbSlot_ + 0x47F5AB, 0x47F694, 0x47F6EB, 0x47F7FB, 0x47F892, // GameMap2Slot_ + 0x47FB86, 0x47FC3A, 0x47FCF2, // SlotMap2Game_ + 0x480117, 0x4801CF, 0x480234, 0x480310, // SaveBackup_ + 0x4803F3, 0x48049F, 0x480512, 0x4805F2, // RestoreSave_ + 0x480767, 0x4807E6, 0x480839, 0x4808D3 // EraseSave_ }); - MakeJump(0x47E5E1, AddPageOffset02); + MakeJump(0x47E5E1, add_page_offset_hack2); // GetSlotList_ - MakeCalls(AddPageOffset03, {0x47E756}); + MakeCall(0x47E756, add_page_offset_hack3); // ShowSlotList_ } static void GetSaveFileTime(char* filename, FILETIME* ftSlot) { @@ -408,43 +408,69 @@ static void GetSaveFileTime(char* filename, FILETIME* ftSlot) { }; } -static const char* commentFmt = "%02d/%02d/%d - %02d:%02d:%02d"; +static const char* autoFmt = "AUTO: %02d/%02d/%d - %02d:%02d:%02d"; +static const char* quickFmt = "QUICK: %02d/%02d/%d - %02d:%02d:%02d"; -static void CreateSaveComment(char* bufstr) { +static void CreateSaveComment(char* bufstr, bool isAuto) { SYSTEMTIME stUTC, stLocal; GetSystemTime(&stUTC); SystemTimeToTzSpecificLocalTime(NULL, &stUTC, &stLocal); - char buf[30]; - sprintf_s(buf, commentFmt, stLocal.wDay, stLocal.wMonth, stLocal.wYear, stLocal.wHour, stLocal.wMinute, stLocal.wSecond); - strcpy(bufstr, buf); + const char* fmt = (isAuto) ? autoFmt : quickFmt; + sprintf_s(bufstr, 30, fmt, stLocal.wDay, stLocal.wMonth, stLocal.wYear, stLocal.wHour, stLocal.wMinute, stLocal.wSecond); } -static long autoQuickSave = 0; -static long quickSavePage = 0; +static long quickSavePageInit = -1; +static long quickSavePageCount; +static long currentPageCount; + +static long quickSavePage = -1; +static long quickSaveSlot = 0; + +static long dontCheckSlot = 0; +static bool qFirst = true; static FILETIME ftPrevSlot; static DWORD __stdcall QuickSaveGame(fo::DbFile* file, char* filename) { - long currSlot = fo::var::slot_cursor; + long currSlot = quickSaveSlot; - if (file) { // This slot is not empty - fo::func::db_fclose(file); + if (dontCheckSlot) { + if (file) fo::func::db_fclose(file); + } else { + // for quick feature + if (file) { // This slot is not empty + fo::func::db_fclose(file); - FILETIME ftCurrSlot; - GetSaveFileTime(filename, &ftCurrSlot); + FILETIME ftCurrSlot; + GetSaveFileTime(filename, &ftCurrSlot); - if (currSlot == 0 || ftCurrSlot.dwHighDateTime > ftPrevSlot.dwHighDateTime || - (ftCurrSlot.dwHighDateTime == ftPrevSlot.dwHighDateTime && ftCurrSlot.dwLowDateTime > ftPrevSlot.dwLowDateTime)) - { - ftPrevSlot.dwHighDateTime = ftCurrSlot.dwHighDateTime; - ftPrevSlot.dwLowDateTime = ftCurrSlot.dwLowDateTime; + if (currSlot == 0 || + ftCurrSlot.dwHighDateTime > ftPrevSlot.dwHighDateTime || + (ftCurrSlot.dwHighDateTime == ftPrevSlot.dwHighDateTime && ftCurrSlot.dwLowDateTime > ftPrevSlot.dwLowDateTime)) + { + ftPrevSlot.dwHighDateTime = ftCurrSlot.dwHighDateTime; + ftPrevSlot.dwLowDateTime = ftCurrSlot.dwLowDateTime; - if (++currSlot > autoQuickSave) { - currSlot = 0; - } else { - fo::var::slot_cursor = currSlot; - return 0x47B929; // check next slot + if (!qFirst && currSlot < 9) { + fo::var::slot_cursor = ++quickSaveSlot; + return 0x47B929; // check next slot + } + currSlot = 0; // set if currSlot >= 9 + qFirst = false; + } + } + // next save slot + if (++quickSaveSlot >= 10) { + quickSaveSlot = 0; + // next page + if (quickSavePageCount > 1 && quickSavePageInit != -1) { + if (++currentPageCount >= quickSavePageCount) { + currentPageCount = 0; + quickSavePage = quickSavePageInit; + } else if (quickSavePage <= 9980) { + quickSavePage += 10; + } } } } @@ -452,7 +478,7 @@ static DWORD __stdcall QuickSaveGame(fo::DbFile* file, char* filename) { // Save to slot fo::var::slot_cursor = currSlot; fo::LSData* saveData = (fo::LSData*)FO_VAR_LSData; - CreateSaveComment(saveData[currSlot].comment); + CreateSaveComment(saveData[currSlot].comment, dontCheckSlot != 0); fo::var::quick_done = 1; return 0x47B9A4; // normal return @@ -472,13 +498,28 @@ static void __declspec(naked) SaveGame_hack0() { static void __declspec(naked) SaveGame_hack1() { __asm { - mov ds:[FO_VAR_slot_cursor], 0; + mov eax, quickSaveSlot; + mov ds:[FO_VAR_slot_cursor], eax; mov eax, quickSavePage; mov LSPageOffset, eax; retn; } } +long ExtraSaveSlots::GetQuickSavePage() { + return quickSavePage; +} + +long ExtraSaveSlots::GetQuickSaveSlot() { + return quickSaveSlot; +} + +void ExtraSaveSlots::SetQuickSaveSlot(long page, long slot, long check) { + if (quickSavePage >= 0 && page >= 0 && page <= 9990) quickSavePage = page - (page % 10); + if (slot >= 0 && slot < 10) quickSaveSlot = slot; + dontCheckSlot = check; +} + void ExtraSaveSlots::init() { bool extraSaveSlots = (IniReader::GetConfigInt("Misc", "ExtraSaveSlots", 0) != 0); if (extraSaveSlots) { @@ -487,19 +528,19 @@ void ExtraSaveSlots::init() { dlogr(" Done", DL_INIT); } - autoQuickSave = IniReader::GetConfigInt("Misc", "AutoQuickSave", 0); - if (autoQuickSave > 0) { + quickSavePageCount = IniReader::GetConfigInt("Misc", "AutoQuickSave", 0); + if (quickSavePageCount > 0) { dlog("Applying auto quick save patch.", DL_INIT); - if (autoQuickSave > 10) autoQuickSave = 10; - autoQuickSave--; // reserved slot count + if (quickSavePageCount > 10) quickSavePageCount = 10; - quickSavePage = IniReader::GetConfigInt("Misc", "AutoQuickSavePage", 0); + quickSavePage = IniReader::GetConfigInt("Misc", "AutoQuickSavePage", 1); if (quickSavePage > 1000) quickSavePage = 1000; if (extraSaveSlots && quickSavePage > 0) { quickSavePage = (quickSavePage - 1) * 10; + quickSavePageInit = quickSavePage; MakeCall(0x47B923, SaveGame_hack1, 1); - } else { + } else { // for quickSavePage == 0 SafeWrite8(0x47B923, 0x89); SafeWrite32(0x47B924, 0x5193B83D); // mov [slot_cursor], edi = 0 } diff --git a/sfall/Modules/ExtraSaveSlots.h b/sfall/Modules/ExtraSaveSlots.h index 0c319a33..f67a3fe2 100644 --- a/sfall/Modules/ExtraSaveSlots.h +++ b/sfall/Modules/ExtraSaveSlots.h @@ -23,13 +23,18 @@ namespace sfall { -extern DWORD LSPageOffset; - class ExtraSaveSlots : public Module { public: const char* name() { return "SuperSave"; } void init(); void exit() override; + + static long GetSaveSlot(); + static void SetSaveSlot(long page, long slot); + + static long GetQuickSavePage(); + static long GetQuickSaveSlot(); + static void SetQuickSaveSlot(long page, long slot, long check); }; } diff --git a/sfall/Modules/HookScripts/InventoryHs.cpp b/sfall/Modules/HookScripts/InventoryHs.cpp index 3f3a3c82..ff9d8c77 100644 --- a/sfall/Modules/HookScripts/InventoryHs.cpp +++ b/sfall/Modules/HookScripts/InventoryHs.cpp @@ -215,10 +215,10 @@ capsMultiDrop: static void __declspec(naked) InvenActionExplosiveDropHack() { __asm { pushadc; - xor ecx, ecx; // no itemReplace - push 6; // event: item drop ground - call InventoryMoveHook_Script; // edx - item - cmp eax, -1; // ret value + xor ecx, ecx; // no itemReplace + push 6; // event: item drop ground + call InventoryMoveHook_Script; // edx - item + cmp eax, -1; // ret value popadc; jnz noDrop; mov dword ptr ds:[FO_VAR_dropped_explosive], ebp; // overwritten engine code (ebp = 1) @@ -226,7 +226,7 @@ static void __declspec(naked) InvenActionExplosiveDropHack() { retn; noDrop: add esp, 4; - jmp InvenActionObjDropRet; // no drop + jmp InvenActionObjDropRet; // no drop } } @@ -626,15 +626,14 @@ void Inject_InventoryMoveHook() { Inject_SwitchHandHook(); MakeJump(0x4713A9, UseArmorHack); // old 0x4713A3 MakeJump(0x476491, DropIntoContainerHack); - MakeJump(0x471338, DropIntoContainerHandSlotHack); - MakeJump(0x4712AB, DropIntoContainerHandSlotHack); + MakeJumps(DropIntoContainerHandSlotHack, { 0x471338, 0x4712AB }); HookCall(0x471200, MoveInventoryHook); HookCall(0x476549, DropAmmoIntoWeaponHook); // old 0x476588 HookCalls(InvenActionCursorObjDropHook, { 0x473851, 0x47386F, 0x47379A // caps multi drop }); - MakeCall(0x473807, InvenActionExplosiveDropHack, 1); // drop active explosives + MakeCall(0x473807, InvenActionExplosiveDropHack, 1); // drop active explosives MakeCall(0x49B660, PickupObjectHack); SafeWrite32(0x49B665, 0x850FD285); // test edx, edx diff --git a/sfall/Modules/LoadGameHook.cpp b/sfall/Modules/LoadGameHook.cpp index 74f4f0c6..3c0666e3 100644 --- a/sfall/Modules/LoadGameHook.cpp +++ b/sfall/Modules/LoadGameHook.cpp @@ -123,7 +123,7 @@ void __stdcall SetInLoop(DWORD mode, LoopFlag flag) { } void GetSavePath(char* buf, char* ftype) { - sprintf(buf, "%s\\savegame\\slot%.2d\\sfall%s.sav", fo::var::patches, fo::var::slot_cursor + 1 + LSPageOffset, ftype); //add SuperSave Page offset + sprintf(buf, "%s\\savegame\\slot%.2d\\sfall%s.sav", fo::var::patches, ExtraSaveSlots::GetSaveSlot() + 1, ftype); //add SuperSave Page offset } static void __stdcall SaveGame2() { diff --git a/sfall/Modules/MetaruleExtender.cpp b/sfall/Modules/MetaruleExtender.cpp index 9822fc29..e235af03 100644 --- a/sfall/Modules/MetaruleExtender.cpp +++ b/sfall/Modules/MetaruleExtender.cpp @@ -18,6 +18,7 @@ #include "..\main.h" #include "..\FalloutEngine\Fallout2.h" +#include "ExtraSaveSlots.h" #include "LoadGameHook.h" #include "PartyControl.h" @@ -36,7 +37,14 @@ static bool HorriganEncounterDisabled = false; enum class MetaruleFunction : long { SET_HORRIGAN_ENCOUNTER = 200, // sets the number of days for the Frank Horrigan encounter or disable encounter CLEAR_KEYBOARD_BUFFER = 201, // clears the keyboard input buffer, should be used in the HOOK_KEYPRESS hook to clear keyboard events in some cases - EXT_PARTY_ORDER_ATTACK = 999 + PARTY_ORDER_ATTACK = 999, + + // save slot controls + GET_CURRENT_SAVE_SLOT = 1000, + SET_CURRENT_SAVE_SLOT = 1001, + GET_CURRENT_QSAVE_PAGE = 1002, + GET_CURRENT_QSAVE_SLOT = 1003, + SET_CURRENT_QSAVE_SLOT = 1004, }; /* @@ -63,9 +71,24 @@ static long __fastcall op_metarule3_ext(long metafunc, long* args) { case MetaruleFunction::CLEAR_KEYBOARD_BUFFER: __asm call fo::funcoffs::kb_clear_; break; - case MetaruleFunction::EXT_PARTY_ORDER_ATTACK: + case MetaruleFunction::PARTY_ORDER_ATTACK: PartyControl::OrderAttackPatch(); break; + case MetaruleFunction::GET_CURRENT_SAVE_SLOT: + result = ExtraSaveSlots::GetSaveSlot(); + break; + case MetaruleFunction::SET_CURRENT_SAVE_SLOT: + ExtraSaveSlots::SetSaveSlot(args[0], args[1]); + break; + case MetaruleFunction::GET_CURRENT_QSAVE_PAGE: + result = ExtraSaveSlots::GetQuickSavePage(); + break; + case MetaruleFunction::GET_CURRENT_QSAVE_SLOT: + result = ExtraSaveSlots::GetQuickSaveSlot(); + break; + case MetaruleFunction::SET_CURRENT_QSAVE_SLOT: + ExtraSaveSlots::SetQuickSaveSlot(args[0], args[1], args[2]); + break; default: fo::func::debug_printf("\nOPCODE ERROR: metarule3(%d, ...) - metarule function number does not exist.\n > Script: %s, procedure %s.", metafunc, fo::var::currentProgram->fileName, fo::func::findCurrentProc(fo::var::currentProgram));