Merge 8a5fbe9870 into a5c64a4331
@ -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,12 @@
|
||||
---
|
||||
title: "[Announcement] "
|
||||
---
|
||||
|
||||
<!-- Announcements are for maintainers only.
|
||||
Use this for release notes, breaking changes, and project status updates. -->
|
||||
|
||||
## Summary
|
||||
|
||||
## Details
|
||||
|
||||
## Action required (if any)
|
||||
@ -0,0 +1,6 @@
|
||||
---
|
||||
title: ""
|
||||
---
|
||||
|
||||
<!-- Use this category for general conversation that does not fit a bug report,
|
||||
INI request, Q&A, or ideas discussion. -->
|
||||
@ -0,0 +1,26 @@
|
||||
---
|
||||
title: "[Idea] "
|
||||
---
|
||||
|
||||
## Problem or motivation
|
||||
|
||||
<!-- What problem does this idea solve? Who is affected? -->
|
||||
|
||||
## Proposed solution
|
||||
|
||||
<!-- Describe the behaviour or change you would like to see -->
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
<!-- Any other approaches you have thought about (optional) -->
|
||||
|
||||
## Component this relates to
|
||||
|
||||
<!-- Pick one or more:
|
||||
- rdpwrap.dll (C++ wrapper library)
|
||||
- RDPWInst / installer (Delphi)
|
||||
- rdpWrapper GUI (sergiye C# app)
|
||||
- rdpwrap.ini (offset database)
|
||||
- CI/CD pipelines
|
||||
- Documentation
|
||||
-->
|
||||
@ -0,0 +1,25 @@
|
||||
---
|
||||
title: "[Q&A] "
|
||||
---
|
||||
|
||||
## What are you trying to do?
|
||||
|
||||
<!-- Describe the goal, e.g. enable concurrent RDP sessions on Windows 11 24H2 -->
|
||||
|
||||
## What have you already tried?
|
||||
|
||||
<!-- Steps taken, links consulted, etc. -->
|
||||
|
||||
## System information
|
||||
|
||||
| Field | Value |
|
||||
|---|---|
|
||||
| Windows version (winver) | <!-- e.g. Windows 11 Version 24H2 (OS Build 26100.3915) --> |
|
||||
| termsrv.dll version | <!-- (Get-Item C:\Windows\System32\termsrv.dll).VersionInfo.FileVersion --> |
|
||||
| Architecture | <!-- x64 / x86 / ARM64 --> |
|
||||
| RDPConf status | <!-- green / yellow / red / not installed --> |
|
||||
| RDP Wrapper release | <!-- e.g. ini-2026.03.29.1400 --> |
|
||||
|
||||
## Error or unexpected behaviour
|
||||
|
||||
<!-- Paste any relevant console output, error messages, or screenshots -->
|
||||
@ -0,0 +1,16 @@
|
||||
---
|
||||
title: "[Show] "
|
||||
---
|
||||
|
||||
## What are you sharing?
|
||||
|
||||
<!-- A working config, a custom INI section, an automation script, a deployment
|
||||
setup, or anything else the community might find useful -->
|
||||
|
||||
## Details
|
||||
|
||||
<!-- Describe it, paste relevant code/config, add screenshots if helpful -->
|
||||
|
||||
## System or environment
|
||||
|
||||
<!-- Windows version, architecture, any relevant context -->
|
||||
@ -0,0 +1,89 @@
|
||||
name: Bug Report
|
||||
description: RDP Wrapper is not working correctly on your machine.
|
||||
labels: [bug]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Before submitting, please confirm:
|
||||
- You are running the latest release from https://github.com/sjackson0109/rdpwrap/releases/latest
|
||||
- Your termsrv.dll version is listed in `rdpwrap.ini` (check RDPConf for the status colour)
|
||||
- You have excluded `C:\Program Files\RDP Wrapper\` from your antivirus
|
||||
|
||||
- type: dropdown
|
||||
id: status
|
||||
attributes:
|
||||
label: RDPConf status
|
||||
description: What colour/status does RDPConf show?
|
||||
options:
|
||||
- Fully supported (green) — but RDP still fails
|
||||
- Partially supported (yellow)
|
||||
- Not supported (red)
|
||||
- Not installed
|
||||
- Wrapper DLL not found
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: windows_version
|
||||
attributes:
|
||||
label: Windows version
|
||||
description: "Run: winver — paste the full string e.g. Windows 11 Version 24H2 (OS Build 26100.xxxx)"
|
||||
placeholder: "Windows 11 Version 24H2 (OS Build 26100.3915)"
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: termsrv_version
|
||||
attributes:
|
||||
label: termsrv.dll version
|
||||
description: "Run: (Get-Item C:\\Windows\\System32\\termsrv.dll).VersionInfo.FileVersion"
|
||||
placeholder: "10.0.26100.3915"
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: rdpwrap_version
|
||||
attributes:
|
||||
label: RDP Wrapper version
|
||||
description: Version shown in RDPConf or the release tag you downloaded.
|
||||
placeholder: "ini-2026.03.29.1400"
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: dropdown
|
||||
id: architecture
|
||||
attributes:
|
||||
label: System architecture
|
||||
options:
|
||||
- x64 (64-bit)
|
||||
- x86 (32-bit)
|
||||
- ARM64
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: Describe the problem
|
||||
description: What happens? What did you expect to happen?
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: steps
|
||||
attributes:
|
||||
label: Steps to reproduce
|
||||
placeholder: |
|
||||
1. Run install.bat as administrator
|
||||
2. Open RDPConf
|
||||
3. Status shows ...
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: logs
|
||||
attributes:
|
||||
label: Relevant log output
|
||||
description: Paste any output from the install/update bat scripts or Event Viewer (optional).
|
||||
render: text
|
||||
@ -0,0 +1,20 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: Q&A — ask a setup or usage question
|
||||
url: https://github.com/sjackson0109/rdpwrap/discussions/new?category=q-a
|
||||
about: Not sure why it is not working? Ask in Discussions rather than opening an issue.
|
||||
- name: Ideas — suggest an improvement
|
||||
url: https://github.com/sjackson0109/rdpwrap/discussions/new?category=ideas
|
||||
about: Have an idea that is not ready to be a formal feature request? Start a discussion.
|
||||
- name: General discussion
|
||||
url: https://github.com/sjackson0109/rdpwrap/discussions/new?category=general
|
||||
about: Anything that does not fit the other categories.
|
||||
- name: Existing INI offset database
|
||||
url: https://github.com/sjackson0109/rdpwrap/releases/latest
|
||||
about: Check whether your Windows build is already covered before opening an INI request.
|
||||
- name: Original project (stascorp, archived)
|
||||
url: https://github.com/stascorp/rdpwrap
|
||||
about: Historical reference only — no longer maintained.
|
||||
- name: sergiye/rdpWrapper (C# rewrite)
|
||||
url: https://github.com/sergiye/rdpWrapper
|
||||
about: Alternative C# implementation with auto-offset generation.
|
||||
@ -0,0 +1,41 @@
|
||||
name: Feature Request
|
||||
description: Suggest an improvement or new capability.
|
||||
labels: [enhancement]
|
||||
body:
|
||||
- type: textarea
|
||||
id: problem
|
||||
attributes:
|
||||
label: Problem or motivation
|
||||
description: What problem does this feature solve? Who is affected?
|
||||
placeholder: "At the moment there is no way to ..."
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: solution
|
||||
attributes:
|
||||
label: Proposed solution
|
||||
description: Describe the behaviour or change you would like to see.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: alternatives
|
||||
attributes:
|
||||
label: Alternatives considered
|
||||
description: Any other approaches you have thought about (optional).
|
||||
|
||||
- type: dropdown
|
||||
id: component
|
||||
attributes:
|
||||
label: Component this relates to
|
||||
options:
|
||||
- rdpwrap.dll (C++ wrapper library)
|
||||
- RDPWInst / installer (Delphi)
|
||||
- rdpWrapper GUI (sergiye C# app)
|
||||
- rdpwrap.ini (offset database)
|
||||
- CI/CD pipelines
|
||||
- Documentation
|
||||
- Other
|
||||
validations:
|
||||
required: true
|
||||
@ -0,0 +1,71 @@
|
||||
name: INI Update Request — New Windows Build
|
||||
description: Your termsrv.dll version is not in the INI database and auto-generation failed.
|
||||
labels: [ini-update]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Use this template when RDPConf shows **Not supported (red)** and the automatic
|
||||
offset-generation step in the installer could not resolve your build.
|
||||
|
||||
If you have the technical skills, please consider following
|
||||
[HOW-TO-ADD-NEW-WINDOWS-BUILDS.md](../docs/HOW-TO-ADD-NEW-WINDOWS-BUILDS.md)
|
||||
and submitting a pull request instead.
|
||||
|
||||
- type: input
|
||||
id: termsrv_version
|
||||
attributes:
|
||||
label: termsrv.dll version
|
||||
description: "Run: (Get-Item C:\\Windows\\System32\\termsrv.dll).VersionInfo.FileVersion"
|
||||
placeholder: "10.0.26100.3915"
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: windows_version
|
||||
attributes:
|
||||
label: Windows version string
|
||||
description: "Run: winver — paste the full string"
|
||||
placeholder: "Windows 11 Version 24H2 (OS Build 26100.3915)"
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: dropdown
|
||||
id: architecture
|
||||
attributes:
|
||||
label: System architecture
|
||||
options:
|
||||
- x64 (64-bit)
|
||||
- x86 (32-bit)
|
||||
- ARM64
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: dropdown
|
||||
id: auto_gen
|
||||
attributes:
|
||||
label: Did the auto-generation step run?
|
||||
description: The installer prints a message about TryAutoGenerateOffsets during install or update.
|
||||
options:
|
||||
- "Yes — it ran but failed with an error"
|
||||
- "Yes — it ran and reported success, but wrapper still shows Not supported"
|
||||
- "No — it was skipped or I installed offline"
|
||||
- Not sure
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: auto_gen_output
|
||||
attributes:
|
||||
label: Auto-generation output (if any)
|
||||
description: Paste the console output from the installer relating to offset generation.
|
||||
render: text
|
||||
|
||||
- type: textarea
|
||||
id: ini_section
|
||||
attributes:
|
||||
label: Generated INI section (if available)
|
||||
description: |
|
||||
If you ran RDPWrapOffsetFinder manually, paste the output here.
|
||||
Run: .\RDPWrapOffsetFinder.exe C:\Windows\System32\termsrv.dll
|
||||
render: ini
|
||||
@ -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,782 @@
|
||||
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/**'
|
||||
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: ${{ github.workflow }}-${{ github.ref_name }}
|
||||
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'; SlnPlatform = 'x64'; OutDir = 'x64/Release'; ZydisCfg = 'Release MD DLL'; ZydisBin = 'ReleaseX64'; Arch = 'x64' }
|
||||
@{ Platform = 'Win32'; SlnPlatform = 'x86'; OutDir = 'Release'; 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 via .sln so $(SolutionDir) resolves to the submodule root
|
||||
# where zydis/ lives - vcxproj AdditionalIncludeDirectories and
|
||||
# AdditionalDependencies both use $(SolutionDir)\zydis\... and
|
||||
# fail with C1083 when the vcxproj is targeted directly.
|
||||
# NB: the solution uses x86/x64 platform names (not Win32) - use SlnPlatform.
|
||||
msbuild src-csharp/RDPOffsetFinder/RDPWrapOffsetFinder.sln `
|
||||
/t:RDPWrapOffsetFinder `
|
||||
/p:Configuration=Release `
|
||||
/p:Platform="$($cfg.SlnPlatform)" `
|
||||
/p:PlatformToolset=v143 `
|
||||
/v:minimal
|
||||
|
||||
$arch = $cfg.Arch
|
||||
# Win32 OutDir = Release\ (no platform prefix); x64 OutDir = x64\Release\
|
||||
$exeSrc = "src-csharp/RDPOffsetFinder/$($cfg.OutDir)/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 }}'
|
||||
# MSI ProductVersion requires major<256; strip century: "2026.4.2" -> "26.4.2"
|
||||
$msiVer = $ver.Substring(2)
|
||||
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=$msiVer `
|
||||
/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.
|
||||
|
||||
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,63 @@
|
||||
name: Build C++ DLL
|
||||
|
||||
# Trigger on:
|
||||
# - PRs targeting main/master that touch C++ source (compile check before merge)
|
||||
# - Version tag pushes (e.g. v1.7.0)
|
||||
# - Manual dispatch
|
||||
on:
|
||||
pull_request:
|
||||
branches: [main, master]
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
# ── Build x64 and Win32 release DLLs ─────────────────────────────────────────
|
||||
build:
|
||||
runs-on: windows-2022
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- platform: x64
|
||||
msbuild_platform: x64
|
||||
- platform: x86
|
||||
msbuild_platform: Win32
|
||||
- platform: arm64
|
||||
msbuild_platform: ARM64
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Setup MSBuild
|
||||
uses: microsoft/setup-msbuild@v3
|
||||
|
||||
# Override the v120 platform toolset with the VS 2022 toolset (v143).
|
||||
# The rdpwrap C++ source is standard Win32 API code and compiles cleanly
|
||||
# with any modern MSVC version.
|
||||
- name: Build rdpwrap.dll (${{ matrix.platform }})
|
||||
working-directory: src-x86-x64-Fusix
|
||||
run: |
|
||||
msbuild RDPWrap.vcxproj `
|
||||
/p:Configuration=Release `
|
||||
/p:Platform="${{ matrix.msbuild_platform }}" `
|
||||
/p:PlatformToolset=v143 `
|
||||
/p:WindowsTargetPlatformVersion=10.0
|
||||
|
||||
- name: Upload ${{ matrix.platform }} artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: rdpwrap-dll-${{ matrix.platform }}
|
||||
path: src-x86-x64-Fusix/Release/${{ matrix.platform }}/rdpwrap.dll
|
||||
if-no-files-found: error
|
||||
|
||||
# NOTE: Full GitHub Releases (including rdpwrap DLLs) 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.
|
||||
@ -0,0 +1,139 @@
|
||||
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]
|
||||
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,70 @@
|
||||
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]
|
||||
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,97 @@
|
||||
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]
|
||||
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
|
||||
sln_platform: x64
|
||||
zydis_cfg: "Release MD DLL"
|
||||
zydis_bin: ReleaseX64
|
||||
out_dir: x64/Release
|
||||
- platform: Win32
|
||||
sln_platform: x86
|
||||
zydis_cfg: "Release MD DLL"
|
||||
zydis_bin: ReleaseX86
|
||||
out_dir: Release
|
||||
|
||||
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 via .sln so $(SolutionDir) resolves to the submodule root where
|
||||
# AdditionalIncludeDirectories and AdditionalDependencies reference zydis/
|
||||
# NB: the solution uses x86/x64 platform names (not Win32) - use sln_platform.
|
||||
- name: Build RDPWrapOffsetFinder (${{ matrix.platform }})
|
||||
working-directory: src-csharp/RDPOffsetFinder
|
||||
run: |
|
||||
msbuild RDPWrapOffsetFinder.sln `
|
||||
/t:RDPWrapOffsetFinder `
|
||||
/p:Configuration=Release `
|
||||
/p:Platform="${{ matrix.sln_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"
|
||||
# Win32 OutDir = Release\ (no platform prefix); x64 OutDir = x64\Release\
|
||||
$exe = "$root/${{ matrix.out_dir }}/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.
|
||||
@ -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,457 @@
|
||||
# How to Add Support for New Windows Builds
|
||||
|
||||
This guide explains the technical process for reverse engineering new Windows builds to extract the necessary RDP Wrapper configuration parameters.
|
||||
|
||||
## Overview
|
||||
|
||||
When Microsoft releases new Windows updates, the `termsrv.dll` file changes, and RDP Wrapper needs updated offset configurations to function properly. This document covers **two paths**:
|
||||
|
||||
1. **[Quick path — RDPWrapOffsetFinder (recommended)](#quick-path-rdpwrapoffsetfinder-automated):** the compiled tool bundled in every release can generate a valid INI section in seconds on most builds.
|
||||
2. **[Manual path — disassembly and reverse engineering](#manual-path-disassembly-and-reverse-engineering):** used when the automated tool cannot find all offsets for a new or unusual build.
|
||||
|
||||
---
|
||||
|
||||
## Quick path: RDPWrapOffsetFinder (automated)
|
||||
|
||||
> **TL;DR** — run the tool, copy the generated INI block, open a PR.
|
||||
|
||||
RDPWrapOffsetFinder (by **llccd**, bundled in every release as `RDPWrapOffsetFinder_x64.exe` and `RDPWrapOffsetFinder_x86.exe`) performs automated static analysis on the installed `termsrv.dll` and emits a ready-to-use INI section.
|
||||
|
||||
### Step A: Run RDPWrapOffsetFinder
|
||||
|
||||
```powershell
|
||||
# Locate termsrv.dll version (so you know what tag to add)
|
||||
(Get-Item C:\Windows\System32\termsrv.dll).VersionInfo.ProductVersion
|
||||
|
||||
# Run the tool (run as Administrator; it reads the locked DLL via sharing flags)
|
||||
.\RDPWrapOffsetFinder_x64.exe
|
||||
```
|
||||
|
||||
On success the tool prints an INI block similar to:
|
||||
|
||||
```ini
|
||||
[10.0.26100.3915]
|
||||
SingleUserPatch.x64=1
|
||||
SingleUserOffset.x64=0A1B2C
|
||||
...
|
||||
```
|
||||
|
||||
If the tool exits with `Unsupported version`, try the nosym (no-symbols) variant:
|
||||
|
||||
```powershell
|
||||
.\RDPWrapOffsetFinder_nosym_x64.exe
|
||||
```
|
||||
|
||||
### Step B: Use the autoupdate helper (optional)
|
||||
|
||||
The `autoupdate/` directory in `src-csharp/RDPOffsetFinder/` contains a convenience script that wraps the tool, appends the output to a local copy of `rdpwrap.ini`, and validates the result:
|
||||
|
||||
```batch
|
||||
cd src-csharp\RDPOffsetFinder\RDPWrapOffsetFinder\autoupdate
|
||||
autoupdate.bat
|
||||
```
|
||||
|
||||
The script will:
|
||||
1. Run `RDPWrapOffsetFinder.exe` against the current system's `termsrv.dll`.
|
||||
2. Merge the new section into `rdpwrap_template.ini`.
|
||||
3. Print the diff so you can review before committing.
|
||||
|
||||
### Step C: Validate the generated block
|
||||
|
||||
Before opening a pull request:
|
||||
|
||||
```powershell
|
||||
# Quick sanity check — restart Terminal Services with the new INI
|
||||
net stop TermService
|
||||
Copy-Item .\rdpwrap_updated.ini "C:\Program Files\RDP Wrapper\rdpwrap.ini"
|
||||
net start TermService
|
||||
|
||||
# Visual validation
|
||||
.\RDPCheck.exe # should show "Installed", "Listening", and "Supported"
|
||||
```
|
||||
|
||||
### Step D: Submit a pull request
|
||||
|
||||
1. Add (or update) the version block in `msi/rdpwrap.ini`.
|
||||
2. Commit with message: `ini: add support for 10.0.XXXXX.YYYY`.
|
||||
3. Open a pull request against the `main` branch using the [bug_report](.github/ISSUE_TEMPLATE/bug_report.yml) template.
|
||||
Include: the output of `RDPWrapOffsetFinder.exe`, your `termsrv.dll` version, and a screenshot of `RDPCheck.exe` showing "Supported".
|
||||
|
||||
> **Security note:** Never attach the actual `termsrv.dll` binary — this would infringe Microsoft's licence. The version string and SHA-256 hash are sufficient for reproducibility.
|
||||
|
||||
---
|
||||
|
||||
## Manual path: disassembly and reverse engineering
|
||||
|
||||
Use this path when:
|
||||
- RDPWrapOffsetFinder cannot find all offsets (prints partial output or crashes), **or**
|
||||
- You want to understand *why* a patch works, not just *where* to apply it.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
### Required Tools
|
||||
|
||||
**Disassemblers (Choose one):**
|
||||
- **Ghidra** (Free, recommended) - NSA's reverse engineering tool
|
||||
- **IDA Pro** (Commercial) - Industry standard
|
||||
- **x64dbg** (Free) - Good for dynamic analysis
|
||||
- **Radare2** (Free) - Command-line focused
|
||||
|
||||
**Supporting Tools:**
|
||||
- **HxD** or similar hex editor
|
||||
- **PE Explorer** - For PE structure analysis
|
||||
- **Process Monitor** - Runtime file/registry monitoring
|
||||
- **API Monitor** - Function call tracing
|
||||
- **RDPCheck.exe** - For testing configurations
|
||||
|
||||
### Required Knowledge
|
||||
|
||||
- Assembly language (x86/x64)
|
||||
- PE file format basics
|
||||
- Windows API understanding
|
||||
- Basic cryptography concepts
|
||||
|
||||
## Step 1: Obtain the Target File
|
||||
|
||||
### Extract termsrv.dll
|
||||
|
||||
```powershell
|
||||
# Navigate to System32 directory
|
||||
cd C:\Windows\System32
|
||||
|
||||
# Copy termsrv.dll to analysis directory
|
||||
copy termsrv.dll C:\Analysis\termsrv.dll
|
||||
|
||||
# Get file version information
|
||||
Get-ItemProperty C:\Analysis\termsrv.dll | Select-Object VersionInfo
|
||||
```
|
||||
|
||||
### Determine Version Number
|
||||
|
||||
```powershell
|
||||
# PowerShell method
|
||||
(Get-Item C:\Analysis\termsrv.dll).VersionInfo.ProductVersion
|
||||
|
||||
# Alternative: WMIC method
|
||||
wmic datafile where name="C:\\Windows\\System32\\termsrv.dll" get Version
|
||||
```
|
||||
|
||||
The version format will be: `10.0.XXXXX.YYYY` (e.g., `10.0.26100.7623`)
|
||||
|
||||
## Step 2: Initial Analysis
|
||||
|
||||
### Load in Disassembler
|
||||
|
||||
1. Open termsrv.dll in your chosen disassembler
|
||||
2. Let it complete initial analysis (auto-analysis)
|
||||
3. Examine the import table for key functions
|
||||
4. Identify the main code sections
|
||||
|
||||
### Key Function Identification
|
||||
|
||||
Search for these critical functions that RDP Wrapper needs to patch:
|
||||
|
||||
1. `CSessionArbitrationHelper::IsSingleSessionPerUserEnabled`
|
||||
2. `CDefPolicy::Query`
|
||||
3. `CEnforcementCore::GetInstanceOfTSLicense`
|
||||
4. `CSLQuery::Initialize`
|
||||
|
||||
## Step 3: Finding Function Offsets
|
||||
|
||||
### Method 1: String Reference Analysis
|
||||
|
||||
```
|
||||
1. Search for relevant strings:
|
||||
- "Terminal Services"
|
||||
- "Session"
|
||||
- "Licence"
|
||||
- "Policy"
|
||||
- Error messages related to licensing
|
||||
|
||||
2. Follow cross-references from strings to functions
|
||||
3. Analyse the functions that reference these strings
|
||||
```
|
||||
|
||||
### Method 2: Import Table Analysis
|
||||
|
||||
```
|
||||
1. Examine imported functions:
|
||||
- GetTokenInformation
|
||||
- WinStationQueryInformationW
|
||||
- RegQueryValueExW
|
||||
- Licence-related APIs
|
||||
|
||||
2. Find functions that call these imports
|
||||
3. Trace backwards to find policy validation logic
|
||||
```
|
||||
|
||||
### Method 3: Pattern Matching
|
||||
|
||||
Look for specific assembly patterns that indicate the functions we need to patch:
|
||||
|
||||
#### Single User Patch Pattern
|
||||
```asm
|
||||
; Look for patterns like:
|
||||
BB 01 00 00 00 ; mov ebx, 1 (single session enabled)
|
||||
; Or:
|
||||
B8 01 00 00 00 ; mov eax, 1
|
||||
```
|
||||
|
||||
#### DefPolicy Patch Pattern
|
||||
```asm
|
||||
; Look for licence policy validation:
|
||||
B8 01 00 00 00 ; mov eax, 1 (policy result)
|
||||
89 81 38 06 00 00 ; mov [rcx+638h], eax (store result)
|
||||
; Or similar patterns with different registers
|
||||
```
|
||||
|
||||
## Step 4: Extracting Configuration Parameters
|
||||
|
||||
### Single User Offset
|
||||
|
||||
1. Find `CSessionArbitrationHelper::IsSingleSessionPerUserEnabled`
|
||||
2. Look for the instruction that returns 1 (single session restriction)
|
||||
3. Note the file offset of this instruction
|
||||
4. The patch will change this to return 0 (allow multiple sessions)
|
||||
|
||||
### DefPolicy Offset
|
||||
|
||||
1. Find `CDefPolicy::Query`
|
||||
2. Look for licence validation logic
|
||||
3. Find where it sets the result to indicate "licenced"
|
||||
4. Note the offset for the instruction to patch
|
||||
|
||||
### LocalOnly Offset
|
||||
|
||||
1. Find `CEnforcementCore::GetInstanceOfTSLicense`
|
||||
2. Look for local connection restrictions
|
||||
3. Find the jump/conditional that enforces local-only policy
|
||||
4. Note the offset to patch this restriction
|
||||
|
||||
### SLInit Parameters
|
||||
|
||||
1. Find `CSLQuery::Initialize`
|
||||
2. Analyse the data structure it initializes
|
||||
3. Find the memory offsets for these fields:
|
||||
- `bInitialized`
|
||||
- `bServerSku`
|
||||
- `lMaxUserSessions`
|
||||
- `bAppServerAllowed`
|
||||
- `bRemoteConnAllowed`
|
||||
- `bMultimonAllowed`
|
||||
- `ulMaxDebugSessions`
|
||||
- `bFUSEnabled`
|
||||
|
||||
## Step 5: Creating the Configuration
|
||||
|
||||
### Basic INI Structure
|
||||
|
||||
```ini
|
||||
[10.0.XXXXX.YYYY]
|
||||
; Single user session patch
|
||||
SingleUserPatch.x64=1
|
||||
SingleUserOffset.x64=OFFSET_HEX
|
||||
SingleUserCode.x64=PATCH_CODE
|
||||
|
||||
; Licence policy patch
|
||||
DefPolicyPatch.x64=1
|
||||
DefPolicyOffset.x64=OFFSET_HEX
|
||||
DefPolicyCode.x64=PATCH_CODE
|
||||
|
||||
; Local-only restriction patch
|
||||
LocalOnlyPatch.x64=1
|
||||
LocalOnlyOffset.x64=OFFSET_HEX
|
||||
LocalOnlyCode.x64=PATCH_CODE
|
||||
|
||||
; Software licensing hook
|
||||
SLInitHook.x64=1
|
||||
SLInitOffset.x64=OFFSET_HEX
|
||||
SLInitFunc.x64=New_CSLQuery_Initialize
|
||||
|
||||
[10.0.XXXXX.YYYY-SLInit]
|
||||
bServerSku.x64=OFFSET_HEX
|
||||
bRemoteConnAllowed.x64=OFFSET_HEX
|
||||
bFUSEnabled.x64=OFFSET_HEX
|
||||
bAppServerAllowed.x64=OFFSET_HEX
|
||||
bMultimonAllowed.x64=OFFSET_HEX
|
||||
lMaxUserSessions.x64=OFFSET_HEX
|
||||
ulMaxDebugSessions.x64=OFFSET_HEX
|
||||
bInitialized.x64=OFFSET_HEX
|
||||
```
|
||||
|
||||
### Common Patch Codes
|
||||
|
||||
```ini
|
||||
; Available patch codes (defined in [PatchCodes] section):
|
||||
Zero=00 ; Set to zero
|
||||
nop=90 ; No operation
|
||||
jmpshort=EB ; Short jump
|
||||
mov_eax_1_nop_2=B8010000009090 ; mov eax,1 + 2 NOPs
|
||||
CDefPolicy_Query_eax_rcx_jmp=B80001000089813806000090EB ; Policy bypass
|
||||
```
|
||||
|
||||
## Step 6: Testing and Validation
|
||||
|
||||
### Initial Testing
|
||||
|
||||
1. Create a test INI file with your calculated offsets
|
||||
2. Back up the original rdpwrap.ini
|
||||
3. Replace with your test configuration
|
||||
4. Restart Terminal Services
|
||||
5. Run RDPCheck.exe to verify status
|
||||
|
||||
### Dynamic Analysis
|
||||
|
||||
1. Use x64dbg to attach to the running termsrv.exe process
|
||||
2. Set breakpoints at your calculated offsets
|
||||
3. Verify that your patches are being applied correctly
|
||||
3. Monitor for any crashes or unexpected behaviour
|
||||
|
||||
### Validation Steps
|
||||
|
||||
```powershell
|
||||
# Stop Terminal Services
|
||||
net stop TermService
|
||||
|
||||
# Apply new configuration
|
||||
copy test_rdpwrap.ini C:\Program Files\RDP Wrapper\rdpwrap.ini
|
||||
|
||||
# Start Terminal Services
|
||||
net start TermService
|
||||
|
||||
# Test with RDPCheck
|
||||
RDPCheck.exe
|
||||
|
||||
# Test actual RDP connection
|
||||
mstsc /v:localhost
|
||||
```
|
||||
|
||||
## Step 7: Documentation and Sharing
|
||||
|
||||
### Document Your Findings
|
||||
|
||||
Create a detailed report including:
|
||||
- Windows build version and SHA256 of termsrv.dll
|
||||
- Methodology used
|
||||
- Specific offsets found
|
||||
- Testing results
|
||||
- Any challenges encountered
|
||||
|
||||
### Share with Community
|
||||
|
||||
1. Post your configuration in a GitHub issue
|
||||
2. Include the termsrv.dll file (zipped) for verification
|
||||
3. Provide testing evidence (screenshots from RDPCheck)
|
||||
4. Document any system-specific requirements
|
||||
|
||||
## Common Challenges
|
||||
|
||||
### Address Space Layout Randomization (ASLR)
|
||||
|
||||
Modern Windows uses ASLR, but the relative offsets within the DLL remain constant. Always work with file offsets, not memory addresses.
|
||||
|
||||
### Compiler Optimisations
|
||||
|
||||
Microsoft's compiler optimisations can:
|
||||
- Inline functions
|
||||
- Reorder code
|
||||
- Change calling conventions
|
||||
- Merge similar functions
|
||||
|
||||
### Code Signing
|
||||
|
||||
Windows verifies code signatures, so:
|
||||
- Patches must be applied at runtime, not to the file
|
||||
- Use the RDP Wrapper's hooking mechanism
|
||||
- Never modify the original termsrv.dll
|
||||
|
||||
### Function Variations
|
||||
|
||||
The same logical function might be implemented differently across builds:
|
||||
- Different assembly patterns
|
||||
- Different register usage
|
||||
- Inlined vs separate functions
|
||||
|
||||
## Advanced Techniques
|
||||
|
||||
### Comparative Analysis
|
||||
|
||||
When analysing a new build:
|
||||
1. Compare with a known working build
|
||||
2. Look for similar patterns and structures
|
||||
3. Use diff tools on disassembled code
|
||||
|
||||
### Automated Pattern Detection
|
||||
|
||||
Some community members have created scripts to:
|
||||
- Search for common assembly patterns
|
||||
- Compare function signatures
|
||||
- Suggest likely offset candidates
|
||||
|
||||
### Binary Diffing
|
||||
|
||||
Tools like BinDiff can help identify:
|
||||
- Changed functions between builds
|
||||
- Similar code blocks
|
||||
- Function renaming/reorganisation
|
||||
|
||||
## Community Resources
|
||||
|
||||
### Trusted Contributors
|
||||
|
||||
Community members known for accurate analysis:
|
||||
- **@Fabliv** - Consistently provides verified configurations
|
||||
- **@sebaxakerhtc** - Regular contributor with detailed analysis
|
||||
- **@maxpiva** - Historical configurations and tools
|
||||
|
||||
### Useful Repositories
|
||||
|
||||
- Main project: `stascorp/rdpwrap`
|
||||
- Community tools: Various forks with analysis scripts
|
||||
- Configuration databases: Community-maintained INI collections
|
||||
|
||||
## Contributing Your Analysis
|
||||
|
||||
### GitHub Issue Format
|
||||
|
||||
When posting a new configuration:
|
||||
|
||||
```markdown
|
||||
## Windows Build: 10.0.XXXXX.YYYY
|
||||
|
||||
**System Information:**
|
||||
- Edition: Windows 11 Pro/Home/Enterprise
|
||||
- Architecture: x64
|
||||
- Installation: Clean/Update from X.X.X
|
||||
|
||||
**Analysis Results:**
|
||||
[Paste your INI configuration here]
|
||||
|
||||
**Verification:**
|
||||
- ✅ RDPCheck shows "Installed" and "Listening"
|
||||
- ✅ Multiple simultaneous connections tested
|
||||
- ✅ No crashes or stability issues
|
||||
|
||||
**Files:**
|
||||
[Attach termsrv.dll.zip]
|
||||
```
|
||||
|
||||
### Testing by Others
|
||||
|
||||
Before a configuration is accepted:
|
||||
1. Multiple community members should test
|
||||
2. Verify on different system configurations
|
||||
3. Confirm no regressions on existing functionality
|
||||
4. Test edge cases (different user accounts, domain environments)
|
||||
|
||||
## Conclusion
|
||||
|
||||
Adding support for new Windows builds requires:
|
||||
- Technical reverse engineering skills
|
||||
- Patience for trial-and-error testing
|
||||
- Community collaboration for verification
|
||||
- Detailed documentation for maintainability
|
||||
|
||||
While this process cannot be easily automated due to Microsoft's security measures and varying compilation patterns, the community has developed efficient workflows that typically produce working configurations within days of new Windows releases.
|
||||
|
||||
The key to success is methodical analysis, thorough testing, and collaboration with the experienced community members who have mastered this process.
|
||||
@ -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>
|
||||