mirror of
https://github.com/ClassiCube/ClassiCube.git
synced 2025-09-13 09:35:23 -04:00
Add support for loading .dat files, mark single-player support as done.
This commit is contained in:
parent
41c091e832
commit
4b51c5c4c0
@ -45,7 +45,6 @@
|
||||
<DefineConstants>TRACE;</DefineConstants>
|
||||
<BaseIntermediateOutputPath>obj\</BaseIntermediateOutputPath>
|
||||
<StartAction>Project</StartAction>
|
||||
<StartArguments>wwwf null 127.0.0.1 25566</StartArguments>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Debug_DX' ">
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
@ -115,9 +114,10 @@
|
||||
<Compile Include="Ionic.Zlib\ZlibCodec.cs" />
|
||||
<Compile Include="Map\ChunkMeshBuilder.cs" />
|
||||
<Compile Include="Map\ChunkMeshBuilderTex2Col4.cs" />
|
||||
<Compile Include="Map\IMapFile.cs" />
|
||||
<Compile Include="Map\Map.cs" />
|
||||
<Compile Include="Map\MapDat.cs" />
|
||||
<Compile Include="Map\MapFcm3.cs" />
|
||||
<Compile Include="Map\MapLvl.cs" />
|
||||
<Compile Include="Model\BlockModel.cs" />
|
||||
<Compile Include="Model\ChickenModel.cs" />
|
||||
<Compile Include="Model\CreeperModel.cs" />
|
||||
|
26
ClassicalSharp/Map/IMapFile.cs
Normal file
26
ClassicalSharp/Map/IMapFile.cs
Normal file
@ -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 ) {
|
||||
}
|
||||
}
|
||||
}
|
183
ClassicalSharp/Map/MapDat.cs
Normal file
183
ClassicalSharp/Map/MapDat.cs
Normal file
@ -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() ) ); }
|
||||
}
|
||||
}
|
@ -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 static byte[] Load( Stream stream, Game game, out int width, out int height, out int length ) {
|
||||
public override bool SupportsLoading {
|
||||
get { return true; }
|
||||
}
|
||||
|
||||
public override bool SupportsSaving {
|
||||
get { return true; }
|
||||
}
|
||||
|
||||
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;
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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 );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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: `<username> <mppass> <ip> <port> <skin server>`, 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: `<username> <mppass> <ip> <port> <skin server>`, where skin server is optional.*
|
||||
|
||||
#### Key combinations
|
||||
* Press escape (after joining a world) to switch to the pause menu.
|
||||
|
Loading…
x
Reference in New Issue
Block a user