Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 67 additions & 2 deletions Instalator/setup.inf
Original file line number Diff line number Diff line change
@@ -1,15 +1,80 @@
[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

[CopyFiles]
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,
42 changes: 41 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -32,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.
Expand Down
226 changes: 226 additions & 0 deletions tools/Create-Sfx.ps1
Original file line number Diff line number Diff line change
@@ -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 }
}