diff --git a/CodeWalker.Benchmarks/App.config b/CodeWalker.Benchmarks/App.config new file mode 100644 index 0000000..9146286 --- /dev/null +++ b/CodeWalker.Benchmarks/App.config @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/CodeWalker.Benchmarks/Benchmarks.cs b/CodeWalker.Benchmarks/Benchmarks.cs new file mode 100644 index 0000000..d88c9f1 --- /dev/null +++ b/CodeWalker.Benchmarks/Benchmarks.cs @@ -0,0 +1,200 @@ +using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Configs; +using BenchmarkDotNet.Environments; +using BenchmarkDotNet.Jobs; +using CodeWalker.GameFiles; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CodeWalker.Benchmarks +{ + [MemoryDiagnoser] + [Config(typeof(JitsConfig))] + public class Benchmarks + { + private class JitsConfig : ManualConfig + { + public JitsConfig() + { + AddJob(Job.Default.WithJit(Jit.RyuJit).WithPlatform(Platform.X64)); + } + } + public static string markup = @" + vehshare + + + + brabusgt600brabusgt600 + brabusgt600 + GT 600 + BRABUS + null + null + null + null + + null + ta176m177 + LAYOUT_LOW + BULLET_COVER_OFFSET_INFO + EXPLOSION_INFO_DEFAULT + + DEFAULT_FOLLOW_VEHICLE_CAMERA + MID_BOX_VEHICLE_AIM_CAMERA + VEHICLE_BONNET_CAMERA_NEAR_EXTRA_HIGH + DEFAULT_POV_CAMERA + + + + + + + + + + + + + + VFXVEHICLEINFO_CAR_BULLET + + + + + + + + + + + + + + + + + + + + + + 60.000000 + 80.000000 + 100.000000 + 120.000000 + 500.000000 + 500.000000 + + + + + + + + + + + SWANKNESS_3 + + FLAG_SPORTS FLAG_RICH_CAR FLAG_NO_BROKEN_DOWN_SCENARIO FLAG_RECESSED_TAILLIGHT_CORONAS FLAG_NO_HEAVY_BRAKE_ANIMATION + VEHICLE_TYPE_CAR + VPT_FRONT_AND_BACK_PLATES + VDT_BANSHEE + VC_SPORT + VWT_HIEND + + docktrailer + trailers + trailers2 + trailers3 + trailers4 + tanker + trailerlogs + tr2 + trflat + + + + + S_M_Y_Cop_01 + + + + + + + + + + + + WHEEL_FRONT_RIGHT_CAMERA + WHEEL_FRONT_LEFT_CAMERA + WHEEL_REAR_RIGHT_CAMERA + WHEEL_REAR_LEFT_CAMERA + + + + + + + + + + + LOW_BULLET_FRONT_LEFT + LOW_BULLET_FRONT_RIGHT + + + + + + + + + + + + + + + + + vehicles_banshee_interior + brabusgt600 + + + + + + +"; + + private byte[] data; + private RpfFileEntry fileEntry; + + [GlobalSetup] + public void Setup() + { + data = Encoding.UTF8.GetBytes(markup); + fileEntry = RpfFile.CreateFileEntry("kaas.meta", "saak.meta", ref data); + } + + [Benchmark(Baseline = true)] + public void RunLoad() + { + var vehiclesFileExpected = new VehiclesFile(); + vehiclesFileExpected.LoadOld(data, fileEntry); + } + + [Benchmark] + public void RunLoadNew() + { + var vehiclesFile = new VehiclesFile(); + vehiclesFile.Load(data, fileEntry); + } + } +} diff --git a/CodeWalker.Benchmarks/CodeWalker.Benchmarks.csproj b/CodeWalker.Benchmarks/CodeWalker.Benchmarks.csproj new file mode 100644 index 0000000..4bf9dff --- /dev/null +++ b/CodeWalker.Benchmarks/CodeWalker.Benchmarks.csproj @@ -0,0 +1,201 @@ + + + + + + + Debug + AnyCPU + {EDDA8A8E-5333-4E28-8221-A31E3B70EB7A} + Exe + CodeWalker.Benchmarks + CodeWalker.Benchmarks + v4.8 + 512 + true + true + + + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\packages\BenchmarkDotNet.0.13.10\lib\netstandard2.0\BenchmarkDotNet.dll + + + ..\packages\BenchmarkDotNet.Annotations.0.13.10\lib\netstandard2.0\BenchmarkDotNet.Annotations.dll + + + ..\packages\CommandLineParser.2.9.1\lib\net461\CommandLine.dll + + + ..\packages\Microsoft.Diagnostics.Tracing.TraceEvent.3.0.2\lib\net462\Dia2Lib.dll + True + + + ..\packages\Gee.External.Capstone.2.3.0\lib\netstandard2.0\Gee.External.Capstone.dll + + + ..\packages\Iced.1.17.0\lib\net45\Iced.dll + + + ..\packages\Microsoft.Bcl.AsyncInterfaces.1.1.0\lib\net461\Microsoft.Bcl.AsyncInterfaces.dll + + + ..\packages\Microsoft.CodeAnalysis.Common.4.1.0\lib\netstandard2.0\Microsoft.CodeAnalysis.dll + + + ..\packages\Microsoft.CodeAnalysis.CSharp.4.1.0\lib\netstandard2.0\Microsoft.CodeAnalysis.CSharp.dll + + + ..\packages\Microsoft.Diagnostics.Tracing.TraceEvent.3.0.2\lib\net462\Microsoft.Diagnostics.FastSerialization.dll + + + ..\packages\Microsoft.Diagnostics.NETCore.Client.0.2.251802\lib\netstandard2.0\Microsoft.Diagnostics.NETCore.Client.dll + + + ..\packages\Microsoft.Diagnostics.Runtime.2.2.332302\lib\netstandard2.0\Microsoft.Diagnostics.Runtime.dll + + + ..\packages\Microsoft.Diagnostics.Tracing.TraceEvent.3.0.2\lib\net462\Microsoft.Diagnostics.Tracing.TraceEvent.dll + + + ..\packages\Microsoft.DotNet.PlatformAbstractions.3.1.6\lib\net45\Microsoft.DotNet.PlatformAbstractions.dll + + + ..\packages\Microsoft.Extensions.Configuration.2.1.1\lib\netstandard2.0\Microsoft.Extensions.Configuration.dll + + + ..\packages\Microsoft.Extensions.Configuration.Abstractions.2.1.1\lib\netstandard2.0\Microsoft.Extensions.Configuration.Abstractions.dll + + + ..\packages\Microsoft.Extensions.Configuration.Binder.2.1.1\lib\netstandard2.0\Microsoft.Extensions.Configuration.Binder.dll + + + ..\packages\Microsoft.Extensions.DependencyInjection.Abstractions.2.1.1\lib\netstandard2.0\Microsoft.Extensions.DependencyInjection.Abstractions.dll + + + ..\packages\Microsoft.Extensions.Logging.2.1.1\lib\netstandard2.0\Microsoft.Extensions.Logging.dll + + + ..\packages\Microsoft.Extensions.Logging.Abstractions.2.1.1\lib\netstandard2.0\Microsoft.Extensions.Logging.Abstractions.dll + + + ..\packages\Microsoft.Extensions.Options.2.1.1\lib\netstandard2.0\Microsoft.Extensions.Options.dll + + + ..\packages\Microsoft.Extensions.Primitives.2.1.1\lib\netstandard2.0\Microsoft.Extensions.Primitives.dll + + + ..\packages\Microsoft.Win32.Registry.5.0.0\lib\net461\Microsoft.Win32.Registry.dll + + + ..\packages\Microsoft.Diagnostics.Tracing.TraceEvent.3.0.2\lib\net462\OSExtensions.dll + + + ..\packages\Perfolizer.0.2.1\lib\netstandard2.0\Perfolizer.dll + + + ..\packages\SharpDX.4.2.0\lib\net45\SharpDX.dll + + + ..\packages\SharpDX.Mathematics.4.2.0\lib\net45\SharpDX.Mathematics.dll + + + + ..\packages\System.Buffers.4.5.1\lib\net461\System.Buffers.dll + + + ..\packages\System.Collections.Immutable.5.0.0\lib\net461\System.Collections.Immutable.dll + + + + + ..\packages\System.Memory.4.5.4\lib\net461\System.Memory.dll + + + + ..\packages\System.Numerics.Vectors.4.5.0\lib\net46\System.Numerics.Vectors.dll + + + ..\packages\System.Reflection.Metadata.5.0.0\lib\net461\System.Reflection.Metadata.dll + + + ..\packages\System.Runtime.CompilerServices.Unsafe.5.0.0\lib\net45\System.Runtime.CompilerServices.Unsafe.dll + + + ..\packages\System.Runtime.InteropServices.RuntimeInformation.4.0.0\lib\net45\System.Runtime.InteropServices.RuntimeInformation.dll + True + True + + + ..\packages\System.Security.AccessControl.5.0.0\lib\net461\System.Security.AccessControl.dll + + + ..\packages\System.Security.Principal.Windows.5.0.0\lib\net461\System.Security.Principal.Windows.dll + + + ..\packages\System.Text.Encoding.CodePages.4.5.1\lib\net461\System.Text.Encoding.CodePages.dll + + + ..\packages\System.Threading.Tasks.Extensions.4.5.4\lib\net461\System.Threading.Tasks.Extensions.dll + + + + + + + + + ..\packages\Microsoft.Diagnostics.Tracing.TraceEvent.3.0.2\lib\net462\TraceReloggerLib.dll + True + + + + + + + + + + + + + + + + + + {FF6B9F41-14BE-474E-9ED0-549C3BEB7E00} + CodeWalker.Core + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + + + + \ No newline at end of file diff --git a/CodeWalker.Benchmarks/Program.cs b/CodeWalker.Benchmarks/Program.cs new file mode 100644 index 0000000..06047a2 --- /dev/null +++ b/CodeWalker.Benchmarks/Program.cs @@ -0,0 +1,17 @@ +using BenchmarkDotNet.Running; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CodeWalker.Benchmarks +{ + internal class Program + { + static void Main(string[] args) + { + BenchmarkRunner.Run(); + } + } +} diff --git a/CodeWalker.Benchmarks/Properties/AssemblyInfo.cs b/CodeWalker.Benchmarks/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..bea5153 --- /dev/null +++ b/CodeWalker.Benchmarks/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("CodeWalker.Benchmarks")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("CodeWalker.Benchmarks")] +[assembly: AssemblyCopyright("Copyright © 2023")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("edda8a8e-5333-4e28-8221-a31e3b70eb7a")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/CodeWalker.Benchmarks/packages.config b/CodeWalker.Benchmarks/packages.config new file mode 100644 index 0000000..027c21b --- /dev/null +++ b/CodeWalker.Benchmarks/packages.config @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/CodeWalker.Core/CodeWalker.Core.csproj b/CodeWalker.Core/CodeWalker.Core.csproj index b515aad..526603e 100644 --- a/CodeWalker.Core/CodeWalker.Core.csproj +++ b/CodeWalker.Core/CodeWalker.Core.csproj @@ -7,11 +7,16 @@ + + + + + diff --git a/CodeWalker.Core/GameFiles/FileTypes/AwcFile.cs b/CodeWalker.Core/GameFiles/FileTypes/AwcFile.cs index cc79536..4918930 100644 --- a/CodeWalker.Core/GameFiles/FileTypes/AwcFile.cs +++ b/CodeWalker.Core/GameFiles/FileTypes/AwcFile.cs @@ -70,10 +70,10 @@ namespace CodeWalker.GameFiles if (!string.IsNullOrEmpty(Name)) { - var nl = Name.ToLowerInvariant(); + var nl = Name; var fn = Path.GetFileNameWithoutExtension(nl); - JenkIndex.Ensure(fn + "_left"); - JenkIndex.Ensure(fn + "_right"); + JenkIndex.EnsureLower(fn + "_left"); + JenkIndex.EnsureLower(fn + "_right"); } if ((data == null) || (data.Length < 8)) diff --git a/CodeWalker.Core/GameFiles/FileTypes/DistantLightsFile.cs b/CodeWalker.Core/GameFiles/FileTypes/DistantLightsFile.cs index 0ddc67d..29b8444 100644 --- a/CodeWalker.Core/GameFiles/FileTypes/DistantLightsFile.cs +++ b/CodeWalker.Core/GameFiles/FileTypes/DistantLightsFile.cs @@ -40,7 +40,7 @@ namespace CodeWalker.GameFiles RpfFileEntry = entry; Name = entry.Name; - if (!entry.NameLower.EndsWith("_hd.dat")) + if (!entry.Name.EndsWith("_hd.dat", StringComparison.OrdinalIgnoreCase)) { HD = false; GridSize = 16; diff --git a/CodeWalker.Core/GameFiles/FileTypes/FxcFile.cs b/CodeWalker.Core/GameFiles/FileTypes/FxcFile.cs index cefbaf7..49c0517 100644 --- a/CodeWalker.Core/GameFiles/FileTypes/FxcFile.cs +++ b/CodeWalker.Core/GameFiles/FileTypes/FxcFile.cs @@ -1186,7 +1186,7 @@ namespace CodeWalker.GameFiles SlotGS = br.ReadUInt16(); //6, 5 SlotHS = br.ReadUInt16(); //6, 5 Name = FxcFile.ReadString(br); // _locals //"rage_matrices", "misc_globals", "lighting_globals", "more_stuff" - JenkIndex.Ensure(Name?.ToLowerInvariant()); //why not :P + JenkIndex.EnsureLower(Name); //why not :P } public void Write(BinaryWriter bw) { diff --git a/CodeWalker.Core/GameFiles/FileTypes/GtxdFile.cs b/CodeWalker.Core/GameFiles/FileTypes/GtxdFile.cs index 1d7932e..3c5c319 100644 --- a/CodeWalker.Core/GameFiles/FileTypes/GtxdFile.cs +++ b/CodeWalker.Core/GameFiles/FileTypes/GtxdFile.cs @@ -36,7 +36,7 @@ namespace CodeWalker.GameFiles FilePath = Name; - if (entry.NameLower.EndsWith(".ymt")) + if (entry.Name.EndsWith(".ymt", StringComparison.OrdinalIgnoreCase)) { MemoryStream ms = new MemoryStream(data); if (RbfFile.IsRBF(ms)) @@ -57,7 +57,7 @@ namespace CodeWalker.GameFiles //not an RBF file... } } - else if (entry.NameLower.EndsWith(".meta")) + else if (entry.Name.EndsWith(".meta", StringComparison.OrdinalIgnoreCase)) { //required for update\x64\dlcpacks\mpheist\dlc.rpf\common\data\gtxd.meta and update\x64\dlcpacks\mpluxe\dlc.rpf\common\data\gtxd.meta string xml = TextUtil.GetUTF8Text(data); diff --git a/CodeWalker.Core/GameFiles/FileTypes/Gxt2File.cs b/CodeWalker.Core/GameFiles/FileTypes/Gxt2File.cs index 5e4731c..58f656c 100644 --- a/CodeWalker.Core/GameFiles/FileTypes/Gxt2File.cs +++ b/CodeWalker.Core/GameFiles/FileTypes/Gxt2File.cs @@ -6,6 +6,7 @@ using System.Globalization; using System.IO; using System.Linq; using System.Text; +using System.Threading; using System.Threading.Tasks; namespace CodeWalker.GameFiles @@ -172,7 +173,7 @@ namespace CodeWalker.GameFiles public static class GlobalText { - public static ConcurrentDictionary Index = new (); + public static ConcurrentDictionary Index = new (4, 24000); private static object syncRoot = new object(); public static volatile bool FullIndexBuilt = false; diff --git a/CodeWalker.Core/GameFiles/FileTypes/MrfFile.cs b/CodeWalker.Core/GameFiles/FileTypes/MrfFile.cs index 0651bbf..cd98e7f 100644 --- a/CodeWalker.Core/GameFiles/FileTypes/MrfFile.cs +++ b/CodeWalker.Core/GameFiles/FileTypes/MrfFile.cs @@ -798,9 +798,13 @@ namespace CodeWalker.GameFiles { } - protected override void WriteToStream(byte[] value, bool ignoreEndianess = false) + protected override void WriteToStream(byte[] value, bool ignoreEndianess = false, int count = -1, int offset = 0) { - position += value.Length; + if (count == -1) + { + count = value.Length; + } + position += count; length = Math.Max(length, position); } } diff --git a/CodeWalker.Core/GameFiles/FileTypes/PedFile.cs b/CodeWalker.Core/GameFiles/FileTypes/PedFile.cs index 1c60cb3..82f0788 100644 --- a/CodeWalker.Core/GameFiles/FileTypes/PedFile.cs +++ b/CodeWalker.Core/GameFiles/FileTypes/PedFile.cs @@ -87,7 +87,6 @@ namespace CodeWalker.GameFiles } - private void NonMetaLoad(byte[] data) { //non meta not supported yet! but see what's in there... @@ -103,22 +102,6 @@ namespace CodeWalker.GameFiles Pso.Load(ms); LoadPso(); } - else - { - } - } - - - - - - - - - - - - } } diff --git a/CodeWalker.Core/GameFiles/FileTypes/PedsFile.cs b/CodeWalker.Core/GameFiles/FileTypes/PedsFile.cs index c11583b..4e778ee 100644 --- a/CodeWalker.Core/GameFiles/FileTypes/PedsFile.cs +++ b/CodeWalker.Core/GameFiles/FileTypes/PedsFile.cs @@ -9,6 +9,7 @@ using System.Xml; using TC = System.ComponentModel.TypeConverterAttribute; using EXP = System.ComponentModel.ExpandableObjectConverter; +using System.Xml.Linq; namespace CodeWalker.GameFiles @@ -34,7 +35,7 @@ namespace CodeWalker.GameFiles //can be PSO .ymt or XML .meta - MemoryStream ms = new MemoryStream(data); + using MemoryStream ms = new MemoryStream(data); if (PsoFile.IsPSO(ms)) { Pso = new PsoFile(); @@ -46,26 +47,28 @@ namespace CodeWalker.GameFiles Xml = TextUtil.GetUTF8Text(data); } - XmlDocument xdoc = new XmlDocument(); - if (!string.IsNullOrEmpty(Xml)) - { - try - { - xdoc.LoadXml(Xml); - } - catch (Exception ex) - { - var msg = ex.Message; - } - } - else - { } + using var textReader = new StringReader(Xml); + + //XmlDocument xdoc = new XmlDocument(); + //if (!string.IsNullOrEmpty(Xml)) + //{ + // try + // { + // xdoc.LoadXml(Xml); + // } + // catch (Exception ex) + // { + // var msg = ex.Message; + // } + //} + + using var xmlReader = XmlReader.Create(textReader); - if (xdoc.DocumentElement != null) - { - InitDataList = new CPedModelInfo__InitDataList(xdoc.DocumentElement); - } + //if (xdoc.DocumentElement != null) + //{ + InitDataList = new CPedModelInfo__InitDataList(xmlReader); + //} @@ -84,6 +87,87 @@ namespace CodeWalker.GameFiles public CTxdRelationship[] txdRelationships { get; set; } public CMultiTxdRelationship[] multiTxdRelationships { get; set; } + public CPedModelInfo__InitDataList(XmlReader reader) + { + while (reader.Read()) + { + reader.MoveToContent(); + + //var _ = xmlReader.Name switch + //{ + // "residentTxd" => ResidentTxd = Xml.GetChildInnerText(xmlReader, "residentTxd"), + // "InitDatas" => LoadInitDatas(xmlReader), + // "txdRelationships" => LoadTxdRelationships(xmlReader), + // _ => throw new Exception() + //}; + + switch (reader.Name) + { + case string Name when Name.Equals("residentTxd", StringComparison.OrdinalIgnoreCase): + residentTxd = Xml.GetChildInnerText(reader, "residentTxd"); + break; + case string Name when Name.Equals("InitDatas", StringComparison.OrdinalIgnoreCase): + if (reader.IsEmptyElement) + { + reader.ReadStartElement(); + break; + } + reader.ReadStartElement(); + var initDatasList = new List(); + + while (reader.IsItemElement()) + { + initDatasList.Add(new CPedModelInfo__InitData(reader)); + } + if (initDatasList.Count > 0) + { + InitDatas = initDatasList.ToArray(); + } + reader.ReadEndElement(); + break; + case string Name when Name.Equals("txdRelationships", StringComparison.OrdinalIgnoreCase): + if (reader.IsEmptyElement) + { + reader.ReadStartElement(); + break; + } + reader.ReadStartElement(); + var txdRelationshipsList = new List(); + + while (reader.IsItemElement()) + { + txdRelationshipsList.Add(new CTxdRelationship(reader)); + } + reader.ReadEndElement(); + if (txdRelationshipsList.Count > 0) + { + txdRelationships = txdRelationshipsList.ToArray(); + } + break; + case string Name when Name.Equals("multiTxdRelationships", StringComparison.OrdinalIgnoreCase): + if (reader.IsEmptyElement) + { + reader.ReadStartElement(); + break; + } + reader.ReadStartElement(); + var multiTxdList = new List(); + while (reader.IsItemElement()) + { + multiTxdList.Add(new CMultiTxdRelationship(reader)); + } + reader.ReadEndElement(); + if (multiTxdList.Count > 0) + { + multiTxdRelationships = multiTxdList.ToArray(); + } + break; + default: + break; + } + } + } + public CPedModelInfo__InitDataList(XmlNode node) { XmlNodeList items; @@ -126,7 +210,6 @@ namespace CodeWalker.GameFiles multiTxdRelationships[i] = new CMultiTxdRelationship(items[i]); } } - } } @@ -204,6 +287,249 @@ namespace CodeWalker.GameFiles public float DefaultRemoveRangeMultiplier { get; set; } public bool AllowCloseSpawning { get; set; } + public CPedModelInfo__InitData(XmlReader reader) + { + reader.ReadStartElement("Item"); + while (reader.Name != "Item" && reader.Read()) + { + if (reader.NodeType == XmlNodeType.EndElement && reader.Name == "Item") + { + reader.ReadEndElement(); + return; + } + + while (reader.MoveToContent() != XmlNodeType.Element && reader.Read()) + { + + } + + switch (reader.Name) + { + case "Name": + Name = Xml.GetChildInnerText(reader, "Name"); + break; + case "PropsName": + PropsName = Xml.GetChildInnerText(reader, "PropsName"); + break; + case "ClipDictionaryName": + ClipDictionaryName = Xml.GetChildInnerText(reader, "ClipDictionaryName"); + break; + case "BlendShapeFileName": + BlendShapeFileName = Xml.GetChildInnerText(reader, "BlendShapeFileName"); + break; + case "ExpressionSetName": + ExpressionSetName = Xml.GetChildInnerText(reader, "ExpressionSetName"); + break; + case "ExpressionDictionaryName": + ExpressionDictionaryName = Xml.GetChildInnerText(reader, "ExpressionDictionaryName"); + break; + case "ExpressionName": + ExpressionName = Xml.GetChildInnerText(reader, "ExpressionName"); + break; + case "Pedtype": + Pedtype = Xml.GetChildInnerText(reader, "Pedtype"); + break; + case "MovementClipSet": + MovementClipSet = Xml.GetChildInnerText(reader, "MovementClipSet"); + break; + case "MovementClipSets": + var clipSetsList = new List(); + foreach(var item in Xml.IterateItems(reader, "MovementClipSets")) + { + clipSetsList.Add(item.Value); + } + + MovementClipSets = clipSetsList.ToArray(); + break; + case "StrafeClipSet": + StrafeClipSet = Xml.GetChildInnerText(reader, "StrafeClipSet"); + break; + case "MovementToStrafeClipSet": + MovementToStrafeClipSet = Xml.GetChildInnerText(reader, "MovementToStrafeClipSet"); + break; + case "InjuredStrafeClipSet": + InjuredStrafeClipSet = Xml.GetChildInnerText(reader, "InjuredStrafeClipSet"); + break; + case "FullBodyDamageClipSet": + FullBodyDamageClipSet = Xml.GetChildInnerText(reader, "FullBodyDamageClipSet"); + break; + case "AdditiveDamageClipSet": + AdditiveDamageClipSet = Xml.GetChildInnerText(reader, "AdditiveDamageClipSet"); + break; + case "DefaultGestureClipSet": + DefaultGestureClipSet = Xml.GetChildInnerText(reader, "DefaultGestureClipSet"); + break; + case "FacialClipsetGroupName": + FacialClipsetGroupName = Xml.GetChildInnerText(reader, "FacialClipsetGroupName"); + break; + case "DefaultVisemeClipSet": + DefaultVisemeClipSet = Xml.GetChildInnerText(reader, "DefaultVisemeClipSet"); + break; + case "SidestepClipSet": + SidestepClipSet = Xml.GetChildInnerText(reader, "SidestepClipSet"); + break; + case "PoseMatcherName": + PoseMatcherName = Xml.GetChildInnerText(reader, "PoseMatcherName"); + break; + case "PoseMatcherProneName": + PoseMatcherProneName = Xml.GetChildInnerText(reader, "PoseMatcherProneName"); + break; + case "GetupSetHash": + GetupSetHash = Xml.GetChildInnerText(reader, "GetupSetHash"); + break; + case "CreatureMetadataName": + CreatureMetadataName = Xml.GetChildInnerText(reader, "CreatureMetadataName"); + break; + case "DecisionMakerName": + DecisionMakerName = Xml.GetChildInnerText(reader, "DecisionMakerName"); + break; + case "MotionTaskDataSetName": + MotionTaskDataSetName = Xml.GetChildInnerText(reader, "MotionTaskDataSetName"); + break; + case "DefaultTaskDataSetName": + DefaultTaskDataSetName = Xml.GetChildInnerText(reader, "DefaultTaskDataSetName"); + break; + case "PedCapsuleName": + PedCapsuleName = Xml.GetChildInnerText(reader, "PedCapsuleName"); + break; + case "PedLayoutName": + PedLayoutName = Xml.GetChildInnerText(reader, "PedLayoutName"); + break; + case "PedComponentSetName": + PedComponentSetName = Xml.GetChildInnerText(reader, "PedComponentSetName"); + break; + case "PedComponentClothName": + PedComponentClothName = Xml.GetChildInnerText(reader, "PedComponentClothName"); + break; + case "PedIKSettingsName": + PedIKSettingsName = Xml.GetChildInnerText(reader, "PedIKSettingsName"); + break; + case "TaskDataName": + TaskDataName = Xml.GetChildInnerText(reader, "TaskDataName"); + break; + case "IsStreamedGfx": + IsStreamedGfx = Xml.GetChildBoolAttribute(reader, "IsStreamedGfx", "value"); + break; + case "AmbulanceShouldRespondTo": + AmbulanceShouldRespondTo = Xml.GetChildBoolAttribute(reader, "AmbulanceShouldRespondTo", "value"); + break; + case "CanRideBikeWithNoHelmet": + CanRideBikeWithNoHelmet = Xml.GetChildBoolAttribute(reader, "CanRideBikeWithNoHelmet", "value"); + break; + case "CanSpawnInCar": + CanSpawnInCar = Xml.GetChildBoolAttribute(reader, "CanSpawnInCar", "value"); + break; + case "IsHeadBlendPed": + IsHeadBlendPed = Xml.GetChildBoolAttribute(reader, "IsHeadBlendPed", "value"); + break; + case "bOnlyBulkyItemVariations": + bOnlyBulkyItemVariations = Xml.GetChildBoolAttribute(reader, "bOnlyBulkyItemVariations", "value"); + break; + case "RelationshipGroup": + RelationshipGroup = Xml.GetChildInnerText(reader, "RelationshipGroup"); + break; + case "NavCapabilitiesName": + NavCapabilitiesName = Xml.GetChildInnerText(reader, "NavCapabilitiesName"); + break; + case "PerceptionInfo": + PerceptionInfo = Xml.GetChildInnerText(reader, "PerceptionInfo"); + break; + case "DefaultBrawlingStyle": + DefaultBrawlingStyle = Xml.GetChildInnerText(reader, "DefaultBrawlingStyle"); + break; + case "DefaultUnarmedWeapon": + DefaultUnarmedWeapon = Xml.GetChildInnerText(reader, "DefaultUnarmedWeapon"); + break; + case "Personality": + Personality = Xml.GetChildInnerText(reader, "Personality"); + break; + case "CombatInfo": + CombatInfo = Xml.GetChildInnerText(reader, "CombatInfo"); + break; + case "VfxInfoName": + VfxInfoName = Xml.GetChildInnerText(reader, "VfxInfoName"); + break; + case "AmbientClipsForFlee": + AmbientClipsForFlee = Xml.GetChildInnerText(reader, "AmbientClipsForFlee"); + break; + case "Radio1": + Radio1 = Xml.GetChildInnerText(reader, "Radio1"); // MetaName.ePedRadioGenre + break; + case "Radio2": + Radio2 = Xml.GetChildInnerText(reader, "Radio2"); // MetaName.ePedRadioGenre + break; + case "FUpOffset": + FUpOffset = Xml.GetChildFloatAttribute(reader, "FUpOffset", "value"); + break; + case "RUpOffset": + RUpOffset = Xml.GetChildFloatAttribute(reader, "RUpOffset", "value"); + break; + case "FFrontOffset": + FFrontOffset = Xml.GetChildFloatAttribute(reader, "FFrontOffset", "value"); + break; + case "RFrontOffset": + RFrontOffset = Xml.GetChildFloatAttribute(reader, "RFrontOffset", "value"); + break; + case "MinActivationImpulse": + MinActivationImpulse = Xml.GetChildFloatAttribute(reader, "MinActivationImpulse", "value"); + break; + case "Stubble": + Stubble = Xml.GetChildFloatAttribute(reader, "Stubble", "value"); + break; + case "HDDist": + HDDist = Xml.GetChildFloatAttribute(reader, "HDDist", "value"); + break; + case "TargetingThreatModifier": + TargetingThreatModifier = Xml.GetChildFloatAttribute(reader, "TargetingThreatModifier", "value"); + break; + case "KilledPerceptionRangeModifer": + KilledPerceptionRangeModifer = Xml.GetChildFloatAttribute(reader, "KilledPerceptionRangeModifer", "value"); + break; + case "Sexiness": + Sexiness = Xml.GetChildInnerText(reader, "Sexiness"); // MetaTypeName.ARRAYINFO MetaName.eSexinessFlags + break; + case "Age": + Age = (byte)Xml.GetChildUIntAttribute(reader, "Age", "value"); + break; + case "MaxPassengersInCar": + MaxPassengersInCar = (byte)Xml.GetChildUIntAttribute(reader, "MaxPassengersInCar", "value"); + break; + case "ExternallyDrivenDOFs": + ExternallyDrivenDOFs = Xml.GetChildInnerText(reader, "ExternallyDrivenDOFs"); // MetaTypeName.ARRAYINFO MetaName.eExternallyDrivenDOFs + break; + case "PedVoiceGroup": + PedVoiceGroup = Xml.GetChildInnerText(reader, "PedVoiceGroup"); + break; + case "AnimalAudioObject": + AnimalAudioObject = Xml.GetChildInnerText(reader, "AnimalAudioObject"); + break; + case "AbilityType": + AbilityType = Xml.GetChildInnerText(reader, "AbilityType"); // MetaName.SpecialAbilityType + break; + case "ThermalBehaviour": + ThermalBehaviour = Xml.GetChildInnerText(reader, "ThermalBehaviour"); // MetaName.ThermalBehaviour + break; + case "SuperlodType": + SuperlodType = Xml.GetChildInnerText(reader, "SuperlodType"); // MetaName.eSuperlodType + break; + case "ScenarioPopStreamingSlot": + ScenarioPopStreamingSlot = Xml.GetChildInnerText(reader, "ScenarioPopStreamingSlot"); // MetaName.eScenarioPopStreamingSlot + break; + case "DefaultSpawningPreference": + DefaultSpawningPreference = Xml.GetChildInnerText(reader, "DefaultSpawningPreference"); // MetaName.DefaultSpawnPreference + break; + case "DefaultRemoveRangeMultiplier": + DefaultRemoveRangeMultiplier = Xml.GetChildFloatAttribute(reader, "DefaultRemoveRangeMultiplier", "value"); + break; + case "AllowCloseSpawning": + AllowCloseSpawning = Xml.GetChildBoolAttribute(reader, "AllowCloseSpawning", "value"); + break; + default: + reader.Skip(); + break; + } + } + } public CPedModelInfo__InitData(XmlNode node) { @@ -300,6 +626,25 @@ namespace CodeWalker.GameFiles public string parent { get; set; } public string child { get; set; } + + public CTxdRelationship(XmlReader reader) + { + if (reader.Name == "parent") + { + parent = Xml.GetChildInnerText(reader, "parent"); + } else if (reader.Name == "child") + { + child = Xml.GetChildInnerText(reader, "child"); + } + if (reader.Name == "parent") + { + parent = Xml.GetChildInnerText(reader, "parent"); + } + else if (reader.Name == "child") + { + child = Xml.GetChildInnerText(reader, "child"); + } + } public CTxdRelationship(XmlNode node) { parent = Xml.GetChildInnerText(node, "parent"); @@ -331,6 +676,34 @@ namespace CodeWalker.GameFiles } } + public CMultiTxdRelationship(XmlReader reader) + { + reader.ReadStartElement("Item"); + while (reader.MoveToContent() == XmlNodeType.Element && reader.Name != "Item") + { + switch (reader.Name) + { + case "children": + var childrenList = new List(); + foreach (var item in Xml.IterateItems(reader, "children")) + { + childrenList.Add(item.Value); + } + if (childrenList.Count > 0) + { + children = childrenList.ToArray(); + } + break; + case "parent": + parent = Xml.GetChildInnerText(reader, "parent"); + break; + default: + throw new InvalidOperationException($"Found invalid XML Element \"{reader.Name}\" of type \"{reader.NodeType}\""); + } + } + reader.ReadEndElement(); + } + public override string ToString() { return parent + ": " + (children?.Length ?? 0).ToString() + " children"; diff --git a/CodeWalker.Core/GameFiles/FileTypes/RelFile.cs b/CodeWalker.Core/GameFiles/FileTypes/RelFile.cs index 0411c26..bd73988 100644 --- a/CodeWalker.Core/GameFiles/FileTypes/RelFile.cs +++ b/CodeWalker.Core/GameFiles/FileTypes/RelFile.cs @@ -105,7 +105,26 @@ namespace CodeWalker.GameFiles RpfFileEntry = entry; } - public void Load(byte[] data, RpfFileEntry entry) + public unsafe string ReadString(BinaryReader br, int length, bool ignoreNullTerminator = false) + { + var bytes = stackalloc char[length]; + var currentLength = 0; + while (currentLength < length) + { + var c = (char)br.ReadByte(); + if (c != 0) + { + bytes[currentLength] = c; + currentLength++; + } + else if (!ignoreNullTerminator) + break; + } + + return new string(bytes, 0, currentLength); + } + + public unsafe void Load(byte[] data, RpfFileEntry entry) { RawFileData = data; if (entry != null) @@ -114,8 +133,8 @@ namespace CodeWalker.GameFiles Name = entry.Name; } - MemoryStream ms = new MemoryStream(data); - BinaryReader br = new BinaryReader(ms); + using MemoryStream ms = new MemoryStream(data); + using BinaryReader br = new BinaryReader(ms); StringBuilder sb = new StringBuilder(); RelType = (RelDatFileType)br.ReadUInt32(); //type @@ -134,19 +153,25 @@ namespace CodeWalker.GameFiles } NameTableOffsets = ntoffsets; string[] names = new string[NameTableCount]; + var remainingLength = (int)NameTableLength; for (uint i = 0; i < NameTableCount; i++) { - sb.Clear(); - while (true) + int length = 0; + if (i < NameTableCount - 1) { - char c = (char)br.ReadByte(); - if (c != 0) sb.Append(c); - else break; + length = (int)(NameTableOffsets[i + 1] - NameTableOffsets[i]); + } - names[i] = sb.ToString(); + else + { + length = remainingLength; + } + + names[i] = ReadString(br, length); //JenkIndex.Ensure(names[i]); //really need both here..? - JenkIndex.Ensure(names[i].ToLowerInvariant()); + JenkIndex.EnsureLower(names[i]); + remainingLength -= length; } NameTable = names; } @@ -164,18 +189,13 @@ namespace CodeWalker.GameFiles for (uint i = 0; i < IndexCount; i++) { byte sl = br.ReadByte(); - sb.Clear(); - for (int j = 0; j < sl; j++) - { - char c = (char)br.ReadByte(); - if (c != 0) sb.Append(c); - } + var str = ReadString(br, (int)sl, true); RelIndexString ristr = new RelIndexString(); - ristr.Name = sb.ToString(); + ristr.Name = str; ristr.Offset = br.ReadUInt32(); ristr.Length = br.ReadUInt32(); indexstrs[i] = ristr; - JenkIndex.Ensure(ristr.Name.ToLowerInvariant()); + JenkIndex.EnsureLower(ristr.Name); } IndexStrings = indexstrs; } @@ -235,9 +255,6 @@ namespace CodeWalker.GameFiles { } //EOF! - br.Dispose(); - ms.Dispose(); - ParseDataBlock(); @@ -253,8 +270,8 @@ namespace CodeWalker.GameFiles - MemoryStream ms = new MemoryStream(DataBlock); - BinaryReader br = new BinaryReader(ms); + using MemoryStream ms = new MemoryStream(DataBlock); + using BinaryReader br = new BinaryReader(ms); DataUnkVal = br.ReadUInt32(); //3 bytes used... for? ..version? flags? #region DataUnkVal unk values test @@ -315,12 +332,6 @@ namespace CodeWalker.GameFiles RelDatasSorted = reldatas.ToArray(); - br.Dispose(); - ms.Dispose(); - - - - RelDataDict.Clear(); foreach (var reldata in RelDatas) { @@ -328,7 +339,7 @@ namespace CodeWalker.GameFiles { reldata.NameHash = JenkHash.GenHash(reldata.Name); //should this be lower case? JenkIndex.Ensure(reldata.Name); - JenkIndex.Ensure(reldata.Name.ToLowerInvariant()); //which one to use? + JenkIndex.EnsureLower(reldata.Name); //which one to use? } //if (reldata.NameHash == 0) @@ -349,24 +360,10 @@ namespace CodeWalker.GameFiles for (int i = 0; i < snd.ChildSoundsCount; i++) { var audhash = snd.ChildSoundsHashes[i]; - RelData auddata = null; - if (RelDataDict.TryGetValue(audhash, out auddata)) + if (RelDataDict.TryGetValue(audhash, out var auddata)) { snd.ChildSounds[i] = auddata; } - else - { } - } - } - if (snd.AudioContainers != null) - { - foreach (var cnt in snd.AudioContainers) - { - string cname = JenkIndex.TryGetString(cnt.Hash); - if (!string.IsNullOrEmpty(cname)) - { } - else - { } } } } @@ -385,8 +382,6 @@ namespace CodeWalker.GameFiles { speechDict[speechData.DataOffset] = speechData; } - else - { } speechData.Type = Dat4SpeechType.ByteArray; speechData.TypeID = 0; //will be set again after this @@ -397,32 +392,24 @@ namespace CodeWalker.GameFiles var hashOffset = HashTableOffsets[i]; var hash = HashTable[i]; var itemOffset = hashOffset - 8; - Dat4SpeechData speechData = null; - speechDict.TryGetValue(itemOffset, out speechData); - if (speechData != null) + if (speechDict.TryGetValue(itemOffset, out var speechData) && speechData != null) { speechData.Type = Dat4SpeechType.Hash; speechData.TypeID = 4; speechData.Hash = hash; } - else - { } } for (uint i = 0; i < PackTableCount; i++) { var packOffset = PackTableOffsets[i]; var pack = PackTable[i]; var itemOffset = packOffset - 12; - Dat4SpeechData speechData = null; - speechDict.TryGetValue(itemOffset, out speechData); - if (speechData != null) + if (speechDict.TryGetValue(itemOffset, out var speechData) && speechData != null) { speechData.Type = Dat4SpeechType.Container; speechData.TypeID = 8; speechData.ContainerHash = pack; } - else - { }//shouldn't happen! } @@ -456,33 +443,31 @@ namespace CodeWalker.GameFiles d.Data = data; - using (BinaryReader dbr = new BinaryReader(new MemoryStream(data))) - { - d.ReadType(dbr); + using BinaryReader dbr = new BinaryReader(new MemoryStream(data)); + d.ReadType(dbr); - switch (RelType) - { - case RelDatFileType.Dat4: //speech.dat4.rel, audioconfig.dat4.rel - return ReadData4(d, dbr); - case RelDatFileType.Dat10ModularSynth: //amp.dat10.rel - return ReadData10(d, dbr); - case RelDatFileType.Dat15DynamicMixer: //mix.dat15.rel - return ReadData15(d, dbr); - case RelDatFileType.Dat16Curves: //curves.dat16.rel - return ReadData16(d, dbr); - case RelDatFileType.Dat22Categories: //categories.dat22.rel - return ReadData22(d, dbr); - case RelDatFileType.Dat54DataEntries: //sounds.dat54.rel - return ReadData54(d, dbr); - case RelDatFileType.Dat149: //game.dat149.rel - return ReadData149(d, dbr); - case RelDatFileType.Dat150: //game.dat150.rel - return ReadData150(d, dbr); - case RelDatFileType.Dat151: //game.dat151.rel - return ReadData151(d, dbr); - default: - return d; //shouldn't get here... - } + switch (RelType) + { + case RelDatFileType.Dat4: //speech.dat4.rel, audioconfig.dat4.rel + return ReadData4(d, dbr); + case RelDatFileType.Dat10ModularSynth: //amp.dat10.rel + return ReadData10(d, dbr); + case RelDatFileType.Dat15DynamicMixer: //mix.dat15.rel + return ReadData15(d, dbr); + case RelDatFileType.Dat16Curves: //curves.dat16.rel + return ReadData16(d, dbr); + case RelDatFileType.Dat22Categories: //categories.dat22.rel + return ReadData22(d, dbr); + case RelDatFileType.Dat54DataEntries: //sounds.dat54.rel + return ReadData54(d, dbr); + case RelDatFileType.Dat149: //game.dat149.rel + return ReadData149(d, dbr); + case RelDatFileType.Dat150: //game.dat150.rel + return ReadData150(d, dbr); + case RelDatFileType.Dat151: //game.dat151.rel + return ReadData151(d, dbr); + default: + return d; //shouldn't get here... } } @@ -25334,7 +25319,7 @@ namespace CodeWalker.GameFiles { return 0; } - if (str.StartsWith("hash_")) + if (str.StartsWith("hash_", StringComparison.OrdinalIgnoreCase)) { return Convert.ToUInt32(str.Substring(5), 16); } diff --git a/CodeWalker.Core/GameFiles/FileTypes/Stats.cs b/CodeWalker.Core/GameFiles/FileTypes/Stats.cs index 09bacf7..1118ece 100644 --- a/CodeWalker.Core/GameFiles/FileTypes/Stats.cs +++ b/CodeWalker.Core/GameFiles/FileTypes/Stats.cs @@ -26,16 +26,14 @@ namespace CodeWalker.GameFiles public static bool Ensure(string str) { uint hash = JenkHash.GenHash(str); - if (hash == 0) return true; - lock (syncRoot) - { - if (!Index.ContainsKey(hash)) - { - Index.Add(hash, str); - return false; - } - } - return true; + return Ensure(str, hash); + } + + public static bool EnsureLower(string str) + { + uint hash = JenkHash.GenHashLower(str); + + return Ensure(str, hash); } public static bool Ensure(string str, uint hash) diff --git a/CodeWalker.Core/GameFiles/FileTypes/VehiclesFile.cs b/CodeWalker.Core/GameFiles/FileTypes/VehiclesFile.cs index dad02cc..946ed09 100644 --- a/CodeWalker.Core/GameFiles/FileTypes/VehiclesFile.cs +++ b/CodeWalker.Core/GameFiles/FileTypes/VehiclesFile.cs @@ -1,10 +1,15 @@ -using SharpDX; +using CodeWalker.World; +using SharpDX; using System; +using System.Collections; using System.Collections.Generic; +using System.IO; using System.Linq; +using System.Runtime.InteropServices; using System.Text; using System.Threading.Tasks; using System.Xml; +using System.Xml.Linq; namespace CodeWalker.GameFiles { @@ -28,14 +33,14 @@ namespace CodeWalker.GameFiles - public void Load(byte[] data, RpfFileEntry entry) + public void LoadOld(byte[] data, RpfFileEntry entry) { RpfFileEntry = entry; Name = entry.Name; FilePath = Name; - if (entry.NameLower.EndsWith(".meta")) + if (entry.IsExtension(".meta")) { string xml = TextUtil.GetUTF8Text(data); @@ -53,12 +58,64 @@ namespace CodeWalker.GameFiles } } + public void Load(byte[] data, RpfFileEntry entry) + { + RpfFileEntry = entry; + Name = entry.Name; + FilePath = Name; + + + if (entry.IsExtension(".meta")) + { + using var textReader = new StreamReader(new MemoryStream(data), Encoding.UTF8); + + using var xmlReader = XmlReader.Create(textReader); + + while (xmlReader.Read()) + { + xmlReader.MoveToContent(); + + //var _ = xmlReader.Name switch + //{ + // "residentTxd" => ResidentTxd = Xml.GetChildInnerText(xmlReader, "residentTxd"), + // "InitDatas" => LoadInitDatas(xmlReader), + // "txdRelationships" => LoadTxdRelationships(xmlReader), + // _ => throw new Exception() + //}; + + switch (xmlReader.Name) + { + case string Name when Name.Equals("residentTxd", StringComparison.OrdinalIgnoreCase): + ResidentTxd = Xml.GetChildInnerText(xmlReader, "residentTxd"); + break; + case string Name when Name.Equals("InitDatas", StringComparison.OrdinalIgnoreCase): + LoadInitDatas(xmlReader); + break; + case string Name when Name.Equals("txdRelationships", StringComparison.OrdinalIgnoreCase): + LoadTxdRelationships(xmlReader); + break; + default: + break; + } + } + + + //ResidentTxd = Xml.GetChildInnerText(xmldoc.SelectSingleNode("CVehicleModelInfo__InitDataList"), "residentTxd"); + + //LoadInitDatas(xmldoc); + + //LoadTxdRelationships(xmldoc); + + Loaded = true; + } + } + private void LoadInitDatas(XmlDocument xmldoc) { XmlNodeList items = xmldoc.SelectNodes("CVehicleModelInfo__InitDataList/InitDatas/Item | CVehicleModelInfo__InitDataList/InitDatas/item"); - InitDatas = new List(); + InitDatas = new List(items.Count); for (int i = 0; i < items.Count; i++) { var node = items[i]; @@ -68,6 +125,53 @@ namespace CodeWalker.GameFiles } } + private void LoadInitDatas(XmlReader reader) + { + if (!reader.IsStartElement() || reader.Name != "InitDatas") + { + throw new InvalidOperationException("XmlReader is not at start element of \"InitDatas\""); + } + + InitDatas = new List(); + + reader.ReadStartElement("InitDatas"); + + while (reader.MoveToContent() == XmlNodeType.Element && reader.Name == "Item") + { + if (reader.IsStartElement()) + { + VehicleInitData d = new VehicleInitData(); + d.Load(reader); + InitDatas.Add(d); + } + } + } + + private void LoadTxdRelationships(XmlReader reader) + { + if (reader.IsEmptyElement) + { + TxdRelationships = new Dictionary(); + reader.ReadStartElement(); + return; + } + TxdRelationships = new Dictionary(); + + foreach(var item in Xml.IterateItems(reader, "txdRelationships")) + { + var childstr = item.Element("child")?.Value; + var parentstr = item.Element("parent")?.Value; + + if ((!string.IsNullOrEmpty(parentstr)) && (!string.IsNullOrEmpty(childstr))) + { + if (!TxdRelationships.ContainsKey(childstr)) + { + TxdRelationships.Add(childstr, parentstr); + } + } + } + } + private void LoadTxdRelationships(XmlDocument xmldoc) { XmlNodeList items = xmldoc.SelectNodes("CVehicleModelInfo__InitDataList/txdRelationships/Item | CVehicleModelInfo__InitDataList/txdRelationships/item"); @@ -89,8 +193,6 @@ namespace CodeWalker.GameFiles } } } - - } @@ -106,7 +208,7 @@ namespace CodeWalker.GameFiles public string expressionName { get; set; } //null public string animConvRoofDictName { get; set; } //null public string animConvRoofName { get; set; } //null - public string animConvRoofWindowsAffected { get; set; } // + public string[] animConvRoofWindowsAffected { get; set; } // public string ptfxAssetName { get; set; } //weap_xs_vehicle_weapons public string audioNameHash { get; set; } // public string layout { get; set; } //LAYOUT_STD_ARENA_1HONLY @@ -185,6 +287,354 @@ namespace CodeWalker.GameFiles public VehicleOverrideRagdollThreshold pOverrideRagdollThreshold { get; set; } // public string[] firstPersonDrivebyData { get; set; } //// STD_IMPALER2_FRONT_LEFT// STD_IMPALER2_FRONT_RIGHT// + public void Load(XmlReader reader) + { + reader.ReadStartElement("Item"); + while (reader.Name != "Item" && reader.Read()) + { + if (reader.NodeType == XmlNodeType.EndElement && reader.Name == "Item") + { + reader.ReadEndElement(); + return; + } + if (reader.IsStartElement()) + { + while (reader.IsStartElement()) + { + switch (reader.Name) + { + case "modelName": + modelName = Xml.GetChildInnerText(reader, "modelName"); + break; + case "txdName": + txdName = Xml.GetChildInnerText(reader, "txdName"); + break; + case "handlingId": + handlingId = Xml.GetChildInnerText(reader, "handlingId"); + break; + case "gameName": + gameName = Xml.GetChildInnerText(reader, "gameName"); + break; + case "vehicleMakeName": + vehicleMakeName = Xml.GetChildInnerText(reader, "vehicleMakeName"); + break; + case "expressionDictName": + expressionDictName = Xml.GetChildInnerText(reader, "expressionDictName"); + break; + case "expressionName": + expressionName = Xml.GetChildInnerText(reader, "expressionName"); + break; + case "animConvRoofDictName": + animConvRoofDictName = Xml.GetChildInnerText(reader, "animConvRoofDictName"); + break; + case "animConvRoofName": + animConvRoofName = Xml.GetChildInnerText(reader, "animConvRoofName"); + break; + case "animConvRoofWindowsAffected": + animConvRoofWindowsAffected = GetStringItemArray(reader, "animConvRoofWindowsAffected"); + break; + case "ptfxAssetName": + ptfxAssetName = Xml.GetChildInnerText(reader, "ptfxAssetName"); + break; + case "audioNameHash": + audioNameHash = Xml.GetChildInnerText(reader, "audioNameHash"); + break; + case "layout": + layout = Xml.GetChildInnerText(reader, "layout"); + break; + case "coverBoundOffsets": + coverBoundOffsets = Xml.GetChildInnerText(reader, "coverBoundOffsets"); + break; + case "explosionInfo": + explosionInfo = Xml.GetChildInnerText(reader, "explosionInfo"); + break; + case "scenarioLayout": + scenarioLayout = Xml.GetChildInnerText(reader, "scenarioLayout"); + break; + case "cameraName": + cameraName = Xml.GetChildInnerText(reader, "cameraName"); + break; + case "aimCameraName": + aimCameraName = Xml.GetChildInnerText(reader, "aimCameraName"); + break; + case "bonnetCameraName": + bonnetCameraName = Xml.GetChildInnerText(reader, "bonnetCameraName"); + break; + case "povCameraName": + povCameraName = Xml.GetChildInnerText(reader, "povCameraName"); + break; + case "FirstPersonDriveByIKOffset": + FirstPersonDriveByIKOffset = Xml.GetChildVector3Attributes(reader, "FirstPersonDriveByIKOffset"); + break; + case "FirstPersonDriveByUnarmedIKOffset": + FirstPersonDriveByUnarmedIKOffset = Xml.GetChildVector3Attributes(reader, "FirstPersonDriveByUnarmedIKOffset"); + break; + case "FirstPersonProjectileDriveByIKOffset": + FirstPersonProjectileDriveByIKOffset = Xml.GetChildVector3Attributes(reader, "FirstPersonProjectileDriveByIKOffset"); + break; + case "FirstPersonProjectileDriveByPassengerIKOffset": + FirstPersonProjectileDriveByPassengerIKOffset = Xml.GetChildVector3Attributes(reader, "FirstPersonProjectileDriveByPassengerIKOffset"); + break; + case "FirstPersonDriveByRightPassengerIKOffset": + FirstPersonDriveByRightPassengerIKOffset = Xml.GetChildVector3Attributes(reader, "FirstPersonDriveByRightPassengerIKOffset"); + break; + case "FirstPersonDriveByRightPassengerUnarmedIKOffset": + FirstPersonDriveByRightPassengerUnarmedIKOffset = Xml.GetChildVector3Attributes(reader, "FirstPersonDriveByRightPassengerUnarmedIKOffset"); + break; + case "FirstPersonMobilePhoneOffset": + FirstPersonMobilePhoneOffset = Xml.GetChildVector3Attributes(reader, "FirstPersonMobilePhoneOffset"); + break; + case "FirstPersonPassengerMobilePhoneOffset": + FirstPersonPassengerMobilePhoneOffset = Xml.GetChildVector3Attributes(reader, "FirstPersonPassengerMobilePhoneOffset"); + break; + case "PovCameraOffset": + PovCameraOffset = Xml.GetChildVector3Attributes(reader, "PovCameraOffset"); + break; + case "PovCameraVerticalAdjustmentForRollCage": + PovCameraVerticalAdjustmentForRollCage = Xml.GetChildVector3Attributes(reader, "PovCameraVerticalAdjustmentForRollCage"); + break; + case "PovPassengerCameraOffset": + PovPassengerCameraOffset = Xml.GetChildVector3Attributes(reader, "PovPassengerCameraOffset"); + break; + case "PovRearPassengerCameraOffset": + PovRearPassengerCameraOffset = Xml.GetChildVector3Attributes(reader, "PovRearPassengerCameraOffset"); + break; + case "vfxInfoName": + vfxInfoName = Xml.GetChildInnerText(reader, "vfxInfoName"); + break; + case "shouldUseCinematicViewMode": + shouldUseCinematicViewMode = Xml.GetChildBoolAttribute(reader, "shouldUseCinematicViewMode"); + break; + case "shouldCameraTransitionOnClimbUpDown": + shouldCameraTransitionOnClimbUpDown = Xml.GetChildBoolAttribute(reader, "shouldCameraTransitionOnClimbUpDown"); + break; + case "shouldCameraIgnoreExiting": + shouldCameraIgnoreExiting = Xml.GetChildBoolAttribute(reader, "shouldCameraIgnoreExiting"); + break; + case "AllowPretendOccupants": + AllowPretendOccupants = Xml.GetChildBoolAttribute(reader, "AllowPretendOccupants"); + break; + case "AllowJoyriding": + AllowJoyriding = Xml.GetChildBoolAttribute(reader, "AllowJoyriding"); + break; + case "AllowSundayDriving": + AllowSundayDriving = Xml.GetChildBoolAttribute(reader, "AllowSundayDriving"); + break; + case "AllowBodyColorMapping": + AllowBodyColorMapping = Xml.GetChildBoolAttribute(reader, "AllowBodyColorMapping"); + break; + case "wheelScale": + wheelScale = Xml.GetChildFloatAttribute(reader, "wheelScale"); + break; + case "wheelScaleRear": + wheelScaleRear = Xml.GetChildFloatAttribute(reader, "wheelScaleRear"); + break; + case "dirtLevelMin": + dirtLevelMin = Xml.GetChildFloatAttribute(reader, "dirtLevelMin"); + break; + case "dirtLevelMax": + dirtLevelMax = Xml.GetChildFloatAttribute(reader, "dirtLevelMax"); + break; + case "envEffScaleMin": + envEffScaleMin = Xml.GetChildFloatAttribute(reader, "envEffScaleMin"); + break; + case "envEffScaleMax": + envEffScaleMax = Xml.GetChildFloatAttribute(reader, "envEffScaleMax"); + break; + case "envEffScaleMin2": + envEffScaleMin2 = Xml.GetChildFloatAttribute(reader, "envEffScaleMin2"); + break; + case "envEffScaleMax2": + envEffScaleMax2 = Xml.GetChildFloatAttribute(reader, "envEffScaleMax2"); + break; + case "damageMapScale": + damageMapScale = Xml.GetChildFloatAttribute(reader, "damageMapScale"); + break; + case "damageOffsetScale": + damageOffsetScale = Xml.GetChildFloatAttribute(reader, "damageOffsetScale"); + break; + case "diffuseTint": + diffuseTint = new Color4(Convert.ToUInt32(Xml.GetChildStringAttribute(reader, "diffuseTint", "value").Replace("0x", ""), 16)); ; + break; + case "steerWheelMult": + steerWheelMult = Xml.GetChildFloatAttribute(reader, "steerWheelMult"); + break; + case "HDTextureDist": + HDTextureDist = Xml.GetChildFloatAttribute(reader, "HDTextureDist"); + break; + case "lodDistances": + lodDistances = GetFloatArray(reader, "lodDistances", '\n'); + break; + case "minSeatHeight": + minSeatHeight = Xml.GetChildFloatAttribute(reader, "minSeatHeight"); + break; + case "identicalModelSpawnDistance": + identicalModelSpawnDistance = Xml.GetChildFloatAttribute(reader, "identicalModelSpawnDistance"); + break; + case "maxNumOfSameColor": + maxNumOfSameColor = Xml.GetChildIntAttribute(reader, "maxNumOfSameColor"); + break; + case "defaultBodyHealth": + defaultBodyHealth = Xml.GetChildFloatAttribute(reader, "defaultBodyHealth"); + break; + case "pretendOccupantsScale": + pretendOccupantsScale = Xml.GetChildFloatAttribute(reader, "pretendOccupantsScale"); + break; + case "visibleSpawnDistScale": + visibleSpawnDistScale = Xml.GetChildFloatAttribute(reader, "visibleSpawnDistScale"); + break; + case "trackerPathWidth": + trackerPathWidth = Xml.GetChildFloatAttribute(reader, "trackerPathWidth"); + break; + case "weaponForceMult": + weaponForceMult = Xml.GetChildFloatAttribute(reader, "weaponForceMult"); + break; + case "frequency": + frequency = Xml.GetChildFloatAttribute(reader, "frequency"); + break; + case "swankness": + swankness = Xml.GetChildInnerText(reader, "swankness"); + break; + case "maxNum": + maxNum = Xml.GetChildIntAttribute(reader, "maxNum", "value"); + break; + case "flags": + flags = GetStringArray(reader, "flags", ' '); + break; + case "type": + type = Xml.GetChildInnerText(reader, "type"); + break; + case "plateType": + plateType = Xml.GetChildInnerText(reader, "plateType"); + break; + case "dashboardType": + dashboardType = Xml.GetChildInnerText(reader, "dashboardType"); + break; + case "vehicleClass": + vehicleClass = Xml.GetChildInnerText(reader, "vehicleClass"); + break; + case "wheelType": + wheelType = Xml.GetChildInnerText(reader, "wheelType"); + break; + case "trailers": + trailers = GetStringItemArray(reader, "trailers"); + break; + case "additionalTrailers": + additionalTrailers = GetStringItemArray(reader, "additionalTrailers"); + break; + case "drivers": + if (reader.IsEmptyElement) + { + reader.ReadStartElement(); + break; + } + + var _drivers = new List(); + + foreach (var item in Xml.IterateItems(reader, "drivers")) + { + var driver = new VehicleDriver(); + driver.driverName = item.Element("driverName")?.Value ?? string.Empty; + driver.npcName = item.Element("npcName")?.Value ?? string.Empty; + + if (!string.IsNullOrEmpty(driver.npcName) || !string.IsNullOrEmpty(driver.driverName)) + { + _drivers.Add(driver); + } + } + drivers = _drivers.ToArray(); + break; + case "doorsWithCollisionWhenClosed": + doorsWithCollisionWhenClosed = GetStringItemArray(reader, "doorsWithCollisionWhenClosed"); + break; + case "driveableDoors": + driveableDoors = GetStringItemArray(reader, "driveableDoors"); + break; + case "bumpersNeedToCollideWithMap": + bumpersNeedToCollideWithMap = Xml.GetChildBoolAttribute(reader, "bumpersNeedToCollideWithMap", "value"); + break; + case "needsRopeTexture": + needsRopeTexture = Xml.GetChildBoolAttribute(reader, "needsRopeTexture", "value"); + break; + case "requiredExtras": + requiredExtras = GetStringArray(reader, "requiredExtras", ' '); + break; + case "rewards": + rewards = GetStringItemArray(reader, "rewards"); + break; + case "cinematicPartCamera": + cinematicPartCamera = GetStringItemArray(reader, "cinematicPartCamera"); + break; + case "NmBraceOverrideSet": + NmBraceOverrideSet = Xml.GetChildInnerText(reader, "NmBraceOverrideSet"); + break; + case "buoyancySphereOffset": + buoyancySphereOffset = Xml.GetChildVector3Attributes(reader, "buoyancySphereOffset"); + break; + case "buoyancySphereSizeScale": + buoyancySphereSizeScale = Xml.GetChildFloatAttribute(reader, "buoyancySphereSizeScale", "value"); + break; + case "pOverrideRagdollThreshold": + if (reader.IsEmptyElement) + { + reader.ReadStartElement(); + break; + } + + switch (reader.GetAttribute("type")) + { + case "NULL": + break; + case "CVehicleModelInfo__CVehicleOverrideRagdollThreshold": + reader.ReadStartElement(); + pOverrideRagdollThreshold = new VehicleOverrideRagdollThreshold(); + while (reader.MoveToContent() == XmlNodeType.Element) + { + if (reader.Name == "MinComponent") + { + pOverrideRagdollThreshold.MinComponent = Xml.GetChildIntAttribute(reader, "MinComponent", "value"); + } + else if (reader.Name == "MaxComponent") + { + pOverrideRagdollThreshold.MaxComponent = Xml.GetChildIntAttribute(reader, "MaxComponent", "value"); + } + else if (reader.Name == "ThresholdMult") + { + pOverrideRagdollThreshold.ThresholdMult = Xml.GetChildFloatAttribute(reader, "ThresholdMult", "value"); + } else + { + break; + } + } + + reader.ReadEndElement(); + break; + default: + break; + } + break; + case "firstPersonDrivebyData": + firstPersonDrivebyData = GetStringItemArray(reader, "firstPersonDrivebyData"); + break; + case "extraIncludes": + extraIncludes = GetStringItemArray(reader, "extraIncludes"); + break; + case "FirstPersonDriveByLeftPassengerIKOffset": + case "FirstPersonDriveByLeftPassengerUnarmedIKOffset": + default: + reader.Skip(); + break; + } + } + } + else + { + reader.Read(); + } + } + + reader.ReadEndElement(); + } public void Load(XmlNode node) { @@ -197,7 +647,7 @@ namespace CodeWalker.GameFiles expressionName = Xml.GetChildInnerText(node, "expressionName"); animConvRoofDictName = Xml.GetChildInnerText(node, "animConvRoofDictName"); animConvRoofName = Xml.GetChildInnerText(node, "animConvRoofName"); - animConvRoofWindowsAffected = Xml.GetChildInnerText(node, "animConvRoofWindowsAffected");//? + animConvRoofWindowsAffected = GetStringItemArray(node, "animConvRoofWindowsAffected");//? ptfxAssetName = Xml.GetChildInnerText(node, "ptfxAssetName"); audioNameHash = Xml.GetChildInnerText(node, "audioNameHash"); layout = Xml.GetChildInnerText(node, "layout"); @@ -327,29 +777,73 @@ namespace CodeWalker.GameFiles if (getStringArrayList.Count == 0) return null; return getStringArrayList.ToArray(); } + + private string[] GetStringItemArray(XmlReader node, string childName) + { + if (node.IsEmptyElement) + { + node.ReadStartElement(); + return null; + } + + lock(getStringArrayList) + { + getStringArrayList.Clear(); + node.ReadStartElement(); + while (node.MoveToContent() == XmlNodeType.Element && node.Name == "Item") + { + var istr = node.ReadElementContentAsString(); + if (!string.IsNullOrEmpty(istr)) + { + getStringArrayList.Add(istr); + } + } + node.ReadEndElement(); + + if (getStringArrayList.Count == 0) + return null; + return getStringArrayList.ToArray(); + } + } + + private string[] GetStringArray(string ldastr, char delimiter) + { + var ldarr = ldastr?.Split(delimiter); + if (ldarr == null) return null; + lock(getStringArrayList) + { + getStringArrayList.Clear(); + foreach (var ldstr in ldarr) + { + var ldt = ldstr?.Trim(); + if (!string.IsNullOrEmpty(ldt)) + { + getStringArrayList.Add(ldt); + } + } + if (getStringArrayList.Count == 0) return null; + return getStringArrayList.ToArray(); + } + } + private string[] GetStringArray(XmlNode node, string childName, char delimiter) { var ldastr = Xml.GetChildInnerText(node, childName); - var ldarr = ldastr?.Split(delimiter); - if (ldarr == null) return null; - getStringArrayList.Clear(); - foreach (var ldstr in ldarr) - { - var ldt = ldstr?.Trim(); - if (!string.IsNullOrEmpty(ldt)) - { - getStringArrayList.Add(ldt); - } - } - if (getStringArrayList.Count == 0) return null; - return getStringArrayList.ToArray(); + return GetStringArray(ldastr, delimiter); } - private float[] GetFloatArray(XmlNode node, string childName, char delimiter) + + private string[] GetStringArray(XmlReader reader, string childName, char delimiter) + { + var ldastr = Xml.GetChildInnerText(reader, childName); + return GetStringArray(ldastr, delimiter); + } + + private unsafe float[] GetFloatArray(string ldastr, char delimiter) { - var ldastr = Xml.GetChildInnerText(node, childName); var ldarr = ldastr?.Split(delimiter); if (ldarr == null) return null; - getFloatArrayList.Clear(); + var floats = stackalloc float[ldarr.Length]; + var i = 0; foreach (var ldstr in ldarr) { var ldt = ldstr?.Trim(); @@ -358,12 +852,30 @@ namespace CodeWalker.GameFiles float f; if (FloatUtil.TryParse(ldt, out f)) { - getFloatArrayList.Add(f); + floats[i] = f; + i++; } } } - if (getFloatArrayList.Count == 0) return null; - return getFloatArrayList.ToArray(); + if (i == 0) return null; + + var result = new float[i]; + + Marshal.Copy((IntPtr)floats, result, 0, i); + + return result; + } + + private float[] GetFloatArray(XmlNode node, string childName, char delimiter) + { + var ldastr = Xml.GetChildInnerText(node, childName); + return GetFloatArray(ldastr, delimiter); + } + + private float[] GetFloatArray(XmlReader reader, string childName, char delimiter) + { + var ldastr = Xml.GetChildInnerText(reader, childName); + return GetFloatArray(ldastr, delimiter); } private static List getStringArrayList = new List(); //kinda hacky.. @@ -374,28 +886,283 @@ namespace CodeWalker.GameFiles { return modelName; } + + public override bool Equals(object obj) + { + return obj is VehicleInitData data && + modelName == data.modelName && + txdName == data.txdName && + handlingId == data.handlingId && + gameName == data.gameName && + vehicleMakeName == data.vehicleMakeName && + expressionDictName == data.expressionDictName && + expressionName == data.expressionName && + animConvRoofDictName == data.animConvRoofDictName && + animConvRoofName == data.animConvRoofName && + animConvRoofWindowsAffected == data.animConvRoofWindowsAffected && + ptfxAssetName == data.ptfxAssetName && + audioNameHash == data.audioNameHash && + layout == data.layout && + coverBoundOffsets == data.coverBoundOffsets && + explosionInfo == data.explosionInfo && + scenarioLayout == data.scenarioLayout && + cameraName == data.cameraName && + aimCameraName == data.aimCameraName && + bonnetCameraName == data.bonnetCameraName && + povCameraName == data.povCameraName && + FirstPersonDriveByIKOffset.Equals(data.FirstPersonDriveByIKOffset) && + FirstPersonDriveByUnarmedIKOffset.Equals(data.FirstPersonDriveByUnarmedIKOffset) && + FirstPersonProjectileDriveByIKOffset.Equals(data.FirstPersonProjectileDriveByIKOffset) && + FirstPersonProjectileDriveByPassengerIKOffset.Equals(data.FirstPersonProjectileDriveByPassengerIKOffset) && + FirstPersonDriveByRightPassengerIKOffset.Equals(data.FirstPersonDriveByRightPassengerIKOffset) && + FirstPersonDriveByRightPassengerUnarmedIKOffset.Equals(data.FirstPersonDriveByRightPassengerUnarmedIKOffset) && + FirstPersonMobilePhoneOffset.Equals(data.FirstPersonMobilePhoneOffset) && + FirstPersonPassengerMobilePhoneOffset.Equals(data.FirstPersonPassengerMobilePhoneOffset) && + PovCameraOffset.Equals(data.PovCameraOffset) && + PovCameraVerticalAdjustmentForRollCage.Equals(data.PovCameraVerticalAdjustmentForRollCage) && + PovPassengerCameraOffset.Equals(data.PovPassengerCameraOffset) && + PovRearPassengerCameraOffset.Equals(data.PovRearPassengerCameraOffset) && + vfxInfoName == data.vfxInfoName && + shouldUseCinematicViewMode == data.shouldUseCinematicViewMode && + shouldCameraTransitionOnClimbUpDown == data.shouldCameraTransitionOnClimbUpDown && + shouldCameraIgnoreExiting == data.shouldCameraIgnoreExiting && + AllowPretendOccupants == data.AllowPretendOccupants && + AllowJoyriding == data.AllowJoyriding && + AllowSundayDriving == data.AllowSundayDriving && + AllowBodyColorMapping == data.AllowBodyColorMapping && + wheelScale == data.wheelScale && + wheelScaleRear == data.wheelScaleRear && + dirtLevelMin == data.dirtLevelMin && + dirtLevelMax == data.dirtLevelMax && + envEffScaleMin == data.envEffScaleMin && + envEffScaleMax == data.envEffScaleMax && + envEffScaleMin2 == data.envEffScaleMin2 && + envEffScaleMax2 == data.envEffScaleMax2 && + damageMapScale == data.damageMapScale && + damageOffsetScale == data.damageOffsetScale && + diffuseTint.Equals(data.diffuseTint) && + steerWheelMult == data.steerWheelMult && + HDTextureDist == data.HDTextureDist && + StructuralComparisons.StructuralEqualityComparer.Equals(lodDistances, data.lodDistances) && + minSeatHeight == data.minSeatHeight && + identicalModelSpawnDistance == data.identicalModelSpawnDistance && + maxNumOfSameColor == data.maxNumOfSameColor && + defaultBodyHealth == data.defaultBodyHealth && + pretendOccupantsScale == data.pretendOccupantsScale && + visibleSpawnDistScale == data.visibleSpawnDistScale && + trackerPathWidth == data.trackerPathWidth && + weaponForceMult == data.weaponForceMult && + frequency == data.frequency && + swankness == data.swankness && + maxNum == data.maxNum && + StructuralComparisons.StructuralEqualityComparer.Equals(flags, data.flags) && + type == data.type && + plateType == data.plateType && + dashboardType == data.dashboardType && + vehicleClass == data.vehicleClass && + wheelType == data.wheelType && + StructuralComparisons.StructuralEqualityComparer.Equals(trailers, data.trailers) && + StructuralComparisons.StructuralEqualityComparer.Equals(additionalTrailers, data.additionalTrailers) && + StructuralComparisons.StructuralEqualityComparer.Equals(drivers, data.drivers) && + StructuralComparisons.StructuralEqualityComparer.Equals(extraIncludes, data.extraIncludes) && + StructuralComparisons.StructuralEqualityComparer.Equals(doorsWithCollisionWhenClosed, data.doorsWithCollisionWhenClosed) && + StructuralComparisons.StructuralEqualityComparer.Equals(driveableDoors, data.driveableDoors) && + bumpersNeedToCollideWithMap == data.bumpersNeedToCollideWithMap && + needsRopeTexture == data.needsRopeTexture && + StructuralComparisons.StructuralEqualityComparer.Equals(requiredExtras, data.requiredExtras) && + StructuralComparisons.StructuralEqualityComparer.Equals(rewards, data.rewards) && + StructuralComparisons.StructuralEqualityComparer.Equals(cinematicPartCamera, data.cinematicPartCamera) && + NmBraceOverrideSet == data.NmBraceOverrideSet && + buoyancySphereOffset.Equals(data.buoyancySphereOffset) && + buoyancySphereSizeScale == data.buoyancySphereSizeScale && + EqualityComparer.Default.Equals(pOverrideRagdollThreshold, data.pOverrideRagdollThreshold) && + StructuralComparisons.StructuralEqualityComparer.Equals(firstPersonDrivebyData, data.firstPersonDrivebyData); + } + + public override int GetHashCode() + { + int hashCode = 1102137281; + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(modelName); + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(txdName); + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(handlingId); + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(gameName); + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(vehicleMakeName); + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(expressionDictName); + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(expressionName); + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(animConvRoofDictName); + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(animConvRoofName); + hashCode = hashCode * -1521134295 + StructuralComparisons.StructuralEqualityComparer.GetHashCode(animConvRoofWindowsAffected); + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(ptfxAssetName); + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(audioNameHash); + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(layout); + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(coverBoundOffsets); + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(explosionInfo); + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(scenarioLayout); + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(cameraName); + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(aimCameraName); + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(bonnetCameraName); + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(povCameraName); + hashCode = hashCode * -1521134295 + FirstPersonDriveByIKOffset.GetHashCode(); + hashCode = hashCode * -1521134295 + FirstPersonDriveByUnarmedIKOffset.GetHashCode(); + hashCode = hashCode * -1521134295 + FirstPersonProjectileDriveByIKOffset.GetHashCode(); + hashCode = hashCode * -1521134295 + FirstPersonProjectileDriveByPassengerIKOffset.GetHashCode(); + hashCode = hashCode * -1521134295 + FirstPersonDriveByRightPassengerIKOffset.GetHashCode(); + hashCode = hashCode * -1521134295 + FirstPersonDriveByRightPassengerUnarmedIKOffset.GetHashCode(); + hashCode = hashCode * -1521134295 + FirstPersonMobilePhoneOffset.GetHashCode(); + hashCode = hashCode * -1521134295 + FirstPersonPassengerMobilePhoneOffset.GetHashCode(); + hashCode = hashCode * -1521134295 + PovCameraOffset.GetHashCode(); + hashCode = hashCode * -1521134295 + PovCameraVerticalAdjustmentForRollCage.GetHashCode(); + hashCode = hashCode * -1521134295 + PovPassengerCameraOffset.GetHashCode(); + hashCode = hashCode * -1521134295 + PovRearPassengerCameraOffset.GetHashCode(); + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(vfxInfoName); + hashCode = hashCode * -1521134295 + shouldUseCinematicViewMode.GetHashCode(); + hashCode = hashCode * -1521134295 + shouldCameraTransitionOnClimbUpDown.GetHashCode(); + hashCode = hashCode * -1521134295 + shouldCameraIgnoreExiting.GetHashCode(); + hashCode = hashCode * -1521134295 + AllowPretendOccupants.GetHashCode(); + hashCode = hashCode * -1521134295 + AllowJoyriding.GetHashCode(); + hashCode = hashCode * -1521134295 + AllowSundayDriving.GetHashCode(); + hashCode = hashCode * -1521134295 + AllowBodyColorMapping.GetHashCode(); + hashCode = hashCode * -1521134295 + wheelScale.GetHashCode(); + hashCode = hashCode * -1521134295 + wheelScaleRear.GetHashCode(); + hashCode = hashCode * -1521134295 + dirtLevelMin.GetHashCode(); + hashCode = hashCode * -1521134295 + dirtLevelMax.GetHashCode(); + hashCode = hashCode * -1521134295 + envEffScaleMin.GetHashCode(); + hashCode = hashCode * -1521134295 + envEffScaleMax.GetHashCode(); + hashCode = hashCode * -1521134295 + envEffScaleMin2.GetHashCode(); + hashCode = hashCode * -1521134295 + envEffScaleMax2.GetHashCode(); + hashCode = hashCode * -1521134295 + damageMapScale.GetHashCode(); + hashCode = hashCode * -1521134295 + damageOffsetScale.GetHashCode(); + hashCode = hashCode * -1521134295 + diffuseTint.GetHashCode(); + hashCode = hashCode * -1521134295 + steerWheelMult.GetHashCode(); + hashCode = hashCode * -1521134295 + HDTextureDist.GetHashCode(); + hashCode = hashCode * -1521134295 + StructuralComparisons.StructuralEqualityComparer.GetHashCode(lodDistances); + hashCode = hashCode * -1521134295 + minSeatHeight.GetHashCode(); + hashCode = hashCode * -1521134295 + identicalModelSpawnDistance.GetHashCode(); + hashCode = hashCode * -1521134295 + maxNumOfSameColor.GetHashCode(); + hashCode = hashCode * -1521134295 + defaultBodyHealth.GetHashCode(); + hashCode = hashCode * -1521134295 + pretendOccupantsScale.GetHashCode(); + hashCode = hashCode * -1521134295 + visibleSpawnDistScale.GetHashCode(); + hashCode = hashCode * -1521134295 + trackerPathWidth.GetHashCode(); + hashCode = hashCode * -1521134295 + weaponForceMult.GetHashCode(); + hashCode = hashCode * -1521134295 + frequency.GetHashCode(); + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(swankness); + hashCode = hashCode * -1521134295 + maxNum.GetHashCode(); + hashCode = hashCode * -1521134295 + StructuralComparisons.StructuralEqualityComparer.GetHashCode(flags); + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(type); + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(plateType); + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(dashboardType); + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(vehicleClass); + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(wheelType); + hashCode = hashCode * -1521134295 + StructuralComparisons.StructuralEqualityComparer.GetHashCode(trailers); + hashCode = hashCode * -1521134295 + StructuralComparisons.StructuralEqualityComparer.GetHashCode(additionalTrailers); + hashCode = hashCode * -1521134295 + StructuralComparisons.StructuralEqualityComparer.GetHashCode(drivers); + hashCode = hashCode * -1521134295 + StructuralComparisons.StructuralEqualityComparer.GetHashCode(extraIncludes); + hashCode = hashCode * -1521134295 + StructuralComparisons.StructuralEqualityComparer.GetHashCode(doorsWithCollisionWhenClosed); + hashCode = hashCode * -1521134295 + StructuralComparisons.StructuralEqualityComparer.GetHashCode(driveableDoors); + hashCode = hashCode * -1521134295 + bumpersNeedToCollideWithMap.GetHashCode(); + hashCode = hashCode * -1521134295 + needsRopeTexture.GetHashCode(); + hashCode = hashCode * -1521134295 + StructuralComparisons.StructuralEqualityComparer.GetHashCode(requiredExtras); + hashCode = hashCode * -1521134295 + StructuralComparisons.StructuralEqualityComparer.GetHashCode(rewards); + hashCode = hashCode * -1521134295 + StructuralComparisons.StructuralEqualityComparer.GetHashCode(cinematicPartCamera); + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(NmBraceOverrideSet); + hashCode = hashCode * -1521134295 + buoyancySphereOffset.GetHashCode(); + hashCode = hashCode * -1521134295 + buoyancySphereSizeScale.GetHashCode(); + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(pOverrideRagdollThreshold); + hashCode = hashCode * -1521134295 + StructuralComparisons.StructuralEqualityComparer.GetHashCode(firstPersonDrivebyData); + return hashCode; + } + + public static bool operator ==(VehicleInitData left, VehicleInitData right) + { + return EqualityComparer.Default.Equals(left, right); + } + + public static bool operator !=(VehicleInitData left, VehicleInitData right) + { + return !(left == right); + } } - public class VehicleOverrideRagdollThreshold + public class VehicleOverrideRagdollThreshold : IEquatable { public int MinComponent { get; set; } public int MaxComponent { get; set; } public float ThresholdMult { get; set; } + public override bool Equals(object obj) + { + return obj is VehicleOverrideRagdollThreshold threshold && Equals(threshold); + } + + public bool Equals(VehicleOverrideRagdollThreshold other) + { + return MinComponent == other.MinComponent && + MaxComponent == other.MaxComponent && + ThresholdMult == other.ThresholdMult; + } + + public override int GetHashCode() + { + int hashCode = 1172526364; + hashCode = hashCode * -1521134295 + MinComponent.GetHashCode(); + hashCode = hashCode * -1521134295 + MaxComponent.GetHashCode(); + hashCode = hashCode * -1521134295 + ThresholdMult.GetHashCode(); + return hashCode; + } + public override string ToString() { return MinComponent.ToString() + ", " + MaxComponent.ToString() + ", " + ThresholdMult.ToString(); } + + public static bool operator ==(VehicleOverrideRagdollThreshold left, VehicleOverrideRagdollThreshold right) + { + return left.Equals(right); + } + + public static bool operator !=(VehicleOverrideRagdollThreshold left, VehicleOverrideRagdollThreshold right) + { + return !(left == right); + } } - public class VehicleDriver + public class VehicleDriver : IEquatable { public string driverName { get; set; } public string npcName { get; set; } + public override bool Equals(object obj) + { + return obj is VehicleDriver driver && Equals(driver); + } + + public bool Equals(VehicleDriver other) + { + return driverName == other.driverName && + npcName == other.npcName; + } + + public override int GetHashCode() + { + int hashCode = -1906737521; + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(driverName); + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(npcName); + return hashCode; + } + public override string ToString() { return driverName + ", " + npcName; } + + public static bool operator ==(VehicleDriver left, VehicleDriver right) + { + return left.Equals(right); + } + + public static bool operator !=(VehicleDriver left, VehicleDriver right) + { + return !(left == right); + } } } diff --git a/CodeWalker.Core/GameFiles/FileTypes/YddFile.cs b/CodeWalker.Core/GameFiles/FileTypes/YddFile.cs index 0ed54f0..8acbfcf 100644 --- a/CodeWalker.Core/GameFiles/FileTypes/YddFile.cs +++ b/CodeWalker.Core/GameFiles/FileTypes/YddFile.cs @@ -75,20 +75,17 @@ namespace CodeWalker.GameFiles var drawable = drawables[i]; var hash = hashes[i]; drawable.Hash = hash; - if ((drawable.Name == null) || (drawable.Name.EndsWith("#dd"))) + if (drawable.Name == null || drawable.Name.EndsWith("#dd", StringComparison.OrdinalIgnoreCase)) { string hstr = JenkIndex.TryGetString(hash); if (!string.IsNullOrEmpty(hstr)) { drawable.Name = hstr; } - else - { } } } Drawables = Dict.Values.ToArray(); - } Loaded = true; diff --git a/CodeWalker.Core/GameFiles/FileTypes/YmapFile.cs b/CodeWalker.Core/GameFiles/FileTypes/YmapFile.cs index fc99f3c..ef5fe03 100644 --- a/CodeWalker.Core/GameFiles/FileTypes/YmapFile.cs +++ b/CodeWalker.Core/GameFiles/FileTypes/YmapFile.cs @@ -862,6 +862,7 @@ namespace CodeWalker.GameFiles ChildYmaps[i] = gfc.GetYmap(chash); if (ChildYmaps[i] == null) { + Console.WriteLine($"Couldn't find child ymap! {chash} for {Name}"); //couldn't find child ymap.. } } @@ -895,7 +896,10 @@ namespace CodeWalker.GameFiles for (int i = 0; i < ChildYmaps.Length; i++) { var cmap = ChildYmaps[i]; - if (cmap == null) continue; //nothing here.. + if (cmap == null) + { + continue; //nothing here.. + } //cmap.EnsureChildYmaps(); if ((cmap.Loaded) && (!cmap.MergedWithParent)) { @@ -956,7 +960,7 @@ namespace CodeWalker.GameFiles YmapEntityDef p = null; if ((pymap != null) && (pymap.AllEntities != null)) { - if ((pind < pymap.AllEntities.Length)) + if (pind < pymap.AllEntities.Length) { p = pymap.AllEntities[pind]; ent.Parent = p; @@ -964,7 +968,9 @@ namespace CodeWalker.GameFiles } } else - { }//should only happen if parent ymap not loaded yet... + { + Console.WriteLine($"Parent not loaded yet for {pymap.Name}"); + } } } } @@ -974,8 +980,6 @@ namespace CodeWalker.GameFiles { LODLights.Init(Parent.DistantLODLights); } - else - { } } } @@ -987,8 +991,11 @@ namespace CodeWalker.GameFiles { //used by the editor to add to the ymap. - List allents = new List(); - if (AllEntities != null) allents.AddRange(AllEntities); + List allents; + if (AllEntities != null) + allents = new List(AllEntities); + else + allents = new List(); ent.Index = allents.Count; ent.Ymap = this; allents.Add(ent); @@ -999,8 +1006,11 @@ namespace CodeWalker.GameFiles { //root entity, add to roots. - List rootents = new List(); - if (RootEntities != null) rootents.AddRange(RootEntities); + List rootents; + if (RootEntities != null) + rootents = new List(RootEntities); + else + rootents = new List(); rootents.Add(ent); RootEntities = rootents.ToArray(); } @@ -1012,7 +1022,8 @@ namespace CodeWalker.GameFiles public bool RemoveEntity(YmapEntityDef ent) { //used by the editor to remove from the ymap. - if (ent == null) return false; + if (ent == null) + return false; var res = true; @@ -1020,23 +1031,32 @@ namespace CodeWalker.GameFiles List newAllEntities = new List(); List newRootEntities = new List(); - for (int i = 0; i < AllEntities.Length; i++) + if (AllEntities != null) { - var oent = AllEntities[i]; - oent.Index = newAllEntities.Count; - if (oent != ent) newAllEntities.Add(oent); - else if (i != idx) + for (int i = 0; i < AllEntities.Length; i++) { - res = false; //indexes didn't match.. this shouldn't happen! + var oent = AllEntities[i]; + oent.Index = newAllEntities.Count; + if (oent != ent) + { + newAllEntities.Add(oent); + } + else if (i != idx) + { + res = false; //indexes didn't match.. this shouldn't happen! + } } } - for (int i = 0; i < RootEntities.Length; i++) + if (RootEntities != null) { - var oent = RootEntities[i]; - if (oent != ent) newRootEntities.Add(oent); + for (int i = 0; i < RootEntities.Length; i++) + { + var oent = RootEntities[i]; + if (oent != ent) newRootEntities.Add(oent); + } } - if ((AllEntities.Length == newAllEntities.Count) || (RootEntities.Length == newRootEntities.Count)) + if (AllEntities == null || AllEntities.Length == newAllEntities.Count || RootEntities == null || RootEntities.Length == newRootEntities.Count) { res = false; } @@ -1055,7 +1075,8 @@ namespace CodeWalker.GameFiles public void AddCarGen(YmapCarGen cargen) { List cargens = new List(); - if (CarGenerators != null) cargens.AddRange(CarGenerators); + if (CarGenerators != null) + cargens.AddRange(CarGenerators); cargen.Ymap = this; cargens.Add(cargen); CarGenerators = cargens.ToArray(); @@ -1334,10 +1355,9 @@ namespace CodeWalker.GameFiles public void SetName(string newname) { - var newnamel = newname.ToLowerInvariant(); var newnamex = newname + ".ymap"; - var newhash = JenkHash.GenHash(newnamel); - JenkIndex.Ensure(newnamel); + var newhash = JenkHash.GenHashLower(newname); + JenkIndex.EnsureLower(newname); if (RpfFileEntry != null) { RpfFileEntry.Name = newnamex; @@ -1692,7 +1712,7 @@ namespace CodeWalker.GameFiles public int Index { get; set; } public float Distance { get; set; } //used for rendering - public bool IsVisible; //used for rendering + public bool IsWithinLodDist; //used for rendering public bool ChildrenVisible; //used for rendering public bool ChildrenRendered; //used when rendering ymap mode to reduce LOD flashing... public YmapEntityDef Parent { get; set; } //for browsing convenience, also used/updated for rendering diff --git a/CodeWalker.Core/GameFiles/FileTypes/YndFile.cs b/CodeWalker.Core/GameFiles/FileTypes/YndFile.cs index d86fecc..24b924d 100644 --- a/CodeWalker.Core/GameFiles/FileTypes/YndFile.cs +++ b/CodeWalker.Core/GameFiles/FileTypes/YndFile.cs @@ -1890,7 +1890,7 @@ namespace CodeWalker.GameFiles { return 0; } - if (str.StartsWith("hash_")) + if (str.StartsWith("hash_", StringComparison.OrdinalIgnoreCase)) { return Convert.ToUInt32(str.Substring(5), 16); } diff --git a/CodeWalker.Core/GameFiles/FileTypes/YtypFile.cs b/CodeWalker.Core/GameFiles/FileTypes/YtypFile.cs index 6d22181..ed79a47 100644 --- a/CodeWalker.Core/GameFiles/FileTypes/YtypFile.cs +++ b/CodeWalker.Core/GameFiles/FileTypes/YtypFile.cs @@ -288,16 +288,16 @@ namespace CodeWalker.GameFiles { } NameHash = _CMapTypes.name; - if ((NameHash == 0) && (entry.NameLower != null)) + if ((NameHash == 0) && (entry.Name != null)) { - int ind = entry.NameLower.LastIndexOf('.'); + int ind = entry.Name.LastIndexOf('.'); if (ind > 0) { - NameHash = JenkHash.GenHash(entry.NameLower.Substring(0, ind)); + NameHash = JenkHash.GenHashLower(entry.Name.AsSpan(0, ind)); } else { - NameHash = JenkHash.GenHash(entry.NameLower); + NameHash = JenkHash.GenHashLower(entry.Name); } } diff --git a/CodeWalker.Core/GameFiles/GameFile.cs b/CodeWalker.Core/GameFiles/GameFile.cs index a61f02b..aa115a1 100644 --- a/CodeWalker.Core/GameFiles/GameFile.cs +++ b/CodeWalker.Core/GameFiles/GameFile.cs @@ -1,4 +1,4 @@ -using System; + using System; using System.Collections.Generic; using System.Linq; using System.Text; @@ -10,6 +10,7 @@ namespace CodeWalker.GameFiles { public volatile bool Loaded = false; public volatile bool LoadQueued = false; + public DateTime LastLoadTime = DateTime.MinValue; public RpfFileEntry RpfFileEntry { get; set; } public string Name { get; set; } public string FilePath { get; set; } //used by the project form. @@ -91,7 +92,7 @@ namespace CodeWalker.GameFiles - public struct GameFileCacheKey + public struct GameFileCacheKey : IEquatable { public uint Hash { get; set; } public GameFileType Type { get; set; } @@ -102,13 +103,22 @@ namespace CodeWalker.GameFiles Type = type; } - public override bool Equals(object obj) + public override readonly bool Equals(object obj) { - if (obj == null) return false; - if (obj is not GameFileCacheKey gameFileCacheKey) return false; + if (obj == null) + return false; + if (obj is not GameFileCacheKey gameFileCacheKey) + return false; return gameFileCacheKey.Hash == Hash && gameFileCacheKey.Type == Type; } + public readonly bool Equals(GameFileCacheKey obj) + { + if (obj == null) + return false; + return obj.Hash == Hash && obj.Type == Type; + } + public static bool operator ==(GameFileCacheKey first, GameFileCacheKey second) { return first.Equals(second); @@ -119,12 +129,9 @@ namespace CodeWalker.GameFiles return !first.Equals(second); } - public override int GetHashCode() + public override readonly int GetHashCode() { return (int)Hash; } } - - - } diff --git a/CodeWalker.Core/GameFiles/GameFileCache.cs b/CodeWalker.Core/GameFiles/GameFileCache.cs index ea5fc4f..8e3ee66 100644 --- a/CodeWalker.Core/GameFiles/GameFileCache.cs +++ b/CodeWalker.Core/GameFiles/GameFileCache.cs @@ -1,9 +1,10 @@ using CodeWalker.Core.Utils; +using CodeWalker.World; +using Dasync.Collections; using SharpDX; using System; using System.Collections.Concurrent; using System.Collections.Generic; -using System.Diagnostics; using System.IO; using System.Linq; using System.Text; @@ -34,10 +35,10 @@ namespace CodeWalker.GameFiles private object updateSyncRoot = new object(); private object requestSyncRoot = new object(); - + private ReaderWriterLockSlim archetypeLock = new ReaderWriterLockSlim(); private ConcurrentDictionary projectFiles = new ConcurrentDictionary(); //for cache files loaded in project window: ydr,ydd,ytd,yft - private ConcurrentDictionary projectArchetypes = new ConcurrentDictionary(); //used to override archetypes in world view with project ones + private Dictionary projectArchetypes = new Dictionary(); //used to override archetypes in world view with project ones @@ -59,7 +60,7 @@ namespace CodeWalker.GameFiles //static cached data loaded at init - public ConcurrentDictionary YtypDict { get; set; } + public Dictionary YtypDict { get; set; } public List AllCacheFiles { get; set; } public Dictionary YmapHierarchyDict { get; set; } @@ -113,6 +114,10 @@ namespace CodeWalker.GameFiles public bool LoadVehicles = true; public bool LoadPeds = true; public bool LoadAudio = true; + + public bool PedsLoaded = false; + public bool VehiclesLoaded = false; + private bool PreloadedMode = true; private string GTAFolder; @@ -142,6 +147,14 @@ namespace CodeWalker.GameFiles } } + public long MaxMemoryUsage + { + get + { + return mainCache.MaxMemoryUsage; + } + } + public GameFileCache(long size, double cacheTime, string folder, string dlc, bool mods, string excludeFolders) @@ -174,113 +187,146 @@ namespace CodeWalker.GameFiles GTAFolder = folder; } - public void Init(Action updateStatus = null, Action errorLog = null) + public volatile bool IsIniting = false; + public void Init(Action updateStatus = null, Action errorLog = null, bool force = true) { - using var _ = new DisposableTimer("GameFileCache.Init"); - if (updateStatus != null) UpdateStatus += updateStatus; - if (errorLog != null) ErrorLog += errorLog; - - Clear(); - - - if (RpfMan == null) + if (IsIniting) { - //EnableDlc = !string.IsNullOrEmpty(SelectedDlc); - - - - RpfMan = RpfManager.GetInstance(); - RpfMan.ExcludePaths = GetExcludePaths(); - RpfMan.EnableMods = EnableMods; - RpfMan.BuildExtendedJenkIndex = BuildExtendedJenkIndex; - RpfMan.Init(GTAFolder, UpdateStatus, ErrorLog);//, true); - - - InitGlobal(); - - InitDlc(); - - - - //RE test area! - //TestAudioRels(); - //TestAudioYmts(); - //TestAudioAwcs(); - //TestMetas(); - //TestPsos(); - //TestRbfs(); - //TestCuts(); - //TestYlds(); - //TestYeds(); - //TestYcds(); - //TestYtds(); - //TestYbns(); - //TestYdrs(); - //TestYdds(); - //TestYfts(); - //TestYpts(); - //TestYnvs(); - //TestYvrs(); - //TestYwrs(); - //TestYmaps(); - //TestYpdbs(); - //TestYfds(); - //TestMrfs(); - //TestFxcs(); - //TestPlacements(); - //TestDrawables(); - //TestCacheFiles(); - //TestHeightmaps(); - //TestWatermaps(); - //GetShadersXml(); - //GetArchetypeTimesList(); - //string typestr = PsoTypes.GetTypesString(); + return; } - else + IsIniting = true; + try { - GC.Collect(); //try free up some of the previously used memory.. + using var _ = new DisposableTimer("GameFileCache.Init"); + if (updateStatus != null) UpdateStatus += updateStatus; + if (errorLog != null) ErrorLog += errorLog; + + + if (RpfMan == null) + { + //EnableDlc = !string.IsNullOrEmpty(SelectedDlc); + + RpfMan = RpfManager.GetInstance(); + RpfMan.ExcludePaths = GetExcludePaths(); + RpfMan.EnableMods = EnableMods; + RpfMan.BuildExtendedJenkIndex = BuildExtendedJenkIndex; + RpfMan.Init(GTAFolder, UpdateStatus, ErrorLog);//, true); + + + InitGlobal(); + + InitDlc(); + + + + + //RE test area! + //TestAudioRels(); + //TestAudioYmts(); + //TestAudioAwcs(); + //TestMetas(); + //TestPsos(); + //TestRbfs(); + //TestCuts(); + //TestYlds(); + //TestYeds(); + //TestYcds(); + //TestYtds(); + //TestYbns(); + //TestYdrs(); + //TestYdds(); + //TestYfts(); + //TestYpts(); + //TestYnvs(); + //TestYvrs(); + //TestYwrs(); + //TestYmaps(); + //TestYpdbs(); + //TestYfds(); + //TestMrfs(); + //TestFxcs(); + //TestPlacements(); + //TestDrawables(); + //TestCacheFiles(); + //TestHeightmaps(); + //TestWatermaps(); + //GetShadersXml(); + //GetArchetypeTimesList(); + //string typestr = PsoTypes.GetTypesString(); + } + else + { + //GC.Collect(); //try free up some of the previously used memory.. + } + + UpdateStatus?.Invoke("Scan complete"); + + + IsInited = true; + } + catch(Exception ex) + { + Console.WriteLine(ex); + throw; + } + finally + { + IsIniting = false; } - - UpdateStatus?.Invoke("Scan complete"); - - - IsInited = true; } - public void Init(Action updateStatus, Action errorLog, List allRpfs) + public void Init(Action updateStatus, Action errorLog, List allRpfs, bool force = true) { - using var _ = new DisposableTimer("GameFileCache.Init"); - if (updateStatus != null) + if (IsIniting) { - UpdateStatus += updateStatus; + return; } - if (errorLog != null) + IsIniting = true; + try { - ErrorLog += errorLog; + using var _ = new DisposableTimer("GameFileCache.Init"); + if (updateStatus != null) + { + UpdateStatus += updateStatus; + } + if (errorLog != null) + { + ErrorLog += errorLog; + } + Clear(); + + PreloadedMode = true; + EnableDlc = true;//just so everything (mainly archetypes) will load.. + EnableMods = false; + RpfMan ??= RpfManager.GetInstance(); + RpfMan.Init(allRpfs); + + + AllRpfs = allRpfs; + BaseRpfs = allRpfs; + DlcRpfs = new List(); + + + Task.WhenAll( + Task.Run(() => InitGlobalDicts(force)), + Task.Run(() => InitManifestDicts(force)), + Task.Run(() => InitGtxds(force)), + Task.Run(() => InitArchetypeDicts(force)), + Task.Run(() => InitStringDictsAsync(force)), + Task.Run(() => InitAudio(force)) + ).GetAwaiter().GetResult(); + + vehicleFiles.Clear(); + + IsInited = true; + } + catch (Exception ex) { + ErrorLog?.Invoke(ex.ToString()); + throw; + } + finally + { + IsIniting = false; } - - Clear(); - - PreloadedMode = true; - EnableDlc = true;//just so everything (mainly archetypes) will load.. - EnableMods = false; - RpfMan ??= RpfManager.GetInstance(); - RpfMan.Init(allRpfs); - - - AllRpfs = allRpfs; - BaseRpfs = allRpfs; - DlcRpfs = new List(); - - Task.WhenAll( - Task.Run(InitGlobalDicts), - Task.Run(InitManifestDicts), - Task.Run(InitGtxds), - Task.Run(InitArchetypeDicts), - Task.Run(InitStringDicts), - Task.Run(InitAudio) - ).GetAwaiter().GetResult(); - - IsInited = true; } private void InitGlobal() @@ -293,23 +339,27 @@ namespace CodeWalker.GameFiles InitGlobalDicts(); } - private void InitDlc() + private void InitDlc(bool force = true) { InitDlcList(); InitActiveMapRpfFiles(); Task.WhenAll( - Task.Run(InitMapDicts), - Task.Run(InitManifestDicts), - Task.Run(InitGtxds), - Task.Run(InitMapCaches), - Task.Run(InitArchetypeDicts), - Task.Run(InitStringDicts), - Task.Run(InitVehicles), - Task.Run(InitPeds), - Task.Run(InitAudio) + Task.Run(() => { + InitMapDicts(force); + InitMapCaches(force); + }), + Task.Run(() => InitManifestDicts(force)), + Task.Run(() => InitArchetypeDicts(force)), + Task.Run(() => InitStringDictsAsync(force)), + Task.Run(() => InitVehicles(force)), + Task.Run(() => InitPeds(force)), + Task.Run(() => InitAudio(force)), + Task.Run(() => InitGtxds(force)) ).GetAwaiter().GetResult(); + + vehicleFiles.Clear(); } private void InitDlcList() @@ -343,11 +393,12 @@ namespace CodeWalker.GameFiles //get dlc path names in the appropriate format for reference by the dlclist paths - Dictionary dlcDict = new Dictionary(StringComparer.OrdinalIgnoreCase); - Dictionary dlcDict2 = new Dictionary(StringComparer.OrdinalIgnoreCase); + Dictionary dlcDict = new Dictionary(80, StringComparer.OrdinalIgnoreCase); + Dictionary dlcDict2 = new Dictionary(80, StringComparer.OrdinalIgnoreCase); foreach (RpfFile dlcrpf in DlcRpfs) { - if (dlcrpf == null) continue; + if (dlcrpf == null) + continue; if (dlcrpf.Name.Equals("dlc.rpf", StringComparison.OrdinalIgnoreCase)) { string path = GetDlcRpfVirtualPath(dlcrpf.Path); @@ -359,7 +410,6 @@ namespace CodeWalker.GameFiles - //find all the paths for patched files in update.rpf and build the dict DlcPatchedPaths.Clear(); string updrpfpath = "update\\update.rpf"; @@ -547,7 +597,7 @@ namespace CodeWalker.GameFiles foreach (var rpf in DlcRpfs) { - if (rpf.NameLower == "update.rpf")//include this so that files not in child rpf's can be used.. + if (rpf.Name.Equals("update.rpf", StringComparison.OrdinalIgnoreCase))//include this so that files not in child rpf's can be used.. { string path = rpf.Path.Replace('\\', '/'); ActiveMapRpfFiles[path] = rpf; @@ -868,7 +918,7 @@ namespace CodeWalker.GameFiles { if ((fm.platform == null) || (fm.platform == "x64")) { - if (path.StartsWith(fm.path)) + if (path.StartsWith(fm.path, StringComparison.OrdinalIgnoreCase)) { path = path.Replace(fm.path, fm.mountAs); } @@ -885,7 +935,7 @@ namespace CodeWalker.GameFiles { if ((fm.platform == null) || (fm.platform == "x64")) { - if (path.StartsWith(fm.mountAs)) + if (path.StartsWith(fm.mountAs, StringComparison.OrdinalIgnoreCase)) { path = path.Replace(fm.mountAs, fm.path); } @@ -913,7 +963,7 @@ namespace CodeWalker.GameFiles { foreach (var file in list) { - if (!file.Path.StartsWith("mods")) + if (!file.Path.StartsWith("mods", StringComparison.OrdinalIgnoreCase)) { rlist.Add(file); } @@ -929,7 +979,7 @@ namespace CodeWalker.GameFiles } else { - if (file.Path.StartsWith("mods")) + if (file.Path.StartsWith("mods", StringComparison.OrdinalIgnoreCase)) { var basepath = file.Path.Substring(5); if (!RpfMan.RpfDict.ContainsKey(basepath)) //this file isn't overriding anything @@ -948,45 +998,44 @@ namespace CodeWalker.GameFiles } - private void InitGlobalDicts() + private void InitGlobalDicts(bool force = false) { UpdateStatus?.Invoke("Building global dictionaries..."); using var timer = new DisposableTimer("InitGlobalDicts"); - YdrDict = new Dictionary(80000); - YddDict = new Dictionary(11000); - YtdDict = new Dictionary(40000); - YftDict = new Dictionary(40000); - YcdDict = new Dictionary(20000); - YedDict = new Dictionary(300); + YdrDict ??= new Dictionary(80000); + YddDict ??= new Dictionary(11000); + YtdDict ??= new Dictionary(40000); + YftDict ??= new Dictionary(40000); + YcdDict ??= new Dictionary(20000); + YedDict ??= new Dictionary(300); foreach (var rpffile in AllRpfs) { if (rpffile.AllEntries == null) continue; foreach (var entry in rpffile.AllEntries) { - if (entry is RpfFileEntry) + if (entry is RpfFileEntry fentry) { - RpfFileEntry fentry = entry as RpfFileEntry; - if (entry.NameLower.EndsWith(".ydr")) + if (entry.IsExtension(".ydr")) { YdrDict[entry.ShortNameHash] = fentry; } - else if (entry.NameLower.EndsWith(".ydd")) + else if (entry.IsExtension(".ydd")) { YddDict[entry.ShortNameHash] = fentry; } - else if (entry.NameLower.EndsWith(".ytd")) + else if (entry.IsExtension(".ytd")) { YtdDict[entry.ShortNameHash] = fentry; } - else if (entry.NameLower.EndsWith(".yft")) + else if (entry.IsExtension(".yft")) { YftDict[entry.ShortNameHash] = fentry; } - else if (entry.NameLower.EndsWith(".ycd")) + else if (entry.IsExtension(".ycd")) { YcdDict[entry.ShortNameHash] = fentry; } - else if (entry.NameLower.EndsWith(".yed")) + else if (entry.IsExtension(".yed")) { YedDict[entry.ShortNameHash] = fentry; } @@ -996,32 +1045,33 @@ namespace CodeWalker.GameFiles Console.WriteLine($"YdrDict: {YdrDict.Count}; YddDict: {YddDict.Count}; YtdDict: {YtdDict.Count}; YftDict: {YftDict.Count}; YcdDict: {YcdDict.Count}; YedDict: {YedDict.Count}"); } - private void InitMapDicts() + private void InitMapDicts(bool force = true) { UpdateStatus?.Invoke("Building map dictionaries..."); using var _ = new DisposableTimer("InitMapDicts"); - YmapDict = new Dictionary(); - YbnDict = new Dictionary(); - YnvDict = new Dictionary(); + YmapDict ??= new Dictionary(4600); + YbnDict ??= new Dictionary(8800); + YnvDict ??= new Dictionary(4500); + AllYmapsDict = new Dictionary(11000); foreach (var rpffile in ActiveMapRpfFiles.Values) //RpfMan.BaseRpfs) { if (rpffile.AllEntries == null) continue; foreach (var entry in rpffile.AllEntries) { - if (entry is RpfFileEntry) + if (entry is RpfFileEntry fentry) { - RpfFileEntry fentry = entry as RpfFileEntry; - if (entry.NameLower.EndsWith(".ymap")) + if (entry.IsExtension(".ymap")) { //YmapDict[entry.NameHash] = fentry; YmapDict[entry.ShortNameHash] = fentry; + AllYmapsDict[entry.ShortNameHash] = fentry; } - else if (entry.NameLower.EndsWith(".ybn")) + else if (entry.IsExtension(".ybn")) { //YbnDict[entry.NameHash] = fentry; YbnDict[entry.ShortNameHash] = fentry; } - else if (entry.NameLower.EndsWith(".ynv")) + else if (entry.IsExtension(".ynv")) { YnvDict[entry.ShortNameHash] = fentry; } @@ -1029,31 +1079,15 @@ namespace CodeWalker.GameFiles } } - AllYmapsDict = new Dictionary(); - foreach (var rpffile in AllRpfs) - { - if (rpffile.AllEntries == null) continue; - foreach (var entry in rpffile.AllEntries) - { - if (entry is RpfFileEntry) - { - RpfFileEntry fentry = entry as RpfFileEntry; - if (entry.NameLower.EndsWith(".ymap")) - { - AllYmapsDict[entry.ShortNameHash] = fentry; - } - } - } - } - + Console.WriteLine($"YmapDict: {YmapDict.Count}; YbnDict: {YbnDict.Count}; YnvDict: {YnvDict.Count}; AllYmapsDict: {AllYmapsDict.Count};"); } - private void InitManifestDicts() + private void InitManifestDicts(bool force = true) { UpdateStatus?.Invoke("Loading manifests..."); using var _ = new DisposableTimer("InitManifestDicts"); - AllManifests = new List(); - hdtexturelookup = new Dictionary(); + AllManifests = new List(2000); + hdtexturelookup = new Dictionary(24000); IEnumerable rpfs = PreloadedMode ? AllRpfs : ActiveMapRpfFiles.Values; foreach (RpfFile file in rpfs) { @@ -1061,130 +1095,101 @@ namespace CodeWalker.GameFiles //manifest and meta parsing.. foreach (RpfEntry entry in file.AllEntries) { - //sp_manifest.ymt - //if (entry.NameLower.EndsWith("zonebind.ymt")/* || entry.Name.EndsWith("vinewood.ymt")*/) - //{ - // YmtFile ymt = GetFile(entry); - //} - if (entry.Name.EndsWith(".ymf"))// || entry.Name.EndsWith(".ymt")) + if (!entry.IsExtension(".ymf")) { - try + continue; + } + try + { + UpdateStatus?.Invoke(string.Format(entry.Path)); + YmfFile ymffile = RpfMan.GetFile(entry); + if (ymffile != null) { - UpdateStatus?.Invoke(string.Format(entry.Path)); - YmfFile ymffile = RpfMan.GetFile(entry); - if (ymffile != null) + AllManifests.Add(ymffile); + + if (ymffile.HDTxdAssetBindings != null) { - AllManifests.Add(ymffile); - - if (ymffile.Pso != null) - { } - else if (ymffile.Rbf != null) - { } - else if (ymffile.Meta != null) - { } - else - { } - - - if (ymffile.HDTxdAssetBindings != null) + for (int i = 0; i < ymffile.HDTxdAssetBindings.Length; i++) { - for (int i = 0; i < ymffile.HDTxdAssetBindings.Length; i++) - { - var b = ymffile.HDTxdAssetBindings[i]; - var targetasset = JenkHash.GenHash(b.targetAsset.ToString().ToLowerInvariant()); - var hdtxd = JenkHash.GenHash(b.HDTxd.ToString().ToLowerInvariant()); - hdtexturelookup[targetasset] = hdtxd; - } + var b = ymffile.HDTxdAssetBindings[i]; + var targetasset = JenkHash.GenHashLower(b.targetAsset.ToString()); + var hdtxd = JenkHash.GenHashLower(b.HDTxd.ToString()); + hdtexturelookup[targetasset] = hdtxd; } - } - } - catch (Exception ex) - { - string errstr = entry.Path + "\n" + ex.ToString(); - ErrorLog(errstr); + } } - + catch (Exception ex) + { + string errstr = entry.Path + "\n" + ex.ToString(); + ErrorLog?.Invoke(errstr); + Console.WriteLine(errstr); + } } - } + + Console.WriteLine($"hdtexturelookup: {hdtexturelookup.Count}; AllManifests: {AllManifests.Count};"); } - private void InitGtxds() + private static ConcurrentDictionary vehicleFiles = new(4, 72, StringComparer.OrdinalIgnoreCase); + private async Task InitGtxds(bool force = true) { UpdateStatus?.Invoke("Loading global texture list..."); using var timer = new DisposableTimer("InitGtxds"); - var parentTxds = new Dictionary(); + var parentTxds = new ConcurrentDictionary(4, 4000); IEnumerable rpfs = PreloadedMode ? AllRpfs : (IEnumerable)ActiveMapRpfFiles.Values; - var addTxdRelationships = new Action>((from) => - { + static void addTxdRelationships(Dictionary from, ConcurrentDictionary parentTxds) { foreach (var kvp in from) { - uint chash = JenkHash.GenHash(kvp.Key.ToLowerInvariant()); - uint phash = JenkHash.GenHash(kvp.Value.ToLowerInvariant()); - if (!parentTxds.ContainsKey(chash)) - { - parentTxds.Add(chash, phash); - } - else - { - } + uint chash = JenkHash.GenHashLower(kvp.Key); + uint phash = JenkHash.GenHashLower(kvp.Value); + + parentTxds.TryAdd(chash, phash); } - }); - - var addRpfTxdRelationships = new Action>((from) => - { - foreach (RpfFile file in from) - { - if (file.AllEntries == null) continue; - foreach (RpfEntry entry in file.AllEntries) - { - try - { - if ((entry.NameLower == "gtxd.ymt") || (entry.NameLower == "gtxd.meta") || (entry.NameLower == "mph4_gtxd.ymt")) - { - GtxdFile ymt = RpfMan.GetFile(entry); - if (ymt.TxdRelationships != null) - { - addTxdRelationships(ymt.TxdRelationships); - } - } - else if (entry.NameLower == "vehicles.meta") - { - VehiclesFile vf = RpfMan.GetFile(entry);//could also get loaded in InitVehicles... - if (vf.TxdRelationships != null) - { - addTxdRelationships(vf.TxdRelationships); - } - } - } - catch (Exception ex) - { - string errstr = entry.Path + "\n" + ex.ToString(); - ErrorLog(errstr); - } - } - } - - }); - - - addRpfTxdRelationships(rpfs); - - - if (EnableDlc) - { - addRpfTxdRelationships(DlcActiveRpfs); } - - textureParents = parentTxds; + IEnumerable allRpfs = rpfs; + if (EnableDlc && DlcActiveRpfs.Count > 0) + { + allRpfs = allRpfs.Concat(DlcActiveRpfs); + } + await allRpfs.Where(p => p.AllEntries != null).ParallelForEachAsync(async (file) => + { + foreach (RpfEntry entry in file.AllEntries) + { + if ( + entry.Name.Equals("gtxd.ymt", StringComparison.OrdinalIgnoreCase) + || entry.Name.Equals("gtxd.meta", StringComparison.OrdinalIgnoreCase) + || entry.Name.Equals("mph4_gtxd.ymt", StringComparison.OrdinalIgnoreCase) + ) + { + GtxdFile ymt = await RpfMan.GetFileAsync(entry).ConfigureAwait(false); + if (ymt.TxdRelationships != null) + { + addTxdRelationships(ymt.TxdRelationships, parentTxds); + } + } + else if (entry.Name.Equals("vehicles.meta", StringComparison.OrdinalIgnoreCase)) + { + if (!vehicleFiles.TryGetValue(entry.Path, out var vf)) + { + vf = vehicleFiles[entry.Path] = await RpfMan.GetFileAsync(entry).ConfigureAwait(false); + } + if (vf.TxdRelationships != null) + { + addTxdRelationships(vf.TxdRelationships, parentTxds); + } + } + } + }); + + textureParents = parentTxds.ToDictionary(p => p.Key, p => p.Value); //ensure resident global texture dicts: YtdFile ytd1 = new YtdFile(GetYtdEntry(JenkHash.GenHash("mapdetail"))); @@ -1196,15 +1201,15 @@ namespace CodeWalker.GameFiles AddTextureLookups(ytd2); - + Console.WriteLine($"textureParents: {textureParents.Count}"); } - private void InitMapCaches() + private void InitMapCaches(bool force = true) { UpdateStatus?.Invoke("Loading cache..."); using var _ = new DisposableTimer("InitMapCaches"); - AllCacheFiles = new List(); - YmapHierarchyDict = new Dictionary(); + AllCacheFiles = new List(1); + YmapHierarchyDict = new Dictionary(5000); CacheDatFile loadCacheFile(string path, bool finalAttempt) @@ -1222,7 +1227,9 @@ namespace CodeWalker.GameFiles YmapHierarchyDict[node.Name] = node; } else - { } //ymap not found... + { + Console.WriteLine($"Couldn't find {node.Name} in YmapDict"); + } //ymap not found... } } else if (finalAttempt) @@ -1267,60 +1274,139 @@ namespace CodeWalker.GameFiles } } - + Console.WriteLine($"AllCacheFiles: {AllCacheFiles.Count}; YmapHierarchyDict: {YmapHierarchyDict.Count};"); } - private void InitArchetypeDicts() + private ConcurrentBag ytypCache; + private async Task InitArchetypeDicts(bool force = true) { UpdateStatus?.Invoke("Loading archetypes..."); - YtypDict = new ConcurrentDictionary(); + + ytypCache = new ConcurrentBag(); archetypesLoaded = false; - archetypeDict.Clear(); - - if (!LoadArchetypes) return; - - using var timer = new DisposableTimer("InitArchetypeDicts"); - - var rpfs = EnableDlc ? AllRpfs : BaseRpfs; - - var allYtypes = rpfs - .Where(p => p.AllEntries != null && (EnableDlc || !p.Path.StartsWith("update"))) - .SelectMany(p => p.AllEntries.Where(p => p.NameLower.EndsWith(".ytyp"))); - - Parallel.ForEach(allYtypes, new ParallelOptions { MaxDegreeOfParallelism = 4 }, (entry) => + try { - try + archetypeDict.Clear(); + using var timer = new DisposableTimer("InitArchetypeDicts"); + + var rpfs = EnableDlc ? AllRpfs : BaseRpfs; + + var allYtypes = rpfs + .Where(p => p.AllEntries != null && (EnableDlc || !p.Path.StartsWith("update", StringComparison.OrdinalIgnoreCase))); + //.SelectMany(p => p.AllEntries.Where(p => p.IsExtension(".ytyp"))); + + //await allYtypes.ParallelForEachAsync(async (file) => + //{ + // foreach (var entry in file.AllEntries) { + // if (!entry.Name.EndsWith(".ytyp", StringComparison.OrdinalIgnoreCase)) continue; + // try + // { + // AddYtypToDictionary(entry); + // } + // catch (Exception ex) + // { + // ErrorLog(entry.Path + ": " + ex.ToString()); + // } + // } + //}, maxDegreeOfParallelism: 8); + + await allYtypes.ParallelForEachAsync(async (file) => { - AddYtypToDictionary(entry); - } - catch (Exception ex) + foreach (var entry in file.AllEntries) + { + if (entry.IsExtension(".ytyp")) + { + try + { + ytypCache.Add(await RpfMan.GetFileAsync(entry).ConfigureAwait(false)); + } + catch (Exception ex) + { + ErrorLog(entry.Path + ": " + ex.ToString()); + } + } + } + }).ConfigureAwait(false); + + YtypDict = new Dictionary(ytypCache.Count); + + foreach (var ytyp in ytypCache) { - ErrorLog(entry.Path + ": " + ex.ToString()); + AddYtypToDictionary(ytyp.RpfFileEntry, ytyp); } - }); + + //Parallel.ForEach(allYtypes, new ParallelOptions { MaxDegreeOfParallelism = 4 }, (file) => + //{ + // foreach(var entry in file.AllEntries) + // { + // if (entry.IsExtension(".ytyp")) + // { + // try + // { + // AddYtypToDictionary(entry); + // } + // catch (Exception ex) + // { + // ErrorLog(entry.Path + ": " + ex.ToString()); + // } + // } + // } + //}); + + //Parallel.ForEach(allYtypes, new ParallelOptions { MaxDegreeOfParallelism = 4 }, (entry) => + //{ + // try + // { + // AddYtypToDictionary(entry); + // } + // catch (Exception ex) + // { + // ErrorLog(entry.Path + ": " + ex.ToString()); + // } + //}); + } + catch(Exception ex) + { + Console.WriteLine(ex); + throw; + } archetypesLoaded = true; } - private void AddYtypToDictionary(RpfEntry entry) + private void AddYtypToDictionary(RpfEntry entry, YtypFile ytypFile) { - //UpdateStatus?.Invoke(string.Format(entry.Path)); - YtypFile ytypfile = RpfMan.GetFile(entry); - if (ytypfile == null) + if (ytypFile == null) { throw new Exception("Couldn't load ytyp file."); //couldn't load the file for some reason... shouldn't happen.. } - if (ytypfile.Meta == null) + if (ytypFile.Meta == null) { - if (ytypfile.Pso == null && ytypfile.Rbf == null) + if (ytypFile.Pso == null && ytypFile.Rbf == null) { throw new Exception("ytyp file was not in meta format."); } return; } - YtypDict[ytypfile.NameHash] = ytypfile; + + ytypCache.Add(ytypFile); + YtypDict[ytypFile.NameHash] = ytypFile; + + if ((ytypFile.AllArchetypes == null) || (ytypFile.AllArchetypes.Length == 0)) + { + ErrorLog(entry.Path + ": no archetypes found"); + } + else + { + foreach (var arch in ytypFile.AllArchetypes) + { + uint hash = arch.Hash; + if (hash == 0) continue; + archetypeDict[hash] = arch; + } + } //if (YtypDict.ContainsKey(ytypfile.NameHash)) //{ @@ -1335,19 +1421,7 @@ namespace CodeWalker.GameFiles - if ((ytypfile.AllArchetypes == null) || (ytypfile.AllArchetypes.Length == 0)) - { - ErrorLog(entry.Path + ": no archetypes found"); - } - else - { - foreach (var arch in ytypfile.AllArchetypes) - { - uint hash = arch.Hash; - if (hash == 0) continue; - archetypeDict[hash] = arch; - } - } + ////if (ytypfile.AudioEmitters != null) @@ -1375,7 +1449,7 @@ namespace CodeWalker.GameFiles } - public void InitStringDicts() + public async ValueTask InitStringDictsAsync(bool force = true) { UpdateStatus?.Invoke("Loading strings..."); using var timer = new DisposableTimer("InitStringDicts"); @@ -1383,22 +1457,22 @@ namespace CodeWalker.GameFiles string langstr2 = "americandlc.rpf"; string langstr3 = "american.rpf"; - Gxt2Dict = new Dictionary(); + Gxt2Dict = new Dictionary(1000); var gxt2files = new List(); - foreach (var rpf in AllRpfs) + foreach (var rpf in AllRpfs.Where(p => p.AllEntries != null)) { foreach (var entry in rpf.AllEntries) { if (entry is RpfFileEntry fentry) { var p = entry.Path; - if (entry.NameLower.EndsWith(".gxt2") && (p.Contains(langstr) || p.Contains(langstr2) || p.Contains(langstr3))) + if (entry.IsExtension(".gxt2") && (p.Contains(langstr, StringComparison.OrdinalIgnoreCase) || p.Contains(langstr2, StringComparison.OrdinalIgnoreCase) || p.Contains(langstr3, StringComparison.OrdinalIgnoreCase))) { Gxt2Dict[entry.ShortNameHash] = fentry; if (DoFullStringIndex) { - var gxt2 = RpfMan.GetFile(entry); + var gxt2 = await RpfMan.GetFileAsync(entry).ConfigureAwait(false); if (gxt2 != null) { for (int i = 0; i < gxt2.TextEntries.Length; i++) @@ -1440,7 +1514,7 @@ namespace CodeWalker.GameFiles { foreach (var entry in rpf.AllEntries) { - if (entry.NameLower.EndsWith("statssetup.xml")) + if (entry.Name.EndsWith("statssetup.xml", StringComparison.OrdinalIgnoreCase)) { var xml = RpfMan.GetFileXml(entry.Path); if (xml == null) @@ -1456,13 +1530,13 @@ namespace CodeWalker.GameFiles if (string.IsNullOrEmpty(statname)) { continue; } - var statnamel = statname.ToLowerInvariant(); - StatsNames.Ensure(statname); - StatsNames.Ensure(statnamel); + var statnamel = statname; + StatsNames.EnsureLower(statname); + StatsNames.EnsureLower(statnamel); - StatsNames.Ensure("sp_" + statnamel); - StatsNames.Ensure("mp0_" + statnamel); - StatsNames.Ensure("mp1_" + statnamel); + StatsNames.EnsureLower("sp_" + statnamel); + StatsNames.EnsureLower("mp0_" + statnamel); + StatsNames.EnsureLower("mp1_" + statnamel); } } @@ -1472,9 +1546,10 @@ namespace CodeWalker.GameFiles StatsNames.FullIndexBuilt = true; } - public void InitVehicles() + public void InitVehicles(bool force = true) { if (!LoadVehicles) return; + if (!force && VehiclesLoaded) return; //Neos7 @@ -1498,157 +1573,141 @@ namespace CodeWalker.GameFiles IEnumerable rpfs = PreloadedMode ? AllRpfs : (IEnumerable)ActiveMapRpfFiles.Values; - var allVehicles = new Dictionary(); - var allCarCols = new List(); - var allCarModCols = new List(); - var allCarVariations = new List(); - var allCarVariationsDict = new Dictionary(); - var allVehicleLayouts = new List(); + var allVehicles = new Dictionary(900); + //var allCarCols = new List(); + //var allCarModCols = new List(); + //var allCarVariations = new List(); + //var allCarVariationsDict = new Dictionary(); + //var allVehicleLayouts = new List(); - var addVehicleFiles = new Action>((from) => + IEnumerable allRpfs = rpfs; + + if (EnableDlc && DlcActiveRpfs.Count > 0) { - foreach (RpfFile file in from) - { - if (file.AllEntries == null) continue; - foreach (RpfEntry entry in file.AllEntries) - { -#if !DEBUG - try -#endif - { - if (entry.NameLower == "vehicles.meta") - { - VehiclesFile vf = RpfMan.GetFile(entry); - if (vf.InitDatas != null) - { - foreach (var initData in vf.InitDatas) - { - var name = initData.modelName.ToLowerInvariant(); - var hash = JenkHash.GenHash(name); - if (allVehicles.ContainsKey(hash)) - { } - allVehicles[hash] = initData; - } - } - } - if ((entry.NameLower == "carcols.ymt") || (entry.NameLower == "carcols.meta")) - { - var cf = RpfMan.GetFile(entry); - if (cf.VehicleModelInfo != null) - { } - allCarCols.Add(cf); - } - if (entry.NameLower == "carmodcols.ymt") - { - var cf = RpfMan.GetFile(entry); - if (cf.VehicleModColours != null) - { } - allCarModCols.Add(cf); - } - if ((entry.NameLower == "carvariations.ymt") || (entry.NameLower == "carvariations.meta")) - { - var cf = RpfMan.GetFile(entry); - if (cf.VehicleModelInfo?.variationData != null) - { - foreach (var variation in cf.VehicleModelInfo.variationData) - { - var name = variation.modelName.ToLowerInvariant(); - var hash = JenkHash.GenHash(name); - allCarVariationsDict[hash] = variation; - } - } - allCarVariations.Add(cf); - } - if (entry.NameLower.StartsWith("vehiclelayouts") && entry.NameLower.EndsWith(".meta")) - { - var lf = RpfMan.GetFile(entry); - if (lf.Xml != null) - { } - allVehicleLayouts.Add(lf); - } - } -#if !DEBUG - catch (Exception ex) - { - string errstr = entry.Path + "\n" + ex.ToString(); - ErrorLog(errstr); - } -#endif - } - } - - }); - - - addVehicleFiles(rpfs); - - if (EnableDlc) - { - addVehicleFiles(DlcActiveRpfs); + allRpfs = allRpfs.Concat(DlcActiveRpfs); } - Console.WriteLine($"allVehicles: {allVehicles.Count()}"); + Parallel.ForEach(allRpfs.Where(p => p.AllEntries != null).SelectMany(p => p.AllEntries), (entry) => + { + if (!entry.Name.Equals("vehicles.meta", StringComparison.OrdinalIgnoreCase)) + { + return; + } + try + { + vehicleFiles[entry.Path] = RpfMan.GetFile(entry); + //if ((entry.Name.Equals("carcols.ymt", StringComparison.OrdinalIgnoreCase)) || (entry.Name.Equals("carcols.meta", StringComparison.OrdinalIgnoreCase))) + //{ + // var cf = RpfMan.GetFile(entry); + // if (cf.VehicleModelInfo != null) + // { } + // allCarCols.Add(cf); + //} + //if (entry.Name.Equals("carmodcols.ymt", StringComparison.OrdinalIgnoreCase)) + //{ + // var cf = RpfMan.GetFile(entry); + // if (cf.VehicleModColours != null) + // { } + // allCarModCols.Add(cf); + //} + //if ((entry.Name.Equals("carvariations.ymt", StringComparison.OrdinalIgnoreCase)) || (entry.Name.Equals("carvariations.meta", StringComparison.OrdinalIgnoreCase))) + //{ + // var cf = RpfMan.GetFile(entry); + // if (cf.VehicleModelInfo?.variationData != null) + // { + // foreach (var variation in cf.VehicleModelInfo.variationData) + // { + // var name = variation.modelName.ToLowerInvariant(); + // var hash = JenkHash.GenHash(name); + // allCarVariationsDict[hash] = variation; + // } + // } + // allCarVariations.Add(cf); + //} + //if (entry.Name.StartsWith("vehiclelayouts", StringComparison.OrdinalIgnoreCase) && entry.Name.EndsWith(".meta", StringComparison.OrdinalIgnoreCase)) + //{ + // var lf = RpfMan.GetFile(entry); + // if (lf.Xml != null) + // { } + // allVehicleLayouts.Add(lf); + //} + } + catch (Exception ex) + { + string errstr = entry.Path + "\n" + ex.ToString(); + ErrorLog(errstr); + Console.WriteLine(errstr); + } + }); + + foreach(var vf in vehicleFiles.Values) + { + if (vf.InitDatas != null) + { + foreach (var initData in vf.InitDatas) + { + var name = initData.modelName; + var hash = JenkHash.GenHashLower(name); + if (!allVehicles.ContainsKey(hash)) + { + allVehicles[hash] = initData; + } + } + } + } + + Console.WriteLine($"allVehicles: {allVehicles.Count}; vehicleFiles: {vehicleFiles.Count}"); VehiclesInitDict = allVehicles; - } - public void InitPeds() + public void InitPeds(bool force = true) { if (!LoadPeds) return; + if (!force && PedsLoaded) return; UpdateStatus?.Invoke("Loading peds..."); using var _ = new DisposableTimer("InitPeds"); IEnumerable rpfs = PreloadedMode ? AllRpfs : (IEnumerable)ActiveMapRpfFiles.Values; - List dlcrpfs = new List(); + List dlcrpfs = null; if (EnableDlc) { + dlcrpfs = new List(); foreach (var rpf in DlcActiveRpfs) { dlcrpfs.Add(rpf); if (rpf.Children == null) continue; - foreach (var crpf in rpf.Children) - { - dlcrpfs.Add(crpf); - if (crpf.Children?.Count > 0) - { } - } + dlcrpfs.AddRange(rpf.Children); } } - var allPeds = new Dictionary(); - var allPedsFiles = new List(); - var allPedYmts = new Dictionary(); - var allPedDrwDicts = new Dictionary>(); - var allPedTexDicts = new Dictionary>(); - var allPedClothDicts = new Dictionary>(); + var allPeds = new ConcurrentDictionary(4, 1100); + //var allPedsFiles = new List(); + var allPedYmts = new ConcurrentDictionary(4, 1100); + var allPedDrwDicts = new ConcurrentDictionary>(4, 1100); + var allPedTexDicts = new ConcurrentDictionary>(4, 1100); + var allPedClothDicts = new ConcurrentDictionary>(4, 200); - Dictionary ensureDict(Dictionary> coll, MetaHash hash) + Dictionary ensureDict(ConcurrentDictionary> coll, MetaHash hash) { - Dictionary dict; - if (!coll.TryGetValue(hash, out dict)) - { - dict = new Dictionary(); - coll[hash] = dict; - } - return dict; + return coll.GetOrAdd(hash, (key) => new Dictionary()); } - var addPedDicts = new Action((namel, hash, dir) => + var addPedDicts = new Action((name, hash, dir) => { - Dictionary dict = null; + Dictionary pedClotsDict = null; var files = dir?.Files; if (files != null) { foreach (var file in files) { - if (file.NameLower == namel + ".yld") + if (file.Name.Equals(name + ".yld", StringComparison.OrdinalIgnoreCase)) { - dict = ensureDict(allPedClothDicts, hash); - dict[file.ShortNameHash] = file; + pedClotsDict ??= ensureDict(allPedClothDicts, hash); + pedClotsDict[file.ShortNameHash] = file; } } } @@ -1657,7 +1716,7 @@ namespace CodeWalker.GameFiles { foreach (var cdir in dir.Directories) { - if (cdir.NameLower == namel) + if (cdir.Name.Equals(name, StringComparison.OrdinalIgnoreCase)) { dir = cdir; break; @@ -1666,128 +1725,123 @@ namespace CodeWalker.GameFiles files = dir?.Files; if (files != null) { + Dictionary pedDrwDicts = null; + Dictionary pedTextDicts = null; foreach (var file in files) { - if (file?.NameLower == null) continue; - if (file.NameLower.EndsWith(".ydd")) + if (file?.Name == null) continue; + if (file.IsExtension(".ydd")) { - dict = ensureDict(allPedDrwDicts, hash); - dict[file.ShortNameHash] = file; + pedDrwDicts ??= ensureDict(allPedDrwDicts, hash); + pedDrwDicts[file.ShortNameHash] = file; } - else if (file.NameLower.EndsWith(".ytd")) + else if (file.IsExtension(".ytd")) { - dict = ensureDict(allPedTexDicts, hash); - dict[file.ShortNameHash] = file; + pedTextDicts ??= ensureDict(allPedTexDicts, hash); + pedTextDicts[file.ShortNameHash] = file; } - else if (file.NameLower.EndsWith(".yld")) + else if (file.IsExtension(".yld")) { - dict = ensureDict(allPedClothDicts, hash); - dict[file.ShortNameHash] = file; + pedClotsDict ??= ensureDict(allPedClothDicts, hash); + pedClotsDict[file.ShortNameHash] = file; } } } } }); - var addPedsFiles = new Action>((from) => + var numDlcs = dlcrpfs?.Count ?? 0; + + var allRpfs = rpfs; + + if (numDlcs > 0) { - foreach (RpfFile file in from) - { - if (file.AllEntries == null) continue; - foreach (RpfEntry entry in file.AllEntries) - { - try - { - if ((entry.NameLower == "peds.ymt") || (entry.NameLower == "peds.meta")) - { - var pf = RpfMan.GetFile(entry); - if (pf.InitDataList?.InitDatas != null) - { - foreach (var initData in pf.InitDataList.InitDatas) - { - var name = initData.Name.ToLowerInvariant(); - var hash = JenkHash.GenHash(name); - if (allPeds.ContainsKey(hash)) - { } - allPeds[hash] = initData; - } - } - allPedsFiles.Add(pf); - } - } - catch (Exception ex) - { - string errstr = entry.Path + "\n" + ex.ToString(); - ErrorLog?.Invoke(errstr); - Console.WriteLine(errstr); - } - } - } - }); - - var addPedFiles = new Action>((from) => - { - foreach (RpfFile file in from) - { - if (file.AllEntries == null) continue; - foreach (RpfEntry entry in file.AllEntries) - { - try - { - if (entry.Name.EndsWith(".ymt", StringComparison.OrdinalIgnoreCase)) - { - var testname = entry.ShortName; - var testhash = JenkHash.GenHashLower(testname); - if (allPeds.ContainsKey(testhash)) - { - var pf = RpfMan.GetFile(entry); - if (pf != null) - { - allPedYmts[testhash] = pf; - addPedDicts(testname, testhash, entry.Parent); - } - } - } - } - catch (Exception ex) - { - string errstr = entry.Path + "\n" + ex.ToString(); - ErrorLog?.Invoke(errstr); - Console.WriteLine(errstr); - } - } - } - }); - - - - addPedsFiles(rpfs); - addPedsFiles(dlcrpfs); - - addPedFiles(rpfs); - addPedFiles(dlcrpfs); - - - - PedsInitDict = allPeds; - PedVariationsDict = allPedYmts; - PedDrawableDicts = allPedDrwDicts; - PedTextureDicts = allPedTexDicts; - PedClothDicts = allPedClothDicts; - - - foreach (var kvp in PedsInitDict) - { - if (!PedVariationsDict.ContainsKey(kvp.Key)) - { }//checking we found them all! + allRpfs = rpfs.Concat(dlcrpfs); } + var allEntries = allRpfs + .Where(p => p.AllEntries != null) + .SelectMany(p => p.AllEntries) + .Where(p => p.Name.EndsWithAny(".ymt", ".meta")); + + Parallel.ForEach(allEntries, new ParallelOptions { MaxDegreeOfParallelism = 4 }, (entry) => + { + if (!entry.Name.Equals("peds.ymt", StringComparison.OrdinalIgnoreCase) && !entry.Name.Equals("peds.meta", StringComparison.OrdinalIgnoreCase)) + { + return; + } + try + { + var pf = RpfMan.GetFile(entry); + if (pf.InitDataList?.InitDatas != null) + { + foreach (var initData in pf.InitDataList.InitDatas) + { + var name = initData.Name; + var hash = JenkHash.GenHashLower(name); + allPeds.TryAdd(hash, initData); + } + } + //allPedsFiles.Add(pf); + } + catch (Exception ex) + { + string errstr = entry.Path + "\n" + ex.ToString(); + ErrorLog?.Invoke(errstr); + Console.WriteLine(errstr); + } + }); + + Parallel.ForEach(allEntries, new ParallelOptions { MaxDegreeOfParallelism = 4 }, (entry) => + { + if (!entry.IsExtension(".ymt")) + { + return; + } + try + { + var shortName = entry.ShortName; + var shortHash = entry.ShortNameHash; + if (allPeds.ContainsKey(shortHash)) + { + var pf = RpfMan.GetFile(entry); + if (pf != null) + { + allPedYmts.TryAdd(shortHash, pf); + addPedDicts(shortName, shortHash, entry.Parent); + } + } + } + catch (Exception ex) + { + string errstr = entry.Path + "\n" + ex.ToString(); + ErrorLog?.Invoke(errstr); + Console.WriteLine(errstr); + } + }); + + PedsInitDict = allPeds.ToDictionary(p => p.Key, p => p.Value); + PedVariationsDict = allPedYmts.ToDictionary(p => p.Key, p => p.Value); + PedDrawableDicts = allPedDrwDicts.ToDictionary(p => p.Key, p => p.Value); + PedTextureDicts = allPedTexDicts.ToDictionary(p => p.Key, p => p.Value); + PedClothDicts = allPedClothDicts.ToDictionary(p => p.Key, p => p.Value); + + + //foreach (var kvp in PedsInitDict) + //{ + // if (!PedVariationsDict.ContainsKey(kvp.Key)) + // { }//checking we found them all! + //} + } - public void InitAudio() + private bool AudioLoaded = false; + public readonly ReaderWriterLockSlim AudioDatRelFilesLock = new ReaderWriterLockSlim(); + public async ValueTask InitAudio(bool force = true) { if (!LoadAudio) return; + if (AudioLoaded && !force) return; UpdateStatus?.Invoke("Loading audio..."); using var timer = new DisposableTimer("InitAudio"); @@ -1797,10 +1851,9 @@ namespace CodeWalker.GameFiles if (rpffile.AllEntries == null) return; foreach (var entry in rpffile.AllEntries) { - if (entry is RpfFileEntry) + if (entry is RpfFileEntry fentry) { - RpfFileEntry fentry = entry as RpfFileEntry; - if (entry.NameLower.EndsWith(".rel")) + if (entry.IsExtension(".rel")) { datrelentries[entry.NameHash] = fentry; } @@ -1829,7 +1882,7 @@ namespace CodeWalker.GameFiles { foreach (var rpf in AllRpfs) //this is a bit of a hack - DLC orders won't be correct so likely will select wrong versions of things { - if (rpf.NameLower.StartsWith("dlc")) + if (rpf.Name.StartsWith("dlc", StringComparison.OrdinalIgnoreCase)) { addRpfDatRelEntries(rpf); } @@ -1837,79 +1890,99 @@ namespace CodeWalker.GameFiles } } - - var audioDatRelFiles = new List(); - var audioConfigDict = new Dictionary(); - var audioSpeechDict = new Dictionary(); - var audioSynthsDict = new Dictionary(); - var audioMixersDict = new Dictionary(); - var audioCurvesDict = new Dictionary(); - var audioCategsDict = new Dictionary(); - var audioSoundsDict = new Dictionary(); - var audioGameDict = new Dictionary(); - - - - foreach (var datrelentry in datrelentries.Values) + try { - var relfile = RpfMan.GetFile(datrelentry); - if (relfile == null) continue; + AudioDatRelFiles ??= new List(20); + AudioConfigDict ??= new Dictionary(150); + AudioSpeechDict ??= new Dictionary(90000); + AudioSynthsDict ??= new Dictionary(1500); + AudioMixersDict ??= new Dictionary(3500); + AudioCurvesDict ??= new Dictionary(1000); + AudioCategsDict ??= new Dictionary(250); + AudioSoundsDict ??= new Dictionary(60000); + AudioGameDict ??= new Dictionary(20000); - audioDatRelFiles.Add(relfile); + var audioDatRelFiles = AudioDatRelFiles; + var audioConfigDict = AudioConfigDict; + var audioSpeechDict = AudioSpeechDict; + var audioSynthsDict = AudioSynthsDict; + var audioMixersDict = AudioMixersDict; + var audioCurvesDict = AudioCurvesDict; + var audioCategsDict = AudioCategsDict; + var audioSoundsDict = AudioSoundsDict; + var audioGameDict = AudioGameDict; - var d = audioGameDict; - var t = relfile.RelType; - switch (t) + var relFiles = new ConcurrentBag(); + + await datrelentries.Values.ParallelForEachAsync(async (datrelentry) => { - case RelDatFileType.Dat4: - d = relfile.IsAudioConfig ? audioConfigDict : audioSpeechDict; - break; - case RelDatFileType.Dat10ModularSynth: - d = audioSynthsDict; - break; - case RelDatFileType.Dat15DynamicMixer: - d = audioMixersDict; - break; - case RelDatFileType.Dat16Curves: - d = audioCurvesDict; - break; - case RelDatFileType.Dat22Categories: - d = audioCategsDict; - break; - case RelDatFileType.Dat54DataEntries: - d = audioSoundsDict; - break; - case RelDatFileType.Dat149: - case RelDatFileType.Dat150: - case RelDatFileType.Dat151: - default: - d = audioGameDict; - break; - } + var relfile = await RpfMan.GetFileAsync(datrelentry).ConfigureAwait(false); + if (relfile == null) + return; - foreach (var reldata in relfile.RelDatas) + relFiles.Add(relfile); + }).ConfigureAwait(false); + + foreach (var relfile in relFiles) { - if (reldata.NameHash == 0) continue; - //if (d.TryGetValue(reldata.NameHash, out var exdata) && (exdata.TypeID != reldata.TypeID)) - //{ }//sanity check - d[reldata.NameHash] = reldata; - } + audioDatRelFiles.Add(relfile); + var d = audioGameDict; + var t = relfile.RelType; + switch (t) + { + case RelDatFileType.Dat4: + d = relfile.IsAudioConfig ? audioConfigDict : audioSpeechDict; + break; + case RelDatFileType.Dat10ModularSynth: + d = audioSynthsDict; + break; + case RelDatFileType.Dat15DynamicMixer: + d = audioMixersDict; + break; + case RelDatFileType.Dat16Curves: + d = audioCurvesDict; + break; + case RelDatFileType.Dat22Categories: + d = audioCategsDict; + break; + case RelDatFileType.Dat54DataEntries: + d = audioSoundsDict; + break; + case RelDatFileType.Dat149: + case RelDatFileType.Dat150: + case RelDatFileType.Dat151: + default: + d = audioGameDict; + break; + } + + foreach (var reldata in relfile.RelDatas) + { + if (reldata.NameHash == 0) + continue; + d[reldata.NameHash] = reldata; + } + + } + } catch(Exception e) + { + Console.WriteLine(e); + throw; } + Console.WriteLine( + $"AudioDatRelFiles: {AudioDatRelFiles.Count}; " + + $"AudioConfigDict: {AudioConfigDict.Count}; " + + $"AudioSpeechDict: {AudioSpeechDict.Count}; " + + $"AudioSynthsDict: {AudioSynthsDict.Count}; " + + $"AudioMixersDict: {AudioMixersDict.Count}; " + + $"AudioCurvesDict: {AudioCurvesDict.Count}; " + + $"AudioCategsDict: {AudioCategsDict.Count}; " + + $"AudioSoundsDict: {AudioSoundsDict.Count}; " + + $"AudioGameDict: {AudioGameDict.Count};"); - - - AudioDatRelFiles = audioDatRelFiles; - AudioConfigDict = audioConfigDict; - AudioSpeechDict = audioSpeechDict; - AudioSynthsDict = audioSynthsDict; - AudioMixersDict = audioMixersDict; - AudioCurvesDict = audioCurvesDict; - AudioCategsDict = audioCategsDict; - AudioSoundsDict = audioSoundsDict; - AudioGameDict = audioGameDict; - + AudioLoaded = true; } @@ -2011,19 +2084,36 @@ namespace CodeWalker.GameFiles public void RemoveProjectArchetype(Archetype a) { if ((a?.Hash ?? 0) == 0) return; - Archetype tarch = null; - projectArchetypes.TryGetValue(a.Hash, out tarch); - if (tarch == a) + try { - projectArchetypes.TryRemove(a.Hash, out _); + projectArchetypes.TryGetValue(a.Hash, out var tarch); + if (tarch == a) + { + projectArchetypes.Remove(a.Hash); + } } + finally + { + archetypeLock.ExitWriteLock(); + } + } public void ClearProjectArchetypes() { - projectArchetypes.Clear(); + archetypeLock.EnterWriteLock(); + try + { + projectArchetypes.Clear(); + } + finally + { + archetypeLock.ExitWriteLock(); + } + } - public void TryLoadEnqueue(GameFile gf) + private readonly SemaphoreSlim maxCacheLock = new SemaphoreSlim(Environment.ProcessorCount / 2, Environment.ProcessorCount / 2); + public async Task TryLoadEnqueue(GameFile gf) { if (gf.Loaded || gf.LoadQueued) { @@ -2032,7 +2122,27 @@ namespace CodeWalker.GameFiles if (requestQueue.Count < 20)// && (!gf.LoadQueued) { gf.LoadQueued = true; - requestQueue.Enqueue(gf); + gf.LastLoadTime = DateTime.Now; + + await Task.Run(async () => + { + await maxCacheLock.WaitAsync(); + try + { + await LoadFileAsync(gf).ConfigureAwait(false); + } + catch(Exception ex) + { + Console.WriteLine(ex); + throw ex; + } + finally + { + maxCacheLock.Release(); + } + }); + + //requestQueue.Enqueue(gf); } } @@ -2040,12 +2150,20 @@ namespace CodeWalker.GameFiles public Archetype GetArchetype(uint hash) { if (!archetypesLoaded) return null; - if (projectArchetypes.TryGetValue(hash, out var arch) && arch != null) + archetypeLock.EnterReadLock(); + try { + if (projectArchetypes.TryGetValue(hash, out var arch) && arch != null) + { + return arch; + } + archetypeDict.TryGetValue(hash, out arch); return arch; } - archetypeDict.TryGetValue(hash, out arch); - return arch; + finally + { + archetypeLock.ExitReadLock(); + } } public MapDataStoreNode GetMapNode(uint hash) { @@ -2054,10 +2172,11 @@ namespace CodeWalker.GameFiles return node; } - public YdrFile GetYdr(uint hash) + private readonly object ydrLock = new object(); + public YdrFile GetYdr(uint hash, bool immediate = false) { if (!IsInited) return null; - lock (requestSyncRoot) + lock (ydrLock) { var key = new GameFileCacheKey(hash, GameFileType.Ydr); if (projectFiles.TryGetValue(key, out GameFile pgf)) @@ -2076,8 +2195,6 @@ namespace CodeWalker.GameFiles ydr = new YdrFile(e); if (!mainCache.TryAdd(key, ydr)) { - //ErrorLog("Out of cache space - couldn't load drawable: " + JenkIndex.GetString(hash)); //too spammy... - ydr.LoadQueued = false; return null; } } @@ -2089,10 +2206,12 @@ namespace CodeWalker.GameFiles return ydr; } } + + private readonly object yddLock = new object(); public YddFile GetYdd(uint hash) { if (!IsInited) return null; - lock (requestSyncRoot) + lock (yddLock) { var key = new GameFileCacheKey(hash, GameFileType.Ydd); if (projectFiles.TryGetValue(key, out GameFile pgf)) @@ -2103,36 +2222,29 @@ namespace CodeWalker.GameFiles if (ydd == null) { var e = GetYddEntry(hash); - if (e != null) + if (e == null) return null; + + ydd = new YddFile(e); + if (!mainCache.TryAdd(key, ydd)) { - ydd = new YddFile(e); - if (mainCache.TryAdd(key, ydd)) - { - TryLoadEnqueue(ydd); - } - else - { - ydd.LoadQueued = false; - //ErrorLog("Out of cache space - couldn't load drawable dictionary: " + JenkIndex.GetString(hash)); //too spammy... - } - } - else - { - //ErrorLog("Drawable dictionary not found: " + JenkIndex.GetString(hash)); //too spammy... + return null; } } - else if (!ydd.Loaded) + + if (!ydd.Loaded && !ydd.LoadQueued) { TryLoadEnqueue(ydd); } return ydd; } } + + private readonly SemaphoreSlim ytdLock = new SemaphoreSlim(1, 1); public YtdFile GetYtd(uint hash) { if (!IsInited) return null; - lock (requestSyncRoot) - { + ytdLock.Wait(); + try { var key = new GameFileCacheKey(hash, GameFileType.Ytd); if (projectFiles.TryGetValue(key, out GameFile pgf)) { @@ -2142,70 +2254,61 @@ namespace CodeWalker.GameFiles if (ytd == null) { var e = GetYtdEntry(hash); - if (e != null) + if (e == null) { - ytd = new YtdFile(e); - if (mainCache.TryAdd(key, ytd)) - { - TryLoadEnqueue(ytd); - } - else - { - ytd.LoadQueued = false; - //ErrorLog("Out of cache space - couldn't load texture dictionary: " + JenkIndex.GetString(hash)); //too spammy... - } + return null; } - else + ytd = new YtdFile(e); + if (!mainCache.TryAdd(key, ytd)) { - //ErrorLog("Texture dictionary not found: " + JenkIndex.GetString(hash)); //too spammy... + return null; } } - else if (!ytd.Loaded) + if (!ytd.Loaded && !ytd.LoadQueued) { - TryLoadEnqueue(ytd); + _ = TryLoadEnqueue(ytd); } return ytd; + } finally + { + ytdLock.Release(); } } + + private readonly object ymapLock = new object(); public YmapFile GetYmap(uint hash) { if (!IsInited) return null; - lock (requestSyncRoot) + lock (ymapLock) { var key = new GameFileCacheKey(hash, GameFileType.Ymap); YmapFile ymap = mainCache.TryGet(key) as YmapFile; if (ymap == null) { var e = GetYmapEntry(hash); - if (e != null) + if (e == null) { - ymap = new YmapFile(e); - if (mainCache.TryAdd(key, ymap)) - { - TryLoadEnqueue(ymap); - } - else - { - ymap.LoadQueued = false; - //ErrorLog("Out of cache space - couldn't load ymap: " + JenkIndex.GetString(hash)); - } + return null; } - else + ymap = new YmapFile(e); + if (!mainCache.TryAdd(key, ymap)) { - //ErrorLog("Ymap not found: " + JenkIndex.GetString(hash)); //too spammy... + return null; } } - else if (!ymap.Loaded) + if (!ymap.Loaded && !ymap.LoadQueued) { TryLoadEnqueue(ymap); } return ymap; } } + + private readonly object yftLock = new object(); public YftFile GetYft(uint hash) { if (!IsInited) return null; - lock (requestSyncRoot) + lock (yftLock) { var key = new GameFileCacheKey(hash, GameFileType.Yft); YftFile yft = mainCache.TryGet(key) as YftFile; @@ -2216,165 +2319,133 @@ namespace CodeWalker.GameFiles if (yft == null) { var e = GetYftEntry(hash); - if (e != null) + if (e == null) { - yft = new YftFile(e); - if (mainCache.TryAdd(key, yft)) - { - TryLoadEnqueue(yft); - } - else - { - yft.LoadQueued = false; - //ErrorLog("Out of cache space - couldn't load yft: " + JenkIndex.GetString(hash)); //too spammy... - } + return null; } - else + yft = new YftFile(e); + if (!mainCache.TryAdd(key, yft)) { - //ErrorLog("Yft not found: " + JenkIndex.GetString(hash)); //too spammy... + return null; } } - else if (!yft.Loaded) + if (!yft.Loaded && !yft.LoadQueued) { TryLoadEnqueue(yft); } return yft; } } + + private readonly object ybnLock = new object(); public YbnFile GetYbn(uint hash) { if (!IsInited) return null; - lock (requestSyncRoot) + lock (ybnLock) { var key = new GameFileCacheKey(hash, GameFileType.Ybn); YbnFile ybn = mainCache.TryGet(key) as YbnFile; if (ybn == null) { var e = GetYbnEntry(hash); - if (e != null) + if (e == null) { - ybn = new YbnFile(e); - if (mainCache.TryAdd(key, ybn)) - { - TryLoadEnqueue(ybn); - } - else - { - ybn.LoadQueued = false; - //ErrorLog("Out of cache space - couldn't load ybn: " + JenkIndex.GetString(hash)); //too spammy... - } + return null; } - else + ybn = new YbnFile(e); + if (!mainCache.TryAdd(key, ybn)) { - //ErrorLog("Ybn not found: " + JenkIndex.GetString(hash)); //too spammy... + return null; } } - else if (!ybn.Loaded) + if (!ybn.Loaded && !ybn.LoadQueued) { TryLoadEnqueue(ybn); } return ybn; } } + + private readonly object ycdLock = new object(); public YcdFile GetYcd(uint hash) { if (!IsInited) return null; - lock (requestSyncRoot) + lock (ycdLock) { var key = new GameFileCacheKey(hash, GameFileType.Ycd); YcdFile ycd = mainCache.TryGet(key) as YcdFile; if (ycd == null) { var e = GetYcdEntry(hash); - if (e != null) + if (e == null) { - ycd = new YcdFile(e); - if (mainCache.TryAdd(key, ycd)) - { - TryLoadEnqueue(ycd); - } - else - { - ycd.LoadQueued = false; - //ErrorLog("Out of cache space - couldn't load ycd: " + JenkIndex.GetString(hash)); //too spammy... - } + return null; } - else + ycd = new YcdFile(e); + if (!mainCache.TryAdd(key, ycd)) { - //ErrorLog("Ycd not found: " + JenkIndex.GetString(hash)); //too spammy... + return null; } } - else if (!ycd.Loaded) + if (!ycd.Loaded && !ycd.LoadQueued) { TryLoadEnqueue(ycd); } return ycd; } } + + private readonly object yedLock = new object(); public YedFile GetYed(uint hash) { if (!IsInited) return null; - lock (requestSyncRoot) + lock (yedLock) { var key = new GameFileCacheKey(hash, GameFileType.Yed); YedFile yed = mainCache.TryGet(key) as YedFile; if (yed == null) { var e = GetYedEntry(hash); - if (e != null) + if (e == null) { - yed = new YedFile(e); - if (mainCache.TryAdd(key, yed)) - { - TryLoadEnqueue(yed); - } - else - { - yed.LoadQueued = false; - //ErrorLog("Out of cache space - couldn't load yed: " + JenkIndex.GetString(hash)); //too spammy... - } + return null; } - else + yed = new YedFile(e); + if (!mainCache.TryAdd(key, yed)) { - //ErrorLog("Yed not found: " + JenkIndex.GetString(hash)); //too spammy... + return null; } } - else if (!yed.Loaded) + if (!yed.Loaded && !yed.LoadQueued) { TryLoadEnqueue(yed); } return yed; } } + + private readonly object yvnLock = new object(); public YnvFile GetYnv(uint hash) { if (!IsInited) return null; - lock (requestSyncRoot) + lock (yvnLock) { var key = new GameFileCacheKey(hash, GameFileType.Ynv); YnvFile ynv = mainCache.TryGet(key) as YnvFile; if (ynv == null) { var e = GetYnvEntry(hash); - if (e != null) + if (e == null) { - ynv = new YnvFile(e); - if (mainCache.TryAdd(key, ynv)) - { - TryLoadEnqueue(ynv); - } - else - { - ynv.LoadQueued = false; - //ErrorLog("Out of cache space - couldn't load ycd: " + JenkIndex.GetString(hash)); //too spammy... - } + return null; } - else + ynv = new YnvFile(e); + if (!mainCache.TryAdd(key, ynv)) { - //ErrorLog("Ycd not found: " + JenkIndex.GetString(hash)); //too spammy... + return null; } } - else if (!ynv.Loaded) + if (!ynv.Loaded && !ynv.LoadQueued) { TryLoadEnqueue(ynv); } @@ -2406,7 +2477,10 @@ namespace CodeWalker.GameFiles RpfFileEntry entry; if (!YmapDict.TryGetValue(hash, out entry)) { - AllYmapsDict.TryGetValue(hash, out entry); + if (!AllYmapsDict.TryGetValue(hash, out entry)) + { + Console.WriteLine($"Couldn't find ymap {JenkIndex.GetString(hash)}"); + } } return entry; } @@ -2454,6 +2528,17 @@ namespace CodeWalker.GameFiles return false; } + public ValueTask LoadFileAsync(T file) where T : GameFile, PackedFile + { + if (file == null) return new ValueTask(false); + RpfFileEntry entry = file.RpfFileEntry; + if (entry != null) + { + return RpfMan.LoadFileAsync(file, entry); + } + return new ValueTask(false); + } + public T GetFileUncached(RpfFileEntry e) where T : GameFile, new() { @@ -2466,23 +2551,19 @@ namespace CodeWalker.GameFiles public void BeginFrame() { - lock (requestSyncRoot) - { - mainCache.BeginFrame(); - } + mainCache.BeginFrame(); } - private void LoadFile(GameFile req) + private async ValueTask LoadFileAsync(GameFile req) { + //process content requests. + if (req.Loaded) + return; //it's already loaded... (somehow) + + if ((req.LastUseTime - DateTime.Now).TotalSeconds > 3.0) + return; //hasn't been requested lately..! ignore, will try again later if necessary try { - //process content requests. - if (req.Loaded) - return; //it's already loaded... (somehow) - - if ((req.LastUseTime - DateTime.Now).TotalSeconds > 3.0) - return; //hasn't been requested lately..! ignore, will try again later if necessary - //if (!loadedsomething) //{ //UpdateStatus?.Invoke("Loading " + req.RpfFileEntry.Name + "..."); @@ -2491,37 +2572,37 @@ namespace CodeWalker.GameFiles switch (req.Type) { case GameFileType.Ydr: - req.Loaded = LoadFile(req as YdrFile); + req.Loaded = await LoadFileAsync(req as YdrFile); break; case GameFileType.Ydd: - req.Loaded = LoadFile(req as YddFile); + req.Loaded = await LoadFileAsync(req as YddFile); break; case GameFileType.Ytd: - req.Loaded = LoadFile(req as YtdFile); + req.Loaded = await LoadFileAsync(req as YtdFile); //if (req.Loaded) AddTextureLookups(req as YtdFile); break; case GameFileType.Ymap: YmapFile y = req as YmapFile; - req.Loaded = LoadFile(y); + req.Loaded = await LoadFileAsync(y); if (req.Loaded) y.InitYmapEntityArchetypes(this); break; case GameFileType.Yft: - req.Loaded = LoadFile(req as YftFile); + req.Loaded = await LoadFileAsync(req as YftFile); break; case GameFileType.Ybn: - req.Loaded = LoadFile(req as YbnFile); + req.Loaded = await LoadFileAsync(req as YbnFile); break; case GameFileType.Ycd: - req.Loaded = LoadFile(req as YcdFile); + req.Loaded = await LoadFileAsync(req as YcdFile); break; case GameFileType.Yed: - req.Loaded = LoadFile(req as YedFile); + req.Loaded = await LoadFileAsync(req as YedFile); break; case GameFileType.Ynv: - req.Loaded = LoadFile(req as YnvFile); + req.Loaded = await LoadFileAsync(req as YnvFile); break; case GameFileType.Yld: - req.Loaded = LoadFile(req as YldFile); + req.Loaded = await LoadFileAsync(req as YldFile); break; default: break; @@ -2537,26 +2618,103 @@ namespace CodeWalker.GameFiles ErrorLog("Error loading " + req.ToString()); } } - catch(Exception e) + catch (Exception e) { req.LoadQueued = false; req.Loaded = false; Console.WriteLine(e); + TryLoadEnqueue(req); } finally { req.LoadQueued = false; } - } + private void LoadFile(GameFile req) + { + lock (req) + { + try + { + //process content requests. + if (req.Loaded) + return; //it's already loaded... (somehow) + + if ((req.LastUseTime - DateTime.Now).TotalSeconds > 3.0) + return; //hasn't been requested lately..! ignore, will try again later if necessary + + //if (!loadedsomething) + //{ + //UpdateStatus?.Invoke("Loading " + req.RpfFileEntry.Name + "..."); + //} + + switch (req.Type) + { + case GameFileType.Ydr: + req.Loaded = LoadFile(req as YdrFile); + break; + case GameFileType.Ydd: + req.Loaded = LoadFile(req as YddFile); + break; + case GameFileType.Ytd: + req.Loaded = LoadFile(req as YtdFile); + //if (req.Loaded) AddTextureLookups(req as YtdFile); + break; + case GameFileType.Ymap: + YmapFile y = req as YmapFile; + req.Loaded = LoadFile(y); + if (req.Loaded) y.InitYmapEntityArchetypes(this); + break; + case GameFileType.Yft: + req.Loaded = LoadFile(req as YftFile); + break; + case GameFileType.Ybn: + req.Loaded = LoadFile(req as YbnFile); + break; + case GameFileType.Ycd: + req.Loaded = LoadFile(req as YcdFile); + break; + case GameFileType.Yed: + req.Loaded = LoadFile(req as YedFile); + break; + case GameFileType.Ynv: + req.Loaded = LoadFile(req as YnvFile); + break; + case GameFileType.Yld: + req.Loaded = LoadFile(req as YldFile); + break; + default: + break; + } + + string str = (req.Loaded ? "Loaded " : "Error loading ") + req.ToString(); + //string str = string.Format("{0}: {1}: {2}", requestQueue.Count, (req.Loaded ? "Loaded" : "Error loading"), req); + + UpdateStatus?.Invoke(str); + //ErrorLog(str); + if (!req.Loaded) + { + ErrorLog("Error loading " + req.ToString()); + } + } + catch (Exception e) + { + req.LoadQueued = false; + req.Loaded = false; + Console.WriteLine(e); + throw; + } + finally + { + req.LoadQueued = false; + } + } + } public bool ContentThreadProc() { - lock (requestSyncRoot) - { - mainCache.BeginFrame(); - } + mainCache.BeginFrame(); int itemcount = 0; lock (updateSyncRoot) { @@ -2855,7 +3013,7 @@ namespace CodeWalker.GameFiles { try { - if (doydr && entry.NameLower.EndsWith(".ydr")) + if (doydr && entry.IsExtension(".ydr")) { UpdateStatus?.Invoke(entry.Path); YdrFile ydr = RpfMan.GetFile(entry); @@ -2864,7 +3022,7 @@ namespace CodeWalker.GameFiles if (ydr.Drawable == null) { continue; } collectDrawable(ydr.Drawable); } - else if (doydd & entry.NameLower.EndsWith(".ydd")) + else if (doydd & entry.IsExtension(".ydd")) { UpdateStatus?.Invoke(entry.Path); YddFile ydd = RpfMan.GetFile(entry); @@ -2876,7 +3034,7 @@ namespace CodeWalker.GameFiles collectDrawable(drawable); } } - else if (doyft && entry.NameLower.EndsWith(".yft")) + else if (doyft && entry.IsExtension(".yft")) { UpdateStatus?.Invoke(entry.Path); YftFile yft = RpfMan.GetFile(entry); @@ -2902,7 +3060,7 @@ namespace CodeWalker.GameFiles } } } - else if (doypt && entry.NameLower.EndsWith(".ypt")) + else if (doypt && entry.IsExtension(".ypt")) { UpdateStatus?.Invoke(entry.Path); YptFile ypt = RpfMan.GetFile(entry); @@ -3018,19 +3176,30 @@ namespace CodeWalker.GameFiles } - private class ShaderXmlDataCollection + public class ShaderTextureData + { + public HashSet Textures { get; set; } = new HashSet(); + public HashSet Geometries { get; set; } = new HashSet(); + public int Count { get; set; } = 0; + } + + public class ShaderXmlDataCollection { public MetaHash Name { get; set; } public Dictionary FileNames { get; set; } = new Dictionary(); public Dictionary RenderBuckets { get; set; } = new Dictionary(); public Dictionary VertexLayouts { get; set; } = new Dictionary(); public Dictionary TexParams { get; set; } = new Dictionary(); + + public Dictionary TextureData { get; set; } = new Dictionary(); + + public HashSet Textures { get; set; } = new HashSet(); public Dictionary> ValParams { get; set; } = new Dictionary>(); public Dictionary> ArrParams { get; set; } = new Dictionary>(); public int GeomCount { get; set; } = 0; - public void AddShaderUse(ShaderFX s, DrawableGeometry g) + public void AddShaderUse(ShaderFX s, DrawableGeometry g, DrawableBase d = null) { GeomCount++; @@ -3054,6 +3223,20 @@ namespace CodeWalker.GameFiles if (p.DataType == 0)//texture { AddItem(h, TexParams); + + if (!TextureData.TryGetValue(h, out var tex)) + { + tex = new ShaderTextureData(); + TextureData[h] = tex; + } + + tex.Count++; + + if (p.Data is TextureBase texture) + { + tex.Textures.Add($"{texture.Name} ({(d.Owner as GameFile)?.Name})"); + } + } else if (p.DataType == 1)//vector { @@ -3094,7 +3277,6 @@ namespace CodeWalker.GameFiles } } } - } public void AddItem(T t, Dictionary d) { @@ -3123,8 +3305,15 @@ namespace CodeWalker.GameFiles kvps.Sort((a, b) => { return b.Value.CompareTo(a.Value); }); return kvps.Select((a) => { return a.Key; }).ToList(); } + + public List> GetSortedList(Dictionary d) + { + var kvps = d.ToList(); + kvps.Sort((a, b) => { return b.Value.Count.CompareTo(a.Value.Count); }); + return kvps.ToList(); + } } - private struct ShaderXmlVertexLayout + public struct ShaderXmlVertexLayout { public VertexDeclarationTypes Types { get; set; } public uint Flags { get; set; } diff --git a/CodeWalker.Core/GameFiles/MetaTypes/Archetype.cs b/CodeWalker.Core/GameFiles/MetaTypes/Archetype.cs index 6c791c7..c02935a 100644 --- a/CodeWalker.Core/GameFiles/MetaTypes/Archetype.cs +++ b/CodeWalker.Core/GameFiles/MetaTypes/Archetype.cs @@ -393,11 +393,11 @@ namespace CodeWalker.GameFiles { foreach (var portal in portals) { - List newAttachedObjects = new List(); - if (portal.AttachedObjects == null) + if (portal.AttachedObjects == null || portal.AttachedObjects.Length == 0) continue; - foreach(var objIndex in portal.AttachedObjects) + List newAttachedObjects = new List(); + foreach (var objIndex in portal.AttachedObjects) { if (objIndex == deletedIndex) continue; if (objIndex > deletedIndex) @@ -411,9 +411,10 @@ namespace CodeWalker.GameFiles { foreach (var room in rooms) { - List newAttachedObjects = new List(); - if (room.AttachedObjects == null) + if (room.AttachedObjects == null || room.AttachedObjects.Length == 0) continue; + + List newAttachedObjects = new List(); foreach (var objIndex in room.AttachedObjects) { if (objIndex == deletedIndex) continue; @@ -532,10 +533,12 @@ namespace CodeWalker.GameFiles } public MCMloRoomDef GetEntityRoom(MCEntityDef ent) { - if (rooms == null) return null; + if (rooms == null) + return null; int objectIndex = GetEntityObjectIndex(ent); - if (objectIndex < 0) return null; + if (objectIndex < 0) + return null; for (int i = 0; i < rooms.Length; i++) { @@ -684,10 +687,15 @@ namespace CodeWalker.GameFiles { var ient = Entities[j]; var iarch = gfc.GetArchetype(ient._CEntityDef.archetypeName); - ient.SetArchetype(iarch); if (iarch == null) - { } //can't find archetype - des stuff eg {des_prologue_door} + { + Console.WriteLine($"Can't find archetype for {ient._CEntityDef.archetypeName}!"); + } + else + { + ient.SetArchetype(iarch); + } } UpdateBBs(arch); @@ -708,7 +716,13 @@ namespace CodeWalker.GameFiles ient.SetArchetype(iarch); if (iarch == null) - { } //can't find archetype - des stuff eg {des_prologue_door} + { + Console.WriteLine($"Couldn't find archetype {ient._CEntityDef.archetypeName}"); + } + else + { + ient.SetArchetype(iarch); + } } } } @@ -729,7 +743,8 @@ namespace CodeWalker.GameFiles for (int j = 0; j < rooms.Length; j++) { var room = rooms[j]; - if ((room.AttachedObjects == null) || (room.AttachedObjects.Length == 0)) continue; + if (room.AttachedObjects == null || room.AttachedObjects.Length == 0) + continue; Vector3 min = new Vector3(float.MaxValue); Vector3 max = new Vector3(float.MinValue); for (int k = 0; k < room.AttachedObjects.Length; k++) diff --git a/CodeWalker.Core/GameFiles/MetaTypes/Meta.cs b/CodeWalker.Core/GameFiles/MetaTypes/Meta.cs index 8771575..96a05e3 100644 --- a/CodeWalker.Core/GameFiles/MetaTypes/Meta.cs +++ b/CodeWalker.Core/GameFiles/MetaTypes/Meta.cs @@ -1,5 +1,4 @@ -using System; -using System.Collections.Generic; +using System;using System.Collections.Generic; using System.ComponentModel; using System.IO; using System.Linq; @@ -675,6 +674,35 @@ namespace CodeWalker.GameFiles { return "Array_StructurePointer: " + PointerDataIndex.ToString() + " (" + Count1.ToString() + "/" + Count2.ToString() + ")"; } + + public override bool Equals(object obj) + { + return obj is Array_StructurePointer pointer && + Pointer == pointer.Pointer && + Count1 == pointer.Count1 && + Count2 == pointer.Count2 && + Unk1 == pointer.Unk1; + } + + public override int GetHashCode() + { + int hashCode = -1900453823; + hashCode = hashCode * -1521134295 + Pointer.GetHashCode(); + hashCode = hashCode * -1521134295 + Count1.GetHashCode(); + hashCode = hashCode * -1521134295 + Count2.GetHashCode(); + hashCode = hashCode * -1521134295 + Unk1.GetHashCode(); + return hashCode; + } + + public static bool operator ==(Array_StructurePointer left, Array_StructurePointer right) + { + return left.Equals(right); + } + + public static bool operator !=(Array_StructurePointer left, Array_StructurePointer right) + { + return !(left == right); + } } [TC(typeof(EXP))] public struct Array_Structure //16 bytes - pointer for a structure array { @@ -717,6 +745,37 @@ namespace CodeWalker.GameFiles { return "Array_Structure: " + PointerDataIndex.ToString() + " (" + Count1.ToString() + "/" + Count2.ToString() + ")"; } + + public static bool operator ==(Array_Structure x, Array_Structure y) + { + return x.Equals(y); + } + + public static bool operator !=(Array_Structure x, Array_Structure y) + { + return !x.Equals(y); + } + + public override bool Equals(object obj) + { + if (obj is null || obj is not Array_Structure arrObj) + return false; + + return arrObj.Pointer == this.Pointer + && arrObj.Count1 == this.Count1 + && arrObj.Count2 == this.Count2 + && arrObj.Unk1 == this.Unk1; + } + + public override int GetHashCode() + { + int hashCode = -1900453823; + hashCode = hashCode * -1521134295 + Pointer.GetHashCode(); + hashCode = hashCode * -1521134295 + Count1.GetHashCode(); + hashCode = hashCode * -1521134295 + Count2.GetHashCode(); + hashCode = hashCode * -1521134295 + Unk1.GetHashCode(); + return hashCode; + } } [TC(typeof(EXP))] public struct Array_uint //16 bytes - pointer for a uint array { @@ -757,6 +816,37 @@ namespace CodeWalker.GameFiles { return "Array_uint: " + PointerDataIndex.ToString() + " (" + Count1.ToString() + "/" + Count2.ToString() + ")"; } + + public static bool operator ==(Array_uint x, Array_uint y) + { + return x.Equals(y); + } + + public static bool operator !=(Array_uint x, Array_uint y) + { + return !x.Equals(y); + } + + public override bool Equals(object obj) + { + if (obj is null || obj is not Array_uint arrObj) + return false; + + return arrObj.Pointer == this.Pointer + && arrObj.Count1 == this.Count1 + && arrObj.Count2 == this.Count2 + && arrObj.Unk1 == this.Unk1; + } + + public override int GetHashCode() + { + int hashCode = -1900453823; + hashCode = hashCode * -1521134295 + Pointer.GetHashCode(); + hashCode = hashCode * -1521134295 + Count1.GetHashCode(); + hashCode = hashCode * -1521134295 + Count2.GetHashCode(); + hashCode = hashCode * -1521134295 + Unk1.GetHashCode(); + return hashCode; + } } [TC(typeof(EXP))] public struct Array_ushort //16 bytes - pointer for a ushort array { @@ -797,6 +887,37 @@ namespace CodeWalker.GameFiles { return "Array_ushort: " + PointerDataIndex.ToString() + " (" + Count1.ToString() + "/" + Count2.ToString() + ")"; } + + public static bool operator ==(Array_ushort x, Array_ushort y) + { + return x.Equals(y); + } + + public static bool operator !=(Array_ushort x, Array_ushort y) + { + return !x.Equals(y); + } + + public override bool Equals(object obj) + { + if (obj is null || obj is not Array_ushort arrObj) + return false; + + return arrObj.Pointer == this.Pointer + && arrObj.Count1 == this.Count1 + && arrObj.Count2 == this.Count2 + && arrObj.Unk1 == this.Unk1; + } + + public override int GetHashCode() + { + int hashCode = -1900453823; + hashCode = hashCode * -1521134295 + Pointer.GetHashCode(); + hashCode = hashCode * -1521134295 + Count1.GetHashCode(); + hashCode = hashCode * -1521134295 + Count2.GetHashCode(); + hashCode = hashCode * -1521134295 + Unk1.GetHashCode(); + return hashCode; + } } [TC(typeof(EXP))] public struct Array_byte //16 bytes - pointer for a byte array { @@ -836,6 +957,35 @@ namespace CodeWalker.GameFiles { return "Array_byte: " + PointerDataIndex.ToString() + " (" + Count1.ToString() + "/" + Count2.ToString() + ")"; } + + public override bool Equals(object obj) + { + return obj is Array_byte @byte && + Pointer == @byte.Pointer && + Count1 == @byte.Count1 && + Count2 == @byte.Count2 && + Unk1 == @byte.Unk1; + } + + public override int GetHashCode() + { + int hashCode = -1900453823; + hashCode = hashCode * -1521134295 + Pointer.GetHashCode(); + hashCode = hashCode * -1521134295 + Count1.GetHashCode(); + hashCode = hashCode * -1521134295 + Count2.GetHashCode(); + hashCode = hashCode * -1521134295 + Unk1.GetHashCode(); + return hashCode; + } + + public static bool operator ==(Array_byte left, Array_byte right) + { + return left.Equals(right); + } + + public static bool operator !=(Array_byte left, Array_byte right) + { + return !(left == right); + } } [TC(typeof(EXP))] public struct Array_float //16 bytes - pointer for a float array { @@ -1118,7 +1268,7 @@ namespace CodeWalker.GameFiles - [TC(typeof(EXP))] public struct MetaHash + [TC(typeof(EXP))] public struct MetaHash : IEquatable { public uint Hash { get; set; } @@ -1180,7 +1330,7 @@ namespace CodeWalker.GameFiles return new MetaHash(v); } - public override bool Equals(object obj) + public override readonly bool Equals(object obj) { if (obj == null) return false; if (obj is not MetaHash metaHash) return false; @@ -1188,7 +1338,14 @@ namespace CodeWalker.GameFiles return metaHash.Hash == Hash; } - public override int GetHashCode() + public readonly bool Equals(MetaHash obj) + { + if (obj == null) return false; + + return obj.Hash == Hash; + } + + public override readonly int GetHashCode() { return (int)Hash; } diff --git a/CodeWalker.Core/GameFiles/MetaTypes/MetaBuilder.cs b/CodeWalker.Core/GameFiles/MetaTypes/MetaBuilder.cs index a0d98f6..5e32ba7 100644 --- a/CodeWalker.Core/GameFiles/MetaTypes/MetaBuilder.cs +++ b/CodeWalker.Core/GameFiles/MetaTypes/MetaBuilder.cs @@ -153,19 +153,22 @@ namespace CodeWalker.GameFiles public Array_Vector3 AddPaddedVector3ArrayPtr(Vector4[] items) { - if ((items == null) || (items.Length == 0)) return new Array_Vector3(); + if (items == null || items.Length == 0) + return new Array_Vector3(); var ptr = AddItemArray((MetaName)MetaTypeName.VECTOR4, items); //padded to vec4... return new Array_Vector3(ptr); } public Array_uint AddHashArrayPtr(MetaHash[] items) { - if ((items == null) || (items.Length == 0)) return new Array_uint(); + if (items == null || items.Length == 0) + return new Array_uint(); var ptr = AddItemArray((MetaName)MetaTypeName.HASH, items); return new Array_uint(ptr); } public Array_uint AddUintArrayPtr(uint[] items) { - if ((items == null) || (items.Length == 0)) return new Array_uint(); + if (items == null || items.Length == 0) + return new Array_uint(); var ptr = AddItemArray((MetaName)MetaTypeName.UINT, items); return new Array_uint(ptr); } diff --git a/CodeWalker.Core/GameFiles/MetaTypes/MetaNames.cs b/CodeWalker.Core/GameFiles/MetaTypes/MetaNames.cs index 49e320c..d0de3f1 100644 --- a/CodeWalker.Core/GameFiles/MetaTypes/MetaNames.cs +++ b/CodeWalker.Core/GameFiles/MetaTypes/MetaNames.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; using System.Linq; @@ -10,7 +11,7 @@ namespace CodeWalker.GameFiles public static class MetaNames { - public static Dictionary stringCache = new Dictionary(); + public static ConcurrentDictionary stringCache = new ConcurrentDictionary(); public static bool TryGetString(uint h, out string str) { if (stringCache.TryGetValue(h, out str)) @@ -21,10 +22,10 @@ namespace CodeWalker.GameFiles { str = ((MetaName)h).ToString(); if (str.StartsWith("@")) str = str.Substring(1); //mainly to handle the @null entry - stringCache.Add(h, str); + stringCache.TryAdd(h, str); return true; } - stringCache.Add(h, str); + stringCache.TryAdd(h, str); str = null; return false; } diff --git a/CodeWalker.Core/GameFiles/MetaTypes/MetaTypes.cs b/CodeWalker.Core/GameFiles/MetaTypes/MetaTypes.cs index 8c63ec2..9b29d66 100644 --- a/CodeWalker.Core/GameFiles/MetaTypes/MetaTypes.cs +++ b/CodeWalker.Core/GameFiles/MetaTypes/MetaTypes.cs @@ -1438,26 +1438,39 @@ namespace CodeWalker.GameFiles public static byte[] ConvertToBytes(T item) where T : struct { int size = Marshal.SizeOf(typeof(T)); - int offset = 0; + //int offset = 0; byte[] arr = new byte[size]; - IntPtr ptr = Marshal.AllocHGlobal(size); - Marshal.StructureToPtr(item, ptr, true); - Marshal.Copy(ptr, arr, 0, size); - Marshal.FreeHGlobal(ptr); - offset += size; + MemoryMarshal.TryWrite(arr.AsSpan(), ref item); return arr; + //IntPtr ptr = Marshal.AllocHGlobal(size); + //Marshal.StructureToPtr(item, ptr, true); + //Marshal.Copy(ptr, arr, 0, size); + //Marshal.FreeHGlobal(ptr); + //offset += size; + //return arr; } public static byte[] ConvertArrayToBytes(params T[] items) where T : struct { if (items == null) return null; - var size = Marshal.SizeOf(typeof(T)) * items.Length; - var b = new byte[size]; - GCHandle handle = GCHandle.Alloc(items, GCHandleType.Pinned); - var h = handle.AddrOfPinnedObject(); - Marshal.Copy(h, b, 0, size); - handle.Free(); - return b; + return MemoryMarshal.AsBytes(items.AsSpan()).ToArray(); + + //var size = Marshal.SizeOf(typeof(T)) * items.Length; + //var b = new byte[size]; + //GCHandle handle = GCHandle.Alloc(items, GCHandleType.Pinned); + //var h = handle.AddrOfPinnedObject(); + //Marshal.Copy(h, b, 0, size); + //handle.Free(); + //return b; + + //var size = Marshal.SizeOf(typeof(T)) * items.Length; + //var b = new byte[size]; + //return MemoryMarshal.AsBytes(items).ToArray(); + //GCHandle handle = GCHandle.Alloc(items, GCHandleType.Pinned); + //var h = handle.AddrOfPinnedObject(); + //Marshal.Copy(h, b, 0, size); + //handle.Free(); + //return b; //int size = Marshal.SizeOf(typeof(T)); @@ -1502,20 +1515,33 @@ namespace CodeWalker.GameFiles } public static Span ConvertDataArray(byte[] data, int offset, int count) where T : struct { + //T[] items = new T[count]; + //int itemsize = Marshal.SizeOf(typeof(T)); + ////for (int i = 0; i < count; i++) + ////{ + //// int off = offset + i * itemsize; + //// items[i] = ConvertData(data, off); + ////} + //GCHandle handle = GCHandle.Alloc(items, GCHandleType.Pinned); + //var h = handle.AddrOfPinnedObject(); + //Marshal.Copy(data, offset, h, itemsize * count); + //handle.Free(); + + //return items; return MemoryMarshal.Cast(data.AsSpan(offset, count * Marshal.SizeOf(typeof(T)))); - T[] items = new T[count]; - int itemsize = Marshal.SizeOf(typeof(T)); + //T[] items = new T[count]; + //int itemsize = Marshal.SizeOf(typeof(T)); //for (int i = 0; i < count; i++) //{ // int off = offset + i * itemsize; // items[i] = ConvertData(data, off); //} - GCHandle handle = GCHandle.Alloc(items, GCHandleType.Pinned); - var h = handle.AddrOfPinnedObject(); - Marshal.Copy(data, offset, h, itemsize * count); - handle.Free(); + //GCHandle handle = GCHandle.Alloc(items, GCHandleType.Pinned); + //var h = handle.AddrOfPinnedObject(); + //Marshal.Copy(data, offset, h, itemsize * count); + //handle.Free(); - return items; + //return items; } public static T[] ConvertDataArray(Meta meta, MetaName name, Array_StructurePointer array) where T : struct { @@ -1583,13 +1609,13 @@ namespace CodeWalker.GameFiles } //don't try to read too many items.. - ConvertDataArray(ptrblock.Data, itemoffset * Marshal.SizeOf(typeof(T)), itemcount).CopyTo(items.AsSpan(curi)); - //for (int i = 0; i < itemcount; i++) - //{ - // int offset = (itemoffset + i) * itemsize; - // int index = curi + i; - // items[index] = ConvertData(ptrblock.Data, offset); - //} + //ConvertDataArray(ptrblock.Data, itemoffset * Marshal.SizeOf(typeof(T)), itemcount).CopyTo(items.AsSpan(curi)); + for (int i = 0; i < itemcount; i++) + { + int offset = (itemoffset + i) * itemsize; + int index = curi + i; + items[index] = ConvertData(ptrblock.Data, offset); + } itemoffset = 0; //start at beginning of next block.. curi += itemcount; itemsleft -= itemcount; @@ -2500,7 +2526,7 @@ namespace CodeWalker.GameFiles { _Data = data; RoomName = MetaTypes.GetString(meta, _Data.name); - AttachedObjects = MetaTypes.GetUintArray(meta, _Data.attachedObjects); + AttachedObjects = MetaTypes.GetUintArray(meta, _Data.attachedObjects) ?? Array.Empty(); } public override void Load(Meta meta, MetaPOINTER ptr) @@ -2521,14 +2547,7 @@ namespace CodeWalker.GameFiles _Data.name = new CharPointer(); } - if (AttachedObjects != null) - { - _Data.attachedObjects = mb.AddUintArrayPtr(AttachedObjects); - } - else - { - _Data.attachedObjects = new Array_uint(); - } + _Data.attachedObjects = mb.AddUintArrayPtr(AttachedObjects); mb.AddStructureInfo(MetaName.CMloRoomDef); return mb.AddItemPtr(MetaName.CMloRoomDef, _Data); @@ -4101,6 +4120,61 @@ namespace CodeWalker.GameFiles MaxCellY = (int)Math.Floor(gv.Y); } } + + public static bool operator ==(rage__spdGrid2D x, rage__spdGrid2D y) + { + return x.Equals(y); + } + + public static bool operator !=(rage__spdGrid2D x, rage__spdGrid2D y) + { + return x.Equals(y); + } + + public override bool Equals(object obj) + { + if (obj is null || obj is not rage__spdGrid2D arrObj) + return false; + + return arrObj.MaxCellX == this.MaxCellX + && arrObj.MaxCellY == this.MaxCellY + && arrObj.MinCellX == this.MinCellX + && arrObj.MinCellY == this.MinCellY + && arrObj.CellDimX == this.CellDimX + && arrObj.CellDimY == this.CellDimY + && arrObj.Unused0 == this.Unused0 + && arrObj.Unused1 == this.Unused1 + && arrObj.Unused2 == this.Unused2 + && arrObj.Unused3 == this.Unused3 + && arrObj.Unused4 == this.Unused4 + && arrObj.Unused5 == this.Unused5 + && arrObj.Unused6 == this.Unused6 + && arrObj.Unused7 == this.Unused7 + && arrObj.Unused8 == this.Unused8 + && arrObj.Unused9 == this.Unused9; + } + + public override int GetHashCode() + { + int hashCode = 418850833; + hashCode = hashCode * -1521134295 + Unused0.GetHashCode(); + hashCode = hashCode * -1521134295 + Unused1.GetHashCode(); + hashCode = hashCode * -1521134295 + Unused2.GetHashCode(); + hashCode = hashCode * -1521134295 + MinCellX.GetHashCode(); + hashCode = hashCode * -1521134295 + MaxCellX.GetHashCode(); + hashCode = hashCode * -1521134295 + MinCellY.GetHashCode(); + hashCode = hashCode * -1521134295 + MaxCellY.GetHashCode(); + hashCode = hashCode * -1521134295 + Unused3.GetHashCode(); + hashCode = hashCode * -1521134295 + Unused4.GetHashCode(); + hashCode = hashCode * -1521134295 + Unused5.GetHashCode(); + hashCode = hashCode * -1521134295 + Unused6.GetHashCode(); + hashCode = hashCode * -1521134295 + CellDimX.GetHashCode(); + hashCode = hashCode * -1521134295 + CellDimY.GetHashCode(); + hashCode = hashCode * -1521134295 + Unused7.GetHashCode(); + hashCode = hashCode * -1521134295 + Unused8.GetHashCode(); + hashCode = hashCode * -1521134295 + Unused9.GetHashCode(); + return hashCode; + } } [TC(typeof(EXP))] public struct rage__spdAABB //32 bytes, Key:1158138379 @@ -4150,6 +4224,57 @@ namespace CodeWalker.GameFiles public Array_ushort Unk_3844724227 { get; set; } //248 248: Array: 0: 3844724227 {0: UnsignedShort: 0: 256} public Array_Structure Clusters { get; set; } //264 264: Array: 0: Clusters//3587988394 {0: Structure: CScenarioPointCluster//750308016: 256} public CScenarioPointLookUps LookUps { get; set; } //280 280: Structure: CScenarioPointLookUps//3019621867: LookUps//1097626284 + + public override bool Equals(object obj) + { + return obj is CScenarioPointRegion region && + VersionNumber == region.VersionNumber && + Unused0 == region.Unused0 && + EqualityComparer.Default.Equals(Points, region.Points) && + Unused1 == region.Unused1 && + Unused2 == region.Unused2 && + Unused3 == region.Unused3 && + Unused4 == region.Unused4 && + EqualityComparer.Default.Equals(EntityOverrides, region.EntityOverrides) && + Unused5 == region.Unused5 && + Unused6 == region.Unused6 && + EqualityComparer.Default.Equals(ChainingGraph, region.ChainingGraph) && + EqualityComparer.Default.Equals(AccelGrid, region.AccelGrid) && + EqualityComparer.Default.Equals(Unk_3844724227, region.Unk_3844724227) && + EqualityComparer.Default.Equals(Clusters, region.Clusters) && + EqualityComparer.Default.Equals(LookUps, region.LookUps); + } + + public override int GetHashCode() + { + int hashCode = 1436693857; + hashCode = hashCode * -1521134295 + VersionNumber.GetHashCode(); + hashCode = hashCode * -1521134295 + Unused0.GetHashCode(); + hashCode = hashCode * -1521134295 + Points.GetHashCode(); + hashCode = hashCode * -1521134295 + Unused1.GetHashCode(); + hashCode = hashCode * -1521134295 + Unused2.GetHashCode(); + hashCode = hashCode * -1521134295 + Unused3.GetHashCode(); + hashCode = hashCode * -1521134295 + Unused4.GetHashCode(); + hashCode = hashCode * -1521134295 + EntityOverrides.GetHashCode(); + hashCode = hashCode * -1521134295 + Unused5.GetHashCode(); + hashCode = hashCode * -1521134295 + Unused6.GetHashCode(); + hashCode = hashCode * -1521134295 + ChainingGraph.GetHashCode(); + hashCode = hashCode * -1521134295 + AccelGrid.GetHashCode(); + hashCode = hashCode * -1521134295 + Unk_3844724227.GetHashCode(); + hashCode = hashCode * -1521134295 + Clusters.GetHashCode(); + hashCode = hashCode * -1521134295 + LookUps.GetHashCode(); + return hashCode; + } + + public static bool operator ==(CScenarioPointRegion left, CScenarioPointRegion right) + { + return left.Equals(right); + } + + public static bool operator !=(CScenarioPointRegion left, CScenarioPointRegion right) + { + return !(left == right); + } } [TC(typeof(EXP))] public class MCScenarioPointRegion : MetaWrapper { @@ -4531,10 +4656,43 @@ namespace CodeWalker.GameFiles public uint Unused2 { get; set; }//40 public uint Unused3 { get; set; }//44 + public override bool Equals(object obj) + { + return obj is CScenarioPointContainer container && + EqualityComparer.Default.Equals(LoadSavePoints, container.LoadSavePoints) && + EqualityComparer.Default.Equals(MyPoints, container.MyPoints) && + Unused0 == container.Unused0 && + Unused1 == container.Unused1 && + Unused2 == container.Unused2 && + Unused3 == container.Unused3; + } + + public override int GetHashCode() + { + int hashCode = 746587205; + hashCode = hashCode * -1521134295 + LoadSavePoints.GetHashCode(); + hashCode = hashCode * -1521134295 + MyPoints.GetHashCode(); + hashCode = hashCode * -1521134295 + Unused0.GetHashCode(); + hashCode = hashCode * -1521134295 + Unused1.GetHashCode(); + hashCode = hashCode * -1521134295 + Unused2.GetHashCode(); + hashCode = hashCode * -1521134295 + Unused3.GetHashCode(); + return hashCode; + } + public override string ToString() { return LoadSavePoints.Count1.ToString() + " LoadSavePoints, " + MyPoints.Count1.ToString() + " MyPoints"; } + + public static bool operator ==(CScenarioPointContainer left, CScenarioPointContainer right) + { + return left.Equals(right); + } + + public static bool operator !=(CScenarioPointContainer left, CScenarioPointContainer right) + { + return !(left == right); + } } [TC(typeof(EXP))] public class MCScenarioPointContainer : MetaWrapper { @@ -5032,6 +5190,45 @@ namespace CodeWalker.GameFiles { return Nodes.Count1.ToString() + " Nodes, " + Edges.Count1.ToString() + " Edges, " + Chains.Count1.ToString() + " Chains"; } + + public static bool operator !=(CScenarioChainingGraph x, CScenarioChainingGraph y) + { + return !x.Equals(y); + } + + public static bool operator ==(CScenarioChainingGraph x, CScenarioChainingGraph y) + { + return x.Equals(y); + } + + public override bool Equals(object obj) + { + if (obj is null || obj is not CScenarioChainingGraph arrObj) + return false; + + return arrObj.Nodes == this.Nodes + && arrObj.Edges == this.Edges + && arrObj.Chains == this.Chains; + } + + public override int GetHashCode() + { + int hashCode = -1851298727; + hashCode = hashCode * -1521134295 + Nodes.GetHashCode(); + hashCode = hashCode * -1521134295 + Edges.GetHashCode(); + hashCode = hashCode * -1521134295 + Chains.GetHashCode(); + hashCode = hashCode * -1521134295 + Unused0.GetHashCode(); + hashCode = hashCode * -1521134295 + Unused1.GetHashCode(); + hashCode = hashCode * -1521134295 + Unused2.GetHashCode(); + hashCode = hashCode * -1521134295 + Unused3.GetHashCode(); + hashCode = hashCode * -1521134295 + Unused4.GetHashCode(); + hashCode = hashCode * -1521134295 + Unused5.GetHashCode(); + hashCode = hashCode * -1521134295 + Unused6.GetHashCode(); + hashCode = hashCode * -1521134295 + Unused7.GetHashCode(); + hashCode = hashCode * -1521134295 + Unused8.GetHashCode(); + hashCode = hashCode * -1521134295 + Unused9.GetHashCode(); + return hashCode; + } } [TC(typeof(EXP))] public class MCScenarioChainingGraph : MetaWrapper { @@ -5731,6 +5928,39 @@ namespace CodeWalker.GameFiles { return "CScenarioPointLookUps"; } + + public override bool Equals(object obj) + { + return obj is CScenarioPointLookUps ups && + EqualityComparer.Default.Equals(TypeNames, ups.TypeNames) && + EqualityComparer.Default.Equals(PedModelSetNames, ups.PedModelSetNames) && + EqualityComparer.Default.Equals(VehicleModelSetNames, ups.VehicleModelSetNames) && + EqualityComparer.Default.Equals(GroupNames, ups.GroupNames) && + EqualityComparer.Default.Equals(InteriorNames, ups.InteriorNames) && + EqualityComparer.Default.Equals(RequiredIMapNames, ups.RequiredIMapNames); + } + + public override int GetHashCode() + { + int hashCode = -153113894; + hashCode = hashCode * -1521134295 + TypeNames.GetHashCode(); + hashCode = hashCode * -1521134295 + PedModelSetNames.GetHashCode(); + hashCode = hashCode * -1521134295 + VehicleModelSetNames.GetHashCode(); + hashCode = hashCode * -1521134295 + GroupNames.GetHashCode(); + hashCode = hashCode * -1521134295 + InteriorNames.GetHashCode(); + hashCode = hashCode * -1521134295 + RequiredIMapNames.GetHashCode(); + return hashCode; + } + + public static bool operator ==(CScenarioPointLookUps left, CScenarioPointLookUps right) + { + return left.Equals(right); + } + + public static bool operator !=(CScenarioPointLookUps left, CScenarioPointLookUps right) + { + return !(left == right); + } } [TC(typeof(EXP))] public class MCScenarioPointLookUps : MetaWrapper { diff --git a/CodeWalker.Core/GameFiles/MetaTypes/MetaXml.cs b/CodeWalker.Core/GameFiles/MetaTypes/MetaXml.cs index 15f8715..764de58 100644 --- a/CodeWalker.Core/GameFiles/MetaTypes/MetaXml.cs +++ b/CodeWalker.Core/GameFiles/MetaTypes/MetaXml.cs @@ -1,4 +1,6 @@ -using SharpDX; + + +using SharpDX; using System; using System.Collections.Generic; using System.IO; @@ -15,144 +17,143 @@ namespace CodeWalker.GameFiles public static string GetXml(RpfFileEntry e, byte[] data, out string filename, string outputfolder = "") { var fn = e.Name; - var fnl = fn.ToLowerInvariant(); if (!string.IsNullOrEmpty(outputfolder)) { - outputfolder = Path.Combine(outputfolder, e.GetShortName()); + outputfolder = Path.Combine(outputfolder, e.ShortName); } - if (fnl.EndsWith(".ymt")) + if (fn.EndsWith(".ymt", StringComparison.OrdinalIgnoreCase)) { YmtFile ymt = RpfFile.GetFile(e, data); return GetXml(ymt, out filename); } - else if (fnl.EndsWith(".ymf")) + else if (fn.EndsWith(".ymf", StringComparison.OrdinalIgnoreCase)) { YmfFile ymf = RpfFile.GetFile(e, data); return GetXml(ymf, out filename); } - else if (fnl.EndsWith(".ymap")) + else if (fn.EndsWith(".ymap", StringComparison.OrdinalIgnoreCase)) { YmapFile ymap = RpfFile.GetFile(e, data); return GetXml(ymap, out filename); } - else if (fnl.EndsWith(".ytyp")) + else if (fn.EndsWith(".ytyp", StringComparison.OrdinalIgnoreCase)) { YtypFile ytyp = RpfFile.GetFile(e, data); return GetXml(ytyp, out filename); } - else if (fnl.EndsWith(".pso")) + else if (fn.EndsWith(".pso", StringComparison.OrdinalIgnoreCase)) { JPsoFile pso = RpfFile.GetFile(e, data); return GetXml(pso, out filename); } - else if (fnl.EndsWith(".cut")) + else if (fn.EndsWith(".cut", StringComparison.OrdinalIgnoreCase)) { CutFile cut = RpfFile.GetFile(e, data); return GetXml(cut, out filename); } - else if (fnl.EndsWith(".rel")) + else if (fn.EndsWith(".rel", StringComparison.OrdinalIgnoreCase)) { RelFile rel = RpfFile.GetFile(e, data); return GetXml(rel, out filename); } - else if (fnl.EndsWith(".ynd")) + else if (fn.EndsWith(".ynd", StringComparison.OrdinalIgnoreCase)) { YndFile ynd = RpfFile.GetFile(e, data); return GetXml(ynd, out filename); } - else if (fnl.EndsWith(".ynv")) + else if (fn.EndsWith(".ynv", StringComparison.OrdinalIgnoreCase)) { YnvFile ynv = RpfFile.GetFile(e, data); return GetXml(ynv, out filename); } - else if (fnl.EndsWith(".ycd")) + else if (fn.EndsWith(".ycd", StringComparison.OrdinalIgnoreCase)) { YcdFile ycd = RpfFile.GetFile(e, data); return GetXml(ycd, out filename); } - else if (fnl.EndsWith(".ybn")) + else if (fn.EndsWith(".ybn", StringComparison.OrdinalIgnoreCase)) { YbnFile ybn = RpfFile.GetFile(e, data); return GetXml(ybn, out filename); } - else if (fnl.EndsWith(".ytd")) + else if (fn.EndsWith(".ytd", StringComparison.OrdinalIgnoreCase)) { YtdFile ytd = RpfFile.GetFile(e, data); return GetXml(ytd, out filename, outputfolder); } - else if (fnl.EndsWith(".ydr")) + else if (fn.EndsWith(".ydr", StringComparison.OrdinalIgnoreCase)) { YdrFile ydr = RpfFile.GetFile(e, data); return GetXml(ydr, out filename, outputfolder); } - else if (fnl.EndsWith(".ydd")) + else if (fn.EndsWith(".ydd", StringComparison.OrdinalIgnoreCase)) { YddFile ydd = RpfFile.GetFile(e, data); return GetXml(ydd, out filename, outputfolder); } - else if (fnl.EndsWith(".yft")) + else if (fn.EndsWith(".yft", StringComparison.OrdinalIgnoreCase)) { YftFile yft = RpfFile.GetFile(e, data); return GetXml(yft, out filename, outputfolder); } - else if (fnl.EndsWith(".ypt")) + else if (fn.EndsWith(".ypt", StringComparison.OrdinalIgnoreCase)) { YptFile ypt = RpfFile.GetFile(e, data); return GetXml(ypt, out filename, outputfolder); } - else if (fnl.EndsWith(".yld")) + else if (fn.EndsWith(".yld", StringComparison.OrdinalIgnoreCase)) { YldFile yld = RpfFile.GetFile(e, data); return GetXml(yld, out filename); } - else if (fnl.EndsWith(".yed")) + else if (fn.EndsWith(".yed", StringComparison.OrdinalIgnoreCase)) { YedFile yed = RpfFile.GetFile(e, data); return GetXml(yed, out filename); } - else if (fnl.EndsWith(".ywr")) + else if (fn.EndsWith(".ywr", StringComparison.OrdinalIgnoreCase)) { YwrFile ywr = RpfFile.GetFile(e, data); return GetXml(ywr, out filename); } - else if (fnl.EndsWith(".yvr")) + else if (fn.EndsWith(".yvr", StringComparison.OrdinalIgnoreCase)) { YvrFile yvr = RpfFile.GetFile(e, data); return GetXml(yvr, out filename); } - else if (fnl.EndsWith(".ypdb")) + else if (fn.EndsWith(".ypdb", StringComparison.OrdinalIgnoreCase)) { YpdbFile ypdb = RpfFile.GetFile(e, data); return GetXml(ypdb, out filename); } - else if (fnl.EndsWith(".yfd")) + else if (fn.EndsWith(".yfd", StringComparison.OrdinalIgnoreCase)) { YfdFile yfd = RpfFile.GetFile(e, data); return GetXml(yfd, out filename); } - else if (fnl.EndsWith(".awc")) + else if (fn.EndsWith(".awc", StringComparison.OrdinalIgnoreCase)) { AwcFile awc = RpfFile.GetFile(e, data); return GetXml(awc, out filename, outputfolder); } - else if (fnl.EndsWith(".fxc")) + else if (fn.EndsWith(".fxc", StringComparison.OrdinalIgnoreCase)) { FxcFile fxc = RpfFile.GetFile(e, data); return GetXml(fxc, out filename, outputfolder); } - else if (fnl.EndsWith("cache_y.dat")) + else if (fn.EndsWith("cache_y.dat", StringComparison.OrdinalIgnoreCase)) { CacheDatFile cdf = RpfFile.GetFile(e, data); return GetXml(cdf, out filename, outputfolder); } - else if (fnl.EndsWith(".dat") && fnl.StartsWith("heightmap")) + else if (fn.EndsWith(".dat", StringComparison.OrdinalIgnoreCase) && fn.StartsWith("heightmap", StringComparison.OrdinalIgnoreCase)) { HeightmapFile hmf = RpfFile.GetFile(e, data); return GetXml(hmf, out filename, outputfolder); } - else if (fnl.EndsWith(".mrf")) + else if (fn.EndsWith(".mrf", StringComparison.OrdinalIgnoreCase)) { MrfFile mrf = RpfFile.GetFile(e, data); return GetXml(mrf, out filename, outputfolder); @@ -1653,10 +1654,8 @@ namespace CodeWalker.GameFiles for (int i = 0; i < pso.SchemaSection.Entries.Length; i++) { var entry = pso.SchemaSection.Entries[i]; - var enuminfo = entry as PsoEnumInfo; - var structinfo = entry as PsoStructureInfo; - if (enuminfo != null) + if (entry is PsoEnumInfo enuminfo) { if (!EnumDict.ContainsKey(enuminfo.IndexInfo.NameHash)) { @@ -1670,7 +1669,7 @@ namespace CodeWalker.GameFiles //} } } - else if (structinfo != null) + else if (entry is PsoStructureInfo structinfo) { if (!StructDict.ContainsKey(structinfo.IndexInfo.NameHash)) { @@ -1691,14 +1690,12 @@ namespace CodeWalker.GameFiles public PsoStructureInfo GetStructureInfo(MetaName name) { - PsoStructureInfo i = null; - StructDict.TryGetValue(name, out i); + StructDict.TryGetValue(name, out PsoStructureInfo i); return i; } public PsoEnumInfo GetEnumInfo(MetaName name) { - PsoEnumInfo i = null; - EnumDict.TryGetValue(name, out i); + EnumDict.TryGetValue(name, out PsoEnumInfo i); return i; } @@ -2004,7 +2001,7 @@ namespace CodeWalker.GameFiles var aind = ind + 1; if (itemCount > 0) { - OpenTag(sb, ind, arrTag); + OpenTag(sb, ind, arrTag, metaName: typeName); for (int n = 0; n < itemCount; n++) { Indent(sb, aind); diff --git a/CodeWalker.Core/GameFiles/MetaTypes/Pso.cs b/CodeWalker.Core/GameFiles/MetaTypes/Pso.cs index fd6b074..dc7977c 100644 --- a/CodeWalker.Core/GameFiles/MetaTypes/Pso.cs +++ b/CodeWalker.Core/GameFiles/MetaTypes/Pso.cs @@ -337,16 +337,26 @@ namespace CodeWalker.GameFiles var reader = new DataReader(stream, Endianess.BigEndian); var identInt = reader.ReadUInt32(); stream.Position = 0; - return ((identInt ) == 1347635534); //"PSIN" + return IsPSO(identInt); //"PSIN" } + public static bool IsPSO(uint identInt) + { + return identInt == 1347635534; + } + public static bool IsRBF(Stream stream) { var reader = new DataReader(stream, Endianess.BigEndian); var identInt = reader.ReadUInt32(); stream.Position = 0; - return ((identInt & 0xFFFFFF00) == 0x52424600); + return IsRBF(identInt); + } + + public static bool IsRBF(uint identInt) + { + return (identInt & 0xFFFFFF00) == 0x52424600; } } @@ -871,15 +881,15 @@ namespace CodeWalker.GameFiles foreach (var str in strs) { JenkIndex.Ensure(str); - JenkIndex.Ensure(str.ToLowerInvariant()); + JenkIndex.EnsureLower(str); } Strings = strs.ToArray(); } public void Write(DataWriter writer) { - var strStream = new MemoryStream(); - var strWriter = new DataWriter(strStream, Endianess.BigEndian); + using var strStream = new MemoryStream(); + using var strWriter = new DataWriter(strStream, Endianess.BigEndian); foreach (var str in Strings) { strWriter.Write(str); @@ -926,7 +936,7 @@ namespace CodeWalker.GameFiles foreach (var str in strs) { JenkIndex.Ensure(str); - JenkIndex.Ensure(str.ToLowerInvariant()); + JenkIndex.EnsureLower(str); } Strings = strs.ToArray(); } diff --git a/CodeWalker.Core/GameFiles/MetaTypes/PsoTypes.cs b/CodeWalker.Core/GameFiles/MetaTypes/PsoTypes.cs index e6d9c3f..b531f5e 100644 --- a/CodeWalker.Core/GameFiles/MetaTypes/PsoTypes.cs +++ b/CodeWalker.Core/GameFiles/MetaTypes/PsoTypes.cs @@ -15819,46 +15819,52 @@ namespace CodeWalker.GameFiles public static T ConvertDataRaw(byte[] data) where T : struct { - MemoryMarshal.TryRead(data.AsSpan(), out T value); - - return value; + GCHandle handle = GCHandle.Alloc(data, GCHandleType.Pinned); + var h = handle.AddrOfPinnedObject(); + var r = Marshal.PtrToStructure(h); + handle.Free(); + return r; } + public static T ConvertDataRawOld(byte[] data, int offset) where T : struct + { + GCHandle handle = GCHandle.Alloc(data, GCHandleType.Pinned); + var h = handle.AddrOfPinnedObject(); + var r = Marshal.PtrToStructure(h + offset); + handle.Free(); + return r; + } + public static T ConvertDataRaw(byte[] data, int offset) where T : struct { - MemoryMarshal.TryRead(data.AsSpan(offset), out T value); - + MemoryMarshal.TryRead(data.AsSpan(offset), out var value); return value; - //return MemoryMarshal.GetReference(data.AsSpan(offset)); - //GCHandle handle = GCHandle.Alloc(data, GCHandleType.Pinned); - //var h = handle.AddrOfPinnedObject(); - //var r = Marshal.PtrToStructure(h + offset); - //handle.Free(); - //return r; } + public static T ConvertData(byte[] data, int offset) where T : struct, IPsoSwapEnd { - MemoryMarshal.TryRead(data.AsSpan(offset), out T value); - - value.SwapEnd(); - - return value; - //GCHandle handle = GCHandle.Alloc(data, GCHandleType.Pinned); - //var h = handle.AddrOfPinnedObject(); - //var r = Marshal.PtrToStructure(h + offset); - //handle.Free(); - //r.SwapEnd(); - //return r; + GCHandle handle = GCHandle.Alloc(data, GCHandleType.Pinned); + var h = handle.AddrOfPinnedObject(); + var r = Marshal.PtrToStructure(h + offset); + handle.Free(); + r.SwapEnd(); + return r; } - public static SpanConvertDataArrayRaw(byte[] data, int offset, int count) where T : struct + public static Span ConvertDataArrayRaw(byte[] data, int offset, int count) where T : struct { - return MemoryMarshal.Cast(data.AsSpan(offset, count * Marshal.SizeOf(typeof(T)))); + T[] items = new T[count]; + int itemsize = Marshal.SizeOf(typeof(T)); + //for (int i = 0; i < count; i++) + //{ + // int off = offset + i * itemsize; + // items[i] = ConvertDataRaw(data, off); + //} - //GCHandle handle = GCHandle.Alloc(items, GCHandleType.Pinned); - //var h = handle.AddrOfPinnedObject(); - //Marshal.Copy(data, offset, h, itemsize * count); - //handle.Free(); + GCHandle handle = GCHandle.Alloc(items, GCHandleType.Pinned); + var h = handle.AddrOfPinnedObject(); + Marshal.Copy(data, offset, h, itemsize * count); + handle.Free(); - //return items; + return items; } diff --git a/CodeWalker.Core/GameFiles/MetaTypes/Rbf.cs b/CodeWalker.Core/GameFiles/MetaTypes/Rbf.cs index f2f2e55..bbb5db6 100644 --- a/CodeWalker.Core/GameFiles/MetaTypes/Rbf.cs +++ b/CodeWalker.Core/GameFiles/MetaTypes/Rbf.cs @@ -108,8 +108,8 @@ namespace CodeWalker.GameFiles if (descriptorIndex == descriptors.Count) // new descriptor + data { var nameLength = reader.ReadInt16(); - var nameBytes = reader.ReadBytes(nameLength); - var name = Encoding.ASCII.GetString(nameBytes); + var name = reader.ReadStringLength(nameLength); + //var name = Encoding.ASCII.GetString(nameBytes); var descriptor = new RbfEntryDescription(); descriptor.Name = name; @@ -203,8 +203,7 @@ namespace CodeWalker.GameFiles case 0x60: { var valueLength = reader.ReadInt16(); - var valueBytes = reader.ReadBytes(valueLength); - var value = Encoding.ASCII.GetString(valueBytes); + var value = reader.ReadStringLength(valueLength); var stringValue = new RbfString(); stringValue.Name = descriptor.Name; stringValue.Value = value; diff --git a/CodeWalker.Core/GameFiles/Resources/Bounds.cs b/CodeWalker.Core/GameFiles/Resources/Bounds.cs index 5693d7d..f2b0a71 100644 --- a/CodeWalker.Core/GameFiles/Resources/Bounds.cs +++ b/CodeWalker.Core/GameFiles/Resources/Bounds.cs @@ -5044,8 +5044,8 @@ namespace CodeWalker.GameFiles var line = lines[i]; if (line[0] == '#') continue; - if (line.StartsWith(startLine)) continue; - if (line.StartsWith(endLine)) break; + if (line.StartsWith(startLine, StringComparison.OrdinalIgnoreCase)) continue; + if (line.StartsWith(endLine, StringComparison.OrdinalIgnoreCase)) break; string[] parts = line.Split(new[] { '\t', ' ' }, StringSplitOptions.RemoveEmptyEntries); diff --git a/CodeWalker.Core/GameFiles/Resources/Drawable.cs b/CodeWalker.Core/GameFiles/Resources/Drawable.cs index c2703fb..54d2518 100644 --- a/CodeWalker.Core/GameFiles/Resources/Drawable.cs +++ b/CodeWalker.Core/GameFiles/Resources/Drawable.cs @@ -4719,6 +4719,8 @@ namespace CodeWalker.GameFiles [TypeConverter(typeof(ExpandableObjectConverter))] public class DrawableBase : ResourceFileBase { + private DrawableModel[] allModels; + public override long BlockLength { get { return 168; } @@ -4800,7 +4802,17 @@ namespace CodeWalker.GameFiles public DrawableModelsBlock DrawableModels { get; set; } - public DrawableModel[] AllModels { get; set; } + public DrawableModel[] AllModels { + get + { + if (allModels is null) + { + BuildAllModels(); + } + return allModels; + } + set => allModels = value; + } public Dictionary VertexDecls { get; set; } public object Owner { get; set; } @@ -4867,7 +4879,7 @@ namespace CodeWalker.GameFiles this.DrawableModels = reader.ReadBlockAt((DrawableModelsPointer == 0) ? DrawableModelsHighPointer : DrawableModelsPointer, this); - BuildAllModels(); + //BuildAllModels(); BuildVertexDecls(); AssignGeometryShaders(ShaderGroup); @@ -5185,7 +5197,7 @@ namespace CodeWalker.GameFiles } BuildRenderMasks(); - BuildAllModels(); + //BuildAllModels(); BuildVertexDecls(); FileVFT = 1079456120; @@ -5236,13 +5248,54 @@ namespace CodeWalker.GameFiles public void BuildAllModels() { - var allModels = new List(); - if (DrawableModels?.High != null) allModels.AddRange(DrawableModels.High); - if (DrawableModels?.Med != null) allModels.AddRange(DrawableModels.Med); - if (DrawableModels?.Low != null) allModels.AddRange(DrawableModels.Low); - if (DrawableModels?.VLow != null) allModels.AddRange(DrawableModels.VLow); - if (DrawableModels?.Extra != null) allModels.AddRange(DrawableModels.Extra); - AllModels = allModels.ToArray(); + if (DrawableModels is null) + { + allModels = Array.Empty(); + return; + } + var length = (DrawableModels.High?.Length ?? 0) + (DrawableModels.Med?.Length ?? 0) + (DrawableModels.Low?.Length ?? 0) + (DrawableModels.VLow?.Length ?? 0); + allModels = new DrawableModel[length]; + var currIndex = 0; + if (DrawableModels.High != null) + { + for (int i = 0; i < DrawableModels.High.Length; i++) + { + allModels[currIndex] = DrawableModels.High[i]; + currIndex++; + } + } + if (DrawableModels.Med != null) + { + for (int i = 0; i < DrawableModels.Med.Length; i++) + { + allModels[currIndex] = DrawableModels.Med[i]; + currIndex++; + } + } + if (DrawableModels.Low != null) + { + for (int i = 0; i < DrawableModels.Low.Length; i++) + { + allModels[currIndex] = DrawableModels.Low[i]; + currIndex++; + } + } + if (DrawableModels.VLow != null) + { + for (int i = 0; i < DrawableModels.VLow.Length; i++) + { + allModels[currIndex] = DrawableModels.VLow[i]; + currIndex++; + } + } + if (DrawableModels.Extra != null) + { + for (int i = 0; i < DrawableModels.Extra.Length; i++) + { + allModels[currIndex] = DrawableModels.Extra[i]; + currIndex++; + } + } } public void BuildVertexDecls() diff --git a/CodeWalker.Core/GameFiles/Resources/Frag.cs b/CodeWalker.Core/GameFiles/Resources/Frag.cs index 7091840..b197f9d 100644 --- a/CodeWalker.Core/GameFiles/Resources/Frag.cs +++ b/CodeWalker.Core/GameFiles/Resources/Frag.cs @@ -1346,7 +1346,7 @@ namespace CodeWalker.GameFiles if (ItemDataByteLength != 0)//sometimes this is 0 and UnkUshort3>0, which is weird { - ShatterMapRowOffsets = reader.ReadStructs(ItemDataCount);//byte offsets for following array + ShatterMapRowOffsets = reader.ReadStructs(ItemDataCount).ToArray();//byte offsets for following array ShatterMap = new WindowShatterMapRow[ItemDataCount]; for (int i = 0; i < ItemDataCount; i++) { @@ -1942,7 +1942,13 @@ namespace CodeWalker.GameFiles public void BuildOffsets() { - var offs = new List(); + if (Windows == null) + { + TotalLength = 16u; + WindowOffsets = Array.Empty(); + return; + } + var offs = new List(Windows.Length); var bc = 16u; if (Windows != null) { diff --git a/CodeWalker.Core/GameFiles/Resources/Particle.cs b/CodeWalker.Core/GameFiles/Resources/Particle.cs index 8434c30..03b3bff 100644 --- a/CodeWalker.Core/GameFiles/Resources/Particle.cs +++ b/CodeWalker.Core/GameFiles/Resources/Particle.cs @@ -3711,14 +3711,17 @@ namespace CodeWalker.GameFiles public ParticleKeyframePropName(uint h) { Hash = h; } public ParticleKeyframePropName(string str) { - var strl = str?.ToLowerInvariant() ?? ""; - if (strl.StartsWith("hash_")) + if (string.IsNullOrEmpty(str)) { - Hash = Convert.ToUInt32(strl.Substring(5), 16); + Hash = 0; + } + else if (str.StartsWith("hash_", StringComparison.OrdinalIgnoreCase)) + { + Hash = Convert.ToUInt32(str.Substring(5), 16); } else { - Hash = JenkHash.GenHash(strl); + Hash = JenkHash.GenHashLower(str); } } diff --git a/CodeWalker.Core/GameFiles/Resources/ResourceBaseTypes.cs b/CodeWalker.Core/GameFiles/Resources/ResourceBaseTypes.cs index 9ccb354..e14e037 100644 --- a/CodeWalker.Core/GameFiles/Resources/ResourceBaseTypes.cs +++ b/CodeWalker.Core/GameFiles/Resources/ResourceBaseTypes.cs @@ -761,11 +761,15 @@ namespace CodeWalker.GameFiles //TODO: NEEDS TO BE TESTED!!! data_items = new T[EntriesCount]; var posbckp = reader.Position; - reader.Position = (long)EntriesPointer; - for (int i = 0; i < EntriesCount; i++) + if (EntriesCount > 0) { - data_items[i] = reader.ReadBlock(); + reader.Position = (long)EntriesPointer; + for (int i = 0; i < EntriesCount; i++) + { + data_items[i] = reader.ReadBlock(); + } } + reader.Position = posbckp; } diff --git a/CodeWalker.Core/GameFiles/Resources/ResourceBuilder.cs b/CodeWalker.Core/GameFiles/Resources/ResourceBuilder.cs index dba5d87..b3bbf4d 100644 --- a/CodeWalker.Core/GameFiles/Resources/ResourceBuilder.cs +++ b/CodeWalker.Core/GameFiles/Resources/ResourceBuilder.cs @@ -1,4 +1,5 @@ -using System; +using CodeWalker.Core.Utils; +using System; using System.Collections.Generic; using System.IO; using System.IO.Compression; @@ -470,7 +471,7 @@ namespace CodeWalker.GameFiles { DeflateStream ds = new DeflateStream(ms, CompressionMode.Decompress); MemoryStream outstr = RpfFile.recyclableMemoryStreamManager.GetStream("Decompress", data.Length); - ds.CopyTo(outstr); + ds.CopyToFast(outstr); return outstr.ToArray(); } } diff --git a/CodeWalker.Core/GameFiles/Resources/ResourceData.cs b/CodeWalker.Core/GameFiles/Resources/ResourceData.cs index f1d18d2..e980d39 100644 --- a/CodeWalker.Core/GameFiles/Resources/ResourceData.cs +++ b/CodeWalker.Core/GameFiles/Resources/ResourceData.cs @@ -23,7 +23,9 @@ //shamelessly stolen and mangled +using CodeWalker.Utils; using System; +using System.Buffers; using System.Collections.Generic; using System.ComponentModel; using System.IO; @@ -34,7 +36,6 @@ using System.Threading.Tasks; namespace CodeWalker.GameFiles { - /// /// Represents a resource data reader. /// @@ -53,8 +54,23 @@ namespace CodeWalker.GameFiles // this is a dictionary that contains all the resource blocks // which were read from this resource reader - public Dictionary blockPool = new Dictionary(); - public Dictionary arrayPool = new Dictionary(); + public Dictionary blockPool + { + get + { + return _blockPool ??= new Dictionary(); + } + } + public Dictionary arrayPool { + get + { + return _arrayPool ??= new Dictionary(); + } + } + + private Dictionary _arrayPool; + private Dictionary _blockPool; + private long position = SYSTEM_BASE; /// /// Gets the length of the underlying stream. @@ -72,10 +88,22 @@ namespace CodeWalker.GameFiles /// public override long Position { - get; - set; - } = SYSTEM_BASE; - + get => position; + set { + if ((value & SYSTEM_BASE) == SYSTEM_BASE) + { + systemStream.Position = value & ~SYSTEM_BASE; + } + else if ((value & GRAPHICS_BASE) == GRAPHICS_BASE) + { + graphicsStream.Position = value & ~GRAPHICS_BASE; + } else + { + throw new InvalidOperationException($"Invalid position {position}"); + } + position = value; + } + } /// /// Initializes a new resource data reader for the specified system- and graphics-stream. /// @@ -108,93 +136,55 @@ namespace CodeWalker.GameFiles // } //} - this.systemStream = new MemoryStream(data, 0, (int)systemSize); - this.graphicsStream = new MemoryStream(data, (int)systemSize, (int)graphicsSize); + if ((int)systemSize > data.Length) + { + throw new ArgumentException($"systemSize {systemSize} is larger than data length ({data.Length})", nameof(systemSize)); + } + if ((int)graphicsSize > data.Length) + { + throw new ArgumentException($"graphicsSize {graphicsSize} is larger than data length ({data.Length})", nameof(graphicsSize)); + } + this.systemStream = Stream.Synchronized(new MemoryStream(data, 0, (int)systemSize)); + this.graphicsStream = Stream.Synchronized(new MemoryStream(data, (int)systemSize, (int)graphicsSize)); } public ResourceDataReader(int systemSize, int graphicsSize, byte[] data, Endianess endianess = Endianess.LittleEndian) : base((Stream)null, endianess) { - this.systemStream = new MemoryStream(data, 0, systemSize); - this.graphicsStream = new MemoryStream(data, systemSize, graphicsSize); + this.systemStream = Stream.Synchronized(new MemoryStream(data, 0, systemSize)); + this.graphicsStream = Stream.Synchronized(new MemoryStream(data, systemSize, graphicsSize)); } - /// - /// Reads data from the underlying stream. This is the only method that directly accesses - /// the data in the underlying stream. - /// - protected override byte[] ReadFromStream(int count, bool ignoreEndianess = false, byte[] buffer = null) + public override Stream GetStream() { if ((Position & SYSTEM_BASE) == SYSTEM_BASE) { - // read from system stream... - - systemStream.Position = Position & ~SYSTEM_BASE; - - buffer ??= new byte[count]; - systemStream.Read(buffer, 0, count); - - // handle endianess - if (!ignoreEndianess && (Endianess == Endianess.BigEndian)) - { - Array.Reverse(buffer); - } - - Position = systemStream.Position | SYSTEM_BASE; - return buffer; - + return systemStream; } else if ((Position & GRAPHICS_BASE) == GRAPHICS_BASE) { - // read from graphic stream... - - graphicsStream.Position = Position & ~GRAPHICS_BASE; - - buffer ??= new byte[count]; - graphicsStream.Read(buffer, 0, count); - - // handle endianess - if (!ignoreEndianess && (Endianess == Endianess.BigEndian)) - { - Array.Reverse(buffer); - } - - Position = graphicsStream.Position | GRAPHICS_BASE; - return buffer; + return graphicsStream; } - throw new Exception("illegal position!"); + + throw new InvalidOperationException("illegal position!"); } - public override byte ReadByte() + internal override void SetPositionAfterRead(Stream stream) { - if ((Position & SYSTEM_BASE) == SYSTEM_BASE) + + if (stream == systemStream) { - - - - - // read from system stream... - - systemStream.Position = Position & ~SYSTEM_BASE; - - var readByte = (byte)systemStream.ReadByte(); - - Position = systemStream.Position | SYSTEM_BASE; - return readByte; - + position = systemStream.Position | SYSTEM_BASE; } - if ((Position & GRAPHICS_BASE) == GRAPHICS_BASE) + else if (stream == graphicsStream) { - // read from graphic stream... - - graphicsStream.Position = Position & ~GRAPHICS_BASE; - - var readByte = (byte)graphicsStream.ReadByte(); - - Position = graphicsStream.Position | GRAPHICS_BASE; - return readByte; + position = graphicsStream.Position | GRAPHICS_BASE; + } + + if ((position & SYSTEM_BASE) != SYSTEM_BASE && (position & GRAPHICS_BASE) != GRAPHICS_BASE) + { + throw new InvalidOperationException($"Invalid position {position}"); } - throw new Exception("illegal position!"); } /// @@ -233,7 +223,7 @@ namespace CodeWalker.GameFiles if (result == null) { - return default(T); + return default; } if (usepool) @@ -279,14 +269,14 @@ namespace CodeWalker.GameFiles return items; } - - public byte[] ReadBytesAt(ulong position, uint count, bool cache = true) + internal const int StackallocThreshold = 512; + public unsafe byte[] ReadBytesAt(ulong position, uint count, bool cache = true, byte[] buffer = null) { long pos = (long)position; if ((pos <= 0) || (count == 0)) return null; var posbackup = Position; Position = pos; - var result = ReadBytes((int)count); + var result = ReadBytes((int)count, buffer); Position = posbackup; if (cache) arrayPool[(long)position] = result; return result; @@ -296,9 +286,18 @@ namespace CodeWalker.GameFiles if ((position <= 0) || (count == 0)) return null; var result = new ushort[count]; - var length = count * 2; - byte[] data = ReadBytesAt(position, length, false); - Buffer.BlockCopy(data, 0, result, 0, (int)length); + var length = count * sizeof(ushort); + var data = ArrayPool.Shared.Rent((int)length); + try + { + ReadBytesAt(position, length, false, data); + Buffer.BlockCopy(data, 0, result, 0, (int)length); + } + finally + { + ArrayPool.Shared.Return(data); + } + //var posbackup = Position; //Position = position; @@ -317,9 +316,18 @@ namespace CodeWalker.GameFiles { if ((position <= 0) || (count == 0)) return null; var result = new short[count]; - var length = count * 2; - byte[] data = ReadBytesAt(position, length, false); - Buffer.BlockCopy(data, 0, result, 0, (int)length); + var length = count * sizeof(short); + var buffer = ArrayPool.Shared.Rent((int)length); + try + { + ReadBytesAt(position, length, false, buffer); + Buffer.BlockCopy(buffer, 0, result, 0, (int)length); + } + finally + { + ArrayPool.Shared.Return(buffer); + } + if (cache) arrayPool[(long)position] = result; @@ -330,18 +338,17 @@ namespace CodeWalker.GameFiles if ((position <= 0) || (count == 0)) return null; var result = new uint[count]; - var length = count * 4; - byte[] data = ReadBytesAt(position, length, false); - Buffer.BlockCopy(data, 0, result, 0, (int)length); + var length = count * sizeof(uint); + var buffer = ArrayPool.Shared.Rent((int)length); + try + { + ReadBytesAt(position, length, false, buffer); + Buffer.BlockCopy(buffer, 0, result, 0, (int)length); + } finally + { + ArrayPool.Shared.Return(buffer); + } - //var posbackup = Position; - //Position = position; - //var result = new uint[count]; - //for (uint i = 0; i < count; i++) - //{ - // result[i] = ReadUInt32(); - //} - //Position = posbackup; if (cache) arrayPool[(long)position] = result; @@ -352,18 +359,17 @@ namespace CodeWalker.GameFiles if ((position <= 0) || (count == 0)) return null; var result = new ulong[count]; - var length = count * 8; - byte[] data = ReadBytesAt(position, length, false); - Buffer.BlockCopy(data, 0, result, 0, (int)length); - - //var posbackup = Position; - //Position = position; - //var result = new ulong[count]; - //for (uint i = 0; i < count; i++) - //{ - // result[i] = ReadUInt64(); - //} - //Position = posbackup; + var length = count * sizeof(ulong); + var data = ArrayPool.Shared.Rent((int)length); + try + { + ReadBytesAt(position, length, false, data); + Buffer.BlockCopy(data, 0, result, 0, (int)length); + } + finally + { + ArrayPool.Shared.Return(data); + } if (cache) arrayPool[(long)position] = result; @@ -374,88 +380,85 @@ namespace CodeWalker.GameFiles if ((position <= 0) || (count == 0)) return null; var result = new float[count]; - var length = count * 4; - byte[] data = ReadBytesAt(position, length, false); - Buffer.BlockCopy(data, 0, result, 0, (int)length); - - //var posbackup = Position; - //Position = position; - //var result = new float[count]; - //for (uint i = 0; i < count; i++) - //{ - // result[i] = ReadSingle(); - //} - //Position = posbackup; + var length = count * sizeof(float); + var data = ArrayPool.Shared.Rent((int)length); + try + { + ReadBytesAt(position, length, false, data); + Buffer.BlockCopy(data, 0, result, 0, (int)length); + } + finally + { + ArrayPool.Shared.Return(data); + } if (cache) arrayPool[(long)position] = result; return result; } - public T[] ReadStructsAt(ulong position, uint count, bool cache = true) + public T[] ReadStructsAt(ulong position, uint count, bool cache = true) where T : struct { if ((position <= 0) || (count == 0)) return null; uint structsize = (uint)Marshal.SizeOf(typeof(T)); var length = count * structsize; - byte[] data = ReadBytesAt(position, length, false); + var data = ArrayPool.Shared.Rent((int)length); + try + { + ReadBytesAt(position, length, false, data); - //var result2 = new T[count]; - //Buffer.BlockCopy(data, 0, result2, 0, (int)length); //error: "object must be an array of primitives" :( + var result = new T[count]; - //var result = new T[count]; - //GCHandle handle = GCHandle.Alloc(data, GCHandleType.Pinned); - //var h = handle.AddrOfPinnedObject(); - //for (uint i = 0; i < count; i++) - //{ - // result[i] = Marshal.PtrToStructure(h + (int)(i * structsize)); - //} - //handle.Free(); + var resultSpan = MemoryMarshal.Cast(data.AsSpan(0, (int)length)); + resultSpan.CopyTo(result); - var result = new T[count]; - GCHandle handle = GCHandle.Alloc(result, GCHandleType.Pinned); - var h = handle.AddrOfPinnedObject(); - Marshal.Copy(data, 0, h, (int)length); - handle.Free(); + if (cache) arrayPool[(long)position] = result; - - if (cache) arrayPool[(long)position] = result; - - return result; + return result; + } + finally + { + ArrayPool.Shared.Return(data); + } } - public T[] ReadStructs(uint count) + public T[] ReadStructs(uint count) where T : struct { uint structsize = (uint)Marshal.SizeOf(typeof(T)); var result = new T[count]; var length = count * structsize; - byte[] data = ReadBytes((int)length); + var data = ArrayPool.Shared.Rent((int)length); + try + { + ReadBytes((int)length, data); - //GCHandle handle = GCHandle.Alloc(data, GCHandleType.Pinned); - //var h = handle.AddrOfPinnedObject(); - //for (uint i = 0; i < count; i++) - //{ - // result[i] = Marshal.PtrToStructure(h + (int)(i * structsize)); - //} - //handle.Free(); + var resultSpan = MemoryMarshal.Cast(data.AsSpan(0, (int)length)); + resultSpan.CopyTo(result); - GCHandle handle = GCHandle.Alloc(result, GCHandleType.Pinned); - var h = handle.AddrOfPinnedObject(); - Marshal.Copy(data, 0, h, (int)length); - handle.Free(); + return result; + } + finally + { + ArrayPool.Shared.Return(data); + } - - return result; } public T ReadStruct() where T : struct { uint structsize = (uint)Marshal.SizeOf(typeof(T)); var length = structsize; - byte[] data = ReadBytes((int)length); - GCHandle handle = GCHandle.Alloc(data, GCHandleType.Pinned); - var h = handle.AddrOfPinnedObject(); - var result = Marshal.PtrToStructure(h); - handle.Free(); - return result; + var data = ArrayPool.Shared.Rent((int)length); + try + { + ReadBytes((int)length, data); + MemoryMarshal.TryRead(data, out var value); + + return value; + } finally + { + ArrayPool.Shared.Return(data); + } + } public T ReadStructAt(long position) where T : struct @@ -513,13 +516,22 @@ namespace CodeWalker.GameFiles } } - /// - /// Gets or sets the position within the underlying stream. - /// + private long position = SYSTEM_BASE; public override long Position { - get; - set; + get => position; + set + { + if ((value & SYSTEM_BASE) == SYSTEM_BASE) + { + systemStream.Position = value & ~SYSTEM_BASE; + } + else if ((value & GRAPHICS_BASE) == GRAPHICS_BASE) + { + graphicsStream.Position = value & ~GRAPHICS_BASE; + } + position = value; + } } /// @@ -528,61 +540,33 @@ namespace CodeWalker.GameFiles public ResourceDataWriter(Stream systemStream, Stream graphicsStream, Endianess endianess = Endianess.LittleEndian) : base((Stream)null, endianess) { - this.systemStream = systemStream; - this.graphicsStream = graphicsStream; + this.systemStream = Stream.Synchronized(systemStream); + this.graphicsStream = Stream.Synchronized(graphicsStream); } - /// - /// Writes data to the underlying stream. This is the only method that directly accesses - /// the data in the underlying stream. - /// - protected override void WriteToStream(byte[] value, bool ignoreEndianess = true) + internal override Stream GetStream() { if ((Position & SYSTEM_BASE) == SYSTEM_BASE) { - // write to system stream... - - systemStream.Position = Position & ~SYSTEM_BASE; - - // handle endianess - if (!ignoreEndianess && (Endianess == Endianess.BigEndian)) - { - var buf = (byte[])value.Clone(); - Array.Reverse(buf); - systemStream.Write(buf, 0, buf.Length); - } - else - { - systemStream.Write(value, 0, value.Length); - } - - Position = systemStream.Position | 0x50000000; - return; - + return systemStream; } - if ((Position & GRAPHICS_BASE) == GRAPHICS_BASE) + else if ((Position & GRAPHICS_BASE) == GRAPHICS_BASE) { - // write to graphic stream... - - graphicsStream.Position = Position & ~GRAPHICS_BASE; - - // handle endianess - if (!ignoreEndianess && (Endianess == Endianess.BigEndian)) - { - var buf = (byte[])value.Clone(); - Array.Reverse(buf); - graphicsStream.Write(buf, 0, buf.Length); - } - else - { - graphicsStream.Write(value, 0, value.Length); - } - - Position = graphicsStream.Position | 0x60000000; - return; + return graphicsStream; } + throw new InvalidOperationException("illegal position!"); + } - throw new Exception("illegal position!"); + internal override void SetPositionAfterWrite(Stream stream) + { + if (stream == systemStream) + { + position = systemStream.Position | SYSTEM_BASE; + } + else if (stream == graphicsStream) + { + position = graphicsStream.Position | GRAPHICS_BASE; + } } /// @@ -599,20 +583,41 @@ namespace CodeWalker.GameFiles public void WriteStruct(T val) where T : struct { int size = Marshal.SizeOf(typeof(T)); - byte[] arr = new byte[size]; - IntPtr ptr = Marshal.AllocHGlobal(size); - Marshal.StructureToPtr(val, ptr, true); - Marshal.Copy(ptr, arr, 0, size); - Marshal.FreeHGlobal(ptr); + + var arr = new byte[size]; + + MemoryMarshal.TryWrite(arr, ref val); + Write(arr); + + //byte[] arr = new byte[size]; + //IntPtr ptr = Marshal.AllocHGlobal(size); + //Marshal.StructureToPtr(val, ptr, true); + //Marshal.Copy(ptr, arr, 0, size); + //Marshal.FreeHGlobal(ptr); + //Write(arr); } + + public void WriteStructs(Span val) where T : struct + { + if (val == null) return; + + var bytes = MemoryMarshal.AsBytes(val); + + Write(bytes); + + //foreach (var v in val) + //{ + // WriteStruct(v); + //} + } + public void WriteStructs(T[] val) where T : struct { if (val == null) return; - foreach (var v in val) - { - WriteStruct(v); - } + var bytes = MemoryMarshal.AsBytes(val); + + Write(bytes); } diff --git a/CodeWalker.Core/GameFiles/Resources/Texture.cs b/CodeWalker.Core/GameFiles/Resources/Texture.cs index 26f0127..89d7788 100644 --- a/CodeWalker.Core/GameFiles/Resources/Texture.cs +++ b/CodeWalker.Core/GameFiles/Resources/Texture.cs @@ -636,8 +636,8 @@ namespace CodeWalker.GameFiles /// public override void Read(ResourceDataReader reader, params object[] parameters) { - uint format = Convert.ToUInt32(parameters[0]); - int Width = Convert.ToInt32(parameters[1]); + //uint format = Convert.ToUInt32(parameters[0]); + //int Width = Convert.ToInt32(parameters[1]); int Height = Convert.ToInt32(parameters[2]); int Levels = Convert.ToInt32(parameters[3]); int Stride = Convert.ToInt32(parameters[4]); @@ -646,7 +646,7 @@ namespace CodeWalker.GameFiles int length = Stride * Height; for (int i = 0; i < Levels; i++) { - fullLength += length; + fullLength += Math.Max(length, 4 * 4); length /= 4; } diff --git a/CodeWalker.Core/GameFiles/RpfFile.cs b/CodeWalker.Core/GameFiles/RpfFile.cs index bb3afb7..5459b11 100644 --- a/CodeWalker.Core/GameFiles/RpfFile.cs +++ b/CodeWalker.Core/GameFiles/RpfFile.cs @@ -1,4 +1,5 @@ -using Microsoft.IO; +using CodeWalker.Core.Utils; +using Microsoft.IO; using System; using System.Buffers; using System.Buffers.Binary; @@ -20,24 +21,6 @@ namespace CodeWalker.GameFiles public class RpfFile { public string Name { get; set; } //name of this RPF file/package - - - private string _nameLower; - public string NameLower - { - get - { - if (_nameLower == null) - { - _nameLower = Name.ToLowerInvariant(); - } - return _nameLower; - } - set - { - _nameLower = value; - } - } public string Path { get; set; } //path within the RPF structure public string FilePath { get; set; } //full file path of the RPF public long FileSize { get; set; } @@ -80,19 +63,48 @@ namespace CodeWalker.GameFiles public uint GrandTotalBinaryFileCount { get; set; } public long ExtractedByteCount { get; set; } + static RpfFile() + { + recyclableMemoryStreamManager.BufferDiscarded += (sender, args) => + { + Console.WriteLine($"Buffer Discarded: BufferType: {args.BufferType}; Reason: {args.Reason};"); + }; + + recyclableMemoryStreamManager.StreamDoubleDisposed += (sender, args) => + { + Console.WriteLine($"StreamDoubleDisposed: Stack1: {args.DisposeStack1}; Stack2: {args.DisposeStack2};"); + }; + + recyclableMemoryStreamManager.StreamOverCapacity += (sender, args) => + { + Console.WriteLine($"StreamOverCapacity: MaximumCapacity is {args.MaximumCapacity / 1024f / 1024f} MB, requisted: {args.RequestedCapacity / 1024f / 1024f:0.##} MB Stack: {args.AllocationStack}"); + }; + + recyclableMemoryStreamManager.StreamFinalized += (sender, args) => + { + Console.WriteLine($"StreamFinalized: {args.AllocationStack}"); + }; + } + + public RpfFile(FileInfo fileInfo) + { + Name = fileInfo.Name; + FilePath = fileInfo.FullName; + FileSize = fileInfo.Length; + } public RpfFile(string fpath, string relpath) //for a ROOT filesystem RPF { FileInfo fi = new FileInfo(fpath); Name = fi.Name; - Path = relpath.ToLowerInvariant(); + Path = relpath; FilePath = fpath; FileSize = fi.Length; } public RpfFile(string name, string path, long filesize) //for a child RPF { Name = name; - Path = path.ToLowerInvariant(); + Path = path; FilePath = path; FileSize = filesize; } @@ -104,7 +116,7 @@ namespace CodeWalker.GameFiles string rel_parent_path = parentFile.Path; string full_parent_path = parentFile.FilePath; - if(rel_parent_path.StartsWith(@"mods\")) + if(rel_parent_path.StartsWith(@"mods\", StringComparison.OrdinalIgnoreCase)) { status = "already in mods folder"; return null; @@ -132,7 +144,7 @@ namespace CodeWalker.GameFiles public bool IsInModsFolder() { - return GetTopParent().Path.StartsWith(@"mods\"); + return GetTopParent().Path.StartsWith(@"mods\", StringComparison.OrdinalIgnoreCase); } public RpfFile GetTopParent() @@ -169,11 +181,13 @@ namespace CodeWalker.GameFiles throw new Exception("Invalid Resource - not GTAV!"); } - byte[] entriesdata = ArrayPool.Shared.Rent((int)EntryCount * 16); - byte[] namesdata = ArrayPool.Shared.Rent((int)NamesLength); + var entriesLength = (int)EntryCount * 16; + var namesLength = (int)NamesLength; + byte[] entriesdata = ArrayPool.Shared.Rent(entriesLength); + byte[] namesdata = ArrayPool.Shared.Rent(namesLength); - br.BaseStream.Read(entriesdata, 0, (int)EntryCount * 16); - br.BaseStream.Read(namesdata, 0, (int)NamesLength); + br.BaseStream.Read(entriesdata, 0, entriesLength); + br.BaseStream.Read(namesdata, 0, namesLength); switch (Encryption) { @@ -181,22 +195,22 @@ namespace CodeWalker.GameFiles case RpfEncryption.OPEN: //OpenIV style RPF with unencrypted TOC break; case RpfEncryption.AES: - GTACrypto.DecryptAES(entriesdata, (int)EntryCount * 16); - GTACrypto.DecryptAES(namesdata, (int)NamesLength); + GTACrypto.DecryptAES(entriesdata, entriesLength); + GTACrypto.DecryptAES(namesdata, namesLength); IsAESEncrypted = true; break; case RpfEncryption.NG: default: - GTACrypto.DecryptNG(entriesdata, Name, (uint)FileSize, 0, (int)EntryCount * 16); - GTACrypto.DecryptNG(namesdata, Name, (uint)FileSize, 0, (int)NamesLength); + GTACrypto.DecryptNG(entriesdata, Name, (uint)FileSize, 0, entriesLength); + GTACrypto.DecryptNG(namesdata, Name, (uint)FileSize, 0, namesLength); IsNGEncrypted = true; break; } - using var entriesrdr = new DataReader(new MemoryStream(entriesdata, 0, (int)EntryCount * 16)); - using var namesrdr = new DataReader(new MemoryStream(namesdata, 0, (int)NamesLength)); + using var entriesrdr = new DataReader(new MemoryStream(entriesdata, 0, entriesLength)); + using var namesrdr = new DataReader(new MemoryStream(namesdata, 0, namesLength)); AllEntries = new List((int)EntryCount); @@ -245,26 +259,19 @@ namespace CodeWalker.GameFiles { namesrdr.Position = entry.NameOffset; entry.Name = namesrdr.ReadString(256); - if (entry.Name.Length > 256) - { - // long names can freeze the RPFExplorer - entry.Name = entry.Name.Substring(0, 256); - } - + JenkIndex.EnsureLower(entry.Name); if (entry is RpfResourceFileEntry rfe)// && string.IsNullOrEmpty(e.Name)) { - rfe.IsEncrypted = rfe.Name.EndsWith(".ysc", StringComparison.OrdinalIgnoreCase);//any other way to know..? + rfe.IsEncrypted = rfe.IsExtension(".ysc");//any other way to know..? } } Root = (RpfDirectoryEntry)AllEntries[0]; - Root.Path = Path.ToLowerInvariant();// + "\\" + Root.Name; - var stack = new Stack(); - stack.Push(Root); - while (stack.Count > 0) - { - var item = stack.Pop(); + Root.Path = Path;// + "\\" + Root.Name; + //var stack = new Stack(); + void addSubDirectory(RpfDirectoryEntry item) + { int starti = (int)item.EntriesIndex; int endi = (int)(item.EntriesIndex + item.EntriesCount); @@ -274,19 +281,46 @@ namespace CodeWalker.GameFiles e.Parent = item; if (e is RpfDirectoryEntry rde) { - rde.Path = item.Path + "\\" + rde.Name; + rde.Path = item.Path + '\\' + rde.Name; item.Directories.Add(rde); - stack.Push(rde); + addSubDirectory(rde); } - else if (e is RpfFileEntry) + else if (e is RpfFileEntry rfe) { - RpfFileEntry rfe = e as RpfFileEntry; - rfe.Path = item.Path + "\\" + rfe.Name; + rfe.Path = item.Path + '\\' + rfe.Name; item.Files.Add(rfe); } } } + addSubDirectory(Root); + //stack.Push(Root); + //while (stack.Count > 0) + //{ + // var item = stack.Pop(); + + // int starti = (int)item.EntriesIndex; + // int endi = (int)(item.EntriesIndex + item.EntriesCount); + + // for (int i = starti; i < endi; i++) + // { + // RpfEntry e = AllEntries[i]; + // e.Parent = item; + // if (e is RpfDirectoryEntry rde) + // { + // rde.Path = item.Path + "\\" + rde.Name; + // item.Directories.Add(rde); + // stack.Push(rde); + // } + // else if (e is RpfFileEntry) + // { + // RpfFileEntry rfe = e as RpfFileEntry; + // rfe.Path = item.Path + "\\" + rfe.Name; + // item.Files.Add(rfe); + // } + // } + //} + br.BaseStream.Position = StartPos; CurrentFileReader = null; @@ -294,10 +328,6 @@ namespace CodeWalker.GameFiles ArrayPool.Shared.Return(namesdata); } - - - - public bool ScanStructure(Action updateStatus, Action errorLog) { @@ -343,7 +373,7 @@ namespace CodeWalker.GameFiles if (entry is RpfBinaryFileEntry binentry) { //search all the sub resources for YSC files. (recurse!) - if (binentry.Name.EndsWith(".rpf", StringComparison.OrdinalIgnoreCase) && binentry.Path.Length < 5000) // a long path is most likely an attempt to crash CW, so skip it + if (binentry.IsExtension(".rpf") && binentry.Path.Length < 5000) // a long path is most likely an attempt to crash CW, so skip it { br.BaseStream.Position = StartPos + ((long)binentry.FileOffset * 512); @@ -424,8 +454,7 @@ namespace CodeWalker.GameFiles long l = binentry.GetFileSize(); //search all the sub resources for YSC files. (recurse!) - string lname = binentry.NameLower; - if (lname.EndsWith(".rpf")) + if (binentry.IsExtension(".rpf")) { br.BaseStream.Position = StartPos + ((long)binentry.FileOffset * 512); @@ -442,9 +471,7 @@ namespace CodeWalker.GameFiles RpfResourceFileEntry resentry = entry as RpfResourceFileEntry; - string lname = resentry.NameLower; - - if (lname.EndsWith(".ysc")) + if (resentry.IsExtension(".ysc")) { updateStatus?.Invoke("Extracting " + resentry.Name + "..."); @@ -483,7 +510,7 @@ namespace CodeWalker.GameFiles DeflateStream ds = new DeflateStream(ms, CompressionMode.Decompress); MemoryStream outstr = recyclableMemoryStreamManager.GetStream(); - ds.CopyTo(outstr); + ds.CopyToFast(outstr); byte[] deflated = outstr.GetBuffer(); byte[] outbuf = new byte[outstr.Length]; //need to copy to the right size buffer for File.WriteAllBytes(). Array.Copy(deflated, outbuf, outbuf.Length); @@ -556,7 +583,8 @@ namespace CodeWalker.GameFiles { try { - using (BinaryReader br = new BinaryReader(File.OpenRead(GetPhysicalFilePath()))) + using var fileStream = new FileStream(GetPhysicalFilePath(), FileMode.Open, FileAccess.Read, FileShare.Read, 4096); + using (BinaryReader br = new BinaryReader(fileStream)) { if (entry is RpfBinaryFileEntry binaryFileEntry) { @@ -579,106 +607,230 @@ namespace CodeWalker.GameFiles return null; } } + + public ValueTask ExtractFileAsync(RpfFileEntry entry) + { + try + { + using var fileStream = new FileStream(GetPhysicalFilePath(), FileMode.Open, FileAccess.Read, FileShare.Read, 4096, FileOptions.Asynchronous); + using (BinaryReader br = new BinaryReader(fileStream)) + { + if (entry is RpfBinaryFileEntry binaryFileEntry) + { + return ExtractFileBinaryAsync(binaryFileEntry, br); + } + else if (entry is RpfResourceFileEntry resourceFileEntry) + { + return ExtractFileResourceAsync(resourceFileEntry, br); + } + else + { + Console.WriteLine($"{entry} is not a BinaryFileEntry of ResourceFileEntry"); + return new ValueTask(); + } + } + } + catch (Exception ex) + { + LastError = ex.ToString(); + LastException = ex; + return new ValueTask(); + } + } + + public async ValueTask ExtractFileBinaryAsync(RpfBinaryFileEntry entry, BinaryReader br) + { + br.BaseStream.Position = StartPos + ((long)entry.FileOffset * 512); + + long l = entry.GetFileSize(); + + if (l <= 0) + { + return null; + } + + uint offset = 0;// 0x10; + uint totlen = (uint)l - offset; + + byte[] tbytes = ArrayPool.Shared.Rent((int)totlen); + + br.BaseStream.Position += offset; + await br.ReadAsync(tbytes, 0, (int)totlen).ConfigureAwait(false); + + if (entry.IsEncrypted) + { + if (IsAESEncrypted) + { + GTACrypto.DecryptAES(tbytes, (int)totlen); + } + else //if (IsNGEncrypted) //assume the archive is set to NG encryption if not AES... (comment: fix for openIV modded files) + { + GTACrypto.DecryptNG(tbytes, entry.Name, entry.FileUncompressedSize, 0, (int)totlen); + } + } + + byte[] defl; + if (entry.FileSize > 0) //apparently this means it's compressed + { + defl = await DecompressBytesAsync(tbytes).ConfigureAwait(false); + } + else + { + defl = new byte[(int)totlen]; + Array.Copy(tbytes, defl, (int)totlen); + } + + ArrayPool.Shared.Return(tbytes); + + return defl; + } + public byte[] ExtractFileBinary(RpfBinaryFileEntry entry, BinaryReader br) { br.BaseStream.Position = StartPos + ((long)entry.FileOffset * 512); long l = entry.GetFileSize(); - if (l > 0) + if (l <= 0) { - uint offset = 0;// 0x10; - uint totlen = (uint)l - offset; - - byte[] tbytes = new byte[totlen]; - - br.BaseStream.Position += offset; - br.Read(tbytes, 0, (int)totlen); - - if (entry.IsEncrypted) - { - if (IsAESEncrypted) - { - GTACrypto.DecryptAES(tbytes); - } - else //if (IsNGEncrypted) //assume the archive is set to NG encryption if not AES... (comment: fix for openIV modded files) - { - GTACrypto.DecryptNG(tbytes, entry.Name, entry.FileUncompressedSize); - } - //else - //{ } - } - - byte[] defl = tbytes; - - if (entry.FileSize > 0) //apparently this means it's compressed - { - defl = DecompressBytes(tbytes); - } - else - { - } - - return defl; + return null; } - return null; + uint offset = 0;// 0x10; + uint totlen = (uint)l - offset; + + byte[] tbytes = ArrayPool.Shared.Rent((int)totlen); + + br.BaseStream.Position += offset; + br.Read(tbytes, 0, (int)totlen); + + if (entry.IsEncrypted) + { + if (IsAESEncrypted) + { + GTACrypto.DecryptAES(tbytes, (int)totlen); + } + else //if (IsNGEncrypted) //assume the archive is set to NG encryption if not AES... (comment: fix for openIV modded files) + { + GTACrypto.DecryptNG(tbytes, entry.Name, entry.FileUncompressedSize, 0, (int)totlen); + } + } + + byte[] defl; + if (entry.FileSize > 0) //apparently this means it's compressed + { + defl = DecompressBytes(tbytes); + } + else + { + defl = new byte[(int)totlen]; + Buffer.BlockCopy(tbytes, 0, defl, 0, (int)totlen); + } + + ArrayPool.Shared.Return(tbytes); + + return defl; } + + public async ValueTask ExtractFileResourceAsync(RpfResourceFileEntry entry, BinaryReader br) + { + br.BaseStream.Position = StartPos + ((long)entry.FileOffset * 512); + + if (entry.FileSize <= 0) + { + return null; + } + + uint offset = 0x10; + uint totlen = entry.FileSize - offset; + + byte[] tbytes = ArrayPool.Shared.Rent((int)totlen); + + + br.BaseStream.Position += offset; + + await br.ReadAsync(tbytes, 0, (int)totlen); + if (entry.IsEncrypted) + { + if (IsAESEncrypted) + { + GTACrypto.DecryptAES(tbytes, (int)totlen); + } + else //if (IsNGEncrypted) //assume the archive is set to NG encryption if not AES... (comment: fix for openIV modded files) + { + GTACrypto.DecryptNG(tbytes, entry.Name, entry.FileSize, 0, (int)totlen); + } + } + + byte[] deflated = await DecompressBytesAsync(tbytes); + + byte[] data; + if (deflated != null) + { + data = deflated; + } + else + { + entry.FileSize -= offset; + + data = new byte[(int)totlen]; + Buffer.BlockCopy(tbytes, 0, data, 0, (int)totlen); + } + + ArrayPool.Shared.Return(tbytes); + + return data; + } + public byte[] ExtractFileResource(RpfResourceFileEntry entry, BinaryReader br) { br.BaseStream.Position = StartPos + ((long)entry.FileOffset * 512); - - if (entry.FileSize > 0) + if (entry.FileSize <= 0) { - uint offset = 0x10; - uint totlen = entry.FileSize - offset; - - byte[] tbytes = new byte[totlen]; - - - br.BaseStream.Position += offset; - //byte[] hbytes = br.ReadBytes(16); //what are these 16 bytes actually used for? - //if (entry.FileSize > 0xFFFFFF) - //{ //(for huge files, the full file size is packed in 4 of these bytes... seriously wtf) - // var filesize = (hbytes[7] << 0) | (hbytes[14] << 8) | (hbytes[5] << 16) | (hbytes[2] << 24); - //} - - - br.Read(tbytes, 0, (int)totlen); - if (entry.IsEncrypted) - { - if (IsAESEncrypted) - { - GTACrypto.DecryptAES(tbytes); - } - else //if (IsNGEncrypted) //assume the archive is set to NG encryption if not AES... (comment: fix for openIV modded files) - { - GTACrypto.DecryptNG(tbytes, entry.Name, entry.FileSize); - } - //else - //{ } - } - - byte[] deflated = DecompressBytes(tbytes); - - byte[] data = null; - - if (deflated != null) - { - data = deflated; - } - else - { - entry.FileSize -= offset; - data = tbytes; - } - - - return data; + return null; } - return null; + uint offset = 0x10; + uint totlen = entry.FileSize - offset; + + byte[] tbytes = ArrayPool.Shared.Rent((int)totlen); + + + br.BaseStream.Position += offset; + + + br.Read(tbytes, 0, (int)totlen); + if (entry.IsEncrypted) + { + if (IsAESEncrypted) + { + GTACrypto.DecryptAES(tbytes, (int)totlen); + } + else //if (IsNGEncrypted) //assume the archive is set to NG encryption if not AES... (comment: fix for openIV modded files) + { + GTACrypto.DecryptNG(tbytes, entry.Name, entry.FileSize, 0, (int)totlen); + } + } + + byte[] deflated = DecompressBytes(tbytes); + + byte[] data; + if (deflated != null) + { + data = deflated; + } + else + { + entry.FileSize -= offset; + + data = new byte[(int)totlen]; + Buffer.BlockCopy(tbytes, 0, data, 0, (int)totlen); + } + + ArrayPool.Shared.Return(tbytes); + + return data; } public static T GetFile(RpfEntry e) where T : class, PackedFile, new() @@ -892,7 +1044,7 @@ namespace CodeWalker.GameFiles { LastError = string.Empty; LastException = null; - if (!entry.NameLower.EndsWith(".rpf")) //don't try to extract rpf's, they will be done separately.. + if (!entry.IsExtension(".rpf")) //don't try to extract rpf's, they will be done separately.. { if (entry is RpfBinaryFileEntry) { @@ -1025,29 +1177,25 @@ namespace CodeWalker.GameFiles - public static RecyclableMemoryStreamManager recyclableMemoryStreamManager = new RecyclableMemoryStreamManager(); + public static RecyclableMemoryStreamManager recyclableMemoryStreamManager = new RecyclableMemoryStreamManager(256 * 1024, 1024 * 1024, 128 * 1024 * 1024, false, 256 * 1024 * 100, 1024 * 1024 * 128 * 4); + public byte[] DecompressBytes(byte[] bytes) { try { - using (DeflateStream ds = new DeflateStream(new MemoryStream(bytes), CompressionMode.Decompress)) + using DeflateStream ds = new DeflateStream(new MemoryStream(bytes), CompressionMode.Decompress); + using var outstr = recyclableMemoryStreamManager.GetStream("DecompressBytes", bytes.Length); + + ds.CopyToFast(outstr); + byte[] outbuf = outstr.ToArray(); //need to copy to the right size buffer for output. + + if (outbuf.Length <= bytes.Length) { - using (var outstr = recyclableMemoryStreamManager.GetStream("DecompressBytes", bytes.Length)) - { - ds.CopyTo(outstr); - byte[] deflated = outstr.GetBuffer(); - byte[] outbuf = new byte[outstr.Length]; //need to copy to the right size buffer for output. - Buffer.BlockCopy(deflated, 0, outbuf, 0, outbuf.Length); - - if (outbuf.Length <= bytes.Length) - { - LastError = "Warning: Decompressed data was smaller than compressed data..."; - //return null; //could still be OK for tiny things! - } - - return outbuf; - } + LastError = "Warning: Decompressed data was smaller than compressed data..."; + //return null; //could still be OK for tiny things! } + + return outbuf; } catch (Exception ex) { @@ -1056,6 +1204,39 @@ namespace CodeWalker.GameFiles return null; } } + + public async Task DecompressBytesAsync(byte[] bytes) + { + try + { + using DeflateStream ds = new DeflateStream(new MemoryStream(bytes), CompressionMode.Decompress); + using var outstr = recyclableMemoryStreamManager.GetStream("DecompressBytes", bytes.Length); + + await ds.CopyToFastAsync(outstr).ConfigureAwait(false); + byte[] outbuf = outstr.ToArray(); //need to copy to the right size buffer for output. + //byte[] deflated = outstr.GetBuffer(); + //Console.WriteLine($"{outstr.Read(outbuf, 0, outbuf.Length)}; {outbuf.Length}"); + + //Buffer.BlockCopy(deflated, 0, outbuf, 0, outbuf.Length); + + //Buffer.BlockCopy(deflated, 0, outbuf, 0, outbuf.Length); + + if (outbuf.Length <= bytes.Length) + { + LastError = "Warning: Decompressed data was smaller than compressed data..."; + //return null; //could still be OK for tiny things! + } + + return outbuf; + } + catch (Exception ex) + { + LastError = "Could not decompress.";// ex.ToString(); + LastException = ex; + return null; + } + } + public static byte[] CompressBytes(byte[] data) //TODO: refactor this with ResourceBuilder.Compress/Decompress { using (MemoryStream ms = recyclableMemoryStreamManager.GetStream("CompressBytes", data.Length)) @@ -1152,7 +1333,7 @@ namespace CodeWalker.GameFiles Root = new RpfDirectoryEntry(); Root.File = this; Root.Name = string.Empty; - Root.Path = Path.ToLowerInvariant(); + Root.Path = Path; } if (Children == null) { @@ -1517,15 +1698,15 @@ namespace CodeWalker.GameFiles //recursively update paths, including in child RPFs. if (dir == null) { - Root.Path = Path.ToLowerInvariant(); + Root.Path = Path; dir = Root; } foreach (var file in dir.Files) { - file.Path = dir.Path + "\\" + file.NameLower; + file.Path = dir.Path + "\\" + file.Name; RpfBinaryFileEntry binf = file as RpfBinaryFileEntry; - if ((binf != null) && file.Name.EndsWith(".rpf", StringComparison.OrdinalIgnoreCase)) + if ((binf != null) && file.IsExtension(".rpf")) { RpfFile childrpf = FindChildArchive(binf); if (childrpf != null) @@ -1685,9 +1866,8 @@ namespace CodeWalker.GameFiles //create a new directory inside the given parent dir RpfFile parent = dir.File; - string namel = name.ToLowerInvariant(); string fpath = parent.GetPhysicalFilePath(); - string rpath = dir.Path + "\\" + namel; + string rpath = dir.Path + "\\" + name; if (!File.Exists(fpath)) { @@ -1862,7 +2042,6 @@ namespace CodeWalker.GameFiles entry.File = parent; entry.Path = rpath; entry.Name = name; - entry.NameLower = namel; @@ -2194,6 +2373,9 @@ namespace CodeWalker.GameFiles public RpfFile File { get; set; } public RpfDirectoryEntry Parent { get; set; } + public static int ExtensionHits = 0; + public static int ExtensionMisses = 0; + public uint NameHash { get { if (nameHash == 0 && !string.IsNullOrEmpty(Name)) @@ -2229,19 +2411,38 @@ namespace CodeWalker.GameFiles return; } name = value; - nameLower = null; nameHash = 0; shortNameHash = 0; shortName = null; + extension = null; } } - public string NameLower + + public string Extension { get { - return nameLower ??= Name?.ToLowerInvariant(); + if (extension == null) + { + int length = Name.Length; + for (int i = length; --i >= 0;) + { + char ch = Name[i]; + if (ch == '.') + { + extension = Name.Substring(i, length - i).ToLowerInvariant(); + break; + } + if (ch == System.IO.Path.DirectorySeparatorChar || ch == System.IO.Path.AltDirectorySeparatorChar) + { + break; + } + } + extension ??= string.Empty; + } + + return extension; } - set { nameLower = value; } } public string ShortName { @@ -2249,14 +2450,22 @@ namespace CodeWalker.GameFiles { if (string.IsNullOrEmpty(shortName) && !string.IsNullOrEmpty(Name)) { - int ind = Name.LastIndexOf('.'); - if (ind > 0) + int length = Name.Length; + for (int i = length; --i >= 0;) { - shortName = Name.Substring(0, ind); - } - else - { - shortName = Name; + char ch = Name[i]; + if (ch == '.') + { + Interlocked.Increment(ref ExtensionHits); + shortName = Name.Substring(0, i); + break; + } + if (ch == System.IO.Path.DirectorySeparatorChar || ch == System.IO.Path.AltDirectorySeparatorChar) + { + Interlocked.Increment(ref ExtensionMisses); + shortName = Name; + break; + } } } @@ -2273,10 +2482,10 @@ namespace CodeWalker.GameFiles public uint H1; //first 2 header values from RPF table... public uint H2; private string name; - private string nameLower; private uint shortNameHash; private uint nameHash; private string shortName; + private string extension; public abstract void Read(DataReader reader); public abstract void Write(DataWriter writer); @@ -2286,9 +2495,9 @@ namespace CodeWalker.GameFiles return Path; } - public string GetShortName() + public bool IsExtension(string ext) { - return ShortName; + return Extension.Equals(ext, StringComparison.Ordinal); } } diff --git a/CodeWalker.Core/GameFiles/RpfManager.cs b/CodeWalker.Core/GameFiles/RpfManager.cs index 63fd00c..a675d5c 100644 --- a/CodeWalker.Core/GameFiles/RpfManager.cs +++ b/CodeWalker.Core/GameFiles/RpfManager.cs @@ -1,4 +1,6 @@ -using CodeWalker.Core.Utils; + + +using CodeWalker.Core.Utils; using System; using System.Collections.Concurrent; using System.Collections.Generic; @@ -56,10 +58,10 @@ namespace CodeWalker.GameFiles AllRpfs = new List(); DlcNoModRpfs = new List(); AllNoModRpfs = new List(); - RpfDict = new Dictionary(); - EntryDict = new Dictionary(); - ModRpfDict = new Dictionary(); - ModEntryDict = new Dictionary(); + RpfDict = new Dictionary(StringComparer.OrdinalIgnoreCase); + EntryDict = new Dictionary(StringComparer.OrdinalIgnoreCase); + ModRpfDict = new Dictionary(StringComparer.OrdinalIgnoreCase); + ModEntryDict = new Dictionary(StringComparer.OrdinalIgnoreCase); var rpfs = new ConcurrentBag(); Parallel.ForEach(allfiles, (rpfpath) => @@ -73,7 +75,7 @@ namespace CodeWalker.GameFiles bool excl = false; for (int i = 0; i < ExcludePaths.Length; i++) { - if (rf.Path.StartsWith(ExcludePaths[i])) + if (rf.Path.StartsWith(ExcludePaths[i], StringComparison.OrdinalIgnoreCase)) { excl = true; break; @@ -147,10 +149,10 @@ namespace CodeWalker.GameFiles DlcRpfs = new List(); DlcNoModRpfs = new List(); AllNoModRpfs = new List(); - RpfDict = new Dictionary(); - EntryDict = new Dictionary(); - ModRpfDict = new Dictionary(); - ModEntryDict = new Dictionary(); + RpfDict = new Dictionary(StringComparer.OrdinalIgnoreCase); + EntryDict = new Dictionary(StringComparer.OrdinalIgnoreCase); + ModRpfDict = new Dictionary(StringComparer.OrdinalIgnoreCase); + ModEntryDict = new Dictionary(StringComparer.OrdinalIgnoreCase); foreach (var rpf in allRpfs) { RpfDict[rpf.Path] = rpf; @@ -171,8 +173,8 @@ namespace CodeWalker.GameFiles private void AddRpfFile(RpfFile file, bool isdlc, bool ismod) { - isdlc = isdlc || (file.NameLower == "update.rpf") || (file.NameLower.StartsWith("dlc") && file.NameLower.EndsWith(".rpf")); - ismod = ismod || (file.Path.StartsWith("mods\\")); + isdlc = isdlc || file.Name.Equals("update.rpf", StringComparison.OrdinalIgnoreCase) || (file.Name.StartsWith("dlc", StringComparison.OrdinalIgnoreCase) && file.Name.EndsWith(".rpf", StringComparison.OrdinalIgnoreCase)); + ismod = ismod || (file.Path.StartsWith("mods\\", StringComparison.OrdinalIgnoreCase)); if (file.AllEntries != null) { @@ -249,8 +251,7 @@ namespace CodeWalker.GameFiles public RpfFile FindRpfFile(string path, bool exactPathOnly) { - RpfFile file = null; //check the dictionary - + RpfFile file; if (EnableMods && ModRpfDict.TryGetValue(path, out file)) { return file; @@ -261,14 +262,13 @@ namespace CodeWalker.GameFiles return file; } - string lpath = path.ToLowerInvariant(); //try look at names etc foreach (RpfFile tfile in AllRpfs) { - if (!exactPathOnly && tfile.NameLower == lpath) + if (!exactPathOnly && tfile.Name.Equals(path, StringComparison.OrdinalIgnoreCase)) { return tfile; } - if (tfile.Path == lpath) + if (tfile.Path.Equals(path, StringComparison.OrdinalIgnoreCase)) { return tfile; } @@ -281,21 +281,20 @@ namespace CodeWalker.GameFiles public RpfEntry GetEntry(string path) { RpfEntry entry; - string pathl = path.ToLowerInvariant(); - if (EnableMods && ModEntryDict.TryGetValue(pathl, out entry)) + if (EnableMods && ModEntryDict.TryGetValue(path, out entry)) { return entry; } - EntryDict.TryGetValue(pathl, out entry); + EntryDict.TryGetValue(path, out entry); if (entry == null) { - pathl = pathl.Replace("/", "\\"); - pathl = pathl.Replace("common:", "common.rpf"); - if (EnableMods && ModEntryDict.TryGetValue(pathl, out entry)) + path = path.Replace("/", "\\"); + path = path.Replace("common:", "common.rpf"); + if (EnableMods && ModEntryDict.TryGetValue(path, out entry)) { return entry; } - EntryDict.TryGetValue(pathl, out entry); + EntryDict.TryGetValue(path, out entry); } return entry; } @@ -357,6 +356,30 @@ namespace CodeWalker.GameFiles } return file; } + public async Task GetFileAsync(RpfEntry e) where T : class, PackedFile, new() + { + try + { + T file = null; + byte[] data = null; + RpfFileEntry entry = e as RpfFileEntry; + if (entry != null) + { + data = await entry.File.ExtractFileAsync(entry).ConfigureAwait(false); + } + if (data != null) + { + file = new T(); + file.Load(data, entry); + } + return file; + } catch(Exception ex) + { + Console.WriteLine(ex); + throw; + } + + } public bool LoadFile(T file, RpfEntry e) where T : class, PackedFile { byte[] data = null; @@ -367,8 +390,40 @@ namespace CodeWalker.GameFiles } if (data != null) { - file.Load(data, entry); - return true; + try + { + file.Load(data, entry); + return true; + } + catch(Exception ex) + { + Console.WriteLine($"Error occured while loading {entry.Name} at {entry.Path}:\n{ex}"); + throw; + } + } + return false; + } + + public async ValueTask LoadFileAsync(T file, RpfEntry e) where T : class, PackedFile + { + byte[] data = null; + RpfFileEntry entry = e as RpfFileEntry; + if (entry != null) + { + data = await entry.File.ExtractFileAsync(entry).ConfigureAwait(false); + } + if (data != null && data.Length > 0) + { + try + { + file.Load(data, entry); + return true; + } + catch(Exception ex) + { + Console.WriteLine($"Error occured while loading {entry.Name} at {entry.Path}:\n{ex}"); + throw; + } } return false; } @@ -386,71 +441,64 @@ namespace CodeWalker.GameFiles JenkIndex.Ensure(file.Name); foreach (RpfEntry entry in file.AllEntries) { - var nlow = entry.NameLower; - if (string.IsNullOrEmpty(nlow)) continue; + var name = entry.Name; + if (string.IsNullOrEmpty(name)) + continue; //JenkIndex.Ensure(entry.Name); //JenkIndex.Ensure(nlow); - int ind = nlow.LastIndexOf('.'); - if (ind > 0) - { - - JenkIndex.Ensure(entry.Name.Substring(0, ind)); - JenkIndex.Ensure(nlow.Substring(0, ind)); + var nameWithoutExtension = entry.ShortName; + JenkIndex.EnsureBoth(nameWithoutExtension); - //if (ind < entry.Name.Length - 2) - //{ - // JenkIndex.Ensure(entry.Name.Substring(0, ind) + ".#" + entry.Name.Substring(ind + 2)); - // JenkIndex.Ensure(entry.NameLower.Substring(0, ind) + ".#" + entry.NameLower.Substring(ind + 2)); - //} - } - else - { - JenkIndex.Ensure(entry.Name); - JenkIndex.Ensure(nlow); - } + //if (ind < entry.Name.Length - 2) + //{ + // JenkIndex.Ensure(entry.Name.Substring(0, ind) + ".#" + entry.Name.Substring(ind + 2)); + // JenkIndex.Ensure(entry.NameLower.Substring(0, ind) + ".#" + entry.NameLower.Substring(ind + 2)); + //} if (BuildExtendedJenkIndex) { - if (nlow.EndsWith(".ydr"))// || nlow.EndsWith(".yft")) //do yft's get lods? + if (name.EndsWith(".ydr", StringComparison.OrdinalIgnoreCase))// || nlow.EndsWith(".yft")) //do yft's get lods? { - var sname = nlow.Substring(0, nlow.Length - 4); - JenkIndex.Ensure(sname + "_lod"); - JenkIndex.Ensure(sname + "_loda"); - JenkIndex.Ensure(sname + "_lodb"); + var sname = entry.ShortName; + var nameLod = sname + "_lod"; + JenkIndex.EnsureLower(nameLod); + JenkIndex.EnsureLower(nameLod + 'a'); + JenkIndex.EnsureLower(nameLod + 'b'); } - if (nlow.EndsWith(".ydd")) + else if (name.EndsWith(".ydd", StringComparison.OrdinalIgnoreCase)) { - if (nlow.EndsWith("_children.ydd")) + if (name.EndsWith("_children.ydd", StringComparison.OrdinalIgnoreCase)) { - var strn = nlow.Substring(0, nlow.Length - 13); - JenkIndex.Ensure(strn); - JenkIndex.Ensure(strn + "_lod"); - JenkIndex.Ensure(strn + "_loda"); - JenkIndex.Ensure(strn + "_lodb"); + var strn = entry.Name.Substring(0, name.Length - 13); + JenkIndex.EnsureLower(strn); + var nameChildrenLod = strn + "_lod"; + JenkIndex.EnsureLower(nameChildrenLod); + JenkIndex.EnsureLower(nameChildrenLod + 'a'); + JenkIndex.EnsureLower(nameChildrenLod + 'b'); } - var idx = nlow.LastIndexOf('_'); + var idx = name.LastIndexOf('_'); if (idx > 0) { - var str1 = nlow.Substring(0, idx); + var str1 = name.Substring(0, idx); var idx2 = str1.LastIndexOf('_'); if (idx2 > 0) { var str2 = str1.Substring(0, idx2); - JenkIndex.Ensure(str2 + "_lod"); + JenkIndex.EnsureLower(str2 + "_lod"); var maxi = 100; for (int i = 1; i <= maxi; i++) { - var str3 = str2 + "_" + i.ToString().PadLeft(2, '0'); + var str3 = str2 + '_' + i.ToString().PadLeft(2, '0') + "_lod"; //JenkIndex.Ensure(str3); - JenkIndex.Ensure(str3 + "_lod"); + JenkIndex.EnsureLower(str3); } } } } - if (nlow.EndsWith(".sps")) + else if(name.EndsWith(".sps", StringComparison.OrdinalIgnoreCase)) { - JenkIndex.Ensure(nlow);//for shader preset filename hashes! + JenkIndex.EnsureLower(entry.Name);//for shader preset filename hashes! } - if (nlow.EndsWith(".awc")) //create audio container path hashes... + else if(name.EndsWith(".awc", StringComparison.OrdinalIgnoreCase)) //create audio container path hashes... { string[] parts = entry.Path.Split('\\'); int pl = parts.Length; @@ -459,21 +507,18 @@ namespace CodeWalker.GameFiles string fn = parts[pl - 1]; string fd = parts[pl - 2]; string hpath = fn.Substring(0, fn.Length - 4); - if (fd.EndsWith(".rpf")) + if (fd.EndsWith(".rpf", StringComparison.OrdinalIgnoreCase)) { fd = fd.Substring(0, fd.Length - 4); } - hpath = fd + "/" + hpath; - if (parts[pl - 3] != "sfx") - { }//no hit + hpath = fd + '/' + hpath; - JenkIndex.Ensure(hpath); + JenkIndex.EnsureLower(hpath); } } - if (nlow.EndsWith(".nametable")) + else if(name.EndsWith(".nametable", StringComparison.OrdinalIgnoreCase)) { - RpfBinaryFileEntry binfe = entry as RpfBinaryFileEntry; - if (binfe != null) + if (entry is RpfBinaryFileEntry binfe) { byte[] data = file.ExtractFile(binfe); if (data != null) @@ -487,9 +532,8 @@ namespace CodeWalker.GameFiles string str = sb.ToString(); if (!string.IsNullOrEmpty(str)) { - string strl = str.ToLowerInvariant(); //JenkIndex.Ensure(str); - JenkIndex.Ensure(strl); + JenkIndex.EnsureLower(str); ////DirMod_Sounds_ entries apparently can be used to infer SP audio strings ////no luck here yet though @@ -508,8 +552,6 @@ namespace CodeWalker.GameFiles } } } - else - { } } } } diff --git a/CodeWalker.Core/GameFiles/Utils/DDSIO.cs b/CodeWalker.Core/GameFiles/Utils/DDSIO.cs index 8463654..b96e030 100644 --- a/CodeWalker.Core/GameFiles/Utils/DDSIO.cs +++ b/CodeWalker.Core/GameFiles/Utils/DDSIO.cs @@ -96,10 +96,12 @@ using CodeWalker.GameFiles; +using DirectXTexNet; using System; using System.Collections.Generic; using System.IO; using System.Linq; +using System.Runtime.InteropServices; using System.Text; using System.Threading.Tasks; @@ -122,8 +124,6 @@ namespace CodeWalker.Utils public static class DDSIO { - - public static byte[] GetPixels(Texture texture, int mip) { //dexyfex version @@ -159,55 +159,94 @@ namespace CodeWalker.Utils bool swaprb = true; - switch (format) + if (DirectXTexNet.TexHelper.Instance.IsCompressed((DirectXTexNet.DXGI_FORMAT)format)) { - // compressed - case DXGI_FORMAT.DXGI_FORMAT_BC1_UNORM: // TextureFormat.D3DFMT_DXT1 - px = DecompressDxt1(imgdata, w, h); - break; - case DXGI_FORMAT.DXGI_FORMAT_BC2_UNORM: // TextureFormat.D3DFMT_DXT3 - px = DecompressDxt3(imgdata, w, h); - break; - case DXGI_FORMAT.DXGI_FORMAT_BC3_UNORM: // TextureFormat.D3DFMT_DXT5 - px = DecompressDxt5(imgdata, w, h); - break; - case DXGI_FORMAT.DXGI_FORMAT_BC4_UNORM: // TextureFormat.D3DFMT_ATI1 - px = DecompressBC4(imgdata, w, h); - break; - case DXGI_FORMAT.DXGI_FORMAT_BC5_UNORM: // TextureFormat.D3DFMT_ATI2 - px = DecompressBC5(imgdata, w, h); - break; - case DXGI_FORMAT.DXGI_FORMAT_BC7_UNORM: // TextureFormat.D3DFMT_BC7 - //BC7 TODO!! - break; - - // uncompressed - case DXGI_FORMAT.DXGI_FORMAT_B5G5R5A1_UNORM: // TextureFormat.D3DFMT_A1R5G5B5 - px = ConvertBGR5A1ToRGBA8(imgdata, w, h); //needs testing - break; - case DXGI_FORMAT.DXGI_FORMAT_A8_UNORM: // TextureFormat.D3DFMT_A8 - px = ConvertA8ToRGBA8(imgdata, w, h); - swaprb = false; - break; - case DXGI_FORMAT.DXGI_FORMAT_R8G8B8A8_UNORM: // TextureFormat.D3DFMT_A8B8G8R8 - case DXGI_FORMAT.DXGI_FORMAT_R8G8B8A8_TYPELESS: - px = imgdata; - break; - case DXGI_FORMAT.DXGI_FORMAT_R8_UNORM: // TextureFormat.D3DFMT_L8 - px = ConvertR8ToRGBA8(imgdata, w, h); - break; - case DXGI_FORMAT.DXGI_FORMAT_B8G8R8A8_UNORM: // TextureFormat.D3DFMT_A8R8G8B8 - px = imgdata; - swaprb = false; - break; - case DXGI_FORMAT.DXGI_FORMAT_B8G8R8X8_UNORM: // TextureFormat.D3DFMT_X8R8G8B8 - px = imgdata; - swaprb = false; - break; - default: - break; //shouldn't get here... + px = Decompress(imgdata, w, h, format); + } else + { + switch (format) + { + case DXGI_FORMAT.DXGI_FORMAT_B5G5R5A1_UNORM: // TextureFormat.D3DFMT_A1R5G5B5 + px = ConvertBGR5A1ToRGBA8(imgdata, w, h); //needs testing + break; + case DXGI_FORMAT.DXGI_FORMAT_A8_UNORM: // TextureFormat.D3DFMT_A8 + px = ConvertA8ToRGBA8(imgdata, w, h); + swaprb = false; + break; + case DXGI_FORMAT.DXGI_FORMAT_R8G8B8A8_UNORM: // TextureFormat.D3DFMT_A8B8G8R8 + case DXGI_FORMAT.DXGI_FORMAT_R8G8B8A8_TYPELESS: + px = imgdata; + break; + case DXGI_FORMAT.DXGI_FORMAT_R8_UNORM: // TextureFormat.D3DFMT_L8 + px = ConvertR8ToRGBA8(imgdata, w, h); + break; + case DXGI_FORMAT.DXGI_FORMAT_B8G8R8A8_UNORM: // TextureFormat.D3DFMT_A8R8G8B8 + px = imgdata; + swaprb = false; + break; + case DXGI_FORMAT.DXGI_FORMAT_B8G8R8X8_UNORM: // TextureFormat.D3DFMT_X8R8G8B8 + px = imgdata; + swaprb = false; + break; + default: + px = imgdata; + break; + } } + + + + //switch (format) + //{ + // // compressed + // case DXGI_FORMAT.DXGI_FORMAT_BC1_UNORM: // TextureFormat.D3DFMT_DXT1 + // px = DecompressDxt1(imgdata, w, h); + // break; + // case DXGI_FORMAT.DXGI_FORMAT_BC2_UNORM: // TextureFormat.D3DFMT_DXT3 + // px = DecompressDxt3(imgdata, w, h); + // break; + // case DXGI_FORMAT.DXGI_FORMAT_BC3_UNORM: // TextureFormat.D3DFMT_DXT5 + // px = DecompressDxt5(imgdata, w, h); + // break; + // case DXGI_FORMAT.DXGI_FORMAT_BC4_UNORM: // TextureFormat.D3DFMT_ATI1 + // px = Decompress(imgdata, w, h, DXGI_FORMAT.DXGI_FORMAT_BC4_UNORM); + // break; + // case DXGI_FORMAT.DXGI_FORMAT_BC5_UNORM: // TextureFormat.D3DFMT_ATI2 + // px = Decompress(imgdata, w, h, DXGI_FORMAT.DXGI_FORMAT_BC5_UNORM); + // break; + // case DXGI_FORMAT.DXGI_FORMAT_BC7_UNORM: // TextureFormat.D3DFMT_BC7 + // px = Decompress(imgdata, w, h, DXGI_FORMAT.DXGI_FORMAT_BC7_UNORM); + // //BC7 TODO!! + // break; + + // // uncompressed + // case DXGI_FORMAT.DXGI_FORMAT_B5G5R5A1_UNORM: // TextureFormat.D3DFMT_A1R5G5B5 + // px = ConvertBGR5A1ToRGBA8(imgdata, w, h); //needs testing + // break; + // case DXGI_FORMAT.DXGI_FORMAT_A8_UNORM: // TextureFormat.D3DFMT_A8 + // px = ConvertA8ToRGBA8(imgdata, w, h); + // swaprb = false; + // break; + // case DXGI_FORMAT.DXGI_FORMAT_R8G8B8A8_UNORM: // TextureFormat.D3DFMT_A8B8G8R8 + // case DXGI_FORMAT.DXGI_FORMAT_R8G8B8A8_TYPELESS: + // px = imgdata; + // break; + // case DXGI_FORMAT.DXGI_FORMAT_R8_UNORM: // TextureFormat.D3DFMT_L8 + // px = ConvertR8ToRGBA8(imgdata, w, h); + // break; + // case DXGI_FORMAT.DXGI_FORMAT_B8G8R8A8_UNORM: // TextureFormat.D3DFMT_A8R8G8B8 + // px = imgdata; + // swaprb = false; + // break; + // case DXGI_FORMAT.DXGI_FORMAT_B8G8R8X8_UNORM: // TextureFormat.D3DFMT_X8R8G8B8 + // px = imgdata; + // swaprb = false; + // break; + // default: + // break; //shouldn't get here... + //} + if (swaprb && (px != null)) { int pxc = px.Length / 4;// w * h; @@ -564,7 +603,10 @@ namespace CodeWalker.Utils images[i].format = format; //(DXGI_FORMAT)img.Format; images[i].pixels = buf + add; - DXTex.ComputePitch(images[i].format, images[i].width, images[i].height, out images[i].rowPitch, out images[i].slicePitch, 0); + DXTex.ComputePitch(images[i].format, images[i].width, images[i].height, out var rowPitch, out var slicePitch, 0); + images[i].rowPitch = (int)rowPitch; + images[i].slicePitch = (int)slicePitch; + //DXTex.ComputePitch(images[i].format, images[i].width, images[i].height, out images[i].rowPitch, out images[i].slicePitch, 0); add += images[i].slicePitch; div *= 2; @@ -2641,6 +2683,48 @@ namespace CodeWalker.Utils } } + public static unsafe byte[] Decompress(Byte[] data, int width, int height, DXGI_FORMAT format) + { + Console.WriteLine(format); + Console.WriteLine(width); + Console.WriteLine(height); + + long inputRowPitch; + long inputSlicePitch; + TexHelper.Instance.ComputePitch((DirectXTexNet.DXGI_FORMAT)format, width, height, out inputRowPitch, out inputSlicePitch, DirectXTexNet.CP_FLAGS.NONE); + + DirectXTexNet.DXGI_FORMAT FormatDecompressed; + + if (format.ToString().Contains("SRGB")) + FormatDecompressed = DirectXTexNet.DXGI_FORMAT.R8G8B8A8_UNORM_SRGB; + else + FormatDecompressed = DirectXTexNet.DXGI_FORMAT.R8G8B8A8_UNORM; + + byte* buf; + buf = (byte*)Marshal.AllocHGlobal((int)inputSlicePitch); + Marshal.Copy(data, 0, (IntPtr)buf, (int)inputSlicePitch); + + DirectXTexNet.Image inputImage = new DirectXTexNet.Image( + width, height, (DirectXTexNet.DXGI_FORMAT)format, inputRowPitch, + inputSlicePitch, (IntPtr)buf, null); + + DirectXTexNet.TexMetadata texMetadata = new DirectXTexNet.TexMetadata(width, height, 1, 1, 1, 0, 0, + (DirectXTexNet.DXGI_FORMAT)format, DirectXTexNet.TEX_DIMENSION.TEXTURE2D); + + ScratchImage scratchImage = TexHelper.Instance.InitializeTemporary( + new DirectXTexNet.Image[] { inputImage }, texMetadata, null); + + using (var decomp = scratchImage.Decompress(0, FormatDecompressed)) + { + byte[] result = new byte[4 * width * height]; + Marshal.Copy(decomp.GetImage(0).Pixels, result, 0, result.Length); + + inputImage = null; + scratchImage.Dispose(); + + return result; + } + } internal static byte[] DecompressBC5(byte[] imageData, int width, int height) { diff --git a/CodeWalker.Core/GameFiles/Utils/Data.cs b/CodeWalker.Core/GameFiles/Utils/Data.cs index 58d3243..add4ce7 100644 --- a/CodeWalker.Core/GameFiles/Utils/Data.cs +++ b/CodeWalker.Core/GameFiles/Utils/Data.cs @@ -1,4 +1,6 @@ -/* + + +/* Copyright(c) 2015 Neodymium Permission is hereby granted, free of charge, to any person obtaining a copy @@ -29,6 +31,12 @@ using System; using System.IO; using System.Runtime.CompilerServices; using System.Text; +using CodeWalker.Core.Utils; +using System.Buffers.Binary; +using System.Buffers; +using System.Runtime.InteropServices; +using System.Threading.Tasks; +using System.Threading; namespace CodeWalker.GameFiles { @@ -98,18 +106,40 @@ namespace CodeWalker.GameFiles /// public DataReader(Stream stream, Endianess endianess = Endianess.LittleEndian) { - this.baseStream = stream; + if (stream is not null) + { + this.baseStream = Stream.Synchronized(stream); + } this.Endianess = endianess; } + public virtual Stream GetStream() + { + return baseStream; + } + + internal virtual void SetPositionAfterRead(Stream stream) + { + return; + } + /// /// Reads data from the underlying stream. This is the only method that directly accesses /// the data in the underlying stream. /// protected virtual byte[] ReadFromStream(int count, bool ignoreEndianess = false, byte[] buffer = null) { + var stream = GetStream(); buffer ??= new byte[count]; - baseStream.Read(buffer, 0, count); + + try + { + stream.Read(buffer, 0, count); + } + finally + { + SetPositionAfterRead(stream); + } // handle endianess if (!ignoreEndianess && (Endianess == Endianess.BigEndian)) @@ -125,11 +155,22 @@ namespace CodeWalker.GameFiles /// public virtual byte ReadByte() { - var result = baseStream.ReadByte(); + var stream = GetStream(); + int result; + try + { + result = stream.ReadByte(); + } + finally + { + SetPositionAfterRead(stream); + } + if (result == -1) { throw new InvalidOperationException("Tried to read from stream beyond end!"); } + return (byte) result; } @@ -205,21 +246,37 @@ namespace CodeWalker.GameFiles return BitConverter.ToDouble(ReadFromStream(8, buffer: _buffer), 0); } + /// + /// Reads a string. + /// + unsafe public string ReadStringLength(int length) + { + if (length == 0) + { + return string.Empty; + } + var bytes = stackalloc byte[length]; + for (int i = 0; i < length; i++) + { + bytes[i] = ReadByte(); + } + + return new string((sbyte*)bytes, 0, length, Encoding.ASCII); + //return Encoding.UTF8.GetString(bytes, Math.Min(charsRead, maxLength)); + } + /// /// Reads a string. /// unsafe public string ReadString(int maxLength = 1024) { var bytes = stackalloc byte[Math.Min(maxLength, 1024)]; + var chars = stackalloc char[Math.Min(maxLength, 1024)]; var temp = ReadByte(); var charsRead = 0; while (temp != 0 && (Length == -1 || Position <= Length)) { - if (charsRead > 1023) - { - throw new Exception("String too long!"); - } - if (charsRead < maxLength) + if (charsRead < maxLength && charsRead < 1024) { bytes[charsRead] = temp; } @@ -227,7 +284,10 @@ namespace CodeWalker.GameFiles charsRead++; } - return Encoding.UTF8.GetString(bytes, Math.Min(charsRead, maxLength)); + var charsCount = Encoding.UTF8.GetChars(bytes, charsRead, chars, Math.Min(maxLength, 1024)); + + return new string(chars, 0, charsCount); + //return Encoding.UTF8.GetString(bytes, Math.Min(charsRead, maxLength)); } unsafe public string ReadStringLower() @@ -357,12 +417,23 @@ namespace CodeWalker.GameFiles } } + internal virtual Stream GetStream() + { + return baseStream; + } + + internal virtual void SetPositionAfterWrite(Stream stream) + { } + /// /// Initializes a new data writer for the specified stream. /// public DataWriter(Stream stream, Endianess endianess = Endianess.LittleEndian) { - this.baseStream = stream; + if (stream is not null) + { + this.baseStream = Stream.Synchronized(stream); + } this.Endianess = endianess; } @@ -370,17 +441,65 @@ namespace CodeWalker.GameFiles /// Writes data to the underlying stream. This is the only method that directly accesses /// the data in the underlying stream. /// - protected virtual void WriteToStream(byte[] value, bool ignoreEndianess = false) + protected virtual void WriteToStream(byte[] value, bool ignoreEndianess = false, int count = -1, int offset = 0) { + var stream = GetStream(); + if (count == -1) + { + count = value.Length; + } if (!ignoreEndianess && (Endianess == Endianess.BigEndian)) { - var buffer = (byte[])value.Clone(); - Array.Reverse(buffer); - baseStream.Write(buffer, 0, buffer.Length); + var buffer = ArrayPool.Shared.Rent(count); + try + { + Array.Copy(value, offset, buffer, 0, count); + Array.Reverse(buffer, 0, count); + stream.Write(buffer, 0, count); + } + finally + { + ArrayPool.Shared.Return(buffer); + } } else { - baseStream.Write(value, 0, value.Length); + stream.Write(value, offset, count); + } + SetPositionAfterWrite(stream); + } + + protected virtual void WriteToStream(Span value, bool ignoreEndianess = false) + { + byte[] sharedBuffer = ArrayPool.Shared.Rent(value.Length); + try + { + value.CopyTo(sharedBuffer); + WriteToStream(sharedBuffer, count: value.Length); + } + finally + { + ArrayPool.Shared.Return(sharedBuffer); + } + } + + protected virtual void WriteToStream(Memory buffer, bool ignoreEndianess = false) + { + if (MemoryMarshal.TryGetArray(buffer, out ArraySegment array)) + { + WriteToStream(array.Array!, offset: array.Offset, count: array.Count); + return; + } + + byte[] sharedBuffer = ArrayPool.Shared.Rent(buffer.Length); + try + { + buffer.Span.CopyTo(sharedBuffer); + WriteToStream(sharedBuffer, count: buffer.Length); + } + finally + { + ArrayPool.Shared.Return(sharedBuffer); } } @@ -400,6 +519,16 @@ namespace CodeWalker.GameFiles WriteToStream(value, true); } + public void Write(Span value) + { + WriteToStream(value, true); + } + + public void Write(Memory value) + { + WriteToStream(value, true); + } + /// /// Writes a signed 16-bit value. /// @@ -512,12 +641,12 @@ namespace CodeWalker.GameFiles public virtual void Dispose() { - baseStream.Dispose(); + baseStream?.Dispose(); } public virtual void Close() { - baseStream.Close(); + baseStream?.Close(); } } diff --git a/CodeWalker.Core/GameFiles/Utils/GTACrypto.cs b/CodeWalker.Core/GameFiles/Utils/GTACrypto.cs index 682b3a9..35ed41b 100644 --- a/CodeWalker.Core/GameFiles/Utils/GTACrypto.cs +++ b/CodeWalker.Core/GameFiles/Utils/GTACrypto.cs @@ -1,4 +1,5 @@ -/* + +/* Copyright(c) 2015 Neodymium Permission is hereby granted, free of charge, to any person obtaining a copy diff --git a/CodeWalker.Core/GameFiles/Utils/GTAKeys.cs b/CodeWalker.Core/GameFiles/Utils/GTAKeys.cs index 5a6ad7c..46cabe2 100644 --- a/CodeWalker.Core/GameFiles/Utils/GTAKeys.cs +++ b/CodeWalker.Core/GameFiles/Utils/GTAKeys.cs @@ -28,6 +28,7 @@ //shamelessly stolen using CodeWalker.Core.Properties; +using CodeWalker.Core.Utils; using System; using System.Collections.Generic; using System.IO; @@ -297,7 +298,7 @@ namespace CodeWalker.GameFiles { using (MemoryStream outstr = RpfFile.recyclableMemoryStreamManager.GetStream()) { - ds.CopyTo(outstr); + ds.CopyToFast(outstr); b = outstr.GetBuffer(); } } @@ -1220,7 +1221,7 @@ namespace CodeWalker.GameFiles using (var fs = new FileStream(fileName, FileMode.Create)) { ms.Position = 0; - ms.CopyTo(fs); + ms.CopyToFast(fs); } } @@ -1307,7 +1308,7 @@ namespace CodeWalker.GameFiles using (var fs = new FileStream(fileName, FileMode.Create)) { ms.Position = 0; - ms.CopyTo(fs); + ms.CopyToFast(fs); } } @@ -1445,7 +1446,7 @@ namespace CodeWalker.GameFiles using (var fs = new FileStream(fileName, FileMode.Create)) { ms.Position = 0; - ms.CopyTo(fs); + ms.CopyToFast(fs); } } } diff --git a/CodeWalker.Core/GameFiles/Utils/Jenk.cs b/CodeWalker.Core/GameFiles/Utils/Jenk.cs index dd85ad3..3eec310 100644 --- a/CodeWalker.Core/GameFiles/Utils/Jenk.cs +++ b/CodeWalker.Core/GameFiles/Utils/Jenk.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; +using System.Runtime.CompilerServices; using System.Text; using System.Threading; using System.Threading.Tasks; @@ -28,6 +29,11 @@ namespace CodeWalker.GameFiles HashHex = "0x" + HashUint.ToString("X"); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static char ToLower(char c) + { + return (c >= 'A' && c <= 'Z') ? (char)(c - 'A' + 'a') : c; + } public static uint GenHash(string text, JenkHashInputEncoding encoding) { @@ -64,8 +70,47 @@ namespace CodeWalker.GameFiles uint h = 0; for (int i = 0; i < text.Length; i++) { - - h += (byte)char.ToLowerInvariant(text[i]); + h += (byte)ToLower(text[i]); + h += (h << 10); + h ^= (h >> 6); + } + h += (h << 3); + h ^= (h >> 11); + h += (h << 15); + + return h; + } + + public static uint GenHashLower(ReadOnlySpan text, ReadOnlySpan str2 = default) + { + if (text == null) return 0; + uint h = 0; + for (int i = 0; i < text.Length; i++) + { + h += (byte)ToLower(text[i]); + h += (h << 10); + h ^= (h >> 6); + } + for (int i = 0; i < str2.Length; i++) + { + h += (byte)ToLower(str2[i]); + h += (h << 10); + h ^= (h >> 6); + } + h += (h << 3); + h ^= (h >> 11); + h += (h << 15); + + return h; + } + + public static uint GenHash(ReadOnlySpan text) + { + if (text == null) return 0; + uint h = 0; + for (int i = 0; i < text.Length; i++) + { + h += (byte)text[i]; h += (h << 10); h ^= (h >> 6); } @@ -231,41 +276,42 @@ namespace CodeWalker.GameFiles public static class JenkIndex { - public static ConcurrentDictionary Index = new ConcurrentDictionary(Environment.ProcessorCount, 1500000); + public static ConcurrentDictionary Index = new ConcurrentDictionary(32, 1500000); - public static void Clear() - { - Index.Clear(); - } - - public static bool Ensure(string str) + public static void Ensure(string str) { uint hash = JenkHash.GenHash(str); - if (hash == 0) return true; - if (Index.ContainsKey(hash)) - { - return true; - } - lock (Index) - { - Index[hash] = str; - return false; - } + Ensure(str, hash); } - public static bool EnsureLower(string str) + public static void Ensure(string str, uint hash) { - uint hash = JenkHash.GenHashLower(str); - if (hash == 0) return true; + if (hash == 0) return; + if (Index.ContainsKey(hash)) { - return true; + return; } Index.TryAdd(hash, str); - return false; } + public static void EnsureLower(string str) + { + uint hash = JenkHash.GenHashLower(str); + Ensure(str, hash); + } + + public static void EnsureBoth(string str) + { + uint hash = JenkHash.GenHash(str); + uint hashLower = JenkHash.GenHashLower(str); + Ensure(str, hash); + if (hash != hashLower) + { + Ensure(str, hashLower); + } + } public static void AddRange(params string[] strings) { foreach(var s in strings) @@ -307,10 +353,9 @@ namespace CodeWalker.GameFiles return res; } - public static string[] GetAllStrings() + public static ICollection GetAllStrings() { - string[] res = null; - res = Index.Values.ToArray(); + var res = Index.Values; return res; } diff --git a/CodeWalker.Core/Tests/GameFileCache.cs b/CodeWalker.Core/Tests/GameFileCache.cs index 6443693..49436c8 100644 --- a/CodeWalker.Core/Tests/GameFileCache.cs +++ b/CodeWalker.Core/Tests/GameFileCache.cs @@ -19,7 +19,7 @@ namespace CodeWalker.GameFiles { try { - if (entry.NameLower.EndsWith("cache_y.dat"))// || entry.NameLower.EndsWith("cache_y_bank.dat")) + if (entry.Name.EndsWith("cache_y.dat", StringComparison.OrdinalIgnoreCase))// || entry.NameLower.EndsWith("cache_y_bank.dat")) { UpdateStatus?.Invoke(string.Format(entry.Path)); var cdfile = RpfMan.GetFile(entry); @@ -63,7 +63,7 @@ namespace CodeWalker.GameFiles { foreach (RpfEntry entry in file.AllEntries) { - if (entry.NameLower.EndsWith(".dat") && entry.NameLower.StartsWith("heightmap")) + if (entry.IsExtension(".dat") && entry.Name.StartsWith("heightmap", StringComparison.OrdinalIgnoreCase)) { UpdateStatus?.Invoke(string.Format(entry.Path)); HeightmapFile hmf = null; @@ -100,7 +100,7 @@ namespace CodeWalker.GameFiles { foreach (RpfEntry entry in file.AllEntries) { - if (entry.NameLower.EndsWith(".dat") && entry.NameLower.StartsWith("waterheight")) + if (entry.IsExtension(".dat") && entry.Name.StartsWith("waterheight", StringComparison.OrdinalIgnoreCase)) { UpdateStatus?.Invoke(string.Format(entry.Path)); WatermapFile wmf = null; @@ -147,7 +147,7 @@ namespace CodeWalker.GameFiles var rbfe = rfe as RpfBinaryFileEntry; if ((rfe == null) || (rbfe == null)) continue; - if (rfe.Name.EndsWith(".rel", StringComparison.OrdinalIgnoreCase)) + if (rfe.IsExtension(".rel")) { UpdateStatus?.Invoke(string.Format(entry.Path)); @@ -315,8 +315,7 @@ namespace CodeWalker.GameFiles { try { - var n = entry.NameLower; - if (n.EndsWith(".ymt")) + if (entry.IsExtension(".ymt")) { UpdateStatus?.Invoke(string.Format(entry.Path)); //YmtFile ymtfile = RpfMan.GetFile(entry); @@ -324,7 +323,7 @@ namespace CodeWalker.GameFiles //{ //} - var sn = entry.GetShortName(); + var sn = entry.ShortName; uint un; if (uint.TryParse(sn, out un)) { @@ -388,8 +387,7 @@ namespace CodeWalker.GameFiles { //try //{ - var n = entry.NameLower; - if (n.EndsWith(".awc")) + if (entry.IsExtension(".awc")) { UpdateStatus?.Invoke(string.Format(entry.Path)); var awcfile = RpfMan.GetFile(entry); @@ -417,8 +415,8 @@ namespace CodeWalker.GameFiles { //try { - var n = entry.NameLower; - //if (n.EndsWith(".ymap")) + var n = entry.Name; + //if (n.EndsWith(".ymap", StringComparison.OrdinalIgnoreCase)) //{ // UpdateStatus?.Invoke(string.Format(entry.Path)); // YmapFile ymapfile = RpfMan.GetFile(entry); @@ -427,7 +425,7 @@ namespace CodeWalker.GameFiles // MetaTypes.EnsureMetaTypes(ymapfile.Meta); // } //} - //else if (n.EndsWith(".ytyp")) + //else if (n.EndsWith(".ytyp", StringComparison.OrdinalIgnoreCase)) //{ // UpdateStatus?.Invoke(string.Format(entry.Path)); // YtypFile ytypfile = RpfMan.GetFile(entry); @@ -436,7 +434,7 @@ namespace CodeWalker.GameFiles // MetaTypes.EnsureMetaTypes(ytypfile.Meta); // } //} - //else if (n.EndsWith(".ymt")) + //else if (n.EndsWith(".ymt", StringComparison.OrdinalIgnoreCase)) //{ // UpdateStatus?.Invoke(string.Format(entry.Path)); // YmtFile ymtfile = RpfMan.GetFile(entry); @@ -447,7 +445,7 @@ namespace CodeWalker.GameFiles //} - if (n.EndsWith(".ymap") || n.EndsWith(".ytyp") || n.EndsWith(".ymt")) + if (n.EndsWith(".ymap", StringComparison.OrdinalIgnoreCase) || n.EndsWith(".ytyp", StringComparison.OrdinalIgnoreCase) || n.EndsWith(".ymt", StringComparison.OrdinalIgnoreCase)) { var rfe = entry as RpfResourceFileEntry; if (rfe == null) continue; @@ -465,7 +463,7 @@ namespace CodeWalker.GameFiles if (xml.Length != xml2.Length) { } - if ((xml != xml2) && (!n.EndsWith("srl.ymt") && !n.StartsWith("des_"))) + if ((xml != xml2) && (!n.EndsWith("srl.ymt", StringComparison.OrdinalIgnoreCase) && !n.StartsWith("des_", StringComparison.OrdinalIgnoreCase))) { } } @@ -501,13 +499,13 @@ namespace CodeWalker.GameFiles try #endif { - var n = entry.NameLower; - if (!(n.EndsWith(".pso") || - n.EndsWith(".ymt") || - n.EndsWith(".ymf") || - n.EndsWith(".ymap") || - n.EndsWith(".ytyp") || - n.EndsWith(".cut"))) + var n = entry.Name; + if (!(n.EndsWith(".pso", StringComparison.OrdinalIgnoreCase) || + n.EndsWith(".ymt", StringComparison.OrdinalIgnoreCase) || + n.EndsWith(".ymf", StringComparison.OrdinalIgnoreCase) || + n.EndsWith(".ymap", StringComparison.OrdinalIgnoreCase) || + n.EndsWith(".ytyp", StringComparison.OrdinalIgnoreCase) || + n.EndsWith(".cut", StringComparison.OrdinalIgnoreCase))) continue; //PSO files seem to only have these extensions var fentry = entry as RpfFileEntry; @@ -597,12 +595,12 @@ namespace CodeWalker.GameFiles { foreach (RpfEntry entry in file.AllEntries) { - var n = entry.NameLower; - if (!(n.EndsWith(".ymt") || - n.EndsWith(".ymf") || - n.EndsWith(".ymap") || - n.EndsWith(".ytyp") || - n.EndsWith(".cut"))) + var n = entry.Name; + if (!(n.EndsWith(".ymt", StringComparison.OrdinalIgnoreCase) || + n.EndsWith(".ymf", StringComparison.OrdinalIgnoreCase) || + n.EndsWith(".ymap", StringComparison.OrdinalIgnoreCase) || + n.EndsWith(".ytyp", StringComparison.OrdinalIgnoreCase) || + n.EndsWith(".cut", StringComparison.OrdinalIgnoreCase))) continue; //PSO files seem to only have these extensions var fentry = entry as RpfFileEntry; @@ -686,7 +684,7 @@ namespace CodeWalker.GameFiles var rfe = entry as RpfFileEntry; if (rfe == null) continue; - if (rfe.NameLower.EndsWith(".cut")) + if (rfe.IsExtension(".cut")) { UpdateStatus?.Invoke(string.Format(entry.Path)); @@ -729,7 +727,7 @@ namespace CodeWalker.GameFiles var rfe = entry as RpfFileEntry; if (rfe == null) continue; - if (rfe.NameLower.EndsWith(".yld")) + if (rfe.IsExtension(".yld")) { UpdateStatus?.Invoke(string.Format(entry.Path)); @@ -769,7 +767,7 @@ namespace CodeWalker.GameFiles var rfe = entry as RpfFileEntry; if (rfe == null) continue; - if (rfe.NameLower.EndsWith(".yed")) + if (rfe.IsExtension(".yed")) { UpdateStatus?.Invoke(string.Format(entry.Path)); @@ -813,7 +811,7 @@ namespace CodeWalker.GameFiles { //try //{ - if (entry.NameLower.EndsWith(".ycd")) + if (entry.IsExtension(".ycd")) { UpdateStatus?.Invoke(string.Format(entry.Path)); YcdFile ycd1 = RpfMan.GetFile(entry); @@ -1084,7 +1082,7 @@ namespace CodeWalker.GameFiles { //try { - if (entry.NameLower.EndsWith(".ytd")) + if (entry.IsExtension(".ytd")) { UpdateStatus?.Invoke(string.Format(entry.Path)); YtdFile ytdfile = null; @@ -1103,7 +1101,7 @@ namespace CodeWalker.GameFiles { var dds = Utils.DDSIO.GetDDSFile(tex); var tex2 = Utils.DDSIO.GetTexture(dds); - if (!tex.Name.StartsWith("script_rt")) + if (!tex.Name.StartsWith("script_rt", StringComparison.OrdinalIgnoreCase)) { if (tex.Data?.FullData?.Length != tex2.Data?.FullData?.Length) { } @@ -1181,7 +1179,7 @@ namespace CodeWalker.GameFiles { //try { - if (entry.NameLower.EndsWith(".ybn")) + if (entry.IsExtension(".ybn")) { UpdateStatus?.Invoke(string.Format(entry.Path)); YbnFile ybn = null; @@ -1353,7 +1351,7 @@ namespace CodeWalker.GameFiles { //try { - if (entry.NameLower.EndsWith(".ydr")) + if (entry.IsExtension(".ydr")) { UpdateStatus?.Invoke(string.Format(entry.Path)); YdrFile ydr = null; @@ -1412,7 +1410,7 @@ namespace CodeWalker.GameFiles { //try { - if (entry.NameLower.EndsWith(".ydd")) + if (entry.IsExtension(".ydd")) { UpdateStatus?.Invoke(string.Format(entry.Path)); YddFile ydd = null; @@ -1481,7 +1479,7 @@ namespace CodeWalker.GameFiles { //try { - if (entry.NameLower.EndsWith(".yft")) + if (entry.IsExtension(".yft")) { UpdateStatus?.Invoke(string.Format(entry.Path)); YftFile yft = null; @@ -1558,7 +1556,7 @@ namespace CodeWalker.GameFiles { //try { - if (entry.NameLower.EndsWith(".ypt")) + if (entry.IsExtension(".ypt")) { UpdateStatus?.Invoke(string.Format(entry.Path)); YptFile ypt = null; @@ -1616,7 +1614,7 @@ namespace CodeWalker.GameFiles { //try { - if (entry.NameLower.EndsWith(".ynv")) + if (entry.IsExtension(".ynv")) { UpdateStatus?.Invoke(string.Format(entry.Path)); YnvFile ynv = null; @@ -1697,9 +1695,9 @@ namespace CodeWalker.GameFiles var rfe = entry as RpfFileEntry; if (rfe == null) continue; - if (rfe.NameLower.EndsWith(".yvr")) + if (rfe.IsExtension(".yvr")) { - if (rfe.NameLower == "agencyprep001.yvr") continue; //this file seems corrupted + if (rfe.Name.Equals("agencyprep001.yvr", StringComparison.OrdinalIgnoreCase)) continue; //this file seems corrupted UpdateStatus?.Invoke(string.Format(entry.Path)); @@ -1745,10 +1743,9 @@ namespace CodeWalker.GameFiles try #endif { - var rfe = entry as RpfFileEntry; - if (rfe == null) continue; + if (entry is not RpfFileEntry rfe || rfe == null) continue; - if (rfe.NameLower.EndsWith(".ywr")) + if (rfe.IsExtension(".ywr")) { UpdateStatus?.Invoke(string.Format(entry.Path)); @@ -1789,7 +1786,7 @@ namespace CodeWalker.GameFiles { try { - if (entry.NameLower.EndsWith(".ymap")) + if (entry.IsExtension(".ymap")) { UpdateStatus?.Invoke(string.Format(entry.Path)); YmapFile ymapfile = RpfMan.GetFile(entry); @@ -1817,7 +1814,7 @@ namespace CodeWalker.GameFiles try { - if (rfe.NameLower.EndsWith(".ypdb")) + if (rfe.IsExtension(".ypdb")) { UpdateStatus?.Invoke(string.Format(entry.Path)); YpdbFile ypdb = RpfMan.GetFile(entry); @@ -1866,7 +1863,7 @@ namespace CodeWalker.GameFiles try { - if (rfe.NameLower.EndsWith(".yfd")) + if (rfe.IsExtension(".yfd")) { UpdateStatus?.Invoke(string.Format(entry.Path)); YfdFile yfd = RpfMan.GetFile(entry); @@ -1913,7 +1910,7 @@ namespace CodeWalker.GameFiles { try { - if (entry.NameLower.EndsWith(".mrf")) + if (entry.IsExtension(".mrf")) { UpdateStatus?.Invoke(string.Format(entry.Path)); MrfFile mrffile = RpfMan.GetFile(entry); @@ -2083,7 +2080,7 @@ namespace CodeWalker.GameFiles { try { - if (entry.NameLower.EndsWith(".fxc")) + if (entry.IsExtension(".fxc")) { UpdateStatus?.Invoke(string.Format(entry.Path)); var fxcfile = RpfMan.GetFile(entry); @@ -2298,7 +2295,7 @@ namespace CodeWalker.GameFiles { try { - if (doydr && entry.NameLower.EndsWith(".ydr")) + if (doydr && entry.IsExtension(".ydr")) { UpdateStatus?.Invoke(entry.Path); YdrFile ydr = RpfMan.GetFile(entry); @@ -2327,7 +2324,7 @@ namespace CodeWalker.GameFiles } } } - else if (doydd & entry.NameLower.EndsWith(".ydd")) + else if (doydd & entry.IsExtension(".ydd")) { UpdateStatus?.Invoke(entry.Path); YddFile ydd = RpfMan.GetFile(entry); @@ -2359,7 +2356,7 @@ namespace CodeWalker.GameFiles } } } - else if (doyft && entry.NameLower.EndsWith(".yft")) + else if (doyft && entry.IsExtension(".yft")) { UpdateStatus?.Invoke(entry.Path); YftFile yft = RpfMan.GetFile(entry); diff --git a/CodeWalker.Core/Utils/Cache.cs b/CodeWalker.Core/Utils/Cache.cs index 65fc7cf..7329c7c 100644 --- a/CodeWalker.Core/Utils/Cache.cs +++ b/CodeWalker.Core/Utils/Cache.cs @@ -14,11 +14,12 @@ namespace CodeWalker public long MaxMemoryUsage = 536870912; //512mb public long CurrentMemoryUsage = 0; public double CacheTime = 10.0; //seconds to keep something that's not used + public double LoadingCacheTime = 1.0; public DateTime CurrentTime = DateTime.Now; private LinkedList loadedList = new LinkedList(); private object loadedListLock = new object(); - private Dictionary> loadedListDict = new Dictionary>(); + private ConcurrentDictionary> loadedListDict = new ConcurrentDictionary>(); public int Count { @@ -45,50 +46,52 @@ namespace CodeWalker public TVal TryGet(TKey key) { - LinkedListNode lln = null; lock (loadedListLock) { - if (loadedListDict.TryGetValue(key, out lln)) + if (loadedListDict.TryGetValue(key, out var lln)) { - loadedList.Remove(lln); loadedList.AddLast(lln); lln.Value.LastUseTime = CurrentTime; } + return (lln != null) ? lln.Value : null; } - return (lln != null) ? lln.Value : null; } public bool TryAdd(TKey key, TVal item) { item.Key = key; if (CanAdd()) { + LinkedListNode lln; lock(loadedListLock) { - var lln = loadedList.AddLast(item); - loadedListDict.Add(key, lln); - Interlocked.Add(ref CurrentMemoryUsage, item.MemoryUsage); + lln = loadedList.AddLast(item); } + + lln.Value.LastUseTime = CurrentTime; + loadedListDict.TryAdd(key, lln); + Interlocked.Add(ref CurrentMemoryUsage, item.MemoryUsage); return true; } else { //cache full, check the front of the list for oldest.. var oldlln = loadedList.First; - var cachetime = CacheTime; + var cachetime = LoadingCacheTime; int iter = 0, maxiter = 2; while (!CanAdd() && (iter cachetime)) { - lock(loadedListLock) + Interlocked.Add(ref CurrentMemoryUsage, -oldlln.Value.MemoryUsage); + lock (loadedListLock) { - Interlocked.Add(ref CurrentMemoryUsage, -oldlln.Value.MemoryUsage); - loadedListDict.Remove(oldlln.Value.Key); loadedList.Remove(oldlln); //gc should free up memory later.. } + loadedListDict.TryRemove(oldlln.Value.Key, out _); + oldlln.Value = null; oldlln = null; //GC.Collect(); @@ -99,12 +102,13 @@ namespace CodeWalker } if (CanAdd()) //see if there's enough memory now... { + LinkedListNode newlln; lock(loadedListLock) { - var newlln = loadedList.AddLast(item); - loadedListDict.Add(key, newlln); - Interlocked.Add(ref CurrentMemoryUsage, item.MemoryUsage); + newlln = loadedList.AddLast(item); } + loadedListDict.TryAdd(key, newlln); + Interlocked.Add(ref CurrentMemoryUsage, item.MemoryUsage); return true; } else @@ -127,8 +131,8 @@ namespace CodeWalker { loadedList.Clear(); loadedListDict.Clear(); - CurrentMemoryUsage = 0; } + Interlocked.Exchange(ref CurrentMemoryUsage, 0); } public void Remove(TKey key) @@ -137,22 +141,21 @@ namespace CodeWalker { return; } - lock(loadedListLock) + + if (loadedListDict.TryRemove(key, out var n)) { - LinkedListNode n; - if (loadedListDict.TryGetValue(key, out n)) + lock (loadedListLock) { - loadedListDict.Remove(key); loadedList.Remove(n); - Interlocked.Add(ref CurrentMemoryUsage, -n.Value.MemoryUsage); } + Interlocked.Add(ref CurrentMemoryUsage, -n.Value.MemoryUsage); } } public void Compact() { - lock(loadedList) + lock(loadedListLock) { var oldlln = loadedList.First; while (oldlln != null) @@ -160,7 +163,7 @@ namespace CodeWalker if ((CurrentTime - oldlln.Value.LastUseTime).TotalSeconds < CacheTime) break; var nextln = oldlln.Next; Interlocked.Add(ref CurrentMemoryUsage, -oldlln.Value.MemoryUsage); - loadedListDict.Remove(oldlln.Value.Key); + loadedListDict.TryRemove(oldlln.Value.Key, out _); loadedList.Remove(oldlln); //gc should free up memory later.. oldlln.Value = null; diff --git a/CodeWalker.Core/Utils/StreamingExtensions.cs b/CodeWalker.Core/Utils/StreamingExtensions.cs new file mode 100644 index 0000000..e090d6e --- /dev/null +++ b/CodeWalker.Core/Utils/StreamingExtensions.cs @@ -0,0 +1,167 @@ +using System; +using System.Buffers; +using System.Collections.Generic; +using System.Diagnostics.Contracts; +using System.IO; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace CodeWalker.Core.Utils +{ + public static class StreamingExtensions + { + public static Task ReadAsync(this BinaryReader br, byte[] buffer, int index, int count) + { + return br.BaseStream.ReadAsync(buffer, index, count); + } + + + public static void CopyToFast(this Stream stream, Stream destination) + { + var buffer = ArrayPool.Shared.Rent(81920); + try + { + int read; + while ((read = stream.Read(buffer, 0, buffer.Length)) != 0) + destination.Write(buffer, 0, read); + } + finally + { + ArrayPool.Shared.Return(buffer); + } + } + + public static async Task CopyToFastAsync(this Stream stream, Stream destination, int bufferSize = 131072, CancellationToken cancellationToken = default) + { + var buffer = ArrayPool.Shared.Rent(bufferSize); + try + { + int bytesRead; + while ((bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false)) != 0) + { + await destination.WriteAsync(buffer, 0, bytesRead, cancellationToken).ConfigureAwait(false); + } + } + catch(Exception ex) + { + Console.WriteLine(ex.ToString()); + throw; + } + finally + { + ArrayPool.Shared.Return(buffer); + } + + } + + private static async Task FinishWriteAsync(Task writeTask, byte[] localBuffer) + { + try + { + await writeTask.ConfigureAwait(false); + } + finally + { + ArrayPool.Shared.Return(localBuffer); + } + } + + public static ValueTask WriteAsync(this Stream stream, Memory buffer, CancellationToken cancellationToken = default) + { + if (MemoryMarshal.TryGetArray(buffer, out ArraySegment array)) + { + return new ValueTask(stream.WriteAsync(array.Array!, array.Offset, array.Count, cancellationToken)); + } + + byte[] sharedBuffer = ArrayPool.Shared.Rent(buffer.Length); + buffer.Span.CopyTo(sharedBuffer); + return new ValueTask(FinishWriteAsync(stream.WriteAsync(sharedBuffer, 0, buffer.Length, cancellationToken), sharedBuffer)); + } + + public static void Write(this Stream stream, ReadOnlySpan buffer) + { + byte[] sharedBuffer = ArrayPool.Shared.Rent(buffer.Length); + try + { + buffer.CopyTo(sharedBuffer); + stream.Write(sharedBuffer, 0, buffer.Length); + } + finally + { + ArrayPool.Shared.Return(sharedBuffer); + } + } + + public static int Read(this Stream stream, Span buffer) + { + byte[] sharedBuffer = ArrayPool.Shared.Rent(buffer.Length); + try + { + int numRead = stream.Read(sharedBuffer, 0, buffer.Length); + if ((uint)numRead > (uint)buffer.Length) + { + throw new IOException("Stream too long!"); + } + + new ReadOnlySpan(sharedBuffer, 0, numRead).CopyTo(buffer); + return numRead; + } + finally + { + ArrayPool.Shared.Return(sharedBuffer); + } + } + + public static int Read(this Stream stream, Memory buffer) + { + if (MemoryMarshal.TryGetArray(buffer, out ArraySegment array)) + { + return stream.Read(array.Array!, array.Offset, array.Count); + } + + byte[] sharedBuffer = ArrayPool.Shared.Rent(buffer.Length); + try + { + int numRead = stream.Read(sharedBuffer, 0, buffer.Length); + if ((uint)numRead > (uint)buffer.Length) + { + throw new IOException("Stream too long!"); + } + + new ReadOnlySpan(sharedBuffer, 0, numRead).CopyTo(buffer.Span); + return numRead; + } + finally + { + ArrayPool.Shared.Return(sharedBuffer); + } + } + + public static ValueTask ReadAsync(this Stream stream, Memory buffer, CancellationToken cancellationToken = default) + { + if (MemoryMarshal.TryGetArray(buffer, out ArraySegment array)) + { + return new ValueTask(stream.ReadAsync(array.Array!, array.Offset, array.Count, cancellationToken)); + } + + byte[] sharedBuffer = ArrayPool.Shared.Rent(buffer.Length); + return FinishReadAsync(stream.ReadAsync(sharedBuffer, 0, buffer.Length, cancellationToken), sharedBuffer, buffer); + + static async ValueTask FinishReadAsync(Task readTask, byte[] localBuffer, Memory localDestination) + { + try + { + int result = await readTask.ConfigureAwait(false); + new ReadOnlySpan(localBuffer, 0, result).CopyTo(localDestination.Span); + return result; + } + finally + { + ArrayPool.Shared.Return(localBuffer); + } + } + } + } +} diff --git a/CodeWalker.Core/Utils/Utils.cs b/CodeWalker.Core/Utils/Utils.cs index c7c8159..3c39b63 100644 --- a/CodeWalker.Core/Utils/Utils.cs +++ b/CodeWalker.Core/Utils/Utils.cs @@ -90,22 +90,33 @@ namespace CodeWalker { if (bytes == null) { return string.Empty; } //file not found.. + var start = 0; + var length = bytes.Length; if ((bytes.Length > 3) && (bytes[0] == 0xEF) && (bytes[1] == 0xBB) && (bytes[2] == 0xBF)) { - byte[] newb = new byte[bytes.Length - 3]; - for (int i = 3; i < bytes.Length; i++) - { - newb[i - 3] = bytes[i]; - } - bytes = newb; //trim starting byte order mark + start = 3; + length = bytes.Length - 3; } - return Encoding.UTF8.GetString(bytes); + return Encoding.UTF8.GetString(bytes, start, length); } public static bool Contains(this string source, string toCheck, StringComparison comp) { return source?.IndexOf(toCheck, comp) >= 0; } + public static bool EndsWithAny(this string str, params string[] strings) + { + foreach(var searchString in strings) + { + if (str.EndsWith(searchString, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + } + + return false; + } + } @@ -264,6 +275,12 @@ namespace CodeWalker public static uint UpdateBit(uint value, int bit, bool flag) { if (flag) return SetBit(value, bit); + + + + + + else return ClearBit(value, bit); } public static uint RotateLeft(uint value, int count) @@ -275,24 +292,4 @@ namespace CodeWalker return (value >> count) | (value << (32 - count)); } } - - public static class SpanExtensions - { - public static int Read(this Stream stream, Span buffer) - { - var n = Math.Min(stream.Length - stream.Position, buffer.Length); - if (n <= 0) - return 0; - - for (int i = 0; i < buffer.Length; i++) - { - var result = stream.ReadByte(); - buffer[i] = (byte)result; - } - - return buffer.Length; - } - } - - } diff --git a/CodeWalker.Core/Utils/Vectors.cs b/CodeWalker.Core/Utils/Vectors.cs index 301ee4e..1c9ab3d 100644 --- a/CodeWalker.Core/Utils/Vectors.cs +++ b/CodeWalker.Core/Utils/Vectors.cs @@ -117,6 +117,24 @@ namespace CodeWalker return new Vector2I(a.X - b.X, a.Y - b.Y); } + public static bool operator ==(Vector2I a, Vector2I b) + { + return a.Equals(b); + } + + public static bool operator !=(Vector2I a, Vector2I b) + { + return !a.Equals(b); + } + + public override bool Equals(object obj) + { + if (obj is null) return false; + if (obj is not Vector2I vectorB) return false; + + return vectorB.X == this.X && vectorB.Y == this.Y; + } + } diff --git a/CodeWalker.Core/Utils/Xml.cs b/CodeWalker.Core/Utils/Xml.cs index 556e81a..96e643a 100644 --- a/CodeWalker.Core/Utils/Xml.cs +++ b/CodeWalker.Core/Utils/Xml.cs @@ -1,4 +1,5 @@ -using SharpDX; +using CodeWalker.GameFiles; +using SharpDX; using System; using System.Collections.Generic; using System.Linq; @@ -6,11 +7,27 @@ using System.Text; using System.Text.RegularExpressions; using System.Threading.Tasks; using System.Xml; +using System.Xml.Linq; namespace CodeWalker { public static class Xml { + public static void ValidateReaderState(XmlReader reader, string element) + { + if (reader == null) + { + throw new ArgumentNullException(nameof(reader)); + } + if (!reader.IsStartElement()) + { + throw new InvalidOperationException($"Expected reader to be at a start element but was at \"{reader.NodeType}\" with name \"{reader.Name}\""); + } + if (reader.Name != element) + { + throw new InvalidOperationException($"Expected reader to be at start element of \"{element}\" but was at \"{reader.NodeType}\" with name \"{reader.Name}\""); + } + } public static string GetStringAttribute(XmlNode node, string attribute) { @@ -63,6 +80,55 @@ namespace CodeWalker if (node == null) return null; return node.SelectSingleNode(name)?.InnerText; } + + public static string GetChildInnerText(XElement node, string name) { + if (node == null) return null; + return node.Element(name).Value; + } + + public static string GetChildInnerText(XmlReader reader, string name) + { + ValidateReaderState(reader, name); + if (reader.NodeType == XmlNodeType.Element) + { + if (reader.IsEmptyElement) + { + reader.ReadStartElement(); + return ""; + } + return reader.ReadElementContentAsString(); + } + else + { + return reader.ReadContentAsString(); + } + } + + public static bool GetChildBoolInnerText(XElement node, string name) + { + if (node == null) return false; + string val = node.Element(name).Value; + + bool b; + bool.TryParse(val, out b); + + return b; + } + + public static bool GetChildBoolInnerText(XmlReader reader, string name) + { + ValidateReaderState(reader, name); + if (reader.NodeType == XmlNodeType.Element) + { + return reader.ReadElementContentAsBoolean(); + } + else + { + return reader.ReadContentAsBoolean(); + } + + } + public static bool GetChildBoolInnerText(XmlNode node, string name) { if (node == null) return false; @@ -87,6 +153,14 @@ namespace CodeWalker FloatUtil.TryParse(val, out f); return f; } + + public static float GetChildFloatInnerText(XmlReader reader, string name) + { + ValidateReaderState(reader, name); + + return reader.ReadElementContentAsFloat(); + } + public static T GetChildEnumInnerText(XmlNode node, string name) where T : struct { if (node == null) return new T(); @@ -99,7 +173,7 @@ namespace CodeWalker { return default(T); } - if (val.StartsWith("hash_")) + if (val.StartsWith("hash_", StringComparison.OrdinalIgnoreCase)) { //convert hash_12ABC to Unk_12345 var substr = val.Substring(5); @@ -111,7 +185,18 @@ namespace CodeWalker return enumval; } + public static bool GetChildBoolAttribute(XmlReader reader, string name, string attribute = "value") + { + ValidateReaderState(reader, name); + string val = reader.GetAttribute(attribute); + + bool.TryParse(val, out bool boolval); + + reader.ReadStartElement(); + + return boolval; + } public static bool GetChildBoolAttribute(XmlNode node, string name, string attribute = "value") { if (node == null) return false; @@ -120,6 +205,20 @@ namespace CodeWalker bool.TryParse(val, out b); return b; } + + public static int GetChildIntAttribute(XmlReader reader, string name, string attribute = "value") + { + ValidateReaderState(reader, name); + + string val = reader.GetAttribute(attribute); + + int.TryParse(val, out var i); + + reader.ReadStartElement(); + + return i; + } + public static int GetChildIntAttribute(XmlNode node, string name, string attribute = "value") { if (node == null) return 0; @@ -128,12 +227,31 @@ namespace CodeWalker int.TryParse(val, out i); return i; } + + public static uint GetChildUIntAttribute(XmlReader reader, string name, string attribute = "value") + { + if (reader == null) return 0; + + uint i; + string val = reader.GetAttribute(attribute); + if (val?.StartsWith("0x", StringComparison.OrdinalIgnoreCase) ?? false) + { + var subs = val.Substring(2); + i = Convert.ToUInt32(subs, 16); + } + else + { + uint.TryParse(val, out i); + } + return i; + } + public static uint GetChildUIntAttribute(XmlNode node, string name, string attribute = "value") { if (node == null) return 0; string val = node.SelectSingleNode(name)?.Attributes[attribute]?.InnerText; uint i; - if (val?.StartsWith("0x") ?? false) + if (val?.StartsWith("0x", StringComparison.OrdinalIgnoreCase) ?? false) { var subs = val.Substring(2); i = Convert.ToUInt32(subs, 16); @@ -149,7 +267,7 @@ namespace CodeWalker if (node == null) return 0; string val = node.SelectSingleNode(name)?.Attributes[attribute]?.InnerText; ulong i; - if (val?.StartsWith("0x") ?? false) + if (val?.StartsWith("0x", StringComparison.OrdinalIgnoreCase) ?? false) { var subs = val.Substring(2); i = Convert.ToUInt64(subs, 16); @@ -160,6 +278,26 @@ namespace CodeWalker } return i; } + + public static float GetChildFloatAttribute(XmlReader reader, string name, string attribute = "value") + { + if (!string.IsNullOrEmpty(name)) + { + ValidateReaderState(reader, name); + } + + string val = reader.GetAttribute(attribute); + + FloatUtil.TryParse(val, out float f); + + if (!string.IsNullOrEmpty(name)) + { + reader.ReadStartElement(); + } + + return f; + } + public static float GetChildFloatAttribute(XmlNode node, string name, string attribute = "value") { if (node == null) return 0; @@ -174,12 +312,37 @@ namespace CodeWalker string val = node.SelectSingleNode(name)?.Attributes[attribute]?.InnerText; return val; } + + public static string GetChildStringAttribute(XmlReader reader, string name, string attribute = "value") + { + ValidateReaderState(reader, name); + + var val = reader.GetAttribute(attribute); + + reader.ReadStartElement(); + + return val; + } + public static Vector2 GetChildVector2Attributes(XmlNode node, string name, string x = "x", string y = "y") { float fx = GetChildFloatAttribute(node, name, x); float fy = GetChildFloatAttribute(node, name, y); return new Vector2(fx, fy); } + + public static Vector3 GetChildVector3Attributes(XmlReader reader, string name, string x = "x", string y = "y", string z = "z") + { + ValidateReaderState(reader, name); + float fx = GetChildFloatAttribute(reader, null, x); + float fy = GetChildFloatAttribute(reader, null, y); + float fz = GetChildFloatAttribute(reader, null, z); + + reader.ReadStartElement(); + + return new Vector3(fx, fy, fz); + } + public static Vector3 GetChildVector3Attributes(XmlNode node, string name, string x = "x", string y = "y", string z = "z") { float fx = GetChildFloatAttribute(node, name, x); @@ -207,6 +370,37 @@ namespace CodeWalker node.AppendChild(child); return child; } + + public static bool IsItemElement(this XmlReader reader) + { + if (reader.MoveToContent() == XmlNodeType.Element && reader.Name == "Item") + { + return true; + } + + return false; + } + + public static IEnumerable IterateItems(XmlReader reader, string parentElementName) + { + ValidateReaderState(reader, parentElementName); + reader.MoveToContent(); + if (reader.IsEmptyElement) + { + // Move past empty element + reader.ReadStartElement(parentElementName); + yield break; + } + reader.ReadStartElement(parentElementName); + while(reader.IsItemElement()) + { + if (XNode.ReadFrom(reader) is XElement el) + { + yield return el; + } + } + reader.ReadEndElement(); + } public static XmlElement AddChildWithInnerText(XmlDocument doc, XmlNode node, string name, string innerText) { XmlElement child = AddChild(doc, node, name); diff --git a/CodeWalker.Core/World/AudioZones.cs b/CodeWalker.Core/World/AudioZones.cs index a96834a..3bc4430 100644 --- a/CodeWalker.Core/World/AudioZones.cs +++ b/CodeWalker.Core/World/AudioZones.cs @@ -33,15 +33,23 @@ namespace CodeWalker.World List placements = new List(); - foreach (var relfile in GameFileCache.AudioDatRelFiles) + GameFileCache.AudioDatRelFilesLock.EnterReadLock(); + try { - if (relfile == null) continue; + foreach (var relfile in GameFileCache.AudioDatRelFiles) + { + if (relfile == null) continue; - placements.Clear(); + placements.Clear(); - CreatePlacements(relfile, placements, true); + CreatePlacements(relfile, placements, true); - PlacementsDict[relfile] = placements.ToArray(); + PlacementsDict[relfile] = placements.ToArray(); + } + } + finally + { + GameFileCache.AudioDatRelFilesLock.ExitReadLock(); } AllItems.AddRange(Zones); diff --git a/CodeWalker.Core/World/Scenarios.cs b/CodeWalker.Core/World/Scenarios.cs index 367f82b..4fd1f72 100644 --- a/CodeWalker.Core/World/Scenarios.cs +++ b/CodeWalker.Core/World/Scenarios.cs @@ -1799,7 +1799,6 @@ namespace CodeWalker.World var noneset = new AmbientModelSet(); noneset.Name = "NONE"; - noneset.NameLower = "none"; noneset.NameHash = JenkHash.GenHash("none"); sets[noneset.NameHash] = noneset; @@ -1808,10 +1807,10 @@ namespace CodeWalker.World { AmbientModelSet set = new AmbientModelSet(); set.Load(item); - if (!string.IsNullOrEmpty(set.NameLower)) + if (!string.IsNullOrEmpty(set.Name)) { - JenkIndex.Ensure(set.NameLower); - uint hash = JenkHash.GenHash(set.NameLower); + uint hash = JenkHash.GenHashLower(set.Name); + JenkIndex.Ensure(set.Name, hash); sets[hash] = set; } } @@ -1837,11 +1836,12 @@ namespace CodeWalker.World { ConditionalAnimsGroup group = new ConditionalAnimsGroup(); group.Load(item); - if (!string.IsNullOrEmpty(group.NameLower)) + if (!string.IsNullOrEmpty(group.Name)) { + uint hash = JenkHash.GenHashLower(group.Name); JenkIndex.Ensure(group.Name); - JenkIndex.Ensure(group.NameLower); - uint hash = JenkHash.GenHash(group.NameLower); + JenkIndex.Ensure(group.Name, hash); + groups[hash] = group; } } @@ -1903,7 +1903,6 @@ namespace CodeWalker.World string s_hash = hash.ToString("X"); ms = new AmbientModelSet(); ms.Name = $"UNKNOWN PED MODELSET ({s_hash})"; - ms.NameLower = ms.Name.ToLowerInvariant(); ms.NameHash = new MetaHash(hash); ms.Models = new AmbientModel[] { }; PedModelSets.Add(hash, ms); @@ -1922,7 +1921,6 @@ namespace CodeWalker.World string s_hash = hash.ToString("X"); ms = new AmbientModelSet(); ms.Name = $"UNKNOWN VEHICLE MODELSET ({s_hash})"; - ms.NameLower = ms.Name.ToLowerInvariant(); ms.NameHash = new MetaHash(hash); ms.Models = new AmbientModel[] {}; VehicleModelSets.Add(hash, ms); @@ -2110,7 +2108,6 @@ namespace CodeWalker.World [TypeConverter(typeof(ExpandableObjectConverter))] public class AmbientModelSet { public string Name { get; set; } - public string NameLower { get; set; } public MetaHash NameHash { get; set; } public AmbientModel[] Models { get; set; } @@ -2118,8 +2115,7 @@ namespace CodeWalker.World public void Load(XmlNode node) { Name = Xml.GetChildInnerText(node, "Name"); - NameLower = Name.ToLowerInvariant(); - NameHash = JenkHash.GenHash(NameLower); + NameHash = JenkHash.GenHashLower(Name); var models = node.SelectNodes("Models/Item"); var modellist = new List(); @@ -2190,14 +2186,12 @@ namespace CodeWalker.World { public string OuterXml { get; set; } public string Name { get; set; } - public string NameLower { get; set; } public void Load(XmlNode node) { OuterXml = node.OuterXml; Name = Xml.GetChildInnerText(node, "Name"); - NameLower = Name.ToLowerInvariant(); } public override string ToString() diff --git a/CodeWalker.Core/World/Space.cs b/CodeWalker.Core/World/Space.cs index c57dee8..b6df1dd 100644 --- a/CodeWalker.Core/World/Space.cs +++ b/CodeWalker.Core/World/Space.cs @@ -277,7 +277,7 @@ namespace CodeWalker.World { foreach (var entry in maprpf.AllEntries) { - if (entry.NameLower.EndsWith(".ymap")) + if (entry.IsExtension(".ymap")) { if (!nodedict.ContainsKey(new MetaHash(entry.ShortNameHash))) { @@ -297,7 +297,7 @@ namespace CodeWalker.World { } } } - if (entry.NameLower.EndsWith(".ybn")) + if (entry.IsExtension(".ybn")) { MetaHash ehash = new MetaHash(entry.ShortNameHash); if (!usedboundsdict.ContainsKey(ehash)) @@ -367,7 +367,7 @@ namespace CodeWalker.World } foreach (var dlcrpf in GameFileCache.DlcActiveRpfs) //load nodes from current dlc rpfs { - if (dlcrpf.Path.StartsWith("x64")) continue; //don't override update.rpf YNDs with x64 ones! *hack + if (dlcrpf.Path.StartsWith("x64", StringComparison.OrdinalIgnoreCase)) continue; //don't override update.rpf YNDs with x64 ones! *hack foreach (var rpffile in dlcrpf.Children) { AddRpfYnds(rpffile, yndentries); @@ -509,7 +509,7 @@ namespace CodeWalker.World if (entry is RpfFileEntry) { RpfFileEntry fentry = entry as RpfFileEntry; - if (entry.NameLower.EndsWith(".ynd")) + if (entry.IsExtension(".ynd")) { if (yndentries.ContainsKey(entry.NameHash)) { } @@ -809,7 +809,7 @@ namespace CodeWalker.World if (entry is RpfFileEntry) { RpfFileEntry fentry = entry as RpfFileEntry; - if (entry.NameLower.EndsWith(".ynv")) + if (entry.IsExtension(".ynv")) { if (ynventries.ContainsKey(entry.NameHash)) { } diff --git a/CodeWalker.Core/World/TimecycleMods.cs b/CodeWalker.Core/World/TimecycleMods.cs index 0da5aba..0e64065 100644 --- a/CodeWalker.Core/World/TimecycleMods.cs +++ b/CodeWalker.Core/World/TimecycleMods.cs @@ -38,7 +38,7 @@ namespace CodeWalker.World { foreach (var file in dlcrpf.AllEntries) { - if (file.NameLower.EndsWith(".xml") && file.NameLower.StartsWith("timecycle_mods_")) + if (file.IsExtension(".xml") && file.Name.StartsWith("timecycle_mods_", StringComparison.OrdinalIgnoreCase)) { LoadXml(rpfman.GetFileXml(file.Path)); } diff --git a/CodeWalker.RPFExplorer/CodeWalker.RPFExplorer.csproj b/CodeWalker.RPFExplorer/CodeWalker.RPFExplorer.csproj index 776afa0..ef4cb05 100644 --- a/CodeWalker.RPFExplorer/CodeWalker.RPFExplorer.csproj +++ b/CodeWalker.RPFExplorer/CodeWalker.RPFExplorer.csproj @@ -16,7 +16,10 @@ x64 - + + x64 + + diff --git a/CodeWalker.RPFExplorer/Program.cs b/CodeWalker.RPFExplorer/Program.cs index 3d237d4..baa1d9c 100644 --- a/CodeWalker.RPFExplorer/Program.cs +++ b/CodeWalker.RPFExplorer/Program.cs @@ -7,6 +7,7 @@ using System.Linq; using System.Threading.Tasks; using System.Windows.Forms; using CodeWalker.Utils; +using CodeWalker.Core.Utils; namespace CodeWalker.RPFExplorer { @@ -18,13 +19,31 @@ namespace CodeWalker.RPFExplorer [STAThread] static void Main() { - //Process.Start("CodeWalker.exe", "explorer"); - ConsoleWindow.Hide(); - Application.EnableVisualStyles(); - Application.SetCompatibleTextRenderingDefault(false); - Application.Run(new ExploreForm()); + try + { + if (!NamedPipe.TrySendMessageToOtherProcess("explorer")) + { + ConsoleWindow.Hide(); + //Process.Start("CodeWalker.exe", "explorer"); + Application.EnableVisualStyles(); + Application.SetCompatibleTextRenderingDefault(false); + + var form = new ExploreForm(); + var namedPipe = new NamedPipe(form); + namedPipe.Init(); + + Application.Run(form); + + GTAFolder.UpdateSettings(); + } + } + catch(Exception ex) + { + Console.WriteLine(ex.ToString()); + throw; + } + - GTAFolder.UpdateSettings(); } } } diff --git a/CodeWalker.Test/BinaryPrimitesFloats.cs b/CodeWalker.Test/BinaryPrimitesFloats.cs new file mode 100644 index 0000000..82b472a --- /dev/null +++ b/CodeWalker.Test/BinaryPrimitesFloats.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CodeWalker.Test +{ + internal class BinaryPrimitesFloats + { + } +} diff --git a/CodeWalker.Test/CodeWalker.Test.csproj b/CodeWalker.Test/CodeWalker.Test.csproj new file mode 100644 index 0000000..080e221 --- /dev/null +++ b/CodeWalker.Test/CodeWalker.Test.csproj @@ -0,0 +1,30 @@ + + + + net48 + enable + + false + latest + + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + diff --git a/CodeWalker.Test/JenkGenTests.cs b/CodeWalker.Test/JenkGenTests.cs new file mode 100644 index 0000000..91a01bb --- /dev/null +++ b/CodeWalker.Test/JenkGenTests.cs @@ -0,0 +1,96 @@ +using CodeWalker.GameFiles; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Xunit; + +namespace CodeWalker.Test +{ + public class JenkGenTests + { + [Fact] + public void GenHashMustReturnHash() + { + Assert.Equal((uint)1044535224, JenkHash.GenHash("ASDF")); + + Assert.Equal((uint)1485398060, JenkHash.GenHashLower("asdf")); + } + [Fact] + public void EnsureMustAddStringToDictionary() + { + JenkIndex.Index.Clear(); + Assert.Empty(JenkIndex.Index); + JenkIndex.Ensure("asdf"); + + Assert.Single(JenkIndex.Index); + } + + [Fact] + public void TryGetStringWithMissingHashMustReturnEmptyString() + { + JenkIndex.Index.Clear(); + Assert.Empty(JenkIndex.Index); + + var str = JenkIndex.TryGetString((uint)1485398060); + + Assert.Equal(string.Empty, str); + } + + [Fact] + public void GetStringWithMissingHashMustReturnHashAsString() + { + JenkIndex.Index.Clear(); + Assert.Empty(JenkIndex.Index); + + var str = JenkIndex.GetString((uint)1485398060); + + Assert.Equal(1485398060.ToString(), str); + } + + [Fact] + public void TryGetStringMustReturnAddedString() + { + JenkIndex.Ensure("asdf"); + + var str = JenkIndex.TryGetString((uint)1485398060); + + Assert.Equal("asdf", str); + } + + [Fact] + public void EnsureLowerMustAddLoweredStringToDictionary() + { + JenkIndex.Index.Clear(); + Assert.Empty(JenkIndex.Index); + JenkIndex.EnsureLower("ASDF"); + + Assert.Single(JenkIndex.Index); + + var str = JenkIndex.TryGetString((uint)1485398060); + + Assert.Equal("ASDF", str); + + var missingStr = JenkIndex.TryGetString((uint)1044535224); + Assert.Equal(string.Empty, missingStr); + } + + [Fact] + public void GetStringMustReturnAddedString() + { + JenkIndex.Index.Clear(); + Assert.Empty(JenkIndex.Index); + JenkIndex.EnsureLower("ASDF"); + + Assert.Single(JenkIndex.Index); + + var str = JenkIndex.TryGetString((uint)1485398060); + + Assert.Equal("ASDF", str); + + var missingStr = JenkIndex.TryGetString((uint)1044535224); + Assert.Equal(string.Empty, missingStr); + } + } +} diff --git a/CodeWalker.Test/TestBinaryConversions.cs b/CodeWalker.Test/TestBinaryConversions.cs new file mode 100644 index 0000000..e1c9f51 --- /dev/null +++ b/CodeWalker.Test/TestBinaryConversions.cs @@ -0,0 +1,445 @@ +using CodeWalker.GameFiles; +using System; +using System.Buffers.Binary; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Text; +using System.Xml.Linq; +using Xunit; + +namespace CodeWalker.Test +{ + public static class TestData + { + public const string ReferenceDataBytes = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAAAAAADWA9YDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAATABMAAAAAAAAAAAAAAAAACgAAAAAAAADZAdkBAAAAAAYAAAAAAAAAogGiAQAAAAABAAAAAAAAADsAOwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP3///8mAAAANgAAAEkAAAAAAAAAAAAAAAAAAAAAAAAAAACAQgAAgEIAAAAAAAAAAAAAAAAIQDQAAAAAAEgDSAMAAAAABwAAAAAAAAAGAAYAAAAAAAgAAAAAAAAANwA3AAAAAAAIwA0AAAAAAB4AHgAAAAAACEAVAAAAAAAOAA4AAAAAAAjAGAAAAAAACQAJAAAAAAAIABsAAAAAAAIAAgAAAAAACIAbAAAAAAABAAEAAAAAAA=="; + public static CScenarioPointRegion ReferenceData => new CScenarioPointRegion + { + AccelGrid = new rage__spdGrid2D + { + CellDimX = 64, + CellDimY = 64, + MaxCellX = 38, + MaxCellY = 73, + MinCellX = -3, + MinCellY = 54, + }, + ChainingGraph = new CodeWalker.GameFiles.CScenarioChainingGraph + { + Chains = new CodeWalker.GameFiles.Array_Structure + { + Count1 = 59, + Count2 = 59, + Pointer = 1, + }, + Edges = new CodeWalker.GameFiles.Array_Structure + { + Count1 = 418, + Count2 = 418, + Pointer = 6, + }, + Nodes = new CodeWalker.GameFiles.Array_Structure + { + Count1 = 473, + Count2 = 473, + Pointer = 10, + } + }, + Clusters = new CodeWalker.GameFiles.Array_Structure + { + Count1 = 6, + Count2 = 6, + Pointer = 7, + }, + EntityOverrides = new CodeWalker.GameFiles.Array_Structure + { + Count1 = 19, + Count2 = 19, + Pointer = 2, + }, + LookUps = new CodeWalker.GameFiles.CScenarioPointLookUps + { + GroupNames = new CodeWalker.GameFiles.Array_uint + { + Count1 = 9, + Count2 = 9, + Pointer = 1622024, + }, + InteriorNames = new CodeWalker.GameFiles.Array_uint + { + Count1 = 2, + Count2 = 2, + Pointer = 1769480, + }, + PedModelSetNames = new CodeWalker.GameFiles.Array_uint + { + Count1 = 30, + Count2 = 30, + Pointer = 901128, + }, + RequiredIMapNames = new CodeWalker.GameFiles.Array_uint + { + Count1 = 1, + Count2 = 1, + Pointer = 1802248, + }, + TypeNames = new CodeWalker.GameFiles.Array_uint + { + Count1 = 55, + Count2 = 55, + Pointer = 8, + }, + VehicleModelSetNames = new CodeWalker.GameFiles.Array_uint + { + Count1 = 14, + Count2 = 14, + Pointer = 1392648, + }, + }, + Points = new CodeWalker.GameFiles.CScenarioPointContainer + { + LoadSavePoints = new CodeWalker.GameFiles.Array_Structure + { + PointerDataIndex = 4294967295, + }, + MyPoints = new CodeWalker.GameFiles.Array_Structure + { + Count1 = 982, + Count2 = 982, + Pointer = 3, + } + }, + Unk_3844724227 = new CodeWalker.GameFiles.Array_ushort + { + Count1 = 840, + Count2 = 840, + Pointer = 3424264, + } + }; + } + + public class TestBinaryConversions + { + [Fact] + public void TestConvertData() + { + var data = BitConverter.GetBytes((uint)1234); + var number = MetaTypes.ConvertData(data); + if (number != (uint)1234) + { + throw new Exception("number 1234 converted back to itself doesn't equal 1234"); + } + } + + public bool Equals(Array_Structure a, Array_Structure b) + { + return a.Count1 == b.Count1 && a.Count2 == b.Count2 && a.Pointer == b.Pointer; + } + + public bool Equals(Array_uint a, Array_uint b) + { + return a.Count1 == b.Count1 && a.Count2 == b.Count2 && a.Pointer == b.Pointer; + } + + public bool Equals(rage__spdGrid2D a, rage__spdGrid2D b) + { + return a.Dimensions == b.Dimensions && a.Max == b.Max && a.Min == b.Min; + } + + [Fact] + public void Test_Convert_Data_From_Bytes() + { + var bytes = Convert.FromBase64String(TestData.ReferenceDataBytes); + + var newData = MetaTypes.ConvertData(bytes); + + Assert.Equal(TestData.ReferenceData, newData); + } + + [Fact] + public void Test_Convert_Data_From_Bytes_Offset() + { + var bytes = Convert.FromBase64String(TestData.ReferenceDataBytes); + bytes = new byte[4].Concat(bytes).ToArray(); + + var newData = MetaTypes.ConvertData(bytes, 4); + + Assert.Equal(TestData.ReferenceData, newData); + } + + [Fact] + public void Test_Convert_To_Bytes() + { + var bytes = MetaTypes.ConvertToBytes(TestData.ReferenceData); + + var bytesBase64 = Convert.ToBase64String(bytes); + + Assert.Equal(TestData.ReferenceDataBytes, bytesBase64); + } + + [Fact] + public void Test_Convert_Data_From_And_To_Bytes() + { + var bytes = MetaTypes.ConvertToBytes(TestData.ReferenceData); + + var newData = MetaTypes.ConvertData(bytes); + + Assert.Equal(TestData.ReferenceData, newData); + } + + private readonly SharpDX.Vector3[] referenceArray = new SharpDX.Vector3[] { new SharpDX.Vector3(12.5f, 23.0f, 65.0f), new SharpDX.Vector3(1234.234f, 17896.22f, 9845.12f), new SharpDX.Vector3(-12.3f, -82349.123f, 1234.9f), new SharpDX.Vector3(-23412.4f, 1289.2f, 948.0f) }; + private readonly string referenceArrayBytes = "AABIQQAAuEEAAIJCfUeaRHHQi0Z71BlGzcxEwZDWoMfNXJpEzei2xmYmoUQAAG1E"; + + [Fact] + public void Test_Convert_Array_To_Bytes() + { + var bytes = MetaTypes.ConvertArrayToBytes(referenceArray); + + var base64 = Convert.ToBase64String(bytes); + + Assert.Equal(referenceArrayBytes, base64); + } + + [Fact] + public void Test_Convert_Bytes_To_Array() + { + var bytes = Convert.FromBase64String(referenceArrayBytes); + var arr = MetaTypes.ConvertDataArray(bytes, 0, referenceArray.Length).ToArray(); + Assert.Equal(referenceArray, arr); + + var newBytes = MetaTypes.ConvertArrayToBytes(arr); + + var base64 = Convert.ToBase64String(newBytes); + + Assert.Equal(referenceArrayBytes, base64); + } + } + + public class TestResourceData + { + [Fact] + public void Test_Write_Struct() + { + using var systemStream = new MemoryStream(); + using var graphicsStream = new MemoryStream(); + + using var dataWriter = new ResourceDataWriter(systemStream, graphicsStream); + dataWriter.Position = 0x50000000; + + dataWriter.WriteStruct(TestData.ReferenceData); + + var buffer = systemStream.ToArray(); + + var base64 = Convert.ToBase64String(buffer); + + Assert.Equal(TestData.ReferenceDataBytes, base64); + } + + [Fact] + public void Test_Read_Struct() + { + var buffer = Convert.FromBase64String(TestData.ReferenceDataBytes); + using var systemStream = new MemoryStream(buffer); + using var graphicsStream = new MemoryStream(); + + using var dataReader = new ResourceDataReader(systemStream, graphicsStream); + + var region = dataReader.ReadStruct(); + + Assert.Equal(TestData.ReferenceData, region); + } + + [Fact] + public void Test_Write_Single() + { + using var systemStream = new MemoryStream(); + using var graphicsStream = new MemoryStream(); + + using var dataWriter = new ResourceDataWriter(systemStream, graphicsStream); + dataWriter.Position = 0x50000000; + + dataWriter.Write(20.0f); + + var buffer = systemStream.ToArray(); + var result = BitConverter.ToSingle(buffer, 0); + + Assert.Equal(20.0f, result); + } + + [Fact] + public void Test_Write_Single_Big_Endian() + { + using var systemStream = new MemoryStream(); + using var graphicsStream = new MemoryStream(); + + using var dataWriter = new ResourceDataWriter(systemStream, graphicsStream, Endianess.BigEndian); + dataWriter.Position = 0x50000000; + + dataWriter.Write(20.0f); + + var buffer = systemStream.ToArray(); + + var valueInt = BinaryPrimitives.ReadInt32BigEndian(buffer); + var valueFloat = Unsafe.ReadUnaligned(ref Unsafe.As(ref valueInt)); + //BinaryPrimitives.ReverseEndianness(20.0f) + + Assert.Equal(20.0f, valueFloat); + + dataWriter.Position = 0x50000000; + + dataWriter.Write(double.MaxValue); + + buffer = systemStream.ToArray(); + + var valueLong = BinaryPrimitives.ReadInt64BigEndian(buffer); + var valueDouble = Unsafe.ReadUnaligned(ref Unsafe.As(ref valueLong)); + + Assert.Equal(double.MaxValue, valueDouble); + } + + [Fact] + public void Test_Write_Multiple_Structs() + { + using var systemStream = new MemoryStream(); + using var graphicsStream = new MemoryStream(); + + using var dataWriter = new ResourceDataWriter(systemStream, graphicsStream); + dataWriter.Position = 0x50000000; + + dataWriter.WriteStructs(new[] + { + TestData.ReferenceData, + TestData.ReferenceData, + TestData.ReferenceData, + TestData.ReferenceData + }); + + var buffer = systemStream.ToArray(); + + var structs = MemoryMarshal.Cast(buffer); + + Assert.Equal(4, structs.Length); + + foreach (var scenarioRegion in structs) + { + Assert.Equal(TestData.ReferenceData, scenarioRegion); + } + } + + [Fact] + public void Test_Read_Multiple_Structs() + { + using var systemStream = new MemoryStream(); + using var graphicsStream = new MemoryStream(); + + using var dataWriter = new ResourceDataWriter(systemStream, graphicsStream); + dataWriter.Position = 0x50000000; + + dataWriter.WriteStructs(new[] + { + TestData.ReferenceData, + TestData.ReferenceData, + TestData.ReferenceData, + TestData.ReferenceData + }); + + var buffer = systemStream.ToArray(); + systemStream.Position = 0; + var dataReader = new ResourceDataReader(systemStream, graphicsStream); + + + var structs = dataReader.ReadStructs(4); + + Assert.Equal(4, structs.Length); + + foreach (var scenarioRegion in structs) + { + Assert.Equal(TestData.ReferenceData, scenarioRegion); + } + + structs = dataReader.ReadStructsAt(0 | 0x50000000, 4); + + foreach (var scenarioRegion in structs) + { + Assert.Equal(TestData.ReferenceData, scenarioRegion); + } + + systemStream.Position = 0; + dataWriter.Position = 0x50000000; + + dataWriter.Write(new byte[] { 0, 0, 0, 0 }); + + dataWriter.WriteStructs(new[] +{ + TestData.ReferenceData, + TestData.ReferenceData, + TestData.ReferenceData, + TestData.ReferenceData + }); + + structs = dataReader.ReadStructsAt(4 | 0x50000000, 4); + + foreach (var scenarioRegion in structs) + { + Assert.Equal(TestData.ReferenceData, scenarioRegion); + } + } + + //[Fact] + //public void Test() + //{ + // var attachedObjects = MetaTypes.GetUintArray(meta, _Data.attachedObjects); + //} + + private const string ArchetypeData = "AAAAAAAAAAAAAMhCACAAAAAAAAAAAAAAAAAAAAAAAACYimfA1ehVuLYT5b4BAIB/JopnQFmFTTiUE+U+AQCAfwAAZLfAN4a1AAAItQEAgH/sTWlAAACgQFVVCx0lzgxeAAAAAAAAAAAAAAAAAgAAAFVVCx0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMhCACAAAAAAAAAAAAAAAAAAAAAAAAAY0EjAAAAAAC6txr4BAIB/MdBIQL03hjUMrcY+AQCAfwAAQDa9NwY1AAAItQEAgH/FV0pAAACgQNUOy/klzgxeAAAAAAAAAAAAAAAAAgAAANUOy/kAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMhCACAAAAAAAAAAAAAAAAAAAAAAAAAx0EjAvTeGtS6txr4BAIB/GNBIQAAAAAAMrcY+AQCAfwAAULa9Nwa1AAAItQEAgH/FV0pAAACgQEiqhAclzgxeAAAAAAAAAAAAAAAAAgAAAEiqhAcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMhCACAAAAAAAAAAAAAAAAAAAAAAAAAY0EjAkwEgvC6txr4BAIB/MdBIQGH9H7wMrcY+AQCAfwAAQDZ6/x+8AAAItQEAgH/FV0pAAACgQKSeG4ElzgxeAAAAAAAAAAAAAAAAAgAAAKSeG4EAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMhCACAAAAAAAAAAAAAAAAAAAAAAAAAxtGa/csLov5Jc7r4BAIB/MbRmP4vC6D8OgpA/AQCAfwAAAAAAAMA10tWpPgEAgH/jjAtAAACgQIfyZY4lzgxeAAAAAAAAAAAAAAAAAgAAAIfyZY4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMhCACAAAAAAAAAAAAAAAAAAAAAAAAALJAPB8jGNwZaxG8ABAIB/NAYFQeBdl0FFuWFAAQCAfwAVcT3gviI/YA8MPwEAgH8sQqJBAACgQHL9S+klzgxeAAAAAAAAAAAAAAAAAgAAAHL9S+kAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMhCAAAAAAAAAAAAAAAAAAAAAAAAAAAKLn/ABr2HQCHIl78BAIB/v5s+wMU4s0ArFKE/AQCAf+TkXsDmep1AoMAUPQEAgH9vRb4/AACgQFfFB8MlzgxeAAAAAAAAAAAAAAAAAgAAAFfFB8MAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMhCAAAAAAAAAAAAAAAAAAAAAAAAAACi49fArIuHwUSHNMABAIB/uePXQCvaikFEhzRAAQCAfwAAwDYAoFM+AAAAAAEAgH9xJpVBAACgQHfsUVglzgxeAAAAAAAAAAAAAAAAAgAAAHfsUVgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMhCAAAAAAAAAAAAAAAAAAAAAAAAAAALiATB8jGNwZaxG8ABAIB/NAYFQeBdl0ETZG9AAQCAfwBUfDzgviI/+GQnPwEAgH8mh6JBAACgQBJukFolzgxeAAAAAAAAAAAAAAAAAgAAABJukFoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMhCAAAAAAAAAAAAAAAAAAAAAAAAAABCa4rAzOZiwV1wB8ABAIB/LdDSQFwRgEEnUEBAAQCAf9jJkD9g32k/KH/jPgEAgH/3B4JBAACgQAPtbvIlzgxeAAAAAAAAAAAAAAAAAgAAAAPtbvIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMhCAAgAAAAAAAAAAAAAAAAAAAAAAABw/QLBnTGhwUn1278BAIB/VEUPQa5Ll0G0TJ1AAQCAf0B+xD7gXh6/w57MPwEAgH+hraxBAACgQMJw94QAAAAAAAAAAAAAAAAAAAAAAgAAAMJw94QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMhCAAAAAAAAAAAAAAAAAAAAAAAAAABQmNbAvGduwY4GYL8BAIB/X7TVQDqwQ0EctYY/AQCAfwDwY7wI3qq/qI61PQEAgH8dg3JBAACgQF2sfxslzgxeAAAAAAAAAAAAAAAAAgAAAF2sfxsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMhCAAAAAAAAAAAAAAAAAAAAAAAAAACnXNjAzySKwT7MAUABAIB/FD7TQA7RjUEhr1BAAQCAf0DSo72AD2s+sD0pQAEAgH9G65VBAACgQP9/spclzgxeAAAAAAAAAAAAAAAAAgAAAP9/spcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMhCAAAAAAAAAAAAAAAAAAAAAAAAAAAANwu9enIzwFxymr8BAIB/lSkmPXVyM0BUcpo/AQCAf6CUVzsAAAC1AAAAtQEAgH9bX0NAAACgQC4F4xglzgxeAAAAAAAAAAAAAAAAAgAAAC4F4xgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMhCAAAAAAAAAAAAAAAAAAAAAAAAAACHiFu9cXIzwFxymr8BAIB/4PZEPXpyM0BUcpo/AQCAfzCNNLsAAKA1AAAAtQEAgH+RYkNAAACgQP3hSkclzgxeAAAAAAAAAAAAAAAAAgAAAP3hSkcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMhCAAAAAAAAAAAAAAAAAAAAAAAAAAAJNri/F/HdvX7gLr8BAIB/pDW4PzDz3T1+4C4/AQCAfwAAzLYAgAY2AAAAAAEAgH9jYcw/AACgQM6jquglzgxeAAAAAAAAAAAAAAAAAgAAAM6jqugAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMhCAAAAAAAAAAAAAAAAAAAAAAAAAACHiFu9dXIzwFxymr8BAIB/4PZEPXVyM0BUcpo/AQCAfzCNNLsAAAAAAAAAtQEAgH+NYkNAAACgQDjw2rUlzgxeAAAAAAAAAAAAAAAAAgAAADjw2rUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMhCACAAAAAAAAAAAAAAAAAAAAAAAABHdwTAqd0awJKRs7oBAIB/gXcEQKXdGkCSkbM6AQCAfwAA6DYAAAC1AAAAAAEAgH99yktAAACgQLF+sQIlzgxeAAAAAAAAAAAAAAAAAgAAALF+sQIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMhCACAAAAAAAAAAAAAAAAAAAAAAAABVL7m/3EjpvwAAAAABAIB/LzC5PwZJ6T8AAAAAAQCAfwAAWjcAACg2AAAAAAEAgH8U7RRAAACgQIFf4BwlzgxeAAAAAAAAAAAAAAAAAgAAAIFf4BwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMhCACAAAAAAAAAAAAAAAAAAAAAAAADG+0XAsDpHwL03hrUBAIB/ZftFQKw6R0C9NwY2AQCAfwAAQLcAAAC1vjcGNQEAgH+zb4xAAACgQCUyMgYlzgxeAAAAAAAAAAAAAAAAAgAAACUyMgYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMhCACAAAAAAAAAAAAAAAAAAAAAAAAD/JX2/AHAjwL03hrUBAIB/ryR9P+9vI0AAAAAAAQCAfwAAKLcAAAC2vTcGtQEAgH81Qy9AAACgQJqWbPglzgxeAAAAAAAAAAAAAAAAAgAAAJqWbPgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMhCACAAAAAAAAAAAAAAAAAAAAAAAAAAxgXA/3l5wJxTSbYBAIB/68UFQAd6eUCcU0k2AQCAfwAAILYAAIA1AAAAAAEAgH8kio1AAACgQPUilP4lzgxeAAAAAAAAAAAAAAAAAgAAAPUilP4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMhCACAAAAAAAAAAAAAAAAAAAAAAAAADQ7y/9pcIwAAAAAABAIB/fEK8P/aXCEC9N4Y1AQCAfwAABrcAAAAAvTcGNQEAgH/z4iVAAACgQJaGZPAlzgxeAAAAAAAAAAAAAAAAAgAAAJaGZPAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMhCACAAAAAAAAAAAAAAAAAAAAAAAACKAQ7A93bXvxb7y7wBAIB/AAIOQP921z8v/cs8AQCAfwAAbDcAAAA1AAAGNQEAgH86QTJAAACgQLwZ/DklzgxeAAAAAAAAAAAAAAAAAgAAALwZ/DkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMhCACAAAAAAAAAAAAAAAAAAAAAAAADUIwPBslSewcrf7b8BAIB/MgYFQeBdl0Ho2OG/AQCAfwAvcT1A2t6+WdznvwEAgH8QWKhBAACgQDcbq+MlzgxeAAAAAAAAAAAAAAAAAgAAADcbq+MAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMhCACAAAAAAAAAAAAAAAAAAAAAAAADYfszAdjI/wSdPCb8BAIB/qH7MQHUyP0EnTwk/AQCAfwAAQLcAAAAAAAAAAAEAgH9h/VhBAACgQL+uSSUlzgxeAAAAAAAAAAAAAAAAAgAAAL+uSSUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"; + + private readonly CBaseArchetypeDef referenceArchetype = new CBaseArchetypeDef + { + assetName = new MetaHash((uint)0x1d0b5555), + assetType = rage__fwArchetypeDef__eAssetType.ASSET_TYPE_DRAWABLE, + bbMax = new SharpDX.Vector3(3.617807f, 4.9E-05f, 0.447415f), + bbMin = new SharpDX.Vector3(-3.617834f, -5.1E-05f, -0.447416f), + bsCentre = new SharpDX.Vector3(-1.3589859E-05f, -1.00000034E-06f, -5.066395E-07f), + bsRadius = 3.645381f, + hdTextureDist = 5, + lodDist = 100, + Unused05 = float.NaN, + Unused06 = float.NaN, + Unused07 = float.NaN, + name = new MetaHash((uint)0x1d0b5555), + textureDictionary = new MetaHash((uint)0x5e0cce25), + flags = 0x00002000, + }; + + [Fact] + public void TestConvertDataRaw() + { + var data = Convert.FromBase64String(ArchetypeData); + var basearch = PsoTypes.ConvertDataRawOld(data, 0); + + Assert.Equivalent(referenceArchetype, basearch); + + var basearch2 = PsoTypes.ConvertDataRaw(data, 0); + + Assert.Equivalent(basearch, basearch2); + + var random = new Random(); + var length = random.Next(100); + + var newData = Enumerable.Empty().Concat(new byte[length]).Concat(data).ToArray(); + + basearch = PsoTypes.ConvertDataRawOld(newData, length); + Assert.Equivalent(basearch, referenceArchetype); + + basearch2 = PsoTypes.ConvertDataRaw(newData, length); + + Assert.Equivalent(basearch, basearch2); + + //a.Extensions = MetaTypes.GetExtensions(Meta, basearch.extensions); + + + } + } +} \ No newline at end of file diff --git a/CodeWalker.Test/XmlTests.cs b/CodeWalker.Test/XmlTests.cs new file mode 100644 index 0000000..a00b248 --- /dev/null +++ b/CodeWalker.Test/XmlTests.cs @@ -0,0 +1,1081 @@ +using CodeWalker.GameFiles; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Xml; +using System.Xml.Linq; +using Xunit; +using Xunit.Abstractions; + +namespace CodeWalker.Test +{ + public class XmlTests + { + private readonly ITestOutputHelper output; + public XmlTests(ITestOutputHelper output) + { + this.output = output; + } + public static string markup = @" + vehshare + + + + brabusgt600brabusgt600 + brabusgt600 + GT 600 + BRABUS + null + null + null + null + + null + ta176m177 + LAYOUT_LOW + BULLET_COVER_OFFSET_INFO + EXPLOSION_INFO_DEFAULT + + DEFAULT_FOLLOW_VEHICLE_CAMERA + MID_BOX_VEHICLE_AIM_CAMERA + VEHICLE_BONNET_CAMERA_NEAR_EXTRA_HIGH + DEFAULT_POV_CAMERA + + + + + + + + + + + + + + VFXVEHICLEINFO_CAR_BULLET + + + + + + + + + + + + + + + + + + + + + + 60.000000 + 80.000000 + 100.000000 + 120.000000 + 500.000000 + 500.000000 + + + + + + + + + + + SWANKNESS_3 + + FLAG_SPORTS FLAG_RICH_CAR FLAG_NO_BROKEN_DOWN_SCENARIO FLAG_RECESSED_TAILLIGHT_CORONAS FLAG_NO_HEAVY_BRAKE_ANIMATION + VEHICLE_TYPE_CAR + VPT_FRONT_AND_BACK_PLATES + VDT_BANSHEE + VC_SPORT + VWT_HIEND + + docktrailer + trailers + trailers2 + trailers3 + trailers4 + tanker + trailerlogs + tr2 + trflat + + + + + S_M_Y_Cop_01 + + + + + + + + + + + + WHEEL_FRONT_RIGHT_CAMERA + WHEEL_FRONT_LEFT_CAMERA + WHEEL_REAR_RIGHT_CAMERA + WHEEL_REAR_LEFT_CAMERA + + + + + + + + + + + LOW_BULLET_FRONT_LEFT + LOW_BULLET_FRONT_RIGHT + + + + + + + + + + + + + + + + + vehicles_banshee_interior + brabusgt600 + + + + + + +"; + + public static string pedsList = @" + + comp_peds_generic + + + + CS_LesterCrest_2 + CS_LesterCrest_2_p + move_m@generic + null + + CS_LesterCrest + CS_LesterCrest + CIVMALE + move_m@generic + move_ped_strafing + move_ped_to_strafe + move_strafe_injured + dam_ko + dam_ad + ANIM_GROUP_GESTURE_M_GENERIC + facial_clipset_group_gen_male + ANIM_GROUP_VISEMES_M_HI + CLIP_SET_ID_INVALID + Male + Male_prone + NMBS_SLOW_GETUPS + 3Lateral_Facial + DEFAULT + STANDARD_PED + STANDARD_PED + STANDARD_MALE + + + + NO_IK + + + + + + + + CIVMALE + STANDARD_PED + DEFAULT_PERCEPTION + BS_AI + WEAPON_UNARMED + Streamed_Male + DEFAULT + VFXPEDINFO_HUMAN_GENERIC + FLEE + RADIO_GENRE_OFF + RADIO_GENRE_OFF + + + + + + + + + + + + + + SILENT_CUTSCENE_PVG + + SAT_NONE + TB_WARM + SLOD_HUMAN + SCENARIO_POP_STREAMING_NORMAL + DSP_NORMAL + + + + + IG_LesterCrest_2 + IG_LesterCrest_2_p + move_characters@lester@STD_CaneUp + null + expr_set_ambient_male + null + null + CIVMALE + move_lester_CaneUp + move_ped_strafing + move_ped_to_strafe + move_strafe_injured + dam_ko + dam_ad + ANIM_GROUP_GESTURE_M_GENERIC + facial_clipset_group_gen_male + ANIM_GROUP_VISEMES_M_LO + CLIP_SET_ID_INVALID + Male + Male_prone + NMBS_SLOW_GETUPS + ambientPed_upperWrinkles + DEFAULT + STANDARD_PED + STANDARD_PED + STANDARD_MALE + + + + + + + + + + + + CIVMALE + STANDARD_PED + DEFAULT_PERCEPTION + BS_AI + WEAPON_UNARMED + Streamed_Male + DEFAULT + VFXPEDINFO_HUMAN_GENERIC + FLEE + RADIO_GENRE_REGGAE + RADIO_GENRE_SURF + + + + + + + + + + + + + + LESTER_PVG + + SAT_NONE + TB_WARM + SLOD_HUMAN + SCENARIO_POP_STREAMING_NORMAL + DSP_NORMAL + + + + + CSB_Mrs_R + null + move_f@generic + null + + CSB_Denise_friend + CSB_Denise_friend + CIVFEMALE + move_characters@patricia + move_ped_strafing + move_ped_to_strafe + move_strafe_injured + dam_ko + dam_ad + ANIM_GROUP_GESTURE_F_GENERIC + facial_clipset_group_gen_female + ANIM_GROUP_VISEMES_F_LO + CLIP_SET_ID_INVALID + Male + Male_prone + NMBS_SLOW_GETUPS + null + DEFAULT + STANDARD_PED + STANDARD_PED + STANDARD_FEMALE + + + + + + + + + + + + CIVFEMALE + STANDARD_PED + DEFAULT_PERCEPTION + BS_AI + WEAPON_UNARMED + Streamed_Female + DEFAULT + VFXPEDINFO_HUMAN_GENERIC + FLEE + RADIO_GENRE_POP + RADIO_GENRE_DANCE + + + + + + + + + + + + + + SILENT_CUTSCENE_PVG + + SAT_NONE + TB_WARM + SLOD_HUMAN + SCENARIO_POP_STREAMING_NORMAL + DSP_NORMAL + + + + + CSB_Avon + CSB_Avon_p + move_m@generic + null + + CSB_Avon + CSB_Avon + CIVMALE + move_m@generic + move_ped_strafing + move_ped_to_strafe + move_strafe_injured + dam_ko + dam_ad + ANIM_GROUP_GESTURE_M_GENERIC + facial_clipset_group_gen_male + ANIM_GROUP_VISEMES_M_HI + CLIP_SET_ID_INVALID + Male + Male_prone + NMBS_SLOW_GETUPS + null + DEFAULT + STANDARD_PED + STANDARD_PED + STANDARD_MALE + + + + + + + + + + + + CIVMALE + STANDARD_PED + DEFAULT_PERCEPTION + BS_AI + WEAPON_UNARMED + Streamed_Male + DEFAULT + VFXPEDINFO_HUMAN_GENERIC + FLEE + RADIO_GENRE_OFF + RADIO_GENRE_OFF + + + + + + + + + + + + + + SILENT_CUTSCENE_PVG + + SAT_NONE + TB_WARM + SLOD_HUMAN + SCENARIO_POP_STREAMING_NORMAL + DSP_NORMAL + + + + + IG_Avon + Ig_Avon_p + move_m@generic + null + expr_set_ambient_male_skirt + null + null + CIVMALE + move_chubby + move_ped_strafing + move_ped_to_strafe + move_strafe_injured + dam_ko + dam_ad + ANIM_GROUP_GESTURE_M_GENERIC + facial_clipset_group_gen_male + ANIM_GROUP_VISEMES_M_LO + CLIP_SET_ID_INVALID + Male + Male_prone + NMBS_SLOW_GETUPS + null + DEFAULT + STANDARD_PED + STANDARD_PED + STANDARD_MALE + + + + + + + + + + + + CIVMALE + STANDARD_PED + DEFAULT_PERCEPTION + BS_AI + WEAPON_UNARMED + CONSTRUCTION + DEFAULT + VFXPEDINFO_HUMAN_GENERIC + FLEE + RADIO_GENRE_MODERN_ROCK + RADIO_GENRE_MOTOWN + + + + + + + + + + SF_JEER_AT_HOT_PED + + + + H2AVON_PVG + + SAT_NONE + TB_WARM + SLOD_HUMAN + SCENARIO_POP_STREAMING_NORMAL + DSP_NORMAL + + + + + CSB_Bogdan + CSB_Bogdan_p + move_m@generic + null + + CSB_Bogdan + CSB_Bogdan + CIVMALE + move_m@generic + move_ped_strafing + move_ped_to_strafe + move_strafe_injured + dam_ko + dam_ad + ANIM_GROUP_GESTURE_M_GENERIC + facial_clipset_group_gen_male + ANIM_GROUP_VISEMES_M_HI + CLIP_SET_ID_INVALID + Male + Male_prone + NMBS_SLOW_GETUPS + null + DEFAULT + STANDARD_PED + STANDARD_PED + STANDARD_MALE + + + + + + + + + + + + CIVMALE + STANDARD_PED + DEFAULT_PERCEPTION + BS_AI + WEAPON_UNARMED + Streamed_Male + DEFAULT + VFXPEDINFO_HUMAN_GENERIC + FLEE + RADIO_GENRE_OFF + RADIO_GENRE_OFF + + + + + + + + + + + + + + H2_BOGDAN_PVG + + SAT_NONE + TB_WARM + SLOD_HUMAN + SCENARIO_POP_STREAMING_NORMAL + DSP_NORMAL + + + + + U_M_Y_Juggernaut_01 + U_M_Y_Juggernaut_01_p + move_m@multiplayer + null + + HC_Gunman + HC_Gunman + CIVMALE + move_m@multiplayer + move_ped_strafing + move_ped_to_strafe + move_strafe_injured + dam_ko + dam_ad + ANIM_GROUP_GESTURE_M_GENERIC + facial_clipset_group_gen_male + ANIM_GROUP_VISEMES_M_LO + CLIP_SET_ID_INVALID + Male + Male_prone + NMBS_SLOW_GETUPS + fx_fire_torch + DEFAULT + STANDARD_PED + STANDARD_PED + STANDARD_MALE + + + + + + + + + + + + CIVMALE + STANDARD_PED + DEFAULT_PERCEPTION + BS_AI + WEAPON_UNARMED + Streamed_Male + DEFAULT + VFXPEDINFO_HUMAN_GENERIC + FLEE + RADIO_GENRE_PUNK + RADIO_GENRE_MOTOWN + + + + + + + + + + + + + COLLAR + SILENT_PVG + + SAT_NONE + TB_WARM + SLOD_HUMAN + SCENARIO_POP_STREAMING_NORMAL + DSP_NORMAL + + + + + MP_M_AvonGoon + MP_M_AvonGoon_p + move_m@generic + null + expr_set_ambient_male_skirt + null + null + CIVMALE + move_chubby + move_ped_strafing + move_ped_to_strafe + move_strafe_injured + dam_ko + dam_ad + ANIM_GROUP_GESTURE_M_GENERIC + facial_clipset_group_gen_male + ANIM_GROUP_VISEMES_M_LO + CLIP_SET_ID_INVALID + Male + Male_prone + NMBS_SLOW_GETUPS + null + DEFAULT + STANDARD_PED + STANDARD_PED + STANDARD_MALE + + + + + + + + + + + + CIVMALE + STANDARD_PED + DEFAULT_PERCEPTION + BS_AI + WEAPON_UNARMED + CONSTRUCTION + DEFAULT + VFXPEDINFO_HUMAN_GENERIC + FLEE + RADIO_GENRE_MODERN_ROCK + RADIO_GENRE_MOTOWN + + + + + + + + + + SF_JEER_AT_HOT_PED + + + + G_M_Y_X17_AGuard_PVG + + SAT_NONE + TB_WARM + SLOD_HUMAN + SCENARIO_POP_STREAMING_NORMAL + DSP_NORMAL + + + + + MP_M_BogdanGoon + MP_M_BogdanGoon_p + move_m@generic + null + expr_set_ambient_male_skirt + null + null + CIVMALE + move_chubby + move_ped_strafing + move_ped_to_strafe + move_strafe_injured + dam_ko + dam_ad + ANIM_GROUP_GESTURE_M_GENERIC + facial_clipset_group_gen_male + ANIM_GROUP_VISEMES_M_LO + CLIP_SET_ID_INVALID + Male + Male_prone + NMBS_SLOW_GETUPS + null + DEFAULT + STANDARD_PED + STANDARD_PED + STANDARD_MALE + + + + + + + + + + + + CIVMALE + STANDARD_PED + DEFAULT_PERCEPTION + BS_AI + WEAPON_UNARMED + CONSTRUCTION + DEFAULT + VFXPEDINFO_HUMAN_GENERIC + FLEE + RADIO_GENRE_MODERN_ROCK + RADIO_GENRE_MOTOWN + + + + + + + + + + SF_JEER_AT_HOT_PED + + + + G_M_M_X17_RSO_PVG + + SAT_NONE + TB_WARM + SLOD_HUMAN + SCENARIO_POP_STREAMING_NORMAL + DSP_NORMAL + + + + + U_M_Y_Corpse_01 + null + move_m@generic + null + expr_set_ambient_male_skirt + null + null + CIVMALE + move_chubby + move_ped_strafing + move_ped_to_strafe + move_strafe_injured + dam_ko_@gangops@morgue@table@ + dam_ad + ANIM_GROUP_GESTURE_M_GENERIC + facial_clipset_group_gen_male + ANIM_GROUP_VISEMES_M_LO + CLIP_SET_ID_INVALID + Male + Male_prone + NMBS_SLOW_GETUPS + null + DEFAULT + STANDARD_PED + STANDARD_PED + STANDARD_MALE + + + + + + + + + + + + CIVMALE + STANDARD_PED + DEFAULT_PERCEPTION + BS_AI + WEAPON_UNARMED + CONSTRUCTION + DEFAULT + VFXPEDINFO_HUMAN_GENERIC + FLEE + RADIO_GENRE_MODERN_ROCK + RADIO_GENRE_MOTOWN + + + + + + + + + + SF_JEER_AT_HOT_PED + + + + SILENT_PVG + + SAT_NONE + TB_WARM + SLOD_HUMAN + SCENARIO_POP_STREAMING_NORMAL + DSP_NORMAL + + + + + + + + comp_peds_helmets_moped + + + + + + comp_peds_helmets_motox + + + + + + comp_peds_helmets_sports + + + + + + comp_peds_helmets_shorty + + + + + + strm_peds_mpTattRTs + + + + + + strm_peds_mpShare + + + + + + comp_peds_marine + + + + + + comp_peds_marine + + S_M_M_Marine_01 + S_M_Y_Marine_01 + S_M_Y_Marine_02 + S_M_Y_Marine_03 + + + + +"; + + + [Fact] + public void GetChildInnerTextShouldReturnInnerText() + { + var xdoc = new XmlDocument(); + xdoc.LoadXml(markup); + + XmlNodeList items = xdoc.SelectNodes("CVehicleModelInfo__InitDataList/InitDatas/Item | CVehicleModelInfo__InitDataList/InitDatas/item"); + + + string modelName = null; + string gameName = null; + VehicleInitData initDataExpected = null; + VehicleInitData initData = null; + + var InitDatas = new List(); + for (int i = 0; i < items.Count; i++) + { + var node = items[i]; + + initDataExpected = new VehicleInitData(); + initDataExpected.Load(node); + + modelName = Xml.GetChildInnerText(node, "modelName"); + Assert.Equal("brabusgt600", modelName); + + gameName = Xml.GetChildInnerText(node, "gameName"); + Assert.Equal("GT 600", gameName); + } + + Assert.NotNull(initDataExpected); + + Assert.Equal("brabusgt600", initDataExpected.modelName); + Assert.Equal("GT 600", initDataExpected.gameName); + Assert.Equal("brabusgt600", initDataExpected.txdName); + + //Assert.Null(initDataExpected.trailers); + Assert.Equal(9, initDataExpected.trailers.Length); + Assert.Single(initDataExpected.drivers); + Assert.Equal("S_M_Y_Cop_01", initDataExpected.drivers[0].driverName); + Assert.Equal("trailers4", initDataExpected.trailers[4]); + + using XmlReader xmlReader = XmlReader.Create(new StringReader(markup)); + + gameName = null; + modelName = null; + while (xmlReader.Read()) + { + if (xmlReader.NodeType == XmlNodeType.Element) + { + xmlReader.ReadToFollowing("InitDatas"); + if (xmlReader.Name == "InitDatas") + { + xmlReader.Read(); + xmlReader.MoveToContent(); + + initData = new VehicleInitData(); + initData.Load(xmlReader); + } + } + } + + Assert.NotNull(initData); + + Assert.NotNull(initData.pOverrideRagdollThreshold); + + Assert.Equivalent(initDataExpected, initData); + Assert.Equal(initDataExpected.GetHashCode(), initData.GetHashCode()); + + var bytes = Encoding.UTF8.GetBytes(markup); + + var vehiclesFileExpected = new VehiclesFile(); + var fileEntry = RpfFile.CreateFileEntry("kaas.meta", "saak.meta", ref bytes); + vehiclesFileExpected.LoadOld(bytes, fileEntry); + + var vehiclesFile = new VehiclesFile(); + vehiclesFile.Load(bytes, fileEntry); + + Assert.Equivalent(vehiclesFileExpected, vehiclesFile); + } + + [Fact] + public void IterateOverItemsShouldReturnOnlyItemsAndAdvanceXmlReaderPastEndElement() + { + string xml = @" + WHEEL_FRONT_RIGHT_CAMERA + WHEEL_FRONT_LEFT_CAMERA + WHEEL_REAR_RIGHT_CAMERA + WHEEL_REAR_LEFT_CAMERA + "; + + using var xmlReader = XmlReader.Create(new StringReader(xml)); + + foreach(var item in Xml.IterateItems(xmlReader, "cinematicPartCamera")) + { + Assert.NotNull(item); + output.WriteLine(item.ToString()); + } + } + + [Fact] + public void PedFileShouldBeSame() + { + var xmlText = TextUtil.GetUTF8Text(System.Text.Encoding.UTF8.GetBytes(pedsList)); + using XmlReader xmlReader = XmlReader.Create(new StringReader(xmlText)); + + var xDocument = new XmlDocument(); + xDocument.LoadXml(xmlText); + + var pedsFile = new CPedModelInfo__InitDataList(xmlReader); + var pedsFileExpected = new CPedModelInfo__InitDataList(xDocument.DocumentElement); + + for (int i = 0; i < pedsFileExpected.InitDatas.Length; i++) + { + Assert.Equivalent(pedsFileExpected.InitDatas[i], pedsFile.InitDatas[i]); + } + + for (int i = 0; i < pedsFileExpected.multiTxdRelationships.Length; i++) + { + Assert.Equivalent(pedsFileExpected.multiTxdRelationships[i], pedsFile.multiTxdRelationships[i]); + } + + Assert.Equivalent(pedsFileExpected, pedsFile); + } + } +} diff --git a/CodeWalker.WinForms/CodeWalker.WinForms.csproj b/CodeWalker.WinForms/CodeWalker.WinForms.csproj index 2d5623b..4aafc3b 100644 --- a/CodeWalker.WinForms/CodeWalker.WinForms.csproj +++ b/CodeWalker.WinForms/CodeWalker.WinForms.csproj @@ -1,16 +1,20 @@  - - x64 - - Library net48 true + true latest + + x64 + + + x64 + + diff --git a/CodeWalker.sln b/CodeWalker.sln index f176d21..5d453f4 100644 --- a/CodeWalker.sln +++ b/CodeWalker.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.29806.167 +# Visual Studio Version 17 +VisualStudioVersion = 17.7.34031.279 MinimumVisualStudioVersion = 10.0.40219.1 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "CodeWalker.Shaders", "CodeWalker.Shaders\CodeWalker.Shaders.vcxproj", "{0D14B076-0ABF-434E-AB9F-36E7800D8887}" EndProject @@ -23,6 +23,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CodeWalker.RPFExplorer", "C EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CodeWalker.Vehicles", "CodeWalker.Vehicles\CodeWalker.Vehicles.csproj", "{F5A776B0-2F1A-4C36-87B3-86206AC4B439}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CodeWalker.Test", "CodeWalker.Test\CodeWalker.Test.csproj", "{067FF87E-CA79-4B29-BCF6-11622999EDC6}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CodeWalker.Benchmarks", "CodeWalker.Benchmarks\CodeWalker.Benchmarks.csproj", "{EDDA8A8E-5333-4E28-8221-A31E3B70EB7A}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -128,6 +132,30 @@ Global {F5A776B0-2F1A-4C36-87B3-86206AC4B439}.Release|x64.Build.0 = Release|Any CPU {F5A776B0-2F1A-4C36-87B3-86206AC4B439}.Release|x86.ActiveCfg = Release|Any CPU {F5A776B0-2F1A-4C36-87B3-86206AC4B439}.Release|x86.Build.0 = Release|Any CPU + {067FF87E-CA79-4B29-BCF6-11622999EDC6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {067FF87E-CA79-4B29-BCF6-11622999EDC6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {067FF87E-CA79-4B29-BCF6-11622999EDC6}.Debug|x64.ActiveCfg = Debug|Any CPU + {067FF87E-CA79-4B29-BCF6-11622999EDC6}.Debug|x64.Build.0 = Debug|Any CPU + {067FF87E-CA79-4B29-BCF6-11622999EDC6}.Debug|x86.ActiveCfg = Debug|Any CPU + {067FF87E-CA79-4B29-BCF6-11622999EDC6}.Debug|x86.Build.0 = Debug|Any CPU + {067FF87E-CA79-4B29-BCF6-11622999EDC6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {067FF87E-CA79-4B29-BCF6-11622999EDC6}.Release|Any CPU.Build.0 = Release|Any CPU + {067FF87E-CA79-4B29-BCF6-11622999EDC6}.Release|x64.ActiveCfg = Release|Any CPU + {067FF87E-CA79-4B29-BCF6-11622999EDC6}.Release|x64.Build.0 = Release|Any CPU + {067FF87E-CA79-4B29-BCF6-11622999EDC6}.Release|x86.ActiveCfg = Release|Any CPU + {067FF87E-CA79-4B29-BCF6-11622999EDC6}.Release|x86.Build.0 = Release|Any CPU + {EDDA8A8E-5333-4E28-8221-A31E3B70EB7A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EDDA8A8E-5333-4E28-8221-A31E3B70EB7A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EDDA8A8E-5333-4E28-8221-A31E3B70EB7A}.Debug|x64.ActiveCfg = Debug|Any CPU + {EDDA8A8E-5333-4E28-8221-A31E3B70EB7A}.Debug|x64.Build.0 = Debug|Any CPU + {EDDA8A8E-5333-4E28-8221-A31E3B70EB7A}.Debug|x86.ActiveCfg = Debug|Any CPU + {EDDA8A8E-5333-4E28-8221-A31E3B70EB7A}.Debug|x86.Build.0 = Debug|Any CPU + {EDDA8A8E-5333-4E28-8221-A31E3B70EB7A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EDDA8A8E-5333-4E28-8221-A31E3B70EB7A}.Release|Any CPU.Build.0 = Release|Any CPU + {EDDA8A8E-5333-4E28-8221-A31E3B70EB7A}.Release|x64.ActiveCfg = Release|Any CPU + {EDDA8A8E-5333-4E28-8221-A31E3B70EB7A}.Release|x64.Build.0 = Release|Any CPU + {EDDA8A8E-5333-4E28-8221-A31E3B70EB7A}.Release|x86.ActiveCfg = Release|Any CPU + {EDDA8A8E-5333-4E28-8221-A31E3B70EB7A}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/CodeWalker/CodeWalker.csproj b/CodeWalker/CodeWalker.csproj index cca3d9b..8dc6ad6 100644 --- a/CodeWalker/CodeWalker.csproj +++ b/CodeWalker/CodeWalker.csproj @@ -9,17 +9,23 @@ dexyfex dexyfex software dexyfex - latest + latest x64 + + x64 + - + + + + diff --git a/CodeWalker/ExploreForm.Designer.cs b/CodeWalker/ExploreForm.Designer.cs index 155179a..1565d76 100644 --- a/CodeWalker/ExploreForm.Designer.cs +++ b/CodeWalker/ExploreForm.Designer.cs @@ -1,4 +1,7 @@ using CodeWalker.Core.Utils; +using System; + +using CodeWalker.WinForms; namespace CodeWalker { @@ -15,11 +18,13 @@ namespace CodeWalker /// true if managed resources should be disposed; otherwise, false. protected override void Dispose(bool disposing) { + using var _ = new DisposableTimer("ExploreForm Dipose"); + base.Dispose(disposing); + MainListView.Dispose(); if (disposing && (components != null)) { components.Dispose(); } - base.Dispose(disposing); } #region Windows Form Designer generated code @@ -121,6 +126,7 @@ namespace CodeWalker this.ListContextViewHexMenu = new System.Windows.Forms.ToolStripMenuItem(); this.toolStripSeparator2 = new System.Windows.Forms.ToolStripSeparator(); this.ListContextExportXmlMenu = new System.Windows.Forms.ToolStripMenuItem(); + this.ListContextExtractShadersMenu = new System.Windows.Forms.ToolStripMenuItem(); this.ListContextExtractRawMenu = new System.Windows.Forms.ToolStripMenuItem(); this.ListContextExtractUncompressedMenu = new System.Windows.Forms.ToolStripMenuItem(); this.ListContextExtractAllMenu = new System.Windows.Forms.ToolStripMenuItem(); @@ -990,6 +996,7 @@ namespace CodeWalker this.ListContextViewMenu, this.ListContextViewHexMenu, this.toolStripSeparator2, + this.ListContextExtractShadersMenu, this.ListContextExportXmlMenu, this.ListContextExtractRawMenu, this.ListContextExtractUncompressedMenu, @@ -1047,6 +1054,12 @@ namespace CodeWalker this.ListContextExportXmlMenu.Size = new System.Drawing.Size(208, 22); this.ListContextExportXmlMenu.Text = "Export XML..."; this.ListContextExportXmlMenu.Click += new System.EventHandler(this.ListContextExportXmlMenu_Click); + this.ListContextExtractShadersMenu.Image = ((System.Drawing.Image)(resources.GetObject("ListContextExportXmlMenu.Image"))); + this.ListContextExtractShadersMenu.Name = "ListContextExportShadersMenu"; + this.ListContextExtractShadersMenu.ShortcutKeyDisplayString = "Ctrl+S"; + this.ListContextExtractShadersMenu.Size = new System.Drawing.Size(208, 22); + this.ListContextExtractShadersMenu.Text = "Export Shaders..."; + this.ListContextExtractShadersMenu.Click += new System.EventHandler(this.ListContextExportShaders_Click); // // ListContextExtractRawMenu // @@ -1394,6 +1407,7 @@ namespace CodeWalker private System.Windows.Forms.ToolStripSeparator toolStripSeparator2; private System.Windows.Forms.ToolStripMenuItem ListContextExportXmlMenu; private System.Windows.Forms.ToolStripMenuItem ListContextExtractAllMenu; + private System.Windows.Forms.ToolStripMenuItem ListContextExtractShadersMenu; private System.Windows.Forms.ToolStripSeparator ListContextImportSeparator; private System.Windows.Forms.ToolStripMenuItem ListContextCopyPathMenu; private System.Windows.Forms.ToolStripSeparator ListContextEditSeparator; diff --git a/CodeWalker/ExploreForm.cs b/CodeWalker/ExploreForm.cs index aa738bd..c7c1c42 100644 --- a/CodeWalker/ExploreForm.cs +++ b/CodeWalker/ExploreForm.cs @@ -22,20 +22,27 @@ using System.Xml; using System.Xml.Linq; using WeifenLuo.WinFormsUI.Docking; using System.Runtime.CompilerServices; +using static CodeWalker.GameFiles.GameFileCache; + +using CodeWalker.WinForms; +using Newtonsoft.Json; namespace CodeWalker { public partial class ExploreForm : Form { private volatile bool Ready = false; + private bool IsInited = false; - private Dictionary FileTypes; - private readonly char[] InvalidFileNameChars = Path.GetInvalidFileNameChars(); + public static ExploreForm Instance; - private MainTreeFolder RootFolder; - private List ExtraRootFolders = new List(); - private MainTreeFolder CurrentFolder; - private List CurrentFiles; + private static Dictionary FileTypes; + private static readonly char[] InvalidFileNameChars = Path.GetInvalidFileNameChars(); + + public MainTreeFolder RootFolder; + public List ExtraRootFolders = new List(); + public MainTreeFolder CurrentFolder; + public List CurrentFiles; private bool FirstRefreshed = false; private List CopiedFiles = new List(); @@ -58,11 +65,18 @@ namespace CodeWalker public ThemeBase Theme { get; private set; } - public static CancellationTokenSource CancellationTokenSource = new CancellationTokenSource(); + public CancellationTokenSource CancellationTokenSource = new CancellationTokenSource(); + public static event Action ForceTreeRefresh; + + static ExploreForm() + { + InitFileTypes(); + } public ExploreForm() { + Instance = this; InitializeComponent(); SetTheme(Settings.Default.ExplorerWindowTheme, false); @@ -72,6 +86,8 @@ namespace CodeWalker LoadSettings(); UpdateStatus += UpdateStatus_EventHandler; + ErrorLog += UpdateErrorLog; + ForceTreeRefresh += RefreshMainTreeView_EventHandler; } private void SetTheme(string themestr, bool changing = true) @@ -181,8 +197,6 @@ namespace CodeWalker { //called from ExploreForm_Load - InitFileTypes(); - // This is probably not necessary now that the GTA folder is checked // in the Program.cs when the game is initiated, but we will leave it // here for now to make sure @@ -192,70 +206,88 @@ namespace CodeWalker return; } - FileCache = GameFileCacheFactory.Create(); + FileCache = GameFileCacheFactory.GetInstance(); new Task(async () => { - Thread.CurrentThread.Name = "FileCache ContentThread"; try { - GTA5Keys.LoadFromPath(GTAFolder.CurrentGTAFolder, Settings.Default.Key); - } - catch - { - UpdateStatus?.Invoke("Unable to load gta5.exe!"); - return; - } - - await RefreshMainTreeView(); - - UpdateStatus?.Invoke("Scan complete."); - - InitFileCache(); - - while (!IsDisposed && !CancellationTokenSource.IsCancellationRequested) //run the file cache content thread until the form exits. - { - if (FileCache.IsInited) + try { - FileCache.BeginFrame(); + GTA5Keys.LoadFromPath(GTAFolder.CurrentGTAFolder, Settings.Default.Key); + } + catch + { + UpdateStatus?.Invoke("Unable to load gta5.exe!"); + return; + } - bool fcItemsPending = FileCache.ContentThreadProc(); + await RefreshMainTreeView(); - if (!fcItemsPending) + UpdateStatus?.Invoke("Scan complete."); + + await InitFileCache(); + + + while (!IsDisposed && !CancellationTokenSource.IsCancellationRequested) //run the file cache content thread until the form exits. + { + if (FileCache.IsInited) { - Thread.Sleep(10); + FileCache.BeginFrame(); + + bool fcItemsPending = FileCache.ContentThreadProc(); + + if (!fcItemsPending) + { + await Task.Delay(10); + } + } + else + { + await Task.Delay(20); } } - else - { - Thread.Sleep(20); - } + } catch(Exception e) + { + Console.WriteLine(e); + throw; } }, CancellationTokenSource.Token, TaskCreationOptions.LongRunning).Start(TaskScheduler.Default); } - private void InitFileCache() + private ValueTask InitFileCache() { - Task.Run(() => + if (FileCache.IsInited) + { + return new ValueTask(); + } + return new ValueTask(Task.Run(() => { lock (FileCacheSyncRoot) { if (!FileCache.IsInited) { - UpdateStatus?.Invoke("Loading file cache..."); - var allRpfs = AllRpfs; - FileCache.Init(updateStatus: null, UpdateErrorLog, allRpfs); //inits main dicts and archetypes only... + try + { + UpdateStatus?.Invoke("Loading file cache..."); + var allRpfs = AllRpfs; + FileCache.Init(updateStatus: null, ErrorLog, allRpfs); //inits main dicts and archetypes only... - UpdateStatus?.Invoke("Loading materials..."); - BoundsMaterialTypes.Init(FileCache); + UpdateStatus?.Invoke("Loading materials..."); + BoundsMaterialTypes.Init(FileCache); - UpdateStatus?.Invoke("Loading scenario types..."); - Scenarios.EnsureScenarioTypes(FileCache); + UpdateStatus?.Invoke("Loading scenario types..."); + Scenarios.EnsureScenarioTypes(FileCache); + + UpdateStatus?.Invoke("File cache loaded."); + } catch(Exception ex) + { + MessageBox.Show(ex.ToString(), ex.Message); + } - UpdateStatus?.Invoke("File cache loaded."); } } - }, CancellationTokenSource.Token); + }, CancellationTokenSource.Token)); } public GameFileCache GetFileCache() { @@ -267,7 +299,7 @@ namespace CodeWalker return FileCache; //return it even though it's probably not inited yet.. } - private void InitFileTypes() + public static void InitFileTypes() { FileTypes = new Dictionary(StringComparer.OrdinalIgnoreCase); InitFileType(".rpf", "Rage Package File", 3); @@ -329,12 +361,12 @@ namespace CodeWalker InitSubFileType(".dat", "distantlights.dat", "Distant Lights", 6, FileTypeAction.ViewDistantLights); InitSubFileType(".dat", "distantlights_hd.dat", "Distant Lights", 6, FileTypeAction.ViewDistantLights); } - private void InitFileType(string ext, string name, int imgidx, FileTypeAction defaultAction = FileTypeAction.ViewHex, bool xmlConvertible = false) + private static void InitFileType(string ext, string name, int imgidx, FileTypeAction defaultAction = FileTypeAction.ViewHex, bool xmlConvertible = false) { var ft = new FileTypeInfo(ext, name, imgidx, defaultAction, xmlConvertible); FileTypes[ext] = ft; } - private void InitSubFileType(string ext, string subext, string name, int imgidx, FileTypeAction defaultAction = FileTypeAction.ViewHex, bool xmlConvertible = false) + private static void InitSubFileType(string ext, string subext, string name, int imgidx, FileTypeAction defaultAction = FileTypeAction.ViewHex, bool xmlConvertible = false) { FileTypeInfo pti = null; if (FileTypes.TryGetValue(ext, out pti)) @@ -343,7 +375,7 @@ namespace CodeWalker pti.AddSubType(ft); } } - public FileTypeInfo GetFileType(string fn) + public static FileTypeInfo GetFileType(string fn) { if (fn.IndexOfAny(InvalidFileNameChars) != -1) { @@ -382,6 +414,7 @@ namespace CodeWalker } public event Action UpdateStatus; + public event Action ErrorLog; public void InvokeUpdateStatus(string msg) { @@ -394,7 +427,7 @@ namespace CodeWalker { if (InvokeRequired) { - BeginInvoke(new Action(() => { UpdateStatus(text); })); + BeginInvoke(new Action(() => { UpdateStatus_EventHandler(text); })); } else { @@ -705,6 +738,7 @@ namespace CodeWalker private async Task RefreshMainTreeView() { + using var _ = new DisposableTimer("RefreshMainTreeView"); Ready = false; AllRpfs = null; @@ -755,7 +789,7 @@ namespace CodeWalker var nodes = await Task.WhenAll(tasks); - Invoke(() => + BeginInvoke(() => { MainTreeView.BeginUpdate(); ClearMainTreeView(); @@ -787,7 +821,7 @@ namespace CodeWalker //var allpaths = Directory.EnumerateFileSystemEntries(fullPath, "*", SearchOption.AllDirectories); //var allDirs = new HashSet(Directory.EnumerateDirectories(fullPath, "*", SearchOption.AllDirectories)); //var allfiles = new HashSet(Directory.GetFiles(fullPath, "*", SearchOption.AllDirectories)); - var nodes = new ConcurrentDictionary(); + var nodes = new ConcurrentDictionary(4, allpaths.Length, StringComparer.OrdinalIgnoreCase); var partitioner = Partitioner.Create(allpaths, EnumerablePartitionerOptions.NoBuffering); @@ -812,13 +846,19 @@ namespace CodeWalker var parentidx = parentpath.LastIndexOf('\\'); var parentname = parentpath.Substring(parentidx + 1); var exists = true; + MainTreeFolder _node = null; node = nodes.GetOrAdd(parentpath, (key) => { //Console.WriteLine($"Creating treenode for {parentpath}"); var node = CreateRootDirTreeFolder(parentname, subPath + parentpath, fullPath + parentpath); + _node = node; exists = false; return node; }); + if (_node != null && node != _node) + { + exists = true; + } parentnode ??= node; if (prevnode != null) { @@ -831,17 +871,21 @@ namespace CodeWalker if (exists) break; if (idx < 0) { - f.AddChild(node); + lock (f) + { + f.AddChild(node); + } + } } - if (isFile) + if (fileEntryInfo is FileInfo fileInfo) { if (path.EndsWith(".rpf", StringComparison.OrdinalIgnoreCase)) //add RPF nodes { RpfFile rpf = new RpfFile(path, relpath); - rpf.ScanStructure(updateStatus: null, UpdateErrorLog); + rpf.ScanStructure(updateStatus: null, ErrorLog); if (rpf.LastException != null) //incase of corrupted rpf (or renamed NG encrypted RPF) { @@ -863,7 +907,7 @@ namespace CodeWalker } else { - JenkIndex.Ensure(Path.GetFileNameWithoutExtension(path)); + JenkIndex.EnsureLower(Path.GetFileNameWithoutExtension(path)); if (parentnode != null) { parentnode.AddFile(path); @@ -877,7 +921,7 @@ namespace CodeWalker } catch (Exception ex) { Console.WriteLine(ex); - UpdateErrorLog(ex.ToString()); + ErrorLog?.Invoke(ex.ToString()); } }); @@ -1239,7 +1283,7 @@ namespace CodeWalker } } - public void RefreshMainListViewInvoke() + private void RefreshMainTreeView_EventHandler() { if (InvokeRequired) { @@ -1250,6 +1294,11 @@ namespace CodeWalker RefreshMainListView(); } } + + public static void RefreshMainListViewInvoke() + { + ForceTreeRefresh?.Invoke(); + } private void RefreshMainListView() { MainListView.VirtualListSize = 0; @@ -1261,10 +1310,10 @@ namespace CodeWalker } CurrentFiles = CurrentFolder.GetListItems(); - foreach (var file in CurrentFiles) //cache all the data for use by the list view. - { - file.CacheDetails(this); - } + //foreach (var file in CurrentFiles) //cache all the data for use by the list view. + //{ + // file.CacheDetails(); + //} SortMainListView(SortColumnIndex, SortDirection); //sorts CurrentItems and sets VirtualListSize @@ -1348,21 +1397,26 @@ namespace CodeWalker //Task.Run(() => //{ - Searching = true; + Searching = true; Cursor = Cursors.WaitCursor; - var resultcount = RootFolder.Search(term, this); + var resultcount = RootFolder.Search(term, this); - if (Searching) - { - Searching = false; - UpdateStatus?.Invoke("Search complete. " + resultcount.ToString() + " items found."); - } - else - { - UpdateStatus?.Invoke("Search aborted. " + resultcount.ToString() + " items found."); - } + foreach(var extraFolder in ExtraRootFolders) + { + resultcount += extraFolder.Search(term, this); + } + + if (Searching) + { + Searching = false; + UpdateStatus?.Invoke("Search complete. " + resultcount.ToString() + " items found."); + } + else + { + UpdateStatus?.Invoke("Search aborted. " + resultcount.ToString() + " items found."); + } Cursor = Cursors.Default; @@ -1389,7 +1443,6 @@ namespace CodeWalker if (item != null) { - item.CacheDetails(this); CurrentFiles.Add(item); } else @@ -1505,8 +1558,7 @@ namespace CodeWalker return item.FileType.XmlConvertible; } - - public void View(MainListItem item) + public static void View(MainListItem item) { #if !DEBUG try @@ -1640,7 +1692,7 @@ namespace CodeWalker #if !DEBUG catch (Exception ex) { - UpdateErrorLog(ex.ToString()); + Instance?.UpdateErrorLog(ex.ToString()); return; } #endif @@ -1674,205 +1726,205 @@ namespace CodeWalker } catch (Exception ex) { - UpdateErrorLog(ex.ToString()); + ErrorLog?.Invoke(ex.ToString()); return; } } - private void ViewHex(string name, string path, byte[] data) + private static void ViewHex(string name, string path, byte[] data) { HexForm f = new HexForm(); f.Show(); f.LoadData(name, path, data); } - private void ViewXml(string name, string path, byte[] data, RpfFileEntry e) + private static void ViewXml(string name, string path, byte[] data, RpfFileEntry e) { string xml = Encoding.UTF8.GetString(data); - XmlForm f = new XmlForm(this); + XmlForm f = new XmlForm(); f.Show(); f.LoadXml(name, path, xml, e); } - private void ViewText(string name, string path, byte[] data, RpfFileEntry e) + private static void ViewText(string name, string path, byte[] data, RpfFileEntry e) { string txt = Encoding.UTF8.GetString(data); - TextForm f = new TextForm(this); + TextForm f = new TextForm(); f.Show(); f.LoadText(name, path, txt, e); } - private void ViewYtd(string name, string path, byte[] data, RpfFileEntry e) + private static void ViewYtd(string name, string path, byte[] data, RpfFileEntry e) { var ytd = RpfFile.GetFile(e, data); - YtdForm f = new YtdForm(this); + YtdForm f = new YtdForm(); f.Show(); f.LoadYtd(ytd); } - private void ViewYmt(string name, string path, byte[] data, RpfFileEntry e) + private static void ViewYmt(string name, string path, byte[] data, RpfFileEntry e) { var ymt = RpfFile.GetFile(e, data); - MetaForm f = new MetaForm(this); + MetaForm f = new MetaForm(); f.Show(); f.LoadMeta(ymt); } - private void ViewYmf(string name, string path, byte[] data, RpfFileEntry e) + private static void ViewYmf(string name, string path, byte[] data, RpfFileEntry e) { var ymf = RpfFile.GetFile(e, data); - MetaForm f = new MetaForm(this); + MetaForm f = new MetaForm(); f.Show(); f.LoadMeta(ymf); } - private void ViewYmap(string name, string path, byte[] data, RpfFileEntry e) + private static void ViewYmap(string name, string path, byte[] data, RpfFileEntry e) { var ymap = RpfFile.GetFile(e, data); - MetaForm f = new MetaForm(this); + MetaForm f = new MetaForm(); f.Show(); f.LoadMeta(ymap); } - private void ViewYtyp(string name, string path, byte[] data, RpfFileEntry e) + private static void ViewYtyp(string name, string path, byte[] data, RpfFileEntry e) { var ytyp = RpfFile.GetFile(e, data); - MetaForm f = new MetaForm(this); + MetaForm f = new MetaForm(); f.Show(); f.LoadMeta(ytyp); } - private void ViewJPso(string name, string path, byte[] data, RpfFileEntry e) + private static void ViewJPso(string name, string path, byte[] data, RpfFileEntry e) { var pso = RpfFile.GetFile(e, data); - MetaForm f = new MetaForm(this); + MetaForm f = new MetaForm(); f.Show(); f.LoadMeta(pso); } - private void ViewModel(string name, string path, byte[] data, RpfFileEntry e) + private static void ViewModel(string name, string path, byte[] data, RpfFileEntry e) { - ModelForm f = new ModelForm(this); + ModelForm f = new ModelForm(); f.ViewModel(data, e); } - private void ViewCut(string name, string path, byte[] data, RpfFileEntry e) + private static void ViewCut(string name, string path, byte[] data, RpfFileEntry e) { var cut = RpfFile.GetFile(e, data); - MetaForm f = new MetaForm(this); + MetaForm f = new MetaForm(); f.Show(); f.LoadMeta(cut); } - private void ViewAwc(string name, string path, byte[] data, RpfFileEntry e) + private static void ViewAwc(string name, string path, byte[] data, RpfFileEntry e) { var awc = RpfFile.GetFile(e, data); AwcForm f = new AwcForm(); f.Show(); f.LoadAwc(awc); } - private void ViewGxt(string name, string path, byte[] data, RpfFileEntry e) + private static void ViewGxt(string name, string path, byte[] data, RpfFileEntry e) { var gxt = RpfFile.GetFile(e, data); - TextForm f = new TextForm(this); + TextForm f = new TextForm(); f.Show(); f.LoadGxt2(name, path, gxt); } - private void ViewRel(string name, string path, byte[] data, RpfFileEntry e) + private static void ViewRel(string name, string path, byte[] data, RpfFileEntry e) { var rel = RpfFile.GetFile(e, data); - RelForm f = new RelForm(this); + RelForm f = new RelForm(); f.Show(); f.LoadRel(rel); } - private void ViewFxc(string name, string path, byte[] data, RpfFileEntry e) + private static void ViewFxc(string name, string path, byte[] data, RpfFileEntry e) { var fxc = RpfFile.GetFile(e, data); FxcForm f = new FxcForm(); f.Show(); f.LoadFxc(fxc); } - private void ViewYwr(string name, string path, byte[] data, RpfFileEntry e) + private static void ViewYwr(string name, string path, byte[] data, RpfFileEntry e) { var ywr = RpfFile.GetFile(e, data); YwrForm f = new YwrForm(); f.Show(); f.LoadYwr(ywr); } - private void ViewYvr(string name, string path, byte[] data, RpfFileEntry e) + private static void ViewYvr(string name, string path, byte[] data, RpfFileEntry e) { var yvr = RpfFile.GetFile(e, data); YvrForm f = new YvrForm(); f.Show(); f.LoadYvr(yvr); } - private void ViewYcd(string name, string path, byte[] data, RpfFileEntry e) + private static void ViewYcd(string name, string path, byte[] data, RpfFileEntry e) { var ycd = RpfFile.GetFile(e, data); YcdForm f = new YcdForm(); f.Show(); f.LoadYcd(ycd); } - private void ViewYnd(string name, string path, byte[] data, RpfFileEntry e) + private static void ViewYnd(string name, string path, byte[] data, RpfFileEntry e) { var ynd = RpfFile.GetFile(e, data); - MetaForm f = new MetaForm(this); + MetaForm f = new MetaForm(); f.Show(); f.LoadMeta(ynd); } - private void ViewYed(string name, string path, byte[] data, RpfFileEntry e) + private static void ViewYed(string name, string path, byte[] data, RpfFileEntry e) { var yed = RpfFile.GetFile(e, data); - MetaForm f = new MetaForm(this); + MetaForm f = new MetaForm(); f.Show(); f.LoadMeta(yed); } - private void ViewYld(string name, string path, byte[] data, RpfFileEntry e) + private static void ViewYld(string name, string path, byte[] data, RpfFileEntry e) { var yld = RpfFile.GetFile(e, data); - MetaForm f = new MetaForm(this); + MetaForm f = new MetaForm(); f.Show(); f.LoadMeta(yld); } - private void ViewYfd(string name, string path, byte[] data, RpfFileEntry e) + private static void ViewYfd(string name, string path, byte[] data, RpfFileEntry e) { var yfd = RpfFile.GetFile(e, data); - MetaForm f = new MetaForm(this); + MetaForm f = new MetaForm(); f.Show(); f.LoadMeta(yfd); } - private void ViewCacheDat(string name, string path, byte[] data, RpfFileEntry e) + private static void ViewCacheDat(string name, string path, byte[] data, RpfFileEntry e) { var cachedat = RpfFile.GetFile(e, data); - MetaForm f = new MetaForm(this); + MetaForm f = new MetaForm(); f.Show(); f.LoadMeta(cachedat); } - private void ViewHeightmap(string name, string path, byte[] data, RpfFileEntry e) + private static void ViewHeightmap(string name, string path, byte[] data, RpfFileEntry e) { var heightmap = RpfFile.GetFile(e, data); - MetaForm f = new MetaForm(this); + MetaForm f = new MetaForm(); f.Show(); f.LoadMeta(heightmap); } - private void ViewMrf(string name, string path, byte[] data, RpfFileEntry e) + private static void ViewMrf(string name, string path, byte[] data, RpfFileEntry e) { var mrf = RpfFile.GetFile(e, data); - MetaForm f = new MetaForm(this); + MetaForm f = new MetaForm(); f.Show(); f.LoadMeta(mrf); } - private void ViewNametable(string name, string path, byte[] data, RpfFileEntry e) + private static void ViewNametable(string name, string path, byte[] data, RpfFileEntry e) { - TextForm f = new TextForm(this); + TextForm f = new TextForm(); f.Show(); f.LoadNametable(name, path, data, e); } - private void ViewDistantLights(string name, string path, byte[] data, RpfFileEntry e) + private static void ViewDistantLights(string name, string path, byte[] data, RpfFileEntry e) { var dlf = RpfFile.GetFile(e, data); - GenericForm f = new GenericForm(this); + GenericForm f = new GenericForm(); f.Show(); f.LoadFile(dlf, dlf.RpfFileEntry); } - private void ViewYpdb(string name, string path, byte[] data, RpfFileEntry e) + private static void ViewYpdb(string name, string path, byte[] data, RpfFileEntry e) { var ypdb = RpfFile.GetFile(e, data); - MetaForm f = new MetaForm(this); + MetaForm f = new MetaForm(); f.Show(); f.LoadMeta(ypdb); } - private Form FindExistingForm(RpfFileEntry e) + public static Form FindExistingForm(RpfFileEntry e) { if (e == null) return null; var allforms = Application.OpenForms; @@ -2045,13 +2097,9 @@ namespace CodeWalker - public bool EnsureRpfValidEncryption(RpfFile file = null) + public static bool EnsureRpfValidEncryption(RpfFile file) { - if ((file == null) && (CurrentFolder.RpfFolder == null)) return false; - - var rpf = file ?? CurrentFolder.RpfFolder.File; - - if (rpf == null) return false; + if (file == null) return false; var confirm = new Func((f) => { @@ -2059,7 +2107,7 @@ namespace CodeWalker return (MessageBox.Show(msg, "Change RPF encryption type", MessageBoxButtons.YesNo) == DialogResult.Yes); }); - return RpfFile.EnsureValidEncryption(rpf, confirm); + return RpfFile.EnsureValidEncryption(file, confirm); } @@ -2125,6 +2173,319 @@ namespace CodeWalker } } } + + private class TextureUsageData + { + public HashSet Models { get; set; } = new HashSet(); + public HashSet ContainedIn { get; set; } = new HashSet(); + public int Count { get; set; } = 0; + public string TextureName { get; set; } + } + + private void GetShadersXml() + { + bool doydr = true; + bool doydd = true; + bool doyft = true; + bool doypt = true; + + var data = new Dictionary(); + var textures = new Dictionary(); + + void collectDrawable(DrawableBase d) + { + if (d?.AllModels == null) return; + foreach (var model in d.AllModels) + { + if (model?.Geometries == null) continue; + foreach (var geom in model.Geometries) + { + var s = geom?.Shader; + if (s == null) continue; + ShaderXmlDataCollection dc = null; + if (!data.TryGetValue(s.Name, out dc)) + { + dc = new ShaderXmlDataCollection(); + dc.Name = s.Name; + data.Add(s.Name, dc); + } + dc.AddShaderUse(s, geom, d); + } + } + } + + void collectTextures(DrawableBase d) + { + collectTexturesFromDict(d?.ShaderGroup?.TextureDictionary, (d.Owner as GameFile)?.Name ?? ""); + if (d?.AllModels == null) return; + foreach (var model in d.AllModels) + { + if (model?.Geometries == null) continue; + foreach (var geom in model.Geometries) + { + var s = geom?.Shader; + if (s == null) continue; + addTextureuse(s, geom, d); + } + } + } + + void collectTexturesFromDict(TextureDictionary textureDictionary, string ownerName) + { + if (textureDictionary == null) return; + foreach(var texture in textureDictionary.Dict) + { + if (!textures.TryGetValue(texture.Value.Name, out var tex)) + { + tex = new TextureUsageData() + { + TextureName = texture.Value.Name + }; + textures[texture.Value.Name] = tex; + } + + tex.ContainedIn.Add(ownerName); + } + } + + void addTextureuse(ShaderFX s, DrawableGeometry g, DrawableBase d = null) + { + if (s.ParametersList?.Parameters == null) return; + if (s.ParametersList?.Hashes == null) return; + + for (int i = 0; i < s.ParametersList.Count; i++) + { + var h = s.ParametersList.Hashes[i]; + var p = s.ParametersList.Parameters[i]; + + if (p.DataType == 0 && d.Owner is GameFile gameFile)//texture + { + if (p.Data is TextureBase texture) + { + if (!textures.TryGetValue(texture.Name, out var tex)) + { + tex = new TextureUsageData() + { + TextureName = texture.Name + }; + textures[texture.Name] = tex; + } + + tex.Count++; + tex.Models.Add($"{gameFile.Name}"); + } + + } + } + } + + bool isModelFile(MainListItem item) + { + return item.FullPath.EndsWithAny(".ydr", ".ydd", ".yft", ".ypt", ".ytd"); + } + + var queue = new ConcurrentQueue(); + + var file = CurrentFolder.RpfFile; + + queue.Enqueue(CurrentFolder); + + while (queue.TryDequeue(out var folder)) + { + foreach (var item in folder.GetListItems()) + { + try + { + if (item.Folder != null) + { + queue.Enqueue(item.Folder); + continue; + } + if (!isModelFile(item)) + { + continue; + } + + byte[] fileData = null; + string name = ""; + string path = ""; + + if (item.File != null) + { + if (item.File.File == null) continue; + fileData = item.File.File.ExtractFile(item.File); + name = item.Name; + path = item.FullPath; + } + else if (!string.IsNullOrEmpty(item.FullPath)) + { + //load file from filesystem + fileData = File.ReadAllBytes(item.FullPath); + name = new FileInfo(item.FullPath).Name; + path = item.FullPath; + } + var entry = item.File; + if (entry == null) + { + entry = RpfFile.CreateFileEntry(name, path, ref fileData); + } + if (doydr && entry.IsExtension(".ydr")) + { + UpdateStatus?.Invoke(entry.Path); + YdrFile ydr = RpfFile.GetFile(entry, fileData); + + if (ydr == null) { continue; } + if (ydr.Drawable == null) { continue; } + collectDrawable(ydr.Drawable); + collectTextures(ydr.Drawable); + } + else if (doydd & entry.IsExtension(".ydd")) + { + UpdateStatus?.Invoke(entry.Path); + YddFile ydd = RpfFile.GetFile(entry, fileData); + + if (ydd == null) { continue; } + if (ydd.Dict == null) { continue; } + foreach (var drawable in ydd.Dict.Values) + { + collectDrawable(drawable); + collectTextures(drawable); + } + } + else if (doyft && entry.IsExtension(".yft")) + { + UpdateStatus?.Invoke(entry.Path); + YftFile yft = RpfFile.GetFile(entry, fileData); + + if (yft == null) { continue; } + if (yft.Fragment == null) { continue; } + if (yft.Fragment.Drawable != null) + { + collectDrawable(yft.Fragment.Drawable); + collectTextures(yft.Fragment.Drawable); + } + if ((yft.Fragment.Cloths != null) && (yft.Fragment.Cloths.data_items != null)) + { + foreach (var cloth in yft.Fragment.Cloths.data_items) + { + collectDrawable(cloth.Drawable); + collectTextures(cloth.Drawable); + } + } + if ((yft.Fragment.DrawableArray != null) && (yft.Fragment.DrawableArray.data_items != null)) + { + foreach (var drawable in yft.Fragment.DrawableArray.data_items) + { + collectDrawable(drawable); + collectTextures(drawable); + } + } + } + else if (doypt && entry.IsExtension(".ypt")) + { + UpdateStatus?.Invoke(entry.Path); + YptFile ypt = RpfFile.GetFile(entry, fileData); + + if (ypt == null) { continue; } + if (ypt.DrawableDict == null) { continue; } + foreach (var drawable in ypt.DrawableDict.Values) + { + collectDrawable(drawable); + collectTextures(drawable); + } + } else if (entry.IsExtension(".ytd")) + { + UpdateStatus?.Invoke(entry.Path); + YtdFile ytd = RpfFile.GetFile(entry, fileData); + var textureName = ytd.Name; + if (string.IsNullOrEmpty(name)) + { + textureName = entry.Name; + } + + collectTexturesFromDict(ytd.TextureDict, textureName); + } + } + catch (Exception ex) + { + Console.WriteLine(ex); + } + } + } + + + + + File.WriteAllText(Path.Combine(CurrentFolder.FullPath, "texture_usage.json"), JsonConvert.SerializeObject(textures.Values.OrderBy(p => p.Count), new JsonSerializerSettings { Formatting = Newtonsoft.Json.Formatting.Indented })); + + var shaders = data.Values.ToList(); + shaders.Sort((a, b) => { return b.GeomCount.CompareTo(a.GeomCount); }); + + + StringBuilder sb = new StringBuilder(); + + sb.AppendLine(MetaXml.XmlHeader); + MetaXml.OpenTag(sb, 0, "Shaders"); + foreach (var s in shaders) + { + MetaXml.OpenTag(sb, 1, "Item"); + MetaXml.StringTag(sb, 2, "Name", MetaXml.HashString(s.Name)); + MetaXml.WriteHashItemArray(sb, s.GetSortedList(s.FileNames).ToArray(), 2, "FileName"); + MetaXml.WriteRawArray(sb, s.GetSortedList(s.RenderBuckets).ToArray(), 2, "RenderBucket", ""); + MetaXml.OpenTag(sb, 2, "Layout"); + var layouts = s.GetSortedList(s.VertexLayouts); + foreach (var l in layouts) + { + var vd = new VertexDeclaration(); + vd.Types = l.Types; + vd.Flags = l.Flags; + vd.WriteXml(sb, 3, "Item"); + } + MetaXml.CloseTag(sb, 2, "Layout"); + MetaXml.OpenTag(sb, 2, "Parameters"); + var otstr = "Item name=\"{0}\" type=\"{1}\""; + var texparams = s.GetSortedList(s.TextureData); + var valparams = s.ValParams; + var arrparams = s.ArrParams; + foreach (var tp in texparams) + { + //MetaXml.WriteCustomItemArray(sb, tp.Value.Textures, 3, "Texture"); + MetaXml.OpenTag(sb, 3, string.Format(otstr, ((ShaderParamNames)tp.Key).ToString(), "Texture")); + + foreach(var texture in tp.Value.Textures) + { + sb.AppendLine(texture); + } + + MetaXml.CloseTag(sb, 3, "Item"); + } + foreach (var vp in valparams) + { + var svp = s.GetSortedList(vp.Value); + var defval = svp.FirstOrDefault(); + MetaXml.SelfClosingTag(sb, 3, string.Format(otstr, ((ShaderParamNames)vp.Key).ToString(), "Vector") + " " + FloatUtil.GetVector4XmlString(defval)); + } + foreach (var ap in arrparams) + { + var defval = ap.Value.FirstOrDefault(); + MetaXml.OpenTag(sb, 3, string.Format(otstr, ((ShaderParamNames)ap.Key).ToString(), "Array")); + foreach (var vec in defval) + { + MetaXml.SelfClosingTag(sb, 4, "Value " + FloatUtil.GetVector4XmlString(vec)); + } + MetaXml.CloseTag(sb, 3, "Item"); + } + MetaXml.CloseTag(sb, 2, "Parameters"); + MetaXml.CloseTag(sb, 1, "Item"); + } + MetaXml.CloseTag(sb, 0, "Shaders"); + + var xml = sb.ToString(); + + + File.WriteAllText(Path.Combine(CurrentFolder.FullPath, "shaders.xml"), xml); + } + private void ExportXml() { bool needfolder = false;//need a folder to output ytd XML to, for the texture .dds files @@ -2245,7 +2606,7 @@ namespace CodeWalker try { File.WriteAllText(path, xml); - if (CurrentFolder.FullPath.Equals(Path.GetDirectoryName(path), StringComparison.OrdinalIgnoreCase)) + if (CurrentFolder?.FullPath?.Equals(Path.GetDirectoryName(path), StringComparison.OrdinalIgnoreCase) ?? false) { CurrentFolder.EnsureFile(path); refreshNeeded = true; @@ -2482,7 +2843,7 @@ namespace CodeWalker { if (CurrentFolder.RpfFolder != null) { - if (!EnsureRpfValidEncryption()) return; + if (!EnsureRpfValidEncryption(CurrentFolder.RpfFolder.File)) return; //create new directory entry in the RPF. @@ -2567,7 +2928,7 @@ namespace CodeWalker { if (CurrentFolder.RpfFolder != null) { - if (!EnsureRpfValidEncryption()) return; + if (!EnsureRpfValidEncryption(CurrentFolder.RpfFolder.File)) return; //adding a new RPF as a child of another newrpf = RpfFile.CreateNew(CurrentFolder.RpfFolder, fname, encryption); @@ -2636,7 +2997,7 @@ namespace CodeWalker if (!EnsureCurrentFolderEditable()) return; - if (!EnsureRpfValidEncryption() && (CurrentFolder.RpfFolder != null)) return; + if (!EnsureRpfValidEncryption(CurrentFolder.RpfFolder.File) && (CurrentFolder.RpfFolder != null)) return; OpenFileDialog.Filter = "FBX Files|*.fbx"; @@ -2731,7 +3092,7 @@ namespace CodeWalker if (!EnsureCurrentFolderEditable()) return; - if (!EnsureRpfValidEncryption() && (CurrentFolder.RpfFolder != null)) return; + if ((CurrentFolder.RpfFolder != null) && !EnsureRpfValidEncryption(CurrentFolder.RpfFolder?.File)) return; if (MainListView.SelectedIndices.Count > 0) { @@ -2826,7 +3187,7 @@ namespace CodeWalker if (!EditMode) return; if (CurrentFolder?.IsSearchResults ?? false) return; - if (!EnsureRpfValidEncryption() && (CurrentFolder.RpfFolder != null)) return; + if (!EnsureRpfValidEncryption(CurrentFolder.RpfFolder.File) && (CurrentFolder.RpfFolder != null)) return; OpenFileDialog.Filter = string.Empty; if (OpenFileDialog.ShowDialog(this) != DialogResult.OK) @@ -2847,7 +3208,7 @@ namespace CodeWalker if (checkEncryption) { - if (!EnsureRpfValidEncryption() && (CurrentFolder.RpfFolder != null)) return; + if (!EnsureRpfValidEncryption(CurrentFolder.RpfFolder.File) && (CurrentFolder.RpfFolder != null)) return; } var filelist = new List(); @@ -3069,7 +3430,7 @@ namespace CodeWalker } if (entry != null) { - if (!EnsureRpfValidEncryption()) return; + if (!EnsureRpfValidEncryption(CurrentFolder.RpfFolder.File)) return; //renaming an entry in an RPF RpfFile.RenameEntry(entry, newname); @@ -3167,7 +3528,7 @@ namespace CodeWalker if (parent.RpfFolder != null) { //delete an item in an RPF. - if (!EnsureRpfValidEncryption()) return; + if (!EnsureRpfValidEncryption(CurrentFolder.RpfFolder.File)) return; RpfEntry entry = item.GetRpfEntry(); @@ -3296,7 +3657,7 @@ namespace CodeWalker } catch (Exception ex) { - UpdateErrorLog(ex.ToString()); + ErrorLog?.Invoke(ex.ToString()); } } private void OpenFileLocation() @@ -3364,7 +3725,7 @@ namespace CodeWalker if (!EnsureCurrentFolderEditable()) return; - if (!EnsureRpfValidEncryption() && (CurrentFolder.RpfFolder != null)) return; + if (!EnsureRpfValidEncryption(CurrentFolder.RpfFolder.File) && (CurrentFolder.RpfFolder != null)) return; foreach (var file in CopiedFiles) { @@ -3486,12 +3847,13 @@ namespace CodeWalker GoForward(); //APPCOMMAND_BROWSER_FORWARD } } - else if (m.Msg == 0x10) // WM_CLOSE + base.WndProc(ref m); + + if (m.Msg == 0x10) // WM_CLOSE { ConsoleWindow.Close(); CancellationTokenSource.Cancel(); } - base.WndProc(ref m); } @@ -3502,6 +3864,7 @@ namespace CodeWalker private void ExploreForm_FormClosed(object sender, FormClosedEventArgs e) { + Console.WriteLine($"Form is closing! {DateTime.Now}"); CleanupDropFolder(); SaveSettings(); } @@ -3985,6 +4348,11 @@ namespace CodeWalker ViewSelectedHex(); } + private void ListContextExportShaders_Click(object sender, EventArgs e) + { + GetShadersXml(); + } + private void ListContextExportXmlMenu_Click(object sender, EventArgs e) { ExportXml(); @@ -4282,7 +4650,7 @@ namespace CodeWalker public string FullPath { get; set; } public RpfFile RpfFile { get; set; } public RpfDirectoryEntry RpfFolder { get; set; } - public List Files { get; set; } + public HashSet Files { get; set; } private object filesLock = new object(); public MainTreeFolder Parent { get; set; } public List Children { get; set; } @@ -4297,7 +4665,10 @@ namespace CodeWalker { lock (filesLock) { - if (Files == null) Files = new (); + if (Files == null) + { + Files = new(); + } Files.Add(file); } } @@ -4323,7 +4694,7 @@ namespace CodeWalker public void AddChildToHierarchy(MainTreeFolder child) { - var relpath = child.Path.Replace(Path + "\\", ""); + var relpath = child.Path.Replace(Path + '\\', ""); var idx = relpath.IndexOf('\\'); var lidx = 0; var parent = this; @@ -4333,7 +4704,7 @@ namespace CodeWalker if (parent.Children == null) break; foreach (var tc in parent.Children) { - if (tc.Name.Equals(pname, StringComparison.InvariantCultureIgnoreCase)) + if (tc.Name.Equals(pname, StringComparison.OrdinalIgnoreCase)) { parent = tc; break; @@ -4390,7 +4761,8 @@ namespace CodeWalker { foreach (var file in RpfFolder.Files) { - if (file.Name.EndsWith(".rpf", StringComparison.OrdinalIgnoreCase)) continue; //RPF files are already added.. + if (file.IsExtension(".rpf")) + continue; //RPF files are already added.. ListItems.Add(new MainListItem(file, rootpath, this)); } } @@ -4406,7 +4778,8 @@ namespace CodeWalker { foreach (var file in RpfFolder.Files) { - if (file.Name.EndsWith(".rpf", StringComparison.OrdinalIgnoreCase)) continue; //RPF files are already added.. + if (file.IsExtension(".rpf")) + continue; //RPF files are already added.. ic++; } } @@ -4452,7 +4825,8 @@ namespace CodeWalker foreach (var file in RpfFolder.Files) { //if (!form.Searching) return resultcount; - if (file.Name.EndsWith(".rpf", StringComparison.OrdinalIgnoreCase)) continue; //don't search rpf files.. + if (file.IsExtension(".rpf")) + continue; //don't search rpf files.. if (file.Name.Contains(term, StringComparison.OrdinalIgnoreCase)) { form.AddSearchResult(new MainListItem(file, rootpath, this)); @@ -4470,6 +4844,16 @@ namespace CodeWalker } } + if (this == form.RootFolder) + { + var texDict = form.GetFileCache().TryGetTextureDictForTexture(JenkHash.GenHashLower(term)) ?? form.GetFileCache().TryGetTextureDictForTexture(JenkHash.GenHash(term)); + if (texDict != null) + { + form.AddSearchResult(new MainListItem(texDict.RpfFileEntry, rootpath, this)); + resultcount++; + } + } + form.AddSearchResult(null); return resultcount; @@ -4494,18 +4878,87 @@ namespace CodeWalker public class MainListItem { + private string path; + private string attributes; + private long fileSize = -1l; + private string fileSizeText; + private string fileTypeText; + public string Name { get; set; } public MainTreeFolder Parent { get; set; } public MainTreeFolder Folder { get; set; } public RpfFileEntry File { get; set; } - public string Path { get; set; } + public string Path { get => path; set => path = value; } public string FullPath { get; set; } - public FileTypeInfo FileType { get; set; } - public string FileTypeText { get; set; } - public long FileSize { get; set; } - public string FileSizeText { get; set; } - public string Attributes { get; set; } + public FileTypeInfo FileType { get => ExploreForm.GetFileType(Name); } + public string FileTypeText { + get + { + if (fileTypeText is null) + { + CacheDetails(); + } + return fileTypeText; + } + set => fileTypeText = value; + } + public long FileSize { + get + { + if (fileSize == -1) + { + CacheDetails(); + } + return fileSize; + } + set => fileSize = value; + } + public string FileSizeText { + get + { + if (fileSizeText is null) + { + CacheDetails(); + } + return fileSizeText; + } + set => fileSizeText = value; + } + public string Attributes { + get + { + if (attributes is null) + { + var fld = Folder; + attributes = ""; + if (File != null) + { + if (File is RpfResourceFileEntry resf) + { + attributes += "Resource [V." + resf.Version.ToString() + "]"; + } + if (File.IsEncrypted) + { + if (Attributes.Length > 0) + { + attributes += ", "; + } + attributes += "Encrypted"; + } + } + else if (fld != null) + { + if (fld.RpfFile != null) + { + attributes += fld.RpfFile.Encryption.ToString() + " encryption"; + } + } + } + return attributes; + } + set => attributes = value; + } public int ImageIndex { get; set; } @@ -4520,7 +4973,7 @@ namespace CodeWalker public MainListItem(string file, string rootpath, MainTreeFolder parent) { Parent = parent; - Name = new FileInfo(file).Name; + Name = System.IO.Path.GetFileName(file); Path = file.Replace(rootpath, ""); FullPath = file; } @@ -4538,44 +4991,31 @@ namespace CodeWalker return Name; } - public void CacheDetails(ExploreForm form) + public void CacheDetails() { var fld = Folder; - FileType = form.GetFileType(Name); - FileTypeText = FileType.Name; - FileSizeText = ""; - Attributes = ""; - ImageIndex = FileType.ImageIndex; + var fileType = FileType; + fileTypeText = fileType.Name; + ImageIndex = fileType.ImageIndex; if (File != null) { - FileSize = File.GetFileSize(); - FileSizeText = TextUtil.GetBytesReadable(FileSize); - if (File is RpfResourceFileEntry) - { - var resf = File as RpfResourceFileEntry; - Attributes += "Resource [V." + resf.Version.ToString() + "]"; - } - if (File.IsEncrypted) - { - if (Attributes.Length > 0) Attributes += ", "; - Attributes += "Encrypted"; - } + fileSize = File.GetFileSize(); + fileSizeText = TextUtil.GetBytesReadable(FileSize); } else if (fld != null) { if (fld.RpfFile != null) { - FileSize = fld.RpfFile.FileSize; - FileSizeText = TextUtil.GetBytesReadable(FileSize); - Attributes += fld.RpfFile.Encryption.ToString() + " encryption"; + fileSize = fld.RpfFile.FileSize; + fileSizeText = TextUtil.GetBytesReadable(FileSize); } else { - FileTypeText = "Folder"; + fileTypeText = "Folder"; ImageIndex = 1; //FOLDER imageIndex var ic = fld.GetItemCount(); - FileSize = ic; - FileSizeText = ic.ToString() + " item" + ((ic != 1) ? "s" : ""); + fileSize = ic; + fileSizeText = ic.ToString() + " item" + ((ic != 1) ? "s" : ""); } } else @@ -4583,11 +5023,12 @@ namespace CodeWalker var fi = new FileInfo(FullPath); if (fi.Exists) { - FileSize = fi.Length; - FileSizeText = TextUtil.GetBytesReadable(fi.Length); + fileSize = fi.Length; + fileSizeText = TextUtil.GetBytesReadable(fi.Length); } } - + fileSizeText ??= ""; + fileTypeText ??= ""; } public int SortCompare(MainListItem i, int col, SortOrder dir) @@ -4614,19 +5055,19 @@ namespace CodeWalker { default: case 0: //Name column - return i1.Name.CompareTo(i2.Name); + return StringComparer.OrdinalIgnoreCase.Compare(i1.Name, i2.Name); case 1: //Type column - var ftc = i1.FileTypeText.CompareTo(i2.FileTypeText); - if (ftc == 0) return i1.Name.CompareTo(i2.Name); //same type, sort by name... + var ftc = StringComparer.OrdinalIgnoreCase.Compare(i1.FileTypeText, i2.FileTypeText); + if (ftc == 0) return StringComparer.OrdinalIgnoreCase.Compare(i1.Name, i2.Name); //same type, sort by name... return ftc; case 2: //Size column - return i1.FileSize.CompareTo(i2.FileSize); + return StringComparer.OrdinalIgnoreCase.Compare(i1.FileSize, i2.FileSize); case 3: //Attributes column - var ac = i1.Attributes.CompareTo(i2.Attributes); - if (ac == 0) return i1.Name.CompareTo(i2.Name); //same attributes, sort by name... + var ac = StringComparer.OrdinalIgnoreCase.Compare(i1.Attributes, i2.Attributes); + if (ac == 0) return StringComparer.OrdinalIgnoreCase.Compare(i1.Name, i2.Name); //same attributes, sort by name... return ac; case 4: //path column - return i1.Path.CompareTo(i2.Path); + return StringComparer.OrdinalIgnoreCase.Compare(i1.Path, i2.Path); } //return i1.Name.CompareTo(i2.Name); diff --git a/CodeWalker/Forms/AwcForm.cs b/CodeWalker/Forms/AwcForm.cs index ccb5327..1f41696 100644 --- a/CodeWalker/Forms/AwcForm.cs +++ b/CodeWalker/Forms/AwcForm.cs @@ -8,6 +8,7 @@ using System.Windows.Forms; using System.Drawing; using System.Linq; using Range = FastColoredTextBoxNS.Range; +using CodeWalker.Core.Utils; namespace CodeWalker.Forms { @@ -400,7 +401,7 @@ namespace CodeWalker.Forms { Stream wavStream = audio.GetWavStream(); FileStream stream = File.Create(saveFileDialog.FileName); - wavStream.CopyTo(stream); + wavStream.CopyToFast(stream); stream.Close(); wavStream.Close(); } diff --git a/CodeWalker/Forms/GenericForm.cs b/CodeWalker/Forms/GenericForm.cs index 17d19eb..8410f3c 100644 --- a/CodeWalker/Forms/GenericForm.cs +++ b/CodeWalker/Forms/GenericForm.cs @@ -25,14 +25,11 @@ namespace CodeWalker.Forms } } public string FilePath { get; set; } - - ExploreForm ExploreForm; object CurrentFile; - public GenericForm(ExploreForm exploreForm) + public GenericForm() { - ExploreForm = exploreForm; InitializeComponent(); } diff --git a/CodeWalker/Forms/MetaForm.cs b/CodeWalker/Forms/MetaForm.cs index 941aeb4..d793ff1 100644 --- a/CodeWalker/Forms/MetaForm.cs +++ b/CodeWalker/Forms/MetaForm.cs @@ -45,16 +45,12 @@ namespace CodeWalker.Forms private bool LoadingXml = false; private bool DelayHighlight = false; - - private ExploreForm exploreForm = null; public RpfFileEntry rpfFileEntry { get; private set; } = null; private MetaFormat metaFormat = MetaFormat.XML; - public MetaForm(ExploreForm owner) + public MetaForm() { - exploreForm = owner; - InitializeComponent(); } @@ -417,6 +413,68 @@ namespace CodeWalker.Forms } } + public void LoadMeta(PackedFile gameFile) + { + gameFile = gameFile ?? throw new ArgumentNullException(nameof(gameFile)); + + if (gameFile is YmfFile ymfFile) + { + LoadMeta(ymfFile); + } + else if (gameFile is MrfFile mrfFile) + { + LoadMeta(mrfFile); + } + else if (gameFile is YfdFile yfdFile) + { + LoadMeta(yfdFile); + } + else if (gameFile is YpdbFile ypdbFile) + { + LoadMeta(ypdbFile); + } + else if (gameFile is HeightmapFile heightmap) + { + LoadMeta(heightmap); + } + else if (gameFile is CacheDatFile cacheDatFile) + { + LoadMeta(cacheDatFile); + } + else if (gameFile is YedFile yedFile) + { + LoadMeta(yedFile); + } + else if (gameFile is YldFile yldFile) + { + LoadMeta(yldFile); + } + else if (gameFile is YndFile yndFile) + { + LoadMeta(yndFile); + } + else if (gameFile is CutFile cutFile) + { + LoadMeta(cutFile); + } + else if (gameFile is JPsoFile jpsoFile) + { + LoadMeta(jpsoFile); + } + else if (gameFile is YtypFile ytypFile) + { + LoadMeta(ytypFile); + } + else if (gameFile is YmapFile ymapFile) + { + LoadMeta(ymapFile); + } + else if (gameFile is YmtFile ymtFile) + { + LoadMeta(ymtFile); + } + } + public bool SaveMeta(XmlDocument doc) @@ -426,7 +484,7 @@ namespace CodeWalker.Forms //otherwise, save the generated file to disk? //(currently just return false and revert to XML file save) - if (!(exploreForm?.EditMode ?? false)) return false; + if (!(ExploreForm.Instance?.EditMode ?? false)) return false; if(metaFormat == MetaFormat.XML) return false;//what are we even doing here? @@ -466,14 +524,14 @@ namespace CodeWalker.Forms try { - if (!(exploreForm?.EnsureRpfValidEncryption(rpfFileEntry.File) ?? false)) return false; + if (!(ExploreForm.EnsureRpfValidEncryption(rpfFileEntry.File))) return false; var newentry = RpfFile.CreateFile(rpfFileEntry.Parent, rpfFileEntry.Name, data); if (newentry != rpfFileEntry) { } rpfFileEntry = newentry; - exploreForm?.RefreshMainListViewInvoke(); //update the file details in explorer... + ExploreForm.RefreshMainListViewInvoke(); //update the file details in explorer... modified = false; @@ -492,7 +550,7 @@ namespace CodeWalker.Forms { File.WriteAllBytes(rpfFileEntry.Path, data); - exploreForm?.RefreshMainListViewInvoke(); //update the file details in explorer... + ExploreForm.RefreshMainListViewInvoke(); //update the file details in explorer... modified = false; diff --git a/CodeWalker/Forms/ModelForm.cs b/CodeWalker/Forms/ModelForm.cs index a89cd96..6febced 100644 --- a/CodeWalker/Forms/ModelForm.cs +++ b/CodeWalker/Forms/ModelForm.cs @@ -24,7 +24,7 @@ namespace CodeWalker.Forms { public Form Form { get { return this; } } //for DXForm/DXManager use - private Renderer Renderer = null; + public Renderer Renderer { get; set; } volatile bool formopen = false; @@ -37,6 +37,8 @@ namespace CodeWalker.Forms Weather weather; Clouds clouds; + public CancellationTokenSource CancellationTokenSource { get; } = new CancellationTokenSource(); + bool MouseLButtonDown = false; bool MouseRButtonDown = false; int MouseX; @@ -114,7 +116,6 @@ namespace CodeWalker.Forms public bool showLightGizmos = true; public Skeleton Skeleton = null; - ExploreForm exploreForm = null; RpfFileEntry rpfFileEntry = null; @@ -128,16 +129,14 @@ namespace CodeWalker.Forms - public ModelForm(ExploreForm ExpForm = null) + public ModelForm() { if (this.DesignMode) return; InitializeComponent(); - exploreForm = ExpForm; + gameFileCache = GameFileCacheFactory.GetInstance(); - gameFileCache = ExpForm?.GetFileCache() ?? GameFileCacheFactory.Create(); - - if (ExpForm == null) + if (ExploreForm.Instance == null) { gameFileCache.EnableDlc = false; gameFileCache.EnableMods = false; @@ -163,31 +162,40 @@ namespace CodeWalker.Forms catch(Exception ex) { Console.WriteLine(ex); - throw ex; + throw; } }); - Task.Run(() => + Task.Run(async () => { - while (!IsDisposed) //run the file cache content thread until the form exits. + try { - if (gameFileCache.IsInited) + while (!IsDisposed) //run the file cache content thread until the form exits. { - gameFileCache.BeginFrame(); - - bool fcItemsPending = gameFileCache.ContentThreadProc(); - - if (!fcItemsPending) + if (gameFileCache.IsInited) { - Thread.Sleep(10); + gameFileCache.BeginFrame(); + + bool fcItemsPending = gameFileCache.ContentThreadProc(); + + if (!fcItemsPending) + { + await Task.Delay(10); + } + } + else + { + await Task.Delay(20); } } - else - { - Thread.Sleep(20); - } } + catch(Exception ex) + { + Console.WriteLine($"Exception occurred in gameFileCache ContentThread.\n{ex}"); + throw; + } + }); } @@ -299,7 +307,8 @@ namespace CodeWalker.Forms formopen = true; - new Thread(new ThreadStart(ContentThread)).Start(); + + Task.Run(ContentThread); frametimer.Start(); } @@ -324,19 +333,26 @@ namespace CodeWalker.Forms Console.WriteLine("Clearing cache"); gameFileCache.Clear(); gameFileCache.IsInited = true; - GC.Collect(); + //GC.Collect(); } } - public void RenderScene(DeviceContext context) + public async ValueTask RenderScene(DeviceContext context) { float elapsed = (float)frametimer.Elapsed.TotalSeconds; frametimer.Restart(); + if (elapsed < 0.016666) + { + await Task.Delay((int)(0.016666 * elapsed) * 1000); + } + if (Pauserendering) return; if (!Monitor.TryEnter(Renderer.RenderSyncRoot, 50)) - { return; } //couldn't get a lock, try again next time + { + return; + } //couldn't get a lock, try again next time UpdateControlInputs(elapsed); @@ -376,7 +392,7 @@ namespace CodeWalker.Forms } - private void ContentThread() + private async void ContentThread() { //main content loading thread. //running = true; @@ -453,7 +469,7 @@ namespace CodeWalker.Forms if (!(rcItemsPending)) //gameFileCache.ItemsStillPending || { - Thread.Sleep(1); //sleep if there's nothing to do + await Task.Delay(ActiveForm == null ? 50 : 1).ConfigureAwait(false); } } @@ -482,7 +498,7 @@ namespace CodeWalker.Forms List ycdlist = new List(); foreach (var ycde in ycds) { - ycdlist.Add(ycde.GetShortName()); + ycdlist.Add(ycde.ShortName); } ClipDictComboBox.AutoCompleteCustomSource.AddRange(ycdlist.ToArray()); ClipDictComboBox.Text = ""; @@ -703,10 +719,10 @@ namespace CodeWalker.Forms public void ViewModel(byte[] data, RpfFileEntry e) { - var nl = e?.NameLower ?? ""; + var nl = e?.Name ?? ""; var fe = Path.GetExtension(nl); Show(); - switch (fe) + switch (fe.ToLowerInvariant()) { case ".ydr": var ydr = RpfFile.GetFile(e, data); @@ -902,10 +918,10 @@ namespace CodeWalker.Forms Yft = yft; rpfFileEntry = Yft.RpfFileEntry; ModelHash = Yft.RpfFileEntry?.ShortNameHash ?? 0; - var namelower = Yft.RpfFileEntry?.ShortName; - if (namelower?.EndsWith("_hi", StringComparison.OrdinalIgnoreCase) ?? false) + var name = Yft.RpfFileEntry?.ShortName; + if (name?.EndsWith("_hi", StringComparison.OrdinalIgnoreCase) ?? false) { - ModelHash = JenkHash.GenHashLower(namelower.Substring(0, namelower.Length - 3)); + ModelHash = JenkHash.GenHashLower(name.AsSpan(0, name.Length - 3)); } if (ModelHash != 0) { @@ -1295,7 +1311,7 @@ namespace CodeWalker.Forms { items.Add(kvp); } - items.Sort((a, b) => { return a.Value?.Name?.CompareTo(b.Value?.Name ?? "") ?? 0; }); + items.Sort((a, b) => StringComparer.OrdinalIgnoreCase.Compare(a.Value?.Name, b.Value?.Name)); foreach (var kvp in items) { AddDrawableTreeNode(kvp.Value, kvp.Key, check); @@ -1668,7 +1684,7 @@ namespace CodeWalker.Forms if (td != null) { - YtdForm f = new YtdForm(null, this); + YtdForm f = new YtdForm(this); f.Show(this); f.LoadTexDict(td, fileName); } @@ -1760,7 +1776,7 @@ namespace CodeWalker.Forms private void Save(bool saveAs = false) { - var editMode = exploreForm?.EditMode ?? false; + var editMode = ExploreForm.Instance?.EditMode ?? false; if (string.IsNullOrEmpty(FilePath)) { @@ -1857,14 +1873,14 @@ namespace CodeWalker.Forms try { - if (!(exploreForm?.EnsureRpfValidEncryption(rpfFileEntry.File) ?? false)) return; + if (!(ExploreForm.EnsureRpfValidEncryption(rpfFileEntry.File))) return; var newentry = RpfFile.CreateFile(rpfFileEntry.Parent, rpfFileEntry.Name, fileBytes); if (newentry != rpfFileEntry) { } rpfFileEntry = newentry; - exploreForm?.RefreshMainListViewInvoke(); //update the file details in explorer... + ExploreForm.RefreshMainListViewInvoke(); //update the file details in explorer... StatusLabel.Text = rpfFileEntry.Name + " saved successfully at " + DateTime.Now.ToString(); @@ -1889,7 +1905,7 @@ namespace CodeWalker.Forms fileName = Path.GetFileName(fn); - exploreForm?.RefreshMainListViewInvoke(); //update the file details in explorer... + ExploreForm.RefreshMainListViewInvoke(); //update the file details in explorer... StatusLabel.Text = fileName + " saved successfully at " + DateTime.Now.ToString(); } diff --git a/CodeWalker/Forms/RelForm.cs b/CodeWalker/Forms/RelForm.cs index 4a629e1..e203247 100644 --- a/CodeWalker/Forms/RelForm.cs +++ b/CodeWalker/Forms/RelForm.cs @@ -51,8 +51,6 @@ namespace CodeWalker.Forms private bool modified = false; private bool LoadingXml = false; private bool DelayHighlight = false; - - private ExploreForm exploreForm = null; public RpfFileEntry rpfFileEntry { get; private set; } = null; private MetaFormat metaFormat = MetaFormat.XML; @@ -60,10 +58,8 @@ namespace CodeWalker.Forms private Dat10Synth currentSynth = null; - public RelForm(ExploreForm owner) + public RelForm() { - exploreForm = owner; - InitializeComponent(); } @@ -207,7 +203,7 @@ namespace CodeWalker.Forms private bool SaveRel(XmlDocument doc) { - if (!(exploreForm?.EditMode ?? false)) return false; + if (!(ExploreForm.Instance?.EditMode ?? false)) return false; byte[] data = null; @@ -256,14 +252,14 @@ namespace CodeWalker.Forms try { - if (!(exploreForm?.EnsureRpfValidEncryption(rpfFileEntry.File) ?? false)) return false; + if (!(ExploreForm.EnsureRpfValidEncryption(rpfFileEntry.File))) return false; var newentry = RpfFile.CreateFile(rpfFileEntry.Parent, rpfFileEntry.Name, data); if (newentry != rpfFileEntry) { } rpfFileEntry = newentry; - exploreForm?.RefreshMainListViewInvoke(); //update the file details in explorer... + ExploreForm.RefreshMainListViewInvoke(); //update the file details in explorer... modified = false; @@ -282,7 +278,7 @@ namespace CodeWalker.Forms { File.WriteAllBytes(rpfFileEntry.Path, data); - exploreForm?.RefreshMainListViewInvoke(); //update the file details in explorer... + ExploreForm.RefreshMainListViewInvoke(); //update the file details in explorer... modified = false; @@ -513,7 +509,6 @@ namespace CodeWalker.Forms bool textsearch = SearchTextRadio.Checked; var text = SearchTextBox.Text; - var textl = text.ToLowerInvariant(); uint hash = 0; uint hashl = 0; @@ -521,8 +516,8 @@ namespace CodeWalker.Forms { hash = JenkHash.GenHash(text); JenkIndex.Ensure(text); - hashl = JenkHash.GenHash(textl); - JenkIndex.Ensure(textl); + hashl = JenkHash.GenHashLower(text); + JenkIndex.EnsureLower(text); } else { @@ -536,8 +531,8 @@ namespace CodeWalker.Forms { if (textsearch) { - if (((rd.Name?.Contains(textl, StringComparison.OrdinalIgnoreCase)) ?? false) || (rd.NameHash == hash) || (rd.NameHash == hashl) || - rd.NameHash.ToString().Contains(textl, StringComparison.OrdinalIgnoreCase)) + if (((rd.Name?.Contains(text, StringComparison.OrdinalIgnoreCase)) ?? false) || (rd.NameHash == hash) || (rd.NameHash == hashl) || + rd.NameHash.ToString().Contains(text, StringComparison.OrdinalIgnoreCase)) { results.Add(rd); } diff --git a/CodeWalker/Forms/TextForm.cs b/CodeWalker/Forms/TextForm.cs index 3556924..75dce17 100644 --- a/CodeWalker/Forms/TextForm.cs +++ b/CodeWalker/Forms/TextForm.cs @@ -41,7 +41,6 @@ namespace CodeWalker.Forms private bool modified = false; - private ExploreForm exploreForm = null; public RpfFileEntry rpfFileEntry { get; private set; } = null; @@ -55,10 +54,8 @@ namespace CodeWalker.Forms - public TextForm(ExploreForm owner) + public TextForm() { - exploreForm = owner; - InitializeComponent(); } @@ -160,15 +157,14 @@ namespace CodeWalker.Forms if (!File.Exists(fn)) return; //couldn't find file? - var fnl = fn.ToLowerInvariant(); - if (fnl.EndsWith(".gxt2")) + if (fn.EndsWith(".gxt2", StringComparison.OrdinalIgnoreCase)) { var gxt = new Gxt2File(); gxt.Load(File.ReadAllBytes(fn), null); fileType = TextFileType.GXT2; TextValue = gxt.ToText(); } - else if (fnl.EndsWith(".nametable")) + else if (fn.EndsWith(".nametable", StringComparison.OrdinalIgnoreCase)) { fileType = TextFileType.Nametable; TextValue = File.ReadAllText(fn).Replace('\0', '\n'); @@ -242,7 +238,7 @@ namespace CodeWalker.Forms private bool SaveToRPF(string txt) { - if (!(exploreForm?.EditMode ?? false)) return false; + if (!(ExploreForm.Instance?.EditMode ?? false)) return false; if (rpfFileEntry?.Parent == null) return false; byte[] data = null; @@ -287,14 +283,14 @@ namespace CodeWalker.Forms try { - if (!(exploreForm?.EnsureRpfValidEncryption(rpfFileEntry.File) ?? false)) return false; + if (!(ExploreForm.EnsureRpfValidEncryption(rpfFileEntry.File))) return false; var newentry = RpfFile.CreateFile(rpfFileEntry.Parent, rpfFileEntry.Name, data); if (newentry != rpfFileEntry) { } rpfFileEntry = newentry; - exploreForm?.RefreshMainListViewInvoke(); //update the file details in explorer... + ExploreForm.RefreshMainListViewInvoke(); //update the file details in explorer... modified = false; diff --git a/CodeWalker/Forms/XmlForm.cs b/CodeWalker/Forms/XmlForm.cs index dc84089..5c6649a 100644 --- a/CodeWalker/Forms/XmlForm.cs +++ b/CodeWalker/Forms/XmlForm.cs @@ -44,15 +44,11 @@ namespace CodeWalker.Forms private bool modified = false; private bool LoadingXml = false; private bool DelayHighlight = false; - - private ExploreForm exploreForm = null; public RpfFileEntry rpfFileEntry { get; private set; } = null; - public XmlForm(ExploreForm owner) + public XmlForm() { - exploreForm = owner; - InitializeComponent(); } @@ -215,7 +211,7 @@ namespace CodeWalker.Forms private bool SaveToRPF(string txt) { - if (!(exploreForm?.EditMode ?? false)) return false; + if (!(ExploreForm.Instance?.EditMode ?? false)) return false; if (rpfFileEntry?.Parent == null) return false; byte[] data = null; @@ -238,14 +234,14 @@ namespace CodeWalker.Forms try { - if (!(exploreForm?.EnsureRpfValidEncryption(rpfFileEntry.File) ?? false)) return false; + if (!(ExploreForm.EnsureRpfValidEncryption(rpfFileEntry.File))) return false; var newentry = RpfFile.CreateFile(rpfFileEntry.Parent, rpfFileEntry.Name, data); if (newentry != rpfFileEntry) { } rpfFileEntry = newentry; - exploreForm?.RefreshMainListViewInvoke(); //update the file details in explorer... + ExploreForm.RefreshMainListViewInvoke(); //update the file details in explorer... modified = false; diff --git a/CodeWalker/Forms/YtdForm.Designer.cs b/CodeWalker/Forms/YtdForm.Designer.cs index 1bc0de0..c49922f 100644 --- a/CodeWalker/Forms/YtdForm.Designer.cs +++ b/CodeWalker/Forms/YtdForm.Designer.cs @@ -61,7 +61,7 @@ this.label1 = new System.Windows.Forms.Label(); this.SelTextureZoomCombo = new System.Windows.Forms.ComboBox(); this.SelTexturePanel = new System.Windows.Forms.Panel(); - this.SelTexturePictureBox = new System.Windows.Forms.PictureBox(); + this.SelTexturePictureBox = new PixelBox(); this.SelTextureMipLabel = new System.Windows.Forms.Label(); this.SelTextureDimensionsLabel = new System.Windows.Forms.Label(); this.SelTextureMipTrackBar = new System.Windows.Forms.TrackBar(); @@ -436,6 +436,7 @@ this.SelTexturePictureBox.SizeMode = System.Windows.Forms.PictureBoxSizeMode.AutoSize; this.SelTexturePictureBox.TabIndex = 45; this.SelTexturePictureBox.TabStop = false; + // // SelTextureMipLabel // @@ -573,7 +574,7 @@ private System.Windows.Forms.Label SelTextureDimensionsLabel; private System.Windows.Forms.TrackBar SelTextureMipTrackBar; private System.Windows.Forms.Label label4; - private System.Windows.Forms.PictureBox SelTexturePictureBox; + private PixelBox SelTexturePictureBox; private System.Windows.Forms.TabPage DetailsTabPage; private WinForms.PropertyGridFix DetailsPropertyGrid; private System.Windows.Forms.ListView TexturesListView; diff --git a/CodeWalker/Forms/YtdForm.cs b/CodeWalker/Forms/YtdForm.cs index 408dba5..ac77646 100644 --- a/CodeWalker/Forms/YtdForm.cs +++ b/CodeWalker/Forms/YtdForm.cs @@ -5,10 +5,12 @@ using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; +using System.Drawing.Drawing2D; using System.Drawing.Imaging; using System.IO; using System.Linq; using System.Runtime.InteropServices; +using System.Runtime.Remoting.Channels; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; @@ -23,13 +25,11 @@ namespace CodeWalker.Forms private Texture CurrentTexture = null; private float CurrentZoom = 0.0f; //1.0 = 100%, 0.0 = stretch private bool Modified = false; - private ExploreForm ExploreForm = null; private ModelForm ModelForm = null; - public YtdForm(ExploreForm exploreForm = null, ModelForm modelForm = null) + public YtdForm(ModelForm modelForm = null) { - ExploreForm = exploreForm; ModelForm = modelForm; InitializeComponent(); } @@ -155,8 +155,10 @@ namespace CodeWalker.Forms byte[] pixels = DDSIO.GetPixels(tex, cmip); int w = tex.Width >> cmip; int h = tex.Height >> cmip; - Bitmap bmp = new Bitmap(w, h, PixelFormat.Format32bppArgb); + //Image bmp = Image.FromStream(new MemoryStream(pixels)); + + Bitmap bmp = new Bitmap(w, h, PixelFormat.Format32bppArgb); if (pixels != null) { var BoundsRect = new System.Drawing.Rectangle(0, 0, w, h); @@ -396,7 +398,7 @@ namespace CodeWalker.Forms } else { - var cansave = (ExploreForm?.EditMode ?? false); + var cansave = (ExploreForm.Instance?.EditMode ?? false); var s = "Save " + FileName; var sas = "Save " + FileName + " As..."; FileSaveMenu.Text = s; @@ -435,7 +437,7 @@ namespace CodeWalker.Forms private void SaveYTD(bool saveas = false) { if (Ytd == null) return; - if (!(ExploreForm?.EditMode ?? false)) + if (!(ExploreForm.Instance?.EditMode ?? false)) { saveas = true; } @@ -485,18 +487,18 @@ namespace CodeWalker.Forms else if (!isinrpf) //save direct to filesystem in RPF explorer { File.WriteAllBytes(rpfFileEntry.Path, data); - ExploreForm?.RefreshMainListViewInvoke(); //update the file details in explorer... + ExploreForm.RefreshMainListViewInvoke(); //update the file details in explorer... } else //save to RPF... { - if (!(ExploreForm?.EnsureRpfValidEncryption(rpfFileEntry.File) ?? false)) + if (!ExploreForm.EnsureRpfValidEncryption(rpfFileEntry.File)) { MessageBox.Show("Unable to save file, RPF encryption needs to be OPEN for this operation!"); return; } Ytd.RpfFileEntry = RpfFile.CreateFile(rpfFileEntry.Parent, rpfFileEntry.Name, data); - ExploreForm?.RefreshMainListViewInvoke(); //update the file details in explorer... + ExploreForm.RefreshMainListViewInvoke(); //update the file details in explorer... } Modified = false; @@ -647,4 +649,14 @@ namespace CodeWalker.Forms RenameTexture(SelTextureNameTextBox.Text); } } + + public class PixelBox : PictureBox + { + protected override void OnPaint(PaintEventArgs pe) + { + pe.Graphics.InterpolationMode = InterpolationMode.NearestNeighbor; + pe.Graphics.PixelOffsetMode = PixelOffsetMode.Half; + base.OnPaint(pe); + } + } } diff --git a/CodeWalker/GameFiles/GameFileCacheFactory.cs b/CodeWalker/GameFiles/GameFileCacheFactory.cs index 159613b..f477934 100644 --- a/CodeWalker/GameFiles/GameFileCacheFactory.cs +++ b/CodeWalker/GameFiles/GameFileCacheFactory.cs @@ -1,6 +1,7 @@  + using CodeWalker.Properties; using System; using System.Diagnostics; @@ -11,7 +12,7 @@ namespace CodeWalker.GameFiles public static class GameFileCacheFactory { public static GameFileCache _instance = null; - public static GameFileCache Create() + public static GameFileCache GetInstance() { if (_instance == null) { diff --git a/CodeWalker/MenuForm.cs b/CodeWalker/MenuForm.cs index 80eab13..30cbdfa 100644 --- a/CodeWalker/MenuForm.cs +++ b/CodeWalker/MenuForm.cs @@ -36,13 +36,13 @@ namespace CodeWalker private void RPFExplorerButton_Click(object sender, EventArgs e) { ExploreForm f = new ExploreForm(); - f.Show(this); + f.Show(); } private void RPFBrowserButton_Click(object sender, EventArgs e) { BrowseForm f = new BrowseForm(); - f.Show(this); + f.Show(); } private void ExtractScriptsButton_Click(object sender, EventArgs e) diff --git a/CodeWalker/PedsForm.cs b/CodeWalker/PedsForm.cs index e7cd4b5..8ccad15 100644 --- a/CodeWalker/PedsForm.cs +++ b/CodeWalker/PedsForm.cs @@ -24,7 +24,7 @@ namespace CodeWalker { public Form Form { get { return this; } } //for DXForm/DXManager use - public Renderer Renderer = null; + public Renderer Renderer { get; set; } public object RenderSyncRoot { get { return Renderer.RenderSyncRoot; } } public bool Pauserendering { get; set; } @@ -41,6 +41,8 @@ namespace CodeWalker Entity camEntity = new Entity(); + public CancellationTokenSource CancellationTokenSource { get; } = new CancellationTokenSource(); + bool MouseLButtonDown = false; bool MouseRButtonDown = false; @@ -50,7 +52,7 @@ namespace CodeWalker System.Drawing.Point MouseLastPoint; - public GameFileCache GameFileCache { get; } = GameFileCacheFactory.Create(); + public GameFileCache GameFileCache { get; } = GameFileCacheFactory.GetInstance(); InputManager Input = new InputManager(); @@ -189,7 +191,8 @@ namespace CodeWalker formopen = true; - new Thread(new ThreadStart(ContentThread)).Start(); + + Task.Run(ContentThread); frametimer.Start(); @@ -207,63 +210,75 @@ namespace CodeWalker count++; } } - public void RenderScene(DeviceContext context) + public async ValueTask RenderScene(DeviceContext context) { float elapsed = (float)frametimer.Elapsed.TotalSeconds; frametimer.Restart(); + if (elapsed < 0.016666) + { + await Task.Delay((int)(0.016666 * elapsed) * 1000); + } + if (Pauserendering) return; GameFileCache.BeginFrame(); if (!Monitor.TryEnter(Renderer.RenderSyncRoot, 50)) - { return; } //couldn't get a lock, try again next time + { + return; + } //couldn't get a lock, try again next time - UpdateControlInputs(elapsed); - //space.Update(elapsed); + try + { + UpdateControlInputs(elapsed); + //space.Update(elapsed); - Renderer.Update(elapsed, MouseLastPoint.X, MouseLastPoint.Y); + Renderer.Update(elapsed, MouseLastPoint.X, MouseLastPoint.Y); - //UpdateWidgets(); - //BeginMouseHitTest(); + //UpdateWidgets(); + //BeginMouseHitTest(); - Renderer.BeginRender(context); + Renderer.BeginRender(context); - Renderer.RenderSkyAndClouds(); + Renderer.RenderSkyAndClouds(); - Renderer.SelectedDrawable = null;// SelectedItem.Drawable; + Renderer.SelectedDrawable = null;// SelectedItem.Drawable; - Renderer.RenderPed(SelectedPed); + Renderer.RenderPed(SelectedPed); - //UpdateMouseHitsFromRenderer(); - //RenderSelection(); + //UpdateMouseHitsFromRenderer(); + //RenderSelection(); - RenderGrid(context); + RenderGrid(context); - Renderer.RenderQueued(); + Renderer.RenderQueued(); - //Renderer.RenderBounds(MapSelectionMode.Entity); + //Renderer.RenderBounds(MapSelectionMode.Entity); - //Renderer.RenderSelectionGeometry(MapSelectionMode.Entity); + //Renderer.RenderSelectionGeometry(MapSelectionMode.Entity); - //RenderMoused(); + //RenderMoused(); - Renderer.RenderFinalPass(); + Renderer.RenderFinalPass(); - //RenderMarkers(); - //RenderWidgets(); + //RenderMarkers(); + //RenderWidgets(); - Renderer.EndRender(); - - Monitor.Exit(Renderer.RenderSyncRoot); + Renderer.EndRender(); + } + finally + { + Monitor.Exit(Renderer.RenderSyncRoot); + } //UpdateMarkerSelectionPanelInvoke(); } @@ -324,74 +339,89 @@ namespace CodeWalker private void ContentThread() { - //main content loading thread. - running = true; - - UpdateStatus("Scanning..."); - try { - GTA5Keys.LoadFromPath(GTAFolder.CurrentGTAFolder, Settings.Default.Key); - } - catch - { - MessageBox.Show("Keys not found! This shouldn't happen."); - Close(); - return; - } + //main content loading thread. + running = true; - GameFileCache.EnableDlc = true; - GameFileCache.EnableMods = true; - GameFileCache.LoadPeds = true; - GameFileCache.LoadVehicles = false; - GameFileCache.LoadArchetypes = false;//to speed things up a little - GameFileCache.BuildExtendedJenkIndex = false;//to speed things up a little - GameFileCache.DoFullStringIndex = true;//to get all global text from DLC... - GameFileCache.Init(UpdateStatus, LogError); + UpdateStatus("Scanning..."); - //UpdateDlcListComboBox(gameFileCache.DlcNameList); - - //EnableCacheDependentUI(); - - UpdateGlobalPedsUI(); - - - LoadWorld(); - - - - //initialised = true; - - //EnableDLCModsUI(); - - //UpdateStatus("Ready"); - - - Task.Run(() => { - while (formopen && !IsDisposed) //renderer content loop + try { - bool rcItemsPending = Renderer.ContentThreadProc(); + GTA5Keys.LoadFromPath(GTAFolder.CurrentGTAFolder, Settings.Default.Key); + } + catch + { + MessageBox.Show("Keys not found! This shouldn't happen."); + Close(); + return; + } - if (!rcItemsPending) + if (!GameFileCache.IsInited) + { + GameFileCache.EnableDlc = true; + GameFileCache.EnableMods = true; + GameFileCache.LoadPeds = true; + GameFileCache.LoadVehicles = false; + GameFileCache.LoadArchetypes = false;//to speed things up a little + GameFileCache.BuildExtendedJenkIndex = false;//to speed things up a little + GameFileCache.DoFullStringIndex = true;//to get all global text from DLC... + GameFileCache.Init(UpdateStatus, LogError, force: false); + } + + //UpdateDlcListComboBox(gameFileCache.DlcNameList); + + //EnableCacheDependentUI(); + + UpdateGlobalPedsUI(); + + + LoadWorld(); + + + + //initialised = true; + + //EnableDLCModsUI(); + + //UpdateStatus("Ready"); + + + Task.Run(async () => { + while (formopen && !IsDisposed) //renderer content loop { - Thread.Sleep(1); //sleep if there's nothing to do + bool rcItemsPending = Renderer.ContentThreadProc(); + + if (!rcItemsPending) + { + await Task.Delay(ActiveForm == null ? 50 : 1).ConfigureAwait(false); + } } - } - }); + }); - while (formopen && !IsDisposed) //main asset loop - { - bool fcItemsPending = GameFileCache.ContentThreadProc(); - - if (!fcItemsPending) + Task.Run(async () => { - Thread.Sleep(1); //sleep if there's nothing to do - } + while (formopen && !IsDisposed) //main asset loop + { + bool fcItemsPending = GameFileCache.ContentThreadProc(); + + if (!fcItemsPending) + { + await Task.Delay(ActiveForm == null ? 50 : 1).ConfigureAwait(false); + } + } + + running = false; + }); + } + catch(Exception ex) + { + Console.WriteLine($"Exception occured in PedsForm::ContentThread.\n{ex}"); + } + finally + { + running = false; } - - GameFileCache.Clear(); - - running = false; } @@ -662,7 +692,7 @@ namespace CodeWalker List ycdlist = new List(); foreach (var ycde in ycds) { - ycdlist.Add(ycde.GetShortName()); + ycdlist.Add(ycde.ShortName); } ClipDictComboBox.AutoCompleteCustomSource.AddRange(ycdlist.ToArray()); ClipDictComboBox.Text = ""; diff --git a/CodeWalker/Program.cs b/CodeWalker/Program.cs index e79197f..db005a6 100644 --- a/CodeWalker/Program.cs +++ b/CodeWalker/Program.cs @@ -1,4 +1,5 @@ -using CodeWalker.Forms; +using CodeWalker.Core.Utils; +using CodeWalker.Forms; using CodeWalker.GameFiles; using CodeWalker.Properties; using CodeWalker.Utils; @@ -10,6 +11,7 @@ using System.IO; using System.Linq; using System.Reflection; using System.Runtime.InteropServices; +using System.Threading; using System.Threading.Tasks; using System.Windows.Forms; using System.Windows.Shell; @@ -29,6 +31,13 @@ namespace CodeWalker { CultureInfo.CurrentCulture = CultureInfo.InvariantCulture; ConsoleWindow.Hide(); + + Application.ThreadException += (object sender, ThreadExceptionEventArgs e) => + { + Console.WriteLine($"Unhandeled exception occured: {e.Exception}"); + }; + + bool menumode = false; bool explorermode = false; bool projectmode = false; @@ -98,7 +107,13 @@ namespace CodeWalker } else if (explorermode) { - Application.Run(new ExploreForm()); + if (!NamedPipe.TrySendMessageToOtherProcess("explorer")) + { + var form = new ExploreForm(); + var namedPipe = new NamedPipe(form); + namedPipe.Init(); + Application.Run(form); + } } else if (projectmode) { @@ -114,15 +129,29 @@ namespace CodeWalker } else if (path != null) { - var modelForm = new ModelForm(); - modelForm.Load += new EventHandler(async (sender, eventArgs) => { - modelForm.ViewModel(path); - }); - Application.Run(modelForm); + if (!NamedPipe.TrySendMessageToOtherProcess($"open-file {path}")) + { + Form form = null; + try + { + form = OpenAnyFile.OpenFilePath(path); + } + catch (NotImplementedException ex) + { + MessageBox.Show("Dit type bestand is op het moment nog niet ondersteund!", ex.ToString()); + } + if (form != null) + { + Application.Run(form); + } + } } else { - Application.Run(new WorldForm()); + var form = new WorldForm(); + var namedPipe = new NamedPipe(form); + namedPipe.Init(); + Application.Run(form); } #if !DEBUG } diff --git a/CodeWalker/Project/Panels/EditProjectManifestPanel.cs b/CodeWalker/Project/Panels/EditProjectManifestPanel.cs index d58bd45..45f009a 100644 --- a/CodeWalker/Project/Panels/EditProjectManifestPanel.cs +++ b/CodeWalker/Project/Panels/EditProjectManifestPanel.cs @@ -73,20 +73,19 @@ namespace CodeWalker.Project.Panels var getYtypName = new Func((ytyp) => { - var ytypname = ytyp?.RpfFileEntry?.NameLower; + var ytypname = ytyp?.RpfFileEntry?.Name; if (ytyp != null) { if (string.IsNullOrEmpty(ytypname)) { - ytypname = ytyp.RpfFileEntry?.Name?.ToLowerInvariant(); - if (ytypname == null) ytypname = ""; + ytypname = ytyp.RpfFileEntry?.Name ?? string.Empty; } - if (ytypname.EndsWith(".ytyp")) + if (ytypname.EndsWith(".ytyp", StringComparison.OrdinalIgnoreCase)) { ytypname = ytypname.Substring(0, ytypname.Length - 5); } } - return ytypname; + return ytypname?.ToLowerInvariant(); }); @@ -97,7 +96,7 @@ namespace CodeWalker.Project.Panels sb.AppendLine(" "); foreach (var ymap in CurrentProjectFile.YmapFiles) { - var ymapname = ymap.RpfFileEntry?.NameLower; + var ymapname = ymap.RpfFileEntry?.Name.ToLowerInvariant(); if (string.IsNullOrEmpty(ymapname)) { ymapname = ymap.Name.ToLowerInvariant(); diff --git a/CodeWalker/Project/Panels/ProjectExplorerPanel.cs b/CodeWalker/Project/Panels/ProjectExplorerPanel.cs index 9d8592e..5ec993f 100644 --- a/CodeWalker/Project/Panels/ProjectExplorerPanel.cs +++ b/CodeWalker/Project/Panels/ProjectExplorerPanel.cs @@ -60,8 +60,8 @@ namespace CodeWalker.Project.Panels LoadYmapTreeNodes(ymapfile, ymapnode); - JenkIndex.Ensure(name); - JenkIndex.Ensure(Path.GetFileNameWithoutExtension(name)); + JenkIndex.EnsureBoth(name); + JenkIndex.EnsureBoth(Path.GetFileNameWithoutExtension(name)); } ymapsnode.Expand(); } @@ -84,8 +84,8 @@ namespace CodeWalker.Project.Panels LoadYtypTreeNodes(ytypfile, ytypnode); - JenkIndex.Ensure(name); - JenkIndex.Ensure(Path.GetFileNameWithoutExtension(name)); + JenkIndex.EnsureBoth(name); + JenkIndex.EnsureBoth(Path.GetFileNameWithoutExtension(name)); } ytypsnode.Expand(); } diff --git a/CodeWalker/Project/ProjectFile.cs b/CodeWalker/Project/ProjectFile.cs index adbbcc7..8f40fb7 100644 --- a/CodeWalker/Project/ProjectFile.cs +++ b/CodeWalker/Project/ProjectFile.cs @@ -530,9 +530,9 @@ namespace CodeWalker.Project ytyp.RpfFileEntry.Name = Path.GetFileName(filename); ytyp.FilePath = GetFullFilePath(filename); ytyp.Name = ytyp.RpfFileEntry.Name; - JenkIndex.Ensure(ytyp.Name); - JenkIndex.Ensure(Path.GetFileNameWithoutExtension(ytyp.Name)); - JenkIndex.Ensure(filename); + JenkIndex.EnsureBoth(ytyp.Name); + JenkIndex.EnsureBoth(Path.GetFileNameWithoutExtension(ytyp.Name)); + JenkIndex.EnsureBoth(filename); if (!AddYtypFile(ytyp)) return null; return ytyp; } diff --git a/CodeWalker/Project/ProjectForm.cs b/CodeWalker/Project/ProjectForm.cs index 943acd3..a27fd98 100644 --- a/CodeWalker/Project/ProjectForm.cs +++ b/CodeWalker/Project/ProjectForm.cs @@ -8,6 +8,7 @@ using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; +using System.Diagnostics; using System.Drawing; using System.IO; using System.Linq; @@ -124,26 +125,22 @@ namespace CodeWalker.Project SetTheme(Settings.Default.ProjectWindowTheme, false); ShowDefaultPanels(); + GameFileCache = GameFileCacheFactory.GetInstance(); - if ((WorldForm != null) && (WorldForm.GameFileCache != null)) + if (!GameFileCache.IsInited) { - GameFileCache = WorldForm.GameFileCache; - RpfMan = GameFileCache.RpfMan; - } - else - { - GameFileCache = GameFileCacheFactory.Create(); - new Thread(new ThreadStart(() => + Task.Run(() => { GTA5Keys.LoadFromPath(GTAFolder.CurrentGTAFolder, Settings.Default.Key); GameFileCache.Init(UpdateStatus, UpdateError); - RpfMan = GameFileCache.RpfMan; - })).Start(); + }); } - } + RpfMan = GameFileCache.RpfMan; + } private void UpdateStatus(string text) { + return; try { if (InvokeRequired) @@ -168,6 +165,7 @@ namespace CodeWalker.Project } else { + Console.WriteLine(text); //TODO: error text //ErrorLabel.Text = text; } @@ -1640,14 +1638,14 @@ namespace CodeWalker.Project } break; case ".dat": - if (fn.StartsWith("trains")) + if (fn.StartsWith("trains", StringComparison.OrdinalIgnoreCase)) { var track = CurrentProjectFile.AddTrainsFile(file); if (track != null) LoadTrainTrackFromFile(track, file); } break; case ".rel": - if (fn.EndsWith(".dat151.rel")) + if (fn.EndsWith(".dat151.rel", StringComparison.OrdinalIgnoreCase)) { var dat151 = CurrentProjectFile.AddAudioRelFile(file); if (dat151 != null) LoadAudioRelFromFile(dat151, file); @@ -6129,7 +6127,7 @@ namespace CodeWalker.Project var delim = line.Contains(",") ? "," : " "; var vals = line.Split(new[] { delim }, StringSplitOptions.RemoveEmptyEntries); if (vals.Length < 3) continue; - if (vals[0].StartsWith("X")) continue; + if (vals[0].StartsWith("X", StringComparison.OrdinalIgnoreCase)) continue; Vector3 pos = Vector3.Zero; float dir = 0; var action = CScenarioChainingEdge__eAction.Move; @@ -7083,7 +7081,7 @@ namespace CodeWalker.Project - + private DateTime LastProjectCheck = DateTime.MinValue; public void GetVisibleYmaps(Camera camera, Dictionary ymaps) { if (hidegtavmap) @@ -7106,10 +7104,16 @@ namespace CodeWalker.Project } } - visiblemloentities.Clear(); - foreach (var kvp in ymaps)//TODO: improve performance + if (DateTime.Now - LastProjectCheck < TimeSpan.FromSeconds(1)) + { + return; + } + + LastProjectCheck = DateTime.Now; + + visiblemloentities.Clear(); + foreach (var ymap in ymaps.Values)//TODO: improve performance { - var ymap = kvp.Value; if (ymap.AllEntities != null)//THIS IS TERRIBLE! EATING ALL FPS { foreach (var ent in ymap.AllEntities)//WHYYYY - maybe only do this after loading/editing ytyp! @@ -8664,7 +8668,6 @@ namespace CodeWalker.Project track.Name = fname; track.FilePath = filename; track.RpfFileEntry.Name = fname; - track.RpfFileEntry.NameLower = fname.ToLowerInvariant(); if (WorldForm != null) { diff --git a/CodeWalker/Properties/Settings.Designer.cs b/CodeWalker/Properties/Settings.Designer.cs index 21e87f2..6f3fcbd 100644 --- a/CodeWalker/Properties/Settings.Designer.cs +++ b/CodeWalker/Properties/Settings.Designer.cs @@ -8,6 +8,9 @@ // //------------------------------------------------------------------------------ +using System; +using System.Windows.Forms; + namespace CodeWalker.Properties { diff --git a/CodeWalker/Properties/launchSettings.json b/CodeWalker/Properties/launchSettings.json index cc19ac1..d091a44 100644 --- a/CodeWalker/Properties/launchSettings.json +++ b/CodeWalker/Properties/launchSettings.json @@ -2,7 +2,8 @@ "profiles": { "CodeWalker": { "commandName": "Project", - "workingDirectory": ".." + "workingDirectory": "..", + "nativeDebugging": true } } } \ No newline at end of file diff --git a/CodeWalker/Rendering/DirectX/DXForm.cs b/CodeWalker/Rendering/DirectX/DXForm.cs index 1c6bd25..1713d06 100644 --- a/CodeWalker/Rendering/DirectX/DXForm.cs +++ b/CodeWalker/Rendering/DirectX/DXForm.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Drawing; using System.Linq; using System.Text; +using System.Threading; using System.Threading.Tasks; using System.Windows.Forms; @@ -17,12 +18,14 @@ namespace CodeWalker.Rendering //So, i've used an interface instead, since really just some of the form properties //and a couple of extra methods (these callbacks) are needed by DXManager. + public Renderer Renderer { get; set; } Form Form { get; } + public CancellationTokenSource CancellationTokenSource { get; } public bool Pauserendering { get; set; } void InitScene(Device device); void CleanupScene(); - void RenderScene(DeviceContext context); + ValueTask RenderScene(DeviceContext context); void BuffersResized(int w, int h); bool ConfirmQuit(); } diff --git a/CodeWalker/Rendering/DirectX/DXManager.cs b/CodeWalker/Rendering/DirectX/DXManager.cs index fa01f0d..86ac33a 100644 --- a/CodeWalker/Rendering/DirectX/DXManager.cs +++ b/CodeWalker/Rendering/DirectX/DXManager.cs @@ -15,6 +15,7 @@ using SharpDX.Direct3D; using System.Runtime; using CodeWalker.Core.Utils; using CodeWalker.Properties; +using CodeWalker.GameFiles; namespace CodeWalker.Rendering { @@ -30,10 +31,14 @@ namespace CodeWalker.Rendering public RenderTargetView targetview { get; private set; } public DepthStencilView depthview { get; private set; } + public CancellationToken cancellationToken; + private volatile bool Running = false; private volatile bool Rendering = false; private volatile bool Resizing = false; - private object syncroot = new object(); //for thread safety + private SemaphoreSlim semaphore = new SemaphoreSlim(1); + + public int multisamplecount { get; private set; } = Settings.Default.AntiAliasing; public int multisamplequality { get; private set; } = 0; //should be a setting... public Color clearcolour { get; private set; } = new Color(0.2f, 0.4f, 0.6f, 1.0f); //gross @@ -46,6 +51,8 @@ namespace CodeWalker.Rendering dxform = form; autoStartLoop = autostart; + cancellationToken = form.CancellationTokenSource.Token; + try { //SharpDX.Configuration.EnableObjectTracking = true; @@ -147,31 +154,38 @@ namespace CodeWalker.Rendering private void Cleanup() { - Running = false; - int count = 0; - while (Rendering && (count < 1000)) + try { - Thread.Sleep(1); //try to gracefully exit... - count++; + using var _ = new DisposableTimer("DXManager Cleanup"); + Running = false; + int count = 0; + while (Rendering && (count < 1000)) + { + Thread.Sleep(1); //try to gracefully exit... + count++; + } + + dxform.CleanupScene(); + + if (context != null) context.ClearState(); + + //dipose of all objects + if (depthview != null) depthview.Dispose(); + if (depthbuffer != null) depthbuffer.Dispose(); + if (targetview != null) targetview.Dispose(); + if (backbuffer != null) backbuffer.Dispose(); + if (swapchain != null) swapchain.Dispose(); + if (context != null) context.Dispose(); + + //var objs = SharpDX.Diagnostics.ObjectTracker.FindActiveObjects(); + + if (device != null) device.Dispose(); + } + catch (Exception ex) + { + Console.WriteLine(ex.ToString()); } - dxform.CleanupScene(); - - if (context != null) context.ClearState(); - - //dipose of all objects - if (depthview != null) depthview.Dispose(); - if (depthbuffer != null) depthbuffer.Dispose(); - if (targetview != null) targetview.Dispose(); - if (backbuffer != null) backbuffer.Dispose(); - if (swapchain != null) swapchain.Dispose(); - if (context != null) context.Dispose(); - - //var objs = SharpDX.Diagnostics.ObjectTracker.FindActiveObjects(); - - if (device != null) device.Dispose(); - - GC.Collect(); } private void CreateRenderBuffers() { @@ -209,19 +223,22 @@ namespace CodeWalker.Rendering } private void Resize() { + Console.WriteLine($"Resizing {Resizing}"); if (Resizing) return; - Monitor.Enter(syncroot); + semaphore.Wait(); int width = dxform.Form.ClientSize.Width; int height = dxform.Form.ClientSize.Height; if (targetview != null) targetview.Dispose(); if (backbuffer != null) backbuffer.Dispose(); + + swapchain.ResizeBuffers(1, width, height, Format.Unknown, SwapChainFlags.AllowModeSwitch); CreateRenderBuffers(); - Monitor.Exit(syncroot); + semaphore.Release(); dxform.BuffersResized(width, height); } @@ -244,6 +261,10 @@ namespace CodeWalker.Rendering } if (!e.Cancel) { + if (!dxform.CancellationTokenSource.IsCancellationRequested) + { + dxform.CancellationTokenSource.Cancel(); + } Cleanup(); } } @@ -274,11 +295,38 @@ namespace CodeWalker.Rendering } private void StartRenderLoop() { + if (Running) + { + return; + } + Running = true; - new Task(RenderLoop, TaskCreationOptions.LongRunning).Start(TaskScheduler.Default); + + Task.Run(RenderLoop); //new Thread(new ThreadStart(RenderLoop)).Start(); } - private void RenderLoop() + + + bool isPointVisibleOnAScreen(Point p) + { + foreach (Screen s in Screen.AllScreens) + { + if (p.X < s.Bounds.Right && p.X > s.Bounds.Left && p.Y > s.Bounds.Top && p.Y < s.Bounds.Bottom) + return true; + } + return false; + } + + bool isFormFullyVisible(Form f) + { + return isPointVisibleOnAScreen(new Point(f.Left, f.Top)) + && isPointVisibleOnAScreen(new Point(f.Right, f.Top)) + && isPointVisibleOnAScreen(new Point(f.Left, f.Bottom)) + && isPointVisibleOnAScreen(new Point(f.Right, f.Bottom)); + } + + private int inactiveCount = 0; + private async Task RenderLoop() { //SharpDX.Configuration.EnableObjectTracking = true; //Task.Run(async () => @@ -289,86 +337,130 @@ namespace CodeWalker.Rendering // await Task.Delay(5000); // } //}); - Thread.CurrentThread.Name = "RenderLoop"; - while (Running) + + try { - while (Resizing) + while (Running) { - swapchain.Present(1, PresentFlags.None); //just flip buffers when resizing; don't draw - } - if (dxform.Form.WindowState == FormWindowState.Minimized) - { - dxform.Pauserendering = true; - - Console.WriteLine("Window is minimized"); - dxform.RenderScene(context); - GCSettings.LargeObjectHeapCompactionMode = GCLargeObjectHeapCompactionMode.CompactOnce; - GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced, true, true); - while (dxform.Form.WindowState == FormWindowState.Minimized) + while (Resizing) { - Thread.Sleep(100); //don't hog CPU when minimised - if (dxform.Form.IsDisposed) return; //if closed while minimised + swapchain.Present(1, PresentFlags.None); //just flip buffers when resizing; don't draw } - dxform.Pauserendering = false; - Console.WriteLine("Window is maximized"); - } + if (dxform.Form.WindowState == FormWindowState.Minimized) + { + dxform.Pauserendering = true; - - if (Form.ActiveForm == null) - { - Thread.Sleep(100); //reduce the FPS when the app isn't active (maybe this should be configurable?) - if (context.IsDisposed) return; //if form closed while sleeping (eg from rightclick on taskbar) - } + Console.WriteLine("Window is minimized"); + await dxform.RenderScene(context); + + GCSettings.LargeObjectHeapCompactionMode = GCLargeObjectHeapCompactionMode.CompactOnce; + GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced, true, true); + while (dxform.Form.WindowState == FormWindowState.Minimized) + { + await Task.Delay(100, cancellationToken).ConfigureAwait(false); //don't hog CPU when minimised + if (dxform.Form.IsDisposed || cancellationToken.IsCancellationRequested) return; //if closed while minimised + } + dxform.Pauserendering = false; + Console.WriteLine("Window is maximized"); + } - Rendering = true; - try - { - if (!Monitor.TryEnter(syncroot, 50)) + + if (Form.ActiveForm == null) + { + inactiveCount++; + if (inactiveCount > 100) + { + GCSettings.LargeObjectHeapCompactionMode = GCLargeObjectHeapCompactionMode.CompactOnce; + GC.Collect(); + while (Form.ActiveForm == null) + { + await Task.Delay(100, cancellationToken).ConfigureAwait(false); //reduce the FPS when the app isn't active (maybe this should be configurable?) + if (context.IsDisposed || dxform.Form.IsDisposed || cancellationToken.IsCancellationRequested) return; //if form closed while sleeping (eg from rightclick on taskbar) + } + } + else + { + await Task.Delay(100, cancellationToken).ConfigureAwait(false); + } + } + else + { + inactiveCount = 0; + if (Form.ActiveForm != dxform.Form) + { + await Task.Delay(25, cancellationToken).ConfigureAwait(false); + } + } + + if (context.IsDisposed || dxform.Form.IsDisposed || cancellationToken.IsCancellationRequested) return; + + Rendering = true; + + if (!await semaphore.WaitAsync(50, cancellationToken).ConfigureAwait(false)) { Console.WriteLine("Failed to get lock for syncroot"); - Thread.Sleep(10); //don't hog CPU when not able to render... + await Task.Delay(10, cancellationToken).ConfigureAwait(false); //don't hog CPU when not able to render... continue; } - bool ok = true; + if (dxform.Form.IsDisposed || cancellationToken.IsCancellationRequested) + { + Rendering = false; + return; + } try { - context.OutputMerger.SetRenderTargets(depthview, targetview); - context.Rasterizer.SetViewport(0, 0, dxform.Form.ClientSize.Width, dxform.Form.ClientSize.Height); - } - catch (Exception ex) - { - MessageBox.Show("Error setting main render target!\n" + ex.ToString()); - ok = false; - } - - if (ok) - { - if (dxform.Form.IsDisposed) - { - Monitor.Exit(syncroot); - Rendering = false; - return; //the form was closed... stop!! - } - - dxform.RenderScene(context); + bool ok = true; try { - swapchain.Present(1, PresentFlags.None); + context.OutputMerger.SetRenderTargets(depthview, targetview); + context.Rasterizer.SetViewport(0, 0, dxform.Form.ClientSize.Width, dxform.Form.ClientSize.Height); } catch (Exception ex) { - MessageBox.Show("Error presenting swap chain!\n" + ex.ToString()); + MessageBox.Show("Error setting main render target!\n" + ex.ToString()); + ok = false; + } + + if (ok) + { + if (dxform.Form.IsDisposed || cancellationToken.IsCancellationRequested) + { + + Rendering = false; + return; //the form was closed... stop!! + } + + await dxform.RenderScene(context); + + try + { + swapchain.Present(1, PresentFlags.None); + } + catch (Exception ex) + { + MessageBox.Show("Error presenting swap chain!\n" + ex.ToString()); + } } } + finally + { + semaphore.Release(); + Rendering = false; + } } - finally - { - Monitor.Exit(syncroot); - Rendering = false; - } + } + catch (TaskCanceledException) { } + catch (Exception ex) + { + Console.WriteLine(ex); + throw; + } + finally + { + Running = false; } } diff --git a/CodeWalker/Rendering/Renderable.cs b/CodeWalker/Rendering/Renderable.cs index 533acd3..3897e7b 100644 --- a/CodeWalker/Rendering/Renderable.cs +++ b/CodeWalker/Rendering/Renderable.cs @@ -10,6 +10,7 @@ using Buffer = SharpDX.Direct3D11.Buffer; using CodeWalker.World; using SharpDX.Direct3D; using SharpDX; +using System.Threading; namespace CodeWalker.Rendering { @@ -72,6 +73,12 @@ namespace CodeWalker.Rendering public RenderableModel[] LowModels; public RenderableModel[] VlowModels; public RenderableModel[] AllModels; + + public float LodDistanceHigh; + public float LodDistanceMed; + public float LodDistanceLow; + public float LodDistanceVLow; + //public Dictionary TextureDict { get; private set; } //public long EmbeddedTextureSize { get; private set; } @@ -148,6 +155,10 @@ namespace CodeWalker.Rendering curmodel += vlow.Length; } + LodDistanceHigh = drawable.LodDistHigh; + LodDistanceMed = drawable.LodDistMed; + LodDistanceLow = drawable.LodDistLow; + LodDistanceVLow = drawable.LodDistVlow; //var sg = Drawable.ShaderGroup; //if ((sg != null) && (sg.TextureDictionary != null)) @@ -346,6 +357,30 @@ namespace CodeWalker.Rendering Lights = rlights; } + public RenderableModel[] GetModels(float distance) + { + if (distance > LodDistanceVLow) + { + return Array.Empty(); + } + else if (distance > LodDistanceLow) + { + return VlowModels ?? Array.Empty(); + } + else if (distance > LodDistanceMed) + { + return LowModels ?? Array.Empty(); + } + else if (distance > LodDistanceHigh) + { + return MedModels ?? Array.Empty(); + } + else + { + return HDModels; + } + } + private RenderableModel InitModel(DrawableModel dm) { var rmodel = new RenderableModel(); @@ -723,7 +758,7 @@ namespace CodeWalker.Rendering } else { - foreach (var model in HDModels) //TODO: figure out which models/geometries this should be applying to! + foreach (var model in AllModels) //TODO: figure out which models/geometries this should be applying to! { if (model == null) continue; foreach (var geom in model.Geometries) diff --git a/CodeWalker/Rendering/RenderableCache.cs b/CodeWalker/Rendering/RenderableCache.cs index 4b63cab..0058702 100644 --- a/CodeWalker/Rendering/RenderableCache.cs +++ b/CodeWalker/Rendering/RenderableCache.cs @@ -11,6 +11,7 @@ using System.Collections.Concurrent; using CodeWalker.GameFiles; using System.Threading; using CodeWalker.Properties; +using System.Diagnostics; namespace CodeWalker.Rendering { @@ -20,7 +21,7 @@ namespace CodeWalker.Rendering public DateTime LastUnload = DateTime.UtcNow; public double CacheTime = Settings.Default.GPUCacheTime;// 10.0; //seconds to keep something that's not used public double UnloadTime = Settings.Default.GPUCacheFlushTime;// 0.1; //seconds between running unload cycles - public int MaxItemsPerLoop = 1; //to keep things flowing + public int MaxItemsPerLoop = 3; //to keep things flowing public long TotalGraphicsMemoryUse { @@ -117,6 +118,11 @@ namespace CodeWalker.Rendering { currentDevice = null; + Clear(); + } + + public void Clear() + { renderables.Clear(); textures.Clear(); boundcomps.Clear(); @@ -132,56 +138,65 @@ namespace CodeWalker.Rendering if (currentDevice == null) return false; //can't do anything with no device Monitor.Enter(updateSyncRoot); - - - //load the queued items if possible - int renderablecount = renderables.LoadProc(currentDevice, MaxItemsPerLoop); - int texturecount = textures.LoadProc(currentDevice, MaxItemsPerLoop); - int boundcompcount = boundcomps.LoadProc(currentDevice, MaxItemsPerLoop); - int instbatchcount = instbatches.LoadProc(currentDevice, MaxItemsPerLoop); - int lodlightcount = lodlights.LoadProc(currentDevice, MaxItemsPerLoop); - int distlodlightcount = distlodlights.LoadProc(currentDevice, MaxItemsPerLoop); - int pathbatchcount = pathbatches.LoadProc(currentDevice, MaxItemsPerLoop); - int waterquadcount = waterquads.LoadProc(currentDevice, MaxItemsPerLoop); - - - bool itemsStillPending = - (renderablecount >= MaxItemsPerLoop) || - (texturecount >= MaxItemsPerLoop) || - (boundcompcount >= MaxItemsPerLoop) || - (instbatchcount >= MaxItemsPerLoop) || - (lodlightcount >= MaxItemsPerLoop) || - (distlodlightcount >= MaxItemsPerLoop) || - (pathbatchcount >= MaxItemsPerLoop) || - (waterquadcount >= MaxItemsPerLoop); - - - //todo: change this to unload only when necessary (ie when something is loaded) - var now = DateTime.UtcNow; - var deltat = (now - LastUpdate).TotalSeconds; - var unloadt = (now - LastUnload).TotalSeconds; - if ((unloadt > UnloadTime) && (deltat < 0.25)) //don't try the unload on every loop... or when really busy + try { + //load the queued items if possible + int renderablecount = renderables.LoadProc(currentDevice, MaxItemsPerLoop); + int texturecount = textures.LoadProc(currentDevice, MaxItemsPerLoop); + int boundcompcount = boundcomps.LoadProc(currentDevice, MaxItemsPerLoop); + int instbatchcount = instbatches.LoadProc(currentDevice, MaxItemsPerLoop); + int lodlightcount = lodlights.LoadProc(currentDevice, MaxItemsPerLoop); + int distlodlightcount = distlodlights.LoadProc(currentDevice, MaxItemsPerLoop); + int pathbatchcount = pathbatches.LoadProc(currentDevice, MaxItemsPerLoop); + int waterquadcount = waterquads.LoadProc(currentDevice, MaxItemsPerLoop); - //unload items that haven't been used in longer than the cache period. - renderables.UnloadProc(); - textures.UnloadProc(); - boundcomps.UnloadProc(); - instbatches.UnloadProc(); - lodlights.UnloadProc(); - distlodlights.UnloadProc(); - pathbatches.UnloadProc(); - waterquads.UnloadProc(); - LastUnload = DateTime.UtcNow; + bool itemsStillPending = + (renderablecount >= MaxItemsPerLoop) || + (texturecount >= MaxItemsPerLoop) || + (boundcompcount >= MaxItemsPerLoop) || + (instbatchcount >= MaxItemsPerLoop) || + (lodlightcount >= MaxItemsPerLoop) || + (distlodlightcount >= MaxItemsPerLoop) || + (pathbatchcount >= MaxItemsPerLoop) || + (waterquadcount >= MaxItemsPerLoop); + + + //todo: change this to unload only when necessary (ie when something is loaded) + var now = DateTime.UtcNow; + var deltat = (now - LastUpdate).TotalSeconds; + var unloadt = (now - LastUnload).TotalSeconds; + //Console.WriteLine($"deltat: {deltat}; unloadt: {unloadt}; UnloadTime: {UnloadTime};"); + if ((unloadt > UnloadTime) && (deltat < 1.0)) //don't try the unload on every loop... or when really busy + { + + //unload items that haven't been used in longer than the cache period. + renderables.UnloadProc(); + textures.UnloadProc(); + boundcomps.UnloadProc(); + instbatches.UnloadProc(); + lodlights.UnloadProc(); + distlodlights.UnloadProc(); + pathbatches.UnloadProc(); + waterquads.UnloadProc(); + + LastUnload = DateTime.UtcNow; + } + + + LastUpdate = DateTime.UtcNow; + + return itemsStillPending; + } + catch(Exception ex) + { + Console.WriteLine(ex); + return true; + } + finally + { + Monitor.Exit(updateSyncRoot); } - - - LastUpdate = DateTime.UtcNow; - - Monitor.Exit(updateSyncRoot); - - return itemsStillPending; } public void RenderThreadSync() @@ -255,13 +270,20 @@ namespace CodeWalker.Rendering } + [Flags] + public enum LoadFlags + { + None = 0, + IsLoaded = 1, + LoadQueued = 2, + } public abstract class RenderableCacheItem { public TKey Key; public volatile bool IsLoaded = false; public volatile bool LoadQueued = false; - public long LastUseTime = 0; + public Stopwatch LastUseTime = Stopwatch.StartNew(); //public DateTime LastUseTime { get; set; } public long DataSize { get; set; } @@ -333,26 +355,39 @@ namespace CodeWalker.Rendering LoadedCount = 0; while (itemsToLoad.TryDequeue(out item)) { - if (item.IsLoaded) continue; //don't load it again... - LoadedCount++; - long gcachefree = CacheLimit - Interlocked.Read(ref CacheUse);// CacheUse; - if (gcachefree > item.DataSize) + if (item.IsLoaded) { - try + item.LoadQueued = false; + continue; + } + + try + { + LoadedCount++; + long gcachefree = CacheLimit - Interlocked.Read(ref CacheUse);// CacheUse; + if (gcachefree > item.DataSize) { - item.Load(device); - loadeditems.AddLast(item); - Interlocked.Add(ref CacheUse, item.DataSize); - } - catch //(Exception ex) - { - //todo: error handling... + try + { + item.Load(device); + loadeditems.AddLast(item); + Interlocked.Add(ref CacheUse, item.DataSize); + } + catch (Exception ex) + { + Console.WriteLine(ex); + } } } - else + catch(Exception ex) { - item.LoadQueued = false; //can try load it again later.. + Console.WriteLine(ex); } + finally + { + item.LoadQueued = false; + } + if (LoadedCount >= maxitemsperloop) break; } return LoadedCount; @@ -361,12 +396,12 @@ namespace CodeWalker.Rendering public void UnloadProc() { //unload items that haven't been used in longer than the cache period. - var now = DateTime.UtcNow; + //var now = DateTime.UtcNow; var rnode = loadeditems.First; while (rnode != null) { - var lu = DateTime.FromBinary(Interlocked.Read(ref rnode.Value.LastUseTime)); - if ((now - lu).TotalSeconds > CacheTime) + var lastUsed = rnode.Value.LastUseTime.Elapsed; + if (lastUsed.TotalSeconds > CacheTime) { var nextnode = rnode.Next; itemsToUnload.Enqueue(rnode.Value); @@ -399,7 +434,7 @@ namespace CodeWalker.Rendering } while (itemsToUnload.TryDequeue(out item)) { - if ((item.Key != null) && (cacheitems.ContainsKey(item.Key))) + if (item.Key != null && cacheitems.ContainsKey(item.Key)) { cacheitems.Remove(item.Key); } @@ -420,7 +455,7 @@ namespace CodeWalker.Rendering item.Init(key); cacheitems.Add(key, item); } - Interlocked.Exchange(ref item.LastUseTime, LastFrameTime); + item.LastUseTime.Restart(); if ((!item.IsLoaded) && (!item.LoadQueued))// || { item.LoadQueued = true; diff --git a/CodeWalker/Rendering/Renderer.cs b/CodeWalker/Rendering/Renderer.cs index 9c3e6f0..0dd92df 100644 --- a/CodeWalker/Rendering/Renderer.cs +++ b/CodeWalker/Rendering/Renderer.cs @@ -173,7 +173,7 @@ namespace CodeWalker.Rendering public Renderer(DXForm form, GameFileCache cache) { Form = form; - gameFileCache = cache ?? GameFileCacheFactory.Create(); + gameFileCache = cache ?? GameFileCacheFactory.GetInstance(); renderableCache = new RenderableCache(); var s = Settings.Default; @@ -386,8 +386,11 @@ namespace CodeWalker.Rendering var ctc = renderableCache.LoadedTextureCount; var vr = renderableCache.TotalGraphicsMemoryUse + (shaders != null ? shaders.TotalGraphicsMemoryUse : 0); var vram = TextUtil.GetBytesReadable(vr); - - return $"Drawn: {rgc} geom, Loaded: {crc} dr, {ctc} tx, Vram: {vram}, Fps: {fps}"; + var cacheUsage = TextUtil.GetBytesReadable(gameFileCache.MemoryUsage); + var cacheMax = TextUtil.GetBytesReadable(gameFileCache.MaxMemoryUsage); + + + return $"Drawn: {rgc} geom, Loaded: {crc} dr, {ctc} tx, Vram: {vram}, Fps: {fps}, Cache: {cacheUsage}/{cacheMax}"; } @@ -1690,7 +1693,7 @@ namespace CodeWalker.Rendering for (int i = 0; i < frag.Layers.Length; i++) { CloudHatFragLayer layer = frag.Layers[i]; - uint dhash = JenkHash.GenHash(layer.Filename.ToLowerInvariant()); + uint dhash = JenkHash.GenHashLower(layer.Filename); Archetype arch = gameFileCache.GetArchetype(dhash); if (arch == null) { continue; } @@ -1700,8 +1703,10 @@ namespace CodeWalker.Rendering var drw = gameFileCache.TryGetDrawable(arch); var rnd = TryGetRenderable(arch, drw); - if ((rnd == null) || (rnd.IsLoaded == false) || (rnd.AllTexturesLoaded == false)) - { continue; } + if (rnd == null || !rnd.IsLoaded || !rnd.AllTexturesLoaded) + { + continue; + } RenderableInst rinst = new RenderableInst(); @@ -2051,7 +2056,7 @@ namespace CodeWalker.Rendering ent.Distance = dist; - ent.IsVisible = (dist <= loddist); + ent.IsWithinLodDist = (dist <= loddist); ent.ChildrenVisible = (dist <= cloddist) && (ent._CEntityDef.numChildren > 0); @@ -2061,7 +2066,7 @@ namespace CodeWalker.Rendering if ((ent._CEntityDef.lodLevel == rage__eLodType.LODTYPES_DEPTH_ORPHANHD) || (ent._CEntityDef.lodLevel < renderworldMaxLOD)) { - ent.IsVisible = false; + ent.IsWithinLodDist = false; ent.ChildrenVisible = false; } if (ent._CEntityDef.lodLevel == renderworldMaxLOD) @@ -2090,9 +2095,10 @@ namespace CodeWalker.Rendering } private void RenderWorldRecurseAddEntities(YmapEntityDef ent) { - bool hide = ent.ChildrenVisible; - bool force = (ent.Parent != null) && ent.Parent.ChildrenVisible && !hide; - if (force || (ent.IsVisible && !hide)) + bool childrenVisible = ent.ChildrenVisible; + var parentChildrenVisible = ent.Parent?.ChildrenVisible ?? true; + bool force = parentChildrenVisible && !childrenVisible; + if (force || (ent.IsWithinLodDist && !childrenVisible)) { if (ent.Archetype != null) { @@ -2115,7 +2121,7 @@ namespace CodeWalker.Rendering } } - if (ent.IsVisible && ent.ChildrenVisible && (ent.Children != null)) + if (ent.IsWithinLodDist && ent.ChildrenVisible && (ent.Children != null)) { for (int i = 0; i < ent.Children.Length; i++) { @@ -2167,7 +2173,7 @@ namespace CodeWalker.Rendering if (intent?.Archetype == null) continue; //missing archetype... if (!RenderIsEntityFinalRender(intent)) continue; //proxy or something.. - intent.IsVisible = true; + intent.IsWithinLodDist = true; if (!camera.ViewFrustum.ContainsAABBNoClip(ref intent.BBCenter, ref intent.BBExtent)) { @@ -2192,7 +2198,7 @@ namespace CodeWalker.Rendering if (intent?.Archetype == null) continue; //missing archetype... if (!RenderIsEntityFinalRender(intent)) continue; //proxy or something.. - intent.IsVisible = true; + intent.IsWithinLodDist = true; if (!camera.ViewFrustum.ContainsAABBNoClip(ref intent.BBCenter, ref intent.BBExtent)) { @@ -2390,7 +2396,10 @@ namespace CodeWalker.Rendering { var drawable = gameFileCache.TryGetDrawable(arch); rndbl = TryGetRenderable(arch, drawable); - ArchetypeRenderables[arch] = rndbl; + if (rndbl != null && rndbl.IsLoaded) + { + ArchetypeRenderables[arch] = rndbl; + } } if ((rndbl != null) && rndbl.IsLoaded && (rndbl.AllTexturesLoaded || !waitforchildrentoload)) { @@ -2457,7 +2466,10 @@ namespace CodeWalker.Rendering } private bool RenderYmapLOD(YmapFile ymap, YmapEntityDef entity) { - if (!ymap.Loaded) return false; + if (!ymap.Loaded) + { + return false; + } ymap.EnsureChildYmaps(gameFileCache); @@ -3020,32 +3032,34 @@ namespace CodeWalker.Rendering rndbl = TryGetRenderable(arche, drawable); } - if (rndbl != null) + if (rndbl == null || !rndbl.IsLoaded) { - if (animClip != null) + return false; + } + + if (animClip != null) + { + rndbl.ClipMapEntry = animClip; + rndbl.ClipDict = animClip.Clip?.Ycd; + rndbl.HasAnims = true; + } + + + res = RenderRenderable(rndbl, arche, entity); + + + //fragments have extra drawables! need to render those too... TODO: handle fragments properly... + FragDrawable fd = rndbl.Key as FragDrawable; + if (fd != null) + { + var frag = fd.OwnerFragment; + if ((frag != null) && (frag.DrawableCloth != null)) //cloth... { - rndbl.ClipMapEntry = animClip; - rndbl.ClipDict = animClip.Clip?.Ycd; - rndbl.HasAnims = true; - } - - - res = RenderRenderable(rndbl, arche, entity); - - - //fragments have extra drawables! need to render those too... TODO: handle fragments properly... - FragDrawable fd = rndbl.Key as FragDrawable; - if (fd != null) - { - var frag = fd.OwnerFragment; - if ((frag != null) && (frag.DrawableCloth != null)) //cloth... + rndbl = TryGetRenderable(arche, frag.DrawableCloth); + if (rndbl != null && rndbl.IsLoaded) { - rndbl = TryGetRenderable(arche, frag.DrawableCloth); - if (rndbl != null) - { - bool res2 = RenderRenderable(rndbl, arche, entity); - res = res || res2; - } + bool res2 = RenderRenderable(rndbl, arche, entity); + res = res || res2; } } } @@ -3062,7 +3076,7 @@ namespace CodeWalker.Rendering return false; Renderable rndbl = TryGetRenderable(arche, drawable, txdHash, txdExtra, diffOverride); - if (rndbl == null) + if (rndbl == null || !rndbl.IsLoaded) return false; if (animClip != null) @@ -3243,7 +3257,7 @@ namespace CodeWalker.Rendering rginst.Inst.CastShadow = castshadow; - RenderableModel[] models = isselected ? rndbl.AllModels : rndbl.HDModels; + RenderableModel[] models = isselected ? rndbl.HDModels : rndbl.GetModels(distance); for (int mi = 0; mi < models.Length; mi++) { @@ -3568,17 +3582,23 @@ namespace CodeWalker.Rendering MetaHash ahash = arche.Hash; if (ycd.ClipMap.TryGetValue(ahash, out rndbl.ClipMapEntry)) rndbl.HasAnims = true; - foreach (var model in rndbl.HDModels) + var models = rndbl.HDModels; + for (int i = 0; i < models.Length; i++) { - if (model == null) continue; - foreach (var geom in model.Geometries) + var model = models[i]; + if (models[i] == null) + continue; + for (int j = 0; j < model.Geometries.Length; j++) { - if (geom == null) continue; + var geom = model.Geometries[j]; + if (geom == null) + continue; if (geom.globalAnimUVEnable) { uint cmeindex = geom.DrawableGeom.ShaderID + 1u; MetaHash cmehash = ahash + cmeindex; //this goes to at least uv5! (from uv0) - see hw1_09.ycd - if (ycd.ClipMap.TryGetValue(cmehash, out geom.ClipMapEntryUV)) rndbl.HasAnims = true; + if (ycd.ClipMap.TryGetValue(cmehash, out geom.ClipMapEntryUV)) + rndbl.HasAnims = true; } } } @@ -3587,7 +3607,8 @@ namespace CodeWalker.Rendering var extraTexDict = (drawable.Owner as YptFile)?.PtfxList?.TextureDictionary; - if (extraTexDict == null) extraTexDict = txdExtra; + if (extraTexDict == null) + extraTexDict = txdExtra; bool cacheSD = (rndbl.SDtxds == null); bool cacheHD = (renderhdtextures && (rndbl.HDtxds == null)); diff --git a/CodeWalker/Rendering/ShaderManager.cs b/CodeWalker/Rendering/ShaderManager.cs index 09f619a..f3af61d 100644 --- a/CodeWalker/Rendering/ShaderManager.cs +++ b/CodeWalker/Rendering/ShaderManager.cs @@ -851,10 +851,11 @@ namespace CodeWalker.Rendering if (batch.Renderable.HDModels.Length > 1) { } - foreach (var model in batch.Renderable.HDModels) + for(int i = 0; i < batch.Renderable.HDModels.Length; i++) { - if (model.Geometries.Length > 1) - { } + var model = batch.Renderable.HDModels[i]; + if (model.Geometries.Length == 0) + continue; Basic.SetModelVars(context, model); foreach (var geom in model.Geometries) @@ -866,6 +867,7 @@ namespace CodeWalker.Rendering } } } + } diff --git a/CodeWalker/Tools/BinarySearchForm.cs b/CodeWalker/Tools/BinarySearchForm.cs index 5d91493..8405b8f 100644 --- a/CodeWalker/Tools/BinarySearchForm.cs +++ b/CodeWalker/Tools/BinarySearchForm.cs @@ -440,7 +440,7 @@ namespace CodeWalker.Tools curfile++; - if (fentry.Name.EndsWith(".rpf", StringComparison.OrdinalIgnoreCase)) + if (fentry.IsExtension(".rpf")) { continue; } if (onlyexts != null) diff --git a/CodeWalker/Tools/BrowseForm.Designer.cs b/CodeWalker/Tools/BrowseForm.Designer.cs index 6feb768..2da3469 100644 --- a/CodeWalker/Tools/BrowseForm.Designer.cs +++ b/CodeWalker/Tools/BrowseForm.Designer.cs @@ -1,4 +1,6 @@ -namespace CodeWalker.Tools +using CodeWalker.Forms; + +namespace CodeWalker.Tools { partial class BrowseForm { @@ -78,7 +80,7 @@ this.SelTextureMipTrackBar = new System.Windows.Forms.TrackBar(); this.label4 = new System.Windows.Forms.Label(); this.SelTextureNameTextBox = new System.Windows.Forms.TextBox(); - this.SelTexturePictureBox = new System.Windows.Forms.PictureBox(); + this.SelTexturePictureBox = new PixelBox(); this.MainStatusStrip = new System.Windows.Forms.StatusStrip(); this.StatusLabel = new System.Windows.Forms.ToolStripStatusLabel(); this.TestAllButton = new System.Windows.Forms.Button(); diff --git a/CodeWalker/Tools/BrowseForm.cs b/CodeWalker/Tools/BrowseForm.cs index 86e9254..32c9635 100644 --- a/CodeWalker/Tools/BrowseForm.cs +++ b/CodeWalker/Tools/BrowseForm.cs @@ -218,7 +218,7 @@ namespace CodeWalker.Tools { if (entry is RpfFileEntry) { - bool show = !entry.Name.EndsWith(".rpf", StringComparison.OrdinalIgnoreCase); //rpf entries get their own root node.. + bool show = !entry.IsExtension(".rpf"); //rpf entries get their own root node.. if (show) { //string text = entry.Path.Substring(file.Path.Length + 1); //includes \ on the end @@ -234,14 +234,14 @@ namespace CodeWalker.Tools JenkIndex.Ensure(file.Name); foreach (RpfEntry entry in file.AllEntries) { - if (string.IsNullOrEmpty(entry.Name)) continue; - JenkIndex.Ensure(entry.Name); - JenkIndex.Ensure(entry.NameLower); - int ind = entry.Name.LastIndexOf('.'); - if (ind > 0) + if (string.IsNullOrEmpty(entry.Name)) + continue; + + JenkIndex.EnsureBoth(entry.Name); + var shortName = entry.ShortName; + if (shortName != entry.Name) { - JenkIndex.Ensure(entry.Name.Substring(0, ind)); - JenkIndex.Ensure(entry.NameLower.Substring(0, ind)); + JenkIndex.EnsureBoth(shortName); } } @@ -368,43 +368,43 @@ namespace CodeWalker.Tools bool istexdict = false; - if (rfe.NameLower.EndsWith(".ymap")) + if (rfe.IsExtension(".ymap")) { YmapFile ymap = new YmapFile(rfe); ymap.Load(data, rfe); DetailsPropertyGrid.SelectedObject = ymap; } - else if (rfe.NameLower.EndsWith(".ytyp")) + else if (rfe.IsExtension(".ytyp")) { YtypFile ytyp = new YtypFile(); ytyp.Load(data, rfe); DetailsPropertyGrid.SelectedObject = ytyp; } - else if (rfe.NameLower.EndsWith(".ymf")) + else if (rfe.IsExtension(".ymf")) { YmfFile ymf = new YmfFile(); ymf.Load(data, rfe); DetailsPropertyGrid.SelectedObject = ymf; } - else if (rfe.NameLower.EndsWith(".ymt")) + else if (rfe.IsExtension(".ymt")) { YmtFile ymt = new YmtFile(); ymt.Load(data, rfe); DetailsPropertyGrid.SelectedObject = ymt; } - else if (rfe.NameLower.EndsWith(".ybn")) + else if (rfe.IsExtension(".ybn")) { YbnFile ybn = new YbnFile(); ybn.Load(data, rfe); DetailsPropertyGrid.SelectedObject = ybn; } - else if (rfe.NameLower.EndsWith(".fxc")) + else if (rfe.IsExtension(".fxc")) { FxcFile fxc = new FxcFile(); fxc.Load(data, rfe); DetailsPropertyGrid.SelectedObject = fxc; } - else if (rfe.NameLower.EndsWith(".yft")) + else if (rfe.IsExtension(".yft")) { YftFile yft = new YftFile(); yft.Load(data, rfe); @@ -416,7 +416,7 @@ namespace CodeWalker.Tools istexdict = true; } } - else if (rfe.NameLower.EndsWith(".ydr")) + else if (rfe.IsExtension(".ydr")) { YdrFile ydr = new YdrFile(); ydr.Load(data, rfe); @@ -428,14 +428,14 @@ namespace CodeWalker.Tools istexdict = true; } } - else if (rfe.NameLower.EndsWith(".ydd")) + else if (rfe.IsExtension(".ydd")) { YddFile ydd = new YddFile(); ydd.Load(data, rfe); DetailsPropertyGrid.SelectedObject = ydd; //todo: show embedded texdicts in ydd's? is this possible? } - else if (rfe.NameLower.EndsWith(".ytd")) + else if (rfe.IsExtension(".ytd")) { YtdFile ytd = new YtdFile(); ytd.Load(data, rfe); @@ -443,43 +443,43 @@ namespace CodeWalker.Tools ShowTextures(ytd.TextureDict); istexdict = true; } - else if (rfe.NameLower.EndsWith(".ycd")) + else if (rfe.IsExtension(".ycd")) { YcdFile ycd = new YcdFile(); ycd.Load(data, rfe); DetailsPropertyGrid.SelectedObject = ycd; } - else if (rfe.NameLower.EndsWith(".ynd")) + else if (rfe.IsExtension(".ynd")) { YndFile ynd = new YndFile(); ynd.Load(data, rfe); DetailsPropertyGrid.SelectedObject = ynd; } - else if (rfe.NameLower.EndsWith(".ynv")) + else if (rfe.IsExtension(".ynv")) { YnvFile ynv = new YnvFile(); ynv.Load(data, rfe); DetailsPropertyGrid.SelectedObject = ynv; } - else if (rfe.NameLower.EndsWith("_cache_y.dat")) + else if (rfe.Name.EndsWith("_cache_y.dat", StringComparison.OrdinalIgnoreCase)) { CacheDatFile cdf = new CacheDatFile(); cdf.Load(data, rfe); DetailsPropertyGrid.SelectedObject = cdf; } - else if (rfe.NameLower.EndsWith(".rel")) + else if (rfe.IsExtension(".rel")) { RelFile rel = new RelFile(rfe); rel.Load(data, rfe); DetailsPropertyGrid.SelectedObject = rel; } - else if (rfe.NameLower.EndsWith(".gxt2")) + else if (rfe.IsExtension(".gxt2")) { Gxt2File gxt2 = new Gxt2File(); gxt2.Load(data, rfe); DetailsPropertyGrid.SelectedObject = gxt2; } - else if (rfe.NameLower.EndsWith(".pso")) + else if (rfe.IsExtension(".pso")) { JPsoFile pso = new JPsoFile(); pso.Load(data, rfe); @@ -797,14 +797,14 @@ namespace CodeWalker.Tools int max = 500; foreach (RpfFile file in ScannedFiles) { - if (file.NameLower.Contains(find)) + if (file.Name.Contains(find, StringComparison.OrdinalIgnoreCase)) { AddFileNode(file, null); count++; } foreach (RpfEntry entry in file.AllEntries) { - if (entry.NameLower.Contains(find)) + if (entry.Name.Contains(find, StringComparison.OrdinalIgnoreCase)) { if (entry is RpfDirectoryEntry direntry) { @@ -819,7 +819,8 @@ namespace CodeWalker.Tools } else if (entry is RpfBinaryFileEntry) { - if (entry.Name.EndsWith(".rpf", StringComparison.OrdinalIgnoreCase)) continue; + if (entry.IsExtension(".rpf")) + continue; AddEntryNode(entry, null); count++; } @@ -1079,7 +1080,7 @@ namespace CodeWalker.Tools curfile++; - if (fentry.NameLower.EndsWith(".rpf")) + if (fentry.IsExtension(".rpf")) { continue; } if (ignoreexts != null) @@ -1087,7 +1088,7 @@ namespace CodeWalker.Tools bool ignore = false; for (int i = 0; i < ignoreexts.Length; i++) { - if (fentry.NameLower.EndsWith(ignoreexts[i])) + if (fentry.Name.EndsWith(ignoreexts[i], StringComparison.OrdinalIgnoreCase)) { ignore = true; break; diff --git a/CodeWalker/Tools/ExtractRawForm.cs b/CodeWalker/Tools/ExtractRawForm.cs index 3e27c6f..a26cb7c 100644 --- a/CodeWalker/Tools/ExtractRawForm.cs +++ b/CodeWalker/Tools/ExtractRawForm.cs @@ -142,11 +142,11 @@ namespace CodeWalker.Tools bool extract = false; if (endswith) { - extract = entry.NameLower.EndsWith(matchstr); + extract = entry.Name.EndsWith(matchstr, StringComparison.OrdinalIgnoreCase); } else { - extract = entry.NameLower.Contains(matchstr); + extract = entry.Name.Contains(matchstr, StringComparison.OrdinalIgnoreCase); } var fentry = entry as RpfFileEntry; if (fentry == null) diff --git a/CodeWalker/Tools/ExtractShadersForm.cs b/CodeWalker/Tools/ExtractShadersForm.cs index 3121055..a90683f 100644 --- a/CodeWalker/Tools/ExtractShadersForm.cs +++ b/CodeWalker/Tools/ExtractShadersForm.cs @@ -130,7 +130,7 @@ namespace CodeWalker.Tools } try { - if (entry.NameLower.EndsWith(".fxc")) + if (entry.IsExtension(".fxc")) { UpdateExtractStatus(entry.Path); FxcFile fxc = rpfman.GetFile(entry); diff --git a/CodeWalker/Tools/ExtractTexForm.cs b/CodeWalker/Tools/ExtractTexForm.cs index e66c24e..78f7f2b 100644 --- a/CodeWalker/Tools/ExtractTexForm.cs +++ b/CodeWalker/Tools/ExtractTexForm.cs @@ -143,7 +143,7 @@ namespace CodeWalker.Tools } try { - if (bytd && entry.NameLower.EndsWith(".ytd")) + if (bytd && entry.IsExtension(".ytd")) { UpdateExtractStatus(entry.Path); YtdFile ytd = rpfman.GetFile(entry); @@ -156,7 +156,7 @@ namespace CodeWalker.Tools SaveTexture(tex, entry, outputpath); } } - else if (bydr && entry.NameLower.EndsWith(".ydr")) + else if (bydr && entry.IsExtension(".ydr")) { UpdateExtractStatus(entry.Path); YdrFile ydr = rpfman.GetFile(entry); @@ -174,7 +174,7 @@ namespace CodeWalker.Tools } } } - else if (bydd && entry.NameLower.EndsWith(".ydd")) + else if (bydd && entry.IsExtension(".ydd")) { UpdateExtractStatus(entry.Path); YddFile ydd = rpfman.GetFile(entry); @@ -198,7 +198,7 @@ namespace CodeWalker.Tools } } } - else if (byft && entry.NameLower.EndsWith(".yft")) + else if (byft && entry.IsExtension(".yft")) { UpdateExtractStatus(entry.Path); YftFile yft = rpfman.GetFile(entry); diff --git a/CodeWalker/Tools/JenkIndForm.cs b/CodeWalker/Tools/JenkIndForm.cs index 98c8456..c62c896 100644 --- a/CodeWalker/Tools/JenkIndForm.cs +++ b/CodeWalker/Tools/JenkIndForm.cs @@ -36,21 +36,36 @@ namespace CodeWalker.Tools { Task.Run(() => { - GTA5Keys.LoadFromPath(GTAFolder.CurrentGTAFolder, Settings.Default.Key); - GameFileCache gfc = GameFileCacheFactory.Create(); - gfc.DoFullStringIndex = true; - gfc.Init(UpdateStatus, UpdateStatus); - IndexBuildComplete(); + try + { + GTA5Keys.LoadFromPath(GTAFolder.CurrentGTAFolder, Settings.Default.Key); + GameFileCache gfc = GameFileCacheFactory.GetInstance(); + gfc.DoFullStringIndex = true; + gfc.Init(UpdateStatus, UpdateStatus); + IndexBuildComplete(); + } + catch (Exception ex) + { + Console.WriteLine(ex); + throw; + } }); } else { - Task.Run(() => + Task.Run(async () => { - UpdateStatus("Loading strings..."); - gameFileCache.DoFullStringIndex = true; - gameFileCache.InitStringDicts(); - IndexBuildComplete(); + try + { + UpdateStatus("Loading strings..."); + gameFileCache.DoFullStringIndex = true; + await gameFileCache.InitStringDictsAsync(); + IndexBuildComplete(); + } + catch (Exception ex) + { + Console.WriteLine(ex); + } }); } } @@ -204,7 +219,7 @@ namespace CodeWalker.Tools FindHash(); } - private void LoadStringsButton_Click(object sender, EventArgs e) + private async void LoadStringsButton_Click(object sender, EventArgs e) { if (OpenFileDialog.ShowDialog(this) != DialogResult.OK) { @@ -219,22 +234,29 @@ namespace CodeWalker.Tools try { - string txt = File.ReadAllText(file); - string[] lines = txt.Split('\n'); - foreach (string line in lines) + using var stream = File.OpenRead(file); + + using var reader = new StreamReader(stream); + + var lineCount = 0; + + while (!reader.EndOfStream) { - string str = line.Trim(); - if (str.Length > 2) //remove double quotes from start and end, if both present... + var line = await reader.ReadLineAsync(); + line = line.Trim(); + if (line.Length > 2) //remove double quotes from start and end, if both present... { - if ((str[0] == '\"') && (str[str.Length - 1] == '\"')) + if ((line[0] == '\"') && (line[line.Length - 1] == '\"')) { - str = str.Substring(1, str.Length - 2); + line = line.Substring(1, line.Length - 2); } } - var hash = JenkHash.GenHash(str); - extraStrings[hash] = str; + var hash = JenkHash.GenHash(line); + extraStrings[hash] = line; + lineCount++; } - MessageBox.Show(lines.Length.ToString() + " strings imported successfully."); + + MessageBox.Show($"{lineCount} strings imported successfully."); } catch { @@ -243,7 +265,7 @@ namespace CodeWalker.Tools } - private void SaveStringsButton_Click(object sender, EventArgs e) + private async void SaveStringsButton_Click(object sender, EventArgs e) { if (SaveFileDialog.ShowDialog(this) != DialogResult.OK) { @@ -254,11 +276,22 @@ namespace CodeWalker.Tools try { - string[] lines = JenkIndex.GetAllStrings(); + var lines = JenkIndex.GetAllStrings(); - File.WriteAllLines(file, lines); + using var stream = File.OpenWrite(file); - MessageBox.Show(lines.Length.ToString() + " strings exported successfully."); + using var writer = new StreamWriter(stream); + + writer.AutoFlush = false; + + foreach(var line in lines) + { + await writer.WriteLineAsync(line); + } + + await writer.FlushAsync(); + + MessageBox.Show(lines.Count.ToString() + " strings exported successfully."); } catch { diff --git a/CodeWalker/Utils/AudioUtils.cs b/CodeWalker/Utils/AudioUtils.cs index f4872c9..14b96f9 100644 --- a/CodeWalker/Utils/AudioUtils.cs +++ b/CodeWalker/Utils/AudioUtils.cs @@ -288,15 +288,15 @@ namespace CodeWalker.Utils if (entry is RpfFileEntry) { var fentry = entry as RpfFileEntry; - //if (entry.NameLower.EndsWith(".rel")) + //if (entry.Name.EndsWith(".rel", StringComparison.OrdinalIgnoreCase)) //{ // datrels[entry.NameHash] = fentry; //} - if (sounds && entry.NameLower.EndsWith(".dat54.rel")) + if (sounds && entry.Name.EndsWith(".dat54.rel", StringComparison.OrdinalIgnoreCase)) { datrelentries[entry.NameHash] = fentry; } - if (game && entry.NameLower.EndsWith(".dat151.rel")) + if (game && entry.Name.EndsWith(".dat151.rel", StringComparison.OrdinalIgnoreCase)) { datrelentries[entry.NameHash] = fentry; } @@ -311,13 +311,13 @@ namespace CodeWalker.Utils if (entry is RpfFileEntry) { var fentry = entry as RpfFileEntry; - if (entry.Name.EndsWith(".awc", StringComparison.OrdinalIgnoreCase)) + if (entry.IsExtension(".awc")) { var shortname = entry.ShortName; var parentname = entry.Parent?.ShortName ?? ""; if (string.IsNullOrEmpty(parentname) && (entry.Parent?.File != null)) { - parentname = entry.Parent.File.NameLower; + parentname = entry.Parent.File.Name; int ind = parentname.LastIndexOf('.'); if (ind > 0) { diff --git a/CodeWalker/Utils/ConsoleWindow.cs b/CodeWalker/Utils/ConsoleWindow.cs index d9d9602..b24fb1b 100644 --- a/CodeWalker/Utils/ConsoleWindow.cs +++ b/CodeWalker/Utils/ConsoleWindow.cs @@ -1,4 +1,5 @@ -using System; +using CodeWalker.World; +using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; @@ -36,8 +37,7 @@ namespace CodeWalker.Utils { var logFile = Path.Combine(Path.GetDirectoryName(Application.ExecutablePath), "log.log"); //var streamWriter = File.OpenWrite(logFile); - var streamWriter = new StreamWriter(logFile, false); - streamWriter.AutoFlush = true; + var streamWriter = new StreamWriter(logFile, false, Encoding.UTF8, 1024); consoleStream.AddStream(streamWriter); } @@ -116,15 +116,15 @@ namespace CodeWalker.Utils public override void Write(char ch) { - foreach (var writer in _writers) + for (int i = 0; i < _writers.Count; i++) { try { - writer.Write(ch); + _writers[i].Write(ch); } catch (ObjectDisposedException) { - _writers.Remove(writer); + _writers.Remove(_writers[i]); // handle exception here } catch (IOException) @@ -142,6 +142,54 @@ namespace CodeWalker.Utils } } + public override void WriteLine() + { + base.WriteLine(); + Flush(); + } + + public override void WriteLine(string value) + { + for (int i = 0; i < _writers.Count; i++) + { + try + { + _writers[i].WriteLine(value); + _writers[i].Flush(); + } + catch (ObjectDisposedException) + { + _writers.Remove(_writers[i]); + // handle exception here + } + catch (IOException) + { + // handle exception here + } + } + } + + public override void WriteLine(object value) + { + for (int i = 0; i < _writers.Count; i++) + { + try + { + _writers[i].WriteLine(value); + _writers[i].Flush(); + } + catch (ObjectDisposedException) + { + _writers.Remove(_writers[i]); + // handle exception here + } + catch (IOException) + { + // handle exception here + } + } + } + public override void Close() { base.Close(); diff --git a/CodeWalker/Utils/NamedPipe.cs b/CodeWalker/Utils/NamedPipe.cs new file mode 100644 index 0000000..dd91370 --- /dev/null +++ b/CodeWalker/Utils/NamedPipe.cs @@ -0,0 +1,133 @@ +using CodeWalker.Utils; +using System; +using System.Collections.Generic; +using System.IO; +using System.IO.Pipes; +using System.Linq; +using System.Text; +using System.Windows.Forms; + +namespace CodeWalker.Core.Utils +{ + public class NamedPipe + { + private readonly Form form; + + public NamedPipe(Form form) + { + this.form = form; + } + + public async void Init() + { + try + { + using var client = new NamedPipeClientStream("codewalker"); + client.Connect(100); + return; + } + catch (Exception ex) + { + if (ex is not TimeoutException) + { + throw; + } + else + { + StartServer(); + } + } + } + + public async void StartServer() + { + var server = new NamedPipeServerStream("codewalker", PipeDirection.InOut, NamedPipeServerStream.MaxAllowedServerInstances); + + var buffer = new byte[1024]; + + while (true) + { + try + { + await server.WaitForConnectionAsync(); + var read = await server.ReadAsync(buffer.AsMemory()); + if (read > 0) + { + var message = Encoding.UTF8.GetString(buffer, 0, read); + + Console.WriteLine(message); + + var args = message.Split(' '); + if (args[0] == "explorer") + { + form.BeginInvoke(() => + { + var f = new ExploreForm(); + f.Load += (sender, args) => + { + f.WindowState = FormWindowState.Normal; + f.Activate(); + }; + + f.Show(); + }); + } else if (args[0] == "open-file") + { + try + { + var form = OpenAnyFile.OpenFilePath(string.Join(" ", args.AsSpan(1).ToArray())); + form.Show(); + form.Activate(); + } + catch(NotImplementedException ex) + { + Console.WriteLine(ex); + MessageBox.Show("Dit type bestand is op het moment nog niet ondersteund!", ex.ToString()); + } + } + } + } + catch (Exception ex) + { + if (ex is not IOException) + { + throw; + } + server.Disconnect(); + } + + } + } + + public async void SendMessage(string message) + { + var client = new NamedPipeClientStream("codewalker"); + } + + public static bool TrySendMessageToOtherProcess(string message) + { + try + { + using var client = new NamedPipeClientStream("codewalker"); + client.Connect(100); + + var _buffer = Encoding.UTF8.GetBytes(message); + + client.Write(_buffer, 0, _buffer.Length); + client.WaitForPipeDrain(); + + return true; + } + catch (Exception ex) + { + if (ex is TimeoutException) + { + return false; + } + + throw; + } + + } + } +} diff --git a/CodeWalker/Utils/OpenAnyFile.cs b/CodeWalker/Utils/OpenAnyFile.cs new file mode 100644 index 0000000..870b03c --- /dev/null +++ b/CodeWalker/Utils/OpenAnyFile.cs @@ -0,0 +1,76 @@ +using CodeWalker.Forms; +using CodeWalker.GameFiles; +using CodeWalker.Properties; +using CodeWalker; +using System; +using System.Collections.Generic; +using System.Diagnostics.Tracing; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Markup; +using System.IO; +using System.Windows.Forms; + +namespace CodeWalker.Utils +{ + internal class OpenAnyFile + { + public static Form OpenFilePath(string path) + { + var extension = Path.GetExtension(path); + if (extension == ".ydr" || extension == ".ydd" || extension == ".yft" || extension == ".ybn" || extension == ".ypt" || extension == ".ynv") + { + var modelForm = new ModelForm(); + modelForm.Load += new EventHandler(async (sender, eventArgs) => + { + modelForm.ViewModel(path); + modelForm.Activate(); + }); + + return modelForm; + + } + else if (extension == ".ytd") + { + var textureForm = new YtdForm(); + var data = File.ReadAllBytes(path); + var e = RpfFile.CreateFileEntry(Path.GetFileName(path), path, ref data); + var ytdFile = RpfFile.GetFile(e, data); + textureForm.Load += (sender, eventArgs) => + { + textureForm.LoadYtd(ytdFile); + textureForm.Activate(); + }; + return textureForm; + } + else if (extension == ".ymf" || extension == ".ymap" || extension == ".ytyp" || extension == ".ymt") + { + GTA5Keys.LoadFromPath(GTAFolder.CurrentGTAFolder, Settings.Default.Key); + var fileCache = GameFileCacheFactory.GetInstance(); + if (!fileCache.IsInited) + { + fileCache.Init(); + } + + var metaForm = new MetaForm(); + var data = File.ReadAllBytes(path); + var e = RpfFile.CreateFileEntry(Path.GetFileName(path), path, ref data); + PackedFile packedFile = null; + if (extension == ".ymt") packedFile = RpfFile.GetFile(e, data); + if (extension == ".ymap") packedFile = RpfFile.GetFile(e, data); + if (extension == ".ytyp") packedFile = RpfFile.GetFile(e, data); + if (extension == ".ymf") packedFile = RpfFile.GetFile(e, data); + + metaForm.Load += (sender, eventArgs) => + { + metaForm.Activate(); + metaForm.LoadMeta(packedFile); + }; + return metaForm; + } + + throw new NotImplementedException(); + } + } +} diff --git a/CodeWalker/VehicleForm.cs b/CodeWalker/VehicleForm.cs index 22075f2..cda0088 100644 --- a/CodeWalker/VehicleForm.cs +++ b/CodeWalker/VehicleForm.cs @@ -24,13 +24,16 @@ namespace CodeWalker { public Form Form { get { return this; } } //for DXForm/DXManager use - public Renderer Renderer = null; + public Renderer Renderer { get; set; } public object RenderSyncRoot { get { return Renderer.RenderSyncRoot; } } volatile bool formopen = false; volatile bool running = false; volatile bool pauserendering = false; public bool Pauserendering { get; set; } = false; + + public CancellationTokenSource CancellationTokenSource { get; } = new CancellationTokenSource(); + //volatile bool initialised = false; Stopwatch frametimer = new Stopwatch(); @@ -50,7 +53,7 @@ namespace CodeWalker System.Drawing.Point MouseLastPoint; - public GameFileCache GameFileCache { get; } = GameFileCacheFactory.Create(); + public GameFileCache GameFileCache { get; } = GameFileCacheFactory.GetInstance(); InputManager Input = new InputManager(); @@ -141,7 +144,7 @@ namespace CodeWalker formopen = true; - new Thread(new ThreadStart(ContentThread)).Start(); + Task.Run(ContentThread); frametimer.Start(); @@ -159,64 +162,74 @@ namespace CodeWalker count++; } } - public void RenderScene(DeviceContext context) + public async ValueTask RenderScene(DeviceContext context) { float elapsed = (float)frametimer.Elapsed.TotalSeconds; frametimer.Restart(); + if (elapsed < 0.016666) + { + await Task.Delay((int)(0.016666 * elapsed) * 1000); + } + if (Pauserendering) return; GameFileCache.BeginFrame(); if (!Monitor.TryEnter(Renderer.RenderSyncRoot, 50)) - { return; } //couldn't get a lock, try again next time + { + return; + } //couldn't get a lock, try again next time + try + { + UpdateControlInputs(elapsed); + //space.Update(elapsed); - UpdateControlInputs(elapsed); - //space.Update(elapsed); - - Renderer.Update(elapsed, MouseLastPoint.X, MouseLastPoint.Y); + Renderer.Update(elapsed, MouseLastPoint.X, MouseLastPoint.Y); - //UpdateWidgets(); - //BeginMouseHitTest(); + //UpdateWidgets(); + //BeginMouseHitTest(); - Renderer.BeginRender(context); + Renderer.BeginRender(context); - Renderer.RenderSkyAndClouds(); + Renderer.RenderSkyAndClouds(); - Renderer.SelectedDrawable = null;// SelectedItem.Drawable; + Renderer.SelectedDrawable = null;// SelectedItem.Drawable; - RenderVehicle(); + RenderVehicle(); - //UpdateMouseHitsFromRenderer(); - //RenderSelection(); + //UpdateMouseHitsFromRenderer(); + //RenderSelection(); - RenderGrid(context); + RenderGrid(context); - Renderer.RenderQueued(); + Renderer.RenderQueued(); - //Renderer.RenderBounds(MapSelectionMode.Entity); + //Renderer.RenderBounds(MapSelectionMode.Entity); - Renderer.RenderSelectionGeometry(MapSelectionMode.Entity); + Renderer.RenderSelectionGeometry(MapSelectionMode.Entity); - //RenderMoused(); + //RenderMoused(); - Renderer.RenderFinalPass(); + Renderer.RenderFinalPass(); - //RenderMarkers(); - //RenderWidgets(); - - Renderer.EndRender(); - - Monitor.Exit(Renderer.RenderSyncRoot); + //RenderMarkers(); + //RenderWidgets(); + Renderer.EndRender(); + } + finally + { + Monitor.Exit(Renderer.RenderSyncRoot); + } //UpdateMarkerSelectionPanelInvoke(); } public void BuffersResized(int w, int h) @@ -318,31 +331,33 @@ namespace CodeWalker //UpdateStatus("Ready"); - Task.Run(() => { + Task.Run(async () => { while (formopen && !IsDisposed) //renderer content loop { bool rcItemsPending = Renderer.ContentThreadProc(); if (!rcItemsPending) { - Thread.Sleep(1); //sleep if there's nothing to do + await Task.Delay(ActiveForm == null ? 50 : 1).ConfigureAwait(false); } } }); - while (formopen && !IsDisposed) //main asset loop + Task.Run(async () => { - bool fcItemsPending = GameFileCache.ContentThreadProc(); - - if (!fcItemsPending) + while (formopen && !IsDisposed) //main asset loop { - Thread.Sleep(1); //sleep if there's nothing to do + bool fcItemsPending = GameFileCache.ContentThreadProc(); + + if (!fcItemsPending) + { + await Task.Delay(ActiveForm == null ? 50 : 1).ConfigureAwait(false); + } } - } - GameFileCache.Clear(); + running = false; + }); - running = false; } @@ -1424,4 +1439,4 @@ namespace CodeWalker } } } -} +} \ No newline at end of file diff --git a/CodeWalker/World/CutsceneForm.cs b/CodeWalker/World/CutsceneForm.cs index 1720471..7f44b09 100644 --- a/CodeWalker/World/CutsceneForm.cs +++ b/CodeWalker/World/CutsceneForm.cs @@ -301,7 +301,7 @@ namespace CodeWalker.World { foreach (var entry in rpf.AllEntries) { - if (entry.NameLower.EndsWith(".cut")) + if (entry.IsExtension(".cut")) { var dditem = new CutsceneDropdownItem(); dditem.RpfEntry = entry; diff --git a/CodeWalker/World/WorldSearchForm.cs b/CodeWalker/World/WorldSearchForm.cs index 259b7c4..46a7966 100644 --- a/CodeWalker/World/WorldSearchForm.cs +++ b/CodeWalker/World/WorldSearchForm.cs @@ -92,7 +92,7 @@ namespace CodeWalker.World ArchetypeSearchComplete(); return; } - if (entry.NameLower.EndsWith(".ytyp")) + if (entry.Name.EndsWith(".ytyp", StringComparison.OrdinalIgnoreCase)) { ArchetypeSearchUpdateStatus(entry.Path); @@ -334,7 +334,7 @@ namespace CodeWalker.World EntitySearchComplete(); return; } - if (entry.NameLower.EndsWith(".ymap")) + if (entry.Name.EndsWith(".ymap", StringComparison.OrdinalIgnoreCase)) { EntitySearchUpdateStatus(entry.Path); diff --git a/CodeWalker/WorldForm.Designer.cs b/CodeWalker/WorldForm.Designer.cs index 9aa06eb..11e465d 100644 --- a/CodeWalker/WorldForm.Designer.cs +++ b/CodeWalker/WorldForm.Designer.cs @@ -15,11 +15,11 @@ namespace CodeWalker /// true if managed resources should be disposed; otherwise, false. protected override void Dispose(bool disposing) { + base.Dispose(disposing); if (disposing && (components != null)) { components.Dispose(); } - base.Dispose(disposing); } #region Windows Form Designer generated code @@ -1112,7 +1112,7 @@ namespace CodeWalker | System.Windows.Forms.AnchorStyles.Right))); this.SelectionNameTextBox.BackColor = System.Drawing.Color.White; this.SelectionNameTextBox.Location = new System.Drawing.Point(3, 66); - this.SelectionNameTextBox.Name = "SelectionNameTextBox"; + this.SelectionNameTextBox.Name = "SelectionNameText"; this.SelectionNameTextBox.ReadOnly = true; this.SelectionNameTextBox.Size = new System.Drawing.Size(199, 20); this.SelectionNameTextBox.TabIndex = 26; diff --git a/CodeWalker/WorldForm.cs b/CodeWalker/WorldForm.cs index 1c5bf57..752af87 100644 --- a/CodeWalker/WorldForm.cs +++ b/CodeWalker/WorldForm.cs @@ -18,6 +18,8 @@ using CodeWalker.GameFiles; using CodeWalker.Properties; using CodeWalker.Tools; using CodeWalker.WinForms; +using CodeWalker.Core.Utils; +using System.Data; namespace CodeWalker { @@ -25,7 +27,7 @@ namespace CodeWalker { public Form Form { get { return this; } } //for DXForm/DXManager use - public Renderer Renderer = null; + public Renderer Renderer { get; set; } public object RenderSyncRoot { get { return Renderer.RenderSyncRoot; } } volatile bool formopen = false; @@ -47,6 +49,9 @@ namespace CodeWalker Watermaps watermaps = new Watermaps(); AudioZones audiozones = new AudioZones(); + public CancellationTokenSource CancellationTokenSource { get; } = new CancellationTokenSource(); + public CancellationToken CancellationToken; + public Space Space { get { return space; } } bool MouseLButtonDown = false; @@ -65,8 +70,8 @@ namespace CodeWalker Vector3 prevworldpos = new Vector3(0, 0, 100); //also the start pos - public GameFileCache GameFileCache { get { return gameFileCache; } } - GameFileCache gameFileCache = GameFileCacheFactory.Create(); + public GameFileCache GameFileCache { get => gameFileCache; } + GameFileCache gameFileCache = GameFileCacheFactory.GetInstance(); WorldControlMode ControlMode = WorldControlMode.Free; @@ -108,11 +113,6 @@ namespace CodeWalker object markersortedsyncroot = new object(); - - - - - bool rendercollisionmeshes = Settings.Default.ShowCollisionMeshes; List collisionitems = new List(); List collisionybns = new List(); @@ -216,6 +216,7 @@ namespace CodeWalker public WorldForm() { + CancellationToken = CancellationTokenSource.Token; InitializeComponent(); Renderer = new Renderer(this, gameFileCache); @@ -228,6 +229,11 @@ namespace CodeWalker LastMouseHit.WorldForm = this; PrevMouseHit.WorldForm = this; + if (!gameFileCache.IsInited) + { + Task.Run(() => gameFileCache.Init()); + } + initedOk = Renderer.Init(); } @@ -371,7 +377,7 @@ namespace CodeWalker formopen = true; - new Task(ContentThread, TaskCreationOptions.LongRunning).Start(TaskScheduler.Default); + Task.Run(ContentThread); //Task.Run(ContentThread, ContentThread) //new Thread(new ThreadStart(ContentThread)).Start(); @@ -379,6 +385,11 @@ namespace CodeWalker } public void CleanupScene() { + using var _ = new DisposableTimer("CleanupScene"); + if (!CancellationTokenSource.IsCancellationRequested) + { + CancellationTokenSource.Cancel(); + } formopen = false; Renderer.DeviceDestroyed(); @@ -407,88 +418,94 @@ namespace CodeWalker Console.WriteLine("Clearing cache"); gameFileCache.Clear(); gameFileCache.IsInited = true; - GC.Collect(); + //GC.Collect(); } } - public void RenderScene(DeviceContext context) + public async ValueTask RenderScene(DeviceContext context) { float elapsed = (float)frametimer.Elapsed.TotalSeconds; - + + frametimer.Restart(); if (elapsed < 0.016666) { - Thread.Sleep((int)(0.016666 * elapsed) * 1000); + await Task.Delay((int)(0.016666 * elapsed) * 1000, CancellationToken); } - frametimer.Restart(); - if (Pauserendering) return; GameFileCache.BeginFrame(); if (!Monitor.TryEnter(Renderer.RenderSyncRoot, 50)) - { return; } //couldn't get a lock, try again next time - - UpdateControlInputs(elapsed); - - space.Update(elapsed); - - if (CutsceneForm != null) { - CutsceneForm.UpdateAnimation(elapsed); - } + return; + } //couldn't get a lock, try again next time - Renderer.Update(elapsed, MouseLastPoint.X, MouseLastPoint.Y); - - - - UpdateWidgets(); - - BeginMouseHitTest(); - - - - - Renderer.BeginRender(context); - - Renderer.RenderSkyAndClouds(); - - Renderer.SelectedDrawable = SelectedItem.Drawable; - - if (renderworld) + try { - RenderWorld(); + UpdateControlInputs(elapsed); + + space.Update(elapsed); + + if (CutsceneForm != null) + { + CutsceneForm.UpdateAnimation(elapsed); + } + + Renderer.Update(elapsed, MouseLastPoint.X, MouseLastPoint.Y); + + + + UpdateWidgets(); + + BeginMouseHitTest(); + + + + + Renderer.BeginRender(context); + + Renderer.RenderSkyAndClouds(); + + Renderer.SelectedDrawable = SelectedItem.Drawable; + + if (renderworld) + { + RenderWorld(); + } + else if (rendermaps) + { + RenderYmaps(); + } + else + { + RenderSingleItem(); + } + + UpdateMouseHits(); + + RenderSelection(); + + RenderMoused(); + + Renderer.RenderQueued(); + + Renderer.RenderBounds(SelectionMode); + + Renderer.RenderSelectionGeometry(SelectionMode); + + Renderer.RenderFinalPass(); + + RenderMarkers(); + + RenderWidgets(); + + Renderer.EndRender(); } - else if (rendermaps) + finally { - RenderYmaps(); + Monitor.Exit(Renderer.RenderSyncRoot); } - else - { - RenderSingleItem(); - } - - UpdateMouseHits(); - - RenderSelection(); - - RenderMoused(); - - Renderer.RenderQueued(); - - Renderer.RenderBounds(SelectionMode); - - Renderer.RenderSelectionGeometry(SelectionMode); - - Renderer.RenderFinalPass(); - - RenderMarkers(); - - RenderWidgets(); - - Renderer.EndRender(); - - Monitor.Exit(Renderer.RenderSyncRoot); UpdateMarkerSelectionPanelInvoke(); } @@ -725,10 +742,12 @@ namespace CodeWalker Renderer.RenderWorld(renderworldVisibleYmapDict, spaceEnts); - - foreach (var ymap in Renderer.VisibleYmaps) + if (MouseSelectEnabled) { - UpdateMouseHits(ymap); + for (int i = 0; i < Renderer.VisibleYmaps.Count; i++) + { + UpdateMouseHits(Renderer.VisibleYmaps[i]); + } } @@ -1207,7 +1226,9 @@ namespace CodeWalker //immediately render the bounding box of the currently moused entity. if (!MouseSelectEnabled) - { return; } + { + return; + } PrevMouseHit = LastMouseHit; LastMouseHit = CurMouseHit; @@ -2283,8 +2304,10 @@ namespace CodeWalker { Invoke(new Action(() => { SetControlMode(mode); })); } - catch - { } + catch (Exception ex) + { + Console.WriteLine(ex); + } return; } @@ -4094,21 +4117,29 @@ namespace CodeWalker private void ShowProjectForm() { - if (ProjectForm == null) + try { - ProjectForm = new ProjectForm(this); - ProjectForm.Show(this); - ProjectForm.OnWorldSelectionChanged(SelectedItem); // so that the project form isn't stuck on the welcome window. - } - else - { - if (ProjectForm.WindowState == FormWindowState.Minimized) + if (ProjectForm == null) { - ProjectForm.WindowState = FormWindowState.Normal; + ProjectForm = new ProjectForm(this); + ProjectForm.Show(this); + ProjectForm.OnWorldSelectionChanged(SelectedItem); // so that the project form isn't stuck on the welcome window. } - ProjectForm.Focus(); + else + { + if (ProjectForm.WindowState == FormWindowState.Minimized) + { + ProjectForm.WindowState = FormWindowState.Normal; + } + ProjectForm.Focus(); + } + ToolbarProjectWindowButton.Checked = true; } - ToolbarProjectWindowButton.Checked = true; + catch(Exception ex) + { + Console.WriteLine(ex); + } + } public void OnProjectFormClosed() { @@ -4266,99 +4297,142 @@ namespace CodeWalker } - private void ContentThread() + private async void ContentThread() { - Thread.CurrentThread.Name = "WorldForm ContentThread"; - Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId} {Thread.CurrentThread.Name}"); - //main content loading thread. - running = true; - - UpdateStatus("Scanning..."); - try { - GTA5Keys.LoadFromPath(GTAFolder.CurrentGTAFolder, Settings.Default.Key); + Thread.CurrentThread.Name = "WorldForm ContentThread"; + Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId} {Thread.CurrentThread.Name}"); + //main content loading thread. + running = true; - //save the key for later if it's not saved already. not really ideal to have this in this thread - if (string.IsNullOrEmpty(Settings.Default.Key) && (GTA5Keys.PC_AES_KEY != null)) + UpdateStatus("Scanning..."); + + try { - Settings.Default.Key = Convert.ToBase64String(GTA5Keys.PC_AES_KEY); - Settings.Default.Save(); - } - } - catch - { - MessageBox.Show("Keys not found! This shouldn't happen."); - Close(); - return; - } + GTA5Keys.LoadFromPath(GTAFolder.CurrentGTAFolder, Settings.Default.Key); - gameFileCache.UpdateStatus += UpdateStatus; - gameFileCache.ErrorLog += LogError; - if (!gameFileCache.IsInited) - { - gameFileCache.Init(); - } - - UpdateDlcListComboBox(gameFileCache.DlcNameList); - - EnableCacheDependentUI(); - - - - LoadWorld(); - - - - initialised = true; - - EnableDLCModsUI(); - - new Task(() => - { - Thread.CurrentThread.Name = "Renderer ContentThread"; - while (formopen && !IsDisposed) //renderer content loop - { - bool rcItemsPending = Renderer.ContentThreadProc(); - - if (!rcItemsPending) + //save the key for later if it's not saved already. not really ideal to have this in this thread + if (string.IsNullOrEmpty(Settings.Default.Key) && (GTA5Keys.PC_AES_KEY != null)) { - Thread.Sleep(1); //sleep if there's nothing to do + Settings.Default.Key = Convert.ToBase64String(GTA5Keys.PC_AES_KEY); + Settings.Default.Save(); } } - }, TaskCreationOptions.LongRunning).Start(TaskScheduler.Default); - - while (formopen && !IsDisposed) //main asset loop - { - bool fcItemsPending = gameFileCache.ContentThreadProc(); - - if (!fcItemsPending) + catch { - Thread.Sleep(1); //sleep if there's nothing to do + MessageBox.Show("Keys not found! This shouldn't happen."); + Close(); + return; } + + gameFileCache.UpdateStatus += UpdateStatus; + gameFileCache.ErrorLog += LogError; + while (gameFileCache.IsIniting) + { + await Task.Delay(0); + } + if (!gameFileCache.IsInited) + { + gameFileCache.Init(); + } + + UpdateDlcListComboBox(gameFileCache.DlcNameList); + + EnableCacheDependentUI(); + + + + LoadWorld(); + + + + initialised = true; + + EnableDLCModsUI(); + + Task.Run(async () => + { + try + { + while (formopen && !IsDisposed && !CancellationToken.IsCancellationRequested) //renderer content loop + { + bool rcItemsPending = Renderer.ContentThreadProc(); + + if (!rcItemsPending) + { + await Task.Delay(ActiveForm == null ? 50 : 1, CancellationToken).ConfigureAwait(false); + } + } + } + catch (TaskCanceledException) { } + catch (Exception ex) + { + Console.WriteLine(ex); + } + Console.WriteLine("Renderer ContentThread stopped"); + }); + + Task.Run(async () => + { + try + { + while (formopen && !IsDisposed && !CancellationToken.IsCancellationRequested) //main asset loop + { + bool fcItemsPending = gameFileCache.ContentThreadProc(); + + if (!fcItemsPending) + { + await Task.Delay(ActiveForm == null ? 50 : 1, CancellationToken).ConfigureAwait(false); + } + } + } + catch (TaskCanceledException) { } + catch (Exception ex) + { + Console.WriteLine(ex.ToString()); + } + finally + { + running = false; + } + + Console.WriteLine("GameFileCache ContentThread stopped"); + }); + } + catch(Exception ex) + { + Console.WriteLine($"Exception occured in content thread:\n{ex}"); } - - gameFileCache.Clear(); - - running = false; } + private void doUpdateStatus(string text) + { + StatusLabel.Text = text; + } - + private Stopwatch lastStatusUpdate = Stopwatch.StartNew(); + private TimeSpan updateInterval = TimeSpan.FromSeconds(0.05); private void UpdateStatus(string text) { try { + var elapsed = lastStatusUpdate.Elapsed; + if (elapsed < updateInterval) + return; + lastStatusUpdate.Restart(); if (InvokeRequired) { - BeginInvoke(new Action(() => { UpdateStatus(text); })); + BeginInvoke(new Action(() => { doUpdateStatus(text); })); } else { - StatusLabel.Text = text; + doUpdateStatus(text); } } - catch { } + catch(Exception ex) { + Console.WriteLine(ex); + } } private void UpdateMousedLabel(string text) { @@ -4373,7 +4447,10 @@ namespace CodeWalker MousedLabel.Text = text; } } - catch { } + catch (Exception ex) + { + Console.WriteLine(ex); + } } private void UpdateWeatherTypesComboBox(Weather weather) { @@ -4399,7 +4476,10 @@ namespace CodeWalker WeatherRegionComboBox.SelectedIndex = Math.Max(WeatherRegionComboBox.FindString(Settings.Default.Region), 0); } } - catch { } + catch (Exception ex) + { + Console.WriteLine(ex); + } } private void UpdateCloudTypesComboBox(Clouds clouds) { @@ -4427,7 +4507,10 @@ namespace CodeWalker CloudParamComboBox.SelectedIndex = 0; } } - catch { } + catch (Exception ex) + { + Console.WriteLine(ex); + } } private void UpdateDlcListComboBox(List dlcnames) { @@ -4455,7 +4538,7 @@ namespace CodeWalker } } } - catch { } + catch (Exception ex) { Console.WriteLine(ex); } } private void LogError(string text) @@ -4469,10 +4552,11 @@ namespace CodeWalker else { ConsoleTextBox.AppendText(text + "\r\n"); + Console.WriteLine(text); //MessageBox.Show(text); } } - catch { } + catch (Exception ex) { Console.WriteLine(ex); } } @@ -4491,7 +4575,7 @@ namespace CodeWalker UpdateMarkerSelectionPanel(); } } - catch { } + catch (Exception ex) { Console.WriteLine(ex); } } private void UpdateMarkerSelectionPanel() { @@ -4904,7 +4988,7 @@ namespace CodeWalker ToolsMenuJenkInd.Enabled = true; } } - catch { } + catch (Exception ex) { Console.WriteLine(ex); } } private void EnableDLCModsUI() { @@ -4921,7 +5005,7 @@ namespace CodeWalker DlcLevelComboBox.Enabled = true; } } - catch { } + catch (Exception ex) { Console.WriteLine(ex); } } @@ -5840,7 +5924,7 @@ namespace CodeWalker { BeginInvoke(new Action(() => { ShowSubtitle(text, duration); })); } - catch { } + catch (Exception ex) { Console.WriteLine(ex); } return; } @@ -6867,13 +6951,13 @@ namespace CodeWalker private void ToolsMenuRPFBrowser_Click(object sender, EventArgs e) { BrowseForm f = new BrowseForm(); - f.Show(this); + f.Show(); } private void ToolsMenuRPFExplorer_Click(object sender, EventArgs e) { ExploreForm f = new ExploreForm(); - f.Show(this); + f.Show(); } private void ToolsMenuSelectionInfo_Click(object sender, EventArgs e) @@ -6911,13 +6995,13 @@ namespace CodeWalker private void ToolsMenuJenkGen_Click(object sender, EventArgs e) { JenkGenForm f = new JenkGenForm(); - f.Show(this); + f.Show(); } private void ToolsMenuJenkInd_Click(object sender, EventArgs e) { JenkIndForm f = new JenkIndForm(gameFileCache); - f.Show(this); + f.Show(); } private void ToolsMenuExtractScripts_Click(object sender, EventArgs e) diff --git a/Shaders/BasicPS.cso b/Shaders/BasicPS.cso index ef205ed..266b52d 100644 Binary files a/Shaders/BasicPS.cso and b/Shaders/BasicPS.cso differ diff --git a/Shaders/BasicPS_Deferred.cso b/Shaders/BasicPS_Deferred.cso index 0b9bd99..dd7ffd0 100644 Binary files a/Shaders/BasicPS_Deferred.cso and b/Shaders/BasicPS_Deferred.cso differ diff --git a/Shaders/BasicVS_Box.cso b/Shaders/BasicVS_Box.cso index c5a0cfa..88de5a1 100644 Binary files a/Shaders/BasicVS_Box.cso and b/Shaders/BasicVS_Box.cso differ diff --git a/Shaders/BasicVS_Capsule.cso b/Shaders/BasicVS_Capsule.cso index b07db01..8d5830f 100644 Binary files a/Shaders/BasicVS_Capsule.cso and b/Shaders/BasicVS_Capsule.cso differ diff --git a/Shaders/BasicVS_Cylinder.cso b/Shaders/BasicVS_Cylinder.cso index b693b96..b9934be 100644 Binary files a/Shaders/BasicVS_Cylinder.cso and b/Shaders/BasicVS_Cylinder.cso differ diff --git a/Shaders/BasicVS_PBBNCCT.cso b/Shaders/BasicVS_PBBNCCT.cso index fa226fd..263e3ac 100644 Binary files a/Shaders/BasicVS_PBBNCCT.cso and b/Shaders/BasicVS_PBBNCCT.cso differ diff --git a/Shaders/BasicVS_PBBNCCTTX.cso b/Shaders/BasicVS_PBBNCCTTX.cso index b82c4b8..a8281ff 100644 Binary files a/Shaders/BasicVS_PBBNCCTTX.cso and b/Shaders/BasicVS_PBBNCCTTX.cso differ diff --git a/Shaders/BasicVS_PBBNCCTX.cso b/Shaders/BasicVS_PBBNCCTX.cso index 0cfd0d3..b599dfc 100644 Binary files a/Shaders/BasicVS_PBBNCCTX.cso and b/Shaders/BasicVS_PBBNCCTX.cso differ diff --git a/Shaders/BasicVS_PBBNCT.cso b/Shaders/BasicVS_PBBNCT.cso index 15737f4..869717d 100644 Binary files a/Shaders/BasicVS_PBBNCT.cso and b/Shaders/BasicVS_PBBNCT.cso differ diff --git a/Shaders/BasicVS_PBBNCTT.cso b/Shaders/BasicVS_PBBNCTT.cso index 54f52d8..a0d28f4 100644 Binary files a/Shaders/BasicVS_PBBNCTT.cso and b/Shaders/BasicVS_PBBNCTT.cso differ diff --git a/Shaders/BasicVS_PBBNCTTT.cso b/Shaders/BasicVS_PBBNCTTT.cso index 046e698..d122132 100644 Binary files a/Shaders/BasicVS_PBBNCTTT.cso and b/Shaders/BasicVS_PBBNCTTT.cso differ diff --git a/Shaders/BasicVS_PBBNCTTX.cso b/Shaders/BasicVS_PBBNCTTX.cso index 38a2011..bf97c16 100644 Binary files a/Shaders/BasicVS_PBBNCTTX.cso and b/Shaders/BasicVS_PBBNCTTX.cso differ diff --git a/Shaders/BasicVS_PBBNCTX.cso b/Shaders/BasicVS_PBBNCTX.cso index 80c1507..be70565 100644 Binary files a/Shaders/BasicVS_PBBNCTX.cso and b/Shaders/BasicVS_PBBNCTX.cso differ diff --git a/Shaders/BasicVS_PNCCT.cso b/Shaders/BasicVS_PNCCT.cso index 9c0a731..c54e405 100644 Binary files a/Shaders/BasicVS_PNCCT.cso and b/Shaders/BasicVS_PNCCT.cso differ diff --git a/Shaders/BasicVS_PNCCTT.cso b/Shaders/BasicVS_PNCCTT.cso index 777600f..dcbaf37 100644 Binary files a/Shaders/BasicVS_PNCCTT.cso and b/Shaders/BasicVS_PNCCTT.cso differ diff --git a/Shaders/BasicVS_PNCCTTT.cso b/Shaders/BasicVS_PNCCTTT.cso index 7289c49..16342c8 100644 Binary files a/Shaders/BasicVS_PNCCTTT.cso and b/Shaders/BasicVS_PNCCTTT.cso differ diff --git a/Shaders/BasicVS_PNCCTTTX.cso b/Shaders/BasicVS_PNCCTTTX.cso index e8dc850..b82fe3b 100644 Binary files a/Shaders/BasicVS_PNCCTTTX.cso and b/Shaders/BasicVS_PNCCTTTX.cso differ diff --git a/Shaders/BasicVS_PNCCTTX.cso b/Shaders/BasicVS_PNCCTTX.cso index 79f006f..ebf7ff0 100644 Binary files a/Shaders/BasicVS_PNCCTTX.cso and b/Shaders/BasicVS_PNCCTTX.cso differ diff --git a/Shaders/BasicVS_PNCCTX.cso b/Shaders/BasicVS_PNCCTX.cso index 7d29afd..1766649 100644 Binary files a/Shaders/BasicVS_PNCCTX.cso and b/Shaders/BasicVS_PNCCTX.cso differ diff --git a/Shaders/BasicVS_PNCT.cso b/Shaders/BasicVS_PNCT.cso index b2aa00a..8d98aac 100644 Binary files a/Shaders/BasicVS_PNCT.cso and b/Shaders/BasicVS_PNCT.cso differ diff --git a/Shaders/BasicVS_PNCTT.cso b/Shaders/BasicVS_PNCTT.cso index a342127..ae93506 100644 Binary files a/Shaders/BasicVS_PNCTT.cso and b/Shaders/BasicVS_PNCTT.cso differ diff --git a/Shaders/BasicVS_PNCTTT.cso b/Shaders/BasicVS_PNCTTT.cso index bff38e9..3f02917 100644 Binary files a/Shaders/BasicVS_PNCTTT.cso and b/Shaders/BasicVS_PNCTTT.cso differ diff --git a/Shaders/BasicVS_PNCTTTX.cso b/Shaders/BasicVS_PNCTTTX.cso index b22a3d6..dfa7e88 100644 Binary files a/Shaders/BasicVS_PNCTTTX.cso and b/Shaders/BasicVS_PNCTTTX.cso differ diff --git a/Shaders/BasicVS_PNCTTX.cso b/Shaders/BasicVS_PNCTTX.cso index 193005f..749efcd 100644 Binary files a/Shaders/BasicVS_PNCTTX.cso and b/Shaders/BasicVS_PNCTTX.cso differ diff --git a/Shaders/BasicVS_PNCTX.cso b/Shaders/BasicVS_PNCTX.cso index 1dfa051..887657e 100644 Binary files a/Shaders/BasicVS_PNCTX.cso and b/Shaders/BasicVS_PNCTX.cso differ diff --git a/Shaders/BasicVS_Sphere.cso b/Shaders/BasicVS_Sphere.cso index ba0108c..c02ae1b 100644 Binary files a/Shaders/BasicVS_Sphere.cso and b/Shaders/BasicVS_Sphere.cso differ diff --git a/Shaders/BoundingBoxVS.cso b/Shaders/BoundingBoxVS.cso index 4b94869..15ffed5 100644 Binary files a/Shaders/BoundingBoxVS.cso and b/Shaders/BoundingBoxVS.cso differ diff --git a/Shaders/BoundingSphereVS.cso b/Shaders/BoundingSphereVS.cso index bf60bfa..d2e6dab 100644 Binary files a/Shaders/BoundingSphereVS.cso and b/Shaders/BoundingSphereVS.cso differ diff --git a/Shaders/BoundsPS.cso b/Shaders/BoundsPS.cso index 96086ce..3c4ad34 100644 Binary files a/Shaders/BoundsPS.cso and b/Shaders/BoundsPS.cso differ diff --git a/Shaders/CablePS.cso b/Shaders/CablePS.cso index 3a91124..4793090 100644 Binary files a/Shaders/CablePS.cso and b/Shaders/CablePS.cso differ diff --git a/Shaders/CablePS_Deferred.cso b/Shaders/CablePS_Deferred.cso index 8cbf15d..c1c91ae 100644 Binary files a/Shaders/CablePS_Deferred.cso and b/Shaders/CablePS_Deferred.cso differ diff --git a/Shaders/CableVS.cso b/Shaders/CableVS.cso index 2e02183..0f93b2a 100644 Binary files a/Shaders/CableVS.cso and b/Shaders/CableVS.cso differ diff --git a/Shaders/CloudsPS.cso b/Shaders/CloudsPS.cso index d526e13..66e1549 100644 Binary files a/Shaders/CloudsPS.cso and b/Shaders/CloudsPS.cso differ diff --git a/Shaders/CloudsVS.cso b/Shaders/CloudsVS.cso index 0cce054..b835dec 100644 Binary files a/Shaders/CloudsVS.cso and b/Shaders/CloudsVS.cso differ diff --git a/Shaders/DirLightPS.cso b/Shaders/DirLightPS.cso index 006452e..9accb64 100644 Binary files a/Shaders/DirLightPS.cso and b/Shaders/DirLightPS.cso differ diff --git a/Shaders/DirLightPS_MS.cso b/Shaders/DirLightPS_MS.cso index 76cd692..40884c6 100644 Binary files a/Shaders/DirLightPS_MS.cso and b/Shaders/DirLightPS_MS.cso differ diff --git a/Shaders/DirLightVS.cso b/Shaders/DirLightVS.cso index 13c5d07..90659d2 100644 Binary files a/Shaders/DirLightVS.cso and b/Shaders/DirLightVS.cso differ diff --git a/Shaders/DistantLightsPS.cso b/Shaders/DistantLightsPS.cso index 89c8d02..14a55b3 100644 Binary files a/Shaders/DistantLightsPS.cso and b/Shaders/DistantLightsPS.cso differ diff --git a/Shaders/DistantLightsVS.cso b/Shaders/DistantLightsVS.cso index 91be646..3c51c1e 100644 Binary files a/Shaders/DistantLightsVS.cso and b/Shaders/DistantLightsVS.cso differ diff --git a/Shaders/LightPS.cso b/Shaders/LightPS.cso index 519eeeb..0f0d80f 100644 Binary files a/Shaders/LightPS.cso and b/Shaders/LightPS.cso differ diff --git a/Shaders/LightPS_MS.cso b/Shaders/LightPS_MS.cso index b7e8b4d..2cfa88c 100644 Binary files a/Shaders/LightPS_MS.cso and b/Shaders/LightPS_MS.cso differ diff --git a/Shaders/LightVS.cso b/Shaders/LightVS.cso index b6e8e06..c8d42a6 100644 Binary files a/Shaders/LightVS.cso and b/Shaders/LightVS.cso differ diff --git a/Shaders/LodLightsPS.cso b/Shaders/LodLightsPS.cso index 369bd72..220072d 100644 Binary files a/Shaders/LodLightsPS.cso and b/Shaders/LodLightsPS.cso differ diff --git a/Shaders/LodLightsPS_MS.cso b/Shaders/LodLightsPS_MS.cso index 96fd048..ae20c04 100644 Binary files a/Shaders/LodLightsPS_MS.cso and b/Shaders/LodLightsPS_MS.cso differ diff --git a/Shaders/LodLightsVS.cso b/Shaders/LodLightsVS.cso index d8a4fde..16c1341 100644 Binary files a/Shaders/LodLightsVS.cso and b/Shaders/LodLightsVS.cso differ diff --git a/Shaders/MarkerPS.cso b/Shaders/MarkerPS.cso index 80f0dce..0c8f856 100644 Binary files a/Shaders/MarkerPS.cso and b/Shaders/MarkerPS.cso differ diff --git a/Shaders/MarkerVS.cso b/Shaders/MarkerVS.cso index cb158aa..23f7f3a 100644 Binary files a/Shaders/MarkerVS.cso and b/Shaders/MarkerVS.cso differ diff --git a/Shaders/PPBloomFilterBPHCS.cso b/Shaders/PPBloomFilterBPHCS.cso index cd52234..4d92d58 100644 Binary files a/Shaders/PPBloomFilterBPHCS.cso and b/Shaders/PPBloomFilterBPHCS.cso differ diff --git a/Shaders/PPBloomFilterVCS.cso b/Shaders/PPBloomFilterVCS.cso index c0f2355..b2f529e 100644 Binary files a/Shaders/PPBloomFilterVCS.cso and b/Shaders/PPBloomFilterVCS.cso differ diff --git a/Shaders/PPCopyPixelsPS.cso b/Shaders/PPCopyPixelsPS.cso index c026d50..cf9474f 100644 Binary files a/Shaders/PPCopyPixelsPS.cso and b/Shaders/PPCopyPixelsPS.cso differ diff --git a/Shaders/PPFinalPassPS.cso b/Shaders/PPFinalPassPS.cso index 032e016..1fe266d 100644 Binary files a/Shaders/PPFinalPassPS.cso and b/Shaders/PPFinalPassPS.cso differ diff --git a/Shaders/PPFinalPassVS.cso b/Shaders/PPFinalPassVS.cso index b9e0926..693aa95 100644 Binary files a/Shaders/PPFinalPassVS.cso and b/Shaders/PPFinalPassVS.cso differ diff --git a/Shaders/PPLumBlendCS.cso b/Shaders/PPLumBlendCS.cso index 7572d2e..c0918ad 100644 Binary files a/Shaders/PPLumBlendCS.cso and b/Shaders/PPLumBlendCS.cso differ diff --git a/Shaders/PPReduceTo0DCS.cso b/Shaders/PPReduceTo0DCS.cso index 61b98e6..f26d3b5 100644 Binary files a/Shaders/PPReduceTo0DCS.cso and b/Shaders/PPReduceTo0DCS.cso differ diff --git a/Shaders/PPReduceTo1DCS.cso b/Shaders/PPReduceTo1DCS.cso index 3a33ed7..c84bc72 100644 Binary files a/Shaders/PPReduceTo1DCS.cso and b/Shaders/PPReduceTo1DCS.cso differ diff --git a/Shaders/PPSSAAPS.cso b/Shaders/PPSSAAPS.cso index 6a81c68..96e450a 100644 Binary files a/Shaders/PPSSAAPS.cso and b/Shaders/PPSSAAPS.cso differ diff --git a/Shaders/PathBoxPS.cso b/Shaders/PathBoxPS.cso index ac92ac8..9d729ea 100644 Binary files a/Shaders/PathBoxPS.cso and b/Shaders/PathBoxPS.cso differ diff --git a/Shaders/PathBoxVS.cso b/Shaders/PathBoxVS.cso index da08c2a..2f93a0e 100644 Binary files a/Shaders/PathBoxVS.cso and b/Shaders/PathBoxVS.cso differ diff --git a/Shaders/PathDynVS.cso b/Shaders/PathDynVS.cso index 4a82df6..c95932f 100644 Binary files a/Shaders/PathDynVS.cso and b/Shaders/PathDynVS.cso differ diff --git a/Shaders/PathPS.cso b/Shaders/PathPS.cso index 496c54e..a073089 100644 Binary files a/Shaders/PathPS.cso and b/Shaders/PathPS.cso differ diff --git a/Shaders/PathVS.cso b/Shaders/PathVS.cso index e0433d6..c0fe1e2 100644 Binary files a/Shaders/PathVS.cso and b/Shaders/PathVS.cso differ diff --git a/Shaders/ShadowPS.cso b/Shaders/ShadowPS.cso index ddff484..8b046e5 100644 Binary files a/Shaders/ShadowPS.cso and b/Shaders/ShadowPS.cso differ diff --git a/Shaders/ShadowVS.cso b/Shaders/ShadowVS.cso index 9fe2822..f15cfe4 100644 Binary files a/Shaders/ShadowVS.cso and b/Shaders/ShadowVS.cso differ diff --git a/Shaders/ShadowVS_Skin.cso b/Shaders/ShadowVS_Skin.cso index b401516..2e76539 100644 Binary files a/Shaders/ShadowVS_Skin.cso and b/Shaders/ShadowVS_Skin.cso differ diff --git a/Shaders/SkyMoonPS.cso b/Shaders/SkyMoonPS.cso index 0ae3714..2d0e2c8 100644 Binary files a/Shaders/SkyMoonPS.cso and b/Shaders/SkyMoonPS.cso differ diff --git a/Shaders/SkyMoonVS.cso b/Shaders/SkyMoonVS.cso index 497ee91..4d5bb70 100644 Binary files a/Shaders/SkyMoonVS.cso and b/Shaders/SkyMoonVS.cso differ diff --git a/Shaders/SkySunPS.cso b/Shaders/SkySunPS.cso index 5891ee0..bc40e67 100644 Binary files a/Shaders/SkySunPS.cso and b/Shaders/SkySunPS.cso differ diff --git a/Shaders/SkySunVS.cso b/Shaders/SkySunVS.cso index 61a1ce6..09fb786 100644 Binary files a/Shaders/SkySunVS.cso and b/Shaders/SkySunVS.cso differ diff --git a/Shaders/SkydomePS.cso b/Shaders/SkydomePS.cso index 4b3572c..402ba7b 100644 Binary files a/Shaders/SkydomePS.cso and b/Shaders/SkydomePS.cso differ diff --git a/Shaders/SkydomeVS.cso b/Shaders/SkydomeVS.cso index 19b9389..eac8abe 100644 Binary files a/Shaders/SkydomeVS.cso and b/Shaders/SkydomeVS.cso differ diff --git a/Shaders/TerrainPS.cso b/Shaders/TerrainPS.cso index 45884dd..4629d7d 100644 Binary files a/Shaders/TerrainPS.cso and b/Shaders/TerrainPS.cso differ diff --git a/Shaders/TerrainPS_Deferred.cso b/Shaders/TerrainPS_Deferred.cso index 58d7b1a..b007a86 100644 Binary files a/Shaders/TerrainPS_Deferred.cso and b/Shaders/TerrainPS_Deferred.cso differ diff --git a/Shaders/TerrainVS_PNCCT.cso b/Shaders/TerrainVS_PNCCT.cso index 54f115f..97924d3 100644 Binary files a/Shaders/TerrainVS_PNCCT.cso and b/Shaders/TerrainVS_PNCCT.cso differ diff --git a/Shaders/TerrainVS_PNCCTT.cso b/Shaders/TerrainVS_PNCCTT.cso index 9c921c5..6e41f29 100644 Binary files a/Shaders/TerrainVS_PNCCTT.cso and b/Shaders/TerrainVS_PNCCTT.cso differ diff --git a/Shaders/TerrainVS_PNCCTTTX.cso b/Shaders/TerrainVS_PNCCTTTX.cso index ba3826d..2ad21bc 100644 Binary files a/Shaders/TerrainVS_PNCCTTTX.cso and b/Shaders/TerrainVS_PNCCTTTX.cso differ diff --git a/Shaders/TerrainVS_PNCCTTX.cso b/Shaders/TerrainVS_PNCCTTX.cso index 893bc0b..77641ed 100644 Binary files a/Shaders/TerrainVS_PNCCTTX.cso and b/Shaders/TerrainVS_PNCCTTX.cso differ diff --git a/Shaders/TerrainVS_PNCCTX.cso b/Shaders/TerrainVS_PNCCTX.cso index 89599e4..9639d3b 100644 Binary files a/Shaders/TerrainVS_PNCCTX.cso and b/Shaders/TerrainVS_PNCCTX.cso differ diff --git a/Shaders/TerrainVS_PNCTTTX.cso b/Shaders/TerrainVS_PNCTTTX.cso index b7a1256..293ade2 100644 Binary files a/Shaders/TerrainVS_PNCTTTX.cso and b/Shaders/TerrainVS_PNCTTTX.cso differ diff --git a/Shaders/TerrainVS_PNCTTX.cso b/Shaders/TerrainVS_PNCTTX.cso index 3462b28..ae60076 100644 Binary files a/Shaders/TerrainVS_PNCTTX.cso and b/Shaders/TerrainVS_PNCTTX.cso differ diff --git a/Shaders/TreesLodPS.cso b/Shaders/TreesLodPS.cso index 7166909..7ec8c6e 100644 Binary files a/Shaders/TreesLodPS.cso and b/Shaders/TreesLodPS.cso differ diff --git a/Shaders/TreesLodPS_Deferred.cso b/Shaders/TreesLodPS_Deferred.cso index da19e76..679f296 100644 Binary files a/Shaders/TreesLodPS_Deferred.cso and b/Shaders/TreesLodPS_Deferred.cso differ diff --git a/Shaders/TreesLodVS.cso b/Shaders/TreesLodVS.cso index 72484c6..d3b15fa 100644 Binary files a/Shaders/TreesLodVS.cso and b/Shaders/TreesLodVS.cso differ diff --git a/Shaders/WaterPS.cso b/Shaders/WaterPS.cso index f3e3dca..3257588 100644 Binary files a/Shaders/WaterPS.cso and b/Shaders/WaterPS.cso differ diff --git a/Shaders/WaterPS_Deferred.cso b/Shaders/WaterPS_Deferred.cso index 330dbad..43c391c 100644 Binary files a/Shaders/WaterPS_Deferred.cso and b/Shaders/WaterPS_Deferred.cso differ diff --git a/Shaders/WaterVS_PCT.cso b/Shaders/WaterVS_PCT.cso index 86ad351..93720ef 100644 Binary files a/Shaders/WaterVS_PCT.cso and b/Shaders/WaterVS_PCT.cso differ diff --git a/Shaders/WaterVS_PNCT.cso b/Shaders/WaterVS_PNCT.cso index 671e589..0f8a8fa 100644 Binary files a/Shaders/WaterVS_PNCT.cso and b/Shaders/WaterVS_PNCT.cso differ diff --git a/Shaders/WaterVS_PNCTX.cso b/Shaders/WaterVS_PNCTX.cso index 3ddf8ed..e088fef 100644 Binary files a/Shaders/WaterVS_PNCTX.cso and b/Shaders/WaterVS_PNCTX.cso differ diff --git a/Shaders/WaterVS_PT.cso b/Shaders/WaterVS_PT.cso index 6ec3f6b..8c14e8c 100644 Binary files a/Shaders/WaterVS_PT.cso and b/Shaders/WaterVS_PT.cso differ diff --git a/Shaders/WidgetPS.cso b/Shaders/WidgetPS.cso index fd29998..82b5cde 100644 Binary files a/Shaders/WidgetPS.cso and b/Shaders/WidgetPS.cso differ diff --git a/Shaders/WidgetVS.cso b/Shaders/WidgetVS.cso index 69042cc..95db8a5 100644 Binary files a/Shaders/WidgetVS.cso and b/Shaders/WidgetVS.cso differ