| 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 | } |