diff --git a/ClassicalSharp/ClassicalSharp.csproj b/ClassicalSharp/ClassicalSharp.csproj
index 93eb1b6f4..31a9ed0e9 100644
--- a/ClassicalSharp/ClassicalSharp.csproj
+++ b/ClassicalSharp/ClassicalSharp.csproj
@@ -115,6 +115,7 @@
+
diff --git a/ClassicalSharp/Map/MapFcm3.cs b/ClassicalSharp/Map/MapFcm3.cs
new file mode 100644
index 000000000..f47ecdcae
--- /dev/null
+++ b/ClassicalSharp/Map/MapFcm3.cs
@@ -0,0 +1,134 @@
+// Part of fCraft | Copyright (c) 2009-2014 Matvei Stefarov | BSD-3 | See LICENSE.txt
+using System;
+using System.IO;
+using System.IO.Compression;
+using System.Text;
+
+namespace ClassicalSharp {
+
+ public sealed class MapFcm3 {
+ const uint Identifier = 0x0FC2AF40;
+ const byte Revision = 13;
+
+ public static 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" );
+ }
+
+ width = reader.ReadInt16();
+ height = reader.ReadInt16();
+ length = reader.ReadInt16();
+
+ int posX = reader.ReadInt32();
+ int posY = reader.ReadInt32();
+ int posZ = reader.ReadInt32();
+ byte yaw = reader.ReadByte();
+ byte pitch = reader.ReadByte();
+
+ reader.ReadUInt32(); // date modified
+ reader.ReadUInt32(); // date created
+ reader.ReadBytes( 16 ); // guid
+ reader.ReadBytes( 26 ); // layer index
+ int metaSize = reader.ReadInt32();
+
+ using( DeflateStream ds = new DeflateStream( stream, CompressionMode.Decompress ) ) {
+ reader = new BinaryReader( ds );
+ for( int i = 0; i < metaSize; i++ ) {
+ string group = ReadString( reader );
+ string key = ReadString( reader );
+ string value = ReadString( reader );
+ Console.WriteLine( group + "," + key + "," + value );
+ }
+ byte[] blocks = new byte[width * height * length];
+ int read = ds.Read( blocks, 0, blocks.Length );
+ return blocks;
+ }
+ }
+
+ public static void Save( Stream stream, Game game ) {
+ BinaryWriter writer = new BinaryWriter( stream );
+ LocalPlayer p = game.LocalPlayer;
+ Map map = game.Map;
+
+ writer.Write( Identifier );
+ writer.Write( Revision );
+
+ writer.Write( (short)map.Width );
+ writer.Write( (short)map.Height );
+ writer.Write( (short)map.Length );
+
+ writer.Write( (int)( p.SpawnPoint.X * 32 ) );
+ writer.Write( (int)( p.SpawnPoint.Y * 32 ) );
+ writer.Write( (int)( p.SpawnPoint.Z * 32 ) );
+
+ writer.Write( (byte)0 ); // spawn R
+ writer.Write( (byte)0 ); // spawn L
+
+ writer.Write( (uint)0 ); // date modified
+ writer.Write( (uint)0 ); // date created
+
+ writer.Write( new byte[16] ); // map guid
+ writer.Write( (byte)1 ); // layer count
+
+ long indexOffset = stream.Position;
+ writer.Seek( 25, SeekOrigin.Current );
+ writer.Write( 9 ); // meta count
+
+ int compressedLength;
+ long offset;
+ using( DeflateStream ds = new DeflateStream( stream, CompressionMode.Compress, true ) ) {
+ WriteMetadata( ds, game );
+ offset = stream.Position;
+
+ ds.Write( map.mapData, 0, map.mapData.Length );
+ compressedLength = (int)( stream.Position - offset );
+ }
+
+ writer.BaseStream.Seek( indexOffset, SeekOrigin.Begin );
+ writer.Write( (byte)0 );
+ writer.Write( offset );
+ writer.Write( compressedLength );
+ writer.Write( 0 );
+ writer.Write( 1 );
+ writer.Write( map.mapData.Length );
+ }
+
+ static void WriteMetadata( Stream stream, Game game ) {
+ BinaryWriter writer = new BinaryWriter( stream );
+ LocalPlayer p = game.LocalPlayer;
+ Map map = game.Map;
+
+ WriteMetadataEntry( writer, "SkyCol", map.SkyCol.ToArgb() );
+ WriteMetadataEntry( writer, "CloudsCol", map.CloudsCol.ToArgb() );
+ WriteMetadataEntry( writer, "FogCol", map.FogCol.ToArgb() );
+
+ WriteMetadataEntry( writer, "ClickDist", (int)( p.ReachDistance * 32 ) );
+ WriteMetadataEntry( writer, "SunLight", map.Sunlight.ToArgb() );
+ WriteMetadataEntry( writer, "ShadowLight", map.Shadowlight.ToArgb() );
+
+ WriteMetadataEntry( writer, "Weather", (int)map.Weather );
+ WriteMetadataEntry( writer, "SidesBlock", (int)map.SidesBlock );
+ WriteMetadataEntry( writer, "EdgeBlock", (int)map.EdgeBlock );
+ }
+
+ static string ReadString( BinaryReader reader ) {
+ int length = reader.ReadUInt16();
+ byte[] data = reader.ReadBytes( length );
+ return Encoding.ASCII.GetString( data );
+ }
+
+ static void WriteString( BinaryWriter writer, string str ) {
+ byte[] data = Encoding.ASCII.GetBytes( str );
+ writer.Write( (ushort)data.Length );
+ writer.Write( data );
+ }
+
+ static void WriteMetadataEntry( BinaryWriter writer, string key, int value ) {
+ WriteString( writer, "CS_Client" );
+ WriteString( writer, key );
+ WriteString( writer, value.ToString() );
+ }
+ }
+}
\ No newline at end of file
diff --git a/ClassicalSharp/Singleplayer/Commands.cs b/ClassicalSharp/Singleplayer/Commands.cs
index 98f7a8da8..baac3a7be 100644
--- a/ClassicalSharp/Singleplayer/Commands.cs
+++ b/ClassicalSharp/Singleplayer/Commands.cs
@@ -1,4 +1,5 @@
using System;
+using System.IO;
using ClassicalSharp.Commands;
namespace ClassicalSharp.Singleplayer {
@@ -40,4 +41,57 @@ namespace ClassicalSharp.Singleplayer {
}
}
}
+
+ public sealed class LoadMapCommand : Command {
+
+ public LoadMapCommand() {
+ Name = "LoadMap";
+ Help = new [] {
+ "&a/client loadmap [filename]",
+ "&bfilename: &eLoads a fcm map from the specified filename.",
+ };
+ }
+
+ public override void Execute( CommandReader reader ) {
+ string path = reader.NextAll();
+ if( String.IsNullOrEmpty( path ) ) return;
+
+ path = Path.GetFileName( path );
+ try {
+ using( FileStream fs = File.OpenRead( path ) ) {
+ int width, height, length;
+ game.Map.Reset();
+ game.RaiseOnNewMap();
+ game.SelectionManager.Dispose();
+
+ byte[] blocks = MapFcm3.Load( fs, game, out width, out height, out length );
+ game.Map.UseRawMap( blocks, width, height, length );
+ game.RaiseOnNewMapLoaded();
+ }
+ } catch( FileNotFoundException ) {
+ game.AddChat( "&e/client load: Couldn't find file \"" + path + "\"" );
+ }
+ }
+ }
+
+ public sealed class SaveMapCommand : Command {
+
+ public SaveMapCommand() {
+ Name = "SaveMap";
+ Help = new [] {
+ "&a/client savemap [filename]",
+ "&bfilename: &eSpecifies name of the file to save the map as.",
+ };
+ }
+
+ public override void Execute( CommandReader reader ) {
+ string path = reader.NextAll();
+ if( String.IsNullOrEmpty( path ) ) return;
+
+ path = Path.GetFileName( path );
+ using( FileStream fs = File.Create( path ) ) {
+ MapFcm3.Save( fs, game );
+ }
+ }
+ }
}
diff --git a/ClassicalSharp/Singleplayer/Server.cs b/ClassicalSharp/Singleplayer/Server.cs
index 8b7e7cd84..2ce796cb8 100644
--- a/ClassicalSharp/Singleplayer/Server.cs
+++ b/ClassicalSharp/Singleplayer/Server.cs
@@ -25,6 +25,8 @@ namespace ClassicalSharp.Singleplayer {
NewMap();
MakeMap( 128, 128, 128 );
game.CommandManager.RegisterCommand( new GenerateCommand() );
+ game.CommandManager.RegisterCommand( new SaveMapCommand() );
+ game.CommandManager.RegisterCommand( new LoadMapCommand() );
}
public override void SendChat( string text ) {