Add option to import .chara files as design or onto actors/designs.

This commit is contained in:
Ottermandias 2023-11-21 17:40:06 +01:00
parent 226dbdd4a8
commit 321c481c7d
8 changed files with 412 additions and 48 deletions

View file

@ -32,6 +32,16 @@ public class DesignBase
CustomizationSet = SetCustomizationSet(customize);
}
internal DesignBase(CustomizationService customize, in DesignData designData, EquipFlag equipFlags, CustomizeFlag customizeFlags)
{
_designData = designData;
ApplyCustomize = customizeFlags & CustomizeFlagExtensions.AllRelevant;
ApplyEquip = equipFlags & EquipFlagExtensions.All;
_designFlags = 0;
CustomizationSet = SetCustomizationSet(customize);
}
internal DesignBase(DesignBase clone)
{
_designData = clone._designData;

View file

@ -36,7 +36,7 @@ public class ActorPanel
private readonly DesignConverter _converter;
private readonly ObjectManager _objects;
private readonly DesignManager _designManager;
private readonly DatFileService _datFileService;
private readonly ImportService _importService;
private readonly ICondition _conditions;
private ActorIdentifier _identifier;
@ -48,7 +48,7 @@ public class ActorPanel
public ActorPanel(ActorSelector selector, StateManager stateManager, CustomizationDrawer customizationDrawer,
EquipmentDrawer equipmentDrawer, IdentifierService identification, AutoDesignApplier autoDesignApplier,
Configuration config, DesignConverter converter, ObjectManager objects, DesignManager designManager, DatFileService datFileService,
Configuration config, DesignConverter converter, ObjectManager objects, DesignManager designManager, ImportService importService,
ICondition conditions)
{
_selector = selector;
@ -61,7 +61,7 @@ public class ActorPanel
_converter = converter;
_objects = objects;
_designManager = designManager;
_datFileService = datFileService;
_importService = importService;
_conditions = conditions;
}
@ -81,9 +81,21 @@ public class ActorPanel
if (_state is not { IsLocked: false })
return;
if (_datFileService.CreateImGuiTarget(out var dat))
if (_importService.CreateDatTarget(out var dat))
{
_stateManager.ChangeCustomize(_state!, dat.Customize, CustomizeApplicationFlags, StateChanged.Source.Manual);
_datFileService.CreateSource();
Glamourer.Messager.NotificationMessage($"Applied games .dat file {dat.Description} customizations to {_state.Identifier}.",
NotificationType.Success, false);
}
else if (_importService.CreateCharaTarget(out var designBase, out var name))
{
_stateManager.ApplyDesign(designBase, _state!, StateChanged.Source.Manual);
Glamourer.Messager.NotificationMessage($"Applied Anamnesis .chara file {name} to {_state.Identifier}.", NotificationType.Success,
false);
}
_importService.CreateDatSource();
_importService.CreateCharaSource();
}
private void DrawHeader()

View file

