diff --git a/DuplicateAssemblyScanner/DuplicateAssemblyScanner.ruleset b/DuplicateAssemblyScanner/CodeAnalysis.ruleset similarity index 93% rename from DuplicateAssemblyScanner/DuplicateAssemblyScanner.ruleset rename to DuplicateAssemblyScanner/CodeAnalysis.ruleset index ce59629..f82ac1a 100644 --- a/DuplicateAssemblyScanner/DuplicateAssemblyScanner.ruleset +++ b/DuplicateAssemblyScanner/CodeAnalysis.ruleset @@ -1,5 +1,5 @@  - + @@ -39,4 +39,4 @@ - \ No newline at end of file + diff --git a/DuplicateAssemblyScanner/DuplicateAssemblyScanner.sln b/DuplicateAssemblyScanner/DuplicateAssemblyScanner.sln index 30e3cc7..8d3dc29 100644 --- a/DuplicateAssemblyScanner/DuplicateAssemblyScanner.sln +++ b/DuplicateAssemblyScanner/DuplicateAssemblyScanner.sln @@ -5,6 +5,11 @@ VisualStudioVersion = 16.0.29806.167 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DuplicateAssemblyScanner", "DuplicateAssemblyScanner\DuplicateAssemblyScanner.csproj", "{EFBA373B-88B2-427A-8396-6F9D56C89F74}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{E06C7F71-21C0-4D8E-920F-F8139B39A120}" + ProjectSection(SolutionItems) = preProject + VersionInfo.cs = VersionInfo.cs + EndProjectSection +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU diff --git a/DuplicateAssemblyScanner/DuplicateAssemblyScanner/DuplicateAssemblyScanner.csproj b/DuplicateAssemblyScanner/DuplicateAssemblyScanner/DuplicateAssemblyScanner.csproj index 621424b..9b6a5b1 100644 --- a/DuplicateAssemblyScanner/DuplicateAssemblyScanner/DuplicateAssemblyScanner.csproj +++ b/DuplicateAssemblyScanner/DuplicateAssemblyScanner/DuplicateAssemblyScanner.csproj @@ -11,7 +11,7 @@ DuplicateAssemblyScanner v3.5 512 - true + false latest @@ -22,6 +22,7 @@ DEBUG;TRACE prompt 4 + ..\CodeAnalysis.ruleset pdbonly @@ -30,6 +31,7 @@ TRACE prompt 4 + ..\CodeAnalysis.ruleset ~/Library/Application Support/Steam/ @@ -72,19 +74,30 @@ + + Properties\VersionInfo.cs + + + + + + + + + set "DEPLOYDIR=$(LOCALAPPDATA)\Colossal Order\Cities_Skylines\Addons\Mods\$(TargetName)\" del "%25DEPLOYDIR%25DuplicateAssemblyScanner.dll" -xcopy /y "$(TargetDir)DuplicateAssemblyScanner.dll" "%25DEPLOYDIR%25" +xcopy /y "$(TargetDir)DuplicateAssemblyScanner.dll" "%25DEPLOYDIR%25" set DEPLOYDIR= - \ No newline at end of file + diff --git a/DuplicateAssemblyScanner/DuplicateAssemblyScanner/Properties/AssemblyInfo.cs b/DuplicateAssemblyScanner/DuplicateAssemblyScanner/Properties/AssemblyInfo.cs index 42a713f..24ae080 100644 --- a/DuplicateAssemblyScanner/DuplicateAssemblyScanner/Properties/AssemblyInfo.cs +++ b/DuplicateAssemblyScanner/DuplicateAssemblyScanner/Properties/AssemblyInfo.cs @@ -1,36 +1,6 @@ -using System.Reflection; -using System.Runtime.CompilerServices; +using System.Reflection; using System.Runtime.InteropServices; -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("DuplicateAssemblyScanner")] -[assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("DuplicateAssemblyScanner")] -[assembly: AssemblyCopyright("Copyright © 2020")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM [assembly: Guid("efba373b-88b2-427a-8396-6f9d56c89f74")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] [assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/DuplicateAssemblyScanner/DuplicateAssemblyScanner/UserMod.cs b/DuplicateAssemblyScanner/DuplicateAssemblyScanner/UserMod.cs index 7df37c3..73b4690 100644 --- a/DuplicateAssemblyScanner/DuplicateAssemblyScanner/UserMod.cs +++ b/DuplicateAssemblyScanner/DuplicateAssemblyScanner/UserMod.cs @@ -1,32 +1,51 @@ namespace DuplicateAssemblyScanner { - using ColossalFramework.UI; using DuplicateAssemblyScanner.Util; using ICities; using JetBrains.Annotations; - using UnityEngine.SceneManagement; + /// + /// The main mod class which the game instantiates when the mod is enabled. + /// public class UserMod : IUserMod { + /// + /// Gets mod name shown in content manager and options screens. + /// Version defined in Solution Items > VersionInfo.cs file. + /// [UsedImplicitly] - public string Name => "DAS"; + public string Name => $"DAS v{typeof(UserMod).Assembly.GetName().Version.ToString(3)}"; + /// + /// Gets mod description shown in content manager. + /// [UsedImplicitly] public string Description => "Scans for duplicate assemblies in the app domain, which can cause bugs."; + /// + /// Called when mod is enabled. + /// [UsedImplicitly] public void OnEnabled() { Log.Info("Enabled"); } + /// + /// Called when settings UI is required. + /// + /// + /// Helper for creating UI. [UsedImplicitly] public void OnSettingsUI(UIHelperBase helper) { Log.Info("SettingsUI"); Settings.CreateUI(helper); } + /// + /// Called when mod is disabled. + /// [UsedImplicitly] public void OnDisabled() { Log.Info("Disabled"); } } -} \ No newline at end of file +} diff --git a/DuplicateAssemblyScanner/DuplicateAssemblyScanner/Util/Assemblies.cs b/DuplicateAssemblyScanner/DuplicateAssemblyScanner/Util/Assemblies.cs index b6c94c6..c8debc1 100644 --- a/DuplicateAssemblyScanner/DuplicateAssemblyScanner/Util/Assemblies.cs +++ b/DuplicateAssemblyScanner/DuplicateAssemblyScanner/Util/Assemblies.cs @@ -8,6 +8,9 @@ namespace DuplicateAssemblyScanner.Util { using System.Reflection; using static ColossalFramework.Plugins.PluginManager; + /// + /// Scans for duplicate assemblies and, where found, attempts to work out which mods they are from. + /// public class Assemblies { /// @@ -47,7 +50,7 @@ public static Dictionary> Scan(out bool duplicatesFound) { } else { results.Add(name, new List() { - { ver } + { ver }, }); } diff --git a/DuplicateAssemblyScanner/DuplicateAssemblyScanner/Util/Log.cs b/DuplicateAssemblyScanner/DuplicateAssemblyScanner/Util/Log.cs index 432dd6b..b454af0 100644 --- a/DuplicateAssemblyScanner/DuplicateAssemblyScanner/Util/Log.cs +++ b/DuplicateAssemblyScanner/DuplicateAssemblyScanner/Util/Log.cs @@ -1,27 +1,73 @@ namespace DuplicateAssemblyScanner.Util { using System.Diagnostics; using System.IO; + using System.Reflection; using UnityEngine; + /// + /// A simple logging class. + /// + /// When mod activates, it creates a log file in same location as `output_log.txt`. + /// Mac users: It will be in the Cities app contents. + /// public class Log { - public static readonly string LogFileName = "DuplicateAssemblyScanner.log"; - private enum LogLevel { - Debug, - Info, - Error - } + /// + /// File name for log file. + /// + public static readonly string LogFileName = $"{typeof(Log).Assembly.GetName().Name}.log"; + /// + /// Full path and file name of log file. + /// private static readonly string LogFilePath = Path.Combine(Application.dataPath, LogFileName); + /// + /// Set to true to include timestamp in log entries. + /// + private static readonly bool TimeStamp = false; + + /// + /// Stopwatch used if is true. + /// + private static readonly Stopwatch Timer; + + /// + /// Initializes static members of the class. + /// Resets log file on startup. + /// static Log() { try { if (File.Exists(LogFilePath)) { File.Delete(LogFilePath); } - } catch { } + + if (TimeStamp) { + Timer = Stopwatch.StartNew(); + } + + AssemblyName details = typeof(Log).Assembly.GetName(); + Info($"{details.Name} v{details.Version.ToString()}", true); + } catch { + // ignore + } } + /// + /// Log levels. Also output in log file. + /// + private enum LogLevel { + Debug, + Info, + Error, + } + + /// + /// Logs debug trace, only in DEBUG builds. + /// + /// + /// Log entry text. + /// If true will copy to the main game log file. [Conditional("DEBUG")] public static void Debug(string message, bool copyToGameLog = false) { LogToFile(message, LogLevel.Debug); @@ -30,6 +76,12 @@ public static void Debug(string message, bool copyToGameLog = false) { } } + /// + /// Logs info message. + /// + /// + /// Log entry text. + /// If true will copy to the main game log file. public static void Info(string message, bool copyToGameLog = false) { LogToFile(message, LogLevel.Info); if (copyToGameLog) { @@ -37,6 +89,12 @@ public static void Info(string message, bool copyToGameLog = false) { } } + /// + /// Logs error message and also outputs a stack trace. + /// + /// + /// Log entry text. + /// If true will copy to the main game log file. public static void Error(string message, bool copyToGameLog = true) { LogToFile(message, LogLevel.Error); if (copyToGameLog) { @@ -44,17 +102,31 @@ public static void Error(string message, bool copyToGameLog = true) { } } - private static void LogToFile(string log, LogLevel level) { + /// + /// Write a message to log file. + /// + /// + /// Log entry text. + /// Logging level. If set to a stack trace will be appended. + private static void LogToFile(string message, LogLevel level) { try { using (StreamWriter w = File.AppendText(LogFilePath)) { - w.Write("{0, -9}", $"[{level.ToString()}] "); - w.WriteLine(log); + w.Write("{0, -8}", $"[{level.ToString()}] "); + + if (TimeStamp) { + w.Write("{0, 15}", Timer.ElapsedTicks + " | "); + } + + w.WriteLine(message); + if (level == LogLevel.Error) { w.WriteLine(new StackTrace().ToString()); w.WriteLine(); } } - } catch { } + } catch { + // ignore + } } } } \ No newline at end of file diff --git a/DuplicateAssemblyScanner/DuplicateAssemblyScanner/Util/Settings.cs b/DuplicateAssemblyScanner/DuplicateAssemblyScanner/Util/Settings.cs index b6da1cf..d483cb0 100644 --- a/DuplicateAssemblyScanner/DuplicateAssemblyScanner/Util/Settings.cs +++ b/DuplicateAssemblyScanner/DuplicateAssemblyScanner/Util/Settings.cs @@ -3,34 +3,20 @@ namespace DuplicateAssemblyScanner.Util { using ICities; using System.Collections.Generic; + /// + /// Generates the settings screen based on cached results from the assembly scanner. + /// public class Settings { /// /// Cache of assembly scan results. /// - internal static Dictionary> cacheDictionary_; + private static Dictionary> cacheDictionary_; /// /// Cache of whether duplicates were found by the scan. /// - internal static bool cacheDuplicates_; - - /// - /// Get (cached) results of assembly scan. - /// - /// - /// Will be true if duplicates found. - /// Dictionary of assembly lists keyed by assembly name. - internal static Dictionary> CacheScanResults(out bool duplicatesFound) { - - if (cacheDictionary_ == null) { - cacheDictionary_ = Assemblies.Scan(out bool issues); - cacheDuplicates_ = issues; - } - - duplicatesFound = cacheDuplicates_; - return cacheDictionary_; - } + private static bool cacheDuplicates_; /// /// Generate the options screen listing all the duplicates (if found). @@ -64,11 +50,34 @@ public static void CreateUI(UIHelperBase helper) { checkbox.isEnabled = false; } } - } } } - internal static void NoOp(bool _) { } + /// + /// A dummy click handler for checkboxes. Does nothing. + /// + /// + /// Ignored paramter. + internal static void NoOp(bool _) { + // do nothing + } + + /// + /// Get (cached) results of assembly scan. + /// + /// + /// Will be true if duplicates found. + /// Dictionary of assembly lists keyed by assembly name. + private static Dictionary> CacheScanResults(out bool duplicatesFound) { + + if (cacheDictionary_ == null) { + cacheDictionary_ = Assemblies.Scan(out bool issues); + cacheDuplicates_ = issues; + } + + duplicatesFound = cacheDuplicates_; + return cacheDictionary_; + } } } diff --git a/DuplicateAssemblyScanner/DuplicateAssemblyScanner/packages.config b/DuplicateAssemblyScanner/DuplicateAssemblyScanner/packages.config new file mode 100644 index 0000000..5011e19 --- /dev/null +++ b/DuplicateAssemblyScanner/DuplicateAssemblyScanner/packages.config @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/DuplicateAssemblyScanner/VersionInfo.cs b/DuplicateAssemblyScanner/VersionInfo.cs new file mode 100644 index 0000000..a14c2dc --- /dev/null +++ b/DuplicateAssemblyScanner/VersionInfo.cs @@ -0,0 +1,27 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +// Assembly name - both must have same value +[assembly: AssemblyTitle("DuplicateAssemblyScanner")] +[assembly: AssemblyProduct("DuplicateAssemblyScanner")] + +// Description - can be empty string +[assembly: AssemblyDescription("Scans for duplicate assemblies in the app domain, which can cause bugs.")] + +// Ownership information +[assembly: AssemblyCompany("Cities Skylines Mods")] +[assembly: AssemblyCopyright("MIT")] +[assembly: AssemblyTrademark("")] + +// Culture information (usually leave blank) +[assembly: AssemblyCulture("")] + +// Do not change. +[assembly: ComVisible(false)] + +// Version information for an assembly consists of the following four values: +// Major Version +// Minor Version +// Build Number +// Revision (* = auto) +[assembly: AssemblyVersion("1.1.0.*")]