From ced77b1e9b5ffe0eb66de3d730d8583d12366c91 Mon Sep 17 00:00:00 2001
From: Ignacio Etcheverry <ignalfonsore@gmail.com>
Date: Mon, 20 Jul 2020 15:48:12 +0200
Subject: [PATCH] C#: Switch games to MSBuild Sdks and .NET Standard

Godot.NET.Sdk
-------------

Godot uses its own custom MSBuild Sdk for game
projects. This new Sdk adds its own functionality
on top of 'Microsoft.NET.Sdk'.

The new Sdk is resolved from the NuGet package.

All the default boilerplate was moved from game
projects to the Sdk. The default csproj for
game project can now be as simple as:

```
<Project Sdk="Godot.NET.Sdk/4.0.0-dev2">
  <PropertyGroup>
    <TargetFramework>netstandard2.1</TargetFramework>
  </PropertyGroup>
</Project>
```

Source files are included by automatically so
Godot no longer needs to keep the csproj in sync
when creating new source files.

Define constants
----------------

Godot defines a list of constants for conditional
compilation. When exporting games, this list also
included engine 'features' and platform 'bits'.
There were a few problems with that:

- The 'features' constants were only defined when
  exporting games. Not when building the game for
  running in the editor player.
- If the project was built externally by an IDE,
  the constants wouldn't be defined at all.

The new Sdk assigns default values to these
constants when not built from the Godot editor,
i.e.: when built from an IDE or from the command
line. The default define constants are determined
from the system MSBuild is running on.

However, it's not possible for MSBuild to
determine the set of supported engine features.
It's also not possible to determine if a project
is being built to run on a 32-bit or 64-bit
Godot executable.

As such the 'features' and 'bits' constants had
to be removed.
The benefit of checking those at compile time
was questionable, and they can still be checked
at runtime.

The new list of define constants includes:

- GODOT
- GODOT_<PLATFORM>
  Defaults to the platform MSBuild is running on.
- GODOT_<PC/MOBILE/WEB>
- TOOLS
  When building with the 'Debug' configuration
  (editor and editor player).
- GODOT_REAL_T_IS_DOUBLE
  Not defined by default unless $(GodotRealTIsDouble)
  is overriden to be 'true'.

.NET Standard
-------------

The target framework of game projects was changed
to 'netstandard2.1'.
---
 .editorconfig                                 |   4 +
 modules/mono/csharp_script.cpp                |  11 +-
 .../editor/Godot.NET.Sdk/Godot.NET.Sdk.sln    |  16 +
 .../Godot.NET.Sdk/Godot.NET.Sdk.csproj        |  35 ++
 .../Godot.NET.Sdk/Godot.NET.Sdk.nuspec        |  22 ++
 .../Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.props | 112 ++++++
 .../Godot.NET.Sdk/Sdk/Sdk.targets             |  17 +
 .../GodotTools/GodotTools.Core/FileUtils.cs   |   5 +-
 .../IdentifierUtils.cs                        |  56 +--
 .../ProjectExtensions.cs                      | 118 ------
 .../ProjectGenerator.cs                       | 171 ++-------
 .../GodotTools.ProjectEditor/ProjectUtils.cs  | 341 ++----------------
 .../GodotTools/GodotTools/BottomPanel.cs      |  84 ++---
 .../GodotTools/GodotTools/BuildManager.cs     |  30 +-
 .../GodotTools/GodotTools/CsProjOperations.cs | 138 ++++---
 .../GodotTools/Export/ExportPlugin.cs         |  24 +-
 .../GodotTools/GodotTools/GodotSharpEditor.cs |  98 ++---
 .../GodotTools/Internals/ScriptClassParser.cs |   4 +
 modules/mono/editor/bindings_generator.cpp    |   1 -
 modules/mono/editor/csharp_project.cpp        |  69 ----
 modules/mono/editor/csharp_project.h          |  42 ---
 .../GodotSharp/GodotSharp/GodotSharp.csproj   |  35 +-
 .../GodotSharp/Properties/AssemblyInfo.cs     |  24 --
 .../GodotSharpEditor/GodotSharpEditor.csproj  |  48 +--
 .../Properties/AssemblyInfo.cs                |  25 --
 25 files changed, 472 insertions(+), 1058 deletions(-)
 create mode 100644 modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk.sln
 create mode 100644 modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Godot.NET.Sdk.csproj
 create mode 100644 modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Godot.NET.Sdk.nuspec
 create mode 100644 modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.props
 create mode 100644 modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.targets
 delete mode 100644 modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectExtensions.cs
 delete mode 100644 modules/mono/editor/csharp_project.cpp
 delete mode 100644 modules/mono/editor/csharp_project.h
 delete mode 100644 modules/mono/glue/GodotSharp/GodotSharpEditor/Properties/AssemblyInfo.cs

diff --git a/.editorconfig b/.editorconfig
index f335026e1e3..49517a5104d 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -20,3 +20,7 @@ indent_size = 4
 [.travis.yml]
 indent_style = space
 indent_size = 2
+
+[*.{csproj,props,targets,nuspec}]
+indent_style = space
+indent_size = 2
diff --git a/modules/mono/csharp_script.cpp b/modules/mono/csharp_script.cpp
index 7d3ae315883..6a7e4a80b79 100644
--- a/modules/mono/csharp_script.cpp
+++ b/modules/mono/csharp_script.cpp
@@ -44,7 +44,6 @@
 
 #ifdef TOOLS_ENABLED
 #include "editor/bindings_generator.h"
-#include "editor/csharp_project.h"
 #include "editor/editor_node.h"
 #include "editor/node_dock.h"
 #endif
