Skip to content

Build and Publish

hifihedgehog edited this page Apr 18, 2026 · 32 revisions

Build and Publish

Build, publish, and release reference for PadForge.


Solution Structure

PadForge.sln
├── PadForge.Engine/          Class library (net10.0-windows)
│   ├── Common/               SDL3 P/Invoke, input types, device wrappers
│   ├── Data/                 PadSetting, UserDevice, UserSetting
│   └── Properties/           AssemblyInfo.cs
│
├── PadForge.App/             WPF application (net10.0-windows10.0.26100.0)
│   ├── Common/               SettingsManager, DriverInstaller, InputManager pipeline
│   ├── Views/                XAML pages and code-behind
│   ├── ViewModels/           MVVM ViewModels
│   ├── Services/             InputService, SettingsService, DeviceService, etc.
│   ├── WebAssets/             HTML/CSS/JS for browser virtual controller
│   ├── Models3D/             3D model classes + OBJ meshes (3DModels/)
│   ├── Models2D/             2D overlay layout classes
│   ├── 2DModels/             PNG overlay images (DS4/, XBOX360/)
│   ├── Converter/            WPF value converters
│   ├── Controls/             Custom controls (RangeSlider)
│   ├── Resources/            Icons, SDL3 DLL, embedded driver installers, localization
│   └── Properties/           AssemblyInfo.cs
│
├── nuget-local/              Local NuGet source (MIDI Services SDK)
├── nuget.config               Registers nuget.org + nuget-local/ as package sources
│
└── tools/
    ├── DsuDiag/              DSU/Cemuhook diagnostic client
    ├── vJoy/Test/            vJoy device creation + input test
    ├── vJoy/FfbTest/         DirectInput FFB test tool
    ├── vJoy/SDK/             vJoy SDK headers and libraries
    ├── deploy.ps1            Build + deploy to C:\PadForge
    ├── deploy_and_restart.ps1  Deploy + restart running instance
    ├── capture_screenshots.ps1 Automated wiki screenshot capture
    └── (50+ diagnostic/test scripts for vJoy, UI automation, etc.)

Prerequisites

Requirement Version Notes
.NET SDK 10.0+ net10.0-windows10.0.26100.0 target framework
Windows SDK 10.0.26100.57 Set via WindowsSdkPackageVersion in App csproj
Windows 10/11 x64 WPF + Windows-specific P/Invoke
Visual Studio (optional) 2022+ Not required; dotnet CLI suffices

Minimum supported OS: Windows 10 1809 (build 17763), set via SupportedOSPlatformVersion in the App csproj.

All native DLLs, driver installers, and model assets are included in the repository. No external downloads needed.

Build Commands

Debug Build

dotnet build -c Debug

Output: PadForge.App/bin/Debug/net10.0-windows10.0.26100.0/win-x64/

Suitable for development and debugging. Not deployable. Produces loose DLLs and requires the .NET runtime.

Release Build

dotnet build -c Release

Output: PadForge.App/bin/Release/net10.0-windows10.0.26100.0/win-x64/

Not deployable. Use dotnet publish for deployment.

Publish (Portable Deployment)

dotnet publish -c Release
# or explicitly:
dotnet publish PadForge.App/PadForge.App.csproj -c Release

Output: PadForge.App/bin/Release/net10.0-windows10.0.26100.0/win-x64/publish/

File Description
PadForge.exe ~102 MB, single-file self-contained
SDL3.dll SDL3 native library (content item)
libusb-1.0.dll WinUSB for Switch 2 Pro Controller (content item)
gamecontrollerdb_padforge.txt Custom SDL gamepad mappings (content item)

Critical: Always use dotnet publish for deployment, never dotnet build. The project is configured for single-file self-contained publish. dotnet build produces non-functional multi-file output.

Single-File Publish Configuration

The App csproj defines these publish properties:

<!-- Single-file portable publish: dotnet publish -c Release -->
<PropertyGroup>
  <RuntimeIdentifier>win-x64</RuntimeIdentifier>
  <PublishSingleFile>true</PublishSingleFile>
  <SelfContained>true</SelfContained>
  <IncludeNativeLibrariesForSelfExtract>true</IncludeNativeLibrariesForSelfExtract>
  <EnableCompressionInSingleFile>true</EnableCompressionInSingleFile>
  <DebugType>embedded</DebugType>
