版本隔离与实例档案
同一台机器上装两个 1.20.1,一个装光影一个不装,模组不能串。这页讲我们怎么把「实例」落到磁盘上:哪些东西共享、哪些独立、切换时怎么搬。
version-settings.json
每个版本的独有设置存在游戏目录里,不在启动器数据目录:<dotMc>/starcloud/version-settings.json。结构是「version + profiles 字典」,键是版本 id:
{
"version": 1,
"profiles": {
"1.20.1": {
"isolation": "isolated",
"memory_mode": "manual",
"memory_max_mb": 8192,
"window_mode": "auto",
"extra_jvm_args": ["-XX:+UseZGC"]
}
}
}写入是原子的(先写 .tmp 再 File.Move 覆盖)。读到损坏的 JSON 时静默清空,不阻塞启动——档案里存的是内存数值、窗口尺寸这类可以重设的东西,为它拒绝启动不值得。
catch
{
// 文件损坏 → 视为空档案(下次 Save 覆盖),不影响启动
_profiles = [];
}隔离三态
isolation 字段是三态的:shared / isolated / null(缺省)。null 表示跟随全局默认——这么设计是为了存量数据零迁移,老档案里根本没有这个字段,读出来是 null,行为等同于「跟随全局」。
public static bool IsIsolated(VersionProfile profile, LauncherSettings settings) =>
profile.Isolation switch
{
"shared" => false,
"isolated" => true,
_ => settings.VersionIsolated,
};GameRoot 由隔离状态决定:隔离时游戏目录是 versions/<id>/,共享时是 .minecraft 根目录。共享模式下所有实例的 mods/、saves/ 是同一份。
只隔离 5 类内容目录
隔离不是整个目录复制一份,只有这 5 个目录随实例走:
public static readonly string[] ContentDirs = ["mods", "saves", "config", "resourcepacks", "shaderpacks"];libraries 和 assets 永远共享。理由很实际:libraries 是按内容哈希去重的依赖树,assets 是几千个贴图音效文件,两份实例各存一份就是几十 GB 的重复。共享的代价是「改了某个库会影响所有实例」,这种情况极少,重装实例就能解决。
档位推断
内存、Java、窗口这三项各有一个「档位」,取值 global / manual / auto:
private static string Mode(string? explicitMode, bool hasLegacyValue) =>
explicitMode ?? (hasLegacyValue ? "manual" : "global");推断规则:显式档位优先 → 没有显式档位但有旧值按 manual → 都没有按 global。hasLegacyValue 是「这个版本原来存过具体值」的信号:memory_max_mb 有值 → manual;没有 → global。
价值是升级不改变行为。老版本升上来时没有 memory_mode 字段,但有 memory_max_mb,推断成 manual,用户看到的内存数值和升级前完全一样。要是推断成 global,老用户会发现自己的内存设置被悄悄重置了。
- global —— 用全局设置的值
- manual —— 用版本档案里的具体值
- auto —— 由启动器算。内存 auto 按机器内存和模组/光影数量给建议值(最小 512),窗口 auto 是 854×480 窗口化。
Java 的 auto 不是「随便找个 Java」,而是置空 java 路径,让启动流程回退到版本 JSON 的 javaVersion.majorVersion。
切换隔离时搬内容
切换共享/隔离时,已有的 mods/saves 要搬到新位置。规则是源存在且非空才搬;目标已存在时不覆盖,改名成 mods (2)(NextAvailablePath 加 " (n)",扩展名保留:file.txt → file (2).txt)。两边都保留,不覆盖用户数据。
搬一半失败要回滚
MoveContentDirsAsync 逐个目录 Directory.Move,可能搬到第三个时失败(前两个已经搬走了)。不回滚的话用户会遇到「mods 在新位置,saves 还在老位置」的分裂状态。所以我们记录已移动的项,出错时原样搬回去:
var moved = new List<(string Dst, string Src)>();
try
{
foreach (var dir in ContentDirs) { ...; moved.Add((dst, src)); }
}
catch
{
// 回滚:已移动项原样移回,保持源目录完整
foreach (var (dst, src) in moved)
{
try { Directory.Move(dst, src); } catch { /* 回滚失败时保留现状,由重试兜底 */ }
}
throw;
}回滚失败不再抛——原来的异常要继续往上抛,让用户知道操作失败了。回滚失败时保留现状,由用户重试兜底。
改名和复制
RenameInstance 和 CopyInstance 都要同步三处:目录名、内部 json/jar 文件名、json 里的 id 字段。改 id 走 PatchJsonId,它顺手把原版版本号记到旁边的 starcloudBaseVersion 字段(先取已有的,再退回 inheritsFrom,最后取 id)。
改名前先记下原版版本号。改名会把 id 换成用户起的名字(「整合包A」),原版号就丢了。整合包实例没有 inheritsFrom 字段,不记这一份,之后搜资源按版本筛、推荐整合包、判断加载器全都无从谈起。InstanceSettingsStore 的 RenameKey / Remove 在版本改名或删除时同步档案键。
相关链接:安装原版、安装 Fabric 与 Quilt、下载引擎总览