LinqPad script to load a Level.dat file from Minecraft Better Together
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
void Main() | |
{ | |
string path = @"Path to your Level.dat"; | |
Level l = new Level(); | |
try | |
{ | |
l.ReadFrom(File.OpenRead(path)); | |
} | |
catch (Exception e) | |
{ | |
e.Dump(); | |
} | |
l.Dump(); | |
} | |
public class Level | |
{ | |
public const int HeaderLength = 8; | |
public int Version { get; set; } | |
public Dictionary<string, object> Properties { get; set; } | |
public Level() | |
{ | |
} | |
public void ReadFrom(Stream stream) | |
{ | |
using (BinaryReader reader = new BinaryReader(stream)) | |
{ | |
ReadFrom(reader); | |
} | |
} | |
public void ReadFrom(BinaryReader reader) | |
{ | |
Version = reader.ReadInt32(false); | |
int objectLength = reader.ReadInt32(false); | |
if (reader.BaseStream.Length != objectLength + HeaderLength) | |
{ | |
throw new Exception("Stream is longer than expected"); | |
} | |
this.Properties = this.ReadRoot(reader); | |
if (reader.BaseStream.Position != reader.BaseStream.Length) | |
{ | |
throw new Exception("Additional data left in object"); | |
} | |
} | |
public Dictionary<string, object> ReadRoot(BinaryReader reader) | |
{ | |
string name; | |
object value; | |
if (!ReadProperty(reader, out name, out value)) | |
{ | |
throw new Exception("Unable to read root."); | |
} | |
return value as Dictionary<string, object>; | |
} | |
public bool ReadProperty(BinaryReader reader, out string name, out object value) | |
{ | |
TagType type = ReadType(reader); | |
if (type == TagType.None) | |
{ | |
name = null; | |
value = null; | |
return false; | |
} | |
name = ReadString(reader); | |
try | |
{ | |
switch (type) | |
{ | |
case TagType.None: | |
value = null; | |
return false; | |
case TagType.Boolean: | |
value = reader.ReadBoolean(); | |
break; | |
case TagType.Int16: | |
value = reader.ReadInt16(false); | |
break; | |
case TagType.Int32: | |
value = reader.ReadInt32(false); | |
break; | |
case TagType.Int64: | |
value = reader.ReadInt64(false); | |
break; | |
case TagType.String: | |
value = ReadString(reader); | |
break; | |
case TagType.Float: | |
value = reader.ReadSingle(); | |
break; | |
case TagType.Array: | |
if (name == "lastOpenedWithVersion") | |
{ | |
value = reader.ReadBytes(0x15); | |
} | |
else | |
{ | |
value = reader.ReadBytes(5); | |
} | |
break; | |
case TagType.Object: | |
value = ReadObject(reader); | |
break; | |
default: | |
throw new Exception($"Unknown property of type '{type}'."); | |
} | |
} | |
catch (Exception e) | |
{ | |
throw new Exception($"Error while reading property '{name}'.", e); | |
} | |
return true; | |
} | |
public Dictionary<string, object> ReadObject(BinaryReader reader) | |
{ | |
Dictionary<string, object> properties = new Dictionary<string, object>(); | |
try | |
{ | |
string name; | |
object value; | |
while (ReadProperty(reader, out name, out value)) | |
{ | |
properties.Add(name, value); | |
} | |
} | |
catch (Exception e) | |
{ | |
Exception ne = new Exception("Error while reading object", e); | |
foreach (var p in properties) | |
{ | |
ne.Data.Add(p.Key, p.Value); | |
} | |
throw ne; | |
} | |
return properties; | |
} | |
public DateTime ReadDateTime(BinaryReader reader) | |
{ | |
DateTime unixEpoch = new DateTime(1970, 01, 01); | |
Int64 unixTimestamp = reader.ReadInt64(false); | |
return unixEpoch.AddSeconds(unixTimestamp); | |
} | |
public string ReadString(BinaryReader reader) | |
{ | |
int length = reader.ReadInt16(false); | |
byte[] bytes = reader.ReadBytes(length); | |
return Encoding.ASCII.GetString(bytes); | |
} | |
public TagType ReadType(BinaryReader reader) | |
{ | |
return (TagType)reader.ReadByte(); | |
} | |
public enum TagType | |
{ | |
None, | |
Boolean = 0x01, | |
Int16 = 0x02, | |
Int32 = 0x03, | |
Int64 = 0x04, | |
Float = 0x05, | |
String = 0x08, | |
Array = 0x09, | |
Object = 0x0A, | |
} | |
} | |
public static class BinaryReaderExtensions | |
{ | |
public static byte[] ReadBytes(this BinaryReader reader, int count, bool bigEndian) | |
{ | |
byte[] bytes = reader.ReadBytes(count); | |
if (bigEndian) | |
{ | |
Array.Reverse(bytes); | |
} | |
return bytes; | |
} | |
public static Int32 ReadInt32(this BinaryReader reader, bool bigEndian) | |
{ | |
return BitConverter.ToInt32(reader.ReadBytes(sizeof(Int32), bigEndian), 0); | |
} | |
public static Int16 ReadInt16(this BinaryReader reader, bool bigEndian) | |
{ | |
return BitConverter.ToInt16(reader.ReadBytes(sizeof(Int16), bigEndian), 0); | |
} | |
public static Int64 ReadInt64(this BinaryReader reader, bool bigEndian) | |
{ | |
return BitConverter.ToInt64(reader.ReadBytes(sizeof(Int64), bigEndian), 0); | |
} | |
} | |
// Define other methods and classes here |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment