diff --git a/ClassicalSharp/ClassicalSharp.csproj b/ClassicalSharp/ClassicalSharp.csproj
index 2dae2ef75..da638e75b 100644
--- a/ClassicalSharp/ClassicalSharp.csproj
+++ b/ClassicalSharp/ClassicalSharp.csproj
@@ -45,7 +45,6 @@
TRACE;
obj\
Project
- wwwf null 127.0.0.1 25566
AnyCPU
@@ -115,9 +114,10 @@
+
+
-
diff --git a/ClassicalSharp/Map/IMapFile.cs b/ClassicalSharp/Map/IMapFile.cs
new file mode 100644
index 000000000..da70fedfb
--- /dev/null
+++ b/ClassicalSharp/Map/IMapFile.cs
@@ -0,0 +1,26 @@
+using System;
+using System.IO;
+
+namespace ClassicalSharp {
+
+ public abstract class IMapFile {
+
+ public virtual bool SupportsLoading {
+ get { return false; }
+ }
+
+ public virtual bool SupportsSaving {
+ get { return false; }
+ }
+
+ public virtual byte[] Load( Stream stream, Game game, out int width, out int height, out int length ) {
+ width = 0;
+ height = 0;
+ length = 0;
+ return null;
+ }
+
+ public virtual void Save( Stream stream, Game game ) {
+ }
+ }
+}
diff --git a/ClassicalSharp/Map/MapDat.cs b/ClassicalSharp/Map/MapDat.cs
new file mode 100644
index 000000000..6864929ee
--- /dev/null
+++ b/ClassicalSharp/Map/MapDat.cs
@@ -0,0 +1,183 @@
+using System;
+using System.IO;
+using System.IO.Compression;
+using System.Net;
+using System.Text;
+using OpenTK;
+
+namespace ClassicalSharp {
+
+ public sealed class MapDat : IMapFile {
+
+ 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;
+
+ public override bool SupportsLoading {
+ get { return true; }
+ }
+
+ BinaryReader reader;
+ public override 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.SpawnPoint = Vector3.Zero;
+
+ using( GZipStream gs = new GZipStream( 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.SpawnPoint.X = (int)field.Value;
+ else if( field.FieldName == "ySpawn" )
+ p.SpawnPoint.Y = (int)field.Value;
+ else if( field.FieldName == "zSpawn" )
+ p.SpawnPoint.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, desc.Flags );
+ return desc;
+ }
+
+ void ReadClassData( FieldDescription[] fields, byte flags ) {
+ 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 byte Flags;
+ public FieldDescription[] Fields;
+ public object Value;
+ }
+
+ 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() ) ); }
+ }
+}
\ No newline at end of file
diff --git a/ClassicalSharp/Map/MapFcm3.cs b/ClassicalSharp/Map/MapFcm3.cs
index 143cd00d5..34fc2677e 100644
--- a/ClassicalSharp/Map/MapFcm3.cs
+++ b/ClassicalSharp/Map/MapFcm3.cs
@@ -6,11 +6,20 @@ using System.Text;
namespace ClassicalSharp {
- public sealed class MapFcm3 {
+ public sealed class MapFcm3 : IMapFile {
+
const uint Identifier = 0x0FC2AF40;
const byte Revision = 13;
+
+ public override bool SupportsLoading {
+ get { return true; }
+ }
+
+ public override bool SupportsSaving {
+ get { return true; }
+ }
- public static byte[] Load( Stream stream, Game game, out int width, out int height, out int length ) {
+ public override byte[] Load( Stream stream, Game game, out int width, out int height, out int length ) {
BinaryReader reader = new BinaryReader( stream );
if( reader.ReadInt32() != Identifier || reader.ReadByte() != Revision ) {
throw new InvalidDataException( "Unexpected constant in .fcm file" );
@@ -20,9 +29,9 @@ namespace ClassicalSharp {
height = reader.ReadInt16();
length = reader.ReadInt16();
- int posX = reader.ReadInt32();
- int posY = reader.ReadInt32();
- int posZ = reader.ReadInt32();
+ game.LocalPlayer.SpawnPoint.X = reader.ReadInt32() / 32f;
+ game.LocalPlayer.SpawnPoint.Y = reader.ReadInt32() / 32f;
+ game.LocalPlayer.SpawnPoint.Z = reader.ReadInt32() / 32f;
byte yaw = reader.ReadByte();
byte pitch = reader.ReadByte();
@@ -71,7 +80,7 @@ namespace ClassicalSharp {
}
}
- public static void Save( Stream stream, Game game ) {
+ public override void Save( Stream stream, Game game ) {
BinaryWriter writer = new BinaryWriter( stream );
LocalPlayer p = game.LocalPlayer;
Map map = game.Map;
diff --git a/ClassicalSharp/Map/MapLvl.cs b/ClassicalSharp/Map/MapLvl.cs
deleted file mode 100644
index 33a42cbe8..000000000
--- a/ClassicalSharp/Map/MapLvl.cs
+++ /dev/null
@@ -1,142 +0,0 @@
-using System;
-using System.IO;
-using System.IO.Compression;
-using System.Text;
-
-namespace ClassicalSharp {
-
- public sealed class MapLvl {
- const uint Identifier = 0x88B71B27;
- const byte Revision = 2;
- const ushort streamMagic = 0xEDAC;
- const ushort streamVersion = 0x0500;
-
- public byte[] Load( Stream stream, Game game, out int width, out int height, out int length ) {
- using( GZipStream decompressed = new GZipStream( stream, CompressionMode.Decompress ) ) {
- BinaryReader reader = new BinaryReader( decompressed );
- if( reader.ReadUInt32() != Identifier || reader.ReadByte() != Revision ) {
- throw new InvalidDataException( "Unexpected constant in .lvl file" );
- }
-
- // Java serialization constants
- if( reader.ReadUInt16() != streamMagic || reader.ReadUInt16() != streamVersion ) {
- throw new InvalidDataException( "Unexpected java serialisation constant(s)." );
- }
-
- int typeCode = 0;
- while( ( typeCode = decompressed.ReadByte() ) != -1 ) {
- HandleTypeCode( typeCode, reader );
- }
- }
- width = 0;
- length = 0;
- height = 0;
- return null;
- }
-
- void HandleTypeCode( int typeCode, BinaryReader reader ) {
- switch( typeCode ) {
-
- case 0x42:
- ReadPrimField( reader, FieldType.Byte ); break;
- case 0x43:
- ReadPrimField( reader, FieldType.Char ); break;
- case 0x44:
- ReadPrimField( reader, FieldType.Double ); break;
- case 0x46:
- ReadPrimField( reader, FieldType.Float ); break;
- case 0x49:
- ReadPrimField( reader, FieldType.Integer ); break;
- case 0x4A:
- ReadPrimField( reader, FieldType.Long ); break;
- case 0x53:
- ReadPrimField( reader, FieldType.Short ); break;
- case 0x5A:
- ReadPrimField( reader, FieldType.Boolean ); break;
-
- case 0x4C:
- ReadPrimField( reader, FieldType.Object ); break;
- case 0x5B:
- ReadPrimField( reader, FieldType.Array ); break;
-
- case 0x70: // TC_NULL
- break;
-
- case 0x71: // TC_REFERENCE
- reader.ReadInt32(); // handle
- break;
-
- case 0x72: // TC_CLASSDESC
- {
- string className = ReadString( reader );
- reader.ReadUInt64(); // serial identifier
- reader.ReadByte(); // various flags
- fields = new Field[(reader.ReadByte() << 8) | reader.ReadByte()];
- } break;
-
- case 0x73: // TC_OBJECT
- break;
-
- case 0x74: // TC_STRING
- string value = ReadString( reader );
- break;
-
- case 0x75: // TC_ARRAY
- break;
-
- case 0x76: // TC_CLASS
- break;
-
- case 0x77: // TC_BLOCKDATA
- break;
-
- case 0x78: // TC_ENDBLOCKDATA
- break;
-
- case 0x79: // TC_RESET
- break;
-
- case 0x7A: // TC_BLOCKDATALONG
- break;
-
- case 0x7B: // TC_EXCEPTION
- break;
-
- case 0x7C: // TC_LONGSTRING
- break;
-
- case 0x7D: // TC_PROXYCLASSDESC
- break;
-
- case 0x7E: // TC_ENUM
- break;
-
- }
- }
-
- enum FieldType {
- Byte, Char, Double, Float, Integer, Long,
- Short, Boolean, Object, Array,
- }
-
- struct Field {
- public string Name;
- public FieldType Type;
- }
-
- static string ReadString( BinaryReader reader ) {
- int len = ( reader.ReadByte() << 8 ) | reader.ReadByte();
- byte[] data = reader.ReadBytes( len );
- return Encoding.ASCII.GetString( data );
- }
-
- Field[] fields;
- int fieldIndex;
- void ReadPrimField( BinaryReader reader, FieldType type ) {
- Field field;
- field.Name = ReadString( reader );
- field.Type = type;
- fields[fieldIndex++] = field;
- }
- }
-}
\ No newline at end of file
diff --git a/ClassicalSharp/Singleplayer/Commands.cs b/ClassicalSharp/Singleplayer/Commands.cs
index 243981aba..4ae1b096a 100644
--- a/ClassicalSharp/Singleplayer/Commands.cs
+++ b/ClassicalSharp/Singleplayer/Commands.cs
@@ -48,7 +48,9 @@ namespace ClassicalSharp.Singleplayer {
Name = "LoadMap";
Help = new [] {
"&a/client loadmap [filename]",
- "&bfilename: &eLoads a fcm map from the specified filename.",
+ "&bfilename: &eLoads a map from the specified filename.",
+ "&eSupported formats are .fcm (fCraft map) and ",
+ "&e.dat (Original classic map or WoM saved map)",
};
}
@@ -56,17 +58,31 @@ namespace ClassicalSharp.Singleplayer {
string path = reader.NextAll();
if( String.IsNullOrEmpty( path ) ) return;
+ IMapFile mapFile;
+ if( path.EndsWith( ".dat" ) ) {
+ mapFile = new MapDat();
+ } else if( path.EndsWith( ".fcm" ) ) {
+ mapFile = new MapFcm3();
+ } else {
+ game.AddChat( "&e/client loadmap: Map format of file \"" + path + "\" not supported" );
+ return;
+ }
+
try {
using( FileStream fs = new FileStream( path, FileMode.Open, FileAccess.Read, FileShare.Read ) ) {
int width, height, length;
game.Map.Reset();
- byte[] blocks = MapFcm3.Load( fs, game, out width, out height, out length );
+ byte[] blocks = mapFile.Load( fs, game, out width, out height, out length );
game.Map.UseRawMap( blocks, width, height, length );
game.RaiseOnNewMapLoaded();
+
+ LocalPlayer p = game.LocalPlayer;
+ LocationUpdate update = LocationUpdate.MakePos( p.SpawnPoint, false );
+ p.SetLocation( update, false );
}
} catch( FileNotFoundException ) {
- game.AddChat( "&e/client load: Couldn't find file \"" + path + "\"" );
+ game.AddChat( "&e/client loadmap: Couldn't find file \"" + path + "\"" );
}
}
}
@@ -86,7 +102,8 @@ namespace ClassicalSharp.Singleplayer {
if( String.IsNullOrEmpty( path ) ) return;
using( FileStream fs = new FileStream( path, FileMode.CreateNew, FileAccess.Write ) ) {
- MapFcm3.Save( fs, game );
+ MapFcm3 map = new MapFcm3();
+ map.Save( fs, game );
}
}
}
diff --git a/readme.md b/readme.md
index ac9762bc3..2e5551c59 100644
--- a/readme.md
+++ b/readme.md
@@ -7,10 +7,10 @@ You can get the latest binaries [here](https://github.com/UnknownShadow200/Class
* Works with both minecraft.net and classicube.net accounts.
* Lightweight, minimal memory usage compared to the standard client.
* Works with effectively all graphics cards that support OpenGL.
+* Provides single-player support. (only flatgrass generator, but can load .dat and .fcm maps)
It does not:
* Work with 'modern/premium' Minecraft servers.
-* Provide single-player support. (But I am working on adding it)
#### Requirements
* Windows: .NET framework 2.0 or Mono. (Vista and later have .NET framework 2.0 built in)
@@ -27,7 +27,8 @@ The simple way to use ClassicalSharp is to use the launcher application. You can
Note that the first time you run the launcher, a dialog box will pop up saying: *"Some required resources weren't found. Would you like to download them now?"* Just click OK.
(This is necessary because I cannot legally redistribute the assets of Minecraft Classic with the application)
-*Alternatively, you can pass command line arguments directly to the client. These are expected to be in the form: ` `, where skin server is optional.*
+*Alternatively, you can pass command line arguments to run in multiplayer directly to the client.
+These are expected to be in the form: ` `, where skin server is optional.*
#### Key combinations
* Press escape (after joining a world) to switch to the pause menu.