import java.util.zip.CRC32 import java.util.zip.ZipEntry import java.util.zip.ZipFile import java.util.zip.ZipOutputStream // Text file extensions for LF normalization val textExtensions = setOf(".kt", ".java", ".properties", ".json", ".toml", ".xml", ".txt", ".MF") fun isTextFile(name: String) = textExtensions.any { name.endsWith(it) } || name.startsWith("META-INF/services/") fun normalizeContent(name: String, content: ByteArray): ByteArray { if (!isTextFile(name)) return content return content.toString(Charsets.UTF_8) .replace("\r\n", "\n").replace("\r", "\n") .toByteArray(Charsets.UTF_8) } fun writeStoredEntry(output: ZipOutputStream, name: String, content: ByteArray) { val normalized = normalizeContent(name, content) val entry = ZipEntry(name).apply { time = 0 size = normalized.size.toLong() compressedSize = normalized.size.toLong() crc = CRC32().apply { update(normalized) }.value method = ZipEntry.STORED } output.putNextEntry(entry) output.write(normalized) output.closeEntry() } plugins { id("java-library") } java { toolchain { languageVersion.set(JavaLanguageVersion.of(21)) } } repositories { mavenCentral() maven { name = "hytale-release" url = uri("https://maven.hytale.com/release") } maven { name = "hytale-pre-release" url = uri("https://maven.hytale.com/pre-release") } } val hytaleServer: Configuration by configurations.creating val patchedJar = rootDir.resolve("repo/applications/HytaleServerPatched.jar") dependencies { hytaleServer("com.hypixel.hytale:Server:2026.02.19-1a311a592") } configurations { api.get().extendsFrom(hytaleServer) } tasks.withType { options.compilerArgs.addAll(listOf( "--add-exports", "java.base/jdk.internal.vm.annotation=ALL-UNNAMED" )) } sourceSets { main { java { // Patch files go in runtime/hytale-server/src/ // Use the same package structure as the original JAR // e.g., src/com/hypixel/hytale/server/SomeClass.java srcDir("src") } } } // Task to create the patched JAR by overlaying compiled classes on top of original tasks.register("patchJar") { group = "coldfusion" description = "Creates a patched HytaleServer JAR with compiled modifications" dependsOn(tasks.compileJava) inputs.files(hytaleServer) inputs.files(tasks.compileJava.get().outputs.files).optional() outputs.file(patchedJar) doLast { val compiledClassesDir = tasks.compileJava.get().destinationDirectory.get().asFile val originalJar = hytaleServer.singleFile // Collect all compiled class files with their relative paths val patchedFiles = mutableMapOf() if (compiledClassesDir.exists()) { compiledClassesDir.walkTopDown() .filter { it.isFile } .forEach { file -> val relativePath = file.relativeTo(compiledClassesDir).path.replace(File.separatorChar, '/') patchedFiles[relativePath] = file.readBytes() } } logger.lifecycle("Patching JAR with ${patchedFiles.size} compiled files") // Collect all entries: patched files override original val allEntries = mutableMapOf() // null = directory ZipFile(originalJar).use { original -> original.entries().asSequence().forEach { entry -> allEntries[entry.name] = if (entry.isDirectory) null else original.getInputStream(entry).readBytes() } } // Override with patched files patchedFiles.forEach { (path, content) -> logger.lifecycle(" Patching: $path") allEntries[path] = content } // Write sorted entries with STORED compression and LF normalization patchedJar.outputStream().buffered().let { ZipOutputStream(it) }.use { output -> output.setMethod(ZipOutputStream.STORED) allEntries.keys.sorted().forEach { name -> val content = allEntries[name] if (content == null) { // Directory val entry = ZipEntry(name).apply { time = 0 size = 0 compressedSize = 0 crc = 0 method = ZipEntry.STORED } output.putNextEntry(entry) output.closeEntry() } else { writeStoredEntry(output, name, content) } } } logger.lifecycle("Created patched JAR: $patchedJar") } }