Skip to content

Implement game launcher with crash analysis and backup improvements#6

Merged
Luke1505 merged 4 commits intomainfrom
claude/add-minecraft-launch-feature-3u96B
Apr 19, 2026
Merged

Implement game launcher with crash analysis and backup improvements#6
Luke1505 merged 4 commits intomainfrom
claude/add-minecraft-launch-feature-3u96B

Conversation

@Luke1505
Copy link
Copy Markdown
Collaborator

Summary

This PR re-implements the game launcher functionality that was previously removed, adds crash log analysis capabilities, improves backup management, and introduces update checking for mods. The launcher now supports multiple launcher types (Prism, MultiMC, Modrinth, Vanilla) with automatic authentication and Java detection.

Key Changes

Game Launcher (LaunchService)

  • New LaunchService class that handles Minecraft game launching with:
    • Multi-launcher support (Prism Launcher, MultiMC, Modrinth, Vanilla)
    • Automatic authentication reading from launcher data directories
    • Smart Java detection (launcher-bundled, JAVA_HOME, system PATH, common installation paths)
    • Version JSON resolution and classpath building
    • JVM argument expansion with variable substitution
    • Process management with event emission for game logs
    • Support for both modern (1.13+) and legacy argument formats

Game Console UI

  • New GameConsole component for real-time game output monitoring
  • Log filtering by type (stdout/stderr/system)
  • Auto-scroll with manual scroll detection
  • Log download functionality
  • Clear logs button

Crash Analysis

  • New CrashAnalyzer component for analyzing crash logs
  • Drag-and-drop file upload
  • Integration with installed mods for better error detection
  • Identifies problematic mods and provides recommendations

Backup Improvements

  • Enhanced backup creation to include multiple config directories:
    • config/ (main configuration folder)
    • kubejs/ (KubeJS scripts)
    • defaultconfigs/ (default configurations)
  • Backup naming now supports custom names via optional parameter
  • Backup restoration now extracts to instance root to preserve directory structure
  • New backup rename functionality via backup:rename IPC handler

Update Checking

  • New UpdateCheckerService for checking mod updates
  • New updateStore for managing update state
  • Batch checking with rate-limit awareness
  • Integration with Modrinth API for latest version detection
  • Background update checks when instances are loaded

Configuration & Settings

  • HOCON parser for parsing Human-Optimized Config Object Notation files
  • Config presets in ConfigEditor for saving/loading configuration snapshots
  • Reset to defaults functionality for config files
  • JVM memory settings in Settings UI (max/min memory configuration)

UI Enhancements

  • Header updates with game launch/stop buttons
  • Launch state indicators showing running instances
  • Error display for launch failures
  • Recent instances on landing page now support direct launch
  • Smart Search improvements with type filtering and modified-only filter
  • Sidebar collapse functionality for better space management
  • Update indicators in mod list items

IPC Handlers

  • game:launch - Launch a game instance
  • game:kill - Stop a running game
  • game:isRunning - Check if instance is running
  • game:getRunning - Get all running instances
  • game:log - Event for game log output
  • backup:rename - Rename a backup
  • analyzeCrashLog - Analyze crash log files

Implementation Details

  • LaunchService extends EventEmitter for log streaming
  • Process management uses static Map to track running instances
  • Authentication is best-effort with offline fallback
  • Java version detection reads from release file in JRE root
  • Classpath building handles both JAR files and native libraries
  • Variable substitution supports Minecraft launcher variables (auth_player_name, assets_root, etc.)
  • Game processes are detached to allow MCED to close without terminating Minecraft

https://claude.ai/code/session_01G1cMMQeJbAj2BYdK1pP8LC

claude and others added 4 commits February 23, 2026 09:44
Implements a complete launch-from-MCED feature allowing users to start
Minecraft directly without switching to their launcher.

Key changes:
- New LaunchService.ts: direct Java launch with per-launcher support
  (Modrinth, Prism/MultiMC, CurseForge, vanilla fallback)
- Auth tokens read from launcher storage (Prism accounts.json,
  Modrinth app.db, vanilla launcher_accounts.json); offline fallback
