Skip to content

Commit 6748af0

Browse files
committed
Revised networking for local network.
1 parent dbab0c3 commit 6748af0

5 files changed

Lines changed: 136 additions & 75 deletions

File tree

source/invite_code.cpp

Lines changed: 4 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,9 @@
99

1010
#include "invite_code.h"
1111
#include "logger.h"
12-
#include <curl/curl.h>
13-
#include <string>
1412
#include <cstring>
1513
#include <cstdio>
1614

17-
#define CERT_BUNDLE_PATH "resources/ca_certificates/cacert.pem"
18-
1915
// --- Base64 encode/decode (RFC 4648) ---
2016

2117
static const char b64_table[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
@@ -81,67 +77,12 @@ static bool base64_decode(const char *input, char *output, size_t output_len, si
8177
return true;
8278
}
8379

84-
// --- Public IP fetch via api.ipify.org ---
85-
86-
static size_t ip_write_callback(void *contents, size_t size, size_t nmemb, std::string *s) {
87-
size_t new_length = size * nmemb;
88-
try {
89-
s->append((char *)contents, new_length);
90-
} catch (std::bad_alloc &) {
91-
return 0;
92-
}
93-
return new_length;
94-
}
95-
96-
static bool fetch_public_ip(char *out_ip, size_t ip_len) {
97-
CURL *curl = curl_easy_init();
98-
if (!curl) {
99-
log_message(LOG_ERROR, "[INVITE CODE] Failed to initialize curl.\n");
100-
return false;
101-
}
102-
103-
std::string read_buffer;
104-
bool success = false;
105-
106-
curl_easy_setopt(curl, CURLOPT_URL, "https://api.ipify.org");
107-
curl_easy_setopt(curl, CURLOPT_USERAGENT, "Advancely/1.0");
108-
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, ip_write_callback);
109-
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &read_buffer);
110-
curl_easy_setopt(curl, CURLOPT_CAINFO, CERT_BUNDLE_PATH);
111-
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1L);
112-
curl_easy_setopt(curl, CURLOPT_TIMEOUT, 10L);
113-
114-
CURLcode res = curl_easy_perform(curl);
115-
if (res == CURLE_OK) {
116-
long http_code = 0;
117-
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);
118-
119-
if (http_code == 200 && !read_buffer.empty() && read_buffer.size() < ip_len) {
120-
strncpy(out_ip, read_buffer.c_str(), ip_len - 1);
121-
out_ip[ip_len - 1] = '\0';
122-
log_message(LOG_INFO, "[INVITE CODE] Fetched public IP: %s\n", out_ip);
123-
success = true;
124-
} else {
125-
log_message(LOG_ERROR, "[INVITE CODE] Unexpected response (HTTP %ld, len %zu).\n",
126-
http_code, read_buffer.size());
127-
}
128-
} else {
129-
log_message(LOG_ERROR, "[INVITE CODE] curl_easy_perform() failed: %s\n", curl_easy_strerror(res));
130-
}
131-
132-
curl_easy_cleanup(curl);
133-
return success;
134-
}
135-
13680
// --- Public API ---
13781

