diff --git a/AlienBML b/AlienBML
index db257eb..7369624 160000
--- a/AlienBML
+++ b/AlienBML
@@ -1 +1 @@
-Subproject commit db257eb5d9258a4108bde88d26d52ddd69d42ba9
+Subproject commit 73696246f6f2a5ad358f7aed408f53fd20639b92
diff --git a/CathodeLib/CathodeLib.csproj b/CathodeLib/CathodeLib.csproj
index dafc6ef..a9d2119 100644
--- a/CathodeLib/CathodeLib.csproj
+++ b/CathodeLib/CathodeLib.csproj
@@ -9,11 +9,11 @@
CathodeLib
Matt Filer
Provides support for parsing and writing common Alien: Isolation formats from the Cathode engine.
- Matt Filer 2022
- 0.2.0
+ Matt Filer 2023
+ 0.3.0
Library
- 0.2.0.0
- 0.2.0.0
+ 0.3.0.0
+ 0.3.0.0
False
@@ -39,9 +39,7 @@
-
-
-
+
@@ -73,8 +71,4 @@
-
-
-
-
diff --git a/CathodeLib/Resources/NodeDBs/cathode_enum_lut.bin b/CathodeLib/Resources/NodeDBs/cathode_enum_lut.bin
index 0cebee1..06a6a42 100644
Binary files a/CathodeLib/Resources/NodeDBs/cathode_enum_lut.bin and b/CathodeLib/Resources/NodeDBs/cathode_enum_lut.bin differ
diff --git a/CathodeLib/Scripts/AlienLevel.cs b/CathodeLib/Scripts/AlienLevel.cs
deleted file mode 100644
index 247572d..0000000
--- a/CathodeLib/Scripts/AlienLevel.cs
+++ /dev/null
@@ -1,108 +0,0 @@
-using System.IO;
-
-/*
-namespace CathodeLib
-{
- public class AlienLevel
- {
- public static alien_level Load(string LEVEL_NAME, string ENV_PATH)
- {
- alien_level Result = new alien_level();
- string levelPath = ENV_PATH + "/PRODUCTION/" + LEVEL_NAME;
-
- /*** WORLD ***/
-
-/*
- Result.ModelsMVR = new CATHODE.Models.CathodeMovers(levelPath + "/WORLD/MODELS.MVR");
- Result.CommandsPAK = new CATHODE.Commands.CommandsPAK(levelPath + "/WORLD/COMMANDS.PAK");
-
- Result.RenderableREDS = new CATHODE.Misc.CathodeRenderableElements(levelPath + "/WORLD/REDS.BIN");
- Result.ResourcesBIN = new CATHODE.Misc.CathodeResources(levelPath + "/WORLD/RESOURCES.BIN");
- Result.PhysicsMap = new CATHODE.Misc.CathodePhysicsMap(levelPath + "/WORLD/PHYSICS.MAP");
- Result.EnvironmentMap = new CATHODE.Misc.CathodeEnvironmentMap(levelPath + "/WORLD/ENVIRONMENTMAP.BIN");
- Result.CollisionMap = new CATHODE.Misc.CathodeCollisionMap(levelPath + "/WORLD/COLLISION.MAP");
-
- Result.EnvironmentAnimation = CATHODE.Misc.CathodeEnvironmentAnimation.Load(levelPath + "/WORLD/ENVIRONMENT_ANIMATION.DAT");
-
- //ALPHALIGHT_LEVEL.BIN
- //BEHAVIOR_TREE.DB
- //CHARACTERACCESSORYSETS.BIN
- //COLLISION.BIN
- //COLLISION.HKX
- //COLLISION.HKX64
- //CUTSCENE_DIRECTOR_DATA.BIN
- //EXCLUSIVE_MASTER_RESOURCE_INDICES
- //LEVEL.STR
- //LIGHTS.BIN
- //MATERIAL_MAPPINGS.PAK
- //MORPH_TARGET_DB.BIN
- //OCCLUDER_TRIANGLE_BVH.BIN
- //PATH_BARRIER_RESOURCES
- //PHYSICS.HKX
- //PHYSICS.HKX64
- //RADIOSITY_COLLISION_MAPPING.BIN
- //SNDNODENETWORK.DAT
- //SOUNDBANKDATA.DAT
- //SOUNDDIALOGUELOOKUPS.DAT
- //SOUNDENVIRONMENTDATA.DAT
- //SOUNDEVENTDATA.DAT
- //SOUNDFLASHMODELS.DAT
- //SOUNDLOADZONES.DAT
- //STATE_x/ASSAULT_POSITIONS
- //STATE_x/COVER
- //STATE_x/CRAWL_SPACE_SPOTTING_POSITIONS
- //STATE_x/NAV_MESH
- //STATE_x/SPOTTING_POSITIONS
- //STATE_x/TRAVERSAL
-
- /*** RENDERABLE ***/
-
-/*
-Result.ModelsCST = File.ReadAllBytes(levelPath + "/RENDERABLE/LEVEL_MODELS.CST");
- Result.ModelsMTL = CATHODE.Models.CathodeMaterials.Load(levelPath + "/RENDERABLE/LEVEL_MODELS.MTL");
- Result.ModelsPAK = CATHODE.Models.ModelPAK.Load(levelPath + "/RENDERABLE/LEVEL_MODELS.PAK");
- Result.ModelsBIN = CATHODE.Models.ModelBIN.Load(levelPath + "/RENDERABLE/MODELS_LEVEL.BIN");
-
- Result.ShadersPAK = CATHODE.Shaders.ShadersPAK.Load(levelPath + "/RENDERABLE/LEVEL_SHADERS_DX11.PAK");
- //Result.ShadersBIN = CATHODE.Shaders.ShadersBIN.Load(levelPath + "/RENDERABLE/LEVEL_SHADERS_DX11_BIN.PAK");
- Result.ShadersIDXRemap = CATHODE.Shaders.IDXRemap.Load(levelPath + "/RENDERABLE/LEVEL_SHADERS_DX11_IDX_REMAP.PAK");
-
- Result.LevelTextures = CATHODE.Textures.CathodeTextures.Load(levelPath + "/RENDERABLE/LEVEL_TEXTURES.ALL.PAK", levelPath + "/RENDERABLE/LEVEL_TEXTURE_HEADERS.ALL.BIN");
-
- //LEVEL_TEXTURES.DX11.PAK
- //RADIOSITY_INSTANCE_MAP.TXT
- //RADIOSITY_RUNTIME.BIN
- //DAMAGE/DAMAGE_MAPPING_INFO.BIN
- //GALAXY/GALAXY.DEFINITION_BIN
- //GALAXY/GALAXY.ITEMS_BIN
-
- return Result;
- }
- }
-}
-
-public class alien_level
-{
- public CATHODE.Models.CathodeMovers ModelsMVR;
- public CATHODE.Commands.CommandsPAK CommandsPAK;
-
- public CATHODE.Misc.CathodeRenderableElements RenderableREDS;
- public CATHODE.Misc.CathodeResources ResourcesBIN;
- public CATHODE.Misc.CathodePhysicsMap PhysicsMap;
- public CATHODE.Misc.CathodeEnvironmentMap EnvironmentMap;
- public CATHODE.Misc.CathodeCollisionMap CollisionMap;
-
- public alien_animation_dat EnvironmentAnimation;
-
- public byte[] ModelsCST;
- public alien_mtl ModelsMTL;
- public alien_pak_model ModelsPAK;
- public alien_model_bin ModelsBIN;
-
- public alien_textures LevelTextures;
-
- public alien_shader_pak ShadersPAK;
- public alien_shader_bin_pak ShadersBIN;
- public alien_shader_idx_remap ShadersIDXRemap;
-};
-*/
\ No newline at end of file
diff --git a/CathodeLib/Scripts/AssetPAKs/AssetPAK.cs b/CathodeLib/Scripts/AssetPAKs/AssetPAK.cs
index d8bc1bc..d4a14c4 100644
--- a/CathodeLib/Scripts/AssetPAKs/AssetPAK.cs
+++ b/CathodeLib/Scripts/AssetPAKs/AssetPAK.cs
@@ -6,8 +6,8 @@ namespace CATHODE.Assets
{
public class AssetPAK
{
- protected string FilePathPAK = "";
- protected string FilePathBIN = "";
+ protected string _filePathPAK = "";
+ protected string _filePathBIN = "";
virtual public PAKReturnType Load() { return PAKReturnType.FAIL_FEATURE_IS_COMING_SOON; }
virtual public List GetFileNames() { return null; }
@@ -42,4 +42,17 @@ public enum PAKReturnType
SUCCESS,
SUCCESS_WITH_WARNINGS
};
+
+ public enum FileIdentifiers
+ {
+ ASSET_FILE = 14,
+
+ SHADER_DATA = 3,
+ MODEL_DATA = 19,
+ TEXTURE_DATA = 45,
+
+ //From ABOUT.TXT (unsure where used)
+ STRING_FILE_VERSION = 6,
+ ENTITY_FILE_VERSION = 171,
+ }
}
diff --git a/CathodeLib/Scripts/AssetPAKs/Handlers/MaterialMapping.cs b/CathodeLib/Scripts/AssetPAKs/Handlers/MaterialMapping.cs
index 5ac41f7..9f8e6ba 100644
--- a/CathodeLib/Scripts/AssetPAKs/Handlers/MaterialMapping.cs
+++ b/CathodeLib/Scripts/AssetPAKs/Handlers/MaterialMapping.cs
@@ -17,19 +17,19 @@ namespace CATHODE.Assets
*/
public class MaterialMapping : AssetPAK
{
- List MaterialMappingEntries = new List();
- byte[] FileHeaderJunk = new byte[8];
+ List _entries = new List();
+ byte[] _headerJunk = new byte[8];
/* Initialise the MaterialMapPAK class with the intended location (existing or not) */
public MaterialMapping(string PathToPAK)
{
- FilePathPAK = PathToPAK;
+ _filePathPAK = PathToPAK;
}
/* Load the contents of an existing MaterialMapPAK */
public override PAKReturnType Load()
{
- if (!File.Exists(FilePathPAK))
+ if (!File.Exists(_filePathPAK))
{
return PAKReturnType.FAIL_TRIED_TO_LOAD_VIRTUAL_ARCHIVE;
}
@@ -37,45 +37,45 @@ public override PAKReturnType Load()
try
{
//Open PAK
- BinaryReader ArchiveFile = new BinaryReader(File.OpenRead(FilePathPAK));
+ BinaryReader pak = new BinaryReader(File.OpenRead(_filePathPAK));
//Parse header
- FileHeaderJunk = ArchiveFile.ReadBytes(8); //TODO: Work out what this contains
- int NumberOfFiles = ArchiveFile.ReadInt32();
+ _headerJunk = pak.ReadBytes(8); //TODO: Work out what this contains
+ int entryCount = pak.ReadInt32();
//Parse entries (XML is broken in the build files - doesn't get shipped)
- for (int x = 0; x < NumberOfFiles; x++)
+ for (int x = 0; x < entryCount; x++)
{
//This entry
- EntryMaterialMappingsPAK NewMatEntry = new EntryMaterialMappingsPAK();
- NewMatEntry.MapHeader = ArchiveFile.ReadBytes(4); //TODO: Work out the significance of this value, to be able to construct new PAKs from scratch.
- NewMatEntry.MapEntryCoupleCount = ArchiveFile.ReadInt32();
- NewMatEntry.MapJunk = ArchiveFile.ReadBytes(4); //TODO: Work out if this is always null.
- for (int p = 0; p < (NewMatEntry.MapEntryCoupleCount * 2) + 1; p++)
+ EntryMaterialMappingsPAK entry = new EntryMaterialMappingsPAK();
+ entry.MapHeader = pak.ReadBytes(4); //TODO: Work out the significance of this value, to be able to construct new PAKs from scratch.
+ entry.MapEntryCoupleCount = pak.ReadInt32();
+ entry.MapJunk = pak.ReadBytes(4); //TODO: Work out if this is always null.
+ for (int p = 0; p < (entry.MapEntryCoupleCount * 2) + 1; p++)
{
//String
- int NewMatStringLength = ArchiveFile.ReadInt32();
- string NewMatString = "";
- for (int i = 0; i < NewMatStringLength; i++)
+ int length = pak.ReadInt32();
+ string materialString = "";
+ for (int i = 0; i < length; i++)
{
- NewMatString += ArchiveFile.ReadChar();
+ materialString += pak.ReadChar();
}
//First string is filename, others are materials
if (p == 0)
{
- NewMatEntry.MapFilename = NewMatString;
+ entry.MapFilename = materialString;
}
else
{
- NewMatEntry.MapMatEntries.Add(NewMatString);
+ entry.MapMatEntries.Add(materialString);
}
}
- MaterialMappingEntries.Add(NewMatEntry);
+ _entries.Add(entry);
}
//Done!
- ArchiveFile.Close();
+ pak.Close();
return PAKReturnType.SUCCESS;
}
catch (IOException) { return PAKReturnType.FAIL_COULD_NOT_ACCESS_FILE; }
@@ -86,7 +86,7 @@ public override PAKReturnType Load()
public override List GetFileNames()
{
List FileNameList = new List();
- foreach (EntryMaterialMappingsPAK MapEntry in MaterialMappingEntries)
+ foreach (EntryMaterialMappingsPAK MapEntry in _entries)
{
FileNameList.Add(MapEntry.MapFilename);
}
@@ -97,7 +97,7 @@ public override List GetFileNames()
public override int GetFilesize(string FileName)
{
int size = 0;
- foreach (string MatMap in MaterialMappingEntries[GetFileIndex(FileName)].MapMatEntries)
+ foreach (string MatMap in _entries[GetFileIndex(FileName)].MapMatEntries)
{
size += MatMap.Length;
}
@@ -107,9 +107,9 @@ public override int GetFilesize(string FileName)
/* Find the entry object by name */
public override int GetFileIndex(string FileName)
{
- for (int i = 0; i < MaterialMappingEntries.Count; i++)
+ for (int i = 0; i < _entries.Count; i++)
{
- if (MaterialMappingEntries[i].MapFilename == FileName || MaterialMappingEntries[i].MapFilename == FileName.Replace('/', '\\'))
+ if (_entries[i].MapFilename == FileName || _entries[i].MapFilename == FileName.Replace('/', '\\'))
{
return i;
}
@@ -124,7 +124,7 @@ public override PAKReturnType ReplaceFile(string PathToNewFile, string FileName)
{
//Pull the new mapping info from the import XML
XDocument InputFile = XDocument.Load(PathToNewFile);
- EntryMaterialMappingsPAK MaterialMapping = MaterialMappingEntries[GetFileIndex(FileName)];
+ EntryMaterialMappingsPAK MaterialMapping = _entries[GetFileIndex(FileName)];
List NewOverrides = new List();
foreach (XElement ThisMap in InputFile.Element("material_mappings").Elements())
{
@@ -155,7 +155,7 @@ public override PAKReturnType ExportFile(string PathToExport, string FileName)
XElement MaterialPair = XElement.Parse("");
int ThisEntryNum = 0;
- EntryMaterialMappingsPAK ThisEntry = MaterialMappingEntries[GetFileIndex(FileName)];
+ EntryMaterialMappingsPAK ThisEntry = _entries[GetFileIndex(FileName)];
for (int i = 0; i < ThisEntry.MapEntryCoupleCount; i++)
{
for (int x = 0; x < 2; x++)
@@ -191,24 +191,24 @@ public override PAKReturnType Save()
try
{
//Re-write out to the PAK
- BinaryWriter ArchiveWriter = new BinaryWriter(File.OpenWrite(FilePathPAK));
- ArchiveWriter.BaseStream.SetLength(0);
- ArchiveWriter.Write(FileHeaderJunk);
- ArchiveWriter.Write(MaterialMappingEntries.Count);
- foreach (EntryMaterialMappingsPAK ThisMatRemap in MaterialMappingEntries)
+ BinaryWriter pak = new BinaryWriter(File.OpenWrite(_filePathPAK));
+ pak.BaseStream.SetLength(0);
+ pak.Write(_headerJunk);
+ pak.Write(_entries.Count);
+ foreach (EntryMaterialMappingsPAK entry in _entries)
{
- ArchiveWriter.Write(ThisMatRemap.MapHeader);
- ArchiveWriter.Write(ThisMatRemap.MapEntryCoupleCount);
- ArchiveWriter.Write(ThisMatRemap.MapJunk);
- ArchiveWriter.Write(ThisMatRemap.MapFilename.Length);
- ExtraBinaryUtils.WriteString(ThisMatRemap.MapFilename, ArchiveWriter);
- foreach (string MaterialName in ThisMatRemap.MapMatEntries)
+ pak.Write(entry.MapHeader);
+ pak.Write(entry.MapEntryCoupleCount);
+ pak.Write(entry.MapJunk);
+ pak.Write(entry.MapFilename.Length);
+ ExtraBinaryUtils.WriteString(entry.MapFilename, pak);
+ foreach (string name in entry.MapMatEntries)
{
- ArchiveWriter.Write(MaterialName.Length);
- ExtraBinaryUtils.WriteString(MaterialName, ArchiveWriter);
+ pak.Write(name.Length);
+ ExtraBinaryUtils.WriteString(name, pak);
}
}
- ArchiveWriter.Close();
+ pak.Close();
return PAKReturnType.SUCCESS;
}
diff --git a/CathodeLib/Scripts/AssetPAKs/Handlers/Models.cs b/CathodeLib/Scripts/AssetPAKs/Handlers/Models.cs
index 354c282..140a843 100644
--- a/CathodeLib/Scripts/AssetPAKs/Handlers/Models.cs
+++ b/CathodeLib/Scripts/AssetPAKs/Handlers/Models.cs
@@ -1,6 +1,9 @@
using System;
+using System.Buffers.Binary;
using System.Collections.Generic;
using System.IO;
+using System.Linq;
+using System.Runtime.InteropServices;
using CathodeLib;
namespace CATHODE.Assets
@@ -14,139 +17,120 @@ namespace CATHODE.Assets
*/
public class Models : AssetPAK
{
- List ModelEntries = new List();
- int TableCountPt1 = -1;
- int TableCountPt2 = -1;
- int FilenameListEnd = -1;
- int HeaderListEnd = -1;
- public int _hle { get { return HeaderListEnd; } } //Exposing this for testing purposes
+ List _metadata = new List();
+ List _vertexFormats = new List();
+
+ public List _filePaths;
+ public List _partNames;
/* Initialise the ModelPAK class with the intended location (existing or not) */
public Models(string PathToPAK)
{
- FilePathPAK = PathToPAK;
- FilePathBIN = FilePathPAK.Substring(0, FilePathPAK.Length - Path.GetFileName(FilePathPAK).Length) + "MODELS_" + Path.GetFileName(FilePathPAK).Substring(0, Path.GetFileName(FilePathPAK).Length - 11) + ".BIN";
+ _filePathPAK = PathToPAK;
+ _filePathBIN = _filePathPAK.Substring(0, _filePathPAK.Length - Path.GetFileName(_filePathPAK).Length) + "MODELS_" + Path.GetFileName(_filePathPAK).Substring(0, Path.GetFileName(_filePathPAK).Length - 11) + ".BIN";
}
/* Load the contents of an existing ModelPAK */
public override PAKReturnType Load()
{
- if (!File.Exists(FilePathPAK))
+ if (!File.Exists(_filePathPAK))
{
return PAKReturnType.FAIL_TRIED_TO_LOAD_VIRTUAL_ARCHIVE;
}
- /* TODO: Verify the PAK loading is a ModelPAK by BIN version number */
-
try
- {
- //First, parse the MTL file to find material info
- string PathToMTL = FilePathPAK.Substring(0, FilePathPAK.Length - 3) + "MTL";
- BinaryReader ArchiveFileMtl = new BinaryReader(File.OpenRead(PathToMTL));
-
- //Header
- ArchiveFileMtl.BaseStream.Position += 40; //There are some knowns here, just not required for us yet
- int MaterialEntryCount = ArchiveFileMtl.ReadInt16();
- ArchiveFileMtl.BaseStream.Position += 2; //Skip unknown
-
- //Strings - more work will be done on materials eventually,
- //but taking their names for now is good enough for model export
- List MaterialEntries = new List();
- string ThisMaterialString = "";
- for (int i = 0; i < MaterialEntryCount; i++)
- {
- while (true)
- {
- byte ThisByte = ArchiveFileMtl.ReadByte();
- if (ThisByte == 0x00)
- {
- MaterialEntries.Add(ThisMaterialString);
- ThisMaterialString = "";
- break;
- }
- ThisMaterialString += (char)ThisByte;
- }
- }
- ArchiveFileMtl.Close();
-
- //Read the header info from BIN
- BinaryReader ArchiveFileBin = new BinaryReader(File.OpenRead(FilePathBIN));
- ArchiveFileBin.BaseStream.Position += 4; //Skip magic
- TableCountPt2 = ArchiveFileBin.ReadInt32();
- ArchiveFileBin.BaseStream.Position += 4; //Skip unknown
- TableCountPt1 = ArchiveFileBin.ReadInt32();
-
- //Skip past table 1
- for (int i = 0; i < TableCountPt1; i++)
- {
- byte ThisByte = 0x00;
- while (ThisByte != 0xFF)
- {
- ThisByte = ArchiveFileBin.ReadByte();
- }
+ {
+ #region MODEL_BIN
+ //Read the header info from BIN
+ BinaryReader bin = new BinaryReader(File.OpenRead(_filePathBIN));
+ bin.BaseStream.Position += 4; //Magic
+ int modelCount = bin.ReadInt32();
+ bin.BaseStream.Position += 4; //Unknown
+ int vbfCount = bin.ReadInt32();
+
+ //Read all vertex buffer formats
+ _vertexFormats = new List(vbfCount);
+ for (int EntryIndex = 0; EntryIndex < vbfCount; ++EntryIndex)
+ {
+ long startPos = bin.BaseStream.Position;
+ int count = 1;
+ while (bin.ReadByte() != 0xFF)
+ {
+ bin.BaseStream.Position += Marshal.SizeOf(typeof(AlienVBFE)) - 1;
+ count++;
+ }
+ bin.BaseStream.Position = startPos;
+
+ AlienVBF VertexInput = new AlienVBF();
+ VertexInput.ElementCount = count;
+ VertexInput.Elements = CathodeLib.Utilities.ConsumeArray(bin, VertexInput.ElementCount).ToList();
+ _vertexFormats.Add(VertexInput);
}
- ArchiveFileBin.BaseStream.Position += 23;
-
- //Read file list info
- FilenameListEnd = ArchiveFileBin.ReadInt32();
- int FilenameListStart = (int)ArchiveFileBin.BaseStream.Position;
-
- //Read all file names (bytes)
- byte[] filename_bytes = ArchiveFileBin.ReadBytes(FilenameListEnd);
-
- //Read table 2 (skipping all unknowns for now)
- for (int i = 0; i < TableCountPt2; i++)
- {
- CS2 new_entry = new CS2();
- new_entry.FilenameOffset = ArchiveFileBin.ReadInt32();
- new_entry.Filename = ExtraBinaryUtils.GetStringFromByteArray(filename_bytes, new_entry.FilenameOffset);
- ArchiveFileBin.BaseStream.Position += 4;
- new_entry.ModelPartNameOffset = ArchiveFileBin.ReadInt32();
- new_entry.ModelPartName = ExtraBinaryUtils.GetStringFromByteArray(filename_bytes, new_entry.ModelPartNameOffset);
- ArchiveFileBin.BaseStream.Position += 44;
- new_entry.MaterialLibaryIndex = ArchiveFileBin.ReadInt32();
- new_entry.MaterialName = MaterialEntries[new_entry.MaterialLibaryIndex];
- ArchiveFileBin.BaseStream.Position += 8;
- new_entry.BlockSize = ArchiveFileBin.ReadInt32();
- ArchiveFileBin.BaseStream.Position += 14;
- new_entry.ScaleFactor = ArchiveFileBin.ReadInt16(); //Maybe?
- ArchiveFileBin.BaseStream.Position += 2;
- new_entry.VertCount = ArchiveFileBin.ReadInt16();
- new_entry.FaceCount = ArchiveFileBin.ReadInt16();
- new_entry.BoneCount = ArchiveFileBin.ReadInt16();
- ModelEntries.Add(new_entry);
- }
- ArchiveFileBin.Close();
-
- //Get extra info from each header in the PAK
- BinaryReader ArchiveFile = new BinaryReader(File.OpenRead(FilePathPAK));
- ArchiveFile.BaseStream.Position += 32; //Skip header
- for (int i = 0; i < TableCountPt2; i++)
- {
- ArchiveFile.BaseStream.Position += 8; //Skip unknowns
- int ThisPakSize = BigEndianUtils.ReadInt32(ArchiveFile);
- if (ThisPakSize != BigEndianUtils.ReadInt32(ArchiveFile))
- {
- //Dud entry... handle this somehow?
- }
- int ThisPakOffset = BigEndianUtils.ReadInt32(ArchiveFile);
- ArchiveFile.BaseStream.Position += 14;
- int ThisIndex = BigEndianUtils.ReadInt16(ArchiveFile);
- ArchiveFile.BaseStream.Position += 12;
-
- if (ThisIndex == -1)
- {
- continue; //Again, dud entry. Need to look into this!
- }
- //Push it into the correct entry
- ModelEntries[ThisIndex].PakSize = ThisPakSize;
- ModelEntries[ThisIndex].PakOffset = ThisPakOffset;
+ //Read filename chunk
+ byte[] filenames = bin.ReadBytes(bin.ReadInt32());
+
+ //Read all model metadata
+ _metadata = CathodeLib.Utilities.ConsumeArray(bin, modelCount).ToList();
+
+ //Fetch filenames from chunk
+ _filePaths = new List();
+ _partNames = new List();
+ for (int i = 0; i < _metadata.Count; ++i)
+ {
+ _filePaths.Add(CathodeLib.Utilities.ReadString(filenames, _metadata[i].FileNameOffset).Replace('\\', '/'));
+ _partNames.Add(CathodeLib.Utilities.ReadString(filenames, _metadata[i].ModelPartNameOffset).Replace('\\', '/'));
+ }
+
+ //Read bone chunk
+ byte[] BoneBuffer = bin.ReadBytes(bin.ReadInt32());
+ //TODO: Parse bone chunk!
+
+ bin.Close();
+ #endregion
+
+ #region MODEL_PAK
+ BinaryReader pak = new BinaryReader(File.OpenRead(_filePathPAK));
+
+ //Read & check the header info from the PAK
+ pak.BaseStream.Position += 4; //Skip unused
+ if ((FileIdentifiers)BigEndianUtils.ReadInt32(pak) != FileIdentifiers.ASSET_FILE)
+ return PAKReturnType.FAIL_ARCHIVE_IS_NOT_EXCPETED_TYPE;
+ if ((FileIdentifiers)BigEndianUtils.ReadInt32(pak) != FileIdentifiers.MODEL_DATA)
+ return PAKReturnType.FAIL_ARCHIVE_IS_NOT_EXCPETED_TYPE;
+ int EntryCount = BinaryPrimitives.ReverseEndianness(pak.ReadInt32());
+ if (BigEndianUtils.ReadInt32(pak) != EntryCount)
+ return PAKReturnType.FAIL_GENERAL_LOGIC_ERROR;
+ pak.BaseStream.Position += 12; //Skip unused
+
+ //Get PAK entry information
+ List lengths = new List();
+ for (int i = 0; i < EntryCount; i++)
+ {
+ pak.BaseStream.Position += 8;
+ int Length = BinaryPrimitives.ReverseEndianness(pak.ReadInt32());
+ int DataLength = BinaryPrimitives.ReverseEndianness(pak.ReadInt32()); //TODO: Seems to be the aligned version of Length, aligned to 16 bytes.
+ int Offset = BinaryPrimitives.ReverseEndianness(pak.ReadInt32());
+ pak.BaseStream.Position += 12;
+ int UnknownIndex = BinaryPrimitives.ReverseEndianness(pak.ReadInt16());
+ int BINIndex = BinaryPrimitives.ReverseEndianness(pak.ReadInt16());
+ pak.BaseStream.Position += 12;
+
+ lengths.Add(DataLength);
+ }
+
+ //Pull all content from PAK
+ List content = new List();
+ foreach (int length in lengths)
+ {
+ if (length == -1)
+ content.Add(new byte[] { });
+ else
+ content.Add(pak.ReadBytes(length));
}
- HeaderListEnd = (int)ArchiveFile.BaseStream.Position;
+ pak.Close();
+ #endregion
- //Done!
- ArchiveFile.Close();
return PAKReturnType.SUCCESS;
}
catch (IOException) { return PAKReturnType.FAIL_COULD_NOT_ACCESS_FILE; }
@@ -156,55 +140,71 @@ public override PAKReturnType Load()
/* Return a list of filenames for files in the ModelPAK archive */
public override List GetFileNames()
{
- List FileNameList = new List();
- foreach (CS2 ModelEntry in ModelEntries)
- {
- if (!FileNameList.Contains(ModelEntry.Filename))
- {
- FileNameList.Add(ModelEntry.Filename);
- }
+ List combinedFilenames = new List();
+ for (int i = 0; i < _filePaths.Count; i++)
+ {
+ if (combinedFilenames.Contains(_filePaths[i])) continue;
+ combinedFilenames.Add(_filePaths[i]);
}
- return FileNameList;
+ return combinedFilenames;
}
/* Get all CS2s (added for cross-ref support in OpenCAGE with CommandsPAK) */
- public List GetCS2s()
- {
- return ModelEntries;
- }
+ //public List GetCS2s()
+ //{
+ // return _metadata;
+ //}
- /* Get entry by index (added for cross-ref support in OpenCAGE with CommandsPAK) */
+ /* TEMP TEMP TEMP!!! */
public CS2 GetModelByIndex(int index)
{
- if (index < 0 || index >= ModelEntries.Count) return null;
- return ModelEntries[index];
+ if (index < 0 || index >= _metadata.Count) return new CS2();
+ return _metadata[index];
+ }
+ public string GetModelNameByIndex(int index)
+ {
+ if (index < 0 || index >= _metadata.Count) return "";
+ return _filePaths[index];
+ }
+ public string GetModelSubmeshNameByIndex(int index)
+ {
+ if (index < 0 || index >= _metadata.Count) return "";
+ return _partNames[index];
}
/* Get the selected model's submeshes and add up their sizes */
public override int GetFilesize(string FileName)
- {
- int TotalSize = 0;
- foreach (CS2 ThisModel in ModelEntries)
- {
- if (ThisModel.Filename == FileName.Replace("/", "\\"))
- {
- TotalSize += ThisModel.PakSize;
- }
- }
- return TotalSize;
+ {
+ //TODO: Need to look up by filepath and part name!
+
+ //int TotalSize = 0;
+ //foreach (CS2 ThisModel in _metadata)
+ //{
+ // if (ThisModel.Filename == FileName.Replace("/", "\\"))
+ // {
+ // TotalSize += ThisModel.PakSize;
+ // }
+ //}
+ //return TotalSize;
+
+ return -1;
}
/* Find the model entry object by name */
public override int GetFileIndex(string FileName)
{
- for (int i = 0; i < ModelEntries.Count; i++)
- {
- if (ModelEntries[i].Filename == FileName || ModelEntries[i].Filename == FileName.Replace('/', '\\'))
- {
- return i;
- }
- }
- throw new Exception("Could not find the requested file in ModelPAK!");
+ //TODO: Need to look up by filepath and part name!
+
+ //for (int i = 0; i < _metadata.Count; i++)
+ //{
+ // if (_metadata[i].Filename == FileName || _metadata[i].Filename == FileName.Replace('/', '\\'))
+ // {
+ // return i;
+ // }
+ //}
+ //throw new Exception("Could not find the requested file in ModelPAK!");
+
+ return -1;
}
/* Export an existing file from the ModelPAK archive */
@@ -214,9 +214,10 @@ public override PAKReturnType ExportFile(string PathToExport, string FileName)
try
{
+ /*
//Get the selected model's submeshes
List ModelSubmeshes = new List();
- foreach (CS2 ThisModel in ModelEntries)
+ foreach (CS2 ThisModel in _metadata)
{
if (ThisModel.Filename == FileName.Replace("/", "\\"))
{
@@ -226,7 +227,7 @@ public override PAKReturnType ExportFile(string PathToExport, string FileName)
//Extract each submesh into a CS2 folder by material and submesh name
Directory.CreateDirectory(PathToExport);
- BinaryReader ArchiveFile = new BinaryReader(File.OpenRead(FilePathPAK));
+ BinaryReader ArchiveFile = new BinaryReader(File.OpenRead(_filePathPAK));
foreach (CS2 Submesh in ModelSubmeshes)
{
ArchiveFile.BaseStream.Position = HeaderListEnd + Submesh.PakOffset;
@@ -240,7 +241,7 @@ public override PAKReturnType ExportFile(string PathToExport, string FileName)
File.WriteAllBytes(ThisExportPath + "/" + Submesh.MaterialName, ArchiveFile.ReadBytes(Submesh.PakSize));
}
ArchiveFile.Close();
-
+ */
//Done!
return PAKReturnType.SUCCESS;
}
diff --git a/CathodeLib/Scripts/AssetPAKs/Handlers/PAK2.cs b/CathodeLib/Scripts/AssetPAKs/Handlers/PAK2.cs
index 5320a79..a516018 100644
--- a/CathodeLib/Scripts/AssetPAKs/Handlers/PAK2.cs
+++ b/CathodeLib/Scripts/AssetPAKs/Handlers/PAK2.cs
@@ -13,20 +13,18 @@ namespace CATHODE.Assets
*/
public class PAK2 : AssetPAK
{
- private List Pak2Files = new List();
- private int OffsetListBegin = -1;
- private int NumberOfEntries = -1;
+ private List _entries = new List();
/* Initialise the PAK2 class with the intended PAK2 location (existing or not) */
public PAK2(string PathToPAK)
{
- FilePathPAK = PathToPAK;
+ _filePathPAK = PathToPAK;
}
/* Load the contents of an existing PAK2 */
public override PAKReturnType Load()
{
- if (!File.Exists(FilePathPAK))
+ if (!File.Exists(_filePathPAK))
{
return PAKReturnType.FAIL_TRIED_TO_LOAD_VIRTUAL_ARCHIVE;
}
@@ -34,18 +32,18 @@ public override PAKReturnType Load()
try
{
//Open PAK
- BinaryReader ArchiveFile = new BinaryReader(File.OpenRead(FilePathPAK));
+ BinaryReader ArchiveFile = new BinaryReader(File.OpenRead(_filePathPAK));
//Read the header info
string MagicValidation = "";
for (int i = 0; i < 4; i++) { MagicValidation += ArchiveFile.ReadChar(); }
if (MagicValidation != "PAK2") { ArchiveFile.Close(); return PAKReturnType.FAIL_ARCHIVE_IS_NOT_EXCPETED_TYPE; }
- OffsetListBegin = ArchiveFile.ReadInt32() + 16;
- NumberOfEntries = ArchiveFile.ReadInt32();
+ int offsetListBegin = ArchiveFile.ReadInt32() + 16;
+ int entryCount = ArchiveFile.ReadInt32();
ArchiveFile.BaseStream.Position += 4; //Skip "4"
//Read all file names and create entries
- for (int i = 0; i < NumberOfEntries; i++)
+ for (int i = 0; i < entryCount; i++)
{
string ThisFileName = "";
for (byte b; (b = ArchiveFile.ReadByte()) != 0x00;)
@@ -55,24 +53,24 @@ public override PAKReturnType Load()
EntryPAK2 NewPakFile = new EntryPAK2();
NewPakFile.Filename = ThisFileName;
- Pak2Files.Add(NewPakFile);
+ _entries.Add(NewPakFile);
}
//Read all file offsets
- ArchiveFile.BaseStream.Position = OffsetListBegin;
+ ArchiveFile.BaseStream.Position = offsetListBegin;
List FileOffsets = new List();
- FileOffsets.Add(OffsetListBegin + (NumberOfEntries * 4));
- for (int i = 0; i < NumberOfEntries; i++)
+ FileOffsets.Add(offsetListBegin + (entryCount * 4));
+ for (int i = 0; i < entryCount; i++)
{
FileOffsets.Add(ArchiveFile.ReadInt32());
- Pak2Files[i].Offset = FileOffsets[i];
+ _entries[i].Offset = FileOffsets[i];
}
//Read in the files to entries
- for (int i = 0; i < NumberOfEntries; i++)
+ for (int i = 0; i < entryCount; i++)
{
//Must pass to RemoveLeadingNulls as each file starts with 0-3 null bytes to align files to a 4-byte block reader
- Pak2Files[i].Content = ExtraBinaryUtils.RemoveLeadingNulls(ArchiveFile.ReadBytes(FileOffsets[i + 1] - FileOffsets[i]));
+ _entries[i].Content = ExtraBinaryUtils.RemoveLeadingNulls(ArchiveFile.ReadBytes(FileOffsets[i + 1] - FileOffsets[i]));
}
//Close PAK
@@ -87,7 +85,7 @@ public override PAKReturnType Load()
public override List GetFileNames()
{
List FileNameList = new List();
- foreach (EntryPAK2 ArchiveFile in Pak2Files)
+ foreach (EntryPAK2 ArchiveFile in _entries)
{
FileNameList.Add(ArchiveFile.Filename);
}
@@ -97,15 +95,15 @@ public override List GetFileNames()
/* Get the file size of an archive entry */
public override int GetFilesize(string FileName)
{
- return Pak2Files[GetFileIndex(FileName)].Content.Length;
+ return _entries[GetFileIndex(FileName)].Content.Length;
}
/* Find the a file entry object by name */
public override int GetFileIndex(string FileName)
{
- for (int i = 0; i < Pak2Files.Count; i++)
+ for (int i = 0; i < _entries.Count; i++)
{
- if (Pak2Files[i].Filename == FileName || Pak2Files[i].Filename == FileName.Replace('/', '\\'))
+ if (_entries[i].Filename == FileName || _entries[i].Filename == FileName.Replace('/', '\\'))
{
return i;
}
@@ -122,7 +120,7 @@ public override PAKReturnType AddFile(string PathToNewFile, int TrimFromPath = 0
if (TrimFromPath == 0) { NewFile.Filename = Path.GetFileName(PathToNewFile).ToUpper(); } //Virtual directory support here would be nice too
else { NewFile.Filename = PathToNewFile.Substring(TrimFromPath).ToUpper(); } //Easy to fail here, so be careful on function usage!
NewFile.Content = File.ReadAllBytes(PathToNewFile);
- Pak2Files.Add(NewFile);
+ _entries.Add(NewFile);
return PAKReturnType.SUCCESS;
}
catch (IOException) { return PAKReturnType.FAIL_COULD_NOT_ACCESS_FILE; }
@@ -134,7 +132,7 @@ public override PAKReturnType DeleteFile(string FileName)
{
try
{
- Pak2Files.RemoveAt(GetFileIndex(FileName));
+ _entries.RemoveAt(GetFileIndex(FileName));
return PAKReturnType.SUCCESS;
}
catch
@@ -148,7 +146,7 @@ public override PAKReturnType ReplaceFile(string PathToNewFile, string FileName)
{
try
{
- Pak2Files[GetFileIndex(FileName)].Content = File.ReadAllBytes(PathToNewFile);
+ _entries[GetFileIndex(FileName)].Content = File.ReadAllBytes(PathToNewFile);
return PAKReturnType.SUCCESS;
}
catch (IOException) { return PAKReturnType.FAIL_COULD_NOT_ACCESS_FILE; }
@@ -160,7 +158,7 @@ public override PAKReturnType ExportFile(string PathToExport, string FileName)
{
try
{
- File.WriteAllBytes(PathToExport, Pak2Files[GetFileIndex(FileName)].Content);
+ File.WriteAllBytes(PathToExport, _entries[GetFileIndex(FileName)].Content);
return PAKReturnType.SUCCESS;
}
catch (IOException) { return PAKReturnType.FAIL_COULD_NOT_ACCESS_FILE; }
@@ -174,57 +172,57 @@ public override PAKReturnType Save()
{
//Open/create PAK2 for writing
BinaryWriter ArchiveFileWrite;
- if (File.Exists(FilePathPAK))
+ if (File.Exists(_filePathPAK))
{
- ArchiveFileWrite = new BinaryWriter(File.OpenWrite(FilePathPAK));
+ ArchiveFileWrite = new BinaryWriter(File.OpenWrite(_filePathPAK));
ArchiveFileWrite.BaseStream.SetLength(0);
}
else
{
- ArchiveFileWrite = new BinaryWriter(File.Create(FilePathPAK));
+ ArchiveFileWrite = new BinaryWriter(File.Create(_filePathPAK));
}
//Write header
ExtraBinaryUtils.WriteString("PAK2", ArchiveFileWrite);
int OffsetListBegin_New = 0;
- for (int i = 0; i < Pak2Files.Count; i++)
+ for (int i = 0; i < _entries.Count; i++)
{
- OffsetListBegin_New += Pak2Files[i].Filename.Length + 1;
+ OffsetListBegin_New += _entries[i].Filename.Length + 1;
}
ArchiveFileWrite.Write(OffsetListBegin_New);
- ArchiveFileWrite.Write(Pak2Files.Count);
+ ArchiveFileWrite.Write(_entries.Count);
ArchiveFileWrite.Write(4);
//Write filenames
- for (int i = 0; i < Pak2Files.Count; i++)
+ for (int i = 0; i < _entries.Count; i++)
{
- ExtraBinaryUtils.WriteString(Pak2Files[i].Filename.Replace("\\", "/"), ArchiveFileWrite);
+ ExtraBinaryUtils.WriteString(_entries[i].Filename.Replace("\\", "/"), ArchiveFileWrite);
ArchiveFileWrite.Write((byte)0x00);
}
//Write placeholder offsets for now, we'll correct them after writing the content
- OffsetListBegin = (int)ArchiveFileWrite.BaseStream.Position;
- for (int i = 0; i < Pak2Files.Count; i++)
+ int offsetListBegin = (int)ArchiveFileWrite.BaseStream.Position;
+ for (int i = 0; i < _entries.Count; i++)
{
ArchiveFileWrite.Write(0);
}
//Write files
- for (int i = 0; i < Pak2Files.Count; i++)
+ for (int i = 0; i < _entries.Count; i++)
{
while (ArchiveFileWrite.BaseStream.Position % 4 != 0)
{
ArchiveFileWrite.Write((byte)0x00);
}
- ArchiveFileWrite.Write(Pak2Files[i].Content);
- Pak2Files[i].Offset = (int)ArchiveFileWrite.BaseStream.Position;
+ ArchiveFileWrite.Write(_entries[i].Content);
+ _entries[i].Offset = (int)ArchiveFileWrite.BaseStream.Position;
}
//Re-write offsets with correct values
- ArchiveFileWrite.BaseStream.Position = OffsetListBegin;
- for (int i = 0; i < Pak2Files.Count; i++)
+ ArchiveFileWrite.BaseStream.Position = offsetListBegin;
+ for (int i = 0; i < _entries.Count; i++)
{
- ArchiveFileWrite.Write(Pak2Files[i].Offset);
+ ArchiveFileWrite.Write(_entries[i].Offset);
}
ArchiveFileWrite.Close();
diff --git a/CathodeLib/Scripts/AssetPAKs/Handlers/Shaders.cs b/CathodeLib/Scripts/AssetPAKs/Handlers/Shaders.cs
index b7e9399..403a810 100644
--- a/CathodeLib/Scripts/AssetPAKs/Handlers/Shaders.cs
+++ b/CathodeLib/Scripts/AssetPAKs/Handlers/Shaders.cs
@@ -1,4 +1,5 @@
-using System;
+using CathodeLib;
+using System;
using System.Collections.Generic;
using System.IO;
@@ -14,19 +15,22 @@ namespace CATHODE.Assets
*/
public class Shaders : AssetPAK
{
- List HeaderDump = new List();
+ List _header = new List();
+ private string _filePathIdxRemap = "";
/* Initialise the ShaderPAK class with the intended location (existing or not) */
public Shaders(string PathToPAK)
{
- FilePathPAK = PathToPAK;
- FilePathBIN = PathToPAK.Substring(0, PathToPAK.Length - Path.GetFileName(PathToPAK).Length) + Path.GetFileNameWithoutExtension(FilePathPAK) + "_BIN.PAK";
+ _filePathPAK = PathToPAK;
+ string trimmed = PathToPAK.Substring(0, PathToPAK.Length - Path.GetFileName(PathToPAK).Length) + Path.GetFileNameWithoutExtension(_filePathPAK);
+ _filePathBIN = trimmed + "_BIN.PAK";
+ _filePathIdxRemap = trimmed + "_IDX_REMAP.PAK";
}
/* Load the contents of an existing ShaderPAK set (massive WIP) */
public override PAKReturnType Load()
{
- if (!File.Exists(FilePathPAK))
+ if (!File.Exists(_filePathPAK))
{
return PAKReturnType.FAIL_COULD_NOT_ACCESS_FILE;
}
@@ -34,19 +38,35 @@ public override PAKReturnType Load()
try
{
//The main PAK is unhandled for now, as I can't work out how the main (initial) PAK relates to the _BIN.PAK. Entry counts are different, and no noticeable indexes!
- /*
//Open PAK
- BinaryReader ArchiveFile = new BinaryReader(File.OpenRead(FilePathPAK));
- ExtraBinaryUtils BinaryUtils = new ExtraBinaryUtils();
-
- //Read shader headers
+ BinaryReader pak = new BinaryReader(File.OpenRead(_filePathPAK));
+
+ //Read & check the header info from the PAK
+ pak.BaseStream.Position += 4; //Skip unused
+ if ((FileIdentifiers)pak.ReadInt32() != FileIdentifiers.ASSET_FILE)
+ return PAKReturnType.FAIL_ARCHIVE_IS_NOT_EXCPETED_TYPE;
+ if ((FileIdentifiers)pak.ReadInt32() != FileIdentifiers.SHADER_DATA)
+ return PAKReturnType.FAIL_ARCHIVE_IS_NOT_EXCPETED_TYPE;
+ int pakEntryCount = pak.ReadInt32();
+ if (BigEndianUtils.ReadInt32(pak) != pakEntryCount)
+ return PAKReturnType.FAIL_GENERAL_LOGIC_ERROR;
+
+ //TODO: look at the ios dump, there seems to be a way of working out which is compute/pixel/etc
+
+ /*
+ //TODO: usually we skip 12 bytes here as this bit is unused, but in shader PAKs it seems 8 bytes are used
+
+ pak.BaseStream.Position += 12; //Skip unused
+
+
+ //Read shader headers
for (int i = 0; i < NumOfStringPairs; i++)
{
CathodeShaderHeader newHeaderEntry = new CathodeShaderHeader();
//Get the name (or type) of this header
- ArchiveFile.BaseStream.Position += 32;
- byte[] entryNameOrType = ArchiveFile.ReadBytes(40);
+ pak.BaseStream.Position += 32;
+ byte[] entryNameOrType = pak.ReadBytes(40);
for (int x = 0; x < entryNameOrType.Length; x++)
{
if (entryNameOrType[x] != 0x00)
@@ -60,9 +80,9 @@ public override PAKReturnType Load()
//Skip over the rest for now just so I can scrape all the names (this needs parsing obvs)
for (int x = 0; x < 999999; x++)
{
- if (ArchiveFile.ReadBytes(4).SequenceEqual(new byte[] { 0xA4, 0xBB, 0x25, 0x77 }))
+ if (pak.ReadBytes(4).SequenceEqual(new byte[] { 0xA4, 0xBB, 0x25, 0x77 }))
{
- ArchiveFile.BaseStream.Position -= 4;
+ pak.BaseStream.Position -= 4;
break;
}
}
@@ -71,59 +91,62 @@ public override PAKReturnType Load()
}
//Done!
- ArchiveFile.Close();
*/
+ pak.Close();
- BinaryReader ArchiveFileBin = new BinaryReader(File.OpenRead(FilePathBIN));
+ BinaryReader bin = new BinaryReader(File.OpenRead(_filePathBIN));
- //Validate our header magic (look at _IDX_REMAP as I think this should also be supported)
- ArchiveFileBin.BaseStream.Position = 4;
- if (!(ArchiveFileBin.ReadInt32() == 14 && ArchiveFileBin.ReadInt32() == 3))
+ bin.BaseStream.Position = 4; //skip magic
+ if ((FileIdentifiers)pak.ReadInt32() != FileIdentifiers.ASSET_FILE)
+ return PAKReturnType.FAIL_ARCHIVE_IS_NOT_EXCPETED_TYPE;
+ if ((FileIdentifiers)pak.ReadInt32() != FileIdentifiers.SHADER_DATA)
return PAKReturnType.FAIL_ARCHIVE_IS_NOT_EXCPETED_TYPE;
//Read entry count from header
- ArchiveFileBin.BaseStream.Position = 12;
- int EntryCount = ArchiveFileBin.ReadInt32();
- int EndOfHeaders = 0;
+ int binEntryCount = bin.ReadInt32();
+ if (BigEndianUtils.ReadInt32(pak) != binEntryCount)
+ return PAKReturnType.FAIL_GENERAL_LOGIC_ERROR;
+
+ //TODO: the other info here seems to mirror the extra data used in the other shader PAK which is usually unused
//Skip rest of the main header
- ArchiveFileBin.BaseStream.Position = 32;
+ bin.BaseStream.Position = 32;
//Pull each entry's individual header
- for (int i = 0; i < EntryCount; i++)
+ for (int i = 0; i < binEntryCount; i++)
{
CathodeShaderHeader newStringEntry = new CathodeShaderHeader();
- ArchiveFileBin.BaseStream.Position += 8; //skip blanks
+ bin.BaseStream.Position += 8; //skip blanks
- newStringEntry.FileLength = ArchiveFileBin.ReadInt32();
- newStringEntry.FileLengthWithPadding = ArchiveFileBin.ReadInt32();
- newStringEntry.FileOffset = ArchiveFileBin.ReadInt32();
+ newStringEntry.FileLength = bin.ReadInt32();
+ newStringEntry.FileLengthWithPadding = bin.ReadInt32();
+ newStringEntry.FileOffset = bin.ReadInt32();
- ArchiveFileBin.BaseStream.Position += 8; //skip blanks
+ bin.BaseStream.Position += 8; //skip blanks
- newStringEntry.StringPart1 = ArchiveFileBin.ReadBytes(4);
- newStringEntry.FileIndex = ArchiveFileBin.ReadInt32(); //potentially actually int8 or int16 not 32
+ newStringEntry.StringPart1 = bin.ReadBytes(4);
+ newStringEntry.FileIndex = bin.ReadInt32(); //potentially actually int8 or int16 not 32
- ArchiveFileBin.BaseStream.Position += 8; //skip blanks
+ bin.BaseStream.Position += 8; //skip blanks
- newStringEntry.StringPart2 = ArchiveFileBin.ReadBytes(4);
+ newStringEntry.StringPart2 = bin.ReadBytes(4);
//TEMP: For now I'm just setting the filename to be the index... need to work out how the _BIN relates to the initial .PAK to get names, etc
newStringEntry.FileName = newStringEntry.FileIndex + ".DXBC";
//END OF TEMP
- HeaderDump.Add(newStringEntry);
+ _header.Add(newStringEntry);
}
- EndOfHeaders = (int)ArchiveFileBin.BaseStream.Position;
+ int endOfHeaders = (int)bin.BaseStream.Position;
//Pull each entry's file content
- foreach (CathodeShaderHeader shaderEntry in HeaderDump)
+ foreach (CathodeShaderHeader shaderEntry in _header)
{
- ArchiveFileBin.BaseStream.Position = shaderEntry.FileOffset + EndOfHeaders;
- shaderEntry.FileContent = ArchiveFileBin.ReadBytes(shaderEntry.FileLength);
+ bin.BaseStream.Position = shaderEntry.FileOffset + endOfHeaders;
+ shaderEntry.FileContent = bin.ReadBytes(shaderEntry.FileLength);
}
- ArchiveFileBin.Close();
+ bin.Close();
return PAKReturnType.SUCCESS;
}
catch (IOException) { return PAKReturnType.FAIL_COULD_NOT_ACCESS_FILE; }
@@ -134,7 +157,7 @@ public override PAKReturnType Load()
public override List GetFileNames()
{
List FileNameList = new List();
- foreach (CathodeShaderHeader shaderEntry in HeaderDump)
+ foreach (CathodeShaderHeader shaderEntry in _header)
{
FileNameList.Add(shaderEntry.FileName);
}
@@ -144,15 +167,15 @@ public override List GetFileNames()
/* Get the size of the requested shader (not yet implemented) */
public override int GetFilesize(string FileName)
{
- return HeaderDump[GetFileIndex(FileName)].FileLength;
+ return _header[GetFileIndex(FileName)].FileLength;
}
/* Find the shader entry object by name (not yet implemented) */
public override int GetFileIndex(string FileName)
{
- for (int i = 0; i < HeaderDump.Count; i++)
+ for (int i = 0; i < _header.Count; i++)
{
- if (HeaderDump[i].FileName == FileName)
+ if (_header[i].FileName == FileName)
{
return i;
}
@@ -165,7 +188,7 @@ public override PAKReturnType ExportFile(string PathToExport, string FileName)
{
try
{
- File.WriteAllBytes(PathToExport, HeaderDump[GetFileIndex(FileName)].FileContent);
+ File.WriteAllBytes(PathToExport, _header[GetFileIndex(FileName)].FileContent);
return PAKReturnType.SUCCESS;
}
catch (IOException) { return PAKReturnType.FAIL_COULD_NOT_ACCESS_FILE; }
diff --git a/CathodeLib/Scripts/AssetPAKs/Handlers/Textures.cs b/CathodeLib/Scripts/AssetPAKs/Handlers/Textures.cs
index ee0ae4d..3874285 100644
--- a/CathodeLib/Scripts/AssetPAKs/Handlers/Textures.cs
+++ b/CathodeLib/Scripts/AssetPAKs/Handlers/Textures.cs
@@ -1,7 +1,7 @@
-using System;
+using CathodeLib;
+using System;
using System.Collections.Generic;
using System.IO;
-using CathodeLib;
namespace CATHODE.Assets
{
@@ -14,141 +14,158 @@ namespace CATHODE.Assets
*/
public class Textures : AssetPAK
{
- private List TextureEntries = new List();
+ private List _entries = new List();
private int HeaderListBeginBIN = -1;
- private int HeaderListEndPAK = -1;
private int NumberOfEntriesPAK = -1;
private int NumberOfEntriesBIN = -1;
- private int VersionNumber_BIN = 45;
- private int VersionNumber_PAK = 14;
/* Initialise the TexturePAK class with the intended location (existing or not) */
public Textures(string PathToPAK)
{
- FilePathPAK = PathToPAK;
+ _filePathPAK = PathToPAK;
- if (Path.GetFileName(FilePathPAK).Substring(0, 5).ToUpper() == "LEVEL")
+ if (Path.GetFileName(_filePathPAK).Substring(0, 5).ToUpper() == "LEVEL")
{
- FilePathBIN = FilePathPAK.Substring(0, FilePathPAK.Length - Path.GetFileName(FilePathPAK).Length) + "LEVEL_TEXTURE_HEADERS.ALL.BIN";
+ _filePathBIN = _filePathPAK.Substring(0, _filePathPAK.Length - Path.GetFileName(_filePathPAK).Length) + "LEVEL_TEXTURE_HEADERS.ALL.BIN";
}
else
{
- FilePathBIN = FilePathPAK.Substring(0, FilePathPAK.Length - Path.GetFileName(FilePathPAK).Length) + "GLOBAL_TEXTURES_HEADERS.ALL.BIN";
+ _filePathBIN = _filePathPAK.Substring(0, _filePathPAK.Length - Path.GetFileName(_filePathPAK).Length) + "GLOBAL_TEXTURES_HEADERS.ALL.BIN";
}
}
/* Load the contents of an existing TexturePAK */
public override PAKReturnType Load()
{
- if (!File.Exists(FilePathPAK))
+ if (!File.Exists(_filePathPAK))
{
return PAKReturnType.FAIL_TRIED_TO_LOAD_VIRTUAL_ARCHIVE;
}
try
- {
- /* First, parse the BIN and pull ALL info from it */
- BinaryReader ArchiveFileBin = new BinaryReader(File.OpenRead(FilePathBIN));
+ {
+ #region TEXTURE_BIN
+ /* First, parse the BIN and pull ALL info from it */
+ BinaryReader bin = new BinaryReader(File.OpenRead(_filePathBIN));
//Read the header info from the BIN
- VersionNumber_BIN = ArchiveFileBin.ReadInt32();
- if (VersionNumber_BIN != 45) { return PAKReturnType.FAIL_ARCHIVE_IS_NOT_EXCPETED_TYPE; } //BIN version number is 45 for textures
- NumberOfEntriesBIN = ArchiveFileBin.ReadInt32();
- HeaderListBeginBIN = ArchiveFileBin.ReadInt32();
+ if ((FileIdentifiers)bin.ReadInt32() != FileIdentifiers.TEXTURE_DATA)
+ return PAKReturnType.FAIL_ARCHIVE_IS_NOT_EXCPETED_TYPE;
+ NumberOfEntriesBIN = bin.ReadInt32();
+ HeaderListBeginBIN = bin.ReadInt32();
- //Read all file names from BIN
- string ThisFileName = "";
+ //Read all file names from BIN and create texture entry
for (int i = 0; i < NumberOfEntriesBIN; i++)
{
- ThisFileName = "";
- for (byte b; (b = ArchiveFileBin.ReadByte()) != 0x00;)
- {
- ThisFileName += (char)b;
- }
- if (Path.GetExtension(ThisFileName).ToUpper() != ".DDS")
- {
- ThisFileName += ".dds";
- }
- //Create texture entry and add filename
TEX4 TextureEntry = new TEX4();
- TextureEntry.FileName = ThisFileName;
- TextureEntries.Add(TextureEntry);
+ TextureEntry.FileName = CathodeLib.Utilities.ReadString(bin);
+ //TODO: maybe we should stop doing this & just do in AlienPAK instead
+ if (Path.GetExtension(TextureEntry.FileName).ToUpper() != ".DDS")
+ TextureEntry.FileName += ".dds";
+ _entries.Add(TextureEntry);
}
//Read the texture headers from the BIN
- ArchiveFileBin.BaseStream.Position = HeaderListBeginBIN + 12;
+ bin.BaseStream.Position = HeaderListBeginBIN + 12;
for (int i = 0; i < NumberOfEntriesBIN; i++)
{
- TextureEntries[i].HeaderPos = (int)ArchiveFileBin.BaseStream.Position;
- for (int x = 0; x < 4; x++) { TextureEntries[i].Magic += ArchiveFileBin.ReadChar(); }
- TextureEntries[i].Format = (TextureFormat)ArchiveFileBin.ReadInt32();
- TextureEntries[i].Length_V2 = ArchiveFileBin.ReadInt32();
- TextureEntries[i].Length_V1 = ArchiveFileBin.ReadInt32();
- TextureEntries[i].Texture_V1.Width = ArchiveFileBin.ReadInt16();
- TextureEntries[i].Texture_V1.Height = ArchiveFileBin.ReadInt16();
- TextureEntries[i].Unk_V1 = ArchiveFileBin.ReadInt16();
- TextureEntries[i].Texture_V2.Width = ArchiveFileBin.ReadInt16();
- TextureEntries[i].Texture_V2.Height = ArchiveFileBin.ReadInt16();
- TextureEntries[i].Unk_V2 = ArchiveFileBin.ReadInt16();
- TextureEntries[i].UnknownHeaderBytes = ArchiveFileBin.ReadBytes(20);
+ bin.BaseStream.Position += 4; //TEX4 magic
+ _entries[i].Format = (TextureFormat)bin.ReadInt32();
+ _entries[i].tex_HighRes.Length = bin.ReadInt32();
+ _entries[i].tex_LowRes.Length = bin.ReadInt32();
+ _entries[i].tex_LowRes.Width = bin.ReadInt16();
+ _entries[i].tex_LowRes.Height = bin.ReadInt16();
+ _entries[i].tex_HighRes.Depth = bin.ReadInt16();
+ _entries[i].tex_HighRes.Width = bin.ReadInt16();
+ _entries[i].tex_HighRes.Height = bin.ReadInt16();
+ _entries[i].tex_HighRes.Depth = bin.ReadInt16();
+ _entries[i].tex_LowRes.MipLevels = bin.ReadInt16();
+ _entries[i].tex_HighRes.MipLevels = bin.ReadInt16();
+ _entries[i].Type = bin.ReadInt32();
+ _entries[i].UnknownTexThing = (AlienUnknownTextureThing)bin.ReadInt16();
+ bin.BaseStream.Position += 2; //Always 2048
+ bin.BaseStream.Position += 4; //Skip filename offset value
+ bin.BaseStream.Position += 4; //Skip unused
}
-
+ bin.Close();
+ #endregion
+
+ #region TEXTURE_PAK
/* Second, parse the PAK and pull ONLY header info from it - we'll pull textures when requested (to save memory) */
- ArchiveFileBin.Close();
- BinaryReader ArchiveFile = new BinaryReader(File.OpenRead(FilePathPAK));
-
- //Read the header info from the PAK
- ArchiveFile.BaseStream.Position += 4; //Skip nulls
- VersionNumber_PAK = BigEndianUtils.ReadInt32(ArchiveFile);
- if (BigEndianUtils.ReadInt32(ArchiveFile) != VersionNumber_BIN) { throw new Exception("Archive version mismatch!"); }
- NumberOfEntriesPAK = BigEndianUtils.ReadInt32(ArchiveFile);
- if (BigEndianUtils.ReadInt32(ArchiveFile) != NumberOfEntriesPAK) { throw new Exception("PAK entry count mismatch!"); }
- ArchiveFile.BaseStream.Position += 12; //Skip unknowns (1,1,1)
-
+ BinaryReader pak = new BinaryReader(File.OpenRead(_filePathPAK));
+
+ //Read & check the header info from the PAK
+ pak.BaseStream.Position += 4; //Skip unused
+ if ((FileIdentifiers)BigEndianUtils.ReadInt32(pak) != FileIdentifiers.ASSET_FILE)
+ return PAKReturnType.FAIL_ARCHIVE_IS_NOT_EXCPETED_TYPE;
+ if ((FileIdentifiers)BigEndianUtils.ReadInt32(pak) != FileIdentifiers.TEXTURE_DATA)
+ return PAKReturnType.FAIL_ARCHIVE_IS_NOT_EXCPETED_TYPE;
+ NumberOfEntriesPAK = BigEndianUtils.ReadInt32(pak);
+ if (BigEndianUtils.ReadInt32(pak) != NumberOfEntriesPAK)
+ return PAKReturnType.FAIL_GENERAL_LOGIC_ERROR;
+ pak.BaseStream.Position += 12; //Skip unused
+
//Read the texture headers from the PAK
- int OffsetTracker = (NumberOfEntriesPAK * 48) + 32;
for (int i = 0; i < NumberOfEntriesPAK; i++)
{
- //Header indexes are out of order, so optimise replacements by saving position
- int HeaderPosition = (int)ArchiveFile.BaseStream.Position;
-
- //Pull the entry info
- byte[] UnknownHeaderLead = ArchiveFile.ReadBytes(8);
- int PartLength = BigEndianUtils.ReadInt32(ArchiveFile);
- if (PartLength != BigEndianUtils.ReadInt32(ArchiveFile)) { continue; }
- byte[] UnknownHeaderTrail_1 = ArchiveFile.ReadBytes(18);
-
- //Find the entry
- TEX4 TextureEntry = TextureEntries[BigEndianUtils.ReadInt16(ArchiveFile)];
- TEX4_Part TexturePart = (!TextureEntry.Texture_V1.Saved) ? TextureEntry.Texture_V1 : TextureEntry.Texture_V2;
-
+ pak.BaseStream.Position += 8; //Skip unused
+
+ int length = BigEndianUtils.ReadInt32(pak);
+ if (length != BigEndianUtils.ReadInt32(pak)) { return PAKReturnType.FAIL_GENERAL_LOGIC_ERROR; }
+
+ int offset = BigEndianUtils.ReadInt32(pak);
+
+ pak.BaseStream.Position += 2; //Skip unused
+
+ int isHighRes = BigEndianUtils.ReadInt16(pak);
+
+ pak.BaseStream.Position += 2; //Skip unused
+
+ int val256 = BigEndianUtils.ReadInt16(pak); //always 256
+ if (val256 != 256) { return PAKReturnType.FAIL_GENERAL_LOGIC_ERROR; }
+
+ UInt32 unk1 = BigEndianUtils.ReadUInt32(pak);
+ UInt16 unk2 = BigEndianUtils.ReadUInt16(pak);
+
+ int index = BigEndianUtils.ReadInt16(pak);
+
+ pak.BaseStream.Position += 4; //Skip unused
+
+ UInt32 unk3 = BigEndianUtils.ReadUInt32(pak);
+ UInt32 unk4 = BigEndianUtils.ReadUInt32(pak);
+
+ //Find the entry
+ TEX4_Part texFullRes = (isHighRes == 1) ? _entries[index].tex_HighRes : _entries[index].tex_LowRes;
+ if (length != texFullRes.Length) { return PAKReturnType.FAIL_GENERAL_LOGIC_ERROR; }
+
//Write out the info
- TexturePart.HeaderPos = HeaderPosition;
- TexturePart.StartPos = OffsetTracker;
- TexturePart.UnknownHeaderLead = UnknownHeaderLead;
- TexturePart.Length = PartLength;
- TexturePart.Saved = true;
- TexturePart.UnknownHeaderTrail_1 = UnknownHeaderTrail_1;
- TexturePart.UnknownHeaderTrail_2 = ArchiveFile.ReadBytes(12);
-
- //Keep file offset updated
- OffsetTracker += TexturePart.Length;
+ texFullRes.Offset = offset;
+ texFullRes.unk1 = unk1;
+ texFullRes.unk2 = unk2;
+ texFullRes.unk3 = unk3;
+ texFullRes.unk4 = unk4;
}
- HeaderListEndPAK = (int)ArchiveFile.BaseStream.Position;
//Close PAK
- ArchiveFile.Close();
+ pak.Close();
+ #endregion
+
return PAKReturnType.SUCCESS;
}
- catch (IOException) { return PAKReturnType.FAIL_COULD_NOT_ACCESS_FILE; }
- catch (Exception) { return PAKReturnType.FAIL_UNKNOWN; }
+ catch (IOException) {
+ return PAKReturnType.FAIL_COULD_NOT_ACCESS_FILE;
+ }
+ catch (Exception e) {
+ Console.WriteLine(e.ToString());
+ return PAKReturnType.FAIL_UNKNOWN;
+ }
}
/* Return a list of filenames for files in the TexturePAK archive */
public override List GetFileNames()
{
List FileNameList = new List();
- foreach (TEX4 ArchiveFile in TextureEntries)
+ foreach (TEX4 ArchiveFile in _entries)
{
FileNameList.Add(ArchiveFile.FileName);
}
@@ -160,24 +177,20 @@ public override int GetFilesize(string FileName)
{
int FileIndex = GetFileIndex(FileName);
if (FileIndex == -1) return -1; //CHANGED FOR OPENCAGE
- if (TextureEntries[FileIndex].Texture_V2.Saved)
- {
- return TextureEntries[FileIndex].Texture_V2.Length + 148;
- }
- //Fallback to V1 if this texture has no V2
- else if (TextureEntries[FileIndex].Texture_V1.Saved)
+ if (_entries[FileIndex].tex_HighRes.Length != -1)
{
- return TextureEntries[FileIndex].Texture_V1.Length + 148;
+ return _entries[FileIndex].tex_HighRes.Length + 148;
}
+ return _entries[FileIndex].tex_LowRes.Length + 148;
throw new Exception("Texture has no size! Fatal logic error.");
}
/* Find a file entry object by name */
public override int GetFileIndex(string FileName)
{
- for (int i = 0; i < TextureEntries.Count; i++)
+ for (int i = 0; i < _entries.Count; i++)
{
- if (TextureEntries[i].FileName == FileName || TextureEntries[i].FileName == FileName.Replace('/', '\\'))
+ if (_entries[i].FileName == FileName || _entries[i].FileName == FileName.Replace('/', '\\'))
{
return i;
}
@@ -186,95 +199,99 @@ public override int GetFileIndex(string FileName)
}
/* Replace an existing file in the TexturePAK archive */
+ //TODO: THIS FUNCTION DOES NOT CURRENTLY WORK!
+ // I NEED TO IMPLEMENT PULLING ALL PAK TEXTURE CONTENT, UPDATING WITH NEW, THEN SAVING IT BACK OUT
+ // CURRENTLY IT ONLY UPDATES HEADERS!
public override PAKReturnType ReplaceFile(string PathToNewFile, string FileName)
{
try
{
- //Get the texture entry & parse new DDS
- int EntryIndex = GetFileIndex(FileName);
- if (EntryIndex == -1) return PAKReturnType.FAIL_GENERAL_LOGIC_ERROR; //CHANGED FOR OPENCAGE
- TEX4 TextureEntry = TextureEntries[EntryIndex];
- DDSReader NewTexture = new DDSReader(PathToNewFile);
-
- //Currently we only apply the new texture to the "biggest", some have lower mips that we don't edit (TODO)
- TEX4_Part BiggestPart = TextureEntry.Texture_V2;
- if (BiggestPart.HeaderPos == -1 || !BiggestPart.Saved)
- {
- BiggestPart = TextureEntry.Texture_V1;
+ int index = GetFileIndex(FileName);
+ if (index == -1) return PAKReturnType.FAIL_GENERAL_LOGIC_ERROR;
+
+ //Update our internal knowledge of the textures
+ //TODO: update low and high res - take lowest mip of high?
+ DDSReader newTexture = new DDSReader(PathToNewFile);
+ TEX4_Part texFullRes = _entries[index].tex_HighRes;
+ if (texFullRes.Length == 0)
+ texFullRes = _entries[index].tex_LowRes;
+ if (texFullRes.Length == 0)
+ return PAKReturnType.FAIL_GENERAL_LOGIC_ERROR;
+ texFullRes.Length = (int)newTexture.DataBlock.Length;
+ texFullRes.Width = (Int16)newTexture.Width;
+ texFullRes.Height = (Int16)newTexture.Height;
+ _entries[index].Format = newTexture.Format;
+
+ //Write BIN file
+ BinaryWriter bin = new BinaryWriter(File.OpenWrite(_filePathBIN));
+ bin.BaseStream.SetLength(0);
+ bin.Write(new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 });
+ for (int i = 0; i < _entries.Count; i++)
+ {
+ CathodeLib.Utilities.Write(bin, _entries[i].FileName); //TODO: does this need converting to char array?
+ bin.Write((byte)0x00);
}
- if (BiggestPart.HeaderPos == -1 || !BiggestPart.Saved)
- {
- return PAKReturnType.FAIL_REQUEST_IS_UNSUPPORTED; //Shouldn't reach this.
+ int binHeaderStart = (int)bin.BaseStream.Position - 12;
+ int binEntryCount = 0;
+ for (int i = 0; i < _entries.Count; i++)
+ {
+ ExtraBinaryUtils.WriteString("tex4", bin);
+ bin.Write(BitConverter.GetBytes((int)_entries[i].Format));
+ bin.Write(_entries[i].tex_HighRes.Length);
+ bin.Write(_entries[i].tex_LowRes.Length);
+ bin.Write(_entries[i].tex_LowRes.Width);
+ bin.Write(_entries[i].tex_LowRes.Height);
+ bin.Write(_entries[i].tex_LowRes.Depth);
+ bin.Write(_entries[i].tex_HighRes.Width);
+ bin.Write(_entries[i].tex_HighRes.Height);
+ bin.Write(_entries[i].tex_HighRes.Depth);
+ bin.Write(_entries[i].Type);
+ bin.Write((Int16)_entries[i].UnknownTexThing);
+ bin.Write((Int16)2048); //TODO: derive this from the actual texture
+ bin.Write(0); //TODO: this is filename offset, gen this from how we write!
+ bin.Write(new byte[] { 0x00, 0x00, 0x00, 0x00 });
+ binEntryCount++;
}
-
- //CATHODE seems to ignore texture header information regarding size, so as default, resize any imported textures to the original size.
- //An option is provided in the toolkit to write size information to the header (done above) however, so don't resize if that's the case.
- //More work needs to be done to figure out why CATHODE doesn't honour the header's size value.
- int OriginalLength = BiggestPart.Length;
- Array.Resize(ref NewTexture.DataBlock, OriginalLength);
-
- //Update our internal knowledge of the textures
- BiggestPart.Length = (int)NewTexture.DataBlock.Length;
- BiggestPart.Width = (Int16)NewTexture.Width;
- BiggestPart.Height = (Int16)NewTexture.Height;
- TextureEntry.Format = NewTexture.Format;
- //TODO: Update smallest here too if it exists!
- //Will need to be written into the PAK at "Pull PAK sections before/after V2" too - headers are handled already.
-
- //Load the BIN and write out updated BIN texture header
- BinaryWriter ArchiveFileBinWriter = new BinaryWriter(File.OpenWrite(FilePathBIN));
- ArchiveFileBinWriter.BaseStream.Position = TextureEntry.HeaderPos;
- ExtraBinaryUtils.WriteString(TextureEntry.Magic, ArchiveFileBinWriter);
- ArchiveFileBinWriter.Write(BitConverter.GetBytes((int)TextureEntry.Format));
- ArchiveFileBinWriter.Write((TextureEntry.Texture_V2.Length == -1) ? 0 : TextureEntry.Texture_V2.Length);
- ArchiveFileBinWriter.Write(TextureEntry.Texture_V1.Length);
- ArchiveFileBinWriter.Write(TextureEntry.Texture_V1.Width);
- ArchiveFileBinWriter.Write(TextureEntry.Texture_V1.Height);
- ArchiveFileBinWriter.Write(TextureEntry.Unk_V1);
- ArchiveFileBinWriter.Write(TextureEntry.Texture_V2.Width);
- ArchiveFileBinWriter.Write(TextureEntry.Texture_V2.Height);
- ArchiveFileBinWriter.Write(TextureEntry.Unk_V2);
- ArchiveFileBinWriter.Write(TextureEntry.UnknownHeaderBytes);
- ArchiveFileBinWriter.Close();
-
- //Update headers for V1+2 in PAK if they exist
- BinaryWriter ArchiveFileWriter = new BinaryWriter(File.OpenWrite(FilePathPAK));
- if (TextureEntry.Texture_V1.HeaderPos != -1)
- {
- ArchiveFileWriter.BaseStream.Position = TextureEntry.Texture_V1.HeaderPos;
- ArchiveFileWriter.Write(TextureEntry.Texture_V1.UnknownHeaderLead);
- ArchiveFileWriter.Write(BigEndianUtils.FlipEndian(BitConverter.GetBytes(TextureEntry.Texture_V1.Length)));
- ArchiveFileWriter.Write(BigEndianUtils.FlipEndian(BitConverter.GetBytes(TextureEntry.Texture_V1.Length)));
- ArchiveFileWriter.Write(TextureEntry.Texture_V1.UnknownHeaderTrail_1);
- ArchiveFileWriter.Write(BigEndianUtils.FlipEndian(BitConverter.GetBytes((Int16)EntryIndex)));
- ArchiveFileWriter.Write(TextureEntry.Texture_V1.UnknownHeaderTrail_2);
- }
- if (TextureEntry.Texture_V2.HeaderPos != -1)
- {
- ArchiveFileWriter.BaseStream.Position = TextureEntry.Texture_V2.HeaderPos;
- ArchiveFileWriter.Write(TextureEntry.Texture_V2.UnknownHeaderLead);
- ArchiveFileWriter.Write(BigEndianUtils.FlipEndian(BitConverter.GetBytes(TextureEntry.Texture_V2.Length)));
- ArchiveFileWriter.Write(BigEndianUtils.FlipEndian(BitConverter.GetBytes(TextureEntry.Texture_V2.Length)));
- ArchiveFileWriter.Write(TextureEntry.Texture_V2.UnknownHeaderTrail_1);
- ArchiveFileWriter.Write(BigEndianUtils.FlipEndian(BitConverter.GetBytes((Int16)EntryIndex)));
- ArchiveFileWriter.Write(TextureEntry.Texture_V2.UnknownHeaderTrail_2);
+ bin.BaseStream.Position = 0;
+ bin.Write((int)FileIdentifiers.TEXTURE_DATA);
+ bin.Write(binEntryCount);
+ bin.Write(binHeaderStart);
+ bin.Close();
+
+ //Update headers in PAK for all entries
+ BinaryWriter pak = new BinaryWriter(File.OpenWrite(_filePathPAK));
+ pak.BaseStream.Position = 32;
+ int pakEntryCount = 0;
+ for (int i = 0; i < _entries.Count; i++)
+ {
+ for (int x = 0; x < 2; x++)
+ {
+ TEX4_Part currentRes = (x == 0) ? _entries[i].tex_LowRes : _entries[i].tex_HighRes;
+ if (currentRes.Length == 0) continue;
+ pak.Write(new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 });
+ pak.Write(BigEndianUtils.FlipEndian(currentRes.Length));
+ pak.Write(BigEndianUtils.FlipEndian(currentRes.Length));
+ pak.Write(BigEndianUtils.FlipEndian(currentRes.Offset));
+ pak.Write(new byte[] { 0x00, 0x00 });
+ pak.Write((Int16)x); //isHighRes
+ pak.Write(new byte[] { 0x00, 0x00 });
+ pak.Write((Int16)256); //TODO: derive this from the actual texture
+ pak.Write(BigEndianUtils.FlipEndian(currentRes.unk1));
+ pak.Write(BigEndianUtils.FlipEndian(currentRes.unk2));
+ pak.Write(BigEndianUtils.FlipEndian((Int16)index));
+ pak.Write(new byte[] { 0x00, 0x00, 0x00, 0x00 });
+ pak.Write(BigEndianUtils.FlipEndian(currentRes.unk3));
+ pak.Write(BigEndianUtils.FlipEndian(currentRes.unk4));
+ pakEntryCount++;
+ }
}
- ArchiveFileWriter.Close();
-
- //Pull PAK sections before/after V2
- BinaryReader ArchiveFile = new BinaryReader(File.OpenRead(FilePathPAK));
- byte[] PAK_Pt1 = ArchiveFile.ReadBytes(BiggestPart.StartPos);
- ArchiveFile.BaseStream.Position += OriginalLength;
- byte[] PAK_Pt2 = ArchiveFile.ReadBytes((int)ArchiveFile.BaseStream.Length - (int)ArchiveFile.BaseStream.Position);
- ArchiveFile.Close();
-
- //Write the PAK back out with new content
- ArchiveFileWriter = new BinaryWriter(File.OpenWrite(FilePathPAK));
- ArchiveFileWriter.BaseStream.SetLength(0);
- ArchiveFileWriter.Write(PAK_Pt1);
- ArchiveFileWriter.Write(NewTexture.DataBlock);
- ArchiveFileWriter.Write(PAK_Pt2);
- ArchiveFileWriter.Close();
+ //TODO: Pull all PAK content for textures & then rewrite properly using new info
+ pak.Write(0);
+ pak.Write((int)FileIdentifiers.ASSET_FILE);
+ pak.Write((int)FileIdentifiers.TEXTURE_DATA);
+ pak.Write(pakEntryCount);
+ pak.Write(pakEntryCount);
+ pak.Close();
return PAKReturnType.SUCCESS;
}
@@ -292,30 +309,22 @@ public override PAKReturnType ExportFile(string PathToExport, string FileName)
if (FileIndex == -1) return PAKReturnType.FAIL_GENERAL_LOGIC_ERROR; //CHANGED FOR OPENCAGE
//Get the biggest texture part stored
- TEX4_Part TexturePart;
- if (TextureEntries[FileIndex].Texture_V2.Saved)
- {
- TexturePart = TextureEntries[FileIndex].Texture_V2;
- }
- else if (TextureEntries[FileIndex].Texture_V1.Saved)
- {
- TexturePart = TextureEntries[FileIndex].Texture_V1;
- }
- else
- {
- return PAKReturnType.FAIL_REQUEST_IS_UNSUPPORTED;
- }
+ TEX4_Part TexturePart = _entries[FileIndex].tex_HighRes;
+ if (TexturePart.Length == 0)
+ TexturePart = _entries[FileIndex].tex_LowRes;
+ if (TexturePart.Length == 0)
+ return PAKReturnType.FAIL_GENERAL_LOGIC_ERROR;
//Pull the texture part content from the PAK
- BinaryReader ArchiveFile = new BinaryReader(File.OpenRead(FilePathPAK));
- ArchiveFile.BaseStream.Position = TexturePart.StartPos;
+ BinaryReader ArchiveFile = new BinaryReader(File.OpenRead(_filePathPAK));
+ ArchiveFile.BaseStream.Position = (NumberOfEntriesPAK * 48) + 32 + TexturePart.Offset;
byte[] TexturePartContent = ArchiveFile.ReadBytes(TexturePart.Length);
ArchiveFile.Close();
//Generate a DDS header based on the tex4's information
DDSWriter TextureOutput;
bool FailsafeSave = false;
- switch (TextureEntries[FileIndex].Format)
+ switch (_entries[FileIndex].Format)
{
case TextureFormat.DXGI_FORMAT_BC5_UNORM:
TextureOutput = new DDSWriter(TexturePartContent, TexturePart.Width, TexturePart.Height, 32, 0, TextureType.ATI2N);
diff --git a/CathodeLib/Scripts/AssetPAKs/Headers/CS2.cs b/CathodeLib/Scripts/AssetPAKs/Headers/CS2.cs
index 8d7922d..da1e21c 100644
--- a/CathodeLib/Scripts/AssetPAKs/Headers/CS2.cs
+++ b/CathodeLib/Scripts/AssetPAKs/Headers/CS2.cs
@@ -1,29 +1,83 @@
-using System;
+using CathodeLib;
+using System;
using System.Collections.Generic;
using System.Linq;
+using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
namespace CATHODE.Assets
{
- public class CS2
- {
- /* BIN */
- public string Filename = "";
- public string ModelPartName = "";
- public string MaterialName = ""; //Pulled from MTL with MateralLibraryIndex
+ public class AlienVBF
+ {
+ public int ElementCount;
+ public List Elements;
+ }
+
+ [StructLayout(LayoutKind.Sequential, Pack = 1)]
+ public struct AlienVBFE
+ {
+ public int ArrayIndex;
+ public int Offset; // NOTE: Offset within data structure, generally not important.
+ public VBFE_InputType VariableType; //(int)
+ public VBFE_InputSlot ShaderSlot; //(int)
+ public int VariantIndex; // NOTE: Variant index such as UVs: (UV0, UV1, UV2...)
+ public int Unknown_; // NOTE: Seems to be always 2?
+ };
+
+ public enum VBFE_InputType
+ {
+ AlienVertexInputType_v3 = 0x03,
+ // TODO: Present at 'bsp_torrens' but I haven't seen models that contain that being rendered yet.
+ AlienVertexInputType_Unknown0_ = 0x04,
+ AlienVertexInputType_u32_C = 0x05,
+ AlienVertexInputType_v4u8_i = 0x06,
+ AlienVertexInputType_v4u8_f = 0x09,
+ AlienVertexInputType_v2s16_UV = 0x0A,
+ AlienVertexInputType_v4s16_f = 0x0B,
+ AlienVertexInputType_v4u8_NTB = 0x0F,
+ AlienVertexInputType_u16 = 0x13,
+ };
+
+ public enum VBFE_InputSlot
+ {
+ AlienVertexInputSlot_P = 0x01,
+ AlienVertexInputSlot_BW = 0x02, // NOTE: Bone Weights
+ AlienVertexInputSlot_BI = 0x03, // NOTE: Bone Indices
+ AlienVertexInputSlot_N = 0x04,
+ AlienVertexInputSlot_UV = 0x06,
+ AlienVertexInputSlot_T = 0x07, // NOTE: Tangent
+ AlienVertexInputSlot_B = 0x08, // NOTE: Bitangent
+ AlienVertexInputSlot_C = 0x0A, // NOTE: Color? Specular? What is this?
+ };
- public int FilenameOffset = 0;
- public int ModelPartNameOffset = 0;
- public int MaterialLibaryIndex = 0;
- public int BlockSize = 0;
- public int ScaleFactor = 0;
- public int VertCount = 0;
- public int FaceCount = 0;
- public int BoneCount = 0;
-
- /* PAK */
- public int PakOffset = 0;
- public int PakSize = 0;
- }
+ [StructLayout(LayoutKind.Sequential, Pack = 1)]
+ public struct CS2
+ {
+ public int FileNameOffset;
+ public int UnknownZero_; // NOTE: Always 0 on starting area.
+ public int ModelPartNameOffset;
+ public float UnknownValue0_; // NOTE: Always 0 on starting area.
+ public Vector3 AABBMin;
+ public float LODMinDistance_;
+ public Vector3 AABBMax;
+ public float LODMaxDistance_;
+ public int NextInLODGroup_;
+ public int FirstModelInGroupForNextLOD_;
+ public int MaterialLibraryIndex;
+ public uint Unknown2_; // NOTE: Flags?
+ public int UnknownIndex; // NOTE: -1 means no index. Seems to be related to Next/Parent.
+ public uint BlockSize;
+ public int CollisionIndex_; // NODE: If this is not -1, model piece name starts with "COL_" and are always character models.
+ public int BoneArrayOffset_; // TODO: This indexes on the leftover data and points to an array of linearly growing indices.
+ // And the array count is BoneCount.
+ public UInt16 VertexFormatIndex;
+ public UInt16 VertexFormatIndexLowDetail;
+ public UInt16 VertexFormatIndexDefault_; // TODO: This is always equals to the VertexFormatIndex
+ public UInt16 ScaleFactor;
+ public Int16 HeadRelated_; // NOTE: Seems to be valid on some 'HEAD' models, otherwise -1. Maybe morphing related???
+ public UInt16 VertexCount;
+ public UInt16 IndexCount;
+ public UInt16 BoneCount;
+ };
}
diff --git a/CathodeLib/Scripts/AssetPAKs/Headers/TEX4.cs b/CathodeLib/Scripts/AssetPAKs/Headers/TEX4.cs
index 02e9be0..9b62415 100644
--- a/CathodeLib/Scripts/AssetPAKs/Headers/TEX4.cs
+++ b/CathodeLib/Scripts/AssetPAKs/Headers/TEX4.cs
@@ -20,41 +20,49 @@ public enum TextureFormat : int
//The Tex4 Entry
class TEX4
{
- //Misc header info (used for rewriting and not a lot else)
- public string Magic = "";
- public int Length_V2 = -1;
- public int Length_V1 = -1;
- public Int16 Unk_V2 = -1;
- public Int16 Unk_V1 = -1;
- public byte[] UnknownHeaderBytes = new byte[20];
-
- //The filename and path
public string FileName = "";
- //Misc metadata
public TextureFormat Format;
- public int HeaderPos = -1;
+ public int Type = -1; //AlienTextureType
+ public AlienUnknownTextureThing UnknownTexThing;
- //Actual texture content
- public TEX4_Part Texture_V1 = new TEX4_Part();
- public TEX4_Part Texture_V2 = new TEX4_Part(); //V2 is the largest, unless we don't have a V2 in which case V1 is.
+ public TEX4_Part tex_LowRes = new TEX4_Part();
+ public TEX4_Part tex_HighRes = new TEX4_Part(); //We don't always have this
+ }
+
+ public enum AlienTextureType
+ {
+ SPECULAR_OR_NORMAL = 0,
+ DIFFUSE = 1,
+ LUT = 21,
+
+ DECAL = 5,
+ ENVIRONMENT_MAP = 7,
+
+ }
+
+ public enum AlienUnknownTextureThing
+ {
+ REGULAR_TEXTURE = 0,
+ SOME_SPECIAL_TEXTURE = 9,
}
//The Tex4 Sub-Parts
class TEX4_Part
{
- public Int16 Width = -1;
- public Int16 Height = -1;
-
- public bool Saved = false;
- public int HeaderPos = -1;
-
- public int StartPos = -1;
- public int Length = -1;
-
- //Misc header info (used for rewriting) - all byte arrays will be BIG ENDIAN
- public byte[] UnknownHeaderLead = new byte[8];
- public byte[] UnknownHeaderTrail_1 = new byte[18];
- public byte[] UnknownHeaderTrail_2 = new byte[12];
+ public Int16 Width = 0;
+ public Int16 Height = 0;
+
+ public Int16 Depth = 0;
+ public Int16 MipLevels = 0;
+
+ public int Offset = 0;
+ public int Length = 0;
+
+ //Saving these so we can re-write without issue
+ public UInt32 unk1 = 0;
+ public UInt16 unk2 = 0;
+ public UInt32 unk3 = 0;
+ public UInt32 unk4 = 0;
}
}
diff --git a/CathodeLib/Scripts/CATHODE/AnimationStringDatabase.cs b/CathodeLib/Scripts/CATHODE/AnimationStringDatabase.cs
new file mode 100644
index 0000000..a243900
--- /dev/null
+++ b/CathodeLib/Scripts/CATHODE/AnimationStringDatabase.cs
@@ -0,0 +1,115 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Runtime.InteropServices;
+using System.Text;
+using CATHODE.Scripting;
+using CathodeLib;
+
+namespace CATHODE
+{
+ /* Handles Cathode animation string DB files (ANIM_STRING_DB.BIN, ANIM_STRING_DB_DEBUG.BIN) */
+ public class AnimationStringDatabase : CathodeFile
+ {
+ private Dictionary _strings = new Dictionary();
+
+ public AnimationStringDatabase(string path) : base(path) { }
+
+ #region FILE_IO
+ /* Load the file */
+ protected override bool Load()
+ {
+ if (!File.Exists(_filepath)) return false;
+
+ BinaryReader stream = new BinaryReader(File.OpenRead(_filepath));
+ try
+ {
+ //Read all data in
+ int EntryCount = stream.ReadInt32();
+ int StringCount = stream.ReadInt32();
+ Entry[] entries = Utilities.ConsumeArray(stream, EntryCount);
+ int[] stringOffsets = Utilities.ConsumeArray(stream, StringCount);
+ List strings = new List();
+ for (int i = 0; i < StringCount; i++) strings.Add(Utilities.ReadString(stream));
+
+ //Parse
+ for (int i = 0; i < entries.Length; i++) _strings.Add(entries[i].StringID, strings[entries[i].StringIndex]);
+ //TODO: encoding on a couple strings here is wrong
+ }
+ catch
+ {
+ stream.Close();
+ return false;
+ }
+ stream.Close();
+ return true;
+ }
+
+ /* Save the file */
+ override public bool Save()
+ {
+ BinaryWriter writer = new BinaryWriter(File.OpenWrite(_filepath));
+ try
+ {
+ writer.Write(_strings.Count);
+ writer.Write(_strings.Count);
+ int count = 0;
+ foreach (KeyValuePair value in _strings)
+ {
+ writer.Write(value.Key);
+ writer.Write(count);
+ count++;
+ }
+ int baseline = (_strings.Count * 4 * 2) + 8 + (_strings.Count * 4);
+ writer.BaseStream.Position = baseline;
+ List stringOffsets = new List();
+ foreach (KeyValuePair value in _strings)
+ {
+ stringOffsets.Add((int)writer.BaseStream.Position - baseline);
+ ExtraBinaryUtils.WriteString(value.Value, writer);
+ writer.Write((char)0x00);
+ }
+ writer.BaseStream.Position = (_strings.Count * 4 * 2) + 8;
+ for (int i = 0; i < stringOffsets.Count; i++)
+ {
+ writer.Write(stringOffsets[i]);
+ }
+ }
+ catch
+ {
+ writer.Close();
+ return false;
+ }
+ writer.Close();
+ return true;
+ }
+ #endregion
+
+ #region ACCESSORS
+ /* Add a string to the DB */
+ public void AddString(string str)
+ {
+ uint id = Utilities.AnimationHashedString(str);
+ if (_strings.ContainsKey(id)) return;
+ _strings.Add(id, str);
+ }
+
+ /* Remove a string from the DB */
+ public void RemoveString(string str)
+ {
+ uint id = Utilities.AnimationHashedString(str);
+ _strings.Remove(id);
+ }
+ #endregion
+
+ #region STRUCTURES
+ [StructLayout(LayoutKind.Sequential, Pack = 1)]
+ private struct Entry
+ {
+ public uint StringID;
+ public int StringIndex;
+ };
+ #endregion
+ }
+}
diff --git a/CathodeLib/Scripts/CATHODE/CollisionMapDatabase.cs b/CathodeLib/Scripts/CATHODE/CollisionMapDatabase.cs
new file mode 100644
index 0000000..b177959
--- /dev/null
+++ b/CathodeLib/Scripts/CATHODE/CollisionMapDatabase.cs
@@ -0,0 +1,92 @@
+using CathodeLib;
+using System.IO;
+using System.Runtime.InteropServices;
+
+namespace CATHODE
+{
+ /* Handles Cathode COLLISION.MAP files */
+ public class CollisionMapDatabase : CathodeFile
+ {
+ //TODO: tidy how we access these
+ public Header _header;
+ public Entry[] _entries;
+
+ public CollisionMapDatabase(string path) : base(path) { }
+
+ #region FILE_IO
+ /* Load the file */
+ protected override bool Load()
+ {
+ if (!File.Exists(_filepath)) return false;
+
+ BinaryReader stream = new BinaryReader(File.OpenRead(_filepath));
+ try
+ {
+ _header = Utilities.Consume(stream);
+ _entries = Utilities.ConsumeArray(stream, _header.EntryCount);
+ }
+ catch
+ {
+ stream.Close();
+ return false;
+ }
+ stream.Close();
+ return true;
+ }
+
+ /* Save the file */
+ override public bool Save()
+ {
+ BinaryWriter stream = new BinaryWriter(File.OpenWrite(_filepath));
+ try
+ {
+ stream.BaseStream.SetLength(0);
+ Utilities.Write(stream, _header);
+ Utilities.Write(stream, _entries);
+ }
+ catch
+ {
+ stream.Close();
+ return false;
+ }
+ stream.Close();
+ return true;
+ }
+ #endregion
+
+ #region ACCESSORS
+ /* Data accessors */
+ public int EntryCount { get { return _entries.Length; } }
+ public Entry[] Entries { get { return _entries; } }
+ public Entry GetEntry(int i)
+ {
+ return _entries[i];
+ }
+
+ /* Data setters */
+ public void SetEntry(int i, Entry content)
+ {
+ _entries[i] = content;
+ }
+ #endregion
+
+ #region STRUCTURES
+ [StructLayout(LayoutKind.Sequential, Pack = 1)]
+ public struct Header
+ {
+ public int DataSize;
+ public int EntryCount;
+ };
+
+ [StructLayout(LayoutKind.Sequential, Pack = 1)]
+ public struct Entry
+ {
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
+ public int[] Unknowns1; //12
+ public int ID;
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 7)]
+ public int[] Unknowns2; //12
+ };
+ #endregion
+ }
+}
\ No newline at end of file
diff --git a/CathodeLib/Scripts/CommandsPAK/CommandsPAK.cs b/CathodeLib/Scripts/CATHODE/Commands.cs
similarity index 69%
rename from CathodeLib/Scripts/CommandsPAK/CommandsPAK.cs
rename to CathodeLib/Scripts/CATHODE/Commands.cs
index 7e0a38c..21138ad 100644
--- a/CathodeLib/Scripts/CommandsPAK/CommandsPAK.cs
+++ b/CathodeLib/Scripts/CATHODE/Commands.cs
@@ -1,112 +1,44 @@
//#define DO_PRETTY_COMPOSITES
+using CATHODE.Scripting;
using CathodeLib;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
-#if UNITY_EDITOR || UNITY_STANDALONE
-using UnityEngine;
-#endif
-namespace CATHODE.Commands
+namespace CATHODE
{
- /*
-
- A:I iOS contains all symbols which has been super helpful. There's some new Commands info, as well as a bunch of info about REDS/MVR.
-
- COMMANDS.BIN loads into CommandBuffer object.
- CommandBuffer is looped through once for number of commands entries, adding or removing:
- composite_template
- alias_from_command
- parameter_from_command
- entity_to_seq_from_command
- link
- connector
- resource
- track_from_command
- It's then looped through again, adding or removing:
- entity
- binding_from_command
- **something undefined**
- method
- proxy_from_command
- breakpoint_from_command
-
- */
-
- public class CommandsPAK
+ public class Commands : CathodeFile
{
public Action OnLoaded;
public Action OnSaved;
- /* Load and parse the COMMANDS.PAK */
- public CommandsPAK(string pathToPak)
- {
- _path = pathToPak;
- _didLoadCorrectly = Load(_path);
- }
-
- #region ACCESSORS
- /* Return a list of filenames for composites in the CommandsPAK archive */
- public string[] GetCompositeNames()
- {
- string[] toReturn = new string[_composites.Count];
- for (int i = 0; i < _composites.Count; i++) toReturn[i] = _composites[i].name;
- return toReturn;
- }
-
- /* Find the a script entry object by name */
- public int GetFileIndex(string FileName)
- {
- for (int i = 0; i < _composites.Count; i++) if (_composites[i].name == FileName || _composites[i].name == FileName.Replace('/', '\\')) return i;
- return -1;
- }
-
- /* Get an individual composite */
- public CathodeComposite GetComposite(ShortGuid id)
- {
- if (id.val == null) return null;
- return _composites.FirstOrDefault(o => o.shortGUID == id);
- }
- public CathodeComposite GetCompositeByIndex(int index)
- {
- return (index >= _composites.Count || index < 0) ? null : _composites[index];
- }
-
- /* Get all composites */
- public List Composites { get { return _composites; } }
+ // This is always:
+ // - Root Instance (the map's entry composite, usually containing entities that call mission/environment composites)
+ // - Global Instance (the main data handler for keeping track of mission number, etc - kinda like a big singleton)
+ // - Pause Menu Instance
+ private ShortGuid[] _entryPoints;
+ private Composite[] _entryPointObjects = null;
- /* Get entry point composite objects */
- public CathodeComposite[] EntryPoints
- {
- get
- {
- if (_entryPointObjects != null) return _entryPointObjects;
- _entryPointObjects = new CathodeComposite[_entryPoints.compositeIDs.Length];
- for (int i = 0; i < _entryPoints.compositeIDs.Length; i++) _entryPointObjects[i] = GetComposite(_entryPoints.compositeIDs[i]);
- return _entryPointObjects;
- }
- }
+ private List _composites = null;
- /* Set the root composite for this COMMANDS.PAK (the root of the level - GLOBAL and PAUSEMENU are also instanced) */
- public void SetRootComposite(ShortGuid id)
- {
- _entryPoints.compositeIDs[0] = id;
- _entryPointObjects = null;
- }
- #endregion
+ public Commands(string path) : base(path) { }
- #region WRITING
+ #region FILE_IO
/* Save all changes back out */
- public void Save()
+ override public bool Save()
{
- BinaryWriter writer = new BinaryWriter(File.OpenWrite(_path));
+ if (_entryPoints == null || _entryPoints.Length != 3 || _entryPoints[0] == null)
+ return false;
+
+ BinaryWriter writer = new BinaryWriter(File.OpenWrite(_filepath));
writer.BaseStream.SetLength(0);
//Write entry points
- Utilities.Write(writer, _entryPoints);
+ for (int i = 0; i < 3; i++)
+ Utilities.Write(writer, _entryPoints[i]);
//Write placeholder info for parameter/composite offsets
int offsetToRewrite = (int)writer.BaseStream.Position;
@@ -115,11 +47,96 @@ public void Save()
writer.Write(0);
writer.Write(0);
+ //Fix (& verify) entity-attached resource info
+ for (int i = 0; i < _composites.Count; i++)
+ {
+ List animatedModels = new List();
+ for (int x = 0; x < _composites[i].functions.Count; x++)
+ {
+ if (!CommandsUtils.FunctionTypeExists(_composites[i].functions[x].function)) continue;
+ FunctionType type = CommandsUtils.GetFunctionType(_composites[i].functions[x].function);
+ switch (type)
+ {
+ // Types below require resources we can add, and information we should probably correct, so do it automatically!
+ case FunctionType.SoundBarrier:
+ _composites[i].functions[x].AddResource(ResourceType.COLLISION_MAPPING);
+ break;
+ case FunctionType.ExclusiveMaster:
+ _composites[i].functions[x].AddResource(ResourceType.EXCLUSIVE_MASTER_STATE_RESOURCE);
+ break;
+ case FunctionType.TRAV_1ShotSpline:
+ //TODO: There are loads of TRAV_ entities which are unused in the vanilla game, so I'm not sure if they should apply to those too...
+ _composites[i].functions[x].AddResource(ResourceType.TRAVERSAL_SEGMENT);
+ break;
+ case FunctionType.NavMeshBarrier:
+ _composites[i].functions[x].AddResource(ResourceType.NAV_MESH_BARRIER_RESOURCE);
+ _composites[i].functions[x].AddResource(ResourceType.COLLISION_MAPPING);
+ break;
+ case FunctionType.PhysicsSystem:
+ Parameter dps_index = _composites[i].functions[x].GetParameter("system_index");
+ if (dps_index == null)
+ {
+ dps_index = new Parameter("system_index", new cInteger(0));
+ _composites[i].functions[x].parameters.Add(dps_index);
+ }
+ _composites[i].functions[x].AddResource(ResourceType.DYNAMIC_PHYSICS_SYSTEM).startIndex = ((cInteger)dps_index.content).value;
+ break;
+ case FunctionType.EnvironmentModelReference:
+ Parameter rsc = _composites[i].functions[x].GetParameter("resource");
+ if (rsc == null)
+ {
+ rsc = new Parameter("resource", new cResource(_composites[i].functions[x].shortGUID));
+ _composites[i].functions[x].parameters.Add(rsc);
+ }
+ cResource rsc_p = (cResource)rsc.content;
+ rsc_p.AddResource(ResourceType.ANIMATED_MODEL);
+ break;
+
+ // Types below require various things, but we don't add them as they work without it, so just log a warning.
+ case FunctionType.ModelReference:
+ Parameter mdl = _composites[i].functions[x].GetParameter("resource");
+ if (mdl == null)
+ {
+ mdl = new Parameter("resource", new cResource(_composites[i].functions[x].shortGUID));
+ _composites[i].functions[x].parameters.Add(mdl);
+ }
+ cResource mdl_p = (cResource)mdl.content;
+ if (mdl_p.GetResource(ResourceType.RENDERABLE_INSTANCE) == null)
+ Console.WriteLine("WARNING: ModelReference resource parameter does not contain a RENDERABLE_INSTANCE resource reference!");
+ if (mdl_p.GetResource(ResourceType.COLLISION_MAPPING) == null)
+ Console.WriteLine("WARNING: ModelReference resource parameter does not contain a COLLISION_MAPPING resource reference!");
+ break;
+ case FunctionType.CollisionBarrier:
+ if (_composites[i].functions[x].GetResource(ResourceType.COLLISION_MAPPING) == null)
+ Console.WriteLine("WARNING: CollisionBarrier entity does not contain a COLLISION_MAPPING resource reference!");
+ break;
+
+ // Types below require only RENDERABLE_INSTANCE resource references on the entity, pointing to the commented model.
+ // We can't add them automatically as we need to know REDS indexes!
+ // UPDATE: I think the game can handle any resource being set here!
+ case FunctionType.ParticleEmitterReference: /// [dynamic_mesh] /// - I think i've also seen 1000 particle system too
+ case FunctionType.RibbonEmitterReference: /// [dynamic_mesh]
+ case FunctionType.SurfaceEffectBox: /// Global/Props/fogbox.CS2 -> [VolumeFog]
+ case FunctionType.FogBox: /// Global/Props/fogplane.CS2 -> [Plane01]
+ case FunctionType.SurfaceEffectSphere: /// Global/Props/fogsphere.CS2 -> [Sphere01]
+ case FunctionType.FogSphere: /// Global/Props/fogsphere.CS2 -> [Sphere01]
+ case FunctionType.SimpleRefraction: /// Global/Props/refraction.CS2 -> [Plane01]
+ case FunctionType.SimpleWater: /// Global/Props/noninteractive_water.CS2 -> [Plane01]
+ case FunctionType.LightReference: /// Global/Props/deferred_point_light.cs2 -> [Sphere01], Global/Props/deferred_spot_light.cs2 -> [Sphere02], Global/Props/deferred_strip_light.cs2 -> [Sphere01]
+ if (_composites[i].functions[x].GetResource(ResourceType.RENDERABLE_INSTANCE) == null)
+ Console.WriteLine("ERROR: " + type + " entity does not contain a RENDERABLE_INSTANCE resource reference!");
+ if (_composites[i].functions[x].GetParameter("resource") != null)
+ throw new Exception("Function entity of type " + type + " had an invalid resource parameter applied!");
+ break;
+ }
+ }
+ }
+
//Work out unique parameters to write
- List parameters = new List();
+ List parameters = new List();
for (int i = 0; i < _composites.Count; i++)
{
- List fgEntities = _composites[i].GetEntities();
+ List fgEntities = _composites[i].GetEntities();
for (int x = 0; x < fgEntities.Count; x++)
for (int y = 0; y < fgEntities[x].parameters.Count; y++)
parameters.Add(fgEntities[x].parameters[y].content);
@@ -134,45 +151,45 @@ public void Save()
Utilities.Write(writer, CommandsUtils.GetDataTypeGUID(parameters[i].dataType));
switch (parameters[i].dataType)
{
- case CathodeDataType.POSITION:
- Vector3 pos = ((CathodeTransform)parameters[i]).position;
- Vector3 rot = ((CathodeTransform)parameters[i]).rotation;
+ case DataType.TRANSFORM:
+ Vector3 pos = ((cTransform)parameters[i]).position;
+ Vector3 rot = ((cTransform)parameters[i]).rotation;
writer.Write(pos.x); writer.Write(pos.y); writer.Write(pos.z);
writer.Write(rot.y); writer.Write(rot.x); writer.Write(rot.z);
break;
- case CathodeDataType.INTEGER:
- writer.Write(((CathodeInteger)parameters[i]).value);
+ case DataType.INTEGER:
+ writer.Write(((cInteger)parameters[i]).value);
break;
- case CathodeDataType.STRING:
+ case DataType.STRING:
int stringStart = ((int)writer.BaseStream.Position + 4) / 4;
byte[] stringStartRaw = BitConverter.GetBytes(stringStart);
stringStartRaw[3] = 0x80;
writer.Write(stringStartRaw);
- string str = ((CathodeString)parameters[i]).value;
+ string str = ((cString)parameters[i]).value;
writer.Write(ShortGuidUtils.Generate(str).val);
for (int x = 0; x < str.Length; x++) writer.Write(str[x]);
writer.Write((char)0x00);
Utilities.Align(writer, 4);
break;
- case CathodeDataType.BOOL:
- if (((CathodeBool)parameters[i]).value) writer.Write(1); else writer.Write(0);
+ case DataType.BOOL:
+ if (((cBool)parameters[i]).value) writer.Write(1); else writer.Write(0);
break;
- case CathodeDataType.FLOAT:
- writer.Write(((CathodeFloat)parameters[i]).value);
+ case DataType.FLOAT:
+ writer.Write(((cFloat)parameters[i]).value);
break;
- case CathodeDataType.SHORT_GUID:
- Utilities.Write(writer, ((CathodeResource)parameters[i]).resourceID);
+ case DataType.RESOURCE:
+ Utilities.Write(writer, ((cResource)parameters[i]).resourceID);
break;
- case CathodeDataType.DIRECTION:
- Vector3 dir = ((CathodeVector3)parameters[i]).value;
+ case DataType.VECTOR:
+ Vector3 dir = ((cVector3)parameters[i]).value;
writer.Write(dir.x); writer.Write(dir.y); writer.Write(dir.z);
break;
- case CathodeDataType.ENUM:
- Utilities.Write(writer, ((CathodeEnum)parameters[i]).enumID);
- writer.Write(((CathodeEnum)parameters[i]).enumIndex);
+ case DataType.ENUM:
+ Utilities.Write(writer, ((cEnum)parameters[i]).enumID);
+ writer.Write(((cEnum)parameters[i]).enumIndex);
break;
- case CathodeDataType.SPLINE_DATA:
- CathodeSpline thisSpline = ((CathodeSpline)parameters[i]);
+ case DataType.SPLINE:
+ cSpline thisSpline = ((cSpline)parameters[i]);
writer.Write(((int)writer.BaseStream.Position + 8) / 4);
writer.Write(thisSpline.splinePoints.Count);
for (int x = 0; x < thisSpline.splinePoints.Count; x++)
@@ -201,14 +218,14 @@ public void Save()
Utilities.Align(writer, 4);
//Work out what we want to write
- List ents = _composites[i].GetEntities();
- List entitiesWithLinks = new List(ents.FindAll(o => o.childLinks.Count != 0));
- List entitiesWithParams = new List(ents.FindAll(o => o.parameters.Count != 0));
+ List ents = _composites[i].GetEntities();
+ List entitiesWithLinks = new List(ents.FindAll(o => o.childLinks.Count != 0));
+ List entitiesWithParams = new List(ents.FindAll(o => o.parameters.Count != 0));
//TODO: find a nicer way to sort into entity class types
List cageAnimationEntities = new List();
List triggerSequenceEntities = new List();
- ShortGuid cageAnimationGUID = CommandsUtils.GetFunctionTypeGUID(CathodeFunctionType.CAGEAnimation);
- ShortGuid triggerSequenceGUID = CommandsUtils.GetFunctionTypeGUID(CathodeFunctionType.TriggerSequence);
+ ShortGuid cageAnimationGUID = CommandsUtils.GetFunctionTypeGUID(FunctionType.CAGEAnimation);
+ ShortGuid triggerSequenceGUID = CommandsUtils.GetFunctionTypeGUID(FunctionType.TriggerSequence);
for (int x = 0; x < _composites[i].functions.Count; x++)
{
if (_composites[i].functions[x].function == cageAnimationGUID)
@@ -226,22 +243,21 @@ public void Save()
}
//Reconstruct resources
- List resourceReferences = new List();
- ShortGuid resourceParamID = ShortGuidUtils.Generate("resource");
- for (int x = 0; x < ents.Count; x++)
+ List resourceReferences = new List();
+ ShortGuid resource_param_id = ShortGuidUtils.Generate("resource");
+ for (int x = 0; x < _composites[i].functions.Count; x++)
{
- for (int y = 0; y < ents[x].resources.Count; y++)
- if (!resourceReferences.Contains(ents[x].resources[y]))
- resourceReferences.Add(ents[x].resources[y]);
+ for (int y = 0; y < _composites[i].functions[x].resources.Count; y++)
+ if (!resourceReferences.Contains(_composites[i].functions[x].resources[y]))
+ resourceReferences.Add(_composites[i].functions[x].resources[y]);
- CathodeLoadedParameter resParam = ents[x].parameters.FirstOrDefault(o => o.shortGUID == resourceParamID);
+ Parameter resParam = _composites[i].functions[x].parameters.FirstOrDefault(o => o.shortGUID == resource_param_id);
if (resParam == null) continue;
- List resParamRef = ((CathodeResource)resParam.content).value;
+ List resParamRef = ((cResource)resParam.content).value;
for (int y = 0; y < resParamRef.Count; y++)
if (!resourceReferences.Contains(resParamRef[y]))
resourceReferences.Add(resParamRef[y]);
}
- resourceReferences.AddRange(_composites[i].resources);
//Sort
entitiesWithLinks = entitiesWithLinks.OrderBy(o => o.shortGUID.ToUInt32()).ToList();
@@ -250,25 +266,25 @@ public void Save()
_composites[i].SortEntities();
//Write data
- OffsetPair[] scriptPointerOffsetInfo = new OffsetPair[(int)CommandsDataBlock.NUMBER_OF_SCRIPT_BLOCKS];
- for (int x = 0; x < (int)CommandsDataBlock.NUMBER_OF_SCRIPT_BLOCKS; x++)
+ OffsetPair[] scriptPointerOffsetInfo = new OffsetPair[(int)DataBlock.NUMBER_OF_SCRIPT_BLOCKS];
+ for (int x = 0; x < (int)DataBlock.NUMBER_OF_SCRIPT_BLOCKS; x++)
{
- switch ((CommandsDataBlock)x)
+ switch ((DataBlock)x)
{
- case CommandsDataBlock.COMPOSITE_HEADER:
+ case DataBlock.COMPOSITE_HEADER:
{
scriptPointerOffsetInfo[x] = new OffsetPair(writer.BaseStream.Position, 2);
Utilities.Write(writer, _composites[i].shortGUID);
writer.Write(0);
break;
}
- case CommandsDataBlock.ENTITY_CONNECTIONS:
+ case DataBlock.ENTITY_CONNECTIONS:
{
List offsetPairs = new List();
- foreach (CathodeEntity entityWithLink in entitiesWithLinks)
+ foreach (Entity entityWithLink in entitiesWithLinks)
{
offsetPairs.Add(new OffsetPair(writer.BaseStream.Position, entityWithLink.childLinks.Count));
- Utilities.Write(writer, entityWithLink.childLinks);
+ Utilities.Write(writer, entityWithLink.childLinks);
}
scriptPointerOffsetInfo[x] = new OffsetPair(writer.BaseStream.Position, entitiesWithLinks.Count);
@@ -281,10 +297,10 @@ public void Save()
break;
}
- case CommandsDataBlock.ENTITY_PARAMETERS:
+ case DataBlock.ENTITY_PARAMETERS:
{
List offsetPairs = new List();
- foreach (CathodeEntity entityWithParam in entitiesWithParams)
+ foreach (Entity entityWithParam in entitiesWithParams)
{
offsetPairs.Add(new OffsetPair(writer.BaseStream.Position, entityWithParam.parameters.Count));
for (int y = 0; y < entityWithParam.parameters.Count; y++)
@@ -314,7 +330,7 @@ public void Save()
}
break;
}
- case CommandsDataBlock.ENTITY_OVERRIDES:
+ case DataBlock.ENTITY_OVERRIDES:
{
List offsetPairs = new List();
for (int p = 0; p < _composites[i].overrides.Count; p++)
@@ -332,7 +348,7 @@ public void Save()
}
break;
}
- case CommandsDataBlock.ENTITY_OVERRIDES_CHECKSUM:
+ case DataBlock.ENTITY_OVERRIDES_CHECKSUM:
{
scriptPointerOffsetInfo[x] = new OffsetPair(writer.BaseStream.Position, reshuffledChecksums.Count);
for (int p = 0; p < reshuffledChecksums.Count; p++)
@@ -342,18 +358,18 @@ public void Save()
}
break;
}
- case CommandsDataBlock.COMPOSITE_EXPOSED_PARAMETERS:
+ case DataBlock.COMPOSITE_EXPOSED_PARAMETERS:
{
- scriptPointerOffsetInfo[x] = new OffsetPair(writer.BaseStream.Position, _composites[i].datatypes.Count);
- for (int p = 0; p < _composites[i].datatypes.Count; p++)
+ scriptPointerOffsetInfo[x] = new OffsetPair(writer.BaseStream.Position, _composites[i].variables.Count);
+ for (int p = 0; p < _composites[i].variables.Count; p++)
{
- writer.Write(_composites[i].datatypes[p].shortGUID.val);
- writer.Write(CommandsUtils.GetDataTypeGUID(_composites[i].datatypes[p].type).val);
- writer.Write(_composites[i].datatypes[p].parameter.val);
+ writer.Write(_composites[i].variables[p].shortGUID.val);
+ writer.Write(CommandsUtils.GetDataTypeGUID(_composites[i].variables[p].type).val);
+ writer.Write(_composites[i].variables[p].parameter.val);
}
break;
}
- case CommandsDataBlock.ENTITY_PROXIES:
+ case DataBlock.ENTITY_PROXIES:
{
List offsetPairs = new List();
for (int p = 0; p < _composites[i].proxies.Count; p++)
@@ -373,7 +389,7 @@ public void Save()
}
break;
}
- case CommandsDataBlock.ENTITY_FUNCTIONS:
+ case DataBlock.ENTITY_FUNCTIONS:
{
scriptPointerOffsetInfo[x] = new OffsetPair(writer.BaseStream.Position, _composites[i].functions.Count);
for (int p = 0; p < _composites[i].functions.Count; p++)
@@ -383,46 +399,45 @@ public void Save()
}
break;
}
- case CommandsDataBlock.RESOURCE_REFERENCES:
+ case DataBlock.RESOURCE_REFERENCES:
{
- //TODO: this case is quite messy as full parsing still isn't known
scriptPointerOffsetInfo[x] = new OffsetPair(writer.BaseStream.Position, resourceReferences.Count);
for (int p = 0; p < resourceReferences.Count; p++)
{
- writer.Write(resourceReferences[p].resourceRefID.val);
- writer.Write(resourceReferences[p].unknownID1.val);
- writer.Write(resourceReferences[p].positionOffset.x);
- writer.Write(resourceReferences[p].positionOffset.y);
- writer.Write(resourceReferences[p].positionOffset.z);
- writer.Write(resourceReferences[p].unknownID2.val);
+ writer.Write(resourceReferences[p].position.x);
+ writer.Write(resourceReferences[p].position.y);
+ writer.Write(resourceReferences[p].position.z);
+ writer.Write(resourceReferences[p].rotation.x);
+ writer.Write(resourceReferences[p].rotation.y);
+ writer.Write(resourceReferences[p].rotation.z);
writer.Write(resourceReferences[p].resourceID.val); //Sometimes this is the entity ID that uses the resource, other times it's the "resource" parameter ID link
writer.Write(CommandsUtils.GetResourceEntryTypeGUID(resourceReferences[p].entryType).val);
switch (resourceReferences[p].entryType)
{
- case CathodeResourceReferenceType.RENDERABLE_INSTANCE:
- writer.Write(resourceReferences[p].entryIndexREDS);
- writer.Write(resourceReferences[p].entryCountREDS);
+ case ResourceType.RENDERABLE_INSTANCE:
+ writer.Write(resourceReferences[p].startIndex);
+ writer.Write(resourceReferences[p].count);
break;
- case CathodeResourceReferenceType.COLLISION_MAPPING:
- writer.Write(resourceReferences[p].unknownInteger1);
+ case ResourceType.COLLISION_MAPPING:
+ writer.Write(resourceReferences[p].startIndex);
writer.Write(resourceReferences[p].entityID.val);
break;
- case CathodeResourceReferenceType.EXCLUSIVE_MASTER_STATE_RESOURCE:
- case CathodeResourceReferenceType.NAV_MESH_BARRIER_RESOURCE:
- case CathodeResourceReferenceType.TRAVERSAL_SEGMENT:
- writer.Write(resourceReferences[p].unknownInteger1);
- writer.Write(resourceReferences[p].unknownInteger2);
+ case ResourceType.ANIMATED_MODEL:
+ case ResourceType.DYNAMIC_PHYSICS_SYSTEM:
+ writer.Write(resourceReferences[p].startIndex);
+ writer.Write(-1);
break;
- case CathodeResourceReferenceType.ANIMATED_MODEL:
- case CathodeResourceReferenceType.DYNAMIC_PHYSICS_SYSTEM:
- writer.Write(resourceReferences[p].unknownInteger1);
- writer.Write(resourceReferences[p].unknownInteger2);
+ case ResourceType.EXCLUSIVE_MASTER_STATE_RESOURCE:
+ case ResourceType.NAV_MESH_BARRIER_RESOURCE:
+ case ResourceType.TRAVERSAL_SEGMENT:
+ writer.Write(-1);
+ writer.Write(-1);
break;
}
}
break;
}
- case CommandsDataBlock.TRIGGERSEQUENCE_DATA: //Actually CAGEANIMATION_DATA, but indexes are flipped
+ case DataBlock.TRIGGERSEQUENCE_DATA: //Actually CAGEANIMATION_DATA, but indexes are flipped
{
List globalOffsets = new List();
for (int p = 0; p < cageAnimationEntities.Count; p++)
@@ -514,14 +529,14 @@ public void Save()
writer.Write(cageAnimationEntities[p].paramsData3.Count);
}
- scriptPointerOffsetInfo[(int)CommandsDataBlock.CAGEANIMATION_DATA] = new OffsetPair(writer.BaseStream.Position, globalOffsets.Count);
+ scriptPointerOffsetInfo[(int)DataBlock.CAGEANIMATION_DATA] = new OffsetPair(writer.BaseStream.Position, globalOffsets.Count);
for (int p = 0; p < globalOffsets.Count; p++)
{
writer.Write(globalOffsets[p] / 4);
}
break;
}
- case CommandsDataBlock.CAGEANIMATION_DATA: //Actually TRIGGERSEQUENCE_DATA, but indexes are flipped
+ case DataBlock.CAGEANIMATION_DATA: //Actually TRIGGERSEQUENCE_DATA, but indexes are flipped
{
List globalOffsets = new List();
for (int p = 0; p < triggerSequenceEntities.Count; p++)
@@ -557,19 +572,19 @@ public void Save()
writer.Write(triggerSequenceEntities[p].events.Count);
}
- scriptPointerOffsetInfo[(int)CommandsDataBlock.TRIGGERSEQUENCE_DATA] = new OffsetPair(writer.BaseStream.Position, globalOffsets.Count);
+ scriptPointerOffsetInfo[(int)DataBlock.TRIGGERSEQUENCE_DATA] = new OffsetPair(writer.BaseStream.Position, globalOffsets.Count);
for (int p = 0; p < globalOffsets.Count; p++)
{
writer.Write(globalOffsets[p] / 4);
}
break;
}
- case CommandsDataBlock.UNUSED:
+ case DataBlock.UNUSED:
{
scriptPointerOffsetInfo[x] = new OffsetPair(0, 0);
break;
}
- case CommandsDataBlock.UNKNOWN_COUNTS:
+ case DataBlock.UNKNOWN_COUNTS:
{
//TODO: These count values are unknown. Temp fix in place for the div by 4 at the end on offset (as this isn't actually an offset!)
scriptPointerOffsetInfo[x] = new OffsetPair(_composites[i].unknownPair.GlobalOffset * 4, _composites[i].unknownPair.EntryCount);
@@ -581,7 +596,7 @@ public void Save()
//Write pointers to the pointers of the content
compositeOffsets[i] = (int)writer.BaseStream.Position / 4;
writer.Write(0);
- for (int x = 0; x < (int)CommandsDataBlock.NUMBER_OF_SCRIPT_BLOCKS; x++)
+ for (int x = 0; x < (int)DataBlock.NUMBER_OF_SCRIPT_BLOCKS; x++)
{
if (x == 0)
{
@@ -612,47 +627,19 @@ public void Save()
writer.Close();
OnSaved?.Invoke();
- }
-
- /* Filter down a list of parameters to contain only unique entries */
- private List PruneParameterList(List parameters)
- {
- List prunedList = new List();
- bool canAdd = true;
- for (int i = 0; i < parameters.Count; i++)
- {
- canAdd = true;
- for (int x = 0; x < prunedList.Count; x++)
- {
- if (prunedList[x] == parameters[i]) //This is where the bulk of our logic lies
- {
- canAdd = false;
- break;
- }
- }
- if (canAdd) prunedList.Add(parameters[i]);
- }
- return prunedList;
- }
- #endregion
-
- #region READING
- /* Read offset info & count, jump to the offset & return the count */
- private int JumpToOffset(ref BinaryReader reader)
- {
- CommandsOffsetPair pair = Utilities.Consume(reader);
- reader.BaseStream.Position = pair.offset * 4;
- return pair.count;
+ return true;
}
/* Read the parameter and composite offsets & get entry points */
- private bool Load(string path)
+ override protected bool Load()
{
- if (!File.Exists(path)) return false;
- BinaryReader reader = new BinaryReader(File.OpenRead(path));
+ if (!File.Exists(_filepath)) return false;
+
+ BinaryReader reader = new BinaryReader(File.OpenRead(_filepath));
//Read entry points
- _entryPoints = Utilities.Consume(reader);
+ _entryPoints = new ShortGuid[3];
+ for (int i = 0; i < 3; i++) _entryPoints[i] = Utilities.Consume(reader);
//Read parameter/composite counts
int parameter_offset_pos = reader.ReadInt32() * 4;
@@ -667,78 +654,70 @@ private bool Load(string path)
int[] compositeOffsets = Utilities.ConsumeArray(reader, composite_count);
//Read all parameters from the PAK
- Dictionary parameters = new Dictionary(parameter_count);
+ Dictionary parameters = new Dictionary(parameter_count);
for (int i = 0; i < parameter_count; i++)
{
reader.BaseStream.Position = parameterOffsets[i] * 4;
- CathodeParameter this_parameter = new CathodeParameter(CommandsUtils.GetDataType(new ShortGuid(reader)));
+ ParameterData this_parameter = new ParameterData(CommandsUtils.GetDataType(new ShortGuid(reader)));
switch (this_parameter.dataType)
{
- case CathodeDataType.POSITION:
- this_parameter = new CathodeTransform();
- ((CathodeTransform)this_parameter).position = new Vector3(reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle());
+ case DataType.TRANSFORM:
+ this_parameter = new cTransform();
+ ((cTransform)this_parameter).position = new Vector3(reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle());
float _x, _y, _z; _y = reader.ReadSingle(); _x = reader.ReadSingle(); _z = reader.ReadSingle(); //Y,X,Z!
- ((CathodeTransform)this_parameter).rotation = new Vector3(_x, _y, _z);
+ ((cTransform)this_parameter).rotation = new Vector3(_x, _y, _z);
break;
- case CathodeDataType.INTEGER:
- this_parameter = new CathodeInteger();
- ((CathodeInteger)this_parameter).value = reader.ReadInt32();
+ case DataType.INTEGER:
+ this_parameter = new cInteger(reader.ReadInt32());
break;
- case CathodeDataType.STRING:
- this_parameter = new CathodeString();
+ case DataType.STRING:
reader.BaseStream.Position += 8;
- ((CathodeString)this_parameter).value = Utilities.ReadString(reader);
+ this_parameter = new cString(Utilities.ReadString(reader));
Utilities.Align(reader, 4);
break;
- case CathodeDataType.BOOL:
- this_parameter = new CathodeBool();
- ((CathodeBool)this_parameter).value = (reader.ReadInt32() == 1);
+ case DataType.BOOL:
+ this_parameter = new cBool((reader.ReadInt32() == 1));
break;
- case CathodeDataType.FLOAT:
- this_parameter = new CathodeFloat();
- ((CathodeFloat)this_parameter).value = reader.ReadSingle();
+ case DataType.FLOAT:
+ this_parameter = new cFloat(reader.ReadSingle());
break;
- case CathodeDataType.SHORT_GUID:
- this_parameter = new CathodeResource();
- ((CathodeResource)this_parameter).resourceID = new ShortGuid(reader);
+ case DataType.RESOURCE:
+ this_parameter = new cResource(new ShortGuid(reader));
break;
- case CathodeDataType.DIRECTION:
- this_parameter = new CathodeVector3();
- ((CathodeVector3)this_parameter).value = new Vector3(reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle());
+ case DataType.VECTOR:
+ this_parameter = new cVector3(new Vector3(reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle()));
break;
- case CathodeDataType.ENUM:
- this_parameter = new CathodeEnum();
- ((CathodeEnum)this_parameter).enumID = new ShortGuid(reader);
- ((CathodeEnum)this_parameter).enumIndex = reader.ReadInt32();
+ case DataType.ENUM:
+ this_parameter = new cEnum(new ShortGuid(reader), reader.ReadInt32());
break;
- case CathodeDataType.SPLINE_DATA:
- this_parameter = new CathodeSpline();
+ case DataType.SPLINE:
reader.BaseStream.Position += 4;
- int num_points = reader.ReadInt32();
- for (int x = 0; x < num_points; x++)
+ List points = new List(reader.ReadInt32());
+ for (int x = 0; x < points.Capacity; x++)
{
- CathodeTransform this_point = new CathodeTransform();
- this_point.position = new Vector3(reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle());
- this_point.rotation = new Vector3(reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle()); //TODO is this YXZ?
- ((CathodeSpline)this_parameter).splinePoints.Add(this_point);
+ points.Add(new cTransform(
+ new Vector3(reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle()),
+ new Vector3(reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle()) //TODO is this YXZ?
+ ));
}
+ this_parameter = new cSpline(points);
break;
}
parameters.Add(parameterOffsets[i], this_parameter);
}
//Read all composites from the PAK
- CathodeComposite[] composites = new CathodeComposite[composite_count];
+ Composite[] composites = new Composite[composite_count];
for (int i = 0; i < composite_count; i++)
{
reader.BaseStream.Position = compositeOffsets[i] * 4;
reader.BaseStream.Position += 4; //Skip 0x00,0x00,0x00,0x00
- CathodeComposite composite = new CathodeComposite();
+ Composite composite = new Composite();
//Read the offsets and counts
- OffsetPair[] offsetPairs = new OffsetPair[(int)CommandsDataBlock.NUMBER_OF_SCRIPT_BLOCKS];
+ OffsetPair[] offsetPairs = new OffsetPair[(int)DataBlock.NUMBER_OF_SCRIPT_BLOCKS];
int scriptStartOffset = 0;
- for (int x = 0; x < (int)CommandsDataBlock.NUMBER_OF_SCRIPT_BLOCKS; x++)
+ for (int x = 0; x < (int)DataBlock.NUMBER_OF_SCRIPT_BLOCKS; x++)
{
if (x == 0)
{
@@ -763,24 +742,24 @@ private bool Load(string path)
//Pull data from those offsets
List entityLinks = new List();
List paramRefSets = new List();
- List resourceRefs = new List();
+ List resourceRefs = new List();
Dictionary overrideChecksums = new Dictionary();
for (int x = 0; x < offsetPairs.Length; x++)
{
reader.BaseStream.Position = offsetPairs[x].GlobalOffset * 4;
for (int y = 0; y < offsetPairs[x].EntryCount; y++)
{
- switch ((CommandsDataBlock)x)
+ switch ((DataBlock)x)
{
- case CommandsDataBlock.ENTITY_CONNECTIONS:
+ case DataBlock.ENTITY_CONNECTIONS:
{
reader.BaseStream.Position = (offsetPairs[x].GlobalOffset * 4) + (y * 12);
entityLinks.Add(new CommandsEntityLinks(new ShortGuid(reader)));
int NumberOfParams = JumpToOffset(ref reader);
- entityLinks[entityLinks.Count - 1].childLinks.AddRange(Utilities.ConsumeArray(reader, NumberOfParams));
+ entityLinks[entityLinks.Count - 1].childLinks.AddRange(Utilities.ConsumeArray(reader, NumberOfParams));
break;
}
- case CommandsDataBlock.ENTITY_PARAMETERS:
+ case DataBlock.ENTITY_PARAMETERS:
{
reader.BaseStream.Position = (offsetPairs[x].GlobalOffset * 4) + (y * 12);
paramRefSets.Add(new CommandsParamRefSet(new ShortGuid(reader)));
@@ -788,7 +767,7 @@ private bool Load(string path)
paramRefSets[paramRefSets.Count - 1].refs.AddRange(Utilities.ConsumeArray(reader, NumberOfParams));
break;
}
- case CommandsDataBlock.ENTITY_OVERRIDES:
+ case DataBlock.ENTITY_OVERRIDES:
{
reader.BaseStream.Position = (offsetPairs[x].GlobalOffset * 4) + (y * 12);
OverrideEntity overrider = new OverrideEntity(new ShortGuid(reader));
@@ -797,24 +776,24 @@ private bool Load(string path)
composite.overrides.Add(overrider);
break;
}
- case CommandsDataBlock.ENTITY_OVERRIDES_CHECKSUM:
+ case DataBlock.ENTITY_OVERRIDES_CHECKSUM:
{
reader.BaseStream.Position = (offsetPairs[x].GlobalOffset * 4) + (y * 8);
overrideChecksums.Add(new ShortGuid(reader), new ShortGuid(reader));
break;
- }
+ }
//TODO: Really, I think these should be treated as parameters on the composite class as they are the pins we use for composite instances.
// Need to look into this more and see if any of these entities actually contain much data other than links into the composite itself.
- case CommandsDataBlock.COMPOSITE_EXPOSED_PARAMETERS:
+ case DataBlock.COMPOSITE_EXPOSED_PARAMETERS:
{
reader.BaseStream.Position = (offsetPairs[x].GlobalOffset * 4) + (y * 12);
- DatatypeEntity dtEntity = new DatatypeEntity(new ShortGuid(reader));
+ VariableEntity dtEntity = new VariableEntity(new ShortGuid(reader));
dtEntity.type = CommandsUtils.GetDataType(new ShortGuid(reader));
dtEntity.parameter = new ShortGuid(reader);
- composite.datatypes.Add(dtEntity);
+ composite.variables.Add(dtEntity);
break;
}
- case CommandsDataBlock.ENTITY_PROXIES:
+ case DataBlock.ENTITY_PROXIES:
{
reader.BaseStream.Position = (offsetPairs[x].GlobalOffset * 4) + (y * 20);
ProxyEntity thisProxy = new ProxyEntity(new ShortGuid(reader));
@@ -828,7 +807,7 @@ private bool Load(string path)
composite.proxies.Add(thisProxy);
break;
}
- case CommandsDataBlock.ENTITY_FUNCTIONS:
+ case DataBlock.ENTITY_FUNCTIONS:
{
reader.BaseStream.Position = (offsetPairs[x].GlobalOffset * 4) + (y * 8);
ShortGuid entityID = new ShortGuid(reader);
@@ -836,14 +815,14 @@ private bool Load(string path)
if (CommandsUtils.FunctionTypeExists(functionID))
{
//This entity executes a hard-coded CATHODE function
- CathodeFunctionType functionType = CommandsUtils.GetFunctionType(functionID);
+ FunctionType functionType = CommandsUtils.GetFunctionType(functionID);
switch (functionType)
{
- case CathodeFunctionType.CAGEAnimation:
+ case FunctionType.CAGEAnimation:
CAGEAnimation cageAnimation = new CAGEAnimation(entityID);
composite.functions.Add(cageAnimation);
break;
- case CathodeFunctionType.TriggerSequence:
+ case FunctionType.TriggerSequence:
TriggerSequence triggerSequence = new TriggerSequence(entityID);
composite.functions.Add(triggerSequence);
break;
@@ -863,50 +842,45 @@ private bool Load(string path)
}
break;
}
- //TODO: this case needs a refactor!
- case CommandsDataBlock.RESOURCE_REFERENCES:
+ case DataBlock.RESOURCE_REFERENCES:
{
reader.BaseStream.Position = (offsetPairs[x].GlobalOffset * 4) + (y * 40);
- //TODO: these values change by entry type - need to work out what they're for before allowing editing
- CathodeResourceReference resource_ref = new CathodeResourceReference();
- resource_ref.resourceRefID = new ShortGuid(reader); //renderable element ID (also used in one of the param blocks for something)
- resource_ref.unknownID1 = new ShortGuid(reader); //unk (sometimes 0x00 x4?)
- resource_ref.positionOffset = new Vector3(reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle()); //position offset
- resource_ref.unknownID2 = new ShortGuid(reader); //unk (sometimes 0x00 x4?)
- resource_ref.resourceID = new ShortGuid(reader); //resource id
- resource_ref.entryType = CommandsUtils.GetResourceEntryType(reader.ReadBytes(4)); //entry type
- switch (resource_ref.entryType)
+ ResourceReference resource = new ResourceReference();
+ resource.position = new Vector3(reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle());
+ resource.rotation = new Vector3(reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle());
+ resource.resourceID = new ShortGuid(reader);
+ resource.entryType = CommandsUtils.GetResourceEntryType(reader.ReadBytes(4));
+ switch (resource.entryType)
{
- case CathodeResourceReferenceType.RENDERABLE_INSTANCE:
- resource_ref.entryIndexREDS = reader.ReadInt32(); //REDS.BIN entry index
- resource_ref.entryCountREDS = reader.ReadInt32(); //REDS.BIN entry count
+ case ResourceType.RENDERABLE_INSTANCE:
+ resource.startIndex = reader.ReadInt32(); //REDS.BIN entry index
+ resource.count = reader.ReadInt32(); //REDS.BIN entry count
break;
- case CathodeResourceReferenceType.COLLISION_MAPPING:
- resource_ref.unknownInteger1 = reader.ReadInt32(); //unknown integer (COLLISION.MAP index?)
- resource_ref.entityID = new ShortGuid(reader); //ID which maps to the entity using the resource (?) - check GetFriendlyName
+ case ResourceType.COLLISION_MAPPING:
+ resource.startIndex = reader.ReadInt32(); //COLLISION.MAP entry index?
+ resource.entityID = new ShortGuid(reader); //ID which maps to the entity using the resource (?) - check GetFriendlyName
break;
- case CathodeResourceReferenceType.EXCLUSIVE_MASTER_STATE_RESOURCE:
- case CathodeResourceReferenceType.NAV_MESH_BARRIER_RESOURCE:
- case CathodeResourceReferenceType.TRAVERSAL_SEGMENT:
- resource_ref.unknownInteger1 = reader.ReadInt32(); //always -1?
- resource_ref.unknownInteger2 = reader.ReadInt32(); //always -1?
+ case ResourceType.ANIMATED_MODEL:
+ case ResourceType.DYNAMIC_PHYSICS_SYSTEM:
+ resource.startIndex = reader.ReadInt32(); //PHYSICS.MAP entry index?
+ reader.BaseStream.Position += 4;
break;
- case CathodeResourceReferenceType.ANIMATED_MODEL:
- case CathodeResourceReferenceType.DYNAMIC_PHYSICS_SYSTEM:
- resource_ref.unknownInteger1 = reader.ReadInt32(); //unknown integer
- resource_ref.unknownInteger2 = reader.ReadInt32(); //always zero/-1?
+ case ResourceType.EXCLUSIVE_MASTER_STATE_RESOURCE:
+ case ResourceType.NAV_MESH_BARRIER_RESOURCE:
+ case ResourceType.TRAVERSAL_SEGMENT:
+ reader.BaseStream.Position += 8;
break;
}
- resourceRefs.Add(resource_ref);
+ resourceRefs.Add(resource);
break;
}
- case CommandsDataBlock.CAGEANIMATION_DATA:
+ case DataBlock.CAGEANIMATION_DATA:
{
reader.BaseStream.Position = (offsetPairs[x].GlobalOffset * 4) + (y * 4);
reader.BaseStream.Position = (reader.ReadInt32() * 4);
- CathodeEntity thisEntity = composite.GetEntityByID(new ShortGuid(reader));
+ Entity thisEntity = composite.GetEntityByID(new ShortGuid(reader));
if (thisEntity.variant == EntityVariant.PROXY)
{
break; // We don't handle this just yet... need to resolve the proxy.
@@ -993,12 +967,12 @@ private bool Load(string path)
}
break;
}
- case CommandsDataBlock.TRIGGERSEQUENCE_DATA:
+ case DataBlock.TRIGGERSEQUENCE_DATA:
{
reader.BaseStream.Position = (offsetPairs[x].GlobalOffset * 4) + (y * 4);
reader.BaseStream.Position = (reader.ReadInt32() * 4);
- CathodeEntity thisEntity = composite.GetEntityByID(new ShortGuid(reader));
+ Entity thisEntity = composite.GetEntityByID(new ShortGuid(reader));
if (thisEntity.variant == EntityVariant.PROXY)
{
break; // We don't handle this just yet... need to resolve the proxy.
@@ -1045,79 +1019,53 @@ private bool Load(string path)
//Apply connections between entities
for (int x = 0; x < entityLinks.Count; x++)
- {
- CathodeEntity entToApply = composite.GetEntityByID(entityLinks[x].parentID);
- if (entToApply == null)
- {
- //TODO: We shouldn't hit this, but we do... is this perhaps an ID from another composite, similar to proxies?
- entToApply = new CathodeEntity(entityLinks[x].parentID);
- composite.unknowns.Add(entToApply);
- }
- entToApply.childLinks.AddRange(entityLinks[x].childLinks);
- }
+ composite.GetEntityByID(entityLinks[x].parentID)?.childLinks.AddRange(entityLinks[x].childLinks);
- //Apply parameters to entities
+ //Clone parameter data to entities
for (int x = 0; x < paramRefSets.Count; x++)
{
- CathodeEntity entToApply = composite.GetEntityByID(paramRefSets[x].id);
- if (entToApply == null)
- {
- //TODO: We shouldn't hit this, but we do... is this perhaps an ID from another composite, similar to proxies?
- entToApply = new CathodeEntity(paramRefSets[x].id);
- composite.unknowns.Add(entToApply);
- }
+ Entity entToApply = composite.GetEntityByID(paramRefSets[x].id);
+ if (entToApply == null) continue;
for (int y = 0; y < paramRefSets[x].refs.Count; y++)
- {
- entToApply.parameters.Add(new CathodeLoadedParameter(paramRefSets[x].refs[y].paramID, (CathodeParameter)parameters[paramRefSets[x].refs[y].offset].Clone()));
- }
+ entToApply.parameters.Add(new Parameter(paramRefSets[x].refs[y].paramID, (ParameterData)parameters[paramRefSets[x].refs[y].offset].Clone()));
}
- //Remap resources (TODO: This can be optimised)
- List ents = composite.GetEntities();
+ //Remap resource references
ShortGuid resParamID = ShortGuidUtils.Generate("resource");
- //Check to see if this resource applies to an ENTITY
- List resourceRefsCulled = new List();
- for (int x = 0; x < resourceRefs.Count; x++)
+ ShortGuid physEntID = ShortGuidUtils.Generate("PhysicsSystem");
+ //Check to see if this resource applies to a PARAMETER on an entity
+ for (int x = 0; x < composite.functions.Count; x++)
{
- CathodeEntity ent = ents.FirstOrDefault(o => o.shortGUID == resourceRefs[x].resourceID);
- if (ent != null)
+ for (int y = 0; y < composite.functions[x].parameters.Count; y++)
{
- ent.resources.Add(resourceRefs[x]);
- continue;
+ if (composite.functions[x].parameters[y].shortGUID != resParamID) continue;
+
+ cResource resourceParam = (cResource)composite.functions[x].parameters[y].content;
+ resourceParam.value.AddRange(resourceRefs.Where(o => o.resourceID == resourceParam.resourceID));
+ resourceRefs.RemoveAll(o => o.resourceID == resourceParam.resourceID);
}
- resourceRefsCulled.Add(resourceRefs[x]);
}
- resourceRefs = resourceRefsCulled;
- //Check to see if this resource applies to a PARAMETER
- for (int z = 0; z < ents.Count; z++)
+ //Check to see if this resource applies directly to an ENTITY
+ for (int x = 0; x < composite.functions.Count; x++)
{
- for (int y = 0; y < ents[z].parameters.Count; y++)
- {
- if (ents[z].parameters[y].shortGUID != resParamID) continue;
-
- CathodeResource resourceParam = (CathodeResource)ents[z].parameters[y].content;
- resourceRefsCulled = new List();
- for (int m = 0; m < resourceRefs.Count; m++)
- {
- if (resourceParam.resourceID == resourceRefs[m].resourceID)
- {
- resourceParam.value.Add(resourceRefs[m]);
- continue;
- }
- resourceRefsCulled.Add(resourceRefs[m]);
- }
- resourceRefs = resourceRefsCulled;
- }
+ composite.functions[x].resources.AddRange(resourceRefs.Where(o => o.resourceID == composite.functions[x].shortGUID));
+ resourceRefs.RemoveAll(o => o.resourceID == composite.functions[x].shortGUID);
+ }
+ //Any that are left over will be applied to PhysicsSystem entities
+ if (resourceRefs.Count == 1 && resourceRefs[0].entryType == ResourceType.DYNAMIC_PHYSICS_SYSTEM)
+ {
+ FunctionEntity physEnt = composite.functions.FirstOrDefault(o => o.function == physEntID);
+ if (physEnt != null) physEnt.resources.Add(resourceRefs[0]);
}
- //If it applied to none of the above, apply it to the COMPOSITE
- for (int z = 0; z < resourceRefs.Count; z++)
+ else if (resourceRefs.Count != 0)
{
- composite.resources.Add(resourceRefs[z]);
+ Console.WriteLine("WARNING: This composite contains unexpected trailing resources!");
}
+ resourceRefs.Clear();
composites[i] = composite;
}
- _composites = composites.ToList();
+ _composites = composites.ToList();
reader.Close();
OnLoaded?.Invoke();
@@ -1125,45 +1073,122 @@ private bool Load(string path)
}
#endregion
- private string _path = "";
+ #region ACCESSORS
+ /* Return a list of filenames for composites in the CommandsPAK archive */
+ public string[] GetCompositeNames()
+ {
+ string[] toReturn = new string[_composites.Count];
+ for (int i = 0; i < _composites.Count; i++) toReturn[i] = _composites[i].name;
+ return toReturn;
+ }
- private CommandsEntryPoints _entryPoints;
- private CathodeComposite[] _entryPointObjects = null;
+ /* Find the a script entry object by name */
+ public int GetFileIndex(string FileName)
+ {
+ for (int i = 0; i < _composites.Count; i++) if (_composites[i].name == FileName || _composites[i].name == FileName.Replace('/', '\\')) return i;
+ return -1;
+ }
- private List _composites = null;
+ /* Get an individual composite */
+ public Composite GetComposite(ShortGuid id)
+ {
+ if (id.val == null) return null;
+ return _composites.FirstOrDefault(o => o.shortGUID == id);
+ }
+ public Composite GetCompositeByIndex(int index)
+ {
+ return (index >= _composites.Count || index < 0) ? null : _composites[index];
+ }
- private bool _didLoadCorrectly = false;
- public bool Loaded { get { return _didLoadCorrectly; } }
- public string Filepath { get { return _path; } }
- }
+ /* Get all composites */
+ public List Composites { get { return _composites; } }
- [StructLayout(LayoutKind.Sequential, Pack = 1)]
- public struct CommandsEntryPoints
- {
- // This is always:
- // - Root Instance (the map's entry composite, usually containing entities that call mission/environment composites)
- // - Global Instance (the main data handler for keeping track of mission number, etc - kinda like a big singleton)
- // - Pause Menu Instance
+ /* Get entry point composite objects */
+ public Composite[] EntryPoints
+ {
+ get
+ {
+ if (_entryPoints == null) return null;
+ if (_entryPointObjects != null) return _entryPointObjects;
+ _entryPointObjects = new Composite[_entryPoints.Length];
+ for (int i = 0; i < _entryPoints.Length; i++) _entryPointObjects[i] = GetComposite(_entryPoints[i]);
+ return _entryPointObjects;
+ }
+ }
- [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
- public ShortGuid[] compositeIDs;
- }
+ /* Set the root composite for this COMMANDS.PAK (the root of the level - GLOBAL and PAUSEMENU are also instanced) */
+ public void SetRootComposite(ShortGuid id)
+ {
+ _entryPoints[0] = id;
+ _entryPointObjects = null;
+ }
+ #endregion
- [StructLayout(LayoutKind.Sequential, Pack = 1)]
- public struct CommandsOffsetPair
- {
- public int offset;
- public int count;
- }
+ #region HELPERS
+ /* Read offset info & count, jump to the offset & return the count */
+ private int JumpToOffset(ref BinaryReader reader)
+ {
+ int offset = reader.ReadInt32() * 4;
+ int count = reader.ReadInt32();
- [Serializable]
- [StructLayout(LayoutKind.Sequential, Pack = 1)]
- public struct CathodeEntityLink
- {
- public ShortGuid connectionID; //The unique ID for this connection
- public ShortGuid parentParamID; //The ID of the parameter we're providing out
- public ShortGuid childParamID; //The ID of the parameter we're providing into the child
- public ShortGuid childID; //The ID of the entity we're linking to to provide the value for
+ reader.BaseStream.Position = offset;
+ return count;
+ }
+
+ /* Filter down a list of parameters to contain only unique entries */
+ private List PruneParameterList(List parameters)
+ {
+ List prunedList = new List();
+ bool canAdd = true;
+ for (int i = 0; i < parameters.Count; i++)
+ {
+ canAdd = true;
+ for (int x = 0; x < prunedList.Count; x++)
+ {
+ if (prunedList[x] == parameters[i]) //This is where the bulk of our logic lies
+ {
+ canAdd = false;
+ break;
+ }
+ }
+ if (canAdd) prunedList.Add(parameters[i]);
+ }
+ return prunedList;
+ }
+ #endregion
+
+ /* -- */
+
+ [StructLayout(LayoutKind.Sequential, Pack = 1)]
+ public struct CathodeParameterReference
+ {
+ public ShortGuid paramID; //The ID of the param in the entity
+ public int offset; //The offset of the param this reference points to (in memory this is *4)
+ }
+
+ public class CommandsEntityLinks
+ {
+ public ShortGuid parentID;
+ public List childLinks = new List();
+
+ public CommandsEntityLinks(ShortGuid _id)
+ {
+ parentID = _id;
+ }
+ }
+
+ public class CommandsParamRefSet
+ {
+ public int Index = -1; //TEMP TEST
+
+ public ShortGuid id;
+ public List refs = new List();
+
+ public CommandsParamRefSet(ShortGuid _id)
+ {
+ id = _id;
+ }
+ }
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
@@ -1184,47 +1209,16 @@ public OffsetPair(long _go, int _ec)
}
}
- [StructLayout(LayoutKind.Sequential, Pack = 1)]
- public struct CathodeParameterReference
- {
- public ShortGuid paramID; //The ID of the param in the entity
- public int offset; //The offset of the param this reference points to (in memory this is *4)
- }
-
- public class CommandsEntityLinks
- {
- public ShortGuid parentID;
- public List childLinks = new List();
-
- public CommandsEntityLinks(ShortGuid _id)
- {
- parentID = _id;
- }
- }
-
- public class CommandsParamRefSet
- {
- public int Index = -1; //TEMP TEST
-
- public ShortGuid id;
- public List refs = new List();
-
- public CommandsParamRefSet(ShortGuid _id)
- {
- id = _id;
- }
- }
-
/* TEMP STUFF TO FIX REWRITING */
[Serializable]
public class CathodeParameterKeyframeHeader
{
public ShortGuid ID;
- public CathodeDataType unk2;
+ public DataType unk2;
public ShortGuid keyframeDataID;
//public float unk3;
public ShortGuid parameterID;
- public CathodeDataType parameterDataType;
+ public DataType parameterDataType;
public ShortGuid parameterSubID; //if parameterID is position, this might be x for example
public List connectedEntity; //path to controlled entity
}
diff --git a/CathodeLib/Scripts/CATHODE/CommandsPAK/Components/Composite.cs b/CathodeLib/Scripts/CATHODE/CommandsPAK/Components/Composite.cs
new file mode 100644
index 0000000..935cc0b
--- /dev/null
+++ b/CathodeLib/Scripts/CATHODE/CommandsPAK/Components/Composite.cs
@@ -0,0 +1,78 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+#if UNITY_EDITOR || UNITY_STANDALONE
+using UnityEngine;
+#else
+#endif
+
+namespace CATHODE.Scripting
+{
+ /* A script composite containing entities */
+ [Serializable]
+ public class Composite
+ {
+ public ShortGuid shortGUID; //The id when this composite is used as an entity in another composite
+ public string name = ""; //The string name of the composite
+
+ public OffsetPair unknownPair;
+
+ public List variables = new List(); //Variables which can be accessed outside of this flowgraph as parameters, and connected to nodes as parameters internally
+ public List functions = new List(); //Functional nodes, including hard-coded functions and references to other composites
+
+ public List overrides = new List(); //Overrides of parameters in child composites
+ public List proxies = new List(); //Instances of entities from other composites
+
+ /* If an entity exists in the composite, return it */
+ public Entity GetEntityByID(ShortGuid id)
+ {
+ foreach (Entity entity in variables) if (entity.shortGUID == id) return entity;
+ foreach (Entity entity in functions) if (entity.shortGUID == id) return entity;
+ foreach (Entity entity in overrides) if (entity.shortGUID == id) return entity;
+ foreach (Entity entity in proxies) if (entity.shortGUID == id) return entity;
+ return null;
+ }
+
+ /* Returns a collection of all entities in the composite */
+ public List GetEntities()
+ {
+ List toReturn = new List();
+ toReturn.AddRange(variables);
+ toReturn.AddRange(functions);
+ toReturn.AddRange(overrides);
+ toReturn.AddRange(proxies);
+ return toReturn;
+ }
+
+ /* Sort all entity arrays */
+ public void SortEntities()
+ {
+ variables.OrderBy(o => o.shortGUID.ToUInt32());
+ functions.OrderBy(o => o.shortGUID.ToUInt32());
+ overrides.OrderBy(o => o.shortGUID.ToUInt32());
+ proxies.OrderBy(o => o.shortGUID.ToUInt32());
+ }
+
+ /* Add a new function entity */
+ public FunctionEntity AddFunction(string function, bool autopopulateParameters = false)
+ {
+ FunctionEntity func = new FunctionEntity(function, autopopulateParameters);
+ functions.Add(func);
+ return func;
+ }
+ public FunctionEntity AddFunction(Composite function, bool autopopulateParameters = false)
+ {
+ FunctionEntity func = new FunctionEntity(function.shortGUID, autopopulateParameters);
+ functions.Add(func);
+ return func;
+ }
+
+ /* Add a new variable entity */
+ public VariableEntity AddVariable(string parameter, DataType type, bool addDefaultParam = false)
+ {
+ VariableEntity vari = new VariableEntity(parameter, type, addDefaultParam);
+ variables.Add(vari);
+ return vari;
+ }
+ }
+}
diff --git a/CathodeLib/Scripts/CATHODE/CommandsPAK/Components/Entity.cs b/CathodeLib/Scripts/CATHODE/CommandsPAK/Components/Entity.cs
new file mode 100644
index 0000000..aec1d05
--- /dev/null
+++ b/CathodeLib/Scripts/CATHODE/CommandsPAK/Components/Entity.cs
@@ -0,0 +1,272 @@
+using CATHODE.Assets.Utilities;
+using CATHODE.Scripting;
+using CathodeLib;
+using CathodeLib.Properties;
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Diagnostics.SymbolStore;
+using System.Linq;
+using System.Runtime.InteropServices;
+
+namespace CATHODE.Scripting
+{
+ /* An entity in a composite */
+ [Serializable]
+ public class Entity : IComparable
+ {
+ public Entity(EntityVariant variant)
+ {
+ this.shortGUID = ShortGuidUtils.GenerateRandom();
+ this.variant = variant;
+ }
+ public Entity(ShortGuid shortGUID, EntityVariant variant)
+ {
+ this.shortGUID = shortGUID;
+ this.variant = variant;
+ }
+
+ public ShortGuid shortGUID; //Translates to string via EntityNameLookup.GetEntityName
+ public EntityVariant variant;
+
+ public List childLinks = new List();
+ public List parameters = new List();
+
+ /* Implements IComparable for searching */
+ public int CompareTo(Entity other)
+ {
+ int TotalThis = shortGUID.val[0] + shortGUID.val[1] + shortGUID.val[2] + shortGUID.val[3];
+ int TotalOther = other.shortGUID.val[0] + other.shortGUID.val[1] + other.shortGUID.val[2] + other.shortGUID.val[3];
+ if (TotalThis > TotalOther) return 1;
+ else if (TotalThis == TotalOther) return 0;
+ return -1;
+ }
+
+ /* Get parameter by string name or ShortGuid */
+ public Parameter GetParameter(string name)
+ {
+ ShortGuid id = ShortGuidUtils.Generate(name);
+ return GetParameter(id);
+ }
+ public Parameter GetParameter(ShortGuid id)
+ {
+ return parameters.FirstOrDefault(o => o.shortGUID == id);
+ }
+
+ /* Add a data-supplying parameter to the entity */
+ public Parameter AddParameter(string name, ParameterData data, ParameterVariant variant = ParameterVariant.PARAMETER)
+ {
+ //TODO: we are limiting data-supplying params to ONE per entity here - is this correct? I think links are the only place where you can have multiple of the same.
+ ShortGuid id = ShortGuidUtils.Generate(name);
+ Parameter param = GetParameter(id);
+ if (param == null)
+ {
+ param = new Parameter(id, data, variant);
+ parameters.Add(param);
+ }
+ else
+ {
+ Console.WriteLine("WARNING: Updating data and variant type in parameter " + id);
+ param.content = data;
+ param.variant = variant;
+ }
+ return param;
+ }
+
+ /* Remove a parameter from the entity */
+ public void RemoveParameter(string name)
+ {
+ ShortGuid name_id = ShortGuidUtils.Generate(name);
+ parameters.RemoveAll(o => o.shortGUID == name_id);
+ }
+
+ /* Add a link from a parameter on us out to a parameter on another entity */
+ public void AddParameterLink(string parameter, Entity childEntity, string childParameter)
+ {
+ childLinks.Add(new EntityLink(childEntity.shortGUID, ShortGuidUtils.Generate(parameter), ShortGuidUtils.Generate(childParameter)));
+ }
+
+ /* Remove a link to another entity */
+ public void RemoveParameterLink(string parameter, Entity childEntity, string childParameter)
+ {
+ ShortGuid parameter_id = ShortGuidUtils.Generate(parameter);
+ ShortGuid childParameter_id = ShortGuidUtils.Generate(childParameter);
+ //TODO: do we want to do RemoveAll? should probably just remove the first
+ childLinks.RemoveAll(o => o.parentParamID == parameter_id && o.childID == childEntity.shortGUID && o.childParamID == childParameter_id);
+ }
+ }
+ [Serializable]
+ public class VariableEntity : Entity
+ {
+ public VariableEntity(bool addDefaultParam = false) : base(EntityVariant.DATATYPE) { if (addDefaultParam) AddDefaultParam(); }
+ public VariableEntity(ShortGuid shortGUID, bool addDefaultParam = false) : base(shortGUID, EntityVariant.DATATYPE) { if (addDefaultParam) AddDefaultParam(); }
+
+ public VariableEntity(string parameter, DataType type, bool addDefaultParam = false) : base(EntityVariant.DATATYPE)
+ {
+ this.parameter = ShortGuidUtils.Generate(parameter);
+ this.type = type;
+ if (addDefaultParam) AddDefaultParam();
+ }
+
+ public VariableEntity(ShortGuid shortGUID, ShortGuid parameter, DataType type, bool addDefaultParam = false) : base(shortGUID, EntityVariant.DATATYPE)
+ {
+ this.parameter = parameter;
+ this.type = type;
+ if (addDefaultParam) AddDefaultParam();
+ }
+ public VariableEntity(ShortGuid shortGUID, string parameter, DataType type, bool addDefaultParam = false) : base(shortGUID, EntityVariant.DATATYPE)
+ {
+ this.parameter = ShortGuidUtils.Generate(parameter);
+ this.type = type;
+ if (addDefaultParam) AddDefaultParam();
+ }
+
+ /* Add a default parameter on us when created, to provide a value from */
+ private void AddDefaultParam()
+ {
+ ParameterData thisParam = null;
+ switch (type)
+ {
+ case DataType.STRING:
+ thisParam = new cString("");
+ break;
+ case DataType.FLOAT:
+ thisParam = new cFloat(0.0f);
+ break;
+ case DataType.INTEGER:
+ thisParam = new cInteger(0);
+ break;
+ case DataType.BOOL:
+ thisParam = new cBool(true);
+ break;
+ case DataType.VECTOR:
+ thisParam = new cVector3(new Vector3(0, 0, 0));
+ break;
+ case DataType.TRANSFORM:
+ thisParam = new cTransform(new Vector3(0, 0, 0), new Vector3(0, 0, 0));
+ break;
+ case DataType.ENUM:
+ thisParam = new cEnum("ALERTNESS_STATE", 0); //ALERTNESS_STATE is the first alphabetically
+ break;
+ case DataType.SPLINE:
+ thisParam = new cSpline();
+ break;
+ }
+ parameters.Add(new Parameter(parameter, thisParam));
+ }
+
+ public ShortGuid parameter; //Translates to string via ShortGuidUtils.FindString
+ public DataType type = DataType.NONE;
+ }
+ [Serializable]
+ public class FunctionEntity : Entity
+ {
+ public FunctionEntity() : base(EntityVariant.FUNCTION) { }
+ public FunctionEntity(ShortGuid shortGUID) : base(shortGUID, EntityVariant.FUNCTION) { }
+
+ public FunctionEntity(string function, bool autoGenerateParameters = false) : base(EntityVariant.FUNCTION)
+ {
+ this.function = ShortGuidUtils.Generate(function);
+ if (autoGenerateParameters) EntityUtils.ApplyDefaults(this);
+ }
+ public FunctionEntity(ShortGuid function, bool autoGenerateParameters = false) : base(EntityVariant.FUNCTION)
+ {
+ this.function = function;
+ if (autoGenerateParameters) EntityUtils.ApplyDefaults(this);
+ }
+
+ public FunctionEntity(ShortGuid shortGUID, ShortGuid function, bool autoGenerateParameters = false) : base(shortGUID, EntityVariant.FUNCTION)
+ {
+ this.function = function;
+ if (autoGenerateParameters) EntityUtils.ApplyDefaults(this);
+ }
+ public FunctionEntity(ShortGuid shortGUID, string function, bool autoGenerateParameters = false) : base(shortGUID, EntityVariant.FUNCTION)
+ {
+ this.function = ShortGuidUtils.Generate(function);
+ if (autoGenerateParameters) EntityUtils.ApplyDefaults(this);
+ }
+
+ public ShortGuid function; //Translates to string via ShortGuidUtils.FindString
+ public List resources = new List(); //TODO: can we replace this with a cResource to save duplicating functionality?
+
+ /* Add a new resource reference of type */
+ public ResourceReference AddResource(ResourceType type)
+ {
+ //We can only have one type of resource reference per function entity, so if it already exists, we just return the existing one.
+ ResourceReference rr = GetResource(type);
+ if (rr == null)
+ {
+ rr = new ResourceReference(type);
+ rr.resourceID = shortGUID;
+ switch (rr.entryType)
+ {
+ case ResourceType.DYNAMIC_PHYSICS_SYSTEM:
+ case ResourceType.RENDERABLE_INSTANCE:
+ case ResourceType.ANIMATED_MODEL:
+ rr.startIndex = 0;
+ break;
+ }
+ resources.Add(rr);
+ }
+ return rr;
+ }
+
+ /* Find a resource reference of type */
+ public ResourceReference GetResource(ResourceType type)
+ {
+ return resources.FirstOrDefault(o => o.entryType == type);
+ }
+ }
+ [Serializable]
+ public class ProxyEntity : Entity
+ {
+ public ProxyEntity(ShortGuid shortGUID) : base(shortGUID, EntityVariant.PROXY) { }
+
+ public ShortGuid extraId; //TODO: I'm unsure if this is actually used by the game - we might not need to store it and just make up something when we write.
+ public List hierarchy = new List();
+ }
+ [Serializable]
+ public class OverrideEntity : Entity
+ {
+ public OverrideEntity(ShortGuid shortGUID) : base(shortGUID, EntityVariant.OVERRIDE) { }
+
+ 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 List hierarchy = new List();
+ }
+
+ #region SPECIAL FUNCTION ENTITIES
+ [Serializable]
+ public class CAGEAnimation : FunctionEntity
+ {
+ public CAGEAnimation(ShortGuid id) : base(id) { function = ShortGuidUtils.Generate("CAGEAnimation"); }
+ public List keyframeHeaders = new List();
+ public List keyframeData = new List();
+ public List paramsData3 = new List(); //events?
+ }
+ [Serializable]
+ public class TriggerSequence : FunctionEntity
+ {
+ public TriggerSequence(ShortGuid id) : base(id) { function = ShortGuidUtils.Generate("TriggerSequence"); }
+ public List triggers = new List();
+ public List events = new List();
+ }
+ #endregion
+
+ [Serializable]
+ [StructLayout(LayoutKind.Sequential, Pack = 1)]
+ public struct EntityLink
+ {
+ public EntityLink(ShortGuid childEntityID, ShortGuid parentParam, ShortGuid childParam)
+ {
+ connectionID = ShortGuidUtils.GenerateRandom();
+ parentParamID = parentParam;
+ childParamID = childParam;
+ childID = childEntityID;
+ }
+
+ public ShortGuid connectionID; //The unique ID for this connection
+ public ShortGuid parentParamID; //The ID of the parameter we're providing out
+ public ShortGuid childParamID; //The ID of the parameter we're providing into the child
+ public ShortGuid childID; //The ID of the entity we're linking to to provide the value for
+ }
+}
diff --git a/CathodeLib/Scripts/CATHODE/CommandsPAK/Components/Parameter.cs b/CathodeLib/Scripts/CATHODE/CommandsPAK/Components/Parameter.cs
new file mode 100644
index 0000000..063af40
--- /dev/null
+++ b/CathodeLib/Scripts/CATHODE/CommandsPAK/Components/Parameter.cs
@@ -0,0 +1,28 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace CATHODE.Scripting
+{
+ /* A parameter which consists of a name and data, with a variant for how it is used */
+ [Serializable]
+ public class Parameter
+ {
+ public Parameter(string name, ParameterData data, ParameterVariant var = ParameterVariant.PARAMETER)
+ {
+ shortGUID = ShortGuidUtils.Generate(name);
+ content = data;
+ variant = var;
+ }
+ public Parameter(ShortGuid id, ParameterData data, ParameterVariant var = ParameterVariant.PARAMETER)
+ {
+ shortGUID = id;
+ content = data;
+ variant = var;
+ }
+
+ public ShortGuid shortGUID; //The ID of the param in the entity
+ public ParameterData content = null;
+ public ParameterVariant variant = ParameterVariant.PARAMETER;
+ }
+}
diff --git a/CathodeLib/Scripts/CATHODE/CommandsPAK/Components/ParameterData.cs b/CathodeLib/Scripts/CATHODE/CommandsPAK/Components/ParameterData.cs
new file mode 100644
index 0000000..b6c59fb
--- /dev/null
+++ b/CathodeLib/Scripts/CATHODE/CommandsPAK/Components/ParameterData.cs
@@ -0,0 +1,291 @@
+using CathodeLib;
+using CathodeLib.Properties;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+
+namespace CATHODE.Scripting
+{
+ /* Data which can be used within a parameter */
+ [Serializable]
+ public class ParameterData : ICloneable
+ {
+ public ParameterData() { }
+ public ParameterData(DataType type)
+ {
+ dataType = type;
+ }
+ public DataType dataType = DataType.NONE;
+
+ public static bool operator ==(ParameterData x, ParameterData y)
+ {
+ if (ReferenceEquals(x, null)) return ReferenceEquals(y, null);
+ if (ReferenceEquals(y, null)) return ReferenceEquals(x, null);
+ if (x.dataType != y.dataType) return false;
+ switch (x.dataType)
+ {
+ case DataType.TRANSFORM:
+ cTransform x_t = (cTransform)x;
+ cTransform y_t = (cTransform)y;
+ return x_t.position == y_t.position && x_t.rotation == y_t.rotation;
+ case DataType.INTEGER:
+ return ((cInteger)x).value == ((cInteger)y).value;
+ case DataType.STRING:
+ return ((cString)x).value == ((cString)y).value;
+ case DataType.BOOL:
+ return ((cBool)x).value == ((cBool)y).value;
+ case DataType.FLOAT:
+ return ((cFloat)x).value == ((cFloat)y).value;
+ case DataType.RESOURCE:
+ return ((cResource)x).resourceID == ((cResource)y).resourceID;
+ case DataType.VECTOR:
+ return ((cVector3)x).value == ((cVector3)y).value;
+ case DataType.ENUM:
+ cEnum x_e = (cEnum)x;
+ cEnum y_e = (cEnum)y;
+ return x_e.enumIndex == y_e.enumIndex && x_e.enumID == y_e.enumID;
+ case DataType.SPLINE:
+ return ((cSpline)x).splinePoints == ((cSpline)y).splinePoints;
+ case DataType.NONE:
+ return true;
+ default:
+ return false;
+ }
+ }
+ public static bool operator !=(ParameterData x, ParameterData y)
+ {
+ return !(x == y);
+ }
+
+ public override bool Equals(object obj)
+ {
+ if (!(obj is ParameterData)) return false;
+ return ((ParameterData)obj) == this;
+ }
+
+ public override int GetHashCode()
+ {
+ //this is gross
+ switch (dataType)
+ {
+ case DataType.TRANSFORM:
+ cTransform x_t = (cTransform)this;
+ return Convert.ToInt32(
+ x_t.rotation.x.ToString() + x_t.rotation.y.ToString() + x_t.rotation.z.ToString() +
+ x_t.position.x.ToString() + x_t.position.y.ToString() + x_t.position.z.ToString());
+ case DataType.INTEGER:
+ return ((cInteger)this).value;
+ case DataType.STRING:
+ cString x_s = (cString)this;
+ string num = "";
+ for (int i = 0; i < x_s.value.Length; i++) num += ((int)x_s.value[i]).ToString();
+ return Convert.ToInt32(num);
+ case DataType.BOOL:
+ return ((cBool)this).value ? 1 : 0;
+ case DataType.FLOAT:
+ return Convert.ToInt32(((cFloat)this).value.ToString().Replace(".", ""));
+ case DataType.RESOURCE:
+ string x_g_s = ((cString)this).value.ToString();
+ string num2 = "";
+ for (int i = 0; i < x_g_s.Length; i++) num2 += ((int)x_g_s[i]).ToString();
+ return Convert.ToInt32(num2);
+ case DataType.VECTOR:
+ cVector3 x_v = (cVector3)this;
+ return Convert.ToInt32(x_v.value.x.ToString() + x_v.value.y.ToString() + x_v.value.z.ToString());
+ case DataType.ENUM:
+ cEnum x_e = (cEnum)this;
+ string x_e_s = x_e.enumID.ToString();
+ string num3 = "";
+ for (int i = 0; i < x_e_s.Length; i++) num3 += ((int)x_e_s[i]).ToString();
+ return Convert.ToInt32(num3 + x_e.enumIndex.ToString());
+ case DataType.SPLINE:
+ cSpline x_sd = (cSpline)this;
+ string x_sd_s = "";
+ for (int i = 0; i < x_sd.splinePoints.Count; i++) x_sd_s += x_sd.splinePoints[i].position.GetHashCode().ToString();
+ ShortGuid x_sd_g = ShortGuidUtils.Generate(x_sd_s);
+ string x_sd_g_s = x_sd_g.ToString();
+ string num4 = "";
+ for (int i = 0; i < x_sd_g_s.Length; i++) num4 += ((int)x_sd_g_s[i]).ToString();
+ return Convert.ToInt32(num4);
+ default:
+ return -1;
+ }
+ }
+
+ public object Clone()
+ {
+ switch (dataType)
+ {
+ case DataType.SPLINE:
+ case DataType.RESOURCE:
+ return Utilities.CloneObject(this);
+ //HOTFIX FOR VECTOR 3 CLONE ISSUE - TODO: FIND WHY THIS ISN'T WORKING WITH MEMBERWISE CLONE
+ case DataType.VECTOR:
+ cVector3 v3 = (cVector3)this.MemberwiseClone();
+ v3.value = (Vector3)((cVector3)this).value.Clone();
+ return v3;
+ case DataType.TRANSFORM:
+ cTransform tr = (cTransform)this.MemberwiseClone();
+ tr.position = (Vector3)((cTransform)this).position.Clone();
+ tr.rotation = (Vector3)((cTransform)this).rotation.Clone();
+ return tr;
+ //END OF HOTFIX - SHOULD THIS ALSO APPLY TO OTHERS??
+ default:
+ return this.MemberwiseClone();
+ }
+ }
+ }
+ [Serializable]
+ public class cTransform : ParameterData
+ {
+ public cTransform() { dataType = DataType.TRANSFORM; }
+ public cTransform(Vector3 position, Vector3 rotation)
+ {
+ this.position = position;
+ this.rotation = rotation;
+ dataType = DataType.TRANSFORM;
+ }
+
+ public Vector3 position = new Vector3();
+ public Vector3 rotation = new Vector3(); //In CATHODE this is named Roll/Pitch/Yaw
+ }
+ [Serializable]
+ public class cInteger : ParameterData
+ {
+ public cInteger() { dataType = DataType.INTEGER; }
+ public cInteger(int value)
+ {
+ this.value = value;
+ dataType = DataType.INTEGER;
+ }
+
+ public int value = 0;
+ }
+ [Serializable]
+ public class cString : ParameterData
+ {
+ public cString() { dataType = DataType.STRING; }
+ public cString(string value)
+ {
+ this.value = value;
+ dataType = DataType.STRING;
+ }
+
+ public string value = "";
+ }
+ [Serializable]
+ public class cBool : ParameterData
+ {
+ public cBool() { dataType = DataType.BOOL; }
+ public cBool(bool value)
+ {
+ this.value = value;
+ dataType = DataType.BOOL;
+ }
+
+ public bool value = false;
+ }
+ [Serializable]
+ public class cFloat : ParameterData
+ {
+ public cFloat() { dataType = DataType.FLOAT; }
+ public cFloat(float value)
+ {
+ this.value = value;
+ dataType = DataType.FLOAT;
+ }
+
+ public float value = 0.0f;
+ }
+ [Serializable]
+ public class cResource : ParameterData
+ {
+ public cResource() { dataType = DataType.RESOURCE; }
+ public cResource(ShortGuid resourceID)
+ {
+ this.resourceID = resourceID;
+ dataType = DataType.RESOURCE;
+ }
+ public cResource(List value, ShortGuid resourceID)
+ {
+ this.value = value;
+ this.resourceID = resourceID;
+ dataType = DataType.RESOURCE;
+ }
+
+ public List value = new List();
+ public ShortGuid resourceID; //TODO: this is only ever gonna be the parent of the resouce (node or entity) - should we just generate on compilation?
+
+ /* Add a new resource reference of type */
+ public ResourceReference AddResource(ResourceType type)
+ {
+ //We can only have one type of resource reference, so if it already exists, we just return the existing one.
+ ResourceReference rr = GetResource(type);
+ if (rr == null)
+ {
+ rr = new ResourceReference(type);
+ rr.resourceID = resourceID;
+ switch (rr.entryType)
+ {
+ case ResourceType.DYNAMIC_PHYSICS_SYSTEM:
+ case ResourceType.RENDERABLE_INSTANCE:
+ case ResourceType.ANIMATED_MODEL:
+ rr.startIndex = 0;
+ break;
+ }
+ value.Add(rr);
+ }
+ return rr;
+ }
+
+ /* Find a resource reference of type */
+ public ResourceReference GetResource(ResourceType type)
+ {
+ return value.FirstOrDefault(o => o.entryType == type);
+ }
+ }
+ [Serializable]
+ public class cVector3 : ParameterData
+ {
+ public cVector3() { dataType = DataType.VECTOR; }
+ public cVector3(Vector3 value)
+ {
+ this.value = value;
+ dataType = DataType.VECTOR;
+ }
+
+ public Vector3 value = new Vector3();
+ }
+ [Serializable]
+ public class cEnum : ParameterData
+ {
+ public cEnum(ShortGuid enumID, int enumIndex)
+ {
+ this.enumID = enumID;
+ this.enumIndex = enumIndex;
+ dataType = DataType.ENUM;
+ }
+ public cEnum(string enumName = "ALERTNESS_STATE", int enumIndex = 0)
+ {
+ this.enumID = ShortGuidUtils.Generate(enumName);
+ this.enumIndex = enumIndex;
+ dataType = DataType.ENUM;
+ }
+
+ public ShortGuid enumID;
+ public int enumIndex = 0;
+ }
+ [Serializable]
+ public class cSpline : ParameterData
+ {
+ public cSpline() { dataType = DataType.SPLINE; }
+ public cSpline(List splinePoints)
+ {
+ this.splinePoints = splinePoints;
+ dataType = DataType.SPLINE;
+ }
+
+ public List splinePoints = new List();
+ }
+}
diff --git a/CathodeLib/Scripts/CATHODE/CommandsPAK/Components/ResourceReference.cs b/CathodeLib/Scripts/CATHODE/CommandsPAK/Components/ResourceReference.cs
new file mode 100644
index 0000000..769711d
--- /dev/null
+++ b/CathodeLib/Scripts/CATHODE/CommandsPAK/Components/ResourceReference.cs
@@ -0,0 +1,89 @@
+using CathodeLib;
+using System;
+using System.Collections.Generic;
+
+namespace CATHODE.Scripting
+{
+ /* A reference to a game resource (E.G. a renderable element, a collision mapping, etc) */
+ [Serializable]
+ public class ResourceReference : ICloneable
+ {
+ public ResourceReference()
+ {
+
+ }
+ public ResourceReference(ResourceType type)
+ {
+ switch (type)
+ {
+ case ResourceType.DYNAMIC_PHYSICS_SYSTEM:
+ case ResourceType.RENDERABLE_INSTANCE:
+ case ResourceType.ANIMATED_MODEL:
+ startIndex = 0;
+ break;
+ }
+ entryType = type;
+ }
+
+ public static bool operator ==(ResourceReference x, ResourceReference y)
+ {
+ if (ReferenceEquals(x, null)) return ReferenceEquals(y, null);
+ if (ReferenceEquals(y, null)) return ReferenceEquals(x, null);
+
+ if (x.position != y.position) return false;
+ 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.count != y.count) return false;
+ if (x.entityID != y.entityID) return false;
+
+ return true;
+ }
+ public static bool operator !=(ResourceReference x, ResourceReference y)
+ {
+ return !(x == y);
+ }
+
+ public object Clone()
+ {
+ return this.MemberwiseClone();
+ }
+
+ public override bool Equals(object obj)
+ {
+ return obj is ResourceReference reference &&
+ EqualityComparer.Default.Equals(position, reference.position) &&
+ EqualityComparer.Default.Equals(rotation, reference.rotation) &&
+ EqualityComparer.Default.Equals(resourceID, reference.resourceID) &&
+ entryType == reference.entryType &&
+ startIndex == reference.startIndex &&
+ count == reference.count &&
+ EqualityComparer.Default.Equals(entityID, reference.entityID);
+ }
+
+ public override int GetHashCode()
+ {
+ int hashCode = -1286985782;
+ hashCode = hashCode * -1521134295 + position.GetHashCode();
+ hashCode = hashCode * -1521134295 + rotation.GetHashCode();
+ hashCode = hashCode * -1521134295 + resourceID.GetHashCode();
+ hashCode = hashCode * -1521134295 + entryType.GetHashCode();
+ hashCode = hashCode * -1521134295 + startIndex.GetHashCode();
+ hashCode = hashCode * -1521134295 + count.GetHashCode();
+ hashCode = hashCode * -1521134295 + entityID.GetHashCode();
+ return hashCode;
+ }
+
+ public Vector3 position = new Vector3(0, 0, 0);
+ public Vector3 rotation = new Vector3(0, 0, 0);
+
+ 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 count = 1;
+
+ public ShortGuid entityID = new ShortGuid("FF-FF-FF-FF");
+ }
+}
diff --git a/CathodeLib/Scripts/CATHODE/CommandsPAK/Components/ShortGuid.cs b/CathodeLib/Scripts/CATHODE/CommandsPAK/Components/ShortGuid.cs
new file mode 100644
index 0000000..c3e3674
--- /dev/null
+++ b/CathodeLib/Scripts/CATHODE/CommandsPAK/Components/ShortGuid.cs
@@ -0,0 +1,98 @@
+using System;
+using System.IO;
+using System.Linq;
+using System.Runtime.InteropServices;
+
+namespace CATHODE.Scripting
+{
+ /* A unique id assigned to CATHODE objects */
+ [Serializable]
+ [StructLayout(LayoutKind.Sequential, Pack = 1)]
+ public struct ShortGuid : IComparable
+ {
+ public ShortGuid(float num)
+ {
+ val = BitConverter.GetBytes(num);
+ }
+ public ShortGuid(int num)
+ {
+ val = BitConverter.GetBytes(num);
+ }
+ public ShortGuid(byte[] id)
+ {
+ val = id;
+ }
+ public ShortGuid(BinaryReader reader)
+ {
+ val = reader.ReadBytes(4);
+ }
+ public ShortGuid(string id)
+ {
+ System.String[] arr = id.Split('-');
+ if (arr.Length != 4) throw new Exception("Tried to initialise ShortGuid without 4-byte ID string.");
+ byte[] array = new byte[arr.Length];
+ for (int i = 0; i < arr.Length; i++) array[i] = Convert.ToByte(arr[i], 16);
+ val = array;
+ }
+
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
+ public byte[] val;
+
+ public override bool Equals(object obj)
+ {
+ if (!(obj is ShortGuid)) return false;
+ if (((ShortGuid)obj).val == null) return this.val == null;
+ if (this.val == null) return ((ShortGuid)obj).val == null;
+ return ((ShortGuid)obj).val.SequenceEqual(this.val);
+ }
+ public static bool operator ==(ShortGuid x, ShortGuid y)
+ {
+ if (ReferenceEquals(x, null)) return ReferenceEquals(y, null);
+ if (x.val == null) return y.val == null;
+ if (y.val == null) return x.val == null;
+ return x.val.SequenceEqual(y.val);
+ }
+ public static bool operator !=(ShortGuid x, ShortGuid y)
+ {
+ return !x.val.SequenceEqual(y.val);
+ }
+ public static bool operator ==(ShortGuid x, string y)
+ {
+ return x.ToString() == y;
+ }
+ public static bool operator !=(ShortGuid x, string y)
+ {
+ return x.ToString() != y;
+ }
+ public override int GetHashCode()
+ {
+ return BitConverter.ToInt32(val, 0);
+ }
+
+ public int CompareTo(ShortGuid x)
+ {
+ if (x == null) return 0;
+ if (x.val == null && val != null) return 0;
+ if (x.val != null && val == null) return 0;
+ if (x.val.Length != val.Length) return 0;
+
+ int comp = 0;
+ for (int i = 0; i < x.val.Length; i++)
+ {
+ comp += x.val[i].CompareTo(val[i]);
+ }
+ comp /= x.val.Length;
+
+ return comp;
+ }
+
+ public override string ToString()
+ {
+ return BitConverter.ToString(val);
+ }
+ public uint ToUInt32()
+ {
+ return BitConverter.ToUInt32(val, 0);
+ }
+ }
+}
diff --git a/CathodeLib/Scripts/CommandsPAK/CathodeParameter.cs b/CathodeLib/Scripts/CATHODE/CommandsPAK/Components/TypeEnums.cs
similarity index 69%
rename from CathodeLib/Scripts/CommandsPAK/CathodeParameter.cs
rename to CathodeLib/Scripts/CATHODE/CommandsPAK/Components/TypeEnums.cs
index 4b05b3d..b586d5a 100644
--- a/CathodeLib/Scripts/CommandsPAK/CathodeParameter.cs
+++ b/CathodeLib/Scripts/CATHODE/CommandsPAK/Components/TypeEnums.cs
@@ -1,32 +1,53 @@
using System;
using System.Collections.Generic;
-using System.IO;
-using System.Linq;
-#if UNITY_EDITOR || UNITY_STANDALONE
-using UnityEngine;
-#else
-using System.Numerics;
-using System.Runtime.Serialization.Formatters.Binary;
-#endif
+using System.Text;
-namespace CATHODE.Commands
+namespace CATHODE.Scripting
{
- /* Data types in the CATHODE scripting system */
- public enum CathodeDataType
+ /* Entity variants */
+ public enum EntityVariant
+ {
+ DATATYPE,
+ FUNCTION,
+
+ PROXY,
+ OVERRIDE,
+ }
+
+ /* Parameter variants */
+ public enum ParameterVariant
+ {
+ //Only on EntityMethodInterface
+ METHOD,
+ FINISHED,
+ RELAY,
+
+ //On implementations of the interface
+ STATE,
+ PARAMETER,
+ TARGET,
+ INPUT,
+ INTERNAL,
+ OUTPUT
+ }
+
+ /* Data types */
+ public enum DataType
{
- POSITION,
- FLOAT,
STRING,
- SPLINE_DATA,
- ENUM,
- SHORT_GUID,
- FILEPATH,
- BOOL,
- DIRECTION,
+ FLOAT,
INTEGER,
+ BOOL,
+ VECTOR,
+ TRANSFORM,
+ ENUM,
+ SPLINE,
+ NONE, //Translates to a blank string
+
+ RESOURCE,
+ FILEPATH,
OBJECT,
- NO_TYPE, //Translates to a blank string
ZONE_LINK_PTR,
ZONE_PTR,
MARKER,
@@ -34,9 +55,29 @@ public enum CathodeDataType
CAMERA
}
- /* Function types in the CATHODE scripting system */
- public enum CathodeFunctionType
+ /* Function types */
+ public enum FunctionType
{
+ //TODO: move these interface types outside of FunctionType
+ EntityMethodInterface,
+ AttachmentInterface,
+ BooleanLogicInterface,
+ CameraBehaviorInterface,
+ CloseableInterface,
+ CompositeInterface,
+ EvaluatorInterface,
+ GateInterface,
+ GateResourceInterface,
+ GetComponentInterface,
+ InspectorInterface,
+ ModifierInterface,
+ ProxyInterface,
+ ScriptInterface,
+ SensorAttachmentInterface,
+ SensorInterface,
+ TransformerInterface,
+ ZoneInterface,
+
AccessTerminal,
AchievementMonitor,
AchievementStat,
@@ -57,13 +98,11 @@ public enum CathodeFunctionType
ApplyRelativeTransform,
AreaHitMonitor,
AssetSpawner,
- AttachmentInterface,
Benchmark,
BindObjectsMultiplexer,
BlendLowResFrame,
BloomSettings,
BoneAttachedCamera,
- BooleanLogicInterface,
BooleanLogicOperation,
Box,
BroadcastTrigger,
@@ -71,7 +110,6 @@ public enum CathodeFunctionType
ButtonMashPrompt,
CAGEAnimation,
CameraAimAssistant,
- CameraBehaviorInterface,
CameraCollisionBox,
CameraDofController,
CameraFinder,
@@ -130,7 +168,6 @@ public enum CathodeFunctionType
ClearPrimaryObjective,
ClearSubObjective,
ClipPlanesController,
- CloseableInterface,
CMD_AimAt,
CMD_AimAtCurrentTarget,
CMD_Die,
@@ -155,7 +192,6 @@ public enum CathodeFunctionType
CollisionBarrier,
ColourCorrectionTransition,
ColourSettings,
- CompositeInterface,
CompoundVolume,
ControllableRange,
Convo,
@@ -209,7 +245,6 @@ public enum CathodeFunctionType
EnvironmentMap,
EnvironmentModelReference,
EQUIPPABLE_ITEM,
- EvaluatorInterface,
ExclusiveMaster,
Explosion_AINotifier,
ExternalVariableBool,
@@ -310,8 +345,6 @@ public enum CathodeFunctionType
GameOverCredits,
GameplayTip,
GameStateChanged,
- GateInterface,
- GateResourceInterface,
GCIP_WorldPickup, //n:\\content\\build\\library\\archetypes\\gameplay\\gcip_worldpickup
GenericHighlightEntity,
GetBlueprintAvailable,
@@ -322,7 +355,6 @@ public enum CathodeFunctionType
GetClosestPoint,
GetClosestPointFromSet,
GetClosestPointOnSpline,
- GetComponentInterface,
GetCurrentCameraFov,
GetCurrentCameraPos,
GetCurrentCameraTarget,
@@ -357,7 +389,6 @@ public enum CathodeFunctionType
IdleTask,
ImpactSphere,
InhibitActionsUntilRelease,
- InspectorInterface,
IntegerAbsolute,
IntegerAdd,
IntegerAdd_All,
@@ -456,7 +487,6 @@ public enum CathodeFunctionType
Minigames,
MissionNumber,
ModelReference,
- ModifierInterface,
MonitorActionMap,
MonitorBase,
MonitorPadInput,
@@ -603,7 +633,6 @@ public enum CathodeFunctionType
ProjectiveDecal,
ProximityDetector,
ProximityTrigger,
- ProxyInterface,
QueryGCItemPool,
RadiosityIsland,
RadiosityProxy,
@@ -641,10 +670,7 @@ public enum CathodeFunctionType
ScreenFadeOutToBlackTimed,
ScreenFadeOutToWhite,
ScreenFadeOutToWhiteTimed,
- ScriptInterface,
ScriptVariable,
- SensorAttachmentInterface,
- SensorInterface,
SetAsActiveMissionLevel,
SetBlueprintInfo,
SetBool,
@@ -728,7 +754,6 @@ public enum CathodeFunctionType
TogglePlayerTorch,
Torch_Control, //n:\\content\\build\\library\\archetypes\\script\\gameplay\\torch_control
TorchDynamicMovement,
- TransformerInterface,
TRAV_1ShotClimbUnder,
TRAV_1ShotFloorVentEntrance,
TRAV_1ShotFloorVentExit,
@@ -851,208 +876,259 @@ public enum CathodeFunctionType
WEAPON_TargetObjectFilter,
Zone,
ZoneExclusionLink,
- ZoneInterface,
ZoneLink,
ZoneLoaded,
}
/* Resource reference types */
- public enum CathodeResourceReferenceType
+ public enum ResourceType
{
- //CATHODE_COVER_SEGMENT,
- COLLISION_MAPPING, //This one seems to be called in another script block that I'm not currently parsing
- DYNAMIC_PHYSICS_SYSTEM, //This is a count (usually small) and then a -1 32-bit int
- EXCLUSIVE_MASTER_STATE_RESOURCE, //This just seems to be two -1 32-bit integers (same as above)
- NAV_MESH_BARRIER_RESOURCE, //This just seems to be two -1 32-bit integers (same as above)
- RENDERABLE_INSTANCE, //This one references an entry in the REnDerable elementS (REDS.BIN) database
- TRAVERSAL_SEGMENT, //This just seems to be two -1 32-bit integers
- ANIMATED_MODEL, //This is a count (usually small) and then a -1 32-bit int (same as above)
- }
+ COLLISION_MAPPING,
+ DYNAMIC_PHYSICS_SYSTEM,
+ EXCLUSIVE_MASTER_STATE_RESOURCE,
+ NAV_MESH_BARRIER_RESOURCE,
+ RENDERABLE_INSTANCE,
+ TRAVERSAL_SEGMENT,
+ ANIMATED_MODEL, //Links to ResourceIndex in ENVIRONMENT_ANIMATION.DAT
- /* A parameter compiled in COMMANDS.PAK */
- [Serializable]
- public class CathodeParameter : ICloneable
- {
- public CathodeParameter() { }
- public CathodeParameter(CathodeDataType type)
- {
- dataType = type;
- }
- public CathodeDataType dataType = CathodeDataType.NO_TYPE;
-
- public static bool operator ==(CathodeParameter x, CathodeParameter y)
- {
- if (ReferenceEquals(x, null)) return ReferenceEquals(y, null);
- if (ReferenceEquals(y, null)) return ReferenceEquals(x, null);
- if (x.dataType != y.dataType) return false;
- switch (x.dataType)
- {
- case CathodeDataType.POSITION:
- CathodeTransform x_t = (CathodeTransform)x;
- CathodeTransform y_t = (CathodeTransform)y;
- return x_t.position == y_t.position && x_t.rotation == y_t.rotation;
- case CathodeDataType.INTEGER:
- return ((CathodeInteger)x).value == ((CathodeInteger)y).value;
- case CathodeDataType.STRING:
- return ((CathodeString)x).value == ((CathodeString)y).value;
- case CathodeDataType.BOOL:
- return ((CathodeBool)x).value == ((CathodeBool)y).value;
- case CathodeDataType.FLOAT:
- return ((CathodeFloat)x).value == ((CathodeFloat)y).value;
- case CathodeDataType.SHORT_GUID:
- return ((CathodeResource)x).resourceID == ((CathodeResource)y).resourceID;
- case CathodeDataType.DIRECTION:
- return ((CathodeVector3)x).value == ((CathodeVector3)y).value;
- case CathodeDataType.ENUM:
- CathodeEnum x_e = (CathodeEnum)x;
- CathodeEnum y_e = (CathodeEnum)y;
- return x_e.enumIndex == y_e.enumIndex && x_e.enumID == y_e.enumID;
- case CathodeDataType.SPLINE_DATA:
- return ((CathodeSpline)x).splinePoints == ((CathodeSpline)y).splinePoints;
- case CathodeDataType.NO_TYPE:
- return true;
- default:
- return false;
- }
- }
- public static bool operator !=(CathodeParameter x, CathodeParameter y)
- {
- return !(x == y);
- }
+ // Any below this point are referenced in code, but not used in the vanilla game's CommandsPAKs
+ CATHODE_COVER_SEGMENT,
+ CHOKE_POINT_RESOURCE,
+ ANIMATION_MASK_RESOURCE,
+ PLAY_ANIMATION_DATA_RESOURCE,
- public override bool Equals(object obj)
- {
- if (!(obj is CathodeParameter)) return false;
- return ((CathodeParameter)obj) == this;
- }
- public override int GetHashCode()
- {
- //this is gross
- switch (dataType)
- {
- case CathodeDataType.POSITION:
- CathodeTransform x_t = (CathodeTransform)this;
- return Convert.ToInt32(
- x_t.rotation.x.ToString() + x_t.rotation.y.ToString() + x_t.rotation.z.ToString() +
- x_t.position.x.ToString() + x_t.position.y.ToString() + x_t.position.z.ToString());
- case CathodeDataType.INTEGER:
- return ((CathodeInteger)this).value;
- case CathodeDataType.STRING:
- CathodeString x_s = (CathodeString)this;
- string num = "";
- for (int i = 0; i < x_s.value.Length; i++) num += ((int)x_s.value[i]).ToString();
- return Convert.ToInt32(num);
- case CathodeDataType.BOOL:
- return ((CathodeBool)this).value ? 1 : 0;
- case CathodeDataType.FLOAT:
- return Convert.ToInt32(((CathodeFloat)this).value.ToString().Replace(".", ""));
- case CathodeDataType.SHORT_GUID:
- string x_g_s = ((CathodeString)this).value.ToString();
- string num2 = "";
- for (int i = 0; i < x_g_s.Length; i++) num2 += ((int)x_g_s[i]).ToString();
- return Convert.ToInt32(num2);
- case CathodeDataType.DIRECTION:
- CathodeVector3 x_v = (CathodeVector3)this;
- return Convert.ToInt32(x_v.value.x.ToString() + x_v.value.y.ToString() + x_v.value.z.ToString());
- case CathodeDataType.ENUM:
- CathodeEnum x_e = (CathodeEnum)this;
- string x_e_s = x_e.enumID.ToString();
- string num3 = "";
- for (int i = 0; i < x_e_s.Length; i++) num3 += ((int)x_e_s[i]).ToString();
- return Convert.ToInt32(num3 + x_e.enumIndex.ToString());
- case CathodeDataType.SPLINE_DATA:
- CathodeSpline x_sd = (CathodeSpline)this;
- string x_sd_s = "";
- for (int i = 0; i < x_sd.splinePoints.Count; i++) x_sd_s += x_sd.splinePoints[i].position.GetHashCode().ToString();
- ShortGuid x_sd_g = ShortGuidUtils.Generate(x_sd_s);
- string x_sd_g_s = x_sd_g.ToString();
- string num4 = "";
- for (int i = 0; i < x_sd_g_s.Length; i++) num4 += ((int)x_sd_g_s[i]).ToString();
- return Convert.ToInt32(num4);
- default:
- return -1;
- }
- }
-
- public object Clone()
- {
- switch (dataType)
- {
- case CathodeDataType.SPLINE_DATA:
- case CathodeDataType.SHORT_GUID:
- return Utilities.CloneObject(this);
- //HOTFIX FOR VECTOR 3 CLONE ISSUE - TODO: FIND WHY THIS ISN'T WORKING WITH MEMBERWISE CLONE
- case CathodeDataType.DIRECTION:
- CathodeVector3 v3 = (CathodeVector3)this.MemberwiseClone();
- v3.value = (Vector3)((CathodeVector3)this).value.Clone();
- return v3;
- case CathodeDataType.POSITION:
- CathodeTransform tr = (CathodeTransform)this.MemberwiseClone();
- tr.position = (Vector3)((CathodeTransform)this).position.Clone();
- tr.rotation = (Vector3)((CathodeTransform)this).rotation.Clone();
- return tr;
- //END OF HOTFIX - SHOULD THIS ALSO APPLY TO OTHERS??
- default:
- return this.MemberwiseClone();
- }
- }
- }
- [Serializable]
- public class CathodeTransform : CathodeParameter
- {
- public CathodeTransform() { dataType = CathodeDataType.POSITION; }
- public Vector3 position = new Vector3();
- public Vector3 rotation = new Vector3(); //In CATHODE this is named Roll/Pitch/Yaw
- }
- [Serializable]
- public class CathodeInteger : CathodeParameter
- {
- public CathodeInteger() { dataType = CathodeDataType.INTEGER; }
- public int value = 0;
- }
- [Serializable]
- public class CathodeString : CathodeParameter
- {
- public CathodeString() { dataType = CathodeDataType.STRING; }
- public string value = "";
- }
- [Serializable]
- public class CathodeBool : CathodeParameter
- {
- public CathodeBool() { dataType = CathodeDataType.BOOL; }
- public bool value = false;
- }
- [Serializable]
- public class CathodeFloat : CathodeParameter
- {
- public CathodeFloat() { dataType = CathodeDataType.FLOAT; }
- public float value = 0.0f;
- }
- [Serializable]
- public class CathodeResource : CathodeParameter
- {
- public CathodeResource() { dataType = CathodeDataType.SHORT_GUID; }
- public List value = new List(); //TODO: i dont know if this can actually have multiple entries. need to assert
- public ShortGuid resourceID;
- }
- [Serializable]
- public class CathodeVector3 : CathodeParameter
- {
- public CathodeVector3() { dataType = CathodeDataType.DIRECTION; }
- public Vector3 value = new Vector3();
+ CAMERA_INSTANCE,
+ VENT_ENTRANCE,
+ LOGIC_CHARACTER,
+ NPC_AREA_RESOURCE,
+ GAME_VARIABLE,
+ TUTORIAL_ENTRY_ID,
+ INVENTORY_ITEM_QUANTITY,
+ ANIM_SET,
+ ANIM_TREE_SET,
+ ATTRIBUTE_SET,
+ CHR_SKELETON_SET,
+ SOUND_TORSO_GROUP,
+ SOUND_LEG_GROUP,
+ SOUND_FOOTWEAR_GROUP,
+ IDTAG_ID,
+ SOUND_OBJECT,
+ SOUND_EVENT,
+ SOUND_ARGUMENT,
+ SOUND_SWITCH,
+ SOUND_REVERB,
+ SOUND_STATE,
+ SOUND_RTPC,
+ SOUND_PARAMETER,
+ EQUIPPABLE_ITEM_INSTANCE,
+ TERMINAL_CONTENT_DETAILS,
+ TERMINAL_FOLDER_DETAILS,
+ SEVASTOPOL_LOG_ID,
+ NOSTROMO_LOG_ID,
+ MAP_KEYFRAME_ID,
+ REWIRE_SYSTEM,
+ REWIRE_LOCATION,
+ REWIRE_ACCESS_POINT,
+ GAMEPLAY_TIP_STRING_ID,
+ BLUEPRINT_TYPE,
+ AUTODETECT,
+ OBJECTIVE_ENTRY_ID,
+ ACHIEVEMENT_ID,
+ ACHIEVEMENT_STAT_ID,
+ PRESENCE_ID,
+ CHARACTER,
}
- [Serializable]
- public class CathodeEnum : CathodeParameter
+
+ /* Enums available within Cathode */
+ public enum EnumType
{
- public CathodeEnum() { dataType = CathodeDataType.ENUM; }
- public ShortGuid enumID;
- public int enumIndex = 0;
+ AGGRESSION_GAIN,
+ ALERTNESS_STATE,
+ ALIEN_CONFIGURATION_TYPE,
+ ALIEN_DEVELOPMENT_MANAGER_ABILITIES,
+ ALIEN_DEVELOPMENT_MANAGER_ABILITY_MASKS,
+ ALIEN_DEVELOPMENT_MANAGER_STAGES,
+ ALLIANCE_GROUP,
+ ALLIANCE_STANCE,
+ AMBUSH_TYPE,
+ AMMO_TYPE,
+ ANIM_CALLBACK_ENUM,
+ ANIM_MODE,
+ ANIM_TRACK_TYPE,
+ ANIM_TREE_ENUM,
+ ANIMATION_EFFECT_TYPE,
+ AREA_SWEEP_TYPE,
+ AREA_SWEEP_TYPE_CODE,
+ AUTODETECT,
+ BEHAVIOR_TREE_BRANCH_TYPE,
+ BEHAVIOUR_MOOD_SET,
+ BEHAVIOUR_TREE_FLAGS,
+ BLEND_MODE,
+ BLUEPRINT_LEVEL,
+ BUTTON_TYPE,
+ CAMERA_PATH_CLASS,
+ CAMERA_PATH_TYPE,
+ CHARACTER_BB_ENTRY_TYPE,
+ CHARACTER_CLASS,
+ CHARACTER_CLASS_COMBINATION,
+ CHARACTER_FOLEY_SOUND,
+ CHARACTER_NODE,
+ CHARACTER_STANCE,
+ CHECKPOINT_TYPE,
+ CI_MESSAGE_TYPE,
+ CLIPPING_PLANES_PRESETS,
+ COLLISION_TYPE,
+ COMBAT_BEHAVIOUR,
+ CROUCH_MODE,
+ CUSTOM_CHARACTER_ACCESSORY_OVERRIDE,
+ CUSTOM_CHARACTER_ASSETS,
+ CUSTOM_CHARACTER_BUILD,
+ CUSTOM_CHARACTER_COMPONENT,
+ CUSTOM_CHARACTER_ETHNICITY,
+ CUSTOM_CHARACTER_GENDER,
+ CUSTOM_CHARACTER_MODEL,
+ CUSTOM_CHARACTER_POPULATION,
+ CUSTOM_CHARACTER_SLEEVETYPE,
+ CUSTOM_CHARACTER_TYPE,
+ DAMAGE_EFFECT_TYPE_FLAGS,
+ DAMAGE_EFFECTS,
+ DAMAGE_MODE,
+ DEATH_STYLE,
+ DEVICE_INTERACTION_MODE,
+ DIALOGUE_ACTION,
+ DIALOGUE_ARGUMENT,
+ DIALOGUE_NPC_COMBAT_MODE,
+ DIALOGUE_NPC_CONTEXT,
+ DIALOGUE_NPC_EVENT,
+ DIALOGUE_VOICE_ACTOR,
+ DIFFICULTY_SETTING_TYPE,
+ DOOR_MECHANISM,
+ DOOR_STATE,
+ DUCK_HEIGHT,
+ ENEMY_TYPE,
+ ENVIRONMENT_ARCHETYPE,
+ EQUIPMENT_SLOT,
+ EVENT_OCCURED_TYPE,
+ EXIT_WAYPOINT,
+ FLAG_CHANGE_SOURCE_TYPE,
+ FLASH_INVOKE_TYPE,
+ FLASH_SCRIPT_RENDER_TYPE,
+ FOG_BOX_TYPE,
+ FOLDER_LOCK_TYPE,
+ FOLLOW_CAMERA_MODIFIERS,
+ FOLLOW_TYPE,
+ FRAME_FLAGS,
+ FRONTEND_STATE,
+ GAME_CLIP,
+ GATING_TOOL_TYPE,
+ IDLE,
+ IDLE_STYLE,
+ IMPACT_CHARACTER_BODY_LOCATION_TYPE,
+ INPUT_DEVICE_TYPE,
+ JOB_TYPE,
+ LEVEL_HEAP_TAG,
+ LEVER_TYPE,
+ LIGHT_ADAPTATION_MECHANISM,
+ LIGHT_ANIM,
+ LIGHT_FADE_TYPE,
+ LIGHT_TRANSITION,
+ LIGHT_TYPE,
+ LOCOMOTION_STATE,
+ LOCOMOTION_TARGET_SPEED,
+ LOGIC_CHARACTER_FLAGS,
+ LOGIC_CHARACTER_GAUGE_TYPE,
+ LOGIC_CHARACTER_TIMER_TYPE,
+ LOOK_SPEED,
+ MAP_ICON_TYPE,
+ MELEE_ATTACK_TYPE,
+ MELEE_CONTEXT_TYPE,
+ MOOD,
+ MOOD_INTENSITY,
+ MOVE,
+ MUSIC_RTPC_MODE,
+ NAV_MESH_AREA_TYPE,
+ NAVIGATION_CHARACTER_CLASS,
+ NAVIGATION_CHARACTER_CLASS_COMBINATION,
+ NOISE_TYPE,
+ NPC_AGGRO_LEVEL,
+ NPC_COMBAT_STATE,
+ NPC_COVER_REQUEST_TYPE,
+ NPC_GUN_AIM_MODE,
+ ORIENTATION_AXIS,
+ PATH_DRIVEN_TYPE,
+ PAUSE_SENSES_TYPE,
+ PICKUP_CATEGORY,
+ PLATFORM_TYPE,
+ PLAYER_INVENTORY_SET,
+ POPUP_MESSAGE_ICON,
+ POPUP_MESSAGE_SOUND,
+ PRIORITY,
+ RANGE_TEST_SHAPE,
+ RAYCAST_PRIORITY,
+ RESPAWN_MODE,
+ REWIRE_SYSTEM_NAME,
+ REWIRE_SYSTEM_TYPE,
+ SECONDARY_ANIMATION_LAYER,
+ SENSE_SET,
+ SENSE_SET_DEFAULT,
+ SENSE_SET_SYSTEM,
+ SENSORY_TYPE,
+ SHAKE_TYPE,
+ SIDE,
+ SOUND_POOL,
+ SPEECH_PRIORITY,
+ STEAL_CAMERA_TYPE,
+ SUB_OBJECTIVE_TYPE,
+ SUSPECT_RESPONSE_PHASE,
+ SUSPICIOUS_ITEM,
+ SUSPICIOUS_ITEM_BEHAVIOUR_TREE_PRIORITY,
+ SUSPICIOUS_ITEM_CLOSE_REACTION_DETAIL,
+ SUSPICIOUS_ITEM_REACTION,
+ SUSPICIOUS_ITEM_STAGE,
+ SUSPICIOUS_ITEM_START_OR_CONTINUE_STATE,
+ SUSPICIOUS_ITEM_TRIGGER,
+ TASK_CHARACTER_CLASS_FILTER,
+ TASK_OPERATION_MODE,
+ TASK_PRIORITY,
+ TERMINAL_LOCATION,
+ TEXT_ALIGNMENT,
+ THRESHOLD_QUALIFIER,
+ TRANSITION_DIRECTION,
+ TRAVERSAL_ANIMS,
+ TRAVERSAL_TYPE,
+ UI_ICON_ICON,
+ UI_KEYGATE_TYPE,
+ VENT_LOCK_REASON,
+ VIEWCONE_TYPE,
+ VISIBILITY_SETTINGS_TYPE,
+ WAVE_SHAPE,
+ WEAPON_HANDEDNESS,
+ WEAPON_IMPACT_EFFECT_ORIENTATION,
+ WEAPON_IMPACT_EFFECT_TYPE,
+ WEAPON_IMPACT_FILTER_ORIENTATION,
+ WEAPON_PROPERTY,
+ WEAPON_TYPE,
}
- [Serializable]
- public class CathodeSpline : CathodeParameter
+
+ /* Blocks of data in each compiled composite */
+ public enum DataBlock
{
- public CathodeSpline() { dataType = CathodeDataType.SPLINE_DATA; }
- public List splinePoints = new List();
+ COMPOSITE_HEADER, //Defines the header of the composite, with global ID and string name
+ ENTITY_CONNECTIONS, //Defines the links between entities in the composite
+ ENTITY_PARAMETERS, //Defines parameters to be applied to entities in the composite
+ ENTITY_OVERRIDES, //Defines overrides to apply to nested instances of composites in this composite
+ ENTITY_OVERRIDES_CHECKSUM, //Defines a checksum value for the hierarchy override (TODO)
+ COMPOSITE_EXPOSED_PARAMETERS, //Defines variables which are exposed when instancing this composite which are then connected in to entities (think variable pins in UE4 blueprint)
+ ENTITY_PROXIES, //Defines "proxies" similar to the overrides hierarchy (TODO)
+ ENTITY_FUNCTIONS, //Defines entities with an attached script function within Cathode
+ RESOURCE_REFERENCES, //Defines renderable data which is referenced by entities in this composite
+ CAGEANIMATION_DATA, //Appears to define additional data for CAGEAnimation type entities (TODO)
+ TRIGGERSEQUENCE_DATA, //Appears to define additional data for TriggerSequence type entities (TODO)
+
+ UNUSED, //Unused values
+ UNKNOWN_COUNTS, //TODO - unused?
+
+ NUMBER_OF_SCRIPT_BLOCKS, //THIS IS NOT A DATA BLOCK: merely used as an easy way of sanity checking the number of blocks in-code!
}
}
diff --git a/CathodeLib/Scripts/CommandsPAK/CommandsUtils.cs b/CathodeLib/Scripts/CATHODE/CommandsPAK/Helpers/CommandsUtils.cs
similarity index 50%
rename from CathodeLib/Scripts/CommandsPAK/CommandsUtils.cs
rename to CathodeLib/Scripts/CATHODE/CommandsPAK/Helpers/CommandsUtils.cs
index 77420e7..797c241 100644
--- a/CathodeLib/Scripts/CommandsPAK/CommandsUtils.cs
+++ b/CathodeLib/Scripts/CATHODE/CommandsPAK/Helpers/CommandsUtils.cs
@@ -3,8 +3,9 @@
using System.Linq;
using System.Text;
-namespace CATHODE.Commands
+namespace CATHODE.Scripting
{
+ //Helpful lookup tables for various Cathode Commands types
public static class CommandsUtils
{
static CommandsUtils()
@@ -14,24 +15,35 @@ static CommandsUtils()
SetupResourceEntryTypeLUT();
}
- private static Dictionary _functionTypeLUT = new Dictionary();
+ /* Function Types */
+ private static Dictionary _functionTypeLUT = new Dictionary();
private static void SetupFunctionTypeLUT()
{
if (_functionTypeLUT.Count != 0) return;
- foreach (CathodeFunctionType functionType in Enum.GetValues(typeof(CathodeFunctionType)))
- _functionTypeLUT.Add(ShortGuidUtils.Generate(functionType.ToString()), functionType);
+ foreach (FunctionType functionType in Enum.GetValues(typeof(FunctionType)))
+ {
+ string shortGuidString = functionType.ToString();
+ if (functionType == FunctionType.GCIP_WorldPickup)
+ shortGuidString = "n:\\content\\build\\library\\archetypes\\gameplay\\gcip_worldpickup";
+ if (functionType == FunctionType.PlayForMinDuration)
+ shortGuidString = "n:\\content\\build\\library\\ayz\\animation\\logichelpers\\playforminduration";
+ if (functionType == FunctionType.Torch_Control)
+ shortGuidString = "n:\\content\\build\\library\\archetypes\\script\\gameplay\\torch_control";
+
+ _functionTypeLUT.Add(ShortGuidUtils.Generate(shortGuidString), functionType);
+ }
}
- public static CathodeFunctionType GetFunctionType(byte[] tag)
+ public static FunctionType GetFunctionType(byte[] tag)
{
return GetFunctionType(new ShortGuid(tag));
}
- public static CathodeFunctionType GetFunctionType(ShortGuid tag)
+ public static FunctionType GetFunctionType(ShortGuid tag)
{
SetupFunctionTypeLUT();
return _functionTypeLUT[tag];
}
- public static ShortGuid GetFunctionTypeGUID(CathodeFunctionType type)
+ public static ShortGuid GetFunctionTypeGUID(FunctionType type)
{
SetupFunctionTypeLUT();
return _functionTypeLUT.FirstOrDefault(x => x.Value == type).Key;
@@ -41,39 +53,40 @@ public static bool FunctionTypeExists(ShortGuid tag)
return _functionTypeLUT.ContainsKey(tag);
}
- private static Dictionary _dataTypeLUT = new Dictionary();
+ /* Data Types */
+ private static Dictionary _dataTypeLUT = new Dictionary();
private static void SetupDataTypeLUT()
{
if (_dataTypeLUT.Count != 0) return;
- _dataTypeLUT.Add(ShortGuidUtils.Generate("bool"), CathodeDataType.BOOL);
- _dataTypeLUT.Add(ShortGuidUtils.Generate("int"), CathodeDataType.INTEGER);
- _dataTypeLUT.Add(ShortGuidUtils.Generate("float"), CathodeDataType.FLOAT);
- _dataTypeLUT.Add(ShortGuidUtils.Generate("String"), CathodeDataType.STRING);
- _dataTypeLUT.Add(ShortGuidUtils.Generate("FilePath"), CathodeDataType.FILEPATH);
- _dataTypeLUT.Add(ShortGuidUtils.Generate("SplineData"), CathodeDataType.SPLINE_DATA);
- _dataTypeLUT.Add(ShortGuidUtils.Generate("Direction"), CathodeDataType.DIRECTION);
- _dataTypeLUT.Add(ShortGuidUtils.Generate("Position"), CathodeDataType.POSITION);
- _dataTypeLUT.Add(ShortGuidUtils.Generate("Enum"), CathodeDataType.ENUM);
- _dataTypeLUT.Add(ShortGuidUtils.Generate("ShortGuid"), CathodeDataType.SHORT_GUID);
- _dataTypeLUT.Add(ShortGuidUtils.Generate("Object"), CathodeDataType.OBJECT);
- _dataTypeLUT.Add(ShortGuidUtils.Generate("ZonePtr"), CathodeDataType.ZONE_PTR);
- _dataTypeLUT.Add(ShortGuidUtils.Generate("ZoneLinkPtr"), CathodeDataType.ZONE_LINK_PTR);
- _dataTypeLUT.Add(ShortGuidUtils.Generate(""), CathodeDataType.NO_TYPE);
- _dataTypeLUT.Add(ShortGuidUtils.Generate("Marker"), CathodeDataType.MARKER);
- _dataTypeLUT.Add(ShortGuidUtils.Generate("Character"), CathodeDataType.CHARACTER);
- _dataTypeLUT.Add(ShortGuidUtils.Generate("Camera"), CathodeDataType.CAMERA);
+ _dataTypeLUT.Add(ShortGuidUtils.Generate("bool"), DataType.BOOL);
+ _dataTypeLUT.Add(ShortGuidUtils.Generate("int"), DataType.INTEGER);
+ _dataTypeLUT.Add(ShortGuidUtils.Generate("float"), DataType.FLOAT);
+ _dataTypeLUT.Add(ShortGuidUtils.Generate("String"), DataType.STRING);
+ _dataTypeLUT.Add(ShortGuidUtils.Generate("FilePath"), DataType.FILEPATH);
+ _dataTypeLUT.Add(ShortGuidUtils.Generate("SplineData"), DataType.SPLINE);
+ _dataTypeLUT.Add(ShortGuidUtils.Generate("Direction"), DataType.VECTOR);
+ _dataTypeLUT.Add(ShortGuidUtils.Generate("Position"), DataType.TRANSFORM);
+ _dataTypeLUT.Add(ShortGuidUtils.Generate("Enum"), DataType.ENUM);
+ _dataTypeLUT.Add(ShortGuidUtils.Generate("ShortGuid"), DataType.RESOURCE);
+ _dataTypeLUT.Add(ShortGuidUtils.Generate("Object"), DataType.OBJECT);
+ _dataTypeLUT.Add(ShortGuidUtils.Generate("ZonePtr"), DataType.ZONE_PTR);
+ _dataTypeLUT.Add(ShortGuidUtils.Generate("ZoneLinkPtr"), DataType.ZONE_LINK_PTR);
+ _dataTypeLUT.Add(ShortGuidUtils.Generate(""), DataType.NONE);
+ _dataTypeLUT.Add(ShortGuidUtils.Generate("Marker"), DataType.MARKER);
+ _dataTypeLUT.Add(ShortGuidUtils.Generate("Character"), DataType.CHARACTER);
+ _dataTypeLUT.Add(ShortGuidUtils.Generate("Camera"), DataType.CAMERA);
}
- public static CathodeDataType GetDataType(byte[] tag)
+ public static DataType GetDataType(byte[] tag)
{
return GetDataType(new ShortGuid(tag));
}
- public static CathodeDataType GetDataType(ShortGuid tag)
+ public static DataType GetDataType(ShortGuid tag)
{
SetupDataTypeLUT();
return _dataTypeLUT[tag];
}
- public static ShortGuid GetDataTypeGUID(CathodeDataType type)
+ public static ShortGuid GetDataTypeGUID(DataType type)
{
SetupDataTypeLUT();
return _dataTypeLUT.FirstOrDefault(x => x.Value == type).Key;
@@ -83,24 +96,25 @@ public static bool DataTypeExists(ShortGuid tag)
return _dataTypeLUT.ContainsKey(tag);
}
- private static Dictionary _resourceReferenceTypeLUT = new Dictionary();
+ /* Resource Reference Types */
+ private static Dictionary _resourceReferenceTypeLUT = new Dictionary();
private static void SetupResourceEntryTypeLUT()
{
if (_resourceReferenceTypeLUT.Count != 0) return;
- foreach (CathodeResourceReferenceType referenceType in Enum.GetValues(typeof(CathodeResourceReferenceType)))
+ foreach (ResourceType referenceType in Enum.GetValues(typeof(ResourceType)))
_resourceReferenceTypeLUT.Add(ShortGuidUtils.Generate(referenceType.ToString()), referenceType);
}
- public static CathodeResourceReferenceType GetResourceEntryType(byte[] tag)
+ public static ResourceType GetResourceEntryType(byte[] tag)
{
return GetResourceEntryType(new ShortGuid(tag));
}
- public static CathodeResourceReferenceType GetResourceEntryType(ShortGuid tag)
+ public static ResourceType GetResourceEntryType(ShortGuid tag)
{
SetupResourceEntryTypeLUT();
return _resourceReferenceTypeLUT[tag];
}
- public static ShortGuid GetResourceEntryTypeGUID(CathodeResourceReferenceType type)
+ public static ShortGuid GetResourceEntryTypeGUID(ResourceType type)
{
SetupResourceEntryTypeLUT();
return _resourceReferenceTypeLUT.FirstOrDefault(x => x.Value == type).Key;
diff --git a/CathodeLib/Scripts/CommandsPAK/CompositePathDB.cs b/CathodeLib/Scripts/CATHODE/CommandsPAK/Helpers/CompositeUtils.cs
similarity index 73%
rename from CathodeLib/Scripts/CommandsPAK/CompositePathDB.cs
rename to CathodeLib/Scripts/CATHODE/CommandsPAK/Helpers/CompositeUtils.cs
index 6c121a3..0495542 100644
--- a/CathodeLib/Scripts/CommandsPAK/CompositePathDB.cs
+++ b/CathodeLib/Scripts/CATHODE/CommandsPAK/Helpers/CompositeUtils.cs
@@ -1,4 +1,4 @@
-using CATHODE.Commands;
+using CATHODE.Scripting;
using System;
using System.Collections.Generic;
using System.IO;
@@ -7,27 +7,27 @@
namespace CathodeLib
{
//Has a store of all composite paths in the vanilla game: used for prettifying the all-caps Windows strings
- public static class CompositePathDB
+ public static class CompositeUtils
{
private static Dictionary pathLookup;
- static CompositePathDB()
+ static CompositeUtils()
{
BinaryReader reader = new BinaryReader(new MemoryStream(Properties.Resources.composite_paths));
int compositeCount = reader.ReadInt32();
pathLookup = new Dictionary(compositeCount);
for (int i = 0; i < compositeCount; i++)
- pathLookup.Add(CATHODE.Utilities.Consume(reader), reader.ReadString());
+ pathLookup.Add(CathodeLib.Utilities.Consume(reader), reader.ReadString());
}
- public static string GetFullPathForComposite(ShortGuid guid)
+ public static string GetFullPath(ShortGuid guid)
{
return pathLookup.ContainsKey(guid) ? pathLookup[guid] : "";
}
- public static string GetPrettyPathForComposite(ShortGuid guid)
+ public static string GetPrettyPath(ShortGuid guid)
{
- string fullPath = GetFullPathForComposite(guid);
+ string fullPath = GetFullPath(guid);
if (fullPath.Length < 1) return "";
string first25 = fullPath.Substring(0, 25);
switch (first25)
diff --git a/CathodeLib/Scripts/CATHODE/CommandsPAK/Helpers/EntityUtils.cs b/CathodeLib/Scripts/CATHODE/CommandsPAK/Helpers/EntityUtils.cs
new file mode 100644
index 0000000..d9973dc
--- /dev/null
+++ b/CathodeLib/Scripts/CATHODE/CommandsPAK/Helpers/EntityUtils.cs
@@ -0,0 +1,6496 @@
+using CathodeLib;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Runtime.InteropServices;
+using System.Runtime.Serialization.Formatters.Binary;
+using System.Text;
+
+namespace CATHODE.Scripting
+{
+ //This should be initialised per-commandspak, and serves as a helpful extension to manage entity names
+ public class EntityUtils
+ {
+ private Commands commandsPAK;
+ private Dictionary> vanilla_composites;
+ private Dictionary> custom_composites;
+
+ public EntityUtils(Commands commands = null)
+ {
+ commandsPAK = commands;
+ if (commandsPAK != null)
+ commandsPAK.OnSaved += SaveCustomNames;
+
+ LoadVanillaNames();
+ LoadCustomNames();
+ }
+
+ /* Get the name of an entity contained within a composite */
+ public string GetName(ShortGuid compositeID, ShortGuid entityID)
+ {
+ if (custom_composites != null)
+ if (custom_composites.ContainsKey(compositeID) && custom_composites[compositeID].ContainsKey(entityID))
+ return custom_composites[compositeID][entityID];
+ if (vanilla_composites != null)
+ if (vanilla_composites.ContainsKey(compositeID) && vanilla_composites[compositeID].ContainsKey(entityID))
+ return vanilla_composites[compositeID][entityID];
+ return entityID.ToString();
+ }
+
+ /* Set the name of an entity contained within a composite */
+ public void SetName(ShortGuid compositeID, ShortGuid entityID, string name)
+ {
+ if (!custom_composites.ContainsKey(compositeID))
+ custom_composites.Add(compositeID, new Dictionary());
+
+ if (!custom_composites[compositeID].ContainsKey(entityID))
+ custom_composites[compositeID].Add(entityID, name);
+ else
+ custom_composites[compositeID][entityID] = name;
+ }
+
+ /* Clear the name of an entity contained within a composite */
+ public void ClearName(ShortGuid compositeID, ShortGuid entityID)
+ {
+ if (custom_composites.ContainsKey(compositeID))
+ custom_composites[compositeID].Remove(entityID);
+ }
+
+ /* Applies all default parameter data to a Function entity (POTENTIALLY DESTRUCTIVE!) */
+ public static void ApplyDefaults(FunctionEntity entity)
+ {
+ ApplyDefaultsInternal(entity);
+ }
+
+
+ /* Load all standard entity/composite names from our offline DB */
+ private void LoadVanillaNames()
+ {
+ BinaryReader reader = new BinaryReader(new MemoryStream(CathodeLib.Properties.Resources.composite_entity_names));
+ vanilla_composites = ConsumeDatabase(reader, reader.ReadInt32());
+ reader.Close();
+ }
+
+ /* Pull non-vanilla entity names from the CommandsPAK */
+ private void LoadCustomNames()
+ {
+ if (commandsPAK == null) return;
+ int endPos = GetEndOfCommands();
+ BinaryReader reader = new BinaryReader(File.OpenRead(commandsPAK.Filepath));
+ reader.BaseStream.Position = endPos;
+ if ((int)reader.BaseStream.Length - endPos == 0 || reader.ReadByte() != (byte)254)
+ {
+ custom_composites = new Dictionary>();
+ reader.Close();
+ return;
+ }
+ reader.BaseStream.Position += 4;
+ int compCount = reader.ReadInt32();
+ reader.BaseStream.Position += 8;
+ custom_composites = ConsumeDatabase(reader, compCount);
+ reader.Close();
+ }
+
+ /* Write non-vanilla entity names to the CommandsPAK */
+ private void SaveCustomNames()
+ {
+ if (commandsPAK == null) return;
+ int endPos = GetEndOfCommands();
+ //TODO: move this writing functionality into its own thing
+ BinaryWriter writer = new BinaryWriter(File.OpenWrite(commandsPAK.Filepath));
+ bool hasAlreadyWritten = (int)writer.BaseStream.Length - endPos != 0;
+ int posToJumpBackTo = 0;
+ writer.BaseStream.Position = endPos;
+ writer.Write((byte)254);
+ writer.Write((int)writer.BaseStream.Position + 16);
+ writer.Write(custom_composites.Count);
+ if (hasAlreadyWritten)
+ {
+ writer.BaseStream.Position += 8;
+ }
+ else
+ {
+ posToJumpBackTo = (int)writer.BaseStream.Position;
+ writer.Write(0);
+ writer.Write(0);
+ }
+ foreach (KeyValuePair> composite in custom_composites)
+ {
+ Utilities.Write(writer, composite.Key);
+ writer.Write(composite.Value.Count);
+ foreach (KeyValuePair entity in composite.Value)
+ {
+ Utilities.Write(writer, entity.Key);
+ writer.Write(entity.Value);
+ }
+ }
+ int posToWrite = (int)writer.BaseStream.Position;
+ writer.BaseStream.Position = posToJumpBackTo;
+ writer.Write(posToWrite);
+ writer.Close();
+ }
+
+ /* Consume our stored entity info */
+ private Dictionary> ConsumeDatabase(BinaryReader reader, int compCount)
+ {
+ Dictionary> composites = new Dictionary>(compCount);
+ for (int i = 0; i < compCount; i++)
+ {
+ ShortGuid compositeID = Utilities.Consume