- Java detection: launcher-bundled → JAVA_HOME → PATH → common paths
- Version JSON + classpath resolution per launcher type
- Cross-platform classpath separator fix (: on Linux/Mac, ; on Windows)
- 4 IPC handlers: game:launch, game:kill, game:isRunning, game:getRunning
- Preload API: launchGame, killGame, isGameRunning, getRunningGames
- Header.tsx: Play/Stop button with 3s running-state poll
- App.tsx landing page: Play button overlay on recent instance cards
- LandingPage.tsx: onLaunchInstance prop + Play button overlay

https://claude.ai/code/session_01G1cMMQeJbAj2BYdK1pP8LC
- Stream stdout/stderr from the Minecraft process to the renderer via
  IPC (LaunchService emits 'log' events → mainWindow.webContents.send)
- Add onGameLog / removeGameLogListener to preload contextBridge API
- New GameConsole component: scrollable log view with color-coded
  stdout (green), stderr (red), system (yellow) lines, timestamps,
  clear + download actions, auto-scroll with manual override
- Header: ℹ info icon next to Play button shows tooltip warning that
  the instance must be launched in the original launcher at least once
  before using MCED launch (with offline-fallback note)
- Header: Terminal button appears once game output arrives, opens the
  GameConsole modal

https://claude.ai/code/session_01G1cMMQeJbAj2BYdK1pP8LC
## Backup System (ZIP-based)
- backup:create now zips config/, kubejs/ and defaultconfigs/ with proper folder structure so restores work correctly and KubeJS scripts no longer appear as loose files in the backup dir
- backup:restore extracts to instance root so all folders are restored
- Added missing backup:rename IPC handler

## Game Launcher
- JVM heap memory (max/min) is now configurable in Settings (Game Launch section)
- Added jvmMaxMemory/jvmMinMemory to settingsStore (defaults: 4096/1024 MB)
- LaunchService, IPC handler and preload all updated to pass -Xmx/-Xms from settings

## UI/UX
- Sidebar collapse button: click the chevron on the sidebar edge to collapse/expand the mod list
- SmartSearch filter pills: filter results by type (boolean/integer/float/string/enum/array) or "Changed only"
- "Recently Changed" widget on landing page shows last 5 modified settings with mod name

## Config Editor
- Reset to Defaults button: resets all settings in current config to their defaultValue
- Presets: save/load named config presets via localStorage, accessible from toolbar dropdown

## New Format Support
- HOCON parser added (HoconParser.ts) for .conf files used by older Forge mods
- ConfigService.ts updated to detect .conf files as cfg format

## Mod Update Checker
- UpdateCheckerService checks Modrinth for newer mod versions in the background after instance loads
- updateStore (Zustand) caches results; update badges appear in ModListItem

## Crash Log Analyzer
- New CrashAnalyzer.tsx component: drag & drop crash log .txt/.log
- IPC handler crash:analyze extracts main cause, error patterns and mentioned mod IDs
- Button in Header to open analyzer

## Modpack Export (.mrpack)
- IPC handler export:modpack creates mrpack ZIP with modrinth.index.json manifest + config/ and kubejs/ as overrides
- Export button in Header with prompt for pack name

https://claude.ai/code/session_01G1cMMQeJbAj2BYdK1pP8LC
- preload.ts: add launcherType to detectInstance return type
- App.tsx: add lastOpened timestamp to RecentInstance, cast launcherType
- Header.tsx: cast launcher comparisons to string to fix type narrowing errors
- SmithingEditor.tsx: remove extraneous type/amount fields, rename onRemove -> onClear
- index.ts: mark resolved merge conflict
Copilot AI review requested due to automatic review settings April 19, 2026 21:53
@Luke1505 Luke1505 merged commit 62fff7c into main Apr 19, 2026
5 checks passed
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Re-implements end-to-end game launching and observability in the Electron app, adding crash analysis, improved backups, and mod update checking, plus associated UI/UX enhancements and new settings.

