summaryrefslogtreecommitdiff
path: root/nbt/load.ha
blob: 9e54bbb1f00767e6070efa13c77bc21acbadc36d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
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;
	};
};