Adds BufferReader and BufferWriter. Updates UOP handling. (#101)

This commit is contained in:
Kamron Batman 2020-04-12 21:33:24 -07:00 committed by GitHub
parent 2c0d97cd36
commit 038c3be258
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
98 changed files with 2458 additions and 2185 deletions

View file

@ -19,91 +19,203 @@
***************************************************************************/
using System;
using System.Buffers;
using System.Collections.Generic;
using System.IO;
using Server.Network;
namespace Server
{
public static class MultiData
{
private static readonly MultiComponentList[] m_Components;
public static Dictionary<int, MultiComponentList> Components { get; } = new Dictionary<int, MultiComponentList>();
private static readonly FileStream m_Index;
private static readonly FileStream m_Stream;
private static readonly BinaryReader m_IndexReader;
private static readonly BinaryReader m_StreamReader;
private static readonly bool UsingUOPFormat;
static MultiData()
{
string multiUOPPath = Core.FindDataFile("MultiCollection.uop");
if (File.Exists(multiUOPPath))
{
LoadUOP(multiUOPPath);
UsingUOPFormat = true;
return;
}
string idxPath = Core.FindDataFile("multi.idx");
string mulPath = Core.FindDataFile("multi.mul");
if (File.Exists(idxPath) && File.Exists(mulPath))
{
m_Index = new FileStream(idxPath, FileMode.Open, FileAccess.Read, FileShare.Read);
m_IndexReader = new BinaryReader(m_Index);
var idx = new FileStream(idxPath, FileMode.Open, FileAccess.Read, FileShare.Read);
m_IndexReader = new BinaryReader(idx);
m_Stream = new FileStream(mulPath, FileMode.Open, FileAccess.Read, FileShare.Read);
m_StreamReader = new BinaryReader(m_Stream);
m_Components = new MultiComponentList[(int)(m_Index.Length / 12)];
var stream = new FileStream(mulPath, FileMode.Open, FileAccess.Read, FileShare.Read);
m_StreamReader = new BinaryReader(stream);
string vdPath = Core.FindDataFile("verdata.mul");
if (File.Exists(vdPath))
if (!File.Exists(vdPath)) return;
using FileStream fs = new FileStream(vdPath, FileMode.Open, FileAccess.Read, FileShare.Read);
BinaryReader bin = new BinaryReader(fs);
int count = bin.ReadInt32();
for (int i = 0; i < count; ++i)
{
using FileStream fs = new FileStream(vdPath, FileMode.Open, FileAccess.Read, FileShare.Read);
BinaryReader bin = new BinaryReader(fs);
int file = bin.ReadInt32();
int index = bin.ReadInt32();
int lookup = bin.ReadInt32();
int length = bin.ReadInt32();
bin.ReadInt32(); // extra
int count = bin.ReadInt32();
for (int i = 0; i < count; ++i)
if (file == 14 && index >= 0 && lookup >= 0 && length > 0)
{
int file = bin.ReadInt32();
int index = bin.ReadInt32();
int lookup = bin.ReadInt32();
int length = bin.ReadInt32();
int extra = bin.ReadInt32();
bin.BaseStream.Seek(lookup, SeekOrigin.Begin);
if (file == 14 && index >= 0 && index < m_Components.Length && lookup >= 0 && length > 0)
{
bin.BaseStream.Seek(lookup, SeekOrigin.Begin);
Components[index] = new MultiComponentList(bin, length / 12);
m_Components[index] = new MultiComponentList(bin, length / 12);
bin.BaseStream.Seek(24 + i * 20, SeekOrigin.Begin);
}
bin.BaseStream.Seek(24 + i * 20, SeekOrigin.Begin);
}
bin.Close();
}
bin.Close();
}
else
{
Console.WriteLine("Warning: Multi data files not found");
m_Components = new MultiComponentList[0];
}
}
public static MultiComponentList GetComponents(int multiID)
{
MultiComponentList mcl;
if (multiID >= 0 && multiID < m_Components.Length)
{
mcl = m_Components[multiID];
multiID &= 0x3FFF;
if (mcl == null)
m_Components[multiID] = mcl = Load(multiID);
}
if (Components.ContainsKey(multiID))
mcl = Components[multiID];
else if (!UsingUOPFormat)
Components[multiID] = mcl = Load(multiID);
else
{
mcl = MultiComponentList.Empty;
}
return mcl;
}
public static void LoadUOP(string path)
{
var stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read);
BinaryReader streamReader = new BinaryReader(stream);
// Head Information Start
if (streamReader.ReadInt32() != 0x0050594D) // Not a UOP Files
return;
if (streamReader.ReadInt32() > 5) // Bad Version
return;
// Multi ID List Array Start
UOPHash.BuildChunkIDs(out var chunkIds);
// Multi ID List Array End
streamReader.ReadUInt32(); // format timestamp? 0xFD23EC43
long startAddress = streamReader.ReadInt64();
streamReader.ReadInt32();
streamReader.ReadInt32();
stream.Seek(startAddress, SeekOrigin.Begin); // Head Information End
long nextBlock;
do
{
int blockFileCount = streamReader.ReadInt32();
nextBlock = streamReader.ReadInt64();
int index = 0;
do
{
long offset = streamReader.ReadInt64();
int headerSize = streamReader.ReadInt32(); // header length
int compressedSize = streamReader.ReadInt32(); // compressed size
int decompressedSize = streamReader.ReadInt32(); // decompressed size
ulong filehash = streamReader.ReadUInt64(); // filename hash (HashLittle2)
streamReader.ReadUInt32();
short compressionMethod = streamReader.ReadInt16(); // compression method (0 = none, 1 = zlib)
index++;
if (offset == 0 || decompressedSize == 0 || filehash == 0x126D1E99DDEDEE0A) // Exclude housing.bin
continue;
chunkIds.TryGetValue(filehash, out var chunkID);
long position = stream.Position; // save current position
stream.Seek(offset + headerSize, SeekOrigin.Begin);
Span<byte> sourceData = new byte[compressedSize];
if (stream.Read(sourceData) != compressedSize)
continue;
Span<byte> data;
if (compressionMethod == 1)
{
data = new byte[decompressedSize];
Compression.Unpack(data, ref decompressedSize, sourceData, compressedSize);
}
else
data = sourceData;
var tileList = new List<MultiTileEntry>();
// Skip the first 4 bytes
BufferReader<byte> reader = new BufferReader<byte>(data);
reader.Advance(4); // ???
reader.TryReadLittleEndian(out uint count);
for (uint i = 0; i < count; i++)
{
reader.TryReadLittleEndian(out ushort itemid);
reader.TryReadLittleEndian(out short x);
reader.TryReadLittleEndian(out short y);
reader.TryReadLittleEndian(out short z);
reader.TryReadLittleEndian(out ushort flagValue);
TileFlag tileFlag = flagValue switch
{
1 => TileFlag.None,
257 => TileFlag.Generic,
_ => TileFlag.Background // 0
};
reader.TryReadLittleEndian(out uint clilocsCount);
reader.Advance(clilocsCount * 4); // bypass binary block
tileList.Add(new MultiTileEntry(itemid, x, y, z, tileFlag));
}
Components[chunkID] = new MultiComponentList(tileList);
stream.Seek(position, SeekOrigin.Begin); // back to position
}
while (index < blockFileCount);
}
while (stream.Seek(nextBlock, SeekOrigin.Begin) != 0);
}
// TODO: Change this to read the file all during load time
public static MultiComponentList Load(int multiID)
{
try
@ -131,9 +243,9 @@ namespace Server
{
public ushort m_ItemID;
public short m_OffsetX, m_OffsetY, m_OffsetZ;
public int m_Flags;
public TileFlag m_Flags;
public MultiTileEntry(ushort itemID, short xOffset, short yOffset, short zOffset, int flags)
public MultiTileEntry(ushort itemID, short xOffset, short yOffset, short zOffset, TileFlag flags)
{
m_ItemID = itemID;
m_OffsetX = xOffset;
@ -205,7 +317,7 @@ namespace Server
allTiles[i].m_OffsetX = reader.ReadShort();
allTiles[i].m_OffsetY = reader.ReadShort();
allTiles[i].m_OffsetZ = reader.ReadShort();
allTiles[i].m_Flags = reader.ReadInt();
allTiles[i].m_Flags = (TileFlag)reader.ReadInt();
}
else
for (int i = 0; i < length; ++i)
@ -214,7 +326,7 @@ namespace Server
allTiles[i].m_OffsetX = reader.ReadShort();
allTiles[i].m_OffsetY = reader.ReadShort();
allTiles[i].m_OffsetZ = reader.ReadShort();
allTiles[i].m_Flags = reader.ReadInt();
allTiles[i].m_Flags = (TileFlag)reader.ReadInt();
}
TileList[][] tiles = new TileList[Width][];
@ -253,10 +365,11 @@ namespace Server
allTiles[i].m_OffsetX = reader.ReadInt16();
allTiles[i].m_OffsetY = reader.ReadInt16();
allTiles[i].m_OffsetZ = reader.ReadInt16();
allTiles[i].m_Flags = reader.ReadInt32();
if (PostHSFormat)
reader.ReadInt32(); // ??
allTiles[i].m_Flags = (TileFlag)reader.ReadUInt64();
else
allTiles[i].m_Flags = (TileFlag)reader.ReadUInt32();
MultiTileEntry e = allTiles[i];
@ -306,6 +419,63 @@ namespace Server
Tiles[x][y] = tiles[x][y].ToArray();
}
public MultiComponentList(List<MultiTileEntry> list)
{
var allTiles = List = new MultiTileEntry[list.Count];
for (int i = 0; i < list.Count; ++i)
{
allTiles[i].m_ItemID = list[i].m_ItemID;
allTiles[i].m_OffsetX = list[i].m_OffsetX;
allTiles[i].m_OffsetY = list[i].m_OffsetY;
allTiles[i].m_OffsetZ = list[i].m_OffsetZ;
allTiles[i].m_Flags = list[i].m_Flags;
MultiTileEntry e = allTiles[i];
if (i == 0 || e.m_Flags != 0)
{
if (e.m_OffsetX < m_Min.m_X) m_Min.m_X = e.m_OffsetX;
if (e.m_OffsetY < m_Min.m_Y) m_Min.m_Y = e.m_OffsetY;
if (e.m_OffsetX > m_Max.m_X) m_Max.m_X = e.m_OffsetX;
if (e.m_OffsetY > m_Max.m_Y) m_Max.m_Y = e.m_OffsetY;
}
}
Center = new Point2D(-m_Min.m_X, -m_Min.m_Y);
Width = (m_Max.m_X - m_Min.m_X) + 1;
Height = (m_Max.m_Y - m_Min.m_Y) + 1;
var tiles = new TileList[Width][];
Tiles = new StaticTile[Width][][];
for (int x = 0; x < Width; ++x)
{
tiles[x] = new TileList[Height];
Tiles[x] = new StaticTile[Height][];
for (int y = 0; y < Height; ++y) tiles[x][y] = new TileList();
}
for (int i = 0; i < allTiles.Length; ++i)
if (i == 0 || allTiles[i].m_Flags != 0)
{
int xOffset = allTiles[i].m_OffsetX + Center.m_X;
int yOffset = allTiles[i].m_OffsetY + Center.m_Y;
int itemID = ((allTiles[i].m_ItemID & TileData.MaxItemValue) | 0x10000);
tiles[xOffset][yOffset].Add((ushort)itemID, (sbyte)allTiles[i].m_OffsetZ);
}
for (int x = 0; x < Width; ++x)
for (int y = 0; y < Height; ++y)
Tiles[x][y] = tiles[x][y].ToArray();
}
private MultiComponentList()
{
Tiles = new StaticTile[0][][];
@ -368,7 +538,7 @@ namespace Server
for (int i = 0; i < oldList.Length; ++i)
newList[i] = oldList[i];
newList[oldList.Length] = new MultiTileEntry((ushort)itemID, (short)x, (short)y, (short)z, 1);
newList[oldList.Length] = new MultiTileEntry((ushort)itemID, (short)x, (short)y, (short)z, TileFlag.Background);
List = newList;
@ -552,7 +722,7 @@ namespace Server
if (vy > m_Max.m_Y)
m_Max.m_Y = vy;
List[index++] = new MultiTileEntry((ushort)tile.ID, (short)vx, (short)vy, (short)tile.Z, 1);
List[index++] = new MultiTileEntry((ushort)tile.ID, (short)vx, (short)vy, (short)tile.Z, TileFlag.Background);
}
}
}
@ -578,8 +748,86 @@ namespace Server
writer.Write(ent.m_OffsetX);
writer.Write(ent.m_OffsetY);
writer.Write(ent.m_OffsetZ);
writer.Write(ent.m_Flags);
writer.Write((int)ent.m_Flags);
}
}
}
}
public static class UOPHash
{
public static void BuildChunkIDs(out Dictionary<ulong, int> chunkIds)
{
const int maxId = 0x10000;
chunkIds = new Dictionary<ulong, int>();
for (int i = 0; i < maxId; ++i)
chunkIds[HashLittle2($"build/multicollection/{i:000000}.bin")] = i;
}
private static ulong HashLittle2(string s)
{
int length = s.Length;
uint b, c;
uint a = b = c = 0xDEADBEEF + (uint)length;
int k = 0;
while (length > 12)
{
a += s[k];
a += (uint)s[k + 1] << 8;
a += (uint)s[k + 2] << 16;
a += (uint)s[k + 3] << 24;
b += s[k + 4];
b += (uint)s[k + 5] << 8;
b += (uint)s[k + 6] << 16;
b += (uint)s[k + 7] << 24;
c += s[k + 8];
c += (uint)s[k + 9] << 8;
c += (uint)s[k + 10] << 16;
c += (uint)s[k + 11] << 24;
a -= c; a ^= (c << 4) | (c >> 28); c += b;
b -= a; b ^= (a << 6) | (a >> 26); a += c;
c -= b; c ^= (b << 8) | (b >> 24); b += a;
a -= c; a ^= (c << 16) | (c >> 16); c += b;
b -= a; b ^= (a << 19) | (a >> 13); a += c;
c -= b; c ^= (b << 4) | (b >> 28); b += a;
length -= 12;
k += 12;
}
if (length != 0)
{
switch (length)
{
case 12: c += (uint)s[k + 11] << 24; goto case 11;
case 11: c += (uint)s[k + 10] << 16; goto case 10;
case 10: c += (uint)s[k + 9] << 8; goto case 9;
case 9: c += s[k + 8]; goto case 8;
case 8: b += (uint)s[k + 7] << 24; goto case 7;
case 7: b += (uint)s[k + 6] << 16; goto case 6;
case 6: b += (uint)s[k + 5] << 8; goto case 5;
case 5: b += s[k + 4]; goto case 4;
case 4: a += (uint)s[k + 3] << 24; goto case 3;
case 3: a += (uint)s[k + 2] << 16; goto case 2;
case 2: a += (uint)s[k + 1] << 8; goto case 1;
case 1: a += s[k]; break;
}
c ^= b; c -= (b << 14) | (b >> 18);
a ^= c; a -= (c << 11) | (c >> 21);
b ^= a; b -= (a << 25) | (a >> 7);
c ^= b; c -= (b << 16) | (b >> 16);
a ^= c; a -= (c << 4) | (c >> 28);
b ^= a; b -= (a << 14) | (a >> 18);
c ^= b; c -= (b << 24) | (b >> 8);
}
return ((ulong)b << 32) | c;
}
}
}