Packet Schema (.packet) Format: Difference between revisions
Created page with "= Packet Schema (.packet) Format = This page explains how to define Fantasy Tennis packets using the schema based <code>.packet</code> format consumed by <code>FTPacketGen</code>. The generator turns these schema files into Java classes (packets and nested structures), including parsing for client packets and auto writing for server packets. == Where .packet files live == <code>FTPacketGen</code> scans a directory tree for files ending in <code>.packet</code> (<code>ser..." |
No edit summary |
||
| (One intermediate revision by the same user not shown) | |||
| Line 74: | Line 74: | ||
int32 gold = 1; | int32 gold = 1; | ||
repeated int32 itemIds = 2; | repeated int32 itemIds = 2; | ||
string nickname = 3 [len=16]; | string nickname = 3 [len = 16]; | ||
</syntaxhighlight> | </syntaxhighlight> | ||
| Line 94: | Line 94: | ||
== Supported base types == | == Supported base types == | ||
These are recognized and mapped by the generator | These are recognized and mapped by the generator. Sizes refer to how values are written to / read from the packet payload. | ||
{| class="wikitable" | {| class="wikitable" | ||
! Schema type !! Java type | ! Schema type !! Java type !! Payload representation | ||
|- | |- | ||
| int / int32 / uint32 || int | | int / int32 / uint32 || int || 4 bytes (little-endian) | ||
|- | |- | ||
| long / int64 / uint64 || long | | long / int64 / uint64 || long || 8 bytes (little-endian) | ||
|- | |- | ||
| short / int16 / uint16 || short | | short / int16 / uint16 || short || 2 bytes (little-endian) | ||
|- | |- | ||
| char || char | | char || char || 2 bytes (UTF-16 / Java <code>char</code>) | ||
|- | |- | ||
| byte / int8 / uint8 || byte | | byte / int8 / uint8 || byte || 1 byte | ||
|- | |- | ||
| bool / boolean || boolean | | bool / boolean || boolean || 1 byte (0 = false, 1 = true) | ||
|- | |- | ||
| float || float | | float || float || 4 bytes (IEEE-754, little-endian) | ||
|- | |- | ||
| double || double | | double || double || 8 bytes (IEEE-754, little-endian) | ||
|- | |- | ||
| string || String | | string || String || Variable length, null-terminated (UTF-16LE by default; UTF-8 if specified) | ||
|- | |- | ||
| date || java.util.Date | | date || java.util.Date || 8 bytes (Windows FILETIME) | ||
|- | |- | ||
| bytes || byte[] | | bytes || byte[] || Raw byte sequence (length defined by schema) | ||
|} | |} | ||
Anything else is treated as a custom/nested message type (must be defined as <code>message <TypeName> { ... }</code> somewhere in the scanned schema set). | Anything else is treated as a custom/nested message type (must be defined as <code>message <TypeName> { ... }</code> somewhere in the scanned schema set). | ||
''Notes:'' | |||
* All numeric values are written using little-endian byte order. | |||
* <code>char</code> corresponds to Java <code>char</code> and is always 2 bytes wide. | |||
* String encoding can be overridden using <code>[encoding=utf8]</code> or <code>[len=...]</code>. | |||
== repeated fields == | == repeated fields == | ||
| Line 145: | Line 150: | ||
<syntaxhighlight lang="text"> | <syntaxhighlight lang="text"> | ||
string name = 1 [len=16]; | string name = 1 [len = 16]; | ||
repeated Player players = 2 [type=short]; | repeated Player players = 2 [type = short]; | ||
</syntaxhighlight> | </syntaxhighlight> | ||
| Line 162: | Line 167: | ||
<syntaxhighlight lang="text"> | <syntaxhighlight lang="text"> | ||
string nickname = 1 [len=16]; | string nickname = 1 [len = 16]; | ||
</syntaxhighlight> | </syntaxhighlight> | ||
| Line 169: | Line 174: | ||
<syntaxhighlight lang="text"> | <syntaxhighlight lang="text"> | ||
bytes payload = 1 [len=64]; | bytes payload = 1 [len = 64]; | ||
</syntaxhighlight> | </syntaxhighlight> | ||
| Line 178: | Line 183: | ||
<syntaxhighlight lang="text"> | <syntaxhighlight lang="text"> | ||
repeated int32 fixedSet = 1 [len=10]; | repeated int32 fixedSet = 1 [len = 10]; | ||
</syntaxhighlight> | |||
=== Dynamic length references (len = msg.<field>) === | |||
In addition to constant values, the <code>len</code> option may reference a previously read field using the generated packet instance via <code>msg.<fieldName></code>. | |||
This allows defining dynamic-length fields whose size depends on earlier values in the packet payload. | |||
==== Rules ==== | |||
* The referenced field '''must appear earlier''' in the schema. | |||
* The referenced field must already be fully read when the dynamic field is processed. | |||
* This is most commonly used with <code>repeated</code> fields. | |||
* The reference uses the generated packet instance (<code>msg</code>), not raw schema names. | |||
==== Example ==== | |||
<syntaxhighlight lang="text"> | |||
message CMSG_GuildCreate (0x200B) { | |||
string name = 1; | |||
string introduction = 2; | |||
bool isPublic = 3; | |||
byte levelRestriction = 4; | |||
byte allowedCharacterTypeCount = 5; | |||
repeated byte allowedCharacterType = 6 [len = msg.allowedCharacterTypeCount]; | |||
} | |||
</syntaxhighlight> | </syntaxhighlight> | ||
In this example: | |||
* <code>allowedCharacterTypeCount</code> is read first | |||
* <code>allowedCharacterType</code> reads exactly that many elements | |||
* No count prefix is read for the repeated field | |||
==== Notes ==== | |||
* Dynamic references are evaluated at runtime during packet parsing. | |||
* If the referenced value is invalid or out of bounds, parsing behavior is undefined. | |||
* Only simple field references are supported (no expressions or calculations). | |||
=== type (size/count prefix control) === | === type (size/count prefix control) === | ||
| Line 188: | Line 226: | ||
<syntaxhighlight lang="text"> | <syntaxhighlight lang="text"> | ||
repeated Player players = 1 [type=short]; | repeated Player players = 1 [type = short]; | ||
</syntaxhighlight> | </syntaxhighlight> | ||
| Line 197: | Line 235: | ||
<syntaxhighlight lang="text"> | <syntaxhighlight lang="text"> | ||
int32 flags = 1 [type=byte]; | int32 flags = 1 [type = byte]; | ||
</syntaxhighlight> | </syntaxhighlight> | ||
| Line 203: | Line 241: | ||
For writing strings: | For writing strings: | ||
* Default writing uses UTF-16LE + <code>0x0000</code> terminator. | * Default writing uses UTF-16LE + <code>0x0000</code> terminator. | ||
* With <code>[encoding=utf8]</code> it writes UTF-8 + <code>0x00</code> terminator. | * With <code>[encoding = utf8]</code> it writes UTF-8 + <code>0x00</code> terminator. | ||
<syntaxhighlight lang="text"> | <syntaxhighlight lang="text"> | ||
| Line 211: | Line 249: | ||
Reading strings: | Reading strings: | ||
* Variable-length <code>string</code> is auto-detected by the generated reader (UTF-16LE vs ASCII/UTF-8 style). | * Variable-length <code>string</code> is auto-detected by the generated reader (UTF-16LE vs ASCII/UTF-8 style). | ||
* Fixed-length strings (<code>[len=...]</code>) are read as UTF-8 bytes. | * Fixed-length strings (<code>[len = ...]</code>) are read as UTF-8 bytes. | ||
== Client vs Server packet behavior == | == Client vs Server packet behavior == | ||
| Line 287: | Line 325: | ||
== Practical tips / gotchas == | == Practical tips / gotchas == | ||
* Keep schema field order exactly matching the on wire protocol order. The generator reads/writes strictly in schema order. | * Keep schema field order exactly matching the on wire protocol order. The generator reads/writes strictly in schema order. | ||
* Use <code>[type=short]</code> (or another type) on <code>repeated</code> if the protocol’s list length prefix is not 1 byte. | * Use <code>[type = short]</code> (or another type) on <code>repeated</code> if the protocol’s list length prefix is not 1 byte. | ||
* Use <code>bytes</code> for raw data blocks; don’t try <code>repeated byte</code> unless you specifically want a length-prefixed list of bytes. | * Use <code>bytes</code> for raw data blocks; don’t try <code>repeated byte</code> unless you specifically want a length-prefixed list of bytes. | ||
* Avoid <code>data</code> and <code>metaData</code> as field names. | * Avoid <code>data</code> and <code>metaData</code> as field names. | ||
Latest revision as of 15:41, 18 December 2025
Packet Schema (.packet) Format
This page explains how to define Fantasy Tennis packets using the schema based .packet format consumed by FTPacketGen. The generator turns these schema files into Java classes (packets and nested structures), including parsing for client packets and auto writing for server packets.
Where .packet files live
FTPacketGen scans a directory tree for files ending in .packet (server-core/src/main/packets/). The folder structure becomes the Java package:
com.jftse.server.core.shared.packets.<relative folders...>
Example:
server-core/src/main/packets/
auth/
CMSG_Login.packet
Generates classes in:
com.jftse.server.core.shared.packets.auth
Message blocks
A schema file can contain one or multiple message blocks.
Packet message (has packet id)
Use this to define an actual network packet (generates a class that implements IPacket):
message CMSG_Login (0xFA1) {
string username = 1;
string password = 2 [encoding = utf8];
int32 version = 3;
byte unk0 = 4;
string hwid = 5 [encoding = utf8];
}
- The
(0x....)makes it a packet class and setsPACKET_ID. - If the message name starts with
CMSG(case-insensitive), it is treated as a client packet (read/parse from bytes). - Otherwise it is treated as a server packet (write/build to bytes).
Struct / nested message (no packet id)
Use this to define a reusable structure you can reference as a field type in other messages:
message Account {
int32 id = 1;
int32 id2 = 2;
byte tutorialCount = 3;
int32 lastPlayedPlayerId = 4;
boolean gameMaster = 5;
}
Then use it inside a packet:
message SMSG_PlayerList (0x1005) {
Account account = 1;
repeated Player players = 2;
}
Field syntax
Each field line follows this shape:
[repeated] <type> <name> = <number> [<options>];
Examples:
int32 gold = 1;
repeated int32 itemIds = 2;
string nickname = 3 [len = 16];
Field numbering rules (IMPORTANT)
Field numbers are validated strictly:
- Must start at
1 - Must be sequential with no gaps (
1,2,3,...) - Must not contain duplicates
If you skip a number (or duplicate one), generation fails.
Reserved field names
Do not use these field names:
datametaData
They are reserved internally by the generated packet classes.
Supported base types
These are recognized and mapped by the generator. Sizes refer to how values are written to / read from the packet payload.
| Schema type | Java type | Payload representation |
|---|---|---|
| int / int32 / uint32 | int | 4 bytes (little-endian) |
| long / int64 / uint64 | long | 8 bytes (little-endian) |
| short / int16 / uint16 | short | 2 bytes (little-endian) |
| char | char | 2 bytes (UTF-16 / Java char)
|
| byte / int8 / uint8 | byte | 1 byte |
| bool / boolean | boolean | 1 byte (0 = false, 1 = true) |
| float | float | 4 bytes (IEEE-754, little-endian) |
| double | double | 8 bytes (IEEE-754, little-endian) |
| string | String | Variable length, null-terminated (UTF-16LE by default; UTF-8 if specified) |
| date | java.util.Date | 8 bytes (Windows FILETIME) |
| bytes | byte[] | Raw byte sequence (length defined by schema) |
Anything else is treated as a custom/nested message type (must be defined as message <TypeName> { ... } somewhere in the scanned schema set).
Notes:
- All numeric values are written using little-endian byte order.
charcorresponds to Javacharand is always 2 bytes wide.- String encoding can be overridden using
[encoding=utf8]or[len=...].
repeated fields
Use repeated for lists/arrays (except bytes, which is already a raw byte array).
repeated int32 values = 1;
repeated Player players = 2;
How repeated is encoded
By default, repeated fields are encoded as:
- a count prefix (1 byte by default), then
- that many elements
On the read side, the count prefix is always read as a single byte unless you force fixed length via [len=...].
On the write side, you can control the numeric type used for the count prefix via [type=...].
Options
Options go in square brackets:
string name = 1 [len = 16];
repeated Player players = 2 [type = short];
Options are parsed as:
key = value
separated by commas.
len (strings, bytes, repeated)
len changes how a field is read/written.
string + len
Reads a fixed-length string (UTF-8) of exactly len bytes:
string nickname = 1 [len = 16];
bytes + len
Reads exactly len bytes:
bytes payload = 1 [len = 64];
If bytes has no len, it reads “the rest of the packet payload”.
repeated + len
Reads a fixed number of elements (no count prefix is consumed on read):
repeated int32 fixedSet = 1 [len = 10];
Dynamic length references (len = msg.<field>)
In addition to constant values, the len option may reference a previously read field using the generated packet instance via msg.<fieldName>.
This allows defining dynamic-length fields whose size depends on earlier values in the packet payload.
Rules
- The referenced field must appear earlier in the schema.
- The referenced field must already be fully read when the dynamic field is processed.
- This is most commonly used with
repeatedfields. - The reference uses the generated packet instance (
msg), not raw schema names.
Example
message CMSG_GuildCreate (0x200B) {
string name = 1;
string introduction = 2;
bool isPublic = 3;
byte levelRestriction = 4;
byte allowedCharacterTypeCount = 5;
repeated byte allowedCharacterType = 6 [len = msg.allowedCharacterTypeCount];
}
In this example:
allowedCharacterTypeCountis read firstallowedCharacterTypereads exactly that many elements- No count prefix is read for the repeated field
Notes
- Dynamic references are evaluated at runtime during packet parsing.
- If the referenced value is invalid or out of bounds, parsing behavior is undefined.
- Only simple field references are supported (no expressions or calculations).
type (size/count prefix control)
type controls the integer type used when writing sizes/counts (and can also force a cast for some primitives).
repeated + type
Controls the numeric type used to write the list size:
repeated Player players = 1 [type = short];
This writes (short) players.size() before the elements.
primitive field + type
If you set type=... on a supported primitive field, the generator will cast when writing. This is useful when the schema expresses a value as int32 but the protocol stores it smaller:
int32 flags = 1 [type = byte];
encoding=utf8 (strings)
For writing strings:
- Default writing uses UTF-16LE +
0x0000terminator. - With
[encoding = utf8]it writes UTF-8 +0x00terminator.
string password = 1 [encoding = utf8];
Reading strings:
- Variable-length
stringis auto-detected by the generated reader (UTF-16LE vs ASCII/UTF-8 style). - Fixed-length strings (
[len = ...]) are read as UTF-8 bytes.
Client vs Server packet behavior
The generator treats packets differently depending on the message name.
Client packets (CMSG*)
If the message name starts with CMSG / cmsg:
- The generated class registers itself with
PacketRegistryin a static block. fromBytes(...)parses all defined fields in schema order.- A generic
read(Class<T>)API exists plus type-specific read helpers.
These are also included in the generated PacketAutoRegister list.
Server packets (everything else)
For non-CMSG packet messages:
- The builder’s
build()writes fields into the internal byte buffer automatically (in schema order). toBytes()returns the full header + payload.
Nested/composite messages
Any message without a packet id becomes a plain Java class with fields, getters/setters, and a builder.
When you reference that message name as a field type inside a packet, the generator writes/reads its subfields in order.
Full example: Player list packet
This shows nested messages and a repeated composite type.
message Account {
int32 id = 1;
int32 id2 = 2;
byte tutorialCount = 3;
int32 lastPlayedPlayerId = 4;
boolean gameMaster = 5;
}
message ClothEquipment {
int32 hair = 1;
int32 face = 2;
int32 dress = 3;
int32 pants = 4;
int32 socks = 5;
int32 shoes = 6;
int32 gloves = 7;
int32 racket = 8;
int32 glasses = 9;
int32 bag = 10;
int32 hat = 11;
int32 dye = 12;
}
message Player {
int32 id = 1;
string name = 2;
byte level = 3;
boolean created = 4;
boolean canDelete = 5;
int32 gold = 6;
byte playerType = 7;
byte str = 8;
byte sta = 9;
byte dex = 10;
byte wil = 11;
byte statPoints = 12;
boolean oldRenameAllowed = 13;
boolean renameAllowed = 14;
ClothEquipment clothEquipment = 15;
}
message SMSG_PlayerList (0x1005) {
Account account = 1;
repeated Player players = 2;
}
Practical tips / gotchas
- Keep schema field order exactly matching the on wire protocol order. The generator reads/writes strictly in schema order.
- Use
[type = short](or another type) onrepeatedif the protocol’s list length prefix is not 1 byte. - Use
bytesfor raw data blocks; don’t tryrepeated byteunless you specifically want a length-prefixed list of bytes. - Avoid
dataandmetaDataas field names. - Don’t skip field numbers, generation will fail.