C
C#13mo ago
Bambino

❔ Understanding reading values from binary

I'm reading a version number from the binary of a client via hex. However, I get different results with different client versions. Using IDA, I have found the correct offsets for each client, so that is not the issue. I have reached my intended goal of getting the value for different client versions, but I am trying to understand why. I am new to this so there must be something I do not comprehend. ------------------------- Using BinaryPrimitives.ReadUInt16LittleEndian
var majorPart = BinaryPrimitives.ReadUInt16LittleEndian(buffer.AsSpan(offset));
var minorPart = BinaryPrimitives.ReadUInt16LittleEndian(buffer.AsSpan(offset + 2));
var privatePart = BinaryPrimitives.ReadUInt16LittleEndian(buffer.AsSpan(offset + 5));
var buildPart = BinaryPrimitives.ReadUInt16LittleEndian(buffer.AsSpan(offset + 8));
var majorPart = BinaryPrimitives.ReadUInt16LittleEndian(buffer.AsSpan(offset));
var minorPart = BinaryPrimitives.ReadUInt16LittleEndian(buffer.AsSpan(offset + 2));
var privatePart = BinaryPrimitives.ReadUInt16LittleEndian(buffer.AsSpan(offset + 5));
var buildPart = BinaryPrimitives.ReadUInt16LittleEndian(buffer.AsSpan(offset + 8));
Client 1, I get the correct value: Version: 7.0.98.16 for the version. Client 2, I get Version: 29554.28521.8302.29477 ------------------------- Using Encoding.ASCII.GetString
majorPart = Encoding.ASCII.GetString(buffer.AsSpan(offset, 1));
minorPart = Encoding.ASCII.GetString(buffer.AsSpan(offset + 2, 2));
buildPart = Encoding.ASCII.GetString(buffer.AsSpan(offset + 5, 2));
privatePart = Encoding.ASCII.GetString(buffer.AsSpan(offset + 8, 2));
majorPart = Encoding.ASCII.GetString(buffer.AsSpan(offset, 1));
minorPart = Encoding.ASCII.GetString(buffer.AsSpan(offset + 2, 2));
buildPart = Encoding.ASCII.GetString(buffer.AsSpan(offset + 5, 2));
privatePart = Encoding.ASCII.GetString(buffer.AsSpan(offset + 8, 2));
for the version. Client 2, I get the intended value for Client 2 Version: 1.25.35.00 ------------------------------------------
47 Replies
Bambino
Bambino13mo ago
private static ClientVersion DetectClassicClient()
{
var path = Core.FindDataFile("client.exe", false);

if (File.Exists(path))
{
using FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read);
var buffer = GC.AllocateUninitializedArray<byte>((int)fs.Length, true);
fs.Read(buffer);
// UO Version (unicode)
Span<byte> UOVersion = stackalloc byte[]
{
0x55, 0x4F, 0x20, 0x56, 0x65, 0x72, 0x73, 0x69,
0x6F, 0x6E
};

for (var i = 0; i < buffer.Length - UOVersion.Length; i++)
{
if (vsVersionInfo.SequenceEqual(buffer.AsSpan(i, UOVersion.Length)))
{
var offset = UOVersion.Length + i + 10;

var minorPart = BinaryPrimitives.ReadUInt16LittleEndian(buffer.AsSpan(offset));
var majorPart = BinaryPrimitives.ReadUInt16LittleEndian(buffer.AsSpan(offset + 2));
var privatePart = BinaryPrimitives.ReadUInt16LittleEndian(buffer.AsSpan(offset + 5));
var buildPart = BinaryPrimitives.ReadUInt16LittleEndian(buffer.AsSpan(offset + 8));

return new ClientVersion(majorPart, minorPart, buildPart, privatePart);
}
}
}