Changes:

  • Added a new main-process LaunchService with IPC wiring and renderer UI controls (launch/stop, console log streaming).
  • Introduced crash log analysis UI + IPC handler, and expanded backup creation/restore (incl. kubejs/defaultconfigs) with new rename support.
  • Added Modrinth-based mod update checking with renderer state/store integration and UI indicators; extended config support to include .conf/HOCON parsing.

Reviewed changes

Copilot reviewed 27 out of 28 changed files in this pull request and generated 13 comments.

Show a summary per file
File Description
src/renderer/store/updateStore.ts New Zustand store to track per-mod update metadata and check state.
src/renderer/store/settingsStore.ts Adds JVM memory settings (min/max) to app settings defaults/types.
src/renderer/services/parsers/HoconParser.ts New HOCON parser + stringify support for .conf / HOCON-like configs.
src/renderer/services/api/ModrinthAPI.ts Adds getLatestVersion helper for update checking.
src/renderer/services/UpdateCheckerService.ts New service to batch-check Modrinth versions and populate update store.
src/renderer/services/ConfigService.ts Adds .conf support and maps it to cfg/HOCON parsing.
src/renderer/components/StatusBar.tsx Simplifies status bar visibility/logic around unsaved changes.
src/renderer/components/StatsModal.css Moves styling to CSS variables for theme consistency.
src/renderer/components/SmartSearch.tsx Adds filter pills, “changed only” filtering, and UI refinements.
src/renderer/components/Sidebar.tsx Adds collapsible sidebar UI.
src/renderer/components/Settings.tsx Adds “Game Launch” section for JVM memory configuration.
src/renderer/components/Settings.css Converts settings modal styling to CSS variables and refines UI.
src/renderer/components/ModListItem.tsx Adds update badge indicator on mods with available updates.
src/renderer/components/LandingPage/LandingPage.tsx Adds “launch” affordance for recent instances and prop wiring.
src/renderer/components/KubeJS/RecipeEditor/SmithingEditor.tsx Aligns slot clearing API (onClear) and item shape.
src/renderer/components/Header.tsx Adds launch/stop UI, console/crash analyzer entry points, export button, and running/log tracking.
src/renderer/components/GameConsole.tsx New real-time log console modal with filtering and download.
src/renderer/components/CrashAnalyzer.tsx New crash analysis modal with drag/drop upload and results display.
src/renderer/components/ConfigEditor/ConfigEditor.tsx Adds config presets + reset-to-defaults actions.
src/renderer/components/ChangelogViewer.css Moves styling to CSS variables for theme consistency.
src/renderer/components/Backup/BackupModal.css Minor UI polish (rounded header).
src/renderer/App.tsx Triggers background update checks and adds instance “play” actions + toast UI.
src/main/tsconfig.main.json Adds ignoreDeprecations configuration.
src/main/preload.ts Exposes new IPC APIs for launching, logs, crash analysis, export, and backup rename.
src/main/index.ts Adds backup directory isolation, backup rename, crash analysis handler, export handler, and game launch IPC handlers.
src/main/LaunchService.ts New main-process launcher implementation (auth, Java detection, classpath/natives, process management, log emission).
package.json Adds node-gyp dev dependency.
package-lock.json Locks updated dependency tree (incl. node-gyp and transitive updates).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/main/index.ts
Comment on lines +949 to +956
const match = backupId.match(/^backup-(\d+)-/);
const timestamp = match ? match[1] : String(Date.now());

const newId = `backup-${timestamp}-${newName.replace(/ /g, "-")}.zip`;
const newFile = path.join(backupDir, newId);

await fs.rename(oldFile, newFile);
return { success: true, newId };
Copy link

Copilot AI Apr 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

newName is used to construct the new backup filename with only spaces replaced. This allows path separators and other characters that can create confusing paths or invalid filenames, and may also overwrite an existing backup name. Sanitize newName similarly to getInstanceBackupDir and consider rejecting/handling name collisions before calling fs.rename.

Copilot uses AI. Check for mistakes.
Comment thread src/main/index.ts
Comment on lines 883 to +885
const zip = new AdmZip(backupFile);
zip.extractAllTo(configDir, true);
// Extract to instance root so config/, kubejs/, defaultconfigs/ are all restored correctly
zip.extractAllTo(instancePath, true);
Copy link

