Compare commits

...

8 Commits

Author SHA1 Message Date
0e00cb2d54 remove non-functional code 2025-08-07 13:23:47 +02:00
c3fc688c35 remove nbt_proc and replace it with nbt_initproc
we're moving towards that the user will handle most of the looping and
data feed.
So the use of these functions is no longer intended, becides, they
weren't being maintained whilst the parameters changed, causing the
portion of code to lag behind.
I'd rather rewrite code than have to prune old, unused code.
2025-08-07 13:23:33 +02:00
4871a19695 update README to include (some) planned features 2025-08-07 13:02:08 +02:00
2d33255ce8 remove release CI status indicator, since it won't hold any value 2025-08-07 13:02:08 +02:00
066e1c0049 remove nbt_isprim function, because it never really served any purpose.' 2025-08-07 13:02:08 +02:00
1052dcaac9 add functions for incrementing to the next tag 2025-08-07 13:01:27 +02:00
f952268152 fix: the + unary operator does not add 1, but just specifies intent.
I was very mistaken, and I feel like an idiot.
2025-08-07 13:00:09 +02:00
4b2404e903 move utility functions upwards to keep the logical order 2025-08-07 13:00:09 +02:00
3 changed files with 104 additions and 115 deletions

View File

@@ -6,9 +6,7 @@ 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](https://github.com/thepigeongenerator/mcaselector-lite/actions/workflows/ci.yaml/badge.svg)](https://github.com/thepigeongenerator/mcaselector-lite/actions/workflows/ci.yaml) [![CI](https://github.com/thepigeongenerator/mcaselector-lite/actions/workflows/ci.yaml/badge.svg)](https://github.com/thepigeongenerator/mcaselector-lite/actions/workflows/ci.yaml)
[![release](https://github.com/thepigeongenerator/mcaselector-lite/actions/workflows/release.yaml/badge.svg)](https://github.com/thepigeongenerator/mcaselector-lite/actions/workflows/release.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.
@@ -20,3 +18,7 @@ MCA selector is a tool written in Java. Where it depends upon JRE21 and JavaFX.
The goal of this project is to create a version of the original MCA selector, but written in C, leveraging the improved performance due to native execution and more low-level control. The goal of this project is to create a version of the original MCA selector, but written in C, leveraging the improved performance due to native execution and more low-level control.
I have picked C as the language for the core portions of the application, where I prefer fine-grained control with little abstractions. I have picked C as the language for the core portions of the application, where I prefer fine-grained control with little abstractions.
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
- [ ] CLI-only version
- [ ] filtering chunks based on time spent in them

View File

@@ -7,10 +7,76 @@
#include "../util/compat/endian.h" #include "../util/compat/endian.h"
#include "../util/types.h" #include "../util/types.h"
int nbt_primsize(u8 tag) {
switch (tag) {
case NBT_I8: return 1;
case NBT_I16: return 2;
case NBT_I32: // fall through
case NBT_F32: return 4;
case NBT_I64: // fall through
case NBT_F64: return 8;
default: return -1;
}
}
const u8 *nbt_nextcompound(const u8 *restrict cdat) {
return NULL;
}
const u8 *nbt_nextlist(const u8 *restrict ldat) {
const u8 *ptr = ldat + 5;
i32 len = be32toh(*(u32 *)(ldat + 1));
switch (*ldat) {
case NBT_I8:
case NBT_I16:
case NBT_I32:
case NBT_I64:
case NBT_F32:
case NBT_F64:
return ptr + nbt_primsize(*ldat) * len;
// loop for each compound, whilst `ptr` isn't `NULL`
case NBT_COMPOUND: {
for (u32 i = 0; i < (u32)len && ptr; i++) {
ptr = nbt_nextcompound(ptr);
}
return ptr;
}
// TODO: implement list/array/string handling
case NBT_STR:
case NBT_ARR_I8:
case NBT_ARR_I32:
case NBT_ARR_I64:
case NBT_LIST:
default:
return NULL;
}
}
const u8 *nbt_nexttag(const u8 *restrict buf, u16 naml) { const u8 *nbt_nexttag(const u8 *restrict buf, u16 naml) {
size_t len = nbt_tagdatlen(buf); const u8 *ptr = buf + naml + 3;
if (!len) return NULL; // TODO: compound tags should be handled here size_t mems = 0;
return buf + naml + len + 3;
switch (*buf) {
case NBT_I8:
case NBT_I16:
case NBT_I32:
case NBT_F32:
case NBT_I64:
case NBT_F64:
return ptr + nbt_primsize(*buf);
case NBT_ARR_I64: mems += sizeof(i64) - sizeof(i32); __attribute__((fallthrough));
case NBT_ARR_I32: mems += sizeof(i32) - sizeof(i8); __attribute__((fallthrough));
case NBT_ARR_I8: return ptr + ++mems * (i32)be32toh(*(u32 *)(ptr)) + 4;
case NBT_STR: return ptr + be16toh(*(u16 *)(ptr)) + 2;
case NBT_LIST: return nbt_nextlist(ptr);
default: return NULL;
}
} }
// TODO: not actually doing anything // TODO: not actually doing anything
@@ -44,22 +110,7 @@ static const u8 *nbt_proctag(const u8 *restrict buf, u16 slen) {
return ptr + nbt_primsize(*buf); return ptr + nbt_primsize(*buf);
} }
/* finds which of `pats` is equivalent to `cmp`, assumes `cmp` is `≥len` bytes long */ struct nbt_procdat nbt_initproc(struct nbt_path const *restrict pats, uint npats) {
static const char *getpat(struct nbt_path const *restrict pats, uint npats, i16 dpt, const char *restrict cmp, u16 len) {
for (uint i = 0; i < npats; i++) {
if (strncmp(pats[i].pat[dpt], cmp, len) == 0)
return pats[i].pat[dpt];
}
return NULL;
}
// TODO: make the user do the looping
int nbt_proc(struct nbt_path const *restrict pats, uint npats, const u8 *restrict buf, size_t len) {
// ensure first and last tag(s) are valid
if (buf[0] != NBT_COMPOUND || buf[len - 1] != NBT_END)
return 1;
i16 dpt = 0;
i16 mdpt = 0; i16 mdpt = 0;
// acquire the maximum depth that we'll need to go (exclusive) // acquire the maximum depth that we'll need to go (exclusive)
@@ -70,81 +121,14 @@ int nbt_proc(struct nbt_path const *restrict pats, uint npats, const u8 *restric
assert(mdpt > 0); assert(mdpt > 0);
// storing the segments of the current path // storing the segments of the current path
const char *cpat[mdpt - 1]; const char **cpat = (const char **)calloc(sizeof(void *), mdpt - 1);
memset((void *)cpat, 0, mdpt - 1);
// looping through the different tags // return the initialised structure.
const u8 *ptr = buf + nbt_namelen(buf) + 3; return (struct nbt_procdat){
while (ptr < (buf + len) && dpt >= 0) { pats,
u16 naml = nbt_namelen(ptr); cpat,
const char *mat = getpat(pats, npats, dpt, (char *)(ptr + 3), naml); npats,
cpat[dpt] = mat; 0,
mdpt,
if (mat) { };
switch (*ptr) {
case NBT_END: dpt--; break;
case NBT_COMPOUND: dpt++; break;
default: ptr = nbt_proctag(ptr, naml); break;
}
} else {
ptr = nbt_nexttag(ptr, naml);
if (!ptr) return 1;
}
}
// TODO: finish function
return !dpt;
}
int nbt_primsize(u8 tag) {
switch (tag) {
case NBT_I8: return 1;
case NBT_I16: return 2;
case NBT_I32: // fall through
case NBT_F32: return 4;
case NBT_I64: // fall through
case NBT_F64: return 8;
default: return -1;
}
}
size_t nbt_tagdatlen(const u8 *restrict buf) {
size_t mems = 0;
switch (*buf) {
case NBT_I8:
case NBT_I16:
case NBT_I32:
case NBT_F32:
case NBT_I64:
case NBT_F64:
mems = nbt_primsize(*buf);
return -(mems >= 0) & mems;
case NBT_ARR_I64: mems += sizeof(i64) - sizeof(i32); __attribute__((fallthrough));
case NBT_ARR_I32: mems += sizeof(i32) - sizeof(i8); __attribute__((fallthrough));
case NBT_ARR_I8: return +mems * (i32)be32toh(*(u32 *)(buf)) + 4;
case NBT_STR: return be16toh(*(u16 *)buf) + 2;
case NBT_LIST:
mems = nbt_primsize(*buf);
if (mems > 0) return mems * (i32)be32toh(*(u32 *)(buf + 1)) + 5;
return 0;
default: return 0;
}
}
int nbt_isprim(u8 tag) {
switch (tag) {
case NBT_I8:
case NBT_I16:
case NBT_I32:
case NBT_F32:
case NBT_I64:
case NBT_F64:
return 1;
default:
return 0;
}
} }

View File

@@ -36,30 +36,33 @@ enum nbt_tagid {
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
}; };
/* TODO: write doc */
struct nbt_path { struct nbt_path {
const char **restrict pat; // specifies the NBT path components as separate elements const char **restrict pat; // specifies the NBT path components as separate elements
i16 len; // specifies the length of the NBT elements i16 len; // specifies the length of the NBT elements
}; };
/* returns the name length of a specific tag. `buf` is the pointer to start of the tag */ /* TODO: write doc */
atrb_pure atrb_nonnull(1) static inline u16 nbt_namelen(const u8 *restrict buf) { struct nbt_procdat {
assert(*buf != NBT_END); const struct nbt_path *pats;
return be16toh(*(u16 *)(buf + 1)); const char *restrict *cpat;
} u32 npats;
i16 dpt, mdpt;
};
/* returns the (expected) pointer of the tag following this one. /* searches for the end of a compound tag without processing data, the final pointer is returned.
* `NULL` is returned if anything went wrong. */ * `NULL` is returned upon failure, the otherwise returned pointer is not guaranteed to be valid.
atrb_pure atrb_nonnull(1) const u8 *nbt_nexttag(const u8 *restrict buf, u16 naml); * `cdat` is assumed to be the start of the **compound tag's data**. */
PURE NONNULL((1)) const u8 *nbt_nextcompound(const u8 *restrict cdat);
/* checks whether the tag is a primitive data tag. (not recommended for filtering tags, use a `switch`) /* searches for the end of a list tag without processing data, the final pointer is returned.
* returns a boolean value. */ * `NULL` is returned upon failure, the otherwise returned pointer is not guaranteed to be valid.
atrb_const int nbt_isprim(u8 tag); * `ldat` is assumed to be the start of the **list tag's data.** */
PURE NONNULL((1)) const u8 *nbt_nextlist(const u8 *restrict ldat);
/* gets the byte size of an NBT tag's data (excluding id and name), returns `0` upon error. */ /* searches for the end of a named tag without processing data, the final pointer is returned.
atrb_pure size_t nbt_tagdatlen(const u8 *buf); * `NULL` is returned upon failure, the otherwise returned pointer is not guaranteed to be valid. */
PURE NONNULL((1)) const u8 *nbt_nexttag(const u8 *restrict buf, u16 naml);
/* gets the tag size of primitive types, returns `>0` on success, `<0` on failure */ /* initialises a data structure used whilst processing the tags */
atrb_const int nbt_primsize(u8 tag); PURE NONNULL((1)) struct nbt_procdat nbt_initproc(struct nbt_path const *restrict pats, uint npats);
/* processes the uncompressed `NBT` data in `buf`, with a size of `len`. */
atrb_nonnull(1, 3) int nbt_proc(struct nbt_path const *restrict paths, uint npaths, const u8 *restrict buf, size_t len);