Skip to content
Open
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
bin/
build/
.build/
projects/*
!projects/*.sln
!projects/*.vcxproj
Expand Down
19 changes: 19 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,25 @@ to be added

clumsy must be run as Administrator because it uses WinDivert to intercept packets.

## Building on Windows

Install Visual Studio 2022 or Visual Studio Build Tools with the **Desktop development
with C++** workload, then run:

```bat
build.bat
```

This creates a 64-bit release build by default. Pass the configuration and platform
to select another build, for example `build.bat Debug x32`. The script locates
MSBuild, copies the required runtime files, writes a build log under `.build`, and
prints detected compiler or MSBuild errors when the build fails. When started by
double-clicking, a failed build waits for a key press so the error remains visible.
Set `BUILD_NO_PAUSE=1` before running the script to disable the pause for automation;
the script also disables it automatically when the `CI` environment variable is set.
Incremental builds reuse MSBuild outputs and skip the generated project's obsolete
absolute-path post-build commands.

## What's New In 4.0

- Native "Limit to application" controls in the Filtering panel.
Expand Down
152 changes: 152 additions & 0 deletions build.bat
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
@echo off
setlocal EnableExtensions

rem Build clumsy with Visual Studio 2022. Arguments are optional:
rem build.bat [Release^|Debug] [x64^|x32]

set "CONFIGURATION=%~1"
set "PLATFORM=%~2"
if not defined CONFIGURATION set "CONFIGURATION=Release"
if not defined PLATFORM set "PLATFORM=x64"

if /i not "%CONFIGURATION%"=="Release" if /i not "%CONFIGURATION%"=="Debug" goto :usage
if /i not "%PLATFORM%"=="x64" if /i not "%PLATFORM%"=="x32" goto :usage
set "MSBUILD_PLATFORM=%PLATFORM%"
if /i "%PLATFORM%"=="x32" set "MSBUILD_PLATFORM=Win32"

set "ROOT=%~dp0"
set "SOLUTION=%ROOT%projects\clumsy.sln"
set "OUTPUT=%ROOT%bin\vs\%CONFIGURATION%\%PLATFORM%\clumsy.exe"
set "LOGDIR=%ROOT%.build"
set "BUILDLOG=%LOGDIR%\msbuild-%CONFIGURATION%-%PLATFORM%.log"
set "BOOTLOG=%LOGDIR%\build-%CONFIGURATION%-%PLATFORM%.log"

if not exist "%LOGDIR%" mkdir "%LOGDIR%" 2>nul
if errorlevel 1 (
call :fail 1 "Could not create the build-log directory: %LOGDIR%"
exit /b 1
)
>"%BOOTLOG%" echo Build started %DATE% %TIME%
>>"%BOOTLOG%" echo Configuration: %CONFIGURATION%
>>"%BOOTLOG%" echo Platform: %MSBUILD_PLATFORM%
>>"%BOOTLOG%" echo Solution: %SOLUTION%

if not exist "%SOLUTION%" (
call :fail 1 "Visual Studio solution not found: %SOLUTION%"
exit /b 1
)

if /i "%PLATFORM%"=="x64" (
set "WINDIVERTDIR=%ROOT%external\WinDivert-2.2.0-A\x64"
set "IUPDIR=%ROOT%external\iup-3.30_Win64_dll16_lib"
) else (
set "WINDIVERTDIR=%ROOT%external\WinDivert-2.2.0-A\x86"
set "IUPDIR=%ROOT%external\iup-3.30_Win32_dll16_lib"
)

rem Fail before starting an expensive compile if the runtime package is incomplete.
if not exist "%WINDIVERTDIR%\WinDivert.dll" (
call :fail 1 "Missing runtime dependency: %WINDIVERTDIR%\WinDivert.dll"
exit /b 1
)
if not exist "%WINDIVERTDIR%\WinDivert*.sys" (
call :fail 1 "Missing WinDivert driver under: %WINDIVERTDIR%"
exit /b 1
)
if not exist "%IUPDIR%\iup.dll" (
call :fail 1 "Missing runtime dependency: %IUPDIR%\iup.dll"
exit /b 1
)
if not exist "%ROOT%etc\config.txt" (
call :fail 1 "Missing runtime dependency: %ROOT%etc\config.txt"
exit /b 1
)

set "MSBUILD="
set "VSWHERE=%ProgramFiles(x86)%\Microsoft Visual Studio\Installer\vswhere.exe"
for /f "delims=" %%I in ('where MSBuild.exe 2^>nul') do if not defined MSBUILD set "MSBUILD=%%I"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Select the VS 2022 MSBuild before PATH matches

When the script is run from an older Visual Studio developer prompt, where can select that prompt's MSBuild and prevent the vswhere fallback from running, even if VS 2022 is installed. In the checked projects/clumsy.vcxproj, every configuration requires the v143 toolset, so an older MSBuild can fail with an unavailable-toolset error despite a compatible installation being present; prefer vswhere or validate the PATH candidate before accepting it.

Useful? React with 👍 / 👎.

if not defined MSBUILD (
if exist "%VSWHERE%" (
for /f "usebackq delims=" %%I in (`"%VSWHERE%" -latest -products * -requires Microsoft.Component.MSBuild -find MSBuild\**\Bin\MSBuild.exe`) do if not defined MSBUILD set "MSBUILD=%%I"
)
)

if not defined MSBUILD (
>>"%BOOTLOG%" echo MSBuild discovery failed.
call :fail 1 "MSBuild was not found. Install Visual Studio 2022 or Build Tools with the Desktop development with C++ workload."
exit /b 1
)
>>"%BOOTLOG%" echo MSBuild: %MSBUILD%

echo Building %CONFIGURATION%^|%MSBUILD_PLATFORM% with "%MSBUILD%"...
del /q "%BUILDLOG%" 2>nul
rem Disable the generated project's legacy absolute-path post-build commands.
rem This script performs the same copies below using paths relative to the checkout.
"%MSBUILD%" "%SOLUTION%" /m /nologo /verbosity:minimal /t:Build /p:Configuration=%CONFIGURATION% /p:Platform=%MSBUILD_PLATFORM% /p:PostBuildEventUseInBuild=false /fl /flp:"logfile=%BUILDLOG%;verbosity=normal"
set "RESULT=%ERRORLEVEL%"
>>"%BOOTLOG%" echo MSBuild exit code: %RESULT%

if not "%RESULT%"=="0" (
echo.
echo BUILD FAILED with exit code %RESULT%.
echo -------------------- detected issues --------------------
findstr /i /c:": error " /c:"fatal error" /c:"error MSB" "%BUILDLOG%"
if errorlevel 1 echo No individual error line was detected; inspect the complete log.
echo ---------------------------------------------------------
echo Complete build log: "%BUILDLOG%"
call :wait_on_error
exit /b %RESULT%
)

if not exist "%OUTPUT%" (
echo ERROR: MSBuild reported success, but the executable was not found:
echo "%OUTPUT%"
echo Complete build log: "%BUILDLOG%"
call :wait_on_error
exit /b 1
)

set "OUTPUTDIR=%ROOT%bin\vs\%CONFIGURATION%\%PLATFORM%"
copy /y "%WINDIVERTDIR%\WinDivert.dll" "%OUTPUTDIR%\" >nul
if errorlevel 1 goto :copy_failed
copy /y "%WINDIVERTDIR%\WinDivert*.sys" "%OUTPUTDIR%\" >nul
if errorlevel 1 goto :copy_failed
copy /y "%IUPDIR%\iup.dll" "%OUTPUTDIR%\" >nul
if errorlevel 1 goto :copy_failed
copy /y "%ROOT%etc\config.txt" "%OUTPUTDIR%\" >nul
if errorlevel 1 goto :copy_failed

echo.
echo BUILD SUCCEEDED
echo Executable: "%OUTPUT%"
echo Build log: "%BUILDLOG%"
exit /b 0

:usage
echo Usage: %~nx0 [Release^|Debug] [x64^|x32]
echo Set BUILD_NO_PAUSE=1 to prevent pausing after an error.
call :wait_on_error
exit /b 2

:copy_failed
echo ERROR: The executable was built, but a required runtime file could not be copied.
echo Verify the dependency files under "%ROOT%external" and review "%BUILDLOG%".
>>"%BOOTLOG%" echo Runtime dependency copy failed.
call :wait_on_error
exit /b 1

:fail
echo.
echo ERROR: %~2
if defined BOOTLOG >>"%BOOTLOG%" echo ERROR: %~2
if defined BOOTLOG echo Diagnostic log: "%BOOTLOG%"
call :wait_on_error
exit /b %~1

:wait_on_error
if defined CI exit /b 0
if defined BUILD_NO_PAUSE exit /b 0
echo.
echo Press any key to close this window...
pause >nul
exit /b 0
169 changes: 151 additions & 18 deletions tools/get_genie.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -6,49 +6,182 @@
This binary supports Visual Studio 2022 (vs2022), 2019, 2017, 2015, and MinGW (gmake).
.PARAMETER OutDir
Directory to save genie.exe. Defaults to the script's parent directory (tools/).
.PARAMETER SourceUrl
HTTPS URL of the GENie executable. This can point to a pinned revision or mirror.
.PARAMETER Force
Overwrite genie.exe if it already exists.
.PARAMETER ExpectedSha256
Optional SHA-256 checksum used to verify the downloaded executable.
.PARAMETER DownloadMethod
Downloader to use. Auto tries BITS first and then Invoke-WebRequest.
.PARAMETER TimeoutSec
Timeout, in seconds, for Invoke-WebRequest.
.PARAMETER KeepBackup
Keep the previous executable as genie.exe.bak when replacing it.
.PARAMETER PassThru
Return the installed genie.exe FileInfo object for use in automation.
.EXAMPLE
.\get_genie.ps1
Downloads genie.exe to the tools/ directory.
.EXAMPLE
.\get_genie.ps1 -Force
Re-downloads even if genie.exe already exists.
.EXAMPLE
.\get_genie.ps1 -SourceUrl https://example.test/genie.exe `
-ExpectedSha256 <64-character-hash> -PassThru
Download from a mirror, verify the result, and return the installed file.
#>
param(
[string]$OutDir = $PSScriptRoot,
[switch]$Force
[uri]$SourceUrl = 'https://github.com/bkaradzic/bx/raw/master/tools/bin/windows/genie.exe',
[switch]$Force,
[ValidatePattern('^[0-9a-fA-F]{64}$')]
[string]$ExpectedSha256,
[ValidateSet('Auto', 'Bits', 'WebRequest')]
[string]$DownloadMethod = 'Auto',
[ValidateRange(1, 3600)]
[int]$TimeoutSec = 120,
[switch]$KeepBackup,
[switch]$PassThru
)

$genieUrl = "https://github.com/bkaradzic/bx/raw/master/tools/bin/windows/genie.exe"
$outFile = Join-Path $OutDir "genie.exe"
$ErrorActionPreference = "Stop"

$tempFile = $null
$outFile = Join-Path $OutDir 'genie.exe'

if (Test-Path $outFile -PathType Leaf) {
if (-not $Force) {
Write-Host "genie.exe already exists at $outFile" -ForegroundColor Green
Write-Host "Use -Force to re-download." -ForegroundColor Yellow
exit 0
try {
if (-not $SourceUrl.IsAbsoluteUri -or $SourceUrl.Scheme -ne 'https') {
throw 'SourceUrl must be an absolute HTTPS URL.'
}
Write-Host "Overwriting existing genie.exe..." -ForegroundColor Yellow
}

Write-Host "Downloading GENie from $genieUrl ..." -ForegroundColor Cyan
if (-not (Test-Path $OutDir -PathType Container)) {
New-Item -ItemType Directory -Path $OutDir -Force | Out-Null
}

try {
# Try to use BITS if available (more reliable), fall back to Invoke-WebRequest
if (Get-Command Start-BitsTransfer -ErrorAction SilentlyContinue) {
Start-BitsTransfer -Source $genieUrl -Destination $outFile
$OutDir = (Resolve-Path -LiteralPath $OutDir).Path
$outFile = Join-Path $OutDir "genie.exe"

if (Test-Path $outFile -PathType Leaf) {
if (-not $Force) {
if ($ExpectedSha256) {
$existingSha256 = (Get-FileHash -LiteralPath $outFile -Algorithm SHA256).Hash
if ($existingSha256 -ne $ExpectedSha256) {
throw "Existing genie.exe checksum mismatch. Use -Force to replace it."
}
Write-Host "Existing genie.exe passed SHA-256 verification." -ForegroundColor Green
}
Write-Host "genie.exe already exists at $outFile" -ForegroundColor Green
Write-Host "Use -Force to re-download." -ForegroundColor Yellow
if ($PassThru) {
Get-Item -LiteralPath $outFile
}
exit 0
}
Write-Host "A valid download will replace $outFile." -ForegroundColor Yellow
}

Write-Host "Downloading GENie from $SourceUrl ..." -ForegroundColor Cyan

# Download to a temporary file so an interrupted request cannot replace a
# working copy with a partial executable. A unique name also permits
# concurrent invocations that target the same directory.
$tempFile = Join-Path $OutDir ("genie.{0}.download" -f ([guid]::NewGuid()))

$bitsAvailable = [bool](Get-Command Start-BitsTransfer -ErrorAction SilentlyContinue)
if ($DownloadMethod -eq 'Bits' -and -not $bitsAvailable) {
throw 'Start-BitsTransfer is not available. Use -DownloadMethod WebRequest.'
}

$downloaded = $false
if ($DownloadMethod -ne 'WebRequest' -and $bitsAvailable) {
try {
Start-BitsTransfer -Source $SourceUrl.AbsoluteUri -Destination $tempFile
$downloaded = $true
} catch {
if ($DownloadMethod -eq 'Bits') {
throw
}
Write-Warning "BITS download failed; retrying with Invoke-WebRequest: $_"
Remove-Item $tempFile -Force -ErrorAction SilentlyContinue
}
}

if (-not $downloaded) {
[Net.ServicePointManager]::SecurityProtocol =
[Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls12
Invoke-WebRequest -Uri $SourceUrl.AbsoluteUri -OutFile $tempFile -UseBasicParsing `
-TimeoutSec $TimeoutSec -Headers @{ 'User-Agent' = 'clumsy-get-genie' }
}

# GitHub can return an HTML error document, so confirm the download has the
# DOS header expected at the start of a Windows executable. FileStream is
# compatible with both Windows PowerShell 5.1 and modern PowerShell.
$stream = [IO.File]::OpenRead($tempFile)
try {
$firstByte = $stream.ReadByte()
$secondByte = $stream.ReadByte()
} finally {
$stream.Dispose()
}
if ($firstByte -ne 0x4D -or $secondByte -ne 0x5A) {
throw "The downloaded file is not a valid Windows executable."
}

# Validate the PE signature as well as the initial DOS marker. This rejects
# truncated files and arbitrary data that merely begins with "MZ".
$stream = [IO.File]::OpenRead($tempFile)
$reader = [IO.BinaryReader]::new($stream)
try {
if ($stream.Length -lt 64) {
throw 'The downloaded executable is truncated.'
}
$stream.Position = 0x3C
$peOffset = $reader.ReadUInt32()
if ($peOffset -gt ($stream.Length - 4)) {
throw 'The downloaded executable has an invalid PE header offset.'
}
$stream.Position = $peOffset
if ($reader.ReadUInt32() -ne 0x00004550) {
throw 'The downloaded file does not contain a valid PE signature.'
}
} finally {
$reader.Dispose()
$stream.Dispose()
}

if ($ExpectedSha256) {
$actualSha256 = (Get-FileHash -LiteralPath $tempFile -Algorithm SHA256).Hash
if ($actualSha256 -ne $ExpectedSha256) {
throw "Checksum mismatch. Expected $ExpectedSha256, received $actualSha256."
}
}

if (Test-Path $outFile -PathType Leaf) {
$backupFile = if ($KeepBackup) { "$outFile.bak" } else { $null }
if ($backupFile) {
Remove-Item -LiteralPath $backupFile -Force -ErrorAction SilentlyContinue
}
# File.Replace performs a same-volume atomic replacement on Windows.
[IO.File]::Replace($tempFile, $outFile, $backupFile)
} else {
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
Invoke-WebRequest -Uri $genieUrl -OutFile $outFile -UseBasicParsing
Move-Item -LiteralPath $tempFile -Destination $outFile
}
$tempFile = $null
Write-Host "genie.exe downloaded successfully to $outFile" -ForegroundColor Green
if ($PassThru) {
Get-Item -LiteralPath $outFile
}
} catch {
Write-Host "ERROR: Failed to download genie.exe: $_" -ForegroundColor Red
Write-Host ""
Write-Host "Alternative options:" -ForegroundColor Yellow
Write-Host "1. Build GENie from source: https://github.com/bkaradzic/GENie"
Write-Host "2. Download manually from: $genieUrl"
Write-Host "2. Download manually from: $SourceUrl"
Write-Host " and place it at: $outFile"
exit 1
} finally {
if ($tempFile) {
Remove-Item $tempFile -Force -ErrorAction SilentlyContinue
}
}