@@ -3759,13 +3758,9 @@ Error ResourceFormatSaverCSharpScript::save(const String &p_path, const RES &p_r
 
 #ifdef TOOLS_ENABLED
 	if (!FileAccess::exists(p_path)) {
-		// The file does not yet exists, let's assume the user just created this script
-
-		if (_create_project_solution_if_needed()) {
-			CSharpProject::add_item(GodotSharpDirs::get_project_csproj_path(),
-					"Compile",
-					ProjectSettings::get_singleton()->globalize_path(p_path));
-		} else {
+		// The file does not yet exist, let's assume the user just created this script. In such
+		// cases we need to check whether the solution and csproj were already created or not.
+		if (!_create_project_solution_if_needed()) {
 			ERR_PRINT("C# project could not be created; cannot add file: '" + p_path + "'.");
 		}
 	}
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk.sln b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk.sln
new file mode 100644
index 00000000000..56c0cb77037
--- /dev/null
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk.sln
@@ -0,0 +1,16 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Godot.NET.Sdk", "Godot.NET.Sdk\Godot.NET.Sdk.csproj", "{31B00BFA-DEA1-42FA-A472-9E54A92A8A5F}"
+EndProject
+Global
+	GlobalSection(SolutionConfigurationPlatforms) = preSolution
+		Debug|Any CPU = Debug|Any CPU
+		Release|Any CPU = Release|Any CPU
+	EndGlobalSection
+	GlobalSection(ProjectConfigurationPlatforms) = postSolution
+		{31B00BFA-DEA1-42FA-A472-9E54A92A8A5F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{31B00BFA-DEA1-42FA-A472-9E54A92A8A5F}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{31B00BFA-DEA1-42FA-A472-9E54A92A8A5F}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{31B00BFA-DEA1-42FA-A472-9E54A92A8A5F}.Release|Any CPU.Build.0 = Release|Any CPU
+	EndGlobalSection
+EndGlobal
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Godot.NET.Sdk.csproj b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Godot.NET.Sdk.csproj
new file mode 100644
index 00000000000..86a0a4393e1
--- /dev/null
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Godot.NET.Sdk.csproj
@@ -0,0 +1,35 @@
+<Project Sdk="Microsoft.Build.NoTargets/2.0.1">
+  <PropertyGroup>
+    <TargetFramework>netstandard2.0</TargetFramework>
+
+    <Description>MSBuild .NET Sdk for Godot projects.</Description>
+    <Authors>Godot Engine contributors</Authors>
+
+    <PackageId>Godot.NET.Sdk</PackageId>
+    <Version>4.0.0</Version>
+    <PackageVersion>4.0.0-dev2</PackageVersion>
+    <PackageProjectUrl>https://github.com/godotengine/godot/tree/master/modules/mono/editor/Godot.NET.Sdk</PackageProjectUrl>
+    <PackageType>MSBuildSdk</PackageType>
+    <PackageTags>MSBuildSdk</PackageTags>
+    <GeneratePackageOnBuild>true</GeneratePackageOnBuild>
+  </PropertyGroup>
+
+  <PropertyGroup>
+    <NuspecFile>Godot.NET.Sdk.nuspec</NuspecFile>
+    <GenerateNuspecDependsOn>$(GenerateNuspecDependsOn);SetNuSpecProperties</GenerateNuspecDependsOn>
+  </PropertyGroup>
+
+  <Target Name="SetNuSpecProperties" Condition=" Exists('$(NuspecFile)') ">
+    <PropertyGroup>
+      <NuspecProperties>
+        id=$(PackageId);
+        description=$(Description);
+        authors=$(Authors);
+        version=$(PackageVersion);
+        packagetype=$(PackageType);
+        tags=$(PackageTags);
+        projecturl=$(PackageProjectUrl)
+      </NuspecProperties>
+    </PropertyGroup>
+  </Target>
+</Project>
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Godot.NET.Sdk.nuspec b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Godot.NET.Sdk.nuspec
new file mode 100644
index 00000000000..5b5cefe80e5
--- /dev/null
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Godot.NET.Sdk.nuspec
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<package xmlns="http://schemas.microsoft.com/packaging/2011/10/nuspec.xsd">
+  <metadata>
+    <id>$id$</id>
+    <version>$version$</version>
+    <description>$description$</description>
+    <authors>$authors$</authors>
+    <owners>$authors$</owners>
+    <projectUrl>$projecturl$</projectUrl>
+    <requireLicenseAcceptance>false</requireLicenseAcceptance>
+    <license type="expression">MIT</license>
+    <licenseUrl>https://licenses.nuget.org/MIT</licenseUrl>
+    <tags>$tags$</tags>
+    <packageTypes>
+      <packageType name="$packagetype$" />
+    </packageTypes>
+    <repository url="$projecturl$" />
+  </metadata>
+  <files>
+    <file src="Sdk\**" target="Sdk" />\
+  </files>
+</package>
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.props b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.props
new file mode 100644
index 00000000000..dfc59e6ccb1
--- /dev/null
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.props
@@ -0,0 +1,112 @@
+<Project>
+  <PropertyGroup>
+    <!-- Determines if we should import Microsoft.NET.Sdk, if it wasn't already imported. -->
+    <GodotSdkImportsMicrosoftNetSdk Condition=" '$(UsingMicrosoftNETSdk)' != 'true' ">true</GodotSdkImportsMicrosoftNetSdk>
+
+    <GodotProjectTypeGuid>{8F3E2DF0-C35C-4265-82FC-BEA011F4A7ED}</GodotProjectTypeGuid>
+  </PropertyGroup>
+
+  <PropertyGroup>
+    <Configurations>Debug;ExportDebug;ExportRelease</Configurations>
+    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+
+    <GodotProjectDir Condition=" '$(SolutionDir)' != '' ">$(SolutionDir)</GodotProjectDir>
+    <GodotProjectDir Condition=" '$(SolutionDir)' == '' ">$(MSBuildProjectDirectory)</GodotProjectDir>
+    <GodotProjectDir>$([MSBuild]::EnsureTrailingSlash('$(GodotProjectDir)'))</GodotProjectDir>
+
+    <!-- Custom output paths for Godot projects. In brief, 'bin\' and 'obj\' are moved to '$(GodotProjectDir)\.mono\temp\'. -->
+    <BaseOutputPath>$(GodotProjectDir).mono\temp\bin\</BaseOutputPath>
+    <OutputPath>$(GodotProjectDir).mono\temp\bin\$(Configuration)\</OutputPath>
+    <!--
+    Use custom IntermediateOutputPath and BaseIntermediateOutputPath only if it wasn't already set.
+    Otherwise the old values may have already been changed by MSBuild which can cause problems with NuGet.
+    -->
+    <IntermediateOutputPath Condition=" '$(IntermediateOutputPath)' == '' ">$(GodotProjectDir).mono\temp\obj\$(Configuration)\</IntermediateOutputPath>
+    <BaseIntermediateOutputPath Condition=" '$(BaseIntermediateOutputPath)' == '' ">$(GodotProjectDir).mono\temp\obj\</BaseIntermediateOutputPath>
+
+    <!-- Do not append the target framework name to the output path. -->
+    <AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
+  </PropertyGroup>
+
+  <Import Sdk="Microsoft.NET.Sdk" Project="Sdk.props" Condition=" '$(GodotSdkImportsMicrosoftNetSdk)' == 'true' " />
+
+  <PropertyGroup>
+    <EnableDefaultNoneItems>false</EnableDefaultNoneItems>
+  </PropertyGroup>
+
+  <!--
+  The Microsoft.NET.Sdk only understands of the Debug and Release configurations.
+  We need to set the following properties manually for ExportDebug and ExportRelease.
+  -->
+  <PropertyGroup Condition=" '$(Configuration)' == 'Debug' or '$(Configuration)' == 'ExportDebug' ">
+    <DebugSymbols Condition=" '$(DebugSymbols)' == '' ">true</DebugSymbols>
+    <Optimize Condition=" '$(Optimize)' == '' ">false</Optimize>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)' == 'ExportRelease' ">
+    <Optimize Condition=" '$(Optimize)' == '' ">true</Optimize>
+  </PropertyGroup>
+
+  <PropertyGroup>
+    <GodotApiConfiguration Condition=" '$(Configuration)' != 'ExportRelease' ">Debug</GodotApiConfiguration>
+    <GodotApiConfiguration Condition=" '$(Configuration)' == 'ExportRelease' ">Release</GodotApiConfiguration>
+  </PropertyGroup>
+
+  <!-- Auto-detect the target Godot platform if it was not specified. -->
+  <PropertyGroup Condition=" '$(GodotTargetPlatform)' == '' ">
+    <GodotTargetPlatform Condition=" '$([MSBuild]::IsOsPlatform(Linux))' ">linuxbsd</GodotTargetPlatform>
+    <GodotTargetPlatform Condition=" '$([MSBuild]::IsOsPlatform(FreeBSD))' ">linuxbsd</GodotTargetPlatform>
+    <GodotTargetPlatform Condition=" '$([MSBuild]::IsOsPlatform(OSX))' ">osx</GodotTargetPlatform>
+    <GodotTargetPlatform Condition=" '$([MSBuild]::IsOsPlatform(Windows))' ">windows</GodotTargetPlatform>
+  </PropertyGroup>
+
+  <PropertyGroup>
+    <GodotRealTIsDouble Condition=" '$(GodotRealTIsDouble)' == '' ">false</GodotRealTIsDouble>
+  </PropertyGroup>
+
+  <!-- Godot DefineConstants. -->
+  <PropertyGroup>
+    <!-- Define constant to identify Godot builds. -->
+    <GodotDefineConstants>GODOT</GodotDefineConstants>
+
+    <!--
+    Define constant to determine the target Godot platform. This includes the
+    recognized platform names and the platform category (PC, MOBILE or WEB).
+    -->
+    <GodotPlatformConstants Condition=" '$(GodotTargetPlatform)' == 'windows' ">GODOT_WINDOWS;GODOT_PC</GodotPlatformConstants>
+    <GodotPlatformConstants Condition=" '$(GodotTargetPlatform)' == 'linuxbsd' ">GODOT_LINUXBSD;GODOT_PC</GodotPlatformConstants>
+    <GodotPlatformConstants Condition=" '$(GodotTargetPlatform)' == 'osx' ">GODOT_OSX;GODOT_MACOS;GODOT_PC</GodotPlatformConstants>
+    <GodotPlatformConstants Condition=" '$(GodotTargetPlatform)' == 'server' ">GODOT_SERVER;GODOT_PC</GodotPlatformConstants>
+    <GodotPlatformConstants Condition=" '$(GodotTargetPlatform)' == 'uwp' ">GODOT_UWP;GODOT_PC</GodotPlatformConstants>
+    <GodotPlatformConstants Condition=" '$(GodotTargetPlatform)' == 'haiku' ">GODOT_HAIKU;GODOT_PC</GodotPlatformConstants>
+    <GodotPlatformConstants Condition=" '$(GodotTargetPlatform)' == 'android' ">GODOT_ANDROID;GODOT_MOBILE</GodotPlatformConstants>
+    <GodotPlatformConstants Condition=" '$(GodotTargetPlatform)' == 'iphone' ">GODOT_IPHONE;GODOT_IOS;GODOT_MOBILE</GodotPlatformConstants>
+    <GodotPlatformConstants Condition=" '$(GodotTargetPlatform)' == 'javascript' ">GODOT_JAVASCRIPT;GODOT_HTML5;GODOT_WASM;GODOT_WEB</GodotPlatformConstants>
+
+    <GodotDefineConstants>$(GodotDefineConstants);$(GodotPlatformConstants)</GodotDefineConstants>
+  </PropertyGroup>
+
+  <PropertyGroup>
+    <!-- ExportDebug also defines DEBUG like Debug does. -->
+    <DefineConstants Condition=" '$(Configuration)' == 'ExportDebug' ">$(DefineConstants);DEBUG</DefineConstants>
+    <!-- Debug defines TOOLS to differenciate between Debug and ExportDebug configurations. -->
+    <DefineConstants Condition=" '$(Configuration)' == 'Debug' ">$(DefineConstants);TOOLS</DefineConstants>
+
+    <DefineConstants>$(GodotDefineConstants);$(DefineConstants)</DefineConstants>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <!--
+    TODO:
+    We should consider a nuget package for reference assemblies. This is difficult because the
+    Godot scripting API is continuaslly breaking backwards compatibility even in patch releases.
+    -->
+    <Reference Include="GodotSharp">
+      <Private>false</Private>
+      <HintPath>$(GodotProjectDir).mono\assemblies\$(GodotApiConfiguration)\GodotSharp.dll</HintPath>
+    </Reference>
+    <Reference Include="GodotSharpEditor" Condition=" '$(Configuration)' == 'Debug' ">
+      <Private>false</Private>
+      <HintPath>$(GodotProjectDir).mono\assemblies\$(GodotApiConfiguration)\GodotSharpEditor.dll</HintPath>
+    </Reference>
+  </ItemGroup>
+</Project>
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.targets b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.targets
new file mode 100644
index 00000000000..f5afd75505a
--- /dev/null
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.targets
@@ -0,0 +1,17 @@
+<Project>
+  <Import Sdk="Microsoft.NET.Sdk" Project="Sdk.targets" Condition=" '$(GodotSdkImportsMicrosoftNetSdk)' == 'true' " />
+
+  <PropertyGroup>
+    <EnableGodotProjectTypeGuid Condition=" '$(EnableGodotProjectTypeGuid)' == '' ">true</EnableGodotProjectTypeGuid>
+    <ProjectTypeGuids Condition=" '$(EnableGodotProjectTypeGuid)' == 'true' ">$(GodotProjectTypeGuid);$(DefaultProjectTypeGuid)</ProjectTypeGuids>
+  </PropertyGroup>
+
+  <PropertyGroup>
+    <!--
+    Define constant to determine whether the real_t type in Godot is double precision or not.
+    By default this is false, like the official Godot builds. If someone is using a custom
+    Godot build where real_t is double, they can override the GodotRealTIsDouble property.
+    -->
+    <DefineConstants Condition=" '$(GodotRealTIsDouble)' == 'true' ">GODOT_REAL_T_IS_DOUBLE;$(DefineConstants)</DefineConstants>
+  </PropertyGroup>
+</Project>
diff --git a/modules/mono/editor/GodotTools/GodotTools.Core/FileUtils.cs b/modules/mono/editor/GodotTools/GodotTools.Core/FileUtils.cs
index 85760a3705e..e1ccf0454a8 100644
--- a/modules/mono/editor/GodotTools/GodotTools.Core/FileUtils.cs
+++ b/modules/mono/editor/GodotTools/GodotTools.Core/FileUtils.cs
@@ -19,7 +19,10 @@ namespace GodotTools.Core
             }
 
             if (attempt > maxAttempts + 1)
-                return;
+            {
+                // Overwrite the oldest one
+                backupPath = backupPathBase;
+            }
 
             File.Copy(filePath, backupPath, overwrite: true);
         }
diff --git a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/IdentifierUtils.cs b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/IdentifierUtils.cs
index f93eb9a1faf..ed77076df3d 100644
--- a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/IdentifierUtils.cs
+++ b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/IdentifierUtils.cs
@@ -22,6 +22,37 @@ namespace GodotTools.ProjectEditor
             return string.Join(".", identifiers);
         }
 
+        /// <summary>
+        /// Skips invalid identifier characters including decimal digit numbers at the start of the identifier.
+        /// </summary>
+        private static void SkipInvalidCharacters(string source, int startIndex, StringBuilder outputBuilder)
+        {
+            for (int i = startIndex; i < source.Length; i++)
+            {
+                char @char = source[i];
+
+                switch (char.GetUnicodeCategory(@char))
+                {
+                    case UnicodeCategory.UppercaseLetter:
+                    case UnicodeCategory.LowercaseLetter:
+                    case UnicodeCategory.TitlecaseLetter:
+                    case UnicodeCategory.ModifierLetter:
+                    case UnicodeCategory.LetterNumber:
+                    case UnicodeCategory.OtherLetter:
+                        outputBuilder.Append(@char);
+                        break;
+                    case UnicodeCategory.NonSpacingMark:
+                    case UnicodeCategory.SpacingCombiningMark:
+                    case UnicodeCategory.ConnectorPunctuation:
+                    case UnicodeCategory.DecimalDigitNumber:
+                        // Identifiers may start with underscore
+                        if (outputBuilder.Length > startIndex || @char == '_')
+                            outputBuilder.Append(@char);
+                        break;
+                }
+            }
+        }
+
         public static string SanitizeIdentifier(string identifier, bool allowEmpty)
         {
             if (string.IsNullOrEmpty(identifier))
@@ -44,30 +75,7 @@ namespace GodotTools.ProjectEditor
                 startIndex += 1;
             }
 
