public void TestCompareDecompile() { var resPath = Path.Combine(Environment.CurrentDirectory, @"..\..\Res"); var pccPsb = new PSB(Path.Combine(resPath, "c01c.txt.scn")); //var pccPsb = new PSB(Path.Combine(resPath, "ca01_l_body_1.psz.psb-pure.psb")); //var pccPsb = new PSB(Path.Combine(resPath, "ca01.psb")); //var psbuildPsb = new PSB(Path.Combine(resPath, "ca01_l_body_1.psz.psb-pure.psb.json.psbuild.psb")); //var psbuildPsb = new PSB(Path.Combine(resPath, "dx_れいなh1a1.psb.json-pure.psb")); var psbuildPsb = PsbCompiler.LoadPsbFromJsonFile(Path.Combine(resPath, "c01c.txt.json")); //foreach (var s in psbuildPsb.Strings) //{ // var pccStr = pccPsb.Strings.Find(ss => ss.Value == s.Value); // if (pccStr != null) // { // s.Index = pccStr.Index; // } // else // { // Console.WriteLine($"Can not find: {s}"); // } //} //psbuildPsb.UpdateIndexes(); //File.WriteAllBytes(Path.Combine(resPath, "ca01_build.psb"), psbuildPsb.Build()); CompareValue(pccPsb.Objects, psbuildPsb.Objects); //Console.WriteLine("============"); //CompareValue(psbuildPsb.Objects, pccPsb.Objects); }
public void TestCompareMmo() { var resPath = Path.Combine(Environment.CurrentDirectory, @"..\..\Res\mmo"); var path = Path.Combine(resPath, "template39.json"); var path2 = Path.Combine(resPath, "crash-temp.mmo"); var mmo1 = PsbCompiler.LoadPsbFromJsonFile(path); var allpart1 = FindPart((PsbList)mmo1.Objects["objectChildren"], "body_parts"); var mmo2 = new PSB(path2); var allpart2 = FindPart((PsbList)mmo2.Objects["objectChildren"], "body_parts"); //var p1 = mmo1.Objects.FindByPath( // "/objectChildren/[3]/children/[1]/layerChildren/[0]/children/[0]/frameList/[0]/content/coord"); //var pp = ((IPsbChild) p1).Parent.Parent.Parent.Parent["label"]; PsbDictionary FindPart(PsbList col, string label) { foreach (var c in col) { if (c is PsbDictionary d) { if (d["label"] is PsbString s && s.Value == label) { return(d); } } } return(null); } //PsBuildTest.CompareValue(allpart1, allpart2); PsBuildTest.CompareValue(mmo1.Objects["metaformat"].Children("data"), mmo2.Objects["metaformat"].Children("data")); }
public void TestFindByPath() { var resPath = Path.Combine(Environment.CurrentDirectory, @"..\..\Res"); var path = Path.Combine(resPath, "e-mote38_win-pure.psb.json"); PSB psb = PsbCompiler.LoadPsbFromJsonFile(path); var obj = psb.Objects.FindByPath("/object/all_parts/motion/タイムライン構造/bounds"); var type = obj.Type; var objs = psb.Objects.FindAllByPath("/object/*/motion/*"); foreach (var psbValue in objs) { if (psbValue is PsbDictionary dic) { var s = dic.GetName(); Console.WriteLine(s); } else { Console.WriteLine($"Not a PsbObject: {psbValue}"); } } }
private static void Compile(string s, ushort version, uint?key, PsbSpec?spec, bool canRename, bool canPackShell) { if (!File.Exists(s)) { //此処にいて何処にもいない キミの面影はいつも朧 : https://soundcloud.com/yuhyuhyuhxibbd2/parallel-utau return; } var name = Path.GetFileNameWithoutExtension(s); var ext = Path.GetExtension(s); Console.WriteLine($"Compiling {name} ..."); try { //var filename = name + (_key == null ? _noRename ? ".psb" : "-pure.psb" : "-impure.psb"); var filename = name + ".psb"; PsbCompiler.CompileToFile(s, filename, null, version, key, spec, canRename, canPackShell); } catch (Exception e) { Console.WriteLine($"Compile {name} failed.\r\n{e}"); } Console.WriteLine($"Compile {name} succeed."); }
public void TestCompileMenuPsb() { var resPath = Path.Combine(Environment.CurrentDirectory, @"..\..\Res"); var path = Path.Combine(resPath, "title.psb.json"); PsbCompiler.CompileToFile(path, path + ".psbuild.psb", null, 2); }
public void TestCompileWin() { var resPath = Path.Combine(Environment.CurrentDirectory, @"..\..\Res"); //var path = Path.Combine(resPath, "D愛子a_春服-pure.psb.json"); var path = Path.Combine(resPath, "dx_れいなh1a1.psb.json"); PsbCompiler.CompileToFile(path, path + ".psbuild.psb", null, 4, null, PsbSpec.win); }
public void TestCompileKrkr() { var resPath = Path.Combine(Environment.CurrentDirectory, @"..\..\Res"); //var path = Path.Combine(resPath, "澄怜a_裸.psb-pure.psb.json"); var path = Path.Combine(resPath, "e-mote38_KRKR-pure.psb.json"); PsbCompiler.CompileToFile(path, path + ".psbuild.psb", null, 4, null, PsbSpec.win); }
public void TestInplaceReplace() { FreeMount.Init(); var resPath = Path.Combine(Environment.CurrentDirectory, @"..\..\Res"); //var path = Path.Combine(resPath, "dx_ふかみ_駅員服.psb"); var path = Path.Combine(resPath, "dx_ふかみ_駅員服.lz4.psb"); var jsonPath = Path.Combine(resPath, "dx_ふかみ_駅員服.json"); PsbCompiler.InplaceReplaceToFile(path, jsonPath); }
public void TestCompileCommon() { var resPath = Path.Combine(Environment.CurrentDirectory, @"..\..\Res"); var path = Path.Combine(resPath, "emote396-a8l8.pure.json"); var path2 = Path.Combine(resPath, "emote396-a8l8.pure.psb"); var psb = PsbCompiler.LoadPsbFromJsonFile(path); var psb2 = new PSB(path2); //File.WriteAllBytes("396.psb", psb.Build()); PsbCompiler.CompileToFile(path, path + ".psbuild.psb", null, 4, null, PsbSpec.win); }
public void TestCompileEms() { var resPath = Path.Combine(Environment.CurrentDirectory, @"..\..\Res"); //var path = Path.Combine(resPath, "akira_guide-pure.psb.json"); var path = Path.Combine(resPath, "emote_test2-pure.psb.json"); var psb = PsbCompiler.LoadPsbFromJsonFile(path); psb.Platform = PsbSpec.ems; psb.Merge(); File.WriteAllBytes(path + ".build.psb", psb.Build()); //PsbCompiler.CompileToFile(path, path + ".psbuild.psb", null, 3, null, PsbSpec.ems); }
public void TestPathTravel() { var resPath = Path.Combine(Environment.CurrentDirectory, @"..\..\Res"); var path = Path.Combine(resPath, "e-mote38_win-pure.psb.json"); PSB psb = PsbCompiler.LoadPsbFromJsonFile(path); var targetPath = "/object/all_parts/motion/タイムライン構造/bounds"; var obj = (PsbDictionary)psb.Objects.FindByPath(targetPath); var objPath = obj.Path; Assert.AreEqual(targetPath, objPath); }
public void TestConvertCommon2Krkr() { var resPath = Path.Combine(Environment.CurrentDirectory, @"..\..\Res"); var path = Path.Combine(resPath, "akira_guide-pure.psb.json"); PSB psb = PsbCompiler.LoadPsbFromJsonFile(path); Common2KrkrConverter converter = new Common2KrkrConverter(); converter.Convert(psb); psb.Merge(); File.WriteAllBytes("emote_test_front.psb", psb.Build()); File.WriteAllText("emote_test_front.json", PsbDecompiler.Decompile(psb)); }
public void TestNaN() { var resPath = Path.Combine(Environment.CurrentDirectory, @"..\..\Res"); //var path = Path.Combine(resPath, "dx_れいなh1a1.psb"); //var psb = new PSB(path); var path = Path.Combine(resPath, "dx_れいなh1a1.psb.json"); var psb = PsbCompiler.LoadPsbFromJsonFile(path); var o = psb.Objects.FindByPath( "/object/head_parts/motion/頭部変形基礎/layer/[0]/children/[3]/children/[0]/children/[0]/children/[0]/children/[0]/children/[0]/children/[0]/children/[0]/children/[0]/children/[1]/children/[0]/children/[0]/children/[0]/children/[0]/frameList/[0]/content/coord") as PsbCollection; var num = o[0] as PsbNumber; var val = num.IntValue; var valNaN = num.FloatValue; }
public void TestPackMmo() { var resPath = Path.Combine(Environment.CurrentDirectory, @"..\..\Res"); var path = Path.Combine(resPath, "template39.json"); var path2 = Path.Combine(resPath, "template39-krkr.json"); var psb = PsbCompiler.LoadPsbFromJsonFile(path); //var psb2 = PsbCompiler.LoadPsbFromJsonFile(path2); //psb.Objects["objectChildren"] = psb2.Objects["object"]; //var collection = (PsbList)psb.Objects["objectChildren"]; //collection.RemoveAt(0); psb.Objects["metaformat"] = PsbNull.Null; psb.Merge(); psb.SaveAsMdfFile("temp.mmo"); }
public void TestFindPath() { var resPath = Path.Combine(Environment.CurrentDirectory, @"..\..\Res"); //var path = Path.Combine(resPath, "template39.json"); var path = Path.Combine(resPath, "mmo", "NekoCrash.json"); var mmo = PsbCompiler.LoadPsbFromJsonFile(path); var children = (PsbList)mmo.Objects["objectChildren"]; var source = (PsbList)mmo.Objects["sourceChildren"]; var obj = (PsbDictionary)children.FindByMmoPath( "all_parts/全体構造/■全体レイアウト/move_UD/move_LR/□下半身配置_le/胴体回転中心/全身調整/□頭部調整_le/act_sp"); var realPath = obj.Path; var mmoPath = obj.GetMmoPath(); //"FreeMote/all_parts/全体構造/■全体レイアウト/move_UD/move_LR/□下半身配置_le/胴体回転中心/全身調整/□頭部調整_le/act_sp" //obj = source.FindByMmoPath("face_eye_mabuta_l"); }
public void TestConvertAndBuildMmo() { var resPath = Path.Combine(Environment.CurrentDirectory, @"..\..\Res"); var path = Path.Combine(resPath, "dx_e-moteショコラ小ex制服b.psb.json"); var psb = PsbCompiler.LoadPsbFromJsonFile(path); psb.SwitchSpec(PsbSpec.krkr); psb.Merge(); //File.WriteAllBytes(Path.Combine(resPath, "neko-krkr.psb"), psb.Build()); //return; MmoBuilder mmoBuilder = new MmoBuilder(true); var psbMmo = mmoBuilder.Build(psb); psbMmo.Merge(); File.WriteAllBytes(Path.Combine(resPath, "mmo", "DxNekoCrash.mmo"), psbMmo.Build()); }
private static void Compile(string s) { var name = Path.GetFileNameWithoutExtension(s); var ext = Path.GetExtension(s); Console.WriteLine($"Compiling {name} ..."); try { PsbCompiler.CompileToFile(s, s + (_key == null ? "-pure.psb" : ".psb"), null, _version, _key, _platform); } catch (Exception e) { Console.WriteLine($"Compile {name} failed.\r\n{e}"); } Console.WriteLine($"Compile {name} succeed."); }
public void TestConvertKrkr2Win() { var resPath = Path.Combine(Environment.CurrentDirectory, @"..\..\Res"); //var path = Path.Combine(resPath, "澄怜a_裸.psb-pure.psb"); var path = Path.Combine(resPath, "澄怜a_裸.psb-pure.psb.json"); //var path = Path.Combine(resPath, "e-mote38_KRKR-pure.psb.json"); //var path = Path.Combine(resPath, "e-mote38_KRKR-pure.psb"); PSB psb = PsbCompiler.LoadPsbFromJsonFile(path); //PSB psb = new PSB(path); psb.SwitchSpec(PsbSpec.win); psb.Merge(); File.WriteAllBytes("emote_krkr2win.psb", psb.Build()); File.WriteAllText("emote_krkr2win.json", PsbDecompiler.Decompile(psb)); RL.ConvertToImageFile(psb.Resources.First().Data, "tex-in-psb.png", 4096, 4096, PsbImageFormat.Png, PsbPixelFormat.WinRGBA8); }
public void TestMmoGraft() { var resPath = Path.Combine(Environment.CurrentDirectory, @"..\..\Res"); var path = Path.Combine(resPath, "template39.json"); var path2 = Path.Combine(resPath, "template39-krkr.json"); var mmo = PsbCompiler.LoadPsbFromJsonFile(path); var psb = PsbCompiler.LoadPsbFromJsonFile(path2); MmoBuilder mmoBuilder = new MmoBuilder(true); var psbMmo = mmoBuilder.Build(psb); //mmo.Objects["objectChildren"] = psbMmo.Objects["objectChildren"]; var data = (PsbDictionary)mmo.Objects["metaformat"].Children("data"); var data2 = (PsbDictionary)psbMmo.Objects["metaformat"].Children("data"); data["bustControlDefinitionList"] = data2["bustControlDefinitionList"]; mmo.Merge(); mmo.SaveAsMdfFile(Path.Combine(resPath, "mmo", "temp.mmo")); }
public void TestBuildMmo() { var resPath = Path.Combine(Environment.CurrentDirectory, @"..\..\Res"); var path = Path.Combine(resPath, "e-mote3.0ショコラパジャマa中-krkr.json"); //var path = Path.Combine(resPath, "template39-krkr.json"); var psb = PsbCompiler.LoadPsbFromJsonFile(path); MmoBuilder mmoBuilder = new MmoBuilder(true); //Add custom menu paths mmoBuilder.CustomPartMenuPaths.Add("スカート", "胴体/スカート"); mmoBuilder.CustomPartMenuPaths.Add("前髪_le1", "頭部/前髪1"); mmoBuilder.CustomPartMenuPaths.Add("後髪_le4", "頭部/後髪4"); mmoBuilder.CustomPartMenuPaths.Add("後髪_le5", "頭部/後髪5"); var psbMmo = mmoBuilder.Build(psb); psbMmo.Merge(); File.WriteAllBytes(Path.Combine(resPath, "mmo", "NekoCrash.mmo"), psbMmo.Build()); }
// 적용 버튼 private void button2_Click(object sender, EventArgs e) { using (OpenFileDialog openFile = new OpenFileDialog()) { openFile.Title = "적용할 SCN 파일을 선택해주세요. (다중 선택 가능)"; openFile.DefaultExt = "scn"; openFile.Filter = "SCN 파일 (*.scn)|*.scn;"; openFile.Multiselect = true; if (openFile.ShowDialog() == DialogResult.OK) { progressBar1.Value = 0; progressBar1.Maximum = openFile.FileNames.Length; label2.Text = $"0/{progressBar1.Maximum}"; foreach (var file in openFile.FileNames) { string json = file.Substring(0, file.Length - 3) + "json"; Apply(json); progressBar1.PerformStep(); label2.Text = $"{progressBar1.Value}/{progressBar1.Maximum}"; } progressBar1.Value = 0; foreach (var file in openFile.FileNames) { string json = file.Substring(0, file.Length - 3) + "json"; PsbCompiler.CompileToFile(json, json, null, null, null, null); var purescn = file.Substring(0, file.Length - 3) + "pure.scn"; File.Delete(file); File.Move(purescn, file); progressBar1.PerformStep(); label2.Text = $"{progressBar1.Value}/{progressBar1.Maximum}"; } } } }
public void TestGraft() { var resPath = Path.Combine(Environment.CurrentDirectory, @"..\..\Res"); //var path = Path.Combine(resPath, "澄怜a_裸.psb-pure.psb.json"); var path = Path.Combine(resPath, "e-mote38_KRKR-pure.psb.json"); var path2 = Path.Combine(resPath, "e-mote38_win-pure.psb.json"); PSB psbKrkr = PsbCompiler.LoadPsbFromJsonFile(path); PSB psbWin = PsbCompiler.LoadPsbFromJsonFile(path2); psbWin.SwitchSpec(PsbSpec.krkr); //var metadata = (PsbDictionary)psbWin.Objects["metadata"]; //metadata["attrcomp"] = psbKrkr.Objects["metadata"].Children("attrcomp"); psbWin.Merge(); ////Graft var resKrkr = psbKrkr.CollectResources(false); var resWin = psbWin.CollectResources(false); var headWin = resWin.FirstOrDefault(r => r.Height == 186 && r.Width == 122); var headKrkr = resKrkr.FirstOrDefault(r => r.Height == 186 && r.Width == 122); if (headWin != null && headKrkr != null) { headWin.Resource.Data = headKrkr.Resource.Data; } //foreach (var resourceMetadata in resWin) //{ // var sameRes = resKrkr.FirstOrDefault(r => r.Height == resourceMetadata.Height && r.Width == resourceMetadata.Width); // if (sameRes != null) // { // Console.WriteLine($"{sameRes} {sameRes.Width}x{sameRes.Height} found."); // resourceMetadata.Resource.Data = sameRes.Resource.Data; // } //} psbWin.Merge(); File.WriteAllBytes("emote_win2krkr.psb", psbWin.Build()); //File.WriteAllText("emote_krkr2win.json", PsbDecompiler.Decompile(psb2)); }
/// <summary> /// Pack Archive PSB /// </summary> /// <param name="jsonPath">json path</param> /// <param name="key">crypt key</param> /// <param name="intersect">Only pack files which existed in info.psb.m</param> /// <param name="preferPacked">Prefer using PSB files rather than json files in source folder</param> /// <param name="enableParallel">parallel process</param> /// <param name="keyLen">key length</param> /// <param name="keepRaw">Do not try to compile json or pack MDF</param> public static void PackArchive(string jsonPath, string key, bool intersect, bool preferPacked, bool enableParallel = true, int keyLen = 131, bool keepRaw = false) { if (!File.Exists(jsonPath)) { return; } PSB infoPsb = PsbCompiler.LoadPsbFromJsonFile(jsonPath); if (infoPsb.Type != PsbType.ArchiveInfo) { Console.WriteLine("Json is not an ArchiveInfo PSB."); return; } var resx = PsbResourceJson.LoadByPsbJsonPath(jsonPath); if (!resx.Context.ContainsKey(Context_ArchiveSource) || resx.Context[Context_ArchiveSource] == null) { Console.WriteLine("ArchiveSource must be specified in resx.json Context."); return; } if (keyLen > 0) { resx.Context[Context_MdfKeyLength] = keyLen; } string infoKey = null; if (resx.Context[Context_MdfKey] is string mdfKey) { infoKey = mdfKey; } List <string> sourceDirs = null; if (resx.Context[Context_ArchiveSource] is string path) { sourceDirs = new List <string> { path }; } else if (resx.Context[Context_ArchiveSource] is IList paths) { sourceDirs = new List <string>(paths.Count); sourceDirs.AddRange(from object p in paths select p.ToString()); } else { Console.WriteLine("ArchiveSource incorrect."); return; } var baseDir = Path.GetDirectoryName(jsonPath); var files = new Dictionary <string, (string Path, ProcessMethod Method)>(); var suffix = ArchiveInfoGetSuffix(infoPsb); List <string> filter = null; if (intersect) //only collect files appeared in json { filter = ArchiveInfoCollectFiles(infoPsb, suffix).ToList(); } void CollectFiles(string targetDir) { if (!Directory.Exists(targetDir)) { return; } foreach (var f in Directory.EnumerateFiles(targetDir)) { if (f.EndsWith(".resx.json", true, CultureInfo.InvariantCulture)) { continue; } else if (f.EndsWith(".json", true, CultureInfo.InvariantCulture)) //json source, need compile { var name = Path.GetFileNameWithoutExtension(f); if (preferPacked && files.ContainsKey(name) && files[name].Method != ProcessMethod.Compile) //it's always right no matter set or replace { //ignore } else { if (intersect && filter != null && !filter.Contains(name)) //this file is not appeared in json { //ignore } else { files[name] = (f, keepRaw? ProcessMethod.None: ProcessMethod.Compile); } } } else { var name = Path.GetFileName(f); if (!preferPacked && files.ContainsKey(name) && files[name].Method == ProcessMethod.Compile) { //ignore } else { if (intersect && filter != null && !filter.Contains(name)) { //ignore } else { using var fs = File.OpenRead(f); if (!MdfFile.IsSignatureMdf(fs) && name.DefaultShellType() == "MDF") { files[name] = (f, keepRaw? ProcessMethod.None: ProcessMethod.EncodeMdf); } else { files[name] = (f, ProcessMethod.None); } } } } } } //Collect files Console.WriteLine("Collecting files ..."); foreach (var sourceDir in sourceDirs) { CollectFiles(Path.IsPathRooted(sourceDir) ? sourceDir : Path.Combine(baseDir, sourceDir)); } Console.WriteLine($"Packing {files.Count} files ..."); var bodyBinFileName = Path.GetFileName(jsonPath); var packageName = Path.GetFileNameWithoutExtension(bodyBinFileName); var coreName = ArchiveInfoGetPackageName(packageName); bodyBinFileName = string.IsNullOrEmpty(coreName) ? packageName + "_body.bin" : coreName + "_body.bin"; //using var mmFile = // MemoryMappedFile.CreateFromFile(bodyBinFileName, FileMode.Create, coreName, ); using var bodyFs = File.OpenWrite(bodyBinFileName); var fileInfoDic = new PsbDictionary(files.Count); var fmContext = FreeMount.CreateContext(resx.Context); //byte[] bodyBin = null; if (enableParallel) { var contents = new ConcurrentBag <(string Name, Stream Content)>(); Parallel.ForEach(files, (kv) => { var fileNameWithoutSuffix = ArchiveInfoGetFileNameRemoveSuffix(kv.Key, suffix); if (kv.Value.Method == ProcessMethod.None) { contents.Add((fileNameWithoutSuffix, File.OpenRead(kv.Value.Path))); return; } var mdfContext = new Dictionary <string, object>(resx.Context); var context = FreeMount.CreateContext(mdfContext); if (!string.IsNullOrEmpty(key)) { mdfContext[Context_MdfKey] = key + kv.Key; } else if (resx.Context[Context_MdfMtKey] is string mtKey) { mdfContext[Context_MdfKey] = mtKey + kv.Key; } else { mdfContext.Remove(Context_MdfKey); } mdfContext.Remove(Context_ArchiveSource); if (kv.Value.Method == ProcessMethod.EncodeMdf) { using var mmFs = MemoryMappedFile.CreateFromFile(kv.Value.Path, FileMode.Open); //using var fs = File.OpenRead(kv.Value.Path); contents.Add((fileNameWithoutSuffix, context.PackToShell(mmFs.CreateViewStream(), "MDF"))); //disposed later } else { var content = PsbCompiler.LoadPsbAndContextFromJsonFile(kv.Value.Path); var stream = content.Psb.ToStream(); var shellType = kv.Key.DefaultShellType(); //MARK: use shellType in filename, or use suffix in info? if (!string.IsNullOrEmpty(shellType)) { stream = context.PackToShell(stream, shellType); //disposed later } contents.Add((fileNameWithoutSuffix, stream)); } });
//private static PsbPixelFormat _pixelFormat = PsbPixelFormat.None; static void Main(string[] args) { Console.WriteLine("FreeMote PSB Compiler"); Console.WriteLine("by Ulysses, [email protected]"); FreeMount.Init(); Console.WriteLine($"{FreeMount.PluginsCount} Plugins Loaded."); InMemoryLoading = true; Console.WriteLine(); var app = new CommandLineApplication(); app.OptionsComparison = StringComparison.OrdinalIgnoreCase; //help app.HelpOption(); app.ExtendedHelpText = PrintHelp(); //options var optVer = app.Option <ushort>("-v|--ver <VER>", "Set PSB version [2,4]. Default=3", CommandOptionType.SingleValue); var optKey = app.Option <uint>("-k|--key <KEY>", "Set PSB key (uint, dec)", CommandOptionType.SingleValue); var optSpec = app.Option <PsbSpec>("-p|--spec <SPEC>", "Set PSB platform (krkr/common/win/ems)", CommandOptionType.SingleValue); var optNoRename = app.Option("-no-rename", "Prevent output file renaming, may overwrite your original PSB files", CommandOptionType.NoValue); var optNoShell = app.Option("-no-shell", "Prevent shell packing (compression)", CommandOptionType.NoValue); var optDouble = app.Option("-double|--json-double", "(Json) Use double numbers only (no float)", CommandOptionType.NoValue, true); //var optOutputPath = // app.Option<string>("-o|--output", "(TODO:)Set output directory or file name.", CommandOptionType.SingleValue); //TODO: If set dir, ok; if set filename, only works for the first //args var argPath = app.Argument("Files", "File paths", multipleValues: true); //command: link app.Command("link", linkCmd => { //help linkCmd.Description = "Link textures into an external texture PSB"; linkCmd.HelpOption(); linkCmd.ExtendedHelpText = @" Example: PsBuild link -o Order sample.psb tex000.png tex001.bmp "; //options var optOrder = linkCmd.Option <PsbLinkOrderBy>("-o|--order <ORDER>", "Set texture link order (Name/Order/Convention). Default=Name", CommandOptionType.SingleValue); //args var argPsbPath = linkCmd.Argument("PSB", "PSB Path").IsRequired().Accepts(v => v.ExistingFile()); var argTexPaths = linkCmd.Argument("Textures", "Texture Paths", true).IsRequired(); linkCmd.OnExecute(() => { var order = optOrder.HasValue() ? optOrder.ParsedValue : PsbLinkOrderBy.Name; var psbPath = argPsbPath.Value; var texPaths = argTexPaths.Values; Link(psbPath, texPaths, order); }); }); //command: port app.Command("port", portCmd => { //help portCmd.Description = "Re-compile a PSB to another platform"; portCmd.HelpOption(); portCmd.ExtendedHelpText = @" Example: PsBuild port -p win sample.psb "; //options var optPortSpec = portCmd.Option <PsbSpec>("-p|--spec <SPEC>", "Target PSB platform (krkr/common/win/ems)", CommandOptionType.SingleValue).IsRequired(); //args var argPsbPath = portCmd.Argument("PSB", "PSB Path", multipleValues: true).IsRequired(); portCmd.OnExecute(() => { var portSpec = optPortSpec.ParsedValue; var psbPaths = argPsbPath.Values; foreach (var s in psbPaths) { if (File.Exists(s)) { Port(s, portSpec); } } }); }); //info-psb app.Command("info-psb", archiveCmd => { //help archiveCmd.Description = "Pack files to info.psb.m & body.bin (FreeMote.Plugins required)"; archiveCmd.HelpOption(); archiveCmd.ExtendedHelpText = @" Example: PsBuild info-psb sample_info.psb.m.json (Key specified in resx.json) PsBuild info-psb -k 1234567890ab -l 131 sample_info.psb.m.json (Must keep every filename correct) Hint: Always keep file names correct. A file name in source folder must match a name kept in .m.json If there are both `.m` and `.m.json` in the source folder, `.json` will be used (unless using `--packed`). If you don't have enough RAM to keep the whole output, use `-1by1` and wait patiently. "; //options //var optMdfSeed = archiveCmd.Option("-s|--seed <SEED>", // "Set complete seed (Key+FileName)", // CommandOptionType.SingleValue); var optIntersect = archiveCmd.Option("-i|--intersect", "Only pack files which existed in info.psb.m", CommandOptionType.NoValue); var optPacked = archiveCmd.Option("-p|--packed", "Prefer using PSB files rather than json files in source folder", CommandOptionType.NoValue); var optMdfKey = archiveCmd.Option("-k|--key <KEY>", "Set key (get file name from input path)", CommandOptionType.SingleValue); var optMdfKeyLen = archiveCmd.Option <int>("-l|--length <LEN>", "Set key length. Default=131", CommandOptionType.SingleValue); var optInfoOom = archiveCmd.Option("-1by1|--enumerate", "Disable parallel processing (can be slow but save a lot memory)", CommandOptionType.NoValue); var optInfoRaw = archiveCmd.Option("-raw|--raw", "Keep all sources raw (don't compile jsons or pack MDF shell)", CommandOptionType.NoValue); //args var argPsbPaths = archiveCmd.Argument("PSB", "Archive Info PSB .json paths", true); archiveCmd.OnExecute(() => { bool intersect = optIntersect.HasValue(); bool preferPacked = optPacked.HasValue(); bool enableParallel = FastMode; bool keepRaw = false; if (optInfoOom.HasValue()) { enableParallel = false; } if (optInfoRaw.HasValue()) { keepRaw = true; } string key = optMdfKey.HasValue() ? optMdfKey.Value() : null; //string seed = optMdfSeed.HasValue() ? optMdfSeed.Value() : null; int keyLen = optMdfKeyLen.HasValue() ? optMdfKeyLen.ParsedValue : 131; Stopwatch sw = Stopwatch.StartNew(); foreach (var s in argPsbPaths.Values) { PackArchive(s, key, intersect, preferPacked, enableParallel, keyLen, keepRaw); } sw.Stop(); Console.WriteLine($"Process time: {sw.Elapsed:g}"); }); }); //command: replace app.Command("replace", replaceCmd => { //help replaceCmd.Description = "In-place Replace the images in PSB"; replaceCmd.HelpOption(); replaceCmd.ExtendedHelpText = @" Example: PsBuild replace sample.psb sample.json Hint: Only works with textures not compressed (RGBA8, RGBA4444) pure PSBs. "; var argPsbPath = replaceCmd.Argument("PSB", "PSB path", false); var argJsonPath = replaceCmd.Argument("Json", "PSB Json path", false); replaceCmd.OnExecute(() => { if (!File.Exists(argPsbPath.Value) || !File.Exists(argJsonPath.Value)) { Console.WriteLine("File not exists."); return; } var output = PsbCompiler.InplaceReplaceToFile(argPsbPath.Value, argJsonPath.Value); Console.WriteLine($"In-place Replace Output: {output}"); }); }); app.OnExecute(() => { if (optDouble.HasValue()) { JsonUseDoubleOnly = true; } ushort?ver = optVer.HasValue() ? optVer.ParsedValue : (ushort?)null; uint?key = optKey.HasValue() ? optKey.ParsedValue : (uint?)null; PsbSpec?spec = optSpec.HasValue() ? optSpec.ParsedValue : (PsbSpec?)null; var canRename = !optNoRename.HasValue(); var canPack = !optNoShell.HasValue(); foreach (var file in argPath.Values) { Compile(file, ver, key, spec, canRename, canPack); } }); if (args.Length == 0) { app.ShowHelp(); return; } app.Execute(args); Console.WriteLine("Done."); }
/// <summary> /// Pack Archive PSB /// </summary> /// <param name="jsonPath">json path</param> /// <param name="key">crypt key</param> /// <param name="intersect">Only pack files which existed in info.psb.m</param> /// <param name="preferPacked">Prefer using PSB files rather than json files in source folder</param> /// <param name="enableParallel">parallel process</param> /// <param name="keyLen">key length</param> public static void PackArchive(string jsonPath, string key, bool intersect, bool preferPacked, bool enableParallel = true, int keyLen = 131) { if (!File.Exists(jsonPath)) { return; } PSB infoPsb = PsbCompiler.LoadPsbFromJsonFile(jsonPath); if (infoPsb.Type != PsbType.ArchiveInfo) { Console.WriteLine("Json is not an ArchiveInfo PSB."); return; } var resx = PsbResourceJson.LoadByPsbJsonPath(jsonPath); if (!resx.Context.ContainsKey(Context_ArchiveSource) || resx.Context[Context_ArchiveSource] == null) { Console.WriteLine("ArchiveSource must be specified in resx.json Context."); return; } if (keyLen > 0) { resx.Context[Context_MdfKeyLength] = keyLen; } string infoKey = null; if (resx.Context[Context_MdfKey] is string mdfKey) { infoKey = mdfKey; } List <string> sourceDirs = null; if (resx.Context[Context_ArchiveSource] is string path) { sourceDirs = new List <string> { path }; } else if (resx.Context[Context_ArchiveSource] is IList paths) { sourceDirs = new List <string>(paths.Count); sourceDirs.AddRange(from object p in paths select p.ToString()); } else { Console.WriteLine("ArchiveSource incorrect."); return; } var baseDir = Path.GetDirectoryName(jsonPath); var files = new Dictionary <string, (string Path, ProcessMethod Method)>(); var suffix = ArchiveInfoPsbGetSuffix(infoPsb); List <string> filter = null; if (intersect) { filter = ArchiveInfoPsbCollectFiles(infoPsb, suffix); } void CollectFiles(string targetDir) { if (!Directory.Exists(targetDir)) { return; } foreach (var f in Directory.EnumerateFiles(targetDir)) { if (f.EndsWith(".resx.json", true, CultureInfo.InvariantCulture)) { continue; } else if (f.EndsWith(".json", true, CultureInfo.InvariantCulture)) { var name = Path.GetFileNameWithoutExtension(f); if (preferPacked && files.ContainsKey(name) && files[name].Method != ProcessMethod.Compile) { //ignore } else { if (intersect && filter != null && !filter.Contains(name)) { //ignore } else { files[name] = (f, ProcessMethod.Compile); } } } else { var name = Path.GetFileName(f); if (!preferPacked && files.ContainsKey(name) && files[name].Method == ProcessMethod.Compile) { //ignore } else { if (intersect && filter != null && !filter.Contains(name)) { //ignore } else { using var fs = File.OpenRead(f); if (!MdfFile.IsSignatureMdf(fs) && name.DefaultShellType() == "MDF") { files[name] = (f, ProcessMethod.EncodeMdf); } else { files[name] = (f, ProcessMethod.None); } } } } } } //Collect files foreach (var sourceDir in sourceDirs) { CollectFiles(Path.IsPathRooted(sourceDir) ? sourceDir : Path.Combine(baseDir, sourceDir)); } var fileName = Path.GetFileName(jsonPath); var packageName = Path.GetFileNameWithoutExtension(fileName); var coreName = PsbExtension.ArchiveInfoGetPackageName(packageName); fileName = string.IsNullOrEmpty(coreName) ? packageName + "_body.bin" : coreName + "_body.bin"; var fileInfoDic = new PsbDictionary(files.Count); var fmContext = FreeMount.CreateContext(resx.Context); byte[] bodyBin = null; if (enableParallel) { var contents = new ConcurrentBag <(string Name, Stream Content)>(); Parallel.ForEach(files, (kv) => { var fileNameWithoutSuffix = ArchiveInfoPsbGetFileName(kv.Key, suffix); if (kv.Value.Method == ProcessMethod.None) { contents.Add((fileNameWithoutSuffix, File.OpenRead(kv.Value.Path))); return; } var mdfContext = new Dictionary <string, object>(resx.Context); var context = FreeMount.CreateContext(mdfContext); if (!string.IsNullOrEmpty(key)) { mdfContext[Context_MdfKey] = key + fileNameWithoutSuffix + suffix; } else if (resx.Context[Context_MdfMtKey] is string mtKey) { mdfContext[Context_MdfKey] = mtKey + fileNameWithoutSuffix + suffix; } else { mdfContext.Remove(Context_MdfKey); } mdfContext.Remove(Context_ArchiveSource); if (kv.Value.Method == ProcessMethod.EncodeMdf) { contents.Add((fileNameWithoutSuffix, context.PackToShell( File.OpenRead(kv.Value.Path), "MDF"))); } else { var content = PsbCompiler.LoadPsbAndContextFromJsonFile(kv.Value.Path); var outputMdf = context.PackToShell(content.Psb.ToStream(), "MDF"); contents.Add((fileNameWithoutSuffix, outputMdf)); } });