return null;
}
private static ClientVersion DetectClassicClient()
{
var path = Core.FindDataFile("client.exe", false);

if (File.Exists(path))
{
using FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read);
var buffer = GC.AllocateUninitializedArray<byte>((int)fs.Length, true);
fs.Read(buffer);
// UO Version (unicode)
Span<byte> UOVersion = stackalloc byte[]
{
0x55, 0x4F, 0x20, 0x56, 0x65, 0x72, 0x73, 0x69,
0x6F, 0x6E
};

for (var i = 0; i < buffer.Length - UOVersion.Length; i++)
{
if (vsVersionInfo.SequenceEqual(buffer.AsSpan(i, UOVersion.Length)))
{
var offset = UOVersion.Length + i + 10;

var minorPart = BinaryPrimitives.ReadUInt16LittleEndian(buffer.AsSpan(offset));
var majorPart = BinaryPrimitives.ReadUInt16LittleEndian(buffer.AsSpan(offset + 2));
var privatePart = BinaryPrimitives.ReadUInt16LittleEndian(buffer.AsSpan(offset + 5));
var buildPart = BinaryPrimitives.ReadUInt16LittleEndian(buffer.AsSpan(offset + 8));

return new ClientVersion(majorPart, minorPart, buildPart, privatePart);
}
}
}

return null;
}
private static ClientVersion DetectOGClassicClient()
{
var path = Core.FindDataFile("client.exe", false);
if (File.Exists(path))
{
var minorPart = "0";
var majorPart = "0";
var privatePart = "0";
var buildPart = "0";
using FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read);
var buffer = GC.AllocateUninitializedArray<byte>((int)fs.Length, true);
fs.Read(buffer);
Span<byte> UOVersion = stackalloc byte[]
{
0x55, 0x4F, 0x20, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6F, 0x6E
};

for (var i = 0; i < buffer.Length - UOVersion.Length; i++)
{
if (UOVersion.SequenceEqual(buffer.AsSpan(i, UOVersion.Length)))
{
var offset = UOVersion.Length + i + 10;
// Add an offset of 4 to start at majorPart (0x31)
majorPart = Encoding.ASCII.GetString(buffer.AsSpan(offset, 1));
minorPart = Encoding.ASCII.GetString(buffer.AsSpan(offset + 2, 2));
buildPart = Encoding.ASCII.GetString(buffer.AsSpan(offset + 5, 2));
privatePart = Encoding.ASCII.GetString(buffer.AsSpan(offset + 8, 2));
private static ClientVersion DetectOGClassicClient()
{
var path = Core.FindDataFile("client.exe", false);
if (File.Exists(path))
{
var minorPart = "0";
var majorPart = "0";
var privatePart = "0";
var buildPart = "0";
using FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read);
var buffer = GC.AllocateUninitializedArray<byte>((int)fs.Length, true);
fs.Read(buffer);
Span<byte> UOVersion = stackalloc byte[]
{
0x55, 0x4F, 0x20, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6F, 0x6E
};

for (var i = 0; i < buffer.Length - UOVersion.Length; i++)
{
if (UOVersion.SequenceEqual(buffer.AsSpan(i, UOVersion.Length)))
{
var offset = UOVersion.Length + i + 10;
// Add an offset of 4 to start at majorPart (0x31)
majorPart = Encoding.ASCII.GetString(buffer.AsSpan(offset, 1));
minorPart = Encoding.ASCII.GetString(buffer.AsSpan(offset + 2, 2));
buildPart = Encoding.ASCII.GetString(buffer.AsSpan(offset + 5, 2));
privatePart = Encoding.ASCII.GetString(buffer.AsSpan(offset + 8, 2));
Cont...
string version = $"{majorPart}.{minorPart}.{buildPart}.{privatePart}";
Console.WriteLine("Version: " + version);
int majorPartG = int.Parse(majorPart);
int minorPartG = int.Parse(minorPart);
int buildPartG = int.Parse(buildPart);
int privatePartG;
if (privatePart == "\0\0")
{
privatePartG = 0;
}
else
privatePartG = int.Parse(privatePart);

return new ClientVersion(majorPartG, minorPartG, buildPartG, privatePartG);
}
}
}

return null;
}
string version = $"{majorPart}.{minorPart}.{buildPart}.{privatePart}";
Console.WriteLine("Version: " + version);
int majorPartG = int.Parse(majorPart);
int minorPartG = int.Parse(minorPart);
int buildPartG = int.Parse(buildPart);
int privatePartG;
if (privatePart == "\0\0")
{
privatePartG = 0;
}
else
privatePartG = int.Parse(privatePart);

return new ClientVersion(majorPartG, minorPartG, buildPartG, privatePartG);
}
}
}

