From a05d9785bd94438b713d69694c3089997d8803d6 Mon Sep 17 00:00:00 2001 From: Mathias Lui Date: Sat, 26 Aug 2023 15:43:21 +0200 Subject: [PATCH] Slightly cleaned up CsgoHelper --- .../SteamShared/SteamShared/CsgoHelper.cs | 196 +++++++++++++----- .../SteamShared/SteamShared/SteamHelper.cs | 2 +- 2 files changed, 142 insertions(+), 56 deletions(-) diff --git a/SteamShared/SteamShared/SteamShared/CsgoHelper.cs b/SteamShared/SteamShared/SteamShared/CsgoHelper.cs index 0b1ba3f..67206d7 100644 --- a/SteamShared/SteamShared/SteamShared/CsgoHelper.cs +++ b/SteamShared/SteamShared/SteamShared/CsgoHelper.cs @@ -27,8 +27,11 @@ namespace SteamShared public static readonly int GameID = 730; /// - /// Gets the prefixes allowed for maps when using . + /// Gets the prefixes allowed for maps when using . /// + /// + /// If adjusted, should also be adjusted to set the type. + /// private readonly string[] validMapPrefixes = new[] { "de", @@ -38,7 +41,7 @@ namespace SteamShared }; /// - /// Gets the files relative to the CS:GO install path that are checked when validating. + /// Gets the files relative to the CS:GO install path that are checked when validating. /// private readonly string[] filesToValidate = new[] { @@ -46,7 +49,7 @@ namespace SteamShared }; /// - /// Gets the directories relative to the CS:GO install path that are checked when validating. + /// Gets the directories relative to the CS:GO install path that are checked when validating. /// private readonly string[] directoriesToValidate = new[] { @@ -58,13 +61,17 @@ namespace SteamShared // Nothing to do, don't use this ctor, aside from before the program initialises. } + /// + /// Creates a new instance. + /// + /// the root path where CS:GO is installed. public CsgoHelper(string csgoPath) { this.CsgoPath = csgoPath; } /// - /// Validates files and directories for CS:GO installed in the path. + /// Validates files and directories for CS:GO installed in the path. /// /// whether the files and directories exist. public bool Validate() @@ -72,6 +79,10 @@ namespace SteamShared return this.Validate(this.CsgoPath!); } + /// + /// Gets a handle to the currently running CS:GO instance. + /// + /// a tuple containing the process handle, as well as the arguments it was started with, either can be null. public (Process?,string?) GetRunningCsgo() { // This is used as the normal process handle @@ -93,8 +104,12 @@ namespace SteamShared } /// - /// Validates files and directories for CS:GO installed in the given path. + /// Validates files and directories for CS:GO installed in the given path. /// + /// + /// Directories and files that are checked for + /// are specified in and . + /// /// The path to the CS:GO install directory, in which the executable resides. /// whether the files and directories exist. public bool Validate(string csgoPath) @@ -114,16 +129,28 @@ namespace SteamShared return true; } + /// + /// Gets all maps and tries to fill all the information it can find about it. + /// + /// + /// Note that can't be null for this. + /// + /// a list of all CS:GO maps. public List GetMaps() { - string mapOverviewsPath = System.IO.Path.Combine(this.CsgoPath!, "csgo", "resource", "overviews"); - if (!Directory.Exists(mapOverviewsPath)) - return new List(); + if (this.CsgoPath is null) + return new(); + string mapOverviewsPath = System.IO.Path.Combine(this.CsgoPath, "csgo", "resource", "overviews"); + + if (!Directory.Exists(mapOverviewsPath)) + return new(); + + // Get list of all map text files which have a map type that is allowed List mapTextFiles = Directory.GetFiles(mapOverviewsPath).ToList().Where(f => f.ToLower().EndsWith(".txt")).Where(f => this.mapFileNameValid(f)).ToList(); - List maps = new List(); + List maps = new(); foreach (string file in mapTextFiles) { @@ -133,6 +160,7 @@ namespace SteamShared string potentialRadarFile = System.IO.Path.Combine(this.CsgoPath!, "csgo\\resource\\overviews", System.IO.Path.GetFileNameWithoutExtension(file) + "_radar.dds"); if (File.Exists(potentialRadarFile)) { + // Radar file exists, set it (radar image visible on minimap) map.MapImagePath = potentialRadarFile; } @@ -140,6 +168,7 @@ namespace SteamShared string potentialBspFile = System.IO.Path.Combine(this.CsgoPath!, "csgo\\maps", System.IO.Path.GetFileNameWithoutExtension(file) + ".bsp"); if (File.Exists(potentialBspFile)) { + // Map file exists, set it (actual compiled map file) map.BspFilePath = potentialBspFile; } @@ -147,6 +176,7 @@ namespace SteamShared string potentialNavFile = System.IO.Path.Combine(this.CsgoPath!, "csgo\\maps", System.IO.Path.GetFileNameWithoutExtension(file) + ".nav"); if (File.Exists(potentialNavFile)) { + // NAV file exists, set it (bot navigation mesh file used for calculating movement routes) map.NavFilePath = potentialNavFile; } @@ -154,10 +184,11 @@ namespace SteamShared string potentialAinFile = System.IO.Path.Combine(this.CsgoPath!, "csgo\\maps\\graphs", System.IO.Path.GetFileNameWithoutExtension(file) + ".ain"); if (File.Exists(potentialAinFile)) { + // AIN file exists, set it (similar to NAV file, probably for older games for NPCs, probably not very important) map.AinFilePath = potentialAinFile; } - // Set map type + // Set map type (if adjusted, validMapPrefixes should also be adjusted) switch (System.IO.Path.GetFileNameWithoutExtension(file).Split('_').First().ToLower()) { case "de": @@ -177,7 +208,7 @@ namespace SteamShared break; } - // Get properties from accompanying text file + // Get properties from accompanying text file (could be made prettier) var vdf = new VDFFile(file); if (vdf.RootElements.Count > 0) { @@ -228,7 +259,7 @@ namespace SteamShared } } - // Save map name without prefix + // Save map name without prefix (e.g. de_dust2 is dust2) map.MapFileName = System.IO.Path.GetFileNameWithoutExtension(file).Split('_').Last(); Bitmap image; @@ -259,7 +290,9 @@ namespace SteamShared continue; // Some workaround I found online for some thread error I forgot - // Future self: We probably want to execute it on the thread that owns the image + // @Future self: We probably want to execute it on the thread that owns the image + // + // Hey, future self here, we just make sure that the code is invoked on the UI thread, to access the map object System.Windows.Application.Current.Dispatcher.Invoke((Action)delegate { map.MapImage = Globals.BitmapToImageSource(image); @@ -272,12 +305,12 @@ namespace SteamShared } /// - /// Gets the launch options of the specified Steam user. + /// Gets the launch options of the specified Steam user. /// /// The Steam user of which to get the launch options. /// - /// The launch options, - /// or null if an error occurred, or if the passed was wrong or + /// the launch options, + /// or null if an error occurred, or if the passed was or wrong. /// public string? GetLaunchOptions(SteamUser user) { @@ -300,19 +333,24 @@ namespace SteamShared return Regex.Replace(launchOptions, "\\\\(.)", "$1"); } - public List GetWeapons() + /// + /// Gets a list of all fireable weapons, including their stats. + /// + /// the list of weapons, or if something went wrong. + public List? GetWeapons() { string filePath = Path.Combine(this.CsgoPath!, "csgo\\scripts\\items\\items_game.txt"); + if (!File.Exists(filePath)) - return null!; + return null; var vdfItems = new VDFFile(filePath); Element prefabs = vdfItems["items_game"]?["prefabs"]!; Element items = vdfItems["items_game"]?["items"]!; - if (prefabs == null || items == null) + if (prefabs is null || items is null) // There is no prefab list or item list to read out - return null!; + return null; var weapons = new List(); @@ -321,10 +359,13 @@ namespace SteamShared string? itemPrefab = item["prefab"]?.Value!; string? itemName = item["name"].Value; - if (itemPrefab == null || !itemName!.StartsWith("weapon_")) + if (itemPrefab is null || itemName is null + || !itemName.StartsWith("weapon_")) + { continue; + } - // Here we're sure the item is supposed to be a weapon + // At this point we're sure the item is supposed to be a weapon var weapon = new CsgoWeapon(); weapon.ClassName = itemName; @@ -350,11 +391,17 @@ namespace SteamShared return weapons; } + /// + /// Gets whether a given weapon can be fired (Hint: You can't shoot knives, at least not in the usual sense). + /// + /// The weapon to check. + /// The prefab trace (like a callstack of prefabs getting more and more abstract). + /// whether the weapon is fireable. private bool isWeaponFireable(CsgoWeapon weapon, List? prefabTrace) { bool isWhitelisted = false; - if(prefabTrace != null) + if (prefabTrace != null) { // Stuff involving prefab trace here if(prefabTrace.FirstOrDefault(pr => pr == "primary" || pr == "secondary") != null) @@ -363,14 +410,34 @@ namespace SteamShared } // Other - if(weapon.ClassName == "weapon_taser") + if (weapon.ClassName == "weapon_taser") // Allow zeus, even though it's "equipment" and listed in the melee slot isWhitelisted = true; return isWhitelisted; } - private bool tryPopulateWeapon(CsgoWeapon weapon, Element prefabs, string prefabName, List? prefabTrace = null) + /// + private bool tryPopulateWeapon(CsgoWeapon weapon, Element prefabs, string prefabName) + { + return tryPopulateWeapon(weapon, prefabs, prefabName, null); + } + + /// + /// Tries to populate a weapon with in-game stats. + /// + /// The weapon to be populated. + /// All prefabs that exist. + /// The name of the next (more abstract) prefab to check. + /// + /// This parameter has to be and will be set by the function itself recursively. + /// Always use instead. + /// + /// + /// , if all information could be gathered or we have reached a wall, + /// , if the item is no weapon, or the weapon is not fireable, and its stats shouldn't be used (e.g. knives). + /// + private bool tryPopulateWeapon(CsgoWeapon weapon, Element prefabs, string prefabName, List? prefabTrace) { // Get the initial prefab specified in the item Element prefab = prefabs[prefabName]; @@ -395,7 +462,7 @@ namespace SteamShared Element attributes = prefab["attributes"]; - if (attributes == null && nextPrefab != null) + if (attributes is null && nextPrefab is not null) // it might have no attributes but just skip to the next prefab in that case, because they might have attributes // one example of this is the taser (zeus) return this.tryPopulateWeapon(weapon, prefabs, nextPrefab, prefabTrace); @@ -524,11 +591,19 @@ namespace SteamShared if (prefabTrace == null) prefabTrace = new List(); - prefabTrace.Add(prefab.Name!); + prefabTrace.Add(prefab.Name ?? "unknown prefab name"); return this.tryPopulateWeapon(weapon, prefabs, nextPrefab, prefabTrace); } + /// + /// Checks whether the file name of map at the specified path is valid. + /// + /// + /// It's seen as valid, if the prefix is contained in . + /// + /// The map path to be checked. + /// whether the map type is valid. private bool mapFileNameValid(string mapPath) { string fileName = Path.GetFileName(mapPath.ToLower()); @@ -543,11 +618,11 @@ namespace SteamShared } /// - /// Reads entity list from uncompressed BSP file. + /// Reads entity list from uncompressed BSP file. /// /// The absolute path to the BSP file. /// the entity list, null if actual length differed from length specified in file, or a general error occurred. - public string ReadEntityListFromBsp(string bspFilePath) + public string? ReadEntityListFromBsp(string bspFilePath) { using(var bspFile = File.OpenRead(bspFilePath)) { @@ -568,25 +643,25 @@ namespace SteamShared } } } - return null!; + return null; } /// - /// Reads packed files from a BSP and returns whether any 1. NAV or 2. AIN files were found. + /// Reads packed files from a BSP and checks for NAV and AIN files. /// /// The absolute path to the BSP file. - /// A tuple containing whether nav or ain files were found, in that order. - public (bool, bool, NavMesh) ReadIfPackedNavFilesInBsp(string bspFilePath) + /// a tuple containing whether NAV or AIN files were found, as well as the parsed NAV mesh. + public (bool NavFilesFound, bool AinFilesFound, NavMesh? NavMesh) ReadIfPackedNavFilesInBsp(string bspFilePath) { bool navFound = false; bool ainFound = false; - byte[] readZipBytes = null!; + byte[]? readZipBytes = null; using (var bspFile = File.OpenRead(bspFilePath)) { using (var reader = new BinaryReader(bspFile)) { - // Stuff before lumps + pakfile index * lump array item length + // Skip stuff before (lumps + (pakfile index * lump array item length)) reader.BaseStream.Position = 8 + (40 * 16); // Get lump pos and size @@ -600,34 +675,44 @@ namespace SteamShared } if (readZipBytes == null) - return (false, false, null!); + return (false, false, null); - using (var stream = new MemoryStream(readZipBytes)) + using var stream = new MemoryStream(readZipBytes); + + // Packed files are contained in a ZIP file within the BSP file + using var zip = new ZipArchive(stream, ZipArchiveMode.Read); + + NavMesh? nav = null; + foreach (var entry in zip.Entries) { - using(var zip = new ZipArchive(stream, ZipArchiveMode.Read)) + if (entry.FullName.ToLower().EndsWith(".nav")) { - NavMesh? nav = null; - foreach (var entry in zip.Entries) - { - if (entry.FullName.EndsWith(".nav")) - { - // Found a packed NAV file - navFound = true; - nav = NavFile.Parse(entry.Open()); - } - if(entry.FullName.EndsWith(".ain")) - // Found a packed AIN file - ainFound = true; + // Found a packed NAV file + navFound = true; + nav = NavFile.Parse(entry.Open()); + } + if (entry.FullName.ToLower().EndsWith(".ain")) + // Found a packed AIN file + ainFound = true; - if (navFound && ainFound) - // If both already found, return prematurely - return (true, true, nav!); - } - return (navFound, ainFound, nav!); + if (navFound && ainFound) + { + // If both are already found, return prematurely, + // as we don't care how many there are + return (navFound, ainFound, nav); } } + + // We get here, if either no NAV or no AIN file has been found + // If navFound is null, nav will be null as well + return (navFound, ainFound, nav); } + /// + /// Checks whether a lump is unused (all zeroes). + /// + /// The lump to check. + /// a bool indicating if the lump is unused. private bool isLumpUnused(byte[] lump) { for(int i = 0; i < lump.Length; i++) @@ -635,6 +720,7 @@ namespace SteamShared if (lump[i] != 0) return false; } + return true; } } diff --git a/SteamShared/SteamShared/SteamShared/SteamHelper.cs b/SteamShared/SteamShared/SteamShared/SteamHelper.cs index 5871c95..afa004a 100644 --- a/SteamShared/SteamShared/SteamShared/SteamHelper.cs +++ b/SteamShared/SteamShared/SteamShared/SteamHelper.cs @@ -115,7 +115,7 @@ namespace SteamShared return null; } - foreach (string foundLib in foundSteamLibraries!) + foreach (string foundLib in foundSteamLibraries) { // All paths in the file are escaped allLibraries.Add(new SteamLibrary(foundLib.Replace("\\\\", "\\")));