pinapelz's blog

Entering the Gilded Araya Early, FFXIV Server Emulation

Lets take a look at the state of FFXIV server emulation and walk around the new unreleased trial instance


Every once in a while I think back to Final Fantasy XIV’s producer’s statement following the outrage regarding using 3rd party plugins to clear Dragonsong’s Repirse (Ultimate). Ultimates are designed to be the hardest difficulty of raid content in the game, and as such often times “Race to World First” events are held to see which group of players can clear the content first.

While there’s usually always some controversy surrounding players using damage meters and triggers, interestingly this post was the one time where Yoshi-P (the producer of FFXIV) addressed the use of server emulation.

Server Emulation statemnt from Yoshi-P
Yoshi-P's statement on server emulation from the Lodestone post

There’s quite a lot to unpack here so lets break down the current state of FFXIV server emulation and see what is and isn’t possible.

Quick Note

To my knowledge, there is only one server emulator project for FFXIV that is active and that would be Sapphire. As such, this post will be focusing on Sapphire and its capabilities.

FFXIV is run by a variety of independent programs operating on a multitude of specialized servers, so to completely emulate its server environment outside our infrastructure is impossible

Like most MMOs, FFXIV is split into 2 main components: the client and the server. To put it briefly…

One good example of this dynamic is questing, when you go to accept a quest from an NPC, the client will send a packet to the server saying “Hey, I want to accept this quest” and the server will then respond with “Okay, you’ve accepted the quest”. The client will then render the quest in your quest log.

Questing server to client diagram

All the data pretaining to quest text, textures, and interactions are stored on the client side. The only thing the server needs to handle is recording the state of the quest (accepted, completed, what step you’re on, etc) and telling the client what actions should occur based on that state.

Let’s tak a look at sidequest SubSea007 (A Thousand Words) as an example.

First looking at the client side we have access to the EXH (Excel Header) files which contain all the dialogue and quest descriptions.