return null;
}
If I need to clarify or provide more info let me know
ero
ero13mo ago
i'm just wondering what in the world you chose as the syntax highlighting in these blocks
Bambino
Bambino13mo ago
lol, I used css. My bad. Changed it
mtreit
mtreit13mo ago
So you are trying to turn 8 bytes into a version string?
Bambino
Bambino13mo ago
Yup, 8 bytes for each part
mtreit
mtreit13mo ago
I see 2 bytes for each part.
Bambino
Bambino13mo ago
if I am right, for every two hexadecimal digits, it is 8 bytes?
mtreit
mtreit13mo ago
No, that's 2 bytes Er, one byte 4 bits for each character And you're reading as 16-bit integers which are two bytes at a time. I feel like you have dumped a lot of extraneous code into this question which, might eventually be needed for your program, but is just complicating and confusing the issue here.
Bambino
Bambino13mo ago
Hrmm, so each hexadecimal digit represents 4 bits or half a byte?
mtreit
mtreit13mo ago
Yes
Bambino
Bambino13mo ago
I am pretty confused tbh, but Im trying. Guess my question represents that
mtreit
mtreit13mo ago
You're using IDA?
Bambino
Bambino13mo ago
yup
Bambino
Bambino13mo ago
mtreit
mtreit13mo ago
No offense, but...did you pay for it? Because IDA is pretty expensive.
ero
ero13mo ago
ida has a free version
Bambino
Bambino13mo ago
It's the free version yup
mtreit
mtreit13mo ago
Ok, I only ever had the commercial version. So you should probably simplify the problem. Take the exact bytes you see in IDA and write them to disk in the exact same order to a separate file.
ero
ero13mo ago
why is there 1 short skipped though or actually 3 bytes are skipped how random
mtreit
mtreit13mo ago
Now read that file starting at offset zero and make your code work. I have a feeling the computed offsets are not right.
ero
ero13mo ago
no chance they are the ushorts wouldn't be aligned
mtreit
mtreit13mo ago
When your code works and you understand it, go back to starting with the offset inside the actual file.
ero
ero13mo ago
i guess you can pack(1) but that's unlikely right unless the game is ultra old
Bambino
Bambino13mo ago
if you are referring to privatePart, that is something I need to handle bc the client versions do not always have a privatePart. In this case it did not.
mtreit
mtreit13mo ago
Well the bytes for that probably still occupy memory - usually these kind of structures are fixed size.
Bambino
Bambino13mo ago
done
mtreit
mtreit13mo ago
The bytes might be zero. So you created a binary file? And not a text file?
Bambino
Bambino13mo ago
Ah I just copy pasted in the correct order to a text file Do i save directly from IDA to a bin?
mtreit
mtreit13mo ago
Notes to self
Fundamentals I wish you knew: bits and bytes. Part 1.
The scene: a programming interview The interview candidate is staring at me, frowning. Behind them is the whiteboard, where they have tentatively sketched out a rudimentary function prototype. They are trying to understand more about what exactly they need to do to implement the function, and it’s going poorly.
Notes to self
Fundamentals I wish you knew: bits and bytes. Part 2.
Here’s a file. It contains a series of integer values, stored as binary. I’d like you to do some processing of each value. How would you do it?
Bambino
Bambino13mo ago
Very nice, want me to check it out then check back in?
mtreit
mtreit13mo ago
I think that would probably really help you Working with binary data isn't actually all that hard, but if you have never done it before there are some fundamental things to understand (like, the computer doesn't store things in hexadecimal) that are helpful.
Bambino
Bambino13mo ago
Trying to understand the way the binary is displayed in hexadecimal when choosing a bit mode. When using IDA, the bit mode you choose determines how the hex is formatted? if the binary was compiled for a 16 bit OS, I should choose 16 bit mode for readability? Any links that may help me out with this would also help.
mtreit
mtreit13mo ago
It doesn't change the actual values Presumably it changes how it interprets the program as an executable
Bambino
Bambino13mo ago
got yuh This is throwing me off looking at two different versions of the same file. One from 1998, one from 2023. Definitely a lot of changes have taken place since 98, but that brings a question. Was it common not use multi-byte data back then? Or, use an endian format? Maybe I'm missing a setting in IDA, but I'm pretty sure the exact same. 1998: 55 4F 20 56 65 72 73 69 6F 6E (1.25.35) 31 2E 32 35 2E 33 35 (1.25.35) 2023: 37 00 2C 00 20 00 30 00 2C 00 20 00 39 00 38 00 2C 00 20 00 31 00 00 00 36 (7, 0, 98, 16) Like one is using ASCII and the other UTF-16
mtreit
mtreit13mo ago
The second one is a UTF16 string. The first one seems like the data doesn't remotely match what you think the value should be Looks like an ascii string Starts with "UO V" and I'm too lazy to decode the rest on my phone at the brewery after work. You can do it easily with an ascii table or CyberChef Or just any hex viewer which normally shows the ascii string on the right
Bambino
Bambino13mo ago
My mistake, I coped the wrong values, but yes it is "UO Version." I corrected it above, but yes that answers my question. 31 2E 32 35 2E 33 35
mtreit
mtreit13mo ago
ascii was pretty normal in the 90s Except on Windows NT where everything was Unicode Windows NT didn't take over for consumer operating systems until 2001 when Windows XP released
Bambino
Bambino13mo ago
I've been watching Computer Chronicles, Windows NT is very interesting.
mtreit
mtreit13mo ago
I worked on Windows NT5 which was rebranded Windows 2000. Windows XP followed.
Bambino
Bambino13mo ago
Knowing that the first 128 characters in Unicode are directly mapped to ASCII helps me plan how I will do things when I rewrite my methods to compensate for different versions of the client.
mtreit
mtreit13mo ago
You should use System.Text.Encoding and not try to do it all yourself
Bambino
Bambino13mo ago
That's awesome. Must have changed a lot during it's lifetime considering the UI was based off of WIndows 95.
mtreit
mtreit13mo ago
Yeah it's a very different landscape now 🙂
Bambino
Bambino13mo ago
Will do
mtreit
mtreit13mo ago
Is the string length encoded in the file? Or it's null terminated?
Bambino
Bambino13mo ago
I want to say the 2023 version is using null terminated UTF-16 looking at the values: 37 00 2C 00 20 00 30 00 2C 00 20 00 39 00 38 00 2C 00 20 00 31 00 00 00 36 37 00 -> "7" 2C 00 -> "," 20 00 -> " " 30 00 -> "0" 2C 00 -> "," 20 00 -> " " 39 00 -> "9" 38 00 -> "8" 2C 00 -> "," 20 00 -> " " 31 00 -> "1" 00 00 -> null character 36 -> "6" However, I am not sure on how to find out if string length is encoded in the file.
Accord
Accord13mo ago
Was this issue resolved? If so, run /close - otherwise I will mark this as stale and this post will be archived until there is new activity.
Want results from more Discord servers?
Add your server
More Posts
✅ Optimizing `HTTPClient.GetAsync()` and large stringsI am writing a client application that consumes a web API at a near real-time rate (100ms). I am usi❔ .NET Service discoveryI have a gRPC server installed on a workstation in an on-premise local network. And I have client apHow do you get the output type from a powershell script with System.Management.Automation?I am trying to figure out how to get the type specified in a powershell script `[OutputType([int])]`❔ Make interface deriving from another interface have a sealed implementation of a function...so that you dont have to write the same code for every child interface. details are in pastebin: ❔ Serialize objects from non-standard formatHello, I am trying to serialize objects from a non-standard format and dont know how to go about it ❔ Rider accepting completion deletes the next wordWhen I hit tab in Rider to accept a suggestion, it overwrites everything that comes after. Just likeHow is my code?https://github.com/Jamboi2007/The-vote-game/blob/ccaf87812c4509c17b88e97dd639251c96252bc4/Home❔ c# access variable from other threadI've been searching the web for a while and I cant seem to access a variable like this: `img.Source❔ MAUI Error when loading resources on loading pageI have a view that displays the `Cards` property as a `CollectionView`. The view model is populating✅ Writing a scuffed way to use an "assignment" (=) operatorObviously, there's no such thing as an assignment operator. I'd like to find some way around that. I