using CodeWalker.Core.GameFiles.FileTypes; using CodeWalker.Core.GameFiles.Resources; using CodeWalker.Core.Utils; using CodeWalker.World; using Collections.Pooled; using CommunityToolkit.HighPerformance; using SharpDX; using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; using System.Linq; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Xml; namespace CodeWalker.GameFiles { public partial class GameFileCache { public static GameFileCache Instance; public RpfManager RpfMan; public event Action UpdateStatus; public event Action ErrorLog; public int MaxItemsPerLoop = 3; //to keep things flowing... private ConcurrentQueue requestQueue = new ConcurrentQueue(); ////dynamic cache private Cache mainCache; public Cache MainCache => mainCache; public volatile bool IsInited = false; private volatile bool archetypesLoaded = false; private Dictionary archetypeDict; private ConcurrentDictionary textureLookup = new ConcurrentDictionary(); private Dictionary textureParents; private ConcurrentDictionary hdtexturelookup; private object updateSyncRoot = new object(); private object requestSyncRoot = new object(); private ReaderWriterLockSlim archetypeLock = new ReaderWriterLockSlim(); private ConcurrentDictionary projectFiles = new ConcurrentDictionary(); //for cache files loaded in project window: ydr,ydd,ytd,yft private Dictionary projectArchetypes = new Dictionary(); //used to override archetypes in world view with project ones //static indexes public Dictionary YdrDict { get; private set; } public Dictionary YddDict { get; private set; } public Dictionary YtdDict { get; private set; } public Dictionary YmapDict { get; private set; } public Dictionary YftDict { get; private set; } public Dictionary YbnDict { get; private set; } public Dictionary YcdDict { get; private set; } public Dictionary YedDict { get; private set; } public Dictionary YnvDict { get; private set; } public Dictionary Gxt2Dict { get; private set; } public Dictionary AllYmapsDict { get; private set; } //static cached data loaded at init public Dictionary YtypDict { get; set; } public List AllCacheFiles { get; set; } public Dictionary YmapHierarchyDict { get; set; } public List AllManifests { get; set; } public bool EnableDlc { get; set; } = false;//true;// public bool EnableMods { get; set; } = false; public List DlcPaths { get; set; } = new List(); public List DlcActiveRpfs { get; set; } = new List(); public List DlcSetupFiles { get; set; } = new List(); public List DlcExtraFolderMounts { get; set; } = new List(); public Dictionary DlcPatchedPaths { get; set; } = new Dictionary(StringComparer.OrdinalIgnoreCase); public List DlcCacheFileList { get; set; } = new List(); public List DlcNameList { get; set; } = new List(); public Dictionary DlcNameLookup { get; set; } = new Dictionary(); public string SelectedDlc { get; set; } = string.Empty; public Dictionary ActiveMapRpfFiles { get; set; } = new Dictionary(StringComparer.OrdinalIgnoreCase); public Dictionary TimeCycleModsDict = new Dictionary(); public Dictionary VehiclesInitDict { get; set; } public Dictionary PedsInitDict { get; set; } public Dictionary> PedVariationsDict { get; set; } public Dictionary> PedDrawableDicts { get; set; } public Dictionary> PedTextureDicts { get; set; } public Dictionary> PedClothDicts { get; set; } public Dictionary PedsFiles { get; set; } public List AudioDatRelFiles; public Dictionary AudioConfigDict; public Dictionary AudioSpeechDict; public Dictionary AudioSynthsDict; public Dictionary AudioMixersDict; public Dictionary AudioCurvesDict; public Dictionary AudioCategsDict; public Dictionary AudioSoundsDict; public Dictionary AudioGameDict; public List BaseRpfs { get; private set; } public List AllRpfs { get; private set; } public List DlcRpfs { get; private set; } public bool DoFullStringIndex = false; public bool BuildExtendedJenkIndex = true; public bool LoadArchetypes = true; public bool LoadVehicles = true; public bool LoadPeds = true; public bool LoadAudio = true; public bool PedsLoaded = false; public bool VehiclesLoaded = false; private bool PreloadedMode = true; private string GTAFolder; private string ExcludeFolders; private string Key; public int QueueLength => requestQueue.Count; public int ItemCount => mainCache.Count; public long MemoryUsage => mainCache.CurrentMemoryUsage; public long MaxMemoryUsage => mainCache.MaxMemoryUsage; public GameFileCache(long size, double cacheTime, string folder, string dlc, bool mods, string excludeFolders, string key) { mainCache = new Cache(size, cacheTime);//2GB is good as default SelectedDlc = dlc; EnableDlc = !string.IsNullOrEmpty(SelectedDlc); EnableMods = mods; GTAFolder = folder; ExcludeFolders = excludeFolders; Key = key; Instance = this; } public void Clear() { //IsInited = false; mainCache.Clear(); //textureLookup.Clear(); //requestQueue.Clear(); } public void SetGtaFolder(string folder) { Clear(); GTAFolder = folder; } public volatile bool IsIniting = false; public async Task InitAsync(Action updateStatus = null, Action errorLog = null, bool force = true) { if (IsIniting) { return; } IsIniting = true; try { using var _ = new DisposableTimer("GameFileCache.Init"); if (updateStatus != null) UpdateStatus += updateStatus; if (errorLog != null) ErrorLog += errorLog; if (RpfMan == null) { GTA5Keys.LoadFromPath(GTAFolder, Key); //EnableDlc = !string.IsNullOrEmpty(SelectedDlc); RpfMan = RpfManager.GetInstance(); RpfMan.ExcludePaths = GetExcludePaths(); RpfMan.EnableMods = EnableMods; RpfMan.BuildExtendedJenkIndex = BuildExtendedJenkIndex; RpfMan.Init(GTAFolder, UpdateStatus, ErrorLog);//, true); InitGlobal(); await InitDlcAsync(); //RE test area! //TestAudioRels(); //TestAudioYmts(); //TestAudioAwcs(); //TestMetas(); //TestPsos(); //TestRbfs(); //TestCuts(); //TestYlds(); //TestYeds(); //TestYcds(); //TestYtds(); //TestYbns(); //TestYdrs(); //TestYdds(); //TestYfts(); //TestYpts(); //TestYnvs(); //TestYvrs(); //TestYwrs(); //TestYmaps(); //TestYpdbs(); //TestYfds(); //TestMrfs(); //TestFxcs(); //TestPlacements(); //TestDrawables(); //TestCacheFiles(); //TestHeightmaps(); //TestWatermaps(); //GetShadersXml(); //GetArchetypeTimesList(); //string typestr = PsoTypes.GetTypesString(); } else { //GC.Collect(); //try free up some of the previously used memory.. } UpdateStatus?.Invoke("Scan complete"); IsInited = true; } catch(Exception ex) { Console.WriteLine(ex); throw; } finally { IsIniting = false; } } public async Task InitAsync(Action? updateStatus, Action? errorLog, List allRpfs, bool force = true) { if (IsIniting) { return; } ArgumentNullException.ThrowIfNull(allRpfs, nameof(allRpfs)); IsIniting = true; try { ETWEvents.Log.InitFileCacheStart(); using var _ = new DisposableTimer("GameFileCache.Init"); if (updateStatus is not null) { UpdateStatus += updateStatus; } if (errorLog is not null) { ErrorLog += errorLog; } Clear(); PreloadedMode = true; EnableDlc = true;//just so everything (mainly archetypes) will load.. EnableMods = false; RpfMan ??= RpfManager.GetInstance(); RpfMan.Init(allRpfs); AllRpfs = allRpfs; BaseRpfs = allRpfs; DlcRpfs = new List(); InitGlobalDicts(force); await InitManifestDicts(force); await InitGtxds(force); await InitArchetypeDicts(force); await InitStringDictsAsync(force); await InitAudio(force); //await Task.WhenAll( // Task.Run(() => InitGlobalDicts(force)), // InitManifestDicts(force), // InitGtxds(force), // InitArchetypeDicts(force), // InitStringDictsAsync(force), // InitAudio(force) //); vehicleFiles.Clear(); ETWEvents.Log.InitFileCacheStop(); IsInited = true; } catch (Exception ex) { ErrorLog?.Invoke(ex.ToString()); throw; } finally { IsIniting = false; } } private void InitGlobal() { using var _ = new DisposableTimer("InitGlobal"); BaseRpfs = GetModdedRpfList(RpfMan.BaseRpfs); AllRpfs = GetModdedRpfList(RpfMan.AllRpfs); DlcRpfs = GetModdedRpfList(RpfMan.DlcRpfs); InitGlobalDicts(); } private async Task InitDlcAsync(bool force = true) { InitDlcList(); InitActiveMapRpfFiles(); InitMapDicts(force); InitMapCaches(force); await InitManifestDicts(force); await InitArchetypeDicts(force); await InitStringDictsAsync(force); await InitVehicles(force); await InitPeds(force); await InitAudio(force); await InitGtxds(force); //await Task.WhenAll( // Task.Run(() => { // InitMapDicts(force); // InitMapCaches(force); // }), // Task.Run(() => InitManifestDicts(force)), // Task.Run(() => InitArchetypeDicts(force)), // Task.Run(() => InitStringDictsAsync(force)), // Task.Run(() => InitVehicles(force)), // Task.Run(() => InitPeds(force)), // Task.Run(() => InitAudio(force)), // Task.Run(() => InitGtxds(force)) //); vehicleFiles.Clear(); } 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"; //if (!EnableDlc) //{ // dlclistpath = "common.rpf\\data\\dlclist.xml"; //} var dlclistxml = RpfMan.GetFileXml(dlclistpath); DlcPaths.Clear(); if ((dlclistxml == null) || (dlclistxml.DocumentElement == null)) { ErrorLog?.Invoke("InitDlcList: Couldn't load " + dlclistpath + "."); Console.WriteLine("InitDlcList: Couldn't load " + dlclistpath + "."); } else { foreach (XmlNode pathsnode in dlclistxml.DocumentElement) { foreach (XmlNode itemnode in pathsnode.ChildNodes) { DlcPaths.Add(itemnode.InnerText.ToLowerInvariant().Replace('\\', '/').Replace("platform:", "x64")); } } } //get dlc path names in the appropriate format for reference by the dlclist paths Dictionary dlcDict = new Dictionary(80, StringComparer.OrdinalIgnoreCase); Dictionary dlcDict2 = new Dictionary(80, StringComparer.OrdinalIgnoreCase); foreach (RpfFile dlcrpf in DlcRpfs) { if (dlcrpf == null) continue; if (dlcrpf.Name.Equals("dlc.rpf", StringComparison.OrdinalIgnoreCase)) { string path = GetDlcRpfVirtualPath(dlcrpf.Path); string name = GetDlcNameFromPath(dlcrpf.Path); dlcDict[path] = dlcrpf; dlcDict2[name] = dlcrpf; } } var xmlContentFileNameTable = new NameTable(); //find all the paths for patched files in update.rpf and build the dict DlcPatchedPaths.Clear(); string updrpfpath = "update\\update.rpf"; var updrpffile = RpfMan.FindRpfFile(updrpfpath); if (updrpffile is not null) { XmlDocument updsetupdoc = RpfMan.GetFileXml(updrpfpath + "\\setup2.xml"); DlcSetupFile updsetupfile = new DlcSetupFile(); updsetupfile.Load(updsetupdoc); using XmlReader updcontentdoc = RpfMan.GetFileXmlReader($"{updrpfpath}\\{updsetupfile.datFile}", xmlContentFileNameTable); DlcContentFile updcontentfile = new DlcContentFile(); updcontentfile.Load(updcontentdoc); updsetupfile.DlcFile = updrpffile; updsetupfile.ContentFile = updcontentfile; updcontentfile.DlcFile = updrpffile; updsetupfile.deviceName = "update"; updcontentfile.LoadDicts(updsetupfile, RpfMan, this); if (updcontentfile.ExtraTitleUpdates != null) { foreach (var tumount in updcontentfile.ExtraTitleUpdates.Mounts) { var lpath = tumount.path.ToLowerInvariant(); var relpath = lpath.Replace('/', '\\').Replace("update:\\", ""); var dlcname = GetDlcNameFromPath(relpath); RpfFile dlcfile; dlcDict2.TryGetValue(dlcname, out dlcfile); if (dlcfile == null) { continue; } var dlcpath = $"{dlcfile.Path}\\"; var files = updrpffile.GetFiles(relpath, true); foreach (var file in files) { if (file is null) continue; var fpath = file.Path; var frelpath = fpath.Replace(updrpfpath, "update:").Replace('\\', '/').Replace(lpath, dlcpath).Replace('/', '\\'); if (frelpath.StartsWith("mods\\", StringComparison.OrdinalIgnoreCase)) { frelpath = frelpath.Substring(5); } DlcPatchedPaths[frelpath] = fpath; } } } } else { ErrorLog?.Invoke("InitDlcList: update.rpf not found!"); Console.WriteLine("InitDlcList: update.rpf not found!"); } DlcSetupFiles.Clear(); DlcExtraFolderMounts.Clear(); foreach (string path in DlcPaths) { RpfFile dlcfile; if (dlcDict.TryGetValue(path, out dlcfile)) { try { string setuppath = GetDlcPatchedPath($"{dlcfile.Path}\\setup2.xml"); XmlDocument setupdoc = RpfMan.GetFileXml(setuppath); DlcSetupFile setupfile = new DlcSetupFile(); setupfile.Load(setupdoc); string contentpath = GetDlcPatchedPath($"{dlcfile.Path}\\{setupfile.datFile}"); using XmlReader contentdoc = RpfMan.GetFileXmlReader(contentpath, xmlContentFileNameTable); DlcContentFile contentfile = new DlcContentFile(); contentfile.Load(contentdoc); setupfile.DlcFile = dlcfile; setupfile.ContentFile = contentfile; contentfile.DlcFile = dlcfile; contentfile.LoadDicts(setupfile, RpfMan, this); foreach (var extramount in contentfile.ExtraMounts.Values) { DlcExtraFolderMounts.Add(extramount); } DlcSetupFiles.Add(setupfile); } catch (Exception ex) { ErrorLog?.Invoke($"InitDlcList: Error processing DLC {path}\n{ex}"); Console.WriteLine(ex); } } } //load the DLC in the correct order.... DlcSetupFiles = DlcSetupFiles.OrderBy(o => o.order).ToList(); DlcNameList.Clear(); foreach (var sfile in DlcSetupFiles) { if ((sfile == null) || (sfile.DlcFile == null)) continue; DlcNameList.Add(GetDlcNameFromPath(sfile.DlcFile.Path)); } if (DlcNameList.Count > 0) { if (string.IsNullOrEmpty(SelectedDlc)) { SelectedDlc = DlcNameList[DlcNameList.Count - 1]; } } } private void InitImagesMetas() { //currently not used.. ////parse images.meta //string imagesmetapath = "common.rpf\\data\\levels\\gta5\\images.meta"; //if (EnableDlc) //{ // imagesmetapath = "update\\update.rpf\\common\\data\\levels\\gta5\\images.meta"; //} //var imagesmetaxml = RpfMan.GetFileXml(imagesmetapath); //var imagesnodes = imagesmetaxml.DocumentElement.ChildNodes; //List imagedatafilelist = new List(); //Dictionary imagedatafiles = new Dictionary(); //foreach (XmlNode node in imagesnodes) //{ // DlcContentDataFile datafile = new DlcContentDataFile(node); // string fname = datafile.filename.ToLower(); // fname = fname.Replace('\\', '/'); // imagedatafiles[fname] = datafile; // imagedatafilelist.Add(datafile); //} //filter ActiveMapFiles based on images.meta? //DlcContentDataFile imagesdata; //if (imagedatafiles.TryGetValue(path, out imagesdata)) //{ // ActiveMapRpfFiles[path] = baserpf; //} } 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) { string path = baserpf.Path.Replace('\\', '/'); if (path == "common.rpf") { ActiveMapRpfFiles["common"] = baserpf; } else { int bsind = path.IndexOf('/'); if ((bsind > 0) && (bsind < path.Length)) { path = "x64" + path.Substring(bsind); //if (ActiveMapRpfFiles.ContainsKey(path)) //{ } //x64d.rpf\levels\gta5\generic\cutsobjects.rpf // x64g.rpf\levels\gta5\generic\cutsobjects.rpf - identical? ActiveMapRpfFiles[path] = baserpf; } else { //do we need to include root rpf files? generally don't seem to contain map data? ActiveMapRpfFiles[path] = baserpf; } } } if (!EnableDlc) return; //don't continue for base title only foreach (var rpf in DlcRpfs) { if (rpf.Name.Equals("update.rpf", StringComparison.OrdinalIgnoreCase))//include this so that files not in child rpf's can be used.. { string path = rpf.Path.Replace('\\', '/'); ActiveMapRpfFiles[path] = rpf; break; } } DlcActiveRpfs.Clear(); DlcCacheFileList.Clear(); //int maxdlcorder = 10000000; Dictionary> overlays = new Dictionary>(StringComparer.OrdinalIgnoreCase); foreach (var setupfile in DlcSetupFiles) { if (setupfile.DlcFile != null) { //if (setupfile.order > maxdlcorder) // break; var contentfile = setupfile.ContentFile; var dlcfile = setupfile.DlcFile; DlcActiveRpfs.Add(dlcfile); for (int i = 1; i <= setupfile.subPackCount; i++) { var subpackPath = dlcfile.Path.Replace("\\dlc.rpf", $"\\dlc{i}.rpf"); var subpack = RpfMan.FindRpfFile(subpackPath); if (subpack != null) { DlcActiveRpfs.Add(subpack); if (setupfile.DlcSubpacks is null) setupfile.DlcSubpacks = new List(); setupfile.DlcSubpacks.Add(subpack); } } string dlcname = GetDlcNameFromPath(dlcfile.Path); 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! } foreach (var rpfkvp in contentfile.RpfDataFiles) { string umpath = GetDlcUnmountedPath(rpfkvp.Value.filename); string phpath = GetDlcRpfPhysicalPath(umpath, setupfile); //if (rpfkvp.Value.overlay) AddDlcOverlayRpf(rpfkvp.Key, umpath, setupfile, overlays); AddDlcActiveMapRpfFile(rpfkvp.Key, phpath, setupfile); } DlcExtraFolderMountFile? extramount; DlcContentDataFile? rpfdatafile; foreach (var changeset in contentfile.contentChangeSets) { if (changeset.useCacheLoader) { uint cachehash = JenkHash.GenHashLower(changeset.changeSetName); string cachefilename = $"{dlcname}_{cachehash}_cache_y.dat"; string cachefilepath = $"{dlcfile.Path}\\x64\\data\\cacheloaderdata_dlc\\{cachefilename}"; string cachefilepathpatched = GetDlcPatchedPath(cachefilepath); var entry = RpfMan.GetEntry(cachefilepathpatched); if (entry is not null) { DlcCacheFileList.Add(cachefilepathpatched); } else { if (setupfile.DlcSubpacks is not null) { foreach(var subfile in setupfile.DlcSubpacks) { cachefilepath = $"{subfile.Path}\\x64\\data\\cacheloaderdata_dlc\\{cachefilename}"; cachefilepathpatched = GetDlcPatchedPath(cachefilepath); entry = RpfMan.GetEntry(cachefilepathpatched); if (entry is not null) { DlcCacheFileList.Add(cachefilepathpatched); break; } } } } //if ((changeset.mapChangeSetData != null) && (changeset.mapChangeSetData.Count > 0)) //{ } //else //{ } } else { //if ((changeset.mapChangeSetData != null) && (changeset.mapChangeSetData.Count > 0)) //{ } //if (changeset.executionConditions != null) //{ } } //if (changeset.filesToInvalidate != null) //{ }//not used //if (changeset.filesToDisable != null) //{ }//not used if (changeset.filesToEnable != null) { foreach (string file in changeset.filesToEnable) { string dfn = GetDlcPlatformPath(file).ToLowerInvariant(); if (contentfile.ExtraMounts.TryGetValue(dfn, out extramount)) { //foreach (var rpfkvp in contentfile.RpfDataFiles) //{ // string umpath = GetDlcUnmountedPath(rpfkvp.Value.filename); // string phpath = GetDlcRpfPhysicalPath(umpath, setupfile); // //if (rpfkvp.Value.overlay) // AddDlcOverlayRpf(rpfkvp.Key, umpath, setupfile, overlays); // AddDlcActiveMapRpfFile(rpfkvp.Key, phpath); //} } else if (contentfile.RpfDataFiles.TryGetValue(dfn, out rpfdatafile)) { string phpath = GetDlcRpfPhysicalPath(rpfdatafile.filename, setupfile); //if (rpfdatafile.overlay) AddDlcOverlayRpf(dfn, rpfdatafile.filename, setupfile, overlays); AddDlcActiveMapRpfFile(dfn, phpath, setupfile); } } } //if (changeset.executionConditions != null) //{ } if (changeset.mapChangeSetData != null) { foreach (var mapcs in changeset.mapChangeSetData) { if (mapcs.filesToInvalidate != null) { foreach (string file in mapcs.filesToInvalidate) { string upath = GetDlcMountedPath(file); string fpath = GetDlcPlatformPath(upath); if (fpath.EndsWith(".rpf", StringComparison.OrdinalIgnoreCase)) { RemoveDlcActiveMapRpfFile(fpath, overlays); } } } if (mapcs.filesToEnable != null) { foreach (string file in mapcs.filesToEnable) { string fpath = GetDlcPlatformPath(file); string umpath = GetDlcUnmountedPath(fpath); string phpath = GetDlcRpfPhysicalPath(umpath, setupfile); AddDlcOverlayRpf(fpath, umpath, setupfile, overlays); AddDlcActiveMapRpfFile(fpath, phpath, setupfile); } } } } } if (dlcname.Equals(SelectedDlc, StringComparison.OrdinalIgnoreCase)) { break; //everything's loaded up to the selected DLC. } } } } private void AddDlcActiveMapRpfFile(string vpath, string phpath, DlcSetupFile setupfile) { if (phpath.EndsWith(".rpf", StringComparison.OrdinalIgnoreCase)) { RpfFile rpffile = RpfMan.FindRpfFile(phpath); if (rpffile != null) { ActiveMapRpfFiles[vpath.ToLowerInvariant()] = rpffile; } } else { } //how to handle individual files? eg interiorProxies.meta } private void AddDlcOverlayRpf(string path, string umpath, DlcSetupFile setupfile, Dictionary> overlays) { string opath = GetDlcOverlayPath(umpath, setupfile); if (opath == path) return; List overlayList; if (!overlays.TryGetValue(opath, out overlayList)) { overlayList = new List(); overlays[opath] = overlayList; } overlayList.Add(path); } private void RemoveDlcActiveMapRpfFile(string vpath, Dictionary> overlays) { List overlayList; if (overlays.TryGetValue(vpath, out overlayList)) { foreach (string overlayPath in overlayList) { if (ActiveMapRpfFiles.ContainsKey(overlayPath)) { ActiveMapRpfFiles.Remove(overlayPath); } } overlays.Remove(vpath); } if (ActiveMapRpfFiles.ContainsKey(vpath)) { ActiveMapRpfFiles.Remove(vpath); } else { } //nothing to remove? } private string GetDlcRpfPhysicalPath(string path, DlcSetupFile setupfile) { string devname = setupfile.deviceName.ToLowerInvariant(); string fpath = GetDlcPlatformPath(path).ToLowerInvariant(); string kpath = fpath;//.Replace(devname + ":\\", ""); string dlcpath = setupfile.DlcFile.Path; fpath = fpath.Replace(devname + ":", dlcpath); fpath = fpath.Replace("x64:", dlcpath + "\\x64").Replace('/', '\\'); if (setupfile.DlcSubpacks != null) { if (RpfMan.FindRpfFile(fpath) == null) { foreach (var subpack in setupfile.DlcSubpacks) { dlcpath = subpack.Path; var tpath = kpath.Replace(devname + ":", dlcpath); tpath = tpath.Replace("x64:", dlcpath + "\\x64").Replace('/', '\\'); if (RpfMan.FindRpfFile(tpath) != null) { return GetDlcPatchedPath(tpath); } } } } return GetDlcPatchedPath(fpath); } private string GetDlcOverlayPath(string path, DlcSetupFile setupfile) { string devname = setupfile.deviceName.ToLowerInvariant(); string fpath = path.Replace("%PLATFORM%", "x64").Replace('\\', '/').ToLowerInvariant(); string opath = fpath.Replace(devname + ":/", ""); return opath; } private string GetDlcRpfVirtualPath(string path) { path = path.Replace('\\', '/'); if (path.StartsWith("mods/", StringComparison.OrdinalIgnoreCase)) { path = path.Substring(5); } if (path.Length > 7) { path = path.Substring(0, path.Length - 7);//trim off "dlc.rpf" } if (path.StartsWith("x64", StringComparison.OrdinalIgnoreCase)) { int bsind = path.IndexOf('/'); //replace x64*.rpf if ((bsind > 0) && (bsind < path.Length)) { path = "x64" + path.Substring(bsind); } else { } //no hits here } else if (path.StartsWith("update/x64/dlcpacks", StringComparison.OrdinalIgnoreCase)) { path = path.Replace("update/x64/dlcpacks", "dlcpacks:"); } else { } //no hits here return path; } private string GetDlcNameFromPath(string path) { string[] parts = path.Split('\\'); if (parts.Length > 1) { return parts[parts.Length - 2]; } return path; } public static ReadOnlySpan GetFilePathWithoutDeviceName(ReadOnlySpan path) { var index = path.IndexOf(":"); if (index > 0) { return path.Slice(index + 2); } return path; } public static ReadOnlySpan GetDeviceNameFromPath(ReadOnlySpan path) { var index = path.IndexOf(":"); if (index > 0) { return path.Slice(0, index); } return path; } public static string GetDlcPlatformPath(string path) { return path.Replace("%PLATFORM%", "x64").Replace('\\', '/').Replace("platform:", "x64").ToLowerInvariant(); } private string GetDlcMountedPath(string path) { foreach (var efm in DlcExtraFolderMounts) { foreach (var fm in efm.FolderMounts) { if (fm.platform is null || fm.platform == "x64") { if (path.StartsWith(fm.path, StringComparison.OrdinalIgnoreCase)) { path = path.Replace(fm.path, fm.mountAs); } } } } return path; } private string GetDlcUnmountedPath(string path) { foreach (var efm in DlcExtraFolderMounts) { foreach (var fm in efm.FolderMounts) { if ((fm.platform == null) || (fm.platform == "x64")) { if (path.StartsWith(fm.mountAs, StringComparison.OrdinalIgnoreCase)) { path = path.Replace(fm.mountAs, fm.path); } } } } return path; } public string GetDlcPatchedPath(string path) { string p; if (DlcPatchedPaths.TryGetValue(path, out p)) { return p; } return path; } private List GetModdedRpfList(List list) { //if (!EnableMods) return new List(list); List rlist = new List(); RpfFile f; if (!EnableMods) { foreach (var file in list) { if (!file.Path.StartsWith("mods", StringComparison.OrdinalIgnoreCase)) { rlist.Add(file); } } } else { foreach (var file in list) { if (RpfMan.ModRpfDict.TryGetValue(file.Path, out f)) { rlist.Add(f); } else { if (file.Path.StartsWith("mods", StringComparison.OrdinalIgnoreCase)) { var basepath = file.Path.Substring(5); if (!RpfMan.RpfDict.ContainsKey(basepath)) //this file isn't overriding anything { rlist.Add(file); } } else { rlist.Add(file); } } } } return rlist; } private void InitGlobalDicts(bool force = false) { UpdateStatus?.Invoke("Building global dictionaries..."); using var timer = new DisposableTimer("InitGlobalDicts"); YdrDict ??= new Dictionary(80000); YddDict ??= new Dictionary(11000); YtdDict ??= new Dictionary(40000); YftDict ??= new Dictionary(40000); YcdDict ??= new Dictionary(20000); YedDict ??= new Dictionary(300); foreach (var rpffile in AllRpfs.AsSpan()) { if (rpffile.AllEntries is null) continue; foreach (var entry in rpffile.AllEntries.Span) { if (entry is RpfFileEntry fentry) { if (entry.IsExtension(".ydr")) { YdrDict[entry.ShortNameHash] = fentry; } else if (entry.IsExtension(".ydd")) { YddDict[entry.ShortNameHash] = fentry; } else if (entry.IsExtension(".ytd")) { YtdDict[entry.ShortNameHash] = fentry; } else if (entry.IsExtension(".yft")) { YftDict[entry.ShortNameHash] = fentry; } else if (entry.IsExtension(".ycd")) { YcdDict[entry.ShortNameHash] = fentry; } else if (entry.IsExtension(".yed")) { YedDict[entry.ShortNameHash] = fentry; } } } } Console.WriteLine($"YdrDict: {YdrDict.Count}; YddDict: {YddDict.Count}; YtdDict: {YtdDict.Count}; YftDict: {YftDict.Count}; YcdDict: {YcdDict.Count}; YedDict: {YedDict.Count}"); } private void InitMapDicts(bool force = true) { UpdateStatus?.Invoke("Building map dictionaries..."); using var _ = new DisposableTimer("InitMapDicts"); YmapDict ??= new Dictionary(4600); YbnDict ??= new Dictionary(8800); YnvDict ??= new Dictionary(4500); AllYmapsDict ??= new Dictionary(11000); YmapDict.Clear(); YbnDict.Clear(); YnvDict.Clear(); AllYmapsDict.Clear(); foreach (var rpffile in ActiveMapRpfFiles.Values) //RpfMan.BaseRpfs) { if (rpffile.AllEntries is null) continue; foreach (var entry in rpffile.AllEntries.Span) { if (entry is RpfFileEntry fentry) { if (entry.IsExtension(".ymap")) { //YmapDict[entry.NameHash] = fentry; YmapDict[entry.ShortNameHash] = fentry; AllYmapsDict[entry.ShortNameHash] = fentry; } else if (entry.IsExtension(".ybn")) { //YbnDict[entry.NameHash] = fentry; YbnDict[entry.ShortNameHash] = fentry; } else if (entry.IsExtension(".ynv")) { YnvDict[entry.ShortNameHash] = fentry; } } } } Console.WriteLine($"YmapDict: {YmapDict.Count}; YbnDict: {YbnDict.Count}; YnvDict: {YnvDict.Count}; AllYmapsDict: {AllYmapsDict.Count};"); } private async Task InitManifestDicts(bool force = true) { UpdateStatus?.Invoke("Loading manifests..."); using var _ = new DisposableTimer("InitManifestDicts"); AllManifests ??= new List(2000); hdtexturelookup ??= new ConcurrentDictionary(Environment.ProcessorCount, 24000); AllManifests.Clear(); hdtexturelookup.Clear(); IEnumerable rpfs = PreloadedMode ? AllRpfs : ActiveMapRpfFiles.Values; var allManifests = new ConcurrentBag(); var filteredRpfs = rpfs.Where(p => p.AllEntries is not null).ToArray(); await Parallel.ForAsync(0, filteredRpfs.Length, async (i, cancellationToken) => { var file = filteredRpfs[i]; //manifest and meta parsing.. foreach (RpfEntry entry in file.AllEntries) { if (!entry.IsExtension(".ymf")) { continue; } try { UpdateStatus?.Invoke(entry.Path); YmfFile? ymffile = await RpfManager.GetFileAsync(entry); if (ymffile is null) return; allManifests.Add(ymffile); foreach(var b in ymffile.HDTxdAssetBindings) { var targetasset = JenkHash.GenHashLower(b._targetAsset.GetBytesNullTerminated()); var hdtxd = JenkHash.GenHashLower(b._HDTxd.GetBytesNullTerminated()); hdtexturelookup[targetasset] = hdtxd; } } catch (Exception ex) { string errstr = $"{entry.Path}\n{ex}"; ErrorLog?.Invoke(errstr); Console.WriteLine(errstr); } } }).ConfigureAwait(false); AllManifests.AddRange(allManifests); Console.WriteLine($"hdtexturelookup: {hdtexturelookup.Count}; AllManifests: {AllManifests.Count};"); } private static ConcurrentDictionary vehicleFiles = new(4, 72, StringComparer.OrdinalIgnoreCase); [SkipLocalsInit] private async Task InitGtxds(bool force = true) { UpdateStatus?.Invoke("Loading global texture list..."); using var timer = new DisposableTimer("InitGtxds"); var parentTxds = new ConcurrentDictionary(4, 4000); IEnumerable rpfs = PreloadedMode ? AllRpfs : (IEnumerable)ActiveMapRpfFiles.Values; [SkipLocalsInit] static void addTxdRelationships(PooledDictionary from, ConcurrentDictionary parentTxds) { foreach (var (key, value) in from) { uint chash = JenkHash.GenHashLower(key); uint phash = JenkHash.GenHashLower(value); parentTxds.TryAdd(chash, phash); } } IEnumerable allRpfs = rpfs; if (EnableDlc && DlcActiveRpfs.Count > 0) { allRpfs = allRpfs.Concat(DlcActiveRpfs); } var filteredRpfs = allRpfs.ToArray(); await Parallel.ForAsync(0, filteredRpfs.Length, async (i, cancellationToken) => { var file = filteredRpfs[i]; foreach (RpfEntry entry in file.AllEntries) { if ( entry.Name.Equals("gtxd.ymt", StringComparison.OrdinalIgnoreCase) || entry.Name.Equals("gtxd.meta", StringComparison.OrdinalIgnoreCase) || entry.Name.Equals("mph4_gtxd.ymt", StringComparison.OrdinalIgnoreCase) ) { GtxdFile? ymt = await RpfManager.GetFileAsync(entry); var txdRelationShips = ymt?.TxdRelationships; if (txdRelationShips is not null) { addTxdRelationships(txdRelationShips, parentTxds); } ymt?.Dispose(); } else if (entry.Name.Equals("vehicles.meta", StringComparison.OrdinalIgnoreCase)) { if (!vehicleFiles.TryGetValue(entry.Path, out var vf)) { vf = vehicleFiles[entry.Path] = await RpfManager.GetFileAsync(entry); } var txdRelationShips = vf?.TxdRelationships; if (txdRelationShips is not null) { addTxdRelationships(txdRelationShips, parentTxds); } } } }); textureParents = parentTxds.ToDictionary(p => p.Key, p => p.Value); //ensure resident global texture dicts: var ytdEntry1 = GetYtdEntry(JenkHash.GenHash("mapdetail")); if (ytdEntry1 is null) { Console.WriteLine("Failed to load mapdetail.ytd!"); throw new Exception("Failed to load mapdetail.ytd"); } else { YtdFile ytd1 = new YtdFile(); LoadFile(ytd1); AddTextureLookups(ytd1); } var ytdEntry2 = GetYtdEntry(JenkHash.GenHash("vehshare")); if (ytdEntry2 is null) { Console.WriteLine("Failed to load vehshare.ytd!"); throw new Exception("Failed to laod vehshare.ytd!"); } else { YtdFile ytd2 = new YtdFile(); LoadFile(ytd2); AddTextureLookups(ytd2); } Console.WriteLine($"textureParents: {textureParents.Count}"); } private void InitMapCaches(bool force = true) { UpdateStatus?.Invoke("Loading cache..."); using var _ = new DisposableTimer("InitMapCaches"); AllCacheFiles = new List(1); YmapHierarchyDict = new Dictionary(5000); CacheDatFile loadCacheFile(string path, bool finalAttempt) { try { var cache = RpfMan.GetFile(path); if (cache is not null) { AllCacheFiles.Add(cache); foreach (var node in cache.AllMapNodes) { if (YmapDict.ContainsKey(node.Name)) { YmapHierarchyDict[node.Name] = node; } else { //Console.WriteLine($"Couldn't find {node.Name} in YmapDict"); } //ymap not found... } } else if (finalAttempt) { ErrorLog($"{path}: main cachefile not loaded! Possibly an unsupported GTAV installation version."); } else //update\x64\dlcpacks\mpspecialraces\dlc.rpf\x64\data\cacheloaderdata_dlc\mpspecialraces_3336915258_cache_y.dat (hash of: mpspecialraces_interior_additions) { Console.WriteLine($"Failed to load {path}"); } return cache; } catch (Exception ex) { ErrorLog($"{path}: {ex}"); } return null; } CacheDatFile maincache = null; maincache = loadCacheFile("common.rpf\\data\\gta5_cache_y.dat", true); if (EnableDlc) { maincache = loadCacheFile("update\\update.rpf\\common\\data\\gta5_cache_y.dat", false); maincache = loadCacheFile("update\\update2.rpf\\common\\data\\gta5_cache_y.dat", true); } if (EnableDlc) { foreach (string dlccachefile in DlcCacheFileList) { loadCacheFile(dlccachefile, false); } } Console.WriteLine($"AllCacheFiles: {AllCacheFiles.Count}; YmapHierarchyDict: {YmapHierarchyDict.Count};"); } private ConcurrentBag ytypCache; private async Task InitArchetypeDicts(bool force = true) { UpdateStatus?.Invoke("Loading archetypes..."); ytypCache = new ConcurrentBag(); archetypesLoaded = false; try { archetypeDict ??= new Dictionary(175000); archetypeDict.Clear(); using var timer = new DisposableTimer("InitArchetypeDicts"); var rpfs = EnableDlc ? AllRpfs : BaseRpfs; var allYtypes = rpfs .Where(p => p.AllEntries != null); if (!EnableDlc) { allYtypes = allYtypes.Where(p => !p.Path.StartsWith("update", StringComparison.OrdinalIgnoreCase)); } //.SelectMany(p => p.AllEntries.Where(p => p.IsExtension(".ytyp"))); //await allYtypes.ParallelForEachAsync(async (file) => //{ // foreach (var entry in file.AllEntries) { // if (!entry.Name.EndsWith(".ytyp", StringComparison.OrdinalIgnoreCase)) continue; // try // { // AddYtypToDictionary(entry); // } // catch (Exception ex) // { // ErrorLog(entry.Path + ": " + ex.ToString()); // } // } //}, maxDegreeOfParallelism: 8); using (var timer1 = new DisposableTimer("InitArchetypeDicts GetFiles")) { var allYtypsArr = allYtypes.ToArray(); await Parallel.ForAsync(0, allYtypsArr.Length, async (i, cancellationToken) => { var file = allYtypsArr[i]; foreach (var entry in file.AllEntries) { if (entry.IsExtension(".ytyp")) { try { var ytypFile = await RpfManager.GetFileAsync(entry); if (ytypFile is not null) ytypCache.Add(ytypFile); else Console.WriteLine($"Couldn't load file {entry.Path}"); } catch (Exception ex) { Console.WriteLine($"{entry.Path}: {ex}"); ErrorLog($"{entry.Path}: {ex}"); } } } }); } YtypDict = new Dictionary(ytypCache.Count); foreach (var ytyp in ytypCache) { AddYtypToDictionary(ytyp.RpfFileEntry, ytyp); } Console.WriteLine($"archetypeDict: {archetypeDict.Count}"); //Parallel.ForEach(allYtypes, new ParallelOptions { MaxDegreeOfParallelism = 4 }, (file) => //{ // foreach(var entry in file.AllEntries) // { // if (entry.IsExtension(".ytyp")) // { // try // { // AddYtypToDictionary(entry); // } // catch (Exception ex) // { // ErrorLog(entry.Path + ": " + ex.ToString()); // } // } // } //}); //Parallel.ForEach(allYtypes, new ParallelOptions { MaxDegreeOfParallelism = 4 }, (entry) => //{ // try // { // AddYtypToDictionary(entry); // } // catch (Exception ex) // { // ErrorLog(entry.Path + ": " + ex.ToString()); // } //}); } catch(Exception ex) { Console.WriteLine(ex); throw; } archetypesLoaded = true; } private void AddYtypToDictionary(RpfEntry entry, YtypFile ytypFile) { if (ytypFile is null) { throw new Exception("Couldn't load ytyp file."); //couldn't load the file for some reason... shouldn't happen.. } if (ytypFile.Meta is null) { if (ytypFile.Pso is null && ytypFile.Rbf is null) { throw new Exception("ytyp file was not in meta format."); } return; } YtypDict[ytypFile.NameHash] = ytypFile; if (ytypFile.AllArchetypes.Length == 0) { ErrorLog?.Invoke($"{entry.Path}: no archetypes found"); } else { foreach (var arch in ytypFile.AllArchetypes) { uint hash = arch.Hash; if (hash == 0) continue; archetypeDict[hash] = arch; } } //if (YtypDict.ContainsKey(ytypfile.NameHash)) //{ // //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.AudioEmitters != null) ////{ //// foreach (CExtensionDefAudioEmitter emitter in ytypfile.AudioEmitters) //// { //// //audioind++; //// //uint hash = emitter.name; //// //if (hash == 0) hash = archetype.name; //// //if (hash == 0) //// // continue; //// //if (AudioArchetypes.ContainsKey(hash)) //// //{ //// // var oldval = AudioArchetypes[hash]; //// // //errorAction(entry.Path + ": " + emitter.ToString() + ": (CTimeArchetypeDef) Already in archetype dict. Was in: " + oldval.ToString()); //// // //overwrite with new definition? how to tell? //// // AudioArchetypes[hash] = new Tuple(ytypfile, audioind); //// //} //// //else //// //{ //// // AudioArchetypes.Add(hash, new Tuple(ytypfile, audioind)); //// //} //// } ////} } public async Task InitStringDictsAsync(bool force = true) { 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"; Gxt2Dict = new Dictionary(1000); var gxt2files = new List(); foreach (var rpf in AllRpfs.Where(p => p.AllEntries != null)) { foreach (var entry in rpf.AllEntries) { if (entry is RpfFileEntry fentry) { var p = entry.Path; if (entry.IsExtension(".gxt2") && (p.Contains(langstr, StringComparison.OrdinalIgnoreCase) || p.Contains(langstr2, StringComparison.OrdinalIgnoreCase) || p.Contains(langstr3, StringComparison.OrdinalIgnoreCase))) { Gxt2Dict[entry.ShortNameHash] = fentry; if (DoFullStringIndex) { var gxt2 = await RpfManager.GetFileAsync(entry); if (gxt2 is not null) { foreach(var e in gxt2.TextEntries) { GlobalText.Ensure(e.Text, e.Hash); } gxt2files.Add(gxt2); } } } } } } if (!DoFullStringIndex) { string globalgxt2path = "x64b.rpf\\data\\lang\\" + langstr + ".rpf\\global.gxt2"; var globalgxt2 = await RpfMan.GetFileAsync(globalgxt2path); if (globalgxt2 is not null) { foreach(var e in globalgxt2.TextEntries) { GlobalText.Ensure(e.Text, e.Hash); } } return; } GlobalText.FullIndexBuilt = true; foreach (var rpf in AllRpfs) { foreach (var entry in rpf.AllEntries) { if (entry.Name.EndsWith("statssetup.xml", StringComparison.OrdinalIgnoreCase)) { var xml = RpfMan.GetFileXml(entry.Path); if (xml == null) { continue; } var statnodes = xml.SelectNodes("StatsSetup/stats/stat"); foreach (XmlNode statnode in statnodes) { if (statnode == null) { continue; } var statname = Xml.GetStringAttribute(statnode, "Name"); if (string.IsNullOrEmpty(statname)) { continue; } var statnamel = statname; StatsNames.EnsureLower(statname); StatsNames.EnsureLower(statnamel); StatsNames.EnsureLower("sp_" + statnamel); StatsNames.EnsureLower("mp0_" + statnamel); StatsNames.EnsureLower("mp1_" + statnamel); } } } } StatsNames.FullIndexBuilt = true; } public void AddInitDatas() { var vehiclesInitDict = VehiclesInitDict; foreach (var vf in vehicleFiles.Values) { if (vf.InitDatas is not null) { foreach (var initData in vf.InitDatas.Span) { var name = initData.modelName; var hash = JenkHash.GenHashLower(name); vehiclesInitDict.TryAdd(hash, initData); } } } } public async Task InitVehicles(bool force = true) { if (!LoadVehicles) return; if (!force && VehiclesLoaded) return; //Neos7 //Involved files(at least for rendering purpose ) //Vehicles.meta //Carcols.meta //Carvariations.meta //Vehiclelayouts.meta //The other metas shouldn't be important for rendering //Then the global carcols.ymt is required too //As it contains the general shared tuning options //Carcols for modkits and lights kits definitions //Carvariations links such modkits and lights kits to each vehicle plus defines colours combinations of spawned vehicles //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; //var allCarCols = new List(); //var allCarModCols = new List(); //var allCarVariations = new List(); //var allCarVariationsDict = new Dictionary(); //var allVehicleLayouts = new List(); IEnumerable allRpfs = rpfs; if (EnableDlc && DlcActiveRpfs.Count > 0) { allRpfs = allRpfs.Concat(DlcActiveRpfs); } await Parallel.ForEachAsync(allRpfs.Where(p => p.AllEntries != null).SelectMany(p => p.AllEntries), async (entry, _) => { if (!entry.Name.Equals("vehicles.meta", StringComparison.OrdinalIgnoreCase)) { return; } try { if (!vehicleFiles.ContainsKey(entry.Path)) { vehicleFiles[entry.Path] = await RpfManager.GetFileAsync(entry); } //if ((entry.Name.Equals("carcols.ymt", StringComparison.OrdinalIgnoreCase)) || (entry.Name.Equals("carcols.meta", StringComparison.OrdinalIgnoreCase))) //{ // var cf = RpfMan.GetFile(entry); // if (cf.VehicleModelInfo != null) // { } // allCarCols.Add(cf); //} //if (entry.Name.Equals("carmodcols.ymt", StringComparison.OrdinalIgnoreCase)) //{ // var cf = RpfMan.GetFile(entry); // if (cf.VehicleModColours != null) // { } // allCarModCols.Add(cf); //} //if ((entry.Name.Equals("carvariations.ymt", StringComparison.OrdinalIgnoreCase)) || (entry.Name.Equals("carvariations.meta", StringComparison.OrdinalIgnoreCase))) //{ // var cf = RpfMan.GetFile(entry); // if (cf.VehicleModelInfo?.variationData != null) // { // foreach (var variation in cf.VehicleModelInfo.variationData) // { // var name = variation.modelName.ToLowerInvariant(); // var hash = JenkHash.GenHash(name); // allCarVariationsDict[hash] = variation; // } // } // allCarVariations.Add(cf); //} //if (entry.Name.StartsWith("vehiclelayouts", StringComparison.OrdinalIgnoreCase) && entry.Name.EndsWith(".meta", StringComparison.OrdinalIgnoreCase)) //{ // var lf = RpfMan.GetFile(entry); // if (lf.Xml != null) // { } // allVehicleLayouts.Add(lf); //} } catch (Exception ex) { string errstr = $"{entry.Path}\n{ex}"; ErrorLog(errstr); Console.WriteLine(errstr); } }); VehiclesInitDict ??= new Dictionary(900); VehiclesInitDict.Clear(); AddInitDatas(); Console.WriteLine($"VehiclesInitDict: {VehiclesInitDict.Count}; vehicleFiles: {vehicleFiles.Count}"); } public async Task InitPeds(bool force = true) { if (!LoadPeds) return; if (!force && PedsLoaded) return; UpdateStatus?.Invoke("Loading peds..."); using var _ = new DisposableTimer("InitPeds"); IEnumerable rpfs = PreloadedMode ? AllRpfs : (IEnumerable)ActiveMapRpfFiles.Values; PooledList? dlcrpfs = null; //if (EnableDlc) //{ // Console.WriteLine("Loading DLC peds"); // dlcrpfs = new PooledList(); // foreach (var rpf in DlcActiveRpfs) // { // dlcrpfs.Add(rpf); // if (rpf.Children is null) // continue; // dlcrpfs.AddRange(rpf.Children); // } //} var allPeds = new ConcurrentDictionary(4, 1100); //var allPedsFiles = new List(); var allPedYmts = new ConcurrentBag(); var allPedsFiles = new ConcurrentDictionary(); var allPedDrwDicts = new ConcurrentDictionary>(4, 1100); var allPedTexDicts = new ConcurrentDictionary>(4, 1100); var allPedClothDicts = new ConcurrentDictionary>(4, 200); static Dictionary createDict(MetaHash key) { return new Dictionary(); } static Dictionary ensureDict(ConcurrentDictionary> coll, MetaHash hash) { return coll.GetOrAdd(hash, createDict); } void addPedDicts(ReadOnlySpan name, MetaHash hash, PedFile pedFile) { var dir = pedFile.RpfFileEntry.Parent; var pedsFiles = allPedsFiles.GetOrAdd(hash, (hash) => new PedsFiles()); Dictionary pedClotsDict = null; var files = dir?.Files; if (files != null) { foreach (var file in files.Span) { if (file.IsExtension(".yld") && file.ShortName.Equals(name, StringComparison.OrdinalIgnoreCase)) { pedsFiles.AddClothsDict(pedFile, file); pedClotsDict ??= ensureDict(allPedClothDicts, hash); lock(pedClotsDict) { pedClotsDict[file.ShortNameHash] = file; } } } } if (dir?.Directories != null) { foreach (var cdir in dir.Directories.Span) { if (cdir.Name.AsSpan().Equals(name, StringComparison.OrdinalIgnoreCase)) { dir = cdir; break; } } files = dir?.Files; if (files != null) { Dictionary pedDrwDicts = null; Dictionary pedTextDicts = null; foreach (var file in files.Span) { if (file?.Name == null) continue; if (file.IsExtension(".ydd")) { pedsFiles.AddDrawable(pedFile, file); pedDrwDicts ??= ensureDict(allPedDrwDicts, hash); lock(pedDrwDicts) { pedDrwDicts[file.ShortNameHash] = file; } } else if (file.IsExtension(".ytd")) { pedsFiles.AddTextureDict(pedFile, file); pedTextDicts ??= ensureDict(allPedTexDicts, hash); lock(pedTextDicts) { pedTextDicts[file.ShortNameHash] = file; } } else if (file.IsExtension(".yld")) { pedsFiles.AddClothsDict(pedFile, file); pedClotsDict ??= ensureDict(allPedClothDicts, hash); lock(pedClotsDict) { pedClotsDict[file.ShortNameHash] = file; } } } } } }; var allRpfs = rpfs; if (dlcrpfs is not null && dlcrpfs.Count > 0) { allRpfs = rpfs.Concat(dlcrpfs); } var allEntries = allRpfs .Where(p => p.AllEntries != null) .Where(p => p.Name.EndsWithAny(".ymt", ".meta")) .ToArray(); await Parallel.ForAsync(0, allEntries.Length, new ParallelOptions { MaxDegreeOfParallelism = 4 }, async (i, cancellationToken) => { var file = allEntries[i]; foreach(var entry in file.AllEntries) { if (!entry.Name.Equals("peds.ymt", StringComparison.OrdinalIgnoreCase) && !entry.Name.Equals("peds.meta", StringComparison.OrdinalIgnoreCase)) { return; } try { var pf = await RpfManager.GetFileAsync(entry); if (pf.InitDataList?.InitDatas != null) { foreach (var initData in pf.InitDataList.InitDatas) { var name = initData.Name; var hash = JenkHash.GenHashLower(name); allPeds.TryAdd(hash, initData); } } //allPedsFiles.Add(pf); } catch (Exception ex) { string errstr = entry.Path + "\n" + ex.ToString(); ErrorLog?.Invoke(errstr); Console.WriteLine(errstr); } } }); var pedLookup = new Dictionary(); foreach(var entry in DlcSetupFiles) { foreach(var dataFile in entry.ContentFile.dataFiles.Where(p => p.fileType == "SHOP_PED_APPAREL_META_FILE")) { var path = GetFilePathWithoutDeviceName(GameFileCache.GetDlcPlatformPath(dataFile.filename)).ToString().Replace("/", "\\"); foreach(var file in entry.ContentFile.DlcFile.AllEntries) { if (file.Path.EndsWith(path, StringComparison.OrdinalIgnoreCase)) { var loadedFile = await RpfManager.GetFileAsync(file); var fullDlcNameHash = JenkHash.GenHashLower(loadedFile.FullDlcName); var dlcNameHash = JenkHash.GenHashLower(loadedFile.DlcName); var pedHash = JenkHash.GenHashLower(loadedFile.PedName); pedLookup[fullDlcNameHash] = pedHash; pedLookup[dlcNameHash] = pedHash; var deviceName = entry.deviceName.Replace("dlc_", ""); DlcNameLookup[fullDlcNameHash] = deviceName; DlcNameLookup[dlcNameHash] = deviceName; break; } } } } await Parallel.ForAsync(0, allEntries.Length, new ParallelOptions { MaxDegreeOfParallelism = 4 }, async (i, cancellationToken) => { var file = allEntries[i]; foreach (var entry in file.AllEntries) { if (!entry.IsExtension(".ymt")) { return; } try { var shortHash = entry.ShortNameHash; if (!pedLookup.TryGetValue(shortHash, out var pedhash)) { pedhash = shortHash; } if (allPeds.ContainsKey(pedhash)) { var pf = await RpfManager.GetFileAsync(entry); if (pf is not null) { allPedYmts.Add(pf); addPedDicts(entry.ShortName, pedhash, pf); } } } catch (Exception ex) { string errstr = entry.Path + "\n" + ex.ToString(); ErrorLog?.Invoke(errstr); Console.WriteLine(errstr); } } }); PedsInitDict = allPeds.ToDictionary(p => p.Key, p => p.Value); var groupedYmts = allPedYmts.GroupBy(p => p.RpfFileEntry.ShortNameHash, (key, group) => KeyValuePair.Create((MetaHash)key, (ICollection)group.ToHashSet())); PedVariationsDict = new Dictionary>(groupedYmts); //PedVariationsDict = allPedYmts.ToDictionary(p => p.Key, p => p.Value); PedDrawableDicts = allPedDrwDicts.ToDictionary(p => p.Key, p => p.Value); PedTextureDicts = allPedTexDicts.ToDictionary(p => p.Key, p => p.Value); PedClothDicts = allPedClothDicts.ToDictionary(p => p.Key, p => p.Value); PedsFiles = allPedsFiles.ToDictionary(p => p.Key, p => p.Value); //foreach (var kvp in PedsInitDict) //{ // if (!PedVariationsDict.ContainsKey(kvp.Key)) // { }//checking we found them all! //} dlcrpfs?.Dispose(); } private bool AudioLoaded = false; public readonly ReaderWriterLockSlim AudioDatRelFilesLock = new ReaderWriterLockSlim(); public async Task InitAudio(bool force = true) { if (!LoadAudio) return; if (AudioLoaded && !force) return; UpdateStatus?.Invoke("Loading audio..."); using var timer = new DisposableTimer("InitAudio"); Dictionary datrelentries = new Dictionary(); void addRpfDatRelEntries(RpfFile rpffile) { if (rpffile.AllEntries == null) return; foreach (var entry in rpffile.AllEntries) { if (entry is RpfFileEntry fentry) { if (entry.IsExtension(".rel")) { datrelentries[entry.NameHash] = fentry; } } } } var audrpf = RpfMan.FindRpfFile("x64\\audio\\audio_rel.rpf"); if (audrpf != null) { addRpfDatRelEntries(audrpf); } if (EnableDlc) { var updrpf = RpfMan.FindRpfFile("update\\update.rpf"); if (updrpf != null) { addRpfDatRelEntries(updrpf); } foreach (var dlcrpf in DlcActiveRpfs) //load from current dlc rpfs { addRpfDatRelEntries(dlcrpf); } if (DlcActiveRpfs.Count == 0) //when activated from RPF explorer... DLCs aren't initialised fully { foreach (var rpf in AllRpfs) //this is a bit of a hack - DLC orders won't be correct so likely will select wrong versions of things { if (rpf.Name.StartsWith("dlc", StringComparison.OrdinalIgnoreCase)) { addRpfDatRelEntries(rpf); } } } } try { AudioDatRelFiles ??= new List(220); AudioConfigDict ??= new Dictionary(150); AudioSpeechDict ??= new Dictionary(150000); AudioSynthsDict ??= new Dictionary(5000); AudioMixersDict ??= new Dictionary(5000); AudioCurvesDict ??= new Dictionary(1000); AudioCategsDict ??= new Dictionary(250); AudioSoundsDict ??= new Dictionary(150000); AudioGameDict ??= new Dictionary(30000); var audioDatRelFiles = AudioDatRelFiles; var audioConfigDict = AudioConfigDict; var audioSpeechDict = AudioSpeechDict; var audioSynthsDict = AudioSynthsDict; var audioMixersDict = AudioMixersDict; var audioCurvesDict = AudioCurvesDict; var audioCategsDict = AudioCategsDict; var audioSoundsDict = AudioSoundsDict; var audioGameDict = AudioGameDict; var relFiles = new ConcurrentBag(); var datrelentriesArr = datrelentries.Values.ToArray(); await Parallel.ForAsync(0, datrelentriesArr.Length, async (i, cancellationToken) => { var datrelentry = datrelentriesArr[i]; var relfile = await RpfManager.GetFileAsync(datrelentry).ConfigureAwait(false); if (relfile == null) return; relFiles.Add(relfile); }).ConfigureAwait(false); foreach (var relfile in relFiles) { audioDatRelFiles.Add(relfile); var d = audioGameDict; var t = relfile.RelType; switch (t) { case RelDatFileType.Dat4: d = relfile.IsAudioConfig ? audioConfigDict : audioSpeechDict; break; case RelDatFileType.Dat10ModularSynth: d = audioSynthsDict; break; case RelDatFileType.Dat15DynamicMixer: d = audioMixersDict; break; case RelDatFileType.Dat16Curves: d = audioCurvesDict; break; case RelDatFileType.Dat22Categories: d = audioCategsDict; break; case RelDatFileType.Dat54DataEntries: d = audioSoundsDict; break; case RelDatFileType.Dat149: case RelDatFileType.Dat150: case RelDatFileType.Dat151: default: d = audioGameDict; break; } foreach (var reldata in relfile.RelDatas) { if (reldata.NameHash == 0) continue; d[reldata.NameHash] = reldata; } } } catch(Exception e) { Console.WriteLine(e); throw; } Console.WriteLine( $"AudioDatRelFiles: {AudioDatRelFiles.Count}; " + $"AudioConfigDict: {AudioConfigDict.Count}; " + $"AudioSpeechDict: {AudioSpeechDict.Count}; " + $"AudioSynthsDict: {AudioSynthsDict.Count}; " + $"AudioMixersDict: {AudioMixersDict.Count}; " + $"AudioCurvesDict: {AudioCurvesDict.Count}; " + $"AudioCategsDict: {AudioCategsDict.Count}; " + $"AudioSoundsDict: {AudioSoundsDict.Count}; " + $"AudioGameDict: {AudioGameDict.Count};"); AudioLoaded = true; } public bool SetDlcLevel(string dlc, bool enable) { bool dlcchange = (!dlc.Equals(SelectedDlc, StringComparison.OrdinalIgnoreCase)); bool enablechange = (enable != EnableDlc); bool change = (dlcchange && enable) || enablechange; if (change) { lock (updateSyncRoot) { //lock (textureSyncRoot) { SelectedDlc = dlc; EnableDlc = enable; //mainCache.Clear(); ClearCachedMaps(); InitDlcAsync().GetAwaiter().GetResult(); } } } return change; } public bool SetModsEnabled(bool enable) { bool change = (enable != EnableMods); if (change) { lock (updateSyncRoot) { //lock (textureSyncRoot) { EnableMods = enable; RpfMan.EnableMods = enable; mainCache.Clear(); InitGlobal(); InitDlcAsync().GetAwaiter().GetResult(); } } } return change; } private void ClearCachedMaps() { if (AllYmapsDict != null) { foreach (var ymap in AllYmapsDict.Values) { GameFileCacheKey k = new GameFileCacheKey(ymap.ShortNameHash, GameFileType.Ymap); mainCache.Remove(in k); } } } public void AddProjectFile(GameFile f) { if (f?.RpfFileEntry is null) return; if (f.RpfFileEntry.ShortNameHash == 0) return; var key = new GameFileCacheKey(f.RpfFileEntry.ShortNameHash, f.Type); projectFiles[key] = f; } public void RemoveProjectFile(GameFile f) { if (f?.RpfFileEntry is null) return; if (f.RpfFileEntry.ShortNameHash == 0) return; var key = new GameFileCacheKey(f.RpfFileEntry.ShortNameHash, f.Type); projectFiles.TryRemove(key, out _); } public void ClearProjectFiles() { projectFiles.Clear(); } public void AddProjectArchetype(Archetype? a) { if (a is null || a.Hash == 0) return; projectArchetypes[a.Hash] = a; } public void RemoveProjectArchetype(Archetype? a) { if (a is null || a.Hash == 0) return; try { projectArchetypes.TryGetValue(a.Hash, out var tarch); if (tarch == a) { projectArchetypes.Remove(a.Hash); } } finally { archetypeLock.ExitWriteLock(); } } public void ClearProjectArchetypes() { archetypeLock.EnterWriteLock(); try { projectArchetypes.Clear(); } finally { archetypeLock.ExitWriteLock(); } } private readonly SemaphoreSlim maxCacheLock = new SemaphoreSlim(Environment.ProcessorCount / 2, Environment.ProcessorCount / 2); public async ValueTask TryLoadEnqueue(T gf) where T : GameFile { if (gf.Loaded || gf.LoadQueued) return gf; //if (requestQueue.Count < 20)// && (!gf.LoadQueued) //{ if (!gf.SetLoadQueued(true)) return gf; gf.LastLoadTime = DateTime.Now; await Task.Run(async () => { await maxCacheLock.WaitAsync(); try { await LoadFileAsync(gf).ConfigureAwait(false); } catch (Exception ex) { Console.WriteLine(ex); throw; } finally { maxCacheLock.Release(); } }); return gf; //requestQueue.Enqueue(gf); //} } public Archetype? GetArchetype(uint hash) { if (!archetypesLoaded) return null; archetypeLock.EnterReadLock(); try { if (projectArchetypes.TryGetValue(hash, out var arch) && arch is not null) { return arch; } archetypeDict.TryGetValue(hash, out arch); return arch; } finally { archetypeLock.ExitReadLock(); } } public MapDataStoreNode GetMapNode(uint hash) { if (!IsInited) return null; YmapHierarchyDict.TryGetValue(hash, out var node); return node; } private readonly object ydrLock = new object(); public YdrFile? GetYdr(uint hash, bool immediate = false) { if (!IsInited) return null; lock (ydrLock) { var key = new GameFileCacheKey(hash, GameFileType.Ydr); if (projectFiles.TryGetValue(key, out GameFile? pgf)) { return pgf as YdrFile; } YdrFile? ydr = mainCache.TryGet(in key) as YdrFile; if (ydr is null) { var e = GetYdrEntry(hash); if (e is null) { return null; } ydr = new YdrFile(e); if (!mainCache.TryAdd(key, ydr)) { return null; } } if (!ydr.Loaded && !ydr.LoadQueued) { _ = TryLoadEnqueue(ydr); } return ydr; } } private readonly object yddLock = new object(); public YddFile? GetYdd(uint hash) { if (!IsInited) return null; lock (yddLock) { var key = new GameFileCacheKey(hash, GameFileType.Ydd); if (projectFiles.TryGetValue(key, out GameFile? pgf)) { return pgf as YddFile; } YddFile? ydd = mainCache.TryGet(in key) as YddFile; if (ydd is null) { var e = GetYddEntry(hash); if (e is null) return null; ydd = new YddFile(e); if (!mainCache.TryAdd(key, ydd)) { return null; } } if (!ydd.Loaded && !ydd.LoadQueued) { _ = TryLoadEnqueue(ydd); } return ydd; } } private readonly SemaphoreSlim ytdLock = new SemaphoreSlim(1, 1); public YtdFile? GetYtd(uint hash) { if (!IsInited) return null; ytdLock.Wait(); try { var key = new GameFileCacheKey(hash, GameFileType.Ytd); if (projectFiles.TryGetValue(key, out var pgf)) { return pgf as YtdFile; } YtdFile? ytd = mainCache.TryGet(in key) as YtdFile; if (ytd is null) { var e = GetYtdEntry(hash); if (e is null) { return null; } ytd = new YtdFile(e); if (!mainCache.TryAdd(key, ytd)) { return null; } } if (!ytd.Loaded && !ytd.LoadQueued) { _ = TryLoadEnqueue(ytd); } return ytd; } finally { ytdLock.Release(); } } private YmapFile? getYmapFile(uint hash) { YmapFile? ymap; lock (ymapLock) { var key = new GameFileCacheKey(hash, GameFileType.Ymap); ymap = mainCache.TryGet(in key) as YmapFile; if (projectFiles.TryGetValue(key, out GameFile? pgf) && pgf is YmapFile ymapFile) { return ymapFile; } if (ymap is null) { var e = GetYmapEntry(hash); if (e is null) { Console.WriteLine($"Couldn't find ymap {JenkIndex.GetString(hash)} ({hash})"); return null; } ymap = new YmapFile(e); if (!mainCache.TryAdd(key, ymap)) { Console.WriteLine($"Couldn't add ymap {JenkIndex.GetString(hash)} ({hash}) to cache"); return null; } } } return ymap; } private readonly object ymapLock = new object(); public YmapFile? GetYmap(uint hash) { if (!IsInited) return null; var ymap = getYmapFile(hash); if (ymap is null) return null; if (!ymap.Loaded && !ymap.LoadQueued) { _ = TryLoadEnqueue(ymap); } return ymap; } public ValueTask GetYmapAsync(uint hash) { if (!IsInited) return ValueTask.FromResult((YmapFile?)null); var ymap = getYmapFile(hash); if (ymap is null) return ValueTask.FromResult((YmapFile?)null); if (!ymap.Loaded && !ymap.LoadQueued) { return TryLoadEnqueue(ymap)!; } return ValueTask.FromResult(ymap)!; } private readonly object yftLock = new object(); public YftFile? GetYft(uint hash) { if (!IsInited) return null; lock (yftLock) { var key = new GameFileCacheKey(hash, GameFileType.Yft); YftFile? yft = mainCache.TryGet(in key) as YftFile; if (projectFiles.TryGetValue(key, out GameFile? pgf) && pgf is YftFile yftFile) { return yftFile; } if (yft is null) { var e = GetYftEntry(hash); if (e is null) { return null; } yft = new YftFile(e); if (!mainCache.TryAdd(key, yft)) { return null; } } if (!yft.Loaded && !yft.LoadQueued) { _ = TryLoadEnqueue(yft); } return yft; } } private readonly object ybnLock = new object(); public YbnFile? GetYbn(uint hash) { if (!IsInited) return null; lock (ybnLock) { var key = new GameFileCacheKey(hash, GameFileType.Ybn); YbnFile? ybn = mainCache.TryGet(in key) as YbnFile; if (ybn is null) { var e = GetYbnEntry(hash); if (e is null) { return null; } ybn = new YbnFile(e); if (!mainCache.TryAdd(key, ybn)) { return null; } } if (!ybn.Loaded && !ybn.LoadQueued) { _ = TryLoadEnqueue(ybn); } return ybn; } } private readonly object ycdLock = new object(); public YcdFile? GetYcd(uint hash) { if (!IsInited) return null; lock (ycdLock) { var key = new GameFileCacheKey(hash, GameFileType.Ycd); YcdFile? ycd = mainCache.TryGet(in key) as YcdFile; if (ycd is null) { var e = GetYcdEntry(hash); if (e is null) { return null; } ycd = new YcdFile(e); if (!mainCache.TryAdd(key, ycd)) { return null; } } if (!ycd.Loaded && !ycd.LoadQueued) { _ = TryLoadEnqueue(ycd); } return ycd; } } private readonly object yedLock = new object(); public YedFile? GetYed(uint hash) { if (!IsInited) return null; lock (yedLock) { var key = new GameFileCacheKey(hash, GameFileType.Yed); YedFile? yed = mainCache.TryGet(in key) as YedFile; if (yed is null) { var e = GetYedEntry(hash); if (e is null) { return null; } yed = new YedFile(e); if (!mainCache.TryAdd(key, yed)) { return null; } } if (!yed.Loaded && !yed.LoadQueued) { _ = TryLoadEnqueue(yed); } return yed; } } private readonly object yvnLock = new object(); public YnvFile? GetYnv(uint hash) { if (!IsInited) return null; lock (yvnLock) { var key = new GameFileCacheKey(hash, GameFileType.Ynv); YnvFile? ynv = mainCache.TryGet(in key) as YnvFile; if (ynv == null) { var e = GetYnvEntry(hash); if (e == null) { return null; } ynv = new YnvFile(e); if (!mainCache.TryAdd(key, ynv)) { return null; } } if (!ynv.Loaded && !ynv.LoadQueued) { _ = TryLoadEnqueue(ynv); } return ynv; } } public RpfFileEntry? GetYdrEntry(uint hash) { YdrDict.TryGetValue(hash, out var entry); return entry; } public RpfFileEntry? GetYddEntry(uint hash) { YddDict.TryGetValue(hash, out var entry); return entry; } public RpfFileEntry? GetYtdEntry(uint hash) { YtdDict.TryGetValue(hash, out var entry); return entry; } public RpfFileEntry? GetYmapEntry(uint hash) { if (!YmapDict.TryGetValue(hash, out var entry)) { if (!AllYmapsDict.TryGetValue(hash, out entry)) { Console.WriteLine($"Couldn't find ymap {JenkIndex.GetString(hash)}"); } } return entry; } public RpfFileEntry? GetYftEntry(uint hash) { YftDict.TryGetValue(hash, out var entry); return entry; } public RpfFileEntry? GetYbnEntry(uint hash) { YbnDict.TryGetValue(hash, out var entry); return entry; } public RpfFileEntry? GetYcdEntry(uint hash) { YcdDict.TryGetValue(hash, out var entry); return entry; } public RpfFileEntry? GetYedEntry(uint hash) { YedDict.TryGetValue(hash, out var entry); return entry; } public RpfFileEntry? GetYnvEntry(uint hash) { YnvDict.TryGetValue(hash, out var entry); return entry; } public bool LoadFile(T file) where T : GameFile, PackedFile { if (file is null) return false; RpfFileEntry? entry = file.RpfFileEntry; if (entry is not null) { return RpfMan.LoadFile(file, entry); } return false; } public ValueTask LoadFileAsync(T file) where T : GameFile, PackedFile { if (file is null) return new ValueTask(false); RpfFileEntry? entry = file.RpfFileEntry; if (entry is not null) { return RpfMan.LoadFileAsync(file, entry); } return new ValueTask(false); } public async ValueTask GetFileUncachedAsync(RpfFileEntry e) where T : GameFile, new() { var f = new T(); f.RpfFileEntry = e; await TryLoadEnqueue(f); return f; } public T GetFileUncached(RpfFileEntry e) where T : GameFile, new() { var f = new T(); f.RpfFileEntry = e; _ = TryLoadEnqueue(f); return f; } public void BeginFrame() { mainCache.BeginFrame(); } private async ValueTask LoadFileAsync(GameFile req) { //process content requests. if (req.Loaded) return; //it's already loaded... (somehow) if ((req.LastUseTime - DateTime.Now).TotalSeconds > 3.0) return; //hasn't been requested lately..! ignore, will try again later if necessary try { //if (!loadedsomething) //{ //UpdateStatus?.Invoke("Loading " + req.RpfFileEntry.Name + "..."); //} switch (req) { case YdrFile ydrFile: req.Loaded = await LoadFileAsync(ydrFile); break; case YddFile yddFile: req.Loaded = await LoadFileAsync(yddFile); break; case YtdFile ytdFile: req.Loaded = await LoadFileAsync(ytdFile); //if (req.Loaded) AddTextureLookups(req as YtdFile); break; case YmapFile ymapFile: req.Loaded = await LoadFileAsync(ymapFile); if (req.Loaded) ymapFile.InitYmapEntityArchetypes(this); break; case YftFile yftFile: req.Loaded = await LoadFileAsync(yftFile); break; case YbnFile ybnFile: req.Loaded = await LoadFileAsync(ybnFile); break; case YcdFile ycdFile: req.Loaded = await LoadFileAsync(ycdFile); break; case YedFile yedFile: req.Loaded = await LoadFileAsync(yedFile); break; case YnvFile ynvFile: req.Loaded = await LoadFileAsync(ynvFile); break; case YldFile yldFile: req.Loaded = await LoadFileAsync(yldFile); break; default: break; } string str = (req.Loaded ? "Loaded " : "Error loading ") + req.ToString(); //string str = string.Format("{0}: {1}: {2}", requestQueue.Count, (req.Loaded ? "Loaded" : "Error loading"), req); UpdateStatus?.Invoke(str); //ErrorLog(str); if (!req.Loaded) { ErrorLog("Error loading " + req.ToString()); } } catch (Exception e) { req.LoadQueued = false; req.Loaded = false; Console.WriteLine(e); // Retry loading, at most 2 times if (req.LoadAttempts > 2) { req.LoadAttempts++; _ = TryLoadEnqueue(req); } else { Console.WriteLine($"Loading of file {req.Name} at {req.FilePath} failed with error: {e}"); } } finally { req.LoadQueued = false; } } private void LoadFile(GameFile req) { lock (req) { try { //process content requests. if (req.Loaded) return; //it's already loaded... (somehow) if ((req.LastUseTime - DateTime.Now).TotalSeconds > 3.0) return; //hasn't been requested lately..! ignore, will try again later if necessary //if (!loadedsomething) //{ //UpdateStatus?.Invoke("Loading " + req.RpfFileEntry.Name + "..."); //} switch (req.Type) { case GameFileType.Ydr: req.Loaded = LoadFile(req as YdrFile); break; case GameFileType.Ydd: req.Loaded = LoadFile(req as YddFile); break; case GameFileType.Ytd: req.Loaded = LoadFile(req as YtdFile); //if (req.Loaded) AddTextureLookups(req as YtdFile); break; case GameFileType.Ymap: YmapFile y = req as YmapFile; req.Loaded = LoadFile(y); if (req.Loaded) y.InitYmapEntityArchetypes(this); break; case GameFileType.Yft: req.Loaded = LoadFile(req as YftFile); break; case GameFileType.Ybn: req.Loaded = LoadFile(req as YbnFile); break; case GameFileType.Ycd: req.Loaded = LoadFile(req as YcdFile); break; case GameFileType.Yed: req.Loaded = LoadFile(req as YedFile); break; case GameFileType.Ynv: req.Loaded = LoadFile(req as YnvFile); break; case GameFileType.Yld: req.Loaded = LoadFile(req as YldFile); break; default: break; } string str = (req.Loaded ? "Loaded " : "Error loading ") + req.ToString(); //string str = string.Format("{0}: {1}: {2}", requestQueue.Count, (req.Loaded ? "Loaded" : "Error loading"), req); UpdateStatus?.Invoke(str); //ErrorLog(str); if (!req.Loaded) { ErrorLog?.Invoke($"Error loading {req}"); } } catch (Exception e) { req.LoadQueued = false; req.Loaded = false; Console.WriteLine(e); throw; } finally { req.LoadQueued = false; } } } public bool ContentThreadProc() { //mainCache.BeginFrame(); if (requestQueue.Count == 0) return false; int itemcount = 0; lock (updateSyncRoot) { GameFile req; while ((itemcount < MaxItemsPerLoop) && requestQueue.TryDequeue(out req)) { LoadFile(req); itemcount++; } } return itemcount >= 1; } private void AddTextureLookups(YtdFile ytd) { if (ytd?.TextureDict?.TextureNameHashes?.data_items == null) return; foreach (uint hash in ytd.TextureDict.TextureNameHashes.data_items) { textureLookup[hash] = ytd.RpfFileEntry; } } public YtdFile TryGetTextureDictForTexture(uint hash) { if (textureLookup.TryGetValue(hash, out var e)) { return GetYtd(e.ShortNameHash); } return null; } public YtdFile TryGetParentYtd(uint hash) { MetaHash phash; if (textureParents is not null && textureParents.TryGetValue(hash, out phash)) { return GetYtd(phash); } return null; } public uint TryGetParentYtdHash(uint hash) { if (textureParents is null) { return 0u; } textureParents.TryGetValue(hash, out MetaHash phash); return phash; } public uint TryGetHDTextureHash(uint txdhash) { MetaHash hdhash = 0; if (hdtexturelookup?.TryGetValue(txdhash, out hdhash) ?? false) { return hdhash; } return txdhash; } public Texture TryFindTextureInParent(uint texhash, uint txdhash) { Texture tex = null; var ytd = TryGetParentYtd(txdhash); while ((ytd != null) && (tex == null)) { if (ytd.Loaded && (ytd.TextureDict != null)) { tex = ytd.TextureDict.Lookup(texhash); } if (tex == null) { ytd = TryGetParentYtd(ytd.Key.Hash); } } return tex; } public DrawableBase TryGetDrawable(Archetype arche) { if (arche == null) return null; uint drawhash = arche.Hash; DrawableBase drawable = null; if ((arche.DrawableDict != 0))// && (arche.DrawableDict != arche.Hash)) { //try get drawable from ydd... YddFile ydd = GetYdd(arche.DrawableDict); if (ydd != null) { if (ydd.Loaded && (ydd.Dict != null)) { Drawable d; ydd.Dict.TryGetValue(drawhash, out d); //can't out to base class? drawable = d; if (drawable == null) { return null; //drawable wasn't in dict!! } } else { return null; //ydd not loaded yet, or has no dict } } else { //return null; //couldn't find drawable dict... quit now? } } if (drawable == null) { //try get drawable from ydr. YdrFile ydr = GetYdr(drawhash); if (ydr != null) { if (ydr.Loaded) { drawable = ydr.Drawable; } } else { YftFile yft = GetYft(drawhash); if (yft != null) { if (yft.Loaded) { if (yft.Fragment != null) { drawable = yft.Fragment.Drawable; } } } } } return drawable; } public DrawableBase TryGetDrawable(Archetype arche, out bool waitingForLoad) { waitingForLoad = false; if (arche == null) return null; uint drawhash = arche.Hash; DrawableBase drawable = null; if ((arche.DrawableDict != 0))// && (arche.DrawableDict != arche.Hash)) { //try get drawable from ydd... YddFile ydd = GetYdd(arche.DrawableDict); if (ydd != null) { if (ydd.Loaded) { if (ydd.Dict != null) { Drawable d; ydd.Dict.TryGetValue(drawhash, out d); //can't out to base class? drawable = d; if (drawable == null) { return null; //drawable wasn't in dict!! } } else { return null; //ydd has no dict } } else { waitingForLoad = true; return null; //ydd not loaded yet } } else { //return null; //couldn't find drawable dict... quit now? } } if (drawable == null) { //try get drawable from ydr. YdrFile ydr = GetYdr(drawhash); if (ydr != null) { if (ydr.Loaded) { drawable = ydr.Drawable; } else { waitingForLoad = true; } } else { YftFile yft = GetYft(drawhash); if (yft != null) { if (yft.Loaded) { if (yft.Fragment != null) { drawable = yft.Fragment.Drawable; } } else { waitingForLoad = true; } } } } return drawable; } private string[] GetExcludePaths() { string[] exclpaths = ExcludeFolders.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries); if (exclpaths.Length > 0) { for (int i = 0; i < exclpaths.Length; i++) { exclpaths[i] = exclpaths[i].ToLowerInvariant(); } } else { exclpaths = null; } return exclpaths; } public void GetShadersXml() { bool doydr = true; bool doydd = true; bool doyft = true; bool doypt = true; var data = new Dictionary(); void collectDrawable(DrawableBase d) { if (d?.AllModels == null) return; foreach (var model in d.AllModels) { if (model?.Geometries == null) continue; foreach (var geom in model.Geometries) { var s = geom?.Shader; if (s == null) continue; ShaderXmlDataCollection dc = null; if (!data.TryGetValue(s.Name, out dc)) { dc = new ShaderXmlDataCollection(); dc.Name = s.Name; data.Add(s.Name, dc); } dc.AddShaderUse(s, geom); } } } foreach (RpfFile file in AllRpfs) { foreach (RpfEntry entry in file.AllEntries) { try { if (doydr && entry.IsExtension(".ydr")) { UpdateStatus?.Invoke(entry.Path); YdrFile ydr = RpfManager.GetFile(entry); if (ydr == null) { continue; } if (ydr.Drawable == null) { continue; } collectDrawable(ydr.Drawable); } else if (doydd & entry.IsExtension(".ydd")) { UpdateStatus?.Invoke(entry.Path); YddFile ydd = RpfManager.GetFile(entry); if (ydd == null) { continue; } if (ydd.Dict == null) { continue; } foreach (var drawable in ydd.Dict.Values) { collectDrawable(drawable); } } else if (doyft && entry.IsExtension(".yft")) { UpdateStatus?.Invoke(entry.Path); YftFile yft = RpfManager.GetFile(entry); if (yft == null) { continue; } if (yft.Fragment == null) { continue; } if (yft.Fragment.Drawable != null) { collectDrawable(yft.Fragment.Drawable); } if ((yft.Fragment.Cloths != null) && (yft.Fragment.Cloths.data_items != null)) { foreach (var cloth in yft.Fragment.Cloths.data_items) { collectDrawable(cloth.Drawable); } } if ((yft.Fragment.DrawableArray != null) && (yft.Fragment.DrawableArray.data_items != null)) { foreach (var drawable in yft.Fragment.DrawableArray.data_items) { collectDrawable(drawable); } } } else if (doypt && entry.IsExtension(".ypt")) { UpdateStatus?.Invoke(entry.Path); YptFile ypt = RpfManager.GetFile(entry); if (ypt == null) { continue; } if (ypt.DrawableDict == null) { continue; } foreach (var drawable in ypt.DrawableDict.Values) { collectDrawable(drawable); } } } catch //(Exception ex) { } } } var shaders = data.Values.ToList(); shaders.Sort((a, b) => { return b.GeomCount.CompareTo(a.GeomCount); }); StringBuilder sb = new StringBuilder(); sb.AppendLine(MetaXml.XmlHeader); MetaXml.OpenTag(sb, 0, "Shaders"); foreach (var s in shaders) { MetaXml.OpenTag(sb, 1, "Item"); MetaXml.StringTag(sb, 2, "Name", MetaXml.HashString(s.Name)); MetaXml.WriteHashItemArray(sb, s.GetSortedList(s.FileNames).ToArray(), 2, "FileName"); MetaXml.WriteRawArray(sb, s.GetSortedList(s.RenderBuckets).ToArray(), 2, "RenderBucket", ""); MetaXml.OpenTag(sb, 2, "Layout"); var layouts = s.GetSortedList(s.VertexLayouts); foreach (var l in layouts) { var vd = new VertexDeclaration(); vd.Types = l.Types; vd.Flags = l.Flags; vd.WriteXml(sb, 3, "Item"); } MetaXml.CloseTag(sb, 2, "Layout"); MetaXml.OpenTag(sb, 2, "Parameters"); var otstr = "Item name=\"{0}\" type=\"{1}\""; var texparams = s.GetSortedList(s.TexParams); var valparams = s.ValParams; var arrparams = s.ArrParams; foreach (var tp in texparams) { MetaXml.SelfClosingTag(sb, 3, string.Format(otstr, ((ShaderParamNames)tp).ToString(), "Texture")); } foreach (var vp in valparams) { var svp = s.GetSortedList(vp.Value); var defval = svp.FirstOrDefault(); MetaXml.SelfClosingTag(sb, 3, string.Format(otstr, ((ShaderParamNames)vp.Key).ToString(), "Vector") + " " + FloatUtil.GetVector4XmlString(defval)); } foreach (var ap in arrparams) { var defval = ap.Value.FirstOrDefault(); MetaXml.OpenTag(sb, 3, string.Format(otstr, ((ShaderParamNames)ap.Key).ToString(), "Array")); foreach (var vec in defval) { MetaXml.SelfClosingTag(sb, 4, "Value " + FloatUtil.GetVector4XmlString(vec)); } MetaXml.CloseTag(sb, 3, "Item"); } MetaXml.CloseTag(sb, 2, "Parameters"); MetaXml.CloseTag(sb, 1, "Item"); } MetaXml.CloseTag(sb, 0, "Shaders"); var xml = sb.ToString(); File.WriteAllText("C:\\Shaders.xml", xml); } public void GetArchetypeTimesList() { StringBuilder sb = new StringBuilder(); sb.AppendLine("Name,AssetName,12am,1am,2am,3am,4am,5am,6am,7am,8am,9am,10am,11am,12pm,1pm,2pm,3pm,4pm,5pm,6pm,7pm,8pm,9pm,10pm,11pm,+12am,+1am,+2am,+3am,+4am,+5am,+6am,+7am"); foreach (var ytyp in YtypDict.Values) { foreach (var arch in ytyp.AllArchetypes) { if (arch.Type == MetaName.CTimeArchetypeDef) { var ta = arch as TimeArchetype; var t = ta.ActiveHours.TimeFlags; sb.Append(arch.Name); sb.Append(','); sb.Append(arch.AssetName); sb.Append(','); for (int i = 0; i < 32; i++) { bool v = ((t >> i) & 1) == 1; sb.Append(v ? "1" : "0"); sb.Append(','); } sb.AppendLine(); } } } var csv = sb.ToString(); } public class ShaderTextureData { public HashSet Textures { get; set; } = new HashSet(); public HashSet Geometries { get; set; } = new HashSet(); public int Count { get; set; } = 0; } public class ShaderXmlDataCollection { public MetaHash Name { get; set; } public Dictionary FileNames { get; set; } = new Dictionary(); public Dictionary RenderBuckets { get; set; } = new Dictionary(); public Dictionary VertexLayouts { get; set; } = new Dictionary(); public Dictionary TexParams { get; set; } = new Dictionary(); public Dictionary TextureData { get; set; } = new Dictionary(); public HashSet Textures { get; set; } = new HashSet(); public Dictionary> ValParams { get; set; } = new Dictionary>(); public Dictionary> ArrParams { get; set; } = new Dictionary>(); public int GeomCount { get; set; } = 0; public void AddShaderUse(ShaderFX s, DrawableGeometry g, DrawableBase d = null) { GeomCount++; AddItem(s.FileName, FileNames); AddItem(s.RenderBucket, RenderBuckets); var info = g.VertexBuffer?.Info; if (info != null) { AddItem(new ShaderXmlVertexLayout() { Flags = info.Flags, Types = info.Types }, VertexLayouts); } if (s.ParametersList?.Parameters == null) return; if (s.ParametersList?.Hashes == null) return; for (int i = 0; i < s.ParametersList.Count; i++) { var h = s.ParametersList.Hashes[i]; var p = s.ParametersList.Parameters[i]; if (p.DataType == 0)//texture { AddItem(h, TexParams); if (!TextureData.TryGetValue(h, out var tex)) { tex = new ShaderTextureData(); TextureData[h] = tex; } tex.Count++; if (p.Data is TextureBase texture) { tex.Textures.Add($"{texture.Name} ({(d.Owner as GameFile)?.Name})"); } } else if (p.DataType == 1)//vector { var vp = GetItem(h, ValParams); if (p.Data is Vector4 vec) { AddItem(vec, vp); } } else if (p.DataType > 1)//array { var ap = GetItem(h, ArrParams); if (p.Data is Vector4[] arr) { bool found = false; foreach (var exarr in ap) { if (exarr.Length != arr.Length) continue; bool match = true; for (int j = 0; j < exarr.Length; j++) { if (exarr[j] != arr[j]) { match = false; break; } } if (match) { found = true; break; } } if (!found) { ap.Add(arr); } } } } } public void AddItem(T t, Dictionary d) { if (d.ContainsKey(t)) { d[t]++; } else { d[t] = 1; } } public U GetItem(T t, Dictionary d) where U:new() { U r = default(U); if (!d.TryGetValue(t, out r)) { r = new U(); d[t] = r; } return r; } public List GetSortedList(Dictionary d) { var kvps = d.ToList(); kvps.Sort((a, b) => { return b.Value.CompareTo(a.Value); }); return kvps.Select((a) => { return a.Key; }).ToList(); } public List> GetSortedList(Dictionary d) { var kvps = d.ToList(); kvps.Sort((a, b) => { return b.Value.Count.CompareTo(a.Value.Count); }); return kvps.ToList(); } } public struct ShaderXmlVertexLayout { public VertexDeclarationTypes Types { get; set; } public uint Flags { get; set; } public VertexType VertexType { get { return (VertexType)Flags; } } public override string ToString() { return Types.ToString() + ", " + VertexType.ToString(); } } } }