Skip to content

Settings and Serialization

hifihedgehog edited this page Mar 3, 2026 · 65 revisions

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

PadForge.xml File Format

The settings file is an XML document with SettingsFileData as the root element. File search order:

  1. PadForge.xml (preferred for new installs)
  2. Settings.xml (generic fallback)
  3. If neither exists, creates PadForge.xml in the application directory.

SettingsFileData (Root Element)

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; }
}

XML Structure

<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>

UserDevice

Namespace: PadForge.Engine.Data Implements: INotifyPropertyChanged

Represents a single physical input device. Contains both serialized identity/capabilities and runtime-only fields.

Serializable Identity Properties

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)

Serializable Capability Properties

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

Serializable Metadata

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

Runtime-Only Properties (XmlIgnore)

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

UserSetting

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.

Serializable Properties

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

Derived Properties

Property Type Description
MapToLabel string Display: "Player 1"-"Player 4" or "Unmapped"

Runtime-Only Properties (XmlIgnore)

Property Type Description
OutputState Gamepad Per-device mapped output state (written by Step 3)
VJoyRawOutputState VJoyRawState Per-device raw vJoy output (for custom configs)

Key Design: Multi-Slot Assignment

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.


PadSetting

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.

Checksum

[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().

Descriptor Format

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

Button Mapping Properties

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

D-Pad Mapping Properties

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

Trigger Mapping Properties

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%

Thumbstick Axis Mapping Properties

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

Dead Zone / Response Properties

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)

Force Feedback Properties

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

vJoy Custom Mapping Properties

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)

Utility Methods

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

AppSettingsData

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; }
}

Backward Compatibility

  • SlotCreated: Null on old files -> auto-populated from existing device assignments via AutoCreateSlotsFromExistingAssignments().
  • 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.

MacroData

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; }
}

ActionData

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; }
}

ProfileData

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; }
}

ProfileEntry

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; }
}

Backward Compatibility

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.


SettingsManager

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.

Thread Safety

All access to UserDevices and UserSettings must be done inside a lock on the respective collection's SyncRoot.

Static Properties

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)

Per-Type Slot Limits

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)

Device Management Methods

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

UserSetting Management Methods

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

Auto-Mapping

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"

Profile Management

Method Signature Description
SwapSlots static void SwapSlots(int a, int b) Swaps SlotCreated, SlotEnabled, and all UserSetting.MapTo values

SettingsService

Namespace: PadForge.Services Source: PadForge.App/Services/SettingsService.cs

Handles XML persistence and bidirectional sync between SettingsManager data and WPF ViewModels.

Constants

Constant Value Description
PrimaryFileName "PadForge.xml" Primary settings file name
FallbackFileName "Settings.xml" Fallback settings file name

Properties

Property Type Description
SettingsFilePath string Full path to the active settings file
IsDirty bool Whether settings have been modified since last save

Events

Event Type Description
AutoSaved EventHandler Raised after autosave completes

Constructor

public SettingsService(MainViewModel mainVm)

Initialize()

public void Initialize()
  1. Ensures SettingsManager collections exist.
  2. Finds the settings file via FindSettingsFile().
  3. Loads settings from disk if file exists.
  4. Pushes file path to ViewModel.

Load Methods

LoadFromFile(string filePath)

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:

  1. LoadAppSettings(AppSettingsData) -- app-level settings to SettingsViewModel
  2. LoadPadSettings(UserSetting[], PadSetting[]) -- per-device settings to PadViewModels
  3. LoadMacros(MacroData[]) -- macros to PadViewModels
  4. LoadProfiles(ProfileData[], AppSettingsData) -- profiles to SettingsManager + ViewModel

LoadAppSettings(AppSettingsData appSettings)

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.

LoadPadSettings(UserSetting[] settings, PadSetting[] padSettings)

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.

LoadMacros(MacroData[] macros)

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.

LoadProfiles(ProfileData[] profiles, AppSettingsData appSettings)

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.

Save Methods

Save()

public void Save()

Shorthand for SaveToFile(_settingsFilePath).

SaveToFile(string filePath)

public void SaveToFile(string filePath)
  1. Calls UpdatePadSettingsFromViewModels() to push ViewModel state to PadSettings.
  2. Calls FlushVJoyMappings() and UpdateChecksum() on all PadSettings.
  3. Calls UpdateActiveProfileSnapshot() to persist edits to the active profile.
  4. Collects devices, settings, unique PadSettings (deduplicated by checksum).
  5. Builds AppSettingsData and MacroData[] from ViewModels.
  6. Serializes to XML.

BuildAppSettings() -> AppSettingsData

private AppSettingsData BuildAppSettings()

Reads from SettingsViewModel and SettingsManager to build the serializable DTO.

BuildMacroData() -> MacroData[]

private MacroData[] BuildMacroData()

Collects macros from all PadViewModels into a flat array with PadIndex attributes.

Dirty Tracking / Autosave

MarkDirty()

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.

Reset / Reload

ResetToDefaults()

public void ResetToDefaults()

Clears all UserDevices and UserSettings. Resets all PadViewModel mappings. Marks dirty.

Reload()

public void Reload()

Reloads settings from the file on disk. Clears dirty flag.

UpdatePadSettingsFromViewModels()

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.

Clone this wiki locally