diff --git a/.gitignore b/.gitignore
index 72de34f1ea..faab97ce4f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,6 +3,10 @@
##
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
+# Nitrox DocFx files
+Nitrox.DocFx/api/
+Nitrox.DocFx/_site/
+
# User-specific files
*.rsuser
*.suo
@@ -385,4 +389,4 @@ FodyWeavers.xsd
# JetBrains Rider
.idea/
-*.sln.iml
\ No newline at end of file
+*.sln.iml
diff --git a/Directory.Build.props b/Directory.Build.props
index b9087c4bc0..6903dbdf69 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -7,7 +7,7 @@
false
false
false
- $(SolutionDir)Nitrox.BuildTool\bin\
+ $(MSBuildThisFileDirectory)Nitrox.BuildTool\bin\
$(BuildToolDir)generated_files\
$(BuildGenDir)publicized_assemblies\
true
@@ -22,7 +22,7 @@
true
-
+
true
@@ -43,15 +43,10 @@
all
runtime; build; native; contentfiles; analyzers; buildtransitive
-
-
-
-
-
-
-
-
-
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
@@ -61,11 +56,11 @@
-
+
Nitrox.BuildTool
false
-
+
NitroxModel
diff --git a/Nitrox.Analyzers/Diagnostics/DependencyInjectionMisuseAnalyzer.cs b/Nitrox.Analyzers/Diagnostics/DependencyInjectionMisuseAnalyzer.cs
deleted file mode 100644
index 0f81382354..0000000000
--- a/Nitrox.Analyzers/Diagnostics/DependencyInjectionMisuseAnalyzer.cs
+++ /dev/null
@@ -1,89 +0,0 @@
-using System;
-using System.Collections.Immutable;
-using System.Linq;
-using Microsoft.CodeAnalysis;
-using Microsoft.CodeAnalysis.CSharp;
-using Microsoft.CodeAnalysis.CSharp.Syntax;
-using Microsoft.CodeAnalysis.Diagnostics;
-using Nitrox.Analyzers.Extensions;
-
-namespace Nitrox.Analyzers.Diagnostics;
-
-///
-/// Dependency injection shouldn't be used in types that we can instantiate ourselves (i.e. not MonoBehaviours or Harmony patches).
-/// We should use the Dependency Injection container only when we can apply the DI pattern to effect. If not, making said type static is often more readable and performant.
-///
-[DiagnosticAnalyzer(LanguageNames.CSharp)]
-public sealed class DependencyInjectionMisuseAnalyzer : DiagnosticAnalyzer
-{
- public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rules.MisusedDependencyInjection);
-
- public override void Initialize(AnalysisContext context)
- {
- context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
- context.EnableConcurrentExecution();
-
- context.RegisterCompilationStartAction(analysisContext =>
- {
- INamedTypeSymbol unityEngineObjectTypeSymbol = analysisContext.Compilation.GetTypeByMetadataName("UnityEngine.Object");
- INamedTypeSymbol nitroxPatchTypeSymbol = analysisContext.Compilation.GetTypeByMetadataName("NitroxPatcher.Patches.NitroxPatch");
-
- analysisContext.RegisterSyntaxNodeAction(c => AnalyzeDependencyInjectionMisuse(c, unityEngineObjectTypeSymbol, nitroxPatchTypeSymbol), SyntaxKind.InvocationExpression);
- });
- }
-
- private static void AnalyzeDependencyInjectionMisuse(SyntaxNodeAnalysisContext context, params INamedTypeSymbol[] allowedTypesUsingDependencyInjection)
- {
- InvocationExpressionSyntax expression = (InvocationExpressionSyntax)context.Node;
- if (expression.ChildNodes().FirstOrDefault(n => n is MemberAccessExpressionSyntax) is not MemberAccessExpressionSyntax memberAccessExpression)
- {
- return;
- }
- if (memberAccessExpression.DescendantNodes().FirstOrDefault(n => n is IdentifierNameSyntax) is not IdentifierNameSyntax memberAccessIdentifier)
- {
- return;
- }
- if (memberAccessIdentifier.Parent is TypeOfExpressionSyntax)
- {
- return;
- }
- if (!memberAccessIdentifier.GetName().Equals("NitroxServiceLocator", StringComparison.Ordinal))
- {
- return;
- }
- TypeDeclarationSyntax declaringType = expression.FindInParents();
- if (declaringType == null)
- {
- return;
- }
- INamedTypeSymbol declaringTypeSymbol = context.SemanticModel.GetDeclaredSymbol(declaringType);
- if (declaringTypeSymbol == null)
- {
- return;
- }
- foreach (INamedTypeSymbol allowedType in allowedTypesUsingDependencyInjection)
- {
- if (declaringTypeSymbol.IsType(allowedType))
- {
- return;
- }
- }
-
- Rules.ReportMisusedDependencyInjection(context, declaringType, memberAccessExpression.GetLocation());
- }
-
- private static class Rules
- {
- public static readonly DiagnosticDescriptor MisusedDependencyInjection = new("DIMA001",
- "Dependency Injection container is used directly",
- "The DI container should not be used directly in type '{0}' as the requested service can be supplied via a constructor parameter.",
- "Usage",
- DiagnosticSeverity.Warning,
- true);
-
- public static void ReportMisusedDependencyInjection(SyntaxNodeAnalysisContext context, TypeDeclarationSyntax declaringType, Location location)
- {
- context.ReportDiagnostic(Diagnostic.Create(MisusedDependencyInjection, location, declaringType.GetName()));
- }
- }
-}
diff --git a/Nitrox.Analyzers/Diagnostics/EnumeratorUsageAnalyzer.cs b/Nitrox.Analyzers/Diagnostics/EnumeratorUsageAnalyzer.cs
deleted file mode 100644
index 22e93cac8f..0000000000
--- a/Nitrox.Analyzers/Diagnostics/EnumeratorUsageAnalyzer.cs
+++ /dev/null
@@ -1,61 +0,0 @@
-using System.Collections;
-using System.Collections.Immutable;
-using Microsoft.CodeAnalysis;
-using Microsoft.CodeAnalysis.CSharp;
-using Microsoft.CodeAnalysis.CSharp.Syntax;
-using Microsoft.CodeAnalysis.Diagnostics;
-using Nitrox.Analyzers.Extensions;
-
-namespace Nitrox.Analyzers.Diagnostics;
-
-///
-/// Test that calls to a method returning an IEnumerator are iterated (MoveNext is called). If they aren't iterated than the code in them won't
-/// continue after the first 'yield return'.
-///
-[DiagnosticAnalyzer(LanguageNames.CSharp)]
-public sealed class EnumeratorUsageAnalyzer : DiagnosticAnalyzer
-{
- public const string UNUSED_ENUMERATOR = $"{nameof(EnumeratorUsageAnalyzer)}001";
-
- private static readonly DiagnosticDescriptor unusedEnumerator = new(UNUSED_ENUMERATOR,
- "IEnumerator is not iterated",
- $"The IEnumerator '{{0}}' must be iterated by calling its {nameof(IEnumerator.MoveNext)} otherwise it will stop executing at the first 'yield return' expression",
- "Usage",
- DiagnosticSeverity.Warning,
- true);
-
- public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(unusedEnumerator);
-
- public override void Initialize(AnalysisContext context)
- {
- context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
- context.EnableConcurrentExecution();
-
- context.RegisterSyntaxNodeAction(static c => AnalyzeIEnumeratorInvocation(c), SyntaxKind.InvocationExpression);
- }
-
- private static void AnalyzeIEnumeratorInvocation(SyntaxNodeAnalysisContext context)
- {
- InvocationExpressionSyntax expression = (InvocationExpressionSyntax)context.Node;
- if (expression.Parent == null)
- {
- return;
- }
- IMethodSymbol methodSymbol = context.SemanticModel.GetSymbolInfo(expression, context.CancellationToken).Symbol as IMethodSymbol;
- if (methodSymbol == null)
- {
- return;
- }
- // Ignore if method invoke is used/wrapped by something (variable declaration, as a parameter, etc).
- if (!expression.Parent.IsKind(SyntaxKind.ExpressionStatement))
- {
- return;
- }
- if (!methodSymbol.ReturnType.IsType(context.SemanticModel, "System.Collections.IEnumerator"))
- {
- return;
- }
-
- context.ReportDiagnostic(Diagnostic.Create(unusedEnumerator, expression.GetLocation(), methodSymbol.Name));
- }
-}
diff --git a/Nitrox.Analyzers/Diagnostics/LocalizationAnalyzer.cs b/Nitrox.Analyzers/Diagnostics/LocalizationAnalyzer.cs
deleted file mode 100644
index 0b8615d217..0000000000
--- a/Nitrox.Analyzers/Diagnostics/LocalizationAnalyzer.cs
+++ /dev/null
@@ -1,173 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Collections.Immutable;
-using System.IO;
-using System.Linq;
-using System.Text.RegularExpressions;
-using Microsoft.CodeAnalysis;
-using Microsoft.CodeAnalysis.CSharp;
-using Microsoft.CodeAnalysis.CSharp.Syntax;
-using Microsoft.CodeAnalysis.Diagnostics;
-
-namespace Nitrox.Analyzers.Diagnostics;
-
-///
-/// Tests that requested localization keys exist in the English localization file.
-///
-[DiagnosticAnalyzer(LanguageNames.CSharp)]
-public sealed class LocalizationAnalyzer : DiagnosticAnalyzer
-{
- public const string INVALID_LOCALIZATION_KEY_DIAGNOSTIC_ID = $"{nameof(LocalizationAnalyzer)}001";
-
- private const string NITROX_LOCALIZATION_PREFIX = "Nitrox_";
- private static readonly string relativePathFromSolutionDirToEnglishLanguageFile = Path.Combine("Nitrox.Assets.Subnautica", "LanguageFiles", "en.json");
- private static readonly Regex localizationParseRegex = new(@"^\s*""([^""]+)""\s*:\s*""([^""]+)""", RegexOptions.Compiled | RegexOptions.Multiline);
-
- private static readonly DiagnosticDescriptor invalidLocalizationKeyRule = new(INVALID_LOCALIZATION_KEY_DIAGNOSTIC_ID,
- "Tests localization usages are valid",
- "Localization key '{0}' does not exist in '{1}'",
- "Usage",
- DiagnosticSeverity.Warning,
- true,
- "Tests that requested localization keys exist in the English localization file");
-
- ///
- /// Gets the list of rules of supported diagnostics.
- ///
- public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(invalidLocalizationKeyRule);
-
- ///
- /// Initializes the analyzer by registering on symbol occurrence in the targeted code.
- ///
- public override void Initialize(AnalysisContext context)
- {
- context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
- context.EnableConcurrentExecution();
-
- context.RegisterCompilationStartAction(startContext =>
- {
- IMethodSymbol languageGetMethodSymbol = startContext.Compilation.GetTypesByMetadataName("Language").FirstOrDefault(a => a.ContainingAssembly.Name.Equals("Assembly-Csharp", StringComparison.OrdinalIgnoreCase))?.GetMembers("Get").FirstOrDefault(m => m.Kind == SymbolKind.Method) as IMethodSymbol;
- if (languageGetMethodSymbol == null)
- {
- return;
- }
-
- startContext.Options.AnalyzerConfigOptionsProvider.GlobalOptions.TryGetValue("build_property.projectdir", out string projectDir);
- if (LocalizationHelper.Load(projectDir))
- {
- startContext.RegisterSyntaxNodeAction(c => AnalyzeStringNode(c, languageGetMethodSymbol), SyntaxKind.StringLiteralExpression);
- }
- });
- }
-
- ///
- /// Analyzes string literals in code that are passed as argument to 'Language.main.Get'.
- ///
- private void AnalyzeStringNode(SyntaxNodeAnalysisContext context, IMethodSymbol languageGetMethodSymbol)
- {
- LiteralExpressionSyntax expression = (LiteralExpressionSyntax)context.Node;
- if (expression.Parent is not ArgumentSyntax argument)
- {
- return;
- }
- if (argument.Parent is not { Parent: InvocationExpressionSyntax invocation })
- {
- return;
- }
- if (context.SemanticModel.GetSymbolInfo(invocation).Symbol is not IMethodSymbol method)
- {
- return;
- }
- if (!SymbolEqualityComparer.Default.Equals(method, languageGetMethodSymbol))
- {
- return;
- }
- // Ignore language call for non-nitrox keys.
- string stringValue = expression.Token.ValueText;
- if (!stringValue.StartsWith(NITROX_LOCALIZATION_PREFIX, StringComparison.OrdinalIgnoreCase))
- {
- return;
- }
- if (LocalizationHelper.ContainsKey(stringValue))
- {
- return;
- }
- context.ReportDiagnostic(Diagnostic.Create(invalidLocalizationKeyRule, context.Node.GetLocation(), stringValue, LocalizationHelper.FileName));
- }
-
- ///
- /// Wrapper API for synchronized access to the English localization file.
- ///
- private static class LocalizationHelper
- {
- private static readonly object locker = new();
- private static string EnglishLocalizationFileName { get; set; } = "";
- private static ImmutableDictionary EnglishLocalization { get; set; } = ImmutableDictionary.Empty;
-
- public static bool IsEmpty
- {
- get
- {
- lock (locker)
- {
- return EnglishLocalization.IsEmpty;
- }
- }
- }
-
- public static string FileName
- {
- get
- {
- lock (locker)
- {
- return EnglishLocalizationFileName;
- }
- }
- }
-
- public static bool ContainsKey(string key)
- {
- lock (locker)
- {
- return EnglishLocalization.ContainsKey(key);
- }
- }
-
- public static bool Load(string projectDir)
- {
- if (string.IsNullOrWhiteSpace(projectDir))
- {
- return false;
- }
- string solutionDir = Directory.GetParent(projectDir)?.Parent?.FullName;
- if (!Directory.Exists(solutionDir))
- {
- return false;
- }
-
- string enJson;
- lock (locker)
- {
- EnglishLocalizationFileName = Path.Combine(solutionDir, relativePathFromSolutionDirToEnglishLanguageFile);
- if (!File.Exists(EnglishLocalizationFileName))
- {
- return false;
- }
-
- enJson = File.ReadAllText(EnglishLocalizationFileName);
- }
- // Parse localization JSON to dictionary for lookup.
- Dictionary keyValue = new();
- foreach (Match match in localizationParseRegex.Matches(enJson))
- {
- keyValue.Add(match.Groups[1].Value, match.Groups[2].Value);
- }
- lock (locker)
- {
- EnglishLocalization = keyValue.ToImmutableDictionary();
- }
- return true;
- }
- }
-}
diff --git a/Nitrox.Analyzers/Diagnostics/StringUsageAnalyzer.cs b/Nitrox.Analyzers/Diagnostics/StringUsageAnalyzer.cs
deleted file mode 100644
index 26cd375970..0000000000
--- a/Nitrox.Analyzers/Diagnostics/StringUsageAnalyzer.cs
+++ /dev/null
@@ -1,80 +0,0 @@
-using System.Collections.Immutable;
-using Microsoft.CodeAnalysis;
-using Microsoft.CodeAnalysis.CSharp;
-using Microsoft.CodeAnalysis.CSharp.Syntax;
-using Microsoft.CodeAnalysis.Diagnostics;
-
-namespace Nitrox.Analyzers.Diagnostics;
-
-[DiagnosticAnalyzer(LanguageNames.CSharp)]
-public sealed class StringUsageAnalyzer : DiagnosticAnalyzer
-{
- public const string PREFER_INTERPOLATED_STRING_DIAGNOSTIC_ID = $"{nameof(StringUsageAnalyzer)}001";
-
- private static readonly DiagnosticDescriptor preferInterpolatedStringRule = new(PREFER_INTERPOLATED_STRING_DIAGNOSTIC_ID,
- "Prefer interpolated string over string concat",
- "String concat can be turned into interpolated string",
- "Usage",
- DiagnosticSeverity.Warning,
- true,
- "Prefer interpolated string over concatenating strings");
-
- public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(preferInterpolatedStringRule);
-
- public override void Initialize(AnalysisContext context)
- {
- context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
- context.EnableConcurrentExecution();
-
- context.RegisterSyntaxNodeAction(AnalyzeAddNode, SyntaxKind.AddExpression);
- }
-
- private void AnalyzeAddNode(SyntaxNodeAnalysisContext context)
- {
- bool IsPartOfStringConcat(SyntaxNode node)
- {
- switch (node)
- {
- case LiteralExpressionSyntax literal:
- return literal.IsKind(SyntaxKind.StringLiteralExpression);
- case InterpolatedStringExpressionSyntax:
- return true;
- case MemberAccessExpressionSyntax member:
- string memberType = context.SemanticModel.GetTypeInfo(member).ConvertedType?.Name;
- return memberType == "String";
- case BinaryExpressionSyntax binary:
- // If one side is string-ish then the other side will get implicitly casted to string.
- return binary.IsKind(SyntaxKind.AddExpression) && (IsPartOfStringConcat(binary.Right) || IsPartOfStringConcat(binary.Left));
- default:
- return false;
- }
- }
-
- static bool IsLeftMostNodeInConcat(SyntaxNode node)
- {
- switch (node)
- {
- case BinaryExpressionSyntax:
- case InterpolatedStringContentSyntax:
- return false;
- case ParenthesizedExpressionSyntax:
- return IsLeftMostNodeInConcat(node.Parent);
- }
- return true;
- }
-
- BinaryExpressionSyntax expression = (BinaryExpressionSyntax)context.Node;
- // Deduplicate warnings. Only left most '+' of the expression should be handled here.
- if (!IsLeftMostNodeInConcat(expression.Parent))
- {
- return;
- }
- // Test if this should be interpolated.
- if (!IsPartOfStringConcat(expression.Left) && !IsPartOfStringConcat(expression.Right))
- {
- return;
- }
-
- context.ReportDiagnostic(Diagnostic.Create(preferInterpolatedStringRule, expression.GetLocation(), expression));
- }
-}
diff --git a/Nitrox.Analyzers/Diagnostics/UnitySkippedObjectLifetimeAnalyzer.cs b/Nitrox.Analyzers/Diagnostics/UnitySkippedObjectLifetimeAnalyzer.cs
deleted file mode 100644
index 26a6df3c29..0000000000
--- a/Nitrox.Analyzers/Diagnostics/UnitySkippedObjectLifetimeAnalyzer.cs
+++ /dev/null
@@ -1,123 +0,0 @@
-using System.Collections.Immutable;
-using Microsoft.CodeAnalysis;
-using Microsoft.CodeAnalysis.CSharp;
-using Microsoft.CodeAnalysis.CSharp.Syntax;
-using Microsoft.CodeAnalysis.Diagnostics;
-using Nitrox.Analyzers.Extensions;
-
-namespace Nitrox.Analyzers.Diagnostics;
-
-///
-/// Test that Unity objects are properly checked for their lifetime.
-/// The lifetime check is skipped when using "is null" or "obj?.member" as opposed to "== null".
-///
-[DiagnosticAnalyzer(LanguageNames.CSharp)]
-public sealed class UnitySkippedObjectLifetimeAnalyzer : DiagnosticAnalyzer
-{
- public const string FIX_FUNCTION_NAME = "AliveOrNull";
- public const string FIX_FUNCTION_NAMESPACE = "NitroxClient.Unity.Helper";
- public const string CONDITIONAL_ACCESS_DIAGNOSTIC_ID = $"{nameof(UnitySkippedObjectLifetimeAnalyzer)}001";
- public const string IS_NULL_DIAGNOSTIC_ID = $"{nameof(UnitySkippedObjectLifetimeAnalyzer)}002";
- public const string NULL_COALESCE_DIAGNOSTIC_ID = $"{nameof(UnitySkippedObjectLifetimeAnalyzer)}003";
- private const string RULE_TITLE = "Tests that Unity object lifetime is not ignored";
- private const string RULE_DESCRIPTION = "Tests that Unity object lifetime checks are not ignored.";
-
- private static readonly DiagnosticDescriptor conditionalAccessRule = new(CONDITIONAL_ACCESS_DIAGNOSTIC_ID,
- RULE_TITLE,
- "'?.' is invalid on type '{0}' as it derives from 'UnityEngine.Object', bypassing the Unity object lifetime check",
- "Usage",
- DiagnosticSeverity.Error,
- true,
- RULE_DESCRIPTION);
-
- private static readonly DiagnosticDescriptor isNullRule = new(IS_NULL_DIAGNOSTIC_ID,
- RULE_TITLE,
- "'is null' is invalid on type '{0}' as it derives from 'UnityEngine.Object', bypassing the Unity object lifetime check",
- "Usage",
- DiagnosticSeverity.Error,
- true,
- RULE_DESCRIPTION);
-
- private static readonly DiagnosticDescriptor nullCoalesceRule = new(NULL_COALESCE_DIAGNOSTIC_ID,
- RULE_TITLE,
- "'??' is invalid on type '{0}' as it derives from 'UnityEngine.Object', bypassing the Unity object lifetime check",
- "Usage",
- DiagnosticSeverity.Error,
- true,
- RULE_DESCRIPTION);
-
- ///
- /// Gets the list of rules of supported diagnostics.
- ///
- public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(conditionalAccessRule, isNullRule, nullCoalesceRule);
-
- ///
- /// Initializes the analyzer by registering on symbol occurrence in the targeted code.
- ///
- public override void Initialize(AnalysisContext context)
- {
- context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
- context.EnableConcurrentExecution();
-
- context.RegisterCompilationStartAction(compStartContext =>
- {
- INamedTypeSymbol unityObjectTypeSymbol = compStartContext.Compilation.GetTypeByMetadataName("UnityEngine.Object");
- if (unityObjectTypeSymbol == null)
- {
- return;
- }
-
- compStartContext.RegisterSyntaxNodeAction(c => AnalyzeIsNullNode(c, unityObjectTypeSymbol), SyntaxKind.IsPatternExpression);
- compStartContext.RegisterSyntaxNodeAction(c => AnalyzeConditionalAccessNode(c, unityObjectTypeSymbol), SyntaxKind.ConditionalAccessExpression);
- compStartContext.RegisterSyntaxNodeAction(c => AnalyzeCoalesceNode(c, unityObjectTypeSymbol), SyntaxKind.CoalesceExpression);
- });
- }
-
- private void AnalyzeIsNullNode(SyntaxNodeAnalysisContext context, ITypeSymbol unityObjectSymbol)
- {
- IsPatternExpressionSyntax expression = (IsPatternExpressionSyntax)context.Node;
- // Is this a "is null" check?
- if (expression.Pattern is not ConstantPatternSyntax constantPattern)
- {
- return;
- }
- if (constantPattern.Expression is not LiteralExpressionSyntax literal || !literal.Token.IsKind(SyntaxKind.NullKeyword))
- {
- return;
- }
- // Is it on a UnityEngine.Object?
- if (IsUnityObjectExpression(context, expression.Expression, unityObjectSymbol, out ITypeSymbol originSymbol))
- {
- context.ReportDiagnostic(Diagnostic.Create(isNullRule, constantPattern.GetLocation(), originSymbol!.Name));
- }
- }
-
- private void AnalyzeConditionalAccessNode(SyntaxNodeAnalysisContext context, ITypeSymbol unityObjectSymbol)
- {
- static bool IsFixedWithAliveOrNull(SyntaxNodeAnalysisContext context, ConditionalAccessExpressionSyntax expression)
- {
- return (context.SemanticModel.GetSymbolInfo(expression.Expression).Symbol as IMethodSymbol)?.Name == FIX_FUNCTION_NAME;
- }
-
- ConditionalAccessExpressionSyntax expression = (ConditionalAccessExpressionSyntax)context.Node;
- if (IsUnityObjectExpression(context, expression.Expression, unityObjectSymbol, out ITypeSymbol originSymbol) && !IsFixedWithAliveOrNull(context, expression))
- {
- context.ReportDiagnostic(Diagnostic.Create(conditionalAccessRule, context.Node.GetLocation(), originSymbol!.Name));
- }
- }
-
- private void AnalyzeCoalesceNode(SyntaxNodeAnalysisContext context, ITypeSymbol unityObjectSymbol)
- {
- BinaryExpressionSyntax expression = (BinaryExpressionSyntax)context.Node;
- if (IsUnityObjectExpression(context, expression.Left, unityObjectSymbol, out ITypeSymbol originSymbol))
- {
- context.ReportDiagnostic(Diagnostic.Create(nullCoalesceRule, context.Node.GetLocation(), originSymbol!.Name));
- }
- }
-
- private bool IsUnityObjectExpression(SyntaxNodeAnalysisContext context, ExpressionSyntax possibleUnityAccessExpression, ITypeSymbol compareSymbol, out ITypeSymbol possibleUnitySymbol)
- {
- possibleUnitySymbol = context.SemanticModel.GetTypeInfo(possibleUnityAccessExpression).Type;
- return possibleUnitySymbol.IsType(compareSymbol);
- }
-}
diff --git a/Nitrox.Analyzers/Extensions/CompilationExtensions.cs b/Nitrox.Analyzers/Extensions/CompilationExtensions.cs
deleted file mode 100644
index 737482f0aa..0000000000
--- a/Nitrox.Analyzers/Extensions/CompilationExtensions.cs
+++ /dev/null
@@ -1,18 +0,0 @@
-using System;
-using System.Linq;
-using Microsoft.CodeAnalysis;
-
-namespace Nitrox.Analyzers.Extensions;
-
-internal static class CompilationExtensions
-{
- ///
- /// Returns the found or null.
- ///
- /// The compilation that should have the type.
- /// Assembly name, case sensitive.
- /// Type name include the namespace, case sensitive.
- ///
- public static INamedTypeSymbol GetType(this Compilation compilation, string assemblyNameWithoutDll, string fullTypeName) =>
- compilation.GetTypesByMetadataName(fullTypeName).FirstOrDefault(a => a.ContainingAssembly.Name.Equals(assemblyNameWithoutDll, StringComparison.Ordinal));
-}
diff --git a/Nitrox.Analyzers/Extensions/DebugExtensions.cs b/Nitrox.Analyzers/Extensions/DebugExtensions.cs
deleted file mode 100644
index 8a3fe00265..0000000000
--- a/Nitrox.Analyzers/Extensions/DebugExtensions.cs
+++ /dev/null
@@ -1,43 +0,0 @@
-using System;
-using System.Collections.Concurrent;
-using System.Diagnostics;
-using System.IO;
-using System.Threading.Tasks;
-
-namespace Nitrox.Analyzers.Extensions;
-
-public static class DebugExtensions
-{
- private static readonly string desktopPath = Environment.GetFolderPath(Environment.SpecialFolder.Desktop);
- private static readonly ConcurrentQueue<(object source, string message)> logQueue = new();
- private static readonly object logLocker = new();
-
- ///
- /// Can be used to test analyzers.
- ///
- [Conditional("DEBUG")]
- public static void Log(this object analyzer, string message)
- {
- if (analyzer == null)
- {
- return;
- }
-
- logQueue.Enqueue((analyzer, message));
- Task.Run(() =>
- {
- while (!logQueue.IsEmpty)
- {
- if (!logQueue.TryDequeue(out (object source, string message) pair))
- {
- continue;
- }
-
- lock (logLocker)
- {
- File.AppendAllText(Path.Combine(desktopPath, $"{pair.source.GetType().Name}.log"), pair.message + Environment.NewLine);
- }
- }
- });
- }
-}
diff --git a/Nitrox.Analyzers/Extensions/SymbolExtensions.cs b/Nitrox.Analyzers/Extensions/SymbolExtensions.cs
deleted file mode 100644
index b27442febe..0000000000
--- a/Nitrox.Analyzers/Extensions/SymbolExtensions.cs
+++ /dev/null
@@ -1,32 +0,0 @@
-using Microsoft.CodeAnalysis;
-
-namespace Nitrox.Analyzers.Extensions;
-
-public static class SymbolExtensions
-{
- public static bool IsType(this ITypeSymbol symbol, SemanticModel semanticModel, string fullyQualifiedTypeName)
- {
- return symbol.IsType(semanticModel.Compilation.GetTypeByMetadataName(fullyQualifiedTypeName));
- }
-
- public static bool IsType(this ITypeSymbol symbol, ITypeSymbol targetingSymbol)
- {
- if (symbol == null || targetingSymbol == null)
- {
- return false;
- }
- if (SymbolEqualityComparer.Default.Equals(symbol, targetingSymbol))
- {
- return true;
- }
- while (symbol.BaseType is { } baseTypeSymbol)
- {
- symbol = baseTypeSymbol;
- if (SymbolEqualityComparer.Default.Equals(symbol, targetingSymbol))
- {
- return true;
- }
- }
- return false;
- }
-}
diff --git a/Nitrox.Analyzers/Extensions/SyntaxExtensions.cs b/Nitrox.Analyzers/Extensions/SyntaxExtensions.cs
deleted file mode 100644
index d528829f4f..0000000000
--- a/Nitrox.Analyzers/Extensions/SyntaxExtensions.cs
+++ /dev/null
@@ -1,56 +0,0 @@
-using System.Linq;
-using Microsoft.CodeAnalysis;
-using Microsoft.CodeAnalysis.CSharp;
-using Microsoft.CodeAnalysis.CSharp.Syntax;
-
-namespace Nitrox.Analyzers.Extensions;
-
-public static class SyntaxExtensions
-{
- public static bool IsPartialType(this TypeDeclarationSyntax typeSyntax) => typeSyntax.Modifiers.Any(SyntaxKind.PartialKeyword);
-
-
- public static string GetNamespaceName(this TypeDeclarationSyntax type) => type.Ancestors()
- .Select(n => n switch
- {
- FileScopedNamespaceDeclarationSyntax f => f.Name.ToString(),
- NamespaceDeclarationSyntax ns => ns.Name.ToString(),
- _ => null
- })
- .First();
-
- public static string GetReturnTypeName(this MemberDeclarationSyntax member) => member switch
- {
- FieldDeclarationSyntax field => field.Declaration.ChildNodes().OfType().FirstOrDefault()?.Identifier.ValueText ?? "",
- MethodDeclarationSyntax method => method.ReturnType.ToString(),
- _ => ""
- };
-
- public static string GetName(this MemberDeclarationSyntax member) => member switch
- {
- FieldDeclarationSyntax field => field.Declaration.Variables.FirstOrDefault()?.Identifier.ValueText ?? "",
- TypeDeclarationSyntax type => type.Identifier.Text,
- _ => ""
- };
-
- public static T FindInParents(this SyntaxNode node) where T : SyntaxNode
- {
- if (node == null)
- {
- return null;
- }
- SyntaxNode cur = node.Parent;
- while (cur != null && cur is not T)
- {
- cur = cur.Parent;
- }
- return (T)cur;
- }
-
- public static string GetName(this SyntaxNode node) => node switch
- {
- FieldDeclarationSyntax field => field.Declaration.Variables.FirstOrDefault()?.Identifier.ValueText ?? "",
- TypeDeclarationSyntax type => type.Identifier.ValueText,
- _ => node.TryGetInferredMemberName()
- };
-}
diff --git a/Nitrox.Analyzers/Fixers/UnitySkippedObjectLifetimeFixProvider.cs b/Nitrox.Analyzers/Fixers/UnitySkippedObjectLifetimeFixProvider.cs
deleted file mode 100644
index b86e600d20..0000000000
--- a/Nitrox.Analyzers/Fixers/UnitySkippedObjectLifetimeFixProvider.cs
+++ /dev/null
@@ -1,70 +0,0 @@
-extern alias JB;
-using System.Collections.Immutable;
-using System.Composition;
-using System.Linq;
-using System.Threading;
-using System.Threading.Tasks;
-using JB::JetBrains.Annotations;
-using Microsoft.CodeAnalysis;
-using Microsoft.CodeAnalysis.CodeActions;
-using Microsoft.CodeAnalysis.CodeFixes;
-using Microsoft.CodeAnalysis.CSharp;
-using Microsoft.CodeAnalysis.CSharp.Syntax;
-using Microsoft.CodeAnalysis.Text;
-using Nitrox.Analyzers.Diagnostics;
-using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
-
-namespace Nitrox.Analyzers.Fixers;
-
-[Shared]
-[UsedImplicitly]
-[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(UnitySkippedObjectLifetimeFixProvider))]
-public sealed class UnitySkippedObjectLifetimeFixProvider : CodeFixProvider
-{
- private static readonly IdentifierNameSyntax aliveOrNull = IdentifierName(UnitySkippedObjectLifetimeAnalyzer.FIX_FUNCTION_NAME);
- public override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(UnitySkippedObjectLifetimeAnalyzer.CONDITIONAL_ACCESS_DIAGNOSTIC_ID);
-
- public override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer;
-
- public override async Task RegisterCodeFixesAsync(CodeFixContext context)
- {
- // Code template from: https://learn.microsoft.com/en-us/dotnet/csharp/roslyn-sdk/tutorials/how-to-write-csharp-analyzer-code-fix
- SyntaxNode root = await context.Document.GetSyntaxRootAsync(context.CancellationToken)
- .ConfigureAwait(false);
- Diagnostic diagnostic = context.Diagnostics.First();
- TextSpan diagnosticSpan = diagnostic.Location.SourceSpan;
- ConditionalAccessExpressionSyntax declaration = root!.FindToken(diagnosticSpan.Start).Parent!.AncestorsAndSelf()
- .OfType()
- .First();
- context.RegisterCodeFix(
- CodeAction.Create(
- equivalenceKey: UnitySkippedObjectLifetimeAnalyzer.CONDITIONAL_ACCESS_DIAGNOSTIC_ID,
- title: "Insert AliveOrNull() before conditional access of UnityEngine.Object",
- createChangedDocument: c => InsertAliveOrNullAsync(context.Document, declaration, c)
- ),
- diagnostic);
- }
-
- private async Task InsertAliveOrNullAsync(Document document, ConditionalAccessExpressionSyntax declaration, CancellationToken cancellationToken)
- {
- SyntaxNode root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
- if (root == null)
- {
- return document;
- }
-
- // 1. Wrap expression with an invocation to AliveOrNull, this will cause AliveOrNull to be called before the conditional access.
- InvocationExpressionSyntax wrappedExpression = InvocationExpression(MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, declaration.Expression, aliveOrNull));
- SyntaxNode newDeclaration = declaration.ReplaceNode(declaration.Expression, wrappedExpression);
- root = root!.ReplaceNode(declaration, newDeclaration);
- // 2. Ensure using statement for extension method .AliveOrNull().
- // This is done after the "AliveOrNull" wrap because the declaration instance can't be found when root instance updates.
- if (root is CompilationUnitSyntax compilationRoot && compilationRoot.Usings.All(u => u.Name.ToString() != UnitySkippedObjectLifetimeAnalyzer.FIX_FUNCTION_NAMESPACE))
- {
- root = compilationRoot.AddUsings(UsingDirective(aliveOrNull));
- }
-
- // Replace the old document with the new.
- return document.WithSyntaxRoot(root);
- }
-}
diff --git a/Nitrox.Analyzers/Generators/HarmonyRegisterPatchGenerator.cs b/Nitrox.Analyzers/Generators/HarmonyRegisterPatchGenerator.cs
deleted file mode 100644
index c0a321e40a..0000000000
--- a/Nitrox.Analyzers/Generators/HarmonyRegisterPatchGenerator.cs
+++ /dev/null
@@ -1,213 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Collections.Immutable;
-using System.Diagnostics.CodeAnalysis;
-using System.Linq;
-using System.Text;
-using Microsoft.CodeAnalysis;
-using Microsoft.CodeAnalysis.CSharp;
-using Microsoft.CodeAnalysis.CSharp.Syntax;
-using Nitrox.Analyzers.Extensions;
-
-namespace Nitrox.Analyzers.Generators;
-
-///
-/// Implements the harmony patch registry boilerplate for NitroxPatch inherited types by scanning its static MethodInfo
-/// fields and static patch methods.
-///
-[Generator(LanguageNames.CSharp)]
-internal sealed class HarmonyRegisterPatchGenerator : IIncrementalGenerator
-{
- private static readonly string[] harmonyMethodTypes = { "prefix", "postfix", "transpiler", "finalizer", "manipulator" };
- private static readonly string[] validTargetMethodNames = { "target_method", "targetmethod", "target", "method" };
- private static readonly Lazy generatedCodeAttribute = new(() => $@"[global::System.CodeDom.Compiler.GeneratedCode(""{typeof(HarmonyRegisterPatchGenerator).FullName}"", ""{typeof(HarmonyRegisterPatchGenerator).Assembly.GetName().Version}"")]");
-
- [SuppressMessage("ReSharper", "SuggestVarOrType_Elsewhere")]
- public void Initialize(IncrementalGeneratorInitializationContext context)
- {
- // Setup compilation pipeline for assemblies that use NitroxPatch.
- var compilationPipeline = context.CompilationProvider.Select((c, _) => c.GetType("NitroxPatcher", "NitroxPatcher.Patches.NitroxPatch") != null);
- // Look for partial types inheriting our NitroxPatch type, selecting all the harmony methods and target method infos.
- var harmonyMethodsWithTargetMethods = context.SyntaxProvider
- .CreateSyntaxProvider(
- static (node, _) => IsSyntaxTargetForGeneration(node),
- static (context, _) => GetSemanticTargetForGeneration(context))
- .Where(r => r is not null)
- .WithComparer(NitroxHarmonyType.NitroxHarmonyTypeEqualityComparer.Instance);
-
- // Register the pipeline into the compiler.
- var combinedPipeline = harmonyMethodsWithTargetMethods.Combine(compilationPipeline);
- context.RegisterSourceOutput(combinedPipeline, static (context, source) => Execute(context, source.Left));
- }
-
- private static void Execute(SourceProductionContext context, NitroxHarmonyType nitroxHarmonyType)
- {
- // Build Patch method implementation.
- StringBuilder patchImpl = new();
- for (int fieldIndex = 0; fieldIndex < nitroxHarmonyType.MethodInfoFields.Length; fieldIndex++)
- {
- FieldDeclarationSyntax methodInfoField = nitroxHarmonyType.MethodInfoFields[fieldIndex];
- patchImpl.Append("PatchMultiple(harmony, ")
- .Append(methodInfoField.GetName());
- if (nitroxHarmonyType.HarmonyPatchMethods.Length > 0)
- {
- patchImpl.Append(", ");
- foreach (MethodDeclarationSyntax harmonyPatchMethod in nitroxHarmonyType.HarmonyPatchMethods)
- {
- patchImpl.Append(harmonyPatchMethod.Identifier.ValueText.ToLowerInvariant())
- .Append(": ((Delegate)")
- .Append(harmonyPatchMethod.Identifier.ValueText)
- .Append(").Method, ");
- }
- patchImpl.Remove(patchImpl.Length - 2, 2);
- }
- patchImpl.Append(");");
- // Append new line if not last implementation line.
- if (fieldIndex < nitroxHarmonyType.MethodInfoFields.Length - 1)
- {
- patchImpl.AppendLine();
- }
- }
-
- // Append new code to the compilation.
- context.AddSource($"{nitroxHarmonyType.NameSpace}.{nitroxHarmonyType.TypeName}.g.cs", $$"""
- #pragma warning disable
- using System;
- using HarmonyLib;
-
- namespace {{nitroxHarmonyType.NameSpace}};
-
- partial class {{nitroxHarmonyType.TypeName}}
- {
- {{generatedCodeAttribute.Value}}
- public override void Patch(Harmony harmony)
- {
- {{patchImpl}}
- }
- }
- """);
- }
-
- private static bool IsSyntaxTargetForGeneration(SyntaxNode node)
- {
- if (node is not TypeDeclarationSyntax type)
- {
- return false;
- }
- if (!type.IsPartialType())
- {
- return false;
- }
- // Skip if not deriving from "NitroxPatch".
- if (type.BaseList?.Types.FirstOrDefault(t => t.ToString().Equals("NitroxPatch", StringComparison.Ordinal)) == null)
- {
- return false;
- }
- // Skip if "Patch" method is already defined.
- if (type.Members.OfType().Any(m => m.Modifiers.Any(SyntaxKind.OverrideKeyword) && m.Identifier.ValueText == "Patch"))
- {
- return false;
- }
- return true;
- }
-
- private static NitroxHarmonyType GetSemanticTargetForGeneration(GeneratorSyntaxContext context)
- {
- static bool IsValidPatchMethodName(string methodName) => harmonyMethodTypes.Contains(methodName.ToLowerInvariant());
-
- static bool IsValidTargetMethodFieldName(string fieldName)
- {
- foreach (string n in validTargetMethodNames)
- {
- if (fieldName.StartsWith(n, StringComparison.OrdinalIgnoreCase))
- {
- return true;
- }
- }
- return false;
- }
-
- TypeDeclarationSyntax type = context.Node as TypeDeclarationSyntax;
- if (type == null)
- {
- return null;
- }
- ImmutableArray members = type.Members.ToImmutableArray();
- return new NitroxHarmonyType(type.GetNamespaceName(),
- type.Identifier.ValueText,
- members.OfType()
- .Where(m => m.Modifiers.Any(SyntaxKind.StaticKeyword) && IsValidPatchMethodName(m.Identifier.ValueText))
- .ToImmutableArray(),
- members.OfType()
- .Where(m => m.Modifiers.Any(SyntaxKind.StaticKeyword) && m.GetReturnTypeName() == "MethodInfo" && IsValidTargetMethodFieldName(m.GetName()))
- .ToImmutableArray());
- }
-
- internal record NitroxHarmonyType(string NameSpace, string TypeName, ImmutableArray HarmonyPatchMethods, ImmutableArray MethodInfoFields)
- {
- internal sealed class NitroxHarmonyTypeEqualityComparer : IEqualityComparer
- {
- public static IEqualityComparer Instance { get; } = new NitroxHarmonyTypeEqualityComparer();
-
- public bool Equals(NitroxHarmonyType x, NitroxHarmonyType y)
- {
- if (ReferenceEquals(x, y)) return true;
- if (ReferenceEquals(x, null)) return false;
- if (ReferenceEquals(y, null)) return false;
- if (x.GetType() != y.GetType()) return false;
- return string.Equals(x.NameSpace, y.NameSpace) &&
- string.Equals(x.TypeName, y.TypeName) &&
- x.HarmonyPatchMethods.SequenceEqual(y.HarmonyPatchMethods, HarmonyMethodEqualityComparer.Instance) &&
- x.MethodInfoFields.SequenceEqual(y.MethodInfoFields, MethodInfoFieldEqualityComparer.Instance);
- }
-
- public int GetHashCode(NitroxHarmonyType obj)
- {
- unchecked
- {
- int hashCode = obj.NameSpace != null ? obj.NameSpace.GetHashCode() : 0;
- hashCode = (hashCode * 397) ^ (obj.TypeName != null ? obj.TypeName.GetHashCode() : 0);
- return hashCode;
- }
- }
- }
-
- private sealed class HarmonyMethodEqualityComparer : IEqualityComparer
- {
- public static IEqualityComparer Instance { get; } = new HarmonyMethodEqualityComparer();
-
- public bool Equals(MethodDeclarationSyntax x, MethodDeclarationSyntax y)
- {
- if (ReferenceEquals(x, y)) return true;
- if (ReferenceEquals(x, null)) return false;
- if (ReferenceEquals(y, null)) return false;
- if (x.GetType() != y.GetType()) return false;
- return string.Equals(x.Identifier.ValueText, y.Identifier.ValueText);
- }
-
- public int GetHashCode(MethodDeclarationSyntax obj)
- {
- return obj.Identifier.ValueText.GetHashCode();
- }
- }
-
- private sealed class MethodInfoFieldEqualityComparer : IEqualityComparer
- {
- public static IEqualityComparer Instance { get; } = new MethodInfoFieldEqualityComparer();
-
- public bool Equals(FieldDeclarationSyntax x, FieldDeclarationSyntax y)
- {
- if (ReferenceEquals(x, y)) return true;
- if (ReferenceEquals(x, null)) return false;
- if (ReferenceEquals(y, null)) return false;
- if (x.GetType() != y.GetType()) return false;
- return x.ToString() == y.ToString();
- }
-
- public int GetHashCode(FieldDeclarationSyntax obj)
- {
- return obj.ToString().GetHashCode();
- }
- }
- }
-}
diff --git a/Nitrox.Analyzers/Nitrox.Analyzers.csproj b/Nitrox.Analyzers/Nitrox.Analyzers.csproj
deleted file mode 100644
index e9fcbafc79..0000000000
--- a/Nitrox.Analyzers/Nitrox.Analyzers.csproj
+++ /dev/null
@@ -1,18 +0,0 @@
-
-
-
-
- netstandard2.0
-
-
-
-
-
-
-
diff --git a/Nitrox.BuildTool/Nitrox.BuildTool.csproj b/Nitrox.BuildTool/Nitrox.BuildTool.csproj
index e60938f069..e22bc28891 100644
--- a/Nitrox.BuildTool/Nitrox.BuildTool.csproj
+++ b/Nitrox.BuildTool/Nitrox.BuildTool.csproj
@@ -1,4 +1,4 @@
-
+
net472
@@ -6,14 +6,15 @@
disable
bin\
false
+ Debug;Release;Linux
-
+
-
+
diff --git a/Nitrox.DocFx/.config/dotnet-tools.json b/Nitrox.DocFx/.config/dotnet-tools.json
new file mode 100644
index 0000000000..4faeb59e1c
--- /dev/null
+++ b/Nitrox.DocFx/.config/dotnet-tools.json
@@ -0,0 +1,12 @@
+{
+ "version": 1,
+ "isRoot": true,
+ "tools": {
+ "docfx": {
+ "version": "2.74.1",
+ "commands": [
+ "docfx"
+ ]
+ }
+ }
+}
\ No newline at end of file
diff --git a/Nitrox.DocFx/Nitrox.DocFx.proj b/Nitrox.DocFx/Nitrox.DocFx.proj
new file mode 100644
index 0000000000..f08b548a8b
--- /dev/null
+++ b/Nitrox.DocFx/Nitrox.DocFx.proj
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Nitrox.DocFx/docfx.json b/Nitrox.DocFx/docfx.json
new file mode 100644
index 0000000000..5b0a078a48
--- /dev/null
+++ b/Nitrox.DocFx/docfx.json
@@ -0,0 +1,52 @@
+{
+ "metadata": [
+ {
+ "src": [
+ {
+ "src": "../",
+ "files": [
+ "**/*.csproj"
+ ]
+ }
+ ],
+ "dest": "api",
+ "memberLayout": "separatePages",
+ "namespaceLayout": "nested"
+ }
+ ],
+ "build": {
+ "content": [
+ {
+ "files": [
+ "**/*.{md,yml}"
+ ],
+ "exclude": [
+ "_site/**"
+ ]
+ }
+ ],
+ "resource": [
+ {
+ "files": [
+ "images/**"
+ ]
+ }
+ ],
+ "output": "_site",
+ "template": [
+ "default",
+ "modern"
+ ],
+
+ "globalMetadata": {
+ "_appName": "Nitrox",
+ "_appTitle": "Nitrox",
+ "_gitContribute": {
+ "repo": "https://github.com/SubnauticaNitrox/Nitrox",
+ "branch": "master"
+ },
+ "_enableSearch": true,
+ "pdf": false
+ }
+ }
+}
diff --git a/Nitrox.DocFx/docs/getting-started.md b/Nitrox.DocFx/docs/getting-started.md
new file mode 100644
index 0000000000..8b3a7945ce
--- /dev/null
+++ b/Nitrox.DocFx/docs/getting-started.md
@@ -0,0 +1 @@
+# Getting Started
\ No newline at end of file
diff --git a/Nitrox.DocFx/docs/introduction.md b/Nitrox.DocFx/docs/introduction.md
new file mode 100644
index 0000000000..f6ecaa676c
--- /dev/null
+++ b/Nitrox.DocFx/docs/introduction.md
@@ -0,0 +1 @@
+# Introduction
\ No newline at end of file
diff --git a/Nitrox.DocFx/docs/toc.yml b/Nitrox.DocFx/docs/toc.yml
new file mode 100644
index 0000000000..d7e9ea8cbd
--- /dev/null
+++ b/Nitrox.DocFx/docs/toc.yml
@@ -0,0 +1,4 @@
+- name: Introduction
+ href: introduction.md
+- name: Getting Started
+ href: getting-started.md
\ No newline at end of file
diff --git a/Nitrox.DocFx/toc.yml b/Nitrox.DocFx/toc.yml
new file mode 100644
index 0000000000..061acc65fd
--- /dev/null
+++ b/Nitrox.DocFx/toc.yml
@@ -0,0 +1,4 @@
+- name: Docs
+ href: docs/
+- name: API
+ href: api/
\ No newline at end of file
diff --git a/Nitrox.Test/Nitrox.Test.csproj b/Nitrox.Test/Nitrox.Test.csproj
index 7e032ebeec..b067349e8c 100644
--- a/Nitrox.Test/Nitrox.Test.csproj
+++ b/Nitrox.Test/Nitrox.Test.csproj
@@ -4,6 +4,7 @@
net472
disable
false
+ Debug;Release;Linux
diff --git a/Nitrox.sln b/Nitrox.sln
index afe01605f9..73844200f0 100644
--- a/Nitrox.sln
+++ b/Nitrox.sln
@@ -30,57 +30,66 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Nitrox.Test", "Nitrox.Test\
EndProject
Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "Nitrox.Assets.Subnautica", "Nitrox.Assets.Subnautica\Nitrox.Assets.Subnautica.shproj", "{79E92B6D-5D25-4254-AC9F-FA9A1CD3CBC6}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nitrox.Analyzers", "Nitrox.Analyzers\Nitrox.Analyzers.csproj", "{EB99BD64-EF43-4B06-BBFB-EB5DFA96E55D}"
-EndProject
Global
- GlobalSection(SharedMSBuildProjectFiles) = preSolution
- Nitrox.Assets.Subnautica\Nitrox.Assets.Subnautica.projitems*{79e92b6d-5d25-4254-ac9f-fa9a1cd3cbc6}*SharedItemsImports = 13
- EndGlobalSection
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
+ Linux|Any CPU = Linux|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{CB7FAEFC-D9C0-40B8-A6D5-8B284683E79E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{CB7FAEFC-D9C0-40B8-A6D5-8B284683E79E}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {CB7FAEFC-D9C0-40B8-A6D5-8B284683E79E}.Linux|Any CPU.ActiveCfg = Linux|Any CPU
+ {CB7FAEFC-D9C0-40B8-A6D5-8B284683E79E}.Linux|Any CPU.Build.0 = Linux|Any CPU
{CB7FAEFC-D9C0-40B8-A6D5-8B284683E79E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{CB7FAEFC-D9C0-40B8-A6D5-8B284683E79E}.Release|Any CPU.Build.0 = Release|Any CPU
{EBEBC7DC-FAE3-4FBC-BDD3-38ED8FC072D9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{EBEBC7DC-FAE3-4FBC-BDD3-38ED8FC072D9}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {EBEBC7DC-FAE3-4FBC-BDD3-38ED8FC072D9}.Linux|Any CPU.ActiveCfg = Linux|Any CPU
+ {EBEBC7DC-FAE3-4FBC-BDD3-38ED8FC072D9}.Linux|Any CPU.Build.0 = Linux|Any CPU
{EBEBC7DC-FAE3-4FBC-BDD3-38ED8FC072D9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EBEBC7DC-FAE3-4FBC-BDD3-38ED8FC072D9}.Release|Any CPU.Build.0 = Release|Any CPU
{59EB8953-864F-4147-A210-7FC97E1A5294}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{59EB8953-864F-4147-A210-7FC97E1A5294}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {59EB8953-864F-4147-A210-7FC97E1A5294}.Linux|Any CPU.ActiveCfg = Linux|Any CPU
{59EB8953-864F-4147-A210-7FC97E1A5294}.Release|Any CPU.ActiveCfg = Release|Any CPU
{59EB8953-864F-4147-A210-7FC97E1A5294}.Release|Any CPU.Build.0 = Release|Any CPU
{39E377AD-2163-4428-952D-EBECD402C8F3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{39E377AD-2163-4428-952D-EBECD402C8F3}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {39E377AD-2163-4428-952D-EBECD402C8F3}.Linux|Any CPU.ActiveCfg = Linux|Any CPU
+ {39E377AD-2163-4428-952D-EBECD402C8F3}.Linux|Any CPU.Build.0 = Linux|Any CPU
{39E377AD-2163-4428-952D-EBECD402C8F3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{39E377AD-2163-4428-952D-EBECD402C8F3}.Release|Any CPU.Build.0 = Release|Any CPU
{47D774E0-750C-427B-8C38-F8F985114A2E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{47D774E0-750C-427B-8C38-F8F985114A2E}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {47D774E0-750C-427B-8C38-F8F985114A2E}.Linux|Any CPU.ActiveCfg = Linux|Any CPU
+ {47D774E0-750C-427B-8C38-F8F985114A2E}.Linux|Any CPU.Build.0 = Linux|Any CPU
{47D774E0-750C-427B-8C38-F8F985114A2E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{47D774E0-750C-427B-8C38-F8F985114A2E}.Release|Any CPU.Build.0 = Release|Any CPU
{5453E724-5A8B-46A4-850B-1F17FA2E938D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5453E724-5A8B-46A4-850B-1F17FA2E938D}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {5453E724-5A8B-46A4-850B-1F17FA2E938D}.Linux|Any CPU.ActiveCfg = Linux|Any CPU
+ {5453E724-5A8B-46A4-850B-1F17FA2E938D}.Linux|Any CPU.Build.0 = Linux|Any CPU
{5453E724-5A8B-46A4-850B-1F17FA2E938D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5453E724-5A8B-46A4-850B-1F17FA2E938D}.Release|Any CPU.Build.0 = Release|Any CPU
{77692FDB-F713-41F1-B2AB-9019457B8909}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{77692FDB-F713-41F1-B2AB-9019457B8909}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {77692FDB-F713-41F1-B2AB-9019457B8909}.Linux|Any CPU.ActiveCfg = Linux|Any CPU
+ {77692FDB-F713-41F1-B2AB-9019457B8909}.Linux|Any CPU.Build.0 = Linux|Any CPU
{77692FDB-F713-41F1-B2AB-9019457B8909}.Release|Any CPU.ActiveCfg = Release|Any CPU
{77692FDB-F713-41F1-B2AB-9019457B8909}.Release|Any CPU.Build.0 = Release|Any CPU
{0CD6846B-EDA6-4FFD-A540-2A46CC1074BF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0CD6846B-EDA6-4FFD-A540-2A46CC1074BF}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {0CD6846B-EDA6-4FFD-A540-2A46CC1074BF}.Linux|Any CPU.ActiveCfg = Linux|Any CPU
+ {0CD6846B-EDA6-4FFD-A540-2A46CC1074BF}.Linux|Any CPU.Build.0 = Linux|Any CPU
{0CD6846B-EDA6-4FFD-A540-2A46CC1074BF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0CD6846B-EDA6-4FFD-A540-2A46CC1074BF}.Release|Any CPU.Build.0 = Release|Any CPU
{E4D8C360-34E4-4BE6-909F-3791DD9169B5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E4D8C360-34E4-4BE6-909F-3791DD9169B5}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {E4D8C360-34E4-4BE6-909F-3791DD9169B5}.Linux|Any CPU.ActiveCfg = Linux|Any CPU
+ {E4D8C360-34E4-4BE6-909F-3791DD9169B5}.Linux|Any CPU.Build.0 = Linux|Any CPU
{E4D8C360-34E4-4BE6-909F-3791DD9169B5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E4D8C360-34E4-4BE6-909F-3791DD9169B5}.Release|Any CPU.Build.0 = Release|Any CPU
- {EB99BD64-EF43-4B06-BBFB-EB5DFA96E55D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {EB99BD64-EF43-4B06-BBFB-EB5DFA96E55D}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {EB99BD64-EF43-4B06-BBFB-EB5DFA96E55D}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {EB99BD64-EF43-4B06-BBFB-EB5DFA96E55D}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -88,4 +97,7 @@ Global
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {AC56EA37-FBBC-4D19-8796-29A42A2331A2}
EndGlobalSection
+ GlobalSection(SharedMSBuildProjectFiles) = preSolution
+ Nitrox.Assets.Subnautica\Nitrox.Assets.Subnautica.projitems*{79e92b6d-5d25-4254-ac9f-fa9a1cd3cbc6}*SharedItemsImports = 13
+ EndGlobalSection
EndGlobal
diff --git a/NitroxClient/NitroxClient.csproj b/NitroxClient/NitroxClient.csproj
index 178a2f6015..02e9a48903 100644
--- a/NitroxClient/NitroxClient.csproj
+++ b/NitroxClient/NitroxClient.csproj
@@ -1,8 +1,9 @@
-
+
net472
disable
+ Debug;Release;Linux
diff --git a/NitroxLauncher/NitroxLauncher.csproj b/NitroxLauncher/NitroxLauncher.csproj
index c9358bbb6c..1a637e1ed9 100644
--- a/NitroxLauncher/NitroxLauncher.csproj
+++ b/NitroxLauncher/NitroxLauncher.csproj
@@ -12,6 +12,7 @@
Nitrox
Nitrox
https://github.com/SubnauticaNitrox/Nitrox
+ Debug;Release;Linux
diff --git a/NitroxModel-Subnautica/NitroxModel-Subnautica.csproj b/NitroxModel-Subnautica/NitroxModel-Subnautica.csproj
index 8674facb17..cba3a303de 100644
--- a/NitroxModel-Subnautica/NitroxModel-Subnautica.csproj
+++ b/NitroxModel-Subnautica/NitroxModel-Subnautica.csproj
@@ -4,6 +4,7 @@
net472
NitroxModel_Subnautica
disable
+ Debug;Release;Linux
diff --git a/NitroxModel/NitroxModel.csproj b/NitroxModel/NitroxModel.csproj
index 5f2d6c432d..727a89e884 100644
--- a/NitroxModel/NitroxModel.csproj
+++ b/NitroxModel/NitroxModel.csproj
@@ -1,8 +1,9 @@
-
+
net472
disable
+ Debug;Release;Linux
diff --git a/NitroxPatcher/NitroxPatcher.csproj b/NitroxPatcher/NitroxPatcher.csproj
index 26cdeee752..50000c0d9e 100644
--- a/NitroxPatcher/NitroxPatcher.csproj
+++ b/NitroxPatcher/NitroxPatcher.csproj
@@ -1,8 +1,9 @@
-
+
net472
disable
+ Debug;Release;Linux
diff --git a/NitroxServer-Subnautica/NitroxServer-Subnautica.csproj b/NitroxServer-Subnautica/NitroxServer-Subnautica.csproj
index b7268ed09e..e8fa2a283b 100644
--- a/NitroxServer-Subnautica/NitroxServer-Subnautica.csproj
+++ b/NitroxServer-Subnautica/NitroxServer-Subnautica.csproj
@@ -1,4 +1,4 @@
-
+
net472
@@ -6,6 +6,7 @@
NitroxServer_Subnautica
disable
true
+ Debug;Release;Linux
diff --git a/NitroxServer/NitroxServer.csproj b/NitroxServer/NitroxServer.csproj
index 7d7ceb4f6c..e65dc521b1 100644
--- a/NitroxServer/NitroxServer.csproj
+++ b/NitroxServer/NitroxServer.csproj
@@ -1,8 +1,9 @@
-
+
net472
disable
+ Debug;Release;Linux
diff --git a/index.md b/index.md
new file mode 100644
index 0000000000..f9859f8407
--- /dev/null
+++ b/index.md
@@ -0,0 +1,11 @@
+---
+_layout: landing
+---
+
+# This is the **HOMEPAGE**.
+
+Refer to [Markdown](http://daringfireball.net/projects/markdown/) for how to write markdown files.
+
+## Quick Start Notes:
+
+1. Add images to the *images* folder if the file is referencing an image.
\ No newline at end of file