#import "Basic"; #import "Curl"; #import "File"; #import "Thread"; #import "Windows"; #import "String"; Updater_Thread_Data :: struct { hwnd: HWND; update_interval_seconds: s32 = 3600; // 1 hour stop_event: HANDLE; } write_callback :: (ptr: *u8, size: u64, nmemb: u64, userdata: *void) -> u64 #c_call { builder := cast(*String_Builder) userdata; push_context { append(builder, ptr, xx nmemb); } return nmemb; } download_url :: (url: string) -> string, bool, string { curl := curl_easy_init(); if !curl return "", false, "Failed to initialize Curl"; defer curl_easy_cleanup(curl); curl_easy_setopt(curl, .URL, temp_c_string(url)); curl_easy_setopt(curl, .FOLLOWLOCATION, 1); curl_easy_setopt(curl, .TIMEOUT, 30); // Disable SSL verification to prevent issues with missing CA cert bundles on Windows curl_easy_setopt(curl, .SSL_VERIFYPEER, 0); curl_easy_setopt(curl, .SSL_VERIFYHOST, 0); builder: String_Builder; defer free_buffers(*builder); curl_easy_setopt(curl, .WRITEFUNCTION, write_callback); curl_easy_setopt(curl, .WRITEDATA, *builder); curl_code := curl_easy_perform(curl); if curl_code != .OK { err_msg := tprint("Curl perform failed: %", to_string(curl_easy_strerror(curl_code))); return "", false, err_msg; } response_code: int; curl_easy_getinfo(curl, .RESPONSE_CODE, *response_code); if response_code != 200 { err_msg := tprint("HTTP request failed with status code %", response_code); return "", false, err_msg; } return builder_to_string(*builder), true, ""; } perform_update :: () -> changed: bool, success: bool, error_msg: string { config_data, read_ok := read_entire_file("config.json"); if !read_ok return false, false, "config.json does not exist. Please configure the Config URL first."; defer free(config_data); url := get_json_string_field(config_data, "\"_url\""); if !url return false, false, "No Config URL configured in config.json. Please configure the Config URL first."; mode := get_json_string_field(config_data, "\"_mode\""); if !mode mode = "system_proxy"; port_str := get_json_val_field(config_data, "\"_port\""); port := 10801; if port_str { val, parse_ok, remainder := to_integer(port_str); if parse_ok { port = xx val; } } downloaded, success, err_msg := download_url(url); if !success return false, false, err_msg; defer free(downloaded); modified := modify_config_inbounds(downloaded, port); defer free(modified); final_config := set_json_metadata(modified, url, mode, port); defer free(final_config); if config_data == final_config { return false, true, ""; } write_ok := write_entire_file("config.json", final_config); if !write_ok { return false, false, "Failed to write updated content to config.json"; } return true, true, ""; } updater_thread_proc :: (thread: *Thread) -> s64 { data := cast(*Updater_Thread_Data) thread.data; if !data return 1; while true { // Wait on the stop event for the specified interval. // If the stop event is signaled, wake up immediately and terminate. status := WaitForSingleObject(data.stop_event, cast(DWORD) (data.update_interval_seconds * 1000)); if status == WAIT_OBJECT_0 { break; } changed, success, err_msg := perform_update(); if success && changed { if data.hwnd { PostMessageW(data.hwnd, WM_RESTART_SINGBOX, 0, 0); } } } return 0; } modify_config_inbounds :: (json_content: string, port: int) -> string { // Find "inbounds" key idx := find_index_from_left(json_content, "\"inbounds\""); if idx == -1 { first_brace := find_index_from_left(json_content, #char "{"); if first_brace == -1 return copy_string(json_content); builder: String_Builder; append(*builder, slice(json_content, 0, first_brace + 1)); append(*builder, tprint("\n \"inbounds\": [\n {\n \"type\": \"mixed\",\n \"listen\": \"127.0.0.1\",\n \"listen_port\": %\n }\n ],", port)); append(*builder, slice(json_content, first_brace + 1, json_content.count - (first_brace + 1))); return builder_to_string(*builder); } // Find the opening bracket '[' after "inbounds" search_start := idx + 10; open_bracket_idx := -1; for i: search_start..json_content.count-1 { if json_content[i] == #char "[" { open_bracket_idx = i; break; } else if json_content[i] == #char ":" || is_space(json_content[i]) { continue; } else { break; } } if open_bracket_idx == -1 return copy_string(json_content); // Find the matching closing bracket ']' close_bracket_idx := -1; bracket_depth := 0; for i: open_bracket_idx..json_content.count-1 { if json_content[i] == #char "[" { bracket_depth += 1; } else if json_content[i] == #char "]" { bracket_depth -= 1; if bracket_depth == 0 { close_bracket_idx = i; break; } } } if close_bracket_idx == -1 return copy_string(json_content); builder: String_Builder; append(*builder, slice(json_content, 0, open_bracket_idx)); append(*builder, tprint("[\n {\n \"type\": \"mixed\",\n \"listen\": \"127.0.0.1\",\n \"listen_port\": %\n }\n ]", port)); append(*builder, slice(json_content, close_bracket_idx + 1, json_content.count - (close_bracket_idx + 1))); return builder_to_string(*builder); } get_json_string_field :: (json: string, field: string) -> string { idx := find_index_from_left(json, field); if idx == -1 return ""; search_start := idx + field.count; open_quote := -1; for i: search_start..json.count-1 { if json[i] == #char "\"" { open_quote = i; break; } else if json[i] == #char ":" || is_space(json[i]) { continue; } else { break; } } if open_quote == -1 return ""; close_quote := -1; for i: open_quote+1..json.count-1 { if json[i] == #char "\"" && json[i-1] != #char "\\" { close_quote = i; break; } } if close_quote == -1 return ""; return slice(json, open_quote + 1, close_quote - open_quote - 1); } get_json_val_field :: (json: string, field: string) -> string { idx := find_index_from_left(json, field); if idx == -1 return ""; search_start := idx + field.count; val_start := -1; for i: search_start..json.count-1 { if json[i] == #char ":" || is_space(json[i]) { continue; } else { val_start = i; break; } } if val_start == -1 return ""; val_end := val_start; while val_end < json.count - 1 { next_char := json[val_end + 1]; if next_char == #char "," || next_char == #char "\n" || next_char == #char "\r" || next_char == #char "}" || next_char == #char "]" { break; } val_end += 1; } return trim(slice(json, val_start, val_end - val_start + 1)); } set_json_metadata :: (json: string, url: string, mode: string, port: int) -> string { lines := split(json, "\n"); defer array_free(lines); builder: String_Builder; inserted := false; for lines { trimmed := trim(it); if find_index_from_left(trimmed, "\"_url\"") != -1 continue; if find_index_from_left(trimmed, "\"_mode\"") != -1 continue; if find_index_from_left(trimmed, "\"_port\"") != -1 continue; append(*builder, it); append(*builder, "\n"); if !inserted && find_index_from_left(trimmed, "{") != -1 { append(*builder, tprint(" \"_url\": \"%\",\n \"_mode\": \"%\",\n \"_port\": %,\n", url, mode, port)); inserted = true; } } return builder_to_string(*builder); } strip_json_metadata :: (json: string) -> string { lines := split(json, "\n"); defer array_free(lines); builder: String_Builder; for lines { trimmed := trim(it); if find_index_from_left(trimmed, "\"_url\"") != -1 continue; if find_index_from_left(trimmed, "\"_mode\"") != -1 continue; if find_index_from_left(trimmed, "\"_port\"") != -1 continue; append(*builder, it); append(*builder, "\n"); } return builder_to_string(*builder); }