FINAL FANTASY XIV is an MMORPG for Windows, macOS, and PlayStation. The Mac version is essentially an identical build of the Windows version, except it runs in a customised version of Wine. The launcher / patcher for FFXIV is pretty famously disliked, and for Windows users there’s a very good custom one, but unfortunately it doesn’t work for Mac. So I made my own.

One of the more interesting challenges in doing so was dealing with how the FFXIV Launcher passes arguments between the launcher and the main game executable. In this post, I’ll do a deep dive into my journey of discovery to reimplement this logic in Swift.

## Why A Custom Launcher?

My launcher isn’t anywhere near as featureful as XIVLauncher. I don’t really have the time to dedicate to reimplementing its enormous feature set, plus some of its features would be very difficult to implement in macOS due to its onerous security policies and the fact that the game runs in Wine.

That said, the main things my launcher brings to the table are:

### A Native macOS User Interface

The default launcher, already largely just a Web View, also runs in Wine, so the experience is extremely jarring and out of place on a Mac.

On macOS username + password combos can be safely and securely stored in the Keychain. The default launcher only allows you to save your username.

## The Algorithm

If you load up Process Explorer on a Windows machine while FFXIV is running you will see something like this:

Command line:
"C:/Program Files (x86)/Steam/steamapps/common/FINAL FANTASY XIV Online/game/ffxiv_dx11.exe" "//**sqex0003GLBqsICCnUUr1zHEXvdQZUE-k385enBr_CALwmVqQbuVPSuvbjJlbOvdY1VxxfYsl_l1aNT7LOqY1hXgMFApoeNwsc9knIUdWVWhV7yN_Y-fWjlwbN--IHtqp1Yr_NmtCN9W4CyB7Cn3asasHYWjuLT4KZDY_1JC8sluramSAH3csIL6xvhdkJA1_QoQclBco327gI6s-7SzfhWpqkfXinp0ZiDaufVOoCByrYyDYvyoykEmcOZcgEU81dMCUM_xlS8Fz6MXXkaRhFt3Y0fxQ_M4H0UUJnBRF15wb7Ayw0wQF6tFnwn52b4G36S6nG_wv3aXC-yVZZ_HPTbaaCW9aSefxy1xv5yTTTgC2d5vkSGdMaInqdHkQ7FcNhfZr9jlVfcWRNnPveFijgBG2rb7lWYUESWOBpTTp**//"


Notice how the application is started up with just one argument:

//**sqex0003GLBqsICCnUUr1zHEXvdQZUE-k385enBr_CALwmVqQbuVPSuvbjJlbOvdY1VxxfYsl_l1aNT7LOqY1hXgMFApoeNwsc9knIUdWVWhV7yN_Y-fWjlwbN--IHtqp1Yr_NmtCN9W4CyB7Cn3asasHYWjuLT4KZDY_1JC8sluramSAH3csIL6xvhdkJA1_QoQclBco327gI6s-7SzfhWpqkfXinp0ZiDaufVOoCByrYyDYvyoykEmcOZcgEU81dMCUM_xlS8Fz6MXXkaRhFt3Y0fxQ_M4H0UUJnBRF15wb7Ayw0wQF6tFnwn52b4G36S6nG_wv3aXC-yVZZ_HPTbaaCW9aSefxy1xv5yTTTgC2d5vkSGdMaInqdHkQ7FcNhfZr9jlVfcWRNnPveFijgBG2rb7lWYUESWOBpTTp**//


This whole mess is what we are going to attempt to reimplement. Thankfully, some enterprising developers have already documented how this format works, plus XIVLauncher already exists, and works. So it should be a fairly simple matter of just copying what they’ve done, right?

## Dealing with GetTickCount

As per the XIV Dev docs, the plaintext arguments are first encrypted using Blowfish, using GetTickCount() & 0xFFFF0000 as the encryption key. Hang on, what is GetTickCount()?

According to the Windows developer documentation, GetTickCount is a function provided by kernel32.dll, which gets the number of milliseconds since the system was started.

Well, that’s a problem, it’s easy enough for XIVLauncher to simply DllImport from kernel32 to call the exact same underlying function that FFXIV itself uses, but we can’t do that on macOS. The exact semantics of how it works has to be identical to whatever the Wine shipped with FFXIV for Mac does, otherwise the keys won’t match and the game won’t be able to decrypt the arguments.

We know that the game will call GetTickCount() in Wine, so how does Wine implement it? Thankfully, Wine is open source, so let’s crack open that source code and get sleuthing. We know the function is provided by kernel32.dll in real Windows, so perhaps it’s in wine/dlls/kernel32?

Sure enough, in sync.c we see:

