diff --git a/.gitignore b/.gitignore index bb079fa..50fe9ad 100644 --- a/.gitignore +++ b/.gitignore @@ -241,3 +241,4 @@ ModelManifest.xml # FAKE - F# Make .fake/ /Toolbar/cammodes.psd +Shaders \ No newline at end of file diff --git a/CodeWalker.Core/CodeWalker.Core.csproj b/CodeWalker.Core/CodeWalker.Core.csproj index 09bb40b..b515aad 100644 --- a/CodeWalker.Core/CodeWalker.Core.csproj +++ b/CodeWalker.Core/CodeWalker.Core.csproj @@ -1,12 +1,17 @@ - + netstandard2.0 + latest + true + + + diff --git a/CodeWalker.Core/GameFiles/FileTypes/AwcFile.cs b/CodeWalker.Core/GameFiles/FileTypes/AwcFile.cs index cb6f23a..cc79536 100644 --- a/CodeWalker.Core/GameFiles/FileTypes/AwcFile.cs +++ b/CodeWalker.Core/GameFiles/FileTypes/AwcFile.cs @@ -1917,7 +1917,7 @@ namespace CodeWalker.GameFiles Buffer.BlockCopy(data, 16, newdata, 0, newlen); data = newdata; - ResourceDataReader rd = new ResourceDataReader(resentry, data); + using var rd = new ResourceDataReader(resentry, data); ClipDict = rd.ReadBlock(); diff --git a/CodeWalker.Core/GameFiles/FileTypes/Gxt2File.cs b/CodeWalker.Core/GameFiles/FileTypes/Gxt2File.cs index 09ad5a7..5e4731c 100644 --- a/CodeWalker.Core/GameFiles/FileTypes/Gxt2File.cs +++ b/CodeWalker.Core/GameFiles/FileTypes/Gxt2File.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.ComponentModel; using System.Globalization; @@ -171,69 +172,44 @@ namespace CodeWalker.GameFiles public static class GlobalText { - public static Dictionary Index = new Dictionary(); + public static ConcurrentDictionary Index = new (); private static object syncRoot = new object(); public static volatile bool FullIndexBuilt = false; public static void Clear() { - lock (syncRoot) - { - Index.Clear(); - } + Index.Clear(); } 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 !Index.TryAdd(hash, str); } public static bool Ensure(string str, uint hash) { if (hash == 0) return true; - lock (syncRoot) - { - if (!Index.ContainsKey(hash)) - { - Index.Add(hash, str); - return false; - } - } - return true; + return !Index.TryAdd(hash, str); } public static string GetString(uint hash) { string res; - lock (syncRoot) + if (!Index.TryGetValue(hash, out res)) { - if (!Index.TryGetValue(hash, out res)) - { - res = hash.ToString(); - } + res = hash.ToString(); } return res; } public static string TryGetString(uint hash) { string res; - lock (syncRoot) + if (!Index.TryGetValue(hash, out res)) { - if (!Index.TryGetValue(hash, out res)) - { - res = string.Empty; - } + res = string.Empty; } return res; } diff --git a/CodeWalker.Core/GameFiles/FileTypes/PedFile.cs b/CodeWalker.Core/GameFiles/FileTypes/PedFile.cs index 5fb180a..1c60cb3 100644 --- a/CodeWalker.Core/GameFiles/FileTypes/PedFile.cs +++ b/CodeWalker.Core/GameFiles/FileTypes/PedFile.cs @@ -48,7 +48,7 @@ namespace CodeWalker.GameFiles } - ResourceDataReader rd = new ResourceDataReader(resentry, data); + using var rd = new ResourceDataReader(resentry, data); Meta = rd.ReadBlock(); diff --git a/CodeWalker.Core/GameFiles/FileTypes/YbnFile.cs b/CodeWalker.Core/GameFiles/FileTypes/YbnFile.cs index ac487cd..ec05eba 100644 --- a/CodeWalker.Core/GameFiles/FileTypes/YbnFile.cs +++ b/CodeWalker.Core/GameFiles/FileTypes/YbnFile.cs @@ -51,7 +51,7 @@ namespace CodeWalker.GameFiles throw new Exception("File entry wasn't a resource! (is it binary data?)"); } - ResourceDataReader rd = new ResourceDataReader(resentry, data); + using var rd = new ResourceDataReader(resentry, data); Bounds = rd.ReadBlock(); diff --git a/CodeWalker.Core/GameFiles/FileTypes/YcdFile.cs b/CodeWalker.Core/GameFiles/FileTypes/YcdFile.cs index afcd771..89a58b4 100644 --- a/CodeWalker.Core/GameFiles/FileTypes/YcdFile.cs +++ b/CodeWalker.Core/GameFiles/FileTypes/YcdFile.cs @@ -44,20 +44,20 @@ namespace CodeWalker.GameFiles throw new Exception("File entry wasn't a resource! (is it binary data?)"); } - ResourceDataReader rd = null; + try { - rd = new ResourceDataReader(resentry, data); + using ResourceDataReader rd = new ResourceDataReader(resentry, data); + + ClipDictionary = rd?.ReadBlock(); + + InitDictionaries(); } catch (Exception ex) { //data = entry.File.DecompressBytes(data); //?? LoadException = ex.ToString(); } - - ClipDictionary = rd?.ReadBlock(); - - InitDictionaries(); } public void InitDictionaries() diff --git a/CodeWalker.Core/GameFiles/FileTypes/YddFile.cs b/CodeWalker.Core/GameFiles/FileTypes/YddFile.cs index f9e7e2f..0ed54f0 100644 --- a/CodeWalker.Core/GameFiles/FileTypes/YddFile.cs +++ b/CodeWalker.Core/GameFiles/FileTypes/YddFile.cs @@ -43,7 +43,7 @@ namespace CodeWalker.GameFiles throw new Exception("File entry wasn't a resource! (is it binary data?)"); } - ResourceDataReader rd = new ResourceDataReader(resentry, data); + using var rd = new ResourceDataReader(resentry, data); DrawableDict = rd.ReadBlock(); @@ -74,6 +74,7 @@ namespace CodeWalker.GameFiles { var drawable = drawables[i]; var hash = hashes[i]; + drawable.Hash = hash; if ((drawable.Name == null) || (drawable.Name.EndsWith("#dd"))) { string hstr = JenkIndex.TryGetString(hash); @@ -101,6 +102,12 @@ namespace CodeWalker.GameFiles return data; } + new public long MemoryUsage { + get { + return DrawableDict.MemoryUsage; + } + } + } diff --git a/CodeWalker.Core/GameFiles/FileTypes/YdrFile.cs b/CodeWalker.Core/GameFiles/FileTypes/YdrFile.cs index 55de93b..213fffd 100644 --- a/CodeWalker.Core/GameFiles/FileTypes/YdrFile.cs +++ b/CodeWalker.Core/GameFiles/FileTypes/YdrFile.cs @@ -39,7 +39,7 @@ namespace CodeWalker.GameFiles throw new Exception("File entry wasn't a resource! (is it binary data?)"); } - ResourceDataReader rd = new ResourceDataReader(resentry, data); + using var rd = new ResourceDataReader(resentry, data); //MemoryUsage = 0; @@ -58,6 +58,11 @@ namespace CodeWalker.GameFiles } + public void Unload() + { + + } + public byte[] Save() { byte[] data = ResourceBuilder.Build(Drawable, 165); //ydr is type/version 165... @@ -65,7 +70,13 @@ namespace CodeWalker.GameFiles return data; } - + new public long MemoryUsage + { + get + { + return Drawable.MemoryUsage; + } + } } diff --git a/CodeWalker.Core/GameFiles/FileTypes/YedFile.cs b/CodeWalker.Core/GameFiles/FileTypes/YedFile.cs index 6e62ab4..d236e70 100644 --- a/CodeWalker.Core/GameFiles/FileTypes/YedFile.cs +++ b/CodeWalker.Core/GameFiles/FileTypes/YedFile.cs @@ -41,21 +41,20 @@ namespace CodeWalker.GameFiles throw new Exception("File entry wasn't a resource! (is it binary data?)"); } - ResourceDataReader rd = null; try { - rd = new ResourceDataReader(resentry, data); + using var rd = new ResourceDataReader(resentry, data); + + ExpressionDictionary = rd?.ReadBlock(); + + + InitDictionaries(); } catch (Exception ex) { //data = entry.File.DecompressBytes(data); //?? LoadException = ex.ToString(); } - - ExpressionDictionary = rd?.ReadBlock(); - - - InitDictionaries(); } public byte[] Save() diff --git a/CodeWalker.Core/GameFiles/FileTypes/YfdFile.cs b/CodeWalker.Core/GameFiles/FileTypes/YfdFile.cs index 5c9a79b..9bfb7ba 100644 --- a/CodeWalker.Core/GameFiles/FileTypes/YfdFile.cs +++ b/CodeWalker.Core/GameFiles/FileTypes/YfdFile.cs @@ -36,19 +36,17 @@ namespace CodeWalker.GameFiles throw new Exception("File entry wasn't a resource! (is it binary data?)"); } - ResourceDataReader rd = null; + try { - rd = new ResourceDataReader(resentry, data); + using var rd = new ResourceDataReader(resentry, data); + + FrameFilterDictionary = rd.ReadBlock(); } catch (Exception ex) { - //data = entry.File.DecompressBytes(data); //?? LoadException = ex.ToString(); } - - FrameFilterDictionary = rd?.ReadBlock(); - } public byte[] Save() diff --git a/CodeWalker.Core/GameFiles/FileTypes/YftFile.cs b/CodeWalker.Core/GameFiles/FileTypes/YftFile.cs index 86a65af..4283bef 100644 --- a/CodeWalker.Core/GameFiles/FileTypes/YftFile.cs +++ b/CodeWalker.Core/GameFiles/FileTypes/YftFile.cs @@ -40,7 +40,7 @@ namespace CodeWalker.GameFiles throw new Exception("File entry wasn't a resource! (is it binary data?)"); } - ResourceDataReader rd = new ResourceDataReader(resentry, data); + using var rd = new ResourceDataReader(resentry, data); Fragment = rd.ReadBlock(); diff --git a/CodeWalker.Core/GameFiles/FileTypes/YldFile.cs b/CodeWalker.Core/GameFiles/FileTypes/YldFile.cs index 68032ac..a849a14 100644 --- a/CodeWalker.Core/GameFiles/FileTypes/YldFile.cs +++ b/CodeWalker.Core/GameFiles/FileTypes/YldFile.cs @@ -40,36 +40,35 @@ namespace CodeWalker.GameFiles throw new Exception("File entry wasn't a resource! (is it binary data?)"); } - ResourceDataReader rd = null; + try { - rd = new ResourceDataReader(resentry, data); + using var rd = new ResourceDataReader(resentry, data); + + ClothDictionary = rd?.ReadBlock(); + + if (ClothDictionary != null) + { + Dict = new Dictionary(); + int n = ClothDictionary.ClothNameHashes?.data_items?.Length ?? 0; + for (int i = 0; i < n; i++) + { + if (i >= (ClothDictionary.Clothes?.data_items?.Length ?? 0)) break; + + var hash = ClothDictionary.ClothNameHashes.data_items[i]; + var cloth = ClothDictionary.Clothes.data_items[i]; + + Dict[hash] = cloth; + } + } + + Loaded = true; } catch (Exception ex) { //data = entry.File.DecompressBytes(data); //?? LoadException = ex.ToString(); } - - ClothDictionary = rd?.ReadBlock(); - - - if (ClothDictionary != null) - { - Dict = new Dictionary(); - int n = ClothDictionary.ClothNameHashes?.data_items?.Length ?? 0; - for (int i = 0; i < n; i++) - { - if (i >= (ClothDictionary.Clothes?.data_items?.Length ?? 0)) break; - - var hash = ClothDictionary.ClothNameHashes.data_items[i]; - var cloth = ClothDictionary.Clothes.data_items[i]; - - Dict[hash] = cloth; - } - } - - Loaded = true; } public byte[] Save() diff --git a/CodeWalker.Core/GameFiles/FileTypes/YmapFile.cs b/CodeWalker.Core/GameFiles/FileTypes/YmapFile.cs index 09a5e5a..fc99f3c 100644 --- a/CodeWalker.Core/GameFiles/FileTypes/YmapFile.cs +++ b/CodeWalker.Core/GameFiles/FileTypes/YmapFile.cs @@ -96,7 +96,7 @@ namespace CodeWalker.GameFiles return; } - ResourceDataReader rd = new ResourceDataReader(resentry, data); + using var rd = new ResourceDataReader(resentry, data); Meta = rd.ReadBlock();//maybe null this after load to reduce memory consumption? @@ -1336,15 +1336,11 @@ namespace CodeWalker.GameFiles { var newnamel = newname.ToLowerInvariant(); var newnamex = newname + ".ymap"; - var newnamexl = newname.ToLowerInvariant(); var newhash = JenkHash.GenHash(newnamel); JenkIndex.Ensure(newnamel); if (RpfFileEntry != null) { RpfFileEntry.Name = newnamex; - RpfFileEntry.NameLower = newnamexl; - RpfFileEntry.NameHash = JenkHash.GenHash(newnamexl); - RpfFileEntry.ShortNameHash = newhash; } Name = newnamex; _CMapData.name = newhash; @@ -3034,6 +3030,8 @@ namespace CodeWalker.GameFiles public bool Enabled { get; set; } = true; + public bool Visible { get; set; } = true; + public void Init(YmapLODLights l, YmapDistantLODLights p, int i) { LodLights = l; @@ -3306,7 +3304,7 @@ namespace CodeWalker.GameFiles var vertexCount = indicesOffset / 12; var indexCount = (int)(dataSize - indicesOffset);// / 4; Data = MetaTypes.GetByteArray(meta, vptr, dataSize); - Vertices = MetaTypes.ConvertDataArray(Data, 0, vertexCount); + Vertices = MetaTypes.ConvertDataArray(Data, 0, vertexCount).ToArray(); Indices = new byte[indexCount]; Buffer.BlockCopy(Data, indicesOffset, Indices, 0, indexCount); BuildTriangles(); diff --git a/CodeWalker.Core/GameFiles/FileTypes/YmfFile.cs b/CodeWalker.Core/GameFiles/FileTypes/YmfFile.cs index 8d46d87..ff34af6 100644 --- a/CodeWalker.Core/GameFiles/FileTypes/YmfFile.cs +++ b/CodeWalker.Core/GameFiles/FileTypes/YmfFile.cs @@ -67,14 +67,10 @@ namespace CodeWalker.GameFiles - ResourceDataReader rd = new ResourceDataReader(resentry, data); + using var rd = new ResourceDataReader(resentry, data); Meta = rd.ReadBlock(); - - - - } @@ -90,13 +86,13 @@ namespace CodeWalker.GameFiles MapDataGroups = PsoTypes.GetObjectArray(Pso, d.MapDataGroups); - imapDependencies = PsoTypes.GetItemArray(Pso, d.imapDependencies); + imapDependencies = PsoTypes.GetItemArray(Pso, d.imapDependencies).ToArray(); imapDependencies2 = PsoTypes.GetObjectArray(Pso, d.imapDependencies_2); itypDependencies2 = PsoTypes.GetObjectArray(Pso, d.itypDependencies_2); - HDTxdAssetBindings = PsoTypes.GetItemArray(Pso, d.HDTxdBindingArray); + HDTxdAssetBindings = PsoTypes.GetItemArray(Pso, d.HDTxdBindingArray).ToArray(); Interiors = PsoTypes.GetObjectArray(Pso, d.Interiors); diff --git a/CodeWalker.Core/GameFiles/FileTypes/YmtFile.cs b/CodeWalker.Core/GameFiles/FileTypes/YmtFile.cs index 0e52c51..bd92886 100644 --- a/CodeWalker.Core/GameFiles/FileTypes/YmtFile.cs +++ b/CodeWalker.Core/GameFiles/FileTypes/YmtFile.cs @@ -62,7 +62,7 @@ namespace CodeWalker.GameFiles RpfResourceFileEntry resentry = entry as RpfResourceFileEntry; if (resentry != null) { - ResourceDataReader rd = new ResourceDataReader(resentry, data); + using var rd = new ResourceDataReader(resentry, data); Meta = rd.ReadBlock(); diff --git a/CodeWalker.Core/GameFiles/FileTypes/YndFile.cs b/CodeWalker.Core/GameFiles/FileTypes/YndFile.cs index d1196c1..d86fecc 100644 --- a/CodeWalker.Core/GameFiles/FileTypes/YndFile.cs +++ b/CodeWalker.Core/GameFiles/FileTypes/YndFile.cs @@ -84,7 +84,7 @@ namespace CodeWalker.GameFiles throw new Exception("File entry wasn't a resource! (is it binary data?)"); } - ResourceDataReader rd = new ResourceDataReader(resentry, data); + using var rd = new ResourceDataReader(resentry, data); NodeDictionary = rd.ReadBlock(); @@ -95,8 +95,6 @@ namespace CodeWalker.GameFiles //links will be populated by the space... maybe move that code here? - - string areaidstr = Name.ToLowerInvariant().Replace("nodes", "").Replace(".ynd", ""); int areaid = 0; int.TryParse(areaidstr, out areaid); diff --git a/CodeWalker.Core/GameFiles/FileTypes/YnvFile.cs b/CodeWalker.Core/GameFiles/FileTypes/YnvFile.cs index 345949c..a2ac2b4 100644 --- a/CodeWalker.Core/GameFiles/FileTypes/YnvFile.cs +++ b/CodeWalker.Core/GameFiles/FileTypes/YnvFile.cs @@ -90,7 +90,7 @@ namespace CodeWalker.GameFiles throw new Exception("File entry wasn't a resource! (is it binary data?)"); } - ResourceDataReader rd = new ResourceDataReader(resentry, data); + using var rd = new ResourceDataReader(resentry, data); Nav = rd.ReadBlock(); diff --git a/CodeWalker.Core/GameFiles/FileTypes/YptFile.cs b/CodeWalker.Core/GameFiles/FileTypes/YptFile.cs index b12cdf2..c56164e 100644 --- a/CodeWalker.Core/GameFiles/FileTypes/YptFile.cs +++ b/CodeWalker.Core/GameFiles/FileTypes/YptFile.cs @@ -44,7 +44,7 @@ namespace CodeWalker.GameFiles throw new Exception("File entry wasn't a resource! (is it binary data?)"); } - ResourceDataReader rd = new ResourceDataReader(resentry, data); + using var rd = new ResourceDataReader(resentry, data); //MemoryUsage = 0; diff --git a/CodeWalker.Core/GameFiles/FileTypes/YtdFile.cs b/CodeWalker.Core/GameFiles/FileTypes/YtdFile.cs index 54ad038..19356c8 100644 --- a/CodeWalker.Core/GameFiles/FileTypes/YtdFile.cs +++ b/CodeWalker.Core/GameFiles/FileTypes/YtdFile.cs @@ -31,6 +31,7 @@ namespace CodeWalker.GameFiles Loaded = true; } + public void Load(byte[] data, RpfFileEntry entry) { Name = entry.Name; @@ -43,7 +44,7 @@ namespace CodeWalker.GameFiles throw new Exception("File entry wasn't a resource! (is it binary data?)"); } - ResourceDataReader rd = new ResourceDataReader(resentry, data); + using var rd = new ResourceDataReader(resentry, data); TextureDict = rd.ReadBlock(); @@ -64,7 +65,13 @@ namespace CodeWalker.GameFiles return data; } - + new public long MemoryUsage + { + get + { + return TextureDict.MemoryUsage; + } + } } diff --git a/CodeWalker.Core/GameFiles/FileTypes/YtypFile.cs b/CodeWalker.Core/GameFiles/FileTypes/YtypFile.cs index 5fff26d..6d22181 100644 --- a/CodeWalker.Core/GameFiles/FileTypes/YtypFile.cs +++ b/CodeWalker.Core/GameFiles/FileTypes/YtypFile.cs @@ -208,7 +208,7 @@ namespace CodeWalker.GameFiles - ResourceDataReader rd = new ResourceDataReader(resentry, data); + using var rd = new ResourceDataReader(resentry, data); Meta = rd.ReadBlock(); diff --git a/CodeWalker.Core/GameFiles/FileTypes/YvrFile.cs b/CodeWalker.Core/GameFiles/FileTypes/YvrFile.cs index ec6a2de..c0f7ecb 100644 --- a/CodeWalker.Core/GameFiles/FileTypes/YvrFile.cs +++ b/CodeWalker.Core/GameFiles/FileTypes/YvrFile.cs @@ -31,7 +31,7 @@ namespace CodeWalker.GameFiles throw new Exception("File entry wasn't a resource! (is it binary data?)"); } - ResourceDataReader rd = new ResourceDataReader(resentry, data); + using var rd = new ResourceDataReader(resentry, data); //MemoryUsage = 0; diff --git a/CodeWalker.Core/GameFiles/FileTypes/YwrFile.cs b/CodeWalker.Core/GameFiles/FileTypes/YwrFile.cs index 3f0b636..7f8a0f1 100644 --- a/CodeWalker.Core/GameFiles/FileTypes/YwrFile.cs +++ b/CodeWalker.Core/GameFiles/FileTypes/YwrFile.cs @@ -31,7 +31,7 @@ namespace CodeWalker.GameFiles throw new Exception("File entry wasn't a resource! (is it binary data?)"); } - ResourceDataReader rd = new ResourceDataReader(resentry, data); + using var rd = new ResourceDataReader(resentry, data); //MemoryUsage = 0; diff --git a/CodeWalker.Core/GameFiles/GameFile.cs b/CodeWalker.Core/GameFiles/GameFile.cs index 05d2064..a61f02b 100644 --- a/CodeWalker.Core/GameFiles/GameFile.cs +++ b/CodeWalker.Core/GameFiles/GameFile.cs @@ -101,6 +101,28 @@ namespace CodeWalker.GameFiles Hash = hash; Type = type; } + + public override bool Equals(object obj) + { + if (obj == null) return false; + if (obj is not GameFileCacheKey gameFileCacheKey) return false; + return gameFileCacheKey.Hash == Hash && gameFileCacheKey.Type == Type; + } + + public static bool operator ==(GameFileCacheKey first, GameFileCacheKey second) + { + return first.Equals(second); + } + + public static bool operator !=(GameFileCacheKey first, GameFileCacheKey second) + { + return !first.Equals(second); + } + + public override int GetHashCode() + { + return (int)Hash; + } } diff --git a/CodeWalker.Core/GameFiles/GameFileCache.cs b/CodeWalker.Core/GameFiles/GameFileCache.cs index 16df0af..ea5fc4f 100644 --- a/CodeWalker.Core/GameFiles/GameFileCache.cs +++ b/CodeWalker.Core/GameFiles/GameFileCache.cs @@ -1,7 +1,9 @@ -using SharpDX; +using CodeWalker.Core.Utils; +using SharpDX; using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.Linq; using System.Text; @@ -11,12 +13,12 @@ using System.Xml; namespace CodeWalker.GameFiles { - public class GameFileCache + public partial class GameFileCache { public RpfManager RpfMan; - private Action UpdateStatus; - private Action ErrorLog; - public int MaxItemsPerLoop = 1; //to keep things flowing... + public event Action UpdateStatus; + public event Action ErrorLog; + public int MaxItemsPerLoop = 3; //to keep things flowing... private ConcurrentQueue requestQueue = new ConcurrentQueue(); @@ -25,18 +27,17 @@ namespace CodeWalker.GameFiles public volatile bool IsInited = false; private volatile bool archetypesLoaded = false; - private Dictionary archetypeDict = new Dictionary(); - private Dictionary textureLookup = new Dictionary(); + private ConcurrentDictionary archetypeDict = new ConcurrentDictionary(); + private ConcurrentDictionary textureLookup = new ConcurrentDictionary(); private Dictionary textureParents; private Dictionary hdtexturelookup; private object updateSyncRoot = new object(); private object requestSyncRoot = new object(); - private object textureSyncRoot = new object(); //for the texture lookup. - private Dictionary projectFiles = new Dictionary(); //for cache files loaded in project window: ydr,ydd,ytd,yft - private Dictionary projectArchetypes = new Dictionary(); //used to override archetypes in world view with project ones + 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 @@ -58,7 +59,7 @@ namespace CodeWalker.GameFiles //static cached data loaded at init - public Dictionary YtypDict { get; set; } + public ConcurrentDictionary YtypDict { get; set; } public List AllCacheFiles { get; set; } public Dictionary YmapHierarchyDict { get; set; } @@ -112,7 +113,7 @@ namespace CodeWalker.GameFiles public bool LoadVehicles = true; public bool LoadPeds = true; public bool LoadAudio = true; - private bool PreloadedMode = false; + private bool PreloadedMode = true; private string GTAFolder; private string ExcludeFolders; @@ -162,15 +163,22 @@ namespace CodeWalker.GameFiles textureLookup.Clear(); - GameFile queueclear; - while (requestQueue.TryDequeue(out queueclear)) + while (requestQueue.TryDequeue(out _)) { } //empty the old queue out... } - public void Init(Action updateStatus, Action errorLog) + public void SetGtaFolder(string folder) { - UpdateStatus = updateStatus; - ErrorLog = errorLog; + Clear(); + + GTAFolder = folder; + } + + public void Init(Action updateStatus = null, Action errorLog = null) + { + using var _ = new DisposableTimer("GameFileCache.Init"); + if (updateStatus != null) UpdateStatus += updateStatus; + if (errorLog != null) ErrorLog += errorLog; Clear(); @@ -181,7 +189,7 @@ namespace CodeWalker.GameFiles - RpfMan = new RpfManager(); + RpfMan = RpfManager.GetInstance(); RpfMan.ExcludePaths = GetExcludePaths(); RpfMan.EnableMods = EnableMods; RpfMan.BuildExtendedJenkIndex = BuildExtendedJenkIndex; @@ -233,99 +241,81 @@ namespace CodeWalker.GameFiles GC.Collect(); //try free up some of the previously used memory.. } - UpdateStatus("Scan complete"); + UpdateStatus?.Invoke("Scan complete"); IsInited = true; } public void Init(Action updateStatus, Action errorLog, List allRpfs) { - UpdateStatus = updateStatus; - 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 = new RpfManager(); //try not to use this in this mode... + RpfMan ??= RpfManager.GetInstance(); RpfMan.Init(allRpfs); + AllRpfs = allRpfs; BaseRpfs = allRpfs; DlcRpfs = new List(); - UpdateStatus("Building global dictionaries..."); - InitGlobalDicts(); - - UpdateStatus("Loading manifests..."); - InitManifestDicts(); - - UpdateStatus("Loading global texture list..."); - InitGtxds(); - - UpdateStatus("Loading archetypes..."); - InitArchetypeDicts(); - - UpdateStatus("Loading strings..."); - InitStringDicts(); - - UpdateStatus("Loading audio..."); - InitAudio(); + 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() { + using var _ = new DisposableTimer("InitGlobal"); BaseRpfs = GetModdedRpfList(RpfMan.BaseRpfs); AllRpfs = GetModdedRpfList(RpfMan.AllRpfs); DlcRpfs = GetModdedRpfList(RpfMan.DlcRpfs); - UpdateStatus("Building global dictionaries..."); InitGlobalDicts(); } private void InitDlc() { - - UpdateStatus("Building DLC List..."); InitDlcList(); - UpdateStatus("Building active RPF dictionary..."); InitActiveMapRpfFiles(); - UpdateStatus("Building map dictionaries..."); - InitMapDicts(); - - UpdateStatus("Loading manifests..."); - InitManifestDicts(); - - UpdateStatus("Loading global texture list..."); - InitGtxds(); - - UpdateStatus("Loading cache..."); - InitMapCaches(); - - UpdateStatus("Loading archetypes..."); - InitArchetypeDicts(); - - UpdateStatus("Loading strings..."); - InitStringDicts(); - - UpdateStatus("Loading vehicles..."); - InitVehicles(); - - UpdateStatus("Loading peds..."); - InitPeds(); - - UpdateStatus("Loading audio..."); - InitAudio(); - + 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) + ).GetAwaiter().GetResult(); } private void InitDlcList() { + UpdateStatus?.Invoke("Building DLC List..."); + using var _ = new DisposableTimer("InitDlcList"); //if (!EnableDlc) return; string dlclistpath = "update\\update.rpf\\common\\data\\dlclist.xml"; @@ -353,12 +343,12 @@ namespace CodeWalker.GameFiles //get dlc path names in the appropriate format for reference by the dlclist paths - Dictionary dlcDict = new Dictionary(); - Dictionary dlcDict2 = new Dictionary(); + Dictionary dlcDict = new Dictionary(StringComparer.OrdinalIgnoreCase); + Dictionary dlcDict2 = new Dictionary(StringComparer.OrdinalIgnoreCase); foreach (RpfFile dlcrpf in DlcRpfs) { if (dlcrpf == null) continue; - if (dlcrpf.NameLower == "dlc.rpf") + if (dlcrpf.Name.Equals("dlc.rpf", StringComparison.OrdinalIgnoreCase)) { string path = GetDlcRpfVirtualPath(dlcrpf.Path); string name = GetDlcNameFromPath(dlcrpf.Path); @@ -410,7 +400,7 @@ namespace CodeWalker.GameFiles if (file == null) continue; var fpath = file.Path; var frelpath = fpath.Replace(updrpfpath, "update:").Replace('\\', '/').Replace(lpath, dlcpath).Replace('/', '\\'); - if (frelpath.StartsWith("mods\\")) + if (frelpath.StartsWith("mods\\", StringComparison.OrdinalIgnoreCase)) { frelpath = frelpath.Substring(5); } @@ -475,7 +465,7 @@ namespace CodeWalker.GameFiles foreach (var sfile in DlcSetupFiles) { if ((sfile == null) || (sfile.DlcFile == null)) continue; - DlcNameList.Add(GetDlcNameFromPath(sfile.DlcFile.Path)); + DlcNameList.Add(GetDlcNameFromPath(sfile.DlcFile.Path).ToLowerInvariant()); } if (DlcNameList.Count > 0) @@ -522,6 +512,8 @@ namespace CodeWalker.GameFiles private void InitActiveMapRpfFiles() { + UpdateStatus?.Invoke("Building active RPF dictionary..."); + using var _ = new DisposableTimer("InitActiveMapRpfFiles"); ActiveMapRpfFiles.Clear(); foreach (RpfFile baserpf in BaseRpfs) //start with all the base rpf's (eg x64a.rpf) @@ -599,7 +591,7 @@ namespace CodeWalker.GameFiles string dlcname = GetDlcNameFromPath(dlcfile.Path); - if ((dlcname == "patchday27ng") && (SelectedDlc != dlcname)) + if ((dlcname == "patchday27ng") && (!SelectedDlc.Equals(dlcname, StringComparison.OrdinalIgnoreCase))) { continue; //hack to fix map getting completely broken by this DLC.. but why? need to investigate further! } @@ -730,7 +722,7 @@ namespace CodeWalker.GameFiles - if (dlcname == SelectedDlc) + if (dlcname.Equals(SelectedDlc, StringComparison.OrdinalIgnoreCase)) { break; //everything's loaded up to the selected DLC. } @@ -828,7 +820,7 @@ namespace CodeWalker.GameFiles private string GetDlcRpfVirtualPath(string path) { path = path.Replace('\\', '/'); - if (path.StartsWith("mods/")) + if (path.StartsWith("mods/", StringComparison.OrdinalIgnoreCase)) { path = path.Substring(5); } @@ -836,7 +828,7 @@ namespace CodeWalker.GameFiles { path = path.Substring(0, path.Length - 7);//trim off "dlc.rpf" } - if (path.StartsWith("x64")) + if (path.StartsWith("x64", StringComparison.OrdinalIgnoreCase)) { int bsind = path.IndexOf('/'); //replace x64*.rpf if ((bsind > 0) && (bsind < path.Length)) @@ -846,7 +838,7 @@ namespace CodeWalker.GameFiles else { } //no hits here } - else if (path.StartsWith("update/x64/dlcpacks")) + else if (path.StartsWith("update/x64/dlcpacks", StringComparison.OrdinalIgnoreCase)) { path = path.Replace("update/x64/dlcpacks", "dlcpacks:"); } @@ -857,10 +849,10 @@ namespace CodeWalker.GameFiles } private string GetDlcNameFromPath(string path) { - string[] parts = path.ToLowerInvariant().Split('\\'); + string[] parts = path.Split('\\'); if (parts.Length > 1) { - return parts[parts.Length - 2].ToLowerInvariant(); + return parts[parts.Length - 2]; } return path; } @@ -958,12 +950,14 @@ namespace CodeWalker.GameFiles private void InitGlobalDicts() { - YdrDict = new Dictionary(); - YddDict = new Dictionary(); - YtdDict = new Dictionary(); - YftDict = new Dictionary(); - YcdDict = new Dictionary(); - YedDict = new Dictionary(); + 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); foreach (var rpffile in AllRpfs) { if (rpffile.AllEntries == null) continue; @@ -999,11 +993,13 @@ 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() { + UpdateStatus?.Invoke("Building map dictionaries..."); + using var _ = new DisposableTimer("InitMapDicts"); YmapDict = new Dictionary(); YbnDict = new Dictionary(); YnvDict = new Dictionary(); @@ -1054,9 +1050,11 @@ namespace CodeWalker.GameFiles private void InitManifestDicts() { + UpdateStatus?.Invoke("Loading manifests..."); + using var _ = new DisposableTimer("InitManifestDicts"); AllManifests = new List(); hdtexturelookup = new Dictionary(); - IEnumerable rpfs = PreloadedMode ? AllRpfs : (IEnumerable)ActiveMapRpfFiles.Values; + IEnumerable rpfs = PreloadedMode ? AllRpfs : ActiveMapRpfFiles.Values; foreach (RpfFile file in rpfs) { if (file.AllEntries == null) continue; @@ -1072,7 +1070,7 @@ namespace CodeWalker.GameFiles { try { - UpdateStatus(string.Format(entry.Path)); + UpdateStatus?.Invoke(string.Format(entry.Path)); YmfFile ymffile = RpfMan.GetFile(entry); if (ymffile != null) { @@ -1115,7 +1113,8 @@ namespace CodeWalker.GameFiles private void InitGtxds() { - + UpdateStatus?.Invoke("Loading global texture list..."); + using var timer = new DisposableTimer("InitGtxds"); var parentTxds = new Dictionary(); IEnumerable rpfs = PreloadedMode ? AllRpfs : (IEnumerable)ActiveMapRpfFiles.Values; @@ -1202,6 +1201,8 @@ namespace CodeWalker.GameFiles private void InitMapCaches() { + UpdateStatus?.Invoke("Loading cache..."); + using var _ = new DisposableTimer("InitMapCaches"); AllCacheFiles = new List(); YmapHierarchyDict = new Dictionary(); @@ -1271,38 +1272,33 @@ namespace CodeWalker.GameFiles private void InitArchetypeDicts() { - - YtypDict = new Dictionary(); + UpdateStatus?.Invoke("Loading archetypes..."); + YtypDict = new ConcurrentDictionary(); archetypesLoaded = false; archetypeDict.Clear(); if (!LoadArchetypes) return; + using var timer = new DisposableTimer("InitArchetypeDicts"); var rpfs = EnableDlc ? AllRpfs : BaseRpfs; - foreach (RpfFile file in rpfs) //RpfMan.BaseRpfs)RpfMan.AllRpfs)//ActiveMapRpfFiles.Values) // - { - if (file.AllEntries == null) continue; - if (!EnableDlc && file.Path.StartsWith("update")) continue; + var allYtypes = rpfs + .Where(p => p.AllEntries != null && (EnableDlc || !p.Path.StartsWith("update"))) + .SelectMany(p => p.AllEntries.Where(p => p.NameLower.EndsWith(".ytyp"))); - //parse ytyps - foreach (RpfEntry entry in file.AllEntries) + Parallel.ForEach(allYtypes, new ParallelOptions { MaxDegreeOfParallelism = 4 }, (entry) => + { + try { - try - { - if (entry.NameLower.EndsWith(".ytyp")) - { - AddYtypToDictionary(entry); - } - } - catch (Exception ex) - { - ErrorLog(entry.Path + ": " + ex.Message); - } + AddYtypToDictionary(entry); } - } + catch (Exception ex) + { + ErrorLog(entry.Path + ": " + ex.ToString()); + } + }); archetypesLoaded = true; @@ -1310,7 +1306,7 @@ namespace CodeWalker.GameFiles private void AddYtypToDictionary(RpfEntry entry) { - UpdateStatus(string.Format(entry.Path)); + //UpdateStatus?.Invoke(string.Format(entry.Path)); YtypFile ytypfile = RpfMan.GetFile(entry); if (ytypfile == null) { @@ -1318,18 +1314,24 @@ namespace CodeWalker.GameFiles } if (ytypfile.Meta == null) { - throw new Exception("ytyp file was not in meta format."); - } - if (YtypDict.ContainsKey(ytypfile.NameHash)) - { - //throw new Exception("ytyp " + JenkIndex.GetString(ytypfile.NameHash) + " already loaded."); - //errorAction(entry.Path + ": ytyp " + JenkIndex.GetString(ytypfile.NameHash) + " already loaded."); - YtypDict[ytypfile.NameHash] = ytypfile; //override ytyp and continue anyway, could be unique archetypes in here still... - } - else - { - YtypDict.Add(ytypfile.NameHash, ytypfile); + if (ytypfile.Pso == null && ytypfile.Rbf == null) + { + throw new Exception("ytyp file was not in meta format."); + } + return; } + YtypDict[ytypfile.NameHash] = ytypfile; + + //if (YtypDict.ContainsKey(ytypfile.NameHash)) + //{ + // //throw new Exception("ytyp " + JenkIndex.GetString(ytypfile.NameHash) + " already loaded."); + // //errorAction(entry.Path + ": ytyp " + JenkIndex.GetString(ytypfile.NameHash) + " already loaded."); + // YtypDict[ytypfile.NameHash] = ytypfile; //override ytyp and continue anyway, could be unique archetypes in here still... + //} + //else + //{ + // YtypDict.Add(ytypfile.NameHash, ytypfile); + //} @@ -1343,10 +1345,6 @@ namespace CodeWalker.GameFiles { uint hash = arch.Hash; if (hash == 0) continue; - if (archetypeDict.ContainsKey(hash)) - { - var oldval = archetypeDict[hash]; //replace old archetype? - } archetypeDict[hash] = arch; } } @@ -1379,6 +1377,8 @@ namespace CodeWalker.GameFiles public void InitStringDicts() { + UpdateStatus?.Invoke("Loading strings..."); + using var timer = new DisposableTimer("InitStringDicts"); string langstr = "american_rel"; //todo: make this variable? string langstr2 = "americandlc.rpf"; string langstr3 = "american.rpf"; @@ -1491,7 +1491,8 @@ namespace CodeWalker.GameFiles //Vehiclelayouts mostly to handle ped interactions with the vehicle - + UpdateStatus?.Invoke("Loading vehicles..."); + using var _ = new DisposableTimer("InitVehicles"); IEnumerable rpfs = PreloadedMode ? AllRpfs : (IEnumerable)ActiveMapRpfFiles.Values; @@ -1586,6 +1587,7 @@ namespace CodeWalker.GameFiles addVehicleFiles(DlcActiveRpfs); } + Console.WriteLine($"allVehicles: {allVehicles.Count()}"); VehiclesInitDict = allVehicles; @@ -1595,6 +1597,8 @@ namespace CodeWalker.GameFiles { if (!LoadPeds) return; + UpdateStatus?.Invoke("Loading peds..."); + using var _ = new DisposableTimer("InitPeds"); IEnumerable rpfs = PreloadedMode ? AllRpfs : (IEnumerable)ActiveMapRpfFiles.Values; List dlcrpfs = new List(); if (EnableDlc) @@ -1692,9 +1696,7 @@ namespace CodeWalker.GameFiles if (file.AllEntries == null) continue; foreach (RpfEntry entry in file.AllEntries) { -#if !DEBUG try -#endif { if ((entry.NameLower == "peds.ymt") || (entry.NameLower == "peds.meta")) { @@ -1713,13 +1715,12 @@ namespace CodeWalker.GameFiles allPedsFiles.Add(pf); } } -#if !DEBUG catch (Exception ex) { string errstr = entry.Path + "\n" + ex.ToString(); - ErrorLog(errstr); + ErrorLog?.Invoke(errstr); + Console.WriteLine(errstr); } -#endif } } }); @@ -1731,14 +1732,12 @@ namespace CodeWalker.GameFiles if (file.AllEntries == null) continue; foreach (RpfEntry entry in file.AllEntries) { -#if !DEBUG try -#endif { - if (entry.NameLower.EndsWith(".ymt")) + if (entry.Name.EndsWith(".ymt", StringComparison.OrdinalIgnoreCase)) { - var testname = entry.GetShortNameLower(); - var testhash = JenkHash.GenHash(testname); + var testname = entry.ShortName; + var testhash = JenkHash.GenHashLower(testname); if (allPeds.ContainsKey(testhash)) { var pf = RpfMan.GetFile(entry); @@ -1750,13 +1749,12 @@ namespace CodeWalker.GameFiles } } } -#if !DEBUG catch (Exception ex) { string errstr = entry.Path + "\n" + ex.ToString(); - ErrorLog(errstr); + ErrorLog?.Invoke(errstr); + Console.WriteLine(errstr); } -#endif } } }); @@ -1791,6 +1789,8 @@ namespace CodeWalker.GameFiles { if (!LoadAudio) return; + UpdateStatus?.Invoke("Loading audio..."); + using var timer = new DisposableTimer("InitAudio"); Dictionary datrelentries = new Dictionary(); void addRpfDatRelEntries(RpfFile rpffile) { @@ -1918,7 +1918,7 @@ namespace CodeWalker.GameFiles public bool SetDlcLevel(string dlc, bool enable) { - bool dlcchange = (dlc != SelectedDlc); + bool dlcchange = (!dlc.Equals(SelectedDlc, StringComparison.OrdinalIgnoreCase)); bool enablechange = (enable != EnableDlc); bool change = (dlcchange && enable) || enablechange; @@ -1986,15 +1986,9 @@ namespace CodeWalker.GameFiles { if (f == null) return; if (f.RpfFileEntry == null) return; - if (f.RpfFileEntry.ShortNameHash == 0) - { - f.RpfFileEntry.ShortNameHash = JenkHash.GenHash(f.RpfFileEntry.GetShortNameLower()); - } + if (f.RpfFileEntry.ShortNameHash == 0) return; var key = new GameFileCacheKey(f.RpfFileEntry.ShortNameHash, f.Type); - lock (requestSyncRoot) - { - projectFiles[key] = f; - } + projectFiles[key] = f; } public void RemoveProjectFile(GameFile f) { @@ -2002,54 +1996,43 @@ namespace CodeWalker.GameFiles if (f.RpfFileEntry == null) return; if (f.RpfFileEntry.ShortNameHash == 0) return; var key = new GameFileCacheKey(f.RpfFileEntry.ShortNameHash, f.Type); - lock (requestSyncRoot) - { - projectFiles.Remove(key); - } + projectFiles.TryRemove(key, out _); } public void ClearProjectFiles() { - lock (requestSyncRoot) - { - projectFiles.Clear(); - } + projectFiles.Clear(); } public void AddProjectArchetype(Archetype a) { if ((a?.Hash ?? 0) == 0) return; - lock (requestSyncRoot) - { - projectArchetypes[a.Hash] = a; - } + projectArchetypes[a.Hash] = a; } public void RemoveProjectArchetype(Archetype a) { if ((a?.Hash ?? 0) == 0) return; Archetype tarch = null; - lock (requestSyncRoot) + projectArchetypes.TryGetValue(a.Hash, out tarch); + if (tarch == a) { - projectArchetypes.TryGetValue(a.Hash, out tarch); - if (tarch == a) - { - projectArchetypes.Remove(a.Hash); - } + projectArchetypes.TryRemove(a.Hash, out _); } } public void ClearProjectArchetypes() { - lock (requestSyncRoot) - { - projectArchetypes.Clear(); - } + projectArchetypes.Clear(); } public void TryLoadEnqueue(GameFile gf) { - if (((!gf.Loaded)) && (requestQueue.Count < 10))// && (!gf.LoadQueued) + if (gf.Loaded || gf.LoadQueued) + { + return; + } + if (requestQueue.Count < 20)// && (!gf.LoadQueued) { - requestQueue.Enqueue(gf); gf.LoadQueued = true; + requestQueue.Enqueue(gf); } } @@ -2057,17 +2040,17 @@ namespace CodeWalker.GameFiles public Archetype GetArchetype(uint hash) { if (!archetypesLoaded) return null; - Archetype arch = null; - projectArchetypes.TryGetValue(hash, out arch); - if (arch != null) return arch; + if (projectArchetypes.TryGetValue(hash, out var arch) && arch != null) + { + return arch; + } archetypeDict.TryGetValue(hash, out arch); return arch; } public MapDataStoreNode GetMapNode(uint hash) { if (!IsInited) return null; - MapDataStoreNode node = null; - YmapHierarchyDict.TryGetValue(hash, out node); + YmapHierarchyDict.TryGetValue(hash, out var node); return node; } @@ -2085,25 +2068,21 @@ namespace CodeWalker.GameFiles if (ydr == null) { var e = GetYdrEntry(hash); - if (e != null) + + if (e == null) { - ydr = new YdrFile(e); - if (mainCache.TryAdd(key, ydr)) - { - TryLoadEnqueue(ydr); - } - else - { - ydr.LoadQueued = false; - //ErrorLog("Out of cache space - couldn't load drawable: " + JenkIndex.GetString(hash)); //too spammy... - } + return null; } - else + ydr = new YdrFile(e); + if (!mainCache.TryAdd(key, ydr)) { - //ErrorLog("Drawable not found: " + JenkIndex.GetString(hash)); //too spammy... + //ErrorLog("Out of cache space - couldn't load drawable: " + JenkIndex.GetString(hash)); //too spammy... + ydr.LoadQueued = false; + return null; } } - else if (!ydr.Loaded) + + if (!ydr.Loaded && !ydr.LoadQueued) { TryLoadEnqueue(ydr); } @@ -2493,29 +2472,20 @@ namespace CodeWalker.GameFiles } } - - public bool ContentThreadProc() + private void LoadFile(GameFile req) { - Monitor.Enter(updateSyncRoot); - - GameFile req; - //bool loadedsomething = false; - - int itemcount = 0; - - while (requestQueue.TryDequeue(out req) && (itemcount < MaxItemsPerLoop)) + try { //process content requests. if (req.Loaded) - continue; //it's already loaded... (somehow) + return; //it's already loaded... (somehow) - if ((req.LastUseTime - DateTime.Now).TotalSeconds > 0.5) - continue; //hasn't been requested lately..! ignore, will try again later if necessary + if ((req.LastUseTime - DateTime.Now).TotalSeconds > 3.0) + return; //hasn't been requested lately..! ignore, will try again later if necessary - itemcount++; //if (!loadedsomething) //{ - //UpdateStatus("Loading " + req.RpfFileEntry.Name + "..."); + //UpdateStatus?.Invoke("Loading " + req.RpfFileEntry.Name + "..."); //} switch (req.Type) @@ -2560,25 +2530,47 @@ namespace CodeWalker.GameFiles 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(str); + 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); + } + finally + { + req.LoadQueued = false; + } + + } - //loadedsomething = true; + public bool ContentThreadProc() + { + lock (requestSyncRoot) + { + mainCache.BeginFrame(); + } + int itemcount = 0; + lock (updateSyncRoot) + { + GameFile req; + + while ((itemcount < MaxItemsPerLoop) && requestQueue.TryDequeue(out req)) + { + LoadFile(req); + + itemcount++; + } } - //whether or not we need another content thread loop - bool itemsStillPending = (itemcount >= MaxItemsPerLoop); - - - Monitor.Exit(updateSyncRoot); - - - return itemsStillPending; + return itemcount >= 1; } @@ -2590,32 +2582,24 @@ namespace CodeWalker.GameFiles { if (ytd?.TextureDict?.TextureNameHashes?.data_items == null) return; - lock (textureSyncRoot) + foreach (uint hash in ytd.TextureDict.TextureNameHashes.data_items) { - foreach (uint hash in ytd.TextureDict.TextureNameHashes.data_items) - { - textureLookup[hash] = ytd.RpfFileEntry; - } - + textureLookup[hash] = ytd.RpfFileEntry; } } public YtdFile TryGetTextureDictForTexture(uint hash) { - lock (textureSyncRoot) + if (textureLookup.TryGetValue(hash, out var e)) { - RpfFileEntry e; - if (textureLookup.TryGetValue(hash, out e)) - { - return GetYtd(e.ShortNameHash); - } - + return GetYtd(e.ShortNameHash); } + return null; } public YtdFile TryGetParentYtd(uint hash) { MetaHash phash; - if (textureParents.TryGetValue(hash, out phash)) + if (textureParents is not null && textureParents.TryGetValue(hash, out phash)) { return GetYtd(phash); } @@ -2623,8 +2607,11 @@ namespace CodeWalker.GameFiles } public uint TryGetParentYtdHash(uint hash) { - MetaHash phash = 0; - textureParents.TryGetValue(hash, out phash); + if (textureParents is null) + { + return 0u; + } + textureParents.TryGetValue(hash, out MetaHash phash); return phash; } public uint TryGetHDTextureHash(uint txdhash) @@ -2829,2437 +2816,6 @@ namespace CodeWalker.GameFiles return exclpaths; } - - - - - - - public void TestAudioRels() - { - UpdateStatus("Testing Audio REL files"); - - - bool savetest = true; - bool xmltest = true; - bool asmtest = true; - - foreach (RpfFile rpf in RpfMan.AllRpfs) - { - foreach (RpfEntry entry in rpf.AllEntries) - { - var rfe = entry as RpfFileEntry; - var rbfe = rfe as RpfBinaryFileEntry; - if ((rfe == null) || (rbfe == null)) continue; - - if (rfe.NameLower.EndsWith(".rel")) - { - UpdateStatus(string.Format(entry.Path)); - - RelFile rel = new RelFile(rfe); - RpfMan.LoadFile(rel, rfe); - - - - byte[] data; - - if (savetest) - { - - data = rel.Save(); - if (data != null) - { - if (data.Length != rbfe.FileUncompressedSize) - { } - else if (data.Length != rel.RawFileData.Length) - { } - else - { - for (int i = 0; i < data.Length; i++) //raw file test - if (data[i] != rel.RawFileData[i]) - { break; } - } - - - RelFile rel2 = new RelFile(); - rel2.Load(data, rfe);//roundtrip test - - if (rel2.IndexCount != rel.IndexCount) - { } - if (rel2.RelDatas == null) - { } - - } - else - { } - - } - - if (xmltest) - { - var relxml = RelXml.GetXml(rel); //XML test... - var rel3 = XmlRel.GetRel(relxml); - if (rel3 != null) - { - if (rel3.RelDatasSorted?.Length != rel.RelDatasSorted?.Length) - { } //check nothing went missing... - - - data = rel3.Save(); //full roundtrip! - if (data != null) - { - var rel4 = new RelFile(); - rel4.Load(data, rfe); //insanity check - - if (data.Length != rbfe.FileUncompressedSize) - { } - else if (data.Length != rel.RawFileData.Length) - { } - else - { - for (int i = 0; i < data.Length; i++) //raw file test - if (data[i] != rel.RawFileData[i]) - { break; } - } - - var relxml2 = RelXml.GetXml(rel4); //full insanity - if (relxml2.Length != relxml.Length) - { } - if (relxml2 != relxml) - { } - - } - else - { } - } - else - { } - - } - - if (asmtest) - { - if (rel.RelType == RelDatFileType.Dat10ModularSynth) - { - foreach (var d in rel.RelDatasSorted) - { - if (d is Dat10Synth synth) - { - synth.TestDisassembly(); - } - } - } - } - - } - - } - - } - - - - var hashmap = RelFile.HashesMap; - if (hashmap.Count > 0) - { } - - - var sb2 = new StringBuilder(); - foreach (var kvp in hashmap) - { - string itemtype = kvp.Key.ItemType.ToString(); - if (kvp.Key.FileType == RelDatFileType.Dat151) - { - itemtype = ((Dat151RelType)kvp.Key.ItemType).ToString(); - } - else if (kvp.Key.FileType == RelDatFileType.Dat54DataEntries) - { - itemtype = ((Dat54SoundType)kvp.Key.ItemType).ToString(); - } - else - { - itemtype = kvp.Key.FileType.ToString() + ".Unk" + kvp.Key.ItemType.ToString(); - } - if (kvp.Key.IsContainer) - { - itemtype += " (container)"; - } - - //if (kvp.Key.FileType == RelDatFileType.Dat151) - { - sb2.Append(itemtype); - sb2.Append(" "); - foreach (var val in kvp.Value) - { - sb2.Append(val.ToString()); - sb2.Append(" "); - } - - sb2.AppendLine(); - } - - } - - var hashmapstr = sb2.ToString(); - if (!string.IsNullOrEmpty(hashmapstr)) - { } - - } - public void TestAudioYmts() - { - - StringBuilder sb = new StringBuilder(); - - Dictionary allids = new Dictionary(); - - foreach (RpfFile file in AllRpfs) - { - foreach (RpfEntry entry in file.AllEntries) - { - try - { - var n = entry.NameLower; - if (n.EndsWith(".ymt")) - { - UpdateStatus(string.Format(entry.Path)); - //YmtFile ymtfile = RpfMan.GetFile(entry); - //if ((ymtfile != null)) - //{ - //} - - var sn = entry.GetShortName(); - uint un; - if (uint.TryParse(sn, out un)) - { - if (allids.ContainsKey(un)) - { - allids[un] = allids[un] + 1; - } - else - { - allids[un] = 1; - //ushort s1 = (ushort)(un & 0x1FFFu); - //ushort s2 = (ushort)((un >> 13) & 0x1FFFu); - uint s1 = un % 80000; - uint s2 = (un / 80000); - float f1 = s1 / 5000.0f; - float f2 = s2 / 5000.0f; - sb.AppendFormat("{0}, {1}, 0, {2}\r\n", f1, f2, sn); - } - } - - - } - } - catch (Exception ex) - { - UpdateStatus("Error! " + ex.ToString()); - } - } - } - - var skeys = allids.Keys.ToList(); - skeys.Sort(); - - var hkeys = new List(); - foreach (var skey in skeys) - { - FlagsUint fu = new FlagsUint(skey); - //hkeys.Add(skey.ToString("X")); - hkeys.Add(fu.Bin); - } - - string nstr = string.Join("\r\n", hkeys.ToArray()); - string pstr = sb.ToString(); - if (pstr.Length > 0) - { } - - - } - public void TestAudioAwcs() - { - - StringBuilder sb = new StringBuilder(); - - Dictionary allids = new Dictionary(); - - foreach (RpfFile file in AllRpfs) - { - foreach (RpfEntry entry in file.AllEntries) - { - //try - //{ - var n = entry.NameLower; - if (n.EndsWith(".awc")) - { - UpdateStatus(string.Format(entry.Path)); - var awcfile = RpfMan.GetFile(entry); - if (awcfile != null) - { } - } - //} - //catch (Exception ex) - //{ - // UpdateStatus("Error! " + ex.ToString()); - //} - } - } - } - public void TestMetas() - { - //find all RSC meta files and generate the MetaTypes init code - - MetaTypes.Clear(); - foreach (RpfFile file in AllRpfs) - { - foreach (RpfEntry entry in file.AllEntries) - { - //try - { - var n = entry.NameLower; - //if (n.EndsWith(".ymap")) - //{ - // UpdateStatus(string.Format(entry.Path)); - // YmapFile ymapfile = RpfMan.GetFile(entry); - // if ((ymapfile != null) && (ymapfile.Meta != null)) - // { - // MetaTypes.EnsureMetaTypes(ymapfile.Meta); - // } - //} - //else if (n.EndsWith(".ytyp")) - //{ - // UpdateStatus(string.Format(entry.Path)); - // YtypFile ytypfile = RpfMan.GetFile(entry); - // if ((ytypfile != null) && (ytypfile.Meta != null)) - // { - // MetaTypes.EnsureMetaTypes(ytypfile.Meta); - // } - //} - //else if (n.EndsWith(".ymt")) - //{ - // UpdateStatus(string.Format(entry.Path)); - // YmtFile ymtfile = RpfMan.GetFile(entry); - // if ((ymtfile != null) && (ymtfile.Meta != null)) - // { - // MetaTypes.EnsureMetaTypes(ymtfile.Meta); - // } - //} - - - if (n.EndsWith(".ymap") || n.EndsWith(".ytyp") || n.EndsWith(".ymt")) - { - var rfe = entry as RpfResourceFileEntry; - if (rfe == null) continue; - - UpdateStatus(string.Format(entry.Path)); - - var data = rfe.File.ExtractFile(rfe); - ResourceDataReader rd = new ResourceDataReader(rfe, data); - var meta = rd.ReadBlock(); - var xml = MetaXml.GetXml(meta); - var xdoc = new XmlDocument(); - xdoc.LoadXml(xml); - var meta2 = XmlMeta.GetMeta(xdoc); - var xml2 = MetaXml.GetXml(meta2); - - if (xml.Length != xml2.Length) - { } - if ((xml != xml2) && (!n.EndsWith("srl.ymt") && !n.StartsWith("des_"))) - { } - - } - - - } - //catch (Exception ex) - //{ - // UpdateStatus("Error! " + ex.ToString()); - //} - } - } - - string str = MetaTypes.GetTypesInitString(); - - } - public void TestPsos() - { - //find all PSO meta files and generate the PsoTypes init code - PsoTypes.Clear(); - - var exceptions = new List(); - var allpsos = new List(); - var diffpsos = new List(); - - foreach (RpfFile file in AllRpfs) - { - foreach (RpfEntry entry in file.AllEntries) - { -#if !DEBUG - 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"))) - continue; //PSO files seem to only have these extensions - - var fentry = entry as RpfFileEntry; - var data = entry.File.ExtractFile(fentry); - if (data != null) - { - using (MemoryStream ms = new MemoryStream(data)) - { - if (PsoFile.IsPSO(ms)) - { - UpdateStatus(string.Format(entry.Path)); - - var pso = new PsoFile(); - pso.Load(ms); - - allpsos.Add(fentry.Path); - - PsoTypes.EnsurePsoTypes(pso); - - var xml = PsoXml.GetXml(pso); - if (!string.IsNullOrEmpty(xml)) - { } - - var xdoc = new XmlDocument(); - xdoc.LoadXml(xml); - var pso2 = XmlPso.GetPso(xdoc); - var pso2b = pso2.Save(); - - var pso3 = new PsoFile(); - pso3.Load(pso2b); - var xml3 = PsoXml.GetXml(pso3); - - if (xml.Length != xml3.Length) - { } - if (xml != xml3) - { - diffpsos.Add(fentry.Path); - } - - - //if (entry.NameLower == "clip_sets.ymt") - //{ } - //if (entry.NameLower == "vfxinteriorinfo.ymt") - //{ } - //if (entry.NameLower == "vfxvehicleinfo.ymt") - //{ } - //if (entry.NameLower == "vfxpedinfo.ymt") - //{ } - //if (entry.NameLower == "vfxregioninfo.ymt") - //{ } - //if (entry.NameLower == "vfxweaponinfo.ymt") - //{ } - //if (entry.NameLower == "physicstasks.ymt") - //{ } - - } - } - } - } -#if !DEBUG - catch (Exception ex) - { - UpdateStatus("Error! " + ex.ToString()); - exceptions.Add(ex); - } -#endif - } - } - - string allpsopaths = string.Join("\r\n", allpsos); - string diffpsopaths = string.Join("\r\n", diffpsos); - - string str = PsoTypes.GetTypesInitString(); - if (!string.IsNullOrEmpty(str)) - { - } - } - public void TestRbfs() - { - var exceptions = new List(); - var allrbfs = new List(); - var diffrbfs = new List(); - - foreach (RpfFile file in AllRpfs) - { - 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"))) - continue; //PSO files seem to only have these extensions - - var fentry = entry as RpfFileEntry; - var data = entry.File.ExtractFile(fentry); - if (data != null) - { - using (MemoryStream ms = new MemoryStream(data)) - { - if (RbfFile.IsRBF(ms)) - { - UpdateStatus(string.Format(entry.Path)); - - var rbf = new RbfFile(); - rbf.Load(ms); - - allrbfs.Add(fentry.Path); - - var xml = RbfXml.GetXml(rbf); - if (!string.IsNullOrEmpty(xml)) - { } - - var xdoc = new XmlDocument(); - xdoc.LoadXml(xml); - var rbf2 = XmlRbf.GetRbf(xdoc); - var rbf2b = rbf2.Save(); - - var rbf3 = new RbfFile(); - rbf3.Load(rbf2b); - var xml3 = RbfXml.GetXml(rbf3); - - if (xml.Length != xml3.Length) - { } - if (xml != xml3) - { - diffrbfs.Add(fentry.Path); - } - - if (data.Length != rbf2b.Length) - { - //File.WriteAllBytes("C:\\GitHub\\CodeWalkerResearch\\RBF\\" + fentry.Name + ".dat0", data); - //File.WriteAllBytes("C:\\GitHub\\CodeWalkerResearch\\RBF\\" + fentry.Name + ".dat1", rbf2b); - } - else - { - for (int i = 0; i < data.Length; i++) - { - if (data[i] != rbf2b[i]) - { - diffrbfs.Add(fentry.Path); - break; - } - } - } - - } - } - } - - } - } - - string allrbfpaths = string.Join("\r\n", allrbfs); - string diffrbfpaths = string.Join("\r\n", diffrbfs); - - } - public void TestCuts() - { - - var exceptions = new List(); - - foreach (RpfFile file in AllRpfs) - { - foreach (RpfEntry entry in file.AllEntries) - { -#if !DEBUG - try -#endif - { - var rfe = entry as RpfFileEntry; - if (rfe == null) continue; - - if (rfe.NameLower.EndsWith(".cut")) - { - UpdateStatus(string.Format(entry.Path)); - - CutFile cut = new CutFile(rfe); - RpfMan.LoadFile(cut, rfe); - - //PsoTypes.EnsurePsoTypes(cut.Pso); - } - } -#if !DEBUG - catch (Exception ex) - { - UpdateStatus("Error! " + ex.ToString()); - exceptions.Add(ex); - } -#endif - } - } - - string str = PsoTypes.GetTypesInitString(); - if (!string.IsNullOrEmpty(str)) - { - } - } - public void TestYlds() - { - - var exceptions = new List(); - - foreach (RpfFile file in AllRpfs) - { - foreach (RpfEntry entry in file.AllEntries) - { -#if !DEBUG - try -#endif - { - var rfe = entry as RpfFileEntry; - if (rfe == null) continue; - - if (rfe.NameLower.EndsWith(".yld")) - { - UpdateStatus(string.Format(entry.Path)); - - YldFile yld = new YldFile(rfe); - RpfMan.LoadFile(yld, rfe); - - } - } -#if !DEBUG - catch (Exception ex) - { - UpdateStatus("Error! " + ex.ToString()); - exceptions.Add(ex); - } -#endif - } - } - - if (exceptions.Count > 0) - { } - } - public void TestYeds() - { - - var exceptions = new List(); - - foreach (RpfFile file in AllRpfs) - { - foreach (RpfEntry entry in file.AllEntries) - { -#if !DEBUG - try -#endif - { - var rfe = entry as RpfFileEntry; - if (rfe == null) continue; - - if (rfe.NameLower.EndsWith(".yed")) - { - UpdateStatus(string.Format(entry.Path)); - - YedFile yed = new YedFile(rfe); - RpfMan.LoadFile(yed, rfe); - - var xml = YedXml.GetXml(yed); - var yed2 = XmlYed.GetYed(xml); - var data2 = yed2.Save(); - var yed3 = new YedFile(); - RpfFile.LoadResourceFile(yed3, data2, 25);//full roundtrip - var xml2 = YedXml.GetXml(yed3); - if (xml != xml2) - { } - - } - } -#if !DEBUG - catch (Exception ex) - { - UpdateStatus("Error! " + ex.ToString()); - exceptions.Add(ex); - } -#endif - } - } - - if (exceptions.Count > 0) - { } - } - public void TestYcds() - { - bool savetest = false; - var errorfiles = new List(); - var errorentries = new List(); - foreach (RpfFile file in AllRpfs) - { - foreach (RpfEntry entry in file.AllEntries) - { - //try - //{ - if (entry.NameLower.EndsWith(".ycd")) - { - UpdateStatus(string.Format(entry.Path)); - YcdFile ycd1 = RpfMan.GetFile(entry); - if (ycd1 == null) - { - errorentries.Add(entry); - } - else if (ycd1?.LoadException != null) - { - errorfiles.Add(ycd1);//these ones have file corruption issues and won't load as resource... - } - else if (savetest) - { - if (ycd1.ClipDictionary == null) - { continue; } - - //var data1 = ycd1.Save(); - - var xml = YcdXml.GetXml(ycd1); - var ycdX = XmlYcd.GetYcd(xml); - var data = ycdX.Save(); - var ycd2 = new YcdFile(); - RpfFile.LoadResourceFile(ycd2, data, 46);//full roundtrip - - { - if (ycd2 == null) - { continue; } - if (ycd2.ClipDictionary == null) - { continue; } - - var c1 = ycd1.ClipDictionary.Clips?.data_items; - var c2 = ycd2.ClipDictionary.Clips?.data_items; - if ((c1 == null) || (c2 == null)) - { continue; } - if (c1.Length != c2.Length) - { continue; } - - var a1 = ycd1.ClipDictionary.Animations?.Animations?.data_items; - var a2 = ycd2.ClipDictionary.Animations?.Animations?.data_items; - if ((a1 == null) || (a2 == null)) - { continue; } - if (a1.Length != a2.Length) - { continue; } - - var m1 = ycd1.AnimMap; - var m2 = ycd2.AnimMap; - if ((m1 == null) || (m2 == null)) - { continue; } - if (m1.Count != m2.Count) - { continue; } - foreach (var kvp1 in m1) - { - var an1 = kvp1.Value; - var an2 = an1; - if (!m2.TryGetValue(kvp1.Key, out an2)) - { continue; } - - var sa1 = an1?.Animation?.Sequences?.data_items; - var sa2 = an2?.Animation?.Sequences?.data_items; - if ((sa1 == null) || (sa2 == null)) - { continue; } - if (sa1.Length != sa2.Length) - { continue; } - for (int s = 0; s < sa1.Length; s++) - { - var s1 = sa1[s]; - var s2 = sa2[s]; - if ((s1?.Sequences == null) || (s2?.Sequences == null)) - { continue; } - - if (s1.NumFrames != s2.NumFrames) - { } - if (s1.ChunkSize != s2.ChunkSize) - { } - if (s1.FrameOffset != s2.FrameOffset) - { } - if (s1.DataLength != s2.DataLength) - { } - else - { - //for (int b = 0; b < s1.DataLength; b++) - //{ - // var b1 = s1.Data[b]; - // var b2 = s2.Data[b]; - // if (b1 != b2) - // { } - //} - } - - for (int ss = 0; ss < s1.Sequences.Length; ss++) - { - var ss1 = s1.Sequences[ss]; - var ss2 = s2.Sequences[ss]; - if ((ss1?.Channels == null) || (ss2?.Channels == null)) - { continue; } - if (ss1.Channels.Length != ss2.Channels.Length) - { continue; } - - - for (int c = 0; c < ss1.Channels.Length; c++) - { - var sc1 = ss1.Channels[c]; - var sc2 = ss2.Channels[c]; - if ((sc1 == null) || (sc2 == null)) - { continue; } - if (sc1.Type == AnimChannelType.LinearFloat) - { continue; } - if (sc1.Type != sc2.Type) - { continue; } - if (sc1.Index != sc2.Index) - { continue; } - if (sc1.Type == AnimChannelType.StaticQuaternion) - { - var acsq1 = sc1 as AnimChannelStaticQuaternion; - var acsq2 = sc2 as AnimChannelStaticQuaternion; - var vdiff = acsq1.Value - acsq2.Value; - var len = vdiff.Length(); - var v1len = Math.Max(acsq1.Value.Length(), 1); - if (len > 1e-2f * v1len) - { continue; } - } - else if (sc1.Type == AnimChannelType.StaticVector3) - { - var acsv1 = sc1 as AnimChannelStaticVector3; - var acsv2 = sc2 as AnimChannelStaticVector3; - var vdiff = acsv1.Value - acsv2.Value; - var len = vdiff.Length(); - var v1len = Math.Max(acsv1.Value.Length(), 1); - if (len > 1e-2f * v1len) - { continue; } - } - else if (sc1.Type == AnimChannelType.StaticFloat) - { - var acsf1 = sc1 as AnimChannelStaticFloat; - var acsf2 = sc2 as AnimChannelStaticFloat; - var vdiff = Math.Abs(acsf1.Value - acsf2.Value); - var v1len = Math.Max(Math.Abs(acsf1.Value), 1); - if (vdiff > 1e-2f * v1len) - { continue; } - } - else if (sc1.Type == AnimChannelType.RawFloat) - { - var acrf1 = sc1 as AnimChannelRawFloat; - var acrf2 = sc2 as AnimChannelRawFloat; - for (int v = 0; v < acrf1.Values.Length; v++) - { - var v1 = acrf1.Values[v]; - var v2 = acrf2.Values[v]; - var vdiff = Math.Abs(v1 - v2); - var v1len = Math.Max(Math.Abs(v1), 1); - if (vdiff > 1e-2f * v1len) - { break; } - } - } - else if (sc1.Type == AnimChannelType.QuantizeFloat) - { - var acqf1 = sc1 as AnimChannelQuantizeFloat; - var acqf2 = sc2 as AnimChannelQuantizeFloat; - if (acqf1.ValueBits != acqf2.ValueBits) - { continue; } - if (Math.Abs(acqf1.Offset - acqf2.Offset) > (0.001f * Math.Abs(acqf1.Offset))) - { continue; } - if (Math.Abs(acqf1.Quantum - acqf2.Quantum) > 0.00001f) - { continue; } - for (int v = 0; v < acqf1.Values.Length; v++) - { - var v1 = acqf1.Values[v]; - var v2 = acqf2.Values[v]; - var vdiff = Math.Abs(v1 - v2); - var v1len = Math.Max(Math.Abs(v1), 1); - if (vdiff > 1e-2f * v1len) - { break; } - } - } - else if (sc1.Type == AnimChannelType.IndirectQuantizeFloat) - { - var aciqf1 = sc1 as AnimChannelIndirectQuantizeFloat; - var aciqf2 = sc2 as AnimChannelIndirectQuantizeFloat; - if (aciqf1.FrameBits != aciqf2.FrameBits) - { continue; } - if (aciqf1.ValueBits != aciqf2.ValueBits) - { continue; } - if (Math.Abs(aciqf1.Offset - aciqf2.Offset) > (0.001f * Math.Abs(aciqf1.Offset))) - { continue; } - if (Math.Abs(aciqf1.Quantum - aciqf2.Quantum) > 0.00001f) - { continue; } - for (int f = 0; f < aciqf1.Frames.Length; f++) - { - if (aciqf1.Frames[f] != aciqf2.Frames[f]) - { break; } - } - for (int v = 0; v < aciqf1.Values.Length; v++) - { - var v1 = aciqf1.Values[v]; - var v2 = aciqf2.Values[v]; - var vdiff = Math.Abs(v1 - v2); - var v1len = Math.Max(Math.Abs(v1), 1); - if (vdiff > 1e-2f * v1len) - { break; } - } - } - else if ((sc1.Type == AnimChannelType.CachedQuaternion1) || (sc1.Type == AnimChannelType.CachedQuaternion2)) - { - var acrf1 = sc1 as AnimChannelCachedQuaternion; - var acrf2 = sc2 as AnimChannelCachedQuaternion; - if (acrf1.QuatIndex != acrf2.QuatIndex) - { continue; } - } - - - - - } - - - //for (int f = 0; f < s1.NumFrames; f++) - //{ - // var v1 = ss1.EvaluateVector(f); - // var v2 = ss2.EvaluateVector(f); - // var vdiff = v1 - v2; - // var len = vdiff.Length(); - // var v1len = Math.Max(v1.Length(), 1); - // if (len > 1e-2f*v1len) - // { } - //} - } - - - } - - - } - - - } - - } - } - //if (entry.NameLower.EndsWith(".awc")) //awcs can also contain clip dicts.. - //{ - // UpdateStatus(string.Format(entry.Path)); - // AwcFile awcfile = RpfMan.GetFile(entry); - // if ((awcfile != null)) - // { } - //} - //} - //catch (Exception ex) - //{ - // UpdateStatus("Error! " + ex.ToString()); - //} - } - } - - if (errorfiles.Count > 0) - { } - - } - public void TestYtds() - { - bool ddstest = false; - bool savetest = false; - var errorfiles = new List(); - foreach (RpfFile file in AllRpfs) - { - foreach (RpfEntry entry in file.AllEntries) - { - //try - { - if (entry.NameLower.EndsWith(".ytd")) - { - UpdateStatus(string.Format(entry.Path)); - YtdFile ytdfile = null; - try - { - ytdfile = RpfMan.GetFile(entry); - } - catch(Exception ex) - { - UpdateStatus("Error! " + ex.ToString()); - errorfiles.Add(entry); - } - if (ddstest && (ytdfile != null) && (ytdfile.TextureDict != null)) - { - foreach (var tex in ytdfile.TextureDict.Textures.data_items) - { - var dds = Utils.DDSIO.GetDDSFile(tex); - var tex2 = Utils.DDSIO.GetTexture(dds); - if (!tex.Name.StartsWith("script_rt")) - { - if (tex.Data?.FullData?.Length != tex2.Data?.FullData?.Length) - { } - if (tex.Stride != tex2.Stride) - { } - } - if ((tex.Format != tex2.Format) || (tex.Width != tex2.Width) || (tex.Height != tex2.Height) || (tex.Depth != tex2.Depth) || (tex.Levels != tex2.Levels)) - { } - } - } - if (savetest && (ytdfile != null) && (ytdfile.TextureDict != null)) - { - var fentry = entry as RpfFileEntry; - if (fentry == null) - { continue; } //shouldn't happen - - var bytes = ytdfile.Save(); - - string origlen = TextUtil.GetBytesReadable(fentry.FileSize); - string bytelen = TextUtil.GetBytesReadable(bytes.Length); - - if (ytdfile.TextureDict.Textures?.Count == 0) - { } - - - var ytd2 = new YtdFile(); - //ytd2.Load(bytes, fentry); - RpfFile.LoadResourceFile(ytd2, bytes, 13); - - if (ytd2.TextureDict == null) - { continue; } - if (ytd2.TextureDict.Textures?.Count != ytdfile.TextureDict.Textures?.Count) - { continue; } - - for (int i = 0; i < ytdfile.TextureDict.Textures.Count; i++) - { - var tx1 = ytdfile.TextureDict.Textures[i]; - var tx2 = ytd2.TextureDict.Textures[i]; - var td1 = tx1.Data; - var td2 = tx2.Data; - if (td1.FullData.Length != td2.FullData.Length) - { continue; } - - for (int j = 0; j < td1.FullData.Length; j++) - { - if (td1.FullData[j] != td2.FullData[j]) - { break; } - } - - } - - } - } - } - //catch (Exception ex) - //{ - // UpdateStatus("Error! " + ex.ToString()); - //} - } - } - if (errorfiles.Count > 0) - { } - } - public void TestYbns() - { - bool xmltest = false; - bool savetest = false; - bool reloadtest = false; - var errorfiles = new List(); - foreach (RpfFile file in AllRpfs) - { - foreach (RpfEntry entry in file.AllEntries) - { - //try - { - if (entry.NameLower.EndsWith(".ybn")) - { - UpdateStatus(string.Format(entry.Path)); - YbnFile ybn = null; - try - { - ybn = RpfMan.GetFile(entry); - } - catch (Exception ex) - { - UpdateStatus("Error! " + ex.ToString()); - errorfiles.Add(entry); - } - if (xmltest && (ybn != null) && (ybn.Bounds != null)) - { - var xml = YbnXml.GetXml(ybn); - var ybn2 = XmlYbn.GetYbn(xml); - var xml2 = YbnXml.GetXml(ybn2); - if (xml.Length != xml2.Length) - { } - } - if (savetest && (ybn != null) && (ybn.Bounds != null)) - { - var fentry = entry as RpfFileEntry; - if (fentry == null) - { continue; } //shouldn't happen - - var bytes = ybn.Save(); - - if (!reloadtest) - { continue; } - - string origlen = TextUtil.GetBytesReadable(fentry.FileSize); - string bytelen = TextUtil.GetBytesReadable(bytes.Length); - - - var ybn2 = new YbnFile(); - RpfFile.LoadResourceFile(ybn2, bytes, 43); - - if (ybn2.Bounds == null) - { continue; } - if (ybn2.Bounds.Type != ybn.Bounds.Type) - { continue; } - - //quick check of roundtrip - switch (ybn2.Bounds.Type) - { - case BoundsType.Sphere: - { - var a = ybn.Bounds as BoundSphere; - var b = ybn2.Bounds as BoundSphere; - if (b == null) - { continue; } - break; - } - case BoundsType.Capsule: - { - var a = ybn.Bounds as BoundCapsule; - var b = ybn2.Bounds as BoundCapsule; - if (b == null) - { continue; } - break; - } - case BoundsType.Box: - { - var a = ybn.Bounds as BoundBox; - var b = ybn2.Bounds as BoundBox; - if (b == null) - { continue; } - break; - } - case BoundsType.Geometry: - { - var a = ybn.Bounds as BoundGeometry; - var b = ybn2.Bounds as BoundGeometry; - if (b == null) - { continue; } - if (a.Polygons?.Length != b.Polygons?.Length) - { continue; } - for (int i = 0; i < a.Polygons.Length; i++) - { - var pa = a.Polygons[i]; - var pb = b.Polygons[i]; - if (pa.Type != pb.Type) - { } - } - break; - } - case BoundsType.GeometryBVH: - { - var a = ybn.Bounds as BoundBVH; - var b = ybn2.Bounds as BoundBVH; - if (b == null) - { continue; } - if (a.BVH?.Nodes?.data_items?.Length != b.BVH?.Nodes?.data_items?.Length) - { } - if (a.Polygons?.Length != b.Polygons?.Length) - { continue; } - for (int i = 0; i < a.Polygons.Length; i++) - { - var pa = a.Polygons[i]; - var pb = b.Polygons[i]; - if (pa.Type != pb.Type) - { } - } - break; - } - case BoundsType.Composite: - { - var a = ybn.Bounds as BoundComposite; - var b = ybn2.Bounds as BoundComposite; - if (b == null) - { continue; } - if (a.Children?.data_items?.Length != b.Children?.data_items?.Length) - { } - break; - } - case BoundsType.Disc: - { - var a = ybn.Bounds as BoundDisc; - var b = ybn2.Bounds as BoundDisc; - if (b == null) - { continue; } - break; - } - case BoundsType.Cylinder: - { - var a = ybn.Bounds as BoundCylinder; - var b = ybn2.Bounds as BoundCylinder; - if (b == null) - { continue; } - break; - } - case BoundsType.Cloth: - { - var a = ybn.Bounds as BoundCloth; - var b = ybn2.Bounds as BoundCloth; - if (b == null) - { continue; } - break; - } - default: //return null; // throw new Exception("Unknown bound type"); - break; - } - - - - } - } - } - //catch (Exception ex) - //{ - // UpdateStatus("Error! " + ex.ToString()); - //} - } - } - if (errorfiles.Count > 0) - { } - } - public void TestYdrs() - { - bool savetest = false; - bool boundsonly = true; - var errorfiles = new List(); - foreach (RpfFile file in AllRpfs) - { - foreach (RpfEntry entry in file.AllEntries) - { - //try - { - if (entry.NameLower.EndsWith(".ydr")) - { - UpdateStatus(string.Format(entry.Path)); - YdrFile ydr = null; - try - { - ydr = RpfMan.GetFile(entry); - } - catch (Exception ex) - { - UpdateStatus("Error! " + ex.ToString()); - errorfiles.Add(entry); - } - if (savetest && (ydr != null) && (ydr.Drawable != null)) - { - var fentry = entry as RpfFileEntry; - if (fentry == null) - { continue; } //shouldn't happen - - if (boundsonly && (ydr.Drawable.Bound == null)) - { continue; } - - var bytes = ydr.Save(); - - string origlen = TextUtil.GetBytesReadable(fentry.FileSize); - string bytelen = TextUtil.GetBytesReadable(bytes.Length); - - var ydr2 = new YdrFile(); - RpfFile.LoadResourceFile(ydr2, bytes, 165); - - if (ydr2.Drawable == null) - { continue; } - if (ydr2.Drawable.AllModels?.Length != ydr.Drawable.AllModels?.Length) - { continue; } - - } - } - } - //catch (Exception ex) - //{ - // UpdateStatus("Error! " + ex.ToString()); - //} - } - } - if (errorfiles.Count != 13) - { } - } - public void TestYdds() - { - bool savetest = false; - var errorfiles = new List(); - foreach (RpfFile file in AllRpfs) - { - foreach (RpfEntry entry in file.AllEntries) - { - //try - { - if (entry.NameLower.EndsWith(".ydd")) - { - UpdateStatus(string.Format(entry.Path)); - YddFile ydd = null; - try - { - ydd = RpfMan.GetFile(entry); - } - catch (Exception ex) - { - UpdateStatus("Error! " + ex.ToString()); - errorfiles.Add(entry); - } - if (savetest && (ydd != null) && (ydd.DrawableDict != null)) - { - var fentry = entry as RpfFileEntry; - if (fentry == null) - { continue; } //shouldn't happen - - var bytes = ydd.Save(); - - string origlen = TextUtil.GetBytesReadable(fentry.FileSize); - string bytelen = TextUtil.GetBytesReadable(bytes.Length); - - - var ydd2 = new YddFile(); - RpfFile.LoadResourceFile(ydd2, bytes, 165); - - if (ydd2.DrawableDict == null) - { continue; } - if (ydd2.DrawableDict.Drawables?.Count != ydd.DrawableDict.Drawables?.Count) - { continue; } - - } - if (ydd?.DrawableDict?.Hashes != null) - { - uint h = 0; - foreach (uint th in ydd.DrawableDict.Hashes) - { - if (th <= h) - { } //should never happen - h = th; - } - } - } - } - //catch (Exception ex) - //{ - // UpdateStatus("Error! " + ex.ToString()); - //} - } - } - if (errorfiles.Count > 0) - { } - } - public void TestYfts() - { - bool savetest = false; - var errorfiles = new List(); - var sb = new StringBuilder(); - var flagdict = new Dictionary(); - foreach (RpfFile file in AllRpfs) - { - foreach (RpfEntry entry in file.AllEntries) - { - //try - { - if (entry.NameLower.EndsWith(".yft")) - { - UpdateStatus(string.Format(entry.Path)); - YftFile yft = null; - try - { - yft = RpfMan.GetFile(entry); - } - catch (Exception ex) - { - UpdateStatus("Error! " + ex.ToString()); - errorfiles.Add(entry); - } - if (savetest && (yft != null) && (yft.Fragment != null)) - { - var fentry = entry as RpfFileEntry; - if (fentry == null) - { continue; } //shouldn't happen - - var bytes = yft.Save(); - - - string origlen = TextUtil.GetBytesReadable(fentry.FileSize); - string bytelen = TextUtil.GetBytesReadable(bytes.Length); - - var yft2 = new YftFile(); - RpfFile.LoadResourceFile(yft2, bytes, 162); - - if (yft2.Fragment == null) - { continue; } - if (yft2.Fragment.Drawable?.AllModels?.Length != yft.Fragment.Drawable?.AllModels?.Length) - { continue; } - - } - - if (yft?.Fragment?.GlassWindows?.data_items != null) - { - var lastf = -1; - for (int i = 0; i < yft.Fragment.GlassWindows.data_items.Length; i++) - { - var w = yft.Fragment.GlassWindows.data_items[i]; - if (w.Flags == lastf) continue; - lastf = w.Flags; - flagdict.TryGetValue(w.Flags, out int n); - if (n < 10) - { - flagdict[w.Flags] = n + 1; - sb.AppendLine(entry.Path + " Window " + i.ToString() + ": Flags " + w.Flags.ToString() + ", Low:" + w.FlagsLo.ToString() + ", High:" + w.FlagsHi.ToString()); - } - } - } - - } - } - //catch (Exception ex) - //{ - // UpdateStatus("Error! " + ex.ToString()); - //} - } - } - var teststr = sb.ToString(); - - if (errorfiles.Count > 0) - { } - } - public void TestYpts() - { - var savetest = false; - var errorfiles = new List(); - foreach (RpfFile file in AllRpfs) - { - foreach (RpfEntry entry in file.AllEntries) - { - //try - { - if (entry.NameLower.EndsWith(".ypt")) - { - UpdateStatus(string.Format(entry.Path)); - YptFile ypt = null; - try - { - ypt = RpfMan.GetFile(entry); - } - catch (Exception ex) - { - UpdateStatus("Error! " + ex.ToString()); - errorfiles.Add(entry); - } - if (savetest && (ypt != null) && (ypt.PtfxList != null)) - { - var fentry = entry as RpfFileEntry; - if (fentry == null) - { continue; } //shouldn't happen - - var bytes = ypt.Save(); - - - string origlen = TextUtil.GetBytesReadable(fentry.FileSize); - string bytelen = TextUtil.GetBytesReadable(bytes.Length); - - var ypt2 = new YptFile(); - RpfFile.LoadResourceFile(ypt2, bytes, 68); - - if (ypt2.PtfxList == null) - { continue; } - if (ypt2.PtfxList.Name?.Value != ypt.PtfxList.Name?.Value) - { continue; } - - } - } - } - //catch (Exception ex) - //{ - // UpdateStatus("Error! " + ex.ToString()); - //} - } - } - if (errorfiles.Count > 0) - { } - } - public void TestYnvs() - { - bool xmltest = true; - var savetest = false; - var errorfiles = new List(); - foreach (RpfFile file in AllRpfs) - { - foreach (RpfEntry entry in file.AllEntries) - { - //try - { - if (entry.NameLower.EndsWith(".ynv")) - { - UpdateStatus(string.Format(entry.Path)); - YnvFile ynv = null; - try - { - ynv = RpfMan.GetFile(entry); - } - catch (Exception ex) - { - UpdateStatus("Error! " + ex.ToString()); - errorfiles.Add(entry); - } - if (xmltest && (ynv != null) && (ynv.Nav != null)) - { - var xml = YnvXml.GetXml(ynv); - if (xml != null) - { } - var ynv2 = XmlYnv.GetYnv(xml); - if (ynv2 != null) - { } - var ynv2b = ynv2.Save(); - if (ynv2b != null) - { } - var ynv3 = new YnvFile(); - RpfFile.LoadResourceFile(ynv3, ynv2b, 2); - var xml3 = YnvXml.GetXml(ynv3); - if (xml.Length != xml3.Length) - { } - var xmllines = xml.Split('\n'); - var xml3lines = xml3.Split('\n'); - if (xmllines.Length != xml3lines.Length) - { } - } - if (savetest && (ynv != null) && (ynv.Nav != null)) - { - var fentry = entry as RpfFileEntry; - if (fentry == null) - { continue; } //shouldn't happen - - var bytes = ynv.Save(); - - string origlen = TextUtil.GetBytesReadable(fentry.FileSize); - string bytelen = TextUtil.GetBytesReadable(bytes.Length); - - var ynv2 = new YnvFile(); - RpfFile.LoadResourceFile(ynv2, bytes, 2); - - if (ynv2.Nav == null) - { continue; } - - } - } - } - //catch (Exception ex) - //{ - // UpdateStatus("Error! " + ex.ToString()); - //} - } - } - if (errorfiles.Count > 0) - { } - } - public void TestYvrs() - { - - var exceptions = new List(); - - foreach (RpfFile file in AllRpfs) - { - foreach (RpfEntry entry in file.AllEntries) - { -#if !DEBUG - try -#endif - { - var rfe = entry as RpfFileEntry; - if (rfe == null) continue; - - if (rfe.NameLower.EndsWith(".yvr")) - { - if (rfe.NameLower == "agencyprep001.yvr") continue; //this file seems corrupted - - UpdateStatus(string.Format(entry.Path)); - - YvrFile yvr = new YvrFile(rfe); - RpfMan.LoadFile(yvr, rfe); - - var xml = YvrXml.GetXml(yvr); - var yvr2 = XmlYvr.GetYvr(xml); - var data2 = yvr2.Save(); - var yvr3 = new YvrFile(); - RpfFile.LoadResourceFile(yvr3, data2, 1);//full roundtrip - var xml2 = YvrXml.GetXml(yvr3); - if (xml != xml2) - { } - - } - } -#if !DEBUG - catch (Exception ex) - { - UpdateStatus("Error! " + ex.ToString()); - exceptions.Add(ex); - } -#endif - } - } - - if (exceptions.Count > 0) - { } - } - public void TestYwrs() - { - - var exceptions = new List(); - - foreach (RpfFile file in AllRpfs) - { - foreach (RpfEntry entry in file.AllEntries) - { -#if !DEBUG - try -#endif - { - var rfe = entry as RpfFileEntry; - if (rfe == null) continue; - - if (rfe.NameLower.EndsWith(".ywr")) - { - UpdateStatus(string.Format(entry.Path)); - - YwrFile ywr = new YwrFile(rfe); - RpfMan.LoadFile(ywr, rfe); - - var xml = YwrXml.GetXml(ywr); - var ywr2 = XmlYwr.GetYwr(xml); - var data2 = ywr2.Save(); - var ywr3 = new YwrFile(); - RpfFile.LoadResourceFile(ywr3, data2, 1);//full roundtrip - var xml2 = YwrXml.GetXml(ywr3); - if (xml != xml2) - { } - - } - } -#if !DEBUG - catch (Exception ex) - { - UpdateStatus("Error! " + ex.ToString()); - exceptions.Add(ex); - } -#endif - } - } - - if (exceptions.Count > 0) - { } - } - public void TestYmaps() - { - foreach (RpfFile file in AllRpfs) - { - foreach (RpfEntry entry in file.AllEntries) - { - try - { - if (entry.NameLower.EndsWith(".ymap")) - { - UpdateStatus(string.Format(entry.Path)); - YmapFile ymapfile = RpfMan.GetFile(entry); - if ((ymapfile != null))// && (ymapfile.Meta != null)) - { } - } - } - catch (Exception ex) - { - UpdateStatus("Error! " + ex.ToString()); - } - } - } - } - public void TestYpdbs() - { - foreach (RpfFile file in AllRpfs) - { - foreach (RpfEntry entry in file.AllEntries) - { - var rfe = entry as RpfFileEntry; - if (rfe == null) continue; - - try - { - if (rfe.NameLower.EndsWith(".ypdb")) - { - UpdateStatus(string.Format(entry.Path)); - YpdbFile ypdb = RpfMan.GetFile(entry); - if (ypdb != null) - { - var odata = entry.File.ExtractFile(entry as RpfFileEntry); - //var ndata = ypdb.Save(); - - var xml = YpdbXml.GetXml(ypdb); - var ypdb2 = XmlYpdb.GetYpdb(xml); - var ndata = ypdb2.Save(); - - if (ndata.Length == odata.Length) - { - for (int i = 0; i < ndata.Length; i++) - { - if (ndata[i] != odata[i]) - { break; } - } - } - else - { } - } - else - { } - } - } - catch (Exception ex) - { - UpdateStatus("Error! " + ex.ToString()); - } - - } - } - } - public void TestYfds() - { - foreach (RpfFile file in AllRpfs) - { - foreach (RpfEntry entry in file.AllEntries) - { - var rfe = entry as RpfFileEntry; - if (rfe == null) continue; - - try - { - if (rfe.NameLower.EndsWith(".yfd")) - { - UpdateStatus(string.Format(entry.Path)); - YfdFile yfd = RpfMan.GetFile(entry); - if (yfd != null) - { - if (yfd.FrameFilterDictionary != null) - { - // check that all signatures can be re-calculated - foreach (var f in yfd.FrameFilterDictionary.Filters.data_items) - { - if (f.Signature != f.CalculateSignature()) - { } - } - } - - var xml = YfdXml.GetXml(yfd); - var yfd2 = XmlYfd.GetYfd(xml); - var data2 = yfd2.Save(); - var yfd3 = new YfdFile(); - RpfFile.LoadResourceFile(yfd3, data2, 4);//full roundtrip - var xml2 = YfdXml.GetXml(yfd3); - if (xml != xml2) - { } - } - else - { } - } - } - catch (Exception ex) - { - UpdateStatus("Error! " + ex.ToString()); - } - - } - } - } - public void TestMrfs() - { - foreach (RpfFile file in AllRpfs) - { - foreach (RpfEntry entry in file.AllEntries) - { - try - { - if (entry.NameLower.EndsWith(".mrf")) - { - UpdateStatus(string.Format(entry.Path)); - MrfFile mrffile = RpfMan.GetFile(entry); - if (mrffile != null) - { - var odata = entry.File.ExtractFile(entry as RpfFileEntry); - var ndata = mrffile.Save(); - if (ndata.Length == odata.Length) - { - for (int i = 0; i < ndata.Length; i++) - { - if (ndata[i] != odata[i]) - { break; } - } - } - else - { } - - var xml = MrfXml.GetXml(mrffile); - var mrf2 = XmlMrf.GetMrf(xml); - var ndata2 = mrf2.Save(); - if (ndata2.Length == odata.Length) - { - for (int i = 0; i < ndata2.Length; i++) - { - if (ndata2[i] != odata[i] && !mrfDiffCanBeIgnored(i, mrffile)) - { break; } - } - } - else - { } - - bool mrfDiffCanBeIgnored(int fileOffset, MrfFile originalMrf) - { - foreach (var n in originalMrf.AllNodes) - { - if (n is MrfNodeStateBase state) - { - // If TransitionCount is 0, the TransitionsOffset value can be ignored. - // TransitionsOffset in original MRFs isn't always set to 0 in this case, - // XML-imported MRFs always set it to 0 - if (state.TransitionCount == 0 && fileOffset == (state.FileOffset + 0x1C)) - { - return true; - } - } - } - - return false; - } - } - else - { } - } - } - catch (Exception ex) - { - UpdateStatus("Error! " + ex.ToString()); - } - } - } - - // create and save a custom MRF - { - // Usage example: - // RequestAnimDict("move_m@alien") - // TaskMoveNetworkByName(PlayerPedId(), "mymrf", 0.0, true, 0, 0) - // SetTaskMoveNetworkSignalFloat(PlayerPedId(), "sprintrate", 2.0) - var mymrf = new MrfFile(); - var clip1 = new MrfNodeClip - { - NodeIndex = 0, - Name = JenkHash.GenHash("clip1"), - ClipType = MrfValueType.Literal, - ClipContainerType = MrfClipContainerType.ClipDictionary, - ClipContainerName = JenkHash.GenHash("move_m@alien"), - ClipName = JenkHash.GenHash("alien_run"), - LoopedType = MrfValueType.Literal, - Looped = true, - }; - var clip2 = new MrfNodeClip - { - NodeIndex = 0, - Name = JenkHash.GenHash("clip2"), - ClipType = MrfValueType.Literal, - ClipContainerType = MrfClipContainerType.ClipDictionary, - ClipContainerName = JenkHash.GenHash("move_m@alien"), - ClipName = JenkHash.GenHash("alien_sprint"), - LoopedType = MrfValueType.Literal, - Looped = true, - RateType = MrfValueType.Parameter, - RateParameterName = JenkHash.GenHash("sprintrate"), - }; - var clipstate1 = new MrfNodeState - { - NodeIndex = 0, - Name = JenkHash.GenHash("clipstate1"), - InitialNode = clip1, - Transitions = new[] - { - new MrfStateTransition - { - Duration = 2.5f, - HasDurationParameter = false, - //TargetState = clipstate2, - Conditions = new[] - { - new MrfConditionTimeGreaterThan { Value = 4.0f }, - }, - } - }, - }; - var clipstate2 = new MrfNodeState - { - NodeIndex = 1, - Name = JenkHash.GenHash("clipstate2"), - InitialNode = clip2, - Transitions = new[] - { - new MrfStateTransition - { - Duration = 2.5f, - HasDurationParameter = false, - //TargetState = clipstate1, - Conditions = new[] - { - new MrfConditionTimeGreaterThan { Value = 4.0f }, - }, - } - }, - }; - clipstate1.Transitions[0].TargetState = clipstate2; - clipstate2.Transitions[0].TargetState = clipstate1; - var rootsm = new MrfNodeStateMachine - { - NodeIndex = 0, - Name = JenkHash.GenHash("statemachine"), - States = new[] - { - new MrfStateRef { StateName = clipstate1.Name, State = clipstate1 }, - new MrfStateRef { StateName = clipstate2.Name, State = clipstate2 }, - }, - InitialNode = clipstate1, - }; - mymrf.AllNodes = new MrfNode[] - { - rootsm, - clipstate1, - clip1, - clipstate2, - clip2, - }; - mymrf.RootState = rootsm; - - var mymrfData = mymrf.Save(); - //File.WriteAllBytes("mymrf.mrf", mymrfData); - //File.WriteAllText("mymrf.dot", mymrf.DumpStateGraph()); - } - } - public void TestFxcs() - { - foreach (RpfFile file in AllRpfs) - { - foreach (RpfEntry entry in file.AllEntries) - { - try - { - if (entry.NameLower.EndsWith(".fxc")) - { - UpdateStatus(string.Format(entry.Path)); - var fxcfile = RpfMan.GetFile(entry); - if (fxcfile != null) - { - var odata = entry.File.ExtractFile(entry as RpfFileEntry); - var ndata = fxcfile.Save(); - if (ndata.Length == odata.Length) - { - for (int i = 0; i < ndata.Length; i++) - { - if (ndata[i] != odata[i]) - { break; } - } - } - else - { } - - var xml1 = FxcXml.GetXml(fxcfile);//won't output bytecodes with no output folder - var fxc1 = XmlFxc.GetFxc(xml1); - var xml2 = FxcXml.GetXml(fxc1); - if (xml1 != xml2) - { } - - - for (int i = 0; i < fxcfile.Shaders.Length; i++) - { - if (fxc1.Shaders[i].Name != fxcfile.Shaders[i].Name) - { } - fxc1.Shaders[i].ByteCode = fxcfile.Shaders[i].ByteCode; - } - - var xdata = fxc1.Save(); - if (xdata.Length == odata.Length) - { - for (int i = 0; i < xdata.Length; i++) - { - if (xdata[i] != odata[i]) - { break; } - } - } - else - { } - - - } - else - { } - } - } - catch (Exception ex) - { - UpdateStatus("Error! " + ex.ToString()); - } - } - } - } - public void TestPlacements() - { - //int totplacements = 0; - //int tottimedplacements = 0; - //int totaudioplacements = 0; - //StringBuilder sbtest = new StringBuilder(); - //StringBuilder sbterr = new StringBuilder(); - //sbtest.AppendLine("X, Y, Z, name, assetName, drawableDictionary, textureDictionary, ymap"); - //foreach (RpfFile file in RpfMan.AllRpfs) - //{ - // foreach (RpfEntry entry in file.AllEntries) - // { - // try - // { - // if (entry.NameLower.EndsWith(".ymap")) - // { - // UpdateStatus(string.Format(entry.Path)); - // YmapFile ymapfile = RpfMan.GetFile(entry); - // if ((ymapfile != null))// && (ymapfile.Meta != null)) - // { - // //if (ymapfile.CMapData.parent == 0) //root ymap output - // //{ - // // sbtest.AppendLine(JenkIndex.GetString(ymapfile.CMapData.name) + ": " + entry.Path); - // //} - // if (ymapfile.CEntityDefs != null) - // { - // for (int n = 0; n < ymapfile.CEntityDefs.Length; n++) - // { - // //find ytyp... - // var entdef = ymapfile.CEntityDefs[n]; - // var pos = entdef.position; - // bool istimed = false; - // Tuple archetyp; - // if (!BaseArchetypes.TryGetValue(entdef.archetypeName, out archetyp)) - // { - // sbterr.AppendLine("Couldn't find ytyp for " + entdef.ToString()); - // } - // else - // { - // int ymapbasecount = (archetyp.Item1.CBaseArchetypeDefs != null) ? archetyp.Item1.CBaseArchetypeDefs.Length : 0; - // int baseoffset = archetyp.Item2 - ymapbasecount; - // if (baseoffset >= 0) - // { - // if ((archetyp.Item1.CTimeArchetypeDefs == null) || (baseoffset > archetyp.Item1.CTimeArchetypeDefs.Length)) - // { - // sbterr.AppendLine("Couldn't lookup CTimeArchetypeDef... " + archetyp.ToString()); - // continue; - // } - - // istimed = true; - - // //it's a CTimeArchetypeDef... - // CTimeArchetypeDef ctad = archetyp.Item1.CTimeArchetypeDefs[baseoffset]; - - // //if (ctad.ToString().Contains("spider")) - // //{ - // //} - // //sbtest.AppendFormat("{0}, {1}, {2}, {3}, {4}", pos.X, pos.Y, pos.Z, ctad.ToString(), entry.Name); - // //sbtest.AppendLine(); - - // tottimedplacements++; - // } - // totplacements++; - // } - - // Tuple audiotyp; - // if (AudioArchetypes.TryGetValue(entdef.archetypeName, out audiotyp)) - // { - // if (istimed) - // { - // } - // if (!BaseArchetypes.TryGetValue(entdef.archetypeName, out archetyp)) - // { - // sbterr.AppendLine("Couldn't find ytyp for " + entdef.ToString()); - // } - // if (audiotyp.Item1 != archetyp.Item1) - // { - // } - - // CBaseArchetypeDef cbad = archetyp.Item1.CBaseArchetypeDefs[archetyp.Item2]; - // CExtensionDefAudioEmitter emitr = audiotyp.Item1.AudioEmitters[audiotyp.Item2]; - - // if (emitr.name != cbad.name) - // { - // } - - // string hashtest = JenkIndex.GetString(emitr.effectHash); - - // sbtest.AppendFormat("{0}, {1}, {2}, {3}, {4}, {5}", pos.X, pos.Y, pos.Z, cbad.ToString(), entry.Name, hashtest); - // sbtest.AppendLine(); - - // totaudioplacements++; - // } - - // } - // } - - // //if (ymapfile.TimeCycleModifiers != null) - // //{ - // // for (int n = 0; n < ymapfile.TimeCycleModifiers.Length; n++) - // // { - // // var tcmod = ymapfile.TimeCycleModifiers[n]; - // // Tuple archetyp; - // // if (BaseArchetypes.TryGetValue(tcmod.name, out archetyp)) - // // { - // // } - // // else - // // { - // // } - // // } - // //} - // } - // } - // } - // catch (Exception ex) - // { - // sbterr.AppendLine(entry.Path + ": " + ex.ToString()); - // } - // } - //} - - //UpdateStatus("Ymap scan finished."); - - //sbtest.AppendLine(); - //sbtest.AppendLine(totplacements.ToString() + " total CEntityDef placements parsed"); - //sbtest.AppendLine(tottimedplacements.ToString() + " total CTimeArchetypeDef placements"); - //sbtest.AppendLine(totaudioplacements.ToString() + " total CExtensionDefAudioEmitter placements"); - - //string teststr = sbtest.ToString(); - //string testerr = sbterr.ToString(); - - //return; - } - public void TestDrawables() - { - - - DateTime starttime = DateTime.Now; - - bool doydr = false; - bool doydd = false; - bool doyft = true; - - List errs = new List(); - Dictionary vdecls = new Dictionary(); - Dictionary vdecluse = new Dictionary(); - int drawablecount = 0; - foreach (RpfFile file in AllRpfs) - { - foreach (RpfEntry entry in file.AllEntries) - { - try - { - if (doydr && entry.NameLower.EndsWith(".ydr")) - { - UpdateStatus(entry.Path); - YdrFile ydr = RpfMan.GetFile(entry); - - if (ydr == null) - { - errs.Add(entry.Path + ": Couldn't read file"); - continue; - } - if (ydr.Drawable == null) - { - errs.Add(entry.Path + ": Couldn't read drawable data"); - continue; - } - drawablecount++; - foreach (var kvp in ydr.Drawable.VertexDecls) - { - if (!vdecls.ContainsKey(kvp.Key)) - { - vdecls.Add(kvp.Key, kvp.Value); - vdecluse.Add(kvp.Key, 1); - } - else - { - vdecluse[kvp.Key]++; - } - } - } - else if (doydd & entry.NameLower.EndsWith(".ydd")) - { - UpdateStatus(entry.Path); - YddFile ydd = RpfMan.GetFile(entry); - - if (ydd == null) - { - errs.Add(entry.Path + ": Couldn't read file"); - continue; - } - if (ydd.Dict == null) - { - errs.Add(entry.Path + ": Couldn't read drawable dictionary data"); - continue; - } - foreach (var drawable in ydd.Dict.Values) - { - drawablecount++; - foreach (var kvp in drawable.VertexDecls) - { - if (!vdecls.ContainsKey(kvp.Key)) - { - vdecls.Add(kvp.Key, kvp.Value); - vdecluse.Add(kvp.Key, 1); - } - else - { - vdecluse[kvp.Key]++; - } - } - } - } - else if (doyft && entry.NameLower.EndsWith(".yft")) - { - UpdateStatus(entry.Path); - YftFile yft = RpfMan.GetFile(entry); - - if (yft == null) - { - errs.Add(entry.Path + ": Couldn't read file"); - continue; - } - if (yft.Fragment == null) - { - errs.Add(entry.Path + ": Couldn't read fragment data"); - continue; - } - if (yft.Fragment.Drawable != null) - { - drawablecount++; - foreach (var kvp in yft.Fragment.Drawable.VertexDecls) - { - if (!vdecls.ContainsKey(kvp.Key)) - { - vdecls.Add(kvp.Key, kvp.Value); - vdecluse.Add(kvp.Key, 1); - } - else - { - vdecluse[kvp.Key]++; - } - } - } - if ((yft.Fragment.Cloths != null) && (yft.Fragment.Cloths.data_items != null)) - { - foreach (var cloth in yft.Fragment.Cloths.data_items) - { - drawablecount++; - foreach (var kvp in cloth.Drawable.VertexDecls) - { - if (!vdecls.ContainsKey(kvp.Key)) - { - vdecls.Add(kvp.Key, kvp.Value); - vdecluse.Add(kvp.Key, 1); - } - else - { - vdecluse[kvp.Key]++; - } - } - } - } - if ((yft.Fragment.DrawableArray != null) && (yft.Fragment.DrawableArray.data_items != null)) - { - foreach (var drawable in yft.Fragment.DrawableArray.data_items) - { - drawablecount++; - foreach (var kvp in drawable.VertexDecls) - { - if (!vdecls.ContainsKey(kvp.Key)) - { - vdecls.Add(kvp.Key, kvp.Value); - vdecluse.Add(kvp.Key, 1); - } - else - { - vdecluse[kvp.Key]++; - } - } - } - } - - } - - } - catch (Exception ex) - { - errs.Add(entry.Path + ": " + ex.ToString()); - } - } - } - - - string errstr = string.Join("\r\n", errs); - - - - //build vertex types code string - errs.Clear(); - StringBuilder sbverts = new StringBuilder(); - foreach (var kvp in vdecls) - { - var vd = kvp.Value; - int usage = vdecluse[kvp.Key]; - sbverts.AppendFormat("public struct VertexType{0} //id: {1}, stride: {2}, flags: {3}, types: {4}, refs: {5}", vd.Flags, kvp.Key, vd.Stride, vd.Flags, vd.Types, usage); - sbverts.AppendLine(); - sbverts.AppendLine("{"); - uint compid = 1; - for (int i = 0; i < 16; i++) - { - if (((vd.Flags >> i) & 1) == 1) - { - string typestr = "Unknown"; - uint type = (uint)(((ulong)vd.Types >> (4 * i)) & 0xF); - switch (type) - { - case 0: typestr = "ushort"; break;// Data[i] = new ushort[1 * count]; break; - case 1: typestr = "ushort2"; break;// Data[i] = new ushort[2 * count]; break; - case 2: typestr = "ushort3"; break;// Data[i] = new ushort[3 * count]; break; - case 3: typestr = "ushort4"; break;// Data[i] = new ushort[4 * count]; break; - case 4: typestr = "float"; break;// Data[i] = new float[1 * count]; break; - case 5: typestr = "Vector2"; break;// Data[i] = new float[2 * count]; break; - case 6: typestr = "Vector3"; break;// Data[i] = new float[3 * count]; break; - case 7: typestr = "Vector4"; break;// Data[i] = new float[4 * count]; break; - case 8: typestr = "uint"; break;// Data[i] = new uint[count]; break; - case 9: typestr = "uint"; break;// Data[i] = new uint[count]; break; - case 10: typestr = "uint"; break;// Data[i] = new uint[count]; break; - default: - break; - } - sbverts.AppendLine(" public " + typestr + " Component" + compid.ToString() + ";"); - compid++; - } - - } - sbverts.AppendLine("}"); - sbverts.AppendLine(); - } - - string vertstr = sbverts.ToString(); - string verrstr = string.Join("\r\n", errs); - - UpdateStatus((DateTime.Now - starttime).ToString() + " elapsed, " + drawablecount.ToString() + " drawables, " + errs.Count.ToString() + " errors."); - - } - public void TestCacheFiles() - { - foreach (RpfFile file in AllRpfs) - { - foreach (RpfEntry entry in file.AllEntries) - { - try - { - if (entry.NameLower.EndsWith("cache_y.dat"))// || entry.NameLower.EndsWith("cache_y_bank.dat")) - { - UpdateStatus(string.Format(entry.Path)); - var cdfile = RpfMan.GetFile(entry); - if (cdfile != null) - { - var odata = entry.File.ExtractFile(entry as RpfFileEntry); - //var ndata = cdfile.Save(); - - var xml = CacheDatXml.GetXml(cdfile); - var cdf2 = XmlCacheDat.GetCacheDat(xml); - var ndata = cdf2.Save(); - - if (ndata.Length == odata.Length) - { - for (int i = 0; i < ndata.Length; i++) - { - if (ndata[i] != odata[i]) - { break; } - } - } - else - { } - } - else - { } - } - } - catch (Exception ex) - { - UpdateStatus("Error! " + ex.ToString()); - } - } - } - } - public void TestHeightmaps() - { - var errorfiles = new List(); - foreach (RpfFile file in AllRpfs) - { - foreach (RpfEntry entry in file.AllEntries) - { - if (entry.NameLower.EndsWith(".dat") && entry.NameLower.StartsWith("heightmap")) - { - UpdateStatus(string.Format(entry.Path)); - HeightmapFile hmf = null; - hmf = RpfMan.GetFile(entry); - var d1 = hmf.RawFileData; - //var d2 = hmf.Save(); - var xml = HmapXml.GetXml(hmf); - var hmf2 = XmlHmap.GetHeightmap(xml); - var d2 = hmf2.Save(); - - if (d1.Length == d2.Length) - { - for (int i = 0; i < d1.Length; i++) - { - if (d1[i] != d2[i]) - { } - } - } - else - { } - - } - } - } - if (errorfiles.Count > 0) - { } - } - public void TestWatermaps() - { - var errorfiles = new List(); - foreach (RpfFile file in AllRpfs) - { - foreach (RpfEntry entry in file.AllEntries) - { - if (entry.NameLower.EndsWith(".dat") && entry.NameLower.StartsWith("waterheight")) - { - UpdateStatus(string.Format(entry.Path)); - WatermapFile wmf = null; - wmf = RpfMan.GetFile(entry); - //var d1 = wmf.RawFileData; - //var d2 = wmf.Save(); - //var xml = WatermapXml.GetXml(wmf); - //var wmf2 = XmlWatermap.GetWatermap(xml); - //var d2 = wmf2.Save(); - - //if (d1.Length == d2.Length) - //{ - // for (int i = 0; i < d1.Length; i++) - // { - // if (d1[i] != d2[i]) - // { } - // } - //} - //else - //{ } - - } - } - } - if (errorfiles.Count > 0) - { } - } public void GetShadersXml() { bool doydr = true; @@ -5301,7 +2857,7 @@ namespace CodeWalker.GameFiles { if (doydr && entry.NameLower.EndsWith(".ydr")) { - UpdateStatus(entry.Path); + UpdateStatus?.Invoke(entry.Path); YdrFile ydr = RpfMan.GetFile(entry); if (ydr == null) { continue; } @@ -5310,7 +2866,7 @@ namespace CodeWalker.GameFiles } else if (doydd & entry.NameLower.EndsWith(".ydd")) { - UpdateStatus(entry.Path); + UpdateStatus?.Invoke(entry.Path); YddFile ydd = RpfMan.GetFile(entry); if (ydd == null) { continue; } @@ -5322,7 +2878,7 @@ namespace CodeWalker.GameFiles } else if (doyft && entry.NameLower.EndsWith(".yft")) { - UpdateStatus(entry.Path); + UpdateStatus?.Invoke(entry.Path); YftFile yft = RpfMan.GetFile(entry); if (yft == null) { continue; } @@ -5348,7 +2904,7 @@ namespace CodeWalker.GameFiles } else if (doypt && entry.NameLower.EndsWith(".ypt")) { - UpdateStatus(entry.Path); + UpdateStatus?.Invoke(entry.Path); YptFile ypt = RpfMan.GetFile(entry); if (ypt == null) { continue; } diff --git a/CodeWalker.Core/GameFiles/MetaTypes/Archetype.cs b/CodeWalker.Core/GameFiles/MetaTypes/Archetype.cs index 41a1e27..6c791c7 100644 --- a/CodeWalker.Core/GameFiles/MetaTypes/Archetype.cs +++ b/CodeWalker.Core/GameFiles/MetaTypes/Archetype.cs @@ -88,9 +88,25 @@ namespace CodeWalker.GameFiles public uint TimeFlags { get; set; } public bool[] ActiveHours { get; set; } - public string[] ActiveHoursText { get; set; } + private readonly Lazy _activeHoursText; + public string[] ActiveHoursText { get => _activeHoursText.Value; } public bool ExtraFlag { get { return ((TimeFlags >> 24) & 1) == 1; } } + public TimeArchetype() + { + _activeHoursText = new Lazy(() => + { + var activeHoursText = new string[24]; + for (int i = 0; i < ActiveHours.Length; i++) + { + var nxth = (i < 23) ? (i + 1) : 0; + var hrs = string.Format("{0:00}:00 - {1:00}:00", i, nxth); + activeHoursText[i] = (hrs + (ActiveHours[i] ? " - On" : " - Off")); + } + + return activeHoursText; + }); + } public void Init(YtypFile ytyp, ref CTimeArchetypeDef arch) { @@ -117,16 +133,11 @@ namespace CodeWalker.GameFiles if (ActiveHours == null) { ActiveHours = new bool[24]; - ActiveHoursText = new string[24]; } for (int i = 0; i < 24; i++) { bool v = ((TimeFlags >> i) & 1) == 1; ActiveHours[i] = v; - - int nxth = (i < 23) ? (i + 1) : 0; - string hrs = string.Format("{0:00}:00 - {1:00}:00", i, nxth); - ActiveHoursText[i] = (hrs + (v ? " - On" : " - Off")); } } diff --git a/CodeWalker.Core/GameFiles/MetaTypes/Meta.cs b/CodeWalker.Core/GameFiles/MetaTypes/Meta.cs index 5fbb9b1..8771575 100644 --- a/CodeWalker.Core/GameFiles/MetaTypes/Meta.cs +++ b/CodeWalker.Core/GameFiles/MetaTypes/Meta.cs @@ -1179,6 +1179,29 @@ namespace CodeWalker.GameFiles { return new MetaHash(v); } + + public override bool Equals(object obj) + { + if (obj == null) return false; + if (obj is not MetaHash metaHash) return false; + + return metaHash.Hash == Hash; + } + + public override int GetHashCode() + { + return (int)Hash; + } + + public static bool operator ==(MetaHash a, MetaHash b) + { + return a.Equals(b); + } + + public static bool operator !=(MetaHash a, MetaHash b) + { + return !a.Equals(b); + } } diff --git a/CodeWalker.Core/GameFiles/MetaTypes/MetaNames.cs b/CodeWalker.Core/GameFiles/MetaTypes/MetaNames.cs index 5f24c0c..49e320c 100644 --- a/CodeWalker.Core/GameFiles/MetaTypes/MetaNames.cs +++ b/CodeWalker.Core/GameFiles/MetaTypes/MetaNames.cs @@ -10,14 +10,21 @@ namespace CodeWalker.GameFiles public static class MetaNames { + public static Dictionary stringCache = new Dictionary(); public static bool TryGetString(uint h, out string str) { + if (stringCache.TryGetValue(h, out str)) + { + return str != null; + } if (Enum.IsDefined(typeof(MetaName), h)) { str = ((MetaName)h).ToString(); if (str.StartsWith("@")) str = str.Substring(1); //mainly to handle the @null entry + stringCache.Add(h, str); return true; } + stringCache.Add(h, str); str = null; return false; } diff --git a/CodeWalker.Core/GameFiles/MetaTypes/MetaTypes.cs b/CodeWalker.Core/GameFiles/MetaTypes/MetaTypes.cs index 7e122cd..8c63ec2 100644 --- a/CodeWalker.Core/GameFiles/MetaTypes/MetaTypes.cs +++ b/CodeWalker.Core/GameFiles/MetaTypes/MetaTypes.cs @@ -10,6 +10,7 @@ using System.Xml; using TC = System.ComponentModel.TypeConverterAttribute; using EXP = System.ComponentModel.ExpandableObjectConverter; using CodeWalker.World; +using System.Reflection; namespace CodeWalker.GameFiles { @@ -1479,22 +1480,29 @@ namespace CodeWalker.GameFiles public static T ConvertData(byte[] data) where T : struct { - GCHandle handle = GCHandle.Alloc(data, GCHandleType.Pinned); - var h = handle.AddrOfPinnedObject(); - var r = Marshal.PtrToStructure(h); - handle.Free(); - return r; + 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 ConvertData(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; + MemoryMarshal.TryRead(data.AsSpan(offset), out T value); + + return value; + //GCHandle handle = GCHandle.Alloc(data, GCHandleType.Pinned); + //var h = handle.AddrOfPinnedObject(); + //var r = Marshal.PtrToStructure(h + offset); + //handle.Free(); + //return r; } - public static T[] ConvertDataArray(byte[] data, int offset, int count) where T : struct + public static Span ConvertDataArray(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++) @@ -1520,8 +1528,6 @@ namespace CodeWalker.GameFiles { return null; } T[] items = new T[count]; - int itemsize = Marshal.SizeOf(typeof(T)); - int itemsleft = (int)count; //large arrays get split into chunks... //MetaName blocktype = 0; for (int i = 0; i < count; i++) @@ -1572,13 +1578,18 @@ namespace CodeWalker.GameFiles int blockcount = ptrblock.DataLength / itemsize; int itemcount = blockcount - itemoffset; if (itemcount > itemsleft) - { itemcount = itemsleft; } //don't try to read too many items.. - for (int i = 0; i < itemcount; i++) { - int offset = (itemoffset + i) * itemsize; - int index = curi + i; - items[index] = ConvertData(ptrblock.Data, offset); - } + itemcount = itemsleft; + } //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); + //} itemoffset = 0; //start at beginning of next block.. curi += itemcount; itemsleft -= itemcount; diff --git a/CodeWalker.Core/GameFiles/MetaTypes/MetaXml.cs b/CodeWalker.Core/GameFiles/MetaTypes/MetaXml.cs index 87d2c38..15f8715 100644 --- a/CodeWalker.Core/GameFiles/MetaTypes/MetaXml.cs +++ b/CodeWalker.Core/GameFiles/MetaTypes/MetaXml.cs @@ -1423,7 +1423,7 @@ namespace CodeWalker.GameFiles arrEnum.SwapEnd(); var enumArr = PsoTypes.GetUintArray(cont.Pso, arrEnum); var enumDef = cont.GetEnumInfo((MetaName)arrEntry.ReferenceKey); - WriteItemArray(sb, enumArr, indent, ename, "enum", (ie)=> { + WriteItemArray(sb, enumArr, indent, ename, "enum", (ie)=> { var eval = enumDef?.FindEntry((int)ie); return HashString(eval?.EntryNameHash ?? 0); }); @@ -1945,9 +1945,10 @@ namespace CodeWalker.GameFiles if (lastcol || lastn) sb.AppendLine(); } } - public static void WriteRawArray(StringBuilder sb, T[] arr, int ind, string name, string typeName, Func formatter = null, int arrRowSize = 10) where T : struct + + public static void WriteRawArray(StringBuilder sb, Span arr, int ind, string name, string typeName, Func formatter = null, int arrRowSize = 10) where T : struct { - var aCount = arr?.Length ?? 0; + var aCount = arr.Length; //var arrRowSize = 10; var aind = ind + 1; var arrTag = name;// + " itemType=\"" + typeName + "\""; @@ -1986,15 +1987,25 @@ namespace CodeWalker.GameFiles SelfClosingTag(sb, ind, arrTag); } } + + public static void WriteRawArray(StringBuilder sb, T[] arr, int ind, string name, string typeName, Func formatter = null, int arrRowSize = 10) where T : struct + { + WriteRawArray(sb, arr.AsSpan(), ind, name, typeName, formatter, arrRowSize); + } public static void WriteItemArray(StringBuilder sb, T[] arr, int ind, string name, string typeName, Func formatter) where T : struct { - var aCount = arr?.Length ?? 0; + WriteItemArray(sb, arr.AsSpan(), ind, name, typeName, formatter); + } + + public static void WriteItemArray(StringBuilder sb, Span arr, int ind, string name, string typeName, Func formatter) where T : struct + { + var itemCount = arr.Length; var arrTag = name;// + " itemType=\"Hash\""; var aind = ind + 1; - if (aCount > 0) + if (itemCount > 0) { OpenTag(sb, ind, arrTag); - for (int n = 0; n < aCount; n++) + for (int n = 0; n < itemCount; n++) { Indent(sb, aind); sb.Append(""); diff --git a/CodeWalker.Core/GameFiles/MetaTypes/PsoTypes.cs b/CodeWalker.Core/GameFiles/MetaTypes/PsoTypes.cs index 8ea0cd3..e6d9c3f 100644 --- a/CodeWalker.Core/GameFiles/MetaTypes/PsoTypes.cs +++ b/CodeWalker.Core/GameFiles/MetaTypes/PsoTypes.cs @@ -8,7 +8,7 @@ using System.Threading.Tasks; using TC = System.ComponentModel.TypeConverterAttribute; using EXP = System.ComponentModel.ExpandableObjectConverter; - +using System.Diagnostics; namespace CodeWalker.GameFiles { @@ -15819,45 +15819,46 @@ namespace CodeWalker.GameFiles public static T ConvertDataRaw(byte[] data) where T : struct { - GCHandle handle = GCHandle.Alloc(data, GCHandleType.Pinned); - var h = handle.AddrOfPinnedObject(); - var r = Marshal.PtrToStructure(h); - handle.Free(); - return r; + MemoryMarshal.TryRead(data.AsSpan(), out T value); + + return value; } public static T ConvertDataRaw(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; + MemoryMarshal.TryRead(data.AsSpan(offset), out T 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 { - GCHandle handle = GCHandle.Alloc(data, GCHandleType.Pinned); - var h = handle.AddrOfPinnedObject(); - var r = Marshal.PtrToStructure(h + offset); - handle.Free(); - r.SwapEnd(); - return r; + 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; } - public static T[] ConvertDataArrayRaw(byte[] data, int offset, int count) where T : struct + public static SpanConvertDataArrayRaw(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] = ConvertDataRaw(data, off); - //} + return MemoryMarshal.Cast(data.AsSpan(offset, count * Marshal.SizeOf(typeof(T)))); - 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; } @@ -15878,7 +15879,7 @@ namespace CodeWalker.GameFiles return e; } - public static T[] GetItemArrayRaw(PsoFile pso, Array_Structure arr) where T : struct + public static Span GetItemArrayRaw(PsoFile pso, Array_Structure arr) where T : struct { if ((arr.Count1 > 0) && (arr.Pointer > 0)) { @@ -15887,7 +15888,7 @@ namespace CodeWalker.GameFiles } return null; } - public static T[] GetItemArray(PsoFile pso, Array_Structure arr) where T : struct, IPsoSwapEnd + public static Span GetItemArray(PsoFile pso, Array_Structure arr) where T : struct, IPsoSwapEnd { if ((arr.Count1 > 0) && (arr.Pointer > 0)) { @@ -15906,7 +15907,7 @@ namespace CodeWalker.GameFiles } - public static uint[] GetUintArrayRaw(PsoFile pso, Array_uint arr) + public static Span GetUintArrayRaw(PsoFile pso, Array_uint arr) { byte[] data = pso.DataSection.Data; var entryid = arr.PointerDataId; @@ -15917,12 +15918,12 @@ namespace CodeWalker.GameFiles var entryoffset = arr.PointerDataOffset; var arrentry = pso.DataMapSection.Entries[(int)entryid - 1]; int totoffset = arrentry.Offset + (int)entryoffset; - uint[] readdata = ConvertDataArrayRaw(data, totoffset, arr.Count1); + var readdata = ConvertDataArrayRaw(data, totoffset, arr.Count1); return readdata; } - public static uint[] GetUintArray(PsoFile pso, Array_uint arr) + public static Span GetUintArray(PsoFile pso, Array_uint arr) { - uint[] uints = GetUintArrayRaw(pso, arr); + var uints = GetUintArrayRaw(pso, arr); if (uints == null) return null; for (int i = 0; i < uints.Length; i++) { @@ -15933,7 +15934,7 @@ namespace CodeWalker.GameFiles public static MetaHash[] GetHashArray(PsoFile pso, Array_uint arr) { - uint[] uints = GetUintArrayRaw(pso, arr); + var uints = GetUintArrayRaw(pso, arr); if (uints == null) return null; MetaHash[] hashes = new MetaHash[uints.Length]; for (int n = 0; n < uints.Length; n++) @@ -15946,7 +15947,7 @@ namespace CodeWalker.GameFiles - public static float[] GetFloatArrayRaw(PsoFile pso, Array_float arr) + public static Span GetFloatArrayRaw(PsoFile pso, Array_float arr) { byte[] data = pso.DataSection.Data; var entryid = arr.PointerDataId; @@ -15957,12 +15958,12 @@ namespace CodeWalker.GameFiles var entryoffset = arr.PointerDataOffset; var arrentry = pso.DataMapSection.Entries[(int)entryid - 1]; int totoffset = arrentry.Offset + (int)entryoffset; - float[] readdata = ConvertDataArrayRaw(data, totoffset, arr.Count1); + var readdata = ConvertDataArrayRaw(data, totoffset, arr.Count1); return readdata; } - public static float[] GetFloatArray(PsoFile pso, Array_float arr) + public static Span GetFloatArray(PsoFile pso, Array_float arr) { - float[] floats = GetFloatArrayRaw(pso, arr); + var floats = GetFloatArrayRaw(pso, arr); if (floats == null) return null; for (int i = 0; i < floats.Length; i++) { @@ -15975,7 +15976,7 @@ namespace CodeWalker.GameFiles - public static ushort[] GetUShortArrayRaw(PsoFile pso, Array_Structure arr) + public static Span GetUShortArrayRaw(PsoFile pso, Array_Structure arr) { byte[] data = pso.DataSection.Data; var entryid = arr.PointerDataId; @@ -15986,12 +15987,12 @@ namespace CodeWalker.GameFiles var entryoffset = arr.PointerDataOffset; var arrentry = pso.DataMapSection.Entries[(int)entryid - 1]; int totoffset = arrentry.Offset + (int)entryoffset; - ushort[] readdata = ConvertDataArrayRaw(data, totoffset, arr.Count1); + Span readdata = ConvertDataArrayRaw(data, totoffset, arr.Count1); return readdata; } - public static ushort[] GetUShortArray(PsoFile pso, Array_Structure arr) + public static Span GetUShortArray(PsoFile pso, Array_Structure arr) { - ushort[] ushorts = GetUShortArrayRaw(pso, arr); + var ushorts = GetUShortArrayRaw(pso, arr); if (ushorts == null) return null; for (int i = 0; i < ushorts.Length; i++) { @@ -16007,7 +16008,7 @@ namespace CodeWalker.GameFiles public static T[] GetObjectArray(PsoFile pso, Array_Structure arr) where U : struct, IPsoSwapEnd where T : PsoClass, new() { - U[] items = GetItemArray(pso, arr); + Span items = GetItemArray(pso, arr); if (items == null) return null; if (items.Length == 0) return null; T[] result = new T[items.Length]; @@ -16038,13 +16039,11 @@ namespace CodeWalker.GameFiles - public static PsoPOINTER[] GetPointerArray(PsoFile pso, Array_StructurePointer array) + public static Span GetPointerArray(PsoFile pso, Array_StructurePointer array) { uint count = array.Count1; if (count == 0) return null; - int ptrsize = Marshal.SizeOf(typeof(MetaPOINTER)); - int itemsleft = (int)count; //large arrays get split into chunks... uint ptrindex = array.PointerDataIndex; uint ptroffset = array.PointerDataOffset; var ptrblock = (ptrindex < pso.DataMapSection.EntriesCount) ? pso.DataMapSection.Entries[ptrindex] : null; @@ -16071,13 +16070,12 @@ namespace CodeWalker.GameFiles { uint count = array.Count1; if (count == 0) return null; - PsoPOINTER[] ptrs = GetPointerArray(pso, array); + var ptrs = GetPointerArray(pso, array); if (ptrs == null) return null; if (ptrs.Length < count) { return null; } T[] items = new T[count]; - int itemsize = Marshal.SizeOf(typeof(T)); for (int i = 0; i < count; i++) { diff --git a/CodeWalker.Core/GameFiles/MetaTypes/XmlMeta.cs b/CodeWalker.Core/GameFiles/MetaTypes/XmlMeta.cs index a7e362e..ed52163 100644 --- a/CodeWalker.Core/GameFiles/MetaTypes/XmlMeta.cs +++ b/CodeWalker.Core/GameFiles/MetaTypes/XmlMeta.cs @@ -243,106 +243,106 @@ namespace CodeWalker.GameFiles default: return "XML"; } } - public static MetaFormat GetXMLFormat(string fnamel, out int trimlength) + public static MetaFormat GetXMLFormat(string fileName, out int trimlength) { var mformat = MetaFormat.RSC; trimlength = 4; - if (!fnamel.EndsWith(".xml")) + if (!fileName.EndsWith(".xml", StringComparison.OrdinalIgnoreCase)) { mformat = MetaFormat.XML;//not really correct, but have to return something... } - if (fnamel.EndsWith(".pso.xml")) + if (fileName.EndsWith(".pso.xml")) { mformat = MetaFormat.PSO; trimlength = 8; } - if (fnamel.EndsWith(".rbf.xml")) + if (fileName.EndsWith(".rbf.xml", StringComparison.OrdinalIgnoreCase)) { mformat = MetaFormat.RBF; trimlength = 8; } - if (fnamel.EndsWith(".rel.xml")) + if (fileName.EndsWith(".rel.xml", StringComparison.OrdinalIgnoreCase)) { mformat = MetaFormat.AudioRel; } - if (fnamel.EndsWith(".ynd.xml")) + if (fileName.EndsWith(".ynd.xml", StringComparison.OrdinalIgnoreCase)) { mformat = MetaFormat.Ynd; } - if (fnamel.EndsWith(".ynv.xml")) + if (fileName.EndsWith(".ynv.xml", StringComparison.OrdinalIgnoreCase)) { mformat = MetaFormat.Ynv; } - if (fnamel.EndsWith(".ycd.xml")) + if (fileName.EndsWith(".ycd.xml", StringComparison.OrdinalIgnoreCase)) { mformat = MetaFormat.Ycd; } - if (fnamel.EndsWith(".ybn.xml")) + if (fileName.EndsWith(".ybn.xml", StringComparison.OrdinalIgnoreCase)) { mformat = MetaFormat.Ybn; } - if (fnamel.EndsWith(".ytd.xml")) + if (fileName.EndsWith(".ytd.xml", StringComparison.OrdinalIgnoreCase)) { mformat = MetaFormat.Ytd; } - if (fnamel.EndsWith(".ydr.xml")) + if (fileName.EndsWith(".ydr.xml", StringComparison.OrdinalIgnoreCase)) { mformat = MetaFormat.Ydr; } - if (fnamel.EndsWith(".ydd.xml")) + if (fileName.EndsWith(".ydd.xml", StringComparison.OrdinalIgnoreCase)) { mformat = MetaFormat.Ydd; } - if (fnamel.EndsWith(".yft.xml")) + if (fileName.EndsWith(".yft.xml", StringComparison.OrdinalIgnoreCase)) { mformat = MetaFormat.Yft; } - if (fnamel.EndsWith(".ypt.xml")) + if (fileName.EndsWith(".ypt.xml", StringComparison.OrdinalIgnoreCase)) { mformat = MetaFormat.Ypt; } - if (fnamel.EndsWith(".yld.xml")) + if (fileName.EndsWith(".yld.xml", StringComparison.OrdinalIgnoreCase)) { mformat = MetaFormat.Yld; } - if (fnamel.EndsWith(".yed.xml")) + if (fileName.EndsWith(".yed.xml", StringComparison.OrdinalIgnoreCase)) { mformat = MetaFormat.Yed; } - if (fnamel.EndsWith(".ywr.xml")) + if (fileName.EndsWith(".ywr.xml", StringComparison.OrdinalIgnoreCase)) { mformat = MetaFormat.Ywr; } - if (fnamel.EndsWith(".yvr.xml")) + if (fileName.EndsWith(".yvr.xml", StringComparison.OrdinalIgnoreCase)) { mformat = MetaFormat.Yvr; } - if (fnamel.EndsWith(".awc.xml")) + if (fileName.EndsWith(".awc.xml", StringComparison.OrdinalIgnoreCase)) { mformat = MetaFormat.Awc; } - if (fnamel.EndsWith(".fxc.xml")) + if (fileName.EndsWith(".fxc.xml", StringComparison.OrdinalIgnoreCase)) { mformat = MetaFormat.Fxc; } - if (fnamel.EndsWith("cache_y.dat.xml")) + if (fileName.EndsWith("cache_y.dat.xml", StringComparison.OrdinalIgnoreCase)) { mformat = MetaFormat.CacheFile; } - if (fnamel.EndsWith(".dat.xml") && fnamel.StartsWith("heightmap")) + if (fileName.EndsWith(".dat.xml", StringComparison.OrdinalIgnoreCase) && fileName.StartsWith("heightmap", StringComparison.OrdinalIgnoreCase)) { mformat = MetaFormat.Heightmap; } - if (fnamel.EndsWith(".ypdb.xml")) + if (fileName.EndsWith(".ypdb.xml", StringComparison.OrdinalIgnoreCase)) { mformat = MetaFormat.Ypdb; } - if (fnamel.EndsWith(".yfd.xml")) + if (fileName.EndsWith(".yfd.xml", StringComparison.OrdinalIgnoreCase)) { mformat = MetaFormat.Yfd; } - if (fnamel.EndsWith(".mrf.xml")) + if (fileName.EndsWith(".mrf.xml", StringComparison.OrdinalIgnoreCase)) { mformat = MetaFormat.Mrf; } diff --git a/CodeWalker.Core/GameFiles/Resources/Drawable.cs b/CodeWalker.Core/GameFiles/Resources/Drawable.cs index b4768a3..c2703fb 100644 --- a/CodeWalker.Core/GameFiles/Resources/Drawable.cs +++ b/CodeWalker.Core/GameFiles/Resources/Drawable.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.ComponentModel; +using System.Diagnostics; using System.Linq; using System.Text; using System.Threading.Tasks; @@ -5331,6 +5332,15 @@ namespace CodeWalker.GameFiles var d = new Drawable(); d.LightAttributes = dd.LightAttributes; d.Name = dd.Name; + if (d.Hash != 0) + { + d.Hash = dd.Hash; + } + else + { + d.Hash = JenkHash.GenHash(dd.Name); + } + d.Bound = dd.Bound; r = d; } @@ -5383,6 +5393,7 @@ namespace CodeWalker.GameFiles // reference data public string Name { get; set; } + public uint Hash { get; set; } public Bounds Bound { get; set; } public string ErrorMessage { get; set; } @@ -5455,6 +5466,7 @@ namespace CodeWalker.GameFiles public override void WriteXml(StringBuilder sb, int indent, string ddsfolder) { YdrXml.StringTag(sb, indent, "Name", YdrXml.XmlEscape(Name)); + YdrXml.StringTag(sb, indent, "Hash", Hash.ToString()); base.WriteXml(sb, indent, ddsfolder); if (Bound != null) { @@ -5468,6 +5480,10 @@ namespace CodeWalker.GameFiles public override void ReadXml(XmlNode node, string ddsfolder) { Name = Xml.GetChildInnerText(node, "Name"); + if (uint.TryParse(Xml.GetChildInnerText(node, "Hash"), out var hash)) + { + Hash = hash; + } base.ReadXml(node, ddsfolder); var bnode = node.SelectSingleNode("Bounds"); if (bnode != null) @@ -5847,7 +5863,15 @@ namespace CodeWalker.GameFiles var d = new Drawable(); d.ReadXml(inode, ddsfolder); drawables.Add(d); - drawablehashes.Add(JenkHash.GenHash(d.Name));//TODO: check this! + if (d.Hash != 0) + { + drawablehashes.Add(d.Hash); + } + else + { + drawablehashes.Add(JenkHash.GenHash(d.Name));//TODO: check this! + } + } } diff --git a/CodeWalker.Core/GameFiles/Resources/ResourceBaseTypes.cs b/CodeWalker.Core/GameFiles/Resources/ResourceBaseTypes.cs index 00904e7..9ccb354 100644 --- a/CodeWalker.Core/GameFiles/Resources/ResourceBaseTypes.cs +++ b/CodeWalker.Core/GameFiles/Resources/ResourceBaseTypes.cs @@ -1791,7 +1791,7 @@ namespace CodeWalker.GameFiles { int datalength = ItemCount * StructureSize; byte[] data = reader.ReadBytes(datalength); - Items = MetaTypes.ConvertDataArray(data, 0, ItemCount); + Items = MetaTypes.ConvertDataArray(data, 0, ItemCount).ToArray(); } public override void Write(ResourceDataWriter writer, params object[] parameters) diff --git a/CodeWalker.Core/GameFiles/Resources/ResourceBuilder.cs b/CodeWalker.Core/GameFiles/Resources/ResourceBuilder.cs index 773c9ec..dba5d87 100644 --- a/CodeWalker.Core/GameFiles/Resources/ResourceBuilder.cs +++ b/CodeWalker.Core/GameFiles/Resources/ResourceBuilder.cs @@ -348,9 +348,9 @@ namespace CodeWalker.GameFiles fileBase.FilePagesInfo.GraphicsPagesCount = (byte)graphicsPageFlags.Count; - var systemStream = new MemoryStream(); - var graphicsStream = new MemoryStream(); - var resourceWriter = new ResourceDataWriter(systemStream, graphicsStream); + using var systemStream = new MemoryStream(); + using var graphicsStream = new MemoryStream(); + using var resourceWriter = new ResourceDataWriter(systemStream, graphicsStream); resourceWriter.Position = 0x50000000; foreach (var block in systemBlocks) @@ -456,15 +456,12 @@ namespace CodeWalker.GameFiles public static byte[] Compress(byte[] data) { - using (MemoryStream ms = new MemoryStream()) + using (MemoryStream ms = RpfFile.recyclableMemoryStreamManager.GetStream()) { DeflateStream ds = new DeflateStream(ms, CompressionMode.Compress, true); ds.Write(data, 0, data.Length); ds.Close(); - byte[] deflated = ms.GetBuffer(); - byte[] outbuf = new byte[ms.Length]; //need to copy to the right size buffer... - Array.Copy(deflated, outbuf, outbuf.Length); - return outbuf; + return ms.ToArray(); } } public static byte[] Decompress(byte[] data) @@ -472,14 +469,17 @@ namespace CodeWalker.GameFiles using (MemoryStream ms = new MemoryStream(data)) { DeflateStream ds = new DeflateStream(ms, CompressionMode.Decompress); - MemoryStream outstr = new MemoryStream(); + MemoryStream outstr = RpfFile.recyclableMemoryStreamManager.GetStream("Decompress", data.Length); ds.CopyTo(outstr); - byte[] deflated = outstr.GetBuffer(); - byte[] outbuf = new byte[outstr.Length]; //need to copy to the right size buffer... - Array.Copy(deflated, outbuf, outbuf.Length); - return outbuf; + return outstr.ToArray(); } } + public static DeflateStream Decompress(Stream stream) + { + DeflateStream ds = new DeflateStream(stream, CompressionMode.Decompress); + return ds; + } + } } diff --git a/CodeWalker.Core/GameFiles/Resources/ResourceData.cs b/CodeWalker.Core/GameFiles/Resources/ResourceData.cs index 3093d74..f1d18d2 100644 --- a/CodeWalker.Core/GameFiles/Resources/ResourceData.cs +++ b/CodeWalker.Core/GameFiles/Resources/ResourceData.cs @@ -38,13 +38,16 @@ namespace CodeWalker.GameFiles /// /// Represents a resource data reader. /// - public class ResourceDataReader : DataReader + public class ResourceDataReader : DataReader, IDisposable { private const long SYSTEM_BASE = 0x50000000; private const long GRAPHICS_BASE = 0x60000000; - private Stream systemStream; - private Stream graphicsStream; + private readonly Stream systemStream; + private readonly Stream graphicsStream; + + private readonly long systemSize = 0; + private readonly long graphicsSize = 0; public RpfResourceFileEntry FileEntry { get; set; } @@ -71,7 +74,7 @@ namespace CodeWalker.GameFiles { get; set; - } + } = SYSTEM_BASE; /// /// Initializes a new resource data reader for the specified system- and graphics-stream. @@ -81,14 +84,16 @@ namespace CodeWalker.GameFiles { this.systemStream = systemStream; this.graphicsStream = graphicsStream; + this.systemSize = systemStream.Length; + this.graphicsSize = graphicsStream.Length; } public ResourceDataReader(RpfResourceFileEntry resentry, byte[] data, Endianess endianess = Endianess.LittleEndian) : base((Stream)null, endianess) { FileEntry = resentry; - var systemSize = resentry.SystemSize; - var graphicsSize = resentry.GraphicsSize; + this.systemSize = resentry.SystemSize; + this.graphicsSize = resentry.GraphicsSize; //if (data != null) //{ @@ -103,9 +108,8 @@ namespace CodeWalker.GameFiles // } //} - this.systemStream = new MemoryStream(data, 0, systemSize); - this.graphicsStream = new MemoryStream(data, systemSize, graphicsSize); - Position = 0x50000000; + this.systemStream = new MemoryStream(data, 0, (int)systemSize); + this.graphicsStream = new MemoryStream(data, (int)systemSize, (int)graphicsSize); } public ResourceDataReader(int systemSize, int graphicsSize, byte[] data, Endianess endianess = Endianess.LittleEndian) @@ -113,24 +117,21 @@ namespace CodeWalker.GameFiles { this.systemStream = new MemoryStream(data, 0, systemSize); this.graphicsStream = new MemoryStream(data, systemSize, graphicsSize); - Position = 0x50000000; } - - /// /// 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) + protected override byte[] ReadFromStream(int count, bool ignoreEndianess = false, byte[] buffer = null) { if ((Position & SYSTEM_BASE) == SYSTEM_BASE) { // read from system stream... - systemStream.Position = Position & ~0x50000000; + systemStream.Position = Position & ~SYSTEM_BASE; - var buffer = new byte[count]; + buffer ??= new byte[count]; systemStream.Read(buffer, 0, count); // handle endianess @@ -139,17 +140,17 @@ namespace CodeWalker.GameFiles Array.Reverse(buffer); } - Position = systemStream.Position | 0x50000000; + Position = systemStream.Position | SYSTEM_BASE; return buffer; } - if ((Position & GRAPHICS_BASE) == GRAPHICS_BASE) + else if ((Position & GRAPHICS_BASE) == GRAPHICS_BASE) { // read from graphic stream... - graphicsStream.Position = Position & ~0x60000000; + graphicsStream.Position = Position & ~GRAPHICS_BASE; - var buffer = new byte[count]; + buffer ??= new byte[count]; graphicsStream.Read(buffer, 0, count); // handle endianess @@ -158,12 +159,44 @@ namespace CodeWalker.GameFiles Array.Reverse(buffer); } - Position = graphicsStream.Position | 0x60000000; + Position = graphicsStream.Position | GRAPHICS_BASE; return buffer; } throw new Exception("illegal position!"); } + public override byte ReadByte() + { + if ((Position & SYSTEM_BASE) == SYSTEM_BASE) + { + + + + + // read from system stream... + + systemStream.Position = Position & ~SYSTEM_BASE; + + var readByte = (byte)systemStream.ReadByte(); + + Position = systemStream.Position | SYSTEM_BASE; + return readByte; + + } + if ((Position & GRAPHICS_BASE) == GRAPHICS_BASE) + { + // read from graphic stream... + + graphicsStream.Position = Position & ~GRAPHICS_BASE; + + var readByte = (byte)graphicsStream.ReadByte(); + + Position = graphicsStream.Position | GRAPHICS_BASE; + return readByte; + } + throw new Exception("illegal position!"); + } + /// /// Reads a block. /// @@ -447,6 +480,13 @@ namespace CodeWalker.GameFiles return result; } + public override void Dispose() + { + base.Dispose(); + + systemStream?.Dispose(); + graphicsStream?.Dispose(); + } } @@ -454,7 +494,7 @@ namespace CodeWalker.GameFiles /// /// Represents a resource data writer. /// - public class ResourceDataWriter : DataWriter + public class ResourceDataWriter : DataWriter, IDisposable { private const long SYSTEM_BASE = 0x50000000; private const long GRAPHICS_BASE = 0x60000000; @@ -596,7 +636,13 @@ namespace CodeWalker.GameFiles } } + public override void Dispose() + { + base.Dispose(); + systemStream?.Dispose(); + graphicsStream?.Dispose(); + } } diff --git a/CodeWalker.Core/GameFiles/Resources/Texture.cs b/CodeWalker.Core/GameFiles/Resources/Texture.cs index f27a96a..26f0127 100644 --- a/CodeWalker.Core/GameFiles/Resources/Texture.cs +++ b/CodeWalker.Core/GameFiles/Resources/Texture.cs @@ -218,12 +218,25 @@ namespace CodeWalker.GameFiles public uint Unknown_4Ch { get; set; } // 0x00000000 // reference data - public string Name { get; set; } - public uint NameHash { get; set; } + public string Name { get => name; set + { + name = value; + nameHash = 0; + } + } + public uint NameHash { get + { + if (nameHash == 0) + { + nameHash = JenkHash.GenHashLower(name); + } + return nameHash; + } + set => nameHash = value; } private string_r NameBlock = null; - - + private uint nameHash; + private string name; public TextureUsage Usage { @@ -281,11 +294,6 @@ namespace CodeWalker.GameFiles this.NamePointer // offset ); - if (!string.IsNullOrEmpty(Name)) - { - NameHash = JenkHash.GenHash(Name.ToLowerInvariant()); - } - //switch (Unknown_32h) //{ // case 0x20: @@ -405,7 +413,6 @@ namespace CodeWalker.GameFiles public virtual void ReadXml(XmlNode node, string ddsfolder) { Name = Xml.GetChildInnerText(node, "Name"); - NameHash = JenkHash.GenHash(Name?.ToLowerInvariant()); Unknown_32h = (ushort)Xml.GetChildUIntAttribute(node, "Unk32", "value"); Usage = Xml.GetChildEnumInnerText(node, "Usage"); UsageFlags = Xml.GetChildEnumInnerText(node, "UsageFlags"); diff --git a/CodeWalker.Core/GameFiles/RpfFile.cs b/CodeWalker.Core/GameFiles/RpfFile.cs index 53d8e78..bb3afb7 100644 --- a/CodeWalker.Core/GameFiles/RpfFile.cs +++ b/CodeWalker.Core/GameFiles/RpfFile.cs @@ -1,10 +1,17 @@ -using System; +using Microsoft.IO; +using System; +using System.Buffers; +using System.Buffers.Binary; using System.Collections.Generic; using System.ComponentModel; +using System.Diagnostics; using System.IO; using System.IO.Compression; +using System.IO.Pipes; using System.Linq; +using System.Security.Cryptography.X509Certificates; using System.Text; +using System.Threading; using System.Threading.Tasks; namespace CodeWalker.GameFiles @@ -13,7 +20,24 @@ namespace CodeWalker.GameFiles public class RpfFile { public string Name { get; set; } //name of this RPF file/package - public string NameLower { get; set; } + + + 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; } @@ -61,7 +85,6 @@ namespace CodeWalker.GameFiles { FileInfo fi = new FileInfo(fpath); Name = fi.Name; - NameLower = Name.ToLowerInvariant(); Path = relpath.ToLowerInvariant(); FilePath = fpath; FileSize = fi.Length; @@ -69,7 +92,6 @@ namespace CodeWalker.GameFiles public RpfFile(string name, string path, long filesize) //for a child RPF { Name = name; - NameLower = Name.ToLowerInvariant(); Path = path.ToLowerInvariant(); FilePath = path; FileSize = filesize; @@ -147,8 +169,11 @@ namespace CodeWalker.GameFiles throw new Exception("Invalid Resource - not GTAV!"); } - byte[] entriesdata = br.ReadBytes((int)EntryCount * 16); //4x uints each - byte[] namesdata = br.ReadBytes((int)NamesLength); + byte[] entriesdata = ArrayPool.Shared.Rent((int)EntryCount * 16); + byte[] namesdata = ArrayPool.Shared.Rent((int)NamesLength); + + br.BaseStream.Read(entriesdata, 0, (int)EntryCount * 16); + br.BaseStream.Read(namesdata, 0, (int)NamesLength); switch (Encryption) { @@ -156,25 +181,25 @@ namespace CodeWalker.GameFiles case RpfEncryption.OPEN: //OpenIV style RPF with unencrypted TOC break; case RpfEncryption.AES: - entriesdata = GTACrypto.DecryptAES(entriesdata); - namesdata = GTACrypto.DecryptAES(namesdata); + GTACrypto.DecryptAES(entriesdata, (int)EntryCount * 16); + GTACrypto.DecryptAES(namesdata, (int)NamesLength); + IsAESEncrypted = true; break; case RpfEncryption.NG: - entriesdata = GTACrypto.DecryptNG(entriesdata, Name, (uint)FileSize); - namesdata = GTACrypto.DecryptNG(namesdata, Name, (uint)FileSize); + default: + GTACrypto.DecryptNG(entriesdata, Name, (uint)FileSize, 0, (int)EntryCount * 16); + GTACrypto.DecryptNG(namesdata, Name, (uint)FileSize, 0, (int)NamesLength); + IsNGEncrypted = true; break; - default: //unknown encryption type? assume NG.. never seems to get here - entriesdata = GTACrypto.DecryptNG(entriesdata, Name, (uint)FileSize); - namesdata = GTACrypto.DecryptNG(namesdata, Name, (uint)FileSize); - break; } + using var entriesrdr = new DataReader(new MemoryStream(entriesdata, 0, (int)EntryCount * 16)); + using var namesrdr = new DataReader(new MemoryStream(namesdata, 0, (int)NamesLength)); - var entriesrdr = new DataReader(new MemoryStream(entriesdata)); - var namesrdr = new DataReader(new MemoryStream(namesdata)); - AllEntries = new List(); + + AllEntries = new List((int)EntryCount); TotalFileCount = 0; TotalFolderCount = 0; TotalResourceCount = 0; @@ -213,28 +238,24 @@ namespace CodeWalker.GameFiles e.Read(entriesrdr); - namesrdr.Position = e.NameOffset; - e.Name = namesrdr.ReadString(); - if (e.Name.Length > 256) - { - // long names can freeze the RPFExplorer - e.Name = e.Name.Substring(0, 256); - } - e.NameLower = e.Name.ToLowerInvariant(); - - if ((e is RpfFileEntry) && string.IsNullOrEmpty(e.Name)) - { - } - if ((e is RpfResourceFileEntry))// && string.IsNullOrEmpty(e.Name)) - { - var rfe = e as RpfResourceFileEntry; - rfe.IsEncrypted = rfe.NameLower.EndsWith(".ysc");//any other way to know..? - } - AllEntries.Add(e); } + foreach(var entry in AllEntries) + { + 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); + } + if (entry is RpfResourceFileEntry rfe)// && string.IsNullOrEmpty(e.Name)) + { + rfe.IsEncrypted = rfe.Name.EndsWith(".ysc", StringComparison.OrdinalIgnoreCase);//any other way to know..? + } + } Root = (RpfDirectoryEntry)AllEntries[0]; Root.Path = Path.ToLowerInvariant();// + "\\" + Root.Name; @@ -251,17 +272,16 @@ namespace CodeWalker.GameFiles { RpfEntry e = AllEntries[i]; e.Parent = item; - if (e is RpfDirectoryEntry) + if (e is RpfDirectoryEntry rde) { - RpfDirectoryEntry rde = e as RpfDirectoryEntry; - rde.Path = item.Path + "\\" + rde.NameLower; + 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.NameLower; + rfe.Path = item.Path + "\\" + rfe.Name; item.Files.Add(rfe); } } @@ -270,32 +290,41 @@ namespace CodeWalker.GameFiles br.BaseStream.Position = StartPos; CurrentFileReader = null; - + ArrayPool.Shared.Return(entriesdata); + ArrayPool.Shared.Return(namesdata); } - public void ScanStructure(Action updateStatus, Action errorLog) + public bool ScanStructure(Action updateStatus, Action errorLog) { - using (BinaryReader br = new BinaryReader(File.OpenRead(FilePath))) + + using var fileStream = File.OpenRead(FilePath); + using var br = new BinaryReader(fileStream); + try { - try - { - ScanStructure(br, updateStatus, errorLog); - } - catch (Exception ex) - { - LastError = ex.ToString(); - LastException = ex; - errorLog(FilePath + ": " + LastError); - } + return ScanStructure(br, updateStatus, errorLog); + } + catch (Exception ex) + { + LastError = ex.ToString(); + LastException = ex; + errorLog?.Invoke(FilePath + ": " + LastError); + return false; } } - private void ScanStructure(BinaryReader br, Action updateStatus, Action errorLog) + private bool ScanStructure(BinaryReader br, Action updateStatus, Action errorLog) { - ReadHeader(br); + if (FilePath == "update\\update.rpf\\dlc_patch\\patchday1ng\\x64\\patch\\data\\lang\\chinesesimp.rpf") return false; + try + { + ReadHeader(br); + } catch + { + return false; + } GrandTotalRpfCount = 1; //count this file.. GrandTotalFileCount = 1; //start with this one. @@ -311,13 +340,10 @@ namespace CodeWalker.GameFiles { try { - if (entry is RpfBinaryFileEntry) + if (entry is RpfBinaryFileEntry binentry) { - RpfBinaryFileEntry binentry = entry as RpfBinaryFileEntry; - //search all the sub resources for YSC files. (recurse!) - string lname = binentry.NameLower; - if (lname.EndsWith(".rpf") && binentry.Path.Length < 5000) // a long path is most likely an attempt to crash CW, so skip it + 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 { br.BaseStream.Position = StartPos + ((long)binentry.FileOffset * 512); @@ -327,15 +353,16 @@ namespace CodeWalker.GameFiles subfile.Parent = this; subfile.ParentFileEntry = binentry; - subfile.ScanStructure(br, updateStatus, errorLog); + if (subfile.ScanStructure(br, updateStatus, errorLog)) + { + GrandTotalRpfCount += subfile.GrandTotalRpfCount; + GrandTotalFileCount += subfile.GrandTotalFileCount; + GrandTotalFolderCount += subfile.GrandTotalFolderCount; + GrandTotalResourceCount += subfile.GrandTotalResourceCount; + GrandTotalBinaryFileCount += subfile.GrandTotalBinaryFileCount; - GrandTotalRpfCount += subfile.GrandTotalRpfCount; - GrandTotalFileCount += subfile.GrandTotalFileCount; - GrandTotalFolderCount += subfile.GrandTotalFolderCount; - GrandTotalResourceCount += subfile.GrandTotalResourceCount; - GrandTotalBinaryFileCount += subfile.GrandTotalBinaryFileCount; - - Children.Add(subfile); + Children.Add(subfile); + } } else { @@ -359,7 +386,7 @@ namespace CodeWalker.GameFiles errorLog?.Invoke(entry.Path + ": " + ex.ToString()); } } - + return true; } @@ -437,26 +464,25 @@ namespace CodeWalker.GameFiles br.Read(tbytes, 0, (int)totlen); - byte[] decr; if (IsAESEncrypted) { - decr = GTACrypto.DecryptAES(tbytes); + GTACrypto.DecryptAES(tbytes); //special case! probable duplicate pilot_school.ysc ofpath = outputfolder + "\\" + Name + "___" + resentry.Name; } else { - decr = GTACrypto.DecryptNG(tbytes, resentry.Name, resentry.FileSize); + GTACrypto.DecryptNG(tbytes, resentry.Name, resentry.FileSize); } try { - MemoryStream ms = new MemoryStream(decr); + MemoryStream ms = new MemoryStream(tbytes); DeflateStream ds = new DeflateStream(ms, CompressionMode.Decompress); - MemoryStream outstr = new MemoryStream(); + MemoryStream outstr = recyclableMemoryStreamManager.GetStream(); ds.CopyTo(outstr); byte[] deflated = outstr.GetBuffer(); byte[] outbuf = new byte[outstr.Length]; //need to copy to the right size buffer for File.WriteAllBytes(). @@ -498,7 +524,33 @@ namespace CodeWalker.GameFiles - + //public Stream ExtractFileStream(RpfFileEntry entry) + //{ + // try + // { + // using (BinaryReader br = new BinaryReader(File.OpenRead(GetPhysicalFilePath()))) + // { + // if (entry is RpfBinaryFileEntry binaryFileEntry) + // { + // return ExtractFileBinary(binaryFileEntry, br); + // } + // else if (entry is RpfResourceFileEntry resourceFileEntry) + // { + // return ExtractFileResource(resourceFileEntry, br); + // } + // else + // { + // return null; + // } + // } + // } + // catch (Exception ex) + // { + // LastError = ex.ToString(); + // LastException = ex; + // return null; + // } + //} public byte[] ExtractFile(RpfFileEntry entry) { @@ -506,13 +558,13 @@ namespace CodeWalker.GameFiles { using (BinaryReader br = new BinaryReader(File.OpenRead(GetPhysicalFilePath()))) { - if (entry is RpfBinaryFileEntry) + if (entry is RpfBinaryFileEntry binaryFileEntry) { - return ExtractFileBinary(entry as RpfBinaryFileEntry, br); + return ExtractFileBinary(binaryFileEntry, br); } - else if (entry is RpfResourceFileEntry) + else if (entry is RpfResourceFileEntry resourceFileEntry) { - return ExtractFileResource(entry as RpfResourceFileEntry, br); + return ExtractFileResource(resourceFileEntry, br); } else { @@ -543,27 +595,25 @@ namespace CodeWalker.GameFiles br.BaseStream.Position += offset; br.Read(tbytes, 0, (int)totlen); - byte[] decr = tbytes; - if (entry.IsEncrypted) { if (IsAESEncrypted) { - decr = GTACrypto.DecryptAES(tbytes); + GTACrypto.DecryptAES(tbytes); } else //if (IsNGEncrypted) //assume the archive is set to NG encryption if not AES... (comment: fix for openIV modded files) { - decr = GTACrypto.DecryptNG(tbytes, entry.Name, entry.FileUncompressedSize); + GTACrypto.DecryptNG(tbytes, entry.Name, entry.FileUncompressedSize); } //else //{ } } - byte[] defl = decr; + byte[] defl = tbytes; if (entry.FileSize > 0) //apparently this means it's compressed { - defl = DecompressBytes(decr); + defl = DecompressBytes(tbytes); } else { @@ -596,23 +646,21 @@ namespace CodeWalker.GameFiles br.Read(tbytes, 0, (int)totlen); - - byte[] decr = tbytes; if (entry.IsEncrypted) { if (IsAESEncrypted) { - decr = GTACrypto.DecryptAES(tbytes); + GTACrypto.DecryptAES(tbytes); } else //if (IsNGEncrypted) //assume the archive is set to NG encryption if not AES... (comment: fix for openIV modded files) { - decr = GTACrypto.DecryptNG(tbytes, entry.Name, entry.FileSize); + GTACrypto.DecryptNG(tbytes, entry.Name, entry.FileSize); } //else //{ } } - byte[] deflated = DecompressBytes(decr); + byte[] deflated = DecompressBytes(tbytes); byte[] data = null; @@ -623,7 +671,7 @@ namespace CodeWalker.GameFiles else { entry.FileSize -= offset; - data = decr; + data = tbytes; } @@ -665,6 +713,22 @@ namespace CodeWalker.GameFiles return file; } + public static T GetFile(RpfEntry e, Stream data) where T : class, PackedFileStream, new() + { + T file = null; + RpfFileEntry entry = e as RpfFileEntry; + if ((data != null)) + { + if (entry == null) + { + entry = CreateResourceFileEntry(data, 0); + } + file = new T(); + file.Load(data, entry); + } + return file; + } + public static T GetResourceFile(byte[] data) where T : class, PackedFile, new() @@ -679,6 +743,36 @@ namespace CodeWalker.GameFiles } return file; } + + + public static void LoadResourceFile(T file, Stream data, uint ver) where T : class, PackedFileStream + { + //direct load from a raw, compressed resource file (openIV-compatible format) + + RpfResourceFileEntry resentry = CreateResourceFileEntry(data, ver); + + if (file is GameFile) + { + GameFile gfile = file as GameFile; + + var oldresentry = gfile.RpfFileEntry as RpfResourceFileEntry; + if (oldresentry != null) //update the existing entry with the new one + { + oldresentry.SystemFlags = resentry.SystemFlags; + oldresentry.GraphicsFlags = resentry.GraphicsFlags; + resentry.Name = oldresentry.Name; + } + else + { + gfile.RpfFileEntry = resentry; //just stick it in there for later... + } + } + + data = ResourceBuilder.Decompress(data); + + file.Load(data, resentry); + } + public static void LoadResourceFile(T file, byte[] data, uint ver) where T : class, PackedFile { //direct load from a raw, compressed resource file (openIV-compatible format) @@ -695,9 +789,6 @@ namespace CodeWalker.GameFiles oldresentry.SystemFlags = resentry.SystemFlags; oldresentry.GraphicsFlags = resentry.GraphicsFlags; resentry.Name = oldresentry.Name; - resentry.NameHash = oldresentry.NameHash; - resentry.NameLower = oldresentry.NameLower; - resentry.ShortNameHash = oldresentry.ShortNameHash; } else { @@ -710,6 +801,45 @@ namespace CodeWalker.GameFiles file.Load(data, resentry); } + + public static RpfResourceFileEntry CreateResourceFileEntry(Stream stream, uint ver, uint? header = null) + { + var resentry = new RpfResourceFileEntry(); + + using var reader = new BinaryReader(stream, Encoding.UTF8, true); + + //hopefully this data has an RSC7 header... + uint rsc7 = header ?? reader.ReadUInt32(); // BitConverter.ToUInt32(data, 0); + if (rsc7 == 0x37435352) //RSC7 header present! + { + int version = reader.ReadInt32(); // BitConverter.ToInt32(data, 4);//use this instead of what was given... + resentry.SystemFlags = reader.ReadUInt32();// BitConverter.ToUInt32(data, 8); + resentry.GraphicsFlags = reader.ReadUInt32(); //BitConverter.ToUInt32(data, 12); + //if (stream.Length > 16) + //{ + // int newlen = stream.Length - 16; //trim the header from the data passed to the next step. + // byte[] newdata = new byte[newlen]; + // Buffer.BlockCopy(data, 16, newdata, 0, newlen); + // data = newdata; + //} + //else + //{ + // data = null; //shouldn't happen... empty.. + //} + } + else + { + //direct load from file without the rpf header.. + //assume it's in resource meta format + resentry.SystemFlags = RpfResourceFileEntry.GetFlagsFromSize((int)stream.Length, 0); + resentry.GraphicsFlags = RpfResourceFileEntry.GetFlagsFromSize(0, ver); + } + + resentry.Name = ""; + + return resentry; + } + public static RpfResourceFileEntry CreateResourceFileEntry(ref byte[] data, uint ver) { var resentry = new RpfResourceFileEntry(); @@ -742,7 +872,6 @@ namespace CodeWalker.GameFiles } resentry.Name = ""; - resentry.NameLower = ""; return resentry; } @@ -896,14 +1025,14 @@ namespace CodeWalker.GameFiles - + public static RecyclableMemoryStreamManager recyclableMemoryStreamManager = new RecyclableMemoryStreamManager(); public byte[] DecompressBytes(byte[] bytes) { try { using (DeflateStream ds = new DeflateStream(new MemoryStream(bytes), CompressionMode.Decompress)) { - using (var outstr = new MemoryStream()) + using (var outstr = recyclableMemoryStreamManager.GetStream("DecompressBytes", bytes.Length)) { ds.CopyTo(outstr); byte[] deflated = outstr.GetBuffer(); @@ -929,7 +1058,7 @@ namespace CodeWalker.GameFiles } public static byte[] CompressBytes(byte[] data) //TODO: refactor this with ResourceBuilder.Compress/Decompress { - using (MemoryStream ms = new MemoryStream()) + using (MemoryStream ms = recyclableMemoryStreamManager.GetStream("CompressBytes", data.Length)) { using (var ds = new DeflateStream(ms, CompressionMode.Compress, true)) { @@ -1023,7 +1152,6 @@ namespace CodeWalker.GameFiles Root = new RpfDirectoryEntry(); Root.File = this; Root.Name = string.Empty; - Root.NameLower = string.Empty; Root.Path = Path.ToLowerInvariant(); } if (Children == null) @@ -1397,7 +1525,7 @@ namespace CodeWalker.GameFiles file.Path = dir.Path + "\\" + file.NameLower; RpfBinaryFileEntry binf = file as RpfBinaryFileEntry; - if ((binf != null) && file.NameLower.EndsWith(".rpf")) + if ((binf != null) && file.Name.EndsWith(".rpf", StringComparison.OrdinalIgnoreCase)) { RpfFile childrpf = FindChildArchive(binf); if (childrpf != null) @@ -1413,7 +1541,7 @@ namespace CodeWalker.GameFiles } foreach (var subdir in dir.Directories) { - subdir.Path = dir.Path + "\\" + subdir.NameLower; + subdir.Path = dir.Path + "\\" + subdir.Name; UpdatePaths(subdir); } } @@ -1531,9 +1659,6 @@ namespace CodeWalker.GameFiles entry.File = parent; entry.Path = rpath; entry.Name = name; - entry.NameLower = namel; - entry.NameHash = JenkHash.GenHash(name); - entry.ShortNameHash = JenkHash.GenHash(entry.GetShortNameLower()); dir.Files.Add(entry); @@ -1574,13 +1699,10 @@ namespace CodeWalker.GameFiles entry.File = parent; entry.Path = rpath; entry.Name = name; - entry.NameLower = namel; - entry.NameHash = JenkHash.GenHash(name); - entry.ShortNameHash = JenkHash.GenHash(entry.GetShortNameLower()); foreach (var exdir in dir.Directories) { - if (exdir.NameLower == entry.NameLower) + if (exdir.Name.Equals(entry.Name, StringComparison.OrdinalIgnoreCase)) { throw new Exception("RPF Directory \"" + entry.Name + "\" already exists!"); } @@ -1600,14 +1722,61 @@ namespace CodeWalker.GameFiles return entry; } + public static RpfFileEntry CreateFileEntry(string name, string path, Stream data) + { + //this should only really be used when loading a file from the filesystem. + RpfFileEntry e; + using var reader = new BinaryReader(data, Encoding.UTF8, true); + + uint rsc7 = (reader.BaseStream.Length > 4) ? reader.ReadUInt32() : 0; + if (rsc7 == 0x37435352) //RSC7 header present! create RpfResourceFileEntry and decompress data... + { + e = RpfFile.CreateResourceFileEntry(data, 0);//"version" should be loadable from the header in the data.. + data = ResourceBuilder.Decompress(data); + } + else + { + var be = new RpfBinaryFileEntry + { + FileSize = (uint)(data?.Length ?? 0), + FileUncompressedSize = (uint)(data?.Length ?? 0), + }; + e = be; + } + e.Name = name; + e.Path = path; + return e; + } + + public static RpfFileEntry CreateFileEntry(string name, string path, ref byte[] data) + { + //this should only really be used when loading a file from the filesystem. + RpfFileEntry e = null; + uint rsc7 = (data?.Length > 4) ? BitConverter.ToUInt32(data, 0) : 0; + if (rsc7 == 0x37435352) //RSC7 header present! create RpfResourceFileEntry and decompress data... + { + e = RpfFile.CreateResourceFileEntry(ref data, 0);//"version" should be loadable from the header in the data.. + data = ResourceBuilder.Decompress(data); + } + else + { + var be = new RpfBinaryFileEntry(); + be.FileSize = (uint)data?.Length; + be.FileUncompressedSize = be.FileSize; + e = be; + } + e.Name = name; + e.Path = path; + return e; + } + public static RpfFileEntry CreateFile(RpfDirectoryEntry dir, string name, byte[] data, bool overwrite = true) { - string namel = name.ToLowerInvariant(); if (overwrite) { foreach (var exfile in dir.Files) { - if (exfile.NameLower == namel) + if (exfile.Name.Equals(name, StringComparison.OrdinalIgnoreCase)) { //file already exists. delete the existing one first! //this should probably be optimised to just replace the existing one... @@ -1621,6 +1790,7 @@ namespace CodeWalker.GameFiles RpfFile parent = dir.File; string fpath = parent.GetPhysicalFilePath(); + string namel = name.ToLowerInvariant(); string rpath = dir.Path + "\\" + namel; if (!File.Exists(fpath)) { @@ -1644,7 +1814,6 @@ namespace CodeWalker.GameFiles { //RSC header is present... import as resource var rentry = new RpfResourceFileEntry(); - var version = BitConverter.ToUInt32(data, 4); rentry.SystemFlags = BitConverter.ToUInt32(data, 8); rentry.GraphicsFlags = BitConverter.ToUInt32(data, 12); rentry.FileSize = len; @@ -1661,11 +1830,11 @@ namespace CodeWalker.GameFiles entry = rentry; } - if (namel.EndsWith(".rpf") && (hdr == 0x52504637)) //'RPF7' + if ((hdr == 0x52504637) && name.EndsWith(".rpf", StringComparison.OrdinalIgnoreCase)) //'RPF7' { isrpf = true; } - if (namel.EndsWith(".awc")) + if (name.EndsWith(".awc", StringComparison.OrdinalIgnoreCase)) { isawc = true; } @@ -1693,16 +1862,14 @@ namespace CodeWalker.GameFiles entry.File = parent; entry.Path = rpath; entry.Name = name; - entry.NameLower = name.ToLowerInvariant(); - entry.NameHash = JenkHash.GenHash(name); - entry.ShortNameHash = JenkHash.GenHash(entry.GetShortNameLower()); + entry.NameLower = namel; foreach (var exfile in dir.Files) { - if (exfile.NameLower == entry.NameLower) + if (exfile.Name.Equals(entry.Name, StringComparison.OrdinalIgnoreCase)) { throw new Exception("File \"" + entry.Name + "\" already exists!"); } @@ -1715,15 +1882,13 @@ namespace CodeWalker.GameFiles using (var fstream = File.Open(fpath, FileMode.Open, FileAccess.ReadWrite)) { - using (var bw = new BinaryWriter(fstream)) - { - parent.InsertFileSpace(bw, entry); - long bbeg = parent.StartPos + (entry.FileOffset * 512); - long bend = bbeg + (GetBlockCount(entry.GetFileSize()) * 512); - fstream.Position = bbeg; - fstream.Write(data, 0, data.Length); - WritePadding(fstream, bend); //write 0's until the end of the block. - } + using var bw = new BinaryWriter(fstream); + parent.InsertFileSpace(bw, entry); + long bbeg = parent.StartPos + (entry.FileOffset * 512); + long bend = bbeg + (GetBlockCount(entry.GetFileSize()) * 512); + fstream.Position = bbeg; + fstream.Write(data, 0, data.Length); + WritePadding(fstream, bend); //write 0's until the end of the block. } @@ -1736,14 +1901,10 @@ namespace CodeWalker.GameFiles file.StartPos = parent.StartPos + (entry.FileOffset * 512); parent.Children.Add(file); - using (var fstream = File.OpenRead(fpath)) - { - using (var br = new BinaryReader(fstream)) - { - fstream.Position = file.StartPos; - file.ScanStructure(br, null, null); - } - } + using var fstream = File.OpenRead(fpath); + using var br = new BinaryReader(fstream); + fstream.Position = file.StartPos; + file.ScanStructure(br, null, null); } return entry; @@ -1756,7 +1917,6 @@ namespace CodeWalker.GameFiles //(since all the paths are generated at runtime and not stored) file.Name = newname; - file.NameLower = newname.ToLowerInvariant(); file.Path = GetParentPath(file.Path) + newname; file.FilePath = GetParentPath(file.FilePath) + newname; @@ -1772,13 +1932,9 @@ namespace CodeWalker.GameFiles string dirpath = GetParentPath(entry.Path); entry.Name = newname; - entry.NameLower = newname.ToLowerInvariant(); entry.Path = dirpath + newname; - string sname = entry.GetShortNameLower(); - JenkIndex.Ensure(sname);//could be anything... but it needs to be there - entry.NameHash = JenkHash.GenHash(newname); - entry.ShortNameHash = JenkHash.GenHash(sname); + JenkIndex.EnsureLower(entry.ShortName);//could be anything... but it needs to be there RpfFile parent = entry.File; string fpath = parent.GetPhysicalFilePath(); @@ -2011,7 +2167,7 @@ namespace CodeWalker.GameFiles } if (!dirpath.EndsWith("\\")) { - dirpath = dirpath + "\\"; + dirpath += "\\"; } return dirpath; } @@ -2038,16 +2194,89 @@ namespace CodeWalker.GameFiles public RpfFile File { get; set; } public RpfDirectoryEntry Parent { get; set; } - public uint NameHash { get; set; } - public uint ShortNameHash { get; set; } + public uint NameHash { get + { + if (nameHash == 0 && !string.IsNullOrEmpty(Name)) + { + nameHash = JenkHash.GenHashLower(Name); + } + return nameHash; + } + set + { + nameHash = value; + } + } + public uint ShortNameHash { get + { + if (shortNameHash == 0 && !string.IsNullOrEmpty(ShortName)) + { + shortNameHash = JenkHash.GenHashLower(ShortName); + } + return shortNameHash; + } + set + { + shortNameHash = value; + } + } public uint NameOffset { get; set; } - public string Name { get; set; } - public string NameLower { get; set; } + public string Name { get => name; set + { + if (name == value) + { + return; + } + name = value; + nameLower = null; + nameHash = 0; + shortNameHash = 0; + shortName = null; + } + } + public string NameLower + { + get + { + return nameLower ??= Name?.ToLowerInvariant(); + } + set { nameLower = value; } + } + + public string ShortName { + get + { + if (string.IsNullOrEmpty(shortName) && !string.IsNullOrEmpty(Name)) + { + int ind = Name.LastIndexOf('.'); + if (ind > 0) + { + shortName = Name.Substring(0, ind); + } + else + { + shortName = Name; + } + } + + return shortName; + } + set + { + shortName = value; + } + } + public string Path { get; set; } 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; public abstract void Read(DataReader reader); public abstract void Write(DataWriter writer); @@ -2059,29 +2288,12 @@ namespace CodeWalker.GameFiles public string GetShortName() { - int ind = Name.LastIndexOf('.'); - if (ind > 0) - { - return Name.Substring(0, ind); - } - return Name; - } - public string GetShortNameLower() - { - if (NameLower == null) - { - NameLower = Name.ToLowerInvariant(); - } - int ind = NameLower.LastIndexOf('.'); - if (ind > 0) - { - return NameLower.Substring(0, ind); - } - return NameLower; + return ShortName; } } - [TypeConverter(typeof(ExpandableObjectConverter))] public class RpfDirectoryEntry : RpfEntry + [TypeConverter(typeof(ExpandableObjectConverter))] + public class RpfDirectoryEntry : RpfEntry { public uint EntriesIndex { get; set; } public uint EntriesCount { get; set; } @@ -2113,7 +2325,8 @@ namespace CodeWalker.GameFiles } } - [TypeConverter(typeof(ExpandableObjectConverter))] public abstract class RpfFileEntry : RpfEntry + [TypeConverter(typeof(ExpandableObjectConverter))] + public abstract class RpfFileEntry : RpfEntry { public uint FileOffset { get; set; } public uint FileSize { get; set; } @@ -2144,7 +2357,7 @@ namespace CodeWalker.GameFiles case 0: IsEncrypted = false; break; case 1: IsEncrypted = true; break; default: - throw new Exception("Error in RPF7 file entry."); + throw new Exception($"Error in RPF7 file entry. {EncryptionType}"); } } @@ -2516,14 +2729,15 @@ namespace CodeWalker.GameFiles public override void Read(DataReader reader) { + var buffer = ArrayPool.Shared.Rent(3); NameOffset = reader.ReadUInt16(); - var buf1 = reader.ReadBytes(3); - FileSize = (uint)buf1[0] + (uint)(buf1[1] << 8) + (uint)(buf1[2] << 16); - - var buf2 = reader.ReadBytes(3); - FileOffset = ((uint)buf2[0] + (uint)(buf2[1] << 8) + (uint)(buf2[2] << 16)) & 0x7FFFFF; + reader.ReadBytes(3, buffer); + FileSize = (uint)buffer[0] + (uint)(buffer[1] << 8) + (uint)(buffer[2] << 16); + reader.ReadBytes(3, buffer); + FileOffset = ((uint)buffer[0] + (uint)(buffer[1] << 8) + (uint)(buffer[2] << 16)) & 0x7FFFFF; + ArrayPool.Shared.Return(buffer); SystemFlags = reader.ReadUInt32(); GraphicsFlags = reader.ReadUInt32(); @@ -2751,6 +2965,10 @@ namespace CodeWalker.GameFiles void Load(byte[] data, RpfFileEntry entry); } + public interface PackedFileStream : PackedFile + { + void Load(Stream stream, RpfFileEntry entry); + } diff --git a/CodeWalker.Core/GameFiles/RpfManager.cs b/CodeWalker.Core/GameFiles/RpfManager.cs index c5e9bfd..63fd00c 100644 --- a/CodeWalker.Core/GameFiles/RpfManager.cs +++ b/CodeWalker.Core/GameFiles/RpfManager.cs @@ -1,5 +1,8 @@ -using System; +using CodeWalker.Core.Utils; +using System; +using System.Collections.Concurrent; using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.Linq; using System.Text; @@ -10,14 +13,19 @@ namespace CodeWalker.GameFiles { public class RpfManager { + private static RpfManager _instance = new RpfManager(); + public static RpfManager GetInstance() + { + return _instance ??= new RpfManager(); + } //for caching and management of RPF file data. public string Folder { get; private set; } public string[] ExcludePaths { get; set; } public bool EnableMods { get; set; } public bool BuildExtendedJenkIndex { get; set; } = true; - public Action UpdateStatus { get; private set; } - public Action ErrorLog { get; private set; } + public event Action UpdateStatus; + public event Action ErrorLog; public List BaseRpfs { get; private set; } public List ModRpfs { get; private set; } @@ -34,8 +42,9 @@ namespace CodeWalker.GameFiles public void Init(string folder, Action updateStatus, Action errorLog, bool rootOnly = false, bool buildIndex = true) { - UpdateStatus = updateStatus; - ErrorLog = errorLog; + using var _ = new DisposableTimer("RpfManager.Init"); + UpdateStatus += updateStatus; + ErrorLog += errorLog; string replpath = folder + "\\"; var sopt = rootOnly ? SearchOption.TopDirectoryOnly : SearchOption.AllDirectories; @@ -52,7 +61,8 @@ namespace CodeWalker.GameFiles ModRpfDict = new Dictionary(); ModEntryDict = new Dictionary(); - foreach (string rpfpath in allfiles) + var rpfs = new ConcurrentBag(); + Parallel.ForEach(allfiles, (rpfpath) => { try { @@ -69,37 +79,66 @@ namespace CodeWalker.GameFiles break; } } - if (excl) continue; //skip files in exclude paths. + if (excl) return; //skip files in exclude paths. } rf.ScanStructure(updateStatus, errorLog); if (rf.LastException != null) //incase of corrupted rpf (or renamed NG encrypted RPF) { - continue; + return; } - AddRpfFile(rf, false, false); + rpfs.Add(rf); } catch (Exception ex) { errorLog(rpfpath + ": " + ex.ToString()); } + }); + + var calculateSum = (RpfFile rpf) => { return 0; }; + + calculateSum = (RpfFile rpf) => + { + return rpf.AllEntries?.Count ?? 0 + rpf.Children?.Sum(calculateSum) ?? 0; + }; + + var minCapacity = rpfs.Sum(calculateSum); + if (minCapacity > AllRpfs.Capacity) + { + AllRpfs.Capacity = minCapacity; + } + + foreach(var rpf in rpfs) + { + AddRpfFile(rpf, false, false); } if (buildIndex) { - updateStatus("Building jenkindex..."); - BuildBaseJenkIndex(); + updateStatus?.Invoke("Building jenkindex..."); + Task.Run(() => + { + BuildBaseJenkIndex(); + IsInited = true; + }); + updateStatus?.Invoke("Scan complete"); } + else + { + updateStatus?.Invoke("Scan complete"); + IsInited = true; + } + + - updateStatus("Scan complete"); - - IsInited = true; + } public void Init(List allRpfs) { + using var _ = new DisposableTimer("RpfManager.Init"); //fast init used by RPF explorer's File cache AllRpfs = allRpfs; @@ -122,9 +161,11 @@ namespace CodeWalker.GameFiles } } - BuildBaseJenkIndex(); - - IsInited = true; + Task.Run(() => + { + BuildBaseJenkIndex(); + IsInited = true; + }); } @@ -182,25 +223,13 @@ namespace CodeWalker.GameFiles EntryDict[entry.Path] = entry; } - if (entry is RpfFileEntry) - { - RpfFileEntry fentry = entry as RpfFileEntry; - entry.NameHash = JenkHash.GenHash(entry.NameLower); - int ind = entry.NameLower.LastIndexOf('.'); - entry.ShortNameHash = (ind > 0) ? JenkHash.GenHash(entry.NameLower.Substring(0, ind)) : entry.NameHash; - if (entry.ShortNameHash != 0) - { - //EntryHashDict[entry.ShortNameHash] = entry; - } - } - } } catch (Exception ex) { file.LastError = ex.ToString(); file.LastException = ex; - ErrorLog(entry.Path + ": " + ex.ToString()); + ErrorLog?.Invoke(entry.Path + ": " + ex.ToString()); } } } @@ -348,12 +377,12 @@ namespace CodeWalker.GameFiles public void BuildBaseJenkIndex() { - JenkIndex.Clear(); - StringBuilder sb = new StringBuilder(); - foreach (RpfFile file in AllRpfs) + using var _ = new DisposableTimer("BuildBaseJenkIndex"); + Parallel.ForEach(AllRpfs, new ParallelOptions { MaxDegreeOfParallelism = 4 }, (file) => { try { + StringBuilder sb = new StringBuilder(); JenkIndex.Ensure(file.Name); foreach (RpfEntry entry in file.AllEntries) { @@ -364,6 +393,7 @@ namespace CodeWalker.GameFiles int ind = nlow.LastIndexOf('.'); if (ind > 0) { + JenkIndex.Ensure(entry.Name.Substring(0, ind)); JenkIndex.Ensure(nlow.Substring(0, ind)); @@ -485,11 +515,16 @@ namespace CodeWalker.GameFiles } } - catch + catch(Exception err) { + ErrorLog?.Invoke(err.ToString()); //failing silently!! not so good really } - } + }); + //foreach (RpfFile file in AllRpfs) + //{ + + //} for (int i = 0; i < 100; i++) { diff --git a/CodeWalker.Core/GameFiles/Utils/DDSIO.cs b/CodeWalker.Core/GameFiles/Utils/DDSIO.cs index 3542699..8463654 100644 --- a/CodeWalker.Core/GameFiles/Utils/DDSIO.cs +++ b/CodeWalker.Core/GameFiles/Utils/DDSIO.cs @@ -105,6 +105,20 @@ using System.Threading.Tasks; namespace CodeWalker.Utils { + public class AssertionFailedException : Exception + { + public AssertionFailedException() : base("Assertion failed") + { + } + + public AssertionFailedException(string message) : base(message) + { + } + + public AssertionFailedException(string message, Exception inner) : base(message, inner) + { + } + } public static class DDSIO { @@ -176,6 +190,7 @@ namespace CodeWalker.Utils 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 @@ -221,8 +236,8 @@ namespace CodeWalker.Utils - MemoryStream ms = new MemoryStream(); - BinaryWriter bw = new BinaryWriter(ms); + using MemoryStream ms = new MemoryStream(texture.Data.FullData.Length + 128); + using BinaryWriter bw = new BinaryWriter(ms); int nimages = img.MipMapLevels; @@ -353,8 +368,8 @@ namespace CodeWalker.Utils throw new Exception("Unsupported texture dimension"); } - byte[] buff = ms.GetBuffer(); + byte[] outbuf = new byte[ms.Length]; //need to copy to the right size buffer for File.WriteAllBytes(). Array.Copy(buff, outbuf, outbuf.Length); @@ -544,8 +559,8 @@ namespace CodeWalker.Utils int add = 0; for (int i = 0; i < img.MipMapLevels; i++) { - images[i].width = img.Width / div; - images[i].height = img.Height / div; + images[i].width = Math.Max(img.Width / div, 1); + images[i].height = Math.Max(img.Height / div, 1); images[i].format = format; //(DXGI_FORMAT)img.Format; images[i].pixels = buf + add; @@ -773,7 +788,7 @@ namespace CodeWalker.Utils break; default: - assert(IsValid(fmt)); + assert(IsValid(fmt), $"{fmt} is not a valid texture format"); assert(!IsCompressed(fmt) && !IsPacked(fmt) && !IsPlanar(fmt)); { @@ -904,11 +919,17 @@ namespace CodeWalker.Utils } } - public static void assert(bool b) + public static void assert(bool b, string message = null) { if (!b) { - throw new Exception(); + if (message is null) + { + throw new AssertionFailedException(); + } else + { + throw new AssertionFailedException(message); + } } } diff --git a/CodeWalker.Core/GameFiles/Utils/Data.cs b/CodeWalker.Core/GameFiles/Utils/Data.cs index 755f942..58d3243 100644 --- a/CodeWalker.Core/GameFiles/Utils/Data.cs +++ b/CodeWalker.Core/GameFiles/Utils/Data.cs @@ -26,11 +26,9 @@ using SharpDX; using System; -using System.Collections.Generic; using System.IO; -using System.Linq; +using System.Runtime.CompilerServices; using System.Text; -using System.Threading.Tasks; namespace CodeWalker.GameFiles { @@ -54,10 +52,12 @@ namespace CodeWalker.GameFiles String = 9, } - public class DataReader + public class DataReader : IDisposable { private Stream baseStream; + private readonly byte[] _buffer = new byte[8]; + /// /// Gets or sets the endianess of the underlying stream. /// @@ -106,15 +106,15 @@ namespace CodeWalker.GameFiles /// 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) + protected virtual byte[] ReadFromStream(int count, bool ignoreEndianess = false, byte[] buffer = null) { - var buffer = new byte[count]; + buffer ??= new byte[count]; baseStream.Read(buffer, 0, count); // handle endianess if (!ignoreEndianess && (Endianess == Endianess.BigEndian)) { - Array.Reverse(buffer); + Array.Reverse(buffer, 0, count); } return buffer; @@ -123,17 +123,22 @@ namespace CodeWalker.GameFiles /// /// Reads a byte. /// - public byte ReadByte() + public virtual byte ReadByte() { - return ReadFromStream(1)[0]; + var result = baseStream.ReadByte(); + if (result == -1) + { + throw new InvalidOperationException("Tried to read from stream beyond end!"); + } + return (byte) result; } /// /// Reads a sequence of bytes. /// - public byte[] ReadBytes(int count) + public byte[] ReadBytes(int count, byte[] buffer = null) { - return ReadFromStream(count, true); + return ReadFromStream(count, true, buffer); } /// @@ -141,7 +146,7 @@ namespace CodeWalker.GameFiles /// public short ReadInt16() { - return BitConverter.ToInt16(ReadFromStream(2), 0); + return BitConverter.ToInt16(ReadFromStream(2, buffer: _buffer), 0); } /// @@ -149,7 +154,7 @@ namespace CodeWalker.GameFiles /// public int ReadInt32() { - return BitConverter.ToInt32(ReadFromStream(4), 0); + return BitConverter.ToInt32(ReadFromStream(4, buffer: _buffer), 0); } /// @@ -157,7 +162,7 @@ namespace CodeWalker.GameFiles /// public long ReadInt64() { - return BitConverter.ToInt64(ReadFromStream(8), 0); + return BitConverter.ToInt64(ReadFromStream(8, buffer: _buffer), 0); } /// @@ -165,7 +170,7 @@ namespace CodeWalker.GameFiles /// public ushort ReadUInt16() { - return BitConverter.ToUInt16(ReadFromStream(2), 0); + return BitConverter.ToUInt16(ReadFromStream(2, buffer: _buffer), 0); } /// @@ -173,7 +178,7 @@ namespace CodeWalker.GameFiles /// public uint ReadUInt32() { - return BitConverter.ToUInt32(ReadFromStream(4), 0); + return BitConverter.ToUInt32(ReadFromStream(4, buffer: _buffer), 0); } /// @@ -181,7 +186,7 @@ namespace CodeWalker.GameFiles /// public ulong ReadUInt64() { - return BitConverter.ToUInt64(ReadFromStream(8), 0); + return BitConverter.ToUInt64(ReadFromStream(8, buffer: _buffer), 0); } /// @@ -189,7 +194,7 @@ namespace CodeWalker.GameFiles /// public float ReadSingle() { - return BitConverter.ToSingle(ReadFromStream(4), 0); + return BitConverter.ToSingle(ReadFromStream(4, buffer: _buffer), 0); } /// @@ -197,23 +202,51 @@ namespace CodeWalker.GameFiles /// public double ReadDouble() { - return BitConverter.ToDouble(ReadFromStream(8), 0); + return BitConverter.ToDouble(ReadFromStream(8, buffer: _buffer), 0); } /// /// Reads a string. /// - public string ReadString() + unsafe public string ReadString(int maxLength = 1024) { - var bytes = new List(); - var temp = ReadFromStream(1)[0]; - while (temp != 0) + var bytes = stackalloc byte[Math.Min(maxLength, 1024)]; + var temp = ReadByte(); + var charsRead = 0; + while (temp != 0 && (Length == -1 || Position <= Length)) { - bytes.Add(temp); - temp = ReadFromStream(1)[0]; + if (charsRead > 1023) + { + throw new Exception("String too long!"); + } + if (charsRead < maxLength) + { + bytes[charsRead] = temp; + } + temp = ReadByte(); + charsRead++; } - return Encoding.UTF8.GetString(bytes.ToArray()); + return Encoding.UTF8.GetString(bytes, Math.Min(charsRead, maxLength)); + } + + unsafe public string ReadStringLower() + { + var bytes = stackalloc byte[1024]; + var temp = ReadByte(); + var charsRead = 0; + while (temp != 0 && (Length == -1 || Position <= Length)) + { + if (charsRead > 1023) + { + throw new Exception("String too long!"); + } + bytes[charsRead] = temp; + temp = ReadByte(); + charsRead++; + } + + return Encoding.UTF8.GetString(bytes, charsRead); } @@ -279,12 +312,13 @@ namespace CodeWalker.GameFiles } } - - - + public virtual void Dispose() + { + baseStream?.Dispose(); + } } - public class DataWriter + public class DataWriter : IDisposable { private Stream baseStream; @@ -476,6 +510,15 @@ namespace CodeWalker.GameFiles Write(value.M44); } + public virtual void Dispose() + { + baseStream.Dispose(); + } + + public virtual void Close() + { + baseStream.Close(); + } } diff --git a/CodeWalker.Core/GameFiles/Utils/GTACrypto.cs b/CodeWalker.Core/GameFiles/Utils/GTACrypto.cs index d6ccd47..682b3a9 100644 --- a/CodeWalker.Core/GameFiles/Utils/GTACrypto.cs +++ b/CodeWalker.Core/GameFiles/Utils/GTACrypto.cs @@ -23,8 +23,12 @@ //shamelessly stolen using System; +using System.Buffers; using System.Collections.Generic; +using System.Diagnostics; +using System.IO; using System.Linq; +using System.Runtime.InteropServices; using System.Security.Cryptography; using System.Text; using System.Threading.Tasks; @@ -34,7 +38,10 @@ namespace CodeWalker.GameFiles public class GTACrypto { - + public static byte[] DecryptAES(byte[] data, int length) + { + return DecryptAESData(data, GTA5Keys.PC_AES_KEY, length, 1); + } public static byte[] DecryptAES(byte[] data) { return DecryptAESData(data, GTA5Keys.PC_AES_KEY); @@ -45,6 +52,11 @@ namespace CodeWalker.GameFiles } public static byte[] DecryptAESData(byte[] data, byte[] key, int rounds = 1) + { + return DecryptAESData(data, key, data.Length, rounds); + } + + public static byte[] DecryptAESData(byte[] data, byte[] key, int length, int rounds = 1) { var rijndael = Rijndael.Create(); rijndael.KeySize = 256; @@ -53,18 +65,17 @@ namespace CodeWalker.GameFiles rijndael.Mode = CipherMode.ECB; rijndael.Padding = PaddingMode.None; - var buffer = (byte[])data.Clone(); - var length = data.Length - data.Length % 16; + length = length - length % 16; // decrypt... if (length > 0) { var decryptor = rijndael.CreateDecryptor(); for (var roundIndex = 0; roundIndex < rounds; roundIndex++) - decryptor.TransformBlock(buffer, 0, length, buffer, 0); + decryptor.TransformBlock(data, 0, length, data, 0); } - return buffer; + return data; } public static byte[] EncryptAESData(byte[] data, byte[] key, int rounds = 1) { @@ -93,7 +104,7 @@ namespace CodeWalker.GameFiles - public static byte[] GetNGKey(string name, uint length) + public static uint[][] GetNGKey(string name, uint length) { uint hash = GTA5Hash.CalculateHash(name); uint keyidx = (hash + length + (101 - 40)) % 0x65; @@ -101,63 +112,61 @@ namespace CodeWalker.GameFiles } - public static byte[] DecryptNG(byte[] data, string name, uint length) + public static byte[] DecryptNG(byte[] data, string name, uint fileSize, int offset = 0, int? length = null) { - byte[] key = GetNGKey(name, length); - return DecryptNG(data, key); + var key = GetNGKey(name, fileSize); + return DecryptNG(data, key, offset, length); } - public static byte[] DecryptNG(byte[] data, byte[] key) + public unsafe static byte[] DecryptNG(byte[] data, uint[][] key, int offset = 0, int? length = null) { - var decryptedData = new byte[data.Length]; - - var keyuints = new uint[key.Length / 4]; - Buffer.BlockCopy(key, 0, keyuints, 0, key.Length); - - for (int blockIndex = 0; blockIndex < data.Length / 16; blockIndex++) + length ??= data.Length; + fixed (byte* bptr = data) { - var encryptedBlock = new byte[16]; - Array.Copy(data, 16 * blockIndex, encryptedBlock, 0, 16); - var decryptedBlock = DecryptNGBlock(encryptedBlock, keyuints); - Array.Copy(decryptedBlock, 0, decryptedData, 16 * blockIndex, 16); + for (int blockIndex = offset * 16; blockIndex < length / 16; blockIndex++) + { + DecryptNGBlock(bptr + 16 * blockIndex, key); + } } - if (data.Length % 16 != 0) - { - var left = data.Length % 16; - Buffer.BlockCopy(data, data.Length - left, decryptedData, data.Length - left, left); - } - - return decryptedData; + return data; } - public static byte[] DecryptNGBlock(byte[] data, uint[] key) + public unsafe static void DecryptNGBlock(byte[] data, uint[][] key) { - var buffer = data; - - // prepare key... - var subKeys = new uint[17][]; - for (int i = 0; i < 17; i++) + fixed(byte* bptr = data) { - subKeys[i] = new uint[4]; - subKeys[i][0] = key[4 * i + 0]; - subKeys[i][1] = key[4 * i + 1]; - subKeys[i][2] = key[4 * i + 2]; - subKeys[i][3] = key[4 * i + 3]; + DecryptNGBlock(data, key); } + } - buffer = DecryptNGRoundA(buffer, subKeys[0], GTA5Keys.PC_NG_DECRYPT_TABLES[0]); - buffer = DecryptNGRoundA(buffer, subKeys[1], GTA5Keys.PC_NG_DECRYPT_TABLES[1]); + public unsafe static void DecryptNGBlock(byte* data, uint[][] key) + { + DecryptNGRoundA(data, key[0], GTA5Keys.PC_NG_DECRYPT_TABLES[0]); + DecryptNGRoundA(data, key[1], GTA5Keys.PC_NG_DECRYPT_TABLES[1]); for (int k = 2; k <= 15; k++) - buffer = DecryptNGRoundB(buffer, subKeys[k], GTA5Keys.PC_NG_DECRYPT_TABLES[k]); - buffer = DecryptNGRoundA(buffer, subKeys[16], GTA5Keys.PC_NG_DECRYPT_TABLES[16]); - - return buffer; + DecryptNGRoundB(data, key[k], GTA5Keys.PC_NG_DECRYPT_TABLES[k]); + DecryptNGRoundA(data, key[16], GTA5Keys.PC_NG_DECRYPT_TABLES[16]); } + public unsafe static void DecryptNGRoundA(byte[] data, uint[] key, uint[][] table) + { + fixed(byte* bptr = data) + { + DecryptNGRoundA(bptr, key, table); + } + } + + public unsafe static void DecryptNGRoundB(byte[] data, uint[] key, uint[][] table) + { + fixed (byte* bptr = data) + { + DecryptNGRoundB(bptr, key, table); + } + } // round 1,2,16 - public static byte[] DecryptNGRoundA(byte[] data, uint[] key, uint[][] table) + public unsafe static void DecryptNGRoundA(byte* data, uint[] key, uint[][] table) { var x1 = table[0][data[0]] ^ @@ -184,18 +193,14 @@ namespace CodeWalker.GameFiles table[15][data[15]] ^ key[3]; - var result = new byte[16]; - Array.Copy(BitConverter.GetBytes(x1), 0, result, 0, 4); - Array.Copy(BitConverter.GetBytes(x2), 0, result, 4, 4); - Array.Copy(BitConverter.GetBytes(x3), 0, result, 8, 4); - Array.Copy(BitConverter.GetBytes(x4), 0, result, 12, 4); - return result; + *(uint*)data = x1; + *(uint*)(data + 4) = x2; + *(uint*)(data + 8) = x3; + *(uint*)(data + 12) = x4; } - - // round 3-15 - public static byte[] DecryptNGRoundB(byte[] data, uint[] key, uint[][] table) + public unsafe static void DecryptNGRoundB(byte* data, uint[] key, uint[][] table) { var x1 = table[0][data[0]] ^ @@ -222,31 +227,10 @@ namespace CodeWalker.GameFiles table[12][data[12]] ^ key[3]; - //var result = new byte[16]; - //Array.Copy(BitConverter.GetBytes(x1), 0, result, 0, 4); - //Array.Copy(BitConverter.GetBytes(x2), 0, result, 4, 4); - //Array.Copy(BitConverter.GetBytes(x3), 0, result, 8, 4); - //Array.Copy(BitConverter.GetBytes(x4), 0, result, 12, 4); - //return result; - - var result = new byte[16]; - result[0] = (byte)((x1 >> 0) & 0xFF); - result[1] = (byte)((x1 >> 8) & 0xFF); - result[2] = (byte)((x1 >> 16) & 0xFF); - result[3] = (byte)((x1 >> 24) & 0xFF); - result[4] = (byte)((x2 >> 0) & 0xFF); - result[5] = (byte)((x2 >> 8) & 0xFF); - result[6] = (byte)((x2 >> 16) & 0xFF); - result[7] = (byte)((x2 >> 24) & 0xFF); - result[8] = (byte)((x3 >> 0) & 0xFF); - result[9] = (byte)((x3 >> 8) & 0xFF); - result[10] = (byte)((x3 >> 16) & 0xFF); - result[11] = (byte)((x3 >> 24) & 0xFF); - result[12] = (byte)((x4 >> 0) & 0xFF); - result[13] = (byte)((x4 >> 8) & 0xFF); - result[14] = (byte)((x4 >> 16) & 0xFF); - result[15] = (byte)((x4 >> 24) & 0xFF); - return result; + *(uint*)data = x1; + *(uint*)(data + 4) = x2; + *(uint*)(data + 8) = x3; + *(uint*)(data + 12) = x4; } @@ -267,11 +251,11 @@ namespace CodeWalker.GameFiles public static byte[] EncryptNG(byte[] data, string name, uint length) { - byte[] key = GetNGKey(name, length); + var key = GetNGKey(name, length); return EncryptNG(data, key); } - public static byte[] EncryptNG(byte[] data, byte[] key) + public static byte[] EncryptNG(byte[] data, uint[][] key) { if ((GTA5Keys.PC_NG_ENCRYPT_TABLES == null) || (GTA5Keys.PC_NG_ENCRYPT_LUTs == null)) { @@ -280,14 +264,11 @@ namespace CodeWalker.GameFiles var encryptedData = new byte[data.Length]; - var keyuints = new uint[key.Length / 4]; - Buffer.BlockCopy(key, 0, keyuints, 0, key.Length); - for (int blockIndex = 0; blockIndex < data.Length / 16; blockIndex++) { byte[] decryptedBlock = new byte[16]; Array.Copy(data, 16 * blockIndex, decryptedBlock, 0, 16); - byte[] encryptedBlock = EncryptBlock(decryptedBlock, keyuints); + byte[] encryptedBlock = EncryptBlock(decryptedBlock, key); Array.Copy(encryptedBlock, 0, encryptedData, 16 * blockIndex, 16); } @@ -300,26 +281,15 @@ namespace CodeWalker.GameFiles return encryptedData; } - public static byte[] EncryptBlock(byte[] data, uint[] key) + public static byte[] EncryptBlock(byte[] data, uint[][] key) { var buffer = data; - // prepare key... - var subKeys = new uint[17][]; - for (int i = 0; i < 17; i++) - { - subKeys[i] = new uint[4]; - subKeys[i][0] = key[4 * i + 0]; - subKeys[i][1] = key[4 * i + 1]; - subKeys[i][2] = key[4 * i + 2]; - subKeys[i][3] = key[4 * i + 3]; - } - - buffer = EncryptRoundA(buffer, subKeys[16], GTA5Keys.PC_NG_ENCRYPT_TABLES[16]); + buffer = EncryptRoundA(buffer, key[16], GTA5Keys.PC_NG_ENCRYPT_TABLES[16]); for (int k = 15; k >= 2; k--) - buffer = EncryptRoundB_LUT(buffer, subKeys[k], GTA5Keys.PC_NG_ENCRYPT_LUTs[k]); - buffer = EncryptRoundA(buffer, subKeys[1], GTA5Keys.PC_NG_ENCRYPT_TABLES[1]); - buffer = EncryptRoundA(buffer, subKeys[0], GTA5Keys.PC_NG_ENCRYPT_TABLES[0]); + buffer = EncryptRoundB_LUT(buffer, key[k], GTA5Keys.PC_NG_ENCRYPT_LUTs[k]); + buffer = EncryptRoundA(buffer, key[1], GTA5Keys.PC_NG_ENCRYPT_TABLES[1]); + buffer = EncryptRoundA(buffer, key[0], GTA5Keys.PC_NG_ENCRYPT_TABLES[0]); return buffer; } @@ -425,5 +395,136 @@ namespace CodeWalker.GameFiles + } + + public class DecryptNGStream : Stream + { + public string Name { get; set; } + + private long _length = 0; + + private Stream _baseStream; + + public Stream BaseStream { get => _baseStream; private set => _baseStream = value; } + + public uint[][] Key { get; set; } + private long offset = 0; + + public override long Length + { + get + { + if (_length != 0) return _length; + return _baseStream.Length - offset; + } + } + + private DecryptNGStream(string name, uint length) + { + _length = length; + Name = name; + Key = GTACrypto.GetNGKey(name, length); + } + + public DecryptNGStream(byte[] data, string name, uint fileSize) : this(name, fileSize) + { + BaseStream = new MemoryStream(data); + } + + public DecryptNGStream(Stream data, string name, uint fileSize) : this(name, fileSize) + { + BaseStream = data; + } + + public DecryptNGStream(Stream data, string name, uint fileSize, int start, int length = 0): this(data, name, fileSize) + { + this.offset = start; + this._length = length; + } + + public override bool CanRead => _baseStream.CanRead; + + public override bool CanSeek => _baseStream.CanSeek; + + public override bool CanWrite => _baseStream.CanWrite; + + public override long Position + { + get + { + var position = _baseStream.Position - offset; + + position = position - position % 16; + return position + positionInBuffer; + } + set + { + positionInBuffer = 0; + _baseStream.Position = (value + offset); + ReadBlock(); + } + } + + public override void Flush() + { + return; + } + + public void ReadBlock() + { + _baseStream.Read(_buffer, 0, _buffer.Length); + } + + private byte[] _buffer = new byte[16]; + private byte positionInBuffer = 0; + public override int Read(byte[] buffer, int offset, int count) + { + if (positionInBuffer > 0) + { + var toCopy = (byte)Math.Min(16 - positionInBuffer, count); + Array.Copy(_buffer, positionInBuffer, buffer, offset, toCopy); + positionInBuffer += toCopy; + if (positionInBuffer >= 16) + { + positionInBuffer = 0; + } + offset += toCopy; + count -= toCopy; + } + var i = 0; + while (count >= 16) + { + _baseStream.Read(buffer, offset + i, 16); + i += 16; + } + if (count > 0) + { + _baseStream.Read(_buffer, 0, 16); + GTACrypto.DecryptNG(_buffer, Key, 0, 16); + Array.Copy(_buffer, positionInBuffer, buffer, offset + i, count - i); + positionInBuffer += (byte)(count - i); + } + var data = _baseStream.Read(buffer, offset, count); + + GTACrypto.DecryptNG(buffer, Key, offset, count); + + return data; + } + + public override long Seek(long offset, SeekOrigin origin) + { + throw new NotImplementedException(); + } + + public override void SetLength(long value) + { + throw new NotSupportedException(); + } + + public override void Write(byte[] buffer, int offset, int count) + { + + throw new NotImplementedException(); + } } } diff --git a/CodeWalker.Core/GameFiles/Utils/GTAKeys.cs b/CodeWalker.Core/GameFiles/Utils/GTAKeys.cs index 7a3c1b9..5a6ad7c 100644 --- a/CodeWalker.Core/GameFiles/Utils/GTAKeys.cs +++ b/CodeWalker.Core/GameFiles/Utils/GTAKeys.cs @@ -47,7 +47,7 @@ namespace CodeWalker.GameFiles public static byte[] PC_AES_KEY; // 32 // ng decryption/encryption expanded keys... - public static byte[][] PC_NG_KEYS; // 101, 272 + public static uint[][][] PC_NG_KEYS; // 101, 272 // ng decryption tables... public static uint[][][] PC_NG_DECRYPT_TABLES; // 17, 16, 256 @@ -73,15 +73,26 @@ namespace CodeWalker.GameFiles var exeStr = new MemoryStream(exeData); updateStatus("Searching for AES key..."); - PC_AES_KEY = HashSearch.SearchHash(exeStr, GTA5KeyHashes.PC_AES_KEY_HASH, 0x20); + PC_AES_KEY = HashSearch.SearchHash(exeStr, GTA5KeyHashes.PC_AES_KEY_HASH, 32); //updateStatus("aes key found"); updateStatus("Searching for NG keys..."); - PC_NG_KEYS = HashSearch.SearchHashes(exeStr, GTA5KeyHashes.PC_NG_KEY_HASHES, 0x110); + //PC_NG_KEYS = HashSearch.SearchHashes(exeStr, GTA5KeyHashes.PC_NG_KEY_HASHES, 272); + var tabs = HashSearch.SearchHashes(exeStr, GTA5KeyHashes.PC_NG_KEY_HASHES, 272); + PC_NG_KEYS = new uint[tabs.Length][][]; + for (int i = 0; i < tabs.Length; i++) + { + PC_NG_KEYS[i] = new uint[tabs[i].Length / 4 / 4][]; + for (int j = 0; j < tabs[i].Length / 4; j++) + { + Buffer.BlockCopy(tabs[i], 0, PC_NG_KEYS[i][j], 0, 16); + } + //Buffer.BlockCopy(tabs[i], 0, PC_NG_DECRYPT_TABLES[i][j], 0, tabs[i].Length); + } //updateStatus("ng keys found"); updateStatus("Searching for NG decrypt tables..."); - var tabs = HashSearch.SearchHashes(exeStr, GTA5KeyHashes.PC_NG_DECRYPT_TABLE_HASHES, 0x400); + tabs = HashSearch.SearchHashes(exeStr, GTA5KeyHashes.PC_NG_DECRYPT_TABLE_HASHES, 1024); //updateStatus("ng decrypt tables found"); updateStatus("Searching for NG hash lookup tables..."); @@ -277,14 +288,14 @@ namespace CodeWalker.GameFiles db[i] = (byte)((m[i] - rb1[i] - rb2[i] - rb3[i] - rb4[i]) & 0xFF); } - db = GTACrypto.DecryptAESData(db, PC_AES_KEY); + GTACrypto.DecryptAESData(db, PC_AES_KEY); byte[] b = null; using (MemoryStream dms = new MemoryStream(db)) { using (DeflateStream ds = new DeflateStream(dms, CompressionMode.Decompress)) { - using (MemoryStream outstr = new MemoryStream()) + using (MemoryStream outstr = RpfFile.recyclableMemoryStreamManager.GetStream()) { ds.CopyTo(outstr); b = outstr.GetBuffer(); @@ -848,11 +859,13 @@ namespace CodeWalker.GameFiles random.NextBytes(buf_encrypted); // decrypt - var buf_decrypted = GTACrypto.DecryptNGRoundA( + GTACrypto.DecryptNGRoundA( buf_encrypted, noKey, tables); + var buf_decrypted = buf_encrypted; + // make row var row = new RandomGaussRow(); //row.A[0 + buf_decrypted[inByte0]] = true; @@ -1148,33 +1161,35 @@ namespace CodeWalker.GameFiles public class CryptoIO { - public static byte[][] ReadNgKeys(string fileName) + public static uint[][][] ReadNgKeys(string fileName) { - byte[][] result; + using var fs = new FileStream(fileName, FileMode.Open); + using var rd = new DataReader(fs); - var fs = new FileStream(fileName, FileMode.Open); - var rd = new DataReader(fs); - - result = new byte[101][]; - for (int i = 0; i < 101; i++) - { - result[i] = rd.ReadBytes(272); - } - - fs.Close(); - - return result; + return ReadNgKeys(rd); } - public static byte[][] ReadNgKeys(byte[] data) + public static uint[][][] ReadNgKeys(byte[] data) { - byte[][] result; + using var rd = new DataReader(new MemoryStream(data)); - var rd = new DataReader(new MemoryStream(data)); + return ReadNgKeys(rd); + } - result = new byte[101][]; + public static uint[][][] ReadNgKeys(DataReader rd) + { + var result = new uint[101][][]; for (int i = 0; i < 101; i++) { - result[i] = rd.ReadBytes(272); + result[i] = new uint[68 / 4][]; + for (int j = 0; j < 68 / 4; j++) + { + result[i][j] = new uint[4]; + for (int k = 0; k < 4; k++) + { + result[i][j][k] = rd.ReadUInt32(); + } + + } } @@ -1182,16 +1197,23 @@ namespace CodeWalker.GameFiles } - public static void WriteNgKeys(string fileName, byte[][] keys) + public static void WriteNgKeys(string fileName, uint[][][] keys) { //var fs = new FileStream(fileName, FileMode.Create); //var wr = new DataWriter(fs); - var ms = new MemoryStream(); - var wr = new DataWriter(ms); + using var ms = new MemoryStream(); + using var wr = new DataWriter(ms); for (int i = 0; i < 101; i++) { - wr.Write(keys[i]); + for (int j = 0; j < keys[i].Length; j++) + { + for (int k = 0; j < keys[i][j].Length; k++) + { + wr.Write(keys[i][j][k]); + } + + } } //fs.Close(); @@ -1207,8 +1229,8 @@ namespace CodeWalker.GameFiles { uint[][][] result; - var fs = new FileStream(fileName, FileMode.Open); - var rd = new DataReader(fs); + using var fs = new FileStream(fileName, FileMode.Open); + using var rd = new DataReader(fs); // 17 rounds... result = new uint[17][][]; @@ -1235,7 +1257,7 @@ namespace CodeWalker.GameFiles { uint[][][] result; - var rd = new DataReader(new MemoryStream(data)); + using var rd = new DataReader(new MemoryStream(data)); // 17 rounds... result = new uint[17][][]; @@ -1263,8 +1285,8 @@ namespace CodeWalker.GameFiles { //var fs = new FileStream(fileName, FileMode.Create); //var wr = new DataWriter(fs); - var ms = new MemoryStream(); - var wr = new DataWriter(ms); + using var ms = new MemoryStream(); + using var wr = new DataWriter(ms); // 17 rounds... for (int i = 0; i < 17; i++) @@ -1293,8 +1315,8 @@ namespace CodeWalker.GameFiles { GTA5NGLUT[][] result; - var fs = new FileStream(fileName, FileMode.Open); - var rd = new DataReader(fs); + using var fs = new FileStream(fileName, FileMode.Open); + using var rd = new DataReader(fs); // 17 rounds... result = new GTA5NGLUT[17][]; diff --git a/CodeWalker.Core/GameFiles/Utils/Jenk.cs b/CodeWalker.Core/GameFiles/Utils/Jenk.cs index 00164e8..dd85ad3 100644 --- a/CodeWalker.Core/GameFiles/Utils/Jenk.cs +++ b/CodeWalker.Core/GameFiles/Utils/Jenk.cs @@ -1,8 +1,11 @@ using System; +using System.Collections.Concurrent; using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.Linq; using System.Text; +using System.Threading; using System.Threading.Tasks; namespace CodeWalker.GameFiles @@ -55,6 +58,24 @@ namespace CodeWalker.GameFiles return h; } + public static uint GenHashLower(string text) + { + if (text == null) return 0; + uint h = 0; + for (int i = 0; i < text.Length; i++) + { + + h += (byte)char.ToLowerInvariant(text[i]); + h += (h << 10); + h ^= (h >> 6); + } + h += (h << 3); + h ^= (h >> 11); + h += (h << 15); + + return h; + } + public static uint GenHash(string text) { if (text == null) return 0; @@ -192,59 +213,96 @@ namespace CodeWalker.GameFiles + public static class DictionaryExtension + { + public static bool TryAdd(this Dictionary dict, TKey key, TValue value) + { + if (dict.ContainsKey(key)) + { + return false; + } + dict[key] = value; + return true; + } + } public static class JenkIndex { - public static Dictionary Index = new Dictionary(); - private static object syncRoot = new object(); + public static ConcurrentDictionary Index = new ConcurrentDictionary(Environment.ProcessorCount, 1500000); public static void Clear() { - lock (syncRoot) - { - Index.Clear(); - } + Index.Clear(); } public static bool Ensure(string str) { uint hash = JenkHash.GenHash(str); if (hash == 0) return true; - lock (syncRoot) + if (Index.ContainsKey(hash)) { - if (!Index.ContainsKey(hash)) - { - Index.Add(hash, str); - return false; - } + return true; + } + lock (Index) + { + Index[hash] = str; + return false; + } + } + + public static bool EnsureLower(string str) + { + uint hash = JenkHash.GenHashLower(str); + if (hash == 0) return true; + if (Index.ContainsKey(hash)) + { + return true; + } + + Index.TryAdd(hash, str); + return false; + } + + public static void AddRange(params string[] strings) + { + foreach(var s in strings) + { + uint hash = JenkHash.GenHash(s); + if (hash == 0) continue; + + Index[hash] = s; + } + } + + public static void AddRangeLower(params string[] strings) + { + foreach (var s in strings) + { + uint hash = JenkHash.GenHashLower(s); + if (hash == 0) continue; + + Index[hash] = s; } - return true; } public static string GetString(uint hash) { string res; - lock (syncRoot) + if (!Index.TryGetValue(hash, out res)) { - if (!Index.TryGetValue(hash, out res)) - { - res = hash.ToString(); - } + res = hash.ToString(); } return res; } public static string TryGetString(uint hash) { string res; - lock (syncRoot) + if (!Index.TryGetValue(hash, out res)) { - if (!Index.TryGetValue(hash, out res)) - { - res = string.Empty; - } + res = string.Empty; } return res; } @@ -252,10 +310,7 @@ namespace CodeWalker.GameFiles public static string[] GetAllStrings() { string[] res = null; - lock (syncRoot) - { - res = Index.Values.ToArray(); - } + res = Index.Values.ToArray(); return res; } diff --git a/CodeWalker.Core/Tests/GameFileCache.cs b/CodeWalker.Core/Tests/GameFileCache.cs new file mode 100644 index 0000000..6443693 --- /dev/null +++ b/CodeWalker.Core/Tests/GameFileCache.cs @@ -0,0 +1,2496 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Text; +using System.Xml; + +namespace CodeWalker.GameFiles +{ + public partial class GameFileCache + { + [Conditional("TEST_ALL"), Conditional("TEST_CACHES")] + public void TestCacheFiles() + { + foreach (RpfFile file in AllRpfs) + { + foreach (RpfEntry entry in file.AllEntries) + { + try + { + if (entry.NameLower.EndsWith("cache_y.dat"))// || entry.NameLower.EndsWith("cache_y_bank.dat")) + { + UpdateStatus?.Invoke(string.Format(entry.Path)); + var cdfile = RpfMan.GetFile(entry); + if (cdfile != null) + { + var odata = entry.File.ExtractFile(entry as RpfFileEntry); + //var ndata = cdfile.Save(); + + var xml = CacheDatXml.GetXml(cdfile); + var cdf2 = XmlCacheDat.GetCacheDat(xml); + var ndata = cdf2.Save(); + + if (ndata.Length == odata.Length) + { + for (int i = 0; i < ndata.Length; i++) + { + if (ndata[i] != odata[i]) + { break; } + } + } + else + { } + } + else + { } + } + } + catch (Exception ex) + { + UpdateStatus?.Invoke("Error! " + ex.ToString()); + } + } + } + } + + [Conditional("TEST_ALL"), Conditional("TEST_HEIGHTMAPS")] + public void TestHeightmaps() + { + var errorfiles = new List(); + foreach (RpfFile file in AllRpfs) + { + foreach (RpfEntry entry in file.AllEntries) + { + if (entry.NameLower.EndsWith(".dat") && entry.NameLower.StartsWith("heightmap")) + { + UpdateStatus?.Invoke(string.Format(entry.Path)); + HeightmapFile hmf = null; + hmf = RpfMan.GetFile(entry); + var d1 = hmf.RawFileData; + //var d2 = hmf.Save(); + var xml = HmapXml.GetXml(hmf); + var hmf2 = XmlHmap.GetHeightmap(xml); + var d2 = hmf2.Save(); + + if (d1.Length == d2.Length) + { + for (int i = 0; i < d1.Length; i++) + { + if (d1[i] != d2[i]) + { } + } + } + else + { } + + } + } + } + if (errorfiles.Count > 0) + { } + } + + [Conditional("TEST_ALL"), Conditional("TEST_WATERMAPS")] + public void TestWatermaps() + { + var errorfiles = new List(); + foreach (RpfFile file in AllRpfs) + { + foreach (RpfEntry entry in file.AllEntries) + { + if (entry.NameLower.EndsWith(".dat") && entry.NameLower.StartsWith("waterheight")) + { + UpdateStatus?.Invoke(string.Format(entry.Path)); + WatermapFile wmf = null; + wmf = RpfMan.GetFile(entry); + //var d1 = wmf.RawFileData; + //var d2 = wmf.Save(); + //var xml = WatermapXml.GetXml(wmf); + //var wmf2 = XmlWatermap.GetWatermap(xml); + //var d2 = wmf2.Save(); + + //if (d1.Length == d2.Length) + //{ + // for (int i = 0; i < d1.Length; i++) + // { + // if (d1[i] != d2[i]) + // { } + // } + //} + //else + //{ } + + } + } + } + if (errorfiles.Count > 0) + { } + } + + [Conditional("TEST_ALL"), Conditional("TEST_RELS")] + public void TestAudioRels() + { + UpdateStatus?.Invoke("Testing Audio REL files"); + + + bool savetest = true; + bool xmltest = true; + bool asmtest = true; + + foreach (RpfFile rpf in RpfMan.AllRpfs) + { + foreach (RpfEntry entry in rpf.AllEntries) + { + var rfe = entry as RpfFileEntry; + var rbfe = rfe as RpfBinaryFileEntry; + if ((rfe == null) || (rbfe == null)) continue; + + if (rfe.Name.EndsWith(".rel", StringComparison.OrdinalIgnoreCase)) + { + UpdateStatus?.Invoke(string.Format(entry.Path)); + + RelFile rel = new RelFile(rfe); + RpfMan.LoadFile(rel, rfe); + + + + byte[] data; + + if (savetest) + { + + data = rel.Save(); + if (data != null) + { + if (data.Length != rbfe.FileUncompressedSize) + { } + else if (data.Length != rel.RawFileData.Length) + { } + else + { + for (int i = 0; i < data.Length; i++) //raw file test + if (data[i] != rel.RawFileData[i]) + { break; } + } + + + RelFile rel2 = new RelFile(); + rel2.Load(data, rfe);//roundtrip test + + if (rel2.IndexCount != rel.IndexCount) + { } + if (rel2.RelDatas == null) + { } + + } + else + { } + + } + + if (xmltest) + { + var relxml = RelXml.GetXml(rel); //XML test... + var rel3 = XmlRel.GetRel(relxml); + if (rel3 != null) + { + if (rel3.RelDatasSorted?.Length != rel.RelDatasSorted?.Length) + { } //check nothing went missing... + + + data = rel3.Save(); //full roundtrip! + if (data != null) + { + var rel4 = new RelFile(); + rel4.Load(data, rfe); //insanity check + + if (data.Length != rbfe.FileUncompressedSize) + { } + else if (data.Length != rel.RawFileData.Length) + { } + else + { + for (int i = 0; i < data.Length; i++) //raw file test + if (data[i] != rel.RawFileData[i]) + { break; } + } + + var relxml2 = RelXml.GetXml(rel4); //full insanity + if (relxml2.Length != relxml.Length) + { } + if (relxml2 != relxml) + { } + + } + else + { } + } + else + { } + + } + + if (asmtest) + { + if (rel.RelType == RelDatFileType.Dat10ModularSynth) + { + foreach (var d in rel.RelDatasSorted) + { + if (d is Dat10Synth synth) + { + synth.TestDisassembly(); + } + } + } + } + + } + + } + + } + + + + var hashmap = RelFile.HashesMap; + if (hashmap.Count > 0) + { } + + + var sb2 = new StringBuilder(); + foreach (var kvp in hashmap) + { + string itemtype = kvp.Key.ItemType.ToString(); + if (kvp.Key.FileType == RelDatFileType.Dat151) + { + itemtype = ((Dat151RelType)kvp.Key.ItemType).ToString(); + } + else if (kvp.Key.FileType == RelDatFileType.Dat54DataEntries) + { + itemtype = ((Dat54SoundType)kvp.Key.ItemType).ToString(); + } + else + { + itemtype = kvp.Key.FileType.ToString() + ".Unk" + kvp.Key.ItemType.ToString(); + } + if (kvp.Key.IsContainer) + { + itemtype += " (container)"; + } + + //if (kvp.Key.FileType == RelDatFileType.Dat151) + { + sb2.Append(itemtype); + sb2.Append(" "); + foreach (var val in kvp.Value) + { + sb2.Append(val.ToString()); + sb2.Append(" "); + } + + sb2.AppendLine(); + } + + } + + var hashmapstr = sb2.ToString(); + if (!string.IsNullOrEmpty(hashmapstr)) + { } + + } + + [Conditional("TEST_ALL"), Conditional("TEST_YMTS")] + public void TestAudioYmts() + { + + StringBuilder sb = new StringBuilder(); + + Dictionary allids = new Dictionary(); + + foreach (RpfFile file in AllRpfs) + { + foreach (RpfEntry entry in file.AllEntries) + { + try + { + var n = entry.NameLower; + if (n.EndsWith(".ymt")) + { + UpdateStatus?.Invoke(string.Format(entry.Path)); + //YmtFile ymtfile = RpfMan.GetFile(entry); + //if ((ymtfile != null)) + //{ + //} + + var sn = entry.GetShortName(); + uint un; + if (uint.TryParse(sn, out un)) + { + if (allids.ContainsKey(un)) + { + allids[un] = allids[un] + 1; + } + else + { + allids[un] = 1; + //ushort s1 = (ushort)(un & 0x1FFFu); + //ushort s2 = (ushort)((un >> 13) & 0x1FFFu); + uint s1 = un % 80000; + uint s2 = (un / 80000); + float f1 = s1 / 5000.0f; + float f2 = s2 / 5000.0f; + sb.AppendFormat("{0}, {1}, 0, {2}\r\n", f1, f2, sn); + } + } + + + } + } + catch (Exception ex) + { + UpdateStatus?.Invoke("Error! " + ex.ToString()); + } + } + } + + var skeys = allids.Keys.ToList(); + skeys.Sort(); + + var hkeys = new List(); + foreach (var skey in skeys) + { + FlagsUint fu = new FlagsUint(skey); + //hkeys.Add(skey.ToString("X")); + hkeys.Add(fu.Bin); + } + + string nstr = string.Join("\r\n", hkeys.ToArray()); + string pstr = sb.ToString(); + if (pstr.Length > 0) + { } + + + } + + [Conditional("TEST_ALL"), Conditional("TEST_AWCS")] + public void TestAudioAwcs() + { + + StringBuilder sb = new StringBuilder(); + + Dictionary allids = new Dictionary(); + + foreach (RpfFile file in AllRpfs) + { + foreach (RpfEntry entry in file.AllEntries) + { + //try + //{ + var n = entry.NameLower; + if (n.EndsWith(".awc")) + { + UpdateStatus?.Invoke(string.Format(entry.Path)); + var awcfile = RpfMan.GetFile(entry); + if (awcfile != null) + { } + } + //} + //catch (Exception ex) + //{ + // UpdateStatus?.Invoke("Error! " + ex.ToString()); + //} + } + } + } + + [Conditional("TEST_ALL"), Conditional("TEST_METAS")] + public void TestMetas() + { + //find all RSC meta files and generate the MetaTypes init code + + MetaTypes.Clear(); + foreach (RpfFile file in AllRpfs) + { + foreach (RpfEntry entry in file.AllEntries) + { + //try + { + var n = entry.NameLower; + //if (n.EndsWith(".ymap")) + //{ + // UpdateStatus?.Invoke(string.Format(entry.Path)); + // YmapFile ymapfile = RpfMan.GetFile(entry); + // if ((ymapfile != null) && (ymapfile.Meta != null)) + // { + // MetaTypes.EnsureMetaTypes(ymapfile.Meta); + // } + //} + //else if (n.EndsWith(".ytyp")) + //{ + // UpdateStatus?.Invoke(string.Format(entry.Path)); + // YtypFile ytypfile = RpfMan.GetFile(entry); + // if ((ytypfile != null) && (ytypfile.Meta != null)) + // { + // MetaTypes.EnsureMetaTypes(ytypfile.Meta); + // } + //} + //else if (n.EndsWith(".ymt")) + //{ + // UpdateStatus?.Invoke(string.Format(entry.Path)); + // YmtFile ymtfile = RpfMan.GetFile(entry); + // if ((ymtfile != null) && (ymtfile.Meta != null)) + // { + // MetaTypes.EnsureMetaTypes(ymtfile.Meta); + // } + //} + + + if (n.EndsWith(".ymap") || n.EndsWith(".ytyp") || n.EndsWith(".ymt")) + { + var rfe = entry as RpfResourceFileEntry; + if (rfe == null) continue; + + UpdateStatus?.Invoke(string.Format(entry.Path)); + + var data = rfe.File.ExtractFile(rfe); + using var rd = new ResourceDataReader(rfe, data); + var meta = rd.ReadBlock(); + var xml = MetaXml.GetXml(meta); + var xdoc = new XmlDocument(); + xdoc.LoadXml(xml); + var meta2 = XmlMeta.GetMeta(xdoc); + var xml2 = MetaXml.GetXml(meta2); + + if (xml.Length != xml2.Length) + { } + if ((xml != xml2) && (!n.EndsWith("srl.ymt") && !n.StartsWith("des_"))) + { } + + } + + + } + //catch (Exception ex) + //{ + // UpdateStatus?.Invoke("Error! " + ex.ToString()); + //} + } + } + + string str = MetaTypes.GetTypesInitString(); + + } + + [Conditional("TEST_ALL"), Conditional("TEST_PSOS")] + public void TestPsos() + { + //find all PSO meta files and generate the PsoTypes init code + PsoTypes.Clear(); + + var exceptions = new List(); + var allpsos = new List(); + var diffpsos = new List(); + + foreach (RpfFile file in AllRpfs) + { + foreach (RpfEntry entry in file.AllEntries) + { +#if !DEBUG + 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"))) + continue; //PSO files seem to only have these extensions + + var fentry = entry as RpfFileEntry; + var data = entry.File.ExtractFile(fentry); + if (data != null) + { + using (MemoryStream ms = new MemoryStream(data)) + { + if (PsoFile.IsPSO(ms)) + { + UpdateStatus?.Invoke(string.Format(entry.Path)); + + var pso = new PsoFile(); + pso.Load(ms); + + allpsos.Add(fentry.Path); + + PsoTypes.EnsurePsoTypes(pso); + + var xml = PsoXml.GetXml(pso); + if (!string.IsNullOrEmpty(xml)) + { } + + var xdoc = new XmlDocument(); + xdoc.LoadXml(xml); + var pso2 = XmlPso.GetPso(xdoc); + var pso2b = pso2.Save(); + + var pso3 = new PsoFile(); + pso3.Load(pso2b); + var xml3 = PsoXml.GetXml(pso3); + + if (xml.Length != xml3.Length) + { } + if (xml != xml3) + { + diffpsos.Add(fentry.Path); + } + + + //if (entry.NameLower == "clip_sets.ymt") + //{ } + //if (entry.NameLower == "vfxinteriorinfo.ymt") + //{ } + //if (entry.NameLower == "vfxvehicleinfo.ymt") + //{ } + //if (entry.NameLower == "vfxpedinfo.ymt") + //{ } + //if (entry.NameLower == "vfxregioninfo.ymt") + //{ } + //if (entry.NameLower == "vfxweaponinfo.ymt") + //{ } + //if (entry.NameLower == "physicstasks.ymt") + //{ } + + } + } + } + } +#if !DEBUG + catch (Exception ex) + { + UpdateStatus?.Invoke("Error! " + ex.ToString()); + exceptions.Add(ex); + } +#endif + } + } + + string allpsopaths = string.Join("\r\n", allpsos); + string diffpsopaths = string.Join("\r\n", diffpsos); + + string str = PsoTypes.GetTypesInitString(); + if (!string.IsNullOrEmpty(str)) + { + } + } + + [Conditional("TEST_ALL"), Conditional("TEST_RBFS")] + public void TestRbfs() + { + var exceptions = new List(); + var allrbfs = new List(); + var diffrbfs = new List(); + + foreach (RpfFile file in AllRpfs) + { + 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"))) + continue; //PSO files seem to only have these extensions + + var fentry = entry as RpfFileEntry; + var data = entry.File.ExtractFile(fentry); + if (data != null) + { + using (MemoryStream ms = new MemoryStream(data)) + { + if (RbfFile.IsRBF(ms)) + { + UpdateStatus?.Invoke(string.Format(entry.Path)); + + var rbf = new RbfFile(); + rbf.Load(ms); + + allrbfs.Add(fentry.Path); + + var xml = RbfXml.GetXml(rbf); + if (!string.IsNullOrEmpty(xml)) + { } + + var xdoc = new XmlDocument(); + xdoc.LoadXml(xml); + var rbf2 = XmlRbf.GetRbf(xdoc); + var rbf2b = rbf2.Save(); + + var rbf3 = new RbfFile(); + rbf3.Load(rbf2b); + var xml3 = RbfXml.GetXml(rbf3); + + if (xml.Length != xml3.Length) + { } + if (xml != xml3) + { + diffrbfs.Add(fentry.Path); + } + + if (data.Length != rbf2b.Length) + { + //File.WriteAllBytes("C:\\GitHub\\CodeWalkerResearch\\RBF\\" + fentry.Name + ".dat0", data); + //File.WriteAllBytes("C:\\GitHub\\CodeWalkerResearch\\RBF\\" + fentry.Name + ".dat1", rbf2b); + } + else + { + for (int i = 0; i < data.Length; i++) + { + if (data[i] != rbf2b[i]) + { + diffrbfs.Add(fentry.Path); + break; + } + } + } + + } + } + } + + } + } + + string allrbfpaths = string.Join("\r\n", allrbfs); + string diffrbfpaths = string.Join("\r\n", diffrbfs); + + } + + [Conditional("TEST_ALL"), Conditional("TEST_CUTS")] + public void TestCuts() + { + + var exceptions = new List(); + + foreach (RpfFile file in AllRpfs) + { + foreach (RpfEntry entry in file.AllEntries) + { +#if !DEBUG + try +#endif + { + var rfe = entry as RpfFileEntry; + if (rfe == null) continue; + + if (rfe.NameLower.EndsWith(".cut")) + { + UpdateStatus?.Invoke(string.Format(entry.Path)); + + CutFile cut = new CutFile(rfe); + RpfMan.LoadFile(cut, rfe); + + //PsoTypes.EnsurePsoTypes(cut.Pso); + } + } +#if !DEBUG + catch (Exception ex) + { + UpdateStatus?.Invoke("Error! " + ex.ToString()); + exceptions.Add(ex); + } +#endif + } + } + + string str = PsoTypes.GetTypesInitString(); + if (!string.IsNullOrEmpty(str)) + { + } + } + + [Conditional("TEST_ALL"), Conditional("TEST_YLDS")] + public void TestYlds() + { + + var exceptions = new List(); + + foreach (RpfFile file in AllRpfs) + { + foreach (RpfEntry entry in file.AllEntries) + { +#if !DEBUG + try +#endif + { + var rfe = entry as RpfFileEntry; + if (rfe == null) continue; + + if (rfe.NameLower.EndsWith(".yld")) + { + UpdateStatus?.Invoke(string.Format(entry.Path)); + + YldFile yld = new YldFile(rfe); + RpfMan.LoadFile(yld, rfe); + + } + } +#if !DEBUG + catch (Exception ex) + { + UpdateStatus?.Invoke("Error! " + ex.ToString()); + exceptions.Add(ex); + } +#endif + } + } + + if (exceptions.Count > 0) + { } + } + + [Conditional("TEST_ALL"), Conditional("TEST_YEDS")] + public void TestYeds() + { + + var exceptions = new List(); + + foreach (RpfFile file in AllRpfs) + { + foreach (RpfEntry entry in file.AllEntries) + { +#if !DEBUG + try +#endif + { + var rfe = entry as RpfFileEntry; + if (rfe == null) continue; + + if (rfe.NameLower.EndsWith(".yed")) + { + UpdateStatus?.Invoke(string.Format(entry.Path)); + + YedFile yed = new YedFile(rfe); + RpfMan.LoadFile(yed, rfe); + + var xml = YedXml.GetXml(yed); + var yed2 = XmlYed.GetYed(xml); + var data2 = yed2.Save(); + var yed3 = new YedFile(); + RpfFile.LoadResourceFile(yed3, data2, 25);//full roundtrip + var xml2 = YedXml.GetXml(yed3); + if (xml != xml2) + { } + + } + } +#if !DEBUG + catch (Exception ex) + { + UpdateStatus?.Invoke("Error! " + ex.ToString()); + exceptions.Add(ex); + } +#endif + } + } + + if (exceptions.Count > 0) + { } + } + + [Conditional("TEST_ALL"), Conditional("TEST_YCDS")] + public void TestYcds() + { + bool savetest = false; + var errorfiles = new List(); + var errorentries = new List(); + foreach (RpfFile file in AllRpfs) + { + foreach (RpfEntry entry in file.AllEntries) + { + //try + //{ + if (entry.NameLower.EndsWith(".ycd")) + { + UpdateStatus?.Invoke(string.Format(entry.Path)); + YcdFile ycd1 = RpfMan.GetFile(entry); + if (ycd1 == null) + { + errorentries.Add(entry); + } + else if (ycd1?.LoadException != null) + { + errorfiles.Add(ycd1);//these ones have file corruption issues and won't load as resource... + } + else if (savetest) + { + if (ycd1.ClipDictionary == null) + { continue; } + + //var data1 = ycd1.Save(); + + var xml = YcdXml.GetXml(ycd1); + var ycdX = XmlYcd.GetYcd(xml); + var data = ycdX.Save(); + var ycd2 = new YcdFile(); + RpfFile.LoadResourceFile(ycd2, data, 46);//full roundtrip + + { + if (ycd2 == null) + { continue; } + if (ycd2.ClipDictionary == null) + { continue; } + + var c1 = ycd1.ClipDictionary.Clips?.data_items; + var c2 = ycd2.ClipDictionary.Clips?.data_items; + if ((c1 == null) || (c2 == null)) + { continue; } + if (c1.Length != c2.Length) + { continue; } + + var a1 = ycd1.ClipDictionary.Animations?.Animations?.data_items; + var a2 = ycd2.ClipDictionary.Animations?.Animations?.data_items; + if ((a1 == null) || (a2 == null)) + { continue; } + if (a1.Length != a2.Length) + { continue; } + + var m1 = ycd1.AnimMap; + var m2 = ycd2.AnimMap; + if ((m1 == null) || (m2 == null)) + { continue; } + if (m1.Count != m2.Count) + { continue; } + foreach (var kvp1 in m1) + { + var an1 = kvp1.Value; + var an2 = an1; + if (!m2.TryGetValue(kvp1.Key, out an2)) + { continue; } + + var sa1 = an1?.Animation?.Sequences?.data_items; + var sa2 = an2?.Animation?.Sequences?.data_items; + if ((sa1 == null) || (sa2 == null)) + { continue; } + if (sa1.Length != sa2.Length) + { continue; } + for (int s = 0; s < sa1.Length; s++) + { + var s1 = sa1[s]; + var s2 = sa2[s]; + if ((s1?.Sequences == null) || (s2?.Sequences == null)) + { continue; } + + if (s1.NumFrames != s2.NumFrames) + { } + if (s1.ChunkSize != s2.ChunkSize) + { } + if (s1.FrameOffset != s2.FrameOffset) + { } + if (s1.DataLength != s2.DataLength) + { } + else + { + //for (int b = 0; b < s1.DataLength; b++) + //{ + // var b1 = s1.Data[b]; + // var b2 = s2.Data[b]; + // if (b1 != b2) + // { } + //} + } + + for (int ss = 0; ss < s1.Sequences.Length; ss++) + { + var ss1 = s1.Sequences[ss]; + var ss2 = s2.Sequences[ss]; + if ((ss1?.Channels == null) || (ss2?.Channels == null)) + { continue; } + if (ss1.Channels.Length != ss2.Channels.Length) + { continue; } + + + for (int c = 0; c < ss1.Channels.Length; c++) + { + var sc1 = ss1.Channels[c]; + var sc2 = ss2.Channels[c]; + if ((sc1 == null) || (sc2 == null)) + { continue; } + if (sc1.Type == AnimChannelType.LinearFloat) + { continue; } + if (sc1.Type != sc2.Type) + { continue; } + if (sc1.Index != sc2.Index) + { continue; } + if (sc1.Type == AnimChannelType.StaticQuaternion) + { + var acsq1 = sc1 as AnimChannelStaticQuaternion; + var acsq2 = sc2 as AnimChannelStaticQuaternion; + var vdiff = acsq1.Value - acsq2.Value; + var len = vdiff.Length(); + var v1len = Math.Max(acsq1.Value.Length(), 1); + if (len > 1e-2f * v1len) + { continue; } + } + else if (sc1.Type == AnimChannelType.StaticVector3) + { + var acsv1 = sc1 as AnimChannelStaticVector3; + var acsv2 = sc2 as AnimChannelStaticVector3; + var vdiff = acsv1.Value - acsv2.Value; + var len = vdiff.Length(); + var v1len = Math.Max(acsv1.Value.Length(), 1); + if (len > 1e-2f * v1len) + { continue; } + } + else if (sc1.Type == AnimChannelType.StaticFloat) + { + var acsf1 = sc1 as AnimChannelStaticFloat; + var acsf2 = sc2 as AnimChannelStaticFloat; + var vdiff = Math.Abs(acsf1.Value - acsf2.Value); + var v1len = Math.Max(Math.Abs(acsf1.Value), 1); + if (vdiff > 1e-2f * v1len) + { continue; } + } + else if (sc1.Type == AnimChannelType.RawFloat) + { + var acrf1 = sc1 as AnimChannelRawFloat; + var acrf2 = sc2 as AnimChannelRawFloat; + for (int v = 0; v < acrf1.Values.Length; v++) + { + var v1 = acrf1.Values[v]; + var v2 = acrf2.Values[v]; + var vdiff = Math.Abs(v1 - v2); + var v1len = Math.Max(Math.Abs(v1), 1); + if (vdiff > 1e-2f * v1len) + { break; } + } + } + else if (sc1.Type == AnimChannelType.QuantizeFloat) + { + var acqf1 = sc1 as AnimChannelQuantizeFloat; + var acqf2 = sc2 as AnimChannelQuantizeFloat; + if (acqf1.ValueBits != acqf2.ValueBits) + { continue; } + if (Math.Abs(acqf1.Offset - acqf2.Offset) > (0.001f * Math.Abs(acqf1.Offset))) + { continue; } + if (Math.Abs(acqf1.Quantum - acqf2.Quantum) > 0.00001f) + { continue; } + for (int v = 0; v < acqf1.Values.Length; v++) + { + var v1 = acqf1.Values[v]; + var v2 = acqf2.Values[v]; + var vdiff = Math.Abs(v1 - v2); + var v1len = Math.Max(Math.Abs(v1), 1); + if (vdiff > 1e-2f * v1len) + { break; } + } + } + else if (sc1.Type == AnimChannelType.IndirectQuantizeFloat) + { + var aciqf1 = sc1 as AnimChannelIndirectQuantizeFloat; + var aciqf2 = sc2 as AnimChannelIndirectQuantizeFloat; + if (aciqf1.FrameBits != aciqf2.FrameBits) + { continue; } + if (aciqf1.ValueBits != aciqf2.ValueBits) + { continue; } + if (Math.Abs(aciqf1.Offset - aciqf2.Offset) > (0.001f * Math.Abs(aciqf1.Offset))) + { continue; } + if (Math.Abs(aciqf1.Quantum - aciqf2.Quantum) > 0.00001f) + { continue; } + for (int f = 0; f < aciqf1.Frames.Length; f++) + { + if (aciqf1.Frames[f] != aciqf2.Frames[f]) + { break; } + } + for (int v = 0; v < aciqf1.Values.Length; v++) + { + var v1 = aciqf1.Values[v]; + var v2 = aciqf2.Values[v]; + var vdiff = Math.Abs(v1 - v2); + var v1len = Math.Max(Math.Abs(v1), 1); + if (vdiff > 1e-2f * v1len) + { break; } + } + } + else if ((sc1.Type == AnimChannelType.CachedQuaternion1) || (sc1.Type == AnimChannelType.CachedQuaternion2)) + { + var acrf1 = sc1 as AnimChannelCachedQuaternion; + var acrf2 = sc2 as AnimChannelCachedQuaternion; + if (acrf1.QuatIndex != acrf2.QuatIndex) + { continue; } + } + + + + + } + + + //for (int f = 0; f < s1.NumFrames; f++) + //{ + // var v1 = ss1.EvaluateVector(f); + // var v2 = ss2.EvaluateVector(f); + // var vdiff = v1 - v2; + // var len = vdiff.Length(); + // var v1len = Math.Max(v1.Length(), 1); + // if (len > 1e-2f*v1len) + // { } + //} + } + + + } + + + } + + + } + + } + } + //if (entry.NameLower.EndsWith(".awc")) //awcs can also contain clip dicts.. + //{ + // UpdateStatus?.Invoke(string.Format(entry.Path)); + // AwcFile awcfile = RpfMan.GetFile(entry); + // if ((awcfile != null)) + // { } + //} + //} + //catch (Exception ex) + //{ + // UpdateStatus?.Invoke("Error! " + ex.ToString()); + //} + } + } + + if (errorfiles.Count > 0) + { } + + } + + [Conditional("TEST_ALL"), Conditional("TEST_YTDS")] + public void TestYtds() + { + bool ddstest = false; + bool savetest = false; + var errorfiles = new List(); + foreach (RpfFile file in AllRpfs) + { + foreach (RpfEntry entry in file.AllEntries) + { + //try + { + if (entry.NameLower.EndsWith(".ytd")) + { + UpdateStatus?.Invoke(string.Format(entry.Path)); + YtdFile ytdfile = null; + try + { + ytdfile = RpfMan.GetFile(entry); + } + catch (Exception ex) + { + UpdateStatus?.Invoke("Error! " + ex.ToString()); + errorfiles.Add(entry); + } + if (ddstest && (ytdfile != null) && (ytdfile.TextureDict != null)) + { + foreach (var tex in ytdfile.TextureDict.Textures.data_items) + { + var dds = Utils.DDSIO.GetDDSFile(tex); + var tex2 = Utils.DDSIO.GetTexture(dds); + if (!tex.Name.StartsWith("script_rt")) + { + if (tex.Data?.FullData?.Length != tex2.Data?.FullData?.Length) + { } + if (tex.Stride != tex2.Stride) + { } + } + if ((tex.Format != tex2.Format) || (tex.Width != tex2.Width) || (tex.Height != tex2.Height) || (tex.Depth != tex2.Depth) || (tex.Levels != tex2.Levels)) + { } + } + } + if (savetest && (ytdfile != null) && (ytdfile.TextureDict != null)) + { + var fentry = entry as RpfFileEntry; + if (fentry == null) + { continue; } //shouldn't happen + + var bytes = ytdfile.Save(); + + string origlen = TextUtil.GetBytesReadable(fentry.FileSize); + string bytelen = TextUtil.GetBytesReadable(bytes.Length); + + if (ytdfile.TextureDict.Textures?.Count == 0) + { } + + + var ytd2 = new YtdFile(); + //ytd2.Load(bytes, fentry); + RpfFile.LoadResourceFile(ytd2, bytes, 13); + + if (ytd2.TextureDict == null) + { continue; } + if (ytd2.TextureDict.Textures?.Count != ytdfile.TextureDict.Textures?.Count) + { continue; } + + for (int i = 0; i < ytdfile.TextureDict.Textures.Count; i++) + { + var tx1 = ytdfile.TextureDict.Textures[i]; + var tx2 = ytd2.TextureDict.Textures[i]; + var td1 = tx1.Data; + var td2 = tx2.Data; + if (td1.FullData.Length != td2.FullData.Length) + { continue; } + + for (int j = 0; j < td1.FullData.Length; j++) + { + if (td1.FullData[j] != td2.FullData[j]) + { break; } + } + + } + + } + } + } + //catch (Exception ex) + //{ + // UpdateStatus?.Invoke("Error! " + ex.ToString()); + //} + } + } + if (errorfiles.Count > 0) + { } + } + + [Conditional("TEST_ALL"), Conditional("TEST_YBNS")] + public void TestYbns() + { + bool xmltest = false; + bool savetest = false; + bool reloadtest = false; + var errorfiles = new List(); + foreach (RpfFile file in AllRpfs) + { + foreach (RpfEntry entry in file.AllEntries) + { + //try + { + if (entry.NameLower.EndsWith(".ybn")) + { + UpdateStatus?.Invoke(string.Format(entry.Path)); + YbnFile ybn = null; + try + { + ybn = RpfMan.GetFile(entry); + } + catch (Exception ex) + { + UpdateStatus?.Invoke("Error! " + ex.ToString()); + errorfiles.Add(entry); + } + if (xmltest && (ybn != null) && (ybn.Bounds != null)) + { + var xml = YbnXml.GetXml(ybn); + var ybn2 = XmlYbn.GetYbn(xml); + var xml2 = YbnXml.GetXml(ybn2); + if (xml.Length != xml2.Length) + { } + } + if (savetest && (ybn != null) && (ybn.Bounds != null)) + { + var fentry = entry as RpfFileEntry; + if (fentry == null) + { continue; } //shouldn't happen + + var bytes = ybn.Save(); + + if (!reloadtest) + { continue; } + + string origlen = TextUtil.GetBytesReadable(fentry.FileSize); + string bytelen = TextUtil.GetBytesReadable(bytes.Length); + + + var ybn2 = new YbnFile(); + RpfFile.LoadResourceFile(ybn2, bytes, 43); + + if (ybn2.Bounds == null) + { continue; } + if (ybn2.Bounds.Type != ybn.Bounds.Type) + { continue; } + + //quick check of roundtrip + switch (ybn2.Bounds.Type) + { + case BoundsType.Sphere: + { + var a = ybn.Bounds as BoundSphere; + var b = ybn2.Bounds as BoundSphere; + if (b == null) + { continue; } + break; + } + case BoundsType.Capsule: + { + var a = ybn.Bounds as BoundCapsule; + var b = ybn2.Bounds as BoundCapsule; + if (b == null) + { continue; } + break; + } + case BoundsType.Box: + { + var a = ybn.Bounds as BoundBox; + var b = ybn2.Bounds as BoundBox; + if (b == null) + { continue; } + break; + } + case BoundsType.Geometry: + { + var a = ybn.Bounds as BoundGeometry; + var b = ybn2.Bounds as BoundGeometry; + if (b == null) + { continue; } + if (a.Polygons?.Length != b.Polygons?.Length) + { continue; } + for (int i = 0; i < a.Polygons.Length; i++) + { + var pa = a.Polygons[i]; + var pb = b.Polygons[i]; + if (pa.Type != pb.Type) + { } + } + break; + } + case BoundsType.GeometryBVH: + { + var a = ybn.Bounds as BoundBVH; + var b = ybn2.Bounds as BoundBVH; + if (b == null) + { continue; } + if (a.BVH?.Nodes?.data_items?.Length != b.BVH?.Nodes?.data_items?.Length) + { } + if (a.Polygons?.Length != b.Polygons?.Length) + { continue; } + for (int i = 0; i < a.Polygons.Length; i++) + { + var pa = a.Polygons[i]; + var pb = b.Polygons[i]; + if (pa.Type != pb.Type) + { } + } + break; + } + case BoundsType.Composite: + { + var a = ybn.Bounds as BoundComposite; + var b = ybn2.Bounds as BoundComposite; + if (b == null) + { continue; } + if (a.Children?.data_items?.Length != b.Children?.data_items?.Length) + { } + break; + } + case BoundsType.Disc: + { + var a = ybn.Bounds as BoundDisc; + var b = ybn2.Bounds as BoundDisc; + if (b == null) + { continue; } + break; + } + case BoundsType.Cylinder: + { + var a = ybn.Bounds as BoundCylinder; + var b = ybn2.Bounds as BoundCylinder; + if (b == null) + { continue; } + break; + } + case BoundsType.Cloth: + { + var a = ybn.Bounds as BoundCloth; + var b = ybn2.Bounds as BoundCloth; + if (b == null) + { continue; } + break; + } + default: //return null; // throw new Exception("Unknown bound type"); + break; + } + + + + } + } + } + //catch (Exception ex) + //{ + // UpdateStatus?.Invoke("Error! " + ex.ToString()); + //} + } + } + if (errorfiles.Count > 0) + { } + } + + [Conditional("TEST_ALL"), Conditional("TEST_YDRS")] + public void TestYdrs() + { + bool savetest = false; + bool boundsonly = true; + var errorfiles = new List(); + foreach (RpfFile file in AllRpfs) + { + foreach (RpfEntry entry in file.AllEntries) + { + //try + { + if (entry.NameLower.EndsWith(".ydr")) + { + UpdateStatus?.Invoke(string.Format(entry.Path)); + YdrFile ydr = null; + try + { + ydr = RpfMan.GetFile(entry); + } + catch (Exception ex) + { + UpdateStatus?.Invoke("Error! " + ex.ToString()); + errorfiles.Add(entry); + } + if (savetest && (ydr != null) && (ydr.Drawable != null)) + { + var fentry = entry as RpfFileEntry; + if (fentry == null) + { continue; } //shouldn't happen + + if (boundsonly && (ydr.Drawable.Bound == null)) + { continue; } + + var bytes = ydr.Save(); + + string origlen = TextUtil.GetBytesReadable(fentry.FileSize); + string bytelen = TextUtil.GetBytesReadable(bytes.Length); + + var ydr2 = new YdrFile(); + RpfFile.LoadResourceFile(ydr2, bytes, 165); + + if (ydr2.Drawable == null) + { continue; } + if (ydr2.Drawable.AllModels?.Length != ydr.Drawable.AllModels?.Length) + { continue; } + + } + } + } + //catch (Exception ex) + //{ + // UpdateStatus?.Invoke("Error! " + ex.ToString()); + //} + } + } + if (errorfiles.Count != 13) + { } + } + + [Conditional("TEST_ALL"), Conditional("TEST_YDDS")] + public void TestYdds() + { + bool savetest = false; + var errorfiles = new List(); + foreach (RpfFile file in AllRpfs) + { + foreach (RpfEntry entry in file.AllEntries) + { + //try + { + if (entry.NameLower.EndsWith(".ydd")) + { + UpdateStatus?.Invoke(string.Format(entry.Path)); + YddFile ydd = null; + try + { + ydd = RpfMan.GetFile(entry); + } + catch (Exception ex) + { + UpdateStatus?.Invoke("Error! " + ex.ToString()); + errorfiles.Add(entry); + } + if (savetest && (ydd != null) && (ydd.DrawableDict != null)) + { + var fentry = entry as RpfFileEntry; + if (fentry == null) + { continue; } //shouldn't happen + + var bytes = ydd.Save(); + + string origlen = TextUtil.GetBytesReadable(fentry.FileSize); + string bytelen = TextUtil.GetBytesReadable(bytes.Length); + + + var ydd2 = new YddFile(); + RpfFile.LoadResourceFile(ydd2, bytes, 165); + + if (ydd2.DrawableDict == null) + { continue; } + if (ydd2.DrawableDict.Drawables?.Count != ydd.DrawableDict.Drawables?.Count) + { continue; } + + } + if (ydd?.DrawableDict?.Hashes != null) + { + uint h = 0; + foreach (uint th in ydd.DrawableDict.Hashes) + { + if (th <= h) + { } //should never happen + h = th; + } + } + } + } + //catch (Exception ex) + //{ + // UpdateStatus?.Invoke("Error! " + ex.ToString()); + //} + } + } + if (errorfiles.Count > 0) + { } + } + + [Conditional("TEST_ALL"), Conditional("TEST_YFTS")] + public void TestYfts() + { + bool savetest = false; + var errorfiles = new List(); + var sb = new StringBuilder(); + var flagdict = new Dictionary(); + foreach (RpfFile file in AllRpfs) + { + foreach (RpfEntry entry in file.AllEntries) + { + //try + { + if (entry.NameLower.EndsWith(".yft")) + { + UpdateStatus?.Invoke(string.Format(entry.Path)); + YftFile yft = null; + try + { + yft = RpfMan.GetFile(entry); + } + catch (Exception ex) + { + UpdateStatus?.Invoke("Error! " + ex.ToString()); + errorfiles.Add(entry); + } + if (savetest && (yft != null) && (yft.Fragment != null)) + { + var fentry = entry as RpfFileEntry; + if (fentry == null) + { continue; } //shouldn't happen + + var bytes = yft.Save(); + + + string origlen = TextUtil.GetBytesReadable(fentry.FileSize); + string bytelen = TextUtil.GetBytesReadable(bytes.Length); + + var yft2 = new YftFile(); + RpfFile.LoadResourceFile(yft2, bytes, 162); + + if (yft2.Fragment == null) + { continue; } + if (yft2.Fragment.Drawable?.AllModels?.Length != yft.Fragment.Drawable?.AllModels?.Length) + { continue; } + + } + + if (yft?.Fragment?.GlassWindows?.data_items != null) + { + var lastf = -1; + for (int i = 0; i < yft.Fragment.GlassWindows.data_items.Length; i++) + { + var w = yft.Fragment.GlassWindows.data_items[i]; + if (w.Flags == lastf) continue; + lastf = w.Flags; + flagdict.TryGetValue(w.Flags, out int n); + if (n < 10) + { + flagdict[w.Flags] = n + 1; + sb.AppendLine(entry.Path + " Window " + i.ToString() + ": Flags " + w.Flags.ToString() + ", Low:" + w.FlagsLo.ToString() + ", High:" + w.FlagsHi.ToString()); + } + } + } + + } + } + //catch (Exception ex) + //{ + // UpdateStatus?.Invoke("Error! " + ex.ToString()); + //} + } + } + var teststr = sb.ToString(); + + if (errorfiles.Count > 0) + { } + } + + [Conditional("TEST_ALL"), Conditional("TEST_YPTS")] + public void TestYpts() + { + var savetest = false; + var errorfiles = new List(); + foreach (RpfFile file in AllRpfs) + { + foreach (RpfEntry entry in file.AllEntries) + { + //try + { + if (entry.NameLower.EndsWith(".ypt")) + { + UpdateStatus?.Invoke(string.Format(entry.Path)); + YptFile ypt = null; + try + { + ypt = RpfMan.GetFile(entry); + } + catch (Exception ex) + { + UpdateStatus?.Invoke("Error! " + ex.ToString()); + errorfiles.Add(entry); + } + if (savetest && (ypt != null) && (ypt.PtfxList != null)) + { + var fentry = entry as RpfFileEntry; + if (fentry == null) + { continue; } //shouldn't happen + + var bytes = ypt.Save(); + + + string origlen = TextUtil.GetBytesReadable(fentry.FileSize); + string bytelen = TextUtil.GetBytesReadable(bytes.Length); + + var ypt2 = new YptFile(); + RpfFile.LoadResourceFile(ypt2, bytes, 68); + + if (ypt2.PtfxList == null) + { continue; } + if (ypt2.PtfxList.Name?.Value != ypt.PtfxList.Name?.Value) + { continue; } + + } + } + } + //catch (Exception ex) + //{ + // UpdateStatus?.Invoke("Error! " + ex.ToString()); + //} + } + } + if (errorfiles.Count > 0) + { } + } + + [Conditional("TEST_ALL"), Conditional("TEST_YNVS")] + public void TestYnvs() + { + bool xmltest = true; + var savetest = false; + var errorfiles = new List(); + foreach (RpfFile file in AllRpfs) + { + foreach (RpfEntry entry in file.AllEntries) + { + //try + { + if (entry.NameLower.EndsWith(".ynv")) + { + UpdateStatus?.Invoke(string.Format(entry.Path)); + YnvFile ynv = null; + try + { + ynv = RpfMan.GetFile(entry); + } + catch (Exception ex) + { + UpdateStatus?.Invoke("Error! " + ex.ToString()); + errorfiles.Add(entry); + } + if (xmltest && (ynv != null) && (ynv.Nav != null)) + { + var xml = YnvXml.GetXml(ynv); + if (xml != null) + { } + var ynv2 = XmlYnv.GetYnv(xml); + if (ynv2 != null) + { } + var ynv2b = ynv2.Save(); + if (ynv2b != null) + { } + var ynv3 = new YnvFile(); + RpfFile.LoadResourceFile(ynv3, ynv2b, 2); + var xml3 = YnvXml.GetXml(ynv3); + if (xml.Length != xml3.Length) + { } + var xmllines = xml.Split('\n'); + var xml3lines = xml3.Split('\n'); + if (xmllines.Length != xml3lines.Length) + { } + } + if (savetest && (ynv != null) && (ynv.Nav != null)) + { + var fentry = entry as RpfFileEntry; + if (fentry == null) + { continue; } //shouldn't happen + + var bytes = ynv.Save(); + + string origlen = TextUtil.GetBytesReadable(fentry.FileSize); + string bytelen = TextUtil.GetBytesReadable(bytes.Length); + + var ynv2 = new YnvFile(); + RpfFile.LoadResourceFile(ynv2, bytes, 2); + + if (ynv2.Nav == null) + { continue; } + + } + } + } + //catch (Exception ex) + //{ + // UpdateStatus?.Invoke("Error! " + ex.ToString()); + //} + } + } + if (errorfiles.Count > 0) + { } + } + + [Conditional("TEST_ALL"), Conditional("TEST_YVRS")] + public void TestYvrs() + { + + var exceptions = new List(); + + foreach (RpfFile file in AllRpfs) + { + foreach (RpfEntry entry in file.AllEntries) + { +#if !DEBUG + try +#endif + { + var rfe = entry as RpfFileEntry; + if (rfe == null) continue; + + if (rfe.NameLower.EndsWith(".yvr")) + { + if (rfe.NameLower == "agencyprep001.yvr") continue; //this file seems corrupted + + UpdateStatus?.Invoke(string.Format(entry.Path)); + + YvrFile yvr = new YvrFile(rfe); + RpfMan.LoadFile(yvr, rfe); + + var xml = YvrXml.GetXml(yvr); + var yvr2 = XmlYvr.GetYvr(xml); + var data2 = yvr2.Save(); + var yvr3 = new YvrFile(); + RpfFile.LoadResourceFile(yvr3, data2, 1);//full roundtrip + var xml2 = YvrXml.GetXml(yvr3); + if (xml != xml2) + { } + + } + } +#if !DEBUG + catch (Exception ex) + { + UpdateStatus?.Invoke("Error! " + ex.ToString()); + exceptions.Add(ex); + } +#endif + } + } + + if (exceptions.Count > 0) + { } + } + + [Conditional("TEST_ALL"), Conditional("TEST_YWRS")] + public void TestYwrs() + { + + var exceptions = new List(); + + foreach (RpfFile file in AllRpfs) + { + foreach (RpfEntry entry in file.AllEntries) + { +#if !DEBUG + try +#endif + { + var rfe = entry as RpfFileEntry; + if (rfe == null) continue; + + if (rfe.NameLower.EndsWith(".ywr")) + { + UpdateStatus?.Invoke(string.Format(entry.Path)); + + YwrFile ywr = new YwrFile(rfe); + RpfMan.LoadFile(ywr, rfe); + + var xml = YwrXml.GetXml(ywr); + var ywr2 = XmlYwr.GetYwr(xml); + var data2 = ywr2.Save(); + var ywr3 = new YwrFile(); + RpfFile.LoadResourceFile(ywr3, data2, 1);//full roundtrip + var xml2 = YwrXml.GetXml(ywr3); + if (xml != xml2) + { } + + } + } +#if !DEBUG + catch (Exception ex) + { + UpdateStatus?.Invoke("Error! " + ex.ToString()); + exceptions.Add(ex); + } +#endif + } + } + + if (exceptions.Count > 0) + { } + } + + [Conditional("TEST_ALL"), Conditional("TEST_YMAPS")] + public void TestYmaps() + { + foreach (RpfFile file in AllRpfs) + { + foreach (RpfEntry entry in file.AllEntries) + { + try + { + if (entry.NameLower.EndsWith(".ymap")) + { + UpdateStatus?.Invoke(string.Format(entry.Path)); + YmapFile ymapfile = RpfMan.GetFile(entry); + if ((ymapfile != null))// && (ymapfile.Meta != null)) + { } + } + } + catch (Exception ex) + { + UpdateStatus?.Invoke("Error! " + ex.ToString()); + } + } + } + } + + [Conditional("TEST_ALL"), Conditional("TEST_YPDBS")] + public void TestYpdbs() + { + foreach (RpfFile file in AllRpfs) + { + foreach (RpfEntry entry in file.AllEntries) + { + var rfe = entry as RpfFileEntry; + if (rfe == null) continue; + + try + { + if (rfe.NameLower.EndsWith(".ypdb")) + { + UpdateStatus?.Invoke(string.Format(entry.Path)); + YpdbFile ypdb = RpfMan.GetFile(entry); + if (ypdb != null) + { + var odata = entry.File.ExtractFile(entry as RpfFileEntry); + //var ndata = ypdb.Save(); + + var xml = YpdbXml.GetXml(ypdb); + var ypdb2 = XmlYpdb.GetYpdb(xml); + var ndata = ypdb2.Save(); + + if (ndata.Length == odata.Length) + { + for (int i = 0; i < ndata.Length; i++) + { + if (ndata[i] != odata[i]) + { break; } + } + } + else + { } + } + else + { } + } + } + catch (Exception ex) + { + UpdateStatus?.Invoke("Error! " + ex.ToString()); + } + + } + } + } + + [Conditional("TEST_ALL"), Conditional("TEST_YFDS")] + public void TestYfds() + { + foreach (RpfFile file in AllRpfs) + { + foreach (RpfEntry entry in file.AllEntries) + { + var rfe = entry as RpfFileEntry; + if (rfe == null) continue; + + try + { + if (rfe.NameLower.EndsWith(".yfd")) + { + UpdateStatus?.Invoke(string.Format(entry.Path)); + YfdFile yfd = RpfMan.GetFile(entry); + if (yfd != null) + { + if (yfd.FrameFilterDictionary != null) + { + // check that all signatures can be re-calculated + foreach (var f in yfd.FrameFilterDictionary.Filters.data_items) + { + if (f.Signature != f.CalculateSignature()) + { } + } + } + + var xml = YfdXml.GetXml(yfd); + var yfd2 = XmlYfd.GetYfd(xml); + var data2 = yfd2.Save(); + var yfd3 = new YfdFile(); + RpfFile.LoadResourceFile(yfd3, data2, 4);//full roundtrip + var xml2 = YfdXml.GetXml(yfd3); + if (xml != xml2) + { } + } + else + { } + } + } + catch (Exception ex) + { + UpdateStatus?.Invoke("Error! " + ex.ToString()); + } + + } + } + } + + [Conditional("TEST_ALL"), Conditional("TEST_MRFS")] + public void TestMrfs() + { + foreach (RpfFile file in AllRpfs) + { + foreach (RpfEntry entry in file.AllEntries) + { + try + { + if (entry.NameLower.EndsWith(".mrf")) + { + UpdateStatus?.Invoke(string.Format(entry.Path)); + MrfFile mrffile = RpfMan.GetFile(entry); + if (mrffile != null) + { + var odata = entry.File.ExtractFile(entry as RpfFileEntry); + var ndata = mrffile.Save(); + if (ndata.Length == odata.Length) + { + for (int i = 0; i < ndata.Length; i++) + { + if (ndata[i] != odata[i]) + { break; } + } + } + else + { } + + var xml = MrfXml.GetXml(mrffile); + var mrf2 = XmlMrf.GetMrf(xml); + var ndata2 = mrf2.Save(); + if (ndata2.Length == odata.Length) + { + for (int i = 0; i < ndata2.Length; i++) + { + if (ndata2[i] != odata[i] && !mrfDiffCanBeIgnored(i, mrffile)) + { break; } + } + } + else + { } + + bool mrfDiffCanBeIgnored(int fileOffset, MrfFile originalMrf) + { + foreach (var n in originalMrf.AllNodes) + { + if (n is MrfNodeStateBase state) + { + // If TransitionCount is 0, the TransitionsOffset value can be ignored. + // TransitionsOffset in original MRFs isn't always set to 0 in this case, + // XML-imported MRFs always set it to 0 + if (state.TransitionCount == 0 && fileOffset == (state.FileOffset + 0x1C)) + { + return true; + } + } + } + + return false; + } + } + else + { } + } + } + catch (Exception ex) + { + UpdateStatus?.Invoke("Error! " + ex.ToString()); + } + } + } + + // create and save a custom MRF + { + // Usage example: + // RequestAnimDict("move_m@alien") + // TaskMoveNetworkByName(PlayerPedId(), "mymrf", 0.0, true, 0, 0) + // SetTaskMoveNetworkSignalFloat(PlayerPedId(), "sprintrate", 2.0) + var mymrf = new MrfFile(); + var clip1 = new MrfNodeClip + { + NodeIndex = 0, + Name = JenkHash.GenHash("clip1"), + ClipType = MrfValueType.Literal, + ClipContainerType = MrfClipContainerType.ClipDictionary, + ClipContainerName = JenkHash.GenHash("move_m@alien"), + ClipName = JenkHash.GenHash("alien_run"), + LoopedType = MrfValueType.Literal, + Looped = true, + }; + var clip2 = new MrfNodeClip + { + NodeIndex = 0, + Name = JenkHash.GenHash("clip2"), + ClipType = MrfValueType.Literal, + ClipContainerType = MrfClipContainerType.ClipDictionary, + ClipContainerName = JenkHash.GenHash("move_m@alien"), + ClipName = JenkHash.GenHash("alien_sprint"), + LoopedType = MrfValueType.Literal, + Looped = true, + RateType = MrfValueType.Parameter, + RateParameterName = JenkHash.GenHash("sprintrate"), + }; + var clipstate1 = new MrfNodeState + { + NodeIndex = 0, + Name = JenkHash.GenHash("clipstate1"), + InitialNode = clip1, + Transitions = new[] + { + new MrfStateTransition + { + Duration = 2.5f, + HasDurationParameter = false, + //TargetState = clipstate2, + Conditions = new[] + { + new MrfConditionTimeGreaterThan { Value = 4.0f }, + }, + } + }, + }; + var clipstate2 = new MrfNodeState + { + NodeIndex = 1, + Name = JenkHash.GenHash("clipstate2"), + InitialNode = clip2, + Transitions = new[] + { + new MrfStateTransition + { + Duration = 2.5f, + HasDurationParameter = false, + //TargetState = clipstate1, + Conditions = new[] + { + new MrfConditionTimeGreaterThan { Value = 4.0f }, + }, + } + }, + }; + clipstate1.Transitions[0].TargetState = clipstate2; + clipstate2.Transitions[0].TargetState = clipstate1; + var rootsm = new MrfNodeStateMachine + { + NodeIndex = 0, + Name = JenkHash.GenHash("statemachine"), + States = new[] + { + new MrfStateRef { StateName = clipstate1.Name, State = clipstate1 }, + new MrfStateRef { StateName = clipstate2.Name, State = clipstate2 }, + }, + InitialNode = clipstate1, + }; + mymrf.AllNodes = new MrfNode[] + { + rootsm, + clipstate1, + clip1, + clipstate2, + clip2, + }; + mymrf.RootState = rootsm; + + var mymrfData = mymrf.Save(); + //File.WriteAllBytes("mymrf.mrf", mymrfData); + //File.WriteAllText("mymrf.dot", mymrf.DumpStateGraph()); + } + } + + [Conditional("TEST_ALL"), Conditional("TEST_FXCS")] + public void TestFxcs() + { + foreach (RpfFile file in AllRpfs) + { + foreach (RpfEntry entry in file.AllEntries) + { + try + { + if (entry.NameLower.EndsWith(".fxc")) + { + UpdateStatus?.Invoke(string.Format(entry.Path)); + var fxcfile = RpfMan.GetFile(entry); + if (fxcfile != null) + { + var odata = entry.File.ExtractFile(entry as RpfFileEntry); + var ndata = fxcfile.Save(); + if (ndata.Length == odata.Length) + { + for (int i = 0; i < ndata.Length; i++) + { + if (ndata[i] != odata[i]) + { break; } + } + } + else + { } + + var xml1 = FxcXml.GetXml(fxcfile);//won't output bytecodes with no output folder + var fxc1 = XmlFxc.GetFxc(xml1); + var xml2 = FxcXml.GetXml(fxc1); + if (xml1 != xml2) + { } + + + for (int i = 0; i < fxcfile.Shaders.Length; i++) + { + if (fxc1.Shaders[i].Name != fxcfile.Shaders[i].Name) + { } + fxc1.Shaders[i].ByteCode = fxcfile.Shaders[i].ByteCode; + } + + var xdata = fxc1.Save(); + if (xdata.Length == odata.Length) + { + for (int i = 0; i < xdata.Length; i++) + { + if (xdata[i] != odata[i]) + { break; } + } + } + else + { } + + + } + else + { } + } + } + catch (Exception ex) + { + UpdateStatus?.Invoke("Error! " + ex.ToString()); + } + } + } + } + + [Conditional("TEST_ALL"), Conditional("TEST_PLACEMENTS")] + public void TestPlacements() + { + //int totplacements = 0; + //int tottimedplacements = 0; + //int totaudioplacements = 0; + //StringBuilder sbtest = new StringBuilder(); + //StringBuilder sbterr = new StringBuilder(); + //sbtest.AppendLine("X, Y, Z, name, assetName, drawableDictionary, textureDictionary, ymap"); + //foreach (RpfFile file in RpfMan.AllRpfs) + //{ + // foreach (RpfEntry entry in file.AllEntries) + // { + // try + // { + // if (entry.NameLower.EndsWith(".ymap")) + // { + // UpdateStatus?.Invoke(string.Format(entry.Path)); + // YmapFile ymapfile = RpfMan.GetFile(entry); + // if ((ymapfile != null))// && (ymapfile.Meta != null)) + // { + // //if (ymapfile.CMapData.parent == 0) //root ymap output + // //{ + // // sbtest.AppendLine(JenkIndex.GetString(ymapfile.CMapData.name) + ": " + entry.Path); + // //} + // if (ymapfile.CEntityDefs != null) + // { + // for (int n = 0; n < ymapfile.CEntityDefs.Length; n++) + // { + // //find ytyp... + // var entdef = ymapfile.CEntityDefs[n]; + // var pos = entdef.position; + // bool istimed = false; + // Tuple archetyp; + // if (!BaseArchetypes.TryGetValue(entdef.archetypeName, out archetyp)) + // { + // sbterr.AppendLine("Couldn't find ytyp for " + entdef.ToString()); + // } + // else + // { + // int ymapbasecount = (archetyp.Item1.CBaseArchetypeDefs != null) ? archetyp.Item1.CBaseArchetypeDefs.Length : 0; + // int baseoffset = archetyp.Item2 - ymapbasecount; + // if (baseoffset >= 0) + // { + // if ((archetyp.Item1.CTimeArchetypeDefs == null) || (baseoffset > archetyp.Item1.CTimeArchetypeDefs.Length)) + // { + // sbterr.AppendLine("Couldn't lookup CTimeArchetypeDef... " + archetyp.ToString()); + // continue; + // } + + // istimed = true; + + // //it's a CTimeArchetypeDef... + // CTimeArchetypeDef ctad = archetyp.Item1.CTimeArchetypeDefs[baseoffset]; + + // //if (ctad.ToString().Contains("spider")) + // //{ + // //} + // //sbtest.AppendFormat("{0}, {1}, {2}, {3}, {4}", pos.X, pos.Y, pos.Z, ctad.ToString(), entry.Name); + // //sbtest.AppendLine(); + + // tottimedplacements++; + // } + // totplacements++; + // } + + // Tuple audiotyp; + // if (AudioArchetypes.TryGetValue(entdef.archetypeName, out audiotyp)) + // { + // if (istimed) + // { + // } + // if (!BaseArchetypes.TryGetValue(entdef.archetypeName, out archetyp)) + // { + // sbterr.AppendLine("Couldn't find ytyp for " + entdef.ToString()); + // } + // if (audiotyp.Item1 != archetyp.Item1) + // { + // } + + // CBaseArchetypeDef cbad = archetyp.Item1.CBaseArchetypeDefs[archetyp.Item2]; + // CExtensionDefAudioEmitter emitr = audiotyp.Item1.AudioEmitters[audiotyp.Item2]; + + // if (emitr.name != cbad.name) + // { + // } + + // string hashtest = JenkIndex.GetString(emitr.effectHash); + + // sbtest.AppendFormat("{0}, {1}, {2}, {3}, {4}, {5}", pos.X, pos.Y, pos.Z, cbad.ToString(), entry.Name, hashtest); + // sbtest.AppendLine(); + + // totaudioplacements++; + // } + + // } + // } + + // //if (ymapfile.TimeCycleModifiers != null) + // //{ + // // for (int n = 0; n < ymapfile.TimeCycleModifiers.Length; n++) + // // { + // // var tcmod = ymapfile.TimeCycleModifiers[n]; + // // Tuple archetyp; + // // if (BaseArchetypes.TryGetValue(tcmod.name, out archetyp)) + // // { + // // } + // // else + // // { + // // } + // // } + // //} + // } + // } + // } + // catch (Exception ex) + // { + // sbterr.AppendLine(entry.Path + ": " + ex.ToString()); + // } + // } + //} + + //UpdateStatus?.Invoke("Ymap scan finished."); + + //sbtest.AppendLine(); + //sbtest.AppendLine(totplacements.ToString() + " total CEntityDef placements parsed"); + //sbtest.AppendLine(tottimedplacements.ToString() + " total CTimeArchetypeDef placements"); + //sbtest.AppendLine(totaudioplacements.ToString() + " total CExtensionDefAudioEmitter placements"); + + //string teststr = sbtest.ToString(); + //string testerr = sbterr.ToString(); + + //return; + } + + [Conditional("RUN_TESTS"), Conditional("TEST_DRAWABLES")] + public void TestDrawables() + { + + + DateTime starttime = DateTime.Now; + + bool doydr = false; + bool doydd = false; + bool doyft = true; + + List errs = new List(); + Dictionary vdecls = new Dictionary(); + Dictionary vdecluse = new Dictionary(); + int drawablecount = 0; + foreach (RpfFile file in AllRpfs) + { + foreach (RpfEntry entry in file.AllEntries) + { + try + { + if (doydr && entry.NameLower.EndsWith(".ydr")) + { + UpdateStatus?.Invoke(entry.Path); + YdrFile ydr = RpfMan.GetFile(entry); + + if (ydr == null) + { + errs.Add(entry.Path + ": Couldn't read file"); + continue; + } + if (ydr.Drawable == null) + { + errs.Add(entry.Path + ": Couldn't read drawable data"); + continue; + } + drawablecount++; + foreach (var kvp in ydr.Drawable.VertexDecls) + { + if (!vdecls.ContainsKey(kvp.Key)) + { + vdecls.Add(kvp.Key, kvp.Value); + vdecluse.Add(kvp.Key, 1); + } + else + { + vdecluse[kvp.Key]++; + } + } + } + else if (doydd & entry.NameLower.EndsWith(".ydd")) + { + UpdateStatus?.Invoke(entry.Path); + YddFile ydd = RpfMan.GetFile(entry); + + if (ydd == null) + { + errs.Add(entry.Path + ": Couldn't read file"); + continue; + } + if (ydd.Dict == null) + { + errs.Add(entry.Path + ": Couldn't read drawable dictionary data"); + continue; + } + foreach (var drawable in ydd.Dict.Values) + { + drawablecount++; + foreach (var kvp in drawable.VertexDecls) + { + if (!vdecls.ContainsKey(kvp.Key)) + { + vdecls.Add(kvp.Key, kvp.Value); + vdecluse.Add(kvp.Key, 1); + } + else + { + vdecluse[kvp.Key]++; + } + } + } + } + else if (doyft && entry.NameLower.EndsWith(".yft")) + { + UpdateStatus?.Invoke(entry.Path); + YftFile yft = RpfMan.GetFile(entry); + + if (yft == null) + { + errs.Add(entry.Path + ": Couldn't read file"); + continue; + } + if (yft.Fragment == null) + { + errs.Add(entry.Path + ": Couldn't read fragment data"); + continue; + } + if (yft.Fragment.Drawable != null) + { + drawablecount++; + foreach (var kvp in yft.Fragment.Drawable.VertexDecls) + { + if (!vdecls.ContainsKey(kvp.Key)) + { + vdecls.Add(kvp.Key, kvp.Value); + vdecluse.Add(kvp.Key, 1); + } + else + { + vdecluse[kvp.Key]++; + } + } + } + if ((yft.Fragment.Cloths != null) && (yft.Fragment.Cloths.data_items != null)) + { + foreach (var cloth in yft.Fragment.Cloths.data_items) + { + drawablecount++; + foreach (var kvp in cloth.Drawable.VertexDecls) + { + if (!vdecls.ContainsKey(kvp.Key)) + { + vdecls.Add(kvp.Key, kvp.Value); + vdecluse.Add(kvp.Key, 1); + } + else + { + vdecluse[kvp.Key]++; + } + } + } + } + if ((yft.Fragment.DrawableArray != null) && (yft.Fragment.DrawableArray.data_items != null)) + { + foreach (var drawable in yft.Fragment.DrawableArray.data_items) + { + drawablecount++; + foreach (var kvp in drawable.VertexDecls) + { + if (!vdecls.ContainsKey(kvp.Key)) + { + vdecls.Add(kvp.Key, kvp.Value); + vdecluse.Add(kvp.Key, 1); + } + else + { + vdecluse[kvp.Key]++; + } + } + } + } + + } + + } + catch (Exception ex) + { + errs.Add(entry.Path + ": " + ex.ToString()); + } + } + } + + + string errstr = string.Join("\r\n", errs); + + + + //build vertex types code string + errs.Clear(); + StringBuilder sbverts = new StringBuilder(); + foreach (var kvp in vdecls) + { + var vd = kvp.Value; + int usage = vdecluse[kvp.Key]; + sbverts.AppendFormat("public struct VertexType{0} //id: {1}, stride: {2}, flags: {3}, types: {4}, refs: {5}", vd.Flags, kvp.Key, vd.Stride, vd.Flags, vd.Types, usage); + sbverts.AppendLine(); + sbverts.AppendLine("{"); + uint compid = 1; + for (int i = 0; i < 16; i++) + { + if (((vd.Flags >> i) & 1) == 1) + { + string typestr = "Unknown"; + uint type = (uint)(((ulong)vd.Types >> (4 * i)) & 0xF); + switch (type) + { + case 0: typestr = "ushort"; break;// Data[i] = new ushort[1 * count]; break; + case 1: typestr = "ushort2"; break;// Data[i] = new ushort[2 * count]; break; + case 2: typestr = "ushort3"; break;// Data[i] = new ushort[3 * count]; break; + case 3: typestr = "ushort4"; break;// Data[i] = new ushort[4 * count]; break; + case 4: typestr = "float"; break;// Data[i] = new float[1 * count]; break; + case 5: typestr = "Vector2"; break;// Data[i] = new float[2 * count]; break; + case 6: typestr = "Vector3"; break;// Data[i] = new float[3 * count]; break; + case 7: typestr = "Vector4"; break;// Data[i] = new float[4 * count]; break; + case 8: typestr = "uint"; break;// Data[i] = new uint[count]; break; + case 9: typestr = "uint"; break;// Data[i] = new uint[count]; break; + case 10: typestr = "uint"; break;// Data[i] = new uint[count]; break; + default: + break; + } + sbverts.AppendLine(" public " + typestr + " Component" + compid.ToString() + ";"); + compid++; + } + + } + sbverts.AppendLine("}"); + sbverts.AppendLine(); + } + + string vertstr = sbverts.ToString(); + string verrstr = string.Join("\r\n", errs); + + UpdateStatus?.Invoke((DateTime.Now - starttime).ToString() + " elapsed, " + drawablecount.ToString() + " drawables, " + errs.Count.ToString() + " errors."); + + } + } +} diff --git a/CodeWalker.Core/Utils/Cache.cs b/CodeWalker.Core/Utils/Cache.cs index aac6ce1..65fc7cf 100644 --- a/CodeWalker.Core/Utils/Cache.cs +++ b/CodeWalker.Core/Utils/Cache.cs @@ -1,4 +1,6 @@ -using System; +using CodeWalker.GameFiles; +using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Text; @@ -15,6 +17,7 @@ namespace CodeWalker public DateTime CurrentTime = DateTime.Now; private LinkedList loadedList = new LinkedList(); + private object loadedListLock = new object(); private Dictionary> loadedListDict = new Dictionary>(); public int Count @@ -43,25 +46,30 @@ namespace CodeWalker public TVal TryGet(TKey key) { LinkedListNode lln = null; - if (loadedListDict.TryGetValue(key, out lln)) + lock (loadedListLock) { - loadedList.Remove(lln); - loadedList.AddLast(lln); - lln.Value.LastUseTime = CurrentTime; + if (loadedListDict.TryGetValue(key, out lln)) + { + + loadedList.Remove(lln); + loadedList.AddLast(lln); + + lln.Value.LastUseTime = CurrentTime; + } } return (lln != null) ? lln.Value : null; } public bool TryAdd(TKey key, TVal item) { - if (item.MemoryUsage == 0) - { - } item.Key = key; if (CanAdd()) { - var lln = loadedList.AddLast(item); - loadedListDict.Add(key, lln); - Interlocked.Add(ref CurrentMemoryUsage, item.MemoryUsage); + lock(loadedListLock) + { + var lln = loadedList.AddLast(item); + loadedListDict.Add(key, lln); + Interlocked.Add(ref CurrentMemoryUsage, item.MemoryUsage); + } return true; } else @@ -74,9 +82,13 @@ namespace CodeWalker { while ((!CanAdd()) && (oldlln != null) && ((CurrentTime - oldlln.Value.LastUseTime).TotalSeconds > cachetime)) { - Interlocked.Add(ref CurrentMemoryUsage, -oldlln.Value.MemoryUsage); - loadedListDict.Remove(oldlln.Value.Key); - loadedList.Remove(oldlln); //gc should free up memory later.. + lock(loadedListLock) + { + Interlocked.Add(ref CurrentMemoryUsage, -oldlln.Value.MemoryUsage); + loadedListDict.Remove(oldlln.Value.Key); + loadedList.Remove(oldlln); //gc should free up memory later.. + } + oldlln.Value = null; oldlln = null; //GC.Collect(); @@ -87,9 +99,12 @@ namespace CodeWalker } if (CanAdd()) //see if there's enough memory now... { - var newlln = loadedList.AddLast(item); - loadedListDict.Add(key, newlln); - Interlocked.Add(ref CurrentMemoryUsage, item.MemoryUsage); + lock(loadedListLock) + { + var newlln = loadedList.AddLast(item); + loadedListDict.Add(key, newlln); + Interlocked.Add(ref CurrentMemoryUsage, item.MemoryUsage); + } return true; } else @@ -108,35 +123,49 @@ namespace CodeWalker public void Clear() { - loadedList.Clear(); - loadedListDict.Clear(); - CurrentMemoryUsage = 0; + lock(loadedList) + { + loadedList.Clear(); + loadedListDict.Clear(); + CurrentMemoryUsage = 0; + } } public void Remove(TKey key) { - LinkedListNode n; - if (loadedListDict.TryGetValue(key, out n)) + if (!loadedListDict.ContainsKey(key)) { - loadedListDict.Remove(key); - loadedList.Remove(n); - Interlocked.Add(ref CurrentMemoryUsage, -n.Value.MemoryUsage); + return; + } + lock(loadedListLock) + { + LinkedListNode n; + if (loadedListDict.TryGetValue(key, out n)) + { + loadedListDict.Remove(key); + loadedList.Remove(n); + Interlocked.Add(ref CurrentMemoryUsage, -n.Value.MemoryUsage); + } } } public void Compact() { - var oldlln = loadedList.First; - while (oldlln != null) + lock(loadedList) { - if ((CurrentTime - oldlln.Value.LastUseTime).TotalSeconds < CacheTime) break; - var nextln = oldlln.Next; - Interlocked.Add(ref CurrentMemoryUsage, -oldlln.Value.MemoryUsage); - loadedListDict.Remove(oldlln.Value.Key); - loadedList.Remove(oldlln); //gc should free up memory later.. - oldlln.Value = null; - oldlln = nextln; + var oldlln = loadedList.First; + while (oldlln != null) + { + if ((CurrentTime - oldlln.Value.LastUseTime).TotalSeconds < CacheTime) break; + var nextln = oldlln.Next; + Interlocked.Add(ref CurrentMemoryUsage, -oldlln.Value.MemoryUsage); + loadedListDict.Remove(oldlln.Value.Key); + loadedList.Remove(oldlln); //gc should free up memory later.. + + oldlln.Value = null; + oldlln = nextln; + } } } diff --git a/CodeWalker.Core/Utils/FbxConverter.cs b/CodeWalker.Core/Utils/FbxConverter.cs index 3cd38a1..d62fb67 100644 --- a/CodeWalker.Core/Utils/FbxConverter.cs +++ b/CodeWalker.Core/Utils/FbxConverter.cs @@ -781,7 +781,6 @@ namespace CodeWalker texParam.Unknown_30h = 1;// 131073;//wtf is this? 2x shorts, 0x00020001 texParam.Unknown_32h = 2; texParam.Name = name; - texParam.NameHash = JenkHash.GenHash(name.ToLowerInvariant()); return texParam; } private void AddShaderParam(List paramNames, List paramValues, ShaderParamNames paramName, object paramValue) diff --git a/CodeWalker.Core/Utils/Timer.cs b/CodeWalker.Core/Utils/Timer.cs new file mode 100644 index 0000000..3bc52cd --- /dev/null +++ b/CodeWalker.Core/Utils/Timer.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Text; + +namespace CodeWalker.Core.Utils +{ + public class DisposableTimer : IDisposable + { + public static event Action TimerStopped; + public readonly Stopwatch _stopwatch; + + static DisposableTimer() + { +#if DEBUG + TimerStopped += (timeSpan, name) => Debug.WriteLine($"{name} took {timeSpan.TotalMilliseconds} ms"); +#endif + TimerStopped += (timeSpan, name) => Console.WriteLine($"{name} took {timeSpan.TotalMilliseconds} ms"); + } + + public string Name { get; private set; } + + public DisposableTimer(string name) + { + _stopwatch = Stopwatch.StartNew(); + Name = name; + } + + public void Dispose() + { + _stopwatch.Stop(); + TimerStopped?.Invoke(_stopwatch.Elapsed, Name ?? string.Empty); + } + } +} diff --git a/CodeWalker.Core/Utils/Utils.cs b/CodeWalker.Core/Utils/Utils.cs index bf5748f..c7c8159 100644 --- a/CodeWalker.Core/Utils/Utils.cs +++ b/CodeWalker.Core/Utils/Utils.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.ComponentModel; using System.Drawing; using System.Globalization; +using System.IO; using System.Linq; using System.Runtime.InteropServices; using System.Text; @@ -100,6 +101,10 @@ namespace CodeWalker } return Encoding.UTF8.GetString(bytes); } + public static bool Contains(this string source, string toCheck, StringComparison comp) + { + return source?.IndexOf(toCheck, comp) >= 0; + } } @@ -271,5 +276,23 @@ namespace CodeWalker } } + 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.ErrorReport/CodeWalker.ErrorReport.csproj b/CodeWalker.ErrorReport/CodeWalker.ErrorReport.csproj index f64066c..6c0b155 100644 --- a/CodeWalker.ErrorReport/CodeWalker.ErrorReport.csproj +++ b/CodeWalker.ErrorReport/CodeWalker.ErrorReport.csproj @@ -6,6 +6,7 @@ true CW.ico CodeWalker Error Report Tool + latest \ No newline at end of file diff --git a/CodeWalker.Peds/CodeWalker.Peds.csproj b/CodeWalker.Peds/CodeWalker.Peds.csproj index 4e3ddb5..a4fee3d 100644 --- a/CodeWalker.Peds/CodeWalker.Peds.csproj +++ b/CodeWalker.Peds/CodeWalker.Peds.csproj @@ -15,6 +15,11 @@ x64 + + + + + diff --git a/CodeWalker.RPFExplorer/CodeWalker.RPFExplorer.csproj b/CodeWalker.RPFExplorer/CodeWalker.RPFExplorer.csproj index c31034a..776afa0 100644 --- a/CodeWalker.RPFExplorer/CodeWalker.RPFExplorer.csproj +++ b/CodeWalker.RPFExplorer/CodeWalker.RPFExplorer.csproj @@ -10,12 +10,18 @@ dexyfex software dexyfex CodeWalker RPF Explorer + latest x64 + + + + + diff --git a/CodeWalker.RPFExplorer/Program.cs b/CodeWalker.RPFExplorer/Program.cs index b71d471..3d237d4 100644 --- a/CodeWalker.RPFExplorer/Program.cs +++ b/CodeWalker.RPFExplorer/Program.cs @@ -6,6 +6,7 @@ using System.Diagnostics; using System.Linq; using System.Threading.Tasks; using System.Windows.Forms; +using CodeWalker.Utils; namespace CodeWalker.RPFExplorer { @@ -18,7 +19,7 @@ namespace CodeWalker.RPFExplorer static void Main() { //Process.Start("CodeWalker.exe", "explorer"); - + ConsoleWindow.Hide(); Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); Application.Run(new ExploreForm()); diff --git a/CodeWalker.Shaders/BasicVS_PNCTX.hlsl b/CodeWalker.Shaders/BasicVS_PNCTX.hlsl index 92632f0..304d63c 100644 --- a/CodeWalker.Shaders/BasicVS_PNCTX.hlsl +++ b/CodeWalker.Shaders/BasicVS_PNCTX.hlsl @@ -9,6 +9,22 @@ struct VS_INPUT float4 Tangent : TANGENT; }; +struct PNCTX_OUTPUT +{ + float4 Position : SV_POSITION; + float3 Normal : NORMAL; + float2 Texcoord0 : TEXCOORD0; + float2 Texcoord1 : TEXCOORD1; + float2 Texcoord2 : TEXCOORD2; + float4 Shadows : TEXCOORD3; + float4 LightShadow : TEXCOORD4; + float4 Colour0 : COLOR0; + float4 Colour1 : COLOR1; + float4 Tint : COLOR2; + float4 Tangent : TEXCOORD5; + float4 Bitangent : TEXCOORD6; + float3 CamRelPos : TEXCOORD7; +}; VS_OUTPUT main(VS_INPUT input, uint iid : SV_InstanceID) { diff --git a/CodeWalker.Shaders/CodeWalker.Shaders.vcxproj b/CodeWalker.Shaders/CodeWalker.Shaders.vcxproj index b5d6e35..da33fd1 100644 --- a/CodeWalker.Shaders/CodeWalker.Shaders.vcxproj +++ b/CodeWalker.Shaders/CodeWalker.Shaders.vcxproj @@ -106,6 +106,7 @@ $(ProjectDir)..\Shaders\%(Filename).cso 4.0 + true diff --git a/CodeWalker.Shaders/Common.hlsli b/CodeWalker.Shaders/Common.hlsli index 8f4747e..fb03e61 100644 --- a/CodeWalker.Shaders/Common.hlsli +++ b/CodeWalker.Shaders/Common.hlsli @@ -1,6 +1,3 @@ - - - struct ShaderGlobalLightParams { float3 LightDir; diff --git a/CodeWalker.Shaders/DirLightPS.hlsl b/CodeWalker.Shaders/DirLightPS.hlsl index 8e97e51..a32ed6b 100644 --- a/CodeWalker.Shaders/DirLightPS.hlsl +++ b/CodeWalker.Shaders/DirLightPS.hlsl @@ -17,7 +17,8 @@ PS_OUTPUT main(VS_Output input) { uint3 ssloc = uint3(input.Pos.xy, 0); //pixel location float depth = DepthTex.Load(ssloc).r; - if (depth == 0) discard; //no existing pixel rendered here + if (depth <= 0) + discard; //no existing pixel rendered here float4 diffuse = DiffuseTex.Load(ssloc); float4 normal = NormalTex.Load(ssloc); @@ -29,9 +30,15 @@ PS_OUTPUT main(VS_Output input) switch (RenderMode) { - case 5: output.Colour = float4(diffuse.rgb, 1); return output; - case 6: output.Colour = float4(normal.rgb, 1); return output; - case 7: output.Colour = float4(specular.rgb, 1); return output; + case 5: + output.Colour = float4(diffuse.rgb, 1); + return output; + case 6: + output.Colour = float4(normal.rgb, 1); + return output; + case 7: + output.Colour = float4(specular.rgb, 1); + return output; } float4 spos = float4(input.Screen.xy/input.Screen.w, depth, 1); diff --git a/CodeWalker.Shaders/DirLightPS_MS.hlsl b/CodeWalker.Shaders/DirLightPS_MS.hlsl index 1beb390..3a08efc 100644 --- a/CodeWalker.Shaders/DirLightPS_MS.hlsl +++ b/CodeWalker.Shaders/DirLightPS_MS.hlsl @@ -34,7 +34,8 @@ PS_OUTPUT main(VS_Output input) for (int i = 0; i < sc; i++) { float depth = DepthTex.Load(ssloc, i); - if (depth == 0) continue; //no existing subpixel rendered here + if (depth == 0) + continue; //no existing subpixel rendered here float4 diffuse = DiffuseTex.Load(ssloc, i); float4 normal = NormalTex.Load(ssloc, i); @@ -56,7 +57,8 @@ PS_OUTPUT main(VS_Output input) d *= SampleMult; a *= SampleMult; - if (d <= 0) discard; + if (d <= 0) + discard; PS_OUTPUT output; output.Colour = float4(c, a); diff --git a/CodeWalker.Shaders/LightPS.hlsl b/CodeWalker.Shaders/LightPS.hlsl index 7096c76..429a517 100644 --- a/CodeWalker.Shaders/LightPS.hlsl +++ b/CodeWalker.Shaders/LightPS.hlsl @@ -18,7 +18,8 @@ float4 main(VS_Output input) : SV_TARGET { uint3 ssloc = uint3(input.Pos.xy, 0); //pixel location float depth = DepthTex.Load(ssloc).r; - if (depth == 0) discard; //no existing pixel rendered here + if (depth == 0) + discard; //no existing pixel rendered here float4 diffuse = DiffuseTex.Load(ssloc); float4 normal = NormalTex.Load(ssloc); @@ -31,7 +32,8 @@ float4 main(VS_Output input) : SV_TARGET float3 norm = normal.xyz * 2 - 1; float4 lcol = DeferredLight(camRel, norm, diffuse, specular, irradiance); - if (lcol.a <= 0) discard; + if (lcol.a <= 0) + discard; return lcol; } diff --git a/CodeWalker.Shaders/LightPS.hlsli b/CodeWalker.Shaders/LightPS.hlsli index a7b27ec..d143386 100644 --- a/CodeWalker.Shaders/LightPS.hlsli +++ b/CodeWalker.Shaders/LightPS.hlsli @@ -117,6 +117,7 @@ float4 DeferredLODLight(float3 camRel, float3 norm, float4 diffuse, float4 specu LODLight lodlight = LODLights[iid]; float3 srpos = lodlight.Position - (camRel + CameraPos.xyz); //light position relative to surface position float ldist = length(srpos); + if (LightType == 4)//capsule { float3 ext = lodlight.Direction.xyz * lodlight.OuterAngleOrCapExt; @@ -124,14 +125,17 @@ float4 DeferredLODLight(float3 camRel, float3 norm, float4 diffuse, float4 specu ldist = lsn.w; srpos.xyz = lsn.xyz; } - - if (ldist > lodlight.Falloff) return 0; //out of range of the light... - if (ldist <= 0) return 0; + + if (ldist > lodlight.Falloff) + return 0; //out of range of the light... + if (ldist <= 0) + return 0; float4 rgbi = Unpack4x8UNF(lodlight.Colour).gbar; float3 lcol = rgbi.rgb * rgbi.a * 96.0f; float3 ldir = srpos / ldist; float pclit = saturate(dot(ldir, norm)); + float lamt = 1; if (LightType == 1)//point (sphere) @@ -154,7 +158,8 @@ float4 DeferredLODLight(float3 camRel, float3 norm, float4 diffuse, float4 specu pclit *= lamt; - if (pclit <= 0) return 0; + if (pclit <= 0) + return 0; float3 refl = GetReflectedDir(camRel, norm); float specb = saturate(dot(refl, ldir)); @@ -177,11 +182,14 @@ float4 DeferredLight(float3 camRel, float3 norm, float4 diffuse, float4 specular ldist = lsn.w; srpos.xyz = lsn.xyz; } - if (ldist > InstFalloff) return 0; - if (ldist <= 0) return 0; + if (ldist > InstFalloff) + return 0; + if (ldist <= 0) + return 0; float d = dot(srpos, InstCullingPlaneNormal) - InstCullingPlaneOffset; - if (d > 0) return 0; + if (d > 0) + return 0; float4 rgbi = float4(InstColour, InstIntensity); float3 lcol = rgbi.rgb;// * rgbi.a; // * 5.0f; @@ -198,7 +206,8 @@ float4 DeferredLight(float3 camRel, float3 norm, float4 diffuse, float4 specular float ang = acos(-dot(ldir, InstDirection)); float iang = InstConeInnerAngle; float oang = InstConeOuterAngle; - if (ang > oang) return 0; + if (ang > oang) + return 0; lamt *= saturate(1 - ((ang - iang) / (oang - iang))); lamt *= GetAttenuation(ldist, InstFalloff, InstFalloffExponent); } @@ -209,7 +218,8 @@ float4 DeferredLight(float3 camRel, float3 norm, float4 diffuse, float4 specular pclit *= lamt; - if (pclit <= 0) return 0; + if (pclit <= 0) + return 0; float3 refl = GetReflectedDir(camRel, norm); float specb = saturate(dot(refl, ldir)); diff --git a/CodeWalker.Shaders/LightPS_MS.hlsl b/CodeWalker.Shaders/LightPS_MS.hlsl index ebfab23..e998070 100644 --- a/CodeWalker.Shaders/LightPS_MS.hlsl +++ b/CodeWalker.Shaders/LightPS_MS.hlsl @@ -18,14 +18,14 @@ float4 main(VS_Output input) : SV_TARGET uint2 ssloc = uint2(input.Pos.xy); //pixel location float2 spos = float2(input.Screen.xy / input.Screen.w); float4 c = 0; - float d = 0; int sc = min(SampleCount, 8); [unroll] for (int i = 0; i < sc; i++) { float depth = DepthTex.Load(ssloc, i); - if (depth == 0) continue; //no existing subpixel rendered here + if (depth == 0) + continue; //no existing subpixel rendered here float4 diffuse = DiffuseTex.Load(ssloc, i); float4 normal = NormalTex.Load(ssloc, i); @@ -37,15 +37,16 @@ float4 main(VS_Output input) : SV_TARGET float3 norm = normal.xyz * 2 - 1; float4 colour = DeferredLight(camRel, norm, diffuse, specular, irradiance); + if (colour.a <= 0) + discard; c += colour; - d += depth; } c *= SampleMult; - d *= SampleMult; - if (d <= 0) discard; + if (c.a <= 0) + discard; return c; } diff --git a/CodeWalker.Shaders/LodLightsPS.hlsl b/CodeWalker.Shaders/LodLightsPS.hlsl index b2b7e40..877c952 100644 --- a/CodeWalker.Shaders/LodLightsPS.hlsl +++ b/CodeWalker.Shaders/LodLightsPS.hlsl @@ -18,7 +18,8 @@ float4 main(VS_Output input) : SV_TARGET { uint3 ssloc = uint3(input.Pos.xy, 0); //pixel location float depth = DepthTex.Load(ssloc).r; - if (depth == 0) discard; //no existing pixel rendered here + if (depth == 0) + discard; //no existing pixel rendered here float4 diffuse = DiffuseTex.Load(ssloc); float4 normal = NormalTex.Load(ssloc); @@ -31,7 +32,8 @@ float4 main(VS_Output input) : SV_TARGET float3 norm = normal.xyz * 2 - 1; float4 lcol = DeferredLODLight(camRel, norm, diffuse, specular, irradiance, input.IID); - if (lcol.a <= 0) discard; + if (lcol.a <= 0) + discard; return lcol; } diff --git a/CodeWalker.Shaders/LodLightsPS_MS.hlsl b/CodeWalker.Shaders/LodLightsPS_MS.hlsl index cb7d0f8..4001a02 100644 --- a/CodeWalker.Shaders/LodLightsPS_MS.hlsl +++ b/CodeWalker.Shaders/LodLightsPS_MS.hlsl @@ -26,7 +26,8 @@ float4 main(VS_Output input) : SV_TARGET for (int i = 0; i < sc; i++) { float depth = DepthTex.Load(ssloc, i); - if (depth == 0) continue; //no existing subpixel rendered here + if (depth == 0) + continue; //no existing subpixel rendered here float4 diffuse = DiffuseTex.Load(ssloc, i); float4 normal = NormalTex.Load(ssloc, i); @@ -38,15 +39,16 @@ float4 main(VS_Output input) : SV_TARGET float3 norm = normal.xyz * 2 - 1; float4 colour = DeferredLODLight(camRel, norm, diffuse, specular, irradiance, input.IID); + if (colour.a <= 0) + discard; c += colour; - d += depth; } c *= SampleMult; - d *= SampleMult; - if (d <= 0) discard; + if (c.a <= 0) + discard; return c; } diff --git a/CodeWalker.Shaders/LodLightsVS.hlsl b/CodeWalker.Shaders/LodLightsVS.hlsl index 8880a0f..d0409d3 100644 --- a/CodeWalker.Shaders/LodLightsVS.hlsl +++ b/CodeWalker.Shaders/LodLightsVS.hlsl @@ -12,6 +12,7 @@ struct LODLight float FalloffExponent; float InnerAngle; //for cone float OuterAngleOrCapExt; //outer angle for cone, cap extent for capsule + float Distance; }; struct VS_Output diff --git a/CodeWalker.Vehicles/CodeWalker.Vehicles.csproj b/CodeWalker.Vehicles/CodeWalker.Vehicles.csproj index 4a32fe0..c046705 100644 --- a/CodeWalker.Vehicles/CodeWalker.Vehicles.csproj +++ b/CodeWalker.Vehicles/CodeWalker.Vehicles.csproj @@ -9,12 +9,18 @@ dexyfex software dexyfex CodeWalker Vehicle Viewer + latest x64 + + + + + diff --git a/CodeWalker.WinForms/CodeWalker.WinForms.csproj b/CodeWalker.WinForms/CodeWalker.WinForms.csproj index e599f32..2d5623b 100644 --- a/CodeWalker.WinForms/CodeWalker.WinForms.csproj +++ b/CodeWalker.WinForms/CodeWalker.WinForms.csproj @@ -1,9 +1,14 @@  + + x64 + + Library net48 true + latest diff --git a/CodeWalker/App.config b/CodeWalker/App.config index 38524be..bb8b598 100644 --- a/CodeWalker/App.config +++ b/CodeWalker/App.config @@ -43,27 +43,6 @@ True - - 720 - - - True - - - Global - - - contrails - - - True - - - True - - - True - True @@ -247,6 +226,30 @@ + + 720 + + + False + + + Global + + + contrails + + + True + + + True + + + True + + + 2 + diff --git a/CodeWalker/CodeWalker.csproj b/CodeWalker/CodeWalker.csproj index c2cba3a..cca3d9b 100644 --- a/CodeWalker/CodeWalker.csproj +++ b/CodeWalker/CodeWalker.csproj @@ -9,6 +9,7 @@ dexyfex dexyfex software dexyfex + latest @@ -18,6 +19,7 @@ + @@ -25,6 +27,7 @@ + diff --git a/CodeWalker/ExploreForm.Designer.cs b/CodeWalker/ExploreForm.Designer.cs index ab4bfea..155179a 100644 --- a/CodeWalker/ExploreForm.Designer.cs +++ b/CodeWalker/ExploreForm.Designer.cs @@ -1,4 +1,6 @@ -namespace CodeWalker +using CodeWalker.Core.Utils; + +namespace CodeWalker { partial class ExploreForm { @@ -158,6 +160,7 @@ this.OpenFileDialog = new System.Windows.Forms.OpenFileDialog(); this.FolderBrowserDialog = new System.Windows.Forms.FolderBrowserDialog(); this.VSExtender = new WeifenLuo.WinFormsUI.Docking.VisualStudioToolStripExtender(this.components); + this.openConsoleToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.MainMenu.SuspendLayout(); this.MainToolbar.SuspendLayout(); this.MainStatusBar.SuspendLayout(); @@ -409,6 +412,7 @@ this.ViewSmallIconsMenu, this.ViewListMenu, this.ViewDetailsMenu, + this.openConsoleToolStripMenuItem, this.toolStripSeparator11, this.ViewThemeMenu}); this.ViewMenu.Name = "ViewMenu"; @@ -418,21 +422,21 @@ // ViewLargeIconsMenu // this.ViewLargeIconsMenu.Name = "ViewLargeIconsMenu"; - this.ViewLargeIconsMenu.Size = new System.Drawing.Size(134, 22); + this.ViewLargeIconsMenu.Size = new System.Drawing.Size(180, 22); this.ViewLargeIconsMenu.Text = "Large Icons"; this.ViewLargeIconsMenu.Click += new System.EventHandler(this.ViewLargeIconsMenu_Click); // // ViewSmallIconsMenu // this.ViewSmallIconsMenu.Name = "ViewSmallIconsMenu"; - this.ViewSmallIconsMenu.Size = new System.Drawing.Size(134, 22); + this.ViewSmallIconsMenu.Size = new System.Drawing.Size(180, 22); this.ViewSmallIconsMenu.Text = "Small Icons"; this.ViewSmallIconsMenu.Click += new System.EventHandler(this.ViewSmallIconsMenu_Click); // // ViewListMenu // this.ViewListMenu.Name = "ViewListMenu"; - this.ViewListMenu.Size = new System.Drawing.Size(134, 22); + this.ViewListMenu.Size = new System.Drawing.Size(180, 22); this.ViewListMenu.Text = "List"; this.ViewListMenu.Click += new System.EventHandler(this.ViewListMenu_Click); // @@ -441,14 +445,14 @@ this.ViewDetailsMenu.Checked = true; this.ViewDetailsMenu.CheckState = System.Windows.Forms.CheckState.Checked; this.ViewDetailsMenu.Name = "ViewDetailsMenu"; - this.ViewDetailsMenu.Size = new System.Drawing.Size(134, 22); + this.ViewDetailsMenu.Size = new System.Drawing.Size(180, 22); this.ViewDetailsMenu.Text = "Details"; this.ViewDetailsMenu.Click += new System.EventHandler(this.ViewDetailsMenu_Click); // // toolStripSeparator11 // this.toolStripSeparator11.Name = "toolStripSeparator11"; - this.toolStripSeparator11.Size = new System.Drawing.Size(131, 6); + this.toolStripSeparator11.Size = new System.Drawing.Size(177, 6); // // ViewThemeMenu // @@ -458,7 +462,7 @@ this.ViewThemeLightMenu, this.ViewThemeDarkMenu}); this.ViewThemeMenu.Name = "ViewThemeMenu"; - this.ViewThemeMenu.Size = new System.Drawing.Size(134, 22); + this.ViewThemeMenu.Size = new System.Drawing.Size(180, 22); this.ViewThemeMenu.Text = "Theme"; // // ViewThemeWindowsMenu @@ -506,35 +510,35 @@ // ToolsBinSearchMenu // this.ToolsBinSearchMenu.Name = "ToolsBinSearchMenu"; - this.ToolsBinSearchMenu.Size = new System.Drawing.Size(180, 22); + this.ToolsBinSearchMenu.Size = new System.Drawing.Size(161, 22); this.ToolsBinSearchMenu.Text = "Binary Search..."; this.ToolsBinSearchMenu.Click += new System.EventHandler(this.ToolsBinSearchMenu_Click); // // ToolsAudioExplorerMenu // this.ToolsAudioExplorerMenu.Name = "ToolsAudioExplorerMenu"; - this.ToolsAudioExplorerMenu.Size = new System.Drawing.Size(180, 22); + this.ToolsAudioExplorerMenu.Size = new System.Drawing.Size(161, 22); this.ToolsAudioExplorerMenu.Text = "Audio Explorer"; this.ToolsAudioExplorerMenu.Click += new System.EventHandler(this.ToolsAudioExplorerMenu_Click); // // ToolsRpfBrowserMenu // this.ToolsRpfBrowserMenu.Name = "ToolsRpfBrowserMenu"; - this.ToolsRpfBrowserMenu.Size = new System.Drawing.Size(180, 22); + this.ToolsRpfBrowserMenu.Size = new System.Drawing.Size(161, 22); this.ToolsRpfBrowserMenu.Text = "Old RPF Browser"; this.ToolsRpfBrowserMenu.Click += new System.EventHandler(this.ToolsRpfBrowserMenu_Click); // // ToolsJenkGenMenu // this.ToolsJenkGenMenu.Name = "ToolsJenkGenMenu"; - this.ToolsJenkGenMenu.Size = new System.Drawing.Size(180, 22); + this.ToolsJenkGenMenu.Size = new System.Drawing.Size(161, 22); this.ToolsJenkGenMenu.Text = "JenkGen"; this.ToolsJenkGenMenu.Click += new System.EventHandler(this.ToolsJenkGenMenu_Click); // // ToolsJenkIndMenu // this.ToolsJenkIndMenu.Name = "ToolsJenkIndMenu"; - this.ToolsJenkIndMenu.Size = new System.Drawing.Size(180, 22); + this.ToolsJenkIndMenu.Size = new System.Drawing.Size(161, 22); this.ToolsJenkIndMenu.Text = "JenkInd"; this.ToolsJenkIndMenu.Click += new System.EventHandler(this.ToolsJenkIndMenu_Click); // @@ -1308,6 +1312,13 @@ // this.VSExtender.DefaultRenderer = null; // + // openConsoleToolStripMenuItem + // + this.openConsoleToolStripMenuItem.Name = "openConsoleToolStripMenuItem"; + this.openConsoleToolStripMenuItem.Size = new System.Drawing.Size(180, 22); + this.openConsoleToolStripMenuItem.Text = "Open Console"; + this.openConsoleToolStripMenuItem.Click += new System.EventHandler(this.ViewConsoleMenu_Click); + // // ExploreForm // this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); @@ -1476,5 +1487,6 @@ private System.Windows.Forms.ToolStripSeparator toolStripSeparator13; private System.Windows.Forms.ToolStripMenuItem ListContextNewYtdFileMenu; private System.Windows.Forms.ToolStripMenuItem ToolsAudioExplorerMenu; + private System.Windows.Forms.ToolStripMenuItem openConsoleToolStripMenuItem; } } \ No newline at end of file diff --git a/CodeWalker/ExploreForm.cs b/CodeWalker/ExploreForm.cs index 0044ba9..aa738bd 100644 --- a/CodeWalker/ExploreForm.cs +++ b/CodeWalker/ExploreForm.cs @@ -2,8 +2,11 @@ using CodeWalker.GameFiles; using CodeWalker.Properties; using CodeWalker.Tools; +using CodeWalker.Utils; using CodeWalker.World; +using CodeWalker.Core.Utils; using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.ComponentModel; using System.Data; @@ -16,7 +19,9 @@ using System.Threading; using System.Threading.Tasks; using System.Windows.Forms; using System.Xml; +using System.Xml.Linq; using WeifenLuo.WinFormsUI.Docking; +using System.Runtime.CompilerServices; namespace CodeWalker { @@ -46,13 +51,15 @@ namespace CodeWalker private MainTreeFolder SearchResults; private List AllRpfs { get; set; } - private GameFileCache FileCache { get; set; } = GameFileCacheFactory.Create(); + private GameFileCache FileCache { get; set; } private object FileCacheSyncRoot = new object(); public bool EditMode { get; private set; } = false; public ThemeBase Theme { get; private set; } + public static CancellationTokenSource CancellationTokenSource = new CancellationTokenSource(); + public ExploreForm() { @@ -63,6 +70,8 @@ namespace CodeWalker ShowMainListViewPathColumn(false); LoadSettings(); + + UpdateStatus += UpdateStatus_EventHandler; } private void SetTheme(string themestr, bool changing = true) @@ -183,26 +192,28 @@ namespace CodeWalker return; } + FileCache = GameFileCacheFactory.Create(); - Task.Run(() => + new Task(async () => { + Thread.CurrentThread.Name = "FileCache ContentThread"; try { GTA5Keys.LoadFromPath(GTAFolder.CurrentGTAFolder, Settings.Default.Key); } catch { - UpdateStatus("Unable to load gta5.exe!"); + UpdateStatus?.Invoke("Unable to load gta5.exe!"); return; } - RefreshMainTreeView(); + await RefreshMainTreeView(); - UpdateStatus("Scan complete."); + UpdateStatus?.Invoke("Scan complete."); InitFileCache(); - while (!IsDisposed) //run the file cache content thread until the form exits. + while (!IsDisposed && !CancellationTokenSource.IsCancellationRequested) //run the file cache content thread until the form exits. { if (FileCache.IsInited) { @@ -220,7 +231,7 @@ namespace CodeWalker Thread.Sleep(20); } } - }); + }, CancellationTokenSource.Token, TaskCreationOptions.LongRunning).Start(TaskScheduler.Default); } private void InitFileCache() @@ -231,20 +242,20 @@ namespace CodeWalker { if (!FileCache.IsInited) { - UpdateStatus("Loading file cache..."); + UpdateStatus?.Invoke("Loading file cache..."); var allRpfs = AllRpfs; - FileCache.Init(UpdateStatus, UpdateErrorLog, allRpfs); //inits main dicts and archetypes only... + FileCache.Init(updateStatus: null, UpdateErrorLog, allRpfs); //inits main dicts and archetypes only... - UpdateStatus("Loading materials..."); + UpdateStatus?.Invoke("Loading materials..."); BoundsMaterialTypes.Init(FileCache); - UpdateStatus("Loading scenario types..."); + UpdateStatus?.Invoke("Loading scenario types..."); Scenarios.EnsureScenarioTypes(FileCache); - UpdateStatus("File cache loaded."); + UpdateStatus?.Invoke("File cache loaded."); } } - }); + }, CancellationTokenSource.Token); } public GameFileCache GetFileCache() { @@ -258,7 +269,7 @@ namespace CodeWalker private void InitFileTypes() { - FileTypes = new Dictionary(); + FileTypes = new Dictionary(StringComparer.OrdinalIgnoreCase); InitFileType(".rpf", "Rage Package File", 3); InitFileType("", "File", 4); InitFileType(".dat", "Data File", 4); @@ -339,7 +350,7 @@ namespace CodeWalker return FileTypes[""]; } - var ext = Path.GetExtension(fn).ToLowerInvariant(); + var ext = Path.GetExtension(fn); if (!string.IsNullOrEmpty(ext)) { FileTypeInfo ft; @@ -347,10 +358,9 @@ namespace CodeWalker { if (ft.SubTypes != null) { - var fnl = fn.ToLowerInvariant(); foreach (var sft in ft.SubTypes) { - if (fnl.EndsWith(sft.Extension)) + if (fn.EndsWith(sft.Extension, StringComparison.OrdinalIgnoreCase)) { return sft; } @@ -371,9 +381,14 @@ namespace CodeWalker } } - + public event Action UpdateStatus; - public void UpdateStatus(string text) + public void InvokeUpdateStatus(string msg) + { + UpdateStatus?.Invoke(msg); + } + + public void UpdateStatus_EventHandler(string text) { try { @@ -386,7 +401,10 @@ namespace CodeWalker StatusLabel.Text = text; } } - catch { } + catch(Exception ex) { + Console.WriteLine(ex); + Debug.WriteLine(ex); + } } public void UpdateErrorLog(string text) { @@ -398,10 +416,15 @@ namespace CodeWalker } else { - //StatusLabel.Text = text; + Debug.WriteLine(text); + Console.WriteLine(text); + StatusLabel.Text = text; } } - catch { } + catch (Exception ex) { + Debug.WriteLine(ex); + Console.WriteLine(ex); + } } @@ -459,8 +482,8 @@ namespace CodeWalker public void Navigate(string path) { if (!Ready) return; - var pathl = path.ToLowerInvariant().Replace('/', '\\'); - if ((CurrentFolder != null) && (CurrentFolder.Path.ToLowerInvariant() == pathl)) return; //already there + var pathl = path.Replace('/', '\\'); + if ((CurrentFolder != null) && (CurrentFolder.Path.Equals(path, StringComparison.InvariantCultureIgnoreCase))) return; //already there var hierarchy = pathl.Split(new[] { '\\' }, StringSplitOptions.RemoveEmptyEntries); TreeNode n = MainTreeView.Nodes[0];// FindTreeNode("gta v", null); if (!string.IsNullOrEmpty(path)) @@ -670,7 +693,7 @@ namespace CodeWalker var tnc = (parent != null) ? parent.Nodes : MainTreeView.Nodes; foreach (TreeNode node in tnc) { - if (node.Text.ToLowerInvariant() == text) + if (node.Text.Equals(text, StringComparison.OrdinalIgnoreCase)) { return node; } @@ -680,14 +703,15 @@ namespace CodeWalker - private void RefreshMainTreeView() + private async Task RefreshMainTreeView() { Ready = false; AllRpfs = null; - ClearMainTreeView(); + BeginInvoke(() => MainTreeView.BeginUpdate); + + UpdateStatus?.Invoke("Scanning..."); - UpdateStatus("Scanning..."); var root = new MainTreeFolder(); root.FullPath = GTAFolder.GetCurrentGTAFolderWithTrailingSlash(); @@ -695,7 +719,9 @@ namespace CodeWalker root.Name = "GTA V"; RootFolder = root; - RefreshMainTreeViewRoot(root); + var tasks = new List>(); + + tasks.Add(Task.Run(() => RefreshMainTreeViewRoot(root, true))); var remFolders = new List(); @@ -706,7 +732,15 @@ namespace CodeWalker if (Directory.Exists(extraroot.FullPath)) { - RefreshMainTreeViewRoot(extraroot); + try + { + tasks.Add(Task.Run(() => RefreshMainTreeViewRoot(extraroot))); + } + catch(Exception ex) + { + remFolders.Add(extraroot); + } + } else { @@ -719,106 +753,139 @@ namespace CodeWalker ExtraRootFolders.Remove(remFolder); } + var nodes = await Task.WhenAll(tasks); - Ready = true; + Invoke(() => + { + MainTreeView.BeginUpdate(); + ClearMainTreeView(); + + MainTreeView.Nodes.AddRange(nodes); + + Ready = true; + + MainTreeView.EndUpdate(); + MainTreeViewRefreshComplete(); + }); - MainTreeViewRefreshComplete(); } - private void RefreshMainTreeViewRoot(MainTreeFolder f) + + private TreeNode RefreshMainTreeViewRoot(MainTreeFolder f, bool gameRoot = false) { - var allRpfs = new List(); + var allRpfs = new ConcurrentBag(); var fullPath = f.FullPath; var subPath = f.Path; - var allpaths = Directory.GetFileSystemEntries(fullPath, "*", SearchOption.AllDirectories); - var nodes = new Dictionary(); + FileSystemInfo[] allpaths; - foreach (var path in allpaths) + using (new DisposableTimer("RefreshMainTreeViewRoot -> EnumerateFileSystemInfos")) { - var relpath = path.Replace(fullPath, ""); - var filepathl = path.ToLowerInvariant(); + allpaths = new DirectoryInfo(fullPath).EnumerateFileSystemInfos("*", SearchOption.AllDirectories).ToArray(); + } - var isFile = File.Exists(path); //could be a folder + using var _ = new DisposableTimer("RefreshMainTreeViewRoot"); - UpdateStatus("Scanning " + relpath + "..."); + //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(); - MainTreeFolder parentnode = null, prevnode = null, node = null; - var prevnodepath = ""; - var idx = isFile ? relpath.LastIndexOf('\\') : relpath.Length; - while (idx > 0) //create the folder tree nodes and build up the hierarchy + var partitioner = Partitioner.Create(allpaths, EnumerablePartitionerOptions.NoBuffering); + + Parallel.ForEach(partitioner, new ParallelOptions { MaxDegreeOfParallelism = 4, TaskScheduler = TaskScheduler.Default }, (fileEntryInfo) => + { + try { - var parentpath = relpath.Substring(0, idx); - var parentidx = parentpath.LastIndexOf('\\'); - var parentname = parentpath.Substring(parentidx + 1); - var exists = nodes.TryGetValue(parentpath, out node); - if (!exists) - { - node = CreateRootDirTreeFolder(parentname, subPath + parentpath, fullPath + parentpath); - nodes[parentpath] = node; - } - if (parentnode == null) - { - parentnode = node; - } - if (prevnode != null) - { - node.AddChild(prevnode); - } - prevnode = node; - prevnodepath = parentpath; - idx = relpath.LastIndexOf('\\', idx - 1); - if (exists) break; - if (idx < 0) - { - f.AddChild(node); - } - } + var path = fileEntryInfo.FullName; + var relpath = path.Substring(fullPath.Length); - if (isFile) - { - if (filepathl.EndsWith(".rpf")) //add RPF nodes + var isFile = fileEntryInfo is FileInfo; + //var isFile = !path.I.Contains(path); //could be a folder + + //UpdateStatus?.Invoke("Scanning " + relpath + "..."); + + MainTreeFolder parentnode = null, prevnode = null, node = null; + var prevnodepath = ""; + var idx = isFile ? relpath.LastIndexOf('\\') : relpath.Length; + while (idx > 0) //create the folder tree nodes and build up the hierarchy { - RpfFile rpf = new RpfFile(path, relpath); - - rpf.ScanStructure(UpdateStatus, UpdateErrorLog); - - if (rpf.LastException != null) //incase of corrupted rpf (or renamed NG encrypted RPF) + var parentpath = relpath.Substring(0, idx); + var parentidx = parentpath.LastIndexOf('\\'); + var parentname = parentpath.Substring(parentidx + 1); + var exists = true; + node = nodes.GetOrAdd(parentpath, (key) => { - continue; - } - - node = CreateRpfTreeFolder(rpf, relpath, path); - - RecurseMainTreeViewRPF(node, allRpfs); - - if (parentnode != null) + //Console.WriteLine($"Creating treenode for {parentpath}"); + var node = CreateRootDirTreeFolder(parentname, subPath + parentpath, fullPath + parentpath); + exists = false; + return node; + }); + parentnode ??= node; + if (prevnode != null) { - parentnode.AddChild(node); + node.AddChild(prevnode); } - else + prevnode = node; + prevnodepath = parentpath; + idx = relpath.LastIndexOf('\\', idx - 1); + + if (exists) break; + if (idx < 0) { f.AddChild(node); } } - else + + if (isFile) { - if (parentnode != null) + if (path.EndsWith(".rpf", StringComparison.OrdinalIgnoreCase)) //add RPF nodes { - parentnode.AddFile(path); + RpfFile rpf = new RpfFile(path, relpath); + + rpf.ScanStructure(updateStatus: null, UpdateErrorLog); + + if (rpf.LastException != null) //incase of corrupted rpf (or renamed NG encrypted RPF) + { + return; + } + + node = CreateRpfTreeFolder(rpf, relpath, path); + + RecurseMainTreeViewRPF(node, allRpfs); + + if (parentnode != null) + { + parentnode.AddChild(node); + } + else + { + f.AddChild(node); + } } else { - f.AddFile(path); + JenkIndex.Ensure(Path.GetFileNameWithoutExtension(path)); + if (parentnode != null) + { + parentnode.AddFile(path); + } + else + { + f.AddFile(path); + } } } } - } + catch (Exception ex) { + Console.WriteLine(ex); + UpdateErrorLog(ex.ToString()); + } + }); - - AddMainTreeViewRoot(f); + var rootNode = CreateTreeNode(f); if (f.Children != null) { - f.Children.Sort((n1, n2) => n1.Name.CompareTo(n2.Name)); + f.Children.Sort((n1, n2) => StringComparer.OrdinalIgnoreCase.Compare(n1.Name, n2.Name)); foreach (var node in f.Children) { @@ -826,13 +893,14 @@ namespace CodeWalker } } - if (AllRpfs == null) + if (gameRoot) { - AllRpfs = allRpfs; + AllRpfs = allRpfs.ToList(); } + return rootNode; } - private void RecurseMainTreeViewRPF(MainTreeFolder f, List allRpfs) + private void RecurseMainTreeViewRPF(MainTreeFolder f, ConcurrentBag allRpfs) { var rootpath = GTAFolder.GetCurrentGTAFolderWithTrailingSlash(); @@ -870,11 +938,9 @@ namespace CodeWalker { foreach (RpfEntry entry in rpf.AllEntries) { - if (string.IsNullOrEmpty(entry.NameLower)) continue; - var shortnamel = entry.GetShortNameLower(); - JenkIndex.Ensure(shortnamel); - entry.ShortNameHash = JenkHash.GenHash(shortnamel); - entry.NameHash = JenkHash.GenHash(entry.NameLower); + if (string.IsNullOrEmpty(entry.Name)) continue; + var shortName = entry.ShortName; + JenkIndex.EnsureLower(shortName); } } } @@ -902,22 +968,33 @@ namespace CodeWalker } catch { } } + + private TreeNode CreateTreeNode(MainTreeFolder f) + { + int imgIndex = 1; //FOLDER imageIndex + if (string.IsNullOrEmpty(f.Path)) imgIndex = 0; //ROOT imageIndex + var rn = new TreeNode(f.Name, imgIndex, imgIndex); + rn.Name = f.Path; + rn.ToolTipText = f.FullPath; + rn.Tag = f; + + f.TreeNode = rn; + + return rn; + } + private void AddMainTreeViewRoot(MainTreeFolder f) { try { if (InvokeRequired) { - Invoke(new Action(() => { AddMainTreeViewRoot(f); })); + BeginInvoke(new Action(() => { AddMainTreeViewRoot(f); })); } else { - int imgIndex = 1; //FOLDER imageIndex - if (string.IsNullOrEmpty(f.Path)) imgIndex = 0; //ROOT imageIndex - var rn = MainTreeView.Nodes.Add(f.Path, f.Name, imgIndex, imgIndex); - rn.ToolTipText = f.FullPath; - rn.Tag = f; - f.TreeNode = rn; + var node = CreateTreeNode(f); + MainTreeView.Nodes.Add(node); } } catch { } @@ -926,19 +1003,19 @@ namespace CodeWalker { try { - if (InvokeRequired) - { - Invoke(new Action(() => { AddMainTreeViewNode(f); })); - } - else - { + //if (InvokeRequired) + //{ + // Invoke(new Action(() => { AddMainTreeViewNode(f); })); + //} + //else + //{ string n = f.Name; var root = f.Parent?.TreeNode; RecurseAddMainTreeViewNodes(f, root); root.Expand(); - } + //} } catch { } } @@ -958,7 +1035,7 @@ namespace CodeWalker if (f.Children != null) { - f.Children.Sort((n1, n2) => n1.Name.CompareTo(n2.Name)); + f.Children.Sort((n1, n2) => StringComparer.OrdinalIgnoreCase.Compare(n1.Name, n2.Name)); foreach (var child in f.Children) { @@ -1090,7 +1167,7 @@ namespace CodeWalker { if (f.Parent == null) return; - f.Parent.Children?.Remove(f); + f.Parent.RemoveChild(f); if (f.TreeNode != null) { @@ -1130,13 +1207,13 @@ namespace CodeWalker { if ((child != tnf) && (child.Path == tnf.Path)) { - pfolder.Children.Remove(child); + pfolder.RemoveChild(child); child.TreeNode.Remove(); break; } } } - RecurseMainTreeViewRPF(tnf, AllRpfs); + RecurseMainTreeViewRPF(tnf, new ConcurrentBag(AllRpfs)); RecurseAddMainTreeViewNodes(tnf, pfolder.TreeNode); } private void EnsureImportedFolder(RpfDirectoryEntry entry, RpfDirectoryEntry parentrpffldr) @@ -1265,9 +1342,9 @@ namespace CodeWalker CurrentFiles.Clear(); - UpdateStatus("Searching..."); + UpdateStatus?.Invoke("Searching..."); - var term = text.ToLowerInvariant(); + var term = text; //Task.Run(() => //{ @@ -1280,11 +1357,11 @@ namespace CodeWalker if (Searching) { Searching = false; - UpdateStatus("Search complete. " + resultcount.ToString() + " items found."); + UpdateStatus?.Invoke("Search complete. " + resultcount.ToString() + " items found."); } else { - UpdateStatus("Search aborted. " + resultcount.ToString() + " items found."); + UpdateStatus?.Invoke("Search aborted. " + resultcount.ToString() + " items found."); } Cursor = Cursors.Default; @@ -1429,7 +1506,7 @@ namespace CodeWalker } - private void View(MainListItem item) + public void View(MainListItem item) { #if !DEBUG try @@ -1468,7 +1545,7 @@ namespace CodeWalker if (fe == null) { //this should only happen when opening a file from filesystem... - fe = CreateFileEntry(name, path, ref data); + fe = RpfFile.CreateFileEntry(name, path, ref data); } switch (ft.DefaultAction) @@ -1665,37 +1742,8 @@ namespace CodeWalker } private void ViewModel(string name, string path, byte[] data, RpfFileEntry e) { - var nl = e?.NameLower ?? ""; - var fe = Path.GetExtension(nl); ModelForm f = new ModelForm(this); - f.Show(); - switch (fe) - { - case ".ydr": - var ydr = RpfFile.GetFile(e, data); - f.LoadModel(ydr); - break; - case ".ydd": - var ydd = RpfFile.GetFile(e, data); - f.LoadModels(ydd); - break; - case ".yft": - var yft = RpfFile.GetFile(e, data); - f.LoadModel(yft); - break; - case ".ybn": - var ybn = RpfFile.GetFile(e, data); - f.LoadModel(ybn); - break; - case ".ypt": - var ypt = RpfFile.GetFile(e, data); - f.LoadParticles(ypt); - break; - case ".ynv": - var ynv = RpfFile.GetFile(e, data); - f.LoadNavmesh(ynv); - break; - } + f.ViewModel(data, e); } private void ViewCut(string name, string path, byte[] data, RpfFileEntry e) { @@ -1823,44 +1871,20 @@ namespace CodeWalker f.LoadMeta(ypdb); } - private RpfFileEntry CreateFileEntry(string name, string path, ref byte[] data) - { - //this should only really be used when loading a file from the filesystem. - RpfFileEntry e = null; - uint rsc7 = (data?.Length > 4) ? BitConverter.ToUInt32(data, 0) : 0; - if (rsc7 == 0x37435352) //RSC7 header present! create RpfResourceFileEntry and decompress data... - { - e = RpfFile.CreateResourceFileEntry(ref data, 0);//"version" should be loadable from the header in the data.. - data = ResourceBuilder.Decompress(data); - } - else - { - var be = new RpfBinaryFileEntry(); - be.FileSize = (uint)data?.Length; - be.FileUncompressedSize = be.FileSize; - e = be; - } - e.Name = name; - e.NameLower = name?.ToLowerInvariant(); - e.NameHash = JenkHash.GenHash(e.NameLower); - e.ShortNameHash = JenkHash.GenHash(Path.GetFileNameWithoutExtension(e.NameLower)); - e.Path = path; - return e; - } - private Form FindExistingForm(RpfFileEntry e) { if (e == null) return null; var allforms = Application.OpenForms; - var path = e.Path.ToLowerInvariant(); + var path = e.Path; foreach (var form in allforms) { - var metaform = form as MetaForm; - if (metaform?.rpfFileEntry == e) return metaform; - if (metaform?.rpfFileEntry?.Path?.ToLowerInvariant() == path) - return metaform; //need to test the path as well since the file entry may have been replaced by a new version..! - + if (form is MetaForm metaForm) + { + if (metaForm.rpfFileEntry == e) return metaForm; + if (metaForm.rpfFileEntry?.Path?.Equals(path, StringComparison.OrdinalIgnoreCase) ?? false) + return metaForm; //need to test the path as well since the file entry may have been replaced by a new version..! + } } return null; } @@ -1986,7 +2010,7 @@ namespace CodeWalker private void EnsureEditModeWarning() { - bool mods = CurrentFolder.Path.ToLowerInvariant().StartsWith("mods"); + bool mods = CurrentFolder.Path.StartsWith("mods", StringComparison.OrdinalIgnoreCase); bool srch = CurrentFolder?.IsSearchResults ?? false; bool fsys = CurrentFolder?.RpfFolder == null; bool game = CurrentFolder?.Path != CurrentFolder?.FullPath; @@ -2109,13 +2133,21 @@ namespace CodeWalker var idx = MainListView.SelectedIndices[0]; if ((idx < 0) || (idx >= CurrentFiles.Count)) return; var file = CurrentFiles[idx]; - var nl = file?.File?.NameLower ?? file?.Name?.ToLowerInvariant(); + var nl = file?.File?.Name ?? file?.Name; if (!string.IsNullOrEmpty(nl)) { - needfolder = nl.EndsWith(".ytd") || nl.EndsWith(".ydr") || nl.EndsWith(".ydd") || nl.EndsWith(".yft") || nl.EndsWith(".ypt") || nl.EndsWith(".awc") || nl.EndsWith(".fxc"); + needfolder = nl.EndsWith(".ytd", StringComparison.OrdinalIgnoreCase) + || nl.EndsWith(".ydr", StringComparison.OrdinalIgnoreCase) + || nl.EndsWith(".ydd", StringComparison.OrdinalIgnoreCase) + || nl.EndsWith(".yft", StringComparison.OrdinalIgnoreCase) + || nl.EndsWith(".ypt", StringComparison.OrdinalIgnoreCase) + || nl.EndsWith(".awc", StringComparison.OrdinalIgnoreCase) + || nl.EndsWith(".fxc", StringComparison.OrdinalIgnoreCase); } } + var refreshNeeded = false; + if ((MainListView.SelectedIndices.Count == 1) && (!needfolder)) { var idx = MainListView.SelectedIndices[0]; @@ -2138,7 +2170,7 @@ namespace CodeWalker { //this should only happen when opening a file from filesystem... var name = new FileInfo(file.FullPath).Name; - fentry = CreateFileEntry(name, file.FullPath, ref data); + fentry = RpfFile.CreateFileEntry(name, file.FullPath, ref data); } string xml = MetaXml.GetXml(fentry, data, out newfn); @@ -2155,6 +2187,12 @@ namespace CodeWalker try { File.WriteAllText(path, xml); + if (CurrentFolder.FullPath.Equals(Path.GetDirectoryName(path), StringComparison.OrdinalIgnoreCase)) + { + CurrentFolder.EnsureFile(path); + refreshNeeded = true; + } + } catch (Exception ex) { @@ -2193,7 +2231,7 @@ namespace CodeWalker { //this should only happen when opening a file from filesystem... var name = new FileInfo(file.FullPath).Name; - fentry = CreateFileEntry(name, file.FullPath, ref data); + fentry = RpfFile.CreateFileEntry(name, file.FullPath, ref data); } string xml = MetaXml.GetXml(fentry, data, out newfn, folderpath); @@ -2207,6 +2245,11 @@ namespace CodeWalker try { File.WriteAllText(path, xml); + if (CurrentFolder.FullPath.Equals(Path.GetDirectoryName(path), StringComparison.OrdinalIgnoreCase)) + { + CurrentFolder.EnsureFile(path); + refreshNeeded = true; + } } catch (Exception ex) { @@ -2221,6 +2264,10 @@ namespace CodeWalker MessageBox.Show("Errors were encountered:\n" + errstr); } } + + if (refreshNeeded) { + RefreshMainListView(); + } } private void ExtractRaw() { @@ -2504,7 +2551,7 @@ namespace CodeWalker } if (!IsFilenameOk(fname)) return; //new name contains invalid char(s). don't do anything - if (!fname.ToLowerInvariant().EndsWith(".rpf")) + if (!fname.EndsWith(".rpf", StringComparison.OrdinalIgnoreCase)) { fname = fname + ".rpf";//make sure it ends with .rpf } @@ -2541,7 +2588,7 @@ namespace CodeWalker if (newrpf != null) { var node = CreateRpfTreeFolder(newrpf, newrpf.Path, GTAFolder.GetCurrentGTAFolderWithTrailingSlash() + newrpf.Path); - RecurseMainTreeViewRPF(node, AllRpfs); + RecurseMainTreeViewRPF(node, new ConcurrentBag(AllRpfs)); AddNewFolderTreeNode(node); } @@ -2558,7 +2605,7 @@ namespace CodeWalker } if (!IsFilenameOk(fname)) return; //new name contains invalid char(s). don't do anything - if (!fname.ToLowerInvariant().EndsWith(".ytd")) + if (!fname.EndsWith(".ytd", StringComparison.OrdinalIgnoreCase)) { fname = fname + ".ytd";//make sure it ends with .ytd } @@ -2614,10 +2661,9 @@ namespace CodeWalker var fi = new FileInfo(fpath); var fname = fi.Name; - var fnamel = fname.ToLowerInvariant(); var trimlength = 4; - if (!fnamel.EndsWith(".fbx")) + if (!fname.EndsWith(".fbx", StringComparison.OrdinalIgnoreCase)) { MessageBox.Show(fname + ": Not an FBX file!", "Cannot import FBX"); continue; @@ -2687,6 +2733,20 @@ namespace CodeWalker if (!EnsureRpfValidEncryption() && (CurrentFolder.RpfFolder != null)) return; + if (MainListView.SelectedIndices.Count > 0) + { + var paths = new List(); + for (int i = 0; i < MainListView.SelectedIndices.Count; i++) + { + var idx = MainListView.SelectedIndices[i]; + if ((idx < 0) || (idx >= CurrentFiles.Count)) continue; + + paths.Add(CurrentFiles[idx].FullPath); + } + ImportXml(paths.ToArray()); + return; + } + OpenFileDialog.Filter = "XML Files|*.xml"; if (OpenFileDialog.ShowDialog(this) != DialogResult.OK) return; ImportXml(OpenFileDialog.FileNames); @@ -2706,20 +2766,18 @@ namespace CodeWalker var fi = new FileInfo(fpath); var fname = fi.Name; - var fnamel = fname.ToLowerInvariant(); var fpathin = fpath; - if (!fnamel.EndsWith(".xml")) + if (!fname.EndsWith(".xml", StringComparison.OrdinalIgnoreCase)) { MessageBox.Show(fname + ": Not an XML file!", "Cannot import XML"); continue; } var trimlength = 4; - var mformat = XmlMeta.GetXMLFormat(fnamel, out trimlength); + var mformat = XmlMeta.GetXMLFormat(fname, out trimlength); fname = fname.Substring(0, fname.Length - trimlength); - fnamel = fnamel.Substring(0, fnamel.Length - trimlength); fpathin = fpathin.Substring(0, fpathin.Length - trimlength); fpathin = Path.Combine(Path.GetDirectoryName(fpathin), Path.GetFileNameWithoutExtension(fpathin)); @@ -2926,7 +2984,7 @@ namespace CodeWalker { Clipboard.SetText(fnames.ToString()); } - UpdateStatus(CopiedFiles.Count.ToString() + " item" + ((CopiedFiles.Count != 1) ? "s" : "") + " copied"); + UpdateStatus?.Invoke(CopiedFiles.Count.ToString() + " item" + ((CopiedFiles.Count != 1) ? "s" : "") + " copied"); } private void CopyPath() { @@ -3021,7 +3079,7 @@ namespace CodeWalker //renaming a filesystem item... var dirinfo = new DirectoryInfo(item.FullPath); var newpath = Path.Combine(dirinfo.Parent.FullName, newname); - if (item.FullPath.ToLowerInvariant() == newpath.ToLowerInvariant()) + if (item.FullPath.Equals(newpath, StringComparison.OrdinalIgnoreCase)) { return;//filesystem tends to be case-insensitive... paths are the same } @@ -3281,13 +3339,14 @@ namespace CodeWalker Task.Run(() => { - RefreshMainTreeViewRoot(root); + var node = RefreshMainTreeViewRoot(root); Invoke(new Action(() => { + MainTreeView.Nodes.Add(node); MainTreeView.SelectedNode = root.TreeNode; })); - }); + }, CancellationTokenSource.Token); } private void CloseFolder(MainTreeFolder folder) { @@ -3411,15 +3470,26 @@ namespace CodeWalker } - + [MethodImpl(MethodImplOptions.AggressiveInlining)] protected override void WndProc(ref Message m) { //handle back/forward buttons globally for all the form if (m.Msg == 0x319) //WM_APPCOMMAND { var cmd = (m.LParam.ToInt64() >> 16) & 0xFFF; - if (cmd == 1) GoBack(); //APPCOMMAND_BROWSER_BACKWARD - if (cmd == 2) GoForward(); //APPCOMMAND_BROWSER_FORWARD + if (cmd == 1) + { + GoBack(); //APPCOMMAND_BROWSER_BACKWARD + } + else if (cmd == 2) + { + GoForward(); //APPCOMMAND_BROWSER_FORWARD + } + } + else if (m.Msg == 0x10) // WM_CLOSE + { + ConsoleWindow.Close(); + CancellationTokenSource.Cancel(); } base.WndProc(ref m); } @@ -3800,12 +3870,13 @@ namespace CodeWalker private void RefreshButton_Click(object sender, EventArgs e) { - Task.Run(() => + if (!Ready) return; + Task.Run(async () => { - RefreshMainTreeView(); + await RefreshMainTreeView(); - UpdateStatus("Scan complete."); - }); + UpdateStatus?.Invoke("Scan complete."); + }, CancellationTokenSource.Token); } private void EditModeButton_Click(object sender, EventArgs e) @@ -4109,6 +4180,11 @@ namespace CodeWalker SetView(System.Windows.Forms.View.List); } + private void ViewConsoleMenu_Click(object sender, EventArgs e) + { + ConsoleWindow.Show(); + } + private void ViewDetailsMenu_Click(object sender, EventArgs e) { SetView(System.Windows.Forms.View.Details); @@ -4189,13 +4265,28 @@ namespace CodeWalker public class MainTreeFolder { public string Name { get; set; } + + private string _nameLower; + public string NameLower + { + get + { + return _nameLower ??= Name.ToLowerInvariant(); + } + set + { + _nameLower = value; + } + } public string Path { get; set; } public string FullPath { get; set; } public RpfFile RpfFile { get; set; } public RpfDirectoryEntry RpfFolder { get; set; } public List Files { get; set; } + private object filesLock = new object(); public MainTreeFolder Parent { get; set; } public List Children { get; set; } + private object childrenLock = new object(); public List ListItems { get; set; } public TreeNode TreeNode { get; set; } public bool IsSearchResults { get; set; } @@ -4204,15 +4295,32 @@ namespace CodeWalker public void AddFile(string file) { - if (Files == null) Files = new List(); - Files.Add(file); + lock (filesLock) + { + if (Files == null) Files = new (); + Files.Add(file); + } } public void AddChild(MainTreeFolder child) { - if (Children == null) Children = new List(); - Children.Add(child); - child.Parent = this; + lock(childrenLock) + { + if (Children == null) Children = new List(); + Children.Add(child); + child.Parent = this; + } } + + public void RemoveChild(MainTreeFolder child) { + if (Children == null) return; + + lock(childrenLock) + { + Children.Remove(child); + child.Parent = null; + } + } + public void AddChildToHierarchy(MainTreeFolder child) { var relpath = child.Path.Replace(Path + "\\", ""); @@ -4248,9 +4356,9 @@ namespace CodeWalker } public void RemoveFile(string file) { - if (Files != null) + lock(filesLock) { - Files.Remove(file); + Files?.Remove(file); } } @@ -4270,16 +4378,19 @@ namespace CodeWalker } if (Files != null) { - foreach (var file in Files) + lock(filesLock) { - ListItems.Add(new MainListItem(file, rootpath, this)); + foreach (var file in Files) + { + ListItems.Add(new MainListItem(file, rootpath, this)); + } } } if ((RpfFolder != null) && (RpfFolder.Files != null)) { foreach (var file in RpfFolder.Files) { - if (file.NameLower.EndsWith(".rpf")) continue; //RPF files are already added.. + if (file.Name.EndsWith(".rpf", StringComparison.OrdinalIgnoreCase)) continue; //RPF files are already added.. ListItems.Add(new MainListItem(file, rootpath, this)); } } @@ -4295,7 +4406,7 @@ namespace CodeWalker { foreach (var file in RpfFolder.Files) { - if (file.NameLower.EndsWith(".rpf")) continue; //RPF files are already added.. + if (file.Name.EndsWith(".rpf", StringComparison.OrdinalIgnoreCase)) continue; //RPF files are already added.. ic++; } } @@ -4313,9 +4424,9 @@ namespace CodeWalker int resultcount = 0; //if (!form.Searching) return resultcount; - form.UpdateStatus("Searching " + Path + "..."); + form.InvokeUpdateStatus("Searching " + Path + "..."); - if (Name.ToLowerInvariant().Contains(term)) + if (Name.Contains(term, StringComparison.OrdinalIgnoreCase)) { form.AddSearchResult(new MainListItem(this)); resultcount++; @@ -4329,7 +4440,7 @@ namespace CodeWalker { //if (!form.Searching) return resultcount; var fi = new FileInfo(file); - if (fi.Name.ToLowerInvariant().Contains(term)) + if (fi.Name.Contains(term, StringComparison.OrdinalIgnoreCase)) { form.AddSearchResult(new MainListItem(file, rootpath, this)); resultcount++; @@ -4341,8 +4452,8 @@ namespace CodeWalker foreach (var file in RpfFolder.Files) { //if (!form.Searching) return resultcount; - if (file.NameLower.EndsWith(".rpf")) continue; //don't search rpf files.. - if (file.NameLower.Contains(term)) + if (file.Name.EndsWith(".rpf", StringComparison.OrdinalIgnoreCase)) continue; //don't search rpf files.. + if (file.Name.Contains(term, StringComparison.OrdinalIgnoreCase)) { form.AddSearchResult(new MainListItem(file, rootpath, this)); resultcount++; diff --git a/CodeWalker/ExploreForm.resx b/CodeWalker/ExploreForm.resx index 835a9b5..6c8152e 100644 --- a/CodeWalker/ExploreForm.resx +++ b/CodeWalker/ExploreForm.resx @@ -124,7 +124,7 @@ iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO - xAAADsQBlSsOGwAAAQFJREFUOE+dkz0LglAUhv0H/ZLA3+De1FrQ4NJkY9Du3FRLEAZuRVNQS2FLRDQE + vwAADr8BOAVTJAAAAQFJREFUOE+dkz0LglAUhv0H/ZLA3+De1FrQ4NJkY9Du3FRLEAZuRVNQS2FLRDQE UWu0BBX4Mbie7nvxmslNrQMPXI/nfTgXUUlXGIaUgctQo1F5YVDTNCmmaeZLsgQgV5IUeK7/QSFJEUFS EsXelSUQiPeYjWLvQtMwDNJ1XRoGXwWsMTxfH9Sxt5zT5U6+FxQTsIfGYn+hUt2itrWhZt9h5xGXFBVM K+acerNDPAgJNklv8U2wgsBenuJBbAJB4OcI2EFluBCUW2MuwSa4jrM7xoE0XCDCte6SlOqAIBFM1kf+ @@ -134,7 +134,7 @@ iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO - wwAADsMBx2+oZAAAAGlJREFUOE/ljlsKgEAIRV1Sa3BxbncKxN9CUKbCdIboqwsHxMdB8IjIntCYebHV + vgAADr4B6kKxwAAAAGlJREFUOE/ljlsKgEAIRV1Sa3BxbncKxN9CUKbCdIboqwsHxMdB8IjIntCYebHV OLqIiCFEVEsygVJKzoK1bReGJHdBVCsusbOe7APH57prZz1PH0S9IUHElMDrqPeHDyq+E8xgZ28DcADi +uZiNACH5wAAAABJRU5ErkJggg== @@ -142,7 +142,7 @@ iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO - wwAADsMBx2+oZAAAAKZJREFUOE/Nk0sOwyAMRDlSz8CFOEQqtsldOBitYrFNNa1NXMQnCxaxNIplzzxE + vgAADr4B6kKxwAAAAKZJREFUOE/Nk0sOwyAMRDlSz8CFOEQqtsldOBitYrFNNa1NXMQnCxaxNIplzzxE ophplVI6OopE9GBrvWC01lblvR9DegBoCNGAV3z/6RLkCkBDOHZWDyCSPbwcO+segBDCd1lqCMBbFfO2 btmIHjPaqQ3gcJTFsjzziegxww6QKkDC/HmySZuxQ48ryozjEwDlFZxzOYxeoHhqcfxXAilNSo1/wJgP rxwb6AQLOW8AAAAASUVORK5CYII= @@ -151,7 +151,7 @@ iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO - xAAADsQBlSsOGwAAAKtJREFUOE/NkNENwjAMRDMSM2QFfpkn3wzQLfLflQqiyq/JmVzkthZFIKRaOtU6 + vwAADr8BOAVTJAAAAKtJREFUOE/NkNENwjAMRDMSM2QFfpkn3wzQLfLflQqiyq/JmVzkthZFIKRaOtU6 3z0pDSLyk0Ip5RTq1K9YNU9vb6eGJgS1dB5U2OmtpNlWfY05rAHqxRi7UkpbCEKX66glC6BnAS6ERRQI 4I7bbbp3uRAstmwh82N2ARYCAH7WAoK9eouyFSHI8BkdAuWcNeCVoQ0AQwhMBrwy5AIwDfI9AHMswJ7+ B/hcRZ4ta4FRmq6ouAAAAABJRU5ErkJggg== @@ -160,7 +160,7 @@ iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO - xAAADsQBlSsOGwAAAJVJREFUOE+1kNEJwzAMRDNSZvBCHsLFv8068VJpf/yr+oSckuQCVUMMD8Tp/MAe + vwAADr8BOAVTJAAAAJVJREFUOE+1kNEJwzAMRDNSZvBCHsLFv8068VJpf/yr+oSckuQCVUMMD8Tp/MAe ROQSNPRAQw809PAd7NRax8bSkB3IRqtxAQpWlOk5SZmLghmZ7VRyEGCBQs5Zyyk95LW8FczIbKcSJtDL XRBC2NAFXXKLYPOEGON6GXMXoIPuQbD/xBPOP/FfaOiBhh5o6IGGvyPDB6ug8Uv1Fi1pAAAAAElFTkSu QmCC @@ -169,7 +169,7 @@ iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO - xAAADsQBlSsOGwAAAJ5JREFUOE/VkcEJwzAMRTNSZ/AKuWYenztAIZtkJbcp+OrkK5L5qIbEhxwqeBjZ + vwAADr8BOAVTJAAAAJ5JREFUOE/VkcEJwzAMRTNSZ/AKuWYenztAIZtkJbcp+OrkK5L5qIbEhxwqeBjZ +s9gD1w55xlo21caLkqfxMLTcxFI5Ek7D40dtW/U8DC+BJPEGEsIoYL+R+LDXsKCpgRDFuAwVpy906fS lGDIbjMB77UELBGBwQKDBV4kGR2Uh2SB3nBJIH+OhsHAun7PBb4sDFph8EeCM+4TXCeXDdS/V/ydKVLV AAAAAElFTkSuQmCC @@ -178,7 +178,7 @@ iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO - wwAADsMBx2+oZAAAAIhJREFUOE+9jlEKgCAQRD1KR+gI4Vm8u58WLfpZCbNU22oW0cJD1JnHGm1SSkOB + vgAADr4B6kKxwAAAAIhJREFUOE+9jlEKgCAQRD1KR+gI4Vm8u58WLfpZCbNU22oW0cJD1JnHGm1SSkOB DpH6xBgXDe99IKIesfLksLX2gnNOl8hVWTCG6URRclxTIgUs2f4C6vWVpYRF+R31XaAFNckjAUtoptN7 k4Dhgryj/qMgn8wrgQT1dgHi12FBjVtBC4h/OcasZcFBjvFV4nkAAAAASUVORK5CYII= @@ -186,14 +186,14 @@ iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO - wwAADsMBx2+oZAAAAFBJREFUOE9joBh8+/btPz4MVYYbgBQ5OTmBMUwTMh+qDDegigHYNBKL4S6A0chs + vgAADr4B6kKxwAAAAFBJREFUOE9joBh8+/btPz4MVYYbgBQ5OTmBMUwTMh+qDDegigHYNBKL4S6A0chs YmjauYAQhqkbTi5A1ojMJ0QTNIAQRvECNgxNb4MWMDAAAPsOTf9dAGOQAAAAAElFTkSuQmCC iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO - xAAADsQBlSsOGwAAAVdJREFUOE+lk7FKw1AUhn0BwQdwcHJwEpzcsjkJmZ06+AABfYDqA1h3h+AiHQrS + vwAADr8BOAVTJAAAAVdJREFUOE+lk7FKw1AUhn0BwQdwcHJwEpzcsjkJmZ06+AABfYDqA1h3h+AiHQrS xUkoOjiIEFwERSgOtoqDdcl6/b+bc9OmJHbwhx+Sc/7z33NyT5acc/9iiTzP18S2mIgrFq6AuJiKA2Mr JKKfz7F7PDv11PtQrJjoPZ58jL4fTo7d2+01mqzU6CGl8Hx92fPu6ADBpeU4tTPK7l0v2nD93W2HkWKb vhjMG0A7hZGGT93UXWytemKkWGylBRTwI+AeDBATo5s508TKqlCiVWcSnulCmtTk9agzgTeH+xRPP1oT @@ -267,7 +267,7 @@ iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO - xAAADsQBlSsOGwAAANNJREFUOE+lUjEOgzAM5El9A3skRthZy0yZOvE9vpCh7UCGtAtr6otslIgENepJ + vwAADr8BOAVTJAAAANNJREFUOE+lUjEOgzAM5El9A3skRthZy0yZOvE9vpCh7UCGtAtr6otslIgENepJ J2TfnZ0A1Rm2bWuIC9EFRN2wJQ8y3bXWbpomV9f1TtToQ2frEdgAk1IqCgvR5yHpk5CwyOZxvLnn4+Xe 9uOfw3XwfejwcSQGCfs2CQtRiwYfR2LgeGIKw8K2bc8H/HUCal4g9H3vTUXvgMN2nud9S4rJr5ALd10X 1cn/IBfGNYwx/q4B4z+Rimx4XVcLna1ppAb8HBaEQ4rDAhnCLAsLeEhBuKq+87Osda2nLc8AAAAASUVO @@ -277,7 +277,7 @@ iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO - xAAADsQBlSsOGwAAAKJJREFUOE/VjLENAjEQBL8ESqEE5NxdfAFU4VYcUocjWnDu4DOnhwdZJyQfFoaI + vwAADr8BOAVTJAAAAKJJREFUOE/VjLENAjEQBL8ESqEE5NxdfAFU4VYcUocjWnDu4DOnhwdZJyQfFoaI lVY67e7NJiI/2QxXrEetVVZsAY5938U5NzUbthbgknMW7735iOnYsB0AqBXXlJL5jOnYsDUBqA1uMcbh mYyuz6aAU/M9hKDP3GR0ffYegNrwXEpRADdZr5+aAlB7UAB3j1V/Anh1j1UD4Fub4YrN8HPL9gAVE1vf J6IiRgAAAABJRU5ErkJggg== @@ -304,7 +304,7 @@ AAEAAAD/////AQAAAAAAAAAMAgAAAFdTeXN0ZW0uV2luZG93cy5Gb3JtcywgVmVyc2lvbj00LjAuMC4w LCBDdWx0dXJlPW5ldXRyYWwsIFB1YmxpY0tleVRva2VuPWI3N2E1YzU2MTkzNGUwODkFAQAAACZTeXN0 ZW0uV2luZG93cy5Gb3Jtcy5JbWFnZUxpc3RTdHJlYW1lcgEAAAAERGF0YQcCAgAAAAkDAAAADwMAAADo - HwAAAk1TRnQBSQFMAgEBGAEAAdABAQHQAQEBEAEAARABAAT/AQkBAAj/AUIBTQE2AQQGAAE2AQQCAAEo + HwAAAk1TRnQBSQFMAgEBGAEAAfgBAQH4AQEBEAEAARABAAT/AQkBAAj/AUIBTQE2AQQGAAE2AQQCAAEo AwABQAMAAXADAAEBAQABCAYAARwYAAGAAgABgAMAAoABAAGAAwABgAEAAYABAAKAAgADwAEAAcAB3AHA AQAB8AHKAaYBAAEzBQABMwEAATMBAAEzAQACMwIAAxYBAAMcAQADIgEAAykBAANVAQADTQEAA0IBAAM5 AQABgAF8Af8BAAJQAf8BAAGTAQAB1gEAAf8B7AHMAQABxgHWAe8BAAHWAucBAAGQAakBrQIAAf8BMwMA @@ -446,7 +446,7 @@ iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO - xAAADsQBlSsOGwAAAJtJREFUOE+Vj8ENgCAMRdnE1ZzLCwt5cQJvjkDiRfyfUIJQEJo8U2rof5hWOedW + vwAADr8BOAVTJAAAAJtJREFUOE+Vj8ENgCAMRdnE1ZzLCwt5cQJvjkDiRfyfUIJQEJo8U2rof5hWOedW 4CNrHI8XLp3PsXjCPo7Hion3tXm/mwD7KQtJlwVTFmW6MGyRp+MYYD9kUaZjlBaQX4s8XVvQtSjTtQWk aVGmE4yrBaqFlt6jstDSCX5VBuRj0UtvLSDJopX+R7LAx868X4gGVp5hAQcz4LIxLycs8rg+vnkMAAAA AElFTkSuQmCC @@ -455,7 +455,7 @@ iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO - xAAADsQBlSsOGwAAALVJREFUOE/dk70NQjEMhLMHDZuRBZiAadgD6YmGNjNAhaB4okkb/IEtvfgZIaDj + vwAADr8BOAVTJAAAALVJREFUOE/dk70NQjEMhLMHDZuRBZiAadgD6YmGNjNAhaB4okkb/IEtvfgZIaDj pCvi853y4ySPWmsWDsLmSC1r2xwiLoVlON7aandui+2pIzU0euhV2xNqHtf7y8zouTlcCRm7EFkUBN9s 8HUNKWbObM03QUOk6XEyAUN05nfEg5eAsAEaIg3i/ZOAl5doiLTpJf72jDoLJZpCg693gwRk8RjlaBo9 w1EGGvLdZ5pCxA++c0p3WGOjVX9N2kUAAAAASUVORK5CYII= @@ -467,7 +467,7 @@ iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO - xAAADsQBlSsOGwAAAQFJREFUOE+dkz0LglAUhv0H/ZLA3+De1FrQ4NJkY9Du3FRLEAZuRVNQS2FLRDQE + vwAADr8BOAVTJAAAAQFJREFUOE+dkz0LglAUhv0H/ZLA3+De1FrQ4NJkY9Du3FRLEAZuRVNQS2FLRDQE UWu0BBX4Mbie7nvxmslNrQMPXI/nfTgXUUlXGIaUgctQo1F5YVDTNCmmaeZLsgQgV5IUeK7/QSFJEUFS EsXelSUQiPeYjWLvQtMwDNJ1XRoGXwWsMTxfH9Sxt5zT5U6+FxQTsIfGYn+hUt2itrWhZt9h5xGXFBVM K+acerNDPAgJNklv8U2wgsBenuJBbAJB4OcI2EFluBCUW2MuwSa4jrM7xoE0XCDCte6SlOqAIBFM1kf+ @@ -477,7 +477,7 @@ iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO - wwAADsMBx2+oZAAAAGlJREFUOE/ljlsKgEAIRV1Sa3BxbncKxN9CUKbCdIboqwsHxMdB8IjIntCYebHV + vgAADr4B6kKxwAAAAGlJREFUOE/ljlsKgEAIRV1Sa3BxbncKxN9CUKbCdIboqwsHxMdB8IjIntCYebHV OLqIiCFEVEsygVJKzoK1bReGJHdBVCsusbOe7APH57prZz1PH0S9IUHElMDrqPeHDyq+E8xgZ28DcADi +uZiNACH5wAAAABJRU5ErkJggg== @@ -485,7 +485,7 @@ iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO - wwAADsMBx2+oZAAAAKZJREFUOE/Nk0sOwyAMRDlSz8CFOEQqtsldOBitYrFNNa1NXMQnCxaxNIplzzxE + vgAADr4B6kKxwAAAAKZJREFUOE/Nk0sOwyAMRDlSz8CFOEQqtsldOBitYrFNNa1NXMQnCxaxNIplzzxE ophplVI6OopE9GBrvWC01lblvR9DegBoCNGAV3z/6RLkCkBDOHZWDyCSPbwcO+segBDCd1lqCMBbFfO2 btmIHjPaqQ3gcJTFsjzziegxww6QKkDC/HmySZuxQ48ryozjEwDlFZxzOYxeoHhqcfxXAilNSo1/wJgP rxwb6AQLOW8AAAAASUVORK5CYII= @@ -494,7 +494,7 @@ iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO - xAAADsQBlSsOGwAAAKtJREFUOE/NkNENwjAMRDMSM2QFfpkn3wzQLfLflQqiyq/JmVzkthZFIKRaOtU6 + vwAADr8BOAVTJAAAAKtJREFUOE/NkNENwjAMRDMSM2QFfpkn3wzQLfLflQqiyq/JmVzkthZFIKRaOtU6 3z0pDSLyk0Ip5RTq1K9YNU9vb6eGJgS1dB5U2OmtpNlWfY05rAHqxRi7UkpbCEKX66glC6BnAS6ERRQI 4I7bbbp3uRAstmwh82N2ARYCAH7WAoK9eouyFSHI8BkdAuWcNeCVoQ0AQwhMBrwy5AIwDfI9AHMswJ7+ B/hcRZ4ta4FRmq6ouAAAAABJRU5ErkJggg== @@ -503,7 +503,7 @@ iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO - xAAADsQBlSsOGwAAAJVJREFUOE+1kNEJwzAMRDNSZvBCHsLFv8068VJpf/yr+oSckuQCVUMMD8Tp/MAe + vwAADr8BOAVTJAAAAJVJREFUOE+1kNEJwzAMRDNSZvBCHsLFv8068VJpf/yr+oSckuQCVUMMD8Tp/MAe ROQSNPRAQw809PAd7NRax8bSkB3IRqtxAQpWlOk5SZmLghmZ7VRyEGCBQs5Zyyk95LW8FczIbKcSJtDL XRBC2NAFXXKLYPOEGON6GXMXoIPuQbD/xBPOP/FfaOiBhh5o6IGGvyPDB6ug8Uv1Fi1pAAAAAElFTkSu QmCC @@ -512,7 +512,7 @@ iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO - xAAADsQBlSsOGwAAAJ5JREFUOE/VkcEJwzAMRTNSZ/AKuWYenztAIZtkJbcp+OrkK5L5qIbEhxwqeBjZ + vwAADr8BOAVTJAAAAJ5JREFUOE/VkcEJwzAMRTNSZ/AKuWYenztAIZtkJbcp+OrkK5L5qIbEhxwqeBjZ +s9gD1w55xlo21caLkqfxMLTcxFI5Ek7D40dtW/U8DC+BJPEGEsIoYL+R+LDXsKCpgRDFuAwVpy906fS lGDIbjMB77UELBGBwQKDBV4kGR2Uh2SB3nBJIH+OhsHAun7PBb4sDFph8EeCM+4TXCeXDdS/V/ydKVLV AAAAAElFTkSuQmCC @@ -521,7 +521,7 @@ iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO - wwAADsMBx2+oZAAAAIhJREFUOE+9jlEKgCAQRD1KR+gI4Vm8u58WLfpZCbNU22oW0cJD1JnHGm1SSkOB + vgAADr4B6kKxwAAAAIhJREFUOE+9jlEKgCAQRD1KR+gI4Vm8u58WLfpZCbNU22oW0cJD1JnHGm1SSkOB DpH6xBgXDe99IKIesfLksLX2gnNOl8hVWTCG6URRclxTIgUs2f4C6vWVpYRF+R31XaAFNckjAUtoptN7 k4Dhgryj/qMgn8wrgQT1dgHi12FBjVtBC4h/OcasZcFBjvFV4nkAAAAASUVORK5CYII= @@ -529,14 +529,14 @@ iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO - wwAADsMBx2+oZAAAAFBJREFUOE9joBh8+/btPz4MVYYbgBQ5OTmBMUwTMh+qDDegigHYNBKL4S6A0chs + vgAADr4B6kKxwAAAAFBJREFUOE9joBh8+/btPz4MVYYbgBQ5OTmBMUwTMh+qDDegigHYNBKL4S6A0chs YmjauYAQhqkbTi5A1ojMJ0QTNIAQRvECNgxNb4MWMDAAAPsOTf9dAGOQAAAAAElFTkSuQmCC iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAIGNIUk0AAHolAACAgwAA+f8AAIDpAAB1 - MAAA6mAAADqYAAAXb5JfxUYAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAFjSURBVDhPpVOxSsNQFM0PCP2A + MAAA6mAAADqYAAAXb5JfxUYAAAAJcEhZcwAADr8AAA6/ATgFUyQAAAFjSURBVDhPpVOxSsNQFM0PCP2A Dp0cOgmd3Lo5CU4OTh0E14B+QPQDrLtDcBEHRRx0KgQdFKQQXARRENFGxeHpkvV5zs195kUSHXrg0Mc9 956c5N0GRJ7nLTAGLWjAUIRfQD3SHsclJ4RPFyO732sLs/S6FAGcO2B6s7tj92Zn7P3pEfVIZWlIxttb IpIni/P28y1jEiYLeT7fWBWNfagd62gBNjGBMyAvN9fZaD4e7sSQNZqgloItHS3AAoXbg7hiwqfxlXj2 @@ -551,7 +551,7 @@ iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO - wwAADsMBx2+oZAAAAIhJREFUOE+9jlEKgCAQRD1KR+gI4Vm8u58WLfpZCbNU22oW0cJD1JnHGm1SSkOB + vgAADr4B6kKxwAAAAIhJREFUOE+9jlEKgCAQRD1KR+gI4Vm8u58WLfpZCbNU22oW0cJD1JnHGm1SSkOB DpH6xBgXDe99IKIesfLksLX2gnNOl8hVWTCG6URRclxTIgUs2f4C6vWVpYRF+R31XaAFNckjAUtoptN7 k4Dhgryj/qMgn8wrgQT1dgHi12FBjVtBC4h/OcasZcFBjvFV4nkAAAAASUVORK5CYII= @@ -559,7 +559,7 @@ iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO - xAAADsQBlSsOGwAAALNJREFUOE9jIAS+fftmAMTvgfg/NgxVhh0AFYA1t7W1/XdycsLAKAbATETHIM3N + vwAADr8BOAVTJAAAALNJREFUOE9jIAS+fftmAMTvgfg/NgxVhh0AFYA1t7W1/XdycsLAKAbATETHIM3N zS3/P7z/iIKxGoBuAwgja4aJ4TUAppgQpp4BIAKGyTYAxCEWYzPgfH5+PoZCXPjokaMYBjQsX74cQyEu 3NfXj2GAwZUrVzAU4sLJScmoBkBj4H1kZCSGYlz41s3bYBrZgPm4kis2vH7dejCNbEDCvn37MBQSwsgG CIA45GCG//8pwf8ZANa5gGyReLItAAAAAElFTkSuQmCC diff --git a/CodeWalker/Forms/MetaForm.cs b/CodeWalker/Forms/MetaForm.cs index 47b1826..941aeb4 100644 --- a/CodeWalker/Forms/MetaForm.cs +++ b/CodeWalker/Forms/MetaForm.cs @@ -185,7 +185,7 @@ namespace CodeWalker.Forms if (string.IsNullOrEmpty(FileName)) saveAs = true; if (string.IsNullOrEmpty(FilePath)) saveAs = true; - else if ((FilePath.ToLowerInvariant().StartsWith(GTAFolder.CurrentGTAFolder.ToLowerInvariant()))) saveAs = true; + else if (FilePath.StartsWith(GTAFolder.CurrentGTAFolder, StringComparison.OrdinalIgnoreCase)) saveAs = true; if (!File.Exists(FilePath)) saveAs = true; var fn = FilePath; @@ -456,7 +456,7 @@ namespace CodeWalker.Forms if (rpfFileEntry?.Parent != null) { - if (!rpfFileEntry.Path.ToLowerInvariant().StartsWith("mods")) + if (!rpfFileEntry.Path.StartsWith("mods", StringComparison.OrdinalIgnoreCase)) { if (MessageBox.Show("This file is NOT located in the mods folder - Are you SURE you want to save this file?\r\nWARNING: This could cause permanent damage to your game!!!", "WARNING: Are you sure about this?", MessageBoxButtons.YesNo) != DialogResult.Yes) { diff --git a/CodeWalker/Forms/ModelForm.Designer.cs b/CodeWalker/Forms/ModelForm.Designer.cs index f87b728..09b04a1 100644 --- a/CodeWalker/Forms/ModelForm.Designer.cs +++ b/CodeWalker/Forms/ModelForm.Designer.cs @@ -1,4 +1,6 @@ -namespace CodeWalker.Forms +using CodeWalker.WinForms; + +namespace CodeWalker.Forms { partial class ModelForm { diff --git a/CodeWalker/Forms/ModelForm.cs b/CodeWalker/Forms/ModelForm.cs index 37965d9..a89cd96 100644 --- a/CodeWalker/Forms/ModelForm.cs +++ b/CodeWalker/Forms/ModelForm.cs @@ -29,7 +29,6 @@ namespace CodeWalker.Forms volatile bool formopen = false; //volatile bool running = false; - volatile bool pauserendering = false; //volatile bool initialised = false; Stopwatch frametimer = new Stopwatch(); @@ -46,6 +45,8 @@ namespace CodeWalker.Forms System.Drawing.Point MouseLastPoint; bool MouseInvert = Settings.Default.MouseInvert; + int VerticesCount = 0; + Vector3 prevworldpos = new Vector3(0, 0, 0); //also the start pos @@ -69,6 +70,7 @@ namespace CodeWalker.Forms } } public string FilePath { get; set; } + public bool Pauserendering { get; set; } YdrFile Ydr = null; YddFile Ydd = null; @@ -128,13 +130,70 @@ namespace CodeWalker.Forms public ModelForm(ExploreForm ExpForm = null) { + if (this.DesignMode) return; InitializeComponent(); exploreForm = ExpForm; - gameFileCache = ExpForm?.GetFileCache(); + gameFileCache = ExpForm?.GetFileCache() ?? GameFileCacheFactory.Create(); + + if (ExpForm == null) + { + gameFileCache.EnableDlc = false; + gameFileCache.EnableMods = false; + gameFileCache.LoadPeds = false; + gameFileCache.LoadVehicles = false; + gameFileCache.LoadArchetypes = false;//to speed things up a little + gameFileCache.BuildExtendedJenkIndex = true;//to speed things up a little + gameFileCache.DoFullStringIndex = false;//to get all global text from DLC... + Task.Run(() => { + try + { + gameFileCache.Init(); + if (DetailsPropertyGrid.SelectedObject is DrawableBase drawableBase) + { + UpdateDrawableUI(drawableBase); + } + if (Yft != null) + { + UpdateDrawableUI(Yft.Fragment?.Drawable); + } + UpdateStatus("Done loading"); + } + catch(Exception ex) + { + Console.WriteLine(ex); + throw ex; + } + + }); + + Task.Run(() => + { + while (!IsDisposed) //run the file cache content thread until the form exits. + { + if (gameFileCache.IsInited) + { + gameFileCache.BeginFrame(); + + bool fcItemsPending = gameFileCache.ContentThreadProc(); + + if (!fcItemsPending) + { + Thread.Sleep(10); + } + } + else + { + Thread.Sleep(20); + } + } + }); + } + Renderer = new Renderer(this, gameFileCache); + Renderer.waitforchildrentoload = false; camera = Renderer.camera; timecycle = Renderer.timecycle; weather = Renderer.weather; @@ -159,7 +218,7 @@ namespace CodeWalker.Forms private void Init() { //called from ModelForm_Load - + if (this.DesignMode) return; if (!initedOk) { Close(); @@ -260,13 +319,21 @@ namespace CodeWalker.Forms public void BuffersResized(int w, int h) { Renderer.BuffersResized(w, h); + if (WindowState == FormWindowState.Minimized && gameFileCache.IsInited) + { + Console.WriteLine("Clearing cache"); + gameFileCache.Clear(); + gameFileCache.IsInited = true; + GC.Collect(); + } + } public void RenderScene(DeviceContext context) { float elapsed = (float)frametimer.Elapsed.TotalSeconds; frametimer.Restart(); - if (pauserendering) return; + if (Pauserendering) return; if (!Monitor.TryEnter(Renderer.RenderSyncRoot, 50)) { return; } //couldn't get a lock, try again next time @@ -627,6 +694,47 @@ namespace CodeWalker.Forms selectedLight.UpdateRenderable = true; } + public void ViewModel(string path) + { + var data = File.ReadAllBytes(path); + var fileEntry = RpfFile.CreateFileEntry(Path.GetFileName(path), path, ref data); + ViewModel(data, fileEntry); + } + + public void ViewModel(byte[] data, RpfFileEntry e) + { + var nl = e?.NameLower ?? ""; + var fe = Path.GetExtension(nl); + Show(); + switch (fe) + { + case ".ydr": + var ydr = RpfFile.GetFile(e, data); + LoadModel(ydr); + break; + case ".ydd": + var ydd = RpfFile.GetFile(e, data); + LoadModels(ydd); + break; + case ".yft": + var yft = RpfFile.GetFile(e, data); + LoadModel(yft); + break; + case ".ybn": + var ybn = RpfFile.GetFile(e, data); + LoadModel(ybn); + break; + case ".ypt": + var ypt = RpfFile.GetFile(e, data); + LoadParticles(ypt); + break; + case ".ynv": + var ynv = RpfFile.GetFile(e, data); + LoadNavmesh(ynv); + break; + } + } + private void RenderSingleItem() { @@ -794,10 +902,10 @@ namespace CodeWalker.Forms Yft = yft; rpfFileEntry = Yft.RpfFileEntry; ModelHash = Yft.RpfFileEntry?.ShortNameHash ?? 0; - var namelower = Yft.RpfFileEntry?.GetShortNameLower(); - if (namelower?.EndsWith("_hi") ?? false) + var namelower = Yft.RpfFileEntry?.ShortName; + if (namelower?.EndsWith("_hi", StringComparison.OrdinalIgnoreCase) ?? false) { - ModelHash = JenkHash.GenHash(namelower.Substring(0, namelower.Length - 3)); + ModelHash = JenkHash.GenHashLower(namelower.Substring(0, namelower.Length - 3)); } if (ModelHash != 0) { @@ -843,6 +951,7 @@ namespace CodeWalker.Forms UpdateBoundsUI(ybn); } + public void LoadParticles(YptFile ypt) { if (ypt == null) return; @@ -948,7 +1057,7 @@ namespace CodeWalker.Forms if (gameFileCache == null) return; if (!gameFileCache.IsInited) return;//what to do here? wait for it..? - var ycdhash = JenkHash.GenHash(name.ToLowerInvariant()); + var ycdhash = JenkHash.GenHashLower(name); var ycd = gameFileCache.GetYcd(ycdhash); while ((ycd != null) && (!ycd.Loaded)) { @@ -1097,19 +1206,15 @@ namespace CodeWalker.Forms } - - - - private void UpdateModelsUI(DrawableBase drawable, object detailsObject = null) + public void UpdateDrawableUI(DrawableBase drawable) { - DetailsPropertyGrid.SelectedObject = detailsObject ?? drawable; - DrawableDrawFlags.Clear(); Renderer.SelectionModelDrawFlags.Clear(); Renderer.SelectionGeometryDrawFlags.Clear(); ModelsTreeView.Nodes.Clear(); ModelsTreeView.ShowRootLines = false; TexturesTreeView.Nodes.Clear(); + if (drawable != null) { AddDrawableModelsTreeNodes(drawable.DrawableModels?.High, "High Detail", true); @@ -1162,6 +1267,15 @@ namespace CodeWalker.Forms } } } + + + private void UpdateModelsUI(DrawableBase drawable, object detailsObject = null) + { + DetailsPropertyGrid.SelectedObject = detailsObject ?? drawable; + + UpdateDrawableUI(drawable); + } + private void UpdateModelsUI(Dictionary dict) { //DetailsPropertyGrid.SelectedObject = dict; //this won't look good... @@ -1238,6 +1352,7 @@ namespace CodeWalker.Forms dnode.Tag = drawable; dnode.Checked = check; + VerticesCount = 0; AddDrawableModelsTreeNodes(drawable.DrawableModels?.High, "High Detail", true, dnode); AddDrawableModelsTreeNodes(drawable.DrawableModels?.Med, "Medium Detail", false, dnode); AddDrawableModelsTreeNodes(drawable.DrawableModels?.Low, "Low Detail", false, dnode); @@ -1272,6 +1387,7 @@ namespace CodeWalker.Forms foreach (var geom in model.Geometries) { var gname = geom.ToString(); + VerticesCount += geom.VerticesCount; var gnode = mnode.Nodes.Add(gname); gnode.Tag = geom; gnode.Checked = true;// check; @@ -1545,6 +1661,10 @@ namespace CodeWalker.Forms { td = Ypt?.PtfxList?.TextureDictionary; } + else if ((Ydd != null) && (Ydd.Loaded)) + { + td = Ydd?.Drawables.First().ShaderGroup?.TextureDictionary; + } if (td != null) { @@ -1649,7 +1769,7 @@ namespace CodeWalker.Forms } else { - if ((FilePath.ToLowerInvariant().StartsWith(GTAFolder.CurrentGTAFolder.ToLowerInvariant()))) saveAs = true; + if (FilePath.StartsWith(GTAFolder.CurrentGTAFolder, StringComparison.OrdinalIgnoreCase)) saveAs = true; if (!File.Exists(FilePath)) saveAs = true; } @@ -1727,7 +1847,7 @@ namespace CodeWalker.Forms if (rpfSave) { - if (!rpfFileEntry.Path.ToLowerInvariant().StartsWith("mods")) + if (!rpfFileEntry.Path.StartsWith("mods", StringComparison.OrdinalIgnoreCase)) { if (MessageBox.Show("This file is NOT located in the mods folder - Are you SURE you want to save this file?\r\nWARNING: This could cause permanent damage to your game!!!", "WARNING: Are you sure about this?", MessageBoxButtons.YesNo) != DialogResult.Yes) { @@ -1985,7 +2105,7 @@ namespace CodeWalker.Forms File.WriteAllBytes(fpath, dds); successcount++; } - catch + catch(Exception ex) { errordds.Add(tex.Name ?? "???"); } @@ -2303,7 +2423,7 @@ namespace CodeWalker.Forms private void StatsUpdateTimer_Tick(object sender, EventArgs e) { - StatsLabel.Text = Renderer.GetStatusText(); + StatsLabel.Text = Renderer.GetStatusText() + $" verts: {VerticesCount}"; if (Renderer.timerunning) { diff --git a/CodeWalker/Forms/ModelMatForm.cs b/CodeWalker/Forms/ModelMatForm.cs index ecbc47e..0bbeac4 100644 --- a/CodeWalker/Forms/ModelMatForm.cs +++ b/CodeWalker/Forms/ModelMatForm.cs @@ -226,7 +226,7 @@ namespace CodeWalker.Forms if (ttex == null)//don't do this for embedded textures! { tex.Name = txt; - tex.NameHash = JenkHash.GenHash(txt.ToLowerInvariant()); + tex.NameHash = JenkHash.GenHashLower(txt); } else { diff --git a/CodeWalker/Forms/RelForm.cs b/CodeWalker/Forms/RelForm.cs index b011dfb..4a629e1 100644 --- a/CodeWalker/Forms/RelForm.cs +++ b/CodeWalker/Forms/RelForm.cs @@ -246,7 +246,7 @@ namespace CodeWalker.Forms if (rpfFileEntry?.Parent != null) { - if (!rpfFileEntry.Path.ToLowerInvariant().StartsWith("mods")) + if (!rpfFileEntry.Path.StartsWith("mods", StringComparison.OrdinalIgnoreCase)) { if (MessageBox.Show("This file is NOT located in the mods folder - Are you SURE you want to save this file?\r\nWARNING: This could cause permanent damage to your game!!!", "WARNING: Are you sure about this?", MessageBoxButtons.YesNo) != DialogResult.Yes) { @@ -383,7 +383,7 @@ namespace CodeWalker.Forms if (string.IsNullOrEmpty(FileName)) saveAs = true; if (string.IsNullOrEmpty(FilePath)) saveAs = true; - else if ((FilePath.ToLowerInvariant().StartsWith(GTAFolder.CurrentGTAFolder.ToLowerInvariant()))) saveAs = true; + else if (FilePath.StartsWith(GTAFolder.CurrentGTAFolder, StringComparison.OrdinalIgnoreCase)) saveAs = true; if (!File.Exists(FilePath)) saveAs = true; var fn = FilePath; @@ -536,8 +536,8 @@ namespace CodeWalker.Forms { if (textsearch) { - if (((rd.Name?.ToLowerInvariant().Contains(textl)) ?? false) || (rd.NameHash == hash) || (rd.NameHash == hashl) || - (rd.NameHash.ToString().ToLowerInvariant().Contains(textl))) + if (((rd.Name?.Contains(textl, StringComparison.OrdinalIgnoreCase)) ?? false) || (rd.NameHash == hash) || (rd.NameHash == hashl) || + rd.NameHash.ToString().Contains(textl, StringComparison.OrdinalIgnoreCase)) { results.Add(rd); } diff --git a/CodeWalker/Forms/TextForm.cs b/CodeWalker/Forms/TextForm.cs index 758d1a2..3556924 100644 --- a/CodeWalker/Forms/TextForm.cs +++ b/CodeWalker/Forms/TextForm.cs @@ -197,7 +197,7 @@ namespace CodeWalker.Forms if (string.IsNullOrEmpty(FileName)) saveAs = true; if (string.IsNullOrEmpty(FilePath)) saveAs = true; - else if ((FilePath.ToLowerInvariant().StartsWith(GTAFolder.CurrentGTAFolder.ToLowerInvariant()))) saveAs = true; + else if (FilePath.StartsWith(GTAFolder.CurrentGTAFolder, StringComparison.OrdinalIgnoreCase)) saveAs = true; if (!File.Exists(FilePath)) saveAs = true; var fn = FilePath; @@ -277,7 +277,7 @@ namespace CodeWalker.Forms return false; } - if (!rpfFileEntry.Path.ToLowerInvariant().StartsWith("mods")) + if (!rpfFileEntry.Path.StartsWith("mods", StringComparison.OrdinalIgnoreCase)) { if (MessageBox.Show("This file is NOT located in the mods folder - Are you SURE you want to save this file?\r\nWARNING: This could cause permanent damage to your game!!!", "WARNING: Are you sure about this?", MessageBoxButtons.YesNo) != DialogResult.Yes) { diff --git a/CodeWalker/Forms/XmlForm.cs b/CodeWalker/Forms/XmlForm.cs index f3302c6..dc84089 100644 --- a/CodeWalker/Forms/XmlForm.cs +++ b/CodeWalker/Forms/XmlForm.cs @@ -184,7 +184,7 @@ namespace CodeWalker.Forms if (string.IsNullOrEmpty(FileName)) saveAs = true; if (string.IsNullOrEmpty(FilePath)) saveAs = true; - else if ((FilePath.ToLowerInvariant().StartsWith(GTAFolder.CurrentGTAFolder.ToLowerInvariant()))) saveAs = true; + else if (FilePath.StartsWith(GTAFolder.CurrentGTAFolder, StringComparison.OrdinalIgnoreCase)) saveAs = true; if (!File.Exists(FilePath)) saveAs = true; var fn = FilePath; @@ -228,7 +228,7 @@ namespace CodeWalker.Forms return false; } - if (!rpfFileEntry.Path.ToLowerInvariant().StartsWith("mods")) + if (!rpfFileEntry.Path.StartsWith("mods", StringComparison.OrdinalIgnoreCase)) { if (MessageBox.Show("This file is NOT located in the mods folder - Are you SURE you want to save this file?\r\nWARNING: This could cause permanent damage to your game!!!", "WARNING: Are you sure about this?", MessageBoxButtons.YesNo) != DialogResult.Yes) { diff --git a/CodeWalker/Forms/YtdForm.cs b/CodeWalker/Forms/YtdForm.cs index e064dea..408dba5 100644 --- a/CodeWalker/Forms/YtdForm.cs +++ b/CodeWalker/Forms/YtdForm.cs @@ -249,7 +249,6 @@ namespace CodeWalker.Forms if (tex == null) return; tex.Name = CurrentTexture.Name; - tex.NameHash = CurrentTexture.NameHash; tex.Usage = CurrentTexture.Usage; tex.UsageFlags = CurrentTexture.UsageFlags; tex.Unknown_32h = CurrentTexture.Unknown_32h; @@ -285,7 +284,6 @@ namespace CodeWalker.Forms var tex = CurrentTexture; tex.Name = name; - tex.NameHash = JenkHash.GenHash(name.ToLowerInvariant()); var textures = new List(); textures.AddRange(TexDict.Textures.data_items); @@ -323,8 +321,7 @@ namespace CodeWalker.Forms var dds = File.ReadAllBytes(fn); var tex = DDSIO.GetTexture(dds); tex.Name = Path.GetFileNameWithoutExtension(fn); - tex.NameHash = JenkHash.GenHash(tex.Name?.ToLowerInvariant()); - JenkIndex.Ensure(tex.Name?.ToLowerInvariant()); + JenkIndex.EnsureLower(tex.Name); return tex; } catch @@ -456,7 +453,7 @@ namespace CodeWalker.Forms if (!saveas) { isinrpf = true; - if (!rpfFileEntry.Path.ToLowerInvariant().StartsWith("mods")) + if (!rpfFileEntry.Path.StartsWith("mods", StringComparison.OrdinalIgnoreCase)) { if (MessageBox.Show("This file is NOT located in the mods folder - Are you SURE you want to save this file?\r\nWARNING: This could cause permanent damage to your game!!!", "WARNING: Are you sure about this?", MessageBoxButtons.YesNo) != DialogResult.Yes) { diff --git a/CodeWalker/GameFiles/GameFileCacheFactory.cs b/CodeWalker/GameFiles/GameFileCacheFactory.cs index 15c2d05..159613b 100644 --- a/CodeWalker/GameFiles/GameFileCacheFactory.cs +++ b/CodeWalker/GameFiles/GameFileCacheFactory.cs @@ -2,17 +2,25 @@ using CodeWalker.Properties; +using System; +using System.Diagnostics; namespace CodeWalker.GameFiles { public static class GameFileCacheFactory { - + public static GameFileCache _instance = null; public static GameFileCache Create() { - var s = Settings.Default; - return new GameFileCache(s.CacheSize, s.CacheTime, GTAFolder.CurrentGTAFolder, s.DLC, s.EnableMods, s.ExcludeFolders); + if (_instance == null) + { + var s = Settings.Default; + GTA5Keys.LoadFromPath(GTAFolder.CurrentGTAFolder, Settings.Default.Key); + _instance = new GameFileCache(s.CacheSize, s.CacheTime, GTAFolder.CurrentGTAFolder, s.DLC, s.EnableMods, s.ExcludeFolders); + GTAFolder.OnGTAFolderChanged += _instance.SetGtaFolder; + } + return _instance; } } diff --git a/CodeWalker/PedsForm.cs b/CodeWalker/PedsForm.cs index af8959b..e7cd4b5 100644 --- a/CodeWalker/PedsForm.cs +++ b/CodeWalker/PedsForm.cs @@ -27,9 +27,10 @@ namespace CodeWalker public Renderer Renderer = null; public object RenderSyncRoot { get { return Renderer.RenderSyncRoot; } } + public bool Pauserendering { get; set; } volatile bool formopen = false; volatile bool running = false; - volatile bool pauserendering = false; + //volatile bool initialised = false; Stopwatch frametimer = new Stopwatch(); @@ -211,7 +212,7 @@ namespace CodeWalker float elapsed = (float)frametimer.Elapsed.TotalSeconds; frametimer.Restart(); - if (pauserendering) return; + if (Pauserendering) return; GameFileCache.BeginFrame(); diff --git a/CodeWalker/Program.cs b/CodeWalker/Program.cs index 14a151f..e79197f 100644 --- a/CodeWalker/Program.cs +++ b/CodeWalker/Program.cs @@ -1,9 +1,15 @@ -using CodeWalker.Properties; +using CodeWalker.Forms; +using CodeWalker.GameFiles; +using CodeWalker.Properties; +using CodeWalker.Utils; using System; using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; using System.IO; using System.Linq; using System.Reflection; +using System.Runtime.InteropServices; using System.Threading.Tasks; using System.Windows.Forms; using System.Windows.Shell; @@ -12,18 +18,23 @@ namespace CodeWalker { static class Program { + [DllImport("kernel32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool AllocConsole(); /// /// The main entry point for the application. /// [STAThread] static void Main(string[] args) { - + CultureInfo.CurrentCulture = CultureInfo.InvariantCulture; + ConsoleWindow.Hide(); bool menumode = false; bool explorermode = false; bool projectmode = false; bool vehiclesmode = false; bool pedsmode = false; + string path = null; if ((args != null) && (args.Length > 0)) { foreach (string arg in args) @@ -49,6 +60,18 @@ namespace CodeWalker { pedsmode = true; } + try + { + if (File.Exists(arg)) + { + path = arg; + } + } catch (Exception ex) + { + Debug.WriteLine(ex); + Console.WriteLine(ex); + } + } } @@ -89,6 +112,14 @@ namespace CodeWalker { Application.Run(new PedsForm()); } + else if (path != null) + { + var modelForm = new ModelForm(); + modelForm.Load += new EventHandler(async (sender, eventArgs) => { + modelForm.ViewModel(path); + }); + Application.Run(modelForm); + } else { Application.Run(new WorldForm()); diff --git a/CodeWalker/Project/MenyooXml.cs b/CodeWalker/Project/MenyooXml.cs index 0b3e88b..ec7830d 100644 --- a/CodeWalker/Project/MenyooXml.cs +++ b/CodeWalker/Project/MenyooXml.cs @@ -123,8 +123,8 @@ namespace CodeWalker.Project XmlElement enode = node as XmlElement; - var hashstr = Xml.GetChildInnerText(node, "ModelHash").ToLowerInvariant(); - if (hashstr.StartsWith("0x")) hashstr = hashstr.Substring(2); + var hashstr = Xml.GetChildInnerText(node, "ModelHash"); + if (hashstr.StartsWith("0x", StringComparison.OrdinalIgnoreCase)) hashstr = hashstr.Substring(2); ModelHash = Convert.ToUInt32(hashstr, 16); Type = Xml.GetChildIntInnerText(node, "Type"); diff --git a/CodeWalker/Project/Panels/EditProjectManifestPanel.cs b/CodeWalker/Project/Panels/EditProjectManifestPanel.cs index 4a129e8..d58bd45 100644 --- a/CodeWalker/Project/Panels/EditProjectManifestPanel.cs +++ b/CodeWalker/Project/Panels/EditProjectManifestPanel.cs @@ -102,7 +102,7 @@ namespace CodeWalker.Project.Panels { ymapname = ymap.Name.ToLowerInvariant(); } - if (ymapname.EndsWith(".ymap")) + if (ymapname.EndsWith(".ymap", StringComparison.OrdinalIgnoreCase)) { ymapname = ymapname.Substring(0, ymapname.Length - 5); } diff --git a/CodeWalker/Project/Panels/GenerateLODLightsPanel.cs b/CodeWalker/Project/Panels/GenerateLODLightsPanel.cs index 2fa2236..8d3d947 100644 --- a/CodeWalker/Project/Panels/GenerateLODLightsPanel.cs +++ b/CodeWalker/Project/Panels/GenerateLODLightsPanel.cs @@ -264,12 +264,10 @@ namespace CodeWalker.Project.Panels lodymap._CMapData.name = JenkHash.GenHash(lodname); lodymap.RpfFileEntry = new RpfResourceFileEntry(); lodymap.RpfFileEntry.Name = lodname + ".ymap"; - lodymap.RpfFileEntry.NameLower = lodname + ".ymap"; distymap.Name = distname; distymap._CMapData.name = JenkHash.GenHash(distname); distymap.RpfFileEntry = new RpfResourceFileEntry(); distymap.RpfFileEntry.Name = distname + ".ymap"; - distymap.RpfFileEntry.NameLower = distname + ".ymap"; lodymap._CMapData.parent = distymap._CMapData.name; lodymap.Loaded = true; diff --git a/CodeWalker/Project/ProjectFile.cs b/CodeWalker/Project/ProjectFile.cs index 45609e9..adbbcc7 100644 --- a/CodeWalker/Project/ProjectFile.cs +++ b/CodeWalker/Project/ProjectFile.cs @@ -510,13 +510,11 @@ namespace CodeWalker.Project } public bool RenameYmap(string oldfilename, string newfilename) { - oldfilename = oldfilename.ToLowerInvariant(); - newfilename = newfilename.ToLowerInvariant(); for (int i = 0; i < YmapFilenames.Count; i++) { - if (YmapFilenames[i]?.ToLowerInvariant() == oldfilename) + if (YmapFilenames[i]?.Equals(oldfilename, StringComparison.OrdinalIgnoreCase) ?? false) { - YmapFilenames[i] = newfilename; + YmapFilenames[i] = newfilename.ToLowerInvariant(); HasChanged = true; return true; } @@ -580,13 +578,11 @@ namespace CodeWalker.Project } public bool RenameYtyp(string oldfilename, string newfilename) { - oldfilename = oldfilename.ToLowerInvariant(); - newfilename = newfilename.ToLowerInvariant(); for (int i = 0; i < YtypFilenames.Count; i++) { - if (YtypFilenames[i]?.ToLowerInvariant() == oldfilename) + if (YtypFilenames[i]?.Equals(oldfilename, StringComparison.OrdinalIgnoreCase) ?? false) { - YtypFilenames[i] = newfilename; + YtypFilenames[i] = newfilename.ToLowerInvariant(); HasChanged = true; return true; } @@ -609,7 +605,7 @@ namespace CodeWalker.Project { string relpath = GetRelativePath(ybn.FilePath); if (string.IsNullOrEmpty(relpath)) relpath = ybn.Name; - if (YndFilenames.Contains(relpath)) return false; + if (YndFilenames.Contains(relpath, StringComparer.OrdinalIgnoreCase)) return false; YbnFilenames.Add(relpath); YbnFiles.Add(ybn); return true; @@ -626,10 +622,9 @@ namespace CodeWalker.Project public bool ContainsYbn(string filename) { bool found = false; - filename = filename.ToLowerInvariant(); foreach (var yndfn in YbnFilenames) { - if (yndfn == filename) + if (yndfn.Equals(filename, StringComparison.OrdinalIgnoreCase)) { found = true; break; @@ -651,7 +646,7 @@ namespace CodeWalker.Project newfilename = newfilename.ToLowerInvariant(); for (int i = 0; i < YbnFilenames.Count; i++) { - if (YbnFilenames[i]?.ToLowerInvariant() == oldfilename) + if (YbnFilenames[i]?.Equals(oldfilename, StringComparison.OrdinalIgnoreCase) ?? false) { YbnFilenames[i] = newfilename; HasChanged = true; @@ -937,7 +932,6 @@ namespace CodeWalker.Project RelFile relfile = new RelFile(); relfile.RpfFileEntry = new RpfResourceFileEntry(); relfile.RpfFileEntry.Name = Path.GetFileName(filename); - relfile.RpfFileEntry.NameHash = JenkHash.GenHash(relfile.RpfFileEntry.Name); relfile.FilePath = GetFullFilePath(filename); relfile.Name = relfile.RpfFileEntry.Name; if (!AddAudioRelFile(relfile)) return null; diff --git a/CodeWalker/Project/ProjectForm.cs b/CodeWalker/Project/ProjectForm.cs index 5d1f1a6..943acd3 100644 --- a/CodeWalker/Project/ProjectForm.cs +++ b/CodeWalker/Project/ProjectForm.cs @@ -7102,12 +7102,6 @@ namespace CodeWalker.Project var ymap = CurrentProjectFile.YmapFiles[i]; if (ymap.Loaded) { - // make sure we're replacing ymaps that have been added by the end-user. - if (ymap.RpfFileEntry.ShortNameHash == 0) - { - ymap.RpfFileEntry.ShortNameHash = JenkHash.GenHash(ymap.RpfFileEntry.GetShortNameLower()); - } - ymaps[ymap.RpfFileEntry.ShortNameHash] = ymap; } } diff --git a/CodeWalker/Properties/Settings.Designer.cs b/CodeWalker/Properties/Settings.Designer.cs index 0f96979..21e87f2 100644 --- a/CodeWalker/Properties/Settings.Designer.cs +++ b/CodeWalker/Properties/Settings.Designer.cs @@ -12,7 +12,7 @@ namespace CodeWalker.Properties { [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "16.5.0.0")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "17.7.0.0")] public sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); @@ -142,7 +142,7 @@ namespace CodeWalker.Properties { this["Wireframe"] = value; } } - + [global::System.Configuration.UserScopedSettingAttribute()] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Configuration.DefaultSettingValueAttribute("True")] @@ -154,43 +154,7 @@ namespace CodeWalker.Properties { this["Skydome"] = value; } } - - [global::System.Configuration.UserScopedSettingAttribute()] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Configuration.DefaultSettingValueAttribute("True")] - public bool LODLights { - get { - return ((bool)(this["LODLights"])); - } - set { - this["LODLights"] = value; - } - } - - [global::System.Configuration.UserScopedSettingAttribute()] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Configuration.DefaultSettingValueAttribute("True")] - public bool NatrualAmbientLight { - get { - return ((bool)(this["NatrualAmbientLight"])); - } - set { - this["NatrualAmbientLight"] = value; - } - } - - [global::System.Configuration.UserScopedSettingAttribute()] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Configuration.DefaultSettingValueAttribute("True")] - public bool ArtificialAmbientLight { - get { - return ((bool)(this["ArtificialAmbientLight"])); - } - set { - this["ArtificialAmbientLight"] = value; - } - } - + [global::System.Configuration.UserScopedSettingAttribute()] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Configuration.DefaultSettingValueAttribute("True")] @@ -526,55 +490,7 @@ namespace CodeWalker.Properties { this["RenderMode"] = value; } } - - [global::System.Configuration.UserScopedSetting()] - [global::System.Diagnostics.DebuggerNonUserCode()] - [global::System.Configuration.DefaultSettingValue("EXTRASUNNY")] - public string Weather { - get { - return ((string)(this["Weather"])); - } - set { - this["Weather"] = value; - } - } - - [global::System.Configuration.UserScopedSetting()] - [global::System.Diagnostics.DebuggerNonUserCode()] - [global::System.Configuration.DefaultSettingValue("GLOBAL")] - public string Region { - get { - return ((string)(this["Region"])); - } - set { - this["Region"] = value; - } - } - - [global::System.Configuration.UserScopedSetting()] - [global::System.Diagnostics.DebuggerNonUserCode()] - [global::System.Configuration.DefaultSettingValue("contrails")] - public string Clouds { - get { - return ((string)(this["Clouds"])); - } - set { - this["Clouds"] = value; - } - } - - [global::System.Configuration.UserScopedSetting()] - [global::System.Diagnostics.DebuggerNonUserCode()] - [global::System.Configuration.DefaultSettingValue("720")] - public int TimeOfDay { - get { - return ((int)(this["TimeOfDay"])); - } - set { - this["TimeOfDay"] = value; - } - } - + [global::System.Configuration.UserScopedSettingAttribute()] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Configuration.DefaultSettingValueAttribute("DiffuseSampler")] @@ -914,5 +830,101 @@ namespace CodeWalker.Properties { this["RPFExplorerStartFolder"] = value; } } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("720")] + public int TimeOfDay { + get { + return ((int)(this["TimeOfDay"])); + } + set { + this["TimeOfDay"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("False")] + public bool LODLights { + get { + return ((bool)(this["LODLights"])); + } + set { + this["LODLights"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("Global")] + public string Region { + get { + return ((string)(this["Region"])); + } + set { + this["Region"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("contrails")] + public string Clouds { + get { + return ((string)(this["Clouds"])); + } + set { + this["Clouds"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("True")] + public string Weather { + get { + return ((string)(this["Weather"])); + } + set { + this["Weather"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("True")] + public bool NatrualAmbientLight { + get { + return ((bool)(this["NatrualAmbientLight"])); + } + set { + this["NatrualAmbientLight"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("True")] + public bool ArtificialAmbientLight { + get { + return ((bool)(this["ArtificialAmbientLight"])); + } + set { + this["ArtificialAmbientLight"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("2")] + public int AntiAliasing { + get { + return ((int)(this["AntiAliasing"])); + } + set { + this["AntiAliasing"] = value; + } + } } } diff --git a/CodeWalker/Properties/Settings.settings b/CodeWalker/Properties/Settings.settings index 0164951..ddad151 100644 --- a/CodeWalker/Properties/Settings.settings +++ b/CodeWalker/Properties/Settings.settings @@ -216,5 +216,29 @@ + + 720 + + + False + + + Global + + + contrails + + + True + + + True + + + True + + + 2 + \ No newline at end of file diff --git a/CodeWalker/Rendering/DirectX/DXForm.cs b/CodeWalker/Rendering/DirectX/DXForm.cs index df92505..1c6bd25 100644 --- a/CodeWalker/Rendering/DirectX/DXForm.cs +++ b/CodeWalker/Rendering/DirectX/DXForm.cs @@ -19,7 +19,7 @@ namespace CodeWalker.Rendering Form Form { get; } - + public bool Pauserendering { get; set; } void InitScene(Device device); void CleanupScene(); void RenderScene(DeviceContext context); diff --git a/CodeWalker/Rendering/DirectX/DXManager.cs b/CodeWalker/Rendering/DirectX/DXManager.cs index d941cea..fa01f0d 100644 --- a/CodeWalker/Rendering/DirectX/DXManager.cs +++ b/CodeWalker/Rendering/DirectX/DXManager.cs @@ -7,12 +7,14 @@ using SharpDX.Direct3D11; using SharpDX.DXGI; using Color = SharpDX.Color; using Device = SharpDX.Direct3D11.Device; -using Buffer = SharpDX.Direct3D11.Buffer; using DriverType = SharpDX.Direct3D.DriverType; using System.Threading; using System.Windows.Forms; using SharpDX; using SharpDX.Direct3D; +using System.Runtime; +using CodeWalker.Core.Utils; +using CodeWalker.Properties; namespace CodeWalker.Rendering { @@ -32,7 +34,7 @@ namespace CodeWalker.Rendering private volatile bool Rendering = false; private volatile bool Resizing = false; private object syncroot = new object(); //for thread safety - public int multisamplecount { get; private set; } = 4; //should be a setting.. + 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 private System.Drawing.Size beginSize; @@ -209,7 +211,6 @@ namespace CodeWalker.Rendering { if (Resizing) return; Monitor.Enter(syncroot); - int width = dxform.Form.ClientSize.Width; int height = dxform.Form.ClientSize.Height; @@ -246,6 +247,7 @@ namespace CodeWalker.Rendering Cleanup(); } } + private void Dxform_ClientSizeChanged(object sender, EventArgs e) { Resize(); @@ -273,21 +275,45 @@ namespace CodeWalker.Rendering private void StartRenderLoop() { Running = true; - new Thread(new ThreadStart(RenderLoop)).Start(); + new Task(RenderLoop, TaskCreationOptions.LongRunning).Start(TaskScheduler.Default); + //new Thread(new ThreadStart(RenderLoop)).Start(); } private void RenderLoop() { + //SharpDX.Configuration.EnableObjectTracking = true; + //Task.Run(async () => + //{ + // while (true) + // { + // Console.WriteLine(SharpDX.Diagnostics.ObjectTracker.ReportActiveObjects()); + // await Task.Delay(5000); + // } + //}); + Thread.CurrentThread.Name = "RenderLoop"; while (Running) { while (Resizing) { swapchain.Present(1, PresentFlags.None); //just flip buffers when resizing; don't draw } - while (dxform.Form.WindowState == FormWindowState.Minimized) + if (dxform.Form.WindowState == FormWindowState.Minimized) { - Thread.Sleep(10); //don't hog CPU when minimised - if (dxform.Form.IsDisposed) return; //if closed while minimised + 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) + { + Thread.Sleep(100); //don't hog CPU when minimised + if (dxform.Form.IsDisposed) return; //if closed while minimised + } + dxform.Pauserendering = false; + Console.WriteLine("Window is maximized"); } + + if (Form.ActiveForm == null) { Thread.Sleep(100); //reduce the FPS when the app isn't active (maybe this should be configurable?) @@ -295,48 +321,54 @@ namespace CodeWalker.Rendering } Rendering = true; - if(!Monitor.TryEnter(syncroot, 50)) - { - Thread.Sleep(10); //don't hog CPU when not able to render... - continue; - } - - bool ok = true; 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) + if (!Monitor.TryEnter(syncroot, 50)) { - Monitor.Exit(syncroot); - Rendering = false; - return; //the form was closed... stop!! + Console.WriteLine("Failed to get lock for syncroot"); + Thread.Sleep(10); //don't hog CPU when not able to render... + continue; } - 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) + { + Monitor.Exit(syncroot); + Rendering = false; + return; //the form was closed... stop!! + } + + dxform.RenderScene(context); + + try + { + swapchain.Present(1, PresentFlags.None); + } + catch (Exception ex) + { + MessageBox.Show("Error presenting swap chain!\n" + ex.ToString()); + } } } - - Monitor.Exit(syncroot); - Rendering = false; - + finally + { + Monitor.Exit(syncroot); + Rendering = false; + } } } diff --git a/CodeWalker/Rendering/Renderable.cs b/CodeWalker/Rendering/Renderable.cs index b9877b7..533acd3 100644 --- a/CodeWalker/Rendering/Renderable.cs +++ b/CodeWalker/Rendering/Renderable.cs @@ -1475,6 +1475,7 @@ namespace CodeWalker.Rendering public float FalloffExponent; public float InnerAngle;//for cone public float OuterAngleOrCapExt;//outer angle for cone, cap extent for capsule + public float Distance; } public LODLight[] Points; @@ -1512,7 +1513,7 @@ namespace CodeWalker.Rendering for (int i = 0; i < n; i++) { var l = ll.LodLights[i]; - if (l.Enabled == false) continue; + if (l.Enabled == false || l.Visible == false) continue; var light = new LODLight(); light.Position = l.Position; light.Colour = (uint)l.Colour.ToBgra(); @@ -1549,6 +1550,58 @@ namespace CodeWalker.Rendering } + public (int, int, int) UpdateLods(Vector3 refPos) + { + for (int i = 0; i < Points.Length; i++) + { + Points[i].Distance = Vector3.DistanceSquared(refPos, Points[i].Position); + } + for (int i = 0; i < Spots.Length; i++) + { + Spots[i].Distance = Vector3.DistanceSquared(refPos, Spots[i].Position); + } + + for (int i = 0; i < Caps.Length; i++) + { + Caps[i].Distance = Vector3.DistanceSquared(refPos, Caps[i].Position); + } + + Array.Sort(Points, (a, b) => a.Distance.CompareTo(b.Distance)); + Array.Sort(Spots, (a, b) => a.Distance.CompareTo(b.Distance)); + Array.Sort(Caps, (a, b) => a.Distance.CompareTo(b.Distance)); + + var spotsIndex = 0; + var pointsIndex = 0; + var capsIndex = 0; + for (int i = 0; i < Points.Length; i++) + { + if (Points[i].Distance > 100_000) + { + pointsIndex = i; + break; + } + } + for (int i = 0; i < Spots.Length; i++) + { + if (Spots[i].Distance > 100_000) + { + spotsIndex = i; + break; + } + } + + for (int i = 0; i < Caps.Length; i++) + { + if (Caps[i].Distance > 100_000) + { + capsIndex = i; + break; + } + } + + return (pointsIndex, spotsIndex, capsIndex); + } + public override void Load(Device device) { if ((Points != null) && (Points.Length > 0)) diff --git a/CodeWalker/Rendering/Renderer.cs b/CodeWalker/Rendering/Renderer.cs index afe3d1e..9c3e6f0 100644 --- a/CodeWalker/Rendering/Renderer.cs +++ b/CodeWalker/Rendering/Renderer.cs @@ -9,6 +9,7 @@ using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; +using static CodeWalker.Rendering.RenderableLODLights; namespace CodeWalker.Rendering { @@ -172,11 +173,7 @@ namespace CodeWalker.Rendering public Renderer(DXForm form, GameFileCache cache) { Form = form; - gameFileCache = cache; - if (gameFileCache == null) - { - gameFileCache = GameFileCacheFactory.Create(); - } + gameFileCache = cache ?? GameFileCacheFactory.Create(); renderableCache = new RenderableCache(); var s = Settings.Default; @@ -384,15 +381,13 @@ namespace CodeWalker.Rendering public string GetStatusText() { - int rgc = (shaders != null) ? shaders.RenderedGeometries : 0; - int crc = renderableCache.LoadedRenderableCount; - int ctc = renderableCache.LoadedTextureCount; - int tcrc = renderableCache.MemCachedRenderableCount; - int tctc = renderableCache.MemCachedTextureCount; - long vr = renderableCache.TotalGraphicsMemoryUse + (shaders != null ? shaders.TotalGraphicsMemoryUse : 0); - string vram = TextUtil.GetBytesReadable(vr); - //StatsLabel.Text = string.Format("Drawn: {0} geom, Loaded: {1}/{5} dr, {2}/{6} tx, Vram: {3}, Fps: {4}", rgc, crc, ctc, vram, fps, tcrc, tctc); - return string.Format("Drawn: {0} geom, Loaded: {1} dr, {2} tx, Vram: {3}, Fps: {4}", rgc, crc, ctc, vram, fps); + var rgc = shaders?.RenderedGeometries ?? 0; + var crc = renderableCache.LoadedRenderableCount; + 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}"; } @@ -1850,7 +1845,7 @@ namespace CodeWalker.Rendering - public void RenderWorld(Dictionary renderworldVisibleYmapDict, IEnumerable spaceEnts) + public void RenderWorld(Dictionary ymapsWithinStreamingExtents, IEnumerable spaceEnts) { renderworldentities.Clear(); renderworldrenderables.Clear(); @@ -1860,7 +1855,7 @@ namespace CodeWalker.Rendering RequiredParents.Clear(); RenderEntities.Clear(); - foreach (var ymap in renderworldVisibleYmapDict.Values) + foreach (var ymap in ymapsWithinStreamingExtents.Values) { if (!RenderWorldYmapIsVisible(ymap)) continue; VisibleYmaps.Add(ymap); @@ -1875,7 +1870,7 @@ namespace CodeWalker.Rendering LodManager.ShowScriptedYmaps = ShowScriptedYmaps; LodManager.LODLightsEnabled = renderlodlights; LodManager.HDLightsEnabled = renderlights; - LodManager.Update(renderworldVisibleYmapDict, camera, currentElapsedTime); + LodManager.Update(ymapsWithinStreamingExtents, camera, currentElapsedTime); foreach (var updatelodlights in LodManager.UpdateLodLights) { @@ -2012,120 +2007,6 @@ namespace CodeWalker.Rendering RenderWorldYmapExtras(); } - public void RenderWorld_Orig(Dictionary renderworldVisibleYmapDict, IEnumerable spaceEnts) - { - renderworldentities.Clear(); - renderworldrenderables.Clear(); - VisibleYmaps.Clear(); - - - foreach (var ymap in renderworldVisibleYmapDict.Values) - { - if (!RenderWorldYmapIsVisible(ymap)) continue; - VisibleYmaps.Add(ymap); - } - - RenderWorldAdjustMapViewCamera(); - - - - for (int y = 0; y < VisibleYmaps.Count; y++) - { - var ymap = VisibleYmaps[y]; - YmapFile pymap = ymap.Parent; - if ((pymap == null) && (ymap._CMapData.parent != 0)) - { - renderworldVisibleYmapDict.TryGetValue(ymap._CMapData.parent, out pymap); - ymap.Parent = pymap; - } - if (ymap.RootEntities != null) - { - for (int i = 0; i < ymap.RootEntities.Length; i++) - { - var ent = ymap.RootEntities[i]; - int pind = ent._CEntityDef.parentIndex; - if (pind >= 0) //connect root entities to parents if they have them.. - { - YmapEntityDef p = null; - if ((pymap != null) && (pymap.AllEntities != null)) - { - if ((pind < pymap.AllEntities.Length)) - { - p = pymap.AllEntities[pind]; - ent.Parent = p; - ent.ParentName = p._CEntityDef.archetypeName; - } - } - else - { }//should only happen if parent ymap not loaded yet... - } - RenderWorldRecurseCalcEntityVisibility(ent); - } - } - } - - for (int y = 0; y < VisibleYmaps.Count; y++) - { - var ymap = VisibleYmaps[y]; - if (ymap.RootEntities != null) - { - for (int i = 0; i < ymap.RootEntities.Length; i++) - { - var ent = ymap.RootEntities[i]; - RenderWorldRecurseAddEntities(ent); - } - } - } - - - - - if (spaceEnts != null) - { - foreach (var ae in spaceEnts) //used by active space entities (eg "bullets") - { - if (ae.EntityDef == null) continue; //nothing to render... - RenderWorldCalcEntityVisibility(ae.EntityDef); - renderworldentities.Add(ae.EntityDef); - } - } - - - if (renderentities) - { - for (int i = 0; i < renderworldentities.Count; i++) - { - var ent = renderworldentities[i]; - var arch = ent.Archetype; - var pent = ent.Parent; - var drawable = gameFileCache.TryGetDrawable(arch); - Renderable rndbl = TryGetRenderable(arch, drawable); - if ((rndbl != null) && rndbl.IsLoaded && (rndbl.AllTexturesLoaded || !waitforchildrentoload)) - { - RenderableEntity rent = new RenderableEntity(); - rent.Entity = ent; - rent.Renderable = rndbl; - renderworldrenderables.Add(rent); - } - else if (waitforchildrentoload) - { - //todo: render parent if children loading....... - } - } - for (int i = 0; i < renderworldrenderables.Count; i++) - { - var rent = renderworldrenderables[i]; - var ent = rent.Entity; - var arch = ent.Archetype; - - if (HideEntities.ContainsKey(ent.EntityHash)) continue; //don't render hidden entities! - - RenderArchetype(arch, ent, rent.Renderable, false); - } - } - - RenderWorldYmapExtras(); - } private void RenderWorldCalcEntityVisibility(YmapEntityDef ent) { float dist = (ent.Position - camera.Position).Length(); @@ -3231,7 +3112,7 @@ namespace CodeWalker.Rendering Vector3 bbmax = (arche != null) ? arche.BBMax : rndbl.Key.BoundingBoxMax; Vector3 bscen = (arche != null) ? arche.BSCenter : rndbl.Key.BoundingCenter; float radius = (arche != null) ? arche.BSRadius : rndbl.Key.BoundingSphereRadius; - float distance = 0;// (camrel + bscen).Length(); + float distance = 0;//(camrel + bscen).Length(); bool interiorent = false; bool castshadow = true; @@ -4154,9 +4035,9 @@ namespace CodeWalker.Rendering { if (VisibleLightsPrev.Contains(light) == false) { - if (LodLightsDict.TryGetValue(light.Hash, out var lodlight)) + if (LodLightsDict.TryGetValue(light.Hash, out var lodlight) && lodlight.Enabled) { - lodlight.Enabled = false; + lodlight.Enabled = true; UpdateLodLights.Add(lodlight.LodLights); } } @@ -4165,18 +4046,36 @@ namespace CodeWalker.Rendering { if (VisibleLights.Contains(light) == false) { - if (LodLightsDict.TryGetValue(light.Hash, out var lodlight)) + if (LodLightsDict.TryGetValue(light.Hash, out var lodlight) && !lodlight.Enabled) { - lodlight.Enabled = true; + lodlight.Enabled = false; UpdateLodLights.Add(lodlight.LodLights); } } } + //foreach (var light in LodLightsDict.Values) + //{ + // if (LightVisible(light)) + // { + // if (light.Visible) + // { + // light.Visible = false; + // UpdateLodLights.Add(light.LodLights); + // } + + // } else + // { + // if (!light.Visible) + // { + // light.Visible = true; + // UpdateLodLights.Add(light.LodLights); + // } + // } + //} - var vl = VisibleLights; - VisibleLights = VisibleLightsPrev; - VisibleLightsPrev = vl; + + (VisibleLightsPrev, VisibleLights) = (VisibleLights, VisibleLightsPrev); } private void RecurseAddVisibleLeaves(YmapEntityDef ent) @@ -4254,6 +4153,21 @@ namespace CodeWalker.Rendering return Camera.ViewFrustum.ContainsAABBNoClip(ref ent.BBCenter, ref ent.BBExtent); } } + + private bool LightVisible(YmapLODLight lodLight) + { + var position = lodLight.Position; + var extent = new Vector3(lodLight.Falloff, lodLight.Falloff, lodLight.Falloff); + if (MapViewEnabled) + { + return true; + } + else + { + return Camera.ViewFrustum.ContainsAABBNoClip(ref position, ref extent); + } + } + private bool EntityVisibleAtMaxLodLevel(YmapEntityDef ent) { if (MaxLOD != rage__eLodType.LODTYPES_DEPTH_ORPHANHD) diff --git a/CodeWalker/Rendering/ShaderManager.cs b/CodeWalker/Rendering/ShaderManager.cs index 98a1ef7..09f619a 100644 --- a/CodeWalker/Rendering/ShaderManager.cs +++ b/CodeWalker/Rendering/ShaderManager.cs @@ -18,6 +18,9 @@ namespace CodeWalker.Rendering private int GeometryCount; public int RenderedGeometries; + private int VerticesCount; + public int RenderedVeritices; + private Device Device; public bool wireframe = Settings.Default.Wireframe; @@ -148,7 +151,7 @@ namespace CodeWalker.Rendering IsFrontCounterClockwise = true, IsMultisampleEnabled = true, IsScissorEnabled = false, - SlopeScaledDepthBias = 0.0f + SlopeScaledDepthBias = 0.0f, }; rsSolid = new RasterizerState(device, rsd); rsd.FillMode = FillMode.Wireframe; @@ -157,6 +160,7 @@ namespace CodeWalker.Rendering rsWireframeDblSided = new RasterizerState(device, rsd); rsd.FillMode = FillMode.Solid; rsSolidDblSided = new RasterizerState(device, rsd); + rsd.CullMode = CullMode.Back; BlendStateDescription bsd = new BlendStateDescription() @@ -203,6 +207,7 @@ namespace CodeWalker.Rendering PassOperation = StencilOperation.Zero }, IsDepthEnabled = true, + IsStencilEnabled = false, StencilReadMask = 0, StencilWriteMask = 0 @@ -406,6 +411,7 @@ namespace CodeWalker.Rendering public void RenderQueued(DeviceContext context, Camera camera, Vector4 wind) { GeometryCount = 0; + VerticesCount = 0; Camera = camera; @@ -662,7 +668,7 @@ namespace CodeWalker.Rendering RenderedGeometries = GeometryCount; - + RenderedVeritices = VerticesCount; } public void RenderFinalPass(DeviceContext context) @@ -789,6 +795,8 @@ namespace CodeWalker.Rendering var gmodel = geom.Geom.Owner; shader.SetEntityVars(context, ref geom.Inst); + VerticesCount += geom.Geom.VertexCount; + if (gmodel != model) { model = gmodel; @@ -829,6 +837,7 @@ namespace CodeWalker.Rendering foreach (var geom in batch) { + VerticesCount += geom.Geom.VertexCount; Basic.RenderBoundGeom(context, geom); } @@ -1050,6 +1059,18 @@ namespace CodeWalker.Rendering { return ShaderFile.ToString() + ": " + ShaderName.ToString(); } + + public override int GetHashCode() + { + return ShaderName.GetHashCode(); + } + + public override bool Equals(object obj) + { + if (obj == null) return false; + if (obj is not ShaderKey shaderKey) return false; + return shaderKey.ShaderName == ShaderName && shaderKey.ShaderFile == ShaderFile; + } } public class ShaderRenderBucket { @@ -1097,7 +1118,7 @@ namespace CodeWalker.Rendering ClothBatches.Clear(); VehicleBatches.Clear(); - foreach (var kvp in Batches) + foreach (var kvp in Batches.Where(p => p.Value.Geometries.Count > 0).OrderBy(p => p.Value.Geometries.Average(p => p.Inst.Distance))) { if (kvp.Value.Geometries.Count == 0) continue; diff --git a/CodeWalker/Rendering/Shaders/DeferredScene.cs b/CodeWalker/Rendering/Shaders/DeferredScene.cs index 2313cf3..cdc3419 100644 --- a/CodeWalker/Rendering/Shaders/DeferredScene.cs +++ b/CodeWalker/Rendering/Shaders/DeferredScene.cs @@ -14,6 +14,7 @@ using SharpDX; using SharpDX.DXGI; using SharpDX.Mathematics.Interop; using System.Diagnostics; +using CodeWalker.Properties; namespace CodeWalker.Rendering { @@ -110,7 +111,7 @@ namespace CodeWalker.Rendering public int SSAASampleCount = 1; - public int MSAASampleCount = 4; + public int MSAASampleCount = 8; @@ -460,7 +461,8 @@ namespace CodeWalker.Rendering foreach (var rll in lodlights) { - if (rll.PointsBuffer != null) + var (pointsIndex, spotsIndex, capsIndex) = rll.UpdateLods(camera.Position); + if (rll.PointsBuffer != null && pointsIndex > 0) { context.VertexShader.SetShaderResources(0, rll.PointsBuffer.SRV); context.PixelShader.SetShaderResources(6, rll.PointsBuffer.SRV); @@ -470,9 +472,10 @@ namespace CodeWalker.Rendering LightPSVars.Vars.LightType = 1; LightPSVars.Update(context); LightPSVars.SetPSCBuffer(context, 0); - LightSphere.DrawInstanced(context, rll.PointsBuffer.StructCount); + + LightSphere.DrawInstanced(context, pointsIndex + 1); } - if (rll.SpotsBuffer != null) + if (rll.SpotsBuffer != null && spotsIndex > 0) { context.VertexShader.SetShaderResources(0, rll.SpotsBuffer.SRV); context.PixelShader.SetShaderResources(6, rll.SpotsBuffer.SRV); @@ -482,9 +485,9 @@ namespace CodeWalker.Rendering LightPSVars.Vars.LightType = 2; LightPSVars.Update(context); LightPSVars.SetPSCBuffer(context, 0); - LightCone.DrawInstanced(context, rll.SpotsBuffer.StructCount); + LightCone.DrawInstanced(context, spotsIndex + 1); } - if (rll.CapsBuffer != null) + if (rll.CapsBuffer != null && capsIndex > 0) { context.VertexShader.SetShaderResources(0, rll.CapsBuffer.SRV); context.PixelShader.SetShaderResources(6, rll.CapsBuffer.SRV); @@ -494,7 +497,7 @@ namespace CodeWalker.Rendering LightPSVars.Vars.LightType = 4; LightPSVars.Update(context); LightPSVars.SetPSCBuffer(context, 0); - LightCapsule.DrawInstanced(context, rll.CapsBuffer.StructCount); + LightCapsule.DrawInstanced(context, capsIndex + 1); } } diff --git a/CodeWalker/Rendering/Utils/GpuBuffers.cs b/CodeWalker/Rendering/Utils/GpuBuffers.cs index bc21e58..35fb4db 100644 --- a/CodeWalker/Rendering/Utils/GpuBuffers.cs +++ b/CodeWalker/Rendering/Utils/GpuBuffers.cs @@ -440,6 +440,9 @@ namespace CodeWalker.Rendering case Format.D32_Float: dtexf = Format.R32_Typeless; break; + case Format.R8G8B8A8_SNorm: + dtexf = Format.R8G8B8A8_SNorm; + break; case Format.D32_Float_S8X24_UInt: dtexf = Format.R32G8X24_Typeless;//is this right? who uses this anyway?? break; diff --git a/CodeWalker/Rendering/Utils/Shadowmap.cs b/CodeWalker/Rendering/Utils/Shadowmap.cs index 7962338..89d1dbc 100644 --- a/CodeWalker/Rendering/Utils/Shadowmap.cs +++ b/CodeWalker/Rendering/Utils/Shadowmap.cs @@ -96,7 +96,7 @@ namespace CodeWalker.Rendering Cascades.Add(c); } - DepthRenderRS = DXUtility.CreateRasterizerState(device, FillMode.Solid, CullMode.None, true, false, true, 0, 0.0f, 1.0f); + DepthRenderRS = DXUtility.CreateRasterizerState(device, FillMode.Solid, CullMode.None, true, false, Settings.Default.AntiAliasing > 1, 0, 0.0f, 1.0f); DepthRenderDS = DXUtility.CreateDepthStencilState(device, true, DepthWriteMask.All); DepthRenderVP = new ViewportF(); diff --git a/CodeWalker/Rendering/Utils/UnitSphere.cs b/CodeWalker/Rendering/Utils/UnitSphere.cs index 33f5992..9f180cc 100644 --- a/CodeWalker/Rendering/Utils/UnitSphere.cs +++ b/CodeWalker/Rendering/Utils/UnitSphere.cs @@ -127,7 +127,7 @@ namespace CodeWalker.Rendering VertexBuffer = Buffer.Create(device, BindFlags.VertexBuffer, vdata.ToArray()); - vbbinding = new VertexBufferBinding(VertexBuffer, 16, 0); + vbbinding = new VertexBufferBinding(VertexBuffer, 8, 0); IndexBuffer = Buffer.Create(device, BindFlags.IndexBuffer, idata.ToArray()); indexcount = idata.Count; diff --git a/CodeWalker/Tools/AudioExplorerForm.cs b/CodeWalker/Tools/AudioExplorerForm.cs index 6066994..d9b8353 100644 --- a/CodeWalker/Tools/AudioExplorerForm.cs +++ b/CodeWalker/Tools/AudioExplorerForm.cs @@ -1,5 +1,6 @@ using CodeWalker.GameFiles; using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.ComponentModel; using System.Data; diff --git a/CodeWalker/Tools/BinarySearchForm.cs b/CodeWalker/Tools/BinarySearchForm.cs index 83857e5..5d91493 100644 --- a/CodeWalker/Tools/BinarySearchForm.cs +++ b/CodeWalker/Tools/BinarySearchForm.cs @@ -37,12 +37,12 @@ namespace CodeWalker.Tools DataTextBox.SetTabStopWidth(3); - if (RpfMan == null) + if (RpfMan == null || !RpfMan.IsInited) { Task.Run(() => { GTA5Keys.LoadFromPath(GTAFolder.CurrentGTAFolder, Settings.Default.Key); - RpfMan = new RpfManager(); + RpfMan ??= RpfManager.GetInstance(); RpfMan.Init(GTAFolder.CurrentGTAFolder, UpdateStatus, UpdateStatus, false, false); RPFScanComplete(); }); @@ -417,17 +417,13 @@ namespace CodeWalker.Tools DateTime starttime = DateTime.Now; int resultcount = 0; - for (int f = 0; f < scannedFiles.Count; f++) + foreach(var rpffile in scannedFiles) { - var rpffile = scannedFiles[f]; totfiles += rpffile.TotalFileCount; } - - for (int f = 0; f < scannedFiles.Count; f++) + foreach(var rpffile in scannedFiles) { - var rpffile = scannedFiles[f]; - foreach (var entry in rpffile.AllEntries) { var duration = DateTime.Now - starttime; @@ -444,7 +440,7 @@ namespace CodeWalker.Tools curfile++; - if (fentry.NameLower.EndsWith(".rpf")) + if (fentry.Name.EndsWith(".rpf", StringComparison.OrdinalIgnoreCase)) { continue; } if (onlyexts != null) @@ -452,7 +448,7 @@ namespace CodeWalker.Tools bool ignore = true; for (int i = 0; i < onlyexts.Length; i++) { - if (fentry.NameLower.EndsWith(onlyexts[i])) + if (fentry.Name.EndsWith(onlyexts[i], StringComparison.OrdinalIgnoreCase)) { ignore = false; break; @@ -467,7 +463,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/BrowseForm.cs b/CodeWalker/Tools/BrowseForm.cs index 9da635c..86e9254 100644 --- a/CodeWalker/Tools/BrowseForm.cs +++ b/CodeWalker/Tools/BrowseForm.cs @@ -218,13 +218,13 @@ namespace CodeWalker.Tools { if (entry is RpfFileEntry) { - bool show = !entry.NameLower.EndsWith(".rpf"); //rpf entries get their own root node.. + bool show = !entry.Name.EndsWith(".rpf", StringComparison.OrdinalIgnoreCase); //rpf entries get their own root node.. if (show) { //string text = entry.Path.Substring(file.Path.Length + 1); //includes \ on the end //TreeNode cnode = node.Nodes.Add(text); //cnode.Tag = entry; - TreeNode cnode = AddEntryNode(entry, node); + AddEntryNode(entry, node); } } } @@ -797,7 +797,7 @@ namespace CodeWalker.Tools int max = 500; foreach (RpfFile file in ScannedFiles) { - if (file.Name.ToLowerInvariant().Contains(find)) + if (file.NameLower.Contains(find)) { AddFileNode(file, null); count++; @@ -806,10 +806,8 @@ namespace CodeWalker.Tools { if (entry.NameLower.Contains(find)) { - if (entry is RpfDirectoryEntry) + if (entry is RpfDirectoryEntry direntry) { - RpfDirectoryEntry direntry = entry as RpfDirectoryEntry; - TreeNode node = AddEntryNode(entry, null); foreach (RpfFileEntry cfentry in direntry.Files) @@ -821,7 +819,7 @@ namespace CodeWalker.Tools } else if (entry is RpfBinaryFileEntry) { - if (entry.NameLower.EndsWith(".rpf", StringComparison.InvariantCultureIgnoreCase)) continue; + if (entry.Name.EndsWith(".rpf", StringComparison.OrdinalIgnoreCase)) continue; AddEntryNode(entry, null); count++; } diff --git a/CodeWalker/Tools/ExtractRawForm.cs b/CodeWalker/Tools/ExtractRawForm.cs index d68bfe9..3e27c6f 100644 --- a/CodeWalker/Tools/ExtractRawForm.cs +++ b/CodeWalker/Tools/ExtractRawForm.cs @@ -121,7 +121,7 @@ namespace CodeWalker.Tools - RpfManager rpfman = new RpfManager(); + RpfManager rpfman = RpfManager.GetInstance(); rpfman.Init(searchpath, UpdateExtractStatus, UpdateExtractStatus); diff --git a/CodeWalker/Tools/ExtractShadersForm.cs b/CodeWalker/Tools/ExtractShadersForm.cs index 23af922..3121055 100644 --- a/CodeWalker/Tools/ExtractShadersForm.cs +++ b/CodeWalker/Tools/ExtractShadersForm.cs @@ -109,8 +109,11 @@ namespace CodeWalker.Tools - RpfManager rpfman = new RpfManager(); - rpfman.Init(searchpath, UpdateExtractStatus, UpdateExtractStatus); + RpfManager rpfman = RpfManager.GetInstance(); + if (!rpfman.IsInited) + { + rpfman.Init(searchpath, UpdateExtractStatus, UpdateExtractStatus); + } UpdateExtractStatus("Beginning shader extraction..."); diff --git a/CodeWalker/Tools/ExtractTexForm.cs b/CodeWalker/Tools/ExtractTexForm.cs index 06d9113..e66c24e 100644 --- a/CodeWalker/Tools/ExtractTexForm.cs +++ b/CodeWalker/Tools/ExtractTexForm.cs @@ -125,7 +125,7 @@ namespace CodeWalker.Tools - RpfManager rpfman = new RpfManager(); + RpfManager rpfman = RpfManager.GetInstance(); rpfman.Init(searchpath, UpdateExtractStatus, UpdateExtractStatus); diff --git a/CodeWalker/Utils/AudioUtils.cs b/CodeWalker/Utils/AudioUtils.cs index a121c45..f4872c9 100644 --- a/CodeWalker/Utils/AudioUtils.cs +++ b/CodeWalker/Utils/AudioUtils.cs @@ -311,10 +311,10 @@ namespace CodeWalker.Utils if (entry is RpfFileEntry) { var fentry = entry as RpfFileEntry; - if (entry.NameLower.EndsWith(".awc")) + if (entry.Name.EndsWith(".awc", StringComparison.OrdinalIgnoreCase)) { - var shortname = entry.GetShortNameLower(); - var parentname = entry.Parent?.GetShortNameLower() ?? ""; + var shortname = entry.ShortName; + var parentname = entry.Parent?.ShortName ?? ""; if (string.IsNullOrEmpty(parentname) && (entry.Parent?.File != null)) { parentname = entry.Parent.File.NameLower; @@ -325,7 +325,7 @@ namespace CodeWalker.Utils } } var contname = parentname + "/" + shortname; - var hash = JenkHash.GenHash(contname); + var hash = JenkHash.GenHashLower(contname); awcentries[hash] = fentry; } } diff --git a/CodeWalker/Utils/ConsoleWindow.cs b/CodeWalker/Utils/ConsoleWindow.cs new file mode 100644 index 0000000..d9d9602 --- /dev/null +++ b/CodeWalker/Utils/ConsoleWindow.cs @@ -0,0 +1,157 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace CodeWalker.Utils +{ + public static class ConsoleWindow + { + [DllImport("kernel32.dll", SetLastError = true)] + static extern bool AllocConsole(); + + [DllImport("kernel32.dll")] + static extern IntPtr GetConsoleWindow(); + + [DllImport("user32.dll")] + static extern bool ShowWindow(IntPtr hWnd, int nCmdShow); + + [DllImport("kernel32.dll")] + static extern bool FreeConsole(); + + const int SW_HIDE = 0; + const int SW_SHOW = 5; + + private static ConsoleStream consoleStream = new ConsoleStream(); + + static ConsoleWindow() + { + try + { + var logFile = Path.Combine(Path.GetDirectoryName(Application.ExecutablePath), "log.log"); + //var streamWriter = File.OpenWrite(logFile); + var streamWriter = new StreamWriter(logFile, false); + streamWriter.AutoFlush = true; + + consoleStream.AddStream(streamWriter); + } + catch (Exception ex) + { + Console.WriteLine(ex); + } + + + //var textWriter = new StreamWriter(multiStream); + + consoleStream.AddStream(Console.Out); + + Console.SetOut(consoleStream); + } + + public static IntPtr GetOrCreateConsoleWindow() + { + var handle = GetConsoleWindow(); + if (handle == IntPtr.Zero) + { + AllocConsole(); + Application.ApplicationExit += (sender, args) => + { + ConsoleWindow.Close(); + }; + handle = GetConsoleWindow(); + var originalOut = new StreamWriter(Console.OpenStandardOutput()); + originalOut.AutoFlush = true; + consoleStream.AddStream(originalOut); + } + return handle; + } + + public static void Close() + { + consoleStream.Flush(); + consoleStream.Close(); + FreeConsole(); + } + + public static void Show() + { + var handle = GetOrCreateConsoleWindow(); + ShowWindow(handle, SW_SHOW); + } + + public static void Hide() + { + var handle = GetOrCreateConsoleWindow(); + ShowWindow(handle, SW_HIDE); + } + } + + public class ConsoleStream : TextWriter + { + private readonly List _writers; + + public ConsoleStream(params TextWriter[] streams) + { + _writers = streams.ToList(); + } + + public void AddStream(TextWriter textWriter) + { + _writers.Add(textWriter); + } + + public override void Flush() + { + foreach (var stream in _writers) + { + stream.Flush(); + } + } + + public override void Write(char ch) + { + foreach (var writer in _writers) + { + try + { + writer.Write(ch); + } + catch (ObjectDisposedException) + { + _writers.Remove(writer); + // handle exception here + } + catch (IOException) + { + // handle exception here + } + } + } + + public override void Write(string value) + { + foreach (var writer in _writers) + { + writer.Write(value); + } + } + + public override void Close() + { + base.Close(); + foreach(var writer in _writers) + { + writer.Close(); + } + _writers.Clear(); + } + + public override Encoding Encoding => throw new NotImplementedException(); + } +} diff --git a/CodeWalker/Utils/GTAFolder.cs b/CodeWalker/Utils/GTAFolder.cs index 198d2b5..d881c2c 100644 --- a/CodeWalker/Utils/GTAFolder.cs +++ b/CodeWalker/Utils/GTAFolder.cs @@ -14,8 +14,16 @@ namespace CodeWalker { public static class GTAFolder { - public static string CurrentGTAFolder { get; private set; } = Settings.Default.GTAFolder; + private static string currentGTAFolder = Settings.Default.GTAFolder; + public static event Action OnGTAFolderChanged; + public static string CurrentGTAFolder { get => currentGTAFolder; private set + { + if (currentGTAFolder == value) return; + currentGTAFolder = value; + OnGTAFolderChanged?.Invoke(currentGTAFolder); + } + } public static bool ValidateGTAFolder(string folder, out string failReason) { failReason = ""; diff --git a/CodeWalker/VehicleForm.cs b/CodeWalker/VehicleForm.cs index 802d556..22075f2 100644 --- a/CodeWalker/VehicleForm.cs +++ b/CodeWalker/VehicleForm.cs @@ -30,6 +30,7 @@ namespace CodeWalker volatile bool formopen = false; volatile bool running = false; volatile bool pauserendering = false; + public bool Pauserendering { get; set; } = false; //volatile bool initialised = false; Stopwatch frametimer = new Stopwatch(); @@ -163,7 +164,7 @@ namespace CodeWalker float elapsed = (float)frametimer.Elapsed.TotalSeconds; frametimer.Restart(); - if (pauserendering) return; + if (Pauserendering) return; GameFileCache.BeginFrame(); diff --git a/CodeWalker/World/CutsceneForm.cs b/CodeWalker/World/CutsceneForm.cs index 985921d..1720471 100644 --- a/CodeWalker/World/CutsceneForm.cs +++ b/CodeWalker/World/CutsceneForm.cs @@ -546,14 +546,14 @@ namespace CodeWalker.World private void LoadYcds() { int cutListCount = (CameraCutList?.Length ?? 0) + 1; - var shortName = CutFile.FileEntry?.GetShortNameLower() ?? ""; + var shortName = CutFile.FileEntry?.ShortName ?? ""; Ycds = new YcdFile[cutListCount]; if (!string.IsNullOrEmpty(shortName)) { for (int i = 0; i < cutListCount; i++) { var ycdname = shortName + "-" + i.ToString(); - var ycdhash = JenkHash.GenHash(ycdname); + var ycdhash = JenkHash.GenHashLower(ycdname); var ycd = GameFileCache.GetYcd(ycdhash); while ((ycd != null) && (!ycd.Loaded)) { diff --git a/CodeWalker/World/WorldSearchForm.cs b/CodeWalker/World/WorldSearchForm.cs index cf079dc..259b7c4 100644 --- a/CodeWalker/World/WorldSearchForm.cs +++ b/CodeWalker/World/WorldSearchForm.cs @@ -77,7 +77,7 @@ namespace CodeWalker.World { var rpfman = gfc.RpfMan; - var rpflist = loadedOnly ? gfc.ActiveMapRpfFiles.Values.ToList() : rpfman.AllRpfs; + IEnumerable rpflist = loadedOnly ? gfc.ActiveMapRpfFiles.Values : rpfman.AllRpfs; var results = new List(); foreach (var rpf in rpflist) @@ -319,7 +319,7 @@ namespace CodeWalker.World { var rpfman = gfc.RpfMan; - var rpflist = loadedOnly ? gfc.ActiveMapRpfFiles.Values.ToList() : rpfman.AllRpfs; + IEnumerable rpflist = loadedOnly ? gfc.ActiveMapRpfFiles.Values : rpfman.AllRpfs; var results = new List(); foreach (var rpf in rpflist) diff --git a/CodeWalker/WorldForm.Designer.cs b/CodeWalker/WorldForm.Designer.cs index 42d8944..9aa06eb 100644 --- a/CodeWalker/WorldForm.Designer.cs +++ b/CodeWalker/WorldForm.Designer.cs @@ -32,6 +32,7 @@ namespace CodeWalker { this.components = new System.ComponentModel.Container(); System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(WorldForm)); + System.Windows.Forms.Label label34; this.StatusStrip = new System.Windows.Forms.StatusStrip(); this.StatusLabel = new System.Windows.Forms.ToolStripStatusLabel(); this.MousedLabel = new System.Windows.Forms.ToolStripStatusLabel(); @@ -136,6 +137,7 @@ namespace CodeWalker this.TimedEntitiesCheckBox = new System.Windows.Forms.CheckBox(); this.FieldOfViewTrackBar = new System.Windows.Forms.TrackBar(); this.OptionsRenderTabPage = new System.Windows.Forms.TabPage(); + this.AntiAliasingTrackBar = new System.Windows.Forms.TrackBar(); this.FarClipUpDown = new System.Windows.Forms.NumericUpDown(); this.label32 = new System.Windows.Forms.Label(); this.NearClipUpDown = new System.Windows.Forms.NumericUpDown(); @@ -223,6 +225,7 @@ namespace CodeWalker this.ToolsMenuSelectionInfo = new System.Windows.Forms.ToolStripMenuItem(); this.ToolsMenuProjectWindow = new System.Windows.Forms.ToolStripMenuItem(); this.ToolsMenuCutsceneViewer = new System.Windows.Forms.ToolStripMenuItem(); + this.ToolsMenuAudioExplorer = new System.Windows.Forms.ToolStripMenuItem(); this.ToolsMenuWorldSearch = new System.Windows.Forms.ToolStripMenuItem(); this.ToolsMenuBinarySearch = new System.Windows.Forms.ToolStripMenuItem(); this.ToolsMenuJenkGen = new System.Windows.Forms.ToolStripMenuItem(); @@ -310,7 +313,8 @@ namespace CodeWalker this.ToolbarPanel = new System.Windows.Forms.Panel(); this.SubtitleLabel = new System.Windows.Forms.Label(); this.SubtitleTimer = new System.Windows.Forms.Timer(this.components); - this.ToolsMenuAudioExplorer = new System.Windows.Forms.ToolStripMenuItem(); + this.AntiAliasingValue = new System.Windows.Forms.Label(); + label34 = new System.Windows.Forms.Label(); this.StatusStrip.SuspendLayout(); this.ToolsPanel.SuspendLayout(); this.ToolsTabControl.SuspendLayout(); @@ -340,6 +344,7 @@ namespace CodeWalker ((System.ComponentModel.ISupportInitialize)(this.CollisionMeshRangeTrackBar)).BeginInit(); ((System.ComponentModel.ISupportInitialize)(this.FieldOfViewTrackBar)).BeginInit(); this.OptionsRenderTabPage.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.AntiAliasingTrackBar)).BeginInit(); ((System.ComponentModel.ISupportInitialize)(this.FarClipUpDown)).BeginInit(); ((System.ComponentModel.ISupportInitialize)(this.NearClipUpDown)).BeginInit(); this.OptionsHelpersTabPage.SuspendLayout(); @@ -1686,6 +1691,9 @@ namespace CodeWalker // // OptionsRenderTabPage // + this.OptionsRenderTabPage.Controls.Add(this.AntiAliasingValue); + this.OptionsRenderTabPage.Controls.Add(label34); + this.OptionsRenderTabPage.Controls.Add(this.AntiAliasingTrackBar); this.OptionsRenderTabPage.Controls.Add(this.FarClipUpDown); this.OptionsRenderTabPage.Controls.Add(this.label32); this.OptionsRenderTabPage.Controls.Add(this.NearClipUpDown); @@ -1708,6 +1716,27 @@ namespace CodeWalker this.OptionsRenderTabPage.Text = "Render"; this.OptionsRenderTabPage.UseVisualStyleBackColor = true; // + // label34 + // + label34.AutoSize = true; + label34.Location = new System.Drawing.Point(7, 253); + label34.Name = "label34"; + label34.Size = new System.Drawing.Size(64, 13); + label34.TabIndex = 63; + label34.Text = "Anti-Aliasing"; + // + // AntiAliasingTrackBar + // + this.AntiAliasingTrackBar.LargeChange = 1; + this.AntiAliasingTrackBar.Location = new System.Drawing.Point(10, 269); + this.AntiAliasingTrackBar.Maximum = 8; + this.AntiAliasingTrackBar.Minimum = 1; + this.AntiAliasingTrackBar.Name = "AntiAliasingTrackBar"; + this.AntiAliasingTrackBar.Size = new System.Drawing.Size(184, 45); + this.AntiAliasingTrackBar.TabIndex = 62; + this.AntiAliasingTrackBar.Value = 1; + this.AntiAliasingTrackBar.ValueChanged += new System.EventHandler(this.AntiAliasingTrackBar_ValueChanged); + // // FarClipUpDown // this.FarClipUpDown.Increment = new decimal(new int[] { @@ -2762,12 +2791,12 @@ namespace CodeWalker this.ToolsMenuExtractShaders, this.ToolsMenuOptions}); this.ToolsMenu.Name = "ToolsMenu"; - this.ToolsMenu.Size = new System.Drawing.Size(181, 356); + this.ToolsMenu.Size = new System.Drawing.Size(170, 334); // // ToolsMenuRPFBrowser // this.ToolsMenuRPFBrowser.Name = "ToolsMenuRPFBrowser"; - this.ToolsMenuRPFBrowser.Size = new System.Drawing.Size(180, 22); + this.ToolsMenuRPFBrowser.Size = new System.Drawing.Size(169, 22); this.ToolsMenuRPFBrowser.Text = "RPF Browser..."; this.ToolsMenuRPFBrowser.Visible = false; this.ToolsMenuRPFBrowser.Click += new System.EventHandler(this.ToolsMenuRPFBrowser_Click); @@ -2775,14 +2804,14 @@ namespace CodeWalker // ToolsMenuRPFExplorer // this.ToolsMenuRPFExplorer.Name = "ToolsMenuRPFExplorer"; - this.ToolsMenuRPFExplorer.Size = new System.Drawing.Size(180, 22); + this.ToolsMenuRPFExplorer.Size = new System.Drawing.Size(169, 22); this.ToolsMenuRPFExplorer.Text = "RPF Explorer..."; this.ToolsMenuRPFExplorer.Click += new System.EventHandler(this.ToolsMenuRPFExplorer_Click); // // ToolsMenuSelectionInfo // this.ToolsMenuSelectionInfo.Name = "ToolsMenuSelectionInfo"; - this.ToolsMenuSelectionInfo.Size = new System.Drawing.Size(180, 22); + this.ToolsMenuSelectionInfo.Size = new System.Drawing.Size(169, 22); this.ToolsMenuSelectionInfo.Text = "Selection info..."; this.ToolsMenuSelectionInfo.Click += new System.EventHandler(this.ToolsMenuSelectionInfo_Click); // @@ -2790,7 +2819,7 @@ namespace CodeWalker // this.ToolsMenuProjectWindow.Enabled = false; this.ToolsMenuProjectWindow.Name = "ToolsMenuProjectWindow"; - this.ToolsMenuProjectWindow.Size = new System.Drawing.Size(180, 22); + this.ToolsMenuProjectWindow.Size = new System.Drawing.Size(169, 22); this.ToolsMenuProjectWindow.Text = "Project window..."; this.ToolsMenuProjectWindow.Click += new System.EventHandler(this.ToolsMenuProjectWindow_Click); // @@ -2798,14 +2827,22 @@ namespace CodeWalker // this.ToolsMenuCutsceneViewer.Enabled = false; this.ToolsMenuCutsceneViewer.Name = "ToolsMenuCutsceneViewer"; - this.ToolsMenuCutsceneViewer.Size = new System.Drawing.Size(180, 22); + this.ToolsMenuCutsceneViewer.Size = new System.Drawing.Size(169, 22); this.ToolsMenuCutsceneViewer.Text = "Cutscene viewer..."; this.ToolsMenuCutsceneViewer.Click += new System.EventHandler(this.ToolsMenuCutsceneViewer_Click); // + // ToolsMenuAudioExplorer + // + this.ToolsMenuAudioExplorer.Enabled = false; + this.ToolsMenuAudioExplorer.Name = "ToolsMenuAudioExplorer"; + this.ToolsMenuAudioExplorer.Size = new System.Drawing.Size(169, 22); + this.ToolsMenuAudioExplorer.Text = "Audio explorer..."; + this.ToolsMenuAudioExplorer.Click += new System.EventHandler(this.ToolsMenuAudioExplorer_Click); + // // ToolsMenuWorldSearch // this.ToolsMenuWorldSearch.Name = "ToolsMenuWorldSearch"; - this.ToolsMenuWorldSearch.Size = new System.Drawing.Size(180, 22); + this.ToolsMenuWorldSearch.Size = new System.Drawing.Size(169, 22); this.ToolsMenuWorldSearch.Text = "World search..."; this.ToolsMenuWorldSearch.Click += new System.EventHandler(this.ToolsMenuWorldSearch_Click); // @@ -2813,14 +2850,14 @@ namespace CodeWalker // this.ToolsMenuBinarySearch.Enabled = false; this.ToolsMenuBinarySearch.Name = "ToolsMenuBinarySearch"; - this.ToolsMenuBinarySearch.Size = new System.Drawing.Size(180, 22); + this.ToolsMenuBinarySearch.Size = new System.Drawing.Size(169, 22); this.ToolsMenuBinarySearch.Text = "Binary search..."; this.ToolsMenuBinarySearch.Click += new System.EventHandler(this.ToolsMenuBinarySearch_Click); // // ToolsMenuJenkGen // this.ToolsMenuJenkGen.Name = "ToolsMenuJenkGen"; - this.ToolsMenuJenkGen.Size = new System.Drawing.Size(180, 22); + this.ToolsMenuJenkGen.Size = new System.Drawing.Size(169, 22); this.ToolsMenuJenkGen.Text = "JenkGen..."; this.ToolsMenuJenkGen.Click += new System.EventHandler(this.ToolsMenuJenkGen_Click); // @@ -2828,42 +2865,42 @@ namespace CodeWalker // this.ToolsMenuJenkInd.Enabled = false; this.ToolsMenuJenkInd.Name = "ToolsMenuJenkInd"; - this.ToolsMenuJenkInd.Size = new System.Drawing.Size(180, 22); + this.ToolsMenuJenkInd.Size = new System.Drawing.Size(169, 22); this.ToolsMenuJenkInd.Text = "JenkInd..."; this.ToolsMenuJenkInd.Click += new System.EventHandler(this.ToolsMenuJenkInd_Click); // // ToolsMenuExtractScripts // this.ToolsMenuExtractScripts.Name = "ToolsMenuExtractScripts"; - this.ToolsMenuExtractScripts.Size = new System.Drawing.Size(180, 22); + this.ToolsMenuExtractScripts.Size = new System.Drawing.Size(169, 22); this.ToolsMenuExtractScripts.Text = "Extract scripts..."; this.ToolsMenuExtractScripts.Click += new System.EventHandler(this.ToolsMenuExtractScripts_Click); // // ToolsMenuExtractTextures // this.ToolsMenuExtractTextures.Name = "ToolsMenuExtractTextures"; - this.ToolsMenuExtractTextures.Size = new System.Drawing.Size(180, 22); + this.ToolsMenuExtractTextures.Size = new System.Drawing.Size(169, 22); this.ToolsMenuExtractTextures.Text = "Extract textures..."; this.ToolsMenuExtractTextures.Click += new System.EventHandler(this.ToolsMenuExtractTextures_Click); // // ToolsMenuExtractRawFiles // this.ToolsMenuExtractRawFiles.Name = "ToolsMenuExtractRawFiles"; - this.ToolsMenuExtractRawFiles.Size = new System.Drawing.Size(180, 22); + this.ToolsMenuExtractRawFiles.Size = new System.Drawing.Size(169, 22); this.ToolsMenuExtractRawFiles.Text = "Extract raw files..."; this.ToolsMenuExtractRawFiles.Click += new System.EventHandler(this.ToolsMenuExtractRawFiles_Click); // // ToolsMenuExtractShaders // this.ToolsMenuExtractShaders.Name = "ToolsMenuExtractShaders"; - this.ToolsMenuExtractShaders.Size = new System.Drawing.Size(180, 22); + this.ToolsMenuExtractShaders.Size = new System.Drawing.Size(169, 22); this.ToolsMenuExtractShaders.Text = "Extract shaders..."; this.ToolsMenuExtractShaders.Click += new System.EventHandler(this.ToolsMenuExtractShaders_Click); // // ToolsMenuOptions // this.ToolsMenuOptions.Name = "ToolsMenuOptions"; - this.ToolsMenuOptions.Size = new System.Drawing.Size(180, 22); + this.ToolsMenuOptions.Size = new System.Drawing.Size(169, 22); this.ToolsMenuOptions.Text = "Options..."; this.ToolsMenuOptions.Click += new System.EventHandler(this.ToolsMenuOptions_Click); // @@ -3579,7 +3616,7 @@ namespace CodeWalker this.SubtitleLabel.Font = new System.Drawing.Font("Microsoft Sans Serif", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); this.SubtitleLabel.Location = new System.Drawing.Point(455, 555); this.SubtitleLabel.Name = "SubtitleLabel"; - this.SubtitleLabel.Size = new System.Drawing.Size(84, 18); + this.SubtitleLabel.Size = new System.Drawing.Size(83, 18); this.SubtitleLabel.TabIndex = 8; this.SubtitleLabel.Text = "Test Subtitle"; this.SubtitleLabel.TextAlign = System.Drawing.ContentAlignment.TopCenter; @@ -3590,13 +3627,14 @@ namespace CodeWalker // this.SubtitleTimer.Tick += new System.EventHandler(this.SubtitleTimer_Tick); // - // ToolsMenuAudioExplorer + // AntiAliasingValue // - this.ToolsMenuAudioExplorer.Enabled = false; - this.ToolsMenuAudioExplorer.Name = "ToolsMenuAudioExplorer"; - this.ToolsMenuAudioExplorer.Size = new System.Drawing.Size(180, 22); - this.ToolsMenuAudioExplorer.Text = "Audio explorer..."; - this.ToolsMenuAudioExplorer.Click += new System.EventHandler(this.ToolsMenuAudioExplorer_Click); + this.AntiAliasingValue.AutoSize = true; + this.AntiAliasingValue.Location = new System.Drawing.Point(147, 253); + this.AntiAliasingValue.Name = "AntiAliasingValue"; + this.AntiAliasingValue.Size = new System.Drawing.Size(13, 13); + this.AntiAliasingValue.TabIndex = 64; + this.AntiAliasingValue.Text = "1"; // // WorldForm // @@ -3662,6 +3700,7 @@ namespace CodeWalker ((System.ComponentModel.ISupportInitialize)(this.FieldOfViewTrackBar)).EndInit(); this.OptionsRenderTabPage.ResumeLayout(false); this.OptionsRenderTabPage.PerformLayout(); + ((System.ComponentModel.ISupportInitialize)(this.AntiAliasingTrackBar)).EndInit(); ((System.ComponentModel.ISupportInitialize)(this.FarClipUpDown)).EndInit(); ((System.ComponentModel.ISupportInitialize)(this.NearClipUpDown)).EndInit(); this.OptionsHelpersTabPage.ResumeLayout(false); @@ -3968,5 +4007,7 @@ namespace CodeWalker private System.Windows.Forms.ToolStripMenuItem ToolbarSelectCalmingQuadButton; private System.Windows.Forms.ToolStripMenuItem ToolbarSelectWaveQuadButton; private System.Windows.Forms.ToolStripMenuItem ToolsMenuAudioExplorer; + private System.Windows.Forms.TrackBar AntiAliasingTrackBar; + private System.Windows.Forms.Label AntiAliasingValue; } } \ No newline at end of file diff --git a/CodeWalker/WorldForm.cs b/CodeWalker/WorldForm.cs index 0e4e6a7..1c5bf57 100644 --- a/CodeWalker/WorldForm.cs +++ b/CodeWalker/WorldForm.cs @@ -17,6 +17,7 @@ using CodeWalker.Rendering; using CodeWalker.GameFiles; using CodeWalker.Properties; using CodeWalker.Tools; +using CodeWalker.WinForms; namespace CodeWalker { @@ -29,7 +30,7 @@ namespace CodeWalker volatile bool formopen = false; volatile bool running = false; - volatile bool pauserendering = false; + public bool Pauserendering { get; set; } volatile bool initialised = false; Stopwatch frametimer = new Stopwatch(); @@ -262,6 +263,9 @@ namespace CodeWalker ViewModeComboBox.SelectedIndex = startupviewmode; BoundsStyleComboBox.SelectedIndex = 0; //LoadSettings will handle this + AntiAliasingValue.Text = Settings.Default.AntiAliasing.ToString(); + AntiAliasingTrackBar.Value = Settings.Default.AntiAliasing; + SelectionModeComboBox.SelectedIndex = 0; //Entity mode ShowSelectedExtensionTab(false); @@ -366,7 +370,10 @@ namespace CodeWalker formopen = true; - new Thread(new ThreadStart(ContentThread)).Start(); + + new Task(ContentThread, TaskCreationOptions.LongRunning).Start(TaskScheduler.Default); + //Task.Run(ContentThread, ContentThread) + //new Thread(new ThreadStart(ContentThread)).Start(); frametimer.Start(); } @@ -394,13 +401,28 @@ namespace CodeWalker public void BuffersResized(int w, int h) { Renderer.BuffersResized(w, h); + + if (WindowState == FormWindowState.Minimized && gameFileCache.IsInited) + { + Console.WriteLine("Clearing cache"); + gameFileCache.Clear(); + gameFileCache.IsInited = true; + GC.Collect(); + } } public void RenderScene(DeviceContext context) { float elapsed = (float)frametimer.Elapsed.TotalSeconds; + + + if (elapsed < 0.016666) + { + Thread.Sleep((int)(0.016666 * elapsed) * 1000); + } + frametimer.Restart(); - if (pauserendering) return; + if (Pauserendering) return; GameFileCache.BeginFrame(); @@ -4246,6 +4268,8 @@ namespace CodeWalker private void ContentThread() { + Thread.CurrentThread.Name = "WorldForm ContentThread"; + Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId} {Thread.CurrentThread.Name}"); //main content loading thread. running = true; @@ -4269,7 +4293,12 @@ namespace CodeWalker return; } - gameFileCache.Init(UpdateStatus, LogError); + gameFileCache.UpdateStatus += UpdateStatus; + gameFileCache.ErrorLog += LogError; + if (!gameFileCache.IsInited) + { + gameFileCache.Init(); + } UpdateDlcListComboBox(gameFileCache.DlcNameList); @@ -4285,8 +4314,9 @@ namespace CodeWalker EnableDLCModsUI(); - - Task.Run(() => { + new Task(() => + { + Thread.CurrentThread.Name = "Renderer ContentThread"; while (formopen && !IsDisposed) //renderer content loop { bool rcItemsPending = Renderer.ContentThreadProc(); @@ -4296,7 +4326,7 @@ namespace CodeWalker Thread.Sleep(1); //sleep if there's nothing to do } } - }); + }, TaskCreationOptions.LongRunning).Start(TaskScheduler.Default); while (formopen && !IsDisposed) //main asset loop { @@ -6584,7 +6614,7 @@ namespace CodeWalker if (Renderer.Device == null) return; //can't do this with no device Cursor = Cursors.WaitCursor; - pauserendering = true; + Pauserendering = true; lock (Renderer.RenderSyncRoot) { @@ -6599,7 +6629,7 @@ namespace CodeWalker } } - pauserendering = false; + Pauserendering = false; Cursor = Cursors.Default; } @@ -7817,6 +7847,13 @@ namespace CodeWalker SubtitleTimer.Enabled = false; SubtitleLabel.Visible = false; } + + private void AntiAliasingTrackBar_ValueChanged(object sender, EventArgs e) + { + Settings.Default.AntiAliasing = AntiAliasingTrackBar.Value; + Settings.Default.Save(); + AntiAliasingValue.Text = Settings.Default.AntiAliasing.ToString(); + } } public enum WorldControlMode diff --git a/CodeWalker/WorldForm.resx b/CodeWalker/WorldForm.resx index 0e069f5..e1d97d2 100644 --- a/CodeWalker/WorldForm.resx +++ b/CodeWalker/WorldForm.resx @@ -120,6 +120,9 @@ 182, 17 + + False + dt1_lod hw1_lod @@ -237,12 +240,20 @@ ufo YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAB4SURBVDhP3ZC7DcAgDEQZKTMwHOvSIFriS7BlEB+HMic9 QJbvFThLUkpXzjkSpaeuzMPlEELx3jdsBauyCHBY6UWYPQI93KEljQD3jL6EGzN6x0bASyNYwkKU8Udm gd6TMnIikDJyIqjVNz8T7FgKrAwFX6lVinM3aJ05lWDPRRcAAAAASUVORK5CYII= + + + + + iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8 + YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAB0SURBVDhP7ZNBCoAgEEXnSJ3BqxmetNpaMLhVv5DNRJS2 + CxIeuvA9XSjtg5mHEILPxB6U7JyLxphmSkDK1o5x9dst87SUfTXwRsYsA+paT0BGDGsVOJ92hdz3Bz4f + wGPC48uu7w5IGd+gBlpRMgYCnRwyESUj3CsQkYNFDwAAAABJRU5ErkJggg== iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO - wwAADsMBx2+oZAAAAHRJREFUOE/tk0EKgCAQRedIncGrGZ602lowuFW/kM1ElLYLEh668D1dKO2DmYcQ + vQAADr0BR/uQrQAAAHRJREFUOE/tk0EKgCAQRedIncGrGZ602lowuFW/kM1ElLYLEh668D1dKO2DmYcQ gs/EHpTsnIvGmGZKQMrWjnH12y3ztJR9NfBGxiwD6lpPQEYMaxU4n3aF3PcHPh/AY8Ljy67vDkgZ36AG WlEyBgKdHDIRJSPcKxCRg0UPAAAAAElFTkSuQmCC @@ -250,7 +261,7 @@ ufo iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO - xAAADsQBlSsOGwAAAThJREFUOE+dk01ugzAQhTlBj+MDIJC4A1yEa7DMnlWaSiAu0ZI7BNi0XaRpF7Bg + vgAADr4B6kKxwAAAAThJREFUOE+dk01ugzAQhTlBj+MDIJC4A1yEa7DMnlWaSiAu0ZI7BNi0XaRpF7Bg 4/pzbMsQ0qod6SX2zHvPP4yDdUzTJBR2CieF2YAxOWFot6GKDwrlMAyyKAqZZZkMw1AjTVOdowYHrpFd w4if67p2os/L1wI2DwfuwkRNSitu2+NdA1szJqUVC7ZGYb9/dOQtA/6bptFjcxyBwY7zkfwL0KDF4ESC 7bHCx/miCf7qYJ1jjjYYx3Fm0nfDXfJWzhjMzuBweJJvr++b5K1dOQN7hP9AH0H96EvM83zh7q+2zsH1 @@ -258,12 +269,13 @@ ufo WBXYx9R1nV75RuyHKrrnzCcGjE1u9ZyD4BugoZigQ9xrngAAAABJRU5ErkJggg== - + iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8 - YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAB0SURBVDhP7ZNBCoAgEEXnSJ3BqxmetNpaMLhVv5DNRJS2 - CxIeuvA9XSjtg5mHEILPxB6U7JyLxphmSkDK1o5x9dst87SUfTXwRsYsA+paT0BGDGsVOJ92hdz3Bz4f - wGPC48uu7w5IGd+gBlpRMgYCnRwyESUj3CsQkYNFDwAAAABJRU5ErkJggg== + YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAACtSURBVDhPrZBBEsIgEAR5Gy/wFV55T/wHr+KgHuCKNsVY + ZI2JiU7VVIVlp7OL+1mllIr7cb8Ie++PQwQYITnnM24NWxoBgsQYm/l+gk699bMsRA4h1JTSPsg0Xert + em/mGwh3vW1Z7MvIABSWqXG3+iZHAEw1m4wD49oVANgVOL/VeSgeDAiX1mpWeKy9BIQiI+OxWQF77tG5 + 2Fc729BmeElf/3lNhORe+oecewDObEqX49RqCgAAAABJRU5ErkJggg== @@ -279,7 +291,7 @@ ufo iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO - wwAADsMBx2+oZAAAAH5JREFUOE+9kEEKwCAMBH2bL+hPfI/9ooe2B71aVpKS5iBB0i4MyCZZjcFdrbUu + vQAADr0BR/uQrQAAAH5JREFUOE+9kEEKwCAMBH2bL+hPfI/9ooe2B71aVpKS5iBB0i4MyCZZjcFdrbUu IdsuDMUYB/8H1Fo3HQCPynOhsZTSU0pPAM7wpiG4hcFAzns/j2uAMzzZo3ntDHiYkTWNXwCztAJr+ROl 0IhU+UTzMEsHkG2XS4CE7K8Uwg0o2F4o9CrlEwAAAABJRU5ErkJggg== @@ -292,15 +304,6 @@ ufo EcMw2DzPDMEke9AsYBrHs10vN4I1QqImwwDcFyMjQGaBHr5Bo8nEoYCnCQTGzVeI4oj6fIi+KHgoPBhC 4knCjTww9vxfbIUQNDEyiGIZ8t6tW/k0vC/AOpuiueNOLwVkUeylvju9FJCg8E1vM/2PlTv5UoervVTJ uQAAAABJRU5ErkJggg== - - - - - iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8 - YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAACtSURBVDhPrZBBEsIgEAR5Gy/wFV55T/wHr+KgHuCKNsVY - ZI2JiU7VVIVlp7OL+1mllIr7cb8Ie++PQwQYITnnM24NWxoBgsQYm/l+gk699bMsRA4h1JTSPsg0Xert - em/mGwh3vW1Z7MvIABSWqXG3+iZHAEw1m4wD49oVANgVOL/VeSgeDAiX1mpWeKy9BIQiI+OxWQF77tG5 - 2Fc729BmeElf/3lNhORe+oecewDObEqX49RqCgAAAABJRU5ErkJggg== @@ -386,6 +389,17 @@ ufo 4BJN+IjGo5O8ZJndGVhKxpjWWts551aih0fre+0BLaVchRAezPB2NXSSV/gVwXGYPJiVUt6ns1ghCDjn UQG86w3FToVgDcWCWS9Fvi/Ao0RVAcwUjwpyhzmf4n8BFApS5HZRwRuONGMbrIJ1JIN8O2QAAAAASUVO RK5CYII= + + + + + iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8 + YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAEvSURBVDhP3dK/K0dRGMfxKxRJopCSEkLya/guUhQRmQwG + WfwIkYySgYUSKUKJlOK/MBoMFMofYLUIsfJ+f3NuF3+A8tRree5zP/fcc070f6oHT/jAPTqQj6WvXvCM + TZQgG3H58gFGcYVLtGIN15jBNDbwiGNUIg4pQx8GsQuHhrCDW8yjHyns4Q0DcCXpykM5bFzgHGPYxw1G + UIVMtMHfWUUj4nIg/KurGIYrSAZYOXDGlbhXcZlegUO8Yxzb+BlQAwNW0G0jVAYK0AwHtnCEOyQDZvGC + ObTbKIIvLMA9WIYDizhFMsDjfsAZptCA9JcdfoVBvryOSbgCe4HPTuCz+BQMKEUvJmCy96ET1ehCuAf2 + 5ZF+uwdZKEYtmuBGFSIXhtejBe5PHX7dxL+qKPoEppRHcXOtiDsAAAAASUVORK5CYII= @@ -421,17 +435,6 @@ ufo rp3fhGJScIRLzKMLFTC9cMIu3nCDVUyjB6WkYA93mEWbAyH9cMImPuA+rWMA31YwBU82kF6BS32Er/aO M8zAh+OEghpcwQ2bg3uwBW8ewFd7xQkm0IA4oaAS7bh2KHjBIZbhV/D6GJkFphrdcIP8lFrAGPwPOjCO QdQiTqrAWNICd7gPnUj+xBKaU9dxfhTkjwV/FxU+AbsiGnc46OYIAAAAAElFTkSuQmCC - - - - - iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8 - YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAEvSURBVDhP3dK/K0dRGMfxKxRJopCSEkLya/guUhQRmQwG - WfwIkYySgYUSKUKJlOK/MBoMFMofYLUIsfJ+f3NuF3+A8tRree5zP/fcc070f6oHT/jAPTqQj6WvXvCM - TZQgG3H58gFGcYVLtGIN15jBNDbwiGNUIg4pQx8GsQuHhrCDW8yjHyns4Q0DcCXpykM5bFzgHGPYxw1G - UIVMtMHfWUUj4nIg/KurGIYrSAZYOXDGlbhXcZlegUO8Yxzb+BlQAwNW0G0jVAYK0AwHtnCEOyQDZvGC - ObTbKIIvLMA9WIYDizhFMsDjfsAZptCA9JcdfoVBvryOSbgCe4HPTuCz+BQMKEUvJmCy96ET1ehCuAf2 - 5ZF+uwdZKEYtmuBGFSIXhtejBe5PHX7dxL+qKPoEppRHcXOtiDsAAAAASUVORK5CYII= diff --git a/Shaders/BasicPS.cso b/Shaders/BasicPS.cso index 266b52d..ef205ed 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 dd7ffd0..0b9bd99 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 88de5a1..c5a0cfa 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 8d5830f..b07db01 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 b9934be..b693b96 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 263e3ac..fa226fd 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 a8281ff..b82c4b8 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 b599dfc..0cfd0d3 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 869717d..15737f4 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 a0d28f4..54f52d8 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 d122132..046e698 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 bf97c16..38a2011 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 be70565..80c1507 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 c54e405..9c0a731 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 dcbaf37..777600f 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 16342c8..7289c49 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 b82fe3b..e8dc850 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 ebf7ff0..79f006f 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 1766649..7d29afd 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 8d98aac..b2aa00a 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 ae93506..a342127 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 3f02917..bff38e9 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 dfa7e88..b22a3d6 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 749efcd..193005f 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 887657e..1dfa051 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 c02ae1b..ba0108c 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 15ffed5..4b94869 100644 Binary files a/Shaders/BoundingBoxVS.cso and b/Shaders/BoundingBoxVS.cso differ diff --git a/Shaders/BoundingSphereVS.cso b/Shaders/BoundingSphereVS.cso index d2e6dab..bf60bfa 100644 Binary files a/Shaders/BoundingSphereVS.cso and b/Shaders/BoundingSphereVS.cso differ diff --git a/Shaders/BoundsPS.cso b/Shaders/BoundsPS.cso index 3c4ad34..96086ce 100644 Binary files a/Shaders/BoundsPS.cso and b/Shaders/BoundsPS.cso differ diff --git a/Shaders/CablePS.cso b/Shaders/CablePS.cso index 4793090..3a91124 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 c1c91ae..8cbf15d 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 0f93b2a..2e02183 100644 Binary files a/Shaders/CableVS.cso and b/Shaders/CableVS.cso differ diff --git a/Shaders/CloudsPS.cso b/Shaders/CloudsPS.cso index 66e1549..d526e13 100644 Binary files a/Shaders/CloudsPS.cso and b/Shaders/CloudsPS.cso differ diff --git a/Shaders/CloudsVS.cso b/Shaders/CloudsVS.cso index b835dec..0cce054 100644 Binary files a/Shaders/CloudsVS.cso and b/Shaders/CloudsVS.cso differ diff --git a/Shaders/DirLightPS.cso b/Shaders/DirLightPS.cso index 1eb2782..006452e 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 40884c6..76cd692 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 90659d2..13c5d07 100644 Binary files a/Shaders/DirLightVS.cso and b/Shaders/DirLightVS.cso differ diff --git a/Shaders/DistantLightsPS.cso b/Shaders/DistantLightsPS.cso index 14a55b3..89c8d02 100644 Binary files a/Shaders/DistantLightsPS.cso and b/Shaders/DistantLightsPS.cso differ diff --git a/Shaders/DistantLightsVS.cso b/Shaders/DistantLightsVS.cso index 3c51c1e..91be646 100644 Binary files a/Shaders/DistantLightsVS.cso and b/Shaders/DistantLightsVS.cso differ diff --git a/Shaders/LightPS.cso b/Shaders/LightPS.cso index 0f0d80f..519eeeb 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 1987339..b7e8b4d 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 c8d42a6..b6e8e06 100644 Binary files a/Shaders/LightVS.cso and b/Shaders/LightVS.cso differ diff --git a/Shaders/LodLightsPS.cso b/Shaders/LodLightsPS.cso index 220072d..369bd72 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 7231982..96fd048 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 1fc402d..d8a4fde 100644 Binary files a/Shaders/LodLightsVS.cso and b/Shaders/LodLightsVS.cso differ diff --git a/Shaders/MarkerPS.cso b/Shaders/MarkerPS.cso index 0c8f856..80f0dce 100644 Binary files a/Shaders/MarkerPS.cso and b/Shaders/MarkerPS.cso differ diff --git a/Shaders/MarkerVS.cso b/Shaders/MarkerVS.cso index 23f7f3a..cb158aa 100644 Binary files a/Shaders/MarkerVS.cso and b/Shaders/MarkerVS.cso differ diff --git a/Shaders/PPBloomFilterBPHCS.cso b/Shaders/PPBloomFilterBPHCS.cso index 4d92d58..cd52234 100644 Binary files a/Shaders/PPBloomFilterBPHCS.cso and b/Shaders/PPBloomFilterBPHCS.cso differ diff --git a/Shaders/PPBloomFilterVCS.cso b/Shaders/PPBloomFilterVCS.cso index b2f529e..c0f2355 100644 Binary files a/Shaders/PPBloomFilterVCS.cso and b/Shaders/PPBloomFilterVCS.cso differ diff --git a/Shaders/PPCopyPixelsPS.cso b/Shaders/PPCopyPixelsPS.cso index cf9474f..c026d50 100644 Binary files a/Shaders/PPCopyPixelsPS.cso and b/Shaders/PPCopyPixelsPS.cso differ diff --git a/Shaders/PPFinalPassPS.cso b/Shaders/PPFinalPassPS.cso index 1fe266d..032e016 100644 Binary files a/Shaders/PPFinalPassPS.cso and b/Shaders/PPFinalPassPS.cso differ diff --git a/Shaders/PPFinalPassVS.cso b/Shaders/PPFinalPassVS.cso index 693aa95..b9e0926 100644 Binary files a/Shaders/PPFinalPassVS.cso and b/Shaders/PPFinalPassVS.cso differ diff --git a/Shaders/PPLumBlendCS.cso b/Shaders/PPLumBlendCS.cso index c0918ad..7572d2e 100644 Binary files a/Shaders/PPLumBlendCS.cso and b/Shaders/PPLumBlendCS.cso differ diff --git a/Shaders/PPReduceTo0DCS.cso b/Shaders/PPReduceTo0DCS.cso index f26d3b5..61b98e6 100644 Binary files a/Shaders/PPReduceTo0DCS.cso and b/Shaders/PPReduceTo0DCS.cso differ diff --git a/Shaders/PPReduceTo1DCS.cso b/Shaders/PPReduceTo1DCS.cso index c84bc72..3a33ed7 100644 Binary files a/Shaders/PPReduceTo1DCS.cso and b/Shaders/PPReduceTo1DCS.cso differ diff --git a/Shaders/PPSSAAPS.cso b/Shaders/PPSSAAPS.cso index 96e450a..6a81c68 100644 Binary files a/Shaders/PPSSAAPS.cso and b/Shaders/PPSSAAPS.cso differ diff --git a/Shaders/PathBoxPS.cso b/Shaders/PathBoxPS.cso index 9d729ea..ac92ac8 100644 Binary files a/Shaders/PathBoxPS.cso and b/Shaders/PathBoxPS.cso differ diff --git a/Shaders/PathBoxVS.cso b/Shaders/PathBoxVS.cso index 2f93a0e..da08c2a 100644 Binary files a/Shaders/PathBoxVS.cso and b/Shaders/PathBoxVS.cso differ diff --git a/Shaders/PathDynVS.cso b/Shaders/PathDynVS.cso index c95932f..4a82df6 100644 Binary files a/Shaders/PathDynVS.cso and b/Shaders/PathDynVS.cso differ diff --git a/Shaders/PathPS.cso b/Shaders/PathPS.cso index a073089..496c54e 100644 Binary files a/Shaders/PathPS.cso and b/Shaders/PathPS.cso differ diff --git a/Shaders/PathVS.cso b/Shaders/PathVS.cso index c0fe1e2..e0433d6 100644 Binary files a/Shaders/PathVS.cso and b/Shaders/PathVS.cso differ diff --git a/Shaders/ShadowPS.cso b/Shaders/ShadowPS.cso index 8b046e5..ddff484 100644 Binary files a/Shaders/ShadowPS.cso and b/Shaders/ShadowPS.cso differ diff --git a/Shaders/ShadowVS.cso b/Shaders/ShadowVS.cso index f15cfe4..9fe2822 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 2e76539..b401516 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 2d0e2c8..0ae3714 100644 Binary files a/Shaders/SkyMoonPS.cso and b/Shaders/SkyMoonPS.cso differ diff --git a/Shaders/SkyMoonVS.cso b/Shaders/SkyMoonVS.cso index 4d5bb70..497ee91 100644 Binary files a/Shaders/SkyMoonVS.cso and b/Shaders/SkyMoonVS.cso differ diff --git a/Shaders/SkySunPS.cso b/Shaders/SkySunPS.cso index bc40e67..5891ee0 100644 Binary files a/Shaders/SkySunPS.cso and b/Shaders/SkySunPS.cso differ diff --git a/Shaders/SkySunVS.cso b/Shaders/SkySunVS.cso index 09fb786..61a1ce6 100644 Binary files a/Shaders/SkySunVS.cso and b/Shaders/SkySunVS.cso differ diff --git a/Shaders/SkydomePS.cso b/Shaders/SkydomePS.cso index 402ba7b..4b3572c 100644 Binary files a/Shaders/SkydomePS.cso and b/Shaders/SkydomePS.cso differ diff --git a/Shaders/SkydomeVS.cso b/Shaders/SkydomeVS.cso index eac8abe..19b9389 100644 Binary files a/Shaders/SkydomeVS.cso and b/Shaders/SkydomeVS.cso differ diff --git a/Shaders/TerrainPS.cso b/Shaders/TerrainPS.cso index 4629d7d..45884dd 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 b007a86..58d7b1a 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 97924d3..54f115f 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 6e41f29..9c921c5 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 2ad21bc..ba3826d 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 77641ed..893bc0b 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 9639d3b..89599e4 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 293ade2..b7a1256 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 ae60076..3462b28 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 7ec8c6e..7166909 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 679f296..da19e76 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 d3b15fa..72484c6 100644 Binary files a/Shaders/TreesLodVS.cso and b/Shaders/TreesLodVS.cso differ diff --git a/Shaders/WaterPS.cso b/Shaders/WaterPS.cso index 3257588..f3e3dca 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 43c391c..330dbad 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 93720ef..86ad351 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 0f8a8fa..671e589 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 e088fef..3ddf8ed 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 8c14e8c..6ec3f6b 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 82b5cde..fd29998 100644 Binary files a/Shaders/WidgetPS.cso and b/Shaders/WidgetPS.cso differ diff --git a/Shaders/WidgetVS.cso b/Shaders/WidgetVS.cso index 95db8a5..69042cc 100644 Binary files a/Shaders/WidgetVS.cso and b/Shaders/WidgetVS.cso differ