/// <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)); } });
/// <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)); } });