using System; using System.IO; using System.Linq; using System.Text; using System.Drawing; using System.Diagnostics; using System.Collections.Generic; using OMI.Formats.Languages; using OMI.Formats.Pck; using OMI.Workers; using PckStudio.Interfaces; using PckStudio.Core.Deserializer; using PckStudio.Core.Serializer; using PckStudio.Core.Skin; namespace PckStudio.Core.Extensions { public static class PckAssetExtensions { private const string MipMap = "MipMapLevel"; public static PckAsset CreateNewAssetIf(this PckFile pck, bool condition, string filename, PckAssetType filetype, IDataFormatWriter writer) { if (condition) { return pck.CreateNewAsset(filename, filetype, writer); } return default; } public static PckAsset CreateNewAsset(this PckFile pck, string filename, PckAssetType filetype, IDataFormatWriter writer) { PckAsset asset = pck.CreateNewAsset(filename, filetype); asset.SetData(writer); return asset; } public static Image GetTexture(this PckAsset asset) { if (asset.Type != PckAssetType.SkinFile && asset.Type != PckAssetType.CapeFile && asset.Type != PckAssetType.TextureFile) { throw new Exception("Asset is not suitable to contain image data."); } if (asset.Size == 0) { throw new Exception("Asset is has no data."); } return asset.GetDeserializedData(ImageDeserializer.DefaultDeserializer); } /// /// Tries to get the skin id of the skin /// /// /// Non-zero base number on success, otherwise 0 /// public static int GetSkinId(this PckAsset asset) { if (asset.Type != PckAssetType.SkinFile) throw new InvalidOperationException("Asset is not a skin."); const string skinAssetnamePrefix = "dlcskin"; string assetPath = Path.GetFileNameWithoutExtension(asset.Filename); if (!assetPath.StartsWith(skinAssetnamePrefix)) { Trace.TraceWarning($"[{nameof(GetSkinId)}] Asset name does not start with '{skinAssetnamePrefix}'"); return 0; } int skinId = 0; if (!int.TryParse(assetPath.Substring(skinAssetnamePrefix.Length), out skinId)) { Trace.TraceWarning($"[{nameof(GetSkinId)}] Failed to parse Skin Id"); } return skinId; } public static Skin.Skin GetSkin(this PckAsset asset, LOCFile localizationFile = null) { if (asset.Type != PckAssetType.SkinFile) throw new InvalidOperationException("Asset is not a skin."); int skinId = asset.GetSkinId(); Image texture = asset.GetTexture(); SkinANIM anim = asset.GetParameter("ANIM", SkinANIM.FromString); SkinGameFlags gameFlags = asset.GetParameter("GAME_FLAGS", SkinGameFlags.FromString); IEnumerable boxes = asset.GetMultipleParameters("BOX").Select(kv => SkinBOX.FromString(kv.Value)); IEnumerable offsets = asset.GetMultipleParameters("OFFSET").Select(kv => SkinPartOffset.FromString(kv.Value)).GroupBy(o => o.Type).Select(g => g.First()); // this is so something is displayed in the custom skin editor when a displayname is missing but does write that name as the DISPLAYNAME when done bool isDisplayName = true; string name = ""; // prioritize LOC_KEY since DISPLAYNAMEID doesn't even exist in versions after Update Aquatic if (string.IsNullOrEmpty(name)) { string locKey = asset.GetParameter("LOC_KEY"); if (!string.IsNullOrEmpty(locKey)) { name = locKey; if (localizationFile is not null && localizationFile.HasLocEntry(locKey)) { try { name = localizationFile.GetLocEntry(locKey, "en-EN"); } catch { name = locKey; } if (string.IsNullOrEmpty(name)) name = locKey; } } } string displaynameID = asset.GetParameter("DISPLAYNAMEID"); if (!string.IsNullOrEmpty(displaynameID)) { name = displaynameID; if (localizationFile is not null && localizationFile.HasLocEntry(displaynameID)) { try { name = localizationFile.GetLocEntry(displaynameID, "en-EN"); } catch { name = displaynameID; } if (string.IsNullOrEmpty(name)) name = displaynameID; } } if (string.IsNullOrEmpty(name)) { isDisplayName = true; name = asset.GetParameter("DISPLAYNAME"); if (string.IsNullOrEmpty(name)) { name = Path.GetFileNameWithoutExtension(asset.Filename); } } Skin.Skin skin = new Skin.Skin(name, skinId, texture, anim, gameFlags, boxes, offsets); skin.MetaData.IsDisplayName = isDisplayName; return skin; } public static void SetSkin(this PckAsset asset, Skin.Skin skin, in LOCFile localizationFile) { if (asset.Type != PckAssetType.SkinFile) throw new InvalidOperationException("Asset is not a skin file"); asset.SetTexture(skin.Texture); string skinId = skin.Identifier.ToString("d08"); // TODO: keep filepath asset.Filename = $"dlcskin{skinId}.png"; if (!string.IsNullOrEmpty(skin.MetaData.Name) && skin.MetaData.IsDisplayName) { asset.SetParameter("DISPLAYNAME", skin.MetaData.Name); if (localizationFile is not null) { string skinLocKey = $"IDS_dlcskin{skinId}_DISPLAYNAME"; asset.SetParameter("DISPLAYNAMEID", skinLocKey); localizationFile.SetLocEntry(skinLocKey, skin.MetaData.Name); } } if (!string.IsNullOrEmpty(skin.MetaData.Theme)) { asset.SetParameter("THEMENAME", skin.MetaData.Theme); if (localizationFile is not null) { string skinThemeLocKey = $"IDS_dlcskin{skinId}_THEMENAME"; asset.SetParameter("THEMENAMEID", skinThemeLocKey); localizationFile.SetLocEntry(skinThemeLocKey, skin.MetaData.Theme); } } if (skin.HasCape) { asset.SetParameter("CAPEPATH", $"dlccape{skinId}.png"); } asset.SetParameter("ANIM", skin.Anim.ToString()); asset.SetParameter("GAME_FLAGS", skin.GameFlags.ToString()); asset.SetParameter("FREE", "1"); asset.RemoveParameters("BOX"); asset.RemoveParameters("OFFSET"); foreach (SkinBOX box in skin.Model.AdditionalBoxes) { asset.AddParameter(box.ToParameter()); } foreach (SkinPartOffset offset in skin.Model.PartOffsets) { asset.AddParameter(offset.ToParameter()); } } public static T GetDeserializedData(this PckAsset asset, IPckAssetDeserializer deserializer) { return deserializer.Deserialize(asset); } public static T GetData(this PckAsset asset, IDataFormatReader formatReader) where T : class { using var ms = new MemoryStream(asset.Data); return formatReader.FromStream(ms); } public static void SetSerializedData(this PckAsset asset, T obj, IPckAssetSerializer serializer) { serializer.Serialize(obj, ref asset); } public static void SetData(this PckAsset asset, IDataFormatWriter formatWriter) { using (var stream = new MemoryStream()) { formatWriter.WriteToStream(stream); asset.SetData(stream.ToArray()); } } public static void SetTexture(this PckAsset asset, Image image) { if (asset.Type != PckAssetType.SkinFile && asset.Type != PckAssetType.CapeFile && asset.Type != PckAssetType.TextureFile) { throw new Exception("Asset is not suitable to contain image data."); } asset.SetSerializedData(image, ImageSerializer.DefaultSerializer); } public static bool IsMipmappedFile(this PckAsset asset) { // We only want to test the file name itself. ex: "terrainMipMapLevel2" string name = Path.GetFileNameWithoutExtension(asset.Filename); // check if last character is a digit (0-9). If not return false if (!char.IsDigit(name[name.Length - 1])) return false; // If string does not end with MipMapLevel, then it's not MipMapped if (!name.Remove(name.Length - 1, 1).EndsWith(MipMap)) return false; return true; } public static string GetNormalPath(this PckAsset asset) { if (!asset.IsMipmappedFile()) return asset.Filename; string ext = Path.GetExtension(asset.Filename); return asset.Filename.Remove(asset.Filename.Length - (MipMap.Length + 1) - ext.Length) + ext; } public static void DeserializeParameters(this PckAsset asset, IEnumerable serializedData) { IEnumerable> lines = serializedData .Select(line => line.Split([' '], 2)) .Where (keyValue => keyValue.Length == 2) .Select(keyValue => new KeyValuePair(keyValue[0], keyValue[1])); foreach (KeyValuePair kv in lines) { asset.AddParameter(kv); } } public static IEnumerable SerializeParameters(this PckAsset asset, string separator = " ") { IReadOnlyList> parameters = asset.GetParameters(); return parameters.Select(parameter => parameter.Key + separator + parameter.Value); } } }