Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,4 +76,4 @@ cmake -S NativeSrc -B NativeSrc/build -DCMAKE_BUILD_TYPE=Release; cmake --build

### Install built mod

After building, copy `build/libs/V5-Loader.jar` into `.minecraft/mods/` together with [V5ModLoader.jar](https://rdbt.top/docs/getting-started). V5ModLoader handles authentication and passes the JWT to the loader.
After building, copy `build/libs/V5-Loader.jar` into `.minecraft/mods/`.
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@ import java.io.FileOutputStream
import java.nio.charset.StandardCharsets

internal object ModLoaderUpdater {
private const val MOD_LOADER_CANONICAL_FILE_NAME = "V5ModLoader.jar"
private const val MOD_LOADER_CANONICAL_FILE_NAME = "V5-Loader.jar"
private const val MOD_LOADER_HELPER_TIMEOUT_SECONDS = 90
private const val MOD_LOADER_REPLACE_RETRY_COUNT = 20
private const val UPDATE_POPUP_TITLE = "V5ModLoader"
private const val UPDATE_POPUP_MESSAGE = "V5ModLoader updated. Start minecraft again."
private const val UPDATE_POPUP_TITLE = "V5-Loader"
private const val UPDATE_POPUP_MESSAGE = "V5-Loader updated. Start Minecraft again."

fun stageUpdateAndRelaunch(
gameDir: File,
Expand All @@ -34,9 +34,9 @@ internal object ModLoaderUpdater {
val modsDir = File(canonicalGameDir, "mods").canonicalFile.apply { mkdirs() }
val updaterDir = File(canonicalGameDir, ".v5-self-update").canonicalFile.apply { mkdirs() }
val pid = ProcessHandle.current().pid()
val sourceJar = File(updaterDir, "V5ModLoader-$pid.jar.new").canonicalFile
val targetJar = selectModLoaderTarget(modsDir, candidates)
val backupJar = File(updaterDir, "V5ModLoader-$pid.jar.bak").canonicalFile
val sourceJar = File(updaterDir, "V5-Loader-$pid.jar.new").canonicalFile
val targetJar = selectLoaderTarget(modsDir, candidates)
val backupJar = File(updaterDir, "V5-Loader-$pid.jar.bak").canonicalFile
val targetPath = targetJar.toPath().toAbsolutePath().normalize()
val staleTargets = candidates
.map { it.toPath().toAbsolutePath().normalize() }
Expand All @@ -53,7 +53,7 @@ internal object ModLoaderUpdater {
)
}

private fun selectModLoaderTarget(modsDir: File, candidates: List<File>): File {
private fun selectLoaderTarget(modsDir: File, candidates: List<File>): File {
val canonicalModsDir = modsDir.canonicalFile.toPath().normalize()
val existingTarget = candidates.singleOrNull()
?.canonicalFile
Expand All @@ -74,91 +74,79 @@ internal object ModLoaderUpdater {
}

private fun writeUnixUpdateScript(updatePaths: UpdatePaths): File {
val script = File(updatePaths.sourceJar.parentFile, "modloader-update-${updatePaths.pid}.sh").canonicalFile
val lines = buildList {
add("#!/bin/sh")
add("PID=${updatePaths.pid}")
add("WAIT_SECONDS=0")
add("while kill -0 \"\$PID\" 2>/dev/null; do")
add(" sleep 1")
add(" WAIT_SECONDS=\$((WAIT_SECONDS + 1))")
add(" if [ \"\$WAIT_SECONDS\" -ge \"$MOD_LOADER_HELPER_TIMEOUT_SECONDS\" ]; then")
add(" exit 1")
add(" fi")
add("done")
add("ATTEMPT=0")
add("while [ \"\$ATTEMPT\" -lt \"$MOD_LOADER_REPLACE_RETRY_COUNT\" ]; do")
add(" mkdir -p ${shellQuote(updatePaths.targetJar.parentFile.canonicalPath)}")
add(" rm -f ${shellQuote(updatePaths.backupJar.absolutePath)} >/dev/null 2>&1 || true")
add(" if [ -f ${shellQuote(updatePaths.targetJar.absolutePath)} ]; then")
add(" mv -f ${shellQuote(updatePaths.targetJar.absolutePath)} ${shellQuote(updatePaths.backupJar.absolutePath)} >/dev/null 2>&1 || true")
add(" fi")
add(" if mv -f ${shellQuote(updatePaths.sourceJar.absolutePath)} ${shellQuote(updatePaths.targetJar.absolutePath)} >/dev/null 2>&1; then")
updatePaths.staleTargets.forEach { target ->
add(" rm -f ${shellQuote(target.absolutePath)} >/dev/null 2>&1 || true")
}
add(" rm -f ${shellQuote(updatePaths.backupJar.absolutePath)} >/dev/null 2>&1 || true")
add(" break")
add(" fi")
add(" if [ -f ${shellQuote(updatePaths.backupJar.absolutePath)} ]; then")
add(" mv -f ${shellQuote(updatePaths.backupJar.absolutePath)} ${shellQuote(updatePaths.targetJar.absolutePath)} >/dev/null 2>&1 || true")
add(" fi")
add(" ATTEMPT=\$((ATTEMPT + 1))")
add(" sleep 1")
add("done")
add("[ -f ${shellQuote(updatePaths.targetJar.absolutePath)} ] || exit 1")
add("if command -v osascript >/dev/null 2>&1; then")
add(" nohup osascript -e ${shellQuote("display dialog \"$UPDATE_POPUP_MESSAGE\" with title \"$UPDATE_POPUP_TITLE\" buttons {\"OK\"} default button 1")} >/dev/null 2>&1 &")
add("elif command -v zenity >/dev/null 2>&1; then")
add(" nohup zenity --info --title=${shellQuote(UPDATE_POPUP_TITLE)} --text=${shellQuote(UPDATE_POPUP_MESSAGE)} >/dev/null 2>&1 &")
add("elif command -v kdialog >/dev/null 2>&1; then")
add(" nohup kdialog --title ${shellQuote(UPDATE_POPUP_TITLE)} --msgbox ${shellQuote(UPDATE_POPUP_MESSAGE)} >/dev/null 2>&1 &")
add("elif command -v xmessage >/dev/null 2>&1; then")
add(" nohup xmessage -center ${shellQuote(UPDATE_POPUP_MESSAGE)} >/dev/null 2>&1 &")
add("fi")
add("rm -f ${shellQuote(script.absolutePath)} >/dev/null 2>&1 || true")
add("exit 0")
val script = File(updatePaths.sourceJar.parentFile, "v5-loader-update-${updatePaths.pid}.sh").canonicalFile
val lines = mutableListOf<String>()
lines += "#!/bin/sh"
lines += "PID=${updatePaths.pid}"
lines += "WAIT_SECONDS=0"
lines += "while kill -0 \"\$PID\" 2>/dev/null; do"
lines += " sleep 1"
lines += " WAIT_SECONDS=\$((WAIT_SECONDS + 1))"
lines += " if [ \"\$WAIT_SECONDS\" -ge \"$MOD_LOADER_HELPER_TIMEOUT_SECONDS\" ]; then"
lines += " exit 1"
lines += " fi"
lines += "done"
lines += "ATTEMPT=0"
lines += "while [ \"\$ATTEMPT\" -lt \"$MOD_LOADER_REPLACE_RETRY_COUNT\" ]; do"
lines += " mkdir -p ${shellQuote(updatePaths.targetJar.parentFile.canonicalPath)}"
lines += " rm -f ${shellQuote(updatePaths.backupJar.absolutePath)} >/dev/null 2>&1 || true"
lines += " if [ -f ${shellQuote(updatePaths.targetJar.absolutePath)} ]; then"
lines += " mv -f ${shellQuote(updatePaths.targetJar.absolutePath)} ${shellQuote(updatePaths.backupJar.absolutePath)} >/dev/null 2>&1 || true"
lines += " fi"
lines += " if mv -f ${shellQuote(updatePaths.sourceJar.absolutePath)} ${shellQuote(updatePaths.targetJar.absolutePath)} >/dev/null 2>&1; then"
updatePaths.staleTargets.forEach { target ->
lines += " rm -f ${shellQuote(target.absolutePath)} >/dev/null 2>&1 || true"
}
lines += " rm -f ${shellQuote(updatePaths.backupJar.absolutePath)}"
lines += " rm -f \"\$0\""
lines += " exit 0"
lines += " fi"
lines += " if [ -f ${shellQuote(updatePaths.backupJar.absolutePath)} ]; then"
lines += " mv -f ${shellQuote(updatePaths.backupJar.absolutePath)} ${shellQuote(updatePaths.targetJar.absolutePath)} >/dev/null 2>&1 || true"
lines += " fi"
lines += " ATTEMPT=\$((ATTEMPT + 1))"
lines += " sleep 1"
lines += "done"
lines += "exit 1"
script.writeLines(lines)
return script
}

private fun writeWindowsUpdateScript(updatePaths: UpdatePaths): File {
val script = File(updatePaths.sourceJar.parentFile, "modloader-update-${updatePaths.pid}.cmd").canonicalFile
val lines = buildList {
add("@echo off")
add("setlocal enabledelayedexpansion")
add("set \"PID=${updatePaths.pid}\"")
add("set \"ATTEMPTS=0\"")
add(":wait_for_exit")
add("tasklist /FI \"PID eq %PID%\" | find \"%PID%\" >nul")
add("if not errorlevel 1 (")
add(" timeout /t 1 /nobreak >nul")
add(" set /a ATTEMPTS+=1")
add(" if !ATTEMPTS! geq $MOD_LOADER_HELPER_TIMEOUT_SECONDS exit /b 1")
add(" goto wait_for_exit")
add(")")
add("set \"ATTEMPTS=0\"")
add(":replace_loop")
add("del /f /q ${cmdQuote(updatePaths.backupJar.absolutePath)} >nul 2>nul")
add("if not exist ${cmdQuote(updatePaths.targetJar.parentFile.canonicalPath)} mkdir ${cmdQuote(updatePaths.targetJar.parentFile.canonicalPath)}")
add("if exist ${cmdQuote(updatePaths.targetJar.absolutePath)} move /y ${cmdQuote(updatePaths.targetJar.absolutePath)} ${cmdQuote(updatePaths.backupJar.absolutePath)} >nul 2>nul")
add("move /y ${cmdQuote(updatePaths.sourceJar.absolutePath)} ${cmdQuote(updatePaths.targetJar.absolutePath)} >nul 2>nul")
add("if not errorlevel 1 if exist ${cmdQuote(updatePaths.targetJar.absolutePath)} if not exist ${cmdQuote(updatePaths.sourceJar.absolutePath)} goto cleanup")
add("if exist ${cmdQuote(updatePaths.backupJar.absolutePath)} move /y ${cmdQuote(updatePaths.backupJar.absolutePath)} ${cmdQuote(updatePaths.targetJar.absolutePath)} >nul 2>nul")
add("set /a ATTEMPTS+=1")
add("if !ATTEMPTS! geq $MOD_LOADER_REPLACE_RETRY_COUNT exit /b 1")
add("timeout /t 1 /nobreak >nul")
add("goto replace_loop")
add(":cleanup")
updatePaths.staleTargets.forEach { target ->
add("del /f /q ${cmdQuote(target.absolutePath)} >nul 2>nul")
}
add("del /f /q ${cmdQuote(updatePaths.backupJar.absolutePath)} >nul 2>nul")
add("powershell -NoProfile -ExecutionPolicy Bypass -Command ${cmdQuote(buildPowerShellPopupCommand())} >nul 2>nul")
add("del /f /q \"%~f0\" >nul 2>nul")
add("exit /b 0")
val script = File(updatePaths.sourceJar.parentFile, "v5-loader-update-${updatePaths.pid}.cmd").canonicalFile
val lines = mutableListOf<String>()
lines += "@echo off"
lines += "setlocal enabledelayedexpansion"
lines += "set \"PID=${updatePaths.pid}\""
lines += "set \"ATTEMPTS=0\""
lines += ":wait_for_exit"
lines += "tasklist /FI \"PID eq %PID%\" | find \"%PID%\" >nul"
lines += "if not errorlevel 1 ("
lines += " timeout /t 1 /nobreak >nul"
lines += " set /a ATTEMPTS+=1"
lines += " if !ATTEMPTS! geq $MOD_LOADER_HELPER_TIMEOUT_SECONDS exit /b 1"
lines += " goto wait_for_exit"
lines += ")"
lines += "set \"ATTEMPTS=0\""
lines += ":replace_loop"
lines += "del /f /q ${cmdQuote(updatePaths.backupJar.absolutePath)} >nul 2>nul"
lines += "if not exist ${cmdQuote(updatePaths.targetJar.parentFile.canonicalPath)} mkdir ${cmdQuote(updatePaths.targetJar.parentFile.canonicalPath)}"
lines += "if exist ${cmdQuote(updatePaths.targetJar.absolutePath)} move /y ${cmdQuote(updatePaths.targetJar.absolutePath)} ${cmdQuote(updatePaths.backupJar.absolutePath)} >nul 2>nul"
lines += "move /y ${cmdQuote(updatePaths.sourceJar.absolutePath)} ${cmdQuote(updatePaths.targetJar.absolutePath)} >nul 2>nul"
lines += "if not errorlevel 1 if exist ${cmdQuote(updatePaths.targetJar.absolutePath)} if not exist ${cmdQuote(updatePaths.sourceJar.absolutePath)} goto cleanup"
lines += "if exist ${cmdQuote(updatePaths.backupJar.absolutePath)} move /y ${cmdQuote(updatePaths.backupJar.absolutePath)} ${cmdQuote(updatePaths.targetJar.absolutePath)} >nul 2>nul"
lines += "set /a ATTEMPTS+=1"
lines += "if !ATTEMPTS! geq $MOD_LOADER_REPLACE_RETRY_COUNT exit /b 1"
lines += "timeout /t 1 /nobreak >nul"
lines += "goto replace_loop"
lines += ":cleanup"
updatePaths.staleTargets.forEach { target ->
lines += "del /f /q ${cmdQuote(target.absolutePath)} >nul 2>nul"
}
lines += "del /f /q ${cmdQuote(updatePaths.backupJar.absolutePath)} >nul 2>nul"
lines += "powershell -NoProfile -Command ${cmdQuote(buildPowerShellPopupCommand())}"
lines += "del /f /q \"%~f0\" >nul 2>nul"
lines += "exit /b 0"
script.writeLines(lines, "\r\n")
return script
}
Expand Down
Loading
Loading