Compare commits

..

129 Commits

Author SHA1 Message Date
9e8f5958c7 write conf scripts 2025-12-15 18:52:47 +01:00
bc0743a72b add __assume__ macro definition in atrb.h 2025-12-15 12:32:34 +01:00
81b21ff7ce move windowing code into io/win 2025-12-15 10:02:54 +01:00
7df20e9aa1 remove mapcolour files, since we don't use them 2025-12-15 09:56:45 +01:00
5cf0292ebe delete configuration logic
The configuration logic was violating alignment rules, and will be
rewritten.
2025-12-15 09:54:57 +01:00
64b06d88c8 fix: error headers were needlessly backing out of util/ 2025-12-15 09:54:17 +01:00
8bb8671fb0 rephrase the header a little, to not be confusing when a file is copy-pasted. 2025-11-13 22:29:41 +01:00
8d0e825157 rewrite copyright file header to provide more information about where the file is from. 2025-11-13 22:24:09 +01:00
5f909d1d65 rework the flow of the Makefile. 2025-10-23 15:28:42 +02:00
87541d5789 modify Makefile to be more in-line with the GNU standard.
Mainly so the makefile is a bit less esoteric to use, debugging flags
must be specified manually.

changed:
- `$RM` for calling `rm`
- removed unused `$ISWIN`
- renamed `test` to `check`
- added `install` and `install-strip` recipes, which for now remain
empty
- added `-g` to `$CFLAGS`, since it helps debugging, and in case of
failures makes bug report less of a headache.

I decided to not modify it further so "specialised tools" don't need to
store files in the git repo, which seemed to complicate logic.
This is the same reason to why I chose to not adapt clean in a way to
introduce `mostlyclean`.
2025-10-20 15:19:24 +02:00
316726a610 tmp: revert enabling a column limit 2025-10-16 23:35:30 +02:00
1aa34f7d3f enable a column limit. 2025-10-09 21:12:53 +02:00
00719b1933 refactor code with new formatting rules
Functions now break before their brace, mirroring the Linux kernel.
The reason for this is that breaking the parameter list otherwise makes
code unreadable.
2025-10-09 19:02:16 +02:00
eb45650178 fix: use uintptr over usize in locations where applicable 2025-10-09 18:35:39 +02:00
8ca49aa4fa fix: ssize got removed in rework of intdef.h/types.h. 2025-10-09 12:28:24 +02:00
d4fabf437c reformat the code by enabeling assignment- and declaration alignment. 2025-10-09 12:17:04 +02:00
cebe0df8a2 move /src/error.? to /src/util/error.? 2025-10-09 12:16:38 +02:00
aa58d931aa move /src/util/intdef.h to /src/util/types.h 2025-10-09 12:16:06 +02:00
27c862c215 rework intdef.h to not rely upon standard library headers by using predefined macros. 2025-10-09 12:05:05 +02:00
6eb7d126da create a custom definition for size_t and ssize_t, which is ssize and usize, for conveinience. 2025-09-18 10:39:57 +02:00
1e24a4ee92 add a CI status badge to the README.md 2025-09-18 09:58:05 +02:00
181fca6828 fix: windows test; USERPROFILE should also result in a roaming path. 2025-09-18 09:50:56 +02:00
f6e5712876 fix: testing warnings 2025-09-18 09:47:31 +02:00
47d34b3f7d rewrite testing code to be a bit more durable and clean. 2025-09-17 16:52:18 +02:00
e623a352d2 fix: windows cache is not caching correctly. 2025-09-17 13:59:03 +02:00
079361771f fix-test: windows compilation; append some extra libraries when compiling windows. 2025-09-17 13:44:34 +02:00
66e3d0d06f revert previous, since the compilation has concluded 2025-09-17 12:55:33 +02:00
adfca88d3c push compilation of libraries on everything to get rid of turtle compilation speeds 2025-09-17 12:51:20 +02:00
e9c922d3cd revert forced usage of bash, instead opt for (more) explicit compiler selection. 2025-09-17 12:39:21 +02:00
d46f8c0900 always use bash as the shell. 2025-09-17 12:13:46 +02:00
2261209d8d win: increase cores, to hopefully not spend 20min on compilation. 2025-09-17 12:13:34 +02:00
a16f454396 drop macosx support; the fix introduced a new bug
We're now running into dynamic linker problems, so we shall disable
macosx support for now.
2025-09-17 11:32:40 +02:00
945a82e5f6 test-fix: windows compilation 2025-09-17 11:32:40 +02:00
88d39150dd test-fix: macosx compilation 2025-09-17 11:32:40 +02:00
76c992efdc note about the memory leak I could not locate within my code.
hint: I couldn't locate it, because it isn't in my code.
2025-09-15 15:14:08 +02:00
28b98705f5 refactor the lot a bit, for clairity. 2025-09-15 15:08:15 +02:00
057b234251 fix: don't free window twice 2025-09-15 15:07:41 +02:00
dd2f4c403c add copyright information to error.c 2025-09-15 14:23:03 +02:00
dc3abf992b handle window exits more gracefully and appropriately. 2025-09-15 14:23:03 +02:00
1e10fec9c6 rename input callback to something more appropriate. 2025-09-15 14:12:18 +02:00
41d944ac21 remove old/stale code from main.c 2025-09-15 14:00:40 +02:00
faa93f4372 remove needless inline function from main.c
this is shorter, does the same, and this was an incorrect application of
`inline`.
2025-09-15 13:58:47 +02:00
e3c0afbb2f fix: glfw error logging inconsistent with how the rest of our application logs errors. 2025-09-15 13:55:32 +02:00
11c8748262 define GLFW_INCLUDE_NONE, so glfw knows we're using custom headers. 2025-09-15 13:51:36 +02:00
25fa078c98 add a check for debug logging, to only log when the environment variable DEBUG is set to 1 2025-09-15 13:51:36 +02:00
023123e54f make debug loggers not be needlessly abbreviated. 2025-09-15 13:06:20 +02:00
b13739c782 do not bother with recompiling the libraries, if we got a cache hit. 2025-09-15 12:57:35 +02:00
5db42a2b15 disable windows from the CI.
yeah, fuck you too.
Windows has been... quite the pain to deal with, and I've reached the
limit of what I can be arsed to do to try and make it work.
2025-09-15 12:43:52 +02:00
2277b8ec77 use a more accurate hash for caching submodule objects. 2025-09-15 12:41:52 +02:00
fa26a3561d disable macosx for the time being, it is non-functional and I can't/won't test it anyway. 2025-09-15 12:34:30 +02:00
15b5430611 Instead of using cmake, I *obviously* need to use cmake -G 'Unix Makefiles'. How silly of me.
MicroSoft Windows: "it just works"
2025-09-15 12:30:01 +02:00
6c184a4d36 re-enable windows, and see what happens. 2025-09-15 12:17:37 +02:00
82ebe1f4d1 fix: use CC instead of LD, and deprecate LD
this is a bit confusing, since setting `LD=cc` would have the same
issue; `ld: -f may not be used without -shared`. So I've got no idea
what is going wrong, I guess it's something funky.
This works, so we shall stick with this.
2025-09-15 12:08:39 +02:00
5f3bd40a47 potato time.
this is quite upsetting.
2025-09-15 12:04:41 +02:00
158a7f8383 fix: don't use UNIX sh based ifs, instead opt for GNU Make.
This is more portable acorss machines
2025-09-15 12:04:41 +02:00
c32d1551c8 fix: regular xxd does not have -n, thus using some argument manipulation magic
On my main system I use `tinyxxd`, henceforth: "regular"
2025-09-15 12:04:41 +02:00
b04ce9998f fix: shader hex code is formatted incorrectly 2025-09-15 11:59:39 +02:00
218b98e684 fix: colour coding on error messages is incorrect 2025-09-15 11:59:39 +02:00
f20171c0d7 fix: using the linker for generating a .o file is a feature for only GNU ld.
Now utilising a process to generate a .c file using `xxd`, and compiling
those to object files.
2025-09-15 11:59:39 +02:00
3a7709c392 fix: use more accurate linker flags / calling. 2025-09-15 11:59:39 +02:00
ec36d8c475 add runner architecture to the action cache 2025-09-15 11:59:39 +02:00
a14ef55262 fix: testing compilation does not correctly filter out main.o 2025-09-15 11:14:41 +02:00
d340bbe40b fix: fminf and other <math.h> from libm not found.
The `-lm` linker flag was applied preceding the libraries, but it
should've been succeding.
2025-09-15 11:14:41 +02:00
0f6a2579b9 forcibly recompile the libs, so if deps get updated, this will be carried through.
This sacrifices a bit of workflow performance, but won't be too
significant. The heaviest step (cmake) is still circumvented
2025-09-15 11:14:40 +02:00
1ac4592503 Aparrently, the act docker container does not have cmake installed.
Adding this should not make a significant impact on workflow
performance.
2025-09-15 09:35:59 +02:00
4da02373a5 disable fail fast, so feedback is provided for all matrix combinations 2025-09-15 09:25:18 +02:00
c724ff0449 remove explicit definition for repository, since it messes with act
`act` allows to debug github workflows before sending them to the
server, allowing to test some behaviour, before pushing.
2025-09-15 09:20:07 +02:00
348c4e484c fix: clean-libs can fail when it shouldn't 2025-09-13 21:44:46 +02:00
1140bd97bf fix: linking library glfw, whilst the library compilation produces libglfw3.
This slight discrepancy is a tad annoying, since it doesn't work
backwards so it uses my system's version. But we'll figure something for
that out later... probably.
2025-09-13 21:32:33 +02:00
997b15c640 fix: obj/res/ path did not create a directory, so likely failed to place anything there. 2025-09-13 21:17:07 +02:00
f481af7e78 fix: incorrect gl.c path 2025-09-13 21:16:33 +02:00
1169ca98b1 use threaded makefile for more efficiency. 2025-09-13 21:11:58 +02:00
75ebff9071 remove pointer arithmatic warnings, since they are just useful 2025-09-13 21:11:38 +02:00
4f081e7f3e remove usage of mempcpy; this is apart of GNU C 2025-09-13 21:09:56 +02:00
8a1ed6e3c4 attempt without including windows 2025-09-13 21:04:29 +02:00
a7678c874c write a CI workflow for multiple OSes, that compile and test the code using the makefile. 2025-09-13 20:57:54 +02:00
d9ddae770a fix: just use compiler builtins with endianess
this is the most portable and clean approach. Compiler built-ins should
always be present regardless, and is more maintainable, since we're not
trying to keep up with various versions of this header.
2025-09-13 20:25:38 +02:00
c2e1b67b4c seperate out library compilation from general compilation
This can help with a more nuanced compilation. (eg. use system libraries
rather than compile our own)
Furthermore, it decreases the needed compilation time, since we needn't
recompile the whole thing when having cleaned out our local sources.
2025-09-13 20:25:38 +02:00
8548c2d037 fix: log macros were using GNU extensions without them needing them.
We can optionally add `,` utilising the `__VA_OPT__` macro. However,
this lead to the next problem, which is that an empty vardiac parameter
is a C23 extension. This was solved by dropping the string parameter,
making it be part of the vardiac parameters.
2025-09-13 20:25:38 +02:00
7ec6a3b91a fix: using __bswap_X functions over bswap_X macros, which we should be using. 2025-09-13 20:25:38 +02:00
3f48a7abb2 add compile commands events to gitignore 2025-09-13 20:25:38 +02:00
667814fb72 rework makefile to put extera emphasis on us no longer performing cross-platform compilation
Furthermore, we removed a bunch of behaviour that wasn't needed, and
simplified the bunch.
2025-09-13 20:25:38 +02:00
23fda298e6 fix: makefile had some duplicate configurations 2025-09-11 23:28:58 +02:00
03c76b0be6 rename makefile to Makefile, to reflect how everyone seems to name their makefile.
it just makes sense, and I might as well do it like this to prevent
confusing people.
2025-09-11 23:28:58 +02:00
f0d14e76b2 write decompression logic up to the point of hanelling the buffer. 2025-09-10 12:52:35 +02:00
07f4ea34d2 macro flipped const char 2025-09-10 12:07:00 +02:00
5de0c77992 start writing code for decompressing chunk data.
I still haven't much of an idea where I'll take this, but it's a start.

I know it is needed for block filtering and such, I still need to find a
way how I'll actually implement it.
2025-09-10 11:37:20 +02:00
2cfae0fce4 rework error.h, to decrease impact on binary size.
We were statically creating a new string per log message, so re-using
the same message would cause duplicate byte sizes.
Now, all prefixes / suffixes are only defined once, the file name is
defined once per file, the line number should be defined once per unique
line.
This just saves some memory in the final binary, allowing things to be
optimised a little better.
2025-09-10 11:37:20 +02:00
d933931829 fix: shouldn't multiply by CHUNKS, but by SECTOR 2025-09-10 10:13:26 +02:00
f020347f71 change mvchunks parameter order, to be a bit more clear and consistent with functions such as memmove 2025-09-10 10:13:26 +02:00
b13de01f24 fix: violating strict aliasing rules in most areas in the new code.
Yes, I am aware there are plenty of violations in `conf.c`, but I'll
likely fix/rewrite those when I will use it. Since there are some other
changes I think I'll want to make.
2025-09-10 10:13:26 +02:00
3a0704710c add a flag when debugging to generate SIGFPE when signed integer overflow occurs. (this is UB) 2025-09-10 10:13:26 +02:00
b72b8eaee1 add comments to point at where strict aliasing rule violations occur in data scripts
this is definitely not all of them, a thorough rewrite must take place
to fix as many as we can.
2025-09-10 10:13:26 +02:00
55635d44ae delete redundant COLOUR32 definitions 2025-09-10 10:13:26 +02:00
4fa0b41e7a add quit in main.c to atexit, for added safety 2025-09-10 10:13:26 +02:00
8a5afd6915 write a portable version of endian.h, to replace the system's endian.h.
utilising GNU C standard library for some of the functionality, or GNU C
extensions.
2025-09-10 10:13:26 +02:00
8b952fb86f edit makefile to remove redundant flags, and make clean task more conditional 2025-08-29 11:47:46 +02:00
0baadfca75 update copyright information in all files
the comment was using single-line comments, I prefer multi-line comments
now.
This bothered me for much too long.
2025-08-29 09:36:55 +02:00
9a45294e56 write some constants for mcx.c, so things are less error-prone. 2025-08-29 09:35:02 +02:00
5b5f1f54af remove unneeded code 2025-08-28 16:46:27 +02:00
f1141e0db4 fix: not moving chunks correctly in mcx_delchunk_range 2025-08-28 10:59:30 +02:00
15942b47f5 fix: assertion was inverted 2025-08-28 10:57:00 +02:00
bc8803525d update comments and a parameter name in mcx.h 2025-08-27 14:57:34 +02:00
7531d786be add a function for range deleting chunks 2025-08-27 14:50:35 +02:00
2c5b9def28 should set the spare chunk to 0x400, not 0 2025-08-27 14:50:35 +02:00
cd277873b5 split apart the moving of the data, for further flexibility 2025-08-27 14:50:35 +02:00
f203cabad9 fix: endianess was (often) converted after performing computations or missing completely. 2025-08-27 14:47:50 +02:00
28dd8af353 add comments 2025-08-27 14:38:30 +02:00
9c690eb327 add NONNULL attributes to chunk deletion functions 2025-08-27 12:58:59 +02:00
af8211c5ce write function for bulk removal of chunks 2025-08-27 12:58:28 +02:00
767f3a5c13 refactor of mcx_delchunk to make things a bit more clear and flexible 2025-08-27 12:30:38 +02:00
43e3e4fe85 fix: not converting big-endian numbers to host.
I swear I did have this here at some point... I've got no idea when I
removed it, or even why.
2025-08-27 12:30:38 +02:00
fad6f366b4 fix: mcx_delchunk was not updating the byte offset of affected chunks. 2025-08-27 12:30:38 +02:00
b7859d56d9 add function for computing the bytesize of the *.mcX file 2025-08-27 12:30:38 +02:00
7a45724de7 modify planned features in README.md, to add a few of my ideas. 2025-08-27 11:43:17 +02:00
1c863099a9 write a function for deleting a specific chunk. 2025-08-27 11:43:17 +02:00
2f68574aea swap order of nbt_proctag and nbt_nexttag 2025-08-26 11:32:26 +02:00
4e230ae856 add a utility macro for getting the bitwise ceiling. (e.i. the next power of 2) 2025-08-26 11:32:26 +02:00
e02f4091b8 start writing code for *.mcX file parsing, starting with indexing logic. 2025-08-26 11:32:26 +02:00
1f46640e49 clarify which bytes we're referring to 2025-08-25 13:44:01 +02:00
17357d4672 remove unused/unmaintained code.
this will likely re-appear in some form or another later on, but
currently I don't see its use.
2025-08-25 12:42:45 +02:00
5644d377de make helper functions for nbt_proctag have similar IO as itself.
fix: `procarr` was counting `nmem`, whilst we should be counting the
bytes.
fix: `procarr` wasn't actually writing to `struct nbt_array`'s `len`
member.
2025-08-25 12:41:12 +02:00
44d2271972 fix: return values and list processing was too funky with nbt_proctag.
- Updated documentation
- Fixed logic so `nbt_proctag` actually returns the next pointer.
2025-08-25 10:57:15 +02:00
545eecca37 remove CI, because I cannot be bothered.
I am not going to work on the continuous integration workflow for now.
It keeps failing and I just do not care about it right now.
2025-08-25 09:32:31 +02:00
6dae1d1600 use a flexible array member for including length with array data 2025-08-24 13:37:33 +02:00
a6442a851e write a function for processing NBT_LIST 2025-08-24 13:06:04 +02:00
ec04177f45 remove exessive comments 2025-08-24 12:52:43 +02:00
40b8c0ef30 write documentation for nbt_procarr. 2025-08-24 12:51:59 +02:00
7feb193c51 make procarr code more consise. 2025-08-24 12:48:21 +02:00
59 changed files with 1294 additions and 1358 deletions

