2017-03-01 23:02:46 +01:00
|
|
|
/**
|
|
|
|
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
|
|
|
* Copyright (C) 2017 Dan Mulloy
|
|
|
|
*
|
|
|
|
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
|
|
|
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
|
|
|
* the License, or (at your option) any later version.
|
|
|
|
*
|
|
|
|
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
|
|
|
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
|
|
* See the GNU General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU General Public License along with this program;
|
|
|
|
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
|
|
|
* 02111-1307 USA
|
|
|
|
*/
|
|
|
|
package com.comphenix.protocol;
|
|
|
|
|
2017-03-08 15:08:29 +01:00
|
|
|
import java.io.ByteArrayOutputStream;
|
2017-03-01 23:02:46 +01:00
|
|
|
import java.io.File;
|
|
|
|
import java.io.IOException;
|
|
|
|
import java.text.MessageFormat;
|
|
|
|
import java.text.SimpleDateFormat;
|
|
|
|
import java.util.ArrayList;
|
|
|
|
import java.util.List;
|
2017-05-24 20:35:22 +02:00
|
|
|
import java.util.logging.*;
|
2017-03-01 23:02:46 +01:00
|
|
|
|
|
|
|
import com.comphenix.protocol.PacketType.Protocol;
|
|
|
|
import com.comphenix.protocol.PacketType.Sender;
|
|
|
|
import com.comphenix.protocol.events.ListeningWhitelist;
|
|
|
|
import com.comphenix.protocol.events.PacketEvent;
|
|
|
|
import com.comphenix.protocol.events.PacketListener;
|
|
|
|
import com.comphenix.protocol.injector.netty.WirePacket;
|
2017-03-08 15:08:29 +01:00
|
|
|
import com.google.common.base.Charsets;
|
2017-03-01 23:02:46 +01:00
|
|
|
|
2017-05-24 20:35:22 +02:00
|
|
|
import org.bukkit.ChatColor;
|
|
|
|
import org.bukkit.command.Command;
|
|
|
|
import org.bukkit.command.CommandExecutor;
|
|
|
|
import org.bukkit.command.CommandSender;
|
2019-05-04 05:22:44 +02:00
|
|
|
import org.bukkit.craftbukkit.libs.org.apache.commons.io.HexDump;
|
2017-05-24 20:35:22 +02:00
|
|
|
import org.bukkit.plugin.Plugin;
|
|
|
|
|
2017-03-01 23:02:46 +01:00
|
|
|
/**
|
|
|
|
* Logs packets to a given stream
|
|
|
|
* @author dmulloy2
|
|
|
|
*/
|
|
|
|
public class PacketLogging implements CommandExecutor, PacketListener {
|
|
|
|
public static final String NAME = "packetlog";
|
|
|
|
|
|
|
|
private List<PacketType> sendingTypes = new ArrayList<>();
|
|
|
|
private List<PacketType> receivingTypes = new ArrayList<>();
|
|
|
|
|
|
|
|
private ListeningWhitelist sendingWhitelist;
|
|
|
|
private ListeningWhitelist receivingWhitelist;
|
|
|
|
|
|
|
|
private Logger fileLogger;
|
|
|
|
private LogLocation location = LogLocation.FILE;
|
|
|
|
|
|
|
|
private final ProtocolManager manager;
|
|
|
|
private final Plugin plugin;
|
|
|
|
|
|
|
|
PacketLogging(Plugin plugin, ProtocolManager manager) {
|
|
|
|
this.plugin = plugin;
|
|
|
|
this.manager = manager;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
|
|
|
|
PacketType type = null;
|
|
|
|
|
2017-03-09 16:53:06 +01:00
|
|
|
try {
|
|
|
|
if (args.length > 2) {
|
|
|
|
Protocol protocol;
|
2017-03-01 23:02:46 +01:00
|
|
|
|
2017-03-09 16:53:06 +01:00
|
|
|
try {
|
|
|
|
protocol = Protocol.valueOf(args[0].toUpperCase());
|
|
|
|
} catch (IllegalArgumentException ex) {
|
|
|
|
sender.sendMessage(ChatColor.RED + "Unknown protocol " + args[0]);
|
|
|
|
return true;
|
|
|
|
}
|
2017-03-01 23:02:46 +01:00
|
|
|
|
2017-03-09 16:53:06 +01:00
|
|
|
Sender pSender;
|
2017-03-01 23:02:46 +01:00
|
|
|
|
2017-03-09 16:53:06 +01:00
|
|
|
try {
|
|
|
|
pSender = Sender.valueOf(args[1].toUpperCase());
|
|
|
|
} catch (IllegalArgumentException ex) {
|
|
|
|
sender.sendMessage(ChatColor.RED + "Unknown sender: " + args[1]);
|
|
|
|
return true;
|
|
|
|
}
|
2017-03-01 23:02:46 +01:00
|
|
|
|
|
|
|
try {
|
2017-03-21 02:42:46 +01:00
|
|
|
try { // Try IDs first
|
2017-03-09 16:53:06 +01:00
|
|
|
int id = Integer.parseInt(args[2]);
|
|
|
|
type = PacketType.findCurrent(protocol, pSender, id);
|
2017-03-21 02:42:46 +01:00
|
|
|
} catch (NumberFormatException ex) { // Check packet names
|
2017-03-09 16:53:06 +01:00
|
|
|
String name = args[2];
|
2017-05-24 20:35:22 +02:00
|
|
|
for (PacketType packet : PacketType.values()) {
|
2017-03-09 16:53:06 +01:00
|
|
|
if (packet.getProtocol() == protocol && packet.getSender() == pSender) {
|
|
|
|
if (packet.name().equalsIgnoreCase(name)) {
|
2017-03-08 15:08:29 +01:00
|
|
|
type = packet;
|
2017-05-24 20:35:22 +02:00
|
|
|
break;
|
2017-03-08 15:08:29 +01:00
|
|
|
}
|
2017-03-09 16:53:06 +01:00
|
|
|
for (String className : packet.getClassNames()) {
|
|
|
|
if (className.equalsIgnoreCase(name)) {
|
|
|
|
type = packet;
|
2017-05-24 20:35:22 +02:00
|
|
|
break;
|
2017-03-09 16:53:06 +01:00
|
|
|
}
|
|
|
|
}
|
2017-03-08 15:08:29 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2017-03-21 02:42:46 +01:00
|
|
|
} catch (IllegalArgumentException ex) { // RIP
|
2017-03-09 16:53:06 +01:00
|
|
|
type = null;
|
2017-03-01 23:02:46 +01:00
|
|
|
}
|
|
|
|
|
2017-03-09 16:53:06 +01:00
|
|
|
if (type == null) {
|
|
|
|
sender.sendMessage(ChatColor.RED + "Unknown packet: " + args[2]);
|
|
|
|
return true;
|
2017-03-01 23:02:46 +01:00
|
|
|
}
|
|
|
|
|
2017-03-09 16:53:06 +01:00
|
|
|
if (args.length > 3) {
|
|
|
|
if (args[3].equalsIgnoreCase("console")) {
|
|
|
|
this.location = LogLocation.CONSOLE;
|
|
|
|
} else {
|
|
|
|
this.location = LogLocation.FILE;
|
|
|
|
}
|
2017-03-01 23:02:46 +01:00
|
|
|
}
|
2017-03-09 16:53:06 +01:00
|
|
|
|
|
|
|
if (pSender == Sender.CLIENT) {
|
|
|
|
if (receivingTypes.contains(type)) {
|
|
|
|
receivingTypes.remove(type);
|
|
|
|
} else {
|
|
|
|
receivingTypes.add(type);
|
|
|
|
}
|
2017-03-01 23:02:46 +01:00
|
|
|
} else {
|
2017-03-09 16:53:06 +01:00
|
|
|
if (sendingTypes.contains(type)) {
|
|
|
|
sendingTypes.remove(type);
|
|
|
|
} else {
|
|
|
|
sendingTypes.add(type);
|
|
|
|
}
|
2017-03-01 23:02:46 +01:00
|
|
|
}
|
2017-03-09 16:53:06 +01:00
|
|
|
|
|
|
|
startLogging();
|
|
|
|
sender.sendMessage(ChatColor.GREEN + "Now logging " + type.getPacketClass().getSimpleName());
|
|
|
|
return true;
|
2017-03-01 23:02:46 +01:00
|
|
|
}
|
|
|
|
|
2017-03-09 16:53:06 +01:00
|
|
|
sender.sendMessage(ChatColor.RED + "Invalid syntax: /packetlog <protocol> <sender> <packet> [location]");
|
|
|
|
return true;
|
|
|
|
} catch (Throwable ex) {
|
|
|
|
sender.sendMessage(ChatColor.RED + "Failed to parse command: " + ex.toString());
|
2017-03-01 23:02:46 +01:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private void startLogging() {
|
|
|
|
manager.removePacketListener(this);
|
|
|
|
|
|
|
|
if (sendingTypes.isEmpty() && receivingTypes.isEmpty()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
this.sendingWhitelist = ListeningWhitelist.newBuilder().types(sendingTypes).build();
|
|
|
|
this.receivingWhitelist = ListeningWhitelist.newBuilder().types(receivingTypes).build();
|
|
|
|
|
2017-03-21 02:42:46 +01:00
|
|
|
// Setup the file logger if it hasn't been already
|
2017-03-01 23:02:46 +01:00
|
|
|
if (location == LogLocation.FILE && fileLogger == null) {
|
|
|
|
fileLogger = Logger.getLogger("ProtocolLib-FileLogging");
|
|
|
|
|
|
|
|
for (Handler handler : fileLogger.getHandlers())
|
|
|
|
fileLogger.removeHandler(handler);
|
|
|
|
fileLogger.setUseParentHandlers(false);
|
|
|
|
|
|
|
|
try {
|
|
|
|
File logFile = new File(plugin.getDataFolder(), "log.log");
|
|
|
|
FileHandler handler = new FileHandler(logFile.getAbsolutePath(), true);
|
|
|
|
handler.setFormatter(new LogFormatter());
|
|
|
|
fileLogger.addHandler(handler);
|
|
|
|
} catch (IOException ex) {
|
|
|
|
plugin.getLogger().log(Level.SEVERE, "Failed to obtain log file:", ex);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
manager.addPacketListener(this);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void onPacketSending(PacketEvent event) {
|
|
|
|
log(event);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void onPacketReceiving(PacketEvent event) {
|
|
|
|
log(event);
|
|
|
|
}
|
|
|
|
|
2017-03-21 02:42:46 +01:00
|
|
|
// Here's where the magic happens
|
|
|
|
|
2017-03-08 15:08:29 +01:00
|
|
|
private static String hexDump(byte[] bytes) throws IOException {
|
|
|
|
try (ByteArrayOutputStream output = new ByteArrayOutputStream()) {
|
|
|
|
HexDump.dump(bytes, 0, output, 0);
|
|
|
|
return new String(output.toByteArray(), Charsets.UTF_8);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-03-01 23:02:46 +01:00
|
|
|
private void log(PacketEvent event) {
|
2017-03-08 15:08:29 +01:00
|
|
|
try {
|
2017-03-21 02:42:46 +01:00
|
|
|
byte[] bytes = WirePacket.bytesFromPacket(event.getPacket());
|
|
|
|
String hexDump = hexDump(bytes);
|
2017-03-09 16:53:06 +01:00
|
|
|
|
|
|
|
if (location == LogLocation.FILE) {
|
|
|
|
fileLogger.log(Level.INFO, event.getPacketType() + ":");
|
|
|
|
fileLogger.log(Level.INFO, hexDump);
|
|
|
|
fileLogger.log(Level.INFO, "");
|
|
|
|
} else {
|
|
|
|
System.out.println(event.getPacketType() + ":");
|
|
|
|
System.out.println(hexDump);
|
|
|
|
System.out.println();
|
|
|
|
}
|
2017-03-08 15:08:29 +01:00
|
|
|
} catch (Throwable ex) {
|
2017-03-09 16:53:06 +01:00
|
|
|
plugin.getLogger().log(Level.WARNING, "Failed to log packet " + event.getPacketType() + ":", ex);
|
|
|
|
plugin.getLogger().log(Level.WARNING, "Clearing packet logger...");
|
2017-03-01 23:02:46 +01:00
|
|
|
|
2017-03-09 16:53:06 +01:00
|
|
|
sendingTypes.clear();
|
|
|
|
receivingTypes.clear();
|
|
|
|
startLogging();
|
2017-03-01 23:02:46 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public ListeningWhitelist getSendingWhitelist() {
|
|
|
|
return sendingWhitelist;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public ListeningWhitelist getReceivingWhitelist() {
|
|
|
|
return receivingWhitelist;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public Plugin getPlugin() {
|
|
|
|
return plugin;
|
|
|
|
}
|
|
|
|
|
2017-05-24 20:35:22 +02:00
|
|
|
private enum LogLocation {
|
|
|
|
CONSOLE, FILE
|
2017-03-01 23:02:46 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
private static class LogFormatter extends Formatter {
|
|
|
|
private static final SimpleDateFormat DATE = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
|
|
|
private static final String LINE_SEPARATOR = System.getProperty("line.separator");
|
|
|
|
private static final String FORMAT = "[{0}] {1}";
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public String format(LogRecord record) {
|
|
|
|
String string = formatMessage(record);
|
|
|
|
if (string.isEmpty()) {
|
|
|
|
return LINE_SEPARATOR;
|
|
|
|
}
|
|
|
|
|
|
|
|
StringBuilder message = new StringBuilder();
|
|
|
|
message.append(MessageFormat.format(FORMAT, DATE.format(record.getMillis()), string));
|
|
|
|
message.append(LINE_SEPARATOR);
|
|
|
|
return message.toString();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|