feat(mod): 新增模组关系页,补全内嵌检测与依赖解析#3441
Conversation
Reviewer's Guide添加一个 Jar-in-Jar(嵌入模组)依赖分析系统,包含缓存、UI 和崩溃报告集成,并在模组启用/禁用/删除操作中基于嵌入依赖实现级联行为。 基于 Jar-in-Jar 依赖的模组启用/禁用/删除级联行为序列图sequenceDiagram
actor User
participant PageInstanceCompResource
participant ModJarInJarIndex
User->>PageInstanceCompResource: EDMods(modList, isEnable=false)
activate PageInstanceCompResource
PageInstanceCompResource->>PageInstanceCompResource: _JijFindAffected(list)
PageInstanceCompResource->>ModJarInJarIndex: new ModJarInJarIndex(allMods, mcVersion)
PageInstanceCompResource->>ModJarInJarIndex: FindAffected(targets)
ModJarInJarIndex-->>PageInstanceCompResource: affectedMods
PageInstanceCompResource->>PageInstanceCompResource: _AskJijCascade(affectedMods, false)
PageInstanceCompResource-->>PageInstanceCompResource: choice
alt choice == 1 (cascade disable)
PageInstanceCompResource->>PageInstanceCompResource: EDModsCore(list ∪ affectedMods, false)
else choice == 0 (only selected)
PageInstanceCompResource->>PageInstanceCompResource: EDModsCore(list, false)
end
deactivate PageInstanceCompResource
User->>PageInstanceCompResource: DeleteMods(modList)
activate PageInstanceCompResource
PageInstanceCompResource->>PageInstanceCompResource: _JijFindAffected(list)
PageInstanceCompResource->>ModJarInJarIndex: FindAffected(targets)
ModJarInJarIndex-->>PageInstanceCompResource: affectedMods
PageInstanceCompResource->>PageInstanceCompResource: _AskJijCascade(affectedMods, true)
PageInstanceCompResource-->>PageInstanceCompResource: choice
alt choice == 1 (disable affected)
PageInstanceCompResource->>PageInstanceCompResource: EDModsCore(affectedMods, false)
PageInstanceCompResource->>PageInstanceCompResource: DeleteModsCore(list)
else choice == 2 (delete affected)
PageInstanceCompResource->>PageInstanceCompResource: DeleteModsCore(list ∪ affectedMods)
else choice == 0 (only selected)
PageInstanceCompResource->>PageInstanceCompResource: DeleteModsCore(list)
end
deactivate PageInstanceCompResource
模组加载与崩溃报告导出过程中 Jar-in-Jar 缓存使用的序列图sequenceDiagram
participant ModLocalComp
participant ModJarInJar
participant ModJarInJarCache
participant CrashReportExporter
ModLocalComp->>ModJarInJarCache: UseInstance(instancePath)
ModLocalComp->>ModJarInJar: ResolveCached(path, jar)
ModJarInJar->>ModJarInJarCache: TryGet(path, lastModified, size)
alt cache hit
ModJarInJarCache-->>ModJarInJar: List EmbeddedModNode
ModJarInJar->>ModLocalComp: _FromNodes(nodes, parentPath)
else cache miss
ModJarInJarCache-->>ModJarInJar: null
ModJarInJar->>ModJarInJar: Resolve(parentPath, jar)
ModJarInJar-->>ModLocalComp: EmbeddedMods
ModJarInJar->>ModJarInJarCache: Set(path, lastModified, size, _ToNodes(EmbeddedMods))
end
CrashReportExporter->>ModJarInJarCache: UseInstance(instance.PathInstance)
CrashReportExporter->>CrashReportExporter: _WriteModInfo(reportFolder, instance)
CrashReportExporter->>ModJarInJarCache: Flush()
文件级改动
可能关联的问题
Tips and commandsInteracting with Sourcery
Customizing Your Experience访问你的 dashboard 以:
Getting HelpOriginal review guide in EnglishReviewer's GuideAdds a Jar-in-Jar (embedded mod) dependency analysis system with caching, UI, and crash report integration, plus cascaded enable/disable/delete behavior for mods based on embedded dependencies. Sequence diagram for cascaded mod enable/disable/delete based on Jar-in-Jar dependenciessequenceDiagram
actor User
participant PageInstanceCompResource
participant ModJarInJarIndex
User->>PageInstanceCompResource: EDMods(modList, isEnable=false)
activate PageInstanceCompResource
PageInstanceCompResource->>PageInstanceCompResource: _JijFindAffected(list)
PageInstanceCompResource->>ModJarInJarIndex: new ModJarInJarIndex(allMods, mcVersion)
PageInstanceCompResource->>ModJarInJarIndex: FindAffected(targets)
ModJarInJarIndex-->>PageInstanceCompResource: affectedMods
PageInstanceCompResource->>PageInstanceCompResource: _AskJijCascade(affectedMods, false)
PageInstanceCompResource-->>PageInstanceCompResource: choice
alt choice == 1 (cascade disable)
PageInstanceCompResource->>PageInstanceCompResource: EDModsCore(list ∪ affectedMods, false)
else choice == 0 (only selected)
PageInstanceCompResource->>PageInstanceCompResource: EDModsCore(list, false)
end
deactivate PageInstanceCompResource
User->>PageInstanceCompResource: DeleteMods(modList)
activate PageInstanceCompResource
PageInstanceCompResource->>PageInstanceCompResource: _JijFindAffected(list)
PageInstanceCompResource->>ModJarInJarIndex: FindAffected(targets)
ModJarInJarIndex-->>PageInstanceCompResource: affectedMods
PageInstanceCompResource->>PageInstanceCompResource: _AskJijCascade(affectedMods, true)
PageInstanceCompResource-->>PageInstanceCompResource: choice
alt choice == 1 (disable affected)
PageInstanceCompResource->>PageInstanceCompResource: EDModsCore(affectedMods, false)
PageInstanceCompResource->>PageInstanceCompResource: DeleteModsCore(list)
else choice == 2 (delete affected)
PageInstanceCompResource->>PageInstanceCompResource: DeleteModsCore(list ∪ affectedMods)
else choice == 0 (only selected)
PageInstanceCompResource->>PageInstanceCompResource: DeleteModsCore(list)
end
deactivate PageInstanceCompResource
Sequence diagram for Jar-in-Jar cache usage during mod loading and crash report exportsequenceDiagram
participant ModLocalComp
participant ModJarInJar
participant ModJarInJarCache
participant CrashReportExporter
ModLocalComp->>ModJarInJarCache: UseInstance(instancePath)
ModLocalComp->>ModJarInJar: ResolveCached(path, jar)
ModJarInJar->>ModJarInJarCache: TryGet(path, lastModified, size)
alt cache hit
ModJarInJarCache-->>ModJarInJar: List EmbeddedModNode
ModJarInJar->>ModLocalComp: _FromNodes(nodes, parentPath)
else cache miss
ModJarInJarCache-->>ModJarInJar: null
ModJarInJar->>ModJarInJar: Resolve(parentPath, jar)
ModJarInJar-->>ModLocalComp: EmbeddedMods
ModJarInJar->>ModJarInJarCache: Set(path, lastModified, size, _ToNodes(EmbeddedMods))
end
CrashReportExporter->>ModJarInJarCache: UseInstance(instance.PathInstance)
CrashReportExporter->>CrashReportExporter: _WriteModInfo(reportFolder, instance)
CrashReportExporter->>ModJarInJarCache: Flush()
File-Level Changes
Possibly linked issues
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this comment.
Hey - 我已经给出了一些高层次的反馈:
- 平台/加载器的依赖 ID 集合(ModLocalComp 中的
_IgnoredDepIds和 ModJarInJarIndex 中的_platformIds)目前是重复且略有差异的;建议将这些 ID 集中到一个共享的辅助工具中,以避免未来出现不一致。 PageInstanceCompJarInJar.RefreshList假设ModLocalComp.compResourceListLoader.output已经被填充;如果该页面可能在加载器完成之前就被打开,请添加空/空集合检查,或挂钩到加载器生命周期中,以避免潜在的空引用或过期数据。
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- The platform/loader dependency ID sets (_IgnoredDepIds in ModLocalComp and _platformIds in ModJarInJarIndex) are duplicated and slightly divergent; consider centralizing these IDs into a single shared helper to avoid future inconsistencies.
- PageInstanceCompJarInJar.RefreshList assumes ModLocalComp.compResourceListLoader.output is populated; if this page can be opened before the loader has finished, add a null/empty check or hook into the loader lifecycle to avoid potential null references or stale data.帮我变得更有用!请在每条评论上点击 👍 或 👎,我会根据你的反馈改进后续的评审。
Original comment in English
Hey - I've left some high level feedback:
- The platform/loader dependency ID sets (_IgnoredDepIds in ModLocalComp and _platformIds in ModJarInJarIndex) are duplicated and slightly divergent; consider centralizing these IDs into a single shared helper to avoid future inconsistencies.
- PageInstanceCompJarInJar.RefreshList assumes ModLocalComp.compResourceListLoader.output is populated; if this page can be opened before the loader has finished, add a null/empty check or hook into the loader lifecycle to avoid potential null references or stale data.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- The platform/loader dependency ID sets (_IgnoredDepIds in ModLocalComp and _platformIds in ModJarInJarIndex) are duplicated and slightly divergent; consider centralizing these IDs into a single shared helper to avoid future inconsistencies.
- PageInstanceCompJarInJar.RefreshList assumes ModLocalComp.compResourceListLoader.output is populated; if this page can be opened before the loader has finished, add a null/empty check or hook into the loader lifecycle to avoid potential null references or stale data.Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 63c10f465e
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 05ed984f7c
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 473f259986
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 8d68a6393a
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: fb229015d7
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 15c15f033a
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| var seg = headerL.Substring("dependencies.".Length).Trim('"', ' '); | ||
| if (seg != ownModIdL && jarModIds.Contains(seg)) continue; // 兄弟 mod 的依赖段 |
There was a problem hiding this comment.
Include every mod declared by a multi-mod JAR
When a Forge/NeoForge JAR contains multiple [[mods]] entries, this branch explicitly skips every dependencies.<secondModId> section, while the earlier selection retains only the first mod ID. The second mod is therefore neither registered as a provider nor represented as a dependent, so its prerequisites are absent from the relation page and removing one of them produces no cascade warning. Associate every declared mod ID and its dependency sections with the physical host JAR instead of dropping sibling sections.
Useful? React with 👍 / 👎.
| Raw = g.Select(r => r.Raw).FirstOrDefault(v => v is not null), | ||
| Optional = g.All(r => r.Optional), | ||
| Loader = g.Select(r => r.Loader).FirstOrDefault(l => l is not null) |
There was a problem hiding this comment.
Retain every same-ID dependency requirement
When the host and multiple embedded mods require the same ID with different ranges or loader dialects, this keeps only the first non-null range and first loader, silently discarding the other requirements. For example, if the retained row requires >=2 but is already unsatisfied while another embedded row requires >=1 from an installed 1.5 provider, FindAffected sees no active provider for the retained row and emits no cascade when 1.5 is removed, even though the second embedded mod loses a working prerequisite. Keep the rows separate or evaluate all requirements independently.
Useful? React with 👍 / 👎.
| var ver = providerVersion.Trim(); | ||
| if (ver.Length == 0 || !char.IsDigit(ver[0])) return true; | ||
| if (McConstraintMatcher.Satisfies(dep.Raw, dep.Loader, ver)) return true; |
There was a problem hiding this comment.
Compare v-prefixed provider versions
When a provider reports a common version such as v2.0, this digit gate treats it as unknown and immediately accepts it against every constraint; for example, v2.0 satisfies <2.0. This bypasses McConstraintMatcher, even though that matcher explicitly strips a leading v, causing incompatible providers to be reported as installed and suppressing missing-version and cascade warnings. Strip the supported prefix before this gate or let the matcher evaluate it.
Useful? React with 👍 / 👎.
| if (aPre < 0 && bPre < 0) return 0; | ||
| if (aPre < 0) return 1; // a 无预发布 > b(有预发布) | ||
| if (bPre < 0) return -1; // a 有预发布 < b(正式版) | ||
| return McVersionComparer.CompareVersion(a.Substring(aPre + 1), b.Substring(bPre + 1)); |
There was a problem hiding this comment.
Apply SemVer ordering within prereleases
Although the new branches distinguish stable versions from prereleases, prerelease identifiers are still delegated to McVersionComparer, which orders numeric tokens above alphabetic tokens. SemVer requires numeric identifiers to have lower precedence, so a Fabric constraint such as >=1.0.0-alpha incorrectly accepts provider 1.0.0-1, marking an incompatible provider installed and affecting cascade calculations. Compare dot-separated prerelease identifiers using SemVer's numeric/non-numeric rules.
Useful? React with 👍 / 👎.
| if (!string.IsNullOrEmpty(m.ModId)) _AddProvider(m.ModId, m, m.Version); | ||
| foreach (var n in nodes.Where(n => !string.IsNullOrEmpty(n.ModId))) | ||
| _AddProvider(n.ModId, m, n.Version); |
There was a problem hiding this comment.
Register Fabric provided aliases
When a Fabric mod declares an alias through provides and another mod depends on that alias, only the provider's primary ModId is added here because the metadata parser never retains the provided IDs. The relation page consequently reports the alias dependency as missing, and disabling the provider produces no cascade warning. Register all Fabric provides IDs as providers of the same host and version.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 10f669e338
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| { | ||
| if (_SelfBundleSatisfies(mod, dep)) return JijDepStatus.Bundled; | ||
| _providers.TryGetValue(_Norm(dep.DepId), out var provs); | ||
| var others = provs?.Where(p => p.Mod != mod).ToList() ?? new List<(CompFile Mod, string Version)>(); |
There was a problem hiding this comment.
Treat same-JAR aliases as self-provided
When a Forge/NeoForge JAR declares sibling mods A and B and A requires B, B is now registered through ProvidedIds, but its provider is associated with the same CompFile as A and this filter removes it. Since _SelfBundleSatisfies only considers embedded nodes, the relations page reports B as missing even though both mods are loaded from the same JAR. The fresh evidence beyond the earlier multi-mod issue is that sibling IDs are registered now, but are discarded specifically during analysis.
Useful? React with 👍 / 👎.
| if (fabricObject.ContainsKey("depends")) | ||
| foreach (var dep in (JsonObject)fabricObject["depends"]) | ||
| AddDependency(dep.Key, dep.Value.ToString()); | ||
| { | ||
| var ver = dep.Value is JsonArray arr | ||
| ? string.Join(" || ", | ||
| arr.Select(x => x?.ToString()).Where(x => !string.IsNullOrEmpty(x))) | ||
| : dep.Value?.ToString(); | ||
| AddDependency(dep.Key, string.IsNullOrEmpty(ver) ? null : ver); |
There was a problem hiding this comment.
Parse Fabric optional dependency maps
When a Fabric mod declares optional relationships through the standard recommends or suggests maps, this parser traverses only depends, so those entries never reach DependencyRaw or OptionalDependencies. The new relations page consequently omits Fabric optional dependencies entirely, despite displaying optional Forge dependencies; parse these maps with optional: true as well.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c13a6af279
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| var missing = probed.Where(x => x.status == JijDepStatus.Missing).Select(x => x.d.DepId).ToList(); | ||
| var mismatch = probed.Where(x => x.status == JijDepStatus.VersionMismatch) | ||
| .Select(x => x.d.Raw is null ? x.d.DepId : x.d.DepId + " " + x.d.Raw).ToList(); |
There was a problem hiding this comment.
Warn when every satisfying provider is disabled
When an enabled mod's required dependency has a version-compatible provider whose file is disabled, Analyze returns JijDepStatus.Disabled, but this warning filter includes only Missing and VersionMismatch. The dependency is unavailable at runtime, yet the warnings section remains empty, so include the disabled state among the missing-prerequisite warnings.
Useful? React with 👍 / 👎.
修复之前在 #3378 中暴露出的问题并基于此为PCL-CE增加模组关系自动分析能力、模组调整时支持级联功能。
TODO:
resolves #3439
本PR移植自HMCL #6230 ,由Opus4.8对代码进行了审查。
Summary by Sourcery
添加 Jar-in-Jar 模组关系分析、缓存及管理界面,并将具备依赖感知能力的级联处理集成到模组启用/禁用和删除操作中。
新功能:
错误修复:
增强改进:
Original summary in English
Summary by Sourcery
Add Jar-in-Jar mod relationship analysis, caching, and management UI, and integrate dependency-aware cascade handling into mod enable/disable and delete operations.
New Features:
Bug Fixes:
Enhancements: