File: | Formats.c |
Warning: | line 868, column 35 The left operand of '!=' is a garbage value |
Press '?' to see keyboard shortcuts
Keyboard shortcuts:
1 | #include "Formats.h" | |||
2 | #include "String.h" | |||
3 | #include "World.h" | |||
4 | #include "Deflate.h" | |||
5 | #include "Block.h" | |||
6 | #include "Entity.h" | |||
7 | #include "Platform.h" | |||
8 | #include "ExtMath.h" | |||
9 | #include "Logger.h" | |||
10 | #include "Game.h" | |||
11 | #include "Server.h" | |||
12 | #include "Event.h" | |||
13 | #include "Funcs.h" | |||
14 | #include "Errors.h" | |||
15 | #include "Stream.h" | |||
16 | #include "Chat.h" | |||
17 | #include "Inventory.h" | |||
18 | #include "TexturePack.h" | |||
19 | ||||
20 | ||||
21 | /*########################################################################################################################* | |||
22 | *--------------------------------------------------------General----------------------------------------------------------* | |||
23 | *#########################################################################################################################*/ | |||
24 | static cc_result Map_ReadBlocks(struct Stream* stream) { | |||
25 | World.Volume = World.Width * World.Length * World.Height; | |||
26 | World.Blocks = (BlockRaw*)Mem_TryAlloc(World.Volume, 1); | |||
27 | ||||
28 | if (!World.Blocks) return ERR_OUT_OF_MEMORY; | |||
29 | return Stream_Read(stream, World.Blocks, World.Volume); | |||
30 | } | |||
31 | ||||
32 | static cc_result Map_SkipGZipHeader(struct Stream* stream) { | |||
33 | struct GZipHeader gzHeader; | |||
34 | cc_result res; | |||
35 | GZipHeader_Init(&gzHeader); | |||
36 | ||||
37 | while (!gzHeader.done) { | |||
38 | if ((res = GZipHeader_Read(stream, &gzHeader))) return res; | |||
39 | } | |||
40 | return 0; | |||
41 | } | |||
42 | ||||
43 | IMapImporter Map_FindImporter(const cc_string* path) { | |||
44 | static const cc_string cw = String_FromConst(".cw"){ ".cw", (sizeof(".cw") - 1), (sizeof(".cw") - 1)}, lvl = String_FromConst(".lvl"){ ".lvl", (sizeof(".lvl") - 1), (sizeof(".lvl") - 1)}; | |||
45 | static const cc_string fcm = String_FromConst(".fcm"){ ".fcm", (sizeof(".fcm") - 1), (sizeof(".fcm") - 1)}, dat = String_FromConst(".dat"){ ".dat", (sizeof(".dat") - 1), (sizeof(".dat") - 1)}; | |||
46 | ||||
47 | if (String_CaselessEnds(path, &cw)) return Cw_Load; | |||
48 | #ifndef CC_BUILD_WEB | |||
49 | if (String_CaselessEnds(path, &lvl)) return Lvl_Load; | |||
50 | if (String_CaselessEnds(path, &fcm)) return Fcm_Load; | |||
51 | if (String_CaselessEnds(path, &dat)) return Dat_Load; | |||
52 | #endif | |||
53 | ||||
54 | return NULL((void*)0); | |||
55 | } | |||
56 | ||||
57 | void Map_LoadFrom(const cc_string* path) { | |||
58 | IMapImporter importer; | |||
59 | struct Stream stream; | |||
60 | cc_result res; | |||
61 | Game_Reset(); | |||
62 | ||||
63 | res = Stream_OpenFile(&stream, path); | |||
64 | if (res) { Logger_SysWarn2(res, "opening", path); return; } | |||
65 | ||||
66 | importer = Map_FindImporter(path); | |||
67 | if (!importer) { | |||
68 | Logger_SysWarn2(ERR_NOT_SUPPORTED, "decoding", path); | |||
69 | } else if ((res = importer(&stream))) { | |||
70 | World_Reset(); | |||
71 | Logger_SysWarn2(res, "decoding", path); | |||
72 | } | |||
73 | ||||
74 | res = stream.Close(&stream); | |||
75 | if (res) { Logger_SysWarn2(res, "closing", path); } | |||
76 | ||||
77 | World_SetNewMap(World.Blocks, World.Width, World.Height, World.Length); | |||
78 | LocalPlayer_MoveToSpawn(); | |||
79 | } | |||
80 | ||||
81 | ||||
82 | /*########################################################################################################################* | |||
83 | *--------------------------------------------------MCSharp level Format---------------------------------------------------* | |||
84 | *#########################################################################################################################*/ | |||
85 | #define LVL_CUSTOMTILE163 163 | |||
86 | #define LVL_CHUNKSIZE16 16 | |||
87 | /* MCSharp* format is a GZIP compressed binary map format. All metadata is discarded. | |||
88 | U16 "Identifier" (must be 1874) | |||
89 | U16 "Width", "Length", "Height" | |||
90 | U16 "SpawnX", "SpawnZ", "SpawnY" | |||
91 | U8 "Yaw", "Pitch" | |||
92 | U16 "Build permissions" (ignored) | |||
93 | U8* "Blocks" | |||
94 | ||||
95 | -- this data is only in MCGalaxy maps | |||
96 | U8 "Identifier" (0xBD for 'block definitions', i.e. custom blocks) | |||
97 | U8* "Data" (16x16x16 sparsely allocated chunks) | |||
98 | }*/ | |||
99 | ||||
100 | static const cc_uint8 Lvl_table[256] = { | |||
101 | 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, | |||
102 | 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, | |||
103 | 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, | |||
104 | 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, | |||
105 | 64, 65, 0, 0, 0, 0, 39, 36, 36, 10, 46, 21, 22, 22, 22, 22, | |||
106 | 4, 0, 22, 21, 0, 22, 23, 24, 22, 26, 27, 28, 30, 31, 32, 33, | |||
107 | 34, 35, 36, 22, 20, 49, 45, 1, 4, 0, 9, 11, 4, 19, 5, 17, | |||
108 | 10, 49, 20, 1, 18, 12, 5, 25, 46, 44, 17, 49, 20, 1, 18, 12, | |||
109 | 5, 25, 36, 34, 0, 9, 11, 46, 44, 0, 9, 11, 8, 10, 22, 27, | |||
110 | 22, 8, 10, 28, 17, 49, 20, 1, 18, 12, 5, 25, 46, 44, 11, 9, | |||
111 | 0, 9, 11, 163, 0, 0, 9, 11, 0, 0, 0, 0, 0, 0, 0, 28, | |||
112 | 22, 21, 11, 0, 0, 0, 46, 46, 10, 10, 46, 20, 41, 42, 11, 9, | |||
113 | 0, 8, 10, 10, 8, 0, 22, 22, 0, 0, 0, 0, 0, 0, 0, 0, | |||
114 | 0, 0, 0, 21, 10, 0, 0, 0, 0, 0, 22, 22, 42, 3, 2, 29, | |||
115 | 47, 0, 0, 0, 0, 0, 27, 46, 48, 24, 22, 36, 34, 8, 10, 21, | |||
116 | 29, 22, 10, 22, 22, 41, 19, 35, 21, 29, 49, 34, 16, 41, 0, 22 | |||
117 | }; | |||
118 | ||||
119 | static cc_result Lvl_ReadCustomBlocks(struct Stream* stream) { | |||
120 | cc_uint8 chunk[LVL_CHUNKSIZE16 * LVL_CHUNKSIZE16 * LVL_CHUNKSIZE16]; | |||
121 | cc_uint8 hasCustom; | |||
122 | int baseIndex, index, xx, yy, zz; | |||
123 | cc_result res; | |||
124 | int x, y, z, i; | |||
125 | ||||
126 | /* skip bounds checks when we know chunk is entirely inside map */ | |||
127 | int adjWidth = World.Width & ~0x0F; | |||
128 | int adjHeight = World.Height & ~0x0F; | |||
129 | int adjLength = World.Length & ~0x0F; | |||
130 | ||||
131 | for (y = 0; y < World.Height; y += LVL_CHUNKSIZE16) { | |||
132 | for (z = 0; z < World.Length; z += LVL_CHUNKSIZE16) { | |||
133 | for (x = 0; x < World.Width; x += LVL_CHUNKSIZE16) { | |||
134 | ||||
135 | if ((res = stream->ReadU8(stream, &hasCustom))) return res; | |||
136 | if (hasCustom != 1) continue; | |||
137 | if ((res = Stream_Read(stream, chunk, sizeof(chunk)))) return res; | |||
138 | baseIndex = World_Pack(x, y, z)(((y) * World.Length + (z)) * World.Width + (x)); | |||
139 | ||||
140 | if ((x + LVL_CHUNKSIZE16) <= adjWidth && (y + LVL_CHUNKSIZE16) <= adjHeight && (z + LVL_CHUNKSIZE16) <= adjLength) { | |||
141 | for (i = 0; i < sizeof(chunk); i++) { | |||
142 | xx = i & 0xF; yy = (i >> 8) & 0xF; zz = (i >> 4) & 0xF; | |||
143 | ||||
144 | index = baseIndex + World_Pack(xx, yy, zz)(((yy) * World.Length + (zz)) * World.Width + (xx)); | |||
145 | World.Blocks[index] = World.Blocks[index] == LVL_CUSTOMTILE163 ? chunk[i] : World.Blocks[index]; | |||
146 | } | |||
147 | } else { | |||
148 | for (i = 0; i < sizeof(chunk); i++) { | |||
149 | xx = i & 0xF; yy = (i >> 8) & 0xF; zz = (i >> 4) & 0xF; | |||
150 | if ((x + xx) >= World.Width || (y + yy) >= World.Height || (z + zz) >= World.Length) continue; | |||
151 | ||||
152 | index = baseIndex + World_Pack(xx, yy, zz)(((yy) * World.Length + (zz)) * World.Width + (xx)); | |||
153 | World.Blocks[index] = World.Blocks[index] == LVL_CUSTOMTILE163 ? chunk[i] : World.Blocks[index]; | |||
154 | } | |||
155 | } | |||
156 | } | |||
157 | } | |||
158 | } | |||
159 | return 0; | |||
160 | } | |||
161 | ||||
162 | cc_result Lvl_Load(struct Stream* stream) { | |||
163 | cc_uint8 header[18]; | |||
164 | cc_uint8* blocks; | |||
165 | cc_uint8 section; | |||
166 | cc_result res; | |||
167 | int i; | |||
168 | ||||
169 | struct LocalPlayer* p = &LocalPlayer_Instance; | |||
170 | struct Stream compStream; | |||
171 | struct InflateState state; | |||
172 | Inflate_MakeStream2(&compStream, &state, stream); | |||
173 | ||||
174 | if ((res = Map_SkipGZipHeader(stream))) return res; | |||
175 | if ((res = Stream_Read(&compStream, header, sizeof(header)))) return res; | |||
176 | if (Stream_GetU16_LE(&header[0]) != 1874) return LVL_ERR_VERSION; | |||
177 | ||||
178 | World.Width = Stream_GetU16_LE(&header[2]); | |||
179 | World.Length = Stream_GetU16_LE(&header[4]); | |||
180 | World.Height = Stream_GetU16_LE(&header[6]); | |||
181 | ||||
182 | p->Spawn.X = Stream_GetU16_LE(&header[8]); | |||
183 | p->Spawn.Z = Stream_GetU16_LE(&header[10]); | |||
184 | p->Spawn.Y = Stream_GetU16_LE(&header[12]); | |||
185 | p->SpawnYaw = Math_Packed2Deg(header[14])((header[14]) * 360.0f / 256.0f); | |||
186 | p->SpawnPitch = Math_Packed2Deg(header[15])((header[15]) * 360.0f / 256.0f); | |||
187 | /* (2) pervisit, perbuild permissions */ | |||
188 | ||||
189 | if ((res = Map_ReadBlocks(&compStream))) return res; | |||
190 | blocks = World.Blocks; | |||
191 | /* Bulk convert 4 blocks at once */ | |||
192 | for (i = 0; i < (World.Volume & ~3); i += 4) { | |||
193 | *blocks = Lvl_table[*blocks]; blocks++; | |||
194 | *blocks = Lvl_table[*blocks]; blocks++; | |||
195 | *blocks = Lvl_table[*blocks]; blocks++; | |||
196 | *blocks = Lvl_table[*blocks]; blocks++; | |||
197 | } | |||
198 | for (; i < World.Volume; i++) { | |||
199 | *blocks = Lvl_table[*blocks]; blocks++; | |||
200 | } | |||
201 | ||||
202 | /* 0xBD section type is not present in older .lvl files */ | |||
203 | res = compStream.ReadU8(&compStream, §ion); | |||
204 | if (res == ERR_END_OF_STREAM) return 0; | |||
205 | ||||
206 | if (res) return res; | |||
207 | return section == 0xBD ? Lvl_ReadCustomBlocks(&compStream) : 0; | |||
208 | } | |||
209 | ||||
210 | ||||
211 | /*########################################################################################################################* | |||
212 | *----------------------------------------------------fCraft map format----------------------------------------------------* | |||
213 | *#########################################################################################################################*/ | |||
214 | /* fCraft* format is a binary map format. All metadata is discarded. | |||
215 | U32 "Identifier" (must be FC2AF40) | |||
216 | U8 "Revision" (only '13' supported) | |||
217 | U16 "Width", "Height", "Length" | |||
218 | U32 "SpawnX", "SpawnY", "SpawnZ" | |||
219 | U8 "Yaw", "Pitch" | |||
220 | U32 "DateModified", "DateCreated" (ignored) | |||
221 | U8* "UUID" | |||
222 | U8 "Layers" (only maps with 1 layer supported) | |||
223 | U8* "LayersInfo" (ignored, assumes only layer is map blocks) | |||
224 | U32 "MetaCount" | |||
225 | METADATA { STR "Group", "Key", "Value" } | |||
226 | U8* "Blocks" | |||
227 | }*/ | |||
228 | static cc_result Fcm_ReadString(struct Stream* stream) { | |||
229 | cc_uint8 data[2]; | |||
230 | int len; | |||
231 | cc_result res; | |||
232 | ||||
233 | if ((res = Stream_Read(stream, data, sizeof(data)))) return res; | |||
234 | len = Stream_GetU16_LE(data); | |||
235 | ||||
236 | return stream->Skip(stream, len); | |||
237 | } | |||
238 | ||||
239 | cc_result Fcm_Load(struct Stream* stream) { | |||
240 | cc_uint8 header[79]; | |||
241 | cc_result res; | |||
242 | int i, count; | |||
243 | ||||
244 | struct LocalPlayer* p = &LocalPlayer_Instance; | |||
245 | struct Stream compStream; | |||
246 | struct InflateState state; | |||
247 | Inflate_MakeStream2(&compStream, &state, stream); | |||
248 | ||||
249 | if ((res = Stream_Read(stream, header, sizeof(header)))) return res; | |||
250 | if (Stream_GetU32_LE(&header[0]) != 0x0FC2AF40UL) return FCM_ERR_IDENTIFIER; | |||
251 | if (header[4] != 13) return FCM_ERR_REVISION; | |||
252 | ||||
253 | World.Width = Stream_GetU16_LE(&header[5]); | |||
254 | World.Height = Stream_GetU16_LE(&header[7]); | |||
255 | World.Length = Stream_GetU16_LE(&header[9]); | |||
256 | ||||
257 | p->Spawn.X = ((int)Stream_GetU32_LE(&header[11])) / 32.0f; | |||
258 | p->Spawn.Y = ((int)Stream_GetU32_LE(&header[15])) / 32.0f; | |||
259 | p->Spawn.Z = ((int)Stream_GetU32_LE(&header[19])) / 32.0f; | |||
260 | p->SpawnYaw = Math_Packed2Deg(header[23])((header[23]) * 360.0f / 256.0f); | |||
261 | p->SpawnPitch = Math_Packed2Deg(header[24])((header[24]) * 360.0f / 256.0f); | |||
262 | ||||
263 | /* header[25] (4) date modified */ | |||
264 | /* header[29] (4) date created */ | |||
265 | Mem_Copy(&World.Uuid, &header[33], WORLD_UUID_LEN16); | |||
266 | /* header[49] (26) layer index */ | |||
267 | count = (int)Stream_GetU32_LE(&header[75]); | |||
268 | ||||
269 | /* header isn't compressed, rest of data is though */ | |||
270 | for (i = 0; i < count; i++) { | |||
271 | if ((res = Fcm_ReadString(&compStream))) return res; /* Group */ | |||
272 | if ((res = Fcm_ReadString(&compStream))) return res; /* Key */ | |||
273 | if ((res = Fcm_ReadString(&compStream))) return res; /* Value */ | |||
274 | } | |||
275 | ||||
276 | return Map_ReadBlocks(&compStream); | |||
277 | } | |||
278 | ||||
279 | ||||
280 | /*########################################################################################################################* | |||
281 | *---------------------------------------------------------NBTFile---------------------------------------------------------* | |||
282 | *#########################################################################################################################*/ | |||
283 | enum NbtTagType { | |||
284 | NBT_END, NBT_I8, NBT_I16, NBT_I32, NBT_I64, NBT_F32, | |||
285 | NBT_R64, NBT_I8S, NBT_STR, NBT_LIST, NBT_DICT | |||
286 | }; | |||
287 | ||||
288 | #define NBT_SMALL_SIZE64 STRING_SIZE64 | |||
289 | #define NBT_STRING_SIZE64 STRING_SIZE64 | |||
290 | #define NbtTag_IsSmall(tag)((tag)->dataSize <= 64) ((tag)->dataSize <= NBT_SMALL_SIZE64) | |||
291 | struct NbtTag; | |||
292 | ||||
293 | struct NbtTag { | |||
294 | struct NbtTag* parent; | |||
295 | cc_uint8 type; | |||
296 | cc_string name; | |||
297 | cc_uint32 dataSize; /* size of data for arrays */ | |||
298 | ||||
299 | union { | |||
300 | cc_uint8 u8; | |||
301 | cc_int16 i16; | |||
302 | cc_uint16 u16; | |||
303 | cc_uint32 u32; | |||
304 | float f32; | |||
305 | cc_uint8 small[NBT_SMALL_SIZE64]; | |||
306 | cc_uint8* big; /* malloc for big byte arrays */ | |||
307 | struct { cc_string text; char buffer[NBT_STRING_SIZE64]; } str; | |||
308 | } value; | |||
309 | char _nameBuffer[NBT_STRING_SIZE64]; | |||
310 | cc_result result; | |||
311 | }; | |||
312 | ||||
313 | static cc_uint8 NbtTag_U8(struct NbtTag* tag) { | |||
314 | if (tag->type != NBT_I8) Logger_Abort("Expected I8 NBT tag"); | |||
315 | return tag->value.u8; | |||
316 | } | |||
317 | ||||
318 | static cc_int16 NbtTag_I16(struct NbtTag* tag) { | |||
319 | if (tag->type != NBT_I16) Logger_Abort("Expected I16 NBT tag"); | |||
320 | return tag->value.i16; | |||
321 | } | |||
322 | ||||
323 | static cc_uint16 NbtTag_U16(struct NbtTag* tag) { | |||
324 | if (tag->type != NBT_I16) Logger_Abort("Expected I16 NBT tag"); | |||
325 | return tag->value.u16; | |||
326 | } | |||
327 | ||||
328 | static float NbtTag_F32(struct NbtTag* tag) { | |||
329 | if (tag->type != NBT_F32) Logger_Abort("Expected F32 NBT tag"); | |||
330 | return tag->value.f32; | |||
331 | } | |||
332 | ||||
333 | static cc_uint8* NbtTag_U8_Array(struct NbtTag* tag, int minSize) { | |||
334 | if (tag->type != NBT_I8S) Logger_Abort("Expected I8_Array NBT tag"); | |||
335 | if (tag->dataSize < minSize) Logger_Abort("I8_Array NBT tag too small"); | |||
336 | ||||
337 | return NbtTag_IsSmall(tag)((tag)->dataSize <= 64) ? tag->value.small : tag->value.big; | |||
338 | } | |||
339 | ||||
340 | static cc_string NbtTag_String(struct NbtTag* tag) { | |||
341 | if (tag->type != NBT_STR) Logger_Abort("Expected String NBT tag"); | |||
342 | return tag->value.str.text; | |||
343 | } | |||
344 | ||||
345 | static cc_result Nbt_ReadString(struct Stream* stream, cc_string* str) { | |||
346 | cc_uint8 buffer[NBT_STRING_SIZE64 * 4]; | |||
347 | int len; | |||
348 | cc_result res; | |||
349 | ||||
350 | if ((res = Stream_Read(stream, buffer, 2))) return res; | |||
351 | len = Stream_GetU16_BE(buffer); | |||
352 | ||||
353 | if (len > Array_Elems(buffer)(sizeof(buffer) / sizeof(buffer[0]))) return CW_ERR_STRING_LEN; | |||
354 | if ((res = Stream_Read(stream, buffer, len))) return res; | |||
355 | ||||
356 | String_AppendUtf8(str, buffer, len); | |||
357 | return 0; | |||
358 | } | |||
359 | ||||
360 | typedef void (*Nbt_Callback)(struct NbtTag* tag); | |||
361 | static cc_result Nbt_ReadTag(cc_uint8 typeId, cc_bool readTagName, struct Stream* stream, struct NbtTag* parent, Nbt_Callback callback) { | |||
362 | struct NbtTag tag; | |||
363 | cc_uint8 childType; | |||
364 | cc_uint8 tmp[5]; | |||
365 | cc_result res; | |||
366 | cc_uint32 i, count; | |||
367 | ||||
368 | if (typeId == NBT_END) return 0; | |||
369 | tag.type = typeId; | |||
370 | tag.parent = parent; | |||
371 | tag.dataSize = 0; | |||
372 | String_InitArray(tag.name, tag._nameBuffer)tag.name.buffer = tag._nameBuffer; tag.name.length = 0; tag.name .capacity = sizeof(tag._nameBuffer);; | |||
373 | ||||
374 | if (readTagName) { | |||
375 | res = Nbt_ReadString(stream, &tag.name); | |||
376 | if (res) return res; | |||
377 | } | |||
378 | ||||
379 | switch (typeId) { | |||
380 | case NBT_I8: | |||
381 | res = stream->ReadU8(stream, &tag.value.u8); | |||
382 | break; | |||
383 | case NBT_I16: | |||
384 | res = Stream_Read(stream, tmp, 2); | |||
385 | tag.value.u16 = Stream_GetU16_BE(tmp); | |||
386 | break; | |||
387 | case NBT_I32: | |||
388 | case NBT_F32: | |||
389 | res = Stream_ReadU32_BE(stream, &tag.value.u32); | |||
390 | break; | |||
391 | case NBT_I64: | |||
392 | case NBT_R64: | |||
393 | res = stream->Skip(stream, 8); | |||
394 | break; /* (8) data */ | |||
395 | ||||
396 | case NBT_I8S: | |||
397 | if ((res = Stream_ReadU32_BE(stream, &tag.dataSize))) break; | |||
398 | ||||
399 | if (NbtTag_IsSmall(&tag)((&tag)->dataSize <= 64)) { | |||
400 | res = Stream_Read(stream, tag.value.small, tag.dataSize); | |||
401 | } else { | |||
402 | tag.value.big = (cc_uint8*)Mem_TryAlloc(tag.dataSize, 1); | |||
403 | if (!tag.value.big) return ERR_OUT_OF_MEMORY; | |||
404 | ||||
405 | res = Stream_Read(stream, tag.value.big, tag.dataSize); | |||
406 | if (res) Mem_Free(tag.value.big); | |||
407 | } | |||
408 | break; | |||
409 | case NBT_STR: | |||
410 | String_InitArray(tag.value.str.text, tag.value.str.buffer)tag.value.str.text.buffer = tag.value.str.buffer; tag.value.str .text.length = 0; tag.value.str.text.capacity = sizeof(tag.value .str.buffer);; | |||
411 | res = Nbt_ReadString(stream, &tag.value.str.text); | |||
412 | break; | |||
413 | ||||
414 | case NBT_LIST: | |||
415 | if ((res = Stream_Read(stream, tmp, 5))) break; | |||
416 | childType = tmp[0]; | |||
417 | count = Stream_GetU32_BE(&tmp[1]); | |||
418 | ||||
419 | for (i = 0; i < count; i++) { | |||
420 | res = Nbt_ReadTag(childType, false0, stream, &tag, callback); | |||
421 | if (res) break; | |||
422 | } | |||
423 | break; | |||
424 | ||||
425 | case NBT_DICT: | |||
426 | for (;;) { | |||
427 | if ((res = stream->ReadU8(stream, &childType))) break; | |||
428 | if (childType == NBT_END) break; | |||
429 | ||||
430 | res = Nbt_ReadTag(childType, true1, stream, &tag, callback); | |||
431 | if (res) break; | |||
432 | } | |||
433 | break; | |||
434 | ||||
435 | default: return NBT_ERR_UNKNOWN; | |||
436 | } | |||
437 | ||||
438 | if (res) return res; | |||
439 | tag.result = 0; | |||
440 | callback(&tag); | |||
441 | /* NOTE: callback must set DataBig to NULL, if doesn't want it to be freed */ | |||
442 | if (!NbtTag_IsSmall(&tag)((&tag)->dataSize <= 64)) Mem_Free(tag.value.big); | |||
443 | return tag.result; | |||
444 | } | |||
445 | #define IsTag(tag, tagName)(String_CaselessEqualsConst(&tag->name, tagName)) (String_CaselessEqualsConst(&tag->name, tagName)) | |||
446 | ||||
447 | /*########################################################################################################################* | |||
448 | *--------------------------------------------------ClassicWorld format----------------------------------------------------* | |||
449 | *#########################################################################################################################*/ | |||
450 | /* ClassicWorld is a NBT tag based map format. Tags not listed below are discarded. | |||
451 | COMPOUND "ClassicWorld" { | |||
452 | U8* "UUID" | |||
453 | U16 "X", "Y", "Z" | |||
454 | COMPOUND "Spawn" { | |||
455 | I16 "X", "Y", "Z" | |||
456 | U8 "H", "P" | |||
457 | } | |||
458 | U8* "BlockArray" (lower 8 bits, required) | |||
459 | U8* "BlockArray2" (upper 8 bits, optional) | |||
460 | COMPOUND "Metadata" { | |||
461 | COMPOUND "CPE" { | |||
462 | COMPOUND "ClickDistance" { U16 "Reach" } | |||
463 | COMPOUND "EnvWeatherType" { U8 "WeatherType" } | |||
464 | COMPOUND "EnvMapAppearance" { | |||
465 | U8 "SideBlock", "EdgeBlock" | |||
466 | I16 "SidesLevel" | |||
467 | STR "TextureURL" | |||
468 | } | |||
469 | COMPOUND "EnvColors" { | |||
470 | COMPOUND "Sky" { U16 "R", "G", "B" } | |||
471 | COMPOUND "Cloud" { U16 "R", "G", "B" } | |||
472 | COMPOUND "Fog" { U16 "R", "G", "B" } | |||
473 | COMPOUND "Sunlight" { U16 "R", "G", "B" } | |||
474 | COMPOUND "Ambient" { U16 "R", "G", "B" } | |||
475 | } | |||
476 | COMPOUND "BlockDefinitions" { | |||
477 | COMPOUND "Block_XYZ" { (name must start with 'Block') | |||
478 | U8 "ID", U16 "ID2" | |||
479 | STR "Name" | |||
480 | F32 "Speed" | |||
481 | U8 "CollideType", "BlockDraw" | |||
482 | U8 "TransmitsLight", "FullBright" | |||
483 | U8 "Shape" , "WalkSound" | |||
484 | U8* "Textures", "Fog", "Coords" | |||
485 | } | |||
486 | } | |||
487 | } | |||
488 | } | |||
489 | }*/ | |||
490 | static BlockRaw* Cw_GetBlocks(struct NbtTag* tag) { | |||
491 | BlockRaw* ptr; | |||
492 | if (NbtTag_IsSmall(tag)((tag)->dataSize <= 64)) { | |||
493 | ptr = (BlockRaw*)Mem_Alloc(tag->dataSize, 1, ".cw map blocks"); | |||
494 | Mem_Copy(ptr, tag->value.small, tag->dataSize); | |||
495 | } else { | |||
496 | ptr = tag->value.big; | |||
497 | tag->value.big = NULL((void*)0); /* So Nbt_ReadTag doesn't call Mem_Free on World.Blocks */ | |||
498 | } | |||
499 | return ptr; | |||
500 | } | |||
501 | ||||
502 | static void Cw_Callback_1(struct NbtTag* tag) { | |||
503 | if (IsTag(tag, "X")(String_CaselessEqualsConst(&tag->name, "X"))) { World.Width = NbtTag_U16(tag); return; } | |||
504 | if (IsTag(tag, "Y")(String_CaselessEqualsConst(&tag->name, "Y"))) { World.Height = NbtTag_U16(tag); return; } | |||
505 | if (IsTag(tag, "Z")(String_CaselessEqualsConst(&tag->name, "Z"))) { World.Length = NbtTag_U16(tag); return; } | |||
506 | ||||
507 | if (IsTag(tag, "UUID")(String_CaselessEqualsConst(&tag->name, "UUID"))) { | |||
508 | if (tag->dataSize != WORLD_UUID_LEN16) { | |||
509 | tag->result = CW_ERR_UUID_LEN; | |||
510 | } else { | |||
511 | Mem_Copy(World.Uuid, tag->value.small, WORLD_UUID_LEN16); | |||
512 | } | |||
513 | return; | |||
514 | } | |||
515 | ||||
516 | if (IsTag(tag, "BlockArray")(String_CaselessEqualsConst(&tag->name, "BlockArray"))) { | |||
517 | World.Volume = tag->dataSize; | |||
518 | World.Blocks = Cw_GetBlocks(tag); | |||
519 | } | |||
520 | #ifdef EXTENDED_BLOCKS | |||
521 | if (IsTag(tag, "BlockArray2")(String_CaselessEqualsConst(&tag->name, "BlockArray2") )) World_SetMapUpper(Cw_GetBlocks(tag)); | |||
522 | #endif | |||
523 | } | |||
524 | ||||
525 | static void Cw_Callback_2(struct NbtTag* tag) { | |||
526 | struct LocalPlayer* p = &LocalPlayer_Instance; | |||
527 | if (!IsTag(tag->parent, "Spawn")(String_CaselessEqualsConst(&tag->parent->name, "Spawn" ))) return; | |||
528 | ||||
529 | if (IsTag(tag, "X")(String_CaselessEqualsConst(&tag->name, "X"))) { p->Spawn.X = NbtTag_I16(tag); return; } | |||
530 | if (IsTag(tag, "Y")(String_CaselessEqualsConst(&tag->name, "Y"))) { p->Spawn.Y = NbtTag_I16(tag); return; } | |||
531 | if (IsTag(tag, "Z")(String_CaselessEqualsConst(&tag->name, "Z"))) { p->Spawn.Z = NbtTag_I16(tag); return; } | |||
532 | if (IsTag(tag, "H")(String_CaselessEqualsConst(&tag->name, "H"))) { p->SpawnYaw = Math_Packed2Deg(NbtTag_U8(tag))((NbtTag_U8(tag)) * 360.0f / 256.0f); return; } | |||
533 | if (IsTag(tag, "P")(String_CaselessEqualsConst(&tag->name, "P"))) { p->SpawnPitch = Math_Packed2Deg(NbtTag_U8(tag))((NbtTag_U8(tag)) * 360.0f / 256.0f); return; } | |||
534 | } | |||
535 | ||||
536 | static BlockID cw_curID; | |||
537 | static int cw_colR, cw_colG, cw_colB; | |||
538 | static PackedCol Cw_ParseCol(PackedCol defValue) { | |||
539 | int r = cw_colR, g = cw_colG, b = cw_colB; | |||
540 | if (r > 255 || g > 255 || b > 255) return defValue; | |||
541 | return PackedCol_Make(r, g, b, 255)(((cc_uint8)(r) << 0) | ((cc_uint8)(g) << 8) | (( cc_uint8)(b) << 16) | ((cc_uint8)(255) << 24)); | |||
542 | } | |||
543 | ||||
544 | static void Cw_Callback_4(struct NbtTag* tag) { | |||
545 | BlockID id = cw_curID; | |||
546 | struct LocalPlayer* p = &LocalPlayer_Instance; | |||
547 | ||||
548 | if (!IsTag(tag->parent->parent, "CPE")(String_CaselessEqualsConst(&tag->parent->parent-> name, "CPE"))) return; | |||
549 | if (!IsTag(tag->parent->parent->parent, "Metadata")(String_CaselessEqualsConst(&tag->parent->parent-> parent->name, "Metadata"))) return; | |||
550 | ||||
551 | if (IsTag(tag->parent, "ClickDistance")(String_CaselessEqualsConst(&tag->parent->name, "ClickDistance" ))) { | |||
552 | if (IsTag(tag, "Distance")(String_CaselessEqualsConst(&tag->name, "Distance"))) { p->ReachDistance = NbtTag_U16(tag) / 32.0f; return; } | |||
553 | } | |||
554 | if (IsTag(tag->parent, "EnvWeatherType")(String_CaselessEqualsConst(&tag->parent->name, "EnvWeatherType" ))) { | |||
555 | if (IsTag(tag, "WeatherType")(String_CaselessEqualsConst(&tag->name, "WeatherType") )) { Env.Weather = NbtTag_U8(tag); return; } | |||
556 | } | |||
557 | ||||
558 | if (IsTag(tag->parent, "EnvMapAppearance")(String_CaselessEqualsConst(&tag->parent->name, "EnvMapAppearance" ))) { | |||
559 | if (IsTag(tag, "SideBlock")(String_CaselessEqualsConst(&tag->name, "SideBlock"))) { Env.SidesBlock = NbtTag_U8(tag); return; } | |||
560 | if (IsTag(tag, "EdgeBlock")(String_CaselessEqualsConst(&tag->name, "EdgeBlock"))) { Env.EdgeBlock = NbtTag_U8(tag); return; } | |||
561 | if (IsTag(tag, "SideLevel")(String_CaselessEqualsConst(&tag->name, "SideLevel"))) { Env.EdgeHeight = NbtTag_I16(tag); return; } | |||
562 | ||||
563 | if (IsTag(tag, "TextureURL")(String_CaselessEqualsConst(&tag->name, "TextureURL"))) { | |||
564 | cc_string url = NbtTag_String(tag); | |||
565 | if (url.length) Server_RetrieveTexturePack(&url); | |||
566 | return; | |||
567 | } | |||
568 | } | |||
569 | ||||
570 | /* Callback for compound tag is called after all its children have been processed */ | |||
571 | if (IsTag(tag->parent, "EnvColors")(String_CaselessEqualsConst(&tag->parent->name, "EnvColors" ))) { | |||
572 | if (IsTag(tag, "Sky")(String_CaselessEqualsConst(&tag->name, "Sky"))) { | |||
573 | Env.SkyCol = Cw_ParseCol(ENV_DEFAULT_SKY_COL(((cc_uint8)(0x99) << 0) | ((cc_uint8)(0xCC) << 8 ) | ((cc_uint8)(0xFF) << 16) | ((cc_uint8)(0xFF) << 24))); return; | |||
574 | } else if (IsTag(tag, "Cloud")(String_CaselessEqualsConst(&tag->name, "Cloud"))) { | |||
575 | Env.CloudsCol = Cw_ParseCol(ENV_DEFAULT_CLOUDS_COL(((cc_uint8)(0xFF) << 0) | ((cc_uint8)(0xFF) << 8 ) | ((cc_uint8)(0xFF) << 16) | ((cc_uint8)(0xFF) << 24))); return; | |||
576 | } else if (IsTag(tag, "Fog")(String_CaselessEqualsConst(&tag->name, "Fog"))) { | |||
577 | Env.FogCol = Cw_ParseCol(ENV_DEFAULT_FOG_COL(((cc_uint8)(0xFF) << 0) | ((cc_uint8)(0xFF) << 8 ) | ((cc_uint8)(0xFF) << 16) | ((cc_uint8)(0xFF) << 24))); return; | |||
578 | } else if (IsTag(tag, "Sunlight")(String_CaselessEqualsConst(&tag->name, "Sunlight"))) { | |||
579 | Env_SetSunCol(Cw_ParseCol(ENV_DEFAULT_SUN_COL(((cc_uint8)(0xFF) << 0) | ((cc_uint8)(0xFF) << 8 ) | ((cc_uint8)(0xFF) << 16) | ((cc_uint8)(0xFF) << 24)))); return; | |||
580 | } else if (IsTag(tag, "Ambient")(String_CaselessEqualsConst(&tag->name, "Ambient"))) { | |||
581 | Env_SetShadowCol(Cw_ParseCol(ENV_DEFAULT_SHADOW_COL(((cc_uint8)(0x9B) << 0) | ((cc_uint8)(0x9B) << 8 ) | ((cc_uint8)(0x9B) << 16) | ((cc_uint8)(0xFF) << 24)))); return; | |||
582 | } | |||
583 | } | |||
584 | ||||
585 | if (IsTag(tag->parent, "BlockDefinitions")(String_CaselessEqualsConst(&tag->parent->name, "BlockDefinitions" )) && Game_AllowCustomBlocks) { | |||
586 | static const cc_string blockStr = String_FromConst("Block"){ "Block", (sizeof("Block") - 1), (sizeof("Block") - 1)}; | |||
587 | if (!String_CaselessStarts(&tag->name, &blockStr)) return; | |||
588 | ||||
589 | /* hack for sprite draw (can't rely on order of tags when reading) */ | |||
590 | if (Blocks.SpriteOffset[id] == 0) { | |||
591 | Blocks.SpriteOffset[id] = Blocks.Draw[id]; | |||
592 | Blocks.Draw[id] = DRAW_SPRITE; | |||
593 | } else { | |||
594 | Blocks.SpriteOffset[id] = 0; | |||
595 | } | |||
596 | ||||
597 | Block_DefineCustom(id); | |||
598 | Blocks.CanPlace[id] = true1; | |||
599 | Blocks.CanDelete[id] = true1; | |||
600 | Event_RaiseVoid(&BlockEvents.PermissionsChanged); | |||
601 | ||||
602 | cw_curID = 0; | |||
603 | } | |||
604 | } | |||
605 | ||||
606 | static void Cw_Callback_5(struct NbtTag* tag) { | |||
607 | BlockID id = cw_curID; | |||
608 | cc_uint8* arr; | |||
609 | cc_uint8 sound; | |||
610 | ||||
611 | if (!IsTag(tag->parent->parent->parent, "CPE")(String_CaselessEqualsConst(&tag->parent->parent-> parent->name, "CPE"))) return; | |||
612 | if (!IsTag(tag->parent->parent->parent->parent, "Metadata")(String_CaselessEqualsConst(&tag->parent->parent-> parent->parent->name, "Metadata"))) return; | |||
613 | ||||
614 | if (IsTag(tag->parent->parent, "EnvColors")(String_CaselessEqualsConst(&tag->parent->parent-> name, "EnvColors"))) { | |||
615 | if (IsTag(tag, "R")(String_CaselessEqualsConst(&tag->name, "R"))) { cw_colR = NbtTag_U16(tag); return; } | |||
616 | if (IsTag(tag, "G")(String_CaselessEqualsConst(&tag->name, "G"))) { cw_colG = NbtTag_U16(tag); return; } | |||
617 | if (IsTag(tag, "B")(String_CaselessEqualsConst(&tag->name, "B"))) { cw_colB = NbtTag_U16(tag); return; } | |||
618 | } | |||
619 | ||||
620 | if (IsTag(tag->parent->parent, "BlockDefinitions")(String_CaselessEqualsConst(&tag->parent->parent-> name, "BlockDefinitions")) && Game_AllowCustomBlocks) { | |||
621 | if (IsTag(tag, "ID")(String_CaselessEqualsConst(&tag->name, "ID"))) { cw_curID = NbtTag_U8(tag); return; } | |||
622 | if (IsTag(tag, "ID2")(String_CaselessEqualsConst(&tag->name, "ID2"))) { cw_curID = NbtTag_U16(tag); return; } | |||
623 | if (IsTag(tag, "CollideType")(String_CaselessEqualsConst(&tag->name, "CollideType") )) { Block_SetCollide(id, NbtTag_U8(tag)); return; } | |||
624 | if (IsTag(tag, "Speed")(String_CaselessEqualsConst(&tag->name, "Speed"))) { Blocks.SpeedMultiplier[id] = NbtTag_F32(tag); return; } | |||
625 | if (IsTag(tag, "TransmitsLight")(String_CaselessEqualsConst(&tag->name, "TransmitsLight" ))) { Blocks.BlocksLight[id] = NbtTag_U8(tag) == 0; return; } | |||
626 | if (IsTag(tag, "FullBright")(String_CaselessEqualsConst(&tag->name, "FullBright"))) { Blocks.FullBright[id] = NbtTag_U8(tag) != 0; return; } | |||
627 | if (IsTag(tag, "BlockDraw")(String_CaselessEqualsConst(&tag->name, "BlockDraw"))) { Blocks.Draw[id] = NbtTag_U8(tag); return; } | |||
628 | if (IsTag(tag, "Shape")(String_CaselessEqualsConst(&tag->name, "Shape"))) { Blocks.SpriteOffset[id] = NbtTag_U8(tag); return; } | |||
629 | ||||
630 | if (IsTag(tag, "Name")(String_CaselessEqualsConst(&tag->name, "Name"))) { | |||
631 | cc_string name = NbtTag_String(tag); | |||
632 | Block_SetName(id, &name); | |||
633 | return; | |||
634 | } | |||
635 | ||||
636 | if (IsTag(tag, "Textures")(String_CaselessEqualsConst(&tag->name, "Textures"))) { | |||
637 | arr = NbtTag_U8_Array(tag, 6); | |||
638 | Block_Tex(id, FACE_YMAX)Blocks.Textures[(id) * FACE_COUNT + (FACE_YMAX)] = arr[0]; Block_Tex(id, FACE_YMIN)Blocks.Textures[(id) * FACE_COUNT + (FACE_YMIN)] = arr[1]; | |||
639 | Block_Tex(id, FACE_XMIN)Blocks.Textures[(id) * FACE_COUNT + (FACE_XMIN)] = arr[2]; Block_Tex(id, FACE_XMAX)Blocks.Textures[(id) * FACE_COUNT + (FACE_XMAX)] = arr[3]; | |||
640 | Block_Tex(id, FACE_ZMIN)Blocks.Textures[(id) * FACE_COUNT + (FACE_ZMIN)] = arr[4]; Block_Tex(id, FACE_ZMAX)Blocks.Textures[(id) * FACE_COUNT + (FACE_ZMAX)] = arr[5]; | |||
641 | ||||
642 | /* hacky way of storing upper 8 bits */ | |||
643 | if (tag->dataSize >= 12) { | |||
644 | Block_Tex(id, FACE_YMAX)Blocks.Textures[(id) * FACE_COUNT + (FACE_YMAX)] |= arr[6] << 8; Block_Tex(id, FACE_YMIN)Blocks.Textures[(id) * FACE_COUNT + (FACE_YMIN)] |= arr[7] << 8; | |||
645 | Block_Tex(id, FACE_XMIN)Blocks.Textures[(id) * FACE_COUNT + (FACE_XMIN)] |= arr[8] << 8; Block_Tex(id, FACE_XMAX)Blocks.Textures[(id) * FACE_COUNT + (FACE_XMAX)] |= arr[9] << 8; | |||
646 | Block_Tex(id, FACE_ZMIN)Blocks.Textures[(id) * FACE_COUNT + (FACE_ZMIN)] |= arr[10] << 8; Block_Tex(id, FACE_ZMAX)Blocks.Textures[(id) * FACE_COUNT + (FACE_ZMAX)] |= arr[11] << 8; | |||
647 | } | |||
648 | return; | |||
649 | } | |||
650 | ||||
651 | if (IsTag(tag, "WalkSound")(String_CaselessEqualsConst(&tag->name, "WalkSound"))) { | |||
652 | sound = NbtTag_U8(tag); | |||
653 | Blocks.DigSounds[id] = sound; | |||
654 | Blocks.StepSounds[id] = sound; | |||
655 | if (sound == SOUND_GLASS) Blocks.StepSounds[id] = SOUND_STONE; | |||
656 | return; | |||
657 | } | |||
658 | ||||
659 | if (IsTag(tag, "Fog")(String_CaselessEqualsConst(&tag->name, "Fog"))) { | |||
660 | arr = NbtTag_U8_Array(tag, 4); | |||
661 | Blocks.FogDensity[id] = (arr[0] + 1) / 128.0f; | |||
662 | /* Fix for older ClassicalSharp versions which saved wrong fog density value */ | |||
663 | if (arr[0] == 0xFF) Blocks.FogDensity[id] = 0.0f; | |||
664 | Blocks.FogCol[id] = PackedCol_Make(arr[1], arr[2], arr[3], 255)(((cc_uint8)(arr[1]) << 0) | ((cc_uint8)(arr[2]) << 8) | ((cc_uint8)(arr[3]) << 16) | ((cc_uint8)(255) << 24)); | |||
665 | return; | |||
666 | } | |||
667 | ||||
668 | if (IsTag(tag, "Coords")(String_CaselessEqualsConst(&tag->name, "Coords"))) { | |||
669 | arr = NbtTag_U8_Array(tag, 6); | |||
670 | Blocks.MinBB[id].X = (cc_int8)arr[0] / 16.0f; Blocks.MaxBB[id].X = (cc_int8)arr[3] / 16.0f; | |||
671 | Blocks.MinBB[id].Y = (cc_int8)arr[1] / 16.0f; Blocks.MaxBB[id].Y = (cc_int8)arr[4] / 16.0f; | |||
672 | Blocks.MinBB[id].Z = (cc_int8)arr[2] / 16.0f; Blocks.MaxBB[id].Z = (cc_int8)arr[5] / 16.0f; | |||
673 | return; | |||
674 | } | |||
675 | } | |||
676 | } | |||
677 | ||||
678 | static void Cw_Callback(struct NbtTag* tag) { | |||
679 | struct NbtTag* tmp = tag->parent; | |||
680 | int depth = 0; | |||
681 | while (tmp) { depth++; tmp = tmp->parent; } | |||
682 | ||||
683 | switch (depth) { | |||
684 | case 1: Cw_Callback_1(tag); return; | |||
685 | case 2: Cw_Callback_2(tag); return; | |||
686 | case 4: Cw_Callback_4(tag); return; | |||
687 | case 5: Cw_Callback_5(tag); return; | |||
688 | } | |||
689 | /* ClassicWorld -> Metadata -> CPE -> ExtName -> [values] | |||
690 | 0 1 2 3 4 */ | |||
691 | } | |||
692 | ||||
693 | cc_result Cw_Load(struct Stream* stream) { | |||
694 | struct Stream compStream; | |||
695 | struct InflateState state; | |||
696 | cc_result res; | |||
697 | cc_uint8 tag; | |||
698 | ||||
699 | Inflate_MakeStream2(&compStream, &state, stream); | |||
700 | if ((res = Map_SkipGZipHeader(stream))) return res; | |||
701 | if ((res = compStream.ReadU8(&compStream, &tag))) return res; | |||
702 | ||||
703 | if (tag != NBT_DICT) return CW_ERR_ROOT_TAG; | |||
704 | return Nbt_ReadTag(NBT_DICT, true1, &compStream, NULL((void*)0), Cw_Callback); | |||
705 | } | |||
706 | ||||
707 | ||||
708 | /*########################################################################################################################* | |||
709 | *-------------------------------------------------Minecraft .dat format---------------------------------------------------* | |||
710 | *#########################################################################################################################*/ | |||
711 | /* .dat is a java serialised map format. Rather than bothering following this, I skip a lot of it. | |||
712 | Stream BlockData BlockDataTiny BlockDataLong | |||
713 | |--------------| |---------------| |---------------| |---------------| | |||
714 | | U16 Magic | |>BlockDataTiny | | TC_BLOCKDATA | | TC_BLOCKLONG | | |||
715 | | U16 Version | |>BlockDataLong | | U8 Size | | U32 Size | | |||
716 | | Content[var] | |_______________| | U8 Data[size] | | U8 Data[size] | | |||
717 | |______________| |_______________| |_______________| | |||
718 | ||||
719 | Content | |||
720 | |--------------| |--------------| | |||
721 | | >BlockData | | >NewString | | |||
722 | | >Object | | >TC_RESET | | |||
723 | |______________| | >TC_NULL | | |||
724 | | >PrevObject | | |||
725 | | >NewClass | | |||
726 | | >NewEnum | | |||
727 | ||||
728 | }*/ | |||
729 | enum JTypeCode { | |||
730 | TC_NULL = 0x70, TC_REFERENCE = 0x71, TC_CLASSDESC = 0x72, TC_OBJECT = 0x73, | |||
731 | TC_STRING = 0x74, TC_ARRAY = 0x75, TC_ENDBLOCKDATA = 0x78 | |||
732 | }; | |||
733 | enum JFieldType { | |||
734 | JFIELD_I8 = 'B', JFIELD_F32 = 'F', JFIELD_I32 = 'I', JFIELD_I64 = 'J', | |||
735 | JFIELD_BOOL = 'Z', JFIELD_ARRAY = '[', JFIELD_OBJECT = 'L' | |||
736 | }; | |||
737 | ||||
738 | #define JNAME_SIZE48 48 | |||
739 | struct JFieldDesc { | |||
740 | cc_uint8 Type; | |||
741 | cc_uint8 FieldName[JNAME_SIZE48]; | |||
742 | union { | |||
743 | cc_uint8 U8; | |||
744 | cc_int32 I32; | |||
745 | cc_uint32 U32; | |||
746 | float F32; | |||
747 | struct { cc_uint8* Ptr; cc_uint32 Size; } Array; | |||
748 | } Value; | |||
749 | }; | |||
750 | ||||
751 | struct JClassDesc { | |||
752 | cc_uint8 ClassName[JNAME_SIZE48]; | |||
753 | int FieldsCount; | |||
754 | struct JFieldDesc Fields[22]; | |||
755 | }; | |||
756 | ||||
757 | static cc_result Dat_ReadString(struct Stream* stream, cc_uint8* buffer) { | |||
758 | int len; | |||
759 | cc_result res; | |||
760 | ||||
761 | if ((res = Stream_Read(stream, buffer, 2))) return res; | |||
762 | len = Stream_GetU16_BE(buffer); | |||
763 | ||||
764 | Mem_Set(buffer, 0, JNAME_SIZE48); | |||
765 | if (len > JNAME_SIZE48) return DAT_ERR_JSTRING_LEN; | |||
766 | return Stream_Read(stream, buffer, len); | |||
767 | } | |||
768 | ||||
769 | static cc_result Dat_ReadFieldDesc(struct Stream* stream, struct JFieldDesc* desc) { | |||
770 | cc_uint8 typeCode; | |||
771 | cc_uint8 className1[JNAME_SIZE48]; | |||
772 | cc_result res; | |||
773 | ||||
774 | if ((res = stream->ReadU8(stream, &desc->Type))) return res; | |||
775 | if ((res = Dat_ReadString(stream, desc->FieldName))) return res; | |||
776 | ||||
777 | if (desc->Type == JFIELD_ARRAY || desc->Type == JFIELD_OBJECT) { | |||
778 | if ((res = stream->ReadU8(stream, &typeCode))) return res; | |||
779 | ||||
780 | if (typeCode == TC_STRING) { | |||
781 | return Dat_ReadString(stream, className1); | |||
782 | } else if (typeCode == TC_REFERENCE) { | |||
783 | return stream->Skip(stream, 4); /* (4) handle */ | |||
784 | } else { | |||
785 | return DAT_ERR_JFIELD_CLASS_NAME; | |||
786 | } | |||
787 | } | |||
788 | return 0; | |||
789 | } | |||
790 | ||||
791 | static cc_result Dat_ReadClassDesc(struct Stream* stream, struct JClassDesc* desc) { | |||
792 | cc_uint8 typeCode; | |||
793 | cc_uint8 count[2]; | |||
794 | struct JClassDesc superClassDesc; | |||
795 | cc_result res; | |||
796 | int i; | |||
797 | ||||
798 | if ((res = stream->ReadU8(stream, &typeCode))) return res; | |||
799 | if (typeCode == TC_NULL) { desc->ClassName[0] = '\0'; desc->FieldsCount = 0; return 0; } | |||
800 | if (typeCode != TC_CLASSDESC) return DAT_ERR_JCLASS_TYPE; | |||
801 | ||||
802 | if ((res = Dat_ReadString(stream, desc->ClassName))) return res; | |||
803 | if ((res = stream->Skip(stream, 9))) return res; /* (8) serial version UID, (1) flags */ | |||
804 | ||||
805 | if ((res = Stream_Read(stream, count, 2))) return res; | |||
806 | desc->FieldsCount = Stream_GetU16_BE(count); | |||
807 | if (desc->FieldsCount > Array_Elems(desc->Fields)(sizeof(desc->Fields) / sizeof(desc->Fields[0]))) return DAT_ERR_JCLASS_FIELDS; | |||
808 | ||||
809 | for (i = 0; i < desc->FieldsCount; i++) { | |||
810 | if ((res = Dat_ReadFieldDesc(stream, &desc->Fields[i]))) return res; | |||
811 | } | |||
812 | ||||
813 | if ((res = stream->ReadU8(stream, &typeCode))) return res; | |||
814 | if (typeCode != TC_ENDBLOCKDATA) return DAT_ERR_JCLASS_ANNOTATION; | |||
815 | ||||
816 | return Dat_ReadClassDesc(stream, &superClassDesc); | |||
817 | } | |||
818 | ||||
819 | static cc_result Dat_ReadFieldData(struct Stream* stream, struct JFieldDesc* field) { | |||
820 | cc_uint8 typeCode; | |||
821 | cc_string fieldName; | |||
822 | cc_uint32 count; | |||
823 | struct JClassDesc arrayClassDesc; | |||
824 | cc_result res; | |||
825 | ||||
826 | switch (field->Type) { | |||
827 | case JFIELD_I8: | |||
828 | case JFIELD_BOOL: | |||
829 | return stream->ReadU8(stream, &field->Value.U8); | |||
830 | case JFIELD_F32: | |||
831 | case JFIELD_I32: | |||
832 | return Stream_ReadU32_BE(stream, &field->Value.U32); | |||
833 | case JFIELD_I64: | |||
834 | return stream->Skip(stream, 8); /* (8) data */ | |||
835 | ||||
836 | case JFIELD_OBJECT: { | |||
837 | /* Luckily for us, we only have to account for blockMap object */ | |||
838 | /* Other objects (e.g. player) are stored after the fields we actually care about, so ignore them */ | |||
839 | fieldName = String_FromRaw((char*)field->FieldName, JNAME_SIZE48); | |||
840 | if (!String_CaselessEqualsConst(&fieldName, "blockMap")) return 0; | |||
841 | if ((res = stream->ReadU8(stream, &typeCode))) return res; | |||
842 | ||||
843 | /* Skip all blockMap data with awful hacks */ | |||
844 | /* These offsets were based on server_level.dat map from original minecraft classic server */ | |||
845 | if (typeCode == TC_OBJECT) { | |||
846 | if ((res = stream->Skip(stream, 315))) return res; | |||
847 | if ((res = Stream_ReadU32_BE(stream, &count))) return res; | |||
848 | ||||
849 | if ((res = stream->Skip(stream, 17 * count))) return res; | |||
850 | if ((res = stream->Skip(stream, 152))) return res; | |||
851 | } else if (typeCode != TC_NULL) { | |||
852 | /* WoM maps have this field as null, which makes things easier for us */ | |||
853 | return DAT_ERR_JOBJECT_TYPE; | |||
854 | } | |||
855 | } break; | |||
856 | ||||
857 | case JFIELD_ARRAY: { | |||
858 | if ((res = stream->ReadU8(stream, &typeCode))) return res; | |||
859 | /* NULL/empty array */ | |||
860 | if (typeCode == TC_NULL) { | |||
861 | field->Value.Array.Size = 0; | |||
862 | field->Value.Array.Ptr = NULL((void*)0); | |||
863 | break; | |||
864 | } | |||
865 | ||||
866 | if (typeCode != TC_ARRAY) return DAT_ERR_JARRAY_TYPE; | |||
867 | if ((res = Dat_ReadClassDesc(stream, &arrayClassDesc))) return res; | |||
868 | if (arrayClassDesc.ClassName[1] != JFIELD_I8) return DAT_ERR_JARRAY_CONTENT; | |||
| ||||
869 | ||||
870 | if ((res = Stream_ReadU32_BE(stream, &count))) return res; | |||
871 | field->Value.Array.Size = count; | |||
872 | field->Value.Array.Ptr = (cc_uint8*)Mem_TryAlloc(count, 1); | |||
873 | ||||
874 | if (!field->Value.Array.Ptr) return ERR_OUT_OF_MEMORY; | |||
875 | res = Stream_Read(stream, field->Value.Array.Ptr, count); | |||
876 | if (res) { Mem_Free(field->Value.Array.Ptr); return res; } | |||
877 | } break; | |||
878 | } | |||
879 | return 0; | |||
880 | } | |||
881 | ||||
882 | static int Dat_I32(struct JFieldDesc* field) { | |||
883 | if (field->Type != JFIELD_I32) Logger_Abort("Field type must be Int32"); | |||
884 | return field->Value.I32; | |||
885 | } | |||
886 | ||||
887 | cc_result Dat_Load(struct Stream* stream) { | |||
888 | cc_uint8 header[10]; | |||
889 | struct JClassDesc obj; | |||
890 | struct JFieldDesc* field; | |||
891 | cc_string fieldName; | |||
892 | cc_result res; | |||
893 | int i; | |||
894 | ||||
895 | struct LocalPlayer* p = &LocalPlayer_Instance; | |||
896 | struct Stream compStream; | |||
897 | struct InflateState state; | |||
898 | Inflate_MakeStream2(&compStream, &state, stream); | |||
899 | ||||
900 | if ((res = Map_SkipGZipHeader(stream))) return res; | |||
| ||||
901 | if ((res = Stream_Read(&compStream, header, sizeof(header)))) return res; | |||
902 | /* .dat header */ | |||
903 | if (Stream_GetU32_BE(&header[0]) != 0x271BB788) return DAT_ERR_IDENTIFIER; | |||
904 | if (header[4] != 0x02) return DAT_ERR_VERSION; | |||
905 | ||||
906 | /* Java seralisation headers */ | |||
907 | if (Stream_GetU16_BE(&header[5]) != 0xACED) return DAT_ERR_JIDENTIFIER; | |||
908 | if (Stream_GetU16_BE(&header[7]) != 0x0005) return DAT_ERR_JVERSION; | |||
909 | if (header[9] != TC_OBJECT) return DAT_ERR_ROOT_TYPE; | |||
910 | if ((res = Dat_ReadClassDesc(&compStream, &obj))) return res; | |||
911 | ||||
912 | for (i = 0; i < obj.FieldsCount; i++) { | |||
913 | field = &obj.Fields[i]; | |||
914 | if ((res = Dat_ReadFieldData(&compStream, field))) return res; | |||
915 | fieldName = String_FromRaw((char*)field->FieldName, JNAME_SIZE48); | |||
916 | ||||
917 | if (String_CaselessEqualsConst(&fieldName, "width")) { | |||
918 | World.Width = Dat_I32(field); | |||
919 | } else if (String_CaselessEqualsConst(&fieldName, "height")) { | |||
920 | World.Length = Dat_I32(field); | |||
921 | } else if (String_CaselessEqualsConst(&fieldName, "depth")) { | |||
922 | World.Height = Dat_I32(field); | |||
923 | } else if (String_CaselessEqualsConst(&fieldName, "blocks")) { | |||
924 | if (field->Type != JFIELD_ARRAY) Logger_Abort("Blocks field must be Array"); | |||
925 | World.Blocks = field->Value.Array.Ptr; | |||
926 | World.Volume = field->Value.Array.Size; | |||
927 | } else if (String_CaselessEqualsConst(&fieldName, "xSpawn")) { | |||
928 | p->Spawn.X = (float)Dat_I32(field); | |||
929 | } else if (String_CaselessEqualsConst(&fieldName, "ySpawn")) { | |||
930 | p->Spawn.Y = (float)Dat_I32(field); | |||
931 | } else if (String_CaselessEqualsConst(&fieldName, "zSpawn")) { | |||
932 | p->Spawn.Z = (float)Dat_I32(field); | |||
933 | } | |||
934 | } | |||
935 | return 0; | |||
936 | } | |||
937 | ||||
938 | ||||
939 | /*########################################################################################################################* | |||
940 | *--------------------------------------------------ClassicWorld export----------------------------------------------------* | |||
941 | *#########################################################################################################################*/ | |||
942 | #define CW_META_RGBNBT_I16,0,1,'R',0,0, NBT_I16,0,1,'G',0,0, NBT_I16,0,1,'B',0,0 , NBT_I16,0,1,'R',0,0, NBT_I16,0,1,'G',0,0, NBT_I16,0,1,'B',0,0, | |||
943 | ||||
944 | static int Cw_WriteEndString(cc_uint8* data, const cc_string* text) { | |||
945 | cc_uint8* cur = data + 2; | |||
946 | int i, wrote, len = 0; | |||
947 | ||||
948 | for (i = 0; i < text->length; i++) { | |||
949 | wrote = Convert_CP437ToUtf8(text->buffer[i], cur); | |||
950 | len += wrote; cur += wrote; | |||
951 | } | |||
952 | ||||
953 | Stream_SetU16_BE(data, len); | |||
954 | *cur = NBT_END; | |||
955 | return len + 1; | |||
956 | } | |||
957 | ||||
958 | static cc_uint8 cw_begin[131] = { | |||
959 | NBT_DICT, 0,12, 'C','l','a','s','s','i','c','W','o','r','l','d', | |||
960 | NBT_I8, 0,13, 'F','o','r','m','a','t','V','e','r','s','i','o','n', 1, | |||
961 | NBT_I8S, 0,4, 'U','U','I','D', 0,0,0,16, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, | |||
962 | NBT_I16, 0,1, 'X', 0,0, | |||
963 | NBT_I16, 0,1, 'Y', 0,0, | |||
964 | NBT_I16, 0,1, 'Z', 0,0, | |||
965 | NBT_DICT, 0,5, 'S','p','a','w','n', | |||
966 | NBT_I16, 0,1, 'X', 0,0, | |||
967 | NBT_I16, 0,1, 'Y', 0,0, | |||
968 | NBT_I16, 0,1, 'Z', 0,0, | |||
969 | NBT_I8, 0,1, 'H', 0, | |||
970 | NBT_I8, 0,1, 'P', 0, | |||
971 | NBT_END, | |||
972 | NBT_I8S, 0,10, 'B','l','o','c','k','A','r','r','a','y', 0,0,0,0, | |||
973 | }; | |||
974 | static cc_uint8 cw_map2[18] = { | |||
975 | NBT_I8S, 0,11, 'B','l','o','c','k','A','r','r','a','y','2', 0,0,0,0, | |||
976 | }; | |||
977 | static cc_uint8 cw_meta_cpe[303] = { | |||
978 | NBT_DICT, 0,8, 'M','e','t','a','d','a','t','a', | |||
979 | NBT_DICT, 0,3, 'C','P','E', | |||
980 | NBT_DICT, 0,13, 'C','l','i','c','k','D','i','s','t','a','n','c','e', | |||
981 | NBT_I16, 0,8, 'D','i','s','t','a','n','c','e', 0,0, | |||
982 | NBT_END, | |||
983 | NBT_DICT, 0,14, 'E','n','v','W','e','a','t','h','e','r','T','y','p','e', | |||
984 | NBT_I8, 0,11, 'W','e','a','t','h','e','r','T','y','p','e', 0, | |||
985 | NBT_END, | |||
986 | NBT_DICT, 0,9, 'E','n','v','C','o','l','o','r','s', | |||
987 | NBT_DICT, 0,3, 'S','k','y', CW_META_RGBNBT_I16,0,1,'R',0,0, NBT_I16,0,1,'G',0,0, NBT_I16,0,1,'B',0,0 , | |||
988 | NBT_END, | |||
989 | NBT_DICT, 0,5, 'C','l','o','u','d', CW_META_RGBNBT_I16,0,1,'R',0,0, NBT_I16,0,1,'G',0,0, NBT_I16,0,1,'B',0,0 , | |||
990 | NBT_END, | |||
991 | NBT_DICT, 0,3, 'F','o','g', CW_META_RGBNBT_I16,0,1,'R',0,0, NBT_I16,0,1,'G',0,0, NBT_I16,0,1,'B',0,0 , | |||
992 | NBT_END, | |||
993 | NBT_DICT, 0,7, 'A','m','b','i','e','n','t', CW_META_RGBNBT_I16,0,1,'R',0,0, NBT_I16,0,1,'G',0,0, NBT_I16,0,1,'B',0,0 , | |||
994 | NBT_END, | |||
995 | NBT_DICT, 0,8, 'S','u','n','l','i','g','h','t', CW_META_RGBNBT_I16,0,1,'R',0,0, NBT_I16,0,1,'G',0,0, NBT_I16,0,1,'B',0,0 , | |||
996 | NBT_END, | |||
997 | NBT_END, | |||
998 | NBT_DICT, 0,16, 'E','n','v','M','a','p','A','p','p','e','a','r','a','n','c','e', | |||
999 | NBT_I8, 0,9, 'S','i','d','e','B','l','o','c','k', 0, | |||
1000 | NBT_I8, 0,9, 'E','d','g','e','B','l','o','c','k', 0, | |||
1001 | NBT_I16, 0,9, 'S','i','d','e','L','e','v','e','l', 0,0, | |||
1002 | NBT_STR, 0,10, 'T','e','x','t','u','r','e','U','R','L', 0,0, | |||
1003 | }; | |||
1004 | static cc_uint8 cw_meta_defs[19] = { | |||
1005 | NBT_DICT, 0,16, 'B','l','o','c','k','D','e','f','i','n','i','t','i','o','n','s', | |||
1006 | }; | |||
1007 | static cc_uint8 cw_meta_def[189] = { | |||
1008 | NBT_DICT, 0,9, 'B','l','o','c','k','\0','\0','\0','\0', | |||
1009 | NBT_I8, 0,2, 'I','D', 0, | |||
1010 | /* It would be have been better to just change ID to be a I16 */ | |||
1011 | /* Unfortunately this isn't backwards compatible with ClassicalSharp */ | |||
1012 | NBT_I16, 0,3, 'I','D','2', 0,0, | |||
1013 | NBT_I8, 0,11, 'C','o','l','l','i','d','e','T','y','p','e', 0, | |||
1014 | NBT_F32, 0,5, 'S','p','e','e','d', 0,0,0,0, | |||
1015 | /* Ugly hack for supporting texture IDs over 255 */ | |||
1016 | /* First 6 elements are lower 8 bits, next 6 are upper 8 bits */ | |||
1017 | NBT_I8S, 0,8, 'T','e','x','t','u','r','e','s', 0,0,0,12, 0,0,0,0,0,0, 0,0,0,0,0,0, | |||
1018 | NBT_I8, 0,14, 'T','r','a','n','s','m','i','t','s','L','i','g','h','t', 0, | |||
1019 | NBT_I8, 0,9, 'W','a','l','k','S','o','u','n','d', 0, | |||
1020 | NBT_I8, 0,10, 'F','u','l','l','B','r','i','g','h','t', 0, | |||
1021 | NBT_I8, 0,5, 'S','h','a','p','e', 0, | |||
1022 | NBT_I8, 0,9, 'B','l','o','c','k','D','r','a','w', 0, | |||
1023 | NBT_I8S, 0,3, 'F','o','g', 0,0,0,4, 0,0,0,0, | |||
1024 | NBT_I8S, 0,6, 'C','o','o','r','d','s', 0,0,0,6, 0,0,0,0,0,0, | |||
1025 | NBT_STR, 0,4, 'N','a','m','e', 0,0, | |||
1026 | }; | |||
1027 | static cc_uint8 cw_end[4] = { | |||
1028 | NBT_END, | |||
1029 | NBT_END, | |||
1030 | NBT_END, | |||
1031 | NBT_END, | |||
1032 | }; | |||
1033 | ||||
1034 | ||||
1035 | static cc_result Cw_WriteBockDef(struct Stream* stream, int b) { | |||
1036 | cc_uint8 tmp[512]; | |||
1037 | cc_string name; | |||
1038 | int len; | |||
1039 | ||||
1040 | cc_bool sprite = Blocks.Draw[b] == DRAW_SPRITE; | |||
1041 | union IntAndFloat speed; | |||
1042 | TextureLoc tex; | |||
1043 | cc_uint8 fog; | |||
1044 | PackedCol col; | |||
1045 | Vec3 minBB, maxBB; | |||
1046 | ||||
1047 | Mem_Copy(tmp, cw_meta_def, sizeof(cw_meta_def)); | |||
1048 | { | |||
1049 | /* Hacky unique tag name for each by using hex of block */ | |||
1050 | name = String_Init((char*)&tmp[8], 0, 4); | |||
1051 | String_AppendHex(&name, b >> 8); | |||
1052 | String_AppendHex(&name, b); | |||
1053 | ||||
1054 | tmp[17] = b; | |||
1055 | Stream_SetU16_BE(&tmp[24], b); | |||
1056 | ||||
1057 | tmp[40] = Blocks.Collide[b]; | |||
1058 | speed.f = Blocks.SpeedMultiplier[b]; | |||
1059 | Stream_SetU32_BE(&tmp[49], speed.u); | |||
1060 | ||||
1061 | /* Originally only up to 256 textures were supported, which used up 6 bytes total */ | |||
1062 | /* Later, support for more textures was added, which requires 2 bytes per texture */ | |||
1063 | /* For backwards compatibility, the lower byte of each texture is */ | |||
1064 | /* written into first 6 bytes, then higher byte into next 6 bytes */ | |||
1065 | tex = Block_Tex(b, FACE_YMAX)Blocks.Textures[(b) * FACE_COUNT + (FACE_YMAX)]; tmp[68] = (cc_uint8)tex; tmp[74] = (cc_uint8)(tex >> 8); | |||
1066 | tex = Block_Tex(b, FACE_YMIN)Blocks.Textures[(b) * FACE_COUNT + (FACE_YMIN)]; tmp[69] = (cc_uint8)tex; tmp[75] = (cc_uint8)(tex >> 8); | |||
1067 | tex = Block_Tex(b, FACE_XMIN)Blocks.Textures[(b) * FACE_COUNT + (FACE_XMIN)]; tmp[70] = (cc_uint8)tex; tmp[76] = (cc_uint8)(tex >> 8); | |||
1068 | tex = Block_Tex(b, FACE_XMAX)Blocks.Textures[(b) * FACE_COUNT + (FACE_XMAX)]; tmp[71] = (cc_uint8)tex; tmp[77] = (cc_uint8)(tex >> 8); | |||
1069 | tex = Block_Tex(b, FACE_ZMIN)Blocks.Textures[(b) * FACE_COUNT + (FACE_ZMIN)]; tmp[72] = (cc_uint8)tex; tmp[78] = (cc_uint8)(tex >> 8); | |||
1070 | tex = Block_Tex(b, FACE_ZMAX)Blocks.Textures[(b) * FACE_COUNT + (FACE_ZMAX)]; tmp[73] = (cc_uint8)tex; tmp[79] = (cc_uint8)(tex >> 8); | |||
1071 | ||||
1072 | tmp[97] = Blocks.BlocksLight[b] ? 0 : 1; | |||
1073 | tmp[110] = Blocks.DigSounds[b]; | |||
1074 | tmp[124] = Blocks.FullBright[b] ? 1 : 0; | |||
1075 | tmp[133] = sprite ? 0 : (cc_uint8)(Blocks.MaxBB[b].Y * 16); | |||
1076 | tmp[146] = sprite ? Blocks.SpriteOffset[b] : Blocks.Draw[b]; | |||
1077 | ||||
1078 | fog = (cc_uint8)(128 * Blocks.FogDensity[b] - 1); | |||
1079 | col = Blocks.FogCol[b]; | |||
1080 | tmp[157] = Blocks.FogDensity[b] ? fog : 0; | |||
1081 | tmp[158] = PackedCol_R(col)((cc_uint8)(col >> 0)); tmp[159] = PackedCol_G(col)((cc_uint8)(col >> 8)); tmp[160] = PackedCol_B(col)((cc_uint8)(col >> 16)); | |||
1082 | ||||
1083 | minBB = Blocks.MinBB[b]; maxBB = Blocks.MaxBB[b]; | |||
1084 | tmp[174] = (cc_uint8)(minBB.X * 16); tmp[175] = (cc_uint8)(minBB.Y * 16); tmp[176] = (cc_uint8)(minBB.Z * 16); | |||
1085 | tmp[177] = (cc_uint8)(maxBB.X * 16); tmp[178] = (cc_uint8)(maxBB.Y * 16); tmp[179] = (cc_uint8)(maxBB.Z * 16); | |||
1086 | } | |||
1087 | ||||
1088 | name = Block_UNSAFE_GetName(b); | |||
1089 | len = Cw_WriteEndString(&tmp[187], &name); | |||
1090 | return Stream_Write(stream, tmp, sizeof(cw_meta_def) + len); | |||
1091 | } | |||
1092 | ||||
1093 | cc_result Cw_Save(struct Stream* stream) { | |||
1094 | cc_uint8 tmp[768]; | |||
1095 | PackedCol col; | |||
1096 | struct LocalPlayer* p = &LocalPlayer_Instance; | |||
1097 | cc_result res; | |||
1098 | int b, len; | |||
1099 | ||||
1100 | Mem_Copy(tmp, cw_begin, sizeof(cw_begin)); | |||
1101 | { | |||
1102 | Mem_Copy(&tmp[43], World.Uuid, WORLD_UUID_LEN16); | |||
1103 | Stream_SetU16_BE(&tmp[63], World.Width); | |||
1104 | Stream_SetU16_BE(&tmp[69], World.Height); | |||
1105 | Stream_SetU16_BE(&tmp[75], World.Length); | |||
1106 | Stream_SetU32_BE(&tmp[127], World.Volume); | |||
1107 | ||||
1108 | /* TODO: Maybe keep real spawn too? */ | |||
1109 | Stream_SetU16_BE(&tmp[89], (cc_uint16)p->Base.Position.X); | |||
1110 | Stream_SetU16_BE(&tmp[95], (cc_uint16)p->Base.Position.Y); | |||
1111 | Stream_SetU16_BE(&tmp[101], (cc_uint16)p->Base.Position.Z); | |||
1112 | tmp[107] = Math_Deg2Packed(p->SpawnYaw)((cc_uint8)((p->SpawnYaw) * 256.0f / 360.0f)); | |||
1113 | tmp[112] = Math_Deg2Packed(p->SpawnPitch)((cc_uint8)((p->SpawnPitch) * 256.0f / 360.0f)); | |||
1114 | } | |||
1115 | if ((res = Stream_Write(stream, tmp, sizeof(cw_begin)))) return res; | |||
1116 | if ((res = Stream_Write(stream, World.Blocks, World.Volume))) return res; | |||
1117 | ||||
1118 | if (World.Blocks != World.Blocks2) { | |||
1119 | Mem_Copy(tmp, cw_map2, sizeof(cw_map2)); | |||
1120 | Stream_SetU32_BE(&tmp[14], World.Volume); | |||
1121 | ||||
1122 | if ((res = Stream_Write(stream, tmp, sizeof(cw_map2)))) return res; | |||
1123 | if ((res = Stream_Write(stream, World.Blocks2, World.Volume))) return res; | |||
1124 | } | |||
1125 | ||||
1126 | Mem_Copy(tmp, cw_meta_cpe, sizeof(cw_meta_cpe)); | |||
1127 | { | |||
1128 | Stream_SetU16_BE(&tmp[44], (cc_uint16)(LocalPlayer_Instance.ReachDistance * 32)); | |||
1129 | tmp[78] = Env.Weather; | |||
1130 | ||||
1131 | col = Env.SkyCol; tmp[103] = PackedCol_R(col)((cc_uint8)(col >> 0)); tmp[109] = PackedCol_G(col)((cc_uint8)(col >> 8)); tmp[115] = PackedCol_B(col)((cc_uint8)(col >> 16)); | |||
1132 | col = Env.CloudsCol; tmp[130] = PackedCol_R(col)((cc_uint8)(col >> 0)); tmp[136] = PackedCol_G(col)((cc_uint8)(col >> 8)); tmp[142] = PackedCol_B(col)((cc_uint8)(col >> 16)); | |||
1133 | col = Env.FogCol; tmp[155] = PackedCol_R(col)((cc_uint8)(col >> 0)); tmp[161] = PackedCol_G(col)((cc_uint8)(col >> 8)); tmp[167] = PackedCol_B(col)((cc_uint8)(col >> 16)); | |||
1134 | col = Env.ShadowCol; tmp[184] = PackedCol_R(col)((cc_uint8)(col >> 0)); tmp[190] = PackedCol_G(col)((cc_uint8)(col >> 8)); tmp[196] = PackedCol_B(col)((cc_uint8)(col >> 16)); | |||
1135 | col = Env.SunCol; tmp[214] = PackedCol_R(col)((cc_uint8)(col >> 0)); tmp[220] = PackedCol_G(col)((cc_uint8)(col >> 8)); tmp[226] = PackedCol_B(col)((cc_uint8)(col >> 16)); | |||
1136 | ||||
1137 | tmp[260] = (BlockRaw)Env.SidesBlock; | |||
1138 | tmp[273] = (BlockRaw)Env.EdgeBlock; | |||
1139 | Stream_SetU16_BE(&tmp[286], Env.EdgeHeight); | |||
1140 | } | |||
1141 | len = Cw_WriteEndString(&tmp[301], &TexturePack_Url); | |||
1142 | if ((res = Stream_Write(stream, tmp, sizeof(cw_meta_cpe) + len))) return res; | |||
1143 | ||||
1144 | if ((res = Stream_Write(stream, cw_meta_defs, sizeof(cw_meta_defs)))) return res; | |||
1145 | /* Write block definitions in reverse order so that software that only reads byte 'ID' */ | |||
1146 | /* still loads correct first 256 block defs when saving a map with over 256 block defs */ | |||
1147 | for (b = BLOCK_MAX_DEFINED; b >= 1; b--) { | |||
1148 | if (!Block_IsCustomDefined(b)) continue; | |||
1149 | if ((res = Cw_WriteBockDef(stream, b))) return res; | |||
1150 | } | |||
1151 | return Stream_Write(stream, cw_end, sizeof(cw_end)); | |||
1152 | } | |||
1153 | ||||
1154 | ||||
1155 | /*########################################################################################################################* | |||
1156 | *---------------------------------------------------Schematic export------------------------------------------------------* | |||
1157 | *#########################################################################################################################*/ | |||
1158 | ||||
1159 | static cc_uint8 sc_begin[78] = { | |||
1160 | NBT_DICT, 0,9, 'S','c','h','e','m','a','t','i','c', | |||
1161 | NBT_STR, 0,9, 'M','a','t','e','r','i','a','l','s', 0,7, 'C','l','a','s','s','i','c', | |||
1162 | NBT_I16, 0,5, 'W','i','d','t','h', 0,0, | |||
1163 | NBT_I16, 0,6, 'H','e','i','g','h','t', 0,0, | |||
1164 | NBT_I16, 0,6, 'L','e','n','g','t','h', 0,0, | |||
1165 | NBT_I8S, 0,6, 'B','l','o','c','k','s', 0,0,0,0, | |||
1166 | }; | |||
1167 | static cc_uint8 sc_data[11] = { | |||
1168 | NBT_I8S, 0,4, 'D','a','t','a', 0,0,0,0, | |||
1169 | }; | |||
1170 | static cc_uint8 sc_end[37] = { | |||
1171 | NBT_LIST, 0,8, 'E','n','t','i','t','i','e','s', NBT_DICT, 0,0,0,0, | |||
1172 | NBT_LIST, 0,12, 'T','i','l','e','E','n','t','i','t','i','e','s', NBT_DICT, 0,0,0,0, | |||
1173 | NBT_END, | |||
1174 | }; | |||
1175 | ||||
1176 | cc_result Schematic_Save(struct Stream* stream) { | |||
1177 | cc_uint8 tmp[256], chunk[8192] = { 0 }; | |||
1178 | cc_result res; | |||
1179 | int i; | |||
1180 | ||||
1181 | Mem_Copy(tmp, sc_begin, sizeof(sc_begin)); | |||
1182 | { | |||
1183 | Stream_SetU16_BE(&tmp[41], World.Width); | |||
1184 | Stream_SetU16_BE(&tmp[52], World.Height); | |||
1185 | Stream_SetU16_BE(&tmp[63], World.Length); | |||
1186 | Stream_SetU32_BE(&tmp[74], World.Volume); | |||
1187 | } | |||
1188 | if ((res = Stream_Write(stream, tmp, sizeof(sc_begin)))) return res; | |||
1189 | if ((res = Stream_Write(stream, World.Blocks, World.Volume))) return res; | |||
1190 | ||||
1191 | Mem_Copy(tmp, sc_data, sizeof(sc_data)); | |||
1192 | { | |||
1193 | Stream_SetU32_BE(&tmp[7], World.Volume); | |||
1194 | } | |||
1195 | if ((res = Stream_Write(stream, tmp, sizeof(sc_data)))) return res; | |||
1196 | ||||
1197 | for (i = 0; i < World.Volume; i += sizeof(chunk)) { | |||
1198 | int count = World.Volume - i; count = min(count, sizeof(chunk))((count) < (sizeof(chunk)) ? (count) : (sizeof(chunk))); | |||
1199 | if ((res = Stream_Write(stream, chunk, count))) return res; | |||
1200 | } | |||
1201 | return Stream_Write(stream, sc_end, sizeof(sc_end)); | |||
1202 | } |