summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore7
-rw-r--r--pantheon-bugs.pcapngbin0 -> 843512 bytes
-rw-r--r--pantheon-combat.pcapngbin0 -> 20223452 bytes
-rw-r--r--pantheon-harvesting.pcapngbin0 -> 1341940 bytes
-rw-r--r--pantheon-login.pcapngbin0 -> 7685016 bytes
-rw-r--r--pantheon-long-extended.pcapngbin0 -> 142722232 bytes
-rw-r--r--pantheon-long.pcapngbin0 -> 24115228 bytes
-rw-r--r--pom.xml117
-rwxr-xr-xrun.sh3
-rw-r--r--src/main/java/org/challman/pantheon_parser/App.java398
-rw-r--r--src/main/java/org/challman/pantheon_parser/Entity.java10
-rw-r--r--src/main/java/org/challman/pantheon_parser/EntityMap.java355
-rw-r--r--src/main/java/org/challman/pantheon_parser/algorithm/AhoCorasick.java164
-rw-r--r--src/main/java/org/challman/pantheon_parser/algorithm/BooyerMoore.java194
-rw-r--r--src/test/java/org/challman/pantheon_parser/AppTest.java19
15 files changed, 1267 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..9842b38
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,7 @@
+.classpath
+.idea/*
+.mvn/
+.project
+.settings/*
+target/*
+*~
diff --git a/pantheon-bugs.pcapng b/pantheon-bugs.pcapng
new file mode 100644
index 0000000..062b2a8
--- /dev/null
+++ b/pantheon-bugs.pcapng
Binary files differ
diff --git a/pantheon-combat.pcapng b/pantheon-combat.pcapng
new file mode 100644
index 0000000..5ba3dbf
--- /dev/null
+++ b/pantheon-combat.pcapng
Binary files differ
diff --git a/pantheon-harvesting.pcapng b/pantheon-harvesting.pcapng
new file mode 100644
index 0000000..fddf838
--- /dev/null
+++ b/pantheon-harvesting.pcapng
Binary files differ
diff --git a/pantheon-login.pcapng b/pantheon-login.pcapng
new file mode 100644
index 0000000..75311aa
--- /dev/null
+++ b/pantheon-login.pcapng
Binary files differ
diff --git a/pantheon-long-extended.pcapng b/pantheon-long-extended.pcapng
new file mode 100644
index 0000000..627a75f
--- /dev/null
+++ b/pantheon-long-extended.pcapng
Binary files differ
diff --git a/pantheon-long.pcapng b/pantheon-long.pcapng
new file mode 100644
index 0000000..fe09a70
--- /dev/null
+++ b/pantheon-long.pcapng
Binary files differ
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..c00080f
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,117 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <groupId>org.challman.pantheon_parser</groupId>
+ <artifactId>pantheon-parser</artifactId>
+ <version>1.0-SNAPSHOT</version>
+
+ <name>pantheon-parser</name>
+ <!-- FIXME change it to the project's website -->
+ <url>http://www.example.com</url>
+
+ <properties>
+ <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+ <maven.compiler.release>22</maven.compiler.release>
+ </properties>
+
+ <dependencyManagement>
+ <dependencies>
+ <dependency>
+ <groupId>org.junit</groupId>
+ <artifactId>junit-bom</artifactId>
+ <version>5.11.0</version>
+ <type>pom</type>
+ <scope>import</scope>
+ </dependency>
+ </dependencies>
+ </dependencyManagement>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.junit.jupiter</groupId>
+ <artifactId>junit-jupiter-api</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <!-- Optionally: parameterized tests support -->
+ <dependency>
+ <groupId>org.junit.jupiter</groupId>
+ <artifactId>junit-jupiter-params</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.slytechs.jnet.jnetpcap</groupId>
+ <artifactId>jnetpcap-wrapper</artifactId>
+ <version>2.3.1</version>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
+ <plugins>
+ <!-- clean lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#clean_Lifecycle -->
+ <plugin>
+ <artifactId>maven-clean-plugin</artifactId>
+ <version>3.4.0</version>
+ </plugin>
+ <!-- default lifecycle, jar packaging: see https://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_jar_packaging -->
+ <plugin>
+ <artifactId>maven-resources-plugin</artifactId>
+ <version>3.3.1</version>
+ </plugin>
+ <plugin>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <version>3.13.0</version>
+ </plugin>
+ <plugin>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <version>3.3.0</version>
+ </plugin>
+ <plugin>
+ <artifactId>maven-jar-plugin</artifactId>
+ <version>3.4.2</version>
+ </plugin>
+ <plugin>
+ <artifactId>maven-install-plugin</artifactId>
+ <version>3.1.2</version>
+ </plugin>
+ <plugin>
+ <artifactId>maven-deploy-plugin</artifactId>
+ <version>3.1.2</version>
+ </plugin>
+ <!-- site lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#site_Lifecycle -->
+ <plugin>
+ <artifactId>maven-site-plugin</artifactId>
+ <version>3.12.1</version>
+ </plugin>
+ <plugin>
+ <artifactId>maven-project-info-reports-plugin</artifactId>
+ <version>3.6.1</version>
+ </plugin>
+ </plugins>
+ </pluginManagement>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-shade-plugin</artifactId>
+ <version>3.4.1</version>
+ <executions>
+ <execution>
+ <phase>package</phase>
+ <goals>
+ <goal>shade</goal>
+ </goals>
+ <configuration>
+ <transformers>
+ <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
+ <mainClass>org.challman.pantheon_parser.App</mainClass>
+ </transformer>
+ </transformers>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+</project>
diff --git a/run.sh b/run.sh
new file mode 100755
index 0000000..b5d914e
--- /dev/null
+++ b/run.sh
@@ -0,0 +1,3 @@
+#!/bin/bash
+
+mvn clean compile && mvn exec:java -Dexec.mainClass="org.challman.pantheon_parser.App"
diff --git a/src/main/java/org/challman/pantheon_parser/App.java b/src/main/java/org/challman/pantheon_parser/App.java
new file mode 100644
index 0000000..4d6999f
--- /dev/null
+++ b/src/main/java/org/challman/pantheon_parser/App.java
@@ -0,0 +1,398 @@
+package org.challman.pantheon_parser;
+
+import org.challman.pantheon_parser.algorithm.BooyerMoore.MatchResult;
+import org.challman.pantheon_parser.algorithm.BooyerMoore;
+import org.jnetpcap.BpFilter;
+import org.jnetpcap.Pcap;
+import org.jnetpcap.PcapException;
+import org.jnetpcap.PcapHeader;
+import org.jnetpcap.PcapIf;
+
+import javax.swing.*;
+import java.awt.*;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.charset.StandardCharsets;
+import java.sql.Time;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.BiConsumer;
+
+public class App {
+ private static HashMap<String, String> entityDict = new HashMap<>();
+ private static EntityMap entityMap;
+ private static Entity player = new Entity();
+
+ private static void debugDuplicateCoordinates() {
+ System.out.println("=== DEBUG: Checking for duplicate coordinates ===");
+
+ // Get all entities from entityMap (you'll need to add a method to EntityMap)
+ Collection<Entity> allEntities = entityMap.getAllEntities(); // You'll need to implement this
+
+ if (allEntities == null || allEntities.isEmpty()) {
+ System.out.println("No entities found");
+ return;
+ }
+
+ // Group entities by coordinates
+ Map<String, List<Entity>> coordMap = new HashMap<>();
+
+ for (Entity entity : allEntities) {
+ String coordKey = String.format("%.2f,%.2f,%.2f", entity.x, entity.y, entity.z);
+ coordMap.computeIfAbsent(coordKey, k -> new ArrayList<>()).add(entity);
+ }
+
+ // Find and report duplicates
+ boolean foundDuplicates = false;
+ for (Map.Entry<String, List<Entity>> entry : coordMap.entrySet()) {
+ if (entry.getValue().size() > 1) {
+ foundDuplicates = true;
+ System.out.println("DUPLICATE COORDINATES: " + entry.getKey());
+ for (Entity entity : entry.getValue()) {
+ System.out.println(" - " + entity.name + " (ID: " + entity.id + ") " +
+ "lastUpdated: " + entity.lastUpdated);
+ }
+ }
+ }
+
+ if (!foundDuplicates) {
+ System.out.println("No duplicate coordinates found");
+ }
+
+ // Also check for entities with same ID but different objects
+ debugSameIdDifferentObjects();
+
+ System.out.println("Total entities: " + allEntities.size());
+ System.out.println("Unique coordinates: " + coordMap.size());
+ System.out.println("=== END DEBUG ===");
+ }
+
+ private static void debugSameIdDifferentObjects() {
+ System.out.println("=== DEBUG: Checking for same ID, different objects ===");
+
+ Collection<Entity> allEntities = entityMap.getAllEntities();
+ Map<String, Set<Integer>> idMap = new HashMap<>();
+
+ for (Entity entity : allEntities) {
+ String idHash = entity.id + "|" + System.identityHashCode(entity);
+ idMap.computeIfAbsent(entity.id, k -> new HashSet<>()).add(System.identityHashCode(entity));
+ }
+
+ boolean foundDuplicates = false;
+ for (Map.Entry<String, Set<Integer>> entry : idMap.entrySet()) {
+ if (entry.getValue().size() > 1) {
+ foundDuplicates = true;
+ System.out.println("DUPLICATE ENTITY ID WITH DIFFERENT OBJECTS: " + entry.getKey());
+ System.out.println(" Object hashes: " + entry.getValue());
+
+ // Show details of each duplicate
+ for (Entity entity : allEntities) {
+ if (entity.id.equals(entry.getKey())) {
+ System.out.println(" - " + entity.name + " @ " +
+ String.format("(%.2f,%.2f,%.2f)", entity.x, entity.y, entity.z) +
+ " lastUpdated: " + entity.lastUpdated +
+ " objHash: " + System.identityHashCode(entity));
+ }
+ }
+ }
+ }
+
+ if (!foundDuplicates) {
+ System.out.println("No duplicate entity objects found");
+ }
+ System.out.println("=== END DEBUG ===");
+ }
+
+ private static float bytesToHeading(byte[] headingBytes) {
+ // Convert 2 bytes to 16-bit unsigned integer
+ int headingInt = ((headingBytes[1] & 0xFF) << 8) | (headingBytes[0] & 0xFF);
+ // Convert to degrees (0-360)
+ return (headingInt / 65535.0f) * 360.0f;
+ }
+
+ private static void entityLocation(byte[] packet, Integer endIndex) {
+ byte[] entity_id = Arrays.copyOfRange(packet, endIndex + 1,endIndex + 6);
+ byte[] bytes_x = Arrays.copyOfRange(packet, endIndex + 21,endIndex + 25);
+ byte[] bytes_y = Arrays.copyOfRange(packet, endIndex + 26,endIndex + 30);
+ byte[] bytes_z = Arrays.copyOfRange(packet, endIndex + 31,endIndex + 35);
+
+ float value_x = ByteBuffer.wrap(bytes_x).order(ByteOrder.LITTLE_ENDIAN).getFloat();
+ float value_y = ByteBuffer.wrap(bytes_y).order(ByteOrder.LITTLE_ENDIAN).getFloat();
+ float value_z = ByteBuffer.wrap(bytes_z).order(ByteOrder.LITTLE_ENDIAN).getFloat();
+
+ String entityIdHex = java.util.HexFormat.of().withUpperCase().formatHex(entity_id);
+ String name = entityDict.getOrDefault(entityIdHex, entityIdHex);
+
+ Entity entity = entityMap.getEntity(entityIdHex);
+ if (entity == null) {
+ entity = new Entity();
+ entity.id = entityIdHex;
+ entity.name = name;
+ }
+
+ if (!name.equals(entityIdHex)) {
+ entity.name = name;
+ }
+
+ entity.x = value_x;
+ entity.y = value_y;
+ entity.z = value_z;
+ entity.lastUpdated = System.currentTimeMillis();
+
+ entityMap.updateEntity(entity, player);
+
+ String output = String.format(
+ "entity location - entity id: %s x: %s %.2f y: %s %.2f z: %s %.2f",
+ name,
+ java.util.HexFormat.of().withUpperCase().formatHex(bytes_x), value_x,
+ java.util.HexFormat.of().withUpperCase().formatHex(bytes_y), value_y,
+ java.util.HexFormat.of().withUpperCase().formatHex(bytes_z), value_z
+ );
+// System.out.println(output);
+ }
+
+ private static void playerLocation(byte[] packet, Integer endIndex) {
+ byte[] entity_id = Arrays.copyOfRange(packet, endIndex + 1,endIndex + 6);
+ byte[] bytes_heading = Arrays.copyOfRange(packet, endIndex + 10,endIndex + 12);
+ byte[] bytes_x = Arrays.copyOfRange(packet, endIndex + 13,endIndex + 17);
+ byte[] bytes_y = Arrays.copyOfRange(packet, endIndex + 18,endIndex + 22);
+ byte[] bytes_z = Arrays.copyOfRange(packet, endIndex + 23,endIndex + 27);
+
+
+ float value_x = ByteBuffer.wrap(bytes_x).order(ByteOrder.LITTLE_ENDIAN).getFloat();
+ float value_y = ByteBuffer.wrap(bytes_y).order(ByteOrder.LITTLE_ENDIAN).getFloat();
+ float value_z = ByteBuffer.wrap(bytes_z).order(ByteOrder.LITTLE_ENDIAN).getFloat();
+ float heading = bytesToHeading(bytes_heading);
+
+ player.id = java.util.HexFormat.of().withUpperCase().formatHex(entity_id);
+ player.name = java.util.HexFormat.of().withUpperCase().formatHex(entity_id);
+ player.x = value_x;
+ player.y = value_y;
+ player.z = value_z;
+ player.heading = heading;
+
+ String output = String.format(
+ "player location - entity id: %s x: %s %.2f y: %s %.2f z: %s %.2f",
+ java.util.HexFormat.of().withUpperCase().formatHex(entity_id),
+ java.util.HexFormat.of().withUpperCase().formatHex(bytes_x), value_x,
+ java.util.HexFormat.of().withUpperCase().formatHex(bytes_y), value_y,
+ java.util.HexFormat.of().withUpperCase().formatHex(bytes_z), value_z
+ );
+// System.out.println(output);
+ }
+
+ private static void entitySpawn(byte[] packet, Integer endIndex) {
+ if (packet[endIndex + 7] == (byte) 0x01 || packet[endIndex + 7] == (byte) 0x04) {
+ return;
+ }
+
+ Integer offset = 57;
+
+ Integer entityType = packet[endIndex + 7] & 0xFF;
+ byte[] entityId = Arrays.copyOfRange(packet, endIndex + 1, endIndex + 6);
+ Integer nameLength = (packet[endIndex + offset] & 0xFF) + ((packet[endIndex + offset + 1] & 0xFF) << 8);
+ byte[] nameBytes = Arrays.copyOfRange(packet, endIndex + offset + 2, endIndex + offset + 1 + nameLength);
+ String entityName = new String(nameBytes, StandardCharsets.US_ASCII);
+ String entityIdHex = java.util.HexFormat.of().withUpperCase().formatHex(entityId);
+
+ MatchResult result = subPacket("1c xx xx 00 02 00", packet, endIndex);
+
+ if (result == null) {
+ byte[] last = Arrays.copyOfRange(packet, endIndex, packet.length);
+ result = new MatchResult(last, null, 0, last.length);
+ }
+
+ byte[] subPacket = Arrays.copyOfRange(result.packet, 0, result.endIndex);
+
+ BooyerMoore matcher = new BooyerMoore();
+ matcher.addPattern("15 00 00 xx xx fb");
+
+ List<MatchResult> matches = matcher.search(subPacket);
+
+ if (!matches.isEmpty()) {
+ MatchResult locationResult = matches.getFirst();
+ Entity entity = entityMap.getEntity(entityIdHex);
+ if (entity == null) {
+ entity = new Entity();
+ entity.name = entityName;
+ entity.id = entityIdHex;
+ }
+
+ entity.lastUpdated = -1;
+ entity.type = entityType;
+
+ byte[] bytes_heading = Arrays.copyOfRange(locationResult.packet, locationResult.endIndex - 2,locationResult.endIndex);
+ byte[] bytes_x = Arrays.copyOfRange(locationResult.packet, locationResult.endIndex + 1,locationResult.endIndex + 5);
+ byte[] bytes_y = Arrays.copyOfRange(locationResult.packet, locationResult.endIndex + 6,locationResult.endIndex + 10);
+ byte[] bytes_z = Arrays.copyOfRange(locationResult.packet, locationResult.endIndex + 11,locationResult.endIndex + 15);
+
+ float value_x = ByteBuffer.wrap(bytes_x).order(ByteOrder.LITTLE_ENDIAN).getFloat();
+ float value_y = ByteBuffer.wrap(bytes_y).order(ByteOrder.LITTLE_ENDIAN).getFloat();
+ float value_z = ByteBuffer.wrap(bytes_z).order(ByteOrder.LITTLE_ENDIAN).getFloat();
+ float heading = bytesToHeading(bytes_heading);
+
+ entity.x = value_x;
+ entity.y = value_y;
+ entity.z = value_z;
+ entity.heading = heading;
+
+ entityMap.updateEntity(entity, player);
+
+// System.out.println("Spawn location: x - " + entity.x + " y: " + entity.y + " z: " + entity.z);
+ }
+
+ entityDict.put(java.util.HexFormat.of().withUpperCase().formatHex(entityId), entityName);
+
+// System.out.println("Entity Name: " + entityName + " Entity ID: " + entityIdHex);
+ }
+
+ private static void chatMessage(byte[] packet, Integer endIndex) {
+ if ((packet[endIndex + 1] & 0xFF) > 0x09) {
+ return;
+ }
+
+ Integer offset = 5;
+ Integer playerNameLength = (packet[endIndex + offset] & 0xFF) + ((packet[endIndex + offset + 1] & 0xFF) << 8);
+ byte[] playerNameBytes = Arrays.copyOfRange(packet, endIndex + offset + 2, endIndex + offset + 2 + playerNameLength);
+ String playerName = new String(playerNameBytes, StandardCharsets.US_ASCII);
+
+ offset = endIndex + offset + 1 + playerNameLength;
+ Integer playerMessageLength = (packet[offset] & 0xFF) + ((packet[offset + 1] & 0xFF) << 8);
+ byte[] playerMessageBytes = Arrays.copyOfRange(packet, offset + 2, offset + 1 + playerMessageLength);
+ String playerMessage = new String(playerMessageBytes, StandardCharsets.US_ASCII);
+
+// System.out.println(playerName + " says: " + playerMessage);
+ }
+
+ private static MatchResult subPacket(String pattern, byte[] packet, Integer start) {
+ BooyerMoore matcher = new BooyerMoore();
+ matcher.addPattern(pattern);
+
+ packet = Arrays.copyOfRange(packet, start, packet.length);
+
+ List<MatchResult> matches = matcher.search(packet);
+
+ if (!matches.isEmpty()) {
+ return matches.getFirst();
+ }
+
+ return null;
+ }
+
+ public static void main(String[] args) {
+ SwingUtilities.invokeLater(() -> {
+ entityMap = new EntityMap();
+
+ JFrame frame = new JFrame("Minimap");
+ frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+ frame.setUndecorated(true);
+ frame.setAlwaysOnTop(true);
+ frame.setResizable(false);
+ frame.add(entityMap);
+ frame.pack();
+ frame.setLocation(50, 50);
+
+ // Make it square
+ Dimension size = entityMap.getPreferredSize();
+ frame.setSize(size.width, size.height);
+
+ frame.setVisible(true);
+ });
+
+ HashMap<String, BiConsumer<byte[], Integer>> dict = new HashMap<>();
+
+ BooyerMoore matcher = new BooyerMoore();
+// matcher.addPattern("00 00 d9 eb 00"); // pet mob
+ matcher.addPattern("00 75 5c 01"); // pet
+// matcher.addPattern("1c xx xx 00 02 00");
+// matcher.addPattern("1a e7 00");
+// matcher.addPattern("1a e1 00");
+// matcher.addPattern("00 ff 00 00 00");
+// dict.put("1c fe fe 00 02 00", App::entitySpawn);
+// dict.put("1c fe fe 00 02 00", App::entitySpawn);
+
+// dict.put("1a e7 00", App::entityLocation);
+// dict.put("1a e1 00", App::playerLocation);
+// dict.put("00 ff 00 00 00", App::chatMessage);
+
+ try {
+ List<PcapIf> devices = Pcap.findAllDevs();
+
+ if (devices.isEmpty()) {
+ System.out.println("No devices found");
+ return;
+ }
+
+ System.out.println("Available devices:");
+ for (int i = 0; i < devices.size(); i++) {
+ PcapIf device = devices.get(i);
+ System.out.println(i + ": " + device.name());
+ System.out.println(" Description: " + device.description());
+ System.out.println();
+ }
+
+ // Use the first device
+ String deviceName = devices.get(3).name();
+ System.out.println("Using device: " + deviceName);
+
+ try (Pcap pcap = Pcap.openOffline("pantheon-combat.pcapng")){
+// try (Pcap pcap = Pcap.create(deviceName)) {
+// pcap.setImmediateMode(true);
+// pcap.setBufferSize(64 * 1024);
+// pcap.activate();
+ BpFilter filter = pcap.compile("host 20.22.207.201", true, 0);
+ pcap.setFilter(filter);
+
+ System.out.println("opened");
+ pcap.loop(-1, (String user, PcapHeader header, byte[] packet) -> {
+ packet = Arrays.copyOfRange(packet, 42, packet.length);
+ List<MatchResult> matches = matcher.search(packet);
+
+// debugDuplicateCoordinates();
+
+ if (!matches.isEmpty()) {
+ System.out.println("Packet captured at: " + header.timestamp());
+ System.out.println("Packet length: " + header.captureLength());
+ long timestampLong = (long) header.timestamp();
+ if (timestampLong == 1764204961927760L) {
+ for (int i = 0; i < packet.length; i++) {
+ System.out.printf("%02X ", packet[i]);
+ if ((i + 1) % 16 == 0) System.out.println(); // New line every 16 bytes
+ }
+ System.out.println("\n");
+ }
+ }
+
+ for (MatchResult match : matches) {
+ BiConsumer<byte[], Integer> handler = dict.get(match.toString());
+ System.out.println(match.toString());
+ if (handler != null) {
+ handler.accept(packet, match.endIndex);
+ }
+ }
+
+ if (!matches.isEmpty()) {
+ for (int i = 0; i < packet.length; i++) {
+ System.out.printf("%02X ", packet[i]);
+ if ((i + 1) % 16 == 0) System.out.println(); // New line every 16 bytes
+ }
+ System.out.println("\n");
+ }
+
+ return;
+ }, "Packet data");
+ } catch (PcapException e) {
+ System.out.println(e);
+ }
+
+ } catch (PcapException e) {
+ System.out.println("Error: " + e.getMessage());
+ }
+ }
+} \ No newline at end of file
diff --git a/src/main/java/org/challman/pantheon_parser/Entity.java b/src/main/java/org/challman/pantheon_parser/Entity.java
new file mode 100644
index 0000000..f43c731
--- /dev/null
+++ b/src/main/java/org/challman/pantheon_parser/Entity.java
@@ -0,0 +1,10 @@
+package org.challman.pantheon_parser;
+
+public class Entity {
+ public String id;
+ public String name;
+ public int type;
+ public float x, y, z;
+ public float heading; // in degrees
+ public long lastUpdated;
+} \ No newline at end of file
diff --git a/src/main/java/org/challman/pantheon_parser/EntityMap.java b/src/main/java/org/challman/pantheon_parser/EntityMap.java
new file mode 100644
index 0000000..36d96d6
--- /dev/null
+++ b/src/main/java/org/challman/pantheon_parser/EntityMap.java
@@ -0,0 +1,355 @@
+package org.challman.pantheon_parser;
+
+import javax.swing.*;
+import java.awt.*;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+public class EntityMap extends JPanel {
+ private Map<String, Entity> entities = new ConcurrentHashMap<>();
+ private Entity player;
+ private long entityTimeout = 10000;
+ private volatile boolean needsRepaint = false;
+ private double scale = 1.0;
+ private JButton zoomInButton, zoomOutButton;
+ private JButton closeButton;
+ private int controlPanelHeight = 25;
+
+ // Drag variables
+ private Point dragStartPoint;
+ private Point windowStartPoint;
+
+ public EntityMap() {
+ setOpaque(true);
+ setBackground(new Color(30, 30, 30));
+ setPreferredSize(new Dimension(300, 300));
+ setLayout(new BorderLayout());
+
+ // Create control panel
+ JPanel controlPanel = createControlPanel();
+ add(controlPanel, BorderLayout.NORTH);
+
+ // Add mouse listeners for dragging
+ addMouseListeners();
+
+ Timer timer = new Timer(33, e -> {
+ if (needsRepaint) {
+ repaint();
+ needsRepaint = false;
+ }
+ });
+ timer.start();
+ }
+
+ public Entity getEntity(String id) {
+ return entities.get(id);
+ }
+
+ private JPanel createControlPanel() {
+ JPanel panel = new JPanel(new BorderLayout());
+ panel.setOpaque(false);
+ panel.setPreferredSize(new Dimension(300, controlPanelHeight));
+ panel.setBorder(BorderFactory.createEmptyBorder(2, 5, 2, 5));
+
+ // Left side: drag handle
+ JLabel dragHandle = new JLabel("≡ Minimap");
+ dragHandle.setForeground(Color.LIGHT_GRAY);
+ dragHandle.setFont(new Font("Arial", Font.PLAIN, 10));
+ dragHandle.setCursor(Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR));
+
+ // Right side: control buttons
+ JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT, 3, 0));
+ buttonPanel.setOpaque(false);
+
+ zoomOutButton = new JButton("-");
+ zoomInButton = new JButton("+");
+ closeButton = new JButton("×");
+
+ styleButton(zoomOutButton);
+ styleButton(zoomInButton);
+ styleCloseButton(closeButton);
+
+ zoomOutButton.addActionListener(e -> zoomOut());
+ zoomInButton.addActionListener(e -> zoomIn());
+ closeButton.addActionListener(e -> System.exit(0));
+
+ buttonPanel.add(zoomOutButton);
+ buttonPanel.add(zoomInButton);
+ buttonPanel.add(closeButton);
+
+ panel.add(dragHandle, BorderLayout.WEST);
+ panel.add(buttonPanel, BorderLayout.EAST);
+
+ return panel;
+ }
+
+ private void styleButton(JButton button) {
+ button.setPreferredSize(new Dimension(25, 18));
+ button.setFont(new Font("Arial", Font.BOLD, 10));
+ button.setFocusable(false);
+ button.setBackground(new Color(60, 60, 60));
+ button.setForeground(Color.WHITE);
+ button.setBorder(BorderFactory.createLineBorder(Color.GRAY));
+
+ button.addMouseListener(new MouseAdapter() {
+ @Override
+ public void mouseEntered(MouseEvent e) {
+ button.setBackground(new Color(80, 80, 80));
+ }
+
+ @Override
+ public void mouseExited(MouseEvent e) {
+ button.setBackground(new Color(60, 60, 60));
+ }
+ });
+ }
+
+ private void styleCloseButton(JButton button) {
+ button.setPreferredSize(new Dimension(25, 18));
+ button.setFont(new Font("Arial", Font.BOLD, 12));
+ button.setFocusable(false);
+ button.setBackground(new Color(100, 60, 60));
+ button.setForeground(Color.WHITE);
+ button.setBorder(BorderFactory.createLineBorder(new Color(120, 80, 80)));
+
+ button.addMouseListener(new MouseAdapter() {
+ @Override
+ public void mouseEntered(MouseEvent e) {
+ button.setBackground(new Color(140, 80, 80));
+ }
+
+ @Override
+ public void mouseExited(MouseEvent e) {
+ button.setBackground(new Color(100, 60, 60));
+ }
+ });
+ }
+
+ private void addMouseListeners() {
+ MouseAdapter dragAdapter = new MouseAdapter() {
+ @Override
+ public void mousePressed(MouseEvent e) {
+ dragStartPoint = e.getPoint();
+ Window window = SwingUtilities.getWindowAncestor(EntityMap.this);
+ if (window != null) {
+ windowStartPoint = window.getLocation();
+ }
+ }
+
+ @Override
+ public void mouseDragged(MouseEvent e) {
+ if (dragStartPoint != null && windowStartPoint != null) {
+ Point currentPoint = e.getPoint();
+ int deltaX = currentPoint.x - dragStartPoint.x;
+ int deltaY = currentPoint.y - dragStartPoint.y;
+
+ Window window = SwingUtilities.getWindowAncestor(EntityMap.this);
+ if (window != null) {
+ Point newLocation = new Point(
+ windowStartPoint.x + deltaX,
+ windowStartPoint.y + deltaY
+ );
+ window.setLocation(newLocation);
+ }
+ }
+ }
+
+ @Override
+ public void mouseReleased(MouseEvent e) {
+ dragStartPoint = null;
+ windowStartPoint = null;
+ }
+ };
+
+ // Add listeners to the entire panel for dragging
+ addMouseListener(dragAdapter);
+ addMouseMotionListener(dragAdapter);
+
+ // Also add to control panel for easier dragging
+ Component[] components = getComponents();
+ for (Component comp : components) {
+ if (comp instanceof JPanel) {
+ comp.addMouseListener(dragAdapter);
+ comp.addMouseMotionListener(dragAdapter);
+ }
+ }
+ }
+
+ private void zoomIn() {
+ scale *= 1.2;
+ if (scale > 10.0) scale = 10.0;
+ needsRepaint = true;
+ }
+
+ private void zoomOut() {
+ scale /= 1.2;
+ if (scale < 0.1) scale = 0.1;
+ needsRepaint = true;
+ }
+
+ public void updateEntity(Entity entity, Entity currentPlayer) {
+ entities.put(entity.id, entity);
+ if (currentPlayer != null) {
+ this.player = currentPlayer;
+ }
+ cleanupOldEntities();
+ needsRepaint = true;
+ }
+
+ public Collection<Entity> getAllEntities() {
+ return entities.values();
+ }
+
+ private void cleanupOldEntities() {
+ long currentTime = System.currentTimeMillis();
+ entities.entrySet().removeIf(entry -> {
+ Entity entity = entry.getValue();
+ return entity != player && entity.lastUpdated != -1 && (currentTime - entity.lastUpdated) > entityTimeout;
+ });
+ }
+
+ @Override
+ protected void paintComponent(Graphics g) {
+ super.paintComponent(g);
+
+ Graphics2D g2d = (Graphics2D) g.create();
+ g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
+
+ if (player == null) {
+ g2d.dispose();
+ return;
+ }
+
+ // Calculate available space for the circle
+ int width = getWidth();
+ int height = getHeight() - controlPanelHeight;
+ int size = Math.min(width, height);
+ int mapRadius = size / 2;
+ int centerX = width / 2;
+ int centerY = controlPanelHeight + (height / 2);
+
+ // Draw circular map background (maximized)
+ g2d.setColor(new Color(20, 20, 20));
+ g2d.fillOval(centerX - mapRadius, centerY - mapRadius, size, size);
+
+ // Draw map border
+ g2d.setColor(new Color(100, 100, 100));
+ g2d.setStroke(new BasicStroke(2));
+ g2d.drawOval(centerX - mapRadius, centerY - mapRadius, size, size);
+
+ // Draw grid lines
+ g2d.setColor(new Color(50, 50, 50));
+ g2d.setStroke(new BasicStroke(1));
+ g2d.drawLine(centerX, centerY - mapRadius, centerX, centerY + mapRadius);
+ g2d.drawLine(centerX - mapRadius, centerY, centerX + mapRadius, centerY);
+
+ // Calculate scale with zoom factor
+ double maxDistance = 50.0 / scale;
+ double distanceScale = mapRadius / maxDistance;
+
+ // Draw other entities
+ g2d.setFont(new Font("Arial", Font.PLAIN, 9));
+ for (Entity entity : entities.values()) {
+ if (entity.id.equals(player.id)) continue;
+
+ double dx = entity.x - player.x;
+ double dz = entity.z - player.z;
+ double distance = Math.sqrt(dx * dx + dz * dz);
+
+ if (distance <= maxDistance) {
+ int x = centerX + (int)(dx * distanceScale);
+ int y = centerY - (int)(dz * distanceScale);
+
+ double distFromCenter = Math.sqrt((x - centerX) * (x - centerX) + (y - centerY) * (y - centerY));
+ if (distFromCenter <= mapRadius - 5) {
+ if (entity.type == 2) {
+ g2d.setColor(Color.CYAN);
+ } else if (entity.type == 3) {
+ g2d.setColor(Color.ORANGE);
+ } else {
+ g2d.setColor(Color.WHITE);
+ }
+ g2d.fillOval(x - 3, y - 3, 6, 6);
+
+ if (distance < maxDistance * 0.9) {
+ g2d.setColor(Color.WHITE);
+ g2d.drawString(entity.id, x + 5, y);
+ }
+ }
+ }
+ }
+
+ // Draw player
+ g2d.setColor(Color.RED);
+ g2d.fillOval(centerX - 4, centerY - 4, 8, 8);
+
+ // Draw direction indicator
+ double directionAngle = Math.toRadians(player.heading);
+ int dirX = centerX + (int)((mapRadius - 10) * Math.sin(directionAngle));
+ int dirY = centerY - (int)((mapRadius - 10) * Math.cos(directionAngle));
+ g2d.setColor(Color.YELLOW);
+ g2d.setStroke(new BasicStroke(2));
+ g2d.drawLine(centerX, centerY, dirX, dirY);
+
+ // Draw UI elements in corners
+ drawCornerElements(g2d, centerX, centerY, mapRadius);
+
+ g2d.dispose();
+ }
+
+ private void drawCornerElements(Graphics2D g2d, int centerX, int centerY, int mapRadius) {
+ g2d.setColor(Color.WHITE);
+ g2d.setFont(new Font("Arial", Font.PLAIN, 10));
+
+ // Top-left: Scale indicator
+ int topLeftX = centerX - mapRadius + 5;
+ int topLeftY = centerY - mapRadius + 15;
+ g2d.drawString(String.format("Zoom: %.1fx", scale), topLeftX, topLeftY);
+
+ // Top-right: Entity count
+ int topRightX = centerX + mapRadius - 60;
+ int topRightY = centerY - mapRadius + 15;
+ int entityCount = entities.size() - (player != null ? 1 : 0);
+ g2d.drawString(String.format("Entities: %d", entityCount), topRightX, topRightY);
+
+ // Bottom-left: Player coordinates
+ int bottomLeftX = centerX - mapRadius + 5;
+ int bottomLeftY = centerY + mapRadius - 5;
+ g2d.drawString(String.format("(%.1f, %.1f)", player.x, player.z), bottomLeftX - 10, bottomLeftY);
+
+ // Bottom-right: Compass heading
+ int bottomRightX = centerX + mapRadius - 50;
+ int bottomRightY = centerY + mapRadius - 5;
+ g2d.drawString(String.format("%.0f°", player.heading), bottomRightX, bottomRightY);
+
+ // Draw mini compass in top-right corner
+ drawMiniCompass(g2d, centerX + mapRadius - 20, centerY - mapRadius + 35, player.heading);
+ }
+
+ private void drawMiniCompass(Graphics2D g2d, int x, int y, float heading) {
+ int radius = 8;
+
+ g2d.setColor(new Color(50, 50, 50, 200));
+ g2d.fillOval(x - radius, y - radius, radius * 2, radius * 2);
+ g2d.setColor(Color.WHITE);
+ g2d.drawOval(x - radius, y - radius, radius * 2, radius * 2);
+
+ // Draw N indicator
+ g2d.setFont(new Font("Arial", Font.BOLD, 8));
+ g2d.drawString("N", x - 2, y - radius - 1);
+
+ double needleAngle = Math.toRadians(heading);
+ int needleX = x + (int)(radius * Math.sin(needleAngle));
+ int needleY = y - (int)(radius * Math.cos(needleAngle));
+
+ g2d.setColor(Color.RED);
+ g2d.setStroke(new BasicStroke(1.5f));
+ g2d.drawLine(x, y, needleX, needleY);
+ g2d.setStroke(new BasicStroke(1));
+ }
+} \ No newline at end of file
diff --git a/src/main/java/org/challman/pantheon_parser/algorithm/AhoCorasick.java b/src/main/java/org/challman/pantheon_parser/algorithm/AhoCorasick.java
new file mode 100644
index 0000000..5a0e51b
--- /dev/null
+++ b/src/main/java/org/challman/pantheon_parser/algorithm/AhoCorasick.java
@@ -0,0 +1,164 @@
+package org.challman.pantheon_parser.algorithm;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Queue;
+
+public class AhoCorasick {
+ private final Node root;
+ private boolean built;
+ private static final byte WILDCARD = (byte) 0xFF; // Use 0xFF as wildcard marker
+
+ public AhoCorasick() {
+ this.root = new Node();
+ this.built = false;
+ }
+
+ public void addPattern(byte[] pattern) {
+ if (built) throw new IllegalStateException("Cannot add patterns after building");
+ addPatternRecursive(root, pattern, 0);
+ }
+ private void addPatternRecursive(Node node, byte[] pattern, int index) {
+ if (index == pattern.length) {
+ node.patterns.add(pattern);
+ node.isEnd = true;
+ return;
+ }
+
+ byte current = pattern[index];
+ if (current == WILDCARD) {
+ // Wildcard: add transitions for all possible bytes (0-255)
+ for (int b = 0; b <= 255; b++) {
+ byte byteVal = (byte) b;
+ Node child = node.children.computeIfAbsent(byteVal, k -> new Node());
+ addPatternRecursive(child, pattern, index + 1);
+ }
+ } else {
+ // Normal byte
+ Node child = node.children.computeIfAbsent(current, k -> new Node());
+ addPatternRecursive(child, pattern, index + 1);
+ }
+ }
+
+ public void addPattern(String hexPattern) {
+ // Replace 'xx' with wildcard marker
+ hexPattern = hexPattern.replaceAll("xx", "ff");
+ addPattern(hexToBytes(hexPattern));
+ }
+
+ public void build() {
+ Queue<Node> queue = new LinkedList<>();
+
+ // Initialize root's children failure to root
+ for (Node child : root.children.values()) {
+ child.failure = root;
+ queue.add(child);
+ }
+
+ // Build failure links using BFS
+ while (!queue.isEmpty()) {
+ Node current = queue.poll();
+
+ for (Map.Entry<Byte, Node> entry : current.children.entrySet()) {
+ byte b = entry.getKey();
+ Node child = entry.getValue();
+
+ Node failure = current.failure;
+ while (failure != root && !failure.children.containsKey(b)) {
+ failure = failure.failure;
+ }
+
+ if (failure.children.containsKey(b)) {
+ child.failure = failure.children.get(b);
+ } else {
+ child.failure = root;
+ }
+
+ // Collect output links
+ child.output.addAll(child.failure.output);
+ if (child.isEnd) {
+ child.output.addAll(child.patterns);
+ }
+
+ queue.add(child);
+ }
+ }
+ built = true;
+ }
+
+ public List<MatchResult> search(byte[] data) {
+ if (!built) build();
+
+ List<MatchResult> results = new ArrayList<>();
+ Node current = root;
+
+ for (int i = 0; i < data.length; i++) {
+ byte b = data[i];
+
+ // Follow failure links until we find a valid transition
+ while (current != root && !current.children.containsKey(b)) {
+ current = current.failure;
+ }
+
+ if (current.children.containsKey(b)) {
+ current = current.children.get(b);
+ } else {
+ current = root;
+ }
+
+ // Collect all matches at this position
+ for (byte[] pattern : current.output) {
+ int startIndex = i - pattern.length + 1;
+ results.add(new MatchResult(pattern, startIndex, i));
+ }
+ }
+
+ return results;
+ }
+
+ private static class Node {
+ Map<Byte, Node> children = new HashMap<>();
+ Node failure = null;
+ boolean isEnd = false;
+ List<byte[]> patterns = new ArrayList<>();
+ List<byte[]> output = new ArrayList<>();
+ }
+
+ public static class MatchResult {
+ public final byte[] pattern;
+ public final int startIndex;
+ public final int endIndex;
+
+ public MatchResult(byte[] pattern, int startIndex, int endIndex) {
+ this.pattern = pattern;
+ this.startIndex = startIndex;
+ this.endIndex = endIndex;
+ }
+
+ @Override
+ public String toString() {
+ return String.format(bytesToHex(pattern));
+ }
+ }
+
+ // Utility methods
+ private static byte[] hexToBytes(String hex) {
+ hex = hex.replaceAll("\\s+", "");
+ byte[] data = new byte[hex.length() / 2];
+ for (int i = 0; i < data.length; i++) {
+ data[i] = (byte) Integer.parseInt(hex.substring(i * 2, i * 2 + 2), 16);
+ }
+ return data;
+ }
+
+ private static String bytesToHex(byte[] bytes) {
+ StringBuilder sb = new StringBuilder();
+ for (byte b : bytes) {
+ sb.append(String.format("%02x ", b));
+ }
+ return sb.toString().trim();
+ }
+} \ No newline at end of file
diff --git a/src/main/java/org/challman/pantheon_parser/algorithm/BooyerMoore.java b/src/main/java/org/challman/pantheon_parser/algorithm/BooyerMoore.java
new file mode 100644
index 0000000..1f05652
--- /dev/null
+++ b/src/main/java/org/challman/pantheon_parser/algorithm/BooyerMoore.java
@@ -0,0 +1,194 @@
+package org.challman.pantheon_parser.algorithm;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+
+public class BooyerMoore {
+ private final List<byte[]> patterns = new ArrayList<>();
+
+ public void addPattern(byte[] pattern) {
+ patterns.add(pattern);
+ }
+
+ public void addPattern(String hexPattern) {
+ hexPattern = hexPattern.replaceAll("xx", "fe");
+ addPattern(hexToBytes(hexPattern));
+ }
+
+ public List<MatchResult> search(byte[] data) {
+ List<MatchResult> results = new ArrayList<>();
+
+ for (byte[] pattern : patterns) {
+ for (int i = 0; i <= data.length - pattern.length; i++) {
+ boolean match = true;
+ for (int j = 0; j < pattern.length; j++) {
+ if (pattern[j] != (byte) 0xFE && data[i + j] != pattern[j]) {
+ match = false;
+ break;
+ }
+ }
+ if (match) {
+ results.add(new MatchResult(data, pattern, i, i + pattern.length - 1));
+ i += pattern.length - 1; // Skip ahead to avoid overlapping matches
+ }
+ }
+ }
+
+ return results;
+ }
+
+ public static class MatchResult {
+ public final byte[] packet;
+ public final byte[] pattern;
+ public final int startIndex;
+ public final int endIndex;
+
+ public MatchResult(byte[] packet, byte[] pattern, int startIndex, int endIndex) {
+ this.packet = packet;
+ this.pattern = pattern;
+ this.startIndex = startIndex;
+ this.endIndex = endIndex;
+ }
+
+ @Override
+ public String toString() {
+ return String.format(bytesToHex(pattern));
+ }
+ }
+
+ // Utility methods
+ private static byte[] hexToBytes(String hex) {
+ hex = hex.replaceAll("\\s+", "");
+ byte[] data = new byte[hex.length() / 2];
+ for (int i = 0; i < data.length; i++) {
+ data[i] = (byte) Integer.parseInt(hex.substring(i * 2, i * 2 + 2), 16);
+ }
+ return data;
+ }
+
+ private static String bytesToHex(byte[] bytes) {
+ StringBuilder sb = new StringBuilder();
+ for (byte b : bytes) {
+ sb.append(String.format("%02x ", b));
+ }
+ return sb.toString().trim();
+ }
+}
+//
+//public class BooyerMoore {
+// private final List<PatternInfo> patterns = new ArrayList<>();
+// private static final byte WILDCARD = (byte) 0xFE;
+//
+// public void addPattern(byte[] pattern) {
+// patterns.add(new PatternInfo(pattern, buildBadCharTable(pattern)));
+// }
+//
+// public void addPattern(String hexPattern) {
+// hexPattern = hexPattern.replaceAll("xx", "fe");
+// addPattern(hexToBytes(hexPattern));
+// }
+//
+// public List<MatchResult> search(byte[] data) {
+// List<MatchResult> results = new ArrayList<>();
+//
+// for (PatternInfo patternInfo : patterns) {
+// int i = 0;
+// while (i <= data.length - patternInfo.pattern.length) {
+// int j = patternInfo.pattern.length - 1;
+//
+// // Check from right to left
+// while (j >= 0 && (patternInfo.pattern[j] == WILDCARD ||
+// data[i + j] == patternInfo.pattern[j])) {
+// j--;
+// }
+//
+// if (j < 0) {
+// // Match found
+// results.add(new MatchResult(data, patternInfo.pattern, i, i + patternInfo.pattern.length - 1));
+// i += patternInfo.pattern.length; // Move to next position
+// } else {
+// // Use bad character rule to skip ahead
+// int skip = patternInfo.badCharTable.getOrDefault(data[i + j], patternInfo.pattern.length);
+// // If the pattern byte at mismatch position is wildcard, we can't skip
+// if (patternInfo.pattern[j] == WILDCARD) {
+// i += 1; // Just move one position
+// } else {
+// i += Math.max(1, skip - (patternInfo.pattern.length - 1 - j));
+// }
+// }
+// }
+// }
+//
+// return results;
+// }
+//
+// private Map<Byte, Integer> buildBadCharTable(byte[] pattern) {
+// Map<Byte, Integer> table = new HashMap<>();
+// for (int i = 0; i < pattern.length - 1; i++) {
+// if (pattern[i] != WILDCARD) {
+// // Only add non-wildcard bytes to the table
+// table.put(pattern[i], pattern.length - 1 - i);
+// }
+// }
+// return table;
+// }
+//
+// private static class PatternInfo {
+// final byte[] pattern;
+// final Map<Byte, Integer> badCharTable;
+//
+// PatternInfo(byte[] pattern, Map<Byte, Integer> badCharTable) {
+// this.pattern = pattern;
+// this.badCharTable = badCharTable;
+// }
+// }
+//
+//
+// private static class Node {
+// Map<Byte, Node> children = new HashMap<>();
+// Node failure = null;
+// boolean isEnd = false;
+// List<byte[]> patterns = new ArrayList<>();
+// List<byte[]> output = new ArrayList<>();
+// }
+//
+// public static class MatchResult {
+// public final byte[] packet;
+// public final byte[] pattern;
+// public final int startIndex;
+// public final int endIndex;
+//
+// public MatchResult(byte[] packet, byte[] pattern, int startIndex, int endIndex) {
+// this.packet = packet;
+// this.pattern = pattern;
+// this.startIndex = startIndex;
+// this.endIndex = endIndex;
+// }
+//
+// @Override
+// public String toString() {
+// return String.format(bytesToHex(pattern));
+// }
+// }
+//
+// // Utility methods
+// private static byte[] hexToBytes(String hex) {
+// hex = hex.replaceAll("\\s+", "");
+// byte[] data = new byte[hex.length() / 2];
+// for (int i = 0; i < data.length; i++) {
+// data[i] = (byte) Integer.parseInt(hex.substring(i * 2, i * 2 + 2), 16);
+// }
+// return data;
+// }
+//
+// private static String bytesToHex(byte[] bytes) {
+// StringBuilder sb = new StringBuilder();
+// for (byte b : bytes) {
+// sb.append(String.format("%02x ", b));
+// }
+// return sb.toString().trim();
+// }
+//} \ No newline at end of file
diff --git a/src/test/java/org/challman/pantheon_parser/AppTest.java b/src/test/java/org/challman/pantheon_parser/AppTest.java
new file mode 100644
index 0000000..1f81ad6
--- /dev/null
+++ b/src/test/java/org/challman/pantheon_parser/AppTest.java
@@ -0,0 +1,19 @@
+package org.challman.pantheon_parser;
+
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.junit.jupiter.api.Test;
+
+/**
+ * Unit test for simple App.
+ */
+public class AppTest {
+
+ /**
+ * Rigorous Test :-)
+ */
+ @Test
+ public void shouldAnswerWithTrue() {
+ assertTrue(true);
+ }
+}