subsea007_00117.exh_en.csv
Index	0 [0x0][0x0]	1 [0x0][0x4]
0	TEXT_SUBSEA007_00117_SEQ_00	Latisha needs someone to report a bilking patron.
1	TEXT_SUBSEA007_00117_SEQ_01	A first-time patron has left without settling his bill, and Latisha wants you to report the incident to the Yellowjackets. First, speak with R'sushmo and receive her sketch of the criminal.
2	TEXT_SUBSEA007_00117_SEQ_02	A visibly shaken R'sushmo entrusts you with her rendition of the offender. Deliver it to Godebert at the Coral Tower.
3	TEXT_SUBSEA007_00117_SEQ_03	Unfortunately, the artist's rendition alone will not suffice to identify the offender. Instead, Godebert bids you tell R'sushmo to present herself at the Coral Tower for questioning.
4	TEXT_SUBSEA007_00117_SEQ_04	R'sushmo cannot comprehend what the problem is with her handiwork. You do not have the heart to tell her the truth.
5	TEXT_SUBSEA007_00117_SEQ_05	dummy
6	TEXT_SUBSEA007_00117_SEQ_06	dummy
7	TEXT_SUBSEA007_00117_SEQ_07	dummy
8	TEXT_SUBSEA007_00117_SEQ_08	dummy
9	TEXT_SUBSEA007_00117_SEQ_09	dummy
10	TEXT_SUBSEA007_00117_SEQ_10	dummy
11	TEXT_SUBSEA007_00117_SEQ_11	dummy
12	TEXT_SUBSEA007_00117_SEQ_12	dummy
13	TEXT_SUBSEA007_00117_SEQ_13	dummy
14	TEXT_SUBSEA007_00117_SEQ_14	dummy
15	TEXT_SUBSEA007_00117_SEQ_15	dummy
16	TEXT_SUBSEA007_00117_SEQ_16	dummy
17	TEXT_SUBSEA007_00117_SEQ_17	dummy
18	TEXT_SUBSEA007_00117_SEQ_18	dummy
19	TEXT_SUBSEA007_00117_SEQ_19	dummy
20	TEXT_SUBSEA007_00117_SEQ_20	dummy
21	TEXT_SUBSEA007_00117_SEQ_21	dummy
22	TEXT_SUBSEA007_00117_SEQ_22	dummy
23	TEXT_SUBSEA007_00117_SEQ_23	dummy
24	TEXT_SUBSEA007_00117_TODO_00	Receive R'sushmo's artist's rendition.
25	TEXT_SUBSEA007_00117_TODO_01	Deliver the artist's rendition to Godebert at the Coral Tower.
26	TEXT_SUBSEA007_00117_TODO_02	Speak with R'sushmo.
27	TEXT_SUBSEA007_00117_TODO_03	dummy
28	TEXT_SUBSEA007_00117_TODO_04	dummy
29	TEXT_SUBSEA007_00117_TODO_05	dummy
30	TEXT_SUBSEA007_00117_TODO_06	dummy
31	TEXT_SUBSEA007_00117_TODO_07	dummy
32	TEXT_SUBSEA007_00117_TODO_08	dummy
33	TEXT_SUBSEA007_00117_TODO_09	dummy
34	TEXT_SUBSEA007_00117_TODO_10	dummy
35	TEXT_SUBSEA007_00117_TODO_11	dummy
36	TEXT_SUBSEA007_00117_TODO_12	dummy
37	TEXT_SUBSEA007_00117_TODO_13	dummy
38	TEXT_SUBSEA007_00117_TODO_14	dummy
39	TEXT_SUBSEA007_00117_TODO_15	dummy
40	TEXT_SUBSEA007_00117_TODO_16	dummy
41	TEXT_SUBSEA007_00117_TODO_17	dummy
42	TEXT_SUBSEA007_00117_TODO_18	dummy
43	TEXT_SUBSEA007_00117_TODO_19	dummy
44	TEXT_SUBSEA007_00117_TODO_20	dummy
45	TEXT_SUBSEA007_00117_TODO_21	dummy
46	TEXT_SUBSEA007_00117_TODO_22	dummy
47	TEXT_SUBSEA007_00117_TODO_23	dummy
48	TEXT_SUBSEA007_00117_LATISHA_000_0	We spare no effort to ensure that all our patrons enjoy the finest food and hospitality. And so it is doubly hurtful when someone decides to leave without settling the bill. Nary a bell ago, a first-time patron did just that.
49	TEXT_SUBSEA007_00117_LATISHA_000_1	R'sushmo is the one who lovingly prepared the man's meal. She is quite upset by the whole affair, and has taken it upon herself to draw an illustration of the offender. Might I trouble you to deliver it to the Yellowjackets on our behalf? R'sushmo should be done adding the finishing touches by now.
50	TEXT_SUBSEA007_00117_RSUSHMO_000_10	Grrr... I poured my heart and soul into that grilled dodo... If I ever get my hands on that scoundrel, I'll grind his bones to make my bread!
51	TEXT_SUBSEA007_00117_RSUSHMO_000_11	Here it is, my rendition of the criminal! Please deliver it to Godebert at the Coral Tower─he will know what to do!
52	TEXT_SUBSEA007_00117_TALK_SCENE00003_000	Textless
53	TEXT_SUBSEA007_00117_GODEBERT_000_20	A bilker at the Bismarck, and you have an artist's rendition of the man?
54	TEXT_SUBSEA007_00117_GODEBERT_000_21	Er...what in the seven hells is this supposed to be? It looks more like the prow of a ship than a man's face!
55	TEXT_SUBSEA007_00117_GODEBERT_000_22	Look, we would like nothing more than to bring the offender to justice─'tis our duty, after all. However, we will need a little bit more to go on than this piece of...art.
56	TEXT_SUBSEA007_00117_GODEBERT_000_23	It would be easiest for all involved if R'sushmo were to come here and answer some questions. Please relay to her as much.
57	TEXT_SUBSEA007_00117_TALK_SCENE00005_000	Textless
58	TEXT_SUBSEA007_00117_RSUSHMO_000_30	Ah, so you've spoken with Godebert? Bwahahaha! Within the bell, the Yellowjackets will have that swindling scoundrel locked in a dungeon cell, and then I'll take my filleting knife and─
59	TEXT_SUBSEA007_00117_RSUSHMO_000_31	Huh? Godebert wants me to go to the Coral Tower for <i>questioning</i>!?
60	TEXT_SUBSEA007_00117_RSUSHMO_000_32	But what more information does he want that my drawing doesn't already provide!? Isn't a picture supposed to paint a thousand words!?

