Welcome Guest, Not a member yet? Create Account  

Reversing Spellborn

(This post was last modified: 15-03-2015, 10:10 PM by Saltiel.)

UPDATED DOC IS HERE => https://github.com/TCoS-Rebirth/Documentation

if you want to update it,  ask to join the TCoS-Rebirth organization
by giving your github username.
Hi everybody,
(First of all, sorry for the big post. :/)
I don't know if there is still someone here except JW-NL haha ! But still I think maybe people could be interested in what I have done so far.
So some weeks ago I saw a thread about Star Wars Galaxies Emu, and I told myself "that would be so great for TCoS" and remembered the thread from acidburn and JW-NL reversing the files. And then began the envy to try to reverse the packets allowing me to login into hawksmouth.
I don't know if I will be able to go that far, but still, I managed to go farest than I thought I was capable of. I learned a lot in reverse engineering these last weeks and still have a lot to learn, but I wanted to share what I learned with this passionnate community. As I love the team who made Spellborn, I don't want to do something illegal or that would bother them. So I won't share source code (and there is no source code really exploitable right now), but just documentation about the data and packets.
(this part is a copy paste of what I answered in RageZone forums to someone wanting to do a TCoS server emulation).
Well, I am a noob in reverse engineering but I do programming for a few years now (computer science student for 5 years) and I am an old fan of The Chronicles of Spellborn (discovered the game in 2005 and immediatly fell in love with its visual and music! Saltiel is not the pseudo I used on the game forums).
I don't know why but two weeks ago I suddenly had the envy to reverse it, at least to be able to explore a map, even empty (without npc, mobs etc).
Little disclaimer : I don't want to engage myself in anything because I have a lot of projects, but currently it's something I am working hard on. I have to say that digging through assembly is pretty hard, and a big programm like a MMO is not an easy one to start with.
I started to spot some interesting places in the code, but reverse engineering them is not easy.
What I reversed so far:
<ul class="bbc">[*]connect/disconnect packet[*]login packet (client/server) : can validate login or send error (bad login/password or wrong client version)[*]universes list packet (client/server) (send universe list with name, population etc)[*]a good part of the universe selected packet (sending ip/port etc of the game server)[*]How to get packet ID[*]What is the header of every packets[*]Low level packet writing / reading in d_mmos.dll[*]Some parts of the write/read functions of the packets mentionned previously in SBPacket.dll[*]I also start to have an understanding of the packet management architecture that allows me to find quickly basics information and place to investigate when starting to reverse a packet.</ul>The process is still very slow and "hand crafted". I will try to document what I did when I have the time.
I will edit this post with much details later.
I still have some things to add. I will do when I have the time.
I currently have a dummy server which can answer to one connection. I use the client beta 0.9 because it does not have GameGuard. I know there are ways to deactivate GameGuard but I am currently not skilled enough to do that so I prefer to focus on packet reversing.
Also if someone have the files to make the update to the last version of TCoS, I am very, very interested.
Have a good day !
PS: Sorry for my bad english spoken, need to work on that too ><
Edit: current version of the doc, I am sorry this doc is originally in markdown language on a wiki but this forum does not understand this syntax:
All reversing documented here is currently based on the client beta 0.9.
Sb_client.exe arguments
--show_console: show the console displaying the log (you can also take control of the console and use some functions/load packages)
--packet_log: enable logging of the packets sended et received
--world: not really sure what this option means... seems like bypassing universe selection but does not seem to work
-- uc_debugger: (check syntax) attach unreal engine debugger to the process. Sadly scripts files are not reachable.
--unreal_log: seems to do nothing. Test
TCoS client encodes its strings in UTF16-LE.
Connect to custom login server
In etc/client/sb_client.rc, modifie the line with client/net/login_addr with desired IP and port.
Contains low level functions for sending packets.
Interesting places:
  • d_message class with read (void* dataOut, uint dataSize) and write (const void* dataIn, uint dataSize)
  • d_address class: represent IP+Port
