The TCP protocol of Jazz Jackrabbit 2
Related: The UDP Protocol
struct TCP_Packet { byte packetLength; // Length/size of the packet byte packetID; // Identified type of packet misc extraData; // Can be of variable size };The second byte of the packet tells us what type of packet it is and what kind of data follows the header, if any. The
packetID
byte allows up to 256 different values, but for TCP packets only a very narrow range is actually used, 0×0D to 0×1B to be precise, leaving the rest unused.
struct diconnectClient { byte 0x08; // packetLength, always 8 here byte 0x0D; // packetID byte disconnectMessage; // See table below byte socketID; // The ID of the disconnected socket char serverVersion[4]; }Example: HEX:
08 0D 0C 01 32 31 20 20
ASCII: ....21
This banned socket ID 1 (player 2) from an 1.21 (or actually 1.23) server.
If it’s your socket ID then the server closes your socket (if you got kicked/banned etc..) so you wont be able to send or receive more packets.
disconnectMessage |
Description |
0×01 | Server full |
0×02 | Version different |
0×03 | ? |
0×04 | Error during handshaking |
0×05 | Feature not supported in shareware |
0×06 | Error downloading level |
0×07 | Connection lost |
0×08 | Winsock error |
0×09 | Connection timed out |
0×0A | Server stopped |
0×0B | Kicked off |
0×0C | Banned |
else | Unknown error |
struct joinDetails { byte packetLength; byte 0x0E; byte numberOfPlayers; // How many players are joining? struct playerArray[numberOfPlayers] { byte playerID; byte teamAndChar; // See below byte furColor[4]; char playerName; // playerName is null terminated } }teamAndChar:
team = byte & 0x10; //byte can be 0 (blue) or 1 (red).
char = byte & 0x03; //byte can be 0, 1, 2 or 3. (Jazz,Spaz,Bird,Lori/Frog)
Example of full packet:
HEX: 23 0E 02 01 11 40 40 40 40 47 72 79 74 6F 6C 6C 65 43 43 20 00 02 11 28 28 28 28 70 6C 61 79 65 72 32 00
ASCII: ....@@@@GrytolleCC ...((((player2.
2 players joining the server using socketID 1. Players are GrytolleCC and player2.
If you are making a GIP-script you may not want the name to be shown (JJ2+ only).
In that case, use this as name: §|
If you do, keep in mind that you won’t be able to do things that require staying in server, such as downloading level/tileset files, chatting, read chat etc… You are limited to the playerlist and the plus details packet.
struct joinRequest { byte 0x09; // packetLength, always 9 here byte 0x0F; unsigned short UDPbind; // Alternatively "byte UDPbind[2]", see below char clientVersion[4]; // Common values: "21 " for 1.23, "24 " for 1.24 byte numberOfPlayersFromClient; }If you don’t know how to use short (unsigned) integers, use this approach and the alternative UDPbind struct:
byte UDPbind[2];
UDPbind[0] = floor(bindport % 256);
UDPbind[1] = floor(bindport / 256);
Example packet:
HEX: 09 0F 10 0E 32 31 20 20 02
ASCII: ....21 .
2 players joining spitscreen with bindport 3600 from an 1.21 client.
The bindport is NOT 10052, it is the port you get from listening on an UDP socket. Set bindport to zero if you don’t want to use UDP (like if you’re making a serverlist/GIP script).
struct serverDetails { byte packetLength; byte 0x10; byte socketID; // Your socket ID, take care of it byte numberOfPlayersFromClient; // Same as sent in joinRequest? Confirmation? byte levelFileNameLength; char levelFileName[levelFileNameLength]; int levelCRC; // Remember, an integer is 4 bytes int tilesetCRC; byte gameMode; // See below for a list of gamemodes byte maxScore; // The following is for JJ2+ only int someChecksum[4]; // Seems level-unique byte unknownData[4]; // Random each time? byte plusVersion[5]; // Server’s JJ2+ version? int musicCRC; // I think this is the music file’s checksum. It is unique for levels with same music. }JJ2+ only data:
DA A9 05 7B F9 80 11 30 00 00 03 00 01 08 C0 3D E5 CF C5 57 DD 46 F2 DA F2 00 00 03 00 01 65 2C 2D 86 67 E5 9E 0B 91 5B 63 DB 00 00 03 00 01 A3 63 83 8F 67 E5 9E 0B F4 07 80 F3 00 00 03 00 01 A3 63 83 8F 67 E5 9E 0B F7 0A 67 4D 00 00 03 00 01 A3 63 83 8F 67 E5 9E 0B 4D D8 6B 3A 00 00 03 00 01 A3 63 83 8F 8F E7 D8 53 70 A3 CB FD 00 00 03 00 01 D0 56 89 87 84 BE 29 23 CB 39 F1 56 00 00 03 00 01 96 D0 BD E9 13 E5 AA 74 30 18 3B 1D 00 00 03 00 01 64 D0 FF 6F E6 35 E9 D0 4D BA A4 AC 00 00 03 00 01 64 D0 FF 6F BE FB AA 83 23 44 3C 9C 00 00 03 00 01 [musicCRC ] BE FB AA 83 9D EB 76 5F 00 00 03 00 01 [musicCRC ] BE FB AA 83 AC 3F AF D0 00 55 ac 87 70 a0 2f c1 00 55 ac 87 d1 35 27 3d 00 55 ac 87 ec 80 e6 9eI see patterns… clearly four parts
gameMode |
Description |
0×00 | Singleplayer |
0×01 | Cooperative |
0×02 | Battle |
0×03 | Race |
0×04 | Treasure Hunt |
0×05 | Capture The Flag |
else | Unknown gamemode |
1.23 address | |
Level CRC32 checksum | 0x5D01FC |
Tileset CRC32 checksum | 0x5D0200 |
Gamemode | 0x5A4B68 |
Maxscore | 0x5D01D0 |
struct joinNotification { byte packetLength; byte 0x11; byte socketID; // The joiner(s) have this socket ID byte numberOfPlayers; struct playerArray[numberOfPlayers] { byte playerID; byte teamAndChar; // See Joining details byte furColor[4]; char playerName; // playerName is null terminated } }This packet is sent to all clients except the one who join , the joiner already have all this information.
struct playerList { byte packetLength; byte 0x12; byte numberOfPlayers; struct playerArray[numberOfPlayers?] { byte socketID; byte playerID; byte teamAndChar; // See Joining details byte furColor[4]; char playerName; // playerName is null terminated } }Note that
numberOfPlayers
is sometimes not the total amounts of players. Because of some packet size limit, more packets of this ID could get recieved containing more players..
Note: This packet looks different to JJ2+ clients
struct gameInit { byte 0x02; byte 0x13; }This packet tells the client that the level should show instead of the loading screen.
First packet:
packetLength 0x14 packetCount[2] totalAmountofPackets[2] fileNameLength fileName
Later packets:
packetLength 0x14 packetCount[2] fileContent
h4(#h-9-1). TODO: Check if correct
packetLength 0x15 fileNameLength filename
Example:
HEX: 11 15 0E 6A 6A 32 77 63 33 63 74 66 31 2E 6A 32 6C
ASCII: ...jj2wc3ctf1.j2l
h4(#h-10-1). TODO: Check if correct
struct levelCycle { byte packetLength; byte 0x16; int levelCRC; // integers are 4 bytes int tilesetCRC; byte fileNameLength; char fileName[fileNameLength]; }
packetLength 0x17 unknownData
Example packet:
HEX: 17 17 20 FE 62 00 00 10 11 12 13 00 00 00 00 03 00 00 00 00 00 00 00
h4(#h-12-1). TODO: Have a look at the unknown data
I have seen for CTF games there are the final flag scores for the teams, etc..
struct eventsUpdate { byte packetLength; byte 0x18; byte? checksum; unsigned short counter; [up to 32 bytes of event states] }The number of packets sent depends on the number of generators and triggers in the level, of which the state of each one is represented by a single bit. h4(#h-13-1). TODO: Have a look at the packet overall
0x02 0x19
Tells the clients that the server stopped, then server closes all socket connections.
0×02 0×1A
Requests an update about spawn and trigger state.
h4(#h-15-1). TODO
How is the data formatted? What packet ID?
struct chatMessage { byte packetLength; byte 0x1B; byte fromSocketIDandTeam; // See below byte 0x20; // A simple space character char chatMessage; }
fromSocketIDandTeam
:
socketID = fromSocketIDandTeam & 15;
team = floor(fromSocketIDandTeam/16);
Jazz2Online © 1999-INFINITY (Site Credits). We have a Privacy Policy. Jazz Jackrabbit, Jazz Jackrabbit 2, Jazz Jackrabbit Advance and all related trademarks and media are ™ and © Epic Games. Lori Jackrabbit is © Dean Dodrill. J2O development powered by Loops of Fury and Chemical Beats.
Eat your lima beans, Johnny.