From b97568b60bd92f31aea3721c840ac14abe5311c3 Mon Sep 17 00:00:00 2001 From: Felitendo Date: Fri, 18 Sep 2026 00:13:34 +0200 Subject: [PATCH] feat: offer every launcher when importing instances --- README.md | 7 + ...chers-instances-can-be-imported-from.patch | 881 ++++++++++++++++++ scripts/check.sh | 6 + 3 files changed, 894 insertions(+) create mode 100644 patches/0024-Offer-the-launchers-instances-can-be-imported-from.patch diff --git a/README.md b/README.md index f65f79f..70aba19 100644 --- a/README.md +++ b/README.md @@ -60,6 +60,7 @@ sudo dnf install "$(curl -fsSL https://api.github.com/repos/Felitendo/Modrinth-E | `0021-Show-and-change-skins-of-custom-...` | A custom server account's skin is shown and changed on the skin page, and its head in the account list. | | `0022-Sign-in-to-custom-servers-in-...` | Servers with Yggdrasil Connect, such as LittleSkin, sign in on their own page, with two-factor authentication. | | `0023-Browse-LittleSkin-s-skin-library` | LittleSkin's skin library in the Browse tab, with search, sorting and likes. | +| `0024-Offer-the-launchers-instances-...` | Every launcher the importers read is offered, with the folder it keeps instances in. | ### Offline accounts @@ -181,6 +182,12 @@ you open there is previewed and can be added. ### Instances and crashes +Importing instances offers every launcher the importers read — Prism Launcher, MultiMC, PolyMC, +ATLauncher, GDLauncher and CurseForge — whether they are installed or not, each with the folder it +keeps its instances in on this system, Flatpak and macOS bundles included, and each with its own +logo. Upstream listed only the launchers it found, and left the rest to a path typed in by hand. +MultiMC keeps a plain badge instead: its branding may not be passed on. + A running instance can be started again from the button next to Stop, as whichever account is selected, and the Logs tab then shows a console for each copy. diff --git a/patches/0024-Offer-the-launchers-instances-can-be-imported-from.patch b/patches/0024-Offer-the-launchers-instances-can-be-imported-from.patch new file mode 100644 index 0000000..1f20680 --- /dev/null +++ b/patches/0024-Offer-the-launchers-instances-can-be-imported-from.patch @@ -0,0 +1,881 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Modrinth Enhanced +Date: Fri, 18 Sep 2026 00:03:21 +0200 +Subject: [PATCH] Offer the launchers instances can be imported from + +Importing only listed the launchers that were found and had instances. +Anyone whose launcher sits somewhere else, or who has not installed it +where the launcher usually goes, was left with an empty list and a path +to type in by hand. + +Every launcher the importers read is offered now, installed or not: +Prism Launcher, MultiMC, PolyMC, ATLauncher, GDLauncher and CurseForge, +each with the folder it keeps its instances in on this system, Flatpak +and macOS bundles included. Picking one reads that folder; with nothing +there, its path lands in the field to be corrected. PolyMC and other +MultiMC forks are read by Prism's importer, so the name shown and the +importer used are no longer the same thing. + +The initials in a badge tell the entries apart without carrying anyone +else's logo around. +--- + apps/app-frontend/src/helpers/import.js | 6 + + .../src/providers/setup/creation-modal.ts | 7 +- + .../src/providers/setup/instance-import.ts | 26 +++- + apps/app/build.rs | 1 + + apps/app/src/api/import.rs | 8 + + packages/app-lib/src/api/pack/import/mod.rs | 146 ++++++++++++++---- + packages/ui/src/assets/launchers/README.md | 16 ++ + .../ui/src/assets/launchers/atlauncher.svg | 1 + + .../ui/src/assets/launchers/gdlauncher.png | Bin 0 -> 7218 bytes + packages/ui/src/assets/launchers/polymc.svg | 21 +++ + .../src/assets/launchers/prism-launcher.svg | 57 +++++++ + .../components/ImportInstanceStage.vue | 109 ++++++++++++- + .../components/LauncherMark.vue | 47 ++++++ + packages/ui/src/providers/instance-import.ts | 14 ++ + 14 files changed, 417 insertions(+), 42 deletions(-) + create mode 100644 packages/ui/src/assets/launchers/README.md + create mode 100644 packages/ui/src/assets/launchers/atlauncher.svg + create mode 100644 packages/ui/src/assets/launchers/gdlauncher.png + create mode 100644 packages/ui/src/assets/launchers/polymc.svg + create mode 100644 packages/ui/src/assets/launchers/prism-launcher.svg + create mode 100644 packages/ui/src/components/flows/creation-flow-modal/components/LauncherMark.vue + +diff --git a/apps/app-frontend/src/helpers/import.js b/apps/app-frontend/src/helpers/import.js +index 34cf250..bfc728e 100644 +--- a/apps/app-frontend/src/helpers/import.js ++++ b/apps/app-frontend/src/helpers/import.js +@@ -45,6 +45,12 @@ export async function is_valid_importable_instance(instanceFolder, launcherType) + }) + } + ++/// Gets every launcher instances can be imported from, with the folder each one ++/// uses on this system, whether it is installed or not ++export async function launcher_suggestions() { ++ return await invoke('plugin:import|launcher_suggestions') ++} ++ + /// Gets the default path for the given launcher type + /// null if it can't be found or doesn't exist + /// eg: get_default_launcher_path("MultiMC") +diff --git a/apps/app-frontend/src/providers/setup/creation-modal.ts b/apps/app-frontend/src/providers/setup/creation-modal.ts +index 964b17c..53ba797 100644 +--- a/apps/app-frontend/src/providers/setup/creation-modal.ts ++++ b/apps/app-frontend/src/providers/setup/creation-modal.ts +@@ -113,7 +113,12 @@ export function setupCreationModal( + if (!launcher || instanceSet.size === 0) continue + for (const name of instanceSet) { + importCount += 1 +- const job = await import_instance(launcher.name, launcher.path, name).catch(handleError) ++ // PolyMC and other forks are read by another launcher's importer. ++ const job = await import_instance( ++ launcher.launcher ?? launcher.name, ++ launcher.path, ++ name, ++ ).catch(handleError) + if (job) { + importedInstanceId = installJobInstanceId(job) + } +diff --git a/apps/app-frontend/src/providers/setup/instance-import.ts b/apps/app-frontend/src/providers/setup/instance-import.ts +index 851e44d..752832c 100644 +--- a/apps/app-frontend/src/providers/setup/instance-import.ts ++++ b/apps/app-frontend/src/providers/setup/instance-import.ts +@@ -2,9 +2,9 @@ import { type AbstractWebNotificationManager, provideInstanceImport } from '@mod + import { open } from '@tauri-apps/plugin-dialog' + + import { +- get_default_launcher_path, + get_importable_instances, + import_instance, ++ launcher_suggestions, + } from '@/helpers/import.js' + + export function setupInstanceImportProvider(notificationManager: AbstractWebNotificationManager) { +@@ -12,15 +12,18 @@ export function setupInstanceImportProvider(notificationManager: AbstractWebNoti + + provideInstanceImport({ + async getDetectedLaunchers() { +- const launcherNames = ['MultiMC', 'GDLauncher', 'ATLauncher', 'Curseforge', 'PrismLauncher'] + const launchers = [] +- for (const name of launcherNames) { ++ for (const suggestion of await launcher_suggestions()) { ++ if (!suggestion.installed || !suggestion.path) continue + try { +- const path = await get_default_launcher_path(name) +- if (!path) continue +- const instances = await get_importable_instances(name, path) ++ const instances = await get_importable_instances(suggestion.launcher, suggestion.path) + if (instances?.length > 0) { +- launchers.push({ name, path, instances }) ++ launchers.push({ ++ name: suggestion.name, ++ launcher: suggestion.launcher, ++ path: suggestion.path, ++ instances, ++ }) + } + } catch { + // Skip launchers that fail detection +@@ -28,6 +31,15 @@ export function setupInstanceImportProvider(notificationManager: AbstractWebNoti + } + return launchers + }, ++ async getLauncherSuggestions() { ++ const suggestions = await launcher_suggestions() ++ return suggestions.map((suggestion) => ({ ++ name: suggestion.name, ++ launcher: suggestion.launcher, ++ path: suggestion.path ?? '', ++ installed: suggestion.installed, ++ })) ++ }, + async getImportableInstances(launcherName: string, path: string) { + return (await get_importable_instances(launcherName, path)) ?? [] + }, +diff --git a/apps/app/build.rs b/apps/app/build.rs +index 8cb21f2..8e8a0f8 100644 +--- a/apps/app/build.rs ++++ b/apps/app/build.rs +@@ -108,6 +108,7 @@ fn main() { + "get_importable_instances", + "is_valid_importable_instance", + "get_default_launcher_path", ++ "launcher_suggestions", + ]) + .default_permission( + DefaultPermissionRule::AllowAllCommands, +diff --git a/apps/app/src/api/import.rs b/apps/app/src/api/import.rs +index 773fcb2..cda652f 100644 +--- a/apps/app/src/api/import.rs ++++ b/apps/app/src/api/import.rs +@@ -11,6 +11,7 @@ pub fn init() -> tauri::plugin::TauriPlugin { + get_importable_instances, + is_valid_importable_instance, + get_default_launcher_path, ++ launcher_suggestions, + ]) + .build() + } +@@ -39,6 +40,13 @@ pub async fn is_valid_importable_instance( + ) + } + ++/// Returns every launcher that can be imported from, installed or not, with ++/// the folder each one keeps its instances in on this system. ++#[tauri::command] ++pub async fn launcher_suggestions() -> Result> { ++ Ok(import::launcher_suggestions()) ++} ++ + /// Returns the default path for the given launcher type + /// None if it can't be found or doesn't exist + #[tauri::command] +diff --git a/packages/app-lib/src/api/pack/import/mod.rs b/packages/app-lib/src/api/pack/import/mod.rs +index 72f315d..15da6da 100644 +--- a/packages/app-lib/src/api/pack/import/mod.rs ++++ b/packages/app-lib/src/api/pack/import/mod.rs +@@ -247,36 +247,130 @@ async fn import_instance_inner( + pub fn get_default_launcher_path( + r#type: ImportLauncherType, + ) -> Option { +- let path = match r#type { +- ImportLauncherType::MultiMC => { +- return find_multimc_path(); +- } +- ImportLauncherType::PrismLauncher => { +- Some(dirs::data_dir()?.join("PrismLauncher")) +- } +- ImportLauncherType::ATLauncher => { +- Some(dirs::data_dir()?.join("ATLauncher")) +- } +- ImportLauncherType::GDLauncher => { +- Some(dirs::data_dir()?.join("gdlauncher_next")) +- } +- ImportLauncherType::Curseforge => { +- let home = dirs::home_dir()?; +- let primary = home.join("curseforge").join("minecraft"); +- if primary.exists() { +- return Some(primary); ++ let name = match r#type { ++ ImportLauncherType::MultiMC => "MultiMC", ++ ImportLauncherType::PrismLauncher => "Prism Launcher", ++ ImportLauncherType::ATLauncher => "ATLauncher", ++ ImportLauncherType::GDLauncher => "GDLauncher", ++ ImportLauncherType::Curseforge => "CurseForge", ++ ImportLauncherType::Unknown => return None, ++ }; ++ ++ installed_path(r#type, name) ++} ++ ++/// A launcher instances can be imported from, and where it keeps them. ++#[derive(Debug, Clone, Serialize)] ++pub struct LauncherSuggestion { ++ /// The importer that reads this launcher's instances. ++ pub launcher: String, ++ pub name: String, ++ /// Where it is, or where it would be on this system. ++ pub path: Option, ++ /// Whether it is actually there. ++ pub installed: bool, ++} ++ ++/// The launchers worth offering, with the importer each one is read by. ++/// ++/// PolyMC and other MultiMC forks keep their instances the way Prism does, so ++/// that importer reads them as well. ++const SUGGESTED_LAUNCHERS: &[(ImportLauncherType, &str)] = &[ ++ (ImportLauncherType::PrismLauncher, "Prism Launcher"), ++ (ImportLauncherType::MultiMC, "MultiMC"), ++ (ImportLauncherType::PrismLauncher, "PolyMC"), ++ (ImportLauncherType::ATLauncher, "ATLauncher"), ++ (ImportLauncherType::GDLauncher, "GDLauncher"), ++ (ImportLauncherType::Curseforge, "CurseForge"), ++]; ++ ++/// Every launcher that can be imported from, whether it is installed or not: ++/// one that is not still gets the folder it would be in, to start looking from. ++pub fn launcher_suggestions() -> Vec { ++ SUGGESTED_LAUNCHERS ++ .iter() ++ .map(|&(launcher, name)| { ++ let installed = installed_path(launcher, name); ++ LauncherSuggestion { ++ launcher: launcher.to_string(), ++ name: name.to_owned(), ++ path: installed ++ .clone() ++ .or_else(|| launcher_paths(name).into_iter().next()), ++ installed: installed.is_some(), + } +- Some(dirs::document_dir()?.join("curseforge").join("minecraft")) +- } +- ImportLauncherType::Unknown => None, ++ }) ++ .collect() ++} ++ ++/// Where a launcher of this name actually is, if it is installed. ++fn installed_path(r#type: ImportLauncherType, name: &str) -> Option { ++ let paths = launcher_paths(name); ++ ++ // MultiMC is unpacked wherever the player put it, so its own config file is ++ // what tells one of those places from any other folder of that name. ++ if r#type == ImportLauncherType::MultiMC { ++ return paths ++ .into_iter() ++ .find(|path| path.join("multimc.cfg").exists()); ++ } ++ ++ paths.into_iter().find(|path| path.exists()) ++} ++ ++/// Everywhere a launcher of this name keeps its data on this system, likeliest ++/// first. ++fn launcher_paths(name: &str) -> Vec { ++ let data = dirs::data_dir(); ++ let home = dirs::home_dir(); ++ // Flatpak gives each app a data directory of its own. ++ let flatpak = |app: &str, folder: &str| { ++ home.as_ref().map(|home| { ++ home.join(".var") ++ .join("app") ++ .join(app) ++ .join("data") ++ .join(folder) ++ }) + }; +- let path = path?; +- if path.exists() { Some(path) } else { None } ++ ++ match name { ++ "Prism Launcher" => [ ++ data.map(|data| data.join("PrismLauncher")), ++ flatpak("org.prismlauncher.PrismLauncher", "PrismLauncher"), ++ ] ++ .into_iter() ++ .flatten() ++ .collect(), ++ "PolyMC" => [ ++ data.map(|data| data.join("PolyMC")), ++ flatpak("org.polymc.PolyMC", "PolyMC"), ++ ] ++ .into_iter() ++ .flatten() ++ .collect(), ++ "MultiMC" => multimc_paths(), ++ "ATLauncher" => data ++ .map(|data| vec![data.join("ATLauncher")]) ++ .unwrap_or_default(), ++ "GDLauncher" => data ++ .map(|data| vec![data.join("gdlauncher_next")]) ++ .unwrap_or_default(), ++ "CurseForge" => [ ++ home.map(|home| home.join("curseforge").join("minecraft")), ++ dirs::document_dir() ++ .map(|docs| docs.join("curseforge").join("minecraft")), ++ ] ++ .into_iter() ++ .flatten() ++ .collect(), ++ _ => Vec::new(), ++ } + } + +-/// Searches common locations for a MultiMC installation. ++/// The common locations for a MultiMC installation. + /// MultiMC stores data in its own application directory (not a standard data dir) +-fn find_multimc_path() -> Option { ++fn multimc_paths() -> Vec { + let mut candidates: Vec = Vec::new(); + + // Linux/macOS: ~/.local/share/multimc is the typical location +@@ -318,8 +412,6 @@ fn find_multimc_path() -> Option { + } + + candidates +- .into_iter() +- .find(|p| p.join("multimc.cfg").exists()) + } + + /// Checks if this PathBuf is a valid instance for the given launcher type +diff --git a/packages/ui/src/assets/launchers/README.md b/packages/ui/src/assets/launchers/README.md +new file mode 100644 +index 0000000..33a2c0a +--- /dev/null ++++ b/packages/ui/src/assets/launchers/README.md +@@ -0,0 +1,16 @@ ++# Launcher logos ++ ++The logos of the launchers instances can be imported from, shown beside their name in the import ++dialog. ++ ++| File | From | Licence | ++| -------------------- | --------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------- | ++| `prism-launcher.svg` | [PrismLauncher/PrismLauncher](https://github.com/PrismLauncher/PrismLauncher), `program_info` | [CC BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/), unchanged | ++| `polymc.svg` | [PolyMC/PolyMC](https://github.com/PolyMC/PolyMC), `program_info` | [CC BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/), unchanged | ++| `atlauncher.svg` | [ATLauncher/ATLauncher](https://github.com/ATLauncher/ATLauncher), `packaging/linux` | GPL-3.0, unchanged | ++| `gdlauncher.png` | [gorilla-devs/GDLauncher](https://github.com/gorilla-devs/GDLauncher), `public/icon.png` | GPL-3.0, scaled down to 128×128 | ++ ++CurseForge's logo is the one Modrinth already ships in `@modrinth/assets`. ++ ++MultiMC has none here: its name and logo may not be redistributed, so it keeps a plain badge with ++its initials. +diff --git a/packages/ui/src/assets/launchers/atlauncher.svg b/packages/ui/src/assets/launchers/atlauncher.svg +new file mode 100644 +index 0000000..5dd0000 +--- /dev/null ++++ b/packages/ui/src/assets/launchers/atlauncher.svg +@@ -0,0 +1 @@ ++ +\ No newline at end of file +diff --git a/packages/ui/src/assets/launchers/gdlauncher.png b/packages/ui/src/assets/launchers/gdlauncher.png +new file mode 100644 +index 0000000000000000000000000000000000000000..9d369eb6d30a38821f97569a41fbb0f2346f7fc6 +GIT binary patch +literal 7218 +zcmeAS@N?(olHy`uVBq!ia0y~yU}ykg4mJh`hQoG=rx_Tee|ow&hE&{o8(UoydUfi* +zYxjQNanGCA(tbeW#|sU9S5EzjUAHpby#k%~{##c1;ihEv>e4LTg!DtI23uRMrMtfh +z$qudg%ONx+uwaW1*@r???S+csWtdwyed8Y2gG9f0y&pB0PH2KJsteuW74mn_y!8=f+Coww}3G +z$tOPkyB^&3#`2<_%Bj=g3s+_BkSu;Gdy`pz$DjL*A4NLyWG%i3y+7cnY`*W`Cw*mx +zU=5bQxVUq+N+RDFE_WvsNKBm;x0*5G%$CT@oFbD>S>5h5V`P|eocrw!Ysr{Bc`yIC +ze#{7n+BLN?X>wVE$lqLH^|qAC%8ifPe$N!{IARm@%4o5y#A6GVNel-PbX$U)v^G3m +zlVs+aJ$-5j!+|56ilK3FYZwb|F$SrfXHU({T*=nZz5H*|gv4yKjyy4^LmOwCFtRLK +ze9?Y$V#Cy@Q;%gd{N!}}cE{BF{q5Q}mY4Y^oH=d9prL2>xXJ$e=C`|rnT(fg`ZOnF +z#>|rfb7j68I16tKkh9M`zOL~g(K(JY96c0UMz#q0Nog +zwmCPjtmxf8c_yRo(xn|NH!QSoEXa7f>8JE=Zo38VCOqfn`oDI6)BkXvKgpW9PhDo4 +zH2ya`zuoe{hC~7F{_w0^>my%QG=JDDeOm61($$ZQOOxjDGivDRnX$6|&su3*b@y0H +zyynxF&s~%go`tplzP5x_Y0|03&5vDHcNVVy{PLiE&Yd6b)*@V;k3^2JPg(V_&gbEo +zlP*c?rd;z_yV$_Znk8s%lf{8PfgsP9jn}^IK9lxs*QQC~L58{kt1fIWY%u@z+qlhB +ztXT1hiNS)oE7Y1Ys^{6aRva)0s#M`%@Qpc`4jhbdXWhM@CPd~;nZTt7~%V#~yJ?X!# +z--m{V&P*`SSl#64B=m}V%9F}}Tun37r=8k#Z-`Bs@z$`OI?a>F4h~%xV-S +zJ1vXd$`yK@&;0TFU#VgL6Q|!}_Vnk>>@_MAmR>xwBjuFDe~&l;w`}HQ1>N_oKF^jl +zI;&N%)joby9#gb;FFlEo+Kpc5~ig`(@V2+@z+sWYeAt +z+x9jeDAqdt%zie{7NgSAdGe)Sdp-trRXRn5Zzx<)dg=Mk;6v;|TCPq9Gk*K7^}X0O +zU2M%euNsF~*ZY0DeyzLQ`b9+WgR-K?+<;}S+l!miHf=u4W0O}>a<6UryZ>vnGg4pW +z%q)9!M7rkTpUdwQI%Wn%{`50BbS7t)_l|tkwK`LdyWiT#Z{XAU|G@P9`ug=+mu7A< +zE=`*!A8WBWK;Z1uQ`RxJeGTqZe#yOL#dJeZWvaHdd9LA^&DW<-ol+`h_rid^ +z$^Ns}<~g+&jJN +zV(aF0A_r_TE8Q~H6)!Kd&cEfO?X5bM{Z?O|fy2dLn|^BFF23pU*7bhh&R62jo{>jg +zK1nUmPQ8^U!T)HJF+Coem0yF +z8b$%>X{^)C3-*@DyuA@9J2~_6G56^w&oy-{Np{~_CX-vqd|i4PE5pX@&FkeCP5pSN +zaFO8KIO&%$^G)g=+&a|hX2P*HiN{r2nQy6MT+sD}9%;_9&lp}_{M+U=^%<)YQ%6jO +zGpELzSD7-CgRQ=2b_d3kw7hyG@v7)GL+~PFm6fs%9E?m4GI}$vv@PcCF?^>Ztk9vb +zv}LKvlt}_jN#_zS-23<7*L()S1fIi%COPiT*}R8qp3G}$n8YOK?dZwLcBk6U+Tk_N +z`C|+ADe@%q6m4d&L<=ExFb!*5)6X9xr?7 +zUM+j!igk=m94Wu{Dkx66Bsl3D;}Ve-_A+h9gLc&1DX4iqTe;xjh0O*F^5vG;*rlvo +zn}1|^_5MT8-tOM`?Qj2vm9K&f$knuC=R +z){mqu{_k-FdEiM(s?7J@QPOYjhBy0$%0I6E|I02c|4!eEyDe*c?6;U@vRedpuyV9# +zNgE`75%^Q`OQlD`=*Nm=AD!)T2iaH@IV>Y3E&kte1mPbKuTEc7Rj+$5Znx;^`*#|A +zLw6rIbAsmwW14x){4*a6*A(thU{pl6m7SMmo^^0u)h4H +zs{Yr^xo0mQyw6nXeg1Ejex0bsuCKQz%$+*%fNmzc1=}mxyq*W&r1y2t|MPjOyYD&$ +zbDrb9#e1q|SqP{oeR(~%`N5Nq9DZ{BUA(tHe7O`F@%J73h9xcc-&;Nm)>!IbA=A3n +z^jN=*_;vr?Ji>0gmbY$f%-*s6k=28b9HD)d(v1d5CldaAymx!UhnZ6&ew^`2N&7Kr +znWcxS_qp|}^N*~#&1Jav!={LP&mtt2g(-eI@EABjRg%xWS|)$U|KCrq6xNy< +z|J#jH(omc8!4+w1k>l6Cnyt9JNRD80F;6>$1pR$A4=)EisV +z<>Mma8t=Sun4ZjZ@#q;*C0{c(-WScg^LBEzNd|FjN$2bo$n#!k#wJ;(pLQpzgS{)S +z>Hc%aC7N9x!6J3@+4)88wn%OlQ#$;Cwy73nOV-LlI8HLw%!xJww*maoPjJ@|! +zZI{^>;X@DAeCC%)8X4Cl9F{dx4B{{_-eUIS+7Ipz&sn!Gj(;EX<@MZdd;J}<_oeC? +zx0pScAzU`)@aDvnv>yhww^BLUXD4MYWnadi=1F#bC9qAp9Zg~RW929 +zzxUu@;VGs4-3-SJjQ+%ZIeR|Sk9FIv&Mk$l@>$<@E^e#V?TdYV;(@dPBai;J2)Nx4cayQlE?;0-Y?b1tO-4!Iuk0$0_Hy#PxOB~iS6%`ucn@D( +z`#QDb^XuF8YmcpQKm8%|t7C;NR}e={RIR}4?X^we+S?!4ubbauw=;726PAzf>q=M1 +z{r~xeYtQCN^(iI2-)`<}jtJk%efs`S`!oLw70=o3mG{wRT)NnF%R@cZ#zm_c7AQ1& +z1dH@OR9hf=sUutD!Aa5V4;SavzL-D1*=Ik0oVVhG?+W_b%%Zc)AN8?|KTe2so_*Qz +zk2U*iseQpO@onWzv^duAAgTM$8wR^O!M(r5 +z|Em_(%${H;_%8mBc+HOGQa^7pnuj+(kZ8WuX0R&zRl&7}do#*jwst;_o^zPt&F%Q^ +zV+A6cxNjajasro2ps +zU7JNtkwpT;aqw*Xb&R8FdjFCAJ~JEQHX0dR?s3iO{TSOhGa&eOL;3a^p8e-PZw>Xj +zE5vMIY%}f9^!Td}zV6TSYPlEIZSABo$5FaDE>d#F&%^H%zV%L4?AWmTm%=M)CC)`F +zTor^JYMohjujY3WNc`42+3<7weaH1{dRTrc3(2r-oyEZ1a9!K%;pz2R4_>bHNvJWZ +zk>ij1F~3~M#V`7~(mS3rJjW$`q^x;9=j*n(d%EQ8zWTiThMA35(!aH>Gfc`PWQtw1 +z@_ug;;#7Ls`oEIhu7A0A|)97$tUt{wNt2vy;t!#;3Ev(^5M*x;Ty*=*brH +zx&8b5lH-Ss?z4Z7kAFFN^2q0f(Gw&WXN6i{;gERzZ<~00WOJ^yopYRD+WB=HUG9x% +zc;v*bj!j7$DN9?CX8O;9wx3`FHojmOU)3poC`VRtPnRTvi-D$samh)D_ +z6X(7xPC9Hh#U_eP(Pa(G?%I~+>we^Y-t+zw7n5rLw1r8B%~H~TS%+-(Ir%QZUHZw> +zlt(X@<|jT}Gf5AmBo4sFjpFB-DhU>gb@cCZe>LAk0=qBg(YWS=;D&S!hu)|5w| +zHZfOxnK^aEy?Tz#?{{3gdgk`RjLC&b%-s<)&#esmeMC0WtI=a}@6P@4J)5RZ)qCWB +z=-GuOcNCnYoA=!DnX-4AnEAcG2HW2Mu}S>1%`a)rgUWxsUsv4OrTy{B@yH#g*6#lB +zNBVWNkHp*;`xv*sDEWQveBtA}{EOdg-Fo7*uisLX`@@Av+XJo(WST0b&YI}5Z81p4 +zgSOrB$F|L_zM!o>p(N2hbz4W_7RR>x>}ki|%T;q;eqSZ9T5J23UD?NX%+6Vq^qH?^ +zvDvqG>wCZc=HyVkQ}*ufRu1>vkeqvkSIVO<*KS!=#J%kPSB1LoH@p+lQhwBWvi=Av +z>Xs1q5&TqSv@!Si^Bu|O9`C4lvUA6~SKfu52P-FhdcE@IkN@HM|0KLPWUTIV>?&ZX +zoVjj8uz25t3lH5i6gO0zKK|p~j)%HYOvkKy@7>%Xdw$;+vuk^PGqwnvIDN9kP;F&7 +zo99Z~rWtpqOn6wXdH0mB{Gyt-VKHC%7k|86ooBJKOXB*=#oLEFn#~?t#QTA-mf33bW_-BI=@WSQeIldX?)ByChR~z +z&wTOScDKKOZSmcFY2B<}ZW9zze%PG&QZ3w`@@wyj50wswbWM7vPnyfaab*3@*PClk +ze5jl#`a%7j{!}-wJmK`7#>BSk$y;~))4P-6EX{oTqD6Xwe1-1R2-XVS)?&7Ah32(J +zY!6r5ePrdj{&>;(WFIZR)U5ZyHzs-ZA8+H?FYe`K5%swG@%j91n>(_fz3;V`@4FTd +zr1aqPwjJS$p}g~hBpy@*#tK|WvF5z=)a9quz9p|G&+h#5hM{e~CByVN^<_!=O&0P6 +zCx50t{?vW$s5pDpMQd~8jU_gb*)y)O`EFx&bXa9{P4o9DGfh=KdA`MpcUs=~*!$g^ +z`EXSgci!x_J3jSE%`2bH_GNVVc=o>Y+lK|lq7(hxbQUPyX*t|}ctX%JWry@`ogl6& +zS7eXvV{V@?_oe;HzmNSt-T!*hcAM?{75k0<-ea+_k=)P|pMAg4dBsZWH*O0=co~&S +zl^YzKlZCgvy>a=`+N8(3JLhhR*)96~=C)*hDXH03x%rJ-m}Mq+bT*Wm<@o2^y!Pqt +z5x;G+Ij7GH?>7qy-{=?1Zt&*Pk?ecF8GdKKin=~k53@p?+cePwo339^I@x8acl-H^vi*IJcj!EvB$~b9&DX6b&T=i8^5XK^c**1& +z3fd2jua}Q(+OD8?rTpw2m$!L&-Kl*) +zx#H{E-hiMcj^!a;Pv6~T*3kU;Dq6bm`(FQJ=e#{Wp1A)vPU`5HfFK91#+jcmEv)FJ{iDsboARKBwK7?Tg*ZvESbwzp^HKIkuH{eSC^DL=2|tyy8KwXwSV(qx;rA3nX1`}*t0j)k0GwwIrJ +zchBLhtn%MqKOW1-^RT&s$36{%)9>lDXb7E>dUL}!A-Uw&-9vLHJa3(F>eC^MZ|cWp +zRKA`z)rOJbU_!vOY13w1dog*fSy*VOD0{PGWaw7W`T769RX3lX^8F +z9}o6;2A$X$6t#Fu-yw;)LO&ELKko?%s#>shucNeW_2Esd+6)Pgj!e+f*T1$rs_&BC +z&#Le5ikbsFSbl#K&DW`YbGZ1|DSP+5%-;ncDy9A`>M1)ByEy1q=~vyj^OHB&)E#uI +zPwd%wyuRRTZgyPtyTg$2!CQYrHsIogFXt?W$2Y@Jij24{pm-}#};PI +z;*&1_n7#G1{5rd`yxYa8R}_`S7RfN|`0?9V-^k~4OTgrryj!-(GNnk#%WUnPGVM~3 +z3)3-+^&F2Dnd(p5Q~oBV?8v1Z%JmYg%}bT|g51Q<>4>-#Fa5-Ky-c|0qHW#H6KA40 +zG?zYYEt|DX;@QpDr@c<~G3;5u*kL_M`sEKnuAPeeciixqQ_-wHr~Z~Ki(#;q_We$` +z88dzTi(4w@h0Doj9=AJSp!$zF{@=bme^z>Y{`rchXw7A}&oXAOUhVc1zZC3ppuZ(x +ziq@8*>Py>?S)4iW*v#qVdR1Ww|P5nH7>MhBa78fmD7R6Qm5jrEY*dciOzSOj*AH9_V-&BNn; +z9P?CXKM&EHzkjjqi`#iG8IB)jPv%OGa}@a8vgw#~?((KLcYKy@O^~TJJuI?TTf1_z +z_U9TcONXSJ5^Gj1l8{kc=9n;4e7Q3J8TYAKj1SVCBJI?r3^)44PGm94?b)|gnL*Ah +zW`DxpP40X$(e{lhx|2MAU9?H>`}eOWanjKk4oluUQ#TbG&z!~}u{UJ$%)S4PvPMtV +zJ->Vp^Be|wA2p%krCZ$0CeIChp5uGK&v@py*fitn*vOOGCucq1-Oa4>!I8tEuyCWM +z#*c9CxffrXsr~WN^tV@+>I`19iTP#pN*g;1zb&&QzFWUvxd||MfF|91n^*)oGT=v|WDfnH-5Dryfs@y3J>3`%0na{jp8#`(JSV +zGJDIQ?!leB{G66TW2MTKCEq5_?Vpx;6FyH7YKo!vrjtPBDBs2gqP+0 +z-E?tkrmN4g-Y~=27j|u8^`6wY&)3?4JLP%d!TG;$e!I)rC>a!4>1T50Ov=7>vroRQ +zNLpx9cd0LyxtTS^Rc@6|t-_s|M!B~bZ*j`U*t`{Q=84|ySh88*(~-MA?kBfMCDb`h +zuq{7(>37-ks(J4V`3h=k42qd1&T#jNE%PxKo>hJmF-24|-R6FL#! +z^!)a|zyA(6irZ!`DPWLdRyw}HSiYCnd`)!S|EdN}k*0_pAIg}g7++ev`(g7@&gJsf +zrN<_03{K~PKK_5cChAIJI^!Y+1_lOC +LS3j3^P6 ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ +diff --git a/packages/ui/src/assets/launchers/prism-launcher.svg b/packages/ui/src/assets/launchers/prism-launcher.svg +new file mode 100644 +index 0000000..aeee843 +--- /dev/null ++++ b/packages/ui/src/assets/launchers/prism-launcher.svg +@@ -0,0 +1,57 @@ ++ ++ ++ ++ Prism Launcher Logo ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ Prism Launcher Logo ++ 19/10/2022 ++ ++ ++ Prism Launcher ++ ++ ++ ++ ++ AutiOne, Boba, ely, Fulmine, gon sawa, Pankakes, tobimori, Zeke ++ ++ ++ https://github.com/PrismLauncher/PrismLauncher ++ ++ ++ Prism Launcher ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ +diff --git a/packages/ui/src/components/flows/creation-flow-modal/components/ImportInstanceStage.vue b/packages/ui/src/components/flows/creation-flow-modal/components/ImportInstanceStage.vue +index a4dfcdb..5384d29 100644 +--- a/packages/ui/src/components/flows/creation-flow-modal/components/ImportInstanceStage.vue ++++ b/packages/ui/src/components/flows/creation-flow-modal/components/ImportInstanceStage.vue +@@ -51,6 +51,7 @@ + @update:model-value="toggleLauncherAll(launcher, $event)" + @click.stop + /> ++ + {{ launcher.name }} + + +@@ -75,6 +76,30 @@ + + + ++ ++
++ ++ {{ formatMessage(messages.otherLaunchers) }} ++ ++ ++
++ + +
+