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( $" " );