Copilot AI Apr 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

backup:restore now extracts the entire ZIP to instancePath. Since backups can be manipulated on disk, this allows restoring arbitrary files into the instance root (potentially overwriting unrelated instance data). Consider restricting extraction to expected top-level folders (config/, kubejs/, defaultconfigs/) and validating each entry path before extraction.

Copilot uses AI. Check for mistakes.
Comment on lines +310 to +316
className="p-1.5 text-blue-400/60 hover:text-blue-400 transition-colors rounded-lg hover:bg-blue-500/10"
tabIndex={-1}
aria-label="First-time launch info"
>
<Info className="w-3.5 h-3.5" />
</button>
<div className="absolute right-0 top-full mt-2 w-80 z-50 pointer-events-none opacity-0 group-hover/info:opacity-100 transition-opacity">
Copy link

Copilot AI Apr 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The info button is removed from the tab order via tabIndex={-1}, making the launch hint inaccessible to keyboard users. Consider keeping it focusable (default tabIndex) and showing the tooltip on focus as well as hover, or replacing it with non-interactive text if it shouldn't be reachable.

Suggested change
className="p-1.5 text-blue-400/60 hover:text-blue-400 transition-colors rounded-lg hover:bg-blue-500/10"
tabIndex={-1}
aria-label="First-time launch info"
>
<Info className="w-3.5 h-3.5" />
</button>
<div className="absolute right-0 top-full mt-2 w-80 z-50 pointer-events-none opacity-0 group-hover/info:opacity-100 transition-opacity">
type="button"
className="p-1.5 text-blue-400/60 hover:text-blue-400 transition-colors rounded-lg hover:bg-blue-500/10 focus-visible:text-blue-400 focus-visible:bg-blue-500/10"
aria-label="First-time launch info"
aria-describedby="launch-info-tooltip"
>
<Info className="w-3.5 h-3.5" />
</button>
<div
id="launch-info-tooltip"
role="tooltip"
className="absolute right-0 top-full mt-2 w-80 z-50 pointer-events-none opacity-0 group-hover/info:opacity-100 group-focus-within/info:opacity-100 transition-opacity"
>

Copilot uses AI. Check for mistakes.
Comment on lines +19 to 21
const getUpdate = useUpdateStore((s) => s.getUpdate);
const hasUpdate = !!getUpdate(mod.modId);

Copy link

Copilot AI Apr 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

useUpdateStore((s) => s.getUpdate) subscribes only to the function reference, which is stable; this component will not re-render when updates changes, so the update badge may never appear/disappear. Subscribe to the derived value instead (e.g. select s.updates.get(mod.modId) or s.getUpdate(mod.modId) inside the selector) so Zustand can trigger re-renders on update changes.

Copilot uses AI. Check for mistakes.

