summaryrefslogtreecommitdiff
path: root/nbt/load.ha
diff options
context:
space:
mode:
Diffstat (limited to 'nbt/load.ha')
-rw-r--r--nbt/load.ha152
1 files changed, 152 insertions, 0 deletions
diff --git a/nbt/load.ha b/nbt/load.ha
new file mode 100644
index 0000000..9e54bbb
--- /dev/null
+++ b/nbt/load.ha
@@ -0,0 +1,152 @@
+use endian;
+use strings;
+
+export type Invalid = !void;
+
+type Tag = enum i8 {
+ END = 0,
+ BYTE = 1,
+ SHORT = 2,
+ INT = 3,
+ LONG = 4,
+ FLOAT = 5,
+ DOUBLE = 6,
+ BYTE_ARRAY = 7,
+ STRING = 8,
+ LIST = 9,
+ COMPOUND = 10,
+ INT_ARRAY = 11,
+ LONG_ARRAY = 12,
+};
+
+// XXX: these are duplicated from mcproto; not sure what to do about that.
+
+fn decode_nbytes(in: *[]u8, length: size) ([]u8 | Invalid) = {
+ if (len(in) < length) return Invalid;
+ const res = in[..length];
+ *in = in[length..];
+ return res;
+};
+
+fn decode_byte(in: *[]u8) (i8 | Invalid) =
+ decode_nbytes(in, 1)?[0]: i8;
+fn decode_short(in: *[]u8) (i16 | Invalid) =
+ endian::begetu16(decode_nbytes(in, 2)?): i16;
+fn decode_int(in: *[]u8) (i32 | Invalid) =
+ endian::begetu32(decode_nbytes(in, 4)?): i32;
+fn decode_long(in: *[]u8) (i64 | Invalid) =
+ endian::begetu64(decode_nbytes(in, 8)?): i64;
+
+fn decode_bool(in: *[]u8) (bool | Invalid) =
+ decode_byte(in)? != 0;
+
+fn decode_float(in: *[]u8) (f32 | Invalid) =
+ *(&decode_int(in)?: *f32);
+fn decode_double(in: *[]u8) (f64 | Invalid) =
+ *(&decode_long(in)?: *f64);
+
+fn decode_string(in: *[]u8) (str | Invalid) = {
+ const length = decode_short(in)?;
+ if (length < 0) return Invalid;
+ // TODO: need to deal with braindead java utf8.
+ match (strings::fromutf8(decode_nbytes(in, length: size)?)) {
+ case let s: str =>
+ return strings::dup(s);
+ case =>
+ return Invalid;
+ };
+};
+
+// TODO: depth limit
+export fn load(in: *[]u8) ((str, Object) | Invalid) = {
+ const tag = decode_byte(in)?: Tag;
+ if (tag != Tag::COMPOUND) return Invalid;
+ const name = decode_string(in)?;
+ match (_load(in, tag)?) {
+ case let obj: Object =>
+ return (name, obj);
+ case =>
+ return Invalid;
+ };
+};
+
+fn _load(in: *[]u8, tag: Tag) (Value | Invalid) = {
+ switch (tag) {
+ case Tag::BYTE =>
+ return decode_byte(in)?;
+ case Tag::SHORT =>
+ return decode_short(in)?;
+ case Tag::INT =>
+ return decode_int(in)?;
+ case Tag::LONG =>
+ return decode_long(in)?;
+ case Tag::FLOAT =>
+ return decode_float(in)?;
+ case Tag::DOUBLE =>
+ return decode_double(in)?;
+ case Tag::STRING =>
+ return decode_string(in)?;
+ case Tag::BYTE_ARRAY =>
+ const length = decode_int(in)?;
+ if (length < 0) return Invalid;
+ return alloc(decode_nbytes(in, length: size)?...): []i8;
+ case Tag::LIST =>
+ let success = false;
+
+ const tag = decode_byte(in)?: Tag;
+ const length = decode_int(in)?;
+ if (length < 0) return Invalid;
+
+ let list = alloc([]: [0]Value, length: size);
+ defer if (!success) finish(list);
+ for (let i = 0i32; i < length; i += 1) {
+ static append(list, _load(in, tag)?);
+ };
+
+ success = true;
+ return list;
+ case Tag::COMPOUND =>
+ let success = false;
+
+ let object = newobject(0);
+ defer if (!success) finish(object);
+ for (true) {
+ const tag = decode_byte(in)?: Tag;
+ if (tag == Tag::END) break;
+ const name = decode_string(in)?;
+ defer free(name);
+ set(&object, name, _load(in, tag)?);
+ };
+
+ success = true;
+ return object;
+ case Tag::INT_ARRAY =>
+ let success = false;
+ const length = decode_int(in)?;
+ if (length < 0) return Invalid;
+
+ let array = alloc([]: [0]i32, length: size);
+ defer if (!success) free(array);
+ for (let i = 0i32; i < length; i += 1) {
+ static append(array, decode_int(in)?);
+ };
+
+ success = true;
+ return array;
+ case Tag::LONG_ARRAY =>
+ let success = false;
+ const length = decode_int(in)?;
+ if (length < 0) return Invalid;
+
+ let array = alloc([]: [0]i64, length: size);
+ defer if (!success) free(array);
+ for (let i = 0i32; i < length; i += 1) {
+ static append(array, decode_long(in)?);
+ };
+
+ success = true;
+ return array;
+ case =>
+ return Invalid;
+ };
+};