Inside the client
The WorldsPlayer's own code: how it is built, which parts matter, and where to look when something behaves oddly. This is the map you want before decompiling your own copy.
Three layers
There is a common assumption that is wrong: "the .exe is just a
launcher, so everything is in the .jar". There are three layers,
and the difference between them explains almost every odd behaviour the client
has.
The launcher
WorldsPlayer.exe is 87 KB and contains none of the client. It
locates a JVM, creates it with JNI_CreateJavaVM, and starts the
class named in WorldsPlayer.ini:
[Java Runtime Environment]
Main Class=NET.worlds.console.Gamma
JVM Source=favor_JRE
Minimum Version=1.6
Virtual Machine Parameters=-Xint -esa -Xms64M -Xmx512M
Two of those flags are worth knowing. -esa enables assertions for
the system classes, so a malformed packet fails loudly.
-Xint turns the JIT off — the client is slower but
deterministic, which is a fair trade when you are debugging it.
The jar
lib/worlds.jar is 1.6 MB, 719 classes, four packages:
| Package | Classes | What is in it |
|---|---|---|
NET/worlds/network/ | 84 | The whole protocol. 17,396 lines. Everything on this site derives from here. |
NET/worlds/scape/ | 420 | The 3D side: worlds, avatars, objects, WorldScripts. |
NET/worlds/console/ | 194 | The interface: chat, friends, lists, dialogs, the login wizard. |
NET/worlds/core/ | 21 | Utilities: the .ini reader, the table manager, assorted helpers. |
The native engine
The Java code declares 368 native methods, bound at runtime by
gamma.dll. Underneath sit RenderWare 2.1 by Criterion for
rendering and Granny3D by RAD Game Tools for skeletal animation — both
commercial middleware of the era, both 32-bit Windows.
gamma.dll exports no Java_NET_worlds_* symbols: it
registers its natives dynamically, carrying the class-name strings inside
itself.
Why the protocol could be read completely
Look at where those 368 native methods actually are:
| Package | Native methods | Covering |
|---|---|---|
scape/ | 195 | Rendering: shapes, surfaces, textures, transforms, animation |
console/ | 111 | Windows, dialogs, the embedded browser control |
core/ | 55 | Fast I/O, the Windows registry, the .ini reader |
network/ | 7 | DNS, Windows DDE, launching the updater |
None of those seven is on the protocol path. Packet framing, object ids, properties, all thirty-one commands, the entire handshake — every byte of it is ordinary Java bytecode. Nothing about the wire format is hidden in native code.
The same fact is why the client cannot run on a Linux JVM. However much of its logic is Java, it must load 32-bit Windows DLLs through JNI, so Wine with a 32-bit runtime is the only option and always will be.
The classes that matter
If you decompile the jar, these are the ones to open first. Line counts are from one build and will vary slightly.
| File | Why it matters |
|---|---|
network/WorldServer.java~1,900 lines | The state machine. The heart of the client's networking. If you read one file, read this one. |
network/netConst.java | Every constant in the protocol, in one place. |
network/netPacketReader.java | Packet framing. Shows exactly how the length byte is handled. |
network/ServerInputStream.javaServerOutputStream.java | Serialisation both ways. This is where Worlds' non-standard UTF lives. |
network/ObjID.java | Short and long ids, and the rules about which is which. |
network/PropertyList.javanetProperty.java, net2Property.java | The two property formats, and the code that discards a whole list on one bad byte. |
network/*Cmd.java | One file per protocol command. Easy reading. |
network/Galaxy.java~1,200 lines | The session and the set of servers it knows about. |
scape/Pilot.java | Your own avatar. Sends your position. |
scape/Drone.java | Everyone else's avatars, and how they get loaded. |
scape/PosableShape.java | The avatar filter. This is the class that turns everyone into the default figure when the permitted list cannot be fetched. |
console/Console.java | Chat colours, and where the script server address is looked up. |
core/ServerTableManager.java | The tables.dat format and its crypto. |
Three behaviours the code explains
Why an unknown property is safe
When the client receives the server's property list it stores the whole thing, then asks for the ids it knows by number:
public void propertyUpdate(PropertyList propList) {
this._propList = propList;
net2Property protocol = this._propList.getProperty(3);
...
net2Property http = this._propList.getProperty(24);
...
}
There is no loop over the entries, no switch and no default case. A property it
has never heard of is stored and never looked at again — which is the
whole reason a capability announcement can ride
along there without any client noticing. It also exposes what it kept, through
getProperty(int), so a modified client can read the announcement
without a single patch.
The reply to SESSINIT is the one place that does loop,
with a switch whose default prints a line and asserts. That assert only fires
when assertions are enabled for the client's own classes, and the launcher
passes -esa, which enables them for the system classes only. So it
prints one line and carries on — but that is why an announcement belongs
in the property list and not there.
Why everyone turns into the same avatar
PosableShape.getPermitted checks each avatar against a list the
client fetches from the script server. When that fetch fails, the check falls
through and every avatar that is not a plain .mov is
silently replaced with the default figure. No error, no message.
A single 404 on that list is therefore the reason "third-party avatars don't work" — and because it happens to everyone in the room at once, it looks like a server bug rather than a missing file.
Why remote avatars freeze or fly off
The update interval travels in microseconds and the client divides by a thousand. A server that sends milliseconds by mistake leaves the client dividing by zero, and every remote avatar stops or launches. The modified client floors the interval; a server should also just send the right unit.
WorldScripts, and the hole in them
A WorldScript is a Java class that makes a world do things. The client downloads
WorldScript*.class from whatever address is currently set as the
content server and hands it to defineClass — no signature, no
sandbox, no prompt.
Worth knowing: WorldScripts often fail to load anyway, because of a classloader bug in the original client. Failing and falling back to the internal copy is the normal path, not an error.
Data files beside the client
| File | What it holds |
|---|---|
worlds.ini / override.ini | Configuration. Two files, different sections, read by different code. See the client guide. |
universe/universe.dat | The universe map. |
actions.dat | Emote and action definitions. A data file, so the gestures are editable. |
tables/tables.dat | Lookup tables, lightly encrypted. Includes the inventory table the protocol never uses. |
avatars/ + avatars.zip | The built-in avatars. |
gamma.worldsmarks | Bookmarks, in the world-URL format. |
Gamma.Log | The log. Set debug=2048 and it records every packet in hex. |
Reading it yourself
Decompile lib/worlds.jar from your own installation. Vineflower
handles it; the output needs a few mechanical repairs before it will recompile,
and WorldsClient has a script for each of them.
Start at network/WorldServer.java and follow the commands out from
there. Everything on the protocol page came from
that file and its neighbours.