Merge pull request #11 from sjackson0109/feature/csharp-migration
Feature/csharp migrationpull/4062/head
@ -0,0 +1,25 @@
|
||||
# CODEOWNERS
|
||||
# GitHub automatically requests a review from the listed owners whenever a PR
|
||||
# modifies files matching the pattern. More specific rules override less specific
|
||||
# ones (last match wins), so list broad patterns first.
|
||||
#
|
||||
# Docs: https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners
|
||||
|
||||
# Catch-all: maintainer reviews everything
|
||||
* @sjackson0109
|
||||
|
||||
# C++ DLL source
|
||||
src-x86-x64-Fusix/ @sjackson0109
|
||||
|
||||
# C# tools (installer, configurator, checker, offset-finder)
|
||||
src-csharp/ @sjackson0109
|
||||
|
||||
# WiX MSI packaging
|
||||
msi/ @sjackson0109
|
||||
|
||||
# CI/CD pipelines
|
||||
.github/ @sjackson0109
|
||||
|
||||
# Documentation
|
||||
docs/ @sjackson0109
|
||||
README.md @sjackson0109
|
||||
@ -0,0 +1,34 @@
|
||||
version: 2
|
||||
|
||||
updates:
|
||||
# Keep GitHub Actions pinned to their latest minor/patch releases
|
||||
- package-ecosystem: github-actions
|
||||
directory: /
|
||||
schedule:
|
||||
interval: weekly
|
||||
day: monday
|
||||
open-pull-requests-limit: 10
|
||||
groups:
|
||||
actions-minor:
|
||||
patterns: ["*"]
|
||||
|
||||
# Keep .NET SDK NuGet dependencies up to date (for src-csharp/)
|
||||
- package-ecosystem: nuget
|
||||
directory: /src-csharp
|
||||
schedule:
|
||||
interval: weekly
|
||||
day: monday
|
||||
open-pull-requests-limit: 10
|
||||
|
||||
# Monitor git submodules for upstream releases
|
||||
# Covers: src-csharp/RDPOffsetFinder (llccd/RDPWrapOffsetFinder)
|
||||
# src-csharp/RDPOffsetFinder/zydis (zyantific/zydis)
|
||||
# Note: gitsubmodule ecosystem is currently in Dependabot beta.
|
||||
# Enable once the feature is available for the repository, or track
|
||||
# submodule versions manually via the pinned SHA in .gitmodules.
|
||||
- package-ecosystem: gitsubmodule
|
||||
directory: /
|
||||
schedule:
|
||||
interval: weekly
|
||||
day: monday
|
||||
open-pull-requests-limit: 5
|
||||
@ -0,0 +1,776 @@
|
||||
name: Build and Release
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main, master]
|
||||
paths:
|
||||
- 'msi/rdpwrap.ini'
|
||||
- 'msi/rdpwrap-arm-kb.ini'
|
||||
- 'msi/**'
|
||||
- 'src-x86-x64-Fusix/**'
|
||||
- 'src-csharp/**'
|
||||
- '.github/workflows/build-and-release.yml'
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
description: 'Release tag (e.g. v2026.4.2). Leave blank to auto-generate from date.'
|
||||
required: false
|
||||
default: ''
|
||||
prerelease:
|
||||
description: 'Mark as pre-release'
|
||||
type: boolean
|
||||
required: false
|
||||
default: false
|
||||
|
||||
concurrency:
|
||||
group: release
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
# ────────────────────────────────────────────────────────────────────────────
|
||||
# Job dependency / parallelism map
|
||||
#
|
||||
# build-dll ───────────────► build-csharp ──────────► build-msi ──┐
|
||||
# build-offsetfinder ───────────────────────────────────────────► release
|
||||
# download-sergiye ───────────────────────────────────────────► release
|
||||
# build-dll ─┘
|
||||
# ────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
jobs:
|
||||
|
||||
# ── Job 1: Build rdpwrap.dll (x64, x86, ARM64) ────────────────────────────
|
||||
# No job dependencies — starts immediately.
|
||||
build-dll:
|
||||
name: Build rdpwrap DLL
|
||||
runs-on: windows-2022
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Setup MSBuild
|
||||
uses: microsoft/setup-msbuild@v3
|
||||
|
||||
- name: Build rdpwrap.dll (x64, Win32, ARM64)
|
||||
shell: pwsh
|
||||
run: |
|
||||
foreach ($platform in @('x64', 'Win32', 'ARM64')) {
|
||||
msbuild src-x86-x64-Fusix/RDPWrap.vcxproj `
|
||||
/p:Configuration=Release `
|
||||
/p:Platform="$platform" `
|
||||
/p:PlatformToolset=v143 `
|
||||
/p:WindowsTargetPlatformVersion=10.0 `
|
||||
/v:minimal
|
||||
}
|
||||
Copy-Item src-x86-x64-Fusix/Release/x64/rdpwrap.dll .\rdpwrap_x64.dll
|
||||
Copy-Item src-x86-x64-Fusix/Release/x86/rdpwrap.dll .\rdpwrap_x86.dll
|
||||
Copy-Item src-x86-x64-Fusix/Release/arm64/rdpwrap.dll .\rdpwrap_arm64.dll
|
||||
Write-Host "Built DLLs:"
|
||||
Get-Item .\rdpwrap_x64.dll, .\rdpwrap_x86.dll, .\rdpwrap_arm64.dll | Format-Table Name, Length
|
||||
|
||||
- name: Upload rdpwrap DLLs
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: rdpwrap-dlls
|
||||
path: |
|
||||
rdpwrap_x64.dll
|
||||
rdpwrap_x86.dll
|
||||
rdpwrap_arm64.dll
|
||||
if-no-files-found: error
|
||||
|
||||
|
||||
# ── Job 2: Build RDPWrapOffsetFinder (x64, x86) — parallel to build-dll ───
|
||||
# No job dependencies — starts immediately.
|
||||
build-offsetfinder:
|
||||
name: Build OffsetFinder
|
||||
runs-on: windows-2022
|
||||
outputs:
|
||||
finder_ver: ${{ steps.finder.outputs.finder_ver }}
|
||||
|
||||
steps:
|
||||
- name: Checkout repository (with submodules)
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Setup MSBuild
|
||||
uses: microsoft/setup-msbuild@v3
|
||||
|
||||
- name: Build RDPWrapOffsetFinder from submodule
|
||||
id: finder
|
||||
shell: pwsh
|
||||
run: |
|
||||
$version = (git -C src-csharp/RDPOffsetFinder describe --tags --always).Trim()
|
||||
Write-Host "Submodule version: $version"
|
||||
|
||||
foreach ($cfg in @(
|
||||
@{ Platform = 'x64'; ZydisCfg = 'Release MD DLL'; ZydisBin = 'ReleaseX64'; Arch = 'x64' }
|
||||
@{ Platform = 'Win32'; ZydisCfg = 'Release MD DLL'; ZydisBin = 'ReleaseX86'; Arch = 'x86' }
|
||||
)) {
|
||||
# Build Zydis DLL
|
||||
msbuild src-csharp/RDPOffsetFinder/zydis/msvc/zydis/Zydis.vcxproj `
|
||||
/p:Configuration="$($cfg.ZydisCfg)" `
|
||||
/p:Platform="$($cfg.Platform)" `
|
||||
/p:PlatformToolset=v143 `
|
||||
/v:minimal
|
||||
|
||||
# Build RDPWrapOffsetFinder
|
||||
msbuild src-csharp/RDPOffsetFinder/RDPWrapOffsetFinder/RDPWrapOffsetFinder.vcxproj `
|
||||
/p:Configuration=Release `
|
||||
/p:Platform="$($cfg.Platform)" `
|
||||
/p:PlatformToolset=v143 `
|
||||
/v:minimal
|
||||
|
||||
$arch = $cfg.Arch
|
||||
$exeSrc = "src-csharp/RDPOffsetFinder/RDPWrapOffsetFinder/$($cfg.Platform)/Release/RDPWrapOffsetFinder.exe"
|
||||
$dllSrc = "src-csharp/RDPOffsetFinder/zydis/msvc/bin/$($cfg.ZydisBin)/Zydis.dll"
|
||||
Copy-Item $exeSrc ".\RDPWrapOffsetFinder_$arch.exe"
|
||||
Copy-Item $dllSrc ".\Zydis_$arch.dll"
|
||||
}
|
||||
|
||||
Write-Host "Built finder tools (version $version):"
|
||||
Get-Item .\RDPWrapOffsetFinder_*.exe, .\Zydis_*.dll | Format-Table Name, Length
|
||||
|
||||
echo "finder_ver=$version" >> $env:GITHUB_OUTPUT
|
||||
|
||||
- name: Upload OffsetFinder binaries
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: offsetfinder-binaries
|
||||
path: |
|
||||
RDPWrapOffsetFinder_x64.exe
|
||||
RDPWrapOffsetFinder_x86.exe
|
||||
Zydis_x64.dll
|
||||
Zydis_x86.dll
|
||||
if-no-files-found: error
|
||||
|
||||
|
||||
# ── Job 3: Download rdpWrapper GUI from sergiye — parallel to build-dll ────
|
||||
# No job dependencies — starts immediately.
|
||||
download-sergiye:
|
||||
name: Download sergiye rdpWrapper
|
||||
runs-on: windows-2022
|
||||
outputs:
|
||||
wrapper_ver: ${{ steps.wrapper.outputs.wrapper_ver }}
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Download rdpWrapper (sergiye)
|
||||
id: wrapper
|
||||
shell: pwsh
|
||||
run: |
|
||||
$apiUrl = "https://api.github.com/repos/sergiye/rdpWrapper/releases/latest"
|
||||
$release = Invoke-RestMethod -Uri $apiUrl -Headers @{ "User-Agent" = "rdpwrap-ci" }
|
||||
|
||||
foreach ($arch in @('x64', 'x86')) {
|
||||
$asset = $release.assets |
|
||||
Where-Object { $_.name -eq "rdpWrapper_$arch.exe" } |
|
||||
Select-Object -First 1
|
||||
if (-not $asset) {
|
||||
throw "Could not find rdpWrapper_$arch.exe in sergiye release $($release.tag_name)"
|
||||
}
|
||||
Write-Host "Downloading $($asset.browser_download_url)"
|
||||
Invoke-WebRequest -Uri $asset.browser_download_url -OutFile ".\rdpWrapper_$arch.exe"
|
||||
}
|
||||
|
||||
Write-Host "Wrapper exes:"
|
||||
Get-Item .\rdpWrapper_x64.exe, .\rdpWrapper_x86.exe | Format-Table Name, Length
|
||||
echo "wrapper_ver=$($release.tag_name)" >> $env:GITHUB_OUTPUT
|
||||
|
||||
# ── SHA-256 audit log for third-party binaries ──────────────────────────
|
||||
# Records the hash of every downloaded sergiye binary in the workflow log.
|
||||
# This provides a tamper-evident paper trail without blocking the release.
|
||||
- name: Audit SHA-256 of downloaded binaries
|
||||
shell: pwsh
|
||||
run: |
|
||||
$files = Get-Item .\rdpWrapper_x64.exe, .\rdpWrapper_x86.exe
|
||||
Write-Host "SHA-256 audit (third-party downloads from sergiye/rdpWrapper):"
|
||||
foreach ($f in $files) {
|
||||
$hash = (Get-FileHash $f.FullName -Algorithm SHA256).Hash
|
||||
Write-Host " $($f.Name) $hash"
|
||||
}
|
||||
|
||||
# ── Verify hashes against pinned values in tools/sergiye-hashes.json ────
|
||||
# If the file contains a non-empty 'release' key, hashes must match exactly.
|
||||
# If 'release' is empty this step is skipped (bootstrap scenario).
|
||||
- name: Verify sergiye binary hashes
|
||||
shell: pwsh
|
||||
run: |
|
||||
$pinFile = 'tools/sergiye-hashes.json'
|
||||
$pin = Get-Content $pinFile -Raw | ConvertFrom-Json
|
||||
if ([string]::IsNullOrEmpty($pin.release)) {
|
||||
Write-Host "Hash pinning not yet configured (tools/sergiye-hashes.json 'release' is empty) — skipping."
|
||||
Write-Host "To enable pinning: download the binaries, compute hashes with Get-FileHash, and update $pinFile."
|
||||
} else {
|
||||
Write-Host "Verifying against pinned release: $($pin.release)"
|
||||
$pass = $true
|
||||
foreach ($name in @('rdpWrapper_x64.exe', 'rdpWrapper_x86.exe')) {
|
||||
$expected = $pin.$name
|
||||
$actual = (Get-FileHash ".\$name" -Algorithm SHA256).Hash
|
||||
if ($actual -ne $expected) {
|
||||
Write-Error "Hash mismatch for $name!"
|
||||
Write-Error " Expected: $expected"
|
||||
Write-Error " Actual: $actual"
|
||||
$pass = $false
|
||||
} else {
|
||||
Write-Host " $name OK ($actual)"
|
||||
}
|
||||
}
|
||||
if (-not $pass) { throw "One or more sergiye binary hashes did not match the pinned values." }
|
||||
}
|
||||
|
||||
- name: Upload sergiye wrappers
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: sergiye-wrappers
|
||||
path: |
|
||||
rdpWrapper_x64.exe
|
||||
rdpWrapper_x86.exe
|
||||
if-no-files-found: error
|
||||
|
||||
|
||||
# ── Job 4: Build C# tools (FD + SC) — waits for build-dll ────────────────
|
||||
# Waits for build-dll so the DLLs can be embedded into single-file EXEs.
|
||||
build-csharp:
|
||||
name: Build C# Tools
|
||||
runs-on: windows-2022
|
||||
needs: [build-dll]
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Download rdpwrap DLLs
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: rdpwrap-dlls
|
||||
path: .
|
||||
|
||||
# Stage embedded resources for RDPWInst offline/bundled mode.
|
||||
# RDPWInst.csproj embeds these files when present (Condition="Exists(...)").
|
||||
# Copying them here ensures the single-file EXE can install without network.
|
||||
- name: Stage RDPWInst embedded resources
|
||||
shell: pwsh
|
||||
run: |
|
||||
$res = 'src-csharp/RDPWInst/Resources'
|
||||
New-Item -ItemType Directory -Path $res -Force | Out-Null
|
||||
Copy-Item .\rdpwrap_x86.dll "$res\rdpw32.dll" -Force
|
||||
Copy-Item .\rdpwrap_x64.dll "$res\rdpw64.dll" -Force
|
||||
Copy-Item msi\rdpwrap.ini "$res\rdpwrap.ini" -Force
|
||||
Copy-Item LICENSE "$res\license.txt" -Force
|
||||
Write-Host "Staged resources:"
|
||||
Get-ChildItem $res | Format-Table Name, Length
|
||||
|
||||
- name: Setup .NET SDK
|
||||
uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
dotnet-version: '10.x'
|
||||
|
||||
- name: Cache NuGet packages
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.nuget/packages
|
||||
key: nuget-${{ runner.os }}-${{ hashFiles('**/Directory.Build.props', '**/*.csproj') }}
|
||||
restore-keys: |
|
||||
nuget-${{ runner.os }}-
|
||||
|
||||
# Compute version once so it's consistent across all publish and sign steps
|
||||
- name: Get build version
|
||||
id: build_ver
|
||||
shell: pwsh
|
||||
run: |
|
||||
$v = Get-Date -Format "yyyy.M.d"
|
||||
echo "version=$v" >> $env:GITHUB_OUTPUT
|
||||
Write-Host "Build version: $v"
|
||||
|
||||
# ── Build framework-dependent variants ──────────────────────────────────
|
||||
- name: Publish C# tools (x64, x86, arm64)
|
||||
shell: pwsh
|
||||
env:
|
||||
DOTNET_NOLOGO: true
|
||||
DOTNET_CLI_TELEMETRY_OPTOUT: true
|
||||
run: |
|
||||
$projects = @('RDPWInst', 'RDPConf', 'RDPCheck')
|
||||
$rids = [ordered]@{ x64 = 'win-x64'; x86 = 'win-x86'; arm64 = 'win-arm64' }
|
||||
$ver = '${{ steps.build_ver.outputs.version }}'
|
||||
foreach ($proj in $projects) {
|
||||
foreach ($plat in $rids.Keys) {
|
||||
$rid = $rids[$plat]
|
||||
dotnet publish "src-csharp/$proj/$proj.csproj" `
|
||||
-c Release `
|
||||
-r $rid `
|
||||
-p:Platform=$plat `
|
||||
-p:PublishSingleFile=true `
|
||||
-p:SelfContained=false `
|
||||
-p:Version=$ver `
|
||||
-p:AssemblyVersion="$ver.0" `
|
||||
-p:FileVersion="$ver.0" `
|
||||
--output "dist_cs\$plat\$proj"
|
||||
Copy-Item "dist_cs\$plat\$proj\$proj.exe" ".\${proj}_${plat}.exe"
|
||||
}
|
||||
}
|
||||
Write-Host "C# tool executables:"
|
||||
Get-Item .\RDPWInst_*.exe, .\RDPConf_*.exe, .\RDPCheck_*.exe | Format-Table Name, Length
|
||||
|
||||
# ── Build self-contained variants (no .NET runtime required) ─────────────
|
||||
# Self-contained exes are larger (~60 MB) but work without a .NET install.
|
||||
# Suffixed with _sc to distinguish from the framework-dependent versions.
|
||||
- name: Publish self-contained C# tools (x64, x86, arm64)
|
||||
shell: pwsh
|
||||
env:
|
||||
DOTNET_NOLOGO: true
|
||||
DOTNET_CLI_TELEMETRY_OPTOUT: true
|
||||
run: |
|
||||
$projects = @('RDPWInst', 'RDPConf', 'RDPCheck')
|
||||
$rids = [ordered]@{ x64 = 'win-x64'; x86 = 'win-x86'; arm64 = 'win-arm64' }
|
||||
$ver = '${{ steps.build_ver.outputs.version }}'
|
||||
foreach ($proj in $projects) {
|
||||
foreach ($plat in $rids.Keys) {
|
||||
$rid = $rids[$plat]
|
||||
dotnet publish "src-csharp/$proj/$proj.csproj" `
|
||||
-c Release `
|
||||
-r $rid `
|
||||
-p:Platform=$plat `
|
||||
-p:PublishSingleFile=true `
|
||||
-p:SelfContained=true `
|
||||
-p:Version=$ver `
|
||||
-p:AssemblyVersion="$ver.0" `
|
||||
-p:FileVersion="$ver.0" `
|
||||
--output "dist_cs_sc\$plat\$proj"
|
||||
Copy-Item "dist_cs_sc\$plat\$proj\$proj.exe" ".\${proj}_${plat}_sc.exe"
|
||||
}
|
||||
}
|
||||
Write-Host "Self-contained executables:"
|
||||
Get-Item .\RDPWInst_*_sc.exe, .\RDPConf_*_sc.exe, .\RDPCheck_*_sc.exe | Format-Table Name, Length
|
||||
|
||||
# ── Optional: code-sign all C# executables ──────────────────────────────
|
||||
# Enable by setting repository variable USE_CERT_SIGNING=true and adding
|
||||
# secrets CODESIGN_CERT_BASE64 (PFX as base64) and CODESIGN_CERT_PASSWORD.
|
||||
- name: Sign C# executables
|
||||
if: vars.USE_CERT_SIGNING == 'true'
|
||||
env:
|
||||
CODESIGN_CERT_BASE64: ${{ secrets.CODESIGN_CERT_BASE64 }}
|
||||
CODESIGN_CERT_PASSWORD: ${{ secrets.CODESIGN_CERT_PASSWORD }}
|
||||
shell: pwsh
|
||||
run: |
|
||||
$pfxPath = "$env:RUNNER_TEMP\codesign.pfx"
|
||||
[IO.File]::WriteAllBytes($pfxPath,
|
||||
[Convert]::FromBase64String($env:CODESIGN_CERT_BASE64))
|
||||
$signtool = (Resolve-Path "${env:ProgramFiles(x86)}\Windows Kits\10\bin\*\x64\signtool.exe" |
|
||||
Sort-Object | Select-Object -Last 1).Path
|
||||
# Sign both framework-dependent and self-contained variants (glob picks up all _*.exe files)
|
||||
$exes = Get-Item .\RDPWInst_*.exe, .\RDPConf_*.exe, .\RDPCheck_*.exe
|
||||
foreach ($exe in $exes) {
|
||||
& $signtool sign /fd SHA256 /f $pfxPath `
|
||||
/p $env:CODESIGN_CERT_PASSWORD `
|
||||
/tr http://timestamp.digicert.com /td SHA256 $exe.FullName
|
||||
}
|
||||
Remove-Item $pfxPath -Force
|
||||
|
||||
- name: Upload C# tools
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: csharp-tools
|
||||
path: |
|
||||
RDPWInst_*.exe
|
||||
RDPConf_*.exe
|
||||
RDPCheck_*.exe
|
||||
if-no-files-found: error
|
||||
|
||||
|
||||
# ── Job 5: Build MSI packages — waits for build-csharp (implies build-dll) ─
|
||||
build-msi:
|
||||
name: Build MSI Packages
|
||||
runs-on: windows-2022
|
||||
needs: [build-csharp]
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Download rdpwrap DLLs
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: rdpwrap-dlls
|
||||
path: .
|
||||
|
||||
- name: Download C# tools
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: csharp-tools
|
||||
path: .
|
||||
|
||||
- name: Setup .NET SDK
|
||||
uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
dotnet-version: '10.x'
|
||||
|
||||
- name: Cache NuGet packages (WiX restore)
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.nuget/packages
|
||||
key: nuget-${{ runner.os }}-${{ hashFiles('msi/global.json', 'msi/*.wixproj') }}
|
||||
restore-keys: |
|
||||
nuget-${{ runner.os }}-
|
||||
|
||||
# Get version (same formula as build-csharp for filename consistency)
|
||||
- name: Get build version
|
||||
id: build_ver
|
||||
shell: pwsh
|
||||
run: |
|
||||
$v = Get-Date -Format "yyyy.M.d"
|
||||
echo "version=$v" >> $env:GITHUB_OUTPUT
|
||||
Write-Host "Build version: $v"
|
||||
|
||||
# ── Build MSI installers (WiX v5, x64 / x86 / arm64) ──────────────────
|
||||
- name: Build MSI packages
|
||||
shell: pwsh
|
||||
run: |
|
||||
$ver = '${{ steps.build_ver.outputs.version }}'
|
||||
foreach ($arch in @('x64', 'x86', 'arm64')) {
|
||||
# Stage arch-specific inputs next to the .wixproj
|
||||
Copy-Item ".\rdpwrap_$arch.dll" "msi\rdpwrap_$arch.dll" -Force
|
||||
Copy-Item ".\RDPWInst_$arch.exe" "msi\RDPWInst_$arch.exe" -Force
|
||||
Copy-Item ".\RDPConf_$arch.exe" "msi\RDPConf_$arch.exe" -Force
|
||||
Copy-Item ".\RDPCheck_$arch.exe" "msi\RDPCheck_$arch.exe" -Force
|
||||
|
||||
dotnet build msi/RDPWInst.wixproj `
|
||||
-c Release `
|
||||
/p:Platform=$arch `
|
||||
/p:OutputName="RDPWrapper-$arch" `
|
||||
/p:PackageVersion=$ver `
|
||||
/p:OutputPath="$PWD/msi_out/$arch"
|
||||
if ($LASTEXITCODE -ne 0) { throw "WiX build failed for $arch" }
|
||||
|
||||
$msiSrc = Get-Item "msi_out\$arch\RDPWrapper-$arch.msi" -ErrorAction SilentlyContinue
|
||||
if (-not $msiSrc) {
|
||||
$msiSrc = Get-ChildItem "msi\bin" -Recurse -Filter "RDPWrapper-$arch.msi" |
|
||||
Sort-Object LastWriteTime -Descending | Select-Object -First 1
|
||||
}
|
||||
if (-not $msiSrc) { throw "Could not locate RDPWrapper-$arch.msi after WiX build" }
|
||||
Copy-Item $msiSrc.FullName ".\RDPWrapper-${ver}-$arch.msi"
|
||||
Write-Host "Produced: RDPWrapper-${ver}-$arch.msi ($([math]::Round($msiSrc.Length/1MB,1)) MB)"
|
||||
}
|
||||
Write-Host "MSI packages:"
|
||||
Get-Item .\RDPWrapper-*-*.msi | Format-Table Name, Length
|
||||
|
||||
# ── Optional: sign the MSI packages ────────────────────────────────────
|
||||
# Enable by setting repository variable USE_CERT_SIGNING=true and adding
|
||||
# secrets CODESIGN_CERT_BASE64 (PFX as base64) and CODESIGN_CERT_PASSWORD.
|
||||
- name: Sign MSI packages
|
||||
if: vars.USE_CERT_SIGNING == 'true'
|
||||
env:
|
||||
CODESIGN_CERT_BASE64: ${{ secrets.CODESIGN_CERT_BASE64 }}
|
||||
CODESIGN_CERT_PASSWORD: ${{ secrets.CODESIGN_CERT_PASSWORD }}
|
||||
shell: pwsh
|
||||
run: |
|
||||
$pfxPath = "$env:RUNNER_TEMP\codesign.pfx"
|
||||
[IO.File]::WriteAllBytes($pfxPath,
|
||||
[Convert]::FromBase64String($env:CODESIGN_CERT_BASE64))
|
||||
$signtool = (Resolve-Path "${env:ProgramFiles(x86)}\Windows Kits\10\bin\*\x64\signtool.exe" |
|
||||
Sort-Object | Select-Object -Last 1).Path
|
||||
foreach ($msi in (Get-Item .\RDPWrapper-*-*.msi)) {
|
||||
& $signtool sign /fd SHA256 /f $pfxPath `
|
||||
/p $env:CODESIGN_CERT_PASSWORD `
|
||||
/tr http://timestamp.digicert.com /td SHA256 `
|
||||
$msi.FullName
|
||||
}
|
||||
Remove-Item $pfxPath -Force
|
||||
|
||||
- name: Upload MSI packages
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: msi-packages
|
||||
path: RDPWrapper-*-*.msi
|
||||
if-no-files-found: error
|
||||
|
||||
|
||||
# ── Job 6: Publish GitHub Release — waits for all build jobs ──────────────
|
||||
release:
|
||||
name: Publish Release
|
||||
runs-on: windows-2022
|
||||
needs: [build-dll, build-csharp, build-offsetfinder, download-sergiye, build-msi]
|
||||
# Attach to the 'release' GitHub Environment so repository admins can add
|
||||
# required-reviewer gates in Settings → Environments → release.
|
||||
# If the environment doesn't exist yet it will be created without gates.
|
||||
environment: release
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Download rdpwrap DLLs
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: rdpwrap-dlls
|
||||
path: .
|
||||
|
||||
- name: Download C# tools
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: csharp-tools
|
||||
path: .
|
||||
|
||||
- name: Download OffsetFinder binaries
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: offsetfinder-binaries
|
||||
path: .
|
||||
|
||||
- name: Download sergiye wrappers
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: sergiye-wrappers
|
||||
path: .
|
||||
|
||||
- name: Download MSI packages
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: msi-packages
|
||||
path: .
|
||||
|
||||
- name: Verify all release assets are present
|
||||
shell: pwsh
|
||||
run: |
|
||||
$required = @(
|
||||
'rdpwrap_x64.dll', 'rdpwrap_x86.dll', 'rdpwrap_arm64.dll',
|
||||
'RDPWInst_x64.exe', 'RDPWInst_x86.exe', 'RDPWInst_arm64.exe',
|
||||
'RDPConf_x64.exe', 'RDPConf_x86.exe', 'RDPConf_arm64.exe',
|
||||
'RDPCheck_x64.exe', 'RDPCheck_x86.exe', 'RDPCheck_arm64.exe',
|
||||
'RDPWrapOffsetFinder_x64.exe', 'RDPWrapOffsetFinder_x86.exe',
|
||||
'Zydis_x64.dll', 'Zydis_x86.dll',
|
||||
'rdpWrapper_x64.exe', 'rdpWrapper_x86.exe'
|
||||
)
|
||||
$missing = $required | Where-Object { -not (Test-Path $_) }
|
||||
if ($missing) {
|
||||
throw "Missing required release assets: $($missing -join ', ')"
|
||||
}
|
||||
Write-Host "All required assets present."
|
||||
Get-ChildItem *.exe, *.dll, *.msi | Format-Table Name, Length
|
||||
|
||||
# ── Extract the date string used for tagging and the updated field ───────
|
||||
- name: Get release metadata
|
||||
id: meta
|
||||
shell: pwsh
|
||||
run: |
|
||||
$date = Get-Date -Format "yyyy.MM.dd.HHmm"
|
||||
$stamp = Get-Date -Format "yyyy-MM-dd HH:mm UTC"
|
||||
|
||||
# Pull the Updated= line out of the INI to surface in release notes
|
||||
$iniContent = Get-Content msi/rdpwrap.ini -Raw
|
||||
$iniDate = if ($iniContent -match 'Updated=([^\r\n]+)') { $Matches[1] } else { 'unknown' }
|
||||
|
||||
$inputTag = '${{ inputs.tag }}'.Trim()
|
||||
$tagName = if ($inputTag -ne '') { $inputTag } else { "ini-$date" }
|
||||
$outputName = if ($inputTag -ne '') { "Release $inputTag" } else { "INI Update $date" }
|
||||
|
||||
echo "date=$date" >> $env:GITHUB_OUTPUT
|
||||
echo "stamp=$stamp" >> $env:GITHUB_OUTPUT
|
||||
echo "inidate=$iniDate" >> $env:GITHUB_OUTPUT
|
||||
echo "tag_name=$tagName" >> $env:GITHUB_OUTPUT
|
||||
echo "release_name=$outputName" >> $env:GITHUB_OUTPUT
|
||||
|
||||
# ── Validate the INI has required sections and mandatory per-version keys ─
|
||||
- name: Validate INI
|
||||
shell: pwsh
|
||||
run: |
|
||||
$ini = Get-Content msi/rdpwrap.ini -Raw
|
||||
$lines = Get-Content msi/rdpwrap.ini
|
||||
|
||||
# 1) Check mandatory top-level sections exist
|
||||
foreach ($section in @('[Main]', '[SLPolicy]', '[PatchCodes]')) {
|
||||
if ($ini -notmatch [regex]::Escape($section)) {
|
||||
throw "INI validation failed: missing required section $section"
|
||||
}
|
||||
}
|
||||
|
||||
# 2) Parse every Windows-version section and verify it contains the
|
||||
# mandatory keys LocalOnlyPatch and SLInitHook.
|
||||
$sectionKeys = [System.Collections.Generic.Dictionary[string, System.Collections.Generic.List[string]]]::new()
|
||||
$currentSection = $null
|
||||
|
||||
foreach ($line in $lines) {
|
||||
if ($line -match '^\[(\d[\d\.]+)\]') {
|
||||
$currentSection = $Matches[1]
|
||||
$sectionKeys[$currentSection] = [System.Collections.Generic.List[string]]::new()
|
||||
} elseif ($null -ne $currentSection -and $line -match '^([A-Za-z][A-Za-z0-9_]*)[\s]*=') {
|
||||
$sectionKeys[$currentSection].Add($Matches[1])
|
||||
}
|
||||
}
|
||||
|
||||
$mandatoryKeys = @('LocalOnlyPatch', 'SLInitHook')
|
||||
$errors = [System.Collections.Generic.List[string]]::new()
|
||||
|
||||
foreach ($sec in $sectionKeys.Keys) {
|
||||
foreach ($key in $mandatoryKeys) {
|
||||
if (-not $sectionKeys[$sec].Contains($key)) {
|
||||
$errors.Add(" Section [$sec] is missing mandatory key: $key")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($errors.Count -gt 0) {
|
||||
Write-Error "INI mandatory-key check failed ($($errors.Count) issue(s)):"
|
||||
$errors | ForEach-Object { Write-Error $_ }
|
||||
throw "INI validation failed — see errors above."
|
||||
}
|
||||
|
||||
$sectionCount = $sectionKeys.Count
|
||||
Write-Host "INI is valid. Windows-version sections: $sectionCount (all contain LocalOnlyPatch + SLInitHook)"
|
||||
|
||||
# ── Auto-generate changelog from merged PRs since last release ───────────
|
||||
- name: Generate changelog
|
||||
id: changelog
|
||||
shell: pwsh
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
# Get the tag of the previous release so we can scope the PR list
|
||||
$prevTag = (gh release list --limit 1 --json tagName --jq '.[0].tagName' 2>$null) ?? ''
|
||||
if ($prevTag) {
|
||||
Write-Host "Generating changelog since: $prevTag"
|
||||
$since = gh release view $prevTag --json publishedAt --jq .publishedAt
|
||||
$prs = gh pr list --state merged --base main --search "merged:>=$since" `
|
||||
--json number,title,author --limit 50 | ConvertFrom-Json
|
||||
} else {
|
||||
Write-Host "No previous release found; skipping per-PR changelog."
|
||||
$prs = @()
|
||||
}
|
||||
if ($prs.Count -gt 0) {
|
||||
$lines = $prs | ForEach-Object { "- #$($_.number) $($_.title) (@$($_.author.login))" }
|
||||
$body = $lines -join "`n"
|
||||
} else {
|
||||
$body = "_No merged pull requests found since the previous release._"
|
||||
}
|
||||
# Write multi-line output safely
|
||||
$delim = [guid]::NewGuid().ToString('N')
|
||||
echo "prs<<$delim" >> $env:GITHUB_OUTPUT
|
||||
echo $body >> $env:GITHUB_OUTPUT
|
||||
echo $delim >> $env:GITHUB_OUTPUT
|
||||
|
||||
# ── Assemble the three user-facing distribution bundles ──────────────────
|
||||
- name: Create distribution bundles
|
||||
shell: pwsh
|
||||
run: |
|
||||
# RDPWrapper.zip - complete install package (all arches, framework-dependent)
|
||||
$d = ".\bundle_wrapper"
|
||||
New-Item -ItemType Directory -Path $d -Force | Out-Null
|
||||
Copy-Item .\rdpwrap_x64.dll "$d\rdpwrap_x64.dll"
|
||||
Copy-Item .\rdpwrap_x86.dll "$d\rdpwrap_x86.dll"
|
||||
Copy-Item .\rdpwrap_arm64.dll "$d\rdpwrap_arm64.dll"
|
||||
Copy-Item .\rdpWrapper_x64.exe "$d\rdpWrapper_x64.exe"
|
||||
Copy-Item .\rdpWrapper_x86.exe "$d\rdpWrapper_x86.exe"
|
||||
# Framework-dependent C# tools (require .NET 10 Desktop Runtime)
|
||||
foreach ($proj in @('RDPWInst', 'RDPConf', 'RDPCheck')) {
|
||||
foreach ($arch in @('x64', 'x86', 'arm64')) {
|
||||
$src = ".\${proj}_${arch}.exe"
|
||||
if (Test-Path $src) { Copy-Item $src "$d\${proj}_${arch}.exe" }
|
||||
}
|
||||
}
|
||||
Copy-Item msi\rdpwrap.ini "$d\rdpwrap.ini"
|
||||
Compress-Archive -Path "$d\*" -DestinationPath ".\RDPWrapper.zip" -Force
|
||||
Remove-Item $d -Recurse -Force
|
||||
|
||||
# RDPWrapper-SelfContained.zip - apps bundled with .NET runtime (~60 MB each)
|
||||
$sc = ".\bundle_sc"
|
||||
New-Item -ItemType Directory -Path $sc -Force | Out-Null
|
||||
foreach ($proj in @('RDPWInst', 'RDPConf', 'RDPCheck')) {
|
||||
foreach ($arch in @('x64', 'x86', 'arm64')) {
|
||||
$src = ".\${proj}_${arch}_sc.exe"
|
||||
if (Test-Path $src) { Copy-Item $src "$sc\${proj}_${arch}_sc.exe" }
|
||||
}
|
||||
}
|
||||
Copy-Item msi\rdpwrap.ini "$sc\rdpwrap.ini"
|
||||
Compress-Archive -Path "$sc\*" -DestinationPath ".\RDPWrapper-SelfContained.zip" -Force
|
||||
Remove-Item $sc -Recurse -Force
|
||||
|
||||
# RDPWrapOffsetFinder.zip - offset finder with per-arch subfolders
|
||||
$f = ".\bundle_finder"
|
||||
foreach ($arch in @('x64', 'x86')) {
|
||||
$dir = "$f\$arch"
|
||||
New-Item -ItemType Directory -Path $dir -Force | Out-Null
|
||||
Copy-Item ".\RDPWrapOffsetFinder_$arch.exe" "$dir\RDPWrapOffsetFinder.exe"
|
||||
Copy-Item ".\Zydis_$arch.dll" "$dir\Zydis.dll"
|
||||
}
|
||||
Compress-Archive -Path "$f\*" -DestinationPath ".\RDPWrapOffsetFinder.zip" -Force
|
||||
Remove-Item $f -Recurse -Force
|
||||
|
||||
Write-Host "Distribution bundles:"
|
||||
Get-Item .\RDPWrapper.zip, .\RDPWrapper-SelfContained.zip, .\RDPWrapOffsetFinder.zip | Format-Table Name, Length
|
||||
|
||||
# ── Create/update a versioned GitHub Release with all assets ─────────────
|
||||
- name: Publish release
|
||||
# Pinned to v2 tag SHA for supply-chain safety.
|
||||
# Bump this SHA when upgrading: https://github.com/softprops/action-gh-release/releases
|
||||
uses: softprops/action-gh-release@153bb8e04406b158c6c84fc1615b65b24149a1fe # v2
|
||||
with:
|
||||
tag_name: "${{ steps.meta.outputs.tag_name }}"
|
||||
name: "${{ steps.meta.outputs.release_name }}"
|
||||
prerelease: ${{ inputs.prerelease || false }}
|
||||
make_latest: true
|
||||
body: |
|
||||
## RDP Wrapper - Automated Release
|
||||
|
||||
| Field | Value |
|
||||
|---|---|
|
||||
| INI `Updated` date | `${{ steps.meta.outputs.inidate }}` |
|
||||
| Published | ${{ steps.meta.outputs.stamp }} |
|
||||
| rdpWrapper (sergiye) | ${{ needs.download-sergiye.outputs.wrapper_ver }} |
|
||||
| RDPWrapOffsetFinder (llccd) | ${{ needs.build-offsetfinder.outputs.finder_ver }} |
|
||||
|
||||
> **Framework-dependent tools** (default) require **.NET 10 Desktop Runtime** on the target machine.
|
||||
> Download: https://dotnet.microsoft.com/download/dotnet/10.0
|
||||
> For environments without a runtime install, use the `*_sc.exe` files from `RDPWrapper-SelfContained.zip`.
|
||||
|
||||
### Downloads
|
||||
|
||||
| File | Contents | Use |
|
||||
|---|---|---|
|
||||
| `RDPWrapper.zip` | rdpwrap DLLs, C# tools (x64/x86/arm64), rdpWrapper GUI, rdpwrap.ini | **Main install package** — extract and run `RDPWInst_x64.exe -i -o` (or matching arch) |
|
||||
| `RDPWrapper-SelfContained.zip` | Same C# tools bundled with .NET runtime (no separate runtime install needed) | Use when .NET 10 Desktop Runtime cannot be deployed |
|
||||
| `RDPWrapper-<ver>-x64.msi` / `x86` / `arm64` | Windows Installer packages — GUI-friendly, self-registering uninstall | Preferred for managed/enterprise deployments |
|
||||
| `RDPWrapOffsetFinder.zip` | RDPWrapOffsetFinder.exe + Zydis.dll for x64 and x86 | Generate offsets for an unknown termsrv.dll version manually |
|
||||
|
||||
### Merged pull requests
|
||||
|
||||
${{ steps.changelog.outputs.prs }}
|
||||
|
||||
### Individual loose files (used by the automated installer)
|
||||
|
||||
| File | Purpose |
|
||||
|---|---|
|
||||
| `rdpwrap.ini` | Offset database fetched automatically by the installer |
|
||||
| `RDPWrapOffsetFinder_x64.exe` / `Zydis_x64.dll` | Loose x64 binaries downloaded by `TryAutoGenerateOffsets` |
|
||||
| `RDPWrapOffsetFinder_x86.exe` / `Zydis_x86.dll` | Loose x86 binaries downloaded by `TryAutoGenerateOffsets` |
|
||||
files: |
|
||||
RDPWrapper.zip
|
||||
RDPWrapper-SelfContained.zip
|
||||
RDPWrapper-*-x64.msi
|
||||
RDPWrapper-*-x86.msi
|
||||
RDPWrapper-*-arm64.msi
|
||||
RDPWrapOffsetFinder.zip
|
||||
msi/rdpwrap.ini
|
||||
msi/rdpwrap-arm-kb.ini
|
||||
rdpwrap_x64.dll
|
||||
rdpwrap_x86.dll
|
||||
rdpwrap_arm64.dll
|
||||
RDPWInst_x64.exe
|
||||
RDPWInst_x86.exe
|
||||
RDPWInst_arm64.exe
|
||||
RDPConf_x64.exe
|
||||
RDPConf_x86.exe
|
||||
RDPConf_arm64.exe
|
||||
RDPCheck_x64.exe
|
||||
RDPCheck_x86.exe
|
||||
RDPCheck_arm64.exe
|
||||
RDPWrapOffsetFinder_x64.exe
|
||||
RDPWrapOffsetFinder_x86.exe
|
||||
Zydis_x64.dll
|
||||
Zydis_x86.dll
|
||||
@ -0,0 +1,141 @@
|
||||
name: Build C# Tools
|
||||
|
||||
# Trigger on:
|
||||
# - PRs targeting main/master that touch C# source (compile check before merge)
|
||||
# - Version tag pushes (e.g. v1.7.0)
|
||||
# - Manual dispatch
|
||||
# Produces self-contained, single-file RDPWInst / RDPConf / RDPCheck
|
||||
# executables for x64 and x86.
|
||||
on:
|
||||
pull_request:
|
||||
branches: [main, master]
|
||||
paths:
|
||||
- 'src-csharp/**'
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
||||
DOTNET_NOLOGO: true
|
||||
DOTNET_CLI_TELEMETRY_OPTOUT: true
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
# ── Publish self-contained executables ────────────────────────────────────────
|
||||
build:
|
||||
runs-on: windows-2022
|
||||
strategy:
|
||||
matrix:
|
||||
platform: [x64, x86, arm64]
|
||||
include:
|
||||
- platform: x64
|
||||
rid: win-x64
|
||||
- platform: x86
|
||||
rid: win-x86
|
||||
- platform: arm64
|
||||
rid: win-arm64
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Setup .NET SDK
|
||||
uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
dotnet-version: '10.x'
|
||||
|
||||
- name: Cache NuGet packages
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.nuget/packages
|
||||
key: nuget-${{ runner.os }}-${{ hashFiles('**/Directory.Build.props', '**/*.csproj') }}
|
||||
restore-keys: |
|
||||
nuget-${{ runner.os }}-
|
||||
|
||||
- name: Publish RDPWInst (${{ matrix.platform }})
|
||||
run: |
|
||||
dotnet publish src-csharp/RDPWInst/RDPWInst.csproj `
|
||||
-c Release `
|
||||
-r ${{ matrix.rid }} `
|
||||
-p:Platform=${{ matrix.platform }} `
|
||||
-p:PublishSingleFile=true `
|
||||
-p:SelfContained=false `
|
||||
--output dist/${{ matrix.platform }}/RDPWInst
|
||||
|
||||
- name: Publish RDPConf (${{ matrix.platform }})
|
||||
run: |
|
||||
dotnet publish src-csharp/RDPConf/RDPConf.csproj `
|
||||
-c Release `
|
||||
-r ${{ matrix.rid }} `
|
||||
-p:Platform=${{ matrix.platform }} `
|
||||
-p:PublishSingleFile=true `
|
||||
-p:SelfContained=false `
|
||||
--output dist/${{ matrix.platform }}/RDPConf
|
||||
|
||||
- name: Publish RDPCheck (${{ matrix.platform }})
|
||||
run: |
|
||||
dotnet publish src-csharp/RDPCheck/RDPCheck.csproj `
|
||||
-c Release `
|
||||
-r ${{ matrix.rid }} `
|
||||
-p:Platform=${{ matrix.platform }} `
|
||||
-p:PublishSingleFile=true `
|
||||
-p:SelfContained=false `
|
||||
--output dist/${{ matrix.platform }}/RDPCheck
|
||||
|
||||
# ── Optional: code-sign all three executables ─────────────────────────────
|
||||
# Enable by setting repository variable USE_CERT_SIGNING=true and adding
|
||||
# secrets CODESIGN_CERT_BASE64 (PFX as base64) and CODESIGN_CERT_PASSWORD.
|
||||
- name: Sign executables (${{ matrix.platform }})
|
||||
if: vars.USE_CERT_SIGNING == 'true'
|
||||
env:
|
||||
CODESIGN_CERT_BASE64: ${{ secrets.CODESIGN_CERT_BASE64 }}
|
||||
CODESIGN_CERT_PASSWORD: ${{ secrets.CODESIGN_CERT_PASSWORD }}
|
||||
shell: pwsh
|
||||
run: |
|
||||
$pfxPath = "$env:RUNNER_TEMP\codesign.pfx"
|
||||
[IO.File]::WriteAllBytes($pfxPath,
|
||||
[Convert]::FromBase64String($env:CODESIGN_CERT_BASE64))
|
||||
|
||||
$exes = @(
|
||||
"dist\${{ matrix.platform }}\RDPWInst\RDPWInst.exe",
|
||||
"dist\${{ matrix.platform }}\RDPConf\RDPConf.exe",
|
||||
"dist\${{ matrix.platform }}\RDPCheck\RDPCheck.exe"
|
||||
)
|
||||
|
||||
$signtool = (Resolve-Path "${env:ProgramFiles(x86)}\Windows Kits\10\bin\*\x64\signtool.exe" |
|
||||
Sort-Object | Select-Object -Last 1).Path
|
||||
foreach ($exe in $exes) {
|
||||
& $signtool sign `
|
||||
/fd SHA256 `
|
||||
/f $pfxPath `
|
||||
/p $env:CODESIGN_CERT_PASSWORD `
|
||||
/tr http://timestamp.digicert.com `
|
||||
/td SHA256 `
|
||||
$exe
|
||||
}
|
||||
Remove-Item $pfxPath -Force
|
||||
|
||||
- name: Collect artifacts (${{ matrix.platform }})
|
||||
run: |
|
||||
Copy-Item dist/${{ matrix.platform }}/RDPWInst/RDPWInst.exe RDPWInst_${{ matrix.platform }}.exe
|
||||
Copy-Item dist/${{ matrix.platform }}/RDPConf/RDPConf.exe RDPConf_${{ matrix.platform }}.exe
|
||||
Copy-Item dist/${{ matrix.platform }}/RDPCheck/RDPCheck.exe RDPCheck_${{ matrix.platform }}.exe
|
||||
|
||||
- name: Upload ${{ matrix.platform }} artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: csharp-tools-${{ matrix.platform }}
|
||||
path: |
|
||||
RDPWInst_${{ matrix.platform }}.exe
|
||||
RDPConf_${{ matrix.platform }}.exe
|
||||
RDPCheck_${{ matrix.platform }}.exe
|
||||
if-no-files-found: error
|
||||
|
||||
# NOTE: Full GitHub Releases (including C# tools) are published by publish-ini.yml.
|
||||
# This workflow intentionally stops at the artifact upload step so that partial
|
||||
# releases are never created on the tag. Run publish-ini.yml manually or let it
|
||||
# fire from an INI push to produce the canonical release.
|
||||
@ -0,0 +1,73 @@
|
||||
name: Build MSI Check
|
||||
|
||||
# Lightweight PR check that validates msi/RDPWInst.wxs compiles cleanly.
|
||||
# We stub the binary inputs (DLL/EXE) with placeholder files so WiX can
|
||||
# validate the package structure without needing a full C++ / C# build.
|
||||
# For the real release build with actual binaries, see build-and-release.yml.
|
||||
on:
|
||||
pull_request:
|
||||
branches: [main, master]
|
||||
paths:
|
||||
- 'msi/**'
|
||||
- '.github/workflows/build-msi-check.yml'
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
wix-check:
|
||||
runs-on: windows-2022
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Setup .NET SDK
|
||||
uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
dotnet-version: '10.x'
|
||||
|
||||
- name: Cache NuGet packages
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.nuget/packages
|
||||
key: nuget-${{ runner.os }}-${{ hashFiles('**/Directory.Build.props', '**/*.csproj', '**/global.json') }}
|
||||
restore-keys: |
|
||||
nuget-${{ runner.os }}-
|
||||
|
||||
# Create minimal placeholder files so WiX can resolve all File references
|
||||
# in RDPWInst.wxs without requiring an actual C++ or C# build.
|
||||
# WiX v5 harvests file size at build time but does not validate binary content.
|
||||
- name: Create placeholder binary inputs
|
||||
shell: pwsh
|
||||
run: |
|
||||
$arch = 'x64'
|
||||
# Placeholder DLL and EXEs — WiX only needs the files to exist
|
||||
foreach ($name in @("rdpwrap_$arch.dll", "RDPWInst_$arch.exe", "RDPConf_$arch.exe", "RDPCheck_$arch.exe")) {
|
||||
$dest = "msi\$name"
|
||||
if (-not (Test-Path $dest)) {
|
||||
[System.IO.File]::WriteAllBytes($dest, [byte[]](0x4D, 0x5A)) # MZ stub
|
||||
Write-Host "Created stub: $dest"
|
||||
}
|
||||
}
|
||||
Write-Host "Placeholder files in msi\:"
|
||||
Get-ChildItem msi\ -File | Format-Table Name, Length
|
||||
|
||||
# Build only the x64 MSI — enough to validate WXS structure and WiX rules.
|
||||
# A failure here means a real WiX schema/syntax/reference error that would
|
||||
# also break the release build.
|
||||
- name: Build MSI (x64 WiX check)
|
||||
shell: pwsh
|
||||
run: |
|
||||
dotnet build msi/RDPWInst.wixproj `
|
||||
-c Release `
|
||||
/p:Platform=x64 `
|
||||
/p:PackageVersion=0.0.1 `
|
||||
/p:OutputPath="$PWD/msi_check_out/x64"
|
||||
if ($LASTEXITCODE -ne 0) { throw "WiX build failed — review errors above" }
|
||||
Write-Host "WiX check passed."
|
||||
Get-ChildItem msi_check_out -Recurse -Filter "*.msi" | Format-Table FullName, Length
|
||||
@ -0,0 +1,93 @@
|
||||
name: Build RDPWrapOffsetFinder
|
||||
|
||||
# Trigger on:
|
||||
# - PRs targeting main/master that touch the OffsetFinder submodule or Zydis
|
||||
# - Version tag pushes (e.g. v1.7.0)
|
||||
# - Manual dispatch
|
||||
on:
|
||||
pull_request:
|
||||
branches: [main, master]
|
||||
paths:
|
||||
- 'src-csharp/RDPOffsetFinder/**'
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
# ── Build x64 and Win32 release exes ─────────────────────────────────────────
|
||||
build:
|
||||
runs-on: windows-2022
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- platform: x64
|
||||
zydis_cfg: "Release MD DLL"
|
||||
zydis_bin: ReleaseX64
|
||||
finder_plat: x64
|
||||
- platform: Win32
|
||||
zydis_cfg: "Release MD DLL"
|
||||
zydis_bin: ReleaseX86
|
||||
finder_plat: Win32
|
||||
|
||||
steps:
|
||||
- name: Checkout repository (with submodules)
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Setup MSBuild
|
||||
uses: microsoft/setup-msbuild@v3
|
||||
|
||||
# Build Zydis as a shared DLL so we can ship Zydis.dll alongside the exe
|
||||
- name: Build Zydis (${{ matrix.platform }})
|
||||
working-directory: src-csharp/RDPOffsetFinder
|
||||
run: |
|
||||
msbuild zydis\msvc\zydis\Zydis.vcxproj `
|
||||
/p:Configuration="${{ matrix.zydis_cfg }}" `
|
||||
/p:Platform="${{ matrix.platform }}" `
|
||||
/p:PlatformToolset=v143 `
|
||||
/v:minimal
|
||||
|
||||
# Build the offset finder (links against the Zydis import lib just produced)
|
||||
- name: Build RDPWrapOffsetFinder (${{ matrix.platform }})
|
||||
working-directory: src-csharp/RDPOffsetFinder
|
||||
run: |
|
||||
msbuild RDPWrapOffsetFinder\RDPWrapOffsetFinder.vcxproj `
|
||||
/p:Configuration=Release `
|
||||
/p:Platform="${{ matrix.platform }}" `
|
||||
/p:PlatformToolset=v143 `
|
||||
/v:minimal
|
||||
|
||||
- name: Collect outputs
|
||||
shell: pwsh
|
||||
run: |
|
||||
$arch = if ("${{ matrix.platform }}" -eq "Win32") { "x86" } else { "x64" }
|
||||
$root = "src-csharp/RDPOffsetFinder"
|
||||
$exe = "$root/RDPWrapOffsetFinder/${{ matrix.finder_plat }}/Release/RDPWrapOffsetFinder.exe"
|
||||
$dll = "$root/zydis/msvc/bin/${{ matrix.zydis_bin }}/Zydis.dll"
|
||||
|
||||
Write-Host "Exe: $(Get-Item $exe | Select-Object -Exp Length) bytes"
|
||||
Write-Host "Dll: $(Get-Item $dll | Select-Object -Exp Length) bytes"
|
||||
|
||||
Copy-Item $exe ".\RDPWrapOffsetFinder_$arch.exe"
|
||||
Copy-Item $dll ".\Zydis_$arch.dll"
|
||||
|
||||
- name: Upload ${{ matrix.platform }} artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: finder-${{ matrix.platform }}
|
||||
path: |
|
||||
RDPWrapOffsetFinder_*.exe
|
||||
Zydis_*.dll
|
||||
if-no-files-found: error
|
||||
|
||||
# NOTE: Full GitHub Releases (including OffsetFinder binaries) are published by build-and-release.yml.
|
||||
# This workflow stops at the artifact upload step so that partial releases are never
|
||||
# created on tag pushes. Run build-and-release.yml to produce the canonical release.
|
||||
@ -1,188 +0,0 @@
|
||||
name: Publish INI and Offset Finder Tools
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main, master]
|
||||
paths:
|
||||
- 'res/rdpwrap.ini'
|
||||
- 'tools/RDPWrapOffsetFinder/**'
|
||||
- '.github/workflows/publish-ini.yml'
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
runs-on: windows-2022
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
|
||||
# ── Build rdpwrap.dll for both x64 and Win32 ────────────────────────────
|
||||
- name: Setup MSBuild
|
||||
uses: microsoft/setup-msbuild@v3
|
||||
|
||||
- name: Build rdpwrap.dll (x64 and Win32)
|
||||
shell: pwsh
|
||||
run: |
|
||||
foreach ($platform in @('x64', 'Win32')) {
|
||||
msbuild src-x86-x64-Fusix/RDPWrap.vcxproj `
|
||||
/p:Configuration=Release `
|
||||
/p:Platform="$platform" `
|
||||
/p:PlatformToolset=v143 `
|
||||
/p:WindowsTargetPlatformVersion=10.0 `
|
||||
/v:minimal
|
||||
}
|
||||
Copy-Item src-x86-x64-Fusix/x64/Release/rdpwrap.dll .\rdpwrap_x64.dll
|
||||
Copy-Item src-x86-x64-Fusix/Win32/Release/rdpwrap.dll .\rdpwrap_x86.dll
|
||||
Write-Host "Built DLLs:"
|
||||
Get-Item .\rdpwrap_x64.dll, .\rdpwrap_x86.dll | Format-Table Name, Length
|
||||
|
||||
# ── Extract the date string used for tagging and the updated field ──────
|
||||
- name: Get release metadata
|
||||
id: meta
|
||||
shell: pwsh
|
||||
run: |
|
||||
$date = Get-Date -Format "yyyy.MM.dd.HHmm"
|
||||
$stamp = Get-Date -Format "yyyy-MM-dd HH:mm UTC"
|
||||
|
||||
# Pull the Updated= line out of the INI to surface in release notes
|
||||
$iniContent = Get-Content res/rdpwrap.ini -Raw
|
||||
$iniDate = if ($iniContent -match 'Updated=([^\r\n]+)') { $Matches[1] } else { 'unknown' }
|
||||
|
||||
echo "date=$date" >> $env:GITHUB_OUTPUT
|
||||
echo "stamp=$stamp" >> $env:GITHUB_OUTPUT
|
||||
echo "inidate=$iniDate" >> $env:GITHUB_OUTPUT
|
||||
|
||||
# ── Stage RDPWrapOffsetFinder from committed tools/ ──────────────────────
|
||||
- name: Stage RDPWrapOffsetFinder from tools/
|
||||
id: finder
|
||||
shell: pwsh
|
||||
run: |
|
||||
$version = (Get-Content tools/RDPWrapOffsetFinder/VERSION -Raw).Trim()
|
||||
|
||||
Copy-Item tools/RDPWrapOffsetFinder/x64/RDPWrapOffsetFinder.exe .\RDPWrapOffsetFinder_x64.exe
|
||||
Copy-Item tools/RDPWrapOffsetFinder/x64/Zydis.dll .\Zydis_x64.dll
|
||||
Copy-Item tools/RDPWrapOffsetFinder/x86/RDPWrapOffsetFinder.exe .\RDPWrapOffsetFinder_x86.exe
|
||||
Copy-Item tools/RDPWrapOffsetFinder/x86/Zydis.dll .\Zydis_x86.dll
|
||||
|
||||
Write-Host "Staged finder tools (version $version):"
|
||||
Get-Item .\RDPWrapOffsetFinder_*.exe, .\Zydis_*.dll | Format-Table Name, Length
|
||||
|
||||
echo "finder_ver=$version" >> $env:GITHUB_OUTPUT
|
||||
|
||||
# ── Download the latest rdpWrapper GUI app from sergiye ─────────────────
|
||||
- name: Download rdpWrapper (sergiye)
|
||||
id: wrapper
|
||||
shell: pwsh
|
||||
run: |
|
||||
$apiUrl = "https://api.github.com/repos/sergiye/rdpWrapper/releases/latest"
|
||||
$release = Invoke-RestMethod -Uri $apiUrl -Headers @{ "User-Agent" = "rdpwrap-ci" }
|
||||
|
||||
foreach ($arch in @('x64', 'x86')) {
|
||||
$asset = $release.assets |
|
||||
Where-Object { $_.name -eq "rdpWrapper_$arch.exe" } |
|
||||
Select-Object -First 1
|
||||
if (-not $asset) {
|
||||
throw "Could not find rdpWrapper_$arch.exe in sergiye release $($release.tag_name)"
|
||||
}
|
||||
Write-Host "Downloading $($asset.browser_download_url)"
|
||||
Invoke-WebRequest -Uri $asset.browser_download_url -OutFile ".\rdpWrapper_$arch.exe"
|
||||
}
|
||||
|
||||
Write-Host "Wrapper exes:"
|
||||
Get-Item .\rdpWrapper_x64.exe, .\rdpWrapper_x86.exe | Format-Table Name, Length
|
||||
echo "wrapper_ver=$($release.tag_name)" >> $env:GITHUB_OUTPUT
|
||||
|
||||
# ── Validate the INI has required sections ───────────────────────────────
|
||||
- name: Validate INI
|
||||
shell: pwsh
|
||||
run: |
|
||||
$ini = Get-Content res/rdpwrap.ini -Raw
|
||||
foreach ($section in @('[Main]', '[SLPolicy]', '[PatchCodes]')) {
|
||||
if ($ini -notmatch [regex]::Escape($section)) {
|
||||
throw "INI validation failed: missing required section $section"
|
||||
}
|
||||
}
|
||||
$sectionCount = ([regex]::Matches($ini, '^\[[\d\.]+\]', 'Multiline')).Count
|
||||
Write-Host "INI is valid. Windows-version sections: $sectionCount"
|
||||
|
||||
# ── Assemble the two user-facing distribution bundles ────────────────────
|
||||
- name: Create distribution bundles
|
||||
shell: pwsh
|
||||
run: |
|
||||
# RDPWrapper.zip - complete install package
|
||||
$d = ".\bundle_wrapper"
|
||||
New-Item -ItemType Directory -Path $d -Force | Out-Null
|
||||
Copy-Item .\rdpwrap_x64.dll "$d\rdpwrap_x64.dll"
|
||||
Copy-Item .\rdpwrap_x86.dll "$d\rdpwrap_x86.dll"
|
||||
Copy-Item .\rdpWrapper_x64.exe "$d\rdpWrapper_x64.exe"
|
||||
Copy-Item .\rdpWrapper_x86.exe "$d\rdpWrapper_x86.exe"
|
||||
Copy-Item res\rdpwrap.ini "$d\rdpwrap.ini"
|
||||
Copy-Item bin\install.bat "$d\install.bat"
|
||||
Copy-Item bin\uninstall.bat "$d\uninstall.bat"
|
||||
Copy-Item bin\update.bat "$d\update.bat"
|
||||
Compress-Archive -Path "$d\*" -DestinationPath ".\RDPWrapper.zip" -Force
|
||||
Remove-Item $d -Recurse -Force
|
||||
|
||||
# RDPWrapOffsetFinder.zip - offset finder with per-arch subfolders
|
||||
$f = ".\bundle_finder"
|
||||
foreach ($arch in @('x64', 'x86')) {
|
||||
$dir = "$f\$arch"
|
||||
New-Item -ItemType Directory -Path $dir -Force | Out-Null
|
||||
Copy-Item ".\RDPWrapOffsetFinder_$arch.exe" "$dir\RDPWrapOffsetFinder.exe"
|
||||
Copy-Item ".\Zydis_$arch.dll" "$dir\Zydis.dll"
|
||||
}
|
||||
Compress-Archive -Path "$f\*" -DestinationPath ".\RDPWrapOffsetFinder.zip" -Force
|
||||
Remove-Item $f -Recurse -Force
|
||||
|
||||
Write-Host "Distribution bundles:"
|
||||
Get-Item .\RDPWrapper.zip, .\RDPWrapOffsetFinder.zip | Format-Table Name, Length
|
||||
|
||||
# ── Create/update a versioned GitHub Release with all assets ─────────────
|
||||
- name: Publish release
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
tag_name: "ini-${{ steps.meta.outputs.date }}"
|
||||
name: "INI Update ${{ steps.meta.outputs.date }}"
|
||||
prerelease: false
|
||||
make_latest: true
|
||||
body: |
|
||||
## RDP Wrapper - Automated Release
|
||||
|
||||
| Field | Value |
|
||||
|---|---|
|
||||
| INI `Updated` date | `${{ steps.meta.outputs.inidate }}` |
|
||||
| Published | ${{ steps.meta.outputs.stamp }} |
|
||||
| rdpWrapper (sergiye) | ${{ steps.wrapper.outputs.wrapper_ver }} |
|
||||
| RDPWrapOffsetFinder (llccd) | ${{ steps.finder.outputs.finder_ver }} |
|
||||
|
||||
### Downloads
|
||||
|
||||
| File | Contents | Use |
|
||||
|---|---|---|
|
||||
| `RDPWrapper.zip` | rdpwrap_x64.dll, rdpwrap_x86.dll, rdpWrapper_x64.exe, rdpWrapper_x86.exe, rdpwrap.ini, install/uninstall/update.bat | Main install package - extract and run `rdpWrapper_x64.exe` |
|
||||
| `RDPWrapOffsetFinder.zip` | x64\RDPWrapOffsetFinder.exe + Zydis.dll, x86\RDPWrapOffsetFinder.exe + Zydis.dll | Generate offsets for an unknown termsrv.dll version manually |
|
||||
|
||||
### Individual files (used by the automated installer)
|
||||
|
||||
| File | Purpose |
|
||||
|---|---|
|
||||
| `rdpwrap.ini` | Offset database fetched automatically by the installer |
|
||||
| `RDPWrapOffsetFinder_x64.exe` / `Zydis_x64.dll` | Loose x64 binaries downloaded by `TryAutoGenerateOffsets` |
|
||||
| `RDPWrapOffsetFinder_x86.exe` / `Zydis_x86.dll` | Loose x86 binaries downloaded by `TryAutoGenerateOffsets` |
|
||||
files: |
|
||||
RDPWrapper.zip
|
||||
RDPWrapOffsetFinder.zip
|
||||
res/rdpwrap.ini
|
||||
rdpwrap_x64.dll
|
||||
rdpwrap_x86.dll
|
||||
RDPWrapOffsetFinder_x64.exe
|
||||
RDPWrapOffsetFinder_x86.exe
|
||||
Zydis_x64.dll
|
||||
Zydis_x86.dll
|
||||
@ -1,120 +0,0 @@
|
||||
name: Update RDPWrapOffsetFinder tools
|
||||
|
||||
# Run manually to pull a new version of llccd/RDPWrapOffsetFinder into tools/.
|
||||
# The workflow commits the updated binaries and opens a pull request.
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
description: "llccd release tag to fetch (leave blank for latest)"
|
||||
required: false
|
||||
default: ""
|
||||
|
||||
env:
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
update:
|
||||
runs-on: windows-2022
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Download and extract RDPWrapOffsetFinder
|
||||
id: fetch
|
||||
shell: pwsh
|
||||
run: |
|
||||
$tag = "${{ github.event.inputs.tag }}"
|
||||
if ($tag -eq "") {
|
||||
$apiUrl = "https://api.github.com/repos/llccd/RDPWrapOffsetFinder/releases/latest"
|
||||
} else {
|
||||
$apiUrl = "https://api.github.com/repos/llccd/RDPWrapOffsetFinder/releases/tags/$tag"
|
||||
}
|
||||
|
||||
$release = Invoke-RestMethod -Uri $apiUrl -Headers @{ "User-Agent" = "rdpwrap-ci" }
|
||||
$version = $release.tag_name
|
||||
Write-Host "Fetching version $version"
|
||||
|
||||
$asset = $release.assets | Where-Object { $_.name -like "*.zip" } | Select-Object -First 1
|
||||
if (-not $asset) { throw "No zip asset found in release $version" }
|
||||
|
||||
Invoke-WebRequest -Uri $asset.browser_download_url -OutFile finder.zip -UseBasicParsing
|
||||
Expand-Archive -Path finder.zip -DestinationPath .\finder -Force
|
||||
|
||||
Write-Host "Zip contents:"
|
||||
Get-ChildItem -Recurse .\finder | Select-Object FullName, Length
|
||||
|
||||
# The llccd zip uses "64bit/" and "32bit/" subfolder names.
|
||||
# We pick the symbol-enabled exe (not the _nosymbol variant) from each arch folder.
|
||||
function Get-Bin($filter, $archHint) {
|
||||
$hits = Get-ChildItem -Recurse .\finder -Filter $filter |
|
||||
Where-Object { $_.FullName -match $archHint } |
|
||||
Where-Object { $_.Name -notmatch "nosymbol" }
|
||||
return ($hits | Select-Object -First 1)
|
||||
}
|
||||
|
||||
$x64exe = Get-Bin "RDPWrapOffsetFinder*.exe" "64bit"
|
||||
$x64dll = Get-Bin "Zydis*.dll" "64bit"
|
||||
$x86exe = Get-Bin "RDPWrapOffsetFinder*.exe" "32bit"
|
||||
$x86dll = Get-Bin "Zydis*.dll" "32bit"
|
||||
|
||||
# Fall back to x64 if the release only ships one arch
|
||||
if (-not $x86exe) { $x86exe = $x64exe }
|
||||
if (-not $x86dll) { $x86dll = $x64dll }
|
||||
if (-not $x64exe) { throw "Could not locate x64 exe" }
|
||||
if (-not $x64dll) { throw "Could not locate x64 Zydis.dll" }
|
||||
|
||||
New-Item -ItemType Directory -Path tools\RDPWrapOffsetFinder\x64 -Force | Out-Null
|
||||
New-Item -ItemType Directory -Path tools\RDPWrapOffsetFinder\x86 -Force | Out-Null
|
||||
|
||||
Copy-Item $x64exe.FullName tools\RDPWrapOffsetFinder\x64\RDPWrapOffsetFinder.exe -Force
|
||||
Copy-Item $x64dll.FullName tools\RDPWrapOffsetFinder\x64\Zydis.dll -Force
|
||||
Copy-Item $x86exe.FullName tools\RDPWrapOffsetFinder\x86\RDPWrapOffsetFinder.exe -Force
|
||||
Copy-Item $x86dll.FullName tools\RDPWrapOffsetFinder\x86\Zydis.dll -Force
|
||||
Set-Content -Path tools\RDPWrapOffsetFinder\VERSION -Value $version
|
||||
|
||||
Write-Host "Staged tools:"
|
||||
Get-ChildItem -Recurse tools\RDPWrapOffsetFinder | Select-Object FullName, Length
|
||||
|
||||
echo "version=$version" >> $env:GITHUB_OUTPUT
|
||||
|
||||
- name: Commit and push to a new branch
|
||||
id: push
|
||||
shell: pwsh
|
||||
run: |
|
||||
$version = "${{ steps.fetch.outputs.version }}"
|
||||
$branch = "chore/update-finder-tools-$version"
|
||||
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||
git checkout -b $branch
|
||||
git add tools/RDPWrapOffsetFinder
|
||||
if ((git diff --cached --name-only) -eq "") {
|
||||
Write-Host "No changes - tools already up to date"
|
||||
exit 0
|
||||
}
|
||||
git commit -m "chore: update RDPWrapOffsetFinder tools to $version"
|
||||
git push origin $branch
|
||||
echo "branch=$branch" >> $env:GITHUB_OUTPUT
|
||||
|
||||
- name: Create pull request
|
||||
if: steps.push.outputs.branch != ''
|
||||
shell: pwsh
|
||||
run: |
|
||||
$version = "${{ steps.fetch.outputs.version }}"
|
||||
$branch = "${{ steps.push.outputs.branch }}"
|
||||
gh pr create `
|
||||
--title "chore: update RDPWrapOffsetFinder tools to $version" `
|
||||
--body "Automated update of the committed RDPWrapOffsetFinder binaries in ``tools/RDPWrapOffsetFinder/`` to upstream release $version.
|
||||
|
||||
Triggered manually via the **Update RDPWrapOffsetFinder tools** workflow.
|
||||
Merge to include the updated binaries in the next INI release." `
|
||||
--base master `
|
||||
--head $branch
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
@ -0,0 +1,4 @@
|
||||
[submodule "src-csharp/RDPOffsetFinder"]
|
||||
path = src-csharp/RDPOffsetFinder
|
||||
url = https://github.com/llccd/RDPWrapOffsetFinder
|
||||
shallow = true
|
||||
@ -1,14 +0,0 @@
|
||||
@echo off
|
||||
if not exist "%~dp0RDPWInst.exe" goto :error
|
||||
"%~dp0RDPWInst" -i -o
|
||||
echo ______________________________________________________________
|
||||
echo.
|
||||
echo You can check RDP functionality with RDPCheck program.
|
||||
echo Also you can configure advanced settings with RDPConf program.
|
||||
echo.
|
||||
goto :anykey
|
||||
:error
|
||||
echo [-] Installer executable not found.
|
||||
echo Please extract all files from the downloaded package or check your anti-virus.
|
||||
:anykey
|
||||
pause
|
||||
@ -1,10 +0,0 @@
|
||||
@echo off
|
||||
if not exist "%~dp0RDPWInst.exe" goto :error
|
||||
"%~dp0RDPWInst" -u
|
||||
echo.
|
||||
goto :anykey
|
||||
:error
|
||||
echo [-] Installer executable not found.
|
||||
echo Please extract all files from the downloaded package or check your anti-virus.
|
||||
:anykey
|
||||
pause
|
||||
@ -1,10 +0,0 @@
|
||||
@echo off
|
||||
if not exist "%~dp0RDPWInst.exe" goto :error
|
||||
"%~dp0RDPWInst" -w
|
||||
echo.
|
||||
goto :anykey
|
||||
:error
|
||||
echo [-] Installer executable not found.
|
||||
echo Please extract all files from the downloaded package or check your anti-virus.
|
||||
:anykey
|
||||
pause
|
||||
@ -0,0 +1,100 @@
|
||||
# Code Signing Guide
|
||||
|
||||
Both `build-and-release.yml` and `build-csharp.yml` include a code-signing step that is already wired up and waiting. The step fires automatically as soon as the two secrets below are present in the repository — no workflow edits are needed.
|
||||
|
||||
---
|
||||
|
||||
## What gets signed
|
||||
|
||||
All six framework-dependent executables and all six self-contained executables produced per release:
|
||||
|
||||
| File | Contents |
|
||||
|---|---|
|
||||
| `RDPWInst_x64.exe`, `RDPWInst_x86.exe`, `RDPWInst_arm64.exe` | CLI installer |
|
||||
| `RDPConf_x64.exe`, `RDPConf_x86.exe`, `RDPConf_arm64.exe` | GUI configuration tool |
|
||||
| `RDPCheck_x64.exe`, `RDPCheck_x86.exe`, `RDPCheck_arm64.exe` | GUI connection tester |
|
||||
| `*_sc.exe` variants | Self-contained copies of the above |
|
||||
|
||||
The signing step runs on every `build-and-release.yml` and `build-csharp.yml` triggered build (tag push and manual dispatch), and is **skipped silently** when the secrets are absent.
|
||||
|
||||
---
|
||||
|
||||
## Obtaining a code-signing certificate
|
||||
|
||||
### Option A — Commercial certificate (recommended for public distribution)
|
||||
|
||||
Purchase an **EV (Extended Validation) Code Signing Certificate** from a trusted CA:
|
||||
|
||||
- [DigiCert Code Signing](https://www.digicert.com/signing/code-signing-certificates)
|
||||
- [Sectigo (Comodo)](https://sectigo.com/ssl-certificates-tls/code-signing)
|
||||
- [GlobalSign](https://www.globalsign.com/en/code-signing-certificate/)
|
||||
|
||||
> **Windows SmartScreen** initially blocks unsigned or low-reputation executables. A commercial EV certificate builds reputation immediately; a standard OV certificate requires time to accumulate reputation through user downloads. Self-signed certificates (Option B) will always trigger SmartScreen warnings.
|
||||
|
||||
### Option B — Self-signed certificate (testing / internal use only)
|
||||
|
||||
```powershell
|
||||
# Run in an elevated PowerShell session
|
||||
$cert = New-SelfSignedCertificate `
|
||||
-Type CodeSigningCert `
|
||||
-Subject "CN=RDP Wrapper" `
|
||||
-KeySpec Signature `
|
||||
-KeyAlgorithm RSA `
|
||||
-KeyLength 4096 `
|
||||
-HashAlgorithm SHA256 `
|
||||
-CertStoreLocation "Cert:\CurrentUser\My" `
|
||||
-NotAfter (Get-Date).AddYears(3)
|
||||
|
||||
# Export to PFX (set a strong password)
|
||||
$pw = Read-Host "PFX password" -AsSecureString
|
||||
Export-PfxCertificate -Cert $cert -FilePath "rdpwrap-codesign.pfx" -Password $pw
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Preparing the PFX for GitHub Actions
|
||||
|
||||
```powershell
|
||||
# Base64-encode the PFX so it can be stored as a secret
|
||||
$b64 = [Convert]::ToBase64String([IO.File]::ReadAllBytes(".\rdpwrap-codesign.pfx"))
|
||||
$b64 | Set-Clipboard
|
||||
Write-Host "Base64 PFX copied to clipboard"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Adding the secrets to GitHub
|
||||
|
||||
Navigate to: **Settings → Secrets and variables → Actions → New repository secret**
|
||||
|
||||
| Secret name | Value |
|
||||
|---|---|
|
||||
| `CODESIGN_CERT_BASE64` | Paste the base64-encoded PFX string |
|
||||
| `CODESIGN_CERT_PASSWORD` | The password chosen when exporting the PFX |
|
||||
|
||||
> ⚠️ **Security:** Never commit the `.pfx` file or raw base64 string to the repository. Revoke and reissue the certificate if it is accidentally exposed.
|
||||
|
||||
---
|
||||
|
||||
## Verifying a signed executable
|
||||
|
||||
After a signed release is published:
|
||||
|
||||
```powershell
|
||||
# Check signature status
|
||||
Get-AuthenticodeSignature .\RDPWInst_x64.exe | Format-List
|
||||
|
||||
# Expected output (commercial cert):
|
||||
# Status : Valid
|
||||
# SignerCertificate : [CN=Your Name, O=Your Org, ...]
|
||||
# TimeStamperCertificate : [CN=DigiCert Timestamp 2023, ...]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Renewing / rotating the certificate
|
||||
|
||||
1. Export the new PFX and base64-encode it (see above).
|
||||
2. Update both `CODESIGN_CERT_BASE64` and `CODESIGN_CERT_PASSWORD` via **Settings → Secrets**.
|
||||
3. No workflow changes are needed — the `Sign C# executables` step always reads from secrets at runtime.
|
||||
4. Remove the old PFX from your local machine and revoke the old certificate with the CA if it has not expired.
|
||||
@ -0,0 +1,101 @@
|
||||
# Submodule Update Guide
|
||||
|
||||
This repository includes one git submodule (with nested sub-submodules):
|
||||
|
||||
| Submodule | Upstream | Current tag |
|
||||
|---|---|---|
|
||||
| `src-csharp/RDPOffsetFinder` | [llccd/RDPWrapOffsetFinder](https://github.com/llccd/RDPWrapOffsetFinder) | `v0.9` |
|
||||
| `src-csharp/RDPOffsetFinder/zydis` | [zyantific/zydis](https://github.com/zyantific/zydis) | (pinned by upstream) |
|
||||
| `src-csharp/RDPOffsetFinder/zydis/dependencies/zycore` | [zyantific/zycore-c](https://github.com/zyantific/zycore-c) | (pinned by upstream) |
|
||||
|
||||
The nested `zydis` and `zycore` submodule commits are controlled by llccd's repository — update only the outer submodule and the inner ones follow automatically.
|
||||
|
||||
---
|
||||
|
||||
## Cloning with submodules
|
||||
|
||||
```bash
|
||||
# Full clone (recommended for builds)
|
||||
git clone --recurse-submodules https://github.com/sjackson0109/rdpwrap
|
||||
|
||||
# If you already cloned without --recurse-submodules:
|
||||
git submodule update --init --recursive
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Checking current submodule version
|
||||
|
||||
```powershell
|
||||
git submodule status --recursive
|
||||
# Expected output (one line per submodule):
|
||||
# 68da37acab6593c329776644944f55695a131731 src-csharp/RDPOffsetFinder (v0.9)
|
||||
# 5a68f639e4f01604cc7bfc8d313f583a8137e3d3 src-csharp/RDPOffsetFinder/zydis (...)
|
||||
# fb69402566a15a719e5df7a64a3db95105590b7e src-csharp/RDPOffsetFinder/zydis/dependencies/zycore (...)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Updating to a new upstream release
|
||||
|
||||
> **Do this only when a new tagged release of [llccd/RDPWrapOffsetFinder](https://github.com/llccd/RDPWrapOffsetFinder) is published** and you have verified that the new version produces correct INI sections for a known Windows build.
|
||||
|
||||
```powershell
|
||||
# 1. Fetch all tags from upstream
|
||||
cd src-csharp/RDPOffsetFinder
|
||||
git fetch --tags origin
|
||||
|
||||
# 2. List available tags to find the new release
|
||||
git tag --list --sort=-version:refname | Select-Object -First 10
|
||||
|
||||
# 3. Check out the desired tag
|
||||
git checkout <new-tag> # e.g. v1.0
|
||||
|
||||
# 4. Update nested submodules to match the new tag's references
|
||||
git submodule update --init --recursive
|
||||
|
||||
# 5. Return to repo root and record the new pointer
|
||||
cd ../..
|
||||
git add src-csharp/RDPOffsetFinder
|
||||
git commit -m "chore: update RDPOffsetFinder submodule to <new-tag>"
|
||||
git push
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Verifying the updated submodule builds
|
||||
|
||||
```powershell
|
||||
# From repo root — builds x64 and x86 offset finder binaries
|
||||
cd src-csharp/RDPOffsetFinder
|
||||
|
||||
cmake -B build-x64 -A x64 .
|
||||
cmake --build build-x64 --config Release
|
||||
|
||||
cmake -B build-x86 -A Win32 .
|
||||
cmake --build build-x86 --config Release
|
||||
```
|
||||
|
||||
Or trigger the `build-offsetfinder.yml` workflow on your push branch to let CI validate it.
|
||||
|
||||
---
|
||||
|
||||
## Rollback
|
||||
|
||||
If the update causes build failures, revert the submodule pointer:
|
||||
|
||||
```powershell
|
||||
git revert HEAD # creates a revert commit
|
||||
git push
|
||||
```
|
||||
|
||||
Or manually:
|
||||
|
||||
```powershell
|
||||
cd src-csharp/RDPOffsetFinder
|
||||
git checkout v0.9 # previous known-good tag
|
||||
cd ../..
|
||||
git add src-csharp/RDPOffsetFinder
|
||||
git commit -m "chore: rollback RDPOffsetFinder submodule to v0.9"
|
||||
git push
|
||||
```
|
||||
|
After Width: | Height: | Size: 108 KiB |
|
After Width: | Height: | Size: 26 KiB |
|
After Width: | Height: | Size: 29 KiB |
|
After Width: | Height: | Size: 7.1 KiB |
|
After Width: | Height: | Size: 15 KiB |
@ -0,0 +1,51 @@
|
||||
# Screenshots — `docs/images/`
|
||||
|
||||
This directory holds in-repo screenshot assets referenced by the project README.
|
||||
|
||||
---
|
||||
|
||||
## Files present
|
||||
|
||||
The following PNG screenshots are committed and referenced by the README:
|
||||
|
||||
| Filename | Contents |
|
||||
|---|---|
|
||||
| `RDPWrapperConfig.png` | RDPConf.exe configuration window |
|
||||
| `RDPWrapperCheck.png` | RDPCheck.exe showing green "Supported" status |
|
||||
| `RDPWrapperCheckWarning.png` | RDPCheck.exe showing "Warning" / not-yet-supported state |
|
||||
| `RDPWrapperMSI1.png` | MSI installer welcome / licence screen |
|
||||
| `RDPWrapperMSI2.png` | MSI installer completion screen |
|
||||
|
||||
---
|
||||
|
||||
## Files that would still be welcome
|
||||
|
||||
The following screenshots would improve documentation coverage but are not blocking any CI step:
|
||||
|
||||
| Filename | What to capture |
|
||||
|---|---|
|
||||
| `RDPWrapperConfig-advanced.png` | RDPConf.exe Advanced / License tab — SL policy and licensing status fields |
|
||||
| `rdpwrapper-gui.png` | sergiye/rdpWrapper GUI — main window with connection status |
|
||||
| `install-success.png` | Terminal output of `RDPWInst_x64.exe -i -o` completing successfully |
|
||||
|
||||
---
|
||||
|
||||
## Capture tips
|
||||
|
||||
- Use **1:1 scaling** (100 % DPI) so button labels are legible at small display size.
|
||||
- Crop to exclude desktop wallpaper — show only the tool window, with a 4 px neutral border all round.
|
||||
- Use PNG format (lossless); aim for < 200 KB each (resize if needed — 900 px wide is more than enough).
|
||||
- Redact any username, hostname, or licence strings visible in the window before committing.
|
||||
- Filename convention: `tool-state.png` — all lowercase, hyphen-delimited.
|
||||
|
||||
---
|
||||
|
||||
## Adding a new screenshot
|
||||
|
||||
```bash
|
||||
# After copying the PNG here:
|
||||
git add docs/images/<filename>.png
|
||||
git commit -m "docs: add <description> screenshot"
|
||||
```
|
||||
|
||||
The README references each file with a relative path, so the image will render automatically on GitHub once committed.
|
||||
@ -0,0 +1,24 @@
|
||||
<Project Sdk="WixToolset.Sdk/5.0.2">
|
||||
<!--
|
||||
RDP Wrapper MSI project — WiX v5 (dotnet build)
|
||||
|
||||
Build once per architecture:
|
||||
dotnet build msi/RDPWInst.wixproj -c Release /p:Platform=x64 /p:PackageVersion=yy.M.d
|
||||
dotnet build msi/RDPWInst.wixproj -c Release /p:Platform=x86 /p:PackageVersion=yy.M.d
|
||||
dotnet build msi/RDPWInst.wixproj -c Release /p:Platform=arm64 /p:PackageVersion=yy.M.d
|
||||
|
||||
Each build expects arch-specific inputs alongside this .wixproj:
|
||||
RDPWInst_<arch>.exe RDPConf_<arch>.exe RDPCheck_<arch>.exe
|
||||
rdpwrap_<arch>.dll rdpwrap.ini
|
||||
-->
|
||||
<PropertyGroup>
|
||||
<!-- OutputName includes arch so all three MSIs can coexist in the same output dir -->
|
||||
<OutputName>RDPWrapper-$(Platform)</OutputName>
|
||||
<!-- Version is overridden on the CLI: /p:PackageVersion=yy.M.d (e.g. 26.4.1) -->
|
||||
<PackageVersion>26.3.31</PackageVersion>
|
||||
<!-- Expose PackageVersion to the WiX preprocessor as $(var.PackageVersion) -->
|
||||
<DefineConstants>PackageVersion=$(PackageVersion)</DefineConstants>
|
||||
<!-- Suppress ICE warnings that do not apply to this package type -->
|
||||
<SuppressIces>ICE61</SuppressIces>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
@ -0,0 +1,127 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
RDP Wrapper — MSI installer definition (WiX v5 / schema v4)
|
||||
Parameterised by MSBuild Platform: build once per arch.
|
||||
|
||||
Build via:
|
||||
dotnet build msi/RDPWInst.wixproj -c Release /p:Platform=x64 /p:PackageVersion=yy.M.d
|
||||
dotnet build msi/RDPWInst.wixproj -c Release /p:Platform=x86 /p:PackageVersion=yy.M.d
|
||||
dotnet build msi/RDPWInst.wixproj -c Release /p:Platform=arm64 /p:PackageVersion=yy.M.d
|
||||
|
||||
Required inputs alongside this file (produced by CI / build-local.ps1):
|
||||
RDPWInst_<arch>.exe RDPConf_<arch>.exe RDPCheck_<arch>.exe
|
||||
rdpwrap_<arch>.dll
|
||||
rdpwrap.ini (shared — same file for all arches)
|
||||
-->
|
||||
<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
|
||||
|
||||
<!--
|
||||
$(var.Platform) is automatically set by WiX from the MSBuild Platform property.
|
||||
x86 installs to Program Files (x86); x64/arm64 install to Program Files.
|
||||
-->
|
||||
<?if $(var.Platform) = "x64" ?>
|
||||
<?define Arch = "x64" ?>
|
||||
<?define UpgradeCode = "6623f60c-e84f-41e7-a55b-f42116500064" ?>
|
||||
<?define ProgramFilesDir = "ProgramFiles6432Folder" ?>
|
||||
<?elseif $(var.Platform) = "x86" ?>
|
||||
<?define Arch = "x86" ?>
|
||||
<?define UpgradeCode = "6623f60c-e84f-41e7-a55b-f42116500032" ?>
|
||||
<?define ProgramFilesDir = "ProgramFilesFolder" ?>
|
||||
<?else ?>
|
||||
<?define Arch = "arm64" ?>
|
||||
<?define UpgradeCode = "6623f60c-e84f-41e7-a55b-f4211650aa64" ?>
|
||||
<?define ProgramFilesDir = "ProgramFiles6432Folder" ?>
|
||||
<?endif ?>
|
||||
|
||||
<Package
|
||||
Name="RDP Wrapper Library ($(var.Arch))"
|
||||
Version="$(var.PackageVersion)"
|
||||
Manufacturer="Simon Jackson (@sjackson0109)"
|
||||
UpgradeCode="$(var.UpgradeCode)"
|
||||
Scope="perMachine"
|
||||
InstallerVersion="500"
|
||||
Compressed="yes">
|
||||
|
||||
<!-- Allow upgrades, downgrades, and same-version reinstalls without blocking.
|
||||
Schedule=afterInstallInitialize ensures the old product is fully removed
|
||||
before new files are written, preventing component-ownership conflicts. -->
|
||||
<MajorUpgrade
|
||||
Schedule="afterInstallInitialize"
|
||||
AllowDowngrades="yes"
|
||||
IgnoreRemoveFailure="yes" />
|
||||
|
||||
<MediaTemplate EmbedCab="yes" />
|
||||
|
||||
<!-- ── Installation directory ─────────────────────────────────────────── -->
|
||||
<StandardDirectory Id="$(var.ProgramFilesDir)">
|
||||
<Directory Id="INSTALLFOLDER" Name="RDP Wrapper" />
|
||||
</StandardDirectory>
|
||||
|
||||
<!-- ── Feature ────────────────────────────────────────────────────────── -->
|
||||
<Feature Id="ProductFeature" Title="RDP Wrapper" Level="1">
|
||||
<ComponentGroupRef Id="ProductComponents" />
|
||||
</Feature>
|
||||
|
||||
<!-- ── Custom actions: run arch-native RDPWInst ──────────────────────── -->
|
||||
<!--
|
||||
-f (force): silently uninstalls any existing installation (bat- or
|
||||
MSI-managed) before re-installing. Prevents the "already installed"
|
||||
error when upgrading from a previous MSI or a manual install.
|
||||
-->
|
||||
<CustomAction
|
||||
Id="InstallWrap"
|
||||
FileRef="RDPWInstExe"
|
||||
ExeCommand="-i -o -f"
|
||||
Execute="deferred"
|
||||
Impersonate="no"
|
||||
Return="check" />
|
||||
|
||||
<CustomAction
|
||||
Id="UninstallWrap"
|
||||
FileRef="RDPWInstExe"
|
||||
ExeCommand="-u"
|
||||
Execute="deferred"
|
||||
Impersonate="no"
|
||||
Return="check" />
|
||||
|
||||
<InstallExecuteSequence>
|
||||
<!-- Run install wrapper after files land; skip only when THIS product is
|
||||
being removed as the OLD side of an upgrade (UPGRADINGPRODUCTCODE set). -->
|
||||
<Custom Action="InstallWrap" After="InstallFiles" Condition="NOT UPGRADINGPRODUCTCODE" />
|
||||
<Custom Action="UninstallWrap" Before="RemoveFiles" Condition="REMOVE AND NOT UPGRADINGPRODUCTCODE" />
|
||||
</InstallExecuteSequence>
|
||||
</Package>
|
||||
|
||||
<!-- ── Component group ─────────────────────────────────────────────────── -->
|
||||
<Fragment>
|
||||
<ComponentGroup Id="ProductComponents" Directory="INSTALLFOLDER">
|
||||
|
||||
<!-- RDPWInst (installer / uninstaller tool) -->
|
||||
<Component Id="RDPWInstExe_Comp" Guid="*">
|
||||
<File Id="RDPWInstExe" Source="RDPWInst_$(var.Arch).exe" KeyPath="yes" />
|
||||
</Component>
|
||||
|
||||
<!-- RDPConf (configuration GUI) -->
|
||||
<Component Id="RDPConfExe_Comp" Guid="*">
|
||||
<File Id="RDPConfExe" Source="RDPConf_$(var.Arch).exe" KeyPath="yes" />
|
||||
</Component>
|
||||
|
||||
<!-- RDPCheck (RDP loopback tester) -->
|
||||
<Component Id="RDPCheckExe_Comp" Guid="*">
|
||||
<File Id="RDPCheckExe" Source="RDPCheck_$(var.Arch).exe" KeyPath="yes" />
|
||||
</Component>
|
||||
|
||||
<!-- rdpwrap.dll (core DLL) -->
|
||||
<Component Id="RdpwrapDll_Comp" Guid="*">
|
||||
<File Source="rdpwrap_$(var.Arch).dll" KeyPath="yes" />
|
||||
</Component>
|
||||
|
||||
<!-- rdpwrap.ini (offset database — same file for all arches) -->
|
||||
<Component Id="RdpwrapIni_Comp" Guid="*">
|
||||
<File Source="rdpwrap.ini" KeyPath="yes" />
|
||||
</Component>
|
||||
|
||||
</ComponentGroup>
|
||||
</Fragment>
|
||||
|
||||
</Wix>
|
||||
@ -0,0 +1,5 @@
|
||||
{
|
||||
"msbuild-sdks": {
|
||||
"WixToolset.Sdk": "5.0.2"
|
||||
}
|
||||
}
|
||||
@ -1,49 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
|
||||
<?define ProductName="RDP Wrapper Library" ?>
|
||||
<?define ProductVersion="1.6.2" ?>
|
||||
<?define ProductCode="37ea5771-3352-4a52-9fac-9297331daebd"?>
|
||||
<?define UpgradeCode="6623f60c-e84f-41e7-a55b-f421165deeb5"?>
|
||||
<?define Manufacturer="Stas'M Corp. and contributors"?>
|
||||
|
||||
<Product Id="$(var.ProductCode)" Name="$(var.ProductName)" Language="1033" Version="$(var.ProductVersion)" Manufacturer="$(var.Manufacturer)" UpgradeCode="$(var.UpgradeCode)">
|
||||
<Package InstallerVersion="200" Compressed="yes"/>
|
||||
<Property Id="MSIFASTINSTALL" Value="1" />
|
||||
<Property Id="DISABLEROLLBACK" Value="1" />
|
||||
<MajorUpgrade AllowDowngrades="yes"/>
|
||||
|
||||
<Media Id="1" Cabinet="media1.cab" EmbedCab="yes"/>
|
||||
|
||||
<Directory Id="TARGETDIR" Name="SourceDir">
|
||||
<Directory Id="CommonAppDataFolder">
|
||||
<Directory Id="PACKAGECACHE" Name="Package Cache">
|
||||
<Directory Id="INSTALLLOCATION" Name="{$(var.ProductCode)}">
|
||||
<Component Id="RDPWrap" Guid="affd77d1-b35c-46f3-a97f-1686dc57b8b8">
|
||||
<File Id='RDPWInst' DiskId='1' Source='RDPWInst.exe'/>
|
||||
</Component>
|
||||
</Directory>
|
||||
</Directory>
|
||||
</Directory>
|
||||
</Directory>
|
||||
|
||||
<Feature Id="RDPWrapInstall" Title="RDPWrapSetup" Level="1">
|
||||
<ComponentRef Id="RDPWrap" />
|
||||
</Feature>
|
||||
|
||||
<CustomAction Id='InstallAction' FileKey='RDPWInst' ExeCommand='-i -o' Execute='immediate' Return='check'/>
|
||||
<CustomAction Id='UninstallAction' FileKey='RDPWInst' ExeCommand='-u' Execute='immediate' Return='check'/>
|
||||
<CustomAction Id='UpdateAction' FileKey='RDPWInst' ExeCommand='-w' Execute='immediate' Return='check'/>
|
||||
<!-- <CustomAction Id='ChangeAction' Directory='ProgramFilesFolder' ExeCommand='RDP Wrapper\RDPConf' Execute='immediate' Return='check'/>
|
||||
<CustomAction Id='RepairAction' Directory='ProgramFilesFolder' ExeCommand='RDP Wrapper\RDPCheck' Execute='immediate' Return='check'/> -->
|
||||
|
||||
<InstallExecuteSequence>
|
||||
<Custom Action='InstallAction' After='InstallFinalize'>NOT Installed AND NOT WIX_UPGRADE_DETECTED</Custom>
|
||||
<Custom Action='UninstallAction' Before="RemoveFiles">REMOVE AND NOT UPGRADINGPRODUCTCODE</Custom>
|
||||
<Custom Action='UpdateAction' Before="RemoveFiles">UPGRADINGPRODUCTCODE</Custom>
|
||||
<!-- <Custom Action='ChangeAction' After='InstallFinalize'>Installed AND NOT REINSTALL AND NOT UPGRADINGPRODUCTCODE AND NOT REMOVE</Custom>
|
||||
<Custom Action='RepairAction' After='InstallFinalize'>REINSTALL</Custom> -->
|
||||
</InstallExecuteSequence>
|
||||
|
||||
</Product>
|
||||
</Wix>
|
||||
@ -1,3 +0,0 @@
|
||||
@echo off
|
||||
"%ProgramFiles%\WiX Toolset v3.11\bin\candle" RDPWInst.wxs
|
||||
"%ProgramFiles%\WiX Toolset v3.11\bin\light" RDPWInst.wixobj
|
||||
@ -1,19 +0,0 @@
|
||||
@echo off
|
||||
echo [FILENAMES]> clearres.txt
|
||||
echo Exe=%1>> clearres.txt
|
||||
echo SaveAs=%1>> clearres.txt
|
||||
echo Log=>> clearres.txt
|
||||
echo.>> clearres.txt
|
||||
echo [COMMANDS]>> clearres.txt
|
||||
echo -delete RCDATA,CHARTABLE,>> clearres.txt
|
||||
echo -delete RCDATA,DVCLAL,>> clearres.txt
|
||||
echo -delete RCDATA,PACKAGEINFO,>> clearres.txt
|
||||
echo -delete CURSORGROUP,32761,>> clearres.txt
|
||||
echo -delete CURSORGROUP,32762,>> clearres.txt
|
||||
echo -delete CURSORGROUP,32763,>> clearres.txt
|
||||
echo -delete CURSORGROUP,32764,>> clearres.txt
|
||||
echo -delete CURSORGROUP,32765,>> clearres.txt
|
||||
echo -delete CURSORGROUP,32766,>> clearres.txt
|
||||
echo -delete CURSORGROUP,32767,>> clearres.txt
|
||||
"C:\Program Files\Resource Hacker\ResHacker.exe" -script clearres.txt
|
||||
del clearres.txt
|
||||
@ -1,92 +0,0 @@
|
||||
@echo off
|
||||
setlocal EnableDelayedExpansion
|
||||
echo RDP Wrapper Library Installer v1.0
|
||||
echo Copyright (C) Stas'M Corp. 2013
|
||||
echo.
|
||||
|
||||
set PROCESSOR_ARCHITECTURE | find "x86" > nul
|
||||
if !errorlevel!==0 (
|
||||
goto WOW64CHK
|
||||
) else (
|
||||
goto UNSUPPORTED
|
||||
)
|
||||
|
||||
:WOW64CHK
|
||||
echo [*] Check if running WOW64 subsystem...
|
||||
set PROCESSOR_ARCHITEW6432 > nul
|
||||
if !errorlevel!==0 (
|
||||
goto UNSUPPORTED
|
||||
) else (
|
||||
goto SUPPORTED
|
||||
)
|
||||
|
||||
:SUPPORTED
|
||||
echo [+] Processor architecture is Intel x86 [supported]
|
||||
goto INSTALL
|
||||
|
||||
:UNSUPPORTED
|
||||
echo [-] Unsupported processor architecture
|
||||
goto END
|
||||
|
||||
:INSTALL
|
||||
echo [*] Installing...
|
||||
if not exist rdpwrap.dll (
|
||||
echo [-] Error: rdpwrap.dll file not found
|
||||
goto END
|
||||
)
|
||||
echo [*] Copying file to Program Files...
|
||||
md "%ProgramFiles%\RDP Wrapper"
|
||||
xcopy /y rdpwrap.dll "%ProgramFiles%\RDP Wrapper\"
|
||||
if not !errorlevel!==0 (
|
||||
echo [-] Failed to copy rdpwrap.dll to Program Files folder
|
||||
goto END
|
||||
)
|
||||
echo [*] Modifying registry...
|
||||
reg add "HKLM\SYSTEM\CurrentControlSet\Services\TermService\Parameters" /v ServiceDll /t REG_EXPAND_SZ /d "%ProgramFiles%\RDP Wrapper\rdpwrap.dll" /f
|
||||
if not !errorlevel!==0 (
|
||||
echo [-] Failed to modify registry
|
||||
goto END
|
||||
)
|
||||
echo [*] Setting firewall configuration...
|
||||
reg add "HKLM\SYSTEM\CurrentControlSet\Control\Terminal Server" /v fDenyTSConnections /t REG_DWORD /d 0 /f
|
||||
netsh advfirewall firewall add rule name="Remote Desktop" dir=in protocol=tcp localport=3389 profile=any action=allow
|
||||
netsh advfirewall firewall add rule name="Remote Desktop" dir=in protocol=udp localport=3389 profile=any action=allow
|
||||
echo [*] Looking for TermService PID...
|
||||
tasklist /SVC /FI "SERVICES eq TermService" | find "PID" /V
|
||||
echo.
|
||||
if !errorlevel!==0 (
|
||||
goto DONE
|
||||
) else (
|
||||
goto SVCSTART
|
||||
)
|
||||
|
||||
:SVCSTART
|
||||
echo [*] TermService is stopped. Starting it...
|
||||
sc config TermService start= auto | find "1060" > nul
|
||||
if !errorlevel!==0 (
|
||||
echo [-] TermService is not installed. You need to install it manually.
|
||||
goto END
|
||||
) else (
|
||||
net start TermService
|
||||
goto DONE
|
||||
)
|
||||
|
||||
:DONE
|
||||
echo [+] Installation complete!
|
||||
echo Now reboot or restart service.
|
||||
echo.
|
||||
echo To reboot computer type:
|
||||
echo shutdown /r
|
||||
echo.
|
||||
echo To restart TermService type:
|
||||
echo taskkill /f /pid 1234 ^(replace 1234 with real PID which is shown above^)
|
||||
echo net start TermService
|
||||
echo.
|
||||
echo If second method is used, and there are another services sharing svchost.exe,
|
||||
echo you must start it too:
|
||||
echo net start Service1
|
||||
echo net start Service2
|
||||
echo etc.
|
||||
goto END
|
||||
|
||||
:END
|
||||
@ -0,0 +1,44 @@
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0-windows</TargetFramework>
|
||||
<Platforms>x86;x64;arm64</Platforms>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<PlatformTarget>$(Platform)</PlatformTarget>
|
||||
<!-- Ensure single-file publish works -->
|
||||
<SelfContained>false</SelfContained>
|
||||
<!-- Reproducible builds -->
|
||||
<Deterministic>true</Deterministic>
|
||||
|
||||
<!--
|
||||
Version is intentionally kept as a build-time default for local development.
|
||||
CI overrides these three properties on the dotnet publish command line:
|
||||
/p:Version=yyyy.M.d /p:AssemblyVersion=yyyy.M.d.0 /p:FileVersion=yyyy.M.d.0
|
||||
so released binaries always embed the correct date-based version.
|
||||
-->
|
||||
<Version>$([System.DateTime]::Now.ToString("yyyy.M.d"))</Version>
|
||||
<AssemblyVersion>$([System.DateTime]::Now.ToString("yyyy.M.d")).0</AssemblyVersion>
|
||||
<FileVersion>$([System.DateTime]::Now.ToString("yyyy.M.d")).0</FileVersion>
|
||||
<!-- Author / ownership metadata (embedded in every assembly) -->
|
||||
<Authors>Simon Jackson</Authors>
|
||||
<Company>Simon Jackson (@sjackson0109)</Company>
|
||||
<Copyright>Copyright © 2026 Simon Jackson (@sjackson0109)</Copyright>
|
||||
<Product>RDP Wrapper</Product>
|
||||
<RepositoryUrl>https://github.com/sjackson0109/rdpwrap</RepositoryUrl>
|
||||
<RepositoryType>git</RepositoryType>
|
||||
<PackageProjectUrl>https://github.com/sjackson0109/rdpwrap</PackageProjectUrl>
|
||||
|
||||
<!--
|
||||
Reproducible NuGet restores.
|
||||
RestorePackagesWithLockFile=true causes dotnet to generate packages.lock.json
|
||||
on the first restore if it doesn't exist, and to verify it on subsequent restores.
|
||||
Commit the generated packages.lock.json files in each project directory so that
|
||||
CI restores are deterministic. After committing lock files you can add
|
||||
`- -locked-mode` to dotnet restore/publish calls in CI to enforce exact-match
|
||||
and prevent silent dependency drift.
|
||||
-->
|
||||
<RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
@ -0,0 +1,160 @@
|
||||
// Copyright 2026 sjackson0109 — Apache License 2.0
|
||||
//
|
||||
// Hosts the MsRdpClient2 ActiveX control, connects to 127.0.0.2, and
|
||||
// reports the disconnect reason, mirroring the Delphi RDPDisconnected handler.
|
||||
|
||||
using RDPWrap.Common;
|
||||
|
||||
namespace RDPCheck;
|
||||
|
||||
internal sealed class MainForm : Form
|
||||
{
|
||||
private readonly AxRdpClient2 _rdp;
|
||||
|
||||
// Registry values saved on load — restored on disconnect
|
||||
private int _savedSecurityLayer;
|
||||
private int _savedUserAuthentication;
|
||||
|
||||
// ── Constructor / Layout ──────────────────────────────────────────────────
|
||||
|
||||
public MainForm()
|
||||
{
|
||||
SuspendLayout();
|
||||
|
||||
Text = "RDP Wrapper Check";
|
||||
ClientSize = new Size(800, 600);
|
||||
StartPosition = FormStartPosition.CenterScreen;
|
||||
FormBorderStyle = FormBorderStyle.Sizable;
|
||||
|
||||
_rdp = new AxRdpClient2
|
||||
{
|
||||
Dock = DockStyle.Fill
|
||||
};
|
||||
|
||||
Controls.Add(_rdp);
|
||||
ResumeLayout(false);
|
||||
|
||||
_rdp.Disconnected += OnRdpDisconnected;
|
||||
Load += OnFormLoad;
|
||||
}
|
||||
|
||||
// ── FormLoad — mirrors TFrm.FormCreate ────────────────────────────────────
|
||||
|
||||
private void OnFormLoad(object? sender, EventArgs e)
|
||||
{
|
||||
const string rdpTcpKey =
|
||||
@"SYSTEM\CurrentControlSet\Control\Terminal Server\WinStations\RDP-Tcp";
|
||||
|
||||
_rdp.DisconnectedText = "Disconnected.";
|
||||
_rdp.ConnectingText = "Connecting...";
|
||||
_rdp.ConnectedStatusText = "Connected.";
|
||||
_rdp.UserName = string.Empty;
|
||||
_rdp.Server = "127.0.0.2";
|
||||
|
||||
// Read, then zero-out SecurityLayer / UserAuthentication
|
||||
_savedSecurityLayer = RegistryHelper.ReadInt(rdpTcpKey, "SecurityLayer", 0);
|
||||
_savedUserAuthentication = RegistryHelper.ReadInt(rdpTcpKey, "UserAuthentication", 0);
|
||||
|
||||
try
|
||||
{
|
||||
RegistryHelper.WriteInt(rdpTcpKey, "SecurityLayer", 0);
|
||||
RegistryHelper.WriteInt(rdpTcpKey, "UserAuthentication", 0);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"[RDP] Registry write failed: {ex.Message}");
|
||||
}
|
||||
|
||||
// Read port
|
||||
int port = RegistryHelper.ReadInt(rdpTcpKey, "PortNumber", 3389);
|
||||
_rdp.SetPort(port);
|
||||
|
||||
// Subscribe to COM events now that the handle exists
|
||||
_rdp.SubscribeEvents();
|
||||
|
||||
// Brief delay then connect — matches Delphi Sleep(1000); RDP.Connect;
|
||||
Task.Delay(1000).ContinueWith(_ => Invoke((Action)_rdp.Connect));
|
||||
}
|
||||
|
||||
// ── OnDisconnected — mirrors TFrm.RDPDisconnected ─────────────────────────
|
||||
|
||||
private void OnRdpDisconnected(object? sender, int discReason)
|
||||
{
|
||||
string errStr = discReason switch
|
||||
{
|
||||
0x001 => "Local disconnection.",
|
||||
0x002 => "Disconnected by user.",
|
||||
0x003 => "Disconnected by server.",
|
||||
0x904 => "Socket closed.",
|
||||
0xC08 => "Decompress error.",
|
||||
0x108 => "Connection timed out.",
|
||||
0xC06 => "Decryption error.",
|
||||
0x104 => "DNS name lookup failure.",
|
||||
0x508 => "DNS lookup failed.",
|
||||
0xB06 => "Encryption error.",
|
||||
0x604 => "Windows Sockets gethostbyname() call failed.",
|
||||
0x208 => "Host not found error.",
|
||||
0x408 => "Internal error.",
|
||||
0x906 => "Internal security error.",
|
||||
0xA06 => "Internal security error.",
|
||||
0x506 => "The encryption method specified is not valid.",
|
||||
0x804 => "Bad IP address specified.",
|
||||
0x606 => "Server security data is not valid.",
|
||||
0x406 => "Security data is not valid.",
|
||||
0x308 => "The IP address specified is not valid.",
|
||||
0x808 => "License negotiation failed.",
|
||||
0x908 => "Licensing time-out.",
|
||||
0x106 => "Out of memory.",
|
||||
0x206 => "Out of memory.",
|
||||
0x306 => "Out of memory.",
|
||||
0x706 => "Failed to unpack server certificate.",
|
||||
0x204 => "Socket connection failed.",
|
||||
0x404 => "Windows Sockets recv() call failed.",
|
||||
0x704 => "Time-out occurred.",
|
||||
0x608 => "Internal timer error.",
|
||||
0x304 => "Windows Sockets send() call failed.",
|
||||
0xB07 => "The account is disabled.",
|
||||
0xE07 => "The account is expired.",
|
||||
0xD07 => "The account is locked out.",
|
||||
0xC07 => "The account is restricted.",
|
||||
0x1B07 => "The received certificate is expired.",
|
||||
0x1607 => "The policy does not support delegation of credentials to the target server.",
|
||||
0x2107 => "The server authentication policy does not allow connection requests using saved credentials. The user must enter new credentials.",
|
||||
0x807 => "Login failed.",
|
||||
0x1807 => "No authority could be contacted for authentication. The domain name of the authenticating party could be wrong, the domain could be unreachable, or there might have been a trust relationship failure.",
|
||||
0xA07 => "The specified user has no account.",
|
||||
0xF07 => "The password is expired.",
|
||||
0x1207 => "The user password must be changed before logging on for the first time.",
|
||||
0x1707 => "Delegation of credentials to the target server is not allowed unless mutual authentication has been achieved.",
|
||||
0x2207 => "The smart card is blocked.",
|
||||
0x1C07 => "An incorrect PIN was presented to the smart card.",
|
||||
0xB09 => "Network Level Authentication is required, run RDPCheck as administrator.",
|
||||
0x708 => "RDP is working, but the client doesn't allow loopback connections. Try to connect to your PC from another device in the network.",
|
||||
_ => $"Unknown code 0x{discReason:X}"
|
||||
};
|
||||
|
||||
if (discReason > 2)
|
||||
{
|
||||
MessageBox.Show(errStr, "Disconnected",
|
||||
MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
}
|
||||
|
||||
// Restore registry
|
||||
const string rdpTcpKey =
|
||||
@"SYSTEM\CurrentControlSet\Control\Terminal Server\WinStations\RDP-Tcp";
|
||||
try
|
||||
{
|
||||
RegistryHelper.WriteInt(rdpTcpKey, "SecurityLayer", _savedSecurityLayer);
|
||||
RegistryHelper.WriteInt(rdpTcpKey, "UserAuthentication", _savedUserAuthentication);
|
||||
}
|
||||
catch { }
|
||||
|
||||
Application.Exit();
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing) _rdp.Dispose();
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,15 @@
|
||||
// Copyright 2026 sjackson0109 — Apache License 2.0
|
||||
//
|
||||
// RDPCheck — WinForms RDP tester entry point.
|
||||
|
||||
namespace RDPCheck;
|
||||
|
||||
internal static class Program
|
||||
{
|
||||
[STAThread]
|
||||
internal static void Main()
|
||||
{
|
||||
ApplicationConfiguration.Initialize();
|
||||
Application.Run(new MainForm());
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,32 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<UseWindowsForms>true</UseWindowsForms>
|
||||
<RootNamespace>RDPCheck</RootNamespace>
|
||||
<AssemblyName>RDPCheck</AssemblyName>
|
||||
<ApplicationManifest>app.manifest</ApplicationManifest>
|
||||
<ApplicationIcon>app.ico</ApplicationIcon>
|
||||
<Description>WinForms RDP loopback connection tester — validates that RDP Wrapper is installed and accepting connections correctly.</Description>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Include="app.ico" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\RDPWrap\RDPWrap.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<!-- COM interop for AxMsRdpClient2 — generated via:
|
||||
tlbimp %SystemRoot%\System32\mstscax.dll /out:MSTSCLib.dll
|
||||
Then reference it here:
|
||||
<ItemGroup>
|
||||
<Reference Include="MSTSCLib">
|
||||
<HintPath>Interop\MSTSCLib.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="AxInterop.MSTSCLib">
|
||||
<HintPath>Interop\AxInterop.MSTSCLib.dll</HintPath>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
-->
|
||||
</Project>
|
||||
@ -0,0 +1,147 @@
|
||||
// Copyright 2026 sjackson0109 — Apache License 2.0
|
||||
//
|
||||
// COM interop helpers for hosting the MsRdpClient2 ActiveX control and
|
||||
// receiving its disconnection event without requiring pre-generated TLB wrappers.
|
||||
|
||||
using System.ComponentModel;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace RDPCheck;
|
||||
|
||||
// ── COM interfaces needed for connection-point event subscription ────────────
|
||||
|
||||
[ComImport]
|
||||
[Guid("B196B284-BAB4-101A-B69C-00AA00341D07")]
|
||||
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||
internal interface IConnectionPointContainer
|
||||
{
|
||||
void EnumConnectionPoints(out IntPtr ppEnum);
|
||||
void FindConnectionPoint(ref Guid riid, [MarshalAs(UnmanagedType.Interface)] out IConnectionPoint ppCP);
|
||||
}
|
||||
|
||||
[ComImport]
|
||||
[Guid("B196B287-BAB4-101A-B69C-00AA00341D07")]
|
||||
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||
internal interface IConnectionPoint
|
||||
{
|
||||
void GetConnectionInterface(out Guid pIID);
|
||||
void GetConnectionPointContainer([MarshalAs(UnmanagedType.Interface)] out IConnectionPointContainer ppCPC);
|
||||
void Advise([MarshalAs(UnmanagedType.Interface)] object pUnkSink, out uint pdwCookie);
|
||||
void Unadvise(uint dwCookie);
|
||||
void EnumConnections(out IntPtr ppEnum);
|
||||
}
|
||||
|
||||
// ── IDispatch-based event interface (dispinterface) ──────────────────────────
|
||||
// Matching the DIID of IMsTscAxEvents so the RDP control can route events here.
|
||||
|
||||
[ComVisible(true)]
|
||||
[Guid("336D5562-EBA6-11D0-B0B0-00C04FD610D0")]
|
||||
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
|
||||
internal interface IRdpEvents
|
||||
{
|
||||
[DispId(1)] void OnConnecting();
|
||||
[DispId(2)] void OnConnected();
|
||||
[DispId(3)] void OnLoginComplete();
|
||||
[DispId(4)] void OnDisconnected(int discReason);
|
||||
// Additional high-DISPID events are ignored (not listed here)
|
||||
}
|
||||
|
||||
// ── Managed event sink ────────────────────────────────────────────────────────
|
||||
|
||||
[ComVisible(true)]
|
||||
[ClassInterface(ClassInterfaceType.None)]
|
||||
internal sealed class RdpEventSink : IRdpEvents
|
||||
{
|
||||
private readonly Action<int> _onDisconnected;
|
||||
|
||||
public RdpEventSink(Action<int> onDisconnected) => _onDisconnected = onDisconnected;
|
||||
|
||||
public void OnConnecting() { }
|
||||
public void OnConnected() { }
|
||||
public void OnLoginComplete() { }
|
||||
public void OnDisconnected(int discReason) => _onDisconnected(discReason);
|
||||
}
|
||||
|
||||
// ── AxHost wrapper for MsRdpClient2 ──────────────────────────────────────────
|
||||
|
||||
internal sealed class AxRdpClient2 : AxHost
|
||||
{
|
||||
// CLSID for MsRdpClient2 (msrdp.ocx / mstscax.dll)
|
||||
private static readonly Guid Clsid = new("9059F30F-4EB1-4BD2-9FDC-36F43A218F4A");
|
||||
// DIID for the default source interface (IMsTscAxEvents / DIMsTscAxEvents)
|
||||
private static readonly Guid DiidEvents = new("336D5562-EBA6-11D0-B0B0-00C04FD610D0");
|
||||
|
||||
private dynamic? _ocx;
|
||||
private IConnectionPoint? _cp;
|
||||
private uint _cookie;
|
||||
private RdpEventSink? _sink;
|
||||
|
||||
/// <summary>Raised on the UI thread when the RDP control disconnects.</summary>
|
||||
public event EventHandler<int>? Disconnected;
|
||||
|
||||
public AxRdpClient2() : base(Clsid.ToString("B")) { }
|
||||
|
||||
protected override void AttachInterfaces()
|
||||
{
|
||||
_ocx = GetOcx() as dynamic;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Wires up the COM event sink. Call once after the handle has been created
|
||||
/// (i.e. after the form is shown).
|
||||
/// </summary>
|
||||
public void SubscribeEvents()
|
||||
{
|
||||
if (_ocx is null) return;
|
||||
try
|
||||
{
|
||||
var cpContainer = (IConnectionPointContainer)_ocx;
|
||||
var eventsIid = DiidEvents;
|
||||
cpContainer.FindConnectionPoint(ref eventsIid, out var cp);
|
||||
_sink = new RdpEventSink(reason => Invoke(
|
||||
(Action)(() => Disconnected?.Invoke(this, reason))));
|
||||
cp.Advise(_sink, out _cookie);
|
||||
_cp = cp;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"[RDP] SubscribeEvents failed: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Unsubscribes the event sink.</summary>
|
||||
public void UnsubscribeEvents()
|
||||
{
|
||||
try { _cp?.Unadvise(_cookie); } catch { }
|
||||
_cp = null;
|
||||
}
|
||||
|
||||
// ── Typed property / method wrappers ────────────────────────────────────
|
||||
|
||||
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
|
||||
public string Server { set { if (_ocx != null) _ocx.Server = value; } }
|
||||
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
|
||||
public string UserName { set { if (_ocx != null) _ocx.UserName = value; } }
|
||||
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
|
||||
public string DisconnectedText { set { if (_ocx != null) _ocx.DisconnectedText = value; } }
|
||||
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
|
||||
public string ConnectingText { set { if (_ocx != null) _ocx.ConnectingText = value; } }
|
||||
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
|
||||
public string ConnectedStatusText { set { if (_ocx != null) _ocx.ConnectedStatusText = value; } }
|
||||
|
||||
public void SetPort(int port)
|
||||
{
|
||||
try { if (_ocx != null) _ocx.AdvancedSettings2.RDPPort = port; } catch { }
|
||||
}
|
||||
|
||||
public void Connect()
|
||||
{
|
||||
try { _ocx?.Connect(); } catch { }
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing) UnsubscribeEvents();
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 445 B |
@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<assemblyIdentity version="1.0.0.0" name="RDPCheck.app"/>
|
||||
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
|
||||
<security>
|
||||
<requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
<requestedExecutionLevel level="requireAdministrator" uiAccess="false" />
|
||||
</requestedPrivileges>
|
||||
</security>
|
||||
</trustInfo>
|
||||
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
|
||||
<application>
|
||||
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
|
||||
<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}" />
|
||||
<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}" />
|
||||
<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}" />
|
||||
<supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}" />
|
||||
</application>
|
||||
</compatibility>
|
||||
</assembly>
|
||||
@ -0,0 +1,10 @@
|
||||
{
|
||||
"version": 1,
|
||||
"dependencies": {
|
||||
"net10.0-windows7.0": {
|
||||
"rdpwrap": {
|
||||
"type": "Project"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,53 @@
|
||||
// Copyright 2026 sjackson0109 — Apache License 2.0
|
||||
//
|
||||
// LicenseForm — mirrors LicenseUnit.pas (TLicenseForm).
|
||||
// Shows a readonly multiline license text with Accept / Decline buttons.
|
||||
|
||||
namespace RDPConf;
|
||||
|
||||
internal sealed class LicenseForm : Form
|
||||
{
|
||||
public LicenseForm(string licenseText)
|
||||
{
|
||||
Text = "License Agreement";
|
||||
ClientSize = new Size(600, 440);
|
||||
FormBorderStyle = FormBorderStyle.FixedDialog;
|
||||
MaximizeBox = false;
|
||||
MinimizeBox = false;
|
||||
StartPosition = FormStartPosition.CenterParent;
|
||||
Font = new Font("Segoe UI", 9f);
|
||||
|
||||
var mText = new TextBox
|
||||
{
|
||||
Multiline = true,
|
||||
ReadOnly = true,
|
||||
ScrollBars = ScrollBars.Vertical,
|
||||
Text = licenseText,
|
||||
Location = new Point(8, 8),
|
||||
Size = new Size(576, 380),
|
||||
Font = new Font("Courier New", 8.5f),
|
||||
BackColor = SystemColors.Window
|
||||
};
|
||||
|
||||
var bAccept = new Button
|
||||
{
|
||||
Text = "Accept",
|
||||
DialogResult = DialogResult.OK,
|
||||
Location = new Point(428, 398),
|
||||
Size = new Size(75, 26)
|
||||
};
|
||||
|
||||
var bDecline = new Button
|
||||
{
|
||||
Text = "Decline",
|
||||
DialogResult = DialogResult.Cancel,
|
||||
Location = new Point(509, 398),
|
||||
Size = new Size(75, 26)
|
||||
};
|
||||
|
||||
AcceptButton = bAccept;
|
||||
CancelButton = bDecline;
|
||||
|
||||
Controls.AddRange(new Control[] { mText, bAccept, bDecline });
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,540 @@
|
||||
// Copyright 2026 sjackson0109 — Apache License 2.0
|
||||
//
|
||||
// All logic, registry keys, and label text match the Delphi original.
|
||||
|
||||
using System.Runtime.InteropServices;
|
||||
using RDPWrap.Common;
|
||||
|
||||
namespace RDPConf;
|
||||
|
||||
internal sealed class MainForm : Form
|
||||
{
|
||||
// ── Controls ──────────────────────────────────────────────────────────────
|
||||
private readonly CheckBox cbAllowTSConnections = new();
|
||||
private readonly CheckBox cbSingleSessionPerUser = new();
|
||||
private readonly CheckBox cbHideUsers = new();
|
||||
private readonly CheckBox cbCustomPrg = new();
|
||||
private readonly NumericUpDown seRDPPort = new();
|
||||
private readonly Label lRDPPort = new();
|
||||
private readonly GroupBox gbGeneral = new();
|
||||
|
||||
// NLA radio group
|
||||
private readonly GroupBox gbNLA = new();
|
||||
private readonly RadioButton rbNLA0 = new();
|
||||
private readonly RadioButton rbNLA1 = new();
|
||||
private readonly RadioButton rbNLA2 = new();
|
||||
|
||||
// Shadow radio group
|
||||
private readonly GroupBox gbShadow = new();
|
||||
private readonly RadioButton rbShadow0 = new();
|
||||
private readonly RadioButton rbShadow1 = new();
|
||||
private readonly RadioButton rbShadow2 = new();
|
||||
private readonly RadioButton rbShadow3 = new();
|
||||
private readonly RadioButton rbShadow4 = new();
|
||||
|
||||
// Diagnostics
|
||||
private readonly GroupBox gbDiag = new();
|
||||
private readonly Label lService = new();
|
||||
private readonly Label lsService = new();
|
||||
private readonly Label lListener = new();
|
||||
private readonly Label lsListener = new();
|
||||
private readonly Label lWrapper = new();
|
||||
private readonly Label lsWrapper = new();
|
||||
private readonly Label lTSVer = new();
|
||||
private readonly Label lsTSVer = new();
|
||||
private readonly Label lWrapVer = new();
|
||||
private readonly Label lsWrapVer = new();
|
||||
private readonly Label lsSuppVer = new();
|
||||
|
||||
// Buttons / Timer
|
||||
private readonly Button bOK = new();
|
||||
private readonly Button bCancel = new();
|
||||
private readonly Button bApply = new();
|
||||
private readonly Button bLicense = new();
|
||||
private readonly System.Windows.Forms.Timer timer = new();
|
||||
|
||||
// State (mirrors Delphi globals)
|
||||
private bool _ready;
|
||||
private int _oldPort;
|
||||
|
||||
// ── Constructor ───────────────────────────────────────────────────────────
|
||||
|
||||
public MainForm()
|
||||
{
|
||||
SuspendLayout();
|
||||
BuildLayout();
|
||||
ResumeLayout(false);
|
||||
PerformLayout();
|
||||
|
||||
// Wire events
|
||||
cbAllowTSConnections.CheckedChanged += OnAnyChange;
|
||||
cbSingleSessionPerUser.CheckedChanged += OnAnyChange;
|
||||
cbHideUsers.CheckedChanged += OnAnyChange;
|
||||
cbCustomPrg.CheckedChanged += OnAnyChange;
|
||||
seRDPPort.ValueChanged += OnAnyChange;
|
||||
rbNLA0.CheckedChanged += OnAnyChange;
|
||||
rbNLA1.CheckedChanged += OnAnyChange;
|
||||
rbNLA2.CheckedChanged += OnAnyChange;
|
||||
rbShadow0.CheckedChanged += OnAnyChange;
|
||||
rbShadow1.CheckedChanged += OnAnyChange;
|
||||
rbShadow2.CheckedChanged += OnAnyChange;
|
||||
rbShadow3.CheckedChanged += OnAnyChange;
|
||||
rbShadow4.CheckedChanged += OnAnyChange;
|
||||
bApply.Click += (_, _) => { WriteSettings(); bApply.Enabled = false; };
|
||||
bOK.Click += (_, _) => { if (bApply.Enabled) { WriteSettings(); bApply.Enabled = false; } Close(); };
|
||||
bCancel.Click += (_, _) => Close();
|
||||
bLicense.Click += OnLicenseClick;
|
||||
timer.Interval = 1000;
|
||||
timer.Tick += TimerTimer;
|
||||
FormClosing += OnFormClosing;
|
||||
Shown += OnShown;
|
||||
FormClosed += (_, _) => { if (ArchHelper.Is64Bit) ArchHelper.RevertWow64Redirection(); timer.Stop(); };
|
||||
|
||||
if (ArchHelper.Is64Bit) ArchHelper.DisableWow64Redirection();
|
||||
ReadSettings();
|
||||
_ready = true;
|
||||
}
|
||||
|
||||
private void OnShown(object? sender, EventArgs e)
|
||||
{
|
||||
TimerTimer(sender, e); // immediate first tick
|
||||
timer.Start();
|
||||
}
|
||||
|
||||
// ── Layout ────────────────────────────────────────────────────────────────
|
||||
|
||||
private void BuildLayout()
|
||||
{
|
||||
Text = "RDP Wrapper Configuration";
|
||||
ClientSize = new Size(540, 383);
|
||||
FormBorderStyle = FormBorderStyle.FixedSingle;
|
||||
MaximizeBox = false;
|
||||
StartPosition = FormStartPosition.CenterScreen;
|
||||
Font = new Font("Segoe UI", 9f);
|
||||
|
||||
// ── General GroupBox ──────────────────────────────────────────────────
|
||||
gbGeneral.Text = "General";
|
||||
gbGeneral.Location = new Point(8, 8);
|
||||
gbGeneral.Size = new Size(260, 175);
|
||||
|
||||
cbAllowTSConnections.Text = "Allow Remote Desktop connections";
|
||||
cbAllowTSConnections.Location = new Point(10, 22);
|
||||
cbAllowTSConnections.AutoSize = true;
|
||||
|
||||
cbSingleSessionPerUser.Text = "Single session per user";
|
||||
cbSingleSessionPerUser.Location = new Point(10, 46);
|
||||
cbSingleSessionPerUser.AutoSize = true;
|
||||
|
||||
cbHideUsers.Text = "Hide users on logon screen";
|
||||
cbHideUsers.Location = new Point(10, 70);
|
||||
cbHideUsers.AutoSize = true;
|
||||
|
||||
cbCustomPrg.Text = "Custom program support (HonorLegacySettings)";
|
||||
cbCustomPrg.Location = new Point(10, 94);
|
||||
cbCustomPrg.AutoSize = true;
|
||||
|
||||
lRDPPort.Text = "RDP Port:";
|
||||
lRDPPort.Location = new Point(10, 124);
|
||||
lRDPPort.AutoSize = true;
|
||||
|
||||
seRDPPort.Location = new Point(80, 121);
|
||||
seRDPPort.Minimum = 1;
|
||||
seRDPPort.Maximum = 65535;
|
||||
seRDPPort.Value = 3389;
|
||||
seRDPPort.Width = 70;
|
||||
|
||||
gbGeneral.Controls.AddRange(new Control[]
|
||||
{
|
||||
cbAllowTSConnections, cbSingleSessionPerUser, cbHideUsers,
|
||||
cbCustomPrg, lRDPPort, seRDPPort
|
||||
});
|
||||
|
||||
// ── NLA GroupBox ──────────────────────────────────────────────────────
|
||||
gbNLA.Text = "Network Level Authentication";
|
||||
gbNLA.Location = new Point(276, 8);
|
||||
gbNLA.Size = new Size(254, 88);
|
||||
|
||||
rbNLA0.Text = "No NLA";
|
||||
rbNLA0.Location = new Point(10, 20);
|
||||
rbNLA0.AutoSize = true;
|
||||
|
||||
rbNLA1.Text = "Negotiate (server-side NLA)";
|
||||
rbNLA1.Location = new Point(10, 42);
|
||||
rbNLA1.AutoSize = true;
|
||||
|
||||
rbNLA2.Text = "Require NLA (client + server)";
|
||||
rbNLA2.Location = new Point(10, 64);
|
||||
rbNLA2.AutoSize = true;
|
||||
|
||||
gbNLA.Controls.AddRange(new Control[] { rbNLA0, rbNLA1, rbNLA2 });
|
||||
|
||||
// ── Shadow GroupBox ───────────────────────────────────────────────────
|
||||
gbShadow.Text = "Shadowing";
|
||||
gbShadow.Location = new Point(276, 100);
|
||||
gbShadow.Size = new Size(254, 135);
|
||||
|
||||
rbShadow0.Text = "Disabled";
|
||||
rbShadow0.Location = new Point(10, 20); rbShadow0.AutoSize = true;
|
||||
rbShadow1.Text = "Full Access (with confirmation)";
|
||||
rbShadow1.Location = new Point(10, 42); rbShadow1.AutoSize = true;
|
||||
rbShadow2.Text = "Full Access (no confirmation)";
|
||||
rbShadow2.Location = new Point(10, 62); rbShadow2.AutoSize = true;
|
||||
rbShadow3.Text = "View Only (with confirmation)";
|
||||
rbShadow3.Location = new Point(10, 82); rbShadow3.AutoSize = true;
|
||||
rbShadow4.Text = "View Only (no confirmation)";
|
||||
rbShadow4.Location = new Point(10, 102); rbShadow4.AutoSize = true;
|
||||
|
||||
gbShadow.Controls.AddRange(new Control[]
|
||||
{ rbShadow0, rbShadow1, rbShadow2, rbShadow3, rbShadow4 });
|
||||
|
||||
// ── Diagnostics GroupBox ──────────────────────────────────────────────
|
||||
gbDiag.Text = "Diagnostics";
|
||||
gbDiag.Location = new Point(8, 190);
|
||||
gbDiag.Size = new Size(522, 140);
|
||||
|
||||
AddDiagRow(gbDiag, "Service:", lService, lsService, new Point(10, 22));
|
||||
AddDiagRow(gbDiag, "Listener:", lListener, lsListener, new Point(10, 44));
|
||||
AddDiagRow(gbDiag, "Wrapper:", lWrapper, lsWrapper, new Point(10, 66));
|
||||
AddDiagRow(gbDiag, "TS version:", lTSVer, lsTSVer, new Point(10, 88));
|
||||
AddDiagRow(gbDiag, "Wrapper version:", lWrapVer, lsWrapVer, new Point(10, 110));
|
||||
|
||||
lsSuppVer.Location = new Point(280, 88);
|
||||
lsSuppVer.AutoSize = true;
|
||||
lsSuppVer.Visible = false;
|
||||
gbDiag.Controls.Add(lsSuppVer);
|
||||
|
||||
// ── Buttons ───────────────────────────────────────────────────────────
|
||||
bLicense.Text = "License";
|
||||
bLicense.Size = new Size(80, 26);
|
||||
bLicense.Location = new Point(8, 345);
|
||||
|
||||
bApply.Text = "Apply";
|
||||
bApply.Enabled = false;
|
||||
bApply.Size = new Size(80, 26);
|
||||
bApply.Location = new Point(276, 345);
|
||||
|
||||
bOK.Text = "OK";
|
||||
bOK.Size = new Size(80, 26);
|
||||
bOK.Location = new Point(364, 345);
|
||||
|
||||
bCancel.Text = "Cancel";
|
||||
bCancel.Size = new Size(80, 26);
|
||||
bCancel.Location = new Point(452, 345);
|
||||
|
||||
Controls.AddRange(new Control[]
|
||||
{
|
||||
gbGeneral, gbNLA, gbShadow, gbDiag,
|
||||
bLicense, bApply, bOK, bCancel
|
||||
});
|
||||
}
|
||||
|
||||
private static void AddDiagRow(GroupBox parent, string labelText,
|
||||
Label lbl, Label status, Point origin)
|
||||
{
|
||||
lbl.Text = labelText;
|
||||
lbl.Location = new Point(origin.X, origin.Y);
|
||||
lbl.AutoSize = true;
|
||||
|
||||
status.Text = "...";
|
||||
status.Location = new Point(origin.X + 120, origin.Y);
|
||||
status.AutoSize = true;
|
||||
status.ForeColor = SystemColors.GrayText;
|
||||
|
||||
parent.Controls.Add(lbl);
|
||||
parent.Controls.Add(status);
|
||||
}
|
||||
|
||||
private void OnAnyChange(object? sender, EventArgs e)
|
||||
{
|
||||
if (_ready) bApply.Enabled = true;
|
||||
}
|
||||
|
||||
// ── ReadSettings (mirrors Delphi ReadSettings) ────────────────────────────
|
||||
|
||||
private void ReadSettings()
|
||||
{
|
||||
const string tsKey = @"SYSTEM\CurrentControlSet\Control\Terminal Server";
|
||||
const string rdpTcp = @"SYSTEM\CurrentControlSet\Control\Terminal Server\WinStations\RDP-Tcp";
|
||||
const string policies = @"SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System";
|
||||
|
||||
cbAllowTSConnections.Checked = !RegistryHelper.ReadBool(tsKey, "fDenyTSConnections", false);
|
||||
cbSingleSessionPerUser.Checked = RegistryHelper.ReadBool(tsKey, "fSingleSessionPerUser", false);
|
||||
cbCustomPrg.Checked = RegistryHelper.ReadBool(tsKey, "HonorLegacySettings", false);
|
||||
|
||||
int port = RegistryHelper.ReadInt(rdpTcp, "PortNumber", 3389);
|
||||
seRDPPort.Value = Math.Clamp(port, 1, 65535);
|
||||
_oldPort = port;
|
||||
|
||||
int secLayer = RegistryHelper.ReadInt(rdpTcp, "SecurityLayer", 0);
|
||||
int userAuth = RegistryHelper.ReadInt(rdpTcp, "UserAuthentication", 0);
|
||||
int shadow = RegistryHelper.ReadInt(rdpTcp, "Shadow", -1);
|
||||
|
||||
_ = (secLayer, userAuth) switch
|
||||
{
|
||||
(0, 0) => rbNLA0.Checked = true,
|
||||
(1, 0) => rbNLA1.Checked = true,
|
||||
(2, 1) => rbNLA2.Checked = true,
|
||||
_ => rbNLA0.Checked = true
|
||||
};
|
||||
|
||||
SetShadowRadio(shadow);
|
||||
|
||||
cbHideUsers.Checked = RegistryHelper.ReadBool(policies, "dontdisplaylastusername", false);
|
||||
}
|
||||
|
||||
private void SetShadowRadio(int idx)
|
||||
{
|
||||
RadioButton[] rbs = { rbShadow0, rbShadow1, rbShadow2, rbShadow3, rbShadow4 };
|
||||
if (idx >= 0 && idx < rbs.Length)
|
||||
rbs[idx].Checked = true;
|
||||
}
|
||||
|
||||
private int GetShadowIndex()
|
||||
{
|
||||
RadioButton[] rbs = { rbShadow0, rbShadow1, rbShadow2, rbShadow3, rbShadow4 };
|
||||
for (int i = 0; i < rbs.Length; i++)
|
||||
if (rbs[i].Checked) return i;
|
||||
return -1;
|
||||
}
|
||||
|
||||
// ── WriteSettings (mirrors Delphi WriteSettings) ──────────────────────────
|
||||
|
||||
private void WriteSettings()
|
||||
{
|
||||
const string tsKey = @"SYSTEM\CurrentControlSet\Control\Terminal Server";
|
||||
const string rdpTcp = @"SYSTEM\CurrentControlSet\Control\Terminal Server\WinStations\RDP-Tcp";
|
||||
const string policiesRdp = @"SOFTWARE\Policies\Microsoft\Windows NT\Terminal Services";
|
||||
const string policies = @"SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System";
|
||||
|
||||
RegistryHelper.WriteBool(tsKey, "fDenyTSConnections", !cbAllowTSConnections.Checked);
|
||||
RegistryHelper.WriteBool(tsKey, "fSingleSessionPerUser", cbSingleSessionPerUser.Checked);
|
||||
RegistryHelper.WriteBool(tsKey, "HonorLegacySettings", cbCustomPrg.Checked);
|
||||
|
||||
int newPort = (int)seRDPPort.Value;
|
||||
RegistryHelper.WriteInt(rdpTcp, "PortNumber", newPort);
|
||||
if (_oldPort != newPort)
|
||||
{
|
||||
_oldPort = newPort;
|
||||
ProcessHelper.ExecWait(
|
||||
$"netsh advfirewall firewall set rule name=\"Remote Desktop\" new localport={newPort}");
|
||||
}
|
||||
|
||||
// NLA
|
||||
(int sl, int ua) = (rbNLA0.Checked, rbNLA1.Checked, rbNLA2.Checked) switch
|
||||
{
|
||||
(true, _, _ ) => (0, 0),
|
||||
(_, true, _ ) => (1, 0),
|
||||
(_, _, true) => (2, 1),
|
||||
_ => (-1, -1)
|
||||
};
|
||||
if (sl >= 0)
|
||||
{
|
||||
RegistryHelper.WriteInt(rdpTcp, "SecurityLayer", sl);
|
||||
RegistryHelper.WriteInt(rdpTcp, "UserAuthentication", ua);
|
||||
}
|
||||
|
||||
// Shadow
|
||||
int shadowIdx = GetShadowIndex();
|
||||
if (shadowIdx >= 0)
|
||||
{
|
||||
RegistryHelper.WriteInt(rdpTcp, "Shadow", shadowIdx);
|
||||
RegistryHelper.WriteInt(policiesRdp, "Shadow", shadowIdx);
|
||||
}
|
||||
|
||||
RegistryHelper.WriteBool(policies, "dontdisplaylastusername", cbHideUsers.Checked);
|
||||
}
|
||||
|
||||
// ── TimerTimer (mirrors Delphi TimerTimer) ────────────────────────────────
|
||||
|
||||
private void TimerTimer(object? sender, EventArgs e)
|
||||
{
|
||||
// ── Wrapper state ──
|
||||
string wrapperPath = string.Empty;
|
||||
int wrapState = IsWrapperInstalled(ref wrapperPath);
|
||||
bool checkSupp = false;
|
||||
string iniPath = string.Empty;
|
||||
|
||||
switch (wrapState)
|
||||
{
|
||||
case -1:
|
||||
SetStatus(lsWrapper, "Unknown", SystemColors.GrayText);
|
||||
break;
|
||||
case 0:
|
||||
SetStatus(lsWrapper, "Not installed", SystemColors.GrayText);
|
||||
break;
|
||||
case 1:
|
||||
SetStatus(lsWrapper, "Installed", Color.Green);
|
||||
iniPath = Path.Combine(
|
||||
Path.GetDirectoryName(ArchHelper.ExpandPath(wrapperPath))!,
|
||||
"rdpwrap.ini");
|
||||
checkSupp = File.Exists(iniPath);
|
||||
break;
|
||||
case 2:
|
||||
SetStatus(lsWrapper, "3rd-party", Color.Red);
|
||||
break;
|
||||
}
|
||||
|
||||
// ── Service state ──
|
||||
int svcState = GetTermSrvState();
|
||||
// dwCurrentState constants (mirrors NativeMethods SERVICE_* constants)
|
||||
string svcText = svcState switch
|
||||
{
|
||||
1 => "Stopped",
|
||||
2 => "Starting...",
|
||||
3 => "Stopping...",
|
||||
4 => "Running",
|
||||
5 => "Resuming...",
|
||||
6 => "Suspending...",
|
||||
7 => "Suspended",
|
||||
_ => "Unknown"
|
||||
};
|
||||
Color svcColor = svcState == 4 ? Color.Green // SERVICE_RUNNING
|
||||
: svcState == 1 ? Color.Red // SERVICE_STOPPED
|
||||
: SystemColors.GrayText;
|
||||
SetStatus(lsService, svcText, svcColor);
|
||||
|
||||
// ── Listener ──
|
||||
bool listening = IsListenerWorking();
|
||||
SetStatus(lsListener,
|
||||
listening ? "Listening" : "Not listening",
|
||||
listening ? Color.Green : Color.Red);
|
||||
|
||||
// ── Wrapper version ──
|
||||
string wrapExp = ArchHelper.ExpandPath(wrapperPath);
|
||||
var wrapVer = string.IsNullOrEmpty(wrapperPath) ? null
|
||||
: FileVersionHelper.GetVersion(wrapExp);
|
||||
if (wrapVer is null)
|
||||
SetStatus(lsWrapVer, "N/A", SystemColors.GrayText);
|
||||
else
|
||||
SetStatus(lsWrapVer, wrapVer.ToString(), SystemColors.WindowText);
|
||||
|
||||
// ── TS version ──
|
||||
var tsVer = FileVersionHelper.GetVersionExpanded(@"%SystemRoot%\System32\termsrv.dll");
|
||||
if (tsVer is null)
|
||||
{
|
||||
SetStatus(lsTSVer, "N/A", SystemColors.GrayText);
|
||||
lsSuppVer.Visible = false;
|
||||
return;
|
||||
}
|
||||
|
||||
SetStatus(lsTSVer, tsVer.ToString(), SystemColors.WindowText);
|
||||
lsSuppVer.Visible = checkSupp;
|
||||
|
||||
if (checkSupp)
|
||||
{
|
||||
string iniContent = IniHelper.LoadText(iniPath);
|
||||
int level = IniHelper.CheckSupportLevel(iniContent, tsVer);
|
||||
(string suppText, Color suppColor) = level switch
|
||||
{
|
||||
0 => ("[not supported]", Color.Red),
|
||||
1 => ("[supported partially]", Color.Olive),
|
||||
_ => ("[fully supported]", Color.Green)
|
||||
};
|
||||
lsSuppVer.Text = suppText;
|
||||
lsSuppVer.ForeColor = suppColor;
|
||||
}
|
||||
}
|
||||
|
||||
// ── Helper: IsWrapperInstalled ────────────────────────────────────────────
|
||||
|
||||
/// <returns>-1=error, 0=not installed, 1=installed, 2=3rd-party</returns>
|
||||
private static int IsWrapperInstalled(ref string wrapperPath)
|
||||
{
|
||||
wrapperPath = string.Empty;
|
||||
var host = RegistryHelper.ReadString(
|
||||
@"SYSTEM\CurrentControlSet\Services\TermService", "ImagePath") ?? string.Empty;
|
||||
|
||||
if (!host.Contains("svchost.exe", StringComparison.OrdinalIgnoreCase)) return 2;
|
||||
|
||||
var svcDll = RegistryHelper.ReadString(
|
||||
@"SYSTEM\CurrentControlSet\Services\TermService\Parameters", "ServiceDll") ?? string.Empty;
|
||||
|
||||
if (svcDll.Length == 0) return -1;
|
||||
|
||||
if (!svcDll.Contains("termsrv.dll", StringComparison.OrdinalIgnoreCase) &&
|
||||
!svcDll.Contains("rdpwrap.dll", StringComparison.OrdinalIgnoreCase))
|
||||
return 2;
|
||||
|
||||
if (svcDll.Contains("rdpwrap.dll", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
wrapperPath = svcDll;
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ── Helper: GetTermSrvState (via SCM) ─────────────────────────────────────
|
||||
|
||||
private static int GetTermSrvState()
|
||||
=> ServiceHelper.GetCurrentState("TermService");
|
||||
|
||||
// ── Helper: IsListenerWorking (via WinStationEnumerateW) ──────────────────
|
||||
|
||||
private static bool IsListenerWorking()
|
||||
{
|
||||
if (!NativeMethods.WinStationEnumerate(IntPtr.Zero,
|
||||
out IntPtr pSessions, out uint count)) return false;
|
||||
|
||||
bool found = false;
|
||||
try
|
||||
{
|
||||
// Each entry is { DWORD SessionId; WCHAR[34] Name; DWORD State }
|
||||
// = 4 + 68 + 4 = 76 bytes on x64 (with natural alignment the struct is 76 bytes)
|
||||
const int stride = 76; // sizeof(WTS_SESSION_INFOW) — matches Delphi packed array
|
||||
for (uint i = 0; i < count; i++)
|
||||
{
|
||||
IntPtr entry = pSessions + (int)(i * stride);
|
||||
// Name is at offset 4, WCHAR[34]
|
||||
string name = Marshal.PtrToStringUni(entry + 4, 34).TrimEnd('\0');
|
||||
if (name.Equals("RDP-Tcp", StringComparison.Ordinal))
|
||||
{
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
NativeMethods.WinStationFreeMemory(pSessions);
|
||||
}
|
||||
|
||||
return found;
|
||||
}
|
||||
|
||||
// ── Helper: SetStatus ─────────────────────────────────────────────────────
|
||||
|
||||
private static void SetStatus(Label lbl, string text, Color color)
|
||||
{
|
||||
lbl.Text = text;
|
||||
lbl.ForeColor = color;
|
||||
}
|
||||
|
||||
// ── OnFormClosing — unsaved-changes guard ─────────────────────────────────
|
||||
|
||||
private void OnFormClosing(object? sender, FormClosingEventArgs e)
|
||||
{
|
||||
if (bApply.Enabled)
|
||||
{
|
||||
var result = MessageBox.Show(
|
||||
"Settings are not saved. Do you want to exit?",
|
||||
"Warning",
|
||||
MessageBoxButtons.YesNo,
|
||||
MessageBoxIcon.Warning);
|
||||
e.Cancel = result != DialogResult.Yes;
|
||||
}
|
||||
}
|
||||
|
||||
// ── License button ────────────────────────────────────────────────────────
|
||||
|
||||
private void OnLicenseClick(object? sender, EventArgs e)
|
||||
{
|
||||
var text = ResourceHelper.ReadText(
|
||||
"RDPConf.Resources.license.txt",
|
||||
System.Reflection.Assembly.GetExecutingAssembly());
|
||||
text ??= ResourceHelper.ReadText("RDPWInst.Resources.license.txt") ?? "(license not found)";
|
||||
|
||||
using var dlg = new LicenseForm(text);
|
||||
if (dlg.ShowDialog(this) != DialogResult.OK)
|
||||
Application.Exit();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,15 @@
|
||||
// Copyright 2026 sjackson0109 — Apache License 2.0
|
||||
//
|
||||
// RDPConf — WinForms configuration GUI entry point.
|
||||
|
||||
namespace RDPConf;
|
||||
|
||||
internal static class Program
|
||||
{
|
||||
[STAThread]
|
||||
internal static void Main()
|
||||
{
|
||||
ApplicationConfiguration.Initialize();
|
||||
Application.Run(new MainForm());
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,24 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<UseWindowsForms>true</UseWindowsForms>
|
||||
<RootNamespace>RDPConf</RootNamespace>
|
||||
<AssemblyName>RDPConf</AssemblyName>
|
||||
<ApplicationManifest>app.manifest</ApplicationManifest>
|
||||
<ApplicationIcon>app.ico</ApplicationIcon>
|
||||
<Description>WinForms configuration GUI for RDP Wrapper — manage concurrent session limits, RDP port, and security settings.</Description>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Include="app.ico" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\RDPWrap\RDPWrap.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<!-- Optional embedded license text (copy from msi/rdpwrap.ini or assets before build) -->
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="Resources\license.txt" Condition="Exists('Resources\license.txt')" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
After Width: | Height: | Size: 418 B |
@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<assemblyIdentity version="1.0.0.0" name="RDPConf.app"/>
|
||||
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
|
||||
<security>
|
||||
<requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
<requestedExecutionLevel level="requireAdministrator" uiAccess="false" />
|
||||
</requestedPrivileges>
|
||||
</security>
|
||||
</trustInfo>
|
||||
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
|
||||
<application>
|
||||
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
|
||||
<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}" />
|
||||
<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}" />
|
||||
<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}" />
|
||||
<supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}" />
|
||||
</application>
|
||||
</compatibility>
|
||||
</assembly>
|
||||
@ -0,0 +1,10 @@
|
||||
{
|
||||
"version": 1,
|
||||
"dependencies": {
|
||||
"net10.0-windows7.0": {
|
||||
"rdpwrap": {
|
||||
"type": "Project"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1 @@
|
||||
Subproject commit 68da37acab6593c329776644944f55695a131731
|
||||
@ -0,0 +1,744 @@
|
||||
// Copyright 2026 sjackson0109 — Apache License 2.0
|
||||
//
|
||||
// InstallerEngine — translates every procedure in RDPWInst.dpr to C#.
|
||||
|
||||
using System.Reflection;
|
||||
using RDPWrap.Common;
|
||||
|
||||
namespace RDPWInst;
|
||||
|
||||
/// <summary>
|
||||
/// Orchestrates install / uninstall / update / restart of the RDP Wrapper.
|
||||
/// All public methods return an exit code (0 = success).
|
||||
/// </summary>
|
||||
internal sealed class InstallerEngine
|
||||
{
|
||||
// ── State (mirrors Delphi globals) ────────────────────────────────────────
|
||||
|
||||
private bool _installed;
|
||||
private bool _online;
|
||||
private string _wrapPath = string.Empty;
|
||||
private string _termServicePath = string.Empty;
|
||||
private string _termSrvVerTxt = string.Empty;
|
||||
private uint _termServicePid;
|
||||
private string[] _shareServices = Array.Empty<string>();
|
||||
|
||||
private const string TermService = "TermService";
|
||||
|
||||
// Latest release download base URL
|
||||
private const string ReleaseBaseUrl =
|
||||
"https://github.com/sjackson0109/rdpwrap/releases/latest/download/";
|
||||
|
||||
// ── Public entry points ───────────────────────────────────────────────────
|
||||
|
||||
/// <summary>
|
||||
/// Install the wrapper. Mirrors the <c>-i</c> branch in RDPWInst.dpr.
|
||||
/// </summary>
|
||||
public int Install(bool toSystem32, bool online, bool force = false)
|
||||
{
|
||||
if (_installed)
|
||||
{
|
||||
if (!force)
|
||||
{
|
||||
Console.WriteLine("[*] RDP Wrapper Library is already installed.");
|
||||
return unchecked((int)NativeMethods.ERROR_ACCESS_DENIED);
|
||||
}
|
||||
|
||||
Console.WriteLine("[*] Existing installation detected -- uninstalling first (force mode)...");
|
||||
int urc = Uninstall(keepSettings: true);
|
||||
if (urc != 0)
|
||||
{
|
||||
Console.Error.WriteLine($"[-] Force-uninstall failed (code {urc}) -- aborting install.");
|
||||
return urc;
|
||||
}
|
||||
// Refresh state: after a successful uninstall _installed should now be false.
|
||||
CheckInstall();
|
||||
}
|
||||
|
||||
Console.WriteLine("[*] Notice to user:");
|
||||
Console.WriteLine(" - By using all or any portion of this software, you are agreeing");
|
||||
Console.WriteLine(" to be bound by all the terms and conditions of the license agreement.");
|
||||
Console.WriteLine(" - To read the license agreement, run the installer with -l parameter.");
|
||||
Console.WriteLine(" - If you do not agree to any terms of the license agreement,");
|
||||
Console.WriteLine(" do not use the software.");
|
||||
Console.WriteLine("[*] Installing...");
|
||||
|
||||
_wrapPath = toSystem32
|
||||
? @"%SystemRoot%\system32\rdpwrap.dll"
|
||||
: @"%ProgramFiles%\RDP Wrapper\rdpwrap.dll";
|
||||
|
||||
if (ArchHelper.Is64Bit) ArchHelper.DisableWow64Redirection();
|
||||
|
||||
CheckTermsrvVersion();
|
||||
CheckTermsrvProcess();
|
||||
|
||||
Console.WriteLine("[*] Extracting files...");
|
||||
_online = online;
|
||||
ExtractFiles();
|
||||
|
||||
Console.WriteLine("[*] Checking INI coverage for installed termsrv.dll version...");
|
||||
TryAutoGenerateOffsets();
|
||||
|
||||
Console.WriteLine("[*] Configuring service library...");
|
||||
SetWrapperDll();
|
||||
|
||||
Console.WriteLine("[*] Checking dependencies...");
|
||||
CheckTermsrvDependencies();
|
||||
|
||||
Console.WriteLine("[*] Terminating service...");
|
||||
SecurityHelper.AddPrivilege(NativeMethods.SE_DEBUG_NAME);
|
||||
ProcessHelper.KillProcess(_termServicePid);
|
||||
Thread.Sleep(1000);
|
||||
|
||||
RestartSharedServices();
|
||||
Thread.Sleep(500);
|
||||
ServiceHelper.StartService(TermService);
|
||||
Thread.Sleep(500);
|
||||
|
||||
Console.WriteLine("[*] Configuring registry...");
|
||||
TSConfigRegistry(enable: true);
|
||||
Console.WriteLine("[*] Configuring firewall...");
|
||||
TSConfigFirewall(enable: true);
|
||||
|
||||
Console.WriteLine("[+] Successfully installed.");
|
||||
|
||||
if (ArchHelper.Is64Bit) ArchHelper.RevertWow64Redirection();
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Uninstall the wrapper. Mirrors the <c>-u</c> branch.
|
||||
/// </summary>
|
||||
public int Uninstall(bool keepSettings)
|
||||
{
|
||||
if (!_installed)
|
||||
{
|
||||
Console.WriteLine("[*] RDP Wrapper Library is not installed.");
|
||||
return unchecked((int)NativeMethods.ERROR_ACCESS_DENIED);
|
||||
}
|
||||
|
||||
Console.WriteLine("[*] Uninstalling...");
|
||||
if (ArchHelper.Is64Bit) ArchHelper.DisableWow64Redirection();
|
||||
|
||||
CheckTermsrvProcess();
|
||||
|
||||
Console.WriteLine("[*] Resetting service library...");
|
||||
ResetServiceDll();
|
||||
|
||||
Console.WriteLine("[*] Terminating service...");
|
||||
SecurityHelper.AddPrivilege(NativeMethods.SE_DEBUG_NAME);
|
||||
ProcessHelper.KillProcess(_termServicePid);
|
||||
Thread.Sleep(1000);
|
||||
|
||||
Console.WriteLine("[*] Removing files...");
|
||||
DeleteFiles();
|
||||
|
||||
RestartSharedServices();
|
||||
Thread.Sleep(500);
|
||||
ServiceHelper.StartService(TermService);
|
||||
Thread.Sleep(500);
|
||||
|
||||
if (!keepSettings)
|
||||
{
|
||||
Console.WriteLine("[*] Configuring registry...");
|
||||
TSConfigRegistry(enable: false);
|
||||
Console.WriteLine("[*] Configuring firewall...");
|
||||
TSConfigFirewall(enable: false);
|
||||
}
|
||||
|
||||
if (ArchHelper.Is64Bit) ArchHelper.RevertWow64Redirection();
|
||||
Console.WriteLine("[+] Successfully uninstalled.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Download the latest rdpwrap.ini. Mirrors the <c>-w</c> / CheckUpdate branch.
|
||||
/// </summary>
|
||||
public int Update()
|
||||
{
|
||||
if (!_installed)
|
||||
{
|
||||
Console.WriteLine("[*] RDP Wrapper Library is not installed.");
|
||||
return unchecked((int)NativeMethods.ERROR_ACCESS_DENIED);
|
||||
}
|
||||
|
||||
Console.WriteLine("[*] Checking for updates...");
|
||||
return CheckUpdate();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Force-restart Terminal Services. Mirrors the <c>-r</c> branch.
|
||||
/// </summary>
|
||||
public int Restart()
|
||||
{
|
||||
Console.WriteLine("[*] Restarting...");
|
||||
CheckTermsrvProcess();
|
||||
|
||||
Console.WriteLine("[*] Terminating service...");
|
||||
SecurityHelper.AddPrivilege(NativeMethods.SE_DEBUG_NAME);
|
||||
ProcessHelper.KillProcess(_termServicePid);
|
||||
Thread.Sleep(1000);
|
||||
|
||||
RestartSharedServices();
|
||||
Thread.Sleep(500);
|
||||
ServiceHelper.StartService(TermService);
|
||||
|
||||
Console.WriteLine("[+] Done.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ── CheckInstall ──────────────────────────────────────────────────────────
|
||||
|
||||
/// <summary>
|
||||
/// Validates the TermService registry image path and sets
|
||||
/// <see cref="_installed"/> and <see cref="_termServicePath"/>.
|
||||
/// Mirrors the Delphi CheckInstall procedure.
|
||||
/// </summary>
|
||||
public void CheckInstall()
|
||||
{
|
||||
const string svcKey = @"SYSTEM\CurrentControlSet\Services\TermService";
|
||||
const string paramsKey = @"SYSTEM\CurrentControlSet\Services\TermService\Parameters";
|
||||
|
||||
var imagePath = RegistryHelper.ReadString(svcKey, "ImagePath") ?? string.Empty;
|
||||
if (!imagePath.Contains("svchost.exe", StringComparison.OrdinalIgnoreCase) &&
|
||||
!imagePath.Contains("svchost -k", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
Console.Error.WriteLine("[-] TermService is hosted in a custom application (BeTwin, etc.) - unsupported.");
|
||||
Console.Error.WriteLine($"[*] ImagePath: \"{imagePath}\".");
|
||||
Environment.Exit(unchecked((int)NativeMethods.ERROR_NOT_SUPPORTED));
|
||||
}
|
||||
|
||||
var serviceDll = RegistryHelper.ReadString(paramsKey, "ServiceDll") ?? string.Empty;
|
||||
if (!serviceDll.Contains("termsrv.dll", StringComparison.OrdinalIgnoreCase) &&
|
||||
!serviceDll.Contains("rdpwrap.dll", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
Console.Error.WriteLine("[-] Another third-party TermService library is installed.");
|
||||
Console.Error.WriteLine($"[*] ServiceDll: \"{serviceDll}\".");
|
||||
Environment.Exit(unchecked((int)NativeMethods.ERROR_NOT_SUPPORTED));
|
||||
}
|
||||
|
||||
_termServicePath = serviceDll;
|
||||
_installed = serviceDll.Contains("rdpwrap.dll", StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
// ── CheckTermsrvProcess ────────────────────────────────────────────────────
|
||||
|
||||
/// <summary>
|
||||
/// Finds the TermService process ID, auto-starts the service if needed,
|
||||
/// and collects co-hosted service names. Mirrors CheckTermsrvProcess.
|
||||
/// </summary>
|
||||
private void CheckTermsrvProcess()
|
||||
{
|
||||
bool started = false;
|
||||
retry:
|
||||
var services = ServiceHelper.EnumServiceProcesses();
|
||||
var ts = services.FirstOrDefault(s =>
|
||||
s.ServiceName.Equals(TermService, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
if (ts is null)
|
||||
{
|
||||
Console.Error.WriteLine($"[-] {TermService} not found.");
|
||||
Environment.Exit(unchecked((int)NativeMethods.ERROR_SERVICE_DOES_NOT_EXIST));
|
||||
return;
|
||||
}
|
||||
|
||||
if (ts.ProcessId == 0)
|
||||
{
|
||||
if (started)
|
||||
{
|
||||
Console.Error.WriteLine("[-] Failed to set up TermService. Unknown error.");
|
||||
Environment.Exit(unchecked((int)NativeMethods.ERROR_SERVICE_NOT_ACTIVE));
|
||||
return;
|
||||
}
|
||||
ServiceHelper.SetStartType(TermService, NativeMethods.SERVICE_AUTO_START);
|
||||
ServiceHelper.StartService(TermService);
|
||||
started = true;
|
||||
goto retry;
|
||||
}
|
||||
|
||||
_termServicePid = ts.ProcessId;
|
||||
Console.WriteLine($"[+] TermService found (pid {_termServicePid}).");
|
||||
|
||||
_shareServices = services
|
||||
.Where(s => s.ProcessId == _termServicePid &&
|
||||
!s.ServiceName.Equals(TermService, StringComparison.OrdinalIgnoreCase))
|
||||
.Select(s => s.ServiceName)
|
||||
.ToArray();
|
||||
|
||||
if (_shareServices.Length > 0)
|
||||
Console.WriteLine($"[*] Shared services found: {string.Join(", ", _shareServices)}");
|
||||
else
|
||||
Console.WriteLine("[*] No shared services found.");
|
||||
}
|
||||
|
||||
// ── CheckTermsrvDependencies ───────────────────────────────────────────────
|
||||
|
||||
/// <summary>
|
||||
/// Ensures CertPropSvc and SessionEnv are not disabled.
|
||||
/// Mirrors the Delphi CheckTermsrvDependencies procedure.
|
||||
/// </summary>
|
||||
private static void CheckTermsrvDependencies()
|
||||
{
|
||||
foreach (var svc in new[] { "CertPropSvc", "SessionEnv" })
|
||||
{
|
||||
if (ServiceHelper.GetStartType(svc) == (int)NativeMethods.SERVICE_DISABLED)
|
||||
ServiceHelper.SetStartType(svc, NativeMethods.SERVICE_DEMAND_START);
|
||||
}
|
||||
}
|
||||
|
||||
// ── CheckTermsrvVersion ────────────────────────────────────────────────────
|
||||
|
||||
/// <summary>
|
||||
/// Reads the termsrv.dll version and classifies support level.
|
||||
/// Mirrors the Delphi CheckTermsrvVersion procedure.
|
||||
/// </summary>
|
||||
private void CheckTermsrvVersion()
|
||||
{
|
||||
var fv = FileVersionHelper.GetVersionExpanded(_termServicePath);
|
||||
if (fv is null)
|
||||
{
|
||||
Console.Error.WriteLine("[-] Could not read termsrv.dll version.");
|
||||
return;
|
||||
}
|
||||
|
||||
_termSrvVerTxt = fv.ToString();
|
||||
Console.WriteLine($"[*] Terminal Services version: {_termSrvVerTxt}");
|
||||
|
||||
// Unsupported legacy versions
|
||||
if (fv.Major == 5)
|
||||
{
|
||||
var label = (ArchHelper.Arch == 32) ? "x86" : "x64";
|
||||
Console.WriteLine($"[!] Windows XP / Server 2003 ({label}) is not supported.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Load the built-in INI to check support level
|
||||
var builtInIni = ResourceHelper.ReadText(
|
||||
"RDPWInst.Resources.rdpwrap.ini",
|
||||
Assembly.GetExecutingAssembly()) ?? string.Empty;
|
||||
|
||||
int level = IniHelper.CheckSupportLevel(builtInIni, fv);
|
||||
|
||||
switch (level)
|
||||
{
|
||||
case 0:
|
||||
Console.WriteLine("[-] This version of Terminal Services is not supported.");
|
||||
Console.WriteLine("Try running \"update.bat\" or \"RDPWInst -w\" to download latest INI file.");
|
||||
break;
|
||||
case 1:
|
||||
Console.WriteLine("[!] This version of Terminal Services is supported partially.");
|
||||
Console.WriteLine("It means you may have some limitations such as only 2 concurrent sessions.");
|
||||
Console.WriteLine("Try running \"update.bat\" or \"RDPWInst -w\" to download latest INI file.");
|
||||
break;
|
||||
case 2:
|
||||
Console.WriteLine("[+] This version of Terminal Services is fully supported.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// ── TSConfigRegistry ──────────────────────────────────────────────────────
|
||||
|
||||
/// <summary>
|
||||
/// Writes (or clears) the TS-enable registry values.
|
||||
/// Mirrors the Delphi TSConfigRegistry procedure.
|
||||
/// </summary>
|
||||
private static void TSConfigRegistry(bool enable)
|
||||
{
|
||||
const string tsKey = @"SYSTEM\CurrentControlSet\Control\Terminal Server";
|
||||
const string licKey = @"SYSTEM\CurrentControlSet\Control\Terminal Server\Licensing Core";
|
||||
const string winlogon = @"SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon";
|
||||
const string addInsBase = @"SYSTEM\CurrentControlSet\Control\Terminal Server\AddIns";
|
||||
|
||||
RegistryHelper.WriteBool(tsKey, "fDenyTSConnections", !enable);
|
||||
|
||||
if (enable)
|
||||
{
|
||||
RegistryHelper.WriteBool(licKey, "EnableConcurrentSessions", true);
|
||||
RegistryHelper.WriteBool(winlogon, "AllowMultipleTSSessions", true);
|
||||
RegistryHelper.WriteBool(tsKey, "AllowRemoteRPC", true);
|
||||
RegistryHelper.WriteBool(tsKey, "EnableLinkedConnections", true);
|
||||
|
||||
// AddIns sub-keys (only create if the parent key is absent)
|
||||
if (Microsoft.Win32.Registry.LocalMachine.OpenSubKey(addInsBase) is null)
|
||||
{
|
||||
RegistryHelper.WriteString(addInsBase + @"\Clip Redirector", "Name", "RDPClip");
|
||||
RegistryHelper.WriteInt (addInsBase + @"\Clip Redirector", "Type", 3);
|
||||
RegistryHelper.WriteString(addInsBase + @"\DND Redirector", "Name", "RDPDND");
|
||||
RegistryHelper.WriteInt (addInsBase + @"\DND Redirector", "Type", 3);
|
||||
RegistryHelper.WriteInt (addInsBase + @"\Dynamic VC", "Type", -1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── TSConfigFirewall ──────────────────────────────────────────────────────
|
||||
|
||||
private static void TSConfigFirewall(bool enable)
|
||||
{
|
||||
if (enable)
|
||||
{
|
||||
ProcessHelper.ExecWait(
|
||||
"netsh advfirewall firewall add rule name=\"Remote Desktop\" " +
|
||||
"dir=in protocol=tcp localport=3389 profile=any action=allow");
|
||||
ProcessHelper.ExecWait(
|
||||
"netsh advfirewall firewall add rule name=\"Remote Desktop\" " +
|
||||
"dir=in protocol=udp localport=3389 profile=any action=allow");
|
||||
}
|
||||
else
|
||||
{
|
||||
ProcessHelper.ExecWait(
|
||||
"netsh advfirewall firewall delete rule name=\"Remote Desktop\"");
|
||||
}
|
||||
}
|
||||
|
||||
// ── ExtractFiles ──────────────────────────────────────────────────────────
|
||||
|
||||
/// <summary>
|
||||
/// Creates the install directory, sets ACLs, downloads or extracts the
|
||||
/// INI file, and extracts the correct rdpwrap DLL + optional helpers.
|
||||
/// Mirrors the Delphi ExtractFiles procedure.
|
||||
/// </summary>
|
||||
private void ExtractFiles()
|
||||
{
|
||||
var asm = Assembly.GetExecutingAssembly();
|
||||
var fullPath = ArchHelper.ExpandPath(_wrapPath);
|
||||
var dir = Path.GetDirectoryName(fullPath)!;
|
||||
|
||||
if (!Directory.Exists(dir))
|
||||
{
|
||||
Directory.CreateDirectory(dir);
|
||||
Console.WriteLine($"[+] Folder created: {dir}");
|
||||
SecurityHelper.GrantSidFullAccess(dir, "S-1-5-18"); // Local System
|
||||
SecurityHelper.GrantSidFullAccess(dir, "S-1-5-6"); // Service group
|
||||
}
|
||||
|
||||
// ── INI file ──
|
||||
var iniPath = Path.Combine(dir, "rdpwrap.ini");
|
||||
if (_online)
|
||||
{
|
||||
Console.WriteLine("[*] Downloading latest INI file...");
|
||||
var content = HttpHelper.DownloadString(ReleaseBaseUrl + "rdpwrap.ini");
|
||||
if (content is not null)
|
||||
{
|
||||
File.WriteAllText(iniPath, content, System.Text.Encoding.UTF8);
|
||||
Console.WriteLine($"[+] Latest INI file -> {iniPath}");
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine("[-] Failed to get online INI file, using built-in.");
|
||||
_online = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!_online)
|
||||
{
|
||||
// Try a local rdpwrap.ini beside the installer first
|
||||
var localIni = Path.Combine(
|
||||
Path.GetDirectoryName(Environment.ProcessPath ?? string.Empty) ?? ".",
|
||||
"rdpwrap.ini");
|
||||
|
||||
if (File.Exists(localIni))
|
||||
{
|
||||
File.Copy(localIni, iniPath, overwrite: true);
|
||||
Console.WriteLine($"[+] Current INI file -> {iniPath}");
|
||||
}
|
||||
else
|
||||
{
|
||||
ResourceHelper.ExtractToDisk("RDPWInst.Resources.rdpwrap.ini", iniPath, asm);
|
||||
}
|
||||
}
|
||||
|
||||
// ── Core DLL ──
|
||||
var dllRes = ArchHelper.Is64Bit ? "RDPWInst.Resources.rdpw64.dll"
|
||||
: "RDPWInst.Resources.rdpw32.dll";
|
||||
ResourceHelper.ExtractToDisk(dllRes, fullPath, asm);
|
||||
|
||||
// ── Optional helpers (Vista / Win7 clipboard redirect, Win10 RFX codec) ──
|
||||
ExtractOptionalHelper(asm, dir);
|
||||
}
|
||||
|
||||
private void ExtractOptionalHelper(Assembly asm, string dir)
|
||||
{
|
||||
var fv = FileVersionHelper.GetVersionExpanded(_termServicePath);
|
||||
if (fv is null) return;
|
||||
|
||||
var arch = ArchHelper.Is64Bit ? "64" : "32";
|
||||
|
||||
// rdpclip: Vista 6.0 and Win7 6.1
|
||||
string? clipRes = (fv.Major, fv.Minor) switch
|
||||
{
|
||||
(6, 0) => $"RDPWInst.Resources.rdpclip60{arch}.exe",
|
||||
(6, 1) => $"RDPWInst.Resources.rdpclip61{arch}.exe",
|
||||
_ => null
|
||||
};
|
||||
if (clipRes is not null)
|
||||
{
|
||||
var dest = ArchHelper.ExpandPath(@"%SystemRoot%\System32\rdpclip.exe");
|
||||
if (!File.Exists(dest))
|
||||
ResourceHelper.ExtractToDisk(clipRes, dest, asm);
|
||||
}
|
||||
|
||||
// rfxvmt.dll: Windows 10 (6.10.x maps to 10.0 in NT versioning)
|
||||
if (fv.Major == 10 && fv.Minor == 0)
|
||||
{
|
||||
var rfxRes = $"RDPWInst.Resources.rfxvmt{arch}.dll";
|
||||
var rfxDest = ArchHelper.ExpandPath(@"%SystemRoot%\System32\rfxvmt.dll");
|
||||
if (!File.Exists(rfxDest))
|
||||
ResourceHelper.ExtractToDisk(rfxRes, rfxDest, asm);
|
||||
}
|
||||
}
|
||||
|
||||
// ── SetWrapperDll / ResetServiceDll ──────────────────────────────────────
|
||||
|
||||
private void SetWrapperDll()
|
||||
{
|
||||
const string key = @"SYSTEM\CurrentControlSet\Services\TermService\Parameters";
|
||||
RegistryHelper.WriteExpandString(key, "ServiceDll", _wrapPath);
|
||||
|
||||
// Vista 6.0 workaround — reg.exe write to bypass WOW64 quirk
|
||||
var fv = FileVersionHelper.GetVersionExpanded(_termServicePath);
|
||||
if (fv is { Major: 6, Minor: 0 } && ArchHelper.Is64Bit)
|
||||
{
|
||||
ProcessHelper.ExecWait(
|
||||
$"\"{ArchHelper.ExpandPath("%SystemRoot%")}\\system32\\reg.exe\" " +
|
||||
$"add HKLM\\SYSTEM\\CurrentControlSet\\Services\\TermService\\Parameters " +
|
||||
$"/v ServiceDll /t REG_EXPAND_SZ /d \"{_wrapPath}\" /f");
|
||||
}
|
||||
}
|
||||
|
||||
private static void ResetServiceDll()
|
||||
{
|
||||
const string key = @"SYSTEM\CurrentControlSet\Services\TermService\Parameters";
|
||||
RegistryHelper.WriteExpandString(key, "ServiceDll",
|
||||
@"%SystemRoot%\System32\termsrv.dll");
|
||||
}
|
||||
|
||||
// ── DeleteFiles ───────────────────────────────────────────────────────────
|
||||
|
||||
private void DeleteFiles()
|
||||
{
|
||||
var fullPath = ArchHelper.ExpandPath(_termServicePath);
|
||||
var dir = Path.GetDirectoryName(fullPath)!;
|
||||
var iniPath = Path.Combine(dir, "rdpwrap.ini");
|
||||
|
||||
TryDelete(iniPath);
|
||||
TryDelete(fullPath);
|
||||
TryRemoveDir(dir);
|
||||
}
|
||||
|
||||
private static void TryDelete(string path)
|
||||
{
|
||||
try
|
||||
{
|
||||
File.Delete(path);
|
||||
Console.WriteLine($"[+] Removed file: {path}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.Error.WriteLine($"[-] DeleteFile error: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private static void TryRemoveDir(string dir)
|
||||
{
|
||||
try
|
||||
{
|
||||
Directory.Delete(dir);
|
||||
Console.WriteLine($"[+] Removed folder: {dir}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.Error.WriteLine($"[-] RemoveDirectory error: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
// ── TryAutoGenerateOffsets ────────────────────────────────────────────────
|
||||
|
||||
/// <summary>
|
||||
/// If the running termsrv.dll version is absent from rdpwrap.ini, downloads
|
||||
/// RDPWrapOffsetFinder + Zydis from the release assets and runs the finder
|
||||
/// to append generated offsets. Mirrors the Delphi TryAutoGenerateOffsets.
|
||||
/// </summary>
|
||||
private void TryAutoGenerateOffsets()
|
||||
{
|
||||
if (string.IsNullOrEmpty(_termSrvVerTxt)) return;
|
||||
|
||||
var fullPath = ArchHelper.ExpandPath(_wrapPath);
|
||||
var iniPath = Path.Combine(Path.GetDirectoryName(fullPath)!, "rdpwrap.ini");
|
||||
|
||||
if (IniHelper.HasSection(iniPath, _termSrvVerTxt))
|
||||
{
|
||||
Console.WriteLine($"[+] Version {_termSrvVerTxt} is covered in INI.");
|
||||
return;
|
||||
}
|
||||
|
||||
Console.WriteLine($"[!] Version {_termSrvVerTxt} not found in INI.");
|
||||
Console.WriteLine("[*] Attempting automatic offset generation via RDPWrapOffsetFinder...");
|
||||
|
||||
var archSuffix = ArchHelper.Is64Bit ? "_x64" : "_x86";
|
||||
var tempDir = Path.Combine(Path.GetTempPath(), "rdpwrapoffset");
|
||||
|
||||
try { Directory.CreateDirectory(tempDir); }
|
||||
catch
|
||||
{
|
||||
Console.Error.WriteLine("[-] Could not create temp directory. Skipping auto-generation.");
|
||||
return;
|
||||
}
|
||||
|
||||
var exePath = Path.Combine(tempDir, "RDPWrapOffsetFinder.exe");
|
||||
var dllPath = Path.Combine(tempDir, "Zydis.dll");
|
||||
|
||||
Console.WriteLine($"[*] Downloading RDPWrapOffsetFinder{archSuffix}.exe ...");
|
||||
if (!HttpHelper.DownloadFile(ReleaseBaseUrl + $"RDPWrapOffsetFinder{archSuffix}.exe", exePath))
|
||||
{
|
||||
Console.Error.WriteLine("[-] Download failed. The release asset may not yet be published.");
|
||||
Console.Error.WriteLine("[!] Run the publish-ini workflow on the sjackson0109/rdpwrap repository,");
|
||||
Console.Error.WriteLine("[!] then re-run this installer to enable auto-generation.");
|
||||
return;
|
||||
}
|
||||
|
||||
Console.WriteLine($"[*] Downloading Zydis{archSuffix}.dll ...");
|
||||
if (!HttpHelper.DownloadFile(ReleaseBaseUrl + $"Zydis{archSuffix}.dll", dllPath))
|
||||
{
|
||||
Console.Error.WriteLine("[-] Zydis download failed. Skipping auto-generation.");
|
||||
File.Delete(exePath);
|
||||
return;
|
||||
}
|
||||
|
||||
Console.WriteLine($"[*] Running offset finder for termsrv.dll {_termSrvVerTxt} ...");
|
||||
// Run via cmd.exe so that >> redirect to the INI file functions correctly
|
||||
var sysCmd = ArchHelper.ExpandPath(@"%SystemRoot%\System32\cmd.exe");
|
||||
ProcessHelper.ExecWait($"\"{sysCmd}\" /c \"\"{exePath}\" >> \"{iniPath}\"\"");
|
||||
|
||||
if (IniHelper.HasSection(iniPath, _termSrvVerTxt))
|
||||
Console.WriteLine($"[+] Offsets generated successfully for version {_termSrvVerTxt}");
|
||||
else
|
||||
Console.WriteLine($"[!] Offset finder ran but [{_termSrvVerTxt}] was not added. " +
|
||||
"Session may be limited or unstable for this build.");
|
||||
|
||||
// Clean up temporary tool files
|
||||
try
|
||||
{
|
||||
File.Delete(exePath);
|
||||
File.Delete(dllPath);
|
||||
Directory.Delete(tempDir);
|
||||
}
|
||||
catch { /* best-effort */ }
|
||||
}
|
||||
|
||||
// ── CheckUpdate (GitINIFile path) ─────────────────────────────────────────
|
||||
|
||||
private int CheckUpdate()
|
||||
{
|
||||
var fullPath = ArchHelper.ExpandPath(_termServicePath);
|
||||
var iniPath = Path.Combine(Path.GetDirectoryName(fullPath)!, "rdpwrap.ini");
|
||||
|
||||
if (!TryGetIniDate(iniPath, null, out int oldDate))
|
||||
return unchecked((int)NativeMethods.ERROR_ACCESS_DENIED);
|
||||
|
||||
Console.WriteLine($"[*] Current update date: {FormatDate(oldDate)}");
|
||||
|
||||
var latest = HttpHelper.DownloadString(ReleaseBaseUrl + "rdpwrap.ini");
|
||||
if (latest is null)
|
||||
{
|
||||
Console.Error.WriteLine("[-] Failed to download latest INI from GitHub.");
|
||||
return unchecked((int)NativeMethods.ERROR_ACCESS_DENIED);
|
||||
}
|
||||
|
||||
if (!TryGetIniDate(null, latest, out int newDate))
|
||||
return unchecked((int)NativeMethods.ERROR_ACCESS_DENIED);
|
||||
|
||||
Console.WriteLine($"[*] Latest update date: {FormatDate(newDate)}");
|
||||
|
||||
if (newDate == oldDate)
|
||||
{
|
||||
Console.WriteLine("[*] Everything is up to date.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (newDate > oldDate)
|
||||
{
|
||||
Console.WriteLine("[+] New update is available, updating...");
|
||||
CheckTermsrvProcess();
|
||||
|
||||
Console.WriteLine("[*] Terminating service...");
|
||||
SecurityHelper.AddPrivilege(NativeMethods.SE_DEBUG_NAME);
|
||||
ProcessHelper.KillProcess(_termServicePid);
|
||||
Thread.Sleep(1000);
|
||||
|
||||
RestartSharedServices();
|
||||
Thread.Sleep(500);
|
||||
|
||||
File.WriteAllText(iniPath, latest, System.Text.Encoding.UTF8);
|
||||
Console.WriteLine($"[+] INI file updated: {iniPath}");
|
||||
|
||||
// Recompute version for offset generation
|
||||
var fv = FileVersionHelper.GetVersionExpanded(_termServicePath);
|
||||
if (fv is not null) _termSrvVerTxt = fv.ToString();
|
||||
|
||||
Console.WriteLine("[*] Checking INI coverage for installed termsrv.dll version...");
|
||||
TryAutoGenerateOffsets();
|
||||
|
||||
ServiceHelper.StartService(TermService);
|
||||
Console.WriteLine("[+] Update completed.");
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine("[*] Your INI file is newer than public file. Are you a developer? :)");
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ── Helpers ───────────────────────────────────────────────────────────────
|
||||
|
||||
private void RestartSharedServices()
|
||||
{
|
||||
foreach (var svc in _shareServices)
|
||||
ServiceHelper.StartService(svc);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses the <c>Updated=YYYYMMDD</c> line from an INI file or string.
|
||||
/// Mirrors the Delphi CheckINIDate function.
|
||||
/// </summary>
|
||||
private static bool TryGetIniDate(string? filePath, string? content, out int date)
|
||||
{
|
||||
date = 0;
|
||||
IEnumerable<string> lines;
|
||||
|
||||
if (filePath is not null)
|
||||
{
|
||||
if (!File.Exists(filePath))
|
||||
{
|
||||
Console.Error.WriteLine("[-] Failed to read INI file.");
|
||||
return false;
|
||||
}
|
||||
lines = File.ReadLines(filePath);
|
||||
}
|
||||
else
|
||||
{
|
||||
lines = (content ?? string.Empty).Split('\n');
|
||||
}
|
||||
|
||||
foreach (var line in lines)
|
||||
{
|
||||
var trimmed = line.TrimEnd('\r');
|
||||
if (!trimmed.StartsWith("Updated=", StringComparison.Ordinal)) continue;
|
||||
|
||||
var raw = trimmed["Updated=".Length..].Replace("-", "");
|
||||
if (int.TryParse(raw, out date)) return true;
|
||||
|
||||
Console.Error.WriteLine("[-] Wrong INI date format.");
|
||||
return false;
|
||||
}
|
||||
|
||||
Console.Error.WriteLine("[-] Failed to check INI date (Updated= line not found).");
|
||||
return false;
|
||||
}
|
||||
|
||||
private static string FormatDate(int d)
|
||||
{
|
||||
int y = d / 10000, m = (d / 100) % 100, day = d % 100;
|
||||
return $"{y}.{m:D2}.{day:D2}";
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,114 @@
|
||||
// Copyright 2026 sjackson0109 — Apache License 2.0
|
||||
//
|
||||
// RDPWInst — RDP Wrapper Library Installer
|
||||
|
||||
using RDPWrap.Common;
|
||||
|
||||
namespace RDPWInst;
|
||||
|
||||
internal static class Program
|
||||
{
|
||||
private static string Banner
|
||||
{
|
||||
get
|
||||
{
|
||||
var v = System.Reflection.Assembly.GetExecutingAssembly().GetName().Version;
|
||||
string version = v is null ? "unknown" : $"{v.Major}.{v.Minor}.{v.Build}";
|
||||
return
|
||||
$"RDP Wrapper Library\r\n" +
|
||||
$"Installer v{version} (C# edition)\r\n" +
|
||||
"Copyright (C) Stas'M Corp. 2018\r\n" +
|
||||
"Maintained by sjackson0109 2026\r\n";
|
||||
}
|
||||
}
|
||||
|
||||
private const string Usage =
|
||||
"USAGE:\r\n" +
|
||||
"RDPWInst.exe [-l|-i[-s][-o][-f]|-w|-u[-k]|-r]\r\n\r\n" +
|
||||
"-l display the license agreement\r\n" +
|
||||
"-i install wrapper to Program Files folder (default)\r\n" +
|
||||
"-i -s install wrapper to System32 folder\r\n" +
|
||||
"-i -o online install mode (loads latest INI file)\r\n" +
|
||||
"-i -f force install: silently uninstall existing installation first\r\n" +
|
||||
"-w get latest update for INI file\r\n" +
|
||||
"-u uninstall wrapper\r\n" +
|
||||
"-u -k uninstall wrapper and keep settings\r\n" +
|
||||
"-r force restart Terminal Services\r\n";
|
||||
|
||||
internal static int Main(string[] args)
|
||||
{
|
||||
Console.OutputEncoding = System.Text.Encoding.UTF8;
|
||||
Console.WriteLine(Banner);
|
||||
|
||||
// Validate args
|
||||
if (args.Length < 1 ||
|
||||
(args[0] != "-l" &&
|
||||
args[0] != "-i" &&
|
||||
args[0] != "-w" &&
|
||||
args[0] != "-u" &&
|
||||
args[0] != "-r"))
|
||||
{
|
||||
Console.WriteLine(Usage);
|
||||
Pause();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// -l print license
|
||||
if (args[0] == "-l")
|
||||
{
|
||||
var license = ResourceHelper.ReadText("RDPWInst.Resources.license.txt",
|
||||
System.Reflection.Assembly.GetExecutingAssembly());
|
||||
Console.WriteLine(license ?? "(license resource not found)");
|
||||
Pause();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Windows Vista / Server 2008 minimum check
|
||||
if (Environment.OSVersion.Version < new Version(6, 0))
|
||||
{
|
||||
Console.Error.WriteLine("[-] Unsupported Windows version:");
|
||||
Console.Error.WriteLine(" only >= 6.0 (Vista, Server 2008 and newer) are supported.");
|
||||
Pause();
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!ArchHelper.IsSupported)
|
||||
{
|
||||
Console.Error.WriteLine("[-] Unsupported processor architecture.");
|
||||
Pause();
|
||||
return 1;
|
||||
}
|
||||
|
||||
var engine = new InstallerEngine();
|
||||
engine.CheckInstall();
|
||||
|
||||
int rc = args[0] switch
|
||||
{
|
||||
"-i" => engine.Install(
|
||||
toSystem32: args.Contains("-s"),
|
||||
online: args.Contains("-o"),
|
||||
force: args.Contains("-f")),
|
||||
"-u" => engine.Uninstall(keepSettings: args.Contains("-k")),
|
||||
"-w" => engine.Update(),
|
||||
"-r" => engine.Restart(),
|
||||
_ => 0
|
||||
};
|
||||
|
||||
Pause();
|
||||
return rc;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Waits for a keypress only when the process owns its console window
|
||||
/// (i.e. was launched via double-click or UAC elevation rather than
|
||||
/// piped/redirected from a script).
|
||||
/// </summary>
|
||||
private static void Pause()
|
||||
{
|
||||
if (!Console.IsInputRedirected && !Console.IsOutputRedirected)
|
||||
{
|
||||
Console.WriteLine("\nPress any key to exit...");
|
||||
Console.ReadKey(intercept: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,41 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<RootNamespace>RDPWInst</RootNamespace>
|
||||
<AssemblyName>RDPWInst</AssemblyName>
|
||||
<ApplicationManifest>app.manifest</ApplicationManifest>
|
||||
<Description>Command-line installer and manager for RDP Wrapper — installs rdpwrap.dll and configures the Terminal Services layer.</Description>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\RDPWrap\RDPWrap.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<!--
|
||||
Embedded payload resources.
|
||||
Place the compiled DLL binaries and rdpwrap.ini into src-csharp/RDPWInst/Resources/
|
||||
before building. The resource names referenced in InstallerEngine.cs are:
|
||||
RDPWInst.Resources.rdpw32.dll
|
||||
RDPWInst.Resources.rdpw64.dll
|
||||
RDPWInst.Resources.rdpwrap.ini
|
||||
RDPWInst.Resources.rdpclip6032.exe
|
||||
RDPWInst.Resources.rdpclip6064.exe
|
||||
RDPWInst.Resources.rdpclip6132.exe
|
||||
RDPWInst.Resources.rdpclip6164.exe
|
||||
RDPWInst.Resources.rfxvmt32.dll
|
||||
RDPWInst.Resources.rfxvmt64.dll
|
||||
RDPWInst.Resources.license.txt
|
||||
-->
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="Resources\rdpw32.dll" Condition="Exists('Resources\rdpw32.dll')" />
|
||||
<EmbeddedResource Include="Resources\rdpw64.dll" Condition="Exists('Resources\rdpw64.dll')" />
|
||||
<EmbeddedResource Include="Resources\rdpwrap.ini" Condition="Exists('Resources\rdpwrap.ini')" />
|
||||
<EmbeddedResource Include="Resources\rdpclip6032.exe" Condition="Exists('Resources\rdpclip6032.exe')" />
|
||||
<EmbeddedResource Include="Resources\rdpclip6064.exe" Condition="Exists('Resources\rdpclip6064.exe')" />
|
||||
<EmbeddedResource Include="Resources\rdpclip6132.exe" Condition="Exists('Resources\rdpclip6132.exe')" />
|
||||
<EmbeddedResource Include="Resources\rdpclip6164.exe" Condition="Exists('Resources\rdpclip6164.exe')" />
|
||||
<EmbeddedResource Include="Resources\rfxvmt32.dll" Condition="Exists('Resources\rfxvmt32.dll')" />
|
||||
<EmbeddedResource Include="Resources\rfxvmt64.dll" Condition="Exists('Resources\rfxvmt64.dll')" />
|
||||
<EmbeddedResource Include="Resources\license.txt" Condition="Exists('Resources\license.txt')" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@ -0,0 +1,31 @@
|
||||
# RDPWInst/Resources
|
||||
|
||||
Place the compiled binary payloads here before building RDPWInst.exe.
|
||||
These files are embedded as manifest resources at build time via `<EmbeddedResource Condition="Exists(...)">` entries in `RDPWInst.csproj` — if a file is absent the resource is simply omitted and the build still succeeds (online install mode is used as fallback).
|
||||
|
||||
## CI-staged files (automatically copied by `build-and-release.yml`)
|
||||
|
||||
| File | Source | Used when |
|
||||
|---------------|--------------------------------------------------|-----------------------------|
|
||||
| `rdpw32.dll` | Build output of `src-x86-x64-Fusix/` (Win32) | Always — 32-bit install |
|
||||
| `rdpw64.dll` | Build output of `src-x86-x64-Fusix/` (x64) | Always — 64-bit install |
|
||||
| `rdpwrap.ini` | `msi/rdpwrap.ini` from repo | Always — offline fallback |
|
||||
| `license.txt` | Repo `LICENSE` (copied as plain text) | `RDPWInst -l` flag |
|
||||
|
||||
## Optional legacy files (not in VCS, not staged by CI)
|
||||
|
||||
| File | Purpose | Status / action required |
|
||||
|-------------------|--------------------------------------------|--------------------------------------------------------------------------------------|
|
||||
| `rdpclip6032.exe` | Updated rdpclip for Vista x86 | Not redistributable by this project — obtain from original stascorp release if needed |
|
||||
| `rdpclip6064.exe` | Updated rdpclip for Vista x64 | Same as above |
|
||||
| `rdpclip6132.exe` | Updated rdpclip for Win7 x86 | Same as above |
|
||||
| `rdpclip6164.exe` | Updated rdpclip for Win7 x64 | Same as above |
|
||||
| `rfxvmt32.dll` | RemoteFX codec for Win10 Home x86 | **Not redistributable** — must be extracted from a Windows 10 Home installation at `C:\Windows\System32\rfxvmt.dll`. See [#194](https://github.com/stascorp/rdpwrap/issues/194) for context. |
|
||||
| `rfxvmt64.dll` | RemoteFX codec for Win10 Home x64 | Same as above |
|
||||
|
||||
> **Decision note:** `rfxvmt.dll` is a Microsoft-owned component and cannot be legally bundled.
|
||||
> `InstallerEngine.cs` handles the missing-rfxvmt case at runtime: if the file is absent from
|
||||
> the embedded resources and absent from the install directory, a warning is printed and the
|
||||
> user is directed to copy it manually. No CI step attempts to source or stage these files.
|
||||
|
||||
All binary files in this folder are excluded from version control via `.gitignore`.
|
||||
@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<assemblyIdentity version="1.0.0.0" name="RDPWInst.app"/>
|
||||
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
|
||||
<security>
|
||||
<requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
<!-- requireAdministrator: installer must run elevated. -->
|
||||
<requestedExecutionLevel level="requireAdministrator" uiAccess="false" />
|
||||
</requestedPrivileges>
|
||||
</security>
|
||||
</trustInfo>
|
||||
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
|
||||
<application>
|
||||
<!-- Windows 10 / Server 2016+ -->
|
||||
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
|
||||
<!-- Windows 8.1 / Server 2012 R2 -->
|
||||
<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}" />
|
||||
<!-- Windows 8 / Server 2012 -->
|
||||
<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}" />
|
||||
<!-- Windows 7 / Server 2008 R2 -->
|
||||
<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}" />
|
||||
<!-- Windows Vista / Server 2008 -->
|
||||
<supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}" />
|
||||
</application>
|
||||
</compatibility>
|
||||
</assembly>
|
||||
@ -0,0 +1,10 @@
|
||||
{
|
||||
"version": 1,
|
||||
"dependencies": {
|
||||
"net10.0-windows7.0": {
|
||||
"rdpwrap": {
|
||||
"type": "Project"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,58 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.11.35303.130
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RDPWrap", "RDPWrap\RDPWrap.csproj", "{A1B2C3D4-0001-4000-8000-000000000001}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RDPWInst", "RDPWInst\RDPWInst.csproj", "{A1B2C3D4-0001-4000-8000-000000000002}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RDPConf", "RDPConf\RDPConf.csproj", "{A1B2C3D4-0001-4000-8000-000000000003}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RDPCheck", "RDPCheck\RDPCheck.csproj", "{A1B2C3D4-0001-4000-8000-000000000004}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|x64 = Debug|x64
|
||||
Debug|x86 = Debug|x86
|
||||
Release|x64 = Release|x64
|
||||
Release|x86 = Release|x86
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{A1B2C3D4-0001-4000-8000-000000000001}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{A1B2C3D4-0001-4000-8000-000000000001}.Debug|x64.Build.0 = Debug|x64
|
||||
{A1B2C3D4-0001-4000-8000-000000000001}.Debug|x86.ActiveCfg = Debug|x86
|
||||
{A1B2C3D4-0001-4000-8000-000000000001}.Debug|x86.Build.0 = Debug|x86
|
||||
{A1B2C3D4-0001-4000-8000-000000000001}.Release|x64.ActiveCfg = Release|x64
|
||||
{A1B2C3D4-0001-4000-8000-000000000001}.Release|x64.Build.0 = Release|x64
|
||||
{A1B2C3D4-0001-4000-8000-000000000001}.Release|x86.ActiveCfg = Release|x86
|
||||
{A1B2C3D4-0001-4000-8000-000000000001}.Release|x86.Build.0 = Release|x86
|
||||
{A1B2C3D4-0001-4000-8000-000000000002}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{A1B2C3D4-0001-4000-8000-000000000002}.Debug|x64.Build.0 = Debug|x64
|
||||
{A1B2C3D4-0001-4000-8000-000000000002}.Debug|x86.ActiveCfg = Debug|x86
|
||||
{A1B2C3D4-0001-4000-8000-000000000002}.Debug|x86.Build.0 = Debug|x86
|
||||
{A1B2C3D4-0001-4000-8000-000000000002}.Release|x64.ActiveCfg = Release|x64
|
||||
{A1B2C3D4-0001-4000-8000-000000000002}.Release|x64.Build.0 = Release|x64
|
||||
{A1B2C3D4-0001-4000-8000-000000000002}.Release|x86.ActiveCfg = Release|x86
|
||||
{A1B2C3D4-0001-4000-8000-000000000002}.Release|x86.Build.0 = Release|x86
|
||||
{A1B2C3D4-0001-4000-8000-000000000003}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{A1B2C3D4-0001-4000-8000-000000000003}.Debug|x64.Build.0 = Debug|x64
|
||||
{A1B2C3D4-0001-4000-8000-000000000003}.Debug|x86.ActiveCfg = Debug|x86
|
||||
{A1B2C3D4-0001-4000-8000-000000000003}.Debug|x86.Build.0 = Debug|x86
|
||||
{A1B2C3D4-0001-4000-8000-000000000003}.Release|x64.ActiveCfg = Release|x64
|
||||
{A1B2C3D4-0001-4000-8000-000000000003}.Release|x64.Build.0 = Release|x64
|
||||
{A1B2C3D4-0001-4000-8000-000000000003}.Release|x86.ActiveCfg = Release|x86
|
||||
{A1B2C3D4-0001-4000-8000-000000000003}.Release|x86.Build.0 = Release|x86
|
||||
{A1B2C3D4-0001-4000-8000-000000000004}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{A1B2C3D4-0001-4000-8000-000000000004}.Debug|x64.Build.0 = Debug|x64
|
||||
{A1B2C3D4-0001-4000-8000-000000000004}.Debug|x86.ActiveCfg = Debug|x86
|
||||
{A1B2C3D4-0001-4000-8000-000000000004}.Debug|x86.Build.0 = Debug|x86
|
||||
{A1B2C3D4-0001-4000-8000-000000000004}.Release|x64.ActiveCfg = Release|x64
|
||||
{A1B2C3D4-0001-4000-8000-000000000004}.Release|x64.Build.0 = Release|x64
|
||||
{A1B2C3D4-0001-4000-8000-000000000004}.Release|x86.ActiveCfg = Release|x86
|
||||
{A1B2C3D4-0001-4000-8000-000000000004}.Release|x86.Build.0 = Release|x86
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
@ -0,0 +1,80 @@
|
||||
// Copyright 2026 sjackson0109 — Apache License 2.0
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace RDPWrap.Common;
|
||||
|
||||
/// <summary>
|
||||
/// Architecture detection and WOW64 file-system redirection control.
|
||||
/// Mirrors the Arch / DisableWowRedirection / RevertWowRedirection logic
|
||||
/// from RDPWInst.dpr and RDPConf MainUnit.pas.
|
||||
/// </summary>
|
||||
public static class ArchHelper
|
||||
{
|
||||
private static readonly Lazy<byte> _arch = new(DetectArch);
|
||||
|
||||
/// <summary>Raw architecture byte: 32 or 64. 0 = unsupported.</summary>
|
||||
public static byte Arch => _arch.Value;
|
||||
|
||||
/// <summary><c>true</c> when running on a 64-bit Windows installation.</summary>
|
||||
public static bool Is64Bit => Arch == 64;
|
||||
|
||||
/// <summary>
|
||||
/// Returns <c>true</c> when the processor architecture is supported
|
||||
/// (x86 or x64). Itanium and unknown architectures return <c>false</c>.
|
||||
/// </summary>
|
||||
public static bool IsSupported => Arch != 0;
|
||||
|
||||
private static byte DetectArch()
|
||||
{
|
||||
NativeMethods.GetNativeSystemInfo(out var si);
|
||||
return si.wProcessorArchitecture switch
|
||||
{
|
||||
NativeMethods.PROCESSOR_ARCHITECTURE_INTEL => 32,
|
||||
NativeMethods.PROCESSOR_ARCHITECTURE_AMD64 => 64,
|
||||
_ => 0 // Itanium or unknown — unsupported
|
||||
};
|
||||
}
|
||||
|
||||
// ── WOW64 filesystem redirection ─────────────────────────────────────────
|
||||
|
||||
private static IntPtr _wow64OldValue = IntPtr.Zero;
|
||||
|
||||
/// <summary>
|
||||
/// Disables WOW64 filesystem redirection so that 32-bit processes can
|
||||
/// reach the real <c>%SystemRoot%\System32</c>. Call only on 64-bit hosts.
|
||||
/// Returns <c>true</c> on success.
|
||||
/// </summary>
|
||||
public static bool DisableWow64Redirection()
|
||||
{
|
||||
if (!Is64Bit) return false;
|
||||
return NativeMethods.Wow64DisableWow64FsRedirection(out _wow64OldValue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reverts the WOW64 filesystem redirection state saved by the last call
|
||||
/// to <see cref="DisableWow64Redirection"/>.
|
||||
/// </summary>
|
||||
public static bool RevertWow64Redirection()
|
||||
{
|
||||
if (!Is64Bit) return false;
|
||||
return NativeMethods.Wow64RevertWow64FsRedirection(_wow64OldValue);
|
||||
}
|
||||
|
||||
// ── Environment path expansion ────────────────────────────────────────────
|
||||
|
||||
/// <summary>
|
||||
/// Expands environment strings in <paramref name="path"/>, replacing
|
||||
/// <c>%ProgramFiles%</c> with <c>%ProgramW6432%</c> on 64-bit hosts
|
||||
/// to avoid redirection to the x86 Program Files folder.
|
||||
/// </summary>
|
||||
public static string ExpandPath(string path)
|
||||
{
|
||||
if (Is64Bit)
|
||||
path = path.Replace("%ProgramFiles%", "%ProgramW6432%",
|
||||
StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
var buf = new System.Text.StringBuilder(1024);
|
||||
NativeMethods.ExpandEnvironmentStrings(path, buf, (uint)buf.Capacity);
|
||||
return buf.ToString();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,64 @@
|
||||
// Copyright 2026 sjackson0109 — Apache License 2.0
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace RDPWrap.Common;
|
||||
|
||||
/// <summary>
|
||||
/// File-version reading helper. Mirrors the GetFileVersion function used in
|
||||
/// RDPWInst.dpr and RDPConf MainUnit.pas.
|
||||
/// </summary>
|
||||
public static class FileVersionHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Strongly-typed representation of a Windows file version.
|
||||
/// </summary>
|
||||
public record FileVersionInfo(
|
||||
ushort Major,
|
||||
ushort Minor,
|
||||
ushort Release,
|
||||
ushort Build,
|
||||
bool IsDebug,
|
||||
bool IsPrerelease,
|
||||
bool IsPrivate,
|
||||
bool IsSpecial)
|
||||
{
|
||||
/// <summary>e.g. "10.0.26100.3476"</summary>
|
||||
public override string ToString() => $"{Major}.{Minor}.{Release}.{Build}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the file version of <paramref name="filePath"/>, or <c>null</c>
|
||||
/// if the file does not exist or has no version resource.
|
||||
/// Uses the BCL <see cref="System.Diagnostics.FileVersionInfo"/> which does
|
||||
/// not require loading the DLL as executable — safe for locked DLLs.
|
||||
/// </summary>
|
||||
public static FileVersionInfo? GetVersion(string filePath)
|
||||
{
|
||||
if (!File.Exists(filePath)) return null;
|
||||
|
||||
try
|
||||
{
|
||||
var fvi = System.Diagnostics.FileVersionInfo.GetVersionInfo(filePath);
|
||||
return new FileVersionInfo(
|
||||
(ushort)(fvi.FileMajorPart),
|
||||
(ushort)(fvi.FileMinorPart),
|
||||
(ushort)(fvi.FileBuildPart),
|
||||
(ushort)(fvi.FilePrivatePart),
|
||||
fvi.IsDebug,
|
||||
fvi.IsPreRelease,
|
||||
fvi.IsPrivateBuild,
|
||||
fvi.IsSpecialBuild);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convenience overload: resolves the path via
|
||||
/// <see cref="ArchHelper.ExpandPath"/> before reading.
|
||||
/// </summary>
|
||||
public static FileVersionInfo? GetVersionExpanded(string pathWithEnvVars)
|
||||
=> GetVersion(ArchHelper.ExpandPath(pathWithEnvVars));
|
||||
}
|
||||
@ -0,0 +1,80 @@
|
||||
// Copyright 2026 sjackson0109 — Apache License 2.0
|
||||
|
||||
namespace RDPWrap.Common;
|
||||
|
||||
/// <summary>
|
||||
/// HTTP download helpers that replace the WinInet-based GitINIFile /
|
||||
/// DownloadFileToDisk procedures from RDPWInst.dpr.
|
||||
/// Uses <see cref="HttpClient"/> with a shared static instance.
|
||||
/// </summary>
|
||||
public static class HttpHelper
|
||||
{
|
||||
// Single shared instance — HttpClient is designed to be reused.
|
||||
private static readonly HttpClient _client = new(new HttpClientHandler
|
||||
{
|
||||
AllowAutoRedirect = true,
|
||||
MaxAutomaticRedirections = 5,
|
||||
})
|
||||
{
|
||||
Timeout = TimeSpan.FromSeconds(60),
|
||||
DefaultRequestHeaders = { { "User-Agent", "RDP-Wrapper-Updater/1.0" } }
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Downloads the text content at <paramref name="url"/> and returns it as
|
||||
/// a string. Returns <c>null</c> on any failure.
|
||||
/// Mirrors the Delphi GitINIFile function.
|
||||
/// </summary>
|
||||
public static async Task<string?> DownloadStringAsync(string url)
|
||||
{
|
||||
try
|
||||
{
|
||||
return await _client.GetStringAsync(url).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.Error.WriteLine($"[-] HTTP download failed ({url}): {ex.Message}");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Downloads the binary content at <paramref name="url"/> and saves it to
|
||||
/// <paramref name="destPath"/>. Returns <c>true</c> when the file exists
|
||||
/// and is non-empty after download.
|
||||
/// Mirrors the Delphi DownloadFileToDisk function.
|
||||
/// </summary>
|
||||
public static async Task<bool> DownloadFileAsync(string url, string destPath)
|
||||
{
|
||||
try
|
||||
{
|
||||
using var response = await _client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead)
|
||||
.ConfigureAwait(false);
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
|
||||
await using var file = File.Create(destPath);
|
||||
await stream.CopyToAsync(file).ConfigureAwait(false);
|
||||
|
||||
return new FileInfo(destPath).Length > 0;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.Error.WriteLine($"[-] HTTP file download failed ({url}): {ex.Message}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Synchronous wrapper for <see cref="DownloadStringAsync"/> — suitable
|
||||
/// for the installer's purely-sequential flow.
|
||||
/// </summary>
|
||||
public static string? DownloadString(string url)
|
||||
=> DownloadStringAsync(url).GetAwaiter().GetResult();
|
||||
|
||||
/// <summary>
|
||||
/// Synchronous wrapper for <see cref="DownloadFileAsync"/>.
|
||||
/// </summary>
|
||||
public static bool DownloadFile(string url, string destPath)
|
||||
=> DownloadFileAsync(url, destPath).GetAwaiter().GetResult();
|
||||
}
|
||||
@ -0,0 +1,61 @@
|
||||
// Copyright 2026 sjackson0109 — Apache License 2.0
|
||||
|
||||
namespace RDPWrap.Common;
|
||||
|
||||
/// <summary>
|
||||
/// Lightweight INI-file helpers used to check whether a specific version
|
||||
/// section exists in rdpwrap.ini. Mirrors the INIHasSection function and
|
||||
/// CheckSupport version-lookup logic from RDPWInst.dpr and RDPConf MainUnit.pas.
|
||||
/// </summary>
|
||||
public static class IniHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns <c>true</c> when the INI file at <paramref name="iniPath"/>
|
||||
/// contains the section header <c>[<paramref name="section"/>]</c>.
|
||||
/// Mirrors the Delphi INIHasSection function.
|
||||
/// </summary>
|
||||
public static bool HasSection(string iniPath, string section)
|
||||
{
|
||||
if (!File.Exists(iniPath)) return false;
|
||||
var needle = $"[{section}]";
|
||||
foreach (var line in File.ReadLines(iniPath))
|
||||
{
|
||||
if (line.Contains(needle, StringComparison.Ordinal))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads the full text of <paramref name="iniPath"/> and returns it,
|
||||
/// or an empty string if the file does not exist.
|
||||
/// </summary>
|
||||
public static string LoadText(string iniPath)
|
||||
=> File.Exists(iniPath) ? File.ReadAllText(iniPath) : string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Checks the support level of a given termsrv.dll version against the
|
||||
/// INI content string <paramref name="iniContent"/>.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// 0 = not supported, 1 = partially supported (Vista/7 legacy),
|
||||
/// 2 = fully supported (entry found in ini).
|
||||
/// </returns>
|
||||
public static int CheckSupportLevel(string iniContent,
|
||||
FileVersionHelper.FileVersionInfo fv)
|
||||
{
|
||||
int level = 0;
|
||||
|
||||
// Vista (6.0) and Windows 7 (6.1) are "partially" supported without
|
||||
// a specific INI entry — mirrors the Delphi CheckSupport logic.
|
||||
if ((fv.Major == 6 && fv.Minor == 0) ||
|
||||
(fv.Major == 6 && fv.Minor == 1))
|
||||
level = 1;
|
||||
|
||||
var verTxt = fv.ToString(); // "major.minor.release.build"
|
||||
if (iniContent.Contains($"[{verTxt}]", StringComparison.Ordinal))
|
||||
level = 2;
|
||||
|
||||
return level;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,467 @@
|
||||
// Copyright 2026 sjackson0109
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace RDPWrap.Common;
|
||||
|
||||
/// <summary>
|
||||
/// All P/Invoke declarations used across RDPWInst, RDPConf and RDPCheck.
|
||||
/// Mirrors the unhooked Win32 imports from the original Delphi sources.
|
||||
/// </summary>
|
||||
internal static class NativeMethods
|
||||
{
|
||||
// ─── DLL names ────────────────────────────────────────────────────────────
|
||||
internal const string Kernel32 = "kernel32.dll";
|
||||
internal const string Advapi32 = "advapi32.dll";
|
||||
internal const string WinSta = "winsta.dll";
|
||||
|
||||
// ─── Constants ────────────────────────────────────────────────────────────
|
||||
|
||||
// Architecture
|
||||
internal const ushort PROCESSOR_ARCHITECTURE_INTEL = 0;
|
||||
internal const ushort PROCESSOR_ARCHITECTURE_IA64 = 6;
|
||||
internal const ushort PROCESSOR_ARCHITECTURE_AMD64 = 9;
|
||||
|
||||
// Registry
|
||||
internal const uint KEY_WOW64_64KEY = 0x0100;
|
||||
internal const uint KEY_WOW64_32KEY = 0x0200;
|
||||
internal const uint KEY_READ = 0x20019;
|
||||
internal const uint KEY_WRITE = 0x20006;
|
||||
internal const uint KEY_QUERY_VALUE = 0x0001;
|
||||
internal const uint KEY_SET_VALUE = 0x0002;
|
||||
|
||||
// Service control manager
|
||||
internal const uint SC_MANAGER_CONNECT = 0x0001;
|
||||
internal const uint SC_MANAGER_CREATE_SERVICE = 0x0002;
|
||||
internal const uint SC_MANAGER_ENUMERATE_SERVICE = 0x0004;
|
||||
internal const uint SC_MANAGER_ALL_ACCESS = 0xF003F;
|
||||
|
||||
internal const uint SERVICE_QUERY_CONFIG = 0x0001;
|
||||
internal const uint SERVICE_CHANGE_CONFIG = 0x0002;
|
||||
internal const uint SERVICE_QUERY_STATUS = 0x0004;
|
||||
internal const uint SERVICE_START = 0x0010;
|
||||
internal const uint SERVICE_STOP = 0x0020;
|
||||
internal const uint SERVICE_ALL_ACCESS = 0xF01FF;
|
||||
|
||||
internal const uint SERVICE_WIN32 = 0x30;
|
||||
internal const uint SERVICE_STATE_ALL = 0x03;
|
||||
internal const uint SERVICE_NO_CHANGE = 0xFFFFFFFF;
|
||||
internal const uint SERVICE_AUTO_START = 0x02;
|
||||
internal const uint SERVICE_DEMAND_START = 0x03;
|
||||
internal const uint SERVICE_DISABLED = 0x04;
|
||||
|
||||
internal const uint SERVICE_STOPPED = 0x00000001;
|
||||
internal const uint SERVICE_START_PENDING = 0x00000002;
|
||||
internal const uint SERVICE_STOP_PENDING = 0x00000003;
|
||||
internal const uint SERVICE_RUNNING = 0x00000004;
|
||||
internal const uint SERVICE_CONTINUE_PENDING = 0x00000005;
|
||||
internal const uint SERVICE_PAUSE_PENDING = 0x00000006;
|
||||
internal const uint SERVICE_PAUSED = 0x00000007;
|
||||
|
||||
internal const uint SC_ENUM_PROCESS_INFO = 0;
|
||||
internal const uint SC_STATUS_PROCESS_INFO = 0;
|
||||
internal const uint ERROR_MORE_DATA = 234;
|
||||
internal const uint ERROR_SERVICE_DOES_NOT_EXIST = 1060;
|
||||
internal const uint ERROR_SERVICE_NOT_ACTIVE = 1062;
|
||||
|
||||
// Process/Thread
|
||||
internal const uint PROCESS_TERMINATE = 0x0001;
|
||||
internal const uint THREAD_SUSPEND_RESUME = 0x0002;
|
||||
internal const uint TH32CS_SNAPTHREAD = 0x00000004;
|
||||
|
||||
// Token privileges
|
||||
internal const uint TOKEN_ADJUST_PRIVILEGES = 0x0020;
|
||||
internal const uint TOKEN_QUERY = 0x0008;
|
||||
internal const uint SE_PRIVILEGE_ENABLED = 0x00000002;
|
||||
|
||||
// Privilege names
|
||||
internal const string SE_DEBUG_NAME = "SeDebugPrivilege";
|
||||
internal const string SE_RESTORE_NAME = "SeRestorePrivilege";
|
||||
internal const string SE_BACKUP_NAME = "SeBackupPrivilege";
|
||||
|
||||
// Security
|
||||
internal const uint DACL_SECURITY_INFORMATION = 0x00000004;
|
||||
internal const uint SE_FILE_OBJECT = 1;
|
||||
internal const uint GRANT_ACCESS = 1;
|
||||
internal const uint SUB_CONTAINERS_AND_OBJECTS_INHERIT = 0x3;
|
||||
internal const uint NO_MULTIPLE_TRUSTEE = 0;
|
||||
internal const uint TRUSTEE_IS_SID = 0;
|
||||
internal const uint TRUSTEE_IS_WELL_KNOWN_GROUP = 5;
|
||||
internal const uint GENERIC_ALL = 0x10000000;
|
||||
|
||||
// LoadLibraryEx flags
|
||||
internal const uint LOAD_LIBRARY_AS_DATAFILE = 0x00000002;
|
||||
|
||||
// Version resource type
|
||||
internal const uint RT_VERSION = 16;
|
||||
|
||||
// CreateProcess - STARTUPINFO flags
|
||||
internal const uint STARTF_USESHOWWINDOW = 0x00000001;
|
||||
internal const ushort SW_HIDE = 0;
|
||||
|
||||
// GetModuleHandleEx
|
||||
internal const uint GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS = 0x00000004;
|
||||
internal const uint GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT = 0x00000002;
|
||||
|
||||
// Error codes
|
||||
internal const uint ERROR_SUCCESS = 0;
|
||||
internal const uint ERROR_ACCESS_DENIED = 5;
|
||||
internal const uint ERROR_NOT_SUPPORTED = 50;
|
||||
internal const uint ERROR_SERVICE_ALREADY_RUNNING = 1056;
|
||||
|
||||
// ─── Structures ───────────────────────────────────────────────────────────
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal struct SYSTEM_INFO
|
||||
{
|
||||
internal ushort wProcessorArchitecture;
|
||||
internal ushort wReserved;
|
||||
internal uint dwPageSize;
|
||||
internal IntPtr lpMinimumApplicationAddress;
|
||||
internal IntPtr lpMaximumApplicationAddress;
|
||||
internal UIntPtr dwActiveProcessorMask;
|
||||
internal uint dwNumberOfProcessors;
|
||||
internal uint dwProcessorType;
|
||||
internal uint dwAllocationGranularity;
|
||||
internal ushort wProcessorLevel;
|
||||
internal ushort wProcessorRevision;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
|
||||
internal struct STARTUPINFO
|
||||
{
|
||||
internal uint cb;
|
||||
internal string? lpReserved;
|
||||
internal string? lpDesktop;
|
||||
internal string? lpTitle;
|
||||
internal uint dwX, dwY, dwXSize, dwYSize, dwXCountChars, dwYCountChars;
|
||||
internal uint dwFillAttribute;
|
||||
internal uint dwFlags;
|
||||
internal ushort wShowWindow;
|
||||
internal ushort cbReserved2;
|
||||
internal IntPtr lpReserved2;
|
||||
internal IntPtr hStdInput, hStdOutput, hStdError;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal struct PROCESS_INFORMATION
|
||||
{
|
||||
internal IntPtr hProcess;
|
||||
internal IntPtr hThread;
|
||||
internal uint dwProcessId;
|
||||
internal uint dwThreadId;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal struct THREADENTRY32
|
||||
{
|
||||
internal uint dwSize;
|
||||
internal uint cntUsage;
|
||||
internal uint th32ThreadID;
|
||||
internal uint th32OwnerProcessID;
|
||||
internal int tpBasePri;
|
||||
internal int tpDeltaPri;
|
||||
internal uint dwFlags;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal struct SERVICE_STATUS_PROCESS
|
||||
{
|
||||
internal uint dwServiceType;
|
||||
internal uint dwCurrentState;
|
||||
internal uint dwControlsAccepted;
|
||||
internal uint dwWin32ExitCode;
|
||||
internal uint dwServiceSpecificExitCode;
|
||||
internal uint dwCheckPoint;
|
||||
internal uint dwWaitHint;
|
||||
internal uint dwProcessId;
|
||||
internal uint dwServiceFlags;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
|
||||
internal struct ENUM_SERVICE_STATUS_PROCESS
|
||||
{
|
||||
internal string lpServiceName;
|
||||
internal string lpDisplayName;
|
||||
internal SERVICE_STATUS_PROCESS ServiceStatusProcess;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
|
||||
internal struct QUERY_SERVICE_CONFIG
|
||||
{
|
||||
internal uint dwServiceType;
|
||||
internal uint dwStartType;
|
||||
internal uint dwErrorControl;
|
||||
internal string lpBinaryPathName;
|
||||
internal string lpLoadOrderGroup;
|
||||
internal uint dwTagId;
|
||||
internal string lpDependencies;
|
||||
internal string lpServiceStartName;
|
||||
internal string lpDisplayName;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal struct LUID
|
||||
{
|
||||
internal uint LowPart;
|
||||
internal int HighPart;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal struct LUID_AND_ATTRIBUTES
|
||||
{
|
||||
internal LUID Luid;
|
||||
internal uint Attributes;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal struct TOKEN_PRIVILEGES
|
||||
{
|
||||
internal uint PrivilegeCount;
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 1)]
|
||||
internal LUID_AND_ATTRIBUTES[] Privileges;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
|
||||
internal struct EXPLICIT_ACCESS
|
||||
{
|
||||
internal uint grfAccessPermissions;
|
||||
internal uint grfAccessMode;
|
||||
internal uint grfInheritance;
|
||||
internal TRUSTEE Trustee;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
|
||||
internal struct TRUSTEE
|
||||
{
|
||||
internal IntPtr pMultipleTrustee;
|
||||
internal uint MultipleTrusteeOperation;
|
||||
internal uint TrusteeForm;
|
||||
internal uint TrusteeType;
|
||||
internal IntPtr ptstrName; // SID pointer or string pointer
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
|
||||
internal struct WTS_SESSION_INFO
|
||||
{
|
||||
internal uint SessionId;
|
||||
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 34)]
|
||||
internal string Name;
|
||||
internal uint State;
|
||||
}
|
||||
|
||||
// ─── kernel32.dll ─────────────────────────────────────────────────────────
|
||||
|
||||
[DllImport(Kernel32, SetLastError = true)]
|
||||
internal static extern void GetNativeSystemInfo(out SYSTEM_INFO lpSystemInfo);
|
||||
|
||||
[DllImport(Kernel32, SetLastError = true)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
internal static extern bool Wow64DisableWow64FsRedirection(out IntPtr oldValue);
|
||||
|
||||
[DllImport(Kernel32, SetLastError = true)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
internal static extern bool Wow64RevertWow64FsRedirection(IntPtr oldValue);
|
||||
|
||||
[DllImport(Kernel32, CharSet = CharSet.Unicode, SetLastError = true)]
|
||||
internal static extern IntPtr LoadLibraryEx(string lpFileName, IntPtr hFile, uint dwFlags);
|
||||
|
||||
[DllImport(Kernel32, SetLastError = true)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
internal static extern bool FreeLibrary(IntPtr hModule);
|
||||
|
||||
[DllImport(Kernel32, CharSet = CharSet.Unicode, SetLastError = true)]
|
||||
internal static extern IntPtr FindResource(IntPtr hModule, IntPtr lpName, IntPtr lpType);
|
||||
|
||||
[DllImport(Kernel32, SetLastError = true)]
|
||||
internal static extern IntPtr LoadResource(IntPtr hModule, IntPtr hResInfo);
|
||||
|
||||
[DllImport(Kernel32, SetLastError = true)]
|
||||
internal static extern IntPtr LockResource(IntPtr hResData);
|
||||
|
||||
[DllImport(Kernel32, SetLastError = true)]
|
||||
internal static extern uint SizeofResource(IntPtr hModule, IntPtr hResInfo);
|
||||
|
||||
[DllImport(Kernel32, CharSet = CharSet.Unicode, SetLastError = true)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
internal static extern bool CreateProcess(
|
||||
string? lpApplicationName,
|
||||
string lpCommandLine,
|
||||
IntPtr lpProcessAttributes,
|
||||
IntPtr lpThreadAttributes,
|
||||
[MarshalAs(UnmanagedType.Bool)] bool bInheritHandles,
|
||||
uint dwCreationFlags,
|
||||
IntPtr lpEnvironment,
|
||||
string? lpCurrentDirectory,
|
||||
ref STARTUPINFO lpStartupInfo,
|
||||
out PROCESS_INFORMATION lpProcessInformation);
|
||||
|
||||
[DllImport(Kernel32, SetLastError = true)]
|
||||
internal static extern uint WaitForSingleObject(IntPtr hHandle, uint dwMilliseconds);
|
||||
|
||||
[DllImport(Kernel32, SetLastError = true)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
internal static extern bool CloseHandle(IntPtr hObject);
|
||||
|
||||
[DllImport(Kernel32, SetLastError = true)]
|
||||
internal static extern IntPtr OpenProcess(uint dwDesiredAccess,
|
||||
[MarshalAs(UnmanagedType.Bool)] bool bInheritHandle, uint dwProcessId);
|
||||
|
||||
[DllImport(Kernel32, SetLastError = true)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
internal static extern bool TerminateProcess(IntPtr hProcess, uint uExitCode);
|
||||
|
||||
[DllImport(Kernel32, SetLastError = true)]
|
||||
internal static extern uint GetCurrentProcessId();
|
||||
|
||||
[DllImport(Kernel32, SetLastError = true)]
|
||||
internal static extern uint GetCurrentThreadId();
|
||||
|
||||
[DllImport(Kernel32, SetLastError = true)]
|
||||
internal static extern IntPtr CreateToolhelp32Snapshot(uint dwFlags, uint th32ProcessID);
|
||||
|
||||
[DllImport(Kernel32, SetLastError = true)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
internal static extern bool Thread32First(IntPtr hSnapshot, ref THREADENTRY32 lpte);
|
||||
|
||||
[DllImport(Kernel32, SetLastError = true)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
internal static extern bool Thread32Next(IntPtr hSnapshot, ref THREADENTRY32 lpte);
|
||||
|
||||
[DllImport(Kernel32, SetLastError = true)]
|
||||
internal static extern IntPtr OpenThread(uint dwDesiredAccess,
|
||||
[MarshalAs(UnmanagedType.Bool)] bool bInheritHandle, uint dwThreadId);
|
||||
|
||||
[DllImport(Kernel32, SetLastError = true)]
|
||||
internal static extern uint SuspendThread(IntPtr hThread);
|
||||
|
||||
[DllImport(Kernel32, SetLastError = true)]
|
||||
internal static extern uint ResumeThread(IntPtr hThread);
|
||||
|
||||
[DllImport(Kernel32, CharSet = CharSet.Unicode, SetLastError = true)]
|
||||
internal static extern uint GetModuleFileName(IntPtr hModule,
|
||||
System.Text.StringBuilder lpFilename, uint nSize);
|
||||
|
||||
[DllImport(Kernel32, CharSet = CharSet.Unicode, SetLastError = true)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
internal static extern bool GetModuleHandleEx(uint dwFlags, IntPtr lpModuleName,
|
||||
out IntPtr phModule);
|
||||
|
||||
[DllImport(Kernel32, CharSet = CharSet.Unicode, SetLastError = true)]
|
||||
internal static extern uint ExpandEnvironmentStrings(string lpSrc,
|
||||
System.Text.StringBuilder lpDst, uint nSize);
|
||||
|
||||
[DllImport(Kernel32, SetLastError = true)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
internal static extern bool DeleteFile(string lpFileName);
|
||||
|
||||
[DllImport(Kernel32, SetLastError = true)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
internal static extern bool RemoveDirectory(string lpPathName);
|
||||
|
||||
[DllImport(Kernel32, SetLastError = true)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
internal static extern bool OpenProcessToken(IntPtr processHandle,
|
||||
uint desiredAccess, out IntPtr tokenHandle);
|
||||
|
||||
// ─── advapi32.dll ─────────────────────────────────────────────────────────
|
||||
|
||||
[DllImport(Advapi32, SetLastError = true)]
|
||||
internal static extern IntPtr OpenSCManager(string? lpMachineName,
|
||||
string? lpDatabaseName, uint dwDesiredAccess);
|
||||
|
||||
[DllImport(Advapi32, CharSet = CharSet.Unicode, SetLastError = true)]
|
||||
internal static extern IntPtr OpenService(IntPtr hSCManager,
|
||||
string lpServiceName, uint dwDesiredAccess);
|
||||
|
||||
[DllImport(Advapi32, SetLastError = true)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
internal static extern bool CloseServiceHandle(IntPtr hSCObject);
|
||||
|
||||
[DllImport(Advapi32, SetLastError = true)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
internal static extern bool QueryServiceConfig(IntPtr hService,
|
||||
IntPtr lpServiceConfig, uint cbBufSize, out uint pcbBytesNeeded);
|
||||
|
||||
[DllImport(Advapi32, SetLastError = true)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
internal static extern bool QueryServiceStatusEx(IntPtr hService,
|
||||
uint InfoLevel, IntPtr lpBuffer, uint cbBufSize, out uint pcbBytesNeeded);
|
||||
|
||||
[DllImport(Advapi32, CharSet = CharSet.Unicode, SetLastError = true)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
internal static extern bool ChangeServiceConfig(IntPtr hService,
|
||||
uint dwServiceType, uint dwStartType, uint dwErrorControl,
|
||||
string? lpBinaryPathName, string? lpLoadOrderGroup, IntPtr lpdwTagId,
|
||||
string? lpDependencies, string? lpServiceStartName, string? lpPassword,
|
||||
string? lpDisplayName);
|
||||
|
||||
[DllImport(Advapi32, CharSet = CharSet.Unicode, SetLastError = true)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
internal static extern bool StartService(IntPtr hService,
|
||||
uint dwNumServiceArgs, string[]? lpServiceArgVectors);
|
||||
|
||||
[DllImport(Advapi32, CharSet = CharSet.Unicode, SetLastError = true)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
internal static extern bool EnumServicesStatusEx(
|
||||
IntPtr hSCManager, uint InfoLevel, uint dwServiceType, uint dwServiceState,
|
||||
IntPtr lpServices, uint cbBufSize,
|
||||
out uint pcbBytesNeeded, out uint lpServicesReturned,
|
||||
ref uint lpResumeHandle, string? pszGroupName);
|
||||
|
||||
[DllImport(Advapi32, CharSet = CharSet.Unicode, SetLastError = true)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
internal static extern bool LookupPrivilegeValue(string? lpSystemName,
|
||||
string lpName, out LUID lpLuid);
|
||||
|
||||
[DllImport(Advapi32, SetLastError = true)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
internal static extern bool AdjustTokenPrivileges(IntPtr tokenHandle,
|
||||
[MarshalAs(UnmanagedType.Bool)] bool disableAllPrivileges,
|
||||
ref TOKEN_PRIVILEGES newState, uint bufferLength,
|
||||
IntPtr previousState, IntPtr returnLength);
|
||||
|
||||
[DllImport(Advapi32, CharSet = CharSet.Unicode, SetLastError = true)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
internal static extern bool ConvertStringSidToSid(string stringSid, out IntPtr sid);
|
||||
|
||||
[DllImport(Advapi32, SetLastError = true)]
|
||||
internal static extern uint SetEntriesInAcl(uint cCountOfExplicitEntries,
|
||||
ref EXPLICIT_ACCESS pListOfExplicitEntries, IntPtr oldAcl, out IntPtr newAcl);
|
||||
|
||||
[DllImport(Advapi32, CharSet = CharSet.Unicode, SetLastError = true)]
|
||||
internal static extern uint SetNamedSecurityInfo(string pObjectName,
|
||||
uint ObjectType, uint SecurityInfo,
|
||||
IntPtr psidOwner, IntPtr psidGroup, IntPtr pDacl, IntPtr pSacl);
|
||||
|
||||
[DllImport(Advapi32, SetLastError = true)]
|
||||
internal static extern IntPtr LocalFree(IntPtr hMem);
|
||||
|
||||
// ─── winsta.dll ───────────────────────────────────────────────────────────
|
||||
|
||||
/// <summary>
|
||||
/// Enumerates WTS sessions on the local server.
|
||||
/// Pass <c>IntPtr.Zero</c> as hServer for the local machine.
|
||||
/// </summary>
|
||||
[DllImport(WinSta, EntryPoint = "WinStationEnumerateW",
|
||||
CharSet = CharSet.Unicode, SetLastError = true)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
internal static extern bool WinStationEnumerate(IntPtr hServer,
|
||||
out IntPtr ppSessionInfo, out uint pCount);
|
||||
|
||||
[DllImport(WinSta, SetLastError = true)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
internal static extern bool WinStationFreeMemory(IntPtr p);
|
||||
}
|
||||
@ -0,0 +1,63 @@
|
||||
// Copyright 2026 sjackson0109 — Apache License 2.0
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace RDPWrap.Common;
|
||||
|
||||
/// <summary>
|
||||
/// Process creation and termination helpers. Mirrors ExecWait and KillProcess
|
||||
/// from RDPWInst.dpr (console variant) and RDPConf MainUnit.pas (GUI variant).
|
||||
/// </summary>
|
||||
public static class ProcessHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a process from <paramref name="commandLine"/>, waits for it to
|
||||
/// exit, then returns <c>true</c>. The process window is hidden.
|
||||
/// Mirrors the Delphi ExecWait procedure.
|
||||
/// </summary>
|
||||
public static bool ExecWait(string commandLine, bool hideWindow = true)
|
||||
{
|
||||
var si = new NativeMethods.STARTUPINFO
|
||||
{
|
||||
cb = (uint)Marshal.SizeOf<NativeMethods.STARTUPINFO>(),
|
||||
dwFlags = hideWindow ? NativeMethods.STARTF_USESHOWWINDOW : 0u,
|
||||
wShowWindow = hideWindow ? NativeMethods.SW_HIDE : (ushort)1
|
||||
};
|
||||
|
||||
// CommandLine must be mutable — pass a copy
|
||||
string cmdCopy = new(commandLine);
|
||||
|
||||
if (!NativeMethods.CreateProcess(null, cmdCopy, IntPtr.Zero, IntPtr.Zero,
|
||||
true, 0, IntPtr.Zero, null, ref si, out var pi))
|
||||
{
|
||||
Console.Error.WriteLine(
|
||||
$"[-] CreateProcess error (code {Marshal.GetLastWin32Error()}).");
|
||||
return false;
|
||||
}
|
||||
|
||||
NativeMethods.WaitForSingleObject(pi.hProcess, 0xFFFFFFFF);
|
||||
NativeMethods.CloseHandle(pi.hThread);
|
||||
NativeMethods.CloseHandle(pi.hProcess);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Terminates the process with <paramref name="pid"/>. Mirrors the Delphi
|
||||
/// KillProcess procedure.
|
||||
/// </summary>
|
||||
public static void KillProcess(uint pid)
|
||||
{
|
||||
var hProc = NativeMethods.OpenProcess(NativeMethods.PROCESS_TERMINATE, false, pid);
|
||||
if (hProc == IntPtr.Zero)
|
||||
{
|
||||
Console.Error.WriteLine(
|
||||
$"[-] OpenProcess error (code {Marshal.GetLastWin32Error()}).");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!NativeMethods.TerminateProcess(hProc, 0))
|
||||
Console.Error.WriteLine(
|
||||
$"[-] TerminateProcess error (code {Marshal.GetLastWin32Error()}).");
|
||||
|
||||
NativeMethods.CloseHandle(hProc);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,15 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputType>Library</OutputType>
|
||||
<RootNamespace>RDPWrap.Common</RootNamespace>
|
||||
<AssemblyName>RDPWrap</AssemblyName>
|
||||
<Description>Shared helper library (P/Invoke, registry, service, security helpers) for the RDP Wrapper tool suite.</Description>
|
||||
</PropertyGroup>
|
||||
|
||||
<!-- Grant all three consumer assemblies access to internal members (e.g. NativeMethods) -->
|
||||
<ItemGroup>
|
||||
<InternalsVisibleTo Include="RDPWInst" />
|
||||
<InternalsVisibleTo Include="RDPConf" />
|
||||
<InternalsVisibleTo Include="RDPCheck" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@ -0,0 +1,97 @@
|
||||
// Copyright 2026 sjackson0109 — Apache License 2.0
|
||||
using Microsoft.Win32;
|
||||
|
||||
namespace RDPWrap.Common;
|
||||
|
||||
/// <summary>
|
||||
/// Thin wrappers around <see cref="Microsoft.Win32.Registry"/> that mirror the
|
||||
/// Delphi TRegistry usage in RDPWInst and RDPConf, with optional WOW64 flag
|
||||
/// support (KEY_WOW64_64KEY) for 64-bit registry views from 32-bit processes.
|
||||
/// </summary>
|
||||
public static class RegistryHelper
|
||||
{
|
||||
// On 64-bit Windows we always open the 64-bit view to match the Delphi code
|
||||
// that passes KEY_WOW64_64KEY when Arch = 64.
|
||||
private static RegistryView ViewForArch() =>
|
||||
ArchHelper.Is64Bit ? RegistryView.Registry64 : RegistryView.Default;
|
||||
|
||||
// ── Convenience open helpers ──────────────────────────────────────────────
|
||||
|
||||
/// <summary>Opens a read-only key under HKLM, respecting the host architecture.</summary>
|
||||
public static RegistryKey? OpenHklmRead(string subKey)
|
||||
{
|
||||
using var hive = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, ViewForArch());
|
||||
return hive.OpenSubKey(subKey, writable: false);
|
||||
}
|
||||
|
||||
/// <summary>Opens a writable key under HKLM (creates if absent).</summary>
|
||||
public static RegistryKey OpenHklmWrite(string subKey)
|
||||
{
|
||||
using var hive = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, ViewForArch());
|
||||
return hive.CreateSubKey(subKey, writable: true)
|
||||
?? throw new InvalidOperationException($"Cannot open/create HKLM\\{subKey}");
|
||||
}
|
||||
|
||||
// ── Typed read helpers ────────────────────────────────────────────────────
|
||||
|
||||
/// <summary>
|
||||
/// Reads a string value from HKLM. Returns <c>null</c> if the key or
|
||||
/// value does not exist.
|
||||
/// </summary>
|
||||
public static string? ReadString(string subKey, string valueName)
|
||||
{
|
||||
using var key = OpenHklmRead(subKey);
|
||||
return key?.GetValue(valueName) as string;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a DWORD value from HKLM. Returns <paramref name="defaultValue"/>
|
||||
/// if the key or value does not exist.
|
||||
/// </summary>
|
||||
public static int ReadInt(string subKey, string valueName, int defaultValue = 0)
|
||||
{
|
||||
using var key = OpenHklmRead(subKey);
|
||||
if (key is null) return defaultValue;
|
||||
var raw = key.GetValue(valueName);
|
||||
return raw is int i ? i : defaultValue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a DWORD as a bool (non-zero = true). Returns <paramref name="defaultValue"/>
|
||||
/// if absent.
|
||||
/// </summary>
|
||||
public static bool ReadBool(string subKey, string valueName, bool defaultValue = false)
|
||||
{
|
||||
using var key = OpenHklmRead(subKey);
|
||||
if (key is null) return defaultValue;
|
||||
var raw = key.GetValue(valueName);
|
||||
return raw is int i ? i != 0 : defaultValue;
|
||||
}
|
||||
|
||||
// ── Typed write helpers ───────────────────────────────────────────────────
|
||||
|
||||
/// <summary>Writes a REG_SZ string value.</summary>
|
||||
public static void WriteString(string subKey, string valueName, string value)
|
||||
{
|
||||
using var key = OpenHklmWrite(subKey);
|
||||
key.SetValue(valueName, value, RegistryValueKind.String);
|
||||
}
|
||||
|
||||
/// <summary>Writes a REG_EXPAND_SZ string value (mirrors Delphi WriteExpandString).</summary>
|
||||
public static void WriteExpandString(string subKey, string valueName, string value)
|
||||
{
|
||||
using var key = OpenHklmWrite(subKey);
|
||||
key.SetValue(valueName, value, RegistryValueKind.ExpandString);
|
||||
}
|
||||
|
||||
/// <summary>Writes a DWORD integer value.</summary>
|
||||
public static void WriteInt(string subKey, string valueName, int value)
|
||||
{
|
||||
using var key = OpenHklmWrite(subKey);
|
||||
key.SetValue(valueName, value, RegistryValueKind.DWord);
|
||||
}
|
||||
|
||||
/// <summary>Writes a boolean as a DWORD (1/0).</summary>
|
||||
public static void WriteBool(string subKey, string valueName, bool value)
|
||||
=> WriteInt(subKey, valueName, value ? 1 : 0);
|
||||
}
|
||||
@ -0,0 +1,88 @@
|
||||
// Copyright 2026 sjackson0109 — Apache License 2.0
|
||||
using System.Reflection;
|
||||
|
||||
namespace RDPWrap.Common;
|
||||
|
||||
/// <summary>
|
||||
/// Helpers for reading and extracting managed embedded resources.
|
||||
/// Mirrors the Delphi ExtractRes / ExtractResText procedures from RDPWInst.dpr
|
||||
/// and RDPConf MainUnit.pas, translated to the .NET manifest-resource model.
|
||||
/// </summary>
|
||||
public static class ResourceHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Reads the contents of the embedded resource named
|
||||
/// <paramref name="resourceName"/> from <paramref name="assembly"/>
|
||||
/// and returns it as a UTF-8 string. Returns <c>null</c> if not found.
|
||||
/// </summary>
|
||||
public static string? ReadText(string resourceName,
|
||||
Assembly? assembly = null)
|
||||
{
|
||||
assembly ??= Assembly.GetCallingAssembly();
|
||||
using var stream = assembly.GetManifestResourceStream(resourceName);
|
||||
if (stream is null) return null;
|
||||
using var reader = new StreamReader(stream, System.Text.Encoding.UTF8);
|
||||
return reader.ReadToEnd();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads the contents of the embedded resource named
|
||||
/// <paramref name="resourceName"/> and returns the raw bytes.
|
||||
/// Returns <c>null</c> if not found.
|
||||
/// </summary>
|
||||
public static byte[]? ReadBytes(string resourceName,
|
||||
Assembly? assembly = null)
|
||||
{
|
||||
assembly ??= Assembly.GetCallingAssembly();
|
||||
using var stream = assembly.GetManifestResourceStream(resourceName);
|
||||
if (stream is null) return null;
|
||||
using var ms = new MemoryStream();
|
||||
stream.CopyTo(ms);
|
||||
return ms.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extracts the embedded resource <paramref name="resourceName"/> to disk
|
||||
/// at <paramref name="destPath"/>, creating parent directories as needed.
|
||||
/// Returns <c>true</c> on success. Mirrors the Delphi ExtractRes procedure.
|
||||
/// </summary>
|
||||
public static bool ExtractToDisk(string resourceName, string destPath,
|
||||
Assembly? assembly = null)
|
||||
{
|
||||
assembly ??= Assembly.GetCallingAssembly();
|
||||
using var stream = assembly.GetManifestResourceStream(resourceName);
|
||||
if (stream is null)
|
||||
{
|
||||
Console.Error.WriteLine($"[-] Resource not found: {resourceName}");
|
||||
return false;
|
||||
}
|
||||
|
||||
var dir = Path.GetDirectoryName(destPath);
|
||||
if (!string.IsNullOrEmpty(dir) && !Directory.Exists(dir))
|
||||
Directory.CreateDirectory(dir);
|
||||
|
||||
try
|
||||
{
|
||||
using var file = File.Create(destPath);
|
||||
stream.CopyTo(file);
|
||||
Console.WriteLine($"[+] Extracted {resourceName} -> {destPath}");
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.Error.WriteLine(
|
||||
$"[-] Failed to extract resource {resourceName} to {destPath}: {ex.Message}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Lists all manifest resource names in <paramref name="assembly"/>
|
||||
/// — useful for debugging resource name mismatches.
|
||||
/// </summary>
|
||||
public static IEnumerable<string> ListResources(Assembly? assembly = null)
|
||||
{
|
||||
assembly ??= Assembly.GetCallingAssembly();
|
||||
return assembly.GetManifestResourceNames();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,168 @@
|
||||
// Copyright 2026 sjackson0109 — Apache License 2.0
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace RDPWrap.Common;
|
||||
|
||||
/// <summary>
|
||||
/// Security helpers: granting SID-based DACL entries (GrantSidFullAccess) and
|
||||
/// adjusting process token privileges (AddPrivilege). Mirrors the Delphi
|
||||
/// implementations in RDPWInst.dpr.
|
||||
/// </summary>
|
||||
public static class SecurityHelper
|
||||
{
|
||||
// ── DACL: grant a well-known SID full access to a file/folder ─────────────
|
||||
|
||||
/// <summary>
|
||||
/// Grants GENERIC_ALL access to the well-known SID string
|
||||
/// <paramref name="stringSid"/> on the file/directory at
|
||||
/// <paramref name="path"/>. Mirrors the Delphi GrantSidFullAccess
|
||||
/// procedure (used for "S-1-5-18" = Local System, "S-1-5-6" = Service).
|
||||
/// </summary>
|
||||
public static void GrantSidFullAccess(string path, string stringSid)
|
||||
{
|
||||
if (!NativeMethods.ConvertStringSidToSid(stringSid, out IntPtr pSid))
|
||||
{
|
||||
Console.Error.WriteLine(
|
||||
$"[-] ConvertStringSidToSid error (code {Marshal.GetLastWin32Error()}) " +
|
||||
$"for SID {stringSid}.");
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var ea = new NativeMethods.EXPLICIT_ACCESS
|
||||
{
|
||||
grfAccessPermissions = NativeMethods.GENERIC_ALL,
|
||||
grfAccessMode = NativeMethods.GRANT_ACCESS,
|
||||
grfInheritance = NativeMethods.SUB_CONTAINERS_AND_OBJECTS_INHERIT,
|
||||
Trustee = new NativeMethods.TRUSTEE
|
||||
{
|
||||
pMultipleTrustee = IntPtr.Zero,
|
||||
MultipleTrusteeOperation = NativeMethods.NO_MULTIPLE_TRUSTEE,
|
||||
TrusteeForm = NativeMethods.TRUSTEE_IS_SID,
|
||||
TrusteeType = NativeMethods.TRUSTEE_IS_WELL_KNOWN_GROUP,
|
||||
ptstrName = pSid
|
||||
}
|
||||
};
|
||||
|
||||
uint result = NativeMethods.SetEntriesInAcl(1, ref ea, IntPtr.Zero, out IntPtr pNewAcl);
|
||||
if (result == NativeMethods.ERROR_SUCCESS)
|
||||
{
|
||||
uint secResult = NativeMethods.SetNamedSecurityInfo(
|
||||
path,
|
||||
NativeMethods.SE_FILE_OBJECT,
|
||||
NativeMethods.DACL_SECURITY_INFORMATION,
|
||||
IntPtr.Zero, IntPtr.Zero, pNewAcl, IntPtr.Zero);
|
||||
|
||||
if (secResult != NativeMethods.ERROR_SUCCESS)
|
||||
Console.Error.WriteLine(
|
||||
$"[-] SetNamedSecurityInfo error (code {secResult}).");
|
||||
|
||||
NativeMethods.LocalFree(pNewAcl);
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.Error.WriteLine($"[-] SetEntriesInAcl error (code {result}).");
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
NativeMethods.LocalFree(pSid);
|
||||
}
|
||||
}
|
||||
|
||||
// ── Token privileges ──────────────────────────────────────────────────────
|
||||
|
||||
/// <summary>
|
||||
/// Enables the named privilege (e.g. <c>SeDebugPrivilege</c>) for the
|
||||
/// current process token. Mirrors the Delphi AddPrivilege function.
|
||||
/// Returns <c>true</c> on success.
|
||||
/// </summary>
|
||||
public static bool AddPrivilege(string privilegeName)
|
||||
{
|
||||
if (!NativeMethods.OpenProcessToken(
|
||||
System.Diagnostics.Process.GetCurrentProcess().Handle,
|
||||
NativeMethods.TOKEN_ADJUST_PRIVILEGES | NativeMethods.TOKEN_QUERY,
|
||||
out IntPtr hToken))
|
||||
{
|
||||
Console.Error.WriteLine(
|
||||
$"[-] OpenProcessToken error (code {Marshal.GetLastWin32Error()}).");
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (!NativeMethods.LookupPrivilegeValue(null, privilegeName, out var luid))
|
||||
{
|
||||
Console.Error.WriteLine(
|
||||
$"[-] LookupPrivilegeValue error (code {Marshal.GetLastWin32Error()}).");
|
||||
return false;
|
||||
}
|
||||
|
||||
var tp = new NativeMethods.TOKEN_PRIVILEGES
|
||||
{
|
||||
PrivilegeCount = 1,
|
||||
Privileges = new[]
|
||||
{
|
||||
new NativeMethods.LUID_AND_ATTRIBUTES
|
||||
{
|
||||
Luid = luid,
|
||||
Attributes = NativeMethods.SE_PRIVILEGE_ENABLED
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (!NativeMethods.AdjustTokenPrivileges(
|
||||
hToken, false, ref tp,
|
||||
(uint)Marshal.SizeOf(tp),
|
||||
IntPtr.Zero, IntPtr.Zero))
|
||||
{
|
||||
Console.Error.WriteLine(
|
||||
$"[-] AdjustTokenPrivileges error (code {Marshal.GetLastWin32Error()}).");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
finally
|
||||
{
|
||||
NativeMethods.CloseHandle(hToken);
|
||||
}
|
||||
}
|
||||
|
||||
// ── RDP-Tcp listener ──────────────────────────────────────────────────────
|
||||
|
||||
/// <summary>
|
||||
/// Returns <c>true</c> when there is an active "RDP-Tcp" WTS listener on
|
||||
/// the local machine — i.e. <c>WinStationEnumerateW</c> returns a session
|
||||
/// named "RDP-Tcp". Mirrors the Delphi IsListenerWorking function.
|
||||
/// </summary>
|
||||
public static bool IsRdpListenerWorking()
|
||||
{
|
||||
if (!NativeMethods.WinStationEnumerate(IntPtr.Zero,
|
||||
out IntPtr ppInfo, out uint count))
|
||||
return false;
|
||||
|
||||
try
|
||||
{
|
||||
int ptrSize = IntPtr.Size;
|
||||
// WTS_SESSION_INFO is: DWORD SessionId + 34 WCHARs (Name) + DWORD State
|
||||
// = 4 + 68 + 4 = 76 bytes, but aligned to 4-byte boundary → 76 bytes.
|
||||
int entrySize = 4 + 34 * 2 + 4; // = 76
|
||||
|
||||
for (uint i = 0; i < count; i++)
|
||||
{
|
||||
IntPtr entry = ppInfo + (int)(i * (uint)entrySize);
|
||||
// Name starts at offset 4
|
||||
string name = Marshal.PtrToStringUni(entry + 4, 34).TrimEnd('\0');
|
||||
if (name == "RDP-Tcp") return true;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
NativeMethods.WinStationFreeMemory(ppInfo);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,6 @@
|
||||
{
|
||||
"version": 1,
|
||||
"dependencies": {
|
||||
"net10.0-windows7.0": {}
|
||||
}
|
||||
}
|
||||
@ -1,106 +0,0 @@
|
||||
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<ProjectGuid>{AF9BEAA3-99CD-4B2E-BE67-3F3BD27B961A}</ProjectGuid>
|
||||
<ProjectVersion>12.0</ProjectVersion>
|
||||
<MainSource>RDPWInst.dpr</MainSource>
|
||||
<Config Condition="'$(Config)'==''">Release</Config>
|
||||
<DCC_DCCCompiler>DCC32</DCC_DCCCompiler>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Config)'=='Base' or '$(Base)'!=''">
|
||||
<Base>true</Base>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Config)'=='Release' or '$(Cfg_1)'!=''">
|
||||
<Cfg_1>true</Cfg_1>
|
||||
<CfgParent>Base</CfgParent>
|
||||
<Base>true</Base>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Config)'=='Debug' or '$(Cfg_2)'!=''">
|
||||
<Cfg_2>true</Cfg_2>
|
||||
<CfgParent>Base</CfgParent>
|
||||
<Base>true</Base>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Base)'!=''">
|
||||
<DCC_ExeOutput>..\bin\</DCC_ExeOutput>
|
||||
<DCC_UnitAlias>WinTypes=Windows;WinProcs=Windows;DbiTypes=BDE;DbiProcs=BDE;DbiErrs=BDE;$(DCC_UnitAlias)</DCC_UnitAlias>
|
||||
<DCC_DependencyCheckOutputName>..\bin\RDPWInst.exe</DCC_DependencyCheckOutputName>
|
||||
<DCC_ImageBase>00400000</DCC_ImageBase>
|
||||
<DCC_Platform>x86</DCC_Platform>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Cfg_1)'!=''">
|
||||
<DCC_LocalDebugSymbols>false</DCC_LocalDebugSymbols>
|
||||
<DCC_Define>RELEASE;$(DCC_Define)</DCC_Define>
|
||||
<DCC_SymbolReferenceInfo>0</DCC_SymbolReferenceInfo>
|
||||
<DCC_DebugInformation>false</DCC_DebugInformation>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Cfg_2)'!=''">
|
||||
<DCC_Define>DEBUG;$(DCC_Define)</DCC_Define>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<DelphiCompile Include="RDPWInst.dpr">
|
||||
<MainSource>MainSource</MainSource>
|
||||
</DelphiCompile>
|
||||
<BuildConfiguration Include="Base">
|
||||
<Key>Base</Key>
|
||||
</BuildConfiguration>
|
||||
<BuildConfiguration Include="Release">
|
||||
<Key>Cfg_1</Key>
|
||||
<CfgParent>Base</CfgParent>
|
||||
</BuildConfiguration>
|
||||
<BuildConfiguration Include="Debug">
|
||||
<Key>Cfg_2</Key>
|
||||
<CfgParent>Base</CfgParent>
|
||||
</BuildConfiguration>
|
||||
</ItemGroup>
|
||||
<Import Project="$(BDS)\Bin\CodeGear.Delphi.Targets" Condition="Exists('$(BDS)\Bin\CodeGear.Delphi.Targets')"/>
|
||||
<ProjectExtensions>
|
||||
<Borland.Personality>Delphi.Personality.12</Borland.Personality>
|
||||
<Borland.ProjectType/>
|
||||
<BorlandProject>
|
||||
<Delphi.Personality>
|
||||
<Parameters>
|
||||
<Parameters Name="UseLauncher">False</Parameters>
|
||||
<Parameters Name="LoadAllSymbols">True</Parameters>
|
||||
<Parameters Name="LoadUnspecifiedSymbols">False</Parameters>
|
||||
<Parameters Name="RunParams">-w</Parameters>
|
||||
</Parameters>
|
||||
<VersionInfo>
|
||||
<VersionInfo Name="IncludeVerInfo">False</VersionInfo>
|
||||
<VersionInfo Name="AutoIncBuild">False</VersionInfo>
|
||||
<VersionInfo Name="MajorVer">1</VersionInfo>
|
||||
<VersionInfo Name="MinorVer">0</VersionInfo>
|
||||
<VersionInfo Name="Release">0</VersionInfo>
|
||||
<VersionInfo Name="Build">0</VersionInfo>
|
||||
<VersionInfo Name="Debug">False</VersionInfo>
|
||||
<VersionInfo Name="PreRelease">False</VersionInfo>
|
||||
<VersionInfo Name="Special">False</VersionInfo>
|
||||
<VersionInfo Name="Private">False</VersionInfo>
|
||||
<VersionInfo Name="DLL">False</VersionInfo>
|
||||
<VersionInfo Name="Locale">1049</VersionInfo>
|
||||
<VersionInfo Name="CodePage">1251</VersionInfo>
|
||||
</VersionInfo>
|
||||
<VersionInfoKeys>
|
||||
<VersionInfoKeys Name="CompanyName"/>
|
||||
<VersionInfoKeys Name="FileDescription"/>
|
||||
<VersionInfoKeys Name="FileVersion">1.0.0.0</VersionInfoKeys>
|
||||
<VersionInfoKeys Name="InternalName"/>
|
||||
<VersionInfoKeys Name="LegalCopyright"/>
|
||||
<VersionInfoKeys Name="LegalTrademarks"/>
|
||||
<VersionInfoKeys Name="OriginalFilename"/>
|
||||
<VersionInfoKeys Name="ProductName"/>
|
||||
<VersionInfoKeys Name="ProductVersion">1.0.0.0</VersionInfoKeys>
|
||||
<VersionInfoKeys Name="Comments"/>
|
||||
</VersionInfoKeys>
|
||||
<Excluded_Packages>
|
||||
<Excluded_Packages Name="$(BDS)\bin\bcboffice2k140.bpl">Embarcadero C++Builder Office 2000 Servers Package</Excluded_Packages>
|
||||
<Excluded_Packages Name="$(BDS)\bin\bcbofficexp140.bpl">Embarcadero C++Builder Office XP Servers Package</Excluded_Packages>
|
||||
<Excluded_Packages Name="$(BDS)\bin\dcloffice2k140.bpl">Microsoft Office 2000 Sample Automation Server Wrapper Components</Excluded_Packages>
|
||||
<Excluded_Packages Name="$(BDS)\bin\dclofficexp140.bpl">Microsoft Office XP Sample Automation Server Wrapper Components</Excluded_Packages>
|
||||
</Excluded_Packages>
|
||||
<Source>
|
||||
<Source Name="MainSource">RDPWInst.dpr</Source>
|
||||
</Source>
|
||||
</Delphi.Personality>
|
||||
</BorlandProject>
|
||||
<ProjectFileVersion>12</ProjectFileVersion>
|
||||
</ProjectExtensions>
|
||||
</Project>
|
||||
@ -1,29 +0,0 @@
|
||||
object Frm: TFrm
|
||||
Left = 0
|
||||
Top = 0
|
||||
BorderIcons = [biSystemMenu, biMinimize]
|
||||
BorderStyle = bsSingle
|
||||
Caption = 'Local RDP Checker'
|
||||
ClientHeight = 480
|
||||
ClientWidth = 640
|
||||
Color = clBtnFace
|
||||
Font.Charset = DEFAULT_CHARSET
|
||||
Font.Color = clWindowText
|
||||
Font.Height = -11
|
||||
Font.Name = 'Tahoma'
|
||||
Font.Style = []
|
||||
OldCreateOrder = False
|
||||
Position = poDesktopCenter
|
||||
OnCreate = FormCreate
|
||||
PixelsPerInch = 96
|
||||
TextHeight = 13
|
||||
object RDP: TMsRdpClient2
|
||||
Left = 0
|
||||
Top = 0
|
||||
Width = 640
|
||||
Height = 480
|
||||
TabOrder = 0
|
||||
OnDisconnected = RDPDisconnected
|
||||
ControlData = {0003000008000200000000000B0000000B000000}
|
||||
end
|
||||
end
|
||||
@ -1,160 +0,0 @@
|
||||
{
|
||||
Copyright 2015 Stas'M Corp.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
}
|
||||
|
||||
unit MainUnit;
|
||||
|
||||
interface
|
||||
|
||||
uses
|
||||
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
|
||||
Dialogs, OleServer, MSTSCLib_TLB, OleCtrls, Registry;
|
||||
|
||||
type
|
||||
TFrm = class(TForm)
|
||||
RDP: TMsRdpClient2;
|
||||
procedure RDPDisconnected(ASender: TObject; discReason: Integer);
|
||||
procedure FormCreate(Sender: TObject);
|
||||
private
|
||||
{ Private declarations }
|
||||
public
|
||||
{ Public declarations }
|
||||
end;
|
||||
|
||||
var
|
||||
Frm: TFrm;
|
||||
SecurityLayer, UserAuthentication: DWORD;
|
||||
|
||||
implementation
|
||||
|
||||
{$R *.dfm}
|
||||
|
||||
procedure TFrm.FormCreate(Sender: TObject);
|
||||
var
|
||||
Reg: TRegistry;
|
||||
begin
|
||||
RDP.DisconnectedText := 'Disconnected.';
|
||||
RDP.ConnectingText := 'Connecting...';
|
||||
RDP.ConnectedStatusText := 'Connected.';
|
||||
RDP.UserName := '';
|
||||
RDP.Server := '127.0.0.2';
|
||||
Reg := TRegistry.Create;
|
||||
Reg.RootKey := HKEY_LOCAL_MACHINE;
|
||||
|
||||
if Reg.OpenKey('\SYSTEM\CurrentControlSet\Control\Terminal Server\WinStations\RDP-Tcp', True) then
|
||||
begin
|
||||
try
|
||||
SecurityLayer := Reg.ReadInteger('SecurityLayer');
|
||||
UserAuthentication := Reg.ReadInteger('UserAuthentication');
|
||||
Reg.WriteInteger('SecurityLayer', 0);
|
||||
Reg.WriteInteger('UserAuthentication', 0);
|
||||
except
|
||||
|
||||
end;
|
||||
Reg.CloseKey;
|
||||
end;
|
||||
|
||||
if Reg.OpenKeyReadOnly('\SYSTEM\CurrentControlSet\Control\Terminal Server\WinStations\RDP-Tcp') then begin
|
||||
try
|
||||
RDP.AdvancedSettings2.RDPPort := Reg.ReadInteger('PortNumber');
|
||||
except
|
||||
|
||||
end;
|
||||
Reg.CloseKey;
|
||||
end;
|
||||
Reg.Free;
|
||||
Sleep(1000);
|
||||
RDP.Connect;
|
||||
end;
|
||||
|
||||
procedure TFrm.RDPDisconnected(ASender: TObject; discReason: Integer);
|
||||
var
|
||||
ErrStr: String;
|
||||
Reg: TRegistry;
|
||||
begin
|
||||
case discReason of
|
||||
1: ErrStr := 'Local disconnection.';
|
||||
2: ErrStr := 'Disconnected by user.';
|
||||
3: ErrStr := 'Disconnected by server.';
|
||||
$904: ErrStr := 'Socket closed.';
|
||||
$C08: ErrStr := 'Decompress error.';
|
||||
$108: ErrStr := 'Connection timed out.';
|
||||
$C06: ErrStr := 'Decryption error.';
|
||||
$104: ErrStr := 'DNS name lookup failure.';
|
||||
$508: ErrStr := 'DNS lookup failed.';
|
||||
$B06: ErrStr := 'Encryption error.';
|
||||
$604: ErrStr := 'Windows Sockets gethostbyname() call failed.';
|
||||
$208: ErrStr := 'Host not found error.';
|
||||
$408: ErrStr := 'Internal error.';
|
||||
$906: ErrStr := 'Internal security error.';
|
||||
$A06: ErrStr := 'Internal security error.';
|
||||
$506: ErrStr := 'The encryption method specified is not valid.';
|
||||
$804: ErrStr := 'Bad IP address specified.';
|
||||
$606: ErrStr := 'Server security data is not valid.';
|
||||
$406: ErrStr := 'Security data is not valid.';
|
||||
$308: ErrStr := 'The IP address specified is not valid.';
|
||||
$808: ErrStr := 'License negotiation failed.';
|
||||
$908: ErrStr := 'Licensing time-out.';
|
||||
$106: ErrStr := 'Out of memory.';
|
||||
$206: ErrStr := 'Out of memory.';
|
||||
$306: ErrStr := 'Out of memory.';
|
||||
$706: ErrStr := 'Failed to unpack server certificate.';
|
||||
$204: ErrStr := 'Socket connection failed.';
|
||||
$404: ErrStr := 'Windows Sockets recv() call failed.';
|
||||
$704: ErrStr := 'Time-out occurred.';
|
||||
$608: ErrStr := 'Internal timer error.';
|
||||
$304: ErrStr := 'Windows Sockets send() call failed.';
|
||||
$B07: ErrStr := 'The account is disabled.';
|
||||
$E07: ErrStr := 'The account is expired.';
|
||||
$D07: ErrStr := 'The account is locked out.';
|
||||
$C07: ErrStr := 'The account is restricted.';
|
||||
$1B07: ErrStr := 'The received certificate is expired.';
|
||||
$1607: ErrStr := 'The policy does not support delegation of credentials to the target server.';
|
||||
$2107: ErrStr := 'The server authentication policy does not allow connection requests using saved credentials. The user must enter new credentials.';
|
||||
$807: ErrStr := 'Login failed.';
|
||||
$1807: ErrStr := 'No authority could be contacted for authentication. The domain name of the authenticating party could be wrong, the domain could be unreachable, or there might have been a trust relationship failure.';
|
||||
$A07: ErrStr := 'The specified user has no account.';
|
||||
$F07: ErrStr := 'The password is expired.';
|
||||
$1207: ErrStr := 'The user password must be changed before logging on for the first time.';
|
||||
$1707: ErrStr := 'Delegation of credentials to the target server is not allowed unless mutual authentication has been achieved.';
|
||||
$2207: ErrStr := 'The smart card is blocked.';
|
||||
$1C07: ErrStr := 'An incorrect PIN was presented to the smart card.';
|
||||
$B09: ErrStr := 'Network Level Authentication is required, run RDPCheck as administrator.';
|
||||
$708: ErrStr := 'RDP is working, but the client doesn''t allow loopback connections. Try to connect to your PC from another device in the network.';
|
||||
else ErrStr := 'Unknown code 0x'+IntToHex(discReason, 1);
|
||||
end;
|
||||
if (discReason > 2) then
|
||||
MessageBox(Handle, PWideChar(ErrStr), 'Disconnected', mb_Ok or mb_IconError);
|
||||
|
||||
Reg := TRegistry.Create;
|
||||
Reg.RootKey := HKEY_LOCAL_MACHINE;
|
||||
|
||||
if Reg.OpenKey('\SYSTEM\CurrentControlSet\Control\Terminal Server\WinStations\RDP-Tcp', True) then
|
||||
begin
|
||||
try
|
||||
Reg.WriteInteger('SecurityLayer', SecurityLayer);
|
||||
Reg.WriteInteger('UserAuthentication', UserAuthentication);
|
||||
except
|
||||
|
||||
end;
|
||||
Reg.CloseKey;
|
||||
end;
|
||||
|
||||
Reg.Free;
|
||||
|
||||
Halt(0);
|
||||
end;
|
||||
|
||||
end.
|
||||
|
Before Width: | Height: | Size: 161 KiB |
@ -1,31 +0,0 @@
|
||||
{
|
||||
Copyright 2014 Stas'M Corp.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
}
|
||||
|
||||
program RDPCheck;
|
||||
|
||||
uses
|
||||
Forms,
|
||||
MainUnit in 'MainUnit.pas' {Frm};
|
||||
|
||||
{$R *.res}
|
||||
|
||||
begin
|
||||
Application.Initialize;
|
||||
Application.MainFormOnTaskbar := True;
|
||||
Application.Title := 'Local RDP Checker';
|
||||
Application.CreateForm(TFrm, Frm);
|
||||
Application.Run;
|
||||
end.
|
||||
@ -1,108 +0,0 @@
|
||||
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<ProjectGuid>{90AE83F6-26B8-45D4-92FE-CF4ACCDE9F68}</ProjectGuid>
|
||||
<ProjectVersion>12.0</ProjectVersion>
|
||||
<MainSource>RDPCheck.dpr</MainSource>
|
||||
<Config Condition="'$(Config)'==''">Release</Config>
|
||||
<DCC_DCCCompiler>DCC32</DCC_DCCCompiler>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Config)'=='Base' or '$(Base)'!=''">
|
||||
<Base>true</Base>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Config)'=='Release' or '$(Cfg_1)'!=''">
|
||||
<Cfg_1>true</Cfg_1>
|
||||
<CfgParent>Base</CfgParent>
|
||||
<Base>true</Base>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Config)'=='Debug' or '$(Cfg_2)'!=''">
|
||||
<Cfg_2>true</Cfg_2>
|
||||
<CfgParent>Base</CfgParent>
|
||||
<Base>true</Base>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Base)'!=''">
|
||||
<DCC_ExeOutput>..\bin\</DCC_ExeOutput>
|
||||
<DCC_UnitAlias>WinTypes=Windows;WinProcs=Windows;DbiTypes=BDE;DbiProcs=BDE;DbiErrs=BDE;$(DCC_UnitAlias)</DCC_UnitAlias>
|
||||
<DCC_DependencyCheckOutputName>..\bin\RDPCheck.exe</DCC_DependencyCheckOutputName>
|
||||
<DCC_ImageBase>00400000</DCC_ImageBase>
|
||||
<DCC_Platform>x86</DCC_Platform>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Cfg_1)'!=''">
|
||||
<DCC_LocalDebugSymbols>false</DCC_LocalDebugSymbols>
|
||||
<DCC_Define>RELEASE;$(DCC_Define)</DCC_Define>
|
||||
<DCC_SymbolReferenceInfo>0</DCC_SymbolReferenceInfo>
|
||||
<DCC_DebugInformation>false</DCC_DebugInformation>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Cfg_2)'!=''">
|
||||
<DCC_Define>DEBUG;$(DCC_Define)</DCC_Define>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<DelphiCompile Include="RDPCheck.dpr">
|
||||
<MainSource>MainSource</MainSource>
|
||||
</DelphiCompile>
|
||||
<DCCReference Include="MainUnit.pas">
|
||||
<Form>Frm</Form>
|
||||
</DCCReference>
|
||||
<BuildConfiguration Include="Base">
|
||||
<Key>Base</Key>
|
||||
</BuildConfiguration>
|
||||
<BuildConfiguration Include="Release">
|
||||
<Key>Cfg_1</Key>
|
||||
<CfgParent>Base</CfgParent>
|
||||
</BuildConfiguration>
|
||||
<BuildConfiguration Include="Debug">
|
||||
<Key>Cfg_2</Key>
|
||||
<CfgParent>Base</CfgParent>
|
||||
</BuildConfiguration>
|
||||
</ItemGroup>
|
||||
<Import Project="$(BDS)\Bin\CodeGear.Delphi.Targets" Condition="Exists('$(BDS)\Bin\CodeGear.Delphi.Targets')"/>
|
||||
<ProjectExtensions>
|
||||
<Borland.Personality>Delphi.Personality.12</Borland.Personality>
|
||||
<Borland.ProjectType/>
|
||||
<BorlandProject>
|
||||
<Delphi.Personality>
|
||||
<Parameters>
|
||||
<Parameters Name="UseLauncher">False</Parameters>
|
||||
<Parameters Name="LoadAllSymbols">True</Parameters>
|
||||
<Parameters Name="LoadUnspecifiedSymbols">False</Parameters>
|
||||
</Parameters>
|
||||
<VersionInfo>
|
||||
<VersionInfo Name="IncludeVerInfo">True</VersionInfo>
|
||||
<VersionInfo Name="AutoIncBuild">False</VersionInfo>
|
||||
<VersionInfo Name="MajorVer">2</VersionInfo>
|
||||
<VersionInfo Name="MinorVer">2</VersionInfo>
|
||||
<VersionInfo Name="Release">0</VersionInfo>
|
||||
<VersionInfo Name="Build">0</VersionInfo>
|
||||
<VersionInfo Name="Debug">False</VersionInfo>
|
||||
<VersionInfo Name="PreRelease">False</VersionInfo>
|
||||
<VersionInfo Name="Special">False</VersionInfo>
|
||||
<VersionInfo Name="Private">False</VersionInfo>
|
||||
<VersionInfo Name="DLL">False</VersionInfo>
|
||||
<VersionInfo Name="Locale">1033</VersionInfo>
|
||||
<VersionInfo Name="CodePage">1252</VersionInfo>
|
||||
</VersionInfo>
|
||||
<VersionInfoKeys>
|
||||
<VersionInfoKeys Name="CompanyName">Stas'M Corp.</VersionInfoKeys>
|
||||
<VersionInfoKeys Name="FileDescription">Local RDP Checker</VersionInfoKeys>
|
||||
<VersionInfoKeys Name="FileVersion">2.2.0.0</VersionInfoKeys>
|
||||
<VersionInfoKeys Name="InternalName">RDPCheck</VersionInfoKeys>
|
||||
<VersionInfoKeys Name="LegalCopyright">Copyright © Stas'M Corp. 2015</VersionInfoKeys>
|
||||
<VersionInfoKeys Name="LegalTrademarks">Stas'M Corp.</VersionInfoKeys>
|
||||
<VersionInfoKeys Name="OriginalFilename">RDPCheck.exe</VersionInfoKeys>
|
||||
<VersionInfoKeys Name="ProductName">RDP Host Support</VersionInfoKeys>
|
||||
<VersionInfoKeys Name="ProductVersion">1.6.0.0</VersionInfoKeys>
|
||||
<VersionInfoKeys Name="Comments">http://stascorp.com</VersionInfoKeys>
|
||||
</VersionInfoKeys>
|
||||
<Excluded_Packages>
|
||||
<Excluded_Packages Name="$(BDS)\bin\bcboffice2k140.bpl">Embarcadero C++Builder Office 2000 Servers Package</Excluded_Packages>
|
||||
<Excluded_Packages Name="$(BDS)\bin\bcbofficexp140.bpl">Embarcadero C++Builder Office XP Servers Package</Excluded_Packages>
|
||||
<Excluded_Packages Name="$(BDS)\bin\dcloffice2k140.bpl">Microsoft Office 2000 Sample Automation Server Wrapper Components</Excluded_Packages>
|
||||
<Excluded_Packages Name="$(BDS)\bin\dclofficexp140.bpl">Microsoft Office XP Sample Automation Server Wrapper Components</Excluded_Packages>
|
||||
</Excluded_Packages>
|
||||
<Source>
|
||||
<Source Name="MainSource">RDPCheck.dpr</Source>
|
||||
</Source>
|
||||
</Delphi.Personality>
|
||||
</BorlandProject>
|
||||
<ProjectFileVersion>12</ProjectFileVersion>
|
||||
</ProjectExtensions>
|
||||
</Project>
|
||||
@ -1,47 +0,0 @@
|
||||
object LicenseForm: TLicenseForm
|
||||
Left = 0
|
||||
Top = 0
|
||||
BorderIcons = []
|
||||
BorderStyle = bsDialog
|
||||
Caption = 'License Agreement'
|
||||
ClientHeight = 344
|
||||
ClientWidth = 386
|
||||
Color = clBtnFace
|
||||
Font.Charset = DEFAULT_CHARSET
|
||||
Font.Color = clWindowText
|
||||
Font.Height = -11
|
||||
Font.Name = 'Tahoma'
|
||||
Font.Style = []
|
||||
OldCreateOrder = False
|
||||
Position = poOwnerFormCenter
|
||||
PixelsPerInch = 96
|
||||
TextHeight = 13
|
||||
object mText: TMemo
|
||||
Left = 8
|
||||
Top = 8
|
||||
Width = 370
|
||||
Height = 297
|
||||
ReadOnly = True
|
||||
ScrollBars = ssBoth
|
||||
TabOrder = 0
|
||||
WordWrap = False
|
||||
end
|
||||
object bAccept: TButton
|
||||
Left = 115
|
||||
Top = 311
|
||||
Width = 75
|
||||
Height = 25
|
||||
Caption = '&Accept'
|
||||
ModalResult = 1
|
||||
TabOrder = 1
|
||||
end
|
||||
object bDecline: TButton
|
||||
Left = 196
|
||||
Top = 311
|
||||
Width = 75
|
||||
Height = 25
|
||||
Caption = '&Decline'
|
||||
ModalResult = 2
|
||||
TabOrder = 2
|
||||
end
|
||||
end
|
||||
@ -1,43 +0,0 @@
|
||||
{
|
||||
Copyright 2014 Stas'M Corp.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
}
|
||||
|
||||
unit LicenseUnit;
|
||||
|
||||
interface
|
||||
|
||||
uses
|
||||
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
|
||||
Dialogs, StdCtrls;
|
||||
|
||||
type
|
||||
TLicenseForm = class(TForm)
|
||||
mText: TMemo;
|
||||
bAccept: TButton;
|
||||
bDecline: TButton;
|
||||
private
|
||||
{ Private declarations }
|
||||
public
|
||||
{ Public declarations }
|
||||
end;
|
||||
|
||||
var
|
||||
LicenseForm: TLicenseForm;
|
||||
|
||||
implementation
|
||||
|
||||
{$R *.dfm}
|
||||
|
||||
end.
|
||||
@ -1,241 +0,0 @@
|
||||
object MainForm: TMainForm
|
||||
Left = 0
|
||||
Top = 0
|
||||
BorderStyle = bsDialog
|
||||
Caption = 'RDP Wrapper Configuration'
|
||||
ClientHeight = 314
|
||||
ClientWidth = 404
|
||||
Color = clBtnFace
|
||||
Font.Charset = DEFAULT_CHARSET
|
||||
Font.Color = clWindowText
|
||||
Font.Height = -11
|
||||
Font.Name = 'Tahoma'
|
||||
Font.Style = []
|
||||
OldCreateOrder = False
|
||||
Position = poDesktopCenter
|
||||
OnCloseQuery = FormCloseQuery
|
||||
OnCreate = FormCreate
|
||||
OnDestroy = FormDestroy
|
||||
PixelsPerInch = 96
|
||||
TextHeight = 13
|
||||
object bOK: TButton
|
||||
Left = 40
|
||||
Top = 281
|
||||
Width = 75
|
||||
Height = 25
|
||||
Caption = 'OK'
|
||||
ModalResult = 1
|
||||
TabOrder = 4
|
||||
OnClick = bOKClick
|
||||
end
|
||||
object bCancel: TButton
|
||||
Left = 121
|
||||
Top = 281
|
||||
Width = 75
|
||||
Height = 25
|
||||
Caption = 'Cancel'
|
||||
ModalResult = 2
|
||||
TabOrder = 5
|
||||
OnClick = bCancelClick
|
||||
end
|
||||
object bApply: TButton
|
||||
Left = 202
|
||||
Top = 281
|
||||
Width = 75
|
||||
Height = 25
|
||||
Caption = 'Apply'
|
||||
Enabled = False
|
||||
TabOrder = 6
|
||||
OnClick = bApplyClick
|
||||
end
|
||||
object rgNLA: TRadioGroup
|
||||
Left = 202
|
||||
Top = 89
|
||||
Width = 194
|
||||
Height = 73
|
||||
Caption = 'Authentication Mode'
|
||||
Items.Strings = (
|
||||
'GUI Authentication Only'
|
||||
'Default RDP Authentication'
|
||||
'Network Level Authentication')
|
||||
TabOrder = 2
|
||||
OnClick = cbAllowTSConnectionsClick
|
||||
end
|
||||
object rgShadow: TRadioGroup
|
||||
Left = 202
|
||||
Top = 168
|
||||
Width = 194
|
||||
Height = 105
|
||||
Caption = 'Session Shadowing Mode'
|
||||
Items.Strings = (
|
||||
'Disable Shadowing'
|
||||
'Full access with user'#39's permission'
|
||||
'Full access without permission'
|
||||
'View only with user'#39's permission'
|
||||
'View only without permission')
|
||||
TabOrder = 3
|
||||
OnClick = cbAllowTSConnectionsClick
|
||||
end
|
||||
object bLicense: TButton
|
||||
Left = 283
|
||||
Top = 281
|
||||
Width = 87
|
||||
Height = 25
|
||||
Caption = 'View license...'
|
||||
TabOrder = 7
|
||||
OnClick = bLicenseClick
|
||||
end
|
||||
object gbDiag: TGroupBox
|
||||
Left = 8
|
||||
Top = 6
|
||||
Width = 388
|
||||
Height = 77
|
||||
Caption = 'Diagnostics'
|
||||
TabOrder = 0
|
||||
object lListener: TLabel
|
||||
Left = 11
|
||||
Top = 55
|
||||
Width = 70
|
||||
Height = 13
|
||||
Caption = 'Listener state:'
|
||||
end
|
||||
object lService: TLabel
|
||||
Left = 11
|
||||
Top = 36
|
||||
Width = 67
|
||||
Height = 13
|
||||
Caption = 'Service state:'
|
||||
end
|
||||
object lsListener: TLabel
|
||||
Left = 91
|
||||
Top = 55
|
||||
Width = 44
|
||||
Height = 13
|
||||
Caption = 'Unknown'
|
||||
end
|
||||
object lsService: TLabel
|
||||
Left = 91
|
||||
Top = 36
|
||||
Width = 44
|
||||
Height = 13
|
||||
Caption = 'Unknown'
|
||||
end
|
||||
object lsTSVer: TLabel
|
||||
Left = 226
|
||||
Top = 36
|
||||
Width = 44
|
||||
Height = 13
|
||||
Caption = 'Unknown'
|
||||
end
|
||||
object lsWrapper: TLabel
|
||||
Left = 91
|
||||
Top = 17
|
||||
Width = 44
|
||||
Height = 13
|
||||
Caption = 'Unknown'
|
||||
end
|
||||
object lsWrapVer: TLabel
|
||||
Left = 226
|
||||
Top = 17
|
||||
Width = 44
|
||||
Height = 13
|
||||
Caption = 'Unknown'
|
||||
end
|
||||
object lTSVer: TLabel
|
||||
Left = 202
|
||||
Top = 36
|
||||
Width = 20
|
||||
Height = 13
|
||||
Caption = 'ver.'
|
||||
end
|
||||
object lWrapper: TLabel
|
||||
Left = 11
|
||||
Top = 17
|
||||
Width = 74
|
||||
Height = 13
|
||||
Caption = 'Wrapper state:'
|
||||
end
|
||||
object lWrapVer: TLabel
|
||||
Left = 202
|
||||
Top = 17
|
||||
Width = 20
|
||||
Height = 13
|
||||
Caption = 'ver.'
|
||||
end
|
||||
object lsSuppVer: TLabel
|
||||
Left = 202
|
||||
Top = 55
|
||||
Width = 70
|
||||
Height = 13
|
||||
Caption = '[support level]'
|
||||
end
|
||||
end
|
||||
object gbGeneral: TGroupBox
|
||||
Left = 8
|
||||
Top = 89
|
||||
Width = 188
|
||||
Height = 184
|
||||
Caption = 'General Settings'
|
||||
TabOrder = 1
|
||||
object lRDPPort: TLabel
|
||||
Left = 8
|
||||
Top = 44
|
||||
Width = 47
|
||||
Height = 13
|
||||
Caption = 'RDP port:'
|
||||
end
|
||||
object cbAllowTSConnections: TCheckBox
|
||||
Left = 8
|
||||
Top = 18
|
||||
Width = 132
|
||||
Height = 17
|
||||
Caption = 'Enable Remote Desktop'
|
||||
TabOrder = 0
|
||||
OnClick = cbAllowTSConnectionsClick
|
||||
end
|
||||
object cbSingleSessionPerUser: TCheckBox
|
||||
Left = 8
|
||||
Top = 69
|
||||
Width = 129
|
||||
Height = 17
|
||||
Caption = 'Single session per user'
|
||||
TabOrder = 2
|
||||
OnClick = cbAllowTSConnectionsClick
|
||||
end
|
||||
object cbHideUsers: TCheckBox
|
||||
Left = 8
|
||||
Top = 92
|
||||
Width = 149
|
||||
Height = 17
|
||||
Caption = 'Hide users on logon screen'
|
||||
TabOrder = 3
|
||||
OnClick = cbAllowTSConnectionsClick
|
||||
end
|
||||
object seRDPPort: TSpinEdit
|
||||
Left = 61
|
||||
Top = 41
|
||||
Width = 62
|
||||
Height = 22
|
||||
MaxValue = 65535
|
||||
MinValue = 0
|
||||
TabOrder = 1
|
||||
Value = 0
|
||||
OnChange = seRDPPortChange
|
||||
end
|
||||
object cbCustomPrg: TCheckBox
|
||||
Left = 8
|
||||
Top = 115
|
||||
Width = 169
|
||||
Height = 17
|
||||
Caption = 'Allow to start custom programs'
|
||||
TabOrder = 4
|
||||
OnClick = cbAllowTSConnectionsClick
|
||||
end
|
||||
end
|
||||
object Timer: TTimer
|
||||
Interval = 250
|
||||
OnTimer = TimerTimer
|
||||
Left = 352
|
||||
Top = 27
|
||||
end
|
||||
end
|
||||
@ -1,722 +0,0 @@
|
||||
{
|
||||
Copyright 2017 Stas'M Corp.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
}
|
||||
|
||||
unit MainUnit;
|
||||
|
||||
interface
|
||||
|
||||
uses
|
||||
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
|
||||
Dialogs, StdCtrls, Spin, ExtCtrls, Registry, WinSvc;
|
||||
|
||||
type
|
||||
TMainForm = class(TForm)
|
||||
bOK: TButton;
|
||||
bCancel: TButton;
|
||||
bApply: TButton;
|
||||
cbSingleSessionPerUser: TCheckBox;
|
||||
rgNLA: TRadioGroup;
|
||||
cbAllowTSConnections: TCheckBox;
|
||||
rgShadow: TRadioGroup;
|
||||
seRDPPort: TSpinEdit;
|
||||
lRDPPort: TLabel;
|
||||
lService: TLabel;
|
||||
lListener: TLabel;
|
||||
lWrapper: TLabel;
|
||||
lsListener: TLabel;
|
||||
lsService: TLabel;
|
||||
lsWrapper: TLabel;
|
||||
Timer: TTimer;
|
||||
lTSVer: TLabel;
|
||||
lsTSVer: TLabel;
|
||||
lWrapVer: TLabel;
|
||||
lsWrapVer: TLabel;
|
||||
bLicense: TButton;
|
||||
gbDiag: TGroupBox;
|
||||
lsSuppVer: TLabel;
|
||||
cbHideUsers: TCheckBox;
|
||||
gbGeneral: TGroupBox;
|
||||
cbCustomPrg: TCheckBox;
|
||||
procedure FormCreate(Sender: TObject);
|
||||
procedure cbAllowTSConnectionsClick(Sender: TObject);
|
||||
procedure seRDPPortChange(Sender: TObject);
|
||||
procedure bApplyClick(Sender: TObject);
|
||||
procedure bCancelClick(Sender: TObject);
|
||||
procedure bOKClick(Sender: TObject);
|
||||
procedure FormCloseQuery(Sender: TObject; var CanClose: Boolean);
|
||||
procedure bLicenseClick(Sender: TObject);
|
||||
procedure TimerTimer(Sender: TObject);
|
||||
procedure FormDestroy(Sender: TObject);
|
||||
private
|
||||
{ Private declarations }
|
||||
public
|
||||
{ Public declarations }
|
||||
function ExecWait(Cmdline: String): Boolean;
|
||||
procedure ReadSettings;
|
||||
procedure WriteSettings;
|
||||
end;
|
||||
FILE_VERSION = record
|
||||
Version: record case Boolean of
|
||||
True: (dw: DWORD);
|
||||
False: (w: record
|
||||
Minor, Major: Word;
|
||||
end;)
|
||||
end;
|
||||
Release, Build: Word;
|
||||
bDebug, bPrerelease, bPrivate, bSpecial: Boolean;
|
||||
end;
|
||||
WTS_SESSION_INFOW = record
|
||||
SessionId: DWORD;
|
||||
Name: packed array [0..33] of WideChar;
|
||||
State: DWORD;
|
||||
end;
|
||||
WTS_SESSION = Array[0..0] of WTS_SESSION_INFOW;
|
||||
PWTS_SESSION_INFOW = ^WTS_SESSION;
|
||||
|
||||
const
|
||||
winstadll = 'winsta.dll';
|
||||
var
|
||||
MainForm: TMainForm;
|
||||
Ready: Boolean = False;
|
||||
Arch: Byte;
|
||||
OldWow64RedirectionValue: LongBool;
|
||||
OldPort: Word;
|
||||
INI: String;
|
||||
|
||||
function WinStationEnumerateW(hServer: THandle;
|
||||
var ppSessionInfo: PWTS_SESSION_INFOW; var pCount: DWORD): BOOL; stdcall;
|
||||
external winstadll name 'WinStationEnumerateW';
|
||||
function WinStationFreeMemory(P: Pointer): BOOL; stdcall; external winstadll;
|
||||
|
||||
implementation
|
||||
|
||||
{$R *.dfm}
|
||||
{$R resource.res}
|
||||
|
||||
uses
|
||||
LicenseUnit;
|
||||
|
||||
function ExpandPath(Path: String): String;
|
||||
var
|
||||
Str: Array[0..511] of Char;
|
||||
begin
|
||||
Result := '';
|
||||
FillChar(Str, 512, 0);
|
||||
if Arch = 64 then
|
||||
Path := StringReplace(Path, '%ProgramFiles%', '%ProgramW6432%', [rfReplaceAll, rfIgnoreCase]);
|
||||
if ExpandEnvironmentStrings(PWideChar(Path), Str, 512) > 0 then
|
||||
Result := Str;
|
||||
end;
|
||||
|
||||
function DisableWowRedirection: Boolean;
|
||||
type
|
||||
TFunc = function(var Wow64FsEnableRedirection: LongBool): LongBool; stdcall;
|
||||
var
|
||||
hModule: THandle;
|
||||
Wow64DisableWow64FsRedirection: TFunc;
|
||||
begin
|
||||
Result := False;
|
||||
hModule := GetModuleHandle(kernel32);
|
||||
if hModule <> 0 then
|
||||
Wow64DisableWow64FsRedirection := GetProcAddress(hModule, 'Wow64DisableWow64FsRedirection')
|
||||
else
|
||||
Exit;
|
||||
if @Wow64DisableWow64FsRedirection <> nil then
|
||||
Result := Wow64DisableWow64FsRedirection(OldWow64RedirectionValue);
|
||||
end;
|
||||
|
||||
function RevertWowRedirection: Boolean;
|
||||
type
|
||||
TFunc = function(var Wow64RevertWow64FsRedirection: LongBool): LongBool; stdcall;
|
||||
var
|
||||
hModule: THandle;
|
||||
Wow64RevertWow64FsRedirection: TFunc;
|
||||
begin
|
||||
Result := False;
|
||||
hModule := GetModuleHandle(kernel32);
|
||||
if hModule <> 0 then
|
||||
Wow64RevertWow64FsRedirection := GetProcAddress(hModule, 'Wow64RevertWow64FsRedirection')
|
||||
else
|
||||
Exit;
|
||||
if @Wow64RevertWow64FsRedirection <> nil then
|
||||
Result := Wow64RevertWow64FsRedirection(OldWow64RedirectionValue);
|
||||
end;
|
||||
|
||||
function GetFileVersion(const FileName: TFileName; var FileVersion: FILE_VERSION): Boolean;
|
||||
type
|
||||
VS_VERSIONINFO = record
|
||||
wLength, wValueLength, wType: Word;
|
||||
szKey: Array[1..16] of WideChar;
|
||||
Padding1: Word;
|
||||
Value: VS_FIXEDFILEINFO;
|
||||
Padding2, Children: Word;
|
||||
end;
|
||||
PVS_VERSIONINFO = ^VS_VERSIONINFO;
|
||||
const
|
||||
VFF_DEBUG = 1;
|
||||
VFF_PRERELEASE = 2;
|
||||
VFF_PRIVATE = 8;
|
||||
VFF_SPECIAL = 32;
|
||||
var
|
||||
hFile: HMODULE;
|
||||
hResourceInfo: HRSRC;
|
||||
VersionInfo: PVS_VERSIONINFO;
|
||||
begin
|
||||
Result := False;
|
||||
|
||||
hFile := LoadLibraryEx(PWideChar(FileName), 0, LOAD_LIBRARY_AS_DATAFILE);
|
||||
if hFile = 0 then
|
||||
Exit;
|
||||
|
||||
hResourceInfo := FindResource(hFile, PWideChar(1), PWideChar($10));
|
||||
if hResourceInfo = 0 then
|
||||
Exit;
|
||||
|
||||
VersionInfo := Pointer(LoadResource(hFile, hResourceInfo));
|
||||
if VersionInfo = nil then
|
||||
Exit;
|
||||
|
||||
FileVersion.Version.dw := VersionInfo.Value.dwFileVersionMS;
|
||||
FileVersion.Release := Word(VersionInfo.Value.dwFileVersionLS shr 16);
|
||||
FileVersion.Build := Word(VersionInfo.Value.dwFileVersionLS);
|
||||
FileVersion.bDebug := (VersionInfo.Value.dwFileFlags and VFF_DEBUG) = VFF_DEBUG;
|
||||
FileVersion.bPrerelease := (VersionInfo.Value.dwFileFlags and VFF_PRERELEASE) = VFF_PRERELEASE;
|
||||
FileVersion.bPrivate := (VersionInfo.Value.dwFileFlags and VFF_PRIVATE) = VFF_PRIVATE;
|
||||
FileVersion.bSpecial := (VersionInfo.Value.dwFileFlags and VFF_SPECIAL) = VFF_SPECIAL;
|
||||
|
||||
FreeLibrary(hFile);
|
||||
Result := True;
|
||||
end;
|
||||
|
||||
function IsWrapperInstalled(var WrapperPath: String): ShortInt;
|
||||
var
|
||||
TermServiceHost,
|
||||
TermServicePath: String;
|
||||
Reg: TRegistry;
|
||||
begin
|
||||
Result := -1;
|
||||
WrapperPath := '';
|
||||
Reg := TRegistry.Create;
|
||||
Reg.RootKey := HKEY_LOCAL_MACHINE;
|
||||
if not Reg.OpenKeyReadOnly('\SYSTEM\CurrentControlSet\Services\TermService') then begin
|
||||
Reg.Free;
|
||||
Exit;
|
||||
end;
|
||||
TermServiceHost := Reg.ReadString('ImagePath');
|
||||
Reg.CloseKey;
|
||||
if Pos('svchost.exe', LowerCase(TermServiceHost)) = 0 then
|
||||
begin
|
||||
Result := 2;
|
||||
Reg.Free;
|
||||
Exit;
|
||||
end;
|
||||
if not Reg.OpenKeyReadOnly('\SYSTEM\CurrentControlSet\Services\TermService\Parameters') then
|
||||
begin
|
||||
Reg.Free;
|
||||
Exit;
|
||||
end;
|
||||
TermServicePath := Reg.ReadString('ServiceDll');
|
||||
Reg.CloseKey;
|
||||
Reg.Free;
|
||||
if (Pos('termsrv.dll', LowerCase(TermServicePath)) = 0)
|
||||
and (Pos('rdpwrap.dll', LowerCase(TermServicePath)) = 0) then
|
||||
begin
|
||||
Result := 2;
|
||||
Exit;
|
||||
end;
|
||||
|
||||
if Pos('rdpwrap.dll', LowerCase(TermServicePath)) > 0 then begin
|
||||
WrapperPath := TermServicePath;
|
||||
Result := 1;
|
||||
end else
|
||||
Result := 0;
|
||||
end;
|
||||
|
||||
function GetTermSrvState: ShortInt;
|
||||
type
|
||||
SERVICE_STATUS_PROCESS = record
|
||||
dwServiceType,
|
||||
dwCurrentState,
|
||||
dwControlsAccepted,
|
||||
dwWin32ExitCode,
|
||||
dwServiceSpecificExitCode,
|
||||
dwCheckPoint,
|
||||
dwWaitHint,
|
||||
dwProcessId,
|
||||
dwServiceFlags: DWORD;
|
||||
end;
|
||||
PSERVICE_STATUS_PROCESS = ^SERVICE_STATUS_PROCESS;
|
||||
const
|
||||
SvcName = 'TermService';
|
||||
var
|
||||
hSC: SC_HANDLE;
|
||||
hSvc: THandle;
|
||||
lpServiceStatusProcess: PSERVICE_STATUS_PROCESS;
|
||||
Buf: Pointer;
|
||||
cbBufSize, pcbBytesNeeded: Cardinal;
|
||||
begin
|
||||
Result := -1;
|
||||
hSC := OpenSCManager(nil, SERVICES_ACTIVE_DATABASE, SC_MANAGER_CONNECT);
|
||||
if hSC = 0 then
|
||||
Exit;
|
||||
|
||||
hSvc := OpenService(hSC, PWideChar(SvcName), SERVICE_QUERY_STATUS);
|
||||
if hSvc = 0 then
|
||||
begin
|
||||
CloseServiceHandle(hSC);
|
||||
Exit;
|
||||
end;
|
||||
|
||||
if QueryServiceStatusEx(hSvc, SC_STATUS_PROCESS_INFO, nil, 0, pcbBytesNeeded) then
|
||||
Exit;
|
||||
|
||||
cbBufSize := pcbBytesNeeded;
|
||||
GetMem(Buf, cbBufSize);
|
||||
|
||||
if not QueryServiceStatusEx(hSvc, SC_STATUS_PROCESS_INFO, Buf, cbBufSize, pcbBytesNeeded) then begin
|
||||
FreeMem(Buf, cbBufSize);
|
||||
CloseServiceHandle(hSvc);
|
||||
CloseServiceHandle(hSC);
|
||||
Exit;
|
||||
end else begin
|
||||
lpServiceStatusProcess := Buf;
|
||||
Result := ShortInt(lpServiceStatusProcess^.dwCurrentState);
|
||||
end;
|
||||
FreeMem(Buf, cbBufSize);
|
||||
CloseServiceHandle(hSvc);
|
||||
CloseServiceHandle(hSC);
|
||||
end;
|
||||
|
||||
function IsListenerWorking: Boolean;
|
||||
var
|
||||
pCount: DWORD;
|
||||
SessionInfo: PWTS_SESSION_INFOW;
|
||||
I: Integer;
|
||||
begin
|
||||
Result := False;
|
||||
if not WinStationEnumerateW(0, SessionInfo, pCount) then
|
||||
Exit;
|
||||
for I := 0 to pCount - 1 do
|
||||
if SessionInfo^[I].Name = 'RDP-Tcp' then begin
|
||||
Result := True;
|
||||
Break;
|
||||
end;
|
||||
WinStationFreeMemory(SessionInfo);
|
||||
end;
|
||||
|
||||
function ExtractResText(ResName: String): String;
|
||||
var
|
||||
ResStream: TResourceStream;
|
||||
Str: TStringList;
|
||||
begin
|
||||
ResStream := TResourceStream.Create(HInstance, ResName, RT_RCDATA);
|
||||
Str := TStringList.Create;
|
||||
try
|
||||
Str.LoadFromStream(ResStream);
|
||||
except
|
||||
|
||||
end;
|
||||
ResStream.Free;
|
||||
Result := Str.Text;
|
||||
Str.Free;
|
||||
end;
|
||||
|
||||
function TMainForm.ExecWait(Cmdline: String): Boolean;
|
||||
var
|
||||
si: STARTUPINFO;
|
||||
pi: PROCESS_INFORMATION;
|
||||
begin
|
||||
Result := False;
|
||||
ZeroMemory(@si, sizeof(si));
|
||||
si.cb := sizeof(si);
|
||||
si.dwFlags := STARTF_USESHOWWINDOW;
|
||||
si.wShowWindow := SW_HIDE;
|
||||
UniqueString(Cmdline);
|
||||
if not CreateProcess(nil, PWideChar(Cmdline), nil, nil, True, 0, nil, nil, si, pi) then begin
|
||||
MessageBox(Handle,
|
||||
PWideChar('CreateProcess error (code: ' + IntToStr(GetLastError) + ').'),
|
||||
'Error', MB_ICONERROR or MB_OK);
|
||||
Exit;
|
||||
end;
|
||||
CloseHandle(pi.hThread);
|
||||
WaitForSingleObject(pi.hProcess, INFINITE);
|
||||
CloseHandle(pi.hProcess);
|
||||
Result := True;
|
||||
end;
|
||||
|
||||
procedure TMainForm.ReadSettings;
|
||||
var
|
||||
Reg: TRegistry;
|
||||
SecurityLayer, UserAuthentication: Integer;
|
||||
begin
|
||||
Reg := TRegistry.Create;
|
||||
Reg.RootKey := HKEY_LOCAL_MACHINE;
|
||||
Reg.OpenKeyReadOnly('\SYSTEM\CurrentControlSet\Control\Terminal Server');
|
||||
try
|
||||
cbAllowTSConnections.Checked := not Reg.ReadBool('fDenyTSConnections');
|
||||
except
|
||||
|
||||
end;
|
||||
try
|
||||
cbSingleSessionPerUser.Checked := Reg.ReadBool('fSingleSessionPerUser');
|
||||
except
|
||||
|
||||
end;
|
||||
try
|
||||
cbCustomPrg.Checked := Reg.ReadBool('HonorLegacySettings');
|
||||
except
|
||||
|
||||
end;
|
||||
Reg.CloseKey;
|
||||
|
||||
Reg.OpenKeyReadOnly('\SYSTEM\CurrentControlSet\Control\Terminal Server\WinStations\RDP-Tcp');
|
||||
seRDPPort.Value := 3389;
|
||||
try
|
||||
seRDPPort.Value := Reg.ReadInteger('PortNumber');
|
||||
except
|
||||
|
||||
end;
|
||||
OldPort := seRDPPort.Value;
|
||||
SecurityLayer := 0;
|
||||
UserAuthentication := 0;
|
||||
try
|
||||
SecurityLayer := Reg.ReadInteger('SecurityLayer');
|
||||
UserAuthentication := Reg.ReadInteger('UserAuthentication');
|
||||
except
|
||||
|
||||
end;
|
||||
if (SecurityLayer = 0) and (UserAuthentication = 0) then
|
||||
rgNLA.ItemIndex := 0;
|
||||
if (SecurityLayer = 1) and (UserAuthentication = 0) then
|
||||
rgNLA.ItemIndex := 1;
|
||||
if (SecurityLayer = 2) and (UserAuthentication = 1) then
|
||||
rgNLA.ItemIndex := 2;
|
||||
try
|
||||
rgShadow.ItemIndex := Reg.ReadInteger('Shadow');
|
||||
except
|
||||
|
||||
end;
|
||||
Reg.CloseKey;
|
||||
Reg.OpenKeyReadOnly('\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System');
|
||||
try
|
||||
cbHideUsers.Checked := Reg.ReadBool('dontdisplaylastusername');
|
||||
except
|
||||
|
||||
end;
|
||||
Reg.CloseKey;
|
||||
Reg.Free;
|
||||
end;
|
||||
|
||||
procedure TMainForm.WriteSettings;
|
||||
var
|
||||
Reg: TRegistry;
|
||||
SecurityLayer, UserAuthentication: Integer;
|
||||
begin
|
||||
Reg := TRegistry.Create;
|
||||
Reg.RootKey := HKEY_LOCAL_MACHINE;
|
||||
Reg.OpenKey('\SYSTEM\CurrentControlSet\Control\Terminal Server', True);
|
||||
try
|
||||
Reg.WriteBool('fDenyTSConnections', not cbAllowTSConnections.Checked);
|
||||
except
|
||||
|
||||
end;
|
||||
try
|
||||
Reg.WriteBool('fSingleSessionPerUser', cbSingleSessionPerUser.Checked);
|
||||
except
|
||||
|
||||
end;
|
||||
try
|
||||
Reg.WriteBool('HonorLegacySettings', cbCustomPrg.Checked);
|
||||
except
|
||||
|
||||
end;
|
||||
Reg.CloseKey;
|
||||
|
||||
Reg.OpenKey('\SYSTEM\CurrentControlSet\Control\Terminal Server\WinStations\RDP-Tcp', True);
|
||||
try
|
||||
Reg.WriteInteger('PortNumber', seRDPPort.Value);
|
||||
except
|
||||
|
||||
end;
|
||||
if OldPort <> seRDPPort.Value then
|
||||
begin
|
||||
OldPort := seRDPPort.Value;
|
||||
ExecWait('netsh advfirewall firewall set rule name="Remote Desktop" new localport=' + IntToStr(OldPort));
|
||||
end;
|
||||
case rgNLA.ItemIndex of
|
||||
0: begin
|
||||
SecurityLayer := 0;
|
||||
UserAuthentication := 0;
|
||||
end;
|
||||
1: begin
|
||||
SecurityLayer := 1;
|
||||
UserAuthentication := 0;
|
||||
end;
|
||||
2: begin
|
||||
SecurityLayer := 2;
|
||||
UserAuthentication := 1;
|
||||
end;
|
||||
else begin
|
||||
SecurityLayer := -1;
|
||||
UserAuthentication := -1;
|
||||
end;
|
||||
end;
|
||||
if SecurityLayer >= 0 then begin
|
||||
try
|
||||
Reg.WriteInteger('SecurityLayer', SecurityLayer);
|
||||
Reg.WriteInteger('UserAuthentication', UserAuthentication);
|
||||
except
|
||||
|
||||
end;
|
||||
end;
|
||||
if rgShadow.ItemIndex >= 0 then begin
|
||||
try
|
||||
Reg.WriteInteger('Shadow', rgShadow.ItemIndex);
|
||||
except
|
||||
|
||||
end;
|
||||
end;
|
||||
Reg.CloseKey;
|
||||
Reg.OpenKey('\SOFTWARE\Policies\Microsoft\Windows NT\Terminal Services', True);
|
||||
if rgShadow.ItemIndex >= 0 then begin
|
||||
try
|
||||
Reg.WriteInteger('Shadow', rgShadow.ItemIndex);
|
||||
except
|
||||
|
||||
end;
|
||||
end;
|
||||
Reg.CloseKey;
|
||||
Reg.OpenKey('\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System', True);
|
||||
try
|
||||
Reg.WriteBool('dontdisplaylastusername', cbHideUsers.Checked);
|
||||
except
|
||||
|
||||
end;
|
||||
Reg.CloseKey;
|
||||
Reg.Free;
|
||||
end;
|
||||
|
||||
function CheckSupport(FV: FILE_VERSION): Byte;
|
||||
var
|
||||
VerTxt: String;
|
||||
begin
|
||||
Result := 0;
|
||||
if (FV.Version.w.Major = 6) and (FV.Version.w.Minor = 0) then
|
||||
Result := 1;
|
||||
if (FV.Version.w.Major = 6) and (FV.Version.w.Minor = 1) then
|
||||
Result := 1;
|
||||
VerTxt := Format('%d.%d.%d.%d',
|
||||
[FV.Version.w.Major, FV.Version.w.Minor, FV.Release, FV.Build]);
|
||||
if Pos('[' + VerTxt + ']', INI) > 0 then
|
||||
Result := 2;
|
||||
end;
|
||||
|
||||
procedure TMainForm.TimerTimer(Sender: TObject);
|
||||
var
|
||||
WrapperPath, INIPath: String;
|
||||
FV: FILE_VERSION;
|
||||
L: TStringList;
|
||||
CheckSupp: Boolean;
|
||||
begin
|
||||
CheckSupp := False;
|
||||
case IsWrapperInstalled(WrapperPath) of
|
||||
-1: begin
|
||||
lsWrapper.Caption := 'Unknown';
|
||||
lsWrapper.Font.Color := clGrayText;
|
||||
end;
|
||||
0: begin
|
||||
lsWrapper.Caption := 'Not installed';
|
||||
lsWrapper.Font.Color := clGrayText;
|
||||
end;
|
||||
1: begin
|
||||
lsWrapper.Caption := 'Installed';
|
||||
lsWrapper.Font.Color := clGreen;
|
||||
CheckSupp := True;
|
||||
INIPath := ExtractFilePath(ExpandPath(WrapperPath)) + 'rdpwrap.ini';
|
||||
if not FileExists(INIPath) then
|
||||
CheckSupp := False;
|
||||
end;
|
||||
2: begin
|
||||
lsWrapper.Caption := '3rd-party';
|
||||
lsWrapper.Font.Color := clRed;
|
||||
end;
|
||||
end;
|
||||
case GetTermSrvState of
|
||||
-1, 0: begin
|
||||
lsService.Caption := 'Unknown';
|
||||
lsService.Font.Color := clGrayText;
|
||||
end;
|
||||
SERVICE_STOPPED: begin
|
||||
lsService.Caption := 'Stopped';
|
||||
lsService.Font.Color := clRed;
|
||||
end;
|
||||
SERVICE_START_PENDING: begin
|
||||
lsService.Caption := 'Starting...';
|
||||
lsService.Font.Color := clGrayText;
|
||||
end;
|
||||
SERVICE_STOP_PENDING: begin
|
||||
lsService.Caption := 'Stopping...';
|
||||
lsService.Font.Color := clGrayText;
|
||||
end;
|
||||
SERVICE_RUNNING: begin
|
||||
lsService.Caption := 'Running';
|
||||
lsService.Font.Color := clGreen;
|
||||
end;
|
||||
SERVICE_CONTINUE_PENDING: begin
|
||||
lsService.Caption := 'Resuming...';
|
||||
lsService.Font.Color := clGrayText;
|
||||
end;
|
||||
SERVICE_PAUSE_PENDING: begin
|
||||
lsService.Caption := 'Suspending...';
|
||||
lsService.Font.Color := clGrayText;
|
||||
end;
|
||||
SERVICE_PAUSED: begin
|
||||
lsService.Caption := 'Suspended';
|
||||
lsService.Font.Color := clWindowText;
|
||||
end;
|
||||
end;
|
||||
if IsListenerWorking then begin
|
||||
lsListener.Caption := 'Listening';
|
||||
lsListener.Font.Color := clGreen;
|
||||
end else begin
|
||||
lsListener.Caption := 'Not listening';
|
||||
lsListener.Font.Color := clRed;
|
||||
end;
|
||||
if WrapperPath = '' then begin
|
||||
lsWrapVer.Caption := 'N/A';
|
||||
lsWrapVer.Font.Color := clGrayText;
|
||||
end else
|
||||
if not GetFileVersion(ExpandPath(WrapperPath), FV) then begin
|
||||
lsWrapVer.Caption := 'N/A';
|
||||
lsWrapVer.Font.Color := clGrayText;
|
||||
end else begin
|
||||
lsWrapVer.Caption :=
|
||||
IntToStr(FV.Version.w.Major)+'.'+
|
||||
IntToStr(FV.Version.w.Minor)+'.'+
|
||||
IntToStr(FV.Release)+'.'+
|
||||
IntToStr(FV.Build);
|
||||
lsWrapVer.Font.Color := clWindowText;
|
||||
end;
|
||||
if not GetFileVersion('termsrv.dll', FV) then begin
|
||||
lsTSVer.Caption := 'N/A';
|
||||
lsTSVer.Font.Color := clGrayText;
|
||||
end else begin
|
||||
lsTSVer.Caption :=
|
||||
IntToStr(FV.Version.w.Major)+'.'+
|
||||
IntToStr(FV.Version.w.Minor)+'.'+
|
||||
IntToStr(FV.Release)+'.'+
|
||||
IntToStr(FV.Build);
|
||||
lsTSVer.Font.Color := clWindowText;
|
||||
lsSuppVer.Visible := CheckSupp;
|
||||
if CheckSupp then begin
|
||||
if INI = '' then begin
|
||||
L := TStringList.Create;
|
||||
try
|
||||
L.LoadFromFile(INIPath);
|
||||
except
|
||||
|
||||
end;
|
||||
INI := L.Text;
|
||||
L.Free;
|
||||
end;
|
||||
case CheckSupport(FV) of
|
||||
0: begin
|
||||
lsSuppVer.Caption := '[not supported]';
|
||||
lsSuppVer.Font.Color := clRed;
|
||||
end;
|
||||
1: begin
|
||||
lsSuppVer.Caption := '[supported partially]';
|
||||
lsSuppVer.Font.Color := clOlive;
|
||||
end;
|
||||
2: begin
|
||||
lsSuppVer.Caption := '[fully supported]';
|
||||
lsSuppVer.Font.Color := clGreen;
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TMainForm.bLicenseClick(Sender: TObject);
|
||||
begin
|
||||
LicenseForm.mText.Text := ExtractResText('LICENSE');
|
||||
if LicenseForm.ShowModal <> mrOk then
|
||||
Halt(0);
|
||||
end;
|
||||
|
||||
procedure TMainForm.cbAllowTSConnectionsClick(Sender: TObject);
|
||||
begin
|
||||
if Ready then
|
||||
bApply.Enabled := True;
|
||||
end;
|
||||
|
||||
procedure TMainForm.seRDPPortChange(Sender: TObject);
|
||||
begin
|
||||
if Ready then
|
||||
bApply.Enabled := True;
|
||||
end;
|
||||
|
||||
procedure TMainForm.FormCreate(Sender: TObject);
|
||||
var
|
||||
SI: TSystemInfo;
|
||||
begin
|
||||
GetNativeSystemInfo(SI);
|
||||
case SI.wProcessorArchitecture of
|
||||
0: Arch := 32;
|
||||
6: Arch := 64; // Itanium-based x64
|
||||
9: Arch := 64; // Intel/AMD x64
|
||||
else Arch := 0;
|
||||
end;
|
||||
if Arch = 64 then
|
||||
DisableWowRedirection;
|
||||
ReadSettings;
|
||||
Ready := True;
|
||||
end;
|
||||
|
||||
procedure TMainForm.FormDestroy(Sender: TObject);
|
||||
begin
|
||||
if Arch = 64 then
|
||||
RevertWowRedirection;
|
||||
end;
|
||||
|
||||
procedure TMainForm.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
|
||||
begin
|
||||
if bApply.Enabled then
|
||||
CanClose := MessageBox(Handle, 'Settings are not saved. Do you want to exit?',
|
||||
'Warning', mb_IconWarning or mb_YesNo) = mrYes;
|
||||
end;
|
||||
|
||||
procedure TMainForm.bOKClick(Sender: TObject);
|
||||
begin
|
||||
if bApply.Enabled then begin
|
||||
WriteSettings;
|
||||
bApply.Enabled := False;
|
||||
end;
|
||||
Close;
|
||||
end;
|
||||
|
||||
procedure TMainForm.bCancelClick(Sender: TObject);
|
||||
begin
|
||||
Close;
|
||||
end;
|
||||
|
||||
procedure TMainForm.bApplyClick(Sender: TObject);
|
||||
begin
|
||||
WriteSettings;
|
||||
bApply.Enabled := False;
|
||||
end;
|
||||
|
||||
end.
|
||||
@ -1,33 +0,0 @@
|
||||
{
|
||||
Copyright 2014 Stas'M Corp.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
}
|
||||
|
||||
program RDPConf;
|
||||
|
||||
uses
|
||||
Forms,
|
||||
MainUnit in 'MainUnit.pas' {MainForm},
|
||||
LicenseUnit in 'LicenseUnit.pas' {LicenseForm};
|
||||
|
||||
{$R *.res}
|
||||
|
||||
begin
|
||||
Application.Initialize;
|
||||
Application.MainFormOnTaskbar := True;
|
||||
Application.Title := 'Remote Desktop Protocol Configuration';
|
||||
Application.CreateForm(TMainForm, MainForm);
|
||||
Application.CreateForm(TLicenseForm, LicenseForm);
|
||||
Application.Run;
|
||||
end.
|
||||
@ -1,111 +0,0 @@
|
||||
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<ProjectGuid>{A7CB4C30-85F5-4D96-B510-6F0CDCF7C2DA}</ProjectGuid>
|
||||
<ProjectVersion>12.0</ProjectVersion>
|
||||
<MainSource>RDPConf.dpr</MainSource>
|
||||
<Config Condition="'$(Config)'==''">Debug</Config>
|
||||
<DCC_DCCCompiler>DCC32</DCC_DCCCompiler>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Config)'=='Base' or '$(Base)'!=''">
|
||||
<Base>true</Base>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Config)'=='Release' or '$(Cfg_1)'!=''">
|
||||
<Cfg_1>true</Cfg_1>
|
||||
<CfgParent>Base</CfgParent>
|
||||
<Base>true</Base>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Config)'=='Debug' or '$(Cfg_2)'!=''">
|
||||
<Cfg_2>true</Cfg_2>
|
||||
<CfgParent>Base</CfgParent>
|
||||
<Base>true</Base>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Base)'!=''">
|
||||
<DCC_ExeOutput>..\bin\</DCC_ExeOutput>
|
||||
<DCC_UnitAlias>WinTypes=Windows;WinProcs=Windows;DbiTypes=BDE;DbiProcs=BDE;DbiErrs=BDE;$(DCC_UnitAlias)</DCC_UnitAlias>
|
||||
<DCC_DependencyCheckOutputName>..\bin\RDPConf.exe</DCC_DependencyCheckOutputName>
|
||||
<DCC_ImageBase>00400000</DCC_ImageBase>
|
||||
<DCC_Platform>x86</DCC_Platform>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Cfg_1)'!=''">
|
||||
<DCC_LocalDebugSymbols>false</DCC_LocalDebugSymbols>
|
||||
<DCC_Define>RELEASE;$(DCC_Define)</DCC_Define>
|
||||
<DCC_SymbolReferenceInfo>0</DCC_SymbolReferenceInfo>
|
||||
<DCC_DebugInformation>false</DCC_DebugInformation>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Cfg_2)'!=''">
|
||||
<DCC_Define>DEBUG;$(DCC_Define)</DCC_Define>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<DelphiCompile Include="RDPConf.dpr">
|
||||
<MainSource>MainSource</MainSource>
|
||||
</DelphiCompile>
|
||||
<DCCReference Include="MainUnit.pas">
|
||||
<Form>MainForm</Form>
|
||||
</DCCReference>
|
||||
<DCCReference Include="LicenseUnit.pas">
|
||||
<Form>LicenseForm</Form>
|
||||
</DCCReference>
|
||||
<BuildConfiguration Include="Base">
|
||||
<Key>Base</Key>
|
||||
</BuildConfiguration>
|
||||
<BuildConfiguration Include="Release">
|
||||
<Key>Cfg_1</Key>
|
||||
<CfgParent>Base</CfgParent>
|
||||
</BuildConfiguration>
|
||||
<BuildConfiguration Include="Debug">
|
||||
<Key>Cfg_2</Key>
|
||||
<CfgParent>Base</CfgParent>
|
||||
</BuildConfiguration>
|
||||
</ItemGroup>
|
||||
<Import Project="$(BDS)\Bin\CodeGear.Delphi.Targets" Condition="Exists('$(BDS)\Bin\CodeGear.Delphi.Targets')"/>
|
||||
<ProjectExtensions>
|
||||
<Borland.Personality>Delphi.Personality.12</Borland.Personality>
|
||||
<Borland.ProjectType/>
|
||||
<BorlandProject>
|
||||
<Delphi.Personality>
|
||||
<Source>
|
||||
<Source Name="MainSource">RDPConf.dpr</Source>
|
||||
</Source>
|
||||
<Parameters>
|
||||
<Parameters Name="UseLauncher">False</Parameters>
|
||||
<Parameters Name="LoadAllSymbols">True</Parameters>
|
||||
<Parameters Name="LoadUnspecifiedSymbols">False</Parameters>
|
||||
</Parameters>
|
||||
<VersionInfo>
|
||||
<VersionInfo Name="IncludeVerInfo">False</VersionInfo>
|
||||
<VersionInfo Name="AutoIncBuild">False</VersionInfo>
|
||||
<VersionInfo Name="MajorVer">1</VersionInfo>
|
||||
<VersionInfo Name="MinorVer">0</VersionInfo>
|
||||
<VersionInfo Name="Release">0</VersionInfo>
|
||||
<VersionInfo Name="Build">0</VersionInfo>
|
||||
<VersionInfo Name="Debug">False</VersionInfo>
|
||||
<VersionInfo Name="PreRelease">False</VersionInfo>
|
||||
<VersionInfo Name="Special">False</VersionInfo>
|
||||
<VersionInfo Name="Private">False</VersionInfo>
|
||||
<VersionInfo Name="DLL">False</VersionInfo>
|
||||
<VersionInfo Name="Locale">1033</VersionInfo>
|
||||
<VersionInfo Name="CodePage">1252</VersionInfo>
|
||||
</VersionInfo>
|
||||
<VersionInfoKeys>
|
||||
<VersionInfoKeys Name="CompanyName">Stas'M Corp.</VersionInfoKeys>
|
||||
<VersionInfoKeys Name="FileDescription">RDP Configuration Program</VersionInfoKeys>
|
||||
<VersionInfoKeys Name="FileVersion">1.0.0.0</VersionInfoKeys>
|
||||
<VersionInfoKeys Name="InternalName">RDPConf</VersionInfoKeys>
|
||||
<VersionInfoKeys Name="LegalCopyright">Copyright © Stas'M Corp. 2014</VersionInfoKeys>
|
||||
<VersionInfoKeys Name="LegalTrademarks">Stas'M Corp.</VersionInfoKeys>
|
||||
<VersionInfoKeys Name="OriginalFilename">RDPConf.exe</VersionInfoKeys>
|
||||
<VersionInfoKeys Name="ProductName">RDP Host Support</VersionInfoKeys>
|
||||
<VersionInfoKeys Name="ProductVersion">1.4.0.0</VersionInfoKeys>
|
||||
<VersionInfoKeys Name="Comments">http://stascorp.com</VersionInfoKeys>
|
||||
</VersionInfoKeys>
|
||||
<Excluded_Packages>
|
||||
<Excluded_Packages Name="$(BDS)\bin\bcboffice2k140.bpl">Embarcadero C++Builder Office 2000 Servers Package</Excluded_Packages>
|
||||
<Excluded_Packages Name="$(BDS)\bin\bcbofficexp140.bpl">Embarcadero C++Builder Office XP Servers Package</Excluded_Packages>
|
||||
<Excluded_Packages Name="$(BDS)\bin\dcloffice2k140.bpl">Microsoft Office 2000 Sample Automation Server Wrapper Components</Excluded_Packages>
|
||||
<Excluded_Packages Name="$(BDS)\bin\dclofficexp140.bpl">Microsoft Office XP Sample Automation Server Wrapper Components</Excluded_Packages>
|
||||
</Excluded_Packages>
|
||||
</Delphi.Personality>
|
||||
</BorlandProject>
|
||||
<ProjectFileVersion>12</ProjectFileVersion>
|
||||
</ProjectExtensions>
|
||||
</Project>
|
||||
@ -1,375 +0,0 @@
|
||||
{
|
||||
Copyright 2014 Stas'M Corp.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
}
|
||||
|
||||
unit LiteINI;
|
||||
|
||||
interface
|
||||
|
||||
uses
|
||||
SysUtils;
|
||||
|
||||
type
|
||||
SList = Array of String;
|
||||
INIValue = record
|
||||
Name: String;
|
||||
Value: String;
|
||||
end;
|
||||
INISection = record
|
||||
Name: String;
|
||||
Values: Array of INIValue;
|
||||
end;
|
||||
INIFile = Array of INISection;
|
||||
|
||||
procedure SListClear(var List: SList);
|
||||
function SListAppend(var List: SList; S: String): Integer;
|
||||
function SListFind(List: SList; Value: String): Integer;
|
||||
function INIFindSection(INI: INIFile; Section: String): Integer;
|
||||
function INIFindValue(INI: INIFile; Section: Integer; Value: String): Integer;
|
||||
function INIAddSection(var INI: INIFile; Section: String): Integer;
|
||||
function INIAddValue(var INI: INIFile; Section: Integer; ValueName, Value: String): Integer;
|
||||
procedure INIUnload(var INI: INIFile);
|
||||
procedure INILoad(var INI: INIFile; FileName: String);
|
||||
function INISectionExists(INI: INIFile; Section: String): Boolean;
|
||||
function INIValueExists(INI: INIFile; Section: String; Value: String): Boolean;
|
||||
function INIReadSectionLowAPI(INI: INIFile; Section: Integer; var List: SList): Boolean;
|
||||
function INIReadSection(INI: INIFile; Section: String): SList;
|
||||
function INIReadStringLowAPI(INI: INIFile; Section, Value: Integer; var Str: String): Boolean;
|
||||
function INIReadString(INI: INIFile; Section, Value, Default: String): String;
|
||||
function INIReadInt(INI: INIFile; Section, Value: String; Default: Integer): Integer;
|
||||
function INIReadDWord(INI: INIFile; Section, Value: String; Default: Cardinal): Cardinal;
|
||||
function INIReadIntHex(INI: INIFile; Section, Value: String; Default: Integer): Integer;
|
||||
function INIReadDWordHex(INI: INIFile; Section, Value: String; Default: Cardinal): Cardinal;
|
||||
function INIReadBool(INI: INIFile; Section, Value: String; Default: Boolean): Boolean;
|
||||
function INIReadBytes(INI: INIFile; Section, Value: String): TBytes;
|
||||
function INIReadBytesDef(INI: INIFile; Section, Value: String; Default: TBytes): TBytes;
|
||||
|
||||
implementation
|
||||
|
||||
procedure SListClear(var List: SList);
|
||||
begin
|
||||
SetLength(List, 0);
|
||||
end;
|
||||
|
||||
function SListAppend(var List: SList; S: String): Integer;
|
||||
begin
|
||||
SetLength(List, Length(List) + 1);
|
||||
List[Length(List) - 1] := S;
|
||||
Result := Length(List) - 1;
|
||||
end;
|
||||
|
||||
function SListFind(List: SList; Value: String): Integer;
|
||||
var
|
||||
I: Integer;
|
||||
begin
|
||||
Result := -1;
|
||||
for I := 0 to Length(List) - 1 do
|
||||
if List[I] = Value then begin
|
||||
Result := I;
|
||||
Break;
|
||||
end;
|
||||
end;
|
||||
|
||||
function INIFindSection(INI: INIFile; Section: String): Integer;
|
||||
var
|
||||
I: Integer;
|
||||
begin
|
||||
Result := -1;
|
||||
for I := 0 to Length(INI) - 1 do
|
||||
if INI[I].Name = Section then begin
|
||||
Result := I;
|
||||
Exit;
|
||||
end;
|
||||
end;
|
||||
|
||||
function INIFindValue(INI: INIFile; Section: Integer; Value: String): Integer;
|
||||
var
|
||||
I: Integer;
|
||||
begin
|
||||
Result := -1;
|
||||
if (Section < 0) or (Section >= Length(INI)) then
|
||||
Exit;
|
||||
for I := 0 to Length(INI[Section].Values) - 1 do
|
||||
if INI[Section].Values[I].Name = Value then begin
|
||||
Result := I;
|
||||
Exit;
|
||||
end;
|
||||
end;
|
||||
|
||||
function INIAddSection(var INI: INIFile; Section: String): Integer;
|
||||
begin
|
||||
Result := INIFindSection(INI, Section);
|
||||
if Result >= 0 then
|
||||
Exit;
|
||||
Result := Length(INI);
|
||||
SetLength(INI, Result + 1);
|
||||
INI[Result].Name := Section;
|
||||
SetLength(INI[Result].Values, 0);
|
||||
end;
|
||||
|
||||
function INIAddValue(var INI: INIFile; Section: Integer; ValueName, Value: String): Integer;
|
||||
var
|
||||
I: Integer;
|
||||
begin
|
||||
Result := -1;
|
||||
if (Section < 0) or (Section >= Length(INI)) then
|
||||
Exit;
|
||||
I := INIFindValue(INI, Section, ValueName);
|
||||
if I = -1 then begin
|
||||
Result := Length(INI[Section].Values);
|
||||
SetLength(INI[Section].Values, Result + 1);
|
||||
INI[Section].Values[Result].Name := ValueName;
|
||||
INI[Section].Values[Result].Value := Value;
|
||||
end else begin
|
||||
INI[Section].Values[I].Value := Value;
|
||||
Result := I;
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure INIUnload(var INI: INIFile);
|
||||
begin
|
||||
SetLength(INI, 0);
|
||||
end;
|
||||
|
||||
procedure INILoad(var INI: INIFile; FileName: String);
|
||||
var
|
||||
F: TextFile;
|
||||
S, ValueName, Value: String;
|
||||
INIList: SList;
|
||||
I, Sect: Integer;
|
||||
begin
|
||||
INIUnload(INI);
|
||||
if not FileExists(FileName) then
|
||||
Exit;
|
||||
AssignFile(F, FileName);
|
||||
Reset(F);
|
||||
// Read and filter lines
|
||||
while not EOF(F) do begin
|
||||
Readln(F, S);
|
||||
if (Pos(';', S) <> 1)
|
||||
and (Pos('#', S) <> 1)
|
||||
and (
|
||||
((Pos('[', S) > 0) and (Pos(']', S) > 0)) or
|
||||
(Pos('=', S) > 0)
|
||||
)
|
||||
then
|
||||
SListAppend(INIList, S);
|
||||
end;
|
||||
CloseFile(F);
|
||||
// Parse 2 (parse format)
|
||||
Sect := -1;
|
||||
for I := 0 to Length(INIList) - 1 do begin
|
||||
S := Trim(INIList[I]);
|
||||
if Length(S) >= 2 then
|
||||
if (S[1] = '[') and (S[Length(S)] = ']') then begin
|
||||
S := Trim(Copy(S, 2, Length(S) - 2));
|
||||
Sect := INIAddSection(INI, S);
|
||||
Continue;
|
||||
end;
|
||||
S := INIList[I];
|
||||
if Pos('=', S) > 0 then begin
|
||||
ValueName := Trim(Copy(S, 1, Pos('=', S) - 1));
|
||||
Value := Copy(S, Pos('=', S) + 1, Length(S) - Pos('=', S));
|
||||
if Sect = -1 then
|
||||
Sect := INIAddSection(INI, '');
|
||||
INIAddValue(INI, Sect, ValueName, Value);
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
|
||||
function INISectionExists(INI: INIFile; Section: String): Boolean;
|
||||
begin
|
||||
Result := INIFindSection(INI, Section) > -1;
|
||||
end;
|
||||
|
||||
function INIValueExists(INI: INIFile; Section: String; Value: String): Boolean;
|
||||
var
|
||||
Sect: Integer;
|
||||
begin
|
||||
Sect := INIFindSection(INI, Section);
|
||||
Result := INIFindValue(INI, Sect, Value) > -1;
|
||||
end;
|
||||
|
||||
function INIReadSectionLowAPI(INI: INIFile; Section: Integer; var List: SList): Boolean;
|
||||
var
|
||||
I: Integer;
|
||||
begin
|
||||
Result := False;
|
||||
SetLength(List, 0);
|
||||
if (Section < 0) or (Section >= Length(INI)) then
|
||||
Exit;
|
||||
for I := 0 to Length(INI[Section].Values) - 1 do
|
||||
SListAppend(List, INI[Section].Values[I].Name);
|
||||
Result := True;
|
||||
end;
|
||||
|
||||
function INIReadSection(INI: INIFile; Section: String): SList;
|
||||
var
|
||||
Sect: Integer;
|
||||
begin
|
||||
Sect := INIFindSection(INI, Section);
|
||||
INIReadSectionLowAPI(INI, Sect, Result);
|
||||
end;
|
||||
|
||||
function INIReadStringLowAPI(INI: INIFile; Section, Value: Integer; var Str: String): Boolean;
|
||||
begin
|
||||
Result := False;
|
||||
if (Section < 0) or (Section >= Length(INI)) then
|
||||
Exit;
|
||||
if (Value < 0) or (Value >= Length(INI[Section].Values)) then
|
||||
Exit;
|
||||
Str := INI[Section].Values[Value].Value;
|
||||
Result := True;
|
||||
end;
|
||||
|
||||
function INIReadString(INI: INIFile; Section, Value, Default: String): String;
|
||||
var
|
||||
Sect, Val: Integer;
|
||||
begin
|
||||
Sect := INIFindSection(INI, Section);
|
||||
Val := INIFindValue(INI, Sect, Value);
|
||||
if not INIReadStringLowAPI(INI, Sect, Val, Result) then
|
||||
Result := Default;
|
||||
end;
|
||||
|
||||
function INIReadInt(INI: INIFile; Section, Value: String; Default: Integer): Integer;
|
||||
var
|
||||
S: String;
|
||||
E: Integer;
|
||||
begin
|
||||
S := INIReadString(INI, Section, Value, '');
|
||||
Val(S, Result, E);
|
||||
if E <> 0 then
|
||||
Result := Default;
|
||||
end;
|
||||
|
||||
function INIReadDWord(INI: INIFile; Section, Value: String; Default: Cardinal): Cardinal;
|
||||
var
|
||||
S: String;
|
||||
E: Integer;
|
||||
begin
|
||||
S := INIReadString(INI, Section, Value, '');
|
||||
Val(S, Result, E);
|
||||
if E <> 0 then
|
||||
Result := Default;
|
||||
end;
|
||||
|
||||
function INIReadIntHex(INI: INIFile; Section, Value: String; Default: Integer): Integer;
|
||||
var
|
||||
S: String;
|
||||
E: Integer;
|
||||
begin
|
||||
S := INIReadString(INI, Section, Value, '');
|
||||
Val('$'+S, Result, E);
|
||||
if E <> 0 then
|
||||
Result := Default;
|
||||
end;
|
||||
|
||||
function INIReadDWordHex(INI: INIFile; Section, Value: String; Default: Cardinal): Cardinal;
|
||||
var
|
||||
S: String;
|
||||
E: Integer;
|
||||
begin
|
||||
S := INIReadString(INI, Section, Value, '');
|
||||
Val('$'+S, Result, E);
|
||||
if E <> 0 then
|
||||
Result := Default;
|
||||
end;
|
||||
|
||||
function INIReadBool(INI: INIFile; Section, Value: String; Default: Boolean): Boolean;
|
||||
var
|
||||
S: String;
|
||||
I: Cardinal;
|
||||
E: Integer;
|
||||
begin
|
||||
S := INIReadString(INI, Section, Value, '');
|
||||
Val(S, I, E);
|
||||
if E <> 0 then
|
||||
Result := Default
|
||||
else
|
||||
Result := I > 0;
|
||||
end;
|
||||
|
||||
function StringToBytes(S: String; var B: TBytes): Boolean;
|
||||
var
|
||||
I: Integer;
|
||||
begin
|
||||
Result := False;
|
||||
if Odd(Length(S)) then
|
||||
Exit;
|
||||
SetLength(B, Length(S) div 2);
|
||||
for I := 0 to Length(B) - 1 do begin
|
||||
B[I] := 0;
|
||||
case S[(I*2)+2] of
|
||||
'0': ;
|
||||
'1': B[I] := B[I] or $1;
|
||||
'2': B[I] := B[I] or $2;
|
||||
'3': B[I] := B[I] or $3;
|
||||
'4': B[I] := B[I] or $4;
|
||||
'5': B[I] := B[I] or $5;
|
||||
'6': B[I] := B[I] or $6;
|
||||
'7': B[I] := B[I] or $7;
|
||||
'8': B[I] := B[I] or $8;
|
||||
'9': B[I] := B[I] or $9;
|
||||
'A','a': B[I] := B[I] or $A;
|
||||
'B','b': B[I] := B[I] or $B;
|
||||
'C','c': B[I] := B[I] or $C;
|
||||
'D','d': B[I] := B[I] or $D;
|
||||
'E','e': B[I] := B[I] or $E;
|
||||
'F','f': B[I] := B[I] or $F;
|
||||
else Exit;
|
||||
end;
|
||||
case S[(I*2)+1] of
|
||||
'0': ;
|
||||
'1': B[I] := B[I] or $10;
|
||||
'2': B[I] := B[I] or $20;
|
||||
'3': B[I] := B[I] or $30;
|
||||
'4': B[I] := B[I] or $40;
|
||||
'5': B[I] := B[I] or $50;
|
||||
'6': B[I] := B[I] or $60;
|
||||
'7': B[I] := B[I] or $70;
|
||||
'8': B[I] := B[I] or $80;
|
||||
'9': B[I] := B[I] or $90;
|
||||
'A','a': B[I] := B[I] or $A0;
|
||||
'B','b': B[I] := B[I] or $B0;
|
||||
'C','c': B[I] := B[I] or $C0;
|
||||
'D','d': B[I] := B[I] or $D0;
|
||||
'E','e': B[I] := B[I] or $E0;
|
||||
'F','f': B[I] := B[I] or $F0;
|
||||
else Exit;
|
||||
end;
|
||||
end;
|
||||
Result := True;
|
||||
end;
|
||||
|
||||
function INIReadBytes(INI: INIFile; Section, Value: String): TBytes;
|
||||
var
|
||||
S: String;
|
||||
begin
|
||||
S := INIReadString(INI, Section, Value, '');
|
||||
if not StringToBytes(S, Result) then
|
||||
SetLength(Result, 0);
|
||||
end;
|
||||
|
||||
function INIReadBytesDef(INI: INIFile; Section, Value: String; Default: TBytes): TBytes;
|
||||
var
|
||||
S: String;
|
||||
begin
|
||||
S := INIReadString(INI, Section, Value, '');
|
||||
if not StringToBytes(S, Result) then
|
||||
Result := Default;
|
||||
end;
|
||||
|
||||
end.
|
||||
@ -1,737 +0,0 @@
|
||||
{
|
||||
Copyright 2014 Stas'M Corp.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
}
|
||||
|
||||
library rdpwrap;
|
||||
|
||||
uses
|
||||
SysUtils,
|
||||
Windows,
|
||||
TlHelp32,
|
||||
LiteINI;
|
||||
|
||||
{$R rdpwrap.res}
|
||||
|
||||
// Hook core definitions
|
||||
|
||||
type
|
||||
OldCode = packed record
|
||||
One: DWORD;
|
||||
two: Word;
|
||||
end;
|
||||
|
||||
far_jmp = packed record
|
||||
PushOp: Byte;
|
||||
PushArg: Pointer;
|
||||
RetOp: Byte;
|
||||
end;
|
||||
|
||||
mov_far_jmp = packed record
|
||||
MovOp: Byte;
|
||||
MovArg: Byte;
|
||||
PushOp: Byte;
|
||||
PushArg: Pointer;
|
||||
RetOp: Byte;
|
||||
end;
|
||||
|
||||
TTHREADENTRY32 = packed record
|
||||
dwSize: DWORD;
|
||||
cntUsage: DWORD;
|
||||
th32ThreadID: DWORD;
|
||||
th32OwnerProcessID: DWORD;
|
||||
tpBasePri: LongInt;
|
||||
tpDeltaPri: LongInt;
|
||||
dwFlags: DWORD;
|
||||
end;
|
||||
//IntArray = Array of Integer;
|
||||
FILE_VERSION = record
|
||||
Version: record case Boolean of
|
||||
True: (dw: DWORD);
|
||||
False: (w: record
|
||||
Minor, Major: Word;
|
||||
end;)
|
||||
end;
|
||||
Release, Build: Word;
|
||||
bDebug, bPrerelease, bPrivate, bSpecial: Boolean;
|
||||
end;
|
||||
|
||||
const
|
||||
THREAD_SUSPEND_RESUME = 2;
|
||||
TH32CS_SNAPTHREAD = 4;
|
||||
var
|
||||
INI: INIFile;
|
||||
LogFile: String = '\rdpwrap.txt';
|
||||
bw: {$if CompilerVersion>=16} NativeUInt {$else} DWORD {$endif};
|
||||
IsHooked: Boolean = False;
|
||||
|
||||
// Unhooked import
|
||||
|
||||
function OpenThread(dwDesiredAccess: DWORD; bInheritHandle: BOOL;
|
||||
dwThreadId: DWORD): DWORD; stdcall; external kernel32;
|
||||
|
||||
function CreateToolhelp32Snapshot(dwFlags, th32ProcessID: DWORD): DWORD;
|
||||
stdcall; external kernel32;
|
||||
|
||||
function Thread32First(hSnapshot: THandle; var lpte: TTHREADENTRY32): bool;
|
||||
stdcall; external kernel32;
|
||||
|
||||
function Thread32Next(hSnapshot: THandle; var lpte: TTHREADENTRY32): bool;
|
||||
stdcall; external kernel32;
|
||||
|
||||
// Wrapped import
|
||||
|
||||
var
|
||||
TSMain: function(dwArgc: DWORD; lpszArgv: PWideChar): DWORD; stdcall;
|
||||
TSGlobals: function(lpGlobalData: Pointer): DWORD; stdcall;
|
||||
|
||||
// Hooked import and vars
|
||||
|
||||
var
|
||||
SLGetWindowsInformationDWORD: function(pwszValueName: PWideChar;
|
||||
pdwValue: PDWORD): HRESULT; stdcall;
|
||||
TermSrvBase: Pointer;
|
||||
FV: FILE_VERSION;
|
||||
|
||||
var
|
||||
Stub_SLGetWindowsInformationDWORD: far_jmp;
|
||||
Old_SLGetWindowsInformationDWORD: OldCode;
|
||||
|
||||
// Main code
|
||||
|
||||
procedure WriteLog(S: AnsiString);
|
||||
var
|
||||
F: TextFile;
|
||||
begin
|
||||
if not FileExists(LogFile) then
|
||||
Exit;
|
||||
AssignFile(F, LogFile);
|
||||
Append(F);
|
||||
Write(F, S+#13#10);
|
||||
CloseFile(F);
|
||||
end;
|
||||
|
||||
function GetModuleHandleEx(dwFlags: DWORD; lpModuleName: PWideChar;
|
||||
var phModule: HMODULE): BOOL; stdcall; external kernel32 name 'GetModuleHandleExW';
|
||||
|
||||
function GetCurrentModule: HMODULE;
|
||||
const
|
||||
GET_MODULE_HANDLE_EX_FLAG_PIN = 1;
|
||||
GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT = 2;
|
||||
GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS = 4;
|
||||
begin
|
||||
Result := 0;
|
||||
GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, @GetCurrentModule, Result);
|
||||
end;
|
||||
|
||||
function GetBinaryPath: String;
|
||||
var
|
||||
Buf: Array[0..511] of Byte;
|
||||
begin
|
||||
ZeroMemory(@Buf[0], Length(Buf));
|
||||
GetModuleFileName(GetCurrentModule, PWideChar(@Buf[0]), Length(Buf));
|
||||
Result := PWideChar(@Buf[0]);
|
||||
end;
|
||||
|
||||
procedure StopThreads;
|
||||
var
|
||||
h, CurrTh, ThrHandle, CurrPr: DWORD;
|
||||
Thread: TTHREADENTRY32;
|
||||
begin
|
||||
CurrTh := GetCurrentThreadId;
|
||||
CurrPr := GetCurrentProcessId;
|
||||
h := CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
|
||||
if h <> INVALID_HANDLE_VALUE then
|
||||
begin
|
||||
Thread.dwSize := SizeOf(TTHREADENTRY32);
|
||||
if Thread32First(h, Thread) then
|
||||
repeat
|
||||
if (Thread.th32ThreadID <> CurrTh) and
|
||||
(Thread.th32OwnerProcessID = CurrPr) then
|
||||
begin
|
||||
ThrHandle := OpenThread(THREAD_SUSPEND_RESUME, false,
|
||||
Thread.th32ThreadID);
|
||||
if ThrHandle > 0 then
|
||||
begin
|
||||
SuspendThread(ThrHandle);
|
||||
CloseHandle(ThrHandle);
|
||||
end;
|
||||
end;
|
||||
until not Thread32Next(h, Thread);
|
||||
CloseHandle(h);
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure RunThreads;
|
||||
var
|
||||
h, CurrTh, ThrHandle, CurrPr: DWORD;
|
||||
Thread: TTHREADENTRY32;
|
||||
begin
|
||||
CurrTh := GetCurrentThreadId;
|
||||
CurrPr := GetCurrentProcessId;
|
||||
h := CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
|
||||
if h <> INVALID_HANDLE_VALUE then
|
||||
begin
|
||||
Thread.dwSize := SizeOf(TTHREADENTRY32);
|
||||
if Thread32First(h, Thread) then
|
||||
repeat
|
||||
if (Thread.th32ThreadID <> CurrTh) and
|
||||
(Thread.th32OwnerProcessID = CurrPr) then
|
||||
begin
|
||||
ThrHandle := OpenThread(THREAD_SUSPEND_RESUME, false,
|
||||
Thread.th32ThreadID);
|
||||
if ThrHandle > 0 then
|
||||
begin
|
||||
ResumeThread(ThrHandle);
|
||||
CloseHandle(ThrHandle);
|
||||
end;
|
||||
end;
|
||||
until not Thread32Next(h, Thread);
|
||||
CloseHandle(h);
|
||||
end;
|
||||
end;
|
||||
|
||||
function GetModuleAddress(ModuleName: String; ProcessId: DWORD; var BaseAddr: Pointer; var BaseSize: DWORD): Boolean;
|
||||
var
|
||||
hSnap: THandle;
|
||||
md: MODULEENTRY32;
|
||||
begin
|
||||
Result := False;
|
||||
hSnap := CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, ProcessId);
|
||||
if hSnap = INVALID_HANDLE_VALUE Then
|
||||
Exit;
|
||||
md.dwSize := SizeOf(MODULEENTRY32);
|
||||
if Module32First(hSnap, md) then
|
||||
begin
|
||||
if LowerCase(ExtractFileName(md.szExePath)) = LowerCase(ModuleName) then
|
||||
begin
|
||||
Result := True;
|
||||
BaseAddr := Pointer(md.modBaseAddr);
|
||||
BaseSize := md.modBaseSize;
|
||||
CloseHandle(hSnap);
|
||||
Exit;
|
||||
end;
|
||||
while Module32Next(hSnap, md) Do
|
||||
begin
|
||||
if LowerCase(ExtractFileName(md.szExePath)) = LowerCase(ModuleName) then
|
||||
begin
|
||||
Result := True;
|
||||
BaseAddr := Pointer(md.modBaseAddr);
|
||||
BaseSize := md.modBaseSize;
|
||||
Break;
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
CloseHandle(hSnap);
|
||||
end;
|
||||
|
||||
{procedure FindMem(Mem: Pointer; MemSz: DWORD; Buf: Pointer; BufSz: DWORD;
|
||||
From: DWORD; var A: IntArray);
|
||||
var
|
||||
I: Integer;
|
||||
begin
|
||||
SetLength(A, 0);
|
||||
I:=From;
|
||||
if From>0 then
|
||||
Inc(PByte(Mem), From);
|
||||
while I < MemSz - BufSz + 1 do
|
||||
begin
|
||||
if (not IsBadReadPtr(Mem, BufSz)) and (CompareMem(Mem, Buf, BufSz)) then
|
||||
begin
|
||||
SetLength(A, Length(A)+1);
|
||||
A[Length(A)-1] := I;
|
||||
end;
|
||||
Inc(I);
|
||||
Inc(PByte(Mem));
|
||||
end;
|
||||
end;}
|
||||
|
||||
function GetModuleVersion(const ModuleName: String; var FileVersion: FILE_VERSION): Boolean;
|
||||
type
|
||||
VS_VERSIONINFO = record
|
||||
wLength, wValueLength, wType: Word;
|
||||
szKey: Array[1..16] of WideChar;
|
||||
Padding1: Word;
|
||||
Value: VS_FIXEDFILEINFO;
|
||||
Padding2, Children: Word;
|
||||
end;
|
||||
PVS_VERSIONINFO = ^VS_VERSIONINFO;
|
||||
const
|
||||
VFF_DEBUG = 1;
|
||||
VFF_PRERELEASE = 2;
|
||||
VFF_PRIVATE = 8;
|
||||
VFF_SPECIAL = 32;
|
||||
var
|
||||
hMod: HMODULE;
|
||||
hResourceInfo: HRSRC;
|
||||
VersionInfo: PVS_VERSIONINFO;
|
||||
begin
|
||||
Result := False;
|
||||
|
||||
if ModuleName = '' then
|
||||
hMod := GetModuleHandle(nil)
|
||||
else
|
||||
hMod := GetModuleHandle(PWideChar(ModuleName));
|
||||
if hMod = 0 then
|
||||
Exit;
|
||||
|
||||
hResourceInfo := FindResource(hMod, PWideChar(1), PWideChar($10));
|
||||
if hResourceInfo = 0 then
|
||||
Exit;
|
||||
|
||||
VersionInfo := Pointer(LoadResource(hMod, hResourceInfo));
|
||||
if VersionInfo = nil then
|
||||
Exit;
|
||||
|
||||
FileVersion.Version.dw := VersionInfo.Value.dwFileVersionMS;
|
||||
FileVersion.Release := Word(VersionInfo.Value.dwFileVersionLS shr 16);
|
||||
FileVersion.Build := Word(VersionInfo.Value.dwFileVersionLS);
|
||||
FileVersion.bDebug := (VersionInfo.Value.dwFileFlags and VFF_DEBUG) = VFF_DEBUG;
|
||||
FileVersion.bPrerelease := (VersionInfo.Value.dwFileFlags and VFF_PRERELEASE) = VFF_PRERELEASE;
|
||||
FileVersion.bPrivate := (VersionInfo.Value.dwFileFlags and VFF_PRIVATE) = VFF_PRIVATE;
|
||||
FileVersion.bSpecial := (VersionInfo.Value.dwFileFlags and VFF_SPECIAL) = VFF_SPECIAL;
|
||||
|
||||
Result := True;
|
||||
end;
|
||||
|
||||
function GetFileVersion(const FileName: String; var FileVersion: FILE_VERSION): Boolean;
|
||||
type
|
||||
VS_VERSIONINFO = record
|
||||
wLength, wValueLength, wType: Word;
|
||||
szKey: Array[1..16] of WideChar;
|
||||
Padding1: Word;
|
||||
Value: VS_FIXEDFILEINFO;
|
||||
Padding2, Children: Word;
|
||||
end;
|
||||
PVS_VERSIONINFO = ^VS_VERSIONINFO;
|
||||
const
|
||||
VFF_DEBUG = 1;
|
||||
VFF_PRERELEASE = 2;
|
||||
VFF_PRIVATE = 8;
|
||||
VFF_SPECIAL = 32;
|
||||
var
|
||||
hFile: HMODULE;
|
||||
hResourceInfo: HRSRC;
|
||||
VersionInfo: PVS_VERSIONINFO;
|
||||
begin
|
||||
Result := False;
|
||||
|
||||
hFile := LoadLibraryEx(PWideChar(FileName), 0, LOAD_LIBRARY_AS_DATAFILE);
|
||||
if hFile = 0 then
|
||||
Exit;
|
||||
|
||||
hResourceInfo := FindResource(hFile, PWideChar(1), PWideChar($10));
|
||||
if hResourceInfo = 0 then
|
||||
Exit;
|
||||
|
||||
VersionInfo := Pointer(LoadResource(hFile, hResourceInfo));
|
||||
if VersionInfo = nil then
|
||||
Exit;
|
||||
|
||||
FileVersion.Version.dw := VersionInfo.Value.dwFileVersionMS;
|
||||
FileVersion.Release := Word(VersionInfo.Value.dwFileVersionLS shr 16);
|
||||
FileVersion.Build := Word(VersionInfo.Value.dwFileVersionLS);
|
||||
FileVersion.bDebug := (VersionInfo.Value.dwFileFlags and VFF_DEBUG) = VFF_DEBUG;
|
||||
FileVersion.bPrerelease := (VersionInfo.Value.dwFileFlags and VFF_PRERELEASE) = VFF_PRERELEASE;
|
||||
FileVersion.bPrivate := (VersionInfo.Value.dwFileFlags and VFF_PRIVATE) = VFF_PRIVATE;
|
||||
FileVersion.bSpecial := (VersionInfo.Value.dwFileFlags and VFF_SPECIAL) = VFF_SPECIAL;
|
||||
|
||||
Result := True;
|
||||
end;
|
||||
|
||||
function OverrideSL(ValueName: String; var Value: DWORD): Boolean;
|
||||
begin
|
||||
Result := True;
|
||||
if INIValueExists(INI, 'SLPolicy', ValueName) then begin
|
||||
Value := INIReadDWord(INI, 'SLPolicy', ValueName, 0);
|
||||
Exit;
|
||||
end;
|
||||
Result := False;
|
||||
end;
|
||||
|
||||
function New_SLGetWindowsInformationDWORD(pwszValueName: PWideChar;
|
||||
pdwValue: PDWORD): HRESULT; stdcall;
|
||||
var
|
||||
dw: DWORD;
|
||||
begin
|
||||
// wrapped SLGetWindowsInformationDWORD function
|
||||
// termsrv.dll will call this function instead of original SLC.dll
|
||||
|
||||
// Override SL Policy
|
||||
|
||||
WriteLog('Policy query: ' + pwszValueName);
|
||||
if OverrideSL(pwszValueName, dw) then begin
|
||||
pdwValue^ := dw;
|
||||
Result := S_OK;
|
||||
WriteLog('Policy rewrite: ' + IntToStr(pdwValue^));
|
||||
Exit;
|
||||
end;
|
||||
|
||||
// If the requested value name is not defined above
|
||||
|
||||
// revert to original SL Policy function
|
||||
WriteProcessMemory(GetCurrentProcess, @SLGetWindowsInformationDWORD,
|
||||
@Old_SLGetWindowsInformationDWORD, SizeOf(OldCode), bw);
|
||||
|
||||
// get result
|
||||
Result := SLGetWindowsInformationDWORD(pwszValueName, pdwValue);
|
||||
if Result = S_OK then
|
||||
WriteLog('Policy result: ' + IntToStr(pdwValue^))
|
||||
else
|
||||
WriteLog('Policy request failed');
|
||||
// wrap it back
|
||||
WriteProcessMemory(GetCurrentProcess, @SLGetWindowsInformationDWORD,
|
||||
@Stub_SLGetWindowsInformationDWORD, SizeOf(far_jmp), bw);
|
||||
end;
|
||||
|
||||
function New_Win8SL(pwszValueName: PWideChar; pdwValue: PDWORD): HRESULT; register;
|
||||
var
|
||||
dw: DWORD;
|
||||
begin
|
||||
// wrapped unexported function SLGetWindowsInformationDWORDWrapper in termsrv.dll
|
||||
// for Windows 8 support
|
||||
|
||||
// Override SL Policy
|
||||
|
||||
WriteLog('Policy query: ' + pwszValueName);
|
||||
if OverrideSL(pwszValueName, dw) then begin
|
||||
pdwValue^ := dw;
|
||||
Result := S_OK;
|
||||
WriteLog('Policy rewrite: ' + IntToStr(pdwValue^));
|
||||
Exit;
|
||||
end;
|
||||
|
||||
// If the requested value name is not defined above
|
||||
// use function from SLC.dll
|
||||
|
||||
Result := SLGetWindowsInformationDWORD(pwszValueName, pdwValue);
|
||||
if Result = S_OK then
|
||||
WriteLog('Policy result: ' + IntToStr(pdwValue^))
|
||||
else
|
||||
WriteLog('Policy request failed');
|
||||
end;
|
||||
|
||||
function New_Win8SL_CP(eax: DWORD; pdwValue: PDWORD; ecx: DWORD; pwszValueName: PWideChar): HRESULT; register;
|
||||
begin
|
||||
// wrapped unexported function SLGetWindowsInformationDWORDWrapper in termsrv.dll
|
||||
// for Windows 8 Consumer Preview support
|
||||
|
||||
Result := New_Win8SL(pwszValueName, pdwValue);
|
||||
end;
|
||||
|
||||
function New_CSLQuery_Initialize: HRESULT; stdcall;
|
||||
var
|
||||
Sect: String;
|
||||
bServerSku,
|
||||
bRemoteConnAllowed,
|
||||
bFUSEnabled,
|
||||
bAppServerAllowed,
|
||||
bMultimonAllowed,
|
||||
lMaxUserSessions,
|
||||
ulMaxDebugSessions,
|
||||
bInitialized: PDWORD;
|
||||
begin
|
||||
bServerSku := nil;
|
||||
bRemoteConnAllowed := nil;
|
||||
bFUSEnabled := nil;
|
||||
bAppServerAllowed := nil;
|
||||
bMultimonAllowed := nil;
|
||||
lMaxUserSessions := nil;
|
||||
ulMaxDebugSessions := nil;
|
||||
bInitialized := nil;
|
||||
WriteLog('>>> CSLQuery::Initialize');
|
||||
Sect := IntToStr(FV.Version.w.Major)+'.'+IntToStr(FV.Version.w.Minor)+'.'+
|
||||
IntToStr(FV.Release)+'.'+IntToStr(FV.Build)+'-SLInit';
|
||||
if INISectionExists(INI, Sect) then begin
|
||||
bServerSku := Pointer(Cardinal(TermSrvBase) + INIReadDWordHex(INI, Sect, 'bServerSku.x86', 0));
|
||||
bRemoteConnAllowed := Pointer(Cardinal(TermSrvBase) + INIReadDWordHex(INI, Sect, 'bRemoteConnAllowed.x86', 0));
|
||||
bFUSEnabled := Pointer(Cardinal(TermSrvBase) + INIReadDWordHex(INI, Sect, 'bFUSEnabled.x86', 0));
|
||||
bAppServerAllowed := Pointer(Cardinal(TermSrvBase) + INIReadDWordHex(INI, Sect, 'bAppServerAllowed.x86', 0));
|
||||
bMultimonAllowed := Pointer(Cardinal(TermSrvBase) + INIReadDWordHex(INI, Sect, 'bMultimonAllowed.x86', 0));
|
||||
lMaxUserSessions := Pointer(Cardinal(TermSrvBase) + INIReadDWordHex(INI, Sect, 'lMaxUserSessions.x86', 0));
|
||||
ulMaxDebugSessions := Pointer(Cardinal(TermSrvBase) + INIReadDWordHex(INI, Sect, 'ulMaxDebugSessions.x86', 0));
|
||||
bInitialized := Pointer(Cardinal(TermSrvBase) + INIReadDWordHex(INI, Sect, 'bInitialized.x86', 0));
|
||||
end;
|
||||
|
||||
if bServerSku <> nil then begin
|
||||
bServerSku^ := INIReadDWord(INI, 'SLInit', 'bServerSku', 1);
|
||||
WriteLog('SLInit [0x'+IntToHex(DWORD(bServerSku), 1)+'] bServerSku = ' + IntToStr(bServerSku^));
|
||||
end;
|
||||
if bRemoteConnAllowed <> nil then begin
|
||||
bRemoteConnAllowed^ := INIReadDWord(INI, 'SLInit', 'bRemoteConnAllowed', 1);
|
||||
WriteLog('SLInit [0x'+IntToHex(DWORD(bRemoteConnAllowed), 1)+'] bRemoteConnAllowed = ' + IntToStr(bRemoteConnAllowed^));
|
||||
end;
|
||||
if bFUSEnabled <> nil then begin
|
||||
bFUSEnabled^ := INIReadDWord(INI, 'SLInit', 'bFUSEnabled', 1);
|
||||
WriteLog('SLInit [0x'+IntToHex(DWORD(bFUSEnabled), 1)+'] bFUSEnabled = ' + IntToStr(bFUSEnabled^));
|
||||
end;
|
||||
if bAppServerAllowed <> nil then begin
|
||||
bAppServerAllowed^ := INIReadDWord(INI, 'SLInit', 'bAppServerAllowed', 1);
|
||||
WriteLog('SLInit [0x'+IntToHex(DWORD(bAppServerAllowed), 1)+'] bAppServerAllowed = ' + IntToStr(bAppServerAllowed^));
|
||||
end;
|
||||
if bMultimonAllowed <> nil then begin
|
||||
bMultimonAllowed^ := INIReadDWord(INI, 'SLInit', 'bMultimonAllowed', 1);
|
||||
WriteLog('SLInit [0x'+IntToHex(DWORD(bMultimonAllowed), 1)+'] bMultimonAllowed = ' + IntToStr(bMultimonAllowed^));
|
||||
end;
|
||||
if lMaxUserSessions <> nil then begin
|
||||
lMaxUserSessions^ := INIReadDWord(INI, 'SLInit', 'lMaxUserSessions', 0);
|
||||
WriteLog('SLInit [0x'+IntToHex(DWORD(lMaxUserSessions), 1)+'] lMaxUserSessions = ' + IntToStr(lMaxUserSessions^));
|
||||
end;
|
||||
if ulMaxDebugSessions <> nil then begin
|
||||
ulMaxDebugSessions^ := INIReadDWord(INI, 'SLInit', 'ulMaxDebugSessions', 0);
|
||||
WriteLog('SLInit [0x'+IntToHex(DWORD(ulMaxDebugSessions), 1)+'] ulMaxDebugSessions = ' + IntToStr(ulMaxDebugSessions^));
|
||||
end;
|
||||
if bInitialized <> nil then begin
|
||||
bInitialized^ := INIReadDWord(INI, 'SLInit', 'bInitialized', 1);
|
||||
WriteLog('SLInit [0x'+IntToHex(DWORD(bInitialized), 1)+'] bInitialized = ' + IntToStr(bInitialized^));
|
||||
end;
|
||||
Result := S_OK;
|
||||
WriteLog('<<< CSLQuery::Initialize');
|
||||
end;
|
||||
|
||||
procedure HookFunctions;
|
||||
var
|
||||
ConfigFile, Sect, FuncName: String;
|
||||
V: DWORD;
|
||||
TS_Handle, SLC_Handle: THandle;
|
||||
TermSrvSize: DWORD;
|
||||
SignPtr: Pointer;
|
||||
I: Integer;
|
||||
PatchList: SList;
|
||||
Patch: Array of TBytes;
|
||||
Jump: far_jmp;
|
||||
MovJump: mov_far_jmp;
|
||||
begin
|
||||
{ hook function ^^
|
||||
(called once) }
|
||||
IsHooked := True;
|
||||
TSMain := nil;
|
||||
TSGlobals := nil;
|
||||
SLGetWindowsInformationDWORD := nil;
|
||||
|
||||
WriteLog('Loading configuration...');
|
||||
ConfigFile := ExtractFilePath(GetBinaryPath) + 'rdpwrap.ini';
|
||||
WriteLog('Configuration file: ' + ConfigFile);
|
||||
INILoad(INI, ConfigFile);
|
||||
if Length(INI) = 0 then begin
|
||||
WriteLog('Error: Failed to load configuration');
|
||||
Exit;
|
||||
end;
|
||||
|
||||
LogFile := INIReadString(INI, 'Main', 'LogFile', ExtractFilePath(GetBinaryPath) + 'rdpwrap.txt');
|
||||
WriteLog('Initializing RDP Wrapper...');
|
||||
|
||||
// load termsrv.dll and get functions
|
||||
TS_Handle := LoadLibrary('termsrv.dll');
|
||||
if TS_Handle = 0 then begin
|
||||
WriteLog('Error: Failed to load Terminal Services library');
|
||||
Exit;
|
||||
end;
|
||||
TSMain := GetProcAddress(TS_Handle, 'ServiceMain');
|
||||
TSGlobals := GetProcAddress(TS_Handle, 'SvchostPushServiceGlobals');
|
||||
WriteLog(
|
||||
'Base addr: 0x' + IntToHex(TS_Handle, 8) + #13#10 +
|
||||
'SvcMain: termsrv.dll+0x' + IntToHex(Cardinal(@TSMain) - TS_Handle, 1) + #13#10 +
|
||||
'SvcGlobals: termsrv.dll+0x' + IntToHex(Cardinal(@TSGlobals) - TS_Handle, 1)
|
||||
);
|
||||
|
||||
V := 0;
|
||||
// check termsrv version
|
||||
if GetModuleVersion('termsrv.dll', FV) then
|
||||
V := Byte(FV.Version.w.Minor) or (Byte(FV.Version.w.Major) shl 8)
|
||||
else begin
|
||||
// check NT version
|
||||
// V := GetVersion; // deprecated
|
||||
// V := ((V and $FF) shl 8) or ((V and $FF00) shr 8);
|
||||
end;
|
||||
if V = 0 then begin
|
||||
WriteLog('Error: Failed to detect Terminal Services version');
|
||||
Exit;
|
||||
end;
|
||||
|
||||
WriteLog('Version: '+
|
||||
IntToStr(FV.Version.w.Major)+'.'+
|
||||
IntToStr(FV.Version.w.Minor)+'.'+
|
||||
IntToStr(FV.Release)+'.'+
|
||||
IntToStr(FV.Build));
|
||||
|
||||
// temporarily freeze threads
|
||||
WriteLog('Freezing threads...');
|
||||
StopThreads();
|
||||
|
||||
WriteLog('Caching patch codes...');
|
||||
PatchList := INIReadSection(INI, 'PatchCodes');
|
||||
SetLength(Patch, Length(PatchList));
|
||||
for I := 0 to Length(Patch) - 1 do begin
|
||||
Patch[I] := INIReadBytes(INI, 'PatchCodes', PatchList[I]);
|
||||
if Length(Patch[I]) > 16 then // for security reasons
|
||||
SetLength(Patch[I], 16); // not more than 16 bytes
|
||||
end;
|
||||
|
||||
if (V = $0600) and (INIReadBool(INI, 'Main', 'SLPolicyHookNT60', True)) then begin
|
||||
// Windows Vista
|
||||
// uses SL Policy API (slc.dll)
|
||||
|
||||
// load slc.dll and hook function
|
||||
SLC_Handle := LoadLibrary('slc.dll');
|
||||
SLGetWindowsInformationDWORD := GetProcAddress(SLC_Handle, 'SLGetWindowsInformationDWORD');
|
||||
|
||||
if @SLGetWindowsInformationDWORD <> nil then
|
||||
begin
|
||||
// rewrite original function to call our function (make hook)
|
||||
|
||||
WriteLog('Hook SLGetWindowsInformationDWORD');
|
||||
Stub_SLGetWindowsInformationDWORD.PushOp := $68;
|
||||
Stub_SLGetWindowsInformationDWORD.PushArg := @New_SLGetWindowsInformationDWORD;
|
||||
Stub_SLGetWindowsInformationDWORD.RetOp := $C3;
|
||||
ReadProcessMemory(GetCurrentProcess, @SLGetWindowsInformationDWORD,
|
||||
@Old_SLGetWindowsInformationDWORD, SizeOf(OldCode), bw);
|
||||
WriteProcessMemory(GetCurrentProcess, @SLGetWindowsInformationDWORD,
|
||||
@Stub_SLGetWindowsInformationDWORD, SizeOf(far_jmp), bw);
|
||||
end;
|
||||
end;
|
||||
if (V = $0601) and (INIReadBool(INI, 'Main', 'SLPolicyHookNT61', True)) then begin
|
||||
// Windows 7
|
||||
// uses SL Policy API (slc.dll)
|
||||
|
||||
// load slc.dll and hook function
|
||||
SLC_Handle := LoadLibrary('slc.dll');
|
||||
SLGetWindowsInformationDWORD := GetProcAddress(SLC_Handle, 'SLGetWindowsInformationDWORD');
|
||||
|
||||
if @SLGetWindowsInformationDWORD <> nil then
|
||||
begin
|
||||
// rewrite original function to call our function (make hook)
|
||||
|
||||
WriteLog('Hook SLGetWindowsInformationDWORD');
|
||||
Stub_SLGetWindowsInformationDWORD.PushOp := $68;
|
||||
Stub_SLGetWindowsInformationDWORD.PushArg := @New_SLGetWindowsInformationDWORD;
|
||||
Stub_SLGetWindowsInformationDWORD.RetOp := $C3;
|
||||
ReadProcessMemory(GetCurrentProcess, @SLGetWindowsInformationDWORD,
|
||||
@Old_SLGetWindowsInformationDWORD, SizeOf(OldCode), bw);
|
||||
WriteProcessMemory(GetCurrentProcess, @SLGetWindowsInformationDWORD,
|
||||
@Stub_SLGetWindowsInformationDWORD, SizeOf(far_jmp), bw);
|
||||
end;
|
||||
end;
|
||||
if V = $0602 then begin
|
||||
// Windows 8
|
||||
// uses SL Policy internal unexported function
|
||||
|
||||
// load slc.dll and get function
|
||||
// (will be used on intercepting undefined values)
|
||||
SLC_Handle := LoadLibrary('slc.dll');
|
||||
SLGetWindowsInformationDWORD := GetProcAddress(SLC_Handle, 'SLGetWindowsInformationDWORD');
|
||||
end;
|
||||
if V = $0603 then begin
|
||||
// Windows 8.1
|
||||
// uses SL Policy internal inline code
|
||||
end;
|
||||
if V = $0604 then begin
|
||||
// Windows 10
|
||||
// uses SL Policy internal inline code
|
||||
end;
|
||||
|
||||
Sect := IntToStr(FV.Version.w.Major)+'.'+IntToStr(FV.Version.w.Minor)+'.'+
|
||||
IntToStr(FV.Release)+'.'+IntToStr(FV.Build);
|
||||
|
||||
if INISectionExists(INI, Sect) then
|
||||
if GetModuleAddress('termsrv.dll', GetCurrentProcessId, TermSrvBase, TermSrvSize) then begin
|
||||
if INIReadBool(INI, Sect, 'LocalOnlyPatch.x86', False) then begin
|
||||
WriteLog('Patch CEnforcementCore::GetInstanceOfTSLicense');
|
||||
SignPtr := Pointer(Cardinal(TermSrvBase) + INIReadDWordHex(INI, Sect, 'LocalOnlyOffset.x86', 0));
|
||||
I := SListFind(PatchList, INIReadString(INI, Sect, 'LocalOnlyCode.x86', ''));
|
||||
if I >= 0 then
|
||||
WriteProcessMemory(GetCurrentProcess, SignPtr, @Patch[I][0], Length(Patch[I]), bw);
|
||||
end;
|
||||
if INIReadBool(INI, Sect, 'SingleUserPatch.x86', False) then begin
|
||||
WriteLog('Patch CSessionArbitrationHelper::IsSingleSessionPerUserEnabled');
|
||||
SignPtr := Pointer(Cardinal(TermSrvBase) + INIReadDWordHex(INI, Sect, 'SingleUserOffset.x86', 0));
|
||||
I := SListFind(PatchList, INIReadString(INI, Sect, 'SingleUserCode.x86', ''));
|
||||
if I >= 0 then
|
||||
WriteProcessMemory(GetCurrentProcess, SignPtr, @Patch[I][0], Length(Patch[I]), bw);
|
||||
end;
|
||||
if INIReadBool(INI, Sect, 'DefPolicyPatch.x86', False) then begin
|
||||
WriteLog('Patch CDefPolicy::Query');
|
||||
SignPtr := Pointer(Cardinal(TermSrvBase) + INIReadDWordHex(INI, Sect, 'DefPolicyOffset.x86', 0));
|
||||
I := SListFind(PatchList, INIReadString(INI, Sect, 'DefPolicyCode.x86', ''));
|
||||
if I >= 0 then
|
||||
WriteProcessMemory(GetCurrentProcess, SignPtr, @Patch[I][0], Length(Patch[I]), bw);
|
||||
end;
|
||||
if INIReadBool(INI, Sect, 'SLPolicyInternal.x86', False) then begin
|
||||
WriteLog('Hook SLGetWindowsInformationDWORDWrapper');
|
||||
SignPtr := Pointer(Cardinal(TermSrvBase) + INIReadDWordHex(INI, Sect, 'SLPolicyOffset.x86', 0));
|
||||
MovJump.MovOp := $89; // mov eax, ecx
|
||||
MovJump.MovArg := $C8; // __msfastcall compatibility
|
||||
MovJump.PushOp := $68;
|
||||
MovJump.PushArg := @New_Win8SL;
|
||||
MovJump.RetOp := $C3;
|
||||
FuncName := INIReadString(INI, Sect, 'SLPolicyFunc.x86', 'New_Win8SL');
|
||||
if FuncName = 'New_Win8SL' then
|
||||
MovJump.PushArg := @New_Win8SL;
|
||||
if FuncName = 'New_Win8SL_CP' then
|
||||
MovJump.PushArg := @New_Win8SL_CP;
|
||||
WriteProcessMemory(GetCurrentProcess, SignPtr,
|
||||
@MovJump, SizeOf(mov_far_jmp), bw);
|
||||
end;
|
||||
if INIReadBool(INI, Sect, 'SLInitHook.x86', False) then begin
|
||||
WriteLog('Hook CSLQuery::Initialize');
|
||||
SignPtr := Pointer(Cardinal(TermSrvBase) + INIReadDWordHex(INI, Sect, 'SLInitOffset.x86', 0));
|
||||
Jump.PushOp := $68;
|
||||
Jump.PushArg := @New_CSLQuery_Initialize;
|
||||
Jump.RetOp := $C3;
|
||||
FuncName := INIReadString(INI, Sect, 'SLInitFunc.x86', 'New_CSLQuery_Initialize');
|
||||
if FuncName = 'New_CSLQuery_Initialize' then
|
||||
Jump.PushArg := @New_CSLQuery_Initialize;
|
||||
WriteProcessMemory(GetCurrentProcess, SignPtr,
|
||||
@Jump, SizeOf(far_jmp), bw);
|
||||
end;
|
||||
end;
|
||||
|
||||
// unfreeze threads
|
||||
WriteLog('Resumimg threads...');
|
||||
RunThreads();
|
||||
end;
|
||||
|
||||
function TermServiceMain(dwArgc: DWORD; lpszArgv: PWideChar): DWORD; stdcall;
|
||||
begin
|
||||
// wrap ServiceMain function
|
||||
WriteLog('>>> ServiceMain');
|
||||
if not IsHooked then
|
||||
HookFunctions;
|
||||
Result := 0;
|
||||
if @TSMain <> nil then
|
||||
Result := TSMain(dwArgc, lpszArgv);
|
||||
WriteLog('<<< ServiceMain');
|
||||
end;
|
||||
|
||||
function TermServiceGlobals(lpGlobalData: Pointer): DWORD; stdcall;
|
||||
begin
|
||||
// wrap SvchostPushServiceGlobals function
|
||||
WriteLog('>>> SvchostPushServiceGlobals');
|
||||
if not IsHooked then
|
||||
HookFunctions;
|
||||
Result := 0;
|
||||
if @TSGlobals <> nil then
|
||||
Result := TSGlobals(lpGlobalData);
|
||||
WriteLog('<<< SvchostPushServiceGlobals');
|
||||
end;
|
||||
|
||||
// export section
|
||||
|
||||
exports
|
||||
TermServiceMain index 1 name 'ServiceMain',
|
||||
TermServiceGlobals index 2 name 'SvchostPushServiceGlobals';
|
||||
|
||||
begin
|
||||
// DllMain procedure is not used
|
||||
end.
|
||||
@ -1,105 +0,0 @@
|
||||
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<ProjectGuid>{D6811241-D595-4809-B3B8-13BECEA56E11}</ProjectGuid>
|
||||
<MainSource>rdpwrap.dpr</MainSource>
|
||||
<Config Condition="'$(Config)'==''">Release</Config>
|
||||
<DCC_DCCCompiler>DCC32</DCC_DCCCompiler>
|
||||
<ProjectVersion>12.0</ProjectVersion>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Config)'=='Base' or '$(Base)'!=''">
|
||||
<Base>true</Base>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Config)'=='Release' or '$(Cfg_1)'!=''">
|
||||
<Cfg_1>true</Cfg_1>
|
||||
<CfgParent>Base</CfgParent>
|
||||
<Base>true</Base>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Config)'=='Debug' or '$(Cfg_2)'!=''">
|
||||
<Cfg_2>true</Cfg_2>
|
||||
<CfgParent>Base</CfgParent>
|
||||
<Base>true</Base>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Base)'!=''">
|
||||
<DCC_DependencyCheckOutputName>rdpwrap.dll</DCC_DependencyCheckOutputName>
|
||||
<DCC_UnitAlias>WinTypes=Windows;WinProcs=Windows;$(DCC_UnitAlias)</DCC_UnitAlias>
|
||||
<GenDll>true</GenDll>
|
||||
<DCC_ImageBase>00400000</DCC_ImageBase>
|
||||
<DCC_Platform>x86</DCC_Platform>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Cfg_1)'!=''">
|
||||
<DCC_LocalDebugSymbols>false</DCC_LocalDebugSymbols>
|
||||
<DCC_Define>RELEASE;$(DCC_Define)</DCC_Define>
|
||||
<DCC_SymbolReferenceInfo>0</DCC_SymbolReferenceInfo>
|
||||
<DCC_DebugInformation>false</DCC_DebugInformation>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Cfg_2)'!=''">
|
||||
<DCC_Define>DEBUG;$(DCC_Define)</DCC_Define>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<DelphiCompile Include="rdpwrap.dpr">
|
||||
<MainSource>MainSource</MainSource>
|
||||
</DelphiCompile>
|
||||
<BuildConfiguration Include="Base">
|
||||
<Key>Base</Key>
|
||||
</BuildConfiguration>
|
||||
<BuildConfiguration Include="Release">
|
||||
<Key>Cfg_1</Key>
|
||||
<CfgParent>Base</CfgParent>
|
||||
</BuildConfiguration>
|
||||
<BuildConfiguration Include="Debug">
|
||||
<Key>Cfg_2</Key>
|
||||
<CfgParent>Base</CfgParent>
|
||||
</BuildConfiguration>
|
||||
</ItemGroup>
|
||||
<Import Project="$(BDS)\Bin\CodeGear.Delphi.Targets" Condition="Exists('$(BDS)\Bin\CodeGear.Delphi.Targets')"/>
|
||||
<ProjectExtensions>
|
||||
<Borland.Personality>Delphi.Personality.12</Borland.Personality>
|
||||
<Borland.ProjectType>VCLApplication</Borland.ProjectType>
|
||||
<BorlandProject>
|
||||
<Delphi.Personality>
|
||||
<Source>
|
||||
<Source Name="MainSource">rdpwrap.dpr</Source>
|
||||
</Source>
|
||||
<Parameters>
|
||||
<Parameters Name="UseLauncher">False</Parameters>
|
||||
<Parameters Name="LoadAllSymbols">True</Parameters>
|
||||
<Parameters Name="LoadUnspecifiedSymbols">False</Parameters>
|
||||
</Parameters>
|
||||
<VersionInfo>
|
||||
<VersionInfo Name="IncludeVerInfo">False</VersionInfo>
|
||||
<VersionInfo Name="AutoIncBuild">False</VersionInfo>
|
||||
<VersionInfo Name="MajorVer">1</VersionInfo>
|
||||
<VersionInfo Name="MinorVer">0</VersionInfo>
|
||||
<VersionInfo Name="Release">0</VersionInfo>
|
||||
<VersionInfo Name="Build">0</VersionInfo>
|
||||
<VersionInfo Name="Debug">False</VersionInfo>
|
||||
<VersionInfo Name="PreRelease">False</VersionInfo>
|
||||
<VersionInfo Name="Special">False</VersionInfo>
|
||||
<VersionInfo Name="Private">False</VersionInfo>
|
||||
<VersionInfo Name="DLL">False</VersionInfo>
|
||||
<VersionInfo Name="Locale">1049</VersionInfo>
|
||||
<VersionInfo Name="CodePage">1251</VersionInfo>
|
||||
</VersionInfo>
|
||||
<VersionInfoKeys>
|
||||
<VersionInfoKeys Name="CompanyName"/>
|
||||
<VersionInfoKeys Name="FileDescription"/>
|
||||
<VersionInfoKeys Name="FileVersion">1.0.0.0</VersionInfoKeys>
|
||||
<VersionInfoKeys Name="InternalName"/>
|
||||
<VersionInfoKeys Name="LegalCopyright"/>
|
||||
<VersionInfoKeys Name="LegalTrademarks"/>
|
||||
<VersionInfoKeys Name="OriginalFilename"/>
|
||||
<VersionInfoKeys Name="ProductName"/>
|
||||
<VersionInfoKeys Name="ProductVersion">1.0.0.0</VersionInfoKeys>
|
||||
<VersionInfoKeys Name="Comments"/>
|
||||
</VersionInfoKeys>
|
||||
<Excluded_Packages>
|
||||
<Excluded_Packages Name="$(BDS)\bin\bcboffice2k140.bpl">Embarcadero C++Builder Office 2000 Servers Package</Excluded_Packages>
|
||||
<Excluded_Packages Name="$(BDS)\bin\bcbofficexp140.bpl">Embarcadero C++Builder Office XP Servers Package</Excluded_Packages>
|
||||
<Excluded_Packages Name="$(BDS)\bin\dcloffice2k140.bpl">Microsoft Office 2000 Sample Automation Server Wrapper Components</Excluded_Packages>
|
||||
<Excluded_Packages Name="$(BDS)\bin\dclofficexp140.bpl">Microsoft Office XP Sample Automation Server Wrapper Components</Excluded_Packages>
|
||||
</Excluded_Packages>
|
||||
</Delphi.Personality>
|
||||
</BorlandProject>
|
||||
<ProjectFileVersion>12</ProjectFileVersion>
|
||||
</ProjectExtensions>
|
||||
</Project>
|
||||
@ -1,561 +0,0 @@
|
||||
RDP Wrapper Library project by Stas'M
|
||||
|
||||
Terminal Services supported versions
|
||||
6.0.X.X (Windows Vista, any) [policy hook only]
|
||||
6.0.6000.16386 (Windows Vista) [policy hook + extended patch]
|
||||
6.0.6000.20723 (Windows Vista with KB944917) [todo]
|
||||
6.0.6001.18000 (Windows Vista SP1) [policy hook + extended patch]
|
||||
6.0.6001.22286 (Windows Vista SP1 with KB958612) [todo]
|
||||
6.0.6001.22357 (Windows Vista SP1 with KB958612 v2) [todo]
|
||||
6.0.6001.22323 (Windows Vista SP1 with KB960742) [todo]
|
||||
6.0.6001.22392 (Windows Vista SP1 with KB968680) [todo]
|
||||
6.0.6001.22565 (Windows Vista SP1 with KB977541) [todo]
|
||||
6.0.6001.22635 (Windows Vista SP1 with KB970911) [todo]
|
||||
6.0.6001.22801 (Windows Vista SP1 with KB2381675) [todo]
|
||||
6.0.6002.18005 (Windows Vista SP2) [policy hook + extended patch]
|
||||
6.0.6002.22269 (Windows Vista SP2 with KB977541) [todo]
|
||||
6.0.6002.22340 (Windows Vista SP2 with KB970911) [todo]
|
||||
6.0.6002.22515 (Windows Vista SP2 with KB2381675) [todo]
|
||||
6.0.6002.22641 (Windows Vista SP2 with KB2523307) [todo]
|
||||
6.0.6002.22790 (Windows Vista SP2 with KB2672601) [todo]
|
||||
6.0.6002.19214 (Windows Vista SP2 with KB3003743 GDR) [policy hook + extended patch]
|
||||
6.0.6002.23521 (Windows Vista SP2 with KB3003743 LDR) [policy hook + extended patch]
|
||||
6.1.X.X (Windows 7, any) [policy hook only]
|
||||
6.1.7100.0 (Windows 7 Release Candidate) [todo]
|
||||
6.1.7600.16385 (Windows 7) [policy hook + extended patch]
|
||||
6.1.7600.20661 (Windows 7 with KB951422) [todo]
|
||||
6.1.7600.21085 (Windows 7 with KB951422 v2) [todo]
|
||||
6.1.7600.20621 (Windows 7 with KB979470) [todo]
|
||||
6.1.7600.20890 (Windows 7 with KB2479710) [policy hook + extended patch]
|
||||
6.1.7600.21316 (Windows 7 with KB2750090) [policy hook + extended patch]
|
||||
6.1.7600.21420 (Windows 7 with KB2800789) [todo]
|
||||
6.1.7601.17514 (Windows 7 SP1) [policy hook + extended patch]
|
||||
6.1.7601.21855 (Windows 7 SP1 with KB951422 v2) [todo]
|
||||
6.1.7601.21650 (Windows 7 SP1 with KB2479710) [policy hook + extended patch]
|
||||
6.1.7601.21866 (Windows 7 SP1 with KB2647409) [policy hook + extended patch]
|
||||
6.1.7601.22104 (Windows 7 SP1 with KB2750090) [policy hook + extended patch]
|
||||
6.1.7601.22213 (Windows 7 SP1 with KB2800789) [todo]
|
||||
6.1.7601.22476 (Windows 7 SP1 with KB2870165) [todo]
|
||||
6.1.7601.22435 (Windows 7 SP1 with KB2878424) [todo]
|
||||
6.1.7601.22477 (Windows 7 SP1 with KB2896256) [todo]
|
||||
6.1.7601.18540 (Windows 7 SP1 with KB2984972 GDR) [policy hook + extended patch]
|
||||
6.1.7601.22750 (Windows 7 SP1 with KB2984972 LDR) [policy hook + extended patch]
|
||||
6.1.7601.18637 (Windows 7 SP1 with KB3003743 GDR) [policy hook + extended patch]
|
||||
6.1.7601.22843 (Windows 7 SP1 with KB3003743 LDR) [policy hook + extended patch]
|
||||
6.1.7601.23403 (Windows 7 SP1 with KB3125574) [policy hook + extended patch]
|
||||
6.1.7601.24234 (Windows 7 SP1 with KB4462923) [policy hook + extended patch]
|
||||
6.2.8102.0 (Windows 8 Developer Preview) [policy hook + extended patch]
|
||||
6.2.8250.0 (Windows 8 Consumer Preview) [policy hook + extended patch]
|
||||
6.2.8400.0 (Windows 8 Release Preview) [policy hook + extended patch]
|
||||
6.2.9200.16384 (Windows 8) [policy hook + extended patch]
|
||||
6.2.9200.17048 (Windows 8 with KB2973501 GDR) [policy hook + extended patch]
|
||||
6.2.9200.21166 (Windows 8 with KB2973501 LDR) [policy hook + extended patch]
|
||||
6.3.9431.0 (Windows 8.1 Preview) [init hook + extended patch]
|
||||
6.3.9600.16384 (Windows 8.1) [init hook + extended patch]
|
||||
6.3.9600.17095 (Windows 8.1 with KB2959626) [init hook + extended patch]
|
||||
6.3.9600.17415 (Windows 8.1 with KB3000850) [init hook + extended patch]
|
||||
6.3.9600.18692 (Windows 8.1 with KB4022720) [init hook + extended patch]
|
||||
6.3.9600.18708 (Windows 8.1 with KB4025335) [init hook + extended patch]
|
||||
6.3.9600.18928 (Windows 8.1 with KB4088876) [init hook + extended patch]
|
||||
6.3.9600.19093 (Windows 8.1 with KB4343891) [init hook + extended patch]
|
||||
6.4.9841.0 (Windows 10 Technical Preview) [init hook + extended patch]
|
||||
6.4.9860.0 (Windows 10 Technical Preview UP1) [init hook + extended patch]
|
||||
6.4.9879.0 (Windows 10 Technical Preview UP2) [init hook + extended patch]
|
||||
10.0.9926.0 (Windows 10 Pro Technical Preview) [init hook + extended patch]
|
||||
10.0.10041.0 (Windows 10 Pro Technical Preview UP1) [init hook + extended patch]
|
||||
10.0.10049.0 (Windows 10 Pro Technical Preview UP2) [todo]
|
||||
10.0.10061.0 (Windows 10 Pro Technical Preview UP3) [todo]
|
||||
10.0.10240.16384 (Windows 10 RTM) [init hook + extended patch]
|
||||
10.0.10525.0 (Windows 10 th2_release.150812-1658) [todo]
|
||||
10.0.10532.0 (Windows 10 th2_release.150822-1406) [todo]
|
||||
10.0.10547.0 (Windows 10 th2_release.150913-1511) [todo]
|
||||
10.0.10586.0 (Windows 10 th2_release.151029-1700) [init hook + extended patch]
|
||||
10.0.10586.589 (Windows 10 th2_release.160906-1759) [init hook + extended patch]
|
||||
10.0.11082.1000 (Windows 10 rs1_release.151210-2021) [init hook + extended patch]
|
||||
10.0.11102.1000 (Windows 10 rs1_release.160113-1800) [init hook + extended patch]
|
||||
10.0.14251.1000 (Windows 10 rs1_release.160124-1059) [init hook + extended patch]
|
||||
10.0.14271.1000 (Windows 10 rs1_release.160218-2310) [init hook + extended patch]
|
||||
10.0.14279.1000 (Windows 10 rs1_release.160229-1700) [init hook + extended patch]
|
||||
10.0.14295.1000 (Windows 10 rs1_release.160318-1628) [init hook + extended patch]
|
||||
10.0.14300.1000 (Windows Server 2016 Technical Preview 5) [init hook + extended patch]
|
||||
10.0.14316.1000 (Windows 10 rs1_release.160402-2227) [init hook + extended patch]
|
||||
10.0.14328.1000 (Windows 10 rs1_release.160418-1609) [init hook + extended patch]
|
||||
10.0.14332.1001 (Windows 10 rs1_release.160422-1940) [init hook + extended patch]
|
||||
10.0.14342.1000 (Windows 10 rs1_release.160506-1708) [init hook + extended patch]
|
||||
10.0.14352.1002 (Windows 10 rs1_release.160522-1930) [init hook + extended patch]
|
||||
10.0.14366.0 (Windows 10 rs1_release.160610-1700) [init hook + extended patch]
|
||||
10.0.14367.0 (Windows 10 rs1_release.160613-1700) [init hook + extended patch]
|
||||
10.0.14372.0 (Windows 10 rs1_release.160620-2342) [init hook + extended patch]
|
||||
10.0.14379.0 (Windows 10 rs1_release.160627-1607) [init hook + extended patch]
|
||||
10.0.14383.0 (Windows 10 rs1_release.160701-1839) [init hook + extended patch]
|
||||
10.0.14385.0 (Windows 10 rs1_release.160706-1700) [init hook + extended patch]
|
||||
10.0.14388.0 (Windows 10 rs1_release.160709-1635) [init hook + extended patch]
|
||||
10.0.14393.0 (Windows 10 rs1_release.160715-1616) [init hook + extended patch]
|
||||
10.0.14393.1198 (Windows 10 rs1_release_sec.170427-1353) [init hook + extended patch]
|
||||
10.0.14393.1737 (Windows 10 rs1_release_inmarket.170914-1249) [init hook + extended patch]
|
||||
10.0.14393.2457 (Windows 10 rs1_release_inmarket.180822-1743) [init hook + extended patch]
|
||||
10.0.14901.1000 (Windows 10 rs_prerelease.160805-1700) [init hook + extended patch]
|
||||
10.0.14905.1000 (Windows 10 rs_prerelease.160811-1739) [init hook + extended patch]
|
||||
10.0.14915.1000 (Windows 10 rs_prerelease.160826-1902) [init hook + extended patch]
|
||||
10.0.14926.1000 (Windows 10 rs_prerelease.160910-1529) [init hook + extended patch]
|
||||
10.0.14931.1000 (Windows 10 rs_prerelease.160916-1700) [init hook + extended patch]
|
||||
10.0.14936.1000 (Windows 10 rs_prerelease.160923-1700) [init hook + extended patch]
|
||||
10.0.14942.1000 (Windows 10 rs_prerelease.161003-1929) [init hook + extended patch]
|
||||
10.0.14946.1000 (Windows 10 rs_prerelease.161007-1700) [init hook + extended patch]
|
||||
10.0.14951.1000 (Windows 10 rs_prerelease.161014-1700) [init hook + extended patch]
|
||||
10.0.14955.1000 (Windows 10 rs_prerelease.161020-1700) [init hook + extended patch]
|
||||
10.0.14959.1000 (Windows 10 rs_prerelease.161026-1700) [init hook + extended patch]
|
||||
10.0.14965.1001 (Windows 10 rs_prerelease.161104-1700) [init hook + extended patch]
|
||||
10.0.14971.1000 (Windows 10 rs_prerelease.161111-1700) [init hook + extended patch]
|
||||
10.0.14986.1000 (Windows 10 WinBuild.160101.0800) [init hook + extended patch]
|
||||
10.0.14997.1001 (Windows 10 WinBuild.160101.0800) [init hook + extended patch]
|
||||
10.0.15002.1001 (Windows 10 WinBuild.160101.0800) [init hook + extended patch]
|
||||
10.0.15007.1000 (Windows 10 WinBuild.160101.0800) [init hook + extended patch]
|
||||
10.0.15014.1000 (Windows 10 WinBuild.160101.0800) [init hook + extended patch]
|
||||
10.0.15019.1000 (Windows 10 rs_prerelease.170121-1513) [init hook + extended patch]
|
||||
10.0.15025.1000 (Windows 10 rs_prerelease.170127-1750) [init hook + extended patch]
|
||||
10.0.15031.0 (Windows 10 rs2_release.170204-1546) [init hook + extended patch]
|
||||
10.0.15042.0 (Windows 10 rs2_release.170219-2329) [init hook + extended patch]
|
||||
10.0.15046.0 (Windows 10 WinBuild.160101.0800) [init hook + extended patch]
|
||||
10.0.15048.0 (Windows 10 WinBuild.160101.0800) [init hook + extended patch]
|
||||
10.0.15055.0 (Windows 10 WinBuild.160101.0800) [init hook + extended patch]
|
||||
10.0.15058.0 (Windows 10 WinBuild.160101.0800) [init hook + extended patch]
|
||||
10.0.15061.0 (Windows 10 WinBuild.160101.0800) [init hook + extended patch]
|
||||
10.0.15063.0 (Windows 10 WinBuild.160101.0800) [init hook + extended patch]
|
||||
10.0.15063.296 (Windows 10 WinBuild.160101.0800) [init hook + extended patch]
|
||||
10.0.15063.994 (Windows 10 WinBuild.160101.0800) [init hook + extended patch]
|
||||
10.0.15063.1155 (Windows 10 WinBuild.160101.0800) [init hook + extended patch]
|
||||
10.0.16179.1000 (Windows 10 WinBuild.160101.0800) [init hook + extended patch]
|
||||
10.0.16184.1001 (Windows 10 WinBuild.160101.0800) [init hook + extended patch]
|
||||
10.0.16199.1000 (Windows 10 WinBuild.160101.0800) [init hook + extended patch]
|
||||
10.0.16215.1000 (Windows 10 WinBuild.160101.0800) [init hook + extended patch]
|
||||
10.0.16232.1000 (Windows 10 WinBuild.160101.0800) [init hook + extended patch]
|
||||
10.0.16237.1001 (Windows 10 WinBuild.160101.0800) [init hook + extended patch]
|
||||
10.0.16241.1001 (Windows 10 WinBuild.160101.0800) [init hook + extended patch]
|
||||
10.0.16251.0 (Windows 10 WinBuild.160101.0800) [init hook + extended patch]
|
||||
10.0.16251.1000 (Windows 10 WinBuild.160101.0800) [init hook + extended patch]
|
||||
10.0.16257.1 (Windows 10 WinBuild.160101.0800) [init hook + extended patch]
|
||||
10.0.16257.1000 (Windows 10 WinBuild.160101.0800) [init hook + extended patch]
|
||||
10.0.16273.1000 (Windows 10 WinBuild.160101.0800) [init hook + extended patch]
|
||||
10.0.16275.1000 (Windows 10 WinBuild.160101.0800) [init hook + extended patch]
|
||||
10.0.16278.1000 (Windows 10 WinBuild.160101.0800) [init hook + extended patch]
|
||||
10.0.16281.1000 (Windows 10 WinBuild.160101.0800) [init hook + extended patch]
|
||||
10.0.16288.1 (Windows 10 WinBuild.160101.0800) [init hook + extended patch]
|
||||
10.0.16291.0 (Windows 10 WinBuild.160101.0800) [init hook + extended patch]
|
||||
10.0.16294.1 (Windows 10 WinBuild.160101.0800) [init hook + extended patch]
|
||||
10.0.16296.0 (Windows 10 WinBuild.160101.0800) [init hook + extended patch]
|
||||
10.0.16299.0 (Windows 10 WinBuild.160101.0800) [init hook + extended patch]
|
||||
10.0.16299.15 (Windows 10 WinBuild.160101.0800) [init hook + extended patch]
|
||||
10.0.16353.1000 (Windows 10 WinBuild.160101.0800) [init hook + extended patch]
|
||||
10.0.16362.1000 (Windows 10 WinBuild.160101.0800) [init hook + extended patch]
|
||||
10.0.17004.1000 (Windows 10 WinBuild.160101.0800) [init hook + extended patch]
|
||||
10.0.17017.1000 (Windows 10 WinBuild.160101.0800) [init hook + extended patch]
|
||||
10.0.17025.1000 (Windows 10 WinBuild.160101.0800) [init hook + extended patch]
|
||||
10.0.17035.1000 (Windows 10 WinBuild.160101.0800) [init hook + extended patch]
|
||||
10.0.17040.1000 (Windows 10 WinBuild.160101.0800) [todo]
|
||||
10.0.17046.1000 (Windows 10 WinBuild.160101.0800) [init hook + extended patch]
|
||||
10.0.17063.1000 (Windows 10 WinBuild.160101.0800) [init hook + extended patch]
|
||||
10.0.17074.1002 (Windows 10 WinBuild.160101.0800) [todo]
|
||||
10.0.17083.1000 (Windows 10 WinBuild.160101.0800) [todo]
|
||||
10.0.17115.1 (Windows 10 WinBuild.160101.0800) [init hook + extended patch]
|
||||
10.0.17128.1 (Windows 10 WinBuild.160101.0800) [init hook + extended patch]
|
||||
10.0.17133.1 (Windows 10 WinBuild.160101.0800) [init hook + extended patch]
|
||||
10.0.17134.1 (Windows 10 WinBuild.160101.0800) [init hook + extended patch]
|
||||
10.0.17723.1000 (Windows 10 WinBuild.160101.0800) [init hook + extended patch]
|
||||
10.0.17763.1 (Windows 10 WinBuild.160101.0800) [init hook + extended patch]
|
||||
|
||||
Source code changelog (rdpwrap library):
|
||||
|
||||
2018.10.10 :
|
||||
- added support for termsrv.dll 6.1.7601.24234 x86
|
||||
|
||||
2018.10.04 :
|
||||
- added support for termsrv.dll 10.0.14393.2457 x86
|
||||
|
||||
2018.10.03 :
|
||||
- added support for termsrv.dll 6.1.7601.24234 x64
|
||||
- added support for termsrv.dll 10.0.15063.994 x64
|
||||
- added support for termsrv.dll 10.0.17723.1000 x64
|
||||
- added support for termsrv.dll 10.0.17763.1
|
||||
|
||||
2018.09.10 :
|
||||
- added support for termsrv.dll 6.1.7600.20890
|
||||
- added support for termsrv.dll 6.1.7600.21316
|
||||
- added support for termsrv.dll 6.1.7601.21650
|
||||
- added support for termsrv.dll 6.1.7601.21866
|
||||
- added support for termsrv.dll 6.1.7601.22104
|
||||
- added support for termsrv.dll 6.3.9600.19093
|
||||
- added support for termsrv.dll 10.0.14393.2457 x64
|
||||
- added support for termsrv.dll 10.0.15063.1155 x64
|
||||
|
||||
2018.05.16 :
|
||||
- added support for termsrv.dll 10.0.17115.1
|
||||
- added support for termsrv.dll 10.0.17128.1
|
||||
- added support for termsrv.dll 10.0.17133.1
|
||||
- added support for termsrv.dll 10.0.17134.1
|
||||
|
||||
2018.03.26 :
|
||||
- added support for termsrv.dll 6.3.9600.18928 by 1nd1g0
|
||||
|
||||
2017.12.27 :
|
||||
- added support for termsrv.dll 10.0.17017.1000
|
||||
- added support for termsrv.dll 10.0.17025.1000
|
||||
- added support for termsrv.dll 10.0.17035.1000
|
||||
- added support for termsrv.dll 10.0.17046.1000
|
||||
- added support for termsrv.dll 10.0.17063.1000
|
||||
|
||||
2017.10.13 :
|
||||
- added support for termsrv.dll 10.0.14393.1737
|
||||
- added support for termsrv.dll 10.0.16299.0
|
||||
- added support for termsrv.dll 10.0.16299.15
|
||||
- added support for termsrv.dll 10.0.17004.1000
|
||||
|
||||
2017.09.24 :
|
||||
- added support for termsrv.dll 10.0.16291.0
|
||||
- added support for termsrv.dll 10.0.16294.1
|
||||
- added support for termsrv.dll 10.0.16296.0
|
||||
- added support for termsrv.dll 10.0.16362.1000
|
||||
|
||||
2017.09.15 :
|
||||
- added support for termsrv.dll 10.0.16288.1
|
||||
|
||||
2017.09.06 :
|
||||
- added support for termsrv.dll 10.0.16273.1000
|
||||
- added support for termsrv.dll 10.0.16275.1000
|
||||
- added support for termsrv.dll 10.0.16278.1000
|
||||
- added support for termsrv.dll 10.0.16281.1000
|
||||
- added support for termsrv.dll 10.0.16353.1000
|
||||
|
||||
2017.08.04 :
|
||||
- added support for termsrv.dll 10.0.16257.1
|
||||
- added support for termsrv.dll 10.0.16257.1000
|
||||
|
||||
2017.07.30 :
|
||||
- added support for termsrv.dll 6.3.9600.18708
|
||||
- added support for termsrv.dll 10.0.16232.1000
|
||||
- added support for termsrv.dll 10.0.16237.1001
|
||||
- added support for termsrv.dll 10.0.16241.1001
|
||||
- added support for termsrv.dll 10.0.16251.0
|
||||
- added support for termsrv.dll 10.0.16251.1000
|
||||
|
||||
2017.06.29 :
|
||||
- added support for termsrv.dll 6.3.9600.18692
|
||||
|
||||
2017.06.10 :
|
||||
- added support for termsrv.dll 10.0.15063.296
|
||||
- added support for termsrv.dll 10.0.16215.1000
|
||||
|
||||
2017.05.29 :
|
||||
- added support for termsrv.dll 10.0.16199.1000
|
||||
|
||||
2017.05.17 :
|
||||
- added support for termsrv.dll 10.0.14997.1001 x64
|
||||
|
||||
2017.05.12 :
|
||||
- added support for termsrv.dll 10.0.14393.1198 x86
|
||||
|
||||
2017.05.03 :
|
||||
- added support for termsrv.dll 10.0.16179.1000
|
||||
- added support for termsrv.dll 10.0.16184.1001
|
||||
|
||||
2017.03.22 :
|
||||
- added support for termsrv.dll 10.0.15063.0
|
||||
|
||||
2017.03.21 :
|
||||
- added support for termsrv.dll 10.0.15061.0
|
||||
|
||||
2017.03.16 :
|
||||
- added support for termsrv.dll 10.0.15058.0
|
||||
|
||||
2017.03.14 :
|
||||
- added support for termsrv.dll 10.0.15055.0
|
||||
|
||||
2017.03.05 :
|
||||
- added support for termsrv.dll 10.0.15048.0
|
||||
|
||||
2017.03.02 :
|
||||
- added support for termsrv.dll 10.0.15046.0
|
||||
|
||||
2017.03.01 :
|
||||
- added support for termsrv.dll 10.0.15031.0
|
||||
- added support for termsrv.dll 10.0.15042.0
|
||||
|
||||
2017.02.03 :
|
||||
- added support for termsrv.dll 10.0.15025.1000 x64
|
||||
|
||||
2017.01.28 :
|
||||
- added support for termsrv.dll 10.0.15019.1000
|
||||
|
||||
2017.01.21 :
|
||||
- added support for termsrv.dll 10.0.15014.1000
|
||||
|
||||
2017.01.15 :
|
||||
- added support for termsrv.dll 10.0.15007.1000
|
||||
|
||||
2017.01.12 :
|
||||
- added support for termsrv.dll 10.0.15002.1001
|
||||
|
||||
2016.12.23 :
|
||||
- added support for termsrv.dll 10.0.14986.1000
|
||||
|
||||
2016.11.19 :
|
||||
- added support for termsrv.dll 10.0.14959.1000
|
||||
- added support for termsrv.dll 10.0.14965.1001
|
||||
- added support for termsrv.dll 10.0.14971.1000
|
||||
|
||||
2016.10.28 :
|
||||
- added support for termsrv.dll 10.0.14955.1000
|
||||
|
||||
2016.10.21 :
|
||||
- added support for termsrv.dll 10.0.14951.1000
|
||||
|
||||
2016.10.19 :
|
||||
- added support for termsrv.dll 10.0.14946.1000
|
||||
|
||||
2016.10.08 :
|
||||
- added support for termsrv.dll 10.0.14942.1000
|
||||
|
||||
2016.09.30 :
|
||||
- added support for termsrv.dll 10.0.14936.1000
|
||||
|
||||
2016.09.27 :
|
||||
- added support for termsrv.dll 10.0.14931.1000
|
||||
|
||||
2016.09.15 :
|
||||
- added support for termsrv.dll 10.0.14926.1000
|
||||
|
||||
2016.09.14 :
|
||||
- added support for termsrv.dll 10.0.10586.589
|
||||
|
||||
2016.09.03 :
|
||||
- added support for termsrv.dll 10.0.14915.1000
|
||||
|
||||
2016.08.28 :
|
||||
- added support for termsrv.dll 6.1.7601.23403
|
||||
- added support for termsrv.dll 10.0.14901.1000
|
||||
- added support for termsrv.dll 10.0.14905.1000
|
||||
|
||||
2016.08.12 :
|
||||
- added support for termsrv.dll 10.0.14385.0
|
||||
|
||||
2016.08.01 :
|
||||
- preparing the release
|
||||
|
||||
2016.07.23 :
|
||||
- added online install mode to installer
|
||||
- added feature to keep settings on uninstall
|
||||
- fixed update firewall rule on port change in config tool
|
||||
- added feature to hide users on logon
|
||||
|
||||
2016.07.22 :
|
||||
- added support for termsrv.dll 10.0.14393.0
|
||||
|
||||
2016.07.15 :
|
||||
- added support for termsrv.dll 10.0.14383.0
|
||||
- added support for termsrv.dll 10.0.14388.0
|
||||
|
||||
2016.07.06 :
|
||||
- added support for termsrv.dll 10.0.14379.0
|
||||
|
||||
2016.06.27 :
|
||||
- added support for termsrv.dll 10.0.14372.0 x86
|
||||
|
||||
2016.06.26 :
|
||||
- added support for termsrv.dll 10.0.14372.0 x64 by kbmorris
|
||||
|
||||
2016.06.17 :
|
||||
- fixed issue with termsrv.dll 10.0.14352.1002
|
||||
- added support for termsrv.dll 10.0.14366.0
|
||||
- added support for termsrv.dll 10.0.14367.0
|
||||
|
||||
2016.05.30 :
|
||||
- added support for termsrv.dll 10.0.14352.1002
|
||||
|
||||
2016.05.14 :
|
||||
- added support for termsrv.dll 10.0.14342.1000
|
||||
|
||||
2016.05.08 :
|
||||
- added support for termsrv.dll 10.0.14300.1000 x64
|
||||
- added support for termsrv.dll 10.0.14328.1000
|
||||
|
||||
2016.04.29 :
|
||||
- added support for termsrv.dll 10.0.14332.1001 by maxpiva
|
||||
|
||||
2016.04.14 :
|
||||
- added support for termsrv.dll 10.0.14316.1000
|
||||
|
||||
2016.04.06 :
|
||||
- added support for termsrv.dll 10.0.14295.1000
|
||||
|
||||
2016.03.07 :
|
||||
- added experimental codes for ARMv7 architecture (see rdpwrap-arm-kb.ini)
|
||||
- Windows RT / termsrv.dll 6.2.9200.16384
|
||||
- Windows RT 8.1 / termsrv.dll 6.3.9600.16384
|
||||
- Windows RT 8.1 / termsrv.dll 6.3.9600.17095
|
||||
|
||||
2016.03.06 :
|
||||
- added support for termsrv.dll 10.0.14279.1000
|
||||
|
||||
2016.02.29 :
|
||||
- added support for termsrv.dll 10.0.14271.1000
|
||||
|
||||
2016.01.28 :
|
||||
- added support for termsrv.dll 10.0.14251.1000
|
||||
|
||||
2016.01.26 :
|
||||
- added support for termsrv.dll 10.0.11102.1000
|
||||
|
||||
2016.01.15 :
|
||||
- updated messages in the installer
|
||||
- added support for termsrv.dll 10.0.11082.1000
|
||||
|
||||
2015.11.14 :
|
||||
- added support for termsrv.dll 10.0.10586.0
|
||||
|
||||
2015.08.11 :
|
||||
- embed new rdpclip versions in the installer (for NT 6.0 and 6.1)
|
||||
- preparing the release
|
||||
|
||||
2015.08.07 :
|
||||
- added INI update feature to installer
|
||||
|
||||
2015.07.30 :
|
||||
- fixed issue with Windows 10 Home x86 (wrong LocalOnly offset was specified in INI file)
|
||||
|
||||
2015.07.17 :
|
||||
- added support for termsrv.dll 10.0.10240.16384
|
||||
- added HOW TO hints to KB (so other reverse engineers can do this hard work more easier)
|
||||
|
||||
2015.07.16 :
|
||||
- moved all comments from INI file to Knowledge Base text file
|
||||
- now INI file have smaller size
|
||||
- updated RDP checker: changed IP Address to 127.0.0.2 (sometimes client doesn't want to connect .1), updated text message
|
||||
- updated RDP config: list all possible shadowing modes, also write group policy
|
||||
- updated installer: added workaround for 1056 error
|
||||
- updated copyright years in source code
|
||||
- obtained files from build 10.0.10240.16384
|
||||
- researching Windows 10 RTM
|
||||
|
||||
2015.03.23 :
|
||||
- researching Windows 10 Pro Technical Preview UP1
|
||||
- added support for termsrv.dll 10.0.10041.0
|
||||
|
||||
2015.03.20 :
|
||||
- new build 10.0.10041.0 was released, obtaining files...
|
||||
|
||||
2015.01.26 :
|
||||
- researching Windows 10 Pro Technical Preview (10.0.9926.0 x86)
|
||||
- added support for termsrv.dll 10.0.9926.0 (x86)
|
||||
|
||||
2015.01.22 :
|
||||
- v-yadli contributed offsets for version 10.0.9926.0 (x64)
|
||||
|
||||
2014.12.13 :
|
||||
- added more policy values to INI file
|
||||
|
||||
2014.12.10 :
|
||||
- C++ version seems to work well now!
|
||||
- added support for termsrv.dll 6.4.9879.0
|
||||
- preparing the new release
|
||||
|
||||
2014.12.09 :
|
||||
- many bug fixes in C++ version, you can track it in the git history :)
|
||||
- it can be compiled now :D
|
||||
- we are getting closer to the finish line!
|
||||
|
||||
2014.12.03 :
|
||||
- added INI reader by Fusix for C++ version
|
||||
- asulwer also helped with the development
|
||||
|
||||
2014.11.25 :
|
||||
- corrected some typos in INI file
|
||||
- added EasyPrint policy value
|
||||
|
||||
2014.11.24 :
|
||||
- added support for termsrv.dll 6.3.9600.17415
|
||||
|
||||
2014.11.21 :
|
||||
- new LiteINI module to read INI files
|
||||
- added support to store patch settings in INI file
|
||||
- version support can be extended without recompilation
|
||||
- C++ version needs to be updated
|
||||
|
||||
2014.11.20 :
|
||||
- improved comments
|
||||
- researching KB3000850
|
||||
- found required files
|
||||
- improving RDPWrap...
|
||||
- placing signatures, offsets, values, etc in separate config file
|
||||
- working with code
|
||||
|
||||
2014.11.13 :
|
||||
- researching KB3003743
|
||||
- added support for version 6.0.6002.19214
|
||||
- added support for version 6.0.6002.23521
|
||||
- added support for version 6.1.7601.18637
|
||||
- added support for version 6.1.7601.22843
|
||||
|
||||
2014.11.02 :
|
||||
- researching termsrv.dll 6.4.9860.0
|
||||
- done
|
||||
|
||||
2014.10.19 :
|
||||
- added support for version 6.0.6000.16386 (x64)
|
||||
- added support for version 6.0.6001.18000 (x64)
|
||||
- added support for version 6.1.7600.16385
|
||||
|
||||
2014.10.18 :
|
||||
- corrected some typos in source
|
||||
- simplified signature constants
|
||||
- added support for version 6.0.6000.16386 (x86)
|
||||
- added support for version 6.0.6001.18000 (x86)
|
||||
- added support for version 6.0.6002.18005
|
||||
- added support for version 6.1.7601.17514
|
||||
- added support for version 6.1.7601.18540
|
||||
- added support for version 6.1.7601.22750
|
||||
- added support for version 6.2.9200.17048
|
||||
- added support for version 6.2.9200.21166
|
||||
|
||||
2014.10.17 :
|
||||
- collecting information about all versions of Terminal Services beginning from Vista
|
||||
- added [todo] to the versions list
|
||||
|
||||
2014.10.16 :
|
||||
- got new updates: KB2984972 for Win 7 (still works with 2 concurrent users) and KB2973501 for Win 8 (doesn't work)
|
||||
|
||||
2014.10.02 :
|
||||
- researching Windows 10 TP Remote Desktop
|
||||
- done! even without debugging symbols ^^)
|
||||
|
||||
2014.07.20 :
|
||||
- added support for Windows 8 Release Preview
|
||||
- added support for Windows 8 Consumer Preview
|
||||
- added support for Windows 8 Developer Preview
|
||||
|
||||
2014.07.19 :
|
||||
- improved patching of Windows 8
|
||||
- added policy patches
|
||||
- will patch CDefPolicy::Query
|
||||
- will patch CSessionArbitrationHelper::IsSingleSessionPerUserEnabled
|
||||
|
||||
2014.07.18 :
|
||||
- researched patched files from MDL forum
|
||||
- CSLQuery::GetMaxSessions requires no patching
|
||||
- it's better to change the default policy, so...
|
||||
- will patch CDefPolicy::Query
|
||||
- will patch CEnforcementCore::GetInstanceOfTSLicense
|
||||
- will patch CSessionArbitrationHelper::IsSingleSessionPerUserEnabled
|
||||
- the function CSLQuery::Initialize is hooked correctly
|
||||
|
||||
2014.07.17 :
|
||||
- will hook only CSLQuery::Initialize function
|
||||
- CSLQuery::GetMaxSessions will be patched
|
||||
- added x86 signatures for 6.3.9431.0 (Windows 8.1 Preview)
|
||||
|
||||
2014.07.16 :
|
||||
- changing asm opcodes is bad, will hook CSL functions
|
||||
|
||||
2014.07.15 :
|
||||
- added x86 signatures for 6.3.9600.16384 (Windows 8.1)
|
||||
2014.07.15 :
|
||||
- added x86 signatures for 6.3.9600.17095 (Windows 8.1 with KB2959626)
|
||||
@ -1,35 +0,0 @@
|
||||
# RDPWrapOffsetFinder
|
||||
|
||||
Pre-built binaries of [llccd/RDPWrapOffsetFinder](https://github.com/llccd/RDPWrapOffsetFinder),
|
||||
committed here so that the CI pipeline and releases are self-contained and
|
||||
reproducible without depending on an external release being available at build time.
|
||||
|
||||
## Contents
|
||||
|
||||
```
|
||||
x64/
|
||||
RDPWrapOffsetFinder.exe # x86-64 build
|
||||
Zydis.dll # required runtime (x64)
|
||||
x86/
|
||||
RDPWrapOffsetFinder.exe # x86 32-bit build
|
||||
Zydis.dll # required runtime (x86)
|
||||
VERSION # version tag of the upstream release
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
Extract the appropriate arch folder and run:
|
||||
|
||||
```
|
||||
.\RDPWrapOffsetFinder.exe C:\Windows\System32\termsrv.dll
|
||||
```
|
||||
|
||||
The output `[10.0.xxxxx.xxxxx]` section can be appended to `res/rdpwrap.ini`
|
||||
and submitted as a pull request.
|
||||
|
||||
## Updating
|
||||
|
||||
To update the binaries when llccd releases a new version, trigger the
|
||||
`Update RDPWrapOffsetFinder tools` workflow from the Actions tab
|
||||
(`.github/workflows/update-finder-tools.yml`).
|
||||
It will download the latest release, update this folder, and open a pull request.
|
||||
@ -1 +0,0 @@
|
||||
v0.9
|
||||