So it reads it from some global variable user_shared_data, fair enough. Something else must write into user_shared_data->u.TickCount then. After a lot of digging I discovered wine/server/fd.c.

In fd.c we see:

Getting close, in the same file I can see that the global monotonic_time is set in set_current_time like:

So what is monotonic_counter()? Turns out, it is in request.c:

Eureka! However, this just raises another question: in the Wine shipped with FFXIV, does monotonic_counter() use mach_continuous_time, or does it use mach_absolute_time()???

Searching for wine mach_continuous_time brought me to an interesting patch in the Wine mailing list. Apparently some time in late 2019 they changed GetTickCount() on macOS to use mach_continuous_time(), since that better matches the behaviour of the real Windows implementation. Versions of Wine prior to that will use mach_absolute_time().

How do we know which version of Wine FFXIV uses? This is tricky to answer since it is not a bog standard Wine, but a specialised build from Codeweavers, the company which handled the FFXIV Mac port and are the primary maintainers of Wine. Running:

/Applications/FINAL\ FANTASY\ XIV\ ONLINE.app/Contents/SharedSupport/finalfantasyxiv/bin/wine --version


Product Name: FINAL FANTASY XIV ONLINE
Public Version: 1.0.5
Product Version: 18.5.0.31941local
Build Timestamp: 20200213T194053Z


See what I mean about a specialised build? Well then nothing for it but to crack open Visual Studio on my Windows machine and code up a test application.

## Test Applications

### Windows (C++)

I slapped together a very basic Console Application in Visual Studio C++:

Then I compiled it and copied it over to my Mac. Running it with:

/Applications/FINAL\ FANTASY\ XIV\ ONLINE.app/Contents/SharedSupport/finalfantasyxiv/bin/wine GetTickCount.exe


Returns…

… nothing? What’s going on here? I would expect an error message, or a segfault maybe, but not nothing. After spending entirely too long going down the wrong rabbit holes I realised that this version of Wine does not emit anything on the console unless you also provide --verbose. D’oh.

/Applications/FINAL\ FANTASY\ XIV\ ONLINE.app/Contents/SharedSupport/finalfantasyxiv/bin/wine --verbose GetTickCount.exe


Finally, some console output! An error message:

0026:err:module:import_dll Library MSVCP140D.dll (which is needed by L"C:\\GetTickCount.exe") not found
0026:err:module:LdrInitializeThunk Importing dlls for L"C:\\GetTickCount.exe" failed, status c0000135


Crap. This is the kind of error you would normally fix in Windows by installing the Visual C++ Runtime. However, we need this to work just with whatever is pre-installed in the Wine Bottle that FFXIV runs in…

Maybe it doesn’t like the 64-bit build? I know lots of software in Windows is still compiled in 32-bit…

/Applications/FINAL\ FANTASY\ XIV\ ONLINE.app/Contents/SharedSupport/finalfantasyxiv/bin/wine --verbose GetTickCount-32.exe


Now returns:

winewrapper.exe:error: cannot execute L"GetTickCount-32.exe"


OK, maybe not. Makes sense, anyway. FFXIV is 64-bit, plus Apple removed all 32-bit support from macOS Catalina, and FFXIV is supposed to be fully supported on macOS Catalina, so it must be a 64-bit bottle.

After some searching I discovered that VCRUNTIME140D.dll references the Debug builds of the Visual C++ Runtime, and turns out I did build it in Visual Studio in Debug mode. A quick recompile later, and…

0026:err:module:import_dll Library VCRUNTIME140_1.dll (which is needed by L"C:\\GetTickCount.exe") not found
0026:err:module:LdrInitializeThunk Importing dlls for L"C:\\GetTickCount.exe" failed, status c0000135


Oh come on! At this point I decided to see if there’s a way to simply statically link the Visual C++ Runtime so it’d just be embedded in my .exe. Turns out, there is. In Visual Studio, right click the project, Properties. Then in Configuration Properties → C/C++ → Code Generation, change the Runtime Library setting to Multi-threaded (/MT). A recompile and transfer later and finally it wor–

wine: Unhandled page fault on read access to 0x00000009 at address 0x140021469 (thread 0026), starting debugger...


Oh. Guess it doesn’t like having the Visual C++ 2019 Runtime statically linked, huh. Hang on a minute, Visual C++ 2019, maybe that’s the problem? Maybe the Wine Bottle only has an older Visual C++ Runtime. What’s the oldest C++ build tools I can install for Visual Studio Community 2019?

Seems like it’s the tools from Visual Studio 2015, v140. Let’s install that, retarget the project to it, and try again.

ticks = 141937076


Huzzah! Now, to find out whether this matches mach_continuous_time() or mach_absolute_time().

### macOS (Swift)

I slapped together a very basic single file Swift application like so:

