From fbf95d2eacda9c45dfc484d8b18cba19a3d07145 Mon Sep 17 00:00:00 2001 From: UnknownShadow200 Date: Fri, 26 Feb 2016 21:06:51 +1100 Subject: [PATCH] Created Classic BlockDB draft (markdown) --- Classic-BlockDB-draft.md | 157 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 157 insertions(+) create mode 100644 Classic-BlockDB-draft.md diff --git a/Classic-BlockDB-draft.md b/Classic-BlockDB-draft.md new file mode 100644 index 0000000..9857ddc --- /dev/null +++ b/Classic-BlockDB-draft.md @@ -0,0 +1,157 @@ +```CSharp +using System; +using System.IO; +using System.Runtime.InteropServices; + +namespace ClassicBlockDB { + + public unsafe sealed class BlockDB { + + public ushort Width, Height, Length; + sbyte referenceOffset = 30; + public DateTime ReferenceTime; + int padding = 0; + const byte formatVersion = 1; + const int bufferSize = 64 * 1024; + + public void WriteHeader(BinaryWriter w) { + w.Write((byte)'c'); w.Write((byte)'b'); w.Write((byte)'d'); w.Write((byte)'b'); + w.Write(formatVersion); + w.Write(referenceOffset); + w.Write(Width); w.Write(Height); w.Write(Length); + w.Write(padding); + } + + public void ReadHeader(BinaryReader r) { + if (r.ReadByte() != 'c' || r.ReadByte() != 'b' || r.ReadByte() != 'd' || r.ReadByte() != 'b') + throw new InvalidDataException("Expected 'cbdb' for the header."); + if (r.ReadByte() > formatVersion) + throw new NotSupportedException("Only version 1 of the format is currently supported."); + + referenceOffset = r.ReadSByte(); + Width = r.ReadUInt16(); Height = r.ReadUInt16(); Length = r.ReadUInt16(); + padding = r.ReadInt32(); + ReferenceTime = new DateTime(1970 + referenceOffset, 1, 1, 1, 1, 1, DateTimeKind.Utc); + } + + public void WriteEntries(Stream s, BlockDBEntry[] entries) { + byte[] chunk = new byte[bufferSize]; + for (int i = 0; i < entries.Length; i += bufferSize / BlockDBEntry.Size) { + int count = Math.Min(bufferSize / BlockDBEntry.Size, entries.Length - i); + for (int j = 0; j < count; j++) { + BlockDBEntry entry = entries[i + j]; + int chunkIndex = j * BlockDBEntry.Size; + WriteInt32(chunk, chunkIndex, entry.PlayerID); + WriteInt32(chunk, chunkIndex + 4, entry.Index); + WriteInt32(chunk, chunkIndex + 8, entry.TimeDelta); + + chunk[chunkIndex + 12] = entry.OldBlock; + chunk[chunkIndex + 13] = entry.NewBlock; + chunk[chunkIndex + 14] = entry.Context; + chunk[chunkIndex + 15] = entry.Flags; + } + s.Write(chunk, 0, count * BlockDBEntry.Size); + } + } + + public BlockDBEntry[] ReadEntries(Stream s) { + int count = (int)((s.Length - s.Position) / BlockDBEntry.Size); + BlockDBEntry[] entries = new BlockDBEntry[count]; + byte[] chunk = new byte[bufferSize]; + + for (int i = 0; i < entries.Length; i += bufferSize / BlockDBEntry.Size) { + int read = s.Read(chunk, 0, chunk.Length); + for (int j = 0; j < read; j += BlockDBEntry.Size) { + BlockDBEntry entry = default(BlockDBEntry); + entry.PlayerID = ReadInt32(chunk, j); + entry.Index = ReadInt32(chunk, j + 4); + entry.TimeDelta = ReadInt32(chunk, j + 8); + + entry.OldBlock = chunk[j + 12]; + entry.NewBlock = chunk[j + 13]; + entry.Context = chunk[j + 14]; + entry.Flags = chunk[j + 15]; + entries[i + (j / BlockDBEntry.Size)] = entry; + } + } + return entries; + } + + public void AdjustDimensions(Stream s, ushort width, ushort height, ushort length) { + // If the new BlockDB dimensions are smaller than old, there is no point recalculating the indices. + bool updateIndices = width > Width || height > Height || length > Length; + Swap(ref width, ref Width); Swap(ref height, ref Height); Swap(ref length, ref Length); + WriteHeader(new BinaryWriter(s)); + if (!updateIndices) return; + + byte[] chunk = new byte[bufferSize]; + while (s.Position < s.Length) { + long pos = s.Position; + int read = s.Read(chunk, 0, chunk.Length); + for (int i = 0; i < read; i += BlockDBEntry.Size) { + int index = ReadInt32(chunk, i + 4); + int x = index % width; + int y = (index / width) / length; + int z = (index / width) % length; + index = x + Width * (z + y * Length); + WriteInt32(chunk, i + 4, index); + } + s.Seek(pos, SeekOrigin.Begin); + s.Write(chunk, 0, read); + } + } + + static int ReadInt32(byte[] chunk, int i) { + return chunk[i] | chunk[i + 1] << 8 | chunk[i + 2] << 16 | chunk[i + 3] << 24; + } + + static void WriteInt32(byte[] chunk, int i, int value) { + chunk[i] = (byte)value; + chunk[i + 1] = (byte)(value >> 8); + chunk[i + 2] = (byte)(value >> 16); + chunk[i + 3] = (byte)(value >> 24); + } + + static void Swap(ref ushort a, ref ushort b) { + ushort c = a; a = b; b = c; + } + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct BlockDBEntry { + public int PlayerID; // numerical player id unique for each player + public int Index; // packed index according to (width, height, length) + public int TimeDelta; // seconds since the reference point + public byte OldBlock, NewBlock; + public byte Context, Flags; + + // MCGalaxy specific flags: + // bit 0 set: old block is a physics block, not an ext tile. + // bit 1 set: new block is a physics block, not an ext tile. + + // TODO: should we be using uint16 bitflags for context, with 4 bits leftover for the software? + + public const int Size = 16; + } + + public static class BlockDBContext { + public const byte Place = 0; + public const byte Delete = 1; + public const byte Drawn = 2; + public const byte Paint = 3; + public const byte Replace = 4; + public const byte Paste = 5; + public const byte Cut = 6; + public const byte Fill = 7; + public const byte Restore = 8; + public const byte PhysicsChange = 9; + public const byte Undo = 10; + public const byte Redo = 11; + public const byte UndoPlayer = 12; + + // ProCraft specific contexts + // Portal = 127 + // Door = 128 + } +} +``` \ No newline at end of file