</PropertyGroup>
Property Value Effect
RuntimeIdentifier win-x64 Targets 64-bit Windows only
PublishSingleFile true Bundles all managed assemblies into one exe
SelfContained true Embeds .NET 10 runtime (no install needed on target)
IncludeNativeLibrariesForSelfExtract true Packs native DLLs into the exe; extracted to temp dir at runtime
EnableCompressionInSingleFile true Compresses the bundle to reduce exe size
DebugType embedded Embeds debug symbols in assemblies (no .pdb files); preserves stack traces for crash diagnostics

Note: RuntimeIdentifier is set unconditionally (not only during publish), so dotnet build also targets win-x64 and produces a RID-specific output folder.

Project Configuration Details

PadForge.App.csproj

<PropertyGroup>
  <OutputType>WinExe</OutputType>
  <TargetFramework>net10.0-windows10.0.26100.0</TargetFramework>
  <SupportedOSPlatformVersion>10.0.17763.0</SupportedOSPlatformVersion>
  <WindowsSdkPackageVersion>10.0.26100.57</WindowsSdkPackageVersion>
  <RootNamespace>PadForge</RootNamespace>
  <AssemblyName>PadForge</AssemblyName>
  <UseWPF>true</UseWPF>
  <UseWindowsForms>true</UseWindowsForms>  <!-- For NotifyIcon system tray -->
  <LangVersion>latest</LangVersion>
  <Nullable>disable</Nullable>
  <ImplicitUsings>enable</ImplicitUsings>
  <GenerateAssemblyInfo>false</GenerateAssemblyInfo>  <!-- Manual AssemblyInfo.cs -->
</PropertyGroup>

Key settings:

Property Value Purpose
TargetFramework net10.0-windows10.0.26100.0 .NET 10 + Windows SDK 26100 (Win 11 24H2). Enables WinRT APIs.
SupportedOSPlatformVersion 10.0.17763.0 Minimum OS: Windows 10 1809
WindowsSdkPackageVersion 10.0.26100.57 Pins Windows SDK NuGet package version
UseWindowsForms true NotifyIcon system tray only (not WinForms UI)
GenerateAssemblyInfo false Uses manual Properties/AssemblyInfo.cs
Nullable disable Nullable reference types not used

WinForms implicit using removal. Prevents System.Drawing / System.Windows.Forms ambiguity with WPF types:

<ItemGroup>
  <Using Remove="System.Drawing" />
  <Using Remove="System.Windows.Forms" />
</ItemGroup>

PadForge.Engine.csproj

<PropertyGroup>
  <TargetFramework>net10.0-windows</TargetFramework>
  <RootNamespace>PadForge.Engine</RootNamespace>
  <AssemblyName>PadForge.Engine</AssemblyName>
  <LangVersion>latest</LangVersion>
  <Nullable>disable</Nullable>
  <ImplicitUsings>enable</ImplicitUsings>
  <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
  <DebugType>embedded</DebugType>
</PropertyGroup>

The Engine targets net10.0-windows (no specific SDK version needed). Zero NuGet dependencies. All SDL3 and system interop uses raw [DllImport] P/Invoke.

Assembly Versioning

Versions are set manually in Properties/AssemblyInfo.cs (both projects set GenerateAssemblyInfo to false).

Project File Current Version
PadForge.App PadForge.App/Properties/AssemblyInfo.cs 2.1.0.0
PadForge.Engine PadForge.Engine/Properties/AssemblyInfo.cs 2.0.0.0

App and Engine versions are independent. App tracks user-facing releases, Engine tracks breaking API changes. Both AssemblyInfo files also include metadata attributes (AssemblyTitle, AssemblyCompany, AssemblyCopyright, Guid, etc.).

Important: Do not bump versions unless preparing a release. GitHub Releases use git tag names (e.g., v2.0.0-beta3) as the version identifier, not AssemblyVersion.

NuGet Dependencies

PadForge.App

