Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Generation Recode in Kotlin #7

Draft
wants to merge 12 commits into
base: main
Choose a base branch
from
Draft
5 changes: 2 additions & 3 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ charset = utf-8
end_of_line = lf
indent_style = space
indent_size = 4
continuation_indent_size = 4
insert_final_newline = false
trim_trailing_whitespace = true
max_line_length = off
Expand All @@ -16,9 +15,9 @@ max_line_length = off
ij_kotlin_allow_trailing_comma = true
ij_kotlin_allow_trailing_comma_on_call_site = true
ij_kotlin_imports_layout = *,java.**,javax.**,kotlin.**,^
ij_java_names_count_to_use_import_on_demand = 3
ij_kotlin_packages_to_use_import_on_demand = java.util.*
#Ktlint
ktlint_standard_no-wildcard-imports = disabled
ktlint_standard_package-name = disabled
ktlint_code_style = official
ktlint_ignore_back_ticked_identifier = false
ktlint_ignore_back_ticked_identifier = false
8 changes: 8 additions & 0 deletions .github/workflows/gradle-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ on:

permissions:
contents: read
checks: write

jobs:
build:
Expand All @@ -23,6 +24,13 @@ jobs:
- name: Test with Gradle
run: ./gradlew test

- name: Publish Test Report
uses: mikepenz/action-junit-report@v3
if: success() || failure() # always run even if the previous step fails
with:
report_paths: '**/build/test-results/test/TEST-*.xml'
detailed_summary: true

- name: Build with Gradle
run: ./gradlew build

Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/kdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ jobs:
distribution: 'temurin'

- name: Build with Gradle and Dokka
run: ./gradlew dokkaHtml
run: ./gradlew dokkaHtmlMultiModule

- name: Upload artifact
uses: actions/upload-pages-artifact@v1
Expand Down
50 changes: 15 additions & 35 deletions build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,45 +1,25 @@
plugins {
kotlin("jvm") version "1.8.20"
id("org.jetbrains.dokka") version "1.8.10"
`maven-publish`
id("org.jetbrains.dokka") version "1.8.20"
}

group = "io.layercraft.connector"
version = properties["version"] as String

repositories {
mavenCentral()
}

dependencies {
testImplementation(kotlin("reflect"))
testImplementation(kotlin("test"))
testImplementation(kotlin("test-junit5"))
testImplementation("org.junit.jupiter:junit-jupiter:5.9.0")
}

