Compare commits

..

1 Commits

Author SHA1 Message Date
e9816febfa write a function for deleting a specific chunk. 2025-08-26 12:12:26 +02:00
60 changed files with 1261 additions and 1559 deletions

View File

@@ -1,9 +1,3 @@
# This file is part of MCA-Selector-lite,
# and is licensed under GPL-2.0-only.
# Copyright (C)2025 quinnthepigeon@proton.me Quinn
# For further information, view COPYING and CONTRIBUTORS
# at: www.github.com/thepigeongenerator/mcaselector-lite
#
--- ---
# --------------------------- # ---------------------------
# general style settings # general style settings
@@ -27,9 +21,9 @@ AlignTrailingComments: true
AlignConsecutiveMacros: AcrossEmptyLines AlignConsecutiveMacros: AcrossEmptyLines
AlignEscapedNewlines: Left AlignEscapedNewlines: Left
AlignArrayOfStructures: Left AlignArrayOfStructures: Left
AlignConsecutiveAssignments: Consecutive AlignConsecutiveAssignments: None
AlignConsecutiveBitFields: AcrossEmptyLines AlignConsecutiveBitFields: AcrossEmptyLines
AlignConsecutiveDeclarations: Consecutive AlignConsecutiveDeclarations: None
AlignConsecutiveShortCaseStatements: AlignConsecutiveShortCaseStatements:
Enabled: true Enabled: true
@@ -37,9 +31,9 @@ AlignConsecutiveShortCaseStatements:
# short constructs on a single line # short constructs on a single line
# --------------------------- # ---------------------------
AllowShortBlocksOnASingleLine: Always AllowShortBlocksOnASingleLine: Always
AllowShortFunctionsOnASingleLine: None AllowShortFunctionsOnASingleLine: All
AllowShortLambdasOnASingleLine: All AllowShortLambdasOnASingleLine: All
AllowShortIfStatementsOnASingleLine: Never AllowShortIfStatementsOnASingleLine: AllIfsAndElse
AllowShortCaseLabelsOnASingleLine: true AllowShortCaseLabelsOnASingleLine: true
AllowShortEnumsOnASingleLine: true AllowShortEnumsOnASingleLine: true
AllowShortLoopsOnASingleLine: true AllowShortLoopsOnASingleLine: true
@@ -64,7 +58,7 @@ BraceWrapping:
AfterCaseLabel: false AfterCaseLabel: false
AfterClass: false AfterClass: false
AfterEnum: false AfterEnum: false
AfterFunction: true AfterFunction: false
AfterNamespace: false AfterNamespace: false
AfterObjCDeclaration: false AfterObjCDeclaration: false
AfterStruct: false AfterStruct: false
@@ -102,6 +96,10 @@ IncludeIsMainSourceRegex: ''
IncludeIsMainRegex: '([-_](test|unittest))?$' IncludeIsMainRegex: '([-_](test|unittest))?$'
IncludeBlocks: Regroup IncludeBlocks: Regroup
IncludeCategories: IncludeCategories:
- Regex: '^<glad/gl\.h>'
Priority: 1
SortPriority: 0
CaseSensitive: true
- Regex: '^<.*\.h>' - Regex: '^<.*\.h>'
Priority: 2 Priority: 2
SortPriority: 0 SortPriority: 0

View File

@@ -1,8 +1,5 @@
# This file is part of MCA-Selector-lite, # Copyright (c) 2025 Quinn
# and is licensed under GPL-2.0-only. # Licensed under the MIT Licence. See LICENSE for details
# Copyright (C)2025 quinnthepigeon@proton.me Quinn
# For further information, view COPYING and CONTRIBUTORS
# at: www.github.com/thepigeongenerator/mcaselector-lite
[*] [*]
charset = utf-8 charset = utf-8

View File

@@ -1,62 +0,0 @@
# This file is part of MCA-Selector-lite,
# and is licensed under GPL-2.0-only.
# Copyright (C)2025 quinnthepigeon@proton.me Quinn
# For further information, view COPYING and CONTRIBUTORS
# at: www.github.com/thepigeongenerator/mcaselector-lite
name: CI
on:
push:
branches: [main]
jobs:
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:
- uses: actions/checkout@v5
with:
submodules: true
fetch-depth: 1
- name: "Linux: install deps"
if: runner.os == 'Linux'
run: |
sudo apt update
sudo apt install -y libwayland-dev libxkbcommon-dev xorg-dev cmake xxd
- 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

