diff options
Diffstat (limited to 'nbt/load.ha')
-rw-r--r-- | nbt/load.ha | 152 |
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; + }; +}; |