I'm in the middle of a ton of crap at home, but I'll try to sum up as best I can from what I know/remember.
Sylaei hit it right on, with how the client/server communicate. That's exactly what's going on. EQ2Emu is different than most others though, because LethalEncounter came up with an ingenious way for us to support multiple clients in the same code. These are done through "Structs and Opcodes lists", those xml files you see in the eq2world directory, and the opcodes table in the database.
The "Struct" (for World, Spawns and Items) is an xml data file that simple says "for opcode ____, client version ____, the data in this packet is <this type> and is <this long>.
Example:
Code: Select all
<Struct Name="WS_SpawnStruct" ClientVersion="1">
<Data ElementName="position" Substruct="Substruct_SpawnPositionStruct" Size="1" />
<Data ElementName="vis" Substruct="Substruct_SpawnVisualizationInfoStruct" Size="1" />
<Data ElementName="info" Substruct="Substruct_SpawnInfoStruct" Size="1" />
</Struct>
WS_SpawnStruct is the entry point for determining how to decipher the incoming packets relating to Spawns. The example is for client verion 1 (which merely means this is the struct in the very first client ever used to start EQ2Emu), and you see there are 3
substructs - position, vis and info. Sorry to start out with a confusing one, but this is the only one I actually (almost) understand

Substruct= tells the server to go find yet another struct by that name/client version and build the entire packet structure.
Code: Select all
<Struct Name="Substruct_SpawnPositionStruct" ClientVersion="1" >
<Data ElementName="pos_grid_id" Type="int32" Size="1" />
<Data ElementName="pos_x" Type="float" Size="1" />
.
.
etc...
then add to that:
Code: Select all
<Struct Name="Substruct_SpawnVisualizationInfoStruct" ClientVersion="1">
<Data ElementName="arrow_color" Type="int8" Size="1" />
<Data ElementName="locked_no_loot" Type="int8" Size="1" />
.
.
etc...
and finally:
Code: Select all
<Struct Name="Substruct_SpawnInfoStruct" ClientVersion="1" >
<Data ElementName="hp_remaining" Type="int8" Size="1" />
<Data ElementName="unknown2a" Type="int8" Size="3" />
<Data ElementName="power_percent" Type="int8" Size="1" />
.
.
etc...
In the end, the server has this huge amount of data elements, in a specific order, of specific types and lengths, and now knows that when client version "1" logs in, that client will understand the data from the server sent in that structure. Make sense? (there is of course a static header/footer added to the beginning and end - you will find this info in SpawnStructs.xml)
As for what this all means to you, it means when the world starts, it connects to a LoginServer. The LoginServer allows clients to connect to itself, where it identifies the version # of the client (client version 1, 863, 945, 1045 etc). The client tells Login what version it is. Login then looks in it's own list of supported clients (version_range1 through version_range2 in the `opcodes` table) and if the client matches one of those opcode ranges, Login allows the client through.
The client then picks what server it wants to log into - and though the LoginServer might have allowed the client to log in, if the server receiving the client data does not support client version 1045, then the client gets rejected.
If all the stars are aligned, and login+server allow the client entry, the client logs into a zone - and the zone startup wants to push Spawn data to the client - and it does so by looking at the client's version, building the list of recognizable opcodes based on version_rangeX, and then pushes the Spawn data it knows about to the client - and the client can see a spawn's position, visual appearance, and info (those 3 elements from above).
In order to get a very old client to work, you would probably need to disassemble the client and tear apart every opcode and build a version_rangeX from scratch. No small task, or we would have done it by now. Plus, not all of us have original clients

(though I do!)
How WE derive current client structs, is to collect data from EQ2 Live, open the file in an editor, and start comparing, byte by byte, whether something has changed or not (usually we only do that when something we know is broken or changed). Once we see SOE slipped a new INT32 in, we build a new struct xml to handle that specific client version #. If SOE changed opcodes (which they do all the time), we find out what the old packet was, and look for the same data and see what the new opcode is (usually opcode + 2 it seems in most cases and only for specific sets of functions - spawns, items, spells, achievements, etc) and build new opcode ranges for the client version in the `opcodes` table.
Almost sounds like I know what I'm talking about, huh? Zcoretri could answer better, he's the opcodes/structs guru around here.
I hope this is more helpful than my previous response

Good luck.