9
.gitignore vendored
View File

@@ -1,8 +1,5 @@
# This file is part of MCA-Selector-lite, # Copyright (c) 2025 Quinn
# and is licensed under GPL-2.0-only. # Licensed under the MIT Licence. See LICENSE for details
# Copyright (C)2025 quinnthepigeon@proton.me Quinn
# For further information, view COPYING and CONTRIBUTORS
# at: www.github.com/thepigeongenerator/mcaselector-lite
# ignore all dotfiles by default # ignore all dotfiles by default
.* .*
@@ -21,6 +18,4 @@
*.lock *.lock
/bin/ /bin/
/obj/ /obj/
/lib/obj/
compile_commands.json compile_commands.json
compile_commands.events.json

7
.gitmodules vendored
View File

@@ -1,7 +0,0 @@
[submodule "lib/glfw"]
path = lib/glfw
url = https://github.com/glfw/glfw
[submodule "lib/libarchive"]
path = lib/libarchive
url = https://github.com/libarchive/libarchive

View File

@@ -1 +0,0 @@
quinnthepigeon@proton.me Quinn

339
COPYING
View File

@@ -1,339 +0,0 @@
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Lesser General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) year name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker.
<signature of Ty Coon>, 1 April 1989
Ty Coon, President of Vice
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License.

21
LICENSE Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2025 Quinn
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

107
Makefile
View File

@@ -1,107 +0,0 @@
# This file is part of MCA-Selector-lite,
# and is licensed under GPL-2.0-only.
# Copyright (C)2025 quinnthepigeon@proton.me Quinn
# For further information, view COPYING and CONTRIBUTORS
# at: www.github.com/thepigeongenerator/mcaselector-lite
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

