Skip to content

Commit 1cdd229

Browse files
committed
Add new Redact app for anonymizing recordings
1 parent d03f965 commit 1cdd229

4 files changed

Lines changed: 304 additions & 4 deletions

File tree

Apps/Record/Program.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ class Program
3333

3434
static Thread _fileWriteThread;
3535

36-
private static Logger.LogLevel _logLevel = Logger.LogLevel.Error;
36+
private static Logger.LogLevel _logLevel = Logger.LogLevel.Debug;
3737

3838
private static Logger.LogOutput _logOutput = Logger.LogOutput.Console;
3939

Apps/Redact/Program.cs

Lines changed: 282 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,282 @@
1+
using System;
2+
using System.IO;
3+
4+
using OXGaming.TibiaAPI;
5+
using OXGaming.TibiaAPI.Constants;
6+
using OXGaming.TibiaAPI.Network;
7+
8+
namespace Redact
9+
{
10+
class Program
11+
{
12+
static string _recordingName;
13+
14+
static bool _keepClientPackets;
15+
16+
static void ParseArgs(string[] args)
17+
{
18+
foreach (var arg in args)
19+
{
20+
if (!arg.Contains('=', StringComparison.CurrentCultureIgnoreCase))
21+
{
22+
continue;
23+
}
24+
25+
var splitArg = arg.Split('=');
26+
if (splitArg.Length == 1)
27+
{
28+
switch (splitArg[0])
29+
{
30+
case "--keepclient":
31+
{
32+
_keepClientPackets = true;
33+
}
34+
break;
35+
default:
36+
break;
37+
}
38+
}
39+
else if (splitArg.Length == 2)
40+
{
41+
switch (splitArg[0])
42+
{
43+
case "-r":
44+
case "--recording":
45+
{
46+
_recordingName = splitArg[1].Replace("\"", "");
47+
}
48+
break;
49+
default:
50+
break;
51+
}
52+
}
53+
}
54+
}
55+
56+
static void Main(string[] args)
57+
{
58+
if (args.Length <= 0)
59+
{
60+
Console.WriteLine($"Invalid number of arguments: {args.Length}");
61+
return;
62+
}
63+
64+
ParseArgs(args);
65+
66+
if (string.IsNullOrEmpty(_recordingName) || !_recordingName.EndsWith(".oxr", StringComparison.CurrentCultureIgnoreCase))
67+
{
68+
Console.WriteLine($"Invalid recording file: {_recordingName ?? "null"}");
69+
return;
70+
}
71+
72+
if (!File.Exists(_recordingName))
73+
{
74+
Console.WriteLine($"File does not exist: {_recordingName}");
75+
return;
76+
}
77+
78+
Redact();
79+
}
80+
81+
static void Redact()
82+
{
83+
Console.WriteLine($"Redacting data from {_recordingName}...");
84+
85+
var redactedFile = Path.GetFileNameWithoutExtension(_recordingName) + "_redacted.oxr";
86+
using var reader = new BinaryReader(File.OpenRead(_recordingName));
87+
using var writer = new BinaryWriter(File.OpenWrite(redactedFile));
88+
89+
// OXR files begin with the client version they were recorded with.
90+
// This allows us to easily parse recordings from older client versions.
91+
var tibiaDirectory = string.Empty;
92+
var version = reader.ReadString();
93+
Console.WriteLine($"Client version: {version}");
94+
if (int.TryParse(version.Replace(".", ""), out var versionNumber))
95+
{
96+
var clientDataDirectory = $"ClientData/{versionNumber}";
97+
if (!Directory.Exists(clientDataDirectory))
98+
{
99+
Console.WriteLine($"ClientData directory for version {version} doesn't exist. Falling back to default Tibia directory.");
100+
}
101+
else
102+
{
103+
tibiaDirectory = clientDataDirectory;
104+
}
105+
}
106+
else
107+
{
108+
Console.WriteLine($"Invalid client version at beginning of recording: {version}");
109+
}
110+
111+
var client = new Client(tibiaDirectory);
112+
113+
// We don't want to completely block these packets.
114+
if (_keepClientPackets)
115+
{
116+
client.Connection.OnReceivedClientTalkPacket += Connection_OnReceivedClientTalkPacket;
117+
}
118+
client.Connection.OnReceivedServerFullMapPacket += Connection_OnReceivedServerMapPacket;
119+
client.Connection.OnReceivedServerBottomFloorPacket += Connection_OnReceivedServerMapPacket;
120+
client.Connection.OnReceivedServerBottomRowPacket += Connection_OnReceivedServerMapPacket;
121+
client.Connection.OnReceivedServerTopFloorPacket += Connection_OnReceivedServerMapPacket;
122+
client.Connection.OnReceivedServerTopRowPacket += Connection_OnReceivedServerMapPacket;
123+
client.Connection.OnReceivedServerLeftColumnPacket += Connection_OnReceivedServerMapPacket;
124+
client.Connection.OnReceivedServerRightColumnPacket += Connection_OnReceivedServerMapPacket;
125+
client.Connection.OnReceivedServerFieldDataPacket += Connection_OnReceivedServerMapPacket;
126+
client.Connection.OnReceivedServerCreateOnMapPacket += Connection_OnReceivedServerCreateOnMapPacket;
127+
client.Connection.OnReceivedServerCreatureUpdatePacket += Connection_OnReceivedServerCreatureUpdatePacket;
128+
client.Connection.OnReceivedServerTalkPacket += Connection_OnReceivedServerTalkPacket;
129+
130+
// These packets aren't necessary in a redacted recording.
131+
if (_keepClientPackets)
132+
{
133+
client.Connection.OnReceivedClientAddBuddyPacket += Connection_OnReceivedUselessPacket;
134+
client.Connection.OnReceivedClientBuddyGroupPacket += Connection_OnReceivedUselessPacket;
135+
client.Connection.OnReceivedClientBugReportPacket += Connection_OnReceivedUselessPacket;
136+
client.Connection.OnReceivedClientEditBuddyPacket += Connection_OnReceivedUselessPacket;
137+
client.Connection.OnReceivedClientExcludeFromChannelPacket += Connection_OnReceivedUselessPacket;
138+
client.Connection.OnReceivedClientFriendSystemActionPacket += Connection_OnReceivedUselessPacket;
139+
client.Connection.OnReceivedClientLoginPacket += Connection_OnReceivedUselessPacket;
140+
client.Connection.OnReceivedClientPrivateChannelPacket += Connection_OnReceivedUselessPacket;
141+
client.Connection.OnReceivedClientRuleViolationReportPacket += Connection_OnReceivedUselessPacket;
142+
}
143+
client.Connection.OnReceivedServerBuddyDataPacket += Connection_OnReceivedUselessPacket;
144+
client.Connection.OnReceivedServerBuddyGroupDataPacket += Connection_OnReceivedUselessPacket;
145+
client.Connection.OnReceivedServerBuddyStatusChangePacket += Connection_OnReceivedUselessPacket;
146+
client.Connection.OnReceivedServerChannelEventPacket += Connection_OnReceivedUselessPacket;
147+
client.Connection.OnReceivedServerChannelsPacket += Connection_OnReceivedUselessPacket;
148+
client.Connection.OnReceivedServerCyclopediaCharacterInfoPacket += Connection_OnReceivedUselessPacket;
149+
client.Connection.OnReceivedServerCyclopediaCurrentHouseDataPacket += Connection_OnReceivedUselessPacket;
150+
client.Connection.OnReceivedServerCyclopediaStaticHouseDataPacket += Connection_OnReceivedUselessPacket;
151+
client.Connection.OnReceivedServerFriendSystemDataPacket += Connection_OnReceivedUselessPacket;
152+
client.Connection.OnReceivedServerInspectionListPacket += Connection_OnReceivedUselessPacket;
153+
client.Connection.OnReceivedServerOpenChannelPacket += Connection_OnReceivedUselessPacket;
154+
client.Connection.OnReceivedServerOpenOwnChannelPacket += Connection_OnReceivedUselessPacket;
155+
client.Connection.OnReceivedServerPrivateChannelPacket += Connection_OnReceivedUselessPacket;
156+
client.Connection.OnReceivedServerRequestPurchaseDataPacket += Connection_OnReceivedUselessPacket;
157+
client.Connection.OnReceivedServerScreenshotEventPacket += Connection_OnReceivedUselessPacket;
158+
client.Connection.OnReceivedServerUpdateExivaOptionsPacket += Connection_OnReceivedUselessPacket;
159+
160+
// Packet modification isn't enabled by default when parsing packets.
161+
// Enabling it allows the `outMessage` parameter of the parsing methods
162+
// to reflect our changes so we can then write them to our new file.
163+
client.Connection.IsClientPacketModificationEnabled = true;
164+
client.Connection.IsServerPacketModificationEnabled = true;
165+
166+
writer.Write(version);
167+
168+
while (reader.BaseStream.Position < reader.BaseStream.Length)
169+
{
170+
var packetType = (PacketType)reader.ReadByte();
171+
var timestamp = reader.ReadInt64();
172+
var size = reader.ReadUInt32();
173+
_ = reader.ReadUInt16(); // packet size
174+
var sequenceNumber = reader.ReadUInt32();
175+
var message = new NetworkMessage(client)
176+
{
177+
Size = size
178+
};
179+
var outMessage = new NetworkMessage(client)
180+
{
181+
SequenceNumber = sequenceNumber
182+
};
183+
184+
reader.BaseStream.Position -= 6;
185+
Array.Copy(reader.ReadBytes((int)message.Size), message.GetBuffer(), message.Size);
186+
187+
if (packetType == PacketType.Server)
188+
{
189+
client.Connection.ParseServerMessage(client, message, outMessage);
190+
}
191+
else if (_keepClientPackets)
192+
{
193+
client.Connection.ParseClientMessage(client, message, outMessage);
194+
}
195+
196+
// If the `outMessage` doesn't contain any data we don't want to write it to the file.
197+
if (outMessage.Size <= 8)
198+
{
199+
continue;
200+
}
201+
202+
// Prepare the message to send without an XTEA key so that the proper
203+
// sizes are added to the packet data, but it stays unencrypted.
204+
outMessage.PrepareToSend(null);
205+
206+
var data = outMessage.GetData();
207+
208+
writer.Write((byte)packetType);
209+
writer.Write(timestamp);
210+
writer.Write(data.Length);
211+
writer.Write(data);
212+
}
213+
214+
writer.Flush();
215+
}
216+
217+
private static bool Connection_OnReceivedClientTalkPacket(Packet packet)
218+
{
219+
var p = (OXGaming.TibiaAPI.Network.ClientPackets.Talk)packet;
220+
p.SpeakerName = "Redacted";
221+
return true;
222+
}
223+
224+
private static bool Connection_OnReceivedServerTalkPacket(Packet packet)
225+
{
226+
var p = (OXGaming.TibiaAPI.Network.ServerPackets.Talk)packet;
227+
p.SpeakerName = "Redacted";
228+
return true;
229+
}
230+
231+
private static bool Connection_OnReceivedServerCreatureUpdatePacket(Packet packet)
232+
{
233+
var p = (OXGaming.TibiaAPI.Network.ServerPackets.CreatureUpdate)packet;
234+
if (p.Creature is object)
235+
{
236+
p.Creature.Name = "Redacted";
237+
}
238+
return true;
239+
}
240+
241+
private static bool Connection_OnReceivedServerCreateOnMapPacket(Packet packet)
242+
{
243+
var p = (OXGaming.TibiaAPI.Network.ServerPackets.CreateOnMap)packet;
244+
if (p.Creature is object)
245+
{
246+
p.Creature.Name = "Redacted";
247+
}
248+
return true;
249+
}
250+
251+
private static bool Connection_OnReceivedServerMapPacket(Packet packet)
252+
{
253+
var p = (OXGaming.TibiaAPI.Network.ServerPackets.Map)packet;
254+
foreach (var field in p.Fields)
255+
{
256+
foreach (var obj in field.Objects)
257+
{
258+
if (obj is null || obj.Id >= 100)
259+
{
260+
continue;
261+
}
262+
263+
var creature = p.Client.CreatureStorage.GetCreature(obj.Data);
264+
if (creature is null || creature.Type != CreatureType.Player)
265+
{
266+
continue;
267+
}
268+
269+
creature.Name = "Redacted";
270+
}
271+
}
272+
return true;
273+
}
274+
275+
private static bool Connection_OnReceivedUselessPacket(Packet _)
276+
{
277+
// Because we don't care about this data being in a redacted recording
278+
// we can just return `false` here and it won't be added to the `outMessage`.
279+
return false;
280+
}
281+
}
282+
}

