Project Overview
OpenLauncherLib is a Java library for programmatically launching external Java applications and Minecraft instances. It streamlines classpath management, JVM argument configuration, profile handling and localization, while providing utilities for splash screens, data persistence and RAM selection. You can embed it in desktop clients, server tools or custom launchers to automate pre- and post-launch tasks.
What Problems It Solves
- Centralizes JVM argument and classpath configuration for any Java application
- Simplifies Minecraft launch integration without manual dependency handling
- Provides built-in localization (i18n) support for multi-language UIs
- Offers utilities for splash screens, configuration storage and memory management
High-Level Capabilities
- External Java launching: full control over main class, JVM options, environment variables
- Minecraft launching: auto-resolve Mojang libraries, assets, versions and authentication
- Configuration profiles: create, load and persist multiple launch setups
- Internationalization: load and switch locale bundles at runtime
- Utilities: data storage helpers, RAM selector components, customizable splash displays
Licensing
You may choose between two licensing options:
- GNU General Public License v3 (GPLv3)
• Requires derivative works to remain open source under GPLv3
• Include the fullLICENSE
text in your distribution - GNU Lesser General Public License v3 (LGPLv3)
• Permits linking from proprietary software
• Include theLICENSE.LESSER
text and notices in your distribution
Select the license that matches your project’s distribution and linking requirements.
Quick Start
Below is a basic example demonstrating how to launch a Minecraft instance with custom JVM arguments and RAM settings.
import com.flowpowered.launcher.Launcher;
import com.flowpowered.launcher.profile.LaunchProfile;
public class MinecraftLauncherExample {
public static void main(String[] args) {
// Configure launch profile
LaunchProfile profile = new LaunchProfile()
.setMainClass("net.minecraft.client.main.Main")
.addJvmArgument("-Xms512M")
.addJvmArgument("-Xmx2G")
.addProgramArgument("--username", "Player123")
.setWorkingDirectory("game-directory");
// Create and run launcher
Launcher launcher = new Launcher(profile);
launcher.launch();
}
}
This snippet:
- Sets the Minecraft main class and working directory
- Adds JVM flags for minimum and maximum RAM
- Passes the
--username
argument to the game - Initializes and starts the launcher with one method call
Getting Started
This guide shows how to add OpenLauncherLib to your Gradle project, bootstrap the Gradle wrapper, and launch an external Java application in minutes.
1. Initialize a Java application project
mkdir my-launcher
cd my-launcher
gradle init --type java-application
2. Configure build.gradle
Replace your build.gradle
with:
plugins {
id 'java'
id 'application'
id 'com.github.johnrengelman.shadow' version '7.1.2'
}
group = 'com.example'
version = '1.0.0'
application {
mainClass = 'com.example.AppLauncher'
}
repositories {
mavenCentral()
}
dependencies {
implementation 'fr.theshark34.openlauncherlib:openlauncherlib:VERSION'
}
– Replace VERSION
with the latest release (check Maven Central).
– The Shadow plugin builds a fat JAR for distribution.
3. Bootstrap & build with the Gradle Wrapper
Generate the wrapper (if not already committed):
gradle wrapper --gradle-version 8.10 --distribution-type all
Build the project:
./gradlew clean build
4. Create your launcher
- Create a
libs/
directory and place the target JAR there (for this example,HelloWorld.jar
containingcom.example.HelloWorld
). - Add
src/main/java/com/example/AppLauncher.java
:
package com.example;
import fr.theshark34.openlauncherlib.external.ExternalLaunchProfile;
import fr.theshark34.openlauncherlib.external.ExternalLauncher;
import fr.theshark34.openlauncherlib.LaunchException;
import java.nio.file.Paths;
import java.util.Collections;
public class AppLauncher {
public static void main(String[] args) {
// 1. Build classpath string pointing to your JAR
String classpath = Paths.get("libs/HelloWorld.jar").toString();
// 2. Create a launch profile (main class, classpath, no extra args)
ExternalLaunchProfile profile = new ExternalLaunchProfile(
"com.example.HelloWorld",
classpath,
Collections.emptyList(),
Collections.emptyList()
);
try {
// 3. Launch and wait for exit
Process process = new ExternalLauncher(profile).launch();
int exitCode = process.waitFor();
System.out.println("External process exited with code " + exitCode);
} catch (LaunchException | InterruptedException e) {
e.printStackTrace();
}
}
}
5. Run your launcher
Invoke via the Gradle wrapper:
./gradlew run
You should see the output from HelloWorld.jar
followed byExternal process exited with code 0
.
6. Build a fat JAR (optional)
Produce a single executable JAR including OpenLauncherLib:
./gradlew shadowJar
Run:
java -jar build/libs/my-launcher-1.0.0-all.jar
Your launcher is now ready for distribution!
Core Concepts & API Guide
This section explores OpenLauncherLib’s primary APIs and design patterns: launching external processes, building launch profiles, managing JSON configurations, and handling translations with fallback.
External Process Launching
Purpose: Configure and launch an external Java process with custom classpath, JVM/program arguments, working directory, and ProcessBuilder tweaks.
1. Build the classpath
Use ClasspathConstructor
to collect JARs/paths and generate the platform-specific classpath string.
import io.github.opencubicchunks.launcher.ClasspathConstructor;
import java.nio.file.Paths;
// Collect library JARs and main application JAR
ClasspathConstructor cp = new ClasspathConstructor();
cp.add(Paths.get("libs/dependency1.jar"));
cp.add(Paths.get("libs/dependency2.jar"));
cp.add(Paths.get("myapp.jar"));
String classPath = cp.make(); // handles OS-specific separators
2. Create an ExternalLaunchProfile
Supply the main class name, classpath, VM args, program args, stderr redirection, macOS dock name, and working directory.
import io.github.opencubicchunks.launcher.ExternalLaunchProfile;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.List;
List<String> vmArgs = Arrays.asList("-Xmx1G", "-Dconfig=prod");
List<String> appArgs = Arrays.asList("--port", "8080");
Path workDir = Paths.get("run");
ExternalLaunchProfile profile = new ExternalLaunchProfile(
"com.example.Main",
classPath,
vmArgs,
appArgs,
true, // redirect stderr to stdout
"MyApp", // macOS dock name
workDir
);
3. (Optional) Customize the ProcessBuilder
Implement BeforeLaunchingEvent
to tweak environment variables, I/O redirection, or other settings.
import io.github.opencubicchunks.launcher.BeforeLaunchingEvent;
import java.io.File;
BeforeLaunchingEvent hook = builder -> {
builder.environment().put("MY_ENV", "value");
builder.redirectOutput(new File("logs/out.log"));
};
4. Instantiate and launch
Use ExternalLauncher
to start the process and optionally stream logs.
import io.github.opencubicchunks.launcher.ExternalLauncher;
import io.github.opencubicchunks.launcher.LaunchException;
ExternalLauncher launcher = new ExternalLauncher(profile, hook);
launcher.setLogsEnabled(true);
try {
Process process = launcher.launch();
int exitCode = process.waitFor();
System.out.println("Exited with code " + exitCode);
} catch (LaunchException | InterruptedException e) {
e.printStackTrace();
}
Practical guidance:
- Always call
ClasspathConstructor.make()
to handle OS-specific separators. - Use
redirectErrorStream(true)
for unified output. - Omit
BeforeLaunchingEvent
if no custom tweaks are needed. - Call
launcher.setLogsEnabled(false)
to suppress automatic console logs. - Handle the returned
Process
to monitor exit codes or implement IPC.
Generating an External Launch Profile
Purpose: Build an ExternalLaunchProfile
for launching Minecraft using GameInfos
, GameFolder
, and AuthInfos
.
1. Prepare your inputs
- AuthInfos: holds username, access token, UUID, and optional client token.
- GameFolder: directory layout for assets, libraries, natives, and the main JAR.
- GameVersion: version identifier and
GameType
(vanilla, Forge, Fabric, etc.). - GameInfos: server name,
GameVersion
, optionalGameTweak
array.
2. Construct AuthInfos and GameFolder
import io.github.opencubicchunks.launcher.minecraft.AuthInfos;
import io.github.opencubicchunks.launcher.minecraft.GameFolder;
// Fill with values from your auth flow
AuthInfos auth = new AuthInfos(
"Notch",
"abcd-1234-access-token",
"uuid-5678",
"clientToken-0000"
);
GameFolder folder = new GameFolder(
"assets",
"libraries",
"natives",
"versions/1.16.5/1.16.5.jar"
);
3. Define GameVersion and (for Forge 1.13+) NFVD
import io.github.opencubicchunks.launcher.minecraft.GameType;
import io.github.opencubicchunks.launcher.minecraft.GameVersion;
import io.github.opencubicchunks.launcher.minecraft.NewForgeVersionDiscriminator;
// Vanilla or older Forge
GameVersion version = new GameVersion("1.16.5", GameType.V1_8_HIGHER);
// Forge 1.13+ requires an NFVD implementation
NewForgeVersionDiscriminator nfvd = new YourNFVDImplementation(...);
GameType.V1_13_HIGHER_FORGE.setNFVD(nfvd);
GameVersion forgeVersion = new GameVersion("1.16.5-forge", GameType.V1_13_HIGHER_FORGE);
4. Build GameInfos with optional tweaks
import io.github.opencubicchunks.launcher.minecraft.GameInfos;
import io.github.opencubicchunks.launcher.minecraft.GameTweak;
// Vanilla launch
GameInfos vanillaInfos = new GameInfos("MyServer", version, new GameTweak[0]);
// With Optifine and Shaders
GameInfos tweakedInfos = new GameInfos(
"MyServer",
version,
new GameTweak[]{ GameTweak.OPTIFINE, GameTweak.SHADER }
);
5. Create the ExternalLaunchProfile
import io.github.opencubicchunks.launcher.minecraft.MinecraftLauncher;
ExternalLaunchProfile profile = MinecraftLauncher.createExternalProfile(
tweakedInfos,
folder,
auth
);
Internally this verifies folders and JARs, builds classpath, chooses mainClass
, assembles VM/program args, and handles tweak classes.
6. Launch Minecraft
ProcessBuilder pb = profile.getProcessBuilder();
Process gameProcess = pb.start();
Practical tips:
- Match
GameFolder
paths to your unpacked assets and libraries. - For Forge 1.13+, omit
GameTweak.FORGE
—theGameType
handles Forge. - OpenLauncherLib filters incompatible tweaks; check logs (
LogUtil
) for “support-forge” or “tweak-deprec” messages.
SimpleConfiguration: JSON-based Configuration Storage
Purpose: Provide a lightweight JSON-backed Configuration
with nested keys, default values, and on-disk persistence.
Usage Overview
- Obtain a
Configuration
viaConfigurationManager
. - Read values with
get(...)
orgetOrSet(...)
. - Mutate values with
set(...)
. - Persist changes with
save()
(or auto-save viaset(..., save=true)
).
Retrieving a Configuration
import io.github.opencubicchunks.launcher.config.Configuration;
import io.github.opencubicchunks.launcher.config.ConfigurationManager;
import io.github.opencubicchunks.launcher.config.DefaultConfigurationManager;
import java.util.logging.Logger;
Logger logger = Logger.getLogger("cfg");
ConfigurationManager cfgMgr = new DefaultConfigurationManager(logger);
Configuration cfg = cfgMgr.getConfiguration("config.json");
Reading Values
int port = cfg.get(25565, "server", "port");
String host = cfg.get("localhost", "server", "host");
boolean enabled = cfg.get(false, "features", "coolFeatureEnabled");
Auto-inserting Defaults
String theme = cfg.getOrSet("dark", "ui", "theme");
// To immediately write default to disk:
String lang = cfg.getOrSet("en_US", true, "ui", "language");
Setting and Removing Values
// Auto-saves
cfg.set(1024, "window", "width");
// Batch update without saving
cfg.set("v1.2.3", false, "app", "version");
// Remove a key
cfg.set(null, true, "obsolete", "entry");
Manually Saving
try {
cfg.save();
} catch (IOException e) {
logger.severe("Failed to save config: " + e.getMessage());
}
Nested keys (String... path
) create intermediate JSON objects automatically. Setting at root (cfg.set(obj)
) replaces the entire config.
Best practices:
- Use
getOrSet(default, true, …)
for first-run defaults. - Batch multiple
set(...)
calls then callsave()
once. - Catch and log
IOException
onsave()
to avoid silent failures.
Retrieving Translated Strings with Fallback
Purpose: Use Language.get(...)
to fetch translations and automatically fall back to the default language.
Initialization and Registration
import io.github.opencubicchunks.launcher.i18n.DefaultLanguageManager;
import io.github.opencubicchunks.launcher.i18n.LanguageInfo;
import io.github.opencubicchunks.launcher.config.ConfigurationManager;
import java.util.logging.Logger;
Logger logger = Logger.getLogger("lang");
ConfigurationManager cfgMgr = new DefaultConfigurationManager(logger);
DefaultLanguageManager langMgr = new DefaultLanguageManager(logger, cfgMgr);
// Register English and French JSON files from classpath
langMgr.registerLanguage(LanguageInfo.IDENTIFY, LanguageInfo.EN, "/assets/languages/");
langMgr.registerLanguage(LanguageInfo.IDENTIFY, LanguageInfo.FR, "/assets/languages/");
// Set default (fallback) language
langMgr.setDefaultLanguage(langMgr.getLanguage(LanguageInfo.EN));
Fetching Translations
import io.github.opencubicchunks.launcher.i18n.Language;
// Retrieve French translation
Language french = langMgr.getLanguage(LanguageInfo.FR);
String optionsFr = french.get(LanguageInfo.OPTIONS);
// Missing key in French: falls back to English or returns raw key
String undefinedInFr = french.get(LanguageInfo.SOME_NEW_KEY);
// Nested lookup (e.g., { "menu": { "exit": "Quit" } })
String exitLabel = french.get(LanguageInfo.MENU, "exit");
Practical guidance:
- Always call
setDefaultLanguage(...)
before fetching translations. - Use varargs
nodes
to traverse nested JSON structures. - Registering the same language twice merges JSON files.
- Missing keys return the default-language value or the raw key path.
These core APIs and patterns form the foundation of OpenLauncherLib’s extensible and practical design.
Utility Modules
A collection of optional helpers that simplify common launcher tasks, from RAM selection to crash reporting and logging.
Customizing the Ram Selector Frame
Show how to provide a bespoke RAM‐selection UI by subclassing AbstractOptionFrame
and plugging it into RamSelector
.
- Implement your custom frame
- Extend
AbstractOptionFrame
- Provide a public constructor taking a single
RamSelector
parameter - Override
getSelectedIndex()
/setSelectedIndex(int)
to bind your UI components
- Extend
import fr.theshark34.openlauncherlib.util.ramselector.AbstractOptionFrame;
import fr.theshark34.openlauncherlib.util.ramselector.RamSelector;
import javax.swing.*;
import java.awt.*;
public class MyRamOptionFrame extends AbstractOptionFrame {
private final JSlider slider;
private final JLabel valueLabel;
public MyRamOptionFrame(RamSelector selector) {
super(selector);
setTitle("Custom RAM Selector");
setSize(300, 120);
setLayout(new BorderLayout());
setResizable(false);
setLocationRelativeTo(null);
slider = new JSlider(0, RamSelector.RAM_ARRAY.length - 1, readInitialIndex());
slider.setMajorTickSpacing(1);
slider.setPaintTicks(true);
slider.addChangeListener(e -> valueLabel.setText(RamSelector.RAM_ARRAY[slider.getValue()]));
valueLabel = new JLabel(RamSelector.RAM_ARRAY[slider.getValue()], SwingConstants.CENTER);
add(valueLabel, BorderLayout.NORTH);
add(slider, BorderLayout.CENTER);
}
private int readInitialIndex() {
return getSelector().getFrame() == null
? getSelector().readRam()
: getSelector().getFrame().getSelectedIndex();
}
@Override
public int getSelectedIndex() {
return slider.getValue();
}
@Override
public void setSelectedIndex(int index) {
slider.setValue(index);
valueLabel.setText(RamSelector.RAM_ARRAY[index]);
}
}
- Register your frame with
RamSelector
import java.nio.file.Paths;
import fr.theshark34.openlauncherlib.util.ramselector.RamSelector;
Path configFile = Paths.get("config/ram.txt");
RamSelector selector = new RamSelector(configFile);
// Use your custom frame
selector.setFrameClass(MyRamOptionFrame.class);
selector.display();
- Retrieve JVM arguments and persist choice
String[] vmArgs = selector.getRamArguments(); // ["-Xms1024M", "-Xmx2048M"]
selector.save();
MyLauncher.launch(vmArgs);
Practical tips:
- Constructor must accept only
RamSelector
for reflective instantiation. - Call
selector.save()
on user confirmation. - Saved index (0–9) is stored as a plain integer.
CrashReporter: Automatic Crash Report Generation
Catch unexpected exceptions, write detailed stack traces to timestamped files, notify the user, and exit cleanly.
Key API
new CrashReporter(String projectName, Path crashDir)
catchError(Exception e, String userMessage)
writeError(Exception e) → Path
makeCrashReport(String projectName, Exception e) → String
import fr.theshark34.openlauncherlib.util.CrashReporter;
import java.nio.file.Paths;
import java.nio.file.Path;
Path crashDir = Paths.get("logs/crashes");
CrashReporter reporter = new CrashReporter("MyLauncher", crashDir);
try {
// startup logic...
} catch(Exception e) {
reporter.catchError(e, "Oops! The launcher failed to start.");
}
Practical guidance:
- Ensure
crashDir
is writable; directories are created automatically. - Use
reporter.setDir(newDir)
to change output at runtime. - Override or copy
makeCrashReport()
to customize report format.
LogUtil: Localized and Prefixed Logging
Centralize console logging with automatic language translation and a consistent [OpenLauncherLib]
prefix.
Key Methods
LogUtil.info(String… keys)
/LogUtil.err(String… keys)
LogUtil.rawInfo(String message)
/LogUtil.rawErr(String message)
LogUtil.getLanguageManager()
/LogUtil.getIdentifier()
import fr.theshark34.openlauncherlib.util.LogUtil;
LogUtil.info("launching", "version-info");
// Console: [OpenLauncherLib] Launching… Version 1.0.0
Practical guidance:
- Place your
.properties
files under/assets/languages/
. - Use
rawInfo
for dynamic or debug messages. - Force a language via
LogUtil.getLanguageManager().setDefaultLanguage("fr")
.
ProcessLogManager: Capturing Subprocess Output
Stream stdout or stderr from a Process
into console and optionally to a file.
Core API
new ProcessLogManager(InputStream in)
new ProcessLogManager(InputStream in, Path logFile)
setPrint(boolean)
/setToWrite(Path)
- Call
start()
to begin reading in a thread.
import fr.theshark34.openlauncherlib.util.ProcessLogManager;
import fr.theshark34.openlauncherlib.util.LogUtil;
import java.nio.file.Paths;
import java.nio.file.Path;
ProcessBuilder pb = new ProcessBuilder("java", "-jar", "game.jar");
Process game = pb.start();
Path logPath = Paths.get("logs/game-output.txt");
ProcessLogManager outLogger = new ProcessLogManager(game.getInputStream(), logPath);
outLogger.setPrint(true);
outLogger.start();
ProcessLogManager errLogger = new ProcessLogManager(game.getErrorStream());
errLogger.setPrint(true);
errLogger.start();
int exitCode = game.waitFor();
LogUtil.info("game-exited", String.valueOf(exitCode));
Practical guidance:
- Use separate managers for stdout and stderr.
- Call
setPrint(false)
to disable console output. - Ensure log file’s parent directories exist or catch write errors.
Directory Exploration with Explorer
Navigate, list and filter files and folders using Explorer
, ExploredDirectory
, and FileList
.
Initializing an Explorer
import fr.theshark34.openlauncherlib.util.explorer.Explorer;
import fr.theshark34.openlauncherlib.util.explorer.ExploredDirectory;
import java.nio.file.Paths;
ExploredDirectory root = Explorer.dir("path/to/myDir");
Explorer explorer = new Explorer(Paths.get("path/to/myDir"));
Changing Directories
explorer.cd(Paths.get("anotherDir"));
explorer.cd("subFolder"); // throws FailException if not found
Listing Contents
FileList entries = explorer.list();
FileList subdirs = explorer.subs();
FileList filesOnly = explorer.files();
FileList all = explorer.allRecursive();
Accessing Specific Files or Subfolders
ExploredDirectory images = root.sub("images");
Path config = root.get("config.json");
Filtering with FileList
FileList jars = root.allRecursive().match(".*\\.jar$");
Practical tips:
- All methods throw
FailException
on missing targets. - Chain calls fluently:
List<Path> pngs = Explorer
.dir("assets")
.sub("images")
.allRecursive()
.match(".*\\.png$")
.get();
- Accumulate multiple directories via
FileList.add(...)
.
JavaUtil: Managing Java Execution and Native Library Paths
Provide and override the Java executable command and adjust java.library.path
at runtime.
Key Methods
getJavaCommand()
setJavaCommand(String)
setLibraryPath(String)
import fr.theshark34.openlauncherlib.util.JavaUtil;
// Default java command
String javaCmd = JavaUtil.getJavaCommand();
// Override with custom JRE
JavaUtil.setJavaCommand("/opt/my_jre/bin/java");
// Update native library path
try {
JavaUtil.setLibraryPath("/path/to/native/libs");
} catch (Exception e) {
e.printStackTrace();
}
System.loadLibrary("my_native_lib");
Practical guidance:
- Call
setJavaCommand(...)
before launching subprocesses. - Use
setLibraryPath(...)
early if bundling native libraries. - Reflection-based path changes may be restricted under some JVM security settings.
Saver: Simple Key-Value Persistence via Properties Files
Read, write and remove string key/value pairs from a disk-backed Java Properties
file seamlessly.
Core Features
- Loads or creates the file and parent directories on instantiation
set(key, value)
/get(key)
/get(key, default)
/remove(key)
import fr.theshark34.openlauncherlib.util.Saver;
import java.nio.file.Paths;
Saver saver = new Saver(Paths.get(System.getProperty("user.home"), "myapp", "config.properties"));
// Store
saver.set("username", "player42");
// Retrieve with default
String user = saver.get("username", "guest");
// Remove
saver.remove("username");
String guest = saver.get("username", "guest");
Practical guidance:
- Mutating operations save immediately; avoid in tight loops.
- Catch
FailException
to handle I/O or permission errors.
SplashScreen: Lightweight Image-Driven Launcher Splash
Display an undecorated, optionally transparent, splash window with a background image.
Constructor
new SplashScreen(String title, Image image)
Display Methods
display()
displayFor(long ms)
→ returnsThread
stop()
setTransparent()
import fr.theshark34.openlauncherlib.util.SplashScreen;
import java.awt.Image;
import java.awt.Toolkit;
// Load image
Image img = Toolkit.getDefaultToolkit().getImage("assets/splash.png");
SplashScreen splash = new SplashScreen("MyApp", img);
splash.setTransparent();
Thread splashThread = splash.displayFor(3000);
// Initialize resources off the EDT
initializeGame();
splashThread.join();
Practical guidance:
- Call
setTransparent()
beforedisplay()
for per-pixel transparency. - Use
displayFor(...)
for fire-and-forget; join the returnedThread
to wait. - Run heavy initialization off the Event Dispatch Thread to keep UI responsive.
Advanced Usage & Customization
This section shows how to integrate Forge arguments, use NoFramework for custom launch flows, and hook into the launch process with callbacks.
Forge Launch Argument Provider
Generate and supply standard launch arguments for a Forge-based instance by implementing IForgeArgumentsProvider
and using NewForgeVersionDiscriminator
to extract version metadata.
How It Works
NewForgeVersionDiscriminator
parses a Forge JSON (e.g.1.15.2-forge-32.0.2.json
) to extract:forgeVersion
(e.g. “32.0.2”)mcVersion
(e.g. “1.15.2”)forgeGroup
(e.g. “net.minecraftforge”)mcpVersion
(e.g. “20200625.160719”)
IForgeArgumentsProvider
’s defaultgetForgeArguments()
builds:--launchTarget fmlclient --fml.forgeVersion <forgeVersion> --fml.mcVersion <mcVersion> --fml.forgeGroup <forgeGroup> --fml.mcpVersion <mcpVersion>
Implementing IForgeArgumentsProvider
import fr.flowarg.openlauncherlib.NewForgeVersionDiscriminator;
import fr.flowarg.openlauncherlib.IForgeArgumentsProvider;
import java.nio.file.Path;
import java.util.List;
import java.util.ArrayList;
public class MyForgeArgsProvider implements IForgeArgumentsProvider {
private final NewForgeVersionDiscriminator nfvd;
// Load from JSON: versionsDir/1.15.2-forge-32.0.2.json
public MyForgeArgsProvider(Path versionsDir, String mcVersion, String forgeVersion) throws Exception {
this.nfvd = new NewForgeVersionDiscriminator(versionsDir, mcVersion, forgeVersion);
}
@Override
public NewForgeVersionDiscriminator getNFVD() {
return nfvd;
}
}
Retrieve and apply the arguments:
MyForgeArgsProvider provider = new MyForgeArgsProvider(versionsDir, "1.15.2", "32.0.2");
List<String> forgeArgs = provider.getForgeArguments();
List<String> cmd = new ArrayList<>();
cmd.add(javaPath.toString());
cmd.addAll(forgeArgs);
cmd.addAll(userDefinedArgs);
ProcessBuilder pb = new ProcessBuilder(cmd);
pb.directory(gameDirectory.toFile());
Process process = pb.start();
Customizing Arguments
Override getForgeArguments()
to inject extra flags:
@Override
public List<String> getForgeArguments() {
List<String> args = new ArrayList<>(IForgeArgumentsProvider.super.getForgeArguments());
args.add("--fml.log.level");
args.add("DEBUG");
return args;
}
Tips
- Match JSON file name to
mcVersion-forge-forgeVersion.json
. - Keep the official Forge JSON structure; the parser relies on its layout.
- For testing, use the no-IO constructor:
NewForgeVersionDiscriminator mockNfvd = new NewForgeVersionDiscriminator("32.0.2", "1.15.2", "net.minecraftforge", "20200625.160719");
- Call
getForgeArguments()
at launch time to pick up dynamic changes.
Launching Minecraft with NoFramework
Configure and start a vanilla or mod-loaded Minecraft instance using NoFramework, with custom JSONs, extra arguments, and launch callbacks.
- Instantiate NoFramework
import fr.flowarg.openlauncherlib.NoFramework;
import fr.flowarg.openlauncherlib.auth.AuthInfos;
import fr.flowarg.openlauncherlib.GameFolder;
Path gameDir = Paths.get("C:/Minecraft/instances/MyPack");
AuthInfos auth = new AuthInfos("playerName", "uuid", "accessToken", "clientId", "authXUID");
GameFolder folder = new GameFolder("libraries", "minecraft.jar", "assets", "natives");
// Without extra args:
NoFramework noFramework = new NoFramework(gameDir, auth, folder);
// Or supply initial VM args:
// List<String> vmArgs = List.of("-Xmx2G", "-Xms1G");
// NoFramework noFramework = new NoFramework(gameDir, auth, folder, vmArgs, NoFramework.Type.VM);
- Override JSON filenames
noFramework.setCustomVanillaJsonFileName("custom/1.18.2-custom.json");
noFramework.setCustomModLoaderJsonFileName("custom/forge-1.18.2-40.0.50.json");
- Add extra JVM and game arguments
noFramework.setAdditionalVmArgs(List.of("-Xmx4G", "-XX:+UseG1GC"));
noFramework.setAdditionalArgs(List.of("--width=1280", "--height=720", "--server", "play.example.com"));
- Register a pre-launch callback
noFramework.setLastCallback(launcher -> {
launcher.getProcessBuilder()
.inheritIO()
.directory(gameDir.toFile());
});
- Launch the process
Process gameProcess = noFramework.launch(
"1.18.2", // Minecraft version
"40.0.50", // Forge loader version (ignored for VANILLA)
NoFramework.ModLoader.FORGE // or ModLoader.VANILLA
);
int exitCode = gameProcess.waitFor();
Tips
- For vanilla, use
ModLoader.VANILLA
and pass""
as loader version. - Additional args append after JSON-specified ones; avoid duplicates.
- Custom JSON paths are relative to
gameDir
. - Use the callback to set env vars or redirect I/O.
BeforeLaunchingEvent
Provide a hook just before the process starts to tweak the ProcessBuilder
—add JVM args, set environment variables, change working directory, or redirect streams.
Definition
package fr.flowarg.openlauncherlib.external;
public interface BeforeLaunchingEvent {
/**
* Called after the ProcessBuilder has been configured by the launcher,
* but before the process is actually started.
* @param builder the ProcessBuilder to customize
*/
void onLaunching(ProcessBuilder builder);
}
Usage
- Implement the interface (or use a lambda).
- Register with your launcher instance.
- Inside
onLaunching
, callbuilder.command()
,builder.environment()
,builder.directory()
, or any otherProcessBuilder
API.
Example: Inject JVM Arg, Env Var & Redirect Output
import fr.flowarg.openlauncherlib.external.BeforeLaunchingEvent;
import fr.flowarg.openlauncherlib.launcher.ExternalLauncher;
ExternalLauncher launcher = new ExternalLauncher(profile, settings);
launcher.setBeforeLaunchingEvent(builder -> {
// Insert debug flag after 'java'
List<String> cmd = builder.command();
cmd.add(1, "-Dexample.debug=true");
builder.command(cmd);
// Set an environment variable
builder.environment().put("MY_LAUNCHER_MODE", "debug");
// Change working directory
builder.directory(new File("C:/mygame"));
// Redirect stdout and stderr
builder.redirectOutput(ProcessBuilder.Redirect.appendTo(new File("logs/output.log")));
builder.redirectErrorStream(true);
});
launcher.launch();
Tips
- JVM argument order matters: insert flags before the main class or jar.
- Use
builder.environment()
to set or override variables (e.g.java.library.path
). - Redirecting streams early helps capture startup logs.
- Changing
builder.directory()
allows relative mods/resources in custom folders.
Development & Contribution Guide
This guide explains how to build, test, sign, publish OpenLauncherLib and contribute fixes or enhancements.
Prerequisites
- Java 8 (JDK 8)
- Git client
- GPG key for signing (with passphrase)
- GitHub account with repository write access
- OSSRH/Maven Central credentials (OSSRH_USERNAME, OSSRH_PASSWORD, SONATYPE_USERNAME, SONATYPE_TOKEN)
1. Gradle Wrapper Setup & Usage
Ensure every build uses Gradle 8.10 without a local install.
Commit these four files:
gradle/wrapper/gradle-wrapper.properties
(distributionUrl=https://services.gradle.org/distributions/gradle-8.10-all.zip)gradle/wrapper/gradle-wrapper.jar
gradlew
(UNIX shell)gradlew.bat
(Windows)
Invoke tasks:
# UNIX/macOS
./gradlew clean build --parallel
./gradlew test
# Windows
gradlew.bat clean build
Override JVM options:
export GRADLE_OPTS="-Xmx2g"
./gradlew build
Upgrade wrapper:
./gradlew wrapper --gradle-version 8.10 --distribution-type all
git add gradlew gradlew.bat gradle/wrapper/gradle-wrapper.properties
git commit -m "Upgrade Gradle wrapper to 8.10"
2. Building & Testing
Run full build and tests locally:
./gradlew clean build
build
compiles, runs tests, creates shadow JAR.test
runs unit tests only.shadowJar
produces a fat JAR inbuild/libs/OpenLauncherLib-all.jar
.
Inspect test reports: build/reports/tests/test/index.html
.
3. Signing & Publishing Artifacts
Local Dry Run
Simulate publishing without pushing:
./gradlew publish \
-PossrhUsername=$OSSRH_USERNAME \
-PossrhPassword=$OSSRH_PASSWORD \
-PgpgPrivateKey="$GPG_PRIVATE_KEY" \
-PgpgPassphrase="$GPG_PASSPHRASE" \
-PsonatypeUsername=$SONATYPE_USERNAME \
-PsonatypeToken=$SONATYPE_TOKEN
GitHub Actions Release
On each GitHub release, CI uses .github/workflows/gradle-publish.yml
:
name: Publish OpenLauncherLib
on:
release:
types: [published]
jobs:
publish:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v4
- name: Set up JDK 8
uses: actions/setup-java@v4
with:
java-version: '8'
distribution: 'zulu'
- name: Publish to Maven Central
run: ./gradlew publish
env:
OSSRH_USERNAME: ${{ secrets.OSSRH_USERNAME }}
OSSRH_PASSWORD: ${{ secrets.OSSRH_PASSWORD }}
SONATYPE_USERNAME: ${{ secrets.SONATYPE_USERNAME }}
SONATYPE_TOKEN: ${{ secrets.SONATYPE_TOKEN }}
GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }}
GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
Secrets to configure in repo Settings → Secrets:
- OSSRH_USERNAME / OSSRH_PASSWORD
- SONATYPE_USERNAME / SONATYPE_TOKEN
- GPG_PRIVATE_KEY (base64-encoded)
- GPG_PASSPHRASE
Verify publication via Maven Central staging interface and GitHub Actions logs.
4. Contributing Code
- Fork the repository and clone your fork.
- Create a feature branch:
git checkout -b feature/your-description
- Implement code and corresponding tests under
src/main/java
andsrc/test/java
. - Ensure code style compliance:
./gradlew check
- Commit with descriptive message:
git add . git commit -m "Add feature X: explain benefit"
- Push and open a Pull Request against
main
. - Pass CI checks (build, tests, lint).
- Address review feedback; squash or rebase as needed.
5. License Compliance
- All source files must include the appropriate license header:
- Use GPLv3 header for application code (see
LICENSE
). - Use LGPLv3 header for library components (see
LICENSE.LESSER
).
- Use GPLv3 header for application code (see
- Place headers at the very top of each file before package/imports.
- Update year and author in each new file.
- Do not remove warranty disclaimer or license pointers.
By following this guide, you ensure consistent builds, secure publication, and smooth collaboration.