git » crow.game » commit 4e80150

Add codec API package

author b0in
2023-11-24 19:46:49 UTC
committer b0in
2023-11-24 19:46:49 UTC
parent cc8f063d6370e752f8e501be9845ebbab59391e3

Add codec API package

crow.game.codec.api/pom.xml +18 -0
crow.game.codec.api/src/main/java/crow/game/codec/Context.java +43 -0
crow.game.codec.api/src/main/java/crow/game/codec/InetAddressAndPort.java +9 -0
crow.game.codec.api/src/main/java/crow/game/codec/Packet.java +11 -0
crow.game.codec.api/src/main/java/crow/game/codec/PacketHandler.java +108 -0
crow.game.codec.api/src/main/java/crow/game/codec/PacketRule.java +105 -0
crow.game.codec.api/src/main/java/crow/game/codec/package-info.java +25 -0
crow.game.site/src/site/apt/faq.apt +8 -1
pom.xml +2 -1

diff --git a/crow.game.codec.api/pom.xml b/crow.game.codec.api/pom.xml
new file mode 100644
index 0000000..0d5983d
--- /dev/null
+++ b/crow.game.codec.api/pom.xml
@@ -0,0 +1,18 @@
+<?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>
+  <parent>
+    <groupId>crow.game</groupId>
+    <artifactId>crow.game.root</artifactId>
+    <version>0.1-SNAPSHOT</version>
+  </parent>
+  <artifactId>crow.game.codec.api</artifactId>
+  <dependencies>
+    <dependency>
+      <groupId>io.netty</groupId>
+      <artifactId>netty-codec</artifactId>
+      <version>4.1.95.Final</version>
+    </dependency>
+  </dependencies>
+</project>
diff --git a/crow.game.codec.api/src/main/java/crow/game/codec/Context.java b/crow.game.codec.api/src/main/java/crow/game/codec/Context.java
new file mode 100644
index 0000000..f13e72f
--- /dev/null
+++ b/crow.game.codec.api/src/main/java/crow/game/codec/Context.java
@@ -0,0 +1,43 @@
+package crow.game.codec;
+
+/**
+ * Context wraps the calling context when performing packet logic.
+ * <p>
+ * It allows packet handlers to query details about the running system that may
+ * change based on the given client, the network type, whether we are encoding
+ * or decoding the packet.
+ */
+public interface Context {
+    /**
+     * @return the current RO protocol version as an integer.
+     */
+    public int supportedProtocolVersion();
+
+    /**
+     * @return the underlying object, usually a Netty connection object.
+     */
+    public Object underlyingObject();
+
+    /**
+     * @return the location of the codec workflow this context was built in.
+     */
+    public Source callingSource();
+
+    /**
+     * Source determines where the context was constructed and called into. Packet Rules
+     * can be constructed in the INIT phase, the DECODE phase, or the ENCODE phase
+     * and packet handlers may want to know which is being called for now.
+     */
+    public static enum Source {
+        INIT,
+        DECODE,
+        ENCODE,
+    }
+
+    public default boolean matchesPacketVersion(PacketRule pr) {
+        return (
+            this.supportedProtocolVersion() >= pr.minVersion() &&
+            this.supportedProtocolVersion() <= pr.maxVersion()
+        );
+    }
+}
diff --git a/crow.game.codec.api/src/main/java/crow/game/codec/InetAddressAndPort.java b/crow.game.codec.api/src/main/java/crow/game/codec/InetAddressAndPort.java
new file mode 100644
index 0000000..5038ae3
--- /dev/null
+++ b/crow.game.codec.api/src/main/java/crow/game/codec/InetAddressAndPort.java
@@ -0,0 +1,9 @@
+package crow.game.codec;
+
+import java.net.InetAddress;
+
+/**
+ * Utility object for pairing the ip address and port together.
+ *
+ */
+public record InetAddressAndPort(InetAddress addr, int port) {}
diff --git a/crow.game.codec.api/src/main/java/crow/game/codec/Packet.java b/crow.game.codec.api/src/main/java/crow/game/codec/Packet.java
new file mode 100644
index 0000000..f47ecbc
--- /dev/null
+++ b/crow.game.codec.api/src/main/java/crow/game/codec/Packet.java
@@ -0,0 +1,11 @@
+package crow.game.codec;
+
+import io.netty.buffer.ByteBuf;
+
+/**
+ * The packet object pairs the byte stream with the matched rule.
+ *
+ * The byte buffer does NOT contain the packet ID or length, that is already
+ * read.
+ */
+public record Packet(ByteBuf buffer, PacketRule rule) {}
diff --git a/crow.game.codec.api/src/main/java/crow/game/codec/PacketHandler.java b/crow.game.codec.api/src/main/java/crow/game/codec/PacketHandler.java
new file mode 100644
index 0000000..ff8c554
--- /dev/null
+++ b/crow.game.codec.api/src/main/java/crow/game/codec/PacketHandler.java
@@ -0,0 +1,108 @@
+package crow.game.codec;
+
+import io.netty.buffer.ByteBuf;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+
+/**
+ * Interface responsible for converting to and from Packet objects to a POJO
+ * type
+ *
+ * @param <T> The POJO that maps to the given packet.
+ * @see Context
+ */
+public interface PacketHandler<T> {
+    /**
+     * Convert the given buffer to the type.
+     *
+     * @param ctx    The calling context.
+     * @param buffer The input buffer stream.
+     *
+     * @return The POJO object
+     *
+     * @throws Exception
+     */
+    public T fromPacket(Context ctx, ByteBuf buffer) throws Exception;
+
+    /**
+     * Writes the object of type T to the byte buffer.
+     *
+     * @param ctx    The calling context.
+     * @param buffer The destination buffer.
+     * @param src    The source object we are converting.
+     *
+     * @throws Exception
+     */
+    public void toPacket(Context ctx, ByteBuf buffer, T src) throws Exception;
+
+    /**
+     * @see PacketRule
+     *
+     * @param ctx The calling context.
+     * @return The packet rule object which specifies when this packet handler
+     *         should be called.
+     */
+    public PacketRule rule(Context ctx);
+
+    /**
+     * Parses the address and port pair from the kro server byte stream.
+     *
+     * @param buf The input buffer.
+     * @return The IP address and port pair
+     * @see InetAddressAndPort
+     * @throws UnknownHostException
+     */
+    static InetAddressAndPort readInetAddressAndPort(ByteBuf buf)
+        throws UnknownHostException {
+        byte addrBuf[] = new byte[4];
+        buf.readBytes(addrBuf);
+
+        InetAddress address = InetAddress.getByAddress(addrBuf);
+        int port = buf.readShort();
+        return new InetAddressAndPort(address, port);
+    }
+
+    /**
+     * Reads the string from the byte buffer. The byte buffer is read fully so you
+     * must pass in a sliced buffer of the given size.
+     * <p>
+     * Usage:
+     * <p>
+     * {@code
+     * 		PacketHandler.readStr(buffer.readSlice(N));
+     * }
+     * <br/>
+     * where N is the length of the string field in the packet.
+     *
+     * <p>
+     * NOTE:Strings in KRO are 0-terminated and use pre-determined lengths. So
+     * "hello" of length 20 will be "hello" 0x0 0x0 0x0...
+     *
+     * @param buf The byte buffer to read from.
+     * @return The string object.
+     */
+    static String readStr(ByteBuf buf) {
+        byte x = 0;
+        StringBuffer out = new StringBuffer();
+        do {
+            x = buf.readByte();
+            if (x != 0) {
+                out.append((char) x);
+            }
+        } while (x != 0);
+        return out.toString();
+    }
+
+    /**
+     * Writes the string to the given byte buffer, padding the given 0 bytes based
+     * on the length.
+     *
+     * @param buf The buffer to write to.
+     * @param str The string to write.
+     * @param len The length of the string in the byte stream.
+     */
+    static void writeStr(ByteBuf buf, String str, int len) {
+        buf.writeBytes(str.getBytes());
+        buf.writeZero(len - str.length());
+    }
+}
diff --git a/crow.game.codec.api/src/main/java/crow/game/codec/PacketRule.java b/crow.game.codec.api/src/main/java/crow/game/codec/PacketRule.java
new file mode 100644
index 0000000..522fefb
--- /dev/null
+++ b/crow.game.codec.api/src/main/java/crow/game/codec/PacketRule.java
@@ -0,0 +1,105 @@
+package crow.game.codec;
+
+/**
+ * Represents a specification for which packet ID (labeled 'prefix' here) is of
+ * what type (STATIC or DYNAMIC), what name and Java class POJO, and what kRO
+ * packet version the POJO maps to.
+ *
+ */
+public record PacketRule(
+    int minVersion, int maxVersion, int prefix, int data, Type type, String name, Class<?> clz
+) {
+    /**
+     * The type of packet, currently only 2. see classdoc.
+     */
+    public static enum Type {
+        STATIC,
+        DYNAMIC,
+    }
+
+    /**
+     * Simpler packet rule constructor.
+     *
+     * @param prefix The 2-byte code prefix for matching the packet.
+     * @param data The static or dynamic size tag indicator.
+     * @param type The type of the packet, STATIC or DYNAMIC
+     * @param clz The class of the POJO we are serializing and deserializing.
+     */
+    public PacketRule(int prefix, int data, Type type, Class<?> clz) {
+        this(-1, -1, prefix, data, type, clz.getSimpleName(), clz);
+    }
+
+    /**
+     * Simpler packet rule constructor.
+     *
+     * @param prefix The 2-byte code prefix for matching the packet.
+     * @param data The static or dynamic size tag indicator.
+     * @param type The type of the packet, STATIC or DYNAMIC
+     * @param name The name of the packet, currently only for logging.
+     * @param clz The class of the POJO we are serializing and deserializing.
+     */
+    public PacketRule(
+        int prefix,
+        int data,
+        Type type,
+        String name,
+        Class<?> clz
+    ) {
+        this(-1, -1, prefix, data, type, name, clz);
+    }
+
+    /**
+     * Packet rule constructor.
+     *
+     * @param minVersion the min version, in YYYYMMDD format, or -1 for "as early as possible".
+     * @param maxVersion the maximum packet version this rule supports, in YYYYMMDD format, or -1 for "most recent"
+     * @param prefix The 2-byte code prefix for matching the packet.
+     * @param data The static or dynamic size tag indicator.
+     * @param type The type of the packet, STATIC or DYNAMIC
+     * @param name The name of the packet, currently only for logging.
+     * @param clz The class of the POJO we are serializing and deserializing.
+     */
+    public PacketRule(
+        int minVersion,
+        int maxVersion,
+        int prefix,
+        int data,
+        Type type,
+        String name,
+        Class<?> clz
+    ) {
+        this.minVersion = minVersion == -1 ? 19990101 : minVersion;
+        this.maxVersion = maxVersion == -1 ? 30000101 : maxVersion;
+        this.prefix = prefix;
+        this.data = data;
+        this.type = type;
+        this.name = name;
+        this.clz = clz;
+    }
+
+    /**
+     * Utility constructor for building a non-packet version specific dynamic packet
+     * whose name is derived from the given class.
+     *
+     * @param prefix The 2-byte code prefix for matching the packet.
+     * @param sizeTag The size of the following "packet size" indicator, 2 or 4.
+     * @param clz The class that we are serializing and deserializing.
+     * @return The packet rule object.
+     */
+    public static PacketRule Dynamic(int prefix, int sizeTag, Class<?> clz) {
+        return new PacketRule(prefix, sizeTag, Type.DYNAMIC, clz);
+    }
+
+    /**
+     * Utility constructor for building a non-packet version specific static packet
+     * whose name is derived from the given class.
+     *
+     * @param prefix The 2-byte code prefix for matching the packet.
+     * @param size The size of the following full packet.
+     * @param clz The class that we are serializing and deserializing.
+     * @return The packet rule object.
+     */
+    public static PacketRule Static(int prefix, int size, Class<?> clz) {
+        return new PacketRule(prefix, size, Type.STATIC, clz);
+    }
+}
diff --git a/crow.game.codec.api/src/main/java/crow/game/codec/package-info.java b/crow.game.codec.api/src/main/java/crow/game/codec/package-info.java
new file mode 100644
index 0000000..60c394f
--- /dev/null
+++ b/crow.game.codec.api/src/main/java/crow/game/codec/package-info.java
@@ -0,0 +1,25 @@
+/**
+ * Package containing the base API objects for the protocol implementation.
+ * <p>
+ * This API is currently tied to Netty {@link io.netty.buffer.ByteBuf} but is
+ * otherwise decoupled from Netty. sub-package contains the Netty-specific code
+ * for building {@link crow.game.codec.Packet} objects.
+ * <p>
+ * There are multiple hooks for handling differing packet versions in this API:
+ * <p>
+ * You can define your POJO as "MyPacket20NN0101" alongside {@link crow.game.codec.PacketHandler} "MyPacketHandler20NN0101"
+ * that returns a {@link crow.game.codec.PacketRule} with the version specifiers enabled.
+ * <p>
+ * Then you would have a second POJO that maps to everything that the previous packet did not.
+ * <p>
+ * OR
+ * <p>
+ * You can define a standard packet "MyPacket" and {@link crow.game.codec.PacketHandler} "MyPacketHandle"
+ * that, when you recieve calls into your handler, you can look at the current packet version
+ * via {@link crow.game.codec.Context}.supportedProtocolVersion and act accordingly via branching logic.
+ * <p>
+ * Implementors note: It's a long shot but maybe it will be possible to support multiple protocol versions in the same
+ * running server, via client version identification and pinning, then {@link crow.game.codec.Context}
+ * may contain additional account and client info attached.
+ */
+package crow.game.codec;
diff --git a/crow.game.site/src/site/apt/faq.apt b/crow.game.site/src/site/apt/faq.apt
index 29b6a87..b5bee9d 100644
--- a/crow.game.site/src/site/apt/faq.apt
+++ b/crow.game.site/src/site/apt/faq.apt
@@ -26,4 +26,11 @@ FAQ
 
 * Which kRO protocol versions are supported?
 
