From a8f5e233e844af2485aebf62e3c581288203fb56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Skaziak?= Date: Wed, 28 Jan 2026 15:09:54 +0100 Subject: [PATCH 1/3] UpdatedStateOfSalamander --- README.md | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 200ca7d..178307e 100644 --- a/README.md +++ b/README.md @@ -8,10 +8,23 @@ The original version of Servant Salamander was developed by Petr Šolín during The name Servant Salamander came about when Petr Šolín and his friend Pavel Schreib were brainstorming name for this project. At that time, the well-known file managers were the aging Norton Commander and the rising Windows Commander. They questioned why a file manager should be named Commander, which implied that it commanded instead of served. This thought led to the birth of the name Servant Salamander. -Please bear with us as Salamander was our first major project where we learned to program in C++. From a technology standpoint, it does not use [C++ Core Guidelines](https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines), smart pointers, [RAII](https://en.cppreference.com/w/cpp/language/raii), [STL](https://github.com/microsoft/STL), or [WIL](https://github.com/microsoft/wil), all of which were just beginning to evolve during the time Salamander was created. Many of the comments are written in Czech, but this is manageable due to recent progress in AI-powered translation. Salamander is a pure WinAPI application and does not use any frameworks, such as MFC. +Please bear with us as Salamander was our first major project where we learned to program in C++. From a technology standpoint, it does not use [C++ Core Guidelines](https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines), smart pointers, [RAII](https://en.cppreference.com/w/cpp/language/raii), [STL](https://github.com/microsoft/STL), or [WIL](https://github.com/microsoft/wil), all of which were just beginning to evolve during the time Salamander was created. Historically, many comments were written in Czech. However, an active community effort is translating the codebase to English to improve accessibility for international contributors. Salamander is a pure WinAPI application and does not use any frameworks, such as MFC. We would like to thank [Fine company](https://www.finesoftware.eu/) for making the open sourced Salamander release possible. +## Open Salamander 5.0 Updates + +The 5.0 release marks a transition to open development with several key enhancements: + +- **UI Modernization:** Introduced high-quality SVG icons for toolbars, replacing legacy bitmaps for better scaling on modern displays. +- **Performance Breakthroughs:** + - **Asynchronous Loading:** File icons are now loaded using a dedicated thread pool, significantly speeding up directory browsing. + - **Optimized I/O:** Local-to-local file operations now use a 1MB buffer to minimize system calls and improve throughput. + - **Memory Management:** Refined memory allocation strategies specifically for Unicode string handling. +- **Enhanced Unicode Support:** Comprehensive fixes for Unicode handling in window titles, file execution, and viewer outputs, ensuring full compatibility with international filenames. +- **Codebase Internationalization:** We are systematically translating legacy Czech comments into English (`// CommentsTranslationProject: TRANSLATED`) to foster a global contributor community. +- **Reliability:** Addressed critical threading issues, fixed "Access Denied" errors in worker threads, and resolved stability bugs in directory refreshing. + ## Development ### Prerequisites From de649f30fadc67cdf3db59f9c283e389b0c6fa4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Skaziak?= Date: Thu, 29 Jan 2026 11:08:27 +0100 Subject: [PATCH 2/3] Configured automatic inclusion of src/res/toolbars into the SFX package. --- Instalator/setup.inf | 69 ++++++++++++- tools/Create-Sfx.ps1 | 226 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 293 insertions(+), 2 deletions(-) create mode 100644 tools/Create-Sfx.ps1 diff --git a/Instalator/setup.inf b/Instalator/setup.inf index 8cf409a..9797eed 100644 --- a/Instalator/setup.inf +++ b/Instalator/setup.inf @@ -1,7 +1,7 @@ [Private] ApplicationName=Salamander 5.0 ApplicationNameVer=Salamander 5.0 -DefaultDirectory=C:\Program Files\Salamander 5.0 +DefaultDirectory=C:\ Program Files\Salamander 5.0 LicenseFile=LICENSE SkipChooseDirectory=0 @@ -9,7 +9,72 @@ SkipChooseDirectory=0 salamand.exe,%1\salamand.exe,0 salmon.exe,%1\salmon.exe,0 lang\english.slg,%1\lang\english.slg,0 +toolbars\Back.svg,%1\toolbars\Back.svg,0 +toolbars\CalculateDirectorySizes.svg,%1\toolbars\CalculateDirectorySizes.svg,0 +toolbars\CalculateOccupiedSpace.svg,%1\toolbars\CalculateOccupiedSpace.svg,0 +toolbars\ChangeAttributes.svg,%1\toolbars\ChangeAttributes.svg,0 +toolbars\ChangeCase.svg,%1\toolbars\ChangeCase.svg,0 +toolbars\ChangeDirectory.svg,%1\toolbars\ChangeDirectory.svg,0 +toolbars\ClipboardCopy.svg,%1\toolbars\ClipboardCopy.svg,0 +toolbars\ClipboardCut.svg,%1\toolbars\ClipboardCut.svg,0 +toolbars\ClipboardPaste.svg,%1\toolbars\ClipboardPaste.svg,0 +toolbars\CommandShell.svg,%1\toolbars\CommandShell.svg,0 +toolbars\CompareDirectories.svg,%1\toolbars\CompareDirectories.svg,0 +toolbars\Configuration.svg,%1\toolbars\Configuration.svg,0 +toolbars\ConnectNetworkDrive.svg,%1\toolbars\ConnectNetworkDrive.svg,0 +toolbars\Convert.svg,%1\toolbars\Convert.svg,0 +toolbars\Copy.svg,%1\toolbars\Copy.svg,0 +toolbars\CreateDirectory.svg,%1\toolbars\CreateDirectory.svg,0 +toolbars\Delete.svg,%1\toolbars\Delete.svg,0 +toolbars\Disconnect.svg,%1\toolbars\Disconnect.svg,0 +toolbars\DriveInformation.svg,%1\toolbars\DriveInformation.svg,0 +toolbars\Edit.svg,%1\toolbars\Edit.svg,0 +toolbars\EditNewFile.svg,%1\toolbars\EditNewFile.svg,0 +toolbars\Email.svg,%1\toolbars\Email.svg,0 +toolbars\Filter.svg,%1\toolbars\Filter.svg,0 +toolbars\FindFilesAndDirectories.svg,%1\toolbars\FindFilesAndDirectories.svg,0 +toolbars\FocusNameInOtherPanel.svg,%1\toolbars\FocusNameInOtherPanel.svg,0 +toolbars\Forward.svg,%1\toolbars\Forward.svg,0 +toolbars\GoToHotPath.svg,%1\toolbars\GoToHotPath.svg,0 +toolbars\GoToPathfromOtherPanel.svg,%1\toolbars\GoToPathfromOtherPanel.svg,0 +toolbars\GoToShortcutTarget.svg,%1\toolbars\GoToShortcutTarget.svg,0 +toolbars\HelpContents.svg,%1\toolbars\HelpContents.svg,0 +toolbars\HideSelectedNames.svg,%1\toolbars\HideSelectedNames.svg,0 +toolbars\HideUnselectedNames.svg,%1\toolbars\HideUnselectedNames.svg,0 +toolbars\Modify.svg,%1\toolbars\Modify.svg,0 +toolbars\Move.svg,%1\toolbars\Move.svg,0 +toolbars\MoveItemDown.svg,%1\toolbars\MoveItemDown.svg,0 +toolbars\MoveItemUp.svg,%1\toolbars\MoveItemUp.svg,0 +toolbars\New.svg,%1\toolbars\New.svg,0 +toolbars\NTFSCompress.svg,%1\toolbars\NTFSCompress.svg,0 +toolbars\NTFSUncompress.svg,%1\toolbars\NTFSUncompress.svg,0 +toolbars\OpenFolder.svg,%1\toolbars\OpenFolder.svg,0 +toolbars\OpenNameinOtherPanel.svg,%1\toolbars\OpenNameinOtherPanel.svg,0 +toolbars\Pack.svg,%1\toolbars\Pack.svg,0 +toolbars\ParentDirectory.svg,%1\toolbars\ParentDirectory.svg,0 +toolbars\PasteShortcut.svg,%1\toolbars\PasteShortcut.svg,0 +toolbars\Properties.svg,%1\toolbars\Properties.svg,0 +toolbars\QuickRename.svg,%1\toolbars\QuickRename.svg,0 +toolbars\Refresh.svg,%1\toolbars\Refresh.svg,0 +toolbars\RootDirectory.svg,%1\toolbars\RootDirectory.svg,0 +toolbars\Security.svg,%1\toolbars\Security.svg,0 +toolbars\SelectAll.svg,%1\toolbars\SelectAll.svg,0 +toolbars\SharedDirectories.svg,%1\toolbars\SharedDirectories.svg,0 +toolbars\ShowHiddenNames.svg,%1\toolbars\ShowHiddenNames.svg,0 +toolbars\SmartColumnMode.svg,%1\toolbars\SmartColumnMode.svg,0 +toolbars\SortByAttributes.svg,%1\toolbars\SortByAttributes.svg,0 +toolbars\SortByDate.svg,%1\toolbars\SortByDate.svg,0 +toolbars\SortByExtension.svg,%1\toolbars\SortByExtension.svg,0 +toolbars\SortByName.svg,%1\toolbars\SortByName.svg,0 +toolbars\SortBySize.svg,%1\toolbars\SortBySize.svg,0 +toolbars\SwapPanels.svg,%1\toolbars\SwapPanels.svg,0 +toolbars\Unpack.svg,%1\toolbars\Unpack.svg,0 +toolbars\UnselectAll.svg,%1\toolbars\UnselectAll.svg,0 +toolbars\UserMenu.svg,%1\toolbars\UserMenu.svg,0 +toolbars\View.svg,%1\toolbars\View.svg,0 +toolbars\Views.svg,%1\toolbars\Views.svg,0 +toolbars\WhatIsThis.svg,%1\toolbars\WhatIsThis.svg,0 [CreateShortcuts] 0,Altap Salamander 5.0,%1\salamand.exe, -1,Altap Salamander 5.0,%1\salamand.exe, +1,Altap Salamander 5.0,%1\salamand.exe, \ No newline at end of file diff --git a/tools/Create-Sfx.ps1 b/tools/Create-Sfx.ps1 new file mode 100644 index 0000000..34d62e2 --- /dev/null +++ b/tools/Create-Sfx.ps1 @@ -0,0 +1,226 @@ +<# +.SYNOPSIS +Creates a self-extracting executable (SFX) from a specified directory. + +.DESCRIPTION +The script compiles a minimal C# stub, packs files from the source directory, +and combines them into a single .exe file. It AUTOMATICALLY includes +the 'toolbars' directory from 'src\res\toolbars'. + +.PARAMETER SourceDir +Directory containing installer files (must include setup.exe). + +.PARAMETER OutputPath +Path to the output .exe file. + +.EXAMPLE +.\Create-Sfx.ps1 -SourceDir "Instalator" -OutputPath "Instalator_SFX.exe" +#> +param( + [Parameter(Mandatory=$true)] + [string]$SourceDir, + + [Parameter(Mandatory=$true)] + [string]$OutputPath +) + +$ErrorActionPreference = "Stop" + +# Check if source directory exists +if (-not (Test-Path $SourceDir)) { + Write-Error "Source directory '$SourceDir' does not exist." +} + +# Determine path to 'src\res\toolbars' relative to this script script location +$ScriptRoot = Split-Path $MyInvocation.MyCommand.Path +$ToolbarsSrcPath = Join-Path $ScriptRoot "..\src\res\toolbars" + +# Normalize path +if (Test-Path $ToolbarsSrcPath) { + # Resolve-Path returns a PathInfo object, so we specifically ask for the .Path string property + $ToolbarsSrcPath = (Resolve-Path $ToolbarsSrcPath).Path +} else { + Write-Warning "Toolbars directory not found at: $ToolbarsSrcPath. It will not be included." + $ToolbarsSrcPath = $null +} + +# --- 1. C# Code for the Stub --- +$csharpSource = @" +using System; +using System.Diagnostics; +using System.IO; +using System.IO.Compression; +using System.Reflection; +using System.Threading; + +[assembly: AssemblyTitle("SFX Installer")] +[assembly: AssemblyProduct("SFX Installer")] +[assembly: AssemblyVersion("1.0.0.0")] + +namespace SfxStub +{ + class Program + { + static void Main(string[] args) + { + string tempDir = Path.Combine(Path.GetTempPath(), "Install_" + Guid.NewGuid().ToString("N")); + string currentExe = Process.GetCurrentProcess().MainModule.FileName; + + try + { + // -- Step 1: Read ZIP Data -- + byte[] zipData; + using (var fs = new FileStream(currentExe, FileMode.Open, FileAccess.Read, FileShare.Read)) + { + if (fs.Length < 8) return; + + // Read offset (last 8 bytes) + fs.Seek(-8, SeekOrigin.End); + long zipStartOffset; + using (var br = new BinaryReader(fs, System.Text.Encoding.Default, true)) + { + zipStartOffset = br.ReadInt64(); + } + + // Calculate ZIP length (File Length - Start Offset - 8 bytes footer) + long zipLength = fs.Length - zipStartOffset - 8; + + if (zipLength <= 0) + { + throw new Exception("Invalid SFX file structure."); + } + + // Load ONLY ZIP data into memory + zipData = new byte[zipLength]; + fs.Seek(zipStartOffset, SeekOrigin.Begin); + fs.Read(zipData, 0, (int)zipLength); + } + + // -- Step 2: Extract -- + // Console.WriteLine("Extracting..."); + Directory.CreateDirectory(tempDir); + + using (var ms = new MemoryStream(zipData)) + using (var archive = new ZipArchive(ms, ZipArchiveMode.Read)) + { + archive.ExtractToDirectory(tempDir); + } + + // -- Step 3: Run setup.exe -- + string setupExe = Path.Combine(tempDir, "setup.exe"); + + if (!File.Exists(setupExe)) + { + string[] exeFiles = Directory.GetFiles(tempDir, "*.exe"); + if (exeFiles.Length > 0) setupExe = exeFiles[0]; + else throw new FileNotFoundException("setup.exe not found."); + } + + ProcessStartInfo psi = new ProcessStartInfo(setupExe); + psi.WorkingDirectory = tempDir; + if (args.Length > 0) { + psi.Arguments = string.Join(" ", args); + } + + Process p = Process.Start(psi); + if (p != null) p.WaitForExit(); + } + catch (Exception ex) + { + Console.WriteLine("Error: " + ex.Message); + Console.WriteLine("Press any key to exit..."); + Console.ReadKey(); + } + finally + { + // -- Step 4: Cleanup -- + try + { + if (Directory.Exists(tempDir)) Directory.Delete(tempDir, true); + } + catch { } + } + } + } +} +"@ + +# --- 2. Compile Stub --- +$stubPath = Join-Path $env:TEMP "sfx_stub.exe" +Write-Host "Compiling C# code..." -ForegroundColor Cyan + +$assemblies = @("System.IO.Compression", "System.IO.Compression.FileSystem") + +try { + Add-Type -TypeDefinition $csharpSource -OutputAssembly $stubPath -OutputType ConsoleApplication -ReferencedAssemblies $assemblies +} +catch { + Write-Error "Compilation error. Ensure .NET Framework is installed." +} + +# --- 3. Pack Files --- +$tempZip = [System.IO.Path]::GetTempFileName() +if (Test-Path $tempZip) { Remove-Item $tempZip } +$tempZip = $tempZip + ".zip" + +# Create a staging directory to combine SourceDir and Toolbars +$stagingDir = Join-Path $env:TEMP ("sfx_stage_" + [Guid]::NewGuid().ToString("N")) +New-Item -ItemType Directory -Path $stagingDir | Out-Null + +try { + Write-Host "Preparing files in staging area..." -ForegroundColor Cyan + + # Copy SourceDir content + Copy-Item -Path "$SourceDir\*" -Destination $stagingDir -Recurse -Force + + # Copy Toolbars (Hardcoded) + if ($ToolbarsSrcPath) { + $destPath = Join-Path $stagingDir "toolbars" + Write-Host "Auto-including toolbars from: $ToolbarsSrcPath" -ForegroundColor Cyan + + # Explicitly create destination directory + if (-not (Test-Path $destPath)) { + New-Item -ItemType Directory -Path $destPath | Out-Null + } + + # Copy CONTENT of toolbars into destPath + Copy-Item -Path "$ToolbarsSrcPath\*" -Destination $destPath -Recurse -Force + } + + Write-Host "Compressing contents of staging area:" -ForegroundColor Yellow + # List files relative to staging dir so user sees structure + Get-ChildItem -Path $stagingDir -Recurse | Select-Object -ExpandProperty FullName | ForEach-Object { $_.Substring($stagingDir.Length) } + + Write-Host "`nCompressing..." -ForegroundColor Cyan + Compress-Archive -Path "$stagingDir\*" -DestinationPath $tempZip -CompressionLevel Optimal +} +finally { + # Cleanup staging dir + if (Test-Path $stagingDir) { Remove-Item $stagingDir -Recurse -Force } +} + +# --- 4. Combine (Stub + Zip + Offset) --- +Write-Host "Creating output file: $OutputPath" -ForegroundColor Cyan + +try { + $stubBytes = [System.IO.File]::ReadAllBytes($stubPath) + $zipBytes = [System.IO.File]::ReadAllBytes($tempZip) + + $offset = $stubBytes.Length + $offsetBytes = [System.BitConverter]::GetBytes([long]$offset) + + $fs = [System.IO.File]::Create($OutputPath) + $fs.Write($stubBytes, 0, $stubBytes.Length) + $fs.Write($zipBytes, 0, $zipBytes.Length) + $fs.Write($offsetBytes, 0, $offsetBytes.Length) + $fs.Close() + + Write-Host "Done! Created: $OutputPath" -ForegroundColor Green +} +catch { + Write-Error "Error combining files: $_ " +} +finally { + if (Test-Path $stubPath) { Remove-Item $stubPath } + if (Test-Path $tempZip) { Remove-Item $tempZip } +} \ No newline at end of file From 0ca15213f24dc849a4a13f4425dde3e623bc3f7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Skaziak?= Date: Thu, 29 Jan 2026 11:12:37 +0100 Subject: [PATCH 3/3] ReadmeUpdate --- README.md | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/README.md b/README.md index 178307e..5b34e27 100644 --- a/README.md +++ b/README.md @@ -45,6 +45,33 @@ Solution ```\src\vcxproj\salamand.sln``` may be built from within Visual Studio Use ```\src\vcxproj\!populate_build_dir.cmd``` to populate build directory with files required to run Open Salamander. +### Creating SFX Installer + +To create a standalone self-extracting installer (EXE) for distribution: + +1. **Prepare files:** Ensure the `Instalator` directory contains the latest build of `salamand.exe`, `salmon.exe`, and other required files. +2. **Run the script:** Use the provided PowerShell script in the `tools` directory. + +```powershell +# Run from the project root +.\tools\Create-Sfx.ps1 -SourceDir "Instalator" -OutputPath "OpenSalamander_Setup.exe" +``` + +The script automatically: +- Compiles a C# bootstrap (stub) for extraction. +- Includes the latest SVG icons from `src\res\toolbars`. +- Modifies `setup.inf` (internally in the package) if necessary to ensure icons are installed. +- Produces a single `OpenSalamander_Setup.exe`. + +## Customization + +### Icons +Open Salamander uses scalable SVG icons for its toolbars. +- **Location:** `src\res\toolbars` +- **Format:** Standard SVG +- **Dimensions:** The standard viewbox is **16x16 pixels**. +- **Process:** To add or update an icon, simply place the `.svg` file in the `src\res\toolbars` directory. The build scripts (`!populate_build_dir.cmd` for local dev and `Create-Sfx.ps1` for installer) will automatically include them. + ### Execution Logging The execution logging system runs only in DEBUG builds. It records major application execution paths (startup, plugin loading, directory listing, file operations, and key UI features) through the Trace system. The logs are emitted as TRACE messages, so they appear in the Trace Server when it is connected. In release builds, the logging calls are compiled out and produce no output.