@@ -7,8 +7,6 @@ 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) [![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/) [![issues](https://img.shields.io/github/issues/thepigeongenerator/mcaselector-lite.svg)](https://github.com/thepigeongenerator/mcaselector-lite/issues/)
[![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 ## 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. 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.
This is useful for reducing world size, general world management, perhaps copying chunks from one world to another or various other administrative tasks. This is useful for reducing world size, general world management, perhaps copying chunks from one world to another or various other administrative tasks.
@@ -21,12 +19,7 @@ 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. 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 # planned features
- [ ] (very basic) world map viewing (Y level + surface) - [ ] (very basic) world map viewing
- [ ] filtering chunks based on time spent in them - [ ] filtering chunks based on time spent in them
- [ ] chunk-level removal of data. - [ ] chunk-level removal of data.
- [ ] CLI-only version - [ ] 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. | | [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. 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 [types.h](/src/types.h) there live definitions for fixed-width integer types. Within [intdef.h](/src/util/intdef.h) there live definitions for fixed-width integer types.
### style guide ### style guide
- Code must be written correctly, read [Correct C](./correct-c.md) if more information is required. - Code must be written correctly, read [Correct C](./correct-c.md) if more information is required.

Submodule lib/glfw deleted from 7b6aead9fb

Submodule lib/libarchive deleted from 9525f90ca4

107
makefile Normal file
View File

@@ -0,0 +1,107 @@
# 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)

View File

@@ -1,187 +1,45 @@
/* 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 "mcx.h"
#include <archive.h>
#include <assert.h>
#include <endian.h> #include <endian.h>
#include <stdint.h> #include <stdint.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <time.h> #include <time.h>
#include "../types.h" #include "../util/compat/endian.h"
#include "../util/error.h" #include "../util/intdef.h"
#define SECTOR 0x1000 // sector size void mcx_delchunk(u8 *restrict buf, int idx) {
#define TABLE 0x800 // table (total) element count // load the table data, and clear it
#define CHUNKS 0x400 // amount of chunks in a file u32 *table = (u32 *)buf;
size_t chunk = (table[idx] >> 8) * 0x1000; // compute the byte offset the chunk starts in
size_t blen = (table[idx] & 0xFF) * 0x1000; // compute the byte length of the chunk
table[idx] = 0;
table[idx + 0x400] = time(NULL); // assign the current time to the timestamp, for correctness NOTE: might need to zero-out instead
enum mcx_compression { // store the head and tail end of the current chunk
MCX_COMPRESSION_GZIP = 0x01, u8 *head = buf + chunk;
MCX_COMPRESSION_ZLIB = 0x02, u8 *tail = buf + chunk + blen;
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 */ // count the amount of bytes that we must move
static int mcx_loadchunk(const u8 *restrict buf, const i32 *restrict table, int idx) blen = 0;
{ for (idx++; idx < 0x400; idx++)
const u8 *chunk = buf + (be32toh(table[idx]) >> 8) * SECTOR; blen += table[idx] & 0xFF * 0x1000;
memmove(head, tail, blen);
}
i32 len; /* an `*.mcX` contains a `0x2000` byte long table, the first `0x1000` containing
memcpy(&len, chunk, 4); * `0x400` entries of chunk data.
len = be32toh(len); * This chunk data is big-endian, where bytes `0xFFFFFF00` represent the `0x1000` sector offset.
chunk += 4; * From the start, and bytes `0x000000FF` represent the length in `0x1000` sectors. */
void mcx_index(const u8 *restrict buf, struct mcx_chunk *restrict chunks) {
struct archive *archive = archive_read_new(); const u32 *ptr = (u32 *)buf;
archive_read_support_format_raw(archive); for (uint i = 0; i < 0x400; i++) {
switch (*chunk) { u32 dat = be32toh(ptr[i]);
case MCX_COMPRESSION_GZIP: /* fall-through; ZLIB is handled under the GZIP filter */ chunks[i] = (struct mcx_chunk){
case MCX_COMPRESSION_ZLIB: archive_read_support_filter_gzip(archive); break; .idx = (dat >> 8) * 0x1000,
case MCX_COMPRESSION_NONE: archive_read_support_filter_none(archive); break; .len = (dat & 0xFF) * 0x1000,
case MCX_COMPRESSION_LZ4: archive_read_support_filter_lz4(archive); break; .time = be32toh(ptr[i + 0x400]),
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,38 +1,38 @@
/* Copyright (c) 2025 Quinn. // Copyright (c) 2025 Quinn
* This is a file from the project MCA-Selector-Lite and is // Licensed under the MIT Licence. See LICENSE for details
* licensed under the MIT Licence. See the project's LICENSE file for details. */
#pragma once #pragma once
#include <stdlib.h> #include <stdlib.h>
#include "../types.h"
#include "../util/atrb.h" #include "../util/atrb.h"
#include "../util/intdef.h"
/* contains chunk metadata */ /* contains chunk metadata */
struct mcx_chunk { struct mcx_chunk {
usize idx; // byte offset for start of chunk data size_t idx; // byte offset for start of chunk data
u32 len; // byte length of chunk (+ padding) u32 len; // byte length of chunk (+ padding)
u32 time; // modification time in epoch seconds u32 time; // modification time in epoch seconds
}; };
/* Deletes a single chunk (`chunk`) out of `buf`. // TODO: should return some form of feedback about its success
* The chunk's location data shall become `0`, and timestamp data the current time. /* Deletes chunk `idx` from `buf`, moving all chunks downwards in the process. */
* All succeeding chunks shall be moved back, freeing space. void mcx_delchunk(u8 *restrict buf, int idx);
* 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`. /* indexes the chunks in an `*.mcX` file, writing `0x400` of entries to `chunks` */
* The chunk's location data shall become `0`, and timestamp data the current time. void mcx_index(const u8 *restrict buf, struct mcx_chunk *restrict chunks) NONNULL((1, 2));
* 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`. /* the MCR (Minecraft region) and MCA (Minecraft anvil) files are similar
* If the `chunks` indices are known to be sequential, i.e. have a constant difference of `1`, `mcx_delchunk_range` should be preferred. * MCA is the newer variant, where it includes:
* The chunk's location data shall become `0`, and timestamp data the current time. * - a world height of 256, rather than 128.
* All succeeding chunks shall be moved back, freeing space. * - block IDs have been extended to 4096 from 256
* Returns the amount of bytes removed */ * - block ordering is now YZX, rather than XZY.
usize mcx_delchunk_bulk(u8 *restrict buf, const u16 *restrict chunks, int chunkc) NONNULL((1, 2)); * - biomes are included in the data per XZ column.
* MCR start with an 8KiB header, split in two 4KiB tables
/* Computes the byte size of the `*.mcX` file in `buf` and returns it. */ * - the first containing the offsets of chunks in the region file itself.
usize mcx_calcsize(const u8 *restrict buf) NONNULL((1)) PURE; * - the second providing timestamps on when they were last updated.
* -
*
*
*
*
*/

View File

@@ -1,69 +1,39 @@
/* Copyright (c) 2025 Quinn. // Copyright (c) 2025 Quinn
* This is a file from the project MCA-Selector-Lite and is // Licensed under the MIT Licence. See LICENSE for details
* licensed under the MIT Licence. See the project's LICENSE file for details. */
#include "nbt.h" #include "nbt.h"
#include <assert.h> #include <assert.h>
#include <endian.h>
#include <stddef.h> #include <stddef.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include "../types.h" #include "../util/compat/endian.h"
#include "../util/intdef.h"
#define MAX_DEPTH 512 #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`. /* 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 * 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. */ * 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) static const u8 *procarr(const u8 *restrict buf, i32 nmem, uint size, struct nbt_array *restrict *restrict out) {
{ size_t len = nmem * size;
usize len = nmemb * size; *out = malloc(sizeof(struct nbt_array) + len);
*out = (struct nbt_array){ if (!*out) return buf + len;
out->nmemb = nmemb,
out->dat = malloc(len),
};
if (!out->dat)
return buf + len;
memcpy(out->dat, buf, len); memcpy((*out)->dat, buf, len);
(*out)->len = nmem;
buf += len; buf += len;
/* Only include this code for little-endian systems. Since only they require this logic. /* Only include this code for little-endian systems. Since only they require this logic.
* Producing optimised code for other platforms. */ * Producing optimised code for other platforms. */
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
if (size == 1) if (size == 1) return buf;
return buf; size_t i = 0;
i32 i = 0; while (i < len) {
while (i < nmemb) {
switch (size) { switch (size) {
case 2: ((u16 *)out->dat)[i] = be16toh(((u16 *)out->dat)[i]); break; 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 4: *(u32 *)((*out)->dat + i) = be32toh(*(u32 *)((*out)->dat + i)); break;
case 8: ((u64 *)out->dat)[i] = be16toh(((u64 *)out->dat)[i]); break; case 8: *(u64 *)((*out)->dat + i) = be64toh(*(u64 *)((*out)->dat + i)); break;
default: __builtin_unreachable(); // this should be impossible default: __builtin_unreachable(); // this should be impossible
} }
i += size; i += size;
@@ -73,11 +43,12 @@ static const u8 *procarr(const u8 *restrict buf, i32 nmemb, uint size, struct nb
} }
/* calls `procarr` for the simple types available. */ /* calls `procarr` for the simple types available. */
static const u8 *proclist(const u8 *restrict buf, struct nbt_array *restrict out) static const u8 *proclist(const u8 *restrict buf, struct nbt_array *restrict *restrict out) {
{
uint size; uint size;
switch (*(u8 *)buf) { *out = NULL;
switch (*buf) {
case NBT_I8: size = 1; break; case NBT_I8: size = 1; break;
case NBT_I16: size = 2; break; case NBT_I16: size = 2; break;
case NBT_I32: // fall through case NBT_I32: // fall through
@@ -88,42 +59,39 @@ static const u8 *proclist(const u8 *restrict buf, struct nbt_array *restrict out
} }
buf++; buf++;
i32 len; i32 len = (i32)be32toh(*(u32 *)buf);
memcpy(&len, buf, 4);
len = be32toh(len);
buf += 4; buf += 4;
return procarr(buf, len, size, out); return procarr(buf, len, size, out);
} }
const u8 *nbt_proctag(const u8 *restrict buf, u16 slen, void *restrict out) const u8 *nbt_proctag(const u8 *restrict buf, u16 slen, void *restrict out) {
{
const u8 *ptr, *tmp; const u8 *ptr, *tmp;
ptr = buf + 3 + slen; ptr = buf + 3 + slen;
i32 nmem; i32 nmem;
uint size; uint size;
switch (*buf) { switch (*buf) {
case NBT_I8: *(u8 *)out = *ptr; return ptr + 1; case NBT_I8: *(u8 *)out = *ptr; return ptr + 1;
case NBT_I16: *(u16 *)out = buftoh16(ptr); return ptr + 2; case NBT_I16: *(u16 *)out = be16toh(*(u16 *)ptr); return ptr + 2;
case NBT_I32: // fall through case NBT_I32: // fall through
case NBT_F32: *(u32 *)out = buftoh32(ptr); return ptr + 4; case NBT_F32: *(u32 *)out = be16toh(*(u32 *)ptr); return ptr + 4;
case NBT_I64: // fall through case NBT_I64: // fall through
case NBT_F64: *(u64 *)out = buftoh64(ptr); return ptr + 8; case NBT_F64: *(u64 *)out = be16toh(*(u64 *)ptr); return ptr + 8;
case NBT_STR: nmem = buftoh16(ptr), size = 1, ptr += 2; break; case NBT_STR: nmem = be16toh(*(u16 *)ptr), size = 1, ptr += 2; break;
case NBT_ARR_I8: nmem = buftoh32(ptr), size = 1, ptr += 4; break; case NBT_ARR_I8: nmem = be32toh(*(u32 *)ptr), size = 1, ptr += 4; break;
case NBT_ARR_I32: nmem = buftoh32(ptr), size = 4, ptr += 4; break; case NBT_ARR_I32: nmem = be32toh(*(u32 *)ptr), size = 4, ptr += 4; break;
case NBT_ARR_I64: nmem = buftoh32(ptr), size = 8, ptr += 4; break; case NBT_ARR_I64: nmem = be32toh(*(u32 *)ptr), size = 8, ptr += 4; break;
case NBT_LIST: case NBT_LIST:
return proclist(ptr, (struct nbt_array *)out); return proclist(ptr, (struct nbt_array **)out);
return tmp; return tmp;
default: return NULL; default: return NULL;
} }
return procarr(ptr, nmem, size, (struct nbt_array *)out); return procarr(ptr, nmem, size, (struct nbt_array **)out);
} }
@@ -131,23 +99,22 @@ const u8 *nbt_proctag(const u8 *restrict buf, u16 slen, void *restrict out)
* `ptr` is assumed to be the start of the `NBT_LIST` data, e.i. The list's ID, followed by the list's length. * `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. * 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`. */ * 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; const u8 *tag = ptr;
ptr++; ptr++;
switch (*tag) { switch (*tag) {
case NBT_END: break; case NBT_END: break;
case NBT_I8: ptr += (i32)buftoh32(ptr) * 1; break; case NBT_I8: ptr += (i32)be32toh(*(u32 *)ptr) * 1; break;
case NBT_I16: ptr += (i32)buftoh32(ptr) * 2; break; case NBT_I16: ptr += (i32)be32toh(*(u32 *)ptr) * 2; break;
case NBT_I32: // fall through case NBT_I32: // fall through
case NBT_F32: ptr += (i32)buftoh32(ptr) * 4; break; case NBT_F32: ptr += (i32)be32toh(*(u32 *)ptr) * 4; break;
case NBT_I64: // fall through case NBT_I64: // fall through
case NBT_F64: ptr += (i32)buftoh32(ptr) * 8; break; case NBT_F64: ptr += (i32)be32toh(*(u32 *)ptr) * 8; break;
default: default:
// TODO: handle out of bounds... Might not be required if we use flexible array member // TODO: handle out of bounds... Might not be required if we use flexible array member
(*dpt)++; (*dpt)++;
tags[*dpt] = *tag; tags[*dpt] = *tag;
lens[*dpt] = (i32)buftoh32(ptr); lens[*dpt] = (i32)be32toh(*(u32 *)ptr);
break; break;
} }
ptr += 4; ptr += 4;
@@ -160,9 +127,8 @@ 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. * - `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`. * Where the value is decremented until we reach `0`.
* - `tags` shall contain `MAX_DEPTH` of items representing the list's stored type. */ * - `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) static const u8 *nexttag(const u8 *restrict tag, uint *restrict const dpt, i32 *restrict const lens, u8 *restrict const tags) {
{ u8 type;
u8 type;
const u8 *ptr = tag; const u8 *ptr = tag;
if (lens[*dpt]) { if (lens[*dpt]) {
type = tags[*dpt]; type = tags[*dpt];
@@ -170,7 +136,7 @@ static const u8 *nexttag(const u8 *restrict tag, uint *restrict const dpt, i32 *
*dpt -= !lens[*dpt]; *dpt -= !lens[*dpt];
} else { } else {
type = *tag; type = *tag;
ptr += buftoh16(tag + 1) + 3; ptr += be16toh(*(u16 *)(tag + 1)) + 3;
} }
switch (type) { switch (type) {
@@ -181,10 +147,10 @@ static const u8 *nexttag(const u8 *restrict tag, uint *restrict const dpt, i32 *
case NBT_I64: // fall through case NBT_I64: // fall through
case NBT_F64: ptr += 8; break; case NBT_F64: ptr += 8; break;
case NBT_ARR_I8: ptr += 4 + (i32)buftoh32(ptr) * 1; break; case NBT_ARR_I8: ptr += 4 + (i32)be32toh(*(u32 *)ptr) * 1; break;
case NBT_ARR_I32: ptr += 4 + (i32)buftoh32(ptr) * 4; break; case NBT_ARR_I32: ptr += 4 + (i32)be32toh(*(u32 *)ptr) * 4; break;
case NBT_ARR_I64: ptr += 4 + (i32)buftoh32(ptr) * 8; break; case NBT_ARR_I64: ptr += 4 + (i32)be32toh(*(u32 *)ptr) * 8; break;
case NBT_STR: ptr += 2 + (u16)buftoh16(ptr) * 1; break; case NBT_STR: ptr += 2 + (u16)be16toh(*(u16 *)ptr) * 1; break;
case NBT_END: (*dpt)--; break; case NBT_END: (*dpt)--; break;
case NBT_COMPOUND: (*dpt)++; break; case NBT_COMPOUND: (*dpt)++; break;
@@ -203,12 +169,11 @@ static const u8 *nexttag(const u8 *restrict tag, uint *restrict const dpt, i32 *
* - compound:list:int32 * - compound:list:int32
* - string * - string
*/ */
const u8 *nbt_nexttag(const u8 *restrict buf) const u8 *nbt_nexttag(const u8 *restrict buf) {
{
const u8 *tag; const u8 *tag;
u8 tags[MAX_DEPTH] = {0}; u8 tags[MAX_DEPTH] = {0};
i32 lens[MAX_DEPTH] = {0}; i32 lens[MAX_DEPTH] = {0};
uint dpt = 0; uint dpt = 0;
tag = buf; tag = buf;
do { do {

View File

@@ -1,15 +1,14 @@
/* Copyright (c) 2025 Quinn. // Copyright (c) 2025 Quinn
* This is a file from the project MCA-Selector-Lite and is // Licensed under the MIT Licence. See LICENSE for details
* licensed under the MIT Licence. See the project's LICENSE file for details. */
#pragma once #pragma once
#include <assert.h> #include <assert.h>
#include <endian.h>
#include <stdbool.h> #include <stdbool.h>
#include <stdlib.h> #include <stdlib.h>
#include "../types.h"
#include "../util/atrb.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 (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, * NBT files are a compressed `compound` tag. GZip is the compression used in most cases,
@@ -22,24 +21,24 @@
/* specifies the NBT tag IDs. /* specifies the NBT tag IDs.
* NOTE: every type is stored as BE (big-endian) in the file. */ * NOTE: every type is stored as BE (big-endian) in the file. */
enum nbt_tagid { enum nbt_tagid {
NBT_END = 0x00, // signifies the end of a compound tag NBT_END = 0x00, // signifies the end of a compound tag
NBT_I8 = 0x01, // next byte is for an 8 bit signed integer. 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_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_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_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_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_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_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_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_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_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_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_I64 = 0x0C, // starts with a i32, denoting size, followed by the u32 data
}; };
struct nbt_array { struct nbt_array {
i32 nmemb; i32 len;
void *dat; u8 dat[];
}; };

21
src/error.h Normal file
View File

@@ -0,0 +1,21 @@
// 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)

View File

@@ -1,35 +0,0 @@
#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);

View File

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

7
src/io/input.h Normal file
View File

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

10
src/io/render.h Normal file
View File

@@ -0,0 +1,10 @@
// 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);

68
src/io/render/mapcolour.c Normal file
View File

@@ -0,0 +1,68 @@
#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},
};

74
src/io/render/mapcolour.h Normal file
View File

@@ -0,0 +1,74 @@
#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];

59
src/io/shader.c Normal file
View File

@@ -0,0 +1,59 @@
// 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;
}

10
src/io/shader.h Normal file
View File

@@ -0,0 +1,10 @@
// 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 +0,0 @@
/* 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,11 +0,0 @@
/* 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);

View File

@@ -1,57 +0,0 @@
/* 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;
}

View File

@@ -1,11 +0,0 @@
/* 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);

View File

@@ -1,89 +0,0 @@
/* 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;
}

View File

@@ -1,20 +0,0 @@
/* 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);

74
src/io/window.c Normal file
View File

@@ -0,0 +1,74 @@
// 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);
}

6
src/io/window.h Normal file
View File

@@ -0,0 +1,6 @@
// 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,47 +1,48 @@
/* This file is part of MCA-Selector-lite, // Copyright (c) 2025 Quinn
* and is licensed under GPL-2.0-only. // Licensed under the MIT Licence. See LICENSE for details
* Copyright (C)2025 quinnthepigeon@proton.me Quinn #define GLAD_GL_IMPLEMENTATION
* For further information, view COPYING and CONTRIBUTORS
* at: www.github.com/thepigeongenerator/mcaselector-lite */
#include <GLFW/glfw3.h>
#include <glad/gl.h> #include <glad/gl.h>
#undef GLAD_GL_IMPLEMENTATION
#include <GLFW/glfw3.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include "io/win/window.h" #include "error.h"
#include "util/error.h" #include "io/window.h"
/* reroutes GLFW errors to our logging system. */
static void error_callback(int err, const char *const msg) #define WIN_NAME "MCA Selector Lite"
{ #define WIN_DEFAULT_WIDTH 640
error("glfw returned (%i); \"%s\"", err, msg); #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);
} }
static void quit(void) static inline int init(void) {
{ glfwSetErrorCallback(error_callback);
window_free(); glfwInitHint(GLFW_JOYSTICK_HAT_BUTTONS, GLFW_FALSE); // disable joystick buttons
/* terminates GLFW; destroying any if (!glfwInit()) return 1; // initialize GLFW
* remaining windows, or other resources held by GLFW. */ if (window_init()) return 1;
return 0;
}
static inline void quit(void) {
glfwTerminate(); glfwTerminate();
} }
int main(int argc, char **argv) {
/* Entry-point of the application. */
int main(int argc, char **argv)
{
(void)argc, (void)argv; (void)argc, (void)argv;
printf("debug: [DBG], info: [INF], warning: [WAR], error: [ERR], fatal: [FAT]\n"); printf("debug: [DBG], info: [INF], warning: [WAR], error: [ERR], fatal: [FAT]\n");
atexit(quit); if (init()) fatal("failed to initialize!");
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(); 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; return EXIT_SUCCESS;
} }

View File

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

27
src/util/colour32.h Normal file
View File

@@ -0,0 +1,27 @@
// 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,12 +1,10 @@
/* This file is part of MCA-Selector-lite, // Copyright (c) 2025 Quinn
* and is licensed under GPL-2.0-only. // Licensed under the MIT Licence. See LICENSE for details
* Copyright (C)2025 quinnthepigeon@proton.me Quinn #pragma once
* For further information, view COPYING and CONTRIBUTORS
* at: www.github.com/thepigeongenerator/mcaselector-lite */
#ifndef PORTABLE_ENDIAN_H
#define PORTABLE_ENDIAN_H 1
#if defined(__GNUC__) #if __has_include(<endian.h>)
#include <endian.h>
#else
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
#define le16toh(x) __uint16_identity(x) #define le16toh(x) __uint16_identity(x)
#define le32toh(x) __uint32_identity(x) #define le32toh(x) __uint32_identity(x)
@@ -35,9 +33,5 @@
#define htobe64(x) __uint64_identity(x) #define htobe64(x) __uint64_identity(x)
#else #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) #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 /* byte order */ #endif
#endif
#else
#error GNU C is unavailable
#endif /* __GNUC__ */
#endif /* PORTABLE_ENDIAN_H */

View File

@@ -1,6 +1,5 @@
/* Copyright (c) 2025 Quinn. // Copyright (c) 2025 Quinn
* This is a file from the project MCA-Selector-Lite and is // Licensed under the MIT Licence. See LICENSE for details
* licensed under the MIT Licence. See the project's LICENSE file for details. */
#pragma once #pragma once
#if defined __unix__ #if defined __unix__
@@ -34,16 +33,14 @@ 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 '/' // 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. #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); return access(fname, perms);
} }
#elif defined(_WIN32) #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 '\\' // 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. #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); return _access(fname, perms);
} }
#else #else

View File

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

180
src/util/conf.c Normal file
View File

@@ -0,0 +1,180 @@
// 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
}

70
src/util/conf.h Normal file
View File

@@ -0,0 +1,70 @@
// 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));

View File

@@ -1,64 +0,0 @@
/* 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);
}

View File

@@ -1,22 +0,0 @@
/* 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__)

24
src/util/intdef.h Normal file
View File

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

View File

@@ -1,6 +1,3 @@
/* 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 #pragma once
/* Acquires the next power of two of value `x`. /* Acquires the next power of two of value `x`.

View File

@@ -1,9 +1,6 @@
/* 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 #pragma once
#include "../types.h" #include "intdef.h"
#if defined(__has_attribute) && __has_attribute(vector_size) #if defined(__has_attribute) && __has_attribute(vector_size)
typedef float fvec2 __attribute__((vector_size(sizeof(float) * 2))); // SMID vector for 2 `float` typedef float fvec2 __attribute__((vector_size(sizeof(float) * 2))); // SMID vector for 2 `float`

34
test/dat.h Normal file
View File

@@ -0,0 +1,34 @@
#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,55 +1,19 @@
/* Copyright (c) 2025 Quinn. // Copyright (c) 2025 Quinn
* This is a file from the project MCA-Selector-Lite and is // Licensed under the MIT Licence. See LICENSE for details
* licensed under the MIT Licence. See the project's LICENSE file for details. */ #define GLAD_GL_IMPLEMENTATION
#include <glad/gl.h>
#undef GLAD_GL_IMPLEMENTATION
#include <stdint.h> #include <stdint.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h>
#include "dat.h" // contains the test data
#include "test.h" #include "test.h"
#include "test_conf.h"
int main(void) int main(void) {
{ // get test count
assert_true(-3 >> 5 == -1); // checking for arithmetic shift, rather than logical shift size_t n = sizeof(tests) / sizeof(tests[0]);
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();
struct conf_entry opts[] = { return exec_tests(tests, n);
{"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;
} }

9
test/t_arith.h Normal file
View File

@@ -0,0 +1,9 @@
#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);
}

205
test/t_conf.h Normal file
View File

@@ -0,0 +1,205 @@
#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;
}

View File

@@ -1,21 +0,0 @@
/* 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,17 +1,48 @@
/* Copyright (c) 2025 Quinn. // Copyright (c) 2025 Quinn
* This is a file from the project MCA-Selector-Lite and is // Licensed under the MIT Licence. See LICENSE for details
* licensed under the MIT Licence. See the project's LICENSE file for details. */
#pragma once #pragma once
#include <stdio.h> #include <stdio.h>
#include "../src/types.h" const char *test_ctest;
size_t test_runs = 0;
extern uint test_okay; // evaluates the test
extern uint test_fail; // 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;
}
/* Prints the result `res` of a test. #define assert_true(expr) assert_helper(!!(expr), __FILE__, __LINE__, __func__, #expr) // evaluation expected to be true
* Returns `res` back, for ease of chaining. */ #define assert_false(expr) assert_helper(!(expr), __FILE__, __LINE__, __func__, #expr) // evaluation expected to be false
int test_process(int res, const char *restrict file, uint ln, const char *restrict function, const char *restrict expression);
#define assert_true(expr) test_process((expr), __FILE__, __LINE__, __func__, #expr) // contains the data for executing a single test
#define assert_false(expr) assert_true(!(expr)) 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;
}

View File

@@ -1,142 +0,0 @@
/* 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
}

View File

@@ -1,20 +0,0 @@
/* 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);