Skip to content

Commit 4264d7e

Browse files
authored
Allow specifying an OTB file for map creation (jo3bingham#33)
* Allow specifying an OTB file for map creation By default, the Extract app creates a map (OTBM) file using client IDs for item IDs. A lot of servers have mapped server IDs for items. This optional parameter allows users to have the Extract app use their server IDs when creating a map. * Convert client id to server id * Warn on multiple server IDs mapped to same client ID
1 parent 83a83fd commit 4264d7e

1 file changed

Lines changed: 132 additions & 35 deletions

File tree

Apps/Extract/Program.cs

Lines changed: 132 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ class Program
2424
private static readonly HashSet<uint> _ignoreIds = new HashSet<uint>();
2525

2626
private static readonly Dictionary<uint, uint> _replaceIds = new Dictionary<uint, uint>();
27+
private static readonly Dictionary<ushort, ushort> _clientToServerIds = new Dictionary<ushort, ushort>();
2728

2829
private static Client _client;
2930

@@ -41,6 +42,7 @@ class Program
4142
private static Logger.LogOutput _logOutput = Logger.LogOutput.Console;
4243

4344
private static string _currentFilename;
45+
private static string _otbFilename;
4446
private static string _outDirectory;
4547
private static string _recording;
4648
private static string _tibiaDirectory = string.Empty;
@@ -104,6 +106,10 @@ static bool ParseArgs(string[] args)
104106
Console.WriteLine("[optional] --time=<seconds> or --timestamp=<seconds>: " +
105107
"<seconds> is the number of seconds from the start of the recording to stop extraction." +
106108
"If this is not specified, extraction will run until the end of the recording.");
109+
Console.WriteLine("[optional] --otb=<path>: " +
110+
"<path> is the path to your items.otb file. +" +
111+
"By default, the OTBM file is created using client IDs for items. +" +
112+
"By specifying an OTB file, the OTBM file will be created with server IDs.");
107113

108114
Console.WriteLine("[optional] --loglevel=[debug,info,warning,error,disabled]: " +
109115
"Sets the log level within the API. Default: error");
@@ -157,6 +163,11 @@ static bool ParseArgs(string[] args)
157163
}
158164
}
159165
break;
166+
case "--otb":
167+
{
168+
_otbFilename = splitArg[1].Replace("\"", "");
169+
}
170+
break;
160171
case "--loglevel":
161172
{
162173
_logLevel = Logger.ConvertToLogLevel(splitArg[1]);
@@ -235,6 +246,11 @@ static void Main(string[] args)
235246
}
236247
}
237248

249+
if (!string.IsNullOrEmpty(_otbFilename))
250+
{
251+
LoadOtb();
252+
}
253+
238254
if (_extractMapData)
239255
{
240256
LoadXML("ItemsIgnore.xml");
@@ -295,6 +311,118 @@ static void Main(string[] args)
295311
}
296312
}
297313

