1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
|
# Enigma protocol
Enigma uses TCP sockets for communication. Data is sent in each direction as a continuous stream, with packets being
concatenated one after the other.
In this document, data will be represented in C-like pseudocode. The primitive data types will be the same as those
defined by Java's [DataOutputStream](https://docs.oracle.com/javase/7/docs/api/java/io/DataOutputStream.html), i.e. in
big-endian order for multi-byte integers (`short`, `int` and `long`). The one exception is for Strings, which do *not*
use the same modified UTF format as in `DataOutputStream`, I repeat, the normal `writeUTF` method in `DataOutputStream`
(and the corresponding method in `DataInputStream`) should *not* be used. Instead, there is a custom `utf` struct for
Strings, see below.
## Login protocol
```
Client Server
| |
| Login |
| >>>>>>>>>>>>> |
| |
| SyncMappings |
| <<<<<<<<<<<<< |
| |
| ConfirmChange |
| >>>>>>>>>>>>> |
```
1. On connect, the client sends a login packet to the server. This allows the server to test the validity of the client,
as well as allowing the client to declare metadata about itself, such as the username.
1. After validating the login packet, the server sends all its mappings to the client, and the client will apply them.
1. Upon receiving the mappings, the client sends a `ConfirmChange` packet with `sync_id` set to 0, to confirm that it
has received the mappings and is in sync with the server. Once the server receives this packet, the client will be
allowed to modify mappings.
The server will not accept any other packets from the client until this entire exchange has been completed.
## Kicking clients
When the server kicks a client, it may optionally send a `Kick` packet immediately before closing the connection, which
contains the reason why the client was kicked (so the client can display it to the user). This is not required though -
the server may simply terminate the connection.
## Changing mappings
This section uses the example of renaming, but the same pattern applies to all mapping changes.
```
Client A Server Client B
| | |
| RenameC2S | |
| >>>>>>>>> | |
| | |
| | RenameS2C |
| | >>>>>>>>>>>>> |
| | |
| | ConfirmChange |
| | <<<<<<<<<<<<< |
```
1. Client A validates the name and updates the mapping client-side to give the impression there is no latency >:)
1. Client A sends a rename packet to the server, notifying it of the rename.
1. The server assesses the validity of the rename. If it is invalid for whatever reason (e.g. the mapping was locked or
the name contains invalid characters), then the server sends an appropriate packet back to client A to revert the
change, with `sync_id` set to 0. The server will ignore any `ConfirmChange` packets it receives in response to this.
1. If the rename was valid, the server will lock all clients except client A from being able to modify this mapping, and
then send an appropriate packet to all clients except client A notifying them of this rename. The `sync_id` will be a
unique non-zero value identifying this change.
1. Each client responds to this packet by updating their mappings locally to reflect this change, then sending a
`ConfirmChange` packet with the same `sync_id` as the one in the packet they received, to confirm that they have
received the change.
1. When the server receives the `ConfirmChange` packet, and another change to that mapping hasn't occurred since, the
server will unlock that mapping for that client and allow them to make changes again.
## Packets
```c
struct Packet {
unsigned short packet_id;
data[]; // depends on packet_id
}
```
The IDs for client-to-server packets are as follows:
- 0: `Login`
- 1: `ConfirmChange`
- 2: `Rename`
- 3: `RemoveMapping`
- 4: `ChangeDocs`
- 5: `MarkDeobfuscated`
- 6: `Message`
The IDs for server-to-client packets are as follows:
- 0: `Kick`
- 1: `SyncMappings`
- 2: `Rename`
- 3: `RemoveMapping`
- 4: `ChangeDocs`
- 5: `MarkDeobfuscated`
- 6: `Message`
- 7: `UserList`
### The utf struct
```c
struct utf {
unsigned short length;
byte data[length];
}
```
- `length`: The number of bytes in the UTF-8 encoding of the string. Note, this may not be the same as the number of
Unicode characters in the string.
- `data`: A standard UTF-8 encoded byte array representing the string.
### The Entry struct
```c
enum EntryType {
ENTRY_CLASS = 0, ENTRY_FIELD = 1, ENTRY_METHOD = 2, ENTRY_LOCAL_VAR = 3;
}
struct Entry {
unsigned byte type;
boolean has_parent;
if<has_parent> {
Entry parent;
}
utf name;
boolean has_javadoc;
if<has_javadoc> {
utf javadoc;
}
if<type == ENTRY_FIELD || type == ENTRY_METHOD> {
utf descriptor;
}
if<type == ENTRY_LOCAL_VAR> {
unsigned short index;
boolean parameter;
}
}
```
- `type`: The type of entry this is. One of `ENTRY_CLASS`, `ENTRY_FIELD`, `ENTRY_METHOD` or `ENTRY_LOCAL_VAR`.
- `parent`: The parent entry. Only class entries may have no parent. fields, methods and inner classes must have their
containing class as their parent. Local variables have a method as a parent.
- `name`: The class/field/method/variable name.
- `javadoc`: The javadoc of an entry, if present.
- `descriptor`: The field/method descriptor.
- `index`: The index of the local variable in the local variable table.
- `parameter`: Whether the local variable is a parameter.
### The Message struct
```c
enum MessageType {
MESSAGE_CHAT = 0,
MESSAGE_CONNECT = 1,
MESSAGE_DISCONNECT = 2,
MESSAGE_EDIT_DOCS = 3,
MESSAGE_MARK_DEOBF = 4,
MESSAGE_REMOVE_MAPPING = 5,
MESSAGE_RENAME = 6
};
typedef unsigned byte message_type_t;
struct Message {
message_type_t type;
union { // Note that the size of this varies depending on type, it is not constant size
struct {
utf user;
utf message;
} chat;
struct {
utf user;
} connect;
struct {
utf user;
} disconnect;
struct {
utf user;
Entry entry;
} edit_docs;
struct {
utf user;
Entry entry;
} mark_deobf;
struct {
utf user;
Entry entry;
} remove_mapping;
struct {
utf user;
Entry entry;
utf new_name;
} rename;
} data;
};
```
- `type`: The type of message this is. One of `MESSAGE_CHAT`, `MESSAGE_CONNECT`, `MESSAGE_DISCONNECT`,
`MESSAGE_EDIT_DOCS`, `MESSAGE_MARK_DEOBF`, `MESSAGE_REMOVE_MAPPING`, `MESSAGE_RENAME`.
- `chat`: Chat message. Use in case `type` is `MESSAGE_CHAT`
- `connect`: Sent when a user connects. Use in case `type` is `MESSAGE_CONNECT`
- `disconnect`: Sent when a user disconnects. Use in case `type` is `MESSAGE_DISCONNECT`
- `edit_docs`: Sent when a user edits the documentation of an entry. Use in case `type` is `MESSAGE_EDIT_DOCS`
- `mark_deobf`: Sent when a user marks an entry as deobfuscated. Use in case `type` is `MESSAGE_MARK_DEOBF`
- `remove_mapping`: Sent when a user removes a mapping. Use in case `type` is `MESSAGE_REMOVE_MAPPING`
- `rename`: Sent when a user renames an entry. Use in case `type` is `MESSAGE_RENAME`
- `user`: The user that performed the action.
- `message`: The message the user sent.
- `entry`: The entry that was modified.
- `new_name`: The new name for the entry.
### Login (client-to-server)
```c
struct LoginC2SPacket {
unsigned short protocol_version;
byte checksum[20];
unsigned byte password_length;
char password[password_length];
utf username;
}
```
- `protocol_version`: the version of the protocol. If the version does not match on the server, then the client will be
kicked immediately. Currently always equal to 0.
- `checksum`: the SHA-1 hash of the JAR file the client has open. If this does not match the SHA-1 hash of the JAR file
the server has open, the client will be kicked.
- `password`: the password needed to log into the server. Note that each `char` is 2 bytes, as per the Java data type.
If this password is incorrect, the client will be kicked.
- `username`: the username of the user logging in. If the username is not unique, the client will be kicked.
### ConfirmChange (client-to-server)
```c
struct ConfirmChangeC2SPacket {
unsigned short sync_id;
}
```
- `sync_id`: the sync ID to confirm.
### Rename (client-to-server)
```c
struct RenameC2SPacket {
Entry obf_entry;
utf new_name;
boolean refresh_class_tree;
}
```
- `obf_entry`: the obfuscated name and descriptor of the entry to rename.
- `new_name`: what to rename the entry to.
- `refresh_class_tree`: whether the class tree on the sidebar of Enigma needs refreshing as a result of this change.
### RemoveMapping (client-to-server)
```c
struct RemoveMappingC2SPacket {
Entry obf_entry;
}
```
- `obf_entry`: the obfuscated name and descriptor of the entry to remove the mapping for.
### ChangeDocs (client-to-server)
```c
struct ChangeDocsC2SPacket {
Entry obf_entry;
utf new_docs;
}
```
- `obf_entry`: the obfuscated name and descriptor of the entry to change the documentation for.
- `new_docs`: the new documentation for this entry, or an empty string to remove the documentation.
### MarkDeobfuscated (client-to-server)
```c
struct MarkDeobfuscatedC2SPacket {
Entry obf_entry;
}
```
- `obf_entry`: the obfuscated name and descriptor of the entry to mark as deobfuscated.
### Message (client-to-server)
```c
struct MessageC2SPacket {
utf message;
}
```
- `message`: The text message the user sent.
### Kick (server-to-client)
```c
struct KickS2CPacket {
utf reason;
}
```
- `reason`: the reason for the kick, may or may not be a translation key for the client to display to the user.
### SyncMappings (server-to-client)
```c
struct SyncMappingsS2CPacket {
int num_roots;
MappingNode roots[num_roots];
}
struct MappingNode {
NoParentEntry obf_entry;
boolean is_named;
if<is_named> {
utf name;
boolean has_javadoc;
if<has_javadoc> {
utf javadoc;
}
}
unsigned short children_count;
MappingNode children[children_count];
}
typedef { Entry but without the has_parent or parent fields } NoParentEntry;
```
- `roots`: The root mapping nodes, containing all the entries without parents.
- `obf_entry`: The value of a node, containing the obfuscated name and descriptor of the entry.
- `name`: The deobfuscated name of the entry, if it has a mapping.
- `javadoc`: The documentation for the entry, if it is named and has documentation.
- `children`: The children of this node
### Rename (server-to-client)
```c
struct RenameS2CPacket {
unsigned short sync_id;
Entry obf_entry;
utf new_name;
boolean refresh_class_tree;
}
```
- `sync_id`: the sync ID of the change for locking purposes.
- `obf_entry`: the obfuscated name and descriptor of the entry to rename.
- `new_name`: what to rename the entry to.
- `refresh_class_tree`: whether the class tree on the sidebar of Enigma needs refreshing as a result of this change.
### RemoveMapping (server-to-client)
```c
struct RemoveMappingS2CPacket {
unsigned short sync_id;
Entry obf_entry;
}
```
- `sync_id`: the sync ID of the change for locking purposes.
- `obf_entry`: the obfuscated name and descriptor of the entry to remove the mapping for.
### ChangeDocs (server-to-client)
```c
struct ChangeDocsS2CPacket {
unsigned short sync_id;
Entry obf_entry;
utf new_docs;
}
```
- `sync_id`: the sync ID of the change for locking purposes.
- `obf_entry`: the obfuscated name and descriptor of the entry to change the documentation for.
- `new_docs`: the new documentation for this entry, or an empty string to remove the documentation.
### MarkDeobfuscated (server-to-client)
```c
struct MarkDeobfuscatedS2CPacket {
unsigned short sync_id;
Entry obf_entry;
}
```
- `sync_id`: the sync ID of the change for locking purposes.
- `obf_entry`: the obfuscated name and descriptor of the entry to mark as deobfuscated.
### Message (server-to-client)
```c
struct MessageS2CPacket {
Message message;
}
```
### UserList (server-to-client)
```c
struct UserListS2CPacket {
unsigned short len;
utf user[len];
}
```
|