Skip to content
This repository was archived by the owner on Jan 24, 2026. It is now read-only.

Commit 42f5c74

Browse files
committed
Refactor game launch process and update build configurations
1 parent c0c329a commit 42f5c74

File tree

6 files changed

+134
-482
lines changed

6 files changed

+134
-482
lines changed

.github/workflows/build-native-release.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,11 @@ jobs:
1515
- name: Checkout Repository
1616
uses: actions/checkout@v4
1717

18-
- name: Set up GraalVM JDK 21
19-
uses: graalvm/setup-graalvm@v1
18+
- name: Set up JDK 17
19+
uses: actions/setup-java@v3
2020
with:
21-
distribution: 'graalvm'
22-
java-version: '21'
21+
distribution: 'temurin'
22+
java-version: '17'
2323

2424
- name: Grant execute permission for Gradle (Linux & macOS)
2525
if: runner.os != 'Windows'

.github/workflows/build-native.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,11 @@ jobs:
1414
- name: Checkout Repository
1515
uses: actions/checkout@v4
1616

17-
- name: Set up GraalVM JDK 21
18-
uses: graalvm/setup-graalvm@v1
17+
- name: Set up JDK 17
18+
uses: actions/setup-java@v3
1919
with:
20-
distribution: 'graalvm'
21-
java-version: '21'
20+
distribution: 'temurin'
21+
java-version: '17'
2222

2323
- name: Grant execute permission for Gradle
2424
run: chmod +x gradlew

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,3 +167,4 @@ Thumbs.db
167167
temp/
168168
versions/
169169
jdk/
170+
releases.json

core/src/main/kotlin/dev/ultreon/launcher/Main.kt

Lines changed: 115 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -128,22 +128,24 @@ private fun BitmapFont.width(text: String): Float {
128128
return layout.width
129129
}
130130