-            for (int i = startIndex; i < identifier.Length; i++)
-            {
-                char @char = identifier[i];
-
-                switch (Char.GetUnicodeCategory(@char))
-                {
-                    case UnicodeCategory.UppercaseLetter:
-                    case UnicodeCategory.LowercaseLetter:
-                    case UnicodeCategory.TitlecaseLetter:
-                    case UnicodeCategory.ModifierLetter:
-                    case UnicodeCategory.LetterNumber:
-                    case UnicodeCategory.OtherLetter:
-                        identifierBuilder.Append(@char);
-                        break;
-                    case UnicodeCategory.NonSpacingMark:
-                    case UnicodeCategory.SpacingCombiningMark:
-                    case UnicodeCategory.ConnectorPunctuation:
-                    case UnicodeCategory.DecimalDigitNumber:
-                        // Identifiers may start with underscore
-                        if (identifierBuilder.Length > startIndex || @char == '_')
-                            identifierBuilder.Append(@char);
-                        break;
-                }
-            }
+            SkipInvalidCharacters(identifier, startIndex, identifierBuilder);
 
             if (identifierBuilder.Length == startIndex)
             {
diff --git a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectExtensions.cs b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectExtensions.cs
deleted file mode 100644
index 704f2ec1943..00000000000
--- a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectExtensions.cs
+++ /dev/null
@@ -1,118 +0,0 @@
-using GodotTools.Core;
-using System;
-using System.Collections.Generic;
-using System.IO;
-using Microsoft.Build.Construction;
-using Microsoft.Build.Globbing;
-
-namespace GodotTools.ProjectEditor
-{
-    public static class ProjectExtensions
-    {
-        public static ProjectItemElement FindItemOrNull(this ProjectRootElement root, string itemType, string include, bool noCondition = false)
-        {
-            string normalizedInclude = include.NormalizePath();
-
-            foreach (var itemGroup in root.ItemGroups)
-            {
-                if (noCondition && itemGroup.Condition.Length != 0)
-                    continue;
-
-                foreach (var item in itemGroup.Items)
-                {
-                    if (item.ItemType != itemType)
-                        continue;
-
-                    //var glob = Glob.Parse(item.Include.NormalizePath(), globOptions);
-                    var glob = MSBuildGlob.Parse(item.Include.NormalizePath());
-
-                    if (glob.IsMatch(normalizedInclude))
-                        return item;
-                }
-            }
-
-            return null;
-        }
-        public static ProjectItemElement FindItemOrNullAbs(this ProjectRootElement root, string itemType, string include, bool noCondition = false)
-        {
-            string normalizedInclude = Path.GetFullPath(include).NormalizePath();
-
-            foreach (var itemGroup in root.ItemGroups)
-            {
-                if (noCondition && itemGroup.Condition.Length != 0)
-                    continue;
-
-                foreach (var item in itemGroup.Items)
-                {
-                    if (item.ItemType != itemType)
-                        continue;
-
-                    var glob = MSBuildGlob.Parse(Path.GetFullPath(item.Include).NormalizePath());
-
-                    if (glob.IsMatch(normalizedInclude))
-                        return item;
-                }
-            }
-
-            return null;
-        }
-
-        public static IEnumerable<ProjectItemElement> FindAllItemsInFolder(this ProjectRootElement root, string itemType, string folder)
-        {
-            string absFolderNormalizedWithSep = Path.GetFullPath(folder).NormalizePath() + Path.DirectorySeparatorChar;
-
-            foreach (var itemGroup in root.ItemGroups)
-            {
-                foreach (var item in itemGroup.Items)
-                {
-                    if (item.ItemType != itemType)
-                        continue;
-
-                    string absPathNormalized = Path.GetFullPath(item.Include).NormalizePath();
-
-                    if (absPathNormalized.StartsWith(absFolderNormalizedWithSep))
-                        yield return item;
-                }
-            }
-        }
-
-        public static bool HasItem(this ProjectRootElement root, string itemType, string include, bool noCondition = false)
-        {
-            return root.FindItemOrNull(itemType, include, noCondition) != null;
-        }
-
-        public static bool AddItemChecked(this ProjectRootElement root, string itemType, string include)
-        {
-            if (!root.HasItem(itemType, include, noCondition: true))
-            {
-                root.AddItem(itemType, include);
-                return true;
-            }
-
-            return false;
-        }
-
-        public static bool RemoveItemChecked(this ProjectRootElement root, string itemType, string include)
-        {
-            var item = root.FindItemOrNullAbs(itemType, include);
-            if (item != null)
-            {
-                item.Parent.RemoveChild(item);
-                return true;
-            }
-
-            return false;
-        }
-
-        public static Guid GetGuid(this ProjectRootElement root)
-        {
-            foreach (var property in root.Properties)
-            {
-                if (property.Name == "ProjectGuid")
-                    return Guid.Parse(property.Value);
-            }
-
-            return Guid.Empty;
-        }
-    }
-}
diff --git a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectGenerator.cs b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectGenerator.cs
index 679d5bb444f..5541876f9e2 100644
--- a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectGenerator.cs
+++ b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectGenerator.cs
@@ -1,174 +1,49 @@
-using GodotTools.Core;
 using System;
-using System.Collections.Generic;
 using System.IO;
-using System.Reflection;
 using Microsoft.Build.Construction;
+using Microsoft.Build.Evaluation;
 
 namespace GodotTools.ProjectEditor
 {
     public static class ProjectGenerator
     {
-        private const string CoreApiProjectName = "GodotSharp";
-        private const string EditorApiProjectName = "GodotSharpEditor";
+        public const string GodotSdkVersionToUse = "4.0.0-dev2";
 
-        public const string CSharpProjectTypeGuid = "{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}";
-        public const string GodotProjectTypeGuid = "{8F3E2DF0-C35C-4265-82FC-BEA011F4A7ED}";
+        public static string GodotSdkAttrValue => $"Godot.NET.Sdk/{GodotSdkVersionToUse}";
 
-        public static readonly string GodotDefaultProjectTypeGuids = $"{GodotProjectTypeGuid};{CSharpProjectTypeGuid}";
-
-        public static string GenGameProject(string dir, string name, IEnumerable<string> compileItems)
+        public static ProjectRootElement GenGameProject(string name)
         {
-            string path = Path.Combine(dir, name + ".csproj");
+            if (name.Length == 0)
+                throw new ArgumentException("Project name is empty", nameof(name));
 
-            ProjectPropertyGroupElement mainGroup;
-            var root = CreateLibraryProject(name, "Debug", out mainGroup);
+            var root = ProjectRootElement.Create(NewProjectFileOptions.None);
 
-            mainGroup.SetProperty("ProjectTypeGuids", GodotDefaultProjectTypeGuids);
-            mainGroup.SetProperty("OutputPath", Path.Combine(".mono", "temp", "bin", "$(Configuration)"));
-            mainGroup.SetProperty("BaseIntermediateOutputPath", Path.Combine(".mono", "temp", "obj"));
-            mainGroup.SetProperty("IntermediateOutputPath", Path.Combine("$(BaseIntermediateOutputPath)", "$(Configuration)"));
-            mainGroup.SetProperty("ApiConfiguration", "Debug").Condition = " '$(Configuration)' != 'ExportRelease' ";
-            mainGroup.SetProperty("ApiConfiguration", "Release").Condition = " '$(Configuration)' == 'ExportRelease' ";
+            root.Sdk = GodotSdkAttrValue;
 
-            var debugGroup = root.AddPropertyGroup();
-            debugGroup.Condition = " '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ";
-            debugGroup.AddProperty("DebugSymbols", "true");
-            debugGroup.AddProperty("DebugType", "portable");
-            debugGroup.AddProperty("Optimize", "false");
-            debugGroup.AddProperty("DefineConstants", "$(GodotDefineConstants);GODOT;DEBUG;TOOLS;");
-            debugGroup.AddProperty("ErrorReport", "prompt");
-            debugGroup.AddProperty("WarningLevel", "4");
-            debugGroup.AddProperty("ConsolePause", "false");
+            var mainGroup = root.AddPropertyGroup();
+            mainGroup.AddProperty("TargetFramework", "netstandard2.1");
 
-            var coreApiRef = root.AddItem("Reference", CoreApiProjectName);
-            coreApiRef.AddMetadata("HintPath", Path.Combine("$(ProjectDir)", ".mono", "assemblies", "$(ApiConfiguration)", CoreApiProjectName + ".dll"));
-            coreApiRef.AddMetadata("Private", "False");
+            string sanitizedName = IdentifierUtils.SanitizeQualifiedIdentifier(name, allowEmptyIdentifiers: true);
 
-            var editorApiRef = root.AddItem("Reference", EditorApiProjectName);
-            editorApiRef.Condition = " '$(Configuration)' == 'Debug' ";
-            editorApiRef.AddMetadata("HintPath", Path.Combine("$(ProjectDir)", ".mono", "assemblies", "$(ApiConfiguration)", EditorApiProjectName + ".dll"));
-            editorApiRef.AddMetadata("Private", "False");
-
-            GenAssemblyInfoFile(root, dir, name);
-
-            foreach (var item in compileItems)
-            {
-                root.AddItem("Compile", item.RelativeToPath(dir).Replace("/", "\\"));
-            }
-
-            root.Save(path);
-
-            return root.GetGuid().ToString().ToUpper();
-        }
-
-        private static void GenAssemblyInfoFile(ProjectRootElement root, string dir, string name, string[] assemblyLines = null, string[] usingDirectives = null)
-        {
-            string propertiesDir = Path.Combine(dir, "Properties");
-            if (!Directory.Exists(propertiesDir))
-                Directory.CreateDirectory(propertiesDir);
-
-            string usingDirectivesText = string.Empty;
-
-            if (usingDirectives != null)
-            {
-                foreach (var usingDirective in usingDirectives)
-                    usingDirectivesText += "\nusing " + usingDirective + ";";
-            }
-
-            string assemblyLinesText = string.Empty;
-
-            if (assemblyLines != null)
-                assemblyLinesText += string.Join("\n", assemblyLines) + "\n";
-
-            string content = string.Format(AssemblyInfoTemplate, usingDirectivesText, name, assemblyLinesText);
-
-            string assemblyInfoFile = Path.Combine(propertiesDir, "AssemblyInfo.cs");
-
-            File.WriteAllText(assemblyInfoFile, content);
-
-            root.AddItem("Compile", assemblyInfoFile.RelativeToPath(dir).Replace("/", "\\"));
-        }
-
-        public static ProjectRootElement CreateLibraryProject(string name, string defaultConfig, out ProjectPropertyGroupElement mainGroup)
-        {
-            if (string.IsNullOrEmpty(name))
-                throw new ArgumentException($"{nameof(name)} cannot be empty", nameof(name));
-
-            var root = ProjectRootElement.Create();
-            root.DefaultTargets = "Build";
-
-            mainGroup = root.AddPropertyGroup();
-            mainGroup.AddProperty("Configuration", defaultConfig).Condition = " '$(Configuration)' == '' ";
-            mainGroup.AddProperty("Platform", "AnyCPU").Condition = " '$(Platform)' == '' ";
-            mainGroup.AddProperty("ProjectGuid", "{" + Guid.NewGuid().ToString().ToUpper() + "}");
-            mainGroup.AddProperty("OutputType", "Library");
-            mainGroup.AddProperty("OutputPath", Path.Combine("bin", "$(Configuration)"));
-            mainGroup.AddProperty("RootNamespace", IdentifierUtils.SanitizeQualifiedIdentifier(name, allowEmptyIdentifiers: true));
-            mainGroup.AddProperty("AssemblyName", name);
-            mainGroup.AddProperty("TargetFrameworkVersion", "v4.7");
-            mainGroup.AddProperty("GodotProjectGeneratorVersion", Assembly.GetExecutingAssembly().GetName().Version.ToString());
-
-            var exportDebugGroup = root.AddPropertyGroup();
-            exportDebugGroup.Condition = " '$(Configuration)|$(Platform)' == 'ExportDebug|AnyCPU' ";
-            exportDebugGroup.AddProperty("DebugSymbols", "true");
-            exportDebugGroup.AddProperty("DebugType", "portable");
-            exportDebugGroup.AddProperty("Optimize", "false");
-            exportDebugGroup.AddProperty("DefineConstants", "$(GodotDefineConstants);GODOT;DEBUG;");
-            exportDebugGroup.AddProperty("ErrorReport", "prompt");
-            exportDebugGroup.AddProperty("WarningLevel", "4");
-            exportDebugGroup.AddProperty("ConsolePause", "false");
-
-            var exportReleaseGroup = root.AddPropertyGroup();
-            exportReleaseGroup.Condition = " '$(Configuration)|$(Platform)' == 'ExportRelease|AnyCPU' ";
-            exportReleaseGroup.AddProperty("DebugType", "portable");
-            exportReleaseGroup.AddProperty("Optimize", "true");
-            exportReleaseGroup.AddProperty("DefineConstants", "$(GodotDefineConstants);GODOT;");
-            exportReleaseGroup.AddProperty("ErrorReport", "prompt");
-            exportReleaseGroup.AddProperty("WarningLevel", "4");
-            exportReleaseGroup.AddProperty("ConsolePause", "false");
-
-            // References
-            var referenceGroup = root.AddItemGroup();
-            referenceGroup.AddItem("Reference", "System");
-            var frameworkRefAssembliesItem = referenceGroup.AddItem("PackageReference", "Microsoft.NETFramework.ReferenceAssemblies");
-
-            // Use metadata (child nodes) instead of attributes for the PackageReference.
-            // This is for compatibility with 3.2, where GodotTools uses an old Microsoft.Build.
-            frameworkRefAssembliesItem.AddMetadata("Version", "1.0.0");
-            frameworkRefAssembliesItem.AddMetadata("PrivateAssets", "All");
-
-            root.AddImport(Path.Combine("$(MSBuildBinPath)", "Microsoft.CSharp.targets").Replace("/", "\\"));
+            // If the name is not a valid namespace, manually set RootNamespace to a sanitized one.
+            if (sanitizedName != name)
+                mainGroup.AddProperty("RootNamespace", sanitizedName);
 
             return root;
         }
 
-        private const string AssemblyInfoTemplate =
-            @"using System.Reflection;{0}
+        public static string GenAndSaveGameProject(string dir, string name)
+        {
+            if (name.Length == 0)
+                throw new ArgumentException("Project name is empty", nameof(name));
 
-// Information about this assembly is defined by the following attributes.
-// Change them to the values specific to your project.
+            string path = Path.Combine(dir, name + ".csproj");
 
-[assembly: AssemblyTitle(""{1}"")]
-[assembly: AssemblyDescription("""")]
-[assembly: AssemblyConfiguration("""")]
-[assembly: AssemblyCompany("""")]
-[assembly: AssemblyProduct("""")]
-[assembly: AssemblyCopyright("""")]
-[assembly: AssemblyTrademark("""")]
-[assembly: AssemblyCulture("""")]
+            var root = GenGameProject(name);
 
-// The assembly version has the format ""{{Major}}.{{Minor}}.{{Build}}.{{Revision}}"".
-// The form ""{{Major}}.{{Minor}}.*"" will automatically update the build and revision,
-// and ""{{Major}}.{{Minor}}.{{Build}}.*"" will update just the revision.
+            root.Save(path);
 
-[assembly: AssemblyVersion(""1.0.*"")]
-
-// The following attributes are used to specify the signing key for the assembly,
-// if desired. See the Mono documentation for more information about signing.
-
-//[assembly: AssemblyDelaySign(false)]
-//[assembly: AssemblyKeyFile("""")]
-{2}";
+            return Guid.NewGuid().ToString().ToUpper();
+        }
     }
 }
diff --git a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectUtils.cs b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectUtils.cs
index 8774b4ee310..4041c565972 100644
--- a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectUtils.cs
+++ b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectUtils.cs
@@ -1,9 +1,9 @@
+using System;
 using GodotTools.Core;
 using System.Collections.Generic;
 using System.Diagnostics;
 using System.IO;
 using System.Linq;
-using System.Reflection;
 using Microsoft.Build.Construction;
 using Microsoft.Build.Globbing;
 
@@ -11,7 +11,7 @@ namespace GodotTools.ProjectEditor
 {
     public sealed class MSBuildProject
     {
-        public ProjectRootElement Root { get; }
+        internal ProjectRootElement Root { get; set; }
 
         public bool HasUnsavedChanges { get; set; }
 
@@ -31,91 +31,7 @@ namespace GodotTools.ProjectEditor
             return root != null ? new MSBuildProject(root) : null;
         }
 
-        public static void AddItemToProjectChecked(string projectPath, string itemType, string include)
-        {
-            var dir = Directory.GetParent(projectPath).FullName;
-            var root = ProjectRootElement.Open(projectPath);
-            Debug.Assert(root != null);
-
-            var normalizedInclude = include.RelativeToPath(dir).Replace("/", "\\");
-
-            if (root.AddItemChecked(itemType, normalizedInclude))
-                root.Save();
-        }
-
-        public static void RenameItemInProjectChecked(string projectPath, string itemType, string oldInclude, string newInclude)
-        {
-            var dir = Directory.GetParent(projectPath).FullName;
-            var root = ProjectRootElement.Open(projectPath);
-            Debug.Assert(root != null);
-
-            var normalizedOldInclude = oldInclude.NormalizePath();
-            var normalizedNewInclude = newInclude.NormalizePath();
-
-            var item = root.FindItemOrNullAbs(itemType, normalizedOldInclude);
-
-            if (item == null)
-                return;
-
-            item.Include = normalizedNewInclude.RelativeToPath(dir).Replace("/", "\\");
-            root.Save();
-        }
-
-        public static void RemoveItemFromProjectChecked(string projectPath, string itemType, string include)
-        {
-            var root = ProjectRootElement.Open(projectPath);
-            Debug.Assert(root != null);
-
-            var normalizedInclude = include.NormalizePath();
-
-            if (root.RemoveItemChecked(itemType, normalizedInclude))
-                root.Save();
-        }
-
-        public static void RenameItemsToNewFolderInProjectChecked(string projectPath, string itemType, string oldFolder, string newFolder)
-        {
-            var dir = Directory.GetParent(projectPath).FullName;
-            var root = ProjectRootElement.Open(projectPath);
-            Debug.Assert(root != null);
-
-            bool dirty = false;
-
-            var oldFolderNormalized = oldFolder.NormalizePath();
-            var newFolderNormalized = newFolder.NormalizePath();
-            string absOldFolderNormalized = Path.GetFullPath(oldFolderNormalized).NormalizePath();
-            string absNewFolderNormalized = Path.GetFullPath(newFolderNormalized).NormalizePath();
-
-            foreach (var item in root.FindAllItemsInFolder(itemType, oldFolderNormalized))
-            {
-                string absPathNormalized = Path.GetFullPath(item.Include).NormalizePath();
-                string absNewIncludeNormalized = absNewFolderNormalized + absPathNormalized.Substring(absOldFolderNormalized.Length);
-                item.Include = absNewIncludeNormalized.RelativeToPath(dir).Replace("/", "\\");
-                dirty = true;
-            }
-
-            if (dirty)
-                root.Save();
-        }
-
-        public static void RemoveItemsInFolderFromProjectChecked(string projectPath, string itemType, string folder)
-        {
-            var root = ProjectRootElement.Open(projectPath);
-            Debug.Assert(root != null);
-
-            var folderNormalized = folder.NormalizePath();
-
-            var itemsToRemove = root.FindAllItemsInFolder(itemType, folderNormalized).ToList();
-
-            if (itemsToRemove.Count > 0)
-            {
-                foreach (var item in itemsToRemove)
-                    item.Parent.RemoveChild(item);
-
-                root.Save();
-            }
-        }
-
-        private static string[] GetAllFilesRecursive(string rootDirectory, string mask)
+        private static List<string> GetAllFilesRecursive(string rootDirectory, string mask)
         {
             string[] files = Directory.GetFiles(rootDirectory, mask, SearchOption.AllDirectories);
 
@@ -125,262 +41,59 @@ namespace GodotTools.ProjectEditor
                 files[i] = files[i].RelativeToPath(rootDirectory);
             }
 
-            return files;
+            return new List<string>(files);
         }
 
-        public static string[] GetIncludeFiles(string projectPath, string itemType)
+        // NOTE: Assumes auto-including items. Only used by the scripts metadata generator, which will be replaced with source generators in the future.
+        public static IEnumerable<string> GetIncludeFiles(string projectPath, string itemType)
         {
-            var result = new List<string>();
-            var existingFiles = GetAllFilesRecursive(Path.GetDirectoryName(projectPath), "*.cs");
+            var excluded = new List<string>();
+            var includedFiles = GetAllFilesRecursive(Path.GetDirectoryName(projectPath), "*.cs");
 
             var root = ProjectRootElement.Open(projectPath);
             Debug.Assert(root != null);
 
-            foreach (var itemGroup in root.ItemGroups)
+            foreach (var item in root.Items)
             {
-                if (itemGroup.Condition.Length != 0)
+                if (string.IsNullOrEmpty(item.Condition))
                     continue;
 
-                foreach (var item in itemGroup.Items)
-                {
-                    if (item.ItemType != itemType)
-                        continue;
+                if (item.ItemType != itemType)
+                    continue;
 
-                    string normalizedInclude = item.Include.NormalizePath();
+                string normalizedExclude = item.Exclude.NormalizePath();
 
-                    var glob = MSBuildGlob.Parse(normalizedInclude);
+                var glob = MSBuildGlob.Parse(normalizedExclude);
 
-                    // TODO Check somehow if path has no blob to avoid the following loop...
-
-                    foreach (var existingFile in existingFiles)
-                    {
-                        if (glob.IsMatch(existingFile))
-                        {
-                            result.Add(existingFile);
-                        }
-                    }
-                }
+                excluded.AddRange(includedFiles.Where(includedFile => glob.IsMatch(includedFile)));
             }
 
-            return result.ToArray();
+            includedFiles.RemoveAll(f => excluded.Contains(f));
+
+            return includedFiles;
         }
 
-        public static void EnsureHasProjectTypeGuids(MSBuildProject project)
+        public static void MigrateToProjectSdksStyle(MSBuildProject project, string projectName)
         {
-            var root = project.Root;
+            var origRoot = project.Root;
 
-            bool found = root.PropertyGroups.Any(pg =>
-                string.IsNullOrEmpty(pg.Condition) && pg.Properties.Any(p => p.Name == "ProjectTypeGuids"));
-
-            if (found)
+            if (!string.IsNullOrEmpty(origRoot.Sdk))
                 return;
 
-            root.AddProperty("ProjectTypeGuids", ProjectGenerator.GodotDefaultProjectTypeGuids);
-
+            project.Root = ProjectGenerator.GenGameProject(projectName);
+            project.Root.FullPath = origRoot.FullPath;
             project.HasUnsavedChanges = true;
         }
 
-        ///  Simple function to make sure the Api assembly references are configured correctly
-        public static void FixApiHintPath(MSBuildProject project)
+        public static void EnsureGodotSdkIsUpToDate(MSBuildProject project)
         {
             var root = project.Root;
+            string godotSdkAttrValue = ProjectGenerator.GodotSdkAttrValue;
 
-            void AddPropertyIfNotPresent(string name, string condition, string value)
-            {
-                if (root.PropertyGroups
-                    .Any(g => (string.IsNullOrEmpty(g.Condition) || g.Condition.Trim() == condition) &&
-                              g.Properties
-                                  .Any(p => p.Name == name &&
-                                            p.Value == value &&
-                                            (p.Condition.Trim() == condition || g.Condition.Trim() == condition))))
-                {
-                    return;
-                }
-
-                root.AddProperty(name, value).Condition = " " + condition + " ";
-                project.HasUnsavedChanges = true;
-            }
-
-            AddPropertyIfNotPresent(name: "ApiConfiguration",
-                condition: "'$(Configuration)' != 'ExportRelease'",
-                value: "Debug");
-            AddPropertyIfNotPresent(name: "ApiConfiguration",
-                condition: "'$(Configuration)' == 'ExportRelease'",
-                value: "Release");
-
-            void SetReferenceHintPath(string referenceName, string condition, string hintPath)
-            {
-                foreach (var itemGroup in root.ItemGroups.Where(g =>
-                    g.Condition.Trim() == string.Empty || g.Condition.Trim() == condition))
-                {
-                    var references = itemGroup.Items.Where(item =>
-                        item.ItemType == "Reference" &&
-                        item.Include == referenceName &&
-                        (item.Condition.Trim() == condition || itemGroup.Condition.Trim() == condition));
-
-                    var referencesWithHintPath = references.Where(reference =>
-                        reference.Metadata.Any(m => m.Name == "HintPath"));
-
-                    if (referencesWithHintPath.Any(reference => reference.Metadata
-                        .Any(m => m.Name == "HintPath" && m.Value == hintPath)))
-                    {
-                        // Found a Reference item with the right HintPath
-                        return;
-                    }
-
-                    var referenceWithHintPath = referencesWithHintPath.FirstOrDefault();
-                    if (referenceWithHintPath != null)
-                    {
-                        // Found a Reference item with a wrong HintPath
-                        foreach (var metadata in referenceWithHintPath.Metadata.ToList()
-                            .Where(m => m.Name == "HintPath"))
-                        {
-                            // Safe to remove as we duplicate with ToList() to loop
-                            referenceWithHintPath.RemoveChild(metadata);
-                        }
-
-                        referenceWithHintPath.AddMetadata("HintPath", hintPath);
-                        project.HasUnsavedChanges = true;
-                        return;
-                    }
-
-                    var referenceWithoutHintPath = references.FirstOrDefault();
-                    if (referenceWithoutHintPath != null)
-                    {
-                        // Found a Reference item without a HintPath
-                        referenceWithoutHintPath.AddMetadata("HintPath", hintPath);
-                        project.HasUnsavedChanges = true;
-                        return;
-                    }
-                }
-
-                // Found no Reference item at all. Add it.
-                root.AddItem("Reference", referenceName).Condition = " " + condition + " ";
-                project.HasUnsavedChanges = true;
-            }
-
-            const string coreProjectName = "GodotSharp";
-            const string editorProjectName = "GodotSharpEditor";
-
-            const string coreCondition = "";
-            const string editorCondition = "'$(Configuration)' == 'Debug'";
-
-            var coreHintPath = $"$(ProjectDir)/.mono/assemblies/$(ApiConfiguration)/{coreProjectName}.dll";
-            var editorHintPath = $"$(ProjectDir)/.mono/assemblies/$(ApiConfiguration)/{editorProjectName}.dll";
-
-            SetReferenceHintPath(coreProjectName, coreCondition, coreHintPath);
-            SetReferenceHintPath(editorProjectName, editorCondition, editorHintPath);
-        }
-
-        public static void MigrateFromOldConfigNames(MSBuildProject project)
-        {
-            var root = project.Root;
-
-            bool hasGodotProjectGeneratorVersion = false;
-            bool foundOldConfiguration = false;
-
-            foreach (var propertyGroup in root.PropertyGroups.Where(g => string.IsNullOrEmpty(g.Condition)))
-            {
-                if (!hasGodotProjectGeneratorVersion && propertyGroup.Properties.Any(p => p.Name == "GodotProjectGeneratorVersion"))
-                    hasGodotProjectGeneratorVersion = true;
-
-                foreach (var configItem in propertyGroup.Properties
-                    .Where(p => p.Condition.Trim() == "'$(Configuration)' == ''" && p.Value == "Tools"))
-                {
-                    configItem.Value = "Debug";
-                    foundOldConfiguration = true;
-                    project.HasUnsavedChanges = true;
-                }
-            }
-
-            if (!hasGodotProjectGeneratorVersion)
-            {
-                root.PropertyGroups.First(g => string.IsNullOrEmpty(g.Condition))?
-                    .AddProperty("GodotProjectGeneratorVersion", Assembly.GetExecutingAssembly().GetName().Version.ToString());
-                project.HasUnsavedChanges = true;
-            }
-
-            if (!foundOldConfiguration)
-            {
-                var toolsConditions = new[]
-                {
-                    "'$(Configuration)|$(Platform)' == 'Tools|AnyCPU'",
-                    "'$(Configuration)|$(Platform)' != 'Tools|AnyCPU'",
-                    "'$(Configuration)' == 'Tools'",
-                    "'$(Configuration)' != 'Tools'"
-                };
-
-                foundOldConfiguration = root.PropertyGroups
-                    .Any(g => toolsConditions.Any(c => c == g.Condition.Trim()));
-            }
-
-            if (foundOldConfiguration)
-            {
-                void MigrateConfigurationConditions(string oldConfiguration, string newConfiguration)
-                {
-                    void MigrateConditions(string oldCondition, string newCondition)
-                    {
-                        foreach (var propertyGroup in root.PropertyGroups.Where(g => g.Condition.Trim() == oldCondition))
-                        {
-                            propertyGroup.Condition = " " + newCondition + " ";
-                            project.HasUnsavedChanges = true;
-                        }
-
-                        foreach (var propertyGroup in root.PropertyGroups)
-                        {
-                            foreach (var prop in propertyGroup.Properties.Where(p => p.Condition.Trim() == oldCondition))
-                            {
-                                prop.Condition = " " + newCondition + " ";
-                                project.HasUnsavedChanges = true;
-                            }
-                        }
-
-                        foreach (var itemGroup in root.ItemGroups.Where(g => g.Condition.Trim() == oldCondition))
-                        {
-                            itemGroup.Condition = " " + newCondition + " ";
-                            project.HasUnsavedChanges = true;
-                        }
-
-                        foreach (var itemGroup in root.ItemGroups)
-                        {
-                            foreach (var item in itemGroup.Items.Where(item => item.Condition.Trim() == oldCondition))
-                            {
-                                item.Condition = " " + newCondition + " ";
-                                project.HasUnsavedChanges = true;
-                            }
-                        }
-                    }
-
-                    foreach (var op in new[] {"==", "!="})
-                    {
-                        MigrateConditions($"'$(Configuration)|$(Platform)' {op} '{oldConfiguration}|AnyCPU'", $"'$(Configuration)|$(Platform)' {op} '{newConfiguration}|AnyCPU'");
-                        MigrateConditions($"'$(Configuration)' {op} '{oldConfiguration}'", $"'$(Configuration)' {op} '{newConfiguration}'");
-                    }
-                }
-
-                MigrateConfigurationConditions("Debug", "ExportDebug");
-                MigrateConfigurationConditions("Release", "ExportRelease");
-                MigrateConfigurationConditions("Tools", "Debug"); // Must be last
-            }
-        }
-
-        public static void EnsureHasNugetNetFrameworkRefAssemblies(MSBuildProject project)
-        {
-            var root = project.Root;
-
-            bool found = root.ItemGroups.Any(g => string.IsNullOrEmpty(g.Condition) && g.Items.Any(
-                item => item.ItemType == "PackageReference" && item.Include == "Microsoft.NETFramework.ReferenceAssemblies"));
-
-            if (found)
+            if (!string.IsNullOrEmpty(root.Sdk) && root.Sdk.Trim().Equals(godotSdkAttrValue, StringComparison.OrdinalIgnoreCase))
                 return;
 
-            var frameworkRefAssembliesItem = root.AddItem("PackageReference", "Microsoft.NETFramework.ReferenceAssemblies");
-
-            // Use metadata (child nodes) instead of attributes for the PackageReference.
-            // This is for compatibility with 3.2, where GodotTools uses an old Microsoft.Build.
-            frameworkRefAssembliesItem.AddMetadata("Version", "1.0.0");
-            frameworkRefAssembliesItem.AddMetadata("PrivateAssets", "All");
-
+            root.Sdk = godotSdkAttrValue;
             project.HasUnsavedChanges = true;
         }
     }
diff --git a/modules/mono/editor/GodotTools/GodotTools/BottomPanel.cs b/modules/mono/editor/GodotTools/GodotTools/BottomPanel.cs
index 3de3d8d318e..3ab669a9f3b 100644
--- a/modules/mono/editor/GodotTools/GodotTools/BottomPanel.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/BottomPanel.cs
@@ -24,48 +24,50 @@ namespace GodotTools
         private Button errorsBtn;
         private Button viewLogBtn;
 
+        private void _UpdateBuildTab(int index, int? currentTab)
+        {
+            var tab = (BuildTab)buildTabs.GetChild(index);
+
+            string itemName = Path.GetFileNameWithoutExtension(tab.BuildInfo.Solution);
+            itemName += " [" + tab.BuildInfo.Configuration + "]";
+
+            buildTabsList.AddItem(itemName, tab.IconTexture);
+
+            string itemTooltip = "Solution: " + tab.BuildInfo.Solution;
+            itemTooltip += "\nConfiguration: " + tab.BuildInfo.Configuration;
+            itemTooltip += "\nStatus: ";
+
+            if (tab.BuildExited)
+                itemTooltip += tab.BuildResult == BuildTab.BuildResults.Success ? "Succeeded" : "Errored";
+            else
+                itemTooltip += "Running";
+
+            if (!tab.BuildExited || tab.BuildResult == BuildTab.BuildResults.Error)
+                itemTooltip += $"\nErrors: {tab.ErrorCount}";
+
+            itemTooltip += $"\nWarnings: {tab.WarningCount}";
+
+            buildTabsList.SetItemTooltip(index, itemTooltip);
+
+            // If this tab was already selected before the changes or if no tab was selected
+            if (currentTab == null || currentTab == index)
+            {
+                buildTabsList.Select(index);
+                _BuildTabsItemSelected(index);
+            }
+        }
+
         private void _UpdateBuildTabsList()
         {
             buildTabsList.Clear();
 
-            int currentTab = buildTabs.CurrentTab;
+            int? currentTab = buildTabs.CurrentTab;
 
-            bool noCurrentTab = currentTab < 0 || currentTab >= buildTabs.GetTabCount();
+            if (currentTab < 0 || currentTab >= buildTabs.GetTabCount())
+                currentTab = null;
 
             for (int i = 0; i < buildTabs.GetChildCount(); i++)
-            {
-                var tab = (BuildTab)buildTabs.GetChild(i);
-
-                if (tab == null)
-                    continue;
-
-                string itemName = Path.GetFileNameWithoutExtension(tab.BuildInfo.Solution);
-                itemName += " [" + tab.BuildInfo.Configuration + "]";
-
-                buildTabsList.AddItem(itemName, tab.IconTexture);
-
-                string itemTooltip = "Solution: " + tab.BuildInfo.Solution;
-                itemTooltip += "\nConfiguration: " + tab.BuildInfo.Configuration;
-                itemTooltip += "\nStatus: ";
-
-                if (tab.BuildExited)
-                    itemTooltip += tab.BuildResult == BuildTab.BuildResults.Success ? "Succeeded" : "Errored";
-                else
-                    itemTooltip += "Running";
-
-                if (!tab.BuildExited || tab.BuildResult == BuildTab.BuildResults.Error)
-                    itemTooltip += $"\nErrors: {tab.ErrorCount}";
-
-                itemTooltip += $"\nWarnings: {tab.WarningCount}";
-
-                buildTabsList.SetItemTooltip(i, itemTooltip);
-
-                if (noCurrentTab || currentTab == i)
-                {
-                    buildTabsList.Select(i);
-                    _BuildTabsItemSelected(i);
-                }
-            }
+                _UpdateBuildTab(i, currentTab);
         }
 
         public BuildTab GetBuildTabFor(BuildInfo buildInfo)
@@ -160,13 +162,7 @@ namespace GodotTools
                 }
             }
 
-            var godotDefines = new[]
-            {
-                OS.GetName(),
-                Internal.GodotIs32Bits() ? "32" : "64"
-            };
-
-            bool buildSuccess = BuildManager.BuildProjectBlocking("Debug", godotDefines);
+            bool buildSuccess = BuildManager.BuildProjectBlocking("Debug");
 
             if (!buildSuccess)
                 return;
@@ -272,7 +268,7 @@ namespace GodotTools
                 };
                 panelTabs.AddChild(panelBuildsTab);
 
-                var toolBarHBox = new HBoxContainer { SizeFlagsHorizontal = (int)SizeFlags.ExpandFill };
+                var toolBarHBox = new HBoxContainer {SizeFlagsHorizontal = (int)SizeFlags.ExpandFill};
                 panelBuildsTab.AddChild(toolBarHBox);
 
                 var buildProjectBtn = new Button
@@ -325,7 +321,7 @@ namespace GodotTools
                 };
                 panelBuildsTab.AddChild(hsc);
 
-                buildTabsList = new ItemList { SizeFlagsHorizontal = (int)SizeFlags.ExpandFill };
+                buildTabsList = new ItemList {SizeFlagsHorizontal = (int)SizeFlags.ExpandFill};
                 buildTabsList.ItemSelected += _BuildTabsItemSelected;
                 buildTabsList.NothingSelected += _BuildTabsNothingSelected;
                 hsc.AddChild(buildTabsList);
diff --git a/modules/mono/editor/GodotTools/GodotTools/BuildManager.cs b/modules/mono/editor/GodotTools/GodotTools/BuildManager.cs
index 0974d231762..6399991b842 100644
--- a/modules/mono/editor/GodotTools/GodotTools/BuildManager.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/BuildManager.cs
@@ -6,6 +6,7 @@ using GodotTools.Build;
 using GodotTools.Ides.Rider;
 using GodotTools.Internals;
 using GodotTools.Utils;
+using JetBrains.Annotations;
 using static GodotTools.Internals.Globals;
 using File = GodotTools.Utils.File;
 
@@ -152,7 +153,7 @@ namespace GodotTools
             }
         }
 
-        public static bool BuildProjectBlocking(string config, IEnumerable<string> godotDefines)
+        public static bool BuildProjectBlocking(string config, [CanBeNull] string platform = null)
         {
             if (!File.Exists(GodotSharpDirs.ProjectSlnPath))
                 return true; // No solution to build
@@ -168,29 +169,18 @@ namespace GodotTools
                 return false;
             }
 
-            var editorSettings = GodotSharpEditor.Instance.GetEditorInterface().GetEditorSettings();
-            var buildTool = (BuildTool)editorSettings.GetSetting("mono/builds/build_tool");
-
             using (var pr = new EditorProgress("mono_project_debug_build", "Building project solution...", 1))
             {
                 pr.Step("Building project solution", 0);
 
                 var buildInfo = new BuildInfo(GodotSharpDirs.ProjectSlnPath, targets: new[] {"Build"}, config, restore: true);
 
-                bool escapeNeedsDoubleBackslash = buildTool == BuildTool.MsBuildMono || buildTool == BuildTool.DotnetCli;
-
-                // Add Godot defines
-                string constants = !escapeNeedsDoubleBackslash ? "GodotDefineConstants=\"" : "GodotDefineConstants=\\\"";
-
-                foreach (var godotDefine in godotDefines)
-                    constants += $"GODOT_{godotDefine.ToUpper().Replace("-", "_").Replace(" ", "_").Replace(";", "_")};";
+                // If a platform was not specified, try determining the current one. If that fails, let MSBuild auto-detect it.
+                if (platform != null || OS.PlatformNameMap.TryGetValue(Godot.OS.GetName(), out platform))
+                    buildInfo.CustomProperties.Add($"GodotTargetPlatform={platform}");
 
                 if (Internal.GodotIsRealTDouble())
-                    constants += "GODOT_REAL_T_IS_DOUBLE;";
-
-                constants += !escapeNeedsDoubleBackslash ? "\"" : "\\\"";
-
-                buildInfo.CustomProperties.Add(constants);
+                    buildInfo.CustomProperties.Add("GodotRealTIsDouble=true");
 
                 if (!Build(buildInfo))
                 {
@@ -233,13 +223,7 @@ namespace GodotTools
                     return true; // Requested play from an external editor/IDE which already built the project
             }
 
-            var godotDefines = new[]
-            {
-                Godot.OS.GetName(),
-                Internal.GodotIs32Bits() ? "32" : "64"
-            };
-
-            return BuildProjectBlocking("Debug", godotDefines);
+            return BuildProjectBlocking("Debug");
         }
 
         public static void Initialize()
diff --git a/modules/mono/editor/GodotTools/GodotTools/CsProjOperations.cs b/modules/mono/editor/GodotTools/GodotTools/CsProjOperations.cs
index 421729cc110..a8afb387282 100644
--- a/modules/mono/editor/GodotTools/GodotTools/CsProjOperations.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/CsProjOperations.cs
@@ -1,9 +1,9 @@
 using Godot;
 using System;
+using System.Linq;
 using Godot.Collections;
 using GodotTools.Internals;
 using GodotTools.ProjectEditor;
-using static GodotTools.Internals.Globals;
 using File = GodotTools.Utils.File;
 using Directory = GodotTools.Utils.Directory;
 
@@ -15,7 +15,7 @@ namespace GodotTools
         {
             try
             {
-                return ProjectGenerator.GenGameProject(dir, name, compileItems: new string[] { });
+                return ProjectGenerator.GenAndSaveGameProject(dir, name);
             }
             catch (Exception e)
             {
@@ -24,14 +24,6 @@ namespace GodotTools
             }
         }
 
-        public static void AddItem(string projectPath, string itemType, string include)
-        {
-            if (!(bool)GlobalDef("mono/project/auto_update_project", true))
-                return;
-
-            ProjectUtils.AddItemToProjectChecked(projectPath, itemType, include);
-        }
-
         private static readonly DateTime Epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
 
         private static ulong ConvertToTimestamp(this DateTime value)
@@ -40,81 +32,77 @@ namespace GodotTools
             return (ulong)elapsedTime.TotalSeconds;
         }
 
+        private static bool TryParseFileMetadata(string includeFile, ulong modifiedTime, out Dictionary fileMetadata)
+        {
+            fileMetadata = null;
+
+            var parseError = ScriptClassParser.ParseFile(includeFile, out var classes, out string errorStr);
+
+            if (parseError != Error.Ok)
+            {
+                GD.PushError($"Failed to determine namespace and class for script: {includeFile}. Parse error: {errorStr ?? parseError.ToString()}");
+                return false;
+            }
+
+            string searchName = System.IO.Path.GetFileNameWithoutExtension(includeFile);
+
+            var firstMatch = classes.FirstOrDefault(classDecl =>
+                    classDecl.BaseCount != 0 && // If it doesn't inherit anything, it can't be a Godot.Object.
+                    classDecl.SearchName != searchName // Filter by the name we're looking for
+            );
+
+            if (firstMatch == null)
+                return false; // Not found
+
+            fileMetadata = new Dictionary
+            {
+                ["modified_time"] = $"{modifiedTime}",
+                ["class"] = new Dictionary
+                {
+                    ["namespace"] = firstMatch.Namespace,
+                    ["class_name"] = firstMatch.Name,
+                    ["nested"] = firstMatch.Nested
+                }
+            };
+
+            return true;
+        }
+
         public static void GenerateScriptsMetadata(string projectPath, string outputPath)
         {
-            if (File.Exists(outputPath))
-                File.Delete(outputPath);
+            var metadataDict = Internal.GetScriptsMetadataOrNothing().Duplicate();
 
-            var oldDict = Internal.GetScriptsMetadataOrNothing();
-            var newDict = new Godot.Collections.Dictionary<string, object>();
-
-            foreach (var includeFile in ProjectUtils.GetIncludeFiles(projectPath, "Compile"))
+            bool IsUpToDate(string includeFile, ulong modifiedTime)
             {
-                string projectIncludeFile = ("res://" + includeFile).SimplifyGodotPath();
-
-                ulong modifiedTime = File.GetLastWriteTime(projectIncludeFile).ConvertToTimestamp();
-
-                if (oldDict.TryGetValue(projectIncludeFile, out var oldFileVar))
-                {
-                    var oldFileDict = (Dictionary)oldFileVar;
-
-                    if (ulong.TryParse(oldFileDict["modified_time"] as string, out ulong storedModifiedTime))
-                    {
-                        if (storedModifiedTime == modifiedTime)
-                        {
-                            // No changes so no need to parse again
-                            newDict[projectIncludeFile] = oldFileDict;
-                            continue;
-                        }
-                    }
-                }
-
-                Error parseError = ScriptClassParser.ParseFile(projectIncludeFile, out var classes, out string errorStr);
-                if (parseError != Error.Ok)
-                {
-                    GD.PushError($"Failed to determine namespace and class for script: {projectIncludeFile}. Parse error: {errorStr ?? parseError.ToString()}");
-                    continue;
-                }
-
-                string searchName = System.IO.Path.GetFileNameWithoutExtension(projectIncludeFile);
-
-                var classDict = new Dictionary();
-
-                foreach (var classDecl in classes)
-                {
-                    if (classDecl.BaseCount == 0)
-                        continue; // Does not inherit nor implement anything, so it can't be a script class
-
-                    string classCmp = classDecl.Nested ?
-                        classDecl.Name.Substring(classDecl.Name.LastIndexOf(".", StringComparison.Ordinal) + 1) :
-                        classDecl.Name;
-
-                    if (classCmp != searchName)
-                        continue;
-
-                    classDict["namespace"] = classDecl.Namespace;
-                    classDict["class_name"] = classDecl.Name;
-                    classDict["nested"] = classDecl.Nested;
-                    break;
-                }
-
-                if (classDict.Count == 0)
-                    continue; // Not found
-
-                newDict[projectIncludeFile] = new Dictionary { ["modified_time"] = $"{modifiedTime}", ["class"] = classDict };
+                return metadataDict.TryGetValue(includeFile, out var oldFileVar) &&
+                       ulong.TryParse(((Dictionary)oldFileVar)["modified_time"] as string,
+                           out ulong storedModifiedTime) && storedModifiedTime == modifiedTime;
             }
 
-            if (newDict.Count > 0)
+            var outdatedFiles = ProjectUtils.GetIncludeFiles(projectPath, "Compile")
+                .Select(path => ("res://" + path).SimplifyGodotPath())
+                .ToDictionary(path => path, path => File.GetLastWriteTime(path).ConvertToTimestamp())
+                .Where(pair => !IsUpToDate(includeFile: pair.Key, modifiedTime: pair.Value))
+                .ToArray();
+
+            foreach (var pair in outdatedFiles)
             {
-                string json = JSON.Print(newDict);
+                metadataDict.Remove(pair.Key);
 
-                string baseDir = outputPath.GetBaseDir();
+                string includeFile = pair.Key;
 
-                if (!Directory.Exists(baseDir))
-                    Directory.CreateDirectory(baseDir);
-
-                File.WriteAllText(outputPath, json);
+                if (TryParseFileMetadata(includeFile, modifiedTime: pair.Value, out var fileMetadata))
+                    metadataDict[includeFile] = fileMetadata;
             }
+
+            string json = metadataDict.Count <= 0 ? "{}" : JSON.Print(metadataDict);
+
+            string baseDir = outputPath.GetBaseDir();
+
+            if (!Directory.Exists(baseDir))
+                Directory.CreateDirectory(baseDir);
+
+            File.WriteAllText(outputPath, json);
         }
     }
 }
