summaryrefslogtreecommitdiff
path: root/src/main/java/cuchaz/enigma/network
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/cuchaz/enigma/network')
-rw-r--r--src/main/java/cuchaz/enigma/network/DedicatedEnigmaServer.java164
-rw-r--r--src/main/java/cuchaz/enigma/network/EnigmaClient.java85
-rw-r--r--src/main/java/cuchaz/enigma/network/EnigmaServer.java292
-rw-r--r--src/main/java/cuchaz/enigma/network/IntegratedEnigmaServer.java16
-rw-r--r--src/main/java/cuchaz/enigma/network/ServerPacketHandler.java22
-rw-r--r--src/main/java/cuchaz/enigma/network/packet/ChangeDocsC2SPacket.java59
-rw-r--r--src/main/java/cuchaz/enigma/network/packet/ChangeDocsS2CPacket.java44
-rw-r--r--src/main/java/cuchaz/enigma/network/packet/ConfirmChangeC2SPacket.java33
-rw-r--r--src/main/java/cuchaz/enigma/network/packet/KickS2CPacket.java33
-rw-r--r--src/main/java/cuchaz/enigma/network/packet/LoginC2SPacket.java75
-rw-r--r--src/main/java/cuchaz/enigma/network/packet/MarkDeobfuscatedC2SPacket.java48
-rw-r--r--src/main/java/cuchaz/enigma/network/packet/MarkDeobfuscatedS2CPacket.java40
-rw-r--r--src/main/java/cuchaz/enigma/network/packet/MessageC2SPacket.java39
-rw-r--r--src/main/java/cuchaz/enigma/network/packet/MessageS2CPacket.java36
-rw-r--r--src/main/java/cuchaz/enigma/network/packet/Packet.java15
-rw-r--r--src/main/java/cuchaz/enigma/network/packet/PacketHelper.java135
-rw-r--r--src/main/java/cuchaz/enigma/network/packet/PacketRegistry.java64
-rw-r--r--src/main/java/cuchaz/enigma/network/packet/RemoveMappingC2SPacket.java55
-rw-r--r--src/main/java/cuchaz/enigma/network/packet/RemoveMappingS2CPacket.java40
-rw-r--r--src/main/java/cuchaz/enigma/network/packet/RenameC2SPacket.java64
-rw-r--r--src/main/java/cuchaz/enigma/network/packet/RenameS2CPacket.java48
-rw-r--r--src/main/java/cuchaz/enigma/network/packet/SyncMappingsS2CPacket.java88
-rw-r--r--src/main/java/cuchaz/enigma/network/packet/UserListS2CPacket.java44
23 files changed, 1539 insertions, 0 deletions
diff --git a/src/main/java/cuchaz/enigma/network/DedicatedEnigmaServer.java b/src/main/java/cuchaz/enigma/network/DedicatedEnigmaServer.java
new file mode 100644
index 0000000..2cfe823
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/network/DedicatedEnigmaServer.java
@@ -0,0 +1,164 @@
1package cuchaz.enigma.network;
2
3import com.google.common.io.MoreFiles;
4import cuchaz.enigma.*;
5import cuchaz.enigma.throwables.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;
12
13import java.io.IOException;
14import java.io.PrintWriter;
15import java.nio.file.Files;
16import java.nio.file.Path;
17import java.nio.file.Paths;
18import java.util.concurrent.BlockingQueue;
19import java.util.concurrent.Executors;
20import java.util.concurrent.LinkedBlockingDeque;
21import java.util.concurrent.TimeUnit;
22
23public class DedicatedEnigmaServer extends EnigmaServer {
24
25 private final EnigmaProfile profile;
26 private final MappingFormat mappingFormat;
27 private final Path mappingsFile;
28 private final PrintWriter log;
29 private BlockingQueue<Runnable> tasks = new LinkedBlockingDeque<>();
30
31 public DedicatedEnigmaServer(
32 byte[] jarChecksum,
33 char[] password,
34 EnigmaProfile profile,
35 MappingFormat mappingFormat,
36 Path mappingsFile,
37 PrintWriter log,
38 EntryRemapper mappings,
39 int port
40 ) {
41 super(jarChecksum, password, mappings, port);
42 this.profile = profile;
43 this.mappingFormat = mappingFormat;
44 this.mappingsFile = mappingsFile;
45 this.log = log;
46 }
47
48 @Override
49 protected void runOnThread(Runnable task) {
50 tasks.add(task);
51 }
52
53 @Override
54 public void log(String message) {
55 super.log(message);
56 log.println(message);
57 }
58
59 public static void main(String[] args) {
60 OptionParser parser = new OptionParser();
61
62 OptionSpec<Path> jarOpt = parser.accepts("jar", "Jar file to open at startup")
63 .withRequiredArg()
64 .required()
65 .withValuesConvertedBy(Main.PathConverter.INSTANCE);
66
67 OptionSpec<Path> mappingsOpt = parser.accepts("mappings", "Mappings file to open at startup")
68 .withRequiredArg()
69 .required()
70 .withValuesConvertedBy(Main.PathConverter.INSTANCE);
71
72 OptionSpec<Path> profileOpt = parser.accepts("profile", "Profile json to apply at startup")
73 .withRequiredArg()
74 .withValuesConvertedBy(Main.PathConverter.INSTANCE);
75
76 OptionSpec<Integer> portOpt = parser.accepts("port", "Port to run the server on")
77 .withOptionalArg()
78 .ofType(Integer.class)
79 .defaultsTo(EnigmaServer.DEFAULT_PORT);
80
81 OptionSpec<String> passwordOpt = parser.accepts("password", "The password to join the server")
82 .withRequiredArg()
83 .defaultsTo("");
84
85 OptionSpec<Path> logFileOpt = parser.accepts("log", "The log file to write to")
86 .withRequiredArg()
87 .withValuesConvertedBy(Main.PathConverter.INSTANCE)
88 .defaultsTo(Paths.get("log.txt"));
89
90 OptionSet parsedArgs = parser.parse(args);
91 Path jar = parsedArgs.valueOf(jarOpt);
92 Path mappingsFile = parsedArgs.valueOf(mappingsOpt);
93 Path profileFile = parsedArgs.valueOf(profileOpt);
94 int port = parsedArgs.valueOf(portOpt);
95 char[] password = parsedArgs.valueOf(passwordOpt).toCharArray();
96 if (password.length > EnigmaServer.MAX_PASSWORD_LENGTH) {
97 System.err.println("Password too long, must be at most " + EnigmaServer.MAX_PASSWORD_LENGTH + " characters");
98 System.exit(1);
99 }
100 Path logFile = parsedArgs.valueOf(logFileOpt);
101
102 System.out.println("Starting Enigma server");
103 DedicatedEnigmaServer server;
104 try {
105 byte[] checksum = Utils.zipSha1(parsedArgs.valueOf(jarOpt));
106
107 EnigmaProfile profile = EnigmaProfile.read(profileFile);
108 Enigma enigma = Enigma.builder().setProfile(profile).build();
109 System.out.println("Indexing Jar...");
110 EnigmaProject project = enigma.openJar(jar, ProgressListener.none());
111
112 MappingFormat mappingFormat = MappingFormat.ENIGMA_DIRECTORY;
113 EntryRemapper mappings;
114 if (!Files.exists(mappingsFile)) {
115 mappings = EntryRemapper.empty(project.getJarIndex());
116 } else {
117 System.out.println("Reading mappings...");
118 if (Files.isDirectory(mappingsFile)) {
119 mappingFormat = MappingFormat.ENIGMA_DIRECTORY;
120 } else if ("zip".equalsIgnoreCase(MoreFiles.getFileExtension(mappingsFile))) {
121 mappingFormat = MappingFormat.ENIGMA_ZIP;
122 } else {
123 mappingFormat = MappingFormat.ENIGMA_FILE;
124 }
125 mappings = EntryRemapper.mapped(project.getJarIndex(), mappingFormat.read(mappingsFile, ProgressListener.none(), profile.getMappingSaveParameters()));
126 }
127
128 PrintWriter log = new PrintWriter(Files.newBufferedWriter(logFile));
129
130 server = new DedicatedEnigmaServer(checksum, password, profile, mappingFormat, mappingsFile, log, mappings, port);
131 server.start();
132 System.out.println("Server started");
133 } catch (IOException | MappingParseException e) {
134 System.err.println("Error starting server!");
135 e.printStackTrace();
136 System.exit(1);
137 return;
138 }
139
140 // noinspection RedundantSuppression
141 // noinspection Convert2MethodRef - javac 8 bug
142 Executors.newScheduledThreadPool(1).scheduleAtFixedRate(() -> server.runOnThread(() -> server.saveMappings()), 0, 1, TimeUnit.MINUTES);
143 Runtime.getRuntime().addShutdownHook(new Thread(server::saveMappings));
144
145 while (true) {
146 try {
147 server.tasks.take().run();
148 } catch (InterruptedException e) {
149 break;
150 }
151 }
152 }
153
154 @Override
155 public synchronized void stop() {
156 super.stop();
157 System.exit(0);
158 }
159
160 private void saveMappings() {
161 mappingFormat.write(getMappings().getObfToDeobf(), getMappings().takeMappingDelta(), mappingsFile, ProgressListener.none(), profile.getMappingSaveParameters());
162 log.flush();
163 }
164}
diff --git a/src/main/java/cuchaz/enigma/network/EnigmaClient.java b/src/main/java/cuchaz/enigma/network/EnigmaClient.java
new file mode 100644
index 0000000..bfa53d7
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/network/EnigmaClient.java
@@ -0,0 +1,85 @@
1package cuchaz.enigma.network;
2
3import cuchaz.enigma.gui.GuiController;
4import cuchaz.enigma.network.packet.LoginC2SPacket;
5import cuchaz.enigma.network.packet.Packet;
6import cuchaz.enigma.network.packet.PacketRegistry;
7
8import javax.swing.SwingUtilities;
9import java.io.DataInput;
10import java.io.DataInputStream;
11import java.io.DataOutput;
12import java.io.DataOutputStream;
13import java.io.EOFException;
14import java.io.IOException;
15import java.net.Socket;
16import java.net.SocketException;
17
18public class EnigmaClient {
19
20 private final GuiController controller;
21
22 private final String ip;
23 private final int port;
24 private Socket socket;
25 private DataOutput output;
26
27 public EnigmaClient(GuiController controller, String ip, int port) {
28 this.controller = controller;
29 this.ip = ip;
30 this.port = port;
31 }
32
33 public void connect() throws IOException {
34 socket = new Socket(ip, port);
35 output = new DataOutputStream(socket.getOutputStream());
36 Thread thread = new Thread(() -> {
37 try {
38 DataInput input = new DataInputStream(socket.getInputStream());
39 while (true) {
40 int packetId;
41 try {
42 packetId = input.readUnsignedByte();
43 } catch (EOFException | SocketException e) {
44 break;
45 }
46 Packet<GuiController> packet = PacketRegistry.createS2CPacket(packetId);
47 if (packet == null) {
48 throw new IOException("Received invalid packet id " + packetId);
49 }
50 packet.read(input);
51 SwingUtilities.invokeLater(() -> packet.handle(controller));
52 }
53 } catch (IOException e) {
54 controller.disconnectIfConnected(e.toString());
55 return;
56 }
57 controller.disconnectIfConnected("Disconnected");
58 });
59 thread.setName("Client I/O thread");
60 thread.setDaemon(true);
61 thread.start();
62 }
63
64 public synchronized void disconnect() {
65 if (socket != null && !socket.isClosed()) {
66 try {
67 socket.close();
68 } catch (IOException e1) {
69 System.err.println("Failed to close socket");
70 e1.printStackTrace();
71 }
72 }
73 }
74
75
76 public void sendPacket(Packet<ServerPacketHandler> packet) {
77 try {
78 output.writeByte(PacketRegistry.getC2SId(packet));
79 packet.write(output);
80 } catch (IOException e) {
81 controller.disconnectIfConnected(e.toString());
82 }
83 }
84
85}
diff --git a/src/main/java/cuchaz/enigma/network/EnigmaServer.java b/src/main/java/cuchaz/enigma/network/EnigmaServer.java
new file mode 100644
index 0000000..b0e15a3
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/network/EnigmaServer.java
@@ -0,0 +1,292 @@
1package cuchaz.enigma.network;
2
3import cuchaz.enigma.gui.GuiController;
4import cuchaz.enigma.network.packet.KickS2CPacket;
5import cuchaz.enigma.network.packet.MessageS2CPacket;
6import cuchaz.enigma.network.packet.Packet;
7import cuchaz.enigma.network.packet.PacketRegistry;
8import cuchaz.enigma.network.packet.RemoveMappingS2CPacket;
9import cuchaz.enigma.network.packet.RenameS2CPacket;
10import cuchaz.enigma.network.packet.UserListS2CPacket;
11import cuchaz.enigma.translation.mapping.EntryMapping;
12import cuchaz.enigma.translation.mapping.EntryRemapper;
13import cuchaz.enigma.translation.representation.entry.Entry;
14import cuchaz.enigma.utils.Message;
15
16import java.io.DataInput;
17import java.io.DataInputStream;
18import java.io.DataOutput;
19import java.io.DataOutputStream;
20import java.io.EOFException;
21import java.io.IOException;
22import java.net.ServerSocket;
23import java.net.Socket;
24import java.net.SocketException;
25import java.util.ArrayList;
26import java.util.Collections;
27import java.util.HashMap;
28import java.util.HashSet;
29import java.util.List;
30import java.util.Map;
31import java.util.Set;
32import java.util.concurrent.CopyOnWriteArrayList;
33
34public abstract class EnigmaServer {
35
36 // https://discordapp.com/channels/507304429255393322/566418023372816394/700292322918793347
37 public static final int DEFAULT_PORT = 34712;
38 public static final int PROTOCOL_VERSION = 0;
39 public static final String OWNER_USERNAME = "Owner";
40 public static final int CHECKSUM_SIZE = 20;
41 public static final int MAX_PASSWORD_LENGTH = 255; // length is written as a byte in the login packet
42
43 private final int port;
44 private ServerSocket socket;
45 private List<Socket> clients = new CopyOnWriteArrayList<>();
46 private Map<Socket, String> usernames = new HashMap<>();
47 private Set<Socket> unapprovedClients = new HashSet<>();
48
49 private final byte[] jarChecksum;
50 private final char[] password;
51
52 public static final int DUMMY_SYNC_ID = 0;
53 private final EntryRemapper mappings;
54 private Map<Entry<?>, Integer> syncIds = new HashMap<>();
55 private Map<Integer, Entry<?>> inverseSyncIds = new HashMap<>();
56 private Map<Integer, Set<Socket>> clientsNeedingConfirmation = new HashMap<>();
57 private int nextSyncId = DUMMY_SYNC_ID + 1;
58
59 private static int nextIoId = 0;
60
61 public EnigmaServer(byte[] jarChecksum, char[] password, EntryRemapper mappings, int port) {
62 this.jarChecksum = jarChecksum;
63 this.password = password;
64 this.mappings = mappings;
65 this.port = port;
66 }
67
68 public void start() throws IOException {
69 socket = new ServerSocket(port);
70 log("Server started on " + socket.getInetAddress() + ":" + port);
71 Thread thread = new Thread(() -> {
72 try {
73 while (!socket.isClosed()) {
74 acceptClient();
75 }
76 } catch (SocketException e) {
77 System.out.println("Server closed");
78 } catch (IOException e) {
79 e.printStackTrace();
80 }
81 });
82 thread.setName("Server client listener");
83 thread.setDaemon(true);
84 thread.start();
85 }
86
87 private void acceptClient() throws IOException {
88 Socket client = socket.accept();
89 clients.add(client);
90 Thread thread = new Thread(() -> {
91 try {
92 DataInput input = new DataInputStream(client.getInputStream());
93 while (true) {
94 int packetId;
95 try {
96 packetId = input.readUnsignedByte();
97 } catch (EOFException | SocketException e) {
98 break;
99 }
100 Packet<ServerPacketHandler> packet = PacketRegistry.createC2SPacket(packetId);
101 if (packet == null) {
102 throw new IOException("Received invalid packet id " + packetId);
103 }
104 packet.read(input);
105 runOnThread(() -> packet.handle(new ServerPacketHandler(client, this)));
106 }
107 } catch (IOException e) {
108 kick(client, e.toString());
109 e.printStackTrace();
110 return;
111 }
112 kick(client, "disconnect.disconnected");
113 });
114 thread.setName("Server I/O thread #" + (nextIoId++));
115 thread.setDaemon(true);
116 thread.start();
117 }
118
119 public void stop() {
120 runOnThread(() -> {
121 if (socket != null && !socket.isClosed()) {
122 for (Socket client : clients) {
123 kick(client, "disconnect.server_closed");
124 }
125 try {
126 socket.close();
127 } catch (IOException e) {
128 System.err.println("Failed to close server socket");
129 e.printStackTrace();
130 }
131 }
132 });
133 }
134
135 public void kick(Socket client, String reason) {
136 if (!clients.remove(client)) return;
137
138 sendPacket(client, new KickS2CPacket(reason));
139
140 clientsNeedingConfirmation.values().removeIf(list -> {
141 list.remove(client);
142 return list.isEmpty();
143 });
144 String username = usernames.remove(client);
145 try {
146 client.close();
147 } catch (IOException e) {
148 System.err.println("Failed to close server client socket");
149 e.printStackTrace();
150 }
151
152 if (username != null) {
153 System.out.println("Kicked " + username + " because " + reason);
154 sendMessage(Message.disconnect(username));
155 }
156 sendUsernamePacket();
157 }
158
159 public boolean isUsernameTaken(String username) {
160 return usernames.containsValue(username);
161 }
162
163 public void setUsername(Socket client, String username) {
164 usernames.put(client, username);
165 sendUsernamePacket();
166 }
167
168 private void sendUsernamePacket() {
169 List<String> usernames = new ArrayList<>(this.usernames.values());
170 Collections.sort(usernames);
171 sendToAll(new UserListS2CPacket(usernames));
172 }
173
174 public String getUsername(Socket client) {
175 return usernames.get(client);
176 }
177
178 public void sendPacket(Socket client, Packet<GuiController> packet) {
179 if (!client.isClosed()) {
180 int packetId = PacketRegistry.getS2CId(packet);
181 try {
182 DataOutput output = new DataOutputStream(client.getOutputStream());
183 output.writeByte(packetId);
184 packet.write(output);
185 } catch (IOException e) {
186 if (!(packet instanceof KickS2CPacket)) {
187 kick(client, e.toString());
188 e.printStackTrace();
189 }
190 }
191 }
192 }
193
194 public void sendToAll(Packet<GuiController> packet) {
195 for (Socket client : clients) {
196 sendPacket(client, packet);
197 }
198 }
199
200 public void sendToAllExcept(Socket excluded, Packet<GuiController> packet) {
201 for (Socket client : clients) {
202 if (client != excluded) {
203 sendPacket(client, packet);
204 }
205 }
206 }
207
208 public boolean canModifyEntry(Socket client, Entry<?> entry) {
209 if (unapprovedClients.contains(client)) {
210 return false;
211 }
212
213 Integer syncId = syncIds.get(entry);
214 if (syncId == null) {
215 return true;
216 }
217 Set<Socket> clients = clientsNeedingConfirmation.get(syncId);
218 return clients == null || !clients.contains(client);
219 }
220
221 public int lockEntry(Socket exception, Entry<?> entry) {
222 int syncId = nextSyncId;
223 nextSyncId++;
224 // sync id is sent as an unsigned short, can't have more than 65536
225 if (nextSyncId == 65536) {
226 nextSyncId = DUMMY_SYNC_ID + 1;
227 }
228 Integer oldSyncId = syncIds.get(entry);
229 if (oldSyncId != null) {
230 clientsNeedingConfirmation.remove(oldSyncId);
231 }
232 syncIds.put(entry, syncId);
233 inverseSyncIds.put(syncId, entry);
234 Set<Socket> clients = new HashSet<>(this.clients);
235 clients.remove(exception);
236 clientsNeedingConfirmation.put(syncId, clients);
237 return syncId;
238 }
239
240 public void confirmChange(Socket client, int syncId) {
241 if (usernames.containsKey(client)) {
242 unapprovedClients.remove(client);
243 }
244
245 Set<Socket> clients = clientsNeedingConfirmation.get(syncId);
246 if (clients != null) {
247 clients.remove(client);
248 if (clients.isEmpty()) {
249 clientsNeedingConfirmation.remove(syncId);
250 syncIds.remove(inverseSyncIds.remove(syncId));
251 }
252 }
253 }
254
255 public void sendCorrectMapping(Socket client, Entry<?> entry, boolean refreshClassTree) {
256 EntryMapping oldMapping = mappings.getDeobfMapping(entry);
257 String oldName = oldMapping == null ? null : oldMapping.getTargetName();
258 if (oldName == null) {
259 sendPacket(client, new RemoveMappingS2CPacket(DUMMY_SYNC_ID, entry));
260 } else {
261 sendPacket(client, new RenameS2CPacket(0, entry, oldName, refreshClassTree));
262 }
263 }
264
265 protected abstract void runOnThread(Runnable task);
266
267 public void log(String message) {
268 System.out.println(message);
269 }
270
271 protected boolean isRunning() {
272 return !socket.isClosed();
273 }
274
275 public byte[] getJarChecksum() {
276 return jarChecksum;
277 }
278
279 public char[] getPassword() {
280 return password;
281 }
282
283 public EntryRemapper getMappings() {
284 return mappings;
285 }
286
287 public void sendMessage(Message message) {
288 log(String.format("[MSG] %s", message.translate()));
289 sendToAll(new MessageS2CPacket(message));
290 }
291
292}
diff --git a/src/main/java/cuchaz/enigma/network/IntegratedEnigmaServer.java b/src/main/java/cuchaz/enigma/network/IntegratedEnigmaServer.java
new file mode 100644
index 0000000..21c6825
--- /dev/null
+++ b/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/src/main/java/cuchaz/enigma/network/ServerPacketHandler.java b/src/main/java/cuchaz/enigma/network/ServerPacketHandler.java
new file mode 100644
index 0000000..8618553
--- /dev/null
+++ b/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/src/main/java/cuchaz/enigma/network/packet/ChangeDocsC2SPacket.java b/src/main/java/cuchaz/enigma/network/packet/ChangeDocsC2SPacket.java
new file mode 100644
index 0000000..4d5d86f
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/network/packet/ChangeDocsC2SPacket.java
@@ -0,0 +1,59 @@
1package cuchaz.enigma.network.packet;
2
3import cuchaz.enigma.network.EnigmaServer;
4import cuchaz.enigma.network.ServerPacketHandler;
5import cuchaz.enigma.translation.mapping.EntryMapping;
6import cuchaz.enigma.translation.representation.entry.Entry;
7import cuchaz.enigma.utils.Message;
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/src/main/java/cuchaz/enigma/network/packet/ChangeDocsS2CPacket.java b/src/main/java/cuchaz/enigma/network/packet/ChangeDocsS2CPacket.java
new file mode 100644
index 0000000..bf5b7cb
--- /dev/null
+++ b/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.gui.GuiController;
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<GuiController> {
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(GuiController controller) {
41 controller.changeDocs(new EntryReference<>(entry, entry.getName()), newDocs, false);
42 controller.sendPacket(new ConfirmChangeC2SPacket(syncId));
43 }
44}
diff --git a/src/main/java/cuchaz/enigma/network/packet/ConfirmChangeC2SPacket.java b/src/main/java/cuchaz/enigma/network/packet/ConfirmChangeC2SPacket.java
new file mode 100644
index 0000000..78ef964
--- /dev/null
+++ b/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/src/main/java/cuchaz/enigma/network/packet/KickS2CPacket.java b/src/main/java/cuchaz/enigma/network/packet/KickS2CPacket.java
new file mode 100644
index 0000000..bd007d3
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/network/packet/KickS2CPacket.java
@@ -0,0 +1,33 @@
1package cuchaz.enigma.network.packet;
2
3import cuchaz.enigma.gui.GuiController;
4
5import java.io.DataInput;
6import java.io.DataOutput;
7import java.io.IOException;
8
9public class KickS2CPacket implements Packet<GuiController> {
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(GuiController controller) {
31 controller.disconnectIfConnected(reason);
32 }
33}
diff --git a/src/main/java/cuchaz/enigma/network/packet/LoginC2SPacket.java b/src/main/java/cuchaz/enigma/network/packet/LoginC2SPacket.java
new file mode 100644
index 0000000..722cbbf
--- /dev/null
+++ b/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.utils.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/src/main/java/cuchaz/enigma/network/packet/MarkDeobfuscatedC2SPacket.java b/src/main/java/cuchaz/enigma/network/packet/MarkDeobfuscatedC2SPacket.java
new file mode 100644
index 0000000..98d20d9
--- /dev/null
+++ b/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.utils.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/src/main/java/cuchaz/enigma/network/packet/MarkDeobfuscatedS2CPacket.java b/src/main/java/cuchaz/enigma/network/packet/MarkDeobfuscatedS2CPacket.java
new file mode 100644
index 0000000..b7d6eda
--- /dev/null
+++ b/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.gui.GuiController;
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<GuiController> {
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(GuiController controller) {
37 controller.markAsDeobfuscated(new EntryReference<>(entry, entry.getName()), false);
38 controller.sendPacket(new ConfirmChangeC2SPacket(syncId));
39 }
40}
diff --git a/src/main/java/cuchaz/enigma/network/packet/MessageC2SPacket.java b/src/main/java/cuchaz/enigma/network/packet/MessageC2SPacket.java
new file mode 100644
index 0000000..b8e0f14
--- /dev/null
+++ b/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.utils.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/src/main/java/cuchaz/enigma/network/packet/MessageS2CPacket.java b/src/main/java/cuchaz/enigma/network/packet/MessageS2CPacket.java
new file mode 100644
index 0000000..edeaae0
--- /dev/null
+++ b/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.gui.GuiController;
8import cuchaz.enigma.utils.Message;
9
10public class MessageS2CPacket implements Packet<GuiController> {
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(GuiController handler) {
33 handler.addMessage(message);
34 }
35
36}
diff --git a/src/main/java/cuchaz/enigma/network/packet/Packet.java b/src/main/java/cuchaz/enigma/network/packet/Packet.java
new file mode 100644
index 0000000..2f16dfb
--- /dev/null
+++ b/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/src/main/java/cuchaz/enigma/network/packet/PacketHelper.java b/src/main/java/cuchaz/enigma/network/packet/PacketHelper.java
new file mode 100644
index 0000000..464606e
--- /dev/null
+++ b/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/src/main/java/cuchaz/enigma/network/packet/PacketRegistry.java b/src/main/java/cuchaz/enigma/network/packet/PacketRegistry.java
new file mode 100644
index 0000000..ba5d9de
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/network/packet/PacketRegistry.java
@@ -0,0 +1,64 @@
1package cuchaz.enigma.network.packet;
2
3import cuchaz.enigma.gui.GuiController;
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<GuiController>>, Integer> s2cPacketIds = new HashMap<>();
15 private static final Map<Integer, Supplier<? extends Packet<GuiController>>> 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<GuiController>> 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<GuiController> packet) {
56 return s2cPacketIds.get(packet.getClass());
57 }
58
59 public static Packet<GuiController> createS2CPacket(int id) {
60 Supplier<? extends Packet<GuiController>> creator = s2cPacketCreators.get(id);
61 return creator == null ? null : creator.get();
62 }
63
64}
diff --git a/src/main/java/cuchaz/enigma/network/packet/RemoveMappingC2SPacket.java b/src/main/java/cuchaz/enigma/network/packet/RemoveMappingC2SPacket.java
new file mode 100644
index 0000000..a3f3d91
--- /dev/null
+++ b/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.throwables.IllegalNameException;
5import cuchaz.enigma.translation.representation.entry.Entry;
6import cuchaz.enigma.utils.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/src/main/java/cuchaz/enigma/network/packet/RemoveMappingS2CPacket.java b/src/main/java/cuchaz/enigma/network/packet/RemoveMappingS2CPacket.java
new file mode 100644
index 0000000..7bb1b00
--- /dev/null
+++ b/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.gui.GuiController;
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<GuiController> {
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(GuiController controller) {
37 controller.removeMapping(new EntryReference<>(entry, entry.getName()), false);
38 controller.sendPacket(new ConfirmChangeC2SPacket(syncId));
39 }
40}
diff --git a/src/main/java/cuchaz/enigma/network/packet/RenameC2SPacket.java b/src/main/java/cuchaz/enigma/network/packet/RenameC2SPacket.java
new file mode 100644
index 0000000..03e95d6
--- /dev/null
+++ b/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.throwables.IllegalNameException;
5import cuchaz.enigma.translation.mapping.EntryMapping;
6import cuchaz.enigma.translation.representation.entry.Entry;
7import cuchaz.enigma.utils.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/src/main/java/cuchaz/enigma/network/packet/RenameS2CPacket.java b/src/main/java/cuchaz/enigma/network/packet/RenameS2CPacket.java
new file mode 100644
index 0000000..058f0e5
--- /dev/null
+++ b/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.gui.GuiController;
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<GuiController> {
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(GuiController controller) {
45 controller.rename(new EntryReference<>(entry, entry.getName()), newName, refreshClassTree, false);
46 controller.sendPacket(new ConfirmChangeC2SPacket(syncId));
47 }
48}
diff --git a/src/main/java/cuchaz/enigma/network/packet/SyncMappingsS2CPacket.java b/src/main/java/cuchaz/enigma/network/packet/SyncMappingsS2CPacket.java
new file mode 100644
index 0000000..e6378d1
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/network/packet/SyncMappingsS2CPacket.java
@@ -0,0 +1,88 @@
1package cuchaz.enigma.network.packet;
2
3import cuchaz.enigma.gui.GuiController;
4import cuchaz.enigma.network.EnigmaServer;
5import cuchaz.enigma.translation.mapping.EntryMapping;
6import cuchaz.enigma.translation.mapping.tree.EntryTree;
7import cuchaz.enigma.translation.mapping.tree.EntryTreeNode;
8import cuchaz.enigma.translation.mapping.tree.HashEntryTree;
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<GuiController> {
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(GuiController controller) {
85 controller.openMappings(mappings);
86 controller.sendPacket(new ConfirmChangeC2SPacket(EnigmaServer.DUMMY_SYNC_ID));
87 }
88}
diff --git a/src/main/java/cuchaz/enigma/network/packet/UserListS2CPacket.java b/src/main/java/cuchaz/enigma/network/packet/UserListS2CPacket.java
new file mode 100644
index 0000000..8904848
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/network/packet/UserListS2CPacket.java
@@ -0,0 +1,44 @@
1package cuchaz.enigma.network.packet;
2
3import java.io.DataInput;
4import java.io.DataOutput;
5import java.io.IOException;
6import java.util.ArrayList;
7import java.util.List;
8
9import cuchaz.enigma.gui.GuiController;
10
11public class UserListS2CPacket implements Packet<GuiController> {
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(GuiController handler) {
41 handler.updateUserList(users);
42 }
43
44}