Skip to content

Commit

Permalink
UOPacker: Improve multi unpacking.
Browse files Browse the repository at this point in the history
  • Loading branch information
AsYlum- committed Jul 13, 2024
1 parent 1e70236 commit ef00dec
Show file tree
Hide file tree
Showing 2 changed files with 103 additions and 27 deletions.
104 changes: 80 additions & 24 deletions UoFiddler.Plugin.UopPacker/Classes/LegacyMulFileConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ public static void ToUop(string inFile, string inFileIdx, string outFile, FileTy
int tableCount = (int)Math.Ceiling((double)idxEntries.Count / tableSize);
TableEntry[] tableEntries = new TableEntry[tableSize];

string hashFormat = GetHashFormat(type, typeIndex, out int _);
string[] hashFormat = GetHashFormat(type, typeIndex, out int _);

for (int i = 0; i < tableCount; ++i)
{
Expand All @@ -162,7 +162,18 @@ public static void ToUop(string inFile, string inFileIdx, string outFile, FileTy

tableEntries[tableIdx].Offset = writer.BaseStream.Position;
tableEntries[tableIdx].Size = data.Length;
tableEntries[tableIdx].Identifier = HashLittle2(string.Format(hashFormat, idxEntries[j].Id));
// hash 906142efe9fdb38a, which is file 0009834.tga (and no others, as 7.0.59.5) use a different name format (7 digits instead of 8);
// if in newer versions more of these files will have adopted that format, someone should update this list of exceptions
// (even if this seems so much like a typo from someone from the UO development team :P)
if ((type == FileType.GumpartLegacyMul) && (idxEntries[j].Id == 9834))
{
tableEntries[tableIdx].Identifier = HashLittle2(string.Format(hashFormat[1], idxEntries[j].Id));
}
else
{
tableEntries[tableIdx].Identifier = HashLittle2(string.Format(hashFormat[0], idxEntries[j].Id));
}

tableEntries[tableIdx].Hash = HashAdler32(data);

if (type == FileType.GumpartLegacyMul)
Expand Down Expand Up @@ -224,15 +235,24 @@ public static void ToUop(string inFile, string inFileIdx, string outFile, FileTy
//
// UOP -> MUL
//
public void FromUop(string inFile, string outFile, string outFileIdx, FileType type, int typeIndex)
public void FromUop(string inFile, string outFile, string outFileIdx, FileType type, int typeIndex, string housingBinFile = "")
{
Dictionary<ulong, int> chunkIds = new Dictionary<ulong, int>();
Dictionary<ulong, int> chunkIds2 = new Dictionary<ulong, int>();

string format = GetHashFormat(type, typeIndex, out int maxId);
string[] formats = GetHashFormat(type, typeIndex, out var maxId);

for (int i = 0; i < maxId; ++i)
{
chunkIds[HashLittle2(string.Format(format, i))] = i;
chunkIds[HashLittle2(string.Format(formats[0], i))] = i;
}

if (formats[1] != string.Empty)
{
for (int i = 0; i < maxId; ++i)
{
chunkIds2[HashLittle2(string.Format(formats[1], i))] = i;
}
}

bool[] used = new bool[maxId];
Expand Down Expand Up @@ -286,19 +306,52 @@ public void FromUop(string inFile, string outFile, string outFileIdx, FileType t
continue; // skip empty entry
}

if (type == FileType.MultiCollection && offsets[i].Identifier == 0x126D1E99DDEDEE0A)
// extract housing.bin file (not really needed for muls to work but needed later to pack files back to uop)
if ((type == FileType.MultiCollection) && (offsets[i].Identifier == 0x126D1E99DDEDEE0A) && !string.IsNullOrWhiteSpace(housingBinFile))
{
continue; // skip housing.bin file
// MultiCollection.uop has the file "build/multicollection/housing.bin", which has to be handled separately
using (BinaryWriter writerBin = OpenOutput(housingBinFile))
{
stream.Seek(offsets[i].Offset + offsets[i].HeaderLength, SeekOrigin.Begin);

byte[] binData = reader.ReadBytes(offsets[i].Size);
byte[] binDataToWrite;

if (offsets[i].Compressed)
{
using ZLibStream zlib = new(new MemoryStream(binData), CompressionMode.Decompress);

byte[] decompressed = new byte[offsets[i].DecompressedSize];
zlib.ReadExactly(decompressed);
binDataToWrite = decompressed;
}
else
{
binDataToWrite = binData;
}

writerBin.Write(binDataToWrite, 0, binDataToWrite.Length);
}

continue;
}

if (!chunkIds.TryGetValue(offsets[i].Identifier, out int chunkId))
if (!chunkIds.TryGetValue( offsets[i].Identifier, out var chunkId))
{
throw new Exception("Unknown identifier encountered");
if (!chunkIds2.TryGetValue(offsets[i].Identifier, out int chunkId2))
{
throw new Exception($"Unknown identifier encountered ({offsets[i].Identifier:X})");
}
else
{
// the second collection is used because in some versions GumpartLegacyMul.uop had shorter Identifier
chunkId = chunkId2;
}
}

stream.Seek(offsets[i].Offset + offsets[i].HeaderLength, SeekOrigin.Begin);
byte[] chunkData = reader.ReadBytes(offsets[i].Size);

byte[] chunkData = reader.ReadBytes(offsets[i].Size);
if (offsets[i].Compressed)
{
using ZLibStream zlib = new(new MemoryStream(chunkData), CompressionMode.Decompress);
Expand Down Expand Up @@ -346,7 +399,7 @@ public void FromUop(string inFile, string outFile, string outFileIdx, FileType t
case FileType.MultiCollection:
{
long startPosition = mulWriter.BaseStream.Position;
WriteMultiUOPEntryToMul(mulWriter, chunkData);
WriteMultiUopEntryToMul(mulWriter, chunkData);
long endPosition = mulWriter.BaseStream.Position;

idxWriter.Write((int)(endPosition - startPosition)); // Size
Expand Down Expand Up @@ -390,8 +443,9 @@ public void FromUop(string inFile, string outFile, string outFileIdx, FileType t
}

idxWriter.Seek(i * 12, SeekOrigin.Begin);
idxWriter.Write(-1);
idxWriter.Write((long)0);

idxWriter.Write(-1); // Position (lookup)
idxWriter.Write((long)0); // Size + Extra
}
}
}
Expand Down Expand Up @@ -424,7 +478,6 @@ private static void CheckAndFixMapFiles(string outFile, FileType type, int typeI
using (var mapFile = File.Open(outFile, FileMode.Open, FileAccess.ReadWrite))
{
var sizeDiff = mapFile.Length - expectedSize;

if (sizeDiff > 0)
{
mapFile.SetLength(mapFile.Length - sizeDiff);
Expand All @@ -449,7 +502,7 @@ private static int GetExpectedMapFileSize(int typeIndex)
//
// Hash filename formats (remember: lower case!)
//
private static string GetHashFormat(FileType type, int typeIndex, out int maxId)
private static string[] GetHashFormat(FileType type, int typeIndex, out int maxId)
{
/*
* MaxID is only used for constructing a lookup table.
Expand All @@ -462,26 +515,27 @@ private static string GetHashFormat(FileType type, int typeIndex, out int maxId)
case FileType.ArtLegacyMul:
{
maxId = 0x13FDC; // UOFiddler requires this exact index length to recognize UOHS art files
return "build/artlegacymul/{0:00000000}.tga";
return ["build/artlegacymul/{0:00000000}.tga", string.Empty];
}
case FileType.GumpartLegacyMul:
{
// MaxID = 0xEF3C on 7.0.8.2
return "build/gumpartlegacymul/{0:00000000}.tga";
// maxId = 0xEF3C on 7.0.8.2
return ["build/gumpartlegacymul/{0:00000000}.tga", "build/gumpartlegacymul/{0:0000000}.tga"];
}
case FileType.MapLegacyMul:
{
// MaxID = 0x71 on 7.0.8.2 for Fel/Tram
return string.Concat("build/map", typeIndex, "legacymul/{0:00000000}.dat");
// maxId = 0x71 on 7.0.8.2 for Fel/Tram
return [string.Concat("build/map", typeIndex, "legacymul/{0:00000000}.dat"), string.Empty];
}
case FileType.SoundLegacyMul:
{
// MaxID = 0x1000 on 7.0.8.2
return "build/soundlegacymul/{0:00000000}.dat";
// maxId = 0x1000 on 7.0.8.2
return ["build/soundlegacymul/{0:00000000}.dat", string.Empty];
}
case FileType.MultiCollection:
{
return "build/multicollection/{0:000000}.bin";
maxId = 0x2200; // seems like this is reasonable limit for multis
return ["build/multicollection/{0:000000}.bin", string.Empty];
}
default:
{
Expand Down Expand Up @@ -574,7 +628,7 @@ private static uint HashAdler32(byte[] d)
return b << 16 | a;
}

private static void WriteMultiUOPEntryToMul(BinaryWriter mulWriter, byte[] chunkData)
private static void WriteMultiUopEntryToMul(BinaryWriter mulWriter, byte[] chunkData)
{
Span<byte> span = chunkData.AsSpan();
uint count = BinaryPrimitives.ReadUInt32LittleEndian(span[4..]);
Expand All @@ -586,6 +640,8 @@ private static void WriteMultiUOPEntryToMul(BinaryWriter mulWriter, byte[] chunk
short x = BinaryPrimitives.ReadInt16LittleEndian(span[2..]);
short y = BinaryPrimitives.ReadInt16LittleEndian(span[4..]);
short z = BinaryPrimitives.ReadInt16LittleEndian(span[6..]);

// this probably is just tiledata but needs further investigation
ushort flagValue = BinaryPrimitives.ReadUInt16LittleEndian(span[8..]);
uint clilocsCount = BinaryPrimitives.ReadUInt32LittleEndian(span[10..]);

Expand Down
26 changes: 23 additions & 3 deletions UoFiddler.Plugin.UopPacker/UserControls/UopPackerControl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

using System;
using System.IO;
using System.Linq;
using System.Windows.Forms;
using UoFiddler.Plugin.UopPacker.Classes;

Expand All @@ -26,7 +27,11 @@ private UopPackerControl()

_conv = new LegacyMulFileConverter();

multype.DataSource = uoptype.DataSource = Enum.GetValues(typeof(FileType));
var fileTypes = Enum.GetNames(typeof(FileType));

uoptype.DataSource = fileTypes;
multype.DataSource = fileTypes.SkipLast(1).ToArray(); // remove multi collection from ToUOP() conversion (not supported yet)

mulMapIndex.ReadOnly = uopMapIndex.ReadOnly = true;

Dock = DockStyle.Fill;
Expand Down Expand Up @@ -234,7 +239,7 @@ private void SelectFolder_Click(object sender, EventArgs e)
private int _total;
private int _success;

private void Extract(string inFile, string outFile, string outIdx, FileType type, int typeIndex)
private void Extract(string inFile, string outFile, string outIdx, FileType type, int typeIndex, string housingBinFile = "")
{
try
{
Expand All @@ -244,20 +249,32 @@ private void Extract(string inFile, string outFile, string outIdx, FileType type

if (!File.Exists(inFile))
{
MessageBox.Show($"Input file {inFile} doesn't exist");
return;
}

outFile = FixPath(outFile);

if (File.Exists(outFile))
{
MessageBox.Show($"Output file {outFile} already exists");
return;
}

if (!string.IsNullOrWhiteSpace(housingBinFile))
{
housingBinFile = FixPath(housingBinFile);
if (File.Exists(housingBinFile))
{
MessageBox.Show($"Output file {housingBinFile} already exists");
return;
}
}

outIdx = FixPath(outIdx);
++_total;

_conv.FromUop(inFile, outFile, outIdx, type, typeIndex);
_conv.FromUop(inFile, outFile, outIdx, type, typeIndex, housingBinFile);

++_success;
}
Expand All @@ -277,13 +294,15 @@ private void Pack(string inFile, string inIdx, string outFile, FileType type, in

if (!File.Exists(inFile))
{
MessageBox.Show($"Input file {inFile} doesn't exist");
return;
}

outFile = FixPath(outFile);

if (File.Exists(outFile))
{
MessageBox.Show($"Output file {outFile} already exists");
return;
}

Expand Down Expand Up @@ -320,6 +339,7 @@ private void StartFolderButtonClick(object sender, EventArgs e)
Extract("artLegacyMUL.uop", "art.mul", "artidx.mul", FileType.ArtLegacyMul, 0);
Extract("gumpartLegacyMUL.uop", "gumpart.mul", "gumpidx.mul", FileType.GumpartLegacyMul, 0);
Extract("soundLegacyMUL.uop", "sound.mul", "soundidx.mul", FileType.SoundLegacyMul, 0);
Extract("MultiCollection.uop", "multi-unpacked.mul", "multi-unpacked.idx", FileType.MultiCollection, 0, "housing.bin");

for (int i = 0; i <= 5; ++i)
{
Expand Down

0 comments on commit ef00dec

Please sign in to comment.