Compiling this, then running this more or less at the same time as my Wine program above:

/Applications/FINAL\ FANTASY\ XIV\ ONLINE.app/Contents/SharedSupport/finalfantasyxiv/bin/wine GetTickCount.exe && ./MachTime


Returns:

ticks = 168022842
getTickCount[absolute]: 168022847
getTickCount[continuous]: 935308133


So you can see it’s a damn-near exact match for mach_absolute_time(). Finally!

## Blowfish, Endianness, and Swift

I know I need something that can do Blowfish encryption, and the existing cryptography library I used doesn’t expose this algorithm, which means it’s time to go library hunting! My options are:

• Use the underlying CommonCrypto framework, which only exposes C functions
• Use something pure Swift

As a learning exercise, I decided to pull in a pure Swift library for this: CryptoSwift. I go ahead and start implementing, and write a unit test based on the results generated by XIVLauncher, which are known to work.

However, inexplicably, the cipher text produced with the same key ends up vastly different from CryptoSwift compared to the Blowfish.cs in XIVLauncher. As a sanity check I whip up a quick implementation using CommonCrypto, but it yields the same cipher text as CryptoSwift!

I decide to bust open Visual Studio on my PC once again, this time loading up a small C# project that uses the Blowfish.cs implementation from XIVLauncher to encrypt the same parameters as my unit test. Now I have an environment I can attach debuggers to, to see where the implementations differ. Now, a disclaimer. I’m going to attempt to explain what I consider to be some very strange endianness issues, but I barely understand it and it makes my head hurt.

To start with, understand that Blowfish like many ciphers, fundamentally works by first breaking up your data into blocks of 8 bytes, and then each of those blocks into two 32-bit numbers, L and R. It then does some math on those values, and produces two new 32-bit numbers, which you can then break back apart into your 8 bytes.

First, I put a breakpoint in both projects where the byte array data is finalised, and compare the results. Both implementations agree:

[32, 84, 32, 61, 49, 50, 56, 52, ...]


Now I step forward to where the first block gets encrypted, and it extracts L and R.

C#:

L: 1025528864
R:  876098097


CryptoSwift:

L: 542384189
R: 825374772


Well, well, well, what in the hay is that?! No wonder the cipher text comes out so radically different if the values it’s reading out of the data disagree so badly. Since CryptoSwift and CommonCrypto both produce the same cipher text, whatever they’re doing must be logical in some way, but that doesn’t help me because FFXIV is expecting whatever it is that XIVLauncher is doing! So we’ll have to dig in deeper to see what the differences are in the Blowfish implementation.

To derive the L and R values, Blowfish.cs is using the .NET class BitConverter, specifically its ToUInt32 static method. If only there was some way to see the source code for this class. The answer lies right before my eyes:

This means that, when running on a little endian machine, such as my Intel Mac, it will interpret the 4 bytes in each half of each block as being little endian!

“A little-endian system, … stores the least-significant byte at the smallest address.”

The C# converts [32, 84, 32, 61] to a UInt32 by doing:

(32 << 0) | (84 << 8) | (32 << 16) | (61 << 24)


Whereas CryptoSwift / CommonCrypto does:

(32 << 24) | (84 << 16) | (32 << 8) | 61 << 0


No wonder it doesn’t work! Just to test my theory, and in desperation to see my unit test pass well after midnight, I fork the CryptoSwift code by essentially copying its Blowfish class and other needed files directly into my repo so I can make modifications to it. Unfortunately the license for CryptoSwift is weird and seemingly nonstandard, so I’m not comfortable deriving any code from it, so this is all temporary.

I modify the UInt32 extension to treat the input as little endian, and voila! The ciphertext now matches the .NET version! We did it reddit!

Only thing left now is to try to reimplement this without having a fork of a cryptography library in my repository. CommonCrypto, old faithful, is still there, and I’m not afraid of calling C functions in my Swift, so doing it this way probably makes the most sense, and maybe it could end up a bit faster than a Swift implementation too.

The problem is, CommonCrypto also works only by interpreting the data only in big endian. My solution was to do some old-school byte order swapping:

It’s not pretty but it does the job, and without needing to copy the array either. I ran the input byte array through this function and… the cipher text is still wrong, and different again from any other implementation. After inspecting the .NET cipher text, I noticed something…

.NET:

[24, 176, 106, 176, ...]


Swift:

[176, 106, 176, 24, ...]


Looks like the byte order on the emitted cipher text is also wrong! After doing the ol’ switch-a-roo on that:

[24, 176, 106, 176, ...]


… which means our cipher text now matches XIVLauncher exactly, and without using a CryptoSwift fork! I quickly connect it up in the main application logic, making sure to use mach_absolute_time for wineGetTickCount, and, at long last:

So what do we do now?