Created PSM Version 2

This commit is contained in:
MayNL
2026-07-04 17:52:16 -04:00
parent 991d87d421
commit 03fce7304f
7 changed files with 198 additions and 49 deletions

View File

@@ -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.Clear();
EditorValue.Model.AdditionalBoxes.AddRange(renderer3D1.ModelData); EditorValue.Model.AdditionalBoxes.AddRange(renderer3D1.ModelData);
@@ -194,6 +194,11 @@ namespace PckStudio.Forms.Editor
// just in case they're not the same instance // just in case they're not the same instance
EditorValue.Anim = renderer3D1.ANIM; EditorValue.Anim = renderer3D1.ANIM;
EditorValue.GameFlags = _gameFlags; EditorValue.GameFlags = _gameFlags;
}
private void buttonDone_Click(object sender, EventArgs e)
{
prepareSkinForExport();
DialogResult = DialogResult.OK; DialogResult = DialogResult.OK;
Save(); Save();
} }
@@ -205,7 +210,10 @@ namespace PckStudio.Forms.Editor
saveFileDialog.Filter = SkinModelImporter.Default.SupportedModelFileFormatsFilter; saveFileDialog.Filter = SkinModelImporter.Default.SupportedModelFileFormatsFilter;
saveFileDialog.FileName = EditorValue.MetaData.Name.TrimEnd(new char[] { '\n', '\r' }).Replace(' ', '_'); saveFileDialog.FileName = EditorValue.MetaData.Name.TrimEnd(new char[] { '\n', '\r' }).Replace(' ', '_');
if (saveFileDialog.ShowDialog() == DialogResult.OK) if (saveFileDialog.ShowDialog() == DialogResult.OK)
{
prepareSkinForExport();
SkinModelImporter.Default.Export(saveFileDialog.FileName, EditorValue.GetModelInfo()); SkinModelImporter.Default.Export(saveFileDialog.FileName, EditorValue.GetModelInfo());
}
} }
private void importSkinButton_Click(object sender, EventArgs e) private void importSkinButton_Click(object sender, EventArgs e)
@@ -434,6 +442,7 @@ namespace PckStudio.Forms.Editor
{ {
Image templateTexture = Resources.classic_template; Image templateTexture = Resources.classic_template;
SkinAnimMask templateAnimMask = SkinAnimMask.MODERN_WIDE_MODEL; SkinAnimMask templateAnimMask = SkinAnimMask.MODERN_WIDE_MODEL;
SkinGameFlagsMask templateSkinFlagsMask = SkinGameFlagsMask.HUMAN_SKIN;
SaveFileDialog saveFileDialog = new SaveFileDialog(); SaveFileDialog saveFileDialog = new SaveFileDialog();
saveFileDialog.Title = "Save Template Model"; saveFileDialog.Title = "Save Template Model";
@@ -441,7 +450,7 @@ namespace PckStudio.Forms.Editor
saveFileDialog.FileName = SanitizeModelFilename(EditorValue.MetaData.Name); saveFileDialog.FileName = SanitizeModelFilename(EditorValue.MetaData.Name);
if (saveFileDialog.ShowDialog() == DialogResult.OK) 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); SkinModelImporter.Default.Export(saveFileDialog.FileName, modelInfo);
} }
} }
@@ -462,6 +471,7 @@ namespace PckStudio.Forms.Editor
{ {
renderer3D1.ANIM = skinAdjustmentsEditorControl1.GetAnim(); renderer3D1.ANIM = skinAdjustmentsEditorControl1.GetAnim();
_gameFlags = skinAdjustmentsEditorControl1.GetGameFlags(); _gameFlags = skinAdjustmentsEditorControl1.GetGameFlags();
EditorValue.GameFlags = _gameFlags;
} }
private void armorButtonControl_Click(object sender, EventArgs e) private void armorButtonControl_Click(object sender, EventArgs e)

View File

@@ -11,12 +11,13 @@ namespace PckStudio.ModelSupport.Extension
{ {
public static class SkinExtension 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) public static void SetModelInfo(this Skin skin, SkinModelInfo modelInfo)
{ {
skin.Texture = modelInfo.Texture; skin.Texture = modelInfo.Texture;
skin.Anim = modelInfo.Anim; skin.Anim = modelInfo.Anim;
skin.GameFlags = modelInfo.SkinFlags;
skin.Model = modelInfo.Model; skin.Model = modelInfo.Model;
} }
} }

View File