diff --git a/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs b/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs
index 6bfbc62f3b7..554763eecb1 100644
--- a/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs
@@ -7,6 +7,7 @@ using System.Linq;
 using System.Runtime.CompilerServices;
 using GodotTools.Core;
 using GodotTools.Internals;
+using JetBrains.Annotations;
 using static GodotTools.Internals.Globals;
 using Directory = GodotTools.Utils.Directory;
 using File = GodotTools.Utils.File;
@@ -145,9 +146,7 @@ namespace GodotTools.Export
             if (!File.Exists(GodotSharpDirs.ProjectSlnPath))
                 return;
 
-            string platform = DeterminePlatformFromFeatures(features);
-
-            if (platform == null)
+            if (!DeterminePlatformFromFeatures(features, out string platform))
                 throw new NotSupportedException("Target platform not supported");
 
             string outputDir = new FileInfo(path).Directory?.FullName ??
@@ -160,10 +159,7 @@ namespace GodotTools.Export
 
             AddFile(scriptsMetadataPath, scriptsMetadataPath);
 
-            // Turn export features into defines
-            var godotDefines = features;
-
-            if (!BuildManager.BuildProjectBlocking(buildConfig, godotDefines))
+            if (!BuildManager.BuildProjectBlocking(buildConfig, platform))
                 throw new Exception("Failed to build project");
 
             // Add dependency assemblies
