-
Notifications
You must be signed in to change notification settings - Fork 6
Settings and Serialization
Developer reference for PadForge's settings persistence layer: XML file format, data models, SettingsManager, and SettingsService.
Source files:
-
PadForge.App/Services/SettingsService.cs-- XML load/save, data DTOs -
PadForge.App/Common/SettingsManager.cs-- static thread-safe collections -
PadForge.Engine/Data/PadSetting.cs-- mapping configuration model -
PadForge.Engine/Data/UserDevice.cs-- physical device record -
PadForge.Engine/Data/UserSetting.cs-- device-to-slot linkage
The settings file is an XML document with SettingsFileData as the root element. File search order:
-
PadForge.xml(preferred for new installs) -
Settings.xml(generic fallback) - If neither exists, creates
PadForge.xmlin the application directory.
public class SettingsFileData
{
[XmlArray("Devices")]
[XmlArrayItem("Device")]
public UserDevice[] Devices { get; set; }
[XmlArray("UserSettings")]
[XmlArrayItem("Setting")]
public UserSetting[] Settings { get; set; }
[XmlArray("PadSettings")]
[XmlArrayItem("PadSetting")]
public PadSetting[] PadSettings { get; set; }
[XmlElement("AppSettings")]
public AppSettingsData AppSettings { get; set; }
[XmlArray("Macros")]
[XmlArrayItem("Macro")]
public MacroData[] Macros { get; set; }
[XmlArray("Profiles")]
[XmlArrayItem("Profile")]
public ProfileData[] Profiles { get; set; }
}<SettingsFileData>
<Devices>
<Device>...</Device>
</Devices>
<UserSettings>
<Setting>...</Setting>
</UserSettings>
<PadSettings>
<PadSetting>...</PadSetting>
</PadSettings>
<AppSettings>...</AppSettings>
<Macros>
<Macro PadIndex="0">...</Macro>
</Macros>
<Profiles>
<Profile Id="abc123" Name="Game Profile">...</Profile>
</Profiles>
</SettingsFileData>Namespace: PadForge.Engine.Data
Implements: INotifyPropertyChanged
Represents a single physical input device. Contains both serialized identity/capabilities and runtime-only fields.
| Property | Type | XML | Description |
|---|---|---|---|
InstanceGuid |
Guid |
<InstanceGuid> |
Unique device instance ID (deterministic from device path) |
InstanceName |
string |
<InstanceName> |
Human-readable instance name (e.g. "Xbox Controller") |
ProductGuid |
Guid |
<ProductGuid> |
Product GUID in PIDVID format for family identification |
ProductName |
string |
<ProductName> |
Human-readable product name |
VendorId |
ushort |
<VendorId> |
USB Vendor ID |
ProdId |
ushort |
<ProdId> |
USB Product ID |
DevRevision |
ushort |
<DevRevision> |
USB Product Version / Revision |
DevicePath |
string |
<DevicePath> |
File system device path |
SerialNumber |
string |
<SerialNumber> |
Serial number (e.g. Bluetooth MAC) |
| Property | Type | XML | Description |
|---|---|---|---|
CapAxeCount |
int |
<CapAxeCount> |
Number of axes |
CapButtonCount |
int |
<CapButtonCount> |
Number of buttons (gamepad-mapped count for gamepad devices) |
RawButtonCount |
int |
<RawButtonCount> |
Total raw joystick buttons (before gamepad remapping) |
CapPovCount |
int |
<CapPovCount> |
Number of POV hat switches |
CapType |
int |
<CapType> |
Device type constant from InputDeviceType
|
CapSubType |
int |
<CapSubType> |
Device subtype |
CapFlags |
int |
<CapFlags> |
Device capability flags |
HasGyro |
bool |
<HasGyro> |
Whether device has gyroscope sensor |
HasAccel |
bool |
<HasAccel> |
Whether device has accelerometer sensor |
| Property | Type | Description |
|---|---|---|
DateCreated |
DateTime |
When this device record was first created |
DateUpdated |
DateTime |
When this record was last updated |
IsHidden |
bool |
Whether the device is hidden from the device list |
| Property | Type | Description |
|---|---|---|
IsOnline |
bool |
Whether the device is currently connected |
SdlDevice |
ISdlInputDevice |
Active SDL device wrapper |
InputState |
CustomInputState |
Current input state (atomically swapped by engine) |
ForceFeedbackState |
ForceFeedbackState |
Force feedback / haptic state |
DeviceObjects |
DeviceObjectItem[] |
Axis/button metadata for display names |
Namespace: PadForge.Engine.Data
Implements: INotifyPropertyChanged
Links a physical input device to a virtual controller slot and a mapping configuration. One UserSetting per device-to-slot assignment.
| Property | Type | XML | Description |
|---|---|---|---|
InstanceGuid |
Guid |
<InstanceGuid> |
Device instance GUID (must match UserDevice.InstanceGuid) |
InstanceName |
string |
<InstanceName> |
Human-readable name at creation time |
ProductGuid |
Guid |
<ProductGuid> |
For fallback matching when instance GUIDs change |
ProductName |
string |
<ProductName> |
Human-readable product name |
MapTo |
int |
<MapTo> |
Virtual controller slot index (0-7). -1 = unmapped |
PadSettingChecksum |
string |
<PadSettingChecksum> |
Links to a PadSetting record |
IsEnabled |
bool |
<IsEnabled> |
Whether this mapping is active (default: true) |
SortOrder |
int |
<SortOrder> |
Priority for combining in Step 4 |
DateCreated |
DateTime |
<DateCreated> |
When this setting was created |
| Property | Type | Description |
|---|---|---|
MapToLabel |
string |
Display: "Player 1"-"Player 4" or "Unmapped" |
| Property | Type | Description |
|---|---|---|
OutputState |
Gamepad |
Per-device mapped output state (written by Step 3) |
VJoyRawOutputState |
VJoyRawState |
Per-device raw vJoy output (for custom configs) |
Multiple UserSettings can share the same InstanceGuid with different MapTo values. This enables one physical device to feed multiple virtual controllers. Multiple UserSettings can also share the same PadSettingChecksum -- but at load time, each gets an independent PadSetting clone to prevent shared-mutation bugs.
Namespace: PadForge.Engine.Data
Partial class (additional methods in other files)
Contains the complete mapping configuration for a device-to-slot assignment. All mapping properties are string-typed descriptors.
[XmlElement]
public string PadSettingChecksum { get; set; }MD5 hash of all mapping properties, truncated to 8 hex characters. Used to link UserSettings to PadSettings and detect duplicates. Computed by UpdateChecksum().
All mapping properties use string descriptors in the format consumed by InputManager Step 3:
| Format | Example | Description |
|---|---|---|
"Button N" |
"Button 0" |
Button at index N |
"Axis N" |
"Axis 1" |
Axis at index N |
"IAxis N" |
"IAxis 1" |
Inverted axis at index N |
"HAxis N" |
"HAxis 2" |
Half-axis (0-100% range) |
"IHAxis N" |
"IHAxis 2" |
Inverted half-axis |
"POV N Dir" |
"POV 0 Up" |
POV hat at index N, direction (Up/Down/Left/Right/UpRight/UpLeft/DownRight/DownLeft) |
"Slider N" |
"Slider 0" |
Slider at index N |
"" |
"" |
Unmapped |
| Property | Type | Description |
|---|---|---|
ButtonA |
string |
A / Cross button |
ButtonB |
string |
B / Circle button |
ButtonX |
string |
X / Square button |
ButtonY |
string |
Y / Triangle button |
LeftShoulder |
string |
LB / L1 |
RightShoulder |
string |
RB / R1 |
ButtonBack |
string |
Back / Share / Select |
ButtonStart |
string |
Start / Options |
ButtonGuide |
string |
Guide / PS button |
LeftThumbButton |
string |
LS / L3 |
RightThumbButton |
string |
RS / R3 |
| Property | Type | Description |
|---|---|---|
DPad |
string |
Combined D-Pad (e.g. "POV 0" auto-extracts all 4 directions) |
DPadUp |
string |
Override: D-Pad up |
DPadDown |
string |
Override: D-Pad down |
DPadLeft |
string |
Override: D-Pad left |
DPadRight |
string |
Override: D-Pad right |
| Property | Type | Description |
|---|---|---|
LeftTrigger |
string |
Left trigger mapping |
RightTrigger |
string |
Right trigger mapping |
LeftTriggerDeadZone |
string |
Dead zone 0-100 (stored as string) |
RightTriggerDeadZone |
string |
Dead zone 0-100 |
LeftTriggerAntiDeadZone |
string |
Anti-dead zone 0-100% |
RightTriggerAntiDeadZone |
string |
Anti-dead zone 0-100% |
LeftTriggerMaxRange |
string |
Max range 1-100% |
RightTriggerMaxRange |
string |
Max range 1-100% |
| Property | Type | Description |
|---|---|---|
LeftThumbAxisX |
string |
Left stick X positive direction |
LeftThumbAxisY |
string |
Left stick Y positive direction |
RightThumbAxisX |
string |
Right stick X positive direction |
RightThumbAxisY |
string |
Right stick Y positive direction |
LeftThumbAxisXNeg |
string |
Left stick X negative direction (for button-to-axis) |
LeftThumbAxisYNeg |
string |
Left stick Y negative direction |
RightThumbAxisXNeg |
string |
Right stick X negative direction |
RightThumbAxisYNeg |
string |
Right stick Y negative direction |
| Property | Type | Description |
|---|---|---|
LeftThumbDeadZoneX |
string |
Left stick dead zone X (0-100) |
LeftThumbDeadZoneY |
string |
Left stick dead zone Y (0-100) |
RightThumbDeadZoneX |
string |
Right stick dead zone X (0-100) |
RightThumbDeadZoneY |
string |
Right stick dead zone Y (0-100) |
LeftThumbAntiDeadZoneX |
string |
Left stick anti-dead zone X (0-100%) |
LeftThumbAntiDeadZoneY |
string |
Left stick anti-dead zone Y (0-100%) |
RightThumbAntiDeadZoneX |
string |
Right stick anti-dead zone X (0-100%) |
RightThumbAntiDeadZoneY |
string |
Right stick anti-dead zone Y (0-100%) |
LeftThumbLinear |
string |
Left stick linear response curve (0-100) |
RightThumbLinear |
string |
Right stick linear response curve (0-100) |
| Property | Type | Description |
|---|---|---|
ForceOverall |
string |
Overall gain 0-100 (default "100") |
LeftMotorStrength |
string |
Left motor strength 0-100 (default "100") |
RightMotorStrength |
string |
Right motor strength 0-100 (default "100") |
ForceSwapMotor |
string |
"1" or "0" -- swap left/right motors |
| Property | Type | Description |
|---|---|---|
VJoyMappings |
VJoyMappingEntry[] |
Serialized array of custom vJoy mappings (XML) |
Custom vJoy mappings use a dictionary internally (_vjoyMappingDict) with keys like VJoyAxis0, VJoyButton5, VJoyPov0Up. The dictionary is flushed to the VJoyMappings array before serialization via FlushVJoyMappings().
Key methods:
-
string GetVJoyMapping(string key)-- get descriptor by key -
void SetVJoyMapping(string key, string value)-- set descriptor by key -
void FlushVJoyMappings()-- flush dict to serializable array -
void LoadVJoyMappings()-- load array into dict (called after deserialization)
| Method | Signature | Description |
|---|---|---|
UpdateChecksum |
void UpdateChecksum() |
Recomputes MD5 checksum from all mapping properties |
CloneDeep |
PadSetting CloneDeep() |
Deep clone (JSON round-trip) |
CopyFrom |
void CopyFrom(PadSetting other) |
Copy all properties from another PadSetting |
ToJson |
string ToJson() |
Serialize to JSON (for clipboard) |
FromJson |
static PadSetting FromJson(string json) |
Deserialize from JSON |
MigrateAntiDeadZones |
void MigrateAntiDeadZones() |
Migrate old single-value anti-dead zones to per-axis |
Application-level settings. Stored as a single <AppSettings> element.
public class AppSettingsData
{
[XmlElement] public bool AutoStartEngine { get; set; } = true;
[XmlElement] public bool MinimizeToTray { get; set; }
[XmlElement] public bool StartMinimized { get; set; }
[XmlElement] public bool StartAtLogin { get; set; }
[XmlElement] public bool EnablePollingOnFocusLoss { get; set; } = true;
[XmlElement] public int PollingRateMs { get; set; } = 1;
[XmlElement] public int ThemeIndex { get; set; }
[XmlElement] public bool EnableAutoProfileSwitching { get; set; }
[XmlElement] public string ActiveProfileId { get; set; }
[XmlArray("SlotControllerTypes")]
[XmlArrayItem("Type")]
public int[] SlotControllerTypes { get; set; }
[XmlArray("SlotCreated")]
[XmlArrayItem("Created")]
public bool[] SlotCreated { get; set; }
[XmlArray("SlotEnabled")]
[XmlArrayItem("Enabled")]
public bool[] SlotEnabled { get; set; }
[XmlElement] public bool EnableDsuMotionServer { get; set; }
[XmlElement] public int DsuMotionServerPort { get; set; } = 26760;
[XmlElement] public bool Use2DControllerView { get; set; }
[XmlArray("VJoyConfigs")]
[XmlArrayItem("Config")]
public VJoySlotConfigData[] VJoyConfigs { get; set; }
}-
SlotCreated: Null on old files -> auto-populated from existing device assignments viaAutoCreateSlotsFromExistingAssignments(). -
SlotEnabled: Null on old files -> defaults to all true. -
SlotControllerTypes: Null on old files -> defaults to Xbox360 (0). -
VJoyConfigs: Null on old files -> uses Xbox360 preset defaults.
Serializable DTO for a macro. Stored per pad slot.
public class MacroData
{
[XmlAttribute] public int PadIndex { get; set; }
[XmlElement] public string Name { get; set; } = "New Macro";
[XmlElement] public bool IsEnabled { get; set; } = true;
[XmlElement] public ushort TriggerButtons { get; set; }
[XmlElement] public string TriggerDeviceGuid { get; set; }
[XmlElement] public string TriggerRawButtons { get; set; } // Comma-separated: "13,14"
[XmlElement] public MacroTriggerSource TriggerSource { get; set; }
[XmlElement] public MacroTriggerMode TriggerMode { get; set; }
[XmlElement] public bool ConsumeTriggerButtons { get; set; } = true;
[XmlElement] public MacroRepeatMode RepeatMode { get; set; }
[XmlElement] public int RepeatCount { get; set; } = 1;
[XmlElement] public int RepeatDelayMs { get; set; } = 100;
[XmlElement] public string TriggerCustomButtons { get; set; } // Hex-encoded: "00000003,..."
[XmlArray("Actions")]
[XmlArrayItem("Action")]
public ActionData[] Actions { get; set; }
}public class ActionData
{
[XmlElement] public MacroActionType Type { get; set; }
[XmlElement] public ushort ButtonFlags { get; set; }
[XmlElement] public string CustomButtons { get; set; }
[XmlElement] public int KeyCode { get; set; }
[XmlElement] public string KeyString { get; set; }
[XmlElement] public int DurationMs { get; set; }
[XmlElement] public int AxisValue { get; set; }
[XmlElement] public string AxisTarget { get; set; }
}Per-application profile. Stores a complete snapshot of device assignments, PadSettings, slot topology, and DSU settings.
public class ProfileData
{
[XmlAttribute] public string Id { get; set; } = Guid.NewGuid().ToString("N");
[XmlElement] public string Name { get; set; } = "New Profile";
[XmlElement] public string ExecutableNames { get; set; } = string.Empty; // Pipe-separated paths
[XmlArray("Entries")]
[XmlArrayItem("Entry")]
public ProfileEntry[] Entries { get; set; }
[XmlArray("ProfilePadSettings")]
[XmlArrayItem("PadSetting")]
public PadSetting[] PadSettings { get; set; }
[XmlArray("ProfileMacros")]
[XmlArrayItem("Macro")]
public MacroData[] Macros { get; set; }
[XmlArray("SlotCreated")]
[XmlArrayItem("Created")]
public bool[] SlotCreated { get; set; }
[XmlArray("SlotEnabled")]
[XmlArrayItem("Enabled")]
public bool[] SlotEnabled { get; set; }
[XmlArray("SlotControllerTypes")]
[XmlArrayItem("Type")]
public int[] SlotControllerTypes { get; set; }
[XmlElement] public bool EnableDsuMotionServer { get; set; }
[XmlElement] public int DsuMotionServerPort { get; set; }
}Links a device to a slot within a profile snapshot.
public class ProfileEntry
{
[XmlElement] public Guid InstanceGuid { get; set; }
[XmlElement] public Guid ProductGuid { get; set; }
[XmlElement] public int MapTo { get; set; }
[XmlElement] public string PadSettingChecksum { get; set; }
}Old profiles without topology data (SlotCreated == null) have topology application skipped during profile switch. The slot layout from the previous profile (or default) is preserved.
Namespace: PadForge.Common.Input
Static partial class (canonical partial in SettingsManager.cs, additional partials in InputManager.*.cs)
Central manager for device records and mapping settings. Shared between the background engine thread and the UI thread.
All access to UserDevices and UserSettings must be done inside a lock on the respective collection's SyncRoot.
| Property | Type | Description |
|---|---|---|
UserDevices |
DeviceCollection |
All known physical devices |
UserSettings |
SettingsCollection |
All device-to-slot assignments |
Profiles |
List<ProfileData> |
All saved profiles |
ActiveProfileId |
string |
ID of the currently active profile (null = default) |
EnableAutoProfileSwitching |
bool |
Whether auto-switching is enabled |
SlotCreated |
bool[] |
Which slots have been explicitly created (length: MaxPads=16) |
SlotEnabled |
bool[] |
Which slots are enabled for output (length: MaxPads=16, default: all true) |
| Constant | Value | Description |
|---|---|---|
MaxXbox360Slots |
8 (MaxPads) | Maximum Xbox 360 virtual controllers |
MaxDS4Slots |
8 (MaxPads) | Maximum DualShock 4 virtual controllers |
MaxVJoySlots |
16 | Maximum vJoy virtual controllers (driver limit) |
| Method | Signature | Description |
|---|---|---|
EnsureInitialized |
static void EnsureInitialized() |
Creates collections if null. Safe to call multiple times |
FindDeviceByInstanceGuid |
static UserDevice FindDeviceByInstanceGuid(Guid instanceGuid) |
Thread-safe device lookup |
GetOnlineDevices |
static List<UserDevice> GetOnlineDevices() |
Returns snapshot of online devices |
AddOrGetDevice |
static UserDevice AddOrGetDevice(UserDevice device) |
Adds if not exists, returns existing or new |
RemoveDevice |
static bool RemoveDevice(Guid instanceGuid) |
Removes device and associated UserSettings |
| Method | Signature | Description |
|---|---|---|
FindSettingByInstanceGuid |
static UserSetting FindSettingByInstanceGuid(Guid instanceGuid) |
Find setting by device GUID |
FindSettingByInstanceGuidAndSlot |
static UserSetting FindSettingByInstanceGuidAndSlot(Guid instanceGuid, int padIndex) |
Find setting for specific device+slot pair |
AssignDeviceToSlot |
static UserSetting AssignDeviceToSlot(Guid instanceGuid, int slotIndex) |
Create or update assignment |
UnassignDevice |
static void UnassignDevice(Guid instanceGuid) |
Remove all assignments for a device |
ToggleDeviceSlotAssignment |
static (bool assigned, UserSetting us) ToggleDeviceSlotAssignment(Guid instanceGuid, int slotIndex) |
Toggle multi-slot assignment |
GetAssignedSlots |
static List<int> GetAssignedSlots(Guid instanceGuid) |
Get all slots a device is assigned to |
public static PadSetting CreateDefaultPadSetting(UserDevice ud)Creates a default PadSetting with auto-mapped inputs when ud.CapType == InputDeviceType.Gamepad:
Standardized Gamepad Layout:
| Target | Axis/Button Index | Descriptor |
|---|---|---|
| LeftThumbAxisX | Axis 0 | "Axis 0" |
| LeftThumbAxisY | Axis 1 | "Axis 1" |
| LeftTrigger | Axis 2 | "Axis 2" |
| RightThumbAxisX | Axis 3 | "Axis 3" |
| RightThumbAxisY | Axis 4 | "Axis 4" |
| RightTrigger | Axis 5 | "Axis 5" |
| ButtonA | Button 0 | "Button 0" |
| ButtonB | Button 1 | "Button 1" |
| ButtonX | Button 2 | "Button 2" |
| ButtonY | Button 3 | "Button 3" |
| LeftShoulder | Button 4 | "Button 4" |
| RightShoulder | Button 5 | "Button 5" |
| ButtonBack | Button 6 | "Button 6" |
| ButtonStart | Button 7 | "Button 7" |
| LeftThumbButton | Button 8 | "Button 8" |
| RightThumbButton | Button 9 | "Button 9" |
| ButtonGuide | Button 10 | "Button 10" |
| Method | Signature | Description |
|---|---|---|
SwapSlots |
static void SwapSlots(int a, int b) |
Swaps SlotCreated, SlotEnabled, and all UserSetting.MapTo values |
Namespace: PadForge.Services
Source: PadForge.App/Services/SettingsService.cs
Handles XML persistence and bidirectional sync between SettingsManager data and WPF ViewModels.
| Constant | Value | Description |
|---|---|---|
PrimaryFileName |
"PadForge.xml" |
Primary settings file name |
FallbackFileName |
"Settings.xml" |
Fallback settings file name |
| Property | Type | Description |
|---|---|---|
SettingsFilePath |
string |
Full path to the active settings file |
IsDirty |
bool |
Whether settings have been modified since last save |
| Event | Type | Description |
|---|---|---|
AutoSaved |
EventHandler |
Raised after autosave completes |
public SettingsService(MainViewModel mainVm)public void Initialize()- Ensures SettingsManager collections exist.
- Finds the settings file via
FindSettingsFile(). - Loads settings from disk if file exists.
- Pushes file path to ViewModel.
public void LoadFromFile(string filePath)Deserializes SettingsFileData from XML. For each UserSetting, clones the matching PadSetting template (prevents shared-mutation bugs when devices share checksums). Calls sub-loaders in order:
-
LoadAppSettings(AppSettingsData)-- app-level settings to SettingsViewModel -
LoadPadSettings(UserSetting[], PadSetting[])-- per-device settings to PadViewModels -
LoadMacros(MacroData[])-- macros to PadViewModels -
LoadProfiles(ProfileData[], AppSettingsData)-- profiles to SettingsManager + ViewModel
private void LoadAppSettings(AppSettingsData appSettings)Pushes app settings to SettingsViewModel. Load order is critical: SlotCreated/SlotEnabled BEFORE OutputType, because OutputType fires PropertyChanged which reads SlotCreated.
private void LoadPadSettings(UserSetting[] settings, PadSetting[] padSettings)For each slot, loads the first device's PadSetting into the PadViewModel: dead zones, anti-dead zones, linear response, trigger settings, force feedback, and mapping descriptors.
private void LoadMacros(MacroData[] macros)Clears all pad macros, then populates from serialized data. Derives button style and custom button count from the pad's OutputType and VJoyConfig.
private void LoadProfiles(ProfileData[] profiles, AppSettingsData appSettings)Always adds a built-in "Default" profile at the top. Then adds each serialized profile. Updates topology counts (Xbox/DS4/vJoy slot counts) for each profile list item.
public void Save()Shorthand for SaveToFile(_settingsFilePath).
public void SaveToFile(string filePath)- Calls
UpdatePadSettingsFromViewModels()to push ViewModel state to PadSettings. - Calls
FlushVJoyMappings()andUpdateChecksum()on all PadSettings. - Calls
UpdateActiveProfileSnapshot()to persist edits to the active profile. - Collects devices, settings, unique PadSettings (deduplicated by checksum).
- Builds
AppSettingsDataandMacroData[]from ViewModels. - Serializes to XML.
private AppSettingsData BuildAppSettings()Reads from SettingsViewModel and SettingsManager to build the serializable DTO.
private MacroData[] BuildMacroData()Collects macros from all PadViewModels into a flat array with PadIndex attributes.
public void MarkDirty()Sets IsDirty = true and starts a 250ms debounce timer. When the timer fires, calls Save() and raises AutoSaved. The debounce prevents saving on every single property change during rapid edits.
public void ResetToDefaults()Clears all UserDevices and UserSettings. Resets all PadViewModel mappings. Marks dirty.
public void Reload()Reloads settings from the file on disk. Clears dirty flag.
private void UpdatePadSettingsFromViewModels()Pushes ViewModel slider values (dead zones, force feedback, linear, mapping descriptors) to PadSetting objects. Called before save. For vJoy mappings, uses SetVJoyMapping() instead of reflection.