Skip to content

Commit

Permalink
fix: launching runelite via linux AppImage launcher
Browse files Browse the repository at this point in the history
  • Loading branch information
notmeta committed Nov 24, 2024
1 parent 2ad2051 commit 2f1c489
Show file tree
Hide file tree
Showing 6 changed files with 131 additions and 44 deletions.
5 changes: 3 additions & 2 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import org.jetbrains.kotlin.gradle.plugin.KotlinPluginWrapper
import java.security.MessageDigest
import kotlin.io.path.fileSize

val mainClass = "net.rsprox.gui.ProxyToolGuiKt"
val s3Bucket = "cdn.rsprox.net"

plugins {
Expand Down Expand Up @@ -158,7 +159,7 @@ tasks.register("uploadJarsToS3") {
}

val bootstrap = Bootstrap(
proxy = Proxy(version = project.version.toString(), mainClass = "net.rsprox.gui.ProxyToolGuiKt"),
proxy = Proxy(version = project.version.toString(), mainClass = mainClass),
artifacts = artifacts.sortedBy { it.name },
)

Expand All @@ -171,7 +172,7 @@ tasks.create<JavaExec>("proxy") {
environment("APP_VERSION", project.version)
group = "run"
classpath = sourceSets["main"].runtimeClasspath
mainClass.set("net.rsprox.gui.ProxyToolGuiKt")
mainClass.set(mainClass)
}

tasks.create<JavaExec>("download") {
Expand Down
2 changes: 2 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ junixsocket = "2.10.0"
gson = "2.11.0"
aws-sdk-kotlin = "1.3.13"
jaxb-api = "2.3.1"
jopt-simple = "5.0.1"

[libraries]
netty-bom = { module = "io.netty:netty-bom", version.ref = "netty" }
Expand Down Expand Up @@ -57,6 +58,7 @@ zip4j = { module = "net.lingala.zip4j:zip4j", version.ref = "zip4j" }
junixsocket = { module = "com.kohlschutter.junixsocket:junixsocket-core", version.ref = "junixsocket"}
aws-sdk-kotlin-s3 = { module = "aws.sdk.kotlin:s3", version.ref = "aws-sdk-kotlin" }
jaxb-api = { module = "javax.xml.bind:jaxb-api", version.ref = "jaxb-api" }
jopt-simple = { module = "net.sf.jopt-simple:jopt-simple", version.ref = "jopt-simple" }

[plugins]
kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
Expand Down
1 change: 1 addition & 0 deletions launcher/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ dependencies {
implementation(libs.inline.logger)
implementation(libs.okhttp3)
implementation(libs.gson)
implementation(libs.jopt.simple)
}

tasks.withType<ShadowJar> {
Expand Down
117 changes: 100 additions & 17 deletions launcher/src/main/kotlin/net/rsprox/launcher/Launcher.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,30 +2,87 @@ package net.rsprox.launcher

import com.github.michaelbull.logging.InlineLogger
import com.google.gson.Gson
import joptsimple.OptionParser
import net.rsprox.gui.SplashScreen
import okhttp3.OkHttpClient
import okhttp3.Request
import java.io.ByteArrayInputStream
import java.io.File
import java.io.FileNotFoundException
import java.io.IOException
import java.io.InputStreamReader
import java.io.*
import java.net.URLClassLoader
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.Paths
import java.security.MessageDigest
import java.security.Signature
import java.security.cert.Certificate
import java.security.cert.CertificateFactory
import java.util.Locale
import java.util.*
import java.util.stream.Collectors
import javax.swing.UIManager
import kotlin.io.path.Path
import kotlin.io.path.absolutePathString

public fun main(args: Array<String>) {
val logger = InlineLogger()
val parser = OptionParser(false)
parser.allowsUnrecognizedOptions()
parser.accepts("runelite", "Whether we are launching the RuneLite client")
parser.accepts("classpath", "Classpath for the process are are launching").withRequiredArg()

val options = parser.parse(*args)
if (options.has("classpath")) {
// Sometimes we will be getting launched as a process by an existing RSProx process or a new runelite
// process, e.g. from a Linux AppImage launcher. In this case we need to load the classpath and launch
// the main class ourselves using reflection, passing along any necessary arguments.

val loadingRunelite = options.has("runelite")
val classpathOpt = options.valueOf("classpath").toString()
val classpath = classpathOpt.split(File.pathSeparator).stream().map {
if (loadingRunelite) {
// runelite-launcher doesn't pass the fully qualified paths of jars, so construct them ourselves
Paths.get(System.getProperty("user.home"), ".runelite", "repository2", it).toFile()
} else {
Paths.get(it).toFile()
}
}.collect(Collectors.toList())

val jarUrls = classpath.map { it.toURI().toURL() }.toTypedArray()
val parent = ClassLoader.getPlatformClassLoader()
val loader = URLClassLoader(jarUrls, parent)

UIManager.put("ClassLoader", loader)
val thread = Thread {
try {
val mainClassPath = if (loadingRunelite) "net.runelite.client.RuneLite" else listOf("net" +
".runelite.launcher.Launcher", "net.rsprox.gui.ProxyToolGuiKt").first { it in args}
val mainClass = loader.loadClass(mainClassPath)
val mainArgs = if (loadingRunelite) {
// RuneLite doesn't allow unrecognised arguments so only use arguments after --classpath
args.copyOfRange(args.indexOfFirst { it == "--classpath" } + 2, args.size)
}else {
args.copyOfRange(args.indexOfFirst { it == mainClass.name } + 1, args.size)
}

logger.info { "Launching process using reflection: $mainClassPath ${mainArgs.joinToString(", ")}" }

val main = mainClass.getMethod("main", Array<String>::class.java)
main.invoke(null, mainArgs)
} catch (ex: Exception) {
ex.printStackTrace()
}
}
thread.name = "RSProx"
thread.start()

return
}

Locale.setDefault(Locale.US)
SplashScreen.init()
SplashScreen.stage(0.0, "Preparing", "Setting up environment")
val launcher = Launcher()

val launcher = Launcher(args)
val launcherArgs = launcher.getLaunchArgs(args)
logger.info { "Running process: ${launcherArgs.joinToString(" ")}" }

val builder =
ProcessBuilder()
Expand Down Expand Up @@ -53,11 +110,23 @@ public data class Bootstrap(
val proxy: Proxy,
)

public class Launcher {
public class Launcher(args: Array<String>) {
private val bootstrap = getBootstrap()

init {
logger.info { "Initialising RSProx launcher ${bootstrap.proxy.version}" }
logger.info {
"OS name: ${System.getProperty("os.name")}, version: ${System.getProperty("os.version")}, arch: ${
System.getProperty(
"os.arch"
)
}"
}
logger.info { "Java path: ${getJava()}" }
logger.info { "Java version: ${System.getProperty("java.version")}" }
logger.info { "Launcher args: ${args.joinToString(",")}" }
logger.info { "AppImage: ${System.getenv("APPIMAGE")}" }

SplashScreen.stage(0.30, "Preparing", "Creating directories")
Files.createDirectories(artifactRepo)
}
Expand All @@ -66,7 +135,7 @@ public class Launcher {
val javaHome = Paths.get(System.getProperty("java.home"))

if (!Files.exists(javaHome)) {
throw FileNotFoundException("JAVA_HOME is not set correctly! directory \"$javaHome\" does not exist.")
throw FileNotFoundException("JAVA_HOME is not set correctly! directory '$javaHome' does not exist.")
}

var javaPath = Paths.get(javaHome.toString(), "bin", "java.exe")
Expand All @@ -76,7 +145,7 @@ public class Launcher {
}

if (!Files.exists(javaPath)) {
throw FileNotFoundException("java executable not found in directory \"" + javaPath.parent + "\"")
throw FileNotFoundException("java executable not found in directory '${javaPath.parent}'")
}

return javaPath.toAbsolutePath().toString()
Expand All @@ -94,13 +163,27 @@ public class Launcher {
classpath.append(artifactRepo.resolve(artifact.name).absolutePathString())
}

return listOf(
getJava(),
"-cp",
classpath.toString(),
bootstrap.proxy.mainClass,
*launcherArgs
)
if (System.getenv("APPIMAGE") != null) {
return listOf(
System.getenv("APPIMAGE"),
"-c",
"-J",
"-XX:+DisableAttachMechanism",
"--",
"--classpath",
classpath.toString(),
bootstrap.proxy.mainClass,
*launcherArgs
)
} else {
return listOf(
getJava(),
"-cp",
classpath.toString(),
bootstrap.proxy.mainClass,
*launcherArgs
)
}
}

private fun download() {
Expand Down
28 changes: 3 additions & 25 deletions proxy/src/main/kotlin/net/rsprox/proxy/ProxyService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -54,14 +54,12 @@ import org.bouncycastle.crypto.params.RSAPrivateCrtKeyParameters
import org.newsclub.net.unix.AFUNIXServerSocket
import org.newsclub.net.unix.AFUNIXSocketAddress
import java.io.File
import java.io.FileNotFoundException
import java.io.IOException
import java.math.BigInteger
import java.net.URL
import java.nio.file.Files
import java.nio.file.LinkOption
import java.nio.file.Path
import java.nio.file.Paths
import java.util.*
import java.util.concurrent.TimeUnit
import java.util.stream.Collectors
Expand Down Expand Up @@ -378,26 +376,6 @@ public class ProxyService(
}
}

private fun getJava(): String {
val javaHome = Paths.get(System.getProperty("java.home"))

if (!Files.exists(javaHome)) {
throw FileNotFoundException("JAVA_HOME is not set correctly! directory \"$javaHome\" does not exist.")
}

var javaPath = Paths.get(javaHome.toString(), "bin", "java.exe")

if (!Files.exists(javaPath)) {
javaPath = Paths.get(javaHome.toString(), "bin", "java")
}

if (!Files.exists(javaPath)) {
throw FileNotFoundException("java executable not found in directory \"" + javaPath.parent + "\"")
}

return javaPath.toAbsolutePath().toString()
}

public fun launchRuneLiteClient(
sessionMonitor: SessionMonitor<BinaryHeader>,
character: JagexCharacter?,
Expand All @@ -411,7 +389,7 @@ public class ProxyService(
}
this.connections.addSessionMonitor(port, sessionMonitor)
ClientTypeDictionary[port] = "RuneLite (${operatingSystem.shortName})"
launchJar(
launchJavaProcess(
port,
operatingSystem,
character,
Expand Down Expand Up @@ -496,7 +474,7 @@ public class ProxyService(
launchExecutable(port, result.outputPath, os, character)
}

private fun launchJar(
private fun launchJavaProcess(
port: Int,
operatingSystem: OperatingSystem,
character: JagexCharacter?,
Expand All @@ -511,7 +489,7 @@ public class ProxyService(
try {
val javConfigEndpoint = properties.getProperty(JAV_CONFIG_ENDPOINT)
val launcher = RuneliteLauncher()
val args = listOf(getJava()) + launcher.getLaunchArgs(
val args = launcher.getLaunchArgs(
port,
rsa.publicKey.modulus.toString(16),
javConfig = "http://127.0.0.1:$HTTP_SERVER_PORT/$javConfigEndpoint",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import okhttp3.OkHttpClient
import okhttp3.Request
import java.io.*
import java.nio.file.Files
import java.nio.file.Paths
import java.security.MessageDigest
import java.security.Signature
import java.security.cert.Certificate
Expand Down Expand Up @@ -37,6 +38,26 @@ public class RuneliteLauncher {
logger.info { "Initialising RuneLite launcher ${bootstrap.launcher.version}" }
}

private fun getJava(): String {
val javaHome = Paths.get(System.getProperty("java.home"))

if (!Files.exists(javaHome)) {
throw FileNotFoundException("JAVA_HOME is not set correctly! directory \"$javaHome\" does not exist.")
}

var javaPath = Paths.get(javaHome.toString(), "bin", "java.exe")

if (!Files.exists(javaPath)) {
javaPath = Paths.get(javaHome.toString(), "bin", "java")
}

if (!Files.exists(javaPath)) {
throw FileNotFoundException("java executable not found in directory \"" + javaPath.parent + "\"")
}

return javaPath.toAbsolutePath().toString()
}

public fun getLaunchArgs(
port: Int,
rsa: String,
Expand All @@ -56,6 +77,7 @@ public class RuneliteLauncher {
}

return listOf(
getJava(),
"-cp",
classpath.toString(),
bootstrap.launcher.mainClass,
Expand Down

0 comments on commit 2f1c489

Please sign in to comment.