@@ -289,6 +285,7 @@ namespace GodotTools.Export
             }
         }
 
+        [NotNull]
         private static string ExportDataDirectory(string[] features, string platform, bool isDebug, string outputDir)
         {
             string target = isDebug ? "release_debug" : "release";
@@ -343,18 +340,19 @@ namespace GodotTools.Export
         private static bool PlatformHasTemplateDir(string platform)
         {
             // OSX export templates are contained in a zip, so we place our custom template inside it and let Godot do the rest.
-            return !new[] { OS.Platforms.OSX, OS.Platforms.Android, OS.Platforms.iOS, OS.Platforms.HTML5 }.Contains(platform);
+            return !new[] {OS.Platforms.OSX, OS.Platforms.Android, OS.Platforms.iOS, OS.Platforms.HTML5}.Contains(platform);
         }
 
-        private static string DeterminePlatformFromFeatures(IEnumerable<string> features)
+        private static bool DeterminePlatformFromFeatures(IEnumerable<string> features, out string platform)
         {
             foreach (var feature in features)
             {
-                if (OS.PlatformNameMap.TryGetValue(feature, out string platform))
-                    return platform;
+                if (OS.PlatformNameMap.TryGetValue(feature, out platform))
+                    return true;
             }
 
-            return null;
+            platform = null;
+            return false;
         }
 
         private static string GetBclProfileDir(string profile)
@@ -391,7 +389,7 @@ namespace GodotTools.Export
         /// </summary>
         private static bool PlatformRequiresCustomBcl(string platform)
         {
-            if (new[] { OS.Platforms.Android, OS.Platforms.iOS, OS.Platforms.HTML5 }.Contains(platform))
+            if (new[] {OS.Platforms.Android, OS.Platforms.iOS, OS.Platforms.HTML5}.Contains(platform))
                 return true;
 
             // The 'net_4_x' BCL is not compatible between Windows and the other platforms.
diff --git a/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs b/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs
index f330f9ed2cc..a363ecc9201 100644
--- a/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs
@@ -175,36 +175,6 @@ namespace GodotTools
                     // Once shown a first time, it can be seen again via the Mono menu - it doesn't have to be exclusive from that time on.
                     aboutDialog.Exclusive = false;
                 }
-
-                var fileSystemDock = GetEditorInterface().GetFileSystemDock();
-
-                fileSystemDock.FilesMoved += (file, newFile) =>
-                {
-                    if (Path.GetExtension(file) == Internal.CSharpLanguageExtension)
-                    {
-                        ProjectUtils.RenameItemInProjectChecked(GodotSharpDirs.ProjectCsProjPath, "Compile",
-                            ProjectSettings.GlobalizePath(file), ProjectSettings.GlobalizePath(newFile));
-                    }
-                };
-
-                fileSystemDock.FileRemoved += file =>
-                {
-                    if (Path.GetExtension(file) == Internal.CSharpLanguageExtension)
-                        ProjectUtils.RemoveItemFromProjectChecked(GodotSharpDirs.ProjectCsProjPath, "Compile",
-                            ProjectSettings.GlobalizePath(file));
-                };
-
-                fileSystemDock.FolderMoved += (oldFolder, newFolder) =>
-                {
-                    ProjectUtils.RenameItemsToNewFolderInProjectChecked(GodotSharpDirs.ProjectCsProjPath, "Compile",
-                        ProjectSettings.GlobalizePath(oldFolder), ProjectSettings.GlobalizePath(newFolder));
-                };
-
-                fileSystemDock.FolderRemoved += oldFolder =>
-                {
-                    ProjectUtils.RemoveItemsInFolderFromProjectChecked(GodotSharpDirs.ProjectCsProjPath, "Compile",
-                        ProjectSettings.GlobalizePath(oldFolder));
-                };
             }
         }
 
