diff --git a/CathodeLib/CathodeLib.csproj b/CathodeLib/CathodeLib.csproj index ea49066..ea992d6 100644 --- a/CathodeLib/CathodeLib.csproj +++ b/CathodeLib/CathodeLib.csproj @@ -10,10 +10,10 @@ Matt Filer Provides support for parsing and writing common Alien: Isolation formats from the Cathode engine. Matt Filer 2023 - 0.5.0 + 0.5.1 Library - 0.5.0.0 - 0.5.0.0 + 0.5.1.0 + 0.5.1.0 False @@ -47,6 +47,7 @@ + diff --git a/CathodeLib/Scripts/CATHODE/CharacterAccessorySets.cs b/CathodeLib/Scripts/CATHODE/CharacterAccessorySets.cs new file mode 100644 index 0000000..338bb5c --- /dev/null +++ b/CathodeLib/Scripts/CATHODE/CharacterAccessorySets.cs @@ -0,0 +1,155 @@ +using CATHODE.Scripting; +using CathodeLib; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.IO; +using System.Runtime.InteropServices; + +namespace CATHODE +{ + /* DATA/ENV/PRODUCTION/x/WORLD/CHARACTERACCESSORYSETS.BIN */ + public class CharacterAccessorySets : CathodeFile + { + public List Entries = new List(); + public static new Implementation Implementation = Implementation.CREATE | Implementation.LOAD | Implementation.SAVE; + public CharacterAccessorySets(string path) : base(path) { } + + #region FILE_IO + override protected bool LoadInternal() + { + using (BinaryReader reader = new BinaryReader(File.OpenRead(_filepath))) + { + reader.BaseStream.Position = 4; + int entryCount = reader.ReadInt32(); + for (int i = 0; i < entryCount; i++) + { + Entry entry = new Entry(); + entry.character = Utilities.Consume(reader); + + entry.shirt_composite = Utilities.Consume(reader); + entry.trousers_composite = Utilities.Consume(reader); + entry.shoes_composite = Utilities.Consume(reader); + entry.head_composite = Utilities.Consume(reader); + entry.arms_composite = Utilities.Consume(reader); + entry.collision_composite = Utilities.Consume(reader); + + entry.unk1 = reader.ReadInt32(); + + entry.unk2 = reader.ReadInt32(); + entry.unk3 = reader.ReadInt32(); + entry.unk4 = reader.ReadInt32(); + entry.unk5 = reader.ReadInt32(); + entry.unk6 = reader.ReadInt32(); + entry.decal = (Entry.Decal)reader.ReadInt32(); + entry.unk8 = reader.ReadInt32(); + entry.unk9 = reader.ReadInt32(); + entry.unk10 = reader.ReadInt32(); + entry.unk11 = reader.ReadInt32(); + + byte[] stringBlock = reader.ReadBytes(260); + entry.face_skeleton = Utilities.ReadString(stringBlock); + stringBlock = reader.ReadBytes(260); + entry.body_skeleton = Utilities.ReadString(stringBlock); + + entry.unk12 = reader.ReadInt32(); + entry.unk13 = reader.ReadInt32(); + entry.unk14 = reader.ReadInt32(); + Entries.Add(entry); + } + } + return true; + } + + override protected bool SaveInternal() + { + using (BinaryWriter writer = new BinaryWriter(File.OpenWrite(_filepath))) + { + writer.BaseStream.SetLength(0); + writer.Write(20); + writer.Write(Entries.Count); + for (int i = 0; i < Entries.Count; i++) + { + Utilities.Write(writer, Entries[i].character); + + Utilities.Write(writer, Entries[i].shirt_composite); + Utilities.Write(writer, Entries[i].trousers_composite); + Utilities.Write(writer, Entries[i].shoes_composite); + Utilities.Write(writer, Entries[i].head_composite); + Utilities.Write(writer, Entries[i].arms_composite); + Utilities.Write(writer, Entries[i].collision_composite); + + writer.Write(Entries[i].unk1); + writer.Write(Entries[i].unk2); + writer.Write(Entries[i].unk3); + writer.Write(Entries[i].unk4); + writer.Write(Entries[i].unk5); + writer.Write(Entries[i].unk6); + writer.Write((Int32)Entries[i].decal); + writer.Write(Entries[i].unk8); + writer.Write(Entries[i].unk9); + writer.Write(Entries[i].unk10); + writer.Write(Entries[i].unk11); + + writer.Write(new byte[260]); + writer.BaseStream.Position -= 260; + Utilities.WriteString(Entries[i].face_skeleton, writer, false); + writer.BaseStream.Position += 260 - Entries[i].face_skeleton.Length; + writer.Write(new byte[260]); + writer.BaseStream.Position -= 260; + Utilities.WriteString(Entries[i].body_skeleton, writer, false); + writer.BaseStream.Position += 260 - Entries[i].body_skeleton.Length; + + writer.Write(Entries[i].unk12); + writer.Write(Entries[i].unk13); + writer.Write(Entries[i].unk14); + } + } + return true; + } + #endregion + + #region STRUCTURES + public class Entry + { + public CommandsEntityReference character = new CommandsEntityReference(); + + public ShortGuid shirt_composite = ShortGuid.Invalid; + public ShortGuid trousers_composite = ShortGuid.Invalid; + public ShortGuid shoes_composite = ShortGuid.Invalid; + public ShortGuid head_composite = ShortGuid.Invalid; + public ShortGuid arms_composite = ShortGuid.Invalid; + public ShortGuid collision_composite = ShortGuid.Invalid; + + public int unk1 = 0; + public int unk2 = 1; + public int unk3 = 2; + public int unk4 = 3; //This is often odd values + public int unk5 = 4; + public int unk6 = 5; + + public Decal decal = Decal.MEDICAL; //TODO: Is this decal texture defined by CUSTOMCHARACTERASSETDATA.BIN? + + public int unk8 = 0; + public int unk9 = 0; + public int unk10 = 1; + public int unk11 = 0; + + public string face_skeleton = "AL"; + public string body_skeleton = "MALE"; + + public int unk12 = 3; + public int unk13 = 6; + public int unk14 = 9; + + public enum Decal + { + MEDICAL, + ENGINEERING, + GENERIC, + TECHNICAL, + } + }; + #endregion + } +} \ No newline at end of file diff --git a/CathodeLib/Scripts/CATHODE/CollisionMaps.cs b/CathodeLib/Scripts/CATHODE/CollisionMaps.cs index e5ee965..9c8ebc4 100644 --- a/CathodeLib/Scripts/CATHODE/CollisionMaps.cs +++ b/CathodeLib/Scripts/CATHODE/CollisionMaps.cs @@ -7,6 +7,8 @@ namespace CATHODE { + //This file defines additional info for entities with COLLISION_MAPPING resources. + /* DATA/ENV/PRODUCTION/x/WORLD/COLLISION.MAP */ public class CollisionMaps : CathodeFile { @@ -25,8 +27,7 @@ override protected bool LoadInternal() { Entry entry = new Entry(); entry.Unknown1_ = reader.ReadInt32(); - entry.NodeResourceID = Utilities.Consume(reader); - entry.NodeID = Utilities.Consume(reader); + entry.entity = Utilities.Consume(reader); entry.ResourcesBINID = reader.ReadInt32(); entry.Unknown2_ = reader.ReadInt32(); entry.CollisionHKXEntryIndex = reader.ReadInt16(); @@ -52,8 +53,7 @@ override protected bool SaveInternal() for (int i = 0; i < Entries.Count; i++) { writer.Write(Entries[i].Unknown1_); - Utilities.Write(writer, Entries[i].NodeResourceID); - Utilities.Write(writer, Entries[i].NodeID); + Utilities.Write(writer, Entries[i].entity); writer.Write(Entries[i].ResourcesBINID); writer.Write(Entries[i].CollisionHKXEntryIndex); writer.Write(Entries[i].Unknown3_); @@ -72,8 +72,9 @@ override protected bool SaveInternal() public class Entry { public int Unknown1_; // Is this tree node id? - public ShortGuid NodeResourceID; // NOTE: This might not be the correct name. It seems to correspond to the similarly named variable at alien_resources_bin_entry. - public ShortGuid NodeID; // NOTE: This is a wild guess based on the matching items from Commands PAK. + + public CommandsEntityReference entity; + public int ResourcesBINID; // NOTE: This might not be the correct name. It seems to correspond to the similarly named variable at alien_resources_bin_entry. public int Unknown2_; // NOTE: Is sometimes -1 and other times a small positive integer. Is this tree node parent? diff --git a/CathodeLib/Scripts/CATHODE/Commands.cs b/CathodeLib/Scripts/CATHODE/Commands.cs index 59430f1..8209819 100644 --- a/CathodeLib/Scripts/CATHODE/Commands.cs +++ b/CathodeLib/Scripts/CATHODE/Commands.cs @@ -74,7 +74,7 @@ override protected bool LoadInternal() break; case DataType.STRING: reader.BaseStream.Position += 8; - this_parameter = new cString(Utilities.ReadString(reader)); + this_parameter = new cString(Utilities.ReadString(reader).Replace("\u0092", "'")); Utilities.Align(reader, 4); break; case DataType.BOOL: @@ -148,7 +148,6 @@ override protected bool LoadInternal() List entityLinks = new List(); List paramRefSets = new List(); List resourceRefs = new List(); - Dictionary overrideChecksums = new Dictionary(); for (int x = 0; x < offsetPairs.Length; x++) { reader_parallel.BaseStream.Position = offsetPairs[x].GlobalOffset * 4; @@ -184,7 +183,7 @@ override protected bool LoadInternal() case CompositeFileData.ENTITY_OVERRIDES_CHECKSUM: { reader_parallel.BaseStream.Position = (offsetPairs[x].GlobalOffset * 4) + (y * 8); - overrideChecksums.Add(new ShortGuid(reader_parallel), new ShortGuid(reader_parallel)); + reader_parallel.BaseStream.Position += 8; break; } case CompositeFileData.COMPOSITE_EXPOSED_PARAMETERS: @@ -257,16 +256,16 @@ override protected bool LoadInternal() switch (resource.entryType) { case ResourceType.RENDERABLE_INSTANCE: - resource.startIndex = reader_parallel.ReadInt32(); + resource.index = reader_parallel.ReadInt32(); resource.count = reader_parallel.ReadInt32(); break; case ResourceType.COLLISION_MAPPING: - resource.startIndex = reader_parallel.ReadInt32(); + resource.index = reader_parallel.ReadInt32(); resource.collisionID = new ShortGuid(reader_parallel); break; case ResourceType.ANIMATED_MODEL: case ResourceType.DYNAMIC_PHYSICS_SYSTEM: - resource.startIndex = reader_parallel.ReadInt32(); + resource.index = reader_parallel.ReadInt32(); reader_parallel.BaseStream.Position += 4; break; default: @@ -403,10 +402,6 @@ override protected bool LoadInternal() } } - //Apply checksums to overrides - for (int x = 0; x < composite.overrides.Count; x++) - composite.overrides[x].checksum = overrideChecksums[composite.overrides[x].shortGUID]; - //Apply connections between entities for (int x = 0; x < entityLinks.Count; x++) composite.GetEntityByID(entityLinks[x].parentID)?.childLinks.AddRange(entityLinks[x].childLinks); @@ -589,7 +584,7 @@ override protected bool SaveInternal() dps_index = new Parameter("system_index", new cInteger(0)); Entries[i].functions[x].parameters.Add(dps_index); } - Entries[i].functions[x].AddResource(ResourceType.DYNAMIC_PHYSICS_SYSTEM).startIndex = ((cInteger)dps_index.content).value; + Entries[i].functions[x].AddResource(ResourceType.DYNAMIC_PHYSICS_SYSTEM).index = ((cInteger)dps_index.content).value; break; case FunctionType.EnvironmentModelReference: Parameter rsc = Entries[i].functions[x].GetParameter("resource"); @@ -640,7 +635,7 @@ override protected bool SaveInternal() linkedEntities[i] = new List(ents.FindAll(o => o.childLinks.Count != 0)).OrderBy(o => o.shortGUID.ToUInt32()).ToList(); parameterisedEntities[i] = new List(ents.FindAll(o => o.parameters.Count != 0)).OrderBy(o => o.shortGUID.ToUInt32()).ToList(); reshuffledOverrides[i] = Entries[i].overrides.OrderBy(o => o.shortGUID.ToUInt32()).ToList(); - reshuffledChecksums[i] = Entries[i].overrides.OrderBy(o => o.checksum.ToUInt32()).ToList(); + reshuffledChecksums[i] = Entries[i].overrides.OrderBy(o => o.connectedEntity.GenerateChecksum().ToUInt32()).ToList(); cageAnimationEntities[i] = new List(); triggerSequenceEntities[i] = new List(); @@ -703,10 +698,10 @@ override protected bool SaveInternal() #region WRITE_PARAMETERS //Write out parameters & track offsets - int[] parameterOffsets = new int[parameters.Count]; + Dictionary parameterOffsets = new Dictionary(parameters.Count); for (int i = 0; i < parameters.Count; i++) { - parameterOffsets[i] = (int)writer.BaseStream.Position / 4; + parameterOffsets.Add(parameters[i], (int)writer.BaseStream.Position / 4); Utilities.Write(writer, CommandsUtils.GetDataTypeGUID(parameters[i].dataType)); switch (parameters[i].dataType) { @@ -729,7 +724,7 @@ override protected bool SaveInternal() byte[] stringStartRaw = BitConverter.GetBytes(stringStart); stringStartRaw[3] = 0x80; writer.Write(stringStartRaw); - string str = ((cString)parameters[i]).value; + string str = ((cString)parameters[i]).value.Replace("\u0092", "'"); writer.Write(ShortGuidUtils.Generate(str).val); for (int x = 0; x < str.Length; x++) writer.Write(str[x]); writer.Write((char)0x00); @@ -837,7 +832,7 @@ override protected bool SaveInternal() for (int y = 0; y < sortedParams.Count; y++) { Utilities.Write(writer, sortedParams[y].name); - writer.Write(GetParameterOffset(ref parameterOffsets, ref parameters, ref sortedParams[y].content)); + writer.Write(parameterOffsets[sortedParams[y].content]); } } @@ -874,7 +869,7 @@ override protected bool SaveInternal() for (int p = 0; p < reshuffledChecksums[i].Count; p++) { writer.Write(reshuffledChecksums[i][p].shortGUID.val); - writer.Write(reshuffledChecksums[i][p].checksum.val); + writer.Write(reshuffledChecksums[i][p].connectedEntity.GenerateChecksum().val); } break; } @@ -944,16 +939,16 @@ override protected bool SaveInternal() switch (resourceReferences[i][p].entryType) { case ResourceType.RENDERABLE_INSTANCE: - writer.Write(resourceReferences[i][p].startIndex); + writer.Write(resourceReferences[i][p].index); writer.Write(resourceReferences[i][p].count); break; case ResourceType.COLLISION_MAPPING: - writer.Write(resourceReferences[i][p].startIndex); + writer.Write(resourceReferences[i][p].index); writer.Write(resourceReferences[i][p].collisionID.val); break; case ResourceType.ANIMATED_MODEL: case ResourceType.DYNAMIC_PHYSICS_SYSTEM: - writer.Write(resourceReferences[i][p].startIndex); + writer.Write(resourceReferences[i][p].index); writer.Write(-1); break; case ResourceType.EXCLUSIVE_MASTER_STATE_RESOURCE: @@ -1159,7 +1154,8 @@ override protected bool SaveInternal() //Write out parameter offsets int parameterOffsetPos = (int)writer.BaseStream.Position; - Utilities.Write(writer, parameterOffsets); + foreach (KeyValuePair offset in parameterOffsets) + writer.Write(offset.Value); //Write out composite offsets int compositeOffsetPos = (int)writer.BaseStream.Position; @@ -1197,12 +1193,12 @@ public string[] GetCompositeNames() /* Get an individual composite */ public Composite GetComposite(string name) { - return Entries.FirstOrDefault(o => o.name == name || o.name == name.Replace('/', '\\')); + return Entries.FirstOrDefault(o => o != null && o.name == name || o.name == name.Replace('/', '\\')); } public Composite GetComposite(ShortGuid id) { if (id.val == null) return null; - return Entries.FirstOrDefault(o => o.shortGUID == id); + return Entries.FirstOrDefault(o => o != null && o.shortGUID == id); } /* Get entry point composite objects */ @@ -1243,17 +1239,6 @@ private int JumpToOffset(BinaryReader reader) return count; } - /* Get the offset for the parameter data in the overarching write list */ - private int GetParameterOffset(ref int[] offsets, ref List parameters, ref ParameterData parameter) - { - for (int i = 0; i < parameters.Count; i++) - { - if (parameters[i] == parameter) - return offsets[i]; - } - throw new Exception("Failed to lookup parameter in parameters list, could not find offset!"); - } - /* Refresh the composite pointers for our entry points */ private void RefreshEntryPointObjects() { diff --git a/CathodeLib/Scripts/CATHODE/CommandsPAK/Components/Entity.cs b/CathodeLib/Scripts/CATHODE/CommandsPAK/Components/Entity.cs index a92a34d..79be6e0 100644 --- a/CathodeLib/Scripts/CATHODE/CommandsPAK/Components/Entity.cs +++ b/CathodeLib/Scripts/CATHODE/CommandsPAK/Components/Entity.cs @@ -1,4 +1,4 @@ -using CATHODE.Scripting.Internal; +using CATHODE.Scripting.Internal; #if DEBUG using Newtonsoft.Json.Converters; using Newtonsoft.Json; @@ -12,6 +12,8 @@ using UnityEngine; #else using System.Numerics; +using System.IO; +using CathodeLib; #endif namespace CATHODE.Scripting.Internal @@ -268,7 +270,7 @@ public ResourceReference AddResource(ResourceType type) case ResourceType.DYNAMIC_PHYSICS_SYSTEM: case ResourceType.RENDERABLE_INSTANCE: case ResourceType.ANIMATED_MODEL: - rr.startIndex = 0; + rr.index = 0; break; } resources.Add(rr); @@ -318,17 +320,14 @@ public OverrideEntity(ShortGuid shortGUID) : base(shortGUID, EntityVariant.OVERR public OverrideEntity(List hierarchy = null) : base(EntityVariant.OVERRIDE) { - checksum = ShortGuidUtils.GenerateRandom(); if (hierarchy != null) this.connectedEntity.hierarchy = hierarchy; } public OverrideEntity(ShortGuid shortGUID, List hierarchy = null) : base(shortGUID, EntityVariant.OVERRIDE) { this.shortGUID = shortGUID; - checksum = ShortGuidUtils.GenerateRandom(); if (hierarchy != null) this.connectedEntity.hierarchy = hierarchy; } - public ShortGuid checksum; //TODO: This value is apparently a hash of the hierarchy GUIDs, but need to verify that, and work out the salt. public EntityHierarchy connectedEntity = new EntityHierarchy(); } @@ -450,6 +449,12 @@ public EntityLink(ShortGuid childEntityID, ShortGuid parentParam, ShortGuid chil public ShortGuid childID; //The ID of the entity we're linking to to provide the value for } + /// + /// This is a class to handle hierarchies pointing to entities in Commands. + /// Provides useful functionality for generating checksums (used for overrides in Commands), as well as composite instance IDs (used for legacy systems). + /// Also has methods of capturing the entity pointed to and writing the hierarchies neatly. + /// The hierarchy should always be written to Commands with a trailing ShortGuid.Invalid. + /// [Serializable] #if DEBUG [JsonConverter(typeof(EntityHierarchyConverter))] @@ -461,8 +466,8 @@ public EntityHierarchy(List _hierarchy) { hierarchy = _hierarchy; - if (hierarchy[hierarchy.Count - 1].ToByteString() != "00-00-00-00") - hierarchy.Add(new ShortGuid("00-00-00-00")); + if (hierarchy[hierarchy.Count - 1] != ShortGuid.Invalid) + hierarchy.Add(ShortGuid.Invalid); } public List hierarchy = new List(); @@ -523,12 +528,65 @@ public Entity GetPointedEntity(Commands commands, Composite composite) { return CommandsUtils.ResolveHierarchy(commands, composite, hierarchy, out Composite comp, out string str); } + public ShortGuid GetPointedEntityID() + { + hierarchy.Reverse(); + ShortGuid id = ShortGuid.Invalid; + for (int i = 0; i < hierarchy.Count; i++) + { + if (hierarchy[i] == ShortGuid.Invalid) continue; + id = hierarchy[i]; + break; + } + hierarchy.Reverse(); + return id; + } /* Does this hierarchy point to a valid entity? */ public bool IsHierarchyValid(Commands commands, Composite composite) { return GetPointedEntity(commands, composite) != null; } + + /* Generate the checksum used identify the hierarchy */ + public ShortGuid GenerateChecksum() + { + if (hierarchy.Count == 0) return ShortGuid.Invalid; + if (hierarchy[hierarchy.Count - 1] != ShortGuid.Invalid) hierarchy.Add(ShortGuid.Invalid); + + hierarchy.Reverse(); + ShortGuid checksumGenerated = hierarchy[0]; + for (int i = 0; i < hierarchy.Count; i++) + { + checksumGenerated = checksumGenerated.Combine(hierarchy[i + 1]); + if (i == hierarchy.Count - 2) break; + } + hierarchy.Reverse(); + + return checksumGenerated; + } + + /* Generate the instance ID used to identify the instanced composite we're executed in */ + public ShortGuid GenerateInstance() + { + //TODO: This hijacks the usual use for this class, need to tidy it up + ShortGuid entityID = GetPointedEntityID(); + hierarchy.Insert(0, ShortGuid.InitialiserBase); + hierarchy.Remove(entityID); + hierarchy.Reverse(); + ShortGuid instanceGenerated = hierarchy[0]; + for (int i = 0; i < hierarchy.Count; i++) + { + instanceGenerated = hierarchy[i + 1].Combine(instanceGenerated); + if (i == hierarchy.Count - 2) break; + } + hierarchy.Reverse(); + hierarchy.RemoveAt(0); + hierarchy.RemoveAll(o => o == ShortGuid.Invalid); + hierarchy.Add(entityID); + hierarchy.Add(ShortGuid.Invalid); + return instanceGenerated; + } } #if DEBUG diff --git a/CathodeLib/Scripts/CATHODE/CommandsPAK/Components/ParameterData.cs b/CathodeLib/Scripts/CATHODE/CommandsPAK/Components/ParameterData.cs index 7270120..dd2f693 100644 --- a/CathodeLib/Scripts/CATHODE/CommandsPAK/Components/ParameterData.cs +++ b/CathodeLib/Scripts/CATHODE/CommandsPAK/Components/ParameterData.cs @@ -266,7 +266,7 @@ public ResourceReference AddResource(ResourceType type) case ResourceType.DYNAMIC_PHYSICS_SYSTEM: case ResourceType.RENDERABLE_INSTANCE: case ResourceType.ANIMATED_MODEL: - rr.startIndex = 0; + rr.index = 0; break; } value.Add(rr); diff --git a/CathodeLib/Scripts/CATHODE/CommandsPAK/Components/ResourceReference.cs b/CathodeLib/Scripts/CATHODE/CommandsPAK/Components/ResourceReference.cs index f2a40bd..8ac36d7 100644 --- a/CathodeLib/Scripts/CATHODE/CommandsPAK/Components/ResourceReference.cs +++ b/CathodeLib/Scripts/CATHODE/CommandsPAK/Components/ResourceReference.cs @@ -24,7 +24,7 @@ public ResourceReference(ResourceType type) case ResourceType.DYNAMIC_PHYSICS_SYSTEM: case ResourceType.RENDERABLE_INSTANCE: case ResourceType.ANIMATED_MODEL: - startIndex = 0; + index = 0; break; } entryType = type; @@ -39,7 +39,7 @@ public ResourceReference(ResourceType type) if (x.rotation != y.rotation) return false; if (x.resourceID != y.resourceID) return false; if (x.entryType != y.entryType) return false; - if (x.startIndex != y.startIndex) return false; + if (x.index != y.index) return false; if (x.count != y.count) return false; if (x.collisionID != y.collisionID) return false; @@ -62,7 +62,7 @@ public override bool Equals(object obj) EqualityComparer.Default.Equals(rotation, reference.rotation) && EqualityComparer.Default.Equals(resourceID, reference.resourceID) && entryType == reference.entryType && - startIndex == reference.startIndex && + index == reference.index && count == reference.count && EqualityComparer.Default.Equals(collisionID, reference.collisionID); } @@ -74,7 +74,7 @@ public override int GetHashCode() hashCode = hashCode * -1521134295 + rotation.GetHashCode(); hashCode = hashCode * -1521134295 + resourceID.GetHashCode(); hashCode = hashCode * -1521134295 + entryType.GetHashCode(); - hashCode = hashCode * -1521134295 + startIndex.GetHashCode(); + hashCode = hashCode * -1521134295 + index.GetHashCode(); hashCode = hashCode * -1521134295 + count.GetHashCode(); hashCode = hashCode * -1521134295 + collisionID.GetHashCode(); return hashCode; @@ -86,7 +86,7 @@ public override int GetHashCode() public ShortGuid resourceID; //TODO: we could deprecate this, and just write it knowing what we know with our object structure public ResourceType entryType; - public int startIndex = -1; + public int index = -1; public int count = 1; public ShortGuid collisionID = new ShortGuid("FF-FF-FF-FF"); diff --git a/CathodeLib/Scripts/CATHODE/CommandsPAK/Components/ShortGuid.cs b/CathodeLib/Scripts/CATHODE/CommandsPAK/Components/ShortGuid.cs index e270920..400a1cc 100644 --- a/CathodeLib/Scripts/CATHODE/CommandsPAK/Components/ShortGuid.cs +++ b/CathodeLib/Scripts/CATHODE/CommandsPAK/Components/ShortGuid.cs @@ -16,6 +16,9 @@ namespace CATHODE.Scripting #endif public struct ShortGuid : IComparable { + public static readonly ShortGuid Invalid = new ShortGuid(0); + public static readonly ShortGuid InitialiserBase = new ShortGuid("FE-5B-F0-4A"); + public ShortGuid(float num) { val = BitConverter.GetBytes(num); @@ -70,6 +73,14 @@ public override bool Equals(object obj) { return x.ToByteString() != y; } + public static bool operator ==(ShortGuid x, uint y) + { + return x.ToUInt32() == y; + } + public static bool operator !=(ShortGuid x, uint y) + { + return x.ToUInt32() != y; + } public override int GetHashCode() { if (val == null) return 0; diff --git a/CathodeLib/Scripts/CATHODE/CommandsPAK/Components/TypeEnums.cs b/CathodeLib/Scripts/CATHODE/CommandsPAK/Components/TypeEnums.cs index 2aa9d43..1b1d90e 100644 --- a/CathodeLib/Scripts/CATHODE/CommandsPAK/Components/TypeEnums.cs +++ b/CathodeLib/Scripts/CATHODE/CommandsPAK/Components/TypeEnums.cs @@ -891,13 +891,13 @@ public enum FunctionType /* Resource reference types */ public enum ResourceType { - COLLISION_MAPPING, - DYNAMIC_PHYSICS_SYSTEM, - EXCLUSIVE_MASTER_STATE_RESOURCE, + COLLISION_MAPPING, //Links to data in COLLISION.MAP + DYNAMIC_PHYSICS_SYSTEM, //Links to data in PHYSICS.MAP + EXCLUSIVE_MASTER_STATE_RESOURCE, // ?? -> this seems to define some sort of change of NAV_MESH state using EXCLUSIVE_MASTER_RESOURCE_INDICES NAV_MESH_BARRIER_RESOURCE, - RENDERABLE_INSTANCE, + RENDERABLE_INSTANCE, //Links to data in REDS.BIN TRAVERSAL_SEGMENT, - ANIMATED_MODEL, //Links to ResourceIndex in ENVIRONMENT_ANIMATION.DAT + ANIMATED_MODEL, //Links to data in ENVIRONMENT_ANIMATION.DAT // Any below this point are referenced in code, but not used in the vanilla game's CommandsPAKs CATHODE_COVER_SEGMENT, @@ -905,7 +905,7 @@ public enum ResourceType ANIMATION_MASK_RESOURCE, PLAY_ANIMATION_DATA_RESOURCE, - + // A lot of these below are types that translate to strings in various text databases CAMERA_INSTANCE, VENT_ENTRANCE, LOGIC_CHARACTER, diff --git a/CathodeLib/Scripts/CATHODE/CommandsPAK/Helpers/CommandsUtils.cs b/CathodeLib/Scripts/CATHODE/CommandsPAK/Helpers/CommandsUtils.cs index ac2a30c..e3a478e 100644 --- a/CathodeLib/Scripts/CATHODE/CommandsPAK/Helpers/CommandsUtils.cs +++ b/CathodeLib/Scripts/CATHODE/CommandsPAK/Helpers/CommandsUtils.cs @@ -1,8 +1,9 @@ -using CATHODE.Scripting.Internal; +using CATHODE.Scripting.Internal; using System; using System.Collections.Generic; using System.Linq; using System.Text; +using System.Threading.Tasks; namespace CATHODE.Scripting { @@ -220,6 +221,41 @@ public static Entity ResolveHierarchy(Commands commands, Composite composite, Li return entity; } + /* Generate all possible hierarchies for an entity */ + private static List> _hierarchies = new List>(); + public static List GenerateHierarchies(Commands commands, Composite composite, Entity entity) + { + List hierarchies = new List(); + _hierarchies.Clear(); + + GenerateHierarchiesRecursive(commands, null, commands.EntryPoints[0], composite, new List()); + + for (int i = 0; i < _hierarchies.Count; i++) + { + _hierarchies[i].Add(entity.shortGUID); + hierarchies.Add(new EntityHierarchy(_hierarchies[i])); + } + + return hierarchies; + } + private static void GenerateHierarchiesRecursive(Commands commands, Entity ent, Composite comp, Composite target, List hierarchy) + { + if (ent != null) + hierarchy.Add(ent.shortGUID); + + if (comp.shortGUID == target.shortGUID) + { + _hierarchies.Add(hierarchy); + return; + } + + Parallel.For(0, comp.functions.Count, i => + { + Composite next = commands.GetComposite(comp.functions[i].function); + if (next != null) GenerateHierarchiesRecursive(commands, comp.functions[i], next, target, new List(hierarchy.ConvertAll(x => x))); + }); + } + /* CA's CAGE doesn't properly tidy up hierarchies pointing to deleted entities - so we can do that to save confusion */ public static void PurgeDeadLinks(Commands commands, Composite composite) { diff --git a/CathodeLib/Scripts/CATHODE/CommandsPAK/Helpers/EntityUtils.cs b/CathodeLib/Scripts/CATHODE/CommandsPAK/Helpers/EntityUtils.cs index fbdabb3..3d976a3 100644 --- a/CathodeLib/Scripts/CATHODE/CommandsPAK/Helpers/EntityUtils.cs +++ b/CathodeLib/Scripts/CATHODE/CommandsPAK/Helpers/EntityUtils.cs @@ -8066,7 +8066,7 @@ private static void ApplyDefaultsInternal(Entity entity, FunctionType type) break; case FunctionType.PhysicsSystem: entity.AddParameter("system_index", new cInteger(), ParameterVariant.INTERNAL); //int - if (entity.variant == EntityVariant.FUNCTION) ((FunctionEntity)entity).AddResource(ResourceType.DYNAMIC_PHYSICS_SYSTEM).startIndex = 0; + if (entity.variant == EntityVariant.FUNCTION) ((FunctionEntity)entity).AddResource(ResourceType.DYNAMIC_PHYSICS_SYSTEM).index = 0; break; case FunctionType.BulletChamber: entity.AddParameter("Slot1", new cFloat(), ParameterVariant.INPUT); //Object diff --git a/CathodeLib/Scripts/CATHODE/CommandsPAK/Helpers/ShortGuidUtils.cs b/CathodeLib/Scripts/CATHODE/CommandsPAK/Helpers/ShortGuidUtils.cs index c539c72..f5e233e 100644 --- a/CathodeLib/Scripts/CATHODE/CommandsPAK/Helpers/ShortGuidUtils.cs +++ b/CathodeLib/Scripts/CATHODE/CommandsPAK/Helpers/ShortGuidUtils.cs @@ -70,6 +70,22 @@ private static ShortGuid Generate(string value, bool cache = true) return guid; } + /* Combines two ShortGuid objects into one */ + public static ShortGuid Combine(this ShortGuid guid1, ShortGuid guid2) + { + if (guid2.ToUInt32() != 0) + { + if (guid1.ToUInt32() != 0) + { + ulong hash = BitConverter.ToUInt64(new byte[8] { guid1.val[0], guid1.val[1], guid1.val[2], guid1.val[3], guid2.val[0], guid2.val[1], guid2.val[2], guid2.val[3] }, 0); + hash = ~hash + hash * 262144; hash = (hash ^ (hash >> 31)) * 21; hash = (hash ^ (hash >> 11)) * 65; + return new ShortGuid(BitConverter.GetBytes((uint)(hash >> 22) ^ (uint)hash)); + } + return guid2; + } + return guid1; + } + /* Attempts to look up the string for a given ShortGuid */ public static string FindString(ShortGuid guid) { diff --git a/CathodeLib/Scripts/CATHODE/CustomCharacterConstrainedComponents.cs b/CathodeLib/Scripts/CATHODE/CustomCharacterConstrainedComponents.cs new file mode 100644 index 0000000..5b9be80 --- /dev/null +++ b/CathodeLib/Scripts/CATHODE/CustomCharacterConstrainedComponents.cs @@ -0,0 +1,111 @@ +using CATHODE.Scripting; +using CathodeLib; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.IO; +using System.Runtime.InteropServices; +using static CATHODE.EXPERIMENTAL.MissionSave; + +namespace CATHODE +{ + /* DATA/CHR_INFO/CUSTOMCHARACTERCONSTRAINEDCOMPONENTS.BIN */ + public class CustomCharacterConstrainedComponents : CathodeFile + { + public List Entries = new List(); + public static new Implementation Implementation = Implementation.CREATE | Implementation.LOAD | Implementation.SAVE; + public CustomCharacterConstrainedComponents(string path) : base(path) { } + + #region FILE_IO + override protected bool LoadInternal() + { + using (BinaryReader reader = new BinaryReader(File.OpenRead(_filepath))) + { + reader.BaseStream.Position = 4; + Read(ComponentType.ARMS, reader); + Read(ComponentType.HEADS, reader); + } + return true; + } + + override protected bool SaveInternal() + { + using (BinaryWriter writer = new BinaryWriter(File.OpenWrite(_filepath))) + { + writer.BaseStream.SetLength(0); + writer.Write(20); + for (int i = 0; i < Entries.Count; i++) + { + writer.Write(Entries[i].Components.Count); + for (int x = 0; x < Entries[i].Components.Count; x++) + { + writer.Write(new byte[64]); + writer.BaseStream.Position -= 64; + Utilities.WriteString(Entries[i].Components[x].Name, writer, false); + writer.BaseStream.Position += 64 - Entries[i].Components[x].Name.Length; + + writer.Write(Entries[i].Components[x].unk1); + writer.Write(Entries[i].Components[x].unk2); + writer.Write(Entries[i].Components[x].unk3); + writer.Write(Entries[i].Components[x].unk4); + writer.Write(Entries[i].Components[x].unk5); + writer.Write(Entries[i].Components[x].unk6); + } + } + } + return true; + } + #endregion + + #region HELPERS + private void Read(ComponentType type, BinaryReader reader) + { + int entryCount = reader.ReadInt32(); + Entry arms = new Entry() { Type = ComponentType.ARMS }; + for (int i = 0; i < entryCount; i++) + { + Entry.Component component = new Entry.Component(); + byte[] stringBlock = reader.ReadBytes(64); + component.Name = Utilities.ReadString(stringBlock); + + //TODO: these seem to get concatenated in code + component.unk1 = reader.ReadInt32(); + component.unk2 = reader.ReadInt32(); + component.unk3 = reader.ReadInt32(); + component.unk4 = reader.ReadInt32(); + component.unk5 = reader.ReadInt32(); + component.unk6 = reader.ReadInt32(); + + arms.Components.Add(component); + } + Entries.Add(arms); + } + #endregion + + #region STRUCTURES + public class Entry + { + public ComponentType Type; + public List Components = new List(); + + public class Component + { + public string Name; + + public int unk1; + public int unk2; + public int unk3; + public int unk4; + public int unk5; + public int unk6; + } + }; + + public enum ComponentType + { + ARMS, + HEADS, + } + #endregion + } +} \ No newline at end of file diff --git a/CathodeLib/Scripts/CATHODE/CustomCharacterInfo.cs b/CathodeLib/Scripts/CATHODE/CustomCharacterInfo.cs new file mode 100644 index 0000000..4799fb6 --- /dev/null +++ b/CathodeLib/Scripts/CATHODE/CustomCharacterInfo.cs @@ -0,0 +1,56 @@ +using CATHODE.Scripting; +using CathodeLib; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.IO; +using System.Runtime.InteropServices; + +namespace CATHODE +{ + /* DATA/CHR_INFO/CUSTOMCHARACTERINFO.BIN */ + public class CustomCharacterInfo : CathodeFile + { + public List Entries = new List(); + public static new Implementation Implementation = Implementation.NONE; + public CustomCharacterInfo(string path) : base(path) { } + + #region FILE_IO + override protected bool LoadInternal() + { + using (BinaryReader reader = new BinaryReader(File.OpenRead(_filepath))) + { + reader.BaseStream.Position = 4; + int entryCount = 99; + for (int i = 0; i < entryCount; i++) + { + byte[] stringBlock = reader.ReadBytes(64); + Console.WriteLine(Utilities.ReadString(stringBlock)); + } + } + return true; + } + + override protected bool SaveInternal() + { + using (BinaryWriter writer = new BinaryWriter(File.OpenWrite(_filepath))) + { + writer.BaseStream.SetLength(0); + writer.Write(20); + for (int i = 0; i < Entries.Count; i++) + { + + } + } + return true; + } + #endregion + + #region STRUCTURES + public class Entry + { + + }; + #endregion + } +} \ No newline at end of file diff --git a/CathodeLib/Scripts/CATHODE/MissionSave.cs b/CathodeLib/Scripts/CATHODE/MissionSave.cs index e05c075..362c05e 100644 --- a/CathodeLib/Scripts/CATHODE/MissionSave.cs +++ b/CathodeLib/Scripts/CATHODE/MissionSave.cs @@ -6,7 +6,7 @@ namespace CATHODE.EXPERIMENTAL { - /* PROGRESSION.AIS */ + /* *.AIS */ public class MissionSave : CathodeFile { public static new Implementation Implementation = Implementation.NONE; @@ -18,6 +18,8 @@ public MissionSave(string path) : base(path) { } // "node" names saved with their connected "leafs" which acts like a "system" and // "parameter" to apply to the system + // Check CATHODE::SaveState::save_leaf! + #region FILE_IO /* Load the file */ override protected bool LoadInternal() @@ -25,71 +27,35 @@ override protected bool LoadInternal() using (BinaryReader reader = new BinaryReader(File.OpenRead(_filepath))) { _header = Utilities.Consume
(reader); - string levelname = Utilities.ReadString(reader.ReadBytes(128)); + switch (_header.VersionNum) + { + case AISType.SAVE: + string levelName = Utilities.ReadString(reader.ReadBytes(128)); + Console.WriteLine("Level Name: " + levelName); + string saveName = Utilities.ReadStringAlternating(reader.ReadBytes(256)); + Console.WriteLine("Save Name: " + saveName); + string levelSaveDescriptor = Utilities.ReadString(reader.ReadBytes(160)); + Console.WriteLine("Localised Save Descriptor: " + levelSaveDescriptor); + reader.BaseStream.Position += 8; + string playlist = Utilities.ReadString(reader.ReadBytes(64)); //unsure on this + Console.WriteLine("Playlist: " + playlist); + + reader.BaseStream.Position = 1208; + while (true) + { + if (!ReadEntry(reader)) break; + } + break; + } reader.BaseStream.Position = _header.save_root_offset; - - ValidateGuid(reader, "save_root"); - reader.BaseStream.Position += 8; - - ValidateGuid(reader, "trigger"); - ParseHeaderAndSkip(reader); - - ValidateGuid(reader, "pause_context_trigger"); - ParseHeaderAndSkip(reader); - - ValidateGuid(reader, "forward_triggers"); - ParseHeaderAndSkip(reader); - - ValidateGuid(reader, "m_broadcast_messages"); - ParseHeaderAndSkip(reader); - - ValidateGuid(reader, "player_pos"); - ParseHeaderAndSkip(reader); - - ValidateGuid(reader, "player_pos_valid"); - reader.BaseStream.Position += 2; - - ValidateGuid(reader, "filter_object"); - ParseHeaderAndSkip(reader); - - ValidateGuid(reader, "next_temporary_guid"); - ParseHeaderAndSkip(reader); - - ValidateGuid(reader, "trigger_object"); - ParseHeaderAndSkip(reader); - - ValidateGuid(reader, "temp_entities_data"); - ParseHeaderAndSkip(reader); - - ValidateGuid(reader, "temp_entities"); - ParseHeaderAndSkip(reader); - - //TODO: What do we reach after this point? Can't find the ShortGuid! - - int pos = (int)reader.BaseStream.Position; - - /* - List dump = new List(); - int prevPos = (int)Stream.BaseStream.Position; while (true) { - if (Stream.BaseStream.Position + 4 >= Stream.BaseStream.Length) break; - - ShortGuid consumed_guid = Utilities.Consume(Stream); - if (consumed_guid.ToString() == "00-00-00-00") continue; - - string match = ShortGuidUtils.FindString(consumed_guid); - if (match != consumed_guid.ToString()) - { - dump.Add((Stream.BaseStream.Position - 4) + " => [ + " + ((Stream.BaseStream.Position - 4) - prevPos) + "] => " + match); - prevPos = (int)Stream.BaseStream.Position; - } - - Stream.BaseStream.Position -= 3; + string id = ReadNode(reader); + if (_header.VersionNum == AISType.SAVE && id == "temp_entities") break; //This seems to be the last resolvable in SAVE? + if (id == null) break; } - File.WriteAllLines(Path.GetFileNameWithoutExtension(pathToMVR) + "_dump.txt", dump); - */ + Console.WriteLine("Finished root nodes at " + reader.BaseStream.Position); } return true; } @@ -107,33 +73,84 @@ override protected bool SaveInternal() #endregion #region HELPERS - private void ParseHeaderAndSkip(BinaryReader stream) + private bool ReadEntry(BinaryReader stream) + { + UInt32 type = stream.ReadUInt32(); + if (type == 0) return false; + switch (type) + { + case 55762421: + stream.BaseStream.Position += 8; + break; + default: + UInt32 id = stream.ReadUInt32(); + int val = stream.ReadInt32(); + Console.WriteLine(type + ": " + id + " -> " + val); + break; + } + return true; + } + + private string ReadNode(BinaryReader stream) { + //Read leaf name + ShortGuid id = Utilities.Consume(stream); + if (id.ToUInt32() == 0) return null; + + string id_str = id.ToString(); + Console.WriteLine("Reading " + id_str); + + //The root nodes are always 8 in length + if (id_str == "save_root" || id_str == "progression_root") + { + stream.BaseStream.Position += 8; + return id_str; + } + + //Read entry header byte type = stream.ReadByte(); + + if (type == 0x01) + { + stream.BaseStream.Position += 1; + return id_str; + } + int offset = stream.ReadInt16(); byte unk = stream.ReadByte(); if (unk == 0x01) throw new Exception("Unhandled"); + //Read entry contents + int length = 0; switch (type) { + case 0x02: + length = 0; + break; + case 0x04: + length = 1; + break; case 0x40: case 0x0D: - stream.BaseStream.Position += offset; + length = offset; break; - case 0x04: - stream.BaseStream.Position += 1; + case 0x4D: + length = offset + 3; break; default: throw new Exception("Unhandled"); } - } + byte[] content = stream.ReadBytes(length); - private void ValidateGuid(BinaryReader Stream, string str) - { - ShortGuid consumed_guid = Utilities.Consume(Stream); - if (consumed_guid != ShortGuidUtils.Generate(str)) - throw new Exception(str + " mismatch"); + switch (id_str) + { + case "m_last_saved_level": + string lvl = Utilities.ReadString(content); + Console.WriteLine("\t" + lvl); + break; + } + return id_str; } #endregion @@ -142,7 +159,7 @@ private void ValidateGuid(BinaryReader Stream, string str) public struct Header { public fourcc FourCC; - public int VersionNum; + public AISType VersionNum; public ShortGuid unk1; public ShortGuid unk2; @@ -152,6 +169,11 @@ public struct Header public int save_root_offset; public int Offset3; }; + public enum AISType : Int32 + { + SAVE = 18, + PROGRESSION = 4 + } #endregion } } \ No newline at end of file diff --git a/CathodeLib/Scripts/CATHODE/Movers.cs b/CathodeLib/Scripts/CATHODE/Movers.cs index c4dbdc7..a31301b 100644 --- a/CathodeLib/Scripts/CATHODE/Movers.cs +++ b/CathodeLib/Scripts/CATHODE/Movers.cs @@ -203,17 +203,18 @@ RenderableElementSet is always paired with a MOVER_DESCRIPTOR (see RenderableSce public Vector3 Unknowns5_; public UInt32 visibility; // pulled from iOS dump - should be visibility var? //272 - public ShortGuid commandsNodeID; // this is the ID of the node inside the composite, not the instanced composite node - public ShortGuid resourcesEntryID; // NOTE: This is 'IDFromMVREntry' field on 'alien_resources_bin_entry'. - //280 + + public CommandsEntityReference entity; //The entity in the Commands file + + //280 public UInt32 environmentMapIndex; //environment_map.bin index - converted to short in code //284 public float emissive_val1; //emissive surface val1 public float emissive_val2; //emissive surface val2 public float emissive_val3; //emissive surface val3 //296 - public UInt32 zoneID; //zone id? RenderableScene::create_instance, RenderableScene::initialize - public UInt32 zoneActivator; //zone activator? RenderableScene::create_instance, RenderableScene::initialize + public ShortGuid zoneID; //zone id? RenderableScene::create_instance, RenderableScene::initialize + public ShortGuid zoneActivator; //zone activator? RenderableScene::create_instance, RenderableScene::initialize //304 public UInt32 Unknowns61_; //uVar3 in reserve_light_light_master_sets, val of LightMasterSet, or an incrementer public UInt16 Unknown17_; // TODO: It is -1 most of the time, but some times it isn't. diff --git a/CathodeLib/Scripts/CATHODE/PhysicsMaps.cs b/CathodeLib/Scripts/CATHODE/PhysicsMaps.cs index 777a010..d193378 100644 --- a/CathodeLib/Scripts/CATHODE/PhysicsMaps.cs +++ b/CathodeLib/Scripts/CATHODE/PhysicsMaps.cs @@ -2,6 +2,9 @@ using System.Runtime.InteropServices; using CathodeLib; using System.Collections.Generic; +using CATHODE.Scripting; +using System; +using System.Linq; #if UNITY_EDITOR || UNITY_STANDALONE_WIN using UnityEngine; #else @@ -10,6 +13,8 @@ namespace CATHODE { + //This file defines additional info for entities with DYNAMIC_PHYSICS_SYSTEM resources. + /* DATA/ENV/PRODUCTION/x/WORLD/PHYSICS.MAP */ public class PhysicsMaps : CathodeFile { @@ -27,12 +32,15 @@ override protected bool LoadInternal() for (int i = 0; i < entryCount; i++) { Entry entry = new Entry(); - entry.UnknownNotableValue_ = reader.ReadInt32(); + entry.physics_system_index = reader.ReadInt32(); reader.BaseStream.Position += 4; - for (int x = 0; x < 4; x++) entry.IDs[x] = reader.ReadInt32(); + entry.resource_type = Utilities.Consume(reader); + entry.composite_instance_id = Utilities.Consume(reader); + entry.entity = Utilities.Consume(reader); entry.Row0 = Utilities.Consume(reader); entry.Row1 = Utilities.Consume(reader); entry.Row2 = Utilities.Consume(reader); + reader.BaseStream.Position += 8; Entries.Add(entry); } @@ -49,9 +57,11 @@ override protected bool SaveInternal() writer.Write(Entries.Count); for (int i = 0; i < Entries.Count; i++) { - writer.Write(Entries[i].UnknownNotableValue_); + writer.Write(Entries[i].physics_system_index); writer.Write(new byte[4]); - for (int x = 0; x < 4; x++) writer.Write(Entries[i].IDs[x]); + Utilities.Write(writer, Entries[i].resource_type); + Utilities.Write(writer, Entries[i].composite_instance_id); + Utilities.Write(writer, Entries[i].entity); Utilities.Write(writer, Entries[i].Row0); Utilities.Write(writer, Entries[i].Row1); Utilities.Write(writer, Entries[i].Row2); @@ -65,8 +75,19 @@ override protected bool SaveInternal() #region STRUCTURES public class Entry { - public int UnknownNotableValue_; - public int[] IDs = new int[4]; + //Should match system_index on the PhysicsSystem entity. + public int physics_system_index; + + //DYNAMIC_PHYSICS_SYSTEM + public ShortGuid resource_type; + + //This is the instance ID for the composite containing the PhysicsSystem. + //We do not need to worry about the entity ID for the PhysicsSystem as the resources are written to the composite that contains it. + public ShortGuid composite_instance_id; + + //This is the entity ID and instance ID for the actual instanced composite entity (basically, a step down from the instance above). + public CommandsEntityReference entity; + public Vector4 Row0; // NOTE: This is a 3x4 matrix, seems to have rotation data on the leftmost 3x3 matrix, and position public Vector4 Row1; // on the rightmost 3x1 matrix. public Vector4 Row2; diff --git a/CathodeLib/Scripts/CATHODE/Resources.cs b/CathodeLib/Scripts/CATHODE/Resources.cs index 91f940e..4b18865 100644 --- a/CathodeLib/Scripts/CATHODE/Resources.cs +++ b/CathodeLib/Scripts/CATHODE/Resources.cs @@ -15,8 +15,6 @@ public class Resources : CathodeFile public static new Implementation Implementation = Implementation.CREATE | Implementation.LOAD | Implementation.SAVE; public Resources(string path) : base(path) { } - //This file seems to govern data being drawn from either MVR or COMMANDS? - #region FILE_IO override protected bool LoadInternal() { @@ -29,8 +27,11 @@ override protected bool LoadInternal() for (int i = 0; i < entryCount; i++) { Resource resource = new Resource(); - resource.NodeID = Utilities.Consume(reader); - resource.IDFromMVREntry = reader.ReadInt32(); + + //TODO: I don't think this is as it seems... the composite_instance_id value often translates to a ShortGuid string, frequently Door/AnimatedModel/Light/DYNAMIC_PHYSICS_SYSTEM... + // ... and notably the number of entries that translate to DYNAMIC_PHYSICS_SYSTEM match the number of entries in PHYSICS.MAP (which defines the systems) + + resource.Entity = Utilities.Consume(reader); resource.IndexFromMVREntry = reader.ReadInt32(); Entries.Add(resource); } @@ -50,8 +51,7 @@ override protected bool SaveInternal() for (int i = 0; i < Entries.Count; i++) { - Utilities.Write(writer, Entries[i].NodeID); - writer.Write(Entries[i].IDFromMVREntry); + Utilities.Write(writer, Entries[i].Entity); writer.Write(Entries[i].IndexFromMVREntry); } } @@ -62,8 +62,7 @@ override protected bool SaveInternal() #region STRUCTURES public class Resource { - public ShortGuid NodeID; - public int IDFromMVREntry; //ResourceID? + public CommandsEntityReference Entity; public int IndexFromMVREntry; // NOTE: This is an entry index in this file itself. }; #endregion diff --git a/CathodeLib/Scripts/CATHODE/SoundNodeNetwork.cs b/CathodeLib/Scripts/CATHODE/SoundNodeNetwork.cs index 1feff21..2a959de 100644 --- a/CathodeLib/Scripts/CATHODE/SoundNodeNetwork.cs +++ b/CathodeLib/Scripts/CATHODE/SoundNodeNetwork.cs @@ -17,17 +17,212 @@ public class SoundNodeNetwork : CathodeFile public static new Implementation Implementation = Implementation.NONE; public SoundNodeNetwork(string path) : base(path) { } + private int _test = 0; + private int test + { + set + { + _test = value; + Console.WriteLine(_test); + } + get + { + return _test; + } + } + private float _testf = 0; + private float testf + { + set + { + _testf = value; + Console.WriteLine(_testf); + } + get + { + return _testf; + } + } + + //This file is seemingly somewhat ordered by size? Biggest listener networks get their headers written first ish. + #region FILE_IO override protected bool LoadInternal() { using (BinaryReader reader = new BinaryReader(File.OpenRead(_filepath))) { reader.BaseStream.Position += 4; - int unk1 = reader.ReadInt16(); - int unk2 = reader.ReadInt16(); - int strLength = reader.ReadInt16(); - string str = ""; - for (int i = 0; i < strLength; i++) str += reader.ReadChar(); + int count1 = reader.ReadInt16(); + int entryCount = reader.ReadInt16(); + for (int x = 0; x < entryCount; x++) + { + if (Entries.Count != 0 && Entries[Entries.Count - 1] == "Corridor Junction Area") + { + string sddsf = ""; + } + + int strLength = reader.ReadInt16(); + string listenerName = ""; //confirmed via dev screenshot + for (int i = 0; i < strLength; i++) listenerName += reader.ReadChar(); + Entries.Add(listenerName); + + //start header + Console.WriteLine("start header"); + + int typeID = reader.ReadInt16(); + + test = reader.ReadInt16(); + test = reader.ReadInt16(); + + test = reader.ReadInt16(); + test = reader.ReadInt16(); + + testf = reader.ReadSingle(); //always 1? + if (testf != 1) + { + throw new Exception(""); + } + + testf = reader.ReadSingle(); + testf = reader.ReadSingle(); + testf = reader.ReadSingle(); + + testf = reader.ReadSingle(); + testf = reader.ReadSingle(); + testf = reader.ReadSingle(); + + //Somewhere here we should have an Ambience sound event (maybe start/stop?) + + //I'm unsure if these are actually int16 + test = reader.ReadInt16(); + test = reader.ReadInt16(); + test = reader.ReadInt16(); + + if (test == 0) + { + string sdfdf = ""; + continue; //44 bytes + } + //^ doing this gets us a bit further on BSP_TORRENS, but we still crash after "Torrens Bridge Corridor" due to something there. + + //I'm unsure if these are actually int16 + test = reader.ReadInt16(); + test = reader.ReadInt16(); + + Console.WriteLine("Header Over"); + + //end next bit (48) <- this is all useful above here but just skipping for now, figure out datatypes from ps3 dump + + //TODO: typeID is not the type ID as i expected, since two type 0's load differently on tech_hub + + switch (typeID) + { + //case 0: + // reader.BaseStream.Position = 13044; //temp hack + // break; + default: + //if (typeID == 0) + //{ + // string dafssdf = ""; + //} + + Console.WriteLine("!!!! loading type: " + typeID); + LoadRecursive(reader); + break; + } + } + + + + + //footer? noticing a pattern here + + test = reader.ReadInt32(); + test = reader.ReadInt32(); + test = reader.ReadInt32(); + + test = reader.ReadInt16(); + test = reader.ReadInt16(); + test = reader.ReadByte(); + test = reader.ReadByte(); + + test = reader.ReadInt16(); + test = reader.ReadByte(); + test = reader.ReadByte(); + + test = reader.ReadInt16(); + test = reader.ReadByte(); + test = reader.ReadByte(); + + test = reader.ReadInt16(); + test = reader.ReadByte(); + test = reader.ReadByte(); + + test = reader.ReadInt16(); + test = reader.ReadByte(); + test = reader.ReadByte(); + + test = reader.ReadInt16(); + + // -- + + test = reader.ReadInt32(); + test = reader.ReadInt32(); + test = reader.ReadInt32(); + + test = reader.ReadInt16(); + test = reader.ReadInt16(); + test = reader.ReadByte(); + test = reader.ReadByte(); + + test = reader.ReadInt16(); + test = reader.ReadByte(); + test = reader.ReadByte(); + + test = reader.ReadInt16(); + test = reader.ReadByte(); + test = reader.ReadByte(); + + test = reader.ReadInt16(); + test = reader.ReadByte(); + test = reader.ReadByte(); + + test = reader.ReadInt16(); + + // -- + + test = reader.ReadInt32(); + test = reader.ReadInt32(); + test = reader.ReadInt32(); + + test = reader.ReadInt16(); + test = reader.ReadInt16(); + test = reader.ReadByte(); + test = reader.ReadByte(); + + test = reader.ReadInt16(); + test = reader.ReadByte(); + test = reader.ReadByte(); + + test = reader.ReadInt16(); + test = reader.ReadByte(); + test = reader.ReadByte(); + + test = reader.ReadInt16(); + + // --- + + test = reader.ReadInt32(); + test = reader.ReadInt32(); + test = reader.ReadInt32(); + test = reader.ReadInt16(); + test = reader.ReadInt16(); + + string bruh = ""; + + //38 + reader.BaseStream.Position += 38; + int a = reader.ReadInt16(); int b = reader.ReadInt16(); int c = reader.ReadInt16(); @@ -54,5 +249,33 @@ override protected bool SaveInternal() return true; } #endregion + + private void LoadRecursive(BinaryReader reader) + { + test = reader.ReadInt16(); //small + test = reader.ReadInt16(); //bigger + + int countOne = reader.ReadInt16(); //count of next block + Console.WriteLine("Count of block 1: " + countOne); + + for (int z = 0; z < countOne; z++) + { + test = reader.ReadInt16(); //something index + int countTwo = reader.ReadInt16(); //count of next ints + Console.WriteLine("Count of block 2: " + countTwo); + + if (countTwo == 0) + { + LoadRecursive(reader); + + break; //?? + } + + for (int i = 0; i < countTwo; i++) + { + test = reader.ReadInt32(); //probs indexes to the float array at the bottom of the file + } + } + } } } \ No newline at end of file diff --git a/CathodeLib/Scripts/LEGACY_DAN/ShadersPAK.cs b/CathodeLib/Scripts/LEGACY_DAN/ShadersPAK.cs index a1dc64e..df3541f 100644 --- a/CathodeLib/Scripts/LEGACY_DAN/ShadersPAK.cs +++ b/CathodeLib/Scripts/LEGACY_DAN/ShadersPAK.cs @@ -409,8 +409,10 @@ public ShaderMaterialMetadata GetMaterialMetadataFromShader(Materials.Material I //if (Shader.Header.TextureLinkCount != metadata.textures.Count) throw new Exception("bruh"); - for (int i = 0; i < Shader.Header.TextureLinkCount; ++i) + for (int i = 0; i < Shader.Header.TextureLinkCount; ++i) { + if (i >= metadata.textures.Count) break; //This should no longer be an issue when the shader categories are completed above. + int PairIndex = Shader.TextureLinks[i]; // NOTE: PairIndex == 255 means no index. if (PairIndex < InMaterial.TextureReferences.Length) diff --git a/CathodeLib/Scripts/Level.cs b/CathodeLib/Scripts/Level.cs index b116252..02834d4 100644 --- a/CathodeLib/Scripts/Level.cs +++ b/CathodeLib/Scripts/Level.cs @@ -273,5 +273,33 @@ public void Save() } Materials.Save(); } + + /* Get all levels available within the ENV folder. Pass the path to the folder that contains AI.exe. */ + public static List GetLevels(string gameDirectory, bool swapNostromoForPatch = false) + { + string[] galaxyBins = Directory.GetFiles(gameDirectory + "/DATA/ENV/PRODUCTION/", "GALAXY.DEFINITION_BIN", SearchOption.AllDirectories); + List mapList = new List(); + for (int i = 0; i < galaxyBins.Length; i++) + { + int extraLength = ("/RENDERABLE/GALAXY/GALAXY.DEFINITION_BIN").Length; + string mapPath = galaxyBins[i].Substring(0, galaxyBins[i].Length - extraLength); + + //Try match a few files outside of the GALAXY definition, to ensure we are actually a map. + if (!File.Exists(mapPath + "/WORLD/COMMANDS.PAK")) continue; + if (!File.Exists(mapPath + "/WORLD/MODELS.MVR")) continue; + if (!File.Exists(mapPath + "/RENDERABLE/LEVEL_MODELS.PAK")) continue; + if (!File.Exists(mapPath + "/RENDERABLE/MODELS_LEVEL.BIN")) continue; + + string[] split = galaxyBins[i].Replace("\\", "/").Split(new[] { "/DATA/ENV/PRODUCTION/" }, StringSplitOptions.None); + string file = split[split.Length - 1]; + int length = file.Length - extraLength; + if (length <= 0) continue; + + string mapName = file.Substring(0, length); + if (swapNostromoForPatch && (mapName == "DLC/BSPNOSTROMO_RIPLEY" || mapName == "DLC/BSPNOSTROMO_TWOTEAMS")) mapName += "_PATCH"; + mapList.Add(mapName); + } + return mapList; + } } } diff --git a/CathodeLib/Scripts/Utilities.cs b/CathodeLib/Scripts/Utilities.cs index d66ca75..6bd9a68 100644 --- a/CathodeLib/Scripts/Utilities.cs +++ b/CathodeLib/Scripts/Utilities.cs @@ -1,3 +1,4 @@ +using CATHODE.Scripting; using System; using System.Collections.Generic; using System.IO; @@ -47,6 +48,21 @@ public static void Align(BinaryWriter writer, int val = 4, byte fillWith = 0x00) while (writer.BaseStream.Position % val != 0) writer.Write(fillWith); } + //Reads a string with alternating nulls up to a trailing 0x00 byte + public static string ReadStringAlternating(byte[] bytes) + { + byte[] trimmed = new byte[bytes.Length / 2]; + int x = 0; + for (int i = 0; i < bytes.Length; i++) + { + if (i % 2 != 0) continue; + trimmed[x] = bytes[i]; + if (bytes[i] == 0x00) break; + x++; + } + return ReadString(trimmed); + } + //Reads a string up to a trailing 0x00 byte public static string ReadString(byte[] bytes, int position) { @@ -282,6 +298,25 @@ public OffsetPair(long _go, int _ec) EntryCount = _ec; } } + + /// + /// This acts as a helper class for the link between legacy data and Commands. MVR, resources, and character assets use this link. + /// It defines the ID of the entity we're interested in and the instance ID of the composite that contains it. + /// The instance ID identifies the hierarchy the composite was created at. This can be generated with GenerateInstance. + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public class CommandsEntityReference + { + public ShortGuid entity_id; //The ID of the entity within its written composite + public ShortGuid composite_instance_id; //The instance of the composite this entity is in when created via hierarchy + + public CommandsEntityReference() { } + public CommandsEntityReference(EntityHierarchy hierarchy) + { + entity_id = hierarchy.GetPointedEntityID(); + composite_instance_id = hierarchy.GenerateInstance(); + } + } /* [Serializable] diff --git a/README.md b/README.md index 90d24cf..90f1e01 100644 --- a/README.md +++ b/README.md @@ -59,6 +59,10 @@ Check out a full overview of the Commands structure [on the Wiki](https://github - `CATHODE.SoundEnvironmentData` handles `SOUNDENVIRONMENTDATA.DAT` files - `CATHODE.SoundDialogueLookups` handles `SOUNDDIALOGUELOOKUPS.DAT` files - `CATHODE.SoundBankData` handles `SOUNDBANKDATA.DAT` files +- `CATHODE.CharacterAccessorySets` handles `CHARACTERACCESSORYSETS.BIN` files +- `CATHODE.CustomCharacterInfo` handles `CUSTOMCHARACTERINFO.BIN` files +- `CATHODE.CustomCharacterConstrainedComponents` handles `CUSTOMCHARACTERCONSTRAINEDCOMPONENTS.BIN` files +- `CATHODE.Strings` handles `*.TXT` files ## For configurations: - `CATHODE.BML` handles any `.BML` files @@ -66,7 +70,7 @@ Check out a full overview of the Commands structure [on the Wiki](https://github ## For saves: - `CATHODE.ProgressionSave` handles `PROGRESSION.AIS` files -- `CATHODE.EXPERIMENTAL.MissionSave` handles `PROGRESSION.AIS` files (experimental) +- `CATHODE.EXPERIMENTAL.MissionSave` handles `*.AIS` files (experimental) ---