@@ -4,33 +4,58 @@ using PckStudio.Core.Skin;
namespace PckStudio.ModelSupport.Internal.Format namespace PckStudio.ModelSupport.Internal.Format
{ {
/* /*
Magic - 3 bytes("psm") * VERSION 1
Version - 1 byte [u8] Magic - 3 bytes("psm")
Anim - 4 bytes[int32] Version - 1 byte [u8]
NumberOfParts - 4 bytes[int32] 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) part parent - 1 byte (HEAD=0, BODY=1, LEG0=2, LEG1=3, ARM0=4, ARM1=5)
Position-Y - 4 bytes (float32) Position-X - 4 bytes (float32)
Position-Z - 4 bytes (float32) Position-Y - 4 bytes (float32)
Size-X - 4 bytes (float32) Position-Z - 4 bytes (float32)
Size-Y - 4 bytes (float32) Size-X - 4 bytes (float32)
Size-Z - 4 bytes (float32) Size-Y - 4 bytes (float32)
MirrorAndUvX - 1 bit flag 7 bits uv.x value(0-64) (s8) Size-Z - 4 bytes (float32)
HideWithArmorAndUvY - 1 bit flag 7 bits uv.y value(0-64) (s8) MirrorAndUvX - 1 bit flag 7 bits uv.x value(0-64) (s8)
inflation/scale value - 4 bytes (float32) HideWithArmorAndUvY - 1 bit flag 7 bits uv.y value(0-64) (s8)
} Inflation - 4 bytes (float32)
NumberOfOffsets - 4 bytes[int32] }
{ NumberOfOffsets - 4 bytes[int32]
offset part - 1 byte {
vertical offset - 4 bytes[float] 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 public sealed class PSMFile
{ {
internal static readonly string HEADER_MAGIC = "psm"; internal static readonly string HEADER_MAGIC = "psm";
internal const byte CurrentVersion = 1; internal const byte CurrentVersion = 2;
public readonly byte Version; public readonly byte Version;
@@ -39,13 +64,15 @@ namespace PckStudio.ModelSupport.Internal.Format
Version = version; Version = version;
} }
internal PSMFile(byte version, SkinANIM skinANIM) internal PSMFile(byte version, SkinANIM skinANIM, SkinGameFlags skinFlags)
: this(version) : 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<SkinBOX> Parts = new List<SkinBOX>(); public readonly List<SkinBOX> Parts = new List<SkinBOX>();
public readonly List<SkinPartOffset> Offsets = new List<SkinPartOffset>(); public readonly List<SkinPartOffset> Offsets = new List<SkinPartOffset>();
@@ -82,5 +109,22 @@ namespace PckStudio.ModelSupport.Internal.Format
ARM1 = 3, ARM1 = 3,
LEG0 = 4, LEG0 = 4,
LEG1 = 5, 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,
} }
} }

View File

@@ -6,6 +6,8 @@ using OMI.Workers;
using PckStudio.Core.FileFormats; using PckStudio.Core.FileFormats;
using PckStudio.Core; using PckStudio.Core;
using PckStudio.Core.Skin; using PckStudio.Core.Skin;
using System;
using System.Runtime.InteropServices.WindowsRuntime;
namespace PckStudio.ModelSupport.Internal.Format namespace PckStudio.ModelSupport.Internal.Format
{ {
@@ -35,33 +37,49 @@ namespace PckStudio.ModelSupport.Internal.Format
} }
byte version = reader.ReadByte(); 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."); Trace.TraceError("PSMFileReader.FromStream - Failed to load csmb.\n\tReason: Unsupported version.");
return new PSMFile(byte.MaxValue); return new PSMFile(byte.MaxValue);
} }
var skinANIM = SkinANIM.FromValue(reader.ReadInt32()); 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(); int numOfParts = reader.ReadInt32();
for (int i = 0; i < numOfParts; i++) for (int i = 0; i < numOfParts; i++)
{ {
SkinBOX part = ReadPart(reader); SkinBOX part = ReadPart(reader, version);
csmbFile.Parts.Add(part); psmFile.Parts.Add(part);
} }
int numOfOffsets = reader.ReadInt32(); int numOfOffsets = reader.ReadInt32();
for (int i = 0; i < numOfOffsets; i++) for (int i = 0; i < numOfOffsets; i++)
{ {
SkinPartOffset offset = ReadOffset(reader); 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 posX = reader.ReadSingle();
float posY = reader.ReadSingle(); float posY = reader.ReadSingle();
float posZ = reader.ReadSingle(); float posZ = reader.ReadSingle();
@@ -73,9 +91,9 @@ namespace PckStudio.ModelSupport.Internal.Format
int uvX = mirrorAndUvX & 0x7f; int uvX = mirrorAndUvX & 0x7f;
int uvY = hideWithArmorAndUvY & 0x7f; int uvY = hideWithArmorAndUvY & 0x7f;
bool mirror = (mirrorAndUvX & 0x80) != 0; bool mirror = (mirrorAndUvX & 0x80) != 0;
int hideWithArmor = (hideWithArmorAndUvY & 0x80); armorMask = (armorMask << 1) | ((hideWithArmorAndUvY >> 7) & 1);
float scale = reader.ReadSingle(); 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) private SkinPartOffset ReadOffset(EndiannessAwareBinaryReader reader)
@@ -101,6 +119,40 @@ namespace PckStudio.ModelSupport.Internal.Format
return "LEG0"; return "LEG0";
case PSMParentType.LEG1: case PSMParentType.LEG1:
return "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: default:
throw new InvalidDataException(type.ToString()); throw new InvalidDataException(type.ToString());
} }

View File

@@ -6,6 +6,7 @@ using OMI;
using System; using System;
using OpenTK; using OpenTK;
using PckStudio.Core.Skin; using PckStudio.Core.Skin;
using System.Windows.Forms;
namespace PckStudio.ModelSupport.Internal.Format namespace PckStudio.ModelSupport.Internal.Format
{ {
@@ -33,6 +34,7 @@ namespace PckStudio.ModelSupport.Internal.Format
writer.WriteString(PSMFile.HEADER_MAGIC); writer.WriteString(PSMFile.HEADER_MAGIC);
writer.Write(_PSM.Version); writer.Write(_PSM.Version);
writer.Write(_PSM.SkinANIM.ToValue()); writer.Write(_PSM.SkinANIM.ToValue());
writer.Write((byte)_PSM.SkinFlags.ToValue());
writer.Write(_PSM.Parts.Count); writer.Write(_PSM.Parts.Count);
foreach (SkinBOX part in _PSM.Parts) foreach (SkinBOX part in _PSM.Parts)
{ {
@@ -49,7 +51,11 @@ namespace PckStudio.ModelSupport.Internal.Format
private void WritePart(EndiannessAwareBinaryWriter writer, SkinBOX part) 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.X);
writer.Write(part.Pos.Y); writer.Write(part.Pos.Y);
writer.Write(part.Pos.Z); 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 uvX = (byte)MathHelper.Clamp((int)part.UV.X, 0, 64);
byte uvY = (byte)MathHelper.Clamp((int)part.UV.Y, 0, 64); byte uvY = (byte)MathHelper.Clamp((int)part.UV.Y, 0, 64);
byte mirrorAndUvX = (byte)(Convert.ToByte(part.Mirror) << 7 | uvX); 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(mirrorAndUvX);
writer.Write(hideWithArmorAndUvY); writer.Write(helmetMaskAndUvY);
writer.Write(part.Scale); writer.Write(part.Scale);
} }
@@ -82,6 +88,40 @@ namespace PckStudio.ModelSupport.Internal.Format
return PSMParentType.LEG0; return PSMParentType.LEG0;
case "LEG1": case "LEG1":
return PSMParentType.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: default:
throw new InvalidDataException(type); throw new InvalidDataException(type);
} }

View File

@@ -51,13 +51,13 @@ namespace PckStudio.ModelSupport
internal static SkinModelInfo ImportPsm(string filepath) internal static SkinModelInfo ImportPsm(string filepath)
{ {
var reader = new PSMFileReader(); var reader = new PSMFileReader();
PSMFile csmbFile = reader.FromFile(filepath); PSMFile psmFile = reader.FromFile(filepath);
return new SkinModelInfo(null, csmbFile.SkinANIM, new(csmbFile.Parts, csmbFile.Offsets)); return new SkinModelInfo(null, psmFile.SkinANIM, psmFile.SkinFlags, new(psmFile.Parts, psmFile.Offsets));
} }
internal static void ExportPsm(string filepath, SkinModelInfo modelInfo) 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.Parts.AddRange(modelInfo.Model.AdditionalBoxes);
psmFile.Offsets.AddRange(modelInfo.Model.PartOffsets); psmFile.Offsets.AddRange(modelInfo.Model.PartOffsets);
var writer = new PSMFileWriter(psmFile); var writer = new PSMFileWriter(psmFile);
@@ -143,7 +143,7 @@ namespace PckStudio.ModelSupport
if (mask != SkinAnimMask.NONE) if (mask != SkinAnimMask.NONE)
skinANIM &= ~mask; skinANIM &= ~mask;
return new SkinModelInfo(texture, skinANIM, skinModel); return new SkinModelInfo(texture, skinANIM, SkinGameFlagsMask.HUMAN_SKIN, skinModel);
} }
private static IEnumerable<SkinBOX> ReadOutliner(string parentName, JArray oulineChildren, IReadOnlyCollection<Element> elements) private static IEnumerable<SkinBOX> ReadOutliner(string parentName, JArray oulineChildren, IReadOnlyCollection<Element> elements)

View File

@@ -12,12 +12,14 @@ namespace PckStudio.ModelSupport
{ {
public SkinModel Model { get; } public SkinModel Model { get; }
public SkinANIM Anim { get; } public SkinANIM Anim { get; }
public SkinGameFlags SkinFlags { get; }
public Image Texture { 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; Texture = texture;
Anim = anim; Anim = anim;
SkinFlags = skinFlags;
Model = model; Model = model;
} }
} }