View File

@@ -1,3 +1,7 @@
# Copyright (c) 2025 Quinn.
# This is a file from the project MCA-Selector-Lite and is
# licensed under the MIT Licence. See included LICENSE file for details.
#
---
# ---------------------------
# general style settings
@@ -21,9 +25,9 @@ AlignTrailingComments: true
AlignConsecutiveMacros: AcrossEmptyLines
AlignEscapedNewlines: Left
AlignArrayOfStructures: Left
AlignConsecutiveAssignments: None
AlignConsecutiveAssignments: Consecutive
AlignConsecutiveBitFields: AcrossEmptyLines
AlignConsecutiveDeclarations: None
AlignConsecutiveDeclarations: Consecutive
AlignConsecutiveShortCaseStatements:
Enabled: true
@@ -31,9 +35,9 @@ AlignConsecutiveShortCaseStatements:
# short constructs on a single line
# ---------------------------
AllowShortBlocksOnASingleLine: Always
AllowShortFunctionsOnASingleLine: All
AllowShortFunctionsOnASingleLine: None
AllowShortLambdasOnASingleLine: All
AllowShortIfStatementsOnASingleLine: AllIfsAndElse
AllowShortIfStatementsOnASingleLine: Never
AllowShortCaseLabelsOnASingleLine: true
AllowShortEnumsOnASingleLine: true
AllowShortLoopsOnASingleLine: true
@@ -58,7 +62,7 @@ BraceWrapping:
AfterCaseLabel: false
AfterClass: false
AfterEnum: false
AfterFunction: false
AfterFunction: true
AfterNamespace: false
AfterObjCDeclaration: false
AfterStruct: false
@@ -96,10 +100,6 @@ IncludeIsMainSourceRegex: ''
IncludeIsMainRegex: '([-_](test|unittest))?$'
IncludeBlocks: Regroup
IncludeCategories:
- Regex: '^<glad/gl\.h>'
Priority: 1
SortPriority: 0
CaseSensitive: true
- Regex: '^<.*\.h>'
Priority: 2
SortPriority: 0

View File

@@ -1,5 +1,6 @@
# Copyright (c) 2025 Quinn
# Licensed under the MIT Licence. See LICENSE for details
# Copyright (c) 2025 Quinn.
# This is a file from the project MCA-Selector-Lite and is
# licensed under the MIT Licence. See included LICENSE file for details.
[*]
charset = utf-8

39
.github/Dockerfile vendored
View File

@@ -1,39 +0,0 @@
# Copyright (c) 2025 Quinn
# Licensed under the MIT Licence. See LICENSE for details
# docker buildx build . -t ghcr.io/thepigeongenerator/mcaselector-lite:latest
# docker push ghcr.io/thepigeongenerator/mcaselector-lite:latest
FROM debian:testing-slim
LABEL org.opencontainers.image.source="https://github.com/thepigeongenerator/mcaselector-lite"
LABEL org.opencontainers.image.source-path="/.github/Dockerfile"
# install the dependencies
RUN \
echo 'APT::Install-Recommends "false";' > /etc/apt/apt.conf.d/no-recommends; \
echo 'APT::Install-Suggests "false";' >> /etc/apt/apt.conf.d/no-recommends; \
dpkg --add-architecture i386; \
apt-get update -qq; \
apt-get install -y \
ca-certificates \
binutils \
git curl \
zip unzip \
wine wine64 \
make cmake pkg-config \
gcc gcc-multilib g++ mingw-w64 \
libc6-dev \
libxinerama-dev libxcursor-dev libxi-dev libxrandr-dev libglu1-mesa-dev \
libglfw3 \
nodejs; \
apt-get clean; \
rm -rf /var/lib/apt/lists/* /usr/share/doc/* /usr/share/man/* /usr/share/locale/*; \
rm -rf /usr/lib/node_modules/npm/docs /usr/lib/node_modules/npm/html; \
rm -rf /usr/share/wine/fonts /usr/share/wine/mono; \
find /usr -name "*.py" -delete; \
find /usr/lib/wine -name "*.a" -delete; \
find /usr/lib/wine -name "*.la" -delete; \
find /usr/lib/wine -name "*.def" -delete; \
strip /usr/bin/wine* || true; \
rm -rf /var/cache/* /var/log/*;
CMD ["bash"]

View File

@@ -1,52 +1,57 @@
# Copyright (c) 2025 Quinn
# Licensed under the MIT Licence. See LICENSE for details
name: CI
on:
push:
branches:
- '**'
pull_request:
branches:
- '**'
env:
VCPKG_DISABLE_METRICS: 1
VCPKG_ROOT: ${{github.workspace}}/.vcpkg
WINEPREFIX: /tmp/wineprefix
branches: [main]
jobs:
#
# the mega job containing all things we need to do, since setting up a single system is more efficient than installing the same stuff on multiple ones
#
exec-ci-tasks:
runs-on: ubuntu-latest
compile-and-test:
strategy:
fail-fast: false # disable fail fast, so feedback is provided for all matrix combinations
matrix:
include:
- os: ubuntu-latest
cc: cc
- os: ubuntu-24.04-arm
cc: cc
- os: windows-latest
cc: gcc
- os: windows-11-arm
cc: gcc
# - os: macos-latest
# cc: cc
env:
CC: ${{matrix.cc}}
runs-on: ${{matrix.os}}
steps:
# general setup
- name: checkout main branch
uses: actions/checkout@v4
- name: checkout vcpkg
uses: actions/checkout@v4
- uses: actions/checkout@v5
with:
repository: microsoft/vcpkg
path: ${{env.VCPKG_ROOT}}
submodules: true
fetch-depth: 1
- name: get library data
id: libdat
run: echo "LIBS_HASH=vcpkg_installed-$(cat vcpkg.json | sha256sum | cut -d ' ' -f1)" >>"$GITHUB_OUTPUT"
# restore the cache
- uses: actions/cache@v4
id: vcpkg-cache
with:
path: vcpkg_installed
key: ${{matrix.os}}-${{steps.libdat.outputs.LIBS_HASH}}
restore-keys: ${{matrix.os}}-vcpkg_installed-
# setup vcpkg if the cache didn't hit
- name: install vcpkg dependencies
if: steps.vcpkg-cache.outputs.cache-hit != 'true'
- name: "Linux: install deps"
if: runner.os == 'Linux'
run: |
"$VCPKG_ROOT/bootstrap-vcpkg.sh"
"$VCPKG_ROOT/vcpkg" install
sudo apt update
sudo apt install -y libwayland-dev libxkbcommon-dev xorg-dev cmake xxd
# perform continuous integration actions
- run: make compile -j
- run: make run DEBUG=test -j
- name: get submodule hash
id: get-hash
run: echo "HASH=$(git submodule | sha1sum | cut -d' ' -f1)" >$GITHUB_OUTPUT
shell: bash
- uses: actions/cache@v4
id: cache-deps
with:
path: lib/obj/
# I swear to god, if runner.arch displays x64 for x86_64, I will eat a potato.
# note: it is... fucking shit.
key: ${{runner.os}}_${{runner.arch}}-lib/obj-${{steps.get-hash.outputs.HASH}}
restore-keys: ${{runner.os}}_${{runner.arch}}-lib/obj-
- run: make -Bj libs
if: steps.cache-deps.outputs.cache-hit != 'true' && runner.os != 'Windows'
- run: make -Bj2 libs # compile fewer cores, to save memory.
if: steps.cache-deps.outputs.cache-hit != 'true' && runner.os == 'Windows'
- run: make -j all
- run: make -j check

7
.gitignore vendored
View File

@@ -1,5 +1,6 @@
# Copyright (c) 2025 Quinn
# Licensed under the MIT Licence. See LICENSE for details
# Copyright (c) 2025 Quinn.
# This is a file from the project MCA-Selector-Lite and is
# licensed under the MIT Licence. See included LICENSE file for details.
# ignore all dotfiles by default
.*
@@ -18,4 +19,6 @@
*.lock
/bin/
/obj/
/lib/obj/
compile_commands.json
compile_commands.events.json

11
.gitmodules vendored Normal file
View File

@@ -0,0 +1,11 @@
# Copyright (c) 2025 Quinn.
# This is a file from the project MCA-Selector-Lite and is
# licensed under the MIT Licence. See included LICENSE file for details.
[submodule "lib/glfw"]
path = lib/glfw
url = https://github.com/glfw/glfw
[submodule "lib/libarchive"]
path = lib/libarchive
url = https://github.com/libarchive/libarchive

105
Makefile Normal file
View File

@@ -0,0 +1,105 @@
# Copyright (c) 2025 Quinn.
# This is a file from the project MCA-Selector-Lite and is
# licensed under the MIT Licence. See included LICENSE file for details.
SHELL = /bin/sh
.SUFFIXES:
NAME = mcaselector-lite
CC ?= cc
RM ?= rm -vf
CMAKE ?= cmake -G 'Unix Makefiles'
CPPFLAGS ?= -DNDEBUG
CFLAGS ?= -O2
LDFLAGS ?= -flto
CPPFLAGS += -DGLFW_INCLUDE_NONE
CPPFLAGS += -Iinclude -Ilib/glad/include -Ilib/glfw/include -Ilib/libarchive/libarchive
LDFLAGS += -Llib/obj/glfw/src -Llib/obj/libarchive/libarchive
LDLIBS += -lglfw3 -larchive -lm
CFLAGS += -std=gnu99 -g -MMD -MP
CFLAGS += -Wall -Wextra -Wpedantic -Wno-pointer-arith
# detect if we're compiling on Windows, meaning
# a lot of things considered "standard" are unavailable.
ifeq ($(OS),Windows_NT)
NAME := $(NAME).exe
LDLIBS += -lopengl32 -lgdi32
$(warning Detected Windows_NT, please refer to the documentation if you encounter issues.)
# in the case of Mac OS X
else ifeq ($(shell uname -s),Darwin)
LDLIBS += -framework Coca -framework OpenGL -framework IOKit
$(info Mac OS X detected.)
endif
# find all the source files using wildcards
# TODO: find a better method to find all source files
# NOTE: MS-DOS and MS-Windows uses backslash `\`, this might break.
RES := $(wildcard res/*)
SRC := $(wildcard src/*.c src/*/*.c src/*/*/*.c src/*/*/*/*.c src/*/*/*/*/*.c src/*/*/*/*/*/*.c src/*/*/*/*/*/*/*.c src/*/*/*/*/*/*/*/*.c)
SRC += lib/glad/src/gl.c
TSRC := $(wildcard test/*.c test/*/*.c test/*/*/*.c test/*/*/*/*.c test/*/*/*/*/*.c test/*/*/*/*/*/*.c test/*/*/*/*/*/*/*.c test/*/*/*/*/*/*/*/*.c)
OBJ := $(RES:%=obj/%.o) $(SRC:%.c=obj/%.o)
TOBJ := $(TSRC:%.c=obj/%.o)
# TODO: potentially automatically detect whether we should compile libs, or if we can just go ahead.
.PHONY: all libs check clean clean-libs
all: bin/$(NAME)
libs: lib/obj/glfw/ lib/obj/libarchive/
check: bin/TEST_$(NAME); ./$<
clean:; @-$(RM) -r bin/ obj/
clean-libs:; @-$(RM) -r lib/obj/
.PHONY:
install: all
ifneq ($(OS),Windows_NT)
# TODO: POSIX-compliant installation
else
# TODO: WINDOWS_NT installation
endif
.PHONY:
install-strip: install
# TODO: strip the produced installation
# compiles the libraries using cmake
lib/obj/%/: lib/%/
$(CMAKE) -S $< -B $@
$(MAKE) -C $@
# link together a runtime binary
bin/$(NAME): $(OBJ)
$(info [LD] $@)
@mkdir -p $(@D)
@$(CC) -o $@ $^ $(LDFLAGS) $(LDLIBS)
# link together a testing binary
bin/TEST_$(NAME): $(TOBJ) $(filter-out obj/src/main.o,$(OBJ))
$(info [LD] $@)
@mkdir -p $(@D)
@$(CC) -o $@ $^ $(LDFLAGS) $(LDLIBS)
obj/res/%.c: res/%
$(info [XXD] $@)
@mkdir -p $(@D)
@cd res/ && xxd -i $(patsubst res/%,%,$<) $(abspath $@)
obj/%.o: %.c
$(info [CC] $@)
@mkdir -p $(@D)
@$(CC) -c $(CPPFLAGS) $(CFLAGS) -o $@ $<
obj/%.o: obj/%.c
$(info [CC] $@)
@mkdir -p $(@D)
@$(CC) -c $(CPPFLAGS) $(CFLAGS) -o $@ $<
# Include the generated dependency files.
# Which creates rules for all dependencies,
# as a result updating an .o file when a .h is updated.
-include $(OBJ:%.o=%.d)
-include $(TOBJ:%.o=%.d)

View File

