├── DQAsset ├── DQAsset.csproj ├── UE4Versions.cs ├── ISerializable.cs ├── UE4Structs.cs ├── Program.cs ├── PackageFileSummary.cs ├── FTableRowBase.cs ├── Shared.cs └── UAsset.cs ├── DQAsset.sln ├── README.md └── .gitignore /DQAsset/DQAsset.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | netcoreapp3.1 6 | 7 | 8 | 9 | none 10 | false 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /DQAsset/UE4Versions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace DQAsset 6 | { 7 | public enum UE4Versions : int 8 | { 9 | VER_UE4_OLDEST_LOADABLE_PACKAGE = 214, 10 | VER_UE4_WORLD_LEVEL_INFO = 224, 11 | VER_UE4_ADDED_CHUNKID_TO_ASSETDATA_AND_UPACKAGE = 278, 12 | VER_UE4_CHANGED_CHUNKID_TO_BE_AN_ARRAY_OF_CHUNKIDS = 326, 13 | VER_UE4_ENGINE_VERSION_OBJECT = 336, 14 | VER_UE4_LOAD_FOR_EDITOR_GAME = 365, 15 | VER_UE4_ADD_STRING_ASSET_REFERENCES_MAP = 384, 16 | VER_UE4_PACKAGE_SUMMARY_HAS_COMPATIBLE_ENGINE_VERSION = 444, 17 | VER_UE4_SERIALIZE_TEXT_IN_PACKAGES = 459, 18 | VER_UE4_COOKED_ASSETS_IN_EDITOR_SUPPORT = 485, 19 | VER_UE4_NAME_HASHES_SERIALIZED = 504, 20 | VER_UE4_PRELOAD_DEPENDENCIES_IN_COOKED_EXPORTS = 507, 21 | VER_UE4_TemplateIndex_IN_COOKED_EXPORTS = 508, 22 | VER_UE4_ADDED_SEARCHABLE_NAMES = 510, 23 | VER_UE4_64BIT_EXPORTMAP_SERIALSIZES = 511, 24 | VER_UE4_ADDED_PACKAGE_SUMMARY_LOCALIZATION_ID = 516, 25 | VER_UE4_ADDED_PACKAGE_OWNER = 518, 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /DQAsset.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.30907.101 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DQAsset", "DQAsset\DQAsset.csproj", "{E8A8537F-3579-40FE-9057-41BB770731F7}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {E8A8537F-3579-40FE-9057-41BB770731F7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {E8A8537F-3579-40FE-9057-41BB770731F7}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {E8A8537F-3579-40FE-9057-41BB770731F7}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {E8A8537F-3579-40FE-9057-41BB770731F7}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {1C49CF2B-A940-4836-89AD-CE9449F1B949} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DQXIAsset 2 | UAsset <-> CSV converter for DQXIS/UE4 DataTables, supporting almost all DQXIS table structs. 3 | 4 | The UE4 package reading code here is pretty tightly coupled with the packages used by DQXIS, and not likely to work well with any other UE4 titles (unless they happened to be based on DQXIS's UE4 branch or something) 5 | 6 | DQXIS datatables are kinda unique from other UE4 games datatables, instead of each row containing properties that describe each field name/type/value, rows instead have a single StructProperty inside them, which contains the row data pretty much as a C/C++ struct/binary blob. 7 | 8 | Fortunately for us the format of these StructProperties is stored inside the EXE (as can be seen in [DQXIS-SDK](https://github.com/emoose/DQXIS-SDK)), though the format defined in the EXE doesn't always seem to match up with the actual row data, oftentimes having certain fields moved around/reordered, and padding fields added/removed. 9 | 10 | There are a couple datatables in DQXIS that do use the normal UE4 datatable format though, these seem to be denoted by a "UDS" prefix ('user-defined struct'). 11 | I've tried adding some support for these in here but it's not that great since I didn't design for that kind of table, you'd probably have better luck editing those with [kaiheilos's Uasset Viewer tool](https://github.com/kaiheilos/Utilities). 12 | 13 | --- 14 | 15 | For info about using this tool see the Releases page: https://github.com/emoose/DQXIAsset/releases/ 16 | -------------------------------------------------------------------------------- /DQAsset/ISerializable.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Text; 5 | 6 | namespace DQAsset 7 | { 8 | public interface ISerializable 9 | { 10 | void Deserialize(BinaryReader reader, PackageFile package); 11 | 12 | void Serialize(BinaryWriter writer, PackageFile package); 13 | } 14 | 15 | public interface ISerializableText : ISerializable 16 | { 17 | void DeserializeText(string text, PackageFile package); 18 | string SerializeText(PackageFile package, bool isMainElement); 19 | string SerializeTextHeader(PackageFile package); 20 | } 21 | 22 | [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.GenericParameter | AttributeTargets.Class)] 23 | public class SerializerAttribute : Attribute 24 | { 25 | // Struct size isn't contained in the uexp data, maybe there's a flag in uasset somewhere that defines this? 26 | public bool NoStructSize { get; set; } 27 | 28 | // Number of bytes/array elements 29 | public int Size { get; set; } 30 | 31 | // Won't be written into/read from CSV file, eg. for padding bytes, if used make sure to remove PropertiesData.Clear() line from Program.cs! 32 | public bool Hidden { get; set; } 33 | 34 | // Only serializes/deserializes if field is set 35 | public string OnlyIfSet { get; set; } 36 | public string OnlyIfNotSet { get; set; } 37 | 38 | 39 | public string[] OnlyIfAllSet { get; set; } 40 | public string[] OnlyIfAllNotSet { get; set; } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /DQAsset/UE4Structs.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace DQAsset 5 | { 6 | public struct GenerationInfo 7 | { 8 | public int ExportCount; 9 | public int NameCount; 10 | } 11 | 12 | public struct CompressedChunk 13 | { 14 | public int UncompressedOffset; 15 | public int UncompressedSize; 16 | public int CompressedOffset; 17 | public int CompressedSize; 18 | } 19 | 20 | public class FVector2D : FTableRowBase 21 | { 22 | public float X; 23 | public float Y; 24 | } 25 | 26 | public class FVector : FTableRowBase 27 | { 28 | public float X; 29 | public float Y; 30 | public float Z; 31 | } 32 | 33 | public class FRotator : FTableRowBase 34 | { 35 | public float Pitch; 36 | public float Yaw; 37 | public float Roll; 38 | } 39 | 40 | public class FLinearColor : FTableRowBase 41 | { 42 | public float R; 43 | public float G; 44 | public float B; 45 | public float A; 46 | } 47 | 48 | // ScriptStruct CoreUObject.Quat 49 | // 0x0010 50 | public class FQuat : FTableRowBase 51 | { 52 | public float X; // 0x0000(0x0004) (Edit, BlueprintVisi, ZeroConstructor, SaveGame, IsPlainOldData) 53 | public float Y; // 0x0004(0x0004) (Edit, BlueprintVisi, ZeroConstructor, SaveGame, IsPlainOldData) 54 | public float Z; // 0x0008(0x0004) (Edit, BlueprintVisi, ZeroConstructor, SaveGame, IsPlainOldData) 55 | public float W; // 0x000C(0x0004) (Edit, BlueprintVisi, ZeroConstructor, SaveGame, IsPlainOldData) 56 | } 57 | 58 | // ScriptStruct CoreUObject.Transform 59 | // 0x0030 60 | public class FTransform : FTableRowBase 61 | { 62 | public FQuat Rotation; // 0x0000(0x0010) (Edit, BlueprintVisi, SaveGame, IsPlainOldData) 63 | public FVector Translation; // 0x0010(0x000C) (Edit, BlueprintVisi, SaveGame, IsPlainOldData) 64 | public FVector Scale3D; // 0x0020(0x000C) (Edit, BlueprintVisi, SaveGame, IsPlainOldData) 65 | } 66 | 67 | public class UObject : FTableRowBase 68 | { 69 | public FName Unknown0x0; // 0x0008(0x0008) (Edit, BlueprintVisi, BlueprintReadOnly, ZeroConstructor, DisableEditOnInstance, IsPlainOldData) 70 | public FName Unknown0x8; 71 | public FName Unknown0x10; 72 | public byte Unknown0x18; 73 | public int ClassIndex; 74 | public FName Unknown0x1D; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Mono auto generated files 17 | mono_crash.* 18 | 19 | # Build results 20 | [Dd]ebug/ 21 | [Dd]ebugPublic/ 22 | [Rr]elease/ 23 | [Rr]eleases/ 24 | [Rr]elease-CI/ 25 | x64/ 26 | x86/ 27 | [Ww][Ii][Nn]32/ 28 | [Aa][Rr][Mm]/ 29 | [Aa][Rr][Mm]64/ 30 | bld/ 31 | [Bb]in/ 32 | [Oo]bj/ 33 | [Ll]og/ 34 | [Ll]ogs/ 35 | 36 | # Visual Studio 2015/2017 cache/options directory 37 | .vs/ 38 | # Uncomment if you have tasks that create the project's static files in wwwroot 39 | #wwwroot/ 40 | 41 | # Visual Studio 2017 auto generated files 42 | Generated\ Files/ 43 | 44 | # MSTest test Results 45 | [Tt]est[Rr]esult*/ 46 | [Bb]uild[Ll]og.* 47 | 48 | # NUnit 49 | *.VisualState.xml 50 | TestResult.xml 51 | nunit-*.xml 52 | 53 | # Build Results of an ATL Project 54 | [Dd]ebugPS/ 55 | [Rr]eleasePS/ 56 | dlldata.c 57 | 58 | # Benchmark Results 59 | BenchmarkDotNet.Artifacts/ 60 | 61 | # .NET Core 62 | project.lock.json 63 | project.fragment.lock.json 64 | artifacts/ 65 | 66 | # ASP.NET Scaffolding 67 | ScaffoldingReadMe.txt 68 | 69 | # StyleCop 70 | StyleCopReport.xml 71 | 72 | # Files built by Visual Studio 73 | *_i.c 74 | *_p.c 75 | *_h.h 76 | *.ilk 77 | *.meta 78 | *.obj 79 | *.iobj 80 | *.pch 81 | *.pdb 82 | *.ipdb 83 | *.pgc 84 | *.pgd 85 | *.rsp 86 | *.sbr 87 | *.tlb 88 | *.tli 89 | *.tlh 90 | *.tmp 91 | *.tmp_proj 92 | *_wpftmp.csproj 93 | *.log 94 | *.vspscc 95 | *.vssscc 96 | .builds 97 | *.pidb 98 | *.svclog 99 | *.scc 100 | 101 | # Chutzpah Test files 102 | _Chutzpah* 103 | 104 | # Visual C++ cache files 105 | ipch/ 106 | *.aps 107 | *.ncb 108 | *.opendb 109 | *.opensdf 110 | *.sdf 111 | *.cachefile 112 | *.VC.db 113 | *.VC.VC.opendb 114 | 115 | # Visual Studio profiler 116 | *.psess 117 | *.vsp 118 | *.vspx 119 | *.sap 120 | 121 | # Visual Studio Trace Files 122 | *.e2e 123 | 124 | # TFS 2012 Local Workspace 125 | $tf/ 126 | 127 | # Guidance Automation Toolkit 128 | *.gpState 129 | 130 | # ReSharper is a .NET coding add-in 131 | _ReSharper*/ 132 | *.[Rr]e[Ss]harper 133 | *.DotSettings.user 134 | 135 | # TeamCity is a build add-in 136 | _TeamCity* 137 | 138 | # DotCover is a Code Coverage Tool 139 | *.dotCover 140 | 141 | # AxoCover is a Code Coverage Tool 142 | .axoCover/* 143 | !.axoCover/settings.json 144 | 145 | # Coverlet is a free, cross platform Code Coverage Tool 146 | coverage*.json 147 | coverage*.xml 148 | coverage*.info 149 | 150 | # Visual Studio code coverage results 151 | *.coverage 152 | *.coveragexml 153 | 154 | # NCrunch 155 | _NCrunch_* 156 | .*crunch*.local.xml 157 | nCrunchTemp_* 158 | 159 | # MightyMoose 160 | *.mm.* 161 | AutoTest.Net/ 162 | 163 | # Web workbench (sass) 164 | .sass-cache/ 165 | 166 | # Installshield output folder 167 | [Ee]xpress/ 168 | 169 | # DocProject is a documentation generator add-in 170 | DocProject/buildhelp/ 171 | DocProject/Help/*.HxT 172 | DocProject/Help/*.HxC 173 | DocProject/Help/*.hhc 174 | DocProject/Help/*.hhk 175 | DocProject/Help/*.hhp 176 | DocProject/Help/Html2 177 | DocProject/Help/html 178 | 179 | # Click-Once directory 180 | publish/ 181 | 182 | # Publish Web Output 183 | *.[Pp]ublish.xml 184 | *.azurePubxml 185 | # Note: Comment the next line if you want to checkin your web deploy settings, 186 | # but database connection strings (with potential passwords) will be unencrypted 187 | *.pubxml 188 | *.publishproj 189 | 190 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 191 | # checkin your Azure Web App publish settings, but sensitive information contained 192 | # in these scripts will be unencrypted 193 | PublishScripts/ 194 | 195 | # NuGet Packages 196 | *.nupkg 197 | # NuGet Symbol Packages 198 | *.snupkg 199 | # The packages folder can be ignored because of Package Restore 200 | **/[Pp]ackages/* 201 | # except build/, which is used as an MSBuild target. 202 | !**/[Pp]ackages/build/ 203 | # Uncomment if necessary however generally it will be regenerated when needed 204 | #!**/[Pp]ackages/repositories.config 205 | # NuGet v3's project.json files produces more ignorable files 206 | *.nuget.props 207 | *.nuget.targets 208 | 209 | # Microsoft Azure Build Output 210 | csx/ 211 | *.build.csdef 212 | 213 | # Microsoft Azure Emulator 214 | ecf/ 215 | rcf/ 216 | 217 | # Windows Store app package directories and files 218 | AppPackages/ 219 | BundleArtifacts/ 220 | Package.StoreAssociation.xml 221 | _pkginfo.txt 222 | *.appx 223 | *.appxbundle 224 | *.appxupload 225 | 226 | # Visual Studio cache files 227 | # files ending in .cache can be ignored 228 | *.[Cc]ache 229 | # but keep track of directories ending in .cache 230 | !?*.[Cc]ache/ 231 | 232 | # Others 233 | ClientBin/ 234 | ~$* 235 | *~ 236 | *.dbmdl 237 | *.dbproj.schemaview 238 | *.jfm 239 | *.pfx 240 | *.publishsettings 241 | orleans.codegen.cs 242 | 243 | # Including strong name files can present a security risk 244 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 245 | #*.snk 246 | 247 | # Since there are multiple workflows, uncomment next line to ignore bower_components 248 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 249 | #bower_components/ 250 | 251 | # RIA/Silverlight projects 252 | Generated_Code/ 253 | 254 | # Backup & report files from converting an old project file 255 | # to a newer Visual Studio version. Backup files are not needed, 256 | # because we have git ;-) 257 | _UpgradeReport_Files/ 258 | Backup*/ 259 | UpgradeLog*.XML 260 | UpgradeLog*.htm 261 | ServiceFabricBackup/ 262 | *.rptproj.bak 263 | 264 | # SQL Server files 265 | *.mdf 266 | *.ldf 267 | *.ndf 268 | 269 | # Business Intelligence projects 270 | *.rdl.data 271 | *.bim.layout 272 | *.bim_*.settings 273 | *.rptproj.rsuser 274 | *- [Bb]ackup.rdl 275 | *- [Bb]ackup ([0-9]).rdl 276 | *- [Bb]ackup ([0-9][0-9]).rdl 277 | 278 | # Microsoft Fakes 279 | FakesAssemblies/ 280 | 281 | # GhostDoc plugin setting file 282 | *.GhostDoc.xml 283 | 284 | # Node.js Tools for Visual Studio 285 | .ntvs_analysis.dat 286 | node_modules/ 287 | 288 | # Visual Studio 6 build log 289 | *.plg 290 | 291 | # Visual Studio 6 workspace options file 292 | *.opt 293 | 294 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 295 | *.vbw 296 | 297 | # Visual Studio LightSwitch build output 298 | **/*.HTMLClient/GeneratedArtifacts 299 | **/*.DesktopClient/GeneratedArtifacts 300 | **/*.DesktopClient/ModelManifest.xml 301 | **/*.Server/GeneratedArtifacts 302 | **/*.Server/ModelManifest.xml 303 | _Pvt_Extensions 304 | 305 | # Paket dependency manager 306 | .paket/paket.exe 307 | paket-files/ 308 | 309 | # FAKE - F# Make 310 | .fake/ 311 | 312 | # CodeRush personal settings 313 | .cr/personal 314 | 315 | # Python Tools for Visual Studio (PTVS) 316 | __pycache__/ 317 | *.pyc 318 | 319 | # Cake - Uncomment if you are using it 320 | # tools/** 321 | # !tools/packages.config 322 | 323 | # Tabs Studio 324 | *.tss 325 | 326 | # Telerik's JustMock configuration file 327 | *.jmconfig 328 | 329 | # BizTalk build output 330 | *.btp.cs 331 | *.btm.cs 332 | *.odx.cs 333 | *.xsd.cs 334 | 335 | # OpenCover UI analysis results 336 | OpenCover/ 337 | 338 | # Azure Stream Analytics local run output 339 | ASALocalRun/ 340 | 341 | # MSBuild Binary and Structured Log 342 | *.binlog 343 | 344 | # NVidia Nsight GPU debugger configuration file 345 | *.nvuser 346 | 347 | # MFractors (Xamarin productivity tool) working folder 348 | .mfractor/ 349 | 350 | # Local History for Visual Studio 351 | .localhistory/ 352 | 353 | # BeatPulse healthcheck temp database 354 | healthchecksdb 355 | 356 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 357 | MigrationBackup/ 358 | 359 | # Ionide (cross platform F# VS Code tools) working folder 360 | .ionide/ 361 | 362 | # Fody - auto-generated XML schema 363 | FodyWeavers.xsd 364 | -------------------------------------------------------------------------------- /DQAsset/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | 5 | namespace DQAsset 6 | { 7 | class Program 8 | { 9 | public static bool DoFNameCleanup = true; 10 | 11 | static bool CompareOutput = false; 12 | static string BadFiles = ""; 13 | static bool SkipIfOutputExists = false; 14 | 15 | static List FileBlackList = new List() 16 | { 17 | // most of these seem to use UserDefinedStructs (UDS), not sure how to handle those yet 18 | "DT_Coordinate.uasset", // strange file 19 | "DT_Crowd_SetList.uasset", 20 | "DT_DebugAutoPlay.uasset", 21 | "DT_DebugChairAnimation.uasset", 22 | "DT_DebugCommandMacro.uasset", // seems to work fine, but contains newline chars (and is huge) 23 | "DT_DebugCsCoordinate.uasset", 24 | "DT_DebugNpcAnimation.uasset", 25 | "DT_DebugNpcAnimCheck.uasset", 26 | "DT_DebugNpcClassCoordinate.uasset", 27 | "DT_DebugNpcSpawnTable.uasset", 28 | "DT_NavBuild.uasset", 29 | "DT_BattleAutoCameraCollision.uasset", // UDS STRUCT_DT_AutoCameraCollision 30 | "DT_PokerItem_.uasset", // UDS 31 | "DT_TextDataTest.csv", // messes up our CSV splitting regex 32 | }; 33 | 34 | // Reads PackageFile from a UAsset file, if UExp exists next to it then they'll be merged before reading 35 | static PackageFile ReadPackage(string UAssetPath, bool convertingFromCsv) 36 | { 37 | var UExpPath = Path.ChangeExtension(UAssetPath, ".uexp"); 38 | 39 | var uasset = File.ReadAllBytes(UAssetPath); 40 | var uassetLength = uasset.Length; 41 | if (File.Exists(UExpPath)) 42 | { 43 | var uexp = File.ReadAllBytes(UExpPath); 44 | var uexpLength = uexp.Length; 45 | 46 | Array.Resize(ref uasset, uasset.Length + uexp.Length); 47 | Array.Copy(uexp, 0, uasset, uassetLength, uexpLength); 48 | } 49 | 50 | using (var reader = new BinaryReader(new MemoryStream(uasset))) 51 | { 52 | var sum = new PackageFile(); 53 | if (convertingFromCsv) 54 | sum.SkipPropertyDataLoad = true; 55 | sum.Deserialize(reader); 56 | return sum; 57 | } 58 | } 59 | 60 | // some code to help with C++ -> C# struct conversion... 61 | static void JackDTStructsPostProcess() 62 | { 63 | var lines = File.ReadAllLines(@"C:\src\DQAsset\JackDTStructs.txt"); 64 | var res = ""; 65 | foreach (var line in lines) 66 | { 67 | var curLine = line; 68 | if (curLine.Contains(" MISSED OFFSET")) 69 | continue; 70 | 71 | if ((curLine.Contains("[0x10]") || curLine.Contains("[0x18]")) && curLine.Contains("UNKNOWN PROPERTY:") && 72 | (curLine.Contains("SoftObjectProperty") || curLine.Contains("SoftClassProperty") || curLine.Contains("ArrayProperty"))) 73 | { 74 | var fieldLast = curLine.LastIndexOf('.'); 75 | var fieldName = curLine.Substring(fieldLast + 1); 76 | if (!curLine.Contains("ArrayProperty")) 77 | curLine = " public FName " + fieldName + ";"; 78 | else 79 | curLine = " public List " + fieldName + ";"; 80 | } 81 | else if (curLine.Contains("*") || curLine.Contains("TWeakObjectPtr")) 82 | curLine = " //" + curLine.Substring(4); 83 | 84 | if (curLine.Contains("TEnumAsByte<")) 85 | curLine = curLine.Replace("TEnumAsByte<", "").Replace(">", ""); // meh 86 | 87 | curLine = curLine.Replace(" : public ", " // : public "); 88 | 89 | curLine = curLine.Replace("public struct ", "public "); 90 | 91 | res += curLine + "\r\n"; 92 | } 93 | File.WriteAllText(@"C:\src\DQAsset\JackDTStructs_new3.txt", res); 94 | } 95 | 96 | static void HandleInput(string inputFile, string outputFile) 97 | { 98 | var inputUAsset = inputFile; 99 | 100 | bool convertingFromCsv = false; 101 | 102 | var inputExtension = Path.GetExtension(inputFile).ToLower(); 103 | if (inputExtension == ".csv") 104 | { 105 | if (string.IsNullOrEmpty(outputFile)) 106 | outputFile = Path.Combine(Path.GetDirectoryName(inputFile), Path.GetFileNameWithoutExtension(inputFile) + "_mod"); 107 | else 108 | { 109 | // remove extension from output path if user gave .uasset/.uexp 110 | var ext = Path.GetExtension(outputFile).ToLower(); 111 | if (ext == ".uasset" || ext == ".uexp") 112 | outputFile = outputFile.Substring(0, outputFile.LastIndexOf('.')); 113 | } 114 | 115 | convertingFromCsv = true; 116 | inputUAsset = Path.ChangeExtension(inputFile, ".uasset"); 117 | } 118 | else 119 | if (string.IsNullOrEmpty(outputFile)) 120 | outputFile = Path.ChangeExtension(inputFile, ".csv"); 121 | 122 | var outputUAsset = outputFile + ".uasset"; 123 | var outputUexp = outputFile + ".uexp"; 124 | 125 | if (SkipIfOutputExists && (File.Exists(outputFile) || File.Exists(outputUAsset))) 126 | return; 127 | 128 | if (!File.Exists(inputUAsset)) 129 | { 130 | Console.WriteLine("failed to load base uasset from path"); 131 | Console.WriteLine($" {inputUAsset}"); 132 | return; 133 | } 134 | 135 | var package = ReadPackage(inputUAsset, convertingFromCsv); 136 | if (!convertingFromCsv) 137 | { 138 | File.WriteAllText(outputFile, package.SerializeText()); 139 | Console.WriteLine("wrote out CSV to path"); 140 | Console.WriteLine($" {outputFile}"); 141 | return; 142 | } 143 | 144 | // Not converting to text, must be converting from text 145 | if (!File.Exists(inputFile)) 146 | { 147 | Console.WriteLine("failed to load csv data from path"); 148 | Console.WriteLine($" {inputFile}"); 149 | return; 150 | } 151 | 152 | // Clear existing rows from loaded UAsset, so only ones defined in CSV will still exist 153 | // TODO: this shouldn't be used if any fields are marked with Serializer(Hidden=true), since atm we don't construct default object for them :( 154 | // If any structs are added which use that, comment this line to fix the serializer afterward... 155 | // (or alternatively, fix the serializer code properly so it constructs a default object ^^) 156 | package.ExportObjects[0].PropertiesData.Clear(); 157 | 158 | var csvData = File.ReadAllText(inputFile); 159 | package.DeserializeText(csvData); 160 | 161 | using (var outputUAssetWriter = new BinaryWriter(File.Create(outputUAsset))) 162 | using (var outputUexpWriter = new BinaryWriter(File.Create(outputUexp))) 163 | package.Serialize(outputUexpWriter, outputUAssetWriter, Program.DoFNameCleanup); 164 | 165 | Console.WriteLine("wrote out uasset/uexp files to path"); 166 | Console.WriteLine($" {outputFile}.uasset/uexp"); 167 | 168 | if (CompareOutput) 169 | { 170 | var hasher = System.Security.Cryptography.SHA256.Create(); 171 | 172 | var origUAsset = Path.ChangeExtension(inputFile, ".uasset"); 173 | var origUExp = Path.ChangeExtension(inputFile, ".uexp"); 174 | var newUAsset = outputUAsset; 175 | var newUExp = outputUexp; 176 | 177 | var origUExpHash = hasher.ComputeHash(File.ReadAllBytes(origUExp)); 178 | var newUExpHash = hasher.ComputeHash(File.ReadAllBytes(newUExp)); 179 | if (origUExpHash.ToHexString() != newUExpHash.ToHexString()) 180 | { 181 | Console.WriteLine("INVALID UEXP:"); 182 | Console.WriteLine(" " + origUAsset); 183 | BadFiles += inputFile + "\r\n"; 184 | return; 185 | } 186 | 187 | var origUAssetHash = hasher.ComputeHash(File.ReadAllBytes(origUAsset)); 188 | var newUAssetHash = hasher.ComputeHash(File.ReadAllBytes(newUAsset)); 189 | if (origUAssetHash.ToHexString() != newUAssetHash.ToHexString()) 190 | { 191 | Console.WriteLine("INVALID UASSET:"); 192 | Console.WriteLine(" " + origUAsset); 193 | BadFiles += inputFile + "\r\n"; 194 | return; 195 | } 196 | } 197 | } 198 | 199 | static void BatchFolder(string folderPath) 200 | { 201 | SkipIfOutputExists = false; 202 | CompareOutput = true; 203 | var assets = Directory.GetFiles(folderPath, "*.uasset", SearchOption.AllDirectories); 204 | foreach (var asset in assets) 205 | { 206 | var fname = Path.GetFileName(asset); 207 | if (FileBlackList.Contains(fname) || 208 | fname.StartsWith("DT_PokerItem_")) 209 | 210 | { 211 | Console.WriteLine("blacklisted: " + asset); 212 | continue; 213 | } 214 | Console.WriteLine(asset); 215 | HandleInput(asset, string.Empty); 216 | } 217 | } 218 | 219 | static void Main(string[] args) 220 | { 221 | Console.WriteLine("DQXIAsset by emoose"); 222 | Console.WriteLine("https://github.com/emoose/DQXIAsset"); 223 | Console.WriteLine(); 224 | 225 | //JackDTStructsPostProcess(); 226 | //BatchFolder(@"C:\Games\DQXI\JackGame\Content\DataTables"); 227 | //File.WriteAllText(@"C:\Games\DQXI\bad.txt", BadFiles); 228 | 229 | if (args.Length < 1) 230 | { 231 | Console.WriteLine("Allows converting UAsset/UExp pair to CSV, or CSV to UAsset/UExp pair"); 232 | Console.WriteLine("Usage: DQAsset.exe [-o ] [-sf] "); 233 | Console.WriteLine(); 234 | Console.WriteLine("Options:"); 235 | Console.WriteLine(" -o : sets path to write output file to"); 236 | Console.WriteLine(" -sf: skips fname cleanup, may improve uasset creation speed"); 237 | Console.WriteLine(); 238 | Console.WriteLine("Note that CSV should have the original UAsset/UExp files next to it, for DQAsset to use as a base to update"); 239 | Console.WriteLine("Updated UAsset/UExp pair will be written to _mod.uasset/uexp"); 240 | Console.WriteLine(); 241 | return; 242 | } 243 | 244 | var inputFile = string.Empty; 245 | var outputFile = string.Empty; 246 | 247 | for (int i = 0; i < args.Length; i++) 248 | { 249 | if (args[i] == "-sf") 250 | DoFNameCleanup = false; 251 | else if (args[i] == "-o" && args.Length > i+1) 252 | { 253 | outputFile = args[i + 1]; 254 | i++; 255 | } 256 | else 257 | inputFile = args[i]; 258 | } 259 | 260 | if (string.IsNullOrEmpty(inputFile)) 261 | { 262 | Console.WriteLine("error: no input file specified!"); 263 | return; 264 | } 265 | 266 | HandleInput(inputFile, outputFile); 267 | } 268 | } 269 | } 270 | -------------------------------------------------------------------------------- /DQAsset/PackageFileSummary.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.IO; 3 | 4 | namespace DQAsset 5 | { 6 | public class PackageFileSummary : ISerializable 7 | { 8 | public uint Tag; 9 | public int LegacyFileVersion; 10 | public int LegacyUE3Version; 11 | public int FileVersionUE4; 12 | public int FileVersionLicenseeUE4; 13 | public int CustomVersionCount; 14 | public List CustomVersion; 15 | public int TotalHeaderSize; 16 | public string FolderName; 17 | public uint PackageFlags; 18 | public int NameCount; 19 | public int NameOffset; 20 | public string LocalizationId; 21 | public int GatherableTextDataCount; 22 | public int GatherableTextDataOffset; 23 | public int ExportCount; 24 | public int ExportOffset; 25 | public int ImportCount; 26 | public int ImportOffset; 27 | public int DependsOffset; 28 | public int SoftPackageReferencesCount; 29 | public int SoftPackageReferencesOffset; 30 | public int SearchableNamesOffset; 31 | public int ThumbnailTableOffset; 32 | public byte[] Guid; 33 | 34 | public byte[] PersistentGuid; 35 | public byte[] OwnerPersistentGuid; 36 | 37 | public int GenerationCount; 38 | public List Generations; 39 | 40 | public FEngineVersion SavedByEngineVersion; 41 | public FEngineVersion CompatibleWithEngineVersion; 42 | 43 | // TODO: why was this missing before? 44 | public uint CompressionFlags; 45 | 46 | public int CompressedChunkCount; 47 | public List CompressedChunks; 48 | 49 | public uint PackageSource; 50 | 51 | public int AdditionalPackagesToCookCount; 52 | public List AdditionalPackagesToCook; 53 | 54 | public int NumTextureAllocations; 55 | public int AssetRegistryDataOffset; 56 | public long BulkDataStartOffset; 57 | public int WorldTileInfoDataOffset; 58 | 59 | public int ChunkIdCount; 60 | public List ChunkIds; 61 | 62 | public int PreloadDependencyCount; 63 | public int PreloadDependencyOffset; 64 | 65 | public bool IsVersionOrGreater(UE4Versions Version) 66 | { 67 | int FileVer = FileVersionUE4; 68 | if (FileVer == 0) 69 | FileVer = 514; // workaround for unversioned DQXIS files, 514 is latest ver in 4.18.3 70 | 71 | return FileVer >= (int)Version; 72 | } 73 | 74 | public void Serialize(BinaryWriter writer, PackageFile package) 75 | { 76 | writer.Write(Tag); 77 | writer.Write(LegacyFileVersion); 78 | writer.Write(LegacyUE3Version); 79 | writer.Write(FileVersionUE4); 80 | writer.Write(FileVersionLicenseeUE4); 81 | 82 | CustomVersionCount = CustomVersion.Count; 83 | writer.Write(CustomVersionCount); 84 | foreach (var customVersion in CustomVersion) 85 | writer.Write(customVersion); 86 | 87 | writer.Write(TotalHeaderSize); 88 | writer.WriteFString(FolderName); 89 | 90 | writer.Write(PackageFlags); 91 | writer.Write(NameCount); 92 | writer.Write(NameOffset); 93 | 94 | if (IsVersionOrGreater(UE4Versions.VER_UE4_ADDED_PACKAGE_SUMMARY_LOCALIZATION_ID)) 95 | writer.WriteFString(LocalizationId); 96 | 97 | if (IsVersionOrGreater(UE4Versions.VER_UE4_SERIALIZE_TEXT_IN_PACKAGES)) 98 | { 99 | writer.Write(GatherableTextDataCount); 100 | writer.Write(GatherableTextDataOffset); 101 | } 102 | 103 | writer.Write(ExportCount); 104 | writer.Write(ExportOffset); 105 | writer.Write(ImportCount); 106 | writer.Write(ImportOffset); 107 | writer.Write(DependsOffset); 108 | 109 | if (!IsVersionOrGreater(UE4Versions.VER_UE4_OLDEST_LOADABLE_PACKAGE)) 110 | return; 111 | 112 | if (IsVersionOrGreater(UE4Versions.VER_UE4_ADD_STRING_ASSET_REFERENCES_MAP)) 113 | { 114 | writer.Write(SoftPackageReferencesCount); 115 | writer.Write(SoftPackageReferencesOffset); 116 | } 117 | 118 | if (IsVersionOrGreater(UE4Versions.VER_UE4_ADDED_SEARCHABLE_NAMES)) 119 | writer.Write(SearchableNamesOffset); 120 | 121 | writer.Write(ThumbnailTableOffset); 122 | 123 | writer.Write(Guid); 124 | 125 | if (IsVersionOrGreater(UE4Versions.VER_UE4_ADDED_PACKAGE_OWNER)) 126 | { 127 | writer.Write(PersistentGuid); 128 | writer.Write(OwnerPersistentGuid); 129 | } 130 | 131 | GenerationCount = Generations.Count; 132 | writer.Write(GenerationCount); 133 | foreach (var gen in Generations) 134 | writer.WriteStruct(gen); 135 | 136 | if (IsVersionOrGreater(UE4Versions.VER_UE4_ENGINE_VERSION_OBJECT)) 137 | SavedByEngineVersion.Serialize(writer, package); 138 | else 139 | writer.Write(SavedByEngineVersion.Changelist); 140 | 141 | if (IsVersionOrGreater(UE4Versions.VER_UE4_PACKAGE_SUMMARY_HAS_COMPATIBLE_ENGINE_VERSION)) 142 | CompatibleWithEngineVersion.Serialize(writer, package); 143 | 144 | writer.Write(CompressionFlags); 145 | 146 | CompressedChunkCount = CompressedChunks.Count; 147 | writer.Write(CompressedChunkCount); 148 | foreach (var chunk in CompressedChunks) 149 | writer.WriteStruct(chunk); 150 | 151 | writer.Write(PackageSource); 152 | 153 | AdditionalPackagesToCookCount = AdditionalPackagesToCook.Count; 154 | writer.Write(AdditionalPackagesToCookCount); 155 | foreach (var addtPkg in AdditionalPackagesToCook) 156 | writer.WriteFString(addtPkg); 157 | 158 | if (LegacyFileVersion > -7) 159 | writer.Write(NumTextureAllocations); 160 | 161 | writer.Write(AssetRegistryDataOffset); 162 | writer.Write(BulkDataStartOffset); 163 | 164 | if (IsVersionOrGreater(UE4Versions.VER_UE4_WORLD_LEVEL_INFO)) 165 | writer.Write(WorldTileInfoDataOffset); 166 | 167 | ChunkIdCount = 0; 168 | if (ChunkIds != null) 169 | ChunkIdCount = ChunkIds.Count; 170 | 171 | if (IsVersionOrGreater(UE4Versions.VER_UE4_CHANGED_CHUNKID_TO_BE_AN_ARRAY_OF_CHUNKIDS)) 172 | { 173 | writer.Write(ChunkIdCount); 174 | foreach (var chunk in ChunkIds) 175 | writer.Write(chunk); 176 | } 177 | else if (IsVersionOrGreater(UE4Versions.VER_UE4_ADDED_CHUNKID_TO_ASSETDATA_AND_UPACKAGE)) 178 | writer.Write(ChunkIdCount > 0 ? ChunkIds[0] : 0); 179 | 180 | if (IsVersionOrGreater(UE4Versions.VER_UE4_PRELOAD_DEPENDENCIES_IN_COOKED_EXPORTS)) 181 | { 182 | writer.Write(PreloadDependencyCount); 183 | writer.Write(PreloadDependencyOffset); 184 | } 185 | } 186 | 187 | public void Deserialize(BinaryReader reader, PackageFile package) 188 | { 189 | Tag = reader.ReadUInt32(); 190 | LegacyFileVersion = reader.ReadInt32(); 191 | LegacyUE3Version = reader.ReadInt32(); 192 | FileVersionUE4 = reader.ReadInt32(); 193 | FileVersionLicenseeUE4 = reader.ReadInt32(); 194 | 195 | CustomVersionCount = reader.ReadInt32(); 196 | CustomVersion = new List(); 197 | for (int i = 0; i < CustomVersionCount; i++) 198 | CustomVersion.Add(reader.ReadInt32()); 199 | 200 | TotalHeaderSize = reader.ReadInt32(); 201 | FolderName = reader.ReadFString(); 202 | 203 | PackageFlags = reader.ReadUInt32(); 204 | NameCount = reader.ReadInt32(); 205 | NameOffset = reader.ReadInt32(); 206 | 207 | if (IsVersionOrGreater(UE4Versions.VER_UE4_ADDED_PACKAGE_SUMMARY_LOCALIZATION_ID)) 208 | LocalizationId = reader.ReadFString(); 209 | 210 | if (IsVersionOrGreater(UE4Versions.VER_UE4_SERIALIZE_TEXT_IN_PACKAGES)) 211 | { 212 | GatherableTextDataCount = reader.ReadInt32(); 213 | GatherableTextDataOffset = reader.ReadInt32(); 214 | } 215 | 216 | ExportCount = reader.ReadInt32(); 217 | ExportOffset = reader.ReadInt32(); 218 | ImportCount = reader.ReadInt32(); 219 | ImportOffset = reader.ReadInt32(); 220 | DependsOffset = reader.ReadInt32(); 221 | 222 | if (!IsVersionOrGreater(UE4Versions.VER_UE4_OLDEST_LOADABLE_PACKAGE)) 223 | return; 224 | 225 | if (IsVersionOrGreater(UE4Versions.VER_UE4_ADD_STRING_ASSET_REFERENCES_MAP)) 226 | { 227 | SoftPackageReferencesCount = reader.ReadInt32(); 228 | SoftPackageReferencesOffset = reader.ReadInt32(); 229 | } 230 | 231 | if (IsVersionOrGreater(UE4Versions.VER_UE4_ADDED_SEARCHABLE_NAMES)) 232 | SearchableNamesOffset = reader.ReadInt32(); 233 | 234 | ThumbnailTableOffset = reader.ReadInt32(); 235 | Guid = reader.ReadBytes(0x10); 236 | if (IsVersionOrGreater(UE4Versions.VER_UE4_ADDED_PACKAGE_OWNER)) 237 | { 238 | PersistentGuid = reader.ReadBytes(0x10); 239 | OwnerPersistentGuid = reader.ReadBytes(0x10); 240 | } 241 | 242 | GenerationCount = reader.ReadInt32(); 243 | Generations = new List(); 244 | for (int i = 0; i < GenerationCount; i++) 245 | Generations.Add(reader.ReadStruct()); 246 | 247 | SavedByEngineVersion = new FEngineVersion(); 248 | if (IsVersionOrGreater(UE4Versions.VER_UE4_ENGINE_VERSION_OBJECT)) 249 | SavedByEngineVersion.Deserialize(reader, package); 250 | else 251 | SavedByEngineVersion.Changelist = reader.ReadUInt32(); 252 | 253 | if (IsVersionOrGreater(UE4Versions.VER_UE4_PACKAGE_SUMMARY_HAS_COMPATIBLE_ENGINE_VERSION)) 254 | { 255 | CompatibleWithEngineVersion = new FEngineVersion(); 256 | CompatibleWithEngineVersion.Deserialize(reader, package); 257 | } 258 | 259 | CompressionFlags = reader.ReadUInt32(); 260 | 261 | CompressedChunkCount = reader.ReadInt32(); 262 | CompressedChunks = new List(); 263 | for (int i = 0; i < CompressedChunkCount; i++) 264 | CompressedChunks.Add(reader.ReadStruct()); 265 | 266 | PackageSource = reader.ReadUInt32(); 267 | AdditionalPackagesToCookCount = reader.ReadInt32(); 268 | AdditionalPackagesToCook = new List(); 269 | for (int i = 0; i < AdditionalPackagesToCookCount; i++) 270 | AdditionalPackagesToCook.Add(reader.ReadFString()); 271 | 272 | if (LegacyFileVersion > -7) 273 | NumTextureAllocations = reader.ReadInt32(); 274 | 275 | AssetRegistryDataOffset = reader.ReadInt32(); 276 | BulkDataStartOffset = reader.ReadInt64(); 277 | 278 | if (IsVersionOrGreater(UE4Versions.VER_UE4_WORLD_LEVEL_INFO)) 279 | WorldTileInfoDataOffset = reader.ReadInt32(); 280 | 281 | if (IsVersionOrGreater(UE4Versions.VER_UE4_CHANGED_CHUNKID_TO_BE_AN_ARRAY_OF_CHUNKIDS)) 282 | { 283 | ChunkIdCount = reader.ReadInt32(); 284 | ChunkIds = new List(); 285 | for (int i = 0; i < ChunkIdCount; i++) 286 | ChunkIds.Add(reader.ReadInt32()); 287 | } 288 | else if (IsVersionOrGreater(UE4Versions.VER_UE4_ADDED_CHUNKID_TO_ASSETDATA_AND_UPACKAGE)) 289 | { 290 | ChunkIdCount = 1; 291 | ChunkIds = new List(); 292 | ChunkIds.Add(reader.ReadInt32()); 293 | } 294 | 295 | if (IsVersionOrGreater(UE4Versions.VER_UE4_PRELOAD_DEPENDENCIES_IN_COOKED_EXPORTS)) 296 | { 297 | PreloadDependencyCount = reader.ReadInt32(); 298 | PreloadDependencyOffset = reader.ReadInt32(); 299 | } 300 | } 301 | } 302 | } 303 | -------------------------------------------------------------------------------- /DQAsset/FTableRowBase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Reflection; 7 | using System.Text; 8 | using System.Text.RegularExpressions; 9 | 10 | namespace DQAsset 11 | { 12 | public class FTableRowBase : ISerializableText 13 | { 14 | public object DeserializeValueText(Type type, SerializerAttribute settings, PackageFile package, string value) 15 | { 16 | // strip quotes from around value 17 | if (value.StartsWith('"') && value.EndsWith('"')) 18 | value = value.Substring(1, value.Length - 2); 19 | value = value.Replace("\"\"", "\""); 20 | 21 | switch (type.Name) 22 | { 23 | case "String": 24 | return value.Replace("\\\\r", "\r").Replace("\\\\n", "\n"); 25 | case "Byte[]": 26 | return Shared.StringToByteArray(value); 27 | case "Boolean": 28 | return value == "1" || value == "true" || value == "yes"; 29 | case "Byte": 30 | return byte.Parse(value); 31 | case "Int16": 32 | return short.Parse(value); 33 | case "UInt16": 34 | return ushort.Parse(value); 35 | case "Int32": 36 | return int.Parse(value); 37 | case "UInt32": 38 | return uint.Parse(value); 39 | case "Single": 40 | return float.Parse(value); 41 | case "Double": 42 | return double.Parse(value); 43 | case "Int64": 44 | return long.Parse(value); 45 | case "UInt64": 46 | return ulong.Parse(value); 47 | case "List`1": 48 | // TArray 49 | var elementType = type.GetGenericArguments()[0]; 50 | var listType = typeof(List<>).MakeGenericType(elementType); 51 | var list = Activator.CreateInstance(listType) as IList; 52 | 53 | if (value != "{}") // not empty: 54 | { 55 | if (value.StartsWith('{') && value.EndsWith('}')) 56 | value = value.Substring(1, value.Length - 2); 57 | 58 | var values = Shared.ProcessCsvRow(value).ToList(); 59 | 60 | foreach (var val in values) 61 | list.Add(DeserializeValueText(elementType, settings, package, val)); 62 | } 63 | 64 | return list; 65 | case "FName": 66 | return new FName(value); 67 | default: 68 | if (type.IsEnum) 69 | { 70 | object enumResult; 71 | if (Enum.TryParse(type, value, out enumResult)) 72 | return enumResult; 73 | // if Enum.TryParse failed then the value isn't part of enum (and object struct is likely incorrect) 74 | // Fallback to the enums underlying type 75 | var enumType = Enum.GetUnderlyingType(type); 76 | return DeserializeValueText(enumType, settings, package, value); 77 | } 78 | if (type.GetInterfaces().Contains(typeof(ISerializableText))) 79 | { 80 | var obj = Activator.CreateInstance(type) as ISerializableText; 81 | obj.DeserializeText(value, package); 82 | return obj; 83 | } 84 | break; 85 | } 86 | return null; 87 | } 88 | 89 | public string SerializeValueText(Type type, SerializerAttribute settings, PackageFile package, object value) 90 | { 91 | switch (type.Name) 92 | { 93 | case "String": // FString 94 | var str = (string)value; 95 | if (string.IsNullOrEmpty(str)) 96 | return "\"\""; // hack to fix CSV regex not working properly with empty cells.. 97 | return "\"" + str.Replace("\r", "\\\\r").Replace("\n", "\\\\n").Replace("\"", "\"\"") + "\""; 98 | case "Byte[]": 99 | var byt = (byte[])value; 100 | return byt.ToHexString(); 101 | case "Boolean": 102 | return (bool)value ? "true" : "false"; 103 | case "Byte": 104 | return ((byte)value).ToString(); 105 | case "Int16": 106 | return ((short)value).ToString(); 107 | case "UInt16": 108 | return ((ushort)value).ToString(); 109 | case "Int32": 110 | return ((int)value).ToString(); 111 | case "UInt32": 112 | return ((uint)value).ToString(); 113 | case "Single": 114 | return ((float)value).ToString(); 115 | case "Double": 116 | return ((double)value).ToString(); 117 | case "Int64": 118 | return ((long)value).ToString(); 119 | case "UInt64": 120 | return ((ulong)value).ToString(); 121 | case "List`1": 122 | var elementType = type.GetGenericArguments()[0]; 123 | var list = value as IList; 124 | 125 | var result = ""; 126 | for (int i = 0; i < list.Count; i++) 127 | { 128 | result += SerializeValueText(elementType, settings, package, list[i]).Replace("\"", "\"\""); 129 | if (i + 1 < list.Count) 130 | result += ","; 131 | } 132 | result = "\"{" + result + "}\""; 133 | return result; 134 | case "FName": 135 | var name = (FName)value; 136 | return name.ToString(); 137 | default: 138 | if (type.IsEnum) 139 | { 140 | var enumStr = Enum.GetName(type, value); 141 | if (!string.IsNullOrEmpty(enumStr)) 142 | return enumStr; 143 | 144 | // If Enum.GetName returned empty then the value isn't part of enum (and object struct is likely incorrect) 145 | // Fallback to the enums underlying type 146 | var enumType = Enum.GetUnderlyingType(type); 147 | return SerializeValueText(enumType, settings, package, value); 148 | } 149 | if (type.GetInterfaces().Contains(typeof(ISerializableText))) 150 | { 151 | var obj = value as ISerializableText; 152 | if (obj == null) 153 | return "\"\""; 154 | return "\"" + obj.SerializeText(package, false).Replace("\"", "\"\"") + "\""; 155 | } 156 | break; 157 | } 158 | 159 | return null; 160 | } 161 | 162 | public virtual string SerializeText(PackageFile package, bool isMainElement) 163 | { 164 | var fields = GetType().GetFields().OrderBy(field => field.MetadataToken); // hack to get fields in order of declaration (todo: use something less hacky, this might break mono?) 165 | string retVal = ""; 166 | foreach (var field in fields) 167 | { 168 | var settings = field.GetCustomAttribute(); 169 | if (settings != null && settings.Hidden) 170 | continue; 171 | 172 | string valueText = "\"\""; 173 | if (FieldCanSerialize(field)) 174 | { 175 | valueText = SerializeValueText(field.FieldType, settings, package, field.GetValue(this)); 176 | 177 | // change outer escaped-quotes to regular quotes 178 | if (isMainElement && valueText.StartsWith("\"\"") && valueText.EndsWith("\"\"") && valueText.Length > 4) 179 | valueText = "\"" + valueText.Substring(2, valueText.Length - 4) + "\""; 180 | } 181 | 182 | // hack to escape anything that contains a comma, hope it doesn't break anything.. 183 | if (valueText.Contains(",") && !valueText.Contains("\"")) 184 | valueText = "\"" + valueText + "\""; 185 | 186 | retVal += valueText; 187 | retVal += ","; 188 | } 189 | // remove trailing comma 190 | if (retVal.Length > 0) 191 | retVal = retVal.Substring(0, retVal.Length - 1); 192 | return retVal; 193 | } 194 | 195 | public virtual void DeserializeText(string text, PackageFile package) 196 | { 197 | var values = Shared.ProcessCsvRow(text).ToList(); 198 | var fields = GetType().GetFields().OrderBy(field => field.MetadataToken).ToList(); // hack to get fields in order of declaration (todo: use something less hacky, this might break mono?) 199 | 200 | int valueIdx = 0; 201 | for (int i = 0; i < fields.Count; i++) 202 | { 203 | var field = fields[i]; 204 | 205 | // TODO: need to provide a default value for hidden fields, else they may be left as null (which our serialization code doesn't like!) 206 | var settings = field.GetCustomAttribute(); 207 | if (settings != null && settings.Hidden) 208 | continue; 209 | 210 | if (FieldCanSerialize(field)) 211 | { 212 | object value = DeserializeValueText(field.FieldType, settings, package, values[valueIdx]); 213 | field.SetValue(this, value); 214 | } 215 | 216 | valueIdx++; 217 | } 218 | } 219 | 220 | public string SerializeTextHeader(PackageFile package) 221 | { 222 | var fields = GetType().GetFields().OrderBy(field => field.MetadataToken); // hack to get fields in order of declaration (todo: use something less hacky, this might break mono?) 223 | string retVal = ""; 224 | foreach (var field in fields) 225 | { 226 | var settings = field.GetCustomAttribute(); 227 | if (settings != null && settings.Hidden) 228 | continue; 229 | 230 | retVal += field.Name; 231 | retVal += ","; 232 | } 233 | // remove trailing comma 234 | if (retVal.Length > 0) 235 | retVal = retVal.Substring(0, retVal.Length - 1); 236 | return retVal; 237 | } 238 | 239 | void SerializeBool(SerializerAttribute settings, BinaryWriter writer, PackageFile package, object value, bool isListElement) 240 | { 241 | var val = (bool)value; 242 | if (isListElement) 243 | { 244 | writer.Write(val ? (byte)1 : (byte)0); 245 | return; 246 | } 247 | else 248 | { 249 | if (settings != null) 250 | { 251 | if (settings.Size == 1) 252 | { 253 | writer.Write(val ? (byte)1 : (byte)0); 254 | return; 255 | } 256 | if (settings.Size == 2) 257 | { 258 | writer.Write(val ? (ushort)1 : (ushort)0); 259 | return; 260 | } 261 | if (settings.Size == 4) 262 | { 263 | writer.Write(val ? (uint)1 : (uint)0); 264 | return; 265 | } 266 | if (settings.Size == 8) 267 | { 268 | writer.Write(val ? (ulong)1 : (ulong)0); 269 | return; 270 | } 271 | } 272 | } 273 | writer.Write(val ? (int)1 : (int)0); 274 | } 275 | 276 | public void SerializeValue(Type type, SerializerAttribute settings, BinaryWriter writer, PackageFile package, object value, bool isListElement = false) 277 | { 278 | switch(type.Name) 279 | { 280 | case "String": // FString 281 | var str = (string)value; 282 | writer.WriteFString(str); 283 | break; 284 | case "Byte[]": 285 | writer.Write((byte[])value); 286 | break; 287 | case "Boolean": 288 | SerializeBool(settings, writer, package, value, isListElement); 289 | break; 290 | case "Byte": 291 | writer.Write((byte)value); 292 | break; 293 | case "Int16": 294 | writer.Write((short)value); 295 | break; 296 | case "UInt16": 297 | writer.Write((ushort)value); 298 | break; 299 | case "Int32": 300 | writer.Write((int)value); 301 | break; 302 | case "UInt32": 303 | writer.Write((uint)value); 304 | break; 305 | case "Single": 306 | writer.Write((float)value); 307 | break; 308 | case "Double": 309 | writer.Write((double)value); 310 | break; 311 | case "Int64": 312 | writer.Write((long)value); 313 | break; 314 | case "UInt64": 315 | writer.Write((ulong)value); 316 | break; 317 | case "List`1": 318 | var elementType = type.GetGenericArguments()[0]; 319 | var list = value as IList; 320 | writer.Write(list.Count); 321 | for (int i = 0; i < list.Count; i++) 322 | SerializeValue(elementType, settings, writer, package, list[i], true); 323 | 324 | break; 325 | case "FName": 326 | var name = (FName)value; 327 | name.Serialize(writer, package); 328 | break; 329 | default: 330 | if (type.IsEnum) 331 | { 332 | var enumType = Enum.GetUnderlyingType(type); 333 | SerializeValue(enumType, settings, writer, package, value); 334 | } 335 | if (type.GetInterfaces().Contains(typeof(ISerializable))) 336 | { 337 | var obj = value as ISerializable; 338 | obj.Serialize(writer, package); 339 | } 340 | break; 341 | } 342 | } 343 | 344 | object DeserializeBool(SerializerAttribute settings, BinaryReader reader, PackageFile package, bool isListElement) 345 | { 346 | if (isListElement) 347 | return reader.ReadByte() != 0; 348 | if (settings != null) 349 | { 350 | if (settings.Size == 1) 351 | return reader.ReadByte() != 0; 352 | if (settings.Size == 2) 353 | return reader.ReadUInt16() != 0; 354 | if (settings.Size == 4) 355 | return reader.ReadUInt32() != 0; 356 | if (settings.Size == 8) 357 | return reader.ReadUInt64() != 0; 358 | } 359 | return reader.ReadUInt32() != 0; 360 | } 361 | 362 | public object DeserializeValue(Type type, SerializerAttribute settings, BinaryReader reader, PackageFile package, bool isListElement = false) 363 | { 364 | switch (type.Name) 365 | { 366 | case "String": // FString 367 | return reader.ReadFString(); 368 | case "Byte[]": 369 | int sz = settings?.Size ?? 0; 370 | byte[] data = reader.ReadBytes(sz); 371 | return data; 372 | case "Boolean": 373 | return DeserializeBool(settings, reader, package, isListElement); 374 | case "Byte": 375 | return reader.ReadByte(); 376 | case "Int16": 377 | return reader.ReadInt16(); 378 | case "UInt16": 379 | return reader.ReadUInt16(); 380 | case "Int32": 381 | return reader.ReadInt32(); 382 | case "UInt32": 383 | return reader.ReadUInt32(); 384 | case "Single": 385 | return reader.ReadSingle(); 386 | case "Double": 387 | return reader.ReadDouble(); 388 | case "Int64": 389 | return reader.ReadInt64(); 390 | case "UInt64": 391 | return reader.ReadUInt64(); 392 | case "List`1": 393 | // TArray 394 | var elementType = type.GetGenericArguments()[0]; 395 | var listType = typeof(List<>).MakeGenericType(elementType); 396 | var list = Activator.CreateInstance(listType) as IList; 397 | 398 | int count = reader.ReadInt32(); 399 | for (int i = 0; i < count; i++) 400 | list.Add(DeserializeValue(elementType, settings, reader, package, true)); 401 | 402 | return list; 403 | case "FName": 404 | var name = new FName(); 405 | name.Deserialize(reader, package); 406 | return name; 407 | default: 408 | if (type.IsEnum) 409 | { 410 | var enumType = Enum.GetUnderlyingType(type); 411 | return DeserializeValue(enumType, settings, reader, package); 412 | } 413 | if (type.GetInterfaces().Contains(typeof(ISerializable))) 414 | { 415 | var obj = Activator.CreateInstance(type) as ISerializable; 416 | obj.Deserialize(reader, package); 417 | return obj; 418 | } 419 | break; 420 | } 421 | 422 | return null; 423 | } 424 | 425 | bool CheckIsSet(string fieldName) 426 | { 427 | var fields = GetType().GetFields().OrderBy(field => field.MetadataToken); // hack to get fields in order of declaration (todo: use something less hacky, this might break mono?) 428 | foreach (var field in fields) 429 | { 430 | if (field.Name == fieldName) 431 | { 432 | if (field.FieldType == typeof(FName)) 433 | { 434 | var name = field.GetValue(this) as FName; 435 | return name.Value != "None"; 436 | } 437 | if (field.FieldType == typeof(byte)) 438 | return (byte)field.GetValue(this) == 1; 439 | if (field.FieldType == typeof(short)) 440 | return (short)field.GetValue(this) == 1; 441 | if (field.FieldType == typeof(int)) 442 | return (int)field.GetValue(this) == 1; 443 | if (field.FieldType == typeof(long)) 444 | return (long)field.GetValue(this) == 1; 445 | if (field.FieldType == typeof(ushort)) 446 | return (ushort)field.GetValue(this) == 1; 447 | if (field.FieldType == typeof(uint)) 448 | return (uint)field.GetValue(this) == 1; 449 | if (field.FieldType == typeof(ulong)) 450 | return (ulong)field.GetValue(this) == 1; 451 | 452 | return (bool)field.GetValue(this); 453 | } 454 | } 455 | return false; 456 | } 457 | 458 | bool FieldCanSerialize(FieldInfo field) 459 | { 460 | var settings = field.GetCustomAttribute(); 461 | 462 | if (settings == null) 463 | return true; 464 | 465 | if (!string.IsNullOrEmpty(settings.OnlyIfSet)) 466 | if (!CheckIsSet(settings.OnlyIfSet)) 467 | return false; 468 | 469 | if (!string.IsNullOrEmpty(settings.OnlyIfNotSet)) 470 | if (CheckIsSet(settings.OnlyIfNotSet)) 471 | return false; 472 | 473 | if (settings.OnlyIfAllSet != null && settings.OnlyIfAllSet.Length > 0) 474 | foreach (var field2 in settings.OnlyIfAllSet) 475 | if (!CheckIsSet(field2)) 476 | return false; 477 | 478 | if (settings.OnlyIfAllNotSet != null && settings.OnlyIfAllNotSet.Length > 0) 479 | foreach (var field2 in settings.OnlyIfAllNotSet) 480 | if (CheckIsSet(field2)) 481 | return false; 482 | 483 | return true; 484 | } 485 | 486 | public virtual void Deserialize(BinaryReader reader, PackageFile package) 487 | { 488 | var fields = GetType().GetFields().OrderBy(field => field.MetadataToken); // hack to get fields in order of declaration (todo: use something less hacky, this might break mono?) 489 | foreach (var field in fields) 490 | { 491 | if (!FieldCanSerialize(field)) 492 | continue; 493 | 494 | var settings = field.GetCustomAttribute(); 495 | 496 | var fieldType = field.FieldType.Name; 497 | object value = DeserializeValue(field.FieldType, settings, reader, package); 498 | field.SetValue(this, value); 499 | } 500 | } 501 | 502 | public virtual void Serialize(BinaryWriter writer, PackageFile package) 503 | { 504 | var fields = GetType().GetFields().OrderBy(field => field.MetadataToken); // hack to get fields in order of declaration (todo: use something less hacky, this might break mono?) 505 | foreach (var field in fields) 506 | { 507 | if (!FieldCanSerialize(field)) 508 | continue; 509 | 510 | var settings = field.GetCustomAttribute(); 511 | 512 | var fieldType = field.FieldType.Name; 513 | SerializeValue(field.FieldType, settings, writer, package, field.GetValue(this)); 514 | } 515 | } 516 | } 517 | } 518 | -------------------------------------------------------------------------------- /DQAsset/Shared.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Runtime.InteropServices; 6 | using System.Text; 7 | using System.Text.RegularExpressions; 8 | 9 | namespace DQAsset 10 | { 11 | public static class Shared 12 | { 13 | static uint[] CRCTable_DEPRECATED = 14 | { 15 | 0x00000000, 0x04C11DB7, 0x09823B6E, 0x0D4326D9, 0x130476DC, 0x17C56B6B, 0x1A864DB2, 0x1E475005, 0x2608EDB8, 0x22C9F00F, 0x2F8AD6D6, 0x2B4BCB61, 0x350C9B64, 0x31CD86D3, 0x3C8EA00A, 0x384FBDBD, 16 | 0x4C11DB70, 0x48D0C6C7, 0x4593E01E, 0x4152FDA9, 0x5F15ADAC, 0x5BD4B01B, 0x569796C2, 0x52568B75, 0x6A1936C8, 0x6ED82B7F, 0x639B0DA6, 0x675A1011, 0x791D4014, 0x7DDC5DA3, 0x709F7B7A, 0x745E66CD, 17 | 0x9823B6E0, 0x9CE2AB57, 0x91A18D8E, 0x95609039, 0x8B27C03C, 0x8FE6DD8B, 0x82A5FB52, 0x8664E6E5, 0xBE2B5B58, 0xBAEA46EF, 0xB7A96036, 0xB3687D81, 0xAD2F2D84, 0xA9EE3033, 0xA4AD16EA, 0xA06C0B5D, 18 | 0xD4326D90, 0xD0F37027, 0xDDB056FE, 0xD9714B49, 0xC7361B4C, 0xC3F706FB, 0xCEB42022, 0xCA753D95, 0xF23A8028, 0xF6FB9D9F, 0xFBB8BB46, 0xFF79A6F1, 0xE13EF6F4, 0xE5FFEB43, 0xE8BCCD9A, 0xEC7DD02D, 19 | 0x34867077, 0x30476DC0, 0x3D044B19, 0x39C556AE, 0x278206AB, 0x23431B1C, 0x2E003DC5, 0x2AC12072, 0x128E9DCF, 0x164F8078, 0x1B0CA6A1, 0x1FCDBB16, 0x018AEB13, 0x054BF6A4, 0x0808D07D, 0x0CC9CDCA, 20 | 0x7897AB07, 0x7C56B6B0, 0x71159069, 0x75D48DDE, 0x6B93DDDB, 0x6F52C06C, 0x6211E6B5, 0x66D0FB02, 0x5E9F46BF, 0x5A5E5B08, 0x571D7DD1, 0x53DC6066, 0x4D9B3063, 0x495A2DD4, 0x44190B0D, 0x40D816BA, 21 | 0xACA5C697, 0xA864DB20, 0xA527FDF9, 0xA1E6E04E, 0xBFA1B04B, 0xBB60ADFC, 0xB6238B25, 0xB2E29692, 0x8AAD2B2F, 0x8E6C3698, 0x832F1041, 0x87EE0DF6, 0x99A95DF3, 0x9D684044, 0x902B669D, 0x94EA7B2A, 22 | 0xE0B41DE7, 0xE4750050, 0xE9362689, 0xEDF73B3E, 0xF3B06B3B, 0xF771768C, 0xFA325055, 0xFEF34DE2, 0xC6BCF05F, 0xC27DEDE8, 0xCF3ECB31, 0xCBFFD686, 0xD5B88683, 0xD1799B34, 0xDC3ABDED, 0xD8FBA05A, 23 | 0x690CE0EE, 0x6DCDFD59, 0x608EDB80, 0x644FC637, 0x7A089632, 0x7EC98B85, 0x738AAD5C, 0x774BB0EB, 0x4F040D56, 0x4BC510E1, 0x46863638, 0x42472B8F, 0x5C007B8A, 0x58C1663D, 0x558240E4, 0x51435D53, 24 | 0x251D3B9E, 0x21DC2629, 0x2C9F00F0, 0x285E1D47, 0x36194D42, 0x32D850F5, 0x3F9B762C, 0x3B5A6B9B, 0x0315D626, 0x07D4CB91, 0x0A97ED48, 0x0E56F0FF, 0x1011A0FA, 0x14D0BD4D, 0x19939B94, 0x1D528623, 25 | 0xF12F560E, 0xF5EE4BB9, 0xF8AD6D60, 0xFC6C70D7, 0xE22B20D2, 0xE6EA3D65, 0xEBA91BBC, 0xEF68060B, 0xD727BBB6, 0xD3E6A601, 0xDEA580D8, 0xDA649D6F, 0xC423CD6A, 0xC0E2D0DD, 0xCDA1F604, 0xC960EBB3, 26 | 0xBD3E8D7E, 0xB9FF90C9, 0xB4BCB610, 0xB07DABA7, 0xAE3AFBA2, 0xAAFBE615, 0xA7B8C0CC, 0xA379DD7B, 0x9B3660C6, 0x9FF77D71, 0x92B45BA8, 0x9675461F, 0x8832161A, 0x8CF30BAD, 0x81B02D74, 0x857130C3, 27 | 0x5D8A9099, 0x594B8D2E, 0x5408ABF7, 0x50C9B640, 0x4E8EE645, 0x4A4FFBF2, 0x470CDD2B, 0x43CDC09C, 0x7B827D21, 0x7F436096, 0x7200464F, 0x76C15BF8, 0x68860BFD, 0x6C47164A, 0x61043093, 0x65C52D24, 28 | 0x119B4BE9, 0x155A565E, 0x18197087, 0x1CD86D30, 0x029F3D35, 0x065E2082, 0x0B1D065B, 0x0FDC1BEC, 0x3793A651, 0x3352BBE6, 0x3E119D3F, 0x3AD08088, 0x2497D08D, 0x2056CD3A, 0x2D15EBE3, 0x29D4F654, 29 | 0xC5A92679, 0xC1683BCE, 0xCC2B1D17, 0xC8EA00A0, 0xD6AD50A5, 0xD26C4D12, 0xDF2F6BCB, 0xDBEE767C, 0xE3A1CBC1, 0xE760D676, 0xEA23F0AF, 0xEEE2ED18, 0xF0A5BD1D, 0xF464A0AA, 0xF9278673, 0xFDE69BC4, 30 | 0x89B8FD09, 0x8D79E0BE, 0x803AC667, 0x84FBDBD0, 0x9ABC8BD5, 0x9E7D9662, 0x933EB0BB, 0x97FFAD0C, 0xAFB010B1, 0xAB710D06, 0xA6322BDF, 0xA2F33668, 0xBCB4666D, 0xB8757BDA, 0xB5365D03, 0xB1F740B4 31 | }; 32 | 33 | static uint[][] CRCTablesSB8 = 34 | { 35 | new uint[] { 36 | 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 37 | 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5, 38 | 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, 39 | 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 40 | 0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433, 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01, 41 | 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, 42 | 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 43 | 0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad, 44 | 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, 45 | 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 46 | 0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79, 47 | 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, 48 | 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 49 | 0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45, 50 | 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, 51 | 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d 52 | }, 53 | new uint[] { 54 | 0x00000000, 0x191b3141, 0x32366282, 0x2b2d53c3, 0x646cc504, 0x7d77f445, 0x565aa786, 0x4f4196c7, 0xc8d98a08, 0xd1c2bb49, 0xfaefe88a, 0xe3f4d9cb, 0xacb54f0c, 0xb5ae7e4d, 0x9e832d8e, 0x87981ccf, 55 | 0x4ac21251, 0x53d92310, 0x78f470d3, 0x61ef4192, 0x2eaed755, 0x37b5e614, 0x1c98b5d7, 0x05838496, 0x821b9859, 0x9b00a918, 0xb02dfadb, 0xa936cb9a, 0xe6775d5d, 0xff6c6c1c, 0xd4413fdf, 0xcd5a0e9e, 56 | 0x958424a2, 0x8c9f15e3, 0xa7b24620, 0xbea97761, 0xf1e8e1a6, 0xe8f3d0e7, 0xc3de8324, 0xdac5b265, 0x5d5daeaa, 0x44469feb, 0x6f6bcc28, 0x7670fd69, 0x39316bae, 0x202a5aef, 0x0b07092c, 0x121c386d, 57 | 0xdf4636f3, 0xc65d07b2, 0xed705471, 0xf46b6530, 0xbb2af3f7, 0xa231c2b6, 0x891c9175, 0x9007a034, 0x179fbcfb, 0x0e848dba, 0x25a9de79, 0x3cb2ef38, 0x73f379ff, 0x6ae848be, 0x41c51b7d, 0x58de2a3c, 58 | 0xf0794f05, 0xe9627e44, 0xc24f2d87, 0xdb541cc6, 0x94158a01, 0x8d0ebb40, 0xa623e883, 0xbf38d9c2, 0x38a0c50d, 0x21bbf44c, 0x0a96a78f, 0x138d96ce, 0x5ccc0009, 0x45d73148, 0x6efa628b, 0x77e153ca, 59 | 0xbabb5d54, 0xa3a06c15, 0x888d3fd6, 0x91960e97, 0xded79850, 0xc7cca911, 0xece1fad2, 0xf5facb93, 0x7262d75c, 0x6b79e61d, 0x4054b5de, 0x594f849f, 0x160e1258, 0x0f152319, 0x243870da, 0x3d23419b, 60 | 0x65fd6ba7, 0x7ce65ae6, 0x57cb0925, 0x4ed03864, 0x0191aea3, 0x188a9fe2, 0x33a7cc21, 0x2abcfd60, 0xad24e1af, 0xb43fd0ee, 0x9f12832d, 0x8609b26c, 0xc94824ab, 0xd05315ea, 0xfb7e4629, 0xe2657768, 61 | 0x2f3f79f6, 0x362448b7, 0x1d091b74, 0x04122a35, 0x4b53bcf2, 0x52488db3, 0x7965de70, 0x607eef31, 0xe7e6f3fe, 0xfefdc2bf, 0xd5d0917c, 0xcccba03d, 0x838a36fa, 0x9a9107bb, 0xb1bc5478, 0xa8a76539, 62 | 0x3b83984b, 0x2298a90a, 0x09b5fac9, 0x10aecb88, 0x5fef5d4f, 0x46f46c0e, 0x6dd93fcd, 0x74c20e8c, 0xf35a1243, 0xea412302, 0xc16c70c1, 0xd8774180, 0x9736d747, 0x8e2de606, 0xa500b5c5, 0xbc1b8484, 63 | 0x71418a1a, 0x685abb5b, 0x4377e898, 0x5a6cd9d9, 0x152d4f1e, 0x0c367e5f, 0x271b2d9c, 0x3e001cdd, 0xb9980012, 0xa0833153, 0x8bae6290, 0x92b553d1, 0xddf4c516, 0xc4eff457, 0xefc2a794, 0xf6d996d5, 64 | 0xae07bce9, 0xb71c8da8, 0x9c31de6b, 0x852aef2a, 0xca6b79ed, 0xd37048ac, 0xf85d1b6f, 0xe1462a2e, 0x66de36e1, 0x7fc507a0, 0x54e85463, 0x4df36522, 0x02b2f3e5, 0x1ba9c2a4, 0x30849167, 0x299fa026, 65 | 0xe4c5aeb8, 0xfdde9ff9, 0xd6f3cc3a, 0xcfe8fd7b, 0x80a96bbc, 0x99b25afd, 0xb29f093e, 0xab84387f, 0x2c1c24b0, 0x350715f1, 0x1e2a4632, 0x07317773, 0x4870e1b4, 0x516bd0f5, 0x7a468336, 0x635db277, 66 | 0xcbfad74e, 0xd2e1e60f, 0xf9ccb5cc, 0xe0d7848d, 0xaf96124a, 0xb68d230b, 0x9da070c8, 0x84bb4189, 0x03235d46, 0x1a386c07, 0x31153fc4, 0x280e0e85, 0x674f9842, 0x7e54a903, 0x5579fac0, 0x4c62cb81, 67 | 0x8138c51f, 0x9823f45e, 0xb30ea79d, 0xaa1596dc, 0xe554001b, 0xfc4f315a, 0xd7626299, 0xce7953d8, 0x49e14f17, 0x50fa7e56, 0x7bd72d95, 0x62cc1cd4, 0x2d8d8a13, 0x3496bb52, 0x1fbbe891, 0x06a0d9d0, 68 | 0x5e7ef3ec, 0x4765c2ad, 0x6c48916e, 0x7553a02f, 0x3a1236e8, 0x230907a9, 0x0824546a, 0x113f652b, 0x96a779e4, 0x8fbc48a5, 0xa4911b66, 0xbd8a2a27, 0xf2cbbce0, 0xebd08da1, 0xc0fdde62, 0xd9e6ef23, 69 | 0x14bce1bd, 0x0da7d0fc, 0x268a833f, 0x3f91b27e, 0x70d024b9, 0x69cb15f8, 0x42e6463b, 0x5bfd777a, 0xdc656bb5, 0xc57e5af4, 0xee530937, 0xf7483876, 0xb809aeb1, 0xa1129ff0, 0x8a3fcc33, 0x9324fd72 70 | }, 71 | new uint[] { 72 | 0x00000000, 0x01c26a37, 0x0384d46e, 0x0246be59, 0x0709a8dc, 0x06cbc2eb, 0x048d7cb2, 0x054f1685, 0x0e1351b8, 0x0fd13b8f, 0x0d9785d6, 0x0c55efe1, 0x091af964, 0x08d89353, 0x0a9e2d0a, 0x0b5c473d, 73 | 0x1c26a370, 0x1de4c947, 0x1fa2771e, 0x1e601d29, 0x1b2f0bac, 0x1aed619b, 0x18abdfc2, 0x1969b5f5, 0x1235f2c8, 0x13f798ff, 0x11b126a6, 0x10734c91, 0x153c5a14, 0x14fe3023, 0x16b88e7a, 0x177ae44d, 74 | 0x384d46e0, 0x398f2cd7, 0x3bc9928e, 0x3a0bf8b9, 0x3f44ee3c, 0x3e86840b, 0x3cc03a52, 0x3d025065, 0x365e1758, 0x379c7d6f, 0x35dac336, 0x3418a901, 0x3157bf84, 0x3095d5b3, 0x32d36bea, 0x331101dd, 75 | 0x246be590, 0x25a98fa7, 0x27ef31fe, 0x262d5bc9, 0x23624d4c, 0x22a0277b, 0x20e69922, 0x2124f315, 0x2a78b428, 0x2bbade1f, 0x29fc6046, 0x283e0a71, 0x2d711cf4, 0x2cb376c3, 0x2ef5c89a, 0x2f37a2ad, 76 | 0x709a8dc0, 0x7158e7f7, 0x731e59ae, 0x72dc3399, 0x7793251c, 0x76514f2b, 0x7417f172, 0x75d59b45, 0x7e89dc78, 0x7f4bb64f, 0x7d0d0816, 0x7ccf6221, 0x798074a4, 0x78421e93, 0x7a04a0ca, 0x7bc6cafd, 77 | 0x6cbc2eb0, 0x6d7e4487, 0x6f38fade, 0x6efa90e9, 0x6bb5866c, 0x6a77ec5b, 0x68315202, 0x69f33835, 0x62af7f08, 0x636d153f, 0x612bab66, 0x60e9c151, 0x65a6d7d4, 0x6464bde3, 0x662203ba, 0x67e0698d, 78 | 0x48d7cb20, 0x4915a117, 0x4b531f4e, 0x4a917579, 0x4fde63fc, 0x4e1c09cb, 0x4c5ab792, 0x4d98dda5, 0x46c49a98, 0x4706f0af, 0x45404ef6, 0x448224c1, 0x41cd3244, 0x400f5873, 0x4249e62a, 0x438b8c1d, 79 | 0x54f16850, 0x55330267, 0x5775bc3e, 0x56b7d609, 0x53f8c08c, 0x523aaabb, 0x507c14e2, 0x51be7ed5, 0x5ae239e8, 0x5b2053df, 0x5966ed86, 0x58a487b1, 0x5deb9134, 0x5c29fb03, 0x5e6f455a, 0x5fad2f6d, 80 | 0xe1351b80, 0xe0f771b7, 0xe2b1cfee, 0xe373a5d9, 0xe63cb35c, 0xe7fed96b, 0xe5b86732, 0xe47a0d05, 0xef264a38, 0xeee4200f, 0xeca29e56, 0xed60f461, 0xe82fe2e4, 0xe9ed88d3, 0xebab368a, 0xea695cbd, 81 | 0xfd13b8f0, 0xfcd1d2c7, 0xfe976c9e, 0xff5506a9, 0xfa1a102c, 0xfbd87a1b, 0xf99ec442, 0xf85cae75, 0xf300e948, 0xf2c2837f, 0xf0843d26, 0xf1465711, 0xf4094194, 0xf5cb2ba3, 0xf78d95fa, 0xf64fffcd, 82 | 0xd9785d60, 0xd8ba3757, 0xdafc890e, 0xdb3ee339, 0xde71f5bc, 0xdfb39f8b, 0xddf521d2, 0xdc374be5, 0xd76b0cd8, 0xd6a966ef, 0xd4efd8b6, 0xd52db281, 0xd062a404, 0xd1a0ce33, 0xd3e6706a, 0xd2241a5d, 83 | 0xc55efe10, 0xc49c9427, 0xc6da2a7e, 0xc7184049, 0xc25756cc, 0xc3953cfb, 0xc1d382a2, 0xc011e895, 0xcb4dafa8, 0xca8fc59f, 0xc8c97bc6, 0xc90b11f1, 0xcc440774, 0xcd866d43, 0xcfc0d31a, 0xce02b92d, 84 | 0x91af9640, 0x906dfc77, 0x922b422e, 0x93e92819, 0x96a63e9c, 0x976454ab, 0x9522eaf2, 0x94e080c5, 0x9fbcc7f8, 0x9e7eadcf, 0x9c381396, 0x9dfa79a1, 0x98b56f24, 0x99770513, 0x9b31bb4a, 0x9af3d17d, 85 | 0x8d893530, 0x8c4b5f07, 0x8e0de15e, 0x8fcf8b69, 0x8a809dec, 0x8b42f7db, 0x89044982, 0x88c623b5, 0x839a6488, 0x82580ebf, 0x801eb0e6, 0x81dcdad1, 0x8493cc54, 0x8551a663, 0x8717183a, 0x86d5720d, 86 | 0xa9e2d0a0, 0xa820ba97, 0xaa6604ce, 0xaba46ef9, 0xaeeb787c, 0xaf29124b, 0xad6fac12, 0xacadc625, 0xa7f18118, 0xa633eb2f, 0xa4755576, 0xa5b73f41, 0xa0f829c4, 0xa13a43f3, 0xa37cfdaa, 0xa2be979d, 87 | 0xb5c473d0, 0xb40619e7, 0xb640a7be, 0xb782cd89, 0xb2cddb0c, 0xb30fb13b, 0xb1490f62, 0xb08b6555, 0xbbd72268, 0xba15485f, 0xb853f606, 0xb9919c31, 0xbcde8ab4, 0xbd1ce083, 0xbf5a5eda, 0xbe9834ed 88 | }, 89 | new uint[] { 90 | 0x00000000, 0xb8bc6765, 0xaa09c88b, 0x12b5afee, 0x8f629757, 0x37def032, 0x256b5fdc, 0x9dd738b9, 0xc5b428ef, 0x7d084f8a, 0x6fbde064, 0xd7018701, 0x4ad6bfb8, 0xf26ad8dd, 0xe0df7733, 0x58631056, 91 | 0x5019579f, 0xe8a530fa, 0xfa109f14, 0x42acf871, 0xdf7bc0c8, 0x67c7a7ad, 0x75720843, 0xcdce6f26, 0x95ad7f70, 0x2d111815, 0x3fa4b7fb, 0x8718d09e, 0x1acfe827, 0xa2738f42, 0xb0c620ac, 0x087a47c9, 92 | 0xa032af3e, 0x188ec85b, 0x0a3b67b5, 0xb28700d0, 0x2f503869, 0x97ec5f0c, 0x8559f0e2, 0x3de59787, 0x658687d1, 0xdd3ae0b4, 0xcf8f4f5a, 0x7733283f, 0xeae41086, 0x525877e3, 0x40edd80d, 0xf851bf68, 93 | 0xf02bf8a1, 0x48979fc4, 0x5a22302a, 0xe29e574f, 0x7f496ff6, 0xc7f50893, 0xd540a77d, 0x6dfcc018, 0x359fd04e, 0x8d23b72b, 0x9f9618c5, 0x272a7fa0, 0xbafd4719, 0x0241207c, 0x10f48f92, 0xa848e8f7, 94 | 0x9b14583d, 0x23a83f58, 0x311d90b6, 0x89a1f7d3, 0x1476cf6a, 0xaccaa80f, 0xbe7f07e1, 0x06c36084, 0x5ea070d2, 0xe61c17b7, 0xf4a9b859, 0x4c15df3c, 0xd1c2e785, 0x697e80e0, 0x7bcb2f0e, 0xc377486b, 95 | 0xcb0d0fa2, 0x73b168c7, 0x6104c729, 0xd9b8a04c, 0x446f98f5, 0xfcd3ff90, 0xee66507e, 0x56da371b, 0x0eb9274d, 0xb6054028, 0xa4b0efc6, 0x1c0c88a3, 0x81dbb01a, 0x3967d77f, 0x2bd27891, 0x936e1ff4, 96 | 0x3b26f703, 0x839a9066, 0x912f3f88, 0x299358ed, 0xb4446054, 0x0cf80731, 0x1e4da8df, 0xa6f1cfba, 0xfe92dfec, 0x462eb889, 0x549b1767, 0xec277002, 0x71f048bb, 0xc94c2fde, 0xdbf98030, 0x6345e755, 97 | 0x6b3fa09c, 0xd383c7f9, 0xc1366817, 0x798a0f72, 0xe45d37cb, 0x5ce150ae, 0x4e54ff40, 0xf6e89825, 0xae8b8873, 0x1637ef16, 0x048240f8, 0xbc3e279d, 0x21e91f24, 0x99557841, 0x8be0d7af, 0x335cb0ca, 98 | 0xed59b63b, 0x55e5d15e, 0x47507eb0, 0xffec19d5, 0x623b216c, 0xda874609, 0xc832e9e7, 0x708e8e82, 0x28ed9ed4, 0x9051f9b1, 0x82e4565f, 0x3a58313a, 0xa78f0983, 0x1f336ee6, 0x0d86c108, 0xb53aa66d, 99 | 0xbd40e1a4, 0x05fc86c1, 0x1749292f, 0xaff54e4a, 0x322276f3, 0x8a9e1196, 0x982bbe78, 0x2097d91d, 0x78f4c94b, 0xc048ae2e, 0xd2fd01c0, 0x6a4166a5, 0xf7965e1c, 0x4f2a3979, 0x5d9f9697, 0xe523f1f2, 100 | 0x4d6b1905, 0xf5d77e60, 0xe762d18e, 0x5fdeb6eb, 0xc2098e52, 0x7ab5e937, 0x680046d9, 0xd0bc21bc, 0x88df31ea, 0x3063568f, 0x22d6f961, 0x9a6a9e04, 0x07bda6bd, 0xbf01c1d8, 0xadb46e36, 0x15080953, 101 | 0x1d724e9a, 0xa5ce29ff, 0xb77b8611, 0x0fc7e174, 0x9210d9cd, 0x2aacbea8, 0x38191146, 0x80a57623, 0xd8c66675, 0x607a0110, 0x72cfaefe, 0xca73c99b, 0x57a4f122, 0xef189647, 0xfdad39a9, 0x45115ecc, 102 | 0x764dee06, 0xcef18963, 0xdc44268d, 0x64f841e8, 0xf92f7951, 0x41931e34, 0x5326b1da, 0xeb9ad6bf, 0xb3f9c6e9, 0x0b45a18c, 0x19f00e62, 0xa14c6907, 0x3c9b51be, 0x842736db, 0x96929935, 0x2e2efe50, 103 | 0x2654b999, 0x9ee8defc, 0x8c5d7112, 0x34e11677, 0xa9362ece, 0x118a49ab, 0x033fe645, 0xbb838120, 0xe3e09176, 0x5b5cf613, 0x49e959fd, 0xf1553e98, 0x6c820621, 0xd43e6144, 0xc68bceaa, 0x7e37a9cf, 104 | 0xd67f4138, 0x6ec3265d, 0x7c7689b3, 0xc4caeed6, 0x591dd66f, 0xe1a1b10a, 0xf3141ee4, 0x4ba87981, 0x13cb69d7, 0xab770eb2, 0xb9c2a15c, 0x017ec639, 0x9ca9fe80, 0x241599e5, 0x36a0360b, 0x8e1c516e, 105 | 0x866616a7, 0x3eda71c2, 0x2c6fde2c, 0x94d3b949, 0x090481f0, 0xb1b8e695, 0xa30d497b, 0x1bb12e1e, 0x43d23e48, 0xfb6e592d, 0xe9dbf6c3, 0x516791a6, 0xccb0a91f, 0x740cce7a, 0x66b96194, 0xde0506f1 106 | }, 107 | new uint[] { 108 | 0x00000000, 0x3d6029b0, 0x7ac05360, 0x47a07ad0, 0xf580a6c0, 0xc8e08f70, 0x8f40f5a0, 0xb220dc10, 0x30704bc1, 0x0d106271, 0x4ab018a1, 0x77d03111, 0xc5f0ed01, 0xf890c4b1, 0xbf30be61, 0x825097d1, 109 | 0x60e09782, 0x5d80be32, 0x1a20c4e2, 0x2740ed52, 0x95603142, 0xa80018f2, 0xefa06222, 0xd2c04b92, 0x5090dc43, 0x6df0f5f3, 0x2a508f23, 0x1730a693, 0xa5107a83, 0x98705333, 0xdfd029e3, 0xe2b00053, 110 | 0xc1c12f04, 0xfca106b4, 0xbb017c64, 0x866155d4, 0x344189c4, 0x0921a074, 0x4e81daa4, 0x73e1f314, 0xf1b164c5, 0xccd14d75, 0x8b7137a5, 0xb6111e15, 0x0431c205, 0x3951ebb5, 0x7ef19165, 0x4391b8d5, 111 | 0xa121b886, 0x9c419136, 0xdbe1ebe6, 0xe681c256, 0x54a11e46, 0x69c137f6, 0x2e614d26, 0x13016496, 0x9151f347, 0xac31daf7, 0xeb91a027, 0xd6f18997, 0x64d15587, 0x59b17c37, 0x1e1106e7, 0x23712f57, 112 | 0x58f35849, 0x659371f9, 0x22330b29, 0x1f532299, 0xad73fe89, 0x9013d739, 0xd7b3ade9, 0xead38459, 0x68831388, 0x55e33a38, 0x124340e8, 0x2f236958, 0x9d03b548, 0xa0639cf8, 0xe7c3e628, 0xdaa3cf98, 113 | 0x3813cfcb, 0x0573e67b, 0x42d39cab, 0x7fb3b51b, 0xcd93690b, 0xf0f340bb, 0xb7533a6b, 0x8a3313db, 0x0863840a, 0x3503adba, 0x72a3d76a, 0x4fc3feda, 0xfde322ca, 0xc0830b7a, 0x872371aa, 0xba43581a, 114 | 0x9932774d, 0xa4525efd, 0xe3f2242d, 0xde920d9d, 0x6cb2d18d, 0x51d2f83d, 0x167282ed, 0x2b12ab5d, 0xa9423c8c, 0x9422153c, 0xd3826fec, 0xeee2465c, 0x5cc29a4c, 0x61a2b3fc, 0x2602c92c, 0x1b62e09c, 115 | 0xf9d2e0cf, 0xc4b2c97f, 0x8312b3af, 0xbe729a1f, 0x0c52460f, 0x31326fbf, 0x7692156f, 0x4bf23cdf, 0xc9a2ab0e, 0xf4c282be, 0xb362f86e, 0x8e02d1de, 0x3c220dce, 0x0142247e, 0x46e25eae, 0x7b82771e, 116 | 0xb1e6b092, 0x8c869922, 0xcb26e3f2, 0xf646ca42, 0x44661652, 0x79063fe2, 0x3ea64532, 0x03c66c82, 0x8196fb53, 0xbcf6d2e3, 0xfb56a833, 0xc6368183, 0x74165d93, 0x49767423, 0x0ed60ef3, 0x33b62743, 117 | 0xd1062710, 0xec660ea0, 0xabc67470, 0x96a65dc0, 0x248681d0, 0x19e6a860, 0x5e46d2b0, 0x6326fb00, 0xe1766cd1, 0xdc164561, 0x9bb63fb1, 0xa6d61601, 0x14f6ca11, 0x2996e3a1, 0x6e369971, 0x5356b0c1, 118 | 0x70279f96, 0x4d47b626, 0x0ae7ccf6, 0x3787e546, 0x85a73956, 0xb8c710e6, 0xff676a36, 0xc2074386, 0x4057d457, 0x7d37fde7, 0x3a978737, 0x07f7ae87, 0xb5d77297, 0x88b75b27, 0xcf1721f7, 0xf2770847, 119 | 0x10c70814, 0x2da721a4, 0x6a075b74, 0x576772c4, 0xe547aed4, 0xd8278764, 0x9f87fdb4, 0xa2e7d404, 0x20b743d5, 0x1dd76a65, 0x5a7710b5, 0x67173905, 0xd537e515, 0xe857cca5, 0xaff7b675, 0x92979fc5, 120 | 0xe915e8db, 0xd475c16b, 0x93d5bbbb, 0xaeb5920b, 0x1c954e1b, 0x21f567ab, 0x66551d7b, 0x5b3534cb, 0xd965a31a, 0xe4058aaa, 0xa3a5f07a, 0x9ec5d9ca, 0x2ce505da, 0x11852c6a, 0x562556ba, 0x6b457f0a, 121 | 0x89f57f59, 0xb49556e9, 0xf3352c39, 0xce550589, 0x7c75d999, 0x4115f029, 0x06b58af9, 0x3bd5a349, 0xb9853498, 0x84e51d28, 0xc34567f8, 0xfe254e48, 0x4c059258, 0x7165bbe8, 0x36c5c138, 0x0ba5e888, 122 | 0x28d4c7df, 0x15b4ee6f, 0x521494bf, 0x6f74bd0f, 0xdd54611f, 0xe03448af, 0xa794327f, 0x9af41bcf, 0x18a48c1e, 0x25c4a5ae, 0x6264df7e, 0x5f04f6ce, 0xed242ade, 0xd044036e, 0x97e479be, 0xaa84500e, 123 | 0x4834505d, 0x755479ed, 0x32f4033d, 0x0f942a8d, 0xbdb4f69d, 0x80d4df2d, 0xc774a5fd, 0xfa148c4d, 0x78441b9c, 0x4524322c, 0x028448fc, 0x3fe4614c, 0x8dc4bd5c, 0xb0a494ec, 0xf704ee3c, 0xca64c78c 124 | }, 125 | new uint[] { 126 | 0x00000000, 0xcb5cd3a5, 0x4dc8a10b, 0x869472ae, 0x9b914216, 0x50cd91b3, 0xd659e31d, 0x1d0530b8, 0xec53826d, 0x270f51c8, 0xa19b2366, 0x6ac7f0c3, 0x77c2c07b, 0xbc9e13de, 0x3a0a6170, 0xf156b2d5, 127 | 0x03d6029b, 0xc88ad13e, 0x4e1ea390, 0x85427035, 0x9847408d, 0x531b9328, 0xd58fe186, 0x1ed33223, 0xef8580f6, 0x24d95353, 0xa24d21fd, 0x6911f258, 0x7414c2e0, 0xbf481145, 0x39dc63eb, 0xf280b04e, 128 | 0x07ac0536, 0xccf0d693, 0x4a64a43d, 0x81387798, 0x9c3d4720, 0x57619485, 0xd1f5e62b, 0x1aa9358e, 0xebff875b, 0x20a354fe, 0xa6372650, 0x6d6bf5f5, 0x706ec54d, 0xbb3216e8, 0x3da66446, 0xf6fab7e3, 129 | 0x047a07ad, 0xcf26d408, 0x49b2a6a6, 0x82ee7503, 0x9feb45bb, 0x54b7961e, 0xd223e4b0, 0x197f3715, 0xe82985c0, 0x23755665, 0xa5e124cb, 0x6ebdf76e, 0x73b8c7d6, 0xb8e41473, 0x3e7066dd, 0xf52cb578, 130 | 0x0f580a6c, 0xc404d9c9, 0x4290ab67, 0x89cc78c2, 0x94c9487a, 0x5f959bdf, 0xd901e971, 0x125d3ad4, 0xe30b8801, 0x28575ba4, 0xaec3290a, 0x659ffaaf, 0x789aca17, 0xb3c619b2, 0x35526b1c, 0xfe0eb8b9, 131 | 0x0c8e08f7, 0xc7d2db52, 0x4146a9fc, 0x8a1a7a59, 0x971f4ae1, 0x5c439944, 0xdad7ebea, 0x118b384f, 0xe0dd8a9a, 0x2b81593f, 0xad152b91, 0x6649f834, 0x7b4cc88c, 0xb0101b29, 0x36846987, 0xfdd8ba22, 132 | 0x08f40f5a, 0xc3a8dcff, 0x453cae51, 0x8e607df4, 0x93654d4c, 0x58399ee9, 0xdeadec47, 0x15f13fe2, 0xe4a78d37, 0x2ffb5e92, 0xa96f2c3c, 0x6233ff99, 0x7f36cf21, 0xb46a1c84, 0x32fe6e2a, 0xf9a2bd8f, 133 | 0x0b220dc1, 0xc07ede64, 0x46eaacca, 0x8db67f6f, 0x90b34fd7, 0x5bef9c72, 0xdd7beedc, 0x16273d79, 0xe7718fac, 0x2c2d5c09, 0xaab92ea7, 0x61e5fd02, 0x7ce0cdba, 0xb7bc1e1f, 0x31286cb1, 0xfa74bf14, 134 | 0x1eb014d8, 0xd5ecc77d, 0x5378b5d3, 0x98246676, 0x852156ce, 0x4e7d856b, 0xc8e9f7c5, 0x03b52460, 0xf2e396b5, 0x39bf4510, 0xbf2b37be, 0x7477e41b, 0x6972d4a3, 0xa22e0706, 0x24ba75a8, 0xefe6a60d, 135 | 0x1d661643, 0xd63ac5e6, 0x50aeb748, 0x9bf264ed, 0x86f75455, 0x4dab87f0, 0xcb3ff55e, 0x006326fb, 0xf135942e, 0x3a69478b, 0xbcfd3525, 0x77a1e680, 0x6aa4d638, 0xa1f8059d, 0x276c7733, 0xec30a496, 136 | 0x191c11ee, 0xd240c24b, 0x54d4b0e5, 0x9f886340, 0x828d53f8, 0x49d1805d, 0xcf45f2f3, 0x04192156, 0xf54f9383, 0x3e134026, 0xb8873288, 0x73dbe12d, 0x6eded195, 0xa5820230, 0x2316709e, 0xe84aa33b, 137 | 0x1aca1375, 0xd196c0d0, 0x5702b27e, 0x9c5e61db, 0x815b5163, 0x4a0782c6, 0xcc93f068, 0x07cf23cd, 0xf6999118, 0x3dc542bd, 0xbb513013, 0x700de3b6, 0x6d08d30e, 0xa65400ab, 0x20c07205, 0xeb9ca1a0, 138 | 0x11e81eb4, 0xdab4cd11, 0x5c20bfbf, 0x977c6c1a, 0x8a795ca2, 0x41258f07, 0xc7b1fda9, 0x0ced2e0c, 0xfdbb9cd9, 0x36e74f7c, 0xb0733dd2, 0x7b2fee77, 0x662adecf, 0xad760d6a, 0x2be27fc4, 0xe0beac61, 139 | 0x123e1c2f, 0xd962cf8a, 0x5ff6bd24, 0x94aa6e81, 0x89af5e39, 0x42f38d9c, 0xc467ff32, 0x0f3b2c97, 0xfe6d9e42, 0x35314de7, 0xb3a53f49, 0x78f9ecec, 0x65fcdc54, 0xaea00ff1, 0x28347d5f, 0xe368aefa, 140 | 0x16441b82, 0xdd18c827, 0x5b8cba89, 0x90d0692c, 0x8dd55994, 0x46898a31, 0xc01df89f, 0x0b412b3a, 0xfa1799ef, 0x314b4a4a, 0xb7df38e4, 0x7c83eb41, 0x6186dbf9, 0xaada085c, 0x2c4e7af2, 0xe712a957, 141 | 0x15921919, 0xdececabc, 0x585ab812, 0x93066bb7, 0x8e035b0f, 0x455f88aa, 0xc3cbfa04, 0x089729a1, 0xf9c19b74, 0x329d48d1, 0xb4093a7f, 0x7f55e9da, 0x6250d962, 0xa90c0ac7, 0x2f987869, 0xe4c4abcc 142 | }, 143 | new uint[] { 144 | 0x00000000, 0xa6770bb4, 0x979f1129, 0x31e81a9d, 0xf44f2413, 0x52382fa7, 0x63d0353a, 0xc5a73e8e, 0x33ef4e67, 0x959845d3, 0xa4705f4e, 0x020754fa, 0xc7a06a74, 0x61d761c0, 0x503f7b5d, 0xf64870e9, 145 | 0x67de9cce, 0xc1a9977a, 0xf0418de7, 0x56368653, 0x9391b8dd, 0x35e6b369, 0x040ea9f4, 0xa279a240, 0x5431d2a9, 0xf246d91d, 0xc3aec380, 0x65d9c834, 0xa07ef6ba, 0x0609fd0e, 0x37e1e793, 0x9196ec27, 146 | 0xcfbd399c, 0x69ca3228, 0x582228b5, 0xfe552301, 0x3bf21d8f, 0x9d85163b, 0xac6d0ca6, 0x0a1a0712, 0xfc5277fb, 0x5a257c4f, 0x6bcd66d2, 0xcdba6d66, 0x081d53e8, 0xae6a585c, 0x9f8242c1, 0x39f54975, 147 | 0xa863a552, 0x0e14aee6, 0x3ffcb47b, 0x998bbfcf, 0x5c2c8141, 0xfa5b8af5, 0xcbb39068, 0x6dc49bdc, 0x9b8ceb35, 0x3dfbe081, 0x0c13fa1c, 0xaa64f1a8, 0x6fc3cf26, 0xc9b4c492, 0xf85cde0f, 0x5e2bd5bb, 148 | 0x440b7579, 0xe27c7ecd, 0xd3946450, 0x75e36fe4, 0xb044516a, 0x16335ade, 0x27db4043, 0x81ac4bf7, 0x77e43b1e, 0xd19330aa, 0xe07b2a37, 0x460c2183, 0x83ab1f0d, 0x25dc14b9, 0x14340e24, 0xb2430590, 149 | 0x23d5e9b7, 0x85a2e203, 0xb44af89e, 0x123df32a, 0xd79acda4, 0x71edc610, 0x4005dc8d, 0xe672d739, 0x103aa7d0, 0xb64dac64, 0x87a5b6f9, 0x21d2bd4d, 0xe47583c3, 0x42028877, 0x73ea92ea, 0xd59d995e, 150 | 0x8bb64ce5, 0x2dc14751, 0x1c295dcc, 0xba5e5678, 0x7ff968f6, 0xd98e6342, 0xe86679df, 0x4e11726b, 0xb8590282, 0x1e2e0936, 0x2fc613ab, 0x89b1181f, 0x4c162691, 0xea612d25, 0xdb8937b8, 0x7dfe3c0c, 151 | 0xec68d02b, 0x4a1fdb9f, 0x7bf7c102, 0xdd80cab6, 0x1827f438, 0xbe50ff8c, 0x8fb8e511, 0x29cfeea5, 0xdf879e4c, 0x79f095f8, 0x48188f65, 0xee6f84d1, 0x2bc8ba5f, 0x8dbfb1eb, 0xbc57ab76, 0x1a20a0c2, 152 | 0x8816eaf2, 0x2e61e146, 0x1f89fbdb, 0xb9fef06f, 0x7c59cee1, 0xda2ec555, 0xebc6dfc8, 0x4db1d47c, 0xbbf9a495, 0x1d8eaf21, 0x2c66b5bc, 0x8a11be08, 0x4fb68086, 0xe9c18b32, 0xd82991af, 0x7e5e9a1b, 153 | 0xefc8763c, 0x49bf7d88, 0x78576715, 0xde206ca1, 0x1b87522f, 0xbdf0599b, 0x8c184306, 0x2a6f48b2, 0xdc27385b, 0x7a5033ef, 0x4bb82972, 0xedcf22c6, 0x28681c48, 0x8e1f17fc, 0xbff70d61, 0x198006d5, 154 | 0x47abd36e, 0xe1dcd8da, 0xd034c247, 0x7643c9f3, 0xb3e4f77d, 0x1593fcc9, 0x247be654, 0x820cede0, 0x74449d09, 0xd23396bd, 0xe3db8c20, 0x45ac8794, 0x800bb91a, 0x267cb2ae, 0x1794a833, 0xb1e3a387, 155 | 0x20754fa0, 0x86024414, 0xb7ea5e89, 0x119d553d, 0xd43a6bb3, 0x724d6007, 0x43a57a9a, 0xe5d2712e, 0x139a01c7, 0xb5ed0a73, 0x840510ee, 0x22721b5a, 0xe7d525d4, 0x41a22e60, 0x704a34fd, 0xd63d3f49, 156 | 0xcc1d9f8b, 0x6a6a943f, 0x5b828ea2, 0xfdf58516, 0x3852bb98, 0x9e25b02c, 0xafcdaab1, 0x09baa105, 0xfff2d1ec, 0x5985da58, 0x686dc0c5, 0xce1acb71, 0x0bbdf5ff, 0xadcafe4b, 0x9c22e4d6, 0x3a55ef62, 157 | 0xabc30345, 0x0db408f1, 0x3c5c126c, 0x9a2b19d8, 0x5f8c2756, 0xf9fb2ce2, 0xc813367f, 0x6e643dcb, 0x982c4d22, 0x3e5b4696, 0x0fb35c0b, 0xa9c457bf, 0x6c636931, 0xca146285, 0xfbfc7818, 0x5d8b73ac, 158 | 0x03a0a617, 0xa5d7ada3, 0x943fb73e, 0x3248bc8a, 0xf7ef8204, 0x519889b0, 0x6070932d, 0xc6079899, 0x304fe870, 0x9638e3c4, 0xa7d0f959, 0x01a7f2ed, 0xc400cc63, 0x6277c7d7, 0x539fdd4a, 0xf5e8d6fe, 159 | 0x647e3ad9, 0xc209316d, 0xf3e12bf0, 0x55962044, 0x90311eca, 0x3646157e, 0x07ae0fe3, 0xa1d90457, 0x579174be, 0xf1e67f0a, 0xc00e6597, 0x66796e23, 0xa3de50ad, 0x05a95b19, 0x34414184, 0x92364a30 160 | }, 161 | new uint[] { 162 | 0x00000000, 0xccaa009e, 0x4225077d, 0x8e8f07e3, 0x844a0efa, 0x48e00e64, 0xc66f0987, 0x0ac50919, 0xd3e51bb5, 0x1f4f1b2b, 0x91c01cc8, 0x5d6a1c56, 0x57af154f, 0x9b0515d1, 0x158a1232, 0xd92012ac, 163 | 0x7cbb312b, 0xb01131b5, 0x3e9e3656, 0xf23436c8, 0xf8f13fd1, 0x345b3f4f, 0xbad438ac, 0x767e3832, 0xaf5e2a9e, 0x63f42a00, 0xed7b2de3, 0x21d12d7d, 0x2b142464, 0xe7be24fa, 0x69312319, 0xa59b2387, 164 | 0xf9766256, 0x35dc62c8, 0xbb53652b, 0x77f965b5, 0x7d3c6cac, 0xb1966c32, 0x3f196bd1, 0xf3b36b4f, 0x2a9379e3, 0xe639797d, 0x68b67e9e, 0xa41c7e00, 0xaed97719, 0x62737787, 0xecfc7064, 0x205670fa, 165 | 0x85cd537d, 0x496753e3, 0xc7e85400, 0x0b42549e, 0x01875d87, 0xcd2d5d19, 0x43a25afa, 0x8f085a64, 0x562848c8, 0x9a824856, 0x140d4fb5, 0xd8a74f2b, 0xd2624632, 0x1ec846ac, 0x9047414f, 0x5ced41d1, 166 | 0x299dc2ed, 0xe537c273, 0x6bb8c590, 0xa712c50e, 0xadd7cc17, 0x617dcc89, 0xeff2cb6a, 0x2358cbf4, 0xfa78d958, 0x36d2d9c6, 0xb85dde25, 0x74f7debb, 0x7e32d7a2, 0xb298d73c, 0x3c17d0df, 0xf0bdd041, 167 | 0x5526f3c6, 0x998cf358, 0x1703f4bb, 0xdba9f425, 0xd16cfd3c, 0x1dc6fda2, 0x9349fa41, 0x5fe3fadf, 0x86c3e873, 0x4a69e8ed, 0xc4e6ef0e, 0x084cef90, 0x0289e689, 0xce23e617, 0x40ace1f4, 0x8c06e16a, 168 | 0xd0eba0bb, 0x1c41a025, 0x92cea7c6, 0x5e64a758, 0x54a1ae41, 0x980baedf, 0x1684a93c, 0xda2ea9a2, 0x030ebb0e, 0xcfa4bb90, 0x412bbc73, 0x8d81bced, 0x8744b5f4, 0x4beeb56a, 0xc561b289, 0x09cbb217, 169 | 0xac509190, 0x60fa910e, 0xee7596ed, 0x22df9673, 0x281a9f6a, 0xe4b09ff4, 0x6a3f9817, 0xa6959889, 0x7fb58a25, 0xb31f8abb, 0x3d908d58, 0xf13a8dc6, 0xfbff84df, 0x37558441, 0xb9da83a2, 0x7570833c, 170 | 0x533b85da, 0x9f918544, 0x111e82a7, 0xddb48239, 0xd7718b20, 0x1bdb8bbe, 0x95548c5d, 0x59fe8cc3, 0x80de9e6f, 0x4c749ef1, 0xc2fb9912, 0x0e51998c, 0x04949095, 0xc83e900b, 0x46b197e8, 0x8a1b9776, 171 | 0x2f80b4f1, 0xe32ab46f, 0x6da5b38c, 0xa10fb312, 0xabcaba0b, 0x6760ba95, 0xe9efbd76, 0x2545bde8, 0xfc65af44, 0x30cfafda, 0xbe40a839, 0x72eaa8a7, 0x782fa1be, 0xb485a120, 0x3a0aa6c3, 0xf6a0a65d, 172 | 0xaa4de78c, 0x66e7e712, 0xe868e0f1, 0x24c2e06f, 0x2e07e976, 0xe2ade9e8, 0x6c22ee0b, 0xa088ee95, 0x79a8fc39, 0xb502fca7, 0x3b8dfb44, 0xf727fbda, 0xfde2f2c3, 0x3148f25d, 0xbfc7f5be, 0x736df520, 173 | 0xd6f6d6a7, 0x1a5cd639, 0x94d3d1da, 0x5879d144, 0x52bcd85d, 0x9e16d8c3, 0x1099df20, 0xdc33dfbe, 0x0513cd12, 0xc9b9cd8c, 0x4736ca6f, 0x8b9ccaf1, 0x8159c3e8, 0x4df3c376, 0xc37cc495, 0x0fd6c40b, 174 | 0x7aa64737, 0xb60c47a9, 0x3883404a, 0xf42940d4, 0xfeec49cd, 0x32464953, 0xbcc94eb0, 0x70634e2e, 0xa9435c82, 0x65e95c1c, 0xeb665bff, 0x27cc5b61, 0x2d095278, 0xe1a352e6, 0x6f2c5505, 0xa386559b, 175 | 0x061d761c, 0xcab77682, 0x44387161, 0x889271ff, 0x825778e6, 0x4efd7878, 0xc0727f9b, 0x0cd87f05, 0xd5f86da9, 0x19526d37, 0x97dd6ad4, 0x5b776a4a, 0x51b26353, 0x9d1863cd, 0x1397642e, 0xdf3d64b0, 176 | 0x83d02561, 0x4f7a25ff, 0xc1f5221c, 0x0d5f2282, 0x079a2b9b, 0xcb302b05, 0x45bf2ce6, 0x89152c78, 0x50353ed4, 0x9c9f3e4a, 0x121039a9, 0xdeba3937, 0xd47f302e, 0x18d530b0, 0x965a3753, 0x5af037cd, 177 | 0xff6b144a, 0x33c114d4, 0xbd4e1337, 0x71e413a9, 0x7b211ab0, 0xb78b1a2e, 0x39041dcd, 0xf5ae1d53, 0x2c8e0fff, 0xe0240f61, 0x6eab0882, 0xa201081c, 0xa8c40105, 0x646e019b, 0xeae10678, 0x264b06e6 178 | } 179 | }; 180 | 181 | public static uint UE4Strihash_DEPRECATED(string str) 182 | { 183 | uint Hash = 0; 184 | byte[] Data = Encoding.UTF8.GetBytes(str.ToUpper()); 185 | for (int i = 0; i < Data.Length; i++) 186 | { 187 | byte Ch = Data[i]; 188 | Hash = ((Hash >> 8) & 0x00FFFFFF) ^ CRCTable_DEPRECATED[(Hash ^ Ch) & 0x000000FF]; 189 | } 190 | return Hash; 191 | } 192 | 193 | public static string UE4ToUpper(string str) 194 | { 195 | string retVal = ""; 196 | for (int i = 0; i < str.Length; i++) 197 | { 198 | var Ch = str[i]; 199 | var upperCh = char.ToUpper(Ch); 200 | if (Ch >= 0xFF41 && Ch <= 0xFF5A) 201 | upperCh = Ch; // ue4 doesn't set these to uppercase 202 | retVal += upperCh; 203 | } 204 | return retVal; 205 | } 206 | 207 | public static uint UE4Strihash_DEPRECATED_Wide(string str) 208 | { 209 | uint Hash = 0; 210 | byte[] Data = Encoding.Unicode.GetBytes(UE4ToUpper(str)); 211 | for (int i = 0; i < Data.Length; i+=2) 212 | { 213 | byte Ch = Data[i]; 214 | Hash = ((Hash >> 8) & 0x00FFFFFF) ^ CRCTable_DEPRECATED[(Hash ^ Ch) & 0x000000FF]; 215 | Ch = Data[i + 1]; 216 | Hash = ((Hash >> 8) & 0x00FFFFFF) ^ CRCTable_DEPRECATED[(Hash ^ Ch) & 0x000000FF]; 217 | } 218 | return Hash; 219 | } 220 | 221 | 222 | public static uint UE4StrCrc32(string str, uint CRC = 0) 223 | { 224 | CRC = ~CRC; 225 | byte[] Data = Encoding.UTF8.GetBytes(str); 226 | for (int i = 0; i < Data.Length; i++) 227 | { 228 | byte Ch = Data[i]; 229 | CRC = (CRC >> 8) ^ CRCTablesSB8[0][(CRC ^ Ch) & 0xFF]; 230 | Ch >>= 8; 231 | CRC = (CRC >> 8) ^ CRCTablesSB8[0][(CRC ^ Ch) & 0xFF]; 232 | Ch >>= 8; 233 | CRC = (CRC >> 8) ^ CRCTablesSB8[0][(CRC ^ Ch) & 0xFF]; 234 | Ch >>= 8; 235 | CRC = (CRC >> 8) ^ CRCTablesSB8[0][(CRC ^ Ch) & 0xFF]; 236 | } 237 | return ~CRC; 238 | } 239 | 240 | public static uint UE4StrCrc32_Wide(string str, uint CRC = 0) 241 | { 242 | CRC = ~CRC; 243 | byte[] Data = Encoding.Unicode.GetBytes(str); 244 | for (int i = 0; i < Data.Length; i+=2) 245 | { 246 | ushort Ch = (ushort)((Data[i+1] << 8) | Data[i]); 247 | CRC = (CRC >> 8) ^ CRCTablesSB8[0][(CRC ^ Ch) & 0xFF]; 248 | Ch >>= 8; 249 | CRC = (CRC >> 8) ^ CRCTablesSB8[0][(CRC ^ Ch) & 0xFF]; 250 | Ch >>= 8; 251 | CRC = (CRC >> 8) ^ CRCTablesSB8[0][(CRC ^ Ch) & 0xFF]; 252 | Ch >>= 8; 253 | CRC = (CRC >> 8) ^ CRCTablesSB8[0][(CRC ^ Ch) & 0xFF]; 254 | } 255 | return ~CRC; 256 | } 257 | 258 | public static bool HasNonASCIIChars(string str) 259 | { 260 | return (Encoding.UTF8.GetByteCount(str) != str.Length); 261 | } 262 | 263 | public static void WriteFString(this BinaryWriter writer, string val) 264 | { 265 | byte[] data; 266 | bool unicode = HasNonASCIIChars(val); 267 | int length = 0; 268 | if (unicode) 269 | { 270 | data = Encoding.Unicode.GetBytes(val); 271 | length = (data.Length / 2); 272 | } 273 | else 274 | { 275 | data = Encoding.UTF8.GetBytes(val); 276 | length = data.Length; 277 | } 278 | length++; 279 | if (unicode) 280 | length = -length; 281 | 282 | if (data.Length == 0) 283 | length = 0; 284 | 285 | writer.Write(length); 286 | writer.Write(data); 287 | 288 | if (data.Length > 0) 289 | { 290 | if (unicode) 291 | writer.Write((short)0); 292 | else 293 | writer.Write((byte)0); 294 | } 295 | } 296 | 297 | public static string ReadFString(this BinaryReader reader) 298 | { 299 | int size = reader.ReadInt32(); 300 | if (size == 0) 301 | return String.Empty; 302 | 303 | bool unicode = size < 0; 304 | if (unicode) 305 | size = (-size) * 2; 306 | 307 | byte[] data = reader.ReadBytes(size); 308 | Array.Resize(ref data, unicode ? size - 2 : size - 1); // remove null terminator 309 | 310 | if (unicode) 311 | return Encoding.Unicode.GetString(data); 312 | 313 | return Encoding.UTF8.GetString(data); 314 | } 315 | 316 | public static T ReadStruct(this Stream stream) 317 | { 318 | var size = Marshal.SizeOf(typeof(T)); 319 | 320 | // Read in a byte array 321 | byte[] bytes = new byte[size]; 322 | stream.Read(bytes, 0, size); 323 | //stream.ReadAsync(bytes, 0, size).Wait(); 324 | 325 | return BytesToStruct(bytes); 326 | } 327 | 328 | public static T ReadStruct(this BinaryReader reader) 329 | { 330 | var size = Marshal.SizeOf(typeof(T)); 331 | 332 | // Read in a byte array 333 | byte[] bytes = reader.ReadBytes(size); 334 | 335 | return BytesToStruct(bytes); 336 | } 337 | 338 | public static T BytesToStruct(byte[] bytes) 339 | { 340 | // Pin the managed memory while, copy it out the data, then unpin it 341 | var handle = GCHandle.Alloc(bytes, GCHandleType.Pinned); 342 | var theStructure = (T)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(T)); 343 | handle.Free(); 344 | 345 | return theStructure; 346 | } 347 | 348 | public static byte[] StructToBytes(T structure) 349 | { 350 | var bytes = new byte[Marshal.SizeOf(typeof(T))]; 351 | 352 | // Pin the managed memory while, copy in the data, then unpin it 353 | var handle = GCHandle.Alloc(bytes, GCHandleType.Pinned); 354 | Marshal.StructureToPtr(structure, handle.AddrOfPinnedObject(), true); 355 | handle.Free(); 356 | 357 | return bytes; 358 | } 359 | 360 | public static bool WriteStruct(this BinaryWriter writer, T structure) 361 | { 362 | byte[] bytes = StructToBytes(structure); 363 | 364 | writer.Write(bytes); 365 | 366 | return true; 367 | } 368 | 369 | public static string ToHexString(this byte[] bytes) 370 | { 371 | return bytes.Aggregate("", (current, b) => current + b.ToString("X2")); 372 | } 373 | 374 | public static byte[] StringToByteArray(string hex) 375 | { 376 | if (hex.Length % 2 == 1) 377 | throw new Exception("The binary key cannot have an odd number of digits"); 378 | 379 | byte[] arr = new byte[hex.Length >> 1]; 380 | 381 | for (int i = 0; i < hex.Length >> 1; ++i) 382 | { 383 | arr[i] = (byte)((GetHexVal(hex[i << 1]) << 4) + (GetHexVal(hex[(i << 1) + 1]))); 384 | } 385 | 386 | return arr; 387 | } 388 | 389 | public static int GetHexVal(char hex) 390 | { 391 | int val = (int)hex; 392 | //For uppercase A-F letters: 393 | //return val - (val < 58 ? 48 : 55); 394 | //For lowercase a-f letters: 395 | //return val - (val < 58 ? 48 : 87); 396 | //Or the two combined, but a bit slower: 397 | return val - (val < 58 ? 48 : (val < 97 ? 55 : 87)); 398 | } 399 | 400 | readonly static Regex csvParser = new Regex("(?:^|,)(\\\"(?:[^\\\"]+|\\\"\\\")*\\\"|[^,]*)", RegexOptions.Compiled); 401 | 402 | // better regex that can handle empty cells, unfortunately doesn't work with strings that have double-quotes somewhere besides the start/end of cell 403 | //readonly static Regex csvParser = new Regex(@"(?<=^|,)(?:""{2}|(?:)|[^,""\r\n]+|""(?:""{2}|[^""]+)+"")(?=,|$)", RegexOptions.Compiled); 404 | 405 | //given a row from the csv file, loop through returning an array of column values 406 | public static IEnumerable ProcessCsvRow(string row) 407 | { 408 | MatchCollection results = csvParser.Matches(row); 409 | foreach (Match match in results) 410 | foreach (Capture capture in match.Captures) 411 | { 412 | var ret = capture.Value ?? string.Empty; 413 | 414 | if (ret.StartsWith(",")) 415 | ret = ret.Substring(1); 416 | if (ret == "\"") 417 | ret = string.Empty; 418 | 419 | yield return ret; 420 | } 421 | } 422 | 423 | } 424 | } 425 | -------------------------------------------------------------------------------- /DQAsset/UAsset.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Reflection; 6 | 7 | namespace DQAsset 8 | { 9 | public class PackageFile 10 | { 11 | public static uint PACKAGE_FILE_TAG = 0x9E2A83C1; 12 | 13 | public static readonly Dictionary KnownTypes = Assembly.GetExecutingAssembly() 14 | .GetTypes() 15 | //.Where(t => t.GetInterfaces().Contains(typeof(ISerializable))) 16 | .Where(t => t.BaseType == typeof(FTableRowBase) || (t.BaseType != null && t.BaseType.BaseType == typeof(FTableRowBase))) 17 | .ToDictionary(t => t.Name); 18 | 19 | public PackageFileSummary Header; 20 | public List Names; 21 | public List Imports; 22 | public List Exports; 23 | public List PreloadDependencies; 24 | 25 | public List ExportObjects; 26 | public bool SkipPropertyDataLoad = false; // skips deserializing DT data, eg. if we're just loading this as base to apply CSV to 27 | 28 | public bool UexpWriteInProgress = false; 29 | public bool FNameCleanupInProgress = false; 30 | public List> UExpFnames; 31 | 32 | public void DeserializeText(string text) 33 | { 34 | 35 | //var lines = text.Split('\n', StringSplitOptions.RemoveEmptyEntries); 36 | 37 | text = text.Replace("\r\n", "\n"); 38 | var firstLineEnd = text.IndexOf('\n'); 39 | text = text.Substring(firstLineEnd + 1); 40 | 41 | foreach (var exp in ExportObjects) 42 | { 43 | // Remove any TextHeader lines from the input text 44 | // TODO: check against SerializeTextHeader value? 45 | 46 | exp.DeserializeText(text, this); 47 | } 48 | } 49 | 50 | public string SerializeText() 51 | { 52 | var retVal = ""; 53 | foreach (var exp in ExportObjects) 54 | { 55 | retVal += exp.SerializeTextHeader(this); 56 | retVal += "\n"; 57 | retVal += exp.SerializeText(this, true); 58 | } 59 | return retVal; 60 | } 61 | 62 | public void Deserialize(BinaryReader reader) 63 | { 64 | Header = new PackageFileSummary(); 65 | Header.Deserialize(reader, this); 66 | 67 | reader.BaseStream.Position = Header.NameOffset; 68 | Names = new List(); 69 | for (int i = 0; i < Header.NameCount; i++) 70 | { 71 | var entry = new NameEntry(); 72 | entry.Deserialize(reader, this); 73 | Names.Add(entry); 74 | } 75 | 76 | reader.BaseStream.Position = Header.ImportOffset; 77 | Imports = new List(); 78 | for (int i = 0; i < Header.ImportCount; i++) 79 | { 80 | var imp = new ObjectImport(); 81 | imp.Deserialize(reader, this); 82 | Imports.Add(imp); 83 | } 84 | 85 | reader.BaseStream.Position = Header.ExportOffset; 86 | Exports = new List(); 87 | for (int i = 0; i < Header.ExportCount; i++) 88 | { 89 | var exp = new ObjectExport(); 90 | exp.Deserialize(reader, this); 91 | Exports.Add(exp); 92 | } 93 | 94 | // 8 bytes pointed to by DependsOffset: 00 00 00 00 00 00 00 00 95 | 96 | reader.BaseStream.Position = Header.PreloadDependencyOffset; 97 | PreloadDependencies = new List(); 98 | for (int i = 0; i < Header.PreloadDependencyCount; i++) 99 | { 100 | PreloadDependencies.Add(reader.ReadInt32()); 101 | } 102 | 103 | ExportObjects = new List(); 104 | foreach (var exp in Exports) 105 | { 106 | reader.BaseStream.Position = exp.SerialOffset; 107 | var export = new AbstractExportObject(); 108 | export.Deserialize(reader, this); 109 | ExportObjects.Add(export); 110 | } 111 | 112 | if (!SkipPropertyDataLoad) 113 | if (reader.BaseStream.Position != (reader.BaseStream.Length - 4)) 114 | throw new Exception("Failed to fully deserialize UAsset"); 115 | } 116 | 117 | bool isSecondPass = false; 118 | 119 | public void Serialize(BinaryWriter uexp, BinaryWriter uasset, bool doFnameCleanup) 120 | { 121 | // Set flag to make FName::Serialize add itself to a list 122 | // This way we can update all FNames later without needing to do another whole serialization pass 123 | FNameCleanupInProgress = doFnameCleanup; 124 | if (doFnameCleanup) 125 | { 126 | isSecondPass = false; 127 | UExpFnames = new List>(); 128 | } 129 | 130 | // Write out export data first 131 | if (uexp != null && !isSecondPass) 132 | { 133 | var uexpPosition = uexp.BaseStream.Position; 134 | 135 | if (doFnameCleanup) 136 | foreach (var entry in Names) 137 | entry.InUse = false; 138 | 139 | UexpWriteInProgress = true; 140 | for (int i = 0; i < Exports.Count; i++) 141 | { 142 | var expPosition = uexp.BaseStream.Position; 143 | 144 | var exportHeader = Exports[i]; 145 | var exportData = ExportObjects[i]; 146 | 147 | exportHeader.SerialOffset = uexp.BaseStream.Position - uexpPosition; 148 | exportData.Serialize(uexp, this); 149 | 150 | exportHeader.SerialSize = uexp.BaseStream.Position - expPosition; 151 | } 152 | 153 | uexp.Write(PACKAGE_FILE_TAG); 154 | } 155 | 156 | // Don't allow uasset header FNames to add to UExpFnames 157 | UexpWriteInProgress = false; 158 | 159 | // Header.Generations seems to contain NameCount/ExportCount that matches header 160 | // Check if any match the existing data, and choose that as the generation to update 161 | int updateGenerationIdx = -1; 162 | if (Header.Generations.Count > 0) 163 | { 164 | for (int i = 0; i < Header.Generations.Count; i++) 165 | { 166 | var gen = Header.Generations[i]; 167 | if (gen.NameCount == Header.NameCount && gen.ExportCount == Header.ExportCount) 168 | { 169 | updateGenerationIdx = i; 170 | break; 171 | } 172 | } 173 | } 174 | 175 | // Update header 176 | Header.NameCount = Names.Count; 177 | Header.ImportCount = Imports.Count; 178 | Header.ExportCount = Exports.Count; 179 | 180 | if (updateGenerationIdx >= 0) 181 | { 182 | var gen = Header.Generations[updateGenerationIdx]; 183 | gen.ExportCount = Header.ExportCount; 184 | gen.NameCount = Header.NameCount; 185 | } 186 | 187 | if(Header.IsVersionOrGreater(UE4Versions.VER_UE4_PRELOAD_DEPENDENCIES_IN_COOKED_EXPORTS)) 188 | Header.PreloadDependencyCount = PreloadDependencies.Count; 189 | 190 | // Write header (will get rewritten later with updated offsets etc) 191 | var headerPosition = uasset.BaseStream.Position; 192 | Header.Serialize(uasset, this); 193 | 194 | Header.NameOffset = (int)(uasset.BaseStream.Position - headerPosition); 195 | foreach (var name in Names) 196 | name.Serialize(uasset, this); 197 | 198 | Header.ImportOffset = (int)(uasset.BaseStream.Position - headerPosition); 199 | foreach (var imp in Imports) 200 | imp.Serialize(uasset, this); 201 | 202 | Header.ExportOffset = (int)(uasset.BaseStream.Position - headerPosition); 203 | foreach (var exp in Exports) 204 | exp.Serialize(uasset, this); 205 | 206 | // TODO: should we be handling the Depends stuff properly? 207 | Header.DependsOffset = (int)(uasset.BaseStream.Position - headerPosition); 208 | for (int i = 0; i < Exports.Count; i++) 209 | uasset.Write(0); 210 | 211 | Header.AssetRegistryDataOffset = (int)(uasset.BaseStream.Position - headerPosition); 212 | uasset.Write(0); 213 | 214 | if (Header.IsVersionOrGreater(UE4Versions.VER_UE4_PRELOAD_DEPENDENCIES_IN_COOKED_EXPORTS)) 215 | { 216 | Header.PreloadDependencyOffset = (int)(uasset.BaseStream.Position - headerPosition); 217 | foreach (var preloadDep in PreloadDependencies) 218 | uasset.Write(preloadDep); 219 | } 220 | 221 | Header.TotalHeaderSize = (int)(uasset.BaseStream.Position - headerPosition); 222 | 223 | // Write updated section offsets/sizes to header: 224 | uasset.BaseStream.Position = headerPosition; 225 | Header.Serialize(uasset, this); 226 | 227 | // Update export objects with proper SerialOffset 228 | // TODO: figure out why this wasn't needed previously! 229 | uasset.BaseStream.Position = headerPosition + Header.ExportOffset; 230 | foreach (var exp in Exports) 231 | { 232 | exp.SerialOffset += Header.TotalHeaderSize; 233 | exp.Serialize(uasset, this); 234 | } 235 | 236 | if (doFnameCleanup) 237 | { 238 | var notInUseFNameIndexes = new List(); 239 | 240 | // Process the indexes in reverse order so indexes remain valid during the RemoveAt foreach 241 | for (int i = Names.Count - 1; i >= 0; i--) 242 | if (!Names[i].InUse) 243 | if (!Names[i].Name.StartsWith("/Game/") && Names[i].Name != "UserDefinedStruct") // some reason these isn't referenced by any FName in the file, make sure to exclude it... 244 | notInUseFNameIndexes.Add(i); 245 | 246 | // Some FNames are part of a larger FName, smaller one isn't referenced but I guess is important since it exists... 247 | // Check if any to-be-removed FNames are part of something larger 248 | for (int i = notInUseFNameIndexes.Count - 1; i >= 0; i--) 249 | { 250 | var name = Names[notInUseFNameIndexes[i]].Name; 251 | bool removeFromList = false; 252 | for (int y = 0; y < Names.Count; y++) 253 | { 254 | if (!notInUseFNameIndexes.Contains(y)) 255 | if (Names[y].Name.StartsWith(name)) 256 | { 257 | removeFromList = true; 258 | break; 259 | } 260 | } 261 | if (removeFromList) 262 | notInUseFNameIndexes.RemoveAt(i); 263 | } 264 | 265 | FNameCleanupInProgress = false; 266 | 267 | if (notInUseFNameIndexes.Count <= 0) 268 | return; // no cleanup needed 269 | 270 | // debug: 271 | /*var notInUse = new List(); 272 | foreach (var idx in notInUseFNameIndexes) 273 | notInUse.Add(Names[idx].Name);*/ 274 | 275 | // Remove not in use entries 276 | foreach (var idx in notInUseFNameIndexes) 277 | Names.RemoveAt(idx); 278 | 279 | // Update FName indexes inside uexp 280 | foreach (var name in UExpFnames) 281 | { 282 | uexp.BaseStream.Position = name.Item2; 283 | name.Item1.Serialize(uexp, this); 284 | } 285 | 286 | // Remove uasset header size from Exports SerialOffset (second pass will add the updated header size) 287 | foreach (var exp in Exports) 288 | { 289 | exp.SerialOffset -= Header.TotalHeaderSize; 290 | } 291 | 292 | // Need to do second header serialization pass so that updated Names array/indexes are written 293 | uasset.BaseStream.Position = headerPosition; 294 | uasset.BaseStream.SetLength(0); 295 | isSecondPass = true; 296 | Serialize(null, uasset, false); 297 | isSecondPass = false; 298 | } 299 | } 300 | } 301 | 302 | public class NameEntry : ISerializable 303 | { 304 | public string Name; 305 | public ushort NonCasePreservingHash; 306 | public ushort CasePreservingHash; 307 | 308 | public bool InUse; // set/reset during serialization so we can tell any unused entries 309 | 310 | public void Deserialize(BinaryReader reader, PackageFile package) 311 | { 312 | Name = reader.ReadFString(); 313 | if (!package.Header.IsVersionOrGreater(UE4Versions.VER_UE4_NAME_HASHES_SERIALIZED)) 314 | return; 315 | 316 | NonCasePreservingHash = reader.ReadUInt16(); 317 | CasePreservingHash = reader.ReadUInt16(); 318 | } 319 | 320 | public void Serialize(BinaryWriter writer, PackageFile package) 321 | { 322 | writer.WriteFString(Name); 323 | 324 | if (!package.Header.IsVersionOrGreater(UE4Versions.VER_UE4_NAME_HASHES_SERIALIZED)) 325 | return; 326 | 327 | if (Shared.HasNonASCIIChars(Name)) 328 | { 329 | NonCasePreservingHash = (ushort)(Shared.UE4Strihash_DEPRECATED_Wide(Name) & 0xFFFF); 330 | CasePreservingHash = (ushort)(Shared.UE4StrCrc32_Wide(Name) & 0xFFFF); 331 | } 332 | else 333 | { 334 | NonCasePreservingHash = (ushort)(Shared.UE4Strihash_DEPRECATED(Name) & 0xFFFF); 335 | CasePreservingHash = (ushort)(Shared.UE4StrCrc32(Name) & 0xFFFF); 336 | } 337 | 338 | writer.Write(NonCasePreservingHash); 339 | writer.Write(CasePreservingHash); 340 | } 341 | 342 | public override string ToString() => Name; 343 | } 344 | 345 | public class FName : ISerializable 346 | { 347 | public int Index = 0; 348 | public int InstanceNum = 0; 349 | 350 | public string Value; 351 | 352 | public FName() { } 353 | 354 | public FName(string value) 355 | { 356 | Value = value; 357 | TryGetInstanceNum(); 358 | } 359 | 360 | void TryGetInstanceNum() 361 | { 362 | // Convert FName that contains InstanceNum (eg. W_WPN++2048++) into InstanceNum + Value 363 | if (!Value.EndsWith("++")) 364 | return; 365 | 366 | var numEndIdx = Value.LastIndexOf("++"); 367 | var numStartIdx = Value.LastIndexOf("++", numEndIdx); 368 | if (numStartIdx >= 0 && numEndIdx >= numStartIdx) 369 | { 370 | var instanceNum = Value.Substring(numStartIdx + 2, numEndIdx - (numStartIdx + 2)); 371 | InstanceNum = int.Parse(instanceNum); 372 | Value = Value.Substring(0, numStartIdx); 373 | } 374 | } 375 | 376 | public void Deserialize(BinaryReader reader, PackageFile package) 377 | { 378 | Index = reader.ReadInt32(); 379 | InstanceNum = reader.ReadInt32(); 380 | 381 | if (package.Names.Count > Index && Index >= 0) 382 | Value = package.Names[Index].Name; 383 | else 384 | throw new Exception("Deserialized FName not found in UAsset header?"); 385 | } 386 | 387 | public void Serialize(BinaryWriter writer, PackageFile package) 388 | { 389 | TryGetInstanceNum(); 390 | 391 | Index = package.Names.FindIndex(s => s.Name == Value); 392 | if (Index == -1) 393 | { 394 | var ent = new NameEntry(); 395 | ent.Name = Value; 396 | package.Names.Add(ent); 397 | Index = package.Names.FindIndex(s => s.Name == Value); 398 | if (Index == -1) 399 | throw new Exception("Failed to add new FName for some reason!"); 400 | } 401 | 402 | if (package.FNameCleanupInProgress) 403 | { 404 | package.Names[Index].InUse = true; 405 | if (package.UexpWriteInProgress) 406 | package.UExpFnames.Add(new Tuple(this, writer.BaseStream.Position)); 407 | } 408 | 409 | writer.Write(Index); 410 | writer.Write(InstanceNum); 411 | } 412 | 413 | public override string ToString() 414 | { 415 | if (InstanceNum == 0) 416 | return Value; 417 | return $"{Value}++{InstanceNum}++"; 418 | } 419 | 420 | public override bool Equals(object? other) 421 | { 422 | if (other == null && this == null) 423 | return true; 424 | if (other.GetType() != GetType()) 425 | return false; 426 | var otherName = other as FName; 427 | 428 | otherName.TryGetInstanceNum(); 429 | TryGetInstanceNum(); 430 | 431 | return otherName.Value == this.Value && otherName.InstanceNum == this.InstanceNum; 432 | } 433 | } 434 | 435 | public class PackageIndex : ISerializable 436 | { 437 | public int Index; 438 | 439 | public ObjectImport ImportObject; 440 | public ObjectExport ExportObject; 441 | 442 | public bool IsImport() => Index < 0; 443 | public bool IsExport() => Index > 0; 444 | public bool IsNull() => Index == 0; 445 | 446 | public void Deserialize(BinaryReader reader, PackageFile package) 447 | { 448 | Index = reader.ReadInt32(); 449 | 450 | int importIndex = (-Index) - 1; 451 | int exportIndex = Index - 1; 452 | 453 | if (IsImport() && package.Imports.Count > importIndex) 454 | ImportObject = package.Imports[importIndex]; 455 | if (IsExport() && package.Exports.Count > exportIndex) 456 | ExportObject = package.Exports[exportIndex]; 457 | } 458 | 459 | public void Serialize(BinaryWriter writer, PackageFile package) 460 | { 461 | // TODO: update Index with index of this package import/export 462 | writer.Write(Index); 463 | } 464 | } 465 | 466 | public class ObjectImport : ISerializable 467 | { 468 | public FName ClassPackage; 469 | public FName ClassName; 470 | public PackageIndex PackageRef; 471 | public FName ObjectName; 472 | 473 | public void Deserialize(BinaryReader reader, PackageFile package) 474 | { 475 | ClassPackage = new FName(); 476 | ClassPackage.Deserialize(reader, package); 477 | ClassName = new FName(); 478 | ClassName.Deserialize(reader, package); 479 | PackageRef = new PackageIndex(); 480 | PackageRef.Deserialize(reader, package); 481 | ObjectName = new FName(); 482 | ObjectName.Deserialize(reader, package); 483 | } 484 | 485 | public void Serialize(BinaryWriter writer, PackageFile package) 486 | { 487 | ClassPackage.Serialize(writer, package); 488 | ClassName.Serialize(writer, package); 489 | PackageRef.Serialize(writer, package); 490 | ObjectName.Serialize(writer, package); 491 | } 492 | } 493 | 494 | public class ObjectExport : ISerializable 495 | { 496 | public PackageIndex ClassIndex; 497 | public PackageIndex SuperIndex; 498 | public PackageIndex TemplateIndex; 499 | public PackageIndex OuterIndex; 500 | public FName ObjectName; 501 | public uint ObjectFlags; 502 | public long SerialSize; 503 | public long SerialOffset; 504 | public int bForcedExport; 505 | public int bNotForClient; 506 | public int bNotForServer; 507 | public byte[] PackageGuid; 508 | public uint PackageFlags; 509 | public int bNotAlwaysLoadedForEditorGame; 510 | public int bIsAsset; 511 | public int FirstExportDependency; 512 | public int SerializationBeforeSerializationDependencies; 513 | public int CreateBeforeSerializationDependencies; 514 | public int SerializationBeforeCreateDependencies; 515 | public int CreateBeforeCreateDependencies; 516 | 517 | public void Deserialize(BinaryReader reader, PackageFile package) 518 | { 519 | ClassIndex = new PackageIndex(); 520 | ClassIndex.Deserialize(reader, package); 521 | SuperIndex = new PackageIndex(); 522 | SuperIndex.Deserialize(reader, package); 523 | 524 | if (package.Header.IsVersionOrGreater(UE4Versions.VER_UE4_TemplateIndex_IN_COOKED_EXPORTS)) 525 | { 526 | TemplateIndex = new PackageIndex(); 527 | TemplateIndex.Deserialize(reader, package); 528 | } 529 | 530 | OuterIndex = new PackageIndex(); 531 | OuterIndex.Deserialize(reader, package); 532 | 533 | ObjectName = new FName(); 534 | ObjectName.Deserialize(reader, package); 535 | 536 | ObjectFlags = reader.ReadUInt32(); 537 | 538 | SerialSize = package.Header.IsVersionOrGreater(UE4Versions.VER_UE4_64BIT_EXPORTMAP_SERIALSIZES) ? reader.ReadInt64() : reader.ReadInt32(); 539 | SerialOffset = package.Header.IsVersionOrGreater(UE4Versions.VER_UE4_64BIT_EXPORTMAP_SERIALSIZES) ? reader.ReadInt64() : reader.ReadInt32(); 540 | 541 | bForcedExport = reader.ReadInt32(); 542 | bNotForClient = reader.ReadInt32(); 543 | bNotForServer = reader.ReadInt32(); 544 | 545 | PackageGuid = reader.ReadBytes(0x10); 546 | PackageFlags = reader.ReadUInt32(); 547 | 548 | if (package.Header.IsVersionOrGreater(UE4Versions.VER_UE4_LOAD_FOR_EDITOR_GAME)) 549 | bNotAlwaysLoadedForEditorGame = reader.ReadInt32(); 550 | 551 | if (package.Header.IsVersionOrGreater(UE4Versions.VER_UE4_COOKED_ASSETS_IN_EDITOR_SUPPORT)) 552 | bIsAsset = reader.ReadInt32(); 553 | 554 | if (package.Header.IsVersionOrGreater(UE4Versions.VER_UE4_PRELOAD_DEPENDENCIES_IN_COOKED_EXPORTS)) 555 | { 556 | FirstExportDependency = reader.ReadInt32(); 557 | SerializationBeforeSerializationDependencies = reader.ReadInt32(); 558 | CreateBeforeSerializationDependencies = reader.ReadInt32(); 559 | SerializationBeforeCreateDependencies = reader.ReadInt32(); 560 | CreateBeforeCreateDependencies = reader.ReadInt32(); 561 | } 562 | } 563 | 564 | public void Serialize(BinaryWriter writer, PackageFile package) 565 | { 566 | ClassIndex.Serialize(writer, package); 567 | SuperIndex.Serialize(writer, package); 568 | if (package.Header.IsVersionOrGreater(UE4Versions.VER_UE4_TemplateIndex_IN_COOKED_EXPORTS)) 569 | TemplateIndex.Serialize(writer, package); 570 | OuterIndex.Serialize(writer, package); 571 | 572 | ObjectName.Serialize(writer, package); 573 | writer.Write(ObjectFlags); 574 | 575 | if (package.Header.IsVersionOrGreater(UE4Versions.VER_UE4_64BIT_EXPORTMAP_SERIALSIZES)) 576 | writer.Write(SerialSize); 577 | else 578 | writer.Write((int)SerialSize); 579 | 580 | if (package.Header.IsVersionOrGreater(UE4Versions.VER_UE4_64BIT_EXPORTMAP_SERIALSIZES)) 581 | writer.Write(SerialOffset); 582 | else 583 | writer.Write((int)SerialOffset); 584 | 585 | writer.Write(bForcedExport); 586 | writer.Write(bNotForClient); 587 | writer.Write(bNotForServer); 588 | writer.Write(PackageGuid); 589 | writer.Write(PackageFlags); 590 | 591 | if (package.Header.IsVersionOrGreater(UE4Versions.VER_UE4_LOAD_FOR_EDITOR_GAME)) 592 | writer.Write(bNotAlwaysLoadedForEditorGame); 593 | 594 | if (package.Header.IsVersionOrGreater(UE4Versions.VER_UE4_COOKED_ASSETS_IN_EDITOR_SUPPORT)) 595 | writer.Write(bIsAsset); 596 | 597 | if (package.Header.IsVersionOrGreater(UE4Versions.VER_UE4_PRELOAD_DEPENDENCIES_IN_COOKED_EXPORTS)) 598 | { 599 | writer.Write(FirstExportDependency); 600 | writer.Write(SerializationBeforeSerializationDependencies); 601 | writer.Write(CreateBeforeSerializationDependencies); 602 | writer.Write(SerializationBeforeCreateDependencies); 603 | writer.Write(CreateBeforeCreateDependencies); 604 | } 605 | } 606 | } 607 | 608 | public class PropertyTag : ISerializable 609 | { 610 | public FName Name; 611 | public FName Type; 612 | public int Size; 613 | public int Index; 614 | 615 | public void Deserialize(BinaryReader reader, PackageFile package) 616 | { 617 | Name = new FName(); 618 | Name.Deserialize(reader, package); 619 | Type = new FName(); 620 | Type.Deserialize(reader, package); 621 | Size = reader.ReadInt32(); 622 | Index = reader.ReadInt32(); 623 | } 624 | 625 | public void Serialize(BinaryWriter writer, PackageFile package) 626 | { 627 | Name.Serialize(writer, package); 628 | Type.Serialize(writer, package); 629 | writer.Write(Size); 630 | writer.Write(Index); 631 | } 632 | } 633 | 634 | public class PropertyGuid : ISerializable 635 | { 636 | public byte HasGuid; 637 | public byte[] Guid; 638 | 639 | public void Deserialize(BinaryReader reader, PackageFile package) 640 | { 641 | HasGuid = reader.ReadByte(); 642 | if (HasGuid != 0) 643 | Guid = reader.ReadBytes(0x10); 644 | } 645 | 646 | public void Serialize(BinaryWriter writer, PackageFile package) 647 | { 648 | HasGuid = (byte)(Guid != null ? 1 : 0); 649 | writer.Write(HasGuid); 650 | if (Guid != null) 651 | writer.Write(Guid); 652 | } 653 | } 654 | 655 | public class ObjectProperty : FTableRowBase 656 | { 657 | public PropertyGuid Guid; 658 | public PackageIndex Value; 659 | public override void Deserialize(BinaryReader reader, PackageFile package) 660 | { 661 | Guid = new PropertyGuid(); 662 | Guid.Deserialize(reader, package); 663 | Value = new PackageIndex(); 664 | Value.Deserialize(reader, package); 665 | } 666 | 667 | public override void Serialize(BinaryWriter writer, PackageFile package) 668 | { 669 | Guid.Serialize(writer, package); 670 | Value.Serialize(writer, package); 671 | } 672 | } 673 | 674 | public class AbstractExportObject : ISerializableText 675 | { 676 | public List PropertyTags; 677 | public List Properties; 678 | public uint Reserved; 679 | public Dictionary PropertiesData; 680 | 681 | public string SerializeTextHeader(PackageFile package) 682 | { 683 | if (PropertiesData == null || PropertiesData.Count <= 0) 684 | return null; 685 | 686 | return "RowName," + PropertiesData.First().Value.SerializeTextHeader(package); 687 | } 688 | public string SerializeText(PackageFile package, bool isMainElement) 689 | { 690 | // TODO: add a CSV header line from the type in PropertiesData? 691 | 692 | string[] lines = new string[PropertiesData.Count]; 693 | int i = 0; 694 | foreach (var kvp in PropertiesData) 695 | { 696 | lines[i] = $"{kvp.Key},{kvp.Value.SerializeText(package, isMainElement)}"; 697 | i++; 698 | } 699 | 700 | return String.Join('\n', lines); 701 | } 702 | 703 | public void DeserializeText(string text, PackageFile package) 704 | { 705 | Type? rowType = null; 706 | foreach (var prop in Properties) 707 | { 708 | if (prop.GetType() != typeof(ObjectProperty)) 709 | continue; 710 | 711 | var objectProperty = prop as ObjectProperty; 712 | if (!PackageFile.KnownTypes.TryGetValue(objectProperty.Value.ImportObject.ObjectName.Value, out rowType)) 713 | if (!PackageFile.KnownTypes.TryGetValue("F" + objectProperty.Value.ImportObject.ObjectName.Value, out rowType)) 714 | throw new Exception($"UAsset uses unknown struct type {objectProperty.Value.ImportObject.ObjectName.Value}!"); 715 | 716 | break; // probably shouldn't be doing this, hopefully all DQXI tables only contain a single ObjectProperty... 717 | } 718 | 719 | if (rowType == null) 720 | throw new Exception("Failed to find rowType type!"); 721 | 722 | text = text.Replace("\r\n", "\n"); 723 | 724 | var lines = text.Split('\n', StringSplitOptions.RemoveEmptyEntries); 725 | foreach (var line in lines) 726 | { 727 | // Read + remove RowName from the line 728 | var rowNameEndIdx = line.IndexOf(','); 729 | var key = line.Substring(0, rowNameEndIdx); 730 | var value = line.Substring(rowNameEndIdx + 1); 731 | 732 | var keyFName = new FName(key); 733 | ISerializableText rowValue; 734 | if (PropertiesData.ContainsKey(keyFName)) 735 | rowValue = PropertiesData[keyFName]; 736 | else 737 | rowValue = Activator.CreateInstance(rowType) as ISerializableText; 738 | 739 | rowValue.DeserializeText(value, package); 740 | 741 | PropertiesData[keyFName] = rowValue; 742 | } 743 | // TODO load values from text into class 744 | } 745 | 746 | public void Serialize(BinaryWriter writer, PackageFile package) 747 | { 748 | for (int i = 0; i < PropertyTags.Count; i++) 749 | { 750 | var tag = PropertyTags[i]; 751 | ISerializable property = null; 752 | if (Properties.Count >= i) 753 | property = Properties[i]; 754 | 755 | tag.Serialize(writer, package); 756 | if (property != null) 757 | property.Serialize(writer, package); 758 | } 759 | var endName = new FName(); 760 | endName.Value = "None"; 761 | endName.Serialize(writer, package); 762 | 763 | writer.Write(Reserved); 764 | 765 | foreach (var prop in Properties) 766 | { 767 | if (prop.GetType() != typeof(ObjectProperty)) 768 | continue; 769 | 770 | writer.Write(PropertiesData.Count); 771 | 772 | foreach (var kvp in PropertiesData) 773 | { 774 | kvp.Key.Serialize(writer, package); 775 | 776 | bool writeStructSize = true; 777 | var settings = kvp.Value.GetType().GetCustomAttribute(); 778 | if (settings != null && settings.NoStructSize) 779 | writeStructSize = false; 780 | 781 | long structSizePosition = 0; 782 | 783 | if (writeStructSize) 784 | { 785 | structSizePosition = writer.BaseStream.Position; 786 | writer.Write((int)0);// dummy val, we'll fix it later 787 | } 788 | 789 | var structStartPosition = writer.BaseStream.Position; 790 | kvp.Value.Serialize(writer, package); 791 | var structEndPosition = writer.BaseStream.Position; 792 | 793 | if (writeStructSize) 794 | { 795 | var structSize = structEndPosition - structStartPosition; 796 | writer.BaseStream.Position = structSizePosition; 797 | writer.Write((uint)structSize); 798 | writer.BaseStream.Position = structEndPosition; 799 | } 800 | } 801 | } 802 | } 803 | 804 | public void Deserialize(BinaryReader reader, PackageFile package) 805 | { 806 | PropertyTags = new List(); 807 | Properties = new List(); 808 | while (true) 809 | { 810 | var pos = reader.BaseStream.Position; 811 | var name = new FName(); 812 | name.Deserialize(reader, package); 813 | if (name.Value == "None") 814 | break; 815 | 816 | reader.BaseStream.Position = pos; 817 | 818 | var tag = new PropertyTag(); 819 | tag.Deserialize(reader, package); 820 | PropertyTags.Add(tag); 821 | 822 | Type? propertyType; 823 | if (!PackageFile.KnownTypes.TryGetValue(tag.Type.Value, out propertyType)) 824 | if (!PackageFile.KnownTypes.TryGetValue("F" + tag.Type.Value, out propertyType)) 825 | throw new Exception($"UAsset uses unknown struct type {tag.Type.Value}!"); 826 | 827 | var prop = Activator.CreateInstance(propertyType) as ISerializable; 828 | prop.Deserialize(reader, package); 829 | Properties.Add(prop); 830 | } 831 | Reserved = reader.ReadUInt32(); 832 | 833 | // ObjectProperty data comes after the properties/propertytags section 834 | // I don't know if any other properties are handled this way, probably not, doing this is likely wrong for 99% of other UE4 properties 835 | 836 | PropertiesData = new Dictionary(); 837 | foreach (var prop in Properties) 838 | { 839 | if (prop.GetType() != typeof(ObjectProperty)) 840 | continue; 841 | 842 | var objectProperty = prop as ObjectProperty; 843 | 844 | Type? propertyType; 845 | if (!PackageFile.KnownTypes.TryGetValue(objectProperty.Value.ImportObject.ObjectName.Value, out propertyType)) 846 | if (!PackageFile.KnownTypes.TryGetValue("F" + objectProperty.Value.ImportObject.ObjectName.Value, out propertyType)) 847 | throw new Exception($"UAsset uses unknown struct type {objectProperty.Value.ImportObject.ObjectName.Value}!"); 848 | 849 | if (propertyType == typeof(FJackDataTableNativizationAsset) || propertyType == typeof(FJackDataTableBlueprintClass)) 850 | Program.DoFNameCleanup = false; // contains a bunch of unreferenced FNames, can't cleanup properly :( 851 | 852 | if (package.SkipPropertyDataLoad) 853 | continue; 854 | 855 | int count = reader.ReadInt32(); 856 | for (int i = 0; i < count; i++) 857 | { 858 | var rowName = new FName(); 859 | rowName.Deserialize(reader, package); 860 | 861 | var position = reader.BaseStream.Position; 862 | if (propertyType.BaseType.GetInterfaces().Contains(typeof(ISerializableText))) 863 | { 864 | int structSize = 0; 865 | var settings = propertyType.GetCustomAttribute(); 866 | if (settings == null || !settings.NoStructSize) 867 | { 868 | structSize = reader.ReadInt32(); 869 | position = reader.BaseStream.Position; 870 | } 871 | 872 | var propData = Activator.CreateInstance(propertyType) as ISerializableText; 873 | propData.Deserialize(reader, package); 874 | 875 | var length = reader.BaseStream.Position - position; 876 | if (structSize != 0 && length != structSize) 877 | { 878 | throw new Exception($"DataTable read incorrect number of bytes (read {length}, expected {structSize}!)"); 879 | reader.BaseStream.Position = position + structSize; 880 | } 881 | 882 | PropertiesData.Add(rowName, propData); 883 | } 884 | } 885 | } 886 | } 887 | } 888 | 889 | public class FEngineVersion : ISerializable 890 | { 891 | public ushort Major; 892 | public ushort Minor; 893 | public ushort Patch; 894 | public uint Changelist; 895 | public string Branch; 896 | 897 | public void Deserialize(BinaryReader reader, PackageFile package) 898 | { 899 | Major = reader.ReadUInt16(); 900 | Minor = reader.ReadUInt16(); 901 | Patch = reader.ReadUInt16(); 902 | Changelist = reader.ReadUInt32(); 903 | Branch = reader.ReadFString(); 904 | } 905 | 906 | public void Serialize(BinaryWriter writer, PackageFile package) 907 | { 908 | writer.Write(Major); 909 | writer.Write(Minor); 910 | writer.Write(Patch); 911 | writer.Write(Changelist); 912 | writer.WriteFString(Branch); 913 | } 914 | } 915 | } 916 | --------------------------------------------------------------------------------