Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
75864fd
Upgrade build to Java 17
Magi1053 Jun 9, 2026
d01e9a0
Move memory and Java settings from global config to per-instance
Magi1053 Jun 9, 2026
f56a4ef
Add managed Game Runtime install from Mojang manifest
Magi1053 Jun 9, 2026
aefedc6
Add cross-platform native installer packaging and harden bootstrap st…
Magi1053 Jun 9, 2026
5c03993
Add Microsoft device-code sign-in and refresh account picker
Magi1053 Jun 9, 2026
c61e69f
Replace news panel with JavaFX WebView for embedded page rendering
Magi1053 Jun 9, 2026
1402bf6
Make feature exclude patterns control install conditions
Magi1053 Jun 9, 2026
810e11a
Refactor ProcessorEntry install to respect cache before queueing proc…
Magi1053 Jun 9, 2026
42c1c29
Add modpack memory requirements with insufficient-memory warning
Magi1053 Jun 9, 2026
d12aead
Preserve creator-defined optional feature ordering
Magi1053 Jun 9, 2026
f40d680
Improve instance list readability with larger rows
Magi1053 Jun 9, 2026
2ffb49e
Reload instances when game key changes on options save
Magi1053 Jun 9, 2026
f8989f6
Support per-instance news URLs
Magi1053 Jun 9, 2026
2cfb2a6
Ignore missing launcher registry key
Magi1053 Jun 10, 2026
a72da99
Overhaul launcher look-and-feel with FlatLaf
Magi1053 Jun 10, 2026
203c6e4
Remember last launched instance and improve instance selection handling
Magi1053 Jun 10, 2026
a5e4254
Replace OpenJFX news panel with platform-native embedded browser
Magi1053 Jul 18, 2026
5856aee
Ship browser runtime via cross-platform native installers
Magi1053 Jul 18, 2026
29e2d97
Rewrite game console with deferred show, bounded buffer, and level co…
Magi1053 Jul 21, 2026
6c6dc70
Redesign instance list with icons, status rows, and borders
Magi1053 Jul 23, 2026
209cea0
Add offline accounts with switcher and manage-accounts UI
Magi1053 Jul 23, 2026
0b7e203
Improve launch memory checks, update verification, and downloads.
Magi1053 Jul 23, 2026
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
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
gradle/*.sh text eol=lf
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -155,4 +155,5 @@ $RECYCLE.BIN/
/libraries
/versions
/accounts.dat
/config.json
/config.json
/run
24 changes: 23 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,28 @@ You'll probably need a few hours to get everything working. Fortunately, we have

For your evaluation, there are sample files provided that you will be able to upload to your site to see if it even works for you.

## Building

Build with a Java 17 JDK. The Gradle wrapper should also be run by Java 17:

```
gradlew.bat build
```

For native installer packaging, the bootstrap project provides free per-platform
scripts:

- Windows: `gradlew.bat :launcher-bootstrap:packageWindows -Pversion=...` (builds `Setup.exe` with NSIS)
- Linux: `./gradlew :launcher-bootstrap:packageLinux -Pversion=...` (builds AppImage + generic `.tar.gz`, optional `.deb` with `-PbuildDeb=true`)
- Linux from Windows + WSL: `gradlew.bat :launcher-bootstrap:packageLinuxWsl -Pversion=... -PwslDistro=Ubuntu`
- macOS: `./gradlew :launcher-bootstrap:packageMac -Pversion=...` (builds `.dmg` and `.tar.gz`)

All scripts start from the same staged runtime image generated by:

```
gradlew.bat :launcher-bootstrap:assembleAppImage
```

## Pretty Pictures

If you are going to create modpacks with our GUI tool, you'll be seeing this beaut:
Expand Down Expand Up @@ -70,7 +92,7 @@ Users can select optional mods/features:

![Optional Features](readme/features.png)

The launcher can update itself and you can use it in portable mode too.
The launcher can update itself and stores launcher data in its configured data directory.

### The Light Theme

Expand Down
152 changes: 149 additions & 3 deletions build.bat
Original file line number Diff line number Diff line change
@@ -1,3 +1,149 @@
@echo off
call gradlew clean build
pause
@echo off
setlocal EnableExtensions EnableDelayedExpansion

set "BUILD_INSTALLER=1"
set "BUILD_WINDOWS=1"
set "BUILD_LINUX=1"
set "LINUX_BACKEND=auto"
set "RESOLVE_ARGS="
set "ROOT=%~dp0"
set "DOCKER_IMAGE=gradle:8.14.0-jdk17"

:parse
if "%~1"=="" goto parsed
set "A=%~1"
if /i "!A:~0,2!"=="--" set "A=!A:~2!"
if /i "!A!"=="no-installer" set "BUILD_INSTALLER=0"
if /i "!A!"=="windows" (set "BUILD_WINDOWS=1" & set "BUILD_LINUX=0")
if /i "!A!"=="linux" (set "BUILD_WINDOWS=0" & set "BUILD_LINUX=1")
if /i "!A!"=="docker" set "LINUX_BACKEND=docker"
if /i "!A!"=="wsl" set "LINUX_BACKEND=wsl"
shift
goto parse
:parsed

if "%BUILD_WINDOWS%%BUILD_LINUX%"=="00" (
echo ERROR: Nothing to package.
goto :fail
)

rem Linux-only + Docker: compile and package fully in container (no host JDK).
if "%BUILD_WINDOWS%"=="0" if "%BUILD_LINUX%"=="1" (
call :pick_linux
if errorlevel 1 goto :fail
if /i "!LINUX_BACKEND_KIND!"=="docker" (
call :docker_linux_full
set "EXIT_CODE=!ERRORLEVEL!"
pause
exit /b !EXIT_CODE!
)
)

if "%BUILD_INSTALLER%"=="0" (
set "RESOLVE_ARGS=--no-installer"
) else if "%BUILD_WINDOWS%"=="0" (
set "RESOLVE_ARGS=--no-nsis"
)
call "%ROOT%gradle\resolve-build-env.bat" %RESOLVE_ARGS%
if errorlevel 1 goto :fail
echo Using JAVA_HOME=%JAVA_HOME%

set "TASKS="
if "%BUILD_INSTALLER%"=="1" (
if "%BUILD_WINDOWS%"=="1" set "TASKS=:launcher-bootstrap:packageWindows"
if "%BUILD_LINUX%"=="1" (
if not defined LINUX_TASK (
call :pick_linux
if errorlevel 1 goto :fail
)
if defined TASKS (set "TASKS=!TASKS! !LINUX_TASK!") else set "TASKS=!LINUX_TASK!"
)
echo Packaging tasks: !TASKS!
)

call "%ROOT%gradlew" clean build %TASKS%
set "EXIT_CODE=%ERRORLEVEL%"
pause
exit /b %EXIT_CODE%

:pick_linux
if "%LINUX_BACKEND%"=="docker" (
call :docker_ok
if errorlevel 1 (
echo ERROR: --docker set, but Docker unavailable. No WSL fallback.
exit /b 1
)
set "LINUX_TASK=:launcher-bootstrap:packageLinuxDocker"
set "LINUX_BACKEND_KIND=docker"
echo Linux backend: Docker
exit /b 0
)
if "%LINUX_BACKEND%"=="wsl" (
call :wsl_ok
if errorlevel 1 (
echo ERROR: --wsl set, but WSL unavailable. No Docker fallback.
exit /b 1
)
set "LINUX_TASK=:launcher-bootstrap:packageLinuxWsl"
set "LINUX_BACKEND_KIND=wsl"
echo Linux backend: WSL
exit /b 0
)
call :docker_ok
if not errorlevel 1 (
set "LINUX_TASK=:launcher-bootstrap:packageLinuxDocker"
set "LINUX_BACKEND_KIND=docker"
echo Linux backend: Docker
exit /b 0
)
call :wsl_ok
if not errorlevel 1 (
set "LINUX_TASK=:launcher-bootstrap:packageLinuxWsl"
set "LINUX_BACKEND_KIND=wsl"
echo Linux backend: WSL
exit /b 0
)
echo ERROR: Linux packaging needs Docker or WSL. Use --windows to skip.
exit /b 1

:docker_linux_full
call :docker_ok
if errorlevel 1 (
echo ERROR: Docker unavailable.
exit /b 1
)

set "REPO_MOUNT=%ROOT:\=/%"
if "%REPO_MOUNT:~-1%"=="/" set "REPO_MOUNT=%REPO_MOUNT:~0,-1%"
set "SCRIPT_MOUNT=%ROOT%gradle\docker-linux-build.sh"
set "SCRIPT_MOUNT=%SCRIPT_MOUNT:\=/%"

set "BUILD_DEB=false"
set "SKIP_PACKAGE=false"
if "%BUILD_INSTALLER%"=="0" set "SKIP_PACKAGE=true"

echo Packaging Linux fully in Docker ^(no host JDK^)...
echo Using Docker image: %DOCKER_IMAGE%
docker run --rm --user root --entrypoint bash ^
-e "BUILD_DEB=%BUILD_DEB%" ^
-e "SKIP_PACKAGE=%SKIP_PACKAGE%" ^
-v "%REPO_MOUNT%:/workspace" ^
-v "%SCRIPT_MOUNT%:/tmp/docker-linux-build.sh:ro" ^
-w /workspace ^
"%DOCKER_IMAGE%" ^
-c "tr -d '\r' </tmp/docker-linux-build.sh | bash"
exit /b %ERRORLEVEL%

:docker_ok
where docker >nul 2>&1 || exit /b 1
docker info >nul 2>&1
exit /b %ERRORLEVEL%

:wsl_ok
where wsl >nul 2>&1 || exit /b 1
wsl -- echo WSL_OK >nul 2>&1
exit /b %ERRORLEVEL%

:fail
pause
exit /b 1
43 changes: 7 additions & 36 deletions build.gradle
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
plugins {
id "com.github.johnrengelman.shadow" version "7.1.2"
id 'io.freefair.lombok' version '5.3.0'
id "com.gradleup.shadow" version "8.3.6"
id 'io.freefair.lombok' version '8.13.1'
}

println """
Expand All @@ -19,7 +19,7 @@ subprojects {

java {
toolchain {
languageVersion.set(JavaLanguageVersion.of(8))
languageVersion.set(JavaLanguageVersion.of(17))
}
}

Expand All @@ -28,7 +28,6 @@ subprojects {
}

if (JavaVersion.current().isJava8Compatible()) {
// Java 8 turns on doclint which we fail
tasks.withType(Javadoc) {
options.addStringOption('Xdoclint:none', '-quiet')
}
Expand All @@ -38,38 +37,10 @@ subprojects {
workingDir = new File(rootDir, "run/")
workingDir.mkdirs()
}

// Work around gradle shadow bug
// see https://github.com/johnrengelman/shadow/issues/713
afterEvaluate {
startScripts {
dependsOn(shadowJar)
}

distTar {
dependsOn(shadowJar)
}

distZip {
dependsOn(shadowJar)
}

startShadowScripts {
dependsOn(jar)
}

shadowDistTar {
dependsOn(jar)
}

shadowDistZip {
dependsOn(jar)
}
}
}

task clean {
subprojects {
rootProject.clean.dependsOn tasks.matching { it.name == "clean" }
}
tasks.register('package') {
group = 'distribution'
description = 'Build native launcher installer for the current platform.'
dependsOn ':launcher-bootstrap:package'
}
21 changes: 20 additions & 1 deletion build.sh
100644 → 100755
Original file line number Diff line number Diff line change
@@ -1,3 +1,22 @@
#!/bin/sh
BUILD_INSTALLER=1
if [ "$1" = "--no-installer" ] || [ "$1" = "no-installer" ]; then
BUILD_INSTALLER=0
echo "Skipping native installer packaging."
fi

./gradlew clean build
read -p "Press any key to continue..."
EXIT_CODE=$?
if [ $EXIT_CODE -ne 0 ]; then
read -p "Press any key to continue..."
exit $EXIT_CODE
fi

if [ "$BUILD_INSTALLER" = "1" ]; then
echo "Packaging native installer..."
./gradlew package
EXIT_CODE=$?
fi

read -p "Press any key to continue..."
exit $EXIT_CODE
18 changes: 5 additions & 13 deletions creator-tools/build.gradle
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
plugins {
id 'application'
id "com.github.johnrengelman.shadow"
id 'com.gradleup.shadow'
id 'io.freefair.lombok'
}

version = "2.1.0-SNAPSHOT"

application {
mainClassName = "com.skcraft.launcher.creator.Creator"
mainClass = "com.skcraft.launcher.creator.Creator"
}

dependencies {
Expand All @@ -16,18 +16,10 @@ dependencies {
implementation 'com.jidesoft:jide-oss:3.6.18'
}


processResources {
def projectVersion = project.version.toString()
filesMatching('**/*.properties') {
filter {
it.replace('${project.version}', project.version)
}
filter { it.replace('${project.version}', projectVersion) }
}
}

