#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 { url_data, ok := read_entire_file("url.txt"); if !ok return false, false, "Could not read url.txt. Please configure the Config URL first."; defer free(url_data); url := trim(url_data); if !url return false, false, "url.txt is empty. Please configure the Config URL first."; downloaded, success, err_msg := download_url(url); if !success return false, false, err_msg; defer free(downloaded); modified := modify_config_inbounds(downloaded); defer free(modified); config_data, read_ok := read_entire_file("config.json"); defer if read_ok free(config_data); if read_ok && config_data == modified { return false, true, ""; } write_ok := write_entire_file("config.json", modified); if !write_ok { return false, false, "Failed to write downloaded 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) -> 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, "\n \"inbounds\": [\n {\n \"type\": \"mixed\",\n \"listen\": \"127.0.0.1\",\n \"listen_port\": 20122\n }\n ],"); 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, "[\n {\n \"type\": \"mixed\",\n \"listen\": \"127.0.0.1\",\n \"listen_port\": 20122\n }\n ]"); append(*builder, slice(json_content, close_bracket_idx + 1, json_content.count - (close_bracket_idx + 1))); return builder_to_string(*builder); }