| 31 | /* ── Load ────────────────────────────────────────────────────── */ |
| 32 | |
| 33 | void cbm_ui_config_load(cbm_ui_config_t *cfg) { |
| 34 | cfg->ui_enabled = CBM_UI_DEFAULT_ENABLED; |
| 35 | cfg->ui_port = CBM_UI_DEFAULT_PORT; |
| 36 | |
| 37 | char path[CBM_SZ_1K]; |
| 38 | cbm_ui_config_path(path, (int)sizeof(path)); |
| 39 | |
| 40 | FILE *f = fopen(path, "rb"); |
| 41 | if (!f) { |
| 42 | /* No config file — auto-enable UI if binary has embedded assets */ |
| 43 | if (CBM_EMBEDDED_FILE_COUNT > 0) { |
| 44 | cfg->ui_enabled = true; |
| 45 | } |
| 46 | return; |
| 47 | } |
| 48 | |
| 49 | fseek(f, 0, SEEK_END); |
| 50 | long len = ftell(f); |
| 51 | fseek(f, 0, SEEK_SET); |
| 52 | |
| 53 | if (len <= 0 || len > 4096) { |
| 54 | fclose(f); |
| 55 | return; /* empty or suspiciously large → defaults */ |
| 56 | } |
| 57 | |
| 58 | char *buf = malloc((size_t)len + SKIP_ONE); |
| 59 | if (!buf) { |
| 60 | fclose(f); |
| 61 | return; |
| 62 | } |
| 63 | |
| 64 | size_t nread = fread(buf, SKIP_ONE, (size_t)len, f); |
| 65 | fclose(f); |
| 66 | buf[nread] = '\0'; |
| 67 | |
| 68 | yyjson_doc *doc = yyjson_read(buf, nread, 0); |
| 69 | free(buf); |
| 70 | if (!doc) { |
| 71 | cbm_log_warn("ui.config.corrupt", "path", path); |
| 72 | return; /* corrupt JSON → defaults */ |
| 73 | } |
| 74 | |
| 75 | yyjson_val *root = yyjson_doc_get_root(doc); |
| 76 | if (!yyjson_is_obj(root)) { |
| 77 | yyjson_doc_free(doc); |
| 78 | return; |
| 79 | } |
| 80 | |
| 81 | yyjson_val *v_enabled = yyjson_obj_get(root, "ui_enabled"); |
| 82 | if (yyjson_is_bool(v_enabled)) { |
| 83 | cfg->ui_enabled = yyjson_get_bool(v_enabled); |
| 84 | } |
| 85 | |
| 86 | yyjson_val *v_port = yyjson_obj_get(root, "ui_port"); |
| 87 | if (yyjson_is_int(v_port)) { |
| 88 | cfg->ui_port = (int)yyjson_get_int(v_port); |
| 89 | } |
| 90 |
no test coverage detected