Package Version Source Purpose
ModernWpfUI 0.9.6 nuget.org Fluent Design theme for WPF (dark mode, NavigationView, controls)
HelixToolkit.Core.Wpf 2.27.3 nuget.org 3D viewport rendering (OBJ model loading, camera, lighting)
CommunityToolkit.Mvvm 8.2.2 nuget.org MVVM: ObservableObject, RelayCommand, [ObservableProperty]
Nefarius.ViGEm.Client 1.21.256 nuget.org ViGEm virtual Xbox 360 and DualShock 4 controller client API
NAudio.Wasapi 2.2.1 nuget.org WASAPI loopback audio capture for bass-driven rumble detection
Microsoft.Windows.Devices.Midi2 1.0.16-rc.3.7 nuget-local/ Windows MIDI Services SDK for virtual MIDI device creation

PadForge.Engine

No NuGet dependencies. All SDL3 and system interop is via raw P/Invoke.

Local NuGet Source (nuget-local/)

The Windows MIDI Services SDK is not on nuget.org. The .nupkg is stored locally:

nuget-local/Microsoft.Windows.Devices.Midi2.1.0.16-rc.3.7.nupkg

nuget.config at the solution root registers this folder as a package source:

<configuration>
  <packageSources>
    <add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
    <add key="local" value="nuget-local" />
  </packageSources>
</configuration>

Both nuget.config and nuget-local/ must be present alongside PadForge.sln for dotnet restore to succeed.

Native DLLs (Content Items)

Copied alongside PadForge.exe in the output via Content + CopyToOutputDirectory. Not embedded inside the single-file exe.

SDL3.dll and libusb-1.0.dll

<Content Include="Resources\SDL3\x64\SDL3.dll" Link="SDL3.dll"
         Condition="Exists('Resources\SDL3\x64\SDL3.dll')">
  <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Resources\SDL3\x64\libusb-1.0.dll" Link="libusb-1.0.dll"
         Condition="Exists('Resources\SDL3\x64\libusb-1.0.dll')">
  <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
  • Link="SDL3.dll". Flattens to output root (next to PadForge.exe) instead of preserving the subdirectory path.
  • Condition="Exists(...)". Build succeeds even if the DLL is absent (e.g., fresh clone).
  • SDL3.dll is a custom fork built from SDL3-build/SDL/ with WinUSB support for Switch 2 Pro Controller. See SDL3 Integration for build instructions.
  • libusb-1.0.dll provides WinUSB access for Switch 2 Pro Controller communication.

Source location: PadForge.App/Resources/SDL3/x64/

gamecontrollerdb_padforge.txt

<Content Include="gamecontrollerdb_padforge.txt">
  <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>

Custom SDL gamepad mapping database with PadForge-specific entries (e.g., DualShock 3 via DsHidMini SDF). Loaded by SDL3 at runtime. Community mappings can be submitted via the GitHub issue template.

Source location: PadForge.App/gamecontrollerdb_padforge.txt

Embedded Resources

Compiled into the assembly. Not visible as separate files in the output.

Driver Installers (EmbeddedResource)

<EmbeddedResource Include="Resources\ViGEmBus_1.22.0_x64_x86_arm64.exe" />
<EmbeddedResource Include="Resources\HidHide_1.5.230_x64.exe" />
<EmbeddedResource Include="Resources\vJoyDriver.zip" />
Resource Size Purpose
ViGEmBus_1.22.0_x64_x86_arm64.exe ViGEmBus installer Virtual Xbox 360 / DS4 controller driver
HidHide_1.5.230_x64.exe HidHide installer Hides physical controllers from other applications
vJoyDriver.zip vJoy driver archive Virtual joystick driver (extracted to C:\Program Files\vJoy\)

Extracted at runtime by DriverInstaller.cs when the user clicks Install in Settings. Not needed to run PadForge. Only for optional driver installation.

3D Model Assets (EmbeddedResource)

<!-- 3D controller model assets (adapted from Handheld Companion, CC BY-NC-SA 4.0) -->
<EmbeddedResource Include="3DModels\**\*.obj" />

OBJ mesh files for 3D controller visualization. Loaded at runtime via ControllerModelBase.LoadModel().

Directory Contents
3DModels/XBOX360/ 31 OBJ files (Xbox 360 controller parts)
3DModels/DS4/ 36 OBJ files (DualShock 4 controller parts)