314+
private static void LoadOtb()
315+
{
316+
Console.WriteLine("Loading OTB file...");
317+
using var fileStream = new BinaryReader(File.OpenRead(_otbFilename));
318+
while (fileStream.ReadByte() != 0xFE) { } // skip to root node
319+
while (fileStream.ReadByte() != 0x01) { } // skip to version attribute
320+
var skipBytes = fileStream.ReadUInt16();
321+
fileStream.BaseStream.Seek(skipBytes, SeekOrigin.Current); // skip version info
322+
while (fileStream.BaseStream.Position < fileStream.BaseStream.Length)
323+
{
324+
var serverId = ushort.MinValue;
325+
var clientId = ushort.MinValue;
326+
327+
// We've reached the end of the file.
328+
if (fileStream.ReadByte() == 0xFF)
329+
{
330+
break;
331+
}
332+
333+
// The OTB format is really, really stupid (no offense).
334+
using var ms = new MemoryStream();
335+
while (true)
336+
{
337+
var value = fileStream.ReadByte();
338+
if (value == 0xFE || value == 0xFF)
339+
{
340+
break;
341+
}
342+
else if (value == 0xFD)
343+
{
344+
value = fileStream.ReadByte();
345+
}
346+
ms.WriteByte(value);
347+
}
348+
349+
ms.Position = 0;
350+
using var bs = new BinaryReader(ms);
351+
352+
bs.ReadByte(); // item group
353+
bs.ReadUInt32(); // item flags
354+
355+
while (bs.BaseStream.Position < bs.BaseStream.Length)
356+
{
357+
var attribute = bs.ReadByte();
358+
var dataLen = bs.ReadUInt16();
359+
if (attribute == 0x10)
360+
{
361+
serverId = bs.ReadUInt16();
362+
}
363+
else if (attribute == 0x11)
364+
{
365+
clientId = bs.ReadUInt16();
366+
}
367+
else
368+
{
369+
bs.BaseStream.Seek(dataLen, SeekOrigin.Current);
370+
}
371+
372+
if (serverId != 0 && clientId != 0)
373+
{
374+
if (_clientToServerIds.ContainsKey(clientId))
375+
{
376+
var value = _clientToServerIds[clientId];
377+
Console.WriteLine($"Failed to map Server ID `{serverId}` to Client ID `{clientId}`. " +
378+
$"Client ID `{clientId}` is already mapped to Server ID `{value}`.");
379+
}
380+
else
381+
{
382+
_clientToServerIds[clientId] = serverId;
383+
}
384+
serverId = 0;
385+
clientId = 0;
386+
}
387+
}
388+
}
389+
Console.WriteLine("Done");
390+
}
391+
392+
static void LoadXML(string filename)
393+
{
394+
using (var file = File.OpenRead(filename))
395+
{
396+
Console.WriteLine($"Loading {filename}...");
397+
using (var reader = XmlReader.Create(file))
398+
{
399+
while (reader.Read())
400+
{
401+
if (!reader.IsStartElement())
402+
{
403+
continue;
404+
}
405+
406+
if (!reader.Name.Equals("item", StringComparison.CurrentCultureIgnoreCase))
407+
{
408+
continue;
409+
}
410+
411+
if (uint.TryParse(reader["id"], out var id))
412+
{
413+
_ignoreIds.Add(id);
414+
}
415+
else if (uint.TryParse(reader["fromid"], out var fromId) &&
416+
uint.TryParse(reader["toid"], out var toId))
417+
{
418+
_replaceIds.Add(fromId, toId);
419+
}
420+
}
421+
}
422+
Console.WriteLine("Done");
423+
}
424+
}
425+
298426
private static void ExtractRecordings(List<string> filenames)
299427
{
300428
Console.WriteLine($"Extracting data from {filenames.Count} recording(s)...");
@@ -671,41 +799,6 @@ static bool Connection_OnReceivedMapPacket(Packet packet)
671799
return true;
672800
}
673801

674-
675-
static void LoadXML(string filename)
676-
{
677-
using (var file = File.OpenRead(filename))
678-
{
679-
Console.WriteLine($"Loading {filename}...");
680-
using (var reader = XmlReader.Create(file))
681-
{
682-
while (reader.Read())
683-
{
684-
if (!reader.IsStartElement())
685-
{
686-
continue;
687-
}
688-
689-
if (!reader.Name.Equals("item", StringComparison.CurrentCultureIgnoreCase))
690-
{
691-
continue;
692-
}
693-
694-
if (uint.TryParse(reader["id"], out var id))
695-
{
696-
_ignoreIds.Add(id);
697-
}
698-
else if (uint.TryParse(reader["fromid"], out var fromId) &&
699-
uint.TryParse(reader["toid"], out var toId))
700-
{
701-
_replaceIds.Add(fromId, toId);
702-
}
703-
}
704-
}
705-
Console.WriteLine("Done");
706-
}
707-
}
708-
709802
static FileStream InitializeMapFile(string filename, string outputPath)
710803
{
711804
var file = new FileStream(outputPath, FileMode.CreateNew, FileAccess.ReadWrite, FileShare.ReadWrite, 4096, true);
@@ -818,6 +911,10 @@ static void ParseField(Position position)
818911
{
819912
id = item.Id;
820913
}
914+
if (_clientToServerIds.TryGetValue((ushort)id, out var serverId))
915+
{
916+
id = serverId;
917+
}
821918
WriteData(_otbmFile, BitConverter.GetBytes((ushort)id));
822919

823920
// item data

0 commit comments

Comments
 (0)