Files and the web
Lesson 10.3
Your mod can keep its own files, and with the player’s permission, ask a website for data. Both stay inside the sandbox: Permissions explained the rules.
Your folder
Your mod lives in mods/<your mod>/. You may read every file in it and write only in its data/ folder. Paths use / and start inside your folder: "data/scores.txt" is mods/<your mod>/data/scores.txt (during clight run: the project’s build/run/<your mod>/data/).
The engine refuses .., absolute paths (C:/...), writes outside data/ and files that could run as programs (.exe, .bat, .js, …) with CL_E_PERMISSION_DENIED. data/ holds 256 MiB.
Reading and writing
The functions are in clight::fs and return clight::Expected:
clight::fs::write_text("data/hello.txt", "Hello from my mod!");
if (auto text = clight::fs::read_text("data/hello.txt")) clight::log::info("{}", *text);
else clight::log::warn("no file ({})", int(text.error()));
clight::fs::append_text("data/log.txt", "a player joined\n");
clight::fs::mkdir("data/players");
if (auto info = clight::fs::stat("data/log.txt"); info && info->kind == CL_FS_FILE)
clight::log::info("log.txt has {} bytes", info->size);
auto names = clight::fs::list("data"); // one name per line, folders end with '/'
clight::fs::rename("data/log.txt", "data/old_log.txt");
clight::fs::remove("data/players", true); // true: with everything inside
Writes are atomic: the game writes a new file and then swaps it in, so a crash never leaves half a file. std::ifstream / std::ofstream work too, on the same files: your folder is mounted read-only at / and data/ read/write at /data.
Asking a website
List the website in the manifest; the player allows it on your mod’s Permissions screen:
"permissions": ["net.http:api.ruby-tools.example"]
A request runs in the background. Your callback runs at the next tick, on the side that asked:
static void on_prices(cl_http_req req, int32_t status, cl_buf body, void*) {
clight::HttpReq r(req);
if (status != 200) {
clight::log::warn("no prices: {} {}", status, r.http_error().value_or(""));
return;
}
std::string text(cl_buf_size(body), '\0');
size_t got = 0;
cl_buf_read_bytes(body, text.data(), text.size(), &got);
clight::log::info("prices: {}", text);
}
void ask() {
auto req = clight::http::request("https://api.ruby-tools.example/v1/prices", nullptr, on_prices, nullptr);
if (!req) clight::log::warn("not sent ({})", int(req.error())); // CL_E_PERMISSION_DENIED: not allowed
}
status is 0 when no answer came; http_error() says why (timeout, cancelled, not allowed: <host>, …). body and req are valid only during the callback. For a POST, a download into data/ (save_to) and the limits, fill a cl_http_opts exactly as in C (Filesystem and HTTP):
cl_http_opts o = CL_HTTP_OPTS_INIT;
o.method = CL_HTTP_POST;
o.body = clight::s(R"({"player":"Alex","score":12})");
o.content_type = clight::s("application/json");
clight::http::request("https://api.ruby-tools.example/v1/scores", &o, on_saved, nullptr);
Only https:// works (plain http:// only to localhost), a request takes at most a minute, and an answer at most 64 MiB.
Asking for permission later
Websites your mod can live without go into optional_permissions. Check with cl_cap_granted and ask with cl_cap_request when the player uses the feature (on the client it opens your mod’s Permissions screen); the answer fires mod.permission_changed.
Try it
Count how often the server started in data/starts.txt: read the number (0 when the file is missing), add one, write it back, and log it.
Show a solution
void on_server_started() {
int starts = 0;
if (auto t = clight::fs::read_text("data/starts.txt")) starts = std::atoi(t->c_str());
++starts;
clight::fs::write_text("data/starts.txt", std::to_string(starts));
clight::log::info("server start number {}", starts);
}
Recap: clight::fs reads your folder and writes data/, and clight::http::request talks to the websites the player allowed, with the answer at the next tick.