diff --git a/.vscode/launch.json b/.vscode/launch.json index e867f06..befa809 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -8,8 +8,8 @@ "name": ".NET Launch (console)", "type": "coreclr", "request": "launch", - "preLaunchTask": "build", - "program": "${workspaceFolder}/src/JUS.CLI/bin/Debug/net8.0/JUS.CLI.dll", + "preLaunchTask": "Build", + "program": "${workspaceFolder}/src/JUS.CLI/bin/Debug/net10.0/JUS.CLI.dll", "args": [ "jus", "texts", diff --git a/src/JUS.CLI/JUS/Rom/DemoImageFile.cs b/src/JUS.CLI/JUS/Rom/DemoImageFile.cs new file mode 100644 index 0000000..40ab06e --- /dev/null +++ b/src/JUS.CLI/JUS/Rom/DemoImageFile.cs @@ -0,0 +1,202 @@ +// Copyright (c) 2024 Priverop + +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +using System.Text.RegularExpressions; +using JUS.Tool.BatchConverters; +using JUS.Tool.Containers.Converters; +using JUS.Tool.Utils; +using Yarhl.FileFormat; +using Yarhl.FileSystem; + +namespace JUS.CLI.JUS.Rom +{ + /// + /// Strategy Pattern: Interface for rom importing logic. + /// + public class DemoImageFile : IFileImportStrategy + { + private static readonly Dictionary SpecialDigNumbers = new() { + { '0', '3' }, + { '1', '5' }, + { '2', '7' }, + { '3', '9' }, + }; + + private static readonly Regex DemoPattern = new(@"^demo-.*\.png$"); + + /// + public bool Matches(string filename) + { + return DemoPattern.IsMatch(filename); + } + + /// + /// Import files into the Rom. + /// + /// The node of the Rom. + /// The input file to import. + public void Import(Node gameNode, Node file) + { + string containerPath = "/demo/demo.aar"; + + file.Name = StringFunctions.GetDemoName(file.Name); + + // Ignore the _n_ and _m_, because we obtain them later with the original + if (!IsSpecialDemoNM(file.Name)) { + ProcessContainer(gameNode, file, containerPath); + } + } + + private static void ProcessContainer(Node gameNode, Node pngFile, string containerPath) + { + Node originalAlar = Navigator.SearchNode(gameNode, $"/root/data{containerPath}") ?? throw new FormatException($"Container not found /root/data{containerPath}"); + + IConverter image2Alar3; + + string digName = GetDemoDigName(pngFile.Name) + ".dig"; + string atmName = Path.GetFileNameWithoutExtension(pngFile.Name) + ".atm"; + + // Special demo (3, 5, 7, 9) needs 3 atm, 3 pngs and 1 dig + // The rest (non special 2, 4, 6, title..., opening or sel) are 1 dig = 1 atm = 1 png + if (IsSpecialDemo(pngFile.Name) && !pngFile.Name.Contains("opening") && !pngFile.Name.Contains("sel01")) { + string[] atms = GetSpecialAtms(atmName); + Node[] pngs = GetSpecialPngs(pngFile); + image2Alar3 = new Demo2Alar3(pngs, digName, atms, true); + } else { + image2Alar3 = new Png2Alar3(pngFile, digName, atmName, true); + } + + _ = originalAlar.TransformWith() + .TransformWith(image2Alar3) + .TransformWith(new Alar3ToBinary()); + + Console.WriteLine($"File replaced: /root/data{containerPath}/{pngFile.Name}"); + } + + /// + /// Returns true if the png is a Special Demo Image. + /// + /// The name of the Png we are importing. + /// True if it's a special demo image (03, 05, 07, 09, _m_ or _n_). False otherwise. + private static bool IsSpecialDemo(string pngName) => + pngName.Contains("_03") || + pngName.Contains("_05") || + pngName.Contains("_07") || + pngName.Contains("_09") || + pngName.Contains("_m_") || + pngName.Contains("_n_"); + + /// + /// Returns true if the png is a _m_ or _n_ Special Demo Image. + /// + /// The name of the Png we are importing. + /// True if it's a special demo image (_m_ or _n_). False otherwise. + private static bool IsSpecialDemoNM(string pngName) => + pngName.Contains("_m_") || + pngName.Contains("_n_"); + + /// + /// Obtains the name of the corresponding Dig. + /// + /// + /// bb_00.png => bb_00.dig + /// bb_m_00.png => bb_03.dig + /// bb_n_00.png => bb_03.dig + /// bb_m_01.png => bb_05.dig + /// bb_n_01.png => bb_05.dig + /// bb_m_02.png => bb_07.dig + /// bb_n_02.png => bb_07.dig + /// bb_m_03.png => bb_09.dig + /// bb_n_03.png => bb_09.dig. + /// + private static string GetDemoDigName(string pngName) + { + // SpecialChar is the 4th character of the demo png name. + // jj_m_00.png => m + // jj_n_00.png => n + // jj_03.png => not n nor m, so it's not a special one + if (pngName.Contains("_m_") || pngName.Contains("_n_")) { + // jj_m_01.png => 1 + char number = pngName[6]; + string manga = pngName[..2]; + return manga + "_0" + SpecialDigNumbers.GetValueOrDefault(number); + } else { + return Path.GetFileNameWithoutExtension(pngName); + } + } + + /// + /// Constructs the names of the _m_ and _n_ files for the given base node name and extension. + /// + /// The base name of the node ("bb_03.png"). + /// The file extension (".png", ".atm"). + /// An array of strings containing the names of the base, _m_, and _n_ files. + private static string[] GetSpecialFileNames(string nodeName, string extension) + { + string manga = nodeName[..2]; // "bb" + char number = nodeName[4]; // '3' + + if (!SpecialDigNumbers.Any(kv => kv.Value == number)) { + throw new InvalidOperationException($"Value {number} is not found in SpecialDigNumbers."); + } + + char specialNumber = SpecialDigNumbers.First(kv => kv.Value == number).Key; + + string nameOfMFile = $"{manga}_m_0{specialNumber}{extension}"; + string nameOfNFile = $"{manga}_n_0{specialNumber}{extension}"; + + return [nodeName, nameOfMFile, nameOfNFile]; + } + + /// + /// Returns an array of Nodes with the given PNG Node, the _m_ and _n_ Nodes. + /// + /// + /// bb_03.png => [bb_03.png, bb_m_00.png, bb_n_00.png] + /// bb_05.png => [bb_05.png, bb_m_01.png, bb_n_01.png] + /// bb_07.png => [bb_07.png, bb_m_02.png, bb_n_02.png] + /// bb_09.png => [bb_09.png, bb_m_03.png, bb_n_03.png]. + /// + private static Node[] GetSpecialPngs(Node png) + { + string[] fileNames = GetSpecialFileNames(png.Name, ".png"); + + Node mNode = png.Parent!.Children["demo-" + fileNames[1]] ?? + throw new FormatException("Special m file not found: " + fileNames[1]); + Node nNode = png.Parent.Children["demo-" + fileNames[2]] ?? + throw new FormatException("Special nfile not found: " + fileNames[2]); + + return [png, mNode, nNode]; + } + + /// + /// Returns an array of strings with the names of the given ATM file, the _m_, and the _n_ files. + /// + /// + /// bb_03.atm => [bb_03.atm, bb_m_00.atm, bb_n_00.atm] + /// bb_05.atm => [bb_05.atm, bb_m_01.atm, bb_n_01.atm] + /// bb_07.atm => [bb_07.atm, bb_m_02.atm, bb_n_02.atm] + /// bb_09.atm => [bb_09.atm, bb_m_03.atm, bb_n_03.atm]. + /// + private static string[] GetSpecialAtms(string atm) + { + return GetSpecialFileNames(atm, ".atm"); + } + } +} diff --git a/src/JUS.CLI/JUS/Rom/IFileImportStrategy.cs b/src/JUS.CLI/JUS/Rom/IFileImportStrategy.cs index 1b09f70..9d6949d 100644 --- a/src/JUS.CLI/JUS/Rom/IFileImportStrategy.cs +++ b/src/JUS.CLI/JUS/Rom/IFileImportStrategy.cs @@ -26,6 +26,12 @@ namespace JUS.CLI.JUS.Rom /// public interface IFileImportStrategy { + /// + /// Checks if the strategy supports a filename. + /// + /// The name of the file to check. + bool Matches(string filename); + /// /// Import files into the Rom. /// diff --git a/src/JUS.CLI/JUS/Rom/ImageContainerFile.cs b/src/JUS.CLI/JUS/Rom/ImageContainerFile.cs deleted file mode 100644 index 09c7432..0000000 --- a/src/JUS.CLI/JUS/Rom/ImageContainerFile.cs +++ /dev/null @@ -1,271 +0,0 @@ -// Copyright (c) 2024 Priverop - -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: - -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. - -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -using System.Text.RegularExpressions; -using JUS.Tool.BatchConverters; -using JUS.Tool.Containers; -using JUS.Tool.Containers.Converters; -using JUS.Tool.Utils; -using Yarhl.FileFormat; -using Yarhl.FileSystem; -using Yarhl.IO; - -namespace JUS.CLI.JUS.Rom -{ - /// - /// Strategy Pattern: Interface for rom importing logic. - /// - public class ImageContainerFile : IFileImportStrategy - { - private static readonly Dictionary ContainerLocations = new() { - { "menu-Commu-code.png", ["code.dig", "code.atm", "/Commu/commu_pack.aar"] }, - { "menu-Commu-matchmake.png", ["matchmake.dig", "matchmake.atm", "/Commu/commu_pack.aar"] }, - { "menu-Commu-member.png", ["member.dig", "member.atm", "/Commu/commu_pack.aar"] }, - { "menu-Commu-member_data.png", ["member.dig", "member_data.atm", "/Commu/commu_pack.aar"] }, - { "menu-Commu-member_wireless.png", ["member.dig", "member_wireless.atm", "/Commu/commu_pack.aar"] }, - { "menu-Commu-sure02.png", ["sure02.dig", "sure02.atm", "/Commu/commu_pack.aar"] }, - { "menu-Commu-top_left.png", ["top_left.dig", "top_left.atm", "/Commu/commu_pack.aar"] }, - { "menu-Commu-top_right.png", ["top_right.dig", "top_right.atm", "/Commu/commu_pack.aar"] }, - { "menu-JArena-a_result01.png", ["a_result01.dig", "a_result01.atm", "/JArena/JArena.aar"] }, - { "menu-JArena-congra_top00.png", ["congra_top00.dig", "congra_top00.atm", "/JArena/JArena.aar"] }, - { "menu-JArena-mis01.png", ["mis01.dig", "mis01.atm", "/JArena/JArena.aar"] }, - { "menu-JArena-mis_top10.png", ["mis_top10.dig", "mis_top10.atm", "/JArena/JArena.aar"] }, - { "menu-JArena-ranking_list.png", ["ranking_list.dig", "ranking_list.atm", "/JArena/JArena.aar"] }, - { "menu-database-list_bg0.png", ["list_bg0.dig", "list_bg0.atm", "/database/database.aar"] }, - { "menu-database-personal_bg.png", ["personal_bg.dig", "personal_bg.atm", "/database/database.aar"] }, - { "menu-database-personal_bg2.png", ["personal_bg2.dig", "personal_bg2.atm", "/database/database.aar"] }, - { "menu-database-playing.png", ["playing.dig", "playing.atm", "/database/database.aar"] }, - { "menu-database-story_bg0.png", ["story_bg0.dig", "story_bg0.atm", "/database/database.aar"] }, - { "menu-database-story_bg1.png", ["story_bg1.dig", "story_bg1.atm", "/database/database.aar"] }, - { "menu-deckcheck-win_a.png", ["win.dig", "win_a.atm", "/deckcheck/deckcheck.aar"] }, - { "menu-deckcheck-win_g.png", ["win.dig", "win_g.atm", "/deckcheck/deckcheck.aar"] }, - { "menu-deckselect-deck_standby00.png", ["deck_standby00.dig", "deck_standby00.atm", "/deckselect/deckselect.aar"] }, - { "menu-deckselect-deck_standby01.png", ["deck_standby01.dig", "deck_standby01.atm", "/deckselect/deckselect.aar"] }, - { "menu-input-mode_bg0.png", ["mode_bg0.dig", "mode_bg0.atm", "/input/input.aar"] }, - { "menu-input-mode_bg1.png", ["mode_bg1.dig", "mode_bg1.atm", "/input/input.aar"] }, - { "menu-input-mode_bg2.png", ["mode_bg2.dig", "mode_bg2.atm", "/input/input.aar"] }, - { "menu-input-mode_bg3.png", ["mode_bg3.dig", "mode_bg3.atm", "/input/input.aar"] }, - { "menu-jgalaxy-ba_m_sel01.png", ["ba_m_sel01.dig", "ba_m_sel01.atm", "/jgalaxy/jgalaxy.aar"] }, - { "menu-jgalaxy-victory00.png", ["victory00.dig", "victory00.atm", "/jgalaxy/jgalaxy.aar"] }, - { "menu-jgalaxy-victory01.png", ["victory00.dig", "victory01.atm", "/jgalaxy/jgalaxy.aar"] }, - { "menu-jgalaxy-win_sel.png", ["win_sel.dig", "win_sel.atm", "/jgalaxy/jgalaxy.aar"] }, - { "menu-jpower-jp.png", ["jp.dig", "jp.atm", "/jpower/jpower.aar"] }, - { "menu-jpower-jp_top.png", ["jp_top.dig", "jp_top.atm", "/jpower/jpower.aar"] }, - { "menu-option-info00.png", ["info00.dig", "info00.atm", "/option/option.aar"] }, - { "menu-option-option.png", ["option.dig", "option.atm", "/option/option.aar"] }, - { "menu-ruleselect-rule_sel00.png", ["rule_sel00.dig", "rule_sel00.atm", "/ruleselect/ruleselect.aar"] }, - { "menu-stageselect-st_sel01.png", ["st_sel01.dig", "st_sel01.atm", "/stageselect/stageselect.aar"] }, - { "menu-topmenu-top_bg01.png", ["top_bg01.dig", "top_bg01.atm", "/topmenu/topmenu.aar"] }, - }; - - private static readonly Dictionary SpecialDigNumbers = new() { - { '0', '3' }, - { '1', '5' }, - { '2', '7' }, - { '3', '9' }, - }; - - private static readonly List<(Regex, string[])> PatternList = new() - { - (new Regex(@"^demo-.*\.png$"), ["/demo/demo.aar"]), - }; - - /// - /// Import files into the Rom. - /// - /// The node of the Rom. - /// The input file to import. - public void Import(Node gameNode, Node file) - { - if (ContainerLocations.TryGetValue(file.Name, out string[]? imageInfo)) { - file.Name = StringFunctions.GetOriginalName(file.Name); - ProcessContainer(gameNode, file, imageInfo); - } else { - // Si no se encuentra, intenta encontrar la ruta interna usando patrones - foreach ((Regex pattern, string[] containerPath) in PatternList) { - if (pattern.IsMatch(file.Name)) { - file.Name = StringFunctions.GetDemoName(file.Name); - - // Ignore the _n_ and _m_, because we obtain them later with the original - if (!IsSpecialDemoNM(file.Name)) { - // Get the Dig, Atm and Alar - string[] demoInfo = GetDemoInfo(file.Name, containerPath); - ProcessContainer(gameNode, file, demoInfo, true); - } - - return; - } - } - - Console.WriteLine($"File not compatible as image container: {file.Name}"); - } - } - - private static void ProcessContainer(Node gameNode, Node pngFile, string[] imageInfo, bool transparentTile = false) - { - // 1 - Search the Original Alar3 - Node originalAlar = Navigator.SearchNode(gameNode, $"/root/data{imageInfo[2]}") ?? throw new FormatException($"Container not found /root/data{imageInfo[2]}"); - _ = originalAlar.TransformWith(); - - // 2 - Insert the Png into the Alar3 - IConverter image2Alar3; - - // Special demo needs 3 atm, 3 pngs and 1 dig (imageInfo[0]) - if (IsSpecialDemo(pngFile.Name) && !pngFile.Name.Contains("opening") && !pngFile.Name.Contains("sel01")) { - string[] atms = GetSpecialAtms(imageInfo[1]); - Node[] pngs = GetSpecialPngs(pngFile); - image2Alar3 = new Demo2Alar3(pngs, imageInfo[0], atms, transparentTile); - } else { - image2Alar3 = new Png2Alar3(pngFile, imageInfo[0], imageInfo[1], transparentTile); - } - - Alar newAlar = originalAlar - .TransformWith(image2Alar3) - .GetFormatAs()!; - - BinaryFormat newBinary = newAlar.ConvertWith(new Alar3ToBinary()); - - // 3 - Sustituirlo - _ = originalAlar.ChangeFormat(newBinary); - - Console.WriteLine($"File replaced: /root/data{imageInfo[2]}/{pngFile.Name}"); - } - - /// - /// Get the atm and dig names. - /// - /// The name of the Png we are importing. - /// The path of the alar container of the file. - /// An array with the DIG, ATM, and ContainerPath (.aar) names. - private static string[] GetDemoInfo(string pngName, string[] containerPath) - => [GetDemoDigName(pngName) + ".dig", - Path.GetFileNameWithoutExtension(pngName) + ".atm", - containerPath[0]]; - - /// - /// Returns true if the png is a Special Demo Image. - /// - /// The name of the Png we are importing. - /// True if it's a special demo image (03, 05, 07, 09, _m_ or _n_). False otherwise. - private static bool IsSpecialDemo(string pngName) => - pngName.Contains("_03") || - pngName.Contains("_05") || - pngName.Contains("_07") || - pngName.Contains("_09") || - pngName.Contains("_m_") || - pngName.Contains("_n_"); - - /// - /// Returns true if the png is a _m_ or _n_ Special Demo Image. - /// - /// The name of the Png we are importing. - /// True if it's a special demo image (_m_ or _n_). False otherwise. - private static bool IsSpecialDemoNM(string pngName) => - pngName.Contains("_m_") || - pngName.Contains("_n_"); - - /// - /// Obtains the name of the corresponding Dig. - /// - /// - /// bb_00.png => bb_00.dig - /// bb_m_00.png => bb_03.dig - /// bb_n_00.png => bb_03.dig - /// bb_m_01.png => bb_05.dig - /// bb_n_01.png => bb_05.dig - /// bb_m_02.png => bb_07.dig - /// bb_n_02.png => bb_07.dig - /// bb_m_03.png => bb_09.dig - /// bb_n_03.png => bb_09.dig. - /// - private static string GetDemoDigName(string pngName) - { - // SpecialChar is the 4th character of the demo png name. - // jj_m_00.png => m - // jj_n_00.png => n - // jj_03.png => not n nor m, so it's not a special one - if (pngName.Contains("_m_") || pngName.Contains("_n_")) { - // jj_m_01.png => 1 - char number = pngName[6]; - string manga = pngName[..2]; - return manga + "_0" + SpecialDigNumbers.GetValueOrDefault(number); - } else { - return Path.GetFileNameWithoutExtension(pngName); - } - } - - /// - /// Constructs the names of the _m_ and _n_ files for the given base node name and extension. - /// - /// The base name of the node ("bb_03.png"). - /// The file extension (".png", ".atm"). - /// An array of strings containing the names of the base, _m_, and _n_ files. - private static string[] GetSpecialFileNames(string nodeName, string extension) - { - string manga = nodeName[..2]; // "bb" - char number = nodeName[4]; // '3' - - if (!SpecialDigNumbers.Any(kv => kv.Value == number)) { - throw new InvalidOperationException($"Value {number} is not found in SpecialDigNumbers."); - } - - char specialNumber = SpecialDigNumbers.First(kv => kv.Value == number).Key; - - string nameOfMFile = $"{manga}_m_0{specialNumber}{extension}"; - string nameOfNFile = $"{manga}_n_0{specialNumber}{extension}"; - - return new[] { nodeName, nameOfMFile, nameOfNFile }; - } - - /// - /// Returns an array of Nodes with the given PNG Node, the _m_ and _n_ Nodes. - /// - /// - /// bb_03.png => [bb_03.png, bb_m_00.png, bb_n_00.png] - /// bb_05.png => [bb_05.png, bb_m_01.png, bb_n_01.png] - /// bb_07.png => [bb_07.png, bb_m_02.png, bb_n_02.png] - /// bb_09.png => [bb_09.png, bb_m_03.png, bb_n_03.png]. - /// - private static Node[] GetSpecialPngs(Node png) - { - string[] fileNames = GetSpecialFileNames(png.Name, ".png"); - - Node mNode = png.Parent!.Children["demo-" + fileNames[1]] ?? - throw new FormatException("Special m file not found: " + fileNames[1]); - Node nNode = png.Parent.Children["demo-" + fileNames[2]] ?? - throw new FormatException("Special nfile not found: " + fileNames[2]); - - return new[] { png, mNode, nNode }; - } - - /// - /// Returns an array of strings with the names of the given ATM file, the _m_, and the _n_ files. - /// - /// - /// bb_03.atm => [bb_03.atm, bb_m_00.atm, bb_n_00.atm] - /// bb_05.atm => [bb_05.atm, bb_m_01.atm, bb_n_01.atm] - /// bb_07.atm => [bb_07.atm, bb_m_02.atm, bb_n_02.atm] - /// bb_09.atm => [bb_09.atm, bb_m_03.atm, bb_n_03.atm]. - /// - private static string[] GetSpecialAtms(string atm) - { - return GetSpecialFileNames(atm, ".atm"); - } - } -} diff --git a/src/JUS.CLI/JUS/Rom/MenuImageFile.cs b/src/JUS.CLI/JUS/Rom/MenuImageFile.cs new file mode 100644 index 0000000..d530575 --- /dev/null +++ b/src/JUS.CLI/JUS/Rom/MenuImageFile.cs @@ -0,0 +1,105 @@ +// Copyright (c) 2024 Priverop + +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +using JUS.Tool.BatchConverters; +using JUS.Tool.Containers.Converters; +using JUS.Tool.Utils; +using Yarhl.FileSystem; + +namespace JUS.CLI.JUS.Rom +{ + /// + /// Strategy Pattern: Interface for rom importing logic. + /// + public class MenuImageFile : IFileImportStrategy + { + private static readonly Dictionary ContainerLocations = new() { + { "menu-Commu-code.png", ["code.dig", "code.atm", "/Commu/commu_pack.aar"] }, + { "menu-Commu-matchmake.png", ["matchmake.dig", "matchmake.atm", "/Commu/commu_pack.aar"] }, + { "menu-Commu-member.png", ["member.dig", "member.atm", "/Commu/commu_pack.aar"] }, + { "menu-Commu-member_data.png", ["member.dig", "member_data.atm", "/Commu/commu_pack.aar"] }, + { "menu-Commu-member_wireless.png", ["member.dig", "member_wireless.atm", "/Commu/commu_pack.aar"] }, + { "menu-Commu-sure02.png", ["sure02.dig", "sure02.atm", "/Commu/commu_pack.aar"] }, + { "menu-Commu-top_left.png", ["top_left.dig", "top_left.atm", "/Commu/commu_pack.aar"] }, + { "menu-Commu-top_right.png", ["top_right.dig", "top_right.atm", "/Commu/commu_pack.aar"] }, + { "menu-JArena-a_result01.png", ["a_result01.dig", "a_result01.atm", "/JArena/JArena.aar"] }, + { "menu-JArena-congra_top00.png", ["congra_top00.dig", "congra_top00.atm", "/JArena/JArena.aar"] }, + { "menu-JArena-mis01.png", ["mis01.dig", "mis01.atm", "/JArena/JArena.aar"] }, + { "menu-JArena-mis_top10.png", ["mis_top10.dig", "mis_top10.atm", "/JArena/JArena.aar"] }, + { "menu-JArena-ranking_list.png", ["ranking_list.dig", "ranking_list.atm", "/JArena/JArena.aar"] }, + { "menu-database-list_bg0.png", ["list_bg0.dig", "list_bg0.atm", "/database/database.aar"] }, + { "menu-database-personal_bg.png", ["personal_bg.dig", "personal_bg.atm", "/database/database.aar"] }, + { "menu-database-personal_bg2.png", ["personal_bg2.dig", "personal_bg2.atm", "/database/database.aar"] }, + { "menu-database-playing.png", ["playing.dig", "playing.atm", "/database/database.aar"] }, + { "menu-database-story_bg0.png", ["story_bg0.dig", "story_bg0.atm", "/database/database.aar"] }, + { "menu-database-story_bg1.png", ["story_bg1.dig", "story_bg1.atm", "/database/database.aar"] }, + { "menu-deckcheck-win_a.png", ["win.dig", "win_a.atm", "/deckcheck/deckcheck.aar"] }, + { "menu-deckcheck-win_g.png", ["win.dig", "win_g.atm", "/deckcheck/deckcheck.aar"] }, + { "menu-deckselect-deck_standby00.png", ["deck_standby00.dig", "deck_standby00.atm", "/deckselect/deckselect.aar"] }, + { "menu-deckselect-deck_standby01.png", ["deck_standby01.dig", "deck_standby01.atm", "/deckselect/deckselect.aar"] }, + { "menu-input-mode_bg0.png", ["mode_bg0.dig", "mode_bg0.atm", "/input/input.aar"] }, + { "menu-input-mode_bg1.png", ["mode_bg1.dig", "mode_bg1.atm", "/input/input.aar"] }, + { "menu-input-mode_bg2.png", ["mode_bg2.dig", "mode_bg2.atm", "/input/input.aar"] }, + { "menu-input-mode_bg3.png", ["mode_bg3.dig", "mode_bg3.atm", "/input/input.aar"] }, + { "menu-jgalaxy-ba_m_sel01.png", ["ba_m_sel01.dig", "ba_m_sel01.atm", "/jgalaxy/jgalaxy.aar"] }, + { "menu-jgalaxy-victory00.png", ["victory00.dig", "victory00.atm", "/jgalaxy/jgalaxy.aar"] }, + { "menu-jgalaxy-victory01.png", ["victory00.dig", "victory01.atm", "/jgalaxy/jgalaxy.aar"] }, + { "menu-jgalaxy-win_sel.png", ["win_sel.dig", "win_sel.atm", "/jgalaxy/jgalaxy.aar"] }, + { "menu-jpower-jp.png", ["jp.dig", "jp.atm", "/jpower/jpower.aar"] }, + { "menu-jpower-jp_top.png", ["jp_top.dig", "jp_top.atm", "/jpower/jpower.aar"] }, + { "menu-option-info00.png", ["info00.dig", "info00.atm", "/option/option.aar"] }, + { "menu-option-option.png", ["option.dig", "option.atm", "/option/option.aar"] }, + { "menu-ruleselect-rule_sel00.png", ["rule_sel00.dig", "rule_sel00.atm", "/ruleselect/ruleselect.aar"] }, + { "menu-stageselect-st_sel01.png", ["st_sel01.dig", "st_sel01.atm", "/stageselect/stageselect.aar"] }, + { "menu-topmenu-top_bg01.png", ["top_bg01.dig", "top_bg01.atm", "/topmenu/topmenu.aar"] }, + }; + + /// + public bool Matches(string filename) + { + return ContainerLocations.ContainsKey(filename); + } + + /// + /// Import files into the Rom. + /// + /// The node of the Rom. + /// The input file to import. + public void Import(Node gameNode, Node file) + { + if (ContainerLocations.TryGetValue(file.Name, out string[]? imageInfo)) { + file.Name = StringFunctions.GetOriginalName(file.Name); + ProcessContainer(gameNode, file, imageInfo); + } else { + Console.WriteLine($"File not compatible as menu image: {file.Name}"); + } + } + + private static void ProcessContainer(Node gameNode, Node pngFile, string[] imageInfo) + { + Node originalAlar = Navigator.SearchNode(gameNode, $"/root/data{imageInfo[2]}") ?? throw new FormatException($"Container not found /root/data{imageInfo[2]}"); + + originalAlar.TransformWith() + .TransformWith(new Png2Alar3(pngFile, imageInfo[0], imageInfo[1])) + .TransformWith(new Alar3ToBinary()); + + Console.WriteLine($"File replaced: /root/data{imageInfo[2]}/{pngFile.Name}"); + } + } +} diff --git a/src/JUS.CLI/JUS/Rom/TextContainerFile.cs b/src/JUS.CLI/JUS/Rom/TextContainerFile.cs index c70e82a..dda9e87 100644 --- a/src/JUS.CLI/JUS/Rom/TextContainerFile.cs +++ b/src/JUS.CLI/JUS/Rom/TextContainerFile.cs @@ -17,14 +17,9 @@ // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. -using System.Text.RegularExpressions; using JUS.Tool.Containers; using JUS.Tool.Containers.Converters; -using JUS.Tool.Graphics.Converters; -using JUS.Tool.Utils; -using Yarhl.FileFormat; using Yarhl.FileSystem; -using Yarhl.IO; namespace JUS.CLI.JUS.Rom { @@ -40,11 +35,11 @@ public class TextContainerFile : IFileImportStrategy { "jquiz.bin", "/jquiz/jquiz_pack.aar" }, }; - private static readonly List<(Regex, string)> PatternList = new() + /// + public bool Matches(string filename) { - (new Regex(@"^bin-.*-.*\.bin$"), "/bin/InfoDeck.aar"), // "{container}/bin/deck/{file.Name}" - (new Regex(@"^deck-.*-.*\.bin$"), "/deck/Deck.aar"), // "{container}/bin/deck/{file.Name}" - }; + return ContainerLocations.ContainsKey(filename); + } /// /// Import files into the Rom. @@ -57,39 +52,23 @@ public void Import(Node gameNode, Node file) file.Name = GetFileName(file.Name); ProcessContainer(gameNode, file, path); } else { - // Si no se encuentra, intenta encontrar la ruta interna usando patrones - foreach ((Regex pattern, string containerPath) in PatternList) { - if (pattern.IsMatch(file.Name)) { - string? parent = GetParentName(file.Name); - file.Name = StringFunctions.GetOriginalName(file.Name); - ProcessContainer(gameNode, file, containerPath, parent); - return; - } - } - Console.WriteLine($"File not compatible as text container: {file.Name}"); } } - private static void ProcessContainer(Node gameNode, Node file, string containerPath, string? parent = null) + private static void ProcessContainer(Node gameNode, Node file, string containerPath) { - Node containerNode = Navigator.SearchNode(gameNode, $"/root/data{containerPath}")!; - - Alar alar = containerNode.TransformWith() - .GetFormatAs()!; - alar.InsertModification(file, parent!); - BinaryFormat newBinary = alar.ConvertWith(new Alar3ToBinary()); + Node containerNode = Navigator.SearchNode(gameNode, $"/root/data{containerPath}") ?? throw new FormatException($"Container not found /root/data{containerPath}"); - _ = containerNode.ChangeFormat(newBinary); + Alar alar = containerNode.TransformWith().GetFormatAs()!; + alar.InsertModification(file); + _ = containerNode.TransformWith(new Alar3ToBinary()); - string fullPath = parent != null - ? $"/root/data{containerPath}/{parent}/{file.Name}" - : $"/root/data{containerPath}/{file.Name}"; - Console.WriteLine($"File replaced: {fullPath}"); + Console.WriteLine($"File replaced: /root/data{containerPath}/{file.Name}"); } /// - /// Gets the file name without the container prefix. "jgalaxy-mission.bin" will return "mission.bin". + /// Gets the file name without the container prefix. "jgalaxy-mission.bin" returns "mission.bin". /// private static string GetFileName(string name) { @@ -99,27 +78,5 @@ private static string GetFileName(string name) return name[(name.IndexOf('-') + 1)..]; } - - /// - /// Gets the directory name of the file (parent). "bin-deck-bb.bin" will return "deck". - /// - /// The string containing potentially "bin-deck-", "bin-info-", "deck-play"... prefixes. - /// The directory name. If the input string is null or empty, the original string is returned. - private static string? GetParentName(string name) - { - if (string.IsNullOrEmpty(name) || !name.Contains('-')) { - return null; - } - - // Regular expression to capture the second word - var regex = new Regex(@"^[^-]+-([^-]+)-"); - Match match = regex.Match(name); - - if (match.Success && match.Groups.Count > 1) { - return match.Groups[1].Value; - } - - return null; - } } } diff --git a/src/JUS.CLI/JUS/Rom/TextFile.cs b/src/JUS.CLI/JUS/Rom/TextFile.cs index 68db0ba..19798e9 100644 --- a/src/JUS.CLI/JUS/Rom/TextFile.cs +++ b/src/JUS.CLI/JUS/Rom/TextFile.cs @@ -26,32 +26,37 @@ namespace JUS.CLI.JUS.Rom /// public class TextFile : IFileImportStrategy { - // ToDo: Remove filename of the second string, we only need the directory private static readonly Dictionary TextLocations = new() { - { "tutorial.bin", "/deckmake/tutorial.bin" }, - { "tutorial0.bin", "/battle/tutorial0.bin" }, - { "tutorial1.bin", "/battle/tutorial1.bin" }, - { "tutorial2.bin", "/battle/tutorial2.bin" }, - { "tutorial3.bin", "/battle/tutorial3.bin" }, - { "tutorial4.bin", "/battle/tutorial4.bin" }, - { "tutorial5.bin", "/battle/tutorial5.bin" }, - { "ability_t.bin", "/bin/ability_t.bin" }, - { "bgm.bin", "/bin/bgm.bin" }, - { "chr_b_t.bin", "/bin/chr_b_t.bin" }, - { "chr_s_t.bin", "/bin/chr_s_t.bin" }, - { "clearlst.bin", "/bin/clearlst.bin" }, - { "commwin.bin", "/bin/commwin.bin" }, - { "demo.bin", "/bin/demo.bin" }, - { "infoname.bin", "/bin/infoname.bin" }, - { "komatxt.bin", "/bin/komatxt.bin" }, - { "location.bin", "/bin/location.bin" }, - { "piece.bin", "/bin/piece.bin" }, - { "pname.bin", "/bin/pname.bin" }, - { "rulemess.bin", "/bin/rulemess.bin" }, - { "stage.bin", "/bin/stage.bin" }, - { "title.bin", "/bin/title.bin" }, + { "tutorial.bin", "/deckmake" }, + { "tutorial0.bin", "/battle" }, + { "tutorial1.bin", "/battle" }, + { "tutorial2.bin", "/battle" }, + { "tutorial3.bin", "/battle" }, + { "tutorial4.bin", "/battle" }, + { "tutorial5.bin", "/battle" }, + { "ability_t.bin", "/bin" }, + { "bgm.bin", "/bin" }, + { "chr_b_t.bin", "/bin" }, + { "chr_s_t.bin", "/bin" }, + { "clearlst.bin", "/bin" }, + { "commwin.bin", "/bin" }, + { "demo.bin", "/bin" }, + { "infoname.bin", "/bin" }, + { "komatxt.bin", "/bin" }, + { "location.bin", "/bin" }, + { "piece.bin", "/bin" }, + { "pname.bin", "/bin" }, + { "rulemess.bin", "/bin" }, + { "stage.bin", "/bin" }, + { "title.bin", "/bin" }, }; + /// + public bool Matches(string filename) + { + return TextLocations.ContainsKey(filename); + } + /// /// Import files into the Rom. /// @@ -60,9 +65,9 @@ public class TextFile : IFileImportStrategy public void Import(Node gameNode, Node file) { if (TextLocations.TryGetValue(file.Name, out string? value)) { - Node toReplace = Navigator.SearchNode(gameNode, $"/root/data{value}")!; + Node toReplace = Navigator.SearchNode(gameNode, $"/root/data{value}/{file.Name}")!; toReplace.ChangeFormat(file.Format!); - Console.WriteLine($"File replaced: /root/data{value}"); + Console.WriteLine($"File replaced: /root/data{value}/{file.Name}"); } } } diff --git a/src/JUS.CLI/JUS/Rom/TextPatternFile.cs b/src/JUS.CLI/JUS/Rom/TextPatternFile.cs new file mode 100644 index 0000000..d63903e --- /dev/null +++ b/src/JUS.CLI/JUS/Rom/TextPatternFile.cs @@ -0,0 +1,89 @@ +// Copyright (c) 2024 Priverop + +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +using System.Text.RegularExpressions; +using JUS.Tool.Containers; +using JUS.Tool.Containers.Converters; +using JUS.Tool.Utils; +using Yarhl.FileSystem; + +namespace JUS.CLI.JUS.Rom +{ + /// + /// Strategy Pattern: Interface for rom importing logic. + /// + public class TextPatternFile : IFileImportStrategy + { + private static readonly List<(Regex Pattern, string)> PatternList = + [ + (new Regex(@"^bin-.*-.*\.bin$"), "/bin/InfoDeck.aar"), // "{container}/bin/deck/{file.Name}" + (new Regex(@"^deck-.*-.*\.bin$"), "/deck/Deck.aar"), // "{container}/bin/deck/{file.Name}" + ]; + + public bool Matches(string filename) + { + return PatternList.Any(x => x.Pattern.IsMatch(filename)); + } + + /// + /// Import files into the Rom. + /// + /// The node of the Rom. + /// The input file to import. + public void Import(Node gameNode, Node file) + { + // Si no se encuentra, intenta encontrar la ruta interna usando patrones + foreach ((Regex pattern, string containerPath) in PatternList) { + if (pattern.IsMatch(file.Name)) { + string parent = GetParentName(file.Name); + file.Name = StringFunctions.GetOriginalName(file.Name); + ProcessContainer(gameNode, file, containerPath, parent); + return; + } + } + + Console.WriteLine($"File not compatible as text container: {file.Name}"); + } + + private static void ProcessContainer(Node gameNode, Node file, string containerPath, string parent) + { + Node containerNode = Navigator.SearchNode(gameNode, $"/root/data{containerPath}") ?? throw new FormatException($"Container not found /root/data{containerPath}"); + + Alar alar = containerNode.TransformWith().GetFormatAs()!; + alar.InsertModification(file, parent); + _ = containerNode.TransformWith(new Alar3ToBinary()); + + Console.WriteLine($"File replaced: /root/data{containerPath}/{parent}/{file.Name}"); + } + + /// + /// Gets the directory name of the file (parent). "bin-deck-bb.bin" will return "deck". + /// + /// The string containing potentially "bin-deck-", "bin-info-", "deck-play"... prefixes. + /// The directory name. If the input string is null or empty, the original string is returned. + private static string GetParentName(string name) + { + // Regular expression to capture the second word + var regex = new Regex(@"^[^-]+-([^-]+)-"); + Match match = regex.Match(name); + + return match.Groups[1].Value; + } + } +} diff --git a/src/JUS.CLI/JUS/RomCommands.cs b/src/JUS.CLI/JUS/RomCommands.cs index 745ce6e..65c2418 100644 --- a/src/JUS.CLI/JUS/RomCommands.cs +++ b/src/JUS.CLI/JUS/RomCommands.cs @@ -17,7 +17,6 @@ // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. -using System.Text.RegularExpressions; using JUS.CLI.JUS.Rom; using SceneGate.Ekona.Containers.Rom; using Yarhl.FileSystem; @@ -30,43 +29,14 @@ namespace JUS.CLI.JUS /// public static class RomCommands { - private static readonly Dictionary ImportStrategies = new() - { - { "tutorial.bin", new TextFile() }, - { "tutorial0.bin", new TextFile() }, - { "tutorial1.bin", new TextFile() }, - { "tutorial2.bin", new TextFile() }, - { "tutorial3.bin", new TextFile() }, - { "tutorial4.bin", new TextFile() }, - { "tutorial5.bin", new TextFile() }, - { "ability_t.bin", new TextFile() }, - { "bgm.bin", new TextFile() }, - { "chr_b_t.bin", new TextFile() }, - { "chr_s_t.bin", new TextFile() }, - { "clearlst.bin", new TextFile() }, - { "commwin.bin", new TextFile() }, - { "demo.bin", new TextFile() }, - { "infoname.bin", new TextFile() }, - { "komatxt.bin", new TextFile() }, - { "location.bin", new TextFile() }, - { "piece.bin", new TextFile() }, - { "pname.bin", new TextFile() }, - { "rulemess.bin", new TextFile() }, - { "stage.bin", new TextFile() }, - { "title.bin", new TextFile() }, - { "jgalaxy-jgalaxy.bin", new TextContainerFile() }, - { "jgalaxy-mission.bin", new TextContainerFile() }, - { "jgalaxy-battle.bin", new TextContainerFile() }, - { "jquiz.bin", new TextContainerFile() }, - }; - - private static readonly List<(Regex pattern, IFileImportStrategy strategy)> PatternStrategies = new() - { - (new Regex(@"^bin-.*-.*\.bin$"), new TextContainerFile()), - (new Regex(@"^deck-.*-.*\.bin$"), new TextContainerFile()), - (new Regex(@"^menu-.*-.*\.png$"), new ImageContainerFile()), - (new Regex(@"^demo-.*\.png$"), new ImageContainerFile()), - }; + private static readonly IFileImportStrategy[] Strategies = + [ + new TextFile(), + new TextContainerFile(), + new TextPatternFile(), + new MenuImageFile(), + new DemoImageFile(), + ]; /// /// Import files into the Rom. @@ -84,24 +54,19 @@ public static void Import(string game, string input, string output) Node inputFiles = NodeFactory.FromDirectory(input); inputFiles.SortChildren((x, y) => string.Compare(x.Name, y.Name, StringComparison.CurrentCulture)); + bool match = false; + + // TODO: find a way to not do 1 by 1. foreach (Node file in inputFiles.Children) { - // Fixed names - if (ImportStrategies.TryGetValue(file.Name, out IFileImportStrategy? strategy)) { - strategy.Import(gameNode, file); - } else { - // Pattern names - bool matched = false; - foreach ((Regex pattern, IFileImportStrategy patternStrategy) in PatternStrategies) { - if (pattern.IsMatch(file.Name)) { - patternStrategy.Import(gameNode, file); - matched = true; - break; - } + foreach (IFileImportStrategy strategy in Strategies) { + if (strategy.Matches(file.Name)) { + match = true; + strategy.Import(gameNode, file); } + } - if (!matched) { - Console.WriteLine($"File not compatible: {file.Name}"); - } + if (!match) { + Console.WriteLine($"File not compatible: {file.Name}"); } }