summaryrefslogtreecommitdiff
path: root/enigma-server/src/main/java
diff options
context:
space:
mode:
authorGravatar Runemoro2020-06-03 13:39:42 -0400
committerGravatar GitHub2020-06-03 18:39:42 +0100
commit0f47403d0220757fed189b76e2071e25b1025cb8 (patch)
tree879bf72c4476f0a5e0d82da99d7ff2b2276bcaca /enigma-server/src/main/java
parentFix search dialog hanging for a short time sometimes (#250) (diff)
downloadenigma-fork-0f47403d0220757fed189b76e2071e25b1025cb8.tar.gz
enigma-fork-0f47403d0220757fed189b76e2071e25b1025cb8.tar.xz
enigma-fork-0f47403d0220757fed189b76e2071e25b1025cb8.zip
Split GUI code to separate module (#242)
* Split into modules * Post merge compile fixes Co-authored-by: modmuss50 <modmuss50@gmail.com>
Diffstat (limited to 'enigma-server/src/main/java')
-rw-r--r--enigma-server/src/main/java/cuchaz/enigma/network/ClientPacketHandler.java29
-rw-r--r--enigma-server/src/main/java/cuchaz/enigma/network/DedicatedEnigmaServer.java200
-rw-r--r--enigma-server/src/main/java/cuchaz/enigma/network/EnigmaClient.java78
-rw-r--r--enigma-server/src/main/java/cuchaz/enigma/network/EnigmaServer.java290
-rw-r--r--enigma-server/src/main/java/cuchaz/enigma/network/IntegratedEnigmaServer.java16
-rw-r--r--enigma-server/src/main/java/cuchaz/enigma/network/Message.java393
-rw-r--r--enigma-server/src/main/java/cuchaz/enigma/network/ServerPacketHandler.java22
-rw-r--r--enigma-server/src/main/java/cuchaz/enigma/network/packet/ChangeDocsC2SPacket.java59
-rw-r--r--enigma-server/src/main/java/cuchaz/enigma/network/packet/ChangeDocsS2CPacket.java44
-rw-r--r--enigma-server/src/main/java/cuchaz/enigma/network/packet/ConfirmChangeC2SPacket.java33
-rw-r--r--enigma-server/src/main/java/cuchaz/enigma/network/packet/KickS2CPacket.java33
-rw-r--r--enigma-server/src/main/java/cuchaz/enigma/network/packet/LoginC2SPacket.java75
-rw-r--r--enigma-server/src/main/java/cuchaz/enigma/network/packet/MarkDeobfuscatedC2SPacket.java48
-rw-r--r--enigma-server/src/main/java/cuchaz/enigma/network/packet/MarkDeobfuscatedS2CPacket.java40
-rw-r--r--enigma-server/src/main/java/cuchaz/enigma/network/packet/MessageC2SPacket.java39
-rw-r--r--enigma-server/src/main/java/cuchaz/enigma/network/packet/MessageS2CPacket.java36
-rw-r--r--enigma-server/src/main/java/cuchaz/enigma/network/packet/Packet.java15
-rw-r--r--enigma-server/src/main/java/cuchaz/enigma/network/packet/PacketHelper.java135
-rw-r--r--enigma-server/src/main/java/cuchaz/enigma/network/packet/PacketRegistry.java64
-rw-r--r--enigma-server/src/main/java/cuchaz/enigma/network/packet/RemoveMappingC2SPacket.java55
-rw-r--r--enigma-server/src/main/java/cuchaz/enigma/network/packet/RemoveMappingS2CPacket.java40
-rw-r--r--enigma-server/src/main/java/cuchaz/enigma/network/packet/RenameC2SPacket.java64
-rw-r--r--enigma-server/src/main/java/cuchaz/enigma/network/packet/RenameS2CPacket.java48
-rw-r--r--enigma-server/src/main/java/cuchaz/enigma/network/packet/SyncMappingsS2CPacket.java88
-rw-r--r--enigma-server/src/main/java/cuchaz/enigma/network/packet/UserListS2CPacket.java44
25 files changed, 1988 insertions, 0 deletions
diff --git a/enigma-server/src/main/java/cuchaz/enigma/network/ClientPacketHandler.java b/enigma-server/src/main/java/cuchaz/enigma/network/ClientPacketHandler.java
new file mode 100644
index 0000000..720744b
--- /dev/null
+++ b/enigma-server/src/main/java/cuchaz/enigma/network/ClientPacketHandler.java
@@ -0,0 +1,29 @@
1package cuchaz.enigma.network;
2
3import cuchaz.enigma.analysis.EntryReference;
4import cuchaz.enigma.translation.mapping.EntryMapping;
5import cuchaz.enigma.translation.mapping.tree.EntryTree;
6import cuchaz.enigma.network.packet.Packet;
7import cuchaz.enigma.translation.representation.entry.Entry;
8
9import java.util.List;
10
11public interface ClientPacketHandler {
12 void openMappings(EntryTree<EntryMapping> mappings);
13
14 void rename(EntryReference<Entry<?>, Entry<?>> reference, String newName, boolean refreshClassTree, boolean jumpToReference);
15
16 void removeMapping(EntryReference<Entry<?>, Entry<?>> reference, boolean jumpToReference);
17
18 void changeDocs(EntryReference<Entry<?>, Entry<?>> reference, String updatedDocs, boolean jumpToReference);
19
20 void markAsDeobfuscated(EntryReference<Entry<?>, Entry<?>> reference, boolean jumpToReference);
21
22 void disconnectIfConnected(String reason);
23
24 void sendPacket(Packet<ServerPacketHandler> packet);
25
26 void addMessage(Message message);
27
28 void updateUserList(List<String> users);
29}
diff --git a/enigma-server/src/main/java/cuchaz/enigma/network/DedicatedEnigmaServer.java b/enigma-server/src/main/java/cuchaz/enigma/network/DedicatedEnigmaServer.java
new file mode 100644
index 0000000..924302f
--- /dev/null
+++ b/enigma-server/src/main/java/cuchaz/enigma/network/DedicatedEnigmaServer.java
@@ -0,0 +1,200 @@
1package cuchaz.enigma.network;
2
3import com.google.common.io.MoreFiles;
4import cuchaz.enigma.*;
5import cuchaz.enigma.translation.mapping.serde.MappingParseException;
6import cuchaz.enigma.translation.mapping.EntryRemapper;
7import cuchaz.enigma.translation.mapping.serde.MappingFormat;
8import cuchaz.enigma.utils.Utils;
9import joptsimple.OptionParser;
10import joptsimple.OptionSet;
11import joptsimple.OptionSpec;
12import joptsimple.ValueConverter;
13
14import java.io.IOException;
15import java.io.PrintWriter;
16import java.nio.file.Files;
17import java.nio.file.Path;
18import java.nio.file.Paths;
19import java.util.concurrent.BlockingQueue;
20import java.util.concurrent.Executors;
21import java.util.concurrent.LinkedBlockingDeque;
22import java.util.concurrent.TimeUnit;
23
24public class DedicatedEnigmaServer extends EnigmaServer {
25
26 private final EnigmaProfile profile;
27 private final MappingFormat mappingFormat;
28 private final Path mappingsFile;
29 private final PrintWriter log;
30 private BlockingQueue<Runnable> tasks = new LinkedBlockingDeque<>();
31
32 public DedicatedEnigmaServer(
33 byte[] jarChecksum,
34 char[] password,
35 EnigmaProfile profile,
36 MappingFormat mappingFormat,
37 Path mappingsFile,
38 PrintWriter log,
39 EntryRemapper mappings,
40 int port
41 ) {
42 super(jarChecksum, password, mappings, port);
43 this.profile = profile;
44 this.mappingFormat = mappingFormat;
45 this.mappingsFile = mappingsFile;
46 this.log = log;
47 }
48
49 @Override
50 protected void runOnThread(Runnable task) {
51 tasks.add(task);
52 }
53
54 @Override
55 public void log(String message) {
56 super.log(message);
57 log.println(message);
58 }
59
60 public static void main(String[] args) {
61 OptionParser parser = new OptionParser();
62
63 OptionSpec<Path> jarOpt = parser.accepts("jar", "Jar file to open at startup")
64 .withRequiredArg()
65 .required()
66 .withValuesConvertedBy(PathConverter.INSTANCE);
67
68 OptionSpec<Path> mappingsOpt = parser.accepts("mappings", "Mappings file to open at startup")
69 .withRequiredArg()
70 .required()
71 .withValuesConvertedBy(PathConverter.INSTANCE);
72
73 OptionSpec<Path> profileOpt = parser.accepts("profile", "Profile json to apply at startup")
74 .withRequiredArg()
75 .withValuesConvertedBy(PathConverter.INSTANCE);
76
77 OptionSpec<Integer> portOpt = parser.accepts("port", "Port to run the server on")
78 .withOptionalArg()
79 .ofType(Integer.class)
80 .defaultsTo(EnigmaServer.DEFAULT_PORT);
81
82 OptionSpec<String> passwordOpt = parser.accepts("password", "The password to join the server")
83 .withRequiredArg()
84 .defaultsTo("");
85
86 OptionSpec<Path> logFileOpt = parser.accepts("log", "The log file to write to")
87 .withRequiredArg()
88 .withValuesConvertedBy(PathConverter.INSTANCE)
89 .defaultsTo(Paths.get("log.txt"));
90
91 OptionSet parsedArgs = parser.parse(args);
92 Path jar = parsedArgs.valueOf(jarOpt);
93 Path mappingsFile = parsedArgs.valueOf(mappingsOpt);
94 Path profileFile = parsedArgs.valueOf(profileOpt);
95 int port = parsedArgs.valueOf(portOpt);
96 char[] password = parsedArgs.valueOf(passwordOpt).toCharArray();
97 if (password.length > EnigmaServer.MAX_PASSWORD_LENGTH) {
98 System.err.println("Password too long, must be at most " + EnigmaServer.MAX_PASSWORD_LENGTH + " characters");
99 System.exit(1);
100 }
101 Path logFile = parsedArgs.valueOf(logFileOpt);
102
103 System.out.println("Starting Enigma server");
104 DedicatedEnigmaServer server;
105 try {
106 byte[] checksum = Utils.zipSha1(parsedArgs.valueOf(jarOpt));
107
108 EnigmaProfile profile = EnigmaProfile.read(profileFile);
109 Enigma enigma = Enigma.builder().setProfile(profile).build();
110 System.out.println("Indexing Jar...");
111 EnigmaProject project = enigma.openJar(jar, ProgressListener.none());
112
113 MappingFormat mappingFormat = MappingFormat.ENIGMA_DIRECTORY;
114 EntryRemapper mappings;
115 if (!Files.exists(mappingsFile)) {
116 mappings = EntryRemapper.empty(project.getJarIndex());
117 } else {
118 System.out.println("Reading mappings...");
119 if (Files.isDirectory(mappingsFile)) {
120 mappingFormat = MappingFormat.ENIGMA_DIRECTORY;
121 } else if ("zip".equalsIgnoreCase(MoreFiles.getFileExtension(mappingsFile))) {
122 mappingFormat = MappingFormat.ENIGMA_ZIP;
123 } else {
124 mappingFormat = MappingFormat.ENIGMA_FILE;
125 }
126 mappings = EntryRemapper.mapped(project.getJarIndex(), mappingFormat.read(mappingsFile, ProgressListener.none(), profile.getMappingSaveParameters()));
127 }
128
129 PrintWriter log = new PrintWriter(Files.newBufferedWriter(logFile));
130
131 server = new DedicatedEnigmaServer(checksum, password, profile, mappingFormat, mappingsFile, log, mappings, port);
132 server.start();
133 System.out.println("Server started");
134 } catch (IOException | MappingParseException e) {
135 System.err.println("Error starting server!");
136 e.printStackTrace();
137 System.exit(1);
138 return;
139 }
140
141 // noinspection RedundantSuppression
142 // noinspection Convert2MethodRef - javac 8 bug
143 Executors.newScheduledThreadPool(1).scheduleAtFixedRate(() -> server.runOnThread(() -> server.saveMappings()), 0, 1, TimeUnit.MINUTES);
144 Runtime.getRuntime().addShutdownHook(new Thread(server::saveMappings));
145
146 while (true) {
147 try {
148 server.tasks.take().run();
149 } catch (InterruptedException e) {
150 break;
151 }
152 }
153 }
154
155 @Override
156 public synchronized void stop() {
157 super.stop();
158 System.exit(0);
159 }
160
161 private void saveMappings() {
162 mappingFormat.write(getMappings().getObfToDeobf(), getMappings().takeMappingDelta(), mappingsFile, ProgressListener.none(), profile.getMappingSaveParameters());
163 log.flush();
164 }
165
166 public static class PathConverter implements ValueConverter<Path> {
167 public static final ValueConverter<Path> INSTANCE = new PathConverter();
168
169 PathConverter() {
170 }
171
172 @Override
173 public Path convert(String path) {
174 // expand ~ to the home dir
175 if (path.startsWith("~")) {
176 // get the home dir
177 Path dirHome = Paths.get(System.getProperty("user.home"));
178
179 // is the path just ~/ or is it ~user/ ?
180 if (path.startsWith("~/")) {
181 return dirHome.resolve(path.substring(2));
182 } else {
183 return dirHome.getParent().resolve(path.substring(1));
184 }
185 }
186
187 return Paths.get(path);
188 }
189
190 @Override
191 public Class<? extends Path> valueType() {
192 return Path.class;
193 }
194
195 @Override
196 public String valuePattern() {
197 return "path";
198 }
199 }
200}
diff --git a/enigma-server/src/main/java/cuchaz/enigma/network/EnigmaClient.java b/enigma-server/src/main/java/cuchaz/enigma/network/EnigmaClient.java
new file mode 100644
index 0000000..71bd011
--- /dev/null
+++ b/enigma-server/src/main/java/cuchaz/enigma/network/EnigmaClient.java
@@ -0,0 +1,78 @@
1package cuchaz.enigma.network;
2
3import cuchaz.enigma.network.packet.Packet;
4import cuchaz.enigma.network.packet.PacketRegistry;
5
6import javax.swing.*;
7import java.io.*;
8import java.net.Socket;
9import java.net.SocketException;
10
11public class EnigmaClient {
12
13 private final ClientPacketHandler controller;
14
15 private final String ip;
16 private final int port;
17 private Socket socket;
18 private DataOutput output;
19
20 public EnigmaClient(ClientPacketHandler controller, String ip, int port) {
21 this.controller = controller;
22 this.ip = ip;
23 this.port = port;
24 }
25
26 public void connect() throws IOException {
27 socket = new Socket(ip, port);
28 output = new DataOutputStream(socket.getOutputStream());
29 Thread thread = new Thread(() -> {
30 try {
31 DataInput input = new DataInputStream(socket.getInputStream());
32 while (true) {
33 int packetId;
34 try {
35 packetId = input.readUnsignedByte();
36 } catch (EOFException | SocketException e) {
37 break;
38 }
39 Packet<ClientPacketHandler> packet = PacketRegistry.createS2CPacket(packetId);
40 if (packet == null) {
41 throw new IOException("Received invalid packet id " + packetId);
42 }
43 packet.read(input);
44 SwingUtilities.invokeLater(() -> packet.handle(controller));
45 }
46 } catch (IOException e) {
47 controller.disconnectIfConnected(e.toString());
48 return;
49 }
50 controller.disconnectIfConnected("Disconnected");
51 });
52 thread.setName("Client I/O thread");
53 thread.setDaemon(true);
54 thread.start();
55 }
56
57 public synchronized void disconnect() {
58 if (socket != null && !socket.isClosed()) {
59 try {
60 socket.close();
61 } catch (IOException e1) {
62 System.err.println("Failed to close socket");
63 e1.printStackTrace();
64 }
65 }
66 }
67
68
69 public void sendPacket(Packet<ServerPacketHandler> packet) {
70 try {
71 output.writeByte(PacketRegistry.getC2SId(packet));
72 packet.write(output);
73 } catch (IOException e) {
74 controller.disconnectIfConnected(e.toString());
75 }
76 }
77
78}
diff --git a/enigma-server/src/main/java/cuchaz/enigma/network/EnigmaServer.java b/enigma-server/src/main/java/cuchaz/enigma/network/EnigmaServer.java
new file mode 100644
index 0000000..6027a6b
--- /dev/null
+++ b/enigma-server/src/main/java/cuchaz/enigma/network/EnigmaServer.java
@@ -0,0 +1,290 @@
1package cuchaz.enigma.network;
2
3import cuchaz.enigma.network.packet.KickS2CPacket;
4import cuchaz.enigma.network.packet.MessageS2CPacket;
5import cuchaz.enigma.network.packet.Packet;
6import cuchaz.enigma.network.packet.PacketRegistry;
7import cuchaz.enigma.network.packet.RemoveMappingS2CPacket;
8import cuchaz.enigma.network.packet.RenameS2CPacket;
9import cuchaz.enigma.network.packet.UserListS2CPacket;
10import cuchaz.enigma.translation.mapping.EntryMapping;
11import cuchaz.enigma.translation.mapping.EntryRemapper;
12import cuchaz.enigma.translation.representation.entry.Entry;
13
14import java.io.DataInput;
15import java.io.DataInputStream;
16import java.io.DataOutput;
17import java.io.DataOutputStream;
18import java.io.EOFException;
19import java.io.IOException;
20import java.net.ServerSocket;
21import java.net.Socket;
22import java.net.SocketException;
23import java.util.ArrayList;
24import java.util.Collections;
25import java.util.HashMap;
26import java.util.HashSet;
27import java.util.List;
28import java.util.Map;
29import java.util.Set;
30import java.util.concurrent.CopyOnWriteArrayList;
31
32public abstract class EnigmaServer {
33
34 // https://discordapp.com/channels/507304429255393322/566418023372816394/700292322918793347
35 public static final int DEFAULT_PORT = 34712;
36 public static final int PROTOCOL_VERSION = 0;
37 public static final String OWNER_USERNAME = "Owner";
38 public static final int CHECKSUM_SIZE = 20;
39 public static final int MAX_PASSWORD_LENGTH = 255; // length is written as a byte in the login packet
40
41 private final int port;
42 private ServerSocket socket;
43 private List<Socket> clients = new CopyOnWriteArrayList<>();
44 private Map<Socket, String> usernames = new HashMap<>();
45 private Set<Socket> unapprovedClients = new HashSet<>();
46
47 private final byte[] jarChecksum;
48 private final char[] password;
49
50 public static final int DUMMY_SYNC_ID = 0;
51 private final EntryRemapper mappings;
52 private Map<Entry<?>, Integer> syncIds = new HashMap<>();
53 private Map<Integer, Entry<?>> inverseSyncIds = new HashMap<>();
54 private Map<Integer, Set<Socket>> clientsNeedingConfirmation = new HashMap<>();
55 private int nextSyncId = DUMMY_SYNC_ID + 1;
56
57 private static int nextIoId = 0;
58
59 public EnigmaServer(byte[] jarChecksum, char[] password, EntryRemapper mappings, int port) {
60 this.jarChecksum = jarChecksum;
61 this.password = password;
62 this.mappings = mappings;
63 this.port = port;
64 }
65
66 public void start() throws IOException {
67 socket = new ServerSocket(port);
68 log("Server started on " + socket.getInetAddress() + ":" + port);
69 Thread thread = new Thread(() -> {
70 try {
71 while (!socket.isClosed()) {
72 acceptClient();
73 }
74 } catch (SocketException e) {
75 System.out.println("Server closed");
76 } catch (IOException e) {
77 e.printStackTrace();
78 }
79 });
80 thread.setName("Server client listener");
81 thread.setDaemon(true);
82 thread.start();
83 }
84
85 private void acceptClient() throws IOException {
86 Socket client = socket.accept();
87 clients.add(client);
88 Thread thread = new Thread(() -> {
89 try {
90 DataInput input = new DataInputStream(client.getInputStream());
91 while (true) {
92 int packetId;
93 try {
94 packetId = input.readUnsignedByte();
95 } catch (EOFException | SocketException e) {
96 break;
97 }
98 Packet<ServerPacketHandler> packet = PacketRegistry.createC2SPacket(packetId);
99 if (packet == null) {
100 throw new IOException("Received invalid packet id " + packetId);
101 }
102 packet.read(input);
103 runOnThread(() -> packet.handle(new ServerPacketHandler(client, this)));
104 }
105 } catch (IOException e) {
106 kick(client, e.toString());
107 e.printStackTrace();
108 return;
109 }
110 kick(client, "disconnect.disconnected");
111 });
112 thread.setName("Server I/O thread #" + (nextIoId++));
113 thread.setDaemon(true);
114 thread.start();
115 }
116
117 public void stop() {
118 runOnThread(() -> {
119 if (socket != null && !socket.isClosed()) {
120 for (Socket client : clients) {
121 kick(client, "disconnect.server_closed");
122 }
123 try {
124 socket.close();
125 } catch (IOException e) {
126 System.err.println("Failed to close server socket");
127 e.printStackTrace();
128 }
129 }
130 });
131 }
132
133 public void kick(Socket client, String reason) {
134 if (!clients.remove(client)) return;
135
136 sendPacket(client, new KickS2CPacket(reason));
137
138 clientsNeedingConfirmation.values().removeIf(list -> {
139 list.remove(client);
140 return list.isEmpty();
141 });
142 String username = usernames.remove(client);
143 try {
144 client.close();
145 } catch (IOException e) {
146 System.err.println("Failed to close server client socket");
147 e.printStackTrace();
148 }
149
150 if (username != null) {
151 System.out.println("Kicked " + username + " because " + reason);
152 sendMessage(Message.disconnect(username));
153 }
154 sendUsernamePacket();
155 }
156
157 public boolean isUsernameTaken(String username) {
158 return usernames.containsValue(username);
159 }
160
161 public void setUsername(Socket client, String username) {
162 usernames.put(client, username);
163 sendUsernamePacket();
164 }
165
166 private void sendUsernamePacket() {
167 List<String> usernames = new ArrayList<>(this.usernames.values());
168 Collections.sort(usernames);
169 sendToAll(new UserListS2CPacket(usernames));
170 }
171
172 public String getUsername(Socket client) {
173 return usernames.get(client);
174 }
175
176 public void sendPacket(Socket client, Packet<ClientPacketHandler> packet) {
177 if (!client.isClosed()) {
178 int packetId = PacketRegistry.getS2CId(packet);
179 try {
180 DataOutput output = new DataOutputStream(client.getOutputStream());
181 output.writeByte(packetId);
182 packet.write(output);
183 } catch (IOException e) {
184 if (!(packet instanceof KickS2CPacket)) {
185 kick(client, e.toString());
186 e.printStackTrace();
187 }
188 }
189 }
190 }
191
192 public void sendToAll(Packet<ClientPacketHandler> packet) {
193 for (Socket client : clients) {
194 sendPacket(client, packet);
195 }
196 }
197
198 public void sendToAllExcept(Socket excluded, Packet<ClientPacketHandler> packet) {
199 for (Socket client : clients) {
200 if (client != excluded) {
201 sendPacket(client, packet);
202 }
203 }
204 }
205
206 public boolean canModifyEntry(Socket client, Entry<?> entry) {
207 if (unapprovedClients.contains(client)) {
208 return false;
209 }
210
211 Integer syncId = syncIds.get(entry);
212 if (syncId == null) {
213 return true;
214 }
215 Set<Socket> clients = clientsNeedingConfirmation.get(syncId);
216 return clients == null || !clients.contains(client);
217 }
218
219 public int lockEntry(Socket exception, Entry<?> entry) {
220 int syncId = nextSyncId;
221 nextSyncId++;
222 // sync id is sent as an unsigned short, can't have more than 65536
223 if (nextSyncId == 65536) {
224 nextSyncId = DUMMY_SYNC_ID + 1;
225 }
226 Integer oldSyncId = syncIds.get(entry);
227 if (oldSyncId != null) {
228 clientsNeedingConfirmation.remove(oldSyncId);
229 }
230 syncIds.put(entry, syncId);
231 inverseSyncIds.put(syncId, entry);
232 Set<Socket> clients = new HashSet<>(this.clients);
233 clients.remove(exception);
234 clientsNeedingConfirmation.put(syncId, clients);
235 return syncId;
236 }
237
238 public void confirmChange(Socket client, int syncId) {
239 if (usernames.containsKey(client)) {
240 unapprovedClients.remove(client);
241 }
242
243 Set<Socket> clients = clientsNeedingConfirmation.get(syncId);
244 if (clients != null) {
245 clients.remove(client);
246 if (clients.isEmpty()) {
247 clientsNeedingConfirmation.remove(syncId);
248 syncIds.remove(inverseSyncIds.remove(syncId));
249 }
250 }
251 }
252
253 public void sendCorrectMapping(Socket client, Entry<?> entry, boolean refreshClassTree) {
254 EntryMapping oldMapping = mappings.getDeobfMapping(entry);
255 String oldName = oldMapping == null ? null : oldMapping.getTargetName();
256 if (oldName == null) {
257 sendPacket(client, new RemoveMappingS2CPacket(DUMMY_SYNC_ID, entry));
258 } else {
259 sendPacket(client, new RenameS2CPacket(0, entry, oldName, refreshClassTree));
260 }
261 }
262
263 protected abstract void runOnThread(Runnable task);
264
265 public void log(String message) {
266 System.out.println(message);
267 }
268
269 protected boolean isRunning() {
270 return !socket.isClosed();
271 }
272
273 public byte[] getJarChecksum() {
274 return jarChecksum;
275 }
276
277 public char[] getPassword() {
278 return password;
279 }
280
281 public EntryRemapper getMappings() {
282 return mappings;
283 }
284
285 public void sendMessage(Message message) {
286 log(String.format("[MSG] %s", message.translate()));
287 sendToAll(new MessageS2CPacket(message));
288 }
289
290}
diff --git a/enigma-server/src/main/java/cuchaz/enigma/network/IntegratedEnigmaServer.java b/enigma-server/src/main/java/cuchaz/enigma/network/IntegratedEnigmaServer.java
new file mode 100644
index 0000000..21c6825
--- /dev/null
+++ b/enigma-server/src/main/java/cuchaz/enigma/network/IntegratedEnigmaServer.java
@@ -0,0 +1,16 @@
1package cuchaz.enigma.network;
2
3import cuchaz.enigma.translation.mapping.EntryRemapper;
4
5import javax.swing.*;
6
7public class IntegratedEnigmaServer extends EnigmaServer {
8 public IntegratedEnigmaServer(byte[] jarChecksum, char[] password, EntryRemapper mappings, int port) {
9 super(jarChecksum, password, mappings, port);
10 }
11
12 @Override
13 protected void runOnThread(Runnable task) {
14 SwingUtilities.invokeLater(task);
15 }
16}
diff --git a/enigma-server/src/main/java/cuchaz/enigma/network/Message.java b/enigma-server/src/main/java/cuchaz/enigma/network/Message.java
new file mode 100644
index 0000000..c157838
--- /dev/null
+++ b/enigma-server/src/main/java/cuchaz/enigma/network/Message.java
@@ -0,0 +1,393 @@
1package cuchaz.enigma.network;
2
3import java.io.DataInput;
4import java.io.DataOutput;
5import java.io.IOException;
6import java.util.Objects;
7
8import cuchaz.enigma.network.packet.PacketHelper;
9import cuchaz.enigma.translation.representation.entry.Entry;
10import cuchaz.enigma.utils.I18n;
11
12public abstract class Message {
13
14 public final String user;
15
16 public static Chat chat(String user, String message) {
17 return new Chat(user, message);
18 }
19
20 public static Connect connect(String user) {
21 return new Connect(user);
22 }
23
24 public static Disconnect disconnect(String user) {
25 return new Disconnect(user);
26 }
27
28 public static EditDocs editDocs(String user, Entry<?> entry) {
29 return new EditDocs(user, entry);
30 }
31
32 public static MarkDeobf markDeobf(String user, Entry<?> entry) {
33 return new MarkDeobf(user, entry);
34 }
35
36 public static RemoveMapping removeMapping(String user, Entry<?> entry) {
37 return new RemoveMapping(user, entry);
38 }
39
40 public static Rename rename(String user, Entry<?> entry, String newName) {
41 return new Rename(user, entry, newName);
42 }
43
44 public abstract String translate();
45
46 public abstract Type getType();
47
48 public static Message read(DataInput input) throws IOException {
49 byte typeId = input.readByte();
50 if (typeId < 0 || typeId >= Type.values().length) {
51 throw new IOException(String.format("Invalid message type ID %d", typeId));
52 }
53 Type type = Type.values()[typeId];
54 String user = input.readUTF();
55 switch (type) {
56 case CHAT:
57 String message = input.readUTF();
58 return chat(user, message);
59 case CONNECT:
60 return connect(user);
61 case DISCONNECT:
62 return disconnect(user);
63 case EDIT_DOCS:
64 Entry<?> entry = PacketHelper.readEntry(input);
65 return editDocs(user, entry);
66 case MARK_DEOBF:
67 entry = PacketHelper.readEntry(input);
68 return markDeobf(user, entry);
69 case REMOVE_MAPPING:
70 entry = PacketHelper.readEntry(input);
71 return removeMapping(user, entry);
72 case RENAME:
73 entry = PacketHelper.readEntry(input);
74 String newName = input.readUTF();
75 return rename(user, entry, newName);
76 default:
77 throw new IllegalStateException("unreachable");
78 }
79 }
80
81 public void write(DataOutput output) throws IOException {
82 output.writeByte(getType().ordinal());
83 PacketHelper.writeString(output, user);
84 }
85
86 private Message(String user) {
87 this.user = user;
88 }
89
90 @Override
91 public boolean equals(Object o) {
92 if (this == o) return true;
93 if (o == null || getClass() != o.getClass()) return false;
94 Message message = (Message) o;
95 return Objects.equals(user, message.user);
96 }
97
98 @Override
99 public int hashCode() {
100 return Objects.hash(user);
101 }
102
103 public enum Type {
104 CHAT,
105 CONNECT,
106 DISCONNECT,
107 EDIT_DOCS,
108 MARK_DEOBF,
109 REMOVE_MAPPING,
110 RENAME,
111 }
112
113 public static final class Chat extends Message {
114
115 public final String message;
116
117 private Chat(String user, String message) {
118 super(user);
119 this.message = message;
120 }
121
122 @Override
123 public void write(DataOutput output) throws IOException {
124 super.write(output);
125 PacketHelper.writeString(output, message);
126 }
127
128 @Override
129 public String translate() {
130 return String.format(I18n.translate("message.chat.text"), user, message);
131 }
132
133 @Override
134 public Type getType() {
135 return Type.CHAT;
136 }
137
138 @Override
139 public boolean equals(Object o) {
140 if (this == o) return true;
141 if (o == null || getClass() != o.getClass()) return false;
142 if (!super.equals(o)) return false;
143 Chat chat = (Chat) o;
144 return Objects.equals(message, chat.message);
145 }
146
147 @Override
148 public int hashCode() {
149 return Objects.hash(super.hashCode(), message);
150 }
151
152 @Override
153 public String toString() {
154 return String.format("Message.Chat { user: '%s', message: '%s' }", user, message);
155 }
156
157 }
158
159 public static final class Connect extends Message {
160
161 private Connect(String user) {
162 super(user);
163 }
164
165 @Override
166 public String translate() {
167 return String.format(I18n.translate("message.connect.text"), user);
168 }
169
170 @Override
171 public Type getType() {
172 return Type.CONNECT;
173 }
174
175 @Override
176 public String toString() {
177 return String.format("Message.Connect { user: '%s' }", user);
178 }
179
180 }
181
182 public static final class Disconnect extends Message {
183
184 private Disconnect(String user) {
185 super(user);
186 }
187
188 @Override
189 public String translate() {
190 return String.format(I18n.translate("message.disconnect.text"), user);
191 }
192
193 @Override
194 public Type getType() {
195 return Type.DISCONNECT;
196 }
197
198 @Override
199 public String toString() {
200 return String.format("Message.Disconnect { user: '%s' }", user);
201 }
202
203 }
204
205 public static final class EditDocs extends Message {
206
207 public final Entry<?> entry;
208
209 private EditDocs(String user, Entry<?> entry) {
210 super(user);
211 this.entry = entry;
212 }
213
214 @Override
215 public void write(DataOutput output) throws IOException {
216 super.write(output);
217 PacketHelper.writeEntry(output, entry);
218 }
219
220 @Override
221 public String translate() {
222 return String.format(I18n.translate("message.edit_docs.text"), user, entry);
223 }
224
225 @Override
226 public Type getType() {
227 return Type.EDIT_DOCS;
228 }
229
230 @Override
231 public boolean equals(Object o) {
232 if (this == o) return true;
233 if (o == null || getClass() != o.getClass()) return false;
234 if (!super.equals(o)) return false;
235 EditDocs editDocs = (EditDocs) o;
236 return Objects.equals(entry, editDocs.entry);
237 }
238
239 @Override
240 public int hashCode() {
241 return Objects.hash(super.hashCode(), entry);
242 }
243
244 @Override
245 public String toString() {
246 return String.format("Message.EditDocs { user: '%s', entry: %s }", user, entry);
247 }
248
249 }
250
251 public static final class MarkDeobf extends Message {
252
253 public final Entry<?> entry;
254
255 private MarkDeobf(String user, Entry<?> entry) {
256 super(user);
257 this.entry = entry;
258 }
259
260 @Override
261 public void write(DataOutput output) throws IOException {
262 super.write(output);
263 PacketHelper.writeEntry(output, entry);
264 }
265
266 @Override
267 public String translate() {
268 return String.format(I18n.translate("message.mark_deobf.text"), user, entry);
269 }
270
271 @Override
272 public Type getType() {
273 return Type.MARK_DEOBF;
274 }
275
276 @Override
277 public boolean equals(Object o) {
278 if (this == o) return true;
279 if (o == null || getClass() != o.getClass()) return false;
280 if (!super.equals(o)) return false;
281 MarkDeobf markDeobf = (MarkDeobf) o;
282 return Objects.equals(entry, markDeobf.entry);
283 }
284
285 @Override
286 public int hashCode() {
287 return Objects.hash(super.hashCode(), entry);
288 }
289
290 @Override
291 public String toString() {
292 return String.format("Message.MarkDeobf { user: '%s', entry: %s }", user, entry);
293 }
294
295 }
296
297 public static final class RemoveMapping extends Message {
298
299 public final Entry<?> entry;
300
301 private RemoveMapping(String user, Entry<?> entry) {
302 super(user);
303 this.entry = entry;
304 }
305
306 @Override
307 public void write(DataOutput output) throws IOException {
308 super.write(output);
309 PacketHelper.writeEntry(output, entry);
310 }
311
312 @Override
313 public String translate() {
314 return String.format(I18n.translate("message.remove_mapping.text"), user, entry);
315 }
316
317 @Override
318 public Type getType() {
319 return Type.REMOVE_MAPPING;
320 }
321
322 @Override
323 public boolean equals(Object o) {
324 if (this == o) return true;
325 if (o == null || getClass() != o.getClass()) return false;
326 if (!super.equals(o)) return false;
327 RemoveMapping that = (RemoveMapping) o;
328 return Objects.equals(entry, that.entry);
329 }
330
331 @Override
332 public int hashCode() {
333 return Objects.hash(super.hashCode(), entry);
334 }
335
336 @Override
337 public String toString() {
338 return String.format("Message.RemoveMapping { user: '%s', entry: %s }", user, entry);
339 }
340
341 }
342
343 public static final class Rename extends Message {
344
345 public final Entry<?> entry;
346 public final String newName;
347
348 private Rename(String user, Entry<?> entry, String newName) {
349 super(user);
350 this.entry = entry;
351 this.newName = newName;
352 }
353
354 @Override
355 public void write(DataOutput output) throws IOException {
356 super.write(output);
357 PacketHelper.writeEntry(output, entry);
358 PacketHelper.writeString(output, newName);
359 }
360
361 @Override
362 public String translate() {
363 return String.format(I18n.translate("message.rename.text"), user, entry, newName);
364 }
365
366 @Override
367 public Type getType() {
368 return Type.RENAME;
369 }
370
371 @Override
372 public boolean equals(Object o) {
373 if (this == o) return true;
374 if (o == null || getClass() != o.getClass()) return false;
375 if (!super.equals(o)) return false;
376 Rename rename = (Rename) o;
377 return Objects.equals(entry, rename.entry) &&
378 Objects.equals(newName, rename.newName);
379 }
380
381 @Override
382 public int hashCode() {
383 return Objects.hash(super.hashCode(), entry, newName);
384 }
385
386 @Override
387 public String toString() {
388 return String.format("Message.Rename { user: '%s', entry: %s, newName: '%s' }", user, entry, newName);
389 }
390
391 }
392
393}
diff --git a/enigma-server/src/main/java/cuchaz/enigma/network/ServerPacketHandler.java b/enigma-server/src/main/java/cuchaz/enigma/network/ServerPacketHandler.java
new file mode 100644
index 0000000..8618553
--- /dev/null
+++ b/enigma-server/src/main/java/cuchaz/enigma/network/ServerPacketHandler.java
@@ -0,0 +1,22 @@
1package cuchaz.enigma.network;
2
3import java.net.Socket;
4
5public class ServerPacketHandler {
6
7 private final Socket client;
8 private final EnigmaServer server;
9
10 public ServerPacketHandler(Socket client, EnigmaServer server) {
11 this.client = client;
12 this.server = server;
13 }
14
15 public Socket getClient() {
16 return client;
17 }
18
19 public EnigmaServer getServer() {
20 return server;
21 }
22}
diff --git a/enigma-server/src/main/java/cuchaz/enigma/network/packet/ChangeDocsC2SPacket.java b/enigma-server/src/main/java/cuchaz/enigma/network/packet/ChangeDocsC2SPacket.java
new file mode 100644
index 0000000..1b52cf1
--- /dev/null
+++ b/enigma-server/src/main/java/cuchaz/enigma/network/packet/ChangeDocsC2SPacket.java
@@ -0,0 +1,59 @@
1package cuchaz.enigma.network.packet;
2
3import cuchaz.enigma.translation.mapping.EntryMapping;
4import cuchaz.enigma.network.EnigmaServer;
5import cuchaz.enigma.network.Message;
6import cuchaz.enigma.network.ServerPacketHandler;
7import cuchaz.enigma.translation.representation.entry.Entry;
8import cuchaz.enigma.utils.Utils;
9
10import java.io.DataInput;
11import java.io.DataOutput;
12import java.io.IOException;
13
14public class ChangeDocsC2SPacket implements Packet<ServerPacketHandler> {
15 private Entry<?> entry;
16 private String newDocs;
17
18 ChangeDocsC2SPacket() {
19 }
20
21 public ChangeDocsC2SPacket(Entry<?> entry, String newDocs) {
22 this.entry = entry;
23 this.newDocs = newDocs;
24 }
25
26 @Override
27 public void read(DataInput input) throws IOException {
28 this.entry = PacketHelper.readEntry(input);
29 this.newDocs = PacketHelper.readString(input);
30 }
31
32 @Override
33 public void write(DataOutput output) throws IOException {
34 PacketHelper.writeEntry(output, entry);
35 PacketHelper.writeString(output, newDocs);
36 }
37
38 @Override
39 public void handle(ServerPacketHandler handler) {
40 EntryMapping mapping = handler.getServer().getMappings().getDeobfMapping(entry);
41
42 boolean valid = handler.getServer().canModifyEntry(handler.getClient(), entry);
43 if (!valid) {
44 String oldDocs = mapping == null ? null : mapping.getJavadoc();
45 handler.getServer().sendPacket(handler.getClient(), new ChangeDocsS2CPacket(EnigmaServer.DUMMY_SYNC_ID, entry, oldDocs == null ? "" : oldDocs));
46 return;
47 }
48
49 if (mapping == null) {
50 mapping = new EntryMapping(handler.getServer().getMappings().deobfuscate(entry).getName());
51 }
52 handler.getServer().getMappings().mapFromObf(entry, mapping.withDocs(Utils.isBlank(newDocs) ? null : newDocs));
53
54 int syncId = handler.getServer().lockEntry(handler.getClient(), entry);
55 handler.getServer().sendToAllExcept(handler.getClient(), new ChangeDocsS2CPacket(syncId, entry, newDocs));
56 handler.getServer().sendMessage(Message.editDocs(handler.getServer().getUsername(handler.getClient()), entry));
57 }
58
59}
diff --git a/enigma-server/src/main/java/cuchaz/enigma/network/packet/ChangeDocsS2CPacket.java b/enigma-server/src/main/java/cuchaz/enigma/network/packet/ChangeDocsS2CPacket.java
new file mode 100644
index 0000000..12a3025
--- /dev/null
+++ b/enigma-server/src/main/java/cuchaz/enigma/network/packet/ChangeDocsS2CPacket.java
@@ -0,0 +1,44 @@
1package cuchaz.enigma.network.packet;
2
3import cuchaz.enigma.analysis.EntryReference;
4import cuchaz.enigma.network.ClientPacketHandler;
5import cuchaz.enigma.translation.representation.entry.Entry;
6
7import java.io.DataInput;
8import java.io.DataOutput;
9import java.io.IOException;
10
11public class ChangeDocsS2CPacket implements Packet<ClientPacketHandler> {
12 private int syncId;
13 private Entry<?> entry;
14 private String newDocs;
15
16 ChangeDocsS2CPacket() {
17 }
18
19 public ChangeDocsS2CPacket(int syncId, Entry<?> entry, String newDocs) {
20 this.syncId = syncId;
21 this.entry = entry;
22 this.newDocs = newDocs;
23 }
24
25 @Override
26 public void read(DataInput input) throws IOException {
27 this.syncId = input.readUnsignedShort();
28 this.entry = PacketHelper.readEntry(input);
29 this.newDocs = PacketHelper.readString(input);
30 }
31
32 @Override
33 public void write(DataOutput output) throws IOException {
34 output.writeShort(syncId);
35 PacketHelper.writeEntry(output, entry);
36 PacketHelper.writeString(output, newDocs);
37 }
38
39 @Override
40 public void handle(ClientPacketHandler controller) {
41 controller.changeDocs(new EntryReference<>(entry, entry.getName()), newDocs, false);
42 controller.sendPacket(new ConfirmChangeC2SPacket(syncId));
43 }
44}
diff --git a/enigma-server/src/main/java/cuchaz/enigma/network/packet/ConfirmChangeC2SPacket.java b/enigma-server/src/main/java/cuchaz/enigma/network/packet/ConfirmChangeC2SPacket.java
new file mode 100644
index 0000000..78ef964
--- /dev/null
+++ b/enigma-server/src/main/java/cuchaz/enigma/network/packet/ConfirmChangeC2SPacket.java
@@ -0,0 +1,33 @@
1package cuchaz.enigma.network.packet;
2
3import cuchaz.enigma.network.ServerPacketHandler;
4
5import java.io.DataInput;
6import java.io.DataOutput;
7import java.io.IOException;
8
9public class ConfirmChangeC2SPacket implements Packet<ServerPacketHandler> {
10 private int syncId;
11
12 ConfirmChangeC2SPacket() {
13 }
14
15 public ConfirmChangeC2SPacket(int syncId) {
16 this.syncId = syncId;
17 }
18
19 @Override
20 public void read(DataInput input) throws IOException {
21 this.syncId = input.readUnsignedShort();
22 }
23
24 @Override
25 public void write(DataOutput output) throws IOException {
26 output.writeShort(syncId);
27 }
28
29 @Override
30 public void handle(ServerPacketHandler handler) {
31 handler.getServer().confirmChange(handler.getClient(), syncId);
32 }
33}
diff --git a/enigma-server/src/main/java/cuchaz/enigma/network/packet/KickS2CPacket.java b/enigma-server/src/main/java/cuchaz/enigma/network/packet/KickS2CPacket.java
new file mode 100644
index 0000000..9a112a8
--- /dev/null
+++ b/enigma-server/src/main/java/cuchaz/enigma/network/packet/KickS2CPacket.java
@@ -0,0 +1,33 @@
1package cuchaz.enigma.network.packet;
2
3import cuchaz.enigma.network.ClientPacketHandler;
4
5import java.io.DataInput;
6import java.io.DataOutput;
7import java.io.IOException;
8
9public class KickS2CPacket implements Packet<ClientPacketHandler> {
10 private String reason;
11
12 KickS2CPacket() {
13 }
14
15 public KickS2CPacket(String reason) {
16 this.reason = reason;
17 }
18
19 @Override
20 public void read(DataInput input) throws IOException {
21 this.reason = PacketHelper.readString(input);
22 }
23
24 @Override
25 public void write(DataOutput output) throws IOException {
26 PacketHelper.writeString(output, reason);
27 }
28
29 @Override
30 public void handle(ClientPacketHandler controller) {
31 controller.disconnectIfConnected(reason);
32 }
33}
diff --git a/enigma-server/src/main/java/cuchaz/enigma/network/packet/LoginC2SPacket.java b/enigma-server/src/main/java/cuchaz/enigma/network/packet/LoginC2SPacket.java
new file mode 100644
index 0000000..da0f44a
--- /dev/null
+++ b/enigma-server/src/main/java/cuchaz/enigma/network/packet/LoginC2SPacket.java
@@ -0,0 +1,75 @@
1package cuchaz.enigma.network.packet;
2
3import cuchaz.enigma.network.EnigmaServer;
4import cuchaz.enigma.network.ServerPacketHandler;
5import cuchaz.enigma.network.Message;
6
7import java.io.DataInput;
8import java.io.DataOutput;
9import java.io.IOException;
10import java.util.Arrays;
11
12public class LoginC2SPacket implements Packet<ServerPacketHandler> {
13 private byte[] jarChecksum;
14 private char[] password;
15 private String username;
16
17 LoginC2SPacket() {
18 }
19
20 public LoginC2SPacket(byte[] jarChecksum, char[] password, String username) {
21 this.jarChecksum = jarChecksum;
22 this.password = password;
23 this.username = username;
24 }
25
26 @Override
27 public void read(DataInput input) throws IOException {
28 if (input.readUnsignedShort() != EnigmaServer.PROTOCOL_VERSION) {
29 throw new IOException("Mismatching protocol");
30 }
31 this.jarChecksum = new byte[EnigmaServer.CHECKSUM_SIZE];
32 input.readFully(jarChecksum);
33 this.password = new char[input.readUnsignedByte()];
34 for (int i = 0; i < password.length; i++) {
35 password[i] = input.readChar();
36 }
37 this.username = PacketHelper.readString(input);
38 }
39
40 @Override
41 public void write(DataOutput output) throws IOException {
42 output.writeShort(EnigmaServer.PROTOCOL_VERSION);
43 output.write(jarChecksum);
44 output.writeByte(password.length);
45 for (char c : password) {
46 output.writeChar(c);
47 }
48 PacketHelper.writeString(output, username);
49 }
50
51 @Override
52 public void handle(ServerPacketHandler handler) {
53 boolean usernameTaken = handler.getServer().isUsernameTaken(username);
54 handler.getServer().setUsername(handler.getClient(), username);
55 handler.getServer().log(username + " logged in with IP " + handler.getClient().getInetAddress().toString() + ":" + handler.getClient().getPort());
56
57 if (!Arrays.equals(password, handler.getServer().getPassword())) {
58 handler.getServer().kick(handler.getClient(), "disconnect.wrong_password");
59 return;
60 }
61
62 if (usernameTaken) {
63 handler.getServer().kick(handler.getClient(), "disconnect.username_taken");
64 return;
65 }
66
67 if (!Arrays.equals(jarChecksum, handler.getServer().getJarChecksum())) {
68 handler.getServer().kick(handler.getClient(), "disconnect.wrong_jar");
69 return;
70 }
71
72 handler.getServer().sendPacket(handler.getClient(), new SyncMappingsS2CPacket(handler.getServer().getMappings().getObfToDeobf()));
73 handler.getServer().sendMessage(Message.connect(username));
74 }
75}
diff --git a/enigma-server/src/main/java/cuchaz/enigma/network/packet/MarkDeobfuscatedC2SPacket.java b/enigma-server/src/main/java/cuchaz/enigma/network/packet/MarkDeobfuscatedC2SPacket.java
new file mode 100644
index 0000000..a41c620
--- /dev/null
+++ b/enigma-server/src/main/java/cuchaz/enigma/network/packet/MarkDeobfuscatedC2SPacket.java
@@ -0,0 +1,48 @@
1package cuchaz.enigma.network.packet;
2
3import cuchaz.enigma.network.ServerPacketHandler;
4import cuchaz.enigma.translation.mapping.EntryMapping;
5import cuchaz.enigma.translation.representation.entry.Entry;
6import cuchaz.enigma.network.Message;
7
8import java.io.DataInput;
9import java.io.DataOutput;
10import java.io.IOException;
11
12public class MarkDeobfuscatedC2SPacket implements Packet<ServerPacketHandler> {
13 private Entry<?> entry;
14
15 MarkDeobfuscatedC2SPacket() {
16 }
17
18 public MarkDeobfuscatedC2SPacket(Entry<?> entry) {
19 this.entry = entry;
20 }
21
22 @Override
23 public void read(DataInput input) throws IOException {
24 this.entry = PacketHelper.readEntry(input);
25 }
26
27 @Override
28 public void write(DataOutput output) throws IOException {
29 PacketHelper.writeEntry(output, entry);
30 }
31
32 @Override
33 public void handle(ServerPacketHandler handler) {
34 boolean valid = handler.getServer().canModifyEntry(handler.getClient(), entry);
35 if (!valid) {
36 handler.getServer().sendCorrectMapping(handler.getClient(), entry, true);
37 return;
38 }
39
40 handler.getServer().getMappings().mapFromObf(entry, new EntryMapping(handler.getServer().getMappings().deobfuscate(entry).getName()));
41 handler.getServer().log(handler.getServer().getUsername(handler.getClient()) + " marked " + entry + " as deobfuscated");
42
43 int syncId = handler.getServer().lockEntry(handler.getClient(), entry);
44 handler.getServer().sendToAllExcept(handler.getClient(), new MarkDeobfuscatedS2CPacket(syncId, entry));
45 handler.getServer().sendMessage(Message.markDeobf(handler.getServer().getUsername(handler.getClient()), entry));
46
47 }
48}
diff --git a/enigma-server/src/main/java/cuchaz/enigma/network/packet/MarkDeobfuscatedS2CPacket.java b/enigma-server/src/main/java/cuchaz/enigma/network/packet/MarkDeobfuscatedS2CPacket.java
new file mode 100644
index 0000000..7504430
--- /dev/null
+++ b/enigma-server/src/main/java/cuchaz/enigma/network/packet/MarkDeobfuscatedS2CPacket.java
@@ -0,0 +1,40 @@
1package cuchaz.enigma.network.packet;
2
3import cuchaz.enigma.analysis.EntryReference;
4import cuchaz.enigma.network.ClientPacketHandler;
5import cuchaz.enigma.translation.representation.entry.Entry;
6
7import java.io.DataInput;
8import java.io.DataOutput;
9import java.io.IOException;
10
11public class MarkDeobfuscatedS2CPacket implements Packet<ClientPacketHandler> {
12 private int syncId;
13 private Entry<?> entry;
14
15 MarkDeobfuscatedS2CPacket() {
16 }
17
18 public MarkDeobfuscatedS2CPacket(int syncId, Entry<?> entry) {
19 this.syncId = syncId;
20 this.entry = entry;
21 }
22
23 @Override
24 public void read(DataInput input) throws IOException {
25 this.syncId = input.readUnsignedShort();
26 this.entry = PacketHelper.readEntry(input);
27 }
28
29 @Override
30 public void write(DataOutput output) throws IOException {
31 output.writeShort(syncId);
32 PacketHelper.writeEntry(output, entry);
33 }
34
35 @Override
36 public void handle(ClientPacketHandler controller) {
37 controller.markAsDeobfuscated(new EntryReference<>(entry, entry.getName()), false);
38 controller.sendPacket(new ConfirmChangeC2SPacket(syncId));
39 }
40}
diff --git a/enigma-server/src/main/java/cuchaz/enigma/network/packet/MessageC2SPacket.java b/enigma-server/src/main/java/cuchaz/enigma/network/packet/MessageC2SPacket.java
new file mode 100644
index 0000000..3bc09e7
--- /dev/null
+++ b/enigma-server/src/main/java/cuchaz/enigma/network/packet/MessageC2SPacket.java
@@ -0,0 +1,39 @@
1package cuchaz.enigma.network.packet;
2
3import java.io.DataInput;
4import java.io.DataOutput;
5import java.io.IOException;
6
7import cuchaz.enigma.network.ServerPacketHandler;
8import cuchaz.enigma.network.Message;
9
10public class MessageC2SPacket implements Packet<ServerPacketHandler> {
11
12 private String message;
13
14 MessageC2SPacket() {
15 }
16
17 public MessageC2SPacket(String message) {
18 this.message = message;
19 }
20
21 @Override
22 public void read(DataInput input) throws IOException {
23 message = PacketHelper.readString(input);
24 }
25
26 @Override
27 public void write(DataOutput output) throws IOException {
28 PacketHelper.writeString(output, message);
29 }
30
31 @Override
32 public void handle(ServerPacketHandler handler) {
33 String message = this.message.trim();
34 if (!message.isEmpty()) {
35 handler.getServer().sendMessage(Message.chat(handler.getServer().getUsername(handler.getClient()), message));
36 }
37 }
38
39}
diff --git a/enigma-server/src/main/java/cuchaz/enigma/network/packet/MessageS2CPacket.java b/enigma-server/src/main/java/cuchaz/enigma/network/packet/MessageS2CPacket.java
new file mode 100644
index 0000000..2b07968
--- /dev/null
+++ b/enigma-server/src/main/java/cuchaz/enigma/network/packet/MessageS2CPacket.java
@@ -0,0 +1,36 @@
1package cuchaz.enigma.network.packet;
2
3import java.io.DataInput;
4import java.io.DataOutput;
5import java.io.IOException;
6
7import cuchaz.enigma.network.ClientPacketHandler;
8import cuchaz.enigma.network.Message;
9
10public class MessageS2CPacket implements Packet<ClientPacketHandler> {
11
12 private Message message;
13
14 MessageS2CPacket() {
15 }
16
17 public MessageS2CPacket(Message message) {
18 this.message = message;
19 }
20
21 @Override
22 public void read(DataInput input) throws IOException {
23 message = Message.read(input);
24 }
25
26 @Override
27 public void write(DataOutput output) throws IOException {
28 message.write(output);
29 }
30
31 @Override
32 public void handle(ClientPacketHandler handler) {
33 handler.addMessage(message);
34 }
35
36}
diff --git a/enigma-server/src/main/java/cuchaz/enigma/network/packet/Packet.java b/enigma-server/src/main/java/cuchaz/enigma/network/packet/Packet.java
new file mode 100644
index 0000000..2f16dfb
--- /dev/null
+++ b/enigma-server/src/main/java/cuchaz/enigma/network/packet/Packet.java
@@ -0,0 +1,15 @@
1package cuchaz.enigma.network.packet;
2
3import java.io.DataInput;
4import java.io.DataOutput;
5import java.io.IOException;
6
7public interface Packet<H> {
8
9 void read(DataInput input) throws IOException;
10
11 void write(DataOutput output) throws IOException;
12
13 void handle(H handler);
14
15}
diff --git a/enigma-server/src/main/java/cuchaz/enigma/network/packet/PacketHelper.java b/enigma-server/src/main/java/cuchaz/enigma/network/packet/PacketHelper.java
new file mode 100644
index 0000000..464606e
--- /dev/null
+++ b/enigma-server/src/main/java/cuchaz/enigma/network/packet/PacketHelper.java
@@ -0,0 +1,135 @@
1package cuchaz.enigma.network.packet;
2
3import cuchaz.enigma.translation.representation.MethodDescriptor;
4import cuchaz.enigma.translation.representation.TypeDescriptor;
5import cuchaz.enigma.translation.representation.entry.ClassEntry;
6import cuchaz.enigma.translation.representation.entry.Entry;
7import cuchaz.enigma.translation.representation.entry.FieldEntry;
8import cuchaz.enigma.translation.representation.entry.LocalVariableEntry;
9import cuchaz.enigma.translation.representation.entry.MethodEntry;
10
11import java.io.DataInput;
12import java.io.DataOutput;
13import java.io.IOException;
14import java.nio.charset.StandardCharsets;
15
16public class PacketHelper {
17
18 private static final int ENTRY_CLASS = 0, ENTRY_FIELD = 1, ENTRY_METHOD = 2, ENTRY_LOCAL_VAR = 3;
19 private static final int MAX_STRING_LENGTH = 65535;
20
21 public static Entry<?> readEntry(DataInput input) throws IOException {
22 return readEntry(input, null, true);
23 }
24
25 public static Entry<?> readEntry(DataInput input, Entry<?> parent, boolean includeParent) throws IOException {
26 int type = input.readUnsignedByte();
27
28 if (includeParent && input.readBoolean()) {
29 parent = readEntry(input, null, true);
30 }
31
32 String name = readString(input);
33
34 String javadocs = null;
35 if (input.readBoolean()) {
36 javadocs = readString(input);
37 }
38
39 switch (type) {
40 case ENTRY_CLASS: {
41 if (parent != null && !(parent instanceof ClassEntry)) {
42 throw new IOException("Class requires class parent");
43 }
44 return new ClassEntry((ClassEntry) parent, name, javadocs);
45 }
46 case ENTRY_FIELD: {
47 if (!(parent instanceof ClassEntry)) {
48 throw new IOException("Field requires class parent");
49 }
50 TypeDescriptor desc = new TypeDescriptor(readString(input));
51 return new FieldEntry((ClassEntry) parent, name, desc, javadocs);
52 }
53 case ENTRY_METHOD: {
54 if (!(parent instanceof ClassEntry)) {
55 throw new IOException("Method requires class parent");
56 }
57 MethodDescriptor desc = new MethodDescriptor(readString(input));
58 return new MethodEntry((ClassEntry) parent, name, desc, javadocs);
59 }
60 case ENTRY_LOCAL_VAR: {
61 if (!(parent instanceof MethodEntry)) {
62 throw new IOException("Local variable requires method parent");
63 }
64 int index = input.readUnsignedShort();
65 boolean parameter = input.readBoolean();
66 return new LocalVariableEntry((MethodEntry) parent, index, name, parameter, javadocs);
67 }
68 default: throw new IOException("Received unknown entry type " + type);
69 }
70 }
71
72 public static void writeEntry(DataOutput output, Entry<?> entry) throws IOException {
73 writeEntry(output, entry, true);
74 }
75
76 public static void writeEntry(DataOutput output, Entry<?> entry, boolean includeParent) throws IOException {
77 // type
78 if (entry instanceof ClassEntry) {
79 output.writeByte(ENTRY_CLASS);
80 } else if (entry instanceof FieldEntry) {
81 output.writeByte(ENTRY_FIELD);
82 } else if (entry instanceof MethodEntry) {
83 output.writeByte(ENTRY_METHOD);
84 } else if (entry instanceof LocalVariableEntry) {
85 output.writeByte(ENTRY_LOCAL_VAR);
86 } else {
87 throw new IOException("Don't know how to serialize entry of type " + entry.getClass().getSimpleName());
88 }
89
90 // parent
91 if (includeParent) {
92 output.writeBoolean(entry.getParent() != null);
93 if (entry.getParent() != null) {
94 writeEntry(output, entry.getParent(), true);
95 }
96 }
97
98 // name
99 writeString(output, entry.getName());
100
101 // javadocs
102 output.writeBoolean(entry.getJavadocs() != null);
103 if (entry.getJavadocs() != null) {
104 writeString(output, entry.getJavadocs());
105 }
106
107 // type-specific stuff
108 if (entry instanceof FieldEntry) {
109 writeString(output, ((FieldEntry) entry).getDesc().toString());
110 } else if (entry instanceof MethodEntry) {
111 writeString(output, ((MethodEntry) entry).getDesc().toString());
112 } else if (entry instanceof LocalVariableEntry) {
113 LocalVariableEntry localVar = (LocalVariableEntry) entry;
114 output.writeShort(localVar.getIndex());
115 output.writeBoolean(localVar.isArgument());
116 }
117 }
118
119 public static String readString(DataInput input) throws IOException {
120 int length = input.readUnsignedShort();
121 byte[] bytes = new byte[length];
122 input.readFully(bytes);
123 return new String(bytes, StandardCharsets.UTF_8);
124 }
125
126 public static void writeString(DataOutput output, String str) throws IOException {
127 byte[] bytes = str.getBytes(StandardCharsets.UTF_8);
128 if (bytes.length > MAX_STRING_LENGTH) {
129 throw new IOException("String too long, was " + bytes.length + " bytes, max " + MAX_STRING_LENGTH + " allowed");
130 }
131 output.writeShort(bytes.length);
132 output.write(bytes);
133 }
134
135}
diff --git a/enigma-server/src/main/java/cuchaz/enigma/network/packet/PacketRegistry.java b/enigma-server/src/main/java/cuchaz/enigma/network/packet/PacketRegistry.java
new file mode 100644
index 0000000..3b8af81
--- /dev/null
+++ b/enigma-server/src/main/java/cuchaz/enigma/network/packet/PacketRegistry.java
@@ -0,0 +1,64 @@
1package cuchaz.enigma.network.packet;
2
3import cuchaz.enigma.network.ClientPacketHandler;
4import cuchaz.enigma.network.ServerPacketHandler;
5
6import java.util.HashMap;
7import java.util.Map;
8import java.util.function.Supplier;
9
10public class PacketRegistry {
11
12 private static final Map<Class<? extends Packet<ServerPacketHandler>>, Integer> c2sPacketIds = new HashMap<>();
13 private static final Map<Integer, Supplier<? extends Packet<ServerPacketHandler>>> c2sPacketCreators = new HashMap<>();
14 private static final Map<Class<? extends Packet<ClientPacketHandler>>, Integer> s2cPacketIds = new HashMap<>();
15 private static final Map<Integer, Supplier<? extends Packet<ClientPacketHandler>>> s2cPacketCreators = new HashMap<>();
16
17 private static <T extends Packet<ServerPacketHandler>> void registerC2S(int id, Class<T> clazz, Supplier<T> creator) {
18 c2sPacketIds.put(clazz, id);
19 c2sPacketCreators.put(id, creator);
20 }
21
22 private static <T extends Packet<ClientPacketHandler>> void registerS2C(int id, Class<T> clazz, Supplier<T> creator) {
23 s2cPacketIds.put(clazz, id);
24 s2cPacketCreators.put(id, creator);
25 }
26
27 static {
28 registerC2S(0, LoginC2SPacket.class, LoginC2SPacket::new);
29 registerC2S(1, ConfirmChangeC2SPacket.class, ConfirmChangeC2SPacket::new);
30 registerC2S(2, RenameC2SPacket.class, RenameC2SPacket::new);
31 registerC2S(3, RemoveMappingC2SPacket.class, RemoveMappingC2SPacket::new);
32 registerC2S(4, ChangeDocsC2SPacket.class, ChangeDocsC2SPacket::new);
33 registerC2S(5, MarkDeobfuscatedC2SPacket.class, MarkDeobfuscatedC2SPacket::new);
34 registerC2S(6, MessageC2SPacket.class, MessageC2SPacket::new);
35
36 registerS2C(0, KickS2CPacket.class, KickS2CPacket::new);
37 registerS2C(1, SyncMappingsS2CPacket.class, SyncMappingsS2CPacket::new);
38 registerS2C(2, RenameS2CPacket.class, RenameS2CPacket::new);
39 registerS2C(3, RemoveMappingS2CPacket.class, RemoveMappingS2CPacket::new);
40 registerS2C(4, ChangeDocsS2CPacket.class, ChangeDocsS2CPacket::new);
41 registerS2C(5, MarkDeobfuscatedS2CPacket.class, MarkDeobfuscatedS2CPacket::new);
42 registerS2C(6, MessageS2CPacket.class, MessageS2CPacket::new);
43 registerS2C(7, UserListS2CPacket.class, UserListS2CPacket::new);
44 }
45
46 public static int getC2SId(Packet<ServerPacketHandler> packet) {
47 return c2sPacketIds.get(packet.getClass());
48 }
49
50 public static Packet<ServerPacketHandler> createC2SPacket(int id) {
51 Supplier<? extends Packet<ServerPacketHandler>> creator = c2sPacketCreators.get(id);
52 return creator == null ? null : creator.get();
53 }
54
55 public static int getS2CId(Packet<ClientPacketHandler> packet) {
56 return s2cPacketIds.get(packet.getClass());
57 }
58
59 public static Packet<ClientPacketHandler> createS2CPacket(int id) {
60 Supplier<? extends Packet<ClientPacketHandler>> creator = s2cPacketCreators.get(id);
61 return creator == null ? null : creator.get();
62 }
63
64}
diff --git a/enigma-server/src/main/java/cuchaz/enigma/network/packet/RemoveMappingC2SPacket.java b/enigma-server/src/main/java/cuchaz/enigma/network/packet/RemoveMappingC2SPacket.java
new file mode 100644
index 0000000..3f85228
--- /dev/null
+++ b/enigma-server/src/main/java/cuchaz/enigma/network/packet/RemoveMappingC2SPacket.java
@@ -0,0 +1,55 @@
1package cuchaz.enigma.network.packet;
2
3import cuchaz.enigma.network.ServerPacketHandler;
4import cuchaz.enigma.translation.mapping.IllegalNameException;
5import cuchaz.enigma.translation.representation.entry.Entry;
6import cuchaz.enigma.network.Message;
7
8import java.io.DataInput;
9import java.io.DataOutput;
10import java.io.IOException;
11
12public class RemoveMappingC2SPacket implements Packet<ServerPacketHandler> {
13 private Entry<?> entry;
14
15 RemoveMappingC2SPacket() {
16 }
17
18 public RemoveMappingC2SPacket(Entry<?> entry) {
19 this.entry = entry;
20 }
21
22 @Override
23 public void read(DataInput input) throws IOException {
24 this.entry = PacketHelper.readEntry(input);
25 }
26
27 @Override
28 public void write(DataOutput output) throws IOException {
29 PacketHelper.writeEntry(output, entry);
30 }
31
32 @Override
33 public void handle(ServerPacketHandler handler) {
34 boolean valid = handler.getServer().canModifyEntry(handler.getClient(), entry);
35
36 if (valid) {
37 try {
38 handler.getServer().getMappings().removeByObf(entry);
39 } catch (IllegalNameException e) {
40 valid = false;
41 }
42 }
43
44 if (!valid) {
45 handler.getServer().sendCorrectMapping(handler.getClient(), entry, true);
46 return;
47 }
48
49 handler.getServer().log(handler.getServer().getUsername(handler.getClient()) + " removed the mapping for " + entry);
50
51 int syncId = handler.getServer().lockEntry(handler.getClient(), entry);
52 handler.getServer().sendToAllExcept(handler.getClient(), new RemoveMappingS2CPacket(syncId, entry));
53 handler.getServer().sendMessage(Message.removeMapping(handler.getServer().getUsername(handler.getClient()), entry));
54 }
55}
diff --git a/enigma-server/src/main/java/cuchaz/enigma/network/packet/RemoveMappingS2CPacket.java b/enigma-server/src/main/java/cuchaz/enigma/network/packet/RemoveMappingS2CPacket.java
new file mode 100644
index 0000000..70d803c
--- /dev/null
+++ b/enigma-server/src/main/java/cuchaz/enigma/network/packet/RemoveMappingS2CPacket.java
@@ -0,0 +1,40 @@
1package cuchaz.enigma.network.packet;
2
3import cuchaz.enigma.analysis.EntryReference;
4import cuchaz.enigma.network.ClientPacketHandler;
5import cuchaz.enigma.translation.representation.entry.Entry;
6
7import java.io.DataInput;
8import java.io.DataOutput;
9import java.io.IOException;
10
11public class RemoveMappingS2CPacket implements Packet<ClientPacketHandler> {
12 private int syncId;
13 private Entry<?> entry;
14
15 RemoveMappingS2CPacket() {
16 }
17
18 public RemoveMappingS2CPacket(int syncId, Entry<?> entry) {
19 this.syncId = syncId;
20 this.entry = entry;
21 }
22
23 @Override
24 public void read(DataInput input) throws IOException {
25 this.syncId = input.readUnsignedShort();
26 this.entry = PacketHelper.readEntry(input);
27 }
28
29 @Override
30 public void write(DataOutput output) throws IOException {
31 output.writeShort(syncId);
32 PacketHelper.writeEntry(output, entry);
33 }
34
35 @Override
36 public void handle(ClientPacketHandler controller) {
37 controller.removeMapping(new EntryReference<>(entry, entry.getName()), false);
38 controller.sendPacket(new ConfirmChangeC2SPacket(syncId));
39 }
40}
diff --git a/enigma-server/src/main/java/cuchaz/enigma/network/packet/RenameC2SPacket.java b/enigma-server/src/main/java/cuchaz/enigma/network/packet/RenameC2SPacket.java
new file mode 100644
index 0000000..e3e7e37
--- /dev/null
+++ b/enigma-server/src/main/java/cuchaz/enigma/network/packet/RenameC2SPacket.java
@@ -0,0 +1,64 @@
1package cuchaz.enigma.network.packet;
2
3import cuchaz.enigma.network.ServerPacketHandler;
4import cuchaz.enigma.translation.mapping.IllegalNameException;
5import cuchaz.enigma.translation.mapping.EntryMapping;
6import cuchaz.enigma.translation.representation.entry.Entry;
7import cuchaz.enigma.network.Message;
8
9import java.io.DataInput;
10import java.io.DataOutput;
11import java.io.IOException;
12
13public class RenameC2SPacket implements Packet<ServerPacketHandler> {
14 private Entry<?> entry;
15 private String newName;
16 private boolean refreshClassTree;
17
18 RenameC2SPacket() {
19 }
20
21 public RenameC2SPacket(Entry<?> entry, String newName, boolean refreshClassTree) {
22 this.entry = entry;
23 this.newName = newName;
24 this.refreshClassTree = refreshClassTree;
25 }
26
27 @Override
28 public void read(DataInput input) throws IOException {
29 this.entry = PacketHelper.readEntry(input);
30 this.newName = PacketHelper.readString(input);
31 this.refreshClassTree = input.readBoolean();
32 }
33
34 @Override
35 public void write(DataOutput output) throws IOException {
36 PacketHelper.writeEntry(output, entry);
37 PacketHelper.writeString(output, newName);
38 output.writeBoolean(refreshClassTree);
39 }
40
41 @Override
42 public void handle(ServerPacketHandler handler) {
43 boolean valid = handler.getServer().canModifyEntry(handler.getClient(), entry);
44
45 if (valid) {
46 try {
47 handler.getServer().getMappings().mapFromObf(entry, new EntryMapping(newName));
48 } catch (IllegalNameException e) {
49 valid = false;
50 }
51 }
52
53 if (!valid) {
54 handler.getServer().sendCorrectMapping(handler.getClient(), entry, refreshClassTree);
55 return;
56 }
57
58 handler.getServer().log(handler.getServer().getUsername(handler.getClient()) + " renamed " + entry + " to " + newName);
59
60 int syncId = handler.getServer().lockEntry(handler.getClient(), entry);
61 handler.getServer().sendToAllExcept(handler.getClient(), new RenameS2CPacket(syncId, entry, newName, refreshClassTree));
62 handler.getServer().sendMessage(Message.rename(handler.getServer().getUsername(handler.getClient()), entry, newName));
63 }
64}
diff --git a/enigma-server/src/main/java/cuchaz/enigma/network/packet/RenameS2CPacket.java b/enigma-server/src/main/java/cuchaz/enigma/network/packet/RenameS2CPacket.java
new file mode 100644
index 0000000..787e89e
--- /dev/null
+++ b/enigma-server/src/main/java/cuchaz/enigma/network/packet/RenameS2CPacket.java
@@ -0,0 +1,48 @@
1package cuchaz.enigma.network.packet;
2
3import cuchaz.enigma.analysis.EntryReference;
4import cuchaz.enigma.network.ClientPacketHandler;
5import cuchaz.enigma.translation.representation.entry.Entry;
6
7import java.io.DataInput;
8import java.io.DataOutput;
9import java.io.IOException;
10
11public class RenameS2CPacket implements Packet<ClientPacketHandler> {
12 private int syncId;
13 private Entry<?> entry;
14 private String newName;
15 private boolean refreshClassTree;
16
17 RenameS2CPacket() {
18 }
19
20 public RenameS2CPacket(int syncId, Entry<?> entry, String newName, boolean refreshClassTree) {
21 this.syncId = syncId;
22 this.entry = entry;
23 this.newName = newName;
24 this.refreshClassTree = refreshClassTree;
25 }
26
27 @Override
28 public void read(DataInput input) throws IOException {
29 this.syncId = input.readUnsignedShort();
30 this.entry = PacketHelper.readEntry(input);
31 this.newName = PacketHelper.readString(input);
32 this.refreshClassTree = input.readBoolean();
33 }
34
35 @Override
36 public void write(DataOutput output) throws IOException {
37 output.writeShort(syncId);
38 PacketHelper.writeEntry(output, entry);
39 PacketHelper.writeString(output, newName);
40 output.writeBoolean(refreshClassTree);
41 }
42
43 @Override
44 public void handle(ClientPacketHandler controller) {
45 controller.rename(new EntryReference<>(entry, entry.getName()), newName, refreshClassTree, false);
46 controller.sendPacket(new ConfirmChangeC2SPacket(syncId));
47 }
48}
diff --git a/enigma-server/src/main/java/cuchaz/enigma/network/packet/SyncMappingsS2CPacket.java b/enigma-server/src/main/java/cuchaz/enigma/network/packet/SyncMappingsS2CPacket.java
new file mode 100644
index 0000000..76ecbc7
--- /dev/null
+++ b/enigma-server/src/main/java/cuchaz/enigma/network/packet/SyncMappingsS2CPacket.java
@@ -0,0 +1,88 @@
1package cuchaz.enigma.network.packet;
2
3import cuchaz.enigma.translation.mapping.EntryMapping;
4import cuchaz.enigma.translation.mapping.tree.EntryTree;
5import cuchaz.enigma.translation.mapping.tree.EntryTreeNode;
6import cuchaz.enigma.translation.mapping.tree.HashEntryTree;
7import cuchaz.enigma.network.ClientPacketHandler;
8import cuchaz.enigma.network.EnigmaServer;
9import cuchaz.enigma.translation.representation.entry.Entry;
10
11import java.io.DataInput;
12import java.io.DataOutput;
13import java.io.IOException;
14import java.util.Collection;
15import java.util.List;
16import java.util.stream.Collectors;
17
18public class SyncMappingsS2CPacket implements Packet<ClientPacketHandler> {
19 private EntryTree<EntryMapping> mappings;
20
21 SyncMappingsS2CPacket() {
22 }
23
24 public SyncMappingsS2CPacket(EntryTree<EntryMapping> mappings) {
25 this.mappings = mappings;
26 }
27
28 @Override
29 public void read(DataInput input) throws IOException {
30 mappings = new HashEntryTree<>();
31 int size = input.readInt();
32 for (int i = 0; i < size; i++) {
33 readEntryTreeNode(input, null);
34 }
35 }
36
37 private void readEntryTreeNode(DataInput input, Entry<?> parent) throws IOException {
38 Entry<?> entry = PacketHelper.readEntry(input, parent, false);
39 EntryMapping mapping = null;
40 if (input.readBoolean()) {
41 String name = input.readUTF();
42 if (input.readBoolean()) {
43 String javadoc = input.readUTF();
44 mapping = new EntryMapping(name, javadoc);
45 } else {
46 mapping = new EntryMapping(name);
47 }
48 }
49 mappings.insert(entry, mapping);
50 int size = input.readUnsignedShort();
51 for (int i = 0; i < size; i++) {
52 readEntryTreeNode(input, entry);
53 }
54 }
55
56 @Override
57 public void write(DataOutput output) throws IOException {
58 List<EntryTreeNode<EntryMapping>> roots = mappings.getRootNodes().collect(Collectors.toList());
59 output.writeInt(roots.size());
60 for (EntryTreeNode<EntryMapping> node : roots) {
61 writeEntryTreeNode(output, node);
62 }
63 }
64
65 private static void writeEntryTreeNode(DataOutput output, EntryTreeNode<EntryMapping> node) throws IOException {
66 PacketHelper.writeEntry(output, node.getEntry(), false);
67 EntryMapping value = node.getValue();
68 output.writeBoolean(value != null);
69 if (value != null) {
70 PacketHelper.writeString(output, value.getTargetName());
71 output.writeBoolean(value.getJavadoc() != null);
72 if (value.getJavadoc() != null) {
73 PacketHelper.writeString(output, value.getJavadoc());
74 }
75 }
76 Collection<? extends EntryTreeNode<EntryMapping>> children = node.getChildNodes();
77 output.writeShort(children.size());
78 for (EntryTreeNode<EntryMapping> child : children) {
79 writeEntryTreeNode(output, child);
80 }
81 }
82
83 @Override
84 public void handle(ClientPacketHandler controller) {
85 controller.openMappings(mappings);
86 controller.sendPacket(new ConfirmChangeC2SPacket(EnigmaServer.DUMMY_SYNC_ID));
87 }
88}
diff --git a/enigma-server/src/main/java/cuchaz/enigma/network/packet/UserListS2CPacket.java b/enigma-server/src/main/java/cuchaz/enigma/network/packet/UserListS2CPacket.java
new file mode 100644
index 0000000..b4a277a
--- /dev/null
+++ b/enigma-server/src/main/java/cuchaz/enigma/network/packet/UserListS2CPacket.java
@@ -0,0 +1,44 @@
1package cuchaz.enigma.network.packet;
2
3import cuchaz.enigma.network.ClientPacketHandler;
4
5import java.io.DataInput;
6import java.io.DataOutput;
7import java.io.IOException;
8import java.util.ArrayList;
9import java.util.List;
10
11public class UserListS2CPacket implements Packet<ClientPacketHandler> {
12
13 private List<String> users;
14
15 UserListS2CPacket() {
16 }
17
18 public UserListS2CPacket(List<String> users) {
19 this.users = users;
20 }
21
22 @Override
23 public void read(DataInput input) throws IOException {
24 int len = input.readUnsignedShort();
25 users = new ArrayList<>(len);
26 for (int i = 0; i < len; i++) {
27 users.add(input.readUTF());
28 }
29 }
30
31 @Override
32 public void write(DataOutput output) throws IOException {
33 output.writeShort(users.size());
34 for (String user : users) {
35 PacketHelper.writeString(output, user);
36 }
37 }
38
39 @Override
40 public void handle(ClientPacketHandler handler) {
41 handler.updateUserList(users);
42 }
43
44}