/// <summary> /// Link /// </summary> /// <param name="psb"></param> /// <param name="resx">advanced resource json(resx.jon)</param> /// <param name="baseDir"></param> internal static void Link(this PSB psb, PsbResourceJson resx, string baseDir) { var resList = psb.CollectResources(); foreach (var resxResource in resx.Resources) { //Scan for Resource var resMd = resList.FirstOrDefault(r => resxResource.Key == $"{r.Part}{PsbResCollector.ResourceNameDelimiter}{r.Name}"); if (resMd == null && psb.Type == PsbType.Pimg) { resMd = resList.FirstOrDefault(r => resxResource.Key == Path.GetFileNameWithoutExtension(r.Name)); } if (resMd == null && uint.TryParse(resxResource.Key, out uint rid)) { resMd = resList.FirstOrDefault(r => r.Index == rid); } if (resMd == null) { Console.WriteLine($"[WARN]{resxResource.Key} is not used."); continue; } var fullPath = Path.IsPathRooted(resxResource.Value) ? resxResource.Value : Path.Combine(baseDir ?? "", resxResource.Value.Replace('/', '\\')); byte[] data = LoadImageBytes(fullPath, resMd); resMd.Resource.Data = data; } }
/// <summary> /// Compile to file /// </summary> /// <param name="inputPath">Json file path</param> /// <param name="outputPath">Output path</param> /// <param name="inputResPath">Special resource Json file path</param> /// <param name="version">PSB version</param> /// <param name="cryptKey">CryptKey, if you need to use it outside FreeMote</param> /// <param name="platform">PSB Platform</param> /// <param name="renameOutput">If true, the output file extension is renamed by type</param> /// <param name="keepShell">If true, the output can be compressed PSB shell type (if specified)</param> /// <returns>The actual output path</returns> public static string CompileToFile(string inputPath, string outputPath, string inputResPath = null, ushort?version = null, uint?cryptKey = null, PsbSpec?platform = null, bool renameOutput = true, bool keepShell = true) { if (string.IsNullOrEmpty(inputPath)) { throw new FileNotFoundException("Can not find input json file."); } if (string.IsNullOrEmpty(inputResPath) || !File.Exists(inputResPath)) { inputResPath = Path.ChangeExtension(inputPath, ".resx.json"); if (!File.Exists(inputResPath)) { inputResPath = Path.ChangeExtension(inputPath, ".res.json"); } } string resJson = null; string baseDir = Path.GetDirectoryName(inputPath); if (File.Exists(inputResPath)) { resJson = File.ReadAllText(inputResPath); baseDir = Path.GetDirectoryName(inputResPath); if (renameOutput) //start renaming { if (resJson.Trim().StartsWith("{")) { PsbResourceJson resx = JsonConvert.DeserializeObject <PsbResourceJson>(resJson); bool pure = cryptKey == null && resx.CryptKey == null; string ext = pure ? ".pure" : ".impure"; ext += resx.PsbType.HasValue ? resx.PsbType.Value.DefaultExtension() : ".psb"; if (resx.Context != null && resx.Context.ContainsKey(Consts.Context_PsbShellType) && keepShell) { var shellType = resx.Context[Consts.Context_PsbShellType] as string; if (!string.IsNullOrEmpty(shellType) && shellType.ToUpperInvariant() != "PSB") { ext += $".{shellType.ToLowerInvariant()}"; } } var newPath = Path.ChangeExtension(outputPath, ext); if (!string.IsNullOrWhiteSpace(newPath)) { outputPath = newPath; } } } } var result = Compile(File.ReadAllText(inputPath), resJson, baseDir, version, cryptKey, platform, keepShell); // ReSharper disable once AssignNullToNotNullAttribute File.WriteAllBytes(outputPath, result); return(outputPath); }
/// <summary> /// Compile Json to PSB /// </summary> /// <param name="inputJson">Json text</param> /// <param name="inputResJson">Resource Json text</param> /// <param name="baseDir">If resource Json uses relative paths (usually it does), specify the base dir</param> /// <param name="version">PSB version</param> /// <param name="cryptKey">CryptKey, if you need to use it outside FreeMote</param> /// <param name="spec">PSB Platform</param> /// <returns></returns> public static byte[] Compile(string inputJson, string inputResJson, string baseDir = null, ushort?version = null, uint?cryptKey = null, PsbSpec?spec = null) { //Parse PSB psb = Parse(inputJson, version ?? 3); //Link if (!string.IsNullOrWhiteSpace(inputResJson)) { if (inputResJson.Trim().StartsWith("{")) //resx.json { PsbResourceJson resx = JsonConvert.DeserializeObject <PsbResourceJson>(inputResJson); if (resx.PsbType != null) { psb.Type = resx.PsbType.Value; } if (resx.PsbVersion != null && version == null) { psb.Header.Version = resx.PsbVersion.Value; } if (resx.Platform != null && spec == null) { spec = resx.Platform; } if (resx.CryptKey != null & cryptKey == null) { cryptKey = resx.CryptKey; } if (resx.ExternalTextures) { Console.WriteLine("[INFO] External Texture mode ON, no resource will be compiled."); } else { psb.Link(resx, baseDir); } } else { List <string> resources = JsonConvert.DeserializeObject <List <string> >(inputResJson); psb.Link(resources, baseDir); } } //Build psb.Merge(); if (spec != null && spec != psb.Platform) { psb.SwitchSpec(spec.Value, spec.Value.DefaultPixelFormat()); psb.Merge(); } var bytes = psb.Build(); //Convert return(cryptKey != null?PsbFile.EncodeToBytes(cryptKey.Value, bytes, EncodeMode.Encrypt, EncodePosition.Auto) : bytes); }
/// <summary> /// Link /// </summary> /// <param name="psb"></param> /// <param name="resx">advanced resource json(resx.jon)</param> /// <param name="baseDir"></param> internal static void Link(this PSB psb, PsbResourceJson resx, string baseDir) { if (resx.Resources == null) { return; } var context = FreeMount.CreateContext(resx.Context); if (psb.TypeHandler != null) { psb.TypeHandler.Link(psb, context, resx.Resources, baseDir); } else { PsbResHelper.LinkImages(psb, context, resx.Resources, baseDir); } }
internal static void OutputResources(PSB psb, FreeMountContext context, string filePath, PsbExtractOption extractOption = PsbExtractOption.Original, PsbImageFormat extractFormat = PsbImageFormat.png, bool useResx = true) { var name = Path.GetFileNameWithoutExtension(filePath); var dirPath = Path.Combine(Path.GetDirectoryName(filePath), name); PsbResourceJson resx = new PsbResourceJson(psb, context.Context); if (File.Exists(dirPath)) { name += "-resources"; dirPath += "-resources"; } if (!Directory.Exists(dirPath)) //ensure there is no file with same name! { if (psb.Resources.Count != 0) { Directory.CreateDirectory(dirPath); } } var resDictionary = psb.TypeHandler.OutputResources(psb, context, name, dirPath, extractOption); //MARK: We use `.resx.json` to distinguish from psbtools' `.res.json` if (useResx) { resx.Resources = resDictionary; resx.Context = context.Context; File.WriteAllText(Path.ChangeExtension(filePath, ".resx.json"), JsonConvert.SerializeObject(resx, Formatting.Indented)); } else { File.WriteAllText(Path.ChangeExtension(filePath, ".res.json"), JsonConvert.SerializeObject(resDictionary.Values.ToList(), Formatting.Indented)); } }
/// <summary> /// Load PSB From Json file /// </summary> /// <param name="inputPath">Json file path</param> /// <param name="inputResPath">Resource Json file</param> /// <param name="version">PSB version</param> /// <returns></returns> public static PSB LoadPsbFromJsonFile(string inputPath, string inputResPath = null, ushort?version = null) { if (string.IsNullOrEmpty(inputPath)) { throw new FileNotFoundException("Can not find input json file."); } if (string.IsNullOrEmpty(inputResPath) || !File.Exists(inputResPath)) { inputResPath = Path.ChangeExtension(inputPath, ".resx.json"); if (!File.Exists(inputResPath)) { inputResPath = Path.ChangeExtension(inputPath, ".res.json"); } } string inputResJson = null; string baseDir = Path.GetDirectoryName(inputPath); if (File.Exists(inputResPath)) { inputResJson = File.ReadAllText(inputResPath); baseDir = Path.GetDirectoryName(inputPath); } //Parse PSB psb = Parse(File.ReadAllText(inputPath), version ?? 3); //Link if (!string.IsNullOrWhiteSpace(inputResJson)) { if (inputResJson.Trim().StartsWith("{")) //resx.json { PsbResourceJson resx = JsonConvert.DeserializeObject <PsbResourceJson>(inputResJson); if (resx.PsbType != null) { psb.Type = resx.PsbType.Value; } if (resx.PsbVersion != null && version == null) { psb.Header.Version = resx.PsbVersion.Value; } if (resx.ExternalTextures) { Console.WriteLine("[INFO] External Texture mode ON, no resource will be compiled."); } else { psb.Link(resx, baseDir); } if (resx.Platform != null) { psb.SwitchSpec(resx.Platform.Value, resx.Platform.Value.DefaultPixelFormat()); } } else { List <string> resources = JsonConvert.DeserializeObject <List <string> >(inputResJson); psb.Link(resources, baseDir); } } if (version != null) { psb.Header.Version = version.Value; } psb.Merge(); return(psb); }
internal static void OutputResources(PSB psb, FreeMountContext context, string filePath, PsbExtractOption extractOption = PsbExtractOption.Original, PsbImageFormat extractFormat = PsbImageFormat.png, bool useResx = true) { var name = Path.GetFileNameWithoutExtension(filePath); var dirPath = Path.Combine(Path.GetDirectoryName(filePath), name); PsbResourceJson resx = new PsbResourceJson(psb, context.Context); if (File.Exists(dirPath)) { name += "-resources"; dirPath += "-resources"; } var extraDir = Path.Combine(dirPath, Consts.ExtraResourceFolderName); if (!Directory.Exists(dirPath)) //ensure there is no file with same name! { if (psb.Resources.Count != 0) { Directory.CreateDirectory(dirPath); } } if (psb.ExtraResources.Count > 0) { var extraDic = PsbResHelper.OutputExtraResources(psb, context, name, extraDir, out var flattenArrays, extractOption); resx.ExtraResources = extraDic; if (flattenArrays != null && flattenArrays.Count > 0) { resx.ExtraFlattenArrays = flattenArrays; } } var resDictionary = psb.TypeHandler.OutputResources(psb, context, name, dirPath, extractOption); //MARK: We use `.resx.json` to distinguish from psbtools' `.res.json` if (useResx) { resx.Resources = resDictionary; resx.Context = context.Context; string json; if (Consts.JsonArrayCollapse) { json = ArrayCollapseJsonTextWriter.SerializeObject(resx); } else { json = JsonConvert.SerializeObject(resx, Formatting.Indented); } File.WriteAllText(ChangeExtensionForOutputJson(filePath, ".resx.json"), json); } else { if (psb.ExtraResources.Count > 0) { throw new NotSupportedException("PSBv4 cannot use legacy res.json format."); } File.WriteAllText(ChangeExtensionForOutputJson(filePath, ".res.json"), JsonConvert.SerializeObject(resDictionary.Values.ToList(), Formatting.Indented)); } }
internal static void OutputResources(PSB psb, FreeMountContext context, string filePath, PsbImageOption imageOption = PsbImageOption.Original, PsbImageFormat extractFormat = PsbImageFormat.Png, bool useResx = true) { var name = Path.GetFileNameWithoutExtension(filePath); var dirPath = Path.Combine(Path.GetDirectoryName(filePath), name); var resources = psb.CollectResources(); PsbResourceJson resx = new PsbResourceJson(psb, context.Context); if (File.Exists(dirPath)) { name += "-resources"; dirPath += "-resources"; } if (!Directory.Exists(dirPath)) //ensure there is no file with same name! { if (psb.Resources.Count != 0 || resources.Count != 0) { Directory.CreateDirectory(dirPath); } } Dictionary <string, string> resDictionary = new Dictionary <string, string>(); if (imageOption == PsbImageOption.Original) { for (int i = 0; i < psb.Resources.Count; i++) { var relativePath = psb.Resources[i].Index == null ? $"#{i}.bin" : $"{psb.Resources[i].Index}.bin"; File.WriteAllBytes( Path.Combine(dirPath, relativePath), psb.Resources[i].Data); resDictionary.Add(Path.GetFileNameWithoutExtension(relativePath), $"{name}/{relativePath}"); } } else { for (int i = 0; i < resources.Count; i++) { var resource = resources[i]; //Generate Friendly Name string relativePath = resource.GetFriendlyName(psb.Type); switch (imageOption) { case PsbImageOption.Extract: ImageFormat pixelFormat; switch (extractFormat) { case PsbImageFormat.Png: relativePath += ".png"; pixelFormat = ImageFormat.Png; break; default: relativePath += ".bmp"; pixelFormat = ImageFormat.Bmp; break; } relativePath = CheckPath(relativePath, i); if (resource.Compress == PsbCompressType.RL) { RL.DecompressToImageFile(resource.Data, Path.Combine(dirPath, relativePath), resource.Height, resource.Width, extractFormat, resource.PixelFormat); } else if (resource.Compress == PsbCompressType.Tlg || resource.Compress == PsbCompressType.ByName) { var bmp = context.ResourceToBitmap(resource.Compress == PsbCompressType.Tlg ? ".tlg" : Path.GetExtension(resource.Name), resource.Data); if (bmp == null) { if (resource.Compress == PsbCompressType.Tlg) //Fallback to managed TLG decoder { using (var ms = new MemoryStream(resource.Data)) using (var br = new BinaryReader(ms)) { bmp = new TlgImageConverter().Read(br); bmp.Save(Path.Combine(dirPath, relativePath), pixelFormat); bmp.Dispose(); } } relativePath = Path.ChangeExtension(relativePath, Path.GetExtension(resource.Name)); File.WriteAllBytes(Path.Combine(dirPath, relativePath), resource.Data); } else { bmp.Save(Path.Combine(dirPath, relativePath), pixelFormat); bmp.Dispose(); } } //else if (resource.Compress == PsbCompressType.ByName) //{ // relativePath = Path.ChangeExtension(relativePath, Path.GetExtension(resource.Name)); // File.WriteAllBytes(Path.Combine(dirPath, relativePath), resource.Data); //} else { RL.ConvertToImageFile(resource.Data, Path.Combine(dirPath, relativePath), resource.Height, resource.Width, extractFormat, resource.PixelFormat); } break; case PsbImageOption.Original: if (resources[i].Compress == PsbCompressType.RL) { relativePath += ".rl"; relativePath = CheckPath(relativePath, i); File.WriteAllBytes(Path.Combine(dirPath, relativePath), resource.Data); } else if (resource.Compress == PsbCompressType.Tlg) { relativePath += ".tlg"; relativePath = CheckPath(relativePath, i); File.WriteAllBytes(Path.Combine(dirPath, relativePath), resource.Data); } else { relativePath += ".raw"; relativePath = CheckPath(relativePath, i); File.WriteAllBytes(Path.Combine(dirPath, relativePath), resource.Data); } break; case PsbImageOption.Decompress: relativePath += ".raw"; relativePath = CheckPath(relativePath, i); File.WriteAllBytes(Path.Combine(dirPath, relativePath), resources[i].Compress == PsbCompressType.RL ? RL.Decompress(resource.Data) : resource.Data); break; case PsbImageOption.Compress: relativePath += ".rl"; relativePath = CheckPath(relativePath, i); File.WriteAllBytes(Path.Combine(dirPath, relativePath), resources[i].Compress != PsbCompressType.RL ? RL.Compress(resource.Data) : resource.Data); break; default: throw new ArgumentOutOfRangeException(nameof(imageOption), imageOption, null); } try { resDictionary.Add(i.ToString(), $"{name}/{relativePath}"); } catch (ArgumentException e) { throw new PsbBadFormatException(PsbBadFormatReason.Resources, "There are resources with same names! Try Raw export mode.", e); } } } //MARK: We use `.resx.json` to distinguish from psbtools' `.res.json` if (useResx) { resx.Resources = resDictionary; resx.Context = context.Context; File.WriteAllText(Path.ChangeExtension(filePath, ".resx.json"), JsonConvert.SerializeObject(resx, Formatting.Indented)); } else { File.WriteAllText(Path.ChangeExtension(filePath, ".res.json"), JsonConvert.SerializeObject(resDictionary.Values.ToList(), Formatting.Indented)); } string CheckPath(string rPath, int id) { var k = Path.GetFileNameWithoutExtension(rPath); if (resDictionary.ContainsKey(k)) { return($"{id}{Path.GetExtension(rPath)}"); } return(rPath); } }
/// <summary> /// Decompile to files /// </summary> /// <param name="inputPath">PSB file path</param> /// <param name="imageOption">whether to extract image to common format</param> /// <param name="extractFormat">if extract, what format do you want</param> /// <param name="useResx">if false, use array-based resource json (legacy)</param> public static void DecompileToFile(string inputPath, PsbImageOption imageOption = PsbImageOption.Original, PsbImageFormat extractFormat = PsbImageFormat.Png, bool useResx = true) { var name = Path.GetFileNameWithoutExtension(inputPath); var dirPath = Path.Combine(Path.GetDirectoryName(inputPath), name); File.WriteAllText(inputPath + ".json", Decompile(inputPath, out var psb)); var resources = psb.CollectResources(); PsbResourceJson resx = new PsbResourceJson { PsbVersion = psb.Header.Version, PsbType = psb.Type, Platform = psb.Platform, ExternalTextures = psb.Type == PsbType.Motion && psb.Resources.Count <= 0 }; if (!Directory.Exists(dirPath)) //ensure no file with same name! { Directory.CreateDirectory(dirPath); } Dictionary <string, string> resDictionary = new Dictionary <string, string>(); if (imageOption == PsbImageOption.Original) { for (int i = 0; i < psb.Resources.Count; i++) { var relativePath = psb.Resources[i].Index == null ? $"#{i}.bin" : $"{psb.Resources[i].Index}.bin"; File.WriteAllBytes( Path.Combine(dirPath, relativePath), psb.Resources[i].Data); resDictionary.Add(Path.GetFileNameWithoutExtension(relativePath), $"{name}/{relativePath}"); } } else { for (int i = 0; i < resources.Count; i++) { var resource = resources[i]; //Generate Friendly Name string relativePath; if (psb.Type == PsbType.Pimg && !string.IsNullOrWhiteSpace(resource.Name)) { relativePath = Path.GetFileNameWithoutExtension(resource.Name); } else if (string.IsNullOrWhiteSpace(resource.Name) || string.IsNullOrWhiteSpace(resource.Part)) { relativePath = resource.Index.ToString(); } else { relativePath = $"{resource.Part}{PsbResCollector.ResourceNameDelimiter}{resource.Name}"; } switch (imageOption) { case PsbImageOption.Extract: //var pixelFormat = resource.Spec.DefaultPixelFormat(); //MARK: PixelFormat should refer `type` switch (extractFormat) { case PsbImageFormat.Png: relativePath += ".png"; if (resource.Compress == PsbCompressType.RL) { RL.UncompressToImageFile(resource.Data, Path.Combine(dirPath, relativePath), resource.Height, resource.Width, PsbImageFormat.Png, resource.PixelFormat); } else if (resource.Compress == PsbCompressType.Tlg || resource.Compress == PsbCompressType.ByName && resource.Name.EndsWith(".tlg", true, null)) { TlgImageConverter converter = new TlgImageConverter(); using (var ms = new MemoryStream(resource.Data)) { BinaryReader br = new BinaryReader(ms); converter.Read(br).Save(Path.Combine(dirPath, relativePath), ImageFormat.Png); } //WARN: tlg is kept and recorded in resource json for compile relativePath = Path.ChangeExtension(relativePath, ".tlg"); File.WriteAllBytes(Path.Combine(dirPath, relativePath), resource.Data); } else if (resource.Compress == PsbCompressType.ByName) { relativePath = Path.ChangeExtension(relativePath, Path.GetExtension(resource.Name)); File.WriteAllBytes(Path.Combine(dirPath, relativePath), resource.Data); } else { RL.ConvertToImageFile(resource.Data, Path.Combine(dirPath, relativePath), resource.Height, resource.Width, extractFormat, resource.PixelFormat); } break; default: relativePath += ".bmp"; if (resource.Compress == PsbCompressType.RL) { RL.UncompressToImageFile(resource.Data, Path.Combine(dirPath, relativePath), resource.Height, resource.Width, PsbImageFormat.Bmp, resource.PixelFormat); } else if (resource.Compress == PsbCompressType.Tlg || resource.Compress == PsbCompressType.ByName && resource.Name.EndsWith(".tlg", true, null)) { TlgImageConverter converter = new TlgImageConverter(); using (var ms = new MemoryStream(resource.Data)) { BinaryReader br = new BinaryReader(ms); converter.Read(br).Save(Path.Combine(dirPath, relativePath), ImageFormat.Bmp); } //WARN: tlg is kept and recorded in resource json for compile relativePath = Path.ChangeExtension(relativePath, ".tlg"); File.WriteAllBytes(Path.Combine(dirPath, relativePath), resource.Data); } else if (resource.Compress == PsbCompressType.ByName) { relativePath = Path.ChangeExtension(relativePath, Path.GetExtension(resource.Name)); File.WriteAllBytes(Path.Combine(dirPath, relativePath), resource.Data); } else { RL.ConvertToImageFile(resource.Data, Path.Combine(dirPath, relativePath), resource.Height, resource.Width, extractFormat, resource.PixelFormat); } break; } break; case PsbImageOption.Original: if (resources[i].Compress == PsbCompressType.RL) { relativePath += ".rl"; File.WriteAllBytes(Path.Combine(dirPath, relativePath), resource.Data); } else if (resource.Compress == PsbCompressType.Tlg) { relativePath += ".tlg"; File.WriteAllBytes(Path.Combine(dirPath, relativePath), resource.Data); } else { relativePath += ".raw"; File.WriteAllBytes(Path.Combine(dirPath, relativePath), resource.Data); } break; case PsbImageOption.Uncompress: File.WriteAllBytes(Path.Combine(dirPath, relativePath), resources[i].Compress == PsbCompressType.RL ? RL.Uncompress(resource.Data) : resource.Data); relativePath += ".raw"; break; case PsbImageOption.Compress: File.WriteAllBytes(Path.Combine(dirPath, relativePath), resources[i].Compress != PsbCompressType.RL ? RL.Compress(resource.Data) : resource.Data); relativePath += ".rl"; break; default: throw new ArgumentOutOfRangeException(nameof(imageOption), imageOption, null); } try { resDictionary.Add(Path.GetFileNameWithoutExtension(relativePath), $"{name}/{relativePath}"); } catch (ArgumentException e) { throw new BadImageFormatException("There are resources with same names! Try Raw export mode.", e); } } } //MARK: We use `.resx.json` to distinguish from psbtools' `.res.json` if (useResx) { resx.Resources = resDictionary; File.WriteAllText(inputPath + ".resx.json", JsonConvert.SerializeObject(resx, Formatting.Indented)); } else { File.WriteAllText(inputPath + ".res.json", JsonConvert.SerializeObject(resDictionary.Values.ToList(), Formatting.Indented)); } }
/// <summary> /// Compile Json to PSB /// </summary> /// <param name="inputJson">Json text</param> /// <param name="inputResJson">Resource Json text</param> /// <param name="baseDir">If resource Json uses relative paths (usually it does), specify the base dir</param> /// <param name="version">PSB version</param> /// <param name="cryptKey">CryptKey, use null for pure PSB</param> /// <param name="spec">PSB Platform</param> /// <param name="keepShell">If true, try to compress PSB to shell type (MDF/LZ4 etc.) specified in resx.json; otherwise just output PSB</param> /// <returns></returns> public static byte[] Compile(string inputJson, string inputResJson, string baseDir = null, ushort?version = null, uint?cryptKey = null, PsbSpec?spec = null, bool keepShell = true) { var context = FreeMount.CreateContext(); //Parse PSB psb = Parse(inputJson, version ?? 3); //Link if (!string.IsNullOrWhiteSpace(inputResJson)) { if (inputResJson.Trim().StartsWith("{")) //resx.json { PsbResourceJson resx = JsonConvert.DeserializeObject <PsbResourceJson>(inputResJson); if (resx.PsbType != null) { psb.Type = resx.PsbType.Value; } if (resx.PsbVersion != null && version == null) { psb.Header.Version = resx.PsbVersion.Value; } if (resx.Platform != null && spec == null) { spec = resx.Platform; } if (resx.CryptKey != null & cryptKey == null) { cryptKey = resx.CryptKey; } context = FreeMount.CreateContext(resx.Context); if (resx.HasExtraResources) { PsbResHelper.LinkExtraResources(psb, context, resx.ExtraResources, resx.ExtraFlattenArrays, baseDir); } if (resx.ExternalTextures) { #if DEBUG Console.WriteLine("[INFO] External Texture mode ON, no resource will be compiled."); #endif } else { psb.Link(resx, baseDir); } } else { List <string> resources = JsonConvert.DeserializeObject <List <string> >(inputResJson); psb.Link(resources, baseDir); } } //Build psb.Merge(); if (spec != null && spec != psb.Platform) { psb.SwitchSpec(spec.Value, spec.Value.DefaultPixelFormat()); psb.Merge(); } var bytes = psb.Build(); //Convert if (cryptKey != null) { bytes = PsbFile.EncodeToBytes(cryptKey.Value, bytes, EncodeMode.Encrypt, EncodePosition.Auto); } if (context.HasShell && keepShell) { using var outStream = context.PackToShell(new MemoryStream(bytes)); bytes = outStream.ToArray(); } return(bytes); }