Skip to content
Permalink
Browse files

Implement FastMap CPE extension.

  • Loading branch information...
UnknownShadow200 committed Apr 1, 2018
1 parent 24ce8e4 commit b616f0af449873989593995f03fc45d05d00ae83
@@ -107,6 +107,9 @@ public enum CpeExt

/// <summary> Allows sending motd/handshake packets, without also needing to resend world. </summary>
InstantMOTD,

/// <summary> More optimised sending of map. </summary>
FastMap,
}


@@ -19,7 +19,8 @@ internal sealed class LevelChunkStream : Stream {
public override long Seek(long offset, SeekOrigin origin) { throw ex; }
public override void SetLength(long length) { throw ex; }

internal int index, position, length;
int index;
internal byte chunkValue;
Player player;
byte[] data = new byte[chunkSize + 4];
const int chunkSize = 1024;
@@ -61,7 +62,7 @@ internal sealed class LevelChunkStream : Stream {

void WritePacket() {
Packet.WriteI16((short)index, data, 1);
data[1027] = (byte)(100 * (float)position / length);
data[1027] = chunkValue;
player.SendNow(new Packet(data));
index = 0;
}
@@ -26,6 +26,11 @@ public PacketWriter( [NotNull] Stream stream )
}


public override void Write( int data ) {
base.Write( IPAddress.HostToNetworkOrder( (int)data ) );
}


public override void Write( string str ) {
if( str == null ) throw new ArgumentNullException( "str" );
if( str.Length > Packet.StringSize ) throw new ArgumentException( "String is too long (>64).", "str" );
@@ -217,7 +217,7 @@ public class Players {

bool NegotiateProtocolExtension() {
// write our ExtInfo and ExtEntry packets
writer.Write(Packet.MakeExtInfo("ProCraft", 29).Bytes);
writer.Write(Packet.MakeExtInfo("ProCraft", 30).Bytes);

writer.Write(Packet.MakeExtEntry(ClickDistanceExtName, 1).Bytes);
writer.Write(Packet.MakeExtEntry(CustomBlocksExtName, 1).Bytes);
@@ -256,6 +256,7 @@ public class Players {
writer.Write(Packet.MakeExtEntry(InventoryOrderExtName, 1).Bytes);

writer.Write(Packet.MakeExtEntry(InstantMOTDExtName, 1).Bytes);
writer.Write(Packet.MakeExtEntry(FastMapExtName, 1).Bytes);

// Fix for ClassiCube Client which violates the spec -
// If server supports version > 1 but client version 1, client should reply with version 1.
@@ -381,6 +382,9 @@ public class Players {
case InstantMOTDExtName:
if (version == 1) ext = CpeExt.InstantMOTD;
break;
case FastMapExtName:
if (version == 1) ext = CpeExt.FastMap;
break;
}
if (ext != CpeExt.None)
supportedExts.Add(ext);
@@ -4,6 +4,7 @@
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Net;
using System.Net.Sockets;
@@ -1115,6 +1116,10 @@ bool LoginSequence()

writer.Write(OpCode.MapBegin);
BytesSent++;
if (Supports(CpeExt.FastMap)) {
writer.Write(map.Volume);
BytesSent += 4;
}

if (Supports(CpeExt.BlockDefinitions)) {
if (oldWorld != null) {
@@ -1166,10 +1171,20 @@ bool LoginSequence()
Block maxLegal = supportsCustomBlocks ? Map.MaxCustomBlockType : Map.MaxLegalBlockType;
Logger.Log(LogType.Debug, "Player.JoinWorldNow: Sending compressed map to {0}.", Name);

if (supportsCustomBlocks && supportsBlockDefs)
map.CompressMap(this);
else
map.CompressAndConvertMap((byte)maxLegal, this);
using (LevelChunkStream dst = new LevelChunkStream(this)) {
Stream compressor = null;
try {
compressor = map.CompressMapHeader(this, dst);

if (supportsCustomBlocks && supportsBlockDefs) {
map.CompressMap(dst, compressor);
} else {
map.CompressAndConvertMap((byte)maxLegal, dst, compressor);
}
} finally {
if (compressor != null) compressor.Close();
}
}
}

void SendJoinMessage(World oldWorld, World newWorld) {
@@ -1286,10 +1301,10 @@ bool LoginSequence()
Send(Packet.MakeEnvSetMapProperty(EnvProp.WeatherSpeed, World.WeatherSpeed));
Send(Packet.MakeEnvSetMapProperty(EnvProp.WeatherFade, World.WeatherFade));

Send(Packet.MakeEnvSetMapProperty(EnvProp.SkyboxHorSpeed, World.SkyboxHorSpeed));
Send(Packet.MakeEnvSetMapProperty(EnvProp.SkyboxVerSpeed, World.SkyboxVerSpeed));
Send(Packet.MakeEnvSetMapProperty(EnvProp.SkyboxHorSpeed, World.SkyboxHorSpeed));
Send(Packet.MakeEnvSetMapProperty(EnvProp.SkyboxVerSpeed, World.SkyboxVerSpeed));

} else if (Supports(CpeExt.EnvMapAppearance2)) {
} else if (Supports(CpeExt.EnvMapAppearance2)) {
Send(Packet.MakeEnvSetMapAppearance2(World.GetTexture(), side, edge, World.GetEdgeLevel(),
World.GetCloudsHeight(), World.MaxFogDistance, HasCP437));
} else if (Supports(CpeExt.EnvMapAppearance)) {
@@ -2156,6 +2156,7 @@ public void ResetIdleTimer()
const string TwoWayPingExtName = "TwoWayPing";
const string InventoryOrderExtName = "InventoryOrder";
const string InstantMOTDExtName = "InstantMOTD";
const string FastMapExtName = "FastMap";

bool supportsBlockDefs, supportsCustomBlocks;
internal bool supportsExtPositions;
@@ -874,43 +874,45 @@ public void ClearUpdateQueue()
}

const int bufferSize = 64 * 1024;
internal void CompressMap(Player dst) {
byte[] array = Blocks;
using (LevelChunkStream ms = new LevelChunkStream(dst))
using (GZipStream compressor = new GZipStream(ms, CompressionMode.Compress, true))
{
int count = IPAddress.HostToNetworkOrder(array.Length); // convert to big endian
internal Stream CompressMapHeader(Player player, LevelChunkStream dst) {
Stream compressor = null;
if (player.Supports(CpeExt.FastMap)) {
compressor = new DeflateStream(dst, CompressionMode.Compress, true);
} else {
compressor = new GZipStream(dst, CompressionMode.Compress, true);
int count = IPAddress.HostToNetworkOrder(Volume); // convert to big endian
compressor.Write(BitConverter.GetBytes(count), 0, 4);
ms.length = array.Length;

for (int i = 0; i < array.Length; i += bufferSize) {
int len = Math.Min(bufferSize, array.Length - i);
ms.position = i;
compressor.Write(array, i, len);
}
}
return compressor;
}

internal void CompressMap(LevelChunkStream dst, Stream compressor) {
byte[] array = Blocks;
float progScale = 100.0f / array.Length;

for (int i = 0; i < array.Length; i += bufferSize) {
int len = Math.Min(bufferSize, array.Length - i);
dst.chunkValue = (byte)(i * progScale);
compressor.Write(array, i, len);
}
}

internal void CompressAndConvertMap(byte maxLegal, Player dst) {
internal void CompressAndConvertMap(byte maxLegal, LevelChunkStream dst, Stream compressor) {
byte[] array = Blocks;
float progScale = 100.0f / array.Length;

byte* fallback = stackalloc byte[256];
MakeFallbacks(fallback, maxLegal, World);
using (LevelChunkStream ms = new LevelChunkStream(dst))
using (GZipStream compressor = new GZipStream(ms, CompressionMode.Compress, true))
{
int count = IPAddress.HostToNetworkOrder(array.Length); // convert to big endian
compressor.Write(BitConverter.GetBytes(count), 0, 4);
ms.length = array.Length;

byte[] buffer = new byte[bufferSize];
for (int i = 0; i < array.Length; i += bufferSize) {
int len = Math.Min(bufferSize, array.Length - i);
for (int j = 0; j < len; j++)
buffer[j] = fallback[array[i + j]];

ms.position = i;
compressor.Write(buffer, 0, len);
byte[] buffer = new byte[bufferSize];

for (int i = 0; i < array.Length; i += bufferSize) {
int len = Math.Min(bufferSize, array.Length - i);
for (int j = 0; j < len; j++) {
buffer[j] = fallback[array[i + j]];
}

dst.chunkValue = (byte)(i * progScale);
compressor.Write(buffer, 0, len);
}
}

0 comments on commit b616f0a

Please sign in to comment.
You can’t perform that action at this time.