Web Controller Assets (EmbeddedResource)

<!-- Web controller frontend assets (embedded, served by WebControllerServer) -->
<EmbeddedResource Include="WebAssets\**\*" />

HTML/CSS/JS for the browser-based virtual controller. Served by the embedded Kestrel web server at runtime.

File Purpose
WebAssets/index.html Landing page
WebAssets/controller.html Virtual gamepad UI
WebAssets/css/controller.css Gamepad styling
WebAssets/js/controller_client.js WebSocket client logic
WebAssets/js/nipplejs.min.js Virtual joystick library

Localization Strings (EmbeddedResource)

<EmbeddedResource Update="Resources\Strings\Strings.resx">
  <Generator>PublicResXFileCodeGenerator</Generator>
  <LastGenOutput>Strings.Designer.cs</LastGenOutput>
</EmbeddedResource>

Resource files for multilingual UI support:

File Language
Strings.resx English (default/fallback)
Strings.de.resx German
Strings.es.resx Spanish
Strings.fr.resx French
Strings.it.resx Italian
Strings.ja.resx Japanese
Strings.ko.resx Korean
Strings.nl.resx Dutch
Strings.pt-BR.resx Brazilian Portuguese
Strings.zh-Hans.resx Simplified Chinese

The default Strings.resx uses PublicResXFileCodeGenerator to produce Strings.Designer.cs for strongly-typed access via Strings.PropertyName. Satellite .resx files follow standard .NET localization conventions and compile into satellite assemblies automatically.

2D Model Assets (Resource)

<!-- 2D controller model assets (Gamepad-Asset-Pack by AL2009man, MIT license) -->
<Resource Include="2DModels\**\*.png" />