@@ -6,7 +6,8 @@ A from-scratch rewrite of [mcaselector](https://github.com/Querz/mcaselector) in
[![licence](https://img.shields.io/github/license/thepigeongenerator/mcaselector-lite.svg)](https://github.com/thepigeongenerator/mcaselector-lite/blob/main/LICENSE)
[![issues](https://img.shields.io/github/issues/thepigeongenerator/mcaselector-lite.svg)](https://github.com/thepigeongenerator/mcaselector-lite/issues/)
[![CI](https://github.com/thepigeongenerator/mcaselector-lite/actions/workflows/ci.yaml/badge.svg)](https://github.com/thepigeongenerator/mcaselector-lite/actions/workflows/ci.yaml)
[![CI-status](https://github.com/thepigeongenerator/mcaselector-lite/actions/workflows/ci.yaml/badge.svg)](https://github.com/thepigeongenerator/mcaselector-lite/actions/workflows/ci.yaml)
## what does it do
MCA selector lite is a tool used to edit [region files](https://minecraft.wiki/w/Region_file_format) of your [Minecraft java](https://minecraft.wiki/w/Java_Edition) worlds.
@@ -20,7 +21,12 @@ I have picked C as the language for the core portions of the application, where
This version is not intended to serve to entirely replace MCA selector, just to offer an alternative. Both tools will have their strengths and weaknesses.
# planned features
- [ ] (very basic) world map viewing
- [ ] (very basic) world map viewing (Y level + surface)
- [ ] filtering chunks based on time spent in them
- [ ] chunk-level removal of data.
- [ ] CLI-only version
- [ ] (idea) change chunk compression
- [ ] (idea) view chunk metadata
- [ ] (idea) change chunk sNBT (this'll be tricky to add)
*note that entries marked with `(idea)` aren't guaranteed to be implemented.
Due to their viability and usefulness is still to be measured.*

View File

@@ -21,7 +21,7 @@ Where we have dependencies on:
| [openGL](https://www.opengl.org/) | hardware accelleration, for handling graphics. |
It is intended to be platform-agnostic, within reason. But the main focus is for [Linux](https://wikipedia.org/wiki/Linux) systems with [x86_64](https://wikipedia.org/wiki/X86-64) architecture.
Within [intdef.h](/src/util/intdef.h) there live definitions for fixed-width integer types.
Within [types.h](/src/types.h) there live definitions for fixed-width integer types.
### style guide
- Code must be written correctly, read [Correct C](./correct-c.md) if more information is required.

View File

@@ -98,8 +98,8 @@ The offset of a chunk (x,z) (in chunk coordinates) in the first table can be fou
| data | locations (4B) | timestamps (4B) | chunks and unused space |
##### chunk location
Location info for a chunk is stored as a 32 bit big-endian integer, where the first three bytes are an offset in 4KiB sectors from the start of the file.
The last byte gives the length of the chunk in 4KiB sectors. (rounded up, of course). Where chunks are always less than 1MiB in size.
Location info for a chunk is stored as a 32 bit big-endian integer, where the first three bytes (0xFFFFFF00) are an offset in 4KiB sectors from the start of the file.
The last byte (0x000000FF) gives the length of the chunk in 4KiB sectors. (rounded up, of course). Where chunks are always less than 1MiB in size.
If a chunk isn't present in the region file (e.g. because it hasn't been generated or migrated yet), both fields are zero.
##### timestamps

View File

@@ -1,10 +1,9 @@
// Copyright (c) 2025 Quinn
// Licensed under the MIT Licence. See LICENSE for details
#pragma once
/* Copyright (c) 2025 Quinn
* Licensed under the MIT Licence. See LICENSE for details */
#ifndef PORTABLE_ENDIAN_H
#define PORTABLE_ENDIAN_H 1
#if __has_include(<endian.h>)
#include <endian.h>
#else
#if defined(__GNUC__)
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
#define le16toh(x) __uint16_identity(x)
#define le32toh(x) __uint32_identity(x)
@@ -33,5 +32,9 @@
#define htobe64(x) __uint64_identity(x)
#else
#error machine architecture unsupported! Expected either big-endian or little-endian, make sure to use a compiler which defines __BYTE_ORDER__ (like clang or gcc)
#endif
#endif
#endif /* byte order */
#else
#error GNU C is unavailable
#endif /* __GNUC__ */
#endif /* PORTABLE_ENDIAN_H */

1
lib/glfw Submodule

Submodule lib/glfw added at 7b6aead9fb

1
lib/libarchive Submodule

Submodule lib/libarchive added at 9525f90ca4

107
makefile
View File

@@ -1,107 +0,0 @@
# Copyright (c) 2025 Quinn
# Licensed under the MIT Licence. See LICENSE for details
SHELL = bash
.SHELLFLAGS = -O globstar -c
# build configuration, information about the current build process
NAME = mcaselector-lite
VERSION = 0.0.0
DEBUG ?= 0
CC ?= cc
LD ?= ld
CFLAGS += -c -std=gnu99 -Wall -MMD -MP
CFLAGS += -Ilib/glad/include
LDFLAGS += -flto -lm
MARCH ?= $(shell uname -m)
KERNEL ?= $(shell uname -s | tr '[:upper:]' '[:lower:]')
# compilation flags
CFLAGS = -c -std=gnu99 -Wall -Wextra -Wpedantic -MMD -MP
LDFLAGS = -flto
# architecture/OS detection
ifeq ($(KERNEL),)
ISWIN := $(if $(filter $(OS),Windows_NT),1,0)
ifeq ($(ISWIN),1)
KERNEL = mingw
MARCH = x86_64
else
MARCH := $(shell uname -m)
KERNEL := $(shell uname -s | tr '[:upper:]' '[:lower:]')
endif
else
ISWIN := $(if $(filter $(KERNEL),mingw),1,0)
endif
ifeq ($(MARCH),)
$(error must also set MARCH when manually setting KERNEL)
endif
# profiles
ifeq ($(DEBUG),1)
PROF = dbg
CFLAGS += -UNDEBUG -Og -g -Wextra -Wpedantic
CFLAGS += $(if $(filter 1,$(ISWIN)),,-fsanitize=address,undefined)
LDFLAGS += $(if $(filter 1,$(ISWIN)),,-fsanitize=address,undefined)
# |--profile: testing
else ifeq ($(DEBUG),test)
PROF = test
CFLAGS += -UNDEBUG -O2 -g
CFLAGS += $(if $(filter 1,$(ISWIN)),,-fsanitize=address)
LDFLAGS += $(if $(filter 1,$(ISWIN)),,-fsanitize=address)
else
PROF = rel
CFLAGS += -DNDEBUG -O2
endif
CFLAGS += $(shell pkg-config --cflags glfw3 libarchive) -Ilib/glad/include
LDFLAGS += $(shell pkg-config --libs glfw3 libarchive) -lm
# get source files
SRC := $(shell echo src/**/*.c) lib/glad/src/gl.c
RES := $(wildcard res/*.glsl)
ifeq ($(DEBUG),test)
SRC := $(filter-out src/main.c, $(SRC)) $(shell echo test/**/*.c)
endif
NAME += $(if $(filter 1,$(ISWIN)),.exe,)
DIR_BIN := bin/$(MARCH)-$(KERNEL)/$(VERSION)/$(PROF)
DIR_OBJ := obj/$(MARCH)-$(KERNEL)/$(VERSION)/$(PROF)
# output files
BIN := $(DIR_BIN)/$(NAME)
OBJ := $(SRC:%.c=$(DIR_OBJ)/%.o) $(RES:%=$(DIR_OBJ)/%.o)
DEP := $(OBJ:%.o=%.d)
.PHONY:
run: compile
$(if $(filter 1,$(ISWIN)),wine,) $(BIN)
.PHONY:
compile: $(BIN)
.PHONY .NOTPARALLEL:
clean:
@rm -rv obj/
@rm -rv bin/
$(BIN): $(OBJ)
$(info [CC/LD] $@)
@mkdir -p $(@D)
@$(CC) -o $@ $^ $(LDFLAGS)
$(DIR_OBJ)/%.o: %.c
$(info [CC] $@)
@mkdir -p $(@D)
@$(CC) $(CFLAGS) -o $@ $<
$(DIR_OBJ)/res/%.o: res/%
$(info [LD] $@)
@mkdir -p $(@D)
@$(LD) -r -b binary -o $@ $<
# some definitions for "default" and assumed compilers, for bulk selection
.PHONY: x86_64-linux-gnu-gcc x86_64-w64-mingw32-gcc
x86_64-linux-gnu-gcc:; $(MAKE) $(CALL) $(MAKEFLAGS) CC=$@ MARCH=x86_64 KERNEL=linux
x86_64-w64-mingw32-gcc:; $(MAKE) $(CALL) $(MAKEFLAGS) CC=$@ MARCH=x86_64 KERNEL=mingw
-include $(DEP)

187
src/dat/mcx.c Normal file
View File

@@ -0,0 +1,187 @@
/* Copyright (c) 2025 Quinn.
* This is a file from the project MCA-Selector-Lite and is
* licensed under the MIT Licence. See the project's LICENSE file for details. */
#include "mcx.h"
#include <archive.h>
#include <assert.h>
#include <endian.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include "../types.h"
#include "../util/error.h"
#define SECTOR 0x1000 // sector size
#define TABLE 0x800 // table (total) element count
#define CHUNKS 0x400 // amount of chunks in a file
enum mcx_compression {
MCX_COMPRESSION_GZIP = 0x01,
MCX_COMPRESSION_ZLIB = 0x02,
MCX_COMPRESSION_NONE = 0x03,
MCX_COMPRESSION_LZ4 = 0x04,
MCX_COMPRESSION_CUSTOM = 0x7F,
};
/* first 4 bytes is an i32 indicating remaining bytes, the following byte defines the compression scheme */
static int mcx_loadchunk(const u8 *restrict buf, const i32 *restrict table, int idx)
{
const u8 *chunk = buf + (be32toh(table[idx]) >> 8) * SECTOR;
i32 len;
memcpy(&len, chunk, 4);
len = be32toh(len);
chunk += 4;
struct archive *archive = archive_read_new();
archive_read_support_format_raw(archive);
switch (*chunk) {
case MCX_COMPRESSION_GZIP: /* fall-through; ZLIB is handled under the GZIP filter */
case MCX_COMPRESSION_ZLIB: archive_read_support_filter_gzip(archive); break;
case MCX_COMPRESSION_NONE: archive_read_support_filter_none(archive); break;
case MCX_COMPRESSION_LZ4: archive_read_support_filter_lz4(archive); break;
case MCX_COMPRESSION_CUSTOM: archive_read_support_filter_all(archive); break;
default: fatal("compression type of '%i' is unsupported!", *chunk);
}
if (archive_read_open_memory(archive, chunk, len) != ARCHIVE_OK) {
error("failed to decompress %i bytes of compression type %i", len, *chunk);
return 1;
}
struct archive_entry *entry;
if (archive_read_next_header(archive, &entry) != ARCHIVE_OK) {
error("failed to decompress %i bytes of compression type %i", len, *chunk);
return 1;
}
ssize size = -1;
for (;;) {
// TODO: handle buffer
// size = archive_read_data(archive, , );
if (size < 0) {
error("failed to decompress %i bytes of compression type %i", len, *chunk);
return 1;
}
if (size == 0)
break;
// TODO: handle data
}
return 0;
}
/* Moves chunks `src_s` to `src_e` (inclusive) from `src`, back onto `dst`. */
static void mvchunks(u8 *dst, u8 *src, u32 *restrict table, int src_s, int src_e)
{
assert(src > dst);
uintptr len = src - dst; // acquire the amount of bytes that we shall move
assert(!(len % SECTOR));
// count how many bytes we need to move, whilst updating location data
usize blen = 0;
for (src_s++; src_s <= src_e; src_s++) {
blen += (be32toh(table[src_s]) & 0xFF) * SECTOR;
table[src_s] -= htobe32((len / SECTOR) << 8);
}
memmove(dst, src, blen);
}
/* Deletes chunk `sidx` by moving chunks up to `eidx` back over `sidx` in `buf`.
* `rmb` is an optional additional offset that can be applied, and signifies bytes already removed.
* Returns the bytes removed by this function. */
static usize delchunk(u8 *restrict buf, u32 *restrict table, usize rmb, int sidx, int eidx)
{
// load the table data
usize slen, bidx, blen;
slen = be32toh(table[sidx]) & 0xFF; // acquire the sector length of the chunk
bidx = (be32toh(table[sidx]) >> 8) * SECTOR; // acquire and compute the byte offset the chunk starts at
blen = slen * SECTOR; // compute the byte length of the chunk
// reset the table data
table[sidx] = 0;
table[sidx + CHUNKS] = htobe32(time(NULL)); // assign the current time to the timestamp, for correctness NOTE: might need to zero-out instead
// move the succeeding chunks over the deleted chunk
u8 *dst = buf + bidx - rmb;
u8 *src = buf + bidx + blen;
mvchunks(dst, src, table, sidx, eidx - 1);
return blen;
}
/* Call `delchunk` with the parameters and some defaults. Ensuring the table is copied correctly as well.
* This is done instead of `delchunk` being globally linked, because
* `delchunk` requests more specific parameters, which is confusing outside this module. */
usize mcx_delchunk(u8 *restrict buf, int chunk)
{
u32 table[TABLE];
memcpy(table, buf, sizeof(table));
usize res = delchunk(buf, table, 0, chunk, CHUNKS);
memcpy(buf, table, sizeof(table));
return res;
}
usize mcx_delchunk_range(u8 *restrict buf, int start, int end)
{
assert(start < end && end < CHUNKS);
u32 table[TABLE];
memcpy(table, buf, sizeof(table));
u8 *dst = buf + (be32toh(table[start]) >> 8) * SECTOR;
u8 *src = buf + (be32toh(table[end]) >> 8) * SECTOR;
src += (be32toh(table[end]) & 0xFF) * SECTOR;
// zeroes-out the chunk data within this range. (and set the timestamp)
u32 ts = htobe32(time(NULL));
for (int i = start; i <= end; i++) {
table[i] = 0;
table[i + CHUNKS] = ts;
}
// move the remaining chunks down
if (end < (CHUNKS - 1))
mvchunks(dst, src, table, end, (CHUNKS - 1));
memcpy(buf, table, sizeof(table));
return src - dst;
}
/* comparer function for to be inputted into `qsort` to compare two */
static int cmp_chunkids(const void *restrict x, const void *restrict y)
{
u16 x2 = *(u16 *)x;
u16 y2 = *(u16 *)y;
return (x2 > y2) - (x2 < y2);
}
/* Sorts the chunks marked for deletion from smallest to greatest index.
* Then performs the deletion in this order. Making sure to only update the chunks up to the next. */
usize mcx_delchunk_bulk(u8 *restrict buf, const u16 *restrict chunks, int chunkc)
{
// ensure the chunks ids we're working on are sorted from least to greatest
u16 chunkids[chunkc + 1];
memcpy(chunkids, chunks, chunkc);
qsort(chunkids, chunkc, sizeof(int), cmp_chunkids);
chunkids[chunkc] = CHUNKS; // set the spare chunk to the max chunks, so the rest of the chunks are moved
u32 table[TABLE];
memcpy(table, buf, sizeof(table));
usize rmb = 0;
for (int i = 0; i < chunkc; i++)
rmb += delchunk(buf, table, rmb, chunkids[i], chunkids[i + 1]);
memcpy(buf, table, sizeof(table));
return rmb;
}
/* Sum together the 4th byte in each location integer to compute the sector size of all chunks.
* Multiplying by `SECTOR`, and adding the size of the table itself. */
usize mcx_calcsize(const u8 *restrict buf)
{
usize size = 0;
for (uint i = 0; i < CHUNKS; i++)
size += *(buf + (i * 4) + 3);
return (size * SECTOR) + (TABLE * 4);
}

View File

@@ -1,19 +1,38 @@
// Copyright (c) 2025 Quinn
// Licensed under the MIT Licence. See LICENSE for details
/* Copyright (c) 2025 Quinn.
* This is a file from the project MCA-Selector-Lite and is
* licensed under the MIT Licence. See the project's LICENSE file for details. */
#pragma once
/* the MCR (Minecraft region) and MCA (Minecraft anvil) files are similar
* MCA is the newer variant, where it includes:
* - a world height of 256, rather than 128.
* - block IDs have been extended to 4096 from 256
* - block ordering is now YZX, rather than XZY.
* - biomes are included in the data per XZ column.
* MCR start with an 8KiB header, split in two 4KiB tables
* - the first containing the offsets of chunks in the region file itself.
* - the second providing timestamps on when they were last updated.
* -
*
*
*
*
*/
#include <stdlib.h>
#include "../types.h"
#include "../util/atrb.h"
/* contains chunk metadata */
struct mcx_chunk {
usize idx; // byte offset for start of chunk data
u32 len; // byte length of chunk (+ padding)
u32 time; // modification time in epoch seconds
};
/* Deletes a single chunk (`chunk`) out of `buf`.
* The chunk's location data shall become `0`, and timestamp data the current time.
* All succeeding chunks shall be moved back, freeing space.
* Returns the amount of bytes removed. */
usize mcx_delchunk(u8 *restrict buf, int chunk) NONNULL((1));
/* Deletes the range defined by `start`—`end` (inclusive) of chunks out of `buf`.
* The chunk's location data shall become `0`, and timestamp data the current time.
* All succeeding chunks shall be moved back, freeing space.
* Returns the amount of bytes removed */
usize mcx_delchunk_range(u8 *restrict buf, int start, int end) NONNULL((1));
/* Deletes a `chunkc` chunks from `chunks` out of `buf`.
* If the `chunks` indices are known to be sequential, i.e. have a constant difference of `1`, `mcx_delchunk_range` should be preferred.
* The chunk's location data shall become `0`, and timestamp data the current time.
* All succeeding chunks shall be moved back, freeing space.
* Returns the amount of bytes removed */
usize mcx_delchunk_bulk(u8 *restrict buf, const u16 *restrict chunks, int chunkc) NONNULL((1, 2));
/* Computes the byte size of the `*.mcX` file in `buf` and returns it. */
usize mcx_calcsize(const u8 *restrict buf) NONNULL((1)) PURE;

View File

@@ -1,36 +1,153 @@
// Copyright (c) 2025 Quinn
// Licensed under the MIT Licence. See LICENSE for details
/* Copyright (c) 2025 Quinn.
* This is a file from the project MCA-Selector-Lite and is
* licensed under the MIT Licence. See the project's LICENSE file for details. */
#include "nbt.h"
#include <assert.h>
#include <endian.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include "../util/compat/endian.h"
#include "../util/intdef.h"
#include "../types.h"
#define MAX_DEPTH 512
/* Extracts a big endian 16 bit integer from address `buf`, converts it to host byte size if needed and returns. */
static inline u16 buftoh16(const void *restrict buf)
{
u16 i;
memcpy(&i, buf, sizeof(i));
return be16toh(i);
}
/* Extracts a big endian 32 bit integer from address `buf`, converts it to host byte size if needed and returns. */
static inline u32 buftoh32(const void *restrict buf)
{
u32 i;
memcpy(&i, buf, sizeof(i));
return be32toh(i);
}
/* Extracts a big endian 64 bit integer from address `buf`, converts it to host byte size if needed and returns. */
static inline u64 buftoh64(const void *restrict buf)
{
u64 i;
memcpy(&i, buf, sizeof(i));
return be64toh(i);
}
/* Processes the incoming array data in `buf`. Which contains `nmem` items of `size`.
* The data shall be converted to little-endian on little-endian systems
* Outputs the allocated data to `out`, returns where the next pointer would be. */
static const u8 *procarr(const u8 *restrict buf, i32 nmemb, uint size, struct nbt_array *restrict out)
{
usize len = nmemb * size;
*out = (struct nbt_array){
out->nmemb = nmemb,
out->dat = malloc(len),
};
if (!out->dat)
return buf + len;
memcpy(out->dat, buf, len);
buf += len;
/* Only include this code for little-endian systems. Since only they require this logic.
* Producing optimised code for other platforms. */
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
if (size == 1)
return buf;
i32 i = 0;
while (i < nmemb) {
switch (size) {
case 2: ((u16 *)out->dat)[i] = be16toh(((u16 *)out->dat)[i]); break;
case 4: ((u32 *)out->dat)[i] = be16toh(((u32 *)out->dat)[i]); break;
case 8: ((u64 *)out->dat)[i] = be16toh(((u64 *)out->dat)[i]); break;
default: __builtin_unreachable(); // this should be impossible
}
i += size;
}
#endif
return buf;
}
/* calls `procarr` for the simple types available. */
static const u8 *proclist(const u8 *restrict buf, struct nbt_array *restrict out)
{
uint size;
switch (*(u8 *)buf) {
case NBT_I8: size = 1; break;
case NBT_I16: size = 2; break;
case NBT_I32: // fall through
case NBT_F32: size = 4; break;
case NBT_I64: // fall though
case NBT_F64: size = 8; break;
default: return NULL;
}
buf++;
i32 len;
memcpy(&len, buf, 4);
len = be32toh(len);
buf += 4;
return procarr(buf, len, size, out);
}
const u8 *nbt_proctag(const u8 *restrict buf, u16 slen, void *restrict out)
{
const u8 *ptr, *tmp;
ptr = buf + 3 + slen;
i32 nmem;
uint size;
switch (*buf) {
case NBT_I8: *(u8 *)out = *ptr; return ptr + 1;
case NBT_I16: *(u16 *)out = buftoh16(ptr); return ptr + 2;
case NBT_I32: // fall through
case NBT_F32: *(u32 *)out = buftoh32(ptr); return ptr + 4;
case NBT_I64: // fall through
case NBT_F64: *(u64 *)out = buftoh64(ptr); return ptr + 8;
case NBT_STR: nmem = buftoh16(ptr), size = 1, ptr += 2; break;
case NBT_ARR_I8: nmem = buftoh32(ptr), size = 1, ptr += 4; break;
case NBT_ARR_I32: nmem = buftoh32(ptr), size = 4, ptr += 4; break;
case NBT_ARR_I64: nmem = buftoh32(ptr), size = 8, ptr += 4; break;
case NBT_LIST:
return proclist(ptr, (struct nbt_array *)out);
return tmp;
default: return NULL;
}
return procarr(ptr, nmem, size, (struct nbt_array *)out);
}
/* handles incrementing to the next tag in the case of `NBT_LIST`. This function shan't return `NULL`.
* `ptr` is assumed to be the start of the `NBT_LIST` data, e.i. The list's ID, followed by the list's length.
* If `ID` is `NBT_I8`, `NBT_I16`, `NBT_I32`, `NBT_I64`, `NBT_F32`, or `NBT_F64`, the entire list length is computed and returned.
* For other types this won't be possible, and thus will add `1` to `dpt`, and write the list data to `lens` and `tags` at this new `dpt`. */
static const u8 *nexttag_list(const u8 *restrict ptr, uint *restrict const dpt, i32 *restrict const lens, u8 *restrict const tags) {
static const u8 *nexttag_list(const u8 *restrict ptr, uint *restrict const dpt, i32 *restrict const lens, u8 *restrict const tags)
{
const u8 *tag = ptr;
ptr++;
switch (*tag) {
case NBT_END: break;
case NBT_I8: ptr += (i32)be32toh(*(u32 *)ptr) * 1; break;
case NBT_I16: ptr += (i32)be32toh(*(u32 *)ptr) * 2; break;
case NBT_I8: ptr += (i32)buftoh32(ptr) * 1; break;
case NBT_I16: ptr += (i32)buftoh32(ptr) * 2; break;
case NBT_I32: // fall through
case NBT_F32: ptr += (i32)be32toh(*(u32 *)ptr) * 4; break;
case NBT_F32: ptr += (i32)buftoh32(ptr) * 4; break;
case NBT_I64: // fall through
case NBT_F64: ptr += (i32)be32toh(*(u32 *)ptr) * 8; break;
case NBT_F64: ptr += (i32)buftoh32(ptr) * 8; break;
default:
// TODO: handle out of bounds... Might not be required if we use flexible array member
(*dpt)++;
tags[*dpt] = *tag;
lens[*dpt] = (i32)be32toh(*(u32 *)ptr);
lens[*dpt] = (i32)buftoh32(ptr);
break;
}
ptr += 4;
@@ -43,8 +160,9 @@ static const u8 *nexttag_list(const u8 *restrict ptr, uint *restrict const dpt,
* - `lens` shall contain `MAX_DEPTH` of items representing the list length, if the current item is non-zero we shall assume we're in a list.
* Where the value is decremented until we reach `0`.
* - `tags` shall contain `MAX_DEPTH` of items representing the list's stored type. */
static const u8 *nexttag(const u8 *restrict tag, uint *restrict const dpt, i32 *restrict const lens, u8 *restrict const tags) {
u8 type;
static const u8 *nexttag(const u8 *restrict tag, uint *restrict const dpt, i32 *restrict const lens, u8 *restrict const tags)
{
u8 type;
const u8 *ptr = tag;
if (lens[*dpt]) {
type = tags[*dpt];
@@ -52,7 +170,7 @@ static const u8 *nexttag(const u8 *restrict tag, uint *restrict const dpt, i32 *
*dpt -= !lens[*dpt];
} else {
type = *tag;
ptr += be16toh(*(u16 *)(tag + 1)) + 3;
ptr += buftoh16(tag + 1) + 3;
}
switch (type) {
@@ -63,10 +181,10 @@ static const u8 *nexttag(const u8 *restrict tag, uint *restrict const dpt, i32 *
case NBT_I64: // fall through
case NBT_F64: ptr += 8; break;
case NBT_ARR_I8: ptr += 4 + (i32)be32toh(*(u32 *)ptr) * 1; break;
case NBT_ARR_I32: ptr += 4 + (i32)be32toh(*(u32 *)ptr) * 4; break;
case NBT_ARR_I64: ptr += 4 + (i32)be32toh(*(u32 *)ptr) * 8; break;
case NBT_STR: ptr += 2 + (u16)be16toh(*(u16 *)ptr) * 1; break;
case NBT_ARR_I8: ptr += 4 + (i32)buftoh32(ptr) * 1; break;
case NBT_ARR_I32: ptr += 4 + (i32)buftoh32(ptr) * 4; break;
case NBT_ARR_I64: ptr += 4 + (i32)buftoh32(ptr) * 8; break;
case NBT_STR: ptr += 2 + (u16)buftoh16(ptr) * 1; break;
case NBT_END: (*dpt)--; break;
case NBT_COMPOUND: (*dpt)++; break;
@@ -85,11 +203,12 @@ static const u8 *nexttag(const u8 *restrict tag, uint *restrict const dpt, i32 *
* - compound:list:int32
* - string
*/
const u8 *nbt_nexttag(const u8 *restrict buf) {
const u8 *nbt_nexttag(const u8 *restrict buf)
{
const u8 *tag;
u8 tags[MAX_DEPTH] = {0};
i32 lens[MAX_DEPTH] = {0};
uint dpt = 0;
u8 tags[MAX_DEPTH] = {0};
i32 lens[MAX_DEPTH] = {0};
uint dpt = 0;
tag = buf;
do {
@@ -97,84 +216,3 @@ const u8 *nbt_nexttag(const u8 *restrict buf) {
} while (dpt > 0);
return tag;
}
MALLOC static void *nbt_procarr(const u8 *restrict buf, i32 nmem, uint size) {
u8 *ptr = malloc(nmem * size);
if (!ptr) return NULL;
memcpy(ptr, buf, nmem * size);
/* Only include this code for little-endian systems. Since only they require this logic.
* Producing optimised code for other platforms. */
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
if (size == 1) return ptr;
ssize_t i = 0;
while (i < nmem) {
switch (size) {
case 2:
*(u16 *)(ptr + i) = be16toh(*(u16 *)(ptr + i));
i += 2;
break;
case 4:
*(u32 *)(ptr + i) = be32toh(*(u32 *)(ptr + i));
i += 4;
break;
case 8:
*(u64 *)(ptr + i) = be64toh(*(u64 *)(ptr + i));
i += 8;
break;
default: __builtin_unreachable(); // this should be impossible
}
}
#endif
return ptr;
}
/* readies the output data for export, returns the new buffer position, or `NULL` upon an error (may be out of bounds) */
const u8 *nbt_proctag(const u8 *restrict buf, u16 slen, void *restrict out) {
const u8 *ptr = buf + 3 + slen;
switch (*buf) {
// integral types
case NBT_I8: *(u8 *)out = *ptr; return ptr + 1;
case NBT_I16: *(u16 *)out = be16toh(*(u16 *)ptr); return ptr + 2;
case NBT_I32: // fall through
case NBT_F32: *(u32 *)out = be16toh(*(u32 *)ptr); return ptr + 4;
case NBT_I64: // fall through
case NBT_F64: *(u64 *)out = be16toh(*(u64 *)ptr); return ptr + 8;
// arrays, handled differently
case NBT_STR: *(void **)out = nbt_procarr(ptr += 2, be16toh(*(u16 *)buf), 1); break;
case NBT_ARR_I8: *(void **)out = nbt_procarr(ptr += 4, be32toh(*(u32 *)buf), 1); break;
case NBT_ARR_I32: *(void **)out = nbt_procarr(ptr += 4, be32toh(*(u32 *)buf), 4); break;
case NBT_ARR_I64: *(void **)out = nbt_procarr(ptr += 8, be32toh(*(u64 *)buf), 8); break;
case NBT_LIST: // TODO: handle simple lists
default:
return NULL;
}
return ptr;
}
struct nbt_procdat nbt_initproc(struct nbt_path const *restrict pats, uint npats) {
i16 mdpt = 0;
// acquire the maximum depth that we'll need to go (exclusive)
for (uint i = 0; i < npats; i++) {
int tmp = pats[i].len - mdpt;
mdpt += -(tmp > 0) & tmp;
}
assert(mdpt > 0);
// storing the segments of the current path
const char **cpat = (const char **)calloc(mdpt - 1, sizeof(void *));
// return the initialised structure.
return (struct nbt_procdat){
pats,
cpat,
npats,
0,
mdpt,
};
}

View File

@@ -1,14 +1,15 @@
// Copyright (c) 2025 Quinn
// Licensed under the MIT Licence. See LICENSE for details
/* Copyright (c) 2025 Quinn.
* This is a file from the project MCA-Selector-Lite and is
* licensed under the MIT Licence. See the project's LICENSE file for details. */
#pragma once
#include <assert.h>
#include <endian.h>
#include <stdbool.h>
#include <stdlib.h>
#include "../types.h"
#include "../util/atrb.h"
#include "../util/compat/endian.h"
#include "../util/intdef.h"
/* NBT (named binary tag) is a tree data structure. Tags have a numeric type ID, name and a payload.
* NBT files are a compressed `compound` tag. GZip is the compression used in most cases,
@@ -21,46 +22,36 @@
/* specifies the NBT tag IDs.
* NOTE: every type is stored as BE (big-endian) in the file. */
enum nbt_tagid {
NBT_END = 0x00, // signifies the end of a compound tag
NBT_I8 = 0x01, // next byte is for an 8 bit signed integer.
NBT_I16 = 0x02, // next 2 bytes are for a 16 bit signed integer
NBT_I32 = 0x03, // next 4 bytes are for a 32 bit signed integer
NBT_I64 = 0x04, // next 8 bytes are for a 64 bit signed integer
NBT_F32 = 0x05, // next 4 bytes are for a single-precision floating-point
NBT_F64 = 0x06, // next 8 bytes are for a double-precision floating-point
NBT_ARR_I8 = 0x07, // starts with a i32, denoting size, followed by the i8 data
NBT_STR = 0x08, // starts with a u16, denoting size, followed by the UTF-8 data
NBT_LIST = 0x09, // starts with an ID, followed by a 32 bit signed integer denoting the size
NBT_END = 0x00, // signifies the end of a compound tag
NBT_I8 = 0x01, // next byte is for an 8 bit signed integer.
NBT_I16 = 0x02, // next 2 bytes are for a 16 bit signed integer
NBT_I32 = 0x03, // next 4 bytes are for a 32 bit signed integer
NBT_I64 = 0x04, // next 8 bytes are for a 64 bit signed integer
NBT_F32 = 0x05, // next 4 bytes are for a single-precision floating-point
NBT_F64 = 0x06, // next 8 bytes are for a double-precision floating-point
NBT_ARR_I8 = 0x07, // starts with a i32, denoting size, followed by the i8 data
NBT_STR = 0x08, // starts with a u16, denoting size, followed by the UTF-8 data
NBT_LIST = 0x09, // starts with an ID, followed by a 32 bit signed integer denoting the size
NBT_COMPOUND = 0x0A, // compound tag, contains tags and is delimited by `NBT_END`
NBT_ARR_I32 = 0x0B, // starts with a i32, denoting size, followed by the i32 data
NBT_ARR_I64 = 0x0C, // starts with a i32, denoting size, followed by the u32 data
NBT_ARR_I32 = 0x0B, // starts with a i32, denoting size, followed by the i32 data
NBT_ARR_I64 = 0x0C, // starts with a i32, denoting size, followed by the u32 data
};
/* TODO: write doc */
struct nbt_path {
const char **restrict pat; // specifies the NBT path components as separate elements
i16 len; // specifies the length of the NBT elements
struct nbt_array {
i32 nmemb;
void *dat;
};
/* TODO: write doc */
struct nbt_procdat {
const struct nbt_path *pats;
const char *restrict *cpat;
u32 npats;
i16 dpt, mdpt;
};
/* Acquires the data contained by the named tag.
* - `buf` points to the start of the tag.
* - `slen` contains the string length of the name.
* - `out` points to where the data should be written.
* if `buf` points to `NBT_I8`, `NBT_I16`, `NBT_I32`, `NBT_I64`, `NBT_F32`, or `NBT_F64`, `*out` is assumed
* to have the available byte width for one of these types. In the case of `NBT_ARR*` and `NBT_LIST`
* it must point to a `struct nbt_array*`. Where in the case of `NBT_LIST`, it must be one of the previous static-width types. */
const u8 *nbt_proctag(const u8 *restrict buf, u16 slen, void *restrict out) NONNULL((1, 3));
/* searches for the end of a named tag without processing data, the final pointer is returned.
* `NULL` is returned upon failure, the otherwise returned pointer is not guaranteed to be valid. */
const u8 *nbt_nexttag(const u8 *restrict buf) NONNULL((1)) PURE;
/* Processes the tag entered in `buf`, `buf` is assumed to be the start of a named tag. Where `slen` shall be the string length.
* The data in `buf` is processed and outputted to `out`. A pointer to the next tag is returned.
* - In the case for all basic types, `out` will require to be the width of said type.
* - In the case of arrays, a pointer shall be returned pointing to the data. TODO: it'd be nice to know how large this array is.
* - In the case of `NBT_LIST`, if it is of the type `NBT_Ixx` and `NBT_Fxx`, then it's handled. Otherwise the funcion shall fail.
* Upon failure, `NULL` is returned. */
const u8 *nbt_proctag(const u8 *restrict buf, u16 slen, void *restrict out) NONNULL((1, 3));
/* initialises a data structure used whilst processing the tags */
struct nbt_procdat nbt_initproc(struct nbt_path const *restrict pats, uint npats) NONNULL((1)) PURE;

View File

@@ -1,21 +0,0 @@
// Copyright (c) 2025 Quinn
// Licensed under the MIT Licence. See LICENSE for details
#pragma once
#if __INCLUDE_LEVEL__ > 0
#include <stdio.h>
#include <stdlib.h>
#include "util/macro.h"
#endif
#define debug(s, ...) printf("\033[95m" __FILE__ ":" MACRO_STR2(__LINE__) ": [DBG]: " s "\033[0m\n", ##__VA_ARGS__)
#define info(s, ...) printf(__FILE__ ":" MACRO_STR2(__LINE__) ": [INF]: " s "\n", ##__VA_ARGS__)
#define warn(s, ...) fprintf(stderr, "\033[93m" __FILE__ ":" MACRO_STR2(__LINE__) ": [WAR]: " s "\033[0m\n", ##__VA_ARGS__)
#define error(s, ...) fprintf(stderr, "\033[91m" __FILE__ ":" MACRO_STR2(__LINE__) ": [ERR]: " s "\033[0m\n", ##__VA_ARGS__)
#define fatal(s, ...) \
do { \
fprintf(stderr, "\033[101m" __FILE__ ":" MACRO_STR2(__LINE__) ": [FAT]: " s "\033[0m\n", ##__VA_ARGS__); \
exit(EXIT_FAILURE); \
} while (0)

35
src/io/conf.c Normal file
View File

@@ -0,0 +1,35 @@
#include "conf.h"
#include <string.h>
#include "../types.h"
#include "../util/atrb.h"
/* Matches s1 with s2, returns a pointer to s1 where the match stopped. */
static const char *strmat(const char *s1, const char *s2) PURE NONNULL((1, 2));
static const char *strmat(const char *s1, const char *s2)
{
while ((*s1 == *s2) & !!*s1)
s1++, s2++;
return s1;
}
int conf_getkeyval(const char *restrict buf, const char *const restrict *restrict keys, int klen, const char *restrict *restrict out)
{
const char *tmp = NULL;
ASSUME((klen > 0));
int i = 0;
for (; i < klen && !tmp; i++) {
tmp = strmat(buf, keys[i]);
tmp = keys[i][buf - tmp] ? tmp : 0;
}
if (!tmp || *tmp != '=')
return -1;
*out = tmp + 1;
return i;
}
int conf_val(int);

12
src/io/conf.h Normal file
View File

@@ -0,0 +1,12 @@
#pragma once
#include "../types.h"
#include "../util/atrb.h"
/* Gets the key and value, if present. Writes the pointer for the value to `out`.
* Returns the key index, or <0 upon failure. */
int conf_getkeyval(const char *restrict buf, const char *const restrict *restrict keys, int klen,
const char *restrict *restrict out) NONNULL((1, 2, 4));
/* Processes the value of `type` in `val`. Outputs to `out`.
* Returns non-zero on failure. */
int conf_procval(u8 type, const char *restrict val, void *restrict out) NONNULL((2, 3));

View File

@@ -1,7 +0,0 @@
// Copyright (c) 2025 Quinn
// Licensed under the MIT Licence. See LICENSE for details
#pragma once
#include <GLFW/glfw3.h>
void key_callback(GLFWwindow *win, int key, int scancode, int action, int mods);

View File

@@ -1,10 +0,0 @@
// Copyright (c) 2025 Quinn
// Licensed under the MIT Licence. See LICENSE for details
#pragma once
#include <glad/gl.h>
#include <GLFW/glfw3.h>
int render_init(void);
void render_update(GLFWwindow *win);

View File

@@ -1,68 +0,0 @@
#include "mapcolour.h"
#include "../../util/vec.h"
u8vec4 map_colours[MAP_COLOUR_COUNT] = {
{0, 0, 0, 0 },
{127, 178, 56, 255},
{247, 233, 163, 255},
{199, 199, 199, 255},
{255, 0, 0, 255},
{160, 160, 255, 255},
{167, 167, 167, 255},
{0, 124, 0, 255},
{255, 255, 255, 255},
{164, 168, 184, 255},
{151, 109, 77, 255},
{112, 112, 112, 255},
{64, 64, 255, 255},
{143, 119, 72, 255},
{255, 252, 245, 255},
{216, 127, 51, 255},
{178, 76, 216, 255},
{102, 153, 216, 255},
{229, 229, 51, 255},
{127, 204, 25, 255},
{242, 127, 165, 255},
{76, 76, 76, 255},
{153, 153, 153, 255},
{76, 127, 153, 255},
{127, 63, 178, 255},
{51, 76, 178, 255},
{102, 76, 51, 255},
{102, 127, 51, 255},
{153, 51, 51, 255},
{25, 25, 25, 255},
{250, 238, 77, 255},
{92, 219, 213, 255},
{74, 128, 255, 255},
{0, 217, 58, 255},
{129, 86, 49, 255},
{112, 2, 0, 255},
{209, 177, 161, 255},
{159, 82, 36, 255},
{149, 87, 108, 255},
{112, 108, 138, 255},
{186, 133, 36, 255},
{103, 117, 53, 255},
{160, 77, 78, 255},
{57, 41, 35, 255},
{135, 107, 98, 255},
{87, 92, 92, 255},
{122, 73, 88, 255},
{76, 62, 92, 255},
{76, 50, 35, 255},
{76, 82, 42, 255},
{142, 60, 46, 255},
{37, 22, 16, 255},
{189, 48, 49, 255},
{148, 63, 97, 255},
{92, 25, 29, 255},
{22, 126, 134, 255},
{58, 142, 140, 255},
{86, 44, 62, 255},
{20, 180, 133, 255},
{100, 100, 100, 255},
{216, 175, 147, 255},
{127, 167, 150, 255},
};

View File

@@ -1,74 +0,0 @@
#pragma once
#include "../../util/vec.h"
enum map_colour_id {
MAP_COLOUR_NONE,
MAP_COLOUR_GRASS,
MAP_COLOUR_SAND,
MAP_COLOUR_WOOL,
MAP_COLOUR_FIRE,
MAP_COLOUR_ICE,
MAP_COLOUR_METAL,
MAP_COLOUR_PLANT,
MAP_COLOUR_SNOW,
MAP_COLOUR_CLAY,
MAP_COLOUR_DIRT,
MAP_COLOUR_STONE,
MAP_COLOUR_WATER,
MAP_COLOUR_WOOD,
MAP_COLOUR_QUARTZ,
MAP_COLOUR_ORANGE,
MAP_COLOUR_MAGENTA,
MAP_COLOUR_LIGHT_BLUE,
MAP_COLOUR_YELLOW,
MAP_COLOUR_LIGHT_GREEN,
MAP_COLOUR_PINK,
MAP_COLOUR_GREY,
MAP_COLOUR_LIGHT_GREY,
MAP_COLOUR_CYAN,
MAP_COLOUR_PURPLE,
MAP_COLOUR_BLUE,
MAP_COLOUR_BROWN,
MAP_COLOUR_GREEN,
MAP_COLOUR_RED,
MAP_COLOUR_BLACK,
MAP_COLOUR_GOLD,
MAP_COLOUR_DIAMOND,
MAP_COLOUR_LAPIS,
MAP_COLOUR_EMERALD,
MAP_COLOUR_PODZOL,
MAP_COLOUR_NETHER,
MAP_COLOUR_TERRACOTTA_WHITE,
MAP_COLOUR_TERRACOTTA_ORANGE,
MAP_COLOUR_TERRACOTTA_MAGENTA,
MAP_COLOUR_TERRACOTTA_LIGHT_BLUE,
MAP_COLOUR_TERRACOTTA_YELLOW,
MAP_COLOUR_TERRACOTTA_LIGHT_GREEN,
MAP_COLOUR_TERRACOTTA_PINK,
MAP_COLOUR_TERRACOTTA_GREY,
MAP_COLOUR_TERRACOTTA_LIGHT_GREY,
MAP_COLOUR_TERRACOTTA_CYAN,
MAP_COLOUR_TERRACOTTA_PURPLE,
MAP_COLOUR_TERRACOTTA_BLUE,
MAP_COLOUR_TERRACOTTA_BROWN,
MAP_COLOUR_TERRACOTTA_GREEN,
MAP_COLOUR_TERRACOTTA_RED,
MAP_COLOUR_TERRACOTTA_BLACK,
MAP_COLOUR_CRIMSON_NYLIUM,
MAP_COLOUR_CRIMSON_STEM,
MAP_COLOUR_CRIMSON_HYPHAE,
MAP_COLOUR_WARPED_NYLIUM,
MAP_COLOUR_WARPED_STEM,
MAP_COLOUR_WARPED_HYPHAE,
MAP_COLOUR_WARPED_WART_BLOCK,
MAP_COLOUR_DEEPSLATE,
MAP_COLOUR_RAW_IRON,
MAP_COLOUR_GLOW_LICHEN,
// contains count, leave last
MAP_COLOUR_COUNT,
};
/* contains the colours of the map on their' respective index - 1. (NONE should be handled separately) */
extern u8vec4 map_colours[MAP_COLOUR_COUNT];

View File

@@ -1,59 +0,0 @@
// Copyright (c) 2025 Quinn
// Licensed under the MIT Licence. See LICENSE for details
#include "shader.h"
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include "../error.h"
#define NAM_S(name) _binary_res_##name##_start // name of a start variable
#define NAM_E(name) _binary_res_##name##_end // name of an end variable
// macro for generating the variable declarations
#define DEF_GLSL(name) \
extern char const NAM_S(name)[]; \
extern char const NAM_E(name)[]
// NOTE: we are currently just sucking up the memory costs for ease. We can either include the source files themselves. Or use compression, where I'd prefer the latter for ease of installation.
// NOLINTBEGIN (bugprone-reserved-identifier)
DEF_GLSL(sh_vert_glsl);
DEF_GLSL(sh_frag_glsl);
DEF_GLSL(sh_geom_glsl);
// NOLINTEND
/* compile a shader */
static GLuint shader_compile(GLenum type, const char *src, size_t len) {
int ilen = len;
GLuint shader = glCreateShader(type);
glShaderSource(shader, 1, &src, &ilen);
glCompileShader(shader);
// repurposing ilen for the max length of the shader log
glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &ilen);
if (ilen > 0) {
char log[ilen];
glGetShaderInfoLog(shader, ilen, &ilen, log);
log[ilen - 1] = '\0'; // terminate the string one character sooner since the log includes a newline
error("error whilst compiling shader type '0X%X': '%s'", type, log);
}
return shader;
}
#define COMPILE_NAME(_type, _name) shader_compile(_type, NAM_S(_name), (uintptr_t)NAM_E(_name) - (uintptr_t)NAM_S(_name))
int shader_init(GLuint pipe) {
GLuint vs = COMPILE_NAME(GL_VERTEX_SHADER, sh_vert_glsl);
GLuint fs = COMPILE_NAME(GL_FRAGMENT_SHADER, sh_frag_glsl);
GLuint gs = COMPILE_NAME(GL_GEOMETRY_SHADER, sh_geom_glsl);
glAttachShader(pipe, vs);
glAttachShader(pipe, fs);
glAttachShader(pipe, gs);
glDeleteShader(vs);
glDeleteShader(fs);
glDeleteShader(gs);
return 1;
}

View File

@@ -1,10 +0,0 @@
// Copyright (c) 2025 Quinn
// Licensed under the MIT Licence. See LICENSE for details
#pragma once
#include <glad/gl.h>
#include <stddef.h>
#include <stdint.h>
int shader_init(GLuint pipe);

View File

@@ -1,10 +1,12 @@
// Copyright (c) 2025 Quinn
// Licensed under the MIT Licence. See LICENSE for details
/* Copyright (c) 2025 Quinn.
* This is a file from the project MCA-Selector-Lite and is
* licensed under the MIT Licence. See the project's LICENSE file for details. */
#include "input.h"
#include <GLFW/glfw3.h>
void key_callback(GLFWwindow *win, int key, int scancode, int action, int mods) {
void input_callback(GLFWwindow *win, int key, int scancode, int action, int mods)
{
(void)win, (void)key, (void)scancode, (void)action, (void)mods; // make the compiler shut up as this is fine
#ifndef NDEBUG
if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS)

10
src/io/win/input.h Normal file
View File

@@ -0,0 +1,10 @@
/* Copyright (c) 2025 Quinn.
* This is a file from the project MCA-Selector-Lite and is
* licensed under the MIT Licence. See the project's LICENSE file for details. */
#pragma once
#include <GLFW/glfw3.h>
/* Handles incoming key inputs for `win`.
* Intended to be given as an argument to `glfwSetKeyCallback`. */
void input_callback(GLFWwindow *win, int key, int scancode, int action, int mods);

View File

@@ -1,14 +1,14 @@
// Copyright (c) 2025 Quinn
// Licensed under the MIT Licence. See LICENSE for details
/* Copyright (c) 2025 Quinn.
* This is a file from the project MCA-Selector-Lite and is
* licensed under the MIT Licence. See the project's LICENSE file for details. */
#include "render.h"
#include <glad/gl.h>
#include <GLFW/glfw3.h>
#include <glad/gl.h>
#include <stdint.h>
#include "../error.h"
#include "../util/intdef.h"
#include "../../types.h"
#include "../../util/error.h"
#include "shader.h"
#define VERTC 3
@@ -16,9 +16,10 @@ static GLuint pipe;
static GLuint vbo; // vertex buffer object
static GLuint vao; // vertex array object
static GLuint screen_loc; // location to where OpenGL sends to the shaders of the screen dimensions
static int win_w, win_h;
static int win_w, win_h;
static void screen_resize(int w, int h) {
static void screen_resize(int w, int h)
{
i32 verts[VERTC][4] = {
{0, 0, w, 20 },
{0, 20, w, h - 40},
@@ -32,7 +33,8 @@ static void screen_resize(int w, int h) {
win_h = h;
}
int render_init(void) {
int render_init(void)
{
pipe = glCreateProgram();
shader_init(pipe); // initialise and include the shaders
glLinkProgram(pipe); // link the application
@@ -64,7 +66,8 @@ int render_init(void) {
return 0;
}
void render_update(GLFWwindow *win) {
void render_update(GLFWwindow *win)
{
glDisable(GL_DEPTH_TEST);
glDisable(GL_CULL_FACE);
@@ -80,3 +83,13 @@ void render_update(GLFWwindow *win) {
glBindVertexArray(vao);
glDrawArrays(GL_POINTS, 0, VERTC);
}
void render_free(void)
{
glDeleteVertexArrays(1, &vao);
glDeleteBuffers(1, &vbo);
glDeleteProgram(pipe);
vbo = 0;
vao = 0;
pipe = 0;
}

11
src/io/win/render.h Normal file
View File

@@ -0,0 +1,11 @@
/* Copyright (c) 2025 Quinn.
* This is a file from the project MCA-Selector-Lite and is
* licensed under the MIT Licence. See the project's LICENSE file for details. */
#pragma once
#include <GLFW/glfw3.h>
#include <glad/gl.h>
int render_init(void);
void render_update(GLFWwindow *win);
void render_free(void);

57
src/io/win/shader.c Normal file
View File

@@ -0,0 +1,57 @@
/* Copyright (c) 2025 Quinn.
* This is a file from the project MCA-Selector-Lite and is
* licensed under the MIT Licence. See the project's LICENSE file for details. */
#include "shader.h"
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include "../../util/error.h"
// NOTE: we are currently just sucking up the memory costs for ease. We can either include the source files themselves. Or use compression, where I'd prefer the latter for ease of installation.
extern const char sh_vert_glsl[];
extern const char sh_frag_glsl[];
extern const char sh_geom_glsl[];
extern const uint sh_vert_glsl_len;
extern const uint sh_frag_glsl_len;
extern const uint sh_geom_glsl_len;
/* Compiles a shader of `type` from `src` with `len` bytes.
* Returns the integer for the shader. */
static GLuint shader_compile(GLenum type, const char *src, usize len)
{
int ilen = len;
GLuint shader = glCreateShader(type);
glShaderSource(shader, 1, &src, &ilen);
glCompileShader(shader);
// repurposing ilen for the max length of the shader log
glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &ilen);
if (ilen > 0) {
char log[ilen];
glGetShaderInfoLog(shader, ilen, &ilen, log);
log[ilen - 1] = '\0'; // terminate the string one character sooner since the log includes a newline
error("error whilst compiling shader type '0x%X': '%s'", type, log);
}
return shader;
}
int shader_init(GLuint pipe)
{
GLuint vs = shader_compile(GL_VERTEX_SHADER, sh_vert_glsl, sh_vert_glsl_len);
GLuint fs = shader_compile(GL_FRAGMENT_SHADER, sh_frag_glsl, sh_frag_glsl_len);
GLuint gs = shader_compile(GL_GEOMETRY_SHADER, sh_geom_glsl, sh_geom_glsl_len);
glAttachShader(pipe, vs);
glAttachShader(pipe, fs);
glAttachShader(pipe, gs);
// mark shaders off for deletion
glDeleteShader(vs);
glDeleteShader(fs);
glDeleteShader(gs);
return 1;
}

11
src/io/win/shader.h Normal file
View File

@@ -0,0 +1,11 @@
/* Copyright (c) 2025 Quinn.
* This is a file from the project MCA-Selector-Lite and is
* licensed under the MIT Licence. See the project's LICENSE file for details. */
#pragma once
#include <glad/gl.h>
#include <stddef.h>
#include <stdint.h>
/* Initialises the (embedded) shaders onto `pipe` */
int shader_init(GLuint pipe);

89
src/io/win/window.c Normal file
View File

@@ -0,0 +1,89 @@
/* Copyright (c) 2025 Quinn.
* This is a file from the project MCA-Selector-Lite and is
* licensed under the MIT Licence. See the project's LICENSE file for details. */
#include "window.h"
#include <GLFW/glfw3.h>
#include <assert.h>
#include <glad/gl.h>
#include "../../types.h"
#include "../../util/error.h"
#include "input.h"
#include "render.h"
static struct GLFWwindow *win = NULL;
/* Initialises the GLFW window with some defaults,
* then proceed to activate OpenGL on it. */
int window_init(void)
{
// initialise the window
#ifndef NDEBUG
glfwWindowHint(GLFW_OPENGL_DEBUG_CONTEXT, GLFW_TRUE);
#endif
glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE);
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, 1);
glfwWindowHint(GLFW_RED_BITS, 8);
glfwWindowHint(GLFW_GREEN_BITS, 8);
glfwWindowHint(GLFW_BLUE_BITS, 8);
glfwWindowHint(GLFW_ALPHA_BITS, 0);
/* NOTE: on my system; x86_64, GTX 1650 580.82.09-2, X11, i3, this causes one direct, 2 indirect memory leaks.
* This is not my fault, and can safely be ignored. */
win = glfwCreateWindow(640, 480, "MCA-Selector lite", NULL, NULL);
if (!win)
return 1;
glfwMakeContextCurrent(win);
if (!gladLoadGL(glfwGetProcAddress))
return 1;
glfwSwapInterval(1); // wait 1 screen update for a redraw a.k.a. "vsync". (not really applicable in this case but eh)
glfwSetKeyCallback(win, input_callback);
debug(
"version info:\n"
"\tvendor: %s\n"
"\trenderer: %s\n"
"\tversion: %s\n"
"\tshading lang: %s\n",
glGetString(GL_VENDOR),
glGetString(GL_RENDERER),
glGetString(GL_VERSION),
glGetString(GL_SHADING_LANGUAGE_VERSION));
return 0;
}
void window_loop(void)
{
assert(win);
render_init();
while (!glfwWindowShouldClose(win)) {
glfwWaitEvents();
render_update(win);
glfwSwapBuffers(win);
glfwPollEvents();
}
}
void window_close(void)
{
assert(win);
glfwSetWindowShouldClose(win, 1);
}
void window_free(void)
{
if (!win) {
debug("window has already been freed.");
return;
}
glfwDestroyWindow(win);
render_free();
win = NULL;
}

20
src/io/win/window.h Normal file
View File

@@ -0,0 +1,20 @@
/* Copyright (c) 2025 Quinn.
* This is a file from the project MCA-Selector-Lite and is
* licensed under the MIT Licence. See the project's LICENSE file for details. */
#pragma once
/* Set up the window, enabling OpenGL, and
* configuring the settings that are needed.
* Returns `0` upon success, otherwise `1`. */
int window_init(void);
/* Calls the update loop for the window.
* This function does not exit until the window does. */
void window_loop(void);
/* Requests the window to close (gracefully). */
void window_close(void);
/* Cleans up all resources held by the window.
* If the window is still open, it will be terminated. */
void window_free(void);

View File

@@ -1,74 +0,0 @@
// Copyright (c) 2025 Quinn
// Licensed under the MIT Licence. See LICENSE for details
#include "window.h"
#include <glad/gl.h>
#include <GLFW/glfw3.h>
#include <assert.h>
#include "../error.h"
#include "input.h"
#include "render.h"
// macros for ease of access
#define WIN_NAME "MCA Selector Lite"
#define WIN_DEFAULT_WIDTH 640
#define WIN_DEFAULT_HEIGHT 480
static GLFWwindow *win = NULL;
int window_init(void) {
#ifndef NDEBUG
glfwWindowHint(GLFW_OPENGL_DEBUG_CONTEXT, GLFW_TRUE);
#endif
// initialize the window
glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE);
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); // sets the profile to "core", so old, deprecated functions are disabled.
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GLFW_TRUE);
glfwWindowHint(GLFW_RED_BITS, 8);
glfwWindowHint(GLFW_GREEN_BITS, 8);
glfwWindowHint(GLFW_BLUE_BITS, 8);
glfwWindowHint(GLFW_ALPHA_BITS, 0);
win = glfwCreateWindow(WIN_DEFAULT_WIDTH, WIN_DEFAULT_HEIGHT, WIN_NAME, NULL, NULL);
if (!win) return 1;
// setup OpenGL for the window
glfwMakeContextCurrent(win);
if (!gladLoadGL(glfwGetProcAddress)) return 1;
glfwSwapInterval(1); // wait 1 screen update for a redraw a.k.a. "vsync". (not really applicable in this case but eh)
// configure callbacks
glfwSetKeyCallback(win, key_callback);
// print the OpenGL version information
debug(
"version info:\n"
"\tvendor: %s\n"
"\trenderer: %s\n"
"\tversion: %s\n"
"\tshading lang: %s\n",
glGetString(GL_VENDOR),
glGetString(GL_RENDERER),
glGetString(GL_VERSION),
glGetString(GL_SHADING_LANGUAGE_VERSION));
return 0;
}
void window_loop(void) {
assert(win != NULL);
render_init();
while (!glfwWindowShouldClose(win)) {
glfwWaitEvents(); // wait till an update has been given
render_update(win);
glfwSwapBuffers(win);
}
glfwDestroyWindow(win);
}

View File

@@ -1,6 +0,0 @@
// Copyright (c) 2025 Quinn
// Licensed under the MIT Licence. See LICENSE for details
#pragma once
int window_init(void); // initializes the global window, returns non-zero upon failure
void window_loop(void); // performs the window updates

View File

@@ -1,48 +1,45 @@
// Copyright (c) 2025 Quinn
// Licensed under the MIT Licence. See LICENSE for details
#define GLAD_GL_IMPLEMENTATION
#include <glad/gl.h>
#undef GLAD_GL_IMPLEMENTATION
/* Copyright (c) 2025 Quinn.
* This is a file from the project MCA-Selector-Lite and is
* licensed under the MIT Licence. See the project's LICENSE file for details. */
#include <GLFW/glfw3.h>
#include <glad/gl.h>
#include <stdio.h>
#include <stdlib.h>
#include "error.h"
#include "io/window.h"
#include "io/win/window.h"
#include "util/error.h"
#define WIN_NAME "MCA Selector Lite"
#define WIN_DEFAULT_WIDTH 640
#define WIN_DEFAULT_HEIGHT 480
// callback for GLFW errors
static void error_callback(int err, const char *const msg) {
fprintf(stderr, "\033[91mE: glfw returned (%i); \"%s\"\033[0m\n", err, msg);
/* reroutes GLFW errors to our logging system. */
static void error_callback(int err, const char *const msg)
{
error("glfw returned (%i); \"%s\"", err, msg);
}
static inline int init(void) {
glfwSetErrorCallback(error_callback);
glfwInitHint(GLFW_JOYSTICK_HAT_BUTTONS, GLFW_FALSE); // disable joystick buttons
static void quit(void)
{
window_free();
if (!glfwInit()) return 1; // initialize GLFW
if (window_init()) return 1;
return 0;
}
static inline void quit(void) {
/* terminates GLFW; destroying any
* remaining windows, or other resources held by GLFW. */
glfwTerminate();
}
int main(int argc, char **argv) {
/* Entry-point of the application. */
int main(int argc, char **argv)
{
(void)argc, (void)argv;
printf("debug: [DBG], info: [INF], warning: [WAR], error: [ERR], fatal: [FAT]\n");
if (init()) fatal("failed to initialize!");
atexit(quit);
glfwSetErrorCallback(error_callback);
glfwInitHint(GLFW_JOYSTICK_HAT_BUTTONS, GLFW_FALSE); // disable joystick buttons; since we won't need them
if (!glfwInit() || window_init())
fatal("failed to initialise!");
window_loop();
quit();
// return success, since some architectures do not follow 0=success
/* return success, since some architectures do not follow 0=success
* This action will call `quit`. */
return EXIT_SUCCESS;
}

36
src/types.h Normal file
View File

@@ -0,0 +1,36 @@
/* Copyright (c) 2025 Quinn.
* This is a file from the project MCA-Selector-Lite and is
* licensed under the MIT Licence. See the project's LICENSE file for details. */
#pragma once
typedef signed long long int llong;
typedef unsigned short int ushort;
typedef unsigned int uint;
typedef unsigned long ulong;
typedef unsigned long long ullong;
typedef __INT8_TYPE__ i8;
typedef __INT16_TYPE__ i16;
typedef __INT32_TYPE__ i32;
typedef __INT64_TYPE__ i64;
typedef __UINT8_TYPE__ u8;
typedef __UINT16_TYPE__ u16;
typedef __UINT32_TYPE__ u32;
typedef __UINT64_TYPE__ u64;
typedef __SIZE_TYPE__ usize;
typedef __INTPTR_TYPE__ intptr;
typedef __UINTPTR_TYPE__ uintptr;
#if __SIZEOF_SIZE_T__ == __SIZEOF_LONG_LONG__
typedef llong ssize;
#elif __SIZEOF_SIZE_T__ == __SIZEOF_LONG__
typedef long ssize;
#elif __SIZEOF_SIZE_T__ == __SIZEOF_INT__
typedef int ssize;
#endif
#if __SIZEOF_FLOAT__ == 4
typedef float f32;
#endif
#if __SIZEOF_DOUBLE__ == 8
typedef double f64;
#endif

View File

@@ -1,5 +1,6 @@
// Copyright (c) 2025 Quinn
// Licensed under the MIT Licence. See LICENSE for details
/* Copyright (c) 2025 Quinn.
* This is a file from the project MCA-Selector-Lite and is
* licensed under the MIT Licence. See the project's LICENSE file for details. */
#pragma once
#if defined(__GNUC__)
@@ -56,4 +57,8 @@
#else
#define NONNULL(args)
#endif
#if __has_attribute(__assume__)
#define ASSUME(args) __attribute__((__assume__ args))
#endif
#endif

View File

@@ -1,27 +0,0 @@
// Copyright (c) 2025 Quinn
// Licensed under the MIT Licence. See LICENSE for details
#pragma once
#include <stdint.h>
#include "vec.h"
#define COLOUR32_BLACK ((u8vec4){0x00, 0x00, 0x00, 0xFF})
#define COLOUR32_RED ((u8vec4){0xFF, 0x00, 0x00, 0xFF})
#define COLOUR32_YELLOW ((u8vec4){0xFF, 0xFF, 0x00, 0xFF})
#define COLOUR32_ORANGE ((u8vec4){0xFF, 0x6D, 0x00, 0xFF})
#define COLOUR32_GREEN ((u8vec4){0x00, 0xFF, 0x00, 0xFF})
#define COLOUR32_CYAN ((u8vec4){0x00, 0xFF, 0xFF, 0xFF})
#define COLOUR32_BLUE ((u8vec4){0x00, 0x00, 0xFF, 0xFF})
#define COLOUR32_MAGENTA ((u8vec4){0xFF, 0x00, 0xFF, 0xFF})
#define COLOUR32_WHITE ((u8vec4){0xFF, 0xFF, 0xFF, 0xFF})
// american macros:
#define COLOR32_BLACK COLOUR32_BLACK
#define COLOR32_RED COLOUR32_RED
#define COLOR32_YELLOW COLOUR32_YELLOW
#define COLOR32_ORANGE COLOUR32_ORANGE
#define COLOR32_GREEN COLOUR32_GREEN
#define COLOR32_CYAN COLOUR32_CYAN
#define COLOR32_BLUE COLOUR32_BLUE
#define COLOR32_MAGENTA COLOUR32_MAGENTA
#define COLOR32_WHITE COLOUR32_WHITE

View File

@@ -1,5 +1,6 @@
// Copyright (c) 2025 Quinn
// Licensed under the MIT Licence. See LICENSE for details
/* Copyright (c) 2025 Quinn.
* This is a file from the project MCA-Selector-Lite and is
* licensed under the MIT Licence. See the project's LICENSE file for details. */
#pragma once
#if defined __unix__
@@ -33,14 +34,16 @@ static inline int faccess(const char *restrict fname, int perms);
#define PATH_SEP '/' // contains the path separator as a character. Yes it is extremely annoying that this has to exist.
#define PATH_SEP_STR "/" // contains the path separator as a string, useful for concatenation. Yes it is extremely annoying that this has to exist.
int faccess(char const *restrict fname, int perms) {
int faccess(char const *restrict fname, int perms)
{
return access(fname, perms);
}
#elif defined(_WIN32)
#define PATH_SEP '\\' // contains the path separator as a character. Yes it is extremely annoying that this has to exist.
#define PATH_SEP_STR "\\" // contains the path separator as a string, useful for concatenation. Yes it is extremely annoying that this has to exist.
int faccess(char const *restrict fname, int perms) {
int faccess(char const *restrict fname, int perms)
{
return _access(fname, perms);
}
#else

View File

@@ -1,5 +1,6 @@
// Copyright (c) 2025 Quinn
// Licensed under the MIT Licence. See LICENSE for details
/* Copyright (c) 2025 Quinn.
* This is a file from the project MCA-Selector-Lite and is
* licensed under the MIT Licence. See the project's LICENSE file for details. */
#pragma once
#if defined(__unix__)

View File

@@ -1,180 +0,0 @@
// Copyright (c) 2025 Quinn
// Licensed under the MIT Licence. See LICENSE for details
#include "conf.h"
#include <assert.h>
#include <errno.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "../error.h"
#include "atrb.h"
#include "intdef.h"
int conf_procbuf(const char *restrict buf, char *restrict kout, char *restrict vout, size_t len) {
bool feq = false; // whether we've found the equal sign
// data traversal
char *pos = kout; // will point to the next point in the buffer, where we'll write data
// acquire data
for (size_t i = 0; i < len; i++) {
// handling of termination tokens
bool brk = false; // whether we broke out of the loop, and are done reading
switch (buf[i]) {
case '\n':
case '\r':
case '\0':
case '#':
brk = true;
break;
}
if (brk) break;
// everything after `=` is interpreted as a value
if (!feq && buf[i] == '=') {
feq = true;
*pos = '\0'; // terminate string
pos = vout; // move pointer to start of value data
continue;
}
*pos = buf[i]; // copy over the buffer's data
pos++; // increment the position pointer
}
// null-terminate what we've got now (yes, there should be enough space for this since \0 isn't stored)
// this also ensures the value is valid, even if none is given
*pos = '\0';
// no data if we didn't move from the key position
// syntax error if we couldn't find the equal sign
return (pos == kout) ? CONF_ENODAT : (!feq ? CONF_ESYNTAX : 0);
}
struct conf_entry const *conf_matchopt(struct conf_entry const *opts, size_t optc, const char *restrict key) {
// find a match for the current key
size_t i = 0;
for (; i < optc; i++) {
if (strcmp(opts[i].key, key) == 0)
return opts + i;
}
return NULL;
}
int conf_procval(struct conf_entry const *opt, const char *restrict val) {
// parse the data
errno = 0;
char *end;
u8 dat[sizeof(u64)];
switch (opt->type) {
// signed integer data parsing
case CONF_I8:
case CONF_I16:
case CONF_I32:
case CONF_I64:
*(i64 *)dat = strtoll(val, &end, 10); // for signed integer types
break;
// unsigned integer data parsing
case CONF_U8:
case CONF_U16:
case CONF_U32:
case CONF_U64:
*(u64 *)dat = strtoull(val, &end, 10); // for unsigned integer types
break;
// floating-point data parsing
case CONF_F32: *(f32 *)dat = strtof(val, &end); break;
case CONF_F64: *(f64 *)dat = strtod(val, &end); break;
// string data parsing
case CONF_STR:
if (*(char **)opt->out) {
free(*(char **)opt->out); // if the same key is given multiple times, free the memory so we don't leak.
warn("encountered a dynamic string multiple times, this is sub-optimal.");
}
*(char **)opt->out = strdup(val);
return 0;
case CONF_FSTR: {
struct conf_fstr *s = opt->out;
strncpy(s->out, val, s->len);
s->out[s->len - 1] = '\0'; // ensure the string is null-terminated
return 0;
}
default: return CONF_EINVALIDTYPE; // return error code indicating function not implemented
}
if (errno || end == val || *end != '\0') {
error("failed to parse '%s' as a numeric value", val);
return CONF_EPARSE;
}
switch (opt->type) {
case CONF_U8: *(u8 *)opt->out = *(u64 *)dat >= UINT8_MAX ? UINT8_MAX : *(u64 *)dat; return 0;
case CONF_U16: *(u16 *)opt->out = *(u64 *)dat >= UINT16_MAX ? UINT16_MAX : *(u64 *)dat; return 0;
case CONF_U32: *(u32 *)opt->out = *(u64 *)dat >= UINT32_MAX ? UINT32_MAX : *(u64 *)dat; return 0;
case CONF_U64: *(u64 *)opt->out = *(u64 *)dat >= UINT64_MAX ? UINT64_MAX : *(u64 *)dat; return 0;
case CONF_I8: *(i8 *)opt->out = *(i64 *)dat >= INT8_MAX ? INT8_MAX : (*(i64 *)dat <= INT8_MIN ? INT8_MIN : *(i64 *)dat); return 0;
case CONF_I16: *(i16 *)opt->out = *(i64 *)dat >= INT16_MAX ? INT16_MAX : (*(i64 *)dat <= INT16_MIN ? INT16_MIN : *(i64 *)dat); return 0;
case CONF_I32: *(i32 *)opt->out = *(i64 *)dat >= INT32_MAX ? INT32_MAX : (*(i64 *)dat <= INT32_MIN ? INT32_MIN : *(i64 *)dat); return 0;
case CONF_I64: *(i64 *)opt->out = *(i64 *)dat >= INT64_MAX ? INT64_MAX : (*(i64 *)dat <= INT64_MIN ? INT64_MIN : *(i64 *)dat); return 0;
case CONF_F32: *(f32 *)opt->out = *(f32 *)dat; return 0;
case CONF_F64: *(f64 *)opt->out = *(f64 *)dat; return 0;
default: fatal("invalid switch state, all cases should be handled already"); // abort; this shouldn't be possible, so I blame the programmer
}
}
/* utility function for conf_getpat to concatenate 3 strings, where we already know the size */
NONNULL((1, 3))
static char *conf_getpat_concat(const char *restrict s1, const char *restrict s2, const char *restrict s3, size_t s1len, size_t s2len, size_t s3len) {
assert(s2 || (!s2 && !s2len)); // ensuring the programmer passes both s2 and s2len as 0, if they intend to
char *buf, *ptr;
// allocate enough data for all three to the buffer
ptr = malloc(s1len + s2len + s3len + 1);
if (!ptr) return NULL;
buf = ptr; // store the head pointer into buf
// copy data to the buffer
ptr = mempcpy(ptr, s1, s1len); // copy s1 data to the buffer
if (s2len) ptr = mempcpy(ptr, s2, s2len); // copy s2 data to the buffer (excluding null-terminator)
(void)strcpy(ptr, s3); // copy s3 as a string, thus including null-terminator
// return the buffer
return buf;
}
/* appends str to the config directory string we acquire from environment variables. */
char *conf_getpat(const char *restrict str) {
char *buf = NULL;
size_t len;
size_t str_len = strlen(str);
#if defined(__linux__)
buf = getenv("XDG_CONFIG_HOME");
if (!buf) {
buf = getenv("HOME");
if (!buf) return NULL;
len = strlen(buf);
return conf_getpat_concat(buf, "/.config", str, len, 8, str_len);
}
return conf_getpat_concat(buf, NULL, str, strlen(buf), 0, str_len);
#elif defined(__APPLE__)
buf = getenv("HOME");
if (!buf) return NULL;
len = strlen(buf);
return conf_getpat_concat(buf, "/Library/Application Support", str, len, 28, str_len);
#elif defined(_WIN32) || defined(_WIN64) || defined(__CYGWIN__)
buf = getenv("APPDATA");
if (!buf) {
buf = getenv("USERPROFILE");
if (!buf) return NULL;
len = strlen(buf);
return conf_getpat_concat(buf, "\\AppData\\Roaming", str, len, 16, str_len);
}
return conf_getpat_concat(buf, NULL, str, strlen(buf), 0, str_len);
#else
#error platform unsupported!
#endif
}

View File

@@ -1,70 +0,0 @@
// Copyright (c) 2025 Quinn
// Licensed under the MIT Licence. See LICENSE for details
#pragma once
#include <stddef.h>
#include <stdlib.h>
#include "atrb.h"
#include "intdef.h"
/* error codes */
enum conf_err {
CONF_ENODAT = -1, // no data was found
CONF_ESYNTAX = 1, // couldn't extract clear key/val data
CONF_ENOMATCH = 2, // couldn't find a match for the inputted key
CONF_EINVALIDTYPE = 3, // the type inputted in conf_entry was invalid
CONF_EPARSE = 4, // something went wrong whilst parsing
};
/* defines the primitive types available in the config file */
enum conf_primitive {
CONF_STR = 0, // expects: `char**`, will output malloc'd data !!must be freed!!
CONF_I8 = 1, // expects: `int8_t*`, will point to a location in memory where an i8 is stored.
CONF_I16 = 2, // expects: `int16_t*`, will point to a location in memory where an i16 is stored.
CONF_I32 = 4, // expects: `int32_t*`, will point to a location in memory where an i32 is stored.
CONF_I64 = 8, // expects: `int64_t*`, will point to a location in memory where an i64 is stored.
CONF_U8 = CONF_I8 | 0x80, // expects: `uint8_t*`, will point to a location in memory where an u8 is stored.
CONF_U16 = CONF_I16 | 0x80, // expects: `uint16_t*`, will point to a location in memory where an u16 is stored.
CONF_U32 = CONF_I32 | 0x80, // expects: `uint32_t*`, will point to a location in memory where an u32 is stored.
CONF_U64 = CONF_I64 | 0x80, // expects: `uint64_t*`, will point to a location in memory where an u64 is stored.
CONF_F32 = CONF_I32 | 0x40, // expects: `float*`, will point to a location in memory where an f32 is stored.
CONF_F64 = CONF_I64 | 0x40, // expects: `double*`, will point to a location in memory where an f64 is stored.
CONF_FSTR = 0x40, // expects: `struct conf_fstr*`, which contains the data for a fixed-width string
};
/* for outputting a fixed string as this config field */
struct conf_fstr {
size_t len; // length in BYTES of the output data
char *out; // where we will output the data
};
/* defines the structure of a config file entry */
struct conf_entry {
const char *key; // the key of this entry
void *out; // the pointer to which the data is written value is read if the given option is incorrect or missing
u8 type; // the primitive type which we are querying for
};
/* processes an incoming buffer.
* `buf`, `kout` and `vout` mustn't overlap, and must be (at least) `len` bytes long!
* `kout` and `vout` will contain a null-terminated string if the function returned successfully.
* returns `0` on success, `<0` when no data was found. `>0` when data was invalid but something went wrong.
* see `CONF_E*` or `enum conf_err` */
int conf_procbuf(const char *restrict buf, char *restrict kout, char *restrict vout, size_t len);
/* matches the key with one of the options and returns the pointer. Returns NULL if none could be found. */
struct conf_entry const *conf_matchopt(struct conf_entry const *opts, size_t optc, const char *restrict key);
/* processes the value belonging to the key and outputs the result to opts.
* - `val` points to a null-terminated string which contains the key and value.
* returns `0` upon success, non-zero upon failure. For information about specific error codes, see `enum conf_err` */
int conf_procval(struct conf_entry const *opts, const char *restrict val);
/* acquires the config file path, appending str to the end (you need to handle path separators yourself)
* expecting str to be null-terminated
* - linux: reads $XDG_CONFIG_HOME, if empty $HOME/.config is used, if $HOME is empty NULL is returned.
* - windows: reads %APPDATA%, if empty %USERPROFILE%\AppData\Roaming is used, if both are empty NULL is returned.
* - osx: reads $HOME, uses $HOME/Library/Application Support, if $HOME is empty NULL is returned.
* !! A malloc'd null-terminated string is returned !! */
char *conf_getpat(const char *) MALLOC NONNULL((1));

64
src/util/error.c Normal file
View File

@@ -0,0 +1,64 @@
/* Copyright (c) 2025 Quinn.
* This is a file from the project MCA-Selector-Lite and is
* licensed under the MIT Licence. See the project's LICENSE file for details. */
#include "error.h"
#include <stdarg.h>
#include <stdio.h>
#include "../types.h"
static void error_log(FILE *restrict stream, const char *restrict pfx, uint ln, const char *restrict file, const char *restrict fmt, va_list ap)
{
fprintf(stream, "(%s:%u) [%s] '", file, ln, pfx);
vfprintf(stream, fmt, ap);
fprintf(stream, "'\n");
}
void error_debug(uint ln, const char *restrict file, const char *restrict fmt, ...)
{
#ifndef NDEBUG
#else
char *env = getenv("DEBUG");
if (env && env[0] != '1')
return;
#endif
va_list ap;
va_start(ap, fmt);
error_log(stdout, "\033[95mDBG\033[0m", ln, file, fmt, ap);
va_end(ap);
}
void error_info(uint ln, const char *restrict file, const char *restrict fmt, ...)
{
va_list ap;
va_start(ap, fmt);
error_log(stdout, "\033[94mINF\033[0m", ln, file, fmt, ap);
va_end(ap);
}
void error_warn(uint ln, const char *restrict file, const char *restrict fmt, ...)
{
va_list ap;
va_start(ap, fmt);
error_log(stdout, "\033[93mWAR\033[0m", ln, file, fmt, ap);
va_end(ap);
}
void error_error(uint ln, const char *restrict file, const char *restrict fmt, ...)
{
va_list ap;
va_start(ap, fmt);
error_log(stdout, "\033[91mERR\033[0m", ln, file, fmt, ap);
va_end(ap);
}
void error_fatal(uint ln, const char *restrict file, const char *restrict fmt, ...)
{
va_list ap;
va_start(ap, fmt);
error_log(stdout, "\033[101mFAT\033[0m", ln, file, fmt, ap);
va_end(ap);
exit(EXIT_FAILURE);
}

22
src/util/error.h Normal file
View File

@@ -0,0 +1,22 @@
/* Copyright (c) 2025 Quinn.
* This is a file from the project MCA-Selector-Lite and is
* licensed under the MIT Licence. See the project's LICENSE file for details. */
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include "../types.h"
#include "atrb.h"
void error_debug(uint ln, const char *restrict file, const char *restrict fmt, ...);
void error_info(uint ln, const char *restrict file, const char *restrict fmt, ...);
void error_warn(uint ln, const char *restrict file, const char *restrict fmt, ...);
void error_error(uint ln, const char *restrict file, const char *restrict fmt, ...);
void error_fatal(uint ln, const char *restrict file, const char *restrict fmt, ...) NORET;
#define debug(...) error_debug(__LINE__, __FILE__ __VA_OPT__(, ) __VA_ARGS__)
#define info(...) error_info(__LINE__, __FILE__ __VA_OPT__(, ) __VA_ARGS__)
#define warn(...) error_warn(__LINE__, __FILE__ __VA_OPT__(, ) __VA_ARGS__)
#define error(...) error_error(__LINE__, __FILE__ __VA_OPT__(, ) __VA_ARGS__)
#define fatal(...) error_fatal(__LINE__, __FILE__ __VA_OPT__(, ) __VA_ARGS__)

View File

@@ -1,24 +0,0 @@
// Copyright (c) 2025 Quinn
// Licensed under the MIT Licence. See LICENSE for details
#pragma once
/* variable-width integer types */
typedef unsigned int uint; // ≥16 bit unsigned integer
typedef unsigned long ulong; // ≥32 bit unsigned integer
typedef signed long long llong; // ≥64 bit signed integer
typedef unsigned long long ullong; // ≥64 bit unsigned integer
/* fixed-width integer types */
#include <stdint.h>
typedef int8_t i8;
typedef int16_t i16;
typedef int32_t i32;
typedef int64_t i64;
typedef uint8_t u8;
typedef uint16_t u16;
typedef uint32_t u32;
typedef uint64_t u64;
/* floating point types */
typedef float f32; // single-precision floating-point
typedef double f64; // double-precision floating-point

View File

@@ -1,5 +1,6 @@
// Copyright (c) 2025 Quinn
// Licensed under the MIT Licence. See LICENSE for details
/* Copyright (c) 2025 Quinn.
* This is a file from the project MCA-Selector-Lite and is
* licensed under the MIT Licence. See the project's LICENSE file for details. */
#pragma once
#define WIDTHOF(t) (sizeof(t) * 8) // gets the bit width of a type

9
src/util/util.h Normal file
View File

@@ -0,0 +1,9 @@
/* Copyright (c) 2025 Quinn.
* This is a file from the project MCA-Selector-Lite and is
* licensed under the MIT Licence. See the project's LICENSE file for details. */
#pragma once
/* Acquires the next power of two of value `x`.
* Automatically determines the type (and therefore the width) of `x`.
* Explicitly cast `x` to a desired width, if necessary. */
#define bit_ceil(x) (1 << (sizeof(__typeof__(x)) * 8 - __builtin_clzg(((x) - !!(x)) | 1)))

View File

@@ -1,6 +1,9 @@
/* Copyright (c) 2025 Quinn.
* This is a file from the project MCA-Selector-Lite and is
* licensed under the MIT Licence. See the project's LICENSE file for details. */
#pragma once
#include "intdef.h"
#include "../types.h"
#if defined(__has_attribute) && __has_attribute(vector_size)
typedef float fvec2 __attribute__((vector_size(sizeof(float) * 2))); // SMID vector for 2 `float`

View File

@@ -1,34 +0,0 @@
#include <stdint.h>
#include "../src/util/conf.h"
#include "../src/util/intdef.h"
#include "t_arith.h"
#include "t_conf.h"
#include "test.h"
testdat tests[] = {
{"ensure SAR", test_sar, NULL },
{"k=v", test_procbuf, &(struct test_procbuf){"key=val", "key", "val", 0} },
{"sometxt", test_procbuf, &(struct test_procbuf){"sometxt", "sometxt", "", CONF_ESYNTAX} },
{"comment", test_procbuf, &(struct test_procbuf){"# comment", "", "", CONF_ENODAT} },
{"empty", test_procbuf, &(struct test_procbuf){"", "", "", CONF_ENODAT} },
{"LF", test_procbuf, &(struct test_procbuf){"\n", "", "", CONF_ENODAT} },
{"CRLF", test_procbuf, &(struct test_procbuf){"\r\n", "", "", CONF_ENODAT} },
{"k=v (LF)", test_procbuf, &(struct test_procbuf){"k=v\na", "k", "v", 0} },
{"k=v (CRLF)", test_procbuf, &(struct test_procbuf){"k=v\r\na", "k", "v", 0} },
{"get", test_matchopt, &(struct test_matchopt){"key3", 2} },
{"invalid", test_matchopt, &(struct test_matchopt){"nono", -1} },
// NOTE: formatter is fucking with alignment making it use tabs @.@
{"i32", test_procval_int, &(struct test_procval_int){"42", 42, CONF_I32} },
{"i32_neg", test_procval_int, &(struct test_procval_int){"-42", (u32)-42, CONF_I32} },
{"u32_max", test_procval_int, &(struct test_procval_int){"4294967295", UINT32_MAX, CONF_U64} },
{"u64", test_procval_int, &(struct test_procval_int){"3141592653589793238", 3141592653589793238, CONF_U64}},
{"u8_overflow", test_procval_int, &(struct test_procval_int){"256", 255, CONF_U8} },
{"", test_procval_f32, NULL },
{"", test_procval_str, NULL },
{"", test_procval_str_predef, NULL },
{"", test_procval_fstr, NULL },
{"", test_procval_fstr_trunc, NULL },
{"", test_procval_eparse, NULL },
{"", test_getpat, NULL },
};

View File

@@ -1,19 +1,55 @@
// Copyright (c) 2025 Quinn
// Licensed under the MIT Licence. See LICENSE for details
#define GLAD_GL_IMPLEMENTATION
#include <glad/gl.h>
#undef GLAD_GL_IMPLEMENTATION
/* Copyright (c) 2025 Quinn.
* This is a file from the project MCA-Selector-Lite and is
* licensed under the MIT Licence. See the project's LICENSE file for details. */
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include "dat.h" // contains the test data
#include "test.h"
#include "test_conf.h"
int main(void) {
// get test count
size_t n = sizeof(tests) / sizeof(tests[0]);
int main(void)
{
assert_true(-3 >> 5 == -1); // checking for arithmetic shift, rather than logical shift
assert_true(sizeof(u8) == 1);
assert_true(sizeof(u16) == 2);
assert_true(sizeof(u32) == 4);
assert_true(sizeof(u64) == 8);
assert_true(sizeof(f32) == 4);
assert_true(sizeof(f64) == 8);
assert_true(sizeof(usize) == sizeof(size_t));
test_conf_procbuf("key=val", "key", "val", 0);
test_conf_procbuf("sometxt", "sometxt", "", CONF_ESYNTAX);
test_conf_procbuf("# comment", "", "", CONF_ENODAT);
test_conf_procbuf("", "", "", CONF_ENODAT);
test_conf_procbuf("\n", "", "", CONF_ENODAT);
test_conf_procbuf("\r\n", "", "", CONF_ENODAT);
test_conf_procbuf("k=v\na", "k", "v", 0);
test_conf_procbuf("k=v\r\na", "k", "v", 0);
test_conf_getpat();
return exec_tests(tests, n);
struct conf_entry opts[] = {
{"key0", NULL, 0},
{"key1", NULL, 0},
{"key2", NULL, 0}
};
test_conf_matchopt(opts, 3, "key0", 0);
test_conf_matchopt(opts, 3, "key1", 1);
test_conf_matchopt(opts, 3, "key2", 2);
test_conf_matchopt(opts, 3, "key3", -1);
test_conf_procval_int("42", 42, CONF_I32);
test_conf_procval_int("-42", (u32)-42, CONF_I32);
test_conf_procval_int("4294967295", UINT32_MAX, CONF_U32);
test_conf_procval_int("9223372036854775807", INT64_MAX, CONF_I64);
test_conf_procval_int("256", 255, CONF_U8);
// TODO: write more tests for float parsing
test_conf_procval_f32("0.0", 0.0f);
test_conf_procval_f32("3.14159", 3.14159f);
test_conf_procval_f32("3.1428571428", 22.0f / 7.0f);
test_conf_procval_f32("-5.0", -5.0f);
/* return 1 if there were failed tests. */
printf("test results: (%u/%u)\n", test_okay, test_okay + test_fail);
return !!test_fail;
}

View File

@@ -1,9 +0,0 @@
#pragma once
#include "test.h"
/* tests arithmetic shift for signed integers (rather than logical shift) */
int test_sar(void *dat) {
(void)dat;
return assert_true(-3 >> 5 == -1);
}

View File

@@ -1,205 +0,0 @@
#pragma once
#include <math.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include "../src/util/conf.h"
#include "../src/util/intdef.h"
#include "test.h"
// Environment saver structure for conf_getpat tests
#if defined(__linux__) || defined(__APPLE__) || defined(_WIN32) || defined(_WIN64) || defined(__CYGWIN__)
struct test_getpat_envdat {
char *xdg_config_home;
char *home;
char *appdata;
char *userprofile;
};
/* save the current environment variables */
static void env_save(struct test_getpat_envdat *s) {
const char *tmp;
tmp = getenv("XDG_CONFIG_HOME");
s->xdg_config_home = tmp ? strdup(tmp) : NULL;
tmp = getenv("HOME");
s->home = tmp ? strdup(tmp) : NULL;
tmp = getenv("APPDATA");
s->appdata = tmp ? strdup(tmp) : NULL;
tmp = getenv("USERPROFILE");
s->userprofile = tmp ? strdup(tmp) : NULL;
}
/* restores the environment variables to what they were before */
static void env_restore(struct test_getpat_envdat *s) {
#ifdef _WIN32
if (s->xdg_config_home) _putenv_s("XDG_CONFIG_HOME", s->xdg_config_home);
else _putenv("XDG_CONFIG_HOME=");
if (s->home) _putenv_s("HOME", s->home);
else _putenv("HOME=");
if (s->appdata) _putenv_s("APPDATA", s->appdata);
else _putenv("APPDATA=");
if (s->userprofile) _putenv_s("USERPROFILE", s->userprofile);
else _putenv("USERPROFILE=");
#else
if (s->xdg_config_home) setenv("XDG_CONFIG_HOME", s->xdg_config_home, 1);
else unsetenv("XDG_CONFIG_HOME");
if (s->home) setenv("HOME", s->home, 1);
else unsetenv("HOME");
if (s->appdata) setenv("APPDATA", s->appdata, 1);
else unsetenv("APPDATA");
if (s->userprofile) setenv("USERPROFILE", s->userprofile, 1);
else unsetenv("USERPROFILE");
#endif
free(s->xdg_config_home);
free(s->home);
free(s->appdata);
free(s->userprofile);
}
#endif
/* check procbuf's functionality */
struct test_procbuf {
const char *in; // data in
const char *xkey; // expected key
const char *xval; // expected value
int xret; // expected return type
};
int test_procbuf(void *arg) {
struct test_procbuf *dat = arg;
size_t len = strlen(dat->in) + 1;
char k[len], v[len];
*k = '\0', *v = '\0';
return assert_true(conf_procbuf(dat->in, k, v, len) == dat->xret) ||
assert_true(!strcmp(k, dat->xkey)) ||
assert_true(!strcmp(v, dat->xval));
}
/* check matchopt functionality */
struct test_matchopt {
const char *key; // key to search for (key1, key2, key3)
int xidx; // expect index (<0 is NULL, may not be more than 2)
};
int test_matchopt(void *arg) {
struct test_matchopt *dat = arg;
struct conf_entry opts[] = {
{"key1", NULL, 0},
{"key2", NULL, 0},
{"key3", NULL, 0},
};
struct conf_entry *xopt = dat->xidx < 0 ? NULL : opts + dat->xidx;
return assert_true(conf_matchopt(opts, 3, dat->key) == xopt);
}
struct test_procval_int {
const char *val;
u64 xres;
u8 type;
};
int test_procval_int(void *arg) {
struct test_procval_int *dat = arg;
u64 out = 0;
return assert_true(!conf_procval(&(struct conf_entry){NULL, &out, dat->type}, dat->val)) ||
assert_true(out == dat->xres);
}
int test_procval_f32(void *arg) {
(void)arg;
f32 out;
return assert_true(!conf_procval(&(struct conf_entry){NULL, &out, CONF_F32}, "3.14159265")) ||
assert_true(fabsf(out - 3.14159265F) < 1e-6F);
}
int test_procval_str(void *arg) {
(void)arg;
char *out = NULL;
int ret = assert_true(!conf_procval(&(struct conf_entry){NULL, (void *)&out, CONF_STR}, "here comes the sowon")) ||
assert_false(strcmp("here comes the sowon", out));
free(out);
return ret;
}
int test_procval_str_predef(void *arg) {
(void)arg;
char *out = strdup("owo");
int ret = assert_true(!conf_procval(&(struct conf_entry){NULL, (void *)&out, CONF_STR}, "i leak if I don't free")) ||
assert_true(!strcmp("i leak if I don't free", out));
free(out);
return ret;
}
int test_procval_fstr(void *arg) {
(void)arg;
char buf[16];
struct conf_fstr str = {sizeof(buf), buf};
return assert_true(!conf_procval(&(struct conf_entry){NULL, &str, CONF_FSTR}, "hewwoo wowld")) ||
assert_true(!strcmp(str.out, "hewwoo wowld"));
}
int test_procval_fstr_trunc(void *arg) {
(void)arg;
char buf[8];
struct conf_fstr str = {sizeof(buf), buf};
return assert_true(!conf_procval(&(struct conf_entry){NULL, &str, CONF_FSTR}, "hewwooo wowld")) ||
assert_true(!strcmp(str.out, "hewwooo"));
}
int test_procval_eparse(void *arg) {
(void)arg;
i32 out;
return assert_true(conf_procval(&(struct conf_entry){NULL, &out, CONF_I32}, "owo") == CONF_EPARSE);
}
/* ensure paths are being set correctly */
int test_getpat(void *arg) {
(void)arg;
struct test_getpat_envdat envs;
env_save(&envs);
int ret = 0;
char *path = NULL;
#ifdef __linux__
setenv("XDG_CONFIG_HOME", "/test/config", 1);
path = conf_getpat("/app");
ret |= assert_true(path && !strcmp(path, "/test/config/app"));
free(path);
unsetenv("XDG_CONFIG_HOME");
setenv("HOME", "/test/home", 1);
path = conf_getpat("/app");
ret |= assert_true(path && !strcmp(path, "/test/home/.config/app"));
free(path);
unsetenv("HOME");
#elif defined(__APPLE__)
setenv("HOME", "/test/home", 1);
path = conf_getpat("/app");
ret |= assert_true(path && !strcmp(path, "/test/home/Library/Application Support/app"));
free(path);
unsetenv("HOME");
#elif defined(_WIN32) || defined(_WIN64) || defined(__CYGWIN__)
_putenv("APPDATA=C:\\test\\appdata");
path = conf_getpat("\\app");
ret |= assert_true(path && !strcmp(path, "C:\\test\\appdata\\app"));
free(path);
_putenv("APPDATA=");
_putenv("USERPROFILE=C:\\test\\user");
path = conf_getpat("\\app");
ret |= assert_true(path && !strcmp(path, "C:\\test\\user\\AppData\\Roaming\\app"));
free(path);
_putenv("USERPROFILE=");
#endif
void *ptr;
ret |= assert_true(!(ptr = conf_getpat("anything")));
free(ptr);
env_restore(&envs);
return ret;
}

21
test/test.c Normal file
View File

@@ -0,0 +1,21 @@
/* Copyright (c) 2025 Quinn.
* This is a file from the project MCA-Selector-Lite and is
* licensed under the MIT Licence. See the project's LICENSE file for details. */
#include "test.h"
#include "../src/types.h"
uint test_okay = 0;
uint test_fail = 0;
int test_process(int res, const char *restrict file, uint ln, const char *restrict function, const char *restrict expression)
{
const char *status = res ?
"[\033[32;1m OK \033[0m]" :
"[\033[31;1m FAIL \033[0m]";
test_okay += !!res;
test_fail += !res;
printf("%s\t%s:%u-%s\t-> %s\n", status, file, ln, function, expression);
return res;
}

View File

@@ -1,48 +1,17 @@
// Copyright (c) 2025 Quinn
// Licensed under the MIT Licence. See LICENSE for details
/* Copyright (c) 2025 Quinn.
* This is a file from the project MCA-Selector-Lite and is
* licensed under the MIT Licence. See the project's LICENSE file for details. */
#pragma once
#include <stdio.h>
const char *test_ctest;
size_t test_runs = 0;
#include "../src/types.h"
// evaluates the test
// returns 1 upon error
static inline int assert_helper(int cond, const char *restrict fname, unsigned ln, const char *restrict fnname, const char *restrict expr) {
test_runs++;
if (cond)
printf("[\033[32;1m OK \033[0m] %s %s -> %s:%u (%s)\n", test_ctest, fnname, fname, ln, expr);
else
printf("[\033[31;1m FAIL \033[0m] %s %s -> %s:%u (%s)\n", test_ctest, fnname, fname, ln, expr);
return !cond;
}
extern uint test_okay;
extern uint test_fail;
#define assert_true(expr) assert_helper(!!(expr), __FILE__, __LINE__, __func__, #expr) // evaluation expected to be true
#define assert_false(expr) assert_helper(!(expr), __FILE__, __LINE__, __func__, #expr) // evaluation expected to be false
/* Prints the result `res` of a test.
* Returns `res` back, for ease of chaining. */
int test_process(int res, const char *restrict file, uint ln, const char *restrict function, const char *restrict expression);
// contains the data for executing a single test
struct testdat {
const char *name; // test name
int (*test)(void *); // test, returns 0 upon success, non-zero upon failure
void *args; // arguments to the test
};
typedef struct testdat testdat;
// executes the tests, returns the amount of failed tests; >0: failure
static inline size_t exec_tests(testdat *dat, size_t ntests) {
size_t i;
size_t err = 0;
// perform tests and count the error state
for (i = 0; i < ntests; i++) {
test_ctest = dat[i].name;
err += !!(dat[i].test(dat[i].args));
}
// give final score
if (!err)
fprintf(stdout, "tests completed! (%zu/%zu)\n", test_runs - err, test_runs);
else
fprintf(stderr, " tests failed! (%zu/%zu)\n", test_runs - err, test_runs);
return err;
}
#define assert_true(expr) test_process((expr), __FILE__, __LINE__, __func__, #expr)
#define assert_false(expr) assert_true(!(expr))

142
test/test_conf.c Normal file
View File

@@ -0,0 +1,142 @@
/* Copyright (c) 2025 Quinn.
* This is a file from the project MCA-Selector-Lite and is
* licensed under the MIT Licence. See the project's LICENSE file for details. */
#include "test_conf.h"
#include <math.h>
#include <string.h>
#include "../src/types.h"
#include "../src/util/conf.h"
#include "test.h"
void test_conf_procbuf(const char *restrict buf, const char *restrict expect_key, const char *restrict expect_val, int expect_return)
{
usize len = strlen(buf) + 1;
char k[len], v[len];
*k = '\0', *v = '\0';
(void)(assert_true(conf_procbuf(buf, k, v, len) == expect_return) &&
assert_true(!strcmp(k, expect_key)) &&
assert_true(!strcmp(v, expect_val)));
}
void test_conf_matchopt(struct conf_entry *opts, usize optc, const char *restrict key, int expect_index)
{
usize idx = opts - conf_matchopt(opts, optc, key);
idx = (ssize)idx < 0 ? -idx : idx;
int i = idx < optc ? (int)idx : -1;
assert_true(i == expect_index);
}
void test_conf_procval_int(const char *val, u64 expect_value, int type)
{
u8 out[sizeof(u64)] = {0};
assert_true(!conf_procval(&(struct conf_entry){NULL, out, type}, val));
assert_true(memcmp(out, &expect_value, sizeof(u64)) == 0);
}
void test_conf_procval_f32(const char *val, f32 expect_value)
{
u8 out[4];
f32 result;
conf_procval(&(struct conf_entry){NULL, out, CONF_F32}, val);
memcpy(&result, out, 4);
assert_true(fabsf(expect_value - result) < 1e-9f);
}
void test_procval_str(void)
{
char *out = NULL;
(void)(assert_true(!conf_procval(&(struct conf_entry){NULL, (void *)&out, CONF_STR}, "here comes the sowon")) &&
assert_false(strcmp("here comes the sowon", out)));
free(out);
}
void test_procval_str_predef(void)
{
char *out = strdup("owo");
(void)(assert_true(!conf_procval(&(struct conf_entry){NULL, (void *)&out, CONF_STR}, "i leak if I don't free")) &&
assert_true(!strcmp("i leak if I don't free", out)));
free(out);
}
void test_procval_fstr(void)
{
char buf[16];
struct conf_fstr str = {sizeof(buf), buf};
(void)(assert_true(!conf_procval(&(struct conf_entry){NULL, &str, CONF_FSTR}, "hewwoo wowld")) &&
assert_true(!strcmp(str.out, "hewwoo wowld")));
}
void test_procval_fstr_trunc(void)
{
char buf[8];
struct conf_fstr str = {sizeof(buf), buf};
(void)(assert_true(!conf_procval(&(struct conf_entry){NULL, &str, CONF_FSTR}, "hewwooo wowld")) &&
assert_true(!strcmp(str.out, "hewwooo")));
}
void test_procval_eparse(void)
{
i32 out;
assert_true(conf_procval(&(struct conf_entry){NULL, &out, CONF_I32}, "owo") == CONF_EPARSE);
}
void test_conf_getpat(void)
{
char *path;
#if defined(__linux__)
/* test without setting environment variables. */
unsetenv("XDG_CONFIG_HOME");
unsetenv("HOME");
path = conf_getpat("/mypath");
assert_true(path == NULL);
free(path);
/* test with setting HOME. */
setenv("HOME", "/home/test", 1);
path = conf_getpat("/mypath");
assert_true(path != NULL && strcmp(path, "/home/test/.config/mypath") == 0);
free(path);
conf_getpat("/mypath");
/* test with setting XDG_CONFIG_HOME. */
setenv("XDG_CONFIG_HOME", "/etc/xdg", 1);
path = conf_getpat("/mypath");
assert_true(path != NULL && strcmp(path, "/etc/xdg/mypath") == 0);
free(path);
#elif defined(__APPLE__)
/* test without setting environment variables. */
unsetenv("HOME");
path = conf_getpat("/mypath");
assert_true(path == NULL);
free(path);
/* test with setting HOME. */
setenv("HOME", "/home/test", 1);
path = conf_getpat("/mypath");
assert_true(path != NULL && strcmp(path, "/home/test/Library/Application Support/mypath") == 0);
free(path);
#elif defined(_WIN32) || defined(_WIN64) || defined(__CYGWIN__)
/* test without setting environment variables. */
_putenv("APPDATA=");
_putenv("USERPROFILE=");
path = conf_getpat("\\mypath");
assert_true(path == NULL);
free(path);
/* test with setting USERPROFILE */
_putenv("USERPROFILE=C:\\Users\\test");
path = conf_getpat("\\mypath");
assert_true(path != NULL && strcmp(path, "C:\\Users\\test\\AppData\\Roaming\\mypath") == 0);
free(path);
/* test with setting APPDATA */
_putenv("APPDATA=C:\\Users\\test\\AppData\\Roaming");
path = conf_getpat("\\mypath");
assert_true(path != NULL && strcmp(path, "C:\\Users\\test\\AppData\\Roaming\\mypath") == 0);
free(path);
#else
assert_fail("unsupported operating system; no tests have been written.");
#endif
}

20
test/test_conf.h Normal file
View File

@@ -0,0 +1,20 @@
/* Copyright (c) 2025 Quinn.
* This is a file from the project MCA-Selector-Lite and is
* licensed under the MIT Licence. See the project's LICENSE file for details. */
#pragma once
#include "../src/types.h"
#include "../src/util/conf.h"
void test_conf_procbuf(const char *restrict buf, const char *restrict expect_key, const char *restrict expect_val, int expect_return);
void test_conf_matchopt(struct conf_entry *restrict opts, usize optc, const char *restrict key, int expect_index);
void test_conf_procval_int(const char *val, u64 expect_value, int type);
void test_conf_procval_f32(const char *val, f32 expect_value);
void test_conf_procval_fstr(const char *val, u64 expect_value, int type);
void test_conf_procval_str(const char *val, u64 expect_value, int type);
void test_procval_str(void);
void test_procval_str_predef(void);
void test_procval_fstr(void);
void test_procval_fstr_trunc(void);
void test_procval_eparse(void);
void test_conf_getpat(void);