Files
abysmal-isle/Ultima/FileIndex.cs
Unstable Kitsune b918192e4e Overwrite
Complete Overwrite of the Folder with the free shard. ServUO 57.3 has been added.
2023-11-28 23:20:26 -05:00

564 lines
13 KiB
C#

#region References
using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.InteropServices;
#endregion
namespace Ultima
{
public sealed class FileIndex
{
public Entry3D[] Index { get; private set; }
public Stream Stream { get; private set; }
public long IdxLength { get; private set; }
private readonly string MulPath;
public Stream Seek(int index, out int length, out int extra, out bool patched)
{
if (index < 0 || index >= Index.Length)
{
length = extra = 0;
patched = false;
return null;
}
Entry3D e = Index[index];
if (e.lookup < 0)
{
length = extra = 0;
patched = false;
return null;
}
length = e.length & 0x7FFFFFFF;
extra = e.extra;
if ((e.length & (1 << 31)) != 0)
{
patched = true;
Verdata.Seek(e.lookup);
return Verdata.Stream;
}
if (e.length < 0)
{
length = extra = 0;
patched = false;
return null;
}
if ((Stream == null) || (!Stream.CanRead) || (!Stream.CanSeek))
{
if (MulPath == null)
{
Stream = null;
}
else
{
Stream = new FileStream(MulPath, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite);
}
}
if (Stream == null)
{
length = extra = 0;
patched = false;
return null;
}
else if (Stream.Length < e.lookup)
{
length = extra = 0;
patched = false;
return null;
}
patched = false;
Stream.Seek(e.lookup, SeekOrigin.Begin);
return Stream;
}
public bool Valid(int index, out int length, out int extra, out bool patched)
{
if (index < 0 || index >= Index.Length)
{
length = extra = 0;
patched = false;
return false;
}
Entry3D e = Index[index];
if (e.lookup < 0)
{
length = extra = 0;
patched = false;
return false;
}
length = e.length & 0x7FFFFFFF;
extra = e.extra;
if ((e.length & (1 << 31)) != 0)
{
patched = true;
return true;
}
if (e.length < 0)
{
length = extra = 0;
patched = false;
return false;
}
if ((MulPath == null) || !File.Exists(MulPath))
{
length = extra = 0;
patched = false;
return false;
}
if ((Stream == null) || (!Stream.CanRead) || (!Stream.CanSeek))
{
Stream = new FileStream(MulPath, FileMode.Open, FileAccess.Read, FileShare.Read);
}
if (Stream.Length < e.lookup)
{
length = extra = 0;
patched = false;
return false;
}
patched = false;
return true;
}
public FileIndex(string idxFile, string mulFile, int length, int file)
: this(idxFile, mulFile, null, length, file, ".dat", -1, false)
{ }
public FileIndex(
string idxFile,
string mulFile,
string uopFile,
int length,
int file,
string uopEntryExtension,
int idxLength,
bool hasExtra)
{
Index = new Entry3D[length];
string idxPath = null;
MulPath = null;
string uopPath = null;
if (Files.MulPath == null)
{
Files.LoadMulPath();
}
if (Files.MulPath.Count > 0)
{
idxPath = Files.MulPath[idxFile.ToLower()];
MulPath = Files.MulPath[mulFile.ToLower()];
if (!String.IsNullOrEmpty(uopFile) && Files.MulPath.ContainsKey(uopFile.ToLower()))
{
uopPath = Files.MulPath[uopFile.ToLower()];
}
if (String.IsNullOrEmpty(idxPath))
{
idxPath = null;
}
else
{
if (String.IsNullOrEmpty(Path.GetDirectoryName(idxPath)))
{
idxPath = Path.Combine(Files.RootDir, idxPath);
}
if (!File.Exists(idxPath))
{
idxPath = null;
}
}
if (String.IsNullOrEmpty(MulPath))
{
MulPath = null;
}
else
{
if (String.IsNullOrEmpty(Path.GetDirectoryName(MulPath)))
{
MulPath = Path.Combine(Files.RootDir, MulPath);
}
if (!File.Exists(MulPath))
{
MulPath = null;
}
}
if (String.IsNullOrEmpty(uopPath))
{
uopPath = null;
}
else
{
if (String.IsNullOrEmpty(Path.GetDirectoryName(uopPath)))
{
uopPath = Path.Combine(Files.RootDir, uopPath);
}
if (!File.Exists(uopPath))
{
uopPath = null;
}
else
{
MulPath = uopPath;
}
}
}
/* UOP files support code, written by Wyatt (c) www.ruosi.org
* idxLength variable was added for compatibility with legacy code for art (see art.cs)
* At the moment the only UOP file having entries with extra field is gumpartlegacy.uop,
* and it's two dwords in the beginning of the entry.
* It's possible that UOP can include some entries with unknown hash: not really unknown for me, but
* not useful for reading legacy entries. That's why i removed unknown hash exception throwing from this code
*/
if (MulPath != null && MulPath.EndsWith(".uop"))
{
using (var index = new FileStream(MulPath, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite))
{
Stream = new FileStream(MulPath, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite);
var fi = new FileInfo(MulPath);
string uopPattern = fi.Name.Replace(fi.Extension, "").ToLowerInvariant();
using (var br = new BinaryReader(Stream))
{
br.BaseStream.Seek(0, SeekOrigin.Begin);
if (br.ReadInt32() != 0x50594D)
{
throw new ArgumentException("Bad UOP file.");
}
br.ReadInt64(); // version + signature
long nextBlock = br.ReadInt64();
br.ReadInt32(); // block capacity
int count = br.ReadInt32();
if (idxLength > 0)
{
IdxLength = idxLength * 12;
}
var hashes = new Dictionary<ulong, int>();
for (int i = 0; i < length; i++)
{
string entryName = string.Format("build/{0}/{1:D8}{2}", uopPattern, i, uopEntryExtension);
ulong hash = HashFileName(entryName);
if (!hashes.ContainsKey(hash))
{
hashes.Add(hash, i);
}
}
br.BaseStream.Seek(nextBlock, SeekOrigin.Begin);
do
{
int filesCount = br.ReadInt32();
nextBlock = br.ReadInt64();
for (int i = 0; i < filesCount; i++)
{
long offset = br.ReadInt64();
int headerLength = br.ReadInt32();
int compressedLength = br.ReadInt32();
int decompressedLength = br.ReadInt32();
ulong hash = br.ReadUInt64();
br.ReadUInt32(); // Adler32
short flag = br.ReadInt16();
int entryLength = flag == 1 ? compressedLength : decompressedLength;
if (offset == 0)
{
continue;
}
int idx;
if (hashes.TryGetValue(hash, out idx))
{
if (idx < 0 || idx > Index.Length)
{
throw new IndexOutOfRangeException("hashes dictionary and files collection have different count of entries!");
}
Index[idx].lookup = (int)(offset + headerLength);
Index[idx].length = entryLength;
if (hasExtra)
{
long curPos = br.BaseStream.Position;
br.BaseStream.Seek(offset + headerLength, SeekOrigin.Begin);
byte[] extra = br.ReadBytes(8);
var extra1 = (ushort)((extra[3] << 24) | (extra[2] << 16) | (extra[1] << 8) | extra[0]);
var extra2 = (ushort)((extra[7] << 24) | (extra[6] << 16) | (extra[5] << 8) | extra[4]);
Index[idx].lookup += 8;
Index[idx].extra = extra1 << 16 | extra2;
br.BaseStream.Seek(curPos, SeekOrigin.Begin);
}
}
}
}
while (br.BaseStream.Seek(nextBlock, SeekOrigin.Begin) != 0);
}
}
}
else if ((idxPath != null) && (MulPath != null))
{
using (var index = new FileStream(idxPath, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite))
{
Stream = new FileStream(MulPath, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite);
var count = (int)(index.Length / 12);
IdxLength = index.Length;
GCHandle gc = GCHandle.Alloc(Index, GCHandleType.Pinned);
var buffer = new byte[index.Length];
index.Read(buffer, 0, (int)index.Length);
Marshal.Copy(buffer, 0, gc.AddrOfPinnedObject(), (int)Math.Min(IdxLength, length * 12));
gc.Free();
for (int i = count; i < length; ++i)
{
Index[i].lookup = -1;
Index[i].length = -1;
Index[i].extra = -1;
}
}
}
else
{
Stream = null;
return;
}
Entry5D[] patches = Verdata.Patches;
if (file > -1)
{
for (int i = 0; i < patches.Length; ++i)
{
Entry5D patch = patches[i];
if (patch.file == file && patch.index >= 0 && patch.index < length)
{
Index[patch.index].lookup = patch.lookup;
Index[patch.index].length = patch.length | (1 << 31);
Index[patch.index].extra = patch.extra;
}
}
}
}
public FileIndex(string idxFile, string mulFile, int file)
{
string idxPath = null;
MulPath = null;
if (Files.MulPath == null)
{
Files.LoadMulPath();
}
if (Files.MulPath.Count > 0)
{
idxPath = Files.MulPath[idxFile.ToLower()];
MulPath = Files.MulPath[mulFile.ToLower()];
if (String.IsNullOrEmpty(idxPath))
{
idxPath = null;
}
else
{
if (String.IsNullOrEmpty(Path.GetDirectoryName(idxPath)))
{
idxPath = Path.Combine(Files.RootDir, idxPath);
}
if (!File.Exists(idxPath))
{
idxPath = null;
}
}
if (String.IsNullOrEmpty(MulPath))
{
MulPath = null;
}
else
{
if (String.IsNullOrEmpty(Path.GetDirectoryName(MulPath)))
{
MulPath = Path.Combine(Files.RootDir, MulPath);
}
if (!File.Exists(MulPath))
{
MulPath = null;
}
}
}
if ((idxPath != null) && (MulPath != null))
{
using (var index = new FileStream(idxPath, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite))
{
Stream = new FileStream(MulPath, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite);
var count = (int)(index.Length / 12);
IdxLength = index.Length;
Index = new Entry3D[count];
GCHandle gc = GCHandle.Alloc(Index, GCHandleType.Pinned);
var buffer = new byte[index.Length];
index.Read(buffer, 0, (int)index.Length);
Marshal.Copy(buffer, 0, gc.AddrOfPinnedObject(), (int)index.Length);
gc.Free();
}
}
else
{
Stream = null;
Index = new Entry3D[1];
return;
}
Entry5D[] patches = Verdata.Patches;
if (file > -1)
{
for (int i = 0; i < patches.Length; ++i)
{
Entry5D patch = patches[i];
if (patch.file == file && patch.index >= 0 && patch.index < Index.Length)
{
Index[patch.index].lookup = patch.lookup;
Index[patch.index].length = patch.length | (1 << 31);
Index[patch.index].extra = patch.extra;
}
}
}
}
/// <summary>
/// Method for calculating entry hash by it's name.
/// Taken from Mythic.Package.dll
/// </summary>
/// <param name="s"></param>
/// <returns></returns>
public static ulong HashFileName(string s)
{
uint eax, ecx, edx, ebx, esi, edi;
eax = ecx = edx = ebx = esi = edi = 0;
ebx = edi = esi = (uint)s.Length + 0xDEADBEEF;
int i = 0;
for (i = 0; i + 12 < s.Length; i += 12)
{
edi = (uint)((s[i + 7] << 24) | (s[i + 6] << 16) | (s[i + 5] << 8) | s[i + 4]) + edi;
esi = (uint)((s[i + 11] << 24) | (s[i + 10] << 16) | (s[i + 9] << 8) | s[i + 8]) + esi;
edx = (uint)((s[i + 3] << 24) | (s[i + 2] << 16) | (s[i + 1] << 8) | s[i]) - esi;
edx = (edx + ebx) ^ (esi >> 28) ^ (esi << 4);
esi += edi;
edi = (edi - edx) ^ (edx >> 26) ^ (edx << 6);
edx += esi;
esi = (esi - edi) ^ (edi >> 24) ^ (edi << 8);
edi += edx;
ebx = (edx - esi) ^ (esi >> 16) ^ (esi << 16);
esi += edi;
edi = (edi - ebx) ^ (ebx >> 13) ^ (ebx << 19);
ebx += esi;
esi = (esi - edi) ^ (edi >> 28) ^ (edi << 4);
edi += ebx;
}
if (s.Length - i > 0)
{
switch (s.Length - i)
{
case 12:
esi += (uint)s[i + 11] << 24;
goto case 11;
case 11:
esi += (uint)s[i + 10] << 16;
goto case 10;
case 10:
esi += (uint)s[i + 9] << 8;
goto case 9;
case 9:
esi += s[i + 8];
goto case 8;
case 8:
edi += (uint)s[i + 7] << 24;
goto case 7;
case 7:
edi += (uint)s[i + 6] << 16;
goto case 6;
case 6:
edi += (uint)s[i + 5] << 8;
goto case 5;
case 5:
edi += s[i + 4];
goto case 4;
case 4:
ebx += (uint)s[i + 3] << 24;
goto case 3;
case 3:
ebx += (uint)s[i + 2] << 16;
goto case 2;
case 2:
ebx += (uint)s[i + 1] << 8;
goto case 1;
case 1:
ebx += s[i];
break;
}
esi = (esi ^ edi) - ((edi >> 18) ^ (edi << 14));
ecx = (esi ^ ebx) - ((esi >> 21) ^ (esi << 11));
edi = (edi ^ ecx) - ((ecx >> 7) ^ (ecx << 25));
esi = (esi ^ edi) - ((edi >> 16) ^ (edi << 16));
edx = (esi ^ ecx) - ((esi >> 28) ^ (esi << 4));
edi = (edi ^ edx) - ((edx >> 18) ^ (edx << 14));
eax = (esi ^ edi) - ((edi >> 8) ^ (edi << 24));
return ((ulong)edi << 32) | eax;
}
return ((ulong)esi << 32) | eax;
}
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct Entry3D
{
public int lookup;
public int length;
public int extra;
}
}