131-
private fun launchGame(version: GameVersion) {
132-
if (version.id in arrayOf("0.0.0-indev", "0.0.1-indev")) {
131+
var runningProcess: Process? = null
132+
133+
private fun launchGame(version: GameVersion, button: Button): Process =
134+
if (version.id in arrayOf("0.0.0-indev", "0.0.1-indev") || version is ChannelVersion) {
133135
if (System.getProperty("os.name").startsWith("Windows")) {
134-
ProcessBuilder("cmd", "/c", "gradlew.bat lwjgl3:run").run {
136+
ProcessBuilder("cmd", "/c", "gradlew.bat --no-daemon lwjgl3:run").run {
135137
environment()["PATH"] = "$JAVA_HOME\\bin:${System.getenv("PATH")}"
136138
environment()["JAVA_HOME"] = JAVA_HOME
137139
directory(File("versions/${version.id}/"))
138140
}.inheritIO().start()
139141
} else if (System.getProperty("os.name").startsWith("Linux")) {
140-
ProcessBuilder("bash", "-c", "chmod +x gradlew && ./gradlew lwjgl3:run").run {
142+
ProcessBuilder("bash", "-c", "chmod +x gradlew && ./gradlew --no-daemon lwjgl3:run").run {
141143
environment()["PATH"] = "$JAVA_HOME/bin:${System.getenv("PATH")}"
142144
environment()["JAVA_HOME"] = JAVA_HOME
143145
directory(File("versions/${version.id}/"))
144146
}.inheritIO().start()
145147
} else if (System.getProperty("os.name").startsWith("Mac")) {
146-
ProcessBuilder("bash", "-c", "chmod +x gradlew && ./gradlew lwjgl3:run").run {
148+
ProcessBuilder("bash", "-c", "chmod +x gradlew && ./gradlew --no-daemon lwjgl3:run").run {
147149
environment()["PATH"] = "$JAVA_HOME/bin:${System.getenv("PATH")}"
148150
environment()["JAVA_HOME"] = JAVA_HOME
149151
directory(File("versions/${version.id}/"))
@@ -154,42 +156,45 @@ private fun launchGame(version: GameVersion) {
154156
} else {
155157
if (System.getProperty("os.name").startsWith("Windows")) {
156158
ProcessBuilder(
157-
"..\\..\\$JAVA_HOME\\bin\\$JAVA_EXEC_NAME",
159+
"$JAVA_HOME\\bin\\$JAVA_EXEC_NAME",
158160
"-cp",
159161
"lib/*",
160162
"dev.ultreon.quantum.lwjgl3.Lwjgl3Launcher"
161163
).run {
162164
environment()["PATH"] = "${File(JAVA_HOME).absolutePath}\\bin:${System.getenv("PATH")}"
163165
environment()["JAVA_HOME"] = File(JAVA_HOME).absolutePath
164166
directory(File("versions/${version.id}/"))
165-
}
167+
}.inheritIO().start()
166168
} else if (System.getProperty("os.name").startsWith("Linux")) {
167169
ProcessBuilder(
168-
"../../$JAVA_HOME/bin/$JAVA_EXEC_NAME",
170+
"$JAVA_HOME/bin/$JAVA_EXEC_NAME",
169171
"-cp",
170172
"lib/*",
171173
"dev.ultreon.quantum.lwjgl3.Lwjgl3Launcher"
172174
).run {
173175
environment()["PATH"] = "${File(JAVA_HOME).absolutePath}/bin:${System.getenv("PATH")}"
174176
environment()["JAVA_HOME"] = File(JAVA_HOME).absolutePath
175177
directory(File("versions/${version.id}/"))
176-
}
178+
}.inheritIO().start()
177179
} else if (System.getProperty("os.name").startsWith("Mac")) {
178180
ProcessBuilder(
179-
"../../$JAVA_HOME/bin/$JAVA_EXEC_NAME",
181+
"$JAVA_HOME/bin/$JAVA_EXEC_NAME",
180182
"-cp",
181183
"lib/*",
182184
"dev.ultreon.quantum.lwjgl3.Lwjgl3Launcher"
183185
).run {
184186
environment()["PATH"] = "${File(JAVA_HOME).absolutePath}/bin:${System.getenv("PATH")}"
185187
environment()["JAVA_HOME"] = File(JAVA_HOME).absolutePath
186188
directory(File("versions/${version.id}/"))
187-
}
189+
}.inheritIO().start()
188190
} else {
189191
throw UnsupportedOperationException()
190-
}.inheritIO().start()
192+
}
193+
}.also {
194+
button.text = "Click to Stop"
195+
button.enabled = true
196+
runningProcess = it
191197
}
192-
}
193198

194199
class Downloader(
195200
private val url: String,
@@ -201,10 +206,14 @@ class Downloader(
201206
private var downloadedBytes: Long = 0
202207

203208
fun download() {
204-
thread {
209+
thread(isDaemon = false) {
205210
val connection = URL(url).openConnection()
206211
totalBytes = if (connection.contentLengthLong == -1L) totalBytes else connection.contentLengthLong
207212

213+
if (!Gdx.files.local("temp").exists()) {
214+
Gdx.files.local("temp").mkdirs()
215+
}
216+
208217
val file = RandomAccessFile(Gdx.files.local("temp/$name").file(), "rw")
209218

210219
connection.inputStream.use { inputStream ->
@@ -297,15 +306,15 @@ val JDK_URL = if (System.getProperty("os.name").startsWith("Windows")) {
297306
throw UnsupportedOperationException()
298307
}
299308

300-
val JAVA_HOME = if (System.getProperty("os.name").startsWith("Windows")) {
309+
val JAVA_HOME = File(if (System.getProperty("os.name").startsWith("Windows")) {
301310
"jdk/jdk-${JDK_VERSION.replace("_", "+")}"
302311
} else if (System.getProperty("os.name").startsWith("Linux")) {
303312
"jdk/jdk-${JDK_VERSION.replace("_", "+")}"
304313
} else if (System.getProperty("os.name").startsWith("Mac")) {
305314
"jdk/jdk-${JDK_VERSION.replace("_", "+")}/Contents/Home"
306315
} else {
307316
throw UnsupportedOperationException()
308-
}
317+
}).absolutePath
309318

310319
val JAVA_EXEC_NAME = if (System.getProperty("os.name").startsWith("Windows")) {
311320
"javaw.exe"
@@ -416,19 +425,32 @@ fun versionsFromGitHub(): List<GameVersion> {
416425

417426
fun unpackGame(version: GameVersion) {
418427
if (System.getProperty("os.name").startsWith("Windows")) {
428+
if (!Gdx.files.local("versions").exists()) {
429+
Gdx.files.local("versions").mkdirs()
430+
}
431+
419432
ZipFile(Gdx.files.local("temp/${version.id}").file()).use { zip ->
420433
if (!Gdx.files.local("versions/${version.id}").exists()) {
421434
Gdx.files.local("versions/${version.id}").mkdirs()
422435
}
423436
zip.entries().asSequence().forEach { entry ->
424437
zip.getInputStream(entry).use { inputStream ->
425-
if (!Gdx.files.local("versions/${version.id}/${entry.name.substringAfter('/')}").parent()
426-
.exists()
427-
) {
428-
Gdx.files.local("versions/${version.id}/${entry.name.substringAfter('/')}").parent().mkdirs()
438+
if (version.id in arrayOf("0.0.0-indev", "0.0.1-indev") || version is ChannelVersion) {
439+
if (!Gdx.files.local("versions/${version.id}/${entry.name.substringAfter('/')}").parent()
440+
.exists()
441+
) {
442+
Gdx.files.local("versions/${version.id}/${entry.name.substringAfter('/')}").parent().mkdirs()
443+
}
444+
Gdx.files.local("versions/${version.id}/${entry.name.substringAfter('/')}")
445+
.write(inputStream, false)
446+
} else {
447+
if (!Gdx.files.local("temp/${version.id}-extract").exists()) {
448+
Gdx.files.local("temp/${version.id}-extract").mkdirs()
449+
}
450+
Gdx.files.local("temp/${version.id}-extract/${entry.name.substringAfter('/')}").parent().mkdirs()
451+
Gdx.files.local("temp/${version.id}-extract/${entry.name.substringAfter('/')}")
452+
.write(inputStream, false)
429453
}
430-
Gdx.files.local("versions/${version.id}/${entry.name.substringAfter('/')}")
431-
.write(inputStream, false)
432454
}
433455
}
434456
}
@@ -448,7 +470,7 @@ fun unpackGame(version: GameVersion) {
448470
println("Failed to unpack ${version.id}")
449471
}
450472

451-
if (version.id in arrayOf("0.0.0-indev", "0.0.1-indev")) {
473+
if (version.id in arrayOf("0.0.0-indev", "0.0.1-indev") || version is ChannelVersion) {
452474
val exec2 = Runtime.getRuntime()
453475
.exec(arrayOf("mv", "${Gdx.files.local("temp/${version.id}-extract").list()[0]}", "versions/${version.id}"))
454476

@@ -491,7 +513,7 @@ fun unpackGame(version: GameVersion) {
491513
println("Failed to unpack ${version.id}")
492514
}
493515

494-
if (version.id in arrayOf("0.0.0-indev", "0.0.1-indev")) {
516+
if (version.id in arrayOf("0.0.0-indev", "0.0.1-indev") || version is ChannelVersion) {
495517
val arrayOf =
496518
arrayOf("mv", "${Gdx.files.local("temp/${version.id}-extract").list()[0]}", "versions/${version.id}")
497519
println(arrayOf.contentToString())
@@ -554,28 +576,61 @@ fun unpack(path: String, dest: String) {
554576
}
555577
}
556578

579+
fun killProcess(process: Process) {
580+
try {
581+
process.destroyForcibly()
582+
process.descendants().forEach {
583+
it.destroyForcibly()
584+
}
585+
process.waitFor()
586+
} catch (e: Exception) {
587+
println(e.message)
588+
}
589+
}
590+
557591
/** [com.badlogic.gdx.ApplicationListener] implementation shared by all platforms. */
558-
class Main : ApplicationAdapter() {
592+
object Main : ApplicationAdapter() {
593+
594+
private lateinit var looper: Thread
559595
private val spriteBatch by lazy { SpriteBatch() }
560596

561597
private val font by lazy { BitmapFont(Gdx.files.internal("luna_pixel.fnt")) }
562598

563599
private var selectedVersion: GameVersion? = null
564600

601+
private var triedDestoyingOnce = false
602+
565603
private val playButton by lazy {
566604
Button(font, callback = {
605+
val runningProcess1 = runningProcess
606+
if (runningProcess1 != null && runningProcess1.isAlive) {
607+
try {
608+
killProcess(runningProcess1)
609+
} catch (e: Exception) {
610+
println(e.message)
611+
}
612+
return@Button
613+
}
614+
615+
triedDestoyingOnce = false
567616
val version = selectedVersion ?: return@Button
568617

569618
enabled = false
570619

620+
if (version is ChannelVersion) {
621+
File("versions/${version.id}").deleteRecursively()
622+
}
623+
571624
if (!version.isDownloaded()) {
572625
downloadGame(version, this) {
626+
627+
File("temp").deleteRecursively()
573628
text = "Launching ${version.name}"
574-
launchGame(version)
629+
launchGame(version, this)
575630
}
576631
} else {
577632
text = "Launching ${version.name}"
578-
launchGame(version)
633+
launchGame(version, this)
579634
}
580635
})
581636
}
@@ -597,8 +652,8 @@ class Main : ApplicationAdapter() {
597652
}
598653

599654
private val background by lazy { Texture(Gdx.files.internal("background.png")) }
600-
601655
val width get() = Gdx.graphics.width.toFloat() / 2f
656+
602657
val height get() = Gdx.graphics.height.toFloat() / 2f
603658

604659
private var selectedButton: Button? = null
@@ -650,6 +705,26 @@ class Main : ApplicationAdapter() {
650705
return true
651706
}
652707
}
708+
709+
looper = thread(isDaemon = false) {
710+
while (true) {
711+
if (runningProcess != null && !runningProcess!!.isAlive) {
712+
runningProcess = null
713+
playButton.enabled = true
714+
playButton.text = "Play"
715+
}
716+
717+
try {
718+
Thread.sleep(1000)
719+
} catch (e: Exception) {
720+
return@thread
721+
}
722+
723+
if (Thread.interrupted()) {
724+
return@thread
725+
}
726+
}
727+
}
653728
}
654729

655730
var scrollY = 0f
@@ -698,4 +773,16 @@ class Main : ApplicationAdapter() {
698773
spriteBatch.end()
699774
}
700775
}
776+
777+
fun handleClose(): Boolean {
778+
val process = runningProcess
779+
if (process != null && process.isAlive) {
780+
killProcess(process)
781+
return false
782+
}
783+
if (this::looper.isInitialized) {
784+
looper.interrupt()
785+
}
786+
return true
787+
}
701788
}

lwjgl3/src/main/kotlin/dev/ultreon/launcher/lwjgl3/Lwjgl3Launcher.kt

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,26 @@ package dev.ultreon.launcher.lwjgl3
44

55
import com.badlogic.gdx.backends.lwjgl3.Lwjgl3Application
66
import com.badlogic.gdx.backends.lwjgl3.Lwjgl3ApplicationConfiguration
7+
import com.badlogic.gdx.backends.lwjgl3.Lwjgl3Window
8+
import com.badlogic.gdx.backends.lwjgl3.Lwjgl3WindowAdapter
9+
import com.badlogic.gdx.backends.lwjgl3.Lwjgl3WindowListener
710
import dev.ultreon.launcher.Main
811

912
/** Launches the desktop (LWJGL3) application. */
1013
fun main() {
1114
// This handles macOS support and helps on Windows.
1215
if (StartupHelper.startNewJvmIfRequired())
1316
return
14-
Lwjgl3Application(Main(), Lwjgl3ApplicationConfiguration().apply {
17+
Lwjgl3Application(Main, Lwjgl3ApplicationConfiguration().apply {
1518
setTitle("Quantum Launcher")
1619
setWindowedMode(1280, 640)
1720
setWindowIcon(*(arrayOf(128, 64, 32, 16).map { "libgdx$it.png" }.toTypedArray()))
1821
setResizable(false)
22+
23+
setWindowListener(object : Lwjgl3WindowAdapter() {
24+
override fun closeRequested(): Boolean {
25+
return Main.handleClose()
26+
}
27+
})
1928
})
2029
}

0 commit comments

Comments
 (0)