useEffect(() => {
if (autoScroll) {
bottomRef.current?.scrollIntoView({ behavior: "instant" });
Copy link

Copilot AI Apr 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

scrollIntoView does not support behavior: "instant" (standard values are "auto" and "smooth"); this will fail TypeScript type-checking (and may be ignored at runtime). Use behavior: "auto" or omit the option for an immediate jump.

Suggested change
bottomRef.current?.scrollIntoView({ behavior: "instant" });
bottomRef.current?.scrollIntoView({ behavior: "auto" });

Copilot uses AI. Check for mistakes.
export function LandingPage({ onSelectInstance, onLaunchInstance, recentInstances = [] }: LandingPageProps) {
const [hoveredCard, setHoveredCard] = useState<string | null>(null);

console.log("LandingPage recentInstances:", recentInstances);
Copy link

Copilot AI Apr 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Leftover debug logging (console.log("LandingPage recentInstances:", ...)) will spam the console in production. Please remove or guard it behind a development flag.

Copilot uses AI. Check for mistakes.
Comment on lines +65 to +66
const running = await window.api.isGameRunning(currentInstance.path);
if (!cancelled) setIsRunning(running);
Copy link

Copilot AI Apr 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The check function awaits window.api.isGameRunning(...) without a try/catch. If the IPC call rejects (e.g. main process error), this will create an unhandled rejection every 3 seconds. Wrap the await in a try/catch and decide on a safe fallback (e.g. treat as not running and stop polling).

Suggested change
const running = await window.api.isGameRunning(currentInstance.path);
if (!cancelled) setIsRunning(running);
try {
const running = await window.api.isGameRunning(currentInstance.path);
if (!cancelled) setIsRunning(running);
} catch {
if (!cancelled) setIsRunning(false);
}

Copilot uses AI. Check for mistakes.
Comment on lines +39 to +49
// Compare versions - simple string comparison
if (latest.version !== mod.version && latest.version > mod.version) {
updates.set(mod.modId, {
modId: mod.modId,
currentVersion: mod.version,
latestVersion: latest.version,
latestVersionId: latest.versionId,
changelog: latest.changelog,
source: "modrinth",
});
}
Copy link

Copilot AI Apr 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Update detection compares versions using plain string ordering (latest.version > mod.version). This produces incorrect results for common version formats (e.g. "10.0.0" vs "2.0.0", or Forge-style build numbers). Consider using a real semver comparator when possible, or simply flag an update when the version differs (and optionally prefer the newest by date_published from Modrinth).

Copilot uses AI. Check for mistakes.
Comment thread src/main/LaunchService.ts
Comment on lines +366 to +391
private async findJava(launcher: string, mcVersion: string): Promise<string> {
const requiredMajor = this.requiredJavaMajor(mcVersion);
const javaExe = os.platform() === 'win32' ? 'java.exe' : 'java';

// 1. Launcher-bundled Java
const launcherJava = await this.findLauncherJava(launcher, javaExe, requiredMajor);
if (launcherJava) return launcherJava;

// 2. JAVA_HOME
if (process.env.JAVA_HOME) {
const candidate = path.join(process.env.JAVA_HOME, 'bin', javaExe);
if (await this.fileExists(candidate)) return candidate;
}

// 3. System PATH
try {
const cmd = os.platform() === 'win32' ? 'where java' : 'which java';
const { stdout } = await execAsync(cmd);
const javaPath = stdout.trim().split('\n')[0].trim();
if (javaPath && (await this.fileExists(javaPath))) return javaPath;
} catch {}

// 4. Common installation paths
for (const candidate of this.getCommonJavaPaths(javaExe)) {
if (await this.fileExists(candidate)) return candidate;
}
Copy link

Copilot AI Apr 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

findJava computes requiredMajor but only enforces it for launcher-bundled Java. For JAVA_HOME / PATH / common paths, it returns the first java found without checking the major version, which can reliably break launching (e.g. Java 8 found first for MC 1.20+). Consider validating candidates by running java -version (or reading the release file when possible) and ensuring major >= requiredMajor before accepting.

Copilot uses AI. Check for mistakes.
Comment thread src/main/index.ts
const name = `Backup-${new Date(timestamp).toISOString().split("T")[0]}`;
const backupFile = path.join(backupDir, `backup-${timestamp}-${name.replace(/ /g, "-")}.zip`);
const backupName = name || `Backup-${new Date(timestamp).toISOString().split("T")[0]}`;
const backupFile = path.join(backupDir, `backup-${timestamp}-${backupName.replace(/ /g, "-")}.zip`);
Copy link

Copilot AI Apr 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

backupName is incorporated into the output filename with only spaces replaced. If the caller supplies path separators or other special characters, this can create unintended nested paths or invalid filenames. Sanitize backupName (e.g., allow only [a-zA-Z0-9_-], or run through path.basename and replace other characters) before building backupFile.

Suggested change
const backupFile = path.join(backupDir, `backup-${timestamp}-${backupName.replace(/ /g, "-")}.zip`);
const sanitizedBackupName = path.basename(backupName)
.replace(/[^a-zA-Z0-9_-]/g, "-")
.replace(/-+/g, "-")
.replace(/^-|-$/g, "") || "backup";
const backupFile = path.join(backupDir, `backup-${timestamp}-${sanitizedBackupName}.zip`);

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants