Skip to content

Commit

Permalink
Update customize and equipment editing for Glamourer compatibility
Browse files Browse the repository at this point in the history
  • Loading branch information
chirpxiv committed Sep 27, 2023
1 parent 9545d49 commit ba110c0
Show file tree
Hide file tree
Showing 6 changed files with 118 additions and 63 deletions.
56 changes: 29 additions & 27 deletions Ktisis/Data/Files/AnamCharaFile.cs
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,8 @@ public void WriteToFile(Actor actor, SaveModes mode) {
ObjectKind = (ObjectKind)actor.GameObject.ObjectKind;

SaveMode = mode;

var custom = actor.GetCustomize();

if (IncludeSection(SaveModes.EquipmentWeapons, mode)) {
MainHand = new WeaponSave(actor.GetWeaponEquip(EquipSlot.MainHand));
Expand Down Expand Up @@ -137,48 +139,48 @@ public void WriteToFile(Actor actor, SaveModes mode) {
}

if (IncludeSection(SaveModes.AppearanceHair, mode)) {
Hair = actor.DrawData.Customize.HairStyle;
EnableHighlights = (actor.DrawData.Customize.HasHighlights & 0x80) != 0;
HairTone = actor.DrawData.Customize.HairColor;
Highlights = actor.DrawData.Customize.HairColor2;
Hair = custom.HairStyle;
EnableHighlights = (custom.HasHighlights & 0x80) != 0;
HairTone = custom.HairColor;
Highlights = custom.HairColor2;
/*HairColor = actor.ModelObject?.ExtendedAppearance?.HairColor;
HairGloss = actor.ModelObject?.ExtendedAppearance?.HairGloss;
HairHighlight = actor.ModelObject?.ExtendedAppearance?.HairHighlight;*/
}

if (IncludeSection(SaveModes.AppearanceFace, mode) || IncludeSection(SaveModes.AppearanceBody, mode)) {
Race = (AnamRace)actor.DrawData.Customize.Race;
Gender = actor.DrawData.Customize.Gender;
Tribe = (AnamTribe)actor.DrawData.Customize.Tribe;
Age = actor.DrawData.Customize.Age;
Race = (AnamRace)custom.Race;
Gender = custom.Gender;
Tribe = (AnamTribe)custom.Tribe;
Age = custom.Age;
}

if (IncludeSection(SaveModes.AppearanceFace, mode)) {
Head = actor.DrawData.Customize.FaceType;
REyeColor = actor.DrawData.Customize.EyeColor;
LimbalEyes = actor.DrawData.Customize.FaceFeaturesColor;
FacialFeatures = (AnamFacialFeature)actor.DrawData.Customize.FaceFeatures;
Eyebrows = actor.DrawData.Customize.Eyebrows;
LEyeColor = actor.DrawData.Customize.EyeColor2;
Eyes = actor.DrawData.Customize.EyeShape;
Nose = actor.DrawData.Customize.NoseShape;
Jaw = actor.DrawData.Customize.JawShape;
Mouth = actor.DrawData.Customize.LipStyle;
LipsToneFurPattern = actor.DrawData.Customize.LipColor;
FacePaint = (byte)actor.DrawData.Customize.Facepaint;
FacePaintColor = actor.DrawData.Customize.FacepaintColor;
Head = custom.FaceType;
REyeColor = custom.EyeColor;
LimbalEyes = custom.FaceFeaturesColor;
FacialFeatures = (AnamFacialFeature)custom.FaceFeatures;
Eyebrows = custom.Eyebrows;
LEyeColor = custom.EyeColor2;
Eyes = custom.EyeShape;
Nose = custom.NoseShape;
Jaw = custom.JawShape;
Mouth = custom.LipStyle;
LipsToneFurPattern = custom.LipColor;
FacePaint = (byte)custom.Facepaint;
FacePaintColor = custom.FacepaintColor;
/*LeftEyeColor = actor.ModelObject?.ExtendedAppearance?.LeftEyeColor;
RightEyeColor = actor.ModelObject?.ExtendedAppearance?.RightEyeColor;
LimbalRingColor = actor.ModelObject?.ExtendedAppearance?.LimbalRingColor;
MouthColor = actor.ModelObject?.ExtendedAppearance?.MouthColor;*/
}

if (IncludeSection(SaveModes.AppearanceBody, mode)) {
Height = actor.DrawData.Customize.Height;
Skintone = actor.DrawData.Customize.SkinColor;
EarMuscleTailSize = actor.DrawData.Customize.RaceFeatureSize;
TailEarsType = actor.DrawData.Customize.RaceFeatureType;
Bust = actor.DrawData.Customize.BustSize;
Height = custom.Height;
Skintone = custom.SkinColor;
EarMuscleTailSize = custom.RaceFeatureSize;
TailEarsType = custom.RaceFeatureType;
Bust = custom.BustSize;

unsafe { HeightMultiplier = actor.Model->Height; }

Expand Down Expand Up @@ -223,7 +225,7 @@ public unsafe void Apply(Actor* actor, SaveModes mode) {
LeftRing?.Write(actor, EquipIndex.RingLeft);
}

var custom = actor->DrawData.Customize;
var custom = actor->GetCustomize();

if (IncludeSection(SaveModes.AppearanceHair, mode)) {
if (Hair != null)
Expand Down
13 changes: 11 additions & 2 deletions Ktisis/Interface/Windows/ActorEdit/EditCustomize.cs
Original file line number Diff line number Diff line change
Expand Up @@ -135,12 +135,21 @@ private static void InvokeFeatureIcons(object[] args) {
public unsafe static void Draw() {
// Customize

var custom = Target->DrawData.Customize;
if (Target->ModelId != 0) {
ImGui.Text("Target actor must be a human to edit customization data.");
ImGui.Spacing();
if (ImGui.Button("Turn Human")) {
Target->ModelId = 0;
Target->Redraw();
}
return;
}

var custom = Target->GetCustomize();

if (custom.Race == 0 || custom.Tribe == 0) {
custom.Race = Race.Hyur;
custom.Tribe = Tribe.Highlander;
Target->DrawData.Customize = custom;
}

var index = custom.GetMakeIndex();
Expand Down
2 changes: 1 addition & 1 deletion Ktisis/Interface/Windows/Workspace/Tabs/ActorTab.cs
Original file line number Diff line number Diff line change
Expand Up @@ -155,4 +155,4 @@ public unsafe static void ImportExportChara(Actor* actor) {
ImGui.Spacing();
}
}
}
}
32 changes: 32 additions & 0 deletions Ktisis/Interop/Hooks/ActorHooks.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
using System;
using System.Linq;

using Dalamud.Hooking;
using Dalamud.Logging;

using FFXIVClientStructs.FFXIV.Client.Game.Object;
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;

using Ktisis.Structs.Actor;
using Ktisis.Interface.Windows.Workspace;
Expand All @@ -19,17 +24,44 @@ internal unsafe static IntPtr ControlGaze(nint a1) {
return ControlGazeHook.Original(a1);
}

internal static Hook<UpdateCustomizeDelegate> UpdateCustomizeHook = null!;
internal unsafe delegate bool UpdateCustomizeDelegate(ActorModel* self, Customize* custom, bool skipEquip);
internal unsafe static bool UpdateCustomizeDetour(ActorModel* self, Customize* custom, bool skipEquip) {
var exec = UpdateCustomizeHook.Original(self, custom, skipEquip);
if (!Ktisis.IsInGPose || !skipEquip) return exec;

var actors = Services.ObjectTable;
//.Where(x => x.Address != nint.Zero && x.ObjectIndex is >= 200 and < 240);

foreach (var actor in actors) {
var csActor = (GameObject*)actor.Address;
PluginLog.Information($"{actor.ObjectIndex} {(nint)csActor->DrawObject} == {(nint)self:X}");
if ((ActorModel*)csActor->DrawObject != self) continue;
PluginLog.Information($"Normalizing");
//Methods.NormalizeCustomize?.Invoke(&self->Customize, custom);
}

return exec;
}

// Init & Dispose

internal unsafe static void Init() {
var controlGaze = Services.SigScanner.ScanText("40 53 41 54 41 55 48 81 EC ?? ?? ?? ?? 48 8B D9");
ControlGazeHook = Hook<ControlGazeDelegate>.FromAddress(controlGaze, ControlGaze);
ControlGazeHook.Enable();

var updateCustom = Services.SigScanner.ScanText("E8 ?? ?? ?? ?? 83 BF ?? ?? ?? ?? ?? 75 34");
UpdateCustomizeHook = Hook<UpdateCustomizeDelegate>.FromAddress(updateCustom, UpdateCustomizeDetour);
//UpdateCustomizeHook.Enable();
}

internal static void Dispose() {
ControlGazeHook.Disable();
ControlGazeHook.Dispose();

UpdateCustomizeHook.Disable();
UpdateCustomizeHook.Dispose();
}
}
}
72 changes: 40 additions & 32 deletions Ktisis/Structs/Actor/Actor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -57,15 +57,21 @@ public unsafe void LookAt(Gaze* tar, GazeControl bodyPart) {
// Change equipment - no redraw method

public unsafe ItemEquip GetEquip(EquipIndex index)
=> this.Model != null ? this.Model->GetEquipSlot((int)index) : new();
=> this.Model != null ? this.Model->GetEquipSlot((int)index) : default;

public unsafe Customize GetCustomize()
=> this.Model != null ? this.Model->GetCustomize() ?? default : default;

public WeaponEquip GetWeaponEquip(EquipSlot slot)
=> slot == EquipSlot.MainHand ? this.DrawData.MainHand.GetEquip() : this.DrawData.OffHand.GetEquip();

public unsafe void Equip(EquipIndex index, ItemEquip item) {
if (Methods.ActorChangeEquip == null) return;
fixed (ActorDrawData* ptr = &DrawData)

fixed (ActorDrawData* ptr = &DrawData) {
Methods.ActorChangeEquip(ptr, index, (ItemEquip)0xFFFFFFFF);
Methods.ActorChangeEquip(ptr, index, item);
}
}

public void Equip(List<(EquipSlot, object)> items) {
Expand All @@ -78,52 +84,54 @@ public void Equip(List<(EquipSlot, object)> items) {

public unsafe void Equip(int slot, WeaponEquip item) {
if (Methods.ActorChangeWeapon == null) return;
fixed (ActorDrawData* ptr = &DrawData)
fixed (ActorDrawData* ptr = &DrawData) {
Methods.ActorChangeWeapon(ptr, slot, default, 0, 1, 0, 0);
Methods.ActorChangeWeapon(ptr, slot, item, 0, 1, 0, 0);
}
}

// Change customize - no redraw method

public unsafe bool UpdateCustomize() {
fixed (Customize* custom = &DrawData.Customize)
return ((Human*)Model)->UpdateDrawData((byte*)custom, true);
if (this.Model == null) return false;

var human = (Human*)this.Model;
var result = human->UpdateDrawData((byte*)&this.Model->Customize, true);
fixed (Customize* ptr = &DrawData.Customize)
return result | ((Human*)Model)->UpdateDrawData((byte*)ptr, true);
}

// Apply new customize

public unsafe void ApplyCustomize(Customize custom) {
var cur = DrawData.Customize;
DrawData.Customize = custom;
if (this.ModelId != 0) return;

var cur = GetCustomize();

// Fix UpdateCustomize on Carbuncles & Minions
if (DrawData.Customize.ModelType == 0)
DrawData.Customize.ModelType = 1;
if (custom.ModelType == 0)
custom.ModelType = 1;

if (custom.Race == Race.Viera) {
// avoid crash when loading invalid ears
var ears = custom.RaceFeatureType;
custom.RaceFeatureType = ears switch {
> 4 => 1,
0 => 4,
_ => ears
};
}

var faceHack = cur.FaceType != custom.FaceType;
if (cur.Race != custom.Race
|| cur.Tribe != custom.Tribe // Eye glitch.
|| cur.Gender != custom.Gender
|| cur.FaceType != custom.FaceType // Eye glitch.
) {
DrawData.Customize = custom;
var redraw = !UpdateCustomize()
|| faceHack
|| cur.Tribe != custom.Tribe;

if (redraw) {
Redraw(faceHack);
} else {
if (DrawData.Customize.Race == Race.Viera) {
// avoid crash when loading invalid ears
var ears = DrawData.Customize.RaceFeatureType;
DrawData.Customize.RaceFeatureType = ears switch {
> 4 => 1,
0 => 4,
_ => ears
};
}

var res = UpdateCustomize();
if (!res) {
Logger.Warning("Failed to update character. Forcing redraw.");
Redraw(faceHack);
} else if (cur.BustSize != custom.BustSize && Model != null) {
Model->ScaleBust();
}
} else if (cur.BustSize != custom.BustSize && Model != null) {
Model->ScaleBust();
}
}

Expand Down
6 changes: 5 additions & 1 deletion Ktisis/Structs/Actor/ActorModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
using System.Runtime.InteropServices;
using System.Collections.Generic;

using FFXIVClientStructs.FFXIV.Client.Game.Character;
using FFXIVClientStructs.Havok;
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
using FFXIVClientStructs.FFXIV.Client.Graphics.Render;
Expand Down Expand Up @@ -30,6 +29,8 @@ public struct ActorModel {

[FieldOffset(0x370)] public nint Sklb;

[FieldOffset(0x8F0)] public Customize Customize;

[FieldOffset(0x8F4)] public unsafe fixed uint DemiEquip[5];
[FieldOffset(0x910)] public unsafe fixed uint HumanEquip[10];

Expand All @@ -38,6 +39,9 @@ public struct ActorModel {
return (CharacterBase*)self;
}

public unsafe Customize? GetCustomize()
=> AsCharacter()->GetModelType() == ModelType.Human ? this.Customize : null;

public unsafe ItemEquip GetEquipSlot(int slot) => AsCharacter()->GetModelType() switch {
ModelType.Human => (ItemEquip)this.HumanEquip[slot],
ModelType.DemiHuman => slot < 5 ? (ItemEquip)this.DemiEquip[slot] : default,
Expand Down

0 comments on commit ba110c0

Please sign in to comment.