138-
bool invite_code_generate(const char *port, const char *host_name, char *out_code, size_t out_code_len) {
139-
if (!port || port[0] == '\0' || !host_name || host_name[0] == '\0' || !out_code || out_code_len < 32) {
140-
return false;
141-
}
142-
143-
char ip[64];
144-
if (!fetch_public_ip(ip, sizeof(ip))) {
82+
bool invite_code_generate(const char *ip, const char *port, const char *host_name,
83+
char *out_code, size_t out_code_len) {
84+
if (!ip || ip[0] == '\0' || !port || port[0] == '\0' ||
85+
!host_name || host_name[0] == '\0' || !out_code || out_code_len < 32) {
14586
return false;
14687
}
14788

source/invite_code.h

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,18 +17,20 @@ extern "C" {
1717
#endif
1818

1919
/**
20-
* @brief Generates a Base64 invite code from the host's public IP, port, and display name.
20+
* @brief Generates a Base64 invite code from the host's IP, port, and display name.
2121
*
22-
* Fetches the public IP via api.ipify.org, combines it with the given port and
23-
* host name as "IP\nPort\nHostName", and Base64-encodes the result.
22+
* Combines the given IP, port, and host name as "IP\nPort\nHostName"
23+
* and Base64-encodes the result.
2424
*
25+
* @param ip The host's LAN/Hamachi IP address.
2526
* @param port The host port string (e.g., "25565").
2627
* @param host_name The host's display name (shown to receivers on decode).
2728
* @param out_code Buffer to write the Base64 invite code into.
2829
* @param out_code_len Size of the out_code buffer.
29-
* @return true on success, false if the public IP could not be fetched.
30+
* @return true on success, false if any input is empty or encoding fails.
3031
*/
31-
bool invite_code_generate(const char *port, const char *host_name, char *out_code, size_t out_code_len);
32+
bool invite_code_generate(const char *ip, const char *port, const char *host_name,
33+
char *out_code, size_t out_code_len);
3234

3335
/**
3436
* @brief Decodes a Base64 invite code back into IP, port, and host name.

source/settings.cpp

Lines changed: 113 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -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"

source/settings_utils.cpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -434,6 +434,7 @@ void settings_set_defaults(AppSettings *settings) {
434434
// Co-op Defaults
435435
settings->network_mode = DEFAULT_NETWORK_MODE;
436436
settings->coop_goal_logic = DEFAULT_COOP_GOAL_LOGIC;
437+
settings->host_ip[0] = '\0';
437438
strncpy(settings->host_port, DEFAULT_HOST_PORT, sizeof(settings->host_port) - 1);
438439
settings->host_port[sizeof(settings->host_port) - 1] = '\0';
439440
settings->receiver_invite_code[0] = '\0';
@@ -1095,6 +1096,14 @@ bool settings_load(AppSettings *settings) {
10951096
defaults_were_used = true;
10961097
}
10971098

1099+
const cJSON *ip = cJSON_GetObjectItem(coop_settings, "host_ip");
1100+
if (ip && cJSON_IsString(ip)) {
1101+
strncpy(settings->host_ip, ip->valuestring, sizeof(settings->host_ip) - 1);
1102+
settings->host_ip[sizeof(settings->host_ip) - 1] = '\0';
1103+
} else {
1104+
settings->host_ip[0] = '\0';
1105+
}
1106+
10981107
const cJSON *port = cJSON_GetObjectItem(coop_settings, "host_port");
10991108
if (port && cJSON_IsString(port)) {
11001109
strncpy(settings->host_port, port->valuestring, sizeof(settings->host_port) - 1);
@@ -1336,6 +1345,8 @@ void settings_save(const AppSettings *settings, const TemplateData *td, Settings
13361345
cJSON_DeleteItemFromObject(coop_obj, "goal_logic");
13371346
cJSON_AddItemToObject(coop_obj, "goal_logic",
13381347
cJSON_CreateString(coop_goal_logic_to_string(settings->coop_goal_logic)));
1348+
cJSON_DeleteItemFromObject(coop_obj, "host_ip");
1349+
cJSON_AddItemToObject(coop_obj, "host_ip", cJSON_CreateString(settings->host_ip));
13391350
cJSON_DeleteItemFromObject(coop_obj, "host_port");
13401351
cJSON_AddItemToObject(coop_obj, "host_port", cJSON_CreateString(settings->host_port));
13411352
cJSON_DeleteItemFromObject(coop_obj, "receiver_invite_code");

source/settings_utils.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -325,6 +325,7 @@ struct AppSettings {
325325
// --- Co-op Settings ---
326326
NetworkMode network_mode; // Singleplayer, Host, or Receiver
327327
CoopGoalLogic coop_goal_logic; // How to merge progress from multiple players
328+
char host_ip[64]; // The host's LAN/Hamachi IP address (entered manually by the host)
328329
char host_port[16]; // The port the host listens on (default "25565" - Force Port Mod)
329330
char receiver_invite_code[512]; // Base64-encoded connection string pasted by receivers
330331

0 commit comments

Comments
 (0)