@ -50,7 +50,7 @@ public unsafe class DebugTab : ITab
private readonly ObjectManager _objectManager;
private readonly GlamourerIpc _ipc;
private readonly CodeService _code;
private readonly DatFileService _datFileService;
private readonly ImportService _importService;
private readonly ItemManager _items;
private readonly ActorService _actors;
@ -81,7 +81,7 @@ public unsafe class DebugTab : ITab
DesignFileSystem designFileSystem, DesignManager designManager, StateManager state, Configuration config,
PenumbraChangedItemTooltip penumbraTooltip, MetaService metaService, GlamourerIpc ipc, DalamudPluginInterface pluginInterface,
AutoDesignManager autoDesignManager, JobService jobs, CodeService code, CustomizeUnlockManager customizeUnlocks,
ItemUnlockManager itemUnlocks, DesignConverter designConverter, DatFileService datFileService, InventoryService inventoryService,
ItemUnlockManager itemUnlocks, DesignConverter designConverter, ImportService importService, InventoryService inventoryService,
HumanModelList humans, FunModule funModule)
{
_changeCustomizeService = changeCustomizeService;
@ -107,7 +107,7 @@ public unsafe class DebugTab : ITab
_customizeUnlocks = customizeUnlocks;
_itemUnlocks = itemUnlocks;
_designConverter = designConverter;
_datFileService = datFileService;
_importService = importService;
_inventoryService = inventoryService;
_humans = humans;
_funModule = funModule;
@ -284,10 +284,10 @@ public unsafe class DebugTab : ITab
ImGui.InputTextWithHint("##datFilePath", "Dat File Path...", ref _datFilePath, 256);
var exists = _datFilePath.Length > 0 && File.Exists(_datFilePath);
if (ImGuiUtil.DrawDisabledButton("Load##Dat", Vector2.Zero, string.Empty, !exists))
_datFile = _datFileService.LoadDesign(_datFilePath, out var tmp) ? tmp : null;
_datFile = _importService.LoadDat(_datFilePath, out var tmp) ? tmp : null;
if (ImGuiUtil.DrawDisabledButton("Save##Dat", Vector2.Zero, string.Empty, _datFilePath.Length == 0 || _datFile == null))
_datFileService.SaveDesign(_datFilePath, _datFile!.Value.Customize, _datFile!.Value.Description);
_importService.SaveDesignAsDat(_datFilePath, _datFile!.Value.Customize, _datFile!.Value.Description);
if (_datFile != null)
{

View file

@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using System.Xml.Linq;
using Dalamud.Interface;
using Dalamud.Interface.ImGuiFileDialog;
using Dalamud.Interface.Internal.Notifications;
@ -25,7 +26,7 @@ namespace Glamourer.Gui.Tabs.DesignTab;
public class DesignPanel(DesignFileSystemSelector _selector, CustomizationDrawer _customizationDrawer, DesignManager _manager,
ObjectManager _objects, StateManager _state, EquipmentDrawer _equipmentDrawer, ModAssociationsTab _modAssociations,
DesignDetailTab _designDetails, DesignConverter _converter, DatFileService _datFileService, MultiDesignPanel _multiDesignPanel)
DesignDetailTab _designDetails, DesignConverter _converter, ImportService _importService, MultiDesignPanel _multiDesignPanel)
{
private readonly FileDialogManager _fileDialog = new();
@ -299,16 +300,22 @@ public class DesignPanel(DesignFileSystemSelector _selector, CustomizationDrawer
if (_selector.Selected == null || _selector.Selected.WriteProtected())
return;
if (_datFileService.CreateImGuiTarget(out var dat))
if (_importService.CreateDatTarget(out var dat))
{
_manager.ChangeCustomize(_selector.Selected!, CustomizeIndex.Clan, dat.Customize[CustomizeIndex.Clan]);
_manager.ChangeCustomize(_selector.Selected!, CustomizeIndex.Gender, dat.Customize[CustomizeIndex.Gender]);
foreach (var idx in CustomizationExtensions.AllBasic)
_manager.ChangeCustomize(_selector.Selected!, idx, dat.Customize[idx]);
Glamourer.Messager.NotificationMessage($"Applied games .dat file {dat.Description} customizations to {_selector.Selected.Name}.", NotificationType.Success, false);
}
else if (_importService.CreateCharaTarget(out var designBase, out var name))
{
_manager.ApplyDesign(_selector.Selected!, designBase);
Glamourer.Messager.NotificationMessage($"Applied Anamnesis .chara file {name} to {_selector.Selected.Name}.", NotificationType.Success, false);
}
}
_datFileService.CreateSource();
_importService.CreateDatSource();
}
private void DrawPanel()
@ -417,7 +424,7 @@ public class DesignPanel(DesignFileSystemSelector _selector, CustomizationDrawer
private void DrawSaveToDat()
{
var verified = _datFileService.Verify(_selector.Selected!.DesignData.Customize, out _);
var verified = _importService.Verify(_selector.Selected!.DesignData.Customize, out _);
var tt = verified
? "Export the currently configured customizations of this design to a character creation data file."
: "The current design contains customizations that can not be applied during character creation.";
@ -428,7 +435,7 @@ public class DesignPanel(DesignFileSystemSelector _selector, CustomizationDrawer
_fileDialog.SaveFileDialog("Save File...", ".dat", "FFXIV_CHARA_01.dat", ".dat", (v, path) =>
{
if (v && _selector.Selected != null)
_datFileService.SaveDesign(path, _selector.Selected!.DesignData.Customize, _selector.Selected!.Name);
_importService.SaveDesignAsDat(path, _selector.Selected!.DesignData.Customize, _selector.Selected!.Name);
}, startPath);
_fileDialog.Draw();

View file

@ -1,29 +1,32 @@
using System;
using Dalamud.Interface.Internal.Notifications;
using Dalamud.Interface.Utility;
using Glamourer.Designs;
using Glamourer.Interop;
using ImGuiNET;
using OtterGui.Classes;
using OtterGui.Widgets;
namespace Glamourer.Gui.Tabs.DesignTab;
public class DesignTab : ITab
public class DesignTab(DesignFileSystemSelector _selector, DesignPanel _panel, ImportService _importService, DesignManager _manager)
: ITab
{
public readonly DesignFileSystemSelector Selector;
private readonly DesignPanel _panel;
public DesignTab(DesignFileSystemSelector selector, DesignPanel panel)
{
Selector = selector;
_panel = panel;
}
public ReadOnlySpan<byte> Label
=> "Designs"u8;
public void DrawContent()
{
Selector.Draw(GetDesignSelectorSize());
_selector.Draw(GetDesignSelectorSize());
if (_importService.CreateCharaTarget(out var designBase, out var name))
{
var newDesign = _manager.CreateClone(designBase, name, true);
Glamourer.Messager.NotificationMessage($"Imported Anamnesis .chara file {name} as new design {newDesign.Name}", NotificationType.Success, false);
}
ImGui.SameLine();
_panel.Draw();
_importService.CreateCharaSource();
}
public float GetDesignSelectorSize()

View file

@ -0,0 +1,295 @@
using System;
using Glamourer.Customization;
using Glamourer.Designs;
using Glamourer.Services;
using Glamourer.Structs;
using Newtonsoft.Json.Linq;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
using Race = Penumbra.GameData.Enums.Race;
namespace Glamourer.Interop.CharaFile;
public sealed class CharaFile
{
public string Name = string.Empty;
public DesignData Data = new();
public CustomizeFlag ApplyCustomize;
public EquipFlag ApplyEquip;
public static CharaFile? ParseData(ItemManager items, string data, string? name = null)
{
try
{
var jObj = JObject.Parse(data);
SanityCheck(jObj);
var ret = new CharaFile();
ret.Data.SetDefaultEquipment(items);
ret.Data.ModelId = ParseModelId(jObj);
ret.Name = jObj["Nickname"]?.ToObject<string>() ?? name ?? "New Design";
ret.ApplyCustomize = ParseCustomize(jObj, ref ret.Data.Customize);
ret.ApplyEquip = ParseEquipment(items, jObj, ref ret.Data);
return ret;
}
catch (Exception ex)
{
return null;
}
}
private static EquipFlag ParseEquipment(ItemManager items, JObject jObj, ref DesignData data)
{
EquipFlag ret = 0;
ParseWeapon(items, jObj, "MainHand", EquipSlot.MainHand, ref data, ref ret);
ParseWeapon(items, jObj, "OffHand", EquipSlot.OffHand, ref data, ref ret);
ParseGear(items, jObj, "HeadGear", EquipSlot.Head, ref data, ref ret);
ParseGear(items, jObj, "Body", EquipSlot.Body, ref data, ref ret);
ParseGear(items, jObj, "Hands", EquipSlot.Hands, ref data, ref ret);
ParseGear(items, jObj, "Legs", EquipSlot.Legs, ref data, ref ret);
ParseGear(items, jObj, "Feet", EquipSlot.Feet, ref data, ref ret);
ParseGear(items, jObj, "Ears", EquipSlot.Ears, ref data, ref ret);
ParseGear(items, jObj, "Neck", EquipSlot.Neck, ref data, ref ret);
ParseGear(items, jObj, "Wrists", EquipSlot.Wrists, ref data, ref ret);
ParseGear(items, jObj, "LeftRing", EquipSlot.LFinger, ref data, ref ret);
ParseGear(items, jObj, "RightRing", EquipSlot.RFinger, ref data, ref ret);
return ret;
}
private static void ParseWeapon(ItemManager items, JObject jObj, string property, EquipSlot slot, ref DesignData data, ref EquipFlag flags)
{
var jTok = jObj[property];
if (jTok == null)
return;
var set = jTok["ModelSet"]?.ToObject<ushort>() ?? 0;
var type = jTok["ModelBase"]?.ToObject<ushort>() ?? 0;
var variant = jTok["ModelVariant"]?.ToObject<byte>() ?? 0;
var dye = jTok["DyeId"]?.ToObject<byte>() ?? 0;
var item = items.Identify(slot, set, type, variant, slot is EquipSlot.OffHand ? data.MainhandType : FullEquipType.Unknown);
if (!item.Valid)
return;
data.SetItem(slot, item);
data.SetStain(slot, (StainId)dye);
flags |= slot.ToFlag();
flags |= slot.ToStainFlag();
}
private static void ParseGear(ItemManager items, JObject jObj, string property, EquipSlot slot, ref DesignData data, ref EquipFlag flags)
{
var jTok = jObj[property];
if (jTok == null)
return;
var set = jTok["ModelBase"]?.ToObject<ushort>() ?? 0;
var variant = jTok["ModelVariant"]?.ToObject<byte>() ?? 0;
var dye = jTok["DyeId"]?.ToObject<byte>() ?? 0;
var item = items.Identify(slot, set, variant);
if (!item.Valid)
return;
data.SetItem(slot, item);
data.SetStain(slot, dye);
flags |= slot.ToFlag();
flags |= slot.ToStainFlag();
}
private static CustomizeFlag ParseCustomize(JObject jObj, ref Customize customize)
{
CustomizeFlag ret = 0;
customize.Race = ParseRace(jObj, ref ret);
customize.Gender = ParseGender(jObj, ref ret);
customize.Clan = ParseTribe(jObj, ref ret);
ParseByte(jObj, "Height", CustomizeIndex.Height, ref customize, ref ret);
ParseByte(jObj, "Head", CustomizeIndex.Face, ref customize, ref ret);
ParseByte(jObj, "Hair", CustomizeIndex.Hairstyle, ref customize, ref ret);
ParseHighlights(jObj, ref customize, ref ret);
ParseByte(jObj, "Skintone", CustomizeIndex.SkinColor, ref customize, ref ret);
ParseByte(jObj, "REyeColor", CustomizeIndex.EyeColorRight, ref customize, ref ret);
ParseByte(jObj, "HairTone", CustomizeIndex.HairColor, ref customize, ref ret);
ParseByte(jObj, "Highlights", CustomizeIndex.HighlightsColor, ref customize, ref ret);
ParseFacial(jObj, ref customize, ref ret);
ParseByte(jObj, "LimbalEyes", CustomizeIndex.TattooColor, ref customize, ref ret);
ParseByte(jObj, "Eyebrows", CustomizeIndex.Eyebrows, ref customize, ref ret);
ParseByte(jObj, "LEyeColor", CustomizeIndex.EyeColorLeft, ref customize, ref ret);
ParseByte(jObj, "Eyes", CustomizeIndex.EyeShape, ref customize, ref ret);
ParseByte(jObj, "Nose", CustomizeIndex.Nose, ref customize, ref ret);
ParseByte(jObj, "Jaw", CustomizeIndex.Jaw, ref customize, ref ret);
ParseByte(jObj, "Mouth", CustomizeIndex.Mouth, ref customize, ref ret);
ParseByte(jObj, "LipsToneFurPattern", CustomizeIndex.LipColor, ref customize, ref ret);
ParseByte(jObj, "EarMuscleTailSize", CustomizeIndex.MuscleMass, ref customize, ref ret);
ParseByte(jObj, "TailEarsType", CustomizeIndex.TailShape, ref customize, ref ret);
ParseByte(jObj, "Bust", CustomizeIndex.BustSize, ref customize, ref ret);
ParseByte(jObj, "FacePaint", CustomizeIndex.FacePaint, ref customize, ref ret);
ParseByte(jObj, "FacePaintColor", CustomizeIndex.FacePaintColor, ref customize, ref ret);
ParseAge(jObj);
if (ret.HasFlag(CustomizeFlag.EyeShape))
ret |= CustomizeFlag.SmallIris;
if (ret.HasFlag(CustomizeFlag.Mouth))
ret |= CustomizeFlag.Lipstick;
if (ret.HasFlag(CustomizeFlag.FacePaint))
ret |= CustomizeFlag.FacePaintReversed;
return ret;
}
private static uint ParseModelId(JObject jObj)
{
var jTok = jObj["ModelType"];
if (jTok == null)
throw new Exception("No Model ID given.");
var id = jTok.ToObject<uint>();
if (id != 0)
throw new Exception($"Model ID {id} != 0 not supported.");
return id;
}
private static void ParseFacial(JObject jObj, ref Customize customize, ref CustomizeFlag application)
{
var jTok = jObj["FacialFeatures"];
if (jTok == null)
return;
application |= CustomizeFlag.FacialFeature1
| CustomizeFlag.FacialFeature2
| CustomizeFlag.FacialFeature3
| CustomizeFlag.FacialFeature4
| CustomizeFlag.FacialFeature5
| CustomizeFlag.FacialFeature6
| CustomizeFlag.FacialFeature7
| CustomizeFlag.LegacyTattoo;
var value = jTok.ToObject<string>()!;
if (value is "None")
return;
if (value.Contains("First"))
customize[CustomizeIndex.FacialFeature1] = CustomizeValue.Max;
if (value.Contains("Second"))
customize[CustomizeIndex.FacialFeature2] = CustomizeValue.Max;
if (value.Contains("Third"))
customize[CustomizeIndex.FacialFeature3] = CustomizeValue.Max;
if (value.Contains("Fourth"))
customize[CustomizeIndex.FacialFeature4] = CustomizeValue.Max;
if (value.Contains("Fifth"))
customize[CustomizeIndex.FacialFeature5] = CustomizeValue.Max;
if (value.Contains("Sixth"))
customize[CustomizeIndex.FacialFeature6] = CustomizeValue.Max;
if (value.Contains("Seventh"))
customize[CustomizeIndex.FacialFeature7] = CustomizeValue.Max;
if (value.Contains("LegacyTattoo"))
customize[CustomizeIndex.LegacyTattoo] = CustomizeValue.Max;
}
private static void ParseHighlights(JObject jObj, ref Customize customize, ref CustomizeFlag application)
{
var jTok = jObj["EnableHighlights"];
if (jTok == null)
return;
var value = jTok.ToObject<bool>();
application |= CustomizeFlag.Highlights;
customize[CustomizeIndex.Highlights] = value ? CustomizeValue.Max : CustomizeValue.Zero;
}
private static Race ParseRace(JObject jObj, ref CustomizeFlag application)
{
var race = jObj["Race"]?.ToObject<string>() switch
{
null => Race.Unknown,
"Hyur" => Race.Hyur,
"Elezen" => Race.Elezen,
"Lalafel" => Race.Lalafell,
"Miqote" => Race.Miqote,
"Roegadyn" => Race.Roegadyn,
"AuRa" => Race.AuRa,
"Hrothgar" => Race.Hrothgar,
"Viera" => Race.Viera,
_ => throw new Exception($"Invalid Race value {jObj["Race"]?.ToObject<string>()}."),
};
if (race == Race.Unknown)
return Race.Hyur;
application |= CustomizeFlag.Race;
return race;
}
private static Gender ParseGender(JObject jObj, ref CustomizeFlag application)
{
var gender = jObj["Gender"]?.ToObject<string>() switch
{
null => Gender.Unknown,
"Masculine" => Gender.Male,
"Feminine" => Gender.Female,
_ => throw new Exception($"Invalid Gender value {jObj["Gender"]?.ToObject<string>()}."),
};
if (gender == Gender.Unknown)
return Gender.Male;
application |= CustomizeFlag.Gender;
return gender;
}
private static void ParseAge(JObject jObj)
{
var age = jObj["Age"]?.ToObject<string>();
if (age is not null and not "Normal")
throw new Exception($"Age {age} != Normal is not supported.");
}
private static unsafe void ParseByte(JObject jObj, string property, CustomizeIndex idx, ref Customize customize,
ref CustomizeFlag application)
{
var jTok = jObj[property];
if (jTok == null)
return;
customize.Data.Data[idx.ToByteAndMask().ByteIdx] = jTok.ToObject<byte>();
application |= idx.ToFlag();
}
private static SubRace ParseTribe(JObject jObj, ref CustomizeFlag application)
{
var tribe = jObj["Tribe"]?.ToObject<string>() switch
{
null => SubRace.Unknown,
"Midlander" => SubRace.Midlander,
"Highlander" => SubRace.Highlander,
"Wildwood" => SubRace.Wildwood,
"Duskwight" => SubRace.Duskwight,
"Plainsfolk" => SubRace.Plainsfolk,
"Dunesfolk" => SubRace.Dunesfolk,
"SeekerOfTheSun" => SubRace.SeekerOfTheSun,
"KeeperOfTheMoon" => SubRace.KeeperOfTheMoon,
"SeaWolf" => SubRace.Seawolf,
"Hellsguard" => SubRace.Hellsguard,
"Raen" => SubRace.Raen,
"Xaela" => SubRace.Xaela,
"Helions" => SubRace.Helion,
"TheLost" => SubRace.Lost,
"Rava" => SubRace.Rava,
"Veena" => SubRace.Veena,
_ => throw new Exception($"Invalid Tribe value {jObj["Tribe"]?.ToObject<string>()}."),
};
if (tribe == SubRace.Unknown)
return SubRace.Midlander;
application |= CustomizeFlag.Clan;
return tribe;
}
private static void SanityCheck(JObject jObj)
{
if (jObj["TypeName"]?.ToObject<string>() is not "Anamnesis Character File")
throw new Exception("Wrong TypeName property set.");
var type = jObj["ObjectKind"]?.ToObject<string>();
if (type is not "Player")
throw new Exception($"ObjectKind {type} != Player is not supported.");
}
}

View file

@ -1,39 +1,34 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using Dalamud.Interface.DragDrop;
using Dalamud.Interface.Internal.Notifications;
using Glamourer.Customization;
using Glamourer.Designs;
using Glamourer.Services;
using Glamourer.Unlocks;
using ImGuiNET;
using OtterGui.Classes;
namespace Glamourer.Interop;
public class DatFileService
public class ImportService(CustomizationService _customizations, IDragDropManager _dragDropManager, ItemManager _items)
{
private readonly CustomizationService _customizations;
private readonly CustomizeUnlockManager _unlocks;
private readonly IDragDropManager _dragDropManager;
public DatFileService(CustomizationService customizations, CustomizeUnlockManager unlocks, IDragDropManager dragDropManager)
{
_customizations = customizations;
_unlocks = unlocks;
_dragDropManager = dragDropManager;
}
public void CreateSource()
{
_dragDropManager.CreateImGuiSource("DatDragger", m => m.Files.Count == 1 && m.Extensions.Contains(".dat"), m =>
public void CreateDatSource()
=> _dragDropManager.CreateImGuiSource("DatDragger", m => m.Files.Count == 1 && m.Extensions.Contains(".dat"), m =>
{
ImGui.TextUnformatted($"Dragging {Path.GetFileName(m.Files[0])} to import customizations for Glamourer...");
return true;
});
}
public bool CreateImGuiTarget(out DatCharacterFile file)
public void CreateCharaSource()
=> _dragDropManager.CreateImGuiSource("CharaDragger", m => m.Files.Count == 1 && m.Extensions.Contains(".chara"), m =>
{
ImGui.TextUnformatted($"Dragging {Path.GetFileName(m.Files[0])} to import Anamnesis data for Glamourer...");
return true;
});
public bool CreateDatTarget(out DatCharacterFile file)
{
if (!_dragDropManager.CreateImGuiTarget("DatDragger", out var files, out _) || files.Count != 1)
{
@ -41,10 +36,52 @@ public class DatFileService
return false;
}
return LoadDesign(files[0], out file);
return LoadDat(files[0], out file);
}
public bool LoadDesign(string path, out DatCharacterFile file)
public bool CreateCharaTarget([NotNullWhen(true)] out DesignBase? design, out string name)
{
if (!_dragDropManager.CreateImGuiTarget("CharaDragger", out var files, out _) || files.Count != 1)
{
design = null;
name = string.Empty;
return false;
}
return LoadChara(files[0], out design, out name);
}
public bool LoadChara(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 = CharaFile.CharaFile.ParseData(_items, text, Path.GetFileNameWithoutExtension(path));
if (file == null)
throw new Exception();
name = file.Name;
design = new DesignBase(_customizations, file.Data, file.ApplyEquip, file.ApplyCustomize);
}
catch (Exception ex)
{
Glamourer.Messager.NotificationMessage(ex, $"Could not read .chara file {path}.", NotificationType.Error);
design = null;
name = string.Empty;
return false;
}
return true;
}
public bool LoadDat(string path, out DatCharacterFile file)
{
if (!File.Exists(path))
{
@ -70,7 +107,7 @@ public class DatFileService
return true;
}
public bool SaveDesign(string path, in Customize input, string description)
public bool SaveDesignAsDat(string path, in Customize input, string description)
{
if (!Verify(input, out var voice))
return false;

View file

@ -96,7 +96,7 @@ public static class ServiceManager
.AddSingleton<JobService>()
.AddSingleton<CustomizeUnlockManager>()
.AddSingleton<ItemUnlockManager>()
.AddSingleton<DatFileService>()
.AddSingleton<ImportService>()
.AddSingleton<InventoryService>()
.AddSingleton<ContextMenuService>()
.AddSingleton<ScalingService>();