Skip to content

Commit 11fb5c7

Browse files
committed
Add support for UOP format in compare items, land and gumps plugins.
1 parent b5b5ea5 commit 11fb5c7

11 files changed

Lines changed: 725 additions & 137 deletions

UoFiddler.Plugin.Compare/Classes/SecondArt.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,12 @@ internal static class SecondArt
1818

1919
public static void SetFileIndex(string idxPath, string mulPath)
2020
{
21-
_fileIndex = new SecondFileIndex(idxPath, mulPath, 0x14000);
21+
SetFileIndex(idxPath, mulPath, null);
22+
}
23+
24+
public static void SetFileIndex(string idxPath, string mulPath, string uopPath)
25+
{
26+
_fileIndex = new SecondFileIndex(idxPath, mulPath, uopPath, 0x14000, ".tga", 0x13FDC, false);
2227
_cache = new Bitmap[0x14000];
2328
FileIndexChanged?.Invoke();
2429
}
Lines changed: 281 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,281 @@
1+
/***************************************************************************
2+
*
3+
* $Author: Turley
4+
*
5+
* "THE BEER-WARE LICENSE"
6+
* As long as you retain this notice you can do whatever you want with
7+
* this stuff. If we meet some day, and you think this stuff is worth it,
8+
* you can buy me a beer in return.
9+
*
10+
***************************************************************************/
11+
12+
using System;
13+
using System.Collections.Generic;
14+
using System.IO;
15+
using System.Runtime.InteropServices;
16+
using Ultima.Helpers;
17+
18+
namespace UoFiddler.Plugin.Compare.Classes
19+
{
20+
public enum SecondCompressionFlag
21+
{
22+
None = 0,
23+
Zlib = 1,
24+
Mythic = 3
25+
}
26+
27+
public interface SecondIEntry
28+
{
29+
int Lookup { get; set; }
30+
int Length { get; set; }
31+
int Extra { get; set; }
32+
int DecompressedLength { get; set; }
33+
int Extra1 { get; set; }
34+
int Extra2 { get; set; }
35+
SecondCompressionFlag Flag { get; set; }
36+
}
37+
38+
public interface SecondIFileAccessor
39+
{
40+
SecondIEntry GetEntry(int index);
41+
FileStream Stream { get; set; }
42+
int IndexLength { get; }
43+
long IdxLength { get; }
44+
SecondIEntry this[int index] { get; set; }
45+
}
46+
47+
// Layout-sensitive: matches the on-disk 12-byte idx entry. Do not reorder fields.
48+
[StructLayout(LayoutKind.Sequential, Pack = 1)]
49+
public struct SecondEntry3D : SecondIEntry
50+
{
51+
public int lookup;
52+
public int length;
53+
public int extra;
54+
55+
#pragma warning disable S2292
56+
public int Lookup { get => lookup; set => lookup = value; }
57+
public int Length { get => length; set => length = value; }
58+
public int Extra { get => extra; set => extra = value; }
59+
public int DecompressedLength { get => length; set => length = value; }
60+
#pragma warning restore S2292
61+
62+
public int Extra1
63+
{
64+
get => (Extra >> 16) & 0xFFFF;
65+
set => Extra = (Extra & 0x0000FFFF) | ((value & 0xFFFF) << 16);
66+
}
67+
68+
public int Extra2
69+
{
70+
get => Extra & 0x0000FFFF;
71+
set => Extra = (int)((Extra & 0xFFFF0000) | (uint)(value & 0xFFFF));
72+
}
73+
74+
public SecondCompressionFlag Flag { get => SecondCompressionFlag.None; set { } }
75+
}
76+
77+
public struct SecondEntry6D : SecondIEntry
78+
{
79+
public int Lookup { get; set; }
80+
public int Length { get; set; }
81+
82+
private int _extra1Backing;
83+
private int _extra2Backing;
84+
85+
public int Extra
86+
{
87+
get => (_extra1Backing << 16) | _extra2Backing;
88+
set
89+
{
90+
_extra1Backing = (value >> 16) & 0xFFFF;
91+
_extra2Backing = value & 0xFFFF;
92+
}
93+
}
94+
95+
public int DecompressedLength { get; set; }
96+
public int Extra1 { get; set; }
97+
public int Extra2 { get; set; }
98+
public SecondCompressionFlag Flag { get; set; }
99+
}
100+
101+
public class SecondMulFileAccessor : SecondIFileAccessor
102+
{
103+
public SecondEntry3D[] Index { get; }
104+
public FileStream Stream { get; set; }
105+
public long IdxLength { get; }
106+
public int IndexLength => Index.Length;
107+
108+
public SecondIEntry this[int index]
109+
{
110+
get => Index[index];
111+
set => Index[index] = (SecondEntry3D)value;
112+
}
113+
114+
public SecondMulFileAccessor(string idxPath, string mulPath, int length)
115+
{
116+
Index = new SecondEntry3D[length];
117+
118+
using (var idx = new FileStream(idxPath, FileMode.Open, FileAccess.Read, FileShare.Read))
119+
{
120+
Stream = new FileStream(mulPath, FileMode.Open, FileAccess.Read, FileShare.Read);
121+
int count = (int)(idx.Length / 12);
122+
IdxLength = idx.Length;
123+
124+
GCHandle gc = GCHandle.Alloc(Index, GCHandleType.Pinned);
125+
byte[] buffer = new byte[idx.Length];
126+
idx.ReadExactly(buffer, 0, (int)idx.Length);
127+
Marshal.Copy(buffer, 0, gc.AddrOfPinnedObject(), (int)Math.Min(IdxLength, Index.Length * 12L));
128+
gc.Free();
129+
130+
for (int i = count; i < Index.Length; ++i)
131+
{
132+
Index[i].Lookup = -1;
133+
Index[i].Length = -1;
134+
Index[i].Extra = -1;
135+
}
136+
}
137+
}
138+
139+
public SecondIEntry GetEntry(int index)
140+
{
141+
if (index < 0 || index >= Index.Length)
142+
{
143+
return new SecondEntry3D();
144+
}
145+
146+
return Index[index];
147+
}
148+
}
149+
150+
public class SecondUopFileAccessor : SecondIFileAccessor
151+
{
152+
public SecondEntry6D[] Index { get; }
153+
public FileStream Stream { get; set; }
154+
public long IdxLength { get; }
155+
public int IndexLength => Index.Length;
156+
157+
public SecondIEntry this[int index]
158+
{
159+
get => Index[index];
160+
set => Index[index] = (SecondEntry6D)value;
161+
}
162+
163+
public SecondUopFileAccessor(string path, string uopEntryExtension, int length, int idxLength, bool hasExtra)
164+
{
165+
Index = new SecondEntry6D[length];
166+
167+
if (idxLength > 0)
168+
{
169+
IdxLength = idxLength * 12L;
170+
}
171+
172+
Stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read);
173+
174+
var fileInfo = new FileInfo(path);
175+
string uopPattern = fileInfo.Name.Replace(fileInfo.Extension, "").ToLowerInvariant();
176+
177+
using (var br = new BinaryReader(Stream, System.Text.Encoding.Default, leaveOpen: true))
178+
{
179+
br.BaseStream.Seek(0, SeekOrigin.Begin);
180+
181+
if (br.ReadInt32() != 0x50594D)
182+
{
183+
throw new ArgumentException("Bad UOP file.");
184+
}
185+
186+
_ = br.ReadUInt32(); // version
187+
_ = br.ReadUInt32(); // signature
188+
long nextBlock = br.ReadInt64();
189+
_ = br.ReadUInt32(); // block capacity
190+
_ = br.ReadInt32(); // count
191+
192+
var hashes = new Dictionary<ulong, int>();
193+
for (int i = 0; i < length; i++)
194+
{
195+
string entryName = $"build/{uopPattern}/{i:D8}{uopEntryExtension}";
196+
ulong hash = UopUtils.HashFileName(entryName);
197+
hashes.TryAdd(hash, i);
198+
}
199+
200+
br.BaseStream.Seek(nextBlock, SeekOrigin.Begin);
201+
202+
// UOP entries are sparse; pre-mark all as invalid.
203+
for (var i = 0; i < Index.Length; i++)
204+
{
205+
Index[i].Lookup = -1;
206+
Index[i].Length = -1;
207+
Index[i].Extra = -1;
208+
}
209+
210+
do
211+
{
212+
int filesCount = br.ReadInt32();
213+
nextBlock = br.ReadInt64();
214+
215+
for (int i = 0; i < filesCount; i++)
216+
{
217+
long offset = br.ReadInt64();
218+
int headerLength = br.ReadInt32();
219+
int compressedLength = br.ReadInt32();
220+
int decompressedLength = br.ReadInt32();
221+
ulong hash = br.ReadUInt64();
222+
_ = br.ReadUInt32(); // data hash
223+
short flag = br.ReadInt16();
224+
225+
if (offset == 0)
226+
{
227+
continue;
228+
}
229+
230+
if (!hashes.TryGetValue(hash, out int idx))
231+
{
232+
continue;
233+
}
234+
235+
if (idx < 0 || idx > Index.Length)
236+
{
237+
throw new IndexOutOfRangeException("hashes dictionary and files collection have different count of entries!");
238+
}
239+
240+
offset += headerLength;
241+
242+
if (hasExtra && flag != 3)
243+
{
244+
long curPos = br.BaseStream.Position;
245+
br.BaseStream.Seek(offset, SeekOrigin.Begin);
246+
var extra1 = br.ReadInt32();
247+
var extra2 = br.ReadInt32();
248+
Index[idx].Lookup = (int)(offset + 8);
249+
Index[idx].Length = compressedLength - 8;
250+
Index[idx].DecompressedLength = decompressedLength;
251+
Index[idx].Flag = (SecondCompressionFlag)flag;
252+
Index[idx].Extra = (extra1 << 16) | extra2;
253+
Index[idx].Extra1 = extra1;
254+
Index[idx].Extra2 = extra2;
255+
br.BaseStream.Seek(curPos, SeekOrigin.Begin);
256+
}
257+
else
258+
{
259+
Index[idx].Lookup = (int)offset;
260+
Index[idx].Length = compressedLength;
261+
Index[idx].DecompressedLength = decompressedLength;
262+
Index[idx].Flag = (SecondCompressionFlag)flag;
263+
Index[idx].Extra = 0x0FFFFFFF;
264+
}
265+
}
266+
}
267+
while (br.BaseStream.Seek(nextBlock, SeekOrigin.Begin) != 0);
268+
}
269+
}
270+
271+
public SecondIEntry GetEntry(int index)
272+
{
273+
if (index < 0 || index >= Index.Length)
274+
{
275+
return new SecondEntry6D();
276+
}
277+
278+
return Index[index];
279+
}
280+
}
281+
}

0 commit comments

Comments
 (0)