/// <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; } }
public void Link(PSB psb, FreeMountContext context, IDictionary <string, string> resPaths, string baseDir = null) { var rawResList = psb.CollectResources <AudioMetadata>(); foreach (var resPath in resPaths) { var fullPath = Path.Combine(baseDir ?? "", resPath.Value); var resMd = rawResList.FirstOrDefault(r => r.Name == resPath.Key); if (resMd == null) { if (uint.TryParse(resPath.Key, out var idx)) { var resource = psb.Resources.FirstOrDefault(r => r.Index == idx); if (resource != null) { resource.Data = File.ReadAllBytes(fullPath); } else { Console.WriteLine($"[WARN] {resPath.Key} is not used."); } } } else { resMd.Link(fullPath, context); } } }
public void Link(PSB psb, FreeMountContext context, IList <string> resPaths, string baseDir = null, PsbLinkOrderBy order = PsbLinkOrderBy.Convention) { var rawResList = psb.CollectResources <AudioMetadata>(); if (order == PsbLinkOrderBy.Order) { for (int i = 0; i < rawResList.Count; i++) { var resMd = rawResList[i]; var fullPath = Path.Combine(baseDir ?? "", resPaths[i]); resMd.Link(fullPath, context); } return; } foreach (var resPath in resPaths) { var resMd = rawResList.FirstOrDefault(r => r.Name == Path.GetFileNameWithoutExtension(resPath)); if (resMd == null) { Console.WriteLine($"[WARN] {resPath} is not used."); continue; } var fullPath = Path.Combine(baseDir ?? "", resPath); resMd.Link(fullPath, context); } }
public Dictionary <string, string> OutputResources(PSB psb, FreeMountContext context, string name, string dirPath, PsbExtractOption extractOption = PsbExtractOption.Original) { var resources = psb.CollectResources <AudioMetadata>(); Dictionary <string, string> resDictionary = new Dictionary <string, string>(); if (extractOption == PsbExtractOption.Original) { for (int i = 0; i < psb.Resources.Count; i++) { var relativePath = psb.Resources[i].Index == null ? $"#{i}.raw" : $"{psb.Resources[i].Index}.raw"; File.WriteAllBytes( Path.Combine(dirPath, relativePath), psb.Resources[i].Data); resDictionary.Add(Path.GetFileNameWithoutExtension(relativePath), $"{name}/{relativePath}"); } } else { foreach (var resource in resources) { if (resource.ChannelList.Count == 1) { var bts = resource.ChannelList[0].TryToWave(context); var relativePath = resource.GetFileName(resource.ChannelList[0].WaveExtension); //WaveExtension may change after ToWave if (bts != null) { File.WriteAllBytes(Path.Combine(dirPath, relativePath), bts); resDictionary.Add(resource.Name, $"{name}/{relativePath}"); } } else if (resource.ChannelList.Count > 1) { for (var j = 0; j < resource.ChannelList.Count; j++) { var waveChannel = resource.ChannelList[j]; var bts = waveChannel.TryToWave(context); var relativePath = resource.GetFileName($"-{j}{waveChannel.WaveExtension}"); if (bts != null) { File.WriteAllBytes(Path.Combine(dirPath, relativePath), bts); resDictionary.Add(resource.Name, $"{name}/{relativePath}"); } } } } } return(resDictionary); }
public void Convert(PSB psb) { if (!FromSpec.Contains(psb.Platform)) { throw new FormatException("Can not convert Spec for this PSB"); } var asSpec = EmsAsCommon ? PsbSpec.ems : PsbSpec.common; var toSpec = psb.Platform == PsbSpec.win ? asSpec : PsbSpec.win; var toPixelFormat = toSpec == asSpec ? PsbPixelFormat.CommonRGBA8 : PsbPixelFormat.WinRGBA8; var resList = psb.CollectResources <ImageMetadata>(false); foreach (var resMd in resList) { var resourceData = resMd.Resource.Data; if (resourceData == null) { continue; } if (resMd.Compress == PsbCompressType.RL) { resourceData = RL.Decompress(resourceData); } if (resMd.PixelFormat == PsbPixelFormat.DXT5) { resourceData = RL.GetPixelBytesFromImage( DxtUtil.Dxt5Decode(resourceData, resMd.Width, resMd.Height), toPixelFormat); resMd.TypeString.Value = toPixelFormat.ToStringForPsb(); } else { RL.Switch_0_2(ref resourceData); if (UseRL) { resourceData = RL.Compress(resourceData); } } resMd.Resource.Data = resourceData; } psb.Platform = toSpec; }
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)); }
// ReSharper disable once InvalidXmlDocComment /// <summary> /// Inlined PSB -> External Texture PSB. Inverse of <seealso cref="PsbCompiler.Link"/> /// </summary> /// <param name="psb"></param> /// <param name="order">To make a regular external texture PSB you should set it to <see cref="PsbLinkOrderBy.Name"/>.</param> /// <param name="disposeResInPsb">Whether to remove resources in PSB. To make a real external texture PSB you should set it to true.</param> /// <returns>Ordered textures</returns> public static List <Bitmap> Unlink(this PSB psb, PsbLinkOrderBy order = PsbLinkOrderBy.Name, bool disposeResInPsb = true) { var resources = psb.CollectResources(); List <Bitmap> texs = new List <Bitmap>(); for (int i = 0; i < resources.Count; i++) { var resource = resources[i]; var tex = RL.ConvertToImage(resource.Data, resource.Height, resource.Width, resource.PixelFormat); switch (order) { case PsbLinkOrderBy.Convention: tex.Tag = resource.GetFriendlyName(psb.Type); break; default: var tId = resource.TextureIndex; if (tId == null) { throw new FormatException( "Unable to unlink with texture names since they can't be recognized."); } tex.Tag = $"tex{tId.Value:D3}"; break; } texs.Add(tex); //Finally, dispose if (disposeResInPsb) { resource.Data = null; } } return(texs); }
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> /// Link /// </summary> /// <param name="psb"></param> /// <param name="resPaths">resource paths</param> /// <param name="baseDir"></param> /// <param name="order">how to arrange images</param> /// <param name="isExternal">Whether this is an external texture PSB</param> public static void Link(this PSB psb, IList <string> resPaths, string baseDir = null, PsbLinkOrderBy order = PsbLinkOrderBy.Convention, bool isExternal = false) { if (isExternal) { psb.MotionResourceInstrument(); } var resList = psb.CollectResources(); var context = FreeMount.CreateContext(); if (order == PsbLinkOrderBy.Order) { for (int i = 0; i < resList.Count; i++) { var resMd = resList[i]; var fullPath = Path.Combine(baseDir ?? "", resPaths[i]); byte[] data = LoadImageBytes(fullPath, resMd, context); resMd.Resource.Data = data; } return; } if (order == PsbLinkOrderBy.Name) { if (psb.Platform == PsbSpec.krkr) { throw new InvalidOperationException( $"Can not link by file name for krkr PSB. Please consider using {PsbLinkOrderBy.Convention}"); } resList.Sort((md1, md2) => (int)(md1.TextureIndex ?? 0) - (int)(md2.TextureIndex ?? 0)); } for (var i = 0; i < resPaths.Count; i++) { var resPath = resPaths[i]; var resName = Path.GetFileNameWithoutExtension(resPath); //var resMd = uint.TryParse(resName, out uint rid) // ? resList.FirstOrDefault(r => r.Index == rid) // : resList.FirstOrDefault(r => // resName == $"{r.Part}{PsbResCollector.ResourceNameDelimiter}{r.Name}"); //Scan for Resource ResourceMetadata resMd = null; if (order == PsbLinkOrderBy.Name) { if (resName == null) { continue; } if (resList.Count == 1 && resPaths.Count == 1) { //If there is only one resource and one texture, we won't care about file name. resMd = resList[0]; } else { var texIdx = ResourceMetadata.GetTextureIndex(resName); if (texIdx == null) { Console.WriteLine($"[WARN]{resPath} is not used since the file name cannot be recognized."); continue; } if (resList.Count <= texIdx.Value) { Console.WriteLine($"[WARN]{resPath} is not used since the tex No. is too large."); continue; } resMd = resList[(int)texIdx.Value]; } } else //if (order == PsbLinkOrderBy.Convention) { resMd = resList.FirstOrDefault(r => resName == $"{r.Part}{PsbResCollector.ResourceNameDelimiter}{r.Name}"); if (resMd == null && uint.TryParse(resName, out uint rid)) { resMd = resList.FirstOrDefault(r => r.Index == rid); } if (resMd == null && psb.Type == PsbType.Pimg) { resMd = resList.FirstOrDefault(r => resName == Path.GetFileNameWithoutExtension(r.Name)); } } if (resMd == null) { Console.WriteLine($"[WARN]{resPath} is not used."); continue; } var fullPath = Path.Combine(baseDir ?? "", resPath.Replace('/', '\\')); byte[] data = LoadImageBytes(fullPath, resMd, context); resMd.Resource.Data = data; } }
public Dictionary <string, string> OutputResources(PSB psb, FreeMountContext context, string name, string dirPath, PsbExtractOption extractOption = PsbExtractOption.Original) { var resources = psb.CollectResources <AudioMetadata>(); Dictionary <string, string> resDictionary = new Dictionary <string, string>(); if (extractOption == PsbExtractOption.Original) { for (int i = 0; i < psb.Resources.Count; i++) { var relativePath = psb.Resources[i].Index == null ? $"#{i}.raw" : $"{psb.Resources[i].Index}.raw"; File.WriteAllBytes( Path.Combine(dirPath, relativePath), psb.Resources[i].Data); resDictionary.Add(Path.GetFileNameWithoutExtension(relativePath), $"{name}/{relativePath}"); } } else { foreach (var resource in resources) { if (resource.ChannelList.Count == 1) { var bts = resource.ChannelList[0].TryToWave(context); var relativePath = resource.GetFileName(resource.ChannelList[0].Extension + resource.ChannelList[0].WaveExtension); //WaveExtension may change after ToWave if (bts != null) { File.WriteAllBytes(Path.Combine(dirPath, relativePath), bts); resDictionary.Add(resource.Name, $"{name}/{relativePath}"); } } else if (resource.ChannelList.Count > 1) { if (resource.Pan == PsbAudioPan.LeftRight) //load audio.vag.l.wav & audio.vag.r.wav { var left = resource.GetLeftChannel(); var relativePathL = resource.GetFileName($"{left.Extension}.l{left.WaveExtension}"); var btsL = left.TryToWave(context); if (btsL != null) { File.WriteAllBytes(Path.Combine(dirPath, relativePathL), btsL); } else { relativePathL = resource.GetFileName($"{left.Extension}.l{left.Extension}"); File.WriteAllBytes(Path.Combine(dirPath, relativePathL), left.Data.Data); resDictionary.Add(resource.Name, $"{name}/{relativePathL}"); } var right = resource.GetRightChannel(); var relativePathR = resource.GetFileName($"{right.Extension}.r{right.WaveExtension}"); var btsR = right.TryToWave(context); if (btsR != null) { File.WriteAllBytes(Path.Combine(dirPath, relativePathR), btsR); } else { relativePathR = resource.GetFileName($"{right.Extension}.r{right.Extension}"); File.WriteAllBytes(Path.Combine(dirPath, relativePathR), right.Data.Data); resDictionary.Add(resource.Name, $"{name}/{relativePathR}"); } if (btsL != null && btsR != null) { var relativePath = resource.GetFileName($"{left.Extension}{left.WaveExtension}"); resDictionary.Add(resource.Name, $"{name}/{relativePath}"); //a virtual file path } } else //not LeftRight { for (var j = 0; j < resource.ChannelList.Count; j++) //load audio.vag.1.wav etc. { var waveChannel = resource.ChannelList[j]; if (waveChannel.Data.Index == null) { Console.WriteLine($"[WARN] Channel {j} is not linked with a Resource."); continue; } var bts = waveChannel.TryToWave(context); var noStr = waveChannel.Data.Index == null ? $".@{j}" : $".#{waveChannel.Data.Index.Value}"; //TODO: handle @ - channel internal No. var relativePath = resource.GetFileName($"{waveChannel.Extension}{noStr}{waveChannel.WaveExtension}"); if (bts != null) { File.WriteAllBytes(Path.Combine(dirPath, relativePath), bts); resDictionary.Add(resource.Name, $"{name}/{relativePath}"); } } } } } } return(resDictionary); }