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)
---