Some checks failed
Axolotl desktop CI / guardrails (push) Has been cancelled
Axolotl desktop CI / desktop (macos-latest) (push) Has been cancelled
Axolotl desktop CI / desktop (ubuntu-latest) (push) Has been cancelled
Axolotl desktop CI / desktop (windows-latest) (push) Has been cancelled
Axolotl desktop CI / website (push) Has been cancelled
Repository checks / typos (push) Has been cancelled
Repository checks / tombi (push) Has been cancelled
Rust checks / shear (push) Has been cancelled
Sync source to CNB / Sync Git ref (push) Has been cancelled
Deploy telemetry dashboard / deploy (push) Has been cancelled
Prepare pnpm cache / prepare (push) Has been cancelled
1115 lines
30 KiB
TypeScript
1115 lines
30 KiB
TypeScript
/**
|
|
* All theseus API calls return serialized values (both return values and errors);
|
|
* So, for example, addDefaultInstance creates a blank instance object, where the Rust struct is serialized,
|
|
* and deserialized into a usable JS object.
|
|
*/
|
|
import type { Labrinth } from '@modrinth/api-client'
|
|
import {
|
|
clearPinnedContentViewPreferences,
|
|
type ContentItem,
|
|
type ContentOwner,
|
|
} from '@modrinth/ui'
|
|
import { invoke } from '@tauri-apps/api/core'
|
|
|
|
import { isOfflineMode } from '@/composables/useNetworkStatus'
|
|
import { buildGcCandidateChain, resolveAutoGcStrategy } from '@/helpers/gc/auto-selector'
|
|
import { collectGcContext } from '@/helpers/gc/context'
|
|
import { detectGcStrategy, GC_STRATEGY_DEFINITIONS } from '@/helpers/gc/strategies'
|
|
import type { GcContext, ResolvedGcStrategyId } from '@/helpers/gc/types'
|
|
import { setLastGcLaunchReport } from '@/helpers/gc-notice'
|
|
import { AUTO_GC_PRESET_ARG } from '@/helpers/java-arguments'
|
|
import { get_jre, get_memory_status } from '@/helpers/jre.js'
|
|
import { get as getSettings } from '@/helpers/settings'
|
|
|
|
import type { InstallJobSnapshot } from './install'
|
|
import { removeInstanceCache } from './instance-cache'
|
|
import type {
|
|
CacheBehaviour,
|
|
ContentFile,
|
|
ContentFileProjectType,
|
|
GameInstance,
|
|
InstanceLoader,
|
|
} from './types'
|
|
|
|
export interface InstancePostUpgradeWarning {
|
|
code: import('./instance-upgrade').InstanceUpgradeIssueCode
|
|
contentId: string | null
|
|
relativePath: string | null
|
|
}
|
|
|
|
export interface InstancePostUpgradeNotice {
|
|
instanceId: string
|
|
upgradeJobId: string
|
|
targetGameVersion: string
|
|
consecutiveCleanLaunches: number
|
|
warnings: InstancePostUpgradeWarning[]
|
|
}
|
|
|
|
export async function get_post_upgrade_notice(
|
|
instanceId: string,
|
|
): Promise<InstancePostUpgradeNotice | null> {
|
|
return await invoke('plugin:instance|instance_get_post_upgrade_notice', { instanceId })
|
|
}
|
|
|
|
export async function dismiss_post_upgrade_notice(instanceId: string): Promise<void> {
|
|
await invoke('plugin:instance|instance_dismiss_post_upgrade_notice', { instanceId })
|
|
}
|
|
|
|
export async function remove(instanceId: string): Promise<void> {
|
|
await invoke('plugin:instance|instance_remove', { instanceId })
|
|
removeInstanceCache(instanceId)
|
|
clearPinnedContentViewPreferences(instanceId)
|
|
}
|
|
|
|
export interface CreateDirectLinkInstanceRequest {
|
|
name?: string | null
|
|
launcherType: 'HMCL' | 'PCL2' | 'PCL2CE' | 'Generic'
|
|
basePath: string
|
|
instanceFolder: string
|
|
instancePath?: string | null
|
|
}
|
|
|
|
/** Associate an existing `.minecraft/versions/<id>` directory in place. */
|
|
export async function create_direct_link(request: CreateDirectLinkInstanceRequest) {
|
|
return await invoke<GameInstance>('plugin:instance|instance_create_direct_link', { request })
|
|
}
|
|
|
|
export interface DirectLinkSyncReport {
|
|
imported: number
|
|
updated: number
|
|
removed: number
|
|
missing: number
|
|
errors: string[]
|
|
}
|
|
|
|
export type ExternalMinecraftRootMode = 'isolated' | 'shared'
|
|
|
|
export interface ExternalMinecraftRoot {
|
|
path: string
|
|
mode: ExternalMinecraftRootMode
|
|
}
|
|
|
|
export async function sync_direct_links(
|
|
roots: ExternalMinecraftRoot[],
|
|
): Promise<DirectLinkSyncReport> {
|
|
return await invoke<DirectLinkSyncReport>('plugin:instance|instance_sync_direct_links', {
|
|
roots,
|
|
})
|
|
}
|
|
|
|
export async function get(instanceId: string): Promise<GameInstance | null> {
|
|
return await invoke('plugin:instance|instance_get', { instanceId })
|
|
}
|
|
|
|
export async function get_many(instanceIds: string[]): Promise<GameInstance[]> {
|
|
return await invoke('plugin:instance|instance_get_many', { instanceIds })
|
|
}
|
|
|
|
export async function get_projects(
|
|
instanceId: string,
|
|
cacheBehaviour?: CacheBehaviour,
|
|
): Promise<Record<string, ContentFile>> {
|
|
return await invoke('plugin:instance|instance_get_projects', { instanceId, cacheBehaviour })
|
|
}
|
|
|
|
export async function get_installed_project_ids(instanceId: string): Promise<string[]> {
|
|
return await invoke('plugin:instance|instance_get_installed_project_ids', { instanceId })
|
|
}
|
|
|
|
export type CoreComponentKind = 'jar_mod' | 'replacement_jar' | 'agent'
|
|
|
|
export type CoreComponentSource = {
|
|
provider: string
|
|
projectId: string | null
|
|
versionId: string | null
|
|
fileId: string | null
|
|
pageUrl: string | null
|
|
}
|
|
|
|
export type CoreComponent = {
|
|
id: string
|
|
kind: CoreComponentKind
|
|
fileName: string
|
|
relativePath: string
|
|
enabled: boolean
|
|
removed: boolean
|
|
order: number
|
|
sha1: string | null
|
|
sha256: string | null
|
|
source: CoreComponentSource | null
|
|
targetGameVersion: string
|
|
createdAt: string
|
|
modifiedAt: string
|
|
failureReason: string | null
|
|
}
|
|
|
|
export type CoreJarPreview = {
|
|
outputPath: string
|
|
componentCount: number
|
|
replacementComponentId: string | null
|
|
entries: number
|
|
sha1: string
|
|
sha256: string
|
|
}
|
|
|
|
export type McArchiveCoreInstallResult =
|
|
| { state: 'installed'; component: CoreComponent }
|
|
| {
|
|
state: 'manual_download'
|
|
fileName: string
|
|
pageUrl: string | null
|
|
expectedSha256: string | null
|
|
}
|
|
|
|
export async function list_core_components(instanceId: string): Promise<CoreComponent[]> {
|
|
return await invoke('plugin:instance|instance_list_core_components', { instanceId })
|
|
}
|
|
|
|
export async function add_core_jar_mod(
|
|
instanceId: string,
|
|
sourcePath: string,
|
|
targetGameVersion: string,
|
|
source?: CoreComponentSource,
|
|
): Promise<CoreComponent> {
|
|
return await invoke('plugin:instance|instance_add_core_jar_mod', {
|
|
instanceId,
|
|
sourcePath,
|
|
targetGameVersion,
|
|
source,
|
|
})
|
|
}
|
|
|
|
export async function replace_core_jar(
|
|
instanceId: string,
|
|
sourcePath: string,
|
|
targetGameVersion: string,
|
|
source?: CoreComponentSource,
|
|
): Promise<CoreComponent> {
|
|
return await invoke('plugin:instance|instance_replace_core_jar', {
|
|
instanceId,
|
|
sourcePath,
|
|
targetGameVersion,
|
|
source,
|
|
})
|
|
}
|
|
|
|
export async function move_core_component(
|
|
instanceId: string,
|
|
componentId: string,
|
|
direction: -1 | 1,
|
|
): Promise<CoreComponent[]> {
|
|
return await invoke('plugin:instance|instance_move_core_component', {
|
|
instanceId,
|
|
componentId,
|
|
direction,
|
|
})
|
|
}
|
|
|
|
export async function set_core_component_enabled(
|
|
instanceId: string,
|
|
componentId: string,
|
|
enabled: boolean,
|
|
): Promise<CoreComponent> {
|
|
return await invoke('plugin:instance|instance_set_core_component_enabled', {
|
|
instanceId,
|
|
componentId,
|
|
enabled,
|
|
})
|
|
}
|
|
|
|
export async function remove_core_component(
|
|
instanceId: string,
|
|
componentId: string,
|
|
): Promise<void> {
|
|
await invoke('plugin:instance|instance_remove_core_component', { instanceId, componentId })
|
|
}
|
|
|
|
export async function restore_core_component(
|
|
instanceId: string,
|
|
componentId: string,
|
|
): Promise<CoreComponent> {
|
|
return await invoke('plugin:instance|instance_restore_core_component', {
|
|
instanceId,
|
|
componentId,
|
|
})
|
|
}
|
|
|
|
export async function preview_core_jar(instanceId: string): Promise<CoreJarPreview | null> {
|
|
return await invoke('plugin:instance|instance_preview_core_jar', { instanceId })
|
|
}
|
|
|
|
export async function install_mcarchive_modloader(
|
|
instanceId: string,
|
|
gameVersion: string,
|
|
): Promise<McArchiveCoreInstallResult> {
|
|
return await invoke('plugin:instance|instance_install_mcarchive_modloader', {
|
|
instanceId,
|
|
gameVersion,
|
|
})
|
|
}
|
|
|
|
export async function import_mcarchive_modloader(
|
|
instanceId: string,
|
|
gameVersion: string,
|
|
sourcePath: string,
|
|
): Promise<McArchiveCoreInstallResult> {
|
|
return await invoke('plugin:instance|instance_import_mcarchive_modloader', {
|
|
instanceId,
|
|
gameVersion,
|
|
sourcePath,
|
|
})
|
|
}
|
|
|
|
export type McArchiveContentInstallRequest = {
|
|
projectId: string
|
|
projectSlug: string
|
|
versionId: string
|
|
fileId: string
|
|
projectType: ContentFileProjectType
|
|
}
|
|
|
|
export type McArchiveContentInstallResult =
|
|
| { state: 'installed'; relativePath: string }
|
|
| {
|
|
state: 'manual_download'
|
|
fileName: string
|
|
pageUrl: string | null
|
|
expectedSha256: string | null
|
|
}
|
|
|
|
export type PlanetMinecraftContentInstallRequest = {
|
|
projectId: string
|
|
versionId: string
|
|
projectType: ContentFileProjectType
|
|
}
|
|
|
|
export type PlanetMinecraftContentInstallResult =
|
|
| { state: 'installed'; relativePath: string }
|
|
| { state: 'manual_download'; pageUrl: string; fileName: string | null }
|
|
|
|
export async function install_mcarchive_content(
|
|
instanceId: string,
|
|
request: McArchiveContentInstallRequest,
|
|
): Promise<McArchiveContentInstallResult> {
|
|
return await invoke('plugin:instance|instance_install_mcarchive_content', {
|
|
instanceId,
|
|
request,
|
|
})
|
|
}
|
|
|
|
export async function import_mcarchive_content(
|
|
instanceId: string,
|
|
request: McArchiveContentInstallRequest,
|
|
sourcePath: string,
|
|
): Promise<McArchiveContentInstallResult> {
|
|
return await invoke('plugin:instance|instance_import_mcarchive_content', {
|
|
instanceId,
|
|
request,
|
|
sourcePath,
|
|
})
|
|
}
|
|
|
|
export async function install_planet_minecraft_content(
|
|
instanceId: string,
|
|
request: PlanetMinecraftContentInstallRequest,
|
|
): Promise<PlanetMinecraftContentInstallResult> {
|
|
return await invoke('plugin:instance|instance_install_planet_minecraft_content', {
|
|
instanceId,
|
|
request,
|
|
})
|
|
}
|
|
|
|
export async function import_planet_minecraft_content(
|
|
instanceId: string,
|
|
request: PlanetMinecraftContentInstallRequest,
|
|
sourcePath: string,
|
|
): Promise<PlanetMinecraftContentInstallResult> {
|
|
return await invoke('plugin:instance|instance_import_planet_minecraft_content', {
|
|
instanceId,
|
|
request,
|
|
sourcePath,
|
|
})
|
|
}
|
|
|
|
export type InstanceInstallTarget = {
|
|
game_version: string
|
|
loader: string
|
|
}
|
|
|
|
export type InstanceInstallCandidate = {
|
|
id: string
|
|
name: string
|
|
icon_path?: string | null
|
|
game_version: string
|
|
loader: InstanceLoader
|
|
installed: boolean
|
|
compatible: boolean
|
|
}
|
|
|
|
export async function get_install_candidates(
|
|
projectId: string,
|
|
projectType: string,
|
|
targets: InstanceInstallTarget[],
|
|
): Promise<InstanceInstallCandidate[]> {
|
|
return await invoke('plugin:instance|instance_get_install_candidates', {
|
|
projectId,
|
|
projectType,
|
|
targets,
|
|
})
|
|
}
|
|
|
|
// Get content items with rich metadata for an instance
|
|
// Returns content items filtered to exclude modpack files (if linked),
|
|
// sorted alphabetically by project name
|
|
export async function get_content_items(
|
|
instanceId: string,
|
|
cacheBehaviour?: CacheBehaviour,
|
|
): Promise<ContentItem[]> {
|
|
const items = await invoke<ContentItem[]>('plugin:instance|instance_get_content_items', {
|
|
instanceId,
|
|
cacheBehaviour,
|
|
})
|
|
return items
|
|
}
|
|
|
|
export async function get_content_items_by_paths(
|
|
instanceId: string,
|
|
paths: string[],
|
|
cacheBehaviour?: CacheBehaviour,
|
|
): Promise<ContentItem[]> {
|
|
return await invoke<ContentItem[]>('plugin:instance|instance_get_content_items_by_paths', {
|
|
instanceId,
|
|
paths,
|
|
cacheBehaviour,
|
|
})
|
|
}
|
|
|
|
export type ContentOwnershipKind = 'pack_managed' | 'user_added' | 'local_discovered'
|
|
export type PackMemberMaterializationState = 'present' | 'pending_manual' | 'missing' | 'removed'
|
|
export type PackMemberOverrideKind = 'none' | 'disabled' | 'removed' | 'version'
|
|
|
|
export interface InstanceContentSnapshotItem {
|
|
fileId: string | null
|
|
entryId: string | null
|
|
memberId: string | null
|
|
ownershipKind: ContentOwnershipKind
|
|
materializationState: PackMemberMaterializationState
|
|
overrideKind: PackMemberOverrideKind
|
|
expectedRelativePath: string
|
|
required: boolean
|
|
projectType: string
|
|
provider: 'modrinth' | 'curseforge' | 'mcarchive' | null
|
|
providerProjectId: string | null
|
|
providerReleaseId: string | null
|
|
content: ContentItem | null
|
|
capabilities: {
|
|
canToggle: boolean
|
|
canDelete: boolean
|
|
canUpdate: boolean
|
|
canChangeVersion: boolean
|
|
canRestorePackDefault: boolean
|
|
}
|
|
dependency: {
|
|
autoDependency: boolean
|
|
requiredBy: Array<{
|
|
provider: 'modrinth' | 'curseforge' | 'mcarchive' | 'local'
|
|
projectId: string
|
|
releaseId: string
|
|
}>
|
|
requires: Array<{
|
|
provider: 'modrinth' | 'curseforge' | 'mcarchive' | 'local'
|
|
projectId: string
|
|
releaseId: string
|
|
}>
|
|
orphaned: boolean
|
|
} | null
|
|
}
|
|
|
|
export interface PendingManualDownload {
|
|
id: string
|
|
instanceId: string
|
|
packMemberId: string | null
|
|
contentEntryId: string | null
|
|
operationKind: 'pack_install' | 'pack_update' | 'content_install' | 'content_update'
|
|
operationTargetId: string | null
|
|
projectType: string
|
|
provider: 'modrinth' | 'curseforge' | 'mcarchive'
|
|
providerProjectId: string
|
|
providerReleaseId: string
|
|
fileName: string
|
|
websiteUrl: string | null
|
|
targetRelativePath: string
|
|
expectedSha1: string | null
|
|
expectedSize: number | null
|
|
expectedFingerprint: number | null
|
|
state: 'waiting' | 'matched' | 'imported' | 'error' | 'cancelled'
|
|
context: Record<string, unknown>
|
|
createdAt: string
|
|
modifiedAt: string
|
|
}
|
|
|
|
export interface InstanceContentSnapshot {
|
|
instanceId: string
|
|
revision: number
|
|
pack: {
|
|
name: string
|
|
iconPath: string | null
|
|
provider: 'modrinth' | 'curseforge' | 'mcarchive' | null
|
|
projectId: string | null
|
|
versionId: string | null
|
|
reconciled: boolean
|
|
canUpdate: boolean
|
|
metadata: LinkedModpackInfo | null
|
|
} | null
|
|
items: InstanceContentSnapshotItem[]
|
|
pendingManualDownloads: PendingManualDownload[]
|
|
warnings: Array<{
|
|
code: string
|
|
message: string
|
|
provider: 'modrinth' | 'curseforge' | 'mcarchive' | null
|
|
}>
|
|
}
|
|
|
|
export async function get_content_snapshot(instanceId: string): Promise<InstanceContentSnapshot> {
|
|
return await invoke('plugin:instance|instance_get_content_snapshot', { instanceId })
|
|
}
|
|
|
|
export async function refresh_content(instanceId: string): Promise<InstanceContentSnapshot> {
|
|
return await invoke('plugin:instance|instance_refresh_content', { instanceId })
|
|
}
|
|
|
|
export type ContentUpdateScope = 'user_added' | 'pack' | 'item'
|
|
|
|
export interface ContentUpdatePlan {
|
|
id: string
|
|
instanceId: string
|
|
revision: number
|
|
scope: ContentUpdateScope
|
|
actions: Array<{
|
|
contentId: string
|
|
relativePath: string | null
|
|
ownershipKind: ContentOwnershipKind
|
|
provider: 'modrinth' | 'curseforge' | 'mcarchive'
|
|
currentReleaseId: string | null
|
|
targetReleaseId: string
|
|
}>
|
|
warnings: string[]
|
|
}
|
|
|
|
export interface ContentUpdateResolution {
|
|
contentId: string
|
|
choice: 'keep_override' | 'restore_pack_default'
|
|
}
|
|
|
|
export async function plan_content_updates(
|
|
instanceId: string,
|
|
scope: ContentUpdateScope,
|
|
target?: string,
|
|
): Promise<ContentUpdatePlan> {
|
|
return await invoke('plugin:instance|instance_plan_content_updates', {
|
|
instanceId,
|
|
scope,
|
|
target,
|
|
})
|
|
}
|
|
|
|
export async function apply_content_update_plan(
|
|
planId: string,
|
|
resolutions: ContentUpdateResolution[] = [],
|
|
): Promise<InstanceContentSnapshot> {
|
|
return await invoke('plugin:instance|instance_apply_content_update_plan', {
|
|
planId,
|
|
resolutions,
|
|
})
|
|
}
|
|
|
|
// Linked modpack info returned from backend
|
|
export interface LinkedModpackInfo {
|
|
project: Labrinth.Projects.v2.Project
|
|
version: Labrinth.Versions.v2.Version
|
|
owner: ContentOwner | null
|
|
update:
|
|
| {
|
|
provider: 'modrinth'
|
|
project_id: string
|
|
current_version_id: string
|
|
target_version_id: string
|
|
}
|
|
| {
|
|
provider: 'curseforge'
|
|
project_id: number
|
|
current_file_id: number
|
|
target_file_id: number
|
|
}
|
|
| null
|
|
update_version: Labrinth.Versions.v2.Version | null
|
|
}
|
|
|
|
// Get linked modpack info for an instance
|
|
// Returns project, version, and owner information for the linked modpack,
|
|
// or null if the instance is not linked to a modpack
|
|
export async function get_linked_modpack_info(
|
|
instanceId: string,
|
|
cacheBehaviour?: CacheBehaviour,
|
|
): Promise<LinkedModpackInfo | null> {
|
|
return await invoke('plugin:instance|instance_get_linked_modpack_info', {
|
|
instanceId,
|
|
cacheBehaviour,
|
|
})
|
|
}
|
|
|
|
// Get content items that are part of the linked modpack
|
|
// Returns the modpack's dependencies as ContentItem list
|
|
// Returns empty array if the instance is not linked to a modpack
|
|
export async function get_linked_modpack_content(
|
|
instanceId: string,
|
|
cacheBehaviour?: CacheBehaviour,
|
|
): Promise<ContentItem[]> {
|
|
const items = await invoke<ContentItem[]>('plugin:instance|instance_get_linked_modpack_content', {
|
|
instanceId,
|
|
cacheBehaviour,
|
|
})
|
|
return items
|
|
}
|
|
|
|
// Convert a list of dependencies into ContentItems with rich metadata
|
|
export async function get_dependencies_as_content_items(
|
|
dependencies: Labrinth.Versions.v3.Dependency[],
|
|
cacheBehaviour?: CacheBehaviour,
|
|
): Promise<ContentItem[]> {
|
|
const items = await invoke<ContentItem[]>(
|
|
'plugin:instance|instance_get_dependencies_as_content_items',
|
|
{
|
|
dependencies,
|
|
cacheBehaviour,
|
|
},
|
|
)
|
|
return items
|
|
}
|
|
|
|
export async function get_full_path(instanceId: string): Promise<string> {
|
|
return await invoke('plugin:instance|instance_get_full_path', { instanceId })
|
|
}
|
|
|
|
export async function get_mod_full_path(instanceId: string, projectPath: string): Promise<string> {
|
|
return await invoke('plugin:instance|instance_get_mod_full_path', { instanceId, projectPath })
|
|
}
|
|
|
|
export interface JavaVersion {
|
|
parsed_version: number
|
|
version: string
|
|
architecture: string
|
|
path: string
|
|
}
|
|
|
|
export async function get_optimal_jre_key(instanceId: string): Promise<JavaVersion | null> {
|
|
return await invoke('plugin:instance|instance_get_optimal_jre_key', { instanceId })
|
|
}
|
|
|
|
export async function list(): Promise<GameInstance[]> {
|
|
return await invoke('plugin:instance|instance_list')
|
|
}
|
|
|
|
export type DailyPlaytime = {
|
|
date: string
|
|
played_seconds: number
|
|
session_count: number
|
|
top_instance_name?: string | null
|
|
}
|
|
|
|
export async function set_pinned(instanceId: string, pinned: boolean): Promise<GameInstance> {
|
|
return await invoke('plugin:instance|instance_set_pinned', { instanceId, pinned })
|
|
}
|
|
|
|
export async function get_daily_playtime(
|
|
startDate: string,
|
|
endDate: string,
|
|
): Promise<DailyPlaytime[]> {
|
|
return await invoke('plugin:instance|instance_get_daily_playtime', { startDate, endDate })
|
|
}
|
|
|
|
export type DailyPlaytimeEntry = {
|
|
instance_id: string
|
|
instance_name: string
|
|
played_seconds: number
|
|
session_count: number
|
|
}
|
|
|
|
export async function get_daily_playtime_details(date: string): Promise<DailyPlaytimeEntry[]> {
|
|
return await invoke('plugin:instance|instance_get_daily_playtime_details', { date })
|
|
}
|
|
|
|
export async function check_installed(instanceId: string, projectId: string): Promise<boolean> {
|
|
return await invoke('plugin:instance|instance_check_installed', { instanceId, projectId })
|
|
}
|
|
|
|
export async function update_all(instanceId: string): Promise<Record<string, string>> {
|
|
return await invoke('plugin:instance|instance_update_all', { instanceId })
|
|
}
|
|
|
|
// Updates a specified project
|
|
export async function update_project(instanceId: string, projectPath: string): Promise<string> {
|
|
return await invoke('plugin:instance|instance_update_project', { instanceId, projectPath })
|
|
}
|
|
|
|
export interface ResolutionPreferences {
|
|
game_versions?: string[]
|
|
loaders?: string[]
|
|
}
|
|
|
|
export interface ResolveContentRequest {
|
|
project_id: string
|
|
version_id?: string | null
|
|
content_type: Labrinth.Content.v3.ContentType
|
|
selected?: ResolutionPreferences
|
|
excluded_project_ids?: string[]
|
|
force_project_ids?: string[]
|
|
}
|
|
|
|
export interface ResolvedContent {
|
|
project_id: string
|
|
version_id: string
|
|
dependent_on_version_id?: string | null
|
|
required?: boolean
|
|
}
|
|
|
|
export interface ResolveContentPlan {
|
|
primary: ResolvedContent
|
|
dependencies: ResolvedContent[]
|
|
skipped: Array<{
|
|
project_id: string
|
|
version_id?: string | null
|
|
dependent_on_version_id?: string | null
|
|
reason: string
|
|
}>
|
|
}
|
|
|
|
export async function add_project_from_version(
|
|
instanceId: string,
|
|
versionId: string,
|
|
): Promise<string> {
|
|
return await invoke('plugin:instance|instance_add_project_from_version', {
|
|
instanceId,
|
|
versionId,
|
|
})
|
|
}
|
|
|
|
export async function install_project_with_dependencies(
|
|
instanceId: string,
|
|
request: ResolveContentRequest,
|
|
): Promise<ResolveContentPlan> {
|
|
return await invoke('plugin:instance|instance_install_project_with_dependencies', {
|
|
instanceId,
|
|
request,
|
|
})
|
|
}
|
|
|
|
export async function preview_project_with_dependencies(
|
|
instanceId: string,
|
|
request: ResolveContentRequest,
|
|
): Promise<ResolveContentPlan> {
|
|
return await invoke('plugin:instance|instance_preview_project_with_dependencies', {
|
|
instanceId,
|
|
request,
|
|
})
|
|
}
|
|
|
|
export async function preview_project_with_dependencies_for_target(
|
|
request: ResolveContentRequest,
|
|
gameVersion: string,
|
|
loader: string,
|
|
): Promise<ResolveContentPlan> {
|
|
return await invoke('plugin:instance|instance_preview_project_with_dependencies_for_target', {
|
|
request,
|
|
gameVersion,
|
|
loader,
|
|
})
|
|
}
|
|
|
|
export async function queue_project_with_dependencies(
|
|
instanceId: string,
|
|
request: ResolveContentRequest,
|
|
display: { title: string; iconUrl?: string | null },
|
|
): Promise<InstallJobSnapshot> {
|
|
return await invoke('plugin:instance|instance_queue_project_with_dependencies', {
|
|
instanceId,
|
|
request,
|
|
displayTitle: display.title,
|
|
displayIcon: display.iconUrl ?? null,
|
|
})
|
|
}
|
|
|
|
export async function switch_project_version_with_dependencies(
|
|
instanceId: string,
|
|
projectPath: string,
|
|
versionId: string,
|
|
): Promise<string> {
|
|
return await invoke('plugin:instance|instance_switch_project_version_with_dependencies', {
|
|
instanceId,
|
|
projectPath,
|
|
versionId,
|
|
})
|
|
}
|
|
|
|
// Import a world save into an instance
|
|
// Returns the imported world name
|
|
export async function import_world_save(
|
|
instanceId: string,
|
|
sourcePath: string,
|
|
innerBase?: string,
|
|
): Promise<string> {
|
|
return await invoke('plugin:instance|instance_import_world_save', {
|
|
instanceId,
|
|
sourcePath,
|
|
innerBase,
|
|
})
|
|
}
|
|
|
|
export async function install_datapack_to_world(
|
|
instanceId: string,
|
|
worldPath: string,
|
|
sourcePath: string,
|
|
innerBase?: string,
|
|
): Promise<string> {
|
|
return await invoke('plugin:instance|instance_install_datapack_to_world', {
|
|
instanceId,
|
|
worldPath,
|
|
sourcePath,
|
|
innerBase,
|
|
})
|
|
}
|
|
|
|
// Add a project to an instance from a path + project_type
|
|
// Returns a path to the new project file
|
|
export async function add_project_from_path(
|
|
instanceId: string,
|
|
projectPath: string,
|
|
projectType?: ContentFileProjectType,
|
|
innerBase?: string,
|
|
): Promise<string> {
|
|
return await invoke('plugin:instance|instance_add_project_from_path', {
|
|
instanceId,
|
|
projectPath,
|
|
projectType,
|
|
innerBase,
|
|
})
|
|
}
|
|
|
|
// Toggle disabling a project
|
|
export async function toggle_disable_project(
|
|
instanceId: string,
|
|
projectPath: string,
|
|
desiredEnabled?: boolean,
|
|
): Promise<string> {
|
|
return await invoke('plugin:instance|instance_toggle_disable_project', {
|
|
instanceId,
|
|
projectPath,
|
|
desiredEnabled,
|
|
})
|
|
}
|
|
|
|
export async function toggle_content_entry(
|
|
instanceId: string,
|
|
contentId: string,
|
|
desiredEnabled?: boolean,
|
|
): Promise<string> {
|
|
return await invoke('plugin:instance|instance_toggle_content_entry', {
|
|
instanceId,
|
|
contentId,
|
|
desiredEnabled,
|
|
})
|
|
}
|
|
|
|
export type ContentToggleResult = {
|
|
contentId: string
|
|
path: string
|
|
enabled: boolean
|
|
}
|
|
|
|
export async function toggle_content_entries(
|
|
instanceId: string,
|
|
contentIds: string[],
|
|
desiredEnabled?: boolean,
|
|
): Promise<ContentToggleResult[]> {
|
|
return await invoke('plugin:instance|instance_toggle_content_entries', {
|
|
instanceId,
|
|
contentIds,
|
|
desiredEnabled,
|
|
})
|
|
}
|
|
|
|
// Roll back an updated project to its previous file (kept as a .old backup)
|
|
export async function rollback_project(instanceId: string, projectPath: string): Promise<string> {
|
|
return await invoke('plugin:instance|instance_rollback_project', {
|
|
instanceId,
|
|
projectPath,
|
|
})
|
|
}
|
|
|
|
// Remove a project
|
|
export async function remove_project(instanceId: string, projectPath: string): Promise<void> {
|
|
return await invoke('plugin:instance|instance_remove_project', { instanceId, projectPath })
|
|
}
|
|
|
|
export async function remove_content_entry(instanceId: string, contentId: string): Promise<void> {
|
|
return await invoke('plugin:instance|instance_remove_content_entry', { instanceId, contentId })
|
|
}
|
|
|
|
export async function update_content_entry(instanceId: string, contentId: string): Promise<string> {
|
|
return await invoke('plugin:instance|instance_update_content_entry', { instanceId, contentId })
|
|
}
|
|
|
|
export async function switch_content_entry_version(
|
|
instanceId: string,
|
|
contentId: string,
|
|
versionId: string,
|
|
): Promise<string> {
|
|
return await invoke('plugin:instance|instance_switch_content_entry_version', {
|
|
instanceId,
|
|
contentId,
|
|
versionId,
|
|
})
|
|
}
|
|
|
|
export async function restore_pack_member_default(
|
|
instanceId: string,
|
|
memberId: string,
|
|
): Promise<string | null> {
|
|
return await invoke('plugin:instance|instance_restore_pack_member_default', {
|
|
instanceId,
|
|
memberId,
|
|
})
|
|
}
|
|
|
|
// Update a managed Modrinth instance to a specific version
|
|
export async function update_managed_modrinth_version(
|
|
instanceId: string,
|
|
versionId: string,
|
|
): Promise<InstallJobSnapshot> {
|
|
return await invoke('plugin:instance|instance_update_managed_modrinth_version', {
|
|
instanceId,
|
|
versionId,
|
|
})
|
|
}
|
|
|
|
// Repair a managed Modrinth instance
|
|
export async function update_repair_modrinth(instanceId: string): Promise<InstallJobSnapshot> {
|
|
return await invoke('plugin:instance|instance_repair_managed_modrinth', { instanceId })
|
|
}
|
|
|
|
// Export an instance to .mrpack
|
|
// included_overrides is an array of paths to override folders to include (ie: 'mods', 'resource_packs')
|
|
// Version id is optional (ie: 1.1.5)
|
|
export async function export_instance_mrpack(
|
|
instanceId: string,
|
|
exportLocation: string,
|
|
includedOverrides: string[],
|
|
versionId?: string,
|
|
description?: string,
|
|
name?: string,
|
|
): Promise<void> {
|
|
return await invoke('plugin:instance|instance_export_mrpack', {
|
|
instanceId,
|
|
exportLocation,
|
|
includedOverrides,
|
|
versionId,
|
|
description,
|
|
name,
|
|
})
|
|
}
|
|
|
|
// Given a folder path, populate an array of all the subfolders
|
|
// Intended to be used for finding potential override folders
|
|
// profile
|
|
// -- mods
|
|
// -- resourcepacks
|
|
// -- file1
|
|
// => [mods, resourcepacks]
|
|
// allows selection for 'included_overrides' in export_instance_mrpack
|
|
export async function get_pack_export_candidates(instanceId: string): Promise<string[]> {
|
|
return await invoke('plugin:instance|instance_get_pack_export_candidates', { instanceId })
|
|
}
|
|
|
|
export interface GcLaunchIntent {
|
|
active_preset_id: string
|
|
block_tokens: string[]
|
|
candidate_ids: string[]
|
|
candidates: string[][]
|
|
}
|
|
|
|
export interface GcLaunchReport {
|
|
preferred_strategy: string
|
|
chosen_strategy: string
|
|
chosen_args: string[]
|
|
pruned_args: string[]
|
|
reason_chain: string[]
|
|
}
|
|
|
|
export interface InstanceRunResult {
|
|
process: unknown
|
|
gc_notice: GcLaunchReport | null
|
|
}
|
|
|
|
export function gcReportFellBack(report: GcLaunchReport): boolean {
|
|
return report.chosen_strategy !== report.preferred_strategy || report.pruned_args.length > 0
|
|
}
|
|
|
|
/**
|
|
* The Java that will actually run the instance: a per-instance `java_path`
|
|
* wins, otherwise the optimal JRE for the game version.
|
|
*/
|
|
async function resolveEffectiveJava(
|
|
instance: Pick<GameInstance, 'java_path'>,
|
|
instanceId: string,
|
|
): Promise<JavaVersion | null> {
|
|
if (instance.java_path) {
|
|
try {
|
|
return await get_jre(instance.java_path)
|
|
} catch {
|
|
// Invalid override; fall back to the optimal JRE.
|
|
}
|
|
}
|
|
return await get_optimal_jre_key(instanceId)
|
|
}
|
|
|
|
function normalizeJvmToken(token: string): string {
|
|
const stripped = token.replace(/^-+/, '')
|
|
const withoutPrefix = stripped.startsWith('XX:') ? stripped.slice(3) : stripped
|
|
const withoutModifier =
|
|
withoutPrefix.startsWith('+') || withoutPrefix.startsWith('-')
|
|
? withoutPrefix.slice(1)
|
|
: withoutPrefix
|
|
return withoutModifier.split('=')[0] ?? withoutModifier
|
|
}
|
|
|
|
function strategyTokenKeys(strategy: ResolvedGcStrategyId, context: GcContext): Set<string> {
|
|
const tokens = GC_STRATEGY_DEFINITIONS[strategy].buildArgs(context).split(/\s+/).filter(Boolean)
|
|
return new Set(tokens.map(normalizeJvmToken))
|
|
}
|
|
|
|
/**
|
|
* Determine whether the effective Java args contain an auto GC marker or an
|
|
* active GC preset block, and if so build the ordered candidate chain for the
|
|
* backend to verify against the actual JVM. The args are returned untouched so
|
|
* the backend replaces exactly the preset block.
|
|
*/
|
|
export async function resolveGcLaunchIntent(
|
|
instanceId: string,
|
|
): Promise<{ args: string[]; gcIntent: GcLaunchIntent | null }> {
|
|
const instance = await get(instanceId)
|
|
if (!instance) return { args: [], gcIntent: null }
|
|
|
|
const settings = await getSettings()
|
|
const effectiveArgs = instance.extra_launch_args ?? settings.extra_launch_args
|
|
|
|
const java = await resolveEffectiveJava(instance, instanceId)
|
|
const javaMajorVersion = java?.parsed_version ?? null
|
|
let modCount = 0
|
|
try {
|
|
const snapshot = await get_content_snapshot(instanceId)
|
|
modCount = snapshot.items.filter(
|
|
(item) => item.projectType === 'mod' && item.materializationState === 'present',
|
|
).length
|
|
} catch {
|
|
modCount = 0
|
|
}
|
|
|
|
const memory = instance.memory ?? settings.memory
|
|
let allocatedMemoryMb = memory.maximum
|
|
if (memory.automatic) {
|
|
try {
|
|
const status = await get_memory_status(instanceId, memory.maximum, true)
|
|
allocatedMemoryMb = status.allocated_mb
|
|
} catch {
|
|
// Fall back to the configured value when automatic memory info is unavailable.
|
|
}
|
|
}
|
|
const context = await collectGcContext(
|
|
allocatedMemoryMb,
|
|
instance.loader,
|
|
javaMajorVersion,
|
|
modCount,
|
|
)
|
|
|
|
let activePresetId: string | null = null
|
|
let blockTokens: string[] = []
|
|
if (effectiveArgs.includes(AUTO_GC_PRESET_ARG)) {
|
|
activePresetId = 'gc-auto'
|
|
blockTokens = [AUTO_GC_PRESET_ARG]
|
|
} else {
|
|
const detected = detectGcStrategy(effectiveArgs.join(' '))
|
|
if (detected) {
|
|
activePresetId = `gc-${detected}`
|
|
const presetKeys = strategyTokenKeys(detected, context)
|
|
blockTokens = effectiveArgs.filter((arg) => presetKeys.has(normalizeJvmToken(arg)))
|
|
}
|
|
}
|
|
|
|
if (!activePresetId || !blockTokens.length) {
|
|
return { args: effectiveArgs, gcIntent: null }
|
|
}
|
|
|
|
const preferred: ResolvedGcStrategyId =
|
|
activePresetId === 'gc-auto'
|
|
? resolveAutoGcStrategy(context).resolvedStrategy
|
|
: (activePresetId.slice('gc-'.length) as ResolvedGcStrategyId)
|
|
|
|
const { ids, args: candidates } = buildGcCandidateChain(context, preferred)
|
|
|
|
return {
|
|
args: effectiveArgs,
|
|
gcIntent: {
|
|
active_preset_id: activePresetId,
|
|
block_tokens: blockTokens,
|
|
candidate_ids: ids,
|
|
candidates,
|
|
},
|
|
}
|
|
}
|
|
|
|
// Run Minecraft using an instance
|
|
// Returns the process metadata plus an optional GC fallback report.
|
|
export async function run(
|
|
instanceId: string,
|
|
serverAddress: string | null = null,
|
|
): Promise<InstanceRunResult> {
|
|
const { args, gcIntent } = await resolveGcLaunchIntent(instanceId)
|
|
const result = await invoke<InstanceRunResult>('plugin:instance|instance_run', {
|
|
instanceId,
|
|
serverAddress,
|
|
offlineMode: isOfflineMode(),
|
|
extraLaunchArgs: args,
|
|
gcIntent,
|
|
})
|
|
setLastGcLaunchReport(result.gc_notice)
|
|
return result
|
|
}
|
|
|
|
export async function kill(instanceId: string): Promise<void> {
|
|
return await invoke('plugin:instance|instance_kill', { instanceId })
|
|
}
|
|
|
|
// Edits an instance
|
|
export async function edit(instanceId: string, editInstance: Partial<GameInstance>): Promise<void> {
|
|
return await invoke('plugin:instance|instance_edit', { instanceId, editInstance })
|
|
}
|
|
|
|
export async function cache_icon(iconName: string, bytes: number[]): Promise<string> {
|
|
return await invoke('plugin:instance|instance_cache_icon', { iconName, bytes })
|
|
}
|
|
|
|
// Edits an instance's icon
|
|
export async function edit_icon(instanceId: string, iconPath: string | null): Promise<void> {
|
|
return await invoke('plugin:instance|instance_edit_icon', { instanceId, iconPath })
|
|
}
|
|
|
|
export type SymlinkCapability = 'supported' | 'requires_admin' | 'unsupported'
|
|
|
|
export async function check_symlink_capability(): Promise<SymlinkCapability> {
|
|
return await invoke('check_symlink_capability')
|
|
}
|
|
|
|
export async function allow_symlink_target(path: string): Promise<void> {
|
|
return await invoke('allow_symlink_target', { path })
|
|
}
|