Allow import of .cma files.

This commit is contained in:
Ottermandias 2023-12-01 14:38:51 +01:00
parent 60a53d4bff
commit cdefe64e4e
2 changed files with 147 additions and 4 deletions

View file

@ -0,0 +1,111 @@
using System;
using Glamourer.Designs;
using Glamourer.Services;
using Newtonsoft.Json.Linq;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
namespace Glamourer.Interop.CharaFile;
public sealed class CmaFile
{
public string Name = string.Empty;
public DesignData Data = new();
public static CmaFile? ParseData(ItemManager items, string data, string? name = null)
{
try
{
var jObj = JObject.Parse(data);
var ret = new CmaFile();
ret.Data.SetDefaultEquipment(items);
ParseMainHand(items, jObj, ref ret.Data);
ParseOffHand(items, jObj, ref ret.Data);
ret.Name = jObj["Description"]?.ToObject<string>() ?? name ?? "New Design";
ParseEquipment(items, jObj, ref ret.Data);
ParseCustomization(jObj, ref ret.Data);
return ret;
}
catch
{
return null;
}
}
private static unsafe void ParseCustomization(JObject jObj, ref DesignData data)
{
var bytes = jObj["CharacterBytes"]?.ToObject<string>() ?? string.Empty;
if (bytes.Length is not 26 * 3 - 1)
return;
bytes = bytes.Replace(" ", string.Empty);
var byteData = Convert.FromHexString(bytes);
fixed (byte* ptr = byteData)
{
data.Customize.Data.Read(ptr);
}
}
private static unsafe void ParseEquipment(ItemManager items, JObject jObj, ref DesignData data)
{
var bytes = jObj["EquipmentBytes"]?.ToObject<string>() ?? string.Empty;
bytes = bytes.Replace(" ", string.Empty);
var byteData = Convert.FromHexString(bytes);
fixed (byte* ptr = byteData)
{
foreach (var slot in EquipSlotExtensions.EqdpSlots)
{
var idx = slot.ToIndex();
if (idx * 4 + 3 >= byteData.Length)
continue;
var armor = ((CharacterArmor*)ptr)[idx];
var item = items.Identify(slot, armor.Set, armor.Variant);
data.SetItem(slot, item);
data.SetStain(slot, armor.Stain);
}
data.Customize.Data.Read(ptr);
}
}
private static void ParseMainHand(ItemManager items, JObject jObj, ref DesignData data)
{
var mainhand = jObj["MainHand"];
if (mainhand == null)
{
data.SetItem(EquipSlot.MainHand, items.DefaultSword);
data.SetStain(EquipSlot.MainHand, 0);
return;
}
var set = mainhand["Item1"]?.ToObject<ushort>() ?? items.DefaultSword.ModelId;
var type = mainhand["Item2"]?.ToObject<ushort>() ?? items.DefaultSword.WeaponType;
var variant = mainhand["Item3"]?.ToObject<byte>() ?? items.DefaultSword.Variant;
var stain = mainhand["Item4"]?.ToObject<byte>() ?? 0;
var item = items.Identify(EquipSlot.MainHand, set, type, variant);
data.SetItem(EquipSlot.MainHand, item.Valid ? item : items.DefaultSword);
data.SetStain(EquipSlot.MainHand, stain);
}
private static void ParseOffHand(ItemManager items, JObject jObj, ref DesignData data)
{
var offhand = jObj["OffHand"];
var defaultOffhand = items.GetDefaultOffhand(data.Item(EquipSlot.MainHand));
if (offhand == null)
{
data.SetItem(EquipSlot.MainHand, defaultOffhand);
data.SetStain(EquipSlot.MainHand, defaultOffhand.ModelId.Id == 0 ? 0 : data.Stain(EquipSlot.MainHand));
return;
}
var set = offhand["Item1"]?.ToObject<ushort>() ?? items.DefaultSword.ModelId;
var type = offhand["Item2"]?.ToObject<ushort>() ?? items.DefaultSword.WeaponType;
var variant = offhand["Item3"]?.ToObject<byte>() ?? items.DefaultSword.Variant;
var stain = offhand["Item4"]?.ToObject<byte>() ?? 0;
var item = items.Identify(EquipSlot.OffHand, set, type, variant, data.MainhandType);
data.SetItem(EquipSlot.OffHand, item.Valid ? item : defaultOffhand);
data.SetStain(EquipSlot.OffHand, defaultOffhand.ModelId.Id == 0 ? 0 : (StainId)stain);
}
}

View file

@ -6,7 +6,9 @@ using Dalamud.Interface.DragDrop;
using Dalamud.Interface.Internal.Notifications; using Dalamud.Interface.Internal.Notifications;
using Glamourer.Customization; using Glamourer.Customization;
using Glamourer.Designs; using Glamourer.Designs;
using Glamourer.Interop.CharaFile;
using Glamourer.Services; using Glamourer.Services;
using Glamourer.Structs;
using ImGuiNET; using ImGuiNET;
using OtterGui.Classes; using OtterGui.Classes;
@ -22,9 +24,9 @@ public class ImportService(CustomizationService _customizations, IDragDropManage
}); });
public void CreateCharaSource() public void CreateCharaSource()
=> _dragDropManager.CreateImGuiSource("CharaDragger", m => m.Files.Count == 1 && m.Extensions.Contains(".chara"), m => => _dragDropManager.CreateImGuiSource("CharaDragger", m => m.Files.Count == 1 && m.Extensions.Contains(".chara") || m.Extensions.Contains(".cma"), m =>
{ {
ImGui.TextUnformatted($"Dragging {Path.GetFileName(m.Files[0])} to import Anamnesis data for Glamourer..."); ImGui.TextUnformatted($"Dragging {Path.GetFileName(m.Files[0])} to import Anamnesis/CMTool data for Glamourer...");
return true; return true;
}); });
@ -47,8 +49,8 @@ public class ImportService(CustomizationService _customizations, IDragDropManage
name = string.Empty; name = string.Empty;
return false; return false;
} }
return LoadChara(files[0], out design, out name); return Path.GetExtension(files[0]) is ".chara" ? LoadChara(files[0], out design, out name) : LoadCma(files[0], out design, out name);
} }
public bool LoadChara(string path, [NotNullWhen(true)] out DesignBase? design, out string name) public bool LoadChara(string path, [NotNullWhen(true)] out DesignBase? design, out string name)
@ -81,6 +83,36 @@ public class ImportService(CustomizationService _customizations, IDragDropManage
return true; return true;
} }
public bool LoadCma(string path, [NotNullWhen(true)] out DesignBase? design, out string name)
{
if (!File.Exists(path))
{
design = null;
name = string.Empty;
return false;
}
try
{
var text = File.ReadAllText(path);
var file = CmaFile.ParseData(_items, text, Path.GetFileNameWithoutExtension(path));
if (file == null)
throw new Exception();
name = file.Name;
design = new DesignBase(_customizations, file.Data, EquipFlagExtensions.All, CustomizeFlagExtensions.AllRelevant);
}
catch (Exception ex)
{
Glamourer.Messager.NotificationMessage(ex, $"Could not read .cma file {path}.", NotificationType.Error);
design = null;
name = string.Empty;
return false;
}
return true;
}
public bool LoadDat(string path, out DatCharacterFile file) public bool LoadDat(string path, out DatCharacterFile file)
{ {
if (!File.Exists(path)) if (!File.Exists(path))