Skip to content

版本隔离与实例档案 ​

同一台机器上装两个 1.20.1,一个装光影一个不装,模组不能串。这页讲我们怎么把「实例」落到磁盘上:哪些东西共享、哪些独立、切换时怎么搬。

version-settings.json ​

每个版本的独有设置存在游戏目录里,不在启动器数据目录:<dotMc>/starcloud/version-settings.json。结构是「version + profiles 字典」,键是版本 id:

json
{
  "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 时静默清空,不阻塞启动——档案里存的是内存数值、窗口尺寸这类可以重设的东西,为它拒绝启动不值得。

csharp
catch
{
    // 文件损坏 → 视为空档案(下次 Save 覆盖),不影响启动
    _profiles = [];
}

隔离三态 ​

isolation 字段是三态的:shared / isolated / null(缺省)。null 表示跟随全局默认——这么设计是为了存量数据零迁移,老档案里根本没有这个字段,读出来是 null,行为等同于「跟随全局」。

csharp
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 个目录随实例走:

csharp
public static readonly string[] ContentDirs = ["mods", "saves", "config", "resourcepacks", "shaderpacks"];

libraries 和 assets 永远共享。理由很实际:libraries 是按内容哈希去重的依赖树,assets 是几千个贴图音效文件,两份实例各存一份就是几十 GB 的重复。共享的代价是「改了某个库会影响所有实例」,这种情况极少,重装实例就能解决。

档位推断 ​

内存、Java、窗口这三项各有一个「档位」,取值 global / manual / auto:

csharp
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 还在老位置」的分裂状态。所以我们记录已移动的项,出错时原样搬回去:

csharp
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、下载引擎总览

基于 MIT 许可证(有修改)开源