diff options
author | WlodekM <[email protected]> | 2024-06-16 10:35:45 +0300 |
---|---|---|
committer | WlodekM <[email protected]> | 2024-06-16 10:35:45 +0300 |
commit | abef6da56913f1c55528103e60a50451a39628b1 (patch) | |
tree | b3c8092471ecbb73e568cd0d336efa0e7871ee8d /src/Formats.c |
initial commit
Diffstat (limited to 'src/Formats.c')
-rw-r--r-- | src/Formats.c | 1897 |
1 files changed, 1897 insertions, 0 deletions
diff --git a/src/Formats.c b/src/Formats.c new file mode 100644 index 0000000..2a86fd4 --- /dev/null +++ b/src/Formats.c @@ -0,0 +1,1897 @@ +#include "Formats.h" +#include "String.h" +#include "World.h" +#include "Deflate.h" +#include "Block.h" +#include "Entity.h" +#include "Platform.h" +#include "ExtMath.h" +#include "Logger.h" +#include "Game.h" +#include "Server.h" +#include "Event.h" +#include "Funcs.h" +#include "Errors.h" +#include "Stream.h" +#include "Chat.h" +#include "TexturePack.h" +#include "Utils.h" + +#ifdef CC_BUILD_FILESYSTEM +static struct LocationUpdate* spawn_point; +static struct MapImporter* imp_head; +static struct MapImporter* imp_tail; + + +/*########################################################################################################################* +*--------------------------------------------------------General----------------------------------------------------------* +*#########################################################################################################################*/ +static cc_result Map_ReadBlocks(struct Stream* stream) { + World.Volume = World.Width * World.Length * World.Height; + World.Blocks = (BlockRaw*)Mem_TryAlloc(World.Volume, 1); + + if (!World.Blocks) return ERR_OUT_OF_MEMORY; + return Stream_Read(stream, World.Blocks, World.Volume); +} + +static cc_result Map_SkipGZipHeader(struct Stream* stream) { + struct GZipHeader gzHeader; + cc_result res; + GZipHeader_Init(&gzHeader); + + while (!gzHeader.done) { + if ((res = GZipHeader_Read(stream, &gzHeader))) return res; + } + return 0; +} + +void MapImporter_Register(struct MapImporter* imp) { + LinkedList_Append(imp, imp_head, imp_tail); +} + +struct MapImporter* MapImporter_Find(const cc_string* path) { + struct MapImporter* imp; + cc_string ext; + + for (imp = imp_head; imp; imp = imp->next) + { + ext = String_FromReadonly(imp->fileExt); + if (String_CaselessEnds(path, &ext)) return imp; + } + return NULL; +} + +cc_result Map_LoadFrom(const cc_string* path) { + cc_string relPath, fileName, fileExt; + struct LocationUpdate update = { 0 }; + struct MapImporter* imp; + struct Stream stream; + cc_result res; + Game_Reset(); + + spawn_point = &update; + res = Stream_OpenFile(&stream, path); + if (res) { Logger_SysWarn2(res, "opening", path); return res; } + + imp = MapImporter_Find(path); + if (!imp) { + res = ERR_NOT_SUPPORTED; + } else if ((res = imp->import(&stream))) { + World_Reset(); + } + + /* No point logging error for closing readonly file */ + (void)stream.Close(&stream); + if (res) Logger_SysWarn2(res, "decoding", path); + + World_SetNewMap(World.Blocks, World.Width, World.Height, World.Length); + if (!spawn_point) LocalPlayer_CalcDefaultSpawn(Entities.CurPlayer, &update); + LocalPlayers_MoveToSpawn(&update); + + relPath = *path; + Utils_UNSAFE_GetFilename(&relPath); + String_UNSAFE_Separate(&relPath, '.', &fileName, &fileExt); + String_Copy(&World.Name, &fileName); + return res; +} + + +/*########################################################################################################################* +*--------------------------------------------------MCSharp level Format---------------------------------------------------* +*#########################################################################################################################*/ +#define LVL_CUSTOMTILE 163 +#define LVL_CHUNKSIZE 16 +/* MCSharp* format is a GZIP compressed binary map format. All metadata is discarded. + U16 "Identifier" (must be 1874) + U16 "Width", "Length", "Height" + U16 "SpawnX", "SpawnZ", "SpawnY" + U8 "Yaw", "Pitch" + U16 "Build permissions" (ignored) + U8* "Blocks" + + -- this data is only in MCGalaxy maps + U8 "Identifier" (0xBD for 'block definitions', i.e. custom blocks) + U8* "Data" (16x16x16 sparsely allocated chunks) +}*/ + +static const cc_uint8 Lvl_table[256] = { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, + 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, + 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, + 64, 65, 0, 0, 0, 0, 39, 36, 36, 10, 46, 21, 22, 22, 22, 22, + 4, 0, 22, 21, 0, 22, 23, 24, 22, 26, 27, 28, 30, 31, 32, 33, + 34, 35, 36, 22, 20, 49, 45, 1, 4, 0, 9, 11, 4, 19, 5, 17, + 10, 49, 20, 1, 18, 12, 5, 25, 46, 44, 17, 49, 20, 1, 18, 12, + 5, 25, 36, 34, 0, 9, 11, 46, 44, 0, 9, 11, 8, 10, 22, 27, + 22, 8, 10, 28, 17, 49, 20, 1, 18, 12, 5, 25, 46, 44, 11, 9, + 0, 9, 11, 163, 0, 0, 9, 11, 0, 0, 0, 0, 0, 0, 0, 28, + 22, 21, 11, 0, 0, 0, 46, 46, 10, 10, 46, 20, 41, 42, 11, 9, + 0, 8, 10, 10, 8, 0, 22, 22, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 21, 10, 0, 0, 0, 0, 0, 22, 22, 42, 3, 2, 29, + 47, 0, 0, 0, 0, 0, 27, 46, 48, 24, 22, 36, 34, 8, 10, 21, + 29, 22, 10, 22, 22, 41, 19, 35, 21, 29, 49, 34, 16, 41, 0, 22 +}; + +static cc_result Lvl_ReadCustomBlocks(struct Stream* stream) { + cc_uint8 chunk[LVL_CHUNKSIZE * LVL_CHUNKSIZE * LVL_CHUNKSIZE]; + cc_uint8 hasCustom; + int baseIndex, index, xx, yy, zz; + cc_result res; + int x, y, z, i; + + /* skip bounds checks when we know chunk is entirely inside map */ + int adjWidth = World.Width & ~0x0F; + int adjHeight = World.Height & ~0x0F; + int adjLength = World.Length & ~0x0F; + + for (y = 0; y < World.Height; y += LVL_CHUNKSIZE) { + for (z = 0; z < World.Length; z += LVL_CHUNKSIZE) { + for (x = 0; x < World.Width; x += LVL_CHUNKSIZE) { + + if ((res = stream->ReadU8(stream, &hasCustom))) return res; + if (hasCustom != 1) continue; + if ((res = Stream_Read(stream, chunk, sizeof(chunk)))) return res; + baseIndex = World_Pack(x, y, z); + + if ((x + LVL_CHUNKSIZE) <= adjWidth && (y + LVL_CHUNKSIZE) <= adjHeight && (z + LVL_CHUNKSIZE) <= adjLength) { + for (i = 0; i < sizeof(chunk); i++) { + xx = i & 0xF; yy = (i >> 8) & 0xF; zz = (i >> 4) & 0xF; + + index = baseIndex + World_Pack(xx, yy, zz); + World.Blocks[index] = World.Blocks[index] == LVL_CUSTOMTILE ? chunk[i] : World.Blocks[index]; + } + } else { + for (i = 0; i < sizeof(chunk); i++) { + xx = i & 0xF; yy = (i >> 8) & 0xF; zz = (i >> 4) & 0xF; + if ((x + xx) >= World.Width || (y + yy) >= World.Height || (z + zz) >= World.Length) continue; + + index = baseIndex + World_Pack(xx, yy, zz); + World.Blocks[index] = World.Blocks[index] == LVL_CUSTOMTILE ? chunk[i] : World.Blocks[index]; + } + } + } + } + } + return 0; +} + +/* Imports a world from a .lvl MCSharp server map file */ +/* Used by MCSharp/MCLawl/MCForge/MCDzienny/MCGalaxy */ +static cc_result Lvl_Load(struct Stream* stream) { + cc_uint8 header[18]; + cc_uint8* blocks; + cc_uint8 section; + cc_result res; + int i; + + struct Stream compStream; + struct InflateState state; + Inflate_MakeStream2(&compStream, &state, stream); + + if ((res = Map_SkipGZipHeader(stream))) return res; + if ((res = Stream_Read(&compStream, header, sizeof(header)))) return res; + if (Stream_GetU16_LE(&header[0]) != 1874) return LVL_ERR_VERSION; + + World.Width = Stream_GetU16_LE(&header[2]); + World.Length = Stream_GetU16_LE(&header[4]); + World.Height = Stream_GetU16_LE(&header[6]); + + spawn_point->flags = LU_HAS_POS | LU_HAS_YAW | LU_HAS_PITCH; + spawn_point->pos.x = Stream_GetU16_LE(&header[8]); + spawn_point->pos.z = Stream_GetU16_LE(&header[10]); + spawn_point->pos.y = Stream_GetU16_LE(&header[12]); + spawn_point->yaw = Math_Packed2Deg(header[14]); + spawn_point->pitch = Math_Packed2Deg(header[15]); + /* (2) pervisit, perbuild permissions */ + + if ((res = Map_ReadBlocks(&compStream))) return res; + blocks = World.Blocks; + /* Bulk convert 4 blocks at once */ + for (i = 0; i < (World.Volume & ~3); i += 4) { + *blocks = Lvl_table[*blocks]; blocks++; + *blocks = Lvl_table[*blocks]; blocks++; + *blocks = Lvl_table[*blocks]; blocks++; + *blocks = Lvl_table[*blocks]; blocks++; + } + for (; i < World.Volume; i++) { + *blocks = Lvl_table[*blocks]; blocks++; + } + + /* 0xBD section type is not present in older .lvl files */ + res = compStream.ReadU8(&compStream, §ion); + if (res == ERR_END_OF_STREAM) return 0; + + if (res) return res; + /* Unrecognised section type, stop reading */ + if (section != 0xBD) return 0; + + res = Lvl_ReadCustomBlocks(&compStream); + /* At least one map out there has a corrupted 0xBD section */ + if (res == ERR_END_OF_STREAM) { + Chat_AddRaw("&cEnd of stream reading .lvl custom blocks section"); + Chat_AddRaw("&c Some blocks may therefore appear incorrectly"); + res = 0; + } + return res; +} + + +/*########################################################################################################################* +*----------------------------------------------------fCraft map format----------------------------------------------------* +*#########################################################################################################################*/ +/* fCraft* format is a binary map format. All metadata is discarded. + U32 "Identifier" (must be FC2AF40) + U8 "Revision" (only '13' supported) + U16 "Width", "Height", "Length" + U32 "SpawnX", "SpawnY", "SpawnZ" + U8 "Yaw", "Pitch" + U32 "DateModified", "DateCreated" (ignored) + U8* "UUID" + U8 "Layers" (only maps with 1 layer supported) + U8* "LayersInfo" (ignored, assumes only layer is map blocks) + U32 "MetaCount" + METADATA { STR "Group", "Key", "Value" } + U8* "Blocks" +}*/ +static cc_result Fcm_ReadString(struct Stream* stream) { + cc_uint8 data[2]; + int len; + cc_result res; + + if ((res = Stream_Read(stream, data, sizeof(data)))) return res; + len = Stream_GetU16_LE(data); + + return stream->Skip(stream, len); +} + +/* Imports a world from a .fcm fCraft server map file (v3 only) */ +/* Used by fCraft/800Craft/LegendCraft/ProCraft */ +static cc_result Fcm_Load(struct Stream* stream) { + cc_uint8 header[79]; + cc_result res; + int i, count; + + struct Stream compStream; + struct InflateState state; + Inflate_MakeStream2(&compStream, &state, stream); + + if ((res = Stream_Read(stream, header, sizeof(header)))) return res; + if (Stream_GetU32_LE(&header[0]) != 0x0FC2AF40UL) return FCM_ERR_IDENTIFIER; + if (header[4] != 13) return FCM_ERR_REVISION; + + World.Width = Stream_GetU16_LE(&header[5]); + World.Height = Stream_GetU16_LE(&header[7]); + World.Length = Stream_GetU16_LE(&header[9]); + + spawn_point->flags = LU_HAS_POS | LU_HAS_YAW | LU_HAS_PITCH; + spawn_point->pos.x = ((int)Stream_GetU32_LE(&header[11])) / 32.0f; + spawn_point->pos.y = ((int)Stream_GetU32_LE(&header[15])) / 32.0f; + spawn_point->pos.z = ((int)Stream_GetU32_LE(&header[19])) / 32.0f; + spawn_point->yaw = Math_Packed2Deg(header[23]); + spawn_point->pitch = Math_Packed2Deg(header[24]); + + /* header[25] (4) date modified */ + /* header[29] (4) date created */ + Mem_Copy(&World.Uuid, &header[33], WORLD_UUID_LEN); + /* header[49] (26) layer index */ + count = (int)Stream_GetU32_LE(&header[75]); + + /* header isn't compressed, rest of data is though */ + for (i = 0; i < count; i++) { + if ((res = Fcm_ReadString(&compStream))) return res; /* Group */ + if ((res = Fcm_ReadString(&compStream))) return res; /* Key */ + if ((res = Fcm_ReadString(&compStream))) return res; /* Value */ + } + + return Map_ReadBlocks(&compStream); +} + + +/*########################################################################################################################* +*---------------------------------------------------------NBTFile---------------------------------------------------------* +*#########################################################################################################################*/ +enum NbtTagType { + NBT_END, NBT_I8, NBT_I16, NBT_I32, NBT_I64, NBT_F32, + NBT_F64, NBT_I8S, NBT_STR, NBT_LIST, NBT_DICT +}; + +#define NBT_SMALL_SIZE STRING_SIZE +#define NBT_STRING_SIZE STRING_SIZE + +#define NbtTag_IsSmall(tag) ((tag)->dataSize <= NBT_SMALL_SIZE) +#define IsTag(tag, tagName) (String_CaselessEqualsConst(&tag->name, tagName)) +struct NbtTag; + +struct NbtTag { + struct NbtTag* parent; + cc_uint8 type; + cc_string name; + cc_uint32 dataSize; /* size of data for arrays */ + + union { + cc_uint8 u8; + cc_int16 i16; + cc_uint16 u16; + cc_int32 i32; + cc_uint32 u32; + float f32; + cc_uint8 small[NBT_SMALL_SIZE]; + cc_uint8* big; /* malloc for big byte arrays */ + struct { cc_string text; char buffer[STRING_SIZE * 2]; } str; + } value; + char _nameBuffer[NBT_STRING_SIZE]; + cc_result result; + int listIndex; +}; + +static cc_uint8 NbtTag_U8(struct NbtTag* tag) { + if (tag->type == NBT_I8) return tag->value.u8; + + tag->result = NBT_ERR_EXPECTED_I8; + return 0; +} + +static cc_int16 NbtTag_I16(struct NbtTag* tag) { + if (tag->type == NBT_I16) return tag->value.i16; + if (tag->type == NBT_I8) return tag->value.u8; + + tag->result = NBT_ERR_EXPECTED_I16; + return 0; +} + +static cc_uint16 NbtTag_U16(struct NbtTag* tag) { + if (tag->type == NBT_I16) return tag->value.u16; + if (tag->type == NBT_I8) return tag->value.u8; + + tag->result = NBT_ERR_EXPECTED_I16; + return 0; +} + +static int NbtTag_I32(struct NbtTag* tag) { + if (tag->type == NBT_I32) return tag->value.i32; + if (tag->type == NBT_I16) return tag->value.i16; + if (tag->type == NBT_I8) return tag->value.u8; + + tag->result = NBT_ERR_EXPECTED_I32; + return 0; +} + +static float NbtTag_F32(struct NbtTag* tag) { + if (tag->type == NBT_F32) return tag->value.f32; + + tag->result = NBT_ERR_EXPECTED_F32; + return 0; +} + +static cc_uint8* NbtTag_U8_Array(struct NbtTag* tag, int minSize) { + if (tag->type != NBT_I8S) { tag->result = NBT_ERR_EXPECTED_ARR; return NULL; } + if (tag->dataSize < minSize) { tag->result = NBT_ERR_ARR_TOO_SMALL; return NULL; } + + return NbtTag_IsSmall(tag) ? tag->value.small : tag->value.big; +} + +static cc_string NbtTag_String(struct NbtTag* tag) { + if (tag->type == NBT_STR) return tag->value.str.text; + + tag->result = NBT_ERR_EXPECTED_STR; + return String_Empty; +} + +static cc_result Nbt_ReadString(struct Stream* stream, cc_string* str) { + cc_uint8 buffer[NBT_STRING_SIZE * 4]; + int len; + cc_result res; + + if ((res = Stream_Read(stream, buffer, 2))) return res; + len = Stream_GetU16_BE(buffer); + + if (len > Array_Elems(buffer)) return CW_ERR_STRING_LEN; + if ((res = Stream_Read(stream, buffer, len))) return res; + + String_AppendUtf8(str, buffer, len); + return 0; +} + +typedef void (*Nbt_Callback)(struct NbtTag* tag); +static cc_result Nbt_ReadTag(cc_uint8 typeId, cc_bool readTagName, struct Stream* stream, + struct NbtTag* parent, Nbt_Callback callback, int listIndex) { + struct NbtTag tag; + cc_uint8 childType; + cc_uint8 tmp[5]; + cc_result res; + cc_uint32 i, count; + + if (typeId == NBT_END) return 0; + tag.type = typeId; + tag.parent = parent; + tag.dataSize = 0; + tag.listIndex = listIndex; + String_InitArray(tag.name, tag._nameBuffer); + + if (readTagName) { + res = Nbt_ReadString(stream, &tag.name); + if (res) return res; + } + + switch (typeId) { + case NBT_I8: + res = stream->ReadU8(stream, &tag.value.u8); + break; + case NBT_I16: + res = Stream_Read(stream, tmp, 2); + tag.value.u16 = Stream_GetU16_BE(tmp); + break; + case NBT_I32: + case NBT_F32: + res = Stream_ReadU32_BE(stream, &tag.value.u32); + break; + case NBT_I64: + case NBT_F64: + res = stream->Skip(stream, 8); + break; /* (8) data */ + + case NBT_I8S: + if ((res = Stream_ReadU32_BE(stream, &tag.dataSize))) break; + + if (NbtTag_IsSmall(&tag)) { + res = Stream_Read(stream, tag.value.small, tag.dataSize); + } else { + tag.value.big = (cc_uint8*)Mem_TryAlloc(tag.dataSize, 1); + if (!tag.value.big) return ERR_OUT_OF_MEMORY; + + res = Stream_Read(stream, tag.value.big, tag.dataSize); + if (res) Mem_Free(tag.value.big); + } + break; + case NBT_STR: + String_InitArray(tag.value.str.text, tag.value.str.buffer); + res = Nbt_ReadString(stream, &tag.value.str.text); + break; + + case NBT_LIST: + if ((res = Stream_Read(stream, tmp, 5))) break; + childType = tmp[0]; + count = Stream_GetU32_BE(&tmp[1]); + + for (i = 0; i < count; i++) { + res = Nbt_ReadTag(childType, false, stream, &tag, callback, i); + if (res) break; + } + break; + + case NBT_DICT: + for (;;) { + if ((res = stream->ReadU8(stream, &childType))) break; + if (childType == NBT_END) break; + + res = Nbt_ReadTag(childType, true, stream, &tag, callback, 0); + if (res) break; + } + break; + + default: return NBT_ERR_UNKNOWN; + } + + if (res) return res; + tag.result = 0; + callback(&tag); + /* NOTE: callback must set DataBig to NULL, if doesn't want it to be freed */ + if (!NbtTag_IsSmall(&tag)) Mem_Free(tag.value.big); + return tag.result; +} + + +static BlockRaw* Nbt_TakeArray(struct NbtTag* tag, const char* type) { + BlockRaw* ptr; + if (NbtTag_IsSmall(tag)) { + /* Small data is stored inline in tha tag, so need to copy it out */ + ptr = (BlockRaw*)Mem_Alloc(tag->dataSize, 1, type); + Mem_Copy(ptr, tag->value.small, tag->dataSize); + } else { + ptr = tag->value.big; + tag->value.big = NULL; /* So Nbt_ReadTag doesn't call Mem_Free on the array */ + } + return ptr; +} + +static cc_result Nbt_Read(struct Stream* stream, Nbt_Callback callback) { + struct Stream compStream; + struct InflateState state; + cc_result res; + cc_uint8 tag; + + Inflate_MakeStream2(&compStream, &state, stream); + if ((res = Map_SkipGZipHeader(stream))) return res; + if ((res = compStream.ReadU8(&compStream, &tag))) return res; + + if (tag != NBT_DICT) return CW_ERR_ROOT_TAG; + return Nbt_ReadTag(NBT_DICT, true, &compStream, NULL, callback, 0); +} + + +/*########################################################################################################################* +*--------------------------------------------------------NBTWriter--------------------------------------------------------* +*#########################################################################################################################*/ +static cc_uint8* Nbt_WriteConst(cc_uint8* data, const char* text) { + int i, len = String_Length(text); + *data++ = 0; + *data++ = (cc_uint8)len; + + for (i = 0; i < len; i++) { *data++ = text[i]; } + return data; +} + +static cc_uint8* Nbt_WriteString(cc_uint8* data, const char* name, const cc_string* text) { + cc_uint8* start; int i; + + *data++ = NBT_STR; + data = Nbt_WriteConst(data, name); + start = data; + data += 2; /* length written later */ + + for (i = 0; i < text->length; i++) { + data = Convert_CP437ToUtf8(text->buffer[i], data) + data; + } + + Stream_SetU16_BE(start, (int)(data - start) - 2); + return data; +} + +static cc_uint8* Nbt_WriteDict(cc_uint8* data, const char* name) { + *data++ = NBT_DICT; + data = Nbt_WriteConst(data, name); + + return data; +} + +static cc_uint8* Nbt_WriteArray(cc_uint8* data, const char* name, int size) { + *data++ = NBT_I8S; + data = Nbt_WriteConst(data, name); + + Stream_SetU32_BE(data, size); + return data + 4; +} + +static cc_uint8* Nbt_WriteUInt8(cc_uint8* data, const char* name, cc_uint8 value) { + *data++ = NBT_I8; + data = Nbt_WriteConst(data, name); + + *data = value; + return data + 1; +} + +static cc_uint8* Nbt_WriteUInt16(cc_uint8* data, const char* name, cc_uint16 value) { + *data++ = NBT_I16; + data = Nbt_WriteConst(data, name); + + Stream_SetU16_BE(data, value); + return data + 2; +} + +static cc_uint8* Nbt_WriteInt32(cc_uint8* data, const char* name, int value) { + *data++ = NBT_I32; + data = Nbt_WriteConst(data, name); + + Stream_SetU32_BE(data, value); + return data + 4; +} + +static cc_uint8* Nbt_WriteFloat(cc_uint8* data, const char* name, float value) { + union IntAndFloat raw; + *data++ = NBT_F32; + data = Nbt_WriteConst(data, name); + + raw.f = value; + Stream_SetU32_BE(data, raw.u); + return data + 4; +} + + +/*########################################################################################################################* +*--------------------------------------------------ClassicWorld format----------------------------------------------------* +*#########################################################################################################################*/ +/* ClassicWorld is a NBT tag based map format. Tags not listed below are discarded. +COMPOUND "ClassicWorld" { + U8* "UUID" + U16 "X", "Y", "Z" + COMPOUND "Spawn" { + I16 "X", "Y", "Z" + U8 "H", "P" + } + U8* "BlockArray" (lower 8 bits, required) + U8* "BlockArray2" (upper 8 bits, optional) + COMPOUND "Metadata" { + COMPOUND "CPE" { + COMPOUND "ClickDistance" { U16 "Reach" } + COMPOUND "EnvWeatherType" { U8 "WeatherType" } + COMPOUND "EnvMapAppearance" { + U8 "SideBlock", "EdgeBlock" + I16 "SidesLevel" + STR "TextureURL" + } + COMPOUND "EnvColors" { + COMPOUND "Sky" { U16 "R", "G", "B" } + COMPOUND "Cloud" { U16 "R", "G", "B" } + COMPOUND "Fog" { U16 "R", "G", "B" } + COMPOUND "Sunlight" { U16 "R", "G", "B" } + COMPOUND "Ambient" { U16 "R", "G", "B" } + } + COMPOUND "BlockDefinitions" { + COMPOUND "Block_XYZ" { (name must start with 'Block') + U8 "ID", U16 "ID2" + STR "Name" + F32 "Speed" + U8 "CollideType", "BlockDraw" + U8 "TransmitsLight", "FullBright" + U8 "Shape" , "WalkSound" + U8* "Textures", "Fog", "Coords" + } + } + } + } +}*/ + +static void Cw_Callback_1(struct NbtTag* tag) { + if (IsTag(tag, "X")) { World.Width = NbtTag_U16(tag); return; } + if (IsTag(tag, "Y")) { World.Height = NbtTag_U16(tag); return; } + if (IsTag(tag, "Z")) { World.Length = NbtTag_U16(tag); return; } + + if (IsTag(tag, "UUID")) { + if (tag->dataSize != WORLD_UUID_LEN) { + tag->result = CW_ERR_UUID_LEN; + } else { + Mem_Copy(World.Uuid, tag->value.small, WORLD_UUID_LEN); + } + return; + } + + if (IsTag(tag, "BlockArray")) { + World.Volume = tag->dataSize; + World.Blocks = Nbt_TakeArray(tag, ".cw map blocks"); + } +#ifdef EXTENDED_BLOCKS + if (IsTag(tag, "BlockArray2")) { + World_SetMapUpper(Nbt_TakeArray(tag, ".cw map blocks2")); + } +#endif +} + +static void Cw_Callback_2(struct NbtTag* tag) { + if (IsTag(tag->parent, "MapGenerator")) { + if (IsTag(tag, "Seed")) { World.Seed = NbtTag_I32(tag); return; } + return; + } + if (!IsTag(tag->parent, "Spawn")) return; + spawn_point->flags = LU_HAS_POS | LU_HAS_YAW | LU_HAS_PITCH; + + if (IsTag(tag, "X")) { spawn_point->pos.x = NbtTag_I16(tag); return; } + if (IsTag(tag, "Y")) { spawn_point->pos.y = NbtTag_I16(tag); return; } + if (IsTag(tag, "Z")) { spawn_point->pos.z = NbtTag_I16(tag); return; } + if (IsTag(tag, "H")) { spawn_point->yaw = Math_Packed2Deg(NbtTag_U8(tag)); return; } + if (IsTag(tag, "P")) { spawn_point->pitch = Math_Packed2Deg(NbtTag_U8(tag)); return; } +} + +static BlockID cw_curID; +static int cw_colR, cw_colG, cw_colB; +static PackedCol Cw_ParseColor(PackedCol defValue) { + int r = cw_colR, g = cw_colG, b = cw_colB; + if (r > 255 || g > 255 || b > 255) return defValue; + return PackedCol_Make(r, g, b, 255); +} + +static void Cw_Callback_4(struct NbtTag* tag) { + BlockID id = cw_curID; + struct LocalPlayer* p = &LocalPlayer_Instances[0]; + + if (!IsTag(tag->parent->parent, "CPE")) return; + if (!IsTag(tag->parent->parent->parent, "Metadata")) return; + + if (IsTag(tag->parent, "ClickDistance")) { + if (IsTag(tag, "Distance")) { p->ReachDistance = NbtTag_U16(tag) / 32.0f; return; } + } + if (IsTag(tag->parent, "EnvWeatherType")) { + if (IsTag(tag, "WeatherType")) { Env.Weather = NbtTag_U8(tag); return; } + } + + if (IsTag(tag->parent, "EnvMapAppearance")) { + if (IsTag(tag, "SideBlock")) { Env.SidesBlock = NbtTag_U8(tag); return; } + if (IsTag(tag, "EdgeBlock")) { Env.EdgeBlock = NbtTag_U8(tag); return; } + if (IsTag(tag, "SideLevel")) { Env.EdgeHeight = NbtTag_I16(tag); return; } + + if (IsTag(tag, "TextureURL")) { + cc_string url = NbtTag_String(tag); + if (url.length) Server_RetrieveTexturePack(&url); + return; + } + } + + if (IsTag(tag->parent, "EnvMapAspect")) { + if (IsTag(tag, "EdgeBlock")) { Env.EdgeBlock = NbtTag_U16(tag); return; } + if (IsTag(tag, "SideBlock")) { Env.SidesBlock = NbtTag_U16(tag); return; } + if (IsTag(tag, "EdgeHeight")) { Env.EdgeHeight = NbtTag_I32(tag); return; } + if (IsTag(tag, "SidesOffset")) { Env.SidesOffset = NbtTag_I32(tag); return; } + if (IsTag(tag, "CloudsHeight")) { Env.CloudsHeight = NbtTag_I32(tag); return; } + if (IsTag(tag, "CloudsSpeed")) { Env.CloudsSpeed = NbtTag_F32(tag); return; } + if (IsTag(tag, "WeatherSpeed")) { Env.WeatherSpeed = NbtTag_F32(tag); return; } + if (IsTag(tag, "WeatherFade")) { Env.WeatherFade = NbtTag_F32(tag); return; } + if (IsTag(tag, "ExpFog")) { Env.ExpFog = NbtTag_U8(tag); return; } + if (IsTag(tag, "SkyboxHor")) { Env.SkyboxHorSpeed = NbtTag_F32(tag); return; } + if (IsTag(tag, "SkyboxVer")) { Env.SkyboxVerSpeed = NbtTag_F32(tag); return; } + } + + /* Callback for compound tag is called after all its children have been processed */ + if (IsTag(tag->parent, "EnvColors")) { + if (IsTag(tag, "Sky")) { + Env.SkyCol = Cw_ParseColor(ENV_DEFAULT_SKY_COLOR); return; + } else if (IsTag(tag, "Cloud")) { + Env.CloudsCol = Cw_ParseColor(ENV_DEFAULT_CLOUDS_COLOR); return; + } else if (IsTag(tag, "Fog")) { + Env.FogCol = Cw_ParseColor(ENV_DEFAULT_FOG_COLOR); return; + } else if (IsTag(tag, "Sunlight")) { + Env_SetSunCol(Cw_ParseColor(ENV_DEFAULT_SUN_COLOR)); return; + } else if (IsTag(tag, "Ambient")) { + Env_SetShadowCol(Cw_ParseColor(ENV_DEFAULT_SHADOW_COLOR)); return; + } else if (IsTag(tag, "Skybox")) { + Env.SkyboxCol = Cw_ParseColor(ENV_DEFAULT_SKYBOX_COLOR); return; + } + } + + if (IsTag(tag->parent, "BlockDefinitions") && Game_AllowCustomBlocks) { + static const cc_string blockStr = String_FromConst("Block"); + if (!String_CaselessStarts(&tag->name, &blockStr)) return; + + /* hack for sprite draw (can't rely on order of tags when reading) */ + if (Blocks.SpriteOffset[id] == 0) { + Blocks.SpriteOffset[id] = Blocks.Draw[id]; + Blocks.Draw[id] = DRAW_SPRITE; + } else { + Blocks.SpriteOffset[id] = 0; + } + + Block_DefineCustom(id, false); + Blocks.CanPlace[id] = true; + Blocks.CanDelete[id] = true; + Event_RaiseVoid(&BlockEvents.PermissionsChanged); + + cw_curID = 0; + } +} + +static void Cw_Callback_5(struct NbtTag* tag) { + BlockID id = cw_curID; + cc_uint8* arr; + cc_uint8 sound; + + if (!IsTag(tag->parent->parent->parent, "CPE")) return; + if (!IsTag(tag->parent->parent->parent->parent, "Metadata")) return; + + if (IsTag(tag->parent->parent, "EnvColors")) { + if (IsTag(tag, "R")) { cw_colR = NbtTag_U16(tag); return; } + if (IsTag(tag, "G")) { cw_colG = NbtTag_U16(tag); return; } + if (IsTag(tag, "B")) { cw_colB = NbtTag_U16(tag); return; } + } + + if (IsTag(tag->parent->parent, "BlockDefinitions") && Game_AllowCustomBlocks) { + if (IsTag(tag, "ID")) { cw_curID = NbtTag_U8(tag); return; } + if (IsTag(tag, "ID2")) { cw_curID = NbtTag_U16(tag); return; } + if (IsTag(tag, "CollideType")) { Blocks.Collide[id] = NbtTag_U8(tag); return; } + if (IsTag(tag, "Speed")) { Blocks.SpeedMultiplier[id] = NbtTag_F32(tag); return; } + if (IsTag(tag, "TransmitsLight")) { Blocks.BlocksLight[id] = NbtTag_U8(tag) == 0; return; } + if (IsTag(tag, "FullBright")) { Blocks.Brightness[id] = Block_ReadBrightness(NbtTag_U8(tag)); return; } + if (IsTag(tag, "BlockDraw")) { Blocks.Draw[id] = NbtTag_U8(tag); return; } + if (IsTag(tag, "Shape")) { Blocks.SpriteOffset[id] = NbtTag_U8(tag); return; } + + if (IsTag(tag, "Name")) { + cc_string name = NbtTag_String(tag); + Block_SetName(id, &name); + return; + } + + if (IsTag(tag, "Textures")) { + arr = NbtTag_U8_Array(tag, 6); + if (!arr) return; + + Block_Tex(id, FACE_YMAX) = arr[0]; Block_Tex(id, FACE_YMIN) = arr[1]; + Block_Tex(id, FACE_XMIN) = arr[2]; Block_Tex(id, FACE_XMAX) = arr[3]; + Block_Tex(id, FACE_ZMIN) = arr[4]; Block_Tex(id, FACE_ZMAX) = arr[5]; + + /* hacky way of storing upper 8 bits */ + if (tag->dataSize >= 12) { + Block_Tex(id, FACE_YMAX) |= arr[6] << 8; Block_Tex(id, FACE_YMIN) |= arr[7] << 8; + Block_Tex(id, FACE_XMIN) |= arr[8] << 8; Block_Tex(id, FACE_XMAX) |= arr[9] << 8; + Block_Tex(id, FACE_ZMIN) |= arr[10] << 8; Block_Tex(id, FACE_ZMAX) |= arr[11] << 8; + } + return; + } + + if (IsTag(tag, "WalkSound")) { + sound = NbtTag_U8(tag); + Blocks.DigSounds[id] = sound; + Blocks.StepSounds[id] = sound; + if (sound == SOUND_GLASS) Blocks.StepSounds[id] = SOUND_STONE; + return; + } + + if (IsTag(tag, "Fog")) { + arr = NbtTag_U8_Array(tag, 4); + if (!arr) return; + + Blocks.FogDensity[id] = (arr[0] + 1) / 128.0f; + /* Backwards compatibility with apps that use 0xFF to indicate no fog */ + if (arr[0] == 0 || arr[0] == 0xFF) Blocks.FogDensity[id] = 0.0f; + Blocks.FogCol[id] = PackedCol_Make(arr[1], arr[2], arr[3], 255); + return; + } + + if (IsTag(tag, "Coords")) { + arr = NbtTag_U8_Array(tag, 6); + if (!arr) return; + + Blocks.MinBB[id].x = (cc_int8)arr[0] / 16.0f; Blocks.MaxBB[id].x = (cc_int8)arr[3] / 16.0f; + Blocks.MinBB[id].y = (cc_int8)arr[1] / 16.0f; Blocks.MaxBB[id].y = (cc_int8)arr[4] / 16.0f; + Blocks.MinBB[id].z = (cc_int8)arr[2] / 16.0f; Blocks.MaxBB[id].z = (cc_int8)arr[5] / 16.0f; + return; + } + } +} + +static void Cw_Callback(struct NbtTag* tag) { + struct NbtTag* tmp = tag->parent; + int depth = 0; + while (tmp) { depth++; tmp = tmp->parent; } + + switch (depth) { + case 1: Cw_Callback_1(tag); return; + case 2: Cw_Callback_2(tag); return; + case 4: Cw_Callback_4(tag); return; + case 5: Cw_Callback_5(tag); return; + } + /* ClassicWorld -> Metadata -> CPE -> ExtName -> [values] + 0 1 2 3 4 */ +} + +/* Imports a world from a .cw ClassicWorld map file */ +/* Used by ClassiCube/ClassicalSharp */ +static cc_result Cw_Load(struct Stream* stream) { + return Nbt_Read(stream, Cw_Callback); +} + + +/*########################################################################################################################* +*-----------------------------------------------Java serialisation format-------------------------------------------------* +*#########################################################################################################################*/ +/* Rather than bothering following this, I skip a lot of the java serialisation format + Stream BlockData BlockDataTiny BlockDataLong +|--------------| |---------------| |---------------| |---------------| +| U16 Magic | |>BlockDataTiny | | TC_BLOCKDATA | | TC_BLOCKLONG | +| U16 Version | |>BlockDataLong | | U8 Size | | U32 Size | +| Content[var] | |_______________| | U8 Data[size] | | U8 Data[size] | +|______________| |_______________| |_______________| + + Content +|--------------| |--------------| +| >BlockData | | >NewString | +| >Object | | >TC_RESET | +|______________| | >TC_NULL | +| >PrevObject | +| >NewClass | +| >NewEnum | + +}*/ +enum JTypeCode { + TC_NULL = 0x70, TC_REFERENCE = 0x71, TC_CLASSDESC = 0x72, TC_OBJECT = 0x73, + TC_STRING = 0x74, TC_ARRAY = 0x75, TC_BLOCKDATA = 0x77, TC_ENDBLOCKDATA = 0x78 +}; +enum JFieldType { + JFIELD_I8 = 'B', JFIELD_F64 = 'D', JFIELD_F32 = 'F', JFIELD_I32 = 'I', JFIELD_I64 = 'J', + JFIELD_BOOL = 'Z', JFIELD_ARRAY = '[', JFIELD_OBJECT = 'L' +}; + +#define JNAME_SIZE 48 +#define SC_WRITE_METHOD 0x01 +#define SC_SERIALIZABLE 0x02 + +static cc_uint32 reference_id; +#define Java_AddReference() reference_id++; + +union JValue { + cc_uint8 U8; + cc_int32 I32; + cc_uint32 U32; + float F32; + struct { cc_uint8* Ptr; cc_uint32 Size; } Array; +}; + +struct JFieldDesc { + cc_uint8 Type; + cc_uint8 FieldName[JNAME_SIZE]; + union JValue Value; + /* "Value" field here is not accurate to how java deserialising actually works, */ + /* but easier to store here since only care about Level class values anyways */ +}; + +struct JClassDesc; +struct JClassDesc { + cc_uint8 ClassName[JNAME_SIZE]; + cc_uint8 Flags; + int FieldsCount; + struct JFieldDesc Fields[38]; + cc_uint32 Reference; + struct JClassDesc* SuperClass; + struct JClassDesc* tmp; +}; + +struct JArray { + struct JClassDesc* Desc; + cc_uint8* Data; /* for byte arrays */ + cc_uint32 Size; /* for byte arrays */ +}; + +struct JUnion { + cc_uint8 Type; + union { + cc_uint8 String[JNAME_SIZE]; /* TC_STRING */ + struct JClassDesc* Object; /* TC_OBJECT */ + struct JArray Array; /* TC_ARRAY */ + } Value; +}; + +static cc_result Java_ReadString(struct Stream* stream, cc_uint8* buffer) { + int len; + cc_result res; + + if ((res = Stream_Read(stream, buffer, 2))) return res; + len = Stream_GetU16_BE(buffer); + + Mem_Set(buffer, 0, JNAME_SIZE); + if (len > JNAME_SIZE) return JAVA_ERR_JSTRING_LEN; + return Stream_Read(stream, buffer, len); +} + + +static cc_result Java_ReadObject(struct Stream* stream, struct JUnion* object); +static cc_result Java_ReadObjectData(struct Stream* stream, struct JUnion* object); +static cc_result Java_SkipAnnotation(struct Stream* stream) { + cc_uint8 typeCode, count; + struct JUnion object; + cc_result res; + + for (;;) + { + if ((res = stream->ReadU8(stream, &typeCode))) return res; + + switch (typeCode) + { + case TC_BLOCKDATA: + if ((res = stream->ReadU8(stream, &count))) return res; + if ((res = stream->Skip(stream, count))) return res; + break; + case TC_ENDBLOCKDATA: + return 0; + default: + object.Type = typeCode; + if ((res = Java_ReadObjectData(stream, &object))) return res; + break; + } + } + return 0; +} + + +/* Most .dat maps only use at most 16 different class types */ +/* However some survival test maps can use up to 30 */ +#define CLASS_CAPACITY 30 +static struct JClassDesc* class_cache; +static int class_count; +static cc_result Java_ReadClassDesc(struct Stream* stream, struct JClassDesc** desc); + +static cc_result Java_ReadFieldDesc(struct Stream* stream, struct JFieldDesc* desc) { + struct JUnion className; + cc_result res; + + if ((res = stream->ReadU8(stream, &desc->Type))) return res; + if ((res = Java_ReadString(stream, desc->FieldName))) return res; + + if (desc->Type == JFIELD_ARRAY || desc->Type == JFIELD_OBJECT) { + return Java_ReadObject(stream, &className); + } + return 0; +} + +static cc_result Java_ReadNewClassDesc(struct Stream* stream, struct JClassDesc* desc) { + cc_uint8 count[2]; + cc_result res; + int i; + + if ((res = Java_ReadString(stream, desc->ClassName))) return res; + if ((res = stream->Skip(stream, 8))) return res; /* serial version UID */ + if ((res = stream->ReadU8(stream, &desc->Flags))) return res; + + desc->Reference = reference_id; + Java_AddReference(); + + if ((res = Stream_Read(stream, count, 2))) return res; + desc->FieldsCount = Stream_GetU16_BE(count); + if (desc->FieldsCount > Array_Elems(desc->Fields)) return JAVA_ERR_JCLASS_FIELDS; + + for (i = 0; i < desc->FieldsCount; i++) { + if ((res = Java_ReadFieldDesc(stream, &desc->Fields[i]))) return res; + } + + if ((res = Java_SkipAnnotation(stream))) return res; + return Java_ReadClassDesc(stream, &desc->SuperClass); +} + +static cc_result Java_ReadClassDesc(struct Stream* stream, struct JClassDesc** desc) { + cc_uint8 typeCode; + cc_uint32 reference; + cc_result res; + int i; + if ((res = stream->ReadU8(stream, &typeCode))) return res; + + switch (typeCode) + { + case TC_NULL: + *desc = NULL; + return 0; + + case TC_REFERENCE: + if ((res = Stream_ReadU32_BE(stream, &reference))) return res; + + /* Use a previously defined ClassDescriptor */ + for (i = 0; i < class_count; i++) + { + if (class_cache[i].Reference != reference) continue; + + *desc = &class_cache[i]; + return 0; + } + return JAVA_ERR_JCLASS_REFERENCE; + + case TC_CLASSDESC: + if (class_count >= CLASS_CAPACITY) return JAVA_ERR_JCLASSES_COUNT; + + *desc = &class_cache[class_count++]; + return Java_ReadNewClassDesc(stream, *desc); + } + return JAVA_ERR_JCLASS_TYPE; +} + + +static cc_result Java_ReadValue(struct Stream* stream, cc_uint8 type, union JValue* value) { + struct JUnion obj; + cc_result res; + + switch (type) { + case JFIELD_I8: + case JFIELD_BOOL: + return stream->ReadU8(stream, &value->U8); + case JFIELD_F32: + case JFIELD_I32: + return Stream_ReadU32_BE(stream, &value->U32); + case JFIELD_F64: + case JFIELD_I64: + return stream->Skip(stream, 8); /* (8) data */ + case JFIELD_OBJECT: + return Java_ReadObject(stream, &obj); + + case JFIELD_ARRAY: + if ((res = Java_ReadObject(stream, &obj))) return res; + value->Array.Size = 0; + value->Array.Ptr = NULL; + + /* Array is a byte array */ + /* NOTE: This can technically leak memory if this array is discarded, however */ + /* so far the only observed byte array in .dat files is the map blocks anyways */ + if (obj.Type == TC_ARRAY && obj.Value.Array.Desc->ClassName[1] == JFIELD_I8) { + value->Array.Size = obj.Value.Array.Size; + value->Array.Ptr = obj.Value.Array.Data; + } + return 0; + } + return JAVA_ERR_JVALUE_TYPE; +} + +static cc_result Java_ReadClassData(struct Stream* stream, struct JClassDesc* desc) { + struct JFieldDesc* field; + cc_result res; + int i; + + if (!(desc->Flags & SC_SERIALIZABLE)) + return JAVA_ERR_JOBJECT_FLAGS; + + for (i = 0; i < desc->FieldsCount; i++) + { + field = &desc->Fields[i]; + if ((res = Java_ReadValue(stream, field->Type, &field->Value))) return res; + } + + if (desc->Flags & SC_WRITE_METHOD) + return Java_SkipAnnotation(stream); + return 0; +} + +static cc_result Java_ReadNewString(struct Stream* stream, struct JUnion* object) { + Java_AddReference(); + return Java_ReadString(stream, object->Value.String); +} + +static cc_result Java_ReadNewObject(struct Stream* stream, struct JUnion* object) { + struct JClassDesc* head; + cc_result res; + if ((res = Java_ReadClassDesc(stream, &object->Value.Object))) return res; + Java_AddReference(); + + /* Linked list of classes, with most superclass fist as head */ + head = object->Value.Object; head->tmp = NULL; + while (head->SuperClass) { + head->SuperClass->tmp = head; + head = head->SuperClass; + } + + /* Class data is read with most superclass first */ + while (head) { + if ((res = Java_ReadClassData(stream, head))) return res; + head = head->tmp; + } + return 0; +} + +static cc_result Java_ReadNewArray(struct Stream* stream, struct JUnion* object) { + struct JArray* array = &object->Value.Array; + union JValue value; + cc_uint32 count; + cc_uint8 type; + cc_result res; + int i; + + if ((res = Java_ReadClassDesc(stream, &array->Desc))) return res; + if ((res = Stream_ReadU32_BE(stream, &count))) return res; + type = array->Desc->ClassName[1]; + Java_AddReference(); + + if (type != JFIELD_I8) { + /* Not a byte array, so just discard the unnecessary values */ + for (i = 0; i < count; i++) + { + if ((res = Java_ReadValue(stream, type, &value))) return res; + } + return 0; + } + + array->Size = count; + array->Data = (cc_uint8*)Mem_TryAlloc(count, 1); + + if (!array->Data) return ERR_OUT_OF_MEMORY; + res = Stream_Read(stream, array->Data, count); + if (res) { Mem_Free(array->Data); } + return res; +} + +static cc_result Java_ReadObjectData(struct Stream* stream, struct JUnion* object) { + cc_uint32 reference; + switch (object->Type) + { + case TC_STRING: return Java_ReadNewString(stream, object); + case TC_NULL: return 0; + case TC_REFERENCE: return Stream_ReadU32_BE(stream, &reference); + case TC_OBJECT: return Java_ReadNewObject(stream, object); + case TC_ARRAY: return Java_ReadNewArray(stream, object); + } + return JAVA_ERR_INVALID_TYPECODE; +} + +static cc_result Java_ReadObject(struct Stream* stream, struct JUnion* object) { + cc_result res; + if ((res = stream->ReadU8(stream, &object->Type))) return res; + return Java_ReadObjectData(stream, object); +} + +static int Java_I32(struct JFieldDesc* field) { + if (field->Type != JFIELD_I32) Logger_Abort("Field type must be Int32"); + return field->Value.I32; +} + + +/*########################################################################################################################* +*-------------------------------------------------Minecraft .dat format---------------------------------------------------* +*#########################################################################################################################*/ +/* Minecraft Classic used 3 different GZIP compressed binary map formats throughout its various versions +Preclassic - Classic 0.12: + U8* "Blocks" (256x64x256 array) +Classic 0.13: + U32 "Identifier" (must be 0x271BB788) + U8 "Version" (must be 1) + STR "Name" (ignored) + STR "Author" (ignored) + U64 "Creation" (ignored) + U16 "Width" + U16 "Length" + U16 "Height" + U8* "Blocks" +Classic 0.15 to Classic 0.30: + U32 "Identifier" (must be 0x271BB788) + U8 "Version" (must be 2) + VAR "Level" (Java serialised level object instance) +}*/ + +static void Dat_Format0And1(void) { + /* Formats 0 and 1 don't store spawn position, so use default of map centre */ + spawn_point = NULL; + + /* Similiar env to how it appears in preclassic - 0.13 classic client */ + Env.CloudsHeight = -30000; + Env.SkyCol = PackedCol_Make(0x7F, 0xCC, 0xFF, 0xFF); + Env.FogCol = PackedCol_Make(0x7F, 0xCC, 0xFF, 0xFF); +} + +static cc_result Dat_LoadFormat0(struct Stream* stream) { + Dat_Format0And1(); + /* Similiar env to how it appears in preclassic client */ + Env.EdgeBlock = BLOCK_AIR; + Env.SidesBlock = BLOCK_AIR; + + /* Map 'format' is just the 256x64x256 blocks of the level */ + World.Width = 256; + World.Height = 64; + World.Length = 256; + + #define PC_VOLUME (256 * 64 * 256) + World.Volume = PC_VOLUME; + World.Blocks = (BlockRaw*)Mem_TryAlloc(PC_VOLUME, 1); + if (!World.Blocks) return ERR_OUT_OF_MEMORY; + + /* First 5 bytes already read earlier as .dat header */ + Mem_Set(World.Blocks, BLOCK_STONE, 5); + return Stream_Read(stream, World.Blocks + 5, PC_VOLUME - 5); +} + +static cc_result Dat_LoadFormat1(struct Stream* stream) { + cc_uint8 level_name[JNAME_SIZE]; + cc_uint8 level_author[JNAME_SIZE]; + cc_uint8 header[8 + 2 + 2 + 2]; + cc_result res; + + Dat_Format0And1(); + if ((res = Java_ReadString(stream, level_name))) return res; + if ((res = Java_ReadString(stream, level_author))) return res; + if ((res = Stream_Read(stream, header, sizeof(header)))) return res; + + /* bytes 0-8 = created timestamp (currentTimeMillis) */ + World.Width = Stream_GetU16_BE(header + 8); + World.Length = Stream_GetU16_BE(header + 10); + World.Height = Stream_GetU16_BE(header + 12); + return Map_ReadBlocks(stream); +} + +static cc_result Dat_LoadFormat2(struct Stream* stream) { + struct JClassDesc classes[CLASS_CAPACITY]; + cc_uint8 header[2 + 2]; + struct JUnion obj; + struct JClassDesc* desc; + struct JFieldDesc* field; + cc_string fieldName; + cc_result res; + int i; + if ((res = Stream_Read(stream, header, sizeof(header)))) return res; + + /* Reset state for Java Serialisation */ + class_cache = classes; + class_count = 0; + reference_id = 0x7E0000; + + /* Java seralisation headers */ + if (Stream_GetU16_BE(header + 0) != 0xACED) return DAT_ERR_JIDENTIFIER; + if (Stream_GetU16_BE(header + 2) != 0x0005) return DAT_ERR_JVERSION; + + if ((res = Java_ReadObject(stream, &obj))) return res; + if (obj.Type != TC_OBJECT) return DAT_ERR_ROOT_OBJECT; + desc = obj.Value.Object; + + for (i = 0; i < desc->FieldsCount; i++) + { + field = &desc->Fields[i]; + fieldName = String_FromRaw((char*)field->FieldName, JNAME_SIZE); + + if (String_CaselessEqualsConst(&fieldName, "width")) { + World.Width = Java_I32(field); + } else if (String_CaselessEqualsConst(&fieldName, "height")) { + World.Length = Java_I32(field); + } else if (String_CaselessEqualsConst(&fieldName, "depth")) { + World.Height = Java_I32(field); + } else if (String_CaselessEqualsConst(&fieldName, "blocks")) { + if (field->Type != JFIELD_ARRAY) Logger_Abort("Blocks field must be Array"); + World.Blocks = field->Value.Array.Ptr; + World.Volume = field->Value.Array.Size; + } else if (String_CaselessEqualsConst(&fieldName, "xSpawn")) { + spawn_point->pos.x = (float)Java_I32(field); + spawn_point->flags = LU_HAS_POS; + } else if (String_CaselessEqualsConst(&fieldName, "ySpawn")) { + spawn_point->pos.y = (float)Java_I32(field); + spawn_point->flags = LU_HAS_POS; + } else if (String_CaselessEqualsConst(&fieldName, "zSpawn")) { + spawn_point->pos.z = (float)Java_I32(field); + spawn_point->flags = LU_HAS_POS; + } + } + return 0; +} + +/* Imports a world from a .dat classic map file */ +/* Used by Minecraft Classic/WoM client */ +static cc_result Dat_Load(struct Stream* stream) { + cc_uint8 header[4 + 1]; + cc_uint32 signature; + cc_result res; + + struct Stream compStream; + struct InflateState state; + Inflate_MakeStream2(&compStream, &state, stream); + if ((res = Map_SkipGZipHeader(stream))) return res; + if ((res = Stream_Read(&compStream, header, sizeof(header)))) return res; + + signature = Stream_GetU32_BE(header + 0); + switch (signature) + { + /* Classic map format signature */ + case 0x271BB788: break; + /* Not an actual signature, but 99% of preclassic */ + /* to classic 0.12 maps start with these 4 bytes */ + case 0x01010101: return Dat_LoadFormat0(&compStream); + /* Bogus .dat file */ + default: return DAT_ERR_IDENTIFIER; + } + + /* Format version */ + switch (header[4]) + { + /* Format version 1 = classic 0.13 */ + case 0x01: return Dat_LoadFormat1(&compStream); + /* Format version 2 = classic 0.15 to 0.30 */ + case 0x02: return Dat_LoadFormat2(&compStream); + /* Bogus .dat file */ + default: return DAT_ERR_VERSION; + } +} + + +/*########################################################################################################################* +*-----------------------------------------------------MCLevel format------------------------------------------------------* +*#########################################################################################################################*/ +/* MCLevel is a NBT tag based map format used by Minecraft Indev. Tags not listed below are discarded. +COMPOUND "MinecraftLevel" { + COMPOUND "Map" { + I16 "Width", "Height", "Length" + I16 "Spawn" [3] + U8* "Blocks" + } + COMPOUND "Environment" { + I32 "SkyColor" + I32 "FogColor" + I32 "CloudColor" + I16 "CloudHeight" + } +}*/ +static int mcl_edgeHeight, mcl_sidesHeight; + +static void MCLevel_ParseMap(struct NbtTag* tag) { + if (IsTag(tag, "width")) { World.Width = NbtTag_U16(tag); return; } + if (IsTag(tag, "height")) { World.Height = NbtTag_U16(tag); return; } + if (IsTag(tag, "length")) { World.Length = NbtTag_U16(tag); return; } + + if (IsTag(tag, "blocks")) { + World.Volume = tag->dataSize; + World.Blocks = Nbt_TakeArray(tag, ".mclevel map blocks"); + } +} + +static PackedCol MCLevel_ParseColor(struct NbtTag* tag) { + int RGB = NbtTag_I32(tag); + return PackedCol_Make(RGB >> 16, RGB >> 8, RGB, 255); +} + +static void MCLevel_ParseEnvironment(struct NbtTag* tag) { + if (IsTag(tag, "SkyColor")) { + Env.SkyCol = MCLevel_ParseColor(tag); + } else if (IsTag(tag, "FogColor")) { + Env.FogCol = MCLevel_ParseColor(tag); + } else if (IsTag(tag, "CloudColor")) { + Env.CloudsCol = MCLevel_ParseColor(tag); + } else if (IsTag(tag, "CloudHeight")) { + Env.CloudsHeight = NbtTag_U16(tag); + } else if (IsTag(tag, "SurroundingGroundType")) { + Env.SidesBlock = NbtTag_U8(tag); + /* TODO need to explore this fully */ + if (Env.SidesBlock == BLOCK_GRASS) Env.SidesBlock = BLOCK_DIRT; + } else if (IsTag(tag, "SurroundingWaterType")) { + Env.EdgeBlock = NbtTag_U8(tag); + } else if (IsTag(tag, "SurroundingGroundHeight")) { + mcl_sidesHeight = NbtTag_U16(tag); + } else if (IsTag(tag, "SurroundingWaterHeight")) { + mcl_edgeHeight = NbtTag_U16(tag); + } + /* TODO: SkyBrightness */ +} + + +static void MCLevel_Callback_2(struct NbtTag* tag) { + struct NbtTag* group = tag->parent; + if (IsTag(group, "Map")) { + MCLevel_ParseMap(tag); + } else if (IsTag(group, "Environment")) { + MCLevel_ParseEnvironment(tag); + } +} + +static void MCLevel_Callback_3(struct NbtTag* tag) { + struct NbtTag* group = tag->parent->parent; + struct NbtTag* field = tag->parent; + + if (IsTag(group, "Map") && IsTag(field, "spawn")) { + cc_int16 value = NbtTag_I16(tag); + spawn_point->flags = LU_HAS_POS; + + if (tag->listIndex == 0) spawn_point->pos.x = value; + if (tag->listIndex == 1) spawn_point->pos.y = value - 1.0f; + if (tag->listIndex == 2) spawn_point->pos.z = value; + } +} + +static void MCLevel_Callback(struct NbtTag* tag) { + struct NbtTag* tmp = tag->parent; + int depth = 0; + while (tmp) { depth++; tmp = tmp->parent; } + + switch (depth) { + case 2: MCLevel_Callback_2(tag); return; + case 3: MCLevel_Callback_3(tag); return; + } + /* MinecraftLevel -> Map/Environment -> [value] + 0 1 2 */ +} + +/* Imports a world from a .mclevel NBT map file */ +/* Used by Minecraft Indev client */ +static cc_result MCLevel_Load(struct Stream* stream) { + cc_result res = Nbt_Read(stream, MCLevel_Callback); + + Env.EdgeHeight = mcl_edgeHeight; + Env.SidesOffset = mcl_sidesHeight - mcl_edgeHeight; + return res; +} + + +/*########################################################################################################################* +*--------------------------------------------------ClassicWorld export----------------------------------------------------* +*#########################################################################################################################*/ +static cc_uint8* Cw_WriteColor(cc_uint8* data, const char* name, PackedCol color) { + data = Nbt_WriteDict(data, name); + { + data = Nbt_WriteUInt16(data, "R", PackedCol_R(color)); + data = Nbt_WriteUInt16(data, "G", PackedCol_G(color)); + data = Nbt_WriteUInt16(data, "B", PackedCol_B(color)); + } *data++ = NBT_END; + + return data; +} + +static const cc_uint8 cw_end[4] = { + NBT_END, + NBT_END, + NBT_END, +NBT_END, +}; + +static cc_result Cw_WriteBockDef(struct Stream* stream, int b) { + cc_uint8 buffer[1024]; + char nameBuffer[10]; + cc_uint8* cur; + cc_string name; + cc_bool sprite = Blocks.Draw[b] == DRAW_SPRITE; + TextureLoc tex; + cc_uint8 fog; + PackedCol col; + Vec3 minBB, maxBB; + + /* Hacky unique tag name for each by using hex of block */ + String_InitArray_NT(name, nameBuffer); + String_AppendConst(&name, "Block"); + String_AppendHex(&name, b >> 8); + String_AppendHex(&name, b); + nameBuffer[9] = '\0'; + + cur = buffer; + cur = Nbt_WriteDict(cur, nameBuffer); + { + cur = Nbt_WriteUInt8(cur, "ID", b); + /* It would be have been better to just change ID to be a I16 */ + /* Unfortunately this isn't backwards compatible with ClassicalSharp */ + cur = Nbt_WriteUInt16(cur, "ID2", b); + cur = Nbt_WriteUInt8(cur, "CollideType", Blocks.Collide[b]); + cur = Nbt_WriteFloat(cur, "Speed", Blocks.SpeedMultiplier[b]); + + /* Originally only up to 256 textures were supported, which used up 6 bytes total */ + /* Later, support for more textures was added, which requires 2 bytes per texture */ + /* For backwards compatibility, the lower byte of each texture is */ + /* written into first 6 bytes, then higher byte into next 6 bytes (ugly hack) */ + cur = Nbt_WriteArray(cur, "Textures", 12); + tex = Block_Tex(b, FACE_YMAX); cur[0] = (cc_uint8)tex; cur[ 6] = (cc_uint8)(tex >> 8); + tex = Block_Tex(b, FACE_YMIN); cur[1] = (cc_uint8)tex; cur[ 7] = (cc_uint8)(tex >> 8); + tex = Block_Tex(b, FACE_XMIN); cur[2] = (cc_uint8)tex; cur[ 8] = (cc_uint8)(tex >> 8); + tex = Block_Tex(b, FACE_XMAX); cur[3] = (cc_uint8)tex; cur[ 9] = (cc_uint8)(tex >> 8); + tex = Block_Tex(b, FACE_ZMIN); cur[4] = (cc_uint8)tex; cur[10] = (cc_uint8)(tex >> 8); + tex = Block_Tex(b, FACE_ZMAX); cur[5] = (cc_uint8)tex; cur[11] = (cc_uint8)(tex >> 8); + cur += 12; + + cur = Nbt_WriteUInt8(cur, "TransmitsLight", Blocks.BlocksLight[b] ? 0 : 1); + cur = Nbt_WriteUInt8(cur, "WalkSound", Blocks.DigSounds[b]); + cur = Nbt_WriteUInt8(cur, "FullBright", Block_WriteFullBright(Blocks.Brightness[b])); + cur = Nbt_WriteUInt8(cur, "Shape", sprite ? 0 : (cc_uint8)(Blocks.MaxBB[b].y * 16)); + cur = Nbt_WriteUInt8(cur, "BlockDraw", sprite ? Blocks.SpriteOffset[b] : Blocks.Draw[b]); + + cur = Nbt_WriteArray(cur, "Fog", 4); + fog = (cc_uint8)(128 * Blocks.FogDensity[b] - 1); + col = Blocks.FogCol[b]; + cur[0] = Blocks.FogDensity[b] ? fog : 0xFF; /* write 0xFF instead of 0 for backwards compatibility */ + cur[1] = PackedCol_R(col); cur[2] = PackedCol_G(col); cur[3] = PackedCol_B(col); + cur += 4; + + cur = Nbt_WriteArray(cur, "Coords", 6); + minBB = Blocks.MinBB[b]; maxBB = Blocks.MaxBB[b]; + cur[0] = (cc_uint8)(minBB.x * 16); cur[1] = (cc_uint8)(minBB.y * 16); cur[2] = (cc_uint8)(minBB.z * 16); + cur[3] = (cc_uint8)(maxBB.x * 16); cur[4] = (cc_uint8)(maxBB.y * 16); cur[5] = (cc_uint8)(maxBB.z * 16); + cur += 6; + + name = Block_UNSAFE_GetName(b); + cur = Nbt_WriteString(cur, "Name", &name); + } *cur++ = NBT_END; + + return Stream_Write(stream, buffer, (int)(cur - buffer)); +} + +cc_result Cw_Save(struct Stream* stream) { + struct LocalPlayer* p = Entities.CurPlayer; + cc_uint8 buffer[2048]; + cc_uint8* cur; + cc_result res; + int b; + + cur = buffer; + cur = Nbt_WriteDict(cur, "ClassicWorld"); + cur = Nbt_WriteUInt8(cur, "FormatVersion", 1); + cur = Nbt_WriteArray(cur, "UUID", WORLD_UUID_LEN); Mem_Copy(cur, World.Uuid, WORLD_UUID_LEN); cur += WORLD_UUID_LEN; + cur = Nbt_WriteUInt16(cur, "X", World.Width); + cur = Nbt_WriteUInt16(cur, "Y", World.Height); + cur = Nbt_WriteUInt16(cur, "Z", World.Length); + + cur = Nbt_WriteDict(cur, "MapGenerator"); + { + cur = Nbt_WriteInt32(cur, "Seed", World.Seed); + } *cur++ = NBT_END; + + + /* TODO: Maybe keep real spawn too? */ + cur = Nbt_WriteDict(cur, "Spawn"); + { + cur = Nbt_WriteUInt16(cur, "X", (cc_uint16)p->Base.Position.x); + cur = Nbt_WriteUInt16(cur, "Y", (cc_uint16)p->Base.Position.y); + cur = Nbt_WriteUInt16(cur, "Z", (cc_uint16)p->Base.Position.z); + cur = Nbt_WriteUInt8(cur, "H", Math_Deg2Packed(p->SpawnYaw)); + cur = Nbt_WriteUInt8(cur, "P", Math_Deg2Packed(p->SpawnPitch)); + } *cur++ = NBT_END; + cur = Nbt_WriteArray(cur, "BlockArray", World.Volume); + + if ((res = Stream_Write(stream, buffer, (int)(cur - buffer)))) return res; + if ((res = Stream_Write(stream, World.Blocks, World.Volume))) return res; + +#ifdef EXTENDED_BLOCKS + if (World.Blocks != World.Blocks2) { + cur = buffer; + cur = Nbt_WriteArray(cur, "BlockArray2", World.Volume); + + if ((res = Stream_Write(stream, buffer, (int)(cur - buffer)))) return res; + if ((res = Stream_Write(stream, World.Blocks2, World.Volume))) return res; + } +#endif + + cur = buffer; + cur = Nbt_WriteDict(cur, "Metadata"); + cur = Nbt_WriteDict(cur, "CPE"); + { + cur = Nbt_WriteDict(cur, "ClickDistance"); + { + cur = Nbt_WriteUInt16(cur, "Distance", (cc_uint16)(p->ReachDistance * 32)); + } *cur++ = NBT_END; + + cur = Nbt_WriteDict(cur, "EnvWeatherType"); + { + cur = Nbt_WriteUInt8(cur, "WeatherType", Env.Weather); + } *cur++ = NBT_END; + + cur = Nbt_WriteDict(cur, "EnvColors"); + { + cur = Cw_WriteColor(cur, "Sky", Env.SkyCol); + cur = Cw_WriteColor(cur, "Cloud", Env.CloudsCol); + cur = Cw_WriteColor(cur, "Fog", Env.FogCol); + cur = Cw_WriteColor(cur, "Ambient", Env.ShadowCol); + cur = Cw_WriteColor(cur, "Sunlight", Env.SunCol); + cur = Cw_WriteColor(cur, "Skybox", Env.SkyboxCol); + } *cur++ = NBT_END; + + cur = Nbt_WriteDict(cur, "EnvMapAppearance"); + { + cur = Nbt_WriteUInt8(cur, "SideBlock", (BlockRaw)Env.SidesBlock); + cur = Nbt_WriteUInt8(cur, "EdgeBlock", (BlockRaw)Env.EdgeBlock); + cur = Nbt_WriteUInt16(cur, "SideLevel", Env.EdgeHeight); + cur = Nbt_WriteString(cur, "TextureURL", &TexturePack_Url); + } *cur++ = NBT_END; + + cur = Nbt_WriteDict(cur, "EnvMapAspect"); + { + cur = Nbt_WriteUInt16(cur, "EdgeBlock", Env.EdgeBlock); + cur = Nbt_WriteUInt16(cur, "SideBlock", Env.SidesBlock); + cur = Nbt_WriteInt32(cur, "EdgeHeight", Env.EdgeHeight); + cur = Nbt_WriteInt32(cur, "SidesOffset", Env.SidesOffset); + cur = Nbt_WriteInt32(cur, "CloudsHeight", Env.CloudsHeight); + cur = Nbt_WriteFloat(cur, "CloudsSpeed", Env.CloudsSpeed); + cur = Nbt_WriteFloat(cur, "WeatherSpeed", Env.WeatherSpeed); + cur = Nbt_WriteFloat(cur, "WeatherFade", Env.WeatherFade); + cur = Nbt_WriteUInt8(cur, "ExpFog", (cc_uint8)Env.ExpFog); + cur = Nbt_WriteFloat(cur, "SkyboxHor", Env.SkyboxHorSpeed); + cur = Nbt_WriteFloat(cur, "SkyboxVer", Env.SkyboxVerSpeed); + } *cur++ = NBT_END; + + cur = Nbt_WriteDict(cur, "BlockDefinitions"); + if ((res = Stream_Write(stream, buffer, (int)(cur - buffer)))) return res; + + { + /* Write block definitions in reverse order so that software that only reads byte 'ID' */ + /* still loads correct first 256 block defs when saving a map with over 256 block defs */ + for (b = BLOCK_MAX_DEFINED; b >= 1; b--) { + if (!Block_IsCustomDefined(b)) continue; + if ((res = Cw_WriteBockDef(stream, b))) return res; + } + } + } + return Stream_Write(stream, cw_end, sizeof(cw_end)); +} + + +/*########################################################################################################################* +*---------------------------------------------------Schematic export------------------------------------------------------* +*#########################################################################################################################*/ +static cc_uint8 sc_begin[] = { +NBT_DICT, 0,9, 'S','c','h','e','m','a','t','i','c', + NBT_STR, 0,9, 'M','a','t','e','r','i','a','l','s', 0,7, 'C','l','a','s','s','i','c', + NBT_I16, 0,5, 'W','i','d','t','h', 0,0, + NBT_I16, 0,6, 'H','e','i','g','h','t', 0,0, + NBT_I16, 0,6, 'L','e','n','g','t','h', 0,0, + NBT_I8S, 0,6, 'B','l','o','c','k','s', 0,0,0,0, +}; +static cc_uint8 sc_data[] = { + NBT_I8S, 0,4, 'D','a','t','a', 0,0,0,0, +}; +static cc_uint8 sc_end[] = { + NBT_LIST, 0,8, 'E','n','t','i','t','i','e','s', NBT_DICT, 0,0,0,0, + NBT_LIST, 0,12, 'T','i','l','e','E','n','t','i','t','i','e','s', NBT_DICT, 0,0,0,0, +NBT_END, +}; + +cc_result Schematic_Save(struct Stream* stream) { + cc_uint8 tmp[256], chunk[8192] = { 0 }; + cc_result res; + int i; + + Mem_Copy(tmp, sc_begin, sizeof(sc_begin)); + { + Stream_SetU16_BE(&tmp[41], World.Width); + Stream_SetU16_BE(&tmp[52], World.Height); + Stream_SetU16_BE(&tmp[63], World.Length); + Stream_SetU32_BE(&tmp[74], World.Volume); + } + if ((res = Stream_Write(stream, tmp, sizeof(sc_begin)))) return res; + if ((res = Stream_Write(stream, World.Blocks, World.Volume))) return res; + + Mem_Copy(tmp, sc_data, sizeof(sc_data)); + { + Stream_SetU32_BE(&tmp[7], World.Volume); + } + if ((res = Stream_Write(stream, tmp, sizeof(sc_data)))) return res; + + for (i = 0; i < World.Volume; i += sizeof(chunk)) { + int count = World.Volume - i; count = min(count, sizeof(chunk)); + if ((res = Stream_Write(stream, chunk, count))) return res; + } + return Stream_Write(stream, sc_end, sizeof(sc_end)); +} + + +/*########################################################################################################################* +*------------------------------------------------------Dat export---------------------------------------------------------* +*#########################################################################################################################*/ +static const struct JField { + cc_uint8 type, isFloat; + const char* name; + void* value; +} level_fields[] = { + { JFIELD_I32, false, "width", &World.Width }, + { JFIELD_I32, false, "depth", &World.Height }, + { JFIELD_I32, false, "height", &World.Length }, + { JFIELD_I32, true, "xSpawn", &LocalPlayer_Instances[0].Base.Position.x }, + { JFIELD_I32, true, "ySpawn", &LocalPlayer_Instances[0].Base.Position.y }, + { JFIELD_I32, true, "zSpawn", &LocalPlayer_Instances[0].Base.Position.z }, + { JFIELD_ARRAY,0, "blocks" } + /* TODO classic only blocks */ +}; + +static int WriteJavaString(cc_uint8* dst, const char* value) { + int length = String_Length(value); + dst[0] = 0; + dst[1] = length; + Mem_Copy(dst + 2, value, length); + return length; +} + +static cc_result WriteClassDesc(struct Stream* stream, cc_uint8 typecode, const char* klass, + int numFields, const struct JField* fields) { + cc_uint8 header[256] = { 0 }; + static const cc_uint8 footer[] = { + TC_ENDBLOCKDATA, /* classAnnotations */ + TC_NULL /* superClassDesc */ + }; + int i, length; + cc_result res; + + header[0] = typecode; + header[1] = TC_CLASSDESC; + length = WriteJavaString(header + 2, klass); + header[4 + length + 8] = SC_SERIALIZABLE; + header[4 + length + 9] = 0; + header[4 + length + 10] = numFields; + + if ((res = Stream_Write(stream, header, 15 + length))) return res; + + for (i = 0; i < numFields; i++) + { + header[0] = fields[i].type; + length = WriteJavaString(header + 1, fields[i].name); + + if (fields[i].type == JFIELD_ARRAY) { + header[3 + length + 0] = TC_STRING; + WriteJavaString(&header[3 + length + 1], "[B"); + length += 5; + } + if ((res = Stream_Write(stream, header, 3 + length))) return res; + } + + if ((res = Stream_Write(stream, footer, sizeof(footer)))) return res; + return 0; +} + +static const cc_uint8 cpe_fallback[] = { + BLOCK_SLAB, BLOCK_BROWN_SHROOM, BLOCK_SAND, BLOCK_AIR, BLOCK_STILL_LAVA, BLOCK_PINK, + BLOCK_GREEN, BLOCK_DIRT, BLOCK_BLUE, BLOCK_CYAN, BLOCK_GLASS, BLOCK_IRON, BLOCK_OBSIDIAN, BLOCK_WHITE, + BLOCK_WOOD, BLOCK_STONE +}; + +#define DAT_BUFFER_SIZE (64 * 1024) +static cc_result WriteLevelBlocks(struct Stream* stream) { + cc_uint8 buffer[DAT_BUFFER_SIZE]; + int i, bIndex = 0; + cc_result res; + BlockID b; + + for (i = 0; i < World.Volume; i++) + { + b = World_GetRawBlock(i); + /* TODO: Better fallback decision (e.g. air if custom block is 'gas' type) */ + if (b > BLOCK_STONE_BRICK) b = BLOCK_STONE; + /* TODO: Move to GameVersion.c and account for game version */ + if (b > BLOCK_OBSIDIAN) b = cpe_fallback[b - BLOCK_COBBLE_SLAB]; + + buffer[bIndex] = (cc_uint8)b; + bIndex++; + if (bIndex < DAT_BUFFER_SIZE) continue; + + if ((res = Stream_Write(stream, buffer, DAT_BUFFER_SIZE))) return res; + bIndex = 0; + } + + if (bIndex == 0) return 0; + return Stream_Write(stream, buffer, bIndex); +} + +cc_result Dat_Save(struct Stream* stream) { + static const cc_uint8 header[] = { + 0x27,0x1B,0xB7,0x88, 0x02, /* DAT signature + version */ + 0xAC,0xED, 0x00,0x05 /* JSF signature + version */ + }; + const struct JField* field; + cc_uint8 tmp[4]; + cc_result res; + int i, value; + + if ((res = Stream_Write(stream, header, sizeof(header)))) return res; + if ((res = WriteClassDesc(stream, TC_OBJECT, "com.mojang.minecraft.level.Level", + Array_Elems(level_fields), level_fields))) return res; + + /* Write field values */ + for (i = 0; i < Array_Elems(level_fields); i++) + { + field = &level_fields[i]; + + if (field->type == JFIELD_I32) { + value = field->isFloat ? *((float*)field->value) : *((int*)field->value); + Stream_SetU32_BE(tmp, value); + if ((res = Stream_Write(stream, tmp, 4))) return res; + } else { + if ((res = WriteClassDesc(stream, TC_ARRAY, "[B", 0, NULL))) return res; + Stream_SetU32_BE(tmp, World.Volume); + if ((res = Stream_Write(stream, tmp, 4))) return res; + if ((res = WriteLevelBlocks(stream))) return res; + } + } + return 0; +} + + +/*########################################################################################################################* +*-------------------------------------------------------Formats component-------------------------------------------------* +*#########################################################################################################################*/ +static struct MapImporter cw_imp = { ".cw", Cw_Load }; +static struct MapImporter dat_imp = { ".dat", Dat_Load }; +static struct MapImporter lvl_imp = { ".lvl", Lvl_Load }; +static struct MapImporter mine_imp = { ".mine", Dat_Load }; +static struct MapImporter fcm_imp = { ".fcm", Fcm_Load }; +static struct MapImporter mclvl_imp = { ".mclevel", MCLevel_Load }; + +static void OnInit(void) { + MapImporter_Register(&cw_imp); + MapImporter_Register(&dat_imp); + MapImporter_Register(&lvl_imp); + MapImporter_Register(&mine_imp); + MapImporter_Register(&fcm_imp); + MapImporter_Register(&mclvl_imp); +} + +static void OnFree(void) { + imp_head = NULL; +} +#else +/* No point including map format code when can't save/load maps anyways */ +struct MapImporter* MapImporter_Find(const cc_string* path) { return NULL; } +cc_result Map_LoadFrom(const cc_string* path) { return ERR_NOT_SUPPORTED; } + +cc_result Cw_Save(struct Stream* stream) { return ERR_NOT_SUPPORTED; } +cc_result Dat_Save(struct Stream* stream) { return ERR_NOT_SUPPORTED; } +cc_result Schematic_Save(struct Stream* stream) { return ERR_NOT_SUPPORTED; } + +static void OnInit(void) { } +static void OnFree(void) { } +#endif + +struct IGameComponent Formats_Component = { + OnInit, /* Init */ + OnFree /* Free */ +}; |