-    TODO: write kRO protocol version details.
+    I am currently writing against a client named 2021-11-05_Ragexe_1636095152_patched.exe
+    but am working writing lots of version-aware hooks based on existing rathena code.
+
+    (TODO: include patchset list if necessary)
+
+* Who did your website? Can I pay them thousands of dollars to do mine?  /sarc
+
+   It is purposefully OK. I'm not beating myself up over it.
diff --git a/pom.xml b/pom.xml
index e849568..0603779 100644
--- a/pom.xml
+++ b/pom.xml
@@ -6,12 +6,12 @@
   <artifactId>crow.game.root</artifactId>
   <version>0.1-SNAPSHOT</version>
   <packaging>pom</packaging>
+  <url>https://b0in.xyz/code/crow.game</url>
   <inceptionYear>2023</inceptionYear>
   <organization>
     <name>boin</name>
     <url>https://b0in.xyz/</url>
   </organization>
-  <url>https://b0in.xyz/code/crow.game</url>
   <licenses>
     <license>
       <name>GNU General Public License (GPL) version 3.0</name>
@@ -30,6 +30,7 @@
   </developers>
   <modules>
     <module>crow.game.site</module>
+    <module>crow.game.codec.api</module>
   </modules>
   <scm>
     <connection>scm:git:https://b0in.xyz/crow/crow-latest-git.tar.gz</connection>