Merge in featured-list-creator into utils/

Originally a C program (previously Python script) at https://github.com/principia-game/featured-list-creator for creating fl.cache files for the community site, merge into the main repository so it is with all the other util programs.
This commit is contained in:
ROllerozxa 2026-06-02 00:20:09 +02:00
commit 66923575b4
6 changed files with 170 additions and 1 deletions

View file

@ -16,6 +16,11 @@ jobs:
steps:
- uses: actions/checkout@v6
- name: Install deps
run: |
sudo apt-get update
sudo apt-get install -y libjansson-dev
- name: Build
run: |
cd utils

View file

@ -1,4 +1,4 @@
SUBDIRS := lvl-icon-extractor lvlbuf-decompressor lvledit progress-get
SUBDIRS := featured-list-creator lvl-icon-extractor lvlbuf-decompressor lvledit progress-get
.PHONY: all clean $(SUBDIRS)

View file

@ -9,6 +9,7 @@ This directory contains utility scripts and programs used for Principia developm
## Programs
Most of these programs rely on source files from the main Principia codebase and need to be compiled to run. Likely only works on Linux. You can use the `Makefile` in each directory or run `make` in the `utils` directory to build all of them.
- `featured-list-creator`: Generate a `fl.cache` file for use on the Principia community site
- `lvl-icon-extractor`: Extract the embedded level icon in a Principia level file
- `lvlbuf-decompressor`: Decompress the level buffer of a Principia level file
- `lvledit`: Edit metadata of Principia level files

View file

@ -0,0 +1,27 @@
BIN = ../bin
PRINCIPIA = ../..
PROGRAM = featured-list-creator
SRCS := main.c
# always static unless you do STATIC=0
STATIC ?= 1
CC ?= gcc
CFLAGS ?= -O2 -ffunction-sections -fdata-sections
LDFLAGS ?= -Wl,--gc-sections
ifeq ($(STATIC),1)
LDFLAGS += -l:libjansson.a
else
LDFLAGS += -ljansson
endif
all: $(BIN)/$(PROGRAM)
$(BIN)/$(PROGRAM): $(SRCS)
@mkdir -p $(BIN)
$(CC) $(CFLAGS) $(INCLUDES) -o $@ $^ $(LDFLAGS)
clean:
rm $(BIN)/$(PROGRAM)
.PHONY: all clean

View file

@ -0,0 +1,38 @@
# Featured List Creator
This is a C program that generates a featured list file (`fl.cache`) from JSON data and images, which is served by the Principia community site to display featured levels and getting started links in the main menu.
The program requires the jansson library for JSON parsing, and will attempt to statically link against it by default. You can do `make STATIC=0` to link dynamically instead.
## Usage
There are two optional arguments for specifying the input JSON file and output `fl.cache` file. The default values are below:
```bash
./featured-list-creator data/data.json fl.cache
```
## Format
The format of the input JSON file is as follows:
```json
{
"featured_levels": [
{
"id": 1,
"name": "Name",
"author": "Author",
"jpeg_image": "level_thumbnail.jpg"
}
[...]
],
"gettingstarted_list": [
{
"name": "Link 1",
"link": "https://example.org"
}
[...]
]
}
```
The paths to the JPEG images are relative to the current working directory when running the program.

View file

@ -0,0 +1,98 @@
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <jansson.h>
void write_u32(FILE *f, uint32_t value) {
fwrite(&value, sizeof(uint32_t), 1, f);
}
void write(FILE *f, const char* value) {
fwrite(value, 1, strlen(value), f);
}
#define GET_JSON_INTEGER(obj, key) json_integer_value(json_object_get((obj), (key)))
#define GET_JSON_STRING(obj, key) json_string_value(json_object_get((obj), (key)))
int main(int argc, char *argv[]) {
if (argc < 3) {
printf("Usage: %s [data.json] [fl.cache]\n", argv[0]);
return 1;
}
const char *input_filename = argv[1];
const char *output_filename = argv[2];
json_t *root; json_error_t error;
root = json_load_file(input_filename, 0, &error);
if (!root) {
printf("Error loading JSON file: %s\n", error.text);
return 1;
}
json_t *featured_levels = json_object_get(root, "featured_levels");
json_t *gettingstarted_list = json_object_get(root, "gettingstarted_list");
if (json_array_size(featured_levels) > 4 || json_array_size(gettingstarted_list) > 12)
printf("Array lengths are likely too large for Principia, but carry on.\n");
FILE *f = fopen(output_filename, "wb+");
if (!f) {
printf("Error opening output file\n");
return 1;
}
size_t index;
json_t *feat_level, *gettingstarted;
write_u32(f, json_array_size(featured_levels)); // featured_level_count
json_array_foreach(featured_levels, index, feat_level) {
uint32_t id = GET_JSON_INTEGER(feat_level, "id");
const char *name = GET_JSON_STRING(feat_level, "name");
const char *author = GET_JSON_STRING(feat_level, "author");
const char *jpeg_image_path = GET_JSON_STRING(feat_level, "jpeg_image");
FILE *jpeg_file = fopen(jpeg_image_path, "rb");
if (!jpeg_file) {
printf("Error opening JPEG image file %s\n", jpeg_image_path);
fclose(f);
return 1;
}
fseek(jpeg_file, 0, SEEK_END);
long jpeg_size = ftell(jpeg_file);
fseek(jpeg_file, 0, SEEK_SET);
unsigned char *jpeg_stream = malloc(jpeg_size);
fread(jpeg_stream, 1, jpeg_size, jpeg_file);
fclose(jpeg_file);
write_u32(f, id); // fl_id
write_u32(f, strlen(name)); // fl_name_size
write(f, name); // fl_name
write_u32(f, strlen(author)); // fl_author_size
write(f, author); // fl_author
write_u32(f, jpeg_size); // fl_jpegstream_size
fwrite(jpeg_stream, 1, jpeg_size, f); // fl_jpegstream
free(jpeg_stream);
}
write_u32(f, 0); // Number of contests (unused and slightly broken)
write_u32(f, json_array_size(gettingstarted_list)); // gettingstarted_list_count
json_array_foreach(gettingstarted_list, index, gettingstarted) {
const char *name = GET_JSON_STRING(gettingstarted, "name");
const char *link = GET_JSON_STRING(gettingstarted, "link");
write_u32(f, strlen(name)); // gs_name_size
write(f, name); // gs_name
write_u32(f, strlen(link)); // gs_link_size
write(f, link); // gs_link
}
fclose(f);
return 0;
}