| author | b0in
<b0in@proton.me> 2023-11-24 19:46:49 UTC |
| committer | b0in
<b0in@proton.me> 2023-11-24 19:46:49 UTC |
| parent | cc8f063d6370e752f8e501be9845ebbab59391e3 |
| 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>