25static void hook_getsavepath();
29static void hook_initfirstmap();
66static void hook_qs_load();
67static void hook_check_dirs();
68static void hook_ql_helper();
70static uint32_t s_ql_return_ok;
71static uint32_t s_ql_return_fail;
72static uint32_t s_hdr_result_ok;
126 log_error(
"Campaign registry full or NULL definition");
134 log_info(
"Registered campaign %u: %s (type %u, map: %s, saves: %s)",
146 if (stat(path, &stats) == 0)
148 return S_ISDIR(stats.st_mode);
160 uiAPI.SFStringConstructor_char(&back_slash,
"\\");
161 uiAPI.SFStringConstructor_char(&base_dirs[0],
"save");
162 uiAPI.SFStringConstructor_char(&base_dirs[1],
"temp");
163 uiAPI.SFStringConstructor_char(&base_dirs[2],
"char");
164 uiAPI.SFStringConstructor_char(&base_dirs[3],
"campaign2");
165 uiAPI.SFStringConstructor_char(&base_dirs[4],
"campaign3");
167 s_getDataStorageLocation(paths, 0);
168 uiAPI.SFStringConstructor_char(&base_path, paths[0]);
170 uiAPI.SFStringConcat(&base_path, &base_dirs[0]);
171 char *real_path =
uiAPI.SFStringCMbStr(&base_path);
178 for (
int i = 1; i < 5; i++)
181 uiAPI.SFStringConstructor_char(&temp, real_path);
182 uiAPI.SFStringConcat(&temp, &back_slash);
183 uiAPI.SFStringConcat(&temp, &base_dirs[i]);
184 char *temp_path =
uiAPI.SFStringCMbStr(&temp);
189 uiAPI.SFStringDestructor(&temp);
200 uiAPI.SFStringConstructor_char(&temp, real_path);
203 uiAPI.SFStringConcat(&temp, &back_slash);
204 uiAPI.SFStringConcat(&temp, &camp_folder);
205 char *temp_path =
uiAPI.SFStringCMbStr(&temp);
210 uiAPI.SFStringDestructor(&temp);
211 uiAPI.SFStringDestructor(&camp_folder);
217 for (
int i = 0; i < 5; i++)
219 uiAPI.SFStringDestructor(&base_dirs[i]);
221 uiAPI.SFStringDestructor(&base_path);
222 uiAPI.SFStringDestructor(&back_slash);
230 s_getDataStorageLocation(paths, 0);
231 uiAPI.SFStringConstructor_char(output, paths[0]);
232 uiAPI.SFStringConstructor_wchar(&base_save, L
"save\\");
233 uiAPI.SFStringConcat(output, &base_save);
234 uiAPI.SFStringDestructor(&base_save);
236 switch (campaign_type)
246 uiAPI.SFStringConstructor_wchar(&campaign_path, L
"campaign2\\");
247 uiAPI.SFStringConcat(output, &campaign_path);
248 uiAPI.SFStringDestructor(&campaign_path);
255 uiAPI.SFStringConstructor_wchar(&campaign_path, L
"campaign3\\");
256 uiAPI.SFStringConcat(output, &campaign_path);
257 uiAPI.SFStringDestructor(&campaign_path);
275 log_error(
"getSavePath: no registered campaign for type %u, using the base save folder",
284 uiAPI.SFStringConstructor_char(&back_slash,
"\\");
285 uiAPI.SFStringConcat(output, &campaign_path);
286 uiAPI.SFStringConcat(output, &back_slash);
287 uiAPI.SFStringDestructor(&campaign_path);
288 uiAPI.SFStringDestructor(&back_slash);
312 uiAPI.SFStringConstructor(&avatar_name);
314 uiAPI.SFStringConstructor_char(&postfix,
"~QUICKSAVE.sav");
315 uiAPI.SFStringConcat(&avatar_name, &postfix);
318 uiAPI.SFStringConcat(&base_path,&avatar_name);
319 uint32_t shallContinue =
false;
325 shallContinue =
true;
329 uiAPI.SFStringDestructor(&avatar_name);
330 uiAPI.SFStringDestructor(&postfix);
331 uiAPI.SFStringDestructor(&base_path);
343static void __declspec(naked) quickload_hook()
345 asm (
"mov %%esi, %%ecx \n\t"
347 "test %%eax, %%eax \n\t"
351 "o" (s_ql_return_ok),
"o" (s_ql_return_fail) );
380static void __declspec(naked) hdr_helper()
382 asm (
"mov 0x3c4(%%ebx), %%eax \n\t"
383 "mov %%eax, %%ecx \n\t"
385 "mov %%eax, %%ecx \n\t"
386 "lea -0x330(%%ebp), %%eax \n\t"
390 "o" (s_hdr_result_ok) );
420 bool inUse = (player->
players[player_id].
use != 0);
432 uiAPI.SFStringFromWchar(&tilda, L
'~', 1);
433 uiAPI.SFStringConstructor_wchar(&quicksave, L
"QUICKSAVE.SAV");
434 uiAPI.SFStringConcat(&avatar_name, &tilda);
435 uiAPI.SFStringConcat(&avatar_name, &quicksave);
439 uiAPI.SFStringConcat(&base_path, &avatar_name);
453 uiAPI.SFStringConstructor(&dummy);
455 uiAPI.SFStringDestructor(&dummy);
463 uiAPI.SFStringDestructor(&avatar_name);
464 uiAPI.SFStringDestructor(&tilda);
465 uiAPI.SFStringDestructor(&quicksave);
466 uiAPI.SFStringDestructor(&base_path);
478static void hook_check_dirs()
499 uint8_t subskill, uint32_t campaign_id,
bool is_shadowblade)
524 uiAPI.SFStringConstructor_char(&dot_map,
".map");
525 uiAPI.SFStringConstructor(&full_template);
526 if (custom != NULL && custom->
starterkit[0] !=
'\0')
532 uiAPI.SFStringConstructor(&default_template);
533 uiAPI.SFprintf(&default_template, L
"SK_%02d%02d.des", skill, subskill);
535 uiAPI.SFStringConstructor_char(&template_path,
"figure_template\\starterkit\\");
536 uiAPI.SFStringConcat(&full_template, &template_path);
537 uiAPI.SFStringConcat(&full_template, &default_template);
559 snprintf(folder,
sizeof(folder),
"map\\%s\\", custom->
campaign_folder);
560 uiAPI.SFStringConstructor_char(&campagn_path, folder);
561 uiAPI.SFStringConstructor_char(&intial_map_name, custom->
start_map);
578 uiAPI.SFStringConstructor_char(&campagn_path,
"map\\Campaign2\\");
579 uiAPI.SFStringConstructor_wchar(&intial_map_name, L
"P101_Mirraw_Thur");
583 uiAPI.SFStringConstructor_char(&campagn_path,
"map\\Campaign3\\");
586 uiAPI.SFStringConstructor_wchar(&intial_map_name, L
"P202_City_Of_Souls");
590 uiAPI.SFStringConstructor_wchar(&intial_map_name, L
"P201_Blackwater_Coast");
605 log_error(
"initFirstMap: campaign type %u has no registered campaign, starting Order instead",
609 uiAPI.SFStringConstructor_char(&campagn_path,
"map\\Campaign\\");
610 uiAPI.SFStringConstructor_wchar(&intial_map_name, L
"000_Greyfell");
630 uiAPI.SFStringDestructor(&intial_map_name);
637 uiAPI.SFStringConstructor_wchar(&intial_map_name, L
"tutorial");
644 uiAPI.SFStringConstructor_wchar(&tutorial_template, L
"FT_Tutorial.des");
645 uiAPI.SFStringConstructor_char(&tutorial_template_path,
"figure_template\\");
646 uiAPI.SFStringConstructor(&tutorial_kit_name);
647 uiAPI.SFStringConcat(&tutorial_kit_name, &tutorial_template_path);
648 uiAPI.SFStringConcat(&tutorial_kit_name, &tutorial_template);
650 uiAPI.SFStringDestructor(&tutorial_kit_name);
651 uiAPI.SFStringDestructor(&tutorial_template);
652 uiAPI.SFStringDestructor(&tutorial_template_path);
661 uiAPI.SFStringConcat(&campagn_path, &intial_map_name);
662 uiAPI.SFStringConcat(&campagn_path, &dot_map);
668 uiAPI.SFStringDestructor(&default_template);
669 uiAPI.SFStringDestructor(&template_path);
670 uiAPI.SFStringDestructor(&full_template);
671 uiAPI.SFStringDestructor(&dot_map);
672 uiAPI.SFStringDestructor(&campagn_path);
673 uiAPI.SFStringDestructor(&intial_map_name);
677void hook_initfirstmap()
687#define CAMPAIGN_PANEL_WIDTH 443
688#define CAMPAIGN_NAME_Y 32
689#define CAMPAIGN_NAME_H 48
690#define CAMPAIGN_AUTHOR_Y 88
691#define CAMPAIGN_AUTHOR_H 28
692#define CAMPAIGN_DESC_Y 140
693#define CAMPAIGN_DESC_H 360
694#define CAMPAIGN_PLAY_X 108
695#define CAMPAIGN_PLAY_Y 540
696#define CAMPAIGN_PLAY_W 227
697#define CAMPAIGN_PLAY_H 36
700#define CAMPAIGN_DESC_WRAP 40
704#define CAMPAIGN_ROW_X 108
705#define CAMPAIGN_ROW_Y_START 40
706#define CAMPAIGN_ROW_PITCH 36
707#define CAMPAIGN_ROW_W 227
708#define CAMPAIGN_ROW_H 30
710static bool s_screen_exists =
false;
711static bool s_screen_visible =
false;
714static CMnuLabel *s_campaign_name_label = NULL;
715static CMnuLabel *s_campaign_author_label = NULL;
716static CMnuLabel *s_campaign_desc_label = NULL;
719static int32_t s_selected_campaign = -1;
725 s_screen_exists =
false;
726 s_screen_visible =
false;
727 s_campaign_screen = NULL;
728 s_campaign_name_label = NULL;
729 s_campaign_author_label = NULL;
730 s_campaign_desc_label = NULL;
731 s_selected_campaign = -1;
732 memset(s_campaign_buttons, 0,
sizeof(s_campaign_buttons));
735static void hook_getsavepath()
750 s_video_sequence_stop(seq);
762 log_error(
"Campaign launch unavailable: missing CAppMenu or engine addresses");
771 s_enter_campaign_flow(app_menu, 1);
777static void show_campaign_details(int32_t campaign_index)
784 s_selected_campaign = campaign_index;
788 snprintf(author,
sizeof(author),
"by %s",
791 char wrapped_description[1024] = {0};
807 if (s_campaign_buttons[i] == _this)
809 show_campaign_details((int32_t)i);
813 log_error(
"Campaign button not found in registry");
819 if (s_selected_campaign < 0 || s_selected_campaign >= (int32_t)
g_campaign_count)
821 log_error(
"Play pressed with no campaign selected");
830 if (s_campaign_screen != NULL)
832 uiAPI.setContainerVisible(s_campaign_screen,
false, 0);
834 s_screen_visible =
false;
844 if (s_screen_exists && s_campaign_screen != NULL)
846 s_screen_visible = !s_screen_visible;
847 uiAPI.setContainerVisible(s_campaign_screen, s_screen_visible, 0);
851 s_campaign_screen =
uiAPI.createContainer(0, 0, 1024, 768,
852 "ui_bgr_landscape_bg.msb",
"", 0.99f);
854 "ui_bgr_pregame_border_transparency.msb",
855 "ui_bgr_pregame_border.msb", 0.5f);
857 "ui_bgr_pregame_border_left_transparency.msb",
858 "ui_bgr_pregame_border_left.msb", 0.5f);
860 "ui_bgr_pregame_border_right_transparency.msb",
861 "ui_bgr_pregame_border_right.msb", 0.5f);
863 if (!s_campaign_screen || !frame || !list_panel || !detail_panel)
865 log_error(
"Unable to create Campaign Menu containers");
867 if (s_campaign_screen)
869 uiAPI.destroyContainer(s_campaign_screen);
873 uiAPI.destroyContainer(frame);
877 uiAPI.destroyContainer(list_panel);
881 uiAPI.destroyContainer(detail_panel);
884 s_campaign_screen = NULL;
885 s_screen_exists =
false;
886 s_screen_visible =
false;
890 uiAPI.containerAddControl(parent, (
CMnuBase *)s_campaign_screen,
'\x01',
'\x01', 0);
891 uiAPI.containerAddControl(s_campaign_screen, (
CMnuBase *)frame,
'\x01',
'\x01', 0);
892 uiAPI.containerAddControl(frame, (
CMnuBase *)list_panel,
'\x01',
'\x01', 0);
893 uiAPI.containerAddControl(frame, (
CMnuBase *)detail_panel,
'\x01',
'\x01', 0);
895 char title[32] =
"Custom Campaigns";
896 CMnuLabel *title_label =
uiAPI.attachLabel(NULL, frame, title, 6, 468, 16, 128, 16);
897 uiAPI.setMenuID(title_label, 0x6);
900 char btn_default[32] =
"ui_mainmenu_button_default.msh";
901 char btn_pressed[32] =
"ui_mainmenu_button_pressed.msh";
902 char btn_disabled[32] =
"ui_mainmenu_button_disabled.msh";
903 char btn_load[1] =
"";
907 s_campaign_buttons[i] =
uiAPI.attachNewButton(
909 btn_default, btn_pressed, btn_load, btn_disabled,
918 char placeholder[2] =
" ";
920 s_campaign_name_label =
uiAPI.attachLabel(NULL, detail_panel, placeholder, 6,
923 uiAPI.setMenuID(s_campaign_name_label, 0x6);
926 s_campaign_author_label =
uiAPI.attachLabel(NULL, detail_panel, placeholder, 6,
929 uiAPI.setMenuID(s_campaign_author_label, 0x6);
932 s_campaign_desc_label =
uiAPI.attachLabel(NULL, detail_panel, placeholder, 11,
935 uiAPI.setMenuID(s_campaign_desc_label, 0x6);
937 char play_label[8] =
"PLAY";
938 uiAPI.attachNewButton(detail_panel,
939 btn_default, btn_pressed, btn_load, btn_disabled,
946 char back_default[32] =
"ui_btn_nav_back_default.msh";
947 char back_pressed[32] =
"ui_btn_nav_back_pressed.msh";
948 char back_disabled[32] =
"ui_btn_nav_back_disabled.msh";
949 char back_load[1] =
"";
950 char back_label[1] =
"";
951 uiAPI.attachNewButton(s_campaign_screen,
952 back_default, back_pressed, back_load, back_disabled,
953 back_label, 7, 52, 678, 48, 48, 31,
956 s_screen_exists =
true;
957 s_screen_visible =
true;
959 show_campaign_details(0);
CUtlCallBackInit_ptr CUtlCallBackInit
void campaign_launch_flow(int32_t campaign_index)
void(__thiscall * videoSequenceStop_ptr)(SF_CUiVideoSequence *_this)
void close_campaign_screen_callback(CMnuSmpButton *button)
void(__thiscall * appMenuEnterCampaignFlow_ptr)(CAppMenu *_this, uint32_t param_1)
#define CAMPAIGN_ROW_Y_START
SF_String *(__thiscall * AC95_get_figure_name_ptr)(void *AC95, SF_String *name_buffer, uint32_t figure_id)
void(__thiscall * AC82_Zero_ptr)(AutoClass82 *_this)
CMnuBaseFlagGetter_ptr CMnuBaseIsVisible
void(__thiscall * CUiGame_009a1fd0_ptr)(void *_this, uint32_t param1)
#define CAMPAIGN_AUTHOR_Y
gameInfoSetAvatarType_ptr s_gameinfo_set_avatar_type
uint32_t __thiscall getHdrStringID(uint32_t camp_type)
#define CAMPAIGN_DESC_WRAP
void(__thiscall * some_vector_fun_ptr)(void *_this, uint32_t *size)
uint32_t g_campaign_count
void(__thiscall * gameInfoSetMapPathFull_ptr)(SF_GameInfo *_this, SF_String *map_path)
uint8_t __thiscall quickLoad_helper(SF_CUiMain *_this)
void(__thiscall * prepareTransition_ptr)(CAppMenu *_this, uint32_t param_1, uint32_t param_2)
uint32_t(__cdecl * checkFileExists_ptr)(SF_String *name)
The engine's file existence check, resolved by initialize_campaign_hooks().
SF_String *__thiscall getSavePath(CAppSession *_this, SF_String *output, uint32_t campaign_type)
void stop_intro_video(CAppMenu *app_menu)
uint32_t(__thiscall * CMnuBaseFlagGetter_ptr)(CMnuBase *_this)
int32_t register_campaign(const SFSF_CampaignDef *def)
void(__thiscall * gameInfoSetAvatarType_ptr)(SF_GameInfo *_this, uint16_t avatar_type)
#define CAMPAIGN_PANEL_WIDTH
void __thiscall show_custom_campaign_screen(CMnuSmpButton *_this)
Builds the campaign screen.
some_vector_fun_ptr some_vector_dispose
void(__thiscall * avatarVectorsCopy_ptr)(void *dest_vectors, void *src_vectors)
startGame_ptr s_start_game
initDefaultInfo_ptr initDefaultInfo
void initialize_preparenewgame_rewrite()
#define CAMPAIGN_ROW_PITCH
SFSF_CampaignDef g_campaigns[16]
void(__fastcall * playCampaignIntro_ptr)(CAppMenu *_this)
some_vector_fun_ptr some_vector_init
void __thiscall on_campaign_selected(CMnuSmpButton *_this)
Left panel row callback.
LPCSTR *(__cdecl * GetDataStorageLocation_ptr)(char **param_1, uint32_t type)
getBasePathString_ptr s_get_base_path_string
int isDirectoryExists(const char *path)
#define SFSF_CAMPAIGN_TYPE_BASE
void(__thiscall * CUiGameZero_ptr)(void *_this)
GdAvatarInternal *(__thiscall * avatarInternalCopy_ptr)(GdAvatarInternal *dest, GdAvatarInternal *src)
CUiGameZero_ptr CUiGameZero
SF_GameInfo *(__thiscall * initDefaultInfo_ptr)(SF_GameInfo *_this)
void __thiscall on_campaign_play(CMnuSmpButton *_this)
Play button callback - launches whatever the list has selected.
checkFileExists_ptr checkFileExists
void __thiscall initFirstMap(SF_GameInfo *_this, uint32_t skip_tutorial, uint8_t skill, uint8_t subskill, uint32_t campaign_id, bool is_shadowblade)
Proper map selection code for the future.
void initialize_campaign_hooks()
#define CAMPAIGN_AUTHOR_H
void __thiscall loadQuickSave(CAppMenu *_this, uint32_t unknown)
CUiGame_009a1fd0_ptr CUiGame_009a1fd0
playCampaignIntro_ptr s_play_campaign_intro
#define SFSF_MAX_CAMPAIGNS
int32_t g_active_custom_campaign
void campaign_hook_on_main_menu(CAppMenu *app_menu)
void(__thiscall * startGame_ptr)(CAppMenu *_this, SF_GameInfo *game_info, uint32_t param_2, uint32_t param_3, uint32_t param_4, uint32_t coord, SF_String *param_6)
char **(__cdecl * getBasePathString_ptr)(void *out_string_obj)
AC95_get_figure_name_ptr AC95_get_figure_name
CAppMenu * g_campaign_app_menu
void(__thiscall * CUtlCallBackInit_ptr)(void *_this, void *param1, void *param2, uint32_t param3)
prepareTransition_ptr prepareTransition
void checkDirs(CAppSession *_this)
void log_error(const char *format,...)
void log_info(const char *format,...)
int AddrOf(int offset)
returns "real" virtual address of given memory offset
bool BeginRewrite(MemoryRegion &mem_region)
allows rewriting code in a given memory region by overwriting permissions for that memory region
bool EndRewrite(MemoryRegion &mem_region)
ends rewriting by restoring old permissions in a given memory region
void wrap_text(const char *input, char *output, size_t max_width)
Wraps text to a character width, inserting newlines.
void apply_label_colour(CMnuLabel *label, float red, float green, float blue)
Sets both colour slots of a label. (normal and on hover).
void set_centred_label_text(CMnuLabel *label, const char *text, int panel_width, int y, int height)
Repositions a label so its text block sits centred, then sets the text.
MemoryRegion is used for rewriting code in a given memory block.
CGdControllerClient * controllerClient
CGdControllerClientData data
uint32_t param_2_callback
CGdControllerClient * CGdControllerClient
GdAvatarInternal internal
uint16_t avatar_figure_index
CUiMain_data CUiMain_data
SF_String starter_kit_name