@@ -125,6 +125,7 @@ static bool are_settings_different(const AppSettings *a, const AppSettings *b) {
125125 // Co-op settings
126126 a->network_mode != b->network_mode ||
127127 a->coop_goal_logic != b->coop_goal_logic ||
128+ strcmp (a->host_ip , b->host_ip ) != 0 ||
128129 strcmp (a->host_port , b->host_port ) != 0 ||
129130 strcmp (a->receiver_invite_code , b->receiver_invite_code ) != 0 ||
130131 a->coop_player_count != b->coop_player_count ) {
@@ -205,8 +206,9 @@ void settings_render_gui(bool *p_open, AppSettings *app_settings, ImFont *roboto
205206 // Flag to show a warning about hotkeys needing a settings window restart
206207 static bool show_hotkey_warning_message = false ;
207208
208- // Co-op invite code error flags (block Apply when true)
209+ // Co-op error flags (block Apply when true)
209210 static bool coop_invite_error = false ;
211+ static bool coop_host_input_error = false ;
210212
211213 // Holds temporary copy of the settings for editing
212214 static AppSettings temp_settings;
@@ -2285,6 +2287,7 @@ ImGui::SetTooltip("%s", tooltip_buffer); \
22852287 }
22862288 if ((NetworkMode) mode != temp_settings.network_mode ) {
22872289 coop_invite_error = false ;
2290+ coop_host_input_error = false ;
22882291 }
22892292 temp_settings.network_mode = (NetworkMode) mode;
22902293
@@ -2295,6 +2298,60 @@ ImGui::SetTooltip("%s", tooltip_buffer); \
22952298 ImGui::Text (" Host Settings" );
22962299 ImGui::Spacing ();
22972300
2301+ // Validate IP address (IPv4: four octets 0-255)
2302+ auto is_valid_ipv4 = [](const char *ip) -> bool {
2303+ if (!ip || ip[0 ] == ' \0 ' ) return false ;
2304+ int octets = 0 ;
2305+ int value = -1 ;
2306+ for (const char *p = ip; ; p++) {
2307+ if (*p >= ' 0' && *p <= ' 9' ) {
2308+ if (value < 0 ) value = 0 ;
2309+ value = value * 10 + (*p - ' 0' );
2310+ if (value > 255 ) return false ;
2311+ } else if (*p == ' .' || *p == ' \0 ' ) {
2312+ if (value < 0 ) return false ;
2313+ octets++;
2314+ value = -1 ;
2315+ if (*p == ' \0 ' ) break ;
2316+ } else {
2317+ return false ;
2318+ }
2319+ }
2320+ return octets == 4 ;
2321+ };
2322+
2323+ // Validate port (1-65535)
2324+ auto is_valid_port = [](const char *port) -> bool {
2325+ if (!port || port[0 ] == ' \0 ' ) return false ;
2326+ int value = 0 ;
2327+ for (const char *p = port; *p; p++) {
2328+ if (*p < ' 0' || *p > ' 9' ) return false ;
2329+ value = value * 10 + (*p - ' 0' );
2330+ if (value > 65535 ) return false ;
2331+ }
2332+ return value >= 1 ;
2333+ };
2334+
2335+ bool ip_filled = temp_settings.host_ip [0 ] != ' \0 ' ;
2336+ bool ip_valid = is_valid_ipv4 (temp_settings.host_ip );
2337+ bool port_filled = temp_settings.host_port [0 ] != ' \0 ' ;
2338+ bool port_valid = is_valid_port (temp_settings.host_port );
2339+
2340+ ImGui::SetNextItemWidth (200 .0f );
2341+ ImGui::InputText (" IP Address" , temp_settings.host_ip , sizeof (temp_settings.host_ip ));
2342+ if (ImGui::IsItemHovered ()) {
2343+ char tooltip_buf[256 ];
2344+ snprintf (tooltip_buf, sizeof (tooltip_buf),
2345+ " Your LAN or Hamachi/VPN IP address.\n "
2346+ " This is the IP receivers will connect to.\n "
2347+ " Find it via 'ipconfig' (Windows) or 'ifconfig' (Mac/Linux)." );
2348+ ImGui::SetTooltip (" %s" , tooltip_buf);
2349+ }
2350+ if (ip_filled && !ip_valid) {
2351+ ImGui::SameLine ();
2352+ ImGui::TextColored (ImVec4 (1 .0f , 0 .4f , 0 .4f , 1 .0f ), " Invalid IP address (expected format: x.x.x.x)" );
2353+ }
2354+
22982355 ImGui::SetNextItemWidth (120 .0f );
22992356 ImGui::InputText (" Port" , temp_settings.host_port , sizeof (temp_settings.host_port ),
23002357 ImGuiInputTextFlags_CharsDecimal);
@@ -2305,6 +2362,12 @@ ImGui::SetTooltip("%s", tooltip_buffer); \
23052362 " The 'Force Port Mod' defaults it to 25565." );
23062363 ImGui::SetTooltip (" %s" , tooltip_buf);
23072364 }
2365+ if (port_filled && !port_valid) {
2366+ ImGui::SameLine ();
2367+ ImGui::TextColored (ImVec4 (1 .0f , 0 .4f , 0 .4f , 1 .0f ), " Invalid port (must be 1-65535)" );
2368+ }
2369+
2370+ coop_host_input_error = (ip_filled && !ip_valid) || (port_filled && !port_valid);
23082371
23092372 ImGui::Spacing ();
23102373 ImGui::Separator ();
@@ -2490,24 +2553,59 @@ ImGui::SetTooltip("%s", tooltip_buffer); \
24902553 host_display_name = (first.display_name [0 ] != ' \0 ' ) ? first.display_name : first.username ;
24912554 }
24922555 bool no_host_name = !host_display_name || host_display_name[0 ] == ' \0 ' ;
2556+ bool no_host_ip = temp_settings.host_ip [0 ] == ' \0 ' ;
2557+ bool no_host_port = temp_settings.host_port [0 ] == ' \0 ' ;
2558+ bool generate_disabled = no_host_name || no_host_ip || no_host_port ||
2559+ !ip_valid || !port_valid || has_unsaved_changes;
24932560
2494- if (no_host_name ) ImGui::BeginDisabled ();
2561+ if (generate_disabled ) ImGui::BeginDisabled ();
24952562 if (ImGui::Button (" Generate & Copy Invite Code" )) {
24962563 char code[512 ];
2497- if (invite_code_generate (temp_settings.host_port , host_display_name, code, sizeof (code))) {
2564+ if (invite_code_generate (temp_settings.host_ip , temp_settings.host_port ,
2565+ host_display_name, code, sizeof (code))) {
24982566 SDL_SetClipboardText (code);
24992567 snprintf (invite_status_msg, sizeof (invite_status_msg),
25002568 " Invite code copied to clipboard!" );
25012569 coop_invite_error = false ;
25022570 } else {
25032571 snprintf (invite_status_msg, sizeof (invite_status_msg),
2504- " Failed to generate invite code (check internet connection) ." );
2572+ " Failed to generate invite code." );
25052573 coop_invite_error = true ;
25062574 }
25072575 }
25082576 if (ImGui::IsItemHovered (ImGuiHoveredFlags_AllowWhenDisabled)) {
25092577 char tooltip_buf[256 ];
2510- if (no_host_name) {
2578+ if (has_unsaved_changes) {
2579+ snprintf (tooltip_buf, sizeof (tooltip_buf),
2580+ " Apply your settings first before generating an invite code." );
2581+ } else if (!ip_valid && ip_filled) {
2582+ snprintf (tooltip_buf, sizeof (tooltip_buf),
2583+ " The IP address is invalid. Expected format: x.x.x.x" );
2584+ } else if (!port_valid && port_filled) {
2585+ snprintf (tooltip_buf, sizeof (tooltip_buf),
2586+ " The port is invalid. Must be a number between 1 and 65535." );
2587+ } else if (no_host_ip && no_host_port && no_host_name) {
2588+ snprintf (tooltip_buf, sizeof (tooltip_buf),
2589+ " Enter your IP address, port, and add at least one player to the roster first.\n "
2590+ " The first player's name is used as the host name in the invite code." );
2591+ } else if (no_host_ip && no_host_port) {
2592+ snprintf (tooltip_buf, sizeof (tooltip_buf),
2593+ " Enter your LAN/Hamachi IP address and port above first." );
2594+ } else if (no_host_ip && no_host_name) {
2595+ snprintf (tooltip_buf, sizeof (tooltip_buf),
2596+ " Enter your IP address and add at least one player to the roster first.\n "
2597+ " The first player's name is used as the host name in the invite code." );
2598+ } else if (no_host_port && no_host_name) {
2599+ snprintf (tooltip_buf, sizeof (tooltip_buf),
2600+ " Enter a port and add at least one player to the roster first.\n "
2601+ " The first player's name is used as the host name in the invite code." );
2602+ } else if (no_host_ip) {
2603+ snprintf (tooltip_buf, sizeof (tooltip_buf),
2604+ " Enter your LAN/Hamachi IP address above first." );
2605+ } else if (no_host_port) {
2606+ snprintf (tooltip_buf, sizeof (tooltip_buf),
2607+ " Enter a port above first." );
2608+ } else if (no_host_name) {
25112609 snprintf (tooltip_buf, sizeof (tooltip_buf),
25122610 " Add at least one player to the roster above first.\n "
25132611 " The first player's name is used as the host name in the invite code." );
@@ -2519,7 +2617,7 @@ ImGui::SetTooltip("%s", tooltip_buffer); \
25192617 }
25202618 ImGui::SetTooltip (" %s" , tooltip_buf);
25212619 }
2522- if (no_host_name ) ImGui::EndDisabled ();
2620+ if (generate_disabled ) ImGui::EndDisabled ();
25232621 if (invite_status_msg[0 ] != ' \0 ' ) {
25242622 ImGui::SameLine ();
25252623 if (coop_invite_error) {
@@ -2776,7 +2874,7 @@ ImGui::SetTooltip("%s", tooltip_buffer); \
27762874 // Disable "Apply Settings" button on visual editing mode or unsaved template editor changes
27772875 bool visual_editing = t && t->is_visual_layout_editing ;
27782876 bool template_unsaved = t && t->template_editor_has_unsaved_changes ;
2779- bool apply_disabled = visual_editing || template_unsaved || coop_invite_error;
2877+ bool apply_disabled = visual_editing || template_unsaved || coop_invite_error || coop_host_input_error ;
27802878
27812879 // Apply the changes or pressing Enter or Ctrl/Cmd + S keys in the settings window when NO popup is shown
27822880
@@ -2890,6 +2988,10 @@ ImGui::SetTooltip("%s", tooltip_buffer); \
28902988 snprintf (apply_button_tooltip_buffer, sizeof (apply_button_tooltip_buffer),
28912989 " Disabled while the Template Editor has unsaved changes.\n "
28922990 " Save or revert your template changes first, then apply settings." );
2991+ } else if (coop_host_input_error) {
2992+ snprintf (apply_button_tooltip_buffer, sizeof (apply_button_tooltip_buffer),
2993+ " Disabled due to an invalid IP address or port in the Co-op tab.\n "
2994+ " Fix the highlighted fields before applying." );
28932995 } else if (coop_invite_error) {
28942996 snprintf (apply_button_tooltip_buffer, sizeof (apply_button_tooltip_buffer),
28952997 " Disabled due to a Co-op invite code error.\n "
@@ -3070,6 +3172,10 @@ ImGui::SetTooltip("%s", tooltip_buffer); \
30703172 snprintf (restart_button_tooltip_buffer, sizeof (restart_button_tooltip_buffer),
30713173 " Disabled while the Template Editor has unsaved changes.\n "
30723174 " Save or revert your template changes first, then restart." );
3175+ } else if (coop_host_input_error) {
3176+ snprintf (restart_button_tooltip_buffer, sizeof (restart_button_tooltip_buffer),
3177+ " Disabled due to an invalid IP address or port in the Co-op tab.\n "
3178+ " Fix the highlighted fields before restarting." );
30733179 } else if (coop_invite_error) {
30743180 snprintf (restart_button_tooltip_buffer, sizeof (restart_button_tooltip_buffer),
30753181 " Disabled due to a Co-op invite code error.\n "
0 commit comments