├── .gitattributes ├── .gitignore ├── Bin2Object.sln ├── Bin2Object ├── Attributes.cs ├── Bin2Object.csproj ├── BinaryObjectReader.cs ├── BinaryObjectStream.cs ├── BinaryObjectWriter.cs └── Endianness.cs ├── LICENSE ├── README.md └── Tests ├── Tests.cs └── Tests.csproj /.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 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | bld/ 21 | [Bb]in/ 22 | [Oo]bj/ 23 | [Ll]og/ 24 | 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | # Uncomment if you have tasks that create the project's static files in wwwroot 28 | #wwwroot/ 29 | 30 | # MSTest test Results 31 | [Tt]est[Rr]esult*/ 32 | [Bb]uild[Ll]og.* 33 | 34 | # NUNIT 35 | *.VisualState.xml 36 | TestResult.xml 37 | 38 | # Build Results of an ATL Project 39 | [Dd]ebugPS/ 40 | [Rr]eleasePS/ 41 | dlldata.c 42 | 43 | # DNX 44 | project.lock.json 45 | artifacts/ 46 | 47 | *_i.c 48 | *_p.c 49 | *_i.h 50 | *.ilk 51 | *.meta 52 | *.obj 53 | *.pch 54 | *.pdb 55 | *.pgc 56 | *.pgd 57 | *.rsp 58 | *.sbr 59 | *.tlb 60 | *.tli 61 | *.tlh 62 | *.tmp 63 | *.tmp_proj 64 | *.log 65 | *.vspscc 66 | *.vssscc 67 | .builds 68 | *.pidb 69 | *.svclog 70 | *.scc 71 | 72 | # Chutzpah Test files 73 | _Chutzpah* 74 | 75 | # Visual C++ cache files 76 | ipch/ 77 | *.aps 78 | *.ncb 79 | *.opendb 80 | *.opensdf 81 | *.sdf 82 | *.cachefile 83 | *.VC.db 84 | *.VC.VC.opendb 85 | 86 | # Visual Studio profiler 87 | *.psess 88 | *.vsp 89 | *.vspx 90 | *.sap 91 | 92 | # TFS 2012 Local Workspace 93 | $tf/ 94 | 95 | # Guidance Automation Toolkit 96 | *.gpState 97 | 98 | # ReSharper is a .NET coding add-in 99 | _ReSharper*/ 100 | *.[Rr]e[Ss]harper 101 | *.DotSettings.user 102 | 103 | # JustCode is a .NET coding add-in 104 | .JustCode 105 | 106 | # TeamCity is a build add-in 107 | _TeamCity* 108 | 109 | # DotCover is a Code Coverage Tool 110 | *.dotCover 111 | 112 | # NCrunch 113 | _NCrunch_* 114 | .*crunch*.local.xml 115 | nCrunchTemp_* 116 | 117 | # MightyMoose 118 | *.mm.* 119 | AutoTest.Net/ 120 | 121 | # Web workbench (sass) 122 | .sass-cache/ 123 | 124 | # Installshield output folder 125 | [Ee]xpress/ 126 | 127 | # DocProject is a documentation generator add-in 128 | DocProject/buildhelp/ 129 | DocProject/Help/*.HxT 130 | DocProject/Help/*.HxC 131 | DocProject/Help/*.hhc 132 | DocProject/Help/*.hhk 133 | DocProject/Help/*.hhp 134 | DocProject/Help/Html2 135 | DocProject/Help/html 136 | 137 | # Click-Once directory 138 | publish/ 139 | 140 | # Publish Web Output 141 | *.[Pp]ublish.xml 142 | *.azurePubxml 143 | # TODO: Comment the next line if you want to checkin your web deploy settings 144 | # but database connection strings (with potential passwords) will be unencrypted 145 | *.pubxml 146 | *.publishproj 147 | 148 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 149 | # checkin your Azure Web App publish settings, but sensitive information contained 150 | # in these scripts will be unencrypted 151 | PublishScripts/ 152 | 153 | # NuGet Packages 154 | *.nupkg 155 | # The packages folder can be ignored because of Package Restore 156 | **/packages/* 157 | # except build/, which is used as an MSBuild target. 158 | !**/packages/build/ 159 | # Uncomment if necessary however generally it will be regenerated when needed 160 | #!**/packages/repositories.config 161 | # NuGet v3's project.json files produces more ignoreable files 162 | *.nuget.props 163 | *.nuget.targets 164 | 165 | # Microsoft Azure Build Output 166 | csx/ 167 | *.build.csdef 168 | 169 | # Microsoft Azure Emulator 170 | ecf/ 171 | rcf/ 172 | 173 | # Windows Store app package directories and files 174 | AppPackages/ 175 | BundleArtifacts/ 176 | Package.StoreAssociation.xml 177 | _pkginfo.txt 178 | 179 | # Visual Studio cache files 180 | # files ending in .cache can be ignored 181 | *.[Cc]ache 182 | # but keep track of directories ending in .cache 183 | !*.[Cc]ache/ 184 | 185 | # Others 186 | ClientBin/ 187 | ~$* 188 | *~ 189 | *.dbmdl 190 | *.dbproj.schemaview 191 | *.pfx 192 | *.publishsettings 193 | node_modules/ 194 | orleans.codegen.cs 195 | 196 | # Since there are multiple workflows, uncomment next line to ignore bower_components 197 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 198 | #bower_components/ 199 | 200 | # RIA/Silverlight projects 201 | Generated_Code/ 202 | 203 | # Backup & report files from converting an old project file 204 | # to a newer Visual Studio version. Backup files are not needed, 205 | # because we have git ;-) 206 | _UpgradeReport_Files/ 207 | Backup*/ 208 | UpgradeLog*.XML 209 | UpgradeLog*.htm 210 | 211 | # SQL Server files 212 | *.mdf 213 | *.ldf 214 | 215 | # Business Intelligence projects 216 | *.rdl.data 217 | *.bim.layout 218 | *.bim_*.settings 219 | 220 | # Microsoft Fakes 221 | FakesAssemblies/ 222 | 223 | # GhostDoc plugin setting file 224 | *.GhostDoc.xml 225 | 226 | # Node.js Tools for Visual Studio 227 | .ntvs_analysis.dat 228 | 229 | # Visual Studio 6 build log 230 | *.plg 231 | 232 | # Visual Studio 6 workspace options file 233 | *.opt 234 | 235 | # Visual Studio LightSwitch build output 236 | **/*.HTMLClient/GeneratedArtifacts 237 | **/*.DesktopClient/GeneratedArtifacts 238 | **/*.DesktopClient/ModelManifest.xml 239 | **/*.Server/GeneratedArtifacts 240 | **/*.Server/ModelManifest.xml 241 | _Pvt_Extensions 242 | 243 | # Paket dependency manager 244 | .paket/paket.exe 245 | paket-files/ 246 | 247 | # FAKE - F# Make 248 | .fake/ 249 | 250 | # JetBrains Rider 251 | .idea/ 252 | *.sln.iml 253 | -------------------------------------------------------------------------------- /Bin2Object.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.29411.108 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Bin2Object", "Bin2Object\Bin2Object.csproj", "{865AA871-F000-4794-A28A-17EAB5D53579}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tests", "Tests\Tests.csproj", "{D5EEAFD0-6721-4482-97A3-B7A1FDE300AE}" 9 | ProjectSection(ProjectDependencies) = postProject 10 | {865AA871-F000-4794-A28A-17EAB5D53579} = {865AA871-F000-4794-A28A-17EAB5D53579} 11 | EndProjectSection 12 | EndProject 13 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{6531D379-7A7E-4C66-B98F-C1E7D4F83009}" 14 | ProjectSection(SolutionItems) = preProject 15 | LICENSE = LICENSE 16 | README.md = README.md 17 | EndProjectSection 18 | EndProject 19 | Global 20 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 21 | Debug|Any CPU = Debug|Any CPU 22 | Release|Any CPU = Release|Any CPU 23 | EndGlobalSection 24 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 25 | {865AA871-F000-4794-A28A-17EAB5D53579}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 26 | {865AA871-F000-4794-A28A-17EAB5D53579}.Debug|Any CPU.Build.0 = Debug|Any CPU 27 | {865AA871-F000-4794-A28A-17EAB5D53579}.Release|Any CPU.ActiveCfg = Release|Any CPU 28 | {865AA871-F000-4794-A28A-17EAB5D53579}.Release|Any CPU.Build.0 = Release|Any CPU 29 | {D5EEAFD0-6721-4482-97A3-B7A1FDE300AE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 30 | {D5EEAFD0-6721-4482-97A3-B7A1FDE300AE}.Debug|Any CPU.Build.0 = Debug|Any CPU 31 | {D5EEAFD0-6721-4482-97A3-B7A1FDE300AE}.Release|Any CPU.ActiveCfg = Release|Any CPU 32 | {D5EEAFD0-6721-4482-97A3-B7A1FDE300AE}.Release|Any CPU.Build.0 = Release|Any CPU 33 | EndGlobalSection 34 | GlobalSection(SolutionProperties) = preSolution 35 | HideSolutionNode = FALSE 36 | EndGlobalSection 37 | GlobalSection(ExtensibilityGlobals) = postSolution 38 | SolutionGuid = {7734C402-03EE-47BE-A869-058DB5683DF9} 39 | EndGlobalSection 40 | EndGlobal 41 | -------------------------------------------------------------------------------- /Bin2Object/Attributes.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016 Perfare - https://github.com/Perfare/Il2CppDumper/ 2 | // Copyright (c) 2016 Alican Çubukçuoğlu - https://github.com/AlicanC/AlicanC-s-Modern-Warfare-2-Tool/ 3 | // Copyright (c) 2017-2021 Katy Coe - http://www.djkaty.com - https://github.com/djkaty/Bin2Object/ 4 | 5 | using System; 6 | 7 | namespace NoisyCowStudios.Bin2Object 8 | { 9 | [AttributeUsage(AttributeTargets.Field)] 10 | public class ArrayLengthAttribute : Attribute 11 | { 12 | public string FieldName { get; set; } 13 | public int FixedSize { get; set; } 14 | } 15 | 16 | [AttributeUsage(AttributeTargets.Field)] 17 | public class StringAttribute : Attribute 18 | { 19 | public bool IsNullTerminated { get; set; } 20 | public int FixedSize { get; set; } 21 | } 22 | 23 | [AttributeUsage(AttributeTargets.Field, AllowMultiple = true)] 24 | public class VersionAttribute : Attribute 25 | { 26 | public double Min { get; set; } = -1; 27 | public double Max { get; set; } = -1; 28 | } 29 | 30 | [AttributeUsage(AttributeTargets.Field)] 31 | public class SkipWhenReadingAttribute : Attribute { } 32 | } 33 | -------------------------------------------------------------------------------- /Bin2Object/Bin2Object.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.1 5 | any 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /Bin2Object/BinaryObjectReader.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016 Perfare - https://github.com/Perfare/Il2CppDumper/ 2 | // Copyright (c) 2016 Alican Çubukçuoğlu - https://github.com/AlicanC/AlicanC-s-Modern-Warfare-2-Tool/ 3 | // Copyright (c) 2017-2021 Katy Coe - http://www.djkaty.com - https://github.com/djkaty/Bin2Object/ 4 | 5 | using System; 6 | using System.Collections.Generic; 7 | using System.IO; 8 | using System.Linq; 9 | using System.Reflection; 10 | using System.Text; 11 | 12 | namespace NoisyCowStudios.Bin2Object 13 | { 14 | public class BinaryObjectReader : BinaryReader 15 | { 16 | // Method cache for primitive mappings 17 | private Dictionary readMethodCache { get; } 18 | 19 | // Generic method cache to dramatically speed up repeated calls to ReadObject with the same T 20 | private Dictionary readObjectGenericCache = new Dictionary(); 21 | 22 | // VersionAttribute cache to dramatically speed up repeated calls to ReadObject with the same T 23 | private Dictionary>> readObjectVersionCache = new Dictionary>>(); 24 | 25 | // Thread synchronization objects (for thread safety) 26 | private object readLock = new object(); 27 | 28 | // Initialization 29 | public BinaryObjectReader(Stream stream, Endianness endianness = Endianness.Little, bool leaveOpen = false) : base(stream, Encoding.Default, leaveOpen) { 30 | Endianness = endianness; 31 | 32 | readMethodCache = typeof(BinaryObjectReader).GetMethods().Where(m => m.Name.StartsWith("Read") && !m.GetParameters().Any()).GroupBy(m => m.ReturnType).ToDictionary(kv => kv.Key.Name, kv => kv.First()); 33 | } 34 | 35 | // Position in the stream 36 | public long Position { 37 | get => BaseStream.Position; 38 | set => BaseStream.Position = value; 39 | } 40 | 41 | // Allows you to specify primitive types which should be read as different types in the stream 42 | // Key: type in object; Value: type in stream 43 | public Dictionary PrimitiveMappings { get; } = new Dictionary(); 44 | 45 | // Allows you to specify object types which should be read as different types in the stream 46 | // The fields of the read object will be copied to the fields of the target object with matching names 47 | // Key: object type to return; Value: object type to read from stream 48 | public Dictionary ObjectMappings { get; } = new Dictionary(); 49 | 50 | public Endianness Endianness { get; set; } 51 | 52 | public double Version { get; set; } = 1; 53 | 54 | public Encoding Encoding { get; set; } = Encoding.UTF8; 55 | 56 | public override byte[] ReadBytes(int count) { 57 | var bytes = base.ReadBytes(count); 58 | return (Endianness == Endianness.Little ? bytes : bytes.Reverse().ToArray()); 59 | } 60 | 61 | public override long ReadInt64() => BitConverter.ToInt64(ReadBytes(8), 0); 62 | 63 | public override ulong ReadUInt64() => BitConverter.ToUInt64(ReadBytes(8), 0); 64 | 65 | public override int ReadInt32() => BitConverter.ToInt32(ReadBytes(4), 0); 66 | 67 | public override uint ReadUInt32() => BitConverter.ToUInt32(ReadBytes(4), 0); 68 | 69 | public override short ReadInt16() => BitConverter.ToInt16(ReadBytes(2), 0); 70 | 71 | public override ushort ReadUInt16() => BitConverter.ToUInt16(ReadBytes(2), 0); 72 | 73 | public byte[] ReadBytes(long addr, int count) { 74 | lock (readLock) { 75 | Position = addr; 76 | return ReadBytes(count); 77 | } 78 | } 79 | 80 | public long ReadInt64(long addr) { 81 | lock (readLock) { 82 | Position = addr; 83 | return ReadInt64(); 84 | } 85 | } 86 | 87 | public ulong ReadUInt64(long addr) { 88 | lock (readLock) { 89 | Position = addr; 90 | return ReadUInt64(); 91 | } 92 | } 93 | 94 | public int ReadInt32(long addr) { 95 | lock (readLock) { 96 | Position = addr; 97 | return ReadInt32(); 98 | } 99 | } 100 | 101 | public uint ReadUInt32(long addr) { 102 | lock (readLock) { 103 | Position = addr; 104 | return ReadUInt32(); 105 | } 106 | } 107 | 108 | public short ReadInt16(long addr) { 109 | lock (readLock) { 110 | Position = addr; 111 | return ReadInt16(); 112 | } 113 | } 114 | 115 | public ushort ReadUInt16(long addr) { 116 | lock (readLock) { 117 | Position = addr; 118 | return ReadUInt16(); 119 | } 120 | } 121 | 122 | public byte ReadByte(long addr) { 123 | lock (readLock) { 124 | Position = addr; 125 | return ReadByte(); 126 | } 127 | } 128 | 129 | public bool ReadBoolean(long addr) { 130 | lock (readLock) { 131 | Position = addr; 132 | return ReadBoolean(); 133 | } 134 | } 135 | 136 | public T ReadObject(long addr) where T : new() { 137 | lock (readLock) { 138 | Position = addr; 139 | return ReadObject(); 140 | } 141 | } 142 | 143 | private object ReadPrimitive(Type t) { 144 | 145 | // Checked for mapped primitive types 146 | if (PrimitiveMappings.TryGetValue(t.Name, out Type mapping)) { 147 | var mappedReader = readMethodCache[mapping.Name]; 148 | var result = mappedReader.Invoke(this, null); 149 | return Convert.ChangeType(result, t); 150 | } 151 | 152 | // Unmapped primitive (eliminating obj causes Visual Studio 16.3.5 to crash) 153 | object obj = t.Name switch { 154 | "Int64" => ReadInt64(), 155 | "UInt64" => ReadUInt64(), 156 | "Int32" => ReadInt32(), 157 | "UInt32" => ReadUInt32(), 158 | "Int16" => ReadInt16(), 159 | "UInt16" => ReadUInt16(), 160 | "Byte" => ReadByte(), 161 | "Boolean" => ReadBoolean(), 162 | _ => throw new ArgumentException("Unsupported primitive type specified: " + t.FullName) 163 | }; 164 | return obj; 165 | } 166 | 167 | // Copy matching named fields from one object to another 168 | // Fields in the source not in the target and vice versa are ignored 169 | private T MapObject(object from) where T : new() { 170 | var t = new T(); 171 | 172 | var fromType = from.GetType(); 173 | var toTypeFields = typeof(T).GetFields(); 174 | 175 | // Iterate source fields 176 | foreach (var f in fromType.GetFields()) { 177 | 178 | // Find target field with matching name - ignore the rest 179 | if (toTypeFields.FirstOrDefault(tf => tf.Name == f.Name) is FieldInfo targetField) { 180 | targetField.SetValue(t, Convert.ChangeType(f.GetValue(from), targetField.FieldType)); 181 | } 182 | } 183 | return t; 184 | } 185 | 186 | public T ReadObject() where T : new() { 187 | 188 | var type = typeof(T); 189 | 190 | // Object is actually a primitive 191 | if (type.IsPrimitive) { 192 | return (T) ReadPrimitive(type); 193 | } 194 | 195 | // Check for object mapping 196 | if (ObjectMappings.TryGetValue(type, out var streamType)) { 197 | if (!readObjectGenericCache.TryGetValue(streamType.FullName, out MethodInfo mi2)) { 198 | var us = GetType().GetMethod("ReadObject", Type.EmptyTypes); 199 | mi2 = us.MakeGenericMethod(streamType); 200 | readObjectGenericCache.Add(streamType.FullName, mi2); 201 | } 202 | var obj = mi2.Invoke(this, null); 203 | 204 | return MapObject(obj); 205 | } 206 | 207 | var t = new T(); 208 | 209 | // First time caching 210 | if (!readObjectVersionCache.ContainsKey(type)) { 211 | var fields = new Dictionary>(); 212 | foreach (var i in type.GetFields()) 213 | if (i.GetCustomAttribute(false) is SkipWhenReadingAttribute) 214 | fields.Add(i, new List<(double, double)> { (-2, -2) }); 215 | else 216 | fields.Add(i, i.GetCustomAttributes(false).Select(v => (v.Min, v.Max)).ToList()); 217 | 218 | readObjectVersionCache.Add(type, fields); 219 | } 220 | 221 | foreach (var (i, versions) in readObjectVersionCache[type]) { 222 | // Skip fields with SkipWhenReading set 223 | if (versions.FirstOrDefault() == (-2, -2)) 224 | continue; 225 | 226 | // Only process fields for our selected object versioning (always process if none supplied) 227 | if (versions.Any() && !versions.Any(v => (v.Min <= Version || v.Min == -1) && (v.Max >= Version || v.Max == -1))) 228 | continue; 229 | 230 | // String 231 | if (i.FieldType == typeof(string)) { 232 | var attr = i.GetCustomAttribute(false); 233 | 234 | // No String attribute? Use a null-terminated string by default 235 | if (attr == null || attr.IsNullTerminated) 236 | i.SetValue(t, ReadNullTerminatedString()); 237 | else { 238 | if (attr.FixedSize <= 0) 239 | throw new ArgumentException("String attribute for array field " + i.Name + " configuration invalid"); 240 | i.SetValue(t, ReadFixedLengthString(attr.FixedSize)); 241 | } 242 | } 243 | 244 | // Array 245 | else if (i.FieldType.IsArray) { 246 | var attr = i.GetCustomAttribute(false) ?? 247 | throw new InvalidOperationException("Array field " + i.Name + " must have ArrayLength attribute"); 248 | 249 | int lengthPrimitive; 250 | 251 | if (attr.FieldName != null) { 252 | var field = type.GetField(attr.FieldName) ?? 253 | throw new ArgumentException("Array field " + i.Name + " has invalid FieldName in ArrayLength attribute"); 254 | lengthPrimitive = Convert.ToInt32(field.GetValue(t)); 255 | } 256 | else if (attr.FixedSize > 0) { 257 | lengthPrimitive = attr.FixedSize; 258 | } 259 | else { 260 | throw new ArgumentException("ArrayLength attribute for array field " + i.Name + " configuration invalid"); 261 | } 262 | 263 | var us = GetType().GetMethod("ReadArray", new[] {typeof(int)}); 264 | var mi2 = us.MakeGenericMethod(i.FieldType.GetElementType()); 265 | i.SetValue(t, mi2.Invoke(this, new object[] { lengthPrimitive })); 266 | } 267 | 268 | // Primitive type 269 | // This is unnecessary but saves on many generic Invoke calls which are really slow 270 | else if (i.FieldType.IsPrimitive) { 271 | i.SetValue(t, ReadPrimitive(i.FieldType)); 272 | } 273 | 274 | // Object 275 | else { 276 | if (!readObjectGenericCache.TryGetValue(i.FieldType.FullName, out MethodInfo mi2)) { 277 | var us = GetType().GetMethod("ReadObject", Type.EmptyTypes); 278 | mi2 = us.MakeGenericMethod(i.FieldType); 279 | readObjectGenericCache.Add(i.FieldType.FullName, mi2); 280 | } 281 | i.SetValue(t, mi2.Invoke(this, null)); 282 | } 283 | } 284 | return t; 285 | } 286 | 287 | public T[] ReadArray(long addr, int count) where T : new() { 288 | lock (readLock) { 289 | Position = addr; 290 | return ReadArray(count); 291 | } 292 | } 293 | 294 | public T[] ReadArray(int count) where T : new() { 295 | T[] t = new T[count]; 296 | 297 | if (!typeof(T).IsPrimitive) { 298 | for (int i = 0; i < count; i++) 299 | t[i] = ReadObject(); 300 | } else { 301 | var type = typeof(T); 302 | 303 | // Checked for mapped primitive types 304 | if (PrimitiveMappings.TryGetValue(type.Name, out Type mapping)) { 305 | var mappedReader = readMethodCache[mapping.Name]; 306 | 307 | for (var i = 0; i < count; i++) 308 | t[i] = (T) Convert.ChangeType(mappedReader.Invoke(this, null), type); 309 | } else { 310 | // Unmapped primitive (eliminating obj causes Visual Studio 16.3.5 to crash) 311 | for (var i = 0; i < count; i++) 312 | t[i] = (T) (type.Name switch { 313 | "Int64" => (object) ReadInt64(), 314 | "UInt64" => ReadUInt64(), 315 | "Int32" => ReadInt32(), 316 | "UInt32" => ReadUInt32(), 317 | "Int16" => ReadInt16(), 318 | "UInt16" => ReadUInt16(), 319 | "Byte" => ReadByte(), 320 | "Boolean" => ReadBoolean() ? 1ul : 0ul, 321 | _ => throw new ArgumentException("Unsupported primitive type specified: " + type.FullName) 322 | }); 323 | } 324 | } 325 | return t; 326 | } 327 | 328 | public string ReadNullTerminatedString(long addr, Encoding encoding = null) { 329 | lock (readLock) { 330 | Position = addr; 331 | return ReadNullTerminatedString(encoding); 332 | } 333 | } 334 | 335 | public string ReadNullTerminatedString(Encoding encoding = null) { 336 | List bytes = new List(); 337 | byte b; 338 | while ((b = ReadByte()) != 0) 339 | bytes.Add(b); 340 | return encoding?.GetString(bytes.ToArray()) ?? Encoding.GetString(bytes.ToArray()); 341 | } 342 | 343 | public string ReadFixedLengthString(long addr, int length, Encoding encoding = null) { 344 | lock (readLock) { 345 | Position = addr; 346 | return ReadFixedLengthString(length, encoding); 347 | } 348 | } 349 | 350 | public string ReadFixedLengthString(int length, Encoding encoding = null) { 351 | byte[] b = ReadArray(length); 352 | List bytes = new List(); 353 | foreach (var c in b) 354 | if (c == 0) 355 | break; 356 | else 357 | bytes.Add(c); 358 | return encoding?.GetString(bytes.ToArray()) ?? Encoding.GetString(bytes.ToArray()); 359 | } 360 | } 361 | } 362 | -------------------------------------------------------------------------------- /Bin2Object/BinaryObjectStream.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Katy Coe - http://www.djkaty.com - https://github.com/djkaty/Bin2Object/ 2 | 3 | using System; 4 | using System.Collections.Generic; 5 | using System.IO; 6 | using System.Text; 7 | 8 | namespace NoisyCowStudios.Bin2Object 9 | { 10 | // Combined BinaryObjectReader and BinaryObjectWriter that works with a MemoryStream 11 | public class BinaryObjectStream : MemoryStream { 12 | // The reader and writer 13 | public BinaryObjectReader Reader { get; } 14 | public BinaryObjectWriter Writer { get; } 15 | 16 | public Endianness Endianness { 17 | get => Reader.Endianness; 18 | set { 19 | Reader.Endianness = value; 20 | Writer.Endianness = value; 21 | } 22 | } 23 | 24 | public double Version { 25 | get => Reader.Version; 26 | set { 27 | Reader.Version = value; 28 | Writer.Version = value; 29 | } 30 | } 31 | 32 | public Encoding Encoding { 33 | get => Reader.Encoding; 34 | set { 35 | Reader.Encoding = value; 36 | Writer.Encoding = value; 37 | } 38 | } 39 | 40 | // Add a primitive mapping to the reader and writer 41 | public void AddPrimitiveMapping(Type objType, Type streamType) { 42 | Reader.PrimitiveMappings.Add(objType.Name, streamType); 43 | Writer.PrimitiveMappings.Add(objType, streamType); 44 | } 45 | 46 | // Add an object mapping to the reader 47 | public void AddObjectMapping(Type objType, Type streamType) { 48 | Reader.ObjectMappings.Add(objType, streamType); 49 | } 50 | 51 | // Create reader/writer 52 | public BinaryObjectStream(byte[] bytes, Endianness endianness = Endianness.Little, bool leaveOpen = false) : base(bytes) { 53 | Reader = new BinaryObjectReader(this, endianness, leaveOpen); 54 | Writer = new BinaryObjectWriter(this, endianness, leaveOpen); 55 | } 56 | public BinaryObjectStream(Endianness endianness = Endianness.Little, bool leaveOpen = false) { 57 | Reader = new BinaryObjectReader(this, endianness, leaveOpen); 58 | Writer = new BinaryObjectWriter(this, endianness, leaveOpen); 59 | } 60 | 61 | // Surrogate methods 62 | public byte[] ReadBytes(int count) => Reader.ReadBytes(count); 63 | public long ReadInt64() => Reader.ReadInt64(); 64 | public ulong ReadUInt64() => Reader.ReadUInt64(); 65 | public int ReadInt32() => Reader.ReadInt32(); 66 | public uint ReadUInt32() => Reader.ReadUInt32(); 67 | public short ReadInt16() => Reader.ReadInt16(); 68 | public ushort ReadUInt16() => Reader.ReadUInt16(); 69 | public bool ReadBoolean() => Reader.ReadBoolean(); 70 | public float ReadSingle() => Reader.ReadSingle(); 71 | public double ReadDouble() => Reader.ReadDouble(); 72 | public new byte ReadByte() => Reader.ReadByte(); 73 | 74 | public byte[] ReadBytes(long addr, int count) => Reader.ReadBytes(addr, count); 75 | public long ReadInt64(long addr) => Reader.ReadInt64(addr); 76 | public ulong ReadUInt64(long addr) => Reader.ReadUInt64(addr); 77 | public int ReadInt32(long addr) => Reader.ReadInt32(addr); 78 | public uint ReadUInt32(long addr) => Reader.ReadUInt32(addr); 79 | public short ReadInt16(long addr) => Reader.ReadInt16(addr); 80 | public ushort ReadUInt16(long addr) => Reader.ReadUInt16(addr); 81 | public byte ReadByte(long addr) => Reader.ReadByte(addr); 82 | public bool ReadBoolean(long addr) => Reader.ReadBoolean(addr); 83 | 84 | public T ReadObject(long addr) where T : new() => Reader.ReadObject(addr); 85 | public T ReadObject() where T : new() => Reader.ReadObject(); 86 | 87 | public T[] ReadArray(long addr, int count) where T : new() => Reader.ReadArray(addr, count); 88 | public T[] ReadArray(int count) where T : new() => Reader.ReadArray(count); 89 | 90 | public string ReadNullTerminatedString(long addr, Encoding encoding = null) => Reader.ReadNullTerminatedString(addr, encoding); 91 | public string ReadNullTerminatedString(Encoding encoding = null) => Reader.ReadNullTerminatedString(encoding); 92 | public string ReadFixedLengthString(long addr, int length, Encoding encoding = null) => Reader.ReadFixedLengthString(addr, length, encoding); 93 | public string ReadFixedLengthString(int length, Encoding encoding = null) => Reader.ReadFixedLengthString(length, encoding); 94 | 95 | public void WriteEndianBytes(byte[] bytes) => Writer.WriteEndianBytes(bytes); 96 | public void Write(long int64) => Writer.Write(int64); 97 | public void Write(ulong uint64) => Writer.Write(uint64); 98 | public void Write(int int32) => Writer.Write(int32); 99 | public void Write(uint uint32) => Writer.Write(uint32); 100 | public void Write(short int16) => Writer.Write(int16); 101 | public void Write(ushort uint16) => Writer.Write(uint16); 102 | 103 | public void Write(long addr, byte[] bytes) => Writer.Write(addr, bytes); 104 | public void Write(long addr, long int64) => Writer.Write(addr, int64); 105 | public void Write(long addr, ulong uint64) => Writer.Write(addr, uint64); 106 | public void Write(long addr, int int32) => Writer.Write(addr, int32); 107 | public void Write(long addr, uint uint32) => Writer.Write(addr, uint32); 108 | public void Write(long addr, short int16) => Writer.Write(addr, int16); 109 | public void Write(long addr, ushort uint16) => Writer.Write(addr, uint16); 110 | public void Write(long addr, byte value) => Writer.Write(addr, value); 111 | public void Write(long addr, bool value) => Writer.Write(addr, value); 112 | 113 | public void WriteObject(long addr, T obj) => Writer.WriteObject(addr, obj); 114 | public void WriteObject(T obj) => Writer.WriteObject(obj); 115 | 116 | public void WriteArray(long addr, T[] array) => Writer.WriteArray(addr, array); 117 | public void WriteArray(T[] array) => Writer.WriteArray(array); 118 | 119 | public void WriteNullTerminatedString(long addr, string str, Encoding encoding = null) => Writer.WriteNullTerminatedString(addr, str, encoding); 120 | public void WriteNullTerminatedString(string str, Encoding encoding = null) => Writer.WriteNullTerminatedString(str, encoding); 121 | public void WriteFixedLengthString(long addr, string str, int size = -1, Encoding encoding = null) => Writer.WriteFixedLengthString(addr, str, size, encoding); 122 | public void WriteFixedLengthString(string str, int size = -1, Encoding encoding = null) => Writer.WriteFixedLengthString(str, size, encoding); 123 | } 124 | } 125 | 126 | -------------------------------------------------------------------------------- /Bin2Object/BinaryObjectWriter.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Katy Coe - http://www.djkaty.com - https://github.com/djkaty/Bin2Object/ 2 | 3 | using System; 4 | using System.Collections.Generic; 5 | using System.IO; 6 | using System.Linq; 7 | using System.Reflection; 8 | using System.Text; 9 | 10 | namespace NoisyCowStudios.Bin2Object 11 | { 12 | public class BinaryObjectWriter : BinaryWriter 13 | { 14 | // Generic method cache to dramatically speed up repeated calls to WriteObject with the same T 15 | private Dictionary writeObjectGenericCache = new Dictionary(); 16 | 17 | // VersionAttribute cache to dramatically speed up repeated calls to ReadObject with the same T 18 | private Dictionary>> writeObjectVersionCache = new Dictionary>>(); 19 | 20 | // Thread synchronization objects (for thread safety) 21 | private object writeLock = new object(); 22 | 23 | // Initialization 24 | public BinaryObjectWriter(Stream stream, Endianness endianness = Endianness.Little, bool leaveOpen = false) : base(stream, Encoding.Default, leaveOpen) { 25 | Endianness = endianness; 26 | } 27 | 28 | // Position in the stream 29 | public long Position { 30 | get => BaseStream.Position; 31 | set => BaseStream.Position = value; 32 | } 33 | 34 | // Allows you to specify types which should be written as different types to the stream 35 | // Key: type in object; Value: type in stream 36 | public Dictionary PrimitiveMappings { get; } = new Dictionary(); 37 | 38 | public Endianness Endianness { get; set; } 39 | 40 | public double Version { get; set; } = 1; 41 | 42 | public Encoding Encoding { get; set; } = Encoding.UTF8; 43 | 44 | public void WriteEndianBytes(byte[] bytes) { 45 | Write(Endianness == Endianness.Little ? bytes : bytes.Reverse().ToArray()); 46 | } 47 | 48 | public override void Write(long int64) => WriteEndianBytes(BitConverter.GetBytes(int64)); 49 | 50 | public override void Write(ulong uint64) => WriteEndianBytes(BitConverter.GetBytes(uint64)); 51 | 52 | public override void Write(int int32) => WriteEndianBytes(BitConverter.GetBytes(int32)); 53 | 54 | public override void Write(uint uint32) => WriteEndianBytes(BitConverter.GetBytes(uint32)); 55 | 56 | public override void Write(short int16) => WriteEndianBytes(BitConverter.GetBytes(int16)); 57 | 58 | public override void Write(ushort uint16) => WriteEndianBytes(BitConverter.GetBytes(uint16)); 59 | 60 | public void Write(long addr, byte[] bytes) { 61 | lock (writeLock) { 62 | Position = addr; 63 | WriteEndianBytes(bytes); 64 | } 65 | } 66 | 67 | public void Write(long addr, long int64) { 68 | lock (writeLock) { 69 | Position = addr; 70 | Write(int64); 71 | } 72 | } 73 | 74 | public void Write(long addr, ulong uint64) { 75 | lock (writeLock) { 76 | Position = addr; 77 | Write(uint64); 78 | } 79 | } 80 | 81 | public void Write(long addr, int int32) { 82 | lock (writeLock) { 83 | Position = addr; 84 | Write(int32); 85 | } 86 | } 87 | 88 | public void Write(long addr, uint uint32) { 89 | lock (writeLock) { 90 | Position = addr; 91 | Write(uint32); 92 | } 93 | } 94 | 95 | public void Write(long addr, short int16) { 96 | lock (writeLock) { 97 | Position = addr; 98 | Write(int16); 99 | } 100 | } 101 | 102 | public void Write(long addr, ushort uint16) { 103 | lock (writeLock) { 104 | Position = addr; 105 | Write(uint16); 106 | } 107 | } 108 | 109 | public void Write(long addr, byte value) { 110 | lock (writeLock) { 111 | Position = addr; 112 | Write(value); 113 | } 114 | } 115 | 116 | public void Write(long addr, bool value) { 117 | lock (writeLock) { 118 | Position = addr; 119 | Write(value); 120 | } 121 | } 122 | 123 | public void WriteObject(long addr, T obj) { 124 | lock (writeLock) { 125 | Position = addr; 126 | WriteObject(obj); 127 | } 128 | } 129 | 130 | public void WriteObject(T obj) { 131 | var type = typeof(T); 132 | var ti = type.GetTypeInfo(); 133 | 134 | if (ti.IsPrimitive) { 135 | // Checked for mapped primitive types 136 | if ((from m in PrimitiveMappings where m.Key.GetTypeInfo().Name == type.Name select m.Value).FirstOrDefault() is Type mapping) { 137 | var mappedWriter = (from m in GetType().GetMethods() where m.Name == "Write" && m.GetParameters()[0].ParameterType == mapping && m.ReturnType == typeof(void) select m).FirstOrDefault(); 138 | mappedWriter?.Invoke(this, new object[] { obj }); 139 | return; 140 | } 141 | 142 | // Unmapped primitive 143 | switch (obj) { 144 | case long v: 145 | Write(v); 146 | break; 147 | case ulong v: 148 | Write(v); 149 | break; 150 | case int v: 151 | Write(v); 152 | break; 153 | case uint v: 154 | Write(v); 155 | break; 156 | case short v: 157 | Write(v); 158 | break; 159 | case ushort v: 160 | Write(v); 161 | break; 162 | case byte v: 163 | Write(v); 164 | break; 165 | case bool v: 166 | Write(v); 167 | break; 168 | default: 169 | throw new ArgumentException("Unsupported primitive type specified: " + type.FullName); 170 | } 171 | return; 172 | } 173 | 174 | // First time caching 175 | if (!writeObjectVersionCache.ContainsKey(type)) { 176 | var fields = new Dictionary>(); 177 | foreach (var i in type.GetFields()) 178 | fields.Add(i, i.GetCustomAttributes(false).Select(v => (v.Min, v.Max)).ToList()); 179 | 180 | writeObjectVersionCache.Add(type, fields); 181 | } 182 | 183 | foreach (var (i, versions) in writeObjectVersionCache[type]) { 184 | // Only process fields for our selected object versioning (always process if none supplied) 185 | if (versions.Any() && !versions.Any(v => (v.Min <= Version || v.Min == -1) && (v.Max >= Version || v.Max == -1))) 186 | continue; 187 | 188 | // String 189 | if (i.FieldType == typeof(string)) { 190 | var attr = i.GetCustomAttribute(false); 191 | 192 | // No String attribute? Use a null-terminated string by default 193 | if (attr == null || attr.IsNullTerminated) 194 | WriteNullTerminatedString((string) i.GetValue(obj)); 195 | else { 196 | if (attr.FixedSize <= 0) 197 | throw new ArgumentException("String attribute for array field " + i.Name + " configuration invalid"); 198 | WriteFixedLengthString((string) i.GetValue(obj), attr.FixedSize); 199 | } 200 | } 201 | 202 | // Array 203 | else if (i.FieldType.IsArray) { 204 | var attr = i.GetCustomAttribute(false) ?? 205 | throw new InvalidOperationException("Array field " + i.Name + " must have ArrayLength attribute"); 206 | 207 | int lengthPrimitive; 208 | 209 | if (attr.FieldName != null) { 210 | var field = type.GetField(attr.FieldName) ?? 211 | throw new ArgumentException("Array field " + i.Name + " has invalid FieldName in ArrayLength attribute"); 212 | lengthPrimitive = Convert.ToInt32(field.GetValue(obj)); 213 | } else if (attr.FixedSize > 0) { 214 | lengthPrimitive = attr.FixedSize; 215 | } else { 216 | throw new ArgumentException("ArrayLength attribute for array field " + i.Name + " configuration invalid"); 217 | } 218 | 219 | var arr = i.GetValue(obj); 220 | var us = GetType().GetMethods().Where(m => m.Name == "WriteArray" && m.GetParameters().Length == 1 && m.IsGenericMethodDefinition).Single(); 221 | var mi2 = us.MakeGenericMethod(i.FieldType.GetElementType()); 222 | mi2.Invoke(this, new object[] { arr }); 223 | } 224 | 225 | // Primitive type 226 | // This is unnecessary but saves on many generic Invoke calls which are really slow 227 | else if (i.FieldType.IsPrimitive) { 228 | // Checked for mapped primitive types 229 | if ((from m in PrimitiveMappings where m.Key.GetTypeInfo().Name == i.FieldType.Name select m.Value).FirstOrDefault() is Type mapping) { 230 | var mappedWriter = (from m in GetType().GetMethods() where m.Name == "Write" && m.GetParameters()[0].ParameterType == mapping && m.ReturnType == typeof(void) select m).FirstOrDefault(); 231 | mappedWriter?.Invoke(this, new object[] { Convert.ChangeType(i.GetValue(obj), mapping) }); 232 | } else { 233 | // Unmapped primitive type 234 | switch (i.GetValue(obj)) { 235 | case long v: 236 | Write(v); 237 | break; 238 | case ulong v: 239 | Write(v); 240 | break; 241 | case int v: 242 | Write(v); 243 | break; 244 | case uint v: 245 | Write(v); 246 | break; 247 | case short v: 248 | Write(v); 249 | break; 250 | case ushort v: 251 | Write(v); 252 | break; 253 | case byte v: 254 | Write(v); 255 | break; 256 | case bool v: 257 | Write(v); 258 | break; 259 | default: 260 | throw new ArgumentException("Unsupported primitive type specified: " + type.FullName); 261 | } 262 | } 263 | } 264 | 265 | // Object 266 | else { 267 | if (!writeObjectGenericCache.TryGetValue(i.FieldType.FullName, out MethodInfo mi2)) { 268 | var us = GetType().GetMethods().Where(m => m.Name == "WriteObject" && m.IsGenericMethodDefinition).Single(); 269 | mi2 = us.MakeGenericMethod(i.FieldType); 270 | writeObjectGenericCache.Add(i.FieldType.FullName, mi2); 271 | } 272 | mi2.Invoke(this, new[] { i.GetValue(obj) }); 273 | } 274 | } 275 | } 276 | 277 | public void WriteArray(long addr, T[] array) { 278 | lock (writeLock) { 279 | Position = addr; 280 | WriteArray(array); 281 | } 282 | } 283 | 284 | public void WriteArray(T[] array) { 285 | for (int i = 0; i < array.Length; i++) { 286 | WriteObject(array[i]); 287 | } 288 | } 289 | 290 | public void WriteNullTerminatedString(long addr, string str, Encoding encoding = null) { 291 | lock (writeLock) { 292 | Position = addr; 293 | WriteNullTerminatedString(str, encoding); 294 | } 295 | } 296 | 297 | public void WriteNullTerminatedString(string str, Encoding encoding = null) { 298 | WriteFixedLengthString(str, str.Length + 1, encoding); 299 | } 300 | 301 | // The difference between this and BinaryWriter.Write(string) is that the latter adds a length prefix before the string 302 | public void WriteFixedLengthString(long addr, string str, int size = -1, Encoding encoding = null) { 303 | lock (writeLock) { 304 | Position = addr; 305 | WriteFixedLengthString(str, size, encoding); 306 | } 307 | } 308 | 309 | public void WriteFixedLengthString(string str, int size = -1, Encoding encoding = null) { 310 | var bytes = encoding?.GetBytes(str) ?? Encoding.GetBytes(str); 311 | Write(bytes); 312 | 313 | if (size != -1) 314 | for (var padding = str.Length; padding < size; padding++) 315 | Write((byte) 0); 316 | } 317 | } 318 | } 319 | -------------------------------------------------------------------------------- /Bin2Object/Endianness.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017-2020 Katy Coe - http://www.djkaty.com - https://github.com/djkaty/Bin2Object/ 2 | 3 | namespace NoisyCowStudios.Bin2Object 4 | { 5 | public enum Endianness 6 | { 7 | Little, 8 | Big 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Perfare - https://github.com/Perfare/Il2CppDumper/ 4 | Copyright (c) 2016 Alican Cubukcuoglu - https://github.com/AlicanC/AlicanC-s-Modern-Warfare-2-Tool/ 5 | Copyright (c) 2017-2020 Katy Coe - http://www.djkaty.com - https://github.com/djkaty/Bin2Object/ 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in all 15 | copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | SOFTWARE. 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Bin2Object 2 | A BinaryReader and BinaryWriter which can serialize and deserialize arbitrary binary formats into objects and vice versa. 3 | 4 | Built against .NET Standard 2.1 (.NET Core 3.1). 5 | 6 | See Tests\Tests.cs for usage examples. 7 | 8 | www.djkaty.com 9 | 10 | Copyright © 2016 Perfare - https://github.com/Perfare/Il2CppDumper/ 11 | 12 | Copyright © 2016 Alican Cubukcuoglu - https://github.com/AlicanC/AlicanC-s-Modern-Warfare-2-Tool/ 13 | 14 | Copyright © 2017-2020 Katy Coe - http://www.djkaty.com - https://github.com/djkaty/Bin2Object/ 15 | -------------------------------------------------------------------------------- /Tests/Tests.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017-2019 Katy Coe - http://www.djkaty.com - https://github.com/djkaty/Bin2Object/ 2 | 3 | using System; 4 | using System.IO; 5 | using NUnit.Framework; 6 | using NoisyCowStudios.Bin2Object; 7 | using System.Linq; 8 | 9 | namespace Tests 10 | { 11 | // Stop the "this item is never used" compiler spam 12 | #pragma warning disable CS0649 13 | class TestObject 14 | { 15 | public int a; 16 | public short b; 17 | // No String attribute specified - use null-terminated by default 18 | public string c; 19 | public byte d; 20 | } 21 | 22 | class TestMappedObject 23 | { 24 | [SkipWhenReading] 25 | public string c = "XYZ"; 26 | 27 | public int e; // Field does not exist in target 28 | 29 | public short a; // Map from a shorter primitive type 30 | public ulong d; // Map from a longer primitive type 31 | public short b; 32 | } 33 | 34 | class TestObjectWithArrays 35 | { 36 | public int numberOfItems; 37 | [ArrayLength(FieldName = "numberOfItems")] 38 | public ushort[] itemArray; 39 | [ArrayLength(FixedSize = 5)] 40 | public byte[] fiveItems; 41 | } 42 | 43 | class TestObjectWithStrings 44 | { 45 | [String(FixedSize = 8)] 46 | public string eightCharString; 47 | [String(FixedSize = 4)] 48 | public string fourCharString; 49 | [String(IsNullTerminated = true)] 50 | public string nullTerminatedString; 51 | } 52 | 53 | class TestObjectWithVersioning 54 | { 55 | public int allVersionsItem; 56 | [Version(Min = 2)] 57 | public string version2AndHigherItem; 58 | [Version(Max = 1)] 59 | public string version1Item; 60 | [Version(Min = 1, Max = 2)] 61 | [String(IsNullTerminated = true)] // adding a 2nd attribute to prove it works 62 | public string version1And2Item; 63 | } 64 | 65 | class TestObjectWithMultiVersioning 66 | { 67 | public byte a; 68 | [Version(Min = 1, Max = 2)] 69 | [Version(Min = 4)] 70 | public byte b; 71 | public byte c; 72 | } 73 | 74 | class TestObjectWithPrimitiveMapping 75 | { 76 | public int int1; 77 | public int int2; 78 | public bool bool1; 79 | public long long1; 80 | } 81 | #pragma warning restore CS0649 82 | 83 | [TestFixture] 84 | class Tests 85 | { 86 | [Test] 87 | public void TestPrimitives([Values(Endianness.Little, Endianness.Big)] Endianness endianness) { 88 | var testData = new byte[] {0x04, 0x03, 0x02, 0x01, 0xFF, 0xFF, 0xFF, 0xFF}; 89 | bool bigEndian = endianness == Endianness.Big; 90 | 91 | using (var stream = new MemoryStream(testData)) 92 | using (var reader = new BinaryObjectReader(stream, endianness)) { 93 | // Raw bytes 94 | reader.Position = 0; 95 | 96 | var bytes = reader.ReadBytes(8); 97 | Assert.AreEqual(8, bytes.Length); 98 | for (var i = 0; i < bytes.Length; i++) 99 | if (bigEndian) 100 | Assert.AreEqual(testData[i], bytes[bytes.Length - i - 1]); 101 | else 102 | Assert.AreEqual(testData[i], bytes[i]); 103 | 104 | // Primitives with endianness 105 | reader.Position = 0; 106 | 107 | try { 108 | Assert.AreEqual(bigEndian ? 0x04030201FFFFFFFF : 0xFFFFFFFF01020304, reader.ReadInt64()); 109 | } 110 | catch (OverflowException) { } 111 | reader.Position -= 8; 112 | try { 113 | Assert.AreEqual(bigEndian ? 0x04030201FFFFFFFF : 0xFFFFFFFF01020304, reader.ReadUInt64()); 114 | } 115 | catch (OverflowException) { } 116 | reader.Position -= 8; 117 | Assert.AreEqual(bigEndian ? 0x04030201 : 0x01020304, reader.ReadInt32()); 118 | reader.Position -= 4; 119 | Assert.AreEqual(bigEndian ? 0x04030201 : 0x01020304, reader.ReadUInt32()); 120 | reader.Position -= 4; 121 | Assert.AreEqual(bigEndian ? 0x0403 : 0x0304, reader.ReadInt16()); 122 | reader.Position -= 2; 123 | Assert.AreEqual(bigEndian ? 0x0403 : 0x0304, reader.ReadUInt16()); 124 | 125 | // Signedness 126 | reader.Position = 4; 127 | 128 | Assert.AreEqual(-1, reader.ReadInt32()); 129 | reader.Position -= 4; 130 | Assert.AreEqual(0xFFFFFFFF, reader.ReadUInt32()); 131 | 132 | reader.Position = 6; 133 | 134 | Assert.AreEqual(-1, reader.ReadInt16()); 135 | reader.Position -= 2; 136 | Assert.AreEqual(0xFFFF, reader.ReadUInt16()); 137 | } 138 | } 139 | 140 | [Test] 141 | public void TestNullTerminatedString() { 142 | var testData = new byte[] {0x41, 0x42, 0x43, 0x44, 0x00}; // ABCD\0 143 | 144 | using (var stream = new MemoryStream(testData)) 145 | using (var reader = new BinaryObjectReader(stream)) { 146 | var str = reader.ReadNullTerminatedString(); 147 | Assert.AreEqual("ABCD", str); 148 | } 149 | } 150 | 151 | [Test] 152 | public void TestObject([Values(Endianness.Little, Endianness.Big)] Endianness endianness) { 153 | byte[] testData; 154 | 155 | if (endianness == Endianness.Little) 156 | testData = new byte[] {0x04, 0x03, 0x02, 0x01, 0x34, 0x12, 0x41, 0x42, 0x43, 0x44, 0x00, 0x99}; 157 | else { 158 | testData = new byte[] {0x01, 0x02, 0x03, 0x04, 0x12, 0x34, 0x41, 0x42, 0x43, 0x44, 0x00, 0x99}; 159 | } 160 | 161 | using (var stream = new MemoryStream(testData)) 162 | using (var reader = new BinaryObjectReader(stream, endianness)) { 163 | var obj = reader.ReadObject(); 164 | 165 | Assert.AreEqual(0x01020304, obj.a); 166 | Assert.AreEqual(0x1234, obj.b); 167 | Assert.That("ABCD" == obj.c); 168 | Assert.AreEqual(0x99, obj.d); 169 | } 170 | } 171 | 172 | [Test] 173 | public void TestArray() { 174 | var testData = new byte[] {0x34, 0x12, 0x78, 0x56, 0xFF, 0xFF}; 175 | 176 | using (var stream = new MemoryStream(testData)) 177 | using (var reader = new BinaryObjectReader(stream)) { 178 | var arr = reader.ReadArray(3); 179 | 180 | Assert.AreEqual(3, arr.Length); 181 | Assert.AreEqual(0x1234, arr[0]); 182 | Assert.AreEqual(0x5678, arr[1]); 183 | Assert.AreEqual(-1, arr[2]); 184 | } 185 | } 186 | 187 | [Test] 188 | public void TestArrayOfObject([Values(Endianness.Little, Endianness.Big)] Endianness endianness) { 189 | byte[] testData; 190 | 191 | if (endianness == Endianness.Little) 192 | testData = new byte[] { 193 | 0x04, 0x03, 0x02, 0x01, 0x34, 0x12, 0x41, 0x42, 0x43, 0x44, 0x00, 0x99, 194 | 0x05, 0x03, 0x02, 0x01, 0x35, 0x12, 0x41, 0x42, 0x43, 0x45, 0x00, 0x9A 195 | }; 196 | else { 197 | testData = new byte[] { 198 | 0x01, 0x02, 0x03, 0x04, 0x12, 0x34, 0x41, 0x42, 0x43, 0x44, 0x00, 0x99, 199 | 0x01, 0x02, 0x03, 0x05, 0x12, 0x35, 0x41, 0x42, 0x43, 0x45, 0x00, 0x9A, 200 | }; 201 | } 202 | 203 | using (var stream = new MemoryStream(testData)) 204 | using (var reader = new BinaryObjectReader(stream, endianness)) { 205 | var objArr = reader.ReadArray(2); 206 | 207 | Assert.AreEqual(2, objArr.Length); 208 | 209 | Assert.AreEqual(0x01020304, objArr[0].a); 210 | Assert.AreEqual(0x1234, objArr[0].b); 211 | Assert.That("ABCD" == objArr[0].c); 212 | Assert.AreEqual(0x99, objArr[0].d); 213 | 214 | Assert.AreEqual(0x01020305, objArr[1].a); 215 | Assert.AreEqual(0x1235, objArr[1].b); 216 | Assert.That("ABCE" == objArr[1].c); 217 | Assert.AreEqual(0x9A, objArr[1].d); 218 | } 219 | } 220 | 221 | [Test] 222 | public void TestObjectWithArrays() { 223 | var testData = new byte[] 224 | {0x03, 0x00, 0x00, 0x00, 0x34, 0x12, 0x78, 0x56, 0xBC, 0x9A, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E}; 225 | 226 | using (var stream = new MemoryStream(testData)) 227 | using (var reader = new BinaryObjectReader(stream)) { 228 | var obj = reader.ReadObject(); 229 | 230 | Assert.AreEqual(3, obj.numberOfItems); 231 | Assert.AreEqual(3, obj.itemArray.Length); 232 | Assert.AreEqual(0x1234, obj.itemArray[0]); 233 | Assert.AreEqual(0x5678, obj.itemArray[1]); 234 | Assert.AreEqual(0x9ABC, obj.itemArray[2]); 235 | for (int i = 0; i < 5; i++) 236 | Assert.AreEqual(i + 0x0A, obj.fiveItems[i]); 237 | } 238 | } 239 | 240 | [Test] 241 | public void TestObjectWithStrings() { 242 | var testData = new byte[] 243 | {0x41, 0x42, 0x43, 0x44, 0x45, 0x00, 0x00, 0x00, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x00}; 244 | 245 | using (var stream = new MemoryStream(testData)) 246 | using (var reader = new BinaryObjectReader(stream)) { 247 | var obj = reader.ReadObject(); 248 | 249 | Assert.AreEqual("ABCDE", obj.eightCharString); 250 | Assert.AreEqual("FGHI", obj.fourCharString); 251 | Assert.AreEqual("JK", obj.nullTerminatedString); 252 | } 253 | } 254 | 255 | [Test] 256 | public void TestObjectWithVersioning() { 257 | var testData = new byte[] 258 | {0x34, 0x12, 0x00, 0x00, 0x41, 0x00, 0x42, 0x00, 0x43, 0x00}; 259 | 260 | for (int v = 1; v <= 3; v++) 261 | using (var stream = new MemoryStream(testData)) 262 | using (var reader = new BinaryObjectReader(stream)) { 263 | reader.Version = v; 264 | var obj = reader.ReadObject(); 265 | 266 | Assert.AreEqual(0x1234, obj.allVersionsItem); 267 | Assert.AreEqual((v == 1 ? "A" : null), obj.version1Item); 268 | Assert.AreEqual((v >= 2 ? "A" : null), obj.version2AndHigherItem); 269 | Assert.AreEqual((v < 3 ? "B" : null), obj.version1And2Item); 270 | } 271 | } 272 | 273 | [Test] 274 | public void TestObjectWithMultiVersioning() { 275 | var testData = new byte[] 276 | {0x01, 0x02, 0x03}; 277 | 278 | for (int v = 1; v <= 5; v++) 279 | using (var stream = new MemoryStream(testData)) 280 | using (var reader = new BinaryObjectReader(stream)) { 281 | reader.Version = v; 282 | var obj = reader.ReadObject(); 283 | 284 | Assert.AreEqual(0x01, obj.a); 285 | Assert.AreEqual(v != 3 ? 0x02 : 0x00, obj.b); 286 | Assert.AreEqual(v != 3 ? 0x03 : 0x02, obj.c); 287 | } 288 | } 289 | 290 | [Test] 291 | public void TestMappedPrimitives() { 292 | var testData = new byte[] 293 | {0x01, 0x02, 0x01, 0x87, 0x65, 0x43, 0x21}; 294 | 295 | using (var stream = new MemoryStream(testData)) 296 | using (var reader = new BinaryObjectReader(stream)) { 297 | reader.PrimitiveMappings.Add(typeof(int).Name, typeof(byte)); 298 | reader.PrimitiveMappings.Add(typeof(bool).Name, typeof(byte)); 299 | reader.PrimitiveMappings.Add(typeof(long).Name, typeof(int)); 300 | 301 | var obj = reader.ReadObject(); 302 | 303 | Assert.AreEqual(obj.int1, 1); 304 | Assert.AreEqual(obj.int2, 2); 305 | Assert.AreEqual(obj.bool1, true); 306 | Assert.AreEqual(obj.long1, 0x21436587); 307 | } 308 | } 309 | 310 | [Test] 311 | public void TestMappedObject() { 312 | 313 | var testData = new byte[] 314 | { 0xCC, 0xCC, 0xCC, 0xCC, 0x02, 0x01, 0x99, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x34, 0x12 }; 315 | 316 | using var stream = new MemoryStream(testData); 317 | using var reader = new BinaryObjectReader(stream); 318 | 319 | reader.ObjectMappings.Add(typeof(TestObject), typeof(TestMappedObject)); 320 | 321 | var obj = reader.ReadObject(); 322 | 323 | Assert.AreEqual(0x0102, obj.a); 324 | Assert.AreEqual(0x1234, obj.b); 325 | Assert.That("XYZ" == obj.c); 326 | Assert.AreEqual(0x99, obj.d); 327 | } 328 | 329 | [Test] 330 | public void TestWriterPrimitives() { 331 | var expected = new byte[] 332 | { 0x01, 0x02, 0x03, 333 | 0xef, 0xcd, 0xab, 0x89, 0x67, 0x45, 0x23, 0x01, 334 | 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, 335 | 0x78, 0x56, 0x34, 0x12, 0x98, 0xba, 0xdc, 0xfe, 336 | 0x34, 0x12, 0xdc, 0xfe, 0xcc, 0x01 }; 337 | 338 | using var stream = new MemoryStream(); 339 | using (var writer = new BinaryObjectWriter(stream)) { 340 | writer.WriteEndianBytes(new byte[] { 0x01, 0x02, 0x03 }); 341 | writer.Write((long) 0x0123456789abcdef); 342 | writer.Write((ulong) 0xffeeddccbbaa9988); 343 | writer.Write((int) 0x12345678); 344 | writer.Write((uint) 0xfedcba98); 345 | writer.Write((short) 0x1234); 346 | writer.Write((ushort) 0xfedc); 347 | writer.Write((byte) 0xcc); 348 | writer.Write(true); 349 | } 350 | var bytes = stream.ToArray(); 351 | Assert.That(Enumerable.SequenceEqual(bytes, expected)); 352 | } 353 | 354 | [Test] 355 | public void TestWriterStrings() { 356 | var expected = new byte[] 357 | { 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x00, 358 | 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x00, 359 | 0x74, 0x65, 0x73, 0x74, 360 | 0x74, 0x65, 0x73, 0x74, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; 361 | 362 | using var stream = new MemoryStream(); 363 | using (var writer = new BinaryObjectWriter(stream)) { 364 | writer.WriteNullTerminatedString("hello"); 365 | writer.WriteNullTerminatedString("world"); 366 | writer.WriteFixedLengthString("test"); 367 | writer.WriteFixedLengthString("test", 10); 368 | } 369 | var bytes = stream.ToArray(); 370 | Assert.That(Enumerable.SequenceEqual(bytes, expected)); 371 | } 372 | 373 | [Test] 374 | public void TestWriterObject([Values(Endianness.Little, Endianness.Big)] Endianness endianness) { 375 | var expected = 376 | endianness == Endianness.Little? new byte[] 377 | { 0x78, 0x56, 0x34, 0x12, 0x34, 0x12, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x00, 0xff } 378 | 379 | : new byte[] 380 | { 0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x00, 0xff }; 381 | 382 | var obj = new TestObject { 383 | a = 0x12345678, 384 | b = 0x1234, 385 | c = "hello", 386 | d = 0xff 387 | }; 388 | 389 | using var stream = new MemoryStream(); 390 | using (var writer = new BinaryObjectWriter(stream, endianness)) { 391 | writer.WriteObject(obj); 392 | } 393 | var bytes = stream.ToArray(); 394 | Assert.That(Enumerable.SequenceEqual(bytes, expected)); 395 | } 396 | 397 | [Test] 398 | public void TestWriterArray() { 399 | var expected = new byte[] { 0x34, 0x12, 0x78, 0x56, 0xff, 0xff }; 400 | 401 | var testData = new short[] { 0x1234, 0x5678, -1 }; 402 | 403 | using var stream = new MemoryStream(); 404 | using (var writer = new BinaryObjectWriter(stream)) { 405 | writer.WriteArray(testData); 406 | } 407 | var bytes = stream.ToArray(); 408 | Assert.That(Enumerable.SequenceEqual(bytes, expected)); 409 | } 410 | 411 | [Test] 412 | public void TestWriterArrayOfObject([Values(Endianness.Little, Endianness.Big)] Endianness endianness) { 413 | byte[] expected; 414 | 415 | if (endianness == Endianness.Little) 416 | expected = new byte[] { 417 | 0x04, 0x03, 0x02, 0x01, 0x34, 0x12, 0x41, 0x42, 0x43, 0x44, 0x00, 0x99, 418 | 0x05, 0x03, 0x02, 0x01, 0x35, 0x12, 0x41, 0x42, 0x43, 0x45, 0x00, 0x9A 419 | }; 420 | else { 421 | expected = new byte[] { 422 | 0x01, 0x02, 0x03, 0x04, 0x12, 0x34, 0x41, 0x42, 0x43, 0x44, 0x00, 0x99, 423 | 0x01, 0x02, 0x03, 0x05, 0x12, 0x35, 0x41, 0x42, 0x43, 0x45, 0x00, 0x9A, 424 | }; 425 | } 426 | 427 | var testData = new TestObject[] { 428 | new TestObject { a = 0x01020304, b = 0x1234, c = "ABCD", d = 0x99 }, 429 | new TestObject { a = 0x01020305, b = 0x1235, c = "ABCE", d = 0x9A } 430 | }; 431 | 432 | using var stream = new MemoryStream(); 433 | using (var writer = new BinaryObjectWriter(stream, endianness)) { 434 | writer.WriteArray(testData); 435 | } 436 | var bytes = stream.ToArray(); 437 | Assert.That(Enumerable.SequenceEqual(bytes, expected)); 438 | } 439 | 440 | [Test] 441 | public void TestWriterObjectWithArrays() { 442 | var expected = new byte[] 443 | {0x03, 0x00, 0x00, 0x00, 0x34, 0x12, 0x78, 0x56, 0xBC, 0x9A, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E}; 444 | 445 | var testData = new TestObjectWithArrays { 446 | numberOfItems = 3, 447 | itemArray = new ushort[] { 0x1234, 0x5678, 0x9ABC }, 448 | fiveItems = new byte[] { 0x0A, 0x0B, 0x0C, 0x0D, 0x0E } 449 | }; 450 | 451 | using var stream = new MemoryStream(); 452 | using (var writer = new BinaryObjectWriter(stream)) { 453 | writer.WriteObject(testData); 454 | } 455 | var bytes = stream.ToArray(); 456 | Assert.That(Enumerable.SequenceEqual(bytes, expected)); 457 | } 458 | 459 | [Test] 460 | public void TestWriterObjectWithStrings() { 461 | var expected = new byte[] 462 | {0x41, 0x42, 0x43, 0x44, 0x45, 0x00, 0x00, 0x00, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x00}; 463 | 464 | var testData = new TestObjectWithStrings { 465 | eightCharString = "ABCDE", 466 | fourCharString = "FGHI", 467 | nullTerminatedString = "JK" 468 | }; 469 | 470 | using var stream = new MemoryStream(); 471 | using (var writer = new BinaryObjectWriter(stream)) { 472 | writer.WriteObject(testData); 473 | } 474 | var bytes = stream.ToArray(); 475 | Assert.That(Enumerable.SequenceEqual(bytes, expected)); 476 | } 477 | 478 | [Test] 479 | public void TestWriterObjectWithVersioning() { 480 | var expected = new byte[][] { 481 | new byte[] {0x34, 0x12, 0x00, 0x00, 0x41, 0x00, 0x43, 0x00 }, 482 | new byte[] {0x34, 0x12, 0x00, 0x00, 0x42, 0x00, 0x43, 0x00 }, 483 | new byte[] {0x34, 0x12, 0x00, 0x00, 0x42, 0x00 } 484 | }; 485 | 486 | var testData = new TestObjectWithVersioning { 487 | allVersionsItem = 0x1234, 488 | version1Item = "A", 489 | version2AndHigherItem = "B", 490 | version1And2Item = "C" 491 | }; 492 | 493 | for (int v = 1; v <= 3; v++) 494 | using (var stream = new MemoryStream()) { 495 | using (var writer = new BinaryObjectWriter(stream)) { 496 | writer.Version = v; 497 | writer.WriteObject(testData); 498 | } 499 | var bytes = stream.ToArray(); 500 | Assert.That(Enumerable.SequenceEqual(bytes, expected[v - 1])); 501 | } 502 | } 503 | 504 | [Test] 505 | public void TestWriterObjectWithMultiVersioning() { 506 | var expected = new byte[][] { 507 | new byte[] {0x01, 0x02, 0x03}, 508 | new byte[] {0x01, 0x02, 0x03}, 509 | new byte[] {0x01, 0x03}, 510 | new byte[] {0x01, 0x02, 0x03}, 511 | new byte[] {0x01, 0x02, 0x03}, 512 | }; 513 | 514 | var testData = new TestObjectWithMultiVersioning { 515 | a = 0x01, 516 | b = 0x02, 517 | c = 0x03 518 | }; 519 | 520 | for (int v = 1; v <= 5; v++) 521 | using (var stream = new MemoryStream()) { 522 | using (var writer = new BinaryObjectWriter(stream)) { 523 | writer.Version = v; 524 | writer.WriteObject(testData); 525 | } 526 | var bytes = stream.ToArray(); 527 | Assert.That(Enumerable.SequenceEqual(bytes, expected[v - 1])); 528 | } 529 | } 530 | 531 | [Test] 532 | public void TestWriterMappedPrimitives() { 533 | var expected = new byte[] 534 | {0x01, 0x02, 0x01, 0x87, 0x65, 0x43, 0x21}; 535 | 536 | var testData = new TestObjectWithPrimitiveMapping { 537 | int1 = 1, 538 | int2 = 2, 539 | bool1 = true, 540 | long1 = 0x21436587 541 | }; 542 | 543 | using var stream = new MemoryStream(); 544 | using (var writer = new BinaryObjectWriter(stream)) { 545 | writer.PrimitiveMappings.Add(typeof(int), typeof(byte)); 546 | writer.PrimitiveMappings.Add(typeof(bool), typeof(byte)); 547 | writer.PrimitiveMappings.Add(typeof(long), typeof(int)); 548 | 549 | writer.WriteObject(testData); 550 | } 551 | var bytes = stream.ToArray(); 552 | Assert.That(Enumerable.SequenceEqual(bytes, expected)); 553 | } 554 | } 555 | } 556 | -------------------------------------------------------------------------------- /Tests/Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp3.1 5 | any 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | --------------------------------------------------------------------------------