shadowJar {
archiveClassifier.set("")
}

build {
dependsOn(shadowJar)
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,15 @@

import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.common.util.concurrent.MoreExecutors;
import com.skcraft.launcher.Configuration;
import com.skcraft.launcher.Launcher;
import com.skcraft.launcher.creator.controller.WelcomeController;
import com.skcraft.launcher.creator.dialog.WelcomeDialog;
import com.skcraft.launcher.creator.model.creator.CreatorConfig;
import com.skcraft.launcher.creator.model.creator.RecentEntry;
import com.skcraft.launcher.creator.model.creator.Workspace;
import com.skcraft.launcher.persistence.Persistence;
import com.skcraft.launcher.swing.LauncherLookAndFeel;
import com.skcraft.launcher.swing.SwingHelper;
import lombok.Getter;

Expand Down Expand Up @@ -75,7 +77,9 @@ public static void main(String[] args) throws Exception {

SwingUtilities.invokeAndWait(() -> {
SwingHelper.setSwingProperties("Modpack Creator");
SwingHelper.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
File launcherConfigFile = new File(creator.getDataDir(), "launcher/config.json");
Configuration launcherConfig = Persistence.load(launcherConfigFile, Configuration.class);
LauncherLookAndFeel.install(launcherConfig.getThemeMode());

try {
creator.showWelcome();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

package com.skcraft.launcher.creator.controller;

import com.google.common.base.Strings;
import com.skcraft.launcher.creator.dialog.ManifestEntryDialog;
import com.skcraft.launcher.creator.model.creator.ManifestEntry;
import com.skcraft.launcher.swing.SwingHelper;
Expand All @@ -28,12 +29,16 @@ public ManifestEntryController(ManifestEntryDialog dialog, ManifestEntry manifes
private void copyFrom() {
dialog.getIncludeCheck().setSelected(manifestEntry.isSelected());
dialog.getPrioritySpinner().setValue(manifestEntry.getManifestInfo().getPriority());
SwingHelper.setTextAndResetCaret(dialog.getNewsUrlText(), manifestEntry.getManifestInfo().getNewsUrl());
SwingHelper.setTextAndResetCaret(dialog.getIconUrlText(), manifestEntry.getManifestInfo().getIconUrl());
SwingHelper.setTextAndResetCaret(dialog.getGameKeysText(), SwingHelper.listToLines(manifestEntry.getGameKeys()));
}

private void copyTo() {
manifestEntry.setSelected(dialog.getIncludeCheck().isSelected());
manifestEntry.getManifestInfo().setPriority((Integer) dialog.getPrioritySpinner().getValue());
manifestEntry.getManifestInfo().setNewsUrl(Strings.emptyToNull(dialog.getNewsUrlText().getText().trim()));
manifestEntry.getManifestInfo().setIconUrl(Strings.emptyToNull(dialog.getIconUrlText().getText().trim()));
manifestEntry.setGameKeys(SwingHelper.linesToList(dialog.getGameKeysText().getText()));
}

Expand Down
Loading