We also have access to the Lua files which contain mostly information about what should happen during each cutscene and sometimes “expected” quest logic.

SubSea007_00117.lua

A lot of the data in the Lua script is somewhat obfuscated, but we can see how the quest is split into individual scenes and what the client expects to happen during each scene.

This would probably be where it ends if this were a single player game, but since FFXIV is an MMO, everybody needs to be on the same page in terms of how far you’ve progressed in the quest. This is where the server comes in.

SubSea007.cpp

Since all the data about how NPCs need to move is scripted locally, all we need to do server side is to tell the client which scene needs to play

void onTalk( World::Quest& quest, Entity::Player& player, uint64_t actorId ) override
{
switch( actorId )
{
    case Actor0:
    {
    Scene00000( quest, player );
    break;
    }
    case Actor1:
    {
    if( quest.getSeq() == Seq1 )
        Scene00002( quest, player );
    else
        Scene00005( quest, player );
    break;
    }
    case Actor2:
    {
    Scene00003( quest, player );
    break;
    }
}
}

The onTalk function is called whenever the player interacts with an NPC and they have the quest active. So we need to check that the player is interacting with the correct NPC at the correct step of the quest before we call the appropriate function.

Let’s have a look at Scene00002 as an example:

  void Scene00002( World::Quest& quest, Entity::Player& player )
  {
    eventMgr().playQuestScene( player, getId(), 2, HIDE_HOTBAR, bindSceneReturn( &SubSea007::Scene00002Return ) );
  }

  void Scene00002Return( World::Quest& quest, Entity::Player& player, const Event::SceneResult& result )
  {
    eventMgr().sendNotice( player, getId(), 0, { Item0Icon } );
    quest.setUI8BH( 1 );
    quest.setSeq( Seq2 );
  }

After confirming that the player is on Seq1 of the quest, we call the Scene00002 function which will play the cutscene and then call Scene00002Return once the cutscene is finished.

We then send a notice to the player (in this case showing that they’ve completed thsi step of the quest)

To know what flags to set we can often go back to look at the Lua script and see what the client expects to happen during each particular scene, or we can brute force it by setting each flag manually and seeing what happens. (Sometimes not all flags are used since the Sapphire emulation is inherently different from the official servers)

In this case UI8BH is the flag that gives the player a key item. Turning this flag on makes it so that the necessary item is rendered within the player’s Key Item inventory.

This should give you an idea of how the client and server interact with each other. So while emulating the “independent programs operating on a multitude of specialized servers” isn’t possible, we can still see that in terms of “casual content” such as questing or just walking around the world, the client is doing most of the heavy lifting.

If all you wanted to do was walk around the world, interact with NPCs, /gpose, do basic story quests, then I’d say we’re pretty much there already. (I will get to instanced content later)

it would cost tens of millions of yen just to obtain the necessary servers. Without these servers and their proprietary programming, while one could potentially pull the client software and display model data and the like, the game itself will not operate.

Ok, I’m pretty sure my PC with a i7-9700K and 32GB of RAM does not cost tens of millions of yen. I think running Sapphire is doing a little more than just “pulling the client software and displaying model data”. You’re able to walk around the world and maps as if you were playing the game normally. You can interact with NPCs, dye your gear, etc.

While I definetly don’t think my PC can handle 10 000+ players on at the same time, I’m pretty sure it can handle the 8 players required for a raid. Its not that the hardware can’t handle it, its rather that the software isn’t there yet.

Even if one were to somehow accomplish such a feat, it would still be physically impossible to run the unique programming introduced to the servers upon the application of each patch before the patch is even released. The progression timeline for each duty exists solely within this server-side code, and is never included in the patch data downloaded to players’ clients.

