diff --git a/src/StudioCore/AssetLocator.cs b/src/StudioCore/AssetLocator.cs index e0f66de56..88e20c28b 100644 --- a/src/StudioCore/AssetLocator.cs +++ b/src/StudioCore/AssetLocator.cs @@ -1,18 +1,15 @@ using Octokit; using SoulsFormats; using StudioCore.Editor; -using System; using System.Collections.Generic; -using System.Globalization; using System.IO; -using System.Linq; -using System.Text.RegularExpressions; namespace StudioCore; public static class Locator { public static AssetLocator AssetLocator { get; set; } + public static Project ActiveProject { get; set; } } /// @@ -83,2211 +80,131 @@ public override bool Equals(object obj) /// public class AssetLocator { - public static readonly string GameExecutableFilter; - public static readonly string ProjectJsonFilter; - public static readonly string RegulationBinFilter; - public static readonly string Data0Filter; - public static readonly string ParamBndDcxFilter; - public static readonly string ParamBndFilter; - public static readonly string EncRegulationFilter; - public static readonly string ParamLooseFilter; - public static readonly string CsvFilter; - public static readonly string TxtFilter; - public static readonly string FmgJsonFilter; - - private List FullMapList; - - static AssetLocator() - { - // These patterns are meant to be passed directly into PlatformUtils. - // Everything about their handling should be done there. - - // Game Executable (.EXE, EBOOT.BIN)|*.EXE*;*EBOOT.BIN* - // Windows executable (*.EXE)|*.EXE* - // Playstation executable (*.BIN)|*.BIN* - GameExecutableFilter = "exe,bin"; - // Project file (project.json)|PROJECT.JSON - ProjectJsonFilter = "json"; - // Regulation file (regulation.bin)|REGULATION.BIN - RegulationBinFilter = "bin"; - // Data file (Data0.bdt)|DATA0.BDT - Data0Filter = "bdt"; - // ParamBndDcx (gameparam.parambnd.dcx)|GAMEPARAM.PARAMBND.DCX - ParamBndDcxFilter = "parambnd.dcx"; - // ParamBnd (gameparam.parambnd)|GAMEPARAM.PARAMBND - ParamBndFilter = "parambnd"; - // Enc_RegBndDcx (enc_regulation.bnd.dcx)|ENC_REGULATION.BND.DCX - EncRegulationFilter = "bnd.dcx"; - // Loose Param file (*.Param)|*.Param - ParamLooseFilter = "param"; - // CSV file (*.csv)|*.csv - CsvFilter = "csv"; - // Text file (*.txt)|*.txt - TxtFilter = "txt"; - // Exported FMGs (*.fmg.json)|*.fmg.json - FmgJsonFilter = "fmg.json"; - // All file filter is implicitly added by NFD. Ideally this is used explicitly. - // All files|*.* - } - - public GameType Type { get; private set; } = GameType.Undefined; + public GameType Type => Locator.ActiveProject != null ? Locator.ActiveProject.Type : GameType.Undefined; /// /// The game interroot where all the game assets are /// - public string GameRootDirectory { get; private set; } + public string GameRootDirectory => Locator.ActiveProject.ParentProject.AssetLocator.RootDirectory; /// /// An optional override mod directory where modded files are stored /// - public string GameModDirectory { get; private set; } - - /// - /// Directory where misc DSMapStudio files associated with a project are stored. - /// - public string ProjectMiscDir => @$"{GameModDirectory}\DSMapStudio"; - - public string GetAssetPath(string relpath) - { - if (GameModDirectory != null) - { - var modpath = $@"{GameModDirectory}\{relpath}"; - if (File.Exists(modpath)) - { - return modpath; - } - } - - return $@"{GameRootDirectory}\{relpath}"; - } - - public GameType GetGameTypeForExePath(string exePath) - { - var type = GameType.Undefined; - if (exePath.ToLower().Contains("darksouls.exe")) - { - type = GameType.DarkSoulsPTDE; - } - else if (exePath.ToLower().Contains("darksoulsremastered.exe")) - { - type = GameType.DarkSoulsRemastered; - } - else if (exePath.ToLower().Contains("darksoulsii.exe")) - { - type = GameType.DarkSoulsIISOTFS; - } - else if (exePath.ToLower().Contains("darksoulsiii.exe")) - { - type = GameType.DarkSoulsIII; - } - else if (exePath.ToLower().Contains("eboot.bin")) - { - var path = Path.GetDirectoryName(exePath); - if (Directory.Exists($@"{path}\dvdroot_ps4")) - { - type = GameType.Bloodborne; - } - else - { - type = GameType.DemonsSouls; - } - } - else if (exePath.ToLower().Contains("sekiro.exe")) - { - type = GameType.Sekiro; - } - else if (exePath.ToLower().Contains("eldenring.exe")) - { - type = GameType.EldenRing; - } - else if (exePath.ToLower().Contains("armoredcore6.exe")) - { - type = GameType.ArmoredCoreVI; - } - - return type; - } - - public bool CheckFilesExpanded(string gamepath, GameType game) - { - if (game == GameType.EldenRing) - { - if (!Directory.Exists($@"{gamepath}\map")) - { - return false; - } - - if (!Directory.Exists($@"{gamepath}\asset")) - { - return false; - } - } - - if (game is GameType.DarkSoulsPTDE or GameType.DarkSoulsIII or GameType.Sekiro) - { - if (!Directory.Exists($@"{gamepath}\map")) - { - return false; - } - - if (!Directory.Exists($@"{gamepath}\obj")) - { - return false; - } - } - - if (game == GameType.DarkSoulsIISOTFS) - { - if (!Directory.Exists($@"{gamepath}\map")) - { - return false; - } - - if (!Directory.Exists($@"{gamepath}\model\obj")) - { - return false; - } - } - - if (game == GameType.ArmoredCoreVI) - { - if (!Directory.Exists($@"{gamepath}\map")) - { - return false; - } - - if (!Directory.Exists($@"{gamepath}\asset")) - { - return false; - } - } - - return true; - } - - public void SetModProjectDirectory(string dir) - { - GameModDirectory = dir; - } - - public void SetFromProjectSettings(ProjectSettings settings, string moddir) - { - Type = settings.GameType; - GameRootDirectory = settings.GameRoot; - GameModDirectory = moddir; - FullMapList = null; - } + public string GameModDirectory => Locator.ActiveProject.AssetLocator.RootDirectory; - public bool CreateRecoveryProject() - { - if (GameRootDirectory == null || GameModDirectory == null) - { - return false; - } + public string GetAssetPath(string relpath) => Locator.ActiveProject.AssetLocator.GetAssetPath(relpath); - try - { - var time = DateTime.Now.ToString("dd-MM-yyyy-(hh-mm-ss)", CultureInfo.InvariantCulture); - GameModDirectory = GameModDirectory + $@"\recovery\{time}"; - if (!Directory.Exists(GameModDirectory)) - { - Directory.CreateDirectory(GameModDirectory); - } + public bool CreateRecoveryProject() => Locator.ActiveProject.CreateRecoveryProject() != null; - return true; - } - catch (Exception e) - { - return false; - } - } + /// + /// Gets the full list of maps in the game (excluding chalice dungeons). Basically if there's an msb for it, + /// it will be in this list. + /// + /// + public List GetFullMapList() => Locator.ActiveProject.AssetLocator.GetFullMapList(); - public bool FileExists(string relpath) - { - if (GameModDirectory != null && File.Exists($@"{GameModDirectory}\{relpath}")) - { - return true; - } + public AssetDescription GetMapMSB(string mapid, bool writemode = false) => Locator.ActiveProject.AssetLocator.GetMapMSB(mapid, writemode); - if (File.Exists($@"{GameRootDirectory}\{relpath}")) - { - return true; - } + public List GetMapBTLs(string mapid, bool writemode = false) => Locator.ActiveProject.AssetLocator.GetMapBTLs(mapid, writemode); - return false; - } + public AssetDescription GetMapNVA(string mapid, bool writemode = false) => Locator.ActiveProject.AssetLocator.GetMapNVA(mapid, writemode); - public string GetOverridenFilePath(string relpath) - { - if (GameModDirectory != null && File.Exists($@"{GameModDirectory}\{relpath}")) - { - return $@"{GameModDirectory}\{relpath}"; - } + public AssetDescription GetWorldLoadListList(bool writemode = false) => Locator.ActiveProject.AssetLocator.GetWorldLoadListList(writemode); - if (File.Exists($@"{GameRootDirectory}\{relpath}")) - { - return $@"{GameRootDirectory}\{relpath}"; - } + /// + /// Get folders with msgbnds used in-game + /// + /// Dictionary with language name and path + public Dictionary GetMsgLanguages() => Locator.ActiveProject.AssetLocator.GetMsgLanguages(); - return null; - } + /// + /// Get path of item.msgbnd (english by default) + /// + public AssetDescription GetItemMsgbnd(string langFolder, bool writemode = false) => Locator.ActiveProject.AssetLocator.GetItemMsgbnd(langFolder, writemode); /// - /// Gets the full list of maps in the game (excluding chalice dungeons). Basically if there's an msb for it, - /// it will be in this list. + /// Get path of menu.msgbnd (english by default) /// - /// - public List GetFullMapList() - { - if (GameRootDirectory == null) - { - return null; - } + public AssetDescription GetMenuMsgbnd(string langFolder, bool writemode = false) => Locator.ActiveProject.AssetLocator.GetMenuMsgbnd(langFolder, writemode); - if (FullMapList != null) - { - return FullMapList; - } + public AssetDescription GetMsgbnd(string msgBndType, string langFolder, bool writemode = false) => Locator.ActiveProject.AssetLocator.GetMsgbnd(msgBndType, langFolder, writemode); - try - { - HashSet mapSet = new(); + public string GetGameIDForDir() => AssetUtils.GetGameIDForDir(Locator.ActiveProject.AssetLocator.Type); - // DS2 has its own structure for msbs, where they are all inside individual folders - if (Type == GameType.DarkSoulsIISOTFS) - { - List maps = Directory.GetFileSystemEntries(GameRootDirectory + @"\map", @"m*").ToList(); - if (GameModDirectory != null) - { - if (Directory.Exists(GameModDirectory + @"\map")) - { - maps.AddRange(Directory.GetFileSystemEntries(GameModDirectory + @"\map", @"m*").ToList()); - } - } + public string GetScriptAssetsCommonDir() => Locator.ActiveProject.AssetLocator.GetScriptAssetsCommonDir(); - foreach (var map in maps) - { - mapSet.Add(Path.GetFileNameWithoutExtension($@"{map}.blah")); - } - } - else - { - List msbFiles = Directory - .GetFileSystemEntries(GameRootDirectory + @"\map\MapStudio\", @"*.msb") - .Select(Path.GetFileNameWithoutExtension).ToList(); - msbFiles.AddRange(Directory - .GetFileSystemEntries(GameRootDirectory + @"\map\MapStudio\", @"*.msb.dcx") - .Select(Path.GetFileNameWithoutExtension).Select(Path.GetFileNameWithoutExtension).ToList()); - if (GameModDirectory != null && Directory.Exists(GameModDirectory + @"\map\MapStudio\")) - { - msbFiles.AddRange(Directory - .GetFileSystemEntries(GameModDirectory + @"\map\MapStudio\", @"*.msb") - .Select(Path.GetFileNameWithoutExtension).ToList()); - msbFiles.AddRange(Directory - .GetFileSystemEntries(GameModDirectory + @"\map\MapStudio\", @"*.msb.dcx") - .Select(Path.GetFileNameWithoutExtension).Select(Path.GetFileNameWithoutExtension) - .ToList()); - } + public string GetScriptAssetsDir() => Locator.ActiveProject.AssetLocator.GetScriptAssetsDir(); - foreach (var msb in msbFiles) - { - mapSet.Add(msb); - } - } + public string GetUpgraderAssetsDir() => Locator.ActiveProject.AssetLocator.GetUpgraderAssetsDir(); - Regex mapRegex = new(@"^m\d{2}_\d{2}_\d{2}_\d{2}$"); - List mapList = mapSet.Where(x => mapRegex.IsMatch(x)).ToList(); - mapList.Sort(); - FullMapList = mapList; - return FullMapList; - } - catch (DirectoryNotFoundException e) - { - // Game is likely not UXM unpacked - return new List(); - } - } + public string GetGameOffsetsAssetsDir() => Locator.ActiveProject.AssetLocator.GetGameOffsetsAssetsDir(); - public AssetDescription GetMapMSB(string mapid, bool writemode = false) - { - AssetDescription ad = new(); - ad.AssetPath = null; - if (mapid.Length != 12) - { - return ad; - } + public string GetStrippedRowNamesPath(string paramName) => Locator.ActiveProject.AssetLocator.GetStrippedRowNamesPath(paramName); - string preferredPath; - string backupPath; - // SOFTS - if (Type == GameType.DarkSoulsIISOTFS) - { - preferredPath = $@"map\{mapid}\{mapid}.msb"; - backupPath = $@"map\{mapid}\{mapid}.msb"; - } - // BB chalice maps - else if (Type == GameType.Bloodborne && mapid.StartsWith("m29")) - { - preferredPath = $@"\map\MapStudio\{mapid.Substring(0, 9)}_00\{mapid}.msb.dcx"; - backupPath = $@"\map\MapStudio\{mapid.Substring(0, 9)}_00\{mapid}.msb"; - } - // DeS, DS1, DS1R - else if (Type == GameType.DarkSoulsPTDE || Type == GameType.DarkSoulsRemastered || - Type == GameType.DemonsSouls) - { - preferredPath = $@"\map\MapStudio\{mapid}.msb"; - backupPath = $@"\map\MapStudio\{mapid}.msb.dcx"; - } - // BB, DS3, ER, SSDT - else if (Type == GameType.Bloodborne || Type == GameType.DarkSoulsIII || Type == GameType.EldenRing || - Type == GameType.Sekiro) - { - preferredPath = $@"\map\MapStudio\{mapid}.msb.dcx"; - backupPath = $@"\map\MapStudio\{mapid}.msb"; - } - else - { - preferredPath = $@"\map\MapStudio\{mapid}.msb.dcx"; - backupPath = $@"\map\MapStudio\{mapid}.msb"; - } + public PARAMDEF GetParamdefForParam(string paramType) => Locator.ActiveProject.AssetLocator.GetParamdefForParam(paramType); - if ((GameModDirectory != null && File.Exists($@"{GameModDirectory}\{preferredPath}")) || - (writemode && GameModDirectory != null)) - { - ad.AssetPath = $@"{GameModDirectory}\{preferredPath}"; - } - else if ((GameModDirectory != null && File.Exists($@"{GameModDirectory}\{backupPath}")) || - (writemode && GameModDirectory != null)) - { - ad.AssetPath = $@"{GameModDirectory}\{backupPath}"; - } - else if (File.Exists($@"{GameRootDirectory}\{preferredPath}")) - { - ad.AssetPath = $@"{GameRootDirectory}\{preferredPath}"; - } - else if (File.Exists($@"{GameRootDirectory}\{backupPath}")) - { - ad.AssetPath = $@"{GameRootDirectory}\{backupPath}"; - } + public AssetDescription GetDS2GeneratorParam(string mapid, bool writemode = false) => Locator.ActiveProject.AssetLocator.GetDS2GeneratorParam(mapid, writemode); - ad.AssetName = mapid; - return ad; - } + public AssetDescription GetDS2GeneratorLocationParam(string mapid, bool writemode = false) => Locator.ActiveProject.AssetLocator.GetDS2GeneratorLocationParam(mapid, writemode); + public AssetDescription GetDS2GeneratorRegistParam(string mapid, bool writemode = false) => Locator.ActiveProject.AssetLocator.GetDS2GeneratorRegistParam(mapid, writemode); - public List GetMapBTLs(string mapid, bool writemode = false) - { - List adList = new(); - if (mapid.Length != 12) - { - return adList; - } + public AssetDescription GetDS2EventParam(string mapid, bool writemode = false) => Locator.ActiveProject.AssetLocator.GetDS2EventParam(mapid, writemode); - if (Type is GameType.DarkSoulsIISOTFS) - { - // DS2 BTL is located inside map's .gibdt file - AssetDescription ad = new(); - var path = $@"model\map\g{mapid[1..]}.gibhd"; + public AssetDescription GetDS2EventLocationParam(string mapid, bool writemode = false) => Locator.ActiveProject.AssetLocator.GetDS2EventLocationParam(mapid, writemode); - if ((GameModDirectory != null && File.Exists($@"{GameModDirectory}\{path}")) || - (writemode && GameModDirectory != null)) - { - ad.AssetPath = $@"{GameModDirectory}\{path}"; - } - else if (File.Exists($@"{GameRootDirectory}\{path}")) - { - ad.AssetPath = $@"{GameRootDirectory}\{path}"; - } + public AssetDescription GetDS2ObjInstanceParam(string mapid, bool writemode = false) => Locator.ActiveProject.AssetLocator.GetDS2ObjInstanceParam(mapid, writemode); - if (ad.AssetPath != null) - { - ad.AssetName = $@"g{mapid[1..]}"; - ad.AssetVirtualPath = $@"{mapid}\light.btl.dcx"; - adList.Add(ad); - } + public List GetMapModelsFromBXF(string mapid) => Locator.ActiveProject.AssetLocator.GetMapModelsFromBXF(mapid); + public List GetMapModels(string mapid) => Locator.ActiveProject.AssetLocator.GetMapModels(mapid); + public string MapModelNameToAssetName(string mapid, string modelname) => Locator.ActiveProject.AssetLocator.MapModelNameToAssetName(mapid, modelname); - AssetDescription ad2 = new(); - path = $@"model_lq\map\g{mapid[1..]}.gibhd"; + /// + /// Gets the adjusted map ID that contains all the map assets + /// + /// The msb map ID to adjust + /// The map ID for the purpose of asset storage + public string GetAssetMapID(string mapid) => Locator.ActiveProject.AssetLocator.GetAssetMapID(mapid); - if ((GameModDirectory != null && File.Exists($@"{GameModDirectory}\{path}")) || - (writemode && GameModDirectory != null)) - { - ad2.AssetPath = $@"{GameModDirectory}\{path}"; - } - else if (File.Exists($@"{GameRootDirectory}\{path}")) - { - ad2.AssetPath = $@"{GameRootDirectory}\{path}"; - } + public AssetDescription GetMapModel(string mapid, string model) => Locator.ActiveProject.AssetLocator.GetMapModel(mapid, model); - if (ad2.AssetPath != null) - { - ad2.AssetName = $@"g{mapid[1..]}_lq"; - ad2.AssetVirtualPath = $@"{mapid}\light.btl.dcx"; - adList.Add(ad2); - } - } - else if (Type is GameType.Bloodborne or GameType.DarkSoulsIII or GameType.Sekiro or GameType.EldenRing or GameType.ArmoredCoreVI) - { - string path; - if (Type is GameType.EldenRing or GameType.ArmoredCoreVI) - { - path = $@"map\{mapid[..3]}\{mapid}"; - } - else - { - path = $@"map\{mapid}"; - } + public AssetDescription GetMapCollisionModel(string mapid, string model, bool hi = true) => Locator.ActiveProject.AssetLocator.GetMapCollisionModel(mapid, model, hi); - List files = new(); + public List GetMapTextures(string mapid) => Locator.ActiveProject.AssetLocator.GetMapTextures(mapid); - if (Directory.Exists($@"{GameRootDirectory}\{path}")) - { - files.AddRange(Directory.GetFiles($@"{GameRootDirectory}\{path}", "*.btl").ToList()); - files.AddRange(Directory.GetFiles($@"{GameRootDirectory}\{path}", "*.btl.dcx").ToList()); - } + public List GetEnvMapTextureNames(string mapid) => Locator.ActiveProject.AssetLocator.GetEnvMapTextureNames(mapid); - if (Directory.Exists($@"{GameModDirectory}\{path}")) - { - // Check for additional BTLs the user has created. - files.AddRange(Directory.GetFiles($@"{GameModDirectory}\{path}", "*.btl").ToList()); - files.AddRange(Directory.GetFiles($@"{GameModDirectory}\{path}", "*.btl.dcx").ToList()); - files = files.DistinctBy(f => f.Split("\\").Last()).ToList(); - } + public AssetDescription GetChrTextures(string chrid) => Locator.ActiveProject.AssetLocator.GetChrTextures(chrid); - foreach (var file in files) - { - AssetDescription ad = new(); - var fileName = file.Split("\\").Last(); - if ((GameModDirectory != null && File.Exists($@"{GameModDirectory}\{path}\{fileName}")) || - (writemode && GameModDirectory != null)) - { - ad.AssetPath = $@"{GameModDirectory}\{path}\{fileName}"; - } - else if (File.Exists($@"{GameRootDirectory}\{path}\{fileName}")) - { - ad.AssetPath = $@"{GameRootDirectory}\{path}\{fileName}"; - } + public AssetDescription GetMapNVMModel(string mapid, string model) => Locator.ActiveProject.AssetLocator.GetMapNVMModel(mapid, model); - if (ad.AssetPath != null) - { - ad.AssetName = fileName; - adList.Add(ad); - } - } - } + public AssetDescription GetHavokNavmeshes(string mapid) => Locator.ActiveProject.AssetLocator.GetHavokNavmeshes(mapid); - return adList; - } + public AssetDescription GetHavokNavmeshModel(string mapid, string model) => Locator.ActiveProject.AssetLocator.GetHavokNavmeshModel(mapid, model); - public AssetDescription GetMapNVA(string mapid, bool writemode = false) - { - AssetDescription ad = new(); - ad.AssetPath = null; - if (mapid.Length != 12) - { - return ad; - } - // BB chalice maps + public List GetChrModels() => Locator.ActiveProject.AssetLocator.GetChrModels(); - if (Type == GameType.Bloodborne && mapid.StartsWith("m29")) - { - var path = $@"\map\{mapid.Substring(0, 9)}_00\{mapid}"; - if ((GameModDirectory != null && File.Exists($@"{GameModDirectory}\{path}.nva.dcx")) || - (writemode && GameModDirectory != null && Type != GameType.DarkSoulsPTDE)) - { - ad.AssetPath = $@"{GameModDirectory}\{path}.nva.dcx"; - } - else if (File.Exists($@"{GameRootDirectory}\{path}.nva.dcx")) - { - ad.AssetPath = $@"{GameRootDirectory}\{path}.nva.dcx"; - } - } - else - { - var path = $@"\map\{mapid}\{mapid}"; - if ((GameModDirectory != null && File.Exists($@"{GameModDirectory}\{path}.nva.dcx")) || - (writemode && GameModDirectory != null && Type != GameType.DarkSoulsPTDE)) - { - ad.AssetPath = $@"{GameModDirectory}\{path}.nva.dcx"; - } - else if (File.Exists($@"{GameRootDirectory}\{path}.nva.dcx")) - { - ad.AssetPath = $@"{GameRootDirectory}\{path}.nva.dcx"; - } - else if ((GameModDirectory != null && File.Exists($@"{GameModDirectory}\{path}.nva")) || - (writemode && GameModDirectory != null)) - { - ad.AssetPath = $@"{GameModDirectory}\{path}.nva"; - } - else if (File.Exists($@"{GameRootDirectory}\{path}.nva")) - { - ad.AssetPath = $@"{GameRootDirectory}\{path}.nva"; - } - } + public AssetDescription GetChrModel(string chr) => Locator.ActiveProject.AssetLocator.GetChrModel(chr); - ad.AssetName = mapid; - return ad; - } + public List GetObjModels() => Locator.ActiveProject.AssetLocator.GetObjModels(); - public AssetDescription GetWorldLoadListList(bool writemode = false) - { - AssetDescription ad = new(); + public AssetDescription GetObjModel(string obj) => Locator.ActiveProject.AssetLocator.GetObjModel(obj); - if (writemode || (GameModDirectory != null && File.Exists($@"{GameModDirectory}\map\worldmsblist.worldloadlistlist.dcx"))) - { - ad.AssetPath = $@"{GameModDirectory}\map\worldmsblist.worldloadlistlist.dcx"; - } else - { - ad.AssetPath = $@"{GameRootDirectory}\map\worldmsblist.worldloadlistlist.dcx"; - } - - return ad; - } + public AssetDescription GetObjTexture(string obj) => Locator.ActiveProject.AssetLocator.GetObjTexture(obj); - /// - /// Get folders with msgbnds used in-game - /// - /// Dictionary with language name and path - public Dictionary GetMsgLanguages() - { - Dictionary dict = new(); - List folders = new(); - try - { - if (Type == GameType.DemonsSouls) - { - folders = Directory.GetDirectories(GameRootDirectory + @"\msg").ToList(); - // Japanese uses root directory - if (File.Exists(GameRootDirectory + @"\msg\menu.msgbnd.dcx") || - File.Exists(GameRootDirectory + @"\msg\item.msgbnd.dcx")) - { - dict.Add("Japanese", ""); - } - } - else if (Type == GameType.DarkSoulsIISOTFS) - { - folders = Directory.GetDirectories(GameRootDirectory + @"\menu\text").ToList(); - } - else - { - // Exclude folders that don't have typical msgbnds - folders = Directory.GetDirectories(GameRootDirectory + @"\msg") - .Where(x => !"common,as,eu,jp,na,uk,japanese".Contains(x.Split("\\").Last())).ToList(); - } + public AssetDescription GetAetTexture(string aetid) => Locator.ActiveProject.AssetLocator.GetAetTexture(aetid); - foreach (var path in folders) - { - dict.Add(path.Split("\\").Last(), path); - } - } - catch (Exception e) when (e is DirectoryNotFoundException or FileNotFoundException) - { - } + public List GetPartsModels() => Locator.ActiveProject.AssetLocator.GetPartsModels(); - return dict; - } + public AssetDescription GetPartsModel(string part) => Locator.ActiveProject.AssetLocator.GetPartsModel(part); - /// - /// Get path of item.msgbnd (english by default) - /// - public AssetDescription GetItemMsgbnd(string langFolder, bool writemode = false) - { - return GetMsgbnd("item", langFolder, writemode); - } + public AssetDescription GetPartTextures(string partsId) => Locator.ActiveProject.AssetLocator.GetPartTextures(partsId); /// - /// Get path of menu.msgbnd (english by default) - /// - public AssetDescription GetMenuMsgbnd(string langFolder, bool writemode = false) - { - return GetMsgbnd("menu", langFolder, writemode); - } - - public AssetDescription GetMsgbnd(string msgBndType, string langFolder, bool writemode = false) - { - AssetDescription ad = new(); - var path = $@"msg\{langFolder}\{msgBndType}.msgbnd.dcx"; - if (Type == GameType.DemonsSouls) - { - path = $@"msg\{langFolder}\{msgBndType}.msgbnd.dcx"; - // Demon's Souls has msgbnds directly in the msg folder - if (!File.Exists($@"{GameRootDirectory}\{path}")) - { - path = $@"msg\{msgBndType}.msgbnd.dcx"; - } - } - else if (Type == GameType.DarkSoulsPTDE) - { - path = $@"msg\{langFolder}\{msgBndType}.msgbnd"; - } - else if (Type == GameType.DarkSoulsRemastered) - { - path = $@"msg\{langFolder}\{msgBndType}.msgbnd.dcx"; - } - else if (Type == GameType.DarkSoulsIISOTFS) - { - // DS2 does not have an msgbnd but loose fmg files instead - path = $@"menu\text\{langFolder}"; - AssetDescription ad2 = new(); - ad2.AssetPath = writemode ? path : $@"{GameRootDirectory}\{path}"; - //TODO: doesn't support project files - return ad2; - } - else if (Type == GameType.DarkSoulsIII) - { - path = $@"msg\{langFolder}\{msgBndType}_dlc2.msgbnd.dcx"; - } - - if (writemode) - { - ad.AssetPath = path; - return ad; - } - - if ((GameModDirectory != null && File.Exists($@"{GameModDirectory}\{path}")) || - (writemode && GameModDirectory != null)) - { - ad.AssetPath = $@"{GameModDirectory}\{path}"; - } - else if (File.Exists($@"{GameRootDirectory}\{path}")) - { - ad.AssetPath = $@"{GameRootDirectory}\{path}"; - } - - return ad; - } - - public string GetGameIDForDir() - { - switch (Type) - { - case GameType.DemonsSouls: - return "DES"; - case GameType.DarkSoulsPTDE: - return "DS1"; - case GameType.DarkSoulsRemastered: - return "DS1R"; - case GameType.DarkSoulsIISOTFS: - return "DS2S"; - case GameType.Bloodborne: - return "BB"; - case GameType.DarkSoulsIII: - return "DS3"; - case GameType.Sekiro: - return "SDT"; - case GameType.EldenRing: - return "ER"; - case GameType.ArmoredCoreVI: - return "AC6"; - default: - throw new Exception("Game type not set"); - } - } - - public string GetAliasAssetsDir() - { - return $@"Assets\Aliases\{GetGameIDForDir()}"; - } - - - public string GetScriptAssetsCommonDir() - { - return @"Assets\MassEditScripts\Common"; - } - - public string GetScriptAssetsDir() - { - return $@"Assets\MassEditScripts\{GetGameIDForDir()}"; - } - - public string GetUpgraderAssetsDir() - { - return $@"{GetParamAssetsDir()}\Upgrader"; - } - - public string GetGameOffsetsAssetsDir() - { - return $@"Assets\GameOffsets\{GetGameIDForDir()}"; - } - - public string GetParamAssetsDir() - { - return $@"Assets\Paramdex\{GetGameIDForDir()}"; - } - - public string GetParamdefDir() - { - return $@"{GetParamAssetsDir()}\Defs"; - } - - public string GetTentativeParamTypePath() - { - return $@"{GetParamAssetsDir()}\Defs\TentativeParamType.csv"; - } - - public ulong[] GetParamdefPatches() - { - if (Directory.Exists($@"{GetParamAssetsDir()}\DefsPatch")) - { - var entries = Directory.GetFileSystemEntries($@"{GetParamAssetsDir()}\DefsPatch"); - return entries.Select(e => ulong.Parse(Path.GetFileNameWithoutExtension(e))).ToArray(); - } - - return new ulong[] { }; - } - - public string GetParamdefPatchDir(ulong patch) - { - return $@"{GetParamAssetsDir()}\DefsPatch\{patch}"; - } - - public string GetParammetaDir() - { - return $@"{GetParamAssetsDir()}\Meta"; - } - - public string GetParamNamesDir() - { - return $@"{GetParamAssetsDir()}\Names"; - } - - public string GetStrippedRowNamesPath(string paramName) - { - var dir = $@"{ProjectMiscDir}\Stripped Row Names"; - return $@"{dir}\{paramName}.txt"; - } - - public PARAMDEF GetParamdefForParam(string paramType) - { - PARAMDEF pd = PARAMDEF.XmlDeserialize($@"{GetParamdefDir()}\{paramType}.xml"); - return pd; - } - - public AssetDescription GetDS2GeneratorParam(string mapid, bool writemode = false) - { - AssetDescription ad = new(); - var path = $@"Param\generatorparam_{mapid}"; - if ((GameModDirectory != null && File.Exists($@"{GameModDirectory}\{path}.param")) || - (writemode && GameModDirectory != null)) - { - ad.AssetPath = $@"{GameModDirectory}\{path}.param"; - } - else if (File.Exists($@"{GameRootDirectory}\{path}.param")) - { - ad.AssetPath = $@"{GameRootDirectory}\{path}.param"; - } - - ad.AssetName = mapid + "_generators"; - return ad; - } - - public AssetDescription GetDS2GeneratorLocationParam(string mapid, bool writemode = false) - { - AssetDescription ad = new(); - var path = $@"Param\generatorlocation_{mapid}"; - if ((GameModDirectory != null && File.Exists($@"{GameModDirectory}\{path}.param")) || - (writemode && GameModDirectory != null)) - { - ad.AssetPath = $@"{GameModDirectory}\{path}.param"; - } - else if (File.Exists($@"{GameRootDirectory}\{path}.param")) - { - ad.AssetPath = $@"{GameRootDirectory}\{path}.param"; - } - - ad.AssetName = mapid + "_generator_locations"; - return ad; - } - - public AssetDescription GetDS2GeneratorRegistParam(string mapid, bool writemode = false) - { - AssetDescription ad = new(); - var path = $@"Param\generatorregistparam_{mapid}"; - if ((GameModDirectory != null && File.Exists($@"{GameModDirectory}\{path}.param")) || - (writemode && GameModDirectory != null)) - { - ad.AssetPath = $@"{GameModDirectory}\{path}.param"; - } - else if (File.Exists($@"{GameRootDirectory}\{path}.param")) - { - ad.AssetPath = $@"{GameRootDirectory}\{path}.param"; - } - - ad.AssetName = mapid + "_generator_registrations"; - return ad; - } - - public AssetDescription GetDS2EventParam(string mapid, bool writemode = false) - { - AssetDescription ad = new(); - var path = $@"Param\eventparam_{mapid}"; - if ((GameModDirectory != null && File.Exists($@"{GameModDirectory}\{path}.param")) || - (writemode && GameModDirectory != null)) - { - ad.AssetPath = $@"{GameModDirectory}\{path}.param"; - } - else if (File.Exists($@"{GameRootDirectory}\{path}.param")) - { - ad.AssetPath = $@"{GameRootDirectory}\{path}.param"; - } - - ad.AssetName = mapid + "_event_params"; - return ad; - } - - public AssetDescription GetDS2EventLocationParam(string mapid, bool writemode = false) - { - AssetDescription ad = new(); - var path = $@"Param\eventlocation_{mapid}"; - if ((GameModDirectory != null && File.Exists($@"{GameModDirectory}\{path}.param")) || - (writemode && GameModDirectory != null)) - { - ad.AssetPath = $@"{GameModDirectory}\{path}.param"; - } - else if (File.Exists($@"{GameRootDirectory}\{path}.param")) - { - ad.AssetPath = $@"{GameRootDirectory}\{path}.param"; - } - - ad.AssetName = mapid + "_event_locations"; - return ad; - } - - public AssetDescription GetDS2ObjInstanceParam(string mapid, bool writemode = false) - { - AssetDescription ad = new(); - var path = $@"Param\mapobjectinstanceparam_{mapid}"; - if ((GameModDirectory != null && File.Exists($@"{GameModDirectory}\{path}.param")) || - (writemode && GameModDirectory != null)) - { - ad.AssetPath = $@"{GameModDirectory}\{path}.param"; - } - else if (File.Exists($@"{GameRootDirectory}\{path}.param")) - { - ad.AssetPath = $@"{GameRootDirectory}\{path}.param"; - } - - ad.AssetName = mapid + "_object_instance_params"; - return ad; - } - - // Used to get the map model list from within the mapbhd/bdt - public List GetMapModelsFromBXF(string mapid) - { - List ret = new(); - - if (Locator.AssetLocator.Type is GameType.DarkSoulsIISOTFS) - { - var path = $@"{Locator.AssetLocator.GameModDirectory}/model/map/{mapid}.mapbdt"; - - if (!File.Exists(path)) - { - path = $@"{Locator.AssetLocator.GameRootDirectory}/model/map/{mapid}.mapbdt"; - } - - if (File.Exists(path)) - { - var bdtPath = path; - var bhdPath = path.Replace("bdt", "bhd"); - - var bxf = BXF4.Read(bhdPath, bdtPath); - - if (bxf != null) - { - foreach (var file in bxf.Files) - { - if (file.Name.Contains(".flv")) - { - var name = Path.GetFileNameWithoutExtension(Path.GetFileNameWithoutExtension(file.Name)); - - AssetDescription ad = new(); - ad.AssetName = name; - ad.AssetArchiveVirtualPath = $@"map/{name}/model/"; - - ret.Add(ad); - } - } - } - } - } - - return ret; - } - public List GetMapModels(string mapid) - { - List ret = new(); - if (Type == GameType.DarkSoulsIII || Type == GameType.Sekiro) - { - if (!Directory.Exists(GameRootDirectory + $@"\map\{mapid}\")) - { - return ret; - } - - List mapfiles = Directory - .GetFileSystemEntries(GameRootDirectory + $@"\map\{mapid}\", @"*.mapbnd.dcx").ToList(); - foreach (var f in mapfiles) - { - AssetDescription ad = new(); - ad.AssetPath = f; - var name = Path.GetFileNameWithoutExtension(Path.GetFileNameWithoutExtension(f)); - ad.AssetName = name; - ad.AssetArchiveVirtualPath = $@"map/{mapid}/model/{name}"; - ad.AssetVirtualPath = $@"map/{mapid}/model/{name}/{name}.flver"; - ret.Add(ad); - } - } - else if (Type == GameType.DarkSoulsIISOTFS) - { - AssetDescription ad = new(); - var name = mapid; - ad.AssetName = name; - ad.AssetArchiveVirtualPath = $@"map/{mapid}/model"; - ret.Add(ad); - } - else if (Type == GameType.EldenRing) - { - var mapPath = GameRootDirectory + $@"\map\{mapid[..3]}\{mapid}"; - if (!Directory.Exists(mapPath)) - { - return ret; - } - - List mapfiles = Directory.GetFileSystemEntries(mapPath, @"*.mapbnd.dcx").ToList(); - foreach (var f in mapfiles) - { - AssetDescription ad = new(); - ad.AssetPath = f; - var name = Path.GetFileNameWithoutExtension(Path.GetFileNameWithoutExtension(f)); - ad.AssetName = name; - ad.AssetArchiveVirtualPath = $@"map/{mapid}/model/{name}"; - ad.AssetVirtualPath = $@"map/{mapid}/model/{name}/{name}.flver"; - ret.Add(ad); - } - } - else if (Type == GameType.ArmoredCoreVI) - { - var mapPath = GameRootDirectory + $@"\map\{mapid[..3]}\{mapid}"; - if (!Directory.Exists(mapPath)) - { - return ret; - } - - List mapfiles = Directory.GetFileSystemEntries(mapPath, @"*.mapbnd.dcx").ToList(); - foreach (var f in mapfiles) - { - AssetDescription ad = new(); - ad.AssetPath = f; - var name = Path.GetFileNameWithoutExtension(Path.GetFileNameWithoutExtension(f)); - ad.AssetName = name; - ad.AssetArchiveVirtualPath = $@"map/{mapid}/model/{name}"; - ad.AssetVirtualPath = $@"map/{mapid}/model/{name}/{name}.flver"; - ret.Add(ad); - } - } - else - { - if (!Directory.Exists(GameRootDirectory + $@"\map\{mapid}\")) - { - return ret; - } - - var ext = Type == GameType.DarkSoulsPTDE ? @"*.flver" : @"*.flver.dcx"; - List mapfiles = Directory.GetFileSystemEntries(GameRootDirectory + $@"\map\{mapid}\", ext) - .ToList(); - foreach (var f in mapfiles) - { - AssetDescription ad = new(); - ad.AssetPath = f; - var name = Path.GetFileNameWithoutExtension(Path.GetFileNameWithoutExtension(f)); - ad.AssetName = name; - // ad.AssetArchiveVirtualPath = $@"map/{mapid}/model/{name}"; - ad.AssetVirtualPath = $@"map/{mapid}/model/{name}/{name}.flver"; - ret.Add(ad); - } - } - - return ret; - } - - public string MapModelNameToAssetName(string mapid, string modelname) - { - if (Type == GameType.DarkSoulsPTDE || Type == GameType.DarkSoulsRemastered) - { - return $@"{modelname}A{mapid.Substring(1, 2)}"; - } - - if (Type == GameType.DemonsSouls) - { - return $@"{modelname}"; - } - - if (Type == GameType.DarkSoulsIISOTFS) - { - return modelname; - } - - return $@"{mapid}_{modelname.Substring(1)}"; - } - - /// - /// Gets the adjusted map ID that contains all the map assets - /// - /// The msb map ID to adjust - /// The map ID for the purpose of asset storage - public string GetAssetMapID(string mapid) - { - if (Type is GameType.EldenRing or GameType.ArmoredCoreVI) - { - return mapid; - } - - if (Type is GameType.DarkSoulsRemastered) - { - if (mapid.StartsWith("m99")) - { - // DSR m99 maps contain their own assets - return mapid; - } - } - else if (Type is GameType.DemonsSouls) - { - return mapid; - } - else if (Type is GameType.Bloodborne) - { - if (mapid.StartsWith("m29")) - { - // Special case for chalice dungeon assets - return "m29_00_00_00"; - } - } - - // Default - return mapid.Substring(0, 6) + "_00_00"; - } - - public AssetDescription GetMapModel(string mapid, string model) - { - AssetDescription ret = new(); - if (Type == GameType.DarkSoulsPTDE || Type == GameType.Bloodborne || Type == GameType.DemonsSouls) - { - ret.AssetPath = GetAssetPath($@"map\{mapid}\{model}.flver"); - } - else if (Type == GameType.DarkSoulsRemastered) - { - ret.AssetPath = GetAssetPath($@"map\{mapid}\{model}.flver.dcx"); - } - else if (Type == GameType.DarkSoulsIISOTFS) - { - ret.AssetPath = GetAssetPath($@"model\map\{mapid}.mapbhd"); - } - else if (Type == GameType.EldenRing) - { - ret.AssetPath = GetAssetPath($@"map\{mapid[..3]}\{mapid}\{model}.mapbnd.dcx"); - } - else if (Type == GameType.ArmoredCoreVI) - { - ret.AssetPath = GetAssetPath($@"map\{mapid[..3]}\{mapid}\{model}.mapbnd.dcx"); - } - else - { - ret.AssetPath = GetAssetPath($@"map\{mapid}\{model}.mapbnd.dcx"); - } - - ret.AssetName = model; - if (Type == GameType.DarkSoulsIISOTFS) - { - ret.AssetArchiveVirtualPath = $@"map/{mapid}/model"; - ret.AssetVirtualPath = $@"map/{mapid}/model/{model}.flv.dcx"; - } - else - { - if (Type is not GameType.DemonsSouls - and not GameType.DarkSoulsPTDE - and not GameType.DarkSoulsRemastered - and not GameType.Bloodborne) - { - ret.AssetArchiveVirtualPath = $@"map/{mapid}/model/{model}"; - } - - ret.AssetVirtualPath = $@"map/{mapid}/model/{model}/{model}.flver"; - } - - return ret; - } - - public AssetDescription GetMapCollisionModel(string mapid, string model, bool hi = true) - { - AssetDescription ret = new(); - if (Type == GameType.DarkSoulsPTDE || Type == GameType.DemonsSouls) - { - if (hi) - { - ret.AssetPath = GetAssetPath($@"map\{mapid}\{model}.hkx"); - ret.AssetName = model; - ret.AssetVirtualPath = $@"map/{mapid}/hit/hi/{model}.hkx"; - } - else - { - ret.AssetPath = GetAssetPath($@"map\{mapid}\l{model.Substring(1)}.hkx"); - ret.AssetName = model; - ret.AssetVirtualPath = $@"map/{mapid}/hit/lo/l{model.Substring(1)}.hkx"; - } - } - else if (Type == GameType.DarkSoulsIISOTFS) - { - ret.AssetPath = GetAssetPath($@"model\map\h{mapid.Substring(1)}.hkxbhd"); - ret.AssetName = model; - ret.AssetVirtualPath = $@"map/{mapid}/hit/hi/{model}.hkx.dcx"; - ret.AssetArchiveVirtualPath = $@"map/{mapid}/hit/hi"; - } - else if (Type == GameType.DarkSoulsIII || Type == GameType.Bloodborne) - { - if (hi) - { - ret.AssetPath = GetAssetPath($@"map\{mapid}\h{mapid.Substring(1)}.hkxbhd"); - ret.AssetName = model; - ret.AssetVirtualPath = $@"map/{mapid}/hit/hi/h{model.Substring(1)}.hkx.dcx"; - ret.AssetArchiveVirtualPath = $@"map/{mapid}/hit/hi"; - } - else - { - ret.AssetPath = GetAssetPath($@"map\{mapid}\l{mapid.Substring(1)}.hkxbhd"); - ret.AssetName = model; - ret.AssetVirtualPath = $@"map/{mapid}/hit/lo/l{model.Substring(1)}.hkx.dcx"; - ret.AssetArchiveVirtualPath = $@"map/{mapid}/hit/lo"; - } - } - else - { - return GetNullAsset(); - } - - return ret; - } - - public List GetMapTextures(string mapid) - { - List ads = new(); - - if (Type == GameType.DarkSoulsIISOTFS) - { - AssetDescription t = new(); - t.AssetPath = GetAssetPath($@"model\map\t{mapid.Substring(1)}.tpfbhd"); - t.AssetArchiveVirtualPath = $@"map/tex/{mapid}/tex"; - ads.Add(t); - } - else if (Type == GameType.DarkSoulsPTDE) - { - // TODO - } - else if (Type == GameType.EldenRing) - { - // TODO ER - } - else if (Type == GameType.ArmoredCoreVI) - { - // TODO AC6 - } - else if (Type == GameType.DemonsSouls) - { - var mid = mapid.Substring(0, 3); - var paths = Directory.GetFileSystemEntries($@"{GameRootDirectory}\map\{mid}\", "*.tpf.dcx"); - foreach (var path in paths) - { - AssetDescription ad = new(); - ad.AssetPath = path; - var tid = Path.GetFileNameWithoutExtension(path).Substring(4, 4); - ad.AssetVirtualPath = $@"map/tex/{mid}/{tid}"; - ads.Add(ad); - } - } - else - { - // Clean this up. Even if it's common code having something like "!=Sekiro" can lead to future issues - var mid = mapid.Substring(0, 3); - - AssetDescription t0000 = new(); - t0000.AssetPath = GetAssetPath($@"map\{mid}\{mid}_0000.tpfbhd"); - t0000.AssetArchiveVirtualPath = $@"map/tex/{mid}/0000"; - ads.Add(t0000); - - AssetDescription t0001 = new(); - t0001.AssetPath = GetAssetPath($@"map\{mid}\{mid}_0001.tpfbhd"); - t0001.AssetArchiveVirtualPath = $@"map/tex/{mid}/0001"; - ads.Add(t0001); - - AssetDescription t0002 = new(); - t0002.AssetPath = GetAssetPath($@"map\{mid}\{mid}_0002.tpfbhd"); - t0002.AssetArchiveVirtualPath = $@"map/tex/{mid}/0002"; - ads.Add(t0002); - - AssetDescription t0003 = new(); - t0003.AssetPath = GetAssetPath($@"map\{mid}\{mid}_0003.tpfbhd"); - t0003.AssetArchiveVirtualPath = $@"map/tex/{mid}/0003"; - ads.Add(t0003); - - if (Type == GameType.DarkSoulsRemastered) - { - AssetDescription env = new(); - env.AssetPath = GetAssetPath($@"map\{mid}\GI_EnvM_{mid}.tpfbhd"); - env.AssetArchiveVirtualPath = $@"map/tex/{mid}/env"; - ads.Add(env); - } - else if (Type == GameType.Bloodborne || Type == GameType.DarkSoulsIII) - { - AssetDescription env = new(); - env.AssetPath = GetAssetPath($@"map\{mid}\{mid}_envmap.tpf.dcx"); - env.AssetVirtualPath = $@"map/tex/{mid}/env"; - ads.Add(env); - } - else if (Type == GameType.Sekiro) - { - //TODO SDT - } - } - - return ads; - } - - public List GetEnvMapTextureNames(string mapid) - { - List l = new(); - if (Type == GameType.DarkSoulsIII) - { - var mid = mapid.Substring(0, 3); - if (File.Exists(GetAssetPath($@"map\{mid}\{mid}_envmap.tpf.dcx"))) - { - TPF t = TPF.Read(GetAssetPath($@"map\{mid}\{mid}_envmap.tpf.dcx")); - foreach (TPF.Texture tex in t.Textures) - { - l.Add(tex.Name); - } - } - } - - return l; - } - - private string GetChrTexturePath(string chrid) - { - if (Type is GameType.DemonsSouls) - { - return GetOverridenFilePath($@"chr\{chrid}\{chrid}.tpf"); - } - - if (Type is GameType.DarkSoulsPTDE) - { - var path = GetOverridenFilePath($@"chr\{chrid}\{chrid}.tpf"); - if (path != null) - { - return path; - } - - return GetOverridenFilePath($@"chr\{chrid}.chrbnd"); - } - - if (Type is GameType.DarkSoulsIISOTFS) - { - return GetOverridenFilePath($@"model\chr\{chrid}.texbnd"); - } - - if (Type is GameType.DarkSoulsRemastered) - { - // TODO: Some textures require getting chrtpfbhd from chrbnd, then using it with chrtpfbdt in chr folder. - return GetOverridenFilePath($@"chr\{chrid}.chrbnd"); - } - - if (Type is GameType.Bloodborne) - { - return GetOverridenFilePath($@"chr\{chrid}_2.tpf.dcx"); - } - - if (Type is GameType.DarkSoulsIII or GameType.Sekiro) - { - return GetOverridenFilePath($@"chr\{chrid}.texbnd.dcx"); - } - - if (Type is GameType.EldenRing) - { - // TODO: Maybe add an option down the line to load lower quality - return GetOverridenFilePath($@"chr\{chrid}_h.texbnd.dcx"); - } - - if (Type is GameType.ArmoredCoreVI) - { - return GetOverridenFilePath($@"chr\{chrid}.texbnd.dcx"); - } - - return null; - } - - public AssetDescription GetChrTextures(string chrid) - { - AssetDescription ad = new(); - ad.AssetArchiveVirtualPath = null; - ad.AssetPath = null; - if (Type is GameType.DemonsSouls) - { - var path = GetChrTexturePath(chrid); - if (path != null) - { - ad.AssetPath = path; - ad.AssetVirtualPath = $@"chr/{chrid}/tex"; - } - } - else if (Type is GameType.DarkSoulsPTDE) - { - var path = GetChrTexturePath(chrid); - if (path != null) - { - ad.AssetPath = path; - if (path.EndsWith(".chrbnd")) - { - ad.AssetArchiveVirtualPath = $@"chr/{chrid}/tex"; - } - else - { - ad.AssetVirtualPath = $@"chr/{chrid}/tex"; - } - } - } - else if (Type is GameType.DarkSoulsRemastered) - { - // TODO: Some textures require getting chrtpfbhd from chrbnd, then using it with chrtpfbdt in chr folder. - var path = GetChrTexturePath(chrid); - if (path != null) - { - ad = new AssetDescription(); - ad.AssetPath = path; - ad.AssetVirtualPath = $@"chr/{chrid}/tex"; - } - } - else if (Type is GameType.DarkSoulsIISOTFS) - { - var path = GetChrTexturePath(chrid); - if (path != null) - { - ad = new AssetDescription(); - ad.AssetPath = path; - ad.AssetVirtualPath = $@"chr/{chrid}/tex"; - } - } - else if (Type is GameType.Bloodborne) - { - var path = GetChrTexturePath(chrid); - if (path != null) - { - ad.AssetPath = path; - ad.AssetVirtualPath = $@"chr/{chrid}/tex"; - } - } - else if (Type is GameType.DarkSoulsIII or GameType.Sekiro) - { - var path = GetChrTexturePath(chrid); - if (path != null) - { - ad.AssetPath = path; - ad.AssetArchiveVirtualPath = $@"chr/{chrid}/tex"; - } - } - else if (Type is GameType.EldenRing or GameType.ArmoredCoreVI) - { - var path = GetChrTexturePath(chrid); - if (path != null) - { - ad.AssetPath = path; - ad.AssetArchiveVirtualPath = $@"chr/{chrid}/tex"; - } - } - - return ad; - } - - public AssetDescription GetMapNVMModel(string mapid, string model) - { - AssetDescription ret = new(); - if (Type == GameType.DarkSoulsPTDE || Type == GameType.DarkSoulsRemastered || Type == GameType.DemonsSouls) - { - ret.AssetPath = GetAssetPath($@"map\{mapid}\{model}.nvm"); - ret.AssetName = model; - ret.AssetArchiveVirtualPath = $@"map/{mapid}/nav"; - ret.AssetVirtualPath = $@"map/{mapid}/nav/{model}.nvm"; - } - else - { - return GetNullAsset(); - } - - return ret; - } - - public AssetDescription GetHavokNavmeshes(string mapid) - { - AssetDescription ret = new(); - ret.AssetPath = GetAssetPath($@"map\{mapid}\{mapid}.nvmhktbnd.dcx"); - ret.AssetName = mapid; - ret.AssetArchiveVirtualPath = $@"map/{mapid}/nav"; - return ret; - } - - public AssetDescription GetHavokNavmeshModel(string mapid, string model) - { - AssetDescription ret = new(); - ret.AssetPath = GetAssetPath($@"map\{mapid}\{mapid}.nvmhktbnd.dcx"); - ret.AssetName = model; - ret.AssetArchiveVirtualPath = $@"map/{mapid}/nav"; - ret.AssetVirtualPath = $@"map/{mapid}/nav/{model}.hkx"; - - return ret; - } - - public List GetChrModels() - { - try - { - HashSet chrs = new(); - List ret = new(); - - var modelDir = @"\chr"; - var modelExt = @".chrbnd.dcx"; - if (Type == GameType.DarkSoulsPTDE) - { - modelExt = ".chrbnd"; - } - else if (Type == GameType.DarkSoulsIISOTFS) - { - modelDir = @"\model\chr"; - modelExt = ".bnd"; - } - - if (Type == GameType.DemonsSouls) - { - var chrdirs = Directory.GetDirectories(GameRootDirectory + modelDir); - foreach (var f in chrdirs) - { - var name = Path.GetFileNameWithoutExtension(f + ".dummy"); - if (name.StartsWith("c")) - { - ret.Add(name); - } - } - - return ret; - } - - List chrfiles = Directory.GetFileSystemEntries(GameRootDirectory + modelDir, $@"*{modelExt}") - .ToList(); - foreach (var f in chrfiles) - { - var name = Path.GetFileNameWithoutExtension(Path.GetFileNameWithoutExtension(f)); - ret.Add(name); - chrs.Add(name); - } - - if (GameModDirectory != null && Directory.Exists(GameModDirectory + modelDir)) - { - chrfiles = Directory.GetFileSystemEntries(GameModDirectory + modelDir, $@"*{modelExt}").ToList(); - foreach (var f in chrfiles) - { - var name = Path.GetFileNameWithoutExtension(Path.GetFileNameWithoutExtension(f)); - if (!chrs.Contains(name)) - { - ret.Add(name); - chrs.Add(name); - } - } - } - - return ret; - } - catch (DirectoryNotFoundException e) - { - // Game likely isn't UXM unpacked - return new List(); - } - } - - public AssetDescription GetChrModel(string chr) - { - AssetDescription ret = new(); - ret.AssetName = chr; - ret.AssetArchiveVirtualPath = $@"chr/{chr}/model"; - if (Type == GameType.DarkSoulsIISOTFS) - { - ret.AssetVirtualPath = $@"chr/{chr}/model/{chr}.flv"; - } - else - { - ret.AssetVirtualPath = $@"chr/{chr}/model/{chr}.flver"; - } - - return ret; - } - - public List GetObjModels() - { - try - { - HashSet objs = new(); - List ret = new(); - - var modelDir = @"\obj"; - var modelExt = @".objbnd.dcx"; - if (Type == GameType.DarkSoulsPTDE) - { - modelExt = ".objbnd"; - } - else if (Type == GameType.DarkSoulsIISOTFS) - { - modelDir = @"\model\obj"; - modelExt = ".bnd"; - } - else if (Type == GameType.EldenRing) - { - // AEGs are objs in my heart :( - modelDir = @"\asset\aeg"; - modelExt = ".geombnd.dcx"; - } - else if (Type == GameType.ArmoredCoreVI) - { - // AEGs are objs in my heart :( - modelDir = @"\asset\environment\geometry"; - modelExt = ".geombnd.dcx"; - } - - // Directories to search for obj models - List searchDirs = new(); - if (Type == GameType.EldenRing) - { - searchDirs = Directory.GetFileSystemEntries(GameRootDirectory + modelDir, @"aeg*").ToList(); - } - else - { - searchDirs.Add(GameRootDirectory + modelDir); - } - - foreach (var searchDir in searchDirs) - { - List objfiles = Directory.GetFileSystemEntries(searchDir, $@"*{modelExt}").ToList(); - foreach (var f in objfiles) - { - var name = Path.GetFileNameWithoutExtension(Path.GetFileNameWithoutExtension(f)); - ret.Add(name); - objs.Add(name); - } - - if (GameModDirectory != null && Directory.Exists(searchDir)) - { - objfiles = Directory.GetFileSystemEntries(searchDir, $@"*{modelExt}").ToList(); - foreach (var f in objfiles) - { - var name = Path.GetFileNameWithoutExtension(Path.GetFileNameWithoutExtension(f)); - if (!objs.Contains(name)) - { - ret.Add(name); - objs.Add(name); - } - } - } - } - - return ret; - } - catch (DirectoryNotFoundException e) - { - // Game likely isn't UXM unpacked - return new List(); - } - } - - public AssetDescription GetObjModel(string obj) - { - AssetDescription ret = new(); - ret.AssetName = obj; - ret.AssetArchiveVirtualPath = $@"obj/{obj}/model"; - if (Type == GameType.DarkSoulsIISOTFS) - { - ret.AssetVirtualPath = $@"obj/{obj}/model/{obj}.flv"; - } - else if (Type is GameType.EldenRing or GameType.ArmoredCoreVI) - { - ret.AssetVirtualPath = $@"obj/{obj}/model/{obj.ToUpper()}.flver"; - } - else - { - ret.AssetVirtualPath = $@"obj/{obj}/model/{obj}.flver"; - } - - return ret; - } - - public AssetDescription GetObjTexture(string obj) - { - AssetDescription ad = new(); - ad.AssetPath = null; - ad.AssetArchiveVirtualPath = null; - string path = null; - if (Type == GameType.DarkSoulsPTDE) - { - path = GetOverridenFilePath($@"obj\{obj}.objbnd"); - } - else if (Type is GameType.DemonsSouls or GameType.DarkSoulsRemastered or GameType.Bloodborne - or GameType.DarkSoulsIII or GameType.Sekiro) - { - path = GetOverridenFilePath($@"obj\{obj}.objbnd.dcx"); - } - - if (path != null) - { - ad.AssetPath = path; - ad.AssetArchiveVirtualPath = $@"obj/{obj}/tex"; - } - - return ad; - } - - public AssetDescription GetAetTexture(string aetid) - { - AssetDescription ad = new(); - ad.AssetPath = null; - ad.AssetArchiveVirtualPath = null; - string path; - if (Type == GameType.EldenRing) - { - path = GetOverridenFilePath($@"asset\aet\{aetid.Substring(0, 6)}\{aetid}.tpf.dcx"); - } - else if (Type is GameType.ArmoredCoreVI) - { - path = GetOverridenFilePath($@"\asset\environment\texture\{aetid}.tpf.dcx"); - } - else - { - throw new NotSupportedException(); - } - - if (path != null) - { - ad.AssetPath = path; - ad.AssetArchiveVirtualPath = $@"aet/{aetid}/tex"; - } - - return ad; - } - - public List GetPartsModels() - { - try - { - HashSet parts = new(); - List ret = new(); - - var modelDir = @"\parts"; - var modelExt = @".partsbnd.dcx"; - if (Type == GameType.DarkSoulsPTDE) - { - modelExt = ".partsbnd"; - } - else if (Type == GameType.DarkSoulsIISOTFS) - { - modelDir = @"\model\parts"; - modelExt = ".bnd"; - var partsGatheredFiles = - Directory.GetFiles(GameRootDirectory + modelDir, "*", SearchOption.AllDirectories); - foreach (var f in partsGatheredFiles) - { - if (!f.EndsWith("common.commonbnd.dcx") && !f.EndsWith("common_cloth.commonbnd.dcx") && - !f.EndsWith("facepreset.bnd")) - { - ret.Add(Path.GetFileNameWithoutExtension(f)); - } - } - - return ret; - } - - List partsFiles = Directory.GetFileSystemEntries(GameRootDirectory + modelDir, $@"*{modelExt}") - .ToList(); - foreach (var f in partsFiles) - { - var name = Path.GetFileNameWithoutExtension(Path.GetFileNameWithoutExtension(f)); - ret.Add(name); - parts.Add(name); - } - - if (GameModDirectory != null && Directory.Exists(GameModDirectory + modelDir)) - { - partsFiles = Directory.GetFileSystemEntries(GameModDirectory + modelDir, $@"*{modelExt}").ToList(); - foreach (var f in partsFiles) - { - var name = Path.GetFileNameWithoutExtension(Path.GetFileNameWithoutExtension(f)); - if (!parts.Contains(name)) - { - ret.Add(name); - parts.Add(name); - } - } - } - - return ret; - } - catch (DirectoryNotFoundException e) - { - // Game likely isn't UXM unpacked - return new List(); - } - } - - public AssetDescription GetPartsModel(string part) - { - AssetDescription ret = new(); - ret.AssetName = part; - ret.AssetArchiveVirtualPath = $@"parts/{part}/model"; - if (Type == GameType.DarkSoulsIISOTFS) - { - ret.AssetVirtualPath = $@"parts/{part}/model/{part}.flv"; - } - else if (Type is GameType.DarkSoulsPTDE) - { - ret.AssetVirtualPath = $@"parts/{part}/model/{part.ToUpper()}.flver"; - } - else - { - ret.AssetVirtualPath = $@"parts/{part}/model/{part}.flver"; - } - - return ret; - } - - public AssetDescription GetPartTextures(string partsId) - { - AssetDescription ad = new(); - ad.AssetArchiveVirtualPath = null; - ad.AssetPath = null; - if (Type == GameType.ArmoredCoreVI) - { - string path; - if (partsId.Substring(0, 2) == "wp") - { - string id; - if (partsId.EndsWith("_l")) - { - id = partsId[..^2].Split("_").Last(); - path = GetOverridenFilePath($@"parts\wp_{id}_l.tpf.dcx"); - } - else - { - id = partsId.Split("_").Last(); - path = GetOverridenFilePath($@"parts\wp_{id}.tpf.dcx"); - } - } - else - { - path = GetOverridenFilePath($@"parts\{partsId}_u.tpf.dcx"); - } - - if (path != null) - { - ad.AssetPath = path; - ad.AssetVirtualPath = $@"parts/{partsId}/tex"; - } - } - else if (Type == GameType.EldenRing) - { - // Maybe add an option down the line to load lower quality - var path = GetOverridenFilePath($@"parts\{partsId}.partsbnd.dcx"); - if (path != null) - { - ad.AssetPath = path; - ad.AssetArchiveVirtualPath = $@"parts/{partsId}/tex"; - } - } - else if (Type == GameType.DarkSoulsIII || Type == GameType.Sekiro) - { - var path = GetOverridenFilePath($@"parts\{partsId}.partsbnd.dcx"); - if (path != null) - { - ad.AssetPath = path; - ad.AssetArchiveVirtualPath = $@"parts/{partsId}/tex"; - } - } - else if (Type == GameType.Bloodborne) - { - var path = GetOverridenFilePath($@"parts\{partsId}.partsbnd.dcx"); - if (path != null) - { - ad.AssetPath = path; - ad.AssetVirtualPath = $@"parts/{partsId}/tex"; - } - } - else if (Type == GameType.DarkSoulsPTDE) - { - var path = GetOverridenFilePath($@"parts\{partsId}.partsbnd"); - if (path != null) - { - ad.AssetPath = path; - ad.AssetArchiveVirtualPath = $@"parts/{partsId}/tex"; - } - } - else if (Type == GameType.DemonsSouls) - { - var path = GetOverridenFilePath($@"parts\{partsId}.partsbnd.dcx"); - if (path != null) - { - ad.AssetPath = path; - ad.AssetArchiveVirtualPath = $@"parts/{partsId}/tex"; - } - } - - return ad; - } - - public AssetDescription GetNullAsset() - { - AssetDescription ret = new(); - ret.AssetPath = "null"; - ret.AssetName = "null"; - ret.AssetArchiveVirtualPath = "null"; - ret.AssetVirtualPath = "null"; - return ret; - } - - /// - /// Converts a virtual path to an actual filesystem path. Only resolves virtual paths up to the bnd level, - /// which the remaining string is output for additional handling + /// Converts a virtual path to an actual filesystem path. Only resolves virtual paths up to the bnd level, + /// which the remaining string is output for additional handling /// /// /// - public string VirtualToRealPath(string virtualPath, out string bndpath) - { - var pathElements = virtualPath.Split('/'); - Regex mapRegex = new(@"^m\d{2}_\d{2}_\d{2}_\d{2}$"); - var ret = ""; - - // Parse the virtual path with a DFA and convert it to a game path - var i = 0; - if (pathElements[i].Equals("map")) - { - i++; - if (pathElements[i].Equals("tex")) - { - i++; - if (Type == GameType.DarkSoulsIISOTFS) - { - var mid = pathElements[i]; - i++; - var id = pathElements[i]; - if (id == "tex") - { - bndpath = ""; - return GetAssetPath($@"model\map\t{mid.Substring(1)}.tpfbhd"); - } - } - else if (Type == GameType.DemonsSouls) - { - var mid = pathElements[i]; - i++; - bndpath = ""; - return GetAssetPath($@"map\{mid}\{mid}_{pathElements[i]}.tpf.dcx"); - } - else - { - var mid = pathElements[i]; - i++; - bndpath = ""; - if (pathElements[i] == "env") - { - if (Type == GameType.DarkSoulsRemastered) - { - return GetAssetPath($@"map\{mid}\GI_EnvM_{mid}.tpf.dcx"); - } - - return GetAssetPath($@"map\{mid}\{mid}_envmap.tpf.dcx"); - } - - return GetAssetPath($@"map\{mid}\{mid}_{pathElements[i]}.tpfbhd"); - } - } - else if (mapRegex.IsMatch(pathElements[i])) - { - var mapid = pathElements[i]; - i++; - if (pathElements[i].Equals("model")) - { - i++; - bndpath = ""; - if (Type == GameType.DarkSoulsPTDE) - { - return GetAssetPath($@"map\{mapid}\{pathElements[i]}.flver"); - } - - if (Type == GameType.DarkSoulsRemastered) - { - return GetAssetPath($@"map\{mapid}\{pathElements[i]}.flver.dcx"); - } - - if (Type == GameType.DarkSoulsIISOTFS) - { - return GetAssetPath($@"model\map\{mapid}.mapbhd"); - } - - if (Type == GameType.Bloodborne || Type == GameType.DemonsSouls) - { - return GetAssetPath($@"map\{mapid}\{pathElements[i]}.flver.dcx"); - } - - if (Type == GameType.EldenRing) - { - return GetAssetPath($@"map\{mapid.Substring(0, 3)}\{mapid}\{pathElements[i]}.mapbnd.dcx"); - } - - if (Type == GameType.ArmoredCoreVI) - { - return GetAssetPath($@"map\{mapid.Substring(0, 3)}\{mapid}\{pathElements[i]}.mapbnd.dcx"); - } - - return GetAssetPath($@"map\{mapid}\{pathElements[i]}.mapbnd.dcx"); - } - - if (pathElements[i].Equals("hit")) - { - i++; - var hittype = pathElements[i]; - i++; - if (Type == GameType.DarkSoulsPTDE || Type == GameType.DemonsSouls) - { - bndpath = ""; - return GetAssetPath($@"map\{mapid}\{pathElements[i]}"); - } - - if (Type == GameType.DarkSoulsIISOTFS) - { - bndpath = ""; - return GetAssetPath($@"model\map\h{mapid.Substring(1)}.hkxbhd"); - } - - if (Type == GameType.DarkSoulsIII || Type == GameType.Bloodborne) - { - bndpath = ""; - if (hittype == "lo") - { - return GetAssetPath($@"map\{mapid}\l{mapid.Substring(1)}.hkxbhd"); - } - - return GetAssetPath($@"map\{mapid}\h{mapid.Substring(1)}.hkxbhd"); - } - - bndpath = ""; - return null; - } - - if (pathElements[i].Equals("nav")) - { - i++; - if (Type == GameType.DarkSoulsPTDE || Type == GameType.DemonsSouls || - Type == GameType.DarkSoulsRemastered) - { - if (i < pathElements.Length) - { - bndpath = $@"{pathElements[i]}"; - } - else - { - bndpath = ""; - } - - if (Type == GameType.DarkSoulsRemastered) - { - return GetAssetPath($@"map\{mapid}\{mapid}.nvmbnd.dcx"); - } - - return GetAssetPath($@"map\{mapid}\{mapid}.nvmbnd"); - } - - if (Type == GameType.DarkSoulsIII) - { - bndpath = ""; - return GetAssetPath($@"map\{mapid}\{mapid}.nvmhktbnd.dcx"); - } - - bndpath = ""; - return null; - } - } - } - else if (pathElements[i].Equals("chr")) - { - i++; - var chrid = pathElements[i]; - i++; - if (pathElements[i].Equals("model")) - { - bndpath = ""; - if (Type == GameType.DarkSoulsPTDE) - { - return GetOverridenFilePath($@"chr\{chrid}.chrbnd"); - } - - if (Type == GameType.DarkSoulsIISOTFS) - { - return GetOverridenFilePath($@"model\chr\{chrid}.bnd"); - } - - if (Type == GameType.DemonsSouls) - { - return GetOverridenFilePath($@"chr\{chrid}\{chrid}.chrbnd.dcx"); - } - - return GetOverridenFilePath($@"chr\{chrid}.chrbnd.dcx"); - } - - if (pathElements[i].Equals("tex")) - { - bndpath = ""; - return GetChrTexturePath(chrid); - } - } - else if (pathElements[i].Equals("obj")) - { - i++; - var objid = pathElements[i]; - i++; - if (pathElements[i].Equals("model") || pathElements[i].Equals("tex")) - { - bndpath = ""; - if (Type == GameType.DarkSoulsPTDE) - { - return GetOverridenFilePath($@"obj\{objid}.objbnd"); - } - - if (Type == GameType.DarkSoulsIISOTFS) - { - return GetOverridenFilePath($@"model\obj\{objid}.bnd"); - } - - if (Type == GameType.EldenRing) - { - // Derive subfolder path from model name (all vanilla AEG are within subfolders) - if (objid.Length >= 6) - { - return GetOverridenFilePath($@"asset\aeg\{objid.Substring(0, 6)}\{objid}.geombnd.dcx"); - } - - return null; - } - - if (Type == GameType.ArmoredCoreVI) - { - if (objid.Length >= 6) - { - return GetOverridenFilePath($@"asset\environment\geometry\{objid}.geombnd.dcx"); - } - - return null; - } - - return GetOverridenFilePath($@"obj\{objid}.objbnd.dcx"); - } - } - else if (pathElements[i].Equals("parts")) - { - i++; - var partsId = pathElements[i]; - i++; - if (pathElements[i].Equals("model") || pathElements[i].Equals("tex")) - { - bndpath = ""; - if (Type == GameType.DarkSoulsPTDE || Type == GameType.DarkSoulsRemastered) - { - return GetOverridenFilePath($@"parts\{partsId}.partsbnd"); - } - - if (Type == GameType.DarkSoulsIISOTFS) - { - var partType = ""; - switch (partsId.Substring(0, 2)) - { - case "as": - partType = "accessories"; - break; - case "am": - partType = "arm"; - break; - case "bd": - partType = "body"; - break; - case "fa": - case "fc": - case "fg": - partType = "face"; - break; - case "hd": - partType = "head"; - break; - case "leg": - partType = "leg"; - break; - case "sd": - partType = "shield"; - break; - case "wp": - partType = "weapon"; - break; - } - - return GetOverridenFilePath($@"model\parts\{partType}\{partsId}.bnd"); - } - - if (Type == GameType.EldenRing) - { - return GetOverridenFilePath($@"parts\{partsId}\{partsId}.partsbnd.dcx"); - } - - if (Type == GameType.ArmoredCoreVI && pathElements[i].Equals("tex")) - { - string path; - if (partsId.Substring(0, 2) == "wp") - { - string id; - if (partsId.EndsWith("_l")) - { - id = partsId[..^2].Split("_").Last(); - path = GetOverridenFilePath($@"parts\wp_{id}_l.tpf.dcx"); - } - else - { - id = partsId.Split("_").Last(); - path = GetOverridenFilePath($@"parts\wp_{id}.tpf.dcx"); - } - } - else - { - path = GetOverridenFilePath($@"parts\{partsId}_u.tpf.dcx"); - } - - return path; - } - - return GetOverridenFilePath($@"parts\{partsId}.partsbnd.dcx"); - } - } - - bndpath = virtualPath; - return null; - } - - public string GetBinderVirtualPath(string virtualPathToBinder, string binderFilePath) - { - var filename = Path.GetFileNameWithoutExtension($@"{binderFilePath}.blah"); - if (filename.Length > 0) - { - filename = $@"{virtualPathToBinder}/{filename}"; - } - else - { - filename = virtualPathToBinder; - } - - return filename; - } + public string VirtualToRealPath(string virtualPath, out string bndpath) => Locator.ActiveProject.AssetLocator.VirtualToRealPath(virtualPath, out bndpath); } diff --git a/src/StudioCore/AssetUtils.cs b/src/StudioCore/AssetUtils.cs new file mode 100644 index 000000000..356da967f --- /dev/null +++ b/src/StudioCore/AssetUtils.cs @@ -0,0 +1,216 @@ +using SoulsFormats; +using StudioCore.Editor; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Text.RegularExpressions; + +namespace StudioCore; + +/// +/// Helper functions and statics for Project/Assetlocator +/// +public class AssetUtils +{ + public static readonly string GameExecutableFilter; + public static readonly string ProjectJsonFilter; + public static readonly string RegulationBinFilter; + public static readonly string Data0Filter; + public static readonly string ParamBndDcxFilter; + public static readonly string ParamBndFilter; + public static readonly string EncRegulationFilter; + public static readonly string ParamLooseFilter; + public static readonly string CsvFilter; + public static readonly string TxtFilter; + public static readonly string FmgJsonFilter; + + static AssetUtils() + { + // These patterns are meant to be passed directly into PlatformUtils. + // Everything about their handling should be done there. + + // Game Executable (.EXE, EBOOT.BIN)|*.EXE*;*EBOOT.BIN* + // Windows executable (*.EXE)|*.EXE* + // Playstation executable (*.BIN)|*.BIN* + GameExecutableFilter = "exe,bin"; + // Project file (project.json)|PROJECT.JSON + ProjectJsonFilter = "json"; + // Regulation file (regulation.bin)|REGULATION.BIN + RegulationBinFilter = "bin"; + // Data file (Data0.bdt)|DATA0.BDT + Data0Filter = "bdt"; + // ParamBndDcx (gameparam.parambnd.dcx)|GAMEPARAM.PARAMBND.DCX + ParamBndDcxFilter = "parambnd.dcx"; + // ParamBnd (gameparam.parambnd)|GAMEPARAM.PARAMBND + ParamBndFilter = "parambnd"; + // Enc_RegBndDcx (enc_regulation.bnd.dcx)|ENC_REGULATION.BND.DCX + EncRegulationFilter = "bnd.dcx"; + // Loose Param file (*.Param)|*.Param + ParamLooseFilter = "param"; + // CSV file (*.csv)|*.csv + CsvFilter = "csv"; + // Text file (*.txt)|*.txt + TxtFilter = "txt"; + // Exported FMGs (*.fmg.json)|*.fmg.json + FmgJsonFilter = "fmg.json"; + // All file filter is implicitly added by NFD. Ideally this is used explicitly. + // All files|*.* + } + public static GameType GetGameTypeForExePath(string exePath) + { + var type = GameType.Undefined; + if (exePath.ToLower().Contains("darksouls.exe")) + { + type = GameType.DarkSoulsPTDE; + } + else if (exePath.ToLower().Contains("darksoulsremastered.exe")) + { + type = GameType.DarkSoulsRemastered; + } + else if (exePath.ToLower().Contains("darksoulsii.exe")) + { + type = GameType.DarkSoulsIISOTFS; + } + else if (exePath.ToLower().Contains("darksoulsiii.exe")) + { + type = GameType.DarkSoulsIII; + } + else if (exePath.ToLower().Contains("eboot.bin")) + { + var path = Path.GetDirectoryName(exePath); + if (Directory.Exists($@"{path}\dvdroot_ps4")) + { + type = GameType.Bloodborne; + } + else + { + type = GameType.DemonsSouls; + } + } + else if (exePath.ToLower().Contains("sekiro.exe")) + { + type = GameType.Sekiro; + } + else if (exePath.ToLower().Contains("eldenring.exe")) + { + type = GameType.EldenRing; + } + else if (exePath.ToLower().Contains("armoredcore6.exe")) + { + type = GameType.ArmoredCoreVI; + } + + return type; + } + + public static bool CheckFilesExpanded(string gamepath, GameType game) + { + if (game == GameType.EldenRing) + { + if (!Directory.Exists($@"{gamepath}\map")) + { + return false; + } + + if (!Directory.Exists($@"{gamepath}\asset")) + { + return false; + } + } + + if (game is GameType.DarkSoulsPTDE or GameType.DarkSoulsIII or GameType.Sekiro) + { + if (!Directory.Exists($@"{gamepath}\map")) + { + return false; + } + + if (!Directory.Exists($@"{gamepath}\obj")) + { + return false; + } + } + + if (game == GameType.DarkSoulsIISOTFS) + { + if (!Directory.Exists($@"{gamepath}\map")) + { + return false; + } + + if (!Directory.Exists($@"{gamepath}\model\obj")) + { + return false; + } + } + + if (game == GameType.ArmoredCoreVI) + { + if (!Directory.Exists($@"{gamepath}\map")) + { + return false; + } + + if (!Directory.Exists($@"{gamepath}\asset")) + { + return false; + } + } + + return true; + } + + public static string GetGameIDForDir(GameType Type) + { + switch (Type) + { + case GameType.DemonsSouls: + return "DES"; + case GameType.DarkSoulsPTDE: + return "DS1"; + case GameType.DarkSoulsRemastered: + return "DS1R"; + case GameType.DarkSoulsIISOTFS: + return "DS2S"; + case GameType.Bloodborne: + return "BB"; + case GameType.DarkSoulsIII: + return "DS3"; + case GameType.Sekiro: + return "SDT"; + case GameType.EldenRing: + return "ER"; + case GameType.ArmoredCoreVI: + return "AC6"; + default: + throw new Exception("Game type not set"); + } + } + + public static AssetDescription GetNullAsset() + { + AssetDescription ret = new(); + ret.AssetPath = "null"; + ret.AssetName = "null"; + ret.AssetArchiveVirtualPath = "null"; + ret.AssetVirtualPath = "null"; + return ret; + } + + public static string GetBinderVirtualPath(string virtualPathToBinder, string binderFilePath) + { + var filename = Path.GetFileNameWithoutExtension($@"{binderFilePath}.blah"); + if (filename.Length > 0) + { + filename = $@"{virtualPathToBinder}/{filename}"; + } + else + { + filename = virtualPathToBinder; + } + + return filename; + } +} diff --git a/src/StudioCore/Editor/Action.cs b/src/StudioCore/Editor/Action.cs index f3762df93..466095945 100644 --- a/src/StudioCore/Editor/Action.cs +++ b/src/StudioCore/Editor/Action.cs @@ -309,10 +309,10 @@ public override ActionEvent Undo() public class DuplicateFMGEntryAction : EditorAction { - private readonly FMGBank.EntryGroup EntryGroup; - private FMGBank.EntryGroup NewEntryGroup; + private readonly FMGEntryGroup EntryGroup; + private FMGEntryGroup NewEntryGroup; - public DuplicateFMGEntryAction(FMGBank.EntryGroup entryGroup) + public DuplicateFMGEntryAction(FMGEntryGroup entryGroup) { EntryGroup = entryGroup; } @@ -333,10 +333,10 @@ public override ActionEvent Undo() public class DeleteFMGEntryAction : EditorAction { - private FMGBank.EntryGroup BackupEntryGroup = new(); - private FMGBank.EntryGroup EntryGroup; + private FMGEntryGroup BackupEntryGroup = new(); + private FMGEntryGroup EntryGroup; - public DeleteFMGEntryAction(FMGBank.EntryGroup entryGroup) + public DeleteFMGEntryAction(FMGEntryGroup entryGroup) { EntryGroup = entryGroup; } diff --git a/src/StudioCore/Editor/EditorDecorations.cs b/src/StudioCore/Editor/EditorDecorations.cs index 46c1b3f9a..5c8c06dd1 100644 --- a/src/StudioCore/Editor/EditorDecorations.cs +++ b/src/StudioCore/Editor/EditorDecorations.cs @@ -259,21 +259,21 @@ public static void ParamRefsSelectables(ParamBank bank, List paramRefs return rows; } - private static List<(string, FMGBank.EntryGroup)> resolveFMGRefs(List fmgRefs, Param.Row context, + private static List<(string, FMGEntryGroup)> resolveFMGRefs(List fmgRefs, Param.Row context, dynamic oldval) { - if (!FMGBank.IsLoaded) + if (!Locator.ActiveProject.FMGBank.IsLoaded) { - return new List<(string, FMGBank.EntryGroup)>(); + return new List<(string, FMGEntryGroup)>(); } return fmgRefs.Where(rf => { Param.Cell? c = context?[rf.conditionField]; return context == null || c == null || Convert.ToInt32(c.Value.Value) == rf.conditionValue; - }).Select(rf => FMGBank.FmgInfoBank.Find(x => x.Name == rf.fmg)) + }).Select(rf => Locator.ActiveProject.FMGBank.FmgInfoBank.FirstOrDefault(x => x.Name == rf.fmg)) .Where(fmgi => fmgi != null) - .Select(fmgi => (fmgi.Name, FMGBank.GenerateEntryGroup((int)oldval, fmgi))) + .Select(fmgi => (fmgi.Name, Locator.ActiveProject.FMGBank.GenerateEntryGroup((int)oldval, fmgi))) .ToList(); } @@ -282,11 +282,11 @@ public static void FmgRefSelectable(EditorScreen ownerScreen, List fmgNa { List textsToPrint = UICache.GetCached(ownerScreen, (int)oldval, "PARAM META FMGREF", () => { - List<(string, FMGBank.EntryGroup)> refs = resolveFMGRefs(fmgNames, context, oldval); + List<(string, FMGEntryGroup)> refs = resolveFMGRefs(fmgNames, context, oldval); return refs.Where(x => x.Item2 != null) .Select(x => { - FMGBank.EntryGroup group = x.Item2; + FMGEntryGroup group = x.Item2; var toPrint = ""; if (!string.IsNullOrWhiteSpace(group.Title?.Text)) { @@ -379,7 +379,7 @@ public static void VirtualParamRefSelectables(ParamBank bank, string virtualRefN { List matchedExtRefPath = currentRef.paths.Select(x => string.Format(x, searchValue)).ToList(); - AssetLocator al = ParamBank.PrimaryBank.AssetLocator; + AssetLocator al = Locator.AssetLocator; ExtRefItem(context, fieldName, $"modded {currentRef.name}", matchedExtRefPath, al.GameModDirectory, cacheOwner); ExtRefItem(context, fieldName, $"vanilla {currentRef.name}", matchedExtRefPath, @@ -464,7 +464,7 @@ public static void ParamRefEnumQuickLink(ParamBank bank, object oldval, List reftypes, Param.R ActionManager executor) { // Add Goto statements - List<(string, FMGBank.EntryGroup)> refs = resolveFMGRefs(reftypes, context, oldval); + List<(string, FMGEntryGroup)> refs = resolveFMGRefs(reftypes, context, oldval); var ctrlDown = InputTracker.GetKey(Key.ControlLeft) || InputTracker.GetKey(Key.ControlRight); - foreach ((var name, FMGBank.EntryGroup group) in refs) + foreach ((var name, FMGEntryGroup group) in refs) { if (ImGui.Selectable($@"Goto {name} Text")) { diff --git a/src/StudioCore/Editor/ProjectSettings.cs b/src/StudioCore/Editor/ProjectSettings.cs index c0689984c..495fe6974 100644 --- a/src/StudioCore/Editor/ProjectSettings.cs +++ b/src/StudioCore/Editor/ProjectSettings.cs @@ -1,4 +1,5 @@ -using StudioCore.Platform; +using Silk.NET.Core; +using StudioCore.Platform; using System; using System.Collections.Generic; using System.IO; @@ -39,8 +40,6 @@ public class ProjectSettings /// public bool UseLooseParams { get; set; } = false; - public bool PartialParams { get; set; } = false; - // FMG editor public string LastFmgLanguageUsed { get; set; } = ""; @@ -79,6 +78,24 @@ public static ProjectSettings Deserialize(string path) return null; } } + + internal ProjectSettings CopyAndAssumeFromModDir(string moddir) + { + ProjectSettings newProj = new(); + newProj.ProjectName = Path.GetFileName(Path.GetDirectoryName(moddir)); + newProj.GameRoot = GameRoot; + newProj.GameType = GameType; + switch (newProj.GameType) + { + case GameType.DarkSoulsIISOTFS: + newProj.UseLooseParams = File.Exists($@"{moddir}\Param\AreaParam.param"); + break; + case GameType.DarkSoulsIII: + newProj.UseLooseParams = File.Exists($@"{moddir}\param\gameparam\gameparam_dlc2.parambnd.dcx"); + break; + } + return newProj; + } } public class NewProjectOptions diff --git a/src/StudioCore/Interface/AliasUtils.cs b/src/StudioCore/Interface/AliasUtils.cs index 886038d0c..01906485d 100644 --- a/src/StudioCore/Interface/AliasUtils.cs +++ b/src/StudioCore/Interface/AliasUtils.cs @@ -231,9 +231,9 @@ public static string FindPlayerCharacterName(Entity e, string modelName) var term = c.Value.ToParamEditorString(); var result = term; - if (FMGBank.IsLoaded) + if (Locator.ActiveProject.FMGBank.IsLoaded) { - var matchingFmgInfo = FMGBank.FmgInfoBank.Find(x => x.Name.Contains("Character")); + var matchingFmgInfo = Locator.ActiveProject.FMGBank.FmgInfoBank.First(x => x.Name.Contains("Character")); if (matchingFmgInfo != null) { diff --git a/src/StudioCore/MapStudioNew.cs b/src/StudioCore/MapStudioNew.cs index 113a3cbd6..23ed73aac 100644 --- a/src/StudioCore/MapStudioNew.cs +++ b/src/StudioCore/MapStudioNew.cs @@ -41,8 +41,6 @@ public class MapStudioNew public static bool LowRequirementsMode; - private readonly AssetLocator _assetLocator; - private readonly IGraphicsContext _context; private readonly List _editors; @@ -91,21 +89,20 @@ public unsafe MapStudioNew(IGraphicsContext context, string version) _context.Window.Title = _programTitle; PlatformUtils.InitializeWindows(context.Window.SdlWindowHandle); - _assetLocator = new AssetLocator(); - Locator.AssetLocator = _assetLocator; // Yeah, I'm not passing this as a parameter anymore :P + Locator.AssetLocator = new AssetLocator(); // Banks ModelAliasBank.Bank = new AliasBank(AliasType.Model); MapAliasBank.Bank = new AliasBank(AliasType.Map); - MsbEditorScreen msbEditor = new(_context.Window, _context.Device, _assetLocator); - ModelEditorScreen modelEditor = new(_context.Window, _context.Device, _assetLocator); - ParamEditorScreen paramEditor = new(_context.Window, _context.Device, _assetLocator); - TextEditorScreen textEditor = new(_context.Window, _context.Device, _assetLocator); + MsbEditorScreen msbEditor = new(_context.Window, _context.Device); + ModelEditorScreen modelEditor = new(_context.Window, _context.Device); + ParamEditorScreen paramEditor = new(_context.Window, _context.Device); + TextEditorScreen textEditor = new(_context.Window, _context.Device); _editors = new List { msbEditor, modelEditor, paramEditor, textEditor }; _focusedEditor = msbEditor; - _soapstoneService = new SoapstoneService(_version, _assetLocator, msbEditor); + _soapstoneService = new SoapstoneService(_version, msbEditor); _settingsMenu.MsbEditor = msbEditor; _settingsMenu.ModelEditor = modelEditor; @@ -114,11 +111,6 @@ public unsafe MapStudioNew(IGraphicsContext context, string version) HelpWindow = new HelpWindow(); - ParamBank.PrimaryBank.SetAssetLocator(_assetLocator); - ParamBank.VanillaBank.SetAssetLocator(_assetLocator); - FMGBank.SetAssetLocator(_assetLocator); - MtdBank.LoadMtds(_assetLocator); - ImGui.GetIO()->ConfigFlags |= ImGuiConfigFlags.NavEnableKeyboard; SetupFonts(); _context.ImguiRenderer.OnSetupDone(); @@ -403,7 +395,7 @@ public void CrashShutdown() private void ChangeProjectSettings(ProjectSettings newsettings, string moddir, NewProjectOptions options) { _projectSettings = newsettings; - _assetLocator.SetFromProjectSettings(newsettings, moddir); + Locator.ActiveProject = new Project(newsettings, moddir); _settingsMenu.ProjSettings = _projectSettings; // Banks @@ -412,6 +404,7 @@ private void ChangeProjectSettings(ProjectSettings newsettings, string moddir, N ParamBank.ReloadParams(newsettings, options); MtdBank.ReloadMtds(); + FMGBank.ReloadFMGs(); foreach (EditorScreen editor in _editors) { @@ -477,7 +470,7 @@ public void UnapplyStyle() private void DumpFlverLayouts() { - if (PlatformUtils.Instance.SaveFileDialog("Save Flver layout dump", new[] { AssetLocator.TxtFilter }, + if (PlatformUtils.Instance.SaveFileDialog("Save Flver layout dump", new[] { AssetUtils.TxtFilter }, out var path)) { using (StreamWriter file = new(path)) @@ -528,11 +521,11 @@ private bool AttemptLoadProject(ProjectSettings settings, string filename, NewPr { if (PlatformUtils.Instance.OpenFileDialog( $"Select executable for {settings.GameType}...", - new[] { AssetLocator.GameExecutableFilter }, + new[] { AssetUtils.GameExecutableFilter }, out var path)) { settings.GameRoot = path; - GameType gametype = _assetLocator.GetGameTypeForExePath(settings.GameRoot); + GameType gametype = AssetUtils.GetGameTypeForExePath(settings.GameRoot); if (gametype == settings.GameType) { success = true; @@ -560,7 +553,7 @@ private bool AttemptLoadProject(ProjectSettings settings, string filename, NewPr if (success) { - if (!_assetLocator.CheckFilesExpanded(settings.GameRoot, settings.GameType)) + if (!AssetUtils.CheckFilesExpanded(settings.GameRoot, settings.GameType)) { if (!GameNotUnpackedWarning(settings.GameType)) { @@ -660,12 +653,12 @@ public void AttemptSaveOnCrash() } } - var success = _assetLocator.CreateRecoveryProject(); + var success = Locator.AssetLocator.CreateRecoveryProject(); if (success) { SaveAll(); PlatformUtils.Instance.MessageBox( - $"Attempted to save project files to {_assetLocator.GameModDirectory} for manual recovery.\n" + + $"Attempted to save project files to {Locator.AssetLocator.GameModDirectory} for manual recovery.\n" + "You must manually replace your project files with these recovery files should you wish to restore them.\n" + "Given the program has crashed, these files may be corrupt and you should backup your last good saved\n" + "files before attempting to use these.", @@ -807,7 +800,7 @@ private unsafe void Update(float deltaseconds) { if (PlatformUtils.Instance.OpenFileDialog( "Choose the project json file", - new[] { AssetLocator.ProjectJsonFilter }, + new[] { AssetUtils.ProjectJsonFilter }, out var path)) { ProjectSettings settings = ProjectSettings.Deserialize(path); @@ -872,13 +865,13 @@ private unsafe void Update(float deltaseconds) { if (ImGui.MenuItem("Open Project Folder", "", false, !TaskManager.AnyActiveTasks())) { - var projectPath = _assetLocator.GameModDirectory; + var projectPath = Locator.AssetLocator.GameModDirectory; Process.Start("explorer.exe", projectPath); } if (ImGui.MenuItem("Open Game Folder", "", false, !TaskManager.AnyActiveTasks())) { - var gamePath = _assetLocator.GameRootDirectory; + var gamePath = Locator.AssetLocator.GameRootDirectory; Process.Start("explorer.exe", gamePath); } @@ -939,17 +932,17 @@ private unsafe void Update(float deltaseconds) if (ImGui.MenuItem("MSBE read/write test")) { - MSBReadWrite.Run(_assetLocator); + MSBReadWrite.Run(Locator.AssetLocator); } if (ImGui.MenuItem("MSB_AC6 Read/Write Test")) { - MSB_AC6_Read_Write.Run(_assetLocator); + MSB_AC6_Read_Write.Run(Locator.AssetLocator); } if (ImGui.MenuItem("BTL read/write test")) { - BTLReadWrite.Run(_assetLocator); + BTLReadWrite.Run(Locator.AssetLocator); } if (ImGui.MenuItem("Insert unique rows IDs into params")) @@ -1089,7 +1082,7 @@ private unsafe void Update(float deltaseconds) _newProjectOptions.settings.GameRoot = gname; } - _newProjectOptions.settings.GameType = _assetLocator.GetGameTypeForExePath(gname); + _newProjectOptions.settings.GameType = AssetUtils.GetGameTypeForExePath(gname); if (_newProjectOptions.settings.GameType == GameType.Bloodborne) { @@ -1102,11 +1095,11 @@ private unsafe void Update(float deltaseconds) { if (PlatformUtils.Instance.OpenFileDialog( "Select executable for the game you want to mod...", - new[] { AssetLocator.GameExecutableFilter }, + new[] { AssetUtils.GameExecutableFilter }, out var path)) { _newProjectOptions.settings.GameRoot = Path.GetDirectoryName(path); - _newProjectOptions.settings.GameType = _assetLocator.GetGameTypeForExePath(path); + _newProjectOptions.settings.GameType = AssetUtils.GetGameTypeForExePath(path); if (_newProjectOptions.settings.GameType == GameType.Bloodborne) { @@ -1175,25 +1168,6 @@ private unsafe void Update(float deltaseconds) _newProjectOptions.settings.UseLooseParams = looseparams; } } - else if (FeatureFlags.EnablePartialParam && _newProjectOptions.settings.GameType == GameType.EldenRing) - { - ImGui.NewLine(); - ImGui.AlignTextToFramePadding(); - ImGui.Text(@"Save partial regulation: "); - ImGui.SameLine(); - Utils.ImGuiGenericHelpPopup("TODO (disbababled)", "##Help_PartialParam", - "TODO: why does this setting exist separately from loose params?"); - ImGui.SameLine(); - var partialReg = _newProjectOptions.settings.PartialParams; - if (ImGui.Checkbox("##partialparams", ref partialReg)) - { - _newProjectOptions.settings.PartialParams = partialReg; - } - - ImGui.SameLine(); - ImGui.TextUnformatted( - "Warning: partial params require merging before use in game.\nRow names on unchanged rows will be forgotten between saves"); - } else if (_newProjectOptions.settings.GameType is GameType.ArmoredCoreVI) { //TODO AC6 @@ -1276,7 +1250,7 @@ private unsafe void Update(float deltaseconds) } var gameroot = _newProjectOptions.settings.GameRoot; - if (!_assetLocator.CheckFilesExpanded(gameroot, _newProjectOptions.settings.GameType)) + if (!AssetUtils.CheckFilesExpanded(gameroot, _newProjectOptions.settings.GameType)) { if (!GameNotUnpackedWarning(_newProjectOptions.settings.GameType)) { diff --git a/src/StudioCore/MsbEditor/ModelEditorScreen.cs b/src/StudioCore/MsbEditor/ModelEditorScreen.cs index 3045bd8e5..0b0ee6cdc 100644 --- a/src/StudioCore/MsbEditor/ModelEditorScreen.cs +++ b/src/StudioCore/MsbEditor/ModelEditorScreen.cs @@ -33,8 +33,6 @@ public class ModelEditorScreen : EditorScreen, AssetBrowserEventHandler, SceneTr private Task _loadingTask; private MeshRenderableProxy _renderMesh; - - public AssetLocator AssetLocator; public ActionManager EditorActionManager = new(); public Rectangle Rect; public RenderScene RenderScene; @@ -43,11 +41,9 @@ public class ModelEditorScreen : EditorScreen, AssetBrowserEventHandler, SceneTr private bool ViewportUsingKeyboard; private Sdl2Window Window; - public ModelEditorScreen(Sdl2Window window, GraphicsDevice device, AssetLocator locator) + public ModelEditorScreen(Sdl2Window window, GraphicsDevice device) { Rect = window.Bounds; - AssetLocator = locator; - ResourceManager.Locator = AssetLocator; Window = window; if (device != null) @@ -61,10 +57,10 @@ public ModelEditorScreen(Sdl2Window window, GraphicsDevice device, AssetLocator Viewport = new NullViewport("Modeleditvp", EditorActionManager, _selection, Rect.Width, Rect.Height); } - _universe = new Universe(AssetLocator, RenderScene, _selection); + _universe = new Universe(RenderScene, _selection); _sceneTree = new SceneTree(SceneTree.Configuration.ModelEditor, this, "modeledittree", _universe, - _selection, EditorActionManager, Viewport, AssetLocator); + _selection, EditorActionManager, Viewport); _propEditor = new PropertyEditor(EditorActionManager, _propCache); ModelAssetBrowser = new AssetBrowserScreen(AssetBrowserSource.ModelEditor, _universe, RenderScene, _selection, EditorActionManager, this, Viewport); @@ -240,7 +236,7 @@ public bool InputCaptured() public void OnProjectChanged(ProjectSettings newSettings) { - if (AssetLocator.Type != GameType.Undefined) + if (Locator.AssetLocator.Type != GameType.Undefined) { ModelAssetBrowser.OnProjectChanged(); } @@ -306,25 +302,25 @@ public void LoadModel(string modelid, ModelEditorModelType modelType, string map switch (modelType) { case ModelEditorModelType.Character: - asset = AssetLocator.GetChrModel(modelid); - assettex = AssetLocator.GetChrTextures(modelid); + asset = Locator.AssetLocator.GetChrModel(modelid); + assettex = Locator.AssetLocator.GetChrTextures(modelid); break; case ModelEditorModelType.Object: - asset = AssetLocator.GetObjModel(modelid); - assettex = AssetLocator.GetObjTexture(modelid); + asset = Locator.AssetLocator.GetObjModel(modelid); + assettex = Locator.AssetLocator.GetObjTexture(modelid); break; case ModelEditorModelType.Parts: - asset = AssetLocator.GetPartsModel(modelid); - assettex = AssetLocator.GetPartTextures(modelid); + asset = Locator.AssetLocator.GetPartsModel(modelid); + assettex = Locator.AssetLocator.GetPartTextures(modelid); break; case ModelEditorModelType.MapPiece: - asset = AssetLocator.GetMapModel(mapid, modelid); - assettex = AssetLocator.GetNullAsset(); + asset = Locator.AssetLocator.GetMapModel(mapid, modelid); + assettex = AssetUtils.GetNullAsset(); break; default: //Uh oh - asset = AssetLocator.GetNullAsset(); - assettex = AssetLocator.GetNullAsset(); + asset = AssetUtils.GetNullAsset(); + assettex = AssetUtils.GetNullAsset(); break; } diff --git a/src/StudioCore/MsbEditor/MsbEditorScreen.cs b/src/StudioCore/MsbEditor/MsbEditorScreen.cs index ac2a435a6..d9d6f6150 100644 --- a/src/StudioCore/MsbEditor/MsbEditorScreen.cs +++ b/src/StudioCore/MsbEditor/MsbEditorScreen.cs @@ -28,8 +28,6 @@ public class MsbEditorScreen : EditorScreen, SceneTreeEventHandler private static readonly object _lock_PauseUpdate = new(); public Selection _selection = new Selection(); - public readonly AssetLocator AssetLocator; - private IModal _activeModal; private int _createEntityMapIndex; @@ -73,11 +71,9 @@ public class MsbEditorScreen : EditorScreen, SceneTreeEventHandler private Sdl2Window Window; - public MsbEditorScreen(Sdl2Window window, GraphicsDevice device, AssetLocator locator) + public MsbEditorScreen(Sdl2Window window, GraphicsDevice device) { Rect = window.Bounds; - AssetLocator = locator; - ResourceManager.Locator = AssetLocator; Window = window; if (device != null) @@ -92,14 +88,14 @@ public MsbEditorScreen(Sdl2Window window, GraphicsDevice device, AssetLocator lo Viewport = new NullViewport("Mapeditvp", EditorActionManager, _selection, Rect.Width, Rect.Height); } - Universe = new Universe(AssetLocator, RenderScene, _selection); + Universe = new Universe(RenderScene, _selection); SceneTree = new SceneTree(SceneTree.Configuration.MapEditor, this, "mapedittree", Universe, _selection, - EditorActionManager, Viewport, AssetLocator); + EditorActionManager, Viewport); PropEditor = new PropertyEditor(EditorActionManager, _propCache); DispGroupEditor = new DisplayGroupsEditor(RenderScene, _selection, EditorActionManager); PropSearch = new SearchProperties(Universe, _propCache); - NavMeshEditor = new NavmeshEditor(locator, RenderScene, _selection); + NavMeshEditor = new NavmeshEditor(RenderScene, _selection); MapAssetBrowser = new AssetBrowserScreen(AssetBrowserSource.MapEditor, Universe, RenderScene, _selection, EditorActionManager, this, Viewport); EditorActionManager.AddEventHandler(SceneTree); @@ -529,7 +525,7 @@ public void DrawEditorMenu() if (ImGui.BeginMenu("Create")) { - if (AssetLocator.Type is GameType.EldenRing) + if (Locator.ActiveProject.Type is GameType.EldenRing) { if (ImGui.Selectable("New Map")) { @@ -641,7 +637,7 @@ public void DrawEditorMenu() ImGui.EndMenu(); } - if (AssetLocator.Type is GameType.EldenRing) + if (Locator.ActiveProject.AssetLocator.Type is GameType.EldenRing) { if (ImGui.BeginMenu("Asset Prefabs")) { @@ -858,7 +854,7 @@ public void DrawEditorMenu() { var loadedMaps = Universe.LoadedObjectContainers.Values.Where(x => x != null); - if (AssetLocator.Type is not GameType.DarkSoulsIISOTFS) + if (Locator.AssetLocator.Type is not GameType.DarkSoulsIISOTFS) { if (ImGui.BeginMenu("Render enemy patrol routes")) { @@ -910,7 +906,7 @@ public void DrawEditorMenu() } } - if (AssetLocator.Type is GameType.DemonsSouls or + if (Locator.AssetLocator.Type is GameType.DemonsSouls or GameType.DarkSoulsPTDE or GameType.DarkSoulsRemastered) { if (ImGui.BeginMenu("Regenerate MCP and MCG")) @@ -1090,7 +1086,7 @@ public unsafe void OnGUI(string[] initcmd) PatrolDrawManager.Generate(Universe); } - if (AssetLocator.Type is GameType.EldenRing) + if (Locator.ActiveProject != null && Locator.ActiveProject.AssetLocator.Type is GameType.EldenRing) { if (InputTracker.GetKeyDown(KeyBindings.Current.Map_AssetPrefabExport)) { @@ -1253,7 +1249,7 @@ public unsafe void OnGUI(string[] initcmd) // Not usable yet if (FeatureFlags.EnableNavmeshBuilder) { - NavMeshEditor.OnGui(AssetLocator.Type); + NavMeshEditor.OnGui(Locator.AssetLocator.Type); } ResourceManager.OnGuiDrawTasks(Viewport.Width, Viewport.Height); @@ -1396,7 +1392,7 @@ public void SetObjectModelForSelection(string modelName, string assetType, strin if (assetType == "Chr") { - switch (AssetLocator.Type) + switch (Locator.AssetLocator.Type) { case GameType.DemonsSouls: if (s.WrappedObject is MSBD.Part.Enemy) @@ -1435,7 +1431,7 @@ public void SetObjectModelForSelection(string modelName, string assetType, strin } if (assetType == "Obj") { - switch (AssetLocator.Type) + switch (Locator.AssetLocator.Type) { case GameType.DemonsSouls: if (s.WrappedObject is MSBD.Part.Object) @@ -1476,7 +1472,7 @@ public void SetObjectModelForSelection(string modelName, string assetType, strin } if (assetType == "MapPiece") { - switch (AssetLocator.Type) + switch (Locator.AssetLocator.Type) { case GameType.DemonsSouls: if (s.WrappedObject is MSBD.Part.MapPiece) @@ -1838,7 +1834,7 @@ private void UnDummySelection() private void DummyUndummySelection(string[] sourceTypes, string[] targetTypes) { Type msbclass; - switch (AssetLocator.Type) + switch (Locator.AssetLocator.Type) { case GameType.DemonsSouls: msbclass = typeof(MSBD); @@ -2011,9 +2007,9 @@ public void ReloadUniverse() GC.Collect(); Universe.PopulateMapList(); - if (AssetLocator.Type != GameType.Undefined) + if (Locator.AssetLocator.Type != GameType.Undefined) { - PopulateClassNames(AssetLocator.Type); + PopulateClassNames(Locator.AssetLocator.Type); } } @@ -2064,7 +2060,7 @@ private void GenerateMCGMCP(Dictionary orderedMaps) foreach (var map in orderedMaps) { string mapid = map.Key; - if (AssetLocator.Type is GameType.DemonsSouls) + if (Locator.AssetLocator.Type is GameType.DemonsSouls) { if (mapid != "m03_01_00_99" && !mapid.StartsWith("m99")) { @@ -2080,10 +2076,10 @@ private void GenerateMCGMCP(Dictionary orderedMaps) { if (orderMap.Key.StartsWith(areaId) && orderMap.Key != "m03_01_00_99") { - areaDirectories.Add(Path.Combine(AssetLocator.GameRootDirectory, "map", orderMap.Key)); + areaDirectories.Add(Path.Combine(Locator.AssetLocator.GameRootDirectory, "map", orderMap.Key)); } } - SoulsMapMetadataGenerator.GenerateMCGMCP(areaDirectories, AssetLocator, toBigEndian: true); + SoulsMapMetadataGenerator.GenerateMCGMCP(areaDirectories, Locator.AssetLocator, toBigEndian: true); } } else @@ -2092,21 +2088,21 @@ private void GenerateMCGMCP(Dictionary orderedMaps) { List areaDirectories = new List { - Path.Combine(AssetLocator.GameRootDirectory, "map", mapid) + Path.Combine(Locator.AssetLocator.GameRootDirectory, "map", mapid) }; - SoulsMapMetadataGenerator.GenerateMCGMCP(areaDirectories, AssetLocator, toBigEndian: true); + SoulsMapMetadataGenerator.GenerateMCGMCP(areaDirectories, Locator.AssetLocator, toBigEndian: true); } } } - else if (AssetLocator.Type is GameType.DarkSoulsPTDE or GameType.DarkSoulsRemastered) + else if (Locator.AssetLocator.Type is GameType.DarkSoulsPTDE or GameType.DarkSoulsRemastered) { if (ImGui.Selectable($"{mapid}")) { List areaDirectories = new List { - Path.Combine(AssetLocator.GameRootDirectory, "map", mapid) + Path.Combine(Locator.AssetLocator.GameRootDirectory, "map", mapid) }; - SoulsMapMetadataGenerator.GenerateMCGMCP(areaDirectories, AssetLocator, toBigEndian: false); + SoulsMapMetadataGenerator.GenerateMCGMCP(areaDirectories, Locator.AssetLocator, toBigEndian: false); } } } diff --git a/src/StudioCore/MsbEditor/MtdBank.cs b/src/StudioCore/MsbEditor/MtdBank.cs index b4a737baf..91d7e3c94 100644 --- a/src/StudioCore/MsbEditor/MtdBank.cs +++ b/src/StudioCore/MsbEditor/MtdBank.cs @@ -8,8 +8,6 @@ namespace StudioCore.MsbEditor; public class MtdBank { - private static AssetLocator AssetLocator; - private static Dictionary _mtds = new(); private static Dictionary _matbins = new(); @@ -27,14 +25,14 @@ public static void ReloadMtds() try { IBinder mtdBinder = null; - if (AssetLocator.Type == GameType.DarkSoulsIII || AssetLocator.Type == GameType.Sekiro) + if (Locator.AssetLocator.Type == GameType.DarkSoulsIII || Locator.AssetLocator.Type == GameType.Sekiro) { - mtdBinder = BND4.Read(AssetLocator.GetAssetPath(@"mtd\allmaterialbnd.mtdbnd.dcx")); + mtdBinder = BND4.Read(Locator.AssetLocator.GetAssetPath(@"mtd\allmaterialbnd.mtdbnd.dcx")); IsMatbin = false; } - else if (AssetLocator.Type is GameType.EldenRing or GameType.ArmoredCoreVI) + else if (Locator.AssetLocator.Type is GameType.EldenRing or GameType.ArmoredCoreVI) { - mtdBinder = BND4.Read(AssetLocator.GetAssetPath(@"material\allmaterial.matbinbnd.dcx")); + mtdBinder = BND4.Read(Locator.AssetLocator.GetAssetPath(@"material\allmaterial.matbinbnd.dcx")); IsMatbin = true; } @@ -80,16 +78,4 @@ public static void ReloadMtds() } })); } - - public static void LoadMtds(AssetLocator l) - { - AssetLocator = l; - - if (AssetLocator.Type == GameType.Undefined) - { - return; - } - - ReloadMtds(); - } } diff --git a/src/StudioCore/MsbEditor/NavmeshEditor.cs b/src/StudioCore/MsbEditor/NavmeshEditor.cs index fbc2c33d6..f70485fef 100644 --- a/src/StudioCore/MsbEditor/NavmeshEditor.cs +++ b/src/StudioCore/MsbEditor/NavmeshEditor.cs @@ -15,7 +15,6 @@ namespace StudioCore.MsbEditor; /// public class NavmeshEditor { - private readonly AssetLocator _locator; private readonly MeshRenderableProxy _previewMesh = null; private readonly Selection _selection; private readonly int icount = 0; @@ -34,9 +33,8 @@ public class NavmeshEditor private int MinRegionArea = 3; private float SlopeAngle = 30.0f; - public NavmeshEditor(AssetLocator locator, RenderScene scene, Selection sel) + public NavmeshEditor(RenderScene scene, Selection sel) { - _locator = locator; _scene = scene; _selection = sel; } @@ -121,7 +119,7 @@ public unsafe void OnGui(GameType game) _previewMesh.World = mrp.World; // Do a test save - var path = $@"{_locator.GameModDirectory}\navout\test.hkx"; + var path = $@"{Locator.AssetLocator.GameModDirectory}\navout\test.hkx"; using (FileStream s2 = File.Create(path)) { BinaryWriterEx bw = new(false, s2); diff --git a/src/StudioCore/MsbEditor/SceneTree.cs b/src/StudioCore/MsbEditor/SceneTree.cs index 36db75535..04dae0f4c 100644 --- a/src/StudioCore/MsbEditor/SceneTree.cs +++ b/src/StudioCore/MsbEditor/SceneTree.cs @@ -47,9 +47,6 @@ public enum ViewMode Flat, ObjectType } - - private readonly AssetLocator _assetLocator; - private readonly Configuration _configuration; private readonly List _dragDropDestObjects = new(); private readonly List _dragDropDests = new(); @@ -94,7 +91,7 @@ private ulong private ViewMode _viewMode = ViewMode.ObjectType; public SceneTree(Configuration configuration, SceneTreeEventHandler handler, string id, Universe universe, - Selection sel, ActionManager aman, IViewport vp, AssetLocator al) + Selection sel, ActionManager aman, IViewport vp) { _handler = handler; _id = id; @@ -102,7 +99,6 @@ public SceneTree(Configuration configuration, SceneTreeEventHandler handler, str _selection = sel; _editorActionManager = aman; _viewport = vp; - _assetLocator = al; _configuration = configuration; if (_configuration == Configuration.ModelEditor) @@ -131,12 +127,12 @@ private void RebuildTypeViewCache(Map map) mapcache.Add(MapEntity.MapEntityType.Part, new Dictionary>()); mapcache.Add(MapEntity.MapEntityType.Region, new Dictionary>()); mapcache.Add(MapEntity.MapEntityType.Event, new Dictionary>()); - if (_assetLocator.Type is GameType.Bloodborne or GameType.DarkSoulsIII or GameType.Sekiro + if (Locator.AssetLocator.Type is GameType.Bloodborne or GameType.DarkSoulsIII or GameType.Sekiro or GameType.EldenRing or GameType.ArmoredCoreVI) { mapcache.Add(MapEntity.MapEntityType.Light, new Dictionary>()); } - else if (_assetLocator.Type is GameType.DarkSoulsIISOTFS) + else if (Locator.AssetLocator.Type is GameType.DarkSoulsIISOTFS) { mapcache.Add(MapEntity.MapEntityType.Light, new Dictionary>()); mapcache.Add(MapEntity.MapEntityType.DS2Event, new Dictionary>()); @@ -487,7 +483,7 @@ private void TypeView(Map map) { // Regions don't have multiple types in certain games if (cats.Key == MapEntity.MapEntityType.Region && - _assetLocator.Type is GameType.DemonsSouls + Locator.AssetLocator.Type is GameType.DemonsSouls or GameType.DarkSoulsPTDE or GameType.DarkSoulsRemastered or GameType.Bloodborne) @@ -617,7 +613,7 @@ public unsafe void OnGui() if (_configuration == Configuration.MapEditor) { - if (_assetLocator.Type is GameType.DarkSoulsIISOTFS) + if (Locator.AssetLocator.Type is GameType.DarkSoulsIISOTFS) { if (ParamBank.PrimaryBank.IsLoadingParams) { @@ -905,7 +901,7 @@ public unsafe void OnGui() } } - if (/*_assetLocator.Type == GameType.Bloodborne &&*/ _configuration == Configuration.MapEditor) + if (Locator.AssetLocator.Type == GameType.Bloodborne && _configuration == Configuration.MapEditor) { ChaliceDungeonImportButton(); } diff --git a/src/StudioCore/MsbEditor/Universe.cs b/src/StudioCore/MsbEditor/Universe.cs index 4f6af99d6..23a060c56 100644 --- a/src/StudioCore/MsbEditor/Universe.cs +++ b/src/StudioCore/MsbEditor/Universe.cs @@ -30,7 +30,6 @@ internal partial class BtlLightSerializerContext : JsonSerializerContext /// public class Universe { - private readonly AssetLocator _assetLocator; private readonly RenderScene _renderScene; public int _dispGroupCount = 8; @@ -38,9 +37,8 @@ public class Universe public bool postLoad; - public Universe(AssetLocator al, RenderScene scene, Selection sel) + public Universe(RenderScene scene, Selection sel) { - _assetLocator = al; _renderScene = scene; Selection = sel; } @@ -50,7 +48,7 @@ public Universe(AssetLocator al, RenderScene scene, Selection sel) public List EnvMapTextures { get; private set; } = new(); - public GameType GameType => _assetLocator.Type; + public GameType GameType => Locator.AssetLocator.Type; public Map GetLoadedMap(string id) { @@ -316,43 +314,43 @@ public RenderableProxy GetModelDrawable(Map map, Entity obj, string modelname, b var loadflver = false; var filt = RenderFilter.All; - var amapid = _assetLocator.GetAssetMapID(map.Name); + var amapid = Locator.AssetLocator.GetAssetMapID(map.Name); ResourceManager.ResourceJobBuilder job = ResourceManager.CreateNewJob(@"Loading mesh"); if (modelname.ToLower().StartsWith("m")) { loadflver = true; - asset = _assetLocator.GetMapModel(amapid, _assetLocator.MapModelNameToAssetName(amapid, modelname)); + asset = Locator.AssetLocator.GetMapModel(amapid, Locator.AssetLocator.MapModelNameToAssetName(amapid, modelname)); filt = RenderFilter.MapPiece; } else if (modelname.ToLower().StartsWith("c")) { loadflver = true; - asset = _assetLocator.GetChrModel(modelname); + asset = Locator.AssetLocator.GetChrModel(modelname); filt = RenderFilter.Character; } else if (modelname.ToLower().StartsWith("o") || modelname.StartsWith("AEG")) { loadflver = true; - asset = _assetLocator.GetObjModel(modelname); + asset = Locator.AssetLocator.GetObjModel(modelname); filt = RenderFilter.Object; } else if (modelname.ToLower().StartsWith("h")) { loadcol = true; - asset = _assetLocator.GetMapCollisionModel(amapid, - _assetLocator.MapModelNameToAssetName(amapid, modelname), false); + asset = Locator.AssetLocator.GetMapCollisionModel(amapid, + Locator.AssetLocator.MapModelNameToAssetName(amapid, modelname), false); filt = RenderFilter.Collision; } else if (modelname.ToLower().StartsWith("n")) { loadnav = true; - asset = _assetLocator.GetMapNVMModel(amapid, _assetLocator.MapModelNameToAssetName(amapid, modelname)); + asset = Locator.AssetLocator.GetMapNVMModel(amapid, Locator.AssetLocator.MapModelNameToAssetName(amapid, modelname)); filt = RenderFilter.Navmesh; } else { - asset = _assetLocator.GetNullAsset(); + asset = AssetUtils.GetNullAsset(); } ModelMarkerType modelMarkerType = @@ -389,7 +387,7 @@ public RenderableProxy GetModelDrawable(Map map, Entity obj, string modelname, b return mesh; } - if (loadnav && _assetLocator.Type != GameType.DarkSoulsIISOTFS) + if (loadnav && Locator.AssetLocator.Type != GameType.DarkSoulsIISOTFS) { MeshRenderableProxy mesh = MeshRenderableProxy.MeshRenderableFromNVMResource( _renderScene, asset.AssetVirtualPath, modelMarkerType); @@ -420,7 +418,7 @@ public RenderableProxy GetModelDrawable(Map map, Entity obj, string modelname, b return mesh; } - else if (loadnav && _assetLocator.Type == GameType.DarkSoulsIISOTFS) + else if (loadnav && Locator.AssetLocator.Type == GameType.DarkSoulsIISOTFS) { } else if (loadflver) @@ -531,14 +529,14 @@ public void LoadDS2Generators(string mapid, Map map) (int)regist.GetCellHandleOrThrow("EnemyParamID").Value); if (chrid != null) { - AssetDescription asset = _assetLocator.GetChrModel($@"c{chrid}"); + AssetDescription asset = Locator.AssetLocator.GetChrModel($@"c{chrid}"); MeshRenderableProxy model = MeshRenderableProxy.MeshRenderableFromFlverResource( _renderScene, asset.AssetVirtualPath, ModelMarkerType.Enemy); model.DrawFilter = RenderFilter.Character; generatorObjs[row.ID].RenderSceneMesh = model; model.SetSelectable(generatorObjs[row.ID]); chrsToLoad.Add(asset); - AssetDescription tasset = _assetLocator.GetChrTextures($@"c{chrid}"); + AssetDescription tasset = Locator.AssetLocator.GetChrTextures($@"c{chrid}"); if (tasset.AssetVirtualPath != null || tasset.AssetArchiveVirtualPath != null) { chrsToLoad.Add(tasset); @@ -617,7 +615,7 @@ public void LoadDS2Generators(string mapid, Map map) public void PopulateMapList() { LoadedObjectContainers.Clear(); - foreach (var m in _assetLocator.GetFullMapList()) + foreach (var m in Locator.AssetLocator.GetFullMapList()) { LoadedObjectContainers.Add(m, null); } @@ -635,7 +633,7 @@ public void LoadRelatedMapsER(string mapid, Dictionary public bool LoadMap(string mapid, bool selectOnLoad = false) { - if (_assetLocator.Type == GameType.DarkSoulsIISOTFS + if (Locator.AssetLocator.Type == GameType.DarkSoulsIISOTFS && ParamBank.PrimaryBank.Params == null) { // ParamBank must be loaded for DS2 maps @@ -644,7 +642,7 @@ public bool LoadMap(string mapid, bool selectOnLoad = false) return false; } - AssetDescription ad = _assetLocator.GetMapMSB(mapid); + AssetDescription ad = Locator.AssetLocator.GetMapMSB(mapid); if (ad.AssetPath == null) { return false; @@ -660,7 +658,7 @@ public BTL ReturnBTL(AssetDescription ad) { BTL btl; - if (_assetLocator.Type == GameType.DarkSoulsIISOTFS) + if (Locator.AssetLocator.Type == GameType.DarkSoulsIISOTFS) { using BXF4 bdt = BXF4.Read(ad.AssetPath, ad.AssetPath[..^3] + "bdt"); BinderFile file = bdt.Files.Find(f => f.Name.EndsWith("light.btl.dcx")); @@ -710,7 +708,7 @@ public async void LoadMapAsync(string mapid, bool selectOnLoad = false) HashSet navsToLoad = new(); //drawgroup count - switch (_assetLocator.Type) + switch (Locator.AssetLocator.Type) { // imgui checkbox click seems to break at some point after 8 (8*32) checkboxes, so let's just hope that never happens, yeah? case GameType.DemonsSouls: @@ -729,42 +727,42 @@ public async void LoadMapAsync(string mapid, bool selectOnLoad = false) _dispGroupCount = 8; //? break; default: - throw new Exception($"Error: Did not expect Gametype {_assetLocator.Type}"); + throw new Exception($"Error: Did not expect Gametype {Locator.AssetLocator.Type}"); //break; } - AssetDescription ad = _assetLocator.GetMapMSB(mapid); + AssetDescription ad = Locator.AssetLocator.GetMapMSB(mapid); if (ad.AssetPath == null) { return; } IMsb msb; - if (_assetLocator.Type == GameType.DarkSoulsIII) + if (Locator.AssetLocator.Type == GameType.DarkSoulsIII) { msb = MSB3.Read(ad.AssetPath); } - else if (_assetLocator.Type == GameType.Sekiro) + else if (Locator.AssetLocator.Type == GameType.Sekiro) { msb = MSBS.Read(ad.AssetPath); } - else if (_assetLocator.Type == GameType.EldenRing) + else if (Locator.AssetLocator.Type == GameType.EldenRing) { msb = MSBE.Read(ad.AssetPath); } - else if (_assetLocator.Type == GameType.ArmoredCoreVI) + else if (Locator.AssetLocator.Type == GameType.ArmoredCoreVI) { msb = MSB_AC6.Read(ad.AssetPath); } - else if (_assetLocator.Type == GameType.DarkSoulsIISOTFS) + else if (Locator.AssetLocator.Type == GameType.DarkSoulsIISOTFS) { msb = MSB2.Read(ad.AssetPath); } - else if (_assetLocator.Type == GameType.Bloodborne) + else if (Locator.AssetLocator.Type == GameType.Bloodborne) { msb = MSBB.Read(ad.AssetPath); } - else if (_assetLocator.Type == GameType.DemonsSouls) + else if (Locator.AssetLocator.Type == GameType.DemonsSouls) { msb = MSBD.Read(ad.AssetPath); } @@ -775,21 +773,21 @@ public async void LoadMapAsync(string mapid, bool selectOnLoad = false) map.LoadMSB(msb); - var amapid = _assetLocator.GetAssetMapID(mapid); + var amapid = Locator.AssetLocator.GetAssetMapID(mapid); foreach (IMsbModel model in msb.Models.GetEntries()) { AssetDescription asset; if (model.Name.StartsWith("m")) { - asset = _assetLocator.GetMapModel(amapid, - _assetLocator.MapModelNameToAssetName(amapid, model.Name)); + asset = Locator.AssetLocator.GetMapModel(amapid, + Locator.AssetLocator.MapModelNameToAssetName(amapid, model.Name)); mappiecesToLoad.Add(asset); } else if (model.Name.StartsWith("c")) { - asset = _assetLocator.GetChrModel(model.Name); + asset = Locator.AssetLocator.GetChrModel(model.Name); chrsToLoad.Add(asset); - AssetDescription tasset = _assetLocator.GetChrTextures(model.Name); + AssetDescription tasset = Locator.AssetLocator.GetChrTextures(model.Name); if (tasset.AssetVirtualPath != null || tasset.AssetArchiveVirtualPath != null) { chrsToLoad.Add(tasset); @@ -797,9 +795,9 @@ public async void LoadMapAsync(string mapid, bool selectOnLoad = false) } else if (model.Name.StartsWith("o")) { - asset = _assetLocator.GetObjModel(model.Name); + asset = Locator.AssetLocator.GetObjModel(model.Name); objsToLoad.Add(asset); - AssetDescription tasset = _assetLocator.GetObjTexture(model.Name); + AssetDescription tasset = Locator.AssetLocator.GetObjTexture(model.Name); if (tasset.AssetVirtualPath != null || tasset.AssetArchiveVirtualPath != null) { objsToLoad.Add(tasset); @@ -807,20 +805,20 @@ public async void LoadMapAsync(string mapid, bool selectOnLoad = false) } else if (model.Name.StartsWith("AEG")) { - asset = _assetLocator.GetObjModel(model.Name); + asset = Locator.AssetLocator.GetObjModel(model.Name); objsToLoad.Add(asset); } else if (model.Name.StartsWith("h")) { - asset = _assetLocator.GetMapCollisionModel(amapid, - _assetLocator.MapModelNameToAssetName(amapid, model.Name), false); + asset = Locator.AssetLocator.GetMapCollisionModel(amapid, + Locator.AssetLocator.MapModelNameToAssetName(amapid, model.Name), false); colsToLoad.Add(asset); } - else if (model.Name.StartsWith("n") && _assetLocator.Type != GameType.DarkSoulsIISOTFS && - _assetLocator.Type != GameType.Bloodborne) + else if (model.Name.StartsWith("n") && Locator.AssetLocator.Type != GameType.DarkSoulsIISOTFS && + Locator.AssetLocator.Type != GameType.Bloodborne) { - asset = _assetLocator.GetMapNVMModel(amapid, - _assetLocator.MapModelNameToAssetName(amapid, model.Name)); + asset = Locator.AssetLocator.GetMapNVMModel(amapid, + Locator.AssetLocator.MapModelNameToAssetName(amapid, model.Name)); navsToLoad.Add(asset); } } @@ -835,7 +833,7 @@ public async void LoadMapAsync(string mapid, bool selectOnLoad = false) } // Load BTLs (must be done after MapOffset is set) - List BTLs = _assetLocator.GetMapBTLs(mapid); + List BTLs = Locator.AssetLocator.GetMapBTLs(mapid); foreach (AssetDescription btl_ad in BTLs) { BTL btl = ReturnBTL(btl_ad); @@ -845,7 +843,7 @@ public async void LoadMapAsync(string mapid, bool selectOnLoad = false) } } - if (_assetLocator.Type == GameType.EldenRing && CFG.Current.EnableEldenRingAutoMapOffset) + if (Locator.AssetLocator.Type == GameType.EldenRing && CFG.Current.EnableEldenRingAutoMapOffset) { if (SpecialMapConnections.GetEldenMapTransform(mapid, LoadedObjectContainers) is Transform loadTransform) @@ -871,15 +869,15 @@ public async void LoadMapAsync(string mapid, bool selectOnLoad = false) Selection.AddSelection(map.RootObject); } - if (_assetLocator.Type == GameType.DarkSoulsIISOTFS) + if (Locator.AssetLocator.Type == GameType.DarkSoulsIISOTFS) { LoadDS2Generators(amapid, map); } // Temporary DS3 navmesh loading - if (FeatureFlags.LoadDS3Navmeshes && _assetLocator.Type == GameType.DarkSoulsIII) + if (FeatureFlags.LoadDS3Navmeshes && Locator.AssetLocator.Type == GameType.DarkSoulsIII) { - AssetDescription nvaasset = _assetLocator.GetMapNVA(amapid); + AssetDescription nvaasset = Locator.AssetLocator.GetMapNVA(amapid); if (nvaasset.AssetPath != null) { NVA nva = NVA.Read(nvaasset.AssetPath); @@ -889,8 +887,8 @@ public async void LoadMapAsync(string mapid, bool selectOnLoad = false) MapEntity n = new(map, nav, MapEntity.MapEntityType.Editor); map.AddObject(n); var navid = $@"n{nav.ModelID:D6}"; - var navname = "n" + _assetLocator.MapModelNameToAssetName(amapid, navid).Substring(1); - AssetDescription nasset = _assetLocator.GetHavokNavmeshModel(amapid, navname); + var navname = "n" + Locator.AssetLocator.MapModelNameToAssetName(amapid, navid).Substring(1); + AssetDescription nasset = Locator.AssetLocator.GetHavokNavmeshModel(amapid, navname); MeshRenderableProxy mesh = MeshRenderableProxy.MeshRenderableFromHavokNavmeshResource( _renderScene, nasset.AssetVirtualPath, ModelMarkerType.Other); @@ -922,7 +920,7 @@ public async void LoadMapAsync(string mapid, bool selectOnLoad = false) if (CFG.Current.EnableTexturing) { job = ResourceManager.CreateNewJob($@"Loading {amapid} textures"); - foreach (AssetDescription asset in _assetLocator.GetMapTextures(amapid)) + foreach (AssetDescription asset in Locator.AssetLocator.GetMapTextures(amapid)) { if (asset.AssetArchiveVirtualPath != null) { @@ -1001,9 +999,9 @@ public async void LoadMapAsync(string mapid, bool selectOnLoad = false) if (FeatureFlags.LoadNavmeshes) { job = ResourceManager.CreateNewJob(@"Loading Navmeshes"); - if (_assetLocator.Type == GameType.DarkSoulsIII && FeatureFlags.LoadDS3Navmeshes) + if (Locator.AssetLocator.Type == GameType.DarkSoulsIII && FeatureFlags.LoadDS3Navmeshes) { - AssetDescription nav = _assetLocator.GetHavokNavmeshes(amapid); + AssetDescription nav = Locator.AssetLocator.GetHavokNavmeshes(amapid); job.AddLoadArchiveTask(nav.AssetArchiveVirtualPath, AccessLevel.AccessGPUOptimizedOnly, false, ResourceManager.ResourceType.NavmeshHKX); } @@ -1028,7 +1026,7 @@ public async void LoadMapAsync(string mapid, bool selectOnLoad = false) } // Real bad hack - EnvMapTextures = _assetLocator.GetEnvMapTextureNames(amapid); + EnvMapTextures = Locator.AssetLocator.GetEnvMapTextureNames(amapid); ScheduleTextureRefresh(); @@ -1131,40 +1129,40 @@ public void LoadFlver(FLVER2 flver, MeshRenderableProxy proxy, string name) private void SaveDS2Generators(Map map) { // Load all the params - AssetDescription regparamad = _assetLocator.GetDS2GeneratorRegistParam(map.Name); - AssetDescription regparamadw = _assetLocator.GetDS2GeneratorRegistParam(map.Name, true); + AssetDescription regparamad = Locator.AssetLocator.GetDS2GeneratorRegistParam(map.Name); + AssetDescription regparamadw = Locator.AssetLocator.GetDS2GeneratorRegistParam(map.Name, true); Param regparam = Param.Read(regparamad.AssetPath); - PARAMDEF reglayout = _assetLocator.GetParamdefForParam(regparam.ParamType); + PARAMDEF reglayout = Locator.AssetLocator.GetParamdefForParam(regparam.ParamType); regparam.ApplyParamdef(reglayout); - AssetDescription locparamad = _assetLocator.GetDS2GeneratorLocationParam(map.Name); - AssetDescription locparamadw = _assetLocator.GetDS2GeneratorLocationParam(map.Name, true); + AssetDescription locparamad = Locator.AssetLocator.GetDS2GeneratorLocationParam(map.Name); + AssetDescription locparamadw = Locator.AssetLocator.GetDS2GeneratorLocationParam(map.Name, true); Param locparam = Param.Read(locparamad.AssetPath); - PARAMDEF loclayout = _assetLocator.GetParamdefForParam(locparam.ParamType); + PARAMDEF loclayout = Locator.AssetLocator.GetParamdefForParam(locparam.ParamType); locparam.ApplyParamdef(loclayout); - AssetDescription genparamad = _assetLocator.GetDS2GeneratorParam(map.Name); - AssetDescription genparamadw = _assetLocator.GetDS2GeneratorParam(map.Name, true); + AssetDescription genparamad = Locator.AssetLocator.GetDS2GeneratorParam(map.Name); + AssetDescription genparamadw = Locator.AssetLocator.GetDS2GeneratorParam(map.Name, true); Param genparam = Param.Read(genparamad.AssetPath); - PARAMDEF genlayout = _assetLocator.GetParamdefForParam(genparam.ParamType); + PARAMDEF genlayout = Locator.AssetLocator.GetParamdefForParam(genparam.ParamType); genparam.ApplyParamdef(genlayout); - AssetDescription evtparamad = _assetLocator.GetDS2EventParam(map.Name); - AssetDescription evtparamadw = _assetLocator.GetDS2EventParam(map.Name, true); + AssetDescription evtparamad = Locator.AssetLocator.GetDS2EventParam(map.Name); + AssetDescription evtparamadw = Locator.AssetLocator.GetDS2EventParam(map.Name, true); Param evtparam = Param.Read(evtparamad.AssetPath); - PARAMDEF evtlayout = _assetLocator.GetParamdefForParam(evtparam.ParamType); + PARAMDEF evtlayout = Locator.AssetLocator.GetParamdefForParam(evtparam.ParamType); evtparam.ApplyParamdef(evtlayout); - AssetDescription evtlparamad = _assetLocator.GetDS2EventLocationParam(map.Name); - AssetDescription evtlparamadw = _assetLocator.GetDS2EventLocationParam(map.Name, true); + AssetDescription evtlparamad = Locator.AssetLocator.GetDS2EventLocationParam(map.Name); + AssetDescription evtlparamadw = Locator.AssetLocator.GetDS2EventLocationParam(map.Name, true); Param evtlparam = Param.Read(evtlparamad.AssetPath); - PARAMDEF evtllayout = _assetLocator.GetParamdefForParam(evtlparam.ParamType); + PARAMDEF evtllayout = Locator.AssetLocator.GetParamdefForParam(evtlparam.ParamType); evtlparam.ApplyParamdef(evtllayout); - AssetDescription objparamad = _assetLocator.GetDS2ObjInstanceParam(map.Name); - AssetDescription objparamadw = _assetLocator.GetDS2ObjInstanceParam(map.Name, true); + AssetDescription objparamad = Locator.AssetLocator.GetDS2ObjInstanceParam(map.Name); + AssetDescription objparamadw = Locator.AssetLocator.GetDS2ObjInstanceParam(map.Name, true); Param objparam = Param.Read(objparamad.AssetPath); - PARAMDEF objlayout = _assetLocator.GetParamdefForParam(objparam.ParamType); + PARAMDEF objlayout = Locator.AssetLocator.GetParamdefForParam(objparam.ParamType); objparam.ApplyParamdef(objlayout); // Clear them out @@ -1328,33 +1326,33 @@ private void SaveDS2Generators(Map map) private DCX.Type GetCompressionType() { - if (_assetLocator.Type == GameType.DarkSoulsIII) + if (Locator.AssetLocator.Type == GameType.DarkSoulsIII) { return DCX.Type.DCX_DFLT_10000_44_9; } - if (_assetLocator.Type == GameType.EldenRing) + if (Locator.AssetLocator.Type == GameType.EldenRing) { return DCX.Type.DCX_DFLT_10000_44_9; } - if (_assetLocator.Type == GameType.ArmoredCoreVI) + if (Locator.AssetLocator.Type == GameType.ArmoredCoreVI) { return DCX.Type.DCX_DFLT_10000_44_9; } - else if (_assetLocator.Type == GameType.DarkSoulsIISOTFS) + else if (Locator.AssetLocator.Type == GameType.DarkSoulsIISOTFS) { return DCX.Type.None; } - else if (_assetLocator.Type == GameType.Sekiro) + else if (Locator.AssetLocator.Type == GameType.Sekiro) { return DCX.Type.DCX_DFLT_10000_44_9; } - else if (_assetLocator.Type == GameType.Bloodborne) + else if (Locator.AssetLocator.Type == GameType.Bloodborne) { return DCX.Type.DCX_DFLT_10000_44_9; } - else if (_assetLocator.Type == GameType.DemonsSouls) + else if (Locator.AssetLocator.Type == GameType.DemonsSouls) { return DCX.Type.None; } @@ -1367,10 +1365,10 @@ private DCX.Type GetCompressionType() /// public void SaveBTL(Map map) { - List BTLs = _assetLocator.GetMapBTLs(map.Name); - List BTLs_w = _assetLocator.GetMapBTLs(map.Name, true); + List BTLs = Locator.AssetLocator.GetMapBTLs(map.Name); + List BTLs_w = Locator.AssetLocator.GetMapBTLs(map.Name, true); DCX.Type compressionType = GetCompressionType(); - if (_assetLocator.Type == GameType.DarkSoulsIISOTFS) + if (Locator.AssetLocator.Type == GameType.DarkSoulsIISOTFS) { for (var i = 0; i < BTLs.Count; i++) { @@ -1389,8 +1387,8 @@ public void SaveBTL(Map map) file.Bytes = btl.Write(DCX.Type.DCX_DFLT_10000_24_9); var bdtPath = BTLs_w[i].AssetPath[..^3] + "bdt"; - Utils.WriteWithBackup(_assetLocator, Utils.GetLocalAssetPath(_assetLocator, bdtPath), bdt, - Utils.GetLocalAssetPath(_assetLocator, BTLs_w[i].AssetPath)); + Utils.WriteWithBackup(Locator.AssetLocator, Utils.GetLocalAssetPath(Locator.AssetLocator, bdtPath), bdt, + Utils.GetLocalAssetPath(Locator.AssetLocator, BTLs_w[i].AssetPath)); } } } @@ -1410,8 +1408,8 @@ public void SaveBTL(Map map) { btl.Lights = newLights; - Utils.WriteWithBackup(_assetLocator, - Utils.GetLocalAssetPath(_assetLocator, BTLs_w[i].AssetPath), btl); + Utils.WriteWithBackup(Locator.AssetLocator, + Utils.GetLocalAssetPath(Locator.AssetLocator, BTLs_w[i].AssetPath), btl); } } } @@ -1423,11 +1421,11 @@ public void SaveMap(Map map) SaveBTL(map); try { - AssetDescription ad = _assetLocator.GetMapMSB(map.Name); - AssetDescription adw = _assetLocator.GetMapMSB(map.Name, true); + AssetDescription ad = Locator.AssetLocator.GetMapMSB(map.Name); + AssetDescription adw = Locator.AssetLocator.GetMapMSB(map.Name, true); IMsb msb; DCX.Type compressionType = GetCompressionType(); - if (_assetLocator.Type == GameType.DarkSoulsIII) + if (Locator.AssetLocator.Type == GameType.DarkSoulsIII) { MSB3 prev = MSB3.Read(ad.AssetPath); MSB3 n = new(); @@ -1436,7 +1434,7 @@ public void SaveMap(Map map) n.Routes = prev.Routes; msb = n; } - else if (_assetLocator.Type == GameType.EldenRing) + else if (Locator.AssetLocator.Type == GameType.EldenRing) { MSBE n = new(); //Asset path may be null if this is a newly created map @@ -1448,7 +1446,7 @@ public void SaveMap(Map map) } msb = n; } - else if (_assetLocator.Type == GameType.ArmoredCoreVI) + else if (Locator.AssetLocator.Type == GameType.ArmoredCoreVI) { MSB_AC6 prev = MSB_AC6.Read(ad.AssetPath); MSB_AC6 n = new(); @@ -1456,14 +1454,14 @@ public void SaveMap(Map map) n.Routes = prev.Routes; msb = n; } - else if (_assetLocator.Type == GameType.DarkSoulsIISOTFS) + else if (Locator.AssetLocator.Type == GameType.DarkSoulsIISOTFS) { MSB2 prev = MSB2.Read(ad.AssetPath); MSB2 n = new(); n.PartPoses = prev.PartPoses; msb = n; } - else if (_assetLocator.Type == GameType.Sekiro) + else if (Locator.AssetLocator.Type == GameType.Sekiro) { MSBS prev = MSBS.Read(ad.AssetPath); MSBS n = new(); @@ -1472,11 +1470,11 @@ public void SaveMap(Map map) n.Routes = prev.Routes; msb = n; } - else if (_assetLocator.Type == GameType.Bloodborne) + else if (Locator.AssetLocator.Type == GameType.Bloodborne) { msb = new MSBB(); } - else if (_assetLocator.Type == GameType.DemonsSouls) + else if (Locator.AssetLocator.Type == GameType.DemonsSouls) { MSBD prev = MSBD.Read(ad.AssetPath); MSBD n = new(); @@ -1490,7 +1488,7 @@ public void SaveMap(Map map) //((MSB1)msb).Models = t.Models; } - map.SerializeToMSB(msb, _assetLocator.Type); + map.SerializeToMSB(msb, Locator.AssetLocator.Type); // Create the map directory if it doesn't exist if (!Directory.Exists(Path.GetDirectoryName(adw.AssetPath))) @@ -1532,7 +1530,7 @@ public void SaveMap(Map map) File.Move(mapPath + ".temp", mapPath); - if (_assetLocator.Type == GameType.DarkSoulsIISOTFS) + if (Locator.AssetLocator.Type == GameType.DarkSoulsIISOTFS) { SaveDS2Generators(map); } @@ -1565,10 +1563,11 @@ public void UpdateWorldMsbList() { try { - if (_assetLocator.Type == GameType.EldenRing) + var assetLocator = Locator.ActiveProject.AssetLocator; + if (assetLocator.Type == GameType.EldenRing) { - AssetDescription ad = _assetLocator.GetWorldLoadListList(); - AssetDescription adw = _assetLocator.GetWorldLoadListList(true); + AssetDescription ad = assetLocator.GetWorldLoadListList(); + AssetDescription adw = assetLocator.GetWorldLoadListList(true); if (!Directory.Exists(Path.GetDirectoryName(adw.AssetPath))) { @@ -1587,7 +1586,7 @@ public void UpdateWorldMsbList() } } - Utils.WriteWithBackup(_assetLocator.GameRootDirectory, _assetLocator.GameModDirectory, @"\map\worldmsblist.worldloadlistlist.dcx", loadList); + Utils.WriteWithBackup(Locator.ActiveProject.ParentProject.AssetLocator.RootDirectory, assetLocator.RootDirectory, @"\map\worldmsblist.worldloadlistlist.dcx", loadList); TaskLogs.AddLog($"Saved WorldMsbList"); } diff --git a/src/StudioCore/ParamEditor/MassEditScript.cs b/src/StudioCore/ParamEditor/MassEditScript.cs index 1ad38de2b..aa59de4d6 100644 --- a/src/StudioCore/ParamEditor/MassEditScript.cs +++ b/src/StudioCore/ParamEditor/MassEditScript.cs @@ -51,8 +51,8 @@ private MassEditScript(string path, string name) public static void ReloadScripts() { - var cdir = ParamBank.PrimaryBank.AssetLocator.GetScriptAssetsCommonDir(); - var dir = ParamBank.PrimaryBank.AssetLocator.GetScriptAssetsDir(); + var cdir = Locator.AssetLocator.GetScriptAssetsCommonDir(); + var dir = Locator.AssetLocator.GetScriptAssetsDir(); scriptList = new List(); LoadScriptsFromDir(cdir); LoadScriptsFromDir(dir); diff --git a/src/StudioCore/ParamEditor/ParamBank.cs b/src/StudioCore/ParamEditor/ParamBank.cs index 4ba179d64..721620f69 100644 --- a/src/StudioCore/ParamEditor/ParamBank.cs +++ b/src/StudioCore/ParamEditor/ParamBank.cs @@ -33,10 +33,15 @@ public enum RowGetType SelectedRows = 2 } - public static ParamBank PrimaryBank = new(); - public static ParamBank VanillaBank = new(); + public static ParamBank PrimaryBank => Locator.ActiveProject.ParamBank; + public static ParamBank VanillaBank => Locator.ActiveProject.ParentProject.ParamBank; public static Dictionary AuxBanks = new(); + /// + /// Mapping from path -> PARAMDEF for cache and and comparison purposes. TODO: check for paramdef comparisons and evaluate if the file/paramdef was actually the same. + /// + private static readonly Dictionary _paramdefsCache = new(); + public static string ClipboardParam = null; public static List ClipboardRows = new(); @@ -44,14 +49,18 @@ public enum RowGetType /// /// Mapping from ParamType -> PARAMDEF. /// - private static Dictionary _paramdefs; + private Dictionary _paramdefs = new(); + + //TODO private this + public Dictionary ParamMetas = new(); + /// /// Mapping from Param filename -> Manual ParamType. /// This is for params with no usable ParamType at some particular game version. /// By convention, ParamTypes ending in "_TENTATIVE" do not have official data to reference. /// - private static Dictionary _tentativeParamType; + private Dictionary _tentativeParamType; /// /// Map related params. @@ -96,6 +105,8 @@ public enum RowGetType private static readonly HashSet EMPTYSET = new(); + public Project Project; + private Dictionary _params; private ulong _paramVersion; @@ -111,11 +122,10 @@ public enum RowGetType private Dictionary _usedTentativeParamTypes; private Dictionary> _vanillaDiffCache; //If param != vanillaparam - internal AssetLocator AssetLocator; - private Param EnemyParam; + private Param EnemyParam => _params["EnemyParam"]; - public static bool IsDefsLoaded { get; private set; } + public bool IsDefsLoaded { get; private set; } public static bool IsMetaLoaded { get; private set; } public bool IsLoadingParams { get; private set; } @@ -172,6 +182,11 @@ public IReadOnlyDictionary> PrimaryDiffCache } } + public ParamBank(Project owner) + { + Project = owner; + } + private static FileNotFoundException CreateParamMissingException(GameType type) { if (type is GameType.DarkSoulsPTDE or GameType.Sekiro) @@ -190,21 +205,23 @@ private static FileNotFoundException CreateParamMissingException(GameType type) $"Cannot locate param files for {type}.\nYour game folder may be missing game files, please verify game files through steam to restore them."); } - private static List<(string, PARAMDEF)> LoadParamdefs(AssetLocator assetLocator) + private List<(string, PARAMDEF)> LoadParamdefs() { _paramdefs = new Dictionary(); _tentativeParamType = new Dictionary(); - var dir = assetLocator.GetParamdefDir(); - var files = Directory.GetFiles(dir, "*.xml"); + var files = Project.AssetLocator.GetAllProjectFiles($@"Paramdex\{AssetUtils.GetGameIDForDir(Locator.ActiveProject.Type)}\Defs", ["*.xml"], true, false); List<(string, PARAMDEF)> defPairs = new(); foreach (var f in files) { - PARAMDEF pdef = PARAMDEF.XmlDeserialize(f, true); + if (!_paramdefsCache.TryGetValue(f, out PARAMDEF pdef)) + { + pdef = PARAMDEF.XmlDeserialize(f, true); + } _paramdefs.Add(pdef.ParamType, pdef); defPairs.Add((f, pdef)); } - var tentativeMappingPath = assetLocator.GetTentativeParamTypePath(); + var tentativeMappingPath = Project.AssetLocator.GetProjectFilePath($@"{Project.AssetLocator.GetParamdexDir()}\Defs\TentativeParamType.csv"); if (File.Exists(tentativeMappingPath)) { // No proper CSV library is used currently, and all CSV parsing is in the context of param files. @@ -223,23 +240,23 @@ private static FileNotFoundException CreateParamMissingException(GameType type) return defPairs; } - - public static void LoadParamMeta(List<(string, PARAMDEF)> defPairs, AssetLocator assetLocator) + public void LoadParamMeta(List<(string, PARAMDEF)> defPairs) { - var mdir = assetLocator.GetParammetaDir(); + //This way of tying stuff together still sucks + var mdir = Project.AssetLocator.GetProjectFilePath($@"{Locator.ActiveProject.AssetLocator.GetParamdexDir()}\Meta"); foreach ((var f, PARAMDEF pdef) in defPairs) { var fName = f.Substring(f.LastIndexOf('\\') + 1); - ParamMetaData.XmlDeserialize($@"{mdir}\{fName}", pdef); + var md = ParamMetaData.XmlDeserialize($@"{mdir}\{fName}", pdef); + ParamMetas.Add(pdef, md); } } public CompoundAction LoadParamDefaultNames(string param = null, bool onlyAffectEmptyNames = false, bool onlyAffectVanillaNames = false) { - var dir = AssetLocator.GetParamNamesDir(); var files = param == null - ? Directory.GetFiles(dir, "*.txt") - : new[] { Path.Combine(dir, $"{param}.txt") }; + ? Project.AssetLocator.GetAllProjectFiles($@"{Project.AssetLocator.GetParamdexDir()}\Names", ["*.txt"], true) + : new[] { Project.AssetLocator.GetProjectFilePath($@"{Project.AssetLocator.GetParamdexDir()}\Names\{param}.txt") }; List actions = new(); foreach (var f in files) { @@ -299,7 +316,7 @@ private void LoadParamFromBinder(IBinder parambnd, ref Dictionary Param p; - if (AssetLocator.Type == GameType.ArmoredCoreVI) + if (Project.Type == GameType.ArmoredCoreVI) { _usedTentativeParamTypes = new Dictionary(); p = Param.ReadIgnoreCompression(f.Bytes); @@ -357,7 +374,7 @@ private void LoadParamFromBinder(IBinder parambnd, ref Dictionary // Try to fixup Elden Ring ChrModelParam for ER 1.06 because many have been saving botched params and // it's an easy fixup - if (AssetLocator.Type == GameType.EldenRing && + if (Project.Type == GameType.EldenRing && p.ParamType == "CHR_MODEL_PARAM_ST" && version == 10601000) { @@ -380,7 +397,7 @@ private void LoadParamFromBinder(IBinder parambnd, ref Dictionary var name = f.Name.Split("\\").Last(); var message = $"Could not apply ParamDef for {name}"; - if (AssetLocator.Type == GameType.DarkSoulsRemastered && + if (Project.Type == GameType.DarkSoulsRemastered && name is "m99_ToneMapBank.param" or "m99_ToneCorrectBank.param" or "default_ToneCorrectBank.param") { @@ -396,101 +413,19 @@ name is "m99_ToneMapBank.param" or "m99_ToneCorrectBank.param" } } } - - /// - /// Checks for DeS paramBNDs and returns the name of the parambnd with the highest priority. - /// - private string GetDesGameparamName(string rootDirectory) - { - var name = ""; - name = "gameparamna.parambnd.dcx"; - if (File.Exists($@"{rootDirectory}\param\gameparam\{name}")) - { - return name; - } - - name = "gameparamna.parambnd"; - if (File.Exists($@"{rootDirectory}\param\gameparam\{name}")) - { - return name; - } - - name = "gameparam.parambnd.dcx"; - if (File.Exists($@"{rootDirectory}\param\gameparam\{name}")) - { - return name; - } - - name = "gameparam.parambnd"; - if (File.Exists($@"{rootDirectory}\param\gameparam\{name}")) - { - return name; - } - - return ""; - } - private void LoadParamsDES() { - var dir = AssetLocator.GameRootDirectory; - var mod = AssetLocator.GameModDirectory; - - var paramBinderName = GetDesGameparamName(mod); - if (paramBinderName == "") - { - paramBinderName = GetDesGameparamName(dir); - } - - // Load params - var param = $@"{mod}\param\gameparam\{paramBinderName}"; - if (!File.Exists(param)) - { - param = $@"{dir}\param\gameparam\{paramBinderName}"; - } - - if (!File.Exists(param)) + var param = Project.AssetLocator.GetAssetPathFromOptions([@$"\param\gameparam\gameparamna.parambnd.dcx", @$"\param\gameparam\gameparamna.parambnd", @$"\param\gameparam\gameparam.parambnd.dcx", @$"\param\gameparam\gameparam.parambnd"]).Item2; + if (param == null) { - throw CreateParamMissingException(AssetLocator.Type); + throw CreateParamMissingException(Project.Type); } - LoadParamsDESFromFile(param); - //DrawParam - Dictionary drawparams = new(); - if (Directory.Exists($@"{dir}\param\drawparam")) + var drawparams = Project.AssetLocator.GetAllAssets($@"\param\drawparam", ["*.parambnd.dcx", "*.parambnd"]); + foreach (string drawparam in drawparams) { - foreach (var p in Directory.GetFiles($@"{dir}\param\drawparam", "*.parambnd.dcx")) - { - drawparams[Path.GetFileNameWithoutExtension(p)] = p; - } - } - - if (Directory.Exists($@"{mod}\param\drawparam")) - { - foreach (var p in Directory.GetFiles($@"{mod}\param\drawparam", "*.parambnd.dcx")) - { - drawparams[Path.GetFileNameWithoutExtension(p)] = p; - } - } - - foreach (KeyValuePair drawparam in drawparams) - { - LoadParamsDESFromFile(drawparam.Value); - } - } - - private void LoadVParamsDES() - { - var paramBinderName = GetDesGameparamName(AssetLocator.GameRootDirectory); - - LoadParamsDESFromFile($@"{AssetLocator.GameRootDirectory}\param\gameparam\{paramBinderName}"); - if (Directory.Exists($@"{AssetLocator.GameRootDirectory}\param\drawparam")) - { - foreach (var p in Directory.GetFiles($@"{AssetLocator.GameRootDirectory}\param\drawparam", - "*.parambnd.dcx")) - { - LoadParamsDS1FromFile(p); - } + LoadParamsDESFromFile(drawparam); } } @@ -502,56 +437,17 @@ private void LoadParamsDESFromFile(string path) private void LoadParamsDS1() { - var dir = AssetLocator.GameRootDirectory; - var mod = AssetLocator.GameModDirectory; - if (!File.Exists($@"{dir}\\param\GameParam\GameParam.parambnd")) - { - throw CreateParamMissingException(AssetLocator.Type); - } - - // Load params - var param = $@"{mod}\param\GameParam\GameParam.parambnd"; - if (!File.Exists(param)) + var param = Project.AssetLocator.GetAssetPath($@"param\GameParam\GameParam.parambnd"); + if (param == null) { - param = $@"{dir}\param\GameParam\GameParam.parambnd"; + throw CreateParamMissingException(Project.Type); } - LoadParamsDS1FromFile(param); - //DrawParam - Dictionary drawparams = new(); - if (Directory.Exists($@"{dir}\param\DrawParam")) + var drawparams = Project.AssetLocator.GetAllAssets($@"param\DrawParam", ["*.parambnd"]); + foreach (string drawparam in drawparams) { - foreach (var p in Directory.GetFiles($@"{dir}\param\DrawParam", "*.parambnd")) - { - drawparams[Path.GetFileNameWithoutExtension(p)] = p; - } - } - - if (Directory.Exists($@"{mod}\param\DrawParam")) - { - foreach (var p in Directory.GetFiles($@"{mod}\param\DrawParam", "*.parambnd")) - { - drawparams[Path.GetFileNameWithoutExtension(p)] = p; - } - } - - foreach (KeyValuePair drawparam in drawparams) - { - LoadParamsDS1FromFile(drawparam.Value); - } - } - - private void LoadVParamsDS1() - { - LoadParamsDS1FromFile($@"{AssetLocator.GameRootDirectory}\param\GameParam\GameParam.parambnd"); - if (Directory.Exists($@"{AssetLocator.GameRootDirectory}\param\DrawParam")) - { - foreach (var p in Directory.GetFiles($@"{AssetLocator.GameRootDirectory}\param\DrawParam", - "*.parambnd")) - { - LoadParamsDS1FromFile(p); - } + LoadParamsDS1FromFile(drawparam); } } @@ -563,56 +459,17 @@ private void LoadParamsDS1FromFile(string path) private void LoadParamsDS1R() { - var dir = AssetLocator.GameRootDirectory; - var mod = AssetLocator.GameModDirectory; - if (!File.Exists($@"{dir}\\param\GameParam\GameParam.parambnd.dcx")) - { - throw CreateParamMissingException(AssetLocator.Type); - } - - // Load params - var param = $@"{mod}\param\GameParam\GameParam.parambnd.dcx"; - if (!File.Exists(param)) + var param = Project.AssetLocator.GetAssetPath($@"param\GameParam\GameParam.parambnd.dcx"); + if (param == null) { - param = $@"{dir}\param\GameParam\GameParam.parambnd.dcx"; + throw CreateParamMissingException(Project.Type); } - LoadParamsDS1RFromFile(param); - //DrawParam - Dictionary drawparams = new(); - if (Directory.Exists($@"{dir}\param\DrawParam")) - { - foreach (var p in Directory.GetFiles($@"{dir}\param\DrawParam", "*.parambnd.dcx")) - { - drawparams[Path.GetFileNameWithoutExtension(p)] = p; - } - } - - if (Directory.Exists($@"{mod}\param\DrawParam")) - { - foreach (var p in Directory.GetFiles($@"{mod}\param\DrawParam", "*.parambnd.dcx")) - { - drawparams[Path.GetFileNameWithoutExtension(p)] = p; - } - } - - foreach (KeyValuePair drawparam in drawparams) + var drawparams = Project.AssetLocator.GetAllAssets($@"param\DrawParam", ["*.parambnd.dcx"]); + foreach (string drawparam in drawparams) { - LoadParamsDS1RFromFile(drawparam.Value); - } - } - - private void LoadVParamsDS1R() - { - LoadParamsDS1RFromFile($@"{AssetLocator.GameRootDirectory}\param\GameParam\GameParam.parambnd.dcx"); - if (Directory.Exists($@"{AssetLocator.GameRootDirectory}\param\DrawParam")) - { - foreach (var p in Directory.GetFiles($@"{AssetLocator.GameRootDirectory}\param\DrawParam", - "*.parambnd.dcx")) - { - LoadParamsDS1FromFile(p); - } + LoadParamsDS1RFromFile(drawparam); } } @@ -624,28 +481,14 @@ private void LoadParamsDS1RFromFile(string path) private void LoadParamsBBSekiro() { - var dir = AssetLocator.GameRootDirectory; - var mod = AssetLocator.GameModDirectory; - if (!File.Exists($@"{dir}\\param\gameparam\gameparam.parambnd.dcx")) - { - throw CreateParamMissingException(AssetLocator.Type); - } - - // Load params - var param = $@"{mod}\param\gameparam\gameparam.parambnd.dcx"; - if (!File.Exists(param)) + var param = Project.AssetLocator.GetAssetPath($@"param\gameparam\gameparam.parambnd.dcx"); + if (param == null) { - param = $@"{dir}\param\gameparam\gameparam.parambnd.dcx"; + throw CreateParamMissingException(Project.Type); } - LoadParamsBBSekiroFromFile(param); } - private void LoadVParamsBBSekiro() - { - LoadParamsBBSekiroFromFile($@"{AssetLocator.GameRootDirectory}\param\gameparam\gameparam.parambnd.dcx"); - } - private void LoadParamsBBSekiroFromFile(string path) { using BND4 bnd = BND4.Read(path); @@ -665,75 +508,17 @@ private static List GetLooseParamsInDir(string dir) private void LoadParamsDS2(bool loose) { - var dir = AssetLocator.GameRootDirectory; - var mod = AssetLocator.GameModDirectory; - if (!File.Exists($@"{dir}\enc_regulation.bnd.dcx")) - { - throw CreateParamMissingException(AssetLocator.Type); - } - - if (!BND4.Is($@"{dir}\enc_regulation.bnd.dcx")) - { - PlatformUtils.Instance.MessageBox( - "Attempting to decrypt DS2 regulation file, else functionality will be limited.", "", - MessageBoxButtons.OK, MessageBoxIcon.Error); - //return; - } - - // Load loose params (prioritizing ones in mod folder) - List looseParams = GetLooseParamsInDir(mod); - if (Directory.Exists($@"{dir}\Param")) - { - // Include any params in game folder that are not in mod folder - foreach (var path in Directory.GetFileSystemEntries($@"{dir}\Param", @"*.param")) - { - if (looseParams.Find(e => Path.GetFileName(e) == Path.GetFileName(path)) == null) - { - // Project folder does not contain this loose param - looseParams.Add(path); - } - } - } - - // Load reg params - var param = $@"{mod}\enc_regulation.bnd.dcx"; - if (!File.Exists(param)) - { - param = $@"{dir}\enc_regulation.bnd.dcx"; - } - - var enemyFile = $@"{mod}\Param\EnemyParam.param"; - if (!File.Exists(enemyFile)) + var param = Project.AssetLocator.GetAssetPath($@"enc_regulation.bnd.dcx"); + if (param == null) { - enemyFile = $@"{dir}\Param\EnemyParam.param"; + throw CreateParamMissingException(Project.Type); } - - LoadParamsDS2FromFile(looseParams, param, enemyFile, loose); + var looseParams = Project.AssetLocator.GetAllAssets($@"Param", [$@"*.param"]); + LoadParamsDS2FromFile(looseParams, param, loose); LoadExternalRowNames(); } - private void LoadVParamsDS2(bool loose) - { - if (!File.Exists($@"{AssetLocator.GameRootDirectory}\enc_regulation.bnd.dcx")) - { - throw CreateParamMissingException(AssetLocator.Type); - } - - if (!BND4.Is($@"{AssetLocator.GameRootDirectory}\enc_regulation.bnd.dcx")) - { - PlatformUtils.Instance.MessageBox( - "Attempting to decrypt DS2 regulation file, else functionality will be limited.", "", - MessageBoxButtons.OK, MessageBoxIcon.Error); - } - - // Load loose params - List looseParams = GetLooseParamsInDir(AssetLocator.GameRootDirectory); - - LoadParamsDS2FromFile(looseParams, $@"{AssetLocator.GameRootDirectory}\enc_regulation.bnd.dcx", - $@"{AssetLocator.GameRootDirectory}\Param\EnemyParam.param", loose); - } - - private void LoadParamsDS2FromFile(List looseParams, string path, string enemypath, bool loose) + private void LoadParamsDS2FromFile(IEnumerable looseParams, string path, bool loose) { BND4 paramBnd; if (!BND4.Is(path)) @@ -746,32 +531,6 @@ private void LoadParamsDS2FromFile(List looseParams, string path, string paramBnd = BND4.Read(path); } - BinderFile bndfile = paramBnd.Files.Find(x => Path.GetFileName(x.Name) == "EnemyParam.param"); - if (bndfile != null) - { - EnemyParam = Param.Read(bndfile.Bytes); - } - - // Otherwise the param is a loose param - if (File.Exists(enemypath)) - { - EnemyParam = Param.Read(enemypath); - } - - if (EnemyParam is { ParamType: not null }) - { - try - { - PARAMDEF def = _paramdefs[EnemyParam.ParamType]; - EnemyParam.ApplyParamdef(def); - } - catch (Exception e) - { - TaskLogs.AddLog($"Could not apply ParamDef for {EnemyParam.ParamType}", - LogLevel.Warning, TaskLogs.LogPriority.Normal, e); - } - } - LoadParamFromBinder(paramBnd, ref _params, out _paramVersion); foreach (var p in looseParams) @@ -803,7 +562,7 @@ private void LoadParamsDS2FromFile(List looseParams, string path, string catch (Exception e) { var message = $"Could not apply ParamDef for {fname}"; - if (AssetLocator.Type == GameType.DarkSoulsIISOTFS && + if (Project.Type == GameType.DarkSoulsIISOTFS && fname is "GENERATOR_DBG_LOCATION_PARAM") { // Known cases that don't affect standard modmaking @@ -823,34 +582,25 @@ private void LoadParamsDS2FromFile(List looseParams, string path, string private void LoadParamsDS3(bool loose) { - var dir = AssetLocator.GameRootDirectory; - var mod = AssetLocator.GameModDirectory; - if (!File.Exists($@"{dir}\Data0.bdt")) + string param; + bool looseFile; + if (loose) { - throw CreateParamMissingException(AssetLocator.Type); + var p = Project.AssetLocator.GetAssetPathFromOptions([$@"param\gameparam\gameparam_dlc2.parambnd.dcx", $@"Data0.bdt"]); + looseFile = p.Item1 == 0; + param = p.Item2; } - - var vparam = $@"{dir}\Data0.bdt"; - // Load loose params if they exist - if (loose && File.Exists($@"{mod}\\param\gameparam\gameparam_dlc2.parambnd.dcx")) + else { - LoadParamsDS3FromFile($@"{mod}\param\gameparam\gameparam_dlc2.parambnd.dcx", true); + var p = Project.AssetLocator.GetAssetPathFromOptions([$@"Data0.bdt", $@"param\gameparam\gameparam_dlc2.parambnd.dcx"]); + looseFile = p.Item1 == 1; + param = p.Item2; } - else + if (param == null) { - var param = $@"{mod}\Data0.bdt"; - if (!File.Exists(param)) - { - param = vparam; - } - - LoadParamsDS3FromFile(param, false); + throw CreateParamMissingException(Project.Type); } - } - - private void LoadVParamsDS3() - { - LoadParamsDS3FromFile($@"{AssetLocator.GameRootDirectory}\Data0.bdt", false); + LoadParamsDS3FromFile(param, looseFile); } private void LoadParamsDS3FromFile(string path, bool isLoose) @@ -859,55 +609,16 @@ private void LoadParamsDS3FromFile(string path, bool isLoose) LoadParamFromBinder(lparamBnd, ref _params, out _paramVersion); } - private void LoadParamsER(bool partial) + private void LoadParamsER() { - var dir = AssetLocator.GameRootDirectory; - var mod = AssetLocator.GameModDirectory; - if (!File.Exists($@"{dir}\\regulation.bin")) - { - throw CreateParamMissingException(AssetLocator.Type); - } - - // Load params - var param = $@"{mod}\regulation.bin"; - if (!File.Exists(param) || partial) + var param = Project.AssetLocator.GetAssetPath($@"regulation.bin"); + if (param == null) { - param = $@"{dir}\regulation.bin"; + throw CreateParamMissingException(Project.Type); } - LoadParamsERFromFile(param); - param = $@"{mod}\regulation.bin"; - if (partial && File.Exists(param)) - { - using BND4 pParamBnd = SFUtil.DecryptERRegulation(param); - Dictionary cParamBank = new(); - ulong v; - LoadParamFromBinder(pParamBnd, ref cParamBank, out v, true); - foreach (KeyValuePair pair in cParamBank) - { - Param baseParam = _params[pair.Key]; - foreach (Param.Row row in pair.Value.Rows) - { - Param.Row bRow = baseParam[row.ID]; - if (bRow == null) - { - baseParam.AddRow(row); - } - else - { - bRow.Name = row.Name; - foreach (Param.Column field in bRow.Columns) - { - Param.Cell cell = bRow[field]; - cell.Value = row[field].Value; - } - } - } - } - } - - string sysParam = AssetLocator.GetAssetPath(@"param\systemparam\systemparam.parambnd.dcx"); + string sysParam = Project.AssetLocator.GetAssetPath(@"param\systemparam\systemparam.parambnd.dcx"); if (File.Exists(sysParam)) { LoadParamsERFromFile(sysParam, false); @@ -917,7 +628,7 @@ private void LoadParamsER(bool partial) TaskLogs.AddLog("Systemparam could not be found. These require an unpacked game to modify.", LogLevel.Information, TaskLogs.LogPriority.Normal); } - var eventParam = AssetLocator.GetAssetPath(@"param\eventparam\eventparam.parambnd.dcx"); + var eventParam = Project.AssetLocator.GetAssetPath(@"param\eventparam\eventparam.parambnd.dcx"); if (File.Exists(eventParam)) { LoadParamsERFromFile(eventParam, false); @@ -928,23 +639,6 @@ private void LoadParamsER(bool partial) } } - private void LoadVParamsER() - { - LoadParamsERFromFile($@"{AssetLocator.GameRootDirectory}\regulation.bin"); - - var sysParam = $@"{AssetLocator.GameRootDirectory}\param\systemparam\systemparam.parambnd.dcx"; - if (File.Exists(sysParam)) - { - LoadParamsERFromFile(sysParam, false); - } - - var eventParam = $@"{AssetLocator.GameRootDirectory}\param\eventparam\eventparam.parambnd.dcx"; - if (File.Exists(eventParam)) - { - LoadParamsERFromFile(eventParam, false); - } - } - private void LoadParamsERFromFile(string path, bool encrypted = true) { if (encrypted) @@ -961,24 +655,15 @@ private void LoadParamsERFromFile(string path, bool encrypted = true) private void LoadParamsAC6() { - var dir = AssetLocator.GameRootDirectory; - var mod = AssetLocator.GameModDirectory; - if (!File.Exists($@"{dir}\\regulation.bin")) - { - throw CreateParamMissingException(AssetLocator.Type); - } - - // Load params - var param = $@"{mod}\regulation.bin"; - if (!File.Exists(param)) + var param = Project.AssetLocator.GetAssetPath($@"regulation.bin"); + if (param == null) { - param = $@"{dir}\regulation.bin"; + throw CreateParamMissingException(Project.Type); } - LoadParamsAC6FromFile(param, true); - string sysParam = AssetLocator.GetAssetPath(@"param\systemparam\systemparam.parambnd.dcx"); - if (File.Exists(sysParam)) + string sysParam = Project.AssetLocator.GetAssetPath(@"param\systemparam\systemparam.parambnd.dcx"); + if (sysParam != null) { LoadParamsAC6FromFile(sysParam, false); } @@ -987,8 +672,8 @@ private void LoadParamsAC6() TaskLogs.AddLog("Systemparam could not be found. These require an unpacked game to modify.", LogLevel.Information, TaskLogs.LogPriority.Normal); } - string graphicsConfigParam = AssetLocator.GetAssetPath(@"param\graphicsconfig\graphicsconfig.parambnd.dcx"); - if (File.Exists(graphicsConfigParam)) + string graphicsConfigParam = Project.AssetLocator.GetAssetPath(@"param\graphicsconfig\graphicsconfig.parambnd.dcx"); + if (graphicsConfigParam != null) { LoadParamsAC6FromFile(graphicsConfigParam, false); } @@ -997,8 +682,8 @@ private void LoadParamsAC6() TaskLogs.AddLog("Graphicsconfig could not be found. These require an unpacked game to modify.", LogLevel.Information, TaskLogs.LogPriority.Normal); } - string eventParam = AssetLocator.GetAssetPath(@"param\eventparam\eventparam.parambnd.dcx"); - if (File.Exists(eventParam)) + string eventParam = Project.AssetLocator.GetAssetPath(@"param\eventparam\eventparam.parambnd.dcx"); + if (eventParam != null) { LoadParamsAC6FromFile(eventParam, false); } @@ -1008,29 +693,6 @@ private void LoadParamsAC6() } } - private void LoadVParamsAC6() - { - LoadParamsAC6FromFile($@"{AssetLocator.GameRootDirectory}\regulation.bin"); - - var sysParam = $@"{AssetLocator.GameRootDirectory}\param\systemparam\systemparam.parambnd.dcx"; - if (File.Exists(sysParam)) - { - LoadParamsAC6FromFile(sysParam, false); - } - - var graphicsConfigParam = $@"{AssetLocator.GameRootDirectory}\param\graphicsconfig\graphicsconfig.parambnd.dcx"; - if (File.Exists(graphicsConfigParam)) - { - LoadParamsAC6FromFile(graphicsConfigParam, false); - } - - var eventParam = $@"{AssetLocator.GameRootDirectory}\param\eventparam\eventparam.parambnd.dcx"; - if (File.Exists(eventParam)) - { - LoadParamsAC6FromFile(eventParam, false); - } - } - private void LoadParamsAC6FromFile(string path, bool encrypted = true) { if (encrypted) @@ -1045,131 +707,94 @@ private void LoadParamsAC6FromFile(string path, bool encrypted = true) } } - //Some returns and repetition, but it keeps all threading and loading-flags visible inside this method - public static void ReloadParams(ProjectSettings settings, NewProjectOptions options) + private void LoadParams() { - // Steal assetlocator from PrimaryBank. - AssetLocator locator = PrimaryBank.AssetLocator; - _paramdefs = new Dictionary(); IsDefsLoaded = false; - IsMetaLoaded = false; - - AuxBanks = new Dictionary(); - - PrimaryBank._params = new Dictionary(); - PrimaryBank.IsLoadingParams = true; - - UICache.ClearCaches(); - - TaskManager.Run(new TaskManager.LiveTask("Param - Load Params", TaskManager.RequeueType.WaitThenRequeue, - false, () => - { - if (PrimaryBank.AssetLocator.Type != GameType.Undefined) - { - List<(string, PARAMDEF)> defPairs = LoadParamdefs(locator); - IsDefsLoaded = true; - TaskManager.Run(new TaskManager.LiveTask("Param - Load Meta", - TaskManager.RequeueType.WaitThenRequeue, false, () => - { - LoadParamMeta(defPairs, locator); - IsMetaLoaded = true; - })); - } - - if (locator.Type == GameType.DemonsSouls) - { - PrimaryBank.LoadParamsDES(); - } + IsLoadingParams = true; - if (locator.Type == GameType.DarkSoulsPTDE) - { - PrimaryBank.LoadParamsDS1(); - } + _params = new Dictionary(); - if (locator.Type == GameType.DarkSoulsRemastered) + if (Project.Type != GameType.Undefined) + { + List<(string, PARAMDEF)> defPairs = LoadParamdefs(); + IsDefsLoaded = true; + TaskManager.Run(new TaskManager.LiveTask("Param - Load Meta", + TaskManager.RequeueType.WaitThenRequeue, false, () => { - PrimaryBank.LoadParamsDS1R(); - } + LoadParamMeta(defPairs); + IsMetaLoaded = true; + })); + } - if (locator.Type == GameType.DarkSoulsIISOTFS) - { - PrimaryBank.LoadParamsDS2(settings.UseLooseParams); - } + if (Project.Type == GameType.DemonsSouls) + { + LoadParamsDES(); + } - if (locator.Type == GameType.DarkSoulsIII) - { - PrimaryBank.LoadParamsDS3(settings.UseLooseParams); - } + if (Project.Type == GameType.DarkSoulsPTDE) + { + LoadParamsDS1(); + } - if (locator.Type == GameType.Bloodborne || locator.Type == GameType.Sekiro) - { - PrimaryBank.LoadParamsBBSekiro(); - } + if (Project.Type == GameType.DarkSoulsRemastered) + { + LoadParamsDS1R(); + } - if (locator.Type == GameType.EldenRing) - { - PrimaryBank.LoadParamsER(settings.PartialParams); - } + if (Project.Type == GameType.DarkSoulsIISOTFS) + { + LoadParamsDS2(Project.Settings.UseLooseParams); + } - if (locator.Type == GameType.ArmoredCoreVI) - { - PrimaryBank.LoadParamsAC6(); - } + if (Project.Type == GameType.DarkSoulsIII) + { + LoadParamsDS3(Project.Settings.UseLooseParams); + } - PrimaryBank.ClearParamDiffCaches(); - PrimaryBank.IsLoadingParams = false; + if (Project.Type == GameType.Bloodborne || Project.Type == GameType.Sekiro) + { + LoadParamsBBSekiro(); + } - VanillaBank.IsLoadingParams = true; - VanillaBank._params = new Dictionary(); - TaskManager.Run(new TaskManager.LiveTask("Param - Load Vanilla Params", - TaskManager.RequeueType.WaitThenRequeue, false, () => - { - if (locator.Type == GameType.DemonsSouls) - { - VanillaBank.LoadVParamsDES(); - } + if (Project.Type == GameType.EldenRing) + { + LoadParamsER(); + } - if (locator.Type == GameType.DarkSoulsPTDE) - { - VanillaBank.LoadVParamsDS1(); - } + if (Project.Type == GameType.ArmoredCoreVI) + { + LoadParamsAC6(); + } - if (locator.Type == GameType.DarkSoulsRemastered) - { - VanillaBank.LoadVParamsDS1R(); - } + ClearParamDiffCaches(); - if (locator.Type == GameType.DarkSoulsIISOTFS) - { - VanillaBank.LoadVParamsDS2(settings.UseLooseParams); - } + IsLoadingParams = false; + } - if (locator.Type == GameType.DarkSoulsIII) - { - VanillaBank.LoadVParamsDS3(); - } + //Some returns and repetition, but it keeps all threading and loading-flags visible inside this method + public static void ReloadParams(ProjectSettings settings, NewProjectOptions options) + { + IsMetaLoaded = false; - if (locator.Type == GameType.Bloodborne || locator.Type == GameType.Sekiro) - { - VanillaBank.LoadVParamsBBSekiro(); - } + AuxBanks = new Dictionary(); - if (locator.Type == GameType.EldenRing) - { - VanillaBank.LoadVParamsER(); - } + UICache.ClearCaches(); - if (locator.Type == GameType.ArmoredCoreVI) - { - VanillaBank.LoadVParamsAC6(); - } + TaskManager.Run(new TaskManager.LiveTask("Param - Load Params", TaskManager.RequeueType.WaitThenRequeue, + false, () => + { + PrimaryBank.LoadParams(); - VanillaBank.IsLoadingParams = false; + TaskManager.Run(new TaskManager.LiveTask("Param - Load Vanilla Params", + TaskManager.RequeueType.WaitThenRequeue, false, () => + { + VanillaBank.LoadParams(); TaskManager.Run(new TaskManager.LiveTask("Param - Check Differences", TaskManager.RequeueType.WaitThenRequeue, false, () => RefreshAllParamDiffCaches(true))); + UICache.ClearCaches(); })); if (options != null) @@ -1188,59 +813,20 @@ public static void ReloadParams(ProjectSettings settings, NewProjectOptions opti } } } + UICache.ClearCaches(); })); } - public static void LoadAuxBank(string path, string looseDir, string enemyPath, ProjectSettings settings) + public static void LoadAuxBank(string dir, ProjectSettings settings = null) { - // Steal assetlocator - AssetLocator locator = PrimaryBank.AssetLocator; - ParamBank newBank = new(); - newBank.SetAssetLocator(locator); - newBank._params = new Dictionary(); - newBank.IsLoadingParams = true; - if (locator.Type == GameType.ArmoredCoreVI) - { - newBank.LoadParamsAC6FromFile(path); - } - else if (locator.Type == GameType.EldenRing) - { - newBank.LoadParamsERFromFile(path); - } - else if (locator.Type == GameType.Sekiro) - { - newBank.LoadParamsBBSekiroFromFile(path); - } - else if (locator.Type == GameType.DarkSoulsIII) - { - newBank.LoadParamsDS3FromFile(path, path.Trim().ToLower().EndsWith(".dcx")); - } - else if (locator.Type == GameType.Bloodborne) - { - newBank.LoadParamsBBSekiroFromFile(path); - } - else if (locator.Type == GameType.DarkSoulsIISOTFS) - { - List looseParams = GetLooseParamsInDir(looseDir); - newBank.LoadParamsDS2FromFile(looseParams, path, enemyPath, settings.UseLooseParams); - } - else if (locator.Type == GameType.DarkSoulsRemastered) - { - newBank.LoadParamsDS1RFromFile(path); - } - else if (locator.Type == GameType.DarkSoulsPTDE) - { - newBank.LoadParamsDS1FromFile(path); - } - else if (locator.Type == GameType.DemonsSouls) - { - newBank.LoadParamsDESFromFile(path); - } + // skip the meme and just treat as project + Project siblingVirtualProject = new Project(dir, Locator.ActiveProject.ParentProject, settings); + ParamBank newBank = siblingVirtualProject.ParamBank; + + newBank.LoadParams(); - newBank.ClearParamDiffCaches(); - newBank.IsLoadingParams = false; newBank.RefreshParamDiffCaches(true); - AuxBanks[Path.GetFileName(Path.GetDirectoryName(path)).Replace(' ', '_')] = newBank; + AuxBanks[Path.GetFileName(dir).Replace(' ', '_')] = newBank; } @@ -1411,31 +997,17 @@ private static bool IsChanged(Param.Row row, ReadOnlySpan vanillaRows return true; } - - public void SetAssetLocator(AssetLocator l) - { - AssetLocator = l; - //ReloadParams(); - } - private void SaveParamsDS1() { - var dir = AssetLocator.GameRootDirectory; - var mod = AssetLocator.GameModDirectory; - if (!File.Exists($@"{dir}\\param\GameParam\GameParam.parambnd")) + var dir = Project.ParentProject.AssetLocator.RootDirectory; + var mod = Project.AssetLocator.RootDirectory; + var param = Project.AssetLocator.GetAssetPath($@"param\GameParam\GameParam.parambnd"); + if (param == null) { TaskLogs.AddLog("Cannot locate param files. Save failed.", LogLevel.Error, TaskLogs.LogPriority.High); return; } - - // Load params - var param = $@"{mod}\param\GameParam\GameParam.parambnd"; - if (!File.Exists(param)) - { - param = $@"{dir}\param\GameParam\GameParam.parambnd"; - } - using BND3 paramBnd = BND3.Read(param); // Replace params with edited ones @@ -1450,43 +1022,32 @@ private void SaveParamsDS1() Utils.WriteWithBackup(dir, mod, @"param\GameParam\GameParam.parambnd", paramBnd); // Drawparam - if (Directory.Exists($@"{AssetLocator.GameRootDirectory}\param\DrawParam")) + foreach (var bnd in Project.AssetLocator.GetAllAssets($@"param\DrawParam", [$@"*.parambnd"])) { - foreach (var bnd in Directory.GetFiles($@"{AssetLocator.GameRootDirectory}\param\DrawParam", - "*.parambnd")) + using BND3 drawParamBnd = BND3.Read(bnd); + foreach (BinderFile p in drawParamBnd.Files) { - using BND3 drawParamBnd = BND3.Read(bnd); - foreach (BinderFile p in drawParamBnd.Files) + if (_params.ContainsKey(Path.GetFileNameWithoutExtension(p.Name))) { - if (_params.ContainsKey(Path.GetFileNameWithoutExtension(p.Name))) - { - p.Bytes = _params[Path.GetFileNameWithoutExtension(p.Name)].Write(); - } + p.Bytes = _params[Path.GetFileNameWithoutExtension(p.Name)].Write(); } - - Utils.WriteWithBackup(dir, mod, @$"param\DrawParam\{Path.GetFileName(bnd)}", drawParamBnd); } + + Utils.WriteWithBackup(dir, mod, @$"param\DrawParam\{Path.GetFileName(bnd)}", drawParamBnd); } } private void SaveParamsDS1R() { - var dir = AssetLocator.GameRootDirectory; - var mod = AssetLocator.GameModDirectory; - if (!File.Exists($@"{dir}\\param\GameParam\GameParam.parambnd.dcx")) + var dir = Project.ParentProject.AssetLocator.RootDirectory; + var mod = Project.AssetLocator.RootDirectory; + var param = Project.AssetLocator.GetAssetPath($@"param\GameParam\GameParam.parambnd.dcx"); + if (param == null) { TaskLogs.AddLog("Cannot locate param files. Save failed.", LogLevel.Error, TaskLogs.LogPriority.High); return; } - - // Load params - var param = $@"{mod}\param\GameParam\GameParam.parambnd.dcx"; - if (!File.Exists(param)) - { - param = $@"{dir}\param\GameParam\GameParam.parambnd.dcx"; - } - using BND3 paramBnd = BND3.Read(param); // Replace params with edited ones @@ -1501,30 +1062,27 @@ private void SaveParamsDS1R() Utils.WriteWithBackup(dir, mod, @"param\GameParam\GameParam.parambnd.dcx", paramBnd); // Drawparam - if (Directory.Exists($@"{AssetLocator.GameRootDirectory}\param\DrawParam")) + foreach (var bnd in Project.AssetLocator.GetAllAssets($@"param\DrawParam", ["*.parambnd.dcx"])) { - foreach (var bnd in Directory.GetFiles($@"{AssetLocator.GameRootDirectory}\param\DrawParam", - "*.parambnd.dcx")) + using BND3 drawParamBnd = BND3.Read(bnd); + foreach (BinderFile p in drawParamBnd.Files) { - using BND3 drawParamBnd = BND3.Read(bnd); - foreach (BinderFile p in drawParamBnd.Files) + if (_params.ContainsKey(Path.GetFileNameWithoutExtension(p.Name))) { - if (_params.ContainsKey(Path.GetFileNameWithoutExtension(p.Name))) - { - p.Bytes = _params[Path.GetFileNameWithoutExtension(p.Name)].Write(); - } + p.Bytes = _params[Path.GetFileNameWithoutExtension(p.Name)].Write(); } - - Utils.WriteWithBackup(dir, mod, @$"param\DrawParam\{Path.GetFileName(bnd)}", drawParamBnd); } + + Utils.WriteWithBackup(dir, mod, @$"param\DrawParam\{Path.GetFileName(bnd)}", drawParamBnd); } } private void SaveParamsDS2(bool loose) { - var dir = AssetLocator.GameRootDirectory; - var mod = AssetLocator.GameModDirectory; - if (!File.Exists($@"{dir}\enc_regulation.bnd.dcx")) + var dir = Project.ParentProject.AssetLocator.RootDirectory; + var mod = Project.AssetLocator.RootDirectory; + var param = Project.AssetLocator.GetAssetPath($@"enc_regulation.bnd.dcx"); + if (param == null) { TaskLogs.AddLog("Cannot locate param files. Save failed.", LogLevel.Error, TaskLogs.LogPriority.High); @@ -1532,31 +1090,20 @@ private void SaveParamsDS2(bool loose) } // Load params - var param = $@"{mod}\enc_regulation.bnd.dcx"; BND4 paramBnd; - if (!File.Exists(param)) + if (!BND4.Is(param)) { - // If there is no mod file, check the base file. Decrypt it if you have to. - param = $@"{dir}\enc_regulation.bnd.dcx"; - if (!BND4.Is($@"{dir}\enc_regulation.bnd.dcx")) - { - // Decrypt the file - paramBnd = SFUtil.DecryptDS2Regulation(param); + // Decrypt the file + paramBnd = SFUtil.DecryptDS2Regulation(param); - // Since the file is encrypted, check for a backup. If it has none, then make one and write a decrypted one. - if (!File.Exists($@"{param}.bak")) - { - File.Copy(param, $@"{param}.bak", true); - paramBnd.Write(param); - } - } - // No need to decrypt - else + // Since the file is encrypted, check for a backup. If it has none, then make one and write a decrypted one. + if (!File.Exists($@"{param}.bak")) { - paramBnd = BND4.Read(param); + File.Copy(param, $@"{param}.bak", true); + paramBnd.Write(param); } } - // Mod file exists, use that. + // No need to decrypt else { paramBnd = BND4.Read(param); @@ -1665,22 +1212,28 @@ private void SaveParamsDS2(bool loose) private void SaveParamsDS3(bool loose) { - var dir = AssetLocator.GameRootDirectory; - var mod = AssetLocator.GameModDirectory; - if (!File.Exists($@"{dir}\Data0.bdt")) + var dir = Project.ParentProject.AssetLocator.RootDirectory; + var mod = Project.AssetLocator.RootDirectory; + string param; + bool looseFile; + if (loose) + { + var p = Project.AssetLocator.GetAssetPathFromOptions([$@"param\gameparam\gameparam_dlc2.parambnd.dcx", $@"Data0.bdt"]); + looseFile = p.Item1 == 0; + param = p.Item2; + } + else + { + var p = Project.AssetLocator.GetAssetPathFromOptions([$@"Data0.bdt", $@"param\gameparam\gameparam_dlc2.parambnd.dcx"]); + looseFile = p.Item1 != 1; + param = p.Item2; + } + if (param == null) { TaskLogs.AddLog("Cannot locate param files. Save failed.", LogLevel.Error, TaskLogs.LogPriority.High); return; } - - // Load params - var param = $@"{mod}\Data0.bdt"; - if (!File.Exists(param)) - { - param = $@"{dir}\Data0.bdt"; - } - BND4 paramBnd = SFUtil.DecryptDS3Regulation(param); // Replace params with edited ones @@ -1712,42 +1265,21 @@ private void SaveParamsDS3(bool loose) Unicode = true, Files = paramBnd.Files.Where(f => f.Name.EndsWith(".param")).ToList() }; - - /*BND4 stayBND = new BND4 - { - BigEndian = false, - Compression = DCX.Type.DCX_DFLT_10000_44_9, - Extended = 0x04, - Unk04 = false, - Unk05 = false, - Format = Binder.Format.Compression | Binder.Format.Flag6 | Binder.Format.LongOffsets | Binder.Format.Names1, - Unicode = true, - Files = paramBnd.Files.Where(f => f.Name.EndsWith(".stayparam")).ToList() - };*/ - Utils.WriteWithBackup(dir, mod, @"param\gameparam\gameparam_dlc2.parambnd.dcx", paramBND); - //Utils.WriteWithBackup(dir, mod, @"param\stayparam\stayparam.parambnd.dcx", stayBND); } } private void SaveParamsBBSekiro() { - var dir = AssetLocator.GameRootDirectory; - var mod = AssetLocator.GameModDirectory; - if (!File.Exists($@"{dir}\\param\gameparam\gameparam.parambnd.dcx")) + var dir = Project.ParentProject.AssetLocator.RootDirectory; + var mod = Project.AssetLocator.RootDirectory; + var param = Project.AssetLocator.GetAssetPath($@"param\gameparam\gameparam.parambnd.dcx"); + if (param == null) { TaskLogs.AddLog("Cannot locate param files. Save failed.", LogLevel.Error, TaskLogs.LogPriority.High); return; } - - // Load params - var param = $@"{mod}\param\gameparam\gameparam.parambnd.dcx"; - if (!File.Exists(param)) - { - param = $@"{dir}\param\gameparam\gameparam.parambnd.dcx"; - } - BND4 paramBnd = BND4.Read(param); // Replace params with edited ones @@ -1764,23 +1296,11 @@ private void SaveParamsBBSekiro() private void SaveParamsDES() { - var dir = AssetLocator.GameRootDirectory; - var mod = AssetLocator.GameModDirectory; + var dir = Project.ParentProject.AssetLocator.RootDirectory; + var mod = Project.AssetLocator.RootDirectory; + var param = Project.ParentProject.AssetLocator.GetAssetPathFromOptions([@$"\param\gameparam\gameparamna.parambnd.dcx", @$"\param\gameparam\gameparamna.parambnd", @$"\param\gameparam\gameparam.parambnd.dcx", @$"\param\gameparam\gameparam.parambnd"]).Item2; - var paramBinderName = GetDesGameparamName(mod); - if (paramBinderName == "") - { - paramBinderName = GetDesGameparamName(dir); - } - - // Load params - var param = $@"{mod}\param\gameparam\{paramBinderName}"; - if (!File.Exists(param)) - { - param = $@"{dir}\param\gameparam\{paramBinderName}"; - } - - if (!File.Exists(param)) + if (param == null) { TaskLogs.AddLog("Cannot locate param files. Save failed.", LogLevel.Error, TaskLogs.LogPriority.High); @@ -1820,39 +1340,23 @@ private void SaveParamsDES() Utils.WriteWithBackup(dir, mod, @"param\gameparam\gameparam.parambnd", paramBnd); // Drawparam - List drawParambndPaths = new(); - if (Directory.Exists($@"{AssetLocator.GameRootDirectory}\param\drawparam")) + var drawparambnds = Project.AssetLocator.GetAllAssets($@"param\drawparam", ["*.parambnd.dcx", "*.parambnd"]); + foreach (var bnd in drawparambnds) { - foreach (var bnd in Directory.GetFiles($@"{AssetLocator.GameRootDirectory}\param\drawparam", - "*.parambnd.dcx")) - { - drawParambndPaths.Add(bnd); - } - - // Also save decompressed parambnds because DeS debug uses them. - foreach (var bnd in Directory.GetFiles($@"{AssetLocator.GameRootDirectory}\param\drawparam", - "*.parambnd")) + using BND3 drawParamBnd = BND3.Read(bnd); + foreach (BinderFile p in drawParamBnd.Files) { - drawParambndPaths.Add(bnd); - } - - foreach (var bnd in drawParambndPaths) - { - using BND3 drawParamBnd = BND3.Read(bnd); - foreach (BinderFile p in drawParamBnd.Files) + if (_params.ContainsKey(Path.GetFileNameWithoutExtension(p.Name))) { - if (_params.ContainsKey(Path.GetFileNameWithoutExtension(p.Name))) - { - p.Bytes = _params[Path.GetFileNameWithoutExtension(p.Name)].Write(); - } + p.Bytes = _params[Path.GetFileNameWithoutExtension(p.Name)].Write(); } - - Utils.WriteWithBackup(dir, mod, @$"param\drawparam\{Path.GetFileName(bnd)}", drawParamBnd); } + + Utils.WriteWithBackup(dir, mod, @$"param\drawparam\{Path.GetFileName(bnd)}", drawParamBnd); } } - private void SaveParamsER(bool partial) + private void SaveParamsER() { void OverwriteParamsER(BND4 paramBnd) { @@ -1862,59 +1366,34 @@ void OverwriteParamsER(BND4 paramBnd) if (_params.ContainsKey(Path.GetFileNameWithoutExtension(p.Name))) { Param paramFile = _params[Path.GetFileNameWithoutExtension(p.Name)]; - IReadOnlyList backup = paramFile.Rows; - List changed = new(); - if (partial) - { - TaskManager.WaitAll(); //wait on dirtycache update - HashSet dirtyCache = _vanillaDiffCache[Path.GetFileNameWithoutExtension(p.Name)]; - foreach (Param.Row row in paramFile.Rows) - { - if (dirtyCache.Contains(row.ID)) - { - changed.Add(row); - } - } - - paramFile.Rows = changed; - } - p.Bytes = paramFile.Write(); - paramFile.Rows = backup; } } } - var dir = AssetLocator.GameRootDirectory; - var mod = AssetLocator.GameModDirectory; - if (!File.Exists($@"{dir}\\regulation.bin")) + var dir = Project.ParentProject.AssetLocator.RootDirectory; + var mod = Project.AssetLocator.RootDirectory; + var param = Project.AssetLocator.GetAssetPath($@"regulation.bin"); + if (param == null) { TaskLogs.AddLog("Cannot locate param files. Save failed.", LogLevel.Error, TaskLogs.LogPriority.High); return; } - - // Load params - var param = $@"{mod}\regulation.bin"; - if (!File.Exists(param) || _pendingUpgrade) - { - param = $@"{dir}\regulation.bin"; - } - BND4 regParams = SFUtil.DecryptERRegulation(param); OverwriteParamsER(regParams); Utils.WriteWithBackup(dir, mod, @"regulation.bin", regParams, GameType.EldenRing); - string sysParam = AssetLocator.GetAssetPath(@"param\systemparam\systemparam.parambnd.dcx"); - if (File.Exists(sysParam)) + string sysParam = Project.AssetLocator.GetAssetPath(@"param\systemparam\systemparam.parambnd.dcx"); + if (sysParam != null) { using BND4 sysParams = BND4.Read(sysParam); OverwriteParamsER(sysParams); Utils.WriteWithBackup(dir, mod, @"param\systemparam\systemparam.parambnd.dcx", sysParams); } - var eventParam = AssetLocator.GetAssetPath(@"param\eventparam\eventparam.parambnd.dcx"); - if (File.Exists(eventParam)) + var eventParam = Project.AssetLocator.GetAssetPath(@"param\eventparam\eventparam.parambnd.dcx"); + if (sysParam != null) { using var eventParams = BND4.Read(eventParam); OverwriteParamsER(eventParams); @@ -1935,7 +1414,7 @@ void OverwriteParamsAC6(BND4 paramBnd) if (_params.TryGetValue(paramName, out Param paramFile)) { IReadOnlyList backup = paramFile.Rows; - if (AssetLocator.Type is GameType.ArmoredCoreVI) + if (Project.Type is GameType.ArmoredCoreVI) { if (_usedTentativeParamTypes.TryGetValue(paramName, out var oldParamType)) { @@ -1957,44 +1436,37 @@ void OverwriteParamsAC6(BND4 paramBnd) } } - var dir = AssetLocator.GameRootDirectory; - var mod = AssetLocator.GameModDirectory; - if (!File.Exists($@"{dir}\\regulation.bin")) + var dir = Project.ParentProject.AssetLocator.RootDirectory; + var mod = Project.AssetLocator.RootDirectory; + var param = Project.AssetLocator.GetAssetPath($@"regulation.bin"); + if (param == null) { TaskLogs.AddLog("Cannot locate param files. Save failed.", LogLevel.Error, TaskLogs.LogPriority.High); return; } - - // Load params - var param = $@"{mod}\regulation.bin"; - if (!File.Exists(param) || _pendingUpgrade) - { - param = $@"{dir}\regulation.bin"; - } - BND4 regParams = SFUtil.DecryptAC6Regulation(param); OverwriteParamsAC6(regParams); Utils.WriteWithBackup(dir, mod, @"regulation.bin", regParams, GameType.ArmoredCoreVI); - string sysParam = AssetLocator.GetAssetPath(@"param\systemparam\systemparam.parambnd.dcx"); - if (File.Exists(sysParam)) + string sysParam = Project.AssetLocator.GetAssetPath(@"param\systemparam\systemparam.parambnd.dcx"); + if (sysParam != null) { using BND4 sysParams = BND4.Read(sysParam); OverwriteParamsAC6(sysParams); Utils.WriteWithBackup(dir, mod, @"param\systemparam\systemparam.parambnd.dcx", sysParams); } - string graphicsConfigParam = AssetLocator.GetAssetPath(@"param\graphicsconfig\graphicsconfig.parambnd.dcx"); - if (File.Exists(graphicsConfigParam)) + string graphicsConfigParam = Project.AssetLocator.GetAssetPath(@"param\graphicsconfig\graphicsconfig.parambnd.dcx"); + if (graphicsConfigParam != null) { using BND4 graphicsConfigParams = BND4.Read(graphicsConfigParam); OverwriteParamsAC6(graphicsConfigParams); Utils.WriteWithBackup(dir, mod, @"param\graphicsconfig\graphicsconfig.parambnd.dcx", graphicsConfigParams); } - string eventParam = AssetLocator.GetAssetPath(@"param\eventparam\eventparam.parambnd.dcx"); - if (File.Exists(eventParam)) + string eventParam = Project.AssetLocator.GetAssetPath(@"param\eventparam\eventparam.parambnd.dcx"); + if (eventParam != null) { using BND4 eventParams = BND4.Read(eventParam); OverwriteParamsAC6(eventParams); @@ -2004,49 +1476,49 @@ void OverwriteParamsAC6(BND4 paramBnd) _pendingUpgrade = false; } - public void SaveParams(bool loose = false, bool partialParams = false) + public void SaveParams(bool loose = false) { if (_params == null) { return; } - if (AssetLocator.Type == GameType.DarkSoulsPTDE) + if (Project.Type == GameType.DarkSoulsPTDE) { SaveParamsDS1(); } - if (AssetLocator.Type == GameType.DarkSoulsRemastered) + if (Project.Type == GameType.DarkSoulsRemastered) { SaveParamsDS1R(); } - if (AssetLocator.Type == GameType.DemonsSouls) + if (Project.Type == GameType.DemonsSouls) { SaveParamsDES(); } - if (AssetLocator.Type == GameType.DarkSoulsIISOTFS) + if (Project.Type == GameType.DarkSoulsIISOTFS) { SaveParamsDS2(loose); } - if (AssetLocator.Type == GameType.DarkSoulsIII) + if (Project.Type == GameType.DarkSoulsIII) { SaveParamsDS3(loose); } - if (AssetLocator.Type == GameType.Bloodborne || AssetLocator.Type == GameType.Sekiro) + if (Project.Type == GameType.Bloodborne || Project.Type == GameType.Sekiro) { SaveParamsBBSekiro(); } - if (AssetLocator.Type == GameType.EldenRing) + if (Project.Type == GameType.EldenRing) { - SaveParamsER(partialParams); + SaveParamsER(); } - if (AssetLocator.Type == GameType.ArmoredCoreVI) + if (Project.Type == GameType.ArmoredCoreVI) { SaveParamsAC6(); } @@ -2319,23 +1791,23 @@ public ParamUpgradeResult UpgradeRegulation(ParamBank vanillaBank, string oldVan } // Backup modded params - string modRegulationPath = $@"{AssetLocator.GameModDirectory}\regulation.bin"; + string modRegulationPath = $@"{Project.AssetLocator.RootDirectory}\regulation.bin"; File.Copy(modRegulationPath, $@"{modRegulationPath}.upgrade.bak", true); // Load old vanilla regulation BND4 oldVanillaParamBnd; - if (AssetLocator.Type == GameType.EldenRing) + if (Project.Type == GameType.EldenRing) { oldVanillaParamBnd = SFUtil.DecryptERRegulation(oldVanillaParamPath); } - else if (AssetLocator.Type == GameType.ArmoredCoreVI) + else if (Project.Type == GameType.ArmoredCoreVI) { oldVanillaParamBnd = SFUtil.DecryptAC6Regulation(oldVanillaParamPath); } else { throw new NotImplementedException( - $"Param upgrading for game type {AssetLocator.Type} is not supported."); + $"Param upgrading for game type {Project.Type} is not supported."); } Dictionary oldVanillaParams = new(); @@ -2466,7 +1938,7 @@ private void LoadExternalRowNames() var failCount = 0; foreach (KeyValuePair p in _params) { - var path = AssetLocator.GetStrippedRowNamesPath(p.Key); + var path = Project.AssetLocator.GetStrippedRowNamesPath(p.Key); if (File.Exists(path)) { var names = File.ReadAllLines(path); @@ -2510,7 +1982,7 @@ private void StripRowNames() r.Name = ""; } - var path = AssetLocator.GetStrippedRowNamesPath(p.Key); + var path = Project.AssetLocator.GetStrippedRowNamesPath(p.Key); Directory.CreateDirectory(Path.GetDirectoryName(path)); File.WriteAllLines(path, list); } diff --git a/src/StudioCore/ParamEditor/ParamEditorScreen.cs b/src/StudioCore/ParamEditor/ParamEditorScreen.cs index 3b5bb6775..5a727b97b 100644 --- a/src/StudioCore/ParamEditor/ParamEditorScreen.cs +++ b/src/StudioCore/ParamEditor/ParamEditorScreen.cs @@ -153,9 +153,9 @@ public void DecorateContextMenuItems(Param.Row row) private void PopulateDecorator() { - if (_entryCache.Count == 0 && FMGBank.IsLoaded) + if (_entryCache.Count == 0 && Locator.ActiveProject.FMGBank.IsLoaded) { - List fmgEntries = FMGBank.GetFmgEntriesByCategory(_category, false); + List fmgEntries = Locator.ActiveProject.FMGBank.GetFmgEntriesByCategory(_category, false); foreach (FMG.Entry fmgEntry in fmgEntries) { _entryCache[fmgEntry.ID] = fmgEntry; @@ -168,8 +168,6 @@ public class ParamEditorScreen : EditorScreen { public static bool EditorMode; - public readonly AssetLocator AssetLocator; - /// /// Whitelist of games and maximum param version to allow param upgrading. /// Used to restrict upgrading before DSMS properly supports it. @@ -216,9 +214,8 @@ public class ParamEditorScreen : EditorScreen public List<(ulong, string, string)> ParamUpgradeEdits; public ulong ParamUpgradeVersionSoftWhitelist; - public ParamEditorScreen(Sdl2Window window, GraphicsDevice device, AssetLocator locator) + public ParamEditorScreen(Sdl2Window window, GraphicsDevice device) { - AssetLocator = locator; _views = new List(); _views.Add(new ParamEditorView(this, 0)); _activeView = _views[0]; @@ -286,7 +283,7 @@ public void DrawEditorMenu() { if (ImGui.Selectable("Open Scripts Folder")) { - Process.Start("explorer.exe", AssetLocator.GetScriptAssetsDir()); + Process.Start("explorer.exe", Locator.AssetLocator.GetScriptAssetsDir()); } if (ImGui.Selectable("Reload Scripts")) @@ -625,23 +622,23 @@ void ImportRowNames(bool currentParamOnly, string title) if (ImGui.MenuItem("Current Param", KeyBindings.Current.Param_HotReload.HintText, false, canHotReload && _activeView._selection.GetActiveParam() != null)) { - ParamReloader.ReloadMemoryParam(ParamBank.PrimaryBank, ParamBank.PrimaryBank.AssetLocator, + ParamReloader.ReloadMemoryParam(ParamBank.PrimaryBank, Locator.AssetLocator, _activeView._selection.GetActiveParam()); } if (ImGui.MenuItem("All Params", KeyBindings.Current.Param_HotReloadAll.HintText, false, canHotReload)) { - ParamReloader.ReloadMemoryParams(ParamBank.PrimaryBank, ParamBank.PrimaryBank.AssetLocator, + ParamReloader.ReloadMemoryParams(ParamBank.PrimaryBank, Locator.AssetLocator, ParamBank.PrimaryBank.Params.Keys.ToArray()); } - foreach (var param in ParamReloader.GetReloadableParams(ParamBank.PrimaryBank.AssetLocator)) + foreach (var param in ParamReloader.GetReloadableParams(Locator.AssetLocator)) { if (ImGui.MenuItem(param, "", false, canHotReload)) { ParamReloader.ReloadMemoryParams(ParamBank.PrimaryBank, - ParamBank.PrimaryBank.AssetLocator, new[] { param }); + Locator.AssetLocator, new[] { param }); } } } @@ -652,7 +649,7 @@ void ImportRowNames(bool currentParamOnly, string title) var activeParam = _activeView._selection.GetActiveParam(); if (activeParam != null && _projectSettings.GameType == GameType.DarkSoulsIII) { - ParamReloader.GiveItemMenu(ParamBank.PrimaryBank.AssetLocator, + ParamReloader.GiveItemMenu(Locator.AssetLocator, _activeView._selection.GetSelectedRows(), _activeView._selection.GetActiveParam()); } @@ -683,55 +680,30 @@ void ImportRowNames(bool currentParamOnly, string title) // Only support ER for now if (ImGui.MenuItem("Load Params for comparison...", null, false)) { - string[] allParamTypes = - { - AssetLocator.RegulationBinFilter, AssetLocator.Data0Filter, AssetLocator.ParamBndDcxFilter, - AssetLocator.ParamBndFilter, AssetLocator.EncRegulationFilter - }; try { - if (_projectSettings.GameType != GameType.DarkSoulsIISOTFS) + // NativeFileDialog doesn't show the title currently, so manual dialogs are required for now. + var res = PlatformUtils.Instance.MessageBox( + "To compare params, you can select the mod/project folder containing them, or the project.json in that folder.\n" + + "If you load from folder, Project settings such as usage of looseParams will be inferred from your current project.\n" + + "If you are selecting a folder and have loose params, ensure you select the mod/project folder that CONTAINS the param folder.\n\n" + + "Would you like to select a folder?", + "Select folder?", + MessageBoxButtons.YesNo, + MessageBoxIcon.Information); + if (res == DialogResult.Yes) { - if (PlatformUtils.Instance.OpenFileDialog("Select file containing params", allParamTypes, - out var path)) + if (PlatformUtils.Instance.OpenFolderDialog("Select mod or project folder", + out var folder)) { - ParamBank.LoadAuxBank(path, null, null, _projectSettings); + ParamBank.LoadAuxBank(folder); } } - else + else if (res == DialogResult.No) { - // NativeFileDialog doesn't show the title currently, so manual dialogs are required for now. - PlatformUtils.Instance.MessageBox( - "To compare DS2 params, select the file locations of alternative params, including\n" + - "the loose params folder, the non-loose parambnd or regulation, and the loose enemy param.\n\n" + - "First, select the loose params folder.", - "Select loose params", - MessageBoxButtons.OK, - MessageBoxIcon.Information); - if (PlatformUtils.Instance.OpenFolderDialog("Select folder for looseparams", - out var folder)) + if (PlatformUtils.Instance.OpenFileDialog("Select project.json", [AssetUtils.ProjectJsonFilter], out var file)) { - PlatformUtils.Instance.MessageBox( - "Second, select the non-loose parambnd or regulation", - "Select regulation", - MessageBoxButtons.OK, - MessageBoxIcon.Information); - if (PlatformUtils.Instance.OpenFileDialog( - "Select file containing remaining, non-loose params", allParamTypes, - out var path)) - { - PlatformUtils.Instance.MessageBox( - "Finally, select the file containing enemyparam", - "Select enemyparam", - MessageBoxButtons.OK, - MessageBoxIcon.Information); - if (PlatformUtils.Instance.OpenFileDialog( - "Select file containing enemyparam", - new[] { AssetLocator.ParamLooseFilter }, out var enemyPath)) - { - ParamBank.LoadAuxBank(path, folder, enemyPath, _projectSettings); - } - } + ParamBank.LoadAuxBank(Path.GetDirectoryName(file), ProjectSettings.Deserialize(file)); } } } @@ -910,13 +882,13 @@ public unsafe void OnGUI(string[] initcmd) { if (InputTracker.GetKeyDown(KeyBindings.Current.Param_HotReloadAll)) { - ParamReloader.ReloadMemoryParams(ParamBank.PrimaryBank, ParamBank.PrimaryBank.AssetLocator, + ParamReloader.ReloadMemoryParams(ParamBank.PrimaryBank, Locator.AssetLocator, ParamBank.PrimaryBank.Params.Keys.ToArray()); } else if (InputTracker.GetKeyDown(KeyBindings.Current.Param_HotReload) && _activeView._selection.GetActiveParam() != null) { - ParamReloader.ReloadMemoryParam(ParamBank.PrimaryBank, ParamBank.PrimaryBank.AssetLocator, + ParamReloader.ReloadMemoryParam(ParamBank.PrimaryBank, Locator.AssetLocator, _activeView._selection.GetActiveParam()); } } @@ -1153,7 +1125,7 @@ public void Save() { if (_projectSettings != null) { - ParamBank.PrimaryBank.SaveParams(_projectSettings.UseLooseParams, _projectSettings.PartialParams); + ParamBank.PrimaryBank.SaveParams(_projectSettings.UseLooseParams); TaskLogs.AddLog("Saved params"); } } @@ -1175,7 +1147,7 @@ public void SaveAll() { if (_projectSettings != null) { - ParamBank.PrimaryBank.SaveParams(_projectSettings.UseLooseParams, _projectSettings.PartialParams); + ParamBank.PrimaryBank.SaveParams(_projectSettings.UseLooseParams); TaskLogs.AddLog("Saved params"); } } @@ -1208,9 +1180,9 @@ private void LoadUpgraderData() ParamUpgradeEdits = null; try { - var baseDir = AssetLocator.GetUpgraderAssetsDir(); - var wlFile = Path.Join(AssetLocator.GetUpgraderAssetsDir(), "version.txt"); - var massEditFile = Path.Join(AssetLocator.GetUpgraderAssetsDir(), "massedit.txt"); + var baseDir = Locator.AssetLocator.GetUpgraderAssetsDir(); + var wlFile = Path.Join(Locator.AssetLocator.GetUpgraderAssetsDir(), "version.txt"); + var massEditFile = Path.Join(Locator.AssetLocator.GetUpgraderAssetsDir(), "massedit.txt"); if (!File.Exists(wlFile) || !File.Exists(massEditFile)) { return; @@ -1244,10 +1216,10 @@ private void LoadUpgraderData() private void ParamUpgradeDisplay() { - if (ParamBank.IsDefsLoaded + if (Locator.ActiveProject != null && Locator.ActiveProject.ParamBank.IsDefsLoaded && ParamBank.PrimaryBank.Params != null && ParamBank.VanillaBank.Params != null - && ParamUpgrade_SupportedGames.Contains(ParamBank.PrimaryBank.AssetLocator.Type) + && ParamUpgrade_SupportedGames.Contains(Locator.AssetLocator.Type) && !ParamBank.PrimaryBank.IsLoadingParams && !ParamBank.VanillaBank.IsLoadingParams && ParamBank.PrimaryBank.ParamVersion < ParamBank.VanillaBank.ParamVersion) @@ -1284,7 +1256,7 @@ private void ParamUpgradeDisplay() { if (PlatformUtils.Instance.OpenFileDialog( $"Select regulation.bin for game version {ParamBank.PrimaryBank.ParamVersion}...", - new[] { AssetLocator.RegulationBinFilter }, + new[] { AssetUtils.RegulationBinFilter }, out var path)) { UpgradeRegulation(ParamBank.PrimaryBank, ParamBank.VanillaBank, path); @@ -1352,7 +1324,7 @@ public void UpgradeRegulation(ParamBank bank, ParamBank vanillaBank, string oldR if (result == ParamBank.ParamUpgradeResult.RowConflictsFound) { // If there's row conflicts write a conflict log - var logPath = $@"{bank.AssetLocator.GameModDirectory}\regulationUpgradeLog.txt"; + var logPath = $@"{Locator.AssetLocator.GameModDirectory}\regulationUpgradeLog.txt"; if (File.Exists(logPath)) { File.Delete(logPath); @@ -2069,7 +2041,7 @@ public int CountViews() private static bool SaveCsvDialog(out string path) { var result = PlatformUtils.Instance.SaveFileDialog( - "Choose CSV file", new[] { AssetLocator.CsvFilter, AssetLocator.TxtFilter }, out path); + "Choose CSV file", new[] { AssetUtils.CsvFilter, AssetUtils.TxtFilter }, out path); if (result && !path.ToLower().EndsWith(".csv")) path += ".csv"; @@ -2080,7 +2052,7 @@ private static bool SaveCsvDialog(out string path) private static bool OpenCsvDialog(out string path) { return PlatformUtils.Instance.OpenFileDialog( - "Choose CSV file", new[] { AssetLocator.CsvFilter, AssetLocator.TxtFilter }, out path); + "Choose CSV file", new[] { AssetUtils.CsvFilter, AssetUtils.TxtFilter }, out path); } private static bool ReadCsvDialog(out string csv) diff --git a/src/StudioCore/ParamEditor/ParamEditorView.cs b/src/StudioCore/ParamEditor/ParamEditorView.cs index 67e9d7066..41f3d559b 100644 --- a/src/StudioCore/ParamEditor/ParamEditorView.cs +++ b/src/StudioCore/ParamEditor/ParamEditorView.cs @@ -84,7 +84,7 @@ private void ParamView_ParamList_Header(bool isActiveView) lastParamSearch = _selection.currentParamSearchString; } - if (ParamBank.PrimaryBank.AssetLocator.Type is GameType.DemonsSouls + if (Locator.AssetLocator.Type is GameType.DemonsSouls or GameType.DarkSoulsPTDE or GameType.DarkSoulsRemastered) { @@ -96,7 +96,7 @@ or GameType.DarkSoulsPTDE ImGui.Separator(); } - else if (ParamBank.PrimaryBank.AssetLocator.Type is GameType.DarkSoulsIISOTFS) + else if (Locator.AssetLocator.Type is GameType.DarkSoulsIISOTFS) { // DS2 has map params, add UI element to toggle viewing map params and GameParams. if (ImGui.Checkbox("Edit Map Params", ref _mapParamView)) @@ -106,7 +106,7 @@ or GameType.DarkSoulsPTDE ImGui.Separator(); } - else if (ParamBank.PrimaryBank.AssetLocator.Type is GameType.EldenRing || ParamBank.PrimaryBank.AssetLocator.Type is GameType.ArmoredCoreVI) + else if (Locator.AssetLocator.Type is GameType.EldenRing || Locator.AssetLocator.Type is GameType.ArmoredCoreVI) { if (ImGui.Checkbox("Edit Event Params", ref _eventParamView)) { @@ -182,7 +182,7 @@ private void ParamView_ParamList_Main(bool doFocus, float scale, float scrollTo) List keyList = list.Where(param => param.Item1 == ParamBank.PrimaryBank) .Select(param => ParamBank.PrimaryBank.GetKeyForParam(param.Item2)).ToList(); - if (ParamBank.PrimaryBank.AssetLocator.Type is GameType.DemonsSouls + if (Locator.AssetLocator.Type is GameType.DemonsSouls or GameType.DarkSoulsPTDE or GameType.DarkSoulsRemastered) { @@ -195,7 +195,7 @@ or GameType.DarkSoulsPTDE keyList = keyList.FindAll(p => !p.EndsWith("Bank")); } } - else if (ParamBank.PrimaryBank.AssetLocator.Type is GameType.DarkSoulsIISOTFS) + else if (Locator.AssetLocator.Type is GameType.DarkSoulsIISOTFS) { if (_mapParamView) { @@ -206,7 +206,7 @@ or GameType.DarkSoulsPTDE keyList = keyList.FindAll(p => !ParamBank.DS2MapParamlist.Contains(p.Split('_')[0])); } } - else if (ParamBank.PrimaryBank.AssetLocator.Type is GameType.EldenRing) + else if (Locator.AssetLocator.Type is GameType.EldenRing) { if (_eventParamView) { @@ -226,7 +226,7 @@ or GameType.DarkSoulsPTDE keyList = keyList.FindAll(p => !p.StartsWith("Gconfig")); } } - else if (ParamBank.PrimaryBank.AssetLocator.Type is GameType.ArmoredCoreVI) + else if (Locator.AssetLocator.Type is GameType.ArmoredCoreVI) { if (_eventParamView) { diff --git a/src/StudioCore/ParamEditor/ParamMeta.cs b/src/StudioCore/ParamEditor/ParamMeta.cs index fc84eae12..363597b4f 100644 --- a/src/StudioCore/ParamEditor/ParamMeta.cs +++ b/src/StudioCore/ParamEditor/ParamMeta.cs @@ -13,7 +13,6 @@ namespace StudioCore.ParamEditor; public class ParamMetaData { private const int XML_VERSION = 0; - private static readonly Dictionary _ParamMetas = new(); private readonly string _path; internal XmlDocument _xml; @@ -21,7 +20,6 @@ public class ParamMetaData private ParamMetaData(PARAMDEF def, string path) { - Add(def, this); foreach (PARAMDEF.Field f in def.Fields) { new FieldMetaData(this, f); @@ -61,8 +59,6 @@ private ParamMetaData(XmlDocument xml, string path, PARAMDEF def) $"Mismatched XML version; current version: {XML_VERSION}, file version: {xmlVersion}"); } - Add(def, this); - XmlNode self = root.SelectSingleNode("Self"); if (self != null) { @@ -222,14 +218,8 @@ public static ParamMetaData Get(PARAMDEF def) return null; } - return _ParamMetas[def]; + return Locator.ActiveProject.ParamBank.ParamMetas[def]; } - - private static void Add(PARAMDEF key, ParamMetaData meta) - { - _ParamMetas.Add(key, meta); - } - internal static XmlNode GetXmlNode(XmlDocument xml, XmlNode parent, string child) { XmlNode node = parent.SelectSingleNode(child); @@ -382,7 +372,7 @@ public static void SaveAll() field.Value.Commit(FixName(field.Key.InternalName)); //does not handle shared names } - foreach (ParamMetaData param in _ParamMetas.Values) + foreach (ParamMetaData param in Locator.ActiveProject.ParamBank.ParamMetas.Values) { param.Commit(); param.Save(); diff --git a/src/StudioCore/ParamEditor/SearchEngine.cs b/src/StudioCore/ParamEditor/SearchEngine.cs index 594d5844e..184a1cde5 100644 --- a/src/StudioCore/ParamEditor/SearchEngine.cs +++ b/src/StudioCore/ParamEditor/SearchEngine.cs @@ -275,13 +275,8 @@ internal enum MassEditRowSource internal class ParamSearchEngine : SearchEngine { - public static ParamSearchEngine pse = new(ParamBank.PrimaryBank); - private readonly ParamBank bank; - - private ParamSearchEngine(ParamBank bank) - { - this.bank = bank; - } + public static ParamSearchEngine pse = new(); + private ParamBank bank => ParamBank.PrimaryBank; internal override void Setup() { @@ -340,13 +335,8 @@ internal override void Setup() internal class RowSearchEngine : SearchEngine<(ParamBank, Param), Param.Row> { - public static RowSearchEngine rse = new(ParamBank.PrimaryBank); - private readonly ParamBank bank; - - private RowSearchEngine(ParamBank bank) - { - this.bank = bank; - } + public static RowSearchEngine rse = new(); + private ParamBank bank => ParamBank.PrimaryBank; internal override void Setup() { @@ -535,7 +525,7 @@ internal override void Setup() throw new Exception(); } - List fmgEntries = FMGBank.GetFmgEntriesByCategory(category, false); + List fmgEntries = Locator.ActiveProject.FMGBank.GetFmgEntriesByCategory(category, false); Dictionary _cache = new(); foreach (FMG.Entry fmgEntry in fmgEntries) { @@ -766,12 +756,12 @@ internal override void Setup() category = cat; } - if (category == FmgEntryCategory.None || !FMGBank.IsLoaded) + if (category == FmgEntryCategory.None || !Locator.ActiveProject.FMGBank.IsLoaded) { return row => rx.IsMatch(row.Name ?? "") || rx.IsMatch(row.ID.ToString()); } - List fmgEntries = FMGBank.GetFmgEntriesByCategory(category, false); + List fmgEntries = Locator.ActiveProject.FMGBank.GetFmgEntriesByCategory(category, false); Dictionary _cache = new(); foreach (FMG.Entry fmgEntry in fmgEntries) { diff --git a/src/StudioCore/Project.cs b/src/StudioCore/Project.cs new file mode 100644 index 000000000..a5660aa9f --- /dev/null +++ b/src/StudioCore/Project.cs @@ -0,0 +1,103 @@ +using SoulsFormats; +using StudioCore.Editor; +using StudioCore.ParamEditor; +using StudioCore.TextEditor; +using System; +using System.Globalization; +using System.IO; +using System.Linq; + +namespace StudioCore; + +/// +/// Holds asset locator, data banks, etc +/// +public class Project +{ + public readonly ProjectSettings Settings; + + public readonly Project ParentProject; + + public readonly ProjectAssetLocator AssetLocator; + + public readonly ParamBank ParamBank; + + public readonly FMGBank FMGBank; + + + public GameType Type => Settings.GameType; + + /// + /// Creates a project based in a single folder with no parent. This is for Game or DSMS files. + /// + public Project(ProjectSettings settings) + { + Settings = settings; + AssetLocator = new(this, settings.GameRoot); + ParentProject = null; + + ParamBank = new(this); + FMGBank = new(this); + } + /// + /// Creates a project based in a folder with no explicit parent project, with a new ParentProject for the game directory. This is for a mod. + /// + public Project(ProjectSettings settings, string moddir) + { + Settings = settings; + AssetLocator = new(this, moddir); + ParentProject = new Project(settings); + + ParamBank = new(this); + FMGBank = new(this); + } + /// + /// Creates a project based in a folder with an explicit parent project. This is for an addon or fork of a mod. + /// + public Project(string moddir, Project parent, ProjectSettings settings = null) + { + if (settings == null) + { + Settings = parent.Settings.CopyAndAssumeFromModDir(moddir); + } + else + { + Settings = settings; + } + AssetLocator = new(this, moddir); + ParentProject = parent; + + ParamBank = new(this); + FMGBank = new(this); + } + + /// + /// Creates a project based on an existing one for recovery purposes. Shares resources with the original project. + /// + public Project(Project parent) + { + var time = DateTime.Now.ToString("dd-MM-yyyy-(hh-mm-ss)", CultureInfo.InvariantCulture); + Settings = parent.Settings; + AssetLocator = new(this, parent.AssetLocator.RootDirectory + $@"\recovery\{time}"); + ParentProject = parent.ParentProject; + if (!Directory.Exists(AssetLocator.RootDirectory)) + { + Directory.CreateDirectory(AssetLocator.RootDirectory); + } + + ParamBank = parent.ParamBank; + FMGBank = parent.FMGBank; + } + + public Project CreateRecoveryProject() + { + try + { + return new Project(this); + } + catch (Exception e) + { + return this; + } + } +} diff --git a/src/StudioCore/ProjectAssetLocator.cs b/src/StudioCore/ProjectAssetLocator.cs new file mode 100644 index 000000000..97ee2c398 --- /dev/null +++ b/src/StudioCore/ProjectAssetLocator.cs @@ -0,0 +1,1937 @@ +using SoulsFormats; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text.RegularExpressions; + +namespace StudioCore; + +/// +/// Exposes an interface to retrieve game assets from the various souls games. +/// +public class ProjectAssetLocator +{ + public readonly Project Project; + + public GameType Type => Project.Type; + public ProjectAssetLocator ParentAssetLocator => Project.ParentProject?.AssetLocator; + + /// + /// The root directory where all the assets are + /// + public readonly string RootDirectory; + + /// + /// Directory where misc DSMapStudio files associated with a project are stored. + /// + public string ProjectMiscDir => @$"{RootDirectory}\DSMapStudio"; + + private List MapList; + + public ProjectAssetLocator(Project owner, string dir) + { + Project = owner; + RootDirectory = dir; + } + + private string GetFileNameWithoutExtensions(string path) + { + return Path.GetFileNameWithoutExtension(Path.GetFileNameWithoutExtension(path)); + } + + public string GetAssetPath(string relpath) + { + if (File.Exists($@"{RootDirectory}\{relpath}")) + { + return $@"{RootDirectory}\{relpath}"; + } + if (ParentAssetLocator != null) + { + return ParentAssetLocator.GetAssetPath(relpath); + } + return null; + } + public string GetProjectFilePath(string relpath) + { + if (ParentAssetLocator == null) + { + return $@"Assets\{relpath}"; + } + string path = $@"{ProjectMiscDir}\{relpath}"; + if (File.Exists(path)) + { + return path; + } + else + { + return ParentAssetLocator.GetProjectFilePath(relpath); + } + } + + public IEnumerable GetProjectFileAllPaths(string relpath, List paths = null) + { + if (paths == null) + { + paths = new(); + } + if (ParentAssetLocator == null) + { + paths.Add($@"Assets\{relpath}"); + return paths; + } + string path = $@"{ProjectMiscDir}\{relpath}"; + if (File.Exists(path)) + { + paths.Add(path); + } + return ParentAssetLocator.GetProjectFileAllPaths(relpath, paths);; + } + + public (int, string) GetAssetPathFromOptions(IEnumerable relpaths) + { + int i = 0; + foreach (string relpath in relpaths) + { + var path = $@"{RootDirectory}\{relpath}"; + if (File.Exists(path)) + { + return (i, path); + } + i++; + } + if (ParentAssetLocator != null) + { + return ParentAssetLocator.GetAssetPathFromOptions(relpaths); + } + return (-1, null); + } + + public bool FileExists(string relpath) + { + if (ParentAssetLocator != null && ParentAssetLocator.FileExists(relpath)) + { + return true; + } + + if (File.Exists($@"{RootDirectory}\{relpath}")) + { + return true; + } + + return false; + } + public IEnumerable GetAllAssets(string relpath, string[] extensionPatterns, bool distinct = true, bool subDirectories = false) + { + List files = new(); + string rpath = $@"{RootDirectory}\{relpath}"; + if (Directory.Exists(rpath)) + { + foreach (string pattern in extensionPatterns) + { + files.AddRange(Directory.GetFiles(rpath, pattern, subDirectories ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly)); + } + } + if (ParentAssetLocator != null) + { + files.AddRange(ParentAssetLocator.GetAllAssets(relpath, extensionPatterns, distinct, subDirectories)); + } + if (distinct) + { + // WARNING DistinctBy doesn't guarantee preference by order, though implementations do + return files.DistinctBy(GetFileNameWithoutExtensions); + } + else + { + return files; + } + } + public IEnumerable GetAllProjectFiles(string relpath, string[] extensionPatterns, bool distinct = true, bool subDirectories = false) + { + List files = new(); + string rpath; + if (ParentAssetLocator == null) + { + rpath = $@"Assets\{relpath}"; + foreach (string pattern in extensionPatterns) + { + files.AddRange(Directory.GetFiles(rpath, pattern, subDirectories ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly)); + } + return files; + } + rpath = $@"{RootDirectory}\{ProjectMiscDir}\{relpath}"; + if (Directory.Exists(rpath)) + { + foreach (string pattern in extensionPatterns) + { + files.AddRange(Directory.GetFiles(rpath, pattern, subDirectories ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly)); + } + } + if (ParentAssetLocator != null) + { + files.AddRange(ParentAssetLocator.GetAllProjectFiles(relpath, extensionPatterns, distinct, subDirectories)); + } + if (distinct) + { + // WARNING DistinctBy doesn't guarantee preference by order, though implementations do + return files.DistinctBy(GetFileNameWithoutExtensions); + } + else + { + return files; + } + } + public IEnumerable GetAllSubDirs(string relpath, bool distinct = true, bool subDirectories = false) + { + List dirs = new(); + string rpath = $@"{RootDirectory}\{relpath}"; + if (Directory.Exists(rpath)) + { + dirs.AddRange(Directory.GetDirectories(rpath, "*", subDirectories ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly)); + } + if (ParentAssetLocator != null) + { + dirs.AddRange(ParentAssetLocator.GetAllSubDirs(relpath, subDirectories)); + } + if (distinct) + { + // WARNING DistinctBy doesn't guarantee preference by order, though implementations do + return dirs.DistinctBy(Path.GetFileName); + } + else + { + return dirs; + } + } + + /// + /// Gets the full list of maps in the game (excluding chalice dungeons). Basically if there's an msb for it, + /// it will be in this list. + /// + /// + public List GetFullMapList() + { + + if (MapList != null) + { + return MapList; + } + + try + { + HashSet mapSet = new(); + + // DS2 has its own structure for msbs, where they are all inside individual folders + if (Type == GameType.DarkSoulsIISOTFS) + { + foreach (var map in GetAllAssets(@"map", [@"*.msb"], true, true)) + { + mapSet.Add(Path.GetFileNameWithoutExtension(map)); + } + } + else + { + foreach (var msb in GetAllAssets(@"map\MapStudio\", [@"*.msb", @"*.msb.dcx"])) + { + mapSet.Add(GetFileNameWithoutExtensions(msb)); + } + } + Regex mapRegex = new(@"^m\d{2}_\d{2}_\d{2}_\d{2}$"); + List mapList = mapSet.Where(x => mapRegex.IsMatch(x)).ToList(); + mapList.Sort(); + MapList = mapList; + return MapList; + } + catch (DirectoryNotFoundException e) + { + // Game is likely not UXM unpacked + if (ParentAssetLocator != null) + { + MapList = ParentAssetLocator.GetFullMapList(); + return MapList; + } + return new List(); + } + } + + public AssetDescription GetMapMSB(string mapid, bool writemode = false) + { + AssetDescription ad = new(); + ad.AssetPath = null; + if (mapid.Length != 12) + { + return ad; + } + + string preferredPath; + string backupPath; + // SOFTS + if (Type == GameType.DarkSoulsIISOTFS) + { + preferredPath = $@"map\{mapid}\{mapid}.msb"; + backupPath = $@"map\{mapid}\{mapid}.msb"; + } + // BB chalice maps + else if (Type == GameType.Bloodborne && mapid.StartsWith("m29")) + { + preferredPath = $@"\map\MapStudio\{mapid.Substring(0, 9)}_00\{mapid}.msb.dcx"; + backupPath = $@"\map\MapStudio\{mapid.Substring(0, 9)}_00\{mapid}.msb"; + } + // DeS, DS1, DS1R + else if (Type == GameType.DarkSoulsPTDE || Type == GameType.DarkSoulsRemastered || + Type == GameType.DemonsSouls) + { + preferredPath = $@"\map\MapStudio\{mapid}.msb"; + backupPath = $@"\map\MapStudio\{mapid}.msb.dcx"; + } + // BB, DS3, ER, SSDT + else if (Type == GameType.Bloodborne || Type == GameType.DarkSoulsIII || Type == GameType.EldenRing || + Type == GameType.Sekiro) + { + preferredPath = $@"\map\MapStudio\{mapid}.msb.dcx"; + backupPath = $@"\map\MapStudio\{mapid}.msb"; + } + else + { + preferredPath = $@"\map\MapStudio\{mapid}.msb.dcx"; + backupPath = $@"\map\MapStudio\{mapid}.msb"; + } + + if (writemode) + { + ad.AssetPath = $@"{RootDirectory}\{preferredPath}"; + } + else + { + ad.AssetPath = GetAssetPathFromOptions([preferredPath, backupPath]).Item2; + } + + ad.AssetName = mapid; + return ad; + } + + public List GetMapBTLs(string mapid, bool writemode = false) + { + List adList = new(); + if (mapid.Length != 12) + { + return adList; + } + + if (Type is GameType.DarkSoulsIISOTFS) + { + // DS2 BTL is located inside map's .gibdt file + AssetDescription ad = new(); + var path = $@"model\map\g{mapid[1..]}.gibhd"; + + if (writemode) + { + ad.AssetPath = $@"{RootDirectory}\{path}"; + } + else + { + ad.AssetPath = GetAssetPath(path); + } + + if (ad.AssetPath != null) + { + ad.AssetName = $@"g{mapid[1..]}"; + ad.AssetVirtualPath = $@"{mapid}\light.btl.dcx"; + adList.Add(ad); + } + + AssetDescription ad2 = new(); + path = $@"model_lq\map\g{mapid[1..]}.gibhd"; + + if (writemode) + { + ad2.AssetPath = $@"{RootDirectory}\{path}"; + } + else + { + ad2.AssetPath = GetAssetPath(path); + } + + if (ad2.AssetPath != null) + { + ad2.AssetName = $@"g{mapid[1..]}_lq"; + ad2.AssetVirtualPath = $@"{mapid}\light.btl.dcx"; + adList.Add(ad2); + } + } + else if (Type is GameType.Bloodborne or GameType.DarkSoulsIII or GameType.Sekiro or GameType.EldenRing or GameType.ArmoredCoreVI) + { + string path; + if (Type is GameType.EldenRing or GameType.ArmoredCoreVI) + { + path = $@"map\{mapid[..3]}\{mapid}"; + } + else + { + path = $@"map\{mapid}"; + } + + List files = new(); + + files = GetAllAssets(path, ["*.btl", "*.btl.dcx"]).ToList(); + + foreach (var file in files) + { + AssetDescription ad = new(); + var fileName = file.Split("\\").Last(); + if (writemode) + { + ad.AssetPath = $@"{RootDirectory}\{path}\{fileName}"; + } + else + { + ad.AssetPath = GetAssetPath($@"{path}\{fileName}"); + } + + if (ad.AssetPath != null) + { + ad.AssetName = fileName; + adList.Add(ad); + } + } + } + + return adList; + } + public AssetDescription GetMapNVA(string mapid, bool writemode = false) + { + AssetDescription ad = new(); + ad.AssetPath = null; + if (mapid.Length != 12) + { + return ad; + } + // BB chalice maps + + if (Type == GameType.Bloodborne && mapid.StartsWith("m29")) + { + var path = $@"\map\{mapid.Substring(0, 9)}_00\{mapid}"; + if (writemode) + { + ad.AssetPath = $@"{RootDirectory}\{path}.nva.dcx"; + } + else + { + ad.AssetPath = GetAssetPath($@"{path}.nva.dcx"); + } + } + else if (Type == GameType.DarkSoulsPTDE) + { + var path = $@"\map\{mapid}\{mapid}"; + if (writemode) + { + ad.AssetPath = $@"{RootDirectory}\{path}.nva"; + } + else + { + ad.AssetPath = GetAssetPathFromOptions([$@"{path}.nva", $@"{path}.nva.dcx"]).Item2; + } + } + else + { + var path = $@"\map\{mapid}\{mapid}"; + if (writemode) + { + ad.AssetPath = $@"{RootDirectory}\{path}.nva.dcx"; + } + else + { + ad.AssetPath = GetAssetPathFromOptions([$@"{path}.nva.dcx", $@"{path}.nva"]).Item2; + } + } + + ad.AssetName = mapid; + return ad; + } + + public AssetDescription GetWorldLoadListList(bool writemode = false) + { + AssetDescription ad = new(); + + if (writemode) + { + ad.AssetPath = $@"{RootDirectory}\map\worldmsblist.worldloadlistlist.dcx"; + } + else + { + ad.AssetPath = GetAssetPath($@"map\worldmsblist.worldloadlistlist.dcx"); + } + + return ad; + } + + /// + /// Get folders with msgbnds used in-game + /// + /// Dictionary with language name and path + public Dictionary GetMsgLanguages() + { + Dictionary dict = new(); + List folders = new(); + try + { + if (Type == GameType.DemonsSouls) + { + folders = GetAllSubDirs(@"\msg").ToList(); + // Japanese uses root directory + if (FileExists(@"\msg\menu.msgbnd.dcx") || FileExists(@"\msg\item.msgbnd.dcx")) + { + dict.Add("Japanese", ""); + } + } + else if (Type == GameType.DarkSoulsIISOTFS) + { + folders = GetAllSubDirs(@"\menu\text").ToList(); + } + else + { + // Exclude folders that don't have typical msgbnds + folders = GetAllSubDirs(@"\msg") + .Where(x => !"common,as,eu,jp,na,uk,japanese".Contains(Path.GetFileName(x))).ToList(); + } + + foreach (var path in folders) + { + dict.Add(path.Split("\\").Last(), path); + } + } + catch (Exception e) when (e is DirectoryNotFoundException or FileNotFoundException) + { + } + + return dict; + } + + /// + /// Get path of item.msgbnd (english by default) + /// + public AssetDescription GetItemMsgbnd(string langFolder, bool writemode = false) + { + return GetMsgbnd("item", langFolder, writemode); + } + + /// + /// Get path of menu.msgbnd (english by default) + /// + public AssetDescription GetMenuMsgbnd(string langFolder, bool writemode = false) + { + return GetMsgbnd("menu", langFolder, writemode); + } + + public AssetDescription GetMsgbnd(string msgBndType, string langFolder, bool writemode = false) + { + AssetDescription ad = new(); + IEnumerable path; + if (Project.Type == GameType.DemonsSouls) + { + // Demon's Souls has msgbnds directly in the msg folder + path = [$@"msg\{langFolder}\{msgBndType}.msgbnd.dcx", $@"msg\{msgBndType}.msgbnd.dcx"]; + } + else if (Project.Type == GameType.DarkSoulsPTDE) + { + path = [$@"msg\{langFolder}\{msgBndType}.msgbnd"]; + } + else if (Project.Type == GameType.DarkSoulsRemastered) + { + path = [$@"msg\{langFolder}\{msgBndType}.msgbnd.dcx"]; + } + else if (Project.Type == GameType.DarkSoulsIISOTFS) + { + // DS2 does not have an msgbnd but loose fmg files instead + path = [$@"menu\text\{langFolder}"]; + } + else if (Project.Type == GameType.DarkSoulsIII) + { + path = [$@"msg\{langFolder}\{msgBndType}_dlc2.msgbnd.dcx"]; + } + else + { + path = [$@"msg\{langFolder}\{msgBndType}.msgbnd.dcx"]; + } + + if (writemode) + { + ad.AssetPath = $@"{Project.AssetLocator.RootDirectory}\{path.First()}"; + } + else + { + ad.AssetPath = Project.AssetLocator.GetAssetPathFromOptions(path).Item2; + } + + return ad; + } + + public string GetAliasAssetsDir() + { + return GetProjectFilePath($@"Aliases"); + } + + public string GetParamdexDir() + { + return $@"Paramdex\{AssetUtils.GetGameIDForDir(Type)}"; + } + + public string GetStrippedRowNamesPath(string paramName) + { + return GetProjectFilePath($@"Stripped Row Names\{paramName}.txt"); + } + + public string GetScriptAssetsCommonDir() + { + return GetProjectFilePath($@"MassEditScripts\Common"); + } + + public string GetScriptAssetsDir() + { + return GetProjectFilePath($@"MassEditScripts\{AssetUtils.GetGameIDForDir(Type)}"); + } + + public string GetUpgraderAssetsDir() + { + return GetProjectFilePath($@"{GetParamdexDir()}\Upgrader"); + } + + public string GetGameOffsetsAssetsDir() + { + return GetProjectFilePath($@"GameOffsets\{AssetUtils.GetGameIDForDir(Type)}"); + } + + public PARAMDEF GetParamdefForParam(string paramType) + { + //This is ds2 only and really ds2 should be using parambank params not its own copies + PARAMDEF pd = PARAMDEF.XmlDeserialize($@"{GetParamdexDir()}\{AssetUtils.GetGameIDForDir(Type)}\Defs\{paramType}.xml"); + return pd; + } + + public AssetDescription GetDS2GeneratorParam(string mapid, bool writemode = false) + { + AssetDescription ad = new(); + var path = $@"Param\generatorparam_{mapid}"; + if (writemode) + { + ad.AssetPath = $@"{RootDirectory}\{path}.param"; + } + else + { + ad.AssetPath = GetAssetPath($@"{path}.param"); + } + + ad.AssetName = mapid + "_generators"; + return ad; + } + + public AssetDescription GetDS2GeneratorLocationParam(string mapid, bool writemode = false) + { + AssetDescription ad = new(); + var path = $@"Param\generatorlocation_{mapid}"; + if (writemode) + { + ad.AssetPath = $@"{RootDirectory}\{path}.param"; + } + else + { + ad.AssetPath = GetAssetPath($@"{path}.param"); + } + + ad.AssetName = mapid + "_generator_locations"; + return ad; + } + + public AssetDescription GetDS2GeneratorRegistParam(string mapid, bool writemode = false) + { + AssetDescription ad = new(); + var path = $@"Param\generatorregistparam_{mapid}"; + if (writemode) + { + ad.AssetPath = $@"{RootDirectory}\{path}.param"; + } + else + { + ad.AssetPath = GetAssetPath($@"{path}.param"); + } + + ad.AssetName = mapid + "_generator_registrations"; + return ad; + } + + public AssetDescription GetDS2EventParam(string mapid, bool writemode = false) + { + AssetDescription ad = new(); + var path = $@"Param\eventparam_{mapid}"; + if (writemode) + { + ad.AssetPath = $@"{RootDirectory}\{path}.param"; + } + else + { + ad.AssetPath = GetAssetPath($@"{path}.param"); + } + + ad.AssetName = mapid + "_event_params"; + return ad; + } + + public AssetDescription GetDS2EventLocationParam(string mapid, bool writemode = false) + { + AssetDescription ad = new(); + var path = $@"Param\eventlocation_{mapid}"; + if (writemode) + { + ad.AssetPath = $@"{RootDirectory}\{path}.param"; + } + else + { + ad.AssetPath = GetAssetPath($@"{path}.param"); + } + + ad.AssetName = mapid + "_event_locations"; + return ad; + } + + public AssetDescription GetDS2ObjInstanceParam(string mapid, bool writemode = false) + { + AssetDescription ad = new(); + var path = $@"Param\mapobjectinstanceparam_{mapid}"; + if (writemode) + { + ad.AssetPath = $@"{RootDirectory}\{path}.param"; + } + else + { + ad.AssetPath = GetAssetPath($@"{path}.param"); + } + + ad.AssetName = mapid + "_object_instance_params"; + return ad; + } + + // Used to get the map model list from within the mapbhd/bdt + public List GetMapModelsFromBXF(string mapid) + { + List ret = new(); + + if (Locator.AssetLocator.Type is GameType.DarkSoulsIISOTFS) + { + var path = GetAssetPath($@"model/map/{mapid}.mapbdt"); + + if (path != null) + { + var bdtPath = path; + var bhdPath = path.Replace("bdt", "bhd"); + + var bxf = BXF4.Read(bhdPath, bdtPath); + + if (bxf != null) + { + foreach (var file in bxf.Files) + { + if (file.Name.Contains(".flv")) + { + var name = GetFileNameWithoutExtensions(file.Name); + + AssetDescription ad = new(); + ad.AssetName = name; + ad.AssetArchiveVirtualPath = $@"map/{name}/model/"; + + ret.Add(ad); + } + } + } + } + } + + return ret; + } + + public List GetMapModels(string mapid) + { + List ret = new(); + if (Type == GameType.DarkSoulsIII || Type == GameType.Sekiro) + { + foreach (var f in GetAllAssets($@"\map\{mapid}\", [@"*.mapbnd.dcx"])) + { + AssetDescription ad = new(); + ad.AssetPath = f; + var name = GetFileNameWithoutExtensions(f); + ad.AssetName = name; + ad.AssetArchiveVirtualPath = $@"map/{mapid}/model/{name}"; + ad.AssetVirtualPath = $@"map/{mapid}/model/{name}/{name}.flver"; + ret.Add(ad); + } + } + else if (Type == GameType.DarkSoulsIISOTFS) + { + AssetDescription ad = new(); + var name = mapid; + ad.AssetName = name; + ad.AssetArchiveVirtualPath = $@"map/{mapid}/model"; + ret.Add(ad); + } + else if (Type == GameType.EldenRing) + { + var mapPath = RootDirectory + $@"\map\{mapid[..3]}\{mapid}"; + foreach (var f in GetAllAssets(mapPath, [@"*.mapbnd.dcx"])) + { + AssetDescription ad = new(); + ad.AssetPath = f; + var name = GetFileNameWithoutExtensions(f); + ad.AssetName = name; + ad.AssetArchiveVirtualPath = $@"map/{mapid}/model/{name}"; + ad.AssetVirtualPath = $@"map/{mapid}/model/{name}/{name}.flver"; + ret.Add(ad); + } + } + else if (Type == GameType.ArmoredCoreVI) + { + var mapPath = RootDirectory + $@"\map\{mapid[..3]}\{mapid}"; + foreach (var f in GetAllAssets(mapPath, [@"*.mapbnd.dcx"])) + { + AssetDescription ad = new(); + ad.AssetPath = f; + var name = GetFileNameWithoutExtensions(f); + ad.AssetName = name; + ad.AssetArchiveVirtualPath = $@"map/{mapid}/model/{name}"; + ad.AssetVirtualPath = $@"map/{mapid}/model/{name}/{name}.flver"; + ret.Add(ad); + } + } + else + { + if (!Directory.Exists(RootDirectory + $@"\map\{mapid}\")) + { + return ret; + } + + var ext = Type == GameType.DarkSoulsPTDE ? @"*.flver" : @"*.flver.dcx"; + List mapfiles = Directory.GetFileSystemEntries(RootDirectory + $@"\map\{mapid}\", ext) + .ToList(); + foreach (var f in mapfiles) + { + AssetDescription ad = new(); + ad.AssetPath = f; + var name = GetFileNameWithoutExtensions(f); + ad.AssetName = name; + // ad.AssetArchiveVirtualPath = $@"map/{mapid}/model/{name}"; + ad.AssetVirtualPath = $@"map/{mapid}/model/{name}/{name}.flver"; + ret.Add(ad); + } + } + + return ret; + } + public string MapModelNameToAssetName(string mapid, string modelname) + { + if (Type == GameType.DarkSoulsPTDE || Type == GameType.DarkSoulsRemastered) + { + return $@"{modelname}A{mapid.Substring(1, 2)}"; + } + + if (Type == GameType.DemonsSouls) + { + return $@"{modelname}"; + } + + if (Type == GameType.DarkSoulsIISOTFS) + { + return modelname; + } + + return $@"{mapid}_{modelname.Substring(1)}"; + } + + /// + /// Gets the adjusted map ID that contains all the map assets + /// + /// The msb map ID to adjust + /// The map ID for the purpose of asset storage + public string GetAssetMapID(string mapid) + { + if (Type is GameType.EldenRing or GameType.ArmoredCoreVI) + { + return mapid; + } + + if (Type is GameType.DarkSoulsRemastered) + { + if (mapid.StartsWith("m99")) + { + // DSR m99 maps contain their own assets + return mapid; + } + } + else if (Type is GameType.DemonsSouls) + { + return mapid; + } + else if (Type is GameType.Bloodborne) + { + if (mapid.StartsWith("m29")) + { + // Special case for chalice dungeon assets + return "m29_00_00_00"; + } + } + + // Default + return mapid.Substring(0, 6) + "_00_00"; + } + public AssetDescription GetMapModel(string mapid, string model) + { + AssetDescription ret = new(); + if (Type == GameType.DarkSoulsPTDE || Type == GameType.Bloodborne || Type == GameType.DemonsSouls) + { + ret.AssetPath = GetAssetPath($@"map\{mapid}\{model}.flver"); + } + else if (Type == GameType.DarkSoulsRemastered) + { + ret.AssetPath = GetAssetPath($@"map\{mapid}\{model}.flver.dcx"); + } + else if (Type == GameType.DarkSoulsIISOTFS) + { + ret.AssetPath = GetAssetPath($@"model\map\{mapid}.mapbhd"); + } + else if (Type == GameType.EldenRing) + { + ret.AssetPath = GetAssetPath($@"map\{mapid[..3]}\{mapid}\{model}.mapbnd.dcx"); + } + else if (Type == GameType.ArmoredCoreVI) + { + ret.AssetPath = GetAssetPath($@"map\{mapid[..3]}\{mapid}\{model}.mapbnd.dcx"); + } + else + { + ret.AssetPath = GetAssetPath($@"map\{mapid}\{model}.mapbnd.dcx"); + } + + ret.AssetName = model; + if (Type == GameType.DarkSoulsIISOTFS) + { + ret.AssetArchiveVirtualPath = $@"map/{mapid}/model"; + ret.AssetVirtualPath = $@"map/{mapid}/model/{model}.flv.dcx"; + } + else + { + if (Type is not GameType.DemonsSouls + and not GameType.DarkSoulsPTDE + and not GameType.DarkSoulsRemastered + and not GameType.Bloodborne) + { + ret.AssetArchiveVirtualPath = $@"map/{mapid}/model/{model}"; + } + + ret.AssetVirtualPath = $@"map/{mapid}/model/{model}/{model}.flver"; + } + + return ret; + } + + public AssetDescription GetMapCollisionModel(string mapid, string model, bool hi = true) + { + AssetDescription ret = new(); + if (Type == GameType.DarkSoulsPTDE || Type == GameType.DemonsSouls) + { + if (hi) + { + ret.AssetPath = GetAssetPath($@"map\{mapid}\{model}.hkx"); + ret.AssetName = model; + ret.AssetVirtualPath = $@"map/{mapid}/hit/hi/{model}.hkx"; + } + else + { + ret.AssetPath = GetAssetPath($@"map\{mapid}\l{model.Substring(1)}.hkx"); + ret.AssetName = model; + ret.AssetVirtualPath = $@"map/{mapid}/hit/lo/l{model.Substring(1)}.hkx"; + } + } + else if (Type == GameType.DarkSoulsIISOTFS) + { + ret.AssetPath = GetAssetPath($@"model\map\h{mapid.Substring(1)}.hkxbhd"); + ret.AssetName = model; + ret.AssetVirtualPath = $@"map/{mapid}/hit/hi/{model}.hkx.dcx"; + ret.AssetArchiveVirtualPath = $@"map/{mapid}/hit/hi"; + } + else if (Type == GameType.DarkSoulsIII || Type == GameType.Bloodborne) + { + if (hi) + { + ret.AssetPath = GetAssetPath($@"map\{mapid}\h{mapid.Substring(1)}.hkxbhd"); + ret.AssetName = model; + ret.AssetVirtualPath = $@"map/{mapid}/hit/hi/h{model.Substring(1)}.hkx.dcx"; + ret.AssetArchiveVirtualPath = $@"map/{mapid}/hit/hi"; + } + else + { + ret.AssetPath = GetAssetPath($@"map\{mapid}\l{mapid.Substring(1)}.hkxbhd"); + ret.AssetName = model; + ret.AssetVirtualPath = $@"map/{mapid}/hit/lo/l{model.Substring(1)}.hkx.dcx"; + ret.AssetArchiveVirtualPath = $@"map/{mapid}/hit/lo"; + } + } + else + { + return AssetUtils.GetNullAsset(); + } + + return ret; + } + + public List GetMapTextures(string mapid) + { + List ads = new(); + + if (Type == GameType.DarkSoulsIISOTFS) + { + AssetDescription t = new(); + t.AssetPath = GetAssetPath($@"model\map\t{mapid.Substring(1)}.tpfbhd"); + t.AssetArchiveVirtualPath = $@"map/tex/{mapid}/tex"; + ads.Add(t); + } + else if (Type == GameType.DarkSoulsPTDE) + { + // TODO + } + else if (Type == GameType.EldenRing) + { + // TODO ER + } + else if (Type == GameType.ArmoredCoreVI) + { + // TODO AC6 + } + else if (Type == GameType.DemonsSouls) + { + var mid = mapid.Substring(0, 3); + var paths = Directory.GetFileSystemEntries($@"{RootDirectory}\map\{mid}\", "*.tpf.dcx"); + foreach (var path in paths) + { + AssetDescription ad = new(); + ad.AssetPath = path; + var tid = Path.GetFileNameWithoutExtension(path).Substring(4, 4); + ad.AssetVirtualPath = $@"map/tex/{mid}/{tid}"; + ads.Add(ad); + } + } + else + { + // Clean this up. Even if it's common code having something like "!=Sekiro" can lead to future issues + var mid = mapid.Substring(0, 3); + + AssetDescription t0000 = new(); + t0000.AssetPath = GetAssetPath($@"map\{mid}\{mid}_0000.tpfbhd"); + t0000.AssetArchiveVirtualPath = $@"map/tex/{mid}/0000"; + ads.Add(t0000); + + AssetDescription t0001 = new(); + t0001.AssetPath = GetAssetPath($@"map\{mid}\{mid}_0001.tpfbhd"); + t0001.AssetArchiveVirtualPath = $@"map/tex/{mid}/0001"; + ads.Add(t0001); + + AssetDescription t0002 = new(); + t0002.AssetPath = GetAssetPath($@"map\{mid}\{mid}_0002.tpfbhd"); + t0002.AssetArchiveVirtualPath = $@"map/tex/{mid}/0002"; + ads.Add(t0002); + + AssetDescription t0003 = new(); + t0003.AssetPath = GetAssetPath($@"map\{mid}\{mid}_0003.tpfbhd"); + t0003.AssetArchiveVirtualPath = $@"map/tex/{mid}/0003"; + ads.Add(t0003); + + if (Type == GameType.DarkSoulsRemastered) + { + AssetDescription env = new(); + env.AssetPath = GetAssetPath($@"map\{mid}\GI_EnvM_{mid}.tpfbhd"); + env.AssetArchiveVirtualPath = $@"map/tex/{mid}/env"; + ads.Add(env); + } + else if (Type == GameType.Bloodborne || Type == GameType.DarkSoulsIII) + { + AssetDescription env = new(); + env.AssetPath = GetAssetPath($@"map\{mid}\{mid}_envmap.tpf.dcx"); + env.AssetVirtualPath = $@"map/tex/{mid}/env"; + ads.Add(env); + } + else if (Type == GameType.Sekiro) + { + //TODO SDT + } + } + + return ads; + } + public List GetEnvMapTextureNames(string mapid) + { + List l = new(); + if (Type == GameType.DarkSoulsIII) + { + var mid = mapid.Substring(0, 3); + if (File.Exists(GetAssetPath($@"map\{mid}\{mid}_envmap.tpf.dcx"))) + { + TPF t = TPF.Read(GetAssetPath($@"map\{mid}\{mid}_envmap.tpf.dcx")); + foreach (TPF.Texture tex in t.Textures) + { + l.Add(tex.Name); + } + } + } + + return l; + } + + private string GetChrTexturePath(string chrid) + { + if (Type is GameType.DemonsSouls) + { + return GetAssetPath($@"chr\{chrid}\{chrid}.tpf"); + } + + if (Type is GameType.DarkSoulsPTDE) + { + var path = GetAssetPath($@"chr\{chrid}\{chrid}.tpf"); + if (path != null) + { + return path; + } + + return GetAssetPath($@"chr\{chrid}.chrbnd"); + } + + if (Type is GameType.DarkSoulsIISOTFS) + { + return GetAssetPath($@"model\chr\{chrid}.texbnd"); + } + + if (Type is GameType.DarkSoulsRemastered) + { + // TODO: Some textures require getting chrtpfbhd from chrbnd, then using it with chrtpfbdt in chr folder. + return GetAssetPath($@"chr\{chrid}.chrbnd"); + } + + if (Type is GameType.Bloodborne) + { + return GetAssetPath($@"chr\{chrid}_2.tpf.dcx"); + } + + if (Type is GameType.DarkSoulsIII or GameType.Sekiro) + { + return GetAssetPath($@"chr\{chrid}.texbnd.dcx"); + } + + if (Type is GameType.EldenRing) + { + // TODO: Maybe add an option down the line to load lower quality + return GetAssetPath($@"chr\{chrid}_h.texbnd.dcx"); + } + + if (Type is GameType.ArmoredCoreVI) + { + return GetAssetPath($@"chr\{chrid}.texbnd.dcx"); + } + + return null; + } + + public AssetDescription GetChrTextures(string chrid) + { + AssetDescription ad = new(); + ad.AssetArchiveVirtualPath = null; + ad.AssetPath = null; + if (Type is GameType.DemonsSouls) + { + var path = GetChrTexturePath(chrid); + if (path != null) + { + ad.AssetPath = path; + ad.AssetVirtualPath = $@"chr/{chrid}/tex"; + } + } + else if (Type is GameType.DarkSoulsPTDE) + { + var path = GetChrTexturePath(chrid); + if (path != null) + { + ad.AssetPath = path; + if (path.EndsWith(".chrbnd")) + { + ad.AssetArchiveVirtualPath = $@"chr/{chrid}/tex"; + } + else + { + ad.AssetVirtualPath = $@"chr/{chrid}/tex"; + } + } + } + else if (Type is GameType.DarkSoulsRemastered) + { + // TODO: Some textures require getting chrtpfbhd from chrbnd, then using it with chrtpfbdt in chr folder. + var path = GetChrTexturePath(chrid); + if (path != null) + { + ad = new AssetDescription(); + ad.AssetPath = path; + ad.AssetVirtualPath = $@"chr/{chrid}/tex"; + } + } + else if (Type is GameType.DarkSoulsIISOTFS) + { + var path = GetChrTexturePath(chrid); + if (path != null) + { + ad = new AssetDescription(); + ad.AssetPath = path; + ad.AssetVirtualPath = $@"chr/{chrid}/tex"; + } + } + else if (Type is GameType.Bloodborne) + { + var path = GetChrTexturePath(chrid); + if (path != null) + { + ad.AssetPath = path; + ad.AssetVirtualPath = $@"chr/{chrid}/tex"; + } + } + else if (Type is GameType.DarkSoulsIII or GameType.Sekiro) + { + var path = GetChrTexturePath(chrid); + if (path != null) + { + ad.AssetPath = path; + ad.AssetArchiveVirtualPath = $@"chr/{chrid}/tex"; + } + } + else if (Type is GameType.EldenRing or GameType.ArmoredCoreVI) + { + var path = GetChrTexturePath(chrid); + if (path != null) + { + ad.AssetPath = path; + ad.AssetArchiveVirtualPath = $@"chr/{chrid}/tex"; + } + } + + return ad; + } + public AssetDescription GetMapNVMModel(string mapid, string model) + { + AssetDescription ret = new(); + if (Type == GameType.DarkSoulsPTDE || Type == GameType.DarkSoulsRemastered || Type == GameType.DemonsSouls) + { + ret.AssetPath = GetAssetPath($@"map\{mapid}\{model}.nvm"); + ret.AssetName = model; + ret.AssetArchiveVirtualPath = $@"map/{mapid}/nav"; + ret.AssetVirtualPath = $@"map/{mapid}/nav/{model}.nvm"; + } + else + { + return AssetUtils.GetNullAsset(); + } + + return ret; + } + + public AssetDescription GetHavokNavmeshes(string mapid) + { + AssetDescription ret = new(); + ret.AssetPath = GetAssetPath($@"map\{mapid}\{mapid}.nvmhktbnd.dcx"); + ret.AssetName = mapid; + ret.AssetArchiveVirtualPath = $@"map/{mapid}/nav"; + return ret; + } + + public AssetDescription GetHavokNavmeshModel(string mapid, string model) + { + AssetDescription ret = new(); + ret.AssetPath = GetAssetPath($@"map\{mapid}\{mapid}.nvmhktbnd.dcx"); + ret.AssetName = model; + ret.AssetArchiveVirtualPath = $@"map/{mapid}/nav"; + ret.AssetVirtualPath = $@"map/{mapid}/nav/{model}.hkx"; + + return ret; + } + + public List GetChrModels() + { + try + { + HashSet chrs = new(); + List ret = new(); + + var modelDir = @"\chr"; + var modelExt = @".chrbnd.dcx"; + if (Type == GameType.DarkSoulsPTDE) + { + modelExt = ".chrbnd"; + } + else if (Type == GameType.DarkSoulsIISOTFS) + { + modelDir = @"\model\chr"; + modelExt = ".bnd"; + } + + if (Type == GameType.DemonsSouls) + { + foreach (var f in GetAllSubDirs(modelDir).Select(Path.GetFileName).Where(x => x.StartsWith("c"))) + { + ret.Add(f); + } + + return ret; + } + + foreach (var f in GetAllAssets(modelDir, [$@"*{modelExt}"])) + { + var name = GetFileNameWithoutExtensions(f); + ret.Add(name); + chrs.Add(name); + } + + return ret; + } + catch (DirectoryNotFoundException e) + { + // Game likely isn't UXM unpacked + return new List(); + } + } + public AssetDescription GetChrModel(string chr) + { + AssetDescription ret = new(); + ret.AssetName = chr; + ret.AssetArchiveVirtualPath = $@"chr/{chr}/model"; + if (Type == GameType.DarkSoulsIISOTFS) + { + ret.AssetVirtualPath = $@"chr/{chr}/model/{chr}.flv"; + } + else + { + ret.AssetVirtualPath = $@"chr/{chr}/model/{chr}.flver"; + } + + return ret; + } + + public List GetObjModels() + { + try + { + HashSet objs = new(); + List ret = new(); + + var modelDir = @"\obj"; + var modelExt = @".objbnd.dcx"; + if (Type == GameType.DarkSoulsPTDE) + { + modelExt = ".objbnd"; + } + else if (Type == GameType.DarkSoulsIISOTFS) + { + modelDir = @"\model\obj"; + modelExt = ".bnd"; + } + else if (Type == GameType.EldenRing) + { + // AEGs are objs in my heart :( + modelDir = @"\asset\aeg"; + modelExt = ".geombnd.dcx"; + } + else if (Type == GameType.ArmoredCoreVI) + { + // AEGs are objs in my heart :( + modelDir = @"\asset\environment\geometry"; + modelExt = ".geombnd.dcx"; + } + + // Directories to search for obj models + List searchDirs = new(); + if (Type == GameType.EldenRing) + { + searchDirs = GetAllSubDirs(modelDir).Where(x => GetFileNameWithoutExtensions(x).StartsWith("aeg")).ToList(); + } + else + { + searchDirs.Add(GetAssetPath(modelDir)); + } + + foreach (var searchDir in searchDirs) + { + foreach (var f in GetAllAssets(searchDir, [$@"*{modelExt}"])) + { + var name = GetFileNameWithoutExtensions(f); + ret.Add(name); + objs.Add(name); + } + } + + return ret; + } + catch (DirectoryNotFoundException e) + { + // Game likely isn't UXM unpacked + return new List(); + } + } + + public AssetDescription GetObjModel(string obj) + { + AssetDescription ret = new(); + ret.AssetName = obj; + ret.AssetArchiveVirtualPath = $@"obj/{obj}/model"; + if (Type == GameType.DarkSoulsIISOTFS) + { + ret.AssetVirtualPath = $@"obj/{obj}/model/{obj}.flv"; + } + else if (Type is GameType.EldenRing or GameType.ArmoredCoreVI) + { + ret.AssetVirtualPath = $@"obj/{obj}/model/{obj.ToUpper()}.flver"; + } + else + { + ret.AssetVirtualPath = $@"obj/{obj}/model/{obj}.flver"; + } + + return ret; + } + + public AssetDescription GetObjTexture(string obj) + { + AssetDescription ad = new(); + ad.AssetPath = null; + ad.AssetArchiveVirtualPath = null; + string path = null; + if (Type == GameType.DarkSoulsPTDE) + { + path = GetAssetPath($@"obj\{obj}.objbnd"); + } + else if (Type is GameType.DemonsSouls or GameType.DarkSoulsRemastered or GameType.Bloodborne + or GameType.DarkSoulsIII or GameType.Sekiro) + { + path = GetAssetPath($@"obj\{obj}.objbnd.dcx"); + } + + if (path != null) + { + ad.AssetPath = path; + ad.AssetArchiveVirtualPath = $@"obj/{obj}/tex"; + } + + return ad; + } + + public AssetDescription GetAetTexture(string aetid) + { + AssetDescription ad = new(); + ad.AssetPath = null; + ad.AssetArchiveVirtualPath = null; + string path; + if (Type == GameType.EldenRing) + { + path = GetAssetPath($@"asset\aet\{aetid.Substring(0, 6)}\{aetid}.tpf.dcx"); + } + else if (Type is GameType.ArmoredCoreVI) + { + path = GetAssetPath($@"\asset\environment\texture\{aetid}.tpf.dcx"); + } + else + { + throw new NotSupportedException(); + } + + if (path != null) + { + ad.AssetPath = path; + ad.AssetArchiveVirtualPath = $@"aet/{aetid}/tex"; + } + + return ad; + } + public List GetPartsModels() + { + try + { + HashSet parts = new(); + List ret = new(); + + var modelDir = @"\parts"; + var modelExt = @".partsbnd.dcx"; + if (Type == GameType.DarkSoulsPTDE) + { + modelExt = ".partsbnd"; + } + else if (Type == GameType.DarkSoulsIISOTFS) + { + modelDir = @"\model\parts"; + modelExt = ".bnd"; + var partsGatheredFiles = GetAllAssets(modelDir, ["*"], true, true); + Directory.GetFiles(RootDirectory + modelDir, "*", SearchOption.AllDirectories); + foreach (var f in partsGatheredFiles) + { + if (!f.EndsWith("common.commonbnd.dcx") && !f.EndsWith("common_cloth.commonbnd.dcx") && + !f.EndsWith("facepreset.bnd")) + { + ret.Add(GetFileNameWithoutExtensions(f)); + } + } + + return ret; + } + + List partsFiles = GetAllAssets(modelDir, [$@"*{modelExt}"]).ToList(); + foreach (var f in partsFiles) + { + var name = GetFileNameWithoutExtensions(f); + ret.Add(name); + parts.Add(name); + } + + return ret; + } + catch (DirectoryNotFoundException e) + { + // Game likely isn't UXM unpacked + return new List(); + } + } + + public AssetDescription GetPartsModel(string part) + { + AssetDescription ret = new(); + ret.AssetName = part; + ret.AssetArchiveVirtualPath = $@"parts/{part}/model"; + if (Type == GameType.DarkSoulsIISOTFS) + { + ret.AssetVirtualPath = $@"parts/{part}/model/{part}.flv"; + } + else if (Type is GameType.DarkSoulsPTDE) + { + ret.AssetVirtualPath = $@"parts/{part}/model/{part.ToUpper()}.flver"; + } + else + { + ret.AssetVirtualPath = $@"parts/{part}/model/{part}.flver"; + } + + return ret; + } + + public AssetDescription GetPartTextures(string partsId) + { + AssetDescription ad = new(); + ad.AssetArchiveVirtualPath = null; + ad.AssetPath = null; + if (Type == GameType.ArmoredCoreVI) + { + string path; + if (partsId.Substring(0, 2) == "wp") + { + string id; + if (partsId.EndsWith("_l")) + { + id = partsId[..^2].Split("_").Last(); + path = GetAssetPath($@"parts\wp_{id}_l.tpf.dcx"); + } + else + { + id = partsId.Split("_").Last(); + path = GetAssetPath($@"parts\wp_{id}.tpf.dcx"); + } + } + else + { + path = GetAssetPath($@"parts\{partsId}_u.tpf.dcx"); + } + + if (path != null) + { + ad.AssetPath = path; + ad.AssetVirtualPath = $@"parts/{partsId}/tex"; + } + } + else if (Type == GameType.EldenRing) + { + // Maybe add an option down the line to load lower quality + var path = GetAssetPath($@"parts\{partsId}.partsbnd.dcx"); + if (path != null) + { + ad.AssetPath = path; + ad.AssetArchiveVirtualPath = $@"parts/{partsId}/tex"; + } + } + else if (Type == GameType.DarkSoulsIII || Type == GameType.Sekiro) + { + var path = GetAssetPath($@"parts\{partsId}.partsbnd.dcx"); + if (path != null) + { + ad.AssetPath = path; + ad.AssetArchiveVirtualPath = $@"parts/{partsId}/tex"; + } + } + else if (Type == GameType.Bloodborne) + { + var path = GetAssetPath($@"parts\{partsId}.partsbnd.dcx"); + if (path != null) + { + ad.AssetPath = path; + ad.AssetVirtualPath = $@"parts/{partsId}/tex"; + } + } + else if (Type == GameType.DarkSoulsPTDE) + { + var path = GetAssetPath($@"parts\{partsId}.partsbnd"); + if (path != null) + { + ad.AssetPath = path; + ad.AssetArchiveVirtualPath = $@"parts/{partsId}/tex"; + } + } + else if (Type == GameType.DemonsSouls) + { + var path = GetAssetPath($@"parts\{partsId}.partsbnd.dcx"); + if (path != null) + { + ad.AssetPath = path; + ad.AssetArchiveVirtualPath = $@"parts/{partsId}/tex"; + } + } + + return ad; + } + + public AssetDescription GetNullAsset() + { + AssetDescription ret = new(); + ret.AssetPath = "null"; + ret.AssetName = "null"; + ret.AssetArchiveVirtualPath = "null"; + ret.AssetVirtualPath = "null"; + return ret; + } + + /// + /// Converts a virtual path to an actual filesystem path. Only resolves virtual paths up to the bnd level, + /// which the remaining string is output for additional handling + /// + /// + /// + public string VirtualToRealPath(string virtualPath, out string bndpath) + { + var pathElements = virtualPath.Split('/'); + Regex mapRegex = new(@"^m\d{2}_\d{2}_\d{2}_\d{2}$"); + var ret = ""; + + // Parse the virtual path with a DFA and convert it to a game path + var i = 0; + if (pathElements[i].Equals("map")) + { + i++; + if (pathElements[i].Equals("tex")) + { + i++; + if (Type == GameType.DarkSoulsIISOTFS) + { + var mid = pathElements[i]; + i++; + var id = pathElements[i]; + if (id == "tex") + { + bndpath = ""; + return GetAssetPath($@"model\map\t{mid.Substring(1)}.tpfbhd"); + } + } + else if (Type == GameType.DemonsSouls) + { + var mid = pathElements[i]; + i++; + bndpath = ""; + return GetAssetPath($@"map\{mid}\{mid}_{pathElements[i]}.tpf.dcx"); + } + else + { + var mid = pathElements[i]; + i++; + bndpath = ""; + if (pathElements[i] == "env") + { + if (Type == GameType.DarkSoulsRemastered) + { + return GetAssetPath($@"map\{mid}\GI_EnvM_{mid}.tpf.dcx"); + } + + return GetAssetPath($@"map\{mid}\{mid}_envmap.tpf.dcx"); + } + + return GetAssetPath($@"map\{mid}\{mid}_{pathElements[i]}.tpfbhd"); + } + } + else if (mapRegex.IsMatch(pathElements[i])) + { + var mapid = pathElements[i]; + i++; + if (pathElements[i].Equals("model")) + { + i++; + bndpath = ""; + if (Type == GameType.DarkSoulsPTDE) + { + return GetAssetPath($@"map\{mapid}\{pathElements[i]}.flver"); + } + + if (Type == GameType.DarkSoulsRemastered) + { + return GetAssetPath($@"map\{mapid}\{pathElements[i]}.flver.dcx"); + } + + if (Type == GameType.DarkSoulsIISOTFS) + { + return GetAssetPath($@"model\map\{mapid}.mapbhd"); + } + + if (Type == GameType.Bloodborne || Type == GameType.DemonsSouls) + { + return GetAssetPath($@"map\{mapid}\{pathElements[i]}.flver.dcx"); + } + + if (Type == GameType.EldenRing) + { + return GetAssetPath($@"map\{mapid.Substring(0, 3)}\{mapid}\{pathElements[i]}.mapbnd.dcx"); + } + + if (Type == GameType.ArmoredCoreVI) + { + return GetAssetPath($@"map\{mapid.Substring(0, 3)}\{mapid}\{pathElements[i]}.mapbnd.dcx"); + } + + return GetAssetPath($@"map\{mapid}\{pathElements[i]}.mapbnd.dcx"); + } + + if (pathElements[i].Equals("hit")) + { + i++; + var hittype = pathElements[i]; + i++; + if (Type == GameType.DarkSoulsPTDE || Type == GameType.DemonsSouls) + { + bndpath = ""; + return GetAssetPath($@"map\{mapid}\{pathElements[i]}"); + } + + if (Type == GameType.DarkSoulsIISOTFS) + { + bndpath = ""; + return GetAssetPath($@"model\map\h{mapid.Substring(1)}.hkxbhd"); + } + + if (Type == GameType.DarkSoulsIII || Type == GameType.Bloodborne) + { + bndpath = ""; + if (hittype == "lo") + { + return GetAssetPath($@"map\{mapid}\l{mapid.Substring(1)}.hkxbhd"); + } + + return GetAssetPath($@"map\{mapid}\h{mapid.Substring(1)}.hkxbhd"); + } + + bndpath = ""; + return null; + } + + if (pathElements[i].Equals("nav")) + { + i++; + if (Type == GameType.DarkSoulsPTDE || Type == GameType.DemonsSouls || + Type == GameType.DarkSoulsRemastered) + { + if (i < pathElements.Length) + { + bndpath = $@"{pathElements[i]}"; + } + else + { + bndpath = ""; + } + + if (Type == GameType.DarkSoulsRemastered) + { + return GetAssetPath($@"map\{mapid}\{mapid}.nvmbnd.dcx"); + } + + return GetAssetPath($@"map\{mapid}\{mapid}.nvmbnd"); + } + + if (Type == GameType.DarkSoulsIII) + { + bndpath = ""; + return GetAssetPath($@"map\{mapid}\{mapid}.nvmhktbnd.dcx"); + } + + bndpath = ""; + return null; + } + } + } + else if (pathElements[i].Equals("chr")) + { + i++; + var chrid = pathElements[i]; + i++; + if (pathElements[i].Equals("model")) + { + bndpath = ""; + if (Type == GameType.DarkSoulsPTDE) + { + return GetAssetPath($@"chr\{chrid}.chrbnd"); + } + + if (Type == GameType.DarkSoulsIISOTFS) + { + return GetAssetPath($@"model\chr\{chrid}.bnd"); + } + + if (Type == GameType.DemonsSouls) + { + return GetAssetPath($@"chr\{chrid}\{chrid}.chrbnd.dcx"); + } + + return GetAssetPath($@"chr\{chrid}.chrbnd.dcx"); + } + + if (pathElements[i].Equals("tex")) + { + bndpath = ""; + return GetChrTexturePath(chrid); + } + } + else if (pathElements[i].Equals("obj")) + { + i++; + var objid = pathElements[i]; + i++; + if (pathElements[i].Equals("model") || pathElements[i].Equals("tex")) + { + bndpath = ""; + if (Type == GameType.DarkSoulsPTDE) + { + return GetAssetPath($@"obj\{objid}.objbnd"); + } + + if (Type == GameType.DarkSoulsIISOTFS) + { + return GetAssetPath($@"model\obj\{objid}.bnd"); + } + + if (Type == GameType.EldenRing) + { + // Derive subfolder path from model name (all vanilla AEG are within subfolders) + if (objid.Length >= 6) + { + return GetAssetPath($@"asset\aeg\{objid.Substring(0, 6)}\{objid}.geombnd.dcx"); + } + + return null; + } + + if (Type == GameType.ArmoredCoreVI) + { + if (objid.Length >= 6) + { + return GetAssetPath($@"asset\environment\geometry\{objid}.geombnd.dcx"); + } + + return null; + } + + return GetAssetPath($@"obj\{objid}.objbnd.dcx"); + } + } + else if (pathElements[i].Equals("parts")) + { + i++; + var partsId = pathElements[i]; + i++; + if (pathElements[i].Equals("model") || pathElements[i].Equals("tex")) + { + bndpath = ""; + if (Type == GameType.DarkSoulsPTDE || Type == GameType.DarkSoulsRemastered) + { + return GetAssetPath($@"parts\{partsId}.partsbnd"); + } + + if (Type == GameType.DarkSoulsIISOTFS) + { + var partType = ""; + switch (partsId.Substring(0, 2)) + { + case "as": + partType = "accessories"; + break; + case "am": + partType = "arm"; + break; + case "bd": + partType = "body"; + break; + case "fa": + case "fc": + case "fg": + partType = "face"; + break; + case "hd": + partType = "head"; + break; + case "leg": + partType = "leg"; + break; + case "sd": + partType = "shield"; + break; + case "wp": + partType = "weapon"; + break; + } + + return GetAssetPath($@"model\parts\{partType}\{partsId}.bnd"); + } + + if (Type == GameType.EldenRing) + { + return GetAssetPath($@"parts\{partsId}\{partsId}.partsbnd.dcx"); + } + + if (Type == GameType.ArmoredCoreVI && pathElements[i].Equals("tex")) + { + string path; + if (partsId.Substring(0, 2) == "wp") + { + string id; + if (partsId.EndsWith("_l")) + { + id = partsId[..^2].Split("_").Last(); + path = GetAssetPath($@"parts\wp_{id}_l.tpf.dcx"); + } + else + { + id = partsId.Split("_").Last(); + path = GetAssetPath($@"parts\wp_{id}.tpf.dcx"); + } + } + else + { + path = GetAssetPath($@"parts\{partsId}_u.tpf.dcx"); + } + + return path; + } + + return GetAssetPath($@"parts\{partsId}.partsbnd.dcx"); + } + } + + bndpath = virtualPath; + return null; + } +} diff --git a/src/StudioCore/Resource/ResourceManager.cs b/src/StudioCore/Resource/ResourceManager.cs index ac39ebcbd..50efba09c 100644 --- a/src/StudioCore/Resource/ResourceManager.cs +++ b/src/StudioCore/Resource/ResourceManager.cs @@ -37,8 +37,6 @@ public enum ResourceType private static QueuedTaskScheduler JobScheduler = new(4, "JobMaster"); private static readonly TaskFactory JobTaskFactory = new(JobScheduler); - public static AssetLocator Locator; - private static readonly Dictionary ResourceDatabase = new(); private static readonly ConcurrentDictionary ActiveJobProgress = new(); private static readonly HashSet InFlightFiles = new(); @@ -125,7 +123,7 @@ private static void LoadBinderResources(LoadBinderResourcesAction action) { Memory f = action.Binder.ReadFile(p.Item3); p.Item1.LoadByteResourceBlock.Post(new LoadByteResourceRequest(p.Item2, f, action.AccessLevel, - Locator.Type)); + Locator.AssetLocator.Type)); action._job.IncrementEstimateTaskSize(1); i++; } @@ -136,7 +134,7 @@ private static void LoadBinderResources(LoadBinderResourcesAction action) { TPF f = TPF.Read(action.Binder.ReadFile(t.Item2)); action._job.AddLoadTPFResources(new LoadTPFResourcesAction(action._job, t.Item1, f, - action.AccessLevel, Locator.Type)); + action.AccessLevel, Locator.AssetLocator.Type)); } catch (Exception e) { @@ -541,8 +539,8 @@ public void ProcessBinder() if (Binder == null) { string o; - var path = Locator.VirtualToRealPath(BinderVirtualPath, out o); - Binder = InstantiateBinderReaderForFile(path, Locator.Type); + var path = Locator.AssetLocator.VirtualToRealPath(BinderVirtualPath, out o); + Binder = InstantiateBinderReaderForFile(path, Locator.AssetLocator.Type); if (Binder == null) { return; @@ -558,7 +556,7 @@ public void ProcessBinder() } var binderpath = f.Name; - var filevirtpath = Locator.GetBinderVirtualPath(BinderVirtualPath, binderpath); + var filevirtpath = AssetUtils.GetBinderVirtualPath(BinderVirtualPath, binderpath); if (AssetWhitelist != null && !AssetWhitelist.Contains(filevirtpath)) { continue; @@ -812,7 +810,7 @@ public void AddLoadFileTask(string virtualPath, AccessLevel al) InFlightFiles.Add(virtualPath); string bndout; - var path = Locator.VirtualToRealPath(virtualPath, out bndout); + var path = Locator.AssetLocator.VirtualToRealPath(virtualPath, out bndout); IResourceLoadPipeline pipeline; if (path == null || virtualPath == "null") @@ -840,7 +838,7 @@ public void AddLoadFileTask(string virtualPath, AccessLevel al) } } - _job.AddLoadTPFResources(new LoadTPFResourcesAction(_job, virt, path, al, Locator.Type)); + _job.AddLoadTPFResources(new LoadTPFResourcesAction(_job, virt, path, al, Locator.AssetLocator.Type)); return; } else @@ -848,7 +846,7 @@ public void AddLoadFileTask(string virtualPath, AccessLevel al) pipeline = _job.FlverLoadPipeline; } - pipeline.LoadFileResourceRequest.Post(new LoadFileResourceRequest(virtualPath, path, al, Locator.Type)); + pipeline.LoadFileResourceRequest.Post(new LoadFileResourceRequest(virtualPath, path, al, Locator.AssetLocator.Type)); } /// @@ -864,14 +862,14 @@ public void AddLoadUDSFMTexturesTask() string path = null; if (texpath.StartsWith("map/tex")) { - path = $@"{Locator.GameRootDirectory}\map\tx\{Path.GetFileName(texpath)}.tpf"; + path = $@"{Locator.AssetLocator.GameRootDirectory}\map\tx\{Path.GetFileName(texpath)}.tpf"; } if (path != null && File.Exists(path)) { _job.AddLoadTPFResources(new LoadTPFResourcesAction(_job, Path.GetDirectoryName(r.Key).Replace('\\', '/'), - path, AccessLevel.AccessGPUOptimizedOnly, Locator.Type)); + path, AccessLevel.AccessGPUOptimizedOnly, Locator.AssetLocator.Type)); } } } @@ -901,7 +899,7 @@ public void AddLoadUnloadedTextures() continue; } - path = Locator.GetAetTexture(fullaetid).AssetPath; + path = Locator.AssetLocator.GetAetTexture(fullaetid).AssetPath; assetTpfs.Add(fullaetid); } @@ -910,7 +908,7 @@ public void AddLoadUnloadedTextures() { _job.AddLoadTPFResources(new LoadTPFResourcesAction(_job, Path.GetDirectoryName(r.Key).Replace('\\', '/'), path, - AccessLevel.AccessGPUOptimizedOnly, Locator.Type)); + AccessLevel.AccessGPUOptimizedOnly, Locator.AssetLocator.Type)); } } } diff --git a/src/StudioCore/SettingsMenu.cs b/src/StudioCore/SettingsMenu.cs index 24e5f085e..faaff4815 100644 --- a/src/StudioCore/SettingsMenu.cs +++ b/src/StudioCore/SettingsMenu.cs @@ -196,22 +196,6 @@ private void DisplaySettings_System() ProjSettings.UseLooseParams = useLoose; } } - - var usepartial = ProjSettings.PartialParams; - if ((FeatureFlags.EnablePartialParam || usepartial)) - { - if (CFG.Current.ShowUITooltips) - { - ShowHelpMarker("Partial params."); - ImGui.SameLine(); - } - - if (ProjSettings.GameType == GameType.EldenRing && - ImGui.Checkbox("Partial params", ref usepartial)) - { - ProjSettings.PartialParams = usepartial; - } - } } } } diff --git a/src/StudioCore/SoapstoneService.cs b/src/StudioCore/SoapstoneService.cs index a9b007db6..659d3842e 100644 --- a/src/StudioCore/SoapstoneService.cs +++ b/src/StudioCore/SoapstoneService.cs @@ -45,16 +45,13 @@ public class SoapstoneService : SoapstoneServiceV1 private static readonly Dictionary revMapNamespaces = mapNamespaces.ToDictionary(e => e.Value, e => e.Key); - - private readonly AssetLocator assetLocator; private readonly MsbEditorScreen msbEditor; private readonly string version; - public SoapstoneService(string version, AssetLocator assetLocator, MsbEditorScreen msbEditor) + public SoapstoneService(string version, MsbEditorScreen msbEditor) { this.version = version; - this.assetLocator = assetLocator; this.msbEditor = msbEditor; } @@ -65,13 +62,13 @@ public override async Task GetServerInfo(ServerCallContext c Id = "DSMapStudio", Version = version, ServerPath = Process.GetCurrentProcess().MainModule?.FileName }; - if (assetLocator.GameModDirectory != null - && gameMapping.TryGetValue(assetLocator.Type, out FromSoftGame gameType)) + if (Locator.AssetLocator.GameModDirectory != null + && gameMapping.TryGetValue(Locator.AssetLocator.Type, out FromSoftGame gameType)) { EditorResource projectResource = new() { Type = EditorResourceType.Project, - ProjectJsonPath = Path.Combine(assetLocator.GameModDirectory, "project.json"), + ProjectJsonPath = Path.Combine(Locator.AssetLocator.GameModDirectory, "project.json"), Game = gameType }; response.Resources.Add(projectResource); @@ -124,9 +121,9 @@ public override async Task GetServerInfo(ServerCallContext c private static bool FMGBankLoaded(FromSoftGame game, out SoulsFmg.FmgLanguage lang) { lang = default; - return FMGBank.IsLoaded - && !string.IsNullOrEmpty(FMGBank.LanguageFolder) - && SoulsFmg.TryGetFmgLanguageEnum(game, FMGBank.LanguageFolder, out lang); + return Locator.ActiveProject.FMGBank.IsLoaded + && !string.IsNullOrEmpty(Locator.ActiveProject.FMGBank.LanguageFolder) + && SoulsFmg.TryGetFmgLanguageEnum(game, Locator.ActiveProject.FMGBank.LanguageFolder, out lang); } private static object AccessParamFile(SoulsKey.GameParamKey p, string key) @@ -234,11 +231,11 @@ private static object AccessFmgProperty(SoulsKey.FmgKey fmgKey, SoulsFmg.FmgKeyI private static bool GetFmgKey( FromSoftGame game, SoulsFmg.FmgLanguage lang, - FMGBank.FMGInfo info, + FMGInfo info, out SoulsKey.FmgKey key) { - // UICategory is used to distinguish between name-keyed FMGs (DS2) and binder-keyed FMGs (item/menu bnds) - if (info.UICategory == FmgUICategory.Text) + // FileCategory is used to distinguish between name-keyed FMGs (DS2) and binder-keyed FMGs (item/menu bnds) + if (info.FileCategory == FmgFileCategory.Loose) { if (SoulsFmg.TryGetFmgNameType(game, info.Name, out List types)) { @@ -271,7 +268,7 @@ public override async Task> GetObjects( RequestedProperties properties) { List results = new(); - if (!gameMapping.TryGetValue(assetLocator.Type, out FromSoftGame game) || resource.Game != game) + if (!gameMapping.TryGetValue(Locator.AssetLocator.Type, out FromSoftGame game) || resource.Game != game) { return results; } @@ -308,9 +305,9 @@ public override async Task> GetObjects( } if (resource.Type == EditorResourceType.Fmg - && FMGBank.IsLoaded - && !string.IsNullOrEmpty(FMGBank.LanguageFolder) - && SoulsFmg.TryGetFmgLanguageEnum(game, FMGBank.LanguageFolder, out SoulsFmg.FmgLanguage lang) + && Locator.ActiveProject.FMGBank.IsLoaded + && !string.IsNullOrEmpty(Locator.ActiveProject.FMGBank.LanguageFolder) + && SoulsFmg.TryGetFmgLanguageEnum(game, Locator.ActiveProject.FMGBank.LanguageFolder, out SoulsFmg.FmgLanguage lang) && MatchesResource(resource, lang.ToString())) { foreach (SoulsKey getKey in keys) @@ -321,8 +318,8 @@ public override async Task> GetObjects( continue; } - FMGBank.FMGInfo info = FMGBank.FmgInfoBank - .Find(info => + FMGInfo info = Locator.ActiveProject.FMGBank.FmgInfoBank + .FirstOrDefault(info => GetFmgKey(game, lang, info, out SoulsKey.FmgKey infoKey) && infoKey.Equals(fileKey)); if (info == null) { @@ -397,7 +394,7 @@ public override async Task> SearchObjects( SearchOptions options) { List results = new(); - if (!gameMapping.TryGetValue(assetLocator.Type, out FromSoftGame game) || resource.Game != game) + if (!gameMapping.TryGetValue(Locator.AssetLocator.Type, out FromSoftGame game) || resource.Game != game) { return results; } @@ -457,9 +454,9 @@ bool addResult(SoulsObject obj) } if (resource.Type == EditorResourceType.Fmg - && FMGBank.IsLoaded - && !string.IsNullOrEmpty(FMGBank.LanguageFolder) - && SoulsFmg.TryGetFmgLanguageEnum(game, FMGBank.LanguageFolder, out SoulsFmg.FmgLanguage lang) + && Locator.ActiveProject.FMGBank.IsLoaded + && !string.IsNullOrEmpty(Locator.ActiveProject.FMGBank.LanguageFolder) + && SoulsFmg.TryGetFmgLanguageEnum(game, Locator.ActiveProject.FMGBank.LanguageFolder, out SoulsFmg.FmgLanguage lang) // Language applies to all FMGs at once currently, so filter it here if requested && MatchesResource(resource, lang.ToString()) && search.GetKeyFilter("Language")(lang.ToString())) @@ -467,7 +464,7 @@ bool addResult(SoulsObject obj) Predicate fmgFilter = search.GetKeyFilter("FMG"); Predicate baseFmgFilter = search.GetKeyFilter("BaseFMG"); Predicate categoryFilter = search.GetKeyFilter("Category"); - foreach (FMGBank.FMGInfo info in FMGBank.FmgInfoBank) + foreach (FMGInfo info in Locator.ActiveProject.FMGBank.FmgInfoBank) { if (!GetFmgKey(game, lang, info, out SoulsKey.FmgKey fileKey) || !SoulsFmg.TryGetFmgInfo(game, fileKey, out SoulsFmg.FmgKeyInfo keyInfo)) @@ -571,7 +568,7 @@ public override async Task OpenResource(ServerCallContext context, EditorResourc { // At the moment, only loading maps is supported. // This could be extended to switching FMG language, or adding a param view, or opening a model to view. - if (!gameMapping.TryGetValue(assetLocator.Type, out FromSoftGame game) || resource.Game != game) + if (!gameMapping.TryGetValue(Locator.AssetLocator.Type, out FromSoftGame game) || resource.Game != game) { return; } @@ -588,7 +585,7 @@ public override async Task OpenObject( EditorResource resource, SoulsKey key) { - if (!gameMapping.TryGetValue(assetLocator.Type, out FromSoftGame game) || resource.Game != game) + if (!gameMapping.TryGetValue(Locator.AssetLocator.Type, out FromSoftGame game) || resource.Game != game) { return; } @@ -655,7 +652,7 @@ public override async Task OpenSearch( { // At the moment, just map properties, since there are some multi-keyed things like entity groups // Params are also possible; FMG might require a new command - if (!gameMapping.TryGetValue(assetLocator.Type, out FromSoftGame game) || resource.Game != game) + if (!gameMapping.TryGetValue(Locator.AssetLocator.Type, out FromSoftGame game) || resource.Game != game) { return; } diff --git a/src/StudioCore/Tests/GlyphCatcher.cs b/src/StudioCore/Tests/GlyphCatcher.cs index 3c0270c11..bdcf3f1b6 100644 --- a/src/StudioCore/Tests/GlyphCatcher.cs +++ b/src/StudioCore/Tests/GlyphCatcher.cs @@ -99,7 +99,7 @@ public static unsafe void CheckFMG() { HashSet chars = new(); var font = ImGui.GetFont(); - foreach (var info in FMGBank.FmgInfoBank) + foreach (var info in Locator.ActiveProject.FMGBank.FmgInfoBank) { foreach (var entry in info.Fmg.Entries) { diff --git a/src/StudioCore/TextEditor/FMGBank.cs b/src/StudioCore/TextEditor/FMGBank.cs index 844f0c702..caf2af43b 100644 --- a/src/StudioCore/TextEditor/FMGBank.cs +++ b/src/StudioCore/TextEditor/FMGBank.cs @@ -1,652 +1,51 @@ using Microsoft.Extensions.Logging; +using SoapstoneLib.Proto.Internal; using SoulsFormats; using StudioCore.Editor; using StudioCore.MsbEditor; using StudioCore.Platform; using System; +using System.Collections; using System.Collections.Generic; using System.IO; using System.Linq; namespace StudioCore.TextEditor; -/// -/// FMG sections in UI -/// -public enum FmgUICategory -{ - Text = 0, - Item = 1, - Menu = 2 -} - -/// -/// Entry type for Title, Summary, Description, or other. -/// -public enum FmgEntryTextType -{ - TextBody = 0, - Title = 1, - Summary = 2, - Description = 3, - ExtraText = 4 -} - -/// -/// Text categories used for grouping multiple FMGs or broad identification -/// -public enum FmgEntryCategory -{ - None = -1, - Goods, - Weapons, - Armor, - Rings, - Spells, - Characters, - Locations, - Gem, - Message, - SwordArts, - Effect, - ActionButtonText, - Tutorial, - LoadingScreen, - Generator, - Booster, - FCS, - Mission, - Archive, - - ItemFmgDummy = 200 // Anything with this will be sorted into the item section of the editor. -} - -/// -/// BND IDs for FMG files used for identification -/// -public enum FmgIDType -{ - // Note: Matching names with _DLC and _PATCH are used as identifiers for patch FMGs. This is a little dumb and patch fmg handling should probably be redone. - None = -1, - - TitleGoods = 10, - TitleWeapons = 11, - TitleArmor = 12, - TitleRings = 13, - TitleSpells = 14, - TitleTest = 15, - TitleTest2 = 16, - TitleTest3 = 17, - TitleCharacters = 18, - TitleLocations = 19, - SummaryGoods = 20, - SummaryWeapons = 21, - SummaryArmor = 22, - SummaryRings = 23, - DescriptionGoods = 24, - DescriptionWeapons = 25, - DescriptionArmor = 26, - DescriptionRings = 27, - SummarySpells = 28, - DescriptionSpells = 29, - - // - TalkMsg = 1, - BloodMsg = 2, - MovieSubtitle = 3, - Event = 30, - MenuInGame = 70, - MenuCommon = 76, - MenuOther = 77, - MenuDialog = 78, - MenuKeyGuide = 79, - MenuLineHelp = 80, - MenuContext = 81, - MenuTags = 90, - Win32Tags = 91, - Win32Messages = 92, - Event_Patch = 101, - MenuDialog_Patch = 102, - Win32Messages_Patch = 103, - TalkMsg_Patch = 104, - BloodMsg_Patch = 107, - MenuLineHelp_Patch = 121, - MenuKeyGuide_Patch = 122, - MenuOther_Patch = 123, - MenuCommon_Patch = 124, - - // DS1 _DLC - DescriptionGoods_Patch = 100, - DescriptionSpells_Patch = 105, - DescriptionWeapons_Patch = 106, - DescriptionArmor_Patch = 108, - DescriptionRings_Patch = 109, - SummaryGoods_Patch = 110, - TitleGoods_Patch = 111, - SummaryRings_Patch = 112, - TitleRings_Patch = 113, - SummaryWeapons_Patch = 114, - TitleWeapons_Patch = 115, - SummaryArmor_Patch = 116, - TitleArmor_Patch = 117, - TitleSpells_Patch = 118, - TitleCharacters_Patch = 119, - TitleLocations_Patch = 120, - - // DS3 _DLC1 - TitleWeapons_DLC1 = 211, - TitleArmor_DLC1 = 212, - TitleRings_DLC1 = 213, - TitleSpells_DLC1 = 214, - TitleCharacters_DLC1 = 215, - TitleLocations_DLC1 = 216, - SummaryGoods_DLC1 = 217, - SummaryRings_DLC1 = 220, - DescriptionGoods_DLC1 = 221, - DescriptionWeapons_DLC1 = 222, - DescriptionArmor_DLC1 = 223, - DescriptionRings_DLC1 = 224, - SummarySpells_DLC1 = 225, - DescriptionSpells_DLC1 = 226, - - // - Modern_MenuText = 200, - Modern_LineHelp = 201, - Modern_KeyGuide = 202, - Modern_SystemMessage_win64 = 203, - Modern_Dialogues = 204, - TalkMsg_DLC1 = 230, - Event_DLC1 = 231, - Modern_MenuText_DLC1 = 232, - Modern_LineHelp_DLC1 = 233, - Modern_SystemMessage_win64_DLC1 = 235, - Modern_Dialogues_DLC1 = 236, - SystemMessage_PS4_DLC1 = 237, - SystemMessage_XboxOne_DLC1 = 238, - BloodMsg_DLC1 = 239, - - // DS3 _DLC2 - TitleGoods_DLC2 = 250, - TitleWeapons_DLC2 = 251, - TitleArmor_DLC2 = 252, - TitleRings_DLC2 = 253, - TitleSpells_DLC2 = 254, - TitleCharacters_DLC2 = 255, - TitleLocations_DLC2 = 256, - SummaryGoods_DLC2 = 257, - SummaryRings_DLC2 = 260, - DescriptionGoods_DLC2 = 261, - DescriptionWeapons_DLC2 = 262, - DescriptionArmor_DLC2 = 263, - DescriptionRings_DLC2 = 264, - SummarySpells_DLC2 = 265, - DescriptionSpells_DLC2 = 266, - - // - TalkMsg_DLC2 = 270, - Event_DLC2 = 271, - Modern_MenuText_DLC2 = 272, - Modern_LineHelp_DLC2 = 273, - Modern_SystemMessage_win64_DLC2 = 275, - Modern_Dialogues_DLC2 = 276, - SystemMessage_PS4_DLC2 = 277, - SystemMessage_XboxOne_DLC2 = 278, - BloodMsg_DLC2 = 279, - - // SDT - Skills = 40, - - // ER - DescriptionGem = 37, - SummarySwordArts = 43, - WeaponEffect = 44, - ERUnk45 = 45, - GoodsInfo2 = 46, - - // - TalkMsg_FemalePC_Alt = 4, - NetworkMessage = 31, - EventTextForTalk = 33, - EventTextForMap = 34, - TutorialTitle = 207, - TutorialBody = 208, - TextEmbedImageName_win64 = 209, - - // AC6 - TitleBooster = 38, - DescriptionBooster = 39, - - // - RankerProfile = 50, - TitleMission = 60, - SummaryMission = 61, - DescriptionMission = 62, - MissionLocation = 63, - TitleArchive = 65, - DescriptionArchive = 66, - TutorialTitle2023 = 73, - TutorialBody2023 = 74, - - // Multiple use cases. Differences are applied in ApplyGameDifferences(); - ReusedFMG_32 = 32, - - // FMG 32 - // BB: GemExtraInfo - // DS3: ActionButtonText - // SDT: ActionButtonText - // ER: ActionButtonText - ReusedFMG_35 = 35, - - // FMG 35 - // Most: TitleGem - // AC6: TitleGenerator - ReusedFMG_36 = 36, - - // FMG 36 - // Most: SummaryGem - // AC6: DescriptionGenerator - ReusedFMG_41 = 41, - - // FMG 41 - // Most: TitleMessage - // AC6: TitleFCS - ReusedFMG_42 = 42, - - // FMG 42 - // Most: TitleSwordArts - // AC6: DescriptionFCS - ReusedFMG_210 = 210, - - // FMG 210 - // DS3: TitleGoods_DLC1 - // SDT: ? - // ER: ToS_win64 - // AC6: TextEmbeddedImageNames - ReusedFMG_205 = 205, - - // FMG 205 - // DS3: SystemMessage_PS4 - // SDT: TutorialText - // ER: LoadingTitle - // AC6: MenuContext - ReusedFMG_206 = 206 - // FMG 206 - // DS3: SystemMessage_XboxOne - // SDT: TutorialTitle - // ER: LoadingText -} - -/// -/// Static class that stores all the strings for a Souls game. -/// -public static partial class FMGBank +/* + * FMGFileSet represents a grouped set of FMGInfos source from the same bnd or loose folder, within a single language. + */ +public class FMGFileSet { - internal static AssetLocator AssetLocator; - - /// - /// List of strings to compare with "FmgIDType" name to identify patch FMGs. - /// - private static readonly List patchStrings = new() { "_Patch", "_DLC1", "_DLC2" }; - - public static bool IsLoaded { get; private set; } - public static bool IsLoading { get; private set; } - public static string LanguageFolder { get; private set; } = ""; - - public static List FmgInfoBank { get; private set; } = new(); - - public static Dictionary ActiveUITypes { get; private set; } = new(); - - /// - /// Get category for grouped entries (Goods, Weapons, etc) - /// - public static FmgEntryCategory GetFmgCategory(int id) - { - switch ((FmgIDType)id) - { - case FmgIDType.TitleTest: - case FmgIDType.TitleTest2: - case FmgIDType.TitleTest3: - case FmgIDType.ERUnk45: - return FmgEntryCategory.None; - - case FmgIDType.DescriptionGoods: - case FmgIDType.DescriptionGoods_Patch: - case FmgIDType.DescriptionGoods_DLC1: - case FmgIDType.DescriptionGoods_DLC2: - case FmgIDType.SummaryGoods: - case FmgIDType.SummaryGoods_Patch: - case FmgIDType.SummaryGoods_DLC1: - case FmgIDType.SummaryGoods_DLC2: - case FmgIDType.TitleGoods: - case FmgIDType.TitleGoods_Patch: - case FmgIDType.TitleGoods_DLC2: - case FmgIDType.GoodsInfo2: - return FmgEntryCategory.Goods; - - case FmgIDType.DescriptionWeapons: - case FmgIDType.DescriptionWeapons_DLC1: - case FmgIDType.DescriptionWeapons_DLC2: - case FmgIDType.SummaryWeapons: - case FmgIDType.TitleWeapons: - case FmgIDType.TitleWeapons_DLC1: - case FmgIDType.TitleWeapons_DLC2: - case FmgIDType.DescriptionWeapons_Patch: - case FmgIDType.SummaryWeapons_Patch: - case FmgIDType.TitleWeapons_Patch: - return FmgEntryCategory.Weapons; - - case FmgIDType.DescriptionArmor: - case FmgIDType.DescriptionArmor_DLC1: - case FmgIDType.DescriptionArmor_DLC2: - case FmgIDType.SummaryArmor: - case FmgIDType.TitleArmor: - case FmgIDType.TitleArmor_DLC1: - case FmgIDType.TitleArmor_DLC2: - case FmgIDType.DescriptionArmor_Patch: - case FmgIDType.SummaryArmor_Patch: - case FmgIDType.TitleArmor_Patch: - return FmgEntryCategory.Armor; - - case FmgIDType.DescriptionRings: - case FmgIDType.DescriptionRings_DLC1: - case FmgIDType.DescriptionRings_DLC2: - case FmgIDType.SummaryRings: - case FmgIDType.SummaryRings_DLC1: - case FmgIDType.SummaryRings_DLC2: - case FmgIDType.TitleRings: - case FmgIDType.TitleRings_DLC1: - case FmgIDType.TitleRings_DLC2: - case FmgIDType.DescriptionRings_Patch: - case FmgIDType.SummaryRings_Patch: - case FmgIDType.TitleRings_Patch: - return FmgEntryCategory.Rings; - - case FmgIDType.DescriptionSpells: - case FmgIDType.DescriptionSpells_DLC1: - case FmgIDType.DescriptionSpells_DLC2: - case FmgIDType.SummarySpells: - case FmgIDType.SummarySpells_DLC1: - case FmgIDType.SummarySpells_DLC2: - case FmgIDType.TitleSpells: - case FmgIDType.TitleSpells_DLC1: - case FmgIDType.TitleSpells_DLC2: - case FmgIDType.DescriptionSpells_Patch: - case FmgIDType.TitleSpells_Patch: - return FmgEntryCategory.Spells; - - case FmgIDType.TitleCharacters: - case FmgIDType.TitleCharacters_DLC1: - case FmgIDType.TitleCharacters_DLC2: - case FmgIDType.TitleCharacters_Patch: - return FmgEntryCategory.Characters; - - case FmgIDType.TitleLocations: - case FmgIDType.TitleLocations_DLC1: - case FmgIDType.TitleLocations_DLC2: - case FmgIDType.TitleLocations_Patch: - return FmgEntryCategory.Locations; - - case FmgIDType.DescriptionGem: - return FmgEntryCategory.Gem; - - case FmgIDType.SummarySwordArts: - return FmgEntryCategory.SwordArts; - - case FmgIDType.TutorialTitle: - case FmgIDType.TutorialBody: - case FmgIDType.TutorialTitle2023: - case FmgIDType.TutorialBody2023: - return FmgEntryCategory.Tutorial; - - case FmgIDType.WeaponEffect: - return FmgEntryCategory.ItemFmgDummy; - - case FmgIDType.TitleMission: - case FmgIDType.SummaryMission: - case FmgIDType.DescriptionMission: - case FmgIDType.MissionLocation: - return FmgEntryCategory.Mission; - - case FmgIDType.TitleBooster: - case FmgIDType.DescriptionBooster: - return FmgEntryCategory.Booster; - - case FmgIDType.TitleArchive: - case FmgIDType.DescriptionArchive: - return FmgEntryCategory.Archive; - - default: - return FmgEntryCategory.None; - } - } - - /// - /// Get entry text type (such as weapon Title, Summary, Description) - /// - public static FmgEntryTextType GetFmgTextType(int id) - { - switch ((FmgIDType)id) - { - case FmgIDType.DescriptionGoods: - case FmgIDType.DescriptionGoods_DLC1: - case FmgIDType.DescriptionGoods_DLC2: - case FmgIDType.DescriptionWeapons: - case FmgIDType.DescriptionWeapons_DLC1: - case FmgIDType.DescriptionWeapons_DLC2: - case FmgIDType.DescriptionArmor: - case FmgIDType.DescriptionArmor_DLC1: - case FmgIDType.DescriptionArmor_DLC2: - case FmgIDType.DescriptionRings: - case FmgIDType.DescriptionRings_DLC1: - case FmgIDType.DescriptionRings_DLC2: - case FmgIDType.DescriptionSpells: - case FmgIDType.DescriptionSpells_DLC1: - case FmgIDType.DescriptionSpells_DLC2: - case FmgIDType.DescriptionArmor_Patch: - case FmgIDType.DescriptionGoods_Patch: - case FmgIDType.DescriptionRings_Patch: - case FmgIDType.DescriptionSpells_Patch: - case FmgIDType.DescriptionWeapons_Patch: - case FmgIDType.DescriptionGem: - case FmgIDType.SummarySwordArts: // Include as Description (for text box size) - case FmgIDType.DescriptionBooster: - case FmgIDType.DescriptionMission: - case FmgIDType.DescriptionArchive: - return FmgEntryTextType.Description; - - case FmgIDType.SummaryGoods: - case FmgIDType.SummaryGoods_DLC1: - case FmgIDType.SummaryGoods_DLC2: - case FmgIDType.SummaryWeapons: - case FmgIDType.SummaryArmor: - case FmgIDType.SummaryRings: - case FmgIDType.SummaryRings_DLC1: - case FmgIDType.SummaryRings_DLC2: - case FmgIDType.SummarySpells: - case FmgIDType.SummarySpells_DLC1: - case FmgIDType.SummarySpells_DLC2: - case FmgIDType.SummaryArmor_Patch: - case FmgIDType.SummaryGoods_Patch: - case FmgIDType.SummaryRings_Patch: - case FmgIDType.SummaryWeapons_Patch: - case FmgIDType.SummaryMission: - case FmgIDType.TutorialTitle: // Include as summary (not all TutorialBody's have a title) - case FmgIDType.TutorialTitle2023: - return FmgEntryTextType.Summary; - - case FmgIDType.TitleGoods: - case FmgIDType.TitleGoods_DLC2: - case FmgIDType.TitleWeapons: - case FmgIDType.TitleWeapons_DLC1: - case FmgIDType.TitleWeapons_DLC2: - case FmgIDType.TitleArmor: - case FmgIDType.TitleArmor_DLC1: - case FmgIDType.TitleArmor_DLC2: - case FmgIDType.TitleRings: - case FmgIDType.TitleRings_DLC1: - case FmgIDType.TitleRings_DLC2: - case FmgIDType.TitleSpells: - case FmgIDType.TitleSpells_DLC1: - case FmgIDType.TitleSpells_DLC2: - case FmgIDType.TitleCharacters: - case FmgIDType.TitleCharacters_DLC1: - case FmgIDType.TitleCharacters_DLC2: - case FmgIDType.TitleLocations: - case FmgIDType.TitleLocations_DLC1: - case FmgIDType.TitleLocations_DLC2: - case FmgIDType.TitleTest: - case FmgIDType.TitleTest2: - case FmgIDType.TitleTest3: - case FmgIDType.TitleArmor_Patch: - case FmgIDType.TitleCharacters_Patch: - case FmgIDType.TitleGoods_Patch: - case FmgIDType.TitleLocations_Patch: - case FmgIDType.TitleRings_Patch: - case FmgIDType.TitleSpells_Patch: - case FmgIDType.TitleWeapons_Patch: - case FmgIDType.TitleBooster: - case FmgIDType.TitleMission: - case FmgIDType.TitleArchive: - return FmgEntryTextType.Title; - - case FmgIDType.GoodsInfo2: - case FmgIDType.MissionLocation: - return FmgEntryTextType.ExtraText; - - case FmgIDType.WeaponEffect: - case FmgIDType.TutorialBody: // Include as TextBody to make it display foremost. - case FmgIDType.TutorialBody2023: - return FmgEntryTextType.TextBody; - - default: - return FmgEntryTextType.TextBody; - } - } - - /// - /// Removes patch/DLC identifiers from strings for the purposes of finding patch FMGs. Kinda dumb. - /// - private static string RemovePatchStrings(string str) - { - foreach (var badString in patchStrings) - { - str = str.Replace(badString, "", StringComparison.CurrentCultureIgnoreCase); - } - - return str; - } - - private static FMGInfo GenerateFMGInfo(BinderFile file) + internal FMGFileSet(FMGLanguage owner, FmgFileCategory category) { - FMG fmg = FMG.Read(file.Bytes); - var name = Enum.GetName(typeof(FmgIDType), file.ID); - FMGInfo info = new() - { - FileName = file.Name.Split("\\").Last(), - Name = name, - FmgID = (FmgIDType)file.ID, - Fmg = fmg, - EntryType = GetFmgTextType(file.ID), - EntryCategory = GetFmgCategory(file.ID) - }; - switch (info.EntryCategory) - { - case FmgEntryCategory.Goods: - case FmgEntryCategory.Weapons: - case FmgEntryCategory.Armor: - case FmgEntryCategory.Rings: - case FmgEntryCategory.Gem: - case FmgEntryCategory.SwordArts: - case FmgEntryCategory.Generator: - case FmgEntryCategory.Booster: - case FmgEntryCategory.FCS: - case FmgEntryCategory.Archive: - info.UICategory = FmgUICategory.Item; - break; - default: - info.UICategory = FmgUICategory.Menu; - break; - } - - ActiveUITypes[info.UICategory] = true; - - return info; + Lang = owner; + FileCategory = category; } + internal FMGLanguage Lang; + internal FmgFileCategory FileCategory; + internal bool IsLoaded = false; + internal bool IsLoading = false; - private static void SetFMGInfoDS2(string file) - { - // TODO: DS2 FMG grouping & UI sorting (copy SetFMGInfo) - FMG fmg = FMG.Read(file); - var name = Path.GetFileNameWithoutExtension(file); - FMGInfo info = new() - { - FileName = file.Split("\\").Last(), - Name = name, - FmgID = FmgIDType.None, - Fmg = fmg, - EntryType = FmgEntryTextType.TextBody, - EntryCategory = FmgEntryCategory.None, - UICategory = FmgUICategory.Text - }; - ActiveUITypes[info.UICategory] = true; - FmgInfoBank.Add(info); - } + internal List FmgInfos = new(); - /// - /// Loads item and menu MsgBnds from paths, generates FMGInfo, and fills FmgInfoBank. - /// - /// True if successful; false otherwise. - private static bool LoadItemMenuMsgBnds(AssetDescription itemMsgPath, AssetDescription menuMsgPath) + internal void InsertFmgInfo(FMGInfo info) { - if (!LoadMsgBnd(itemMsgPath.AssetPath, "item.msgbnd") - || !LoadMsgBnd(menuMsgPath.AssetPath, "menu.msgbnd")) - { - return false; - } - - foreach (FMGInfo info in FmgInfoBank) - { - ApplyGameDifferences(info); - if (CFG.Current.FMG_NoGroupedFmgEntries) - { - info.EntryType = FmgEntryTextType.TextBody; - } - } - - if (!CFG.Current.FMG_NoFmgPatching) - { - foreach (FMGInfo info in FmgInfoBank) - { - SetFMGInfoPatchParent(info); - } - } - - if (CFG.Current.FMG_NoGroupedFmgEntries) - { - FmgInfoBank = FmgInfoBank.OrderBy(e => e.EntryCategory).ThenBy(e => e.FmgID).ToList(); - } - else - { - FmgInfoBank = FmgInfoBank.OrderBy(e => e.Name).ToList(); - } - - HandleDuplicateEntries(); - - return true; + FmgInfos.Add(info); } /// /// Loads MsgBnd from path, generates FMGInfo, and fills FmgInfoBank. /// /// True if successful; false otherwise. - private static bool LoadMsgBnd(string path, string msgBndType = "UNDEFINED") + internal bool LoadMsgBnd(string path, string msgBndType = "UNDEFINED") { if (path == null) { - if (LanguageFolder != "") + if (Lang.LanguageFolder != "") { TaskLogs.AddLog( - $"Could locate text data files when looking for \"{msgBndType}\" in \"{LanguageFolder}\" folder", + $"Could locate text data files when looking for \"{msgBndType}\" in \"{Lang.LanguageFolder}\" folder", LogLevel.Warning); } else @@ -662,8 +61,9 @@ private static bool LoadMsgBnd(string path, string msgBndType = "UNDEFINED") } IBinder fmgBinder; - if (AssetLocator.Type == GameType.DemonsSouls || AssetLocator.Type == GameType.DarkSoulsPTDE || - AssetLocator.Type == GameType.DarkSoulsRemastered) + GameType Type = Lang.Owner.Project.AssetLocator.Type; + if (Type == GameType.DemonsSouls || Type == GameType.DarkSoulsPTDE || + Type == GameType.DarkSoulsRemastered) { fmgBinder = BND3.Read(path); } @@ -674,134 +74,118 @@ private static bool LoadMsgBnd(string path, string msgBndType = "UNDEFINED") foreach (BinderFile file in fmgBinder.Files) { - FmgInfoBank.Add(GenerateFMGInfo(file)); + FmgInfos.Add(GenerateFMGInfo(file)); + } + // I hate this 2 parse system. Solve game differences by including game in the get enum functions. Maybe parentage is solvable by pre-sorting binderfiles but that does seem silly. FMG patching sucks. + foreach (FMGInfo info in FmgInfos) + { + /* TODO sorting without modifying data + if (CFG.Current.FMG_NoGroupedFmgEntries) + { + info.EntryType = FmgEntryTextType.TextBody; + }*/ + SetFMGInfoPatchParent(info); } fmgBinder.Dispose(); + + HandleDuplicateEntries(); + IsLoaded = true; + IsLoading = false; return true; } - public static void ReloadFMGs(string languageFolder = "") + internal bool LoadLooseMsgsDS2(IEnumerable files) { - TaskManager.Run(new TaskManager.LiveTask("FMG - Load Text", TaskManager.RequeueType.WaitThenRequeue, true, - () => - { - IsLoaded = false; - IsLoading = true; + foreach (var file in files) + { + FMGInfo info = GenerateFMGInfoDS2(file); + InsertFmgInfo(info); + } - LanguageFolder = languageFolder; + //TODO ordering + //FmgInfoBank = FmgInfoBank.OrderBy(e => e.Name).ToList(); + HandleDuplicateEntries(); + IsLoaded = true; + IsLoading = false; + return true; + } - ActiveUITypes = new Dictionary(); - foreach (var e in Enum.GetValues(typeof(FmgUICategory))) + internal void HandleDuplicateEntries() + { + var askedAboutDupes = false; + var ignoreDupes = true; + foreach (FMGInfo info in FmgInfos) + { + IEnumerable dupes = info.Fmg.Entries.GroupBy(e => e.ID).SelectMany(g => g.SkipLast(1)); + if (dupes.Any()) + { + var dupeList = string.Join(", ", dupes.Select(dupe => dupe.ID)); + if (!askedAboutDupes && PlatformUtils.Instance.MessageBox( + $"Duplicate text entries have been found in FMG {Path.GetFileNameWithoutExtension(info.FileName)} for the following row IDs:\n\n{dupeList}\n\nRemove all duplicates? (Latest entries are kept)", + "Duplicate Text Entries", MessageBoxButtons.YesNo) == DialogResult.Yes) { - ActiveUITypes.Add((FmgUICategory)e, false); + ignoreDupes = false; } - if (AssetLocator.Type == GameType.Undefined) - { - return; - } + askedAboutDupes = true; - if (AssetLocator.Type == GameType.DarkSoulsIISOTFS) + if (!ignoreDupes) { - if (ReloadDS2FMGs()) + foreach (FMG.Entry dupe in dupes) { - IsLoading = false; - IsLoaded = true; + info.Fmg.Entries.Remove(dupe); } - - return; } - - SetDefaultLanguagePath(); - - AssetDescription itemMsgPath = AssetLocator.GetItemMsgbnd(LanguageFolder); - AssetDescription menuMsgPath = AssetLocator.GetMenuMsgbnd(LanguageFolder); - - FmgInfoBank = new List(); - if (!LoadItemMenuMsgBnds(itemMsgPath, menuMsgPath)) - { - FmgInfoBank = new List(); - IsLoaded = false; - IsLoading = false; - return; - } - - IsLoaded = true; - IsLoading = false; - })); - } - - private static bool ReloadDS2FMGs() - { - SetDefaultLanguagePath(); - AssetDescription desc = AssetLocator.GetItemMsgbnd(LanguageFolder, true); - - if (desc.AssetPath == null) - { - if (LanguageFolder != "") - { - TaskLogs.AddLog($"Could not locate text data files when using \"{LanguageFolder}\" folder", - LogLevel.Warning); - } - else - { - TaskLogs.AddLog("Could not locate text data files when using Default English folder", - LogLevel.Warning); } - - IsLoaded = false; - IsLoading = false; - return false; } - - List files = Directory - .GetFileSystemEntries($@"{AssetLocator.GameRootDirectory}\{desc.AssetPath}", @"*.fmg").ToList(); - FmgInfoBank = new List(); - foreach (var file in files) + } + internal FMGInfo GenerateFMGInfo(BinderFile file) + { + FMG fmg = FMG.Read(file.Bytes); + var name = Enum.GetName(typeof(FmgIDType), file.ID); + FMGInfo info = new() { - var modfile = $@"{AssetLocator.GameModDirectory}\{desc.AssetPath}\{Path.GetFileName(file)}"; - if (AssetLocator.GameModDirectory != null && File.Exists(modfile)) - { - FMG fmg = FMG.Read(modfile); - SetFMGInfoDS2(modfile); - } - else - { - FMG fmg = FMG.Read(file); - SetFMGInfoDS2(file); - } - } + FileSet = this, + FileName = file.Name.Split("\\").Last(), + Name = name, + FmgID = (FmgIDType)file.ID, + Fmg = fmg, + EntryType = FMGEnums.GetFmgTextType(file.ID), + EntryCategory = FMGEnums.GetFmgCategory(file.ID) + }; + info.FileCategory = FMGEnums.GetFMGUICategory(info.EntryCategory); - FmgInfoBank = FmgInfoBank.OrderBy(e => e.Name).ToList(); - HandleDuplicateEntries(); - return true; + ApplyGameDifferences(info); + return info; } - private static void SetDefaultLanguagePath() + internal FMGInfo GenerateFMGInfoDS2(string file) { - if (LanguageFolder == "") + // TODO: DS2 FMG grouping & UI sorting (copy SetFMGInfo) + FMG fmg = FMG.Read(file); + var name = Path.GetFileNameWithoutExtension(file); + FMGInfo info = new() { - // By default, try to find path to English folder. - foreach (KeyValuePair lang in AssetLocator.GetMsgLanguages()) - { - var folder = lang.Value.Split("\\").Last(); - if (folder.Contains("eng", StringComparison.CurrentCultureIgnoreCase)) - { - LanguageFolder = folder; - break; - } - } - } - } + FileSet = this, + FileName = file.Split("\\").Last(), + Name = name, + FmgID = FmgIDType.None, + Fmg = fmg, + EntryType = FmgEntryTextType.TextBody, + EntryCategory = FmgEntryCategory.None, + FileCategory = FmgFileCategory.Loose + }; - private static void SetFMGInfoPatchParent(FMGInfo info) + return info; + } + private void SetFMGInfoPatchParent(FMGInfo info) { - var strippedName = RemovePatchStrings(info.Name); + var strippedName = FMGBank.RemovePatchStrings(info.Name); if (strippedName != info.Name) { // This is a patch FMG, try to find parent FMG. - foreach (FMGInfo parentInfo in FmgInfoBank) + foreach (FMGInfo parentInfo in FmgInfos) { if (parentInfo.Name == strippedName) { @@ -818,16 +202,16 @@ private static void SetFMGInfoPatchParent(FMGInfo info) /// /// Checks and applies FMG info that differs per-game. /// - private static void ApplyGameDifferences(FMGInfo info) + private void ApplyGameDifferences(FMGInfo info) { - GameType gameType = AssetLocator.Type; + GameType gameType = Lang.Owner.Project.AssetLocator.Type; switch (info.FmgID) { case FmgIDType.ReusedFMG_32: if (gameType == GameType.Bloodborne) { info.Name = "GemExtraInfo"; - info.UICategory = FmgUICategory.Item; + info.FileCategory = FmgFileCategory.Item; info.EntryCategory = FmgEntryCategory.Gem; info.EntryType = FmgEntryTextType.ExtraText; } @@ -842,14 +226,14 @@ private static void ApplyGameDifferences(FMGInfo info) if (gameType == GameType.ArmoredCoreVI) { info.Name = "TitleGenerator"; - info.UICategory = FmgUICategory.Item; + info.FileCategory = FmgFileCategory.Item; info.EntryCategory = FmgEntryCategory.Generator; info.EntryType = FmgEntryTextType.Title; } else { info.Name = "TitleGem"; - info.UICategory = FmgUICategory.Item; + info.FileCategory = FmgFileCategory.Item; info.EntryCategory = FmgEntryCategory.Gem; info.EntryType = FmgEntryTextType.Title; } @@ -859,14 +243,14 @@ private static void ApplyGameDifferences(FMGInfo info) if (gameType == GameType.ArmoredCoreVI) { info.Name = "DescriptionGenerator"; - info.UICategory = FmgUICategory.Item; + info.FileCategory = FmgFileCategory.Item; info.EntryCategory = FmgEntryCategory.Generator; info.EntryType = FmgEntryTextType.Description; } else { info.Name = "SummaryGem"; - info.UICategory = FmgUICategory.Item; + info.FileCategory = FmgFileCategory.Item; info.EntryCategory = FmgEntryCategory.Gem; info.EntryType = FmgEntryTextType.Summary; } @@ -876,14 +260,14 @@ private static void ApplyGameDifferences(FMGInfo info) if (gameType == GameType.ArmoredCoreVI) { info.Name = "TitleFCS"; - info.UICategory = FmgUICategory.Item; + info.FileCategory = FmgFileCategory.Item; info.EntryCategory = FmgEntryCategory.FCS; info.EntryType = FmgEntryTextType.Title; } else { info.Name = "TitleMessage"; - info.UICategory = FmgUICategory.Menu; + info.FileCategory = FmgFileCategory.Menu; info.EntryCategory = FmgEntryCategory.Message; info.EntryType = FmgEntryTextType.TextBody; } @@ -893,14 +277,14 @@ private static void ApplyGameDifferences(FMGInfo info) if (gameType == GameType.ArmoredCoreVI) { info.Name = "DescriptionFCS"; - info.UICategory = FmgUICategory.Item; + info.FileCategory = FmgFileCategory.Item; info.EntryCategory = FmgEntryCategory.FCS; info.EntryType = FmgEntryTextType.Description; } else { info.Name = "TitleSwordArts"; - info.UICategory = FmgUICategory.Item; + info.FileCategory = FmgFileCategory.Item; info.EntryCategory = FmgEntryCategory.SwordArts; info.EntryType = FmgEntryTextType.Title; } @@ -961,53 +345,354 @@ private static void ApplyGameDifferences(FMGInfo info) if (gameType == GameType.EldenRing) { info.Name = "ToS_win64"; - info.UICategory = FmgUICategory.Menu; + info.FileCategory = FmgFileCategory.Menu; info.EntryType = FmgEntryTextType.TextBody; info.EntryCategory = FmgEntryCategory.None; } else if (gameType == GameType.ArmoredCoreVI) { info.Name = "TextEmbeddedImageNames"; - info.UICategory = FmgUICategory.Menu; + info.FileCategory = FmgFileCategory.Menu; info.EntryType = FmgEntryTextType.TextBody; info.EntryCategory = FmgEntryCategory.None; } else { info.Name = "TitleGoods_DLC1"; - info.UICategory = FmgUICategory.Item; + info.FileCategory = FmgFileCategory.Item; info.EntryType = FmgEntryTextType.Title; info.EntryCategory = FmgEntryCategory.Goods; - FMGInfo parent = FmgInfoBank.Find(e => e.FmgID == FmgIDType.TitleGoods); + FMGInfo parent = FmgInfos.FirstOrDefault(e => e.FmgID == FmgIDType.TitleGoods); info.AddParent(parent); } break; } } +} + +/* + * FMGLanguage represents a grouped set of FMGFileSets containing FMGInfos sourced from the same language, within a project's FMGBank. + */ +public class FMGLanguage +{ + internal FMGLanguage(FMGBank owner, string language) + { + Owner = owner; + LanguageFolder = language; + } + internal readonly FMGBank Owner; + internal readonly string LanguageFolder; + internal bool IsLoaded => _FmgInfoBanks.Count != 0 && _FmgInfoBanks.All((fs) => fs.Value.IsLoaded); + internal bool IsLoading => _FmgInfoBanks.Count != 0 && _FmgInfoBanks.Any((fs) => fs.Value.IsLoading); + internal readonly Dictionary _FmgInfoBanks = new(); /// - /// Get patched FMG Entries for the specified category, with TextType Title or TextBody. + /// Loads item and menu MsgBnds from paths, generates FMGInfo, and fills FmgInfoBank. /// - /// FMGEntryCategory. If "None", an empty list will be returned. - /// List of patched entries if found; empty list otherwise. - public static List GetFmgEntriesByCategory(FmgEntryCategory category, bool sort = true) + /// True if successful; false otherwise. + internal bool LoadItemMenuMsgBnds(AssetDescription itemMsgPath, AssetDescription menuMsgPath) { - if (category == FmgEntryCategory.None) + FMGFileSet itemMsgBnd = new FMGFileSet(this, FmgFileCategory.Item); + if (itemMsgBnd.LoadMsgBnd(itemMsgPath.AssetPath, "item.msgbnd")) + _FmgInfoBanks.Add(itemMsgBnd.FileCategory, itemMsgBnd); + FMGFileSet menuMsgBnd = new FMGFileSet(this, FmgFileCategory.Menu); + if (menuMsgBnd.LoadMsgBnd(menuMsgPath.AssetPath, "menu.msgbnd")) + _FmgInfoBanks.Add(menuMsgBnd.FileCategory, menuMsgBnd); + if (_FmgInfoBanks.Count == 0) + return false; + return true; + } + + internal bool LoadNormalFmgs() + { + AssetDescription itemMsgPath = Owner.Project.AssetLocator.GetItemMsgbnd(LanguageFolder); + AssetDescription menuMsgPath = Owner.Project.AssetLocator.GetMenuMsgbnd(LanguageFolder); + if (LoadItemMenuMsgBnds(itemMsgPath, menuMsgPath)) { - return new List(); + return true; } + return false; - foreach (FMGInfo info in FmgInfoBank) + } + internal bool LoadDS2FMGs() + { + AssetDescription desc = Owner.Project.AssetLocator.GetItemMsgbnd(LanguageFolder, true); + + if (desc.AssetPath == null) { - if (info.EntryCategory == category && - info.EntryType is FmgEntryTextType.Title or FmgEntryTextType.TextBody) + if (LanguageFolder != "") { - return info.GetPatchedEntries(sort); + TaskLogs.AddLog($"Could not locate text data files when using \"{LanguageFolder}\" folder", + LogLevel.Warning); } - } - - return new List(); + else + { + TaskLogs.AddLog("Could not locate text data files when using Default English folder", + LogLevel.Warning); + } + return false; + } + + IEnumerable files = Owner.Project.AssetLocator.GetAllAssets($@"{desc.AssetPath}", [@"*.fmg"]); + + FMGFileSet looseMsg = new FMGFileSet(this, FmgFileCategory.Loose); + if (looseMsg.LoadLooseMsgsDS2(files)) + { + _FmgInfoBanks.Add(looseMsg.FileCategory, looseMsg); + return true; + } + return false; + } + + public void SaveFMGs() + { + try + { + if (!IsLoaded) + { + return; + } + + if (Owner.Project.AssetLocator.Type == GameType.Undefined) + { + return; + } + + if (Owner.Project.AssetLocator.Type == GameType.DarkSoulsIISOTFS) + { + SaveFMGsDS2(); + } + else + { + SaveFMGsNormal(); + } + TaskLogs.AddLog("Saved FMG text"); + } + catch (SavingFailedException e) + { + TaskLogs.AddLog(e.Wrapped.Message, + LogLevel.Error, TaskLogs.LogPriority.High, e.Wrapped); + } + } + + private void SaveFMGsDS2() + { + foreach (FMGInfo info in _FmgInfoBanks.SelectMany((x) => x.Value.FmgInfos)) + { + Utils.WriteWithBackup(Owner.Project.ParentProject.AssetLocator.RootDirectory, Owner.Project.AssetLocator.RootDirectory, + $@"menu\text\{LanguageFolder}\{info.Name}.fmg", info.Fmg); + } + } + private void SaveFMGsNormal() + { + // Load the fmg bnd, replace fmgs, and save + IBinder fmgBinderItem; + IBinder fmgBinderMenu; + AssetDescription itemMsgPath = Owner.Project.AssetLocator.GetItemMsgbnd(LanguageFolder); + AssetDescription menuMsgPath = Owner.Project.AssetLocator.GetMenuMsgbnd(LanguageFolder); + if (Owner.Project.AssetLocator.Type == GameType.DemonsSouls || Owner.Project.AssetLocator.Type == GameType.DarkSoulsPTDE || + Owner.Project.AssetLocator.Type == GameType.DarkSoulsRemastered) + { + fmgBinderItem = BND3.Read(itemMsgPath.AssetPath); + fmgBinderMenu = BND3.Read(menuMsgPath.AssetPath); + } + else + { + fmgBinderItem = BND4.Read(itemMsgPath.AssetPath); + fmgBinderMenu = BND4.Read(menuMsgPath.AssetPath); + } + + foreach (BinderFile file in fmgBinderItem.Files) + { + FMGInfo info = _FmgInfoBanks.SelectMany((x) => x.Value.FmgInfos).FirstOrDefault(e => e.FmgID == (FmgIDType)file.ID); + if (info != null) + { + file.Bytes = info.Fmg.Write(); + } + } + + foreach (BinderFile file in fmgBinderMenu.Files) + { + FMGInfo info = _FmgInfoBanks.SelectMany((x) => x.Value.FmgInfos).FirstOrDefault(e => e.FmgID == (FmgIDType)file.ID); + if (info != null) + { + file.Bytes = info.Fmg.Write(); + } + } + + AssetDescription itemMsgPathDest = Owner.Project.AssetLocator.GetItemMsgbnd(LanguageFolder, true); + AssetDescription menuMsgPathDest = Owner.Project.AssetLocator.GetMenuMsgbnd(LanguageFolder, true); + var parentDir = Owner.Project.ParentProject.AssetLocator.RootDirectory; + var modDir = Owner.Project.AssetLocator.RootDirectory; + if (fmgBinderItem is BND3 bnd3) + { + Utils.WriteWithBackup(parentDir, modDir, itemMsgPathDest.AssetPath, bnd3); + Utils.WriteWithBackup(parentDir, modDir, menuMsgPathDest.AssetPath, (BND3)fmgBinderMenu); + if (Owner.Project.AssetLocator.Type is GameType.DemonsSouls) + { + bnd3.Compression = DCX.Type.None; + ((BND3)fmgBinderMenu).Compression = DCX.Type.None; + Utils.WriteWithBackup(parentDir, modDir, itemMsgPathDest.AssetPath[..^4], bnd3); + Utils.WriteWithBackup(parentDir, modDir, menuMsgPathDest.AssetPath[..^4], (BND3)fmgBinderMenu); + } + } + else if (fmgBinderItem is BND4 bnd4) + { + Utils.WriteWithBackup(parentDir, modDir, itemMsgPathDest.AssetPath, bnd4); + Utils.WriteWithBackup(parentDir, modDir, menuMsgPathDest.AssetPath, (BND4)fmgBinderMenu); + } + + fmgBinderItem.Dispose(); + fmgBinderMenu.Dispose(); + } +} + +/// +/// Class that stores all the strings for a Souls project. +/// +public class FMGBank +{ + public Project Project; + + public FMGBank(Project project) + { + Project = project; + } + + + public bool IsLoaded => fmgLangs.Count != 0 && fmgLangs.All((fs) => fs.Value.IsLoaded); + public bool IsLoading => fmgLangs.Count != 0 && fmgLangs.Any((fs) => fs.Value.IsLoading); + public string LanguageFolder { get; private set; } = ""; + + public IEnumerable FmgInfoBank { get => fmgLangs[LanguageFolder]._FmgInfoBanks.SelectMany((x) => x.Value.FmgInfos); } + public IEnumerable SortedFmgInfoBank { + get { + //This check shouldn't be here. Should do better housekeeping. + if (IsLoading || !IsLoaded || !fmgLangs.ContainsKey(LanguageFolder)) + { + return []; + } + if (CFG.Current.FMG_NoGroupedFmgEntries) + { + return FmgInfoBank.OrderBy(e => e.EntryCategory).ThenBy(e => e.FmgID); + } + else + { + return FmgInfoBank.OrderBy(e => e.Name); + } + } + } + public Dictionary fmgLangs = new(); + public IEnumerable currentFmgInfoBanks { + get { + if (IsLoading || !IsLoaded || !fmgLangs.ContainsKey(LanguageFolder)) + { + return []; + } + return fmgLangs[LanguageFolder]._FmgInfoBanks.Keys; + } + } + + + /// + /// List of strings to compare with "FmgIDType" name to identify patch FMGs. + /// + private static readonly List patchStrings = new() { "_Patch", "_DLC1", "_DLC2" }; + /// + /// Removes patch/DLC identifiers from strings for the purposes of finding patch FMGs. Kinda dumb. + /// + internal static string RemovePatchStrings(string str) + { + foreach (var badString in patchStrings) + { + str = str.Replace(badString, "", StringComparison.CurrentCultureIgnoreCase); + } + + return str; + } + public static void ReloadFMGs() + { + Locator.ActiveProject.FMGBank.LoadFMGs(); + } + public void LoadFMGs(string languageFolder = "") + { + TaskManager.Run(new TaskManager.LiveTask("FMG - Load Text - " + languageFolder, TaskManager.RequeueType.WaitThenRequeue, true, + () => + { + LanguageFolder = languageFolder; + SetDefaultLanguagePath(); + if (fmgLangs.ContainsKey(LanguageFolder)) + { + return; + } + + if (Project.AssetLocator.Type == GameType.Undefined) + { + return; + } + + FMGLanguage lang = new FMGLanguage(this, LanguageFolder); + bool success = false; + if (Project.AssetLocator.Type == GameType.DarkSoulsIISOTFS) + { + success = lang.LoadDS2FMGs(); + } + else + { + success = lang.LoadNormalFmgs(); + } + if (success) + fmgLangs.Add(lang.LanguageFolder, lang); + })); + } + + public void SaveFMGs() + { + foreach (FMGLanguage lang in fmgLangs.Values) + { + lang.SaveFMGs(); + } + } + private void SetDefaultLanguagePath() + { + if (LanguageFolder == "") + { + // By default, try to find path to English folder. + foreach (KeyValuePair lang in Project.AssetLocator.GetMsgLanguages()) + { + var folder = lang.Value.Split("\\").Last(); + if (folder.Contains("eng", StringComparison.CurrentCultureIgnoreCase)) + { + LanguageFolder = folder; + break; + } + } + } + } + + /// + /// Get patched FMG Entries for the specified category, with TextType Title or TextBody. + /// + /// FMGEntryCategory. If "None", an empty list will be returned. + /// List of patched entries if found; empty list otherwise. + public List GetFmgEntriesByCategory(FmgEntryCategory category, bool sort = true) + { + if (category == FmgEntryCategory.None) + { + return new List(); + } + + foreach (FMGInfo info in FmgInfoBank) + { + if (info.EntryCategory == category && + info.EntryType is FmgEntryTextType.Title or FmgEntryTextType.TextBody) + { + return info.GetPatchedEntries(sort); + } + } + + return new List(); } /// @@ -1015,7 +700,7 @@ private static void ApplyGameDifferences(FMGInfo info) /// /// FMGEntryCategory . If "None", an empty list will be returned. /// List of patched entries if found; empty list otherwise. - public static List GetFmgEntriesByCategoryAndTextType(FmgEntryCategory category, + public List GetFmgEntriesByCategoryAndTextType(FmgEntryCategory category, FmgEntryTextType textType, bool sort = true) { if (category == FmgEntryCategory.None) @@ -1038,7 +723,7 @@ private static void ApplyGameDifferences(FMGInfo info) /// Get patched FMG Entries for the specified FmgIDType. /// /// List of patched entries if found; empty list otherwise. - public static List GetFmgEntriesByFmgIDType(FmgIDType fmgID, bool sort = true) + public List GetFmgEntriesByFmgIDType(FmgIDType fmgID, bool sort = true) { foreach (FMGInfo info in FmgInfoBank) { @@ -1055,9 +740,9 @@ private static void ApplyGameDifferences(FMGInfo info) /// Generate a new EntryGroup using a given ID and FMGInfo. /// Data is updated using FMGInfo PatchChildren. /// - public static EntryGroup GenerateEntryGroup(int id, FMGInfo fmgInfo) + public FMGEntryGroup GenerateEntryGroup(int id, FMGInfo fmgInfo) { - EntryGroup eGroup = new() { ID = id }; + FMGEntryGroup eGroup = new() { ID = id }; if (fmgInfo.EntryCategory == FmgEntryCategory.None || CFG.Current.FMG_NoGroupedFmgEntries) { @@ -1110,224 +795,80 @@ public static EntryGroup GenerateEntryGroup(int id, FMGInfo fmgInfo) return eGroup; } +} - private static void HandleDuplicateEntries() - { - var askedAboutDupes = false; - var ignoreDupes = true; - foreach (FMGInfo info in FmgInfoBank) - { - IEnumerable dupes = info.Fmg.Entries.GroupBy(e => e.ID).SelectMany(g => g.SkipLast(1)); - if (dupes.Any()) - { - var dupeList = string.Join(", ", dupes.Select(dupe => dupe.ID)); - if (!askedAboutDupes && PlatformUtils.Instance.MessageBox( - $"Duplicate text entries have been found in FMG {Path.GetFileNameWithoutExtension(info.FileName)} for the following row IDs:\n\n{dupeList}\n\nRemove all duplicates? (Latest entries are kept)", - "Duplicate Text Entries", MessageBoxButtons.YesNo) == DialogResult.Yes) - { - ignoreDupes = false; - } - - askedAboutDupes = true; - - if (!ignoreDupes) - { - foreach (FMG.Entry dupe in dupes) - { - info.Fmg.Entries.Remove(dupe); - } - } - } - } - } - - private static string FormatJson(string json) - { - json = json.Replace("{\"ID\"", "\r\n{\"ID\""); - json = json.Replace("],", "\r\n],"); - return json; - } - - private static void SaveFMGsDS2() +/// +/// Value pair with an entry and the FMG it belongs to. +/// +public class EntryFMGInfoPair +{ + public EntryFMGInfoPair(FMGInfo fmgInfo, FMG.Entry entry) { - foreach (FMGInfo info in FmgInfoBank) - { - Utils.WriteWithBackup(AssetLocator.GameRootDirectory, AssetLocator.GameModDirectory, - $@"menu\text\{LanguageFolder}\{info.Name}.fmg", info.Fmg); - } + FmgInfo = fmgInfo; + Entry = entry; } - public static void SaveFMGs() - { - try - { - if (!IsLoaded) - { - return; - } - - if (AssetLocator.Type == GameType.Undefined) - { - return; - } - - if (AssetLocator.Type == GameType.DarkSoulsIISOTFS) - { - SaveFMGsDS2(); - TaskLogs.AddLog("Saved FMG text"); - return; - } + public FMGInfo FmgInfo { get; set; } + public FMG.Entry Entry { get; set; } +} - // Load the fmg bnd, replace fmgs, and save - IBinder fmgBinderItem; - IBinder fmgBinderMenu; - AssetDescription itemMsgPath = AssetLocator.GetItemMsgbnd(LanguageFolder); - AssetDescription menuMsgPath = AssetLocator.GetMenuMsgbnd(LanguageFolder); - if (AssetLocator.Type == GameType.DemonsSouls || AssetLocator.Type == GameType.DarkSoulsPTDE || - AssetLocator.Type == GameType.DarkSoulsRemastered) - { - fmgBinderItem = BND3.Read(itemMsgPath.AssetPath); - fmgBinderMenu = BND3.Read(menuMsgPath.AssetPath); - } - else - { - fmgBinderItem = BND4.Read(itemMsgPath.AssetPath); - fmgBinderMenu = BND4.Read(menuMsgPath.AssetPath); - } +/// +/// Base object that stores an FMG and information regarding it. +/// +public class FMGInfo +{ + public FMGFileSet FileSet; - foreach (BinderFile file in fmgBinderItem.Files) - { - FMGInfo info = FmgInfoBank.Find(e => e.FmgID == (FmgIDType)file.ID); - if (info != null) - { - file.Bytes = info.Fmg.Write(); - } - } + public FmgEntryCategory EntryCategory; + public FmgEntryTextType EntryType; + public string FileName; + public FMG Fmg; + public FmgIDType FmgID; + public string Name; - foreach (BinderFile file in fmgBinderMenu.Files) - { - FMGInfo info = FmgInfoBank.Find(e => e.FmgID == (FmgIDType)file.ID); - if (info != null) - { - file.Bytes = info.Fmg.Write(); - } - } + /// + /// List of associated children to this FMGInfo used to get patch entry data. + /// + public List PatchChildren = new(); - AssetDescription itemMsgPathDest = AssetLocator.GetItemMsgbnd(LanguageFolder, true); - AssetDescription menuMsgPathDest = AssetLocator.GetMenuMsgbnd(LanguageFolder, true); - if (fmgBinderItem is BND3 bnd3) - { - Utils.WriteWithBackup(AssetLocator.GameRootDirectory, - AssetLocator.GameModDirectory, itemMsgPathDest.AssetPath, bnd3); - Utils.WriteWithBackup(AssetLocator.GameRootDirectory, - AssetLocator.GameModDirectory, menuMsgPathDest.AssetPath, (BND3)fmgBinderMenu); - if (AssetLocator.Type is GameType.DemonsSouls) - { - bnd3.Compression = DCX.Type.None; - ((BND3)fmgBinderMenu).Compression = DCX.Type.None; - Utils.WriteWithBackup(AssetLocator.GameRootDirectory, - AssetLocator.GameModDirectory, itemMsgPathDest.AssetPath[..^4], bnd3); - Utils.WriteWithBackup(AssetLocator.GameRootDirectory, - AssetLocator.GameModDirectory, menuMsgPathDest.AssetPath[..^4], (BND3)fmgBinderMenu); - } - } - else if (fmgBinderItem is BND4 bnd4) - { - Utils.WriteWithBackup(AssetLocator.GameRootDirectory, - AssetLocator.GameModDirectory, itemMsgPathDest.AssetPath, bnd4); - Utils.WriteWithBackup(AssetLocator.GameRootDirectory, - AssetLocator.GameModDirectory, menuMsgPathDest.AssetPath, (BND4)fmgBinderMenu); - } + public FMGInfo PatchParent; + public FmgFileCategory FileCategory; - fmgBinderItem.Dispose(); - fmgBinderMenu.Dispose(); - TaskLogs.AddLog("Saved FMG text"); - } - catch (SavingFailedException e) + private string _patchPrefix = null; + public string PatchPrefix + { + get { - TaskLogs.AddLog(e.Wrapped.Message, - LogLevel.Error, TaskLogs.LogPriority.High, e.Wrapped); + _patchPrefix ??= Name.Replace(FMGBank.RemovePatchStrings(Name), ""); + return _patchPrefix; } } - public static void SetAssetLocator(AssetLocator l) + public void AddParent(FMGInfo parent) { - AssetLocator = l; + PatchParent = parent; + parent.PatchChildren.Add(this); } /// - /// Value pair with an entry and the FMG it belongs to. + /// Returns a patched list of Entry & FMGInfo value pairs from this FMGInfo and its children. + /// If a PatchParent exists, it will be checked instead. /// - public class EntryFMGInfoPair + public List GetPatchedEntryFMGPairs(bool sort = true) { - public EntryFMGInfoPair(FMGInfo fmgInfo, FMG.Entry entry) + if (PatchParent != null && !CFG.Current.FMG_NoFmgPatching) { - FmgInfo = fmgInfo; - Entry = entry; + return PatchParent.GetPatchedEntryFMGPairs(sort); } - public FMGInfo FmgInfo { get; set; } - public FMG.Entry Entry { get; set; } - } - - /// - /// Base object that stores an FMG and information regarding it. - /// - public class FMGInfo - { - public FmgEntryCategory EntryCategory; - public FmgEntryTextType EntryType; - public string FileName; - public FMG Fmg; - public FmgIDType FmgID; - public string Name; - - /// - /// List of associated children to this FMGInfo used to get patch entry data. - /// - public List PatchChildren = new(); - - public FMGInfo PatchParent; - public FmgUICategory UICategory; - - private string _patchPrefix = null; - public string PatchPrefix + List list = new(); + foreach (FMG.Entry entry in Fmg.Entries) { - get - { - _patchPrefix ??= Name.Replace(RemovePatchStrings(Name), ""); - return _patchPrefix; - } + list.Add(new EntryFMGInfoPair(this, entry)); } - public void AddParent(FMGInfo parent) - { - if (CFG.Current.FMG_NoFmgPatching) - { - return; - } - - PatchParent = parent; - parent.PatchChildren.Add(this); - } - - /// - /// Returns a patched list of Entry & FMGInfo value pairs from this FMGInfo and its children. - /// If a PatchParent exists, it will be checked instead. - /// - public List GetPatchedEntryFMGPairs(bool sort = true) + if (!CFG.Current.FMG_NoFmgPatching) { - if (PatchParent != null) - { - return PatchParent.GetPatchedEntryFMGPairs(sort); - } - - List list = new(); - foreach (FMG.Entry entry in Fmg.Entries) - { - list.Add(new EntryFMGInfoPair(this, entry)); - } - // Check and apply patch entries foreach (FMGInfo child in PatchChildren.OrderBy(e => (int)e.FmgID)) { @@ -1350,29 +891,32 @@ public List GetPatchedEntryFMGPairs(bool sort = true) } } } + } - if (sort) - { - list = list.OrderBy(e => e.Entry.ID).ToList(); - } - - return list; + if (sort) + { + list = list.OrderBy(e => e.Entry.ID).ToList(); } - /// - /// Returns a patched list of entries in this FMGInfo and its children. - /// If a PatchParent exists, it will be checked instead. - /// - public List GetPatchedEntries(bool sort = true) + return list; + } + + /// + /// Returns a patched list of entries in this FMGInfo and its children. + /// If a PatchParent exists, it will be checked instead. + /// + public List GetPatchedEntries(bool sort = true) + { + if (PatchParent != null && !CFG.Current.FMG_NoFmgPatching) { - if (PatchParent != null) - { - return PatchParent.GetPatchedEntries(sort); - } + return PatchParent.GetPatchedEntries(sort); + } - List list = new(); - list.AddRange(Fmg.Entries); + List list = new(); + list.AddRange(Fmg.Entries); + if (!CFG.Current.FMG_NoFmgPatching) + { // Check and apply patch entries foreach (FMGInfo child in PatchChildren.OrderBy(e => (int)e.FmgID)) { @@ -1395,314 +939,313 @@ public List GetPatchedEntryFMGPairs(bool sort = true) } } } - - if (sort) - { - list = list.OrderBy(e => e.ID).ToList(); - } - - return list; - } - - /// - /// Returns title FMGInfo that shares this FMGInfo's EntryCategory. - /// If none are found, an exception will be thrown. - /// - public FMGInfo GetTitleFmgInfo() - { - foreach (var info in FMGBank.FmgInfoBank) - { - if (info.EntryCategory == EntryCategory && info.EntryType == FmgEntryTextType.Title && info.PatchPrefix == PatchPrefix) - { - return info; - } - } - throw new InvalidOperationException($"Couldn't find title FMGInfo for {this.Name}"); } - /// - /// Adds an entry to the end of the FMG. - /// - public void AddEntry(FMG.Entry entry) + if (sort) { - Fmg.Entries.Add(entry); + list = list.OrderBy(e => e.ID).ToList(); } - /// - /// Clones an FMG entry. - /// - /// Cloned entry - public FMG.Entry CloneEntry(FMG.Entry entry) - { - FMG.Entry newEntry = new(entry.ID, entry.Text); - return newEntry; - } - - /// - /// Removes an entry from FMGInfo's FMG. - /// - public void DeleteEntry(FMG.Entry entry) - { - Fmg.Entries.Remove(entry); - } + return list; } - /// - /// A group of entries that may be associated (such as title, summary, description) along with respective FMGs. + /// Returns title FMGInfo that shares this FMGInfo's EntryCategory. + /// If none are found, an exception will be thrown. /// - public class EntryGroup + public FMGInfo GetTitleFmgInfo() { - private int _ID = -1; - public FMG.Entry Description; - public FMGInfo DescriptionInfo; - public FMG.Entry ExtraText; - public FMGInfo ExtraTextInfo; - public FMG.Entry Summary; - public FMGInfo SummaryInfo; - public FMG.Entry TextBody; - public FMGInfo TextBodyInfo; - public FMG.Entry Title; - public FMGInfo TitleInfo; - - public int ID - { - set + foreach (var info in FileSet.FmgInfos) + { + if (info.EntryCategory == EntryCategory && info.EntryType == FmgEntryTextType.Title && info.PatchPrefix == PatchPrefix) { - _ID = value; - if (TextBody != null) - { - TextBody.ID = _ID; - } - - if (Title != null) - { - Title.ID = _ID; - } - - if (Summary != null) - { - Summary.ID = _ID; - } - - if (Description != null) - { - Description.ID = _ID; - } - - if (ExtraText != null) - { - ExtraText.ID = _ID; - } + return info; } - get => _ID; } + throw new InvalidOperationException($"Couldn't find title FMGInfo for {this.Name}"); + } - /// - /// Gets next unused entry ID. - /// - public int GetNextUnusedID() - { - var id = ID; - if (TextBody != null) - { - List entries = TextBodyInfo.GetPatchedEntries(); - do - { - id++; - } while (entries.Find(e => e.ID == id) != null); - } - else if (Title != null) - { - List entries = TitleInfo.GetPatchedEntries(); - do - { - id++; - } while (entries.Find(e => e.ID == id) != null); - } - else if (Summary != null) - { - List entries = SummaryInfo.GetPatchedEntries(); - do - { - id++; - } while (entries.Find(e => e.ID == id) != null); - } - else if (Description != null) - { - List entries = DescriptionInfo.GetPatchedEntries(); - do - { - id++; - } while (entries.Find(e => e.ID == id) != null); - } - else if (ExtraText != null) - { - List entries = ExtraTextInfo.GetPatchedEntries(); - do - { - id++; - } while (entries.Find(e => e.ID == id) != null); - } + /// + /// Adds an entry to the end of the FMG. + /// + public void AddEntry(FMG.Entry entry) + { + Fmg.Entries.Add(entry); + } - return id; - } + /// + /// Clones an FMG entry. + /// + /// Cloned entry + public FMG.Entry CloneEntry(FMG.Entry entry) + { + FMG.Entry newEntry = new(entry.ID, entry.Text); + return newEntry; + } - /// - /// Sets ID of all entries to the next unused entry ID. - /// - public void SetNextUnusedID() - { - ID = GetNextUnusedID(); - } + /// + /// Removes an entry from FMGInfo's FMG. + /// + public void DeleteEntry(FMG.Entry entry) + { + Fmg.Entries.Remove(entry); + } +} - /// - /// Places all entries within this EntryGroup into their assigned FMGs. - /// - public void ImplementEntryGroup() +/// +/// A group of entries that may be associated (such as title, summary, description) along with respective FMGs. +/// +public class FMGEntryGroup +{ + private int _ID = -1; + public FMG.Entry Description; + public FMGInfo DescriptionInfo; + public FMG.Entry ExtraText; + public FMGInfo ExtraTextInfo; + public FMG.Entry Summary; + public FMGInfo SummaryInfo; + public FMG.Entry TextBody; + public FMGInfo TextBodyInfo; + public FMG.Entry Title; + public FMGInfo TitleInfo; + + public int ID + { + set { + _ID = value; if (TextBody != null) { - TextBodyInfo.AddEntry(TextBody); + TextBody.ID = _ID; } if (Title != null) { - TitleInfo.AddEntry(Title); + Title.ID = _ID; } if (Summary != null) { - SummaryInfo.AddEntry(Summary); + Summary.ID = _ID; } if (Description != null) { - DescriptionInfo.AddEntry(Description); + Description.ID = _ID; } if (ExtraText != null) { - ExtraTextInfo.AddEntry(ExtraText); + ExtraText.ID = _ID; } } + get => _ID; + } - /// - /// Duplicates all entries within their assigned FMGs. - /// New entries are inserted into their assigned FMGs. - /// - /// New EntryGroup. - public EntryGroup DuplicateFMGEntries() + /// + /// Gets next unused entry ID. + /// + public int GetNextUnusedID() + { + var id = ID; + if (TextBody != null) { - EntryGroup newGroup = new(); - if (TextBody != null) + List entries = TextBodyInfo.GetPatchedEntries(); + do { - newGroup.TextBodyInfo = TextBodyInfo; - newGroup.TextBody = TextBodyInfo.CloneEntry(TextBody); - TextBodyInfo.AddEntry(newGroup.TextBody); - } - - if (Title != null) + id++; + } while (entries.Find(e => e.ID == id) != null); + } + else if (Title != null) + { + List entries = TitleInfo.GetPatchedEntries(); + do { - newGroup.TitleInfo = TitleInfo; - newGroup.Title = TitleInfo.CloneEntry(Title); - TitleInfo.AddEntry(newGroup.Title); - } - - if (Summary != null) + id++; + } while (entries.Find(e => e.ID == id) != null); + } + else if (Summary != null) + { + List entries = SummaryInfo.GetPatchedEntries(); + do { - newGroup.SummaryInfo = SummaryInfo; - newGroup.Summary = SummaryInfo.CloneEntry(Summary); - SummaryInfo.AddEntry(newGroup.Summary); - } - - if (Description != null) + id++; + } while (entries.Find(e => e.ID == id) != null); + } + else if (Description != null) + { + List entries = DescriptionInfo.GetPatchedEntries(); + do { - newGroup.DescriptionInfo = DescriptionInfo; - newGroup.Description = DescriptionInfo.CloneEntry(Description); - DescriptionInfo.AddEntry(newGroup.Description); - } - - if (ExtraText != null) + id++; + } while (entries.Find(e => e.ID == id) != null); + } + else if (ExtraText != null) + { + List entries = ExtraTextInfo.GetPatchedEntries(); + do { - newGroup.ExtraTextInfo = ExtraTextInfo; - newGroup.ExtraText = ExtraTextInfo.CloneEntry(ExtraText); - ExtraTextInfo.AddEntry(newGroup.ExtraText); - } + id++; + } while (entries.Find(e => e.ID == id) != null); + } + + return id; + } - newGroup.ID = ID; - return newGroup; + /// + /// Sets ID of all entries to the next unused entry ID. + /// + public void SetNextUnusedID() + { + ID = GetNextUnusedID(); + } + + /// + /// Places all entries within this EntryGroup into their assigned FMGs. + /// + public void ImplementEntryGroup() + { + if (TextBody != null) + { + TextBodyInfo.AddEntry(TextBody); } - /// - /// Clones this EntryGroup and returns a duplicate. - /// - /// Cloned EntryGroup. - public EntryGroup CloneEntryGroup() + if (Title != null) { - EntryGroup newGroup = new(); - if (TextBody != null) - { - newGroup.TextBodyInfo = TextBodyInfo; - newGroup.TextBody = TextBodyInfo.CloneEntry(TextBody); - } + TitleInfo.AddEntry(Title); + } - if (Title != null) - { - newGroup.TitleInfo = TitleInfo; - newGroup.Title = TitleInfo.CloneEntry(Title); - } + if (Summary != null) + { + SummaryInfo.AddEntry(Summary); + } - if (Summary != null) - { - newGroup.SummaryInfo = SummaryInfo; - newGroup.Summary = SummaryInfo.CloneEntry(Summary); - } + if (Description != null) + { + DescriptionInfo.AddEntry(Description); + } - if (Description != null) - { - newGroup.DescriptionInfo = DescriptionInfo; - newGroup.Description = DescriptionInfo.CloneEntry(Description); - } + if (ExtraText != null) + { + ExtraTextInfo.AddEntry(ExtraText); + } + } - if (ExtraText != null) - { - newGroup.ExtraTextInfo = ExtraTextInfo; - newGroup.ExtraText = ExtraTextInfo.CloneEntry(ExtraText); - } + /// + /// Duplicates all entries within their assigned FMGs. + /// New entries are inserted into their assigned FMGs. + /// + /// New EntryGroup. + public FMGEntryGroup DuplicateFMGEntries() + { + FMGEntryGroup newGroup = new(); + if (TextBody != null) + { + newGroup.TextBodyInfo = TextBodyInfo; + newGroup.TextBody = TextBodyInfo.CloneEntry(TextBody); + TextBodyInfo.AddEntry(newGroup.TextBody); + } - return newGroup; + if (Title != null) + { + newGroup.TitleInfo = TitleInfo; + newGroup.Title = TitleInfo.CloneEntry(Title); + TitleInfo.AddEntry(newGroup.Title); } - /// - /// Removes all entries from their assigned FMGs. - /// - public void DeleteEntries() + if (Summary != null) { - if (TextBody != null) - { - TextBodyInfo.DeleteEntry(TextBody); - } + newGroup.SummaryInfo = SummaryInfo; + newGroup.Summary = SummaryInfo.CloneEntry(Summary); + SummaryInfo.AddEntry(newGroup.Summary); + } - if (Title != null) - { - TitleInfo.DeleteEntry(Title); - } + if (Description != null) + { + newGroup.DescriptionInfo = DescriptionInfo; + newGroup.Description = DescriptionInfo.CloneEntry(Description); + DescriptionInfo.AddEntry(newGroup.Description); + } - if (Summary != null) - { - SummaryInfo.DeleteEntry(Summary); - } + if (ExtraText != null) + { + newGroup.ExtraTextInfo = ExtraTextInfo; + newGroup.ExtraText = ExtraTextInfo.CloneEntry(ExtraText); + ExtraTextInfo.AddEntry(newGroup.ExtraText); + } - if (Description != null) - { - DescriptionInfo.DeleteEntry(Description); - } + newGroup.ID = ID; + return newGroup; + } - if (ExtraText != null) - { - ExtraTextInfo.DeleteEntry(ExtraText); - } + /// + /// Clones this EntryGroup and returns a duplicate. + /// + /// Cloned EntryGroup. + public FMGEntryGroup CloneEntryGroup() + { + FMGEntryGroup newGroup = new(); + if (TextBody != null) + { + newGroup.TextBodyInfo = TextBodyInfo; + newGroup.TextBody = TextBodyInfo.CloneEntry(TextBody); + } + + if (Title != null) + { + newGroup.TitleInfo = TitleInfo; + newGroup.Title = TitleInfo.CloneEntry(Title); + } + + if (Summary != null) + { + newGroup.SummaryInfo = SummaryInfo; + newGroup.Summary = SummaryInfo.CloneEntry(Summary); + } + + if (Description != null) + { + newGroup.DescriptionInfo = DescriptionInfo; + newGroup.Description = DescriptionInfo.CloneEntry(Description); + } + + if (ExtraText != null) + { + newGroup.ExtraTextInfo = ExtraTextInfo; + newGroup.ExtraText = ExtraTextInfo.CloneEntry(ExtraText); + } + + return newGroup; + } + + /// + /// Removes all entries from their assigned FMGs. + /// + public void DeleteEntries() + { + if (TextBody != null) + { + TextBodyInfo.DeleteEntry(TextBody); + } + + if (Title != null) + { + TitleInfo.DeleteEntry(Title); + } + + if (Summary != null) + { + SummaryInfo.DeleteEntry(Summary); + } + + if (Description != null) + { + DescriptionInfo.DeleteEntry(Description); + } + + if (ExtraText != null) + { + ExtraTextInfo.DeleteEntry(ExtraText); } } } diff --git a/src/StudioCore/TextEditor/FMGEditor.cs b/src/StudioCore/TextEditor/FMGEditor.cs index fb1eff176..4eb7a7ed5 100644 --- a/src/StudioCore/TextEditor/FMGEditor.cs +++ b/src/StudioCore/TextEditor/FMGEditor.cs @@ -10,7 +10,7 @@ namespace StudioCore.TextEditor; public class PropertyEditor { - private FMGBank.EntryGroup _eGroupCache; + private FMGEntryGroup _eGroupCache; private FMG.Entry _entryCache; private int _fmgID; @@ -265,7 +265,7 @@ public unsafe void PropEditorFMG(FMG.Entry entry, string name) _fmgID++; } - public void PropIDFMG(FMGBank.EntryGroup eGroup, List entryCache) + public void PropIDFMG(FMGEntryGroup eGroup, List entryCache) { var oldID = eGroup.ID; var id = oldID; diff --git a/src/StudioCore/TextEditor/FMGEnums.cs b/src/StudioCore/TextEditor/FMGEnums.cs new file mode 100644 index 000000000..9c122bc62 --- /dev/null +++ b/src/StudioCore/TextEditor/FMGEnums.cs @@ -0,0 +1,517 @@ +namespace StudioCore.TextEditor; + +/// +/// FMG sections in UI +/// +public enum FmgFileCategory +{ + Loose = 0, + Item = 1, + Menu = 2 +} + +/// +/// Entry type for Title, Summary, Description, or other. +/// +public enum FmgEntryTextType +{ + TextBody = 0, + Title = 1, + Summary = 2, + Description = 3, + ExtraText = 4 +} + +/// +/// Text categories used for grouping multiple FMGs or broad identification +/// +public enum FmgEntryCategory +{ + None = -1, + Goods, + Weapons, + Armor, + Rings, + Spells, + Characters, + Locations, + Gem, + Message, + SwordArts, + Effect, + ActionButtonText, + Tutorial, + LoadingScreen, + Generator, + Booster, + FCS, + Mission, + Archive, + + ItemFmgDummy = 200 // Anything with this will be sorted into the item section of the editor. +} + +/// +/// BND IDs for FMG files used for identification +/// +public enum FmgIDType +{ + // Note: Matching names with _DLC and _PATCH are used as identifiers for patch FMGs. This is a little dumb and patch fmg handling should probably be redone. + None = -1, + + TitleGoods = 10, + TitleWeapons = 11, + TitleArmor = 12, + TitleRings = 13, + TitleSpells = 14, + TitleTest = 15, + TitleTest2 = 16, + TitleTest3 = 17, + TitleCharacters = 18, + TitleLocations = 19, + SummaryGoods = 20, + SummaryWeapons = 21, + SummaryArmor = 22, + SummaryRings = 23, + DescriptionGoods = 24, + DescriptionWeapons = 25, + DescriptionArmor = 26, + DescriptionRings = 27, + SummarySpells = 28, + DescriptionSpells = 29, + + // + TalkMsg = 1, + BloodMsg = 2, + MovieSubtitle = 3, + Event = 30, + MenuInGame = 70, + MenuCommon = 76, + MenuOther = 77, + MenuDialog = 78, + MenuKeyGuide = 79, + MenuLineHelp = 80, + MenuContext = 81, + MenuTags = 90, + Win32Tags = 91, + Win32Messages = 92, + Event_Patch = 101, + MenuDialog_Patch = 102, + Win32Messages_Patch = 103, + TalkMsg_Patch = 104, + BloodMsg_Patch = 107, + MenuLineHelp_Patch = 121, + MenuKeyGuide_Patch = 122, + MenuOther_Patch = 123, + MenuCommon_Patch = 124, + + // DS1 _DLC + DescriptionGoods_Patch = 100, + DescriptionSpells_Patch = 105, + DescriptionWeapons_Patch = 106, + DescriptionArmor_Patch = 108, + DescriptionRings_Patch = 109, + SummaryGoods_Patch = 110, + TitleGoods_Patch = 111, + SummaryRings_Patch = 112, + TitleRings_Patch = 113, + SummaryWeapons_Patch = 114, + TitleWeapons_Patch = 115, + SummaryArmor_Patch = 116, + TitleArmor_Patch = 117, + TitleSpells_Patch = 118, + TitleCharacters_Patch = 119, + TitleLocations_Patch = 120, + + // DS3 _DLC1 + TitleWeapons_DLC1 = 211, + TitleArmor_DLC1 = 212, + TitleRings_DLC1 = 213, + TitleSpells_DLC1 = 214, + TitleCharacters_DLC1 = 215, + TitleLocations_DLC1 = 216, + SummaryGoods_DLC1 = 217, + SummaryRings_DLC1 = 220, + DescriptionGoods_DLC1 = 221, + DescriptionWeapons_DLC1 = 222, + DescriptionArmor_DLC1 = 223, + DescriptionRings_DLC1 = 224, + SummarySpells_DLC1 = 225, + DescriptionSpells_DLC1 = 226, + + // + Modern_MenuText = 200, + Modern_LineHelp = 201, + Modern_KeyGuide = 202, + Modern_SystemMessage_win64 = 203, + Modern_Dialogues = 204, + TalkMsg_DLC1 = 230, + Event_DLC1 = 231, + Modern_MenuText_DLC1 = 232, + Modern_LineHelp_DLC1 = 233, + Modern_SystemMessage_win64_DLC1 = 235, + Modern_Dialogues_DLC1 = 236, + SystemMessage_PS4_DLC1 = 237, + SystemMessage_XboxOne_DLC1 = 238, + BloodMsg_DLC1 = 239, + + // DS3 _DLC2 + TitleGoods_DLC2 = 250, + TitleWeapons_DLC2 = 251, + TitleArmor_DLC2 = 252, + TitleRings_DLC2 = 253, + TitleSpells_DLC2 = 254, + TitleCharacters_DLC2 = 255, + TitleLocations_DLC2 = 256, + SummaryGoods_DLC2 = 257, + SummaryRings_DLC2 = 260, + DescriptionGoods_DLC2 = 261, + DescriptionWeapons_DLC2 = 262, + DescriptionArmor_DLC2 = 263, + DescriptionRings_DLC2 = 264, + SummarySpells_DLC2 = 265, + DescriptionSpells_DLC2 = 266, + + // + TalkMsg_DLC2 = 270, + Event_DLC2 = 271, + Modern_MenuText_DLC2 = 272, + Modern_LineHelp_DLC2 = 273, + Modern_SystemMessage_win64_DLC2 = 275, + Modern_Dialogues_DLC2 = 276, + SystemMessage_PS4_DLC2 = 277, + SystemMessage_XboxOne_DLC2 = 278, + BloodMsg_DLC2 = 279, + + // SDT + Skills = 40, + + // ER + DescriptionGem = 37, + SummarySwordArts = 43, + WeaponEffect = 44, + ERUnk45 = 45, + GoodsInfo2 = 46, + + // + TalkMsg_FemalePC_Alt = 4, + NetworkMessage = 31, + EventTextForTalk = 33, + EventTextForMap = 34, + TutorialTitle = 207, + TutorialBody = 208, + TextEmbedImageName_win64 = 209, + + // AC6 + TitleBooster = 38, + DescriptionBooster = 39, + + // + RankerProfile = 50, + TitleMission = 60, + SummaryMission = 61, + DescriptionMission = 62, + MissionLocation = 63, + TitleArchive = 65, + DescriptionArchive = 66, + TutorialTitle2023 = 73, + TutorialBody2023 = 74, + + // Multiple use cases. Differences are applied in ApplyGameDifferences(); + ReusedFMG_32 = 32, + + // FMG 32 + // BB: GemExtraInfo + // DS3: ActionButtonText + // SDT: ActionButtonText + // ER: ActionButtonText + ReusedFMG_35 = 35, + + // FMG 35 + // Most: TitleGem + // AC6: TitleGenerator + ReusedFMG_36 = 36, + + // FMG 36 + // Most: SummaryGem + // AC6: DescriptionGenerator + ReusedFMG_41 = 41, + + // FMG 41 + // Most: TitleMessage + // AC6: TitleFCS + ReusedFMG_42 = 42, + + // FMG 42 + // Most: TitleSwordArts + // AC6: DescriptionFCS + ReusedFMG_210 = 210, + + // FMG 210 + // DS3: TitleGoods_DLC1 + // SDT: ? + // ER: ToS_win64 + // AC6: TextEmbeddedImageNames + ReusedFMG_205 = 205, + + // FMG 205 + // DS3: SystemMessage_PS4 + // SDT: TutorialText + // ER: LoadingTitle + // AC6: MenuContext + ReusedFMG_206 = 206 + // FMG 206 + // DS3: SystemMessage_XboxOne + // SDT: TutorialTitle + // ER: LoadingText +} + +public static class FMGEnums +{ + /// + /// Get category for grouped entries (Goods, Weapons, etc) + /// + public static FmgEntryCategory GetFmgCategory(int id) + { + switch ((FmgIDType)id) + { + case FmgIDType.TitleTest: + case FmgIDType.TitleTest2: + case FmgIDType.TitleTest3: + case FmgIDType.ERUnk45: + return FmgEntryCategory.None; + + case FmgIDType.DescriptionGoods: + case FmgIDType.DescriptionGoods_Patch: + case FmgIDType.DescriptionGoods_DLC1: + case FmgIDType.DescriptionGoods_DLC2: + case FmgIDType.SummaryGoods: + case FmgIDType.SummaryGoods_Patch: + case FmgIDType.SummaryGoods_DLC1: + case FmgIDType.SummaryGoods_DLC2: + case FmgIDType.TitleGoods: + case FmgIDType.TitleGoods_Patch: + case FmgIDType.TitleGoods_DLC2: + case FmgIDType.GoodsInfo2: + return FmgEntryCategory.Goods; + + case FmgIDType.DescriptionWeapons: + case FmgIDType.DescriptionWeapons_DLC1: + case FmgIDType.DescriptionWeapons_DLC2: + case FmgIDType.SummaryWeapons: + case FmgIDType.TitleWeapons: + case FmgIDType.TitleWeapons_DLC1: + case FmgIDType.TitleWeapons_DLC2: + case FmgIDType.DescriptionWeapons_Patch: + case FmgIDType.SummaryWeapons_Patch: + case FmgIDType.TitleWeapons_Patch: + return FmgEntryCategory.Weapons; + + case FmgIDType.DescriptionArmor: + case FmgIDType.DescriptionArmor_DLC1: + case FmgIDType.DescriptionArmor_DLC2: + case FmgIDType.SummaryArmor: + case FmgIDType.TitleArmor: + case FmgIDType.TitleArmor_DLC1: + case FmgIDType.TitleArmor_DLC2: + case FmgIDType.DescriptionArmor_Patch: + case FmgIDType.SummaryArmor_Patch: + case FmgIDType.TitleArmor_Patch: + return FmgEntryCategory.Armor; + + case FmgIDType.DescriptionRings: + case FmgIDType.DescriptionRings_DLC1: + case FmgIDType.DescriptionRings_DLC2: + case FmgIDType.SummaryRings: + case FmgIDType.SummaryRings_DLC1: + case FmgIDType.SummaryRings_DLC2: + case FmgIDType.TitleRings: + case FmgIDType.TitleRings_DLC1: + case FmgIDType.TitleRings_DLC2: + case FmgIDType.DescriptionRings_Patch: + case FmgIDType.SummaryRings_Patch: + case FmgIDType.TitleRings_Patch: + return FmgEntryCategory.Rings; + + case FmgIDType.DescriptionSpells: + case FmgIDType.DescriptionSpells_DLC1: + case FmgIDType.DescriptionSpells_DLC2: + case FmgIDType.SummarySpells: + case FmgIDType.SummarySpells_DLC1: + case FmgIDType.SummarySpells_DLC2: + case FmgIDType.TitleSpells: + case FmgIDType.TitleSpells_DLC1: + case FmgIDType.TitleSpells_DLC2: + case FmgIDType.DescriptionSpells_Patch: + case FmgIDType.TitleSpells_Patch: + return FmgEntryCategory.Spells; + + case FmgIDType.TitleCharacters: + case FmgIDType.TitleCharacters_DLC1: + case FmgIDType.TitleCharacters_DLC2: + case FmgIDType.TitleCharacters_Patch: + return FmgEntryCategory.Characters; + + case FmgIDType.TitleLocations: + case FmgIDType.TitleLocations_DLC1: + case FmgIDType.TitleLocations_DLC2: + case FmgIDType.TitleLocations_Patch: + return FmgEntryCategory.Locations; + + case FmgIDType.DescriptionGem: + return FmgEntryCategory.Gem; + + case FmgIDType.SummarySwordArts: + return FmgEntryCategory.SwordArts; + + case FmgIDType.TutorialTitle: + case FmgIDType.TutorialBody: + case FmgIDType.TutorialTitle2023: + case FmgIDType.TutorialBody2023: + return FmgEntryCategory.Tutorial; + + case FmgIDType.WeaponEffect: + return FmgEntryCategory.ItemFmgDummy; + + case FmgIDType.TitleMission: + case FmgIDType.SummaryMission: + case FmgIDType.DescriptionMission: + case FmgIDType.MissionLocation: + return FmgEntryCategory.Mission; + + case FmgIDType.TitleBooster: + case FmgIDType.DescriptionBooster: + return FmgEntryCategory.Booster; + + case FmgIDType.TitleArchive: + case FmgIDType.DescriptionArchive: + return FmgEntryCategory.Archive; + + default: + return FmgEntryCategory.None; + } + } + + /// + /// Get entry text type (such as weapon Title, Summary, Description) + /// + public static FmgEntryTextType GetFmgTextType(int id) + { + switch ((FmgIDType)id) + { + case FmgIDType.DescriptionGoods: + case FmgIDType.DescriptionGoods_DLC1: + case FmgIDType.DescriptionGoods_DLC2: + case FmgIDType.DescriptionWeapons: + case FmgIDType.DescriptionWeapons_DLC1: + case FmgIDType.DescriptionWeapons_DLC2: + case FmgIDType.DescriptionArmor: + case FmgIDType.DescriptionArmor_DLC1: + case FmgIDType.DescriptionArmor_DLC2: + case FmgIDType.DescriptionRings: + case FmgIDType.DescriptionRings_DLC1: + case FmgIDType.DescriptionRings_DLC2: + case FmgIDType.DescriptionSpells: + case FmgIDType.DescriptionSpells_DLC1: + case FmgIDType.DescriptionSpells_DLC2: + case FmgIDType.DescriptionArmor_Patch: + case FmgIDType.DescriptionGoods_Patch: + case FmgIDType.DescriptionRings_Patch: + case FmgIDType.DescriptionSpells_Patch: + case FmgIDType.DescriptionWeapons_Patch: + case FmgIDType.DescriptionGem: + case FmgIDType.SummarySwordArts: // Include as Description (for text box size) + case FmgIDType.DescriptionBooster: + case FmgIDType.DescriptionMission: + case FmgIDType.DescriptionArchive: + return FmgEntryTextType.Description; + + case FmgIDType.SummaryGoods: + case FmgIDType.SummaryGoods_DLC1: + case FmgIDType.SummaryGoods_DLC2: + case FmgIDType.SummaryWeapons: + case FmgIDType.SummaryArmor: + case FmgIDType.SummaryRings: + case FmgIDType.SummaryRings_DLC1: + case FmgIDType.SummaryRings_DLC2: + case FmgIDType.SummarySpells: + case FmgIDType.SummarySpells_DLC1: + case FmgIDType.SummarySpells_DLC2: + case FmgIDType.SummaryArmor_Patch: + case FmgIDType.SummaryGoods_Patch: + case FmgIDType.SummaryRings_Patch: + case FmgIDType.SummaryWeapons_Patch: + case FmgIDType.SummaryMission: + case FmgIDType.TutorialTitle: // Include as summary (not all TutorialBody's have a title) + case FmgIDType.TutorialTitle2023: + return FmgEntryTextType.Summary; + + case FmgIDType.TitleGoods: + case FmgIDType.TitleGoods_DLC2: + case FmgIDType.TitleWeapons: + case FmgIDType.TitleWeapons_DLC1: + case FmgIDType.TitleWeapons_DLC2: + case FmgIDType.TitleArmor: + case FmgIDType.TitleArmor_DLC1: + case FmgIDType.TitleArmor_DLC2: + case FmgIDType.TitleRings: + case FmgIDType.TitleRings_DLC1: + case FmgIDType.TitleRings_DLC2: + case FmgIDType.TitleSpells: + case FmgIDType.TitleSpells_DLC1: + case FmgIDType.TitleSpells_DLC2: + case FmgIDType.TitleCharacters: + case FmgIDType.TitleCharacters_DLC1: + case FmgIDType.TitleCharacters_DLC2: + case FmgIDType.TitleLocations: + case FmgIDType.TitleLocations_DLC1: + case FmgIDType.TitleLocations_DLC2: + case FmgIDType.TitleTest: + case FmgIDType.TitleTest2: + case FmgIDType.TitleTest3: + case FmgIDType.TitleArmor_Patch: + case FmgIDType.TitleCharacters_Patch: + case FmgIDType.TitleGoods_Patch: + case FmgIDType.TitleLocations_Patch: + case FmgIDType.TitleRings_Patch: + case FmgIDType.TitleSpells_Patch: + case FmgIDType.TitleWeapons_Patch: + case FmgIDType.TitleBooster: + case FmgIDType.TitleMission: + case FmgIDType.TitleArchive: + return FmgEntryTextType.Title; + + case FmgIDType.GoodsInfo2: + case FmgIDType.MissionLocation: + return FmgEntryTextType.ExtraText; + + case FmgIDType.WeaponEffect: + case FmgIDType.TutorialBody: // Include as TextBody to make it display foremost. + case FmgIDType.TutorialBody2023: + return FmgEntryTextType.TextBody; + + default: + return FmgEntryTextType.TextBody; + } + } + + public static FmgFileCategory GetFMGUICategory(FmgEntryCategory entryCategory) + { + switch (entryCategory) + { + case FmgEntryCategory.Goods: + case FmgEntryCategory.Weapons: + case FmgEntryCategory.Armor: + case FmgEntryCategory.Rings: + case FmgEntryCategory.Gem: + case FmgEntryCategory.SwordArts: + case FmgEntryCategory.Generator: + case FmgEntryCategory.Booster: + case FmgEntryCategory.FCS: + case FmgEntryCategory.Archive: + return FmgFileCategory.Item; + default: + return FmgFileCategory.Menu; + } + } +} diff --git a/src/StudioCore/TextEditor/FmgExporter.cs b/src/StudioCore/TextEditor/FmgExporter.cs index 7b1010f11..520371557 100644 --- a/src/StudioCore/TextEditor/FmgExporter.cs +++ b/src/StudioCore/TextEditor/FmgExporter.cs @@ -36,426 +36,437 @@ public JsonFMG(FmgIDType fmg_id, FMG fmg) } } -public static partial class FMGBank +/// +/// Imports and exports FMGs using external formats. +/// +public static class FmgExporter { - /// - /// Imports and exports FMGs using external formats. - /// - public static class FmgExporter - { - private const string _entrySeparator = "###"; + private const string _entrySeparator = "###"; - private static Dictionary GetFmgs(string msgBndPath) + private static Dictionary GetFmgs(FMGBank bank, string msgBndPath) + { + Dictionary fmgs = new(); + IBinder fmgBinder; + if (bank.Project.Type is GameType.DemonsSouls or GameType.DarkSoulsPTDE + or GameType.DarkSoulsRemastered) { - Dictionary fmgs = new(); - IBinder fmgBinder; - if (AssetLocator.Type is GameType.DemonsSouls or GameType.DarkSoulsPTDE - or GameType.DarkSoulsRemastered) - { - fmgBinder = BND3.Read(msgBndPath); - } - else - { - fmgBinder = BND4.Read(msgBndPath); - } - foreach (var file in fmgBinder.Files) - { - var fmg = FMG.Read(file.Bytes); - fmgs.Add((FmgIDType)file.ID, fmg); - } + fmgBinder = BND3.Read(msgBndPath); + } + else + { + fmgBinder = BND4.Read(msgBndPath); + } + foreach (var file in fmgBinder.Files) + { + var fmg = FMG.Read(file.Bytes); + fmgs.Add((FmgIDType)file.ID, fmg); + } + + fmgBinder.Dispose(); + + return fmgs; + } - fmgBinder.Dispose(); + private static string FormatJson(string json) + { + json = json.Replace("{\"ID\"", "\r\n{\"ID\""); + json = json.Replace("],", "\r\n],"); + return json; + } - return fmgs; + /// + /// Exports jsons that only contains entries that differ between game and mod directories. + /// + public static void ExportFmgTxt(FMGLanguage lang, bool moddedOnly) + { + if (!PlatformUtils.Instance.OpenFolderDialog("Choose Export Folder", out var path)) + { + return; } - /// - /// Exports jsons that only contains entries that differ between game and mod directories. - /// - public static void ExportFmgTxt(bool moddedOnly) + if (lang.Owner.Project.ParentProject == null) { - if (!PlatformUtils.Instance.OpenFolderDialog("Choose Export Folder", out var path)) - { - return; - } + TaskLogs.AddLog("Error: Project has no parent to compare to. Cannot export modded files without vanilla FMGs to compare to.", + LogLevel.Warning, TaskLogs.LogPriority.High); + return; + } - if (moddedOnly && AssetLocator.GameModDirectory == AssetLocator.GameRootDirectory) - { - TaskLogs.AddLog("Error: Game directory is identical to mod directory. Cannot export modded files without vanilla FMGs to compare to.", - LogLevel.Warning, TaskLogs.LogPriority.High); - return; - } + if (moddedOnly && lang.Owner.Project.AssetLocator.RootDirectory == lang.Owner.Project.ParentProject.AssetLocator.RootDirectory) + { + TaskLogs.AddLog("Error: Game directory is identical to mod directory or project has no parent. Cannot export modded files without vanilla FMGs to compare to.", + LogLevel.Warning, TaskLogs.LogPriority.High); + return; + } - var itemPath = AssetLocator.GetItemMsgbnd(LanguageFolder).AssetPath; - var menuPath = AssetLocator.GetMenuMsgbnd(LanguageFolder).AssetPath; - var itemPath_Vanilla = itemPath.Replace(AssetLocator.GameModDirectory, AssetLocator.GameRootDirectory); - var menuPath_Vanilla = menuPath.Replace(AssetLocator.GameModDirectory, AssetLocator.GameRootDirectory); + var itemPath = Locator.AssetLocator.GetItemMsgbnd(lang.LanguageFolder).AssetPath; + var menuPath = Locator.AssetLocator.GetMenuMsgbnd(lang.LanguageFolder).AssetPath; + var itemPath_Vanilla = itemPath.Replace(lang.Owner.Project.AssetLocator.RootDirectory, lang.Owner.Project.ParentProject.AssetLocator.RootDirectory); + var menuPath_Vanilla = menuPath.Replace(lang.Owner.Project.AssetLocator.RootDirectory, lang.Owner.Project.ParentProject.AssetLocator.RootDirectory); - Dictionary fmgs_vanilla = new(); - fmgs_vanilla.AddAll(GetFmgs(itemPath_Vanilla)); - fmgs_vanilla.AddAll(GetFmgs(menuPath_Vanilla)); + Dictionary fmgs_vanilla = new(); + fmgs_vanilla.AddAll(GetFmgs(lang.Owner, itemPath_Vanilla)); + fmgs_vanilla.AddAll(GetFmgs(lang.Owner, menuPath_Vanilla)); - Dictionary fmgs_mod = new(); - foreach (var info in FmgInfoBank) - { - fmgs_mod.Add(info.FmgID, info.Fmg); - } + Dictionary fmgs_mod = new(); + foreach (var info in lang._FmgInfoBanks.SelectMany((x) => x.Value.FmgInfos)) + { + fmgs_mod.Add(info.FmgID, info.Fmg); + } - Dictionary fmgs_out; + Dictionary fmgs_out; - if (!moddedOnly) - { - // Export all entries - fmgs_out = fmgs_mod; - } - else + if (!moddedOnly) + { + // Export all entries + fmgs_out = fmgs_mod; + } + else + { + // Export modded entries only + fmgs_out = new(); + foreach (var kvp in fmgs_mod) { - // Export modded entries only - fmgs_out = new(); - foreach (var kvp in fmgs_mod) - { - var fmg_mod = kvp.Value; - var entries_vanilla = fmgs_vanilla[kvp.Key].Entries.ToList(); - FMG entries_out = new(fmg_mod.Version); + var fmg_mod = kvp.Value; + var entries_vanilla = fmgs_vanilla[kvp.Key].Entries.ToList(); + FMG entries_out = new(fmg_mod.Version); - foreach (var entry in fmg_mod.Entries) + foreach (var entry in fmg_mod.Entries) + { + FMG.Entry entry_vanilla = null; + for (var i = 0; i < entries_vanilla.Count; i++) { - FMG.Entry entry_vanilla = null; - for (var i = 0; i < entries_vanilla.Count; i++) + if (entries_vanilla[i].ID == entry.ID) { - if (entries_vanilla[i].ID == entry.ID) - { - entry_vanilla = entries_vanilla[i]; - entries_vanilla.RemoveAt(i); - break; - } - } - - if (entry_vanilla != null && entry.Text == entry_vanilla.Text) - { - continue; + entry_vanilla = entries_vanilla[i]; + entries_vanilla.RemoveAt(i); + break; } - - entries_out.Entries.Add(entry); } - if (entries_out.Entries.Count > 0) + if (entry_vanilla != null && entry.Text == entry_vanilla.Text) { - fmgs_out.Add(kvp.Key, entries_out); + continue; } + + entries_out.Entries.Add(entry); } - } - if (fmgs_out.Count == 0) - { - TaskLogs.AddLog("All FMG entries in mod folder are identical to game folder. No files have been exported.", - LogLevel.Information, TaskLogs.LogPriority.High); - return; + if (entries_out.Entries.Count > 0) + { + fmgs_out.Add(kvp.Key, entries_out); + } } + } + + if (fmgs_out.Count == 0) + { + TaskLogs.AddLog("All FMG entries in mod folder are identical to game folder. No files have been exported.", + LogLevel.Information, TaskLogs.LogPriority.High); + return; + } - foreach (var kvp in fmgs_out) + foreach (var kvp in fmgs_out) + { + var fileName = kvp.Key.ToString(); + Dictionary> sharedText = new(); + foreach (var entry in kvp.Value.Entries) { - var fileName = kvp.Key.ToString(); - Dictionary> sharedText = new(); - foreach (var entry in kvp.Value.Entries) - { - // Combine shared text + // Combine shared text - if (entry.Text == null) - continue; + if (entry.Text == null) + continue; - entry.Text = entry.Text.TrimEnd('\n'); + entry.Text = entry.Text.TrimEnd('\n'); - if (!sharedText.TryGetValue(entry.Text, out var ids)) - { - sharedText[entry.Text] = ids = new(); - } - ids.Add(entry.ID); + if (!sharedText.TryGetValue(entry.Text, out var ids)) + { + sharedText[entry.Text] = ids = new(); } + ids.Add(entry.ID); + } - List output = []; - output.Add($"###ID {(int)kvp.Key}"); - foreach (var sharedKvp in sharedText) - { - var text = sharedKvp.Key; - HashSet ids = sharedKvp.Value; + List output = []; + output.Add($"###ID {(int)kvp.Key}"); + foreach (var sharedKvp in sharedText) + { + var text = sharedKvp.Key; + HashSet ids = sharedKvp.Value; - text = text.Replace("\r", ""); - text = text.TrimEnd('\n'); + text = text.Replace("\r", ""); + text = text.TrimEnd('\n'); - output.Add(""); - string idsString = ""; - foreach (var id in ids) - { - idsString += _entrySeparator + id.ToString(); - } - output.Add(idsString); - output.AddRange(text.Split("\n")); + output.Add(""); + string idsString = ""; + foreach (var id in ids) + { + idsString += _entrySeparator + id.ToString(); } - - File.WriteAllLines($@"{path}\{fileName}.fmgmerge.txt", output); + output.Add(idsString); + output.AddRange(text.Split("\n")); } - TaskLogs.AddLog("Finished exporting FMG txt files", - LogLevel.Information, TaskLogs.LogPriority.High); + File.WriteAllLines($@"{path}\{fileName}.fmgmerge.txt", output); } - [Obsolete] - public static bool ExportJsonFMGs() + TaskLogs.AddLog("Finished exporting FMG txt files", + LogLevel.Information, TaskLogs.LogPriority.High); + } + + [Obsolete] + public static bool ExportJsonFMGs(FMGBank bank) + { + if (!PlatformUtils.Instance.OpenFolderDialog("Choose Export Folder", out var path)) { - if (!PlatformUtils.Instance.OpenFolderDialog("Choose Export Folder", out var path)) - { - return false; - } + return false; + } + + var filecount = 0; + if (bank.Project.Type == GameType.DarkSoulsIISOTFS) + { + Directory.CreateDirectory(path); - var filecount = 0; - if (AssetLocator.Type == GameType.DarkSoulsIISOTFS) + foreach (FMGInfo info in bank.FmgInfoBank) { - Directory.CreateDirectory(path); + JsonFMG fmgPair = new(info.FmgID, info.Fmg); + var json = JsonSerializer.Serialize(fmgPair, FmgSerializerContext.Default.JsonFMG); + json = FormatJson(json); - foreach (FMGInfo info in FmgInfoBank) + var fileName = info.Name; + if (CFG.Current.FMG_ShowOriginalNames) { - JsonFMG fmgPair = new(info.FmgID, info.Fmg); - var json = JsonSerializer.Serialize(fmgPair, FmgSerializerContext.Default.JsonFMG); - json = FormatJson(json); - - var fileName = info.Name; - if (CFG.Current.FMG_ShowOriginalNames) - { - fileName = info.FileName; - } + fileName = info.FileName; + } - File.WriteAllText($@"{path}\{fileName}.fmg.json", json); + File.WriteAllText($@"{path}\{fileName}.fmg.json", json); - filecount++; - } + filecount++; } - else + } + else + { + var itemPath = $@"{path}\Item Text"; + var menuPath = $@"{path}\Menu Text"; + Directory.CreateDirectory(itemPath); + Directory.CreateDirectory(menuPath); + foreach (FMGInfo info in bank.FmgInfoBank) { - var itemPath = $@"{path}\Item Text"; - var menuPath = $@"{path}\Menu Text"; - Directory.CreateDirectory(itemPath); - Directory.CreateDirectory(menuPath); - foreach (FMGInfo info in FmgInfoBank) + if (info.FileCategory == FmgFileCategory.Item) { - if (info.UICategory == FmgUICategory.Item) - { - path = itemPath; - } - else if (info.UICategory == FmgUICategory.Menu) - { - path = menuPath; - } + path = itemPath; + } + else if (info.FileCategory == FmgFileCategory.Menu) + { + path = menuPath; + } - JsonFMG fmgPair = new(info.FmgID, info.Fmg); - var json = JsonSerializer.Serialize(fmgPair, FmgSerializerContext.Default.JsonFMG); - json = FormatJson(json); + JsonFMG fmgPair = new(info.FmgID, info.Fmg); + var json = JsonSerializer.Serialize(fmgPair, FmgSerializerContext.Default.JsonFMG); + json = FormatJson(json); - var fileName = info.Name; - if (CFG.Current.FMG_ShowOriginalNames) - { - fileName = info.FileName; - } + var fileName = info.Name; + if (CFG.Current.FMG_ShowOriginalNames) + { + fileName = info.FileName; + } - File.WriteAllText($@"{path}\{fileName}.fmg.json", json); + File.WriteAllText($@"{path}\{fileName}.fmg.json", json); - filecount++; - } + filecount++; } - - TaskLogs.AddLog($"Finished exporting {filecount} text files", - LogLevel.Information, TaskLogs.LogPriority.High); - return true; } - private static bool ImportFmg(FmgIDType fmgId, FMG fmg, bool merge) + TaskLogs.AddLog($"Finished exporting {filecount} text files", + LogLevel.Information, TaskLogs.LogPriority.High); + return true; + } + + private static bool ImportFmg(FMGLanguage lang, FmgIDType fmgId, FMG fmg, bool merge) + { + foreach (FMGInfo info in lang._FmgInfoBanks.SelectMany((x) => x.Value.FmgInfos)) { - foreach (FMGInfo info in FmgInfoBank) + if (info.FmgID == fmgId) { - if (info.FmgID == fmgId) + if (merge) { - if (merge) + // Merge mode. Add and replace FMG entries instead of overwriting FMG entirely + foreach (var entry in fmg.Entries) { - // Merge mode. Add and replace FMG entries instead of overwriting FMG entirely - foreach (var entry in fmg.Entries) + var currentEntry = info.Fmg.Entries.Find(e => e.ID == entry.ID); + if (currentEntry == null) { - var currentEntry = info.Fmg.Entries.Find(e => e.ID == entry.ID); - if (currentEntry == null) - { - info.Fmg.Entries.Add(entry); - } - else if (currentEntry.Text != entry.Text) - { - currentEntry.Text = entry.Text; - } + info.Fmg.Entries.Add(entry); + } + else if (currentEntry.Text != entry.Text) + { + currentEntry.Text = entry.Text; } } - else - { - // Overwrite mode. Replace FMG with imported json - info.Fmg = fmg; - } - - return true; } + else + { + // Overwrite mode. Replace FMG with imported json + info.Fmg = fmg; + } + + return true; } + } - TaskLogs.AddLog($"FMG import error: No loaded FMGs have an ID of \"{fmgId}\"", - LogLevel.Error, TaskLogs.LogPriority.Normal); + TaskLogs.AddLog($"FMG import error: No loaded FMGs have an ID of \"{fmgId}\"", + LogLevel.Error, TaskLogs.LogPriority.Normal); + return false; + } + + public static bool ImportFmgJson(FMGLanguage lang, bool merge) + { + if (!PlatformUtils.Instance.OpenMultiFileDialog("Choose Files to Import", + new[] { AssetUtils.FmgJsonFilter }, out IReadOnlyList files)) + { return false; } - public static bool ImportFmgJson(bool merge) + if (files.Count == 0) { - if (!PlatformUtils.Instance.OpenMultiFileDialog("Choose Files to Import", - new[] { AssetLocator.FmgJsonFilter }, out IReadOnlyList files)) - { - return false; - } - - if (files.Count == 0) - { - return false; - } + return false; + } - var filecount = 0; - foreach (var filePath in files) + var filecount = 0; + foreach (var filePath in files) + { + try { - try + var file = File.ReadAllText(filePath); + JsonFMG json = JsonSerializer.Deserialize(file, FmgSerializerContext.Default.JsonFMG); + bool success = ImportFmg(lang, json.FmgID, json.Fmg, merge); + if (success) { - var file = File.ReadAllText(filePath); - JsonFMG json = JsonSerializer.Deserialize(file, FmgSerializerContext.Default.JsonFMG); - bool success = ImportFmg(json.FmgID, json.Fmg, merge); - if (success) - { - filecount++; - } - } - catch (JsonException e) - { - TaskLogs.AddLog($"FMG import error: Couldn't import \"{filePath}\"", - LogLevel.Error, TaskLogs.LogPriority.Normal, e); + filecount++; } } - - if (filecount == 0) + catch (JsonException e) { - return false; + TaskLogs.AddLog($"FMG import error: Couldn't import \"{filePath}\"", + LogLevel.Error, TaskLogs.LogPriority.Normal, e); } + } - HandleDuplicateEntries(); - PlatformUtils.Instance.MessageBox($"Imported {filecount} json files", "Finished", MessageBoxButtons.OK); - return true; + if (filecount == 0) + { + return false; } - public static bool ImportFmgTxt(bool merge) +// lang.HandleDuplicateEntries(); + PlatformUtils.Instance.MessageBox($"Imported {filecount} json files", "Finished", MessageBoxButtons.OK); + return true; + } + + public static bool ImportFmgTxt(FMGLanguage lang, bool merge) + { + if (!PlatformUtils.Instance.OpenMultiFileDialog("Choose Files to Import", + new[] { AssetUtils.TxtFilter }, out IReadOnlyList files)) { - if (!PlatformUtils.Instance.OpenMultiFileDialog("Choose Files to Import", - new[] { AssetLocator.TxtFilter }, out IReadOnlyList files)) - { - return false; - } + return false; + } - if (files.Count == 0) - { - return false; - } + if (files.Count == 0) + { + return false; + } - var filecount = 0; - foreach (var filePath in files) + var filecount = 0; + foreach (var filePath in files) + { + try { + string fileName = Path.GetFileName(filePath); + FMG fmg = new(); + int fmgId = 0; + var file = File.ReadAllLines(filePath); try { - string fileName = Path.GetFileName(filePath); - FMG fmg = new(); - int fmgId = 0; - var file = File.ReadAllLines(filePath); - try + fmgId = int.Parse(file[0].Replace(_entrySeparator, "").Replace("ID", "")); + } + catch + { + TaskLogs.AddLog($"FMG import error for file {fileName}: Cannot parse FMG ID on line 1.", + LogLevel.Error, TaskLogs.LogPriority.Normal); + return false; + } + + Queue entryIds = new(); + List text = new(); + for (var i = 1; i < file.Length; i++) + { + var line = file[i]; + if (i + 1 == file.Length) { - fmgId = int.Parse(file[0].Replace(_entrySeparator, "").Replace("ID", "")); + text.Add(line); + string str = string.Join("\r\n", text); + while (entryIds.Count > 0) + { + fmg.Entries.Add(new(entryIds.Dequeue(), str)); + } } - catch + else if (line.StartsWith(_entrySeparator)) { - TaskLogs.AddLog($"FMG import error for file {fileName}: Cannot parse FMG ID on line 1.", - LogLevel.Error, TaskLogs.LogPriority.Normal); - return false; - } - - Queue entryIds = new(); - List text = new(); - for (var i = 1; i < file.Length; i++) - { - var line = file[i]; - if (i + 1 == file.Length) + if (text.Count > 0) { - text.Add(line); string str = string.Join("\r\n", text); + while (entryIds.Count > 0) { fmg.Entries.Add(new(entryIds.Dequeue(), str)); } - } - else if (line.StartsWith(_entrySeparator)) - { - if (text.Count > 0) - { - string str = string.Join("\r\n", text); - while (entryIds.Count > 0) + try + { + var ids = line.Split(_entrySeparator); + foreach (var id in ids) { - fmg.Entries.Add(new(entryIds.Dequeue(), str)); - } + if (string.IsNullOrEmpty(id)) + continue; - try - { - var ids = line.Split(_entrySeparator); - foreach (var id in ids) - { - if (string.IsNullOrEmpty(id)) - continue; - - entryIds.Enqueue(int.Parse(id)); - } - } - catch - { - TaskLogs.AddLog($"FMG import error for file {fileName}: Cannot parse entry ID on line {i + 1}.", - LogLevel.Error, TaskLogs.LogPriority.High); - return false; + entryIds.Enqueue(int.Parse(id)); } - text = new(); } - } - else - { - text.Add(line); + catch + { + TaskLogs.AddLog($"FMG import error for file {fileName}: Cannot parse entry ID on line {i + 1}.", + LogLevel.Error, TaskLogs.LogPriority.High); + return false; + } + text = new(); } } - - bool success = ImportFmg((FmgIDType)fmgId, fmg, merge); - if (success) + else { - filecount++; + text.Add(line); } } - catch (Exception e) + + bool success = ImportFmg(lang, (FmgIDType)fmgId, fmg, merge); + if (success) { - TaskLogs.AddLog($"FMG import error: Couldn't import \"{filePath}\"", - LogLevel.Error, TaskLogs.LogPriority.Normal, e); + filecount++; } } - - if (filecount == 0) + catch (Exception e) { - return false; + TaskLogs.AddLog($"FMG import error: Couldn't import \"{filePath}\"", + LogLevel.Error, TaskLogs.LogPriority.Normal, e); } + } - FMGBank.HandleDuplicateEntries(); - TaskLogs.AddLog($"FMG import: Finished importing {filecount} txt files", - LogLevel.Information, TaskLogs.LogPriority.Normal); - return true; + if (filecount == 0) + { + return false; } + +// lang.HandleDuplicateEntries(); + TaskLogs.AddLog($"FMG import: Finished importing {filecount} txt files", + LogLevel.Information, TaskLogs.LogPriority.Normal); + return true; } } diff --git a/src/StudioCore/TextEditor/TextEditorScreen.cs b/src/StudioCore/TextEditor/TextEditorScreen.cs index 568950021..6bff6cc73 100644 --- a/src/StudioCore/TextEditor/TextEditorScreen.cs +++ b/src/StudioCore/TextEditor/TextEditorScreen.cs @@ -13,11 +13,8 @@ namespace StudioCore.TextEditor; public unsafe class TextEditorScreen : EditorScreen { private readonly PropertyEditor _propEditor; - - public readonly AssetLocator AssetLocator; - - private FMGBank.EntryGroup _activeEntryGroup; - private FMGBank.FMGInfo _activeFmgInfo; + private FMGEntryGroup _activeEntryGroup; + private FMGInfo _activeFmgInfo; private int _activeIDCache = -1; private bool _arrowKeyPressed; @@ -31,12 +28,11 @@ public unsafe class TextEditorScreen : EditorScreen private string _searchFilterCached = ""; private string _fmgSearchAllString = ""; private bool _fmgSearchAllActive = false; - private List _filteredFmgInfo = new(); + private List _filteredFmgInfo = new(); public ActionManager EditorActionManager = new(); - public TextEditorScreen(Sdl2Window window, GraphicsDevice device, AssetLocator locator) + public TextEditorScreen(Sdl2Window window, GraphicsDevice device) { - AssetLocator = locator; _propEditor = new PropertyEditor(EditorActionManager); } @@ -46,7 +42,11 @@ public TextEditorScreen(Sdl2Window window, GraphicsDevice device, AssetLocator l public void DrawEditorMenu() { - if (ImGui.BeginMenu("Edit", FMGBank.IsLoaded)) + if (Locator.ActiveProject == null) + return; + FMGBank currentFmgBank = Locator.ActiveProject.FMGBank; + + if (ImGui.BeginMenu("Edit", Locator.ActiveProject.FMGBank.IsLoaded)) { if (ImGui.MenuItem("Undo", KeyBindings.Current.Core_Undo.HintText, false, EditorActionManager.CanUndo())) @@ -75,9 +75,9 @@ public void DrawEditorMenu() ImGui.EndMenu(); } - if (ImGui.BeginMenu("Text Language", !FMGBank.IsLoading)) + if (ImGui.BeginMenu("Text Language", !Locator.ActiveProject.FMGBank.IsLoading)) { - Dictionary folders = FMGBank.AssetLocator.GetMsgLanguages(); + Dictionary folders = Locator.AssetLocator.GetMsgLanguages(); if (folders.Count == 0) { ImGui.TextColored(new Vector4(1.0f, 0.0f, 0.0f, 1.0f), "Cannot find language folders."); @@ -86,7 +86,13 @@ public void DrawEditorMenu() { foreach (KeyValuePair path in folders) { - if (ImGui.MenuItem(path.Key, "", FMGBank.LanguageFolder == path.Key)) + string disp = path.Key; + if (Locator.ActiveProject.FMGBank.fmgLangs.ContainsKey(path.Key)) + { + disp += "*"; + } + + if (ImGui.MenuItem(disp, "", Locator.ActiveProject.FMGBank.LanguageFolder == path.Key)) { ChangeLanguage(path.Key); } @@ -96,7 +102,7 @@ public void DrawEditorMenu() ImGui.EndMenu(); } - if (ImGui.BeginMenu("Import/Export", FMGBank.IsLoaded)) + if (ImGui.BeginMenu("Import/Export", Locator.ActiveProject.FMGBank.IsLoaded)) { if (ImGui.BeginMenu("Merge")) { @@ -105,7 +111,7 @@ public void DrawEditorMenu() if (ImGui.MenuItem("Import text files and merge")) { - if (FMGBank.FmgExporter.ImportFmgTxt(true)) + if (FmgExporter.ImportFmgTxt(currentFmgBank.fmgLangs[currentFmgBank.LanguageFolder], true)) { ClearTextEditorCache(); ResetActionManager(); @@ -116,7 +122,7 @@ public void DrawEditorMenu() "Export: only modded text (different than vanilla) will be exported"); if (ImGui.MenuItem("Export modded text to text files")) { - FMGBank.FmgExporter.ExportFmgTxt(true); + FmgExporter.ExportFmgTxt(currentFmgBank.fmgLangs[currentFmgBank.LanguageFolder], true); } ImGui.EndMenu(); @@ -129,7 +135,7 @@ public void DrawEditorMenu() if (ImGui.MenuItem("Import text files and replace")) { - if (FMGBank.FmgExporter.ImportFmgTxt(false)) + if (FmgExporter.ImportFmgTxt(currentFmgBank.fmgLangs[currentFmgBank.LanguageFolder], false)) { ClearTextEditorCache(); ResetActionManager(); @@ -140,7 +146,7 @@ public void DrawEditorMenu() "Export: all text will be exported"); if (ImGui.MenuItem("Export all text to text files")) { - FMGBank.FmgExporter.ExportFmgTxt(false); + FmgExporter.ExportFmgTxt(currentFmgBank.fmgLangs[currentFmgBank.LanguageFolder], false); } if (ImGui.BeginMenu("Legacy")) @@ -150,7 +156,7 @@ public void DrawEditorMenu() "Import: text replaces currently loaded text entirely."); if (ImGui.MenuItem("Import json")) { - if (FMGBank.FmgExporter.ImportFmgJson(false)) + if (FmgExporter.ImportFmgJson(currentFmgBank.fmgLangs[currentFmgBank.LanguageFolder], false)) { ClearTextEditorCache(); ResetActionManager(); @@ -167,7 +173,7 @@ public void DrawEditorMenu() public void OnGUI(string[] initcmd) { - if (FMGBank.AssetLocator == null) + if (Locator.AssetLocator == null) { return; } @@ -183,7 +189,7 @@ public void OnGUI(string[] initcmd) ImGui.SetNextWindowPos(winp); ImGui.SetNextWindowSize(wins); - if (!ImGui.IsAnyItemActive() && FMGBank.IsLoaded) + if (!ImGui.IsAnyItemActive() && Locator.ActiveProject != null && Locator.ActiveProject.FMGBank.IsLoaded) { // Only allow key shortcuts when an item [text box] is not currently activated if (EditorActionManager.CanUndo() && InputTracker.GetKeyDown(KeyBindings.Current.Core_Undo)) @@ -236,7 +242,7 @@ public void OnGUI(string[] initcmd) searchName = initcmd[1]; } - foreach (FMGBank.FMGInfo info in FMGBank.FmgInfoBank) + foreach (FMGInfo info in Locator.ActiveProject.FMGBank.FmgInfoBank) { var match = false; // This matches top-level item FMGs @@ -268,7 +274,7 @@ public void OnGUI(string[] initcmd) var parsed = int.TryParse(initcmd[2], out var id); if (parsed) { - _activeEntryGroup = FMGBank.GenerateEntryGroup(id, _activeFmgInfo); + _activeEntryGroup = Locator.ActiveProject.FMGBank.GenerateEntryGroup(id, _activeFmgInfo); } } } @@ -285,17 +291,16 @@ public void OnProjectChanged(ProjectSettings newSettings) _filteredFmgInfo.Clear(); ClearTextEditorCache(); ResetActionManager(); - FMGBank.ReloadFMGs(_projectSettings.LastFmgLanguageUsed); } public void Save() { - FMGBank.SaveFMGs(); + Locator.ActiveProject.FMGBank.SaveFMGs(); } public void SaveAll() { - FMGBank.SaveFMGs(); + Locator.ActiveProject.FMGBank.SaveFMGs(); } private void ClearTextEditorCache() @@ -318,7 +323,7 @@ private void ResetActionManager() /// /// Duplicates all Entries in active EntryGroup from their FMGs /// - private void DuplicateFMGEntries(FMGBank.EntryGroup entry) + private void DuplicateFMGEntries(FMGEntryGroup entry) { _activeIDCache = entry.GetNextUnusedID(); var action = new DuplicateFMGEntryAction(entry); @@ -332,7 +337,7 @@ private void DuplicateFMGEntries(FMGBank.EntryGroup entry) /// /// Deletes all Entries within active EntryGroup from their FMGs /// - private void DeleteFMGEntries(FMGBank.EntryGroup entry) + private void DeleteFMGEntries(FMGEntryGroup entry) { var action = new DeleteFMGEntryAction(entry); EditorActionManager.ExecuteAction(action); @@ -381,7 +386,7 @@ private void FMGSearchLogic(ref bool doFocus) } // Descriptions - foreach (FMG.Entry entry in FMGBank.GetFmgEntriesByCategoryAndTextType(_activeFmgInfo.EntryCategory, + foreach (FMG.Entry entry in Locator.ActiveProject.FMGBank.GetFmgEntriesByCategoryAndTextType(_activeFmgInfo.EntryCategory, FmgEntryTextType.Description, false)) { if (entry.Text != null) @@ -398,7 +403,7 @@ private void FMGSearchLogic(ref bool doFocus) } // Summaries - foreach (FMG.Entry entry in FMGBank.GetFmgEntriesByCategoryAndTextType(_activeFmgInfo.EntryCategory, + foreach (FMG.Entry entry in Locator.ActiveProject.FMGBank.GetFmgEntriesByCategoryAndTextType(_activeFmgInfo.EntryCategory, FmgEntryTextType.Summary, false)) { if (entry.Text != null) @@ -415,7 +420,7 @@ private void FMGSearchLogic(ref bool doFocus) } // Extra Text - foreach (FMG.Entry entry in FMGBank.GetFmgEntriesByCategoryAndTextType(_activeFmgInfo.EntryCategory, + foreach (FMG.Entry entry in Locator.ActiveProject.FMGBank.GetFmgEntriesByCategoryAndTextType(_activeFmgInfo.EntryCategory, FmgEntryTextType.ExtraText, false)) { if (entry.Text != null) @@ -444,18 +449,18 @@ private void FMGSearchLogic(ref bool doFocus) } } - private void CategoryListUI(FmgUICategory uiType, bool doFocus) + private void CategoryListUI(FmgFileCategory uiType, bool doFocus) { - List infos; + IEnumerable infos; if (_fmgSearchAllActive) infos = _filteredFmgInfo; else - infos = FMGBank.FmgInfoBank; + infos = Locator.ActiveProject.FMGBank.SortedFmgInfoBank; foreach (var info in infos) { if (info.PatchParent == null - && info.UICategory == uiType + && info.FileCategory == uiType && info.EntryType is FmgEntryTextType.Title or FmgEntryTextType.TextBody) { string displayName; @@ -500,13 +505,13 @@ private void EditorGUI(bool doFocus) { var scale = MapStudioNew.GetUIScale(); - if (!FMGBank.IsLoaded) + if (Locator.ActiveProject == null || !Locator.ActiveProject.FMGBank.IsLoaded) { if (_projectSettings == null) { ImGui.Text("No project loaded. File -> New Project"); } - else if (FMGBank.IsLoading) + else if (Locator.ActiveProject.FMGBank.IsLoading) { ImGui.Text("Loading..."); } @@ -539,7 +544,7 @@ private void EditorGUI(bool doFocus) { _fmgSearchAllActive = true; _filteredFmgInfo.Clear(); - foreach (var info in FMGBank.FmgInfoBank) + foreach (var info in Locator.ActiveProject.FMGBank.SortedFmgInfoBank) { if (info.PatchParent == null) { @@ -577,17 +582,14 @@ private void EditorGUI(bool doFocus) } ImGui.Separator(); - foreach (KeyValuePair v in FMGBank.ActiveUITypes) + foreach (FmgFileCategory v in Locator.ActiveProject.FMGBank.currentFmgInfoBanks) { - if (v.Value) - { - ImGui.Separator(); - ImGui.Text($" {v.Key} Text"); - ImGui.Separator(); - // Categories - CategoryListUI(v.Key, doFocus); - ImGui.Spacing(); - } + ImGui.Separator(); + ImGui.Text($" {v} Text"); + ImGui.Separator(); + // Categories + CategoryListUI(v, doFocus); + ImGui.Spacing(); } if (_activeFmgInfo != null) @@ -666,11 +668,11 @@ private void EditorGUI(bool doFocus) label = Utils.ImGui_WordWrapString(label, ImGui.GetColumnWidth(-1)); if (ImGui.Selectable(label, _activeIDCache == r.ID)) { - _activeEntryGroup = FMGBank.GenerateEntryGroup(r.ID, _activeFmgInfo); + _activeEntryGroup = Locator.ActiveProject.FMGBank.GenerateEntryGroup(r.ID, _activeFmgInfo); } else if (_activeIDCache == r.ID && _activeEntryGroup == null) { - _activeEntryGroup = FMGBank.GenerateEntryGroup(r.ID, _activeFmgInfo); + _activeEntryGroup = Locator.ActiveProject.FMGBank.GenerateEntryGroup(r.ID, _activeFmgInfo); _searchFilterCached = ""; } @@ -678,7 +680,7 @@ private void EditorGUI(bool doFocus) && _activeEntryGroup?.ID != r.ID) { // Up/Down arrow key selection - _activeEntryGroup = FMGBank.GenerateEntryGroup(r.ID, _activeFmgInfo); + _activeEntryGroup = Locator.ActiveProject.FMGBank.GenerateEntryGroup(r.ID, _activeFmgInfo); _arrowKeyPressed = false; } @@ -692,13 +694,13 @@ private void EditorGUI(bool doFocus) { if (ImGui.Selectable("Duplicate Entry")) { - _activeEntryGroup = FMGBank.GenerateEntryGroup(r.ID, _activeFmgInfo); + _activeEntryGroup = Locator.ActiveProject.FMGBank.GenerateEntryGroup(r.ID, _activeFmgInfo); DuplicateFMGEntries(_activeEntryGroup); } if (ImGui.Selectable("Delete Entry")) { - _activeEntryGroup = FMGBank.GenerateEntryGroup(r.ID, _activeFmgInfo); + _activeEntryGroup = Locator.ActiveProject.FMGBank.GenerateEntryGroup(r.ID, _activeFmgInfo); DeleteFMGEntries(_activeEntryGroup); } @@ -766,6 +768,6 @@ private void ChangeLanguage(string path) _filteredFmgInfo.Clear(); ClearTextEditorCache(); ResetActionManager(); - FMGBank.ReloadFMGs(path); + Locator.ActiveProject.FMGBank.LoadFMGs(path); } }