tasks.test {
useJUnitPlatform()
allprojects {
repositories {
mavenCentral()
}
}

kotlin {
jvmToolchain(17)
}
group = "io.layercraft.connector"
version = properties["version"] as String

publishing {
repositories {
maven {
name = "repo"
url = uri("https://maven.pkg.github.com/Layercraft/PacketLib")
credentials {
username = project.findProperty("gpr.user") as String? ?: System.getenv("USERNAME")
password = project.findProperty("gpr.key") as String? ?: System.getenv("TOKEN")
}
}
subprojects {
apply {
plugin("org.jetbrains.kotlin.jvm")
plugin("org.jetbrains.dokka")
}
publications {
register<MavenPublication>("gpr") {
from(components["java"])
}

val dokkaPlugin by configurations
dependencies {
dokkaPlugin("org.jetbrains.dokka:versioning-plugin:1.8.20")
}
}
15 changes: 10 additions & 5 deletions generator/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
plugins {
kotlin("jvm") version "1.8.20"
kotlin("plugin.serialization") version "1.8.21"
}

group = "io.layercraft"
version = "1.0.0"

repositories {
mavenCentral()
}

dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.1")
implementation("com.squareup:kotlinpoet:1.14.2")

implementation("io.ktor:ktor-client-core:2.3.1")
implementation("io.ktor:ktor-client-java:2.3.1")

implementation(project(":types"))

testImplementation(platform("org.junit:junit-bom:5.9.1"))
testImplementation("org.junit.jupiter:junit-jupiter")
}
Expand All @@ -30,4 +30,9 @@ tasks.register<JavaExec>("generateCode") {
dependsOn("compileKotlin")
mainClass.set("GenerateCodeKt")
classpath = sourceSets["main"].runtimeClasspath
args("1.20")
doLast {
println("Generated code in ${project.projectDir}/src/main/kotlin")
this.dependsOn("ktlintFormat")
}
}
17 changes: 17 additions & 0 deletions generator/src/main/kotlin/Downloader.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@

import io.ktor.client.HttpClient
import io.ktor.client.engine.java.Java
import io.ktor.client.request.get
import io.ktor.client.statement.bodyAsText
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.jsonObject

object Downloader {

private val client = HttpClient(Java)

suspend fun getWikiVgPage(protocolVersions: ProtocolVersion): String = client.get(protocolVersions.wikiVgUrl).bodyAsText()

suspend fun getProtocolSpec(protocolVersions: ProtocolVersion): JsonObject = client.get(protocolVersions.url).bodyAsText().let { Json.parseToJsonElement(it).jsonObject }
}
9 changes: 7 additions & 2 deletions generator/src/main/kotlin/GenerateCode.kt
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
fun main() {
println("Hello, World!")
fun main(args: Array<String>) {
val versionArg = args.getOrNull(0) ?: error("Missing version argument")
val version = "V${versionArg.uppercase().replace(".", "_")}"
val protocolVersion = ProtocolVersion.values().find { it.name == version } ?: error("Unknown protocol version")

val generator = PacketsGenerator(protocolVersion)
generator.generate()
}
158 changes: 158 additions & 0 deletions generator/src/main/kotlin/PacketsGenerator.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@

import com.squareup.kotlinpoet.FileSpec
import com.squareup.kotlinpoet.KModifier
import com.squareup.kotlinpoet.TypeSpec
import fields.FieldsHelper
import fields.GenerationContext
import kotlinx.coroutines.runBlocking
import kotlinx.serialization.json.*

class PacketsGenerator(
private val protocolVersion: ProtocolVersion,
) {

private val wikiVgSerializer = WikiVgSerializer(protocolVersion)

private val packetData: JsonObject = runBlocking { Downloader.getProtocolSpec(protocolVersion) }

fun generate() {
val packets = getPackets(packetData)
val types = getTypes(packetData).map { it.name }
val registeredTypes = FieldsHelper.register().map { it.key }
val missingTypes = types.filter { !registeredTypes.contains(it) }

println("Found ${packets.size} packets and ${types.size} types")
println("Missing types: $missingTypes")
if (missingTypes.isNotEmpty()) {
error("Missing types")
}

for (packet in packets) {
val packagePath = "io.layercraft.packetlib.packets.${protocolVersion.packageVersion}.${packet.state.name.lowercase()}.${packet.direction.name.replace("_", "").lowercase()}"

val file = FileSpec.builder(packagePath, packet.javaClassName)
val clazz = TypeSpec
.classBuilder(packet.javaClassName)
.addModifiers(KModifier.DATA)

val ctx = GenerationContext(file, clazz)

FieldsHelper.generateClass(ctx, packet)

file.addType(clazz.build())
file.build().writeTo(System.out)
}
}

private fun getPackets(jsonObject: JsonObject): List<PacketData> {
val keysList = listOf("handshaking", "status", "login", "play")

return keysList.flatMap { key ->
val data = key to jsonObject[key]!!.jsonObject
transformPackets(data.first, data.second)
}
}

private fun getTypes(jsonObject: JsonObject): List<PacketType> {
return jsonObject["types"]!!.jsonObject.map {
PacketType(name = it.key, it.value)
}
}

private fun transformPackets(state: String, jsonObject: JsonObject): List<PacketData> {
val toClient = jsonObject["toClient"]!!.jsonObject["types"]!!.jsonObject
val toServer = jsonObject["toServer"]!!.jsonObject["types"]!!.jsonObject

return iterateThrowPackets(state, "toClient", toClient) + iterateThrowPackets(state, "toServer", toServer)
}

private fun iterateThrowPackets(stateStr: String, directionStr: String, jsonObject: JsonObject): List<PacketData> {
val packetData = getPacketInfoLayer(jsonObject["packet"]!!.jsonArray)
val direction = PacketGeneratorDirection.fromString(directionStr)
val state = PacketGeneratorState.fromString(stateStr)

val packets = jsonObject.filter { it.key != "packet" }.map { entry ->
val name = entry.key
val packet = entry.value.jsonArray[1].jsonArray
val packetInfoLayer = packetData.first { it.className == name }

val id = packetInfoLayer.id.replaceFirst("0x", "").toInt(16)

val wikiVgData = wikiVgSerializer.get(packetInfoLayer.id, state, direction)

PacketData(
id = id,
direction = direction,
state = state,
fields = packet,
wikiVgData = wikiVgData,
name = packetInfoLayer.name,
className = packetInfoLayer.className,
)
}

return packets
}

private fun getPacketInfoLayer(packetData: JsonArray): List<PacketInfoLayer> {
val second = packetData[1].jsonArray
val ids = second[0].jsonObject["type"]!!.jsonArray[1].jsonObject["mappings"]!!.jsonObject
val names = second[1].jsonObject["type"]!!.jsonArray[1].jsonObject["fields"]!!.jsonObject

return ids.map { (id, value) ->
val name = value.jsonPrimitive.content
val className = names[name]!!.jsonPrimitive.content

PacketInfoLayer(id, name, className)
}
}
}

data class PacketInfoLayer(
val id: String,
val name: String,
val className: String, // same as name but with packet_ prefix
)

data class PacketData(
val id: Int,
val idString: String = "0x${id.toString(16)}",
val direction: PacketGeneratorDirection,
val name: String,
val className: String, // same as name but with packet_ prefix
val javaClassName: String = name.replace("_", "").replaceFirstChar { it.titlecase() }.replace(" ", "") + "Packet",
val state: PacketGeneratorState,
val fields: JsonArray, // Allways inside a ["container", [$fields] ]
val wikiVgData: WikiVgData,
)

enum class PacketGeneratorDirection(
val direction: String,
) {
CLIENT_BOUND("toClient"),
SERVER_BOUND("toServer"),
;

companion object {
fun fromString(direction: String): PacketGeneratorDirection {
return values().first { it.direction == direction }
}
}
}

enum class PacketGeneratorState(
val state: String,
) {
HANDSHAKING("handshaking"), STATUS("status"), LOGIN("login"), PLAY("play");

companion object {
fun fromString(state: String): PacketGeneratorState {
return values().first { it.state == state }
}
}
}

data class PacketType(
val name: String,
val data: JsonElement,
)
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
enum class ProtocolVersions(val url: String, val versionId: String) {
// V1_20("https://raw.githubusercontent.com/Layercraft/minecraft-data/master/data/pc/1.20/protocol.json", "18067"),
V1_19_3("https://raw.githubusercontent.com/Layercraft/minecraft-data/master/data/pc/1.19.3/protocol.json", "18071"),
enum class ProtocolVersion(val url: String, wikiVgVersionId: String) {
V1_20("https://raw.githubusercontent.com/Layercraft/minecraft-data/master/data/pc/1.20/protocol.json", "18271"),
V1_19_4("https://raw.githubusercontent.com/Layercraft/minecraft-data/master/data/pc/1.19.4/protocol.json", "18242"),
V1_19_3("https://raw.githubusercontent.com/Layercraft/minecraft-data/master/data/pc/1.19.3/protocol.json", "18067"),
V1_19_2("https://raw.githubusercontent.com/Layercraft/minecraft-data/master/data/pc/1.19.2/protocol.json", "17873"),
V1_19_1("https://raw.githubusercontent.com/Layercraft/minecraft-data/master/data/pc/1.19.1/protocol.json", "17873"),
V1_19("https://raw.githubusercontent.com/Layercraft/minecraft-data/master/data/pc/1.19/protocol.json", "17753"),
Expand All @@ -18,10 +19,10 @@ enum class ProtocolVersions(val url: String, val versionId: String) {
V1_15_2("https://raw.githubusercontent.com/Layercraft/minecraft-data/master/data/pc/1.15.2/protocol.json", "16067"),
;

val wikiVgUrl = "https://wiki.vg/index.php?title=Protocol&oldid=$versionId"
val wikiVgUrl = "https://wiki.vg/index.php?title=Protocol&oldid=$wikiVgVersionId"
val packageVersion = name.lowercase()

companion object {
fun latest() = V1_19_3
fun latest() = V1_20
}
}
Loading