-
Notifications
You must be signed in to change notification settings - Fork 6
Engine Library
The PadForge.Engine assembly is a shared class library containing data types, interfaces, and enums used by both the Engine (input pipeline) and App (UI/ViewModel) assemblies. It has no UI dependencies and targets net8.0-windows.
Project file: PadForge.Engine/PadForge.Engine.csproj
Namespace: PadForge.Engine
File: PadForge.Engine/Common/GamepadTypes.cs
Minimal struct matching the XInput XINPUT_GAMEPAD layout. Used as the output of the mapping pipeline (Step 3 -> Step 4 -> Step 5).
public struct Gamepad
{
public ushort Buttons;
public byte LeftTrigger;
public byte RightTrigger;
public short ThumbLX;
public short ThumbLY;
public short ThumbRX;
public short ThumbRY;
public bool IsButtonPressed(ushort flag);
public void SetButton(ushort flag, bool pressed);
public void Clear();
}| Constant | Value | Description |
|---|---|---|
DPAD_UP |
0x0001 |
D-pad up |
DPAD_DOWN |
0x0002 |
D-pad down |
DPAD_LEFT |
0x0004 |
D-pad left |
DPAD_RIGHT |
0x0008 |
D-pad right |
START |
0x0010 |
Start button |
BACK |
0x0020 |
Back button |
LEFT_THUMB |
0x0040 |
Left stick click |
RIGHT_THUMB |
0x0080 |
Right stick click |
LEFT_SHOULDER |
0x0100 |
Left bumper |
RIGHT_SHOULDER |
0x0200 |
Right bumper |
GUIDE |
0x0400 |
Guide/home button |
A |
0x1000 |
A button |
B |
0x2000 |
B button |
X |
0x4000 |
X button |
Y |
0x8000 |
Y button |
File: PadForge.Engine/Common/GamepadTypes.cs
public struct XInputState
{
public uint PacketNumber;
public Gamepad Gamepad;
}Matches the XINPUT_STATE struct layout (packet number + Gamepad).
File: PadForge.Engine/Common/GamepadTypes.cs
Raw vJoy output state for custom (non-gamepad) configurations. Bypasses the fixed Gamepad struct to support arbitrary axis/button/POV counts.
public struct VJoyRawState
{
public short[] Axes; // Up to 8 axes (signed short range -32768..32767)
public uint[] Buttons; // Button state as 4 x 32-bit words = 128 buttons max
public int[] Povs; // Up to 4 POV hat switches (-1=centered, 0-35900=direction)
public static VJoyRawState Create(int nAxes, int nButtons, int nPovs);
public void SetButton(int index, bool pressed);
public bool IsButtonPressed(int index);
public void Clear();
}public static VJoyRawState Create(int nAxes, int nButtons, int nPovs)Creates a zeroed state with specified capacities. Axes clamped to max 8, buttons to max 128 (stored as (N+31)/32 uint words), POVs to max 4.
Buttons use a 128-bit bitmask stored as uint[4]. Each uint holds 32 buttons. Index calculation:
word = index / 32bit = index % 32
POV values are in hundredths of degrees:
-
0= North -
4500= NE -
9000= East -
13500= SE -
18000= South -
22500= SW -
27000= West -
31500= NW -
0xFFFFFFFF(-1) = Centered
Resets axes to 0, buttons to 0, POVs to -1 (centered).
File: PadForge.Engine/Common/VirtualControllerTypes.cs
public enum VirtualControllerType
{
Xbox360 = 0,
DualShock4 = 1,
VJoy = 2
}File: PadForge.Engine/Common/VirtualControllerTypes.cs
public interface IVirtualController : IDisposable
{
VirtualControllerType Type { get; }
bool IsConnected { get; }
void Connect();
void Disconnect();
void SubmitGamepadState(Gamepad gp);
void RegisterFeedbackCallback(int padIndex, Vibration[] vibrationStates);
}Abstraction over ViGEm/vJoy virtual controller operations. Concrete implementations live in the App assembly:
-
Xbox360VirtualController(ViGEm) -
DS4VirtualController(ViGEm) -
VJoyVirtualController(direct P/Invoke tovJoyInterface.dll)
File: PadForge.Engine/Common/CustomInputState.cs
API-agnostic snapshot of a device's complete input state at a single point in time.
public class CustomInputState
{
public const int MaxAxis = 24;
public const int MaxSliders = 8;
public const int MaxPovs = 4;
public const int MaxButtons = 256;
public int[] Axis; // 0-65535, center = 32767
public int[] Sliders; // 0-65535
public int[] Povs; // centidegrees 0-35900, or -1 for centered
public bool[] Buttons; // true = pressed
public float[] Gyro; // [X, Y, Z] radians per second
public float[] Accel; // [X, Y, Z] meters per second squared
}public CustomInputState()
public CustomInputState(int[] axes, int[] sliders, int[] povs, bool[] buttons)Default constructor creates zeroed arrays with default sizes. POVs initialize to -1 (centered). Copy constructor copies arrays up to max lengths (snapshot isolation).
public CustomInputState Clone()Deep copy of all arrays.
public static void GetAxisMask(
DeviceObjectItem[] items, int numAxes,
out int axisMask, out int actuatorMask, out int actuatorCount)Scans device object items to build axis and force-feedback actuator bitmasks. Bit N set = axis/actuator N exists.
public static int GetSlidersMask(DeviceObjectItem[] items, int numSliders)Scans device object items to build a slider bitmask.
| Array | Range | Center | Description |
|---|---|---|---|
Axis |
0-65535 | 32767 | Indices 0-5 = X, Y, Z, Rx, Ry, Rz. 6-23 = additional |
Sliders |
0-65535 | 32767 | Overflow or dedicated slider controls |
Povs |
0-35900 or -1 | -1 | Centidegrees. -1 = centered |
Buttons |
bool | false | 256 max (covers full Windows VK code range) |
Gyro |
float[3] | 0.0 | Radians/second. Only for gyro-capable devices |
Accel |
float[3] | 0.0 | m/s^2. Only for accelerometer-capable devices |
File: PadForge.Engine/Common/ISdlInputDevice.cs
Common interface for all SDL-based input device wrappers (joystick/gamepad, keyboard, mouse).
public interface ISdlInputDevice : IDisposable
{
// Identity
uint SdlInstanceId { get; }
string Name { get; }
Guid InstanceGuid { get; }
Guid ProductGuid { get; }
string DevicePath { get; }
string SerialNumber { get; }
ushort VendorId { get; }
ushort ProductId { get; }
// Capabilities
int NumAxes { get; }
int NumButtons { get; }
int RawButtonCount { get; }
int NumHats { get; }
bool HasRumble { get; }
bool HasHaptic { get; }
bool HasGyro { get; }
bool HasAccel { get; }
bool IsAttached { get; }
// Haptic
HapticEffectStrategy HapticStrategy { get; }
IntPtr HapticHandle { get; }
uint HapticFeatures { get; }
// State reading
CustomInputState GetCurrentState();
DeviceObjectItem[] GetDeviceObjects();
int GetInputDeviceType();
// Force feedback
bool SetRumble(ushort low, ushort high, uint durationMs = uint.MaxValue);
bool StopRumble();
}public enum HapticEffectStrategy
{
None,
LeftRight,
Sine,
Constant
}Priority order chosen at haptic open time based on SDL_GetHapticFeatures:
- LeftRight -- Best match for dual-motor rumble (low + high frequency).
- Sine -- Periodic effect. Period varies by which motor is stronger.
- Constant -- Fallback. Level from dominant motor.
File: PadForge.Engine/Common/DeviceObjectItem.cs
Describes a single input object (axis, button, hat, slider) on a device. Used by the mapping UI and pipeline.
public class DeviceObjectItem
{
// Identity
public string Name { get; set; }
public Guid ObjectTypeGuid { get; set; }
public DeviceObjectTypeFlags ObjectType { get; set; }
// Position
public int InputIndex { get; set; }
public int Offset { get; set; }
// Aspect
public ObjectAspect Aspect { get; set; }
// Computed helpers
public bool IsForceActuator { get; }
public bool IsAxis { get; }
public bool IsButton { get; }
public bool IsPov { get; }
public bool IsSlider { get; }
}| Property | Type | Default | Description |
|---|---|---|---|
Name |
string |
"" |
Human-readable name (e.g., "X Axis", "Button 3") |
ObjectTypeGuid |
Guid |
Guid.Empty |
Well-known GUID from ObjectGuid
|
ObjectType |
DeviceObjectTypeFlags |
All |
Classification flags |
InputIndex |
int |
0 |
Zero-based index into CustomInputState arrays |
Offset |
int |
0 |
Byte offset for legacy mapping compatibility |
Aspect |
ObjectAspect |
Position |
Position, Velocity, Acceleration, or Force |
-
IsForceActuator:(ObjectType & ForceFeedbackActuator) != 0 -
IsAxis:(ObjectType & Axis) != 0 -
IsButton:(ObjectType & Button) != 0 -
IsPov:(ObjectType & PointOfViewController) != 0 -
IsSlider:ObjectTypeGuid == ObjectGuid.Slider
Returns "{Name} ({TypeLabel}, Index {InputIndex})" where TypeLabel is "Axis", "Slider", "POV", "Button", or "Object".
File: PadForge.Engine/Common/InputTypes.cs
[Flags]
public enum DeviceObjectTypeFlags : int
{
All = 0,
RelativeAxis = 1,
AbsoluteAxis = 2,
Axis = 3, // RelativeAxis | AbsoluteAxis
PushButton = 4,
ToggleButton = 8,
Button = 12, // PushButton | ToggleButton
PointOfViewController = 16,
Collection = 64,
NoData = 128,
ForceFeedbackActuator = 0x01000000,
ForceFeedbackEffectTrigger = 0x02000000
}[Flags]
public enum ObjectAspect : int
{
Position = 0x100,
Velocity = 0x200,
Acceleration = 0x300,
Force = 0x400
}[Flags]
public enum EffectParameterFlags : int
{
None = 0,
Duration = 1,
SamplePeriod = 2,
Gain = 4,
TriggerButton = 8,
TriggerRepeatInterval = 16,
Axes = 32,
Direction = 64,
Envelope = 128,
TypeSpecificParameters = 256,
StartDelay = 512,
AllParameters = 0x3FF,
Start = 0x20000000,
NoRestart = 0x40000000,
NoDownload = unchecked((int)0x80000000)
}Static class providing well-known GUIDs for device object types. Values match DirectInput GUID constants.
public static class ObjectGuid
{
public static readonly Guid XAxis; // {A36D02E0-C9F3-11CF-BFC7-444553540000}
public static readonly Guid YAxis; // {A36D02E1-...}
public static readonly Guid ZAxis; // {A36D02E2-...}
public static readonly Guid RxAxis; // {A36D02F4-...}
public static readonly Guid RyAxis; // {A36D02F5-...}
public static readonly Guid RzAxis; // {A36D02E3-...}
public static readonly Guid Slider; // {A36D02E4-...}
public static readonly Guid Button; // {A36D02F0-...}
public static readonly Guid Key; // {55728220-D33C-11CF-BFC7-444553540000}
public static readonly Guid PovController; // {A36D02F2-...}
public static readonly Guid Unknown; // Guid.Empty
}Integer constants matching DirectInput device type values. Used for UserDevice.CapType.
public static class InputDeviceType
{
public const int Device = 17;
public const int Mouse = 18;
public const int Keyboard = 19;
public const int Joystick = 20;
public const int Gamepad = 21;
public const int Driving = 22;
public const int Flight = 23;
public const int FirstPerson = 24;
public const int Supplemental = 25;
}public enum MapType : int
{
None = 0,
Axis = 1,
Button = 2,
Slider = 3,
POV = 4
}File: PadForge.Engine/Common/ForceFeedbackState.cs
Manages force feedback (rumble) state for a single device with change detection.
public class ForceFeedbackState
{
// Public state
public ushort LeftMotorSpeed { get; } // 0-65535
public ushort RightMotorSpeed { get; } // 0-65535
public bool IsActive { get; }
// Methods
public void SetDeviceForces(UserDevice ud, ISdlInputDevice device, PadSetting ps, Vibration v);
public void StopDeviceForces(ISdlInputDevice device);
public bool Changed(PadSetting ps);
}public void SetDeviceForces(UserDevice ud, ISdlInputDevice device, PadSetting ps, Vibration v)- Reads gain settings from
PadSetting:ForceOverall,LeftMotorStrength,RightMotorStrength(all 0-100). - Applies per-motor and overall gain scaling to raw XInput motor speeds.
- Swaps motors if
ForceSwapMotoris configured. -
Change detection: Only sends to hardware when values differ from cached values. Each
SDL_RumbleJoystickcall restarts the hardware rumble, causing brief gaps. By only sending on change withuint.MaxValueduration (~49 days), rumble stays continuous. - Routes to either
SetHapticForces()(for haptic devices) orSetRumble()(for rumble devices).
private bool SetHapticForces(ISdlInputDevice device, ushort left, ushort right)Translates dual-motor rumble to SDL haptic effects based on the device's HapticEffectStrategy:
| Strategy | SDL Effect | Large Motor | Small Motor |
|---|---|---|---|
| LeftRight | SDL_HAPTIC_LEFTRIGHT |
large_magnitude = left |
small_magnitude = right |
| Sine | SDL_HAPTIC_SINE |
magnitude = max/2, period = 120
|
period = 40 |
| Constant | SDL_HAPTIC_CONSTANT |
level = max/2 |
N/A |
Creates effect on first call via SDL_CreateHapticEffect, updates in-place on subsequent calls via SDL_UpdateHapticEffect.
public bool Changed(PadSetting ps)Returns true if any force feedback setting in the PadSetting has changed since last call. Caches: ForceType, ForceSwapMotor, LeftMotorStrength, RightMotorStrength, ForceOverall.
File: PadForge.Engine/Common/ForceFeedbackState.cs
public class Vibration
{
public ushort LeftMotorSpeed { get; set; } // Low-frequency, heavy rumble. 0-65535
public ushort RightMotorSpeed { get; set; } // High-frequency, light buzz. 0-65535
public Vibration();
public Vibration(ushort leftMotor, ushort rightMotor);
}Matches the XINPUT_VIBRATION layout. Populated from ViGEm FeedbackReceived callback on the ViGEm thread; read by Step 2 on the polling thread.
File: PadForge.Engine/Common/RumbleLogger.cs
public static class RumbleLogger
{
public static bool Enabled { get; set; }
public static void Log(string message);
}Diagnostic logger for force feedback debugging. Disabled by default. Set Enabled = true in InputService.Start() to activate. Thread-safe. Writes timestamped messages to a log file using Stopwatch for high-resolution timing.