d_message class
d_message::read (void* dataOut, uint dataSize)
void* dataOut is a pointer to a memory location where the value read will be stored. Sometimes this memory is in stack, sometime in heap. It totally depends of the caller. For instance if the client tries to read a string, dataOut will be a pointer to the std:Confusedtring::data () (or equivalent).
uint dataSize is the size in bytes of the value you want to read. For instance if you want to read a DWORD (double word = 4 bytes) its value will be 4.
The d_message object keeps track of the data you've read in it. So each time you call its read method, it updates an internal value with the size of data already read (it simply adds dataSize to its current value). This is also an offset allowing it to know where to begin the reading of the data in the message. Each time you call read, you actually ask to read the next data in the d_message object. If there is no more data, or not enough (there is still 2 bytes and you ask to read 4 bytes), the method will raise an exception.
d_message::write (const void* dataIn, uint dataSize)
Work the same way than read but write dataSize bytes of data pointed by dataIn inside the d_message. Same mechanism checking that not too much data is written.
Contains packet high level stuff. Use templates a lot, which means that the window 'Names' in IDA won't show you all the methods because the same actual method can be exported with multiples names (not apparing in 'Names' but only in 'Exports'). So, look in 'Exports' instead.
The interesting classes are d_packet<struct PACKET> and d_serializable<struct PACKET> where 'struct PACKET' is the template parameter. Interesting methods in d_packet:
  • GetID ()
  • GetName () (but actually the name is exactly the same as the template parameter name)
Interesting methods in d_message:
  • ReadMessage (const struct d_message&)
  • WriteMessage (struct d_message&)
/! /! /! ==>WriteMessage and ReadMessage are very interesting to study for each message because it's there you can understand what is put inside the packet.<== /! /! /!
This is how I try to reverse packets, by studying these two methods and their context (who calls them, what structure do they fill etc).
To find quickly which message has a specific ID, in OllyDBG use "find command" and type move ax, ID where ID is a short number. It will point you the good GetID () method. The template parameter gives you the message name.
Note: packet's names follow a simple convention. They have a prefix specifying if its a packet sended by the client to the server or by the server to the client.
C2L: Client to Login server
L2C: Login server to Client
C2S: Client to Server
S2C: Server to Client
There are other prefix but currently I did not find what they mean.
Packets layout
Every packet I have seen in the login process (login+ select universe) have this header:
<pre>struct PacketHeader (32 bits)
WORD PacketID;
WORD PacketSize;
</pre>If the PacketSize attribute does not match the actual packet size, the client will not trigger the ReadMessage or will trigger an error. If the client wants to read more data than you give to him, it will sayin the logs: "PACKET_NAME READ BUFFER OVERFLOW".
Note: For what I've seen so far, the client does not check the size in its read algorithm to verify if its is consistent with the type of packet you send.
The client will just try to read everything it has to read and if you send not enough data, triggers the BUFFER OVERFLOW error seen above. This behavior is due to the d_mmos.dll: [Image: default_biggrin.png]_message::read (const void* outData, uint dataSize) method which check the message size everytime you ask it to read a value in the packet. If the size alreday read + the size of the data you ask to read is greater than the message size: error.
Note: The following sizes does not take the packet's header into account, because the size contained in the packet's header do the same.
<pre>struct CONNECT (4 bytes)
struct PacketHeader header;
DWORD unknownDword; //statuscode?
</pre><pre>struct DISCONNECT (various size)
struct PacketHeader header;
DWORD unknownDword; //statuscode?
DWORD reasonNumChars;//num chars in the reason string
char [reasonNumChars*2] reason;//in UTF16-LE
</pre><pre>struct C2L_USER_LOGIN (various size)
struct PacketHeader header;
DWORD clientVersion;//svn build version
DWORD loginNumCharacters;
char[loginNumCharacters*2] loginString;//in UTF-16LE
DWORD passwordNumCharacters;
char[passwordNumCharacters * 2] passwordString;//in UTF16-LE
</pre><pre>struct L2C_USER_LOGIN_ACK (2 DWORDS = 8 bytes)
struct PacketHeader header;
DWORD zeroDword;//unknown data, its value does nothing
DWORD statusCode;
</pre>statusCode possible values:
  • 0: Login OK
  • 1: Wrong client version
  • 2: Bad login or password
  • 3: Bad login or password (yes, the same)
  • above 3: to investigate!
