16void wrap_text(
const char *input,
char *output,
size_t max_width)
18 size_t input_len = strlen(input);
19 size_t current_line_width = 0;
20 size_t output_index = 0;
22 for (
size_t i = 0; i < input_len; ++i)
24 output[output_index++] = input[i];
27 if (current_line_width >= max_width)
29 size_t break_point = output_index;
30 while (break_point > 0 && !isspace((
unsigned char)output[break_point - 1]))
37 output[break_point - 1] =
'\n';
38 current_line_width = output_index - break_point;
42 output[output_index++] =
'\n';
43 current_line_width = 0;
48 output[output_index] =
'\0';
77 length = strlen(char_string);
79 uiAPI.SFStringSetLength(_this, length);
82 mbstowcs(_this->
raw_data, char_string, length);
103 uiAPI.menuLabelSetString(label, sf_string);
115static bool is_mod_list_shown =
false;
116static bool does_mod_list_exist =
false;
121#define MOD_LIST_ROWS 11
124#define MOD_ROW_Y_START 40
125#define MOD_ROW_PITCH 44
126#define MOD_ROW_WIDTH 227
127#define MOD_ROW_HEIGHT 36
130#define MOD_INFO_PANEL_WIDTH 443
131#define MOD_INFO_TITLE_Y 34
132#define MOD_INFO_TITLE_H 34
133#define MOD_INFO_AUTHOR_Y 62
134#define MOD_INFO_AUTHOR_H 28
135#define MOD_INFO_TYPE_Y 98
136#define MOD_INFO_TYPE_H 28
137#define MOD_INFO_DESC_Y 136
138#define MOD_INFO_DESC_H 248
139#define MOD_INFO_ERROR_Y 396
140#define MOD_INFO_ERROR_H 200
149#define MOD_GLYPH_WIDTH 8
152#define MOD_TITLE_RGB 0.85f, 0.64f, 0.12f
153#define MOD_AUTHOR_RGB 0.75f, 0.75f, 0.75f
154#define MOD_TYPE_RGB 0.45f, 0.72f, 0.95f
155#define MOD_ERROR_RGB 1.0f, 0.0f, 0.0f
186 s_mod_buttons[row] =
nullptr;
187 s_mod_row_target[row] = -1;
189 is_mod_list_shown =
false;
190 does_mod_list_exist =
false;
201 int total_pages = (total_unique_mods + mods_per_page - 1) / mods_per_page;
202 return (total_pages == 0) ? 1 : total_pages;
207 return (page + total_pages) % total_pages;
212 snprintf(mod_title, buffer_size,
"%s - %s",
219 snprintf(mod_author, buffer_size,
"by %s",
223#define MOD_INFO_WRAP 50
226 char *wrapped_description,
size_t wrapped_buffer_size)
228 snprintf(mod_description, description_buffer_size,
"%s",
236 snprintf(mod_page_info, buffer_size,
"(%d / %d)",
237 (page + 1), total_pages);
241 char *wrapped_error_info,
size_t wrapped_buffer_size)
245 snprintf(mod_error_info, error_buffer_size,
"%s",
251 snprintf(mod_error_info, error_buffer_size,
" ");
252 snprintf(wrapped_error_info, wrapped_buffer_size,
" ");
268 if (current == index)
283 if (label ==
nullptr || text ==
nullptr)
289 uiAPI.updateLabelText(label, text);
295 if (label ==
nullptr)
300 uiAPI.setLabelColour(label, red, green, blue,
'\0');
301 uiAPI.setLabelColour(label, red, green, blue,
'\x01');
305static void set_button_label(
CMnuSmpButton *button,
const char *text)
307 if (button ==
nullptr)
315 uiAPI.setButtonName(button, label_string);
327 if (entry ==
nullptr)
334 char mod_title[512] = {0};
337 char mod_author[256] = {0};
340 char mod_description[512] = {0};
341 char wrapped_description[1024] = {0};
343 wrapped_description,
sizeof(wrapped_description));
345 char mod_error_info[512] = {0};
346 char wrapped_error_info[1024] = {0};
348 wrapped_error_info,
sizeof(wrapped_error_info));
374 int mod_index = first_mod + row;
375 const RegisteredMod *entry = (mod_index < total_mods) ? get_mod_at(mod_index) :
nullptr;
377 if (entry !=
nullptr)
379 s_mod_row_target[row] = mod_index;
380 set_button_label(s_mod_buttons[row], entry->
mod->
mod_id);
384 s_mod_row_target[row] = -1;
385 set_button_label(s_mod_buttons[row],
"");
389 char mod_page_info[48] = {0};
400 if (!does_mod_list_exist)
407 if (s_mod_buttons[row] == button)
409 if (s_mod_row_target[row] >= 0)
421static void navigate_page(
int delta)
423 if (!does_mod_list_exist)
444 char row_default[128] =
"ui_mainmenu_button_default.msh";
445 char row_pressed[128] =
"ui_mainmenu_button_pressed.msh";
446 char row_disabled[128] =
"ui_mainmenu_button_disabled.msh";
447 char row_load[1] =
"";
448 char row_label[1] =
"";
452 s_mod_row_target[row] = -1;
453 s_mod_buttons[row] =
uiAPI.attachNewButton(
454 list_panel, row_default, row_pressed, row_load, row_disabled,
462 char btn_disabled[128] =
"ui_btn_togglearrow_right_disabled.msh";
463 char btn_pressed[128] =
"ui_btn_togglearrow_right_pressed.msh";
464 char btn_load[1] =
"";
465 char btn_default[128] =
"ui_btn_togglearrow_right_default.msh";
466 char btn_label[1] =
"";
468 right_nav =
uiAPI.attachNewButton(list_panel, btn_default, btn_pressed, btn_load,
469 btn_disabled, btn_label, 7, (443 - 96),
MOD_NAV_Y,
470 48, 48, 0, (uint32_t) &navigate_page_right);
472 char btn_disabled_left[128] =
"ui_btn_togglearrow_left_disabled.msh";
473 char btn_pressed_left[128] =
"ui_btn_togglearrow_left_pressed.msh";
474 char btn_default_left[128] =
"ui_btn_togglearrow_left_default.msh";
476 left_nav =
uiAPI.attachNewButton(list_panel, btn_default_left, btn_pressed_left,
477 btn_load, btn_disabled_left, btn_label, 7, 48,
MOD_NAV_Y,
478 48, 48, 1, (uint32_t) &navigate_page_left);
480 char page_placeholder[8] =
"";
481 mod_struct.page_label =
uiAPI.attachLabel(
nullptr, list_panel, page_placeholder,
491 char placeholder[2] =
" ";
493 mod_struct.title_label =
uiAPI.attachLabel(
nullptr, info_panel, placeholder,
500 mod_struct.author_label =
uiAPI.attachLabel(
nullptr, info_panel, placeholder,
506 mod_struct.type_label =
uiAPI.attachLabel(
nullptr, info_panel, placeholder,
512 mod_struct.desc_label =
uiAPI.attachLabel(
nullptr, info_panel, placeholder,
518 mod_struct.error_label =
uiAPI.attachLabel(
nullptr, info_panel, placeholder,
530 if (parent_container !=
nullptr)
532 uiAPI.setContainerVisible(parent_container,
false,
false);
534 is_mod_list_shown =
false;
542 const char *background_msb,
543 const char *border_msb,
554 log_error(
"Failed to allocate CMnuContainer");
558 uiAPI.initializeMenuContainer(container);
563 uiAPI.setupMenuContainerData(
575 void *CMnuVisControl = (
void *)
uiAPI.getVisualControl(container);
576 uiAPI.setBaseAlpha(CMnuVisControl, alpha);
583 char close_btn_default[128] =
"ui_btn_nav_back_default.msh";
584 char close_btn_pressed[128] =
"ui_btn_nav_back_pressed.msh";
585 char close_btn_disabled[128] =
"ui_btn_nav_back_disabled.msh";
586 char close_btn_load[1] =
"";
587 char close_btn_label[1] =
"";
589 uiAPI.attachNewButton(
609 if(!does_mod_list_exist)
611 is_mod_list_shown =
true;
614 "ui_bgr_landscape_bg.msb",
620 "ui_bgr_pregame_border_transparency.msb",
621 "ui_bgr_pregame_border.msb", 0.5f
626 "ui_bgr_pregame_border_right_transparency.msb",
627 "ui_bgr_pregame_border_right.msb", 0.5f
632 "ui_bgr_pregame_border_right_transparency.msb",
633 "ui_bgr_pregame_border_right.msb", 0.5f
638 log_error(
"Unable to create Mod Menu, a container allocation failed");
654 uiAPI.destroyContainer(mod_list_info);
675 char sfsf_mod_info[32] =
"Mod Information";
682 does_mod_list_exist =
true;
687 is_mod_list_shown = !is_mod_list_shown;
693 char *button_mesh_default,
694 char *button_mesh_pressed,
695 char *button_initial_load_mesh,
696 char *button_mesh_disabled,
char *label_char,
697 uint8_t font_index, uint16_t x_pos,
698 uint16_t y_pos, uint16_t width,
699 uint16_t height,
int button_index,
700 uint32_t callback_func_ptr)
716 button_mesh_default);
720 button_mesh_pressed);
724 button_initial_load_mesh);
735 log_error(
"Invalid font index 0~32, defaulting to font 6");
739 new_button =
uiAPI.initializeSmpButton(new_button);
740 SF_Font *selected_font =
uiAPI.getFont(fonts, font_index);
744 uiAPI.createButton(new_button,x_pos,y_pos,width,height,mesh_string_default,
745 init_load_mesh,mesh_string_pressed,mesh_string_disabled);
747 uiAPI.setFont(new_button, selected_font);
749 uiAPI.setButtonIndex(new_button, button_index);
751 uiAPI.setMenuButtonFlag(new_button,
'\x01');
753 uiAPI.setButtonName(new_button, label_string);
760 uint32_t param1, param2, param3;
762 uiAPI.attachCallback(&callback, ¶m1, ¶m2, ¶m3);
768 uiAPI.vfunction16AttachCallback(new_button,
'\x01');
770 uiAPI.containerAddControl(parent, (
CMnuBase *)new_button,
'\x01',
'\x01', 0);
782 const char *mod_author,
const char *mod_description)
788 log_error(
"Unable to allocate SFMod for [%s]", (mod_id !=
nullptr) ? mod_id :
"<unnamed>");
838 uint16_t x_pos, uint16_t y_pos,
839 uint16_t width, uint16_t height)
852 log_error(
"Invalid font index 0~32, defaulting to font 6");
856 SF_Font *selected_font =
uiAPI.getFont(fonts, font_index);
858 uiAPI.menuLabelConstructor(new_label);
862 uiAPI.initMenuElement(new_label, x_pos, y_pos, width, height, mesh_string);
864 uiAPI.menuLabelSetFont(new_label, selected_font);
866 uiAPI.containerAddControl(parent, (
CMnuBase *) new_label,
'\x01',
'\x01', 0);
868 uiAPI.menuLabelSetString(new_label, label_string);
878 char *label_chars, uint8_t font_index,
879 uint16_t x_pos, uint16_t y_pos,
880 uint16_t width, uint16_t height)
884 font_index, x_pos, y_pos, width, height);
const char * get_mod_type_label(ModType type)
Tag rendered next to a mod in the list. Empty for external mods.
void clear_mod_errors(SFMod *mod)
Empties a mod's error buffer.
const std::list< RegisteredMod > & get_registered_mods()
The mods known to the framework, grouped framework -> core -> external.
void log_error(const char *format,...)
SF_String_ctor_ptr g_create_sf_string
SF_String_dtor_ptr g_destroy_sf_string
int AddrOf(int offset)
returns "real" virtual address of given memory offset
void prepare_mod_title(SFMod *parent_mod, char *mod_title, size_t buffer_size)
CMnuLabel *__thiscall attachMeshedLabel(CMnuLabel *new_label, CMnuContainer *parent, char *mesh_char, char *label_char, uint8_t font_index, uint16_t x_pos, uint16_t y_pos, uint16_t width, uint16_t height)
#define MOD_LIST_ROWS
Number of mod rows the left panel can show at once.
void prepare_mod_error_info(SFMod *parent_mod, char *mod_error_info, size_t error_buffer_size, char *wrapped_error_info, size_t wrapped_buffer_size)
#define MOD_INFO_AUTHOR_Y
int normalize_page_index(int page, int total_pages)
int calculate_total_pages(int total_unique_mods, int mods_per_page)
void reset_mod_list_screen()
Drops every cached mod list pointer, this will ensure we don't crash after a map load.
CMnuContainer * mod_info_page
void __thiscall updateLabelText(CMnuLabel *label, const char *text)
CMnuContainer * mod_container
void show_mod_details(int mod_index)
Renders one mod into the right hand info panel.
void __thiscall show_mod_list(CMnuSmpButton *button)
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 __fastcall close_mod_list_callback(CMnuSmpButton *button, int32_t *cui_menu_ptr_maybe)
void __thiscall on_mod_selected(CMnuSmpButton *button)
Left panel row callback.
void prepare_mod_description(SFMod *parent_mod, char *mod_description, size_t description_buffer_size, char *wrapped_description, size_t wrapped_buffer_size)
void prepare_mod_author(SFMod *parent_mod, char *mod_author, size_t buffer_size)
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.
void build_mod_info_panel(CMnuContainer *info_panel)
Creates the right hand panel's labels.
#define MOD_INFO_AUTHOR_H
SF_String *__thiscall SFStringConstructor_char(SF_String *_this, const char *char_string)
Constructs an SF_String from a null-terminated char string. Moved here from sf_vanilla_fix_hook....
CMnuContainer *__thiscall createContainer(uint16_t x, uint16_t y, uint16_t width, uint16_t height, const char *background_msb, const char *border_msb, float alpha)
CMnuLabel *__thiscall attachLabel(CMnuLabel *label_ptr, CMnuContainer *parent, char *label_chars, uint8_t font_index, uint16_t x_pos, uint16_t y_pos, uint16_t width, uint16_t height)
CMnuSmpButton *__thiscall attachNewButton(CMnuContainer *parent, char *button_mesh_default, char *button_mesh_pressed, char *button_initial_load_mesh, char *button_mesh_disabled, char *label_char, uint8_t font_index, uint16_t x_pos, uint16_t y_pos, uint16_t width, uint16_t height, int button_index, uint32_t callback_func_ptr)
CMnuLabel * mod_list_title
void build_mod_list_panel(CMnuContainer *list_panel)
Creates the clickable mod rows, the paging arrows and the counter.
void __thiscall SFStringConstructor(SF_String *_this)
Zero-initializes an SF_String (no allocation). Moved here from sf_vanilla_fix_hook....
CMnuSmpButton * right_nav
int calculate_total_unique_mods()
SFMod * createModInfo(const char *mod_id, const char *mod_version, const char *mod_author, const char *mod_description)
void refresh_mod_list_page(int page)
Points the visible rows at the mods on the given page.
void prepare_mod_page_info(int page, int total_pages, char *mod_page_info, size_t buffer_size)
#define MOD_INFO_PANEL_WIDTH
void add_close_button(CMnuContainer *mod_list)
uint32_t param_1_callback
uint32_t param_2_callback
uint32_t param_3_callback
A mod as shown in the in-game mod list.
char mod_description[128]