Move util programs back into main repository

Also includes new Makefiles for building them and a README in the main utils dir
This commit is contained in:
ROllerozxa 2026-05-30 00:51:16 +02:00
commit 01236f4b31
10 changed files with 313 additions and 0 deletions

29
.github/workflows/build_utils.yml vendored Normal file
View file

@ -0,0 +1,29 @@
name: build_utils
on:
push:
paths:
- 'src/**'
- 'utils/**'
pull_request:
paths:
- 'src/**'
- 'utils/**'
jobs:
build_utils:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v6
- name: Build
run: |
cd utils
make
- name: Upload output as artifact
uses: actions/upload-artifact@v7
with:
name: principia-utils
path: utils/bin/
if-no-files-found: error

1
utils/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
bin/

13
utils/Makefile Normal file
View file

@ -0,0 +1,13 @@
SUBDIRS := lvledit progress-get
.PHONY: all clean $(SUBDIRS)
all: $(SUBDIRS)
$(SUBDIRS):
$(MAKE) -C $@
clean:
for dir in $(SUBDIRS); do \
$(MAKE) -C $$dir clean; \
done

13
utils/README.md Normal file
View file

@ -0,0 +1,13 @@
# `utils`
This directory contains utility scripts and programs used for Principia development or for working with file formats.
## Scripts
- `prepare_release_builds.sh`:
- `set_version.lua`: Update the version number across the codebase when preparing a release (See [Making a Release](https://principia-web.se/wiki/Making_a_Release#incrementing-version-tagging)).
- `update-sandbox-menu.sh`: Update the pre-rendered sandbox and item menu
## 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.
- `lvledit`: Edit metadata of Principia level files
- `progress-get`: Get leaderboard score for a given level from a data.bin file

20
utils/lvledit/Makefile Normal file
View file

@ -0,0 +1,20 @@
BIN = ../bin
PRINCIPIA = ../..
PROGRAM = lvledit
SRCS := main.cc $(PRINCIPIA)/src/pkgman.cc $(PRINCIPIA)/src/rand.c
CXX ?= g++
CXXFLAGS ?= -D_NO_TMS -O2
INCLUDES ?= -I$(PRINCIPIA)/src -I$(PRINCIPIA)/lib
LDFLAGS ?= -lz
all: $(BIN)/$(PROGRAM)
$(BIN)/$(PROGRAM): $(SRCS)
@mkdir -p $(BIN)
$(CXX) $(CXXFLAGS) $(INCLUDES) -o $@ $^ $(LDFLAGS)
clean:
rm $(BIN)/$(PROGRAM)
.PHONY: all clean

38
utils/lvledit/README.md Normal file
View file

@ -0,0 +1,38 @@
# `lvledit`
Get and set various fields in the Principia level header.
## Usage
```bash
lvledit <levelpath> <method>
```
To check the maximum level version that lvledit is built to handle, use:
```bash
lvledit --get-built-level-version
```
### Methods
- `get-description`: Get level description
- `set-description`: Set level description
- `get-name`: Get level name
- `set-name`: Set level name
- `get-type`: Get level type
- `get-parent-id`: Get parent ID of level (when it has been derived)
- `get-version`: Get level version
- `get-revision`: Get revision of level
- `get-gids`: Get a list of g_id's that exist in the level
- `get-visibility`: Get the visibility state of the level
- `get-community-id`: Get the community ID of the level
- `set-community-id`: Set the community ID of the level
- `flag-active`: Get if level flag X is active
### Input and output
To input data to a setter method you will need to pipe it through stdout. For example:
```bash
echo -n "Level Title" | lvledit bla.plvl --set-name
```
Output for getters are printed to stdout. Nothing else will print to stdout, errors will be printed to stderr.

147
utils/lvledit/main.cc Normal file
View file

@ -0,0 +1,147 @@
#include <stdio.h>
#include "pkgman.hh"
#define MAX_READ (4096*18)
static lvledit lvl;
static char input[MAX_READ];
static char path[1024];
static size_t input_len = 0;
static void _get_input(void) {
int c;
while ((c = getchar()) != EOF) {
input[input_len] = (char)c;
input_len++;
}
}
static void get_description(void) {
printf("%.*s", lvl.lvl.descr_len, lvl.lvl.descr);
}
static void set_description(void) {
_get_input();
lvl.lvl.descr_len = input_len;
if (lvl.lvl.descr) free(lvl.lvl.descr);
lvl.lvl.descr = input;
lvl.save_to_path(path);
lvl.lvl.descr = 0; /* prevent destructor from trying to free 'input' */
}
static void get_name(void) {
printf("%.*s", lvl.lvl.name_len, lvl.lvl.name);
}
static void set_name(void) {
_get_input();
if (input_len > 255) input_len = 255;
lvl.lvl.name_len = input_len;
memcpy(lvl.lvl.name, input, input_len);
lvl.save_to_path(path);
}
static void get_type(void) {
printf("%d", lvl.lvl.type);
}
static void get_parent_id(void) {
printf("%d", lvl.lvl.parent_id);
}
static void get_version(void) {
printf("%d", lvl.lvl.version);
}
static void get_revision(void) {
printf("%d", lvl.lvl.revision);
}
static void get_visibility(void) {
printf("%d", lvl.lvl.visibility);
}
static void set_visibility(void) {
_get_input();
lvl.lvl.visibility = (uint8_t)atoi(input);
lvl.save_to_path(path);
}
static void get_community_id(void) {
printf("%u", lvl.lvl.community_id);
}
static void set_community_id(void) {
_get_input();
lvl.lvl.community_id = (uint32_t)atoi(input);
lvl.save_to_path(path);
}
static void flag_active(void) {
_get_input();
uint64_t flag = (1ULL << atoi(input));
printf("%u", lvl.lvl.flag_active(flag) ? 1 : 0);
}
static void get_gids(void) {
lvl.print_gids();
}
struct handler {
const char *str;
void (*fn)(void);
};
struct handler handlers[] = {
{"--get-description", get_description},
{"--set-description", set_description},
{"--set-name", set_name},
{"--get-name", get_name},
{"--get-type", get_type},
{"--get-parent-id", get_parent_id},
{"--get-version", get_version},
{"--get-revision", get_revision},
{"--get-gids", get_gids},
{"--get-visibility", get_visibility},
{"--set-visibility", set_visibility},
{"--get-community-id", get_community_id},
{"--set-community-id", set_community_id},
{"--flag-active", flag_active},
};
#define NUM_HANDLERS (sizeof(handlers)/sizeof(struct handler))
int
main(int argc, char **argv) {
if (argc == 3) {
strcpy(path, argv[1]);
if (!lvl.open_from_path(path)) {
fprintf(stderr, "could not open file %s", path);
exit(1);
}
for (int x=0; x<NUM_HANDLERS; x++) {
if (strcmp(handlers[x].str, argv[2]) == 0) {
(handlers[x].fn)();
break;
}
}
return 0;
} else if (argc == 2 && strcmp(argv[1], "--get-built-level-version") == 0) {
printf("%d", LEVEL_VERSION);
return 0;
} else {
fprintf(stderr, "why are we here\n");
printf("usage: lvledit <path> <method>\n");
for (int x=0; x<NUM_HANDLERS; x++) {
printf(" %s\n", handlers[x].str);
}
return 1;
}
fprintf(stderr, "hello??\n");
return 2;
}

View file

@ -0,0 +1,20 @@
BIN = ../bin
PRINCIPIA = ../..
PROGRAM = progress-get
SRCS := main.cc $(PRINCIPIA)/src/progress.cc
CXX ?= g++
CXXFLAGS ?= -D_NO_TMS -O2
INCLUDES ?= -I$(PRINCIPIA)/src -I$(PRINCIPIA)/lib
LDFLAGS ?= -lz
all: $(BIN)/$(PROGRAM)
$(BIN)/$(PROGRAM): $(SRCS)
@mkdir -p $(BIN)
$(CXX) $(CXXFLAGS) $(INCLUDES) -o $@ $^ $(LDFLAGS)
clean:
rm $(BIN)/$(PROGRAM)
.PHONY: all clean

View file

@ -0,0 +1,14 @@
# `progress-get`
Get leaderboard score for a given level from a data.bin file.
## Usage
```bash
progress-get <data.bin path> <level ID>
```
Output is printed to stdout with comma-separated values in the format:
```
<completed>,<num_plays>,<last_score>
```

View file

@ -0,0 +1,18 @@
#include <iostream>
#include "progress.hh"
int main(int argc, char **argv) {
if (argc < 3) {
printf("usage: progress-get [path_to_data_bin] [community-level-id]\n");
return 1;
}
progress::init(argv[1]);
const lvl_progress *p = progress::get_level_progress(1, atoi(argv[2]));
// completed, num_plays, last_score
std::cout << (int)p->completed << ',' << p->num_plays << ',' << p->last_score;
return 0;
}