├── README.md ├── build.gradle.kts ├── client ├── build.gradle.kts ├── gradle.properties ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties └── src │ └── main │ ├── java │ └── net │ │ └── spartanb312 │ │ └── phantom │ │ └── mixin │ │ └── MixinMinecraft.java │ ├── kotlin │ ├── net │ │ └── spartanb312 │ │ │ └── phantom │ │ │ ├── Phantom.kt │ │ │ ├── launch │ │ │ ├── DevFMLCoreMod.kt │ │ │ ├── InitializationManager.kt │ │ │ └── MixinPlugin.kt │ │ │ └── util │ │ │ └── Logger.kt │ └── phantomloader │ │ ├── AES.kt │ │ ├── FMLCoreMod.kt │ │ ├── Loader.kt │ │ ├── MixinCache.kt │ │ └── PhantomClassLoader.kt │ └── resources │ ├── mixins.phantom.json │ └── mixins.phantom.loader.json ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── server ├── build.gradle.kts ├── gradle.properties ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties └── src │ └── main │ └── kotlin │ └── net │ └── spartanb312 │ └── phantom │ └── server │ ├── Main.kt │ └── utils │ ├── HWIDManager.kt │ └── LogUtil.kt └── settings.gradle /README.md: -------------------------------------------------------------------------------- 1 | # Phantom Loader 2 | A mod loader with mixin support for Minecraft 1.12.2 3 | 4 | You can use this loader to load your mod remotely(Including mixin json) 5 | 6 | ## Usage 7 | 1.Put the xx-client.jar to the server path and launch server 8 | 9 | 2.Put the xx-loader.jar to the mods folder 10 | 11 | 3.Add HWIDs to the server 12 | 13 | 4.launch game 14 | 15 | 16 | ## Development 17 | There are 2 ways to run dev client 18 | 19 | 1.Use the raw jar in mods folder(without -client or - loader) 20 | 21 | 2.Task runClient in IDE(UserDevConfiguration is ready.Class hotswap supported) 22 | 23 | 24 | ## To Client Devs 25 | You can't obfuscate MixinPlugin FMLCoreMod InitializationManager 26 | 27 | But If your obfuscator support mixin obf and meta-inf replace then it will be ok. 28 | 29 | BTW ResourceCache is not safe at all.You can use ur own class loader and encrypt the download stream, protocol, confusing the loader logic 30 | 31 | ## Example 32 | https://github.com/SpartanB312/Epsilon 33 | 34 | ## Credits 35 | Some ideas are from falcon loader 36 | -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | } 3 | 4 | group = "net.spartanb312" 5 | version = "1.0" 6 | 7 | repositories { 8 | mavenCentral() 9 | } -------------------------------------------------------------------------------- /client/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import net.minecraftforge.gradle.userdev.UserDevExtension 2 | import org.spongepowered.asm.gradle.plugins.MixinExtension 3 | 4 | val modGroup: String by extra 5 | val modVersion: String by extra 6 | 7 | group = modGroup 8 | version = modVersion 9 | 10 | buildscript { 11 | repositories { 12 | jcenter() 13 | maven("https://files.minecraftforge.net/maven") 14 | maven("https://repo.spongepowered.org/repository/maven-public/") 15 | } 16 | 17 | dependencies { 18 | classpath("net.minecraftforge.gradle:ForgeGradle:5.+") 19 | classpath("org.spongepowered:mixingradle:0.7-SNAPSHOT") 20 | } 21 | } 22 | 23 | plugins { 24 | java 25 | kotlin("jvm") version "1.6.10" 26 | } 27 | 28 | apply { 29 | plugin("net.minecraftforge.gradle") 30 | plugin("org.spongepowered.mixin") 31 | } 32 | 33 | repositories { 34 | jcenter() 35 | mavenCentral() 36 | maven("https://repo.spongepowered.org/repository/maven-public/") 37 | maven("https://jitpack.io") 38 | } 39 | 40 | val library: Configuration by configurations.creating 41 | 42 | val kotlinVersion: String by project 43 | val kotlinxCoroutineVersion: String by project 44 | 45 | dependencies { 46 | 47 | library("org.jetbrains.kotlin:kotlin-stdlib:1.6.0") 48 | library(kotlin("reflect", kotlinVersion)) 49 | library(kotlin("stdlib-jdk8", kotlinVersion)) 50 | library("org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlinxCoroutineVersion") 51 | 52 | fun minecraft(dependencyNotation: Any): Dependency? = "minecraft"(dependencyNotation) 53 | 54 | fun jarOnly(dependencyNotation: Any) { 55 | library(dependencyNotation) 56 | } 57 | 58 | fun ModuleDependency.exclude(moduleName: String) = 59 | exclude(mapOf("module" to moduleName)) 60 | 61 | 62 | minecraft("net.minecraftforge:forge:1.12.2-14.23.5.2860") 63 | 64 | library("org.spongepowered:mixin:0.8-SNAPSHOT") { 65 | exclude("commons-io") 66 | exclude("gson") 67 | exclude("guava") 68 | exclude("launchwrapper") 69 | exclude("log4j-core") 70 | } 71 | 72 | library(fileTree("lib")) 73 | 74 | annotationProcessor("org.spongepowered:mixin:0.8.3:processor") { 75 | exclude("gson") 76 | } 77 | 78 | implementation(library) 79 | 80 | } 81 | 82 | configure { 83 | add(sourceSets["main"], "mixins.phantom.refmap.json") 84 | } 85 | 86 | configure { 87 | mappings( 88 | mapOf( 89 | "channel" to "stable", 90 | "version" to "39-1.12" 91 | ) 92 | ) 93 | 94 | runs { 95 | create("client") { 96 | workingDirectory = project.file("run").path 97 | 98 | properties( 99 | mapOf( 100 | "forge.logging.markers" to "SCAN,REGISTRIES,REGISTRYDUMP", 101 | "forge.logging.console.level" to "info", 102 | "fml.coreMods.load" to "net.spartanb312.phantom.launch.DevFMLCoreMod", 103 | "mixin.env.disableRefMap" to "true" 104 | ) 105 | ) 106 | } 107 | } 108 | } 109 | 110 | 111 | val client = task("client", type = Jar::class) { 112 | archiveClassifier.set("Client") 113 | 114 | from( 115 | library.map { 116 | if (it.isDirectory) it 117 | else zipTree(it) 118 | } 119 | ) 120 | 121 | include( 122 | "net/spartanb312/phantom/**", 123 | "mixins.phantom.json", 124 | "mixins.phantom.refmap.json", 125 | ) 126 | 127 | exclude( 128 | "net.spartanb312.phantom.launch.DevFMLCoreMod" 129 | ) 130 | 131 | with(tasks["jar"] as CopySpec) 132 | } 133 | 134 | val loader = task("loader", type = Jar::class) { 135 | archiveClassifier.set("Loader") 136 | 137 | manifest { 138 | attributes( 139 | "FMLCorePluginContainsFMLMod" to "true", 140 | "FMLCorePlugin" to "phantomloader.FMLCoreMod", 141 | "TweakClass" to "org.spongepowered.asm.launch.MixinTweaker", 142 | "TweakOrder" to 0, 143 | "ForceLoadAsMod" to "true" 144 | ) 145 | } 146 | 147 | from( 148 | library.map { 149 | if (it.isDirectory) it 150 | else zipTree(it) 151 | } 152 | ) 153 | 154 | exclude( 155 | "mixins.phantom.json", 156 | "mixins.phantom.refmap.json", 157 | "net/spartanb312/phantom/**", 158 | "net.spartanb312.phantom.launch.DevFMLCoreMod" 159 | ) 160 | 161 | duplicatesStrategy = DuplicatesStrategy.EXCLUDE 162 | 163 | exclude( 164 | "META-INF/versions/**", 165 | "**/*.RSA", 166 | "**/*.SF", 167 | "**/module-info.class", 168 | "**/LICENSE", 169 | "**/*.txt" 170 | ) 171 | 172 | with(tasks["jar"] as CopySpec) 173 | } 174 | 175 | tasks { 176 | compileJava { 177 | options.encoding = "UTF-8" 178 | sourceCompatibility = "1.8" 179 | targetCompatibility = "1.8" 180 | } 181 | 182 | compileKotlin { 183 | kotlinOptions { 184 | jvmTarget = "1.8" 185 | freeCompilerArgs = listOf("-Xopt-in=kotlin.RequiresOptIn", "-Xinline-classes") 186 | } 187 | } 188 | 189 | jar { 190 | manifest { 191 | attributes( 192 | "FMLCorePluginContainsFMLMod" to "true", 193 | "FMLCorePlugin" to "net.spartanb312.phantom.launch.DevFMLCoreMod", 194 | "TweakClass" to "org.spongepowered.asm.launch.MixinTweaker", 195 | "TweakOrder" to 0, 196 | "ForceLoadAsMod" to "true" 197 | ) 198 | } 199 | from( 200 | library.map { 201 | if (it.isDirectory) it 202 | else zipTree(it) 203 | } 204 | ) 205 | exclude( 206 | "META-INF/versions/**", 207 | "**/*.RSA", 208 | "**/*.SF", 209 | "**/module-info.class", 210 | "**/LICENSE", 211 | "**/*.txt" 212 | ) 213 | } 214 | 215 | "build" { 216 | dependsOn(client, loader) 217 | } 218 | } -------------------------------------------------------------------------------- /client/gradle.properties: -------------------------------------------------------------------------------- 1 | kotlin.code.style=official 2 | org.gradle.jvmargs=-Xmx4G 3 | modGroup=net.spartanb312 4 | modVersion=1.0 5 | kotlinVersion=1.6.10 6 | kotlinxCoroutineVersion=1.5.2 -------------------------------------------------------------------------------- /client/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpartanB312/Verificator/027010dbb9b4414a19fdc060af43e975edbb8977/client/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /client/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /client/src/main/java/net/spartanb312/phantom/mixin/MixinMinecraft.java: -------------------------------------------------------------------------------- 1 | package net.spartanb312.phantom.mixin; 2 | 3 | import net.minecraft.client.Minecraft; 4 | import net.spartanb312.phantom.Phantom; 5 | import org.spongepowered.asm.mixin.Mixin; 6 | import org.spongepowered.asm.mixin.injection.At; 7 | import org.spongepowered.asm.mixin.injection.Inject; 8 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 9 | 10 | @Mixin(Minecraft.class) 11 | public class MixinMinecraft { 12 | 13 | @Inject(method = "init", at = @At("HEAD")) 14 | private void onPreInit(CallbackInfo ci) { 15 | Phantom.INSTANCE.onPreInit(); 16 | } 17 | 18 | @Inject(method = "init", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/Minecraft;checkGLError(Ljava/lang/String;)V", ordinal = 1, shift = At.Shift.AFTER)) 19 | private void onInit(CallbackInfo ci) { 20 | Phantom.INSTANCE.onInit(); 21 | } 22 | 23 | @Inject(method = "init", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/Minecraft;checkGLError(Ljava/lang/String;)V", ordinal = 2, shift = At.Shift.AFTER)) 24 | private void onPostInit(CallbackInfo ci) { 25 | Phantom.INSTANCE.onPostInit(); 26 | } 27 | 28 | @Inject(method = "init", at = @At("RETURN")) 29 | private void onReady(CallbackInfo ci) { 30 | Phantom.INSTANCE.onReady(); 31 | } 32 | 33 | } -------------------------------------------------------------------------------- /client/src/main/kotlin/net/spartanb312/phantom/Phantom.kt: -------------------------------------------------------------------------------- 1 | package net.spartanb312.phantom 2 | 3 | import net.spartanb312.phantom.util.Logger 4 | 5 | /** 6 | * The main mod 7 | */ 8 | object Phantom { 9 | 10 | fun onPreInit() { 11 | Logger.fatal("Pre init") 12 | } 13 | 14 | fun onInit() { 15 | Logger.fatal("Init") 16 | } 17 | 18 | fun onPostInit() { 19 | Logger.fatal("Post Init") 20 | } 21 | 22 | fun onReady() { 23 | Logger.fatal("Ready") 24 | } 25 | 26 | } -------------------------------------------------------------------------------- /client/src/main/kotlin/net/spartanb312/phantom/launch/DevFMLCoreMod.kt: -------------------------------------------------------------------------------- 1 | package net.spartanb312.phantom.launch 2 | 3 | import com.google.gson.Gson 4 | import com.google.gson.JsonObject 5 | import net.minecraftforge.fml.relauncher.IFMLLoadingPlugin 6 | import java.io.File 7 | import java.io.FileOutputStream 8 | import java.nio.charset.StandardCharsets 9 | 10 | /** 11 | * Use this to dev easily in IDE 12 | */ 13 | class DevFMLCoreMod : IFMLLoadingPlugin { 14 | 15 | var isObfuscatedEnvironment = false 16 | private val tempDir: String = System.getProperty("java.io.tmpdir") 17 | private val nextTempFile get() = File(tempDir, "+~JF$randomString.tmp") 18 | private val randomString: String 19 | get() { 20 | val allowedChars = ('0'..'9') + ('a'..'z') + ('A'..'Z') 21 | return (1..18) 22 | .map { allowedChars.random() } 23 | .joinToString("") 24 | } 25 | 26 | init { 27 | val refMapFile = nextTempFile.apply { 28 | try { 29 | FileOutputStream(this).let { 30 | it.write(DevFMLCoreMod::class.java.getResourceAsStream("/mixins.phantom.refmap.json")!!.readBytes()) 31 | it.flush() 32 | it.close() 33 | this.deleteOnExit() 34 | } 35 | } catch (exception: Exception) { 36 | exception.printStackTrace() 37 | } 38 | } 39 | 40 | val mixinCache = mutableListOf() 41 | 42 | Gson().fromJson( 43 | String( 44 | DevFMLCoreMod::class.java.getResourceAsStream("/mixins.phantom.json")!!.readBytes(), 45 | StandardCharsets.UTF_8 46 | ), 47 | JsonObject::class.java 48 | ).apply { 49 | getAsJsonArray("client")?.forEach { 50 | mixinCache.add(it.asString) 51 | } 52 | getAsJsonArray("mixins")?.forEach { 53 | mixinCache.add(it.asString) 54 | } 55 | } 56 | 57 | InitializationManager.init(refMapFile, mixinCache) 58 | } 59 | 60 | override fun getModContainerClass(): String? = null 61 | 62 | override fun getASMTransformerClass(): Array = emptyArray() 63 | 64 | override fun getSetupClass(): String? = null 65 | 66 | override fun injectData(data: Map) { 67 | isObfuscatedEnvironment = (data["runtimeDeobfuscationEnabled"] as Boolean?)!! 68 | } 69 | 70 | override fun getAccessTransformerClass(): String? { 71 | return null 72 | } 73 | 74 | } -------------------------------------------------------------------------------- /client/src/main/kotlin/net/spartanb312/phantom/launch/InitializationManager.kt: -------------------------------------------------------------------------------- 1 | package net.spartanb312.phantom.launch 2 | 3 | import org.apache.logging.log4j.LogManager 4 | import org.spongepowered.asm.launch.MixinBootstrap 5 | import org.spongepowered.asm.mixin.MixinEnvironment 6 | import org.spongepowered.asm.mixin.Mixins 7 | import java.io.File 8 | 9 | /** 10 | * The hook of the mod 11 | */ 12 | object InitializationManager { 13 | 14 | private val logger = LogManager.getLogger("Phantom") 15 | 16 | lateinit var mixinRefmapFile: File 17 | lateinit var mixinCache: List 18 | 19 | fun init(mixinRefmapFile: File, mixinCache: List) { 20 | this.mixinCache = mixinCache 21 | this.mixinRefmapFile = mixinRefmapFile 22 | loadMixin() 23 | } 24 | 25 | private fun loadMixin() { 26 | MixinBootstrap.init() 27 | logger.info("Initializing Phantom mixins") 28 | Mixins.addConfiguration("mixins.phantom.loader.json") 29 | MixinEnvironment.getDefaultEnvironment().obfuscationContext = "searge" 30 | MixinEnvironment.getDefaultEnvironment().side = MixinEnvironment.Side.CLIENT 31 | logger.info("Phantom mixins initialized") 32 | logger.info(MixinEnvironment.getDefaultEnvironment().obfuscationContext) 33 | } 34 | 35 | } -------------------------------------------------------------------------------- /client/src/main/kotlin/net/spartanb312/phantom/launch/MixinPlugin.kt: -------------------------------------------------------------------------------- 1 | package net.spartanb312.phantom.launch 2 | 3 | import net.minecraft.launchwrapper.Launch 4 | import net.minecraftforge.fml.relauncher.FMLLaunchHandler 5 | import org.spongepowered.asm.mixin.extensibility.IMixinConfigPlugin 6 | import org.spongepowered.asm.mixin.extensibility.IMixinInfo 7 | import java.net.MalformedURLException 8 | 9 | /** 10 | * Author B_312 11 | * Use this to update mixin remotely 12 | */ 13 | class MixinPlugin : IMixinConfigPlugin { 14 | 15 | private var mixins: MutableList = ArrayList() 16 | 17 | override fun onLoad(mixinPackage: String) { 18 | try { 19 | Launch.classLoader.addURL(InitializationManager.mixinRefmapFile.toURI().toURL()) 20 | } catch (e: MalformedURLException) { 21 | e.printStackTrace() 22 | } 23 | 24 | InitializationManager.mixinRefmapFile.deleteOnExit() 25 | 26 | InitializationManager.mixinCache.forEach { 27 | mixins.add(it) 28 | if (FMLLaunchHandler.isDeobfuscatedEnvironment()) { 29 | runCatching { 30 | Launch.classLoader.loadClass(it) 31 | } 32 | } 33 | } 34 | } 35 | 36 | override fun getRefMapperConfig(): String { 37 | return try { 38 | InitializationManager.mixinRefmapFile.toURI().toURL().toString() 39 | } catch (e: MalformedURLException) { 40 | "mixins.phantom.refmap.json" 41 | } 42 | } 43 | 44 | override fun shouldApplyMixin(targetClassName: String?, mixinClassName: String?): Boolean { 45 | return true 46 | } 47 | 48 | override fun acceptTargets(myTargets: MutableSet?, otherTargets: MutableSet?) { 49 | } 50 | 51 | override fun getMixins(): MutableList { 52 | return mixins 53 | } 54 | 55 | override fun preApply( 56 | targetClassName: String?, 57 | targetClass: org.objectweb.asm.tree.ClassNode?, 58 | mixinClassName: String?, 59 | mixinInfo: IMixinInfo? 60 | ) { 61 | } 62 | 63 | override fun postApply( 64 | targetClassName: String?, 65 | targetClass: org.objectweb.asm.tree.ClassNode?, 66 | mixinClassName: String?, 67 | mixinInfo: IMixinInfo? 68 | ) { 69 | } 70 | 71 | } -------------------------------------------------------------------------------- /client/src/main/kotlin/net/spartanb312/phantom/util/Logger.kt: -------------------------------------------------------------------------------- 1 | package net.spartanb312.phantom.util 2 | 3 | import org.apache.logging.log4j.LogManager 4 | 5 | object Logger { 6 | 7 | private val logger = LogManager.getLogger("Phantom") 8 | 9 | fun debug(str: String) = logger.debug(str) 10 | 11 | fun info(str: String) = logger.info(str) 12 | 13 | fun warn(str: String) = logger.warn(str) 14 | 15 | fun error(str: String) = logger.error(str) 16 | 17 | fun fatal(str: String) = logger.fatal(str) 18 | 19 | } -------------------------------------------------------------------------------- /client/src/main/kotlin/phantomloader/AES.kt: -------------------------------------------------------------------------------- 1 | package phantomloader 2 | 3 | import java.util.* 4 | import javax.crypto.Cipher 5 | import javax.crypto.spec.SecretKeySpec 6 | 7 | /** 8 | * Use this to encrypt 9 | */ 10 | object AES { 11 | 12 | private const val STANDARD_NAME = "AES" 13 | private const val SPARTAN_KEY = "spartanb312isgod" 14 | 15 | fun String.encode(key: String): String = Cipher.getInstance(STANDARD_NAME).let { 16 | val secretKeySpec = SecretKeySpec(key.processKey().toByteArray(), STANDARD_NAME) 17 | it.init(Cipher.ENCRYPT_MODE, secretKeySpec) 18 | val result = it.doFinal(this.toByteArray()) 19 | String(Base64.getEncoder().encode(result)) 20 | } 21 | 22 | fun String.decode(key: String): String = Cipher.getInstance(STANDARD_NAME).let { 23 | val secretKeySpec = SecretKeySpec(key.processKey().toByteArray(), STANDARD_NAME) 24 | it.init(Cipher.DECRYPT_MODE, secretKeySpec) 25 | val result = it.doFinal(Base64.getDecoder().decode(this)) 26 | String(result) 27 | } 28 | 29 | private fun String.processKey(): String = when { 30 | this.length < 16 -> { 31 | this + SPARTAN_KEY.substring(this.length) 32 | } 33 | this.length > 16 -> { 34 | this.substring(0, 8) + this.substring(this.length - 8, this.length) 35 | } 36 | else -> this 37 | } 38 | 39 | } -------------------------------------------------------------------------------- /client/src/main/kotlin/phantomloader/FMLCoreMod.kt: -------------------------------------------------------------------------------- 1 | package phantomloader 2 | 3 | import net.minecraftforge.fml.relauncher.IFMLLoadingPlugin 4 | 5 | /** 6 | * Entry of the loader 7 | */ 8 | class FMLCoreMod : IFMLLoadingPlugin { 9 | 10 | init { 11 | launch() 12 | } 13 | 14 | override fun getModContainerClass(): String? = null 15 | 16 | override fun getASMTransformerClass(): Array = emptyArray() 17 | 18 | override fun getSetupClass(): String? = null 19 | 20 | override fun injectData(data: Map) { 21 | } 22 | 23 | override fun getAccessTransformerClass(): String? { 24 | return null 25 | } 26 | 27 | } -------------------------------------------------------------------------------- /client/src/main/kotlin/phantomloader/Loader.kt: -------------------------------------------------------------------------------- 1 | package phantomloader 2 | 3 | import net.spartanb312.phantom.launch.InitializationManager 4 | import phantomloader.AES.encode 5 | import phantomloader.MixinCache.getMixins 6 | import phantomloader.MixinCache.getRefMapFile 7 | import java.io.DataInputStream 8 | import java.io.DataOutputStream 9 | import java.net.Socket 10 | 11 | fun launch() { 12 | val socket = Socket("127.0.0.1", 31212) 13 | val inputF = DataInputStream(socket.getInputStream()) 14 | val outputF = DataOutputStream(socket.getOutputStream()) 15 | outputF.writeUTF("[HWID]$hardwareId") 16 | PhantomClassLoader(inputF) 17 | InitializationManager.init(MixinCache.refmapBytes.getRefMapFile(), MixinCache.mixinBytes.getMixins()) 18 | } 19 | 20 | val hardwareId 21 | get() = (System.getenv("PROCESS_IDENTIFIER") 22 | + System.getenv("PROCESSOR_LEVEL") 23 | + System.getenv("PROCESSOR_REVISION") 24 | + System.getenv("PROCESSOR_ARCHITECTURE") 25 | + System.getenv("PROCESSOR_ARCHITEW6432") 26 | + System.getenv("NUMBER_OF_PROCESSORS") 27 | + System.getenv("COMPUTERNAME")).encode("MissingInAction") -------------------------------------------------------------------------------- /client/src/main/kotlin/phantomloader/MixinCache.kt: -------------------------------------------------------------------------------- 1 | package phantomloader 2 | 3 | import com.google.gson.Gson 4 | import com.google.gson.JsonObject 5 | import java.io.File 6 | import java.io.FileOutputStream 7 | import java.nio.charset.StandardCharsets 8 | 9 | object MixinCache { 10 | 11 | lateinit var mixinBytes: ByteArray 12 | lateinit var refmapBytes: ByteArray 13 | 14 | private val tempDir: String = System.getProperty("java.io.tmpdir") 15 | private val nextTempFile get() = File(tempDir, "+~JF$randomString.tmp") 16 | private val randomString: String 17 | get() { 18 | val allowedChars = ('0'..'9') + ('a'..'z') + ('A'..'Z') 19 | return (1..18) 20 | .map { allowedChars.random() } 21 | .joinToString("") 22 | } 23 | 24 | fun ByteArray.getRefMapFile(): File { 25 | return nextTempFile.apply { 26 | try { 27 | FileOutputStream(this).let { 28 | it.write(this@getRefMapFile) 29 | it.flush() 30 | it.close() 31 | this.deleteOnExit() 32 | } 33 | } catch (exception: Exception) { 34 | exception.printStackTrace() 35 | } 36 | } 37 | } 38 | 39 | fun ByteArray.getMixins(): List { 40 | val mixinCache = mutableListOf() 41 | Gson().fromJson( 42 | String(this, StandardCharsets.UTF_8), 43 | JsonObject::class.java 44 | ).apply { 45 | getAsJsonArray("client")?.forEach { 46 | mixinCache.add(it.asString) 47 | } 48 | getAsJsonArray("mixins")?.forEach { 49 | mixinCache.add(it.asString) 50 | } 51 | } 52 | return mixinCache 53 | } 54 | 55 | } -------------------------------------------------------------------------------- /client/src/main/kotlin/phantomloader/PhantomClassLoader.kt: -------------------------------------------------------------------------------- 1 | package phantomloader 2 | 3 | import net.minecraft.launchwrapper.Launch 4 | import net.minecraft.launchwrapper.LaunchClassLoader 5 | import java.io.InputStream 6 | import java.util.zip.ZipInputStream 7 | 8 | class PhantomClassLoader(inputF: InputStream) { 9 | 10 | init { 11 | @Suppress("UNCHECKED_CAST") 12 | val resourceCache = LaunchClassLoader::class.java.getDeclaredField("resourceCache").let { 13 | it.isAccessible = true 14 | it[Launch.classLoader] as MutableMap 15 | } 16 | ZipInputStream(inputF).use { zipStream -> 17 | //Define classes and get mixin files 18 | while (true) { 19 | val zipEntry = zipStream.nextEntry ?: break 20 | if (zipEntry.name.endsWith(".class")) { 21 | resourceCache[zipEntry.name.removeSuffix(".class").replace('/', '.')] = zipStream.readBytes() 22 | } else { 23 | //Cache the mixin 24 | if (zipEntry.name == "mixins.phantom.json") { 25 | MixinCache.mixinBytes = zipStream.readBytes() 26 | } else if (zipEntry.name == "mixins.phantom.refmap.json") { 27 | MixinCache.refmapBytes = zipStream.readBytes() 28 | } 29 | } 30 | } 31 | } 32 | } 33 | 34 | } -------------------------------------------------------------------------------- /client/src/main/resources/mixins.phantom.json: -------------------------------------------------------------------------------- 1 | { 2 | "required": false, 3 | "compatibilityLevel": "JAVA_8", 4 | "package": "net.spartanb312.phantom.mixin", 5 | "minVersion": "0.7", 6 | "refmap": "mixins.epsilon.refmap.json", 7 | "client": [ 8 | "MixinMinecraft" 9 | ] 10 | } -------------------------------------------------------------------------------- /client/src/main/resources/mixins.phantom.loader.json: -------------------------------------------------------------------------------- 1 | { 2 | "plugin": "net.spartanb312.phantom.launch.MixinPlugin", 3 | "required": false, 4 | "package": "net.spartanb312.phantom.mixin", 5 | "compatibilityLevel": "JAVA_8" 6 | } -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | kotlin.code.style=official 2 | org.gradle.jvmargs=-Xmx16G -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpartanB312/Verificator/027010dbb9b4414a19fdc060af43e975edbb8977/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /server/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | java 3 | kotlin("jvm") version "1.6.10" 4 | } 5 | 6 | group = "net.spartanb312" 7 | version = "1.0" 8 | 9 | repositories { 10 | mavenCentral() 11 | } 12 | 13 | val kotlinxCoroutineVersion = "1.5.2" 14 | val library: Configuration by configurations.creating 15 | 16 | dependencies { 17 | library("org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlinxCoroutineVersion") 18 | implementation(library) 19 | } 20 | 21 | tasks { 22 | compileJava { 23 | options.encoding = "UTF-8" 24 | sourceCompatibility = "1.8" 25 | targetCompatibility = "1.8" 26 | } 27 | 28 | compileKotlin { 29 | kotlinOptions { 30 | jvmTarget = "1.8" 31 | freeCompilerArgs = listOf("-Xopt-in=kotlin.RequiresOptIn", "-Xinline-classes") 32 | } 33 | } 34 | 35 | jar { 36 | manifest { 37 | attributes( 38 | "Main-Class" to "net.spartanb312.phantom.server.MainKt" 39 | ) 40 | } 41 | 42 | duplicatesStrategy = org.gradle.api.file.DuplicatesStrategy.EXCLUDE 43 | 44 | from( 45 | library.map { 46 | if (it.isDirectory) it 47 | else zipTree(it) 48 | } 49 | ) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /server/gradle.properties: -------------------------------------------------------------------------------- 1 | kotlin.code.style=official 2 | org.gradle.jvmargs=-Xmx4G 3 | modGroup=net.spartanb312 4 | modVersion=1.0 5 | kotlinVersion=1.6.10 6 | kotlinxCoroutineVersion=1.5.2 -------------------------------------------------------------------------------- /server/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpartanB312/Verificator/027010dbb9b4414a19fdc060af43e975edbb8977/server/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /server/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /server/src/main/kotlin/net/spartanb312/phantom/server/Main.kt: -------------------------------------------------------------------------------- 1 | package net.spartanb312.phantom.server 2 | 3 | import kotlinx.coroutines.Dispatchers 4 | import kotlinx.coroutines.launch 5 | import kotlinx.coroutines.runBlocking 6 | import net.spartanb312.phantom.server.utils.HWIDManager 7 | import net.spartanb312.phantom.server.utils.LogUtil.log 8 | import java.io.DataInputStream 9 | import java.io.DataOutputStream 10 | import java.io.FileInputStream 11 | import java.net.ServerSocket 12 | import java.net.Socket 13 | 14 | const val SERVER_PORT = 31212 15 | const val SERVER_FILE_PATH = "server/" 16 | const val CLIENT_FILE_PATH = "client.jar" 17 | 18 | fun main() = runBlocking { 19 | 20 | log("Loading Server...") 21 | HWIDManager.loadHWID() 22 | val tcpServer = ServerSocket(SERVER_PORT) 23 | 24 | launch(Dispatchers.Default) { 25 | while (!tcpServer.isClosed) { 26 | val inputCommand = readLine() 27 | if (inputCommand != null) onCommand(inputCommand) 28 | } 29 | } 30 | 31 | log("Server loaded!") 32 | 33 | while (!tcpServer.isClosed) { 34 | log("Waiting for connections.") 35 | val clientSocket: Socket = tcpServer.accept() 36 | 37 | launch(Dispatchers.IO) { 38 | 39 | val input = DataInputStream(clientSocket.getInputStream()) 40 | val output = DataOutputStream(clientSocket.getOutputStream()) 41 | 42 | while (!clientSocket.isClosed) { 43 | 44 | val received: String = input.readUTF() 45 | log("IP(" + clientSocket.inetAddress.hostAddress.toString() + ")Send :" + received) 46 | 47 | if (!received.startsWith("[")) { 48 | clientSocket.close() 49 | log("Socket Closed") 50 | } 51 | 52 | var prefix: String 53 | var content: String 54 | 55 | try { 56 | prefix = received.substring(0, received.indexOf("]") + 1) 57 | content = received.substring(received.indexOf("]") + 1) 58 | } catch (e: Exception) { 59 | continue 60 | } 61 | 62 | if (prefix == "[HWID]") { 63 | if (HWIDManager.checkHWID(content)) { 64 | log("Sending file...") 65 | output.sendFile(CLIENT_FILE_PATH) 66 | clientSocket.close() 67 | } else { 68 | output.writeUTF("[FUCK]") 69 | log("Not passed verification!") 70 | clientSocket.close() 71 | } 72 | continue 73 | } 74 | } 75 | } 76 | } 77 | } 78 | 79 | fun onCommand(input: String) { 80 | val args = input.split(" ").toTypedArray() 81 | 82 | if (args[0].equals("/help", ignoreCase = true)) { 83 | log("Phantom Server Console V0.1") 84 | log("Author B_312") 85 | log("Commands: ") 86 | log("/list (Get all hwids)") 87 | log("/add (Add HWID)") 88 | log("/remove (Remove HWID)") 89 | } 90 | 91 | when { 92 | args[0].equals("/add", ignoreCase = true) -> { 93 | if (args.size <= 1) { 94 | log("Usage /add ") 95 | return 96 | } 97 | log("Added HWID " + args[1]) 98 | HWIDManager.addHWID(args[1]) 99 | HWIDManager.saveHWID() 100 | } 101 | args[0].equals("/list", ignoreCase = true) -> { 102 | log(HWIDManager.HWID.toString()) 103 | } 104 | args[0].equals("/remove", ignoreCase = true) -> { 105 | if (args.size <= 1) { 106 | log("Usage /remove ") 107 | return 108 | } 109 | log("Removed HWID " + args[1]) 110 | HWIDManager.removeHWID(args[1]) 111 | HWIDManager.saveHWID() 112 | } 113 | } 114 | } 115 | 116 | 117 | fun DataOutputStream.sendFile(filePath: String) { 118 | val fileStream = DataInputStream(FileInputStream(filePath)) 119 | val startTime = System.currentTimeMillis() 120 | try { 121 | val bufferSize = 8192 122 | val buf = ByteArray(bufferSize) 123 | while (true) { 124 | val read: Int = fileStream.read(buf) 125 | if (read == -1) { 126 | break 127 | } 128 | write(buf, 0, read) 129 | } 130 | flush() 131 | } catch (ignore: Exception) { 132 | log("Sending file failed") 133 | } 134 | fileStream.close() 135 | log("Finished sending file in ${System.currentTimeMillis() - startTime} ms") 136 | } 137 | 138 | -------------------------------------------------------------------------------- /server/src/main/kotlin/net/spartanb312/phantom/server/utils/HWIDManager.kt: -------------------------------------------------------------------------------- 1 | package net.spartanb312.phantom.server.utils 2 | 3 | import net.spartanb312.phantom.server.SERVER_FILE_PATH 4 | import java.io.* 5 | import java.lang.Exception 6 | import java.util.concurrent.CopyOnWriteArrayList 7 | 8 | object HWIDManager { 9 | 10 | var HWID = CopyOnWriteArrayList() 11 | private const val CONFIG_PATH = SERVER_FILE_PATH 12 | private const val HWID_CONFIG = CONFIG_PATH + "HWID.json" 13 | 14 | private fun toFiles(): File { 15 | return File(HWID_CONFIG) 16 | } 17 | 18 | fun addHWID(name: String) { 19 | if (!HWID.contains(name)) { 20 | HWID.add(name) 21 | } 22 | } 23 | 24 | fun removeHWID(name: String) { 25 | HWID.remove(name) 26 | } 27 | 28 | fun checkHWID(name: String): Boolean { 29 | return HWID.contains(name) || HWID.contains("[ALL]") 30 | } 31 | 32 | fun saveHWID() { 33 | if (!toFiles().exists()) { 34 | toFiles().parentFile.mkdirs() 35 | try { 36 | toFiles().createNewFile() 37 | } catch (e4: IOException) { 38 | e4.printStackTrace() 39 | } 40 | } 41 | try { 42 | val writer = BufferedWriter(FileWriter(HWID_CONFIG, false)) 43 | for (hwid in HWID) { 44 | writer.write(hwid) 45 | writer.flush() 46 | writer.newLine() 47 | } 48 | writer.close() 49 | } catch (e: Exception) { 50 | } 51 | } 52 | 53 | fun loadHWID() { 54 | if (toFiles().exists()) { 55 | try { 56 | val reader = BufferedReader(FileReader(HWID_CONFIG)) 57 | var line: String 58 | while (reader.readLine().also { line = it } != null) { 59 | addHWID(line) 60 | } 61 | reader.close() 62 | } catch (ex: Exception) { 63 | } 64 | } else { 65 | saveHWID() 66 | } 67 | } 68 | 69 | } -------------------------------------------------------------------------------- /server/src/main/kotlin/net/spartanb312/phantom/server/utils/LogUtil.kt: -------------------------------------------------------------------------------- 1 | package net.spartanb312.phantom.server.utils 2 | 3 | import net.spartanb312.phantom.server.SERVER_FILE_PATH 4 | import java.io.BufferedWriter 5 | import java.io.File 6 | import java.io.FileWriter 7 | import java.io.IOException 8 | import java.text.SimpleDateFormat 9 | import java.util.* 10 | 11 | object LogUtil { 12 | 13 | private lateinit var bufferedWriter: BufferedWriter 14 | 15 | private var file: File = File("${SERVER_FILE_PATH}logs/" + getDate() + "log.txt") 16 | 17 | init { 18 | try { 19 | if (!file.exists()) { 20 | try { 21 | file.parentFile.mkdirs() 22 | file.createNewFile() 23 | } catch (e: Exception) { 24 | e.printStackTrace() 25 | } 26 | } 27 | bufferedWriter = BufferedWriter(FileWriter(file)) 28 | } catch (ioException: IOException) { 29 | ioException.printStackTrace() 30 | } 31 | } 32 | 33 | fun log(msg: String) { 34 | println("[PhantomServer][" + getDate() + "]" + msg) 35 | saveLog("[PhantomServer][" + getDate() + "]" + msg) 36 | } 37 | 38 | private fun saveLog(string: String) { 39 | try { 40 | bufferedWriter.write(string) 41 | bufferedWriter.flush() 42 | bufferedWriter.newLine() 43 | } catch (e: Exception) { 44 | e.printStackTrace() 45 | } 46 | } 47 | 48 | private fun getDate(): String { 49 | val date = Date() 50 | val format = SimpleDateFormat("MM-dd hh-mm-ss") 51 | return format.format(date) 52 | } 53 | } -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = "Phantom" 2 | include "client","server" --------------------------------------------------------------------------------