diff --git a/engine/Sandbox.SolutionGenerator/Generator.cs b/engine/Sandbox.SolutionGenerator/Generator.cs
index 65cd1662..2e08375f 100644
--- a/engine/Sandbox.SolutionGenerator/Generator.cs
+++ b/engine/Sandbox.SolutionGenerator/Generator.cs
@@ -17,59 +17,47 @@ namespace Sandbox.SolutionGenerator
return project;
}
- private string NormalizePath( string path ) => new Uri( path ).LocalPath;
-
- private string AttemptAbsoluteToRelative( string basePath, string relativePath, int maxDepth = 5 )
+ ///
+ /// Normalize the path to use forward slashes
+ ///
+ private string NormalizePath( string path )
{
- string baseNormalized = NormalizePath( basePath );
- string relativeNormalized = NormalizePath( relativePath );
- string baseEnding = string.Empty;
+ return path.Replace( '\\', '/' );
+ }
- if ( Path.HasExtension( baseNormalized ) )
+ ///
+ /// Converts a path to be relative to a base path, always returning forward slashes.
+ ///
+ private string AttemptAbsoluteToRelative( string basePath, string targetPath )
+ {
+ string targetFileName = string.Empty;
+
+ if ( Path.HasExtension( targetPath ) )
{
- baseEnding = Path.GetFileName( baseNormalized );
- baseNormalized = NormalizePath( baseNormalized.Substring( 0, baseNormalized.Length - baseEnding.Length ) );
+ targetFileName = Path.GetFileName( targetPath );
+ targetPath = Path.GetDirectoryName( targetPath ) ?? targetPath;
}
- if ( Path.HasExtension( relativeNormalized ) )
+ if ( Path.HasExtension( basePath ) )
{
- relativeNormalized = Path.GetDirectoryName( relativeNormalized );
+ basePath = Path.GetDirectoryName( basePath ) ?? basePath;
}
- string finalPath = Path.GetRelativePath( relativeNormalized, basePath );
+ string baseDir = NativeFileSystem.GetCanonicalPath( basePath );
+ string targetDir = NativeFileSystem.GetCanonicalPath( targetPath );
- // Exceed how far we want our relative path to go, bail out and use original path
- if ( finalPath.Split( ".." ).Length > maxDepth )
- {
- if ( baseEnding == null )
- {
- return baseNormalized;
- }
- else
- {
- return Path.Combine( baseNormalized, baseEnding );
- }
- }
- else
- {
- if ( baseEnding == null )
- {
- return finalPath;
- }
- else
- {
- return Path.Combine( finalPath, baseEnding );
- }
- }
+ string relativePath = NormalizePath( Path.GetRelativePath( baseDir, targetDir ) );
+
+ if ( string.IsNullOrEmpty( targetFileName ) )
+ return relativePath;
+
+ return relativePath + "/" + targetFileName;
}
private static readonly JsonSerializerOptions JsonWriteIndented = new() { WriteIndented = true };
public void Run( string gameExePath, string managedFolder, string solutionPath, string relativePath, string projectPath )
{
- string normalizedRelativePath = NormalizePath( projectPath );
- int relativePathoffset = normalizedRelativePath.Length + 1;
-
managedFolder = Path.Combine( relativePath, managedFolder );
solutionPath = Path.Combine( projectPath, solutionPath );
gameExePath = Path.Combine( relativePath, gameExePath );
@@ -80,8 +68,8 @@ namespace Sandbox.SolutionGenerator
{
ProjectName = p.Name,
ProjectReferences = "",
- ManagedRoot = AttemptAbsoluteToRelative( managedFolder, p.CsprojPath ),
- GameRoot = AttemptAbsoluteToRelative( relativePath, p.CsprojPath ),
+ ManagedRoot = AttemptAbsoluteToRelative( p.CsprojPath, managedFolder ),
+ GameRoot = AttemptAbsoluteToRelative( p.CsprojPath, relativePath ),
References = p.References,
GlobalStatic = p.GlobalStatic,
GlobalUsing = p.GlobalUsing,
@@ -109,7 +97,8 @@ namespace Sandbox.SolutionGenerator
var reference = Projects.FirstOrDefault( x => x.Name == proj || x.PackageIdent == proj );
if ( reference != null )
{
- var path = NormalizePath( $"{reference.Path}\\{reference.Name}.csproj" );
+ var absolutePath = NormalizePath( $"{reference.Path}/{reference.Name}.csproj" );
+ var path = AttemptAbsoluteToRelative( p.CsprojPath, absolutePath );
csproj.ProjectReferences += $" \n";
}
else
@@ -126,11 +115,14 @@ namespace Sandbox.SolutionGenerator
var propertiesPath = Path.Combine( p.Path, "Properties" );
Directory.CreateDirectory( propertiesPath );
+ var absoluteExePath = Path.Combine( relativePath, "sbox-dev.exe" );
+ var relativeExePath = AttemptAbsoluteToRelative( propertiesPath, absoluteExePath );
+
var launchSettings = new LaunchSettings { Profiles = new() };
launchSettings.Profiles.Add( "Editor", new LaunchSettings.Profile
{
CommandName = "Executable",
- ExecutablePath = Path.Combine( relativePath, "sbox-dev.exe" ),
+ ExecutablePath = relativeExePath,
CommandLineArgs = $"-project \"{p.SandboxProjectFilePath}\"",
} );
@@ -143,12 +135,7 @@ namespace Sandbox.SolutionGenerator
foreach ( var p in Projects )
{
- string normalizedProjectPath = NormalizePath( p.CsprojPath );
- if ( normalizedProjectPath.StartsWith( normalizedRelativePath ) )
- {
- normalizedProjectPath = normalizedProjectPath.Substring( relativePathoffset );
- }
-
+ string normalizedProjectPath = AttemptAbsoluteToRelative( solutionPath, p.CsprojPath );
normalizedProjectPath = normalizedProjectPath.Trim( '/', '\\' );
slnx.AddProject( normalizedProjectPath, p.Folder );
}
diff --git a/engine/Sandbox.SolutionGenerator/NativeFileSystem.cs b/engine/Sandbox.SolutionGenerator/NativeFileSystem.cs
new file mode 100644
index 00000000..513b95fc
--- /dev/null
+++ b/engine/Sandbox.SolutionGenerator/NativeFileSystem.cs
@@ -0,0 +1,77 @@
+using System;
+using System.IO;
+using System.Runtime.InteropServices;
+
+namespace Sandbox.SolutionGenerator;
+
+///
+/// Provides native file system operations with platform-specific implementations.
+///
+internal static partial class NativeFileSystem
+{
+ [DllImport( "kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true )]
+ private static extern IntPtr CreateFileW( string lpFileName, uint dwDesiredAccess, uint dwShareMode, IntPtr lpSecurityAttributes, uint dwCreationDisposition, uint dwFlagsAndAttributes, IntPtr hTemplateFile );
+
+ [DllImport( "kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true )]
+ private static extern uint GetFinalPathNameByHandleW( IntPtr hFile, char[] lpszFilePath, uint cchFilePath, uint dwFlags );
+
+ [DllImport( "kernel32.dll", SetLastError = true )]
+ private static extern bool CloseHandle( IntPtr hObject );
+
+ private const uint FILE_FLAG_BACKUP_SEMANTICS = 0x02000000;
+ private const uint OPEN_EXISTING = 3;
+ private const uint FILE_SHARE_READ = 1;
+ private const uint FILE_SHARE_WRITE = 2;
+
+ ///
+ /// Gets the canonical path with proper casing for the given path.
+ /// On Windows, this resolves the true filesystem path including correct casing.
+ /// On Linux, this returns the path unchanged since the filesystem is case-sensitive.
+ ///
+ /// The path to canonicalize.
+ /// The canonical path, or the original path if canonicalization fails.
+ internal static string GetCanonicalPath( string path )
+ {
+ if ( string.IsNullOrWhiteSpace( path ) || !Path.IsPathRooted( path ) )
+ return path;
+
+ if ( !OperatingSystem.IsWindows() )
+ return path;
+
+ try
+ {
+ var handle = CreateFileW( path, 0, FILE_SHARE_READ | FILE_SHARE_WRITE, IntPtr.Zero, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, IntPtr.Zero );
+ if ( handle == IntPtr.Zero || handle == new IntPtr( -1 ) )
+ return path;
+
+ try
+ {
+ var buffer = new char[512];
+ var len = GetFinalPathNameByHandleW( handle, buffer, (uint)buffer.Length, 0 );
+ if ( len > 0 && len < buffer.Length )
+ {
+ var finalPath = new string( buffer, 0, (int)len );
+ // Remove the \\?\ prefix added by Windows API
+ if ( finalPath.StartsWith( @"\\?\" ) )
+ {
+ finalPath = finalPath.Substring( 4 );
+ }
+ return finalPath;
+ }
+ else
+ {
+ return path;
+ }
+ }
+ finally
+ {
+ CloseHandle( handle );
+ }
+ }
+ catch
+ {
+ // Ignore errors and return original path
+ return path;
+ }
+ }
+}
diff --git a/engine/Sandbox.SolutionGenerator/Templates/Project.cs b/engine/Sandbox.SolutionGenerator/Templates/Project.cs
index 4dbd1d28..937a9902 100644
--- a/engine/Sandbox.SolutionGenerator/Templates/Project.cs
+++ b/engine/Sandbox.SolutionGenerator/Templates/Project.cs
@@ -54,8 +54,8 @@ internal partial class Project
if ( ProjectName == "Base Library" )
{
sb.AppendLine( $" " );
- sb.AppendLine( $" " );
- sb.AppendLine( $" " );
+ sb.AppendLine( $" " );
+ sb.AppendLine( $" " );
sb.AppendLine( $" " );
sb.AppendLine( $"" );
}
@@ -80,12 +80,12 @@ internal partial class Project
{
sb.AppendLine( $" " );
- sb.AppendLine( $" " );
- sb.AppendLine( $" " );
+ sb.AppendLine( $" " );
+ sb.AppendLine( $" " );
foreach ( var entry in References )
{
- sb.AppendLine( $" " );
+ sb.AppendLine( $" " );
}
sb.AppendLine( $" " );