Skip to content

Commit c670fde

Browse files
siakinnikclansty
andauthored
fix: copy jacket_s when exporting AssetBundle jackets (#42) (#57)
* fix: copy jacket_s when exporting AssetBundle jackets (#42) * refactor: use more robust path handling and copy manifest for jacket_s * refactor: cover all export modes and improve path handling for jacket_s * fix: restore PseudoAssetBundleJacket fallback in ExportOpt * fix(#42): correctly locate and export jacket_s companion AB Previous attempt was a no-op: AssetBundleJacket is always under AssetBundleImages/jacket/ (per StaticSettings.ScanAssetBundles), but the companion small jacket lives in the sibling jacket_s/ directory (per ImageToAbToolController and MakeAbCommand). The Substring-based path derivation pointed into the wrong directory and the export destination was also wrong. Changes: - Add GetAssetBundleJacketSmallPath helper using Path APIs instead of fragile Substring(len-3) math; returns null on unexpected shapes. - CopyMusicToDirectory: restore dropped main .ab.manifest copy, read jacket_s from sibling dir, write to AssetBundleImages/jacket_s/. - ExportOpt: same directory fix for ZIP entries; tidy up ordering. - ModifyId: reserve the new ID's jacket_s slot in DeleteIfExists, delete old jacket_s (+ manifest) on AB->PNG conversion to avoid orphans; delete (not move) the old main .manifest since no new .ab is created at the target under PNG conversion. - MusicXmlWithABJacket.Delete: delete companion jacket_s + manifest alongside the main AB jacket to prevent orphan files. --------- Co-authored-by: Clansty <i@gao4.pw>
1 parent 9b075ce commit c670fde

2 files changed

Lines changed: 87 additions & 5 deletions

File tree

MaiChartManager/Controllers/Music/MusicTransferController.cs

Lines changed: 61 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,25 @@ private static int GetBatchExportMaxConcurrency()
3838
return Math.Max(1, Environment.ProcessorCount / 2);
3939
}
4040

41+
/// <summary>
42+
/// Given an AssetBundle jacket path (e.g. ".../AssetBundleImages/jacket/ui_jacket_000123.ab"),
43+
/// compute the companion small jacket path in the sibling "jacket_s" directory
44+
/// (e.g. ".../AssetBundleImages/jacket_s/ui_jacket_000123_s.ab").
45+
/// Returns null if the path shape is unexpected.
46+
/// </summary>
47+
private static string? GetAssetBundleJacketSmallPath(string assetBundleJacketPath)
48+
{
49+
var dir = Path.GetDirectoryName(assetBundleJacketPath);
50+
if (string.IsNullOrWhiteSpace(dir)) return null;
51+
var parentDir = Path.GetDirectoryName(dir);
52+
if (string.IsNullOrWhiteSpace(parentDir)) return null;
53+
54+
var jacketSDir = Path.Combine(parentDir, "jacket_s");
55+
var nameWithoutExt = Path.GetFileNameWithoutExtension(assetBundleJacketPath);
56+
var ext = Path.GetExtension(assetBundleJacketPath);
57+
return Path.Combine(jacketSDir, nameWithoutExt + "_s" + ext);
58+
}
59+
4160
private static readonly ConcurrentDictionary<string, string> FileHashCache = new(StringComparer.OrdinalIgnoreCase);
4261

4362
private static string GetFileHash(FileInfo fileInfo)
@@ -204,6 +223,19 @@ private void CopyMusicToDirectory(
204223
{
205224
CopySharedFileIfNeeded(music.AssetBundleJacket + ".manifest", Path.Combine(jacketRootDir, jacketFileName + ".manifest"), copiedSharedDestinations);
206225
}
226+
227+
// Issue #42: jacket_s lives in a sibling directory, must be exported into AssetBundleImages/jacket_s/
228+
var jacketSPath = GetAssetBundleJacketSmallPath(music.AssetBundleJacket);
229+
if (jacketSPath is not null && System.IO.File.Exists(jacketSPath))
230+
{
231+
var jacketSRootDir = Path.Combine(Path.GetDirectoryName(jacketRootDir)!, "jacket_s");
232+
var jacketSFileName = Path.GetFileName(jacketSPath);
233+
CopySharedFileIfNeeded(jacketSPath, Path.Combine(jacketSRootDir, jacketSFileName), copiedSharedDestinations);
234+
if (System.IO.File.Exists(jacketSPath + ".manifest"))
235+
{
236+
CopySharedFileIfNeeded(jacketSPath + ".manifest", Path.Combine(jacketSRootDir, jacketSFileName + ".manifest"), copiedSharedDestinations);
237+
}
238+
}
207239
}
208240
else if (music.PseudoAssetBundleJacket is not null)
209241
{
@@ -399,6 +431,17 @@ public void ExportOpt(int id, string assetDir, bool removeEvents = false, bool l
399431
{
400432
zipArchive.CreateEntryFromFile(music.AssetBundleJacket + ".manifest", $"AssetBundleImages/jacket/{Path.GetFileName(music.AssetBundleJacket)}.manifest");
401433
}
434+
435+
// Issue #42: jacket_s lives in a sibling directory, must be exported into AssetBundleImages/jacket_s/
436+
var jacketSPath = GetAssetBundleJacketSmallPath(music.AssetBundleJacket);
437+
if (jacketSPath is not null && System.IO.File.Exists(jacketSPath))
438+
{
439+
zipArchive.CreateEntryFromFile(jacketSPath, $"AssetBundleImages/jacket_s/{Path.GetFileName(jacketSPath)}");
440+
if (System.IO.File.Exists(jacketSPath + ".manifest"))
441+
{
442+
zipArchive.CreateEntryFromFile(jacketSPath + ".manifest", $"AssetBundleImages/jacket_s/{Path.GetFileName(jacketSPath)}.manifest");
443+
}
444+
}
402445
}
403446
else if (music.PseudoAssetBundleJacket is not null)
404447
{
@@ -456,10 +499,11 @@ public async Task ModifyId(int id, [FromBody] int newId, string assetDir)
456499
var newNonDxId = newId % 10000;
457500

458501
var abJacketTarget = Path.Combine(StaticSettings.StreamingAssets, assetDir, "AssetBundleImages", "jacket", $"ui_jacket_{newNonDxId:000000}.ab");
502+
var abJacketSTarget = Path.Combine(StaticSettings.StreamingAssets, assetDir, "AssetBundleImages", "jacket_s", $"ui_jacket_{newNonDxId:000000}_s.ab");
459503
var acbawbTarget = Path.Combine(StaticSettings.StreamingAssets, assetDir, "SoundData", $"music{newNonDxId:000000}");
460504
var movieTarget = Path.Combine(StaticSettings.StreamingAssets, assetDir, "MovieData", $"{newNonDxId:000000}");
461505
var newMusicDir = Path.Combine(StaticSettings.StreamingAssets, assetDir, "music", $"music{newId:000000}");
462-
DeleteIfExists(abJacketTarget, abJacketTarget + ".manifest", acbawbTarget + ".acb", acbawbTarget + ".awb", movieTarget + ".dat", movieTarget + ".mp4", newMusicDir);
506+
DeleteIfExists(abJacketTarget, abJacketTarget + ".manifest", abJacketSTarget, abJacketSTarget + ".manifest", acbawbTarget + ".acb", acbawbTarget + ".awb", movieTarget + ".dat", movieTarget + ".mp4", newMusicDir);
463507
var abiDir = Path.Combine(StaticSettings.StreamingAssets, assetDir, @"AssetBundleImages\jacket");
464508
Directory.CreateDirectory(abiDir);
465509

@@ -480,9 +524,21 @@ public async Task ModifyId(int id, [FromBody] int newId, string assetDir)
480524
logger.LogInformation("Convert jacket: {music.AssetBundleJacket} -> {abJacketTarget}", music.AssetBundleJacket, abJacketTarget);
481525
System.IO.File.WriteAllBytes(localJacketTarget, music.GetMusicJacketPngData()!);
482526
FileSystem.DeleteFile(music.AssetBundleJacket, UIOption.OnlyErrorDialogs, RecycleOption.SendToRecycleBin);
527+
// AB→PNG: the old .ab.manifest no longer has a matching .ab at the new ID, so just delete it instead of moving
483528
if (System.IO.File.Exists(music.AssetBundleJacket + ".manifest"))
484529
{
485-
FileSystem.MoveFile(music.AssetBundleJacket + ".manifest", abJacketTarget + ".manifest", UIOption.OnlyErrorDialogs);
530+
FileSystem.DeleteFile(music.AssetBundleJacket + ".manifest", UIOption.OnlyErrorDialogs, RecycleOption.SendToRecycleBin);
531+
}
532+
533+
// Issue #42: also clean up the companion jacket_s AB so it doesn't stay orphaned under the old ID
534+
var oldJacketSPath = GetAssetBundleJacketSmallPath(music.AssetBundleJacket);
535+
if (oldJacketSPath is not null && System.IO.File.Exists(oldJacketSPath))
536+
{
537+
FileSystem.DeleteFile(oldJacketSPath, UIOption.OnlyErrorDialogs, RecycleOption.SendToRecycleBin);
538+
if (System.IO.File.Exists(oldJacketSPath + ".manifest"))
539+
{
540+
FileSystem.DeleteFile(oldJacketSPath + ".manifest", UIOption.OnlyErrorDialogs, RecycleOption.SendToRecycleBin);
541+
}
486542
}
487543
}
488544

@@ -583,7 +639,7 @@ public async Task ExportAsMaidata(int id, string assetDir, bool ignoreVideo = fa
583639
}
584640

585641
var ma2Contents = new List<(int, string[])>();
586-
642+
587643
// 关于clock_count功能,我决定不走MaiLib了,而是我们自己解析。因为ma2.Compose返回的是裸谱面inote中的内容,没有办法合理的把clock信息插进去。因此,我们自己解析吧。
588644
// 选用最难的一张有效谱面的MET值作为全曲的&clock_count
589645
int clockCount = 0;
@@ -614,7 +670,7 @@ public async Task ExportAsMaidata(int id, string assetDir, bool ignoreVideo = fa
614670
simaiFile.AppendLine($"&chartconverter=MaiChartManager v{Application.ProductVersion}");
615671
simaiFile.AppendLine("&ChartConvertTool=MaiChartManager");
616672
simaiFile.AppendLine($"&ChartConvertToolVersion={Application.ProductVersion}");
617-
673+
618674
// 根据前面读取的结果,向simaiFile中最终写入谱面信息相关字段
619675
foreach (var (i, ma2Content) in ma2Contents)
620676
{
@@ -656,7 +712,7 @@ public async Task ExportAsMaidata(int id, string assetDir, bool ignoreVideo = fa
656712
Comment = version?.GenreName,
657713
AlbumArt = img,
658714
};
659-
715+
660716
if (!AudioConvert.TryResolveAcbAwb(GetAudioCandidateIds(music), out _, out var acbPath, out var awbPath) || acbPath is null || awbPath is null)
661717
{
662718
var message = BuildAudioResolveErrorMessage(music);

MaiChartManager/Models/MusicXmlWithABJacket.cs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,32 @@ public void Delete()
136136
{
137137
Console.WriteLine($"删除 jacket 失败: {RealJacketPath}");
138138
}
139+
140+
// Issue #42: AB jackets come with a companion jacket_s/ui_jacket_xxx_s.ab, delete it too to avoid orphan
141+
if (AssetBundleJacket is not null)
142+
{
143+
var abDir = Path.GetDirectoryName(AssetBundleJacket);
144+
var parentDir = string.IsNullOrWhiteSpace(abDir) ? null : Path.GetDirectoryName(abDir);
145+
if (!string.IsNullOrWhiteSpace(parentDir))
146+
{
147+
var jacketSPath = Path.Combine(parentDir, "jacket_s",
148+
Path.GetFileNameWithoutExtension(AssetBundleJacket) + "_s" + Path.GetExtension(AssetBundleJacket));
149+
if (File.Exists(jacketSPath) && !jacketSPath.Contains(@"\A000\", StringComparison.InvariantCultureIgnoreCase))
150+
{
151+
Console.WriteLine("删除 jacket_s: " + jacketSPath);
152+
try
153+
{
154+
FileSystem.DeleteFile(jacketSPath);
155+
if (File.Exists(jacketSPath + ".manifest"))
156+
FileSystem.DeleteFile(jacketSPath + ".manifest");
157+
}
158+
catch
159+
{
160+
Console.WriteLine($"删除 jacket_s 失败: {jacketSPath}");
161+
}
162+
}
163+
}
164+
}
139165
}
140166

141167
if (StaticSettings.AcbAwb.TryGetValue($"music{NonDxId:000000}.acb", out var acb) && acb?.Contains(@"\A000\", StringComparison.InvariantCultureIgnoreCase) == false)

0 commit comments

Comments
 (0)