From 84683416b55c9d234ca51033dc9facf1f8dc673f Mon Sep 17 00:00:00 2001 From: HeroponRikiBestest Date: Sat, 8 Nov 2025 01:15:04 -0500 Subject: [PATCH 01/13] Attempt nested for real this time --- ProtectionScan/Features/MainFeature.cs | 145 +++++++++++++++++++++++-- 1 file changed, 138 insertions(+), 7 deletions(-) diff --git a/ProtectionScan/Features/MainFeature.cs b/ProtectionScan/Features/MainFeature.cs index c4c206f4..f739a3e9 100644 --- a/ProtectionScan/Features/MainFeature.cs +++ b/ProtectionScan/Features/MainFeature.cs @@ -1,6 +1,8 @@ using System; using System.Collections.Generic; using System.IO; +#if NETCOREAPP +#endif using BinaryObjectScanner; using SabreTools.CommandLine; using SabreTools.CommandLine.Inputs; @@ -32,19 +34,32 @@ internal sealed class MainFeature : Feature #if NETCOREAPP private const string _jsonName = "json"; internal readonly FlagInput JsonInput = new(_jsonName, ["-j", "--json"], "Output to json file"); + +#if NET6_0_OR_GREATER + private const string _nestedName = "nested"; + internal readonly FlagInput NestedInput = new(_nestedName, ["-n", "--nested"], "Output to nested json file"); +#endif #endif private const string _noArchivesName = "no-archives"; - internal readonly FlagInput NoArchivesInput = new(_noArchivesName, ["-na", "--no-archives"], "Disable scanning archives"); + + internal readonly FlagInput NoArchivesInput = + new(_noArchivesName, ["-na", "--no-archives"], "Disable scanning archives"); private const string _noContentsName = "no-contents"; - internal readonly FlagInput NoContentsInput = new(_noContentsName, ["-nc", "--no-contents"], "Disable scanning for content checks"); + + internal readonly FlagInput NoContentsInput = + new(_noContentsName, ["-nc", "--no-contents"], "Disable scanning for content checks"); private const string _noPathsName = "no-paths"; - internal readonly FlagInput NoPathsInput = new(_noPathsName, ["-np", "--no-paths"], "Disable scanning for path checks"); + + internal readonly FlagInput NoPathsInput = + new(_noPathsName, ["-np", "--no-paths"], "Disable scanning for path checks"); private const string _noSubdirsName = "no-subdirs"; - internal readonly FlagInput NoSubdirsInput = new(_noSubdirsName, ["-ns", "--no-subdirs"], "Disable scanning subdirectories"); + + internal readonly FlagInput NoSubdirsInput = + new(_noSubdirsName, ["-ns", "--no-subdirs"], "Disable scanning subdirectories"); #endregion @@ -63,6 +78,11 @@ internal sealed class MainFeature : Feature /// Enable JSON output /// public bool Json { get; private set; } + + /// + /// Enable nested JSON output + /// + public bool Nested { get; private set; } #endif public MainFeature() @@ -74,6 +94,9 @@ public MainFeature() Add(FileOnlyInput); #if NETCOREAPP Add(JsonInput); +#if NET6_0_OR_GREATER + Add(NestedInput); +#endif #endif Add(NoContentsInput); Add(NoArchivesInput); @@ -93,6 +116,9 @@ public override bool Execute() FileOnly = GetBoolean(_fileOnlyName); #if NETCOREAPP Json = GetBoolean(_jsonName); +#if NET6_0_OR_GREATER + Nested = GetBoolean(_nestedName); +#endif #endif // Create scanner for all paths @@ -156,13 +182,18 @@ private void GetAndWriteProtections(Scanner scanner, string path) #if NETCOREAPP if (Json) WriteProtectionResultJson(path, protections); +#if NET6_0_OR_GREATER + if (Nested) + WriteProtectionResultNestedJson(path, protections); +#endif #endif } catch (Exception ex) { try { - using var sw = new StreamWriter(File.OpenWrite($"exception-{DateTime.Now:yyyy-MM-dd_HHmmss.ffff}.txt")); + using var sw = + new StreamWriter(File.OpenWrite($"exception-{DateTime.Now:yyyy-MM-dd_HHmmss.ffff}.txt")); sw.WriteLine(ex); } catch @@ -194,7 +225,8 @@ private void WriteProtectionResults(string path, Dictionary } catch { - Console.WriteLine("Could not open protection log file for writing. Only a console log will be provided."); + Console.WriteLine( + "Could not open protection log file for writing. Only a console log will be provided."); FileOnly = false; } @@ -246,7 +278,8 @@ private void WriteProtectionResultJson(string path, Dictionary + /// Write the protection results from a single path to a nested json file, if possible + /// + /// File or directory path + /// Dictionary of protections found, if any + private void WriteProtectionResultNestedJson(string path, Dictionary> protections) + { + if (protections == null) + { + Console.WriteLine($"No protections found for {path}"); + return; + } + + try + { + // Attempt to open a protection file for writing + using var jsw = + new StreamWriter(File.OpenWrite($"protection-{DateTime.Now:yyyy-MM-dd_HHmmss.ffff}.json")); + + var x = new Dictionary(); + + // Sort the keys for consistent output + string[] keys = [.. protections.Keys]; + Array.Sort(keys); + + // Loop over all keys + foreach (string key in keys) + { + // Skip over files with no protection + var value = protections[key]; + if (value.Count == 0) + continue; + + // Sort the detected protections for consistent output + string[] fileProtections = [.. value]; + Array.Sort(fileProtections); + //foreach (var fileProtection in fileProtections) + + DeepInsert(ref x, key, fileProtections); + } + + // Create the output data + var jsonSerializerOptions = new System.Text.Json.JsonSerializerOptions { WriteIndented = true }; + string serializedData = System.Text.Json.JsonSerializer.Serialize(x, jsonSerializerOptions); + + // Write the output data + // TODO: this prints plus symbols wrong, probably some other things too + jsw.WriteLine(serializedData); + jsw.Flush(); + } + catch (Exception ex) + { + Console.WriteLine(Debug ? ex : "[Exception opening file, please try again]"); + Console.WriteLine(); + } + } + + public static void DeepInsert(ref Dictionary obj, string path, string[] value) + { + var current = obj; + var pathParts = path.Split(Path.DirectorySeparatorChar); + + for (int i = 0; i < pathParts.Length; i++) + { + var part = pathParts[i]; + if (i != (pathParts.Length - 1)) + { + if (!current.ContainsKey(part)) + { + var innerObject = new Dictionary(); + current[part] = innerObject; + current = innerObject; + } + else + { + var innerObject = current[part]; + if (innerObject.GetType() != typeof(Dictionary)) + { + current[part] = new Dictionary(); + current = current[part]; + current.Add("", innerObject); + } + else + { + current[part] = innerObject; + current = innerObject; + } + } + } + else + { + current.Add(part, value); + } + } + } +#endif #endif } } From bb986531e8d3911c767fc2852a1186844e72d545 Mon Sep 17 00:00:00 2001 From: HeroponRikiBestest Date: Sat, 8 Nov 2025 01:57:48 -0500 Subject: [PATCH 02/13] forgot to include handling the base path --- ProtectionScan/Features/MainFeature.cs | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/ProtectionScan/Features/MainFeature.cs b/ProtectionScan/Features/MainFeature.cs index f739a3e9..4073aefe 100644 --- a/ProtectionScan/Features/MainFeature.cs +++ b/ProtectionScan/Features/MainFeature.cs @@ -317,8 +317,9 @@ private void WriteProtectionResultNestedJson(string path, Dictionary(); - + var jsonDictionary = new Dictionary(); + var trimmedPath = path.TrimEnd(['\\', '/']); + // Sort the keys for consistent output string[] keys = [.. protections.Keys]; Array.Sort(keys); @@ -336,12 +337,16 @@ private void WriteProtectionResultNestedJson(string path, Dictionary obj, string path, string[] value) + public static void DeepInsert(ref Dictionary obj, string path, string[] value, string trimmedPath) { var current = obj; var pathParts = path.Split(Path.DirectorySeparatorChar); From d025150fa9960a5c4b4db189997a01d9462480f7 Mon Sep 17 00:00:00 2001 From: HeroponRikiBestest Date: Sat, 8 Nov 2025 11:39:27 -0500 Subject: [PATCH 03/13] Reverted unnecesssary changes --- ProtectionScan/Features/MainFeature.cs | 30 +++++++------------------- 1 file changed, 8 insertions(+), 22 deletions(-) diff --git a/ProtectionScan/Features/MainFeature.cs b/ProtectionScan/Features/MainFeature.cs index 4073aefe..ebbe523b 100644 --- a/ProtectionScan/Features/MainFeature.cs +++ b/ProtectionScan/Features/MainFeature.cs @@ -1,8 +1,6 @@ using System; using System.Collections.Generic; using System.IO; -#if NETCOREAPP -#endif using BinaryObjectScanner; using SabreTools.CommandLine; using SabreTools.CommandLine.Inputs; @@ -42,24 +40,16 @@ internal sealed class MainFeature : Feature #endif private const string _noArchivesName = "no-archives"; - - internal readonly FlagInput NoArchivesInput = - new(_noArchivesName, ["-na", "--no-archives"], "Disable scanning archives"); + internal readonly FlagInput NoArchivesInput = new(_noArchivesName, ["-na", "--no-archives"], "Disable scanning archives"); private const string _noContentsName = "no-contents"; - - internal readonly FlagInput NoContentsInput = - new(_noContentsName, ["-nc", "--no-contents"], "Disable scanning for content checks"); + internal readonly FlagInput NoContentsInput = new(_noContentsName, ["-nc", "--no-contents"], "Disable scanning for content checks"); private const string _noPathsName = "no-paths"; - - internal readonly FlagInput NoPathsInput = - new(_noPathsName, ["-np", "--no-paths"], "Disable scanning for path checks"); + internal readonly FlagInput NoPathsInput = new(_noPathsName, ["-np", "--no-paths"], "Disable scanning for path checks"); private const string _noSubdirsName = "no-subdirs"; - - internal readonly FlagInput NoSubdirsInput = - new(_noSubdirsName, ["-ns", "--no-subdirs"], "Disable scanning subdirectories"); + internal readonly FlagInput NoSubdirsInput = new(_noSubdirsName, ["-ns", "--no-subdirs"], "Disable scanning subdirectories"); #endregion @@ -192,8 +182,7 @@ private void GetAndWriteProtections(Scanner scanner, string path) { try { - using var sw = - new StreamWriter(File.OpenWrite($"exception-{DateTime.Now:yyyy-MM-dd_HHmmss.ffff}.txt")); + using var sw = new StreamWriter(File.OpenWrite($"exception-{DateTime.Now:yyyy-MM-dd_HHmmss.ffff}.txt")); sw.WriteLine(ex); } catch @@ -225,8 +214,7 @@ private void WriteProtectionResults(string path, Dictionary } catch { - Console.WriteLine( - "Could not open protection log file for writing. Only a console log will be provided."); + Console.WriteLine("Could not open protection log file for writing. Only a console log will be provided."); FileOnly = false; } @@ -278,8 +266,7 @@ private void WriteProtectionResultJson(string path, Dictionary(); var trimmedPath = path.TrimEnd(['\\', '/']); From ff39badbd077e30acbfa9be19bff29ffcdad37dc Mon Sep 17 00:00:00 2001 From: HeroponRikiBestest Date: Sat, 8 Nov 2025 11:46:34 -0500 Subject: [PATCH 04/13] Remove unneeded net6.0 gating --- ProtectionScan/Features/MainFeature.cs | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/ProtectionScan/Features/MainFeature.cs b/ProtectionScan/Features/MainFeature.cs index ebbe523b..5178045b 100644 --- a/ProtectionScan/Features/MainFeature.cs +++ b/ProtectionScan/Features/MainFeature.cs @@ -33,10 +33,8 @@ internal sealed class MainFeature : Feature private const string _jsonName = "json"; internal readonly FlagInput JsonInput = new(_jsonName, ["-j", "--json"], "Output to json file"); -#if NET6_0_OR_GREATER private const string _nestedName = "nested"; internal readonly FlagInput NestedInput = new(_nestedName, ["-n", "--nested"], "Output to nested json file"); -#endif #endif private const string _noArchivesName = "no-archives"; @@ -84,9 +82,7 @@ public MainFeature() Add(FileOnlyInput); #if NETCOREAPP Add(JsonInput); -#if NET6_0_OR_GREATER Add(NestedInput); -#endif #endif Add(NoContentsInput); Add(NoArchivesInput); @@ -106,9 +102,7 @@ public override bool Execute() FileOnly = GetBoolean(_fileOnlyName); #if NETCOREAPP Json = GetBoolean(_jsonName); -#if NET6_0_OR_GREATER Nested = GetBoolean(_nestedName); -#endif #endif // Create scanner for all paths @@ -172,10 +166,8 @@ private void GetAndWriteProtections(Scanner scanner, string path) #if NETCOREAPP if (Json) WriteProtectionResultJson(path, protections); -#if NET6_0_OR_GREATER if (Nested) WriteProtectionResultNestedJson(path, protections); -#endif #endif } catch (Exception ex) @@ -284,7 +276,6 @@ private void WriteProtectionResultJson(string path, Dictionary /// Write the protection results from a single path to a nested json file, if possible /// @@ -384,7 +375,6 @@ public static void DeepInsert(ref Dictionary obj, string path, } } } -#endif #endif } } From d2cb73f99210c1f490e314376163753c64f69574 Mon Sep 17 00:00:00 2001 From: HeroponRikiBestest Date: Sat, 8 Nov 2025 12:16:31 -0500 Subject: [PATCH 05/13] Add comments --- ProtectionScan/Features/MainFeature.cs | 45 ++++++++++++++++++-------- 1 file changed, 32 insertions(+), 13 deletions(-) diff --git a/ProtectionScan/Features/MainFeature.cs b/ProtectionScan/Features/MainFeature.cs index 5178045b..244a0ec3 100644 --- a/ProtectionScan/Features/MainFeature.cs +++ b/ProtectionScan/Features/MainFeature.cs @@ -294,7 +294,10 @@ private void WriteProtectionResultNestedJson(string path, Dictionary(); + // A nested dictionary is used in order to avoid complex and unnecessary custom serialization. + // A dictionary with a dynamic value is used so that it's not necessary to first parse entries into a + // traditional node system and then bubble up the entire chain creating non-dynamic dictionaries. + var nestedDictionary = new Dictionary(); var trimmedPath = path.TrimEnd(['\\', '/']); // Sort the keys for consistent output @@ -314,16 +317,17 @@ private void WriteProtectionResultNestedJson(string path, Dictionary obj, string path, string[] value, string trimmedPath) + /// + /// Inserts file protection dictionary entries into a nested dictionary based on path + /// + /// File or directory path + /// The "key" for the given protection entry, already trimmed of its base path + /// The scanned protection(s) for a given file + public static void DeepInsert(ref Dictionary nestedDictionary, string path, string[] protections) { - var current = obj; - var pathParts = path.Split(Path.DirectorySeparatorChar); + var current = nestedDictionary; + var pathParts = path.Split(Path.DirectorySeparatorChar); + // Traverses the nested dictionary until the "root" dictionary is reached. for (int i = 0; i < pathParts.Length; i++) { var part = pathParts[i]; if (i != (pathParts.Length - 1)) { - if (!current.ContainsKey(part)) + if (!current.ContainsKey(part)) // Inserts new subdictionaries if one doesn't already exist { var innerObject = new Dictionary(); current[part] = innerObject; current = innerObject; } - else + else // Traverses already existing subdictionaries { var innerObject = current[part]; + + // If i.e. a packer has protections detected on it, and then files within it also have + // detections of their own, the later traversal of the files within it will fail, as + // the subdictionary for that packer has already been set to . Since it's + // no longer dynamic after being assigned once, the existing value must be pulled, then the + // new subdictionary can be added, and then the existing value can be re-added within the + // packer with a key of an empty string, in order to indicate it's for the packer itself, and + // to avoid potential future collisions. if (innerObject.GetType() != typeof(Dictionary)) { current[part] = new Dictionary(); @@ -369,9 +388,9 @@ public static void DeepInsert(ref Dictionary obj, string path, } } } - else + else // If the root dictionary has been reached, add the file and its protections. { - current.Add(part, value); + current.Add(part, protections); } } } From a9312326b5ea22a59b305441092b74ea2c7d4005 Mon Sep 17 00:00:00 2001 From: HeroponRikiBestest Date: Sat, 8 Nov 2025 12:25:51 -0500 Subject: [PATCH 06/13] Finish comments --- ProtectionScan/Features/MainFeature.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/ProtectionScan/Features/MainFeature.cs b/ProtectionScan/Features/MainFeature.cs index 244a0ec3..927ec8a3 100644 --- a/ProtectionScan/Features/MainFeature.cs +++ b/ProtectionScan/Features/MainFeature.cs @@ -321,6 +321,11 @@ private void WriteProtectionResultNestedJson(string path, Dictionary nestedDictionary, var current = nestedDictionary; var pathParts = path.Split(Path.DirectorySeparatorChar); - // Traverses the nested dictionary until the "root" dictionary is reached. + // Traverses the nested dictionary until the "leaf" dictionary is reached. for (int i = 0; i < pathParts.Length; i++) { var part = pathParts[i]; @@ -388,7 +393,7 @@ public static void DeepInsert(ref Dictionary nestedDictionary, } } } - else // If the root dictionary has been reached, add the file and its protections. + else // If the "leaf" dictionary has been reached, add the file and its protections. { current.Add(part, protections); } From 5a46e1761bc43b831c1a0d86272d08d8ff3f8e4f Mon Sep 17 00:00:00 2001 From: HeroponRikiBestest Date: Sat, 8 Nov 2025 12:57:54 -0500 Subject: [PATCH 07/13] Might as well safeguard if no protections are returned. --- ProtectionScan/Features/MainFeature.cs | 29 +++++++++++++++----------- 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/ProtectionScan/Features/MainFeature.cs b/ProtectionScan/Features/MainFeature.cs index 927ec8a3..b6d44b7f 100644 --- a/ProtectionScan/Features/MainFeature.cs +++ b/ProtectionScan/Features/MainFeature.cs @@ -326,18 +326,23 @@ private void WriteProtectionResultNestedJson(string path, Dictionary Date: Sat, 8 Nov 2025 20:38:57 -0500 Subject: [PATCH 08/13] Use object instead of dynamic --- ProtectionScan/Features/MainFeature.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/ProtectionScan/Features/MainFeature.cs b/ProtectionScan/Features/MainFeature.cs index b6d44b7f..3cd9190b 100644 --- a/ProtectionScan/Features/MainFeature.cs +++ b/ProtectionScan/Features/MainFeature.cs @@ -295,9 +295,9 @@ private void WriteProtectionResultNestedJson(string path, Dictionary(); + // A dictionary with an object value is used so that it's not necessary to first parse entries into a + // traditional node system and then bubble up the entire chain creating non-object dictionaries. + var nestedDictionary = new Dictionary(); var trimmedPath = path.TrimEnd(['\\', '/']); // Sort the keys for consistent output @@ -357,7 +357,7 @@ private void WriteProtectionResultNestedJson(string path, DictionaryFile or directory path /// The "key" for the given protection entry, already trimmed of its base path /// The scanned protection(s) for a given file - public static void DeepInsert(ref Dictionary nestedDictionary, string path, string[] protections) + public static void DeepInsert(ref Dictionary nestedDictionary, string path, string[] protections) { var current = nestedDictionary; var pathParts = path.Split(Path.DirectorySeparatorChar); @@ -370,7 +370,7 @@ public static void DeepInsert(ref Dictionary nestedDictionary, { if (!current.ContainsKey(part)) // Inserts new subdictionaries if one doesn't already exist { - var innerObject = new Dictionary(); + var innerObject = new Dictionary(); current[part] = innerObject; current = innerObject; } @@ -381,20 +381,20 @@ public static void DeepInsert(ref Dictionary nestedDictionary, // If i.e. a packer has protections detected on it, and then files within it also have // detections of their own, the later traversal of the files within it will fail, as // the subdictionary for that packer has already been set to . Since it's - // no longer dynamic after being assigned once, the existing value must be pulled, then the + // no longer object after being assigned once, the existing value must be pulled, then the // new subdictionary can be added, and then the existing value can be re-added within the // packer with a key of an empty string, in order to indicate it's for the packer itself, and // to avoid potential future collisions. if (innerObject.GetType() != typeof(Dictionary)) { current[part] = new Dictionary(); - current = current[part]; + current = (Dictionary)current[part]; current.Add("", innerObject); } else { current[part] = innerObject; - current = innerObject; + current = (Dictionary)innerObject; } } } From cc1503e8184a5bc8670ce512ad2980cca408ecc7 Mon Sep 17 00:00:00 2001 From: HeroponRikiBestest Date: Sat, 8 Nov 2025 20:41:22 -0500 Subject: [PATCH 09/13] Remove weird empty string root node handling --- ProtectionScan/Features/MainFeature.cs | 29 +++++++++++++------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/ProtectionScan/Features/MainFeature.cs b/ProtectionScan/Features/MainFeature.cs index 3cd9190b..d0eb1fc7 100644 --- a/ProtectionScan/Features/MainFeature.cs +++ b/ProtectionScan/Features/MainFeature.cs @@ -328,21 +328,21 @@ private void WriteProtectionResultNestedJson(string path, Dictionary>() { - var tempValue = nestedDictionary[""]; - nestedDictionary.Remove(""); - nestedDictionary.Add(trimmedPath, tempValue); - - // Create the output data - var jsonSerializerOptions = new System.Text.Json.JsonSerializerOptions { WriteIndented = true }; - string serializedData = System.Text.Json.JsonSerializer.Serialize(nestedDictionary, jsonSerializerOptions); - - // Write the output data - // TODO: this prints plus symbols wrong, probably some other things too - jsw.WriteLine(serializedData); - jsw.Flush(); - } + {trimmedPath, nestedDictionary} + }; + + // Create the output data + var jsonSerializerOptions = new System.Text.Json.JsonSerializerOptions { WriteIndented = true }; + string serializedData = System.Text.Json.JsonSerializer.Serialize(finalDictionary, jsonSerializerOptions); + + // Write the output data + // TODO: this prints plus symbols wrong, probably some other things too + jsw.WriteLine(serializedData); + jsw.Flush(); + } catch (Exception ex) { @@ -360,6 +360,7 @@ private void WriteProtectionResultNestedJson(string path, Dictionary nestedDictionary, string path, string[] protections) { var current = nestedDictionary; + path = path.TrimStart(Path.DirectorySeparatorChar); var pathParts = path.Split(Path.DirectorySeparatorChar); // Traverses the nested dictionary until the "leaf" dictionary is reached. From 502fbac071e876e9e0c88e7b3be0f7f8b84d98a3 Mon Sep 17 00:00:00 2001 From: HeroponRikiBestest Date: Sat, 8 Nov 2025 20:48:33 -0500 Subject: [PATCH 10/13] remove uneeded ref --- ProtectionScan/Features/MainFeature.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ProtectionScan/Features/MainFeature.cs b/ProtectionScan/Features/MainFeature.cs index d0eb1fc7..e393d283 100644 --- a/ProtectionScan/Features/MainFeature.cs +++ b/ProtectionScan/Features/MainFeature.cs @@ -318,7 +318,7 @@ private void WriteProtectionResultNestedJson(string path, DictionaryFile or directory path /// The "key" for the given protection entry, already trimmed of its base path /// The scanned protection(s) for a given file - public static void DeepInsert(ref Dictionary nestedDictionary, string path, string[] protections) + public static void DeepInsert(Dictionary nestedDictionary, string path, string[] protections) { var current = nestedDictionary; path = path.TrimStart(Path.DirectorySeparatorChar); From f536fc4b6b876684a9b84c2b3822d5dc4298ce06 Mon Sep 17 00:00:00 2001 From: HeroponRikiBestest Date: Sat, 8 Nov 2025 20:50:53 -0500 Subject: [PATCH 11/13] Modify comment accordingly --- ProtectionScan/Features/MainFeature.cs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/ProtectionScan/Features/MainFeature.cs b/ProtectionScan/Features/MainFeature.cs index e393d283..346e6528 100644 --- a/ProtectionScan/Features/MainFeature.cs +++ b/ProtectionScan/Features/MainFeature.cs @@ -381,11 +381,10 @@ public static void DeepInsert(Dictionary nestedDictionary, strin // If i.e. a packer has protections detected on it, and then files within it also have // detections of their own, the later traversal of the files within it will fail, as - // the subdictionary for that packer has already been set to . Since it's - // no longer object after being assigned once, the existing value must be pulled, then the - // new subdictionary can be added, and then the existing value can be re-added within the - // packer with a key of an empty string, in order to indicate it's for the packer itself, and - // to avoid potential future collisions. + // the subdictionary for that packer has already been set to . The existing + // value must be pulled, then the new subdictionary can be added, and then the existing value + // can be re-added within the packer with a key of an empty string, in order to indicate it's + // for the packer itself, and to avoid potential future collisions. if (innerObject.GetType() != typeof(Dictionary)) { current[part] = new Dictionary(); From 8d8f3df714ff97a3023e5e04f51c141f1af24d81 Mon Sep 17 00:00:00 2001 From: HeroponRikiBestest Date: Sat, 8 Nov 2025 21:02:58 -0500 Subject: [PATCH 12/13] Merge regular and nested json writing --- ProtectionScan/Features/MainFeature.cs | 121 +++++++++---------------- 1 file changed, 43 insertions(+), 78 deletions(-) diff --git a/ProtectionScan/Features/MainFeature.cs b/ProtectionScan/Features/MainFeature.cs index 346e6528..bc6fa95b 100644 --- a/ProtectionScan/Features/MainFeature.cs +++ b/ProtectionScan/Features/MainFeature.cs @@ -34,7 +34,7 @@ internal sealed class MainFeature : Feature internal readonly FlagInput JsonInput = new(_jsonName, ["-j", "--json"], "Output to json file"); private const string _nestedName = "nested"; - internal readonly FlagInput NestedInput = new(_nestedName, ["-n", "--nested"], "Output to nested json file"); + internal readonly FlagInput NestedInput = new(_nestedName, ["-n", "--nested"], "Output to nested json file if json is already enabled"); #endif private const string _noArchivesName = "no-archives"; @@ -166,8 +166,6 @@ private void GetAndWriteProtections(Scanner scanner, string path) #if NETCOREAPP if (Json) WriteProtectionResultJson(path, protections); - if (Nested) - WriteProtectionResultNestedJson(path, protections); #endif } catch (Exception ex) @@ -260,89 +258,56 @@ private void WriteProtectionResultJson(string path, Dictionary - /// Write the protection results from a single path to a nested json file, if possible - /// - /// File or directory path - /// Dictionary of protections found, if any - private void WriteProtectionResultNestedJson(string path, Dictionary> protections) - { - if (protections == null) - { - Console.WriteLine($"No protections found for {path}"); - return; - } - - try - { - // Attempt to open a protection file for writing - using var jsw = new StreamWriter(File.OpenWrite($"protection-{DateTime.Now:yyyy-MM-dd_HHmmss.ffff}.json")); - - // A nested dictionary is used in order to avoid complex and unnecessary custom serialization. - // A dictionary with an object value is used so that it's not necessary to first parse entries into a - // traditional node system and then bubble up the entire chain creating non-object dictionaries. - var nestedDictionary = new Dictionary(); - var trimmedPath = path.TrimEnd(['\\', '/']); - - // Sort the keys for consistent output - string[] keys = [.. protections.Keys]; - Array.Sort(keys); - - // Loop over all keys - foreach (string key in keys) + string serializedData; + if (Nested) { - // Skip over files with no protection - var value = protections[key]; - if (value.Count == 0) - continue; - - // Sort the detected protections for consistent output - string[] fileProtections = [.. value]; - Array.Sort(fileProtections); - //foreach (var fileProtection in fileProtections) - - // Inserts key and protections into nested dictionary, with the key trimmed of the base path. - DeepInsert(nestedDictionary, key.Substring(trimmedPath.Length), fileProtections); - } + // A nested dictionary is used in order to avoid complex and unnecessary custom serialization. + // A dictionary with an object value is used so that it's not necessary to first parse entries into a + // traditional node system and then bubble up the entire chain creating non-object dictionaries. + var nestedDictionary = new Dictionary(); + var trimmedPath = path.TrimEnd(['\\', '/']); + + // Sort the keys for consistent output + string[] keys = [.. protections.Keys]; + Array.Sort(keys); + + // Loop over all keys + foreach (string key in keys) + { + // Skip over files with no protection + var value = protections[key]; + if (value.Count == 0) + continue; + + // Sort the detected protections for consistent output + string[] fileProtections = [.. value]; + Array.Sort(fileProtections); + //foreach (var fileProtection in fileProtections) + + // Inserts key and protections into nested dictionary, with the key trimmed of the base path. + DeepInsert(nestedDictionary, key.Substring(trimmedPath.Length), fileProtections); + } - // While it's possible to hardcode the root dictionary key to be changed to the base path beforehand, - // it's cleaner to avoid trying to circumvent the path splitting logic, and just move the root - // dictionary value into an entry with the base path as the key. - // There is no input as far as has been tested that can result in there not being a root dictionary key - // of an empty string, so this is safe. - // The only exception is if absolutely no protections were returned whatsoever, which is why there's a - // safeguard here at all - - var finalDictionary = new Dictionary>() + // Move nested dictionary into final dictionary with the base path as a key. + var finalDictionary = new Dictionary>() + { + {trimmedPath, nestedDictionary} + }; + + // Create the output data + serializedData = System.Text.Json.JsonSerializer.Serialize(finalDictionary, jsonSerializerOptions); + } + else { - {trimmedPath, nestedDictionary} - }; - - // Create the output data - var jsonSerializerOptions = new System.Text.Json.JsonSerializerOptions { WriteIndented = true }; - string serializedData = System.Text.Json.JsonSerializer.Serialize(finalDictionary, jsonSerializerOptions); + // Create the output data + serializedData = System.Text.Json.JsonSerializer.Serialize(protections, jsonSerializerOptions); + } // Write the output data // TODO: this prints plus symbols wrong, probably some other things too jsw.WriteLine(serializedData); - jsw.Flush(); - + jsw.Flush(); } catch (Exception ex) { From 2f2472d7f2445838e92306ef384b694235beed13 Mon Sep 17 00:00:00 2001 From: HeroponRikiBestest Date: Sat, 8 Nov 2025 23:06:37 -0500 Subject: [PATCH 13/13] Simplify object value checking --- ProtectionScan/Features/MainFeature.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ProtectionScan/Features/MainFeature.cs b/ProtectionScan/Features/MainFeature.cs index bc6fa95b..4048182d 100644 --- a/ProtectionScan/Features/MainFeature.cs +++ b/ProtectionScan/Features/MainFeature.cs @@ -350,7 +350,7 @@ public static void DeepInsert(Dictionary nestedDictionary, strin // value must be pulled, then the new subdictionary can be added, and then the existing value // can be re-added within the packer with a key of an empty string, in order to indicate it's // for the packer itself, and to avoid potential future collisions. - if (innerObject.GetType() != typeof(Dictionary)) + if (innerObject is string[]) { current[part] = new Dictionary(); current = (Dictionary)current[part];