@@ -389,6 +359,37 @@ namespace GodotTools
             return BuildManager.EditorBuildCallback();
         }
 
+        private void ApplyNecessaryChangesToSolution()
+        {
+            try
+            {
+                // Migrate solution from old configuration names to: Debug, ExportDebug and ExportRelease
+                DotNetSolution.MigrateFromOldConfigNames(GodotSharpDirs.ProjectSlnPath);
+
+                var msbuildProject = ProjectUtils.Open(GodotSharpDirs.ProjectCsProjPath)
+                                     ?? throw new Exception("Cannot open C# project");
+
+                // NOTE: The order in which changes are made to the project is important
+
+                // Migrate to MSBuild project Sdks style if using the old style
+                ProjectUtils.MigrateToProjectSdksStyle(msbuildProject, ProjectAssemblyName);
+
+                ProjectUtils.EnsureGodotSdkIsUpToDate(msbuildProject);
+
+                if (msbuildProject.HasUnsavedChanges)
+                {
+                    // Save a copy of the project before replacing it
+                    FileUtils.SaveBackupCopy(GodotSharpDirs.ProjectCsProjPath);
+
+                    msbuildProject.Save();
+                }
+            }
+            catch (Exception e)
+            {
+                GD.PushError(e.ToString());
+            }
+        }
+
         public override void EnablePlugin()
         {
             base.EnablePlugin();
@@ -468,42 +469,7 @@ namespace GodotTools
 
             if (File.Exists(GodotSharpDirs.ProjectSlnPath) && File.Exists(GodotSharpDirs.ProjectCsProjPath))
             {
-                try
-                {
-                    // Migrate solution from old configuration names to: Debug, ExportDebug and ExportRelease
-                    DotNetSolution.MigrateFromOldConfigNames(GodotSharpDirs.ProjectSlnPath);
-
-                    var msbuildProject = ProjectUtils.Open(GodotSharpDirs.ProjectCsProjPath)
-                                         ?? throw new Exception("Cannot open C# project");
-
-                    // NOTE: The order in which changes are made to the project is important
-
-                    // Migrate csproj from old configuration names to: Debug, ExportDebug and ExportRelease
-                    ProjectUtils.MigrateFromOldConfigNames(msbuildProject);
-
-                    // Apply the other fixes only after configurations have been migrated
-
-                    // Make sure the existing project has the ProjectTypeGuids property (for VisualStudio)
-                    ProjectUtils.EnsureHasProjectTypeGuids(msbuildProject);
-
-                    // Make sure the existing project has Api assembly references configured correctly
-                    ProjectUtils.FixApiHintPath(msbuildProject);
-
-                    // Make sure the existing project references the Microsoft.NETFramework.ReferenceAssemblies nuget package
-                    ProjectUtils.EnsureHasNugetNetFrameworkRefAssemblies(msbuildProject);
-
-                    if (msbuildProject.HasUnsavedChanges)
-                    {
-                        // Save a copy of the project before replacing it
-                        FileUtils.SaveBackupCopy(GodotSharpDirs.ProjectCsProjPath);
-
-                        msbuildProject.Save();
-                    }
-                }
-                catch (Exception e)
-                {
-                    GD.PushError(e.ToString());
-                }
+                ApplyNecessaryChangesToSolution();
             }
             else
             {
diff --git a/modules/mono/editor/GodotTools/GodotTools/Internals/ScriptClassParser.cs b/modules/mono/editor/GodotTools/GodotTools/Internals/ScriptClassParser.cs
index 569f27649fc..c72a84c5137 100644
--- a/modules/mono/editor/GodotTools/GodotTools/Internals/ScriptClassParser.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/Internals/ScriptClassParser.cs
@@ -15,6 +15,10 @@ namespace GodotTools.Internals
             public bool Nested { get; }
             public long BaseCount { get; }
 
+            public string SearchName => Nested ?
+                Name.Substring(Name.LastIndexOf(".", StringComparison.Ordinal) + 1) :
+                Name;
+
             public ClassDecl(string name, string @namespace, bool nested, long baseCount)
             {
                 Name = name;
diff --git a/modules/mono/editor/bindings_generator.cpp b/modules/mono/editor/bindings_generator.cpp
index 79e4b7c7942..a17c3711178 100644
--- a/modules/mono/editor/bindings_generator.cpp
+++ b/modules/mono/editor/bindings_generator.cpp
@@ -45,7 +45,6 @@
 #include "../mono_gd/gd_mono_marshal.h"
 #include "../utils/path_utils.h"
 #include "../utils/string_utils.h"
-#include "csharp_project.h"
 
 #define CS_INDENT "    " // 4 whitespaces
 
diff --git a/modules/mono/editor/csharp_project.cpp b/modules/mono/editor/csharp_project.cpp
deleted file mode 100644
index 6f54eb09a2e..00000000000
--- a/modules/mono/editor/csharp_project.cpp
+++ /dev/null
@@ -1,69 +0,0 @@
-/*************************************************************************/
-/*  csharp_project.cpp                                                   */
-/*************************************************************************/
-/*                       This file is part of:                           */
-/*                           GODOT ENGINE                                */
-/*                      https://godotengine.org                          */
-/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur.                 */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md).   */
-/*                                                                       */
-/* Permission is hereby granted, free of charge, to any person obtaining */
-/* a copy of this software and associated documentation files (the       */
-/* "Software"), to deal in the Software without restriction, including   */
-/* without limitation the rights to use, copy, modify, merge, publish,   */
-/* distribute, sublicense, and/or sell copies of the Software, and to    */
-/* permit persons to whom the Software is furnished to do so, subject to */
-/* the following conditions:                                             */
-/*                                                                       */
-/* The above copyright notice and this permission notice shall be        */
-/* included in all copies or substantial portions of the Software.       */
-/*                                                                       */
-/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       */
-/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    */
-/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
-/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  */
-/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  */
-/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */
-/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */
-/*************************************************************************/
-
-#include "csharp_project.h"
-
-#include "core/io/json.h"
-#include "core/os/dir_access.h"
-#include "core/os/file_access.h"
-#include "core/os/os.h"
-#include "core/project_settings.h"
-
-#include "../csharp_script.h"
-#include "../mono_gd/gd_mono_class.h"
-#include "../mono_gd/gd_mono_marshal.h"
-#include "../utils/string_utils.h"
-#include "script_class_parser.h"
-
-namespace CSharpProject {
-
-void add_item(const String &p_project_path, const String &p_item_type, const String &p_include) {
-	if (!GLOBAL_DEF("mono/project/auto_update_project", true)) {
-		return;
-	}
-
-	GDMonoAssembly *tools_project_editor_assembly = GDMono::get_singleton()->get_tools_project_editor_assembly();
-
-	GDMonoClass *klass = tools_project_editor_assembly->get_class("GodotTools.ProjectEditor", "ProjectUtils");
-
-	Variant project_path = p_project_path;
-	Variant item_type = p_item_type;
-	Variant include = p_include;
-	const Variant *args[3] = { &project_path, &item_type, &include };
-	MonoException *exc = nullptr;
-	klass->get_method("AddItemToProjectChecked", 3)->invoke(nullptr, args, &exc);
-
-	if (exc) {
-		GDMonoUtils::debug_print_unhandled_exception(exc);
-		ERR_FAIL();
-	}
-}
-
-} // namespace CSharpProject
diff --git a/modules/mono/editor/csharp_project.h b/modules/mono/editor/csharp_project.h
deleted file mode 100644
index 515b8d3d629..00000000000
--- a/modules/mono/editor/csharp_project.h
+++ /dev/null
@@ -1,42 +0,0 @@
-/*************************************************************************/
-/*  csharp_project.h                                                     */
-/*************************************************************************/
-/*                       This file is part of:                           */
-/*                           GODOT ENGINE                                */
-/*                      https://godotengine.org                          */
-/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur.                 */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md).   */
-/*                                                                       */
-/* Permission is hereby granted, free of charge, to any person obtaining */
-/* a copy of this software and associated documentation files (the       */
-/* "Software"), to deal in the Software without restriction, including   */
-/* without limitation the rights to use, copy, modify, merge, publish,   */
-/* distribute, sublicense, and/or sell copies of the Software, and to    */
-/* permit persons to whom the Software is furnished to do so, subject to */
-/* the following conditions:                                             */
-/*                                                                       */
-/* The above copyright notice and this permission notice shall be        */
-/* included in all copies or substantial portions of the Software.       */
-/*                                                                       */
-/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       */
-/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    */
-/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
-/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  */
-/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  */
-/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */
-/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */
-/*************************************************************************/
-
-#ifndef CSHARP_PROJECT_H
-#define CSHARP_PROJECT_H
-
-#include "core/ustring.h"
-
-namespace CSharpProject {
-
-void add_item(const String &p_project_path, const String &p_item_type, const String &p_include);
-
-} // namespace CSharpProject
-
-#endif // CSHARP_PROJECT_H
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj b/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj
index 06ec2483c89..86a16c17f1c 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj
+++ b/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj
@@ -1,38 +1,16 @@
-<?xml version="1.0" encoding="utf-8"?>
-<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+<Project Sdk="Microsoft.NET.Sdk">
   <PropertyGroup>
-    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
-    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
     <ProjectGuid>{AEBF0036-DA76-4341-B651-A3F2856AB2FA}</ProjectGuid>
-    <OutputType>Library</OutputType>
     <OutputPath>bin/$(Configuration)</OutputPath>
+    <AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
     <RootNamespace>Godot</RootNamespace>
-    <AssemblyName>GodotSharp</AssemblyName>
-    <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
+    <TargetFramework>netstandard2.1</TargetFramework>
     <DocumentationFile>$(OutputPath)/$(AssemblyName).xml</DocumentationFile>
-    <BaseIntermediateOutputPath>obj</BaseIntermediateOutputPath>
+    <EnableDefaultItems>false</EnableDefaultItems>
   </PropertyGroup>
-  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
-    <DebugSymbols>true</DebugSymbols>
-    <DebugType>portable</DebugType>
-    <Optimize>false</Optimize>
-    <DefineConstants>$(GodotDefineConstants);GODOT;DEBUG;</DefineConstants>
-    <ErrorReport>prompt</ErrorReport>
-    <WarningLevel>4</WarningLevel>
-    <ConsolePause>false</ConsolePause>
+  <PropertyGroup>
+    <DefineConstants>$(DefineConstants);GODOT</DefineConstants>
   </PropertyGroup>
-  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
-    <DebugType>portable</DebugType>
-    <Optimize>true</Optimize>
-    <DefineConstants>$(GodotDefineConstants);GODOT;</DefineConstants>
-    <ErrorReport>prompt</ErrorReport>
-    <WarningLevel>4</WarningLevel>
-    <ConsolePause>false</ConsolePause>
-  </PropertyGroup>
-  <ItemGroup>
-    <PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.0" PrivateAssets="All" />
-    <Reference Include="System" />
-  </ItemGroup>
   <ItemGroup>
     <Compile Include="Core\AABB.cs" />
     <Compile Include="Core\Array.cs" />
@@ -90,5 +68,4 @@
   Fortunately code completion, go to definition and such still work.
   -->
   <Import Project="Generated\GeneratedIncludes.props" />
-  <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
 </Project>
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Properties/AssemblyInfo.cs b/modules/mono/glue/GodotSharp/GodotSharp/Properties/AssemblyInfo.cs
index f84e0183f68..da6f2938710 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Properties/AssemblyInfo.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Properties/AssemblyInfo.cs
@@ -1,27 +1,3 @@
-using System.Reflection;
 using System.Runtime.CompilerServices;
 
-// Information about this assembly is defined by the following attributes.
-// Change them to the values specific to your project.
-
-[assembly: AssemblyTitle("GodotSharp")]
-[assembly: AssemblyDescription("")]
-[assembly: AssemblyConfiguration("")]
-[assembly: AssemblyCompany("")]
-[assembly: AssemblyProduct("")]
-[assembly: AssemblyCopyright("")]
-[assembly: AssemblyTrademark("")]
-[assembly: AssemblyCulture("")]
-
-// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}".
-// The form "{Major}.{Minor}.*" will automatically update the build and revision,
-// and "{Major}.{Minor}.{Build}.*" will update just the revision.
-
-[assembly: AssemblyVersion("1.0.*")]
-
-// The following attributes are used to specify the signing key for the assembly,
-// if desired. See the Mono documentation for more information about signing.
-
-//[assembly: AssemblyDelaySign(false)]
-//[assembly: AssemblyKeyFile("")]
 [assembly: InternalsVisibleTo("GodotSharpEditor")]
diff --git a/modules/mono/glue/GodotSharp/GodotSharpEditor/GodotSharpEditor.csproj b/modules/mono/glue/GodotSharp/GodotSharpEditor/GodotSharpEditor.csproj
index 87859313124..a8c4ba96b53 100644
--- a/modules/mono/glue/GodotSharp/GodotSharpEditor/GodotSharpEditor.csproj
+++ b/modules/mono/glue/GodotSharp/GodotSharpEditor/GodotSharpEditor.csproj
@@ -1,46 +1,26 @@
-<?xml version="1.0" encoding="utf-8"?>
-<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+<Project Sdk="Microsoft.NET.Sdk">
   <PropertyGroup>
-    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
-    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
     <ProjectGuid>{8FBEC238-D944-4074-8548-B3B524305905}</ProjectGuid>
-    <OutputType>Library</OutputType>
     <OutputPath>bin/$(Configuration)</OutputPath>
+    <AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
     <RootNamespace>Godot</RootNamespace>
-    <AssemblyName>GodotSharpEditor</AssemblyName>
-    <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
+    <TargetFramework>netstandard2.1</TargetFramework>
     <DocumentationFile>$(OutputPath)/$(AssemblyName).xml</DocumentationFile>
-    <BaseIntermediateOutputPath>obj</BaseIntermediateOutputPath>
+    <EnableDefaultItems>false</EnableDefaultItems>
   </PropertyGroup>
-  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
-    <DebugSymbols>true</DebugSymbols>
-    <DebugType>portable</DebugType>
-    <Optimize>false</Optimize>
-    <DefineConstants>$(GodotDefineConstants);GODOT;DEBUG;</DefineConstants>
-    <ErrorReport>prompt</ErrorReport>
-    <WarningLevel>4</WarningLevel>
-    <ConsolePause>false</ConsolePause>
+  <PropertyGroup>
+    <DefineConstants>$(DefineConstants);GODOT</DefineConstants>
   </PropertyGroup>
-  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
-    <DebugType>portable</DebugType>
-    <Optimize>true</Optimize>
-    <DefineConstants>$(GodotDefineConstants);GODOT;</DefineConstants>
-    <ErrorReport>prompt</ErrorReport>
-    <WarningLevel>4</WarningLevel>
-    <ConsolePause>false</ConsolePause>
-  </PropertyGroup>
-  <ItemGroup>
-    <PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.0" PrivateAssets="All" />
-    <Reference Include="System" />
-  </ItemGroup>
-  <ItemGroup>
-    <Compile Include="Properties\AssemblyInfo.cs" />
-  </ItemGroup>
-  <Import Project="Generated\GeneratedIncludes.props" />
   <ItemGroup>
     <ProjectReference Include="..\GodotSharp\GodotSharp.csproj">
-      <Private>False</Private>
+      <Private>false</Private>
     </ProjectReference>
   </ItemGroup>
-  <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
+  <!--
+  We import a props file with auto-generated includes. This works well with Rider.
+  However, Visual Studio and MonoDevelop won't list them in the solution explorer.
+  We can't use wildcards as there may be undesired old files still hanging around.
+  Fortunately code completion, go to definition and such still work.
+  -->
+  <Import Project="Generated\GeneratedIncludes.props" />
 </Project>
diff --git a/modules/mono/glue/GodotSharp/GodotSharpEditor/Properties/AssemblyInfo.cs b/modules/mono/glue/GodotSharp/GodotSharpEditor/Properties/AssemblyInfo.cs
deleted file mode 100644
index 3684b7a3cb8..00000000000
--- a/modules/mono/glue/GodotSharp/GodotSharpEditor/Properties/AssemblyInfo.cs
+++ /dev/null
@@ -1,25 +0,0 @@
-using System.Reflection;
-
-// Information about this assembly is defined by the following attributes.
-// Change them to the values specific to your project.
-
-[assembly: AssemblyTitle("GodotSharpEditor")]
-[assembly: AssemblyDescription("")]
-[assembly: AssemblyConfiguration("")]
-[assembly: AssemblyCompany("")]
-[assembly: AssemblyProduct("")]
-[assembly: AssemblyCopyright("")]
-[assembly: AssemblyTrademark("")]
-[assembly: AssemblyCulture("")]
-
-// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}".
-// The form "{Major}.{Minor}.*" will automatically update the build and revision,
-// and "{Major}.{Minor}.{Build}.*" will update just the revision.
-
-[assembly: AssemblyVersion("1.0.*")]
-
-// The following attributes are used to specify the signing key for the assembly,
-// if desired. See the Mono documentation for more information about signing.
-
-//[assembly: AssemblyDelaySign(false)]
-//[assembly: AssemblyKeyFile("")]