diff --git a/OpenKh.AssimpUtils/Kh2MdlxAssimp.cs b/OpenKh.AssimpUtils/Kh2MdlxAssimp.cs index d7a7882ab..699d8100e 100644 --- a/OpenKh.AssimpUtils/Kh2MdlxAssimp.cs +++ b/OpenKh.AssimpUtils/Kh2MdlxAssimp.cs @@ -2,8 +2,9 @@ using OpenKh.Kh2; using OpenKh.Kh2.Models; using OpenKh.Kh2.Models.VIF; +using OpenKh.Kh2Anim.Mset; +using OpenKh.Kh2Anim.Mset.Interfaces; using System.Numerics; -using OpenKh.Engine.Monogame.Helpers; namespace OpenKh.AssimpUtils { @@ -120,7 +121,7 @@ public static Assimp.Scene getAssimpScene(MdlxParser mParser) parentNode.Children.Add(boneNode); } - MatrixRecursivity.ComputeMatrices(ref matricesToReverse, mParser); + OpenKh.Engine.Monogame.Helpers.MatrixRecursivity.ComputeMatrices(ref matricesToReverse, mParser); foreach (Assimp.Mesh mesh in scene.Meshes) { @@ -242,6 +243,8 @@ public static Assimp.Scene getAssimpScene(ModelSkeletal model) } // BONES (Node hierarchy) + //Assimp.Node armatureNode = new Assimp.Node("Armature"); + //scene.RootNode.Children.Add(armatureNode); foreach (ModelCommon.Bone bone in model.Bones) { string boneName = "Bone" + bone.Index.ToString("D4"); @@ -250,6 +253,7 @@ public static Assimp.Scene getAssimpScene(ModelSkeletal model) Assimp.Node parentNode; if (bone.ParentIndex == -1) { + //parentNode = armatureNode; parentNode = scene.RootNode; } else @@ -363,10 +367,170 @@ public static VifMesh getVifMeshFromAssimp(Assimp.Mesh mesh, Matrix4x4[] boneMat return vifMesh; } + public static void AddAnimation(Assimp.Scene assimpScene, Bar mdlxBar, AnimationBinary animation) + { + // Set basic data + Assimp.Animation assimpAnimation = new Assimp.Animation(); + assimpAnimation.Name = "EXPORT"; + assimpAnimation.DurationInTicks = animation.MotionFile.InterpolatedMotionHeader.FrameCount; + assimpAnimation.TicksPerSecond = animation.MotionFile.InterpolatedMotionHeader.FrameData.FramesPerSecond; + assimpScene.Animations.Add(assimpAnimation); + + HashSet keyframeTimes = animation.MotionFile.KeyTimes.ToHashSet(); + + // Get real duration + float minTime = 0; + float maxTime = 0; + foreach (var keyTime in keyframeTimes) + { + if(keyTime < minTime) + minTime = keyTime; + if(keyTime > maxTime) + maxTime = keyTime; + } + assimpAnimation.DurationInTicks = maxTime - minTime; + + // Get absolute transformation matrices of the bones + Dictionary frameMatrices = getMatricesForKeyFrames(mdlxBar, animation, keyframeTimes); + + // Prepare channels per bone + Dictionary animationChannelsPerBone = new Dictionary(); + for (int i = 0; i < animation.MotionFile.InterpolatedMotionHeader.BoneCount; i++) + { + Assimp.NodeAnimationChannel nodeAnimChannel = new Assimp.NodeAnimationChannel(); + nodeAnimChannel.NodeName = "Bone" + i.ToString("D4"); + animationChannelsPerBone.Add(i, nodeAnimChannel); + assimpAnimation.NodeAnimationChannels.Add(nodeAnimChannel); + } + + // Get bone data + List modelBones = new List(); + foreach (Bar.Entry barEntry in mdlxBar) + { + if(barEntry.Type == Bar.EntryType.Model) + { + ModelSkeletal modelFile = ModelSkeletal.Read(barEntry.Stream); + modelBones = modelFile.Bones; + break; + } + } + + // Set channels + foreach (float keyTime in frameMatrices.Keys) // Frame + { + for (int j = 0; j < frameMatrices[keyTime].Length; j++) // Bone + { + Assimp.NodeAnimationChannel channel = animationChannelsPerBone[j]; + + Matrix4x4 currentFrameBoneMatrix = frameMatrices[keyTime][j]; + + // Transform to local + if (modelBones[j].ParentIndex != -1) + { + Matrix4x4.Invert(frameMatrices[keyTime][modelBones[j].ParentIndex], out Matrix4x4 invertedParent); + currentFrameBoneMatrix *= invertedParent; + } + + Assimp.Matrix4x4 assimpMatrix = AssimpGeneric.ToAssimp(currentFrameBoneMatrix); + assimpMatrix.Decompose(out Assimp.Vector3D scaling, out Assimp.Quaternion rotation, out Assimp.Vector3D translation); + + Assimp.VectorKey positionKey = new Assimp.VectorKey(keyTime / assimpAnimation.TicksPerSecond, translation); + Assimp.VectorKey scalingKey = new Assimp.VectorKey(keyTime / assimpAnimation.TicksPerSecond, new Assimp.Vector3D(RoundFloat(scaling.X), RoundFloat(scaling.Y), RoundFloat(scaling.Z))); + //Assimp.VectorKey scalingKey = new Assimp.VectorKey(keyTime / assimpAnimation.TicksPerSecond, scaling); + Assimp.QuaternionKey rotationKey = new Assimp.QuaternionKey(keyTime / assimpAnimation.TicksPerSecond, rotation); + + // Ignore duplicates + if(channel.PositionKeys.Count > 0) + { + Assimp.VectorKey previousPositionKey = channel.PositionKeys[channel.PositionKeys.Count - 1]; + Assimp.VectorKey previousScalingKey = channel.ScalingKeys[channel.ScalingKeys.Count - 1]; + Assimp.QuaternionKey previousRotationKey = channel.RotationKeys[channel.RotationKeys.Count - 1]; + + if (!positionKey.Equals(previousPositionKey)) + { + channel.PositionKeys.Add(positionKey); + } + if (!scalingKey.Equals(previousScalingKey)) + { + channel.ScalingKeys.Add(scalingKey); + } + if (!rotationKey.Equals(previousRotationKey)) + { + channel.RotationKeys.Add(rotationKey); + } + } + else + { + channel.PositionKeys.Add(positionKey); + channel.ScalingKeys.Add(scalingKey); + channel.RotationKeys.Add(rotationKey); + } + } + } + + if(assimpScene.RootNode.FindNode("Armature") != null) + { + assimpAnimation.NodeAnimationChannels.Add(getArmatureChannel(keyframeTimes.ToArray()[0] / assimpAnimation.TicksPerSecond, assimpAnimation.DurationInTicks, animation.MotionFile.InterpolatedMotionHeader.FrameData.FramesPerSecond)); + } + } + /**************** * UTILITIES ****************/ private static Vector3 ToVector3(Vector4 pos) => new Vector3(pos.X, pos.Y, pos.Z); + private static float RoundFloat(float value) + { + float reminder = value % 1; + if (reminder > 0.999999 && reminder < 0.999999999999) + { + return value - reminder + 1; + } + else if (reminder > 0 && reminder < 0.00001) + { + return value - reminder; + } + return value; + } + + private static Assimp.NodeAnimationChannel getArmatureChannel(double startFrame, double endFrame, double framesPerSecond) + { + Assimp.NodeAnimationChannel armatureChannel = new Assimp.NodeAnimationChannel(); + armatureChannel.NodeName = "Armature"; + + armatureChannel.PositionKeys.Add(new Assimp.VectorKey(startFrame / framesPerSecond, new Assimp.Vector3D(0,0,0))); + armatureChannel.ScalingKeys.Add(new Assimp.VectorKey(startFrame / framesPerSecond, new Assimp.Vector3D(1, 1, 1))); + armatureChannel.RotationKeys.Add(new Assimp.QuaternionKey(startFrame / framesPerSecond, new Assimp.Quaternion(1, 0, 0, 0))); + + armatureChannel.PositionKeys.Add(new Assimp.VectorKey(endFrame / framesPerSecond, new Assimp.Vector3D(0, 0, 0))); + armatureChannel.ScalingKeys.Add(new Assimp.VectorKey(endFrame / framesPerSecond, new Assimp.Vector3D(1, 1, 1))); + armatureChannel.RotationKeys.Add(new Assimp.QuaternionKey(endFrame / framesPerSecond, new Assimp.Quaternion(1, 0, 0, 0))); + + return armatureChannel; + } + + // Generates the absolute transformation matrices for each bone for the given frames + // Makes use of IAnimMatricesProvider to generate the matrices + private static Dictionary getMatricesForKeyFrames (Bar mdlxBar, AnimationBinary animation, HashSet keyframeTimes) + { + // Mdlx as stream is required + MemoryStream modelStream = new MemoryStream(); + Bar.Write(modelStream, mdlxBar); + modelStream.Position = 0; + + // Calculate matrices + Dictionary frameMatrices = new Dictionary(); + Bar anbBarFile = Bar.Read(animation.toStream()); + foreach (float keyTime in keyframeTimes) + { + // I have no idea why this needs to be done for every frame but otherwise it won't work properly + AnbIndir currentAnb = new AnbIndir(anbBarFile); + IAnimMatricesProvider AnimMatricesProvider = currentAnb.GetAnimProvider(modelStream); + frameMatrices.Add(keyTime, AnimMatricesProvider.ProvideMatrices(keyTime)); + modelStream.Position = 0; + } + + return frameMatrices; + } } } diff --git a/OpenKh.AssimpUtils/OpenKh.AssimpUtils.csproj b/OpenKh.AssimpUtils/OpenKh.AssimpUtils.csproj index cf3159153..526aeb709 100644 --- a/OpenKh.AssimpUtils/OpenKh.AssimpUtils.csproj +++ b/OpenKh.AssimpUtils/OpenKh.AssimpUtils.csproj @@ -15,6 +15,7 @@ + diff --git a/OpenKh.Bbs/OpenKh.Bbs.csproj b/OpenKh.Bbs/OpenKh.Bbs.csproj index f826b8c52..bf612943a 100644 --- a/OpenKh.Bbs/OpenKh.Bbs.csproj +++ b/OpenKh.Bbs/OpenKh.Bbs.csproj @@ -1,7 +1,7 @@ - net6.0 + netstandard2.0;net6.0 latest diff --git a/OpenKh.Bbs/Pmp.cs b/OpenKh.Bbs/Pmp.cs index 3c0c2f96f..833413c89 100644 --- a/OpenKh.Bbs/Pmp.cs +++ b/OpenKh.Bbs/Pmp.cs @@ -1,4 +1,3 @@ -using Microsoft.VisualBasic.CompilerServices; using OpenKh.Common; using OpenKh.Common.Utils; using OpenKh.Imaging; diff --git a/OpenKh.Command.AnbMaker/Commands/AnbExCommand.cs b/OpenKh.Command.AnbMaker/Commands/AnbExCommand.cs index 848f62878..b81793014 100644 --- a/OpenKh.Command.AnbMaker/Commands/AnbExCommand.cs +++ b/OpenKh.Command.AnbMaker/Commands/AnbExCommand.cs @@ -10,6 +10,7 @@ using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; +using System.IO; using System.Linq; using System.Numerics; using System.Reflection; @@ -29,6 +30,7 @@ internal class AnbExCommand : IFbxSourceItemSelector, IMsetInjector [Argument(1, Description = "anb output")] public string Output { get; set; } + public string OutputMset { get; set; } [Option(Description = "specify root armature node name", ShortName = "r")] public string RootName { get; set; } @@ -56,7 +58,7 @@ protected int OnExecute(CommandLineApplication app) var logger = LogManager.GetLogger("InterpolatedMotionMaker"); Output = Path.GetFullPath(Output ?? Path.GetFileNameWithoutExtension(InputModel) + ".anb"); - + OutputMset = Path.GetFullPath(OutputMset ?? Path.GetFileNameWithoutExtension(InputModel) + ".mset"); Console.WriteLine($"Writing to: {Output}"); IEnumerable parms; @@ -117,12 +119,33 @@ protected int OnExecute(CommandLineApplication app) Type = Bar.EntryType.Motion, Name = "A999", Stream = motionStream, + }, + new Bar.Entry + { + Type = Bar.EntryType.MotionTriggers, + Name = "A999", + Stream = new MemoryStream() // Replace null with MemoryStream containing "0" } } ); + var msetBarStream = new MemoryStream(); + Bar.Write( + msetBarStream, + new Bar.Entry[] + { + new Bar.Entry + { + Type = Bar.EntryType.Anb, + Name = "A999", + Stream = anbBarStream + } + } + ); + File.WriteAllBytes(Output, anbBarStream.ToArray()); File.WriteAllBytes(Output + ".raw", motionStream.ToArray()); + File.WriteAllBytes(OutputMset, msetBarStream.ToArray()); logger.Debug($"Motion data generation successful"); diff --git a/OpenKh.Command.AnbMaker/Utils/Builder/InterpolatedMotionBuilder.cs b/OpenKh.Command.AnbMaker/Utils/Builder/InterpolatedMotionBuilder.cs index 0265109b1..db021a11a 100644 --- a/OpenKh.Command.AnbMaker/Utils/Builder/InterpolatedMotionBuilder.cs +++ b/OpenKh.Command.AnbMaker/Utils/Builder/InterpolatedMotionBuilder.cs @@ -132,21 +132,21 @@ float FixPos(float value) new ChannelProvider { - type = Channel.ROTATATION_X, + type = Channel.ROTATION_X, jointFlags = JointFlags.HasRotation, keys = hit.RotationXKeys, fixValue = it => it, }, new ChannelProvider { - type = Channel.ROTATATION_Y, + type = Channel.ROTATION_Y, jointFlags = JointFlags.HasRotation, keys = hit.RotationYKeys, fixValue = it => it, }, new ChannelProvider { - type = Channel.ROTATATION_Z, + type = Channel.ROTATION_Z, jointFlags = JointFlags.HasRotation, keys = hit.RotationZKeys, fixValue = it => it, @@ -175,6 +175,8 @@ float FixPos(float value) }, }; + fixRotations(channels); + var jointFlag = JointFlags.None; foreach (var channel in channels) @@ -234,9 +236,9 @@ out Vector3 translation initialPoseDict[new InitialPoseKey(boneIdx, Channel.SCALE_X)] = FixScalingValue(scale.X); initialPoseDict[new InitialPoseKey(boneIdx, Channel.SCALE_Y)] = FixScalingValue(scale.Y); initialPoseDict[new InitialPoseKey(boneIdx, Channel.SCALE_Z)] = FixScalingValue(scale.Z); - initialPoseDict[new InitialPoseKey(boneIdx, Channel.ROTATATION_X)] = rotation.X; - initialPoseDict[new InitialPoseKey(boneIdx, Channel.ROTATATION_Y)] = rotation.Y; - initialPoseDict[new InitialPoseKey(boneIdx, Channel.ROTATATION_Z)] = rotation.Z; + initialPoseDict[new InitialPoseKey(boneIdx, Channel.ROTATION_X)] = rotation.X; + initialPoseDict[new InitialPoseKey(boneIdx, Channel.ROTATION_Y)] = rotation.Y; + initialPoseDict[new InitialPoseKey(boneIdx, Channel.ROTATION_Z)] = rotation.Z; initialPoseDict[new InitialPoseKey(boneIdx, Channel.TRANSLATION_X)] = FixPosValue(translation.X); initialPoseDict[new InitialPoseKey(boneIdx, Channel.TRANSLATION_Y)] = FixPosValue(translation.Y); initialPoseDict[new InitialPoseKey(boneIdx, Channel.TRANSLATION_Z)] = FixPosValue(translation.Z); @@ -387,5 +389,46 @@ public int Compare(InitialPoseKey? left, InitialPoseKey? right) return diff; } } + + // Fix by Some1fromthedark + private float unwrapAngle(float previous_angle, float current_angle) + { + float diff = current_angle - previous_angle; + if (diff < -Math.PI) + { + while (diff < -Math.PI) + { + current_angle += (float)(2 * Math.PI); + diff = current_angle - previous_angle; + } + } + else if (diff > Math.PI) + { + while (Math.PI < diff) + { + current_angle -= (float)(2 * Math.PI); + diff = current_angle - previous_angle; + } + } + return current_angle; + } + + private void fixRotations(ChannelProvider[] channels) + { + foreach (ChannelProvider channel in channels) + { + if (channel.type == Channel.ROTATION_X || + channel.type == Channel.ROTATION_Y || + channel.type == Channel.ROTATION_Z) + { + float previousAngle = 0; + foreach (var k in channel.keys) + { + k.Value = unwrapAngle(previousAngle, k.Value); + previousAngle = k.Value; + } + } + } + } } } diff --git a/OpenKh.Command.Bdxio/OpenKh.Command.Bdxio.csproj b/OpenKh.Command.Bdxio/OpenKh.Command.Bdxio.csproj index 5702bd30a..0feba2320 100644 --- a/OpenKh.Command.Bdxio/OpenKh.Command.Bdxio.csproj +++ b/OpenKh.Command.Bdxio/OpenKh.Command.Bdxio.csproj @@ -10,7 +10,7 @@ - + diff --git a/OpenKh.Command.ImgTool/OpenKh.Command.ImgTool.csproj b/OpenKh.Command.ImgTool/OpenKh.Command.ImgTool.csproj index c778f3369..c186da0c4 100644 --- a/OpenKh.Command.ImgTool/OpenKh.Command.ImgTool.csproj +++ b/OpenKh.Command.ImgTool/OpenKh.Command.ImgTool.csproj @@ -9,7 +9,7 @@ - + diff --git a/OpenKh.Command.Layout/OpenKh.Command.Layout.csproj b/OpenKh.Command.Layout/OpenKh.Command.Layout.csproj index 5d82aa3bd..e8d7d1f3f 100644 --- a/OpenKh.Command.Layout/OpenKh.Command.Layout.csproj +++ b/OpenKh.Command.Layout/OpenKh.Command.Layout.csproj @@ -8,7 +8,7 @@ - + diff --git a/OpenKh.Command.MapGen/OpenKh.Command.MapGen.csproj b/OpenKh.Command.MapGen/OpenKh.Command.MapGen.csproj index 86eede947..d34deaf7f 100644 --- a/OpenKh.Command.MapGen/OpenKh.Command.MapGen.csproj +++ b/OpenKh.Command.MapGen/OpenKh.Command.MapGen.csproj @@ -17,7 +17,7 @@ - + diff --git a/OpenKh.Command.MapGenUtils/OpenKh.Command.MapGenUtils.csproj b/OpenKh.Command.MapGenUtils/OpenKh.Command.MapGenUtils.csproj index 9541b2c7f..b99d57183 100644 --- a/OpenKh.Command.MapGenUtils/OpenKh.Command.MapGenUtils.csproj +++ b/OpenKh.Command.MapGenUtils/OpenKh.Command.MapGenUtils.csproj @@ -9,8 +9,8 @@ - - + + diff --git a/OpenKh.Command.TexFooter/OpenKh.Command.TexFooter.csproj b/OpenKh.Command.TexFooter/OpenKh.Command.TexFooter.csproj index c778f3369..c186da0c4 100644 --- a/OpenKh.Command.TexFooter/OpenKh.Command.TexFooter.csproj +++ b/OpenKh.Command.TexFooter/OpenKh.Command.TexFooter.csproj @@ -9,7 +9,7 @@ - + diff --git a/OpenKh.Common/Log.cs b/OpenKh.Common/Log.cs index 1fc4f3955..3eade2ef6 100644 --- a/OpenKh.Common/Log.cs +++ b/OpenKh.Common/Log.cs @@ -46,7 +46,7 @@ public static void Close() Flush(); } - private static void Flush() + public static void Flush() { const int Timeout = 3000; diff --git a/OpenKh.Common/OpenKh.Common.csproj b/OpenKh.Common/OpenKh.Common.csproj index 629da827c..108dce0f2 100644 --- a/OpenKh.Common/OpenKh.Common.csproj +++ b/OpenKh.Common/OpenKh.Common.csproj @@ -9,7 +9,7 @@ - + diff --git a/OpenKh.Game/Field/Kh2Field.cs b/OpenKh.Game/Field/Kh2Field.cs index dcb10b250..601214eb6 100644 --- a/OpenKh.Game/Field/Kh2Field.cs +++ b/OpenKh.Game/Field/Kh2Field.cs @@ -133,11 +133,11 @@ public void LoadArea(int world, int area) if (area >= 0 && area < worldInfos.Count) { var areaInfo = worldInfos[area]; - var isKnown = (areaInfo.Flags & 1) != 0; - var isInDoor = (areaInfo.Flags & 2) != 0; - var isMonochrome = (areaInfo.Flags & 4) != 0; - var hasNoShadow = (areaInfo.Flags & 8) != 0; - var hasGlow = (areaInfo.Flags & 16) != 0; + var isKnown = areaInfo.Flags.HasFlag(Kh2.SystemData.Arif.ArifFlags.IsKnownArea); + var isInDoor = areaInfo.Flags.HasFlag(Kh2.SystemData.Arif.ArifFlags.IndoorArea); + var isMonochrome = areaInfo.Flags.HasFlag(Kh2.SystemData.Arif.ArifFlags.Monochrome); + var hasNoShadow = areaInfo.Flags.HasFlag(Kh2.SystemData.Arif.ArifFlags.NoShadow); + var hasGlow = areaInfo.Flags.HasFlag(Kh2.SystemData.Arif.ArifFlags.HasGlow); _targetCamera.Type = isInDoor ? 1 : 0; } diff --git a/OpenKh.Game/OpenKh.Game.csproj b/OpenKh.Game/OpenKh.Game.csproj index 54d7416f9..726deda92 100644 --- a/OpenKh.Game/OpenKh.Game.csproj +++ b/OpenKh.Game/OpenKh.Game.csproj @@ -16,7 +16,7 @@ - + diff --git a/OpenKh.Kh2/Bar.cs b/OpenKh.Kh2/Bar.cs index f3cdb9b34..4793aca67 100644 --- a/OpenKh.Kh2/Bar.cs +++ b/OpenKh.Kh2/Bar.cs @@ -157,6 +157,18 @@ public static Bar Read(Stream stream, Func predicate) .Where(x => predicate(x.Name, x.Type)) .Select(x => { + if(x.Offset == -1) + { + return new Entry + { + Type = x.Type, + Index = x.Index, + Name = x.Name.Split('\0').FirstOrDefault(), + Offset = x.Offset, + Stream = new MemoryStream() + }; + } + reader.BaseStream.Position = x.Offset; var data = reader.ReadBytes(x.Size); diff --git a/OpenKh.Kh2/BaseTable.cs b/OpenKh.Kh2/BaseTable.cs index b46a65547..047f5b763 100644 --- a/OpenKh.Kh2/BaseTable.cs +++ b/OpenKh.Kh2/BaseTable.cs @@ -31,7 +31,9 @@ public static void Write(Stream stream, int version, IEnumerable items) foreach (var item in itemList) BinaryMapping.WriteObject(stream, item); } - } + } + + public class BaseShortTable where T : class @@ -56,6 +58,34 @@ public static void Write(Stream stream, int id, IEnumerable items) Count = (short)itemList.Count, }); + foreach (var item in itemList) + BinaryMapping.WriteObject(stream, item); + } + } + + + //Tables that only have a Count of entries; like soundinfo, etc. + public class BaseTableCountOnly + where T : class + { + [Data] public int Count { get; set; } + + public static List Read(Stream stream) + { + var header = BinaryMapping.ReadObject>(stream); + return Enumerable.Range(0, header.Count) + .Select(_ => BinaryMapping.ReadObject(stream)) + .ToList(); + } + + public static void Write(Stream stream, IEnumerable items) + { + var itemList = items as IList ?? items.ToList(); + BinaryMapping.WriteObject(stream, new BaseTableCountOnly + { + Count = (short)itemList.Count, + }); + foreach (var item in itemList) BinaryMapping.WriteObject(stream, item); } diff --git a/OpenKh.Kh2/Battle/Atkp.cs b/OpenKh.Kh2/Battle/Atkp.cs index 8224c72fe..7f421d565 100644 --- a/OpenKh.Kh2/Battle/Atkp.cs +++ b/OpenKh.Kh2/Battle/Atkp.cs @@ -110,14 +110,42 @@ public enum AttackKind : byte 0x66, 0x5F, 0x6D, 0x6F, 0x76, 0x65, 0x00, 0x63, 0x72, 0x61, 0x73, 0x68, 0x00 }; - public static List Read(Stream stream) => BaseTable.Read(stream); - - public static void Write(Stream stream, IEnumerable items) - { - BaseTable.Write(stream, 6, items); - stream.Position = 130232; - stream.Write(endBytes,0,277); - } - + public static List Read(Stream stream) => BaseTable.Read(stream); + + //New ATKP: Extra byte addition is no longer hardcoded to add to a specific position. + //Rather, they're now added at the end. + //The bytes seem to not be important, the strings resemble ones found in AI. + //However, to prevent messing with the ATKP offset unless explicity adding attack hitboxes, they'll be appended to the end of the file. + public static void Write(Stream stream, IEnumerable items) + { + // Get the initial length of the stream + long initialLength = stream.Length; + + // Write the items to the stream + BaseTable.Write(stream, 6, items); + + // Check if the stream length has increased + if (stream.Length > initialLength) + { + + // Seek to the end of the stream + stream.Seek(0, SeekOrigin.End); + + // Append the bytes to the end of the stream + stream.Write(endBytes, 0, endBytes.Length); + } + if (stream.Length == initialLength) + { + // Seek to the end of the stream + stream.Seek(0, SeekOrigin.End); + } + //Currently if you're just editing hitboxes, nothing changes. No offset differences occur. + //If you add one hitbox, it overwrites endbytes until it reaches the end of the file, and starts appending new bytes AND endbytes after. + + else + { + // If no new data was written, do nothing + } + } } } diff --git a/OpenKh.Kh2/Battle/Limt.cs b/OpenKh.Kh2/Battle/Limt.cs index d7288161a..34f20c44e 100644 --- a/OpenKh.Kh2/Battle/Limt.cs +++ b/OpenKh.Kh2/Battle/Limt.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.IO; using Xe.BinaryMapper; @@ -5,18 +6,51 @@ namespace OpenKh.Kh2.Battle { public class Limt - { + { + + + [Flags] + public enum Characters : byte + { + None = 0, + Sora = 1, + Donald = 2, + Goofy = 3, + Mickey = 4, + Auron = 5, + Mulan = 6, + Aladdin = 7, + JackSparrow = 8, + Beast = 9, + JackSkellington = 10, + Simba = 11, + Tron = 12, + Riku = 13, + Roxas = 14, + Ping = 15, + Stitch = 200, + Genie = 201, + PeterPan = 202, + ChickenLittle = 204, + } + + + [Data] public byte Id { get; set; } - [Data] public byte Character { get; set; } - [Data] public byte Summon { get; set; } + [Data] public Characters Character { get; set; } + [Data] public Characters Summon { get; set; } [Data] public byte Group { get; set; } [Data(Count = 32)] public string FileName { get; set; } [Data] public uint SpawnId { get; set; } [Data] public ushort Command { get; set; } [Data] public ushort Limit { get; set; } - [Data] public byte World { get; set; } - [Data(Count = 19)] public byte[] Padding { get; set; } - + [Data] public ushort World { get; set; } + [Data(Count = 18)] public byte[] Padding { get; set; } + + public override string ToString() + { + return FileName; + } public static List Read(Stream stream) => BaseTable.Read(stream); public static void Write(Stream stream, IEnumerable items) => diff --git a/OpenKh.Kh2/Battle/Lvpm.cs b/OpenKh.Kh2/Battle/Lvpm.cs index e397c8967..13be8217f 100644 --- a/OpenKh.Kh2/Battle/Lvpm.cs +++ b/OpenKh.Kh2/Battle/Lvpm.cs @@ -13,8 +13,12 @@ public class Lvpm [Data] public ushort MinStrength { get; set; } [Data] public ushort Experience { get; set; } + //Default public static List Read(Stream stream) => BaseList.Read(stream, 99); + //Override for having a custom amount of entries + public static List Read(Stream stream, int count) => BaseList.Read(stream, count); + public static void Write(Stream stream, IEnumerable items) => BaseList.Write(stream, items); public Lvpm() { } diff --git a/OpenKh.Kh2/Battle/Stop.cs b/OpenKh.Kh2/Battle/Stop.cs new file mode 100644 index 000000000..140c44743 --- /dev/null +++ b/OpenKh.Kh2/Battle/Stop.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.IO; +using Xe.BinaryMapper; + +namespace OpenKh.Kh2.Battle +{ + public class Stop + { + [Flags] + public enum Flag : uint + { + Exist = 0x01, + DisableDamageReaction = 0x02, + Star = 0x04, + DisableDraw = 0x08, + } + [Data] public ushort Id { get; set; } + [Data] public Flag Flags { get; set; } + + public static List Read(Stream stream) => BaseTable.Read(stream); + + public static void Write(Stream stream, IEnumerable items) => + BaseTable.Write(stream, 2, items); + } +} diff --git a/OpenKh.Kh2/BinaryArchive.cs b/OpenKh.Kh2/BinaryArchive.cs new file mode 100644 index 000000000..45d1c175c --- /dev/null +++ b/OpenKh.Kh2/BinaryArchive.cs @@ -0,0 +1,346 @@ +using OpenKh.Common; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Xe.BinaryMapper; + +namespace OpenKh.Kh2 +{ + // Bianry Archives are used to store subfiles. + // The previous version for this type of file is "Bar.cs". It dealt with linking through name + entry type and a more refined linking structure was required. + public class BinaryArchive + { + /****************************************** + * Constants + ******************************************/ + private const string SIGNATURE = "BAR"; + private const int HEADER_SIZE = 0x10; + private const int ENTRY_SIZE = 0x10; + + /****************************************** + * Properties + ******************************************/ + public byte ExternalFlags { get; set; } + public byte Version { get; set; } + public MotionsetType MSetType { get; set; } + public List Entries { get; set; } + public List Subfiles { get; set; } + + /****************************************** + * Constructors + ******************************************/ + + public BinaryArchive() + { + Entries = new List(); + Subfiles = new List(); + } + + /****************************************** + * Functions - Static + ******************************************/ + + // Creates the BinaryArchive from the given Stream. The final position is the same as the initial position. + public static BinaryArchive Read(Stream barStream) + { + BinaryArchive binaryArchive = new BinaryArchive(); + + int initialPosition = (int)barStream.Position; + + // Read Header + HeaderBinary header = BinaryMapping.ReadObject(barStream); + binaryArchive.ExternalFlags = (byte)((header.ExternalFlagsAndVersion & 0xF0) >> 4); + binaryArchive.Version = (byte)(header.ExternalFlagsAndVersion & 0x0F); + binaryArchive.MSetType = (MotionsetType)header.MotionSetType; + + // Read Entries + List entryBinaries = new List(); + Dictionary fileOffsetSize = new Dictionary(); + + for (int i = 0; i < header.EntryCount; i++) + { + entryBinaries.Add(BinaryMapping.ReadObject(barStream)); + EntryBinary entryBinary = entryBinaries[i]; + if(entryBinary.Size == 0) + { + entryBinary.Offset = -1; + } + + if (!fileOffsetSize.ContainsKey(entryBinary.Offset) && entryBinary.Offset != -1) + { + fileOffsetSize.Add(entryBinary.Offset, entryBinary.Size); + } + + Entry entry = new Entry(); + entry.Type = (EntryType)entryBinary.Type; + entry.Name = entryBinary.Name; + entry.Link = entryBinary.Size == 0 ? -1 : fileOffsetSize.Keys.ToList().IndexOf(entryBinary.Offset); + binaryArchive.Entries.Add(entry); + } + + // Read SubFiles + foreach (int fileOffset in fileOffsetSize.Keys) + { + byte[] fileBytes = new byte[fileOffsetSize[fileOffset]]; + barStream.Position = initialPosition + fileOffset; + barStream.Read(fileBytes, 0, fileOffsetSize[fileOffset]); + binaryArchive.Subfiles.Add(fileBytes); + } + + barStream.Position = initialPosition; + + return binaryArchive; + } + public static BinaryArchive Read(byte[] binaryFile) + { + MemoryStream memStream = new MemoryStream(binaryFile); + return Read(memStream); + } + + public static bool IsValid(Stream stream) + { + if (stream.Length < 4) + { + return false; + } + + string signatureBytes = System.Text.Encoding.ASCII.GetString(stream.ReadBytes(3)); + stream.Position -= 3; + + if (signatureBytes == SIGNATURE) + { + return true; + } + return false; + } + + /****************************************** + * Functions - Local + ******************************************/ + + // Returns the BinaryArchive as a byte array + public byte[] getAsByteArray() + { + RemoveUnlinkedSubfiles(); + + using MemoryStream fileStream = new MemoryStream(); + + // Header + HeaderBinary headerBinary = new HeaderBinary(); + headerBinary.Signature = SIGNATURE; + headerBinary.ExternalFlagsAndVersion = (byte)((ExternalFlags << 4) | (Version & 0x0F)); + headerBinary.EntryCount = Entries.Count; + headerBinary.Address = 0; + headerBinary.MotionSetType = (int)MSetType; + BinaryMapping.WriteObject(fileStream, headerBinary); + + // SubFiles + fileStream.Position += Entries.Count * ENTRY_SIZE; + List offsets = new List(); + foreach(byte[] subfile in Subfiles) + { + offsets.Add((int)fileStream.Position); + fileStream.Write(subfile, 0, subfile.Length); + + // Padding to 16 + byte excess = (byte)(fileStream.Position % 16); + if(excess > 0) + { + for (int i = 0; i < 16 - excess; i++) + { + fileStream.WriteByte(0); + } + } + } + + // Entries + List usedSubFiles = new List(); + for (int i = 0; i < Entries.Count; i++) + { + Entry entry = Entries[i]; + EntryBinary entryBinary = new EntryBinary(); + entryBinary.Type = (short)entry.Type; + entryBinary.Name = entry.Name; + entryBinary.Offset = entry.Link == -1 ? -1 : offsets[entry.Link]; + entryBinary.Size = entry.Link == -1 ? 0 : Subfiles[entry.Link].Length; + + if (!usedSubFiles.Contains(entry.Link)) + { + entryBinary.Flag = 0; + usedSubFiles.Add(entry.Link); + } + else + { + entryBinary.Flag = 1; + } + + fileStream.Position = HEADER_SIZE + (i * ENTRY_SIZE); + BinaryMapping.WriteObject(fileStream, entryBinary); + } + + return fileStream.ToArray(); + } + + public void RemoveUnlinkedSubfiles() + { + List links = new List(); + foreach(Entry entry in Entries) + { + if(entry.Link != -1 && !links.Contains(entry.Link)) + { + links.Add(entry.Link); + } + } + links.Sort(); + + Dictionary newIndices = new Dictionary(); + for(int i = 0; i < links.Count; i++) + { + if (links[i] != i) + { + newIndices.Add(links[i], i); + } + } + + List indicesToDelete = new List(); + for (int i = 0; i < Subfiles.Count; i++) + { + if (!links.Contains(i)) + { + indicesToDelete.Add(i); + } + } + + for (int i = Subfiles.Count; i >= 0; i--) + { + if (indicesToDelete.Contains(i)) + { + Subfiles.RemoveAt(i); + } + } + + foreach (Entry entry in Entries) + { + if (newIndices.ContainsKey(entry.Link)) + { + entry.Link = newIndices[entry.Link]; + } + } + } + + /****************************************** + * Subclasses + ******************************************/ + public class Entry + { + public EntryType Type { get; set; } + public int Link { get; set; } // Index of the subfile it links to + private string _name; + public string Name + { + get + { + return _name; + } + set + { + value += "\0\0\0\0"; + _name = value.Substring(0, 4); + } + } + + public Entry() + { + Name = "\0\0\0\0"; + Link = -1; + } + + // For visualizing easier in Debug Locals + public override string ToString() + { + return "[" + Name + "] Link: " + Link + "; Type: " + Type; + } + } + + /****************************************** + * Enums + ******************************************/ + public enum EntryType //2b + { + Dummy = 0, + Binary = 1, + List = 2, + Bdx = 3, + Model = 4, + DrawOctalTree = 5, + CollisionOctalTree = 6, + ModelTexture = 7, + Dpx = 8, + Motion = 9, + Tim2 = 10, + CameraOctalTree = 11, + AreaDataSpawn = 12, + AreaDataScript = 13, + FogColor = 14, + ColorOctalTree = 15, + MotionTriggers = 16, + Anb = 17, + Pax = 18, + MapCollision2 = 19, + Motionset = 20, + BgObjPlacement = 21, + Event = 22, + ModelCollision = 23, + Imgd = 24, + Seqd = 25, + Layout = 28, + Imgz = 29, + AnimationMap = 30, + Seb = 31, + Wd = 32, + Unknown33, + IopVoice = 34, + RawBitmap = 36, + MemoryCard = 37, + WrappedCollisionData = 38, + Unknown39 = 39, + Unknown40 = 40, + Unknown41 = 41, + Minigame = 42, + JimiData = 43, + Progress = 44, + Synthesis = 45, + BarUnknown = 46, + Vibration = 47, + Vag = 48, + } + public enum MotionsetType //4b + { + Default = 0, + Player = 1, + Raw = 2 + } + + /****************************************** + * Helper classes for reading and writing + ******************************************/ + // struct BINARC + public class HeaderBinary + { + [Data(Count = 3)] public string Signature { get; set; } // BAR + [Data] public byte ExternalFlagsAndVersion { get; set; } // 4b external flags; 4b Version + [Data] public int EntryCount { get; set; } + [Data] public int Address { get; set; } // Always 0 unless ingame + [Data] public int MotionSetType { get; set; } // 30b Replace; 2b Flag (MotionSet Type) + } + // struct INFO + public class EntryBinary + { + [Data] public short Type { get; set; } + [Data] public short Flag { get; set; } // Bitflag + [Data(Count = 4)] public string Name { get; set; } + [Data] public int Offset { get; set; } + [Data] public int Size { get; set; } + } + } +} diff --git a/OpenKh.Kh2/Dpd.Texture.cs b/OpenKh.Kh2/Dpd.Texture.cs index 37665395b..625dff202 100644 --- a/OpenKh.Kh2/Dpd.Texture.cs +++ b/OpenKh.Kh2/Dpd.Texture.cs @@ -9,21 +9,21 @@ public partial class Dpd { public class Texture { - private short shTexDbp; - private short shCltDbp; + public short shTexDbp; + public short shCltDbp; private short shDbw; - private short format; //shDpsm + public short format; //shDpsm; 0x13 => 8bpp + 256 palette; 0x14 => 4bpp + 16 palette private short shX; private short shY; private short width; private short height; - private uint unTex0L; - private uint unTex0H; - private int unClutStart; - private short shTexVramSize; - private short shCltVramSize; - public byte[] Data { get; } - public byte[] Palette { get; } + public uint unTex0L; + public uint unTex0H; + private int unClutStart; // W x H + private short shTexVramSize; // 64 (unclut / 256) + private short shCltVramSize; // 4 + public byte[] Data { get; set; } + public byte[] Palette { get; set; } public Texture() { @@ -46,8 +46,16 @@ internal Texture(Stream textureStream) shTexVramSize = textureStream.ReadInt16(); shCltVramSize = textureStream.ReadInt16(); - Data = textureStream.ReadBytes(width * height); - Palette = textureStream.ReadBytes(0x100 * sizeof(int)); + if(format == 0x14) // 16 color palette + { + Data = textureStream.ReadBytes((width * height) / 2); + Palette = textureStream.ReadBytes(0x10 * sizeof(int)); + } + else // 0x0, 0x13; 256 color palette + { + Data = textureStream.ReadBytes(width * height); + Palette = textureStream.ReadBytes(0x100 * sizeof(int)); + } } internal Texture(BinaryReader reader) @@ -67,8 +75,16 @@ internal Texture(BinaryReader reader) shTexVramSize = reader.ReadInt16(); shCltVramSize = reader.ReadInt16(); - Data = reader.ReadBytes(width * height); - Palette = reader.ReadBytes(0x100 * sizeof(int)); + if(format == 0x14) // 16 color palette + { + Data = reader.ReadBytes((width * height) / 2); + Palette = reader.ReadBytes(0x10 * sizeof(int)); + } + else // 0x0, 0x13; 256 color palette + { + Data = reader.ReadBytes(width * height); + Palette = reader.ReadBytes(0x100 * sizeof(int)); + } } internal void Write(BinaryWriter writer) @@ -120,8 +136,23 @@ public Stream getAsStream() } public Size Size => new Size(width, height); + public int PaletteSize + { + get + { + if (format == 0x13) { + return 256; + } + else if (format == 0x14) { + return 16; + } + else { + throw new Exception("Unsupported palette format"); + } + } + } - public byte[] GetBitmap() + public byte[] GetBitmap() { var swizzled = 0; @@ -146,9 +177,9 @@ private static byte[] GetBitmapFrom8bpp(byte[] src, byte[] palette, int width, i for (int i = 0; i < dst.Length; i += 4) { var index = Ps2.Repl(src[i / 4]); - dst[i + 0] = (byte)Math.Max(0, palette[index * 4 + 2] * 2 - 1); - dst[i + 1] = (byte)Math.Max(0, palette[index * 4 + 1] * 2 - 1); - dst[i + 2] = (byte)Math.Max(0, palette[index * 4 + 0] * 2 - 1); + dst[i + 0] = (byte)Math.Max(0, (int)palette[index * 4 + 2]); + dst[i + 1] = (byte)Math.Max(0, (int)palette[index * 4 + 1]); + dst[i + 2] = (byte)Math.Max(0, (int)palette[index * 4 + 0]); dst[i + 3] = (byte)Math.Max(0, palette[index * 4 + 3] * 2 - 1); } @@ -164,13 +195,13 @@ private static byte[] GetBitmapFrom4bpp(byte[] src, byte[] palette, int width, i dst[i + 0] = palette[index * 4 + 0]; dst[i + 1] = palette[index * 4 + 1]; dst[i + 2] = palette[index * 4 + 2]; - dst[i + 3] = palette[index * 4 + 3]; + dst[i + 3] = (byte)(palette[index * 4 + 3] * 2 - 1); index = src[i / 8] >> 4; dst[i + 4] = palette[index * 4 + 0]; dst[i + 5] = palette[index * 4 + 1]; dst[i + 6] = palette[index * 4 + 2]; - dst[i + 7] = palette[index * 4 + 3]; + dst[i + 7] = (byte)(palette[index * 4 + 3] * 2 - 1); } return dst; diff --git a/OpenKh.Kh2/Jigsaw.cs b/OpenKh.Kh2/Jigsaw.cs index ffc68a57e..da382c244 100644 --- a/OpenKh.Kh2/Jigsaw.cs +++ b/OpenKh.Kh2/Jigsaw.cs @@ -1,15 +1,48 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.IO; using Xe.BinaryMapper; namespace OpenKh.Kh2 { public class Jigsaw - { - [Data] public byte Picture { get; set; } + { + public enum PictureName : byte + { + Awakening = 0, + Heart = 1, + Duality = 2, + Frontier = 3, + Daylight = 4, + Sunset = 5 + } + + public enum WorldList : byte + { + ZZ = 0, + EndofSea = 1, + TwilightTown = 2, + DestinyIsland = 3, + HollowBastion = 4, + BeastsCastle = 5, + OlympusColiseum = 6, + Agrabah = 7, + TheLandOfDragons = 8, + HundredAcreWood = 9, + PrideLand = 10, + Atlantica = 11, + DisneyCastle = 12, + TimelessRiver = 13, + HalloweenTown = 14, + WorldMap = 15, + PortRoyal = 16, + SpaceParanoids = 17, + TheWorldThatNeverWas = 18, + } + + [Data] public PictureName Picture { get; set; } [Data] public byte Part { get; set; } [Data] public ushort Text { get; set; } //z_un_002a2de8, binary addition 0x8000 - [Data] public byte World { get; set; } + [Data] public WorldList World { get; set; } [Data] public byte Room { get; set; } [Data] public byte JigsawIdWorld { get; set; } [Data] public byte Unk07 { get; set; } //has also something to do with pos and orientation diff --git a/OpenKh.Kh2/Libretto.cs b/OpenKh.Kh2/Libretto.cs new file mode 100644 index 000000000..cf1fb2a39 --- /dev/null +++ b/OpenKh.Kh2/Libretto.cs @@ -0,0 +1,145 @@ +using OpenKh.Common; +using System; +using System.Collections.Generic; +using System.IO; +using Xe.BinaryMapper; +namespace OpenKh.Kh2 +//Can properly read/write and update. Can insert new entries between. +{ + public class Libretto + { + [Data] public int MagicCode { get; set; } + [Data] public int Count { get; set; } + [Data] public List Definitions { get; set; } + [Data] public List> Contents { get; set; } + + public class TalkMessageDefinition + { + [Data] public ushort TalkMessageId { get; set; } + [Data] public ushort Unknown { get; set; } + [Data] public uint ContentPointer { get; set; } + } + + public class TalkMessageContent + { + [Data] public uint Unknown1 { get; set; } + [Data] public uint TextId { get; set; } + } + + public class TalkMessagePatch + { + [Data] public ushort TalkMessageId { get; set; } + [Data] public ushort Unknown { get; set; } + [Data] public List Contents { get; set; } + } + + public class ContentPatch + { + [Data] public uint Unknown1 { get; set; } + [Data] public uint TextId { get; set; } + } + + public static Libretto Read(Stream stream) + { + //Store initial position of stream. + var basePosition = stream.Position; + //Create new Libretto object, then read MagicCode & Count from stream. + var libretto = new Libretto + { + MagicCode = stream.ReadInt32(), + Count = stream.ReadInt32() + }; + + //Initialize definitions/contents list w/ capacity equal to count + libretto.Definitions = new List(libretto.Count); + libretto.Contents = new List>(libretto.Count); + + //Loop over number of definitions specified by count. + for (int i = 0; i < libretto.Count; i++) + { + //Read TalkMessageDefinition from the stream, add to Definitions list. + libretto.Definitions.Add(new TalkMessageDefinition + { + TalkMessageId = stream.ReadUInt16(), + Unknown = stream.ReadUInt16(), + ContentPointer = stream.ReadUInt32() + }); + } + + //Loop over each definition in the Definitions list. + foreach (var definition in libretto.Definitions) + { + //Set stream position to the Content Pointer for the current definition. + stream.Position = basePosition + definition.ContentPointer; + //Create a new list to hold our TalkMessageContent objects for the current definition. + var contents = new List(); + + //Read all TalkMessageContents for current definition until Terminating Condition is met. + //while (true) //CAn set to while (true)... + //var content = BinaryMapping.ReadObject(stream); + //while (content.Unknown1 != 0) + + //Literally had to do both a while loop and manually change the content check. + //Also changed the way content is read, from being read like this: + // var content = BinaryMapping.ReadObject(stream); + //And changed the condition. So, might be times where you need to read like this. + //Yea. issue is down to using the BinaryMapper for this. + while (true) + { + // Read a TalkMessageContent object manually from the stream + var content = new TalkMessageContent + { + Unknown1 = stream.ReadUInt32(), + TextId = stream.ReadUInt32() + }; + + // Check for the termination condition: Unknown1 == 0 and TextId == 0 + if (content.Unknown1 == 0 || content.Unknown1 == null) + { + break; + } + + // Add content to the contents list + contents.Add(content); + } + //ADd list of contents for the current definition to the Contents list. + libretto.Contents.Add(contents); + } + + return libretto; + } + + + public static void Write(Stream stream, Libretto libretto) + { + var basePosition = stream.Position; + + stream.Write(libretto.MagicCode); + stream.Write(libretto.Count); + + var offset = 8 + libretto.Definitions.Count * 8; //Set offset variable; start AFTER magiccode+count and update the offset later. Offset = 8 + # of Definitions*8. + + foreach (var definition in libretto.Definitions) + { + stream.Write(definition.TalkMessageId); //Write the TalkMessage for each Definition. + stream.Write(definition.Unknown); //Write the Unknown for each Definition. + stream.Write(offset); //Write the offset for each Definition. + offset += libretto.Contents[libretto.Definitions.IndexOf(definition)].Count * 8 + 4; //Update the Offset in each definition. + } + + stream.Position = basePosition + 8 + libretto.Definitions.Count * 8; + foreach (var contents in libretto.Contents) + { + foreach (var content in contents) + { + stream.Write(content.Unknown1); + stream.Write(content.TextId); + //if (content.Unknown1 == 0) + // break; + } + //Break this until we figure out why it isn't reading. + stream.Write(0); // Write the padding (0x00000000) + } + } + } +} diff --git a/OpenKh.Kh2/Localset.cs b/OpenKh.Kh2/Localset.cs new file mode 100644 index 000000000..01d707668 --- /dev/null +++ b/OpenKh.Kh2/Localset.cs @@ -0,0 +1,16 @@ +using System.Collections.Generic; +using System.IO; +using Xe.BinaryMapper; + +namespace OpenKh.Kh2 +{ + public class Localset + { + [Data] public ushort ProgramId { get; set; } + [Data] public ushort MapNumber { get; set; } + + public static List Read(Stream stream) => BaseTable.Read(stream); + public static void Write(Stream stream, IEnumerable entries) => + BaseTable.Write(stream, 1, entries); + } +} diff --git a/OpenKh.Kh2/Mixdata/Cond_LP.cs b/OpenKh.Kh2/Mixdata/Cond_LP.cs new file mode 100644 index 000000000..9fed483b9 --- /dev/null +++ b/OpenKh.Kh2/Mixdata/Cond_LP.cs @@ -0,0 +1,87 @@ +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Xe.BinaryMapper; + +namespace OpenKh.Kh2.Mixdata +{ + public class CondLP + //LP for Listpatch version of the file. Currently the BaseMixdata Read/Write seems to not work with reading/writing these files? Tests seemed to fail. + //LP versions of the files exist so as to not ruin the original, cleaner version of the code, however if the original version of the code is used for the Listpatch then current mods wouldn't break. + { + private const int MagicCode = 0x4F43494D; + + public enum RewardType + { + Item = 0, + ShopUpgrade = 1 + } + + public enum CollectionType + { + Stack = 0, + Unique = 1 + } + + [Data] public ushort TextId { get; set; } + [Data] public short Reward { get; set; } //either item from 03system or shop upgrades + [Data] public RewardType Type { get; set; } + [Data] public byte MaterialType { get; set; } + [Data] public byte MaterialRank { get; set; } + [Data] public CollectionType ItemCollect { get; set; } + [Data] public short Count { get; set; } + [Data] public short ShopUnlock { get; set; } + + public static List Read(Stream stream) + { + var condLPList = new List(); + using (var reader = new BinaryReader(stream, System.Text.Encoding.Default, true)) + { + int magicCode = reader.ReadInt32(); + int version = reader.ReadInt32(); + int count = reader.ReadInt32(); + reader.ReadInt32(); // Skip padding + + for (int i = 0; i < count; i++) + { + var condLP = new CondLP + { + TextId = reader.ReadUInt16(), + Reward = reader.ReadInt16(), + Type = (CondLP.RewardType)reader.ReadByte(), + MaterialType = reader.ReadByte(), + MaterialRank = reader.ReadByte(), + ItemCollect = (CondLP.CollectionType)reader.ReadByte(), + Count = reader.ReadInt16(), + ShopUnlock = reader.ReadInt16() + }; + condLPList.Add(condLP); + } + } + return condLPList; + } + public static void Write(Stream stream, List condLPList) + { + stream.Position = 0; + using (var writer = new BinaryWriter(stream, System.Text.Encoding.Default, true)) + { + writer.Write(MagicCode); + writer.Write(2); // Version number, hardcoded for example + writer.Write(condLPList.Count); + writer.Write(0); // Padding + + foreach (var condLP in condLPList) + { + writer.Write(condLP.TextId); + writer.Write(condLP.Reward); + writer.Write((byte)condLP.Type); + writer.Write(condLP.MaterialType); + writer.Write(condLP.MaterialRank); + writer.Write((byte)condLP.ItemCollect); + writer.Write(condLP.Count); + writer.Write(condLP.ShopUnlock); + } + } + } + } +} diff --git a/OpenKh.Kh2/Mixdata/Leve_LP.cs b/OpenKh.Kh2/Mixdata/Leve_LP.cs new file mode 100644 index 000000000..5ef327aa5 --- /dev/null +++ b/OpenKh.Kh2/Mixdata/Leve_LP.cs @@ -0,0 +1,64 @@ +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Xe.BinaryMapper; + +namespace OpenKh.Kh2.Mixdata +{ + public class LeveLP + { + private const int MagicCode = 0x564C494D; + + [Data] public ushort Title { get; set; } //Names from Sys.bar like "Amateur Moogle" + [Data] public ushort Stat { get; set; } //Another text ID. + [Data] public short Enable { get; set; } + [Data] public ushort Padding { get; set; } + [Data] public int Exp { get; set; } + + public static List Read(Stream stream) + { + var leveLPList = new List(); + using (var reader = new BinaryReader(stream, System.Text.Encoding.Default, true)) + { + int magicCode = reader.ReadInt32(); + int version = reader.ReadInt32(); + int count = reader.ReadInt32(); + reader.ReadInt32(); // Skip padding + + for (int i = 0; i < count; i++) + { + var leveLP = new LeveLP + { + Title = reader.ReadUInt16(), + Stat = reader.ReadUInt16(), + Enable = reader.ReadInt16(), + Padding = reader.ReadUInt16(), + Exp = reader.ReadInt32() + }; + leveLPList.Add(leveLP); + } + } + return leveLPList; + } + public static void Write(Stream stream, List leveLPList) + { + stream.Position = 0; + using (var writer = new BinaryWriter(stream, System.Text.Encoding.Default, true)) + { + writer.Write(MagicCode); + writer.Write(2); // Version number, hardcoded for example + writer.Write(leveLPList.Count); + writer.Write(0); // Padding + + foreach (var leveLP in leveLPList) + { + writer.Write(leveLP.Title); + writer.Write(leveLP.Stat); + writer.Write(leveLP.Enable); + writer.Write(leveLP.Padding); + writer.Write(leveLP.Exp); + } + } + } + } +} diff --git a/OpenKh.Kh2/Mixdata/Reci_LP.cs b/OpenKh.Kh2/Mixdata/Reci_LP.cs new file mode 100644 index 000000000..a712c51ee --- /dev/null +++ b/OpenKh.Kh2/Mixdata/Reci_LP.cs @@ -0,0 +1,111 @@ +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Xe.BinaryMapper; + +namespace OpenKh.Kh2.Mixdata +{ + public class ReciLP + { + public const int MagicCode = 0x4552494D; + + public enum UnlockType + { + Recipe = 0, + FreeDevelopment1 = 1, + FreeDevelopment2 = 2, + FreeDevelopment3 = 3, + } + + [Data] public ushort Id { get; set; } //03system -> item + [Data] public UnlockType Unlock { get; set; } + [Data] public byte Rank { get; set; } + [Data] public ushort Item { get; set; } + [Data] public ushort UpgradedItem { get; set; } + [Data] public ushort Ingredient1 { get; set; } + [Data] public ushort Ingredient1Amount { get; set; } + [Data] public ushort Ingredient2 { get; set; } + [Data] public ushort Ingredient2Amount { get; set; } + [Data] public ushort Ingredient3 { get; set; } + [Data] public ushort Ingredient3Amount { get; set; } + [Data] public ushort Ingredient4 { get; set; } + [Data] public ushort Ingredient4Amount { get; set; } + [Data] public ushort Ingredient5 { get; set; } + [Data] public ushort Ingredient5Amount { get; set; } + [Data] public ushort Ingredient6 { get; set; } + [Data] public ushort Ingredient6Amount { get; set; } + + public static List Read(Stream stream) + { + var recipes = new List(); + using (var reader = new BinaryReader(stream, System.Text.Encoding.Default, true)) + { + int magicCode = reader.ReadInt32(); + int version = reader.ReadInt32(); + int count = reader.ReadInt32(); + reader.ReadInt32(); // Skip padding + + for (int i = 0; i < count; i++) + { + var recipe = new ReciLP + { + Id = reader.ReadUInt16(), + Unlock = (UnlockType)reader.ReadByte(), + Rank = reader.ReadByte(), + Item = reader.ReadUInt16(), + UpgradedItem = reader.ReadUInt16(), + Ingredient1 = reader.ReadUInt16(), + Ingredient1Amount = reader.ReadUInt16(), + Ingredient2 = reader.ReadUInt16(), + Ingredient2Amount = reader.ReadUInt16(), + Ingredient3 = reader.ReadUInt16(), + Ingredient3Amount = reader.ReadUInt16(), + Ingredient4 = reader.ReadUInt16(), + Ingredient4Amount = reader.ReadUInt16(), + Ingredient5 = reader.ReadUInt16(), + Ingredient5Amount = reader.ReadUInt16(), + Ingredient6 = reader.ReadUInt16(), + Ingredient6Amount = reader.ReadUInt16() + }; + recipes.Add(recipe); + } + } + return recipes; + } + + public static void Write(Stream stream, List recipes) + { + stream.Position = 0; + using (var writer = new BinaryWriter(stream, System.Text.Encoding.Default, true)) + { + writer.Write(MagicCode); + writer.Write(2); // Version number, hardcoded for example + writer.Write(recipes.Count); + writer.Write(0); // Padding + + foreach (var recipe in recipes) + { + writer.Write(recipe.Id); + writer.Write((byte)recipe.Unlock); + writer.Write(recipe.Rank); + writer.Write(recipe.Item); + writer.Write(recipe.UpgradedItem); + writer.Write(recipe.Ingredient1); + writer.Write(recipe.Ingredient1Amount); + writer.Write(recipe.Ingredient2); + writer.Write(recipe.Ingredient2Amount); + writer.Write(recipe.Ingredient3); + writer.Write(recipe.Ingredient3Amount); + writer.Write(recipe.Ingredient4); + writer.Write(recipe.Ingredient4Amount); + writer.Write(recipe.Ingredient5); + writer.Write(recipe.Ingredient5Amount); + writer.Write(recipe.Ingredient6); + writer.Write(recipe.Ingredient6Amount); + } + } + } + + + } +} diff --git a/OpenKh.Kh2/ModelMultiple.cs b/OpenKh.Kh2/ModelMultiple.cs index 453083d33..f8dfbdaf6 100644 --- a/OpenKh.Kh2/ModelMultiple.cs +++ b/OpenKh.Kh2/ModelMultiple.cs @@ -1,15 +1,28 @@ -using System; +using System; using System.IO; namespace OpenKh.Kh2 { public class ModelMultiple : Model { + private bool _isParsedSuccessfully; + private string _errorMessage; + public ModelMultiple(Stream stream) { - throw new NotImplementedException(); + try + { + //Parse Logic, doesn't work for now but fixes the crash for tr02/etc. + _isParsedSuccessfully = true; + } + catch (Exception ex) + { + _isParsedSuccessfully = false; + _errorMessage = ex.Message; + } } + public override int GroupCount => 0; protected override void InternalWrite(Stream stream) diff --git a/OpenKh.Kh2/Models/ModelCommon.cs b/OpenKh.Kh2/Models/ModelCommon.cs index 629a30eae..4fbc4e040 100644 --- a/OpenKh.Kh2/Models/ModelCommon.cs +++ b/OpenKh.Kh2/Models/ModelCommon.cs @@ -142,12 +142,10 @@ public class Bone [Data] public float TranslationZ { get; set; } [Data] public float TranslationW { get; set; } - /* Bone FLags: - * terminate - * below - * enableBias - * reserved [19] - * undefined [10] + /* Bone BitfLags: + * no_envelop + * not_joint > On when the bone has no rigged vertices + * The rest is unused */ public override string ToString() @@ -237,53 +235,27 @@ public static List getBoneHierarchy(List boneList, int boneIndex) return boneHierarchy; } - // Returns the absolute SRT matrix for each bone public static Matrix4x4[] GetBoneMatrices(List boneList) { - Vector3[] absTranslationList = new Vector3[boneList.Count]; - Quaternion[] absRotationList = new Quaternion[boneList.Count]; - + Matrix4x4[] matrices = new Matrix4x4[boneList.Count]; + /* We compute relative matrices */ for (int i = 0; i < boneList.Count; i++) { - Bone bone = boneList[i]; - int parentIndex = bone.ParentIndex; - Quaternion absRotation; - Vector3 absTranslation; - - if (parentIndex == -1) - { - absRotation = Quaternion.Identity; - absTranslation = Vector3.Zero; - } - else - { - absRotation = absRotationList[parentIndex]; - absTranslation = absTranslationList[parentIndex]; - } - - Vector3 localTranslation = Vector3.Transform(new Vector3(bone.TranslationX, bone.TranslationY, bone.TranslationZ), Matrix4x4.CreateFromQuaternion(absRotation)); - absTranslationList[i] = absTranslation + localTranslation; - - var localRotation = Quaternion.Identity; - if (bone.RotationZ != 0) - localRotation *= (Quaternion.CreateFromAxisAngle(Vector3.UnitZ, bone.RotationZ)); - if (bone.RotationY != 0) - localRotation *= (Quaternion.CreateFromAxisAngle(Vector3.UnitY, bone.RotationY)); - if (bone.RotationX != 0) - localRotation *= (Quaternion.CreateFromAxisAngle(Vector3.UnitX, bone.RotationX)); - absRotationList[i] = absRotation * localRotation; + matrices[i] = + Matrix4x4.CreateScale(boneList[i].ScaleX,boneList[i].ScaleY,boneList[i].ScaleZ) * + Matrix4x4.CreateRotationX(boneList[i].RotationX) * + Matrix4x4.CreateRotationY(boneList[i].RotationY) * + Matrix4x4.CreateRotationZ(boneList[i].RotationZ) * + Matrix4x4.CreateTranslation(boneList[i].TranslationX,boneList[i].TranslationY,boneList[i].TranslationZ); } - - Matrix4x4[] matrices = new Matrix4x4[boneList.Count]; + /* We compute absolute matrices */ for (int i = 0; i < boneList.Count; i++) { - var absMatrix = Matrix4x4.Identity; - absMatrix *= Matrix4x4.CreateFromQuaternion(absRotationList[i]); - absMatrix *= Matrix4x4.CreateTranslation(absTranslationList[i]); - matrices[i] = absMatrix; + if (boneList[i].ParentIndex > -1) + matrices[i] *= matrices[boneList[i].ParentIndex]; } - + /* Done. */ return matrices; } diff --git a/OpenKh.Kh2/Motion.cs b/OpenKh.Kh2/Motion.cs index 4296909bb..4420208ef 100644 --- a/OpenKh.Kh2/Motion.cs +++ b/OpenKh.Kh2/Motion.cs @@ -761,9 +761,9 @@ public enum Channel SCALE_X = 0, SCALE_Y = 1, SCALE_Z = 2, - ROTATATION_X = 3, - ROTATATION_Y = 4, - ROTATATION_Z = 5, + ROTATION_X = 3, + ROTATION_Y = 4, + ROTATION_Z = 5, TRANSLATION_X = 6, TRANSLATION_Y = 7, TRANSLATION_Z = 8, diff --git a/OpenKh.Kh2/Place.cs b/OpenKh.Kh2/Place.cs new file mode 100644 index 000000000..0be12ff9c --- /dev/null +++ b/OpenKh.Kh2/Place.cs @@ -0,0 +1,48 @@ +using OpenKh.Common; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Xe.BinaryMapper; + +namespace OpenKh.Kh2 +{ + public class Places + { + [Data] public ushort MessageId { get; set; } + [Data] public ushort Padding { get; set; } + + //Two bytes after MessageId don't seem to matter. + + public class PlacePatch + { + [Data] public int Index { get; set; } + [Data] public ushort MessageId { get; set; } + [Data] public ushort Padding { get; set; } + } + public static List Read(Stream stream) + { + long count = stream.Length / 4; // Each entry is 4 bytes + var placesList = new List((int)count); + + for (long i = 0; i < count; i++) + { + placesList.Add(new Places + { + MessageId = stream.ReadUInt16(), + Padding = stream.ReadUInt16() + }); + } + + return placesList; + } + + public static void Write(Stream stream, List placesList) + { + foreach (var place in placesList) + { + stream.Write(place.MessageId); + stream.Write(place.Padding); + } + } + } +} diff --git a/OpenKh.Kh2/Soundinfo.cs b/OpenKh.Kh2/Soundinfo.cs new file mode 100644 index 000000000..e2196f829 --- /dev/null +++ b/OpenKh.Kh2/Soundinfo.cs @@ -0,0 +1,49 @@ +using OpenKh.Common; +using System; +using System.Collections.Generic; +using System.IO; +using Xe.BinaryMapper; +namespace OpenKh.Kh2 +//Can properly read/write and update. Can insert new entries between. +{ + public class Soundinfo + { + //[Data] public int Count { get; set; } + + [Data] public short Reverb { get; set; } + [Data] public short Rate { get; set; } + [Data] public short EnvironmentWAV { get; set; } + [Data] public short EnvironmentSEB { get; set; } + [Data] public short EnvironmentNUMBER { get; set; } + [Data] public short EnvironmentSPOT { get; set; } + [Data] public short FootstepWAV { get; set; } + [Data] public short FootstepSORA{ get; set; } + [Data] public short FootstepDONALD { get; set; } + [Data] public short FootstepGOOFY { get; set; } + [Data] public short FootstepWORLDFRIEND { get; set; } + [Data] public short FootstepOTHER { get; set; } + + + public class SoundinfoPatch + { + [Data] public int Index { get; set; } + [Data] public short Reverb { get; set; } + [Data] public short Rate { get; set; } + [Data] public short EnvironmentWAV { get; set; } + [Data] public short EnvironmentSEB { get; set; } + [Data] public short EnvironmentNUMBER { get; set; } + [Data] public short EnvironmentSPOT { get; set; } + [Data] public short FootstepWAV { get; set; } + [Data] public short FootstepSORA { get; set; } + [Data] public short FootstepDONALD { get; set; } + [Data] public short FootstepGOOFY { get; set; } + [Data] public short FootstepWORLDFRIEND { get; set; } + [Data] public short FootstepOTHER { get; set; } + } + + public static List Read(Stream stream) => BaseTableCountOnly.Read(stream); + public static void Write(Stream stream, IEnumerable entries) => + BaseTableCountOnly.Write(stream, entries); + + } +} diff --git a/OpenKh.Kh2/SystemData/Arif.cs b/OpenKh.Kh2/SystemData/Arif.cs index 29e52264f..eb2561a87 100644 --- a/OpenKh.Kh2/SystemData/Arif.cs +++ b/OpenKh.Kh2/SystemData/Arif.cs @@ -1,4 +1,5 @@ using OpenKh.Common; +using System; using System.Collections.Generic; using System.IO; using System.Linq; @@ -17,7 +18,17 @@ public class BgmSet /// public class Arif { - [Data] public uint Flags { get; set; } + [Flags] + public enum ArifFlags: uint + { + IsKnownArea = 0x01, + IndoorArea = 0x02, + Monochrome = 0x04, + NoShadow = 0x08, + HasGlow = 0x10 + } + + [Data] public ArifFlags Flags { get; set; } //Originally a uint. Flags are now defined here. YMLs can use either flag names or values [Data] public int Reverb { get; set; } [Data] public int SoundEffectBank1 { get; set; } [Data] public int SoundEffectBank2 { get; set; } diff --git a/OpenKh.Kh2/SystemData/Memt.cs b/OpenKh.Kh2/SystemData/Memt.cs index 257a132ef..c71bbdfae 100644 --- a/OpenKh.Kh2/SystemData/Memt.cs +++ b/OpenKh.Kh2/SystemData/Memt.cs @@ -1,158 +1,188 @@ -using OpenKh.Common; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using Xe.BinaryMapper; - -namespace OpenKh.Kh2.SystemData -{ - public enum MemberVanilla - { - Sora, - Donald, - Goofy, - WorldCharacter, - FormValor, - FormWisdom, - FormTrinity, - FormFinal, - Antiform, - Mickey, - SoraHighPoly, - FormValorHighPoly, - FormWisdomHighPoly, - FormTrinityHighPoly, - FormFinalHighPoly, - AntiformHighPoly, - } - - public enum MemberFinalMix - { - Sora, - Donald, - Goofy, - WorldCharacter, - FormValor, - FormWisdom, - FormLimit, - FormTrinity, - FormFinal, - Antiform, - Mickey, - SoraHighPoly, - FormValorHighPoly, - FormWisdomHighPoly, - FormLimitHighPoly, - FormTrinityHighPoly, - FormFinalHighPoly, - AntiformHighPoly, - } - - public class Memt - { - private const int Version = 5; - public const int MemberCountVanilla = 16; - public const int MemberCountFinalMix = 18; - - public interface IEntry - { - short CheckStoryFlag { get; set; } - short CheckStoryFlagNegation { get; set; } - short[] Members { get; set; } - short Unk06 { get; set; } - short Unk08 { get; set; } - short Unk0A { get; set; } - short Unk0C { get; set; } - short Unk0E { get; set; } - short WorldId { get; set; } - } - - public class EntryVanilla : IEntry - { - [Data] public short WorldId { get; set; } - [Data] public short CheckStoryFlag { get; set; } - [Data] public short CheckStoryFlagNegation { get; set; } - [Data] public short Unk06 { get; set; } - [Data] public short Unk08 { get; set; } - [Data] public short Unk0A { get; set; } - [Data] public short Unk0C { get; set; } - [Data] public short Unk0E { get; set; } - [Data(Count = MemberCountVanilla)] public short[] Members { get; set; } - } - - public class EntryFinalMix : IEntry - { - [Data] public short WorldId { get; set; } - [Data] public short CheckStoryFlag { get; set; } - [Data] public short CheckStoryFlagNegation { get; set; } - [Data] public short Unk06 { get; set; } - [Data] public short Unk08 { get; set; } - [Data] public short Unk0A { get; set; } - [Data] public short Unk0C { get; set; } - [Data] public short Unk0E { get; set; } - [Data(Count = MemberCountFinalMix)] public short[] Members { get; set; } - } - - public class MemberIndices - { - [Data] public byte Player { get; set; } - [Data] public byte Friend1 { get; set; } - [Data] public byte Friend2 { get; set; } - [Data] public byte FriendWorld { get; set; } - } - - public List Entries { get; } - public MemberIndices[] MemberIndexCollection { get; } - - public Memt() - { - Entries = new List().Cast().ToList(); - MemberIndexCollection = new MemberIndices[1] - { - new MemberIndices - { - Player = 0, - Friend1 = 1, - Friend2 = 2, - FriendWorld = 3 - } - }; - } - - internal Memt(Stream stream) - { - const int HeaderSize = 8; - const int StructLengthVanilla = 48; - const int MemberIndicesExpected = 7; - - var previousPosition = stream.Position; - var version = stream.ReadInt32(); - var count = stream.ReadInt32(); - stream.Position = previousPosition; - - if (stream.Length - previousPosition == HeaderSize + - count * StructLengthVanilla + MemberIndicesExpected * 4) - Entries = BaseTable.Read(stream).Cast().ToList(); - else - Entries = BaseTable.Read(stream).Cast().ToList(); - - MemberIndexCollection = Enumerable.Range(0, MemberIndicesExpected) - .Select(x => BinaryMapping.ReadObject(stream)) - .ToArray(); - } - - public static Memt Read(Stream stream) => new Memt(stream); - - public static void Write(Stream stream, Memt memt) - { - var firstElement = memt.Entries.FirstOrDefault(); - if (firstElement is EntryVanilla) - BaseTable.Write(stream, Version, memt.Entries.Cast()); - else if (firstElement is EntryFinalMix) - BaseTable.Write(stream, Version, memt.Entries.Cast()); - - foreach (var item in memt.MemberIndexCollection) - BinaryMapping.WriteObject(stream, item); - } - } -} +using OpenKh.Common; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Xe.BinaryMapper; + +namespace OpenKh.Kh2.SystemData +{ + public enum MemberVanilla + { + Sora, + Donald, + Goofy, + WorldCharacter, + FormValor, + FormWisdom, + FormTrinity, + FormFinal, + Antiform, + Mickey, + SoraHighPoly, + FormValorHighPoly, + FormWisdomHighPoly, + FormTrinityHighPoly, + FormFinalHighPoly, + AntiformHighPoly, + } + + public enum MemberFinalMix + { + Sora, + Donald, + Goofy, + WorldCharacter, + FormValor, + FormWisdom, + FormLimit, + FormTrinity, + FormFinal, + Antiform, + Mickey, + SoraHighPoly, + FormValorHighPoly, + FormWisdomHighPoly, + FormLimitHighPoly, + FormTrinityHighPoly, + FormFinalHighPoly, + AntiformHighPoly, + } + + public class Memt + { + private const int Version = 5; + public const int MemberCountVanilla = 16; + public const int MemberCountFinalMix = 18; + + public interface IEntry + { + short CheckStoryFlag { get; set; } + short CheckStoryFlagNegation { get; set; } + short[] Members { get; set; } + short Unk06 { get; set; } + short Unk08 { get; set; } + short Unk0A { get; set; } + short Unk0C { get; set; } + short Unk0E { get; set; } + short WorldId { get; set; } + } + + public class EntryVanilla : IEntry + { + [Data] public short WorldId { get; set; } + [Data] public short CheckStoryFlag { get; set; } + [Data] public short CheckStoryFlagNegation { get; set; } + [Data] public short Unk06 { get; set; } + [Data] public short Unk08 { get; set; } + [Data] public short Unk0A { get; set; } + [Data] public short Unk0C { get; set; } + [Data] public short Unk0E { get; set; } + [Data(Count = MemberCountVanilla)] public short[] Members { get; set; } + } + + public class EntryFinalMix : IEntry + { + [Data] public short WorldId { get; set; } + [Data] public short CheckStoryFlag { get; set; } + [Data] public short CheckStoryFlagNegation { get; set; } + [Data] public short Unk06 { get; set; } + [Data] public short Unk08 { get; set; } + [Data] public short Unk0A { get; set; } + [Data] public short Unk0C { get; set; } + [Data] public short Unk0E { get; set; } + [Data(Count = MemberCountFinalMix)] public short[] Members { get; set; } + } + + public class MemberIndices + { + [Data] public byte Player { get; set; } + [Data] public byte Friend1 { get; set; } + [Data] public byte Friend2 { get; set; } + [Data] public byte FriendWorld { get; set; } + } + + public List Entries { get; } + public MemberIndices[] MemberIndexCollection { get; } + + public class MemtEntryPatch + { + public int Index { get; set; } + public short WorldId { get; set; } + public short CheckStoryFlag { get; set; } + public short CheckStoryFlagNegation { get; set; } + public short Unk06 { get; set; } + public short Unk08 { get; set; } + public short Unk0A { get; set; } + public short Unk0C { get; set; } + public short Unk0E { get; set; } + public List Members { get; set; } + } + + public class MemberIndicesPatch + { + public int Index { get; set; } + public byte Player { get; set; } + public byte Friend1 { get; set; } + public byte Friend2 { get; set; } + public byte FriendWorld { get; set; } + } + + public class MemtPatches + { + public List MemtEntries { get; set; } + public List MemberIndices { get; set; } + } + + public Memt() + { + Entries = new List(); + MemberIndexCollection = new MemberIndices[7]; + for (int i = 0; i < MemberIndexCollection.Length; i++) + { + MemberIndexCollection[i] = new MemberIndices + { + Player = 0, + Friend1 = 0, + Friend2 = 0, + FriendWorld = 0 + }; + } + } + + internal Memt(Stream stream) + { + const int HeaderSize = 8; + const int StructLengthVanilla = 48; + const int MemberIndicesExpected = 7; + + var previousPosition = stream.Position; + var version = stream.ReadInt32(); + var count = stream.ReadInt32(); + stream.Position = previousPosition; + + if (stream.Length - previousPosition == HeaderSize + + count * StructLengthVanilla + MemberIndicesExpected * 4) + Entries = BaseTable.Read(stream).Cast().ToList(); + else + Entries = BaseTable.Read(stream).Cast().ToList(); + + MemberIndexCollection = Enumerable.Range(0, MemberIndicesExpected) + .Select(x => BinaryMapping.ReadObject(stream)) + .ToArray(); + } + + public static Memt Read(Stream stream) => new Memt(stream); + + public static void Write(Stream stream, Memt memt) + { + var firstElement = memt.Entries.FirstOrDefault(); + if (firstElement is EntryVanilla) + BaseTable.Write(stream, Version, memt.Entries.Cast()); + else if (firstElement is EntryFinalMix) + BaseTable.Write(stream, Version, memt.Entries.Cast()); + + foreach (var item in memt.MemberIndexCollection) + BinaryMapping.WriteObject(stream, item); + } + } +} diff --git a/OpenKh.Kh2/SystemData/Pref/BasePrefTable.cs b/OpenKh.Kh2/SystemData/Pref/BasePrefTable.cs new file mode 100644 index 000000000..43be455db --- /dev/null +++ b/OpenKh.Kh2/SystemData/Pref/BasePrefTable.cs @@ -0,0 +1,182 @@ +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Xe.BinaryMapper; + +namespace OpenKh.Kh2 +{ + public class BaseTableOffsetWithPaddings + where T : class + { + [Data] public int Count { get; set; } + + public static (List Items, List Offsets) ReadWithOffsets(Stream stream) + { + using (var reader = new BinaryReader(stream, System.Text.Encoding.Default, true)) + { + var header = new BaseTableOffsetWithPaddings + { + Count = reader.ReadInt32(), + }; + + var offsets = new List(); + for (int i = 0; i < header.Count; i++) + { + offsets.Add(reader.ReadInt32()); + } + + var items = new List(); + foreach (var offset in offsets.Distinct()) + { + stream.Position = offset; + items.Add(BinaryMapping.ReadObject(stream)); + } + + return (items, offsets); + } + } + + public static void WriteWithOffsets(Stream stream, IEnumerable items, List originalOffsets) + { + using (var writer = new BinaryWriter(stream, System.Text.Encoding.Default, true)) + { + var itemList = items as IList ?? items.ToList(); + var offsets = new List(); + + var header = new BaseTableOffsetWithPaddings + { + Count = originalOffsets.Count + }; + writer.Write(header.Count); + + foreach (var offset in originalOffsets) + { + writer.Write(offset); // Write original offsets + } + + var offsetMap = originalOffsets.Distinct().ToDictionary(offset => offset, offset => (int)stream.Position); + + foreach (var offset in offsetMap.Values) + { + stream.Position = offset; + BinaryMapping.WriteObject(stream, itemList[offsetMap.Keys.ToList().IndexOf(offset)]); + } + } + } + } + + + + public class BaseTableOffsets + where T : class + { + [Data] public int Count { get; set; } + + public static List Read(Stream stream) + { + using (var reader = new BinaryReader(stream, System.Text.Encoding.Default, true)) + { + var header = new BaseTableOffsets + { + Count = reader.ReadInt32() + }; + + var offsets = new int[header.Count]; + for (int i = 0; i < header.Count; i++) + { + offsets[i] = reader.ReadInt32(); + } + + var items = new List(); + foreach (var offset in offsets) + { + stream.Position = offset; + items.Add(BinaryMapping.ReadObject(stream)); + } + return items; + } + } + + public static void Write(Stream stream, IEnumerable items) + { + using (var writer = new BinaryWriter(stream, System.Text.Encoding.Default, true)) + { + var itemList = items as IList ?? items.ToList(); + var offsets = new List(); + + var header = new BaseTableOffsets + { + Count = itemList.Count + }; + writer.Write(header.Count); + + var offsetPosition = stream.Position; + for (int i = 0; i < itemList.Count; i++) + { + writer.Write(0); // Placeholder for offset + } + + for (int i = 0; i < itemList.Count; i++) + { + offsets.Add((int)stream.Position); + BinaryMapping.WriteObject(stream, itemList[i]); + } + + var currentPosition = stream.Position; + stream.Position = offsetPosition; + foreach (var offset in offsets) + { + writer.Write(offset); + } + stream.Position = currentPosition; + } + } + } + + public class BaseTableSstm + where T : class + { + [Data] public int Version { get; set; } + [Data] public int MagicCode { get; set; } + + public static List Read(Stream stream) + { + using (var reader = new BinaryReader(stream, System.Text.Encoding.Default, true)) + { + var header = new BaseTableSstm + { + Version = reader.ReadInt32(), + MagicCode = reader.ReadInt32() + }; + + var items = new List(); + for (int i = 0; i < 95; i++) // 95 float values + { + items.Add(BinaryMapping.ReadObject(stream)); + } + return items; + } + } + + public static void Write(Stream stream, IEnumerable items) + { + using (var writer = new BinaryWriter(stream, System.Text.Encoding.Default, true)) + { + var itemList = items as IList ?? items.ToList(); + + var header = new BaseTableSstm + { + Version = 6, + MagicCode = 0 + }; + writer.Write(header.Version); + writer.Write(header.MagicCode); + + foreach (var item in itemList) + { + BinaryMapping.WriteObject(stream, item); + } + } + } + } +} diff --git a/OpenKh.Kh2/SystemData/Pref/Fmab.cs b/OpenKh.Kh2/SystemData/Pref/Fmab.cs new file mode 100644 index 000000000..29c8eda26 --- /dev/null +++ b/OpenKh.Kh2/SystemData/Pref/Fmab.cs @@ -0,0 +1,37 @@ +using System.Collections.Generic; +using System.IO; +using Xe.BinaryMapper; + +namespace OpenKh.Kh2.SystemData +{ + public class Fmab + { + //public int Index { get; set; } + [Data] public float HighJumpHeight { get; set; } + [Data] public float AirDodgeHeight { get; set; } + [Data] public float AirDodgeSpeed { get; set; } + [Data] public float AirSlideTime { get; set; } + [Data] public float AirSlideSpeed { get; set; } + [Data] public float AirSlideBrake { get; set; } + [Data] public float AirSlideStopBrake { get; set; } + [Data] public float AirSlideInvulnerableFrames { get; set; } + [Data] public float GlideSpeed { get; set; } + [Data] public float GlideFallRatio { get; set; } + [Data] public float GlideFallHeight { get; set; } + [Data] public float GlideFallMax { get; set; } + [Data] public float GlideAcceleration { get; set; } + [Data] public float GlideStartHeight { get; set; } + [Data] public float GlideEndHeight { get; set; } + [Data] public float GlideTurnSpeed { get; set; } + [Data] public float DodgeRollInvulnerableFrames { get; set; } + + public class FmabEntries + { + [Data] public Dictionary Entries { get; set; } + } + + public static List Read(Stream stream) => BaseTableOffsets.Read(stream); + + public static void Write(Stream stream, IEnumerable entries) => BaseTableOffsets.Write(stream, entries); + } +} diff --git a/OpenKh.Kh2/SystemData/Pref/Magi.cs b/OpenKh.Kh2/SystemData/Pref/Magi.cs new file mode 100644 index 000000000..7406150e7 --- /dev/null +++ b/OpenKh.Kh2/SystemData/Pref/Magi.cs @@ -0,0 +1,45 @@ +using System.Collections.Generic; +using System.IO; +using Xe.BinaryMapper; + +namespace OpenKh.Kh2.SystemData +{ + public class Magi + { + //[Data] public uint Id { get; set; } + [Data] public float FireRadius { get; set; } + [Data] public float FireHeight { get; set; } + [Data] public float FireTime { get; set; } + [Data] public float BlizzardFadeTime { get; set; } + [Data] public float BlizzardTime { get; set; } + [Data] public float BlizzardSpeed { get; set; } + [Data] public float BlizzardRadius { get; set; } + [Data] public float BlizzardHitBack { get; set; } + [Data] public float ThunderNoTargetDistance { get; set; } + [Data] public float ThunderBorderHeight { get; set; } + [Data] public float ThunderCheckHeight { get; set; } + [Data] public float ThunderBurstRadius { get; set; } + [Data] public float ThunderHeight { get; set; } + [Data] public float ThunderRadius { get; set; } + [Data] public float ThunderAttackWait { get; set; } + [Data] public float ThunderTime { get; set; } + [Data] public float CureRange { get; set; } + [Data] public float MagnetMinYOffset { get; set; } + [Data] public float MagnetLargeTime { get; set; } + [Data] public float MagnetStayTime { get; set; } + [Data] public float MagnetSmallTime { get; set; } + [Data] public float MagnetRadius { get; set; } + [Data] public float ReflectRadius { get; set; } + [Data] public float ReflectLaserTime { get; set; } + [Data] public float ReflectFinishTime { get; set; } + [Data] public float ReflectLv1Radius { get; set; } + [Data] public float ReflectLv1Height { get; set; } + [Data] public int ReflectLv2Count { get; set; } + [Data] public float ReflectLv2Radius { get; set; } + [Data] public float ReflectLv2Height { get; set; } + [Data] public int ReflectLv3Count { get; set; } + [Data] public float ReflectLv3Radius { get; set; } + [Data] public float ReflectLv3Height { get; set; } + + } +} diff --git a/OpenKh.Kh2/SystemData/Pref/Plyr.cs b/OpenKh.Kh2/SystemData/Pref/Plyr.cs new file mode 100644 index 000000000..2fa563260 --- /dev/null +++ b/OpenKh.Kh2/SystemData/Pref/Plyr.cs @@ -0,0 +1,39 @@ +using System.Collections.Generic; +using System.IO; +using Xe.BinaryMapper; + +namespace OpenKh.Kh2.SystemData +{ + public class Plyr + { + [Data] public float AttackYOffset { get; set; } + [Data] public float AttackRadius { get; set; } + [Data] public float AttackMinHeight { get; set; } + [Data] public float AttackMaxHeight { get; set; } + [Data] public float AttackVAngle { get; set; } + [Data] public float AttackVRange { get; set; } + [Data] public float AttackSRange { get; set; } + [Data] public float AttackHAngle { get; set; } + [Data] public float AttackUMinHeight { get; set; } + [Data] public float AttackUMaxHeight { get; set; } + [Data] public float AttackURange { get; set; } + [Data] public float AttackJFront { get; set; } + [Data] public float AttackAirMinHeight { get; set; } + [Data] public float AttackAirMaxHeight { get; set; } + [Data] public float AttackAirBigHeightOffset { get; set; } + [Data] public float AttackUV0 { get; set; } + [Data] public float AttackJV0 { get; set; } + [Data] public float AttackFirstV0 { get; set; } + [Data] public float AttackComboV0 { get; set; } + [Data] public float AttackFinishV0 { get; set; } + [Data] public float BlowRecoveryH { get; set; } + [Data] public float BlowRecoveryV { get; set; } + [Data] public float BlowRecoveryTime { get; set; } + [Data] public float AutoLockonRange { get; set; } + [Data] public float AutoLockonMinHeight { get; set; } + [Data] public float AutoLockonMaxHeight { get; set; } + [Data] public float AutoLockonTime { get; set; } + [Data] public float AutoLockonHeightAdjust { get; set; } + [Data] public float AutoLockonInnerAdjust { get; set; } + } +} diff --git a/OpenKh.Kh2/SystemData/Pref/Prty.cs b/OpenKh.Kh2/SystemData/Pref/Prty.cs new file mode 100644 index 000000000..477805dc1 --- /dev/null +++ b/OpenKh.Kh2/SystemData/Pref/Prty.cs @@ -0,0 +1,83 @@ +using System.Collections.Generic; +using System.IO; +using Xe.BinaryMapper; + +namespace OpenKh.Kh2.SystemData +{ + public class Prty + { + //Structure: 0x4 bytes for Count, then for Count, offsets pointing to different characters in the file. + //0x00000000 seems to be some filler data? Unknown what purpose it serves. + //Offsets also repeat. This is because the index of an offset is used for the NeoMoveset of a character. + //Unsure why, but worlds with different costumes (NM, WI, TR, etc.) need to use different NeoMoveset values even if they read the exact same values. + //For Sora, NeoMoveset also is linked to the PTYA entry to use. + [Data] public float WalkSpeed { get; set; } + [Data] public float RunSpeed { get; set; } + [Data] public float JumpHeight { get; set; } + [Data] public float TurnSpeed { get; set; } + [Data] public float HangHeight { get; set; } + [Data] public float HangMargin { get; set; } + [Data] public float StunTime { get; set; } + [Data] public float MpChargeTime { get; set; } + [Data] public float UpDownSpeed { get; set; } + [Data] public float DashSpeed { get; set; } + [Data] public float Acceleration { get; set; } + [Data] public float Brake { get; set; } + [Data] public float Subjective { get; set; } + + public class PrtyEntries + { + [Data] public Dictionary Entries { get; set; } + } + public static readonly Dictionary CharacterMap = new Dictionary + { + {"Sora", 0}, + {"ValorForm", 1}, + {"WisdomForm", 2}, + {"MasterForm", 3}, + {"FinalForm", 4}, + {"AntiForm", 5}, + {"SoraLK", 6}, + {"SoraLM", 7}, + {"Donald", 8}, + {"DonaldLK", 9}, + {"DonaldLM", 10}, + {"Goofy", 11}, + {"GoofyLK", 12}, + {"Aladdin", 13}, + {"Auron", 14}, + {"Mulan", 15}, + {"Ping", 16}, + {"Tron", 17}, + {"Mickey", 18}, + {"Beast", 19}, + {"Jack", 20}, + {"Simba", 21}, + {"Sparrow", 22}, + {"Riku", 23}, + {"MagicCarpet", 24}, + {"LightCycle", 25}, + {"SoraDie", 26}, + {"Unknown1", 27}, + {"Unknown2", 28}, + {"GummiShip", 29}, + {"RedRocket", 30}, + {"Neverland", 31}, + {"Session", 32}, + {"LimitForm", 33} + }; + + //Read/Write doesn't currently work. + + public static (List Items, List Offsets) Read(Stream stream) + { + return BaseTableOffsetWithPaddings.ReadWithOffsets(stream); + } + + public static void Write(Stream stream, IEnumerable entries, List originalOffsets) + { + BaseTableOffsetWithPaddings.WriteWithOffsets(stream, entries, originalOffsets); + } + + } +} diff --git a/OpenKh.Kh2/SystemData/Pref/Sstm.cs b/OpenKh.Kh2/SystemData/Pref/Sstm.cs new file mode 100644 index 000000000..110041728 --- /dev/null +++ b/OpenKh.Kh2/SystemData/Pref/Sstm.cs @@ -0,0 +1,118 @@ +using System.Collections.Generic; +using System.IO; +using Xe.BinaryMapper; + +namespace OpenKh.Kh2.SystemData +{ + public class Sstm + { + //[Data] public uint Id { get; set; } + [Data] public float CeilingStop { get; set; } + [Data] public float CeilingDisableCommandTime { get; set; } + [Data] public float HangRangeH { get; set; } + [Data] public float HangRangeL { get; set; } + [Data] public float HangRangeXZ { get; set; } + [Data] public float FallMax { get; set; } + [Data] public float BlowBrakeXZ { get; set; } + [Data] public float BlowMinXZ { get; set; } + [Data] public float BlowBrakeUp { get; set; } + [Data] public float BlowUp { get; set; } + [Data] public float BlowSpeed { get; set; } + [Data] public float BlowToHitBack { get; set; } + [Data] public float HitBack { get; set; } + [Data] public float HitBackSmall { get; set; } + [Data] public float HitBackToJump { get; set; } + [Data] public float FlyBlowBrake { get; set; } + [Data] public float FlyBlowStop { get; set; } + [Data] public float FlyBlowUpAdjust { get; set; } + [Data] public float MagicJump { get; set; } + [Data] public float LockOnRange { get; set; } + [Data] public float LockOnReleaseRange { get; set; } + [Data] public float StunRecovery { get; set; } + [Data] public float StunRecoveryHp { get; set; } + [Data] public float StunRelax { get; set; } + [Data] public float DriveEnemy { get; set; } + [Data] public float ChangeTimeEnemy { get; set; } + [Data] public float DriveTime { get; set; } + [Data] public float DriveTimeRelax { get; set; } + [Data] public float ChangeTimeAddRate { get; set; } + [Data] public float ChangeTimeSubRate { get; set; } + [Data] public float MpDriveRate { get; set; } + [Data] public float MpToMpDrive { get; set; } + [Data] public float SummonTimeRelax { get; set; } + [Data] public float SummonPrayTime { get; set; } + [Data] public float SummonPrayTimeSkip { get; set; } + [Data] public int AntiFormDriveCount { get; set; } + [Data] public int AntiFormSubCount { get; set; } + [Data] public float AntiFormDamageRate { get; set; } + [Data] public float FinalFormRate { get; set; } + [Data] public float FinalFormMulRate { get; set; } + [Data] public float FinalFormMaxRate { get; set; } + [Data] public int FinalFormSubCount { get; set; } + [Data] public float AttackDistanceToSpeed { get; set; } + [Data] public float AlCarpetDashInner { get; set; } + [Data] public float AlCarpetDashDelay { get; set; } + [Data] public float AlCarpetDashAcceleration { get; set; } + [Data] public float AlCarpetDashBrake { get; set; } + [Data] public float LkDashDriftInner { get; set; } + [Data] public float LkDashDriftTime { get; set; } + [Data] public float LkDashAccelerationDrift { get; set; } + [Data] public float LkDashAccelerationStop { get; set; } + [Data] public float LkDashDriftSpeed { get; set; } + [Data] public float LkMagicJump { get; set; } + [Data] public float MickeyChargeWait { get; set; } + [Data] public float MickeyDownRate { get; set; } + [Data] public float MickeyMinRate { get; set; } + [Data] public float LmSwimSpeed { get; set; } + [Data] public float LmSwimControl { get; set; } + [Data] public float LmSwimAcceleration { get; set; } + [Data] public float LmDolphinAcceleration { get; set; } + [Data] public float LmDolphinSpeedMax { get; set; } + [Data] public float LmDolphinSpeedMin { get; set; } + [Data] public float LmDolphinSpeedMaxDistance { get; set; } + [Data] public float LmDolphinSpeedMinDistance { get; set; } + [Data] public float LmDolphinRotationMax { get; set; } + [Data] public float LmDolphinRotationDistance { get; set; } + [Data] public float LmDolphinRotationMaxDistance { get; set; } + [Data] public float LmDolphinDistanceToTime { get; set; } + [Data] public float LmDolphinTimeMax { get; set; } + [Data] public float LmDolphinTimeMin { get; set; } + [Data] public float LmDolphinNearSpeed { get; set; } + [Data] public int DriveBerserkAttack { get; set; } + [Data] public float MpHaste { get; set; } + [Data] public float MpHastera { get; set; } + [Data] public float MpHastega { get; set; } + [Data] public float DrawRange { get; set; } + [Data] public int ComboDamageUp { get; set; } + [Data] public int ReactionDamageUp { get; set; } + [Data] public float DamageDrive { get; set; } + [Data] public float DriveBoost { get; set; } + [Data] public float FormBoost { get; set; } + [Data] public float ExpChance { get; set; } + [Data] public int Defender { get; set; } + [Data] public int ElementUp { get; set; } + [Data] public float DamageAspir { get; set; } + [Data] public float HyperHeal { get; set; } + [Data] public float CombinationBoost { get; set; } + [Data] public float PrizeUp { get; set; } + [Data] public float LuckUp { get; set; } + [Data] public int ItemUp { get; set; } + [Data] public float AutoHeal { get; set; } + [Data] public float SummonBoost { get; set; } + [Data] public float DriveConvert { get; set; } + [Data] public float DefenseMaster { get; set; } + [Data] public int DefenseMasterRatio { get; set; } + + public class SstmPatch + { + public Dictionary Properties { get; set; } = new Dictionary(); + } + + //Read/Write doesn't currently work. + public static List Read(Stream stream) => BaseTableSstm.Read(stream); + + public static void Write(Stream stream, IEnumerable entries) => BaseTableSstm.Write(stream, entries); + + } + +} diff --git a/OpenKh.Patcher/Metadata.cs b/OpenKh.Patcher/Metadata.cs index c1da57177..c6bb4be9d 100644 --- a/OpenKh.Patcher/Metadata.cs +++ b/OpenKh.Patcher/Metadata.cs @@ -1,70 +1,113 @@ -using OpenKh.Kh2; -using System.Collections.Generic; -using System.IO; -using YamlDotNet.Serialization; -using YamlDotNet.Serialization.NamingConventions; - -namespace OpenKh.Patcher -{ - public class Metadata - { - public class Dependency - { - public string Name { get; set; } - } - - public string Title { get; set; } - public string OriginalAuthor { get; set; } - public string Description { get; set; } - [YamlMember(DefaultValuesHandling = DefaultValuesHandling.OmitDefaults)] public string Game { get; set; } - [YamlMember(DefaultValuesHandling = DefaultValuesHandling.OmitDefaults)] public int Specifications { get; set; } - public List Dependencies { get; set; } - public List Assets { get; set; } - - private static readonly IDeserializer deserializer = - new DeserializerBuilder() - .IgnoreFields() - .IgnoreUnmatchedProperties() - .WithNamingConvention(CamelCaseNamingConvention.Instance) - .Build(); - private static readonly ISerializer serializer = - new SerializerBuilder() - .IgnoreFields() - .WithNamingConvention(CamelCaseNamingConvention.Instance) - .Build(); - - public static Metadata Read(Stream stream) => - deserializer.Deserialize(new StreamReader(stream)); +using OpenKh.Kh2; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Text.RegularExpressions; +using YamlDotNet.Serialization; +using YamlDotNet.Serialization.NamingConventions; + +namespace OpenKh.Patcher +{ + public class Metadata + { + public class Dependency + { + public string Name { get; set; } + } + + public string Title { get; set; } + public string OriginalAuthor { get; set; } + public string Description { get; set; } + [YamlMember(DefaultValuesHandling = DefaultValuesHandling.OmitDefaults)] public string Game { get; set; } + [YamlMember(DefaultValuesHandling = DefaultValuesHandling.OmitDefaults)] public int Specifications { get; set; } + public List Dependencies { get; set; } + public List Assets { get; set; } + + private static readonly IDeserializer deserializer = + new DeserializerBuilder() + .IgnoreFields() + .IgnoreUnmatchedProperties() + .WithNamingConvention(CamelCaseNamingConvention.Instance) + .Build(); + private static readonly ISerializer serializer = + new SerializerBuilder() + .IgnoreFields() + .WithNamingConvention(CamelCaseNamingConvention.Instance) + .Build(); + + + public static Metadata Read(Stream stream) + { + try + { + return deserializer.Deserialize(new StreamReader(stream)); //Simplifed way of doing this. Cuts out the "deserializer" redundancies and variable for StreamReader(stream)) + } + + catch (YamlDotNet.Core.YamlException ex) + { + // Handle YAML parsing errors + Debug.WriteLine($"Error deserializing YAML: {ex.Message}"); + + string originalTitle = string.Empty; + + // Extract title using regex + stream.Position = 0; // Reset stream position + using (var reader = new StreamReader(stream)) + { + string yamlContent = reader.ReadToEnd(); + var match = Regex.Match(yamlContent, @"(?<=title:).*"); + if (match.Success) + { + originalTitle = match.Value.Trim(); + } + } + + var metadata = new Metadata + { + Title = $"{originalTitle}* \nMOD YML ERROR DETECTED: CHECK FORMATTING" + }; + + return metadata; // Return modified metadata indicating failure + } + catch (Exception ex) + { + // Handle other unexpected errors + Debug.WriteLine($"Unexpected error: {ex.Message}"); + throw; // Rethrow other exceptions for further investigation + } + } + public void Write(Stream stream) { using (var writer = new StreamWriter(stream)) { serializer.Serialize(writer, this); } - } - public override string ToString() => - serializer.Serialize(this); - } - - public class AssetFile - { - [YamlMember(DefaultValuesHandling = DefaultValuesHandling.OmitDefaults)] public string Name { get; set; } - [YamlMember(DefaultValuesHandling = DefaultValuesHandling.OmitDefaults)] public string Method { get; set; } - [YamlMember(DefaultValuesHandling = DefaultValuesHandling.OmitDefaults)] public string Platform { get; set; } - [YamlMember(DefaultValuesHandling = DefaultValuesHandling.OmitDefaults)] public string Package { get; set; } - [YamlMember(DefaultValuesHandling = DefaultValuesHandling.OmitDefaults)] public List Multi { get; set; } - [YamlMember(DefaultValuesHandling = DefaultValuesHandling.OmitDefaults)] public List Source { get; set; } - - [YamlMember(DefaultValuesHandling = DefaultValuesHandling.OmitDefaults)] public bool Required { get; set; } - [YamlMember(DefaultValuesHandling = DefaultValuesHandling.OmitDefaults)] public string Type { get; set; } - [YamlMember(DefaultValuesHandling = DefaultValuesHandling.OmitDefaults)] public Bar.MotionsetType MotionsetType { get; set; } - [YamlMember(DefaultValuesHandling = DefaultValuesHandling.OmitDefaults)] public string Language { get; set; } - [YamlMember(DefaultValuesHandling = DefaultValuesHandling.OmitDefaults)] public bool IsSwizzled { get; set; } - [YamlMember(DefaultValuesHandling = DefaultValuesHandling.OmitDefaults)] public int Index { get; set; } - } - - public class Multi - { - public string Name { get; set; } - } -} + } + public override string ToString() => + serializer.Serialize(this); + } + + public class AssetFile + { + [YamlMember(DefaultValuesHandling = DefaultValuesHandling.OmitDefaults)] public string Name { get; set; } + [YamlMember(DefaultValuesHandling = DefaultValuesHandling.OmitDefaults)] public string Method { get; set; } + [YamlMember(DefaultValuesHandling = DefaultValuesHandling.OmitDefaults)] public string Platform { get; set; } + [YamlMember(DefaultValuesHandling = DefaultValuesHandling.OmitDefaults)] public string Package { get; set; } + [YamlMember(DefaultValuesHandling = DefaultValuesHandling.OmitDefaults)] public List Multi { get; set; } + [YamlMember(DefaultValuesHandling = DefaultValuesHandling.OmitDefaults)] public List Source { get; set; } + + [YamlMember(DefaultValuesHandling = DefaultValuesHandling.OmitDefaults)] public bool Required { get; set; } + [YamlMember(DefaultValuesHandling = DefaultValuesHandling.OmitDefaults)] public string Type { get; set; } + [YamlMember(DefaultValuesHandling = DefaultValuesHandling.OmitDefaults)] public Bar.MotionsetType MotionsetType { get; set; } + [YamlMember(DefaultValuesHandling = DefaultValuesHandling.OmitDefaults)] public string Language { get; set; } + [YamlMember(DefaultValuesHandling = DefaultValuesHandling.OmitDefaults)] public bool IsSwizzled { get; set; } + [YamlMember(DefaultValuesHandling = DefaultValuesHandling.OmitDefaults)] public int Index { get; set; } + } + + public class Multi + { + public string Name { get; set; } + } +} diff --git a/OpenKh.Patcher/OpenKh.Patcher.csproj b/OpenKh.Patcher/OpenKh.Patcher.csproj index 23cc028f6..915867a75 100644 --- a/OpenKh.Patcher/OpenKh.Patcher.csproj +++ b/OpenKh.Patcher/OpenKh.Patcher.csproj @@ -5,10 +5,11 @@ - + + diff --git a/OpenKh.Patcher/PatcherProcessor.cs b/OpenKh.Patcher/PatcherProcessor.cs index f3ae2026a..bd020a6cd 100644 --- a/OpenKh.Patcher/PatcherProcessor.cs +++ b/OpenKh.Patcher/PatcherProcessor.cs @@ -11,7 +11,7 @@ using System.IO; using System.Linq; using YamlDotNet.Serialization; -using OpenKh.Kh2.Battle; +using OpenKh.Bbs; namespace OpenKh.Patcher { @@ -80,13 +80,13 @@ public void Patch(string originalAssets, string outputDir, string modFilePath) "Recom", }; - public void Patch(string originalAssets, string outputDir, Metadata metadata, string modBasePath, int platform = 1, bool fastMode = false, IDictionary packageMap = null, string LaunchGame = null) + public void Patch(string originalAssets, string outputDir, Metadata metadata, string modBasePath, int platform = 1, bool fastMode = false, IDictionary packageMap = null, string LaunchGame = null, string Language = "en", bool Tests = false) { - + var context = new Context(metadata, originalAssets, modBasePath, outputDir); try { - + if (metadata.Assets == null) throw new Exception("No assets found."); if (metadata.Game != null && GamesList.Contains(metadata.Game.ToLower()) && metadata.Game.ToLower() != LaunchGame.ToLower()) @@ -117,8 +117,8 @@ public void Patch(string originalAssets, string outputDir, Metadata metadata, st _packageFile = assetFile.Package != null && !fastMode ? assetFile.Package : "bbs_first"; break; case "Recom": - if (assetFile!= null) - _packageFile = "Recom"; + if (assetFile != null) + _packageFile = "Recom"; break; default: _packageFile = assetFile.Package != null && !fastMode ? assetFile.Package : "kh2_first"; @@ -181,18 +181,97 @@ public void Patch(string originalAssets, string outputDir, Metadata metadata, st context.EnsureDirectoryExists(dstFile); + //Update: Prevent from copying a blank file should it not exist. try { - context.CopyOriginalFile(name, dstFile); + bool multi = false; + if (assetFile.Multi != null) + { + foreach (var entry in assetFile.Multi) + { + if (File.Exists(context.GetOriginalAssetPath(entry.Name))) + { + multi = true; + } + } + } + //If editing subfiles (not Method: copy and not Method: imd) make sure the original file exists OR + //If copying a file from the mod (NOT Type: internal) make sure it exists (doesnt check if the location its going normally exists) OR + //If copying a file from the users extraction (Type: internal) make sure it exists (doesnt check if the location its going normally exists) OR + //Ignore if its from a test + if ((assetFile.Method != "copy" && assetFile.Method != "imd" && (File.Exists(context.GetOriginalAssetPath(assetFile.Name)) || multi)) || + ((assetFile.Method == "copy" || assetFile.Method == "imd") && assetFile.Source[0].Type != "internal" && File.Exists(context.GetSourceModAssetPath(assetFile.Source[0].Name))) || + ((assetFile.Method == "copy" || assetFile.Method == "imd") && assetFile.Source[0].Type == "internal" && (File.Exists(context.GetOriginalAssetPath(assetFile.Source[0].Name)) || multi)) || + Tests) + { + context.CopyOriginalFile(name, dstFile); - using var _stream = File.Open(dstFile, FileMode.OpenOrCreate, FileAccess.ReadWrite); - PatchFile(context, assetFile, _stream); + using var _stream = File.Open(dstFile, FileMode.OpenOrCreate, FileAccess.ReadWrite); + PatchFile(context, assetFile, _stream); - _stream.Close(); - _stream.Dispose(); + _stream.Close(); + _stream.Dispose(); + } + else + { + List globalFilePaths = new List { ".a.fr", ".a.gr", ".a.it", ".a.sp", ".a.us", "/fr/", "/gr/", "/it/", "/sp/", "/us/" }; + if (assetFile.Method != "copy" && assetFile.Method != "imd") + { + if (platform == 2) + { + if (Language != "jp") + { + if (!context.GetOriginalAssetPath(assetFile.Name).Contains(".a.fm") && !context.GetOriginalAssetPath(assetFile.Name).Contains("/jp/")) + { + Log.Warn("File not found: " + context.GetOriginalAssetPath(assetFile.Name) + " Skipping. \nPlease check your game extraction."); + } + } + else + { + if (!globalFilePaths.Any(x=>context.GetOriginalAssetPath(assetFile.Name).Contains(x))) + { + Log.Warn("File not found: " + context.GetOriginalAssetPath(assetFile.Name) + " Skipping. \nPlease check your game extraction."); + } + } + } + else + { + Log.Warn("File not found: " + context.GetOriginalAssetPath(assetFile.Name) + " Skipping. \nPlease check your game extraction."); + } + } + else if (assetFile.Source[0].Type == "internal") + { + if (platform == 2) + { + if (Language != "jp") + { + if (!context.GetOriginalAssetPath(assetFile.Name).Contains(".a.fm") && !context.GetOriginalAssetPath(assetFile.Name).Contains("/jp/")) + { + Log.Warn("File not found: " + context.GetOriginalAssetPath(assetFile.Name) + " Skipping. \nPlease check your game extraction."); + } + } + else + { + if (!globalFilePaths.Any(x => context.GetOriginalAssetPath(assetFile.Name).Contains(x))) + { + Log.Warn("File not found: " + context.GetOriginalAssetPath(assetFile.Name) + " Skipping. \nPlease check your game extraction."); + } + } + } + else + { + Log.Warn("File not found: " + context.GetOriginalAssetPath(assetFile.Name) + " Skipping. \nPlease check your game extraction."); + } + } + else + { + Log.Warn("File not found: " + context.GetSourceModAssetPath(assetFile.Source[0].Name) + " Skipping. \nPlease check your mod install of: " + metadata.Title); + } + } } - catch (IOException) { } + //This is here so the user does not have to close Mod Manager to see what the warnings were if any. Helpful especially on PC since the build window closes after build unlike emulator where it stays open during mod injection. + Log.Flush(); } }); } @@ -218,6 +297,9 @@ private static void PatchFile(Context context, AssetFile assetFile, Stream strea case "binarc": PatchBinarc(context, assetFile, stream); break; + case "bbsarc": + PatchBBSArc(context, assetFile, stream); + break; case "imd": case "imgd": CreateImageImd(context, assetFile.Source[0]).Write(stream); @@ -244,6 +326,9 @@ private static void PatchFile(Context context, AssetFile assetFile, Stream strea case "listpatch": PatchList(context, assetFile.Source, stream); break; + case "synthpatch": + PatchSynth(context, assetFile.Source, stream); + break; default: Log.Warn($"Method '{assetFile.Method}' not recognized for '{assetFile.Name}'. Falling back to 'copy'"); CopyFile(context, assetFile, stream); @@ -260,7 +345,7 @@ private static void CopyFile(Context context, AssetFile assetFile, Stream stream if (assetFile.Source == null || assetFile.Source.Count == 0) throw new Exception($"File '{assetFile.Name}' does not contain any source"); - string srcFile; + string srcFile; if (assetFile.Source[0].Type == "internal") { @@ -276,6 +361,10 @@ private static void CopyFile(Context context, AssetFile assetFile, Stream stream srcStream.CopyTo(stream); } + + + + //Binarc Update: Specify by Name OR Index. Some files in BARS may have the same name but different indexes, and you want to patch a later index only. private static void PatchBinarc(Context context, AssetFile assetFile, Stream stream) { var binarc = Bar.IsValid(stream) ? Bar.Read(stream) : @@ -289,7 +378,20 @@ private static void PatchBinarc(Context context, AssetFile assetFile, Stream str if (!Enum.TryParse(file.Type, true, out var barEntryType)) throw new Exception($"BinArc type {file.Type} not recognized"); - var entry = binarc.FirstOrDefault(x => x.Name == file.Name && x.Type == barEntryType); + Bar.Entry entry = null; + + // Check if the name is specified + if (!string.IsNullOrEmpty(file.Name)) + { + entry = binarc.FirstOrDefault(x => x.Name == file.Name && x.Type == barEntryType); + } + // If name is not specified but index is + else if (file.Index >= 0 && file.Index < binarc.Count) + { + entry = binarc[file.Index]; + } + + //If entry is not found by name or index, create a new one if (entry == null) { entry = new Bar.Entry @@ -309,6 +411,39 @@ private static void PatchBinarc(Context context, AssetFile assetFile, Stream str entry.Stream?.Dispose(); } + private static void PatchBBSArc(Context context, AssetFile assetFile, Stream stream) + { + var entryList = Arc.IsValid(stream) ? Arc.Read(stream).ToList() : new List(); + foreach (var file in assetFile.Source) + { + var entry = entryList.FirstOrDefault(e => e.Name == file.Name); + + if (entry == null) + { + entry = new Arc.Entry() + { + Name = file.Name + }; + entryList.Add(entry); + } + else if (entry.IsLink) + { + throw new Exception("Cannot patch an arc link!"); + } + + MemoryStream data = new MemoryStream(); + if (entry.Data != null) + { + data.Write(entry.Data); + data.SetPosition(0); + } + PatchFile(context, file, data); + entry.Data = data.ToArray(); + } + + OpenKh.Bbs.Arc.Write(entryList, stream.SetPosition(0)); + } + private static Imgd CreateImageImd(Context context, AssetFile source) { var srcFile = context.GetSourceModAssetPath(source.Name); @@ -430,7 +565,7 @@ private static void PatchBdscript(Context context, AssetFile assetFile, Stream s if (!File.Exists(srcFile)) throw new FileNotFoundException($"The mod does not contain the file {scriptName}", srcFile); - + var programsInput = File.ReadAllText(context.GetSourceModAssetPath(scriptName)); var ascii = BdxAsciiModel.ParseText(programsInput); var decoder = new BdxEncoder( @@ -458,15 +593,19 @@ private static void PatchSpawnPoint(Context context, AssetFile assetFile, Stream if (!File.Exists(srcFile)) throw new FileNotFoundException($"The mod does not contain the file {assetFile.Source[0].Name}", srcFile); - var spawnPoint = Helpers.YamlDeserialize>(File.ReadAllText(srcFile)); + var spawnPoint = OpenKh.Common.Helpers.YamlDeserialize>(File.ReadAllText(srcFile)); Kh2.Ard.SpawnPoint.Write(stream.SetPosition(0), spawnPoint); } private static readonly Dictionary characterMap = new Dictionary(){ - { "Sora", 1 }, { "Donald", 2 }, { "Goofy", 3 }, { "Mickey", 4 }, { "Auron", 5 }, { "PingMulan",6 }, { "Aladdin", 7 }, { "Sparrow", 8 }, { "Beast", 9 }, { "Jack", 10 }, { "Simba", 11 }, { "Tron", 12 }, { "Riku", 13 }, { "Roxas", 14}, {"Ping", 15} + { "Sora", 1 }, { "Donald", 2 }, { "Goofy", 3 }, { "Mickey", 4 }, { "Auron", 5 }, { "PingMulan",6 }, { "Aladdin", 7 }, { "Sparrow", 8 }, { "Beast", 9 }, { "Jack", 10 }, { "Simba", 11 }, { "Tron", 12 }, { "Riku", 13 }, { "Roxas", 14}, {"Ping", 15} }; + private static readonly Dictionary worldIndexMap = new Dictionary(StringComparer.OrdinalIgnoreCase){ + { "worldzz", 0 }, { "endofsea", 1 }, { "twilighttown", 2 }, { "destinyisland", 3 }, { "hollowbastion", 4 }, { "beastscastle", 5 }, { "olympuscoliseum", 6 }, { "agrabah", 7 }, { "thelandofdragons", 8 }, { "100acrewood", 9 }, { "prideland", 10 }, { "atlantica", 11 }, { "disneycastle", 12 }, { "timelessriver", 13}, {"halloweentown", 14}, { "worldmap", 15 }, { "portroyal", 16 }, { "spaceparanoids", 17 }, { "theworldthatneverwas", 18 } + }; + private static readonly IDeserializer deserializer = new DeserializerBuilder().IgnoreUnmatchedProperties().Build(); private static void PatchList(Context context, List sources, Stream stream) @@ -616,23 +755,37 @@ private static void PatchList(Context context, List sources, Stream s case "atkp": var atkpList = Kh2.Battle.Atkp.Read(stream); var moddedAtkp = deserializer.Deserialize>(sourceText); + foreach (var attack in moddedAtkp) { - var oldAtkp = atkpList.First(x => x.Id == attack.Id && x.SubId == attack.SubId && x.Switch == attack.Switch); - atkpList[atkpList.IndexOf(oldAtkp)] = attack; + //Same general template used for cmd, enmp, and przt. + // Check if the attack exists in atkpList based on Id, SubId, and Switch + var existingAttack = atkpList.FirstOrDefault(x => x.Id == attack.Id && x.SubId == attack.SubId && x.Switch == attack.Switch); + + if (existingAttack != null) + { + // Update existing attack in atkpList + atkpList[atkpList.IndexOf(existingAttack)] = attack; + } + else + { + // Add the attack to atkpList if it doesn't exist + atkpList.Add(attack); + } } + Kh2.Battle.Atkp.Write(stream.SetPosition(0), atkpList); break; case "lvpm": var lvpmList = Kh2.Battle.Lvpm.Read(stream); var moddedLvpm = deserializer.Deserialize>(sourceText); - var helperList = LvpmHelper.ConvertLvpmListToHelper(lvpmList); + var helperList = Kh2.Battle.LvpmHelper.ConvertLvpmListToHelper(lvpmList); foreach (var level in moddedLvpm) { var oldLvpm = helperList.First(x => x.Level == level.Level); - lvpmList[helperList.IndexOf(oldLvpm)] = LvpmHelper.ConvertLvpmHelperToLvpm(level); + lvpmList[helperList.IndexOf(oldLvpm)] = Kh2.Battle.LvpmHelper.ConvertLvpmHelperToLvpm(level); } Kh2.Battle.Lvpm.Write(stream.SetPosition(0), lvpmList); break; @@ -660,70 +813,593 @@ private static void PatchList(Context context, List sources, Stream s foreach (var plrp in moddedPlrp) { var oldPlrp = plrpList.First(x => x.Character == plrp.Character && x.Id == plrp.Id); - plrpList[plrpList.IndexOf(oldPlrp)] = plrp; + if (oldPlrp != null) + { + plrpList[plrpList.IndexOf(oldPlrp)] = plrp; + } + else + { + plrpList.Add(plrp); + } } Kh2.Battle.Plrp.Write(stream.SetPosition(0), plrpList); break; case "cmd": - var cmdList = Kh2.SystemData.Cmd.Read(stream); - var moddedCmd = deserializer.Deserialize>(sourceText); - foreach (var commands in moddedCmd) + var cmdList = Kh2.SystemData.Cmd.Read(stream); + var moddedCmd = deserializer.Deserialize>(sourceText); + + foreach (var commands in moddedCmd) { - var oldCommands = cmdList.First(x => x.Id == commands.Id && x.Id == commands.Id); - cmdList[cmdList.IndexOf(oldCommands)] = commands; + var existingCommand = cmdList.FirstOrDefault(x => x.Id == commands.Id); + + if (existingCommand != null) + { + cmdList[cmdList.IndexOf(existingCommand)] = commands; + } + else + { + cmdList.Add(commands); + } } + Kh2.SystemData.Cmd.Write(stream.SetPosition(0), cmdList); break; + case "localset": + var localList = Kh2.Localset.Read(stream); + var moddedLocal = deserializer.Deserialize>(sourceText); + + foreach (var set in moddedLocal) + { + var existingSet = localList.FirstOrDefault(x => x.ProgramId == set.ProgramId); + + if (existingSet != null) + { + localList[localList.IndexOf(existingSet)] = set; + } + else + { + localList.Add(set); + } + } + + Kh2.Localset.Write(stream.SetPosition(0), localList); + break; + + case "jigsaw": + var jigsawList = Kh2.Jigsaw.Read(stream); + var moddedJigsaw = deserializer.Deserialize>(sourceText); + + foreach (var piece in moddedJigsaw) + { + //Allow variations of capitalizations with World spellings; can't handle an extra space unfortunately, making it inconsistent with Arif. + //Can just use ID's for worlds, though. + if (worldIndexMap.TryGetValue(piece.World.ToString().Replace(" ", "").ToLower(), out var worldValue)) + { + piece.World = (Kh2.Jigsaw.WorldList)worldValue; + } + + var existingPiece = jigsawList.FirstOrDefault(x => x.Picture == piece.Picture && x.Part == piece.Part); //Identify a puzzle by its Picture+Part. + + if (existingPiece != null) + { + jigsawList[jigsawList.IndexOf(existingPiece)] = piece; + } + else + { + jigsawList.Add(piece); + } + } + + Kh2.Jigsaw.Write(stream.SetPosition(0), jigsawList); + break; + + + case "enmp": var enmpList = Kh2.Battle.Enmp.Read(stream); var moddedEnmp = deserializer.Deserialize>(sourceText); + foreach (var enmp in moddedEnmp) { - var oldEnmp = enmpList.First(x => x.Id == enmp.Id); - enmpList[enmpList.IndexOf(oldEnmp)] = enmp; + var existingEnmp = enmpList.FirstOrDefault(x => x.Id == enmp.Id); + + if (existingEnmp != null) + { + enmpList[enmpList.IndexOf(existingEnmp)] = enmp; + } + else + { + enmpList.Add(enmp); + } } + Kh2.Battle.Enmp.Write(stream.SetPosition(0), enmpList); break; + case "sklt": var skltList = Kh2.SystemData.Sklt.Read(stream); var moddedSklt = deserializer.Deserialize>(sourceText); + foreach (var sklt in moddedSklt) { - var oldSklt = skltList.First(x => x.CharacterId == sklt.CharacterId); - skltList[skltList.IndexOf(oldSklt)] = sklt; + var existingSklt = skltList.FirstOrDefault(x => x.CharacterId == sklt.CharacterId); + + if (existingSklt != null) + { + skltList[skltList.IndexOf(existingSklt)] = sklt; + } + else + { + skltList.Add(sklt); + } } + Kh2.SystemData.Sklt.Write(stream.SetPosition(0), skltList); break; - + case "przt": var prztList = Kh2.Battle.Przt.Read(stream); var moddedPrzt = deserializer.Deserialize>(sourceText); + foreach (var przt in moddedPrzt) { - var oldPrzt = prztList.First(x => x.Id == przt.Id); - prztList[prztList.IndexOf(oldPrzt)] = przt; + var existingPrzt = prztList.FirstOrDefault(x => x.Id == przt.Id); + + if (existingPrzt != null) + { + prztList[prztList.IndexOf(existingPrzt)] = przt; + } + else + { + prztList.Add(przt); + } } Kh2.Battle.Przt.Write(stream.SetPosition(0), prztList); break; case "magc": - var magcList = Kh2.Battle.Magc.Read(stream); - var moddedMagc = deserializer.Deserialize>(sourceText); + var magcList = Kh2.Battle.Magc.Read(stream); + var moddedMagc = deserializer.Deserialize>(sourceText); foreach (var magc in moddedMagc) { - var oldMagc = magcList.First(x => x.Id == magc.Id && x.Level == magc.Level); - magcList[magcList.IndexOf(oldMagc)] = magc; + var existingMagc = magcList.First(x => x.Id == magc.Id && x.Level == magc.Level); + if (existingMagc != null) + { + //existingMagc = MergeHelper.Merge(existingMagc, magc); + magcList[magcList.IndexOf(existingMagc)] = magc; + } + else + { + magcList.Add(magc); + } } Kh2.Battle.Magc.Write(stream.SetPosition(0), magcList); break; + case "btlv": + var btlvList = Kh2.Battle.Btlv.Read(stream); + var moddedBtlv = deserializer.Deserialize>(sourceText); + + foreach (var btlv in moddedBtlv) + { + var existingBtlv = btlvList.FirstOrDefault(x => x.Id == btlv.Id); + + if (existingBtlv != null) + { + btlvList[btlvList.IndexOf(existingBtlv)] = btlv; + } + else + { + btlvList.Add(btlv); + } + } + + Kh2.Battle.Btlv.Write(stream.SetPosition(0), btlvList); + break; + + case "vtbl": + var vtblList = Kh2.Battle.Vtbl.Read(stream); + var moddedVtbl = deserializer.Deserialize>(sourceText); + + foreach (var vtbl in moddedVtbl) + { + var existingVtbl = vtblList.FirstOrDefault(x => x.Id == vtbl.Id && x.CharacterId == vtbl.CharacterId); + //Search for CharacterID & "Action" ID. + if (existingVtbl != null) + { + vtblList[vtblList.IndexOf(existingVtbl)] = vtbl; + + } + else + { + vtblList.Add(vtbl); + } + } + + Kh2.Battle.Vtbl.Write(stream.SetPosition(0), vtblList); + break; + + //Add new limits. ID found -> Continue. + case "limt": + var limtList = Kh2.Battle.Limt.Read(stream); + var moddedLimt = deserializer.Deserialize>(sourceText); + + foreach (var limt in moddedLimt) + { + var existingLimt = limtList.FirstOrDefault(x => x.Id == limt.Id); + + if (existingLimt != null) + { + limtList[limtList.IndexOf(existingLimt)] = limt; + } + else + { + limtList.Add(limt); + } + } + Kh2.Battle.Limt.Write(stream.SetPosition(0), limtList); + break; + + //Listpatch for Arif. Takes a string as an input for the WorldIndex to use, and ints for the roomIndex to edit. + //Strings do not need to worry about capitalization, etc. so long as they have the same characters. + case "arif": + var originalData = Kh2.SystemData.Arif.Read(stream); + var patches = deserializer.Deserialize>>(sourceText); + + foreach (var worldPatch in patches) + { + if (!worldIndexMap.TryGetValue(worldPatch.Key.ToLower().Replace(" ", ""), out var worldIndex)) + { + Log.Warn($"Invalid world index: {worldPatch.Key}"); + } + + if (worldIndex >= 0 && worldIndex < originalData.Count) + { + var worldData = originalData[worldIndex]; + + foreach (var areaPatch in worldPatch.Value) + { + int areaIndex = areaPatch.Key; + var patch = areaPatch.Value; + + // Add new areas. + while (areaIndex >= worldData.Count) + { + worldData.Add(new Kh2.SystemData.Arif + { + Bgms = new Kh2.SystemData.BgmSet[8], + Reserved = new byte[11] + }); + + // Initialize the BgmSet elements within the Bgms array + for (int i = 0; i < 8; i++) + { + worldData[worldData.Count - 1].Bgms[i] = new Kh2.SystemData.BgmSet(); + } + } + // End of adding new areas. + + if (areaIndex >= 0 && areaIndex < worldData.Count) + { + var areaData = worldData[areaIndex]; + //Below: Compares each field to see if it's specified in the YML. + //If yes, update w/ YML value. + //If no, retain original value. + areaData.Flags = patch.Flags != 0 ? patch.Flags : areaData.Flags; + areaData.Reverb = patch.Reverb != 0 ? patch.Reverb : areaData.Reverb; + areaData.SoundEffectBank1 = patch.SoundEffectBank1 != 0 ? patch.SoundEffectBank1 : areaData.SoundEffectBank1; + areaData.SoundEffectBank2 = patch.SoundEffectBank2 != 0 ? patch.SoundEffectBank2 : areaData.SoundEffectBank2; + for (int i = 0; i < patch.Bgms.Length && i < areaData.Bgms.Length; i++) + { + areaData.Bgms[i].BgmField = patch.Bgms[i].BgmField != 0 ? (ushort)patch.Bgms[i].BgmField : areaData.Bgms[i].BgmField; + areaData.Bgms[i].BgmBattle = patch.Bgms[i].BgmBattle != 0 ? (ushort)patch.Bgms[i].BgmBattle : areaData.Bgms[i].BgmBattle; + } + areaData.Voice = patch.Voice != 0 ? patch.Voice : areaData.Voice; + areaData.NavigationMapItem = patch.NavigationMapItem != 0 ? patch.NavigationMapItem : areaData.NavigationMapItem; + areaData.Command = patch.Command != 0 ? patch.Command : areaData.Command; + areaData.Reserved = patch.Reserved != null ? patch.Reserved : areaData.Reserved; + } + } + } + } + + + Kh2.SystemData.Arif.Write(stream.SetPosition(0), originalData); + break; + + case "place": + var originalPlace = Kh2.Places.Read(stream); + var moddedPlace = deserializer.Deserialize>(sourceText); + + foreach (var place in moddedPlace) + { + if (place.Index >= 0 && place.Index < originalPlace.Count) + { + // Update existing entry + originalPlace[place.Index].MessageId = place.MessageId; + originalPlace[place.Index].Padding = place.Padding; + } + else if (place.Index == originalPlace.Count) + { + // Add new entry + originalPlace.Add(new Places { MessageId = place.MessageId, Padding = place.Padding }); + } + else + { + // Expand the list and add the new entry at the specified index + while (originalPlace.Count < place.Index) + { + originalPlace.Add(new Places { MessageId = 0, Padding = 0 }); + } + originalPlace.Add(new Places { MessageId = place.MessageId, Padding = place.Padding }); + } + } + + Kh2.Places.Write(stream.SetPosition(0), originalPlace); + break; + + case "soundinfo": + var originalSoundInfo = Kh2.Soundinfo.Read(stream); + var moddedSoundInfo = deserializer.Deserialize>(sourceText); + + foreach (var info in moddedSoundInfo) + { + while (originalSoundInfo.Count <= info.Index) + { + originalSoundInfo.Add(new Soundinfo + { + Reverb = 0, + Rate = 0, + EnvironmentWAV = 0, + EnvironmentSEB = 0, + EnvironmentNUMBER = 0, + EnvironmentSPOT = 0, + FootstepWAV = 0, + FootstepSORA = 0, + FootstepDONALD = 0, + FootstepGOOFY = 0, + FootstepWORLDFRIEND = 0, + FootstepOTHER = 0 + }); + } + + originalSoundInfo[info.Index].Reverb = info.Reverb; + originalSoundInfo[info.Index].Rate = info.Rate; + originalSoundInfo[info.Index].EnvironmentWAV = info.EnvironmentWAV; + originalSoundInfo[info.Index].EnvironmentSEB = info.EnvironmentSEB; + originalSoundInfo[info.Index].EnvironmentNUMBER = info.EnvironmentNUMBER; + originalSoundInfo[info.Index].EnvironmentSPOT = info.EnvironmentSPOT; + originalSoundInfo[info.Index].FootstepWAV = info.FootstepWAV; + originalSoundInfo[info.Index].FootstepSORA = info.FootstepSORA; + originalSoundInfo[info.Index].FootstepDONALD = info.FootstepDONALD; + originalSoundInfo[info.Index].FootstepGOOFY = info.FootstepGOOFY; + originalSoundInfo[info.Index].FootstepWORLDFRIEND = info.FootstepWORLDFRIEND; + originalSoundInfo[info.Index].FootstepOTHER = info.FootstepOTHER; + } + + // Write the updated list back to the stream + Kh2.Soundinfo.Write(stream.SetPosition(0), originalSoundInfo); + break; + + case "libretto": + var originalLibretto = Kh2.Libretto.Read(stream); + + var patches2 = deserializer.Deserialize>(sourceText); + + foreach (var patch in patches2) + { + var definition = originalLibretto.Definitions.FirstOrDefault(def => def.TalkMessageId == patch.TalkMessageId); + + if (definition != null) + { + definition.Unknown = patch.Unknown; + + var contentList = new List(); + foreach (var contentPatch in patch.Contents) + { + contentList.Add(new Libretto.TalkMessageContent + { + Unknown1 = contentPatch.Unknown1, + TextId = contentPatch.TextId + }); + } + originalLibretto.Contents[originalLibretto.Definitions.IndexOf(definition)] = contentList; + } + else + { + var newDefinition = new Libretto.TalkMessageDefinition + { + TalkMessageId = patch.TalkMessageId, + Unknown = patch.Unknown, + ContentPointer = 0 // Will update this later after adding content entries + }; + + originalLibretto.Definitions.Add(newDefinition); + originalLibretto.Count++; + + var contentList = new List(); + foreach (var contentPatch in patch.Contents) + { + contentList.Add(new Libretto.TalkMessageContent + { + Unknown1 = contentPatch.Unknown1, + TextId = contentPatch.TextId + }); + } + originalLibretto.Contents.Add(contentList); + } + } + + stream.Position = 0; + Kh2.Libretto.Write(stream, originalLibretto); + break; + + + case "memt": + var memt = Kh2.SystemData.Memt.Read(stream); + var memtEntries = memt.Entries.Cast().ToList(); + var memtPatches = deserializer.Deserialize(sourceText); + + if (memtPatches.MemtEntries != null) + { + foreach (var patch in memtPatches.MemtEntries) + { + if (patch.Index < 0) + throw new IndexOutOfRangeException($"Invalid index {patch.Index} for Memt."); + + if (patch.Index >= memtEntries.Count) + { + // Index is beyond current entries, append new entries up to the patch index + while (memtEntries.Count <= patch.Index) + { + memtEntries.Add(new Kh2.SystemData.Memt.EntryFinalMix()); + } + } + + var memtEntry = memtEntries[patch.Index]; + memtEntry.WorldId = patch.WorldId; + memtEntry.CheckStoryFlag = patch.CheckStoryFlag; + memtEntry.CheckStoryFlagNegation = patch.CheckStoryFlagNegation; + memtEntry.Unk06 = patch.Unk06; + memtEntry.Unk08 = patch.Unk08; + memtEntry.Unk0A = patch.Unk0A; + memtEntry.Unk0C = patch.Unk0C; + memtEntry.Unk0E = patch.Unk0E; + memtEntry.Members = patch.Members.ToArray(); + + memtEntries[patch.Index] = memtEntry; + } + + memt.Entries.Clear(); + memt.Entries.AddRange(memtEntries); + + stream.Position = 0; + Kh2.SystemData.Memt.Write(stream, memt); + } + + if (memtPatches.MemberIndices != null) + { + foreach (var patch in memtPatches.MemberIndices) + { + if (patch.Index < 0 || patch.Index >= memt.MemberIndexCollection.Length) + throw new IndexOutOfRangeException($"Invalid MemberIndices index {patch.Index}."); + + var memberIndices = memt.MemberIndexCollection[patch.Index]; + memberIndices.Player = patch.Player; + memberIndices.Friend1 = patch.Friend1; + memberIndices.Friend2 = patch.Friend2; + memberIndices.FriendWorld = patch.FriendWorld; + + memt.MemberIndexCollection[patch.Index] = memberIndices; + } + + stream.Position = 0; + Kh2.SystemData.Memt.Write(stream, memt); + } + break; + + //New: Pref listpatches. More rigid as they're mostly offset-based. Can be updated to eventually support addition though. + case "fmab": + var fmabList = Kh2.SystemData.Fmab.Read(stream); + + var moddedFmab = deserializer.Deserialize(sourceText); + + foreach (var patch in moddedFmab.Entries) + { + if (patch.Key >= 0 && patch.Key < fmabList.Count) + { + fmabList[patch.Key] = patch.Value; + } + } + + stream.SetLength(0); + Kh2.SystemData.Fmab.Write(stream, fmabList); + break; + default: break; } } + } + private static void PatchSynth(Context context, List sources, Stream stream) + { + foreach (var source in sources) + { + string sourceText = File.ReadAllText(context.GetSourceModAssetPath(source.Name)); + switch (source.Type) + { + case "recipe": + var recipeList = Kh2.Mixdata.ReciLP.Read(stream); // Read existing Reci list + var moddedRecipes = deserializer.Deserialize>(sourceText); // Deserialize modded recipes + foreach (var moddedRecipe in moddedRecipes) + { + var existingRecipe = recipeList.FirstOrDefault(x => x.Id == moddedRecipe.Id); + + if (existingRecipe != null) + { + // Update existing recipe in the list + recipeList[recipeList.IndexOf(existingRecipe)] = moddedRecipe; + + // Update other properties as needed + } + else + { + // Add new recipe to the list + recipeList.Add(moddedRecipe); + } + } + + // Write the updated recipe list back to the stream + Kh2.Mixdata.ReciLP.Write(stream, recipeList); // Pass IEnumerable + break; + + case "condition": + var conditionList = Kh2.Mixdata.CondLP.Read(stream); + var moddedConditions = deserializer.Deserialize>(sourceText); + + foreach (var moddedCondition in moddedConditions) + { + var existingCondition = conditionList.FirstOrDefault(x => x.TextId == moddedCondition.TextId); + + if (existingCondition != null) + { + conditionList[conditionList.IndexOf(existingCondition)] = moddedCondition; + + } + else + { + conditionList.Add(moddedCondition); + } + } + + Kh2.Mixdata.CondLP.Write(stream, conditionList); // Pass IEnumerable + break; + + case "level": + var levelList = Kh2.Mixdata.LeveLP.Read(stream); + var moddedLevels = deserializer.Deserialize>(sourceText); + + foreach (var moddedLevel in moddedLevels) + { + var existingLevel = levelList.FirstOrDefault(x => x.Title == moddedLevel.Title); + + if (existingLevel != null) + { + levelList[levelList.IndexOf(existingLevel)] = moddedLevel; + } + else + { + levelList.Add(moddedLevel); + } + } + + Kh2.Mixdata.LeveLP.Write(stream, levelList); + break; + } + } } } } diff --git a/OpenKh.Research.Panacea/KingdomApi.cpp b/OpenKh.Research.Panacea/KingdomApi.cpp index f946d76c0..549f40931 100644 --- a/OpenKh.Research.Panacea/KingdomApi.cpp +++ b/OpenKh.Research.Panacea/KingdomApi.cpp @@ -15,6 +15,7 @@ PFN_DEFINE(Axa_FreeAllPackages); PFN_DEFINE(Axa_CFileMan_GetRemasteredCount); PFN_DEFINE(Axa_CFileMan_GetRemasteredEntry); PFN_DEFINE(Axa_PackageFile_GetRemasteredAsset); +PFN_DEFINE(Axa_PackageFile_OpenFileImpl); PFN_DEFINE(Axa_AxaSoundStream__threadProc); PFN_DEFINE(Axa_OpenFile); PFN_DEFINE(Axa_DebugPrint); @@ -81,6 +82,11 @@ void* Axa::PackageFile::GetRemasteredAsset(Axa::PackageFile* a1, unsigned int* a return pfn_Axa_PackageFile_GetRemasteredAsset(a1, assetSizePtr, assetNum); } +bool Axa::PackageFile::OpenFileImpl(Axa::PackageFile* a1, const char* filePath, const char* altBasePath) +{ + return pfn_Axa_PackageFile_OpenFileImpl(a1, filePath, altBasePath); +} + __int64 Axa::AxaSoundStream::_threadProc(unsigned int* instance) { return pfn_Axa_AxaSoundStream__threadProc(instance); diff --git a/OpenKh.Research.Panacea/KingdomApi.h b/OpenKh.Research.Panacea/KingdomApi.h index d3ad522b9..15b64157f 100644 --- a/OpenKh.Research.Panacea/KingdomApi.h +++ b/OpenKh.Research.Panacea/KingdomApi.h @@ -61,6 +61,7 @@ namespace Axa char CurrentFileName[260]{}; int unk3 = 0; void* GetRemasteredAsset(Axa::PackageFile* a1, unsigned int* assetSizePtr, int assetNum); + static bool OpenFileImpl(Axa::PackageFile* a1, const char* filePath, const char* altBasePath); }; class CFileMan @@ -132,6 +133,7 @@ PFN_DECLARE(void, Axa_FreeAllPackages, ()); PFN_DECLARE(__int64, Axa_CFileMan_GetRemasteredCount, ()); PFN_DECLARE(Axa::RemasteredEntry*, Axa_CFileMan_GetRemasteredEntry, (Axa::CFileMan* a1, int* origOffsetPtr, int assetNum)); PFN_DECLARE(void*, Axa_PackageFile_GetRemasteredAsset, (Axa::PackageFile* a1, unsigned int* assetSizePtr, int assetNum)); +PFN_DECLARE(bool, Axa_PackageFile_OpenFileImpl, (Axa::PackageFile* a1, const char* filePath, const char* altBasePath)); PFN_DECLARE(__int64, Axa_AxaSoundStream__threadProc, (unsigned int* instance)); PFN_DECLARE(int, Axa_OpenFile, (const char* Format, int OFlag)); PFN_DECLARE(void, Axa_DebugPrint, (const char* Format, ...)); diff --git a/OpenKh.Research.Panacea/OpenKH.cpp b/OpenKh.Research.Panacea/OpenKH.cpp index 82253bf27..2de828c36 100644 --- a/OpenKh.Research.Panacea/OpenKH.cpp +++ b/OpenKh.Research.Panacea/OpenKH.cpp @@ -72,11 +72,12 @@ void Hook() Hook(pfn_Axa_AxaResourceMan_SetResourceItem, "\x48\x89\x5C\x24\x00\x55\x56\x57\x48\x81\xEC\x00\x00\x00\x00\x48\x8B\x05\x00\x00\x00\x00\x48\x33\xC4\x48\x89\x84\x24\x00\x00\x00\x00\x49\x8B\xF0\x8B\xFA\x48\x8B", "xxxx?xxxxxx????xxx????xxxxxxx????xxxxxxx"); Hook(pfn_Axa_PackageMan_GetFileInfo, "\x40\x53\x55\x56\x48\x83\xEC\x50\x48\x8B\x05\x00\x00\x00\x00\x48\x33\xC4\x48\x89\x44\x24\x00\x44\x8B\x05", "xxxxxxxxxxx????xxxxxxx?xxx"); Hook(pfn_Axa_CalcHash, "\x40\x53\x56\x57\x48\x81\xEC\x00\x00\x00\x00\x48\x8B\x05\x00\x00\x00\x00\x48\x33\xC4\x48\x89\x84\x24\x00\x00\x00\x00\x8B\xFA\x48\x8B\xD9\x48\x8D\x54\x24", "xxxxxxx????xxx????xxxxxxx????xxxxxxxxx"); - Hook(pfn_Axa_SetReplacePath, "\x4C\x8D\x81\x60\x02\x00\x00\x4C\x8B\xCA\x48\x8D\x15\x00\x00\x00\x00\x48\x8D\x0D", "xxxxxxxxxxxxx????xxx"); + Hook(pfn_Axa_SetReplacePath, "\x4C\x8D\x81\x00\x00\x00\x00\x4C\x8B\xCA\x48\x8D\x15\x00\x00\x00\x00\x48\x8D\x0D", "xxx????xxxxxx????xxx"); Hook(pfn_Axa_FreeAllPackages, "\x48\x89\x6C\x24\x00\x56\x48\x83\xEC\x20\x8B\x05\x00\x00\x00\x00\x33\xED\x8B\xF5", "xxxx?xxxxxxx????xxxx"); Hook(pfn_Axa_CFileMan_GetRemasteredCount, "\x48\x63\x05\x00\x00\x00\x00\x48\x8D\x0D\x00\x00\x00\x00\x48\x8B\x04\xC1\x8B\x80", "xxx????xxx????xxxxxx"); Hook(pfn_Axa_CFileMan_GetRemasteredEntry, "\x48\x63\x05\x00\x00\x00\x00\x4C\x8D\x0D\x00\x00\x00\x00\x4D\x8B\x0C\xC1\x4D\x8B", "xxx????xxx????xxxxxx"); Hook(pfn_Axa_PackageFile_GetRemasteredAsset, "\x40\x53\x56\x48\x83\xEC\x28\x48\x8B\xD9\x48\x8B\xF2\x48\x8B\x89", "xxxxxxxxxxxxxxxx"); + Hook(pfn_Axa_PackageFile_OpenFileImpl, "\x40\x53\x55\x56\x57\x41\x57\x48\x81\xEC\x00\x00\x00\x00\x48\x8B\x05\x00\x00\x00\x00\x48\x33\xC4\x48\x89\x84\x24\x00\x00\x00\x00\x48", "xxxxxxxxxx????xxx????xxxxxxx????x"); Hook(pfn_Axa_AxaSoundStream__threadProc, "\x48\x8B\xC4\x57\x48\x83\xEC\x60\x48\xC7\x40\x00\x00\x00\x00\x00\x48\x89\x58\x10\x48\x89\x68\x18\x48\x89\x70\x20\x48\x8B\xD9\x33\xED\x83\xB9", "xxxxxxxxxxx?????xxxxxxxxxxxxxxxxxxx"); Hook(pfn_Axa_OpenFile, "\x40\x53\x48\x81\xEC\x00\x00\x00\x00\x48\x8B\x05\x00\x00\x00\x00\x48\x33\xC4\x48\x89\x84\x24\x00\x00\x00\x00\x8B\xDA\x48\x8B\xD1\x48\x8D\x4C\x24", "xxxxx????xxx????xxxxxxx????xxxxxxxxx"); Hook(pfn_Axa_DebugPrint, "\x48\x89\x54\x24\x00\x4C\x89\x44\x24\x00\x4C\x89\x4C\x24\x00\xC3", "xxxx?xxxx?xxxx?x"); @@ -101,6 +102,8 @@ void Hook() int QuickLaunch = 0; __int64 (*LaunchGame)(int game); +__int64 (*LaunchGameEpic)(int game); +__int64 (*LaunchGameSteam)(int game); void QuickBootHook() { LaunchGame(QuickLaunch); @@ -114,6 +117,7 @@ std::wstring OpenKH::m_ExtractPath = L""; bool OpenKH::m_ShowConsole = false; bool OpenKH::m_DebugLog = false; bool OpenKH::m_EnableCache = true; +bool OpenKH::m_SoundDebug = false; bool QuickMenu = false; const uint8_t quickmenupat[] = { 0xB1, 0x01, 0x90 }; const std::wstring gamefolders[] = { @@ -155,8 +159,13 @@ void OpenKH::Initialize() Hook(framefunc, "\x40\x57\x48\x83\xEC\x40\x48\xC7\x44\x24\x00\x00\x00\x00\x00\x48\x89\x5C\x24\x00\x48\x8B\xD9\x8B\x41\x34", "xxxxxxxxxx?????xxxx?xxxxxx"); else Hook(framefunc, "\x40\x57\x48\x83\xEC\x40\x48\xC7\x44\x24\x00\x00\x00\x00\x00\x48\x89\x5C\x24\x00\x48\x8B\xF9\x83\x79\x34\x00", "xxxxxxxxxx?????xxxx?xxxxxxx"); - Hook(LaunchGame, "\x40\x53\x48\x81\xEC\x00\x00\x00\x00\x48\x8B\x05\x00\x00\x00\x00\x48\x33\xC4\x48\x89\x84\x24\x00\x00\x00\x00\x8B\xD9", "xxxxx????xxx????xxxxxxx????xx"); + Hook(LaunchGameEpic, "\x40\x53\x48\x81\xEC\x00\x00\x00\x00\x48\x8B\x05\x00\x00\x00\x00\x48\x33\xC4\x48\x89\x84\x24\x00\x00\x00\x00\x8B\xD9", "xxxxx????xxx????xxxxxxx????xx"); + Hook(LaunchGameSteam, "\x48\x89\x5C\x24\x00\x57\x48\x83\xEC\x30\x45\x33\xC9\x48\x8D\x05\x00\x00\x00\x00\x33\xD2", "xxxx?xxxxxxxxxxx????xx"); FindAllFuncs(); + if (LaunchGameSteam) + LaunchGame = LaunchGameSteam; + else + LaunchGame = LaunchGameEpic; intptr_t m_pReplaceFunc = (intptr_t)QuickBootHook; unsigned char Patch[] { @@ -197,6 +206,8 @@ void OpenKH::Initialize() fputs("debug_log=true\n", f); if (m_EnableCache) fputs("enable_cache=true\n", f); + if (m_SoundDebug) + fputs("sound_debug=true\n", f); if (QuickMenu) fputs("quick_menu=true\n", f); fclose(f); @@ -228,7 +239,7 @@ void OpenKH::Initialize() m_DevPath.append(gamefolders[(int)m_GameID]); if (m_ExtractPath.size() > 0) m_ExtractPath.append(gamefolders[(int)m_GameID]); - + Hook(); Panacea::Initialize(); @@ -286,6 +297,8 @@ void OpenKH::ReadSettings(const char* filename) parseBool(value, m_DebugLog); else if (!strncmp(key, "enable_cache", sizeof(buf))) parseBool(value, m_EnableCache); + else if (!strncmp(key, "sound_debug", sizeof(buf))) + parseBool(value, m_SoundDebug); else if (!strncmp(key, "quick_launch", sizeof(buf))) { if (!_stricmp(value, "kh1") || !_stricmp(value, "kh3d")) diff --git a/OpenKh.Research.Panacea/OpenKH.h b/OpenKh.Research.Panacea/OpenKH.h index 0b2f65b12..0f8bcf9ec 100644 --- a/OpenKh.Research.Panacea/OpenKH.h +++ b/OpenKh.Research.Panacea/OpenKH.h @@ -23,6 +23,7 @@ namespace OpenKH extern bool m_ShowConsole; extern bool m_DebugLog; extern bool m_EnableCache; + extern bool m_SoundDebug; void Initialize(); diff --git a/OpenKh.Research.Panacea/Panacea.cpp b/OpenKh.Research.Panacea/Panacea.cpp index 2a58880c4..71bc180d6 100644 --- a/OpenKh.Research.Panacea/Panacea.cpp +++ b/OpenKh.Research.Panacea/Panacea.cpp @@ -222,6 +222,7 @@ struct MyAppVtbl void* onFrameForSaveWait; void* func3; void* func4recom; + void* func5recom; }; MyAppVtbl customAppVtbl; @@ -236,6 +237,7 @@ Hook* Hook_Axa_CFileMan_GetFileSize; Hook* Hook_Axa_CFileMan_GetRemasteredCount; Hook* Hook_Axa_CFileMan_GetRemasteredEntry; Hook* Hook_Axa_PackageFile_GetRemasteredAsset; +Hook* Hook_Axa_PackageFile_OpenFileImpl; Hook* Hook_VAG_STREAM_play; Hook* Hook_VAG_STREAM_fadeOut; Hook* Hook_VAG_STREAM_setVolume; @@ -318,6 +320,7 @@ void Panacea::Initialize() Hook_Axa_CFileMan_GetRemasteredCount = NewHook(pfn_Axa_CFileMan_GetRemasteredCount, Panacea::GetRemasteredCount, "Axa::CFileMan::GetRemasteredCount"); Hook_Axa_CFileMan_GetRemasteredEntry = NewHook(pfn_Axa_CFileMan_GetRemasteredEntry, Panacea::GetRemasteredEntry, "Axa::CFileMan::GetRemasteredEntry"); Hook_Axa_PackageFile_GetRemasteredAsset = NewHook(pfn_Axa_PackageFile_GetRemasteredAsset, Panacea::GetRemasteredAsset, "Axa::PackageFile::GetRemasteredAsset"); + Hook_Axa_PackageFile_OpenFileImpl = NewHook(pfn_Axa_PackageFile_OpenFileImpl, Panacea::OpenFileImpl, "Axa::PackageFile::OpenFileImpl"); Hook_VAG_STREAM_play = NewHook(pfn_VAG_STREAM_play, Panacea::VAG_STREAM::play, "VAG_STREAM::play"); Hook_VAG_STREAM_fadeOut = NewHook(pfn_VAG_STREAM_fadeOut, Panacea::VAG_STREAM::fadeOut, "VAG_STREAM::fadeOut"); Hook_VAG_STREAM_setVolume = NewHook(pfn_VAG_STREAM_setVolume, Panacea::VAG_STREAM::setVolume, "VAG_STREAM::setVolume"); @@ -694,35 +697,44 @@ void ScanRemasteredFolder(const wchar_t* path, void* addr, const wchar_t* remas { std::vector assetoffs{}; const wchar_t* ext = PathFindExtensionW(path); - if (ext[-2] == L'.' && ext[-1] == L'a') - ext -= 2; - if (!_wcsicmp(ext, L".imd")) - GetIMDOffsets(addr, 0, assetoffs); - else if (!_wcsicmp(ext, L".imz")) - GetIMZOffsets(addr, 0, assetoffs); - else if (!_wcsicmp(ext, L".tm2")) - GetTM2Offsets(addr, 0, assetoffs); - else if (!_wcsicmp(ext, L".pax")) - GetPAXOffsets(addr, 0, assetoffs); - else if (!_wcsicmp(ext, L".2dd") - || !_wcsicmp(ext, L".2ld") - || !_wcsicmp(ext, L".a.fm") - || !_wcsicmp(ext, L".a.fr") - || !_wcsicmp(ext, L".a.gr") - || !_wcsicmp(ext, L".a.it") - || !_wcsicmp(ext, L".a.sp") - || !_wcsicmp(ext, L".a.us") - || !_wcsicmp(ext, L".a.uk") - || !_wcsicmp(ext, L".a.jp") - || !_wcsicmp(ext, L".bar") - || !_wcsicmp(ext, L".bin") - || !_wcsicmp(ext, L".mag") - || !_wcsicmp(ext, L".map") - || !_wcsicmp(ext, L".mdlx")) - GetBAROffsets(addr, 0, assetoffs); - else // unknown file type + switch (OpenKH::m_GameID) + { + case OpenKH::GameId::KingdomHearts2: + if (ext[-2] == L'.' && ext[-1] == L'a') + ext -= 2; + if (!_wcsicmp(ext, L".imd")) + GetIMDOffsets(addr, 0, assetoffs); + else if (!_wcsicmp(ext, L".imz")) + GetIMZOffsets(addr, 0, assetoffs); + else if (!_wcsicmp(ext, L".tm2")) + GetTM2Offsets(addr, 0, assetoffs); + else if (!_wcsicmp(ext, L".pax")) + GetPAXOffsets(addr, 0, assetoffs); + else if (!_wcsicmp(ext, L".2dd") + || !_wcsicmp(ext, L".2ld") + || !_wcsicmp(ext, L".a.fm") + || !_wcsicmp(ext, L".a.fr") + || !_wcsicmp(ext, L".a.gr") + || !_wcsicmp(ext, L".a.it") + || !_wcsicmp(ext, L".a.sp") + || !_wcsicmp(ext, L".a.us") + || !_wcsicmp(ext, L".a.uk") + || !_wcsicmp(ext, L".a.jp") + || !_wcsicmp(ext, L".bar") + || !_wcsicmp(ext, L".bin") + || !_wcsicmp(ext, L".mag") + || !_wcsicmp(ext, L".map") + || !_wcsicmp(ext, L".mdlx")) + GetBAROffsets(addr, 0, assetoffs); + else // unknown file type + for (int i = 0; i < entries.size(); ++i) + assetoffs.push_back(entries[i].origOffset); + break; + default: for (int i = 0; i < entries.size(); ++i) assetoffs.push_back(entries[i].origOffset); + break; + } std::vector modfiles; ScanFolder(remasteredFolder, modfiles); if (modfiles.size() >= assetoffs.size()) @@ -798,6 +810,15 @@ void GetRemasteredFiles(Axa::PackageFile* fileinfo, const wchar_t* path, void* a } } +bool Panacea::OpenFileImpl(Axa::PackageFile* a1, const char* filePath, const char* altBasePath) +{ + auto ret = Hook_Axa_PackageFile_OpenFileImpl->Unpatch()(a1, filePath, altBasePath); + Hook_Axa_PackageFile_OpenFileImpl->Patch(); + if (ret) + strcpy_s(a1->CurrentFileName, filePath); + return ret; +} + long __cdecl Panacea::LoadFile(Axa::CFileMan* _this, const char* filename, void* addr, bool useHdAsset) { wchar_t path[MAX_PATH]; @@ -1006,7 +1027,7 @@ void* __cdecl Panacea::LoadFileWithMalloc(Axa::CFileMan* _this, const char* file { if (OpenKH::m_DebugLog) fwprintf(stdout, L"LoadFileWithMalloc(\"%ls\", %d, \"%hs\")\n", path, useHdAsset, filename2); - auto fileinfo = Axa::PackageMan::GetFileInfo(filename, 0); + auto fileinfo = Axa::PackageMan::GetFileInfo(filename, filename2); if (*sizePtr == -1) return nullptr; FILE* file = _wfopen(path, L"rb"); @@ -1167,10 +1188,15 @@ long __cdecl Panacea::GetFileSize(Axa::CFileMan* _this, const char* filename) __int64 __cdecl Panacea::GetRemasteredCount() { + __int64 count; auto found = RemasteredData.find(PackageFiles[LastOpenedPackage]->CurrentFileName); if (found != RemasteredData.end()) - return found->second.size(); - return PackageFiles[LastOpenedPackage]->CurrentFileData.remasteredCount; + count = found->second.size(); + else + count = PackageFiles[LastOpenedPackage]->CurrentFileData.remasteredCount; + if (OpenKH::m_DebugLog) + fwprintf(stdout, L"GetRemasteredCount() = %lld\n", count); + return count; } Axa::RemasteredEntry* Panacea::GetRemasteredEntry(Axa::CFileMan* a1, int* origOffsetPtr, int assetNum) @@ -1181,10 +1207,14 @@ Axa::RemasteredEntry* Panacea::GetRemasteredEntry(Axa::CFileMan* a1, int* origOf if (assetNum >= found->second.size()) return nullptr; *origOffsetPtr = found->second[assetNum].origOffset; + if (OpenKH::m_DebugLog) + fprintf(stdout, "GetRemasteredEntry(%d) = \"%s\"\n", assetNum, found->second[assetNum].name); return &found->second[assetNum]; } auto ret = Hook_Axa_CFileMan_GetRemasteredEntry->Unpatch()(a1, origOffsetPtr, assetNum); Hook_Axa_CFileMan_GetRemasteredEntry->Patch(); + if (OpenKH::m_DebugLog) + fprintf(stdout, "GetRemasteredEntry(%d) = \"%s\"\n", assetNum, ret->name); return ret; } @@ -1207,6 +1237,8 @@ void* Panacea::GetRemasteredAsset(Axa::PackageFile* a1, unsigned int* assetSizeP wchar_t path[MAX_PATH]; if (GetRawFile(path, sizeof(path), a1->CurrentFileName)) { + if (OpenKH::m_DebugLog) + fwprintf(stdout, L"GetRemasteredAsset(%d) = \"%ls\"\n", assetNum, path); FILE* file = _wfopen(path, L"rb"); Axa::PkgEntry pkgent; fread(&pkgent, sizeof(pkgent), 1, file); @@ -1254,6 +1286,8 @@ void* Panacea::GetRemasteredAsset(Axa::PackageFile* a1, unsigned int* assetSizeP } if (GetFileAttributesW(remastered) != INVALID_FILE_ATTRIBUTES) { + if (OpenKH::m_DebugLog) + fwprintf(stdout, L"GetRemasteredAsset(%d) = \"%ls\"\n", assetNum, remastered); FILE* file = _wfopen(remastered, L"rb"); fseek(file, 0, SEEK_END); *assetSizePtr = ftell(file); @@ -1269,6 +1303,8 @@ void* Panacea::GetRemasteredAsset(Axa::PackageFile* a1, unsigned int* assetSizeP fclose(file); return addr; } + if (OpenKH::m_DebugLog) + fwprintf(stdout, L"GetRemasteredAsset(%d)\n", assetNum); auto ret = Hook_Axa_PackageFile_GetRemasteredAsset->Unpatch()(a1, assetSizePtr, assetNum); Hook_Axa_PackageFile_GetRemasteredAsset->Patch(); return ret; @@ -1372,7 +1408,7 @@ void Panacea::VAG_STREAM::exit() void Panacea::DebugPrint(const char* format, ...) { - if (OpenKH::m_DebugLog) + if (OpenKH::m_DebugLog && OpenKH::m_SoundDebug) { va_list args; va_start(args, format); diff --git a/OpenKh.Research.Panacea/Panacea.h b/OpenKh.Research.Panacea/Panacea.h index ddb0bf6e8..2d48541ec 100644 --- a/OpenKh.Research.Panacea/Panacea.h +++ b/OpenKh.Research.Panacea/Panacea.h @@ -17,6 +17,7 @@ namespace Panacea __int64 __cdecl GetRemasteredCount(); Axa::RemasteredEntry* __cdecl GetRemasteredEntry(Axa::CFileMan* a1, int* origOffsetPtr, int assetNum); void* GetRemasteredAsset(Axa::PackageFile* a1, unsigned int* assetSizePtr, int assetNum); + bool OpenFileImpl(Axa::PackageFile* a1, const char* filePath, const char* altBasePath); namespace VAG_STREAM { void play(const char* fileName, int volume, int fadeVolume, int time); diff --git a/OpenKh.Research.Panacea/dllmain.cpp b/OpenKh.Research.Panacea/dllmain.cpp index 597cae52c..e057b5153 100644 --- a/OpenKh.Research.Panacea/dllmain.cpp +++ b/OpenKh.Research.Panacea/dllmain.cpp @@ -14,6 +14,7 @@ #include #include "OpenKH.h" #include +#include typedef BOOL(WINAPI* PFN_MiniDumpWriteDump)(HANDLE hProcess, DWORD ProcessId, HANDLE hFile, MINIDUMP_TYPE DumpType, PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam, PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam, PMINIDUMP_CALLBACK_INFORMATION CallbackParam); PFN_MiniDumpWriteDump MiniDumpWriteDumpPtr; @@ -69,6 +70,33 @@ void HookVersion() assert(hModule != nullptr); } +void LogTerminate() +{ + std::exception_ptr exptr = std::current_exception(); + if (exptr) + { + try + { + std::rethrow_exception(exptr); + } + catch (const std::exception& ex) + { + // NOTE: We can only really rely on the Win32 API in here, the stdlib might be hosed. + HANDLE stdErr = GetStdHandle(STD_ERROR_HANDLE); + if (stdErr != NULL && stdErr != INVALID_HANDLE_VALUE) + { + DWORD written = 0; + const char* message = ex.what(); + WriteConsoleA(stdErr, message, strlen(message), &written, NULL); + } + if (IsDebuggerPresent()) + DebugBreak(); + } + } + std::abort(); +} + +LONG WINAPI HandleException(struct _EXCEPTION_POINTERS* apExceptionInfo); BOOL APIENTRY DllMain( HMODULE hModule, DWORD ul_reason_for_call, @@ -84,6 +112,8 @@ BOOL APIENTRY DllMain( case DLL_PROCESS_ATTACH: HookVersion(); HookDbgHelp(); + SetUnhandledExceptionFilter(HandleException); + std::set_terminate(LogTerminate); OpenKH::Initialize(); break; case DLL_THREAD_ATTACH: @@ -115,3 +145,87 @@ extern "C" __declspec(dllexport) BOOL WINAPI VerQueryValueW(LPCVOID pBlock, LPCW if (!VerQueryValueWPtr) HookVersion(); return VerQueryValueWPtr(pBlock, lpSubBlock, lplpBuffer, puLen); } + +static std::wstring getCurrentDate() { + auto now = std::chrono::system_clock::now(); + std::time_t now_c = std::chrono::system_clock::to_time_t(now); + std::tm* ptm = std::localtime(&now_c); + + // Format date with '_' separators + std::wostringstream ss; + ss << std::setfill(L'0') << std::setw(2) << ptm->tm_mday << L"_" + << std::setfill(L'0') << std::setw(2) << (ptm->tm_mon + 1) << L"_" + << (ptm->tm_year + 1900); + + return ss.str(); +} + +static std::wstring getCurrentTime() { + auto now = std::chrono::system_clock::now(); + std::time_t now_c = std::chrono::system_clock::to_time_t(now); + std::tm* ptm = std::localtime(&now_c); + + // Format time with '_' separators + std::wostringstream ss; + ss << std::setfill(L'0') << std::setw(2) << ptm->tm_hour << L"_" + << std::setfill(L'0') << std::setw(2) << ptm->tm_min << L"_" + << std::setfill(L'0') << std::setw(2) << ptm->tm_sec; + + return ss.str(); +} + +#pragma comment(lib, "dbghelp.lib") +#pragma comment(lib, "Psapi.lib") +LONG WINAPI HandleException(struct _EXCEPTION_POINTERS* apExceptionInfo) +{ + std::wstring dateStr = getCurrentDate(); + std::wstring timeStr = getCurrentTime(); + + std::wstring curCrashDumpFolder = L"CrashDump\\" + dateStr; + + std::filesystem::create_directories(curCrashDumpFolder); + + std::wstring fileName = timeStr + L"_" + L"Dump.dmp"; + + std::wstring crashDumpFile = curCrashDumpFolder + L"\\" + fileName; + + MINIDUMP_EXCEPTION_INFORMATION info = { NULL, NULL, NULL }; + try + { + //generate crash dump + HANDLE hFile = CreateFileW( + crashDumpFile.c_str(), + GENERIC_WRITE | GENERIC_READ, + 0, + NULL, + CREATE_ALWAYS, + 0, + NULL + ); + + HANDLE hProcess = GetCurrentProcess(); + + if (hFile != NULL) + { + info = + { + GetCurrentThreadId(), + apExceptionInfo, + TRUE + }; + + MiniDumpWriteDump(hProcess, GetCurrentProcessId(), + hFile, MiniDumpWithIndirectlyReferencedMemory, + &info, NULL, NULL + ); + + CloseHandle(hFile); + + } + } + catch (const std::exception& e) + { + } + + return EXCEPTION_EXECUTE_HANDLER; +} diff --git a/OpenKh.Tests.Commands/OpenKh.Tests.Commands.csproj b/OpenKh.Tests.Commands/OpenKh.Tests.Commands.csproj index 868998862..112dde421 100644 --- a/OpenKh.Tests.Commands/OpenKh.Tests.Commands.csproj +++ b/OpenKh.Tests.Commands/OpenKh.Tests.Commands.csproj @@ -8,9 +8,9 @@ - - - + + + diff --git a/OpenKh.Tests.Engine/OpenKh.Tests.Engine.csproj b/OpenKh.Tests.Engine/OpenKh.Tests.Engine.csproj index 78052081a..fea8dbac2 100644 --- a/OpenKh.Tests.Engine/OpenKh.Tests.Engine.csproj +++ b/OpenKh.Tests.Engine/OpenKh.Tests.Engine.csproj @@ -7,10 +7,10 @@ - + - - + + diff --git a/OpenKh.Tests.ModsManager/GameDataExtractionServiceTest.cs b/OpenKh.Tests.ModsManager/GameDataExtractionServiceTest.cs new file mode 100644 index 000000000..e38fa47ce --- /dev/null +++ b/OpenKh.Tests.ModsManager/GameDataExtractionServiceTest.cs @@ -0,0 +1,56 @@ +using OpenKh.Tools.ModsManager.Services; +using System.Threading; +using Xunit; + +namespace OpenKh.Tests.ModsManager +{ + public class GameDataExtractionServiceTest + { + private readonly GameDataExtractionService _gameDataExtractionService = new GameDataExtractionService(); + + //[Fact] + public async Task ExtractKh2Ps2EditionAsyncTest() + { + await _gameDataExtractionService.ExtractKh2Ps2EditionAsync( + isoLocation: @"H:\CCD\KH2fm.ISO", + gameDataLocation: @"H:\Tmp\ModsManagerExtractionRoot", + onProgress: (progress) => Console.WriteLine($"{progress:P}") + ); + } + + //[Fact] + public async Task ExtractKhPcEditionAsyncTest() + { + var pcReleaseLocation = @"H:\Program Files\Epic Games/KH_1.5_2.5"; + var pcReleaseLocationKH3D = @"H:\Program Files\Epic Games/KH_2.8"; + var pcReleaseLanguage = "jp"; + var langFolder = (ConfigurationService.PCVersion == "Steam" && pcReleaseLanguage == "en") ? "dt" : pcReleaseLanguage; + + await _gameDataExtractionService.ExtractKhPcEditionAsync( + gameDataLocation: @"H:\Tmp\ModsManagerPcExtractionRoot", + onProgress: (progress) => Console.WriteLine($"{progress:P}"), + getKhFilePath: fileName + => Path.Combine( + pcReleaseLocation, + "Image", + langFolder, + fileName + ), + getKh3dFilePath: fileName + => Path.Combine( + pcReleaseLocationKH3D, + "Image", + langFolder, + fileName + ), + extractkh1: true, + extractkh2: true, + extractbbs: true, + extractrecom: true, + extractkh3d: true, + ifRetry: ex => Task.FromResult(false), + cancellationToken: CancellationToken.None + ); + } + } +} diff --git a/OpenKh.Tests.ModsManager/OpenKh.Tests.ModsManager.csproj b/OpenKh.Tests.ModsManager/OpenKh.Tests.ModsManager.csproj new file mode 100644 index 000000000..7520c77fc --- /dev/null +++ b/OpenKh.Tests.ModsManager/OpenKh.Tests.ModsManager.csproj @@ -0,0 +1,26 @@ + + + + net6.0-windows + enable + enable + + false + false + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + diff --git a/OpenKh.Tests/OpenKh.Tests.csproj b/OpenKh.Tests/OpenKh.Tests.csproj index 68edff098..cb8df25c1 100644 --- a/OpenKh.Tests/OpenKh.Tests.csproj +++ b/OpenKh.Tests/OpenKh.Tests.csproj @@ -12,10 +12,10 @@ - + - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/OpenKh.Tests/Patcher/PatcherTests.cs b/OpenKh.Tests/Patcher/PatcherTests.cs index 1cd014fae..918000fe3 100644 --- a/OpenKh.Tests/Patcher/PatcherTests.cs +++ b/OpenKh.Tests/Patcher/PatcherTests.cs @@ -1,3 +1,5 @@ +using Antlr4.Runtime; +using OpenKh.Bbs; using OpenKh.Command.Bdxio.Utils; using OpenKh.Common; using OpenKh.Imaging; @@ -63,7 +65,7 @@ public void Kh2CopyBinariesTest() CreateFile(ModInputDir, patch.Assets[0].Name).Dispose(); - patcher.Patch(AssetsInputDir, ModOutputDir, patch, ModInputDir); + patcher.Patch(AssetsInputDir, ModOutputDir, patch, ModInputDir, Tests: true); AssertFileExists(ModOutputDir, patch.Assets[0].Name); } @@ -108,7 +110,7 @@ public void Kh2CreateBinArcIfSourceDoesntExistsTest() x.WriteByte(3); }); - patcher.Patch(AssetsInputDir, ModOutputDir, patch, ModInputDir); + patcher.Patch(AssetsInputDir, ModOutputDir, patch, ModInputDir, Tests: true); AssertFileExists(ModOutputDir, patch.Assets[0].Name); AssertBarFile("abcd", entry => @@ -175,7 +177,7 @@ public void Kh2MergeWithOriginalBinArcTest() }); }); - patcher.Patch(AssetsInputDir, ModOutputDir, patch, ModInputDir); + patcher.Patch(AssetsInputDir, ModOutputDir, patch, ModInputDir, Tests: true); AssertFileExists(ModOutputDir, patch.Assets[0].Name); AssertBarFile("abcd", entry => @@ -233,7 +235,7 @@ public void Kh2CreateImgdTest() File.Copy("Imaging/res/png/32.png", Path.Combine(ModInputDir, "sample.png")); - patcher.Patch(AssetsInputDir, ModOutputDir, patch, ModInputDir); + patcher.Patch(AssetsInputDir, ModOutputDir, patch, ModInputDir, Tests: true); AssertFileExists(ModOutputDir, patch.Assets[0].Name); AssertBarFile("test", entry => @@ -279,7 +281,7 @@ public void Kh2MergeImzTest() }); CreateFile(ModInputDir, "test.imd").Using(patchImd.Write); - patcher.Patch(AssetsInputDir, ModOutputDir, patch, ModInputDir); + patcher.Patch(AssetsInputDir, ModOutputDir, patch, ModInputDir, Tests: true); AssertFileExists(ModOutputDir, "out.imz"); File.OpenRead(Path.Combine(ModOutputDir, "out.imz")).Using(x => @@ -349,7 +351,7 @@ public void Kh2MergeImzInsideBarTest() }); CreateFile(ModInputDir, "test.imd").Using(patchImd.Write); - patcher.Patch(AssetsInputDir, ModOutputDir, patch, ModInputDir); + patcher.Patch(AssetsInputDir, ModOutputDir, patch, ModInputDir, Tests: true); AssertFileExists(ModOutputDir, "out.bar"); AssertBarFile("test", x => @@ -434,7 +436,7 @@ public void MergeKh2MsgTest() writer.Flush(); }); - patcher.Patch(AssetsInputDir, ModOutputDir, patch, ModInputDir); + patcher.Patch(AssetsInputDir, ModOutputDir, patch, ModInputDir, Tests: true); AssertFileExists(ModOutputDir, "msg/jp/sys.msg"); File.OpenRead(Path.Combine(ModOutputDir, "msg/jp/sys.msg")).Using(stream => @@ -501,7 +503,7 @@ public void MergeKh2AreaDataScriptTest() writer.Flush(); }); - patcher.Patch(AssetsInputDir, ModOutputDir, patch, ModInputDir); + patcher.Patch(AssetsInputDir, ModOutputDir, patch, ModInputDir, Tests: true); AssertFileExists(ModOutputDir, "map.script"); File.OpenRead(Path.Combine(ModOutputDir, "map.script")).Using(stream => @@ -556,7 +558,7 @@ public void PatchKh2BdscriptTest() writer.WriteLine(" ret"); writer.Flush(); }); - patcher.Patch(AssetsInputDir, ModOutputDir, patch, ModInputDir); + patcher.Patch(AssetsInputDir, ModOutputDir, patch, ModInputDir, Tests: true); AssertFileExists(ModOutputDir, "aaa"); @@ -608,7 +610,7 @@ public void PatchKh2SpawnPointTest() writer.WriteLine(" Flag: 1"); writer.Flush(); }); - patcher.Patch(AssetsInputDir, ModOutputDir, patch, ModInputDir); + patcher.Patch(AssetsInputDir, ModOutputDir, patch, ModInputDir, Tests: true); AssertFileExists(ModOutputDir, "map.script"); var content = File.ReadAllText(Path.Combine(ModOutputDir, "map.script")); @@ -616,15 +618,15 @@ public void PatchKh2SpawnPointTest() Assert.Equal(2, scripts[0].Type); Assert.Equal(1, scripts[0].Flag); - + } - + public void ListPatchTrsrTest() { var patcher = new PatcherProcessor(); var serializer = new Serializer(); - var patch = new Metadata() { + var patch = new Metadata() { Assets = new List() { new AssetFile() @@ -682,7 +684,7 @@ public void ListPatchTrsrTest() writer.Flush(); }); - patcher.Patch(AssetsInputDir, ModOutputDir, patch, ModInputDir); + patcher.Patch(AssetsInputDir, ModOutputDir, patch, ModInputDir, Tests: true); AssertFileExists(ModOutputDir, "03system.bar"); @@ -694,13 +696,13 @@ public void ListPatchTrsrTest() }); } - + [Fact] public void ListPatchCmdTest() { var patcher = new PatcherProcessor(); var serializer = new Serializer(); - var patch = new Metadata() { + var patch = new Metadata() { Assets = new List() { new AssetFile() @@ -762,7 +764,7 @@ public void ListPatchCmdTest() writer.Flush(); }); - patcher.Patch(AssetsInputDir, ModOutputDir, patch, ModInputDir); + patcher.Patch(AssetsInputDir, ModOutputDir, patch, ModInputDir, Tests: true); AssertFileExists(ModOutputDir, "03system.bar"); @@ -777,7 +779,7 @@ public void ListPatchCmdTest() }); } - + [Fact] public void ListPatchItemTest() { @@ -834,7 +836,7 @@ public void ListPatchItemTest() Ability = 15 } } - + } }; using var itemStream = new MemoryStream(); @@ -861,7 +863,7 @@ public void ListPatchItemTest() writer.Flush(); }); - patcher.Patch(AssetsInputDir, ModOutputDir, patch, ModInputDir); + patcher.Patch(AssetsInputDir, ModOutputDir, patch, ModInputDir, Tests: true); AssertFileExists(ModOutputDir, "03system.bar"); @@ -942,7 +944,7 @@ public void ListPatchSkltTest() writer.Flush(); }); - patcher.Patch(AssetsInputDir, ModOutputDir, patch, ModInputDir); + patcher.Patch(AssetsInputDir, ModOutputDir, patch, ModInputDir, Tests: true); AssertFileExists(ModOutputDir, "03system.bar"); @@ -956,6 +958,324 @@ public void ListPatchSkltTest() }); } + [Fact] //Fixed, needed to initialize the BGMSet & Reserved bytes. + public void ListPatchArifTest() + { + var patcher = new PatcherProcessor(); + var serializer = new Serializer(); + var patch = new Metadata() + { + Assets = new List() + { + new AssetFile() + { + Name = "03system.bin", + Method = "binarc", + Source = new List() + { + new AssetFile() + { + Name = "arif", + Method = "listpatch", + Type = "List", + Source = new List() + { + new AssetFile() + { + Name = "ArifList.yml", + Type = "arif" + } + } + } + } + } + } + }; + + using (var stream = File.Create(Path.Combine(AssetsInputDir, "03system.bin"))) + { + var arifEntry = new Kh2.SystemData.Arif + { + Flags = Kh2.SystemData.Arif.ArifFlags.IsKnownArea, + Reverb = 0, + SoundEffectBank1 = 13, + SoundEffectBank2 = 0, + Bgms = Enumerable.Range(0, 8).Select(_ => new Kh2.SystemData.BgmSet { BgmField = 0, BgmBattle = 0 }).ToArray(), + Voice = 0, + NavigationMapItem = 0, + Command = 0, + Reserved = new byte[11] + }; + + using (var arifStream = new MemoryStream()) + { + Kh2.SystemData.Arif.Write(arifStream, new List> { new List { arifEntry } }); + Bar.Write(stream, new Bar + { + new Bar.Entry() + { + Name = "arif", + Type = Bar.EntryType.List, + Stream = arifStream + } + }); + } + } + + File.Create(Path.Combine(ModInputDir, "ArifList.yml")).Using(stream => + { + var writer = new StreamWriter(stream); + writer.WriteLine("EndOfSea:"); + writer.WriteLine(" 1:"); + writer.WriteLine(" SoundEffectBank1: 13"); + writer.WriteLine(" Voice: 0"); + writer.Flush(); + }); + + patcher.Patch(AssetsInputDir, ModOutputDir, patch, ModInputDir, Tests: true); + + AssertFileExists(ModOutputDir, "03system.bin"); + + File.OpenRead(Path.Combine(ModOutputDir, "03system.bin")).Using(stream => + { + var binarc = Bar.Read(stream); + var arifList = Kh2.SystemData.Arif.Read(binarc[0].Stream); + var arifEntry = arifList[0][0]; + Assert.Equal(13, arifEntry.SoundEffectBank1); + Assert.Equal(0, arifEntry.Voice); + }); + } + + [Fact] + public void ListPatchMemtTest() + { + var patcher = new PatcherProcessor(); + var serializer = new Serializer(); + var patch = new Metadata() + { + Assets = new List() + { + new AssetFile() + { + Name = "03system.bar", + Method = "binarc", + Source = new List() + { + new AssetFile() + { + Name = "memt", + Method = "listpatch", + Type = "List", + Source = new List() + { + new AssetFile() + { + Name = "MemtList.yml", + Type = "memt" + } + } + } + } + } + } + }; + + // Create the 03system.bar file with initial data + File.Create(Path.Combine(AssetsInputDir, "03system.bar")).Using(stream => + { + var memtEntries = new List() + { + new Kh2.SystemData.Memt.EntryFinalMix + { + WorldId = 1, + CheckStoryFlag = 1, + CheckStoryFlagNegation = 0, + Unk06 = 0, + Unk08 = 0, + Unk0A = 0, + Unk0C = 0, + Unk0E = 0, + Members = new short[18] + } + }; + + var memberIndices = new Kh2.SystemData.Memt.MemberIndices[7]; // Ensure there are 7 MemberIndices + for (int i = 0; i < memberIndices.Length; i++) + { + memberIndices[i] = new Kh2.SystemData.Memt.MemberIndices + { + Player = 0, + Friend1 = 0, + Friend2 = 0, + FriendWorld = 0 + }; + } + + using var memtStream = new MemoryStream(); + var memt = new Kh2.SystemData.Memt(); + memt.Entries.AddRange(memtEntries.Cast()); + + Kh2.SystemData.Memt.Write(memtStream, memt); + memtStream.Seek(0, SeekOrigin.Begin); + + Bar.Write(stream, new Bar() + { + new Bar.Entry() + { + Name = "memt", + Type = Bar.EntryType.List, + Stream = memtStream + } + }); + }); + + // Create the MemtList.yml patch file + File.Create(Path.Combine(ModInputDir, "MemtList.yml")).Using(stream => + { + var writer = new StreamWriter(stream); + writer.WriteLine("MemtEntries:"); + writer.WriteLine(" - Index: 0"); + writer.WriteLine(" WorldId: 2"); + writer.WriteLine(" CheckStoryFlag: 3"); + writer.WriteLine(" CheckStoryFlagNegation: 4"); + writer.WriteLine(" Unk06: 5"); + writer.WriteLine(" Unk08: 6"); + writer.WriteLine(" Unk0A: 7"); + writer.WriteLine(" Unk0C: 8"); + writer.WriteLine(" Unk0E: 9"); + writer.WriteLine(" Members: [10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27]"); + writer.WriteLine("MemberIndices:"); + writer.WriteLine(" - Index: 0"); + writer.WriteLine(" Player: 0"); + writer.WriteLine(" Friend1: 0"); + writer.WriteLine(" Friend2: 0"); + writer.WriteLine(" FriendWorld: 0"); + writer.Flush(); + }); + + // Apply the patch + patcher.Patch(AssetsInputDir, ModOutputDir, patch, ModInputDir, Tests: true); + + // Verify the patched data + AssertFileExists(ModOutputDir, "03system.bar"); + + File.OpenRead(Path.Combine(ModOutputDir, "03system.bar")).Using(stream => + { + var binarc = Bar.Read(stream); + var memtStream = binarc[0].Stream; + memtStream.Seek(0, SeekOrigin.Begin); + + var memt = Kh2.SystemData.Memt.Read(memtStream); + + var memtEntry = memt.Entries.Cast().First(); + Assert.Equal((short)2, memtEntry.WorldId); + Assert.Equal((short)3, memtEntry.CheckStoryFlag); + Assert.Equal((short)4, memtEntry.CheckStoryFlagNegation); + Assert.Equal((short)5, memtEntry.Unk06); + Assert.Equal((short)6, memtEntry.Unk08); + Assert.Equal((short)7, memtEntry.Unk0A); + Assert.Equal((short)8, memtEntry.Unk0C); + Assert.Equal((short)9, memtEntry.Unk0E); + Assert.Equal(new short[] { 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27 }, memtEntry.Members); + + // Check MemberIndices deserialization + var memberIndices = memt.MemberIndexCollection.First(); + Assert.Equal((byte)0, memberIndices.Player); + Assert.Equal((byte)0, memberIndices.Friend1); + Assert.Equal((byte)0, memberIndices.Friend2); + Assert.Equal((byte)0, memberIndices.FriendWorld); + }); + } + + + + [Fact] + public void ListPatchFmabTest() + { + var patcher = new PatcherProcessor(); + var serializer = new Serializer(); + var patch = new Metadata() + { + Assets = new List() + { + new AssetFile() + { + Name = "03system.bar", + Method = "binarc", + Source = new List() + { + new AssetFile() + { + Name = "pref", + Method = "binarc", + Type = "Binary", + Source = new List() + { + new AssetFile() + { + Name = "fmab", + Method = "listpatch", + Type = "list", + Source = new List() + { + new AssetFile() + { + Name = "FmabList.yml", + Type = "fmab" + } + } + } + } + } + } + } + } + }; + + File.Create(Path.Combine(AssetsInputDir, "03system.bar")).Using(stream => + { + var FmabEntry = new List() + { + new Kh2.SystemData.Fmab + { + HighJumpHeight = 178, + AirDodgeHeight = 86 + } + }; + + using var fmabStream = new MemoryStream(); + Kh2.SystemData.Fmab.Write(fmabStream, FmabEntry); + Bar.Write(stream, new Bar() { + new Bar.Entry() + { + Name = "fmab", + Type = Bar.EntryType.List, + Stream = fmabStream + } + }); + }); + + File.Create(Path.Combine(ModInputDir, "FmabList.yml")).Using(stream => + { + var writer = new StreamWriter(stream); + writer.WriteLine("- HighJumpHeight: 178"); + writer.WriteLine(" AirDodgeHeight: 86"); + writer.Flush(); + }); + + patcher.Patch(AssetsInputDir, ModOutputDir, patch, ModInputDir, Tests: true); + + AssertFileExists(ModOutputDir, "03system.bar"); + + File.OpenRead(Path.Combine(ModOutputDir, "03system.bar")).Using(stream => + { + var binarc = Bar.Read(stream); + var fmabStream = Kh2.SystemData.Fmab.Read(binarc[0].Stream); + Assert.Equal(178, fmabStream[0].HighJumpHeight); + Assert.Equal(86, fmabStream[0].AirDodgeHeight); + }); + } + [Fact] public void ListPatchFmlvTest() { @@ -1048,7 +1368,7 @@ public void ListPatchFmlvTest() writer.Flush(); }); - patcher.Patch(AssetsInputDir, ModOutputDir, patch, ModInputDir); + patcher.Patch(AssetsInputDir, ModOutputDir, patch, ModInputDir, Tests: true); AssertFileExists(ModOutputDir, "00battle.bar"); @@ -1143,7 +1463,7 @@ public void ListPatchBonsTest() writer.Flush(); }); - patcher.Patch(AssetsInputDir, ModOutputDir, patch, ModInputDir); + patcher.Patch(AssetsInputDir, ModOutputDir, patch, ModInputDir, Tests: true); AssertFileExists(ModOutputDir, "00battle.bar"); @@ -1366,7 +1686,7 @@ public void ListPatchLvupTest() writer.Flush(); }); - patcher.Patch(AssetsInputDir, ModOutputDir, patch, ModInputDir); + patcher.Patch(AssetsInputDir, ModOutputDir, patch, ModInputDir, Tests: true); AssertFileExists(ModOutputDir, "00battle.bin"); @@ -1379,6 +1699,7 @@ public void ListPatchLvupTest() } + [Fact] void ListPatchAtkpTest() { var patcher = new PatcherProcessor(); @@ -1505,7 +1826,7 @@ void ListPatchAtkpTest() writer.Flush(); }); - patcher.Patch(AssetsInputDir, ModOutputDir, patch, ModInputDir); + patcher.Patch(AssetsInputDir, ModOutputDir, patch, ModInputDir, Tests: true); AssertFileExists(ModOutputDir, "00battle.bar"); @@ -1550,7 +1871,7 @@ void ListPatchAtkpTest() } [Fact] - public void ListPatchObjEntryTest() + void ListPatchLvpmTest() { var patcher = new PatcherProcessor(); var serializer = new Serializer(); @@ -1560,60 +1881,98 @@ public void ListPatchObjEntryTest() { new AssetFile() { - Name = "00objentry.bin", - Method = "listpatch", - Type = "List", + Name = "00battle.bar", + Method = "binarc", Source = new List() { new AssetFile() { - Name = "ObjList.yml", - Type = "objentry", + Name = "lvpm", + Method = "listpatch", + Type = "List", + Source = new List() + { + new AssetFile() + { + Name = "LvpmList.yml", + Type = "lvpm" + } + } } } } } }; - File.Create(Path.Combine(AssetsInputDir, "00objentry.bin")).Using(stream => + File.Create(Path.Combine(AssetsInputDir, "00battle.bar")).Using(stream => { - var objEntry = new List() + var lvpmEntry = new List() { - new Kh2.Objentry + new Kh2.Battle.Lvpm { - ObjectId = 1, - ModelName = "M_EX060", - AnimationName = "M_EX060.mset" + HpMultiplier = 89, + Strength = 77, + Defense = 88, + MaxStrength = 40, + MinStrength = 32, + Experience = 777 } - }; - Kh2.Objentry.Write(stream, objEntry); + }; + + using var lvpmStream = new MemoryStream(); + Kh2.Battle.Lvpm.Write(lvpmStream, lvpmEntry); + Bar.Write(stream, new Bar() { + new Bar.Entry() + { + Name = "lvpm", + Type = Bar.EntryType.List, + Stream = lvpmStream + } + }); }); - File.Create(Path.Combine(ModInputDir, "ObjList.yml")).Using(stream => + File.Create(Path.Combine(ModInputDir, "LvpmList.yml")).Using(stream => { var writer = new StreamWriter(stream); - writer.WriteLine("1:"); - writer.WriteLine(" ObjectId: 1"); - writer.WriteLine(" ModelName: M_EX100"); - writer.WriteLine(" AnimationName: M_EX100.mset"); + + var serializer = new Serializer(); + var moddedLvpm = new List{ + new Kh2.Battle.LvpmHelper + { + Level = 0, + HpMultiplier = 89, + Strength = 77, + Defense = 88, + MaxStrength = 40, + MinStrength = 32, + Experience = 777 + } + }; + writer.Write(serializer.Serialize(moddedLvpm)); writer.Flush(); }); - patcher.Patch(AssetsInputDir, ModOutputDir, patch, ModInputDir); + patcher.Patch(AssetsInputDir, ModOutputDir, patch, ModInputDir, Tests: true); - AssertFileExists(ModOutputDir, "00objentry.bin"); + AssertFileExists(ModOutputDir, "00battle.bar"); - File.OpenRead(Path.Combine(ModOutputDir, "00objentry.bin")).Using(stream => + File.OpenRead(Path.Combine(ModOutputDir, "00battle.bar")).Using(stream => { - var objStream = Kh2.Objentry.Read(stream); - Assert.Equal("M_EX100", objStream[0].ModelName); - Assert.Equal("M_EX100.mset", objStream[0].AnimationName); + var binarc = Bar.Read(stream); + var lvpmStream = Kh2.Battle.Lvpm.Read(binarc[0].Stream, 1); + var helperList = Kh2.Battle.LvpmHelper.ConvertLvpmListToHelper(lvpmStream); + Assert.Equal(0, helperList[0].Level); + Assert.Equal(89, helperList[0].HpMultiplier); + Assert.Equal(77, helperList[0].Strength); + Assert.Equal(88, helperList[0].Defense); + Assert.Equal(40, helperList[0].MaxStrength); + Assert.Equal(32, helperList[0].MinStrength); + Assert.Equal(777, helperList[0].Experience); }); - } [Fact] - public void ListPatchPlrpTest() + public void ListPatchPrztTest() { var patcher = new PatcherProcessor(); var serializer = new Serializer(); @@ -1629,15 +1988,15 @@ public void ListPatchPlrpTest() { new AssetFile() { - Name = "plrp", + Name = "przt", Method = "listpatch", Type = "List", Source = new List() { new AssetFile() { - Name = "PlrpList.yml", - Type = "plrp" + Name = "PrztList.yml", + Type = "przt" } } } @@ -1648,49 +2007,200 @@ public void ListPatchPlrpTest() File.Create(Path.Combine(AssetsInputDir, "00battle.bar")).Using(stream => { - var plrpEntry = new List() + var prztEntry = new List() { - new Kh2.Battle.Plrp + new Kh2.Battle.Przt { - Id = 7, - Character = 1, - Ap = 2, - Items = new List(32), - Padding = new byte[52] + Id = 1, + SmallHpOrbs = 0, + BigHpOrbs = 1 } }; - using var plrpStream = new MemoryStream(); - Kh2.Battle.Plrp.Write(plrpStream, plrpEntry); + using var prztStream = new MemoryStream(); + Kh2.Battle.Przt.Write(prztStream, prztEntry); Bar.Write(stream, new Bar() { new Bar.Entry() { - Name = "plrp", + Name = "przt", Type = Bar.EntryType.List, - Stream = plrpStream + Stream = prztStream } }); }); - File.Create(Path.Combine(ModInputDir, "PlrpList.yml")).Using(stream => + File.Create(Path.Combine(ModInputDir, "PrztList.yml")).Using(stream => { var writer = new StreamWriter(stream); var serializer = new Serializer(); - var moddedPlrp = new List{ - new Kh2.Battle.Plrp + var moddedPrzt = new List{ + new Kh2.Battle.Przt { - Id = 7, - Character = 1, - Ap = 200, - Items = new List(32), + Id = 1, + SmallHpOrbs = 0, + BigHpOrbs = 1 + } + }; + writer.Write(serializer.Serialize(moddedPrzt)); + writer.Flush(); + }); + + patcher.Patch(AssetsInputDir, ModOutputDir, patch, ModInputDir, Tests: true); + + AssertFileExists(ModOutputDir, "00battle.bar"); + + File.OpenRead(Path.Combine(ModOutputDir, "00battle.bar")).Using(stream => + { + var binarc = Bar.Read(stream); + var przt = Kh2.Battle.Przt.Read(binarc[0].Stream); + + Assert.Equal(1, przt[0].BigHpOrbs); + }); + } + + + [Fact] + public void ListPatchObjEntryTest() + { + var patcher = new PatcherProcessor(); + var serializer = new Serializer(); + var patch = new Metadata() + { + Assets = new List() + { + new AssetFile() + { + Name = "00objentry.bin", + Method = "listpatch", + Type = "List", + Source = new List() + { + new AssetFile() + { + Name = "ObjList.yml", + Type = "objentry", + } + } + } + } + }; + + File.Create(Path.Combine(AssetsInputDir, "00objentry.bin")).Using(stream => + { + var objEntry = new List() + { + new Kh2.Objentry + { + ObjectId = 1, + ModelName = "M_EX060", + AnimationName = "M_EX060.mset" + } + }; + Kh2.Objentry.Write(stream, objEntry); + }); + + File.Create(Path.Combine(ModInputDir, "ObjList.yml")).Using(stream => + { + var writer = new StreamWriter(stream); + writer.WriteLine("1:"); + writer.WriteLine(" ObjectId: 1"); + writer.WriteLine(" ModelName: M_EX100"); + writer.WriteLine(" AnimationName: M_EX100.mset"); + writer.Flush(); + }); + + patcher.Patch(AssetsInputDir, ModOutputDir, patch, ModInputDir, Tests: true); + + AssertFileExists(ModOutputDir, "00objentry.bin"); + + File.OpenRead(Path.Combine(ModOutputDir, "00objentry.bin")).Using(stream => + { + var objStream = Kh2.Objentry.Read(stream); + Assert.Equal("M_EX100", objStream[0].ModelName); + Assert.Equal("M_EX100.mset", objStream[0].AnimationName); + }); + + } + + [Fact] + public void ListPatchPlrpTest() + { + var patcher = new PatcherProcessor(); + var serializer = new Serializer(); + var patch = new Metadata() + { + Assets = new List() + { + new AssetFile() + { + Name = "00battle.bar", + Method = "binarc", + Source = new List() + { + new AssetFile() + { + Name = "plrp", + Method = "listpatch", + Type = "List", + Source = new List() + { + new AssetFile() + { + Name = "PlrpList.yml", + Type = "plrp" + } + } + } + } + } + } + }; + + File.Create(Path.Combine(AssetsInputDir, "00battle.bar")).Using(stream => + { + var plrpEntry = new List() + { + new Kh2.Battle.Plrp + { + Id = 7, + Character = 1, + Ap = 2, + Items = new List(32), Padding = new byte[52] - } + } + }; + + using var plrpStream = new MemoryStream(); + Kh2.Battle.Plrp.Write(plrpStream, plrpEntry); + Bar.Write(stream, new Bar() { + new Bar.Entry() + { + Name = "plrp", + Type = Bar.EntryType.List, + Stream = plrpStream + } + }); + }); + + File.Create(Path.Combine(ModInputDir, "PlrpList.yml")).Using(stream => + { + var writer = new StreamWriter(stream); + var serializer = new Serializer(); + var moddedPlrp = new List{ + new Kh2.Battle.Plrp + { + Id = 7, + Character = 1, + Ap = 200, + Items = new List(32), + Padding = new byte[52] + } }; writer.Write(serializer.Serialize(moddedPlrp)); writer.Flush(); }); - patcher.Patch(AssetsInputDir, ModOutputDir, patch, ModInputDir); + patcher.Patch(AssetsInputDir, ModOutputDir, patch, ModInputDir, Tests: true); AssertFileExists(ModOutputDir, "00battle.bar"); @@ -1801,7 +2311,7 @@ public void ListPatchEnmpTest() writer.Flush(); }); - patcher.Patch(AssetsInputDir, ModOutputDir, patch, ModInputDir); + patcher.Patch(AssetsInputDir, ModOutputDir, patch, ModInputDir, Tests: true); AssertFileExists(ModOutputDir, "00battle.bar"); @@ -1813,7 +2323,7 @@ public void ListPatchEnmpTest() Assert.Equal(1, enmp[0].Level); }); } - + [Fact] public void ListPatchMagcTest() { @@ -1890,7 +2400,7 @@ public void ListPatchMagcTest() writer.Flush(); }); - patcher.Patch(AssetsInputDir, ModOutputDir, patch, ModInputDir); + patcher.Patch(AssetsInputDir, ModOutputDir, patch, ModInputDir, Tests: true); AssertFileExists(ModOutputDir, "00battle.bar"); @@ -1904,7 +2414,124 @@ public void ListPatchMagcTest() } [Fact] - public void ListPatchPrztTest() + public void ListPatchLimtTest() + { + var patcher = new PatcherProcessor(); + var serializer = new Serializer(); + var patch = new Metadata() + { + Assets = new List() + { + new AssetFile() + { + Name = "00battle.bar", + Method = "binarc", + Source = new List() + { + new AssetFile() + { + Name = "Limt", + Method = "listpatch", + Type = "List", + Source = new List() + { + new AssetFile() + { + Name = "LimtList.yml", + Type = "limt" + } + } + } + } + } + } + }; + + // Create the initial 00battle.bar file with Limt entry + File.Create(Path.Combine(AssetsInputDir, "00battle.bar")).Using(stream => + { + var limtEntry = new List() + { + new Kh2.Battle.Limt + { + Id = 0, + Character = Kh2.Battle.Limt.Characters.Auron, + Summon = Kh2.Battle.Limt.Characters.Sora, + Group = 0, + FileName = "auron.bar", + SpawnId = 0, + Command = 82, + Limit = 204, + World = 0, + Padding = new byte[18] + } + }; + + using var limtStream = new MemoryStream(); + Kh2.Battle.Limt.Write(limtStream, limtEntry); + limtStream.Position = 0; // Ensure stream position is reset before writing to Bar + Bar.Write(stream, new Bar() { + new Bar.Entry() + { + Name = "Limt", + Type = Bar.EntryType.List, + Stream = limtStream + } + }); + }); + + // Create the LimtList.yml file using the serializer + File.Create(Path.Combine(ModInputDir, "LimtList.yml")).Using(stream => + { + using var writer = new StreamWriter(stream); + var moddedLimt = new List + { + new Kh2.Battle.Limt + { + Id = 0, + Character = Kh2.Battle.Limt.Characters.Auron, + Summon = Kh2.Battle.Limt.Characters.Sora, + Group = 0, + FileName = "auron.bar", + SpawnId = 0, + Command = 82, + Limit = 204, + World = 0, + Padding = new byte[18] + } + }; + writer.Write(serializer.Serialize(moddedLimt)); + writer.Flush(); + }); + + // Apply the patch + patcher.Patch(AssetsInputDir, ModOutputDir, patch, ModInputDir, Tests: true); + + // Verify the output file exists + AssertFileExists(ModOutputDir, "00battle.bar"); + + // Read and validate the output file + File.OpenRead(Path.Combine(ModOutputDir, "00battle.bar")).Using(stream => + { + var binarc = Bar.Read(stream); + var limt = Kh2.Battle.Limt.Read(binarc[0].Stream); + + Assert.Equal(0, limt[0].Id); + Assert.Equal(Kh2.Battle.Limt.Characters.Auron, limt[0].Character); + Assert.Equal(Kh2.Battle.Limt.Characters.Sora, limt[0].Summon); + Assert.Equal(0, limt[0].Group); + Assert.Equal("auron.bar", limt[0].FileName.Trim()); + Assert.Equal(0u, limt[0].SpawnId); + Assert.Equal(82, limt[0].Command); + Assert.Equal(204, limt[0].Limit); + Assert.Equal(0, limt[0].World); + Assert.Equal(new byte[18], limt[0].Padding); + }); + } + + + [Fact] + public void ListPatchBtlvTest() { var patcher = new PatcherProcessor(); var serializer = new Serializer(); @@ -1920,15 +2547,15 @@ public void ListPatchPrztTest() { new AssetFile() { - Name = "przt", + Name = "btlv", Method = "listpatch", Type = "List", Source = new List() { new AssetFile() { - Name = "PrztList.yml", - Type = "przt" + Name = "BtlvList.yml", + Type = "btlv" } } } @@ -1939,82 +2566,994 @@ public void ListPatchPrztTest() File.Create(Path.Combine(AssetsInputDir, "00battle.bar")).Using(stream => { - var prztEntry = new List() + var btlvEntry = new List() { - new Kh2.Battle.Przt + new Kh2.Battle.Btlv { - Id = 1, - SmallHpOrbs = 0, - BigHpOrbs = 1 + Id = 0, + ProgressFlag = 3, + WorldZZ = 1, + Padding = new byte[5], } }; - using var prztStream = new MemoryStream(); - Kh2.Battle.Przt.Write(prztStream, prztEntry); + using var btlvStream = new MemoryStream(); + Kh2.Battle.Btlv.Write(btlvStream, btlvEntry); Bar.Write(stream, new Bar() { new Bar.Entry() { - Name = "przt", + Name = "btlv", Type = Bar.EntryType.List, - Stream = prztStream + Stream = btlvStream } }); }); - File.Create(Path.Combine(ModInputDir, "PrztList.yml")).Using(stream => + File.Create(Path.Combine(ModInputDir, "BtlvList.yml")).Using(stream => { var writer = new StreamWriter(stream); var serializer = new Serializer(); - var moddedPrzt = new List{ - new Kh2.Battle.Przt + var moddedBtlv = new List{ + new Kh2.Battle.Btlv { - Id = 1, - SmallHpOrbs = 0, - BigHpOrbs = 1 + Id = 0, + ProgressFlag = 3, + WorldZZ = 1, + Padding = new byte[5], + } }; - writer.Write(serializer.Serialize(moddedPrzt)); + writer.Write(serializer.Serialize(moddedBtlv)); writer.Flush(); }); - patcher.Patch(AssetsInputDir, ModOutputDir, patch, ModInputDir); + patcher.Patch(AssetsInputDir, ModOutputDir, patch, ModInputDir, Tests: true); AssertFileExists(ModOutputDir, "00battle.bar"); File.OpenRead(Path.Combine(ModOutputDir, "00battle.bar")).Using(stream => { var binarc = Bar.Read(stream); - var przt = Kh2.Battle.Przt.Read(binarc[0].Stream); + var btlv = Kh2.Battle.Btlv.Read(binarc[0].Stream); - Assert.Equal(1, przt[0].BigHpOrbs); + Assert.Equal(3, btlv[0].ProgressFlag); }); } [Fact] - public void ProcessMultipleTest() + public void ListPatchVtblTest() { var patcher = new PatcherProcessor(); - var patch = new Metadata + var serializer = new Serializer(); + var patch = new Metadata() { - Assets = new List + Assets = new List() + { + new AssetFile() + { + Name = "00battle.bar", + Method = "binarc", + Source = new List() { - new AssetFile + new AssetFile() { - Name = "somedir/somefile.bar", - Method = "binarc", - Multi = new List - { - new Multi { Name = "somedir/another.bar" } - }, - Source = new List + Name = "vtbl", + Method = "listpatch", + Type = "List", + Source = new List() { - new AssetFile + new AssetFile() { - Name = "test", - Method = "imgd", - Type = "imgd", - Source = new List - { + Name = "VtblList.yml", + Type = "vtbl" + } + } + } + } + } + } + }; + + File.Create(Path.Combine(AssetsInputDir, "00battle.bar")).Using(stream => + { + var vtblEntry = new List() + { + new Kh2.Battle.Vtbl + { + Id = 0, + CharacterId = 1, + Priority = 1, + Reserved = 0, + Voices = new List + { + new Kh2.Battle.Vtbl.Voice { VsbIndex = 0, Weight = 0 } + } + } + }; + + using var vtblStream = new MemoryStream(); + Kh2.Battle.Vtbl.Write(vtblStream, vtblEntry); + Bar.Write(stream, new Bar() { + new Bar.Entry() + { + Name = "vtbl", + Type = Bar.EntryType.List, + Stream = vtblStream + } + }); + }); + + File.Create(Path.Combine(ModInputDir, "VtblList.yml")).Using(stream => + { + var writer = new StreamWriter(stream); + var moddedVtbl = new List{ + new Kh2.Battle.Vtbl + { + Id = 0, + CharacterId = 1, + Priority = 1, + Reserved = 0, + Voices = new List + { + new Kh2.Battle.Vtbl.Voice { VsbIndex = 0, Weight = 0 } + } + } + }; + writer.Write(serializer.Serialize(moddedVtbl)); + writer.Flush(); + }); + + patcher.Patch(AssetsInputDir, ModOutputDir, patch, ModInputDir, Tests: true); + + AssertFileExists(ModOutputDir, "00battle.bar"); + + File.OpenRead(Path.Combine(ModOutputDir, "00battle.bar")).Using(stream => + { + var binarc = Bar.Read(stream); + var vtbl = Kh2.Battle.Vtbl.Read(binarc[0].Stream); + + Assert.Equal(1, vtbl[0].Priority); + }); + } + + + + [Fact] //Libretto test. + public void ListPatchLibrettoTest() + { + var patcher = new PatcherProcessor(); + var serializer = new Serializer(); + var patch = new Metadata() + { + Assets = new List() + { + new AssetFile() + { + Name = "libretto-ca.bar", + Method = "binarc", + Source = new List() + { + new AssetFile() + { + Name = "ca", + Method = "listpatch", + Type = "List", + Source = new List() + { + new AssetFile() + { + Name = "LibrettoList.yml", + Type = "libretto" + } + } + } + } + } + } + }; + + using (var stream = File.Create(Path.Combine(AssetsInputDir, "libretto-ca.bar"))) + { + var librettoEntry = new Kh2.Libretto + { + MagicCode = 0x03, // "LIBR" in ASCII + Count = 1, + Definitions = new List + { + new Kh2.Libretto.TalkMessageDefinition + { + TalkMessageId = 1, + Unknown = 0, + ContentPointer = 8 + 8 // MagicCode (4 bytes) + Count (4 bytes) + Definitions (8 bytes each) + } + }, + Contents = new List> + { + new List + { + new Kh2.Libretto.TalkMessageContent { Unknown1 = 0x02000200, TextId = 2500 } + } + } + }; + + using (var librettoStream = new MemoryStream()) + { + Kh2.Libretto.Write(librettoStream, librettoEntry); + Bar.Write(stream, new Bar + { + new Bar.Entry() + { + Name = "libretto", + Type = Bar.EntryType.List, + Stream = librettoStream + } + }); + } + } + + File.Create(Path.Combine(ModInputDir, "LibrettoList.yml")).Using(stream => + { + var writer = new StreamWriter(stream); + writer.WriteLine("- TalkMessageId: 1"); + writer.WriteLine(" Unknown: 0"); + writer.WriteLine(" Contents:"); + writer.WriteLine(" - Unknown1: 0x02000200"); + writer.WriteLine(" TextId: 2500"); + writer.Flush(); + }); + + patcher.Patch(AssetsInputDir, ModOutputDir, patch, ModInputDir, Tests: true); + + AssertFileExists(ModOutputDir, "libretto-ca.bar"); + + File.OpenRead(Path.Combine(ModOutputDir, "libretto-ca.bar")).Using(stream => + { + var binarc = Bar.Read(stream); + var librettoList = Kh2.Libretto.Read(binarc[0].Stream); + var librettoEntry = librettoList.Contents[0][0]; + Assert.Equal(0x02000200u, librettoEntry.Unknown1); + Assert.Equal(2500u, librettoEntry.TextId); + }); + } + + [Fact] + public void ListPatchLocalsetTest() + { + var patcher = new PatcherProcessor(); + var serializer = new Serializer(); + var patch = new Metadata() + { + Assets = new List() + { + new AssetFile() + { + Name = "07localset.bin", + Method = "binarc", + Source = new List() + { + new AssetFile() + { + Name = "loca", + Method = "listpatch", + Type = "List", + Source = new List() + { + new AssetFile() + { + Name = "LocalsetList.yml", + Type = "localset" + } + } + } + } + } + } + }; + + File.Create(Path.Combine(AssetsInputDir, "07localset.bin")).Using(stream => + { + var localEntry = new List() + { + new Kh2.Localset + { + ProgramId = 1300, + MapNumber = 6, + } + }; + + using var localStream = new MemoryStream(); + Kh2.Localset.Write(localStream, localEntry); + Bar.Write(stream, new Bar() { + new Bar.Entry() + { + Name = "loca", + Type = Bar.EntryType.List, + Stream = localStream + } + }); + }); + + File.Create(Path.Combine(ModInputDir, "LocalsetList.yml")).Using(stream => + { + var writer = new StreamWriter(stream); + writer.WriteLine("- ProgramId: 1300"); + writer.WriteLine(" MapNumber: 6"); + writer.Flush(); + }); + + patcher.Patch(AssetsInputDir, ModOutputDir, patch, ModInputDir, Tests: true); + + AssertFileExists(ModOutputDir, "07localset.bin"); + + File.OpenRead(Path.Combine(ModOutputDir, "07localset.bin")).Using(stream => + { + var binarc = Bar.Read(stream); + var localStream = Kh2.Localset.Read(binarc[0].Stream); + Assert.Equal(1300u, localStream[0].ProgramId); + Assert.Equal(6u, localStream[0].MapNumber); + }); + } + + [Fact] + public void ListPatchJigsawTest() + { + var patcher = new PatcherProcessor(); + var serializer = new Serializer(); + var patch = new Metadata() + { + Assets = new List() + { + new AssetFile() + { + Name = "15jigsaw.bin", + Method = "listpatch", + Type = "List", + Source = new List() + { + new AssetFile() + { + Name = "JigsawList.yml", + Type = "jigsaw", + } + } + } + } + }; + + File.Create(Path.Combine(AssetsInputDir, "15jigsaw.bin")).Using(stream => + { + var jigsawEntry = new List() + { + new Kh2.Jigsaw + { + Picture = Kh2.Jigsaw.PictureName.Duality, + Part = 2, + Text = 1500 + } + }; + Kh2.Jigsaw.Write(stream, jigsawEntry); + }); + + File.Create(Path.Combine(ModInputDir, "JigsawList.yml")).Using(stream => + { + var writer = new StreamWriter(stream); + writer.WriteLine("- Picture: Duality"); + writer.WriteLine(" Part: 2"); + writer.WriteLine(" Text: 1500"); + writer.Flush(); + }); + + patcher.Patch(AssetsInputDir, ModOutputDir, patch, ModInputDir, Tests: true); + + AssertFileExists(ModOutputDir, "15jigsaw.bin"); + + File.OpenRead(Path.Combine(ModOutputDir, "15jigsaw.bin")).Using(stream => + { + var jigsawStream = Kh2.Jigsaw.Read(stream); + Assert.Equal(2, jigsawStream[0].Part); + }); + + } + + [Fact] + public void ListPatchPlacesTest() + { + var patcher = new PatcherProcessor(); + var serializer = new Serializer(); + var patch = new Metadata() + { + Assets = new List() + { + new AssetFile() + { + Name = "place.bin", + Method = "listpatch", + Type = "List", + Source = new List() + { + new AssetFile() + { + Name = "PlaceList.yml", + Type = "place", + } + } + } + } + }; + + File.Create(Path.Combine(AssetsInputDir, "place.bin")).Using(stream => + { + var placeEntry = new List() + { + new Kh2.Places + { + MessageId = 100, + Padding = 0, + } + }; + Kh2.Places.Write(stream, placeEntry); + }); + + File.Create(Path.Combine(ModInputDir, "PlaceList.yml")).Using(stream => + { + var writer = new StreamWriter(stream); + writer.WriteLine("- MessageId: 100"); + writer.WriteLine(" Padding: 0"); + writer.Flush(); + }); + + patcher.Patch(AssetsInputDir, ModOutputDir, patch, ModInputDir, Tests: true); + + AssertFileExists(ModOutputDir, "place.bin"); + + File.OpenRead(Path.Combine(ModOutputDir, "place.bin")).Using(stream => + { + var placesStream = Kh2.Places.Read(stream); + Assert.Equal(100, placesStream[0].MessageId); + }); + + } + + [Fact] + public void ListPatchSoundInfoTest() + { + var patcher = new PatcherProcessor(); + var serializer = new Serializer(); + var patch = new Metadata() + { + Assets = new List() + { + new AssetFile() + { + Name = "12soundinfo.bar", + Method = "binarc", + Source = new List() + { + new AssetFile() + { + Name = "zz", + Method = "listpatch", + Type = "List", + Source = new List() + { + new AssetFile() + { + Name = "SoundInfoList.yml", + Type = "soundinfo" + } + } + } + } + } + } + }; + File.Create(Path.Combine(AssetsInputDir, "12soundinfo.bar")).Using(stream => + { + var soundinfoEntry = new List() + { + new Kh2.Soundinfo + { + Reverb = -1, + Rate = 1, + } + }; + Kh2.Soundinfo.Write(stream, soundinfoEntry); + }); + + File.Create(Path.Combine(ModInputDir, "SoundInfoList.yml")).Using(stream => + { + var writer = new StreamWriter(stream); + writer.WriteLine("- Reverb: -1"); + writer.WriteLine(" Rate: 1"); + writer.Flush(); + }); + + patcher.Patch(AssetsInputDir, ModOutputDir, patch, ModInputDir, Tests: true); + + AssertFileExists(ModOutputDir, "12soundinfo.bar"); + + File.OpenRead(Path.Combine(ModOutputDir, "12soundinfo.bar")).Using(stream => + { + var soundinfoStream = Kh2.Soundinfo.Read(stream); + Assert.Equal(1, soundinfoStream[0].Rate); + }); + + } + + [Fact] + public void ListPatchMixdataReciTest() + { + var patcher = new PatcherProcessor(); + var serializer = new Serializer(); + var patch = new Metadata() + { + Assets = new List() + { + new AssetFile() + { + Name = "mixdata.bar", + Method = "binarc", + Source = new List() + { + new AssetFile() + { + Name = "reci", + Method = "synthpatch", + Type = "Synthesis", + Source = new List() + { + new AssetFile() + { + Name = "ReciList.yml", + Type = "recipe" + } + } + } + } + } + } + }; + + File.Create(Path.Combine(AssetsInputDir, "mixdata.bar")).Using(stream => + { + var recipeEntry = new List() + { + new Kh2.Mixdata.ReciLP + { + Id = 1, + Unlock = 0, + Rank = 0, + } + }; + using var recipeStream = new MemoryStream(); + Kh2.Mixdata.ReciLP.Write(recipeStream, recipeEntry); + Bar.Write(stream, new Bar() { + new Bar.Entry() + { + Name = "reci", + Type = Bar.EntryType.List, + Stream = recipeStream + } + }); + }); + + File.Create(Path.Combine(ModInputDir, "ReciList.yml")).Using(stream => + { + var writer = new StreamWriter(stream); + writer.WriteLine("- Id: 1"); + writer.WriteLine(" Rank: 0"); + writer.Flush(); + }); + + patcher.Patch(AssetsInputDir, ModOutputDir, patch, ModInputDir, Tests: true); + + AssertFileExists(ModOutputDir, "mixdata.bar"); + + File.OpenRead(Path.Combine(ModOutputDir, "mixdata.bar")).Using(stream => + { + var binarc = Bar.Read(stream); + var recipeStream = Kh2.Mixdata.ReciLP.Read(binarc[0].Stream); + Assert.Equal(1, recipeStream[0].Id); + Assert.Equal(0, recipeStream[0].Rank); + }); + + } + + [Fact] + public void ListPatchMixdataLeveLPTest() + { + var patcher = new PatcherProcessor(); + var serializer = new Serializer(); + var patch = new Metadata() + { + Assets = new List() + { + new AssetFile() + { + Name = "mixdata.bar", + Method = "binarc", + Source = new List() + { + new AssetFile() + { + Name = "leve", + Method = "synthpatch", + Type = "Synthesis", + Source = new List() + { + new AssetFile() + { + Name = "LeveList.yml", + Type = "level" + } + } + } + } + } + } + }; + + // Create the initial mixdata.bar file with LeveLP entry + File.Create(Path.Combine(AssetsInputDir, "mixdata.bar")).Using(stream => + { + var leveEntry = new List() + { + new Kh2.Mixdata.LeveLP + { + Title = 1, + Stat = 2, + Enable = 1, + Padding = 0, + Exp = 100 + } + }; + + using var leveStream = new MemoryStream(); + Kh2.Mixdata.LeveLP.Write(leveStream, leveEntry); + leveStream.Position = 0; // Ensure stream position is reset before writing to Bar + Bar.Write(stream, new Bar() { + new Bar.Entry() + { + Name = "leve", + Type = Bar.EntryType.List, + Stream = leveStream + } + }); + }); + + // Create the LeveList.yml file using the serializer + File.Create(Path.Combine(ModInputDir, "LeveList.yml")).Using(stream => + { + using var writer = new StreamWriter(stream); + var moddedLeve = new List + { + new Kh2.Mixdata.LeveLP + { + Title = 1, + Stat = 2, + Enable = 1, + Padding = 0, + Exp = 100 + } + }; + writer.Write(serializer.Serialize(moddedLeve)); + writer.Flush(); + }); + + // Apply the patch + patcher.Patch(AssetsInputDir, ModOutputDir, patch, ModInputDir, Tests: true); + + // Verify the output file exists + AssertFileExists(ModOutputDir, "mixdata.bar"); + + // Read and validate the output file + File.OpenRead(Path.Combine(ModOutputDir, "mixdata.bar")).Using(stream => + { + var binarc = Bar.Read(stream); + var leveStream = Kh2.Mixdata.LeveLP.Read(binarc[0].Stream); + + Assert.Equal(1, leveStream[0].Title); + Assert.Equal(2, leveStream[0].Stat); + Assert.Equal(1, leveStream[0].Enable); + Assert.Equal(0, leveStream[0].Padding); + Assert.Equal(100, leveStream[0].Exp); + }); + } + + [Fact] + public void ListPatchMixdataCondLPTest() + { + var patcher = new PatcherProcessor(); + var serializer = new Serializer(); + var patch = new Metadata() + { + Assets = new List() + { + new AssetFile() + { + Name = "mixdata.bar", + Method = "binarc", + Source = new List() + { + new AssetFile() + { + Name = "cond", + Method = "synthpatch", + Type = "Synthesis", + Source = new List() + { + new AssetFile() + { + Name = "CondList.yml", + Type = "condition" + } + } + } + } + } + } + }; + + // Create the initial mixdata.bar file with CondLP entry + File.Create(Path.Combine(AssetsInputDir, "mixdata.bar")).Using(stream => + { + var condEntry = new List() + { + new Kh2.Mixdata.CondLP + { + TextId = 1, + Reward = 100, + Type = Kh2.Mixdata.CondLP.RewardType.Item, + MaterialType = 0, + MaterialRank = 1, + ItemCollect = Kh2.Mixdata.CondLP.CollectionType.Stack, + Count = 10, + ShopUnlock = 5 + } + }; + + using var condStream = new MemoryStream(); + Kh2.Mixdata.CondLP.Write(condStream, condEntry); + condStream.Position = 0; // Ensure stream position is reset before writing to Bar + Bar.Write(stream, new Bar() { + new Bar.Entry() + { + Name = "cond", + Type = Bar.EntryType.List, + Stream = condStream + } + }); + }); + + // Create the CondList.yml file using the serializer + File.Create(Path.Combine(ModInputDir, "CondList.yml")).Using(stream => + { + using var writer = new StreamWriter(stream); + var moddedCond = new List + { + new Kh2.Mixdata.CondLP + { + TextId = 1, + Reward = 100, + Type = Kh2.Mixdata.CondLP.RewardType.Item, + MaterialType = 0, + MaterialRank = 1, + ItemCollect = Kh2.Mixdata.CondLP.CollectionType.Stack, + Count = 10, + ShopUnlock = 5 + } + }; + writer.Write(serializer.Serialize(moddedCond)); + writer.Flush(); + }); + + // Apply the patch + patcher.Patch(AssetsInputDir, ModOutputDir, patch, ModInputDir, Tests: true); + + // Verify the output file exists + AssertFileExists(ModOutputDir, "mixdata.bar"); + + // Read and validate the output file + File.OpenRead(Path.Combine(ModOutputDir, "mixdata.bar")).Using(stream => + { + var binarc = Bar.Read(stream); + var condStream = Kh2.Mixdata.CondLP.Read(binarc[0].Stream); + + Assert.Equal(1, condStream[0].TextId); + Assert.Equal(100, condStream[0].Reward); + Assert.Equal(Kh2.Mixdata.CondLP.RewardType.Item, condStream[0].Type); + Assert.Equal(0, condStream[0].MaterialType); + Assert.Equal(1, condStream[0].MaterialRank); + Assert.Equal(Kh2.Mixdata.CondLP.CollectionType.Stack, condStream[0].ItemCollect); + Assert.Equal(10, condStream[0].Count); + Assert.Equal(5, condStream[0].ShopUnlock); + }); + } + + + [Fact] + public void BbsArcCreateArcTest() + { + var patcher = new PatcherProcessor(); + var patch = new Metadata + { + Assets = new List { + new AssetFile + { + Name = "somedir/somearc.arc", + Method = "bbsarc", + Source = new List { + new AssetFile + { + Name = "newfile", + Method = "copy", + Source = new List { + new AssetFile + { + Name = "somedir/somearc/newfile.bin" + } + } + } + } + } + } + }; + + CreateFile(ModInputDir, "somedir/somearc/newfile.bin").Using(x => + { + x.Write(new byte[] { 4, 5, 6, 7 }); + }); + + patcher.Patch(AssetsInputDir, ModOutputDir, patch, ModInputDir, Tests: true); + + AssertFileExists(ModOutputDir, patch.Assets[0].Name); + AssertArcFile("newfile", entry => + { + Assert.Equal(4, entry.Data.Length); + Assert.Equal(new byte[] { 4, 5, 6, 7 }, entry.Data); + }, ModOutputDir, patch.Assets[0].Name); + } + + [Fact] + public void BbsArcAddToArcTest() + { + var patcher = new PatcherProcessor(); + var patch = new Metadata + { + Assets = new List { + new AssetFile + { + Name = "somedir/somearc.arc", + Method = "bbsarc", + Source = new List { + new AssetFile + { + Name = "newfile", + Method = "copy", + Source = new List { + new AssetFile + { + Name = "somedir/somearc/newfile.bin" + } + } + } + } + } + } + }; + + CreateFile(AssetsInputDir, "somedir/somearc.arc").Using(x => + { + Arc.Write(new List + { + new Arc.Entry + { + Name = "abcd", + Data = new byte[] {0, 1, 2, 3 } + } + }, x); + }); + + CreateFile(ModInputDir, "somedir/somearc/newfile.bin").Using(x => + { + x.Write(new byte[] { 4, 5, 6, 7 }); + }); + + patcher.Patch(AssetsInputDir, ModOutputDir, patch, ModInputDir, Tests: true); + + AssertFileExists(ModOutputDir, patch.Assets[0].Name); + AssertArcFile("abcd", entry => + { + Assert.Equal(4, entry.Data.Length); + Assert.Equal(new byte[] { 0, 1, 2, 3 }, entry.Data); + }, ModOutputDir, patch.Assets[0].Name); + AssertArcFile("newfile", entry => + { + Assert.Equal(4, entry.Data.Length); + Assert.Equal(new byte[] { 4, 5, 6, 7 }, entry.Data); + }, ModOutputDir, patch.Assets[0].Name); + } + + [Fact] + public void BbsArcReplaceInArcTest() + { + var patcher = new PatcherProcessor(); + var patch = new Metadata + { + Assets = new List { + new AssetFile + { + Name = "somedir/somearc.arc", + Method = "bbsarc", + Source = new List { + new AssetFile + { + Name = "abcd", + Method = "copy", + Source = new List { + new AssetFile + { + Name = "somedir/somearc/abcd.bin" + } + } + } + } + } + } + }; + + CreateFile(AssetsInputDir, "somedir/somearc.arc").Using(x => + { + Arc.Write(new List + { + new Arc.Entry + { + Name = "abcd", + Data = new byte[] {0, 1, 2, 3} + } + }, x); + }); + + CreateFile(ModInputDir, "somedir/somearc/abcd.bin").Using(x => + { + x.Write(new byte[] { 4, 5, 6, 7 }); + }); + + patcher.Patch(AssetsInputDir, ModOutputDir, patch, ModInputDir, Tests: true); + + AssertFileExists(ModOutputDir, patch.Assets[0].Name); + AssertArcFile("abcd", entry => + { + Assert.Equal(4, entry.Data.Length); + Assert.Equal(new byte[] { 4, 5, 6, 7 }, entry.Data); + }, ModOutputDir, patch.Assets[0].Name); + } + + [Fact] + public void ProcessMultipleTest() + { + var patcher = new PatcherProcessor(); + var patch = new Metadata + { + Assets = new List + { + new AssetFile + { + Name = "somedir/somefile.bar", + Method = "binarc", + Multi = new List + { + new Multi { Name = "somedir/another.bar" } + }, + Source = new List + { + new AssetFile + { + Name = "test", + Method = "imgd", + Type = "imgd", + Source = new List + { new AssetFile { Name = "sample.png", @@ -2029,7 +3568,7 @@ public void ProcessMultipleTest() File.Copy("Imaging/res/png/32.png", Path.Combine(ModInputDir, "sample.png")); - patcher.Patch(AssetsInputDir, ModOutputDir, patch, ModInputDir); + patcher.Patch(AssetsInputDir, ModOutputDir, patch, ModInputDir, Tests: true); AssertFileExists(ModOutputDir, patch.Assets[0].Name); AssertBarFile("test", entry => @@ -2069,6 +3608,23 @@ private static void AssertBarFile(string name, Action assertion, para assertion(entry); } + private static void AssertArcFile(string name, Action assertion, params string[] paths) + { + var filePath = Path.Join(paths); + var entries = File.OpenRead(filePath).Using(x => + { + if (!Arc.IsValid(x)) + Assert.Fail($"Not a valid Arc"); + return Arc.Read(x); + }); + + var entry = entries.SingleOrDefault(x => x.Name == name); + if (entry == null) + throw new XunitException($"Arc Entry '{name}' not found"); + + assertion(entry); + } + private static Stream CreateFile(params string[] paths) { var filePath = Path.Join(paths); diff --git a/OpenKh.Tools.BbsMapStudio/OpenKh.Tools.BbsMapStudio.csproj b/OpenKh.Tools.BbsMapStudio/OpenKh.Tools.BbsMapStudio.csproj index 7dfe45110..f9298841a 100644 --- a/OpenKh.Tools.BbsMapStudio/OpenKh.Tools.BbsMapStudio.csproj +++ b/OpenKh.Tools.BbsMapStudio/OpenKh.Tools.BbsMapStudio.csproj @@ -23,7 +23,7 @@ - + diff --git a/OpenKh.Tools.Common.CustomImGui/ImGuiEx.cs b/OpenKh.Tools.Common.CustomImGui/ImGuiEx.cs index 28944ac85..7a6817c82 100644 --- a/OpenKh.Tools.Common.CustomImGui/ImGuiEx.cs +++ b/OpenKh.Tools.Common.CustomImGui/ImGuiEx.cs @@ -309,6 +309,23 @@ public static void ForCombo(string name, string[] items, Func getter, Actio var value = getter(); if (ImGui.Combo(name, ref value, items, items.Length)) setter(value); + } + + //Used for custom color/opacity in kh2mapstudio/eventactivators + public static void ForEdit5(string name, Func getter, Action setter, float speed = 0.01f) + { + var value = getter(); + if (ImGui.DragFloat(name, ref value, speed)) + setter(value); + } + + public static bool ForEdit(string label, Func get, Action set) + { + var value = get(); + var modified = ImGui.InputFloat3(label, ref value); + if (modified) + set(value); + return modified; } } } diff --git a/OpenKh.Tools.Common.CustomImGui/OpenKh.Tools.Common.CustomImGui.csproj b/OpenKh.Tools.Common.CustomImGui/OpenKh.Tools.Common.CustomImGui.csproj index 576294927..4eb296786 100644 --- a/OpenKh.Tools.Common.CustomImGui/OpenKh.Tools.Common.CustomImGui.csproj +++ b/OpenKh.Tools.Common.CustomImGui/OpenKh.Tools.Common.CustomImGui.csproj @@ -7,7 +7,7 @@ - + diff --git a/OpenKh.Tools.Kh2MapStudio/App.cs b/OpenKh.Tools.Kh2MapStudio/App.cs index 3ee5f1ae6..a00d6c2d4 100644 --- a/OpenKh.Tools.Kh2MapStudio/App.cs +++ b/OpenKh.Tools.Kh2MapStudio/App.cs @@ -1,57 +1,59 @@ -using Assimp; -using ImGuiNET; -using Microsoft.Xna.Framework.Input; +using Assimp; +using ImGuiNET; +using Microsoft.Xna.Framework.Input; using OpenKh.Engine; -using OpenKh.Kh2; -using OpenKh.Tools.Common.CustomImGui; -using OpenKh.Tools.Kh2MapStudio.Windows; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Numerics; -using System.Windows; -using Xe.Tools.Wpf.Dialogs; -using static OpenKh.Tools.Common.CustomImGui.ImGuiEx; -using xna = Microsoft.Xna.Framework; - -namespace OpenKh.Tools.Kh2MapStudio -{ - class App : IDisposable - { - private static readonly List MapFilter = - FileDialogFilterComposer.Compose() - .AddExtensions("MAP file", "map") - .AddAllFiles(); - private static readonly List ArdFilter = - FileDialogFilterComposer.Compose() - .AddExtensions("ARD file", "ard") - .AddAllFiles(); - private static readonly List ModelFilter = - FileDialogFilterComposer.Compose() - .AddExtensions("glTF file (GL Transmission Format)", "gltf") - .AddExtensions("FBX file", "fbx") - .AddExtensions("DAE file (Collada) (might be unaccurate)", "dae") - .AddExtensions("OBJ file (Wavefront) (might lose some information)", "obj") - .AddAllFiles(); - +using OpenKh.Kh2; +using OpenKh.Tools.Common.CustomImGui; +using OpenKh.Tools.Kh2MapStudio.Models; +using OpenKh.Tools.Kh2MapStudio.Windows; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Numerics; +using System.Reflection; +using System.Windows; +using Xe.Tools.Wpf.Dialogs; +using static OpenKh.Tools.Common.CustomImGui.ImGuiEx; +using xna = Microsoft.Xna.Framework; + +namespace OpenKh.Tools.Kh2MapStudio +{ + class App : IDisposable + { + private static readonly List MapFilter = + FileDialogFilterComposer.Compose() + .AddExtensions("MAP file", "map") + .AddAllFiles(); + private static readonly List ArdFilter = + FileDialogFilterComposer.Compose() + .AddExtensions("ARD file", "ard") + .AddAllFiles(); + private static readonly List ModelFilter = + FileDialogFilterComposer.Compose() + .AddExtensions("glTF file (GL Transmission Format)", "gltf") + .AddExtensions("FBX file", "fbx") + .AddExtensions("DAE file (Collada) (might be unaccurate)", "dae") + .AddExtensions("OBJ file (Wavefront) (might lose some information)", "obj") + .AddAllFiles(); + private const string SelectArdFilesCaption = "Select ard files"; - - private readonly Vector4 BgUiColor = new Vector4(0.0f, 0.0f, 0.0f, 0.5f); - private readonly MonoGameImGuiBootstrap _bootstrap; - private bool _exitFlag = false; - - private readonly Dictionary _keyMapping = new Dictionary(); + + private readonly Vector4 BgUiColor = new Vector4(0.0f, 0.0f, 0.0f, 0.5f); + private readonly MonoGameImGuiBootstrap _bootstrap; + private bool _exitFlag = false; + + private readonly Dictionary _keyMapping = new Dictionary(); private readonly MapRenderer _mapRenderer; - private string _gamePath; - private string _mapName; - private string _region; - private string _ardPath; - private string _mapPath; - private string _objPath; - private List _mapArdsList = new List(); - private ObjEntryController _objEntryController; - + private string _gamePath; + private string _mapName; + private string _region; + private string _ardPath; + private string _mapPath; + private string _objPath; + private List _mapArdsList = new List(); + private ObjEntryController _objEntryController; + private xna.Point _previousMousePosition; private MapArdsBefore _before; private MapArdsAfter _after; @@ -60,13 +62,13 @@ class App : IDisposable private record MapArdsBefore(string MapName, string MapFile, IEnumerable ArdFilesRelative) { - } - + } + private record MapArdsAfter(string MapName, string MapFile, string ArdFileRelativeInput, IEnumerable ArdFilesRelativeOutput) { - } - + } + private class SelectArdFilesState { public string InputArd { get; set; } @@ -77,97 +79,103 @@ public void Reset() InputArd = null; OutputArds.Clear(); } - } - - public string Title - { - get - { - var mapName = _mapName != null ? $"{_mapName}@" : string.Empty; - return $"{mapName}{_gamePath ?? "unloaded"} | {MonoGameImGuiBootstrap.ApplicationName}"; - } - } - - private string GamePath - { - get => _gamePath; - set - { - _gamePath = value; - UpdateTitle(); - EnumerateMapList(); - - _objEntryController?.Dispose(); - _objEntryController = new ObjEntryController( - _bootstrap.GraphicsDevice, - _objPath, - Path.Combine(_gamePath, "00objentry.bin")); - _mapRenderer.ObjEntryController = _objEntryController; - - Settings.Default.GamePath = value; - Settings.Default.Save(); - - } - } - - private string MapName - { - get => _mapName; - set - { - _mapName = value; - UpdateTitle(); - } - } - + } + + public string Title + { + get + { + var mapName = _mapName != null ? $"{_mapName}@" : string.Empty; + return $"{mapName}{_gamePath ?? "unloaded"} | {MonoGameImGuiBootstrap.ApplicationName}"; + } + } + + private string GamePath + { + get => _gamePath; + set + { + _gamePath = value; + UpdateTitle(); + EnumerateMapList(); + + _objEntryController?.Dispose(); + + // Determine the objentry file to use + var objEntryFileName = Path.Combine(_gamePath, "mapstudio", "00objentry.bin"); + if (!File.Exists(objEntryFileName)) + { + objEntryFileName = Path.Combine(_gamePath, "00objentry.bin"); + } + + _objEntryController = new ObjEntryController( + _bootstrap.GraphicsDevice, + _objPath, + objEntryFileName); + _mapRenderer.ObjEntryController = _objEntryController; + + Settings.Default.GamePath = value; + Settings.Default.Save(); + } + } + private string MapName + { + get => _mapName; + set + { + _mapName = value; + UpdateTitle(); + } + } + private void LoadMapArd(MapArdsAfter after) { MapName = after.MapName; _mapRenderer.Close(); _mapRenderer.OpenMap(after.MapFile); - _mapRenderer.OpenArd(Path.Combine(_ardPath, after.ArdFileRelativeInput)); - _after = after; - } - - private bool IsGameOpen => !string.IsNullOrEmpty(_gamePath); - private bool IsMapOpen => !string.IsNullOrEmpty(_mapName); - private bool IsOpen => IsGameOpen && IsMapOpen; - - public App(MonoGameImGuiBootstrap bootstrap, string gamePath = null) - { - _bootstrap = bootstrap; - _bootstrap.Title = Title; - _mapRenderer = new MapRenderer(bootstrap.Content, bootstrap.GraphicsDeviceManager); - AddKeyMapping(Keys.O, MenuFileOpen); - AddKeyMapping(Keys.S, MenuFileSave); - AddKeyMapping(Keys.Q, MenuFileUnload); - - if (string.IsNullOrEmpty(gamePath)) - gamePath = Settings.Default.GamePath; - - if (!string.IsNullOrEmpty(gamePath)) - OpenFolder(gamePath); - - ImGui.PushStyleColor(ImGuiCol.MenuBarBg, BgUiColor); - } - - public bool MainLoop() - { - _bootstrap.GraphicsDevice.Clear(xna.Color.CornflowerBlue); - ProcessKeyMapping(); - if (!_bootstrap.ImGuiWantTextInput) - ProcessKeyboardInput(Keyboard.GetState(), 1f / 60); - if (!_bootstrap.ImGuiWantCaptureMouse) - ProcessMouseInput(Mouse.GetState()); - - ImGui.PushStyleColor(ImGuiCol.WindowBg, BgUiColor); - ForControl(ImGui.BeginMainMenuBar, ImGui.EndMainMenuBar, MainMenu); - - MainWindow(); - - ForWindow("Tools", () => - { + _mapRenderer.OpenArd(Path.Combine(_ardPath, after.ArdFileRelativeInput)); + _after = after; + } + + private bool IsGameOpen => !string.IsNullOrEmpty(_gamePath); + private bool IsMapOpen => !string.IsNullOrEmpty(_mapName); + private bool IsOpen => IsGameOpen && IsMapOpen; + + public App(MonoGameImGuiBootstrap bootstrap, string gamePath = null) + { + _bootstrap = bootstrap; + _bootstrap.Title = Title; + _mapRenderer = new MapRenderer(bootstrap.Content, bootstrap.GraphicsDeviceManager); + AddKeyMapping(Keys.O, MenuFileOpen); + AddKeyMapping(Keys.S, MenuFileSave); + AddKeyMapping(Keys.Q, MenuFileUnload); + + if (string.IsNullOrEmpty(gamePath)) + gamePath = Settings.Default.GamePath; + + if (!string.IsNullOrEmpty(gamePath)) + OpenFolder(gamePath); + + ImGui.PushStyleColor(ImGuiCol.MenuBarBg, BgUiColor); + } + + public bool MainLoop() + { + _bootstrap.GraphicsDevice.Clear(xna.Color.CornflowerBlue); + ProcessKeyMapping(); + if (!_bootstrap.ImGuiWantTextInput) + ProcessKeyboardInput(Keyboard.GetState(), 1f / 60); + if (!_bootstrap.ImGuiWantCaptureMouse) + ProcessMouseInput(Mouse.GetState()); + + ImGui.PushStyleColor(ImGuiCol.WindowBg, BgUiColor); + ForControl(ImGui.BeginMainMenuBar, ImGui.EndMainMenuBar, MainMenu); + + MainWindow(); + + ForWindow("Tools", () => + { if (_mapRenderer.CurrentArea.AreaSettingsMask is int areaSettingsMask) { ImGui.Text($"AreaSettings 0 -1"); @@ -179,49 +187,59 @@ public bool MainLoop() ImGui.Text($"AreaSettings {x} -1"); } } - } - - if (EditorSettings.ViewCamera) - CameraWindow.Run(_mapRenderer.Camera); - if (EditorSettings.ViewLayerControl) - LayerControllerWindow.Run(_mapRenderer); - if (EditorSettings.ViewSpawnPoint) - SpawnPointWindow.Run(_mapRenderer); - if (EditorSettings.ViewMeshGroup) - MeshGroupWindow.Run(_mapRenderer.MapMeshGroups); - if (EditorSettings.ViewBobDescriptor) - BobDescriptorWindow.Run(_mapRenderer.BobDescriptors, _mapRenderer.BobMeshGroups.Count); - if (EditorSettings.ViewSpawnScriptMap) - SpawnScriptWindow.Run("map", _mapRenderer.SpawnScriptMap); - if (EditorSettings.ViewSpawnScriptBattle) - SpawnScriptWindow.Run("btl", _mapRenderer.SpawnScriptBattle); - if (EditorSettings.ViewSpawnScriptEvent) - SpawnScriptWindow.Run("evt", _mapRenderer.SpawnScriptEvent); - + } + + + if (EditorSettings.ViewCamera) + CameraWindow.Run(_mapRenderer.Camera); + if (EditorSettings.ViewLayerControl) + LayerControllerWindow.Run(_mapRenderer); + if (EditorSettings.ViewSpawnPoint) + SpawnPointWindow.Run(_mapRenderer); + if (EditorSettings.ViewMeshGroup) + MeshGroupWindow.Run(_mapRenderer.MapMeshGroups); + if (EditorSettings.ViewCollision && _mapRenderer.MapCollision != null) + CollisionWindow.Run(_mapRenderer.MapCollision.Coct); + if (EditorSettings.ViewBobDescriptor) + BobDescriptorWindow.Run(_mapRenderer.BobDescriptors, _mapRenderer.BobMeshGroups.Count); + if (EditorSettings.ViewSpawnScriptMap) + SpawnScriptWindow.Run("map", _mapRenderer.SpawnScriptMap); + if (EditorSettings.ViewSpawnScriptBattle) + SpawnScriptWindow.Run("btl", _mapRenderer.SpawnScriptBattle); + if (EditorSettings.ViewSpawnScriptEvent) + SpawnScriptWindow.Run("evt", _mapRenderer.SpawnScriptEvent); + + if (EditorSettings.ViewEventScript && _mapRenderer.EventScripts != null) { foreach (var eventScript in _mapRenderer.EventScripts) { EventScriptWindow.Run(eventScript.Name, eventScript); - } - } - }); - - SelectArdFilesPopup(); - - ImGui.PopStyleColor(); - - return _exitFlag; - } - - public void Dispose() - { - _objEntryController?.Dispose(); - } - - private void SelectArdFilesPopup() + } + } + }); + + //Add separate Camera Window if setting is toggled on. + if (EditorSettings.SeparateCamera) + { + SeparateWindow.Run(_mapRenderer.Camera); + }; + + SelectArdFilesPopup(); + + ImGui.PopStyleColor(); + + return _exitFlag; + } + + public void Dispose() { - var dummy = true; + _objEntryController?.Dispose(); + } + + private void SelectArdFilesPopup() + { + var dummy = true; if (ImGui.BeginPopupModal(SelectArdFilesCaption, ref dummy, ImGuiWindowFlags.Popup | ImGuiWindowFlags.Modal | ImGuiWindowFlags.AlwaysAutoResize)) { @@ -269,50 +287,51 @@ private void SelectArdFilesPopup() _selectArdFilesState.InputArd, _selectArdFilesState.OutputArds ) - ); - - ImGui.CloseCurrentPopup(); + ); + + ImGui.CloseCurrentPopup(); } ImGui.EndDisabled(); ImGui.EndPopup(); - } - } - - private void MainWindow() - { - if (!IsGameOpen) - { - ImGui.Text("Game content not loaded."); - return; - } - - ForControl(() => - { - var nextPos = ImGui.GetCursorPos(); - var ret = ImGui.Begin("MapList", - ImGuiWindowFlags.NoDecoration | - ImGuiWindowFlags.NoCollapse | - ImGuiWindowFlags.NoMove); - ImGui.SetWindowPos(nextPos); - ImGui.SetWindowSize(new Vector2(64, 0)); - return ret; - }, () => { }, (Action)(() => - { - foreach (var mapArds in _mapArdsList) - { - if (ImGui.Selectable(mapArds.MapName, MapName == mapArds.MapName)) - { + } + } + + private void MainWindow() + { + if (!IsGameOpen) + { + ImGui.Text("Game content not loaded."); + return; + } + + ForControl(() => + { + var nextPos = ImGui.GetCursorPos(); + var ret = ImGui.Begin("Map List", //List of all the maps, the left-side bar. + //ImGuiWindowFlags.NoDecoration | //Removes the scroll-bar + ImGuiWindowFlags.NoCollapse | //Prevents it from being collapsible + ImGuiWindowFlags.AlwaysAutoResize | //NEW: Resizes the window to accomodate maps of various name lengths. + ImGuiWindowFlags.NoMove); //Prevents it from being moved around + ImGui.SetWindowPos(nextPos); + ImGui.SetWindowSize(new Vector2(64, 0)); + return ret; + }, () => { }, (Action)(() => + { + foreach (var mapArds in _mapArdsList) + { + if (ImGui.Selectable(mapArds.MapName, MapName == mapArds.MapName)) + { if (mapArds.ArdFilesRelative.Count() == 1) { - LoadMapArd( - new MapArdsAfter( - mapArds.MapName, - mapArds.MapFile, - mapArds.ArdFilesRelative.Single(), - mapArds.ArdFilesRelative - ) - ); - } + LoadMapArd( + new MapArdsAfter( + mapArds.MapName, + mapArds.MapFile, + mapArds.ArdFilesRelative.Single(), + mapArds.ArdFilesRelative + ) + ); + } else { _before = mapArds; @@ -320,157 +339,185 @@ private void MainWindow() _selectArdFilesState.Reset(); ImGui.OpenPopup(SelectArdFilesCaption); - } - } - } - })); - ImGui.SameLine(); - - if (!IsMapOpen) - { - ImGui.Text("Please select a map to edit."); - return; - } - - _mapRenderer.Update(1f / 60); - _mapRenderer.Draw(); - } - - void MainMenu() - { - ForMenuBar(() => - { - ForMenu("File", () => - { - ForMenuItem("Open extracted game folder...", "CTRL+O", MenuFileOpen); - ForMenuItem("Unload current map+ard", "CTRL+Q", MenuFileUnload, IsOpen); - ForMenuItem("Import extern MAP file", MenuFileOpenMap, IsGameOpen); - ForMenuItem("Import extern ARD file", MenuFileOpenArd, IsGameOpen); - ForMenuItem("Save map+ard", "CTRL+S", MenuFileSave, IsOpen); - ForMenuItem("Save map as...", MenuFileSaveMapAs, IsOpen); - ForMenuItem("Save ard as...", MenuFileSaveArdAs, IsOpen); - ImGui.Separator(); - ForMenu("Export", () => - { - ForMenuItem("Map Collision", ExportMapCollision, _mapRenderer.ShowMapCollision.HasValue); - ForMenuItem("Camera Collision", ExportCameraCollision, _mapRenderer.ShowCameraCollision.HasValue); - ForMenuItem("Light Collision", ExportLightCollision, _mapRenderer.ShowLightCollision.HasValue); - }); - ImGui.Separator(); - ForMenu("Preferences", () => - { - ForEdit("Movement speed", () => EditorSettings.MoveSpeed, x => EditorSettings.MoveSpeed = x); - ForEdit("Movement speed (shift)", () => EditorSettings.MoveSpeedShift, x => EditorSettings.MoveSpeedShift = x); - }); - ImGui.Separator(); - ForMenuItem("Exit", MenuFileExit); - }); - ForMenu("View", () => - { - ForMenuCheck("Camera", () => EditorSettings.ViewCamera, x => EditorSettings.ViewCamera = x); - ForMenuCheck("Layer control", () => EditorSettings.ViewLayerControl, x => EditorSettings.ViewLayerControl = x); - ForMenuCheck("Spawn points", () => EditorSettings.ViewSpawnPoint, x => EditorSettings.ViewSpawnPoint = x); - ForMenuCheck("BOB descriptors", () => EditorSettings.ViewBobDescriptor, x => EditorSettings.ViewBobDescriptor = x); - ForMenuCheck("Mesh group", () => EditorSettings.ViewMeshGroup, x => EditorSettings.ViewMeshGroup = x); - ForMenuCheck("Spawn script MAP", () => EditorSettings.ViewSpawnScriptMap, x => EditorSettings.ViewSpawnScriptMap = x); - ForMenuCheck("Spawn script BTL", () => EditorSettings.ViewSpawnScriptBattle, x => EditorSettings.ViewSpawnScriptBattle = x); - ForMenuCheck("Spawn script EVT", () => EditorSettings.ViewSpawnScriptEvent, x => EditorSettings.ViewSpawnScriptEvent = x); - ForMenuCheck("Event script", () => EditorSettings.ViewEventScript, x => EditorSettings.ViewEventScript = x); - }); - ForMenu("Help", () => - { - ForMenuItem("About", ShowAboutDialog); - }); - }); - } - - private void MenuFileOpen() => FileDialog.OnFolder(OpenFolder); - private void MenuFileUnload() => _mapRenderer.Close(); - private void MenuFileOpenMap() => FileDialog.OnOpen(_mapRenderer.OpenMap, MapFilter); - private void MenuFileOpenArd() => FileDialog.OnOpen(_mapRenderer.OpenArd, ArdFilter); - - private void MenuFileSave() - { - _mapRenderer.SaveMap(_after.MapFile); - + } + } + } + })); + //ImGui.SameLine(); + + if (!IsMapOpen) + { + //ImGui.Text("Select a map to edit."); //Text. Appears at the bottom of the list, commented out. + return; + } + + _mapRenderer.Update(1f / 60); + _mapRenderer.Draw(); + } + + void MainMenu() + { + ForMenuBar(() => + { + ForMenu("File", () => + { + ForMenuItem("Open extracted game folder...", "CTRL+O", MenuFileOpen); + ForMenuItem("Unload current map+ard", "CTRL+Q", MenuFileUnload, IsOpen); + ForMenuItem("Import extern MAP file", MenuFileOpenMap, IsGameOpen); + ForMenuItem("Import extern ARD file", MenuFileOpenArd, IsGameOpen); + ForMenuItem("Save map+ard", "CTRL+S", MenuFileSave, IsOpen); + ForMenuItem("Save map as...", MenuFileSaveMapAs, IsOpen); + ForMenuItem("Save ard as...", MenuFileSaveArdAs, IsOpen); + ImGui.Separator(); + ForMenu("Export", () => + { + ForMenuItem("Map Collision", ExportMapCollision, _mapRenderer.ShowMapCollision.HasValue); + ForMenuItem("Camera Collision", ExportCameraCollision, _mapRenderer.ShowCameraCollision.HasValue); + ForMenuItem("Light Collision", ExportLightCollision, _mapRenderer.ShowLightCollision.HasValue); + }); + ImGui.Separator(); + ForMenuItem("Exit", MenuFileExit); + }); + ForMenu("View", () => + { + ForMenuCheck("Camera", () => EditorSettings.ViewCamera, x => EditorSettings.ViewCamera = x); + ForMenuCheck("Separate Camera Window", () => EditorSettings.SeparateCamera, x => EditorSettings.SeparateCamera = x); + ForMenuCheck("Layer control", () => EditorSettings.ViewLayerControl, x => EditorSettings.ViewLayerControl = x); + ForMenuCheck("Spawn points", () => EditorSettings.ViewSpawnPoint, x => EditorSettings.ViewSpawnPoint = x); + ForMenuCheck("BOB descriptors", () => EditorSettings.ViewBobDescriptor, x => EditorSettings.ViewBobDescriptor = x); + ForMenuCheck("Mesh group", () => EditorSettings.ViewMeshGroup, x => EditorSettings.ViewMeshGroup = x); + ForMenuCheck("Collision (Experimental)", () => EditorSettings.ViewCollision, x => EditorSettings.ViewCollision = x); + ForMenuCheck("Spawn script MAP", () => EditorSettings.ViewSpawnScriptMap, x => EditorSettings.ViewSpawnScriptMap = x); + ForMenuCheck("Spawn script BTL", () => EditorSettings.ViewSpawnScriptBattle, x => EditorSettings.ViewSpawnScriptBattle = x); + ForMenuCheck("Spawn script EVT", () => EditorSettings.ViewSpawnScriptEvent, x => EditorSettings.ViewSpawnScriptEvent = x); + ForMenuCheck("Event script", () => EditorSettings.ViewEventScript, x => EditorSettings.ViewEventScript = x); + }); + + ForMenu("Preferences", () => + { + ForMenu("Movement Speed", () => + { + ForEdit("Default Speed", () => EditorSettings.MoveSpeed, x => EditorSettings.MoveSpeed = x); + ForEdit("Accelerated Speed (hold shift)", () => EditorSettings.MoveSpeedShift, x => EditorSettings.MoveSpeedShift = x); + }); + ForMenu("Event Activator Colors & Opacity", () => + { + ForEdit5("Opacity", () => EditorSettings.OpacityLevel, x => EditorSettings.OpacityLevel = x); + ForEdit5("Red", () => EditorSettings.RedValue, x => EditorSettings.RedValue = x); + ForEdit5("Green", () => EditorSettings.GreenValue, x => EditorSettings.GreenValue = x); + ForEdit5("Blue", () => EditorSettings.BlueValue, x => EditorSettings.BlueValue = x); + }); + ForMenu("Event Activator Entrance Colors & Opacity", () => + { + ForEdit5("Opacity", () => EditorSettings.OpacityEntranceLevel, x => EditorSettings.OpacityEntranceLevel = x); + ForEdit5("Red", () => EditorSettings.RedValueEntrance, x => EditorSettings.RedValueEntrance = x); + ForEdit5("Green", () => EditorSettings.GreenValueEntrance, x => EditorSettings.GreenValueEntrance = x); + ForEdit5("Blue", () => EditorSettings.BlueValueEntrance, x => EditorSettings.BlueValueEntrance = x); + }); + ForMenu("Default Window Size", () => + { + ForEdit("Window Width", () => EditorSettings.InitialWindowWidth, x => EditorSettings.InitialWindowWidth = x); + ForEdit("Window Height", () => EditorSettings.InitialWindowHeight, x => EditorSettings.InitialWindowHeight = x); + + }); + }); + + ForMenu("Help", () => + { + ForMenuItem("About", ShowAboutDialog); + ForMenuItem("Preference Info", ShowPrefDialog); + ForMenuItem("Controls", ShowControlsDialog); + }); + }); + } + + private void MenuFileOpen() => FileDialog.OnFolder(OpenFolder); + private void MenuFileUnload() => _mapRenderer.Close(); + private void MenuFileOpenMap() => FileDialog.OnOpen(_mapRenderer.OpenMap, MapFilter); + private void MenuFileOpenArd() => FileDialog.OnOpen(_mapRenderer.OpenArd, ArdFilter); + + private void MenuFileSave() + { + _mapRenderer.SaveMap(_after.MapFile); + foreach (var ard in _after.ArdFilesRelativeOutput) { _mapRenderer.SaveArd(Path.Combine(_ardPath, ard)); - } - } - - private void MenuFileSaveMapAs() - { - var defaultName = MapName + ".map"; - FileDialog.OnSave(_mapRenderer.SaveMap, MapFilter, defaultName); - } - - private void MenuFileSaveArdAs() - { - var defaultName = MapName + ".ard"; - FileDialog.OnSave(_mapRenderer.SaveArd, ArdFilter, defaultName); - } - - private void ExportMapCollision() => FileDialog.OnSave(fileName => - { - ExportScene(fileName, _mapRenderer.MapCollision.Scene); - }, ModelFilter, $"{MapName}_map-collision.dae"); - - private void ExportCameraCollision() => FileDialog.OnSave(fileName => - { - ExportScene(fileName, _mapRenderer.CameraCollision.Scene); - }, ModelFilter, $"{MapName}_camera-collision.dae"); - - private void ExportLightCollision() => FileDialog.OnSave(fileName => - { - ExportScene(fileName, _mapRenderer.LightCollision.Scene); - }, ModelFilter, $"{MapName}_light-collision.dae"); - - private void MenuFileExit() => _exitFlag = true; - - public void OpenFolder(string gamePath) - { - try - { - if (!Directory.Exists(_ardPath = Path.Combine(gamePath, "ard")) || - !Directory.Exists(_mapPath = Path.Combine(gamePath, "map")) || - !Directory.Exists(_objPath = Path.Combine(gamePath, "obj"))) - throw new DirectoryNotFoundException( - "The specified directory must contain the full extracted copy of the game."); - - GamePath = gamePath; - } - catch (Exception ex) - { - ShowError(ex.Message); - } - } - - private void UpdateTitle() - { - _bootstrap.Title = Title; - } - - private void EnumerateMapList() - { - var mapFiles = Array.Empty(); - foreach (var region in Constants.Regions) - { - var testPath = Path.Combine(_mapPath, region); - if (Directory.Exists(testPath)) - { - mapFiles = Directory.GetFiles(testPath, "*.map"); - if (mapFiles.Length != 0) - { - _mapPath = testPath; - _region = region; - break; - } - } - } - - _mapArdsList.Clear(); - + } + } + + private void MenuFileSaveMapAs() + { + var defaultName = MapName + ".map"; + FileDialog.OnSave(_mapRenderer.SaveMap, MapFilter, defaultName); + } + + private void MenuFileSaveArdAs() + { + var defaultName = MapName + ".ard"; + FileDialog.OnSave(_mapRenderer.SaveArd, ArdFilter, defaultName); + } + + private void ExportMapCollision() => FileDialog.OnSave(fileName => + { + ExportScene(fileName, _mapRenderer.MapCollision.Scene); + }, ModelFilter, $"{MapName}_map-collision.dae"); + + private void ExportCameraCollision() => FileDialog.OnSave(fileName => + { + ExportScene(fileName, _mapRenderer.CameraCollision.Scene); + }, ModelFilter, $"{MapName}_camera-collision.dae"); + + private void ExportLightCollision() => FileDialog.OnSave(fileName => + { + ExportScene(fileName, _mapRenderer.LightCollision.Scene); + }, ModelFilter, $"{MapName}_light-collision.dae"); + + private void MenuFileExit() => _exitFlag = true; + + public void OpenFolder(string gamePath) + { + try + { + if (!Directory.Exists(_ardPath = Path.Combine(gamePath, "ard")) || + !Directory.Exists(_mapPath = Path.Combine(gamePath, "map")) || + !Directory.Exists(_objPath = Path.Combine(gamePath, "obj"))) + throw new DirectoryNotFoundException( + "The specified directory must contain the full extracted copy of the game."); + + GamePath = gamePath; + } + catch (Exception ex) + { + ShowError(ex.Message); + } + } + + private void UpdateTitle() + { + _bootstrap.Title = Title; + } + + private void EnumerateMapList() + { + var mapFiles = Array.Empty(); + foreach (var region in Constants.Regions) + { + var testPath = Path.Combine(_mapPath, region); + if (Directory.Exists(testPath)) + { + mapFiles = Directory.GetFiles(testPath, "*.map"); + if (mapFiles.Length != 0) + { + _mapPath = testPath; + _region = region; + break; + } + } + } + + _mapArdsList.Clear(); + foreach (var mapFile in mapFiles) { var mapName = Path.GetFileNameWithoutExtension(mapFile); @@ -481,99 +528,125 @@ private void EnumerateMapList() .ToArray(); _mapArdsList.Add(new MapArdsBefore(mapName, mapFile, ardFiles)); - } - } - - private void AddKeyMapping(Keys key, Action action) - { - _keyMapping[key] = action; - } - - private void ProcessKeyMapping() - { - var k = Keyboard.GetState(); - if (k.IsKeyDown(Keys.LeftControl)) - { - var keys = k.GetPressedKeys(); - foreach (var key in keys) - { - if (_keyMapping.TryGetValue(key, out var action)) - action(); - } - } - } - - private void ProcessKeyboardInput(KeyboardState keyboard, float deltaTime) - { - var speed = (float)(deltaTime * EditorSettings.MoveSpeed); - var moveSpeed = speed; - if (keyboard.IsKeyDown(Keys.LeftShift) || keyboard.IsKeyDown(Keys.RightShift)) - moveSpeed = (float)(deltaTime * EditorSettings.MoveSpeedShift); - - var camera = _mapRenderer.Camera; - if (keyboard.IsKeyDown(Keys.W)) - camera.CameraPosition += Vector3.Multiply(camera.CameraLookAtX, moveSpeed * 5); - if (keyboard.IsKeyDown(Keys.S)) - camera.CameraPosition -= Vector3.Multiply(camera.CameraLookAtX, moveSpeed * 5); - if (keyboard.IsKeyDown(Keys.D)) - camera.CameraPosition -= Vector3.Multiply(camera.CameraLookAtY, moveSpeed * 5); - if (keyboard.IsKeyDown(Keys.A)) - camera.CameraPosition += Vector3.Multiply(camera.CameraLookAtY, moveSpeed * 5); - if (keyboard.IsKeyDown(Keys.Q)) - camera.CameraPosition += Vector3.Multiply(camera.CameraLookAtZ, moveSpeed * 5); - if (keyboard.IsKeyDown(Keys.E)) - camera.CameraPosition -= Vector3.Multiply(camera.CameraLookAtZ, moveSpeed * 5); - - if (keyboard.IsKeyDown(Keys.Up)) - camera.CameraRotationYawPitchRoll += new Vector3(0, 0, 1 * speed); - if (keyboard.IsKeyDown(Keys.Down)) - camera.CameraRotationYawPitchRoll -= new Vector3(0, 0, 1 * speed); - if (keyboard.IsKeyDown(Keys.Left)) - camera.CameraRotationYawPitchRoll += new Vector3(1 * speed, 0, 0); - if (keyboard.IsKeyDown(Keys.Right)) - camera.CameraRotationYawPitchRoll -= new Vector3(1 * speed, 0, 0); - } - - private void ProcessMouseInput(MouseState mouse) - { - const float Speed = 0.25f; - if (mouse.LeftButton == ButtonState.Pressed) - { - var camera = _mapRenderer.Camera; - var xSpeed = (_previousMousePosition.X - mouse.Position.X) * Speed; - var ySpeed = (_previousMousePosition.Y - mouse.Position.Y) * Speed; - camera.CameraRotationYawPitchRoll += new Vector3(1 * -xSpeed, 0, 0); - camera.CameraRotationYawPitchRoll += new Vector3(0, 0, 1 * ySpeed); - } - - _previousMousePosition = mouse.Position; - } - - private static void ExportScene(string fileName, Scene scene) - { - using var ctx = new AssimpContext(); - var extension = Path.GetExtension(fileName).ToLower(); - var exportFormat = ctx.GetSupportedExportFormats(); - foreach (var format in exportFormat) - { - if ($".{format.FileExtension}" == extension) - { - var material = new Material(); - material.Clear(); - - scene.Materials.Add(material); - ctx.ExportFile(scene, fileName, format.FormatId); - return; - } - } - - ShowError($"Unable to export with '{extension}' extension."); - } - - public static void ShowError(string message, string title = "Error") => - MessageBox.Show(message, title, MessageBoxButton.OK, MessageBoxImage.Error); - - private void ShowAboutDialog() => - MessageBox.Show("OpenKH is amazing."); - } -} + } + } + + private void AddKeyMapping(Keys key, Action action) + { + _keyMapping[key] = action; + } + + private void ProcessKeyMapping() + { + var k = Keyboard.GetState(); + if (k.IsKeyDown(Keys.LeftControl)) + { + var keys = k.GetPressedKeys(); + foreach (var key in keys) + { + if (_keyMapping.TryGetValue(key, out var action)) + action(); + } + } + } + + private void ProcessKeyboardInput(KeyboardState keyboard, float deltaTime) + { + var speed = (float)(deltaTime * EditorSettings.MoveSpeed); + var moveSpeed = speed; + if (keyboard.IsKeyDown(Keys.LeftShift) || keyboard.IsKeyDown(Keys.RightShift)) + moveSpeed = (float)(deltaTime * EditorSettings.MoveSpeedShift); + + var camera = _mapRenderer.Camera; + if (keyboard.IsKeyDown(Keys.W)) + camera.CameraPosition += Vector3.Multiply(camera.CameraLookAtX, moveSpeed * 5); + if (keyboard.IsKeyDown(Keys.S)) + camera.CameraPosition -= Vector3.Multiply(camera.CameraLookAtX, moveSpeed * 5); + if (keyboard.IsKeyDown(Keys.D)) + camera.CameraPosition -= Vector3.Multiply(camera.CameraLookAtY, moveSpeed * 5); + if (keyboard.IsKeyDown(Keys.A)) + camera.CameraPosition += Vector3.Multiply(camera.CameraLookAtY, moveSpeed * 5); + if (keyboard.IsKeyDown(Keys.Q)) + camera.CameraPosition += Vector3.Multiply(camera.CameraLookAtZ, moveSpeed * 5); + if (keyboard.IsKeyDown(Keys.E)) + camera.CameraPosition -= Vector3.Multiply(camera.CameraLookAtZ, moveSpeed * 5); + if (keyboard.IsKeyDown(Keys.Space)) + camera.CameraPosition += new Vector3(0, 1 * moveSpeed * 5, 0); + if (keyboard.IsKeyDown(Keys.LeftControl)) + camera.CameraPosition += new Vector3(0, -1 * moveSpeed * 5, 0); + if (keyboard.IsKeyDown(Keys.Up)) + camera.CameraRotationYawPitchRoll += new Vector3(0, 0, 1 * speed); + if (keyboard.IsKeyDown(Keys.Down)) + camera.CameraRotationYawPitchRoll -= new Vector3(0, 0, 1 * speed); + if (keyboard.IsKeyDown(Keys.Left)) + camera.CameraRotationYawPitchRoll += new Vector3(1 * speed, 0, 0); + if (keyboard.IsKeyDown(Keys.Right)) + camera.CameraRotationYawPitchRoll -= new Vector3(1 * speed, 0, 0); + } + + private void ProcessMouseInput(MouseState mouse) + { + const float Speed = 0.25f; + if (mouse.LeftButton == ButtonState.Pressed) + { + var camera = _mapRenderer.Camera; + var xSpeed = (_previousMousePosition.X - mouse.Position.X) * Speed; + var ySpeed = (_previousMousePosition.Y - mouse.Position.Y) * Speed; + camera.CameraRotationYawPitchRoll += new Vector3(1 * -xSpeed, 0, 0); + camera.CameraRotationYawPitchRoll += new Vector3(0, 0, 1 * ySpeed); + } + + _previousMousePosition = mouse.Position; + } + + private static void ExportScene(string fileName, Scene scene) + { + using var ctx = new AssimpContext(); + var extension = Path.GetExtension(fileName).ToLower(); + var exportFormat = ctx.GetSupportedExportFormats(); + foreach (var format in exportFormat) + { + if ($".{format.FileExtension}" == extension) + { + var material = new Material(); + material.Clear(); + + scene.Materials.Add(material); + ctx.ExportFile(scene, fileName, format.FormatId); + return; + } + } + + ShowError($"Unable to export with '{extension}' extension."); + } + + public static void ShowError(string message, string title = "Error") => + MessageBox.Show(message, title, MessageBoxButton.OK, MessageBoxImage.Error); + + private void ShowAboutDialog() => + //MessageBox.Show("OpenKH is amazing."); + MessageBox.Show("Welcome to OpenKH MapStudio." + + "\n\nThis tool allows you to view .map files along with their associated .ard files." + + "\n\nThe .map and .ard files are loaded using the extracted game data. " + + "\n\nEntities are loaded from the extracted 00objentry.bin & obj folder. New maps and entities can be added into the extracted game data to have them usable in MapStudio." + + "\n\nAlternatively, you can create a folder named mapstudio in your extracted game folder. Placing a modified 00objentry.bin as well as any MDLXs inside that folder will cause MapStudio to prioritize loading from that folder instead." + + "\n\nA .map file contains the geometry and collision of the map." + + "\n\nAn .ard file controls what spawns inside of a map." + + "\n\nSpawn points, where you can encounter enemies, cutscenes, cutscene triggers, chest locations, etc. are all handled by .ard files." + + "\n\nView documentation on openkh.dev to learn more about the file." + ); + + private void ShowPrefDialog() => + MessageBox.Show("Movement speed will alter how fast you can move through the map." + + "\n\nEvent Activator Opacity & Red, Green, and Blue Values all control how the triggers that send you to different areas & spawn enemies will look." + + "\n\nEntrance Marker will mark the entrances of warp points with that color, so that you can properly orient the entrance in the map." + + "\n\nValues for RGBA are floats. For the most accurate representation of warps the values should be set between 0 and 1, though values below 0 and above 1 will work." + + "\n\nValues for Opacity are floats between 0 and 1."); + + private void ShowControlsDialog() => + MessageBox.Show("W/A/S/D/E/Q: Moves in any direction, influenced by the camera's rotation." + + "\n\nLeft Control/Space: Move directly down/up, regardless of camera's rotation." + + "\n\nShift: Increase movement speed (can be changed under Preferences)." + + "\n\nLeft Click/Arrow Keys: Rotate Camera"); + } +} diff --git a/OpenKh.Tools.Kh2MapStudio/EditorSettings.cs b/OpenKh.Tools.Kh2MapStudio/EditorSettings.cs index 2ec51ec6d..661f09d00 100644 --- a/OpenKh.Tools.Kh2MapStudio/EditorSettings.cs +++ b/OpenKh.Tools.Kh2MapStudio/EditorSettings.cs @@ -60,6 +60,16 @@ public static bool ViewMeshGroup Settings.Default.ViewMeshGroup = value; Settings.Default.Save(); } + } + + public static bool ViewCollision + { + get => Settings.Default.ViewCollision; + set + { + Settings.Default.ViewCollision = value; + Settings.Default.Save(); + } } public static bool ViewBobDescriptor @@ -110,6 +120,118 @@ public static bool ViewEventScript Settings.Default.ViewEventScript = value; Settings.Default.Save(); } + } + + public static bool SeparateCamera + { + get => Settings.Default.SeparateCamera; + set + { + Settings.Default.SeparateCamera = value; + Settings.Default.Save(); + } + } + public static float OpacityLevel + { + get => Settings.Default.OpacityLevel; + set + { + Settings.Default.OpacityLevel = value; + Settings.Default.Save(); + } } + + public static float RedValue + { + get => Settings.Default.RedValue; + set + { + Settings.Default.RedValue = value; + Settings.Default.Save(); + } + } + + public static float GreenValue + { + get => Settings.Default.GreenValue; + set + { + Settings.Default.GreenValue = value; + Settings.Default.Save(); + } + } + + public static float BlueValue + { + get => Settings.Default.BlueValue; + set + { + Settings.Default.BlueValue = value; + Settings.Default.Save(); + } + } + + public static float OpacityEntranceLevel + { + get => Settings.Default.OpacityEntranceLevel; + set + { + Settings.Default.OpacityEntranceLevel = value; + Settings.Default.Save(); + } + } + + public static float RedValueEntrance + { + get => Settings.Default.RedValueEntrance; + set + { + Settings.Default.RedValueEntrance = value; + Settings.Default.Save(); + } + } + + public static float GreenValueEntrance + { + get => Settings.Default.GreenValueEntrance; + set + { + Settings.Default.GreenValueEntrance = value; + Settings.Default.Save(); + } + } + + public static float BlueValueEntrance + { + get => Settings.Default.BlueValueEntrance; + set + { + Settings.Default.BlueValueEntrance = value; + Settings.Default.Save(); + } + } + + public static int InitialWindowWidth + { + get => Settings.Default.InitialWindowWidth; + set + { + Settings.Default.InitialWindowWidth = value; + Settings.Default.Save(); + + } + } + + public static int InitialWindowHeight + { + get => Settings.Default.InitialWindowHeight; + set + { + Settings.Default.InitialWindowHeight = value; + Settings.Default.Save(); + + } + } + } } diff --git a/OpenKh.Tools.Kh2MapStudio/MapRenderer.cs b/OpenKh.Tools.Kh2MapStudio/MapRenderer.cs index e17ff024d..a34c578a7 100644 --- a/OpenKh.Tools.Kh2MapStudio/MapRenderer.cs +++ b/OpenKh.Tools.Kh2MapStudio/MapRenderer.cs @@ -338,57 +338,70 @@ public void Draw() } } - if (CurrentSpawnPoint != null) - { - foreach (var spawnPoint in CurrentSpawnPoint.SpawnPoints) - { - foreach (var entity in spawnPoint.Entities) - { - _shader.SetModelView(Matrix4x4.CreateRotationX(entity.RotationX) * - Matrix4x4.CreateRotationY(entity.RotationY) * - Matrix4x4.CreateRotationZ(entity.RotationZ) * - Matrix4x4.CreateTranslation(entity.PositionX, -entity.PositionY, -entity.PositionZ)); - RenderMeshNew(pass, CurrentSpawnPoint.ObjEntryCtrl[entity.ObjectId], true); - } - - _graphics.RasterizerState = new RasterizerState() - { - CullMode = CullMode.None - }; - _shader.SetRenderTexture(pass, _whiteTexture); - foreach (var item in spawnPoint.EventActivators) - { - _shader.SetModelView(Matrix4x4.CreateRotationX(item.RotationX) * - Matrix4x4.CreateRotationY(item.RotationY) * - Matrix4x4.CreateRotationZ(item.RotationZ) * - Matrix4x4.CreateScale(item.ScaleX, item.ScaleY, item.ScaleZ) * - Matrix4x4.CreateTranslation(item.PositionX, -item.PositionY, -item.PositionZ)); - pass.Apply(); - - var color = new xna.Color(1f, 0f, 0f, .5f); - var vertices = new PositionColoredTextured[] - { - new PositionColoredTextured(-1, -1, -1, 0, 0, 1f, 0f, 0f, 1f), - new PositionColoredTextured(+1, -1, -1, 0, 0, 1f, 0f, 0f, 1f), - new PositionColoredTextured(+1, +1, -1, 0, 0, 1f, 0f, 0f, 1f), - new PositionColoredTextured(-1, +1, -1, 0, 0, 1f, 0f, 0f, 1f), - new PositionColoredTextured(-1, -1, +1, 0, 0, 1f, 0f, 0f, 1f), - new PositionColoredTextured(+1, -1, +1, 0, 0, 1f, 0f, 0f, 1f), - new PositionColoredTextured(+1, +1, +1, 0, 0, 1f, 0f, 0f, 1f), - new PositionColoredTextured(-1, +1, +1, 0, 0, 1f, 0f, 0f, 1f), - }; - var indices = new int[] - { + if (CurrentSpawnPoint != null) + if (CurrentSpawnPoint != null) + { + foreach (var spawnPoint in CurrentSpawnPoint.SpawnPoints) + { + foreach (var entity in spawnPoint.Entities) + { + _shader.SetModelView(Matrix4x4.CreateRotationX(entity.RotationX) * + Matrix4x4.CreateRotationY(entity.RotationY) * + Matrix4x4.CreateRotationZ(entity.RotationZ) * + Matrix4x4.CreateTranslation(entity.PositionX, -entity.PositionY, -entity.PositionZ)); + RenderMeshNew(pass, CurrentSpawnPoint.ObjEntryCtrl[entity.ObjectId], true); + } + + _graphics.RasterizerState = new RasterizerState() + { + CullMode = CullMode.None + }; + _shader.SetRenderTexture(pass, _whiteTexture); + foreach (var item in spawnPoint.EventActivators) + { + _shader.SetModelView(Matrix4x4.CreateRotationX(item.RotationX) * + Matrix4x4.CreateRotationY(item.RotationY) * + Matrix4x4.CreateRotationZ(item.RotationZ) * + Matrix4x4.CreateScale(item.ScaleX, item.ScaleY, item.ScaleZ) * + Matrix4x4.CreateTranslation(item.PositionX, -item.PositionY, -item.PositionZ)); + pass.Apply(); + + var color = new xna.Color(1f, 0f, 0f, .5f); + //float opacity = 0.3f; + var opacity = (float)(EditorSettings.OpacityLevel); + var RedValue = (float)(EditorSettings.RedValue); + var GreenValue = (float)(EditorSettings.GreenValue); + var BlueValue = (float)(EditorSettings.BlueValue); + var opacityEntrance = (float)(EditorSettings.OpacityEntranceLevel); + var RedValueEntrance = (float)(EditorSettings.RedValueEntrance); + var GreenValueEntrance = (float)(EditorSettings.GreenValueEntrance); + var BlueValueEntrance = (float)(EditorSettings.BlueValueEntrance); + var vertices = new PositionColoredTextured[] + { + //Order of constructing vertices matters. It's currently constructed "Side to side", lets see if we can construct it "Front to back" + new PositionColoredTextured(-1, -1, -1, 0, 0, RedValue, GreenValue, BlueValue, opacity), //Vertex 1 (1-4 are the right-side vertices) + new PositionColoredTextured(+1, -1, -1, 0, 0, RedValueEntrance, GreenValueEntrance, BlueValueEntrance, opacityEntrance), //Vertex 2 (Good with Vertex 7) + new PositionColoredTextured(+1, +1, -1, 0, 0, RedValueEntrance, GreenValueEntrance, BlueValueEntrance, opacityEntrance), //Vertex 3 (Good with Vertex 7) + //(3-6 represent the bottom-left & top-right vertices, connecting.) + new PositionColoredTextured(-1, +1, -1, 0, 0, RedValue, GreenValue, BlueValue, opacity), //Vertex 4 + new PositionColoredTextured(-1, -1, +1, 0, 0, RedValue, GreenValue, BlueValue, opacity), //Vertex 5 (5-8 are the left-side vertices) + new PositionColoredTextured(+1, -1, +1, 0, 0, RedValueEntrance, GreenValueEntrance, BlueValueEntrance, opacityEntrance), //Vertex 6 Good with Vertex 7... + new PositionColoredTextured(+1, +1, +1, 0, 0, RedValueEntrance, GreenValueEntrance, BlueValueEntrance, opacityEntrance), //Vertex 7 + new PositionColoredTextured(-1, +1, +1, 0, 0, RedValue, GreenValue, BlueValue, opacity), //Vertex 8 + + }; + var indices = new int[] + { 0, 1, 3, 3, 1, 2, 1, 5, 2, 2, 5, 6, 5, 4, 6, 6, 4, 7, 4, 0, 7, 7, 0, 3, 3, 2, 7, 7, 2, 6, - 4, 5, 0, 0, 5, 1 - }; - _graphics.DrawUserIndexedPrimitives(PrimitiveType.TriangleList, vertices, 0, 8, indices, 0, 12, MeshLoader.PositionColoredTexturedVertexDeclaration); + 4, 5, 0, 0, 5, 1 + }; + _graphics.DrawUserIndexedPrimitives(PrimitiveType.TriangleList, vertices, 0, 8, indices, 0, 12, MeshLoader.PositionColoredTexturedVertexDeclaration); + } } - } } }); } diff --git a/OpenKh.Tools.Kh2MapStudio/ObjEntryController.cs b/OpenKh.Tools.Kh2MapStudio/ObjEntryController.cs index e4705fb80..14f72fb5e 100644 --- a/OpenKh.Tools.Kh2MapStudio/ObjEntryController.cs +++ b/OpenKh.Tools.Kh2MapStudio/ObjEntryController.cs @@ -49,52 +49,91 @@ public void Dispose() foreach (var texture in meshGroup.Value.Textures) texture.Dispose(); _meshGroups.Clear(); - } - - public string GetName(int objectId) => _objEntryLookupReversed[objectId]; - - public MeshGroup this[int objId] - { - get - { - if (_meshGroups.TryGetValue(objId, out var meshGroup)) - return meshGroup; - - var objEntryName = _objEntryLookupReversed[objId]; + } + + //Below: First tries to get the name from the specified index. + //If that fails, fall back to warning the user with the name. + public string GetName(int objectId) + { + if (_objEntryLookupReversed.TryGetValue(objectId, out var objEntryName)) + { + return objEntryName; + } + return "ENTITY ID NOT PRESENT IN OBJENTRY"; + } + + public MeshGroup this[int objId] + { + get + { + if (_meshGroups.TryGetValue(objId, out var meshGroup)) + return meshGroup; + + // Fix OBJIds being out of range by falling back onto a value of 1. + if (!_objEntryLookupReversed.ContainsKey(objId)) + { + objId = 1; // Default to 1 if out of range + } + + var objEntryName = _objEntryLookupReversed[objId]; + var baseModelPath = Path.Combine(_objPath, objEntryName); + var moddedFolderPath = Path.Combine(_objPath, "..", "mapstudio"); // Move up one level and then to 'mapstudio' + var moddedModelPath = Path.Combine(moddedFolderPath, objEntryName); + var baseModelFileName = baseModelPath + ".mdlx"; + var moddedModelFileName = moddedModelPath + ".mdlx"; + + // Determine the correct file path to load from, prioritizing modded path + var modelFileName = File.Exists(moddedModelFileName) ? moddedModelFileName : baseModelFileName; + + MeshGroup LoadMeshGroup(string fileName) + { + if (!File.Exists(fileName)) + return EmptyMeshGroup; + + var mdlxEntries = File.OpenRead(fileName).Using(Bar.Read); + var modelEntry = mdlxEntries.FirstOrDefault(x => x.Type == Bar.EntryType.Model); + if (modelEntry == null) + return EmptyMeshGroup; + + var model = Mdlx.Read(modelEntry.Stream); + ModelTexture textures = null; + + var textureEntry = mdlxEntries.FirstOrDefault(x => x.Type == Bar.EntryType.ModelTexture); + if (textureEntry != null) + textures = ModelTexture.Read(textureEntry.Stream); + + var modelMotion = MeshLoader.FromKH2(model); + modelMotion.ApplyMotion(modelMotion.InitialPose); + return new MeshGroup + { + MeshDescriptors = modelMotion.MeshDescriptors, + Textures = textures == null ? new IKingdomTexture[0] : textures.LoadTextures(_graphics).ToArray() + }; + } + + meshGroup = LoadMeshGroup(modelFileName); + + // Check if model or texture is missing and load from fallback if necessary. Loads a simple pyramid model. + if (meshGroup == EmptyMeshGroup || meshGroup.Textures.Length == 0) + { + var fallbackModelFileName = Path.Combine(_objPath, "F_HB700.mdlx"); + var fallbackMeshGroup = LoadMeshGroup(fallbackModelFileName); + + if (meshGroup == EmptyMeshGroup) + { + meshGroup = fallbackMeshGroup; + } + else if (meshGroup.Textures.Length == 0) + { + meshGroup.Textures = fallbackMeshGroup.Textures; + } + } + + _meshGroups[objId] = meshGroup; + return meshGroup; + } + } - var modelPath = Path.Combine(_objPath, objEntryName); - var modelFileName = modelPath + ".mdlx"; - if (File.Exists(modelFileName)) - { - var mdlxEntries = File.OpenRead(modelFileName).Using(Bar.Read); - var modelEntry = mdlxEntries.FirstOrDefault(x => x.Type == Bar.EntryType.Model); - if (modelEntry != null) - { - var model = Mdlx.Read(modelEntry.Stream); - ModelTexture textures = null; - - var textureEntry = mdlxEntries.FirstOrDefault(x => x.Type == Bar.EntryType.ModelTexture); - if (textureEntry != null) - textures = ModelTexture.Read(textureEntry.Stream); - - var modelMotion = MeshLoader.FromKH2(model); - modelMotion.ApplyMotion(modelMotion.InitialPose); - meshGroup = new MeshGroup - { - MeshDescriptors = modelMotion.MeshDescriptors, - Textures = textures == null ? new IKingdomTexture[0] : textures.LoadTextures(_graphics).ToArray() - }; - } - else - meshGroup = EmptyMeshGroup; - } - else - meshGroup = EmptyMeshGroup; - - _meshGroups[objId] = meshGroup; - return meshGroup; - } - } public MeshGroup this[string objName] => this[_objEntryLookup[objName]]; } diff --git a/OpenKh.Tools.Kh2MapStudio/OpenKh.Tools.Kh2MapStudio.csproj b/OpenKh.Tools.Kh2MapStudio/OpenKh.Tools.Kh2MapStudio.csproj index ac6eb6c66..01037d96d 100644 --- a/OpenKh.Tools.Kh2MapStudio/OpenKh.Tools.Kh2MapStudio.csproj +++ b/OpenKh.Tools.Kh2MapStudio/OpenKh.Tools.Kh2MapStudio.csproj @@ -23,7 +23,7 @@ - + diff --git a/OpenKh.Tools.Kh2MapStudio/Program.cs b/OpenKh.Tools.Kh2MapStudio/Program.cs index 4c5d975ea..836b9e68b 100644 --- a/OpenKh.Tools.Kh2MapStudio/Program.cs +++ b/OpenKh.Tools.Kh2MapStudio/Program.cs @@ -29,8 +29,10 @@ private static string GetVersion() public string GamePath { get; } #endregion - const int InitialWindowWidth = 1000; - const int InitialWindowHeight = 800; + //New variables for being able to change the default Window Height & Width + readonly int InitialWindowWidth = (int)(EditorSettings.InitialWindowWidth); + readonly int InitialWindowHeight = (int)(EditorSettings.InitialWindowHeight); + // private readonly MonoGameImGuiBootstrap _bootstrap; private App _app; diff --git a/OpenKh.Tools.Kh2MapStudio/Settings.Designer.cs b/OpenKh.Tools.Kh2MapStudio/Settings.Designer.cs index 72c1dddaf..02bbee9b1 100644 --- a/OpenKh.Tools.Kh2MapStudio/Settings.Designer.cs +++ b/OpenKh.Tools.Kh2MapStudio/Settings.Designer.cs @@ -1,4 +1,4 @@ -//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ // // This code was generated by a tool. // Runtime Version:4.0.30319.42000 @@ -12,7 +12,7 @@ namespace OpenKh.Tools.Kh2MapStudio { [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "17.9.0.0")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "17.4.0.0")] internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); @@ -94,7 +94,22 @@ public bool ViewMeshGroup { this["ViewMeshGroup"] = value; } } - + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("False")] + public bool ViewCollision + { + get + { + return ((bool)(this["ViewCollision"])); + } + set + { + this["ViewCollision"] = value; + } + } + [global::System.Configuration.UserScopedSettingAttribute()] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Configuration.DefaultSettingValueAttribute("False")] @@ -166,5 +181,137 @@ public bool ViewEventScript { this["ViewEventScript"] = value; } } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("0.3")] + public float OpacityLevel { + get { + return ((float)(this["OpacityLevel"])); + } + set { + this["OpacityLevel"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("1")] + public float RedValue { + get { + return ((float)(this["RedValue"])); + } + set { + this["RedValue"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("0")] + public float GreenValue { + get { + return ((float)(this["GreenValue"])); + } + set { + this["GreenValue"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("0")] + public float BlueValue { + get { + return ((float)(this["BlueValue"])); + } + set { + this["BlueValue"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("0.3")] + public float OpacityEntranceLevel { + get { + return ((float)(this["OpacityEntranceLevel"])); + } + set { + this["OpacityEntranceLevel"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("1")] + public float RedValueEntrance { + get { + return ((float)(this["RedValueEntrance"])); + } + set { + this["RedValueEntrance"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("0")] + public float GreenValueEntrance { + get { + return ((float)(this["GreenValueEntrance"])); + } + set { + this["GreenValueEntrance"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("0")] + public float BlueValueEntrance { + get { + return ((float)(this["BlueValueEntrance"])); + } + set { + this["BlueValueEntrance"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("False")] + public bool SeparateCamera { + get { + return ((bool)(this["SeparateCamera"])); + } + set { + this["SeparateCamera"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("1000")] + public int InitialWindowWidth { + get { + return ((int)(this["InitialWindowWidth"])); + } + set { + this["InitialWindowWidth"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("800")] + public int InitialWindowHeight { + get { + return ((int)(this["InitialWindowHeight"])); + } + set { + this["InitialWindowHeight"] = value; + } + } } } diff --git a/OpenKh.Tools.Kh2MapStudio/Settings.settings b/OpenKh.Tools.Kh2MapStudio/Settings.settings index 4ed7d5d9c..15257201d 100644 --- a/OpenKh.Tools.Kh2MapStudio/Settings.settings +++ b/OpenKh.Tools.Kh2MapStudio/Settings.settings @@ -38,5 +38,38 @@ False + + 0.3 + + + 1 + + + 0 + + + 0 + + + 0.3 + + + 1 + + + 0 + + + 0 + + + False + + + 1000 + + + 800 + \ No newline at end of file diff --git a/OpenKh.Tools.Kh2MapStudio/Windows/BobDescriptorWindow.cs b/OpenKh.Tools.Kh2MapStudio/Windows/BobDescriptorWindow.cs index 3d321c0f8..3663ea481 100644 --- a/OpenKh.Tools.Kh2MapStudio/Windows/BobDescriptorWindow.cs +++ b/OpenKh.Tools.Kh2MapStudio/Windows/BobDescriptorWindow.cs @@ -1,89 +1,96 @@ -using ImGuiNET; -using Microsoft.Xna.Framework; -using OpenKh.Kh2.Models; -using System; -using System.Collections.Generic; -using static OpenKh.Tools.Common.CustomImGui.ImGuiEx; -using static OpenKh.Tools.Kh2MapStudio.ImGuiExHelpers; - -namespace OpenKh.Tools.Kh2MapStudio.Windows -{ - public class BobDescriptorWindow - { - public static bool Run(List bobDescs, int bobCount) => ForHeader("BOB descriptors", () => - { - var bobToRemove = -1; - for (int i = 0; i < bobDescs.Count; i++) - { - var desc = bobDescs[i]; - if (ImGui.CollapsingHeader($"BOB descriptor##{i}")) - { - if (ImGui.BeginCombo("BOB model", bobCount > 0 ? - $"BOB #{desc.BobIndex}" : "No bob exists in this map")) - { - for (var j = 0; j < bobCount; j++) - { - if (ImGui.Selectable($"BOB #{j}", j == desc.BobIndex)) - desc.BobIndex = j; - } - ImGui.EndCombo(); - } - - ForEdit3("Position", - () => new Vector3(desc.PositionX, desc.PositionY, desc.PositionZ), - x => - { - desc.PositionX = x.X; - desc.PositionY = x.Y; - desc.PositionZ = x.Z; - }, 10f); - ForEdit3("Rotation", - () => new Vector3( - (float)(desc.RotationX * 180.0 / Math.PI), - (float)(desc.RotationY * 180.0 / Math.PI), - (float)(desc.RotationZ * 180.0 / Math.PI)), - x => - { - desc.RotationX = (float)(x.X * Math.PI / 180.0); - desc.RotationY = (float)(x.Y * Math.PI / 180.0); - desc.RotationZ = (float)(x.Z * Math.PI / 180.0); - }); - ForEdit3("Scaling", - () => new Vector3(desc.ScalingX, desc.ScalingY, desc.ScalingZ), - x => - { - desc.ScalingX = x.X; - desc.ScalingY = x.Y; - desc.ScalingZ = x.Z; - }, 0.01f); - - ForEdit("Unk28", () => desc.Unknown28, x => desc.Unknown28 = x); - ForEdit("Unk2c", () => desc.Unknown2c, x => desc.Unknown2c = x); - ForEdit("Unk30", () => desc.Unknown30, x => desc.Unknown30 = x); - ForEdit("Unk34", () => desc.Unknown34, x => desc.Unknown34 = x); - ForEdit("Unk38", () => desc.Unknown38, x => desc.Unknown38 = x); - ForEdit("Unk3c", () => desc.Unknown3c, x => desc.Unknown3c = x); - ForEdit("Unk40", () => desc.Unknown40, x => desc.Unknown40 = x); - ForEdit("Unk44", () => desc.Unknown44, x => desc.Unknown44 = x); - ForEdit("Unk48", () => desc.Unknown48, x => desc.Unknown48 = x); - ForEdit("Unk4c", () => desc.Unknown4c, x => desc.Unknown4c = x); - ForEdit("Unk50", () => desc.Unknown50, x => desc.Unknown50 = x); - ForEdit("Unk54", () => desc.Unknown54, x => desc.Unknown54 = x); - ForEdit("Unk58", () => desc.Unknown58, x => desc.Unknown58 = x); - ForEdit("Unk5c", () => desc.Unknown5c, x => desc.Unknown5c = x); - ForEdit("Unk60", () => desc.Unknown60, x => desc.Unknown60 = x); - ForEdit("Unk64", () => desc.Unknown64, x => desc.Unknown64 = x); - - if (ImGui.SmallButton("Remove this BOB descriptor")) - bobToRemove = i; - } - } - - if (bobToRemove >= 0) - bobDescs.RemoveAt(bobToRemove); - - if (ImGui.SmallButton("Add a new BOB descriptor")) - bobDescs.Add(new BobDescriptor()); - }); - } -} +using ImGuiNET; +using Microsoft.Xna.Framework; +using OpenKh.Kh2.Models; +using System; +using System.Collections.Generic; +using static OpenKh.Tools.Common.CustomImGui.ImGuiEx; +using static OpenKh.Tools.Kh2MapStudio.ImGuiExHelpers; + +namespace OpenKh.Tools.Kh2MapStudio.Windows +{ + public class BobDescriptorWindow + { + public static bool Run(List bobDescs, int bobCount) => ForHeader("BOB descriptors", () => + { + var bobToRemove = -1; + if (ImGui.SmallButton("Add a new BOB descriptor")) + bobDescs.Add(new BobDescriptor()); + for (int i = 0; i < bobDescs.Count; i++) + { + var desc = bobDescs[i]; + ImGui.Indent(20.0f); + var headerLabel = $"BOB descriptor #{i}"; + if (desc.BobIndex >= 0 && desc.BobIndex < bobCount) + { + headerLabel += $" (Model #{desc.BobIndex})"; + } + ImGui.PushID(i); + if (ImGui.CollapsingHeader(headerLabel)) + { + if (ImGui.SmallButton("Remove this BOB descriptor")) + bobToRemove = i; + if (ImGui.BeginCombo("BOB model", bobCount > 0 ? + $"BOB #{desc.BobIndex}" : "No bob exists in this map")) + { + for (var j = 0; j < bobCount; j++) + { + if (ImGui.Selectable($"BOB #{j}", j == desc.BobIndex)) + desc.BobIndex = j; + } + ImGui.EndCombo(); + } + + ForEdit3("Position", + () => new Vector3(desc.PositionX, desc.PositionY, desc.PositionZ), + x => + { + desc.PositionX = x.X; + desc.PositionY = x.Y; + desc.PositionZ = x.Z; + }, 10f); + ForEdit3("Rotation", + () => new Vector3( + (float)(desc.RotationX * 180.0 / Math.PI), + (float)(desc.RotationY * 180.0 / Math.PI), + (float)(desc.RotationZ * 180.0 / Math.PI)), + x => + { + desc.RotationX = (float)(x.X * Math.PI / 180.0); + desc.RotationY = (float)(x.Y * Math.PI / 180.0); + desc.RotationZ = (float)(x.Z * Math.PI / 180.0); + }); + ForEdit3("Scaling", + () => new Vector3(desc.ScalingX, desc.ScalingY, desc.ScalingZ), + x => + { + desc.ScalingX = x.X; + desc.ScalingY = x.Y; + desc.ScalingZ = x.Z; + }, 0.01f); + + ForEdit("Unk28", () => desc.Unknown28, x => desc.Unknown28 = x); + ForEdit("Unk2c", () => desc.Unknown2c, x => desc.Unknown2c = x); + ForEdit("Unk30", () => desc.Unknown30, x => desc.Unknown30 = x); + ForEdit("Unk34", () => desc.Unknown34, x => desc.Unknown34 = x); + ForEdit("Unk38", () => desc.Unknown38, x => desc.Unknown38 = x); + ForEdit("Unk3c", () => desc.Unknown3c, x => desc.Unknown3c = x); + ForEdit("Unk40", () => desc.Unknown40, x => desc.Unknown40 = x); + ForEdit("Unk44", () => desc.Unknown44, x => desc.Unknown44 = x); + ForEdit("Unk48", () => desc.Unknown48, x => desc.Unknown48 = x); + ForEdit("Unk4c", () => desc.Unknown4c, x => desc.Unknown4c = x); + ForEdit("Unk50", () => desc.Unknown50, x => desc.Unknown50 = x); + ForEdit("Unk54", () => desc.Unknown54, x => desc.Unknown54 = x); + ForEdit("Unk58", () => desc.Unknown58, x => desc.Unknown58 = x); + ForEdit("Unk5c", () => desc.Unknown5c, x => desc.Unknown5c = x); + ForEdit("Unk60", () => desc.Unknown60, x => desc.Unknown60 = x); + ForEdit("Unk64", () => desc.Unknown64, x => desc.Unknown64 = x); + } + ImGui.PopID(); + ImGui.Unindent(20.0f); + } + + if (bobToRemove >= 0) + bobDescs.RemoveAt(bobToRemove); + }); + } +} diff --git a/OpenKh.Tools.Kh2MapStudio/Windows/CameraWindow.cs b/OpenKh.Tools.Kh2MapStudio/Windows/CameraWindow.cs index a0f7bef67..e68e1db13 100644 --- a/OpenKh.Tools.Kh2MapStudio/Windows/CameraWindow.cs +++ b/OpenKh.Tools.Kh2MapStudio/Windows/CameraWindow.cs @@ -15,5 +15,16 @@ public static bool Run(Camera camera) => ForHeader("Camera", () => x => camera.CameraRotationYawPitchRoll = new Vector3( -x.X, camera.CameraRotationYawPitchRoll.Y, -x.Y)); }); + } + static class SeparateWindow + { + public static bool Run(Camera camera) => ForWindow("Camera", () => + { + ForEdit3("Position", () => camera.CameraPosition, x => camera.CameraPosition = x); + ForEdit2("Rotation", + () => new Vector2(-camera.CameraRotationYawPitchRoll.X, -camera.CameraRotationYawPitchRoll.Z), + x => camera.CameraRotationYawPitchRoll = new Vector3( + -x.X, camera.CameraRotationYawPitchRoll.Y, -x.Y)); + }); } } diff --git a/OpenKh.Tools.Kh2MapStudio/Windows/CollisionWindow.cs b/OpenKh.Tools.Kh2MapStudio/Windows/CollisionWindow.cs index 4a3b2df2a..0f2d63d69 100644 --- a/OpenKh.Tools.Kh2MapStudio/Windows/CollisionWindow.cs +++ b/OpenKh.Tools.Kh2MapStudio/Windows/CollisionWindow.cs @@ -1,10 +1,14 @@ using ImGuiNET; +using OpenKh.Engine.MonoGame; using OpenKh.Kh2; using static OpenKh.Tools.Common.CustomImGui.ImGuiEx; - +using OpenKh.Kh2.Utils; +using System.Numerics; + +//Partially implemented. Allows you to view the data, namespace OpenKh.Tools.Kh2MapStudio.Windows -{ - static class CollisionWindow +{ + class CollisionWindow { public static void Run(Coct coct) => ForHeader("Collision", () => { @@ -17,8 +21,11 @@ private static void Node(Coct coct, int index) return; ForTreeNode($"Node {index}", () => { - var node = coct.Nodes[index]; - ImGui.Text($"Box {node.BoundingBox}"); + var node = coct.Nodes[index]; + //ImGui.Text($"Box {node.BoundingBox}"); + BoundingBoxInt16 boundingBoxCopy = node.BoundingBox; + ImGuiCollHelper.EditBoundingBox("Bounding Box (Node)", ref boundingBoxCopy); + node.BoundingBox = boundingBoxCopy; // Set the modified bounding box back Node(coct, node.Child1); Node(coct, node.Child2); Node(coct, node.Child3); @@ -31,25 +38,86 @@ private static void Node(Coct coct, int index) foreach (var mesh in node.Meshes) { ForTreeNode($"Mesh {mesh.GetHashCode()}", () => - { - ImGui.Text($"Box {mesh.BoundingBox}"); - ImGui.Text($"Visibility {mesh.Visibility}"); - ImGui.Text($"Group {mesh.Group}"); - + { + ForEdit("Visibility", () => mesh.Visibility, x => mesh.Visibility = x); + ForEdit("Group", () => mesh.Group, x => mesh.Group = x); + BoundingBoxInt16 boundingBoxCopy = mesh.BoundingBox; + ImGuiCollHelper.EditBoundingBox("Bounding Box (Mesh)", ref boundingBoxCopy); + mesh.BoundingBox = boundingBoxCopy; // Set the modified bounding box back + //ImGui.Text($"Box {mesh.BoundingBox}"); + //ImGui.Text($"Visibility {mesh.Visibility}"); + //ImGui.Text($"Group {mesh.Group}"); foreach (var collision in mesh.Collisions) { ForTreeNode($"Collision {collision.GetHashCode()}", () => { - ImGui.Text($"Ground {collision.Ground}"); - ImGui.Text($"Floor Level {collision.FloorLevel}"); - ImGui.Text($"Plane {collision.Plane}"); - ImGui.Text($"Bound Box {collision.BoundingBox}"); - ImGui.Text($"Flags {collision.Attributes:X08}"); + ForEdit("Ground", () => collision.Ground, x => collision.Ground = x); + ForEdit("Floor Level", () => collision.FloorLevel, x => collision.FloorLevel = x); + //ImGui.Text($"Plane {collision.Plane}"); + //ImGui.Text($"Bound Box {collision.BoundingBox}"); + ForEdit("Flags", () => collision.Attributes.Flags, x => collision.Attributes.Flags = x); + BoundingBoxInt16 boundingBoxCopy = collision.BoundingBox; + ImGuiCollHelper.EditBoundingBox("Bounding Box (Collision)", ref boundingBoxCopy); + collision.BoundingBox = boundingBoxCopy; // Set the modified bounding box back + //ForEdit("UV Scroll Index", () => vifPacket.UVScrollIndex, x => vifPacket.UVScrollIndex = x); + Plane plane = collision.Plane; + ImGuiCollHelper.EditPlane(ref plane); + collision.Plane = plane; // Update the collision plane if it was changed + //ImGui.Text($"Flags {collision.Attributes:X08}"); }); } }); } }); + } + //Helper for Bounding Box + public static class ImGuiCollHelper + { + public static void EditBoundingBox(string label, ref BoundingBoxInt16 boundingBox) + { + if (ImGui.TreeNode(label)) + { + Vector3Int16 min = boundingBox.Minimum; + Vector3Int16 max = boundingBox.Maximum; + + EditVector3Int16("Minimum", ref min); + EditVector3Int16("Maximum", ref max); + + boundingBox = new BoundingBoxInt16(min, max); + + ImGui.TreePop(); + } + } + + private static void EditVector3Int16(string label, ref Vector3Int16 vector) + { + int x = vector.X; + int y = vector.Y; + int z = vector.Z; + + if (ImGui.DragInt(label + " X", ref x)) + vector.X = (short)x; + if (ImGui.DragInt(label + " Y", ref y)) + vector.Y = (short)y; + if (ImGui.DragInt(label + " Z", ref z)) + vector.Z = (short)z; + } + + public static void EditPlane(ref Plane plane) + { + Vector3 normal = plane.Normal; + float d = plane.D; + if (ImGui.TreeNode("Plane")) + { + //ImGui.Text("Plane:"); + ImGui.DragFloat("X", ref normal.X); + ImGui.DragFloat("Y", ref normal.Y); + ImGui.DragFloat("Z", ref normal.Z); + ImGui.DragFloat("D", ref d); + plane = new Plane(normal, d); + ImGui.TreePop(); + } + } } } } diff --git a/OpenKh.Tools.Kh2MapStudio/Windows/MeshGroupWindow.cs b/OpenKh.Tools.Kh2MapStudio/Windows/MeshGroupWindow.cs index 958bf8213..419777543 100644 --- a/OpenKh.Tools.Kh2MapStudio/Windows/MeshGroupWindow.cs +++ b/OpenKh.Tools.Kh2MapStudio/Windows/MeshGroupWindow.cs @@ -34,13 +34,19 @@ private static void MeshGroup(MeshGroupModel meshGroup, int index) for (var i = 0; i < meshGroup.Map.vifPacketRenderingGroup.Count; i++) { ForTreeNode($"Mesh Rendering Group {i}##{index}", () => - { + { + // if (ImGui.SmallButton("Apply changes")) //Adds the button ONLY for the entire group. + // meshGroup.Invalidate(); var group = meshGroup.Map.vifPacketRenderingGroup[i]; for (var j = 0; j < group.Length; j++) { + //if (ImGui.SmallButton("Apply changes")) //Adds the button for EACH group. + // meshGroup.Invalidate(); var meshIndex = group[j]; ForTreeNode($"Index {j}, Mesh {meshIndex}##{index}", () => { + if (ImGui.SmallButton("Apply changes")) //Adds the button for EACH group. + meshGroup.Invalidate(); var vifPacket = meshGroup.Map.Chunks[meshIndex]; ForEdit("Texture", () => vifPacket.TextureId, @@ -59,6 +65,7 @@ private static void MeshGroup(MeshGroupModel meshGroup, int index) ForEdit("Priority", () => vifPacket.Priority, x => vifPacket.Priority = x); ForEdit("Draw priority", () => vifPacket.DrawPriority, x => vifPacket.DrawPriority = x); ForEdit("Alpha flag", () => vifPacket.TransparencyFlag, x => vifPacket.TransparencyFlag = x); + ForEdit("UV Scroll Index", () => vifPacket.UVScrollIndex, x => vifPacket.UVScrollIndex = x); ImGui.Text("DMA per VIF dump:"); ImGui.Text(string.Join(",", vifPacket.DmaPerVif.Select(x => $"{x}"))); }); diff --git a/OpenKh.Tools.Kh2MapStudio/Windows/SpawnPointWindow.cs b/OpenKh.Tools.Kh2MapStudio/Windows/SpawnPointWindow.cs index 8ad881af5..26d2c2dac 100644 --- a/OpenKh.Tools.Kh2MapStudio/Windows/SpawnPointWindow.cs +++ b/OpenKh.Tools.Kh2MapStudio/Windows/SpawnPointWindow.cs @@ -1,48 +1,243 @@ using ImGuiNET; +using OpenKh.Common; //New +using Xe.Tools.Wpf.Dialogs; //New using OpenKh.Kh2.Ard; using OpenKh.Tools.Kh2MapStudio.Interfaces; using OpenKh.Tools.Kh2MapStudio.Models; +using System.IO; //New using System; +using System.Collections.Generic; using System.Linq; -using System.Numerics; +using System.Numerics; +using static OpenKh.Kh2.Ard.SpawnPoint; //Newly added, for WalkPath Addition. using static OpenKh.Tools.Common.CustomImGui.ImGuiEx; - +using OpenKh.Kh2; + namespace OpenKh.Tools.Kh2MapStudio.Windows { static class SpawnPointWindow { private static string ObjectFilter = ""; - private static ISpawnPointController _ctrl; + private static ISpawnPointController _ctrl; + private static string _newSpawnPointName = "N_00"; // Default name for the new spawn point + + public static bool Run(ISpawnPointController ctrl) => ForHeader("Spawn point editor", () => + { + _ctrl = ctrl; + + // Check list of spawn points is null or empty + if (ctrl.SpawnPoints != null && ctrl.SpawnPoints.Any()) + { + ImGui.PushItemWidth(200); // Adjust width as needed + if (ImGui.BeginCombo(" ", ctrl.SelectSpawnPoint)) + { + foreach (var spawnPoint in ctrl.SpawnPoints) + { + if (ImGui.Selectable(spawnPoint.Name, spawnPoint.Name == ctrl.SelectSpawnPoint)) + { + ctrl.SelectSpawnPoint = spawnPoint.Name; + } + } + ImGui.EndCombo(); + } + // Add a button to save the file on the same line as the dropdown. + ImGui.SameLine(); + if (ImGui.Button("Save Spawnpoint as YML")) + { + SaveSpawnPointAsYaml(ctrl); + } + ImGui.PushItemWidth(50); + ImGui.InputText("", ref _newSpawnPointName, 4); // String of 4 characters long. + ImGui.PopItemWidth(); + ImGui.SameLine(); // Place the button on the same line as the text input + // Add a button to add a new spawn point entry + + //Rename/Add/Remove SpawnPoint butons. + if (ImGui.Button($"Rename '{ctrl.SelectSpawnPoint}' to '{_newSpawnPointName}'")) + { + RenameSpawnPoint(ctrl); + } + ImGui.SameLine(); + if (ImGui.Button($"Add new Spawnpoint '{_newSpawnPointName}'")) + { + AddNewSpawnPointEntry(ctrl); + } + ImGui.SameLine(); + if (ImGui.Button($"Remove '{ctrl.SelectSpawnPoint}'")) + { + RemoveSelectedSpawnPoint(ctrl); + } + ImGui.PopItemWidth(); + } + else + { + ImGui.Text("No spawn points available."); + } + if (ctrl.CurrentSpawnPoint != null) + Run(ctrl.CurrentSpawnPoint); + }); + private static void RenameSpawnPoint(ISpawnPointController ctrl) + { + var selectedSpawnPoint = ctrl.SpawnPoints.FirstOrDefault(sp => sp.Name == ctrl.SelectSpawnPoint); + if (selectedSpawnPoint != null) + { + // Check if the new name already exists + if (ctrl.SpawnPoints.Any(sp => sp.Name == _newSpawnPointName)) + { + //Insert warning here. Standard error msg seems to pop up and disappear immediately, omitted for now. + return; + } + + // Update the name of the selected spawn point + var oldName = selectedSpawnPoint.Name; + selectedSpawnPoint.Name = _newSpawnPointName; + ctrl.SelectSpawnPoint = _newSpawnPointName; + + // Rename the corresponding Bar.Entry as well + var barEntries = (ctrl as MapRenderer)?.ArdBarEntries; + var barEntry = barEntries?.FirstOrDefault(e => e.Name == oldName); + if (barEntry != null) + { + barEntry.Name = _newSpawnPointName; + } + } + } + + //Add a new spawn point entry + private static void AddNewSpawnPointEntry(ISpawnPointController ctrl) + { + // Use the specified name for the new spawn point + var newSpawnPointName = string.IsNullOrEmpty(_newSpawnPointName) ? "N_00" : _newSpawnPointName; + + // Check if the name already exists in the ARD + var barEntries = (ctrl as MapRenderer)?.ArdBarEntries; + if (barEntries != null && barEntries.Any(e => e.Name == newSpawnPointName)) + { + Console.WriteLine($"Spawn point with name {newSpawnPointName} already exists."); + return; + } + + // Create a new Bar.Entry for AreaDataSpawn + var newEntry = new Bar.Entry + { + Name = newSpawnPointName, + Type = Bar.EntryType.AreaDataSpawn, + Stream = new MemoryStream() + }; + + // Add to the Bar + if (barEntries != null) + { + barEntries.Add(newEntry); + } + + // Create and add a new spawn point model + var objEntryCtrl = (ctrl as MapRenderer)?.ObjEntryController; // Ensure the correct type cast + if (objEntryCtrl != null) + { + var newSpawnPoint = new SpawnPointModel(objEntryCtrl, newEntry.Name, new List()); + ctrl.SpawnPoints.Add(newSpawnPoint); + ctrl.SelectSpawnPoint = newSpawnPoint.Name; + } + } + + //Remove the currently selected spawn point entry + private static void RemoveSelectedSpawnPoint(ISpawnPointController ctrl) + { + if (ctrl.SelectSpawnPoint == null) + { + Console.WriteLine("No spawn point selected to remove."); + return; + } + + var barEntries = (ctrl as MapRenderer)?.ArdBarEntries; + if (barEntries == null) + { + Console.WriteLine("Bar entries not found."); + return; + } + + // Find the entry with the selected name and remove it + var entryToRemove = barEntries.FirstOrDefault(e => e.Name == ctrl.SelectSpawnPoint); + if (entryToRemove != null) + { + barEntries.Remove(entryToRemove); + ctrl.SpawnPoints.RemoveAll(sp => sp.Name == ctrl.SelectSpawnPoint); + ctrl.SelectSpawnPoint = ctrl.SpawnPoints.FirstOrDefault()?.Name; + Console.WriteLine($"Spawn point {entryToRemove.Name} removed."); + } + else + { + Console.WriteLine($"Spawn point with name {ctrl.SelectSpawnPoint} not found in bar entries."); + } + } + + + + //NEW: Easily export spawnpoint as a YML. QoL change to make patching via OpenKH easier, so you don't need to extract the spawnpoint, use OpenKh.Command.Spawnpoints, decompile, etc. + private static readonly List YamlFilter = FileDialogFilterComposer.Compose() + .AddExtensions("YAML file", "yml") + .AddAllFiles(); + + private static void SaveSpawnPointAsYaml(ISpawnPointController ctrl) + { + // Serialize the currently loaded SpawnPoint to YAML + var spawnPoint = ctrl.CurrentSpawnPoint.SpawnPoints; + if (spawnPoint != null) + { + var defaultName = $"{ctrl.CurrentSpawnPoint.Name}.yml"; // Set the default name to the current spawn point's name + Xe.Tools.Wpf.Dialogs.FileDialog.OnSave(savePath => + { + try + { + // Serialize and save the spawn point data to the selected file + File.WriteAllText(savePath, Helpers.YamlSerialize(spawnPoint)); + Console.WriteLine($"Spawn point saved to {savePath}"); + } + catch (Exception ex) + { + // Handle exceptions if needed + Console.WriteLine($"Error saving spawn point as YAML: {ex.Message}"); + } + }, YamlFilter, defaultName); + } + } - public static bool Run(ISpawnPointController ctrl) => ForHeader("Spawn point editor", () => - { - _ctrl = ctrl; - if (ImGui.BeginCombo("Spawn point", ctrl.SelectSpawnPoint)) + private static void Run(SpawnPointModel model) + { + if (ImGui.SmallButton("Add a new Spawn Group")) { - foreach (var spawnPoint in ctrl.SpawnPoints) - { - if (ImGui.Selectable(spawnPoint.Name, spawnPoint.Name == ctrl.SelectSpawnPoint)) - { - ctrl.SelectSpawnPoint = spawnPoint.Name; - } - } - - ImGui.EndCombo(); - } - - if (ctrl.CurrentSpawnPoint != null) - Run(ctrl.CurrentSpawnPoint); - }); + var newSpawnPoint = new Kh2.Ard.SpawnPoint + { + Entities = new List { }, + EventActivators = new List { }, + WalkPath = new List { }, + ReturnParameters = new List { }, + Signals = new List { }, + Teleport = new Kh2.Ard.SpawnPoint.TeleportDesc { } + }; + model.SpawnPoints.Add(newSpawnPoint); + } - private static void Run(SpawnPointModel model) - { - for (int i = 0; i < model.SpawnPoints.Count; i++) + ImGui.SameLine(); + //The above code copies the last spawngroup. + if (model.SpawnPoints.Count > 0) + if (ImGui.SmallButton("Remove last Spawn Group")) + model.SpawnPoints.RemoveAt(model.SpawnPoints.Count - 1); + ImGui.Separator(); + //Tree View of Spawn Groups + if (ImGui.CollapsingHeader("Spawn Groups")) { - var spawnGroup = model.SpawnPoints[i]; - if (ImGui.CollapsingHeader($"Spawn group #{i}")) + ImGui.Indent(20.0f); + for (int i = 0; i < model.SpawnPoints.Count; i++) { - Run(spawnGroup, i); + var spawnGroup = model.SpawnPoints[i]; + if (ImGui.CollapsingHeader($"Spawn group #{i}")) + { + Run(spawnGroup, i); + } } + ImGui.Unindent(20.0f); } } @@ -62,19 +257,74 @@ private static void Run(SpawnPoint point, int index) ForEdit($"Unknown##{index}", () => point.Teleport.Unknown, x => point.Teleport.Unknown = x); ImGui.Separator(); - for (var i = 0; i < point.Entities.Count; i++) - ForTreeNode($"Entity #{index}-{i}", () => Run(point.Entities[i], i)); - for (var i = 0; i < point.EventActivators.Count; i++) + //Entities + if (ImGui.SmallButton("Add a new Entity")) + point.Entities.Add(new SpawnPoint.Entity {ObjectId = 1 }); //Add ObjectId of 1. + ImGui.SameLine(); + if (point.Entities.Count > 0) //Check first to see if there are entities. + if (ImGui.SmallButton("Remove last Entity")) //Placed BEFORE, so that button doesnt repeat 3x. + point.Entities.RemoveAt(point.Entities.Count - 1); + ImGui.Separator(); + for (var i = 0; i < point.Entities.Count; i++) //Entities: Tree View + ForTreeNode($"Entity #{index}-{i}", () => Run(point.Entities[i], i)); + + //EventActivators, then Add/Remove Buttons. + if (ImGui.SmallButton("Add a new Event Activator")) + point.EventActivators.Add(new SpawnPoint.EventActivator()); + ImGui.SameLine(); + if (point.EventActivators.Count > 0) + if (ImGui.SmallButton("Remove last Activator")) + point.EventActivators.RemoveAt(point.EventActivators.Count - 1); + ImGui.Separator(); + for (var i = 0; i < point.EventActivators.Count; i++) //Event Activators: Tree View ForTreeNode($"Event activator #{index}-{i}", () => Run(point.EventActivators[i], i)); - for (var i = 0; i < point.WalkPath.Count; i++) + + //WalkPath, then Add/Remove Buttons. + if (ImGui.SmallButton("Add a new Walking Path")) + { + var newWalkPath = new SpawnPoint.WalkPathDesc(); + + // Ensure Position has at least one value + if (newWalkPath.Positions == null || newWalkPath.Positions.Count == 0) + { + newWalkPath.Positions = new List(); + } + point.WalkPath.Add(newWalkPath); + } + ImGui.SameLine(); + if (point.WalkPath.Count > 0) + if (ImGui.SmallButton("Remove last Walking Path")) + point.WalkPath.RemoveAt(point.WalkPath.Count - 1); + ImGui.Separator(); + for (var i = 0; i < point.WalkPath.Count; i++)//WalkPath: Tree View ForTreeNode($"Walking path #{index}-{i}", () => Run(point.WalkPath[i], i)); - for (var i = 0; i < point.ReturnParameters.Count; i++) + + //Return Parameters, then Add/Remove Buttons. + //Remove/Add + if (ImGui.SmallButton("Add a new Return Parameter")) + point.ReturnParameters.Add(new SpawnPoint.ReturnParameter()); + ImGui.SameLine(); + if (point.ReturnParameters.Count > 0) + if (ImGui.SmallButton("Remove last Return Parameter")) + point.ReturnParameters.RemoveAt(point.ReturnParameters.Count - 1); + ImGui.Separator(); + for (var i = 0; i < point.ReturnParameters.Count; i++)//Return parameters: Tree View ForTreeNode($"Parameter #{index}-{i}", () => Run(point.ReturnParameters[i], i)); - for (var i = 0; i < point.Signals.Count; i++) + + //Signal, then Add/Remove Buttons. + //Remove/Add + if (ImGui.SmallButton("Add a new Signal")) + point.Signals.Add(new SpawnPoint.Signal()); + ImGui.SameLine(); + if (point.Signals.Count > 0) + if (ImGui.SmallButton("Remove last Signal")) + point.Signals.RemoveAt(point.Signals.Count - 1); + ImGui.Separator(); + for (var i = 0; i < point.Signals.Count; i++)//Signals: Tree View ForTreeNode($"Signal #{index}-{i}", () => Run(point.Signals[i], i)); } @@ -91,8 +341,8 @@ private static void Run(SpawnPoint.Entity entity, int index) { if (ImGui.Selectable(obj.ModelName, obj.ObjectId == entity.ObjectId)) entity.ObjectId = (int)obj.ObjectId; - } + } ImGui.EndCombo(); } @@ -173,6 +423,18 @@ private static void Run(SpawnPoint.WalkPathDesc item, int index) ForEdit($"Serial##{index}", () => item.Serial, x => item.Serial = x); ForEdit($"Flag##{index}", () => item.Flag, x => item.Flag = x); ForEdit($"Id##{index}", () => item.Id, x => item.Id = x); + //Add new WalkingPath Positions so that NPCs have multiple possible paths. + if (ImGui.SmallButton($"Add a new position##{index}")) + { + item.Positions.Add(new Position()); + } + ImGui.SameLine(); + if (item.Positions.Count > 0) + if (ImGui.SmallButton($"Remove last position")) + { + item.Positions.RemoveAt(item.Positions.Count - 1); + } + ImGui.Separator(); for (var i = 0; i < item.Positions.Count; i++) { var pos = item.Positions[i]; @@ -189,10 +451,13 @@ private static void Run(SpawnPoint.WalkPathDesc item, int index) private static void Run(SpawnPoint.ReturnParameter item, int index) { - ForEdit($"Unk00##{index}", () => item.Id, x => item.Id = x); - ForEdit($"Unk01##{index}", () => item.Type, x => item.Type = x); - ForEdit($"Unk02##{index}", () => item.Rate, x => item.Rate = x); - ForEdit($"Unk03##{index}", () => item.EntryType, x => item.EntryType = x); + ForEdit($"Id##{index}", () => item.Id, x => item.Id = x); + ForEdit($"Type##{index}", () => item.Type, x => item.Type = x); + ForEdit($"Rate##{index}", () => item.Rate, x => item.Rate = x); + ForEdit($"Entry Type##{index}", () => item.EntryType, x => item.EntryType = x); + ForEdit($"Argument04##{index}", () => item.Argument04, x => item.Argument04 = x); + ForEdit($"Argument08##{index}", () => item.Argument08, x => item.Argument08 = x); + ForEdit($"Argument0c##{index}", () => item.Argument0c, x => item.Argument0c = x); } private static void Run(SpawnPoint.Signal item, int index) diff --git a/OpenKh.Tools.Kh2MdlxEditor/Utils/MdlxEditorImporter.cs b/OpenKh.Tools.Kh2MdlxEditor/Utils/MdlxEditorImporter.cs index d631bd03a..c7bcb84c4 100644 --- a/OpenKh.Tools.Kh2MdlxEditor/Utils/MdlxEditorImporter.cs +++ b/OpenKh.Tools.Kh2MdlxEditor/Utils/MdlxEditorImporter.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Numerics; namespace OpenKh.Tools.Kh2MdlxEditor.Utils { @@ -145,37 +146,71 @@ public static ModelSkeletal replaceMeshModelSkeletal(Assimp.Scene scene, ModelSk } } + // List to calc joint flag + HashSet bonesWithRig = new HashSet(); + foreach(var mesh in scene.Meshes) + { + for(int i = 0; i < mesh.Bones.Count; i++) + { + if (mesh.Bones[i].HasVertexWeights) + bonesWithRig.Add(i); + } + } + for (int i = 0; i < assimpBones.Count; i++) { Assimp.Node assimpBone = assimpBones[i]; - ModelCommon.Bone bone = new ModelCommon.Bone(); bone.Index = (short)i; bone.ParentIndex = (short)assimpBones.IndexOf(assimpBone.Parent); - assimpBones[i].Transform.Decompose( - out Assimp.Vector3D scale, - out Assimp.Quaternion rotation, - out Assimp.Vector3D translation); + // Root + if (i == 0) + { + bone.ParentIndex = -1; + bone.ChildIndex = -1; + } + + // Flags + if (!bonesWithRig.Contains(i)) + { + bone.Flags = 3; + } + //Else 0 or 1 - bone.ScaleX= scale.X; - bone.ScaleY= scale.Y; - bone.ScaleZ= scale.Z; - bone.TranslationX= translation.X; - bone.TranslationY= translation.Y; - bone.TranslationZ= translation.Z; + Matrix4x4 transform = AssimpGeneric.ToNumerics(assimpBones[i].Transform); - System.Numerics.Quaternion numQuat = new System.Numerics.Quaternion(rotation.X, rotation.Y, rotation.Z, rotation.W); + DecomposeEuler(transform, out Vector3 scale, out Vector3 rotation, out Vector3 position); - System.Numerics.Vector3 rotationRadian = MathUtils.toRadian(MathUtils.ToEulerAngles(numQuat)); - bone.RotationX = rotationRadian.X; - bone.RotationY = rotationRadian.Y; - bone.RotationZ = rotationRadian.Z; + bone.ScaleX = scale.X; + bone.ScaleY = scale.Y; + bone.ScaleZ = scale.Z; + bone.TranslationX = position.X; + bone.TranslationY = position.Y; + bone.TranslationZ = position.Z; + bone.RotationX = rotation.X; + bone.RotationY = rotation.Y; + bone.RotationZ = rotation.Z; bone.RotationW = 0; mdlxBones.Add(bone); } + // Each bone has some kind of length in the Scale W value - it is unknown how it's derived except for the following + List lengthProcessed = new List(); + for (int i = 0; i < mdlxBones.Count; i++) { + lengthProcessed.Add(false); + } + foreach (ModelCommon.Bone bone in mdlxBones) + { + if(bone.TranslationX != 0 && bone.TranslationY == 0 && bone.TranslationZ == 0 && + lengthProcessed[bone.ParentIndex] == false) + { + mdlxBones[bone.ParentIndex].ScaleW = bone.TranslationX; + lengthProcessed[bone.ParentIndex] = true; + } + } + return mdlxBones; } @@ -196,5 +231,58 @@ public static ModelSkeletal replaceMeshModelSkeletal(Assimp.Scene scene, ModelSk return children; } + + + public static void DecomposeEuler(Matrix4x4 matrix, out Vector3 scale, out Vector3 rotation, out Vector3 position) + { + // Extract the translation + position = new Vector3(matrix.M41, matrix.M42, matrix.M43); + + // Extract the scale + scale = new Vector3( + new Vector3(matrix.M11, matrix.M12, matrix.M13).Length(), + new Vector3(matrix.M21, matrix.M22, matrix.M23).Length(), + new Vector3(matrix.M31, matrix.M32, matrix.M33).Length() + ); + + // Remove scale from the matrix to isolate the rotation + Matrix4x4 rotationMatrix = new Matrix4x4( + matrix.M11 / scale.X, matrix.M12 / scale.X, matrix.M13 / scale.X, 0.0f, + matrix.M21 / scale.Y, matrix.M22 / scale.Y, matrix.M23 / scale.Y, 0.0f, + matrix.M31 / scale.Z, matrix.M32 / scale.Z, matrix.M33 / scale.Z, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f + ); + + // Use a small epsilon to solve floating-point inaccuracies + const float epsilon = 1e-6f; + + // Extract the rotation angles from the rotation matrix + rotation.Y = (float)Math.Asin(-rotationMatrix.M13); // Angle around Y + + float cosY = (float)Math.Cos(rotation.Y); + + if (Math.Abs(cosY) > epsilon) + { + // Finding angle around X + float tanX = rotationMatrix.M33 / cosY; // A + float tanY = rotationMatrix.M23 / cosY; // B + rotation.X = (float)Math.Atan2(tanY, tanX); + + // Finding angle around Z + tanX = rotationMatrix.M11 / cosY; // E + tanY = rotationMatrix.M12 / cosY; // F + rotation.Z = (float)Math.Atan2(tanY, tanX); + } + else + { + // Y is fixed + rotation.X = 0; + + // Finding angle around Z + float tanX = rotationMatrix.M22; // E + float tanY = -rotationMatrix.M21; // F + rotation.Z = (float)Math.Atan2(tanY, tanX); + } + } } } diff --git a/OpenKh.Tools.Kh2MsetMotionEditor/OpenKh.Tools.Kh2MsetMotionEditor.csproj b/OpenKh.Tools.Kh2MsetMotionEditor/OpenKh.Tools.Kh2MsetMotionEditor.csproj index 819da225a..1c2780fc4 100644 --- a/OpenKh.Tools.Kh2MsetMotionEditor/OpenKh.Tools.Kh2MsetMotionEditor.csproj +++ b/OpenKh.Tools.Kh2MsetMotionEditor/OpenKh.Tools.Kh2MsetMotionEditor.csproj @@ -24,7 +24,7 @@ - + diff --git a/OpenKh.Tools.Kh2ObjectEditor/Classes/MotionSelector_Wrapper.cs b/OpenKh.Tools.Kh2ObjectEditor/Classes/MotionSelector_Wrapper.cs index 33c620c5f..849f8c6af 100644 --- a/OpenKh.Tools.Kh2ObjectEditor/Classes/MotionSelector_Wrapper.cs +++ b/OpenKh.Tools.Kh2ObjectEditor/Classes/MotionSelector_Wrapper.cs @@ -1,21 +1,40 @@ using OpenKh.Kh2; +using OpenKh.Tools.Kh2ObjectEditor.Services; namespace OpenKh.Tools.Kh2ObjectEditor.Classes { public class MotionSelector_Wrapper { public int Index { get; set; } - public Bar.Entry Entry { get; set; } + public BinaryArchive.Entry Entry { get; set; } public string Name { get; set; } - public bool IsDummy { get { return Name.Contains("DUMM"); } } + public bool IsDummy { get { return Name.Contains("DUMM") || LinkedSubfile.Length == 0; } } + public byte[] LinkedSubfile + { + get + { + if(Entry.Link == -1) + { + return new byte[0]; + } + + return MsetService.Instance.MsetBinarc.Subfiles[Entry.Link]; + } + } - public MotionSelector_Wrapper(int index, Bar.Entry entry) + public MotionSelector_Wrapper(int index, BinaryArchive.Entry entry) { Index = index; Entry = entry; - Name = "[" + Index + "] " + Entry.Name + " [" + (MotionSet.MotionName)(index / 4) + "]"; + setName(); + } + public void setName() + { + Name = "[" + Index + "] " + Entry.Name + " [" + (MotionSet.MotionName)(Index / 4) + "]"; + if (Entry.Type == BinaryArchive.EntryType.Motionset) + Name += ""; + if (LinkedSubfile.Length == 0) + Name += ""; } - - } } diff --git a/OpenKh.Tools.Kh2ObjectEditor/Classes/ObjectSelector_Wrapper.cs b/OpenKh.Tools.Kh2ObjectEditor/Classes/ObjectSelector_Wrapper.cs index 6ae7e89c7..60e32d22f 100644 --- a/OpenKh.Tools.Kh2ObjectEditor/Classes/ObjectSelector_Wrapper.cs +++ b/OpenKh.Tools.Kh2ObjectEditor/Classes/ObjectSelector_Wrapper.cs @@ -1,3 +1,5 @@ +using OpenKh.Tools.Kh2ObjectEditor.Utils; + namespace OpenKh.Tools.Kh2ObjectEditor.Classes { public class ObjectSelector_Wrapper @@ -6,5 +8,15 @@ public class ObjectSelector_Wrapper public string FileName { get; set; } // UNIQUE KEY public bool HasMset { get; set; } public bool Selected { get; set; } + + public string GetDescription() + { + string description = ""; + if (ObjectDictionary.Instance.ContainsKey(FileName.ToUpper())) + { + description = ObjectDictionary.Instance[FileName.ToUpper()]; + } + return description; + } } } diff --git a/OpenKh.Tools.Kh2ObjectEditor/Classes/Object_Wrapper.cs b/OpenKh.Tools.Kh2ObjectEditor/Classes/Object_Wrapper.cs index 67afa0232..bbf56ece7 100644 --- a/OpenKh.Tools.Kh2ObjectEditor/Classes/Object_Wrapper.cs +++ b/OpenKh.Tools.Kh2ObjectEditor/Classes/Object_Wrapper.cs @@ -13,7 +13,7 @@ public class Object_Wrapper public string MsetPath { get; set; } public Bar MdlxBar { get; set; } - public Bar MsetBar { get; set; } + public BinaryArchive MsetBar { get; set; } public ModelSkeletal ModelFile { get; set; } public ModelCollision CollisionFile { get; set; } @@ -49,7 +49,7 @@ public void readFiles() if (!Bar.IsValid(streamMset)) throw new Exception("File is not a valid MSET: " + MsetPath); - MsetBar = Bar.Read(streamMset); + MsetBar = BinaryArchive.Read(streamMset); } } @@ -82,9 +82,9 @@ public void openFiles() if(MsetBar != null) { MsetEntries = new List(); - for(int i = 0; i < MsetBar.Count; i++) + for(int i = 0; i < MsetBar.Entries.Count; i++) { - MsetEntries.Add(new MotionSelector_Wrapper(i, MsetBar[i])); + MsetEntries.Add(new MotionSelector_Wrapper(i, MsetBar.Entries[i])); } } } diff --git a/OpenKh.Tools.Kh2ObjectEditor/Modules/AI/ModuleAI_Control.xaml b/OpenKh.Tools.Kh2ObjectEditor/Modules/AI/ModuleAI_Control.xaml index 2a8fa20c3..9e8d44a75 100644 --- a/OpenKh.Tools.Kh2ObjectEditor/Modules/AI/ModuleAI_Control.xaml +++ b/OpenKh.Tools.Kh2ObjectEditor/Modules/AI/ModuleAI_Control.xaml @@ -14,8 +14,9 @@ + - + diff --git a/OpenKh.Tools.Kh2ObjectEditor/Modules/AI/ModuleAI_Control.xaml.cs b/OpenKh.Tools.Kh2ObjectEditor/Modules/AI/ModuleAI_Control.xaml.cs index 3e90e7630..8d7a40da9 100644 --- a/OpenKh.Tools.Kh2ObjectEditor/Modules/AI/ModuleAI_Control.xaml.cs +++ b/OpenKh.Tools.Kh2ObjectEditor/Modules/AI/ModuleAI_Control.xaml.cs @@ -32,5 +32,10 @@ private void Button_ClipboardPaste(object sender, RoutedEventArgs e) { ThisVM.clipboardPaste(); } + + private void Button_Test(object sender, RoutedEventArgs e) + { + ThisVM.TestAiIngame(); + } } } diff --git a/OpenKh.Tools.Kh2ObjectEditor/Modules/AI/ModuleAI_VM.cs b/OpenKh.Tools.Kh2ObjectEditor/Modules/AI/ModuleAI_VM.cs index d76ce84e1..e97726312 100644 --- a/OpenKh.Tools.Kh2ObjectEditor/Modules/AI/ModuleAI_VM.cs +++ b/OpenKh.Tools.Kh2ObjectEditor/Modules/AI/ModuleAI_VM.cs @@ -1,10 +1,13 @@ using OpenKh.Command.Bdxio.Models; using OpenKh.Command.Bdxio.Utils; +using OpenKh.Kh2; using OpenKh.Tools.Kh2ObjectEditor.Services; using OpenKh.Tools.Kh2ObjectEditor.Utils; using SharpDX.DirectWrite; +using System; using System.IO; using System.Windows; +using Xe.BinaryMapper; namespace OpenKh.Tools.Kh2ObjectEditor.Modules.AI { @@ -71,5 +74,58 @@ public void write() BdxStream.Position = 0; MdlxService.Instance.BdxFile = BdxStream; } + + // Tests the AI ingame. + // IMPORTANT: AI must be of the same length, be careful because there's no control of this. + public void TestAiIngame() + { + string filename = Path.GetFileName(MdlxService.Instance.MdlxPath); + + if (filename == "") + return; + + long fileAddress; + try + { + fileAddress = ProcessService.getAddressOfFile(filename); + } + catch (Exception exc) + { + System.Windows.Forms.MessageBox.Show("Game is not running", "There was an error", System.Windows.Forms.MessageBoxButtons.OK, System.Windows.Forms.MessageBoxIcon.Error); + return; + } + + if (fileAddress == 0) + { + System.Windows.Forms.MessageBox.Show("Couldn't find file", "There was an error", System.Windows.Forms.MessageBoxButtons.OK, System.Windows.Forms.MessageBoxIcon.Error); + return; + } + + int entryOffset = -1; + foreach(Bar.Entry entry in MdlxService.Instance.MdlxBar) + { + if(entry.Type == Bar.EntryType.Bdx) + { + entryOffset = entry.Offset; + break; + } + } + if (entryOffset == -1) + { + System.Windows.Forms.MessageBox.Show("AI file not found", "There was an error", System.Windows.Forms.MessageBoxButtons.OK, System.Windows.Forms.MessageBoxIcon.Error); + return; + } + + long aiFileAddress = fileAddress + entryOffset; + + MemoryStream aiStream = new MemoryStream(); + MdlxService.Instance.BdxFile.Position = 0; + MdlxService.Instance.BdxFile.CopyTo(aiStream); + MdlxService.Instance.BdxFile.Position = 0; + aiStream.Position = 0; + + byte[] streamBytes = aiStream.ToArray(); + MemoryAccess.writeMemory(ProcessService.KH2Process, aiFileAddress, streamBytes, true); + } } } diff --git a/OpenKh.Tools.Kh2ObjectEditor/Modules/Collisions/CollisionShapeConverter.cs b/OpenKh.Tools.Kh2ObjectEditor/Modules/Collisions/CollisionShapeConverter.cs new file mode 100644 index 000000000..b92698ea8 --- /dev/null +++ b/OpenKh.Tools.Kh2ObjectEditor/Modules/Collisions/CollisionShapeConverter.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections.Generic; +using System.Windows.Data; + +namespace OpenKh.Tools.Kh2ObjectEditor.Modules.Collisions +{ + public class CollisionShapeConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) + { + if (value is byte byteValue) + { + if (CollisionShapes.TryGetValue(byteValue, out string stringValue)) + { + return stringValue; + } + } + return value.ToString(); + } + + public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) + { + if (value is string stringValue && parameter is Dictionary dictionary) + { + foreach (var kvp in CollisionShapes) + { + if (kvp.Value == stringValue) + { + return kvp.Key; + } + } + } + return value; + } + + public static Dictionary CollisionShapes = new Dictionary + { + { 0, "Ellipsoid" }, + { 1, "Column" }, + { 2, "Cube" }, + { 3, "Sphere" }, + }; + } +} diff --git a/OpenKh.Tools.Kh2ObjectEditor/Modules/Collisions/CollisionTypeConverter.cs b/OpenKh.Tools.Kh2ObjectEditor/Modules/Collisions/CollisionTypeConverter.cs new file mode 100644 index 000000000..c88266dbb --- /dev/null +++ b/OpenKh.Tools.Kh2ObjectEditor/Modules/Collisions/CollisionTypeConverter.cs @@ -0,0 +1,61 @@ +using System; +using System.Collections.Generic; +using System.Windows.Data; + +namespace OpenKh.Tools.Kh2ObjectEditor.Modules.Collisions +{ + public class CollisionTypeConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) + { + if (value is byte byteValue) + { + if (CollisionTypes.TryGetValue(byteValue, out string stringValue)) + { + return stringValue; + } + } + return value.ToString(); + } + + public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) + { + if (value is string stringValue && parameter is Dictionary dictionary) + { + foreach (var kvp in CollisionTypes) + { + if (kvp.Value == stringValue) + { + return kvp.Key; + } + } + } + return value; + } + + public static Dictionary CollisionTypes = new Dictionary + { + { 0, "Background" }, + { 1, "Object" }, + { 2, "Hit" }, + { 3, "Target" }, + { 4, "Background (Player)" }, + { 5, "Reaction" }, + { 6, "Attack" }, + { 7, "Camera" }, + { 8, "Cast Item" }, + { 9, "Item" }, + { 10, "IK" }, + { 11, "IK Down" }, + { 12, "Neck" }, + { 13, "Guard" }, + { 14, "Ref RC" }, + { 15, "Weapon Top" }, + { 16, "Stun" }, + { 17, "Head" }, + { 18, "Blind" }, + { 19, "Talk Camera" }, + { 20, "RTN Neck" }, + }; + } +} diff --git a/OpenKh.Tools.Kh2ObjectEditor/Modules/Collisions/Collisions_Control.xaml b/OpenKh.Tools.Kh2ObjectEditor/Modules/Collisions/Collisions_Control.xaml index b2cacc2d8..bdc9e2469 100644 --- a/OpenKh.Tools.Kh2ObjectEditor/Modules/Collisions/Collisions_Control.xaml +++ b/OpenKh.Tools.Kh2ObjectEditor/Modules/Collisions/Collisions_Control.xaml @@ -8,9 +8,14 @@ d:DesignHeight="450" d:DesignWidth="800"> - - - + + + + + + + + + + + + + + + + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/OpenKh.Tools.Kh2ObjectEditor/Modules/Collisions/Collisions_Control.xaml.cs b/OpenKh.Tools.Kh2ObjectEditor/Modules/Collisions/Collisions_Control.xaml.cs index 04cebf096..bd6398739 100644 --- a/OpenKh.Tools.Kh2ObjectEditor/Modules/Collisions/Collisions_Control.xaml.cs +++ b/OpenKh.Tools.Kh2ObjectEditor/Modules/Collisions/Collisions_Control.xaml.cs @@ -1,6 +1,9 @@ using OpenKh.Kh2; using OpenKh.Tools.Kh2ObjectEditor.Services; +using System.Collections.Generic; +using System.Data; using System.Windows.Controls; +using System.Windows.Input; namespace OpenKh.Tools.Kh2ObjectEditor.Modules.Collisions { @@ -53,5 +56,24 @@ private void Button_Save(object sender, System.Windows.RoutedEventArgs e) { ThisVM.saveCollisions(); } + + private void Button_Test(object sender, System.Windows.RoutedEventArgs e) + { + ThisVM.TestCollisionsIngame(); + } + + private void DataGridRow_MouseLeftButtonUp(object sender, MouseEventArgs e) + { + List selectedIndices = new List(); + foreach (var selectedItem in DataTable.SelectedItems) + { + int index = DataTable.Items.IndexOf(selectedItem); + if (index >= 0) + { + selectedIndices.Add(index); + } + } + ViewerService.Instance.ShowCollisions(selectedIndices); + } } } diff --git a/OpenKh.Tools.Kh2ObjectEditor/Modules/Collisions/M_Collisions_VM.cs b/OpenKh.Tools.Kh2ObjectEditor/Modules/Collisions/M_Collisions_VM.cs index 701cea99e..df7824d56 100644 --- a/OpenKh.Tools.Kh2ObjectEditor/Modules/Collisions/M_Collisions_VM.cs +++ b/OpenKh.Tools.Kh2ObjectEditor/Modules/Collisions/M_Collisions_VM.cs @@ -1,7 +1,11 @@ using OpenKh.Kh2; +using OpenKh.Tools.Kh2ObjectEditor.Modules.Motions; using OpenKh.Tools.Kh2ObjectEditor.Services; +using OpenKh.Tools.Kh2ObjectEditor.Utils; +using System; using System.Collections.Generic; using System.Collections.ObjectModel; +using System.IO; namespace OpenKh.Tools.Kh2ObjectEditor.Modules.Collisions { @@ -9,8 +13,12 @@ public class M_Collisions_VM { public ObservableCollection Collisions { get; set; } + public static Dictionary CollisionTypeOptions { get; set; } + public static Dictionary CollisionShapeOptions { get; set; } + public M_Collisions_VM() { + loadOptions(); Collisions = new ObservableCollection(); reloadCollisions(); } @@ -81,5 +89,59 @@ public void removeCollision(int index) MdlxService.Instance.CollisionFile.EntryList.RemoveAt(index); reloadCollisions(); } + + private void loadOptions() + { + CollisionTypeOptions = CollisionTypeConverter.CollisionTypes; + CollisionShapeOptions = CollisionShapeConverter.CollisionShapes; + } + + // Tests the collisions ingame. + // IMPORTANT: the Collisions must be of the same length, be careful because there's no control of this. + public void TestCollisionsIngame() + { + string filename = Path.GetFileName(MdlxService.Instance.MdlxPath); + + if (filename == "") + return; + + long fileAddress; + try + { + fileAddress = ProcessService.getAddressOfFile(filename); + } + catch (Exception exc) + { + System.Windows.Forms.MessageBox.Show("Game is not running", "There was an error", System.Windows.Forms.MessageBoxButtons.OK, System.Windows.Forms.MessageBoxIcon.Error); + return; + } + + if (fileAddress == 0) + { + System.Windows.Forms.MessageBox.Show("Couldn't find file", "There was an error", System.Windows.Forms.MessageBoxButtons.OK, System.Windows.Forms.MessageBoxIcon.Error); + return; + } + + int entryOffset = -1; + foreach (Bar.Entry entry in MdlxService.Instance.MdlxBar) + { + if (entry.Type == Bar.EntryType.ModelCollision) + { + entryOffset = entry.Offset; + break; + } + } + if (entryOffset == -1) + { + System.Windows.Forms.MessageBox.Show("AI file not found", "There was an error", System.Windows.Forms.MessageBoxButtons.OK, System.Windows.Forms.MessageBoxIcon.Error); + return; + } + + long collisionFileAddress = fileAddress + entryOffset; + + MemoryStream collisionStream = (MemoryStream)MdlxService.Instance.CollisionFile.toStream(); + byte[] streamBytes = collisionStream.ToArray(); + MemoryAccess.writeMemory(ProcessService.KH2Process, collisionFileAddress, streamBytes, true); + } } } diff --git a/OpenKh.Tools.Kh2ObjectEditor/Modules/Effects/M_EffectDpdTexture_Control.xaml b/OpenKh.Tools.Kh2ObjectEditor/Modules/Effects/M_EffectDpdTexture_Control.xaml index c0f6a4f62..2f8d8c57f 100644 --- a/OpenKh.Tools.Kh2ObjectEditor/Modules/Effects/M_EffectDpdTexture_Control.xaml +++ b/OpenKh.Tools.Kh2ObjectEditor/Modules/Effects/M_EffectDpdTexture_Control.xaml @@ -31,6 +31,12 @@ + + + + + + diff --git a/OpenKh.Tools.Kh2ObjectEditor/Modules/Effects/M_EffectDpdTexture_Control.xaml.cs b/OpenKh.Tools.Kh2ObjectEditor/Modules/Effects/M_EffectDpdTexture_Control.xaml.cs index 744dbff80..e8b53cf9f 100644 --- a/OpenKh.Tools.Kh2ObjectEditor/Modules/Effects/M_EffectDpdTexture_Control.xaml.cs +++ b/OpenKh.Tools.Kh2ObjectEditor/Modules/Effects/M_EffectDpdTexture_Control.xaml.cs @@ -1,4 +1,5 @@ using OpenKh.Kh2; +using System.Windows; using System.Windows.Controls; using System.Windows.Media.Imaging; @@ -30,5 +31,22 @@ private void List_Textures_MouseDoubleClick(object sender, System.Windows.Input. { loadImage(List_Textures.SelectedIndex); } + + public void EffectImage_Export(object sender, RoutedEventArgs e) + { + if (List_Textures.SelectedItem != null) + { + ThisVM.ExportTexture(List_Textures.SelectedIndex); + } + } + + public void EffectImage_Replace(object sender, RoutedEventArgs e) + { + if (List_Textures.SelectedItem != null) + { + ThisVM.ReplaceTexture(List_Textures.SelectedIndex); + loadImage(List_Textures.SelectedIndex); + } + } } } diff --git a/OpenKh.Tools.Kh2ObjectEditor/Modules/Effects/M_EffectDpdTexture_VM.cs b/OpenKh.Tools.Kh2ObjectEditor/Modules/Effects/M_EffectDpdTexture_VM.cs index e922dae8b..84f0f6b04 100644 --- a/OpenKh.Tools.Kh2ObjectEditor/Modules/Effects/M_EffectDpdTexture_VM.cs +++ b/OpenKh.Tools.Kh2ObjectEditor/Modules/Effects/M_EffectDpdTexture_VM.cs @@ -1,7 +1,9 @@ +using ModelingToolkit.Objects; using OpenKh.Kh2; -using OpenKh.Tools.Kh2ObjectEditor.Services; +using System; using System.Collections.ObjectModel; -using System.Windows.Media; +using System.Drawing; +using System.Windows.Forms; using System.Windows.Media.Imaging; namespace OpenKh.Tools.Kh2ObjectEditor.Modules.Effects @@ -40,11 +42,160 @@ public void loadWrappers() public BitmapSource getTexture(int index) { - const double dpi = 96.0; + Dpd.Texture texture = TextureWrappers[index].Texture; + + MtMaterial mat = GetMaterial(texture); + return mat.GetAsBitmapImage(); + } + + public static byte SwapBits34(byte x) + { + byte bit3 = (byte)((x & 8) << 1); + byte bit4 = (byte)((x & 16) >> 1); + byte rest = (byte)(x & ~(8 | 16)); + return (byte)(rest | bit3 | bit4); + } + public void ExportTexture(int index) + { Dpd.Texture texture = TextureWrappers[index].Texture; + MtMaterial mat = GetMaterial(texture); + + System.Windows.Forms.SaveFileDialog sfd; + sfd = new System.Windows.Forms.SaveFileDialog(); + sfd.Title = "Export image as PNG"; + sfd.FileName = "Effect_" + index + ".png"; + sfd.ShowDialog(); + if (sfd.FileName != "") + { + mat.ExportAsPng(sfd.FileName); + } + } + + // Must have the same width and height and palette size + public void ReplaceTexture(int index) + { + string filePath = ""; + using (OpenFileDialog openFileDialog = new OpenFileDialog()) + { + openFileDialog.Filter = "Image Files|*.png"; + + if (openFileDialog.ShowDialog() == DialogResult.OK) + { + filePath = openFileDialog.FileName; + } + } + + if (filePath == "") { + return; + } + + try + { + Bitmap pngBitmap = new Bitmap(filePath); + Dpd.Texture texture = TextureWrappers[index].Texture; + + if(pngBitmap.Size.Width != texture.Size.Width || + pngBitmap.Size.Height != texture.Size.Height) + { + MessageBox.Show("Height/Width doesn't match original", "Image import error", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + + if(pngBitmap.Palette.Entries.Length > texture.Palette.Length) + { + MessageBox.Show("Palette color count must be the same size or lower ("+ texture.Palette.Length + ")", "Image import error", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + + MtMaterial mat = GetMaterial(texture); + mat.DiffuseTextureBitmap = pngBitmap; + + if(texture.format == 0x14) { + mat.BitmapToDataClut(16); + } + else { + mat.BitmapToDataClut(256); + } + + texture.Data = mat.Data; + texture.Palette = mat.Clut; + + for (int i = 0; i < texture.Data.Length; i++) + { + texture.Data[i] = SwapBits34(texture.Data[i]); + } + + // Alpha fix + for (int i = 0; i < texture.Palette.Length; i++) + { + if (i % 4 == 3) + { + if (texture.Palette[i] > 0) + { + texture.Palette[i] = (byte)((texture.Palette[i] - 1) / 2); + } + } + } + + if (texture.format == 0x14) + { + byte[] tempData = new byte[mat.Data.Length/2]; + for (int i = 0; i < texture.Data.Length; i+=2) + { + byte tempByte = (byte)(texture.Data[i] << 4); + tempByte += texture.Data[i + 1]; + tempData[i / 2] = tempByte; + } + } + } + catch(Exception ex) + { + MessageBox.Show("There was an error importing the image", "Image import error", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + } + + private MtMaterial GetMaterial(Dpd.Texture texture) + { + MtMaterial mat = new MtMaterial(); + mat.Data = new byte[texture.Data.Length]; + Array.Copy(texture.Data, mat.Data, texture.Data.Length); + mat.Clut = new byte[texture.Palette.Length]; + Array.Copy(texture.Palette, mat.Clut, texture.Palette.Length); + mat.Width = texture.Size.Width; + mat.Height = texture.Size.Height; + mat.ColorSize = 1; + mat.PixelHasAlpha = true; + + for(int i = 0; i < mat.Data.Length; i++) + { + mat.Data[i] = SwapBits34(mat.Data[i]); + } + + // Alpha fix + for (int i = 0; i < mat.Clut.Length; i++) + { + if (i % 4 == 3) + { + if (mat.Clut[i] > 0) + { + mat.Clut[i] = (byte)(mat.Clut[i] * 2 - 1); + } + } + } + + if (texture.format == 0x14) + { + mat.Data = new byte[texture.Data.Length * 2]; // 2 pixels per byte + for (int i = 0; i < texture.Data.Length; i++) + { + byte biPixel = texture.Data[i]; + mat.Data[(i * 2) + 1] = (byte)(biPixel >> 4); + mat.Data[i * 2] = (byte)(biPixel & 0b00001111); + } + } + + mat.GenerateBitmap(); - return BitmapSource.Create(texture.Size.Width, texture.Size.Height, dpi, dpi, PixelFormats.Bgra32, null, texture.GetBitmap(), texture.Size.Width * 4); + return mat; } public class DpdTextureWrapper diff --git a/OpenKh.Tools.Kh2ObjectEditor/Modules/Effects/M_EffectDpds_Control.xaml b/OpenKh.Tools.Kh2ObjectEditor/Modules/Effects/M_EffectDpds_Control.xaml index 310e2944e..62c30fc74 100644 --- a/OpenKh.Tools.Kh2ObjectEditor/Modules/Effects/M_EffectDpds_Control.xaml +++ b/OpenKh.Tools.Kh2ObjectEditor/Modules/Effects/M_EffectDpds_Control.xaml @@ -23,6 +23,8 @@ + + diff --git a/OpenKh.Tools.Kh2ObjectEditor/Modules/Effects/M_EffectDpds_Control.xaml.cs b/OpenKh.Tools.Kh2ObjectEditor/Modules/Effects/M_EffectDpds_Control.xaml.cs index 05e6ba1df..db9982586 100644 --- a/OpenKh.Tools.Kh2ObjectEditor/Modules/Effects/M_EffectDpds_Control.xaml.cs +++ b/OpenKh.Tools.Kh2ObjectEditor/Modules/Effects/M_EffectDpds_Control.xaml.cs @@ -38,5 +38,16 @@ public void Dpd_Replace(object sender, RoutedEventArgs e) ThisVM.Dpd_Replace(List_Dpds.SelectedIndex); } } + public void Dpd_Export(object sender, RoutedEventArgs e) + { + if (List_Dpds.SelectedIndex != null) + { + ThisVM.Dpd_Export(List_Dpds.SelectedIndex); + } + } + public void Dpd_Import(object sender, RoutedEventArgs e) + { + ThisVM.Dpd_Import(); + } } } diff --git a/OpenKh.Tools.Kh2ObjectEditor/Modules/Effects/M_EffectDpds_VM.cs b/OpenKh.Tools.Kh2ObjectEditor/Modules/Effects/M_EffectDpds_VM.cs index 58e41968f..b28695430 100644 --- a/OpenKh.Tools.Kh2ObjectEditor/Modules/Effects/M_EffectDpds_VM.cs +++ b/OpenKh.Tools.Kh2ObjectEditor/Modules/Effects/M_EffectDpds_VM.cs @@ -1,9 +1,9 @@ +using OpenKh.Kh2; using OpenKh.Tools.Kh2ObjectEditor.Services; -using static OpenKh.Tools.Kh2ObjectEditor.Modules.Model.ModuleModelMeshes_VM; +using System; using System.Collections.ObjectModel; -using OpenKh.Kh2; -using static OpenKh.Tools.Kh2ObjectEditor.Modules.Effects.M_EffectDpdTexture_VM; -using System.Windows; +using System.IO; +using System.Windows.Forms; namespace OpenKh.Tools.Kh2ObjectEditor.Modules.Effects { @@ -53,6 +53,41 @@ public void Dpd_Replace(int index) ApdxService.Instance.PaxFile.DpxPackage.DpdList[index] = ClipboardService.Instance.FetchDpd(); loadDpds(); } + public void Dpd_Export(int index) + { + Kh2.Dpd dpd = ApdxService.Instance.PaxFile.DpxPackage.DpdList[index]; + + Microsoft.Win32.SaveFileDialog dlg = new Microsoft.Win32.SaveFileDialog(); + dlg.FileName = "DPD_" + index; + dlg.DefaultExt = ".dpd"; + + Nullable result = dlg.ShowDialog(); + + if (result == true) + { + MemoryStream dpdStream = new MemoryStream(); + dpd.getAsStream().CopyTo(dpdStream); + File.WriteAllBytes(dlg.FileName, dpdStream.ToArray()); + } + } + public void Dpd_Import() + { + using (OpenFileDialog openFileDialog = new OpenFileDialog()) + { + if (openFileDialog.ShowDialog() == DialogResult.OK) + { + string filePath = openFileDialog.FileName; + + Kh2.Dpd dpd; + using (Stream stream = openFileDialog.OpenFile()) + { + dpd = new Kh2.Dpd(stream); + } + ApdxService.Instance.PaxFile.DpxPackage.DpdList.Add(dpd); + loadDpds(); + } + } + } public class DpdWrapper { diff --git a/OpenKh.Tools.Kh2ObjectEditor/Modules/Effects/M_Effects_Control.xaml b/OpenKh.Tools.Kh2ObjectEditor/Modules/Effects/M_Effects_Control.xaml index ac1f7454b..46af53010 100644 --- a/OpenKh.Tools.Kh2ObjectEditor/Modules/Effects/M_Effects_Control.xaml +++ b/OpenKh.Tools.Kh2ObjectEditor/Modules/Effects/M_Effects_Control.xaml @@ -7,10 +7,16 @@ mc:Ignorable="d" d:DesignHeight="450" d:DesignWidth="800"> - - - - + + + + + + + + + + diff --git a/OpenKh.Tools.Kh2ObjectEditor/Modules/Effects/M_Effects_Control.xaml.cs b/OpenKh.Tools.Kh2ObjectEditor/Modules/Effects/M_Effects_Control.xaml.cs index 7a7a3e4db..d30bdf530 100644 --- a/OpenKh.Tools.Kh2ObjectEditor/Modules/Effects/M_Effects_Control.xaml.cs +++ b/OpenKh.Tools.Kh2ObjectEditor/Modules/Effects/M_Effects_Control.xaml.cs @@ -1,8 +1,12 @@ using OpenKh.Kh2; using OpenKh.Tools.Kh2ObjectEditor.Services; +using OpenKh.Tools.Kh2ObjectEditor.Utils; +using System; using System.IO; +using System.Linq; using System.Windows; using System.Windows.Controls; +using Xe.BinaryMapper; namespace OpenKh.Tools.Kh2ObjectEditor.Modules.Effects { @@ -56,6 +60,13 @@ private void Button_ImportDpx(object sender, RoutedEventArgs e) } } } + private void Button_Test(object sender, RoutedEventArgs e) + { + if (ApdxService.Instance.PaxFile?.DpxPackage == null) + return; + + TestEffectsIngame(); + } private void importDpx(Dpx dpxFile) { @@ -73,5 +84,73 @@ private void importDpx(Dpx dpxFile) ApdxService.Instance.PaxFile.DpxPackage.ParticleEffects.Add(effect); } } + + + // Tests the Effects ingame. Writes the element/caster entries. + // IMPORTANT: elements must be of the same length, be careful because there's no control of this. + public void TestEffectsIngame() + { + string filename = Path.GetFileName(ApdxService.Instance.ApdxPath); + + if (filename == "") + return; + + long fileAddress; + try + { + fileAddress = ProcessService.getAddressOfFile(filename); + } + catch (Exception exc) + { + System.Windows.Forms.MessageBox.Show("Game is not running", "There was an error", System.Windows.Forms.MessageBoxButtons.OK, System.Windows.Forms.MessageBoxIcon.Error); + return; + } + + if (fileAddress == 0) + { + System.Windows.Forms.MessageBox.Show("Couldn't find file", "There was an error", System.Windows.Forms.MessageBoxButtons.OK, System.Windows.Forms.MessageBoxIcon.Error); + return; + } + + int entryOffset = -1; + foreach (Bar.Entry entry in ApdxService.Instance.ApdxBar) + { + if (entry.Type == Bar.EntryType.Pax) + { + entryOffset = entry.Offset; + break; + } + } + if (entryOffset == -1) + { + System.Windows.Forms.MessageBox.Show("AI file not found", "There was an error", System.Windows.Forms.MessageBoxButtons.OK, System.Windows.Forms.MessageBoxIcon.Error); + return; + } + + // ELEMENTS + int paxHeaderSize = 16; + long elementsAddress = fileAddress + entryOffset + paxHeaderSize; + MemoryStream elementsStream = new MemoryStream(); + foreach (var elem in ApdxService.Instance.PaxFile.Elements) + { + BinaryMapping.WriteObject(elementsStream, elem); + } + byte[] elementsBytes = elementsStream.ToArray(); + MemoryAccess.writeMemory(ProcessService.KH2Process, elementsAddress, elementsBytes, true); + + // EFFECTS - Not as simple due to Ids being based on offsets + //int dpxHeaderSize = 16; + //int effectSize = 32; + //long effectsAddress = fileAddress + entryOffset + ApdxService.Instance.PaxFile.Header.DpxOffset + dpxHeaderSize; + //for (int i = 0; i < ApdxService.Instance.PaxFile.DpxPackage.ParticleEffects.Count; i++) + //{ + //long effectAddress = effectsAddress + (effectSize * i) + 4; // First int is a dynamic offset + //var dpd = ApdxService.Instance.PaxFile.DpxPackage.ParticleEffects[i]; + //MemoryStream effectsStream = new MemoryStream(); + //BinaryMapping.WriteObject(effectsStream, dpd); + //byte[] effectsBytes = effectsStream.ToArray().Skip(4).ToArray(); + //MemoryAccess.writeMemory(ProcessService.KH2Process, effectAddress, effectsBytes, true); + //} + } } } diff --git a/OpenKh.Tools.Kh2ObjectEditor/Modules/Motions/FrameTriggerConverter.cs b/OpenKh.Tools.Kh2ObjectEditor/Modules/Motions/FrameTriggerConverter.cs new file mode 100644 index 000000000..190ce98cf --- /dev/null +++ b/OpenKh.Tools.Kh2ObjectEditor/Modules/Motions/FrameTriggerConverter.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using System.Windows.Data; + +namespace OpenKh.Tools.Kh2ObjectEditor.Modules.Motions +{ + public class FrameTriggerConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) + { + if (value is byte byteValue) + { + if (TriggerDictionary.Frame.TryGetValue(byteValue, out string stringValue)) + { + return stringValue; + } + } + return value.ToString(); + } + + public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) + { + if (value is string stringValue && parameter is Dictionary dictionary) + { + foreach (var kvp in TriggerDictionary.Frame) + { + if (kvp.Value == stringValue) + { + return kvp.Key; + } + } + } + return value; + } + } +} diff --git a/OpenKh.Tools.Kh2ObjectEditor/Modules/Motions/ModuleMotions_Control.xaml b/OpenKh.Tools.Kh2ObjectEditor/Modules/Motions/ModuleMotions_Control.xaml index 7f2a913db..c0dabd0ee 100644 --- a/OpenKh.Tools.Kh2ObjectEditor/Modules/Motions/ModuleMotions_Control.xaml +++ b/OpenKh.Tools.Kh2ObjectEditor/Modules/Motions/ModuleMotions_Control.xaml @@ -33,12 +33,15 @@ + + + - + @@ -51,8 +54,17 @@ + + + + + + + + + diff --git a/OpenKh.Tools.Kh2ObjectEditor/Modules/Motions/ModuleMotions_Control.xaml.cs b/OpenKh.Tools.Kh2ObjectEditor/Modules/Motions/ModuleMotions_Control.xaml.cs index 62e313928..f425d2162 100644 --- a/OpenKh.Tools.Kh2ObjectEditor/Modules/Motions/ModuleMotions_Control.xaml.cs +++ b/OpenKh.Tools.Kh2ObjectEditor/Modules/Motions/ModuleMotions_Control.xaml.cs @@ -1,10 +1,15 @@ +using Microsoft.Win32; +using OpenKh.AssimpUtils; using OpenKh.Kh2; using OpenKh.Tools.Kh2ObjectEditor.Classes; using OpenKh.Tools.Kh2ObjectEditor.Modules.Motions; using OpenKh.Tools.Kh2ObjectEditor.Services; using OpenKh.Tools.Kh2ObjectEditor.ViewModel; +using System; +using System.IO; using System.Windows; using System.Windows.Controls; +using System.Windows.Input; namespace OpenKh.Tools.Kh2ObjectEditor.Views { @@ -26,20 +31,44 @@ private void Button_ApplyFilters(object sender, System.Windows.RoutedEventArgs e private void list_doubleCLick(object sender, System.Windows.Input.MouseButtonEventArgs e) { MotionSelector_Wrapper item = (MotionSelector_Wrapper)(sender as ListView).SelectedItem; - if (item != null && !item.Name.Contains("DUMM")) + + + // Reaction Command + if (item.Entry.Type == BinaryArchive.EntryType.Motionset) { - try - { - App_Context.Instance.loadMotion(item.Index); - //Mset_Service.Instance.loadMotion(item.Index); - openMotionTabs(MsetService.Instance.LoadedMotion); - } - catch (System.Exception exc) - { - System.Windows.Forms.MessageBox.Show("Maybe you are trying to open a Reaction Command motion", "Animation couldn't be loaded", System.Windows.Forms.MessageBoxButtons.OK, System.Windows.Forms.MessageBoxIcon.Error); - return; - } - } + System.Windows.Forms.MessageBox.Show("This is a Reaction Command", "Motion couldn't be loaded", System.Windows.Forms.MessageBoxButtons.OK, System.Windows.Forms.MessageBoxIcon.Error); + return; + } + // No motion + else if(item.Entry.Type != BinaryArchive.EntryType.Anb) + { + System.Windows.Forms.MessageBox.Show("This is not a Motion or Reaction Command", "Motion couldn't be loaded", System.Windows.Forms.MessageBoxButtons.OK, System.Windows.Forms.MessageBoxIcon.Error); + return; + } + + // Invalid + if (item == null || item.Name.Contains("DUMM")) + { + return; + } + // Unnamed dummy + if (item.LinkedSubfile.Length == 0) + { + System.Windows.Forms.MessageBox.Show("This motion is a dummy (No data)", "Motion couldn't be loaded", System.Windows.Forms.MessageBoxButtons.OK, System.Windows.Forms.MessageBoxIcon.Error); + return; + } + + try + { + App_Context.Instance.loadMotion(item.Index); + //Mset_Service.Instance.loadMotion(item.Index); + openMotionTabs(MsetService.Instance.LoadedMotion); + } + catch (System.Exception exc) + { + System.Windows.Forms.MessageBox.Show("There was an error", "Animation couldn't be loaded", System.Windows.Forms.MessageBoxButtons.OK, System.Windows.Forms.MessageBoxIcon.Error); + return; + } } private void openMotionTabs(AnimationBinary animBinary) @@ -47,6 +76,45 @@ private void openMotionTabs(AnimationBinary animBinary) Frame_Metadata.Content = new MotionMetadata_Control(animBinary); Frame_Triggers.Content = new MotionTriggers_Control(animBinary); } + public void Motion_Rename(object sender, RoutedEventArgs e) + { + if (MotionList.SelectedItem != null) + { + MotionSelector_Wrapper item = (MotionSelector_Wrapper)MotionList.SelectedItem; + MotionRenameWindow newWindow = new MotionRenameWindow(item.Index, this); + newWindow.Show(); + } + } + public void Motion_AddNew(object sender, RoutedEventArgs e) + { + if (MotionList.SelectedItem != null) + { + + MotionSelector_Wrapper item = (MotionSelector_Wrapper)MotionList.SelectedItem; + + BinaryArchive.Entry newMotionEntry = new BinaryArchive.Entry(); + newMotionEntry.Name = "NDUM"; + newMotionEntry.Type = BinaryArchive.EntryType.Anb; + newMotionEntry.Link = -1; + + MsetService.Instance.MsetBinarc.Entries.Insert(item.Index + 1, new BinaryArchive.Entry()); + + ThisVM.loadMotions(); + ThisVM.applyFilters(); + } + } + public void Motion_Remove(object sender, RoutedEventArgs e) + { + if (MotionList.SelectedItem != null) + { + MotionSelector_Wrapper item = (MotionSelector_Wrapper)MotionList.SelectedItem; + + MsetService.Instance.MsetBinarc.Entries.RemoveAt(item.Index); + + ThisVM.loadMotions(); + ThisVM.applyFilters(); + } + } public void Motion_Copy(object sender, RoutedEventArgs e) { if (MotionList.SelectedItem != null) @@ -63,5 +131,143 @@ public void Motion_Replace(object sender, RoutedEventArgs e) ThisVM.Motion_Replace(item.Index); } } + public void Motion_Export(object sender, RoutedEventArgs e) + { + if (MotionList.SelectedItem == null || MdlxService.Instance.ModelFile == null) + { + return; + } + + MotionSelector_Wrapper item = (MotionSelector_Wrapper)MotionList.SelectedItem; + AnimationBinary animation; + using (MemoryStream memStream = new MemoryStream(item.LinkedSubfile)) + { + animation = new AnimationBinary(memStream); + } + + Kh2.Models.ModelSkeletal model = null; + foreach(Bar.Entry barEntry in MdlxService.Instance.MdlxBar) + { + if(barEntry.Type == Bar.EntryType.Model) + { + model = Kh2.Models.ModelSkeletal.Read(barEntry.Stream); + barEntry.Stream.Position = 0; + } + } + Assimp.Scene scene = Kh2MdlxAssimp.getAssimpScene(model); + Kh2MdlxAssimp.AddAnimation(scene, MdlxService.Instance.MdlxBar, animation); + + System.Windows.Forms.SaveFileDialog sfd; + sfd = new System.Windows.Forms.SaveFileDialog(); + sfd.Title = "Export animated model"; + sfd.FileName = MdlxService.Instance.MdlxPath + "." + AssimpGeneric.GetFormatFileExtension(AssimpGeneric.FileFormat.fbx); + sfd.ShowDialog(); + if (sfd.FileName != "") + { + string dirPath = Path.GetDirectoryName(sfd.FileName); + + if (!Directory.Exists(dirPath)) + return; + + dirPath += "\\"; + + AssimpGeneric.ExportScene(scene, AssimpGeneric.FileFormat.fbx, sfd.FileName); + } + } + public void Motion_Import(object sender, RoutedEventArgs e) + { + if (MotionList.SelectedItem == null) { + return; + } + + Frame_Metadata.Content = new ContentControl(); + Frame_Triggers.Content = new ContentControl(); + + MotionSelector_Wrapper item = (MotionSelector_Wrapper)MotionList.SelectedItem; + + try + { + OpenFileDialog openFileDialog = new OpenFileDialog(); + openFileDialog.Multiselect = false; + if (openFileDialog.ShowDialog() == true && openFileDialog.FileNames != null && openFileDialog.FileNames.Length > 0) + { + string filePath = openFileDialog.FileNames[0]; + if (filePath.ToLower().EndsWith(".fbx")) + { + ThisVM.Motion_Import(item.Index, filePath); + } + } + + App_Context.Instance.loadMotion(item.Index); + openMotionTabs(MsetService.Instance.LoadedMotion); + } + catch (Exception exception) { } + } + public void RC_Export(object sender, RoutedEventArgs e) + { + if (MotionList.SelectedItem == null) { + return; + } + + MotionSelector_Wrapper item = (MotionSelector_Wrapper)MotionList.SelectedItem; + + if(item.Entry.Type != BinaryArchive.EntryType.Motionset) { + System.Windows.Forms.MessageBox.Show("The selected entry is not a Moveset", "Can't export entry", System.Windows.Forms.MessageBoxButtons.OK, System.Windows.Forms.MessageBoxIcon.Error); + return; + } + + try + { + System.Windows.Forms.SaveFileDialog sfd; + sfd = new System.Windows.Forms.SaveFileDialog(); + sfd.Title = "Export Reaction Command"; + sfd.FileName = item.Entry.Name + ".mset"; + sfd.ShowDialog(); + if (sfd.FileName != "") + { + File.WriteAllBytes(sfd.FileName, item.LinkedSubfile); + } + } + catch (Exception exc) { } + } + public void RC_Replace(object sender, RoutedEventArgs e) + { + if (MotionList.SelectedItem == null) { + return; + } + + MotionSelector_Wrapper item = (MotionSelector_Wrapper)MotionList.SelectedItem; + + try + { + OpenFileDialog openFileDialog = new OpenFileDialog(); + openFileDialog.Multiselect = false; + if (openFileDialog.ShowDialog() == true && openFileDialog.FileNames != null && openFileDialog.FileNames.Length > 0) + { + string filePath = openFileDialog.FileNames[0]; + if (filePath.ToLower().EndsWith(".mset")) + { + ThisVM.Motion_ImportRC(item.Index, filePath); + } + } + + openMotionTabs(MsetService.Instance.LoadedMotion); + } + catch (Exception exception) { } + } + + private void Button_TEST(object sender, System.Windows.RoutedEventArgs e) + { + ThisVM.TestMsetIngame(); + } + + private void FilterName_PreviewKeyDown(object sender, System.Windows.Input.KeyEventArgs e) + { + if (e.Key == Key.Return) + { + ThisVM.FilterName = FilterName.Text; + ThisVM.applyFilters(); + } + } } } diff --git a/OpenKh.Tools.Kh2ObjectEditor/Modules/Motions/ModuleMotions_VM.cs b/OpenKh.Tools.Kh2ObjectEditor/Modules/Motions/ModuleMotions_VM.cs index 1a2bdeed6..3f94b836e 100644 --- a/OpenKh.Tools.Kh2ObjectEditor/Modules/Motions/ModuleMotions_VM.cs +++ b/OpenKh.Tools.Kh2ObjectEditor/Modules/Motions/ModuleMotions_VM.cs @@ -1,10 +1,19 @@ +using Assimp; +using OpenKh.Command.AnbMaker.Utils.AssimpAnimSource; +using OpenKh.Command.AnbMaker.Utils.Builder; +using OpenKh.Command.AnbMaker.Utils.Builder.Models; using OpenKh.Kh2; using OpenKh.Tools.Kh2ObjectEditor.Classes; using OpenKh.Tools.Kh2ObjectEditor.Services; using OpenKh.Tools.Kh2ObjectEditor.Utils; +using OpenKh.Tools.Kh2ObjectEditor.ViewModel; +using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.IO; +using System.Linq; +using System.Text.RegularExpressions; +using Xe.BinaryMapper; namespace OpenKh.Tools.Kh2ObjectEditor.Modules.Motions { @@ -13,7 +22,6 @@ public class ModuleMotions_VM : NotifyPropertyChangedBase // MOTIONS public List Motions { get; set; } public ObservableCollection MotionsView { get; set; } - public Bar.Entry copiedMotion { get; set; } // FILTERS private string _filterName { get; set; } @@ -51,13 +59,13 @@ public void loadMotions() { Motions.Clear(); - if (MsetService.Instance.MsetBar == null) + if (MsetService.Instance.MsetBinarc == null) return; Motions = new List(); - for (int i = 0; i < MsetService.Instance.MsetBar.Count; i++) + for (int i = 0; i < MsetService.Instance.MsetBinarc.Entries.Count; i++) { - Motions.Add(new MotionSelector_Wrapper(i, MsetService.Instance.MsetBar[i])); + Motions.Add(new MotionSelector_Wrapper(i, MsetService.Instance.MsetBinarc.Entries[i])); } } @@ -81,30 +89,206 @@ public void applyFilters() public void Motion_Copy(int index) { - Bar.Entry item = MsetService.Instance.MsetBar[index]; - copiedMotion = new Bar.Entry(); - copiedMotion.Index = item.Index; - copiedMotion.Name = item.Name; - copiedMotion.Type = item.Type; - item.Stream.Position = 0; - copiedMotion.Stream = new MemoryStream(); - item.Stream.CopyTo(copiedMotion.Stream); - item.Stream.Position = 0; - copiedMotion.Stream.Position = 0; + ClipboardService.Instance.StoreMotion(MsetService.Instance.MsetBinarc.Subfiles[MsetService.Instance.MsetBinarc.Entries[index].Link]); } public void Motion_Replace(int index) { - if (copiedMotion == null) - return; + MsetService.Instance.MsetBinarc.Subfiles.Add(ClipboardService.Instance.FetchMotion()); + MsetService.Instance.MsetBinarc.Entries[index].Type = BinaryArchive.EntryType.Anb; + MsetService.Instance.MsetBinarc.Entries[index].Name = "COPY"; + MsetService.Instance.MsetBinarc.Entries[index].Link = MsetService.Instance.MsetBinarc.Subfiles.Count - 1; - MsetService.Instance.MsetBar[index] = copiedMotion; loadMotions(); applyFilters(); + App_Context.Instance.loadMotion(index); } public void Motion_Rename(int index) { - Bar.Entry item = MsetService.Instance.MsetBar[index]; + //Bar.Entry item = MsetService.Instance.MsetBar[index]; + } + public void Motion_Import(int index, string animationPath) + { + // Find rootNode + var assimp = new Assimp.AssimpContext(); + var scene = assimp.ImportFile(animationPath, Assimp.PostProcessSteps.None); + + string rootNodeName = getRootNodeName(scene.RootNode); + + // Convert to interpolated motion + IEnumerable parms; + parms = new UseAssimp( + inputModel: animationPath, + meshName: null, + rootName: rootNodeName, + animationName: null, + nodeScaling: 1, + positionScaling: 1 + ) + .Parameters; + + List parList = parms.ToList(); + var builder = new InterpolatedMotionBuilder(parList[0]); + var ipm = builder.Ipm; + + // Get as stream + MemoryStream motionStream = (MemoryStream)ipm.toStream(); + + // Insert to mset + int subfileIndex = MsetService.Instance.MsetBinarc.Entries[index].Link; + MemoryStream replaceMotionStream = new MemoryStream(MsetService.Instance.MsetBinarc.Subfiles[subfileIndex]); + AnimationBinary msetEntry = new AnimationBinary(replaceMotionStream); + msetEntry.MotionFile = new Motion.InterpolatedMotion(motionStream); + MsetService.Instance.MsetBinarc.Subfiles[subfileIndex] = ((MemoryStream)msetEntry.toStream()).ToArray(); + + loadMotions(); + } + public void Motion_ImportRC(int index, string msetPath) + { + using FileStream streamMset = File.Open(msetPath, FileMode.Open); + if (!Bar.IsValid(streamMset)) + throw new Exception("File is not a valid MSET: " + msetPath); + + + MemoryStream msetStream = new MemoryStream(); + streamMset.CopyTo(msetStream); + + MsetService.Instance.MsetBinarc.Subfiles[MsetService.Instance.MsetBinarc.Entries[index].Link] = msetStream.ToArray(); + MsetService.Instance.MsetBinarc.Entries[index].Type = BinaryArchive.EntryType.Motionset; + } + + + + // Finds the node that serves as root for the animation + private string getRootNodeName(Node rootNode) + { + string rootNodeName = ""; + if (isRootName(rootNode.Name)) + { + rootNodeName = rootNode.Name; + } + else + { + List nodeNames = GetAllNames(rootNode); + foreach (string nodeName in nodeNames) + { + if (isRootName(nodeName)) + { + rootNodeName = nodeName; + break; + } + } + } + return rootNodeName; + } + private static List GetAllNames(Node node) + { + List names = new List(); + if (node != null) + { + Traverse(node, names); + } + return names; + } + private static void Traverse(Node node, List names) + { + names.Add(node.Name); + foreach (Node child in node.Children) + { + Traverse(child, names); + } + } + // Returns true if the given string contains the word bone, contains numbers and all of its numbers are zeros + private bool isRootName(string nodeName) + { + if (nodeName.ToLower().Contains("bone")) + { + // Regex to find all numbers in the string + Regex numberPattern = new Regex(@"\d+"); + MatchCollection matches = numberPattern.Matches(nodeName); + + foreach (Match match in matches) + { + string number = match.Value; + if(number == "" || match.Value.Trim('0') != "") { + return false; + } + } + + return true; + } + return false; + } + + // Tests the mset ingame. Writes the motion header and motion triggers. + // IMPORTANT: Triggers must be of the same length, be careful because there's no control of this. + public void TestMsetIngame() + { + string filename = Path.GetFileName(MsetService.Instance.MsetPath); + + if (filename == "") + return; + + long fileAddress; + try + { + fileAddress = ProcessService.getAddressOfFile(filename); + } + catch (Exception exc) + { + System.Windows.Forms.MessageBox.Show("Game is not running", "There was an error", System.Windows.Forms.MessageBoxButtons.OK, System.Windows.Forms.MessageBoxIcon.Error); + return; + } + + if (fileAddress == 0) + { + System.Windows.Forms.MessageBox.Show("Couldn't find file", "There was an error", System.Windows.Forms.MessageBoxButtons.OK, System.Windows.Forms.MessageBoxIcon.Error); + return; + } + + byte[] msetBytes = MsetService.Instance.MsetBinarc.getAsByteArray(); + Bar msetBar = Bar.Read(new MemoryStream(msetBytes)); + foreach (Bar.Entry barMotion in msetBar) + { + if (barMotion.Stream.Length == 0 || barMotion.Type != Bar.EntryType.Anb) + continue; + + int entryOffset = barMotion.Offset; + + // Read Anb + barMotion.Stream.Position = 0; + AnimationBinary motionAnb = new AnimationBinary(barMotion.Stream); + barMotion.Stream.Position = 0; + + // Get trigger file offset + Bar barMotionAsBar = Bar.Read(barMotion.Stream); + barMotion.Stream.Position = 0; + int motionOffset = barMotionAsBar[0].Offset; + int triggerOffset = barMotionAsBar[1].Offset; + int reservedLength = 144; + + long motionFileOffset = fileAddress + entryOffset + motionOffset + reservedLength; + long triggerFileOffset = fileAddress + entryOffset + triggerOffset; + + // Write to process memory + if (motionAnb.MotionFile != null && motionAnb.MotionFile.MotionHeader.Type == 0) + { + MemoryStream motionStream = new MemoryStream(); + BinaryWriter writer = new BinaryWriter(motionStream); + + BinaryMapping.WriteObject(motionStream, motionAnb.MotionFile.MotionHeader); + BinaryMapping.WriteObject(motionStream, motionAnb.MotionFile.InterpolatedMotionHeader); + + byte[] motionBytes = motionStream.ToArray(); + MemoryAccess.writeMemory(ProcessService.KH2Process, motionFileOffset, motionBytes, true); + } + if (motionAnb.MotionTriggerFile != null) + { + MemoryStream triggerStream = (MemoryStream)motionAnb.MotionTriggerFile.toStream(); + byte[] triggerBytes = triggerStream.ToArray(); + MemoryAccess.writeMemory(ProcessService.KH2Process, triggerFileOffset, triggerBytes, true); + } + } } } } diff --git a/OpenKh.Tools.Kh2ObjectEditor/Modules/Motions/MotionMetadata_Control.xaml b/OpenKh.Tools.Kh2ObjectEditor/Modules/Motions/MotionMetadata_Control.xaml index 58e8e551f..a56198030 100644 --- a/OpenKh.Tools.Kh2ObjectEditor/Modules/Motions/MotionMetadata_Control.xaml +++ b/OpenKh.Tools.Kh2ObjectEditor/Modules/Motions/MotionMetadata_Control.xaml @@ -6,7 +6,7 @@ xmlns:local="clr-namespace:OpenKh.Tools.Kh2ObjectEditor.Modules.Motions" mc:Ignorable="d" d:DesignHeight="450" d:DesignWidth="800"> - + @@ -29,6 +29,7 @@ + @@ -55,6 +56,7 @@ + - + diff --git a/OpenKh.Tools.Kh2ObjectEditor/Modules/Motions/MotionMetadata_Control.xaml.cs b/OpenKh.Tools.Kh2ObjectEditor/Modules/Motions/MotionMetadata_Control.xaml.cs index b18756a8c..26dbc4a0f 100644 --- a/OpenKh.Tools.Kh2ObjectEditor/Modules/Motions/MotionMetadata_Control.xaml.cs +++ b/OpenKh.Tools.Kh2ObjectEditor/Modules/Motions/MotionMetadata_Control.xaml.cs @@ -1,4 +1,6 @@ using OpenKh.Kh2; +using OpenKh.Tools.Kh2ObjectEditor.Services; +using System.Windows; using System.Windows.Controls; namespace OpenKh.Tools.Kh2ObjectEditor.Modules.Motions @@ -12,5 +14,10 @@ public MotionMetadata_Control(AnimationBinary animationBinary) AnimBinary = animationBinary; DataContext = AnimBinary; } + + private void Button_SaveTriggers(object sender, RoutedEventArgs e) + { + MsetService.Instance.SaveMotion(); + } } } diff --git a/OpenKh.Tools.Kh2ObjectEditor/Modules/Motions/MotionRenameWindow.xaml b/OpenKh.Tools.Kh2ObjectEditor/Modules/Motions/MotionRenameWindow.xaml new file mode 100644 index 000000000..23b4c5a38 --- /dev/null +++ b/OpenKh.Tools.Kh2ObjectEditor/Modules/Motions/MotionRenameWindow.xaml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + diff --git a/OpenKh.Tools.Kh2ObjectEditor/Modules/Motions/MotionRenameWindow.xaml.cs b/OpenKh.Tools.Kh2ObjectEditor/Modules/Motions/MotionRenameWindow.xaml.cs new file mode 100644 index 000000000..b0c3845bf --- /dev/null +++ b/OpenKh.Tools.Kh2ObjectEditor/Modules/Motions/MotionRenameWindow.xaml.cs @@ -0,0 +1,31 @@ +using OpenKh.Tools.Kh2ObjectEditor.Services; +using OpenKh.Tools.Kh2ObjectEditor.Views; +using System.Windows; + +namespace OpenKh.Tools.Kh2ObjectEditor.Modules.Motions +{ + public partial class MotionRenameWindow : Window + { + int MotionIndex { get; set; } + ModuleMotions_Control ParentControl { get; set; } + public MotionRenameWindow(int motionIndex, ModuleMotions_Control parent) + { + MotionIndex = motionIndex; + ParentControl = parent; + InitializeComponent(); + NameTextBox.Text = MsetService.Instance.MsetBinarc.Entries[MotionIndex].Name; + } + + private void Button_Rename(object sender, RoutedEventArgs e) + { + MsetService.Instance.MsetBinarc.Entries[MotionIndex].Name = NameTextBox.Text; + ParentControl.ThisVM.Motions[MotionIndex].setName(); + ParentControl.ThisVM.applyFilters(); + this.Close(); + } + private void Button_Cancel(object sender, RoutedEventArgs e) + { + this.Close(); + } + } +} diff --git a/OpenKh.Tools.Kh2ObjectEditor/Modules/Motions/MotionTriggers_Control.xaml b/OpenKh.Tools.Kh2ObjectEditor/Modules/Motions/MotionTriggers_Control.xaml index 308b8ba27..30b05caa3 100644 --- a/OpenKh.Tools.Kh2ObjectEditor/Modules/Motions/MotionTriggers_Control.xaml +++ b/OpenKh.Tools.Kh2ObjectEditor/Modules/Motions/MotionTriggers_Control.xaml @@ -1,9 +1,9 @@ - @@ -16,20 +16,38 @@ - - - - - + + + + + - + - + + + + + + - + + + + + + + + + + + + + + @@ -41,16 +59,37 @@ - + - + + + + + + - + + + + + + + + + + + + + + diff --git a/OpenKh.Tools.Kh2ObjectEditor/Modules/Motions/MotionTriggers_Control.xaml.cs b/OpenKh.Tools.Kh2ObjectEditor/Modules/Motions/MotionTriggers_Control.xaml.cs index 29e5f9af8..9cc807d1d 100644 --- a/OpenKh.Tools.Kh2ObjectEditor/Modules/Motions/MotionTriggers_Control.xaml.cs +++ b/OpenKh.Tools.Kh2ObjectEditor/Modules/Motions/MotionTriggers_Control.xaml.cs @@ -1,11 +1,9 @@ using OpenKh.Kh2; -using OpenKh.Tools.Kh2ObjectEditor.Modules.Motions; using OpenKh.Tools.Kh2ObjectEditor.Services; -using OpenKh.Tools.Kh2ObjectEditor.ViewModel; using System.Windows; using System.Windows.Controls; -namespace OpenKh.Tools.Kh2ObjectEditor.Views +namespace OpenKh.Tools.Kh2ObjectEditor.Modules.Motions { public partial class MotionTriggers_Control : UserControl { @@ -20,7 +18,7 @@ public MotionTriggers_Control(AnimationBinary animBinary) private void Button_SaveTriggers(object sender, RoutedEventArgs e) { - MsetService.Instance.SaveMotion(); + ThisVM.saveMotion(); } private void Button_CreateTriggers(object sender, RoutedEventArgs e) { @@ -31,6 +29,7 @@ private void Button_CreateTriggers(object sender, RoutedEventArgs e) MsetService.Instance.LoadedMotion.MotionTriggerFile = new MotionTrigger(); MsetService.Instance.SaveMotion(); + ThisVM.loadLists(); } } } diff --git a/OpenKh.Tools.Kh2ObjectEditor/Modules/Motions/MotionTriggers_VM.cs b/OpenKh.Tools.Kh2ObjectEditor/Modules/Motions/MotionTriggers_VM.cs index a798d6c38..bfe4c94ab 100644 --- a/OpenKh.Tools.Kh2ObjectEditor/Modules/Motions/MotionTriggers_VM.cs +++ b/OpenKh.Tools.Kh2ObjectEditor/Modules/Motions/MotionTriggers_VM.cs @@ -1,4 +1,8 @@ using OpenKh.Kh2; +using OpenKh.Tools.Kh2ObjectEditor.Services; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using static OpenKh.Kh2.MotionTrigger; namespace OpenKh.Tools.Kh2ObjectEditor.Modules.Motions { @@ -6,6 +10,12 @@ public class MotionTriggers_VM { public AnimationBinary AnimBinary { get; set; } + public ObservableCollection RangeTriggerList { get; set; } + public ObservableCollection FrameTriggerList { get; set; } + + public static Dictionary RangeTriggerOptions { get; set; } + public static Dictionary FrameTriggerOptions { get; set; } + public bool HasNoTriggers { get @@ -17,7 +27,49 @@ public bool HasNoTriggers public MotionTriggers_VM() { } public MotionTriggers_VM(AnimationBinary animBinary) { + loadOptions(); AnimBinary = animBinary; + + RangeTriggerList = new ObservableCollection(); + FrameTriggerList = new ObservableCollection(); + loadLists(); + } + + public void loadLists() + { + if (AnimBinary.MotionTriggerFile != null) + { + RangeTriggerList.Clear(); + foreach (RangeTrigger trigger in AnimBinary.MotionTriggerFile.RangeTriggerList) + { + RangeTriggerList.Add(trigger); + } + FrameTriggerList.Clear(); + foreach (FrameTrigger trigger in AnimBinary.MotionTriggerFile.FrameTriggerList) + { + FrameTriggerList.Add(trigger); + } + } + } + public void saveMotion() + { + AnimBinary.MotionTriggerFile.RangeTriggerList.Clear(); + foreach (RangeTrigger trigger in RangeTriggerList) + { + AnimBinary.MotionTriggerFile.RangeTriggerList.Add(trigger); + } + AnimBinary.MotionTriggerFile.FrameTriggerList.Clear(); + foreach (FrameTrigger trigger in FrameTriggerList) + { + AnimBinary.MotionTriggerFile.FrameTriggerList.Add(trigger); + } + MsetService.Instance.SaveMotion(); + } + + private void loadOptions() + { + RangeTriggerOptions = TriggerDictionary.Range; + FrameTriggerOptions = TriggerDictionary.Frame; } } } diff --git a/OpenKh.Tools.Kh2ObjectEditor/Modules/Motions/RangeTriggerConverter.cs b/OpenKh.Tools.Kh2ObjectEditor/Modules/Motions/RangeTriggerConverter.cs new file mode 100644 index 000000000..12f71e6a4 --- /dev/null +++ b/OpenKh.Tools.Kh2ObjectEditor/Modules/Motions/RangeTriggerConverter.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using System.Windows.Data; + +namespace OpenKh.Tools.Kh2ObjectEditor.Modules.Motions +{ + public class RangeTriggerConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) + { + if (value is byte byteValue) + { + if (TriggerDictionary.Range.TryGetValue(byteValue, out string stringValue)) + { + return stringValue; + } + } + return value.ToString(); + } + + public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) + { + if (value is string stringValue && parameter is Dictionary dictionary) + { + foreach (var kvp in TriggerDictionary.Range) + { + if (kvp.Value == stringValue) + { + return kvp.Key; + } + } + } + return value; + } + } +} diff --git a/OpenKh.Tools.Kh2ObjectEditor/Modules/Motions/TriggerDictionary.cs b/OpenKh.Tools.Kh2ObjectEditor/Modules/Motions/TriggerDictionary.cs new file mode 100644 index 000000000..64336efc8 --- /dev/null +++ b/OpenKh.Tools.Kh2ObjectEditor/Modules/Motions/TriggerDictionary.cs @@ -0,0 +1,104 @@ +using System.Collections.Generic; + +namespace OpenKh.Tools.Kh2ObjectEditor.Modules.Motions +{ + public class TriggerDictionary + { + public static Dictionary Range = new Dictionary + { +{0, "[0] STATE: Grounded"}, +{1, "[1] STATE: Air"}, +{2, "[2] STATE: Blend to idle"}, +{3, "[3] STATE: No gravity"}, +{4, "[4] Enable collision"}, +{5, "[5] Disable collision"}, +{6, "[6] Enable conditional RC"}, +{7, "[7] Disable conditional RC"}, // Unused +{8, "[8] "}, // Unused +{9, "[9] STATE: Jump/Land"}, +{10, "[10] Attack hitbox"}, +{11, "[11] STATE: Allow combo"}, +{12, "[12] STATE: Weapon swing (Display trail)"}, +{13, "[13] _STATE: ???"}, +{14, "[14] STATE: Allow RC"}, +{15, "[15] _STATE: ???"}, // Unused; srk: attack label +{16, "[16] AI: Special movement"}, +{17, "[17] _AI: ???"}, // srk: AI check's target position then determines if it can continue its combo or quits +{18, "[18] _AI: ???"}, // srk: AI check's target position then determines if it can continue its combo or quits +{19, "[19] AI: Disable invincibility"}, +{20, "[20] Reaction Command (Self)"}, +{21, "[21] _STATE: ???"}, +{22, "[22] ACTION: Turn to target"}, +{23, "[23] Texture Animation"}, +{24, "[24] _STATE: No gravity, keep kinetics"}, +{25, "[25] _STATE: AnmatrCommand"}, +{26, "[26] _STATE: AnmatrCommand 2"}, +{27, "[27] STATE: Hitbox off"}, +{28, "[28] ACTION: Turn to lock"}, +{29, "[29] STATE: Can't leave ground"}, +{30, "[30] _STATE: Freeze animation"}, +{31, "[31] _STATE: ???"}, // srk: vibration +{32, "[32] _STATE: ???"}, +{33, "[33] Attack hitbox (Combo)"}, // Enemy's combo for Once More +{34, "[34] _STATE: ???"}, +{35, "[35] STATE: Allow combo (Magic)"}, // Allows combo if Param1 = MOTION.Id; srk: Allows magic of [value] to combo. 56-58 fire, 59-61 blizzard, 62-64 thunder, 65-67 cure, 68-70 magnet, 71-73 reflect. Values on 00battle/magc +{36, "[36] Pattern enable"}, // Enables a pattern on the entity. Leads to an entry in the PATN table in 00battle. +{37, "[37] Pattern disable"}, // Disables a pattern applied to the entity. +{38, "[38] _STATE: Allow drop from on top"}, // Allows Sora to drop from top of entity +{39, "[39] _STATE: ???"}, +{40, "[40] _STATE: ???"}, +{41, "[41] _STATE: Allow movement"}, +{42, "[42] PHYSICS: Keep momentum & restrict movement"}, +{43, "[43] "}, +{44, "[44] PHYSICS: Immovable by friction"}, +{45, "[45] _ACTION: ???"}, +{46, "[46] _ACTION: "}, +{47, "[47] ACTION: Rotate towards movement direction"}, +{48, "[48] "}, +{49, "[49] ACTION: Maintain motion on ground leave"}, // Keeps the motion going after the entity has left the ground +{50, "[50] ACTION: Allow combo finisher"}, +{51, "[51] Play singleton sound effect"}, +{52, "[52] ACTION: Stop actions"}, +{53, "[53] _ACTION: ???"} + }; + public static Dictionary Frame = new Dictionary + { +{0, "[0] Action: Jump"}, +{1, "[1] Trigger effect caster"}, +{2, "[2] Play footstep sound"}, +{3, "[3] Action: Jump (Dusk)"}, // Motion slot 628 +{4, "[4] Texture animation start"}, +{5, "[5] Texture animation stop"}, +{6, "[6] Use item"}, +{7, "[7] Game Effect"}, // srk: AI activates a special projectile or alternate effect such as slowing down +{8, "[8] Play sound effect"}, +{9, "[9] _VariousTrigger 1"}, // Fat Bandit: start flamethrower; No params - Coded in AI? +{10, "[10] _VariousTrigger 2"}, // Fat Bandit: finish flamethrower; No params - Coded in AI? +{11, "[11] _VariousTrigger 4"}, +{12, "[12] _VariousTrigger 8"}, +{13, "[13] Play voice"}, +{14, "[14] Play voice"}, +{15, "[15] Turn to target"}, //srk: plays VAG lopVoice from AFM +{16, "[16] _DisableCommandTime"}, // srk: keyblade pop +{17, "[17] Cast magic"}, // srk: Allows magic of [value] to combo. 56-58 fire, 59-61 blizzard, 62-64 thunder, 65-67 cure, 68-70 magnet, 71-73 reflect. Values on 00battle/magc +{18, "[18] "}, +{19, "[19] Footstep effect"}, +{20, "[20] "}, +{21, "[21] _Turn to lockon"}, +{22, "[22] Make the weapon appear"}, +{23, "[23] Fade Out"}, +{24, "[24] Fade In"}, +{25, "[25] _Call entity function"}, +{26, "[26] Set mesh color grey"}, +{27, "[27] Reset mesh color"}, +{28, "[28] Revenge Check"}, +{29, "[29] Make the weapon appear with effect"}, +{30, "[30] _LIMIT:PlayVoice"}, +{31, "[31] Controller vibration"}, +{32, "[32] "}, +{33, "[33] "}, +{34, "[34] Quick run check"}, +{35, "[35] Transition to fall if on air"} + }; + } +} diff --git a/OpenKh.Tools.Kh2ObjectEditor/Modules/Textures/TextureAnimations_Control.xaml.cs b/OpenKh.Tools.Kh2ObjectEditor/Modules/Textures/TextureAnimations_Control.xaml.cs index 7db6500b2..9f4d983cc 100644 --- a/OpenKh.Tools.Kh2ObjectEditor/Modules/Textures/TextureAnimations_Control.xaml.cs +++ b/OpenKh.Tools.Kh2ObjectEditor/Modules/Textures/TextureAnimations_Control.xaml.cs @@ -69,6 +69,7 @@ public void Texture_Replace(object sender, RoutedEventArgs e) try { ThisVM.replaceImage(item.Id); + loadImage(item.Id); } catch (System.Exception exc) { diff --git a/OpenKh.Tools.Kh2ObjectEditor/Modules/Textures/TextureAnimations_VM.cs b/OpenKh.Tools.Kh2ObjectEditor/Modules/Textures/TextureAnimations_VM.cs index 1d52caf61..8762a879a 100644 --- a/OpenKh.Tools.Kh2ObjectEditor/Modules/Textures/TextureAnimations_VM.cs +++ b/OpenKh.Tools.Kh2ObjectEditor/Modules/Textures/TextureAnimations_VM.cs @@ -1,11 +1,8 @@ -using OpenKh.AssimpUtils; +using ModelingToolkit.Objects; using OpenKh.Kh2.TextureFooter; using OpenKh.Tools.Kh2ObjectEditor.Services; using OpenKh.Tools.Kh2ObjectEditor.Utils; -using System.Collections.Generic; using System.Collections.ObjectModel; -using System.Drawing; -using System.Drawing.Imaging; using System.IO; namespace OpenKh.Tools.Kh2ObjectEditor.Modules.Textures @@ -35,19 +32,16 @@ public void loadAnims() } public void exportImage(int id) { - List bitmaps = ImageUtils.footerToImages(MdlxService.Instance.TextureFile); - //BitmapSource bitmapImage = ImageUtils.BitmapToImageSource(bitmaps[id]); + MtMaterial mat = GetMaterial(id); System.Windows.Forms.SaveFileDialog sfd; sfd = new System.Windows.Forms.SaveFileDialog(); sfd.Title = "Export image as PNG"; - sfd.FileName = "Animation.png"; + sfd.FileName = Path.GetFileNameWithoutExtension(MdlxService.Instance.MdlxPath) + "_Animation_" +id+".png"; sfd.ShowDialog(); if (sfd.FileName != "") { - bitmaps[id].Save(sfd.FileName, ImageFormat.Png); - //MemoryStream memStream = new MemoryStream(); - //AssimpGeneric.ExportBitmapSourceAsPng(bitmapImage, sfd.FileName); + mat.ExportAsPng(sfd.FileName); } } public void removeImage(int id) @@ -74,10 +68,24 @@ public void replaceImage(int id) if (texAnim == null) return; - MdlxService.Instance.TextureFile.TextureFooterData.TextureAnimationList[id] = texAnim; - MdlxService.Instance.TextureFile.TextureFooterData.UvscList.Clear(); + MdlxService.Instance.TextureFile.TextureFooterData.TextureAnimationList[id].SpriteImage = texAnim.SpriteImage; + loadAnims(); } + public MtMaterial GetMaterial(int index) + { + Kh2.TextureFooter.TextureAnimation texAnim = MdlxService.Instance.TextureFile.TextureFooterData.TextureAnimationList[index]; + MtMaterial mat = new MtMaterial(); + mat.Data = texAnim.SpriteImage; + mat.Clut = MdlxService.Instance.TextureFile.Images[texAnim.TextureIndex].GetClut(); + mat.Width = texAnim.SpriteWidth; + mat.Height = texAnim.SpriteImage.Length / texAnim.SpriteWidth; + mat.ColorSize = 1; + mat.PixelHasAlpha = true; + mat.GenerateBitmap(); + + return mat; + } public class TexAnimWrapper { diff --git a/OpenKh.Tools.Kh2ObjectEditor/Modules/Textures/TextureImages_Control.xaml.cs b/OpenKh.Tools.Kh2ObjectEditor/Modules/Textures/TextureImages_Control.xaml.cs index bf7d0d260..4de4c0371 100644 --- a/OpenKh.Tools.Kh2ObjectEditor/Modules/Textures/TextureImages_Control.xaml.cs +++ b/OpenKh.Tools.Kh2ObjectEditor/Modules/Textures/TextureImages_Control.xaml.cs @@ -1,8 +1,6 @@ -using OpenKh.Tools.Common.Wpf; -using System.IO; +using ModelingToolkit.Objects; using System.Windows; using System.Windows.Controls; -using System.Windows.Media.Imaging; using static OpenKh.Tools.Kh2ObjectEditor.Modules.Textures.TextureImages_VM; namespace OpenKh.Tools.Kh2ObjectEditor.Modules.Textures @@ -24,8 +22,15 @@ public TextureImages_Control() public void loadImage(int index) { - BitmapSource BitmapImage = ThisVM.Textures[index].Texture.GetBimapSource(); - ImageFrame.Source = BitmapImage; + MtMaterial mat = new MtMaterial(); + mat.Data = ThisVM.Textures[index].Texture.GetData(); + mat.Clut = ThisVM.Textures[index].Texture.GetClut(); + mat.Width = ThisVM.Textures[index].Texture.Size.Width; + mat.Height = ThisVM.Textures[index].Texture.Size.Height; + mat.ColorSize = 1; + mat.PixelHasAlpha = true; + mat.GenerateBitmap(); + ImageFrame.Source = mat.GetAsBitmapImage(); } private void list_doubleCLick(object sender, System.Windows.Input.MouseButtonEventArgs e) diff --git a/OpenKh.Tools.Kh2ObjectEditor/Modules/Textures/TextureImages_VM.cs b/OpenKh.Tools.Kh2ObjectEditor/Modules/Textures/TextureImages_VM.cs index 7adc1abb4..b702a959f 100644 --- a/OpenKh.Tools.Kh2ObjectEditor/Modules/Textures/TextureImages_VM.cs +++ b/OpenKh.Tools.Kh2ObjectEditor/Modules/Textures/TextureImages_VM.cs @@ -4,11 +4,9 @@ using OpenKh.Tools.Common.Wpf; using OpenKh.Tools.Kh2ObjectEditor.Services; using OpenKh.Tools.Kh2ObjectEditor.Utils; -using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.IO; -using System.Windows.Documents; using System.Windows.Media.Imaging; namespace OpenKh.Tools.Kh2ObjectEditor.Modules.Textures @@ -85,7 +83,6 @@ public void exportImage(int id) sfd.ShowDialog(); if (sfd.FileName != "") { - MemoryStream memStream = new MemoryStream(); AssimpGeneric.ExportBitmapSourceAsPng(bitmapImage, sfd.FileName); } } diff --git a/OpenKh.Tools.Kh2ObjectEditor/Modules/Textures/TextureSelectedAnimation_Control.xaml b/OpenKh.Tools.Kh2ObjectEditor/Modules/Textures/TextureSelectedAnimation_Control.xaml index 595fd6d33..c4a4db81a 100644 --- a/OpenKh.Tools.Kh2ObjectEditor/Modules/Textures/TextureSelectedAnimation_Control.xaml +++ b/OpenKh.Tools.Kh2ObjectEditor/Modules/Textures/TextureSelectedAnimation_Control.xaml @@ -14,7 +14,7 @@ - + @@ -23,9 +23,9 @@ + HorizontalAlignment="Left" + VerticalAlignment="Top" + Stretch="Uniform"/> diff --git a/OpenKh.Tools.Kh2ObjectEditor/Modules/Textures/TextureSelectedAnimation_Control.xaml.cs b/OpenKh.Tools.Kh2ObjectEditor/Modules/Textures/TextureSelectedAnimation_Control.xaml.cs index 94e7a04e8..078f24320 100644 --- a/OpenKh.Tools.Kh2ObjectEditor/Modules/Textures/TextureSelectedAnimation_Control.xaml.cs +++ b/OpenKh.Tools.Kh2ObjectEditor/Modules/Textures/TextureSelectedAnimation_Control.xaml.cs @@ -1,9 +1,5 @@ -using OpenKh.Tools.Kh2ObjectEditor.Services; -using OpenKh.Tools.Kh2ObjectEditor.Utils; -using System.Collections.Generic; -using System.Drawing; +using ModelingToolkit.Objects; using System.Windows.Controls; -using System.Windows.Media.Imaging; using static OpenKh.Tools.Kh2ObjectEditor.Modules.Textures.TextureSelectedAnimation_VM; namespace OpenKh.Tools.Kh2ObjectEditor.Modules.Textures @@ -21,9 +17,9 @@ public TextureSelectedAnimation_Control(int index) public void loadImage(int index) { - List bitmaps = ImageUtils.footerToImages(MdlxService.Instance.TextureFile); - BitmapSource BitmapImage = ImageUtils.BitmapToImageSource(bitmaps[index]); - ImageFrame.Source = BitmapImage; + MtMaterial mat = ThisVM.GetMaterial(index); + ImageFrame.Source = mat.GetAsBitmapImage(); + TexAnimCanvas.Height = mat.Height; } private void Script_Export(object sender, System.Windows.RoutedEventArgs e) diff --git a/OpenKh.Tools.Kh2ObjectEditor/Modules/Textures/TextureSelectedAnimation_VM.cs b/OpenKh.Tools.Kh2ObjectEditor/Modules/Textures/TextureSelectedAnimation_VM.cs index 3ddd88e3c..91aa6908f 100644 --- a/OpenKh.Tools.Kh2ObjectEditor/Modules/Textures/TextureSelectedAnimation_VM.cs +++ b/OpenKh.Tools.Kh2ObjectEditor/Modules/Textures/TextureSelectedAnimation_VM.cs @@ -1,3 +1,4 @@ +using ModelingToolkit.Objects; using OpenKh.Kh2.TextureFooter; using OpenKh.Tools.Kh2ObjectEditor.Services; using OpenKh.Tools.Kh2ObjectEditor.Utils; @@ -108,6 +109,20 @@ public TextureFrameGroup loadTextureFrameGroup() return null; } + public MtMaterial GetMaterial(int index) + { + Kh2.TextureFooter.TextureAnimation texAnim = MdlxService.Instance.TextureFile.TextureFooterData.TextureAnimationList[index]; + MtMaterial mat = new MtMaterial(); + mat.Data = texAnim.SpriteImage; + mat.Clut = MdlxService.Instance.TextureFile.Images[texAnim.TextureIndex].GetClut(); + mat.Width = texAnim.SpriteWidth; + mat.Height = texAnim.SpriteImage.Length / texAnim.SpriteWidth; + mat.ColorSize = 1; + mat.PixelHasAlpha = true; + mat.GenerateBitmap(); + + return mat; + } public class ScriptWrapper { diff --git a/OpenKh.Tools.Kh2ObjectEditor/Modules/UI/M_UI_Control.xaml b/OpenKh.Tools.Kh2ObjectEditor/Modules/UI/M_UI_Control.xaml new file mode 100644 index 000000000..6d18cb341 --- /dev/null +++ b/OpenKh.Tools.Kh2ObjectEditor/Modules/UI/M_UI_Control.xaml @@ -0,0 +1,73 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/OpenKh.Tools.Kh2ObjectEditor/Modules/UI/M_UI_Control.xaml.cs b/OpenKh.Tools.Kh2ObjectEditor/Modules/UI/M_UI_Control.xaml.cs new file mode 100644 index 000000000..3c54ed4e9 --- /dev/null +++ b/OpenKh.Tools.Kh2ObjectEditor/Modules/UI/M_UI_Control.xaml.cs @@ -0,0 +1,72 @@ +using OpenKh.AssimpUtils; +using OpenKh.Kh2; +using OpenKh.Tools.Common.Wpf; +using OpenKh.Tools.Kh2ObjectEditor.Services; +using OpenKh.Tools.Kh2ObjectEditor.Utils; +using System.IO; +using System.Windows.Controls; +using System.Windows.Media.Imaging; + +namespace OpenKh.Tools.Kh2ObjectEditor.Modules.UI +{ + public partial class M_UI_Control : UserControl + { + public M_UI_Control() + { + InitializeComponent(); + loadImages(); + } + + private void loadImages() + { + if(ApdxService.Instance.ImgdFace != null) + { + BitmapSource faceBitmap = ApdxService.Instance.ImgdFace.GetBimapSource(); + FaceFrame.Source = faceBitmap; + } + if (ApdxService.Instance.ImgdCommand != null) + { + BitmapSource commandBitmap = ApdxService.Instance.ImgdCommand.GetBimapSource(); + CommandFrame.Source = commandBitmap; + } + } + + private void Face_Export(object sender, System.Windows.RoutedEventArgs e) + { + exportImage(ApdxService.Instance.ImgdFace, "Face"); + } + private void Face_Replace(object sender, System.Windows.RoutedEventArgs e) + { + Imgd loadedImgd = ImageUtils.loadPngFileAsImgd(); + ApdxService.Instance.ImgdFace = loadedImgd; + loadImages(); + } + + private void Command_Export(object sender, System.Windows.RoutedEventArgs e) + { + exportImage(ApdxService.Instance.ImgdCommand, "Command"); + } + private void Command_Replace(object sender, System.Windows.RoutedEventArgs e) + { + Imgd loadedImgd = ImageUtils.loadPngFileAsImgd(); + ApdxService.Instance.ImgdCommand = loadedImgd; + loadImages(); + } + + private void exportImage(Imgd image, string imageName) + { + BitmapSource bitmapImage = image.GetBimapSource(); + + System.Windows.Forms.SaveFileDialog sfd; + sfd = new System.Windows.Forms.SaveFileDialog(); + sfd.Title = "Export image as PNG"; + sfd.FileName = imageName + ".png"; + sfd.ShowDialog(); + if (sfd.FileName != "") + { + MemoryStream memStream = new MemoryStream(); + AssimpGeneric.ExportBitmapSourceAsPng(bitmapImage, sfd.FileName); + } + } + } +} diff --git a/OpenKh.Tools.Kh2ObjectEditor/OpenKh.Tools.Kh2ObjectEditor.csproj b/OpenKh.Tools.Kh2ObjectEditor/OpenKh.Tools.Kh2ObjectEditor.csproj index 98b7c97c0..1d5a86679 100644 --- a/OpenKh.Tools.Kh2ObjectEditor/OpenKh.Tools.Kh2ObjectEditor.csproj +++ b/OpenKh.Tools.Kh2ObjectEditor/OpenKh.Tools.Kh2ObjectEditor.csproj @@ -8,7 +8,9 @@ + + diff --git a/OpenKh.Tools.Kh2ObjectEditor/Services/ApdxService.cs b/OpenKh.Tools.Kh2ObjectEditor/Services/ApdxService.cs index 0300ff771..b71b0465f 100644 --- a/OpenKh.Tools.Kh2ObjectEditor/Services/ApdxService.cs +++ b/OpenKh.Tools.Kh2ObjectEditor/Services/ApdxService.cs @@ -17,7 +17,8 @@ public class ApdxService public Pax PaxFile { get; set; } // 979 // Wd (32, BGM Instrument Data) - 734 // Seb (31) - 734 - public Imgd ImgdFile { get; set; } // 220 + public Imgd ImgdFace { get; set; } // 220 (face + comd) + public Imgd ImgdCommand { get; set; } // Seqd (25) - 220 // IopVoice (34) - 144 // Event (22) - 349 @@ -49,7 +50,14 @@ public void LoadFile(string filepath) PaxFile = new Pax(barEntry.Stream); break; case Bar.EntryType.Imgd: - ImgdFile = Imgd.Read(barEntry.Stream); + if(barEntry.Name == "face") + { + ImgdFace = Imgd.Read(barEntry.Stream); + } + else if(barEntry.Name == "comd") + { + ImgdCommand = Imgd.Read(barEntry.Stream); + } break; case Bar.EntryType.Seqd: //SqdFile = Sqd.Read(barEntry.Stream); @@ -70,7 +78,18 @@ public void SaveToBar() barEntry.Stream = PaxFile.getAsStream(); break; case Bar.EntryType.Imgd: - //barEntry.Stream = ; + if (barEntry.Name == "face") + { + MemoryStream memStream = new MemoryStream(); + ImgdFace.Write(memStream); + barEntry.Stream = memStream; + } + else if (barEntry.Name == "comd") + { + MemoryStream memStream = new MemoryStream(); + ImgdCommand.Write(memStream); + barEntry.Stream = memStream; + } break; case Bar.EntryType.Seqd: //barEntry.Stream = ; diff --git a/OpenKh.Tools.Kh2ObjectEditor/Services/AttachmentService.cs b/OpenKh.Tools.Kh2ObjectEditor/Services/AttachmentService.cs index 7c605664e..6e28f5f5b 100644 --- a/OpenKh.Tools.Kh2ObjectEditor/Services/AttachmentService.cs +++ b/OpenKh.Tools.Kh2ObjectEditor/Services/AttachmentService.cs @@ -75,12 +75,12 @@ public void LoadAttachment(string attachmentPath, int? attachToBone = null) if (!Bar.IsValid(streamMset)) throw new Exception("File is not a valid MSET: " + tempMsetPath); - Bar Attach_MsetBar = Bar.Read(streamMset); + BinaryArchive Attach_MsetBar = BinaryArchive.Read(streamMset); Attach_MsetEntries = new List(); - for (int i = 0; i < Attach_MsetBar.Count; i++) + for (int i = 0; i < Attach_MsetBar.Entries.Count; i++) { - Attach_MsetEntries.Add(new MotionSelector_Wrapper(i, Attach_MsetBar[i])); + Attach_MsetEntries.Add(new MotionSelector_Wrapper(i, Attach_MsetBar.Entries[i])); } } } diff --git a/OpenKh.Tools.Kh2ObjectEditor/Services/ClipboardService.cs b/OpenKh.Tools.Kh2ObjectEditor/Services/ClipboardService.cs index 774486439..15079308d 100644 --- a/OpenKh.Tools.Kh2ObjectEditor/Services/ClipboardService.cs +++ b/OpenKh.Tools.Kh2ObjectEditor/Services/ClipboardService.cs @@ -8,6 +8,19 @@ public class ClipboardService { // Note: Objects are copied as bytes or string in order to not copy the reference + private byte[] _motion { get; set; } + public void StoreMotion(byte[] motionFile) + { + _motion = motionFile; + } + public byte[] FetchMotion() + { + if (_motion == null) + return null; + + return _motion; + } + private Stream _dpd { get; set; } public void StoreDpd(Dpd dpd) { diff --git a/OpenKh.Tools.Kh2ObjectEditor/Services/MdlxService.cs b/OpenKh.Tools.Kh2ObjectEditor/Services/MdlxService.cs index e267799ce..93c9e2570 100644 --- a/OpenKh.Tools.Kh2ObjectEditor/Services/MdlxService.cs +++ b/OpenKh.Tools.Kh2ObjectEditor/Services/MdlxService.cs @@ -53,6 +53,8 @@ public void LoadMdlx(string mdlxPath) break; } } + + ViewerService.Instance.Render(); } public void SaveModel() diff --git a/OpenKh.Tools.Kh2ObjectEditor/Services/MsetService.cs b/OpenKh.Tools.Kh2ObjectEditor/Services/MsetService.cs index 9691da02e..e09650ae6 100644 --- a/OpenKh.Tools.Kh2ObjectEditor/Services/MsetService.cs +++ b/OpenKh.Tools.Kh2ObjectEditor/Services/MsetService.cs @@ -10,7 +10,8 @@ public class MsetService { // Apdx File public string MsetPath { get; set; } - public Bar MsetBar { get; set; } + //public Bar MsetBar { get; set; } + public BinaryArchive MsetBinarc { get; set; } //public List Motions { get; set; } //public List MotionSets { get; set; } // Reaction Commands // public List OtherMotions { get; set; } // Some msets have 0x09 entries @@ -27,27 +28,30 @@ public void LoadMset(string msetPath) MsetPath = msetPath; using var streamMset = File.Open(MsetPath, FileMode.Open); - if (!Bar.IsValid(streamMset)) + if (!BinaryArchive.IsValid(streamMset)) throw new Exception("File is not a valid MSET: " + MsetPath); - MsetBar = Bar.Read(streamMset); + MsetBinarc = BinaryArchive.Read(streamMset); } public void LoadMotion(int motionIndex) { LoadedMotionId = motionIndex; LoadCurrentMotion(); + ViewerService.Instance.LoadMotion(); } public void LoadCurrentMotion() { - MsetBar[LoadedMotionId].Stream.Position = 0; - LoadedMotion = new AnimationBinary(MsetBar[LoadedMotionId].Stream); - MsetBar[LoadedMotionId].Stream.Position = 0; + BinaryArchive.Entry motionEntry = MsetBinarc.Entries[LoadedMotionId]; + using MemoryStream fileStream = new MemoryStream(MsetBinarc.Subfiles[motionEntry.Link]); + LoadedMotion = new AnimationBinary(fileStream); } public void SaveMotion() { - MsetBar[LoadedMotionId].Stream = LoadedMotion.toStream(); + BinaryArchive.Entry motionEntry = MsetBinarc.Entries[LoadedMotionId]; + MemoryStream motionStream = (MemoryStream)LoadedMotion.toStream(); + MsetBinarc.Subfiles[motionEntry.Link] = motionStream.ToArray(); } public void SaveFile() @@ -59,9 +63,7 @@ public void SaveFile() sfd.ShowDialog(); if (sfd.FileName != "") { - MemoryStream memStream = new MemoryStream(); - Bar.Write(memStream, MsetBar); - File.WriteAllBytes(sfd.FileName, memStream.ToArray()); + File.WriteAllBytes(sfd.FileName, MsetBinarc.getAsByteArray()); } } @@ -70,9 +72,7 @@ public void OverwriteFile() if (MsetPath == null) return; - MemoryStream memStream = new MemoryStream(); - Bar.Write(memStream, MsetBar); - File.WriteAllBytes(MsetPath, memStream.ToArray()); + File.WriteAllBytes(MsetPath, MsetBinarc.getAsByteArray()); } // SINGLETON diff --git a/OpenKh.Tools.Kh2ObjectEditor/Services/ProcessService.cs b/OpenKh.Tools.Kh2ObjectEditor/Services/ProcessService.cs new file mode 100644 index 000000000..5268e64ef --- /dev/null +++ b/OpenKh.Tools.Kh2ObjectEditor/Services/ProcessService.cs @@ -0,0 +1,50 @@ +using OpenKh.Tools.Kh2ObjectEditor.Utils; +using System; +using System.Diagnostics; +using System.Text; + +namespace OpenKh.Tools.Kh2ObjectEditor.Services +{ + public class ProcessService + { + public static Process KH2Process { get; set; } + public static string ProcessName = "KINGDOM HEARTS II FINAL MIX"; + + public static void locateProcess() + { + Process[] processes = Process.GetProcessesByName(ProcessName); + if (processes.Length == 0) + { + Console.WriteLine("Process not found."); + return; + } + + KH2Process = processes[0]; + } + + public static long findLocationOfString(string searchString, long readSize = 50000000) + { + locateProcess(); + + if (KH2Process == null) + throw new Exception("The process is null"); + + byte[] byteArray = MemoryAccess.readMemory(KH2Process, 0, readSize); + + string result = Encoding.ASCII.GetString(byteArray).ToLower(); + long address = result.IndexOf(searchString.ToLower()); + + return address; + } + + public static long getAddressOfFile(string searchString) + { + long baseAddress = findLocationOfString(searchString); + if (baseAddress == 0) + return 0; + + long fileAddress = MemoryAccess.readLong(KH2Process, baseAddress + 80); + return fileAddress; + } + } +} diff --git a/OpenKh.Tools.Kh2ObjectEditor/Services/TestingService.cs b/OpenKh.Tools.Kh2ObjectEditor/Services/TestingService.cs index db02b6351..fc5aae282 100644 --- a/OpenKh.Tools.Kh2ObjectEditor/Services/TestingService.cs +++ b/OpenKh.Tools.Kh2ObjectEditor/Services/TestingService.cs @@ -1,8 +1,11 @@ using Microsoft.Win32; using OpenKh.Kh2; using OpenKh.Tools.Kh2ObjectEditor.Utils; +using System; using System.Collections.Generic; +using System.Diagnostics; using System.IO; +using System.Linq; namespace OpenKh.Tools.Kh2ObjectEditor.Services { @@ -116,9 +119,20 @@ public void TestMset() { int invalidFiles = 0; Dictionary barEntryCount = new Dictionary(); + Dictionary> rangeTriggerCount = new Dictionary>(); + Dictionary> rangeTriggerSample = new Dictionary>(); + Dictionary> frameTriggerCount = new Dictionary>(); + Dictionary> frameTriggerSample = new Dictionary>(); + HashSet param36 = new HashSet(); + HashSet param37 = new HashSet(); + + List preferredFiles = new List { "B_", "F_", "P_", "W_", "WM" }; foreach (string file in Directory.GetFiles(FolderPath)) { + string filename = Path.GetFileNameWithoutExtension(file); + string fileStart = filename.Substring(0,2); + if (!ObjectEditorUtils.isFilePathValid(file, "mset")) { continue; @@ -137,15 +151,175 @@ public void TestMset() foreach (Bar.Entry barEntry in fileBar) { - if (!barEntryCount.ContainsKey(barEntry.Type)) + // Check count per type + //if (!barEntryCount.ContainsKey(barEntry.Type)) + //{ + // barEntryCount.Add(barEntry.Type, 0); + //} + // + //barEntryCount.TryGetValue(barEntry.Type, out int count); + //barEntryCount[barEntry.Type] = count + 1; + + if (barEntry.Type == Bar.EntryType.Motionset) { - barEntryCount.Add(barEntry.Type, 0); + foreach (Bar.Entry barEntry2 in Bar.Read(barEntry.Stream)) + { + try + { + AnimationBinary anbEntry = new AnimationBinary(barEntry2.Stream); + if (anbEntry.MotionTriggerFile != null) + { + foreach (var rangeTrigger in anbEntry.MotionTriggerFile.RangeTriggerList) + { + // Count + if (!rangeTriggerCount.ContainsKey(rangeTrigger.Trigger)) + { + rangeTriggerCount.Add(rangeTrigger.Trigger, new HashSet()); + } + rangeTriggerCount[rangeTrigger.Trigger].Add(rangeTrigger.ParamSize); + + // Sample + if (!rangeTriggerSample.ContainsKey(rangeTrigger.Trigger)) + { + rangeTriggerSample.Add(rangeTrigger.Trigger, new Dictionary()); + } + if (!rangeTriggerSample[rangeTrigger.Trigger].ContainsKey(rangeTrigger.ParamSize)) + { + rangeTriggerSample[rangeTrigger.Trigger].Add(rangeTrigger.ParamSize, ""); + } + if(rangeTriggerSample[rangeTrigger.Trigger][rangeTrigger.ParamSize] == "" || + (!preferredFiles.Contains(rangeTriggerSample[rangeTrigger.Trigger][rangeTrigger.ParamSize].Substring(0,2)) && (preferredFiles.Contains(fileStart))) + ) + { + rangeTriggerSample[rangeTrigger.Trigger][rangeTrigger.ParamSize] = "" + (filename + " - " + barEntry.Name + "_" + barEntry2.Name); + } + } + foreach (var frameTrigger in anbEntry.MotionTriggerFile.FrameTriggerList) + { + // Count + if (!frameTriggerCount.ContainsKey(frameTrigger.Trigger)) + { + frameTriggerCount.Add(frameTrigger.Trigger, new HashSet()); + } + frameTriggerCount[frameTrigger.Trigger].Add(frameTrigger.ParamSize); + + // Sample + if (!frameTriggerSample.ContainsKey(frameTrigger.Trigger)) + { + frameTriggerSample.Add(frameTrigger.Trigger, new Dictionary()); + } + if (!frameTriggerSample[frameTrigger.Trigger].ContainsKey(frameTrigger.ParamSize)) + { + frameTriggerSample[frameTrigger.Trigger].Add(frameTrigger.ParamSize, ""); + } + if (frameTriggerSample[frameTrigger.Trigger][frameTrigger.ParamSize] == "" || + (!preferredFiles.Contains(frameTriggerSample[frameTrigger.Trigger][frameTrigger.ParamSize].Substring(0, 2)) && (preferredFiles.Contains(fileStart))) + ) + { + frameTriggerSample[frameTrigger.Trigger][frameTrigger.ParamSize] = "" + (filename + " - " + barEntry.Name + "_" + barEntry2.Name); + } + } + } + } + catch (Exception exc) { } + } + continue; } - barEntryCount.TryGetValue(barEntry.Type, out int count); - barEntryCount[barEntry.Type] = count + 1; + if (barEntry.Type != Bar.EntryType.Anb || barEntry.Stream.Length == 0) + continue; + + // Check Pattern enable/disable + try + { + AnimationBinary anbEntry = new AnimationBinary(barEntry.Stream); + if (anbEntry.MotionTriggerFile != null) + { + foreach (var rangeTrigger in anbEntry.MotionTriggerFile.RangeTriggerList) + { + if (!rangeTriggerCount.ContainsKey(rangeTrigger.Trigger)) { + rangeTriggerCount.Add(rangeTrigger.Trigger, new HashSet()); + } + rangeTriggerCount[rangeTrigger.Trigger].Add(rangeTrigger.ParamSize); + //if (rangeTrigger.Trigger == 36) + //{ + // param36.Add(rangeTrigger.Param1); + //} + //else if (rangeTrigger.Trigger == 37) + //{ + // param37.Add(rangeTrigger.Param1); + //} + + // Sample + if (!rangeTriggerSample.ContainsKey(rangeTrigger.Trigger)) + { + rangeTriggerSample.Add(rangeTrigger.Trigger, new Dictionary()); + } + if (!rangeTriggerSample[rangeTrigger.Trigger].ContainsKey(rangeTrigger.ParamSize)) + { + rangeTriggerSample[rangeTrigger.Trigger].Add(rangeTrigger.ParamSize, ""); + } + if (rangeTriggerSample[rangeTrigger.Trigger][rangeTrigger.ParamSize] == "" || + (!preferredFiles.Contains(rangeTriggerSample[rangeTrigger.Trigger][rangeTrigger.ParamSize].Substring(0, 2)) && (preferredFiles.Contains(fileStart))) || + (preferredFiles.Contains(fileStart) && rangeTriggerSample[rangeTrigger.Trigger][rangeTrigger.ParamSize].Contains("_")) + ) + { + rangeTriggerSample[rangeTrigger.Trigger][rangeTrigger.ParamSize] = "" + (filename + " - " + barEntry.Name); + } + } + foreach (var frameTrigger in anbEntry.MotionTriggerFile.FrameTriggerList) + { + if (!frameTriggerCount.ContainsKey(frameTrigger.Trigger)) + { + frameTriggerCount.Add(frameTrigger.Trigger, new HashSet()); + } + frameTriggerCount[frameTrigger.Trigger].Add(frameTrigger.ParamSize); + + + // Sample + if (!frameTriggerSample.ContainsKey(frameTrigger.Trigger)) + { + frameTriggerSample.Add(frameTrigger.Trigger, new Dictionary()); + } + if (!frameTriggerSample[frameTrigger.Trigger].ContainsKey(frameTrigger.ParamSize)) + { + frameTriggerSample[frameTrigger.Trigger].Add(frameTrigger.ParamSize, ""); + } + if (frameTriggerSample[frameTrigger.Trigger][frameTrigger.ParamSize] == "" || + (!preferredFiles.Contains(frameTriggerSample[frameTrigger.Trigger][frameTrigger.ParamSize].Substring(0, 2)) && (preferredFiles.Contains(fileStart)) || + (preferredFiles.Contains(fileStart) && frameTriggerSample[frameTrigger.Trigger][frameTrigger.ParamSize].Contains("_"))) + ) + { + frameTriggerSample[frameTrigger.Trigger][frameTrigger.ParamSize] = "" + (filename + " - " + barEntry.Name); + } + } + } + } + catch(Exception exc) { } } } + + Debug.WriteLine("Range Triggers"); + foreach (byte rangeTrigger in rangeTriggerCount.Keys) { + Debug.Write("[" + rangeTrigger + "] " + String.Join(",", rangeTriggerCount[rangeTrigger]) + "; S: "); + Debug.WriteLine(StringifyDictionary(rangeTriggerSample[rangeTrigger])); + } + Debug.WriteLine("Frame Triggers"); + foreach (byte frameTrigger in frameTriggerCount.Keys) + { + Debug.Write("[" + frameTrigger + "] " + String.Join(",", frameTriggerCount[frameTrigger]) + "; S: "); + Debug.WriteLine(StringifyDictionary(frameTriggerSample[frameTrigger])); + } + } + + private string StringifyDictionary(Dictionary dictio) + { + List stringified = new List(); + foreach(var key in dictio.Keys) + { + stringified.Add(key.ToString() + " - " + dictio[key].ToString()); + } + return String.Join(";", stringified); } // SINGLETON diff --git a/OpenKh.Tools.Kh2ObjectEditor/Services/ViewerService.cs b/OpenKh.Tools.Kh2ObjectEditor/Services/ViewerService.cs new file mode 100644 index 000000000..6222808b5 --- /dev/null +++ b/OpenKh.Tools.Kh2ObjectEditor/Services/ViewerService.cs @@ -0,0 +1,603 @@ +using HelixToolkit.Wpf; +using ModelingToolkit.HelixModule; +using ModelingToolkit.Objects; +using OpenKh.Kh2; +using OpenKh.Kh2Anim.Mset; +using OpenKh.Kh2Anim.Mset.Interfaces; +using OpenKh.Tools.Kh2ObjectEditor.Utils; +using OpenKh.Tools.Kh2ObjectEditor.ViewModel; +using System; +using System.Collections.Generic; +using System.IO; +using System.Numerics; +using System.Threading; +using System.Threading.Tasks; +using System.Windows.Media; +using System.Windows.Media.Media3D; + +namespace OpenKh.Tools.Kh2ObjectEditor.Services +{ + public class ViewerService : NotifyPropertyChangedBase + { + public ViewportController VpController { get; set; } + public MtModel LoadedModel { get; set; } + //------------------------------------- + // Animation + //------------------------------------- + private int _currentFrame { get; set; } + public int CurrentFrame + { + get { return _currentFrame; } + set + { + _currentFrame = value; + OnPropertyChanged("CurrentFrame"); + } + } + private int _motionMinFrame { get; set; } + public int MotionMinFrame + { + get { return _motionMinFrame; } + set + { + _motionMinFrame = value; + OnPropertyChanged("MotionMinFrame"); + } + } + private int _motionMaxFrame { get; set; } + public int MotionMaxFrame + { + get { return _motionMaxFrame; } + set + { + _motionMaxFrame = value; + OnPropertyChanged("MotionMaxFrame"); + } + } + private bool _animationRunning { get; set; } + public bool AnimationRunning + { + get { return _animationRunning; } + set + { + _animationRunning = value; + OnPropertyChanged("AnimationRunning"); + } + } + //------------------------------------- + // Reder Options + //------------------------------------- + public bool _autoCollisions { get; set; } + public bool AutoCollisions + { + get { return _autoCollisions; } + set + { + _autoCollisions = value; + if (!value) + { + foreach (MtShape shape in VpController.ShapeVisuals.Keys) + { + shape.IsVisible = false; + } + } + VpController.Render(); + OnPropertyChanged("AutoCollisions"); + } + } + private bool _autoCollisionsAttack { get; set; } + public bool AutoCollisionsAttack + { + get { return _autoCollisionsAttack; } + set + { + _autoCollisionsAttack = value; + OnPropertyChanged("AutoCollisionsAttack"); + } + } + private bool _autoCollisionsOther { get; set; } + public bool AutoCollisionsOther + { + get { return _autoCollisionsOther; } + set + { + _autoCollisionsOther = value; + OnPropertyChanged("AutoCollisionsOther"); + } + } + private bool _isBoundingBoxVisible { get; set; } + public bool IsBoundingBoxVisible + { + get { return _isBoundingBoxVisible; } + set + { + _isBoundingBoxVisible = value; + MakeBoundingBoxVisible(value); + OnPropertyChanged("IsBoundingBoxVisible"); + } + } + //------------------------------------- + // Skeleton helper + //------------------------------------- + private IAnimMatricesProvider _animMatricesProvider { get; set; } + private AnbIndir _loadedAnb { get; set; } + + //-------------------------------------------------------------------------------------------------------------------------------------------------------------- + // SINGLETON + //-------------------------------------------------------------------------------------------------------------------------------------------------------------- + public ViewerService() + { + AutoCollisionsAttack = true; + AutoCollisionsOther = true; + IsBoundingBoxVisible = false; + StartAnimationTicker(); + } + private static ViewerService _instance = null; + public static ViewerService Instance + { + get + { + if (_instance == null) + { + _instance = new ViewerService(); + } + return _instance; + } + } + public static void Reset() + { + _instance = new ViewerService(); + } + public void HookViewport(HelixViewport3D viewport) + { + Instance.VpController = new ViewportController(viewport); + } + + //-------------------------------------------------------------------------------------------------------------------------------------------------------------- + // FUNCTIONS + //-------------------------------------------------------------------------------------------------------------------------------------------------------------- + + public void Render() + { + VpController.ClearModels(); + VpController.ClearShapes(); + LoadModel(); + LoadCollisions(); + VpController.Render(); + } + + public void LoadModel() + { + if (MdlxService.Instance.MdlxBar == null) { + return; + } + LoadedModel = MdlxProcessor.GetMtModel(MdlxService.Instance.MdlxBar); + VpController.AddModel(LoadedModel); + VpController.ResetCamera(); + } + public void LoadCollisions() + { + if (MdlxService.Instance.CollisionFile?.EntryList == null) { + return; + } + + VpController.ClearShapes(); + for (int i = 0; i < MdlxService.Instance.CollisionFile.EntryList.Count; i++) + { + VpController.AddShape(GetCollisionShape(i)); + } + } + public void ShowCollisions(List collisionIds) + { + List shapes = new List< MtShape >(); + foreach (int collisionId in collisionIds) + { + shapes.AddRange(VpController.FindShapesByName(GetCollisionName(collisionId))); + } + foreach(MtShape shape in VpController.ShapeVisuals.Keys) + { + shape.IsVisible = shapes.Contains(shape); + } + VpController.Render(); + } + public void ShowCollision(int collisionId) + { + foreach (MtShape shape in VpController.FindShapesByName(GetCollisionName(collisionId))) + { + shape.IsVisible = true; + } + VpController.Render(); + } + public void HideCollision(int collisionId) + { + foreach (MtShape shape in VpController.FindShapesByName(GetCollisionName(collisionId))) { + shape.IsVisible = false; + } + VpController.Render(); + } + + public void LoadBoundingBox() + { + if (MsetService.Instance.LoadedMotion?.MotionFile?.InterpolatedMotionHeader?.BoundingBox == null) + { + return; + } + + foreach(MtShape shape in VpController.ShapeVisuals.Keys) + { + if(shape.Name == "BB") + { + VpController.RemoveShape(shape); + } + } + + float sizeX = (float)(MsetService.Instance.LoadedMotion?.MotionFile.InterpolatedMotionHeader.BoundingBox.BoundingBoxMaxX - MsetService.Instance.LoadedMotion?.MotionFile.InterpolatedMotionHeader.BoundingBox.BoundingBoxMinX); + float sizeY = (float)(MsetService.Instance.LoadedMotion?.MotionFile.InterpolatedMotionHeader.BoundingBox.BoundingBoxMaxY - MsetService.Instance.LoadedMotion?.MotionFile.InterpolatedMotionHeader.BoundingBox.BoundingBoxMinY); + float sizeZ = (float)(MsetService.Instance.LoadedMotion?.MotionFile.InterpolatedMotionHeader.BoundingBox.BoundingBoxMaxZ - MsetService.Instance.LoadedMotion?.MotionFile.InterpolatedMotionHeader.BoundingBox.BoundingBoxMinZ); + + float centerX = (float)(MsetService.Instance.LoadedMotion?.MotionFile.InterpolatedMotionHeader.BoundingBox.BoundingBoxMaxX - (sizeX / 2)); + float centerY = (float)(MsetService.Instance.LoadedMotion?.MotionFile.InterpolatedMotionHeader.BoundingBox.BoundingBoxMaxY - (sizeY / 2)); + float centerZ = (float)(MsetService.Instance.LoadedMotion?.MotionFile.InterpolatedMotionHeader.BoundingBox.BoundingBoxMaxX - (sizeZ / 2)); + + MtShape bbShape = MtShape.CreateBoundingBox(new Vector3(centerZ, centerX, centerY), sizeZ, sizeX, sizeY); + bbShape.Name = "BB"; + bbShape.IsVisible = IsBoundingBoxVisible; + bbShape.ShapeColor = Color.FromArgb(255, 255, 255, 0); + VpController.AddShape(bbShape); + } + + public void MakeBoundingBoxVisible(bool isVisible) + { + // Prevent initialization crash + if(VpController == null) { + return; + } + + foreach (MtShape shape in VpController.FindShapesByName("BB")) + { + shape.IsVisible = isVisible; + } + } + + public void LoadMotion() + { + if(MsetService.Instance.LoadedMotion?.MotionFile == null) { + throw new System.Exception("Viewport: There's no moveset loaded"); + } + + if(MsetService.Instance.LoadedMotion.MotionFile.InterpolatedMotionHeader.BoneCount >= 300) + { + SetFpsMode(MODE_10_FPS); + } + else + { + SetFpsMode(MODE_15_FPS); + } + + LoadAnbIndir(); + LoadAnimProvider(); + + MotionMinFrame = (int)MsetService.Instance.LoadedMotion.MotionFile.InterpolatedMotionHeader.FrameData.FrameStart * 2; + MotionMaxFrame = (int)MsetService.Instance.LoadedMotion.MotionFile.InterpolatedMotionHeader.FrameData.FrameEnd * 2; + CurrentFrame = MotionMinFrame; + + LoadPoseForFrame(MotionMinFrame); + + LoadBoundingBox(); + } + + public void FrameIncrease(int amount) + { + CurrentFrame += amount; + if (CurrentFrame > MotionMaxFrame) { + CurrentFrame = MotionMinFrame; + } + else if (CurrentFrame < MotionMinFrame) { + CurrentFrame = MotionMaxFrame; + } + } + public void LoadFrame() + { + LoadPoseForFrame(CurrentFrame); + } + private void LoadPoseForFrame(int frame) + { + LoadAnimProvider(); + Matrix4x4[] matrices = _animMatricesProvider.ProvideMatrices(frame); + for (int i = 0; i < LoadedModel.Joints.Count; i++) { + LoadedModel.Joints[i].AbsoluteTransformationMatrix = matrices[i]; + } + CalcVerticesPositionFromPose(); + UpdateMeshVertices(); + RecalcCollisionPositions(); + CheckFrameShapeVisibility(); + + VpController.Render(); + } + + private void UpdateMeshVertices() + { + List< GeometryModel3D > oldMeshes = new List< GeometryModel3D >(); + foreach (var child in ((Model3DGroup)VpController.ModelVisuals[LoadedModel].Content).Children) + { + if (child is GeometryModel3D geometryModel) + { + oldMeshes.Add(geometryModel); + } + } + + for (int i = 0; i < LoadedModel.Meshes.Count; i++) + { + MtMesh mesh = LoadedModel.Meshes[i]; + GeometryModel3D oldMesh = oldMeshes[i]; + Point3DCollection points = ((MeshGeometry3D)oldMesh.Geometry).Positions; + points.Clear(); + + foreach (MtVertex vertex in mesh.Vertices) + { + points.Add(new Point3D(vertex.AbsolutePosition.Value.X, vertex.AbsolutePosition.Value.Y, vertex.AbsolutePosition.Value.Z)); + } + } + } + + // Calculates the mesh based on the pose for single-weighted models + private void CalcVerticesPositionFromPose() + { + foreach(MtMesh mesh in LoadedModel.Meshes) + { + foreach(MtVertex vertex in mesh.Vertices) + { + vertex.AbsolutePosition = Vector3.Transform(vertex.Weights[0].RelativePosition.Value, LoadedModel.Joints[vertex.Weights[0].JointIndex.Value].AbsoluteTransformationMatrix.Value); + } + } + } + + private void RecalcCollisionPositions() + { + List visibleShapeIndices = new List(); + foreach(MtShape shape in VpController.ShapeVisuals.Keys) + { + if (shape.IsVisible && shape.Name.StartsWith("COL_")) + { + int underscoreIndex = shape.Name.IndexOf("COL_"); + int.TryParse(shape.Name.Substring(underscoreIndex + 4), out int shapeIndex); + visibleShapeIndices.Add(shapeIndex); + } + } + //for (int i = 0; i < MdlxService.Instance.CollisionFile.EntryList.Count; i++) + foreach(int i in visibleShapeIndices) + { + ObjectCollision collisionEntry = MdlxService.Instance.CollisionFile.EntryList[i]; + Vector3 basePosition = Vector3.Zero; + if (collisionEntry.Bone == 16384 && LoadedModel.Joints.Count > 0) // Root + { + basePosition = Vector3.Transform(new Vector3(collisionEntry.PositionX, collisionEntry.PositionY, collisionEntry.PositionZ), LoadedModel.Joints[0].AbsoluteTransformationMatrix.Value); + } + else if (LoadedModel.Joints.Count != 0) + { + basePosition = Vector3.Transform(new Vector3(collisionEntry.PositionX, collisionEntry.PositionY, collisionEntry.PositionZ), LoadedModel.Joints[collisionEntry.Bone].AbsoluteTransformationMatrix.Value); + } + basePosition = new Vector3(basePosition.Z, basePosition.X, basePosition.Y); + + foreach(MtShape shape in VpController.FindShapesByName(GetCollisionName(i))) + { + ModelVisual3D shapeVisual = VpController.ShapeVisuals[shape]; + if (shapeVisual is BoxVisual3D) + { + ((BoxVisual3D)shapeVisual).Center = new Point3D(basePosition.X, basePosition.Y, basePosition.Z); + } + else if (shapeVisual is EllipsoidVisual3D) + { + ((EllipsoidVisual3D)shapeVisual).Center = new Point3D(basePosition.X, basePosition.Y, basePosition.Z); + } + } + } + } + + private void CheckFrameShapeVisibility() + { + if (!AutoCollisions || MsetService.Instance.LoadedMotion.MotionTriggerFile?.RangeTriggerList == null) { + return; + } + + HashSet collisionGroups = new HashSet(); + foreach (MotionTrigger.RangeTrigger trigger in MsetService.Instance.LoadedMotion.MotionTriggerFile.RangeTriggerList) + { + if ((trigger.StartFrame <= CurrentFrame || trigger.StartFrame == -1) && (trigger.EndFrame >= CurrentFrame || trigger.EndFrame == -1)) + { + // Reaction collision + if (trigger.Trigger == 4 && AutoCollisionsOther) + { + collisionGroups.Add(trigger.Param1); + } + // Attack 1 hitbox + if (trigger.Trigger == 10 && AutoCollisionsAttack) + { + // Param 1 is atkp entry + collisionGroups.Add(trigger.Param2); + } + // Attack 2 hitboxes + if (trigger.Trigger == 33 && AutoCollisionsAttack) + { + // Param 1 is atkp entry + collisionGroups.Add(trigger.Param2); + } + // Reaction collision - self + if ((trigger.Trigger == 20 || + trigger.Trigger == 6) && + AutoCollisionsOther) + { + // Param 1 is command id + collisionGroups.Add(trigger.Param2); + } + } + } + + List collisionNames = new List(); + for (int i = 0; i < MdlxService.Instance.CollisionFile.EntryList.Count; i++) + { + if (collisionGroups.Contains(MdlxService.Instance.CollisionFile.EntryList[i].Group)) + { + collisionNames.Add(GetCollisionName(i)); + } + } + + foreach(MtShape shape in VpController.ShapeVisuals.Keys) + { + shape.IsVisible = collisionNames.Contains(shape.Name); + } + } + + public MtShape GetCollisionShape(int collisionId) + { + if (MdlxService.Instance.CollisionFile == null) { + throw new System.Exception("Collision file not found"); + } + + ObjectCollision collisionEntry = MdlxService.Instance.CollisionFile.EntryList[collisionId]; + + // Position + Vector3 basePosition = Vector3.Zero; + if (collisionEntry.Bone == 16384 && LoadedModel.Joints.Count > 0) // Root + { + basePosition = Vector3.Transform(new Vector3(collisionEntry.PositionX, collisionEntry.PositionY, collisionEntry.PositionZ), LoadedModel.Joints[0].AbsoluteTransformationMatrix.Value); + } + else if (LoadedModel.Joints.Count != 0) + { + basePosition = Vector3.Transform(new Vector3(collisionEntry.PositionX, collisionEntry.PositionY, collisionEntry.PositionZ), LoadedModel.Joints[collisionEntry.Bone].AbsoluteTransformationMatrix.Value); + } + basePosition = new Vector3(basePosition.Z, basePosition.X, basePosition.Y); + + // Color + Color color = new Color(); + if (collisionEntry.Type == (byte)ObjectCollision.TypeEnum.HIT) { + color = Color.FromArgb(100, 255, 255, 0); + } + else if (collisionEntry.Type == (byte)ObjectCollision.TypeEnum.ATTACK) + { + color = Color.FromArgb(100, 255, 0, 0); + } + else if (collisionEntry.Type == (byte)ObjectCollision.TypeEnum.REACTION) { + color = Color.FromArgb(100, 0, 255, 0); + } + else if (collisionEntry.Type == (byte)ObjectCollision.TypeEnum.TARGET) + { + color = Color.FromArgb(100, 0, 0, 255); + } + else { + color = Color.FromArgb(100, 255, 255, 255); + } + + MtShape collisionShape = new MtShape(); + if(collisionEntry.Shape == 0) { + collisionShape = MtShape.CreateEllipsoid(basePosition, collisionEntry.Height, collisionEntry.Radius, collisionEntry.Radius); + } + else if (collisionEntry.Shape == 1) { + collisionShape = MtShape.CreateColumn(basePosition, collisionEntry.Height, collisionEntry.Radius); + } + else if (collisionEntry.Shape == 2) { + collisionShape = MtShape.CreateBox(basePosition, collisionEntry.Height, collisionEntry.Radius, collisionEntry.Radius); + collisionShape.Type = MtShape.ShapeType.cube; + } + else if (collisionEntry.Shape == 3) { + collisionShape = MtShape.CreateEllipsoid(basePosition, collisionEntry.Height, collisionEntry.Radius, collisionEntry.Radius); + } + collisionShape.Name = GetCollisionName(collisionId); + collisionShape.ShapeColor = color; + + collisionShape.IsVisible = false; + + return collisionShape; + } + + //-------------------------------------------------------------------------------------------------------------------------------------------------------------- + // DATA LOADERS + //-------------------------------------------------------------------------------------------------------------------------------------------------------------- + // Load the AnbIndir for current motion (Per motion) + private void LoadAnbIndir() + { + if (!ObjectEditorUtils.isFilePathValid(App_Context.Instance.MdlxPath, "mdlx") || + !ObjectEditorUtils.isFilePathValid(MsetService.Instance.MsetPath, "mset") || + MsetService.Instance.LoadedMotion == null) + { + return; + } + + Bar anbBarFile = Bar.Read(new MemoryStream(MsetService.Instance.MsetBinarc.Subfiles[MsetService.Instance.MsetBinarc.Entries[MsetService.Instance.LoadedMotionId].Link])); + + _loadedAnb = new AnbIndir(anbBarFile); + } + // Load the AnimProvider for current motion (Per frame) + private void LoadAnimProvider() + { + // This is a test, model should be saved in the model module + MdlxService.Instance.SaveModel(); + Stream mdlxStream = new MemoryStream(); + Bar.Write(mdlxStream, MdlxService.Instance.MdlxBar); + + _animMatricesProvider = _loadedAnb.GetAnimProvider(mdlxStream); + } + + //-------------------------------------------------------------------------------------------------------------------------------------------------------------- + // FORMATTER + //-------------------------------------------------------------------------------------------------------------------------------------------------------------- + + private string GetCollisionName(int collisionId) + { + return "COL_" + collisionId; + } + + //-------------------------------------------------------------------------------------------------------------------------------------------------------------- + // ANIMATION TICKER + //-------------------------------------------------------------------------------------------------------------------------------------------------------------- + public class FpsMode + { + public TimeSpan MsBetweenTicks { get; set; } + public int FrameStep { get; set; } + + public FpsMode(int msBetweenTicks, int frameStep) { MsBetweenTicks = TimeSpan.FromMilliseconds(msBetweenTicks); FrameStep = frameStep; } + } + public static FpsMode MODE_30_FPS = new FpsMode(33, 1); + public static FpsMode MODE_15_FPS = new FpsMode(67, 2); + public static FpsMode MODE_10_FPS = new FpsMode(100, 3); + public static FpsMode MODE_5_FPS = new FpsMode(200, 6); + private FpsMode _currentFpsMode = MODE_15_FPS; + private PeriodicTimer _timer; + private CancellationTokenSource _cts; + public void SetFpsMode(FpsMode mode) + { + _currentFpsMode = mode; + _cts.Cancel(); + StartAnimationTicker(); + } + async Task StartAnimationTicker() + { + TimeSpan timeInMs = _currentFpsMode.MsBetweenTicks; + + _cts = new CancellationTokenSource(); + _timer = new PeriodicTimer(timeInMs); + while (await _timer.WaitForNextTickAsync(_cts.Token)) + { + try + { + AnimationTicker(); + } + catch (Exception e) { } + } + } + + public void AnimationTicker() + { + if (!AnimationRunning || + !ObjectEditorUtils.isFilePathValid(App_Context.Instance.MdlxPath, "mdlx") || + !ObjectEditorUtils.isFilePathValid(MsetService.Instance.MsetPath, "mset") || + MsetService.Instance.LoadedMotion == null) + { + return; + } + + ViewerService.Instance.FrameIncrease(_currentFpsMode.FrameStep); + ViewerService.Instance.LoadFrame(); + } + } +} diff --git a/OpenKh.Tools.Kh2ObjectEditor/Utils/MdlxProcessor.cs b/OpenKh.Tools.Kh2ObjectEditor/Utils/MdlxProcessor.cs new file mode 100644 index 000000000..9b58db877 --- /dev/null +++ b/OpenKh.Tools.Kh2ObjectEditor/Utils/MdlxProcessor.cs @@ -0,0 +1,132 @@ +using ModelingToolkit.Objects; +using OpenKh.Engine.Extensions; +using OpenKh.Kh2; +using OpenKh.Kh2.Models; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.IO; +using System.Numerics; + +namespace OpenKh.Tools.Kh2ObjectEditor.Utils +{ + public class MdlxProcessor + { + public static MtModel GetMtModel(Bar barFile) + { + MtModel model = new MtModel(); + + ModelSkeletal modelFile = null; + ModelTexture textureFile = null; + + foreach (Bar.Entry barEntry in barFile) + { + try + { + switch (barEntry.Type) + { + case Bar.EntryType.Model: + modelFile = ModelSkeletal.Read(barEntry.Stream); + break; + case Bar.EntryType.ModelTexture: + textureFile = ModelTexture.Read(barEntry.Stream); + break; + default: + break; + } + } + catch (Exception e) { } + } + + if (modelFile != null) + { + // Skeleton + for (int i = 0; i < modelFile.Bones.Count; i++) + { + ModelCommon.Bone bone = modelFile.Bones[i]; + MtJoint joint = new MtJoint(); + joint.Name = "Bone" + i; + joint.ParentId = (bone.ParentIndex == -1) ? null : bone.ParentIndex; + joint.RelativeScale = new Vector3(bone.ScaleX, bone.ScaleY, bone.ScaleZ); + joint.RelativeRotation = new Vector3(bone.RotationX, bone.RotationY, bone.RotationZ); + joint.RelativeTranslation = new Vector3(bone.TranslationX, bone.TranslationY, bone.TranslationZ); + model.Joints.Add(joint); + } + model.CalculateFromRelativeData(); + + // Meshes + for (int i = 0; i < modelFile.Groups.Count; i++) + { + ModelSkeletal.SkeletalGroup group = modelFile.Groups[i]; + + MtMesh mesh = new MtMesh(); + mesh.Name = "Mesh" + i; + mesh.MaterialId = (int)group.Header.TextureIndex; + + foreach (ModelCommon.UVBVertex mdlxVertex in group.Mesh.Vertices) + { + MtVertex vertex = new MtVertex(); + vertex.AbsolutePosition = mdlxVertex.Position; + vertex.TextureCoordinates = new Vector3(mdlxVertex.U / 4096.0f, 1 - (mdlxVertex.V / 4096.0f), 1); + foreach (ModelCommon.BPosition bonePosition in mdlxVertex.BPositions) + { + MtWeightPosition weight = new MtWeightPosition(); + weight.JointIndex = bonePosition.BoneIndex; + weight.Weight = bonePosition.Position.W == 0 ? 1 : bonePosition.Position.W; + weight.RelativePosition = new Vector3(bonePosition.Position.X, bonePosition.Position.Y, bonePosition.Position.Z); + vertex.Weights.Add(weight); + } + mesh.Vertices.Add(vertex); + } + foreach (List mdlxTriangle in group.Mesh.Triangles) + { + MtFace face = new MtFace(); + face.VertexIndices = mdlxTriangle; + mesh.Faces.Add(face); + } + + model.Meshes.Add(mesh); + } + } + + // Materials + if (textureFile != null) + { + for (int i = 0; i < textureFile.Images.Count; i++) + { + ModelTexture.Texture texture = textureFile.Images[i]; + MtMaterial material = new MtMaterial(); + material.Name = "Texture" + i; + material.Width = texture.Size.Width; + material.Height = texture.Size.Height; + material.Data = texture.GetData(); + material.Clut = texture.GetClut(); + material.ColorSize = 4; + + material.DiffuseTextureBitmap = new Bitmap(material.Width, material.Height, System.Drawing.Imaging.PixelFormat.Format32bppArgb); + + using (MemoryStream stream = new MemoryStream(texture.AsRgba8888())) + { + for (int y = 0; y < material.Height; y++) + { + for (int x = 0; x < material.Width; x++) + { + int r = stream.ReadByte(); + int g = stream.ReadByte(); + int b = stream.ReadByte(); + int a = stream.ReadByte(); + Color pixelColor = Color.FromArgb(a, r, g, b); + material.DiffuseTextureBitmap.SetPixel(x, y, pixelColor); + } + } + } + + + model.Materials.Add(material); + } + } + + return model; + } + } +} diff --git a/OpenKh.Tools.Kh2ObjectEditor/Utils/MemoryAccess.cs b/OpenKh.Tools.Kh2ObjectEditor/Utils/MemoryAccess.cs new file mode 100644 index 000000000..980a7e662 --- /dev/null +++ b/OpenKh.Tools.Kh2ObjectEditor/Utils/MemoryAccess.cs @@ -0,0 +1,80 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Runtime.InteropServices; + +namespace OpenKh.Tools.Kh2ObjectEditor.Utils +{ + class MemoryAccess + { + //------------------------ + // LIBRARIES + //------------------------ + + [Flags] + public enum ProcessAccessFlags : uint + { + All = 0x001F0FFF, + Terminate = 0x00000001, + CreateThread = 0x00000002, + VMOperation = 0x00000008, + VMRead = 0x00000010, + VMWrite = 0x00000020, + DupHandle = 0x00000040, + SetInformation = 0x00000200, + QueryInformation = 0x00000400, + Synchronize = 0x00100000 + } + + [DllImport("kernel32.dll")] + static extern IntPtr OpenProcess(ProcessAccessFlags dwDesiredAccess, [MarshalAs(UnmanagedType.Bool)] bool bInheritHandle, int dwProcessId); + + [DllImport("kernel32.dll")] + public static extern bool ReadProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, byte[] lpBuffer, uint dwSize, out int lpNumberOfBytesRead); + + [DllImport("kernel32.dll", SetLastError = true)] + static extern bool WriteProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, byte[] lpBuffer, uint nSize, out int lpNumberOfBytesWritten); + + [DllImport("kernel32.dll")] + public static extern Int32 CloseHandle(IntPtr hProcess); + + //------------------------ + // READ + //------------------------ + + // Read bytes from process at a specific address + public static byte[] readMemory(Process process, long address, long size, bool isGlobalAddress = false) + { + var val = new byte[size]; + long globalAddress = isGlobalAddress ? address : (process.MainModule.BaseAddress.ToInt64() + address); + ReadProcessMemory(process.Handle, new IntPtr(globalAddress), val, (UInt32)val.LongLength, out int bytesRead); + return val; + } + // Shortcuts + public static byte readByte(Process process, long address, bool isGlobalAddress = false) => readMemory(process, address, 1, isGlobalAddress)[0]; + public static short readShort(Process process, long address, bool isGlobalAddress = false) => BitConverter.ToInt16(readMemory(process, address, 2, isGlobalAddress)); + public static int readInt(Process process, long address, bool isGlobalAddress = false) => BitConverter.ToInt32(readMemory(process, address, 4, isGlobalAddress)); + public static long readLong(Process process, long address, bool isGlobalAddress = false) => BitConverter.ToInt64(readMemory(process, address, 8, isGlobalAddress)); + public static float readFloat(Process process, long address, bool isGlobalAddress = false) => BitConverter.ToSingle(readMemory(process, address, 4, isGlobalAddress)); + public static double readDouble(Process process, long address, bool isGlobalAddress = false) => BitConverter.ToDouble(readMemory(process, address, 8, isGlobalAddress)); + + //------------------------ + // WRITE + //------------------------ + + // Write bytes from process at a specific address + public static void writeMemory(Process process, long address, byte[] input, bool isGlobalAddress = false) + { + long globalAddress = isGlobalAddress ? address : (process.MainModule.BaseAddress.ToInt64() + address); + WriteProcessMemory(process.Handle, new IntPtr(globalAddress), input, (UInt32)input.LongLength, out int bytesWritten); + } + // Shortcuts + public static void writeMemory(Process process, long address, List input, bool isGlobalAddress = false) => writeMemory(process, address, input.ToArray(), isGlobalAddress); + public static void writeByte(Process process, long address, byte input, bool isGlobalAddress = false) => writeMemory(process, address, new byte[] { input }, isGlobalAddress); + public static void writeShort(Process process, long address, short input, bool isGlobalAddress = false) => writeMemory(process, address, BitConverter.GetBytes(input), isGlobalAddress); + public static void writeInt(Process process, long address, int input, bool isGlobalAddress = false) => writeMemory(process, address, BitConverter.GetBytes(input), isGlobalAddress); + public static void writeLong(Process process, long address, long input, bool isGlobalAddress = false) => writeMemory(process, address, BitConverter.GetBytes(input), isGlobalAddress); + public static void writeFloat(Process process, long address, float input, bool isGlobalAddress = false) => writeMemory(process, address, BitConverter.GetBytes(input), isGlobalAddress); + public static void writeDouble(Process process, long address, double input, bool isGlobalAddress = false) => writeMemory(process, address, BitConverter.GetBytes(input), isGlobalAddress); + } +} diff --git a/OpenKh.Tools.Kh2ObjectEditor/Utils/ObjectDictionary.cs b/OpenKh.Tools.Kh2ObjectEditor/Utils/ObjectDictionary.cs new file mode 100644 index 000000000..e85bfa161 --- /dev/null +++ b/OpenKh.Tools.Kh2ObjectEditor/Utils/ObjectDictionary.cs @@ -0,0 +1,1911 @@ +using System.Collections.Generic; + +namespace OpenKh.Tools.Kh2ObjectEditor.Utils +{ + public class ObjectDictionary + { + public static Dictionary Instance = new Dictionary + { +{"M_EX060", "(M) Fat Bandit"}, +{"M_EX500", "(M) Trick Ghost"}, +{"M_EX510", "(M) Rabid Dog"}, +{"M_EX520", "(M) Hook Bat"}, +{"M_EX530", "(M) Bookmaster"}, +{"M_EX540", "(M) Aeroplane"}, +{"M_EX550", "(M) Minute Bomb"}, +{"M_EX560", "(M) Hammer Frame"}, +{"M_EX570", "(M) Assault Rider"}, +{"M_EX580", "(M) Nightwalker"}, +{"M_EX620", "(M) Fortuneteller"}, +{"M_EX630", "(M) Luna Bandit"}, +{"M_EX640", "(M) Hot Rod"}, +{"M_EX650", "(M) Cannon Gun"}, +{"M_EX670", "(M) Living Bone"}, +{"M_EX680", "(M) Devastator"}, +{"M_EX690", "(M) Lance Soldier"}, +{"M_EX700", "(M) Mole Driller"}, +{"M_EX720", "(M) Shaman"}, +{"M_EX780", "(M) Aerial Knocker"}, +{"B_MU100", "(B) Shan-Yu"}, +{"B_MU110", "(B) Hayabusa (Shan-Yu’s Falcon)"}, +{"F_HE000", "(F) Standing Torch (HE)"}, +{"F_HE010", "(F) Blue Barrier (HE)"}, +{"F_HE110", "(F) Mist Sphere (HE)"}, +{"F_BB040", "(F) ??? - Invisible Armor? (BB)"}, +{"F_BB070", "(F) ??? - Something from Shadow Stalker? (B)"}, +{"F_WI060", "(F) Cannon tower (WI)"}, +{"N_BB050_BTL", "(N) Cogsworth (BTL) (BB)"}, +{"N_BB060_BTL", "(N) Lumière (BTL) (BB)"}, +{"N_BB070_BTL", "(N) Mrs. Potts (BTL) (BB)"}, +{"N_BB080_BTL", "(N) Wardrobe maid (BTL) (BB)"}, +{"F_BB000", "(F) ??? (BB)"}, +{"F_BB010", "(F) ??? (BB)"}, +{"F_BB020", "(F) ??? (BB)"}, +{"F_BB030", "(F) ??? (BB)"}, +{"N_HE010_ISHI_1", "(N) Hercules (ISHI_1) (HE)"}, +{"F_HE610", "(F) Megara’s rock seal (HE)"}, +{"F_HE500", "(F) Coliseum outer door (HE)"}, +{"F_HE510", "(F) Coliseum inner door (HE)"}, +{"F_HE520", "(F) Underworld’s blue door (HE)"}, +{"F_HE530", "(F) Hades’s room’s door (HE)"}, +{"F_HE580", "(F) Door’s keyhole seal (HE)"}, +{"F_HE590", "(F) Olympus stone (HE)"}, +{"F_HE760", "(F) Panic & Pain Cup (HE)"}, +{"M_EX110", "(M) Silver Rock"}, +{"M_EX120", "(M) Emerald Blues"}, +{"M_EX130", "(M) Crimson Jazz"}, +{"M_EX210", "(M) Air Pirate"}, +{"M_EX590", "(M) Bulky Vendor"}, +{"M_AL020_FIRE", "(M) Fiery Globe"}, +{"M_AL020_ICEE", "(M) Icy Cube"}, +{"B_EX110", "(B) Axel (Twilight Town, 2nd fight)"}, +{"P_EX100", "(P) Sora"}, +{"P_EX100_BTLF", "(P) Sora (Valor)"}, +{"P_EX100_MAGF", "(P) Sora (Wisdom)"}, +{"P_EX100_TRIF", "(P) Sora (Master)"}, +{"P_EX100_ULTF", "(P) Sora (Final)"}, +{"P_EX100_HTLF", "(P) Sora (Anti)"}, +{"P_EX110", "(P) Roxas"}, +{"P_EX200", "(P) Micky (hood)"}, +{"P_EX020", "(P) Donald"}, +{"P_EX030", "(P) Goofy"}, +{"P_BB000", "(P) Beast"}, +{"P_NM000", "(P) Jack Skellington"}, +{"P_NM000_SANTA", "(P) Jack Skellington (XM)"}, +{"P_LK000", "(P) Simba"}, +{"P_AL000", "(P) Aladdin"}, +{"P_MU000", "(P) Mulan"}, +{"P_MU010", "(P) Ping"}, +{"P_HE000", "(P) Auron"}, +{"P_CA000", "(P) Jack Sparrow"}, +{"W_EX010", "(W) Kingdom key"}, +{"W_EX010_10", "(W) Oath Keeper"}, +{"W_EX010_20", "(W) Oblivion"}, +{"W_EX010_Y0", "(W) CHECK Maximum length sword"}, +{"W_EX010_Z0", "(W) CHECK Ultima (AKA: Edge of Ultima)"}, +{"W_EX020", "(W) Mage’s Staff"}, +{"W_EX030", "(W) Knight’s Shield"}, +{"W_EX030_Z0", "(W) Test the King"}, +{"W_AL000", "(W) Scimitar"}, +{"W_HE000", "(W) Battlefields of War"}, +{"W_MU000", "(W) Sword of the Ancestor"}, +{"N_MU080_PLY", "(N) Mushu (PLY) (MU)"}, +{"W_CA000", "(W) Skill and Crossbones"}, +{"W_EX200", "(W) Kingdom Key D"}, +{"W_BB000", "(W) Beast’s Claw"}, +{"M_EX200", "(M) Wight Knight"}, +{"M_EX200_NM", "(M) Wight Knight (NM)"}, +{"M_EX420", "(M) Neoshadow"}, +{"M_EX600", "(M) Magnum Loader"}, +{"M_EX710", "(M) Morning Star"}, +{"M_EX730", "(M) Tornado Step"}, +{"M_EX740", "(M) Crescendo"}, +{"M_EX750", "(M) Creeper Plant"}, +{"B_NM000", "(B) Oogie Boogie"}, +{"N_NM050_BTL", "(N) Lock (BTL) (NM)"}, +{"N_NM060_BTL", "(N) Shock (BTL) (NM)"}, +{"N_NM070_BTL", "(N) Barrel (BTL) (NM)"}, +{"N_AL010_MATSU", "(N) Magic Carpet (MATSU) (AL)"}, +{"N_AL050_MATSU", "(N) Genie (MATSU) (AL)"}, +{"H_AL010_MATSU", "(H) Aladdin (AL)"}, +{"N_AL070_MATSU", "(N) Jafar (MATSU) (AL)"}, +{"H_AL020_MATSU", "(H) Jafar (AL)"}, +{"M_EX090_MATSU", "(M) Bandit (MATSU)"}, +{"F_AL510_MATSU", "(F) ??? (AL)"}, +{"F_AL530_MATSU", "(F) Cart with stuff (AL)"}, +{"F_AL550_MATSU", "(F) Treasure Room’s Gem (AL)"}, +{"F_AL580_MATSU", "(F) Large grey slab (AL)"}, +{"F_AL600_MATSU", "(F) Gem-holding statue (AL)"}, +{"F_AL610_MATSU", "(F) Stairs (AL)"}, +{"F_AL620_MATSU", "(F) Treasure room’s door (AL)"}, +{"F_AL630_MATSU", "(F) Peddler’s shop’s jar (AL)"}, +{"F_AL640_MATSU", "(F) Jafar Djinn lamp (AL)"}, +{"F_AL650_MATSU", "(F) Tip of tower (AL)"}, +{"F_AL660_MATSU", "(F) Jar (AL)"}, +{"F_AL670_MATSU", "(F) Peddler’s shop’s red curtain (AL)"}, +{"F_AL680_MATSU", "(F) Gem pedestal statue (AL)"}, +{"F_AL690_MATSU", "(F) Abu’s gem (AL)"}, +{"F_AL700_MATSU", "(F) Water spitting statue (AL)"}, +{"F_AL001_MATSU", "(F) Water spitting statue (Disabled) (AL)"}, +{"N_LM120_MATSU", "(N) Ariel’s sister A (MATSU) (LM)"}, +{"N_LM130_MATSU", "(N) Ariel’s sister B (MATSU) (LM)"}, +{"N_LM140_MATSU", "(N) Fish A (MATSU) (LM)"}, +{"N_LM150_MATSU", "(N) Stingray (MATSU) (LM)"}, +{"N_LM160_MATSU", "(N) Octopus (MATSU) (LM)"}, +{"N_LM170_MATSU", "(N) Snail A (MATSU) (LM)"}, +{"N_LM180_MATSU", "(N) Snail B (MATSU) (LM)"}, +{"N_LM190_MATSU", "(N) Jellyfish (MATSU) (LM)"}, +{"N_LM200_MATSU", "(N) Fish B (MATSU) (LM)"}, +{"N_LM090_MATSU", "(N) Shark (MATSU) (LM)"}, +{"N_NM030_MATSU", "(N) Zero (MATSU) (NM)"}, +{"N_NM080_MATSU", "(N) Bathtub (MATSU) (NM)"}, +{"P_EX100_NPC", "(P) Sora (NPC)"}, +{"P_EX020_ANGRY_NPC", "(P) Donald (ANGRY) (NPC)"}, +{"H_EX500_BTLF", "(H) Sora (Valor)"}, +{"H_EX530", "(H) DiZ (EX)"}, +{"H_EX560", "(H) Mickey (coat) (EX)"}, +{"H_EX590", "(H) Pete (EX)"}, +{"H_HE010", "(H) Hercules (HE)"}, +{"H_HE020", "(H) Megara (HE)"}, +{"H_HE030", "(H) Auron (HE)"}, +{"H_HE040", "(H) Hades (HE)"}, +{"H_BB010", "(H) Beast (BB)"}, +{"H_BB030", "(H) Belle (BB)"}, +{"H_AL030", "(H) Genie (AL)"}, +{"H_AL040", "(H) Jasmin (AL)"}, +{"H_MU010", "(H) Captain Li Shang (MU)"}, +{"H_MU020", "(H) Mulan (MU)"}, +{"H_MU030", "(H) Ping (MU)"}, +{"H_MU040", "(H) Mushu (MU)"}, +{"H_NM020", "(H) Jack Skellington (Christmas Town) (NM)"}, +{"H_CA010", "(H) Jack Sparrow (CA)"}, +{"M_EX310_NPC", "(M) Behemoth (NPC)"}, +{"M_EX320_NPC", "(M) Wyvern (NPC)"}, +{"M_EX760_NPC", "(M) Armored Knight (NPC)"}, +{"M_EX770_NPC", "(M) Surveillance Robot (NPC)"}, +{"F_EX510", "(F) Naminé’s sketch (Castle Oblivion)"}, +{"F_EX520", "(F) Naminé’s pencil"}, +{"F_EX540", "(F) Naiminé’s doll"}, +{"F_EX500", "(F) Struggle Trophy"}, +{"F_EX550", "(F) Struggle Trophy’s red gem"}, +{"F_EX551", "(F) Struggle Trophy’s blue gem"}, +{"F_EX552", "(F) Struggle Trophy’s yellow gem"}, +{"F_EX553", "(F) Struggle Trophy’s green gem"}, +{"F_EX560", "(F) Train (TT)"}, +{"F_EX570", "(F) Train’s lights? (TT)"}, +{"N_EX550", "(N) Kairi (school uniform) (EX)"}, +{"N_EX630", "(N) DiZ (EX)"}, +{"N_EX750", "(N) Org XIII member - Type A (EX)"}, +{"N_EX760", "(N) Pete (EX)"}, +{"N_ZZ010", "(N) Org XIII member (ZZ)"}, +{"H_ZZ010", "(H) Sora (KH1) (ZZ)"}, +{"H_ZZ020", "(H) Donald (ZZ)"}, +{"H_ZZ030", "(H) Goofy (ZZ)"}, +{"H_ZZ040", "(H) Donald (Casual) (ZZ)"}, +{"H_ZZ050", "(H) Goofy (Casual) (ZZ)"}, +{"H_EX500", "(H) Sora (EX)"}, +{"H_EX501", "(H) Sora (NM) (EX)"}, +{"H_EX520", "(H) Axel (EX)"}, +{"N_MU010_QSATO", "(N) Li Shang (QSATO) (MU)"}, +{"N_MU020_QSATO", "(N) Yao (QSATO) (MU)"}, +{"N_MU030_QSATO", "(N) Chien-Po (QSATO) (MU)"}, +{"N_MU040_QSATO", "(N) Ling (QSATO) (MU)"}, +{"N_MU050_QSATO", "(N) Emperor of China (QSATO) (MU)"}, +{"N_MU060_QSATO", "(N) Chinese Soldier 1 (QSATO) (MU)"}, +{"N_MU070_QSATO", "(N) Chinese Soldier 2 (QSATO) (MU)"}, +{"N_MU080_QSATO", "(N) Mushu (QSATO) (MU)"}, +{"N_MU090_QSATO", "(N) Mushu’s shadow (QSATO) (MU)"}, +{"F_MU510", "(F) Firework’s head (MU)"}, +{"F_MU520", "(F) Firework without head (MU)"}, +{"F_MU550", "(F) Shan-Yu’s Sword (MU)"}, +{"F_MU560", "(F) Palace’s gate (MU)"}, +{"N_MU010_QSATO_2", "(N) Li Shang (QSATO2) (MU)"}, +{"P_HE000_NPC_QSATO", "(P) Auron (NPC_QSATO)"}, +{"W_HE000_NPC_QSATO", "(W) Battlefields of War (NPC_QSATO) (HE)"}, +{"B_HE030_NPC_QSATO", "(B) Hades (NPC_QSATO) (HE)"}, +{"M_EX010", "(M) Soldier"}, +{"M_EX020", "(M) Shadow"}, +{"M_EX050", "(M) Large Body"}, +{"M_EX660", "(M) Rapid Thruster"}, +{"M_EX760", "(M) Armored Knight"}, +{"M_EX770", "(M) Surveillance Robot"}, +{"B_EX100", "(B) Twilight Thorn"}, +{"M_EX890", "(M) Dragoon"}, +{"M_EX900", "(M) Assassin"}, +{"M_EX910", "(M) Samurai"}, +{"M_EX920", "(M) Sniper"}, +{"M_EX930", "(M) Dancer"}, +{"M_EX940", "(M) Berserker"}, +{"M_EX950", "(M) Gambler"}, +{"M_EX960", "(M) Sorcerer"}, +{"M_EX870", "(M) Sorcerer v2 - Cubes?"}, +{"M_EX880", "(M) Creeper"}, +{"M_EX990", "(M) Dusk"}, +{"F_EX000", "(F) ??? - Chest related (EX)"}, +{"F_EX030", "(F) Treasure chest S"}, +{"F_EX030_BB", "(F) Treasure chest S (BB)"}, +{"F_EX030_HE", "(F) Treasure chest S (HE)"}, +{"F_EX030_TR", "(F) Treasure chest S (TR)"}, +{"F_EX040", "(F) Treasure chest L"}, +{"F_EX040_TR", "(F) Treasure chest L (TR)"}, +{"F_AL000", "(F) Water spitting statue (AL)"}, +{"F_AL010", "(F) ??? - Water wave? (AL)"}, +{"F_AL020", "(F) Challenge Sign (AL)"}, +{"F_AL040", "(F) Treasure Box (AL)"}, +{"N_AL090_BTL", "(N) Abu holding gem (BTL) (AL)"}, +{"F_MU000", "(F) Destructable Rock 1 (Reaction Command) (MU)"}, +{"F_MU010", "(F) Destructable Rock 2 (Reaction Command) (MU)"}, +{"F_MU020", "(F) Destructable Rock 3 (Reaction Command) (MU)"}, +{"F_MU040", "(F) Bunch of fireworks (MU)"}, +{"F_MU050", "(F) Firework (Rocket) (MU)"}, +{"F_MU060", "(F) Drive Orb Wagon (MU)"}, +{"F_MU070", "(F) Wind ride (Reaction Command) (MU)"}, +{"F_MU080", "(F) ??? (MU)"}, +{"F_MU090", "(F) ??? (MU)"}, +{"F_MU100", "(F) ??? (MU)"}, +{"F_MU100_SHANG", "(F) ??? (SHANG) (MU)"}, +{"F_MU100_TOWER", "(F) ??? (TOWER) (MU)"}, +{"F_WI000", "(F) ??? - Path? Pyramid? Sparkle? (WI)"}, +{"F_WI010", "(F) ??? - Path? Pyramid? Sparkle? White fire? (WI)"}, +{"B_AL020", "(B) Jafar (Djinn)"}, +{"B_HE030_PART", "(B) Hades (1st & 2nd fight)"}, +{"B_HE030", "(B) Hades (3rd & Paradox Hades Cup fight)"}, +{"B_HE020", "(B) Cerberus"}, +{"B_HE100", "(B) Hydra"}, +{"B_BB100", "(B) Thresholder"}, +{"B_BB110", "(B) Dark Thorn"}, +{"B_BB120", "(B) Shadow Stalker"}, +{"B_BB130", "(B) Thresholder’s possessor?"}, +{"B_MU120", "(B) Storm Rider"}, +{"B_CA000", "(B) Illuminator"}, +{"N_EX760_BTL", "(B) Pete (BTL)"}, +{"N_AL010_BTL", "(N) Magic Carpet (BTL) (AL)"}, +{"P_HE000_BTL", "(P) Auron (BTL)"}, +{"N_HE010_BTL", "(N) Hercules (BTL) (HE)"}, +{"N_HE040_BTL", "(N) Pegasus (BTL) (HE)"}, +{"N_HE020_BTL", "(N) Phil (BTL) (HE)"}, +{"N_HE030_BTL", "(N) Megara (Hydra battle) (BTL) (HE)"}, +{"N_HE030_BTL_DEF", "(N) Megara (Pete battle) (BTL_DEF) (HE)"}, +{"M_BB010_SWORD", "(M) Gargoyle Knight"}, +{"M_BB010_AX", "(M) Gargoyle Warrior"}, +{"F_HE020_A1", "(F) Rock 1 (grey) (HE)"}, +{"F_HE020_B1", "(F) Rock 1 (green) (HE)"}, +{"F_HE020_A2", "(F) Rock 2 (grey) (HE)"}, +{"F_HE020_B2", "(F) Rock 2 (green) (HE)"}, +{"F_NM040_00", "(F) Statue (horse) (NM)"}, +{"F_NM040_10", "(F) Statue (human) (NM)"}, +{"F_NM050", "(F) Falling grave (NM)"}, +{"F_NM070", "(F) Merry-go-round (NM)"}, +{"F_HE040", "(F) Golden Statue’s Hand with Sword (HE)"}, +{"F_HE090", "(F) Golden Statue’s Head (HE)"}, +{"N_BB020_TSURU", "(N) Beast (Prince Outfit) (TSURU) (BB)"}, +{"N_BB010_TSURU", "(N) Belle (TSURU) (BB)"}, +{"N_BB090_TSURU", "(N) Belle (Ball Dress) (TSURU) (BB)"}, +{"N_BB050_TSURU", "(N) Cogsworth (TSURU) (BB)"}, +{"N_BB060_TSURU", "(N) Lumière (TSURU) (BB)"}, +{"N_BB070_TSURU", "(N) Mrs. Potts (TSURU) (BB)"}, +{"N_BB040_TSURU", "(N) Chip (TSURU) (BB)"}, +{"N_BB080_TSURU", "(N) Wardrobe maid (TSURU) (BB)"}, +{"N_BB080_TSURU1", "(N) Wardrobe maid (no face) (TSURU1) (BB)"}, +{"N_ZZ020_TSURU", "(N) Org XIII member (TSURU) (ZZ)"}, +{"N_BB030_TSURU", "(N) Beast (Human Form) (TSURU) (BB)"}, +{"B_BB100_TSURU", "(B) Thresholder Door (Stone) (TSURU)"}, +{"H_BB010_TSURU", "(H) The Beast (TSURU) (BB)"}, +{"H_BB030_TSURU", "(H) Belle (TSURU) (BB)"}, +{"H_BB050_TSURU", "(H) Cogsworth (TSURU) (BB)"}, +{"H_BB060_TSURU", "(H) Lumiere (TSURU) (BB)"}, +{"H_BB070_TSURU", "(H) Mrs. Potts (TSURU) (BB)"}, +{"F_BB530", "(F) Rose container (BB)"}, +{"F_BB550", "(F) Knight Armor 1 (BB)"}, +{"F_BB550_TSURU1", "(F) Knight Armor 2 (TSURU1) (BB)"}, +{"F_BB550_TSURU2", "(F) Knight Armor 3 (TSURU2) (BB)"}, +{"F_BB560", "(F) Knight Armor 4 (BB)"}, +{"F_BB570", "(F) Outside door (BB)"}, +{"F_BB580", "(F) Hall door (BB)"}, +{"F_BB590", "(F) Belle’s door (BB)"}, +{"F_BB630", "(F) Ballroom window (BB)"}, +{"F_BB640", "(F) Great Bridge Door (BB)"}, +{"F_BB660", "(F) Door leading to minigame (BB)"}, +{"F_BB700", "(F) ??? - Path? Pyramid? (BB)"}, +{"N_HB500_TSURU", "(N) 1000 Heartless Battlefront (TSURU) (HB)"}, +{"N_HB510_TSURU", "(N) 1000 Heartless Battlefront (2D) (TSURU) (HB)"}, +{"N_HB520_TSURU", "(N) 1000 Heartless Battlefront (T-Pose) (TSURU) (HB)"}, +{"N_EX650_TSURU", "(N) Villager (boy) (TSURU) (EX)"}, +{"N_EX660_TSURU", "(N) Villager (lady) (TSURU) (EX)"}, +{"N_EX670_TSURU", "(N) Villager (girl) (TSURU) (EX)"}, +{"N_EX680A_TSURU", "(N) Villager (man A) (TSURU) (EX)"}, +{"N_EX680B_TSURU", "(N) Villager (man B) (TSURU) (EX)"}, +{"N_EX690A_TSURU", "(N) Villager (woman A) (TSURU) (EX)"}, +{"N_EX690B_TSURU", "(N) Villager (woman B) (TSURU) (EX)"}, +{"N_EX710_TSURU", "(N) Villager (old man) (TSURU) (EX)"}, +{"N_EX810_TSURU", "(N) Villager (old woman) (TSURU) (EX)"}, +{"N_CM000", "(N) Marluxia (CM)"}, +{"N_CM010", "(N) Larxene (CM)"}, +{"N_CM020", "(N) Lexaeus (CM)"}, +{"N_CM030", "(N) Zexion (CM)"}, +{"N_CM040", "(N) Vexen (CM)"}, +{"N_CM050", "(N) Naminé (CM)"}, +{"H_CM000", "(N) Marluxia (CM)"}, +{"H_CM020", "(H) Lexaeus (CM)"}, +{"H_CM040", "(H) Vexen (CM)"}, +{"H_CM050_TSURU", "(H) Naminé (TSURU) (CM)"}, +{"H_CM060", "(H) Riku (KHI, Org. Outfit) (CM)"}, +{"H_CM070", "(H) Riku (KHI) (CM)"}, +{"H_CM080", "(H) Kairi (KHI) (CM)"}, +{"N_CM060_TSURU", "(N) Squall / Leon (KHI) (TSURU) (CM)"}, +{"N_CM070_TSURU", "(N) Yuffie (KHI) (TSURU) (CM)"}, +{"N_CM080_TSURU", "(N) Aerith (KHI) (TSURU) (CM)"}, +{"N_CM090_TSURU", "(N) Cid (KHI) (TSURU) (CM)"}, +{"N_CM100_TSURU", "(N) Tidus (KHI) (TSURU) (CM)"}, +{"N_CM110_TSURU", "(N) Selphie (KHI) (TSURU) (CM)"}, +{"N_CM120_TSURU", "(N) Wakka (KHI) (TSURU) (CM)"}, +{"F_CM510", "(F) Naminé’s charm (CM)"}, +{"F_CM520", "(F) Kairi’s charm (CM)"}, +{"F_TR010", "(F) A Terminal from Space Paranoids (TR)"}, +{"PLAYER", "PLAYER"}, +{"FRIEND_1", "PARTY 1"}, +{"FRIEND_2", "PARTY 2"}, +{"WORLD_POINT", "WORLD_POINT"}, +{"SAVE_POINT", "SAVE_POINT"}, +{"ACTOR_SORA", "ACTOR_SORA"}, +{"ACTOR_SORA_H", "ACTOR_SORA_H"}, +{"SORA_WEAPON_R", "SORA_WEAPON_R"}, +{"SORA_WEAPON_BTLF", "SORA_WEAPON_BTLF"}, +{"DONALD_WEAPON", "DONALD_WEAPON"}, +{"GOOFY_WEAPON", "GOOFY_WEAPON"}, +{"F_NM610_ISHIDA", "(F) Railing (NM)"}, +{"F_NM550_ISHIDA", "(F) Doll (NM)"}, +{"F_NM640_ISHIDA", "(F) Papers (NM)"}, +{"F_NM660_ISHIDA", "(F) Santa’s list (NM)"}, +{"F_NM670_ISHIDA", "(F) Bear trap (NM)"}, +{"F_NM700_ISHIDA", "(F) Bear plush (NM)"}, +{"F_NM710_ISHIDA", "(F) Boat toy (NM)"}, +{"F_NM720_ISHIDA", "(F) Present 1 (NM)"}, +{"F_NM730_ISHIDA", "(F) Present 3 (NM)"}, +{"F_NM740_ISHIDA", "(F) Present 4 (NM)"}, +{"F_NM690_ISHIDA", "(F) Oogie’s spikes (Green/red) (NM)"}, +{"F_BB050", "(F) Shadow Stalker (Chandelier) (BB)"}, +{"F_BB060", "(F) Shadow Stalker (Columns) (BB)"}, +{"F_BB080", "(F) Shadow Stalker’s ground trap (BB)"}, +{"H_PO010", "(H) Pooh (PO)"}, +{"P_LK100", "(P) Sora (LK)"}, +{"F_HE570", "(F) Underworld boat (HE)"}, +{"F_NM570_ISHIDA", "(F) 2D dress (ISHIDA) (NM)"}, +{"N_EX790", "(N) Maleficent (EX)"}, +{"H_EX760", "(H) Olette (EX)"}, +{"H_EX620", "(H) Malificent (EX)"}, +{"N_LK040_OKA", "(N) Simba (OKA) (LK)"}, +{"N_LK060_OKA", "(N) Hyena (OKA) (LK)"}, +{"N_LK070_OKA", "(N) Wildebeest (OKA) (LK)"}, +{"N_LK080_OKA", "(N) Kiara (OKA) (LK)"}, +{"B_LK100_00", "(B) Shenzi"}, +{"B_LK100_10", "(B) Banzai"}, +{"B_LK100_20", "(B) Ed"}, +{"B_LK110", "(B) Scar"}, +{"P_EX030_NM", "(P) Goofy (NM)"}, +{"P_EX020_NM", "(P) Donald (NM)"}, +{"F_HE550", "(F) Semicircle with stalactites (HE)"}, +{"F_BB100", "(F) Minigame’s lamp (BB)"}, +{"N_LM070_MATSU", "(N) Ariel (with legs, in ripped clothing) (MATSU) (LM)"}, +{"N_LM080_MATSU", "(N) Ariel (wedding dress) (MATSU) (LM)"}, +{"N_LM100_MATSU", "(N) Prince Eric (MATSU) (LM)"}, +{"N_LM110_MATSU", "(N) Prince Eric (wedding suit) (MATSU) (LM)"}, +{"N_LM210_MATSU", "(N) Fish C (MATSU) (LM)"}, +{"N_LM220_MATSU", "(N) Fish D (MATSU) (LM)"}, +{"N_LM230_MATSU", "(N) Dolphin (MATSU) (LM)"}, +{"N_LM240_MATSU", "(N) Ariel (dress) (MATSU) (LM)"}, +{"N_LM250_MATSU", "(N) Flotsam (MATSU) (LM)"}, +{"N_LM251_MATSU", "(N) Jetsam (MATSU) (LM)"}, +{"F_NM630_ISHIDA", "(F) Jack’s santa costume (NM)"}, +{"F_MU570", "(F) Palace’s gate 2 (MU)"}, +{"N_NM120_ISHIDA", "(N) Jack’s sleigh (ISHIDA) (NM)"}, +{"P_EX100_NM", "(P) Sora (NM)"}, +{"F_BB110", "(F) Minigame’s crank (BB)"}, +{"F_HE050", "(F) Debris (HE)"}, +{"H_EX600", "(H) Naminé (EX)"}, +{"W_EX010_NM", "(W) Kingdom Key (NM)"}, +{"W_EX010_10_NM", "(W) Oath Keeper (NM)"}, +{"W_EX010_20_NM", "(W) Oblivion (NM)"}, +{"W_EX020_NM", "(W) Mage’s Staff (NM)"}, +{"W_EX030_NM", "(W) Knight’s Shield (NM)"}, +{"AURON_WEAPON", "(M) AURON_WEAPON"}, +{"N_DI500", "(N) Selphie (DI)"}, +{"F_DI990", "(F) Kairi’s handbag (DI)"}, +{"H_EX510", "(H) Roxas (EX)"}, +{"H_EX550", "(H) Kairi (EX)"}, +{"H_NM010", "(H) Oogie Boogie (NM)"}, +{"F_BB090", "(F) ??? (BB)"}, +{"F_WI320", "(F) Lilliput houses 1 (WI)"}, +{"F_WI330", "(F) Lilliput houses 2 (WI)"}, +{"F_WI340", "(F) Lilliput houses 3 (WI)"}, +{"F_WI350", "(F) Lilliput houses 4 (WI)"}, +{"P_BB000_BTL", "(P) Beast (BTL)"}, +{"H_EX740", "(H) Hayner (EX)"}, +{"H_EX750", "(H) Pence (EX)"}, +{"B_MU130", "(B) ??? - Barriers from Storm Rider?"}, +{"F_BB600", "(F) Dungeon’s chair (BB)"}, +{"F_BB690", "(F) Ballroom’s chandelier (BB)"}, +{"P_TR000", "(P) Tron"}, +{"F_BB120", "(F) Minigame’s box (BB)"}, +{"F_WI380", "(F) Steamboat with Corner stone in cage (WI)"}, +{"F_WI390", "(F) Steamboat’s hook (WI)"}, +{"F_WI400", "(F) Box (Pete throws) (WI)"}, +{"F_WI410", "(F) Barrel (Pete throws) (WI)"}, +{"F_WI420", "(F) Bowl (Pete throws) (WI)"}, +{"F_MU030", "(F) ??? (MU)"}, +{"F_WI070", "(F) Toy box (WI)"}, +{"F_WI080", "(F) Box with stuff (WI)"}, +{"F_WI090", "(F) Sofa 1 (WI)"}, +{"F_WI100", "(F) Chair 1 (WI)"}, +{"F_WI110", "(F) Chair 2 (WI)"}, +{"F_WI120", "(F) Piano (WI)"}, +{"F_WI130", "(F) Wardrobe 1 (WI)"}, +{"F_WI140", "(F) Wardrobe 2 (WI)"}, +{"F_WI150", "(F) Wardrobe 3 (WI)"}, +{"F_WI160", "(F) Ceiling lamp (WI)"}, +{"F_WI170", "(F) Fireplace (WI)"}, +{"F_WI180", "(F) Chimney (WI)"}, +{"F_WI190", "(F) Train toy (WI)"}, +{"F_WI200", "(F) Stool (WI)"}, +{"F_WI210", "(F) Dog bed (WI)"}, +{"F_WI220", "(F) Sofa 2 (WI)"}, +{"F_WI230", "(F) Wall deer trophy (WI)"}, +{"F_WI240", "(F) Wall lamp (WI)"}, +{"F_WI250", "(F) Christmas tree (WI)"}, +{"F_WI260", "(F) Drawer (WI)"}, +{"F_WI270", "(F) Wardrobe 4 (WI)"}, +{"F_WI280", "(F) Wardrobe 5 (WI)"}, +{"F_WI290", "(F) Painting (WI)"}, +{"F_WI300", "(F) Shelf (WI)"}, +{"F_WI310", "(F) ??? (WI)"}, +{"F_WI360", "(F) Building site’s platform (WI)"}, +{"F_AL050", "(F) ??? (AL)"}, +{"F_TR020", "(F) Energy core’s cube (TR)"}, +{"F_TR030", "(F) ??? (TR)"}, +{"F_TT020", "(F) Juggling ball (TT)"}, +{"F_TT030", "(F) Cargo Climb’s cart (TT)"}, +{"F_TT040", "(F) Junk (TT)"}, +{"F_AL030", "(F) ??? (AL)"}, +{"F_PO000", "(F) ??? (PO)"}, +{"F_DC000", "(F) ??? (DC)"}, +{"F_TR070", "(F) ??? (TR)"}, +{"F_HB000", "(F) ??? (HB)"}, +{"P_BB000_TSURU", "(P) Beast (TSURU)"}, +{"N_HB040_BTL", "(N) Stitch (BTL) (HB)"}, +{"F_BB620", "(F) Brown door (BB)"}, +{"F_WI530", "(F) Steamboat’s top (WI)"}, +{"P_TR000_TSURU", "(P) Tron (TSURU)"}, +{"W_NM000", "(W) Bone Fist"}, +{"W_TR000", "(W) Identity Disk"}, +{"W_LK000", "(W) Proud Fang"}, +{"W_EH000", "(W) Way to Dawn"}, +{"P_EX220", "(P) Mickey"}, +{"MENU_FRIEND_1", "MENU_FRIEND_1"}, +{"MENU_FRIEND_2", "MENU_FRIEND_2"}, +{"B_EX120", "(B) Demyx (Only playing sitar?)"}, +{"H_BB020_TSURU", "(H) ??? (TSURU) (BB)"}, +{"H_BB040_TSURU", "(H) Belle (ball dress) (TSURU) (BB)"}, +{"H_LK020", "(H) Simba (LK)"}, +{"F_BB130", "(F) ??? - Minigame related? (BB)"}, +{"F_HE030_S", "(F) Phil minigame S (HE)"}, +{"F_HE030_L", "(F) Phil minigame L (HE)"}, +{"P_EX110_BTLF", "(P) Roxas (Valor) (dual wield)"}, +{"H_LK030", "(H) Nala (LK)"}, +{"F_NM020", "(F) Spitting fountain (NM)"}, +{"F_NM000", "(F) Guillotine blade (NM)"}, +{"F_NM010", "(F) ??? (NM)"}, +{"F_NM030", "(F) Swinging gates (NM)"}, +{"F_NM560_ISHIDA", "(F) The Experiment’s foot (NM)"}, +{"F_NM620_ISHIDA", "(F) Christmas Town door (NM)"}, +{"F_NM650_ISHIDA", "(F) False present for The Experiment (NM)"}, +{"F_NM680_ISHIDA", "(F) Conveyour (NM)"}, +{"F_NM750_ISHIDA", "(F) Rope tying something (NM)"}, +{"F_NM760_ISHIDA", "(F) Sally’s foot (NM)"}, +{"N_BB010_RTN", "(N) Belle (RTN) (BB)"}, +{"F_EX030_AL", "(F) Treasure chest S (AL)"}, +{"F_EX030_CA", "(F) Treasure chest S (CA)"}, +{"F_EX030_NM", "(F) Treasure chest S (NM)"}, +{"F_EX030_MU", "(F) Treasure chest S (MU)"}, +{"F_EX030_PO", "(F) Treasure chest S (PO)"}, +{"F_EX030_DC", "(F) Treasure chest S (DC)"}, +{"F_EX030_WI", "(F) Treasure chest S (WI)"}, +{"F_EX030_EH", "(F) Treasure chest S (EH)"}, +{"F_EX040_BB", "(F) Treasure chest L (BB)"}, +{"F_EX040_HE", "(F) Treasure chest L (HE)"}, +{"F_EX040_AL", "(F) Treasure chest L (AL)"}, +{"F_EX040_CA", "(F) Treasure chest L (CA)"}, +{"F_EX040_NM", "(F) Treasure chest L (NM)"}, +{"F_EX040_MU", "(F) Treasure chest L (MU)"}, +{"F_EX040_PO", "(F) Treasure chest L (PO)"}, +{"F_EX040_DC", "(F) Treasure chest L (DC)"}, +{"F_EX040_WI", "(F) Treasure chest L (WI)"}, +{"F_EX040_EH", "(F) Treasure chest L (EH)"}, +{"H_EX580", "(H) Demyx (EX)"}, +{"F_NM510_ISHIDA", "(F) Jack’s toy (duck) (NM)"}, +{"F_NM520_ISHIDA", "(F) Jack’s toy (jack in the box) (NM)"}, +{"F_NM530_ISHIDA", "(F) Laboratory’s door (NM)"}, +{"F_HB010", "(F) ??? (HB)"}, +{"F_WI020", "(F) Burning building (WI)"}, +{"H_TR010", "(H) Tron (TR)"}, +{"F_AL570_MATSU", "(F) Wall chains (AL)"}, +{"PO06_PLAYER", "PO06_PLAYER"}, +{"PO07_PLAYER", "PO07_PLAYER"}, +{"PO08_PLAYER", "PO08_PLAYER"}, +{"N_TR010_BTL", "(N) Sark (BTL) (TR)"}, +{"F_TR050", "(F) MCP barrier (TR)"}, +{"F_TR060", "(F) MCP wall (TR)"}, +{"B_EX200", "(B) Twilight Thorn’s Energy Ball (Break Raid RC)"}, +{"H_CA020", "(H) Elizabeth (CA)"}, +{"W_EX000", "(W) ??? (EX000)"}, +{"N_TR010_TSURU", "(N) Sark (TSURU) (TR)"}, +{"LAST_ATTACKER", "LAST_ATTACKER"}, +{"N_LM270_MATSU", "(N) Ursula (huge) (MATSU) (LM)"}, +{"N_LM280_MATSU", "(N) Ursula’s tentacles (huge) (MATSU) (LM)"}, +{"H_LM070", "(H) Donald (LM)"}, +{"H_LM090", "(H) Ursula (LM)"}, +{"N_LM290_MATSU", "(N) Ursula (with trident) (MATSU) (LM)"}, +{"F_LM570_MATSU", "(F) Boat (LM)"}, +{"F_LM580_MATSU", "(F) Sunken ship stuff (LM)"}, +{"F_LM590_MATSU", "(F) Piece of wood (LM)"}, +{"P_CA000_SKL_NPC", "(P) Jack Sparrow (skelleton) (NPC)"}, +{"N_HB030_TSURU", "(N) Merlin (TSURU) (HB)"}, +{"N_LM010_PLY", "(N) Donald (PLY) (LM)"}, +{"N_LM020_PLY", "(N) Goofy (PLY) (LM)"}, +{"B_HE110", "(B) Hydra head (Out of the ground)"}, +{"F_TR510", "(F) Elevator (TR)"}, +{"F_TR520", "(F) Energy core (TR)"}, +{"F_TR540", "(F) Solar Sailer"}, +{"H_TR010_TSURU", "(H) Tron (TSURU) (TR)"}, +{"F_NM140", "(F) ??? (NM)"}, +{"WM_CURSOR", "(WM) Gummi ship"}, +{"WM_SYMBOL_TT", "(WM) Traverse Town world"}, +{"WM_SYMBOL_HB", "(WM) Hollow Bastion world"}, +{"WM_SYMBOL_MU", "(WM) Land of Dragons world"}, +{"WM_SYMBOL_BB", "(WM) Beast’s Castle world"}, +{"WM_SYMBOL_HE", "(WM) Olympus world"}, +{"WM_SYMBOL_DC", "(WM) Disney Castle world"}, +{"WM_SYMBOL_LM", "(WM) Atlantica world"}, +{"WM_SYMBOL_CA", "(WM) Port Royal world"}, +{"WM_SYMBOL_NM", "(WM) Halloween Town world"}, +{"WM_SYMBOL_AL", "(WM) Agrabah world"}, +{"WM_SYMBOL_LK", "(WM) Pride Lands world"}, +{"WM_GUMI_01", "WM_GUMI_01"}, +{"F_DC510", "(F) Mickey’s throne (DC)"}, +{"PRIZE_BOX_YELLOW", "PRIZE_BOX_YELLOW"}, +{"PRIZE_BOX_RED", "PRIZE_BOX_RED"}, +{"PRIZE_BOX_BLUE", "PRIZE_BOX_BLUE"}, +{"PRIZE_BOX_GREEN", "PRIZE_BOX_GREEN"}, +{"F_NM100", "(F) Oogie’s present (NM)"}, +{"F_NM110", "(F) Oogie’s bag (NM)"}, +{"P_LM100", "(P) Sora (LM)"}, +{"N_TR010_BTL_L", "(N) Sark (large) (BTL) (TR)"}, +{"F_NM120", "(F) Oogie’s spikes (NM)"}, +{"F_NM160", "(F) Lever (NM)"}, +{"F_NM180", "(F) Present minigame (shooting machine) (NM)"}, +{"F_NM130", "(F) ??? (NM)"}, +{"N_EX770", "(N) Org XIII member - Type B (EX)"}, +{"N_WI010_BTL", "(N) Pete (captain) (BTL) (WI)"}, +{"F_HE600", "(F) Auron doll (HE)"}, +{"N_EX800", "(N) Diablo (Maleficent’s raven) (EX)"}, +{"N_NM050_BTL_2", "(N) Lock (BTL2) (NM)"}, +{"N_NM060_BTL_2", "(N) Shock (BTL2) (NM)"}, +{"N_NM070_BTL_2", "(N) Barrel (BTL2) (NM)"}, +{"H_MU050", "(H) Shan Yu (MU)"}, +{"N_BB060_RTN", "(N) Lumière (RTN) (BB)"}, +{"B_EX130", "(B) Xaldin"}, +{"P_EX100_NM_BTLF", "(P) Sora (NM) (Valor)"}, +{"P_EX100_NM_MAGF", "(P) Sora (NM) (Wisdom)"}, +{"P_EX100_NM_TRIF", "(P) Sora (NM) (Master)"}, +{"P_EX100_NM_ULTF", "(P) Sora (NM) (Final)"}, +{"P_EX100_NM_HTLF", "(P) Sora (NM) (Anti)"}, +{"F_NM080", "(F) Oogie’s present box (NM)"}, +{"F_DC580", "(F) Timeless River door (light) (DC)"}, +{"N_EX850", "(N) Org XIII member - Type E (EX)"}, +{"MENU_FRIEND_3", "MENU_FRIEND_3"}, +{"F_DC520", "(F) Great door corner (DC)"}, +{"H_NM000", "(H) Jack Skellington (NM)"}, +{"H_EX670", "(H) Xaldin (EX)"}, +{"F_BB680", "(F) Wall door with button (AL)"}, +{"B_CA010_HUM_NPC", "(N) Barbossa (Human)"}, +{"B_CA010_SKL_NPC", "(N) Barbossa (Skelleton)"}, +{"B_CA010", "(B) Barbossa"}, +{"F_TR080", "(F) Light cycle’s wall (TR)"}, +{"F_TR090", "(F) Light cycle’s wall 2 (TR)"}, +{"F_TR100", "(F) Light cycle’s wall 3 (single panel) (TR)"}, +{"F_TR110", "(F) Light cycle’s columns (5) (TR)"}, +{"F_TR120", "(F) Light cycle’s columns (3) (TR)"}, +{"F_TR130", "(F) Light cycle’s columns (1) (TR)"}, +{"F_MU590", "(F) ??? - Path? Pyramid? (MU)"}, +{"N_BB020_RTN", "(N) Beast (Prince Outfit) (RTN) (BB)"}, +{"N_BB040_RTN", "(N) Chip (RTN) (BB)"}, +{"N_BB050_RTN", "(N) Cogsworth (RTN) (BB)"}, +{"N_BB070_RTN", "(N) Mrs. Potts (RTN) (BB)"}, +{"N_BB080_RTN", "(N) Wardrobe maid (RTN) (BB)"}, +{"N_BB090_RTN", "(N) Belle (Ball Dress) (RTN) (BB)"}, +{"P_BB000_RTN", "(N) ??? (RTN) (BB)"}, +{"TR_PLAYER", "TR_PLAYER"}, +{"F_CA030_DARK", "(F) Port Royal’s crane (CA)"}, +{"F_CA030_LIGHT", "(F) Port Royal’s crane (CA)"}, +{"B_AL100_FIRE", "(B) Volcanic Lord"}, +{"B_AL100_ICE", "(B) Blizzard Lord"}, +{"P_CA000_RTN", "(P) Jack Sparrow (RTN)"}, +{"N_CA010_RTN", "(N) Elizabeth (RTN) (CA)"}, +{"N_CA020_RTN", "(N) Will Turner (RTN) (CA)"}, +{"F_HE560", "(F) Piece of debris (HE)"}, +{"F_AL520_MATSU", "(F) Shop (AL)"}, +{"H_CA030", "(H) Jack Sparrow (skeleton) (CA)"}, +{"N_WI050_ISHI", "(N) Mickey (classic) (ISHI) (WI)"}, +{"N_LM300_MATSU", "(N) Ursula’s garden’s polyp (MATSU) (LM)"}, +{"N_AL020_RTN", "(N) Iago (RTN) (AL)"}, +{"F_NM170_CATCH", "(F) Present minigame (CATCH) (NM)"}, +{"F_NM170_XL", "(F) Present minigame (XL) (NM)"}, +{"F_NM170_L", "(F) Present minigame (L) (NM)"}, +{"F_NM170_M", "(F) Present minigame (M) (NM)"}, +{"F_NM170_S", "(F) Present minigame (S) (NM)"}, +{"N_PO010_RTN", "(N) Pooh (RTN) (PO)"}, +{"N_PO020_RTN", "(N) Tigger (RTN) (PO)"}, +{"N_PO030_RTN", "(N) Pigglet (RTN) (PO)"}, +{"N_PO050_RTN", "(N) Rabbit (RTN) (PO)"}, +{"N_PO060_RTN", "(N) Owl (RTN) (PO)"}, +{"N_PO080_RTN", "(N) Kanga (RTN) (PO)"}, +{"F_MU530", "(F) Burnt wood plank (MU)"}, +{"H_CA050", "(H) Barbossa (human) (CA)"}, +{"F_TT600", "(F) Magic Train (TT)"}, +{"F_TT610", "(F) Magic Train’s door (TT)"}, +{"F_TT790", "(F) Orange wall (TT)"}, +{"P_EX020_RTN", "(N) Donald (RTN)"}, +{"P_EX030_RTN", "(N) Goofy (RTN)"}, +{"N_LM030_RTN", "(N) Ariel (RTN) (LM)"}, +{"N_LM050_RTN", "(N) Sebastian (RTN) (LM)"}, +{"N_LM060_RTN", "(N) Flounder (RTN) (LM)"}, +{"H_CA040", "(H) Will Turner (CA)"}, +{"N_MU010_RTN", "(N) Li Shang (RTN) (MU)"}, +{"N_MU020_RTN", "(N) Yao (RTN) (MU)"}, +{"N_MU030_RTN", "(N) Chien-Po (RTN) (MU)"}, +{"N_MU040_RTN", "(N) Ling (RTN) (MU)"}, +{"N_MU050_RTN", "(N) Emperor of China (RTN) (MU)"}, +{"N_MU060_RTN", "(N) Chinese Soldier 1 (RTN) (MU)"}, +{"N_MU070_RTN", "(N) Chinese Soldier 2 (RTN) (MU)"}, +{"N_HE010_RTN", "(N) Hercules (RTN) (HE)"}, +{"N_HE020_RTN", "(N) Phil (RTN) (HE)"}, +{"N_HE030_RTN", "(N) Megara (RTN) (HE)"}, +{"N_HE040_RTN", "(N) Pegasus (RTN) (HE)"}, +{"N_HE050_RTN", "(N) Pain (RTN) (HE)"}, +{"N_HE060_RTN", "(N) Panic (RTN) (HE)"}, +{"B_HE030_RTN", "(RTN) Hades (HE)"}, +{"B_CA020", "(M) Undead Pirate A"}, +{"B_CA030", "(M) Undead Pirate B"}, +{"B_CA040", "(M) Undead Pirate C"}, +{"WM_SYMBOL_EH", "(WM) TWTNW world"}, +{"F_MU070_BOSS", "(F) Wind ride (Reaction Command) (BOSS) (MU)"}, +{"B_LK120", "(B) Groundshaker"}, +{"M_EX610", "(M) Strafer"}, +{"P_EX330", "(P) Peter Pan"}, +{"F_CA670", "(F) ??? (CA)"}, +{"F_CA671", "(F) ??? (CA)"}, +{"F_NM540_ISHIDA", "(F) Broken present box (NM)"}, +{"SHOP_POINT_SUB", "SHOP_POINT_SUB"}, +{"N_AL080_RTN", "(N) Peddler (RTN) (AL)"}, +{"N_WI010_RTN", "(N) Pete (captain) (RTN) (WI)"}, +{"N_EX760_RTN", "(N) Pete (RTN) (EX)"}, +{"N_WI020_RTN", "(N) Horace (RTN) (WI)"}, +{"N_WI030_RTN", "(N) Clarabelle (RTN) (WI)"}, +{"N_WI040_RTN", "(N) Clara (RTN) (WI)"}, +{"N_DC010_RTN", "(N) Minnie (RTN) (DC)"}, +{"N_DC020_RTN", "(N) Daisy (RTN) (DC)"}, +{"N_DC030_RTN", "(N) Chip (RTN) (DC)"}, +{"N_DC040_RTN", "(N) Dale (RTN) (DC)"}, +{"N_DC050_RTN", "(N) Magic Broom (RTN) (DC)"}, +{"N_HB030_RTN", "(N) Merlin (RTN) (HB)"}, +{"N_NM010_RTN", "(N) Sally (RTN) (NM)"}, +{"N_NM020_RTN", "(N) Dr. Finkelstein (RTN) (NM)"}, +{"N_NM040_RTN", "(N) The Mayor (RTN) (NM)"}, +{"N_NM050_RTN", "(N) Lock (RTN) (NM)"}, +{"N_NM060_RTN", "(N) Shock (RTN) (NM)"}, +{"N_NM070_RTN", "(N) Barrel (RTN) (NM)"}, +{"N_NM090_RTN", "(N) Santa Claus (RTN) (NM)"}, +{"N_NM100_RTN", "(N) Elf (male) (RTN) (NM)"}, +{"N_NM110_RTN", "(N) Elf (female) (RTN) (NM)"}, +{"H_LM040", "(H) Poseidon (LM)"}, +{"M_EX880_DANCER", "(M) Demyx’s water form"}, +{"SHOP_POINT", "SHOP_POINT"}, +{"P_AL000_RTN", "(P) Aladdin (RTN) (AL)"}, +{"N_AL030_RTN", "(N) Abu (RTN) (AL)"}, +{"N_AL040_RTN", "(N) Jasmine (RTN) (AL)"}, +{"F_CA730", "(F) Barbossa’s sword (CA)"}, +{"F_CA740", "(F) Medallion pendant (CA)"}, +{"F_CA741", "(F) Medallion pendant (With blood) (CA)"}, +{"N_LK010_RTN", "(N) Timon (RTN) (LK)"}, +{"N_LK020_RTN", "(N) Pumba (RTN) (LK)"}, +{"N_LK030_RTN", "(N) Nala (RTN) (LK)"}, +{"N_LK050_RTN", "(N) Lioness (RTN) (LK)"}, +{"N_LK120_RTN", "(N) Rafiki (RTN) (LK)"}, +{"P_LK000_RTN", "(P) Simba (RTN)"}, +{"P_MU010_RTN", "(P) Ping (RTN)"}, +{"LAST_HITMARK", "LAST_HITMARK"}, +{"P_HE000_RTN", "(P) Auron (RTN)"}, +{"N_EX820", "(N) Org XIII member - Roxas (EX)"}, +{"B_TR000", "(B) Hostile Program"}, +{"F_LM530_MATSU", "(F) Pendant (LM)"}, +{"H_LM060", "(H) Prince Eric (LM)"}, +{"N_DC010_BTL", "(N) Minnie (BTL) (DC)"}, +{"N_PO040_RTN", "(N) Eeyore (RTN) (PO)"}, +{"N_PO070_RTN", "(N) Roo (RTN) (PO)"}, +{"N_PO090_RTN", "(N) Gopher (RTN) (PO)"}, +{"N_PO100_RTN", "(N) Kanga & Roo (RTN) (PO)"}, +{"H_LM030", "(H) Ariel (LM)"}, +{"H_LM050", "(H) Ariel (with legs, in ripped clothing) (LM)"}, +{"M_EX020_NM", "(M) Shadow (NM)"}, +{"F_CA020", "(F) Isla de Muerta’s chest’s lid (CA)"}, +{"F_CA050", "(F) Explosive barrel (CA)"}, +{"N_LM010_RTN", "(N) Donald (RTN) (LM)"}, +{"N_LM020_RTN", "(N) Goofy (RTN) (LM)"}, +{"N_LM040_RTN", "(N) Poseidon (RTN) (LM)"}, +{"H_EX650", "(H) Roxas (coat) (EX)"}, +{"P_CA000_HUMAN", "(P) Jack Sparrow (human)"}, +{"F_CA550", "(F) Interceptor (CA)"}, +{"F_CA650", "(F) Black Pearl (CA)"}, +{"F_CA660", "(F) Black Pearl (Low Quality) (CA)"}, +{"F_CA720", "(F) Black Pearl (CA)"}, +{"F_DC530", "(F) Gummi room’s grassy door (DC)"}, +{"F_WI510", "(F) Theater window (room) (WI)"}, +{"N_EX500_BTL", "(N) Hayner (BTL) (EX)"}, +{"F_WI550", "(F) Pete’s door (WI)"}, +{"F_WI500", "(F) World door (WI)"}, +{"W_CA000_HUMAN", "(W) Skill and Crossbones (human)"}, +{"F_LM600_MATSU", "(F) Ursula’s contract (LM)"}, +{"B_EX110_FRIEND", "(A?) Axel"}, +{"F_LM610_MATSU", "(F) Poseidon’s Trident"}, +{"F_LM700_MATSU", "(F) Harpoon (LM)"}, +{"PO06_F_ZZ000", "(PO) Toy box from WI?"}, +{"B_CA020_HUM_OKA", "(B) Undead Pirate A (Human) (OKA)"}, +{"B_CA020_SKL_OKA", "(B) Undead Pirate A (Skeleton) (OKA)"}, +{"B_CA030_HUM_OKA", "(B) Undead Pirate B (Human) (OKA)"}, +{"B_CA030_SKL_OKA", "(B) Undead Pirate B (Skeleton) (OKA)"}, +{"B_CA040_HUM_OKA", "(B) Undead Pirate C (Human) (OKA)"}, +{"B_CA040_SKL_OKA", "(B) Undead Pirate C (Skeleton) (OKA)"}, +{"F_LM510_MATSU", "(F) Letter (LM)"}, +{"F_LM520_MATSU", "(F) Music sheet (LM)"}, +{"P_TR000_RTN", "(P) Tron (RTN)"}, +{"N_DC060_OKA", "(N) Pluto (OKA) (DC)"}, +{"N_DC070_OKA", "(N) Jiminy Cricket (OKA) (DC)"}, +{"N_LM260_MATSU", "(N) Ursula (human) (MATSU) (LM)"}, +{"N_HB020_TSURU", "(N) Huey (TSURU) (HB)"}, +{"F_HB630", "(F) Ansem’s room’s door (HB)"}, +{"F_HB660", "(F) Tron’s THANK YOU! message (HB)"}, +{"P_EX340", "(P) Tinker Bell"}, +{"P_WI030", "(P) Goofy (WI)"}, +{"N_EX780_RTN", "(N) Org XIII member - Type C (RTN) (EX)"}, +{"F_LM710_MATSU", "(F) Fish skeleton (MATSU) (LM)"}, +{"N_LK090_MATSU", "(N) Simba (child) (MATSU) (LK)"}, +{"F_LM560_MATSU", "(F) Spiral shell (MATSU) (LM)"}, +{"P_EX100_AL_CARPET", "(P) Sora (magic carpet)"}, +{"N_HB010_RTN", "(N) Scrooge (RTN) (HB)"}, +{"N_HB020_HUEY_RTN", "(N) Huey (RTN) (HB)"}, +{"N_HB020_DEWEY_RTN", "(N) Dewey (RTN) (HB)"}, +{"N_HB020_LOUIE_RTN", "(N) Louie (RTN) (HB)"}, +{"N_HB530_RTN", "(N) Squall / Leon (RTN) (HB)"}, +{"N_HB540_RTN", "(N) Cid (RTN) (HB)"}, +{"N_HB550_RTN", "(N) Cloud (RTN) (HB)"}, +{"N_HB560_RTN", "(N) Aerith (RTN) (HB)"}, +{"N_HB570_RTN", "(N) Tifa (RTN) (HB)"}, +{"N_HB580_RTN", "(N) Yuffie (RTN) (HB)"}, +{"N_HB590_RTN", "(N) Sephiroth (RTN) (HB)"}, +{"N_HB600_RTN", "(N) Yuna (RTN) (HB)"}, +{"N_HB610_RTN", "(N) Rikku (RTN) (HB)"}, +{"N_HB620_RTN", "(N) Paine (RTN) (HB)"}, +{"P_EX210_RTN", "(P) Mickey (coat) (RTN)"}, +{"N_EX640_MOOGLE_RTN", "(N) Moogle (MOOGLE) (RTN) (EX)"}, +{"N_EX650_HB_BOY_A_RTN", "(N) Villager (boy) (HB_BOY_A) (RTN) (EX)"}, +{"N_EX660_HB_LADY_A_RTN", "(N) Villager (lady) (HB_LADY_A) (RTN) (EX)"}, +{"N_EX670_HB_GIRL_A_RTN", "(N) Villager (girl) (HB_GIRL_A) (RTN) (EX)"}, +{"N_EX680_HB_MAN_A_RTN", "(N) Villager (man) (HB_MAN_A) (RTN) (EX)"}, +{"N_EX680_HB_MAN_B_RTN", "(N) Villager (man) (HB_MAN_B) (RTN) (EX)"}, +{"N_EX680_HB_ITEM_RTN", "(N) Villager (man) (HB_ITEM) (RTN) (EX)"}, +{"N_EX690_HB_WOMAN_A_RTN", "(N) Villager (man) (HB_WOMAN_A) (RTN) (EX)"}, +{"N_EX690_HB_WOMAN_B_RTN", "(N) Villager (man) (HB_WOMAN_B) (RTN) (EX)"}, +{"N_EX700_HB_WEAPON_RTN", "(N) Villager (gentleman) (HB_WEAPON) (RTN) (EX)"}, +{"N_EX500_RTN", "(N) Hayner (RTN) (EX)"}, +{"N_EX510_RTN", "(N) Pence (RTN) (EX)"}, +{"N_EX520_RTN", "(N) Olette (RTN) (EX)"}, +{"N_EX620_RTN", "(N) Naminé (RTN) (EX)"}, +{"N_EX610_RTN", "(N) Vivi (RTN) (EX)"}, +{"N_EX570_RTN", "(N) Seifer (RTN) (EX)"}, +{"N_EX580_RTN", "(N) Raijin (RTN) (EX)"}, +{"N_EX590_RTN", "(N) Fujin (RTN) (EX)"}, +{"N_EX600_RTN", "(N) Setzer (RTN) (EX)"}, +{"N_TT010_RTN", "(N) Yen Sid (RTN) (TT)"}, +{"N_TT020_RTN", "(N) Flora (RTN) (TT)"}, +{"N_TT030_RTN", "(N) Fauna (RTN) (TT)"}, +{"N_TT040_RTN", "(N) Merryweather (RTN) (TT)"}, +{"N_EX650_TT_BOY_A_RTN", "(N) Villager (boy) (TT_BOY_A) (RTN) (EX)"}, +{"N_EX650_TT_BOY_B_RTN", "(N) Villager (boy) (TT_BOY_B) (RTN) (EX)"}, +{"N_EX670_TT_GIRL_A_RTN", "(N) Villager (girl) (TT_GIRL_A) (RTN) (EX)"}, +{"N_EX670_TT_GIRL_B_RTN", "(N) Villager (girl) (TT_GIRL_B) (RTN) (EX)"}, +{"N_EX680_TT_MAN_A_RTN", "(N) Villager (man) (TT_MAN_A) (RTN) (EX)"}, +{"N_EX680_TT_MAN_B_RTN", "(N) Villager (man) (TT_MAN_B) (RTN) (EX)"}, +{"N_EX680_TT_ITEM_RTN", "(N) Villager (man) (TT_ITEM) (RTN) (EX)"}, +{"N_EX680_TT_REFEREE_RTN", "(N) Villager (man) (TT_REFEREE) (RTN) (EX)"}, +{"N_EX690_TT_WOMAN_A_RTN", "(N) Villager (woman) (TT_WOMAN_A) (RTN) (EX)"}, +{"N_EX690_TT_WOMAN_B_RTN", "(N) Villager (woman) (TT_WOMAN_B) (RTN) (EX)"}, +{"N_EX700_TT_WEAPON_RTN", "(N) Villager (gentleman) (TT_WEAPON) (RTN) (EX)"}, +{"N_EX660_TT_LADY_A_RTN", "(N) Villager (lady) (TT_LADY_A) (RTN) (EX)"}, +{"N_EX710_TT_SWEETS_RTN", "(N) Villager (old woman) (TT_SWEETS) (RTN) (EX)"}, +{"N_EX700_TT_SPONSOR_RTN", "(N) Villager (gentleman) (TT_SPONSOR) (RTN) (EX)"}, +{"N_EX650_HB_PROTECT_RTN", "(N) Villager (boy) (HB_PROTECT) (RTN) (EX)"}, +{"N_EX690_HB_ACCE_RTN", "(N) Villager (woman) (HB_ACCE) (RTN) (EX)"}, +{"N_EX710_HB_OLD_F_A_RTN", "(N) Villager (old woman) (HB_OLD_F_A) (RTN) (EX)"}, +{"N_EX810_HB_OLD_M_A_RTN", "(N) Villager (old man) (HB_OLD_M_A) (RTN) (EX)"}, +{"N_EX650_TT_PROTECT_RTN", "(N) Villager (boy) (TT_PROTECT) (RTN) (EX)"}, +{"N_EX690_TT_ACCE_RTN", "(N) Villager (woman) (TT_ACCE) (RTN) (EX)"}, +{"N_EX700_TT_GENTL_A_RTN", "(N) Villager (gentleman) (TT_GENTL_A) (RTN) (EX)"}, +{"N_EX700_TT_GENTL_B_RTN", "(N) Villager (gentleman) (TT_GENTL_B) (RTN) (EX)"}, +{"N_EX810_TT_OLD_M_A_RTN", "(N) Villager (old man) (TT_OLD_M_A) (RTN) (EX)"}, +{"N_EX710_TT_OLD_F_A_RTN", "(N) Villager (old man) (TT_OLD_F_A) (RTN) (EX)"}, +{"B_MU100_HOOD", "(B) Shan-Yu (HOOD) (MU)"}, +{"H_MU050_HOOD", "(H) Shan Yu (HOOD) (MU)"}, +{"N_EX720_RTN", "(N) Dog (RTN)"}, +{"N_EX730_RTN", "(N) Cat (RTN)"}, +{"N_EX740_RTN", "(N) Dove (RTN)"}, +{"N_LK010_BTL", "(N) Timon (BTL) (LK)"}, +{"N_LK020_BTL", "(N) Pumba (BTL) (LK)"}, +{"H_EX630", "(H) Sora (Mermaid) (EX)"}, +{"P_EX030_TR", "(N) Goofy (TR)"}, +{"M_EX020_RAW", "(M) Shadow (RAW)"}, +{"M_EX760_CROWD", "(M) Armored Knight (CROWD)"}, +{"M_EX660_CROWD", "(M) Rapid Thruster (CROWD)"}, +{"M_EX660_RAW", "(M) Rapid Thruster (RAW)"}, +{"F_TR560", "(F) Wall (TR)"}, +{"P_EX020_TR", "(P) Donald (TR)"}, +{"H_LM080", "(H) Goofy (LM)"}, +{"N_NM130_ISHIDA", "(N) Santa’s sleigh (ISHIDA) (NM)"}, +{"W_EX030_10", "(W) Adamant Shield"}, +{"W_EX030_10_NM", "(W) Adamant Shield (NM)"}, +{"W_EX030_20", "(W) Chain Gear"}, +{"W_EX030_20_NM", "(W) Chain Gear (NM)"}, +{"W_EX030_30", "(W) Ogre Shield"}, +{"W_EX030_30_NM", "(W) Ogre Shield (NM)"}, +{"W_EX030_40", "(W) Falling Star"}, +{"W_EX030_40_NM", "(W) Falling Star (NM)"}, +{"W_EX030_50", "(W) Dreamcloud"}, +{"W_EX030_50_NM", "(W) Dreamcloud (NM)"}, +{"W_EX030_60", "(W) Knight Defender"}, +{"W_EX030_60_NM", "(W) Knight Defender (NM)"}, +{"W_EX030_70", "(W) Genji Shield"}, +{"W_EX030_70_NM", "(W) Genji Shield (NM)"}, +{"W_EX030_80", "(W) Akashic Record"}, +{"W_EX030_80_NM", "(W) Akashic Record (NM)"}, +{"W_EX030_90", "(W) Nobody Guard"}, +{"W_EX030_90_NM", "(W) Nobody Guard (NM)"}, +{"W_EX030_Y0", "(W) Detection Shield"}, +{"F_HE020_C1", "(F) Rock 1 (blue) (HE)"}, +{"F_HE020_C2", "(F) Rock 2 (blue) (HE)"}, +{"H_ZZ020_TR", "(H) Donald (TR) (ZZ)"}, +{"W_EX020_10", "(W) Hammer Staff"}, +{"W_EX020_20", "(W) Victory Bell"}, +{"W_EX020_30", "(W) Meteor Staff"}, +{"W_EX020_40", "(W) Comet Staff"}, +{"W_EX020_50", "(W) Lord’s Broom"}, +{"W_EX020_60", "(W) Wisdom Wand"}, +{"W_EX020_70", "(W) Rising Dragon"}, +{"W_EX020_80", "(W) Nobody Lance"}, +{"W_EX020_90", "(W) Shaman’s Relic"}, +{"W_EX020_10_NM", "(W) Hammer Staff (NM)"}, +{"W_EX020_20_NM", "(W) Victory Bell (NM)"}, +{"W_EX020_30_NM", "(W) Meteor Staff (NM)"}, +{"W_EX020_40_NM", "(W) Comet Staff (NM)"}, +{"W_EX020_50_NM", "(W) Lord’s Broom (NM)"}, +{"W_EX020_60_NM", "(W) Wisdom Wand (NM)"}, +{"W_EX020_70_NM", "(W) Rising Dragon (NM)"}, +{"W_EX020_80_NM", "(W) Nobody Lance (NM)"}, +{"W_EX020_90_NM", "(W) Shaman’s Relic (NM)"}, +{"W_EX020_Y0", "(W) Detection Staff"}, +{"F_LM540_MATSU", "(F) Prince statue (LM)"}, +{"F_LM550_MATSU", "(F) Prince statue’s face (LM)"}, +{"F_TT010", "(F) Skateboard (TT)"}, +{"F_LM620_MATSU", "(F) Gold box (LM)"}, +{"F_LM630_MATSU", "(F) Toy box (LM)"}, +{"F_LM640_MATSU", "(F) Toy box open (LM)"}, +{"F_LM650_MATSU", "(F) Candle holder (Spoon and fork) (LM)"}, +{"F_LM670_MATSU", "(F) Fork (LM)"}, +{"F_LM760_MATSU", "(F) Globe (LM)"}, +{"F_PO630", "(F) Balloon (blue) (PO)"}, +{"F_PO680", "(F) Honey Pot (PO)"}, +{"F_PO690", "(F) Honey Pot (full of honey) (PO)"}, +{"F_HB580", "(F) Stool (HB)"}, +{"F_PO510", "(F) Winnie the Pooh book (Damaged) (PO)"}, +{"F_PO600", "(F) Eeyore’s house"}, +{"F_PO640", "(F) Carrot (no texture) (PO)"}, +{"F_PO650", "(F) Pumpkin (no texture) (PO)"}, +{"F_PO660", "(F) Cabbage (no texture) (PO)"}, +{"F_TT000", "(F) Tram (TT)"}, +{"M_EX760_RAW", "(M) Armored Knight (RAW) (1000 battle)"}, +{"M_EX770_RAW", "(M) Surveillance Robot (RAW) (1000 battle)"}, +{"F_CA060", "(F) ??? (CA)"}, +{"F_AL710_MATSU", "(F) Purple jar (AL)"}, +{"F_AL720_MATSU", "(F) Purple floor tile (AL)"}, +{"F_AL730_MATSU", "(F) Broken stone path (AL)"}, +{"B_HE000_ISHI", "(M) Giant Rock Titan (Dummy)"}, +{"PRIZE", "PRIZE"}, +{"PRIZE_MU", "PRIZE_MU"}, +{"F_LM660_MATSU", "(F) Music box (LM)"}, +{"F_LM680_MATSU", "(F) Corkscrew box (LM)"}, +{"F_LM690_MATSU", "(F) Clam-drum kit (LM)"}, +{"F_LM720_MATSU", "(F) Stick (LM)"}, +{"F_LM730_MATSU", "(F) Show clams (LM)"}, +{"F_LM740_MATSU", "(F) Show Clam (LM)"}, +{"F_LM750_MATSU", "(F) Husk (LM)"}, +{"F_LM770_MATSU", "(F) ??? - Path? Pyramid? (LM)"}, +{"F_LM780_MATSU", "(F) Poseidon’s dolphins (LM)"}, +{"B_NM100", "(B) Prison Keeper"}, +{"P_WI020", "(P) Donald (WI)"}, +{"B_NM110", "(B) The Experiment"}, +{"H_EX640", "(H) Sora (Lion) (EX)"}, +{"F_PO590", "(F) Bottle (PO)"}, +{"N_PO110_ISHI", "(N) Sora (KH1, dark) (ISHI) (PO)"}, +{"F_CA710", "(F) Jack Sparrow’s compass (CA)"}, +{"F_HB650", "(F) Winnie the Pooh’s book (HB)"}, +{"F_TT640", "(F) Curtain (TT)"}, +{"F_TT710", "(F) Sleep capsule (TT)"}, +{"F_DC580_DC05", "(F) Timeless River door (dark) (DC)"}, +{"F_CA040", "(F) Anchor (CA)"}, +{"F_TT700", "(F) Secret lab’s door (TT)"}, +{"F_TT800", "(F) Window (TT)"}, +{"F_CA700", "(F) Paddle (CA)"}, +{"H_ZZ030_TR", "(H) Goofy (TR) (ZZ)"}, +{"N_CA080_OKA", "(N) Elizabeth’s arm (OKA) (CA)"}, +{"N_CA090_OKA", "(N) Barbossa’s arm (OKA) (CA)"}, +{"F_TT520", "(F) Photo 1 (TT)"}, +{"F_CA580", "(F) Barbossa’s wine bottle (CA)"}, +{"F_CA590", "(F) Door (Black Pearl’s?) (CA)"}, +{"F_CA600", "(F) Chair (CA)"}, +{"F_CA560", "(F) Gun (CA)"}, +{"F_HB640", "(F) Merlin’s door (HB)"}, +{"B_CA060", "(?) Port Royal enemy?"}, +{"F_CA680", "(F) Dagger (CA)"}, +{"F_CA640", "(F) Gun (CA)"}, +{"AL14_PLAYER", "Sora (Carpet)"}, +{"AL14_CARPET", "Carpet"}, +{"F_CA520", "(F) Rope tying Jack (CA)"}, +{"F_CA750", "(F) Rope tying Sora (CA)"}, +{"F_CA760", "(F) Rope tying Donald (CA)"}, +{"F_CA770", "(F) Rope tying Goofy (CA)"}, +{"P_LK020", "(P) Donald (LK)"}, +{"F_HB020", "(F) ??? (HB)"}, +{"F_HB670", "(F) ??? - Path? Pyramid? (HB)"}, +{"PRIZE_CA", "PRIZE_CA"}, +{"PRIZE_HE", "PRIZE_HE (Phil minigame)"}, +{"PRIZE_TT", "PRIZE_TT"}, +{"P_EX100_TR_LIGHTCYCLE", "(P) Sora (on light cycle)"}, +{"PRIZE_PO", "PRIZE_PO"}, +{"N_EX880", "(N) Org XIII member - Type H (EX)"}, +{"B_EX150", "(B) Luxord (WORKS! can’t be killed, or paused)"}, +{"H_EX500_MAGF", "(H) Sora (Wisdom)"}, +{"H_EX500_TRIF", "(H) Sora (Master)"}, +{"H_EX500_ULTF", "(H) Sora (Final)"}, +{"H_CA060", "(H) Barbossa (skeleton) (CA)"}, +{"F_HB530", "(F) Laboratory’s door (HB)"}, +{"F_HB520", "(F) Xehanort portrait (HB)"}, +{"B_EX210", "(M) Luxord’s card (attack)"}, +{"F_PO610", "(F) Gopher’s hole (PO)"}, +{"M_EX950_CARD", "(M) Sora (Gambler’s card)"}, +{"M_EX950_DICE", "(M) Sora (Gambler’s die)"}, +{"F_TR150", "(F) ??? (TR)"}, +{"F_WI520", "(F) Theater window (WI)"}, +{"F_PO520", "(F) Pooh’s house pop-up card (PO)"}, +{"M_EX990_RTN", "(M) Dusk (RTN)"}, +{"B_CA050", "(B) Grim Reaper"}, +{"F_TT060", "(F) ??? - minigame reaction command? (TT)"}, +{"F_CA530", "(F) Dagger (CA)"}, +{"H_EX690", "(H) Luxord (EX)"}, +{"F_AL070_FIRE", "(F) Fire orbs (AL)"}, +{"F_AL070_BLIZZARD", "(F) Blizzard orbs (AL)"}, +{"F_AL070_THUNDER", "(F) Thunder orbs (AL)"}, +{"F_TT690", "(F) Mansion’s gate (TT)"}, +{"F_TT810", "(F) Mansion’s gate lock (TT)"}, +{"F_EX580", "(F) Sea salt ice cream"}, +{"PRIZE_NM", "PRIZE_NM (present)"}, +{"F_EX040_SPARROW", "(F) Jack Sparrow’s limit’s treasure chest (EX)"}, +{"P_LK030", "(P) Goofy (LK)"}, +{"N_HB530_BTL", "(N) Squall / Leon (BTL) (HB)"}, +{"PO06_N_PO010", "(PO) Pooh"}, +{"PO06_N_PO030", "(PO) Pigglet"}, +{"H_DC010", "(H) Queen Minnie (DC)"}, +{"F_TT120", "(F) Junk 2 (TT)"}, +{"F_MU600", "(F) ??? - Path? Pyramid? (MU)"}, +{"B_EX140", "(B) Xigbar"}, +{"F_AL100", "(F) Sandstorm (AL)"}, +{"N_EX780", "(N) Org XIII member - Type C (EX)"}, +{"F_PO090", "(PO) Bees (PO)"}, +{"M_EX660_E_CROWD", "(M) Rapid Thruster (E_CROWD)"}, +{"M_EX800", "(M) Bolt Tower"}, +{"M_EX800_RAW", "(M) Bolt Tower (RAW)"}, +{"F_TT590", "(F) Purse (TT)"}, +{"B_AL100_1ST", "(M) Volcano Lord"}, +{"B_AL100_2ND", "(M) Blizzard Lord"}, +{"F_HB590", "(F) Ansem’s room chair (HB)"}, +{"F_TT510", "(F) Camera (TT)"}, +{"N_AL070_BTL", "(N) Jafar clone (BTL) (AL)"}, +{"F_AL080", "(F) ??? (AL)"}, +{"F_CA790", "(F) Ship’s back windows (CA)"}, +{"N_EX940_BTL", "(N) Riku (coat) (BTL) (EX)"}, +{"N_EX840", "(N) Org XIII member - Type D (EX)"}, +{"N_EX940", "(N) Riku (coat) (EX)"}, +{"F_CA690", "(F) Isla de Muerta’s chest (Open) (CA)"}, +{"F_AL140", "(F) Boxes and rugs (AL)"}, +{"F_AL150", "(F) Piece of stone wall (AL)"}, +{"F_AL160", "(F) Building floor (AL)"}, +{"F_AL170", "(F) Tip of tower (AL)"}, +{"F_NM090", "(F) Oogie’s punch (NM)"}, +{"F_CA780", "(F) Isla de Muerta’s chest’s top layer (CA)"}, +{"B_EX170", "(B) Xemnas"}, +{"N_WI010_BTL_VS", "(N) Pete (captain) (BTL_VS) (WI)"}, +{"F_HB600", "(F) Ansem Laboratory’s Tron CD (HB)"}, +{"F_HB510", "(F) Goofy killing stone (HB)"}, +{"F_WI010_BOSS", "(F) ??? - Path? Pyramid? Sparkle? White fire? (BOSS) (WI)"}, +{"F_WI020_BOSS", "(F) Burning building (BOSS) (WI)"}, +{"F_LK560", "(F) Mufasa (Ghost) (LK)"}, +{"F_EH520", "(F) Ansem’s machine (EH)"}, +{"F_TT660", "(F) Hayner’s letter (TT)"}, +{"F_TT670", "(F) Medal (TT)"}, +{"F_TT680", "(F) Champion Belt (TT)"}, +{"F_TT780", "(F) Maleficent’s cape (TT)"}, +{"F_TT870", "(F) White box (TT)"}, +{"F_TR140", "(F) Light cycle (TR)"}, +{"P_EX100_TR", "(P) Sora (TR)"}, +{"P_EX100_WI", "(P) Sora (WI)"}, +{"F_CA540", "(F) Rope tying Jack (CA)"}, +{"F_EH530", "(F) Ansem’s machine’s laser (EH)"}, +{"PO06_F_PO040", "(PO) Beehive"}, +{"PO06_F_PO050", "(PO) Tree stump"}, +{"PO06_F_PO060_S", "(PO) Red balloon (S)"}, +{"PO06_F_PO080", "(PO) Honey pot"}, +{"PO07_N_PO010", "(PO) Pooh"}, +{"PO07_N_PO090", "(PO) Gopher"}, +{"PO07_F_PO040", "(PO) Beehive"}, +{"PO07_F_PO080", "(PO) Honey pot"}, +{"PO07_F_PO060_S", "(PO) Red Balloon (S)"}, +{"PO08_N_PO010", "(PO) Pooh"}, +{"PO08_F_PO060_S", "(PO) Balloon (S)"}, +{"PO08_F_PO060_M", "(PO) Balloon (M)"}, +{"PO08_F_PO060_L", "(PO) Balloon (L)"}, +{"PO08_F_PO060_X", "(PO) Balloon (XL)"}, +{"P_EX100_TR_BTLF", "(P) Sora (TR) (Valor)"}, +{"P_EX100_WI_BTLF", "(P) Sora (WI) (Valor)"}, +{"P_EX100_TR_MAGF", "(P) Sora (TR) (Wisdom)"}, +{"P_EX100_WI_MAGF", "(P) Sora (WI) (Wisdom)"}, +{"P_EX100_TR_TRIF", "(P) Sora (TR) (Master)"}, +{"P_EX100_WI_TRIF", "(P) Sora (WI) (Master)"}, +{"P_EX100_TR_ULTF", "(P) Sora (TR) (Final)"}, +{"P_EX100_WI_ULTF", "(P) Sora (WI) (Final)"}, +{"P_EX100_TR_HTLF", "(P) Sora (TR) (Anti)"}, +{"P_EX100_WI_HTLF", "(P) Sora (WI) (Anti)"}, +{"H_EX570", "(H) Xigbar (EX)"}, +{"N_EX870", "(N) Org XIII member - Type G (EX)"}, +{"N_EX580", "(N) Raijin (EX)"}, +{"N_EX590", "(N) Fujin (EX)"}, +{"F_NM150", "(F) ??? (NM)"}, +{"N_EX570_BTL", "(N) Seifer (BTL) (EX)"}, +{"F_EH000", "(F) Crooked Ascension room (EH)"}, +{"F_EH010", "(F) Twilight’s View room (EH)"}, +{"F_EH020", "(F) Big vertical white line (EH)"}, +{"F_EH030", "(F) Crooked Ascension room’s horizontal columns (EH)"}, +{"F_EH040", "(F) White and green beam (EH)"}, +{"F_CA570", "(F) Isla de Muerta’s chest (CA)"}, +{"F_CA620", "(F) Door (Black Pearl’s?) (CA)"}, +{"F_HE770", "(F) Goddess of Fate cup (HE)"}, +{"F_HE780", "(F) Cerberus cup (HE)"}, +{"F_HE790", "(F) Titan cup (HE)"}, +{"F_HE800", "(F) Hades Paradox cup (HE)"}, +{"F_TT730", "(F) Yen Sid´s door (TT)"}, +{"N_EX500_MONEY_RTN", "(N) Hayner (MONEY) (RTN) (EX)"}, +{"M_AL020_FIRE_RAW", "(M) Fiery Globe (RAW)"}, +{"M_AL020_ICEE_RAW", "(M) Icy Cube (RAW)"}, +{"N_HB550_BTL", "(N) Cloud (BTL) (HB)"}, +{"M_AL020_FIRE2", "(M) Fiery Globe (2)"}, +{"M_AL020_ICEE2", "(M) Icy Cube (2)"}, +{"M_EX600_LC", "(M) Magnum Loader (white)"}, +{"F_AL000_DUMMY", "(F) Water spitting statue (DUMMY) (AL)"}, +{"H_HB530", "(H) Squall / Leon (HB)"}, +{"H_HB550", "(H) Cloud (HB)"}, +{"DEAD_BOSS", "DEAD_BOSS"}, +{"H_EX770", "(H) Seifer(EX)"}, +{"N_LK130_MATSU", "(N) Wildebeest stampede (MATSU) (LK)"}, +{"F_TT740", "(F) Yen Sid’s chair (TT)"}, +{"F_TT830", "(F) Fairies’ room’s door (TT)"}, +{"H_EX500_TR", "(H) Sora (TR)"}, +{"H_EX500_TR_BTLF", "(H) Sora (Valor) (TR)"}, +{"H_EX500_TR_MAGF", "(H) Sora (Wisdom) (TR)"}, +{"H_EX500_TR_TRIF", "(H) Sora (Master) (TR)"}, +{"H_EX500_TR_ULTF", "(H) Sora (Final) (TR)"}, +{"F_TT750", "(F) Yen Sid’s book (TT)"}, +{"F_HB680", "(F) ??? - Purple square (HB)"}, +{"F_HB690", "(F) Sora’s HBRC Honorary member card (HB)"}, +{"W_EX010_WI", "(W) Kingdom Key (WI)"}, +{"W_EX020_WI", "(W) Mage’s Staff (WI)"}, +{"W_EX030_WI", "(W) Knight’s Shield (WI)"}, +{"PO08_N_PO020", "(PO) Tigger"}, +{"PO08_N_PO070", "(PO) Roo"}, +{"N_EX860", "(N) Org XIII member - Type F (EX)"}, +{"F_TT130", "(F) Junk 3 (TT)"}, +{"N_HB580_BTL", "(N) Yuffie (BTL) (HB)"}, +{"N_EX910", "(N) DiZ (No head bandages) (EX)"}, +{"H_TT010", "(H) Yen Sid (TT)"}, +{"N_HB570_BTL", "(N) Tifa (BTL) (HB)"}, +{"P_EX100_MEMO", "(P) Sora (MEMO)"}, +{"M_EX600_LC_ATK", "(M) Magnum Loader (blue)"}, +{"M_EX600_LC_CHG", "(M) Magnum Loader (yellow)"}, +{"M_EX600_LC_GRD", "(M) Magnum Loader (green)"}, +{"F_CA000", "(F) Black Pearl (CA)"}, +{"F_CA010", "(F) ??? - Something from Black Pearl? (CA)"}, +{"N_EX760_BTL_WILLY", "(N) Pete (BTL_WILLY) (EX)"}, +{"N_EX760_BTL_MEGARA", "(N) Pete (BTL_MEGARA) (EX)"}, +{"N_EX760_BTL_HERCULES", "(N) Pete (BTL_HERCULES) (EX)"}, +{"F_WI560", "(F) Cannon tower (WI)"}, +{"LAST_GIMMICK", "LAST_GIMMICK"}, +{"M_EX850", "(M) Berserker weapon (Useable)"}, +{"M_EX860", "(M) Sora Die"}, +{"P_EX120", "(P) Sora (KH1)"}, +{"N_PO040_BTL", "(N) Eeyore (BTL) (PO)"}, +{"N_PO020_BTL", "(N) Tigger (BTL) (PO)"}, +{"N_PO030_BTL", "(N) Pigglet (BTL) (PO)"}, +{"N_PO070_BTL", "(N) Roo (BTL) (PO)"}, +{"H_EX730", "(H) DiZ (No head bandages) (EX)"}, +{"H_DI500", "(H) Selphie, school uniform (DI)"}, +{"P_EX350", "(P) Chicken Little"}, +{"B_EX160", "(B) Saïx"}, +{"F_HE020_PO", "(F) Falling crystal (PO)"}, +{"H_EX660", "(H) Xenmas (EX)"}, +{"F_CA690_BTL", "(F) Isla de Muerta’s chest (Grim Reaper) (Open) (BTL) (CA)"}, +{"F_TR040", "(F) Unused creeper thing from Space Paranoids world (TR)"}, +{"N_PO010_BTL", "(N) Pooh (BTL) (PO)"}, +{"H_EX680", "(H) Saïx (EX)"}, +{"N_MU100", "(N) Rapid Thruster avalanche (MU)"}, +{"F_WI511", "(F) Theater window (burning building) (WI)"}, +{"F_WI512", "(F) Theater window (liliput) (WI)"}, +{"F_WI513", "(F) Theater window (building site) (WI)"}, +{"F_CA060_MEDAL", "(F) ??? - Attackable floor? (MEDAL) (CA)"}, +{"F_TT620", "(F) Train (TT)"}, +{"F_TT630", "(F) Train’s door (TT)"}, +{"F_TT631", "(F) Train’s door (dark) (TT)"}, +{"P_TR010", "(P) ??? (TR)"}, +{"F_PO080", "(F) Honey pot (PO)"}, +{"H_EX790", "(H) Sora (KH1 clothes) (EX)"}, +{"F_WI060_PETE", "(F) Cannon tower (PETE) (WI)"}, +{"F_WI360_PETE", "(F) Building site’s platform (PETE) (WI)"}, +{"M_EX620_AL", "(M) Fortuneteller (AL)"}, +{"F_WI570", "(F) ??? (WI)"}, +{"B_TR020", "(B) MCP"}, +{"F_TT650", "(F) Pencil (No texture) (TT)"}, +{"F_TT850", "(F) Drawing book (TT)"}, +{"F_EH050", "(F) Floating building 1 (EH)"}, +{"F_EH060", "(F) Floating building 2 (EH)"}, +{"F_PO530", "(F) Pigglet’s house pop-up card (PO)"}, +{"F_PO540", "(F) Rabbit’s house pop-up card (PO)"}, +{"F_PO550", "(F) Kanga & Roo’s house pop up card (PO)"}, +{"F_PO560", "(F) Spooky cave pop up card (PO)"}, +{"F_PO570", "(F) Starry Hill pop up card (PO)"}, +{"F_TT720", "(F) Thank Naminé journal (TT)"}, +{"F_PO030", "(F) ??? (PO)"}, +{"M_EX790_HALLOWEEN", "(M) Graveyard"}, +{"M_EX790_HALLOWEEN_NM", "(M) Graveyard (NM)"}, +{"M_EX790_CHRISTMAS", "(M) Toy Soldier"}, +{"M_EX790_CHRISTMAS_NM", "(M) Toy Soldier (NM)"}, +{"N_HB590_BTL", "(N) Sephiroth (BTL) (HB)"}, +{"W_EX010_TR", "(W) Kingdom Key (TR)"}, +{"W_EX020_TR", "(W) Mage’s Staff (TR)"}, +{"W_EX030_TR", "(W) Knight’s Shield (TR)"}, +{"F_TR570", "(F) MCP face (TR)"}, +{"F_CA800", "(F) Ship’s rudder (CA)"}, +{"F_LK530", "(F) Flower parts in the wind for Rafiki (LK)"}, +{"F_LK540", "(F) Rafiki’s mixing bowl (LK)"}, +{"B_EX220", "(F) Saix’s claymore (Usable)"}, +{"F_LK510", "(F) Small stones (LK)"}, +{"F_LK520", "(F) Bent tree (LK)"}, +{"F_LK550", "(F) Rafiki’s bowl of red paint (LK)"}, +{"F_LK561", "(F) Mufasa (dark) (LK)"}, +{"F_TT521", "(F) Photo 2 (TT)"}, +{"F_TT522", "(F) Photo 3 (TT)"}, +{"F_TT523", "(F) Photo 4 (TT)"}, +{"F_TT524", "(F) Photo 5 (TT)"}, +{"F_TT525", "(F) Photo 6 (TT)"}, +{"F_TR160", "(F) WARNING message (TR)"}, +{"B_NM110_HEAD", "(B) The Experiment (Head)"}, +{"B_NM110_L_ARM", "(B) The Experiment (Left Hand)"}, +{"B_NM110_R_ARM", "(B) The Experiment (Right Hand)"}, +{"F_LK562", "(F) Mufasa (LK)"}, +{"H_EX500_NM", "(H) Sora (NM)"}, +{"H_EX500_NM_BTLF", "(H) Sora (Valor) (NM)"}, +{"H_EX500_NM_MAGF", "(H) Sora (Wisdom) (NM)"}, +{"H_EX500_NM_TRIF", "(H) Sora (Master) (NM)"}, +{"H_EX500_NM_ULTF", "(H) Sora (Final) (NM)"}, +{"N_EX830", "(N) Org XIII member - Roxas (no hood) (EX)"}, +{"N_EX690_TT_A_SKATE_RTN", "(N) Villager (woman) (TT_A_SKATE) (RTN) (EX)"}, +{"F_WI580", "(F) Building site’s platform (WI)"}, +{"M_EX800_CROWD", "(M) Bolt Tower (CROWD)"}, +{"F_TT110", "(F) Dog’s sack (TT)"}, +{"M_EX500_NM", "(M) Trick Ghost (NM)"}, +{"M_EX540_WI", "(M) Aeroplane (WI)"}, +{"M_EX550_WI", "(M) Minute Bomb (WI)"}, +{"M_EX560_WI", "(M) Hammer Frame (WI)"}, +{"M_EX640_WI", "(M) Hot Rod (WI)"}, +{"M_EX650_TR", "(M) Cannon Gun (TR)"}, +{"M_EX700_NM", "(M) Mole Driller (NM)"}, +{"M_EX120_NM", "(M) Emerald Blues (NM)"}, +{"M_EX530_TR", "(M) Bookmaster (TR)"}, +{"M_EX420_NM", "(M) Neoshadow (NM)"}, +{"M_EX750_NM", "(M) Creeper Plant (NM)"}, +{"M_EX010_NM", "(M) Soldier (NM)"}, +{"M_EX010_TR", "(M) Soldier (TR)"}, +{"M_EX020_WI", "(M) Shadow (WI)"}, +{"M_EX020_NM_RAW", "(M) Shadow (NM) (RAW)"}, +{"M_EX020_WI_RAW", "(M) Shadow (WI) (RAW)"}, +{"M_EX660_WI", "(M) Rapid Thruster (WI)"}, +{"M_EX760_NM", "(M) Armored Knight (NM)"}, +{"M_EX770_TR", "(M) Surveillance Robot (TR)"}, +{"M_EX010_WI", "(M) Soldier (WI)"}, +{"N_EX950", "(N) Riku (face cover, oblivion) (EX)"}, +{"W_EX010_W0", "(W) Struggle Sword"}, +{"M_EX120_TR", "(M) Emerald Blues (TR)"}, +{"N_EX900_RTN", "(N) Riku (Ansem form) (RTN) (EX)"}, +{"N_EX560_RTN", "(N) Kairi (RTN) (EX)"}, +{"F_EH550", "(F) Dark flat line (EH)"}, +{"P_EX220_RTN", "(P) Mickey (RTN)"}, +{"N_EX650_HB_ITEM_RTN", "(N) Villager (boy) (HB_ITEM) (RTN) (EX)"}, +{"N_EX680_HB_PROTECT_RTN", "(N) Villager (man) (HB_PROTECT) (RTN) (EX)"}, +{"N_EX650_TT_ITEM_RTN", "(N) Villager (boy) (TT_ITEM) (RTN) (EX)"}, +{"N_EX680_TT_PROTECT_RTN", "(N) Villager (man) (TT_PROTECT) (RTN) (EX)"}, +{"M_EX800_E_CROWD", "(M) Bolt Tower (E_CROWD)"}, +{"PRIZE_TR", "PRIZE_TR"}, +{"F_TT760", "(F) Gummi Ship (TT)"}, +{"N_BB010_SIT_RTN", "(N) Belle (sitting) (RTN) (BB)"}, +{"N_CA020_SIT_RTN", "(N) Will Turner (sitting) (RTN) (CA)"}, +{"N_HE010_SIT_RTN", "(N) Hercules (sitting) (RTN) (HE)"}, +{"N_NM010_SIT_RTN", "(N) Sally (sitting) (RTN) (NM)"}, +{"N_HB540_SIT_RTN", "(N) Cid (sitting) (RTN) (HB)"}, +{"F_NM570", "(F) 2D dress (NM)"}, +{"M_EX610_RAW", "(M) Strafer (RAW)"}, +{"P_EX130", "(P) Shadow Roxas"}, +{"F_HE020_PETE", "(F) Falling boulder (Pete battle) (HE)"}, +{"N_EX650_BTL1", "(N) Villager (boy) (BTL1) (EX)"}, +{"N_EX670_BTL1", "(N) Villager (girl) (BTL1) (EX)"}, +{"F_HB540", "(F) Hollow Bastion’s gate (HB)"}, +{"B_LK130", "(F) Path for Sora Lion’s groundshaker RC"}, +{"H_EX610", "(H) Mickey (EX)"}, +{"F_AL110", "(F) ??? (AL)"}, +{"M_EX520_AL", "(M) Hook Bat (AL)"}, +{"M_EX130_AL", "(M) Crimson Jazz (AL)"}, +{"M_EX660_AL", "(M) Rapid Thruster (AL)"}, +{"N_EX680_BTL1", "(N) Villager (man) (BTL1) (EX)"}, +{"N_EX690_BTL1", "(N) Villager (woman) (BTL1) (EX)"}, +{"P_EX230", "(P) Mickey (with Keyblade)"}, +{"F_EH560", "(F) Alter of Naught room (EH)"}, +{"B_EX180", "(?) Xemnas’s dragon (Throne)"}, +{"B_EX240", "(?) Xemnas’s dragon (Anchored)"}, +{"W_EX010_30", "(W) Star Seeker"}, +{"W_EX010_40", "(W) Hidden Dragon"}, +{"W_EX010_50", "(W) Hero’s Crest"}, +{"W_EX010_60", "(W) Monochrome"}, +{"W_EX010_70", "(W) Follow the Wind"}, +{"W_EX010_80", "(W) Circle of Life"}, +{"W_EX010_90", "(W) Photon Debugger"}, +{"W_EX010_A0", "(W) Gull Wing"}, +{"W_EX010_B0", "(W) Rumbling Rose"}, +{"W_EX010_C0", "(W) Guardian Soul"}, +{"W_EX010_D0", "(W) Wishing Lamp"}, +{"W_EX010_E0", "(W) Decisive Pumpkin"}, +{"W_EX010_F0", "(W) Sleeping Lion"}, +{"W_EX010_G0", "(W) Sweet Memories"}, +{"W_EX010_H0", "(W) Mysterious Abyss"}, +{"W_EX010_J0", "(W) Fatal Crest"}, +{"W_EX010_K0", "(W) Bond of Flame"}, +{"W_EX010_M0", "(W) Fenrir"}, +{"W_EX010_N0", "(W) Ultima Weapon"}, +{"W_EX010_U0", "(W) Struggle Hammer"}, +{"W_EX010_V0", "(W) Struggle Wand"}, +{"W_EX010_30_NM", "(W) Star Seeker (NM)"}, +{"W_EX010_40_NM", "(W) Hidden Dragon (NM)"}, +{"W_EX010_50_NM", "(W) Hero’s Crest (NM)"}, +{"W_EX010_60_NM", "(W) Monochrome (NM)"}, +{"W_EX010_70_NM", "(W) Follow the Wind (NM)"}, +{"W_EX010_80_NM", "(W) Circle of Life (NM)"}, +{"W_EX010_90_NM", "(W) Photon Debugger (NM)"}, +{"W_EX010_A0_NM", "(W) Gull Wing (NM)"}, +{"W_EX010_B0_NM", "(W) Rumbling Rose (NM)"}, +{"W_EX010_C0_NM", "(W) Guardian Soul (NM)"}, +{"W_EX010_D0_NM", "(W) Wishing Lamp (NM)"}, +{"W_EX010_E0_NM", "(W) Decisive Pumpkin (NM)"}, +{"W_EX010_F0_NM", "(W) Sleeping Lion (NM)"}, +{"W_EX010_G0_NM", "(W) Sweet Memories (NM)"}, +{"W_EX010_H0_NM", "(W) Mysterious Abyss (NM)"}, +{"W_EX010_J0_NM", "(W) Fatal Crest (NM)"}, +{"W_EX010_K0_NM", "(W) Bond of Flame (NM)"}, +{"W_EX010_M0_NM", "(W) Fenrir (NM)"}, +{"W_EX010_N0_NM", "(W) Ultima Weapon (NM)"}, +{"W_EX010_10_TR", "(W) Oath Keeper (TR)"}, +{"W_EX010_20_TR", "(W) Oblivion (TR)"}, +{"W_EX010_30_TR", "(W) Star Seeker (TR)"}, +{"W_EX010_40_TR", "(W) Hidden Dragon (TR)"}, +{"W_EX010_50_TR", "(W) Hero’s Crest (TR)"}, +{"W_EX010_60_TR", "(W) Monochrome (TR)"}, +{"W_EX010_70_TR", "(W) Follow the Wind (TR)"}, +{"W_EX010_80_TR", "(W) Circle of Life (TR)"}, +{"W_EX010_90_TR", "(W) Photon Debugger (TR)"}, +{"W_EX010_A0_TR", "(W) Gull Wing (TR)"}, +{"W_EX010_B0_TR", "(W) Rumbling Rose (TR)"}, +{"W_EX010_C0_TR", "(W) Guardian Soul (TR)"}, +{"W_EX010_D0_TR", "(W) Wishing Lamp (TR)"}, +{"W_EX010_E0_TR", "(W) Decisive Pumpkin (TR)"}, +{"W_EX010_F0_TR", "(W) Sleeping Lion (TR)"}, +{"W_EX010_G0_TR", "(W) Sweet Memories (TR)"}, +{"W_EX010_H0_TR", "(W) Mysterious Abyss (TR)"}, +{"W_EX010_J0_TR", "(W) Fatal Crest (TR)"}, +{"W_EX010_K0_TR", "(W) Bond of Flame (TR)"}, +{"W_EX010_M0_TR", "(W) Fenrir (TR)"}, +{"W_EX010_N0_TR", "(W) Ultima Weapon (TR)"}, +{"W_EX010_10_WI", "(W) Oath Keeper (WI)"}, +{"W_EX010_20_WI", "(W) Oblivion (WI)"}, +{"W_EX010_30_WI", "(W) Star Seeker (WI)"}, +{"W_EX010_40_WI", "(W) Hidden Dragon (WI)"}, +{"W_EX010_50_WI", "(W) Hero’s Crest (WI)"}, +{"W_EX010_60_WI", "(W) Monochrome (WI)"}, +{"W_EX010_70_WI", "(W) Follow the Wind (WI)"}, +{"W_EX010_80_WI", "(W) Circle of Life (WI)"}, +{"W_EX010_90_WI", "(W) Photon Debugger (WI)"}, +{"W_EX010_A0_WI", "(W) Gull Wing (WI)"}, +{"W_EX010_B0_WI", "(W) Rumbling Rose (WI)"}, +{"W_EX010_C0_WI", "(W) Guardian Soul (WI)"}, +{"W_EX010_D0_WI", "(W) Wishing Lamp (WI)"}, +{"W_EX010_E0_WI", "(W) Decisive Pumpkin (WI)"}, +{"W_EX010_F0_WI", "(W) Sleeping Lion (WI)"}, +{"W_EX010_G0_WI", "(W) Sweet Memories (WI)"}, +{"W_EX010_H0_WI", "(W) Mysterious Abyss (WI)"}, +{"W_EX010_J0_WI", "(W) Fatal Crest (WI)"}, +{"W_EX010_K0_WI", "(W) Bond of Flame (WI)"}, +{"W_EX010_M0_WI", "(W) Fenrir (WI)"}, +{"W_EX010_N0_WI", "(W) Ultima Weapon (WI)"}, +{"W_EX020_A0", "(W) Save the Queen"}, +{"W_EX020_A0_NM", "(W) Save the Queen (NM)"}, +{"W_EX020_10_TR", "(W) Hammer Staff (TR)"}, +{"W_EX020_20_TR", "(W) Victory Bell (TR)"}, +{"W_EX020_30_TR", "(W) Meteor Staff (TR)"}, +{"W_EX020_40_TR", "(W) Comet Staff (TR)"}, +{"W_EX020_50_TR", "(W) Lord’s Broom (TR)"}, +{"W_EX020_60_TR", "(W) Wisdom Wand (TR)"}, +{"W_EX020_70_TR", "(W) Rising Dragon (TR)"}, +{"W_EX020_80_TR", "(W) Nobody Lance (TR)"}, +{"W_EX020_90_TR", "(W) Shaman’s Relic (TR)"}, +{"W_EX020_A0_TR", "(W) Save the Queen (TR)"}, +{"W_EX020_10_WI", "(W) Hammer Staff (WI)"}, +{"W_EX020_20_WI", "(W) Victory Bell (WI)"}, +{"W_EX020_30_WI", "(W) Meteor Staff (WI)"}, +{"W_EX020_40_WI", "(W) Comet Staff (WI)"}, +{"W_EX020_50_WI", "(W) Lord’s Broom (WI)"}, +{"W_EX020_60_WI", "(W) Wisdom Wand (WI)"}, +{"W_EX020_70_WI", "(W) Rising Dragon (WI)"}, +{"W_EX020_80_WI", "(W) Nobody Lance (WI)"}, +{"W_EX020_90_WI", "(W) Shaman’s Relic (WI)"}, +{"W_EX020_A0_WI", "(W) Save the Queen (WI)"}, +{"W_EX030_A0", "(W) Save the King"}, +{"W_EX030_A0_NM", "(W) Save the King (NM)"}, +{"W_EX030_10_TR", "(W) Adamant Shield (TR)"}, +{"W_EX030_20_TR", "(W) Chain Gear (TR)"}, +{"W_EX030_30_TR", "(W) Ogre Shield (TR)"}, +{"W_EX030_40_TR", "(W) Falling Star (TR)"}, +{"W_EX030_50_TR", "(W) Dreamcloud (TR)"}, +{"W_EX030_60_TR", "(W) Knight Defender (TR)"}, +{"W_EX030_70_TR", "(W) Genji Shield (TR)"}, +{"W_EX030_80_TR", "(W) Akashic Record (TR)"}, +{"W_EX030_90_TR", "(W) Nobody Guard (TR)"}, +{"W_EX030_A0_TR", "(W) Save the King (TR)"}, +{"W_EX030_10_WI", "(W) Adamant Shield (WI)"}, +{"W_EX030_20_WI", "(W) Chain Gear (WI)"}, +{"W_EX030_30_WI", "(W) Ogre Shield (WI)"}, +{"W_EX030_40_WI", "(W) Falling Star (WI)"}, +{"W_EX030_50_WI", "(W) Dreamcloud (WI)"}, +{"W_EX030_60_WI", "(W) Knight Defender (WI)"}, +{"W_EX030_70_WI", "(W) Genji Shield (WI)"}, +{"W_EX030_80_WI", "(W) Akashic Record (WI)"}, +{"W_EX030_90_WI", "(W) Nobody Guard (WI)"}, +{"W_EX030_A0_WI", "(W) Save the King (WI)"}, +{"F_EX554", "(F) Struggle Trophy’s black gem"}, +{"F_PO700", "(F) Sora & Pooh’s moon (PO)"}, +{"M_EX050_WI", "(M) Large Body (WI)"}, +{"N_EX730_TUTORIAL_RTN", "(N) Cat (TUTORIAL) (RTN)"}, +{"N_EX650_BTL2", "(N) Villager (boy) (BTL2) (EX)"}, +{"N_EX670_BTL2", "(N) Villager (girl) (BTL2) (EX)"}, +{"N_EX680_BTL2", "(N) Villager (man) (BTL2) (EX)"}, +{"N_EX690_BTL2", "(N) Villager (woman) (BTL2) (EX)"}, +{"PO06_F_PO010", "(PO) ???"}, +{"PO06_F_PO020", "(PO) ???"}, +{"PO07_F_PO070_S", "(PO) Snowball (S)"}, +{"PO07_F_PO070_M", "(PO) Snowball (M)"}, +{"PO07_F_PO070_L", "(PO) Snowball (L)"}, +{"P_AL010", "(P) Genie"}, +{"F_HB030", "(F) ??? (HB)"}, +{"B_AL110", "(B) Volcanic Lord’s lava pool"}, +{"B_AL120", "(B) Blizzard Lord’s ice spikes"}, +{"F_TR170", "(F) ??? (TR)"}, +{"H_EX720", "(H) Riku (Ansem) (EX)"}, +{"N_EX770_RTN", "(N) Org XIII member - Type B (RTN) (EX)"}, +{"P_NM000_RTN", "(P) Jack Skellington (RTN)"}, +{"F_TT580", "(F) Stick (TT)"}, +{"P_EX020_TR_TSURU", "(P) Donald (red lines) (TR) (TSURU)"}, +{"PO07_PRIZE_S", "(PO) Prize (S)"}, +{"PO07_PRIZE_M", "(PO) Prize (M)"}, +{"PO07_PRIZE_L", "(PO) Prize (L)"}, +{"P_CA000_SKL_RTN", "(P) Jack Sparrow (skeleton) (RTN)"}, +{"P_EX020_NM_RTN", "(P) Donald (NM) (RTN)"}, +{"P_EX030_NM_RTN", "(P) Goofy (NM) (RTN)"}, +{"P_WI020_RTN", "(P) Donald (WI) (RTN)"}, +{"P_WI030_RTN", "(P) Goofy (WI) (RTN)"}, +{"N_LK070_E_CROWD_1", "(N) Wildebeest (E_CROWD_1) (LK)"}, +{"N_LK070_E_CROWD_2", "(N) Wildebeest (E_CROWD_2) (LK)"}, +{"N_LK070_E_CROWD_3", "(N) Wildebeest (E_CROWD_3) (LK)"}, +{"B_HE030_HE05", "(B) Hades"}, +{"B_MU100_RTN", "(RTN) Shan-Yu"}, +{"P_MU000_RTN", "(P) Mulan (RTN)"}, +{"F_TT070", "(F) Skateboard Checkmark (TT)"}, +{"F_NM721_ISHIDA", "(F) Present 2 (NM)"}, +{"F_NM741_ISHIDA", "(F) Present 5 (NM)"}, +{"P_EH000", "(P) Riku"}, +{"F_TT010_SORA", "(F) Skateboard (SORA) (TT)"}, +{"N_AL010_RTN", "(N) Magic Carpet (RTN) (AL)"}, +{"M_EX790", "(M) Graveyard"}, +{"P_CA000_HUMAN_LOW", "(P) Jack Sparrow (human) (LOW)"}, +{"P_CA000_LOW", "(P) Jack Sparrow (LOW)"}, +{"B_EX170_LAST", "(B) Xemnas (Final)"}, +{"N_EX560", "(N) Kairi (EX)"}, +{"F_TT140", "(F) Dream Sword (TT)"}, +{"F_TT150", "(F) Dream Shield (TT)"}, +{"F_TT160", "(F) Dream Rod (TT)"}, +{"W_EX010_U0_RTN", "(W) Struggle Hammer (RTN)"}, +{"W_EX010_V0_RTN", "(W) Struggle Wand (RTN)"}, +{"W_EX010_W0_RTN", "(W) Struggle Sword (RTN)"}, +{"P_LK020_RTN", "(P) Donald (RTN) (LK)"}, +{"P_LK030_RTN", "(P) Goofy (RTN) (LK)"}, +{"F_TT860", "(F) Sky (TT)"}, +{"B_LK100_00_RTN", "(RTN) Shenzi"}, +{"B_LK100_10_RTN", "(RTN) Banzai"}, +{"B_LK100_20_RTN", "(RTN) Ed"}, +{"H_EX710", "(H) Riku (EX)"}, +{"H_EX780", "(H) Riku (face cover, oblivion) (EX)"}, +{"N_EX600_BTL", "(N) Setzer (BTL) (EX)"}, +{"F_EH100", "(F) Xemnas’s dragon energy core (EH)"}, +{"N_LK110_MATSU", "(N) Pete (lion) (MATSU) (LK)"}, +{"P_EX360", "(P) ??? (EX)"}, +{"B_LK110_PHANTOM", "(M) Scar Ghost"}, +{"F_TT100", "(F) Trashcan (TT)"}, +{"F_AL090_01", "(F) Falling pillar 1 (AL)"}, +{"F_AL090_02", "(F) Falling pillar 2 (AL)"}, +{"F_AL090_03", "(F) Falling pillar 3 (AL)"}, +{"F_EH540", "(F) Door to final battle (EH)"}, +{"F_EH570", "(F) Kingdom Hearts (moon) (EH)"}, +{"F_EH580", "(F) Kingdom Hearts (moon) (pink hole) (EH)"}, +{"B_EX270", "(F) Xemnas’s dragon vehicle"}, +{"P_EX100_SIDECAR", "(P) Sora (sidecar)"}, +{"B_EX270_SIDECAR", "(F) Xemnas’s dragon sidecar"}, +{"B_EX280", "(F) Xemnas armor - Saïx’s claymore"}, +{"B_EX290", "(F) Xemnas armor - Xigbar’s arrowgun"}, +{"B_EX300", "(?) Xemnas armor - ???"}, +{"B_EX310", "(F) Xemnas armor - Xaldin’s spears"}, +{"B_EX320", "(F) Xemnas armor - Axel’s chakrams"}, +{"B_LK100", "(B) Shenzi"}, +{"N_MU060_E_CROWD", "(N) Chinese Soldier 1 (E_CROWD) (MU)"}, +{"N_EX690_TT_TUTOR_RTN", "(N) Villager (woman) (TT_TUTOR) (RTN) (EX)"}, +{"H_EX700", "(H) Kairi (EX)"}, +{"F_TT170", "(F) ??? - flying attack balls - bees? (TT)"}, +{"B_EX110_RTN", "(B) Axel (Scene day he freezes) (RTN)"}, +{"B_EX170_RTN", "(B) Xehanort (Scene looking at Kingdom Hearts) (RTN)"}, +{"P_EX020_TR_RTN", "(P) Donald (TR) (RTN)"}, +{"P_EX030_TR_RTN", "(P) Goofy (TR) (RTN)"}, +{"F_TT600_10", "(F) Magic Train (10) (TT)"}, +{"F_TT620_10", "(F) Train (mirrored) (TT)"}, +{"N_NM030_RTN", "(N) Zero (RTN) (NM)"}, +{"F_TT611", "(F) Magic Train’s door 2 (TT)"}, +{"F_EX050", "(F) Treasure chest S (LK)"}, +{"F_EX060", "(F) Treasure chest L (LK)"}, +{"F_HE030_S_FREE", "(F) Phil minigame S (FREE) (HE)"}, +{"F_HE030_L_FREE", "(F) Phil minigame L (FREE) (HE)"}, +{"B_EX340", "(F) Xemnas’s dragon (Head)"}, +{"PRIZE_COLOSSEUM", "PRIZE_COLOSSEUM (Underdrome’s points)"}, +{"F_PO090_TT", "(F) Bees (TT) (PO)"}, +{"F_PO090_ETC", "(F) Bees (ETC) (PO)"}, +{"B_EX260", "(B) Xemnas (Armor)"}, +{"P_NM000_SANTA_RTN", "(P) Jack Skellington (XM) (RTN)"}, +{"F_EX030_NM_XMAS", "(F) Treasure chest S (NM_XMAS)"}, +{"F_EX040_NM_XMAS", "(F) Treasure chest L (NM_XMAS)"}, +{"B_EX250", "(?) Xemna’s dragon’s arms (Anchored)"}, +{"P_EH010", "(P) ??? - Path? Pyramid? (EH)"}, +{"WM_COCKPIT", "(WM) Cockpit"}, +{"WM_HANGAR", "(WM) Hangar"}, +{"F_HB700", "(F) ??? - Path? Pyramid? (HB)"}, +{"N_EX650_BTL10", "(N) Villager (boy) (BTL10) (EX)"}, +{"N_EX670_BTL10", "(N) Villager (girl) (BTL10) (EX)"}, +{"N_EX680_BTL10", "(N) Villager (man) (BTL10) (EX)"}, +{"N_EX690_BTL10", "(N) Villager (woman) (BTL10) (EX)"}, +{"P_EX210", "(P) Mickey (coat)"}, +{"WM_DC030", "(WM) Chip"}, +{"WM_DC040", "(WM) Dale"}, +{"N_EX610_BTL", "(N) Vivi (BTL) (EX)"}, +{"P_EH000_RTN", "(P) Riku (RTN)"}, +{"F_TT650_10", "(F) Yellow crayon (TT)"}, +{"N_EX920", "(N) Apprentice Xehanort (EX)"}, +{"F_HE810", "(F) White square (HE)"}, +{"H_HE010_GOD", "(H) Hercules (god) (HE)"}, +{"N_PO010_SIT_RTN", "(N) Pooh (sitting) (RTN) (PO)"}, +{"F_HB650_RTN", "(RTN) Winnie the Pooh’s book (HB)"}, +{"F_PO080_RTN", "(RTN) Honey pot (PO)"}, +{"N_NM050_BTL_TOY", "(N) Lock (toy minigame) (BTL) (NM)"}, +{"N_NM060_BTL_TOY", "(N) Shock (toy minigame) (BTL) (NM)"}, +{"N_NM070_BTL_TOY", "(N) Barrel (toy minigame) (BTL) (NM)"}, +{"P_EX120_NPC_RED", "(P) Sora (KH1) (red) (NPC)"}, +{"P_EX120_NPC_GREEN", "(P) Sora (KH1) (green) (NPC)"}, +{"P_EX120_NPC_BLUE", "(P) Sora (KH1) (blue) (NPC)"}, +{"F_TT110_FREE", "(F) Dog’s sack (FREE) (TT)"}, +{"F_TT100_FREE", "(F) Trashcan (FREE) (TT)"}, +{"F_EH080", "(F) Xemnas’s dragon core cylinder (left) (EH)"}, +{"H_HE010_WEAK", "(H) Hercules (weak) (HE)"}, +{"F_EX590", "(F) Kairi’s letter in a bottle"}, +{"F_EX600", "(F) Kairi’s letter"}, +{"SHOP_POINT_LOW", "SHOP_POINT_LOW"}, +{"PRIZE_BOX_NM", "PRIZE_BOX_NM"}, +{"F_EH070", "(F) Xemnas’s dragon core cylinder (right) (EH)"}, +{"F_WI380_RTN", "(F) Steamboat with Corner stone in cage (RTN) (WI)"}, +{"F_WI390_RTN", "(F) Steamboat’s hook (RTN) (WI)"}, +{"N_EX930", "(N) Ansem the Wise (EX)"}, +{"M_EX500_GM", "(M) Trick Ghost (GM)"}, +{"M_EX790_GM", "(M) Graveyard (GM)"}, +{"B_BB100_GM", "(B) Thresholder (Includes archway) (GM)"}, +{"B_BB130_GM", "(B) Possessor (GM)"}, +{"B_BB120_GM", "(B) Shadow Stalker (GM)"}, +{"B_EX330", "(F) Xemnas’s dragon (Flying)"}, +{"P_EX110_NPC_PAJAMAS", "(P) Roxas (pajamas) (NPC)"}, +{"WM_EX100", "(WM) Sora"}, +{"WM_EX020", "(WM) Donald"}, +{"WM_EX030", "(WM) Goofy"}, +{"P_EX100_WM", "(P) Sora (worldmap)"}, +{"P_EX020_WM", "(P) Donald (worldmap)"}, +{"P_EX030_WM", "(P) Goofy (worldmap)"}, +{"N_DC030_WM", "(N) Chip (WM) (DC)"}, +{"N_DC040_WM", "(N) Dale (WM) (DC)"}, +{"PRIZE_CA_GAMBLER", "PRIZE_CA_GAMBLER (medallion)"}, +{"PRIZE_CA_SEA", "PRIZE_CA_SEA (medallion)"}, +{"EH_G_EX250", "(EH) Bomb Bell B"}, +{"P_EH000_SIDECAR", "(P) Riku riding sidecar"}, +{"N_EX500_RAW_RTN", "(N) Hayner (RAW) (RTN) (EX)"}, +{"N_EX510_RAW_RTN", "(N) Pence (RAW) (RTN) (EX)"}, +{"N_EX520_RAW_RTN", "(N) Olette (RAW) (RTN) (EX)"}, +{"N_EX610_RAW_RTN", "(N) Vivi (RAW) (RTN) (EX)"}, +{"N_EX570_RAW_RTN", "(N) Seifer (RAW) (RTN) (EX)"}, +{"N_EX600_RAW_RTN", "(N) Setzer (RAW) (RTN) (EX)"}, +{"N_EX680_TT_PROTECT_RAW_RTN", "(N) Villager (man) (TT_PROTECT) (RAW) (RTN) (EX)"}, +{"N_EX680_TT_REFEREE_RAW_RTN", "(N) Villager (man) (TT_REFEREE) (RAW) (RTN) (EX)"}, +{"N_EX690_TT_ACCE_RAW_RTN", "(N) Villager (woman) (TT_ACCE) (RAW) (RTN) (EX)"}, +{"N_EX700_TT_WEAPON_RAW_RTN", "(N) Villager (gentleman) (TT_WEAPON) (RAW) (RTN) (EX)"}, +{"N_EX710_TT_SWEETS_RAW_RTN", "(N) Villager (old woman) (TT_SWEETS) (RAW) (RTN) (EX)"}, +{"H_EX510_PAJAMAS", "(H) Roxas (pajamas) (EX)"}, +{"H_EX990", "(H) Dusk (EX)"}, +{"F_TT880", "(F) Sky with orange circle (TT)"}, +{"N_EX680_TT_PRT_RAW_RTN", "(N) Villager (man) (TT_PRT) (RAW) (RTN) (EX)"}, +{"N_EX680_TT_REF_RAW_RTN", "(N) Villager (man) (TT_REF) (RAW) (RTN) (EX)"}, +{"N_EX690_TT_ACC_RAW_RTN", "(N) Villager (woman) (TT_ACC) (RAW) (RTN) (EX)"}, +{"N_EX700_TT_WPN_RAW_RTN", "(N) Villager (gentleman) (TT_WPN) (RAW) (RTN) (EX)"}, +{"N_EX710_TT_SWT_RAW_RTN", "(N) Villager (old woman) (TT_SWT) (RAW) (RTN) (EX)"}, +{"N_TT010_SIT_RTN", "(N) Yen Sid (sitting) (RTN) (TT)"}, +{"B_EX230", "(B) ??? - Game acts as if an enemy is present"}, +{"B_EX350", "(B) Large platform from TWTNW with pink spike"}, +{"F_EH110", "(F) Rising building (EH)"}, +{"N_EX700_TT_SPO_RAW_RTN", "(N) Villager (gentleman) (TT_SPO) (RAW) (RTN) (EX)"}, +{"B_EX110_SKIRMISH", "(B) Axel (boss, freezes when RC is used) (SKIRMISH) (EX)"}, +{"N_HB630", "(N) Sephiroth (HB)"}, +{"WM_CHECKER", "WM_CHECKER"}, +{"F_TR580_GM", "(F) MCP face (GM) (TR)"}, +{"N_PO030_AIR_RTN", "(N) Piglet (AIR) (RTN) (PO)"}, +{"F_EX030_TT", "(F) Treasure chest S (TT)"}, +{"F_EX040_TT", "(F) Treasure chest L (TT)"}, +{"F_EH510", "(F) Destiny’s Embrace (EH)"}, +{"M_EX800_MU", "(M) Bolt Tower (MU)"}, +{"M_EX800_MU_RAW", "(M) Bolt Tower (MU) (RAW)"}, +{"F_TT010_NM", "(F) Skateboard (NM)"}, +{"F_TT010_WI", "(F) Skateboard (WI)"}, +{"F_TT010_AL", "(F) Skateboard (AL)"}, +{"F_TT010_HE", "(F) Skateboard (HE)"}, +{"F_TT010_CA", "(F) Skateboard (CA)"}, +{"F_TT010_TR", "(F) Skateboard (TR)"}, +{"B_LK120_GM", "(GM) Groundshaker (G_EX)"}, +{"EH_G_EX290", "(EH) Speeder B (G_EX)"}, +{"EH_G_EX320", "(EH) Spiked Roller B (G_EX)"}, +{"M_EX990_E_CROWD", "(M) Dusk (E_CROWD)"}, +{"N_CA010_GM", "(N) Elizabeth (darker) (GM) (CA)"}, +{"N_CA020_GM", "(N) Will Turner (darker) (GM) (CA)"}, +{"N_EX740_TT_SKATE_RTN", "(N) Dove (TT) (SKATE) (RTN) (EX)"}, +{"N_HB520_TSURU_S", "(N) 1000 Heartless Battlefront (T-Pose) (TSURU) (S) (HB)"}, +{"N_EX610_BTL2", "(N) Vivi (BTL2) (EX)"}, +{"P_EH000_LAST", "(P) Riku (final battle)"}, +{"P_EX100_LAST", "(P) Sora (electrified by Xemnas) (final battle)"}, +{"P_EX030_TR_TSURU", "(P) Goofy (red lines) (TR) (TSURU)"}, +{"EH_G_EX120", "(EH) Xemnas’s dragon missile (G_EX)"}, +{"N_LM031_MATSU", "(N) Ariel (MATSU) (LM)"}, +{"H_LM031_MATSU", "(H) Ariel (MATSU) (LM)"}, +{"N_HB040_TSURU", "(N) Stitch (TSURU) (HB)"}, +{"B_AL100_FIRE_GM", "(B) Volcano Lord (GM)"}, +{"B_AL100_ICE_GM", "(B) Blizzard Lord (GM)"}, +{"B_CA050_GM", "(B) Grim Reaper (GM)"}, +{"B_NM100_GM", "(B) Prison Keeper (GM)"}, +{"B_MU120_GM", "(B) Storm Rider (GM)"}, +{"B_EX140_GM", "(B) Xigbar (GM)"}, +{"B_EX160_GM", "(B) Saïx (GM)"}, +{"B_HE020_GM", "(B) Cerberus (GM)"}, +{"B_HE030_GM", "(B) Hades (GM)"}, +{"B_HE100_GM", "(B) Hydra (GM)"}, +{"B_NM110_GM", "(B) The Experiment (GM)"}, +{"P_CA000HUM_GM", "(B) Jack Sparrow (HUM) (GM)"}, +{"B_CA010_HUM_GM", "(B) Barbosa (Human) (GM)"}, +{"B_EX130_GM", "(B) Xaldin (GM)"}, +{"N_CM040_GM", "(B) Vexen (GM)"}, +{"B_EX110_GM", "(B) Axel (GM)"}, +{"B_EX120_GM", "(B) Demyx (GM)"}, +{"N_HE010_GM1", "(N) Hercules (GM1) (HE)"}, +{"N_HE010_GM2", "(N) Hercules (GM2) (HE)"}, +{"N_HE020_GM", "(N) Phil (GM) (HE)"}, +{"EH_G_EX250_FLY", "(EH) Bomb Bell B (FLY) (G_EX)"}, +{"B_EX210_EH", "(M) Luxord’s card (time?) (EX)"}, +{"F_HB630_01", "(F) Ansem’s room’s door (_01) (HB)"}, +{"M_EX800_DC", "(M) Bolt Tower (DC)"}, +{"N_HB590_GM", "(N) Sephiroth (GM) (HB)"}, +{"N_HB530_BOSS", "(N) Squall / Leon (BOSS) (HB)"}, +{"N_HB550_BOSS", "(N) Cloud (BOSS) (HB)"}, +{"N_HB570_BOSS", "(N) Tifa (BOSS) (HB)"}, +{"N_HB580_BOSS", "(N) Yuffie (BOSS) (HB)"}, +{"M_EX880_DANCER_EH", "(M) Demyx’s water form (EH)"}, +{"B_EX120_HB", "(B) Demyx"}, +{"N_HB530_BTL2", "(N) Squall / Leon (BTL2) (HB)"}, +{"N_HB550_BTL2", "(N) Cloud (BTL2) (HB)"}, +{"N_HB570_BTL2", "(N) Tifa (BTL2) (HB)"}, +{"N_HB580_BTL2", "(N) Yuffie (BTL2) (HB)"}, +{"N_TR580_GM", "(N) MCP face (GM) (TR)"}, +{"P_CA000_KAJI_RTN", "(P) Jack Sparrow (KAJI) (RTN) (CA)"}, +{"N_CA020_KAJI_RTN", "(N) Will Turner (KAJI) (RTN) (CA)"}, +{"N_EX640_SHOP_RTN", "(N) Moogle (SHOP) (RTN) (EX)"}, +{"H_EX620_NM", "(H) Maleficent (NM) (EX)"}, +{"N_EX790_NM", "(N) Maleficent (NM) (EX)"}, +{"N_EX670_TT_A_SKATE_RTN", "(N) Villager (girl) (TT_A_SKATE) (RTN) (EX)"}, +{"N_EX621", "(N) Naminé (EX)"}, +{"N_EX680_TT_B_SKATE_RTN", "(N) Villager (man) (TT_B_SKATE) (RTN) (EX)"}, +{"N_EX650_TT_B_SKATE_RTN", "(N) Villager (boy) (TT_B_SKATE) (RTN) (EX)"}, +{"F_TT030_ETC", "(F) Cargo Climb’s cart (ETC) (TT)"}, +{"P_CA000_KAJI_IS_RTN", "(P) Jack Sparrow steering ship (Interceptor) (RTN) (CA)"}, +{"P_CA000_KAJI_BP_RTN", "(P) Jack Sparrow steering ship (Black Pearl) (RTN) (CA)"}, +{"P_CA000_KAJI_SKL_RTN", "(P) Jack Sparrow stearing ship (Skelleton) (RTN) (CA)"}, +{"P_EX020_NOBG_RTN", "(P) Donald (NOBG) (RTN) (EX)"}, +{"P_EX030_NOBG_RTN", "(P) Goofy (NOBG) (RTN) (EX)"}, +{"M_EX990_ZIPPER_RTN", "(P) Dusk (ZIPPER) (RTN) (EX)"}, +{"B_HE030_CLSM", "(B) Hades (CLSM) (HE)"}, +{"N_EX760_BTL_CLSM", "(N) Pete (BTL) (CLSM) (EX)"}, +{"N_HE010_BTL_CLSM", "(N) Hercules (BTL) (CLSM) (HE)"}, +{"N_HB550_STAND_RTN", "(N) Cloud (STAND) (RTN) (HB)"}, +{"B_EX360", "(B) ??? - Invisible enemy, can’t be hurt. (EX)"}, +{"F_CA710_RTN", "(F) Jack Sparrow’s compass (RTN)"}, +{"N_HE020_MENU_RTN", "(N) Phil (MENU) (RTN) (HE)"}, +{"N_BB050_SAD_RTN", "(N) Cogsworth (SAD) (RTN) (BB)"}, +{"N_CA010_SAD_RTN", "(N) Elizabeth (SAD) (RTN) (CA)"}, +{"N_EX500_ANGER_RTN", "(N) Heyner (ANGER) (RTN) (EX)"}, +{"H_EX660_LAST", "(H) Final Xenmas (EX)"}, +{"N_EX580_RAW_RTN", "(N) Raijin (RAW) (RTN) (EX)"}, +{"N_EX590_RAW_RTN", "(N) Fujin (RAW) (RTN) (EX)"}, +{"F_PO511", "(F) Winnie the Pooh book (No cover) (PO)"}, +{"F_PO512", "(F) Winnie the Pooh book (Completed cover) (PO)"}, +{"F_HB650_01", "(F) Winnie the Pooh’s book (damaged) (HB)"}, +{"N_EX500_ANGER_RAW_RTN", "(N) Heyner (ANGER) (RAW) (RTN) (EX)"}, +{"N_LK020_GM", "(N) Pumba (GM) (LK)"}, +{"B_MU100_GM", "(B) Shan Yu (GM) (MU)"}, +{"B_TR000_GM", "(B) Hostile Program (GM) (TR)"}, +{"M_EX990_RTN_FIXCOLOR", "(M) Dusk (RTN) (FIXCOLOR) (EX)"}, +{"N_CM000_BTL", "(N) Marluxia (BTL) (CM)"}, +{"H_EX660_EV", "(H) Xemnas (dark face) (EV) (EX)"}, +{"N_EX840_EV", "(N) Org XIII member - Type D (EV) (EX)"}, +{"M_EX350_01", "(M) Mushroom 1 (EX)"}, +{"M_EX350_02", "(M) Mushroom 2 (EX)"}, +{"M_EX350_03", "(M) Mushroom 3 (EX)"}, +{"M_EX350_04", "(M) Mushroom 4 (EX)"}, +{"M_EX350_05", "(M) Mushroom 5 (EX)"}, +{"M_EX350_06", "(M) Mushroom 6 (EX)"}, +{"M_EX350_07", "(M) Mushroom 7 (EX)"}, +{"M_EX350_08", "(M) Mushroom 8 (EX)"}, +{"M_EX350_09", "(M) Mushroom 9 (EX)"}, +{"M_EX350_10", "(M) Mushroom 10 (EX)"}, +{"M_EX350_11", "(M) Mushroom 11 (EX)"}, +{"M_EX350_12", "(M) Mushroom 12 (EX)"}, +{"M_EX350_13", "(M) Mushroom 13 (EX)"}, +{"N_CM040_BTL", "(N) Vexen (BTL) (CM)"}, +{"P_EX100_HTLF_BTL", "(P) Vexen’s Anti-Sora (BTL)"}, +{"N_CM020_BTL", "(N) Lexaeus (BTL) (CM)"}, +{"N_EX820_BTL", "(N) Org XIII member - Roxas (Dual wield) (BTL) (EX)"}, +{"N_CM010_BTL", "(N) Larxene (BTL) (CM)"}, +{"N_CM030_BTL", "(N) Zexion (BTL) (CM)"}, +{"W_EX010_P0", "(W) Two Become One"}, +{"W_EX010_R0", "(W) Winner’s Proof"}, +{"W_EX010_P0_NM", "(W) Two Become One (NM)"}, +{"W_EX010_R0_NM", "(W) Winner’s Proof (NM)"}, +{"W_EX010_P0_TR", "(W) Two Become One (TR)"}, +{"W_EX010_R0_TR", "(W) Winner’s Proof (TR)"}, +{"W_EX010_P0_WI", "(W) Two Become One (WI)"}, +{"W_EX010_R0_WI", "(W) Winner’s Proof (WI)"}, +{"W_EX020_B0", "(W) Centurion"}, +{"W_EX020_C0", "(W) Plain Mushroom"}, +{"W_EX020_B0_NM", "(W) Centurion (NM)"}, +{"W_EX020_C0_NM", "(W) Plain Mushroom (NM)"}, +{"W_EX020_B0_TR", "(W) Centurion (TR)"}, +{"W_EX020_C0_TR", "(W) Plain Mushroom (TR)"}, +{"W_EX020_B0_WI", "(W) Centurion (WI)"}, +{"W_EX020_C0_WI", "(W) Plain Mushroom (WI)"}, +{"W_EX030_B0", "(W) Frozen Pride"}, +{"W_EX030_C0", "(W) Joyous Mushroom"}, +{"W_EX030_B0_NM", "(W) Frozen Pride (NM)"}, +{"W_EX030_C0_NM", "(W) Joyous Mushroom (NM)"}, +{"W_EX030_B0_TR", "(W) Frozen Pride (TR)"}, +{"W_EX030_C0_TR", "(W) Joyous Mushroom (TR)"}, +{"W_EX030_B0_WI", "(W) Frozen Pride (WI)"}, +{"W_EX030_C0_WI", "(W) Joyous Mushroom (WI)"}, +{"B_EX390", "(B) Hooded Roxas"}, +{"W_EX010_ROXAS_LIGHT", "(W) Roxas’s Oathkeeper"}, +{"W_EX010_ROXAS_DARK", "(W) Roxas’s Oblivion"}, +{"F_HB090", "(F) CoR’s whirlwind (jumpable) (HB)"}, +{"P_EX100_XM", "(P) Sora (XM)"}, +{"P_EX100_XM_BTLF", "(P) Sora (XM) (Valor)"}, +{"P_EX100_XM_MAGF", "(P) Sora (XM) (Wisdom)"}, +{"P_EX100_XM_TRIF", "(P) Sora (XM) (Master)"}, +{"P_EX100_XM_ULTF", "(P) Sora (XM) (Final)"}, +{"P_EX100_XM_HTLF", "(P) Sora (XM) (Anti)"}, +{"P_EX020_XM", "(P) Donald (XM)"}, +{"P_EX030_XM", "(P) Goofy (XM)"}, +{"P_EX100_KH1F", "(P) Sora (Limit)"}, +{"P_EX100_NM_KH1F", "(P) Sora (NM) (Limit)"}, +{"P_EX100_XM_KH1F", "(P) Sora (XM) (Limit)"}, +{"P_EX100_TR_KH1F", "(P) Sora (TR) (Limit)"}, +{"P_EX100_WI_KH1F", "(P) Sora (WI) (Limit)"}, +{"B_EX400", "(B) Larxene (Absent Silhouette)"}, +{"M_EX500_HB", "(M) Magic Phantom"}, +{"M_EX520_HB", "(M) Beffudler"}, +{"M_EX560_HB", "(M) Iron Hammer"}, +{"M_EX640_HB", "(M) Mad Ride"}, +{"M_EX650_HB", "(M) Camo Cannon"}, +{"M_EX680_HB", "(M) Reckless"}, +{"M_EX690_HB", "(M) Lance Warrior"}, +{"M_EX780_HB", "(M) Aeriel Champ"}, +{"M_EX720_HB", "(M) Necromancer"}, +{"M_EX120_HB", "(M) Spring Metal"}, +{"M_EX210_HB", "(M) Aerial Viking"}, +{"M_EX530_HB", "(M) Runemaster"}, +{"B_EX420", "(B) Lingering Will"}, +{"F_HB050", "(F) CoR’s pushing pillar (HB)"}, +{"F_HB060", "(F) CoR’s rising pillar (HB)"}, +{"F_EX210", "(F) Vexen’s Absent Silhouette portal (can’t be used)"}, +{"F_EX220", "(F) Lexeus’ Absent Silhouette portal (can’t be used)"}, +{"F_EX230", "(F) Zexion’s Absent Silhouette portal (can’t be used)"}, +{"F_EX240", "(F) Marluxia’s Absent Silhouette portal (can’t be used)"}, +{"F_EX250", "(F) Larxene’s Absent Silhouette portal (can’t be used)"}, +{"N_ZZ160_EV", "(N) Ansem SoD (EV) (ZZ)"}, +{"F_HB740", "(F) CoR’s drill column (HB)"}, +{"F_HB750", "(F) CoR’s pushing pillar (HB)"}, +{"F_HB760", "(F) CoR’s rising pillar (HB)"}, +{"B_EX370", "(B) Zexion (Absent Silhouette)"}, +{"M_EX500_HB_GM", "(M) Magic Phantom (GM)"}, +{"B_EX380", "(F) Zexion’s book"}, +{"B_EX410", "(P) Sora book"}, +{"H_EX500_KH1F", "(H) Sora (Limit)"}, +{"H_EX500_NM_KH1F", "(H) Sora (Limit) (NM)"}, +{"H_EX500_XM", "(H) Sora (XM)"}, +{"H_EX500_XM_BTLF", "(H) Sora (Valor) (XM)"}, +{"H_EX500_XM_MAGF", "(H) Sora (Wisdom) (XM)"}, +{"H_EX500_XM_TRIF", "(H) Sora (Limit) (XM)"}, +{"H_EX500_XM_ULTF", "(H) Sora (Master) (XM)"}, +{"H_EX500_XM_KH1F", "(H) Sora (Final) (XM)"}, +{"H_EX500_TR_KH1F", "(H) Sora (Limit) (TR)"}, +{"F_EX260", "(F) Puzzle crown"}, +{"W_EX020_A4", "(W) Save the Queen+"}, +{"W_EX020_B4", "(W) Centurion+"}, +{"W_EX020_C4", "(W) Plain Mushroom+"}, +{"W_EX020_C8", "(W) Precious Mushroom"}, +{"W_EX020_CC", "(W) Precious Mushroom+"}, +{"W_EX020_CG", "(W) Premium Mushroom"}, +{"W_EX020_A4_NM", "(W) Save the Queen+ (NM)"}, +{"W_EX020_B4_NM", "(W) Centurion+ (NM)"}, +{"W_EX020_C4_NM", "(W) Plain Mushroom+ (NM)"}, +{"W_EX020_C8_NM", "(W) Precious Mushroom (NM)"}, +{"W_EX020_CC_NM", "(W) Precious Mushroom+ (NM)"}, +{"W_EX020_CG_NM", "(W) Premium Mushroom (NM)"}, +{"W_EX020_A4_TR", "(W) Save the Queen+ (TR)"}, +{"W_EX020_B4_TR", "(W) Centurion+ (TR)"}, +{"W_EX020_C4_TR", "(W) Plain Mushroom+ (TR)"}, +{"W_EX020_C8_TR", "(W) Precious Mushroom (TR)"}, +{"W_EX020_CC_TR", "(W) Precious Mushroom+ (TR)"}, +{"W_EX020_CG_TR", "(W) Premium Mushroom (TR)"}, +{"W_EX020_A4_WI", "(W) Save the Queen+ (WI)"}, +{"W_EX020_B4_WI", "(W) Centurion+ (WI)"}, +{"W_EX020_C4_WI", "(W) Plain Mushroom+ (WI)"}, +{"W_EX020_C8_WI", "(W) Precious Mushroom (WI)"}, +{"W_EX020_CC_WI", "(W) Precious Mushroom+ (WI)"}, +{"W_EX020_CG_WI", "(W) Premium Mushroom (WI)"}, +{"W_EX030_A4", "(W) Save the King+"}, +{"W_EX030_B4", "(W) Frozen Pride+"}, +{"W_EX030_C4", "(W) Joyous Mushroom+"}, +{"W_EX030_C8", "(W) Majestic Mushroom"}, +{"W_EX030_CC", "(W) Majestic Mushroom+"}, +{"W_EX030_CG", "(W) Ultimate Mushroom"}, +{"W_EX030_A4_NM", "(W) Save the King+ (NM)"}, +{"W_EX030_B4_NM", "(W) Frozen Pride+ (NM)"}, +{"W_EX030_C4_NM", "(W) Joyous Mushroom+ (NM)"}, +{"W_EX030_C8_NM", "(W) Majestic Mushroom (NM)"}, +{"W_EX030_CC_NM", "(W) Majestic Mushroom+ (NM)"}, +{"W_EX030_CG_NM", "(W) Ultimate Mushroom (NM)"}, +{"W_EX030_A4_TR", "(W) Save the King+ (TR)"}, +{"W_EX030_B4_TR", "(W) Frozen Pride+ (TR)"}, +{"W_EX030_C4_TR", "(W) Joyous Mushroom+ (TR)"}, +{"W_EX030_C8_TR", "(W) Majestic Mushroom (TR)"}, +{"W_EX030_CC_TR", "(W) Majestic Mushroom+ (TR)"}, +{"W_EX030_CG_TR", "(W) Ultimate Mushroom (TR)"}, +{"W_EX030_A4_WI", "(W) Save the King+ (WI)"}, +{"W_EX030_B4_WI", "(W) Frozen Pride+ (WI)"}, +{"W_EX030_C4_WI", "(W) Joyous Mushroom+ (WI)"}, +{"W_EX030_C8_WI", "(W) Majestic Mushroom (WI)"}, +{"W_EX030_CC_WI", "(W) Majestic Mushroom+ (WI)"}, +{"W_EX030_CG_WI", "(W) Ultimate Mushroom (WI)"}, +{"F_HB140", "(F) Cavern of Remembrance big pushing block (HB)"}, +{"F_HB040", "(F) CoR’s orb (red) (HB)"}, +{"F_HB050_23", "(F) CoR’s pushing pillar 2 (HB)"}, +{"F_HB040_BL", "(F) CoR’s orb (blue) (HB)"}, +{"F_HB040_YE", "(F) CoR’s orb (yellow) (HB)"}, +{"F_HB040_WH", "(F) CoR’s orb (white) (HB)"}, +{"F_HB070", "(F) CoR’s droppable spike (HB)"}, +{"F_HB080", "(F) CoR’s steam wheel (HB)"}, +{"F_HB110", "(F) ??? (HB)"}, +{"F_HB120", "(F) ??? (HB)"}, +{"F_HB130", "(F) ??? (HB)"}, +{"B_EX110_LV99", "(B99) Axel (Limit Cut)"}, +{"B_EX140_LV99", "(B99) Xigbar (Limit Cut)"}, +{"B_EX160_LV99", "(B99) Saïx (Limit Cut)"}, +{"F_HB100", "(F) ??? (HB)"}, +{"B_EX150_LV99", "(B99) Luxord (Limit Cut)"}, +{"B_EX170_LV99", "(B99) Xemnas (Limit Cut Memory’s Contortion)"}, +{"B_EX170_LAST_LV99", "(B99) Xemnas (Final) (Limit Cut The World of Nothing)?"}, +{"B_EX130_LV99", "(B99) Xaldin (Limit Cut)"}, +{"B_EX120_HB_LV99", "(B99) Demyx (Limit Cut)"}, +{"M_EX590_NM", "(M) Bulky Vendor (NM)"}, +{"F_HB730", "(F) Chamber of Repose’s door (HB)"}, +{"W_EX030_B0_LK", "(W) Frozen Pride (LK)"}, +{"W_EX030_B4_LK", "(W) Frozen Pride+ (LK)"}, +{"PRIZE_M_EX350_03", "PRIZE_M_EX350_03 (Mushroom 13)"}, +{"F_HB710", "(F) Platform opening Chamber of Repose’s path (HB)"}, +{"F_HB720", "(F) Door for the corridor to Chamber of Repose’s (HB)"}, +{"N_EX970", "(N) Aqua’s armor (EX)"}, +{"F_HB770", "(F) Data battle gate (3) (HB)"}, +{"F_HB780", "(F) Data battle gate (8) (HB)"}, +{"F_HB790", "(F) Data battle gate (9) (HB)"}, +{"M_EX350_06_SU", "(M) Mushroom 6 (SU)"}, +{"F_EX040_HB", "(F) Treasure chest L (HB)"}, +{"P_EX020_XM_RTN", "(P) Donald (XM) (RTN)"}, +{"P_EX030_XM_RTN", "(P) Goofy (XM) (RTN)"}, +{"N_EX700_TT_SPO_RAW_TALK_RTN", "(N) Villager (gentleman) (TT_SPO) (RAW) (TALK) (RTN) (EX)"}, +{"N_EX700_TT_SPO_RAW2_RTN", "(N) Villager (gentleman) (TT_SPO) (RAW2) (RTN) (EX)"}, +{"N_EX700_TT_SPO_RW2_RTN", "(N) Villager (gentleman) (TT_SPO) (RW2) (RTN) (EX)"}, +{"W_EX020_94", "(W) Shaman’s Relic+"}, +{"W_EX020_94_NM", "(W) Shaman’s Relic+ (NM)"}, +{"W_EX020_94_TR", "(W) Shaman’s Relic+ (TR)"}, +{"W_EX020_94_WI", "(W) Shaman’s Relic+ (WI)"}, +{"W_EX030_84", "(W) Akashic Record+"}, +{"W_EX030_84_NM", "(W) Akashic Record+ (NM)"}, +{"W_EX030_84_TR", "(W) Akashic Record+ (TR)"}, +{"W_EX030_84_WI", "(W) Akashic Record+ (WI)"}, +{"M_EX880_DANCER_LV99", "(M) Demyx’s water form (Data)"}, +{"F_EX000_DC", "(F) ??? - Chest related (DC) (EX)"}, +{"B_EX220_LV99", "(F) Saix’s claymore limit cut (Usable)"}, +{"B_EX430", "(?) Related to Lingering Will?"}, +{"M_EX350_02_GM", "(M) Mushroom 2 (GM)"}, +{"M_EX350_11_GM", "(M) Mushroom 11 (GM)"}, +{"F_HB601", "(F) Ansem Laboratory’s Xemnas’s CD for Chamber of Repose (HB)"}, +{"H_CM030", "(H) ??? (CM)"} + }; + } +} diff --git a/OpenKh.Tools.Kh2ObjectEditor/ViewModel/App_Context.cs b/OpenKh.Tools.Kh2ObjectEditor/ViewModel/App_Context.cs index dd627b776..c903fd2c6 100644 --- a/OpenKh.Tools.Kh2ObjectEditor/ViewModel/App_Context.cs +++ b/OpenKh.Tools.Kh2ObjectEditor/ViewModel/App_Context.cs @@ -41,10 +41,15 @@ public void openObject() } string tempApdxPath = MdlxPath.ToLower().Replace(".mdlx", ".a.us"); + string tempApdxPathFm = MdlxPath.ToLower().Replace(".mdlx", ".a.fm"); if (ObjectEditorUtils.isFilePathValid(tempApdxPath, "a.us")) { ApdxService.Instance.LoadFile(tempApdxPath); } + else if (ObjectEditorUtils.isFilePathValid(tempApdxPathFm, "a.fm")) + { + ApdxService.Instance.LoadFile(tempApdxPathFm); + } triggerObjectSelected(); } diff --git a/OpenKh.Tools.Kh2ObjectEditor/ViewModel/ModuleLoader_VM.cs b/OpenKh.Tools.Kh2ObjectEditor/ViewModel/ModuleLoader_VM.cs index 0b2b9481a..7b1eb76e0 100644 --- a/OpenKh.Tools.Kh2ObjectEditor/ViewModel/ModuleLoader_VM.cs +++ b/OpenKh.Tools.Kh2ObjectEditor/ViewModel/ModuleLoader_VM.cs @@ -8,7 +8,8 @@ public class ModuleLoader_VM : NotifyPropertyChangedBase { public Visibility TabModelEnabled { get { return MdlxService.Instance.MdlxBar == null ? Visibility.Collapsed : Visibility.Visible; } } public Visibility TabTexturesEnabled { get { return MdlxService.Instance.MdlxBar == null ? Visibility.Collapsed : Visibility.Visible; } } - public Visibility TabMotionsEnabled { get { return MsetService.Instance.MsetBar == null ? Visibility.Collapsed : Visibility.Visible; } } + public Visibility TabUIEnabled { get { return (ApdxService.Instance.ImgdFace == null && ApdxService.Instance.ImgdCommand == null) ? Visibility.Collapsed : Visibility.Visible; } } + public Visibility TabMotionsEnabled { get { return MsetService.Instance.MsetBinarc == null ? Visibility.Collapsed : Visibility.Visible; } } public Visibility TabCollisionsEnabled { get { return MdlxService.Instance.CollisionFile == null ? Visibility.Collapsed : Visibility.Visible; } } public Visibility TabParticlesEnabled { get { return ApdxService.Instance.ApdxBar == null ? Visibility.Collapsed : Visibility.Visible; } } public Visibility TabAIEnabled { get { return MdlxService.Instance.BdxFile == null ? Visibility.Collapsed : Visibility.Visible; } } diff --git a/OpenKh.Tools.Kh2ObjectEditor/ViewModel/ObjectSelector_ViewModel.cs b/OpenKh.Tools.Kh2ObjectEditor/ViewModel/ObjectSelector_ViewModel.cs index 63b7af998..b5b49f1f5 100644 --- a/OpenKh.Tools.Kh2ObjectEditor/ViewModel/ObjectSelector_ViewModel.cs +++ b/OpenKh.Tools.Kh2ObjectEditor/ViewModel/ObjectSelector_ViewModel.cs @@ -70,18 +70,45 @@ public void loadObjects() public void applyFilters() { ObjectsView.Clear(); - foreach (ObjectSelector_Wrapper iObject in Objects) + + bool applyNameFilter = FilterName != null && FilterName != "" & !FilterName.StartsWith(">"); + bool applyDescriptionFilter = FilterName != null && FilterName != "" & FilterName.StartsWith(">"); + bool applyMsetFilter = FilterHasMset; + + foreach(ObjectSelector_Wrapper iObject in Objects) + { + ObjectsView.Add(iObject); + } + + for (int i = ObjectsView.Count - 1; i >= 0; i--) { - if (FilterName != null && FilterName != "" && !iObject.FileName.ToLower().Contains(FilterName.ToLower())) + ObjectSelector_Wrapper iObject = ObjectsView[i]; + if (applyMsetFilter) { - continue; + if (!iObject.HasMset) + { + ObjectsView.RemoveAt(i); + continue; + } } - if (FilterHasMset && !iObject.HasMset) + if (applyNameFilter) { - continue; + if (!iObject.FileName.ToLower().Contains(FilterName.ToLower())) + { + ObjectsView.RemoveAt(i); + continue; + } + } + if (applyDescriptionFilter) + { + string searchCriteria = FilterName.ToLower().Substring(1); + string iObjectDescription = iObject.GetDescription(); + if (!iObjectDescription.ToLower().Contains(searchCriteria.ToLower())) + { + ObjectsView.RemoveAt(i); + continue; + } } - - ObjectsView.Add(iObject); } } diff --git a/OpenKh.Tools.Kh2ObjectEditor/ViewModel/Viewport_ViewModel.cs b/OpenKh.Tools.Kh2ObjectEditor/ViewModel/Viewport_ViewModel.cs deleted file mode 100644 index b4506d870..000000000 --- a/OpenKh.Tools.Kh2ObjectEditor/ViewModel/Viewport_ViewModel.cs +++ /dev/null @@ -1,564 +0,0 @@ -using OpenKh.Kh2; -using OpenKh.Kh2.Models; -using OpenKh.Kh2Anim.Mset; -using OpenKh.Kh2Anim.Mset.Interfaces; -using OpenKh.Tools.Kh2ObjectEditor.Classes; -using OpenKh.Tools.Kh2ObjectEditor.Services; -using OpenKh.Tools.Kh2ObjectEditor.Utils; -using Simple3DViewport.Controls; -using Simple3DViewport.Objects; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Numerics; -using System.Threading; -using System.Threading.Tasks; -using System.Windows.Media; -using System.Windows.Media.Media3D; -using static OpenKh.Kh2.Models.ModelCommon; - -namespace OpenKh.Tools.Kh2ObjectEditor.ViewModel -{ - public class Viewport_ViewModel : NotifyPropertyChangedBase - { - public static FpsMode MODE_30_FPS = new FpsMode(33, 1); - public static FpsMode MODE_15_FPS = new FpsMode(67, 2); - public static FpsMode MODE_10_FPS = new FpsMode(100, 3); - public static FpsMode MODE_5_FPS = new FpsMode(200, 6); - public FpsMode currentFpsMode = MODE_10_FPS; - - public SimpleModel ThisModel { get; set; } - public SimpleModel ThisCollisions { get; set; } - - // Animation - public bool _animationRunning { get; set; } - public bool AnimationRunning - { - get { return _animationRunning; } - set - { - _animationRunning = value; - OnPropertyChanged("AnimationRunning"); - } - } - public bool _renderCollisions { get; set; } - public bool RenderCollisions - { - get { return _renderCollisions; } - set - { - _renderCollisions = value; - OnPropertyChanged("RenderCollisions"); - } - } - public bool _renderHitCollisions { get; set; } - public bool RenderHitCollisions - { - get { return _renderHitCollisions; } - set - { - _renderHitCollisions = value; - OnPropertyChanged("RenderHitCollisions"); - } - } - public int? currentAnim { get; set; } - private int? _currentFrame { get; set; } - public int? CurrentFrame - { - get { return _currentFrame; } - set - { - _currentFrame = value; - OnPropertyChanged("CurrentFrame"); - } - } - - public Matrix4x4[] currentPose { get; set; } - public Matrix4x4[] extraPose { get; set; } - private AnbIndir currentAnb { get; set; } - private IAnimMatricesProvider AnimMatricesProvider { get; set; } - - public Simple3DViewport_Control ViewportControl { get; set; } - public bool enable_frameCommands - { - get - { - return MsetService.Instance.LoadedMotion != null; - } - } - public bool enable_reload - { - get - { - return MdlxService.Instance.ModelFile != null; - } - } - - public Viewport_ViewModel() - { - AnimationRunning = false; - RenderCollisions = true; - RenderHitCollisions = false; - CurrentFrame = 0; - startAnimationTicker(); - - subscribe_ObjectSelected(); - subscribe_MotionSelected(); - } - - public void clearViewport() - { - ViewportControl.VPModels.Clear(); - ThisModel = null; - ThisCollisions = null; - _animationRunning = false; - currentAnim = null; - CurrentFrame = null; - currentPose = null; - extraPose = null; - currentAnb = null; - AnimMatricesProvider = null; - } - - public void loadModel(bool doCameraRestart) - { - AnimationRunning = false; - foreach (ModelSkeletal.SkeletalGroup group in MdlxService.Instance.ModelFile.Groups) - { - group.Mesh = ModelSkeletal.getMeshFromGroup(group, GetBoneMatrices(MdlxService.Instance.ModelFile.Bones)); - } - - if (MdlxService.Instance.ModelFile != null) - { - ThisModel = ViewportHelper.getModel(MdlxService.Instance.ModelFile, MdlxService.Instance.TextureFile); - ViewportControl.VPModels.Clear(); - if (ThisModel != null) - ViewportControl.VPModels.Add(ThisModel); - - // Attachable - if (AttachmentService.Instance.Attach_ModelFile != null) - { - ViewportControl.VPModels.Add(ViewportHelper.getModel(AttachmentService.Instance.Attach_ModelFile, AttachmentService.Instance.Attach_TextureFile)); - } - - // Hit Collisions - if (RenderHitCollisions && MdlxService.Instance.CollisionFile != null) - { - SimpleModel model = new SimpleModel(getHitCollisions(null)); - ViewportControl.VPModels.Add(model); - } - - ViewportControl.render(); - if (doCameraRestart) ViewportControl.restartCamera(); - } - } - - private void loadAnb() - { - if (!ObjectEditorUtils.isFilePathValid(App_Context.Instance.MdlxPath, "mdlx") || !ObjectEditorUtils.isFilePathValid(MsetService.Instance.MsetPath, "mset") || MsetService.Instance.LoadedMotion == null) - return; - - MsetService.Instance.MsetBar[MsetService.Instance.LoadedMotionId].Stream.Position = 0; - Bar anbBarFile = Bar.Read(MsetService.Instance.MsetBar[MsetService.Instance.LoadedMotionId].Stream); - - currentAnb = new AnbIndir(anbBarFile); - } - - private void loadAnimProvider() - { - //using var mdlxStream = File.Open(Mdlx_Service.Instance.MdlxPath, FileMode.Open); - - // This is a test, model should be saved in the model module - MdlxService.Instance.SaveModel(); - Stream mdlxStream = new MemoryStream(); - Bar.Write(mdlxStream, MdlxService.Instance.MdlxBar); - mdlxStream.Position = 0; - - if (!mdlxStream.CanRead || !mdlxStream.CanSeek) - throw new InvalidDataException($"Read or seek must be supported."); - - AnimMatricesProvider = currentAnb.GetAnimProvider(mdlxStream); - } - - public void getMatricesForFrame(int frame) - { - loadAnimProvider(); - - Matrix4x4[] matrices = AnimMatricesProvider.ProvideMatrices(frame); - - foreach (ModelSkeletal.SkeletalGroup group in MdlxService.Instance.ModelFile.Groups) - { - group.Mesh = ModelSkeletal.getMeshFromGroup(group, matrices); - } - } - - public void loadFrame() - { - if (currentAnb == null) - return; - - loadAnimProvider(); - - ThisCollisions = new SimpleModel(new List(), "COLLISIONS_1", new List { "COLLISION", "COLLISION_GROUP" }); - - if (!ObjectEditorUtils.isFilePathValid(MdlxService.Instance.MdlxPath, "mdlx") || !ObjectEditorUtils.isFilePathValid(MsetService.Instance.MsetPath, "mset") || MsetService.Instance.LoadedMotion == null) - return; - - if (MsetService.Instance.LoadedMotion?.MotionFile?.KeyTimes == null || MsetService.Instance.LoadedMotion.MotionFile.KeyTimes.IsEmpty()) - { - return; - } - - int realFrameStart = (int)MsetService.Instance.LoadedMotion.MotionFile.KeyTimes[0]; - if (realFrameStart < 0) realFrameStart = 0; - int realFrameEnd = (int)MsetService.Instance.LoadedMotion.MotionFile.KeyTimes[MsetService.Instance.LoadedMotion.MotionFile.KeyTimes.Count - 1]; - - if (CurrentFrame < realFrameStart) - CurrentFrame = realFrameEnd; - - if (CurrentFrame > realFrameEnd) - CurrentFrame = realFrameStart; - - Matrix4x4[] matrices = AnimMatricesProvider.ProvideMatrices(CurrentFrame.Value); - - foreach (ModelSkeletal.SkeletalGroup group in MdlxService.Instance.ModelFile.Groups) - { - group.Mesh = ModelSkeletal.getMeshFromGroup(group, matrices); - } - - // Render the animated model - ThisModel = ViewportHelper.getModel(MdlxService.Instance.ModelFile, MdlxService.Instance.TextureFile); - ViewportControl.VPModels.Clear(); - if (ThisModel != null) - ViewportControl.VPModels.Add(ThisModel); - - if (RenderCollisions) - { - loadCollisions(matrices); - if (ThisCollisions != null) - ViewportControl.VPModels.Add(ThisCollisions); - } - - if (AttachmentService.Instance.Attach_ModelFile != null) - { - foreach (SimpleModel model in getAttachmentModel(matrices)) - { - ViewportControl.VPModels.Add(model); - } - } - - if (RenderHitCollisions && MdlxService.Instance.CollisionFile != null) - { - SimpleModel model = new SimpleModel(getHitCollisions(matrices)); - ViewportControl.VPModels.Add(model); - } - - ViewportControl.render(); - } - - // Ideally you'd parent bone 0 to the bone to be attached, however I can't get it to work, so this is the best approximation I could do. Not good enough for hitboxes, just for show - public List getAttachmentModel(Matrix4x4[] modelMatrices) - { - App_Context test = App_Context.Instance; - - Stream temp_mdlxStream = new MemoryStream(); - Bar.Write(temp_mdlxStream, AttachmentService.Instance.Attach_MdlxBar); - - if (!temp_mdlxStream.CanRead || !temp_mdlxStream.CanSeek) - throw new InvalidDataException($"Read or seek must be supported."); - - - Bar attach_AnbBarFile = new Bar(); - try // Some animations don't use the weapon - { - attach_AnbBarFile = Bar.Read(AttachmentService.Instance.Attach_MsetEntries[MsetService.Instance.LoadedMotionId].Entry.Stream); - } - catch (Exception ex) - { - return new List(); - } - - AttachmentService.Instance.Attach_MsetEntries[MsetService.Instance.LoadedMotionId].Entry.Stream.Position = 0; - AnbIndir attach_Anb = new AnbIndir(attach_AnbBarFile); - - IAnimMatricesProvider attach_AnimMatricesProvider = attach_Anb.GetAnimProvider(temp_mdlxStream); - - Matrix4x4[] matrices = attach_AnimMatricesProvider.ProvideMatrices(CurrentFrame.Value); - - foreach (ModelSkeletal.SkeletalGroup group in AttachmentService.Instance.Attach_ModelFile.Groups) - { - group.Mesh = ModelSkeletal.getMeshFromGroup(group, matrices); - } - - SimpleModel model = ViewportHelper.getModel(AttachmentService.Instance.Attach_ModelFile, AttachmentService.Instance.Attach_TextureFile); - - List modelList = new List(); - Simple3DViewport.Utils.Simple3DUtils.applyTransform(model.Meshes[0].Geometry, modelMatrices[AttachmentService.Instance.Attach_BoneId]); - modelList.Add(model); - - return modelList; - } - - public void nextFrame() - { - CurrentFrame += 1; - loadCollisions(null); - loadFrame(); - } - public void previousFrame() - { - CurrentFrame -= 1; - loadCollisions(null); - loadFrame(); - } - - public void loadCollisions(Matrix4x4[] matrices) - { - if (MdlxService.Instance.CollisionFile != null) - { - List collisionGroupsToLoad = new List(); - - if (MsetService.Instance.LoadedMotion.MotionTriggerFile?.RangeTriggerList == null) - { - return; - } - - foreach (MotionTrigger.RangeTrigger trigger in MsetService.Instance.LoadedMotion.MotionTriggerFile.RangeTriggerList) - { - if ((trigger.StartFrame <= CurrentFrame || trigger.StartFrame == -1) && (trigger.EndFrame >= CurrentFrame || trigger.EndFrame == -1)) - { - // Attack 1 hitbox - if (trigger.Trigger == 10) - { - // Param 1 is atkp entry - short hitbox1 = (short)(trigger.Param2); - if (!collisionGroupsToLoad.Contains(hitbox1)) - collisionGroupsToLoad.Add(hitbox1); - } - // Attack 2 hitboxes - if (trigger.Trigger == 33) - { - // Param 1 is atkp entry - short hitbox1 = (short)(trigger.Param2); - short hitbox2 = (short)(trigger.Param3); - if (!collisionGroupsToLoad.Contains(hitbox1)) - collisionGroupsToLoad.Add(hitbox1); - if (!collisionGroupsToLoad.Contains(hitbox2)) - collisionGroupsToLoad.Add(hitbox2); - } - // Reaction collision - if (trigger.Trigger == 4) - { - short hitbox1 = (short)(trigger.Param1); - if (!collisionGroupsToLoad.Contains(hitbox1)) - collisionGroupsToLoad.Add(hitbox1); - } - } - } - - List attackCollisions = new List(); - for (int i = 0; i < MdlxService.Instance.CollisionFile.EntryList.Count; i++) - { - ObjectCollision collision = MdlxService.Instance.CollisionFile.EntryList[i]; - if (collisionGroupsToLoad.Contains(collision.Group)) - { - attackCollisions.Add(new Collision_Wrapper("COLLISION_" + i, collision)); - } - } - loadAttackCollisions(attackCollisions, matrices); - } - } - - public void subscribe_ObjectSelected() - { - App_Context.Instance.Event_ObjectSelected += new App_Context.EventHandler(sub_ObjectSelected); - } - private void sub_ObjectSelected(App_Context m, EventArgs e) - { - clearViewport(); - loadModel(true); - } - public void subscribe_MotionSelected() - { - App_Context.Instance.Event_MotionSelected += new App_Context.EventHandler(sub_MotionSelected); - } - private void sub_MotionSelected(App_Context m, EventArgs e) - { - CurrentFrame = 0; - loadAnb(); - loadAnimProvider(); - loadFrame(); - //AnimationRunning = true; - } - - // Animation ticker - async Task startAnimationTicker() - { - TimeSpan timeInMs = currentFpsMode.MsBetweenTicks; - - PeriodicTimer animationTimer = new PeriodicTimer(timeInMs); - while (await animationTimer.WaitForNextTickAsync()) - { - try - { - animationTicker(); - } - catch (Exception e) { } - } - } - - public void animationTicker() - { - if (!AnimationRunning) - { - //CurrentFrame = 0; - return; - } - - if (!ObjectEditorUtils.isFilePathValid(App_Context.Instance.MdlxPath, "mdlx") || !ObjectEditorUtils.isFilePathValid(MsetService.Instance.MsetPath, "mset") || MsetService.Instance.LoadedMotion == null) - return; - - int timeTicked = currentFpsMode.FrameStep; - - CurrentFrame += timeTicked; - loadFrame(); - } - - public List getHitCollisions(Matrix4x4[] matrices) - { - List list_hitMeshes = new List(); - - if (MdlxService.Instance.CollisionFile == null) - return list_hitMeshes; - - Matrix4x4[] boneMatrices = new Matrix4x4[0]; - - if (matrices != null) - boneMatrices = matrices; - else if (MdlxService.Instance.ModelFile != null) - boneMatrices = ModelCommon.GetBoneMatrices(MdlxService.Instance.ModelFile.Bones); - - for (int i = 0; i < MdlxService.Instance.CollisionFile.EntryList.Count; i++) - { - ObjectCollision collision = MdlxService.Instance.CollisionFile.EntryList[i]; - - if (collision.Type == (byte)ObjectCollision.TypeEnum.HIT) - { - - Vector3 basePosition = Vector3.Zero; - if (collision.Bone != 16384 && boneMatrices.Length != 0) - { - basePosition = Vector3.Transform(new Vector3(collision.PositionX, collision.PositionY, collision.PositionZ), boneMatrices[collision.Bone]); - } - - list_hitMeshes.Add(getCollisionMesh( - "COLLISION_HIT_" + i, - new List { "COLLISION", "COLLISION_HIT_SINGLE" }, - collision.Shape, - new Vector3D(basePosition.X, basePosition.Y, basePosition.Z), - Color.FromArgb(100, 0, 0, 255), - collision.Radius, - collision.Height - )); - } - } - - return list_hitMeshes; - } - - public void loadAttackCollisions(List attackCollisions, Matrix4x4[] matrices) - { - if (MdlxService.Instance.CollisionFile != null) - { - Matrix4x4[] boneMatrices = new Matrix4x4[0]; - - if (matrices != null) - boneMatrices = matrices; - else if (MdlxService.Instance.ModelFile != null) - boneMatrices = ModelCommon.GetBoneMatrices(MdlxService.Instance.ModelFile.Bones); - - List simpleMeshes = new List(); - - for (int i = 0; i < attackCollisions.Count; i++) - { - ObjectCollision collision = attackCollisions[i].Collision; - - Vector3 basePosition = Vector3.Zero; - if (collision.Bone != 16384 && boneMatrices.Length != 0) - { - basePosition = Vector3.Transform(new Vector3(collision.PositionX, collision.PositionY, collision.PositionZ), boneMatrices[collision.Bone]); - } - - - Color color = new Color(); - if (collision.Type == (byte)ObjectCollision.TypeEnum.HIT) - { - color = Color.FromArgb(100, 0, 0, 255); - } - else if (collision.Type == (byte)ObjectCollision.TypeEnum.REACTION) - { - color = Color.FromArgb(100, 0, 255, 0); - } - else - { - color = Color.FromArgb(100, 255, 0, 0); - } - - - simpleMeshes.Add(getCollisionMesh( - "COLLISION_" + i, - new List { "COLLISION", "COLLISION_SINGLE" }, - collision.Shape, - new Vector3D(basePosition.X, basePosition.Y, basePosition.Z), - color, - collision.Radius, - collision.Height - )); - } - - ThisCollisions = new SimpleModel(simpleMeshes, "COLLISIONS_1", new List { "COLLISION", "COLLISION_GROUP" }); - } - } - - public static SimpleMesh getCollisionMesh(string id, List labels, byte shape, Vector3D position, Color color, short width, short height) - { - SimpleMesh thisMesh = null; - - if (shape == (byte)ObjectCollision.ShapeEnum.ELLIPSOID) - { - thisMesh = new SimpleMesh(Simple3DViewport.Utils.GeometryShapes.getEllipsoid(width, height, 10, position, color), id, labels); - } - else if (shape == (byte)ObjectCollision.ShapeEnum.COLUMN) - { - thisMesh = new SimpleMesh(Simple3DViewport.Utils.GeometryShapes.getCylinder(width, height, 10, position, color), id, labels); - } - else if (shape == (byte)ObjectCollision.ShapeEnum.CUBE) - { - thisMesh = new SimpleMesh(Simple3DViewport.Utils.GeometryShapes.getCuboid(width, height, width, position, color), id, labels); - } - else if (shape == (byte)ObjectCollision.ShapeEnum.SPHERE) - { - thisMesh = new SimpleMesh(Simple3DViewport.Utils.GeometryShapes.getSphere(width, 10, position, color), id, labels); - } - - return thisMesh; - } - - public void reload() - { - AnimationRunning = false; - CurrentFrame = 0; - clearViewport(); - loadModel(false); - } - - public class FpsMode - { - public TimeSpan MsBetweenTicks { get; set; } - public int FrameStep { get; set; } - - public FpsMode(int msBetweenTicks, int frameStep) { MsBetweenTicks = TimeSpan.FromMilliseconds(msBetweenTicks); FrameStep = frameStep; } - } - } -} diff --git a/OpenKh.Tools.Kh2ObjectEditor/Views/Main_Window.xaml b/OpenKh.Tools.Kh2ObjectEditor/Views/Main_Window.xaml index 934dc5c90..296d8bba5 100644 --- a/OpenKh.Tools.Kh2ObjectEditor/Views/Main_Window.xaml +++ b/OpenKh.Tools.Kh2ObjectEditor/Views/Main_Window.xaml @@ -16,9 +16,10 @@ + - + @@ -29,11 +30,12 @@ + - + diff --git a/OpenKh.Tools.Kh2ObjectEditor/Views/Main_Window.xaml.cs b/OpenKh.Tools.Kh2ObjectEditor/Views/Main_Window.xaml.cs index df603f113..17bf88506 100644 --- a/OpenKh.Tools.Kh2ObjectEditor/Views/Main_Window.xaml.cs +++ b/OpenKh.Tools.Kh2ObjectEditor/Views/Main_Window.xaml.cs @@ -1,10 +1,14 @@ using Microsoft.Win32; +using OpenKh.AssimpUtils; +using OpenKh.Kh2; +using OpenKh.Tools.Common.Wpf; using OpenKh.Tools.Kh2ObjectEditor.Services; using OpenKh.Tools.Kh2ObjectEditor.Utils; using OpenKh.Tools.Kh2ObjectEditor.ViewModel; using System.IO; using System.Linq; using System.Windows; +using System.Windows.Media.Imaging; namespace OpenKh.Tools.Kh2ObjectEditor.Views { @@ -17,6 +21,7 @@ private void Menu_Test(object sender, RoutedEventArgs e) MdlxService checkMdlxService = MdlxService.Instance; MsetService checkMsetService = MsetService.Instance; ApdxService checkApdxService = ApdxService.Instance; + ClipboardService checkClipboardService = ClipboardService.Instance; // BREAKPOINT to check app context info } private void Menu_Test_RunCode(object sender, RoutedEventArgs e) @@ -132,6 +137,82 @@ private void Menu_AttachMdlx(object sender, RoutedEventArgs e) } } - + private void Menu_OpenMdlx(object sender, RoutedEventArgs e) + { + OpenFileDialog openFileDialog = new OpenFileDialog(); + openFileDialog.Filter = "Mdlx file | *.mdlx"; + bool? success = openFileDialog.ShowDialog(); + + if (success == true) + { + if (Directory.Exists(openFileDialog.FileName)) + { + return; + } + else if (File.Exists(openFileDialog.FileName)) + { + if (ObjectEditorUtils.isFilePathValid(openFileDialog.FileName, "mdlx")) + { + App_Context.Instance.loadMdlx(openFileDialog.FileName); + } + } + } + } + + private void Menu_ExportModel(object sender, RoutedEventArgs e) + { + if(MdlxService.Instance.ModelFile != null) + { + Kh2.Models.ModelSkeletal model = null; + foreach (Bar.Entry barEntry in MdlxService.Instance.MdlxBar) + { + if (barEntry.Type == Bar.EntryType.Model) + { + model = Kh2.Models.ModelSkeletal.Read(barEntry.Stream); + barEntry.Stream.Position = 0; + } + } + + Assimp.Scene scene = Kh2MdlxAssimp.getAssimpScene(model); + + System.Windows.Forms.SaveFileDialog sfd; + sfd = new System.Windows.Forms.SaveFileDialog(); + sfd.Title = "Export model"; + sfd.FileName = MdlxService.Instance.MdlxPath + "." + AssimpGeneric.GetFormatFileExtension(AssimpGeneric.FileFormat.fbx); + sfd.ShowDialog(); + if (sfd.FileName != "") + { + string dirPath = Path.GetDirectoryName(sfd.FileName); + + if (!Directory.Exists(dirPath)) + return; + + dirPath += "\\"; + + AssimpGeneric.ExportScene(scene, AssimpGeneric.FileFormat.fbx, sfd.FileName); + exportTextures(dirPath); + } + } + } + + public void exportTextures(string filePath) + { + for (int i = 0; i < MdlxService.Instance.TextureFile.Images.Count; i++) + { + ModelTexture.Texture texture = MdlxService.Instance.TextureFile.Images[i]; + BitmapSource bitmapImage = texture.GetBimapSource(); + + string fullPath = filePath + "Texture" + i.ToString("D4"); + string finalPath = fullPath; + int repeat = 0; + while (File.Exists(finalPath)) + { + repeat++; + finalPath = fullPath + " (" + repeat + ")"; + } + + AssimpGeneric.ExportBitmapSourceAsPng(bitmapImage, fullPath); + } + } } } diff --git a/OpenKh.Tools.Kh2ObjectEditor/Views/ModuleLoader_Control.xaml b/OpenKh.Tools.Kh2ObjectEditor/Views/ModuleLoader_Control.xaml index ca1d55b91..c6c0dc988 100644 --- a/OpenKh.Tools.Kh2ObjectEditor/Views/ModuleLoader_Control.xaml +++ b/OpenKh.Tools.Kh2ObjectEditor/Views/ModuleLoader_Control.xaml @@ -21,6 +21,7 @@ + diff --git a/OpenKh.Tools.Kh2ObjectEditor/Views/ModuleLoader_Control.xaml.cs b/OpenKh.Tools.Kh2ObjectEditor/Views/ModuleLoader_Control.xaml.cs index e693fe07c..761216025 100644 --- a/OpenKh.Tools.Kh2ObjectEditor/Views/ModuleLoader_Control.xaml.cs +++ b/OpenKh.Tools.Kh2ObjectEditor/Views/ModuleLoader_Control.xaml.cs @@ -1,6 +1,7 @@ using OpenKh.Tools.Kh2ObjectEditor.Modules.Collisions; using OpenKh.Tools.Kh2ObjectEditor.Modules.Effects; using OpenKh.Tools.Kh2ObjectEditor.Modules.Textures; +using OpenKh.Tools.Kh2ObjectEditor.Modules.UI; using OpenKh.Tools.Kh2ObjectEditor.ViewModel; using System; using System.Windows.Controls; @@ -23,6 +24,7 @@ public void reloadTabs() { TabModel.Visibility = ThisVM.TabModelEnabled; TabTextures.Visibility = ThisVM.TabTexturesEnabled; + TabUI.Visibility = ThisVM.TabUIEnabled; TabCollisions.Visibility = ThisVM.TabCollisionsEnabled; TabMotions.Visibility = ThisVM.TabMotionsEnabled; TabParticles.Visibility = ThisVM.TabParticlesEnabled; @@ -47,7 +49,10 @@ private void TabClick_Model(object sender, System.Windows.Input.MouseButtonEvent private void TabClick_Textures(object sender, System.Windows.Input.MouseButtonEventArgs e) { contentFrame.Content = new ModuleTextures_Control(); - //contentFrame.Content = new TextureAnimation_Control(); + } + private void TabClick_UI(object sender, System.Windows.Input.MouseButtonEventArgs e) + { + contentFrame.Content = new M_UI_Control(); } private void TabClick_Collisions(object sender, System.Windows.Input.MouseButtonEventArgs e) { diff --git a/OpenKh.Tools.Kh2ObjectEditor/Views/ObjectSelector_Control.xaml b/OpenKh.Tools.Kh2ObjectEditor/Views/ObjectSelector_Control.xaml index d43b722ab..961a94323 100644 --- a/OpenKh.Tools.Kh2ObjectEditor/Views/ObjectSelector_Control.xaml +++ b/OpenKh.Tools.Kh2ObjectEditor/Views/ObjectSelector_Control.xaml @@ -14,13 +14,13 @@ - + - + diff --git a/OpenKh.Tools.Kh2ObjectEditor/Views/ObjectSelector_Control.xaml.cs b/OpenKh.Tools.Kh2ObjectEditor/Views/ObjectSelector_Control.xaml.cs index df3672852..a9f1e6dd0 100644 --- a/OpenKh.Tools.Kh2ObjectEditor/Views/ObjectSelector_Control.xaml.cs +++ b/OpenKh.Tools.Kh2ObjectEditor/Views/ObjectSelector_Control.xaml.cs @@ -1,6 +1,7 @@ using OpenKh.Tools.Kh2ObjectEditor.Classes; using OpenKh.Tools.Kh2ObjectEditor.ViewModel; using System.Windows.Controls; +using System.Windows.Input; namespace OpenKh.Tools.Kh2ObjectEditor.Views { @@ -29,5 +30,14 @@ private void Button_ApplyFilters(object sender, System.Windows.RoutedEventArgs e { ThisVM.applyFilters(); } + + private void FilterName_PreviewKeyDown(object sender, System.Windows.Input.KeyEventArgs e) + { + if (e.Key == Key.Return) + { + ThisVM.FilterName = FilterName.Text; + ThisVM.applyFilters(); + } + } } } diff --git a/OpenKh.Tools.Kh2ObjectEditor/Views/Viewport_Control.xaml b/OpenKh.Tools.Kh2ObjectEditor/Views/Viewport_Control.xaml index 51e18d04e..b1e3033c5 100644 --- a/OpenKh.Tools.Kh2ObjectEditor/Views/Viewport_Control.xaml +++ b/OpenKh.Tools.Kh2ObjectEditor/Views/Viewport_Control.xaml @@ -4,17 +4,29 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:local="clr-namespace:OpenKh.Tools.Kh2ObjectEditor.Views" - xmlns:S3V="clr-namespace:Simple3DViewport.Controls;assembly=Simple3DViewport" + xmlns:S3V="clr-namespace:Simple3DViewport.Controls;assembly=Simple3DViewport" xmlns:h="http://helix-toolkit.org/wpf" mc:Ignorable="d" d:DesignHeight="450" d:DesignWidth="800"> - + + + + - + - + @@ -22,13 +34,24 @@ - Auto advance + + + Auto advance + Bounding Box + + + Auto Collisions + Auto Collisions - Attack + Auto Collisions - Other + + + - Damage Collisions - Hit Detection Collisions + - + + diff --git a/OpenKh.Tools.Kh2ObjectEditor/Views/Viewport_Control.xaml.cs b/OpenKh.Tools.Kh2ObjectEditor/Views/Viewport_Control.xaml.cs index e8283e53c..98c365144 100644 --- a/OpenKh.Tools.Kh2ObjectEditor/Views/Viewport_Control.xaml.cs +++ b/OpenKh.Tools.Kh2ObjectEditor/Views/Viewport_Control.xaml.cs @@ -1,39 +1,118 @@ +using OpenKh.Tools.Kh2ObjectEditor.Services; +using System.Diagnostics; using System.Windows.Controls; -using OpenKh.Tools.Kh2ObjectEditor.ViewModel; +using System.Windows.Controls.Primitives; +using System.Windows.Input; namespace OpenKh.Tools.Kh2ObjectEditor.Views { public partial class Viewport_Control : UserControl { - public Viewport_ViewModel ThisVM { get; set; } + private bool _isMouseOverDockPanel = false; public Viewport_Control() { InitializeComponent(); - ThisVM = new Viewport_ViewModel(); - ThisVM.ViewportControl = Viewport; - DataContext = ThisVM; + DataContext = ViewerService.Instance; + ViewerService.Instance.HookViewport(HelixViewport); } private void Button_PreviousFrame(object sender, System.Windows.RoutedEventArgs e) { - if (!ThisVM.enable_frameCommands) - return; - ThisVM.previousFrame(); + PreviousFrame(); } private void Button_NextFrame(object sender, System.Windows.RoutedEventArgs e) { - if (!ThisVM.enable_frameCommands) - return; - ThisVM.nextFrame(); + NextFrame(); } private void Button_Reload(object sender, System.Windows.RoutedEventArgs e) { - if (!ThisVM.enable_reload) + if (MdlxService.Instance.ModelFile == null) { + return; + } + ViewerService.Instance.Render(); + } + + private void Slider_DragStarted(object sender, DragStartedEventArgs e) + { + if (MsetService.Instance.LoadedMotion == null) { + return; + } + ViewerService.Instance.AnimationRunning = false; + } + private void Slider_DragCompleted(object sender, DragCompletedEventArgs e) + { + if (MsetService.Instance.LoadedMotion == null) { + return; + } + ViewerService.Instance.LoadFrame(); + } + + private void DockPanel_MouseEnter(object sender, System.Windows.Input.MouseEventArgs e) + { + _isMouseOverDockPanel = true; + } + + private void DockPanel_MouseLeave(object sender, System.Windows.Input.MouseEventArgs e) + { + _isMouseOverDockPanel = false; + } + + private void Viewport_KeyDown(object sender, KeyEventArgs e) + { + // Prevent Viewport to use these keys + if (e.Key == Key.Left || + e.Key == Key.Right || + e.Key == Key.Up || + e.Key == Key.Down || + e.Key == Key.Space) + { + e.Handled = true; + } + } + + private void Window_KeyDown(object sender, KeyEventArgs e) + { + if (!_isMouseOverDockPanel) { + return; + } + + if (e.Key == Key.Space) + { + ViewerService.Instance.AnimationRunning = !ViewerService.Instance.AnimationRunning; + } + else if(e.Key == Key.Left) + { + PreviousFrame(); + } + else if (e.Key == Key.Right) + { + NextFrame(); + } + } + + private void PreviousFrame(int frameStep = -1) + { + if (MsetService.Instance.LoadedMotion == null) + { + return; + } + ViewerService.Instance.AnimationRunning = false; + ViewerService.Instance.FrameIncrease(frameStep); + ViewerService.Instance.LoadFrame(); + } + + private void NextFrame(int frameStep = 1) + { + if (MsetService.Instance.LoadedMotion == null) + { return; - ThisVM.reload(); + } + ViewerService.Instance.AnimationRunning = false; + ViewerService.Instance.FrameIncrease(frameStep); + ViewerService.Instance.LoadFrame(); } } } diff --git a/OpenKh.Tools.Kh2SystemEditor/OpenKh.Tools.Kh2SystemEditor.csproj b/OpenKh.Tools.Kh2SystemEditor/OpenKh.Tools.Kh2SystemEditor.csproj index ba11f86b3..86e9f1efd 100644 --- a/OpenKh.Tools.Kh2SystemEditor/OpenKh.Tools.Kh2SystemEditor.csproj +++ b/OpenKh.Tools.Kh2SystemEditor/OpenKh.Tools.Kh2SystemEditor.csproj @@ -20,7 +20,7 @@ - + diff --git a/OpenKh.Tools.Kh2TextEditor/OpenKh.Tools.Kh2TextEditor.csproj b/OpenKh.Tools.Kh2TextEditor/OpenKh.Tools.Kh2TextEditor.csproj index 2c935f5e5..f6c7a568f 100644 --- a/OpenKh.Tools.Kh2TextEditor/OpenKh.Tools.Kh2TextEditor.csproj +++ b/OpenKh.Tools.Kh2TextEditor/OpenKh.Tools.Kh2TextEditor.csproj @@ -20,7 +20,7 @@ - + diff --git a/OpenKh.Tools.KhModels/View/MainWindow.xaml.cs b/OpenKh.Tools.KhModels/View/MainWindow.xaml.cs index 302f12dd3..440a489d9 100644 --- a/OpenKh.Tools.KhModels/View/MainWindow.xaml.cs +++ b/OpenKh.Tools.KhModels/View/MainWindow.xaml.cs @@ -50,21 +50,22 @@ private void Menu_ExportAsDae(object sender, EventArgs e) private void Button_ShowMesh(object sender, RoutedEventArgs e) { thisVM.ShowMesh = ! thisVM.ShowMesh; - thisVM.SetOptions(); - thisVM.VpService.Render(); + thisVM.VpController.SetVisibilityMesh(thisVM.ShowMesh); + thisVM.VpController.Render(); } private void Button_ShowWireframe(object sender, RoutedEventArgs e) { thisVM.ShowWireframe = !thisVM.ShowWireframe; - thisVM.SetOptions(); - thisVM.VpService.Render(); + thisVM.VpController.SetVisibilityWireframe(thisVM.ShowWireframe); + thisVM.VpController.Render(); } private void Button_ShowSkeleton(object sender, RoutedEventArgs e) { thisVM.ShowSkeleton = !thisVM.ShowSkeleton; thisVM.ShowJoints = !thisVM.ShowJoints; - thisVM.SetOptions(); - thisVM.VpService.Render(); + thisVM.VpController.SetVisibilitySkeleton(thisVM.ShowSkeleton); + thisVM.VpController.SetVisibilityJoint(thisVM.ShowJoints); + thisVM.VpController.Render(); } } } diff --git a/OpenKh.Tools.KhModels/View/MainWindowVM.cs b/OpenKh.Tools.KhModels/View/MainWindowVM.cs index c72e30386..b48736140 100644 --- a/OpenKh.Tools.KhModels/View/MainWindowVM.cs +++ b/OpenKh.Tools.KhModels/View/MainWindowVM.cs @@ -17,6 +17,7 @@ using System.Diagnostics; using System.Drawing.Imaging; using System.IO; +using System.Linq; using System.Numerics; using static OpenKh.Ddd.PmoV4; @@ -24,7 +25,7 @@ namespace OpenKh.Tools.KhModels.View { public class MainWindowVM { - public ViewportService VpService { get; set; } + public ViewportController VpController { get; set; } public string Filepath { get; set; } // Options public bool ShowMesh { get; set; } @@ -36,7 +37,7 @@ public class MainWindowVM public MainWindowVM(HelixViewport3D viewport) { - VpService = new ViewportService(viewport); + VpController = new ViewportController(viewport); SetDefaultOptions(); } @@ -50,14 +51,15 @@ public void SetDefaultOptions() ShowOrigin = false; SetOptions(); } + public void SetOptions() { - VpService.ShowMesh = ShowMesh; - VpService.ShowWireframe = ShowWireframe; - VpService.ShowSkeleton = ShowSkeleton; - VpService.ShowJoints = ShowJoints; - VpService.ShowBoundingBox = ShowBoundingBox; - VpService.ShowOrigin = ShowOrigin; + VpController.SetVisibilityMesh(ShowMesh); + VpController.SetVisibilityWireframe(ShowWireframe); + VpController.SetVisibilitySkeleton(ShowSkeleton); + VpController.SetVisibilityJoint(ShowJoints); + VpController.SetVisibilityBoundingBox(ShowBoundingBox); + VpController.SetVisibilityOrigin(ShowOrigin); } public void LoadFilepath(string filepath) @@ -222,7 +224,12 @@ public void LoadFile() if (mtModels.Count > 0) { - VpService.LoadNewModels(mtModels); + VpController.ClearModels(); + foreach(MtModel model in mtModels) + { + VpController.AddModel(model, loadWireframe: false, loadBoundingBox: false); + } + VpController.Render(); } Debug.WriteLine("PMP RENDERED: " + sw.ElapsedMilliseconds); } @@ -232,25 +239,25 @@ public void ExportModel(AssimpGeneric.FileFormat fileFormat = AssimpGeneric.File //MtModel model = VpService.Models[0]; //Assimp.Scene scene = AssimpExporter.ExportScene(model); - Assimp.Scene scene = AssimpExporter.ExportScene(VpService.Models); + Assimp.Scene scene = AssimpExporter.ExportScene(VpController.ModelVisuals.Keys.ToList()); System.Windows.Forms.SaveFileDialog sfd; sfd = new System.Windows.Forms.SaveFileDialog(); sfd.Title = "Export model"; - sfd.FileName = (VpService.Models.Count == 1) ? VpService.Models[0].Name + "." + AssimpGeneric.GetFormatFileExtension(fileFormat) : "Map." + AssimpGeneric.GetFormatFileExtension(fileFormat); + sfd.FileName = (VpController.ModelVisuals.Keys.Count == 1) ? VpController.ModelVisuals.Keys.ToArray()[0].Name + "." + AssimpGeneric.GetFormatFileExtension(fileFormat) : "Map." + AssimpGeneric.GetFormatFileExtension(fileFormat); sfd.ShowDialog(); if (sfd.FileName != "") { string dirPath = Path.GetDirectoryName(sfd.FileName); AssimpGeneric.ExportScene(scene, fileFormat, sfd.FileName); - foreach(MtModel model in VpService.Models) + foreach(MtModel model in VpController.ModelVisuals.Keys.ToArray()) { foreach (MtMaterial material in model.Materials) { if (material.DiffuseTextureBitmap != null) { - string textureName = (VpService.Models.Count == 1) ? Path.Combine(dirPath, material.Name + ".png") : Path.Combine(dirPath, model.Name + "." + material.Name + ".png"); + string textureName = (VpController.ModelVisuals.Keys.ToArray().Length == 1) ? Path.Combine(dirPath, material.Name + ".png") : Path.Combine(dirPath, model.Name + "." + material.Name + ".png"); material.ExportAsPng(textureName); } } diff --git a/OpenKh.Tools.LayoutEditor/OpenKh.Tools.LayoutEditor.csproj b/OpenKh.Tools.LayoutEditor/OpenKh.Tools.LayoutEditor.csproj index d871c21e8..e89903520 100644 --- a/OpenKh.Tools.LayoutEditor/OpenKh.Tools.LayoutEditor.csproj +++ b/OpenKh.Tools.LayoutEditor/OpenKh.Tools.LayoutEditor.csproj @@ -23,7 +23,7 @@ - + diff --git a/OpenKh.Tools.ModsManager/App.xaml b/OpenKh.Tools.ModsManager/App.xaml index c64605563..af6f44d17 100644 --- a/OpenKh.Tools.ModsManager/App.xaml +++ b/OpenKh.Tools.ModsManager/App.xaml @@ -7,6 +7,7 @@ + diff --git a/OpenKh.Tools.ModsManager/OpenKh.Tools.ModsManager.csproj b/OpenKh.Tools.ModsManager/OpenKh.Tools.ModsManager.csproj index c318969de..62397be66 100644 --- a/OpenKh.Tools.ModsManager/OpenKh.Tools.ModsManager.csproj +++ b/OpenKh.Tools.ModsManager/OpenKh.Tools.ModsManager.csproj @@ -13,8 +13,8 @@ - - + + diff --git a/OpenKh.Tools.ModsManager/Services/ConfigurationService.cs b/OpenKh.Tools.ModsManager/Services/ConfigurationService.cs index 5ab5c2d09..8c9c85045 100644 --- a/OpenKh.Tools.ModsManager/Services/ConfigurationService.cs +++ b/OpenKh.Tools.ModsManager/Services/ConfigurationService.cs @@ -40,12 +40,16 @@ private class Config public bool PanaceaInstalled { get; internal set; } public bool ShowConsole { get; internal set; } = false; public bool DebugLog { get; internal set; } = false; + public bool SoundDebug { get; internal set; } = false; public bool EnableCache { get; internal set; } = true; public bool QuickMenu { get; internal set; } = false; public bool DevView { get; internal set; } = false; public bool AutoUpdateMods { get; internal set; } - public bool isEGSVersion { get; internal set; } = true; + public string pcVersion { get; internal set; } = "EGS"; + public bool steamAPITrick1525 { get; internal set; } = false; + public bool steamAPITrick28 { get; internal set; } = false; public List GamesToExtract { get; internal set; } = new List { "kh2" }; + public bool SkipRemastered { get; internal set; } = false; public string LaunchGame { get; internal set; } = "kh2"; public bool DarkMode { get; internal set; } = true; public List YamlGenPrefs { get; internal set; } = new List(); @@ -315,6 +319,15 @@ public static bool DebugLog _config.Save(ConfigPath); } } + public static bool SoundDebug + { + get => _config.SoundDebug; + set + { + _config.SoundDebug = value; + _config.Save(ConfigPath); + } + } public static bool EnableCache { get => _config.EnableCache; @@ -351,12 +364,30 @@ public static bool AutoUpdateMods _config.Save(ConfigPath); } } - public static bool IsEGSVersion + public static string PCVersion + { + get => _config.pcVersion; + set + { + _config.pcVersion = value; + _config.Save(ConfigPath); + } + } + public static bool SteamAPITrick1525 { - get => _config.isEGSVersion; + get => _config.steamAPITrick1525; set { - _config.isEGSVersion = value; + _config.steamAPITrick1525 = value; + _config.Save(ConfigPath); + } + } + public static bool SteamAPITrick28 + { + get => _config.steamAPITrick28; + set + { + _config.steamAPITrick28 = value; _config.Save(ConfigPath); } } @@ -440,6 +471,15 @@ public static bool Extractkh3d _config.Save(ConfigPath); } } + public static bool SkipRemastered + { + get => _config.SkipRemastered; + set + { + _config.SkipRemastered = value; + _config.Save(ConfigPath); + } + } public static string LaunchGame { get => _config.LaunchGame; diff --git a/OpenKh.Tools.ModsManager/Services/GameDataExtractionService.cs b/OpenKh.Tools.ModsManager/Services/GameDataExtractionService.cs new file mode 100644 index 000000000..5517eb1bb --- /dev/null +++ b/OpenKh.Tools.ModsManager/Services/GameDataExtractionService.cs @@ -0,0 +1,290 @@ +using OpenKh.Common; +using OpenKh.Kh1; +using OpenKh.Kh2; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Xe.IO; + +namespace OpenKh.Tools.ModsManager.Services +{ + public class GameDataExtractionService + { + private const int BufferSize = 65536; + private const string REMASTERED_FILES_FOLDER_NAME = "remastered"; + + public class BadConfigurationException : Exception + { + public BadConfigurationException(string message) : base(message) + { + + } + } + + public async Task ExtractKh2Ps2EditionAsync( + string isoLocation, + string gameDataLocation, + Action onProgress) + { + var fileBlocks = File.OpenRead(isoLocation).Using(stream => + { + var bufferedStream = new BufferedStream(stream); + var idxBlock = IsoUtility.GetFileOffset(bufferedStream, "KH2.IDX;1"); + var imgBlock = IsoUtility.GetFileOffset(bufferedStream, "KH2.IMG;1"); + return (idxBlock, imgBlock); + }); + + if (fileBlocks.idxBlock == -1 || fileBlocks.imgBlock == -1) + { + throw new BadConfigurationException( + $"Unable to find the files KH2.IDX and KH2.IMG in the ISO at '{isoLocation}'. The extraction will stop." + ); + } + + onProgress(0); + + await Task.Run(() => + { + using var isoStream = File.OpenRead(isoLocation); + + var idxOffset = fileBlocks.idxBlock * 0x800L; + var idx = Idx.Read(new SubStream(isoStream, idxOffset, isoStream.Length - idxOffset)); + + var imgOffset = fileBlocks.imgBlock * 0x800L; + var imgStream = new SubStream(isoStream, imgOffset, isoStream.Length - imgOffset); + var img = new Img(imgStream, idx, true); + + var fileCount = img.Entries.Count; + var fileProcessed = 0; + foreach (var fileEntry in img.Entries) + { + var fileName = IdxName.Lookup(fileEntry) ?? $"@{fileEntry.Hash32:08X}_{fileEntry.Hash16:04X}"; + using var stream = img.FileOpen(fileEntry); + var fileDestination = Path.Combine(gameDataLocation, "kh2", fileName); + var directoryDestination = Path.GetDirectoryName(fileDestination); + if (!Directory.Exists(directoryDestination)) + { + Directory.CreateDirectory(directoryDestination); + } + File.Create(fileDestination).Using(dstStream => stream.CopyTo(dstStream, BufferSize)); + + fileProcessed++; + onProgress((float)fileProcessed / fileCount); + } + + onProgress(1.0f); + }); + } + + public async Task ExtractKhPcEditionAsync( + string gameDataLocation, + Action onProgress, + Func getKhFilePath, + Func getKh3dFilePath, + bool extractkh1, + bool extractkh2, + bool extractbbs, + bool extractrecom, + bool extractkh3d, + Func> ifRetry, + CancellationToken cancellationToken) + { + await Task.Run(async () => + { + var _nameListkh1 = new string[] + { + "first", + "second", + "third", + "fourth", + "fifth" + }; + var _nameListkh2 = new string[] + { + "first", + "second", + "third", + "fourth", + "fifth", + "sixth" + }; + var _nameListbbs = new string[] + { + "first", + "second", + "third", + "fourth" + }; + var _nameListkh3d = new string[] + { + "first", + "second", + "third", + "fourth" + }; + + var _totalFiles = 0; + var _procTotalFiles = 0; + + onProgress(0); + + if (extractkh1) + { + for (int i = 0; i < 5; i++) + { + using var _stream = new FileStream(getKhFilePath("kh1_" + _nameListkh1[i] + ".hed"), System.IO.FileMode.Open); + var _hedFile = OpenKh.Egs.Hed.Read(_stream); + _totalFiles += _hedFile.Count(); + } + } + if (extractkh2) + { + for (int i = 0; i < 6; i++) + { + using var _stream = new FileStream(getKhFilePath("kh2_" + _nameListkh2[i] + ".hed"), System.IO.FileMode.Open); + var _hedFile = OpenKh.Egs.Hed.Read(_stream); + _totalFiles += _hedFile.Count(); + } + } + if (extractbbs) + { + for (int i = 0; i < 4; i++) + { + using var _stream = new FileStream(getKhFilePath("bbs_" + _nameListbbs[i] + ".hed"), System.IO.FileMode.Open); + var _hedFile = OpenKh.Egs.Hed.Read(_stream); + _totalFiles += _hedFile.Count(); + } + } + if (extractrecom) + { + using var _stream = new FileStream(getKhFilePath("Recom.hed"), System.IO.FileMode.Open); + var _hedFile = OpenKh.Egs.Hed.Read(_stream); + _totalFiles += _hedFile.Count(); + } + if (extractkh3d) + { + for (int i = 0; i < 4; i++) + { + using var _stream = new FileStream(getKh3dFilePath("kh3d_" + _nameListbbs[i] + ".hed"), System.IO.FileMode.Open); + var _hedFile = OpenKh.Egs.Hed.Read(_stream); + _totalFiles += _hedFile.Count(); + } + } + + async Task ProcessHedStreamAsync(string outputDir, Stream hedStream, Stream img) + { + await Task.Yield(); + + foreach (var entry in OpenKh.Egs.Hed.Read(hedStream)) + { + cancellationToken.ThrowIfCancellationRequested(); + + retry: + try + { + var hash = OpenKh.Egs.Helpers.ToString(entry.MD5); + if (!OpenKh.Egs.EgsTools.Names.TryGetValue(hash, out var fileName)) + fileName = $"{hash}.dat"; + + var outputFileName = Path.Combine(outputDir, fileName); + + OpenKh.Egs.EgsTools.CreateDirectoryForFile(outputFileName); + + var hdAsset = new OpenKh.Egs.EgsHdAsset(img.SetPosition(entry.Offset)); + + File.Create(outputFileName).Using(stream => stream.Write(hdAsset.OriginalData)); + + outputFileName = Path.Combine(outputDir, REMASTERED_FILES_FOLDER_NAME, fileName); + + if (!ConfigurationService.SkipRemastered) + { + + foreach (var asset in hdAsset.Assets) + { + var outputFileNameRemastered = Path.Combine(OpenKh.Egs.EgsTools.GetHDAssetFolder(outputFileName), asset); + + OpenKh.Egs.EgsTools.CreateDirectoryForFile(outputFileNameRemastered); + + var assetData = hdAsset.RemasteredAssetsDecompressedData[asset]; + File.Create(outputFileNameRemastered).Using(stream => stream.Write(assetData)); + } + } + } + catch (Exception ex) + { + if (await ifRetry(ex)) + { + goto retry; + } + } + + _procTotalFiles++; + + onProgress((float)_procTotalFiles / _totalFiles); + } + } + + if (extractkh1) + { + for (int i = 0; i < 5; i++) + { + var outputDir = Path.Combine(gameDataLocation, "kh1"); + using var hedStream = File.OpenRead(getKhFilePath("kh1_" + _nameListkh1[i] + ".hed")); + using var img = File.OpenRead(getKhFilePath("kh1_" + _nameListkh1[i] + ".pkg")); + + await ProcessHedStreamAsync(outputDir, hedStream, img); + } + } + if (extractkh2) + { + for (int i = 0; i < 6; i++) + { + var outputDir = Path.Combine(gameDataLocation, "kh2"); + using var hedStream = File.OpenRead(getKhFilePath("kh2_" + _nameListkh2[i] + ".hed")); + using var img = File.OpenRead(getKhFilePath("kh2_" + _nameListkh2[i] + ".pkg")); + + await ProcessHedStreamAsync(outputDir, hedStream, img); + } + } + if (extractbbs) + { + for (int i = 0; i < 4; i++) + { + var outputDir = Path.Combine(gameDataLocation, "bbs"); + using var hedStream = File.OpenRead(getKhFilePath("bbs_" + _nameListbbs[i] + ".hed")); + using var img = File.OpenRead(getKhFilePath("bbs_" + _nameListbbs[i] + ".pkg")); + + await ProcessHedStreamAsync(outputDir, hedStream, img); + } + } + if (extractrecom) + { + for (int i = 0; i < 1; i++) + { + var outputDir = Path.Combine(gameDataLocation, "Recom"); + using var hedStream = File.OpenRead(getKhFilePath("Recom.hed")); + using var img = File.OpenRead(getKhFilePath("Recom.pkg")); + + await ProcessHedStreamAsync(outputDir, hedStream, img); + } + } + if (extractkh3d) + { + for (int i = 0; i < 4; i++) + { + var outputDir = Path.Combine(gameDataLocation, "kh3d"); + using var hedStream = File.OpenRead(getKh3dFilePath("kh3d_" + _nameListkh3d[i] + ".hed")); + using var img = File.OpenRead(getKh3dFilePath("kh3d_" + _nameListkh3d[i] + ".pkg")); + + await ProcessHedStreamAsync(outputDir, hedStream, img); + } + } + onProgress(1); + }); + } + } +} diff --git a/OpenKh.Tools.ModsManager/Services/ModsService.cs b/OpenKh.Tools.ModsManager/Services/ModsService.cs index fd0347020..48c2a780c 100644 --- a/OpenKh.Tools.ModsManager/Services/ModsService.cs +++ b/OpenKh.Tools.ModsManager/Services/ModsService.cs @@ -155,7 +155,7 @@ public static void InstallModFromZip( break; } } - + Directory.CreateDirectory(modPath); var entryExtractCount = 0; @@ -312,7 +312,7 @@ public static async Task InstallModFromGithub( break; } } - + Directory.CreateDirectory(modPath); progressOutput?.Invoke($"Mod found, initializing cloning process"); @@ -384,13 +384,13 @@ public static void InstallModFromLua(string fileName) switch (_lineLead) { case "LUAGUI_NAME": - modName = _lineGib; + modName = "\"" + _lineGib + "\""; break; case "LUAGUI_AUTH": - modAuthor = _lineGib; + modAuthor = "\"" + _lineGib + "\""; break; case "LUAGUI_DESC": - modDescription = _lineGib; + modDescription = "\"" + _lineGib + "\""; break; } @@ -428,7 +428,7 @@ public static IEnumerable GetMods(IEnumerable modNames) Metadata = File.OpenRead(Path.Combine(modPath, ModMetadata)).Using(Metadata.Read), IsEnabled = enabledMods.Contains(modName) }; - } + } } public static async IAsyncEnumerable FetchUpdates() @@ -484,7 +484,8 @@ public static Task RunPacherAsync(bool fastMode) => Task.Run(() => Handle( ConfigurationService.GameEdition, fastMode, packageMap, - ConfigurationService.LaunchGame); + ConfigurationService.LaunchGame, + ConfigurationService.PcReleaseLanguage); } using var packageMapWriter = new StreamWriter(Path.Combine(Path.Combine(ConfigurationService.GameModPath, ConfigurationService.LaunchGame), "patch-package-map.txt")); diff --git a/OpenKh.Tools.ModsManager/Styles/MonoStyleCheckBox.xaml b/OpenKh.Tools.ModsManager/Styles/MonoStyleCheckBox.xaml new file mode 100644 index 000000000..2c23bc624 --- /dev/null +++ b/OpenKh.Tools.ModsManager/Styles/MonoStyleCheckBox.xaml @@ -0,0 +1,130 @@ + + + diff --git a/OpenKh.Tools.ModsManager/ViewModels/MainViewModel.cs b/OpenKh.Tools.ModsManager/ViewModels/MainViewModel.cs index 563f1fd17..bd4843114 100644 --- a/OpenKh.Tools.ModsManager/ViewModels/MainViewModel.cs +++ b/OpenKh.Tools.ModsManager/ViewModels/MainViewModel.cs @@ -41,6 +41,7 @@ public class MainViewModel : BaseNotifyPropertyChanged, IChangeModEnableState private bool _panaceaInstalled; private bool _panaceaConsoleEnabled; private bool _panaceaDebugLogEnabled; + private bool _panaceaSoundDebugEnabled; private bool _panaceaCacheEnabled; private bool _panaceaQuickMenuEnabled; private bool _devView; @@ -142,10 +143,24 @@ public bool PanaceaDebugLogEnabled { _panaceaDebugLogEnabled = value; ConfigurationService.DebugLog = _panaceaDebugLogEnabled; + if (_panaceaSoundDebugEnabled) + PanaceaSoundDebugEnabled = false; OnPropertyChanged(nameof(PanaceaDebugLogEnabled)); UpdatePanaceaSettings(); } } + public bool PanaceaSoundDebugEnabled + { + get => _panaceaSoundDebugEnabled; + set + { + _panaceaSoundDebugEnabled = value; + ConfigurationService.SoundDebug = _panaceaSoundDebugEnabled; + OnPropertyChanged(nameof(PanaceaSoundDebugEnabled)); + UpdatePanaceaSettings(); + } + + } public bool PanaceaCacheEnabled { get => _panaceaCacheEnabled; @@ -299,6 +314,7 @@ public MainViewModel() DevView = ConfigurationService.DevView; _panaceaConsoleEnabled = ConfigurationService.ShowConsole; _panaceaDebugLogEnabled = ConfigurationService.DebugLog; + _panaceaSoundDebugEnabled = ConfigurationService.SoundDebug; _panaceaCacheEnabled = ConfigurationService.EnableCache; _panaceaQuickMenuEnabled = ConfigurationService.QuickMenu; } @@ -462,10 +478,8 @@ await ModsService.InstallMod(name, isZipFile, isLuaFile, progress => ConfigPcsx2Location = ConfigurationService.Pcsx2Location, ConfigPcReleaseLocation = ConfigurationService.PcReleaseLocation, ConfigPcReleaseLocationKH3D = ConfigurationService.PcReleaseLocationKH3D, - ConfigPcReleaseLanguage = ConfigurationService.PcReleaseLanguage, ConfigRegionId = ConfigurationService.RegionId, - ConfigPanaceaInstalled = ConfigurationService.PanaceaInstalled, - ConfigIsEGSVersion = ConfigurationService.IsEGSVersion, + ConfigPanaceaInstalled = ConfigurationService.PanaceaInstalled, }; if (dialog.ShowDialog() == true) { @@ -478,7 +492,6 @@ await ModsService.InstallMod(name, isZipFile, isLuaFile, progress => ConfigurationService.PcReleaseLocationKH3D = dialog.ConfigPcReleaseLocationKH3D; ConfigurationService.RegionId = dialog.ConfigRegionId; ConfigurationService.PanaceaInstalled = dialog.ConfigPanaceaInstalled; - ConfigurationService.IsEGSVersion = dialog.ConfigIsEGSVersion; ConfigurationService.WizardVersionNumber = _wizardVersionNumber; const int EpicGamesPC = 2; @@ -630,7 +643,7 @@ private Task RunGame() isPcsx2 = true; break; case 2: - if (ConfigurationService.IsEGSVersion && !(_launchGame == "kh3d")) + if (ConfigurationService.PCVersion == "EGS" && !(_launchGame == "kh3d")) { if (ConfigurationService.PcReleaseLocation != null) { @@ -653,6 +666,9 @@ private Task RunGame() FileName = "com.epicgames.launcher://apps/4158b699dd70447a981fee752d970a3e%3A5aac304f0e8948268ddfd404334dbdc7%3A68c214c58f694ae88c2dab6f209b43e4?action=launch&silent=true", UseShellExecute = true, }; + Process.Start(processStartInfo); + CloseAllWindows(); + return Task.CompletedTask; } else { @@ -663,7 +679,7 @@ private Task RunGame() return Task.CompletedTask; } } - else if (ConfigurationService.IsEGSVersion && _launchGame == "kh3d") + else if (ConfigurationService.PCVersion == "EGS" && _launchGame == "kh3d") { if (ConfigurationService.PcReleaseLocationKH3D != null) { @@ -691,6 +707,86 @@ private Task RunGame() FileName = "com.epicgames.launcher://apps/c8ff067c1c984cd7ab1998e8a9afc8b6%3Aaa743b9f52e84930b0ba1b701951e927%3Ad1a8f7c478d4439b8c60a5808715dc05?action=launch&silent=true", UseShellExecute = true, }; + Process.Start(processStartInfo); + CloseAllWindows(); + return Task.CompletedTask; + } + else + { + MessageBox.Show( + "Unable to locate KINGDOM HEARTS HD 2.8 Final Chapter Prologue install. Please re-run the setup wizard and confirm it is correct.", + "Run error", MessageBoxButton.OK, MessageBoxImage.Error); + CloseAllWindows(); + return Task.CompletedTask; + } + } + if (ConfigurationService.PCVersion == "Steam" && !(_launchGame == "kh3d") && ConfigurationService.SteamAPITrick1525 == false) + { + if (ConfigurationService.PcReleaseLocation != null) + { + if (ConfigurationService.PanaceaInstalled) + { + string panaceaSettings = Path.Combine(ConfigurationService.PcReleaseLocation, "panacea_settings.txt"); + if (!File.Exists(panaceaSettings)) + { + File.WriteAllLines(Path.Combine(ConfigurationService.PcReleaseLocation, "panacea_settings.txt"), + new string[] + { + $"mod_path={ConfigurationService.GameModPath}", + $"show_console={false}", + }); + } + File.AppendAllText(panaceaSettings, "\nquick_launch=" + _launchGame); + } + processStartInfo = new ProcessStartInfo + { + FileName = "steam://rungameid/2552430", + UseShellExecute = true, + }; + Process.Start(processStartInfo); + CloseAllWindows(); + return Task.CompletedTask; + } + else + { + MessageBox.Show( + "Unable to locate KINGDOM HEARTS HD 1.5+2.5 ReMIX install. Please re-run the setup wizard and confirm it is correct.", + "Run error", MessageBoxButton.OK, MessageBoxImage.Error); + CloseAllWindows(); + return Task.CompletedTask; + } + } + else if (ConfigurationService.PCVersion == "Steam" && _launchGame == "kh3d" && ConfigurationService.SteamAPITrick28 == false) + { + if (ConfigurationService.PcReleaseLocationKH3D != null) + { + string panaceaSettings = Path.Combine(ConfigurationService.PcReleaseLocationKH3D, "panacea_settings.txt"); + if (ConfigurationService.PanaceaInstalled) + { + if (!File.Exists(panaceaSettings)) + { + if (Directory.Exists(ConfigurationService.PcReleaseLocationKH3D)) + { + File.WriteAllLines(Path.Combine(ConfigurationService.PcReleaseLocationKH3D, "panacea_settings.txt"), + new string[] + { + $"mod_path={ConfigurationService.GameModPath}", + $"show_console={false}", + }); + } + else + File.Create(panaceaSettings); + } + File.AppendAllText(panaceaSettings, "\nquick_launch=" + _launchGame); + } + processStartInfo = new ProcessStartInfo + { + FileName = "steam://rungameid/2552440", + UseShellExecute = true, + }; + Process.Start(processStartInfo); + CloseAllWindows(); + return Task.CompletedTask; } else { @@ -739,9 +835,6 @@ private Task RunGame() return Task.CompletedTask; } } - Process.Start(processStartInfo); - CloseAllWindows(); - return Task.CompletedTask; default: return Task.CompletedTask; } @@ -863,8 +956,11 @@ await Task.Run(() => { var sourceFile = Path.Combine(ConfigurationService.GameModPath, _launchGame, entry.Key); var destFile = Path.Combine(patchStagingDir, entry.Value); - Directory.CreateDirectory(Path.GetDirectoryName(destFile)); - File.Move(sourceFile, destFile); + if (File.Exists(sourceFile)) + { + Directory.CreateDirectory(Path.GetDirectoryName(destFile)); + File.Move(sourceFile, destFile); + } } foreach (var directory in Directory.GetDirectories(Path.Combine(ConfigurationService.GameModPath, _launchGame))) @@ -931,12 +1027,12 @@ await Task.Run(() => string _backupDir = null; if (_launchGame != "kh3d" && ConfigurationService.PcReleaseLocation != null) { - _pkgName = Path.Combine(ConfigurationService.PcReleaseLocation, "Image", ConfigurationService.PcReleaseLanguage, _pkgSoft + ".pkg"); + _pkgName = Path.Combine(ConfigurationService.PcReleaseLocation, "Image", ConfigurationService.PCVersion == "Steam" && ConfigurationService.PcReleaseLanguage == "en" ? "dt" : ConfigurationService.PcReleaseLanguage, _pkgSoft + ".pkg"); _backupDir = Path.Combine(ConfigurationService.PcReleaseLocation, "BackupImage"); } else if (ConfigurationService.PcReleaseLocationKH3D != null) { - _pkgName = Path.Combine(ConfigurationService.PcReleaseLocationKH3D, "Image", ConfigurationService.PcReleaseLanguage, _pkgSoft + ".pkg"); + _pkgName = Path.Combine(ConfigurationService.PcReleaseLocationKH3D, "Image", ConfigurationService.PCVersion == "Steam" && ConfigurationService.PcReleaseLanguage == "en" ? "dt" : ConfigurationService.PcReleaseLanguage, _pkgSoft + ".pkg"); _backupDir = Path.Combine(ConfigurationService.PcReleaseLocationKH3D, "BackupImage"); } else @@ -1050,7 +1146,7 @@ await Task.Run(() => Log.Info($"Restoring Package File {file.Replace(".pkg", "")}"); var _fileBare = Path.GetFileName(file); - var _trueName = Path.Combine(ConfigurationService.PcReleaseLocation, "Image", ConfigurationService.PcReleaseLanguage, _fileBare); + var _trueName = Path.Combine(ConfigurationService.PcReleaseLocation, "Image", ConfigurationService.PCVersion == "Steam" && ConfigurationService.PcReleaseLanguage == "en" ? "dt" : ConfigurationService.PcReleaseLanguage, _fileBare); File.Delete(Path.ChangeExtension(_trueName, "hed")); File.Delete(_trueName); @@ -1074,7 +1170,7 @@ await Task.Run(() => Log.Info($"Restoring Package File {file.Replace(".pkg", "")}"); var _fileBare = Path.GetFileName(file); - var _trueName = Path.Combine(ConfigurationService.PcReleaseLocationKH3D, "Image", ConfigurationService.PcReleaseLanguage, _fileBare); + var _trueName = Path.Combine(ConfigurationService.PcReleaseLocationKH3D, "Image", ConfigurationService.PCVersion == "Steam" && ConfigurationService.PcReleaseLanguage == "en" ? "dt" : ConfigurationService.PcReleaseLanguage, _fileBare); File.Delete(Path.ChangeExtension(_trueName, "hed")); File.Delete(_trueName); @@ -1193,7 +1289,8 @@ public void UpdatePanaceaSettings() } } textToWrite += $"\r\nshow_console={_panaceaConsoleEnabled}\r\n" + - $"debug_log={_panaceaDebugLogEnabled}\r\nenable_cache={_panaceaCacheEnabled}\r\nquick_menu={_panaceaQuickMenuEnabled}"; + $"debug_log={_panaceaDebugLogEnabled}\r\nsound_debug={_panaceaSoundDebugEnabled}\r\n" + + $"enable_cache={_panaceaCacheEnabled}\r\nquick_menu={_panaceaQuickMenuEnabled}"; File.WriteAllText(panaceaSettings, textToWrite); } else if (ConfigurationService.PcReleaseLocationKH3D != null) @@ -1210,7 +1307,8 @@ public void UpdatePanaceaSettings() } } textToWrite += $"\r\nshow_console={_panaceaConsoleEnabled}\r\n" + - $"debug_log={_panaceaDebugLogEnabled}\r\nenable_cache={_panaceaCacheEnabled}\r\nquick_menu={_panaceaQuickMenuEnabled}"; + $"debug_log={_panaceaDebugLogEnabled}\r\nsound_debug={_panaceaSoundDebugEnabled}\r\n" + + $"enable_cache={_panaceaCacheEnabled}\r\nquick_menu={_panaceaQuickMenuEnabled}"; File.WriteAllText(panaceaSettings, textToWrite); } } diff --git a/OpenKh.Tools.ModsManager/ViewModels/SetupWizardViewModel.cs b/OpenKh.Tools.ModsManager/ViewModels/SetupWizardViewModel.cs index 69a4cc09d..e4bb468a5 100644 --- a/OpenKh.Tools.ModsManager/ViewModels/SetupWizardViewModel.cs +++ b/OpenKh.Tools.ModsManager/ViewModels/SetupWizardViewModel.cs @@ -17,6 +17,9 @@ using Xe.Tools.Wpf.Dialogs; using Ionic.Zip; using System.Diagnostics; +using System.Text.RegularExpressions; +using System.Threading; +using System.Runtime.ExceptionServices; namespace OpenKh.Tools.ModsManager.ViewModels { @@ -24,6 +27,7 @@ public class SetupWizardViewModel : BaseNotifyPropertyChanged { public ColorThemeService ColorTheme => ColorThemeService.Instance; private const int BufferSize = 65536; + private readonly GameDataExtractionService _gameDataExtractionService = new GameDataExtractionService(); private static readonly string PanaceaDllName = "OpenKH.Panacea.dll"; private static string ApplicationName = Utilities.GetApplicationName(); private static List _isoFilter = FileDialogFilterComposer @@ -38,9 +42,10 @@ public class SetupWizardViewModel : BaseNotifyPropertyChanged const int OpenKHGameEngine = 0; const int PCSX2 = 1; - const int EpicGames = 2; + const int PC = 2; private int _gameEdition; + private string _pcVersion; private string _isoLocation; private string _openKhGameEngineLocation; private string _pcsx2Location; @@ -50,7 +55,6 @@ public class SetupWizardViewModel : BaseNotifyPropertyChanged private int _gameCollection = 0; private string _pcReleaseLanguage; private string _gameDataLocation; - private bool _isEGSVersion; private List LuaScriptPaths = new List(); private bool _overrideGameDataFound = false; @@ -74,12 +78,21 @@ private set OnPropertyChanged(); } } + private Xceed.Wpf.Toolkit.WizardPage _wizardPageAfterLuaBackend; + public Xceed.Wpf.Toolkit.WizardPage WizardPageAfterLuaBackend + { + get => _wizardPageAfterLuaBackend; + private set + { + _wizardPageAfterLuaBackend = value; + OnPropertyChanged(); + } + } public Xceed.Wpf.Toolkit.WizardPage PageIsoSelection { get; internal set; } public Xceed.Wpf.Toolkit.WizardPage PageEosInstall { get; internal set; } - public Xceed.Wpf.Toolkit.WizardPage PageEosConfig { get; internal set; } - public Xceed.Wpf.Toolkit.WizardPage PageLuaBackendInstall { get; internal set; } + public Xceed.Wpf.Toolkit.WizardPage PageSteamAPITrick { get; internal set; } public Xceed.Wpf.Toolkit.WizardPage PageRegion { get; internal set; } - public Xceed.Wpf.Toolkit.WizardPage PCLaunchOption { get; internal set; } + public Xceed.Wpf.Toolkit.WizardPage PageGameData { get; internal set; } public Xceed.Wpf.Toolkit.WizardPage LastPage { get; internal set; } public WizardPageStackService PageStack { get; set; } = new WizardPageStackService(); @@ -136,12 +149,14 @@ public bool IsGameSelected { OpenKHGameEngine => !string.IsNullOrEmpty(OpenKhGameEngineLocation) && File.Exists(OpenKhGameEngineLocation), PCSX2 => !string.IsNullOrEmpty(Pcsx2Location) && File.Exists(Pcsx2Location), - EpicGames => (!string.IsNullOrEmpty(PcReleaseLocation) && + PC => (!string.IsNullOrEmpty(PcReleaseLocation) && Directory.Exists(PcReleaseLocation) && - File.Exists(Path.Combine(PcReleaseLocation, "EOSSDK-Win64-Shipping.dll")))|| + (File.Exists(Path.Combine(PcReleaseLocation, "EOSSDK-Win64-Shipping.dll")) || + File.Exists(Path.Combine(PcReleaseLocation, "steam_api64.dll"))))|| (!string.IsNullOrEmpty(PcReleaseLocationKH3D) && Directory.Exists(PcReleaseLocationKH3D) && - File.Exists(Path.Combine(PcReleaseLocationKH3D, "EOSSDK-Win64-Shipping.dll"))), + (File.Exists(Path.Combine(PcReleaseLocationKH3D, "EOSSDK-Win64-Shipping.dll")) || + File.Exists(Path.Combine(PcReleaseLocationKH3D, "steam_api64.dll")))), _ => false, }; } @@ -157,14 +172,14 @@ public int GameEdition { OpenKHGameEngine => LastPage, PCSX2 => PageIsoSelection, - EpicGames => PageEosInstall, + PC => PageEosInstall, _ => null, }; WizardPageAfterGameData = GameEdition switch { OpenKHGameEngine => LastPage, PCSX2 => PageRegion, - EpicGames => LastPage, + PC => LastPage, _ => null, }; @@ -173,6 +188,78 @@ public int GameEdition OnPropertyChanged(nameof(OpenKhGameEngineConfigVisibility)); OnPropertyChanged(nameof(Pcsx2ConfigVisibility)); OnPropertyChanged(nameof(PcReleaseConfigVisibility)); + OnPropertyChanged(nameof(BothPcReleaseSelected)); + OnPropertyChanged(nameof(PcRelease1525Selected)); + OnPropertyChanged(nameof(PcRelease28Selected)); + } + } + public int PCReleaseLanguage + { + get + { + switch (ConfigurationService.PcReleaseLanguage) + { + case "jp": + _pcReleaseLanguage = "jp"; + return 1; + default: + _pcReleaseLanguage = "en"; + return 0; + } + } + set + { + switch (value) + { + case 1: + ConfigurationService.PcReleaseLanguage = "jp"; + _pcReleaseLanguage = "jp"; + break; + default: + ConfigurationService.PcReleaseLanguage = "en"; + _pcReleaseLanguage = "en"; + break; + } + } + } + + public int LaunchOption + { + get + { + switch (ConfigurationService.PCVersion) + { + case "Steam": + WizardPageAfterLuaBackend = PageSteamAPITrick; + return 1; + case "Other": + WizardPageAfterLuaBackend = PageGameData; + return 2; + default: + WizardPageAfterLuaBackend = PageGameData; + return 0; + } + } + set + { + switch (value) + { + case 1: + _pcVersion = "Steam"; + ConfigurationService.PCVersion = "Steam"; + WizardPageAfterLuaBackend = PageSteamAPITrick; + break; + case 2: + _pcVersion = "Other"; + ConfigurationService.PCVersion = "Other"; + WizardPageAfterLuaBackend = PageGameData; + break; + default: + _pcVersion = "EGS"; + ConfigurationService.PCVersion = "EGS"; + WizardPageAfterLuaBackend = PageGameData; + break; + } } } public RelayCommand SelectOpenKhGameEngineCommand { get; } @@ -202,7 +289,7 @@ public string Pcsx2Location } public RelayCommand SelectPcReleaseCommand { get; } - public Visibility PcReleaseConfigVisibility => GameEdition == EpicGames ? Visibility.Visible : Visibility.Collapsed; + public Visibility PcReleaseConfigVisibility => GameEdition == PC ? Visibility.Visible : Visibility.Collapsed; public Visibility BothPcReleaseSelected => PcReleaseSelections == "both" ? Visibility.Visible : Visibility.Collapsed; public Visibility PcRelease1525Selected => PcReleaseSelections == "1.5+2.5" ? Visibility.Visible: Visibility.Collapsed; public Visibility PcRelease28Selected => PcReleaseSelections == "2.8" ? Visibility.Visible : Visibility.Collapsed; @@ -233,17 +320,21 @@ public string PcReleaseSelections { get { - if (Directory.Exists(PcReleaseLocation) && File.Exists(Path.Combine(PcReleaseLocation, "EOSSDK-Win64-Shipping.dll")) && - Directory.Exists(PcReleaseLocationKH3D) && File.Exists(Path.Combine(PcReleaseLocationKH3D, "EOSSDK-Win64-Shipping.dll"))) + if (Directory.Exists(PcReleaseLocation) && (File.Exists(Path.Combine(PcReleaseLocation, "EOSSDK-Win64-Shipping.dll")) || + File.Exists(Path.Combine(PcReleaseLocation, "steam_api64.dll"))) && + Directory.Exists(PcReleaseLocationKH3D) && (File.Exists(Path.Combine(PcReleaseLocationKH3D, "EOSSDK-Win64-Shipping.dll")) || + File.Exists(Path.Combine(PcReleaseLocationKH3D, "steam_api64.dll"))) && _gameEdition == 2) { return _pcReleasesSelected = "both"; } - else if (Directory.Exists(PcReleaseLocation) && File.Exists(Path.Combine(PcReleaseLocation, "EOSSDK-Win64-Shipping.dll"))) + else if (Directory.Exists(PcReleaseLocation) && (File.Exists(Path.Combine(PcReleaseLocation, "EOSSDK-Win64-Shipping.dll")) || + File.Exists(Path.Combine(PcReleaseLocation, "steam_api64.dll"))) && _gameEdition == 2) { return _pcReleasesSelected = "1.5+2.5"; } - else if (Directory.Exists(PcReleaseLocationKH3D) && File.Exists(Path.Combine(PcReleaseLocationKH3D, "EOSSDK-Win64-Shipping.dll"))) + else if (Directory.Exists(PcReleaseLocationKH3D) && (File.Exists(Path.Combine(PcReleaseLocationKH3D, "EOSSDK-Win64-Shipping.dll")) || + File.Exists(Path.Combine(PcReleaseLocationKH3D, "steam_api64.dll"))) && _gameEdition == 2) { return _pcReleasesSelected = "2.8"; @@ -291,14 +382,6 @@ public string PcReleaseLocationKH3D OnPropertyChanged(nameof(InstallForPc28)); } } - public bool IsEGSVersion - { - get => _isEGSVersion; - set - { - _isEGSVersion = value; - } - } public bool Extractkh1 { get => ConfigurationService.Extractkh1; @@ -324,6 +407,11 @@ public bool Extractkh3d get => ConfigurationService.Extractkh3d; set => ConfigurationService.Extractkh3d = value; } + public bool SkipRemastered + { + get => ConfigurationService.SkipRemastered; + set => ConfigurationService.SkipRemastered = value; + } public bool LuaConfigkh1 { get => LuaScriptPaths.Contains("kh1"); @@ -410,22 +498,7 @@ public bool OverrideGameDataFound OnPropertyChanged(nameof(GameDataNotFoundVisibility)); OnPropertyChanged(nameof(GameDataFoundVisibility)); } - - } - public string PcReleaseLanguage - { - get => _pcReleaseLanguage; - set - { - _pcReleaseLanguage = value; - OnPropertyChanged(); - OnPropertyChanged(nameof(PcReleaseLanguage)); - OnPropertyChanged(nameof(IsLastPanaceaVersionInstalled)); - OnPropertyChanged(nameof(PanaceaInstalledVisibility)); - OnPropertyChanged(nameof(PanaceaNotInstalledVisibility)); - OnPropertyChanged(nameof(IsGameSelected)); - OnPropertyChanged(nameof(IsGameDataFound)); - } + } public RelayCommand SelectGameDataLocationCommand { get; } @@ -443,9 +516,9 @@ public string GameDataLocation } public bool IsNotExtracting { get; private set; } - public bool IsGameDataFound => (IsNotExtracting && GameService.FolderContainsUniqueFile(GameId, Path.Combine(GameDataLocation, "kh2")) || - (GameEdition == EpicGames && (GameService.FolderContainsUniqueFile("kh2", Path.Combine(GameDataLocation, "kh2")) || - GameService.FolderContainsUniqueFile("kh1", Path.Combine(GameDataLocation, "kh1")) || + public bool IsGameDataFound => (IsNotExtracting && GameService.FolderContainsUniqueFile(GameId, Path.Combine(GameDataLocation, "kh2")) || + (GameEdition == PC && (GameService.FolderContainsUniqueFile("kh2", Path.Combine(GameDataLocation, "kh2")) || + GameService.FolderContainsUniqueFile("kh1", Path.Combine(GameDataLocation, "kh1")) || Directory.Exists(Path.Combine(GameDataLocation, "bbs", "message")) || Directory.Exists(Path.Combine(GameDataLocation, "Recom", "SYS"))))|| Directory.Exists(Path.Combine(GameDataLocation, "kh3d","setdata"))|| @@ -477,6 +550,8 @@ public bool IsLuaBackendInstalled return false; } } + public RelayCommand InstallSteamAPIFile { get; set; } + public RelayCommand RemoveSteamAPIFile { get; set; } public RelayCommand InstallLuaBackendCommand { get; set; } public RelayCommand RemoveLuaBackendCommand { get; set; } public Visibility LuaBackendFoundVisibility => IsLuaBackendInstalled ? Visibility.Visible : Visibility.Collapsed; @@ -512,7 +587,7 @@ public bool IsLastPanaceaVersionInstalled // DLL into the right place. So don't bother. PanaceaInstalled = true; return true; - } + } byte[] CalculateChecksum(string fileName) => System.Security.Cryptography.MD5.Create().Using(md5 => @@ -556,10 +631,10 @@ bool IsEqual(byte[] left, byte[] right) else if (File.Exists(PanaceaDestinationLocation) && File.Exists(PanaceaAlternateLocation)) { return IsEqual(CalculateChecksum(PanaceaSourceLocation), - CalculateChecksum(PanaceaDestinationLocation)) || + CalculateChecksum(PanaceaDestinationLocation)) || IsEqual(CalculateChecksum(PanaceaSourceLocation), CalculateChecksum(PanaceaAlternateLocation)); - } + } else { PanaceaInstalled = false; @@ -583,15 +658,26 @@ public SetupWizardViewModel() FileDialog.OnFolder(path => PcReleaseLocationKH3D = path)); SelectGameDataLocationCommand = new RelayCommand(_ => FileDialog.OnFolder(path => GameDataLocation = path)); - ExtractGameDataCommand = new RelayCommand(async _ => - { + ExtractGameDataCommand = new RelayCommand(async _ => + { BEGIN: try { await ExtractGameData(IsoLocation, GameDataLocation); } - - catch (IOException _ex) + catch (OperationCanceledException) + { + // user closed the dialog + } + catch (GameDataExtractionService.BadConfigurationException _ex) + { + MessageBox.Show( + _ex.Message, + "Extraction error", + MessageBoxButton.OK, + MessageBoxImage.Error); + } + catch (Exception _ex) { var _sysMessage = MessageBox.Show(_ex.Message + "\n\nWould you like to try again?", "An Exception was Caught!", MessageBoxButton.YesNo, MessageBoxImage.Warning); @@ -605,85 +691,162 @@ public SetupWizardViewModel() }); DetectInstallsCommand = new RelayCommand(_ => { - // Get ProgramData Folder Location - string programDataFolder = Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData); - string directoryPath = Path.Combine(programDataFolder, "Epic\\EpicGamesLauncher\\Data\\Manifests"); - - if (!Directory.Exists(directoryPath)) + if (ConfigurationService.PCVersion == "EGS") { - MessageBox.Show("No Game Install Locations Found\nPlease Manually Browse To Your Game Install Directory", "Failure", MessageBoxButton.OK); - return; - } + // Get ProgramData Folder Location + string programDataFolder = Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData); + string directoryPath = Path.Combine(programDataFolder, "Epic\\EpicGamesLauncher\\Data\\Manifests"); - // Get List of .item Files in C:\ProgramData\Epic\EpicGamesLauncher\Data\Manifests\ - IEnumerable itemFiles = Directory.EnumerateFiles(directoryPath, "*.item"); + if (!Directory.Exists(directoryPath)) + { + MessageBox.Show("No Game Install Locations Found\nPlease Manually Browse To Your Game Install Directory", "Failure", MessageBoxButton.OK); + return; + } - if (!itemFiles.Any()) - { - MessageBox.Show("No Game Install Locations Found\nPlease Manually Browse To Your Game Install Directory", "Failure", MessageBoxButton.OK); - return; - } + // Get List of .item Files in C:\ProgramData\Epic\EpicGamesLauncher\Data\Manifests\ + IEnumerable itemFiles = Directory.EnumerateFiles(directoryPath, "*.item"); - bool installLocationFoundRemix = false; - bool installLocationFound3D = false; - foreach (string itemFile in itemFiles) - { - // Read Each .item File and Locate Install Location For Games - using StreamReader sr = new StreamReader(itemFile); - string line; - if (sr != null) + if (!itemFiles.Any()) { - while ((line = sr.ReadLine()) is not null) + MessageBox.Show("No Game Install Locations Found\nPlease Manually Browse To Your Game Install Directory", "Failure", MessageBoxButton.OK); + return; + } + + bool installLocationFoundRemix = false; + bool installLocationFound3D = false; + foreach (string itemFile in itemFiles) + { + // Read Each .item File and Locate Install Location For Games + using StreamReader sr = new StreamReader(itemFile); + string line; + if (sr != null) { - if (line.Contains("\"LaunchExecutable\": \"KINGDOM HEARTS HD 1.5+2.5 ReMIX.exe\",")) + while ((line = sr.ReadLine()) is not null) { - while ((line = sr.ReadLine()) is not null) + if (line.Contains("\"LaunchExecutable\": \"KINGDOM HEARTS HD 1.5+2.5 ReMIX.exe\",")) { - if (line.Contains("\"InstallLocation\": \"")) + while ((line = sr.ReadLine()) is not null) { - installLocationFoundRemix = true; - int startIndex = line.IndexOf("\": \"") + 4; - int endIndex = line.IndexOf("\","); - string parsedText = line[startIndex..endIndex]; - parsedText = parsedText.Replace("\\\\", "\\"); - PcReleaseLocation = parsedText; + if (line.Contains("\"InstallLocation\": \"")) + { + installLocationFoundRemix = true; + int startIndex = line.IndexOf("\": \"") + 4; + int endIndex = line.IndexOf("\","); + string parsedText = line[startIndex..endIndex]; + parsedText = parsedText.Replace("\\\\", "\\"); + PcReleaseLocation = parsedText; + } } } - } - else if (line.Contains("\"LaunchExecutable\": \"KINGDOM HEARTS HD 2.8 Final Chapter Prologue.exe\",")) - { - while ((line = sr.ReadLine()) is not null) + else if (line.Contains("\"LaunchExecutable\": \"KINGDOM HEARTS HD 2.8 Final Chapter Prologue.exe\",")) { - if (line.Contains("\"InstallLocation\": \"")) + while ((line = sr.ReadLine()) is not null) { - installLocationFound3D = true; - int startIndex = line.IndexOf("\": \"") + 4; - int endIndex = line.IndexOf("\","); - string parsedText = line[startIndex..endIndex]; - parsedText = parsedText.Replace("\\\\", "\\"); - PcReleaseLocationKH3D = parsedText; + if (line.Contains("\"InstallLocation\": \"")) + { + installLocationFound3D = true; + int startIndex = line.IndexOf("\": \"") + 4; + int endIndex = line.IndexOf("\","); + string parsedText = line[startIndex..endIndex]; + parsedText = parsedText.Replace("\\\\", "\\"); + PcReleaseLocationKH3D = parsedText; + } } - } + } } } } + if (!installLocationFoundRemix && !installLocationFound3D) + { + MessageBox.Show("No Game Install Locations Found\nPlease Manually Browse To Your Game Install Directory", "Failure", MessageBoxButton.OK); + } + else if (!installLocationFoundRemix && installLocationFound3D) + { + MessageBox.Show("Kingdom Hearts HD 1.5+2.5: MISSING\nKingdom Hearts HD 2.8: FOUND", "Success", MessageBoxButton.OK); + } + else if (installLocationFoundRemix && !installLocationFound3D) + { + MessageBox.Show("Kingdom Hearts HD 1.5+2.5: FOUND\nKingdom Hearts HD 2.8: MISSING", "Success", MessageBoxButton.OK); + } + else + { + MessageBox.Show("Kingdom Hearts HD 1.5+2.5: FOUND\nKingdom Hearts HD 2.8: FOUND", "Success", MessageBoxButton.OK); + } } - if (!installLocationFoundRemix && !installLocationFound3D) - { - MessageBox.Show("No Game Install Locations Found\nPlease Manually Browse To Your Game Install Directory", "Failure", MessageBoxButton.OK); - } - else if (!installLocationFoundRemix && installLocationFound3D) - { - MessageBox.Show("Kingdom Hearts HD 1.5+2.5: MISSING\nKingdom Hearts HD 2.8: FOUND", "Success", MessageBoxButton.OK); - } - else if (installLocationFoundRemix && !installLocationFound3D) + else if (ConfigurationService.PCVersion == "Steam") { - MessageBox.Show("Kingdom Hearts HD 1.5+2.5: FOUND\nKingdom Hearts HD 2.8: MISSING", "Success", MessageBoxButton.OK); + // Get ProgramFilesX86 Location + string programDataFolder = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86); + string directoryPath = Path.Combine(programDataFolder, "Steam\\steamapps"); + + if (!Directory.Exists(directoryPath)) + { + MessageBox.Show("No Game Install Locations Found\nPlease Manually Browse To Your Game Install Directory", "Failure", MessageBoxButton.OK); + return; + } + + if (!Directory.Exists(directoryPath)) + { + MessageBox.Show("No Game Install Locations Found\nPlease Manually Browse To Your Game Install Directory", "Failure", MessageBoxButton.OK); + return; + } + + bool installLocationFoundRemix = false; + bool installLocationFound3D = false; + // Read the entire content of the VDF file + string vdfContent = File.ReadAllText(Path.Combine(directoryPath, "libraryfolders.vdf")); + // Define a regular expression to match "path" values + Regex regex = new Regex(@"""path""\s*""([^""]*)""", RegexOptions.IgnoreCase); + // MatchCollection to store all matches found + MatchCollection matches = regex.Matches(vdfContent); + // Iterate through matches and print out the "path" values + if (Directory.Exists(directoryPath)) + { + foreach (Match match in matches) + { + string pathValue = match.Groups[1].Value; // Group 1 is the path raw, without the key + Console.WriteLine($"Path: {pathValue}"); + string parsedText = pathValue.Replace(@"\\", @"\"); + string commonGamesDirectory = Path.Combine(parsedText, "steamapps\\common"); + if (Directory.Exists(commonGamesDirectory)) + { + string kH1525Path = Path.Combine(commonGamesDirectory, @"KINGDOM HEARTS -HD 1.5+2.5 ReMIX-"); + string kH28Path = Path.Combine(commonGamesDirectory, @"KINGDOM HEARTS HD 2.8 Final Chapter Prologue"); + if (Directory.Exists(kH1525Path)) + { + installLocationFoundRemix = true; + PcReleaseLocation = kH1525Path; + } + if (Directory.Exists(kH28Path)) + { + installLocationFound3D = true; + PcReleaseLocationKH3D = kH28Path; + } + } + } + } + if (!installLocationFoundRemix && !installLocationFound3D) + { + MessageBox.Show("No Game Install Locations Found\nPlease Manually Browse To Your Game Install Directory", "Failure", MessageBoxButton.OK); + } + else if (!installLocationFoundRemix && installLocationFound3D) + { + MessageBox.Show("Kingdom Hearts HD 1.5+2.5: MISSING\nKingdom Hearts HD 2.8: FOUND", "Success", MessageBoxButton.OK); + } + else if (installLocationFoundRemix && !installLocationFound3D) + { + MessageBox.Show("Kingdom Hearts HD 1.5+2.5: FOUND\nKingdom Hearts HD 2.8: MISSING", "Success", MessageBoxButton.OK); + } + else + { + MessageBox.Show("Kingdom Hearts HD 1.5+2.5: FOUND\nKingdom Hearts HD 2.8: FOUND", "Success", MessageBoxButton.OK); + } } else { - MessageBox.Show("Kingdom Hearts HD 1.5+2.5: FOUND\nKingdom Hearts HD 2.8: FOUND", "Success", MessageBoxButton.OK); + MessageBox.Show("Launcher \"Other\" does not support auto detect game installation. If you wish to use this feature select either EGS or Steam on the dropdown above", "Unsupported", MessageBoxButton.OK); } + }); InstallPanaceaCommand = new RelayCommand(AlternateName => { @@ -772,7 +935,7 @@ public SetupWizardViewModel() OnPropertyChanged(nameof(IsLastPanaceaVersionInstalled)); OnPropertyChanged(nameof(PanaceaInstalledVisibility)); OnPropertyChanged(nameof(PanaceaNotInstalledVisibility)); - PanaceaInstalled = true; + PanaceaInstalled = true; } }); RemovePanaceaCommand = new RelayCommand(_ => @@ -791,7 +954,7 @@ public SetupWizardViewModel() PanaceaDestinationLocation = Path.Combine(PcReleaseLocationKH3D, "DBGHELP.dll"); PanaceaAlternateLocation = Path.Combine(PcReleaseLocationKH3D, "version.dll"); PanaceaDependenciesLocation = Path.Combine(PcReleaseLocationKH3D, "dependencies"); - } + } else if (PanaceaDestinationLocation == null || PanaceaAlternateLocation == null || PanaceaDestinationLocation == null) { MessageBox.Show( @@ -822,7 +985,7 @@ public SetupWizardViewModel() OnPropertyChanged(nameof(IsLastPanaceaVersionInstalled)); OnPropertyChanged(nameof(PanaceaInstalledVisibility)); OnPropertyChanged(nameof(PanaceaNotInstalledVisibility)); - PanaceaInstalled = false; + PanaceaInstalled = false; }); InstallLuaBackendCommand = new RelayCommand(installed => { @@ -845,7 +1008,7 @@ public SetupWizardViewModel() { MessageBox.Show( $"Unable to extract \"{Path.GetFileName(DownPath)}\" as it is not a zip file. You may have to install it manually.", - "Run error", MessageBoxButton.OK, MessageBoxImage.Error); + "Run error", MessageBoxButton.OK, MessageBoxImage.Error); File.Delete(DownPath); File.Delete(TempExtractionLocation); return; @@ -858,7 +1021,7 @@ public SetupWizardViewModel() else { DestinationCollection = PcReleaseLocationKH3D; - } + } if (DestinationCollection == null) { MessageBox.Show( @@ -1079,12 +1242,12 @@ public SetupWizardViewModel() OnPropertyChanged(nameof(LuaBackendFoundVisibility)); OnPropertyChanged(nameof(LuaBackendNotFoundVisibility)); } - } + } } }); RemoveLuaBackendCommand = new RelayCommand(_ => { - + if (GameCollection == 0 && PcReleaseLocation == null || GameCollection == 1 && PcReleaseLocationKH3D == null) { MessageBox.Show( @@ -1113,386 +1276,194 @@ public SetupWizardViewModel() OnPropertyChanged(nameof(LuaBackendNotFoundVisibility)); } }); - } - - private async Task ExtractGameData(string isoLocation, string gameDataLocation) - { - switch (GameEdition) + InstallSteamAPIFile = new RelayCommand(_ => { - - default: + if (GameCollection == 0) { - var fileBlocks = File.OpenRead(isoLocation).Using(stream => + if (Directory.Exists(ConfigurationService.PcReleaseLocation)) { - var bufferedStream = new BufferedStream(stream); - var idxBlock = IsoUtility.GetFileOffset(bufferedStream, "KH2.IDX;1"); - var imgBlock = IsoUtility.GetFileOffset(bufferedStream, "KH2.IMG;1"); - return (idxBlock, imgBlock); - }); - - if (fileBlocks.idxBlock == -1 || fileBlocks.imgBlock == -1) + File.WriteAllText(Path.Combine(ConfigurationService.PcReleaseLocation, "steam_appid.txt"), "2552430"); + ConfigurationService.SteamAPITrick1525 = true; + } + } + else if (GameCollection == 1) + { + if (Directory.Exists(ConfigurationService.PcReleaseLocationKH3D)) { - MessageBox.Show( - $"Unable to find the files KH2.IDX and KH2.IMG in the ISO at '{isoLocation}'. The extraction will stop.", - "Extraction error", - MessageBoxButton.OK, - MessageBoxImage.Error); - return; + File.WriteAllText(Path.Combine(ConfigurationService.PcReleaseLocationKH3D, "steam_appid.txt"), "2552440"); + ConfigurationService.SteamAPITrick28 = true; } - - IsNotExtracting = false; - ExtractionProgress = 0; - OnPropertyChanged(nameof(IsNotExtracting)); - OnPropertyChanged(nameof(IsGameDataFound)); - OnPropertyChanged(nameof(ProgressBarVisibility)); - OnPropertyChanged(nameof(ExtractionCompleteVisibility)); - OnPropertyChanged(nameof(ExtractionProgress)); - - await Task.Run(() => + } + }); + RemoveSteamAPIFile = new RelayCommand(_ => + { + if (GameCollection == 0) + { + if (File.Exists(Path.Combine(ConfigurationService.PcReleaseLocation, "steam_appid.txt"))) { - using var isoStream = File.OpenRead(isoLocation); - - var idxOffset = fileBlocks.idxBlock * 0x800L; - var idx = Idx.Read(new SubStream(isoStream, idxOffset, isoStream.Length - idxOffset)); - - var imgOffset = fileBlocks.imgBlock * 0x800L; - var imgStream = new SubStream(isoStream, imgOffset, isoStream.Length - imgOffset); - var img = new Img(imgStream, idx, true); - - var fileCount = img.Entries.Count; - var fileProcessed = 0; - foreach (var fileEntry in img.Entries) - { - var fileName = IdxName.Lookup(fileEntry) ?? $"@{fileEntry.Hash32:08X}_{fileEntry.Hash16:04X}"; - using var stream = img.FileOpen(fileEntry); - var fileDestination = Path.Combine(gameDataLocation,"kh2", fileName); - var directoryDestination = Path.GetDirectoryName(fileDestination); - if (!Directory.Exists(directoryDestination)) - Directory.CreateDirectory(directoryDestination); - File.Create(fileDestination).Using(dstStream => stream.CopyTo(dstStream, BufferSize)); - - fileProcessed++; - ExtractionProgress = (float)fileProcessed / fileCount; - OnPropertyChanged(nameof(ExtractionProgress)); - } - - System.Windows.Application.Current.Dispatcher.Invoke(() => - { - IsNotExtracting = true; - ExtractionProgress = 1.0f; - OnPropertyChanged(nameof(IsNotExtracting)); - OnPropertyChanged(nameof(IsGameDataFound)); - OnPropertyChanged(nameof(GameDataNotFoundVisibility)); - OnPropertyChanged(nameof(GameDataFoundVisibility)); - OnPropertyChanged(nameof(ProgressBarVisibility)); - OnPropertyChanged(nameof(ExtractionCompleteVisibility)); - OnPropertyChanged(nameof(ExtractionProgress)); - }); - }); + File.Delete(Path.Combine(ConfigurationService.PcReleaseLocation, "steam_appid.txt")); + ConfigurationService.SteamAPITrick1525 = false; + } } - break; - - case EpicGames: + else if (GameCollection == 1) { - IsNotExtracting = false; - ExtractionProgress = 0; - OnPropertyChanged(nameof(IsNotExtracting)); - OnPropertyChanged(nameof(IsGameDataFound)); - OnPropertyChanged(nameof(ProgressBarVisibility)); - OnPropertyChanged(nameof(ExtractionCompleteVisibility)); - OnPropertyChanged(nameof(ExtractionProgress)); - - await Task.Run(() => + if (File.Exists(Path.Combine(ConfigurationService.PcReleaseLocationKH3D, "steam_appid.txt"))) { - var _nameListkh1 = new string[] - { - "first", - "second", - "third", - "fourth", - "fifth" - }; - var _nameListkh2 = new string[] - { - "first", - "second", - "third", - "fourth", - "fifth", - "sixth" - }; - var _nameListbbs = new string[] - { - "first", - "second", - "third", - "fourth" - }; - var _nameListkh3d = new string[] - { - "first", - "second", - "third", - "fourth" - }; - - var _totalFiles = 0; - var _procTotalFiles = 0; - - if (ConfigurationService.Extractkh1) - { - for (int i = 0; i < 5; i++) - { - using var _stream = new FileStream(Path.Combine(_pcReleaseLocation, "Image", _pcReleaseLanguage, "kh1_" + _nameListkh1[i] + ".hed"), System.IO.FileMode.Open); - var _hedFile = OpenKh.Egs.Hed.Read(_stream); - _totalFiles += _hedFile.Count(); - } - } - if (ConfigurationService.Extractkh2) - { - for (int i = 0; i < 6; i++) - { - using var _stream = new FileStream(Path.Combine(_pcReleaseLocation, "Image", _pcReleaseLanguage, "kh2_" + _nameListkh2[i] + ".hed"), System.IO.FileMode.Open); - var _hedFile = OpenKh.Egs.Hed.Read(_stream); - _totalFiles += _hedFile.Count(); - } - } - if (ConfigurationService.Extractbbs) - { - for (int i = 0; i < 4; i++) - { - using var _stream = new FileStream(Path.Combine(_pcReleaseLocation, "Image", _pcReleaseLanguage, "bbs_" + _nameListbbs[i] + ".hed"), System.IO.FileMode.Open); - var _hedFile = OpenKh.Egs.Hed.Read(_stream); - _totalFiles += _hedFile.Count(); - } - } - if (ConfigurationService.Extractrecom) - { - using var _stream = new FileStream(Path.Combine(_pcReleaseLocation, "Image", _pcReleaseLanguage, "Recom.hed"), System.IO.FileMode.Open); - var _hedFile = OpenKh.Egs.Hed.Read(_stream); - _totalFiles += _hedFile.Count(); - } - if (ConfigurationService.Extractkh3d) - { - for (int i = 0; i < 4; i++) - { - using var _stream = new FileStream(Path.Combine(_pcReleaseLocationKH3D, "Image", _pcReleaseLanguage, "kh3d_" + _nameListbbs[i] + ".hed"), System.IO.FileMode.Open); - var _hedFile = OpenKh.Egs.Hed.Read(_stream); - _totalFiles += _hedFile.Count(); - } - } - - if (ConfigurationService.Extractkh1) - { - for (int i = 0; i < 5; i++) - { - var outputDir = Path.Combine(gameDataLocation, "kh1"); - using var hedStream = File.OpenRead(Path.Combine(_pcReleaseLocation, "Image", _pcReleaseLanguage, "kh1_" + _nameListkh1[i] + ".hed")); - using var img = File.OpenRead(Path.Combine(_pcReleaseLocation, "Image", _pcReleaseLanguage, "kh1_" + _nameListkh1[i] + ".pkg")); - - foreach (var entry in OpenKh.Egs.Hed.Read(hedStream)) - { - var hash = OpenKh.Egs.Helpers.ToString(entry.MD5); - if (!OpenKh.Egs.EgsTools.Names.TryGetValue(hash, out var fileName)) - fileName = $"{hash}.dat"; - - var outputFileName = Path.Combine(outputDir, fileName); - - OpenKh.Egs.EgsTools.CreateDirectoryForFile(outputFileName); - - var hdAsset = new OpenKh.Egs.EgsHdAsset(img.SetPosition(entry.Offset)); + File.Delete(Path.Combine(ConfigurationService.PcReleaseLocationKH3D, "steam_appid.txt")); + ConfigurationService.SteamAPITrick28 = false; + } + } + }); + } - File.Create(outputFileName).Using(stream => stream.Write(hdAsset.OriginalData)); + private async Task ExtractGameData(string isoLocation, string gameDataLocation) + { + void MarkStarted() + { + IsNotExtracting = false; + ExtractionProgress = 0; + OnPropertyChanged(nameof(IsNotExtracting)); + OnPropertyChanged(nameof(IsGameDataFound)); + OnPropertyChanged(nameof(ProgressBarVisibility)); + OnPropertyChanged(nameof(ExtractionCompleteVisibility)); + OnPropertyChanged(nameof(ExtractionProgress)); + } - outputFileName = Path.Combine(outputDir, REMASTERED_FILES_FOLDER_NAME, fileName); + void MarkSuccessful() + { + IsNotExtracting = true; + ExtractionProgress = 1.0f; + OnPropertyChanged(nameof(IsNotExtracting)); + OnPropertyChanged(nameof(IsGameDataFound)); + OnPropertyChanged(nameof(GameDataNotFoundVisibility)); + OnPropertyChanged(nameof(GameDataFoundVisibility)); + OnPropertyChanged(nameof(ProgressBarVisibility)); + OnPropertyChanged(nameof(ExtractionCompleteVisibility)); + OnPropertyChanged(nameof(ExtractionProgress)); + } - foreach (var asset in hdAsset.Assets) - { - var outputFileNameRemastered = Path.Combine(OpenKh.Egs.EgsTools.GetHDAssetFolder(outputFileName), asset); - OpenKh.Egs.EgsTools.CreateDirectoryForFile(outputFileNameRemastered); + void MarkFailure() + { + IsNotExtracting = true; + OnPropertyChanged(nameof(IsNotExtracting)); + OnPropertyChanged(nameof(IsGameDataFound)); + OnPropertyChanged(nameof(ProgressBarVisibility)); + OnPropertyChanged(nameof(ExtractionCompleteVisibility)); + OnPropertyChanged(nameof(ExtractionProgress)); + } - var assetData = hdAsset.RemasteredAssetsDecompressedData[asset]; - File.Create(outputFileNameRemastered).Using(stream => stream.Write(assetData)); - } - _procTotalFiles++; + Action CreateOnProgressProcessor() + { + var lastProgress = 0f; - ExtractionProgress = (float)_procTotalFiles / _totalFiles; - OnPropertyChanged(nameof(ExtractionProgress)); - } - } - } - if(ConfigurationService.Extractkh2) + return progress => + { + if (progress == 0) + { + System.Windows.Application.Current.Dispatcher.Invoke(() => MarkStarted()); + } + else if (progress == 1) + { + System.Windows.Application.Current.Dispatcher.Invoke(() => MarkSuccessful()); + } + else + { + if (0.01f <= progress - lastProgress) { - for (int i = 0; i < 6; i++) - { - var outputDir = Path.Combine(gameDataLocation, "kh2"); - using var hedStream = File.OpenRead(Path.Combine(_pcReleaseLocation, "Image", _pcReleaseLanguage, "kh2_" + _nameListkh2[i] + ".hed")); - using var img = File.OpenRead(Path.Combine(_pcReleaseLocation, "Image", _pcReleaseLanguage, "kh2_" + _nameListkh2[i] + ".pkg")); - - foreach (var entry in OpenKh.Egs.Hed.Read(hedStream)) - { - var hash = OpenKh.Egs.Helpers.ToString(entry.MD5); - if (!OpenKh.Egs.EgsTools.Names.TryGetValue(hash, out var fileName)) - fileName = $"{hash}.dat"; - - var outputFileName = Path.Combine(outputDir, fileName); - - OpenKh.Egs.EgsTools.CreateDirectoryForFile(outputFileName); - - var hdAsset = new OpenKh.Egs.EgsHdAsset(img.SetPosition(entry.Offset)); - - File.Create(outputFileName).Using(stream => stream.Write(hdAsset.OriginalData)); - - outputFileName = Path.Combine(outputDir, REMASTERED_FILES_FOLDER_NAME, fileName); + lastProgress = progress; - foreach (var asset in hdAsset.Assets) - { - var outputFileNameRemastered = Path.Combine(OpenKh.Egs.EgsTools.GetHDAssetFolder(outputFileName), asset); - OpenKh.Egs.EgsTools.CreateDirectoryForFile(outputFileNameRemastered); - - var assetData = hdAsset.RemasteredAssetsDecompressedData[asset]; - File.Create(outputFileNameRemastered).Using(stream => stream.Write(assetData)); - } - _procTotalFiles++; - - ExtractionProgress = (float)_procTotalFiles / _totalFiles; - OnPropertyChanged(nameof(ExtractionProgress)); - } - } - } - if(ConfigurationService.Extractbbs) - { - for (int i = 0; i < 4; i++) + System.Windows.Application.Current.Dispatcher.Invoke(() => { - var outputDir = Path.Combine(gameDataLocation, "bbs"); - using var hedStream = File.OpenRead(Path.Combine(_pcReleaseLocation, "Image", _pcReleaseLanguage, "bbs_" + _nameListbbs[i] + ".hed")); - using var img = File.OpenRead(Path.Combine(_pcReleaseLocation, "Image", _pcReleaseLanguage, "bbs_" + _nameListbbs[i] + ".pkg")); - - foreach (var entry in OpenKh.Egs.Hed.Read(hedStream)) - { - var hash = OpenKh.Egs.Helpers.ToString(entry.MD5); - if (!OpenKh.Egs.EgsTools.Names.TryGetValue(hash, out var fileName)) - fileName = $"{hash}.dat"; - - var outputFileName = Path.Combine(outputDir, fileName); - - OpenKh.Egs.EgsTools.CreateDirectoryForFile(outputFileName); - - var hdAsset = new OpenKh.Egs.EgsHdAsset(img.SetPosition(entry.Offset)); - - File.Create(outputFileName).Using(stream => stream.Write(hdAsset.OriginalData)); - - outputFileName = Path.Combine(outputDir, REMASTERED_FILES_FOLDER_NAME, fileName); - - foreach (var asset in hdAsset.Assets) - { - var outputFileNameRemastered = Path.Combine(OpenKh.Egs.EgsTools.GetHDAssetFolder(outputFileName), asset); - OpenKh.Egs.EgsTools.CreateDirectoryForFile(outputFileNameRemastered); - - var assetData = hdAsset.RemasteredAssetsDecompressedData[asset]; - File.Create(outputFileNameRemastered).Using(stream => stream.Write(assetData)); - } - _procTotalFiles++; - - ExtractionProgress = (float)_procTotalFiles / _totalFiles; - OnPropertyChanged(nameof(ExtractionProgress)); - } - } + ExtractionProgress = progress; + OnPropertyChanged(nameof(ExtractionProgress)); + }); } - if (ConfigurationService.Extractrecom) - { - for (int i = 0; i < 1; i++) - { - var outputDir = Path.Combine(gameDataLocation, "Recom"); - using var hedStream = File.OpenRead(Path.Combine(_pcReleaseLocation, "Image", _pcReleaseLanguage, "Recom.hed")); - using var img = File.OpenRead(Path.Combine(_pcReleaseLocation, "Image", _pcReleaseLanguage, "Recom.pkg")); - - foreach (var entry in OpenKh.Egs.Hed.Read(hedStream)) - { - var hash = OpenKh.Egs.Helpers.ToString(entry.MD5); - if (!OpenKh.Egs.EgsTools.Names.TryGetValue(hash, out var fileName)) - fileName = $"{hash}.dat"; - - var outputFileName = Path.Combine(outputDir, fileName); - - OpenKh.Egs.EgsTools.CreateDirectoryForFile(outputFileName); - - var hdAsset = new OpenKh.Egs.EgsHdAsset(img.SetPosition(entry.Offset)); - - File.Create(outputFileName).Using(stream => stream.Write(hdAsset.OriginalData)); - - outputFileName = Path.Combine(outputDir, REMASTERED_FILES_FOLDER_NAME, fileName); - - foreach (var asset in hdAsset.Assets) - { - var outputFileNameRemastered = Path.Combine(OpenKh.Egs.EgsTools.GetHDAssetFolder(outputFileName), asset); - OpenKh.Egs.EgsTools.CreateDirectoryForFile(outputFileNameRemastered); + } + }; + } - var assetData = hdAsset.RemasteredAssetsDecompressedData[asset]; - File.Create(outputFileNameRemastered).Using(stream => stream.Write(assetData)); - } - _procTotalFiles++; + try + { + switch (GameEdition) + { + default: + { + await _gameDataExtractionService.ExtractKh2Ps2EditionAsync( + isoLocation: isoLocation, + gameDataLocation: gameDataLocation, + onProgress: CreateOnProgressProcessor() + ); + break; + } - ExtractionProgress = (float)_procTotalFiles / _totalFiles; - OnPropertyChanged(nameof(ExtractionProgress)); - } - } - } - if (ConfigurationService.Extractkh3d) - { - for (int i = 0; i < 4; i++) + case PC: + { + var langFolder = ConfigurationService.PCVersion == "Steam" ? "dt" : _pcReleaseLanguage == "jp" ? "jp" : "en"; + + await _gameDataExtractionService.ExtractKhPcEditionAsync( + gameDataLocation: gameDataLocation, + onProgress: CreateOnProgressProcessor(), + getKhFilePath: fileName + => Path.Combine( + _pcReleaseLocation, + "Image", + langFolder, + fileName + ), + getKh3dFilePath: fileName + => Path.Combine( + _pcReleaseLocationKH3D, + "Image", + langFolder, + fileName + ), + extractkh1: ConfigurationService.Extractkh1, + extractkh2: ConfigurationService.Extractkh2, + extractbbs: ConfigurationService.Extractbbs, + extractrecom: ConfigurationService.Extractrecom, + extractkh3d: ConfigurationService.Extractkh3d, + ifRetry: async ex => { - var outputDir = Path.Combine(gameDataLocation, "kh3d"); - using var hedStream = File.OpenRead(Path.Combine(_pcReleaseLocationKH3D, "Image", _pcReleaseLanguage, "kh3d_" + _nameListkh3d[i] + ".hed")); - using var img = File.OpenRead(Path.Combine(_pcReleaseLocationKH3D, "Image", _pcReleaseLanguage, "kh3d_" + _nameListkh3d[i] + ".pkg")); + var delayedResult = new TaskCompletionSource(); - foreach (var entry in OpenKh.Egs.Hed.Read(hedStream)) - { - var hash = OpenKh.Egs.Helpers.ToString(entry.MD5); - if (!OpenKh.Egs.EgsTools.Names.TryGetValue(hash, out var fileName)) - fileName = $"{hash}.dat"; - - var outputFileName = Path.Combine(outputDir, fileName); - - OpenKh.Egs.EgsTools.CreateDirectoryForFile(outputFileName); - - var hdAsset = new OpenKh.Egs.EgsHdAsset(img.SetPosition(entry.Offset)); - - File.Create(outputFileName).Using(stream => stream.Write(hdAsset.OriginalData)); - - outputFileName = Path.Combine(outputDir, REMASTERED_FILES_FOLDER_NAME, fileName); - - foreach (var asset in hdAsset.Assets) + await System.Windows.Application.Current.Dispatcher.InvokeAsync( + () => { - var outputFileNameRemastered = Path.Combine(OpenKh.Egs.EgsTools.GetHDAssetFolder(outputFileName), asset); - OpenKh.Egs.EgsTools.CreateDirectoryForFile(outputFileNameRemastered); + var selection = MessageBox.Show(ex.Message + "\n\nWould you like to retry again?", "An Exception was Caught!", MessageBoxButton.YesNoCancel, MessageBoxImage.Warning); - var assetData = hdAsset.RemasteredAssetsDecompressedData[asset]; - File.Create(outputFileNameRemastered).Using(stream => stream.Write(assetData)); + switch (selection) + { + case MessageBoxResult.Yes: + delayedResult.SetResult(true); + return; + case MessageBoxResult.No: + delayedResult.SetResult(false); + return; + default: + delayedResult.SetException(ex); + return; + } } - _procTotalFiles++; - - ExtractionProgress = (float)_procTotalFiles / _totalFiles; - OnPropertyChanged(nameof(ExtractionProgress)); - } - } - } - System.Windows.Application.Current.Dispatcher.Invoke(() => - { - IsNotExtracting = true; - ExtractionProgress = 1.0f; - OnPropertyChanged(nameof(IsNotExtracting)); - OnPropertyChanged(nameof(IsGameDataFound)); - OnPropertyChanged(nameof(GameDataNotFoundVisibility)); - OnPropertyChanged(nameof(GameDataFoundVisibility)); - OnPropertyChanged(nameof(ProgressBarVisibility)); - OnPropertyChanged(nameof(ExtractionCompleteVisibility)); - OnPropertyChanged(nameof(ExtractionProgress)); - }); - }); + ) + .Task; + + return await delayedResult.Task; + }, + cancellationToken: Abort + ); + break; + } } - break; + } + catch + { + System.Windows.Application.Current.Dispatcher.Invoke(() => MarkFailure()); + throw; } } + + private CancellationTokenSource _abort = new CancellationTokenSource(); + public CancellationToken Abort => _abort.Token; + public void SetAborted() => _abort.Cancel(); } } diff --git a/OpenKh.Tools.ModsManager/Views/MainWindow.xaml b/OpenKh.Tools.ModsManager/Views/MainWindow.xaml index 669cbeee6..c4a5bff6f 100644 --- a/OpenKh.Tools.ModsManager/Views/MainWindow.xaml +++ b/OpenKh.Tools.ModsManager/Views/MainWindow.xaml @@ -91,6 +91,7 @@ + @@ -108,6 +109,13 @@ + + + + + + diff --git a/OpenKh.Tools.ModsManager/Views/MainWindow.xaml.cs b/OpenKh.Tools.ModsManager/Views/MainWindow.xaml.cs index 54a72a77d..4e4ba1399 100644 --- a/OpenKh.Tools.ModsManager/Views/MainWindow.xaml.cs +++ b/OpenKh.Tools.ModsManager/Views/MainWindow.xaml.cs @@ -21,5 +21,10 @@ protected override void OnClosed(EventArgs e) WinSettings.Default.Save(); base.OnClosed(e); } + + private void MenuItem_Click(object sender, RoutedEventArgs e) + { + + } } } diff --git a/OpenKh.Tools.ModsManager/Views/ModManagerView.xaml b/OpenKh.Tools.ModsManager/Views/ModManagerView.xaml index 85dcd368f..9962d5533 100644 --- a/OpenKh.Tools.ModsManager/Views/ModManagerView.xaml +++ b/OpenKh.Tools.ModsManager/Views/ModManagerView.xaml @@ -59,7 +59,7 @@ - + diff --git a/OpenKh.Tools.ModsManager/Views/SetupWizardWindow.xaml b/OpenKh.Tools.ModsManager/Views/SetupWizardWindow.xaml index aba71e966..9555d21cc 100644 --- a/OpenKh.Tools.ModsManager/Views/SetupWizardWindow.xaml +++ b/OpenKh.Tools.ModsManager/Views/SetupWizardWindow.xaml @@ -9,13 +9,10 @@ mc:Ignorable="d" WindowStartupLocation="CenterScreen" Title="{Binding Title}" d:Title="OpenKH Mods Manager set-up" - Height="480" Width="400" ResizeMode="NoResize"> + Height="500" Width="400" ResizeMode="NoResize"> -