This is where the biggest challenge lies for server emulation. While we can access a lot of data regarding where NPCs are, what they say, what items they give… We don’t have access to the server side code that handles instanced boss fights. Even if we spawned the boss in the correct location, it lacks the timeline and logic (outside of auto attacks) to make the encounter work.

Here’s an example script of a instanced battle quite early on in the Ul’dah storyline: Underneath the Sultantree

You can see that even though we can datamine the location of each object that makes up the environment of the instance, the logic behind where and when to spawn the enemies is still server side (along with logic regarding what friendly NPCs should be doing).

For that reason alone, it is impossible for anyone to emulate instanced battle content (especially before the patch is released) without running off of the official servers (or you’re a dev at SE and you leak it).

Implementing the logic for instanced battles would have to be done manually, and while it is possible, it would be a lot of work.

A video of an example quest battle implemented in Sapphire

Its not like they make it easy for us either

Making a server emulator essentially means reverse engineering the game. While we have a lot of data to go off of in terms of the downloaded game files, Square Enix doesn’t particularly make it easy for us to make the game function.

The data sent between your game and the server is known as a packet. These packets are what define specific actions and instructions. For example, when you move your character, your client will send a packet to the server saying “Hey, I’m moving my character to this location” and the server will respond with “Okay, you’re now at this location”.

Each packet is associated with an opcode which is a unique identifier telling the server what the packet is about (and in turn what to do with it). The first task of server emulation is to figure out what each opcode, this step typically isn’t a major roadblock since many Dalamud plugins and ACT/damage meters require this information to function.

These opcodes change every patch because I guess for some reason SquareEnix believes that this will deter people from making 3rd party tools (which is certainly has not). I won’t get into the specifics here since you can easily find a lot more information done by more credible people elsewhere, but just know that this is probably one reason why the main branch of Sapphire is now locked in Heavensward.

The Gilded Araya

So then any time that the dev decides to make leave something in the patch data that isn’t used in the game, it usually means that we can do something with it by emulating it’s server side behavior.

As such is the case with The Gilded Araya, the trial that will be released in patch 6.5 (at the time of writing this it is Patch 6.48). Access to this area is meant as a FanFest 2023 exclusive for attending the event in person, but the data for the trial has already been included in the game files.

While as I’ve mentioned previously, we can’t emulate the server side encounter logic for the trial, we can still walk around the area and see what it looks like.

// This code is auto generated by Sapphire
#include <ScriptObject.h>
#include <Territory/InstanceContent.h>

using namespace Sapphire;

class TheGildedAraya : public Sapphire::ScriptAPI::InstanceContentScript
{
public:
  TheGildedAraya() : Sapphire::ScriptAPI::InstanceContentScript( 20014 )
  { }

  void onInit( InstanceContent& instance ) override
  {
    instance.registerEObj( "Entrance", 2007457, 9795764, 5, { 100.000000f, 0.000000f, 115.000000f }, 1.000000f, 0.000000f); 
    // States -> vf_lock_on (id: 11) vf_lock_of (id: 12) 
    instance.registerEObj( "Exit", 2000139, 0, 4, { 100.000000f, 0.000000f, 85.000000f }, 1.000000f, 0.000000f); 

  }

  void onUpdate( InstanceContent& instance, uint64_t tickCount ) override
  {

  }

  void onEnterTerritory( InstanceContent& instance, Entity::Player& player, uint32_t eventId, uint16_t param1,
                         uint16_t param2 ) override
  {

  }

};

EXPOSE_SCRIPT( TheGildedAraya );

All it takes is a simple script to tell the client that this content exists and that it should render it. The client will then take care of everything else.

The Gilded Araya - Image 1The Gilded Araya - Image 2
The Gilded Araya - Image 1The Gilded Araya - Image 2
The Gilded Araya - Image 3The Gilded Araya - Image 4
The Gilded Araya - Image 3The Gilded Araya - Image 4

Looks like it’s a really great spot to take screenshots. I’ll leave that one to the expert gposers though…

Hopefully you’ve learned a little more about how FFXIV works!