├── .gitattributes ├── .gitignore ├── .gitmodules ├── FEngCli ├── BaseCommand.cs ├── CompileCommand.cs ├── DecompileCommand.cs ├── FEngCli.csproj ├── PackageLoader.cs └── Program.cs ├── FEngLib.sln ├── FEngLib ├── Chunks │ ├── ButtonMapCountChunk.cs │ ├── ChunkReadingException.cs │ ├── EventDataChunk.cs │ ├── FrontendObjectContainerChunk.cs │ ├── MessageDefinitionsChunk.cs │ ├── MessageResponsesDataChunk.cs │ ├── ObjectContainerChunk.cs │ ├── ObjectDataChunk.cs │ ├── PackageHeaderChunk.cs │ ├── PackageMessageTargetsChunk.cs │ ├── PackageResponsesChunk.cs │ ├── ResourceNamesChunk.cs │ ├── ResourceRequestsChunk.cs │ ├── ResourcesContainerChunk.cs │ ├── ScriptDataChunk.cs │ ├── TypeInfoListChunk.cs │ └── TypeListChunk.cs ├── FEngLib.csproj ├── FrontendChunk.cs ├── FrontendChunkBlock.cs ├── FrontendChunkReader.cs ├── FrontendChunkType.cs ├── FrontendChunkWriter.cs ├── FrontendObjectChunk.cs ├── FrontendTagType.cs ├── Messaging │ ├── Commands │ │ ├── Else.cs │ │ ├── EndIf.cs │ │ ├── IfScriptEquals.cs │ │ ├── IfScriptNotEquals.cs │ │ ├── PopPackage.cs │ │ ├── PostMessageToFEng.cs │ │ ├── PostMessageToGame.cs │ │ ├── PostMessageToSound.cs │ │ ├── PushPackageGlobal.cs │ │ ├── RecallRecordedButton.cs │ │ ├── RecordCurrentButton.cs │ │ ├── SetActiveButton.cs │ │ ├── SetInputProcessing.cs │ │ ├── SetScript.cs │ │ └── SwitchToPackage.cs │ ├── IHaveMessageResponses.cs │ ├── MessageResponse.cs │ ├── MessageResponseTagProcessor.cs │ ├── MessageTagStream.cs │ ├── ResponseCommand.cs │ ├── ResponseHelpers.cs │ └── Tags │ │ ├── MessageResponseCountTag.cs │ │ ├── MessageResponseInfoTag.cs │ │ ├── ResponseIdTag.cs │ │ ├── ResponseIntParamTag.cs │ │ ├── ResponseStringParamTag.cs │ │ └── ResponseTargetTag.cs ├── ObjectReaderState.cs ├── Objects │ ├── BaseObject.cs │ ├── ColoredImage.cs │ ├── Group.cs │ ├── Image.cs │ ├── Movie.cs │ ├── MultiImage.cs │ ├── ObjectFlags.cs │ ├── ObjectTag.cs │ ├── ObjectType.cs │ ├── SimpleImage.cs │ ├── Tags │ │ ├── ImageInfoTag.cs │ │ ├── MultiImageTextureFlagsTag.cs │ │ ├── MultiImageTextureTag.cs │ │ ├── ObjectDataTag.cs │ │ ├── ObjectHashTag.cs │ │ ├── ObjectNameTag.cs │ │ ├── ObjectParentTag.cs │ │ ├── ObjectReferenceTag.cs │ │ ├── ObjectTagStream.cs │ │ ├── ObjectTypeTag.cs │ │ ├── StringBufferFormattingTag.cs │ │ ├── StringBufferLabelHashTag.cs │ │ ├── StringBufferLabelTag.cs │ │ ├── StringBufferLeadingTag.cs │ │ ├── StringBufferLengthTag.cs │ │ ├── StringBufferMaxWidthTag.cs │ │ └── StringBufferTextTag.cs │ └── Text.cs ├── Packages │ ├── FrontendPackageLoader.cs │ ├── MessageTargets.cs │ ├── Package.cs │ ├── ResourceRequest.cs │ ├── Tags │ │ ├── MessageTargetCountTag.cs │ │ └── MessageTargetListTag.cs │ └── TypeSizeEntry.cs ├── Scripts │ ├── Event.cs │ ├── Script.cs │ ├── ScriptProcessingContext.cs │ ├── ScriptTag.cs │ ├── ScriptTagStream.cs │ ├── Tags │ │ ├── ScriptChainTag.cs │ │ ├── ScriptEventsTag.cs │ │ ├── ScriptHeaderTag.cs │ │ ├── ScriptKeyNodeTag.cs │ │ ├── ScriptKeyTrackTag.cs │ │ ├── ScriptNameTag.cs │ │ └── ScriptTrackOffsetTag.cs │ ├── Track.cs │ ├── TrackHelpers.cs │ └── TrackNode.cs ├── Structures │ └── Color4.cs ├── Tags │ ├── Tag.cs │ └── TagStream.cs └── Utils │ ├── BinaryExtensions.cs │ ├── BinaryWriterChunkExtensions.cs │ ├── Hashing.cs │ ├── IBinaryAccess.cs │ └── NullTerminatedString.cs ├── FEngRender.GL ├── FEngRender.GL.csproj ├── GLGlyphRenderer.cs ├── GLRenderTreeRenderer.cs ├── ITextureProvider.cs ├── Properties │ ├── Resources.Designer.cs │ └── Resources.resx ├── Quad.cs ├── Resources │ ├── shader.frag.glsl │ └── shader.vert.glsl ├── Texture.cs └── VertexDeclaration.cs ├── FEngRender ├── Data │ ├── RenderTree.cs │ ├── RenderTreeGroup.cs │ ├── RenderTreeImage.cs │ ├── RenderTreeMovie.cs │ ├── RenderTreeNode.cs │ └── RenderTreeText.cs ├── FEngRender.csproj ├── RenderContext.cs └── Utils │ ├── MathHelpers.cs │ └── TextHelpers.cs ├── FEngTestBench ├── FEngTestBench.csproj └── Program.cs └── FEngViewer ├── AppService.cs ├── FEngViewer.csproj ├── GLRenderControl.Designer.cs ├── GLRenderControl.cs ├── GLRenderControl.resx ├── HashList.cs ├── IRenderControl.cs ├── ObjectViewWrapper.cs ├── PackageView.Designer.cs ├── PackageView.cs ├── PackageView.resx ├── Program.cs ├── Properties ├── Resources.Designer.cs └── Resources.resx ├── ResourceRequestSelector.cs ├── Resources ├── FngMessages.txt ├── FngObjects.txt ├── FngScripts.txt ├── The_Missing_textures.png ├── bolt-solid.png ├── clock-solid.png ├── code-solid.png ├── envelope-solid.png ├── exclamation-solid.png ├── file-archive-solid.png ├── file-solid.png ├── file-video-solid.png ├── font-solid.png ├── heading-solid.png ├── image-solid.png ├── images-solid.png ├── layer-group-solid.png ├── object-group-solid.png ├── paint-roller-solid.png ├── pause-solid.png ├── play-solid.png ├── project-diagram-solid.png ├── shapes-solid.png └── sliders-h-solid.png ├── ScriptTrackViewWrapper.cs ├── ScriptViewWrapper.cs ├── TextureProvider.cs ├── TrackEditorControl.Designer.cs ├── TrackEditorControl.cs ├── TrackEditorControl.resx ├── TypeConverters ├── Color4TypeConverter.cs ├── FieldReflectingTypeConverter.cs ├── HexTypeConverter.cs ├── QuaternionTypeConverter.cs ├── Vector2TypeConverter.cs └── Vector3TypeConverter.cs └── UIEditors └── Color4Editor.cs /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NFSTools/FEngLib/7562a4c6196ec07b29868b2be1e1d2615202bd06/.gitmodules -------------------------------------------------------------------------------- /FEngCli/BaseCommand.cs: -------------------------------------------------------------------------------- 1 | namespace FEngCli; 2 | 3 | public abstract class BaseCommand 4 | { 5 | public abstract int Execute(); 6 | } -------------------------------------------------------------------------------- /FEngCli/CompileCommand.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.IO; 3 | using System.Linq; 4 | using CommandLine; 5 | using FEngLib; 6 | using FEngLib.Packages; 7 | using Newtonsoft.Json; 8 | using Newtonsoft.Json.Converters; 9 | 10 | namespace FEngCli; 11 | 12 | [Verb("compile")] 13 | public class CompileCommand : BaseCommand 14 | { 15 | [Option('i', Required = true)] public IEnumerable InputPath { get; set; } 16 | 17 | [Option('o', Required = true)] public string OutputPath { get; set; } 18 | 19 | public override int Execute() 20 | { 21 | var packages = InputPath.Select(path => JsonConvert.DeserializeObject(File.ReadAllText(path), 22 | new JsonSerializerSettings 23 | { 24 | Formatting = Formatting.Indented, 25 | Converters = new List 26 | { 27 | new StringEnumConverter() 28 | }, 29 | TypeNameHandling = TypeNameHandling.Auto, 30 | ReferenceLoopHandling = ReferenceLoopHandling.Error, 31 | PreserveReferencesHandling = PreserveReferencesHandling.Objects, 32 | NullValueHandling = NullValueHandling.Ignore 33 | })); 34 | 35 | using var bw = new BinaryWriter(File.Create(OutputPath)); 36 | 37 | foreach (var package in packages) 38 | { 39 | using var tms = new MemoryStream(); 40 | using var tbw = new BinaryWriter(tms); 41 | new FrontendChunkWriter(package).Write(tbw); 42 | tms.Position = 0; 43 | 44 | bw.Write(0x30203); 45 | bw.Write((uint)tms.Length); 46 | tms.CopyTo(bw.BaseStream); 47 | } 48 | 49 | return 0; 50 | } 51 | } -------------------------------------------------------------------------------- /FEngCli/DecompileCommand.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.IO; 3 | using CommandLine; 4 | using Newtonsoft.Json; 5 | using Newtonsoft.Json.Converters; 6 | 7 | namespace FEngCli; 8 | 9 | [Verb("decompile")] 10 | public class DecompileCommand : BaseCommand 11 | { 12 | [Option('i', Required = true)] public string InputPath { get; set; } 13 | 14 | [Option('o', Required = true)] public string OutputPath { get; set; } 15 | 16 | public override int Execute() 17 | { 18 | var package = PackageLoader.Load(InputPath); 19 | 20 | File.WriteAllText(OutputPath, JsonConvert.SerializeObject(package, new JsonSerializerSettings 21 | { 22 | Formatting = Formatting.Indented, 23 | Converters = new List 24 | { 25 | new StringEnumConverter() 26 | }, 27 | TypeNameHandling = TypeNameHandling.Auto, 28 | ReferenceLoopHandling = ReferenceLoopHandling.Error, 29 | PreserveReferencesHandling = PreserveReferencesHandling.Objects, 30 | NullValueHandling = NullValueHandling.Ignore 31 | })); 32 | 33 | return 0; 34 | } 35 | } -------------------------------------------------------------------------------- /FEngCli/FEngCli.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net6.0 6 | heyitsleo;Fridtjof 7 | NFSTools 8 | CLI for de/recompiling FNG binary chunks 9 | 0.2.0-alpha 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /FEngCli/PackageLoader.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using FEngLib.Packages; 3 | 4 | namespace FEngCli; 5 | 6 | public static class PackageLoader 7 | { 8 | public static Package Load(string path) 9 | { 10 | using var fs = new FileStream(path, FileMode.Open); 11 | using var fr = new BinaryReader(fs); 12 | var marker = fr.ReadUInt32(); 13 | switch (marker) 14 | { 15 | case 0x30203: 16 | fs.Seek(0x10, SeekOrigin.Begin); 17 | break; 18 | case 0xE76E4546: 19 | fs.Seek(0x8, SeekOrigin.Begin); 20 | break; 21 | default: 22 | throw new InvalidDataException($"Invalid FEng chunk file: {path}"); 23 | } 24 | 25 | using var ms = new MemoryStream(); 26 | fs.CopyTo(ms); 27 | ms.Position = 0; 28 | 29 | using var mr = new BinaryReader(ms); 30 | return new FrontendPackageLoader().Load(mr); 31 | } 32 | } -------------------------------------------------------------------------------- /FEngCli/Program.cs: -------------------------------------------------------------------------------- 1 | using CommandLine; 2 | 3 | namespace FEngCli; 4 | 5 | internal static class Program 6 | { 7 | private static int Main(string[] args) 8 | { 9 | return Parser.Default 10 | .ParseArguments(args, typeof(DecompileCommand), typeof(CompileCommand)) 11 | .MapResult((BaseCommand bc) => bc.Execute(), errs => 1); 12 | } 13 | } -------------------------------------------------------------------------------- /FEngLib.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.29728.190 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FEngLib", "FEngLib\FEngLib.csproj", "{52976D61-7019-4B7C-B939-835E35739063}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FEngCli", "FEngCli\FEngCli.csproj", "{146FE215-25BC-4AA6-9546-B6E8CC9C163A}" 9 | EndProject 10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FEngTestBench", "FEngTestBench\FEngTestBench.csproj", "{A6E4B7E2-A217-4EE5-B2A4-9CFCF7ABE084}" 11 | EndProject 12 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FEngRender", "FEngRender\FEngRender.csproj", "{CBCF4BDA-B2F5-490C-9ECF-69E0EDC0D3CC}" 13 | EndProject 14 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FEngViewer", "FEngViewer\FEngViewer.csproj", "{41331FD7-E2D2-4CA7-86D8-774F03DB6409}" 15 | EndProject 16 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FEngRender.GL", "FEngRender.GL\FEngRender.GL.csproj", "{7A41DB9F-2F8C-4F8F-8296-814FCD953318}" 17 | EndProject 18 | Global 19 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 20 | Debug|Any CPU = Debug|Any CPU 21 | Release|Any CPU = Release|Any CPU 22 | EndGlobalSection 23 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 24 | {52976D61-7019-4B7C-B939-835E35739063}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 25 | {52976D61-7019-4B7C-B939-835E35739063}.Debug|Any CPU.Build.0 = Debug|Any CPU 26 | {52976D61-7019-4B7C-B939-835E35739063}.Release|Any CPU.ActiveCfg = Release|Any CPU 27 | {52976D61-7019-4B7C-B939-835E35739063}.Release|Any CPU.Build.0 = Release|Any CPU 28 | {146FE215-25BC-4AA6-9546-B6E8CC9C163A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 29 | {146FE215-25BC-4AA6-9546-B6E8CC9C163A}.Debug|Any CPU.Build.0 = Debug|Any CPU 30 | {146FE215-25BC-4AA6-9546-B6E8CC9C163A}.Release|Any CPU.ActiveCfg = Release|Any CPU 31 | {146FE215-25BC-4AA6-9546-B6E8CC9C163A}.Release|Any CPU.Build.0 = Release|Any CPU 32 | {A6E4B7E2-A217-4EE5-B2A4-9CFCF7ABE084}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 33 | {A6E4B7E2-A217-4EE5-B2A4-9CFCF7ABE084}.Debug|Any CPU.Build.0 = Debug|Any CPU 34 | {A6E4B7E2-A217-4EE5-B2A4-9CFCF7ABE084}.Release|Any CPU.ActiveCfg = Release|Any CPU 35 | {A6E4B7E2-A217-4EE5-B2A4-9CFCF7ABE084}.Release|Any CPU.Build.0 = Release|Any CPU 36 | {CBCF4BDA-B2F5-490C-9ECF-69E0EDC0D3CC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 37 | {CBCF4BDA-B2F5-490C-9ECF-69E0EDC0D3CC}.Debug|Any CPU.Build.0 = Debug|Any CPU 38 | {CBCF4BDA-B2F5-490C-9ECF-69E0EDC0D3CC}.Release|Any CPU.ActiveCfg = Release|Any CPU 39 | {CBCF4BDA-B2F5-490C-9ECF-69E0EDC0D3CC}.Release|Any CPU.Build.0 = Release|Any CPU 40 | {41331FD7-E2D2-4CA7-86D8-774F03DB6409}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 41 | {41331FD7-E2D2-4CA7-86D8-774F03DB6409}.Debug|Any CPU.Build.0 = Debug|Any CPU 42 | {41331FD7-E2D2-4CA7-86D8-774F03DB6409}.Release|Any CPU.ActiveCfg = Release|Any CPU 43 | {41331FD7-E2D2-4CA7-86D8-774F03DB6409}.Release|Any CPU.Build.0 = Release|Any CPU 44 | {7A41DB9F-2F8C-4F8F-8296-814FCD953318}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 45 | {7A41DB9F-2F8C-4F8F-8296-814FCD953318}.Debug|Any CPU.Build.0 = Debug|Any CPU 46 | {7A41DB9F-2F8C-4F8F-8296-814FCD953318}.Release|Any CPU.ActiveCfg = Release|Any CPU 47 | {7A41DB9F-2F8C-4F8F-8296-814FCD953318}.Release|Any CPU.Build.0 = Release|Any CPU 48 | EndGlobalSection 49 | GlobalSection(SolutionProperties) = preSolution 50 | HideSolutionNode = FALSE 51 | EndGlobalSection 52 | GlobalSection(ExtensibilityGlobals) = postSolution 53 | SolutionGuid = {3BBC7AAD-156D-4FAF-B5AB-3347E5B63A6D} 54 | EndGlobalSection 55 | EndGlobal 56 | -------------------------------------------------------------------------------- /FEngLib/Chunks/ButtonMapCountChunk.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using FEngLib.Packages; 3 | 4 | namespace FEngLib.Chunks; 5 | 6 | public class ButtonMapCountChunk : FrontendChunk 7 | { 8 | public uint NumEntries { get; set; } 9 | 10 | public override void Read(Package package, FrontendChunkBlock chunkBlock, 11 | FrontendChunkReader chunkReader, BinaryReader reader) 12 | { 13 | NumEntries = reader.ReadUInt32(); 14 | } 15 | 16 | public override FrontendChunkType GetChunkType() 17 | { 18 | return FrontendChunkType.ButtonMapCount; 19 | } 20 | } -------------------------------------------------------------------------------- /FEngLib/Chunks/ChunkReadingException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.Serialization; 3 | 4 | namespace FEngLib.Chunks; 5 | 6 | [Serializable] 7 | public class ChunkReadingException : Exception 8 | { 9 | // 10 | // For guidelines regarding the creation of new exception types, see 11 | // http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpgenref/html/cpconerrorraisinghandlingguidelines.asp 12 | // and 13 | // http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dncscol/html/csharp07192001.asp 14 | // 15 | 16 | public ChunkReadingException() 17 | { 18 | } 19 | 20 | public ChunkReadingException(string message) : base(message) 21 | { 22 | } 23 | 24 | public ChunkReadingException(string message, Exception inner) : base(message, inner) 25 | { 26 | } 27 | 28 | protected ChunkReadingException( 29 | SerializationInfo info, 30 | StreamingContext context) : base(info, context) 31 | { 32 | } 33 | } -------------------------------------------------------------------------------- /FEngLib/Chunks/EventDataChunk.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using FEngLib.Packages; 3 | 4 | namespace FEngLib.Chunks; 5 | 6 | public class EventDataChunk : FrontendChunk 7 | { 8 | public override void Read(Package package, FrontendChunkBlock chunkBlock, 9 | FrontendChunkReader chunkReader, BinaryReader reader) 10 | { 11 | reader.ReadBytes(chunkBlock.Size); 12 | } 13 | 14 | public override FrontendChunkType GetChunkType() 15 | { 16 | return FrontendChunkType.EventData; 17 | } 18 | } -------------------------------------------------------------------------------- /FEngLib/Chunks/FrontendObjectContainerChunk.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using FEngLib.Objects; 3 | using FEngLib.Packages; 4 | 5 | namespace FEngLib.Chunks; 6 | 7 | public class FrontendObjectContainerChunk : FrontendObjectChunk 8 | { 9 | public override IObject Read(Package package, ObjectReaderState readerState, BinaryReader reader) 10 | { 11 | return readerState.ChunkReader.ReadFrontendObjectChunks(FrontendObject, readerState.CurrentChunkBlock.Size); 12 | } 13 | 14 | public override FrontendChunkType GetChunkType() 15 | { 16 | return FrontendChunkType.FrontendObjectContainer; 17 | } 18 | 19 | public FrontendObjectContainerChunk(IObject frontendObject) : base(frontendObject) 20 | { 21 | } 22 | } -------------------------------------------------------------------------------- /FEngLib/Chunks/MessageDefinitionsChunk.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.IO; 3 | using FEngLib.Packages; 4 | 5 | namespace FEngLib.Chunks; 6 | 7 | public class MessageDefinitionsChunk : FrontendChunk 8 | { 9 | public List Definitions { get; set; } = 10 | new List(); 11 | 12 | public override void Read(Package package, FrontendChunkBlock chunkBlock, 13 | FrontendChunkReader chunkReader, BinaryReader reader) 14 | { 15 | while (reader.BaseStream.Position < chunkBlock.EndOffset) 16 | { 17 | var tagId = reader.ReadUInt16(); 18 | var tagLen = reader.ReadUInt16(); 19 | 20 | switch (tagId) 21 | { 22 | case 0x4E4D: 23 | Definitions.Add(new Package.MessageDefinition 24 | { 25 | Name = new string(reader.ReadChars(tagLen)).Trim('\x00') 26 | }); 27 | break; 28 | case 0x434D: 29 | Definitions[^1].Category = new string(reader.ReadChars(tagLen)).Trim('\x00'); 30 | break; 31 | } 32 | } 33 | } 34 | 35 | public override FrontendChunkType GetChunkType() 36 | { 37 | return FrontendChunkType.MessageDefinitions; 38 | } 39 | } -------------------------------------------------------------------------------- /FEngLib/Chunks/MessageResponsesDataChunk.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using FEngLib.Messaging; 3 | using FEngLib.Objects; 4 | using FEngLib.Packages; 5 | using FEngLib.Tags; 6 | 7 | namespace FEngLib.Chunks; 8 | 9 | public class MessageResponsesDataChunk : FrontendObjectChunk 10 | { 11 | public MessageResponsesDataChunk(IObject frontendObject) : base(frontendObject) 12 | { 13 | } 14 | 15 | public override IObject Read(Package package, ObjectReaderState readerState, BinaryReader reader) 16 | { 17 | var tagProcessor = new MessageResponseTagProcessor(); 18 | TagStream tagStream = new MessageTagStream(reader, 19 | readerState.CurrentChunkBlock.Size); 20 | 21 | while (tagStream.HasTag()) 22 | { 23 | var tag = tagStream.NextTag(); 24 | tagProcessor.ProcessTag(tag); 25 | } 26 | 27 | ResponseHelpers.PopulateMessageResponseList(tagProcessor.MessageResponseEntryList, FrontendObject); 28 | 29 | return FrontendObject; 30 | } 31 | 32 | public override FrontendChunkType GetChunkType() 33 | { 34 | return FrontendChunkType.MessageResponses; 35 | } 36 | } -------------------------------------------------------------------------------- /FEngLib/Chunks/ObjectContainerChunk.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using FEngLib.Packages; 3 | 4 | namespace FEngLib.Chunks; 5 | 6 | public class ObjectContainerChunk : FrontendChunk 7 | { 8 | public override void Read(Package package, FrontendChunkBlock chunkBlock, FrontendChunkReader chunkReader, 9 | BinaryReader reader) 10 | { 11 | chunkReader.ReadObjects(chunkBlock.Size); 12 | } 13 | 14 | public override FrontendChunkType GetChunkType() 15 | { 16 | return FrontendChunkType.ObjectContainer; 17 | } 18 | } -------------------------------------------------------------------------------- /FEngLib/Chunks/PackageHeaderChunk.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using FEngLib.Packages; 3 | using FEngLib.Utils; 4 | 5 | namespace FEngLib.Chunks; 6 | 7 | public class PackageHeaderChunk : FrontendChunk 8 | { 9 | /// 10 | /// The number of entries in the RsNm chunk 11 | /// 12 | public int NumResourceNames { get; set; } 13 | 14 | /// 15 | /// The name of the package 16 | /// 17 | public string Name { get; set; } 18 | 19 | /// 20 | /// The filename of the package 21 | /// 22 | public string Filename { get; set; } 23 | 24 | public override void Read(Package package, FrontendChunkBlock chunkBlock, 25 | FrontendChunkReader chunkReader, BinaryReader reader) 26 | { 27 | if (reader.ReadUInt32() != 0x20000) throw new InvalidDataException("Invalid header constant"); 28 | 29 | if (reader.ReadUInt32() != 0) throw new InvalidDataException("Expected null after constant"); 30 | 31 | NumResourceNames = reader.ReadInt32(); 32 | 33 | reader.ReadUInt32(); 34 | //if (reader.ReadUInt32() != 3) 35 | //{ 36 | // throw new InvalidDataException("Invalid header version"); 37 | //} 38 | 39 | var nameLength = reader.ReadInt32(); 40 | 41 | if (nameLength < 1) throw new InvalidDataException("Invalid name length"); 42 | 43 | var filenameLength = reader.ReadInt32(); 44 | 45 | if (filenameLength < 1) throw new InvalidDataException("Invalid filename length"); 46 | 47 | Name = new string(reader.ReadChars(nameLength)).Trim('\0'); 48 | Filename = new string(reader.ReadChars(filenameLength)).Trim('\0'); 49 | 50 | reader.AlignReader(4); 51 | } 52 | 53 | public override FrontendChunkType GetChunkType() 54 | { 55 | return FrontendChunkType.PackageHeader; 56 | } 57 | } -------------------------------------------------------------------------------- /FEngLib/Chunks/PackageMessageTargetsChunk.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using FEngLib.Messaging; 4 | using FEngLib.Packages; 5 | using FEngLib.Packages.Tags; 6 | using FEngLib.Tags; 7 | 8 | namespace FEngLib.Chunks; 9 | 10 | public class PackageMessageTargetsChunk : FrontendChunk 11 | { 12 | public override void Read(Package package, FrontendChunkBlock chunkBlock, 13 | FrontendChunkReader chunkReader, BinaryReader reader) 14 | { 15 | TagStream tagStream = new MessageTagStream(reader, 16 | chunkBlock.Size); 17 | 18 | while (tagStream.HasTag()) 19 | { 20 | var tag = tagStream.NextTag(); 21 | ProcessTag(tag); 22 | } 23 | } 24 | 25 | private void ProcessTag(Tag tag) 26 | { 27 | switch (tag) 28 | { 29 | // These are not really important to us. They are also trivial to regenerate. 30 | case MessageTargetCountTag: 31 | case MessageTargetListTag: 32 | break; 33 | default: 34 | throw new Exception($"Unexpected tag in MessageTargets: {tag.GetType()}"); 35 | } 36 | } 37 | 38 | public override FrontendChunkType GetChunkType() 39 | { 40 | return FrontendChunkType.Targets; 41 | } 42 | } -------------------------------------------------------------------------------- /FEngLib/Chunks/PackageResponsesChunk.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using FEngLib.Messaging; 3 | using FEngLib.Packages; 4 | using FEngLib.Tags; 5 | 6 | namespace FEngLib.Chunks; 7 | 8 | public class PackageResponsesChunk : FrontendChunk 9 | { 10 | public override void Read(Package package, FrontendChunkBlock chunkBlock, 11 | FrontendChunkReader chunkReader, BinaryReader reader) 12 | { 13 | var tagProcessor = new MessageResponseTagProcessor(); 14 | TagStream tagStream = new MessageTagStream(reader, 15 | chunkBlock.Size); 16 | 17 | while (tagStream.HasTag()) 18 | { 19 | var tag = tagStream.NextTag(); 20 | tagProcessor.ProcessTag(tag); 21 | } 22 | 23 | ResponseHelpers.PopulateMessageResponseList(tagProcessor.MessageResponseEntryList, package); 24 | } 25 | 26 | public override FrontendChunkType GetChunkType() 27 | { 28 | return FrontendChunkType.PackageResponses; 29 | } 30 | } -------------------------------------------------------------------------------- /FEngLib/Chunks/ResourceNamesChunk.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.IO; 3 | using FEngLib.Packages; 4 | using FEngLib.Utils; 5 | 6 | namespace FEngLib.Chunks; 7 | 8 | public class ResourceNamesChunk : FrontendChunk 9 | { 10 | public Dictionary Names { get; set; } 11 | 12 | public override void Read(Package package, FrontendChunkBlock chunkBlock, 13 | FrontendChunkReader chunkReader, BinaryReader reader) 14 | { 15 | Names = new Dictionary(); 16 | 17 | while (reader.BaseStream.Position < chunkBlock.EndOffset) 18 | { 19 | var pos = reader.BaseStream.Position; 20 | var str = NullTerminatedString.Read(reader); 21 | if (!string.IsNullOrEmpty(str)) 22 | Names[pos - chunkBlock.DataOffset] = str; 23 | } 24 | } 25 | 26 | public override FrontendChunkType GetChunkType() 27 | { 28 | return FrontendChunkType.ResourceNames; 29 | } 30 | } -------------------------------------------------------------------------------- /FEngLib/Chunks/ResourceRequestsChunk.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using FEngLib.Packages; 5 | 6 | namespace FEngLib.Chunks; 7 | 8 | public class ResourceRequestsChunk : FrontendChunk 9 | { 10 | private uint[] _nameOffsets; 11 | 12 | public List ResourceRequests { get; set; } 13 | 14 | public override void Read(Package package, FrontendChunkBlock chunkBlock, 15 | FrontendChunkReader chunkReader, BinaryReader reader) 16 | { 17 | if ((chunkBlock.Size - 4) % 0x18 != 0) throw new ChunkReadingException("Malformed RsRq chunk"); 18 | 19 | var numRequests = reader.ReadInt32(); 20 | ResourceRequests = new List(numRequests); 21 | 22 | _nameOffsets = new uint[numRequests]; 23 | 24 | for (var i = 0; i < numRequests; i++) 25 | { 26 | var resourceRequest = new ResourceRequest(); 27 | resourceRequest.ID = reader.ReadUInt32(); 28 | _nameOffsets[i] = reader.ReadUInt32(); 29 | resourceRequest.Type = (ResourceType)reader.ReadUInt32(); 30 | //resourceRequest.Flags = reader.ReadUInt32(); 31 | reader.ReadUInt32(); 32 | reader.ReadUInt32(); 33 | reader.ReadUInt32(); 34 | //resourceRequest.UserParam = reader.ReadUInt32(); 35 | 36 | ResourceRequests.Add(resourceRequest); 37 | } 38 | } 39 | 40 | public override FrontendChunkType GetChunkType() 41 | { 42 | throw new NotImplementedException(); 43 | } 44 | 45 | public uint GetNameOffset(int i) 46 | { 47 | return _nameOffsets[i]; 48 | } 49 | } -------------------------------------------------------------------------------- /FEngLib/Chunks/ResourcesContainerChunk.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using FEngLib.Packages; 3 | 4 | namespace FEngLib.Chunks; 5 | 6 | public class ResourcesContainerChunk : FrontendChunk 7 | { 8 | public override void Read(Package package, FrontendChunkBlock chunkBlock, 9 | FrontendChunkReader chunkReader, BinaryReader reader) 10 | { 11 | ResourceNamesChunk resourceNamesChunk = null; 12 | 13 | foreach (var chunk in chunkReader.ReadMainChunks(chunkBlock.Size)) 14 | { 15 | switch (chunk) 16 | { 17 | case ResourceNamesChunk rnc: 18 | resourceNamesChunk = rnc; 19 | break; 20 | case ResourceRequestsChunk _ when resourceNamesChunk == null: 21 | throw new ChunkReadingException("RsRq came before RsNm?!"); 22 | case ResourceRequestsChunk rrc: 23 | { 24 | for (var index = 0; index < rrc.ResourceRequests.Count; index++) 25 | { 26 | var resourceRequest = rrc.ResourceRequests[index]; 27 | var nameOffset = rrc.GetNameOffset(index); 28 | resourceRequest.Name = resourceNamesChunk.Names[nameOffset]; 29 | } 30 | 31 | package.ResourceRequests.AddRange(rrc.ResourceRequests); 32 | 33 | break; 34 | } 35 | } 36 | } 37 | } 38 | 39 | public override FrontendChunkType GetChunkType() 40 | { 41 | return FrontendChunkType.ResourcesContainer; 42 | } 43 | } -------------------------------------------------------------------------------- /FEngLib/Chunks/TypeInfoListChunk.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using FEngLib.Packages; 3 | 4 | namespace FEngLib.Chunks; 5 | 6 | public class TypeInfoListChunk : FrontendChunk 7 | { 8 | public override void Read(Package package, FrontendChunkBlock chunkBlock, 9 | FrontendChunkReader chunkReader, BinaryReader reader) 10 | { 11 | reader.ReadBytes(chunkBlock.Size); 12 | } 13 | 14 | public override FrontendChunkType GetChunkType() 15 | { 16 | return FrontendChunkType.TypeInfoList; 17 | } 18 | } -------------------------------------------------------------------------------- /FEngLib/Chunks/TypeListChunk.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.IO; 3 | using FEngLib.Packages; 4 | 5 | namespace FEngLib.Chunks; 6 | 7 | public class TypeListChunk : FrontendChunk 8 | { 9 | public List TypeSizeList { get; set; } 10 | 11 | public override void Read(Package package, FrontendChunkBlock chunkBlock, 12 | FrontendChunkReader chunkReader, BinaryReader reader) 13 | { 14 | if (chunkBlock.Size % 8 != 0) 15 | { 16 | throw new ChunkReadingException("Invalid TypeList chunk"); 17 | } 18 | 19 | TypeSizeList = new List(chunkBlock.Size >> 3); 20 | 21 | for (int i = 0; i < chunkBlock.Size >> 3; i++) 22 | { 23 | TypeSizeList.Add(new TypeSizeEntry 24 | { 25 | ID = reader.ReadUInt32(), 26 | Size = reader.ReadUInt32() 27 | }); 28 | } 29 | } 30 | 31 | public override FrontendChunkType GetChunkType() 32 | { 33 | return FrontendChunkType.TypeList; 34 | } 35 | } -------------------------------------------------------------------------------- /FEngLib/FEngLib.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net6.0 5 | heyitsleo;Fridtjof 6 | NFSTools 7 | Library for reading and writing FNGs 8 | 2.0.0-alpha 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /FEngLib/FrontendChunk.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using FEngLib.Packages; 4 | 5 | namespace FEngLib; 6 | 7 | public abstract class FrontendChunk 8 | { 9 | public abstract void Read(Package package, FrontendChunkBlock chunkBlock, 10 | FrontendChunkReader chunkReader, BinaryReader reader); 11 | 12 | public virtual void Write(Package package, 13 | FrontendChunkWriter chunkWriter, BinaryWriter writer) 14 | { 15 | throw new Exception(); 16 | } 17 | 18 | public abstract FrontendChunkType GetChunkType(); 19 | } -------------------------------------------------------------------------------- /FEngLib/FrontendChunkBlock.cs: -------------------------------------------------------------------------------- 1 | namespace FEngLib; 2 | 3 | public class FrontendChunkBlock 4 | { 5 | /// 6 | /// The type of the chunk 7 | /// 8 | public FrontendChunkType ChunkType { get; set; } 9 | 10 | /// 11 | /// The length of the chunk (excluding header) 12 | /// 13 | public int Size { get; set; } 14 | 15 | /// 16 | /// The offset of the chunk (before header) 17 | /// 18 | public long Offset { get; set; } 19 | 20 | /// 21 | /// The data offset of the chunk 22 | /// 23 | public long DataOffset => Offset + 8; 24 | 25 | /// 26 | /// The end offset of the chunk 27 | /// 28 | public long EndOffset => Offset + 8 + Size; 29 | } -------------------------------------------------------------------------------- /FEngLib/FrontendChunkType.cs: -------------------------------------------------------------------------------- 1 | namespace FEngLib; 2 | 3 | public enum FrontendChunkType : uint 4 | { 5 | PackageStart = 0xE76E4546, // FEn\xE7 6 | PackageHeader = 0x64486B50, // PkHd 7 | TypeList = 0x53707954, // TypS 8 | ResourcesContainer = 0xCC736552, // Res\xCC 9 | ResourceNames = 0x6D4E7352, // RsNm 10 | ResourceRequests = 0x71527352, // RsRq 11 | ObjectContainer = 0xCC6A624F, // Obj\xCC 12 | ButtonMapCount = 0x6E747542, // Butn 13 | FrontendObjectContainer = 0xEA624F46, // Obj\xEA 14 | ObjectData = 0x446A624F, // ObjD 15 | ScriptData = 0x70726353, // Scrp 16 | MessageResponses = 0x5267734D, // MsgR 17 | PackageResponses = 0x52676B50, // PkgR 18 | Targets = 0x67726154, // Targ 19 | TypeInfoList = 0xF4734C54, // TLs\xF4 20 | MessageDefinitions = 0x4E67734D, // MsgN 21 | EventData = 0x74614445 // EDat 22 | } -------------------------------------------------------------------------------- /FEngLib/FrontendObjectChunk.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using FEngLib.Objects; 3 | using FEngLib.Packages; 4 | 5 | namespace FEngLib; 6 | 7 | public abstract class FrontendObjectChunk 8 | { 9 | protected IObject FrontendObject { get; } 10 | 11 | protected FrontendObjectChunk(IObject frontendObject) 12 | { 13 | FrontendObject = frontendObject; 14 | } 15 | 16 | public abstract IObject Read(Package package, ObjectReaderState readerState, BinaryReader reader); 17 | public abstract FrontendChunkType GetChunkType(); 18 | } -------------------------------------------------------------------------------- /FEngLib/FrontendTagType.cs: -------------------------------------------------------------------------------- 1 | namespace FEngLib; 2 | 3 | public enum FrontendTagType : ushort 4 | { 5 | // part of ObjD 6 | ObjectType = 0x744F, // Ot 7 | ObjectHash = 0x684F, // Oh 8 | ObjectReference = 0x504F, // OP 9 | ImageInfo = 0x6649, // If 10 | ObjectData = 0x4153, // SA 11 | ObjectParent = 0x4150, // PA 12 | StringBufferLength = 0x6253, // Sb 13 | StringBufferText = 0x7453, // St 14 | StringBufferFormatting = 0x6A53, // Sj 15 | StringBufferLeading = 0x6C53, // Sl 16 | StringBufferMaxWidth = 0x7753, // Sw 17 | StringBufferLabelHash = 0x4853, // SH 18 | MultiImageTexture1 = 0x314D, // M1 19 | MultiImageTexture2 = 0x324D, // M2 20 | MultiImageTexture3 = 0x334D, // M3 21 | MultiImageTextureFlags1 = 0x614D, // Ma 22 | MultiImageTextureFlags2 = 0x624D, // Mb 23 | MultiImageTextureFlags3 = 0x634D, // Mc 24 | ObjectName = 0x6E4F, // On 25 | StringBufferLabel = 0x4C53, // SL 26 | 27 | // part of Scrp 28 | ScriptHeader = 0x6853, // Sh 29 | ScriptChain = 0x6353, // Sc 30 | ScriptKeyTrack = 0x4946, // FI 31 | ScriptTrackOffset = 0x6F54, // To 32 | ScriptKeyNode = 0x644B, // Kd 33 | ScriptEvents = 0x5645, // EV 34 | ScriptName = 0x6E53, // Sn 35 | 36 | // part of Message related chunks (MsgR, PkgR, Targ) 37 | MessageResponseInfo = 0x694D, // Mi 38 | MessageResponseCount = 0x434D, // MC 39 | ResponseId = 0x6952, // Ri 40 | ResponseIntParam = 0x7552, // Ru 41 | ResponseStringParam = 0x7352, // Rs 42 | ResponseTarget = 0x7452, // Rt 43 | MessageTargetCount = 0x6354, // Tc 44 | MessageTargetList = 0x744D, // Mt 45 | } -------------------------------------------------------------------------------- /FEngLib/Messaging/Commands/Else.cs: -------------------------------------------------------------------------------- 1 | namespace FEngLib.Messaging.Commands; 2 | 3 | public class Else : NonParameterizedCommand 4 | { 5 | public override uint GetId() 6 | { 7 | return (uint)ResponseHelpers.FEMessageResponseCommands.MR_Else; 8 | } 9 | 10 | public override string GetCommandName() 11 | { 12 | return "Else"; 13 | } 14 | 15 | public override string ToString() 16 | { 17 | return GetCommandName(); 18 | } 19 | } -------------------------------------------------------------------------------- /FEngLib/Messaging/Commands/EndIf.cs: -------------------------------------------------------------------------------- 1 | namespace FEngLib.Messaging.Commands; 2 | 3 | public class EndIf : NonParameterizedCommand 4 | { 5 | public override uint GetId() 6 | { 7 | return (uint)ResponseHelpers.FEMessageResponseCommands.MR_EndIf; 8 | } 9 | 10 | public override string GetCommandName() 11 | { 12 | return "EndIf"; 13 | } 14 | 15 | public override string ToString() 16 | { 17 | return GetCommandName(); 18 | } 19 | } -------------------------------------------------------------------------------- /FEngLib/Messaging/Commands/IfScriptEquals.cs: -------------------------------------------------------------------------------- 1 | namespace FEngLib.Messaging.Commands; 2 | 3 | public class IfScriptEquals : ScriptCommand 4 | { 5 | public IfScriptEquals(uint scriptHash) : base(scriptHash) 6 | { 7 | } 8 | 9 | public override uint GetId() 10 | { 11 | return (uint)ResponseHelpers.FEMessageResponseCommands.MR_IfScriptEquals; 12 | } 13 | 14 | public override string GetCommandName() 15 | { 16 | return "IfScriptEquals"; 17 | } 18 | } -------------------------------------------------------------------------------- /FEngLib/Messaging/Commands/IfScriptNotEquals.cs: -------------------------------------------------------------------------------- 1 | namespace FEngLib.Messaging.Commands; 2 | 3 | public class IfScriptNotEquals : ScriptCommand 4 | { 5 | public IfScriptNotEquals(uint scriptHash) : base(scriptHash) 6 | { 7 | } 8 | 9 | public override uint GetId() 10 | { 11 | return (uint)ResponseHelpers.FEMessageResponseCommands.MR_IfScriptNotEquals; 12 | } 13 | 14 | public override string GetCommandName() 15 | { 16 | return "IfScriptNotEquals"; 17 | } 18 | } -------------------------------------------------------------------------------- /FEngLib/Messaging/Commands/PopPackage.cs: -------------------------------------------------------------------------------- 1 | namespace FEngLib.Messaging.Commands; 2 | 3 | public class PopPackage : NonParameterizedCommand 4 | { 5 | public override uint GetId() 6 | { 7 | return (uint)ResponseHelpers.FEMessageResponseCommands.MR_PopPackage; 8 | } 9 | 10 | public override string GetCommandName() 11 | { 12 | return "PopPackage"; 13 | } 14 | } -------------------------------------------------------------------------------- /FEngLib/Messaging/Commands/PostMessageToFEng.cs: -------------------------------------------------------------------------------- 1 | namespace FEngLib.Messaging.Commands; 2 | 3 | public class PostMessageToFEng : MessageCommand, ITargetedCommand 4 | { 5 | public uint Target { get; set; } 6 | 7 | public PostMessageToFEng(uint messageHash, uint target) : base(messageHash) 8 | { 9 | Target = target; 10 | } 11 | 12 | public override uint GetId() 13 | { 14 | return (uint)ResponseHelpers.FEMessageResponseCommands.MR_PostMessageToFEng; 15 | } 16 | 17 | public override string GetCommandName() 18 | { 19 | return "PostMessageToFEng"; 20 | } 21 | 22 | public override string ToString() 23 | { 24 | return $"{GetCommandName()}(0x{Target:X}, 0x{MessageHash:X8})"; 25 | } 26 | } -------------------------------------------------------------------------------- /FEngLib/Messaging/Commands/PostMessageToGame.cs: -------------------------------------------------------------------------------- 1 | namespace FEngLib.Messaging.Commands; 2 | 3 | public class PostMessageToGame : MessageCommand 4 | { 5 | public override uint GetId() 6 | { 7 | return (uint)ResponseHelpers.FEMessageResponseCommands.MR_PostMessageToGame; 8 | } 9 | 10 | public override string GetCommandName() 11 | { 12 | return "PostMessageToGame"; 13 | } 14 | 15 | public override string ToString() 16 | { 17 | return $"{GetCommandName()}(0x{MessageHash:X8})"; 18 | } 19 | 20 | public PostMessageToGame(uint messageHash) : base(messageHash) 21 | { 22 | } 23 | } -------------------------------------------------------------------------------- /FEngLib/Messaging/Commands/PostMessageToSound.cs: -------------------------------------------------------------------------------- 1 | namespace FEngLib.Messaging.Commands; 2 | 3 | public class PostMessageToSound : MessageCommand, ITargetedCommand 4 | { 5 | public PostMessageToSound(uint messageHash, uint target) : base(messageHash) 6 | { 7 | Target = target; 8 | } 9 | 10 | public override uint GetId() 11 | { 12 | return (uint)ResponseHelpers.FEMessageResponseCommands.MR_PostMessageToSound; 13 | } 14 | 15 | public override string GetCommandName() 16 | { 17 | return "PostMessageToSound"; 18 | } 19 | 20 | public override string ToString() 21 | { 22 | return $"{GetCommandName()}(0x{Target:X}, 0x{MessageHash:X8})"; 23 | } 24 | 25 | public uint Target 26 | { 27 | get; 28 | set; 29 | } 30 | } -------------------------------------------------------------------------------- /FEngLib/Messaging/Commands/PushPackageGlobal.cs: -------------------------------------------------------------------------------- 1 | namespace FEngLib.Messaging.Commands; 2 | 3 | public class PushPackageGlobal : PackageCommand 4 | { 5 | public PushPackageGlobal(string packageName) : base(packageName) 6 | { 7 | } 8 | 9 | public override uint GetId() 10 | { 11 | return (uint)ResponseHelpers.FEMessageResponseCommands.MR_PushPackageGlobal; 12 | } 13 | 14 | public override string GetCommandName() 15 | { 16 | return "PushPackageGlobal"; 17 | } 18 | } -------------------------------------------------------------------------------- /FEngLib/Messaging/Commands/RecallRecordedButton.cs: -------------------------------------------------------------------------------- 1 | namespace FEngLib.Messaging.Commands; 2 | 3 | public class RecallRecordedButton : ObjectCommand 4 | { 5 | public RecallRecordedButton(uint objectGuid) : base(objectGuid) 6 | { 7 | } 8 | 9 | public override uint GetId() 10 | { 11 | return (uint)ResponseHelpers.FEMessageResponseCommands.MR_RecallRecordedButton; 12 | } 13 | 14 | public override string GetCommandName() 15 | { 16 | return "RecallRecordedButton"; 17 | } 18 | } -------------------------------------------------------------------------------- /FEngLib/Messaging/Commands/RecordCurrentButton.cs: -------------------------------------------------------------------------------- 1 | namespace FEngLib.Messaging.Commands; 2 | 3 | public class RecordCurrentButton : NonParameterizedCommand 4 | { 5 | public override uint GetId() 6 | { 7 | return (uint)ResponseHelpers.FEMessageResponseCommands.MR_RecordCurrentButton; 8 | } 9 | 10 | public override string GetCommandName() 11 | { 12 | return "RecordCurrentButton"; 13 | } 14 | 15 | public override string ToString() 16 | { 17 | return $"{GetCommandName()}()"; 18 | } 19 | } -------------------------------------------------------------------------------- /FEngLib/Messaging/Commands/SetActiveButton.cs: -------------------------------------------------------------------------------- 1 | namespace FEngLib.Messaging.Commands; 2 | 3 | public class SetActiveButton : ObjectCommand 4 | { 5 | public SetActiveButton(uint objectGuid) : base(objectGuid) 6 | { 7 | } 8 | 9 | public override uint GetId() 10 | { 11 | return (uint)ResponseHelpers.FEMessageResponseCommands.MR_SetActiveButton; 12 | } 13 | 14 | public override string GetCommandName() 15 | { 16 | return "SetActiveButton"; 17 | } 18 | } -------------------------------------------------------------------------------- /FEngLib/Messaging/Commands/SetInputProcessing.cs: -------------------------------------------------------------------------------- 1 | namespace FEngLib.Messaging.Commands; 2 | 3 | public class SetInputProcessing : ResponseCommand, IIntegerCommand 4 | { 5 | public bool Enabled { get; private set; } 6 | 7 | public SetInputProcessing(bool enabled) : this(enabled ? 1u : 0u) 8 | { 9 | } 10 | 11 | internal SetInputProcessing(uint parameter) 12 | { 13 | SetParameter(parameter); 14 | } 15 | 16 | public override uint GetId() 17 | { 18 | return (uint)ResponseHelpers.FEMessageResponseCommands.MR_SetInputProcessing; 19 | } 20 | 21 | public override string GetCommandName() 22 | { 23 | return "SetInputProcessing"; 24 | } 25 | 26 | public override string ToString() 27 | { 28 | return $"{GetCommandName()}({Enabled})"; 29 | } 30 | 31 | public uint GetParameter() 32 | { 33 | return Enabled ? 1u : 0u; 34 | } 35 | 36 | public void SetParameter(uint parameter) 37 | { 38 | Enabled = parameter == 1; 39 | } 40 | } -------------------------------------------------------------------------------- /FEngLib/Messaging/Commands/SetScript.cs: -------------------------------------------------------------------------------- 1 | namespace FEngLib.Messaging.Commands; 2 | 3 | public class SetScript : ScriptCommand 4 | { 5 | public SetScript(uint scriptHash) : base(scriptHash) 6 | { 7 | } 8 | 9 | public override uint GetId() 10 | { 11 | return (uint)ResponseHelpers.FEMessageResponseCommands.MR_SetScript; 12 | } 13 | 14 | public override string GetCommandName() 15 | { 16 | return "SetScript"; 17 | } 18 | 19 | public override string ToString() 20 | { 21 | return $"{GetCommandName()}(0x{ScriptHash:X8})"; 22 | } 23 | } -------------------------------------------------------------------------------- /FEngLib/Messaging/Commands/SwitchToPackage.cs: -------------------------------------------------------------------------------- 1 | namespace FEngLib.Messaging.Commands; 2 | 3 | public class SwitchToPackage : PackageCommand 4 | { 5 | public SwitchToPackage(string packageName) : base(packageName) 6 | { 7 | } 8 | 9 | public override uint GetId() 10 | { 11 | return (uint)ResponseHelpers.FEMessageResponseCommands.MR_SwitchToPackage; 12 | } 13 | 14 | public override string GetCommandName() 15 | { 16 | return "SwitchToPackage"; 17 | } 18 | } -------------------------------------------------------------------------------- /FEngLib/Messaging/IHaveMessageResponses.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace FEngLib.Messaging; 4 | 5 | public interface IHaveMessageResponses 6 | { 7 | List MessageResponses { get; } 8 | } -------------------------------------------------------------------------------- /FEngLib/Messaging/MessageResponse.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace FEngLib.Messaging; 4 | 5 | public class MessageResponse 6 | { 7 | public MessageResponse() 8 | { 9 | Responses = new List(); 10 | } 11 | 12 | public MessageResponse(uint id, List responses) 13 | { 14 | Id = id; 15 | Responses = responses; 16 | } 17 | 18 | public uint Id { get; set; } 19 | public List Responses { get; set; } 20 | } -------------------------------------------------------------------------------- /FEngLib/Messaging/MessageResponseTagProcessor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Diagnostics; 5 | using FEngLib.Chunks; 6 | using FEngLib.Messaging.Tags; 7 | using FEngLib.Tags; 8 | 9 | namespace FEngLib.Messaging; 10 | 11 | public class MessageResponseTagProcessor 12 | { 13 | private List _messageResponseEntryList = new(); 14 | 15 | public IReadOnlyList MessageResponseEntryList => _messageResponseEntryList; 16 | 17 | // This is a pretty dumb hack, but it gets the job done... 18 | private interface IResponseCommandEntry 19 | { 20 | void SetTarget(uint target); 21 | void SetIntParam(uint param); 22 | void SetStringParam(string param); 23 | } 24 | 25 | public interface IMessageResponseEntry : IEnumerable 26 | { 27 | uint ID { get; } 28 | } 29 | 30 | public class ResponseCommandEntry : IResponseCommandEntry 31 | { 32 | public ResponseCommandEntry(uint ID) 33 | { 34 | this.ID = ID; 35 | } 36 | 37 | public uint ID { get; } 38 | public uint? Target { get; private set; } 39 | public uint? IParam { get; private set; } 40 | public string SParam { get; private set; } 41 | 42 | void IResponseCommandEntry.SetTarget(uint target) 43 | { 44 | Target = target; 45 | } 46 | 47 | void IResponseCommandEntry.SetIntParam(uint param) 48 | { 49 | Debug.Assert(SParam == null); 50 | IParam = param; 51 | } 52 | 53 | void IResponseCommandEntry.SetStringParam(string param) 54 | { 55 | Debug.Assert(IParam == null); 56 | SParam = param; 57 | } 58 | } 59 | 60 | private class MessageResponseEntry : IMessageResponseEntry 61 | { 62 | public List Commands { get; } 63 | public uint ID { get; } 64 | 65 | public MessageResponseEntry(uint id) 66 | { 67 | ID = id; 68 | Commands = new List(); 69 | } 70 | 71 | public IEnumerator GetEnumerator() 72 | { 73 | return Commands.GetEnumerator(); 74 | } 75 | 76 | IEnumerator IEnumerable.GetEnumerator() 77 | { 78 | return GetEnumerator(); 79 | } 80 | } 81 | 82 | private readonly HashSet _seenMessageHashes = new(); 83 | 84 | public void ProcessTag(Tag tag) 85 | { 86 | switch (tag) 87 | { 88 | case MessageResponseCountTag: // TODO: do we want to use this for data validation? 89 | break; 90 | case MessageResponseInfoTag messageResponseInfoTag: 91 | ProcessMessageResponseInfoTag(messageResponseInfoTag); 92 | break; 93 | case ResponseIdTag responseIdTag: 94 | ProcessResponseIdTag(responseIdTag); 95 | break; 96 | case ResponseIntParamTag responseParamTag: 97 | ProcessResponseIntParamTag(responseParamTag); 98 | break; 99 | case ResponseStringParamTag responseParamTag: 100 | ProcessResponseStringParamTag(responseParamTag); 101 | break; 102 | case ResponseTargetTag responseTargetTag: 103 | ProcessResponseTargetTag(responseTargetTag); 104 | break; 105 | default: 106 | throw new NotImplementedException($"Unsupported tag type: {tag.GetType()}"); 107 | } 108 | } 109 | 110 | private void ProcessResponseIntParamTag(ResponseIntParamTag responseIntParamTag) 111 | { 112 | // C# makes us do this stupid dance to access an interface method. I don't like it, but it is what it is. 113 | ((IResponseCommandEntry) _messageResponseEntryList[^1].Commands[^1]).SetIntParam(responseIntParamTag.Param); 114 | } 115 | 116 | private void ProcessResponseStringParamTag(ResponseStringParamTag responseStringParamTag) 117 | { 118 | ((IResponseCommandEntry)_messageResponseEntryList[^1].Commands[^1]).SetStringParam(responseStringParamTag.Param); 119 | } 120 | 121 | private void ProcessResponseTargetTag(ResponseTargetTag responseTargetTag) 122 | { 123 | ((IResponseCommandEntry)_messageResponseEntryList[^1].Commands[^1]).SetTarget(responseTargetTag.Target); 124 | } 125 | 126 | private void ProcessResponseIdTag(ResponseIdTag responseIdTag) 127 | { 128 | Debug.Assert(responseIdTag.Id <= 0x501); 129 | 130 | _messageResponseEntryList[^1].Commands.Add(new ResponseCommandEntry(responseIdTag.Id)); 131 | //ResponseCommand response = responseIdTag.Id switch 132 | //{ 133 | // var id => throw new ChunkReadingException($"Unsupported ResponseId: 0x{id:X}") 134 | //}; 135 | //var response = new ResponseCommand { Id = responseIdTag.Id }; 136 | //targetEntity.MessageResponses[^1].Responses.Add(response); 137 | } 138 | 139 | private void ProcessMessageResponseInfoTag(MessageResponseInfoTag tag) 140 | { 141 | if (!_seenMessageHashes.Add(tag.Hash)) 142 | throw new ChunkReadingException( 143 | $"Encountered a duplicate MessageResponse with ID 0x{tag.Hash:X}. This should not be possible."); 144 | 145 | _messageResponseEntryList.Add(new MessageResponseEntry(tag.Hash)); 146 | } 147 | } -------------------------------------------------------------------------------- /FEngLib/Messaging/MessageTagStream.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using FEngLib.Chunks; 3 | using FEngLib.Messaging.Tags; 4 | using FEngLib.Packages.Tags; 5 | using FEngLib.Tags; 6 | using static FEngLib.FrontendTagType; 7 | 8 | namespace FEngLib.Messaging; 9 | 10 | public class MessageTagStream : TagStream 11 | { 12 | public MessageTagStream(BinaryReader reader, long length) : 13 | base(reader, length) 14 | { 15 | } 16 | 17 | public override Tag NextTag() 18 | { 19 | var (id, size) = (Reader.ReadUInt16(), Reader.ReadUInt16()); 20 | var pos = Reader.BaseStream.Position; 21 | Tag tag = (FrontendTagType)id switch 22 | { 23 | MessageResponseInfo => new MessageResponseInfoTag(), 24 | MessageResponseCount => new MessageResponseCountTag(), 25 | ResponseId => new ResponseIdTag(), 26 | ResponseIntParam => new ResponseIntParamTag(), 27 | ResponseStringParam => new ResponseStringParamTag(), 28 | ResponseTarget => new ResponseTargetTag(), 29 | MessageTargetCount => new MessageTargetCountTag(), 30 | MessageTargetList => new MessageTargetListTag(), 31 | _ => throw new ChunkReadingException($"Unrecognized tag: 0x{id:X4}") 32 | }; 33 | 34 | tag.Read(Reader, id, size); 35 | 36 | if (Reader.BaseStream.Position - pos != size) 37 | throw new ChunkReadingException( 38 | $"Expected {size} bytes to be read by {tag.GetType()} but {Reader.BaseStream.Position - pos} bytes were read"); 39 | 40 | return tag; 41 | } 42 | } -------------------------------------------------------------------------------- /FEngLib/Messaging/ResponseCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace FEngLib.Messaging; 4 | 5 | //public class Response 6 | //{ 7 | // public uint Id { get; set; } 8 | // public uint? IntParam { get; set; } 9 | // public string StringParam { get; set; } 10 | // public uint Target { get; set; } 11 | //} 12 | 13 | public abstract class ResponseCommand 14 | { 15 | //public abstract uint Id { get; } 16 | 17 | public abstract uint GetId(); 18 | public abstract string GetCommandName(); 19 | public abstract override string ToString(); 20 | } 21 | 22 | public interface ITargetedCommand 23 | { 24 | uint Target { get; set; } 25 | } 26 | 27 | public interface IParameterizedCommand 28 | { 29 | TParameter GetParameter(); 30 | void SetParameter(TParameter parameter); 31 | } 32 | 33 | public interface IStringCommand : IParameterizedCommand 34 | { 35 | } 36 | 37 | public interface IIntegerCommand : IParameterizedCommand 38 | { } 39 | 40 | public abstract class PackageCommand : ResponseCommand, IStringCommand 41 | { 42 | public string PackageName { get; private set; } 43 | 44 | protected PackageCommand(string packageName) 45 | { 46 | PackageName = packageName; 47 | } 48 | 49 | public string GetParameter() 50 | { 51 | return PackageName; 52 | } 53 | 54 | public void SetParameter(string parameter) 55 | { 56 | PackageName = parameter ?? throw new ArgumentNullException(nameof(parameter)); 57 | } 58 | 59 | public override string ToString() 60 | { 61 | var escapedPkgName = PackageName.Replace(@"\", @"\\"); 62 | return $"{GetCommandName()}(\"{escapedPkgName}\")"; 63 | } 64 | } 65 | 66 | public abstract class MessageCommand : ResponseCommand, IIntegerCommand 67 | { 68 | public uint MessageHash { get; private set; } 69 | 70 | protected MessageCommand(uint messageHash) 71 | { 72 | MessageHash = messageHash; 73 | } 74 | 75 | public uint GetParameter() 76 | { 77 | return MessageHash; 78 | } 79 | 80 | public void SetParameter(uint parameter) 81 | { 82 | MessageHash = parameter; 83 | } 84 | } 85 | 86 | public abstract class ScriptCommand : ResponseCommand, IIntegerCommand 87 | { 88 | public uint ScriptHash { get; private set; } 89 | 90 | protected ScriptCommand(uint scriptHash) 91 | { 92 | ScriptHash = scriptHash; 93 | } 94 | 95 | public uint GetParameter() 96 | { 97 | return ScriptHash; 98 | } 99 | 100 | public void SetParameter(uint parameter) 101 | { 102 | ScriptHash = parameter; 103 | } 104 | 105 | public override string ToString() 106 | { 107 | return $"{GetCommandName()}(0x{ScriptHash:X})"; 108 | } 109 | } 110 | 111 | public abstract class ObjectCommand : ResponseCommand, IIntegerCommand 112 | { 113 | public uint ObjectGuid { get; private set; } 114 | 115 | protected ObjectCommand(uint objectGuid) 116 | { 117 | ObjectGuid = objectGuid; 118 | } 119 | 120 | public uint GetParameter() 121 | { 122 | return ObjectGuid; 123 | } 124 | 125 | public void SetParameter(uint parameter) 126 | { 127 | ObjectGuid = parameter; 128 | } 129 | 130 | public override string ToString() 131 | { 132 | return $"{GetCommandName()}(0x{ObjectGuid:X})"; 133 | } 134 | } 135 | 136 | public abstract class NonParameterizedCommand : ResponseCommand 137 | { 138 | public override string ToString() 139 | { 140 | return $"{GetCommandName()}()"; 141 | } 142 | } -------------------------------------------------------------------------------- /FEngLib/Messaging/ResponseHelpers.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Linq; 5 | using FEngLib.Messaging.Commands; 6 | 7 | namespace FEngLib.Messaging; 8 | 9 | internal static class ResponseHelpers 10 | { 11 | public static void PopulateMessageResponseList( 12 | IEnumerable messageResponseEntries, 13 | IHaveMessageResponses entity) 14 | { 15 | foreach (var messageResponseEntry in messageResponseEntries) 16 | { 17 | var commands = messageResponseEntry.Select(CreateCommand).ToList(); 18 | entity.MessageResponses.Add(new MessageResponse(messageResponseEntry.ID, commands)); 19 | } 20 | } 21 | 22 | private static ResponseCommand CreateCommand(MessageResponseTagProcessor.ResponseCommandEntry entry) 23 | { 24 | var commandId = (FEMessageResponseCommands)entry.ID; 25 | var target = entry.Target ?? throw new Exception($"{commandId} command is missing target"); 26 | ResponseCommand constructedCommand = (commandId, entry.IParam, entry.SParam) switch 27 | { 28 | (FEMessageResponseCommands.MR_PushPackageGlobal, null, { } packageName) => new PushPackageGlobal(packageName), 29 | (FEMessageResponseCommands.MR_PostMessageToSound, { } messageId, null) => new PostMessageToSound(messageId, target), 30 | (FEMessageResponseCommands.MR_SetScript, { } scriptId, null) => new SetScript(scriptId), 31 | (FEMessageResponseCommands.MR_PostMessageToFEng, { } messageId, null) => new PostMessageToFEng(messageId, target), 32 | (FEMessageResponseCommands.MR_PostMessageToGame, { } messageId, null) => new PostMessageToGame(messageId), 33 | (FEMessageResponseCommands.MR_PopPackage, 0, null) => new PopPackage(), 34 | (FEMessageResponseCommands.MR_SwitchToPackage, null, { } packageName) => new SwitchToPackage(packageName), 35 | (FEMessageResponseCommands.MR_SetActiveButton, { } objectGuid, null) => new SetActiveButton(objectGuid), 36 | (FEMessageResponseCommands.MR_IfScriptEquals, { } scriptId, null) => new IfScriptEquals(scriptId), 37 | (FEMessageResponseCommands.MR_EndIf, 0, null) => new EndIf(), 38 | (FEMessageResponseCommands.MR_Else, not null /* because of ONE BROKEN FNG, this can't be 0 */, null) => new Else(), 39 | (FEMessageResponseCommands.MR_IfScriptNotEquals, { } scriptId, null) => new IfScriptNotEquals(scriptId), 40 | (FEMessageResponseCommands.MR_SetInputProcessing, { } /* also because of that broken FNG (sigh) */ x, null) => new SetInputProcessing(x), 41 | (FEMessageResponseCommands.MR_RecallRecordedButton, { } objectGuid, null) => new RecallRecordedButton(objectGuid), 42 | (FEMessageResponseCommands.MR_RecordCurrentButton, 0, null) => new RecordCurrentButton(), 43 | var cmd => throw new Exception($"Invalid or unimplemented command: {cmd}") 44 | }; 45 | 46 | // debugging stuff 47 | //Debug.Assert(constructedCommand.GetId() == entry.ID); 48 | 49 | //if (entry.Target is > 0 && (constructedCommand is not ITargetedCommand targetedCommand || 50 | // targetedCommand.Target != entry.Target)) 51 | //{ 52 | // Debug.WriteLine("warn: target mismatch between {0} (0x{1:X}) and {2}", commandId, entry.Target, constructedCommand.GetType()); 53 | //} 54 | 55 | //if (constructedCommand is NonParameterizedCommand) 56 | //{ 57 | // if (entry.IParam is {} param && param != 0) 58 | // { 59 | // Debug.WriteLine("warn: explicitly non-parameterized command {0} has non-zero parameter 0x{1:X}", constructedCommand.GetType(), param); 60 | 61 | // Debugger.Break(); 62 | // } 63 | 64 | // if (entry.SParam is { Length: > 0 } sparam) 65 | // { 66 | // Debug.WriteLine("warn: explicitly non-parameterized command {0} has string parameter {1}", constructedCommand.GetType(), sparam); 67 | 68 | // Debugger.Break(); 69 | // } 70 | //} 71 | //else 72 | //{ 73 | // if (entry.IParam != null && (constructedCommand is not IParameterizedCommand parameterizedCommand || 74 | // parameterizedCommand.GetParameter() != entry.IParam)) 75 | // { 76 | // Debugger.Break(); 77 | // } 78 | 79 | // if (entry.SParam != null && (constructedCommand is not IParameterizedCommand parameterizedCommand2 || 80 | // parameterizedCommand2.GetParameter() != entry.SParam)) 81 | // { 82 | // Debugger.Break(); 83 | // } 84 | //} 85 | 86 | //Debug.Assert((constructedCommand is not ITargetedCommand && (entry.Target ?? 0) == 0) || 87 | // (constructedCommand is ITargetedCommand targetedCommand && targetedCommand.Target == entry.Target)); 88 | return constructedCommand; 89 | } 90 | 91 | internal enum FEMessageResponseCommands 92 | { 93 | MR_SetScript = 0x0, 94 | MR_PostMessageToFEng = 0x1, 95 | MR_PostMessageToGame = 0x2, 96 | MR_PostMessageToSound = 0x3, 97 | MR_SetActiveButton = 0x100, 98 | MR_SetInputProcessing = 0x101, 99 | MR_RecordCurrentButton = 0x102, 100 | MR_RecallRecordedButton = 0x103, 101 | MR_DontNavigate = 0x104, 102 | MR_PassControlToChildCurrent = 0x105, 103 | MR_PassControlToChildGlobal = 0x106, 104 | MR_PassControlToParentCurrent = 0x107, 105 | MR_PassControlToParentGlobal = 0x108, 106 | MR_SwitchToPackage = 0x200, 107 | MR_PushPackageGlobal = 0x201, 108 | MR_PushPackageCurrent = 0x202, 109 | MR_PopPackage = 0x203, 110 | MR_PushPackageNone = 0x204, 111 | MR_RecordPackageMarker = 0x2C0, 112 | MR_SwitchToPackageMarker = 0x2C1, 113 | MR_ClearPackageMarkers = 0x2C2, 114 | MR_IfScriptEquals = 0x300, 115 | MR_IfScriptNotEquals = 0x301, 116 | MR_Else = 0x500, 117 | MR_EndIf = 0x501, 118 | } 119 | } -------------------------------------------------------------------------------- /FEngLib/Messaging/Tags/MessageResponseCountTag.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using FEngLib.Tags; 3 | 4 | namespace FEngLib.Messaging.Tags; 5 | 6 | public class MessageResponseCountTag : Tag 7 | { 8 | public override void Read(BinaryReader br, 9 | ushort id, 10 | ushort length) 11 | { 12 | br.ReadUInt32(); 13 | } 14 | } -------------------------------------------------------------------------------- /FEngLib/Messaging/Tags/MessageResponseInfoTag.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using FEngLib.Tags; 3 | 4 | namespace FEngLib.Messaging.Tags; 5 | 6 | public class MessageResponseInfoTag : Tag 7 | { 8 | public uint Hash { get; set; } 9 | 10 | public override void Read(BinaryReader br, 11 | ushort id, 12 | ushort length) 13 | { 14 | Hash = br.ReadUInt32(); 15 | } 16 | } -------------------------------------------------------------------------------- /FEngLib/Messaging/Tags/ResponseIdTag.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using FEngLib.Tags; 3 | 4 | namespace FEngLib.Messaging.Tags; 5 | 6 | public class ResponseIdTag : Tag 7 | { 8 | public uint Id { get; set; } 9 | 10 | public override void Read(BinaryReader br, ushort id, 11 | ushort length) 12 | { 13 | Id = br.ReadUInt32(); 14 | } 15 | } -------------------------------------------------------------------------------- /FEngLib/Messaging/Tags/ResponseIntParamTag.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using FEngLib.Tags; 3 | 4 | namespace FEngLib.Messaging.Tags; 5 | 6 | public class ResponseIntParamTag : Tag 7 | { 8 | public uint Param { get; set; } 9 | 10 | public override void Read(BinaryReader br, ushort id, 11 | ushort length) 12 | { 13 | Param = br.ReadUInt32(); 14 | } 15 | } -------------------------------------------------------------------------------- /FEngLib/Messaging/Tags/ResponseStringParamTag.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using FEngLib.Tags; 3 | 4 | namespace FEngLib.Messaging.Tags; 5 | 6 | public class ResponseStringParamTag : Tag 7 | { 8 | public string Param { get; set; } 9 | 10 | public override void Read(BinaryReader br, ushort id, 11 | ushort length) 12 | { 13 | Param = new string(br.ReadChars(length)).Trim('\x00'); 14 | } 15 | } -------------------------------------------------------------------------------- /FEngLib/Messaging/Tags/ResponseTargetTag.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using FEngLib.Tags; 3 | 4 | namespace FEngLib.Messaging.Tags; 5 | 6 | public class ResponseTargetTag : Tag 7 | { 8 | public uint Target { get; set; } 9 | 10 | public override void Read(BinaryReader br, ushort id, 11 | ushort length) 12 | { 13 | Target = br.ReadUInt32(); 14 | } 15 | } -------------------------------------------------------------------------------- /FEngLib/ObjectReaderState.cs: -------------------------------------------------------------------------------- 1 | namespace FEngLib; 2 | 3 | public class ObjectReaderState 4 | { 5 | public ObjectReaderState(FrontendChunkBlock currentChunkBlock, FrontendChunkReader chunkReader) 6 | { 7 | CurrentChunkBlock = currentChunkBlock; 8 | ChunkReader = chunkReader; 9 | } 10 | 11 | public FrontendChunkBlock CurrentChunkBlock { get; } 12 | public FrontendChunkReader ChunkReader { get; } 13 | } -------------------------------------------------------------------------------- /FEngLib/Objects/BaseObject.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Diagnostics; 3 | using System.IO; 4 | using System.Numerics; 5 | using FEngLib.Messaging; 6 | using FEngLib.Packages; 7 | using FEngLib.Scripts; 8 | using FEngLib.Structures; 9 | using FEngLib.Utils; 10 | 11 | namespace FEngLib.Objects; 12 | 13 | /// 14 | /// This represents all common data found in an object's 'ObjD' chunk. 15 | /// For objects where the ObjD chunk contains extra data (e.g. images), 16 | /// inherit from this class to represent the extra values in that chunk. 17 | /// 18 | public class ObjectData : IBinaryAccess 19 | { 20 | public Color4 Color { get; set; } 21 | public Vector3 Pivot { get; set; } 22 | public Vector3 Position { get; set; } 23 | public Quaternion Rotation { get; set; } 24 | public Vector3 Size { get; set; } 25 | 26 | public virtual void Read(BinaryReader br) 27 | { 28 | Color = br.ReadColor(); 29 | Pivot = br.ReadVector3(); 30 | Position = br.ReadVector3(); 31 | Rotation = br.ReadQuaternion(); 32 | Size = br.ReadVector3(); 33 | } 34 | 35 | public virtual void Write(BinaryWriter bw) 36 | { 37 | bw.Write(Color); 38 | bw.Write(Pivot); 39 | bw.Write(Position); 40 | bw.Write(Rotation); 41 | bw.Write(Size); 42 | } 43 | } 44 | 45 | /// 46 | /// Common interface for all object types and their properties. 47 | /// Extend this if your object type has extra ObjD attributes, 48 | /// and if there are other object types that inherit from your object type (to ensure type safety). 49 | /// 50 | /// 51 | /// A type inheriting from ObjectData, 52 | /// representing the contents of an ObjD chunk for this object. 53 | /// 54 | public interface IObject : IScriptedObject, IHaveMessageResponses where TData : ObjectData 55 | { 56 | TData Data { get; } 57 | ObjectFlags Flags { get; set; } 58 | ResourceRequest ResourceRequest { get; set; } 59 | string Name { get; set; } 60 | uint NameHash { get; set; } 61 | uint Guid { get; set; } 62 | IObject Parent { get; set; } 63 | ObjectType GetObjectType(); 64 | 65 | void InitializeData(); 66 | 67 | void SetFlag(ObjectFlags flag, bool value) 68 | { 69 | if (value) 70 | Flags |= flag; 71 | else 72 | Flags &= ~flag; 73 | } 74 | } 75 | 76 | public interface IScriptedObject 77 | { 78 | IEnumerable