From 03fce7304ff9a46be0afae918fa797422709512e Mon Sep 17 00:00:00 2001 From: MayNL Date: Sat, 4 Jul 2026 17:52:16 -0400 Subject: [PATCH] Created PSM Version 2 --- PCK-Studio/Forms/Editor/CustomSkinEditor.cs | 14 ++- .../Extension/SkinExtension.cs | 3 +- .../Format/Internal/PSM/PSMFile.cs | 100 +++++++++++++----- .../Format/Internal/PSM/PSMFileReader.cs | 72 +++++++++++-- .../Format/Internal/PSM/PSMFileWriter.cs | 46 +++++++- PckStudio.ModelSupport/SkinModelImporter.cs | 8 +- PckStudio.ModelSupport/SkinModelInfo.cs | 4 +- 7 files changed, 198 insertions(+), 49 deletions(-) diff --git a/PCK-Studio/Forms/Editor/CustomSkinEditor.cs b/PCK-Studio/Forms/Editor/CustomSkinEditor.cs index 72f80766..fc65913e 100644 --- a/PCK-Studio/Forms/Editor/CustomSkinEditor.cs +++ b/PCK-Studio/Forms/Editor/CustomSkinEditor.cs @@ -185,7 +185,7 @@ namespace PckStudio.Forms.Editor } } - private void buttonDone_Click(object sender, EventArgs e) + private void prepareSkinForExport() { EditorValue.Model.AdditionalBoxes.Clear(); EditorValue.Model.AdditionalBoxes.AddRange(renderer3D1.ModelData); @@ -194,6 +194,11 @@ namespace PckStudio.Forms.Editor // just in case they're not the same instance EditorValue.Anim = renderer3D1.ANIM; EditorValue.GameFlags = _gameFlags; + } + + private void buttonDone_Click(object sender, EventArgs e) + { + prepareSkinForExport(); DialogResult = DialogResult.OK; Save(); } @@ -205,7 +210,10 @@ namespace PckStudio.Forms.Editor saveFileDialog.Filter = SkinModelImporter.Default.SupportedModelFileFormatsFilter; saveFileDialog.FileName = EditorValue.MetaData.Name.TrimEnd(new char[] { '\n', '\r' }).Replace(' ', '_'); if (saveFileDialog.ShowDialog() == DialogResult.OK) + { + prepareSkinForExport(); SkinModelImporter.Default.Export(saveFileDialog.FileName, EditorValue.GetModelInfo()); + } } private void importSkinButton_Click(object sender, EventArgs e) @@ -434,6 +442,7 @@ namespace PckStudio.Forms.Editor { Image templateTexture = Resources.classic_template; SkinAnimMask templateAnimMask = SkinAnimMask.MODERN_WIDE_MODEL; + SkinGameFlagsMask templateSkinFlagsMask = SkinGameFlagsMask.HUMAN_SKIN; SaveFileDialog saveFileDialog = new SaveFileDialog(); saveFileDialog.Title = "Save Template Model"; @@ -441,7 +450,7 @@ namespace PckStudio.Forms.Editor saveFileDialog.FileName = SanitizeModelFilename(EditorValue.MetaData.Name); if (saveFileDialog.ShowDialog() == DialogResult.OK) { - SkinModelInfo modelInfo = new SkinModelInfo(templateTexture, templateAnimMask, new SkinModel()); + SkinModelInfo modelInfo = new SkinModelInfo(templateTexture, templateAnimMask, templateSkinFlagsMask, new SkinModel()); SkinModelImporter.Default.Export(saveFileDialog.FileName, modelInfo); } } @@ -462,6 +471,7 @@ namespace PckStudio.Forms.Editor { renderer3D1.ANIM = skinAdjustmentsEditorControl1.GetAnim(); _gameFlags = skinAdjustmentsEditorControl1.GetGameFlags(); + EditorValue.GameFlags = _gameFlags; } private void armorButtonControl_Click(object sender, EventArgs e) diff --git a/PckStudio.ModelSupport/Extension/SkinExtension.cs b/PckStudio.ModelSupport/Extension/SkinExtension.cs index dcdd5527..bd368910 100644 --- a/PckStudio.ModelSupport/Extension/SkinExtension.cs +++ b/PckStudio.ModelSupport/Extension/SkinExtension.cs @@ -11,12 +11,13 @@ namespace PckStudio.ModelSupport.Extension { public static class SkinExtension { - public static SkinModelInfo GetModelInfo(this Skin skin) => new SkinModelInfo(skin.Texture, skin.Anim, skin.Model); + public static SkinModelInfo GetModelInfo(this Skin skin) => new SkinModelInfo(skin.Texture, skin.Anim, skin.GameFlags, skin.Model); public static void SetModelInfo(this Skin skin, SkinModelInfo modelInfo) { skin.Texture = modelInfo.Texture; skin.Anim = modelInfo.Anim; + skin.GameFlags = modelInfo.SkinFlags; skin.Model = modelInfo.Model; } } diff --git a/PckStudio.ModelSupport/Format/Internal/PSM/PSMFile.cs b/PckStudio.ModelSupport/Format/Internal/PSM/PSMFile.cs index 1aebcecc..f6c769e0 100644 --- a/PckStudio.ModelSupport/Format/Internal/PSM/PSMFile.cs +++ b/PckStudio.ModelSupport/Format/Internal/PSM/PSMFile.cs @@ -4,33 +4,58 @@ using PckStudio.Core.Skin; namespace PckStudio.ModelSupport.Internal.Format { -/* - Magic - 3 bytes("psm") - Version - 1 byte [u8] - Anim - 4 bytes[int32] - NumberOfParts - 4 bytes[int32] - { - part parent - 1 byte (HEAD=0, BODY=1, LEG0=2, LEG1=3, ARM0=4, ARM1=5) - Position-X - 4 bytes (float32) - Position-Y - 4 bytes (float32) - Position-Z - 4 bytes (float32) - Size-X - 4 bytes (float32) - Size-Y - 4 bytes (float32) - Size-Z - 4 bytes (float32) - MirrorAndUvX - 1 bit flag 7 bits uv.x value(0-64) (s8) - HideWithArmorAndUvY - 1 bit flag 7 bits uv.y value(0-64) (s8) - inflation/scale value - 4 bytes (float32) - } - NumberOfOffsets - 4 bytes[int32] - { - offset part - 1 byte - vertical offset - 4 bytes[float] - } -*/ + /* + * VERSION 1 + Magic - 3 bytes("psm") + Version - 1 byte [u8] + Anim - 4 bytes[int32] + NumberOfParts - 4 bytes[int32] + { + part parent - 1 byte (HEAD=0, BODY=1, LEG0=2, LEG1=3, ARM0=4, ARM1=5) + Position-X - 4 bytes (float32) + Position-Y - 4 bytes (float32) + Position-Z - 4 bytes (float32) + Size-X - 4 bytes (float32) + Size-Y - 4 bytes (float32) + Size-Z - 4 bytes (float32) + MirrorAndUvX - 1 bit flag 7 bits uv.x value(0-64) (s8) + HideWithArmorAndUvY - 1 bit flag 7 bits uv.y value(0-64) (s8) + Inflation - 4 bytes (float32) + } + NumberOfOffsets - 4 bytes[int32] + { + offset part - 1 byte + vertical offset - 4 bytes[float] + } + + * VERSION 2 + Magic - 3 bytes("psm") + Version - 1 byte [u8] + Anim - 4 bytes [int32] + Game Flags - 1 byte [u8] + NumberOfParts - 4 bytes[int32] + { + ArmorMaskAndParParent - 3 bits Armor Mask 5 bits (Box Parent Type) + Position-X - 4 bytes (float32) + Position-Y - 4 bytes (float32) + Position-Z - 4 bytes (float32) + Size-X - 4 bytes (float32) + Size-Y - 4 bytes (float32) + Size-Z - 4 bytes (float32) + MirrorAndUvX - 1 bit flag 7 bits uv.x value(0-64) (s8) + ArmorMaskingAndUvY - 1 bit Helmet Mask 7 bits uv.y value(0-64) (s8) + Inflation - 4 bytes (float32) + } + NumberOfOffsets - 4 bytes[int32] + { + offset part - 1 byte + vertical offset - 4 bytes[float] + } + */ public sealed class PSMFile { internal static readonly string HEADER_MAGIC = "psm"; - internal const byte CurrentVersion = 1; + internal const byte CurrentVersion = 2; public readonly byte Version; @@ -39,13 +64,15 @@ namespace PckStudio.ModelSupport.Internal.Format Version = version; } - internal PSMFile(byte version, SkinANIM skinANIM) - : this(version) + internal PSMFile(byte version, SkinANIM skinANIM, SkinGameFlags skinFlags) + : this(version) { - SkinANIM = skinANIM; + SkinANIM = skinANIM; + SkinFlags = skinFlags; } - public SkinANIM SkinANIM { get; private set; } + public SkinANIM SkinANIM { get; private set; } + public SkinGameFlags SkinFlags { get; private set; } public readonly List Parts = new List(); public readonly List Offsets = new List(); @@ -82,5 +109,22 @@ namespace PckStudio.ModelSupport.Internal.Format ARM1 = 3, LEG0 = 4, LEG1 = 5, + HEADWEAR = 6, + JACKET = 7, + SLEEVE0 = 8, + SLEEVE1 = 9, + PANTS0 = 10, + PANTS1 = 11, + WAIST = 12, + LEGGING0 = 13, + LEGGING1 = 14, + SOCK0 = 15, + SOCK1 = 16, + BOOT0 = 17, + BOOT1 = 18, + ARMAMOR0 = 19, + ARMAMOR1 = 20, + BODYARMOR = 21, + BELT = 22, } } diff --git a/PckStudio.ModelSupport/Format/Internal/PSM/PSMFileReader.cs b/PckStudio.ModelSupport/Format/Internal/PSM/PSMFileReader.cs index e70ef51e..037f3a0d 100644 --- a/PckStudio.ModelSupport/Format/Internal/PSM/PSMFileReader.cs +++ b/PckStudio.ModelSupport/Format/Internal/PSM/PSMFileReader.cs @@ -6,6 +6,8 @@ using OMI.Workers; using PckStudio.Core.FileFormats; using PckStudio.Core; using PckStudio.Core.Skin; +using System; +using System.Runtime.InteropServices.WindowsRuntime; namespace PckStudio.ModelSupport.Internal.Format { @@ -35,33 +37,49 @@ namespace PckStudio.ModelSupport.Internal.Format } byte version = reader.ReadByte(); - if (version < 1 || version > 1) + if (version < 1 || version > PSMFile.CurrentVersion) { Trace.TraceError("PSMFileReader.FromStream - Failed to load csmb.\n\tReason: Unsupported version."); return new PSMFile(byte.MaxValue); } var skinANIM = SkinANIM.FromValue(reader.ReadInt32()); - PSMFile csmbFile = new PSMFile(version, skinANIM); + var skinFlags = SkinGameFlags.Empty; + + if(version > 1) + skinFlags = SkinGameFlags.FromValue(reader.ReadByte()); + + PSMFile psmFile = new PSMFile(version, skinANIM, skinFlags); int numOfParts = reader.ReadInt32(); for (int i = 0; i < numOfParts; i++) { - SkinBOX part = ReadPart(reader); - csmbFile.Parts.Add(part); + SkinBOX part = ReadPart(reader, version); + psmFile.Parts.Add(part); } int numOfOffsets = reader.ReadInt32(); for (int i = 0; i < numOfOffsets; i++) { SkinPartOffset offset = ReadOffset(reader); - csmbFile.Offsets.Add(offset); + psmFile.Offsets.Add(offset); } - return csmbFile; + return psmFile; } - private SkinBOX ReadPart(EndiannessAwareBinaryReader reader) + private SkinBOX ReadPart(EndiannessAwareBinaryReader reader, int version) { - string type = GetParentType((PSMParentType)reader.ReadByte()); + int armorMask = 0; + string type = ""; + if(version == 1) + type = GetParentType((PSMParentType)reader.ReadByte()); + else + { + byte armorMaskAndParentType = reader.ReadByte(); + + type = GetParentType((PSMParentType)(armorMaskAndParentType & 0x1F)); // get first 5 bits + armorMask = (armorMaskAndParentType & 0xE0) >> 5; // get last 3 bits + } + float posX = reader.ReadSingle(); float posY = reader.ReadSingle(); float posZ = reader.ReadSingle(); @@ -73,9 +91,9 @@ namespace PckStudio.ModelSupport.Internal.Format int uvX = mirrorAndUvX & 0x7f; int uvY = hideWithArmorAndUvY & 0x7f; bool mirror = (mirrorAndUvX & 0x80) != 0; - int hideWithArmor = (hideWithArmorAndUvY & 0x80); + armorMask = (armorMask << 1) | ((hideWithArmorAndUvY >> 7) & 1); float scale = reader.ReadSingle(); - return new SkinBOX(type, new System.Numerics.Vector3(posX, posY, posZ), new System.Numerics.Vector3(sizeX, sizeY, sizeZ), new System.Numerics.Vector2(uvX, uvY), hideWithArmor, mirror, scale); + return new SkinBOX(type, new System.Numerics.Vector3(posX, posY, posZ), new System.Numerics.Vector3(sizeX, sizeY, sizeZ), new System.Numerics.Vector2(uvX, uvY), armorMask, mirror, scale); } private SkinPartOffset ReadOffset(EndiannessAwareBinaryReader reader) @@ -101,6 +119,40 @@ namespace PckStudio.ModelSupport.Internal.Format return "LEG0"; case PSMParentType.LEG1: return "LEG1"; + case PSMParentType.HEADWEAR: + return "HEADWEAR"; + case PSMParentType.JACKET: + return "JACKET"; + case PSMParentType.SLEEVE0: + return "SLEEVE0"; + case PSMParentType.SLEEVE1: + return "SLEEVE1"; + case PSMParentType.PANTS0: + return "PANTS0"; + case PSMParentType.PANTS1: + return "PANTS1"; + case PSMParentType.WAIST: + return "WAIST"; + case PSMParentType.LEGGING0: + return "LEGGING0"; + case PSMParentType.LEGGING1: + return "LEGGING1"; + case PSMParentType.SOCK0: + return "SOCK0"; + case PSMParentType.SOCK1: + return "SOCK1"; + case PSMParentType.BOOT0: + return "BOOT0"; + case PSMParentType.BOOT1: + return "BOOT1"; + case PSMParentType.ARMAMOR0: + return "ARMARMOR0"; + case PSMParentType.ARMAMOR1: + return "ARMARMOR1"; + case PSMParentType.BODYARMOR: + return "BODYARMOR"; + case PSMParentType.BELT: + return "BELT"; default: throw new InvalidDataException(type.ToString()); } diff --git a/PckStudio.ModelSupport/Format/Internal/PSM/PSMFileWriter.cs b/PckStudio.ModelSupport/Format/Internal/PSM/PSMFileWriter.cs index 4cfb3add..fd276faa 100644 --- a/PckStudio.ModelSupport/Format/Internal/PSM/PSMFileWriter.cs +++ b/PckStudio.ModelSupport/Format/Internal/PSM/PSMFileWriter.cs @@ -6,6 +6,7 @@ using OMI; using System; using OpenTK; using PckStudio.Core.Skin; +using System.Windows.Forms; namespace PckStudio.ModelSupport.Internal.Format { @@ -33,6 +34,7 @@ namespace PckStudio.ModelSupport.Internal.Format writer.WriteString(PSMFile.HEADER_MAGIC); writer.Write(_PSM.Version); writer.Write(_PSM.SkinANIM.ToValue()); + writer.Write((byte)_PSM.SkinFlags.ToValue()); writer.Write(_PSM.Parts.Count); foreach (SkinBOX part in _PSM.Parts) { @@ -49,7 +51,11 @@ namespace PckStudio.ModelSupport.Internal.Format private void WritePart(EndiannessAwareBinaryWriter writer, SkinBOX part) { - writer.Write((byte)GetParentPart(part.Type)); + byte armorMask = (byte)((part.ArmorMaskFlags.ToValue() >> 1) & 0x07); + byte parentType = (byte)((int)GetParentPart(part.Type) & 0x1F); + + byte armorMaskAndParentType = (byte)((armorMask << 5) | parentType); + writer.Write(armorMaskAndParentType); writer.Write(part.Pos.X); writer.Write(part.Pos.Y); writer.Write(part.Pos.Z); @@ -60,9 +66,9 @@ namespace PckStudio.ModelSupport.Internal.Format byte uvX = (byte)MathHelper.Clamp((int)part.UV.X, 0, 64); byte uvY = (byte)MathHelper.Clamp((int)part.UV.Y, 0, 64); byte mirrorAndUvX = (byte)(Convert.ToByte(part.Mirror) << 7 | uvX); - byte hideWithArmorAndUvY = (byte)(Convert.ToByte(part.ArmorMaskFlags.ToValue()) << 7 | uvY); + byte helmetMaskAndUvY = (byte)(Convert.ToByte(part.ArmorMaskFlags.ToValue()) << 7 | uvY); writer.Write(mirrorAndUvX); - writer.Write(hideWithArmorAndUvY); + writer.Write(helmetMaskAndUvY); writer.Write(part.Scale); } @@ -82,6 +88,40 @@ namespace PckStudio.ModelSupport.Internal.Format return PSMParentType.LEG0; case "LEG1": return PSMParentType.LEG1; + case "HEADWEAR": + return PSMParentType.HEADWEAR; + case "JACKET": + return PSMParentType.JACKET; + case "SLEEVE0": + return PSMParentType.SLEEVE0; + case "SLEEVE1": + return PSMParentType.SLEEVE1; + case "PANTS0": + return PSMParentType.PANTS0; + case "PANTS1": + return PSMParentType.PANTS1; + case "WAIST": + return PSMParentType.WAIST; + case "LEGGING0": + return PSMParentType.LEGGING0; + case "LEGGING1": + return PSMParentType.LEGGING1; + case "SOCK0": + return PSMParentType.SOCK0; + case "SOCK1": + return PSMParentType.SOCK1; + case "BOOT0": + return PSMParentType.BOOT0; + case "BOOT1": + return PSMParentType.BOOT1; + case "ARMARMOR0": + return PSMParentType.ARMAMOR0; + case "ARMARMOR1": + return PSMParentType.ARMAMOR1; + case "BODYARMOR": + return PSMParentType.BODYARMOR; + case "BELT": + return PSMParentType.BELT; default: throw new InvalidDataException(type); } diff --git a/PckStudio.ModelSupport/SkinModelImporter.cs b/PckStudio.ModelSupport/SkinModelImporter.cs index 59f71ace..e5ff5250 100644 --- a/PckStudio.ModelSupport/SkinModelImporter.cs +++ b/PckStudio.ModelSupport/SkinModelImporter.cs @@ -51,13 +51,13 @@ namespace PckStudio.ModelSupport internal static SkinModelInfo ImportPsm(string filepath) { var reader = new PSMFileReader(); - PSMFile csmbFile = reader.FromFile(filepath); - return new SkinModelInfo(null, csmbFile.SkinANIM, new(csmbFile.Parts, csmbFile.Offsets)); + PSMFile psmFile = reader.FromFile(filepath); + return new SkinModelInfo(null, psmFile.SkinANIM, psmFile.SkinFlags, new(psmFile.Parts, psmFile.Offsets)); } internal static void ExportPsm(string filepath, SkinModelInfo modelInfo) { - PSMFile psmFile = new PSMFile(PSMFile.CurrentVersion, modelInfo.Anim); + PSMFile psmFile = new PSMFile(PSMFile.CurrentVersion, modelInfo.Anim, modelInfo.SkinFlags); psmFile.Parts.AddRange(modelInfo.Model.AdditionalBoxes); psmFile.Offsets.AddRange(modelInfo.Model.PartOffsets); var writer = new PSMFileWriter(psmFile); @@ -143,7 +143,7 @@ namespace PckStudio.ModelSupport if (mask != SkinAnimMask.NONE) skinANIM &= ~mask; - return new SkinModelInfo(texture, skinANIM, skinModel); + return new SkinModelInfo(texture, skinANIM, SkinGameFlagsMask.HUMAN_SKIN, skinModel); } private static IEnumerable ReadOutliner(string parentName, JArray oulineChildren, IReadOnlyCollection elements) diff --git a/PckStudio.ModelSupport/SkinModelInfo.cs b/PckStudio.ModelSupport/SkinModelInfo.cs index a7a1b2f9..24d66af1 100644 --- a/PckStudio.ModelSupport/SkinModelInfo.cs +++ b/PckStudio.ModelSupport/SkinModelInfo.cs @@ -12,12 +12,14 @@ namespace PckStudio.ModelSupport { public SkinModel Model { get; } public SkinANIM Anim { get; } + public SkinGameFlags SkinFlags { get; } public Image Texture { get; } - public SkinModelInfo(Image texture, SkinANIM anim, SkinModel model) + public SkinModelInfo(Image texture, SkinANIM anim, SkinGameFlags skinFlags, SkinModel model) { Texture = texture; Anim = anim; + SkinFlags = skinFlags; Model = model; } }