<pre>struct C2L_QUERY_UNIVERSE_LIST (empty)
struct PacketHeader header;
</pre><pre>struct Universe (various size)
DWORD universeID;
DWORD universeNameNumChars;
char[universeNameNumChars*2] universeName; //UTF16-LE
DWORD universeLanguageNumChars;
char[universeLanguageNumChars*2] universeLanguage; //UTF16-LE
DWORD universeTypeNumChars;
char[universeTypeNumChars*2] universeType; //UTF16-LE
DWORD universePopulationNumChars;
char[universePopulationNumChars*2] universePopulation; //UTF16-LE

struct L2C_QUERY_UNIVERSE_LIST_ACK (various size)
struct PacketHeader header;
DWORD unknownDWORD; // MUST BE 0 to work, integrity check?
DWORD universesNumber; //number of universes available
struct Universe[universesNumber] universesList; // universesNumber times the attributes in the struct Universe
</pre><pre>struct C2L_UNIVERSE_SELECTED (size = 1 DWORD)
struct PacketHeader header;
DWORD universeID; //id of the selected universe
</pre><pre>struct L2C_UNIVERSE_SELECTED_ACK (size = 1 DWORD)
struct PacketHeader header;
DWORD unknownDword#1; //MUST BE 0
DWORD unknownDword#2;
DWORD universePackageNameNumChars;
char[universePackageNameNumChars*2] universePackageName;
DWORD tkey;//to investigate
DWORD gameServerIP;
WORD gameServerPort;
</pre>Note: universePackageName is the name of the file in data/universe with the SBU sufixe removed from its name (I am not speeking of the extension but the sufix), and without the extension.
The tkey may be for "transport key" seems to be (not sure) a security allowing the game world to detect if a client did the login part before connecting to it. I think it works this way: the Login Server generate a tkey for a given player when it tries to login, send this key to the game world (by storing it in a commune DB or whatever) and to the client through this packet. When the client log into the game world, it sends back the key, the game world check if the key is correct and allow connection or not.
<pre>struct C2S_TRAVEL_CONNECT (size = 1 DWORD)
struct PacketHeader header;
DWORD tkey; //the tkey the login server sended in the L2C_UNIVERSE_SELECTED_ACK packet
</pre><pre>struct S2C_WORLD_PRE_LOGIN (size = 2 DWORD)
struct PacketHeader header;
DWORD unknownDWord;//MUST BE 0
DWORD worldId; //must be 1 for character selection
</pre>Note: the worldId seems to be an ID identifying the map to load but I'm not sure. In the log if the number you send is not 1, you see an error like "failed to load game world (worldId)". Should test with other IDs to see if it loads other maps file than baseCharacterSelection.sbw.
<pre>struct C2S_WORLD_PRE_LOGIN_ACK (size = 1 DWORD)
struct PacketHeader header;
DWORD unknownDWord;// = 0; to investigate
Here is the dialog between client and server, don't know if it's very useful. For detailed info about the packets structure, see the section above.
Client sends CONNECT to Login Server (status code?)
Client sends C2L_USER_LOGIN (login + password)
Login Server answers L2C_USER_LOGIN_ACK (status code)
Client sends C2L_QUERY_UNIVERSE_LIST (no data)
Login Server answers L2C_QUERY_UNIVERSE_LIST_ACK (status code?, universes list with infos)
Client sends C2L_UNIVERSE_SELECTED (universe selected id)
Login Server answers L2C_UNIVERSE_SELECTED_ACK (universe package's name, universe (game server) IP + port, and tkey)
Client sends CONNECT to Game Server Beginning of the conversatiom between Client and Game Server
Client sends C2L_TRAVEL_CONNECT to Game Server (with tkey)
Client sends DISCONNECT to Login Server (disconnection reason "connect to game world")
Conversation between Client and Login Server is now over.
Game Server sends S2C-WORLD_PRE_LOGIN (world setup id, must be 1, to investigate)
Client answers C2S_WORLD_PRE_LOGIN_ACK
Game Server sends S2C_WORLD_LOGIN
List of Packets ID
Client to Login packets ID
Login Server packets ID
Client to Game Server packets ID
Game Server packets ID
To be continued (other packet layout etc). If you have specific questions do not hesitate to ask, maybe I could answer it.




that is awesome!

I never played TCoS but immediately fell in love with it when i saw a video on youtube.

So some time ago i tried to do that myself (reversing), i ended up sending random packets to the game and trying to figure out the route they take when read and the errors in the console.

Sadly i never got very far and had to stop.

I have, not much, but some experience in writing multiplayer games.

If this thread develops i think i would feel encouraged to dedicate some spare time to that again.


Keep up the good work!



Wow, i can't believe there are still so many people who try to get Spellborn back.


Keep up your great work guys.


@Saltiel: I have some exe files to update the game manually. (example:

Don't know if you meant that.

What lies hidden. must be found

TCoS Gameplay Videos:


(This post was last modified: 02-09-2014, 04:07 PM by JW-NL.)

<p style="margin-left:40px;">If I remember right a lot of the Spellborn files and installers have been uploaded via torrents when Acid and I started working on it,

not sure if the files are still available online though.

Greetings Jan-Willem.

For me these are the known patches:

Size:             Filename:
  • 197.445.904
  •     9.836.680
  •        607.841
  • 601.618.064
  •   21.749.739
  •   18.251.491
  •        646.281
  •        608.331
  •        608.998
  • 204.512.272
  •     1.869.745
  • 285.021.486
  •        608.481
  •     9.074.328
  • 194.484.532
  •   10.224.036
  •     3.905.742
  • 425.082.352
  • 424.963.674
  •        799.574
  •   19.093.311
  • 181.951.116
  •     8.173.867
  •     1.610.654 sb_patch.1_0_5_1.pfz
  •     1.612.040 sb_patch.1_0_5_2.pfz



I have them from until (including sb_patch.1_0_5_2.pfz) .

Sadly not the older versions, but i think they are still available somewhere.

What lies hidden. must be found

TCoS Gameplay Videos:


(This post was last modified: 02-09-2014, 07:55 PM by Saltiel.)

Thank you for your answers and your interest! I'm happy to see Polymo that my post may have motivated you. [Image: default_smile.png]

Yes Kaiyn I'm talking of those files, I think it would be a big loss if they are definitively lost. I know there are ways to deactivate GameGuard so they could have an interest in the future if the reversing process progresses.


I forgot to say that I use the version beta 0.9. There are also some little mistakes in the current doc I published in my first post, I will update it when I will have the time (I prefer to prioritize on the reversing but documenting is very important too in that process).

I also managed to finish the reversing on the login server part. There is still one or two data in the universe list ack packet that I don't completely understand but I manage to make the client connect to the game world. It's just the beginning of the connection, so I do not see the character selection screen, but eventually that may happen. I hope!



I just updated the client files list:


[url=]'&do=embed' frameborder='0' data-embedContent>><iframe frameborder="0" data-embedcontent=""></iframe>[/url]

[Image: banner10.jpg]

(This post was last modified: 04-09-2014, 09:56 AM by Saltiel.)

Thank you very much acid-burn! Just started to download.


Edit: Hem, I just found a big bug in my program that invalidate the client packet part of my documentation. (read dword as word,... copy paste is bad). I will have to figure out again the packet structure based on my mistakes. I feel so stupid! Update as soon as I can.


Edit2: pfiou, bug solved and it explains some things I did not understand, so I have a better vision of the login process now. I will update the doc soon. [Image: default_smile.png]



I'm getting real excited because of this thread ^^


So glad I started this site, you guys can't believe how much.



I'm glad that this site still exists.


I wouldn't know where to look else for a little living sign of spellborn.


I think this is the last and only remaining community.


@topic: It seems that acids link includes all the patch files, so I don't have to upload mine.

What lies hidden. must be found

TCoS Gameplay Videos:


Users browsing this thread:
1 Guest(s)