Apps/Redact/Redact.csproj

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<OutputType>Exe</OutputType>
5+
<TargetFramework>netcoreapp3.1</TargetFramework>
6+
</PropertyGroup>
7+
8+
<ItemGroup>
9+
<ProjectReference Include="..\..\TibiaAPI\TibiaAPI.csproj" />
10+
</ItemGroup>
11+
</Project>

TibiaAPI.sln

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,13 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TibiaAPI", "TibiaAPI\TibiaA
77
EndProject
88
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Apps", "Apps", "{C086BFA7-CE77-4DDF-9474-C65709DBA8A8}"
99
EndProject
10-
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Extract", "Apps\Extract\Extract.csproj", "{EE3A6146-B1A6-44F0-9C5D-CBD5D44CEF83}"
10+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Extract", "Apps\Extract\Extract.csproj", "{EE3A6146-B1A6-44F0-9C5D-CBD5D44CEF83}"
1111
EndProject
12-
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Record", "Apps\Record\Record.csproj", "{75DB22C7-8FE1-4BF6-8778-AE67B236DFED}"
12+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Record", "Apps\Record\Record.csproj", "{75DB22C7-8FE1-4BF6-8778-AE67B236DFED}"
1313
EndProject
14-
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Watch", "Apps\Watch\Watch.csproj", "{D9E8B07D-C3D8-431D-8E1C-B52EE2AF49C9}"
14+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Watch", "Apps\Watch\Watch.csproj", "{D9E8B07D-C3D8-431D-8E1C-B52EE2AF49C9}"
15+
EndProject
16+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Redact", "Apps\Redact\Redact.csproj", "{F9D5BF93-D832-4AA0-9B1E-2FAE3E5235A2}"
1517
EndProject
1618
Global
1719
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -35,6 +37,10 @@ Global
3537
{D9E8B07D-C3D8-431D-8E1C-B52EE2AF49C9}.Debug|Any CPU.Build.0 = Debug|Any CPU
3638
{D9E8B07D-C3D8-431D-8E1C-B52EE2AF49C9}.Release|Any CPU.ActiveCfg = Release|Any CPU
3739
{D9E8B07D-C3D8-431D-8E1C-B52EE2AF49C9}.Release|Any CPU.Build.0 = Release|Any CPU
40+
{F9D5BF93-D832-4AA0-9B1E-2FAE3E5235A2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
41+
{F9D5BF93-D832-4AA0-9B1E-2FAE3E5235A2}.Debug|Any CPU.Build.0 = Debug|Any CPU
42+
{F9D5BF93-D832-4AA0-9B1E-2FAE3E5235A2}.Release|Any CPU.ActiveCfg = Release|Any CPU
43+
{F9D5BF93-D832-4AA0-9B1E-2FAE3E5235A2}.Release|Any CPU.Build.0 = Release|Any CPU
3844
EndGlobalSection
3945
GlobalSection(SolutionProperties) = preSolution
4046
HideSolutionNode = FALSE
@@ -43,6 +49,7 @@ Global
4349
{EE3A6146-B1A6-44F0-9C5D-CBD5D44CEF83} = {C086BFA7-CE77-4DDF-9474-C65709DBA8A8}
4450
{75DB22C7-8FE1-4BF6-8778-AE67B236DFED} = {C086BFA7-CE77-4DDF-9474-C65709DBA8A8}
4551
{D9E8B07D-C3D8-431D-8E1C-B52EE2AF49C9} = {C086BFA7-CE77-4DDF-9474-C65709DBA8A8}
52+
{F9D5BF93-D832-4AA0-9B1E-2FAE3E5235A2} = {C086BFA7-CE77-4DDF-9474-C65709DBA8A8}
4653
EndGlobalSection
4754
GlobalSection(ExtensibilityGlobals) = postSolution
4855
SolutionGuid = {779085CF-5BE0-48BA-A204-511A8914A67C}

0 commit comments

Comments
 (0)