FFXIV 1.0 Classic Server Dev Blog

Recruitment System Done

Woo, on a roll! So I spent all of Sat. trying to figure out how the hell the Search Info data is sent to the client with no progress. I decided to instead take a look at the recruitment system since I knew some of the packets. First was figuring out packet 0x1C5 which tells the client what "state" the client is in regards to the recruitment system. Two simple flags really (plus some ids): Are you recruiting? and are you the leader of said recruitment? If you are not recruiting, the game will show the "Recruit" button. If you are, depending if you are the recruiter or not the button will show "Close" or "Details". I've also figured out the Start/End recruitment packets (0x1C3/0x1C4), the get details packet (0x1C8) and the search results packet (0x1C7). I still need to figure out what the hell 0x1C6 does, but it just says "Unable to accept recruitment" when a 0 at byte 0 is sent.

Here are some pictures. If you played on Seventh Umbral and chose party, you'd notice the recruit button was blanked out and the search button didn't work. This is because the client is waiting for the current state from the server:

 undefined

Here is the start recruiting window. The received packet is parsed and a confirm is sent back (you can see some messages in the chat window):

 undefined

This is the search window, only some test data is sent back:

 undefined

Finally the details window when a recruitment is selected or you click the "Details" button:

 undefined

Higher res pictures here: http://imgur.com/a/FSaRg

Finished the social packets

The friend and black lists have been finished. It seems both use a "set list" packet which adds entries into their lists. The friend list has an extra packet that is requested for when you open it up which updates all the status of each friend (online or offline). Next up is figuring out the search packets.

Another huge batch of packets figured out!

Over the last few days I have found a lot of packets in the 0x1C_ and 0x1D_ range. The former has mainly to do with social lists; your recruitment list (haven't figured this out yet), black list, and friend list. The latter has to do with the Help Desk section of the app. These deal with showing FAQ messages and GM responses. I can now fill the Help Desk FAQ/Info window with up to 5 messages (title/body). This window could be useful for showing news updates from within the game!

undefined

I also have control of the GM messaging packets... I can inform the client a GM message has been received flashing the GM icon:

undefined

When the user enters the Help Desk, a request is fired for the received GM message:

undefined

Also when writing a new message, the issue list is given (this is dictated by the server):

undefined

I have the end ticket packet as well which closes the GM reply window and makes the GM icon disappear. Sadly creating a GM request message causes the client to D/C from the server for some reason. I am not sure why.


The server can now add/remove players to the friend and black lists. Below is some screenshots:

undefined

undefined

Higher res pictures here: http://imgur.com/a/U7OgG

FFXIV's Opcode Switch Found

I've found a key piece of information that should help reverse engineer FFXIV's protocol: The game's opcode switch. An opcode is the number or ID in a packet, which is used to tell the game (or server) what the packet does. A switch is a type of statement in programming that takes in a number (in this case the opcode), and branches to different pieces of code depending what the number is. Until now I was writing down packet IDs from the stuff that was sniffed while the game was live (from Seventh Umbral), or straight up guessing. Now I know every opcode in the code (getting rid of some of my guesses), and can actually see whats happening when the client receives one. This should make figuring out more game packets easier.

Also I found the enmity indicator packet (0x195) and the set has chocobo packet (0x199). I've also figured out the summon goobbue packet but it's a bit finicky.

More on Actor Parameters

Yesterday I continued my work on looking into actor paramaters. I decided to figure out the hashing function SE was using to convert a string like

charaWork.parameterSave.hp[0]

to the hex number

0x4232BCAA

I already knew the location of the hash routine from yesterday's post, but in all the excitement I didn't actually take a look at what it was doing. Finding out the algorithm was actually very easy. I found a constant being multiplied repeatedly throughout the routine; 0x5bd1e995. A quick Google gave me my answer: MurmurHash2. I used this code https://code.google.com/p/smhasher/source/browse/trunk/MurmurHash2.cpp?r=130#37 to compare what was being run in assembly code, to the C code in the link. The only difference was SE used a seed value of 0 (strange), and the program worked end to beginning unlike this code that works beginning to end. After modifying the C code I was able to convert a bunch of hashes to their proper IDs. Woo, no more hex numbers! I can use the proper labels!

This is the C# version of the code, though I have a bug in the switch statement I still have to figure out. I'm still learning C# and I found my first weird caveat with the language: switch statements can't fall through. People suggested using GOTO as a workaround. Never thought I'd every use GOTO this day and age, but there you go!

public static uint MurmurHash2(string key, uint seed)
{
 // 'm' and 'r' are mixing constants generated offline.
 // They're not really 'magic', they just happen to work well.

 byte[] data = Encoding.ASCII.GetBytes(key);
 const uint m = 0x5bd1e995;
 const int r = 24;
 int len = key.Length;
 int dataIndex = len - 4;

 // Initialize the hash to a 'random' value

 uint h = seed ^ (uint)len;

 // Mix 4 bytes at a time into the hash
 

 while (len >= 4)
 {
 h *= m;

 uint k = (uint)BitConverter.ToInt32(data, dataIndex);
 k = ((k >> 24) & 0xff) | // move byte 3 to byte 0
 ((k << 8) & 0xff0000) | // move byte 1 to byte 2
 ((k >> 8) & 0xff00) | // move byte 2 to byte 1
 ((k << 24) & 0xff000000); // byte 0 to byte 3

 k *= m;
 k ^= k >> r;
 k *= m;

 h ^= k;

 dataIndex -= 4;
 len -= 4;
 }

 // Handle the last few bytes of the input array

 switch (len)
 {
 case 3: 
 h ^= (uint)data[2] << 16; goto case 2;
 case 2: 
 h ^= (uint)data[0] << 8; goto case 1;
 case 1: 
 h ^= data[1];
 h *= m;
 break;
 };

 // Do a few final mixes of the hash to ensure the last few
 // bytes are well-incorporated.

 h ^= h >> 13;
 h *= m;
 h ^= h >> 15;

 return h;
}

 

Newer posts → Home ← Older posts