// ClassicalSharp copyright 2014-2016 UnknownShadow200 | Licensed under MIT using System; using System.IO; using System.IO.Compression; using System.Net; using System.Text; using ClassicalSharp.Entities; using ClassicalSharp.Network; using OpenTK; namespace ClassicalSharp.Map { /// Imports a world from a dat map file (original minecraft classic map) public sealed class MapDatImporter : IMapFormatImporter { const byte TC_NULL = 0x70, TC_REFERENCE = 0x71; const byte TC_CLASSDESC = 0x72, TC_OBJECT = 0x73; const byte TC_STRING = 0x74, TC_ARRAY = 0x75; const byte TC_ENDBLOCKDATA = 0x78; BinaryReader reader; public byte[] Load( Stream stream, Game game, out int width, out int height, out int length ) { byte[] map = null; width = 0; height = 0; length = 0; LocalPlayer p = game.LocalPlayer; p.Spawn = Vector3.Zero; GZipHeaderReader gsHeader = new GZipHeaderReader(); while( !gsHeader.ReadHeader( stream ) ) { } using( DeflateStream gs = new DeflateStream( stream, CompressionMode.Decompress ) ) { reader = new BinaryReader( gs ); ClassDescription obj = ReadData(); for( int i = 0; i < obj.Fields.Length; i++ ) { FieldDescription field = obj.Fields[i]; if( field.FieldName == "width" ) width = (int)field.Value; else if( field.FieldName == "height" ) length = (int)field.Value; else if( field.FieldName == "depth" ) height = (int)field.Value; else if( field.FieldName == "blocks" ) map = (byte[])field.Value; else if( field.FieldName == "xSpawn" ) p.Spawn.X = (int)field.Value; else if( field.FieldName == "ySpawn" ) p.Spawn.Y = (int)field.Value; else if( field.FieldName == "zSpawn" ) p.Spawn.Z = (int)field.Value; } } return map; } ClassDescription ReadData() { if( ReadInt32() != 0x271BB788 || reader.ReadByte() != 0x02 ) { throw new InvalidDataException( "Unexpected constant in .lvl file" ); } // Java serialization constants if( (ushort)ReadInt16() != 0xACED || ReadInt16() != 0x0005 ) { throw new InvalidDataException( "Unexpected java serialisation constant(s)." ); } byte typeCode = reader.ReadByte(); if( typeCode != TC_OBJECT ) ParseError( TC_OBJECT, typeCode ); ClassDescription desc = ReadClassDescription(); ReadClassData( desc.Fields ); return desc; } void ReadClassData( FieldDescription[] fields ) { for( int i = 0; i < fields.Length; i++ ) { switch( fields[i].Type ) { case FieldType.Byte: fields[i].Value = reader.ReadByte(); break; case FieldType.Float: fields[i].Value = ReadInt32(); break; // wrong, but we don't support it anyways case FieldType.Integer: fields[i].Value = ReadInt32(); break; case FieldType.Long: fields[i].Value = ReadInt64(); break; case FieldType.Boolean: fields[i].Value = reader.ReadByte() != 0; break; case FieldType.Object: if( fields[i].FieldName == "blockMap" ) { byte typeCode = reader.ReadByte(); // Skip all blockMap data with awful hacks if( typeCode == TC_OBJECT ) { reader.ReadBytes( 315 ); int count = ReadInt32(); byte[] temp = new byte[17]; Stream stream = reader.BaseStream; for( int j = 0; j < count; j++ ) { stream.Read( temp, 0, temp.Length ); } reader.ReadBytes( 152 ); } } break; case FieldType.Array: ReadArray( ref fields[i] ); break; } } } void ReadArray( ref FieldDescription field ) { byte typeCode = reader.ReadByte(); if( typeCode == TC_NULL ) return; if( typeCode != TC_ARRAY ) ParseError( TC_ARRAY, typeCode ); ClassDescription desc = ReadClassDescription(); field.Value = reader.ReadBytes( ReadInt32() ); } ClassDescription ReadClassDescription() { ClassDescription desc = default( ClassDescription ); byte typeCode = reader.ReadByte(); if( typeCode == TC_NULL ) return desc; if( typeCode != TC_CLASSDESC ) ParseError( TC_CLASSDESC, typeCode ); desc.ClassName = ReadString(); reader.ReadUInt64(); // serial version UID reader.ReadByte(); // flags FieldDescription[] fields = new FieldDescription[ReadInt16()]; for( int i = 0; i < fields.Length; i++ ) { fields[i] = ReadFieldDescription(); } desc.Fields = fields; typeCode = reader.ReadByte(); if( typeCode != TC_ENDBLOCKDATA ) ParseError( TC_ENDBLOCKDATA, typeCode ); ReadClassDescription(); // super class description return desc; } FieldDescription ReadFieldDescription() { FieldDescription desc = default( FieldDescription ); byte fieldType = reader.ReadByte(); desc.Type = (FieldType)fieldType; desc.FieldName = ReadString(); if( desc.Type == FieldType.Array || desc.Type == FieldType.Object ) { byte typeCode = reader.ReadByte(); if( typeCode == TC_STRING ) desc.ClassName1 = ReadString(); else if( typeCode == TC_REFERENCE ) reader.ReadInt32(); // handle else ParseError( TC_STRING, typeCode ); } return desc; } void ParseError( int expected, int typeCode ) { throw new InvalidDataException( String.Format( "Expected {0}, got {1}", expected.ToString( "X2" ), typeCode.ToString( "X2" ) ) ); } struct ClassDescription { public string ClassName; public FieldDescription[] Fields; } struct FieldDescription { public FieldType Type; public string FieldName; public string ClassName1; public object Value; } enum FieldType { Byte = 'B', Float = 'F', Integer = 'I', Long = 'J', Boolean = 'Z', Array = '[', Object = 'L', } long ReadInt64() { return IPAddress.HostToNetworkOrder( reader.ReadInt64() ); } int ReadInt32() { return IPAddress.HostToNetworkOrder( reader.ReadInt32() ); } short ReadInt16() { return IPAddress.HostToNetworkOrder( reader.ReadInt16() ); } string ReadString() { return Encoding.ASCII.GetString( reader.ReadBytes( ReadInt16() ) ); } } }