`.
+function promptHostedGameDir() {
+ hostedGameDirModal.value?.show()
+}
+
+async function installHostedWithGameDir(gameDirRoot: string) {
+ await install(gameDirRoot)
+}
async function openCompleted(instanceId: string) {
try {
const failure = await router.push(`/instance/${encodeURIComponent(instanceId)}/`)
@@ -103,7 +116,12 @@ async function handleStartFresh() {
await openCompleted(createdInstance.value)
return
}
- await install()
+ if (createdInstance.value) {
+ // A previous attempt already created the instance; retry in place.
+ await install()
+ return
+ }
+ promptHostedGameDir()
return
}
showModal?.({
@@ -179,6 +197,10 @@ function handleImportExisting() {
/>
+
{{ installError }}
diff --git a/apps/app/src/api/install.rs b/apps/app/src/api/install.rs
index 56a97fb..919a30d 100644
--- a/apps/app/src/api/install.rs
+++ b/apps/app/src/api/install.rs
@@ -66,8 +66,10 @@ pub async fn hosted_default() -> Result {
}
#[tauri::command]
-pub async fn hosted_create() -> Result {
- Ok(theseus::pack::hosted::create().await?)
+pub async fn hosted_create(
+ game_dir_root: Option,
+) -> Result {
+ Ok(theseus::pack::hosted::create(game_dir_root).await?)
}
#[tauri::command]
diff --git a/apps/app/src/main.rs b/apps/app/src/main.rs
index 6c0254a..9d11770 100644
--- a/apps/app/src/main.rs
+++ b/apps/app/src/main.rs
@@ -232,6 +232,21 @@ async fn initialize_state(app: tauri::AppHandle) -> api::Result<()> {
Ok(())
}
+/// Directory that contains the launcher executable. Used as the default
+/// external game-directory root for the one-click StarLight install flow.
+#[tauri::command]
+fn get_launcher_root_dir() -> api::Result {
+ let exe = std::env::current_exe().map_err(|error| {
+ theseus::Error::from(theseus::ErrorKind::FSError(error.to_string()))
+ })?;
+ let dir = exe.parent().ok_or_else(|| {
+ theseus::Error::from(theseus::ErrorKind::FSError(
+ "Launcher executable has no parent directory".to_string(),
+ ))
+ })?;
+ Ok(dir.to_string_lossy().into_owned())
+}
+
#[tauri::command]
fn get_update_channel(app: tauri::AppHandle) -> api::Result {
let channel = read_update_channel_state(&app)?
@@ -838,6 +853,7 @@ fn main() {
.manage(PendingUpdateData::default())
.invoke_handler(tauri::generate_handler![
initialize_state,
+ get_launcher_root_dir,
get_update_channel,
get_current_app_database_path,
set_update_channel,
diff --git a/packages/app-lib/src/api/pack/hosted.rs b/packages/app-lib/src/api/pack/hosted.rs
index c03d019..ad1cb13 100644
--- a/packages/app-lib/src/api/pack/hosted.rs
+++ b/packages/app-lib/src/api/pack/hosted.rs
@@ -426,10 +426,26 @@ pub async fn default_publication() -> crate::Result {
})
}
-pub async fn create() -> crate::Result {
+pub async fn create(
+ game_dir_root: Option,
+) -> crate::Result {
let publication = default_publication().await?;
let runtime = &publication.manifest.runtime;
let state = State::get().await?;
+ // The pack's game files live in their own folder under the chosen root,
+ // e.g. `/`. Avoid a `versions/` layout: that shape
+ // is reserved for externally linked launcher instances and would make the
+ // launcher expect a Minecraft version JSON beside the pack.
+ let game_dir_override = game_dir_root
+ .as_deref()
+ .map(str::trim)
+ .filter(|root| !root.is_empty())
+ .map(|root| {
+ Path::new(root)
+ .join(&publication.manifest.name)
+ .to_string_lossy()
+ .into_owned()
+ });
let instance = crate::state::create_instance(
crate::state::CreateInstance {
name: publication.manifest.name.clone(),
@@ -440,7 +456,7 @@ pub async fn create() -> crate::Result {
icon_path: None,
link: crate::state::InstanceLink::Unmanaged,
symlink_target: None,
- game_dir_override: None,
+ game_dir_override,
},
&state,
)