PNG overlay images for 2D controller schematic view (DS4/ and XBOX360/ parts). Uses Resource (not EmbeddedResource) so they load as WPF pack URIs (pack://application:,,,/2DModels/...).

Other Resources

<Resource Include="Resources\Xbox Series Controller - Front.png" />
<Resource Include="Resources\Xbox Series Controller - Top.png" />
<Content Include="Resources\PadForge.ico" />
Resource Purpose
Controller PNGs Mapping UI images
PadForge.ico Application icon (via <ApplicationIcon> in csproj)
Resources/ControllerIcons.xaml XAML vector icon definitions (resource dictionary)

CI/CD (GitHub Actions)

Workflow: .github/workflows/build.yml

Triggers

on:
  push:
    branches: [v2-dev]
  pull_request:
    branches: [v2-dev]
  workflow_dispatch:

Runs on every push/PR to v2-dev and on manual trigger.

Build Steps

  1. Checkout. actions/checkout@v4, fetch-depth: 0 (full history for commit counting)
  2. Setup .NET. actions/setup-dotnet@v4, dotnet-version: 10.x
  3. Publish. dotnet publish PadForge.App/PadForge.App.csproj -c Release
  4. Upload artifact. Publish directory as PadForge_r{COMMIT_COUNT}@{COMMIT_SHORT}

Automatic Releases (push to v2-dev only)

On push (not PR), the workflow creates two GitHub releases:

Release Behavior
archive-v2-dev Accumulates every build as PadForge_r{N}@{SHA}.zip. Uses --clobber for latest upload; preserves tag across builds.
latest-v2-dev Recreated on every push (old release deleted first). Contains PadForge.zip with the most recent build. The "always current" download link.

Both are marked --prerelease and cross-link to each other in their notes.

Artifact Naming

Format: PadForge_r{COMMIT_COUNT}@{7-char COMMIT_SHA} (e.g., PadForge_r342@f35bb36)

Environment Variables

env:
  DOTNET_NOLOGO: true
  DOTNET_CLI_TELEMETRY_OPTOUT: true

Formal Releases

Created manually via gh release create with a version tag (e.g., v2.0.0, v2.1.0-beta1). See Release Workflow below.

Deployment

PadForge is portable. No installer required.

Local Deployment

Copy the publish output to any folder:

cp PadForge.App/bin/Release/net10.0-windows10.0.26100.0/win-x64/publish/PadForge.exe C:\PadForge\PadForge.exe

Required companion files (placed automatically by dotnet publish):

File Required Purpose
SDL3.dll Yes SDL3 native library
libusb-1.0.dll Yes WinUSB for Switch 2 Pro Controller
gamecontrollerdb_padforge.txt Optional SDL3 gamepad mappings (enhances device recognition)

Development Deploy Scripts

tools/deploy.ps1              # Build, publish, copy to C:\PadForge
tools/deploy_and_restart.ps1  # Deploy + kill running instance + restart

First Run

PadForge creates PadForge.xml alongside the executable to store settings, mappings, and profiles.

If vJoy is installed, PadForge requests administrator privileges on startup (auto-elevation in App.xaml.cs).

Release Workflow

1. Update Version (if needed)

  • PadForge.App/Properties/AssemblyInfo.cs. AssemblyVersion + AssemblyFileVersion
  • PadForge.Engine/Properties/AssemblyInfo.cs. Only if Engine API changed

2. Build

dotnet publish -c Release

3. Deploy and Test

cp PadForge.App/bin/Release/net10.0-windows10.0.26100.0/win-x64/publish/PadForge.exe C:\PadForge\PadForge.exe

Run C:\PadForge\PadForge.exe and verify functionality.

4. Commit and Push

git add -A
git commit -m "Release vX.Y.Z"
git push

5. Create Binary Zip

cd PadForge.App/bin/Release/net10.0-windows10.0.26100.0/win-x64/publish
zip -r PadForge-vX.Y.Z-win-x64.zip .

6. Create GitHub Release

gh release create vX.Y.Z --title "PadForge vX.Y.Z" --notes "Release notes here"
gh release upload vX.Y.Z PadForge-vX.Y.Z-win-x64.zip

Use --prerelease for beta/RC releases. Use --latest for the default download.

Diagnostic Tools (tools/)

Standalone diagnostic utilities and development scripts.

Tool Command Framework Dependencies Purpose
DsuDiag cd tools/DsuDiag && dotnet run net8.0 None Real-time DSU/Cemuhook UDP client for verifying gyro/accel axis mapping
VJoyTest cd tools/vJoy/Test && dotnet run -- axes=6 buttons=11 povs=1 net8.0-windows (win-x64) None (P/Invoke) Creates vJoy device, runs input test with WinMM readback
FfbTest cd tools/vJoy/FfbTest && dotnet run net8.0 SharpDX 4.2.0, SharpDX.DirectInput 4.2.0 DirectInput FFB test (ConstantForce + Sine). Uses HWND_MESSAGE for Exclusive cooperative level.

Development Scripts

Script Purpose
deploy.ps1 Build + publish + copy to C:\PadForge
deploy_and_restart.ps1 Deploy + kill running instance + restart
capture_screenshots.ps1 Automated wiki screenshot capture
capture_pads.ps1 Screenshot the Pads/mapping page
capture_macros.ps1 Screenshot the Macros page
capture_3d_2d.ps1 Screenshot 3D/2D controller views
cleanup_vjoy.ps1 Remove orphaned vJoy device nodes
check_vjoy_state.ps1 Diagnostic: inspect vJoy driver and device state

The remaining ~50 scripts are ad-hoc utilities for vJoy lifecycle, UI automation testing, and driver debugging.

Troubleshooting Build Issues

Issue Cause Fix
SDL3.dll not found at runtime DLL not in Resources\SDL3\x64\ Download from SDL3 releases or build from fork
MIDI package not found nuget.config missing or nuget-local/ folder missing Ensure both are present at solution root
Missing WPF types Using dotnet build instead of dotnet publish for deployment Always use dotnet publish -c Release for deployment
System.Drawing ambiguity WinForms implicit usings conflict with WPF Ensure <Using Remove="System.Drawing" /> is in csproj
HelixToolkit errors Package restore failed Run dotnet restore first
Large exe size (~102 MB) Expected. Self-contained with bundled .NET runtime EnableCompressionInSingleFile already enabled
CI build fails .NET 10 SDK not available in runner Check actions/setup-dotnet version supports .NET 10 preview
vJoy P/Invoke DllNotFoundException vJoy driver not installed Expected. App degrades gracefully without vJoy

See Also

Clone this wiki locally