From 4bd42fc7ba1cbecbc8754da43c88e205bf605e41 Mon Sep 17 00:00:00 2001 From: vitoplantamura Date: Mon, 10 Jun 2024 19:22:37 +0200 Subject: [PATCH] Initial commit --- .gitignore | 5 + AmtPtpControlPanel/AmtPtpControlPanel.csproj | 114 ++ AmtPtpControlPanel/AmtPtpControlPanel.sln | 31 + AmtPtpControlPanel/App.config | 6 + AmtPtpControlPanel/Main.Designer.cs | 394 ++++++ AmtPtpControlPanel/Main.cs | 689 ++++++++++ AmtPtpControlPanel/Main.resx | 994 +++++++++++++++ AmtPtpControlPanel/Program.cs | 22 + AmtPtpControlPanel/Properties/AssemblyInfo.cs | 36 + .../Properties/Resources.Designer.cs | 73 ++ AmtPtpControlPanel/Properties/Resources.resx | 124 ++ .../Properties/Settings.Designer.cs | 29 + .../Properties/Settings.settings | 7 + AmtPtpControlPanel/Resources/Icon1.ico | Bin 0 -> 52117 bytes AmtPtpControlPanel/app.manifest | 79 ++ AmtPtpDeviceUsbUm/AmtPtpDevice.wprp | 29 + AmtPtpDeviceUsbUm/Device.c | 1017 +++++++++++++++ AmtPtpDeviceUsbUm/Driver.c | 158 +++ AmtPtpDeviceUsbUm/Hid.c | 1130 +++++++++++++++++ AmtPtpDeviceUsbUm/InputInterrupt.c | 650 ++++++++++ AmtPtpDeviceUsbUm/MagicTrackpad2PtpDevice.sln | 43 + .../MagicTrackpad2PtpDevice.vcxproj | 240 ++++ .../MagicTrackpad2PtpDevice.vcxproj.filters | 101 ++ AmtPtpDeviceUsbUm/Queue.c | 302 +++++ AmtPtpDeviceUsbUm/Resource.rc | Bin 0 -> 4792 bytes AmtPtpDeviceUsbUm/include/AppleDefinition.h | 448 +++++++ AmtPtpDeviceUsbUm/include/Device.h | 230 ++++ .../include/DeviceFamily/Wellspring3.h | 139 ++ .../include/DeviceFamily/Wellspring5.h | 139 ++ .../include/DeviceFamily/Wellspring6.h | 139 ++ .../include/DeviceFamily/Wellspring7A.h | 139 ++ .../include/DeviceFamily/Wellspring8.h | 139 ++ .../include/DeviceFamily/WellspringMt2.h | 138 ++ AmtPtpDeviceUsbUm/include/Driver.h | 46 + AmtPtpDeviceUsbUm/include/Hid.h | 169 +++ AmtPtpDeviceUsbUm/include/HidCommon.h | 39 + AmtPtpDeviceUsbUm/include/ModernTrace.h | 28 + AmtPtpDeviceUsbUm/include/Queue.h | 41 + AmtPtpDeviceUsbUm/include/StaticHidRegistry.h | 114 ++ AmtPtpDeviceUsbUm/include/Trace.h | 48 + AmtPtpDeviceUsbUm/include/resource.h | 16 + README.md | 29 + assets/ControlPanel.png | Bin 0 -> 35382 bytes 43 files changed, 8314 insertions(+) create mode 100644 .gitignore create mode 100644 AmtPtpControlPanel/AmtPtpControlPanel.csproj create mode 100644 AmtPtpControlPanel/AmtPtpControlPanel.sln create mode 100644 AmtPtpControlPanel/App.config create mode 100644 AmtPtpControlPanel/Main.Designer.cs create mode 100644 AmtPtpControlPanel/Main.cs create mode 100644 AmtPtpControlPanel/Main.resx create mode 100644 AmtPtpControlPanel/Program.cs create mode 100644 AmtPtpControlPanel/Properties/AssemblyInfo.cs create mode 100644 AmtPtpControlPanel/Properties/Resources.Designer.cs create mode 100644 AmtPtpControlPanel/Properties/Resources.resx create mode 100644 AmtPtpControlPanel/Properties/Settings.Designer.cs create mode 100644 AmtPtpControlPanel/Properties/Settings.settings create mode 100644 AmtPtpControlPanel/Resources/Icon1.ico create mode 100644 AmtPtpControlPanel/app.manifest create mode 100644 AmtPtpDeviceUsbUm/AmtPtpDevice.wprp create mode 100644 AmtPtpDeviceUsbUm/Device.c create mode 100644 AmtPtpDeviceUsbUm/Driver.c create mode 100644 AmtPtpDeviceUsbUm/Hid.c create mode 100644 AmtPtpDeviceUsbUm/InputInterrupt.c create mode 100644 AmtPtpDeviceUsbUm/MagicTrackpad2PtpDevice.sln create mode 100644 AmtPtpDeviceUsbUm/MagicTrackpad2PtpDevice.vcxproj create mode 100644 AmtPtpDeviceUsbUm/MagicTrackpad2PtpDevice.vcxproj.filters create mode 100644 AmtPtpDeviceUsbUm/Queue.c create mode 100644 AmtPtpDeviceUsbUm/Resource.rc create mode 100644 AmtPtpDeviceUsbUm/include/AppleDefinition.h create mode 100644 AmtPtpDeviceUsbUm/include/Device.h create mode 100644 AmtPtpDeviceUsbUm/include/DeviceFamily/Wellspring3.h create mode 100644 AmtPtpDeviceUsbUm/include/DeviceFamily/Wellspring5.h create mode 100644 AmtPtpDeviceUsbUm/include/DeviceFamily/Wellspring6.h create mode 100644 AmtPtpDeviceUsbUm/include/DeviceFamily/Wellspring7A.h create mode 100644 AmtPtpDeviceUsbUm/include/DeviceFamily/Wellspring8.h create mode 100644 AmtPtpDeviceUsbUm/include/DeviceFamily/WellspringMt2.h create mode 100644 AmtPtpDeviceUsbUm/include/Driver.h create mode 100644 AmtPtpDeviceUsbUm/include/Hid.h create mode 100644 AmtPtpDeviceUsbUm/include/HidCommon.h create mode 100644 AmtPtpDeviceUsbUm/include/ModernTrace.h create mode 100644 AmtPtpDeviceUsbUm/include/Queue.h create mode 100644 AmtPtpDeviceUsbUm/include/StaticHidRegistry.h create mode 100644 AmtPtpDeviceUsbUm/include/Trace.h create mode 100644 AmtPtpDeviceUsbUm/include/resource.h create mode 100644 README.md create mode 100644 assets/ControlPanel.png diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ddab165 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +.vs/ +bin/ +obj/ +build/ +intermediate/ diff --git a/AmtPtpControlPanel/AmtPtpControlPanel.csproj b/AmtPtpControlPanel/AmtPtpControlPanel.csproj new file mode 100644 index 0000000..4e097e1 --- /dev/null +++ b/AmtPtpControlPanel/AmtPtpControlPanel.csproj @@ -0,0 +1,114 @@ + + + + + Debug + AnyCPU + {EE1C7A99-9593-4D6A-889B-D75EEA2DE6AB} + WinExe + AmtPtpControlPanel + AmtPtpControlPanel + v4.7.2 + 512 + true + true + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + true + bin\x64\Debug\ + DEBUG;TRACE + full + x64 + 7.3 + prompt + true + + + bin\x64\Release\ + TRACE + true + pdbonly + x64 + 7.3 + prompt + true + + + app.manifest + + + Resources\Icon1.ico + + + + + + + + + + + + + + + + + Form + + + Main.cs + + + + + Main.cs + + + ResXFileCodeGenerator + Resources.Designer.cs + Designer + + + True + Resources.resx + True + + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + True + Settings.settings + True + + + + + + + + + + \ No newline at end of file diff --git a/AmtPtpControlPanel/AmtPtpControlPanel.sln b/AmtPtpControlPanel/AmtPtpControlPanel.sln new file mode 100644 index 0000000..df22906 --- /dev/null +++ b/AmtPtpControlPanel/AmtPtpControlPanel.sln @@ -0,0 +1,31 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.33801.447 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AmtPtpControlPanel", "AmtPtpControlPanel.csproj", "{EE1C7A99-9593-4D6A-889B-D75EEA2DE6AB}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {EE1C7A99-9593-4D6A-889B-D75EEA2DE6AB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EE1C7A99-9593-4D6A-889B-D75EEA2DE6AB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EE1C7A99-9593-4D6A-889B-D75EEA2DE6AB}.Debug|x64.ActiveCfg = Debug|x64 + {EE1C7A99-9593-4D6A-889B-D75EEA2DE6AB}.Debug|x64.Build.0 = Debug|x64 + {EE1C7A99-9593-4D6A-889B-D75EEA2DE6AB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EE1C7A99-9593-4D6A-889B-D75EEA2DE6AB}.Release|Any CPU.Build.0 = Release|Any CPU + {EE1C7A99-9593-4D6A-889B-D75EEA2DE6AB}.Release|x64.ActiveCfg = Release|x64 + {EE1C7A99-9593-4D6A-889B-D75EEA2DE6AB}.Release|x64.Build.0 = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {EF892ED6-EFF6-4552-89C8-4A58160848EC} + EndGlobalSection +EndGlobal diff --git a/AmtPtpControlPanel/App.config b/AmtPtpControlPanel/App.config new file mode 100644 index 0000000..5754728 --- /dev/null +++ b/AmtPtpControlPanel/App.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/AmtPtpControlPanel/Main.Designer.cs b/AmtPtpControlPanel/Main.Designer.cs new file mode 100644 index 0000000..82b1b99 --- /dev/null +++ b/AmtPtpControlPanel/Main.Designer.cs @@ -0,0 +1,394 @@ + +namespace AmtPtpControlPanel +{ + partial class Main + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(Main)); + this.ctlInstallDriver = new System.Windows.Forms.Button(); + this.ctlApply = new System.Windows.Forms.Button(); + this.ctlFeedback = new System.Windows.Forms.TrackBar(); + this.ctlLightLabel = new System.Windows.Forms.Label(); + this.ctlMediumLabel = new System.Windows.Forms.Label(); + this.ctlFirmLabel = new System.Windows.Forms.Label(); + this.ctlSilentClicking = new System.Windows.Forms.CheckBox(); + this.ctlMacOSClickOptions = new System.Windows.Forms.RadioButton(); + this.groupBox1 = new System.Windows.Forms.GroupBox(); + this.groupBox2 = new System.Windows.Forms.GroupBox(); + this.ctlMaximumFeedback = new System.Windows.Forms.RadioButton(); + this.ctlDisableFeedback = new System.Windows.Forms.RadioButton(); + this.ctlFocusHack = new System.Windows.Forms.TextBox(); + this.groupBox3 = new System.Windows.Forms.GroupBox(); + this.ctlStopSizeLabel = new System.Windows.Forms.Label(); + this.ctlStopSizeValue = new System.Windows.Forms.TextBox(); + this.ctlStopPressureLabel = new System.Windows.Forms.Label(); + this.ctlStopPressureValue = new System.Windows.Forms.TextBox(); + this.ctlStopSize = new System.Windows.Forms.RadioButton(); + this.ctlStopPressure = new System.Windows.Forms.RadioButton(); + this.ctlStopDoNothing = new System.Windows.Forms.RadioButton(); + this.groupBox4 = new System.Windows.Forms.GroupBox(); + this.ctlIgnoreNearFingers = new System.Windows.Forms.CheckBox(); + this.ctlIgnoreButtonFinger = new System.Windows.Forms.CheckBox(); + this.ctlPalmRejection = new System.Windows.Forms.CheckBox(); + ((System.ComponentModel.ISupportInitialize)(this.ctlFeedback)).BeginInit(); + this.groupBox1.SuspendLayout(); + this.groupBox2.SuspendLayout(); + this.groupBox3.SuspendLayout(); + this.groupBox4.SuspendLayout(); + this.SuspendLayout(); + // + // ctlInstallDriver + // + this.ctlInstallDriver.Location = new System.Drawing.Point(13, 537); + this.ctlInstallDriver.Name = "ctlInstallDriver"; + this.ctlInstallDriver.Size = new System.Drawing.Size(195, 33); + this.ctlInstallDriver.TabIndex = 5; + this.ctlInstallDriver.Text = "Install Driver"; + this.ctlInstallDriver.UseVisualStyleBackColor = true; + this.ctlInstallDriver.Click += new System.EventHandler(this.ctlInstallDriver_Click); + // + // ctlApply + // + this.ctlApply.Location = new System.Drawing.Point(608, 537); + this.ctlApply.Name = "ctlApply"; + this.ctlApply.Size = new System.Drawing.Size(195, 33); + this.ctlApply.TabIndex = 0; + this.ctlApply.Text = "Apply"; + this.ctlApply.UseVisualStyleBackColor = true; + this.ctlApply.Click += new System.EventHandler(this.ctlApply_Click); + // + // ctlFeedback + // + this.ctlFeedback.Location = new System.Drawing.Point(53, 106); + this.ctlFeedback.Maximum = 2; + this.ctlFeedback.Name = "ctlFeedback"; + this.ctlFeedback.Size = new System.Drawing.Size(227, 56); + this.ctlFeedback.TabIndex = 2; + this.ctlFeedback.Value = 1; + // + // ctlLightLabel + // + this.ctlLightLabel.AutoSize = true; + this.ctlLightLabel.Location = new System.Drawing.Point(50, 145); + this.ctlLightLabel.Name = "ctlLightLabel"; + this.ctlLightLabel.Size = new System.Drawing.Size(39, 17); + this.ctlLightLabel.TabIndex = 3; + this.ctlLightLabel.Text = "Light"; + // + // ctlMediumLabel + // + this.ctlMediumLabel.AutoSize = true; + this.ctlMediumLabel.Location = new System.Drawing.Point(139, 145); + this.ctlMediumLabel.Name = "ctlMediumLabel"; + this.ctlMediumLabel.Size = new System.Drawing.Size(57, 17); + this.ctlMediumLabel.TabIndex = 4; + this.ctlMediumLabel.Text = "Medium"; + // + // ctlFirmLabel + // + this.ctlFirmLabel.AutoSize = true; + this.ctlFirmLabel.Location = new System.Drawing.Point(245, 145); + this.ctlFirmLabel.Name = "ctlFirmLabel"; + this.ctlFirmLabel.Size = new System.Drawing.Size(35, 17); + this.ctlFirmLabel.TabIndex = 5; + this.ctlFirmLabel.Text = "Firm"; + // + // ctlSilentClicking + // + this.ctlSilentClicking.AutoSize = true; + this.ctlSilentClicking.Location = new System.Drawing.Point(112, 68); + this.ctlSilentClicking.Name = "ctlSilentClicking"; + this.ctlSilentClicking.Size = new System.Drawing.Size(115, 21); + this.ctlSilentClicking.TabIndex = 1; + this.ctlSilentClicking.Text = "Silent clicking"; + this.ctlSilentClicking.UseVisualStyleBackColor = true; + // + // ctlMacOSClickOptions + // + this.ctlMacOSClickOptions.AutoSize = true; + this.ctlMacOSClickOptions.Location = new System.Drawing.Point(17, 21); + this.ctlMacOSClickOptions.Name = "ctlMacOSClickOptions"; + this.ctlMacOSClickOptions.Size = new System.Drawing.Size(194, 21); + this.ctlMacOSClickOptions.TabIndex = 0; + this.ctlMacOSClickOptions.TabStop = true; + this.ctlMacOSClickOptions.Text = "Use macOS Click Options:"; + this.ctlMacOSClickOptions.UseVisualStyleBackColor = true; + this.ctlMacOSClickOptions.CheckedChanged += new System.EventHandler(this.ctlClickOptions_CheckedChanged); + // + // groupBox1 + // + this.groupBox1.Controls.Add(this.ctlLightLabel); + this.groupBox1.Controls.Add(this.ctlMacOSClickOptions); + this.groupBox1.Controls.Add(this.ctlSilentClicking); + this.groupBox1.Controls.Add(this.ctlFirmLabel); + this.groupBox1.Controls.Add(this.ctlMediumLabel); + this.groupBox1.Controls.Add(this.ctlFeedback); + this.groupBox1.Location = new System.Drawing.Point(12, 12); + this.groupBox1.Name = "groupBox1"; + this.groupBox1.Size = new System.Drawing.Size(340, 194); + this.groupBox1.TabIndex = 1; + this.groupBox1.TabStop = false; + // + // groupBox2 + // + this.groupBox2.Controls.Add(this.ctlMaximumFeedback); + this.groupBox2.Controls.Add(this.ctlDisableFeedback); + this.groupBox2.Location = new System.Drawing.Point(358, 12); + this.groupBox2.Name = "groupBox2"; + this.groupBox2.Size = new System.Drawing.Size(445, 194); + this.groupBox2.TabIndex = 2; + this.groupBox2.TabStop = false; + this.groupBox2.Text = "Click Options NOT available in macOS:"; + // + // ctlMaximumFeedback + // + this.ctlMaximumFeedback.AutoSize = true; + this.ctlMaximumFeedback.Location = new System.Drawing.Point(28, 98); + this.ctlMaximumFeedback.Name = "ctlMaximumFeedback"; + this.ctlMaximumFeedback.Size = new System.Drawing.Size(332, 21); + this.ctlMaximumFeedback.TabIndex = 1; + this.ctlMaximumFeedback.TabStop = true; + this.ctlMaximumFeedback.Text = "Maximum haptic feedback (very clicky and loud!)"; + this.ctlMaximumFeedback.UseVisualStyleBackColor = true; + this.ctlMaximumFeedback.CheckedChanged += new System.EventHandler(this.ctlClickOptions_CheckedChanged); + // + // ctlDisableFeedback + // + this.ctlDisableFeedback.AutoSize = true; + this.ctlDisableFeedback.Location = new System.Drawing.Point(28, 61); + this.ctlDisableFeedback.Name = "ctlDisableFeedback"; + this.ctlDisableFeedback.Size = new System.Drawing.Size(398, 21); + this.ctlDisableFeedback.TabIndex = 0; + this.ctlDisableFeedback.TabStop = true; + this.ctlDisableFeedback.Text = "Disable haptic feedback and force touch button completely"; + this.ctlDisableFeedback.UseVisualStyleBackColor = true; + this.ctlDisableFeedback.CheckedChanged += new System.EventHandler(this.ctlClickOptions_CheckedChanged); + // + // ctlFocusHack + // + this.ctlFocusHack.Location = new System.Drawing.Point(-32, -32); + this.ctlFocusHack.Name = "ctlFocusHack"; + this.ctlFocusHack.Size = new System.Drawing.Size(22, 22); + this.ctlFocusHack.TabIndex = 10; + this.ctlFocusHack.TabStop = false; + // + // groupBox3 + // + this.groupBox3.Controls.Add(this.ctlStopSizeLabel); + this.groupBox3.Controls.Add(this.ctlStopSizeValue); + this.groupBox3.Controls.Add(this.ctlStopPressureLabel); + this.groupBox3.Controls.Add(this.ctlStopPressureValue); + this.groupBox3.Controls.Add(this.ctlStopSize); + this.groupBox3.Controls.Add(this.ctlStopPressure); + this.groupBox3.Controls.Add(this.ctlStopDoNothing); + this.groupBox3.Location = new System.Drawing.Point(12, 212); + this.groupBox3.Name = "groupBox3"; + this.groupBox3.Size = new System.Drawing.Size(791, 147); + this.groupBox3.TabIndex = 3; + this.groupBox3.TabStop = false; + this.groupBox3.Text = "When you lift your finger from the trackpad:"; + // + // ctlStopSizeLabel + // + this.ctlStopSizeLabel.AutoSize = true; + this.ctlStopSizeLabel.Location = new System.Drawing.Point(491, 108); + this.ctlStopSizeLabel.Name = "ctlStopSizeLabel"; + this.ctlStopSizeLabel.Size = new System.Drawing.Size(164, 17); + this.ctlStopSizeLabel.TabIndex = 6; + this.ctlStopSizeLabel.Text = "units. (7 is a good value)"; + this.ctlStopSizeLabel.Click += new System.EventHandler(this.ctlStop_Click); + // + // ctlStopSizeValue + // + this.ctlStopSizeValue.Location = new System.Drawing.Point(434, 106); + this.ctlStopSizeValue.Name = "ctlStopSizeValue"; + this.ctlStopSizeValue.Size = new System.Drawing.Size(51, 22); + this.ctlStopSizeValue.TabIndex = 5; + this.ctlStopSizeValue.Text = "7"; + this.ctlStopSizeValue.TextAlign = System.Windows.Forms.HorizontalAlignment.Center; + // + // ctlStopPressureLabel + // + this.ctlStopPressureLabel.AutoSize = true; + this.ctlStopPressureLabel.Location = new System.Drawing.Point(419, 71); + this.ctlStopPressureLabel.Name = "ctlStopPressureLabel"; + this.ctlStopPressureLabel.Size = new System.Drawing.Size(306, 17); + this.ctlStopPressureLabel.TabIndex = 3; + this.ctlStopPressureLabel.Text = "units. (0 means no pressure; 0 is a good value)"; + this.ctlStopPressureLabel.Click += new System.EventHandler(this.ctlStop_Click); + // + // ctlStopPressureValue + // + this.ctlStopPressureValue.Location = new System.Drawing.Point(362, 69); + this.ctlStopPressureValue.Name = "ctlStopPressureValue"; + this.ctlStopPressureValue.Size = new System.Drawing.Size(51, 22); + this.ctlStopPressureValue.TabIndex = 2; + this.ctlStopPressureValue.Text = "0"; + this.ctlStopPressureValue.TextAlign = System.Windows.Forms.HorizontalAlignment.Center; + // + // ctlStopSize + // + this.ctlStopSize.AutoSize = true; + this.ctlStopSize.Location = new System.Drawing.Point(17, 106); + this.ctlStopSize.Name = "ctlStopSize"; + this.ctlStopSize.Size = new System.Drawing.Size(453, 21); + this.ctlStopSize.TabIndex = 4; + this.ctlStopSize.TabStop = true; + this.ctlStopSize.Text = "Stop the pointer if the size of the touch area is less than or equal to"; + this.ctlStopSize.UseVisualStyleBackColor = true; + this.ctlStopSize.CheckedChanged += new System.EventHandler(this.ctlStop_CheckedChanged); + // + // ctlStopPressure + // + this.ctlStopPressure.AutoSize = true; + this.ctlStopPressure.Location = new System.Drawing.Point(17, 69); + this.ctlStopPressure.Name = "ctlStopPressure"; + this.ctlStopPressure.Size = new System.Drawing.Size(372, 21); + this.ctlStopPressure.TabIndex = 1; + this.ctlStopPressure.TabStop = true; + this.ctlStopPressure.Text = "Stop the pointer if the pressure is less than or equal to"; + this.ctlStopPressure.UseVisualStyleBackColor = true; + this.ctlStopPressure.CheckedChanged += new System.EventHandler(this.ctlStop_CheckedChanged); + // + // ctlStopDoNothing + // + this.ctlStopDoNothing.AutoSize = true; + this.ctlStopDoNothing.Location = new System.Drawing.Point(17, 32); + this.ctlStopDoNothing.Name = "ctlStopDoNothing"; + this.ctlStopDoNothing.Size = new System.Drawing.Size(98, 21); + this.ctlStopDoNothing.TabIndex = 0; + this.ctlStopDoNothing.TabStop = true; + this.ctlStopDoNothing.Text = "Do nothing"; + this.ctlStopDoNothing.UseVisualStyleBackColor = true; + this.ctlStopDoNothing.CheckedChanged += new System.EventHandler(this.ctlStop_CheckedChanged); + // + // groupBox4 + // + this.groupBox4.Controls.Add(this.ctlPalmRejection); + this.groupBox4.Controls.Add(this.ctlIgnoreButtonFinger); + this.groupBox4.Controls.Add(this.ctlIgnoreNearFingers); + this.groupBox4.Location = new System.Drawing.Point(12, 365); + this.groupBox4.Name = "groupBox4"; + this.groupBox4.Size = new System.Drawing.Size(791, 147); + this.groupBox4.TabIndex = 4; + this.groupBox4.TabStop = false; + this.groupBox4.Text = "Other options:"; + // + // ctlIgnoreNearFingers + // + this.ctlIgnoreNearFingers.AutoSize = true; + this.ctlIgnoreNearFingers.Location = new System.Drawing.Point(17, 32); + this.ctlIgnoreNearFingers.Name = "ctlIgnoreNearFingers"; + this.ctlIgnoreNearFingers.Size = new System.Drawing.Size(400, 21); + this.ctlIgnoreNearFingers.TabIndex = 0; + this.ctlIgnoreNearFingers.Text = "Ignore input from fingers not touching the trackpad surface"; + this.ctlIgnoreNearFingers.UseVisualStyleBackColor = true; + // + // ctlIgnoreButtonFinger + // + this.ctlIgnoreButtonFinger.Location = new System.Drawing.Point(17, 58); + this.ctlIgnoreButtonFinger.Name = "ctlIgnoreButtonFinger"; + this.ctlIgnoreButtonFinger.Size = new System.Drawing.Size(755, 41); + this.ctlIgnoreButtonFinger.TabIndex = 1; + this.ctlIgnoreButtonFinger.Text = "Ignore input from the finger used to click the force touch button (useful for dra" + + "gging, if you use your thumb to click the button and your index finger to move t" + + "he pointer, for example)"; + this.ctlIgnoreButtonFinger.UseVisualStyleBackColor = true; + // + // ctlPalmRejection + // + this.ctlPalmRejection.AutoSize = true; + this.ctlPalmRejection.Location = new System.Drawing.Point(17, 106); + this.ctlPalmRejection.Name = "ctlPalmRejection"; + this.ctlPalmRejection.Size = new System.Drawing.Size(124, 21); + this.ctlPalmRejection.TabIndex = 2; + this.ctlPalmRejection.Text = "Palm Rejection"; + this.ctlPalmRejection.UseVisualStyleBackColor = true; + // + // Main + // + this.AutoScaleDimensions = new System.Drawing.SizeF(8F, 16F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(815, 582); + this.Controls.Add(this.groupBox4); + this.Controls.Add(this.groupBox3); + this.Controls.Add(this.ctlFocusHack); + this.Controls.Add(this.groupBox2); + this.Controls.Add(this.groupBox1); + this.Controls.Add(this.ctlApply); + this.Controls.Add(this.ctlInstallDriver); + this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle; + this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon"))); + this.MaximizeBox = false; + this.Name = "Main"; + this.Text = "Magic Trackpad 2 Control Panel"; + this.Load += new System.EventHandler(this.Main_Load); + ((System.ComponentModel.ISupportInitialize)(this.ctlFeedback)).EndInit(); + this.groupBox1.ResumeLayout(false); + this.groupBox1.PerformLayout(); + this.groupBox2.ResumeLayout(false); + this.groupBox2.PerformLayout(); + this.groupBox3.ResumeLayout(false); + this.groupBox3.PerformLayout(); + this.groupBox4.ResumeLayout(false); + this.groupBox4.PerformLayout(); + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private System.Windows.Forms.Button ctlInstallDriver; + private System.Windows.Forms.Button ctlApply; + private System.Windows.Forms.TrackBar ctlFeedback; + private System.Windows.Forms.Label ctlLightLabel; + private System.Windows.Forms.Label ctlMediumLabel; + private System.Windows.Forms.Label ctlFirmLabel; + private System.Windows.Forms.CheckBox ctlSilentClicking; + private System.Windows.Forms.RadioButton ctlMacOSClickOptions; + private System.Windows.Forms.GroupBox groupBox1; + private System.Windows.Forms.GroupBox groupBox2; + private System.Windows.Forms.RadioButton ctlMaximumFeedback; + private System.Windows.Forms.RadioButton ctlDisableFeedback; + private System.Windows.Forms.TextBox ctlFocusHack; + private System.Windows.Forms.GroupBox groupBox3; + private System.Windows.Forms.Label ctlStopSizeLabel; + private System.Windows.Forms.TextBox ctlStopSizeValue; + private System.Windows.Forms.Label ctlStopPressureLabel; + private System.Windows.Forms.TextBox ctlStopPressureValue; + private System.Windows.Forms.RadioButton ctlStopSize; + private System.Windows.Forms.RadioButton ctlStopPressure; + private System.Windows.Forms.RadioButton ctlStopDoNothing; + private System.Windows.Forms.GroupBox groupBox4; + private System.Windows.Forms.CheckBox ctlIgnoreButtonFinger; + private System.Windows.Forms.CheckBox ctlIgnoreNearFingers; + private System.Windows.Forms.CheckBox ctlPalmRejection; + } +} + diff --git a/AmtPtpControlPanel/Main.cs b/AmtPtpControlPanel/Main.cs new file mode 100644 index 0000000..de5c7c8 --- /dev/null +++ b/AmtPtpControlPanel/Main.cs @@ -0,0 +1,689 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; +using System.Runtime.InteropServices; +using Microsoft.Win32.SafeHandles; +using System.IO; +using System.Threading; +using System.Security.Principal; +using System.Security.AccessControl; +using System.Reflection; +using Microsoft.Win32; + +namespace AmtPtpControlPanel +{ + public partial class Main : Form + { + public Main() + { + InitializeComponent(); + } + + private void ctlInstallDriver_Click(object sender, EventArgs e) + { + string fn = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Windows), + @"System32\DriverStore\FileRepository\amtptpdevice.inf_amd64_5de6239780ba286e\AmtPtpDeviceUsbUm.dll"); + string src = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), + @"AmtPtpDeviceUsbUm.dll"); + + Action showErr = (string str) => + { + MessageBox.Show(str, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); + }; + + if (!File.Exists(src)) + { + showErr("\"" + src + "\" doesn't exist."); + return; + } + + if (!File.Exists(fn)) + { + showErr("\"" + fn + "\" doesn't exist."); + return; + } + + List errs = new List(); + Action err = (string str, Exception ex) => + { + errs.Add(str + (ex == null ? "" : ": " + ex.ToString())); + }; + + Action replaceDriver = () => + { + try + { + if (!TokenManipulator.AddPrivilege("SeRestorePrivilege")) + throw new Exception("TokenManipulator.AddPrivilege of SeRestorePrivilege returned FALSE."); + if (!TokenManipulator.AddPrivilege("SeTakeOwnershipPrivilege")) + throw new Exception("TokenManipulator.AddPrivilege of SeTakeOwnershipPrivilege returned FALSE."); + } + catch (Exception ex) + { + try + { + TokenManipulator.RemovePrivilege("SeRestorePrivilege"); + } + catch + { + } + + try + { + TokenManipulator.RemovePrivilege("SeTakeOwnershipPrivilege"); + } + catch + { + } + + err("AddPrivilege failed", ex); + return; + } + + NTAccount prevOwner = null; + try + { + var fs = File.GetAccessControl(fn); + prevOwner = (NTAccount)fs.GetOwner(typeof(NTAccount)); + } + catch (Exception ex) + { + err("GetOwner failed", ex); + } + + bool ownerSet = false; + if (prevOwner != null) + try + { + var fs = File.GetAccessControl(fn); + fs.SetOwner(WindowsIdentity.GetCurrent().User); + File.SetAccessControl(fn, fs); + ownerSet = true; + } + catch (Exception ex) + { + err("SetOwner #1 failed", ex); + } + + FileSystemAccessRule accessRule = null; + if (ownerSet) + try + { + accessRule = new FileSystemAccessRule( + WindowsIdentity.GetCurrent().User, + FileSystemRights.FullControl, + InheritanceFlags.None, + PropagationFlags.NoPropagateInherit, + AccessControlType.Allow); + } + catch (Exception ex) + { + err("FileSystemAccessRule creation failed", ex); + } + + bool accessRuleAdded = false; + if (accessRule != null) + try + { + var fs = File.GetAccessControl(fn); + fs.AddAccessRule(accessRule); + File.SetAccessControl(fn, fs); + accessRuleAdded = true; + } + catch (Exception ex) + { + err("AddAccessRule failed", ex); + } + + if (accessRuleAdded) + try + { + File.Copy(src, fn, true); + } + catch (Exception ex) + { + err("Copy failed", ex); + } + + if (accessRuleAdded) + try + { + var fs = File.GetAccessControl(fn); + fs.RemoveAccessRule(accessRule); + File.SetAccessControl(fn, fs); + } + catch (Exception ex) + { + err("RemoveAccessRule failed", ex); + } + + if (prevOwner != null) + try + { + var fs = File.GetAccessControl(fn); + fs.SetOwner(prevOwner); + File.SetAccessControl(fn, fs); + } + catch (Exception ex) + { + err("SetOwner #2 failed", ex); + } + + try + { + if (!TokenManipulator.RemovePrivilege("SeRestorePrivilege")) + throw new Exception("TokenManipulator.RemovePrivilege of SeRestorePrivilege returned FALSE."); + } + catch (Exception ex) + { + err("RemovePrivilege failed", ex); + } + + try + { + if (!TokenManipulator.RemovePrivilege("SeTakeOwnershipPrivilege")) + throw new Exception("TokenManipulator.RemovePrivilege of SeTakeOwnershipPrivilege returned FALSE."); + } + catch (Exception ex) + { + err("RemovePrivilege failed", ex); + } + }; + + using (new ButtonWait((Button)sender)) + Device.RestartDevices(replaceDriver); + + if (errs.Count == 0) + MessageBox.Show("Operation succeeded!"); + else + foreach (string errStr in errs) + showErr(errStr); + } + + private void ctlApply_Click(object sender, EventArgs e) + { + using (new ButtonWait((Button)sender)) + if (SaveSettings()) + Device.RestartDevices(); + } + + private void ctlClickOptions_CheckedChanged(object sender, EventArgs e) + { + if (!((RadioButton)sender).Checked) + return; + + RadioButton[] ctls = { ctlMacOSClickOptions, ctlDisableFeedback, ctlMaximumFeedback }; + + foreach (var ctl in ctls) + if (ctl != sender) + ctl.Checked = false; + + Control[] macCtls = { ctlSilentClicking, ctlFeedback, ctlLightLabel, ctlMediumLabel, ctlFirmLabel }; + + bool macEnabled = ctlMacOSClickOptions.Checked; + foreach (var ctl in macCtls) + ctl.Enabled = macEnabled; + } + + private void ctlStop_CheckedChanged(object sender, EventArgs e) + { + if (!((RadioButton)sender).Checked) + return; + + ctlStopPressureValue.Enabled = sender == ctlStopPressure; + ctlStopSizeValue.Enabled = sender == ctlStopSize; + } + + private void ctlStop_Click(object sender, EventArgs e) + { + if (sender == ctlStopPressureValue || sender == ctlStopPressureLabel) + ctlStopPressure.Checked = true; + else if (sender == ctlStopSizeValue || sender == ctlStopSizeLabel) + ctlStopSize.Checked = true; + } + + private delegate void delStringRefInt32Void(string _1, ref Int32 _2); + + private void Main_Load(object sender, EventArgs e) + { + Int32 buttonDisabled = 0; + Int32 feedbackClick = 0x060617; + Int32 feedbackRelease = 0x000014; + Int32 stopPressure = 0; + Int32 stopSize = -1; + Int32 ignoreButtonFinger = 1; + Int32 ignoreNearFingers = 1; + Int32 palmRejection = 1; + + try + { + using (RegistryKey key = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows NT\CurrentVersion\WUDF\Services\AmtPtpDeviceUsbUm\Parameters")) + { + delStringRefInt32Void get = (string name, ref Int32 output) => + { + try + { + output = (Int32)key.GetValue(name); + } + catch + { + } + }; + + get("ButtonDisabled", ref buttonDisabled); + get("FeedbackClick", ref feedbackClick); + get("FeedbackRelease", ref feedbackRelease); + get("StopPressure", ref stopPressure); + get("StopSize", ref stopSize); + get("IgnoreButtonFinger", ref ignoreButtonFinger); + get("IgnoreNearFingers", ref ignoreNearFingers); + get("PalmRejection", ref palmRejection); + } + } + catch + { + } + + if (buttonDisabled != 0) + ctlDisableFeedback.Checked = true; + else if (feedbackClick == 0xffffff && feedbackRelease == 0xffffff) + ctlMaximumFeedback.Checked = true; + else + { + ctlMacOSClickOptions.Checked = true; + + if ((feedbackClick & 0xffff00) == 0 && (feedbackRelease & 0xffff00) == 0) + ctlSilentClicking.Checked = true; + + if ((feedbackClick & 0x0000ff) == 0x15 && (feedbackRelease & 0x0000ff) == 0x10) + ctlFeedback.Value = 0; + else if ((feedbackClick & 0x0000ff) == 0x1e && (feedbackRelease & 0x0000ff) == 0x18) + ctlFeedback.Value = 2; + } + + if (stopPressure == -1 && stopSize == -1) + ctlStopDoNothing.Checked = true; + else if (stopPressure != -1) + { + ctlStopPressure.Checked = true; + ctlStopPressureValue.Text = stopPressure.ToString(); + } + else + { + ctlStopSize.Checked = true; + ctlStopSizeValue.Text = stopSize.ToString(); + } + + if (ignoreButtonFinger != 0) + ctlIgnoreButtonFinger.Checked = true; + + if (ignoreNearFingers != 0) + ctlIgnoreNearFingers.Checked = true; + + if (palmRejection != 0) + ctlPalmRejection.Checked = true; + } + + private bool SaveSettings() + { + Int32 buttonDisabled = 0; + Int32 feedbackClick = 0x060617; + Int32 feedbackRelease = 0x000014; + Int32 stopPressure = 0; + Int32 stopSize = -1; + Int32 ignoreButtonFinger = 1; + Int32 ignoreNearFingers = 1; + Int32 palmRejection = 1; + + if (ctlDisableFeedback.Checked) + { + buttonDisabled = 1; + feedbackClick = 0; + feedbackRelease = 0; + } + else if (ctlMaximumFeedback.Checked) + { + buttonDisabled = 0; + feedbackClick = 0xffffff; + feedbackRelease = 0xffffff; + } + else + { + buttonDisabled = 0; + feedbackClick = ctlFeedback.Value == 0 ? 0x040415 : ctlFeedback.Value == 1 ? 0x060617 : 0x08081e; + feedbackRelease = ctlFeedback.Value == 0 ? 0x000010 : ctlFeedback.Value == 1 ? 0x000014 : 0x020218; + + if (ctlSilentClicking.Checked) + { + feedbackClick = feedbackClick & 0x0000ff; + feedbackRelease = feedbackRelease & 0x0000ff; + } + } + + if (ctlStopDoNothing.Checked) + { + stopPressure = -1; + stopSize = -1; + } + else if (ctlStopPressure.Checked) + { + stopSize = -1; + + if (!Int32.TryParse(ctlStopPressureValue.Text, out stopPressure) || stopPressure < 0) + { + MessageBox.Show("Pressure must be greater than or equal to 0.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); + return false; + } + } + else + { + stopPressure = -1; + + if (!Int32.TryParse(ctlStopSizeValue.Text, out stopSize) || stopSize < 0) + { + MessageBox.Show("Size must be greater than or equal to 0.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); + return false; + } + } + + ignoreButtonFinger = ctlIgnoreButtonFinger.Checked ? 1 : 0; + ignoreNearFingers = ctlIgnoreNearFingers.Checked ? 1 : 0; + palmRejection = ctlPalmRejection.Checked ? 1 : 0; + + try + { + using (RegistryKey keyServices = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows NT\CurrentVersion\WUDF\Services", true)) + using (RegistryKey keyAmtPtpDeviceUsbUm = keyServices.CreateSubKey("AmtPtpDeviceUsbUm", true)) + using (RegistryKey keyParameters = keyAmtPtpDeviceUsbUm.CreateSubKey("Parameters", true)) + { + keyParameters.SetValue("ButtonDisabled", buttonDisabled); + keyParameters.SetValue("FeedbackClick", feedbackClick); + keyParameters.SetValue("FeedbackRelease", feedbackRelease); + keyParameters.SetValue("StopPressure", stopPressure); + keyParameters.SetValue("StopSize", stopSize); + keyParameters.SetValue("IgnoreButtonFinger", ignoreButtonFinger); + keyParameters.SetValue("IgnoreNearFingers", ignoreNearFingers); + keyParameters.SetValue("PalmRejection", palmRejection); + } + } + catch (Exception ex) + { + MessageBox.Show("Error writing to the registry: " + ex.ToString(), "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); + return false; + } + + return true; + } + } + + //========= + // Helpers + //========= + + public class ButtonWait : IDisposable + { + public ButtonWait(Button btn) + { + _btn = btn; + Cursor.Current = Cursors.WaitCursor; + Application.UseWaitCursor = true; + _btn.FindForm().Controls["ctlFocusHack"].Focus(); + _btn.Enabled = false; + Application.DoEvents(); + } + + public void Dispose() + { + Cursor.Current = Cursors.Default; + Application.UseWaitCursor = false; + _btn.Enabled = true; + _btn.Focus(); + } + + private Button _btn; + } + + //================= + // Low level stuff + //================= + + public class TokenManipulator // https://stackoverflow.com/questions/17031552/how-do-you-take-file-ownership-with-powershell/17047190#17047190 + { + [DllImport("advapi32.dll", ExactSpelling = true, SetLastError = true)] + internal static extern bool AdjustTokenPrivileges(IntPtr htok, bool disall, ref TokPriv1Luid newst, int len, IntPtr prev, IntPtr relen); + + [DllImport("kernel32.dll", ExactSpelling = true)] + internal static extern IntPtr GetCurrentProcess(); + + [DllImport("advapi32.dll", ExactSpelling = true, SetLastError = true)] + internal static extern bool OpenProcessToken(IntPtr h, int acc, ref IntPtr phtok); + + [DllImport("advapi32.dll", SetLastError = true)] + internal static extern bool LookupPrivilegeValue(string host, string name, ref long pluid); + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + internal struct TokPriv1Luid + { + public int Count; + public long Luid; + public int Attr; + } + + internal const int SE_PRIVILEGE_DISABLED = 0x00000000; + internal const int SE_PRIVILEGE_ENABLED = 0x00000002; + internal const int TOKEN_QUERY = 0x00000008; + internal const int TOKEN_ADJUST_PRIVILEGES = 0x00000020; + + public const string SE_ASSIGNPRIMARYTOKEN_NAME = "SeAssignPrimaryTokenPrivilege"; + public const string SE_AUDIT_NAME = "SeAuditPrivilege"; + public const string SE_BACKUP_NAME = "SeBackupPrivilege"; + public const string SE_CHANGE_NOTIFY_NAME = "SeChangeNotifyPrivilege"; + public const string SE_CREATE_GLOBAL_NAME = "SeCreateGlobalPrivilege"; + public const string SE_CREATE_PAGEFILE_NAME = "SeCreatePagefilePrivilege"; + public const string SE_CREATE_PERMANENT_NAME = "SeCreatePermanentPrivilege"; + public const string SE_CREATE_SYMBOLIC_LINK_NAME = "SeCreateSymbolicLinkPrivilege"; + public const string SE_CREATE_TOKEN_NAME = "SeCreateTokenPrivilege"; + public const string SE_DEBUG_NAME = "SeDebugPrivilege"; + public const string SE_ENABLE_DELEGATION_NAME = "SeEnableDelegationPrivilege"; + public const string SE_IMPERSONATE_NAME = "SeImpersonatePrivilege"; + public const string SE_INC_BASE_PRIORITY_NAME = "SeIncreaseBasePriorityPrivilege"; + public const string SE_INCREASE_QUOTA_NAME = "SeIncreaseQuotaPrivilege"; + public const string SE_INC_WORKING_SET_NAME = "SeIncreaseWorkingSetPrivilege"; + public const string SE_LOAD_DRIVER_NAME = "SeLoadDriverPrivilege"; + public const string SE_LOCK_MEMORY_NAME = "SeLockMemoryPrivilege"; + public const string SE_MACHINE_ACCOUNT_NAME = "SeMachineAccountPrivilege"; + public const string SE_MANAGE_VOLUME_NAME = "SeManageVolumePrivilege"; + public const string SE_PROF_SINGLE_PROCESS_NAME = "SeProfileSingleProcessPrivilege"; + public const string SE_RELABEL_NAME = "SeRelabelPrivilege"; + public const string SE_REMOTE_SHUTDOWN_NAME = "SeRemoteShutdownPrivilege"; + public const string SE_RESTORE_NAME = "SeRestorePrivilege"; + public const string SE_SECURITY_NAME = "SeSecurityPrivilege"; + public const string SE_SHUTDOWN_NAME = "SeShutdownPrivilege"; + public const string SE_SYNC_AGENT_NAME = "SeSyncAgentPrivilege"; + public const string SE_SYSTEM_ENVIRONMENT_NAME = "SeSystemEnvironmentPrivilege"; + public const string SE_SYSTEM_PROFILE_NAME = "SeSystemProfilePrivilege"; + public const string SE_SYSTEMTIME_NAME = "SeSystemtimePrivilege"; + public const string SE_TAKE_OWNERSHIP_NAME = "SeTakeOwnershipPrivilege"; + public const string SE_TCB_NAME = "SeTcbPrivilege"; + public const string SE_TIME_ZONE_NAME = "SeTimeZonePrivilege"; + public const string SE_TRUSTED_CREDMAN_ACCESS_NAME = "SeTrustedCredManAccessPrivilege"; + public const string SE_UNDOCK_NAME = "SeUndockPrivilege"; + public const string SE_UNSOLICITED_INPUT_NAME = "SeUnsolicitedInputPrivilege"; + + public static bool AddPrivilege(string privilege) + { + try + { + bool retVal; + TokPriv1Luid tp; + IntPtr hproc = GetCurrentProcess(); + IntPtr htok = IntPtr.Zero; + retVal = OpenProcessToken(hproc, TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, ref htok); + tp.Count = 1; + tp.Luid = 0; + tp.Attr = SE_PRIVILEGE_ENABLED; + retVal = LookupPrivilegeValue(null, privilege, ref tp.Luid); + retVal = AdjustTokenPrivileges(htok, false, ref tp, 0, IntPtr.Zero, IntPtr.Zero); + return retVal; + } + catch (Exception ex) + { + throw ex; + } + } + + public static bool RemovePrivilege(string privilege) + { + try + { + bool retVal; + TokPriv1Luid tp; + IntPtr hproc = GetCurrentProcess(); + IntPtr htok = IntPtr.Zero; + retVal = OpenProcessToken(hproc, TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, ref htok); + tp.Count = 1; + tp.Luid = 0; + tp.Attr = SE_PRIVILEGE_DISABLED; + retVal = LookupPrivilegeValue(null, privilege, ref tp.Luid); + retVal = AdjustTokenPrivileges(htok, false, ref tp, 0, IntPtr.Zero, IntPtr.Zero); + return retVal; + } + catch (Exception ex) + { + throw ex; + } + } + } + + public class Device + { + public static bool RestartDevices(Action action = null) + { + Guid guid = new Guid("4a5064e5-7d39-41d1-a0e4-81097edce967"); // <-- driver device interface + + bool success = false; + IntPtr deviceInfoSet = EnableDevices(false, guid, INVALID_HANDLE_VALUE, ref success); + + if (action != null) + action(); + + if (success) + EnableDevices(true, guid, deviceInfoSet, ref success); + + if (deviceInfoSet != INVALID_HANDLE_VALUE) + SetupDiDestroyDeviceInfoList(deviceInfoSet); + + return success; + } + + public static IntPtr EnableDevices(bool enable, Guid guid, IntPtr deviceInfoSetOverride, ref bool success) + { + IntPtr deviceInfoSet = deviceInfoSetOverride != INVALID_HANDLE_VALUE ? deviceInfoSetOverride : + SetupDiGetClassDevs(ref guid, IntPtr.Zero, IntPtr.Zero, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE); + if (deviceInfoSet == INVALID_HANDLE_VALUE) + return INVALID_HANDLE_VALUE; + + uint index = 0; + + while (true) + { + SP_DEVINFO_DATA devInfo = new SP_DEVINFO_DATA(); + devInfo.cbSize = (UInt32)Marshal.SizeOf(devInfo); + if (!SetupDiEnumDeviceInfo(deviceInfoSet, index, ref devInfo)) + break; + else + index++; + + SP_PROPCHANGE_PARAMS propChange = new SP_PROPCHANGE_PARAMS(); + propChange.ClassInstallHeader = new SP_CLASSINSTALL_HEADER(); + propChange.ClassInstallHeader.cbSize = (UInt32)Marshal.SizeOf(propChange.ClassInstallHeader); + propChange.ClassInstallHeader.InstallFunction = DIF_PROPERTYCHANGE; + propChange.Scope = DICS_FLAG_GLOBAL; + propChange.StateChange = enable ? DICS_ENABLE : DICS_DISABLE; + + if (SetupDiSetClassInstallParams(deviceInfoSet, ref devInfo, ref propChange, Marshal.SizeOf(propChange))) + { + if (SetupDiCallClassInstaller(DIF_PROPERTYCHANGE, deviceInfoSet, ref devInfo)) + { + success = true; + } + } + } + + return deviceInfoSet; + } + + // P/Invoke: + + static readonly IntPtr INVALID_HANDLE_VALUE = new IntPtr(-1); + + const int DIGCF_DEFAULT = 0x1; + const int DIGCF_PRESENT = 0x2; + const int DIGCF_ALLCLASSES = 0x4; + const int DIGCF_PROFILE = 0x8; + const int DIGCF_DEVICEINTERFACE = 0x10; + + [DllImport("setupapi.dll", CharSet = CharSet.Auto)] + static extern IntPtr SetupDiGetClassDevs( + ref Guid ClassGuid, + IntPtr Enumerator, + IntPtr hwndParent, + int Flags + ); + + [DllImport("setupapi.dll", SetLastError = true)] + public static extern bool SetupDiDestroyDeviceInfoList + ( + IntPtr DeviceInfoSet + ); + + [StructLayout(LayoutKind.Sequential)] + struct SP_DEVINFO_DATA + { + public UInt32 cbSize; + public Guid ClassGuid; + public UInt32 DevInst; + public IntPtr Reserved; + } + + [DllImport("setupapi.dll", SetLastError = true)] + static extern bool SetupDiEnumDeviceInfo(IntPtr DeviceInfoSet, uint MemberIndex, ref SP_DEVINFO_DATA DeviceInfoData); + + [DllImport("setupapi.dll", SetLastError = true, CharSet = CharSet.Auto)] + static extern bool SetupDiSetClassInstallParams(IntPtr DeviceInfoSet, ref SP_DEVINFO_DATA DeviceInfoData, ref SP_PROPCHANGE_PARAMS ClassInstallParams, int ClassInstallParamsSize); + + [StructLayout(LayoutKind.Sequential)] + struct SP_CLASSINSTALL_HEADER + { + public UInt32 cbSize; + public UInt32 InstallFunction; + } + + [StructLayout(LayoutKind.Sequential)] + struct SP_PROPCHANGE_PARAMS + { + public SP_CLASSINSTALL_HEADER ClassInstallHeader; + public UInt32 StateChange; + public UInt32 Scope; + public UInt32 HwProfile; + } + + const uint DIF_PROPERTYCHANGE = 0x12; + const uint DICS_ENABLE = 1; + const uint DICS_DISABLE = 2; + const uint DICS_FLAG_GLOBAL = 1; + + [DllImport("setupapi.dll", SetLastError = true)] + static extern bool SetupDiCallClassInstaller( + UInt32 InstallFunction, + IntPtr DeviceInfoSet, + ref SP_DEVINFO_DATA DeviceInfoData + ); + } +} diff --git a/AmtPtpControlPanel/Main.resx b/AmtPtpControlPanel/Main.resx new file mode 100644 index 0000000..6bb0a52 --- /dev/null +++ b/AmtPtpControlPanel/Main.resx @@ -0,0 +1,994 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + + AAABAAEAAAAAAAEAIAB/ywAAFgAAAIlQTkcNChoKAAAADUlIRFIAAAEAAAABAAgGAAAAXHKoZgAAAAFv + ck5UAc+id5oAAIAASURBVHja7P13kKN7siWGUYaiJLoQSVFcRjAkBVekFNRSbldaUdqgqKDWcbm7783M + ezPzxlxv+t7bt73vrury3nvvvfdVAApAoVAO5b23KJSvanvvzLzdTZ2TH1Cuu2eef2PwR8b34QPwAV2N + c/Jk/vKX+a+JyL/mN7/57XfT/H8Ev/nNTwB+85vf/ATgN7/5zU8AfvOb3/wE4De/+c1PAH7zm9/8BOA3 + v/nNTwB+85vf/ATgN7/5zU8AfvOb3/wE4De/+c1PAH7zm9/8BOA3v/nNTwB+85vf/ATgN7/5zU8AfvOb + 3/wE4De/+c1PAH7zm9/8BOA3v/nNTwB+85vf/ATgN7/5zU8AfvOb3/wE4De/+c1PAH7zm9/8BOA3v/nN + TwB+85vf/ATgN7/5zU8AfvOb3/wE4De/+c1PAH7zm9/8BOA3v/nNTwB+85vf/ATgN7/5zU8AfvOb3/wE + 4De/+c1PAH7zm9/8BOA3v/kJwP9H8Jvf/ATgN7/5zU8AfvOb3/wE4De/+c1PAH7zm9/8BOA3v/nNTwB+ + 89tfrB0dHf4HSwsL/+Xc9PTfWVtZ/t+/fPny3/b/XfwE4LffUtvb3f0bo0ND/3DQ1v1RVcQTaYgNkrqw + x1IWeFcKHt6Q7HtXZbi35w/mpqf+7998883/zP838xOA334LbG119T93dltuuBoqZLCqSJwl2WIvSBNT + Wqx0JUZKVdA9qQi6K+U4Fj2+KTl3r0r6zSsgA8f3lhbm/8v9vb3/6Be/+MW/7v9b+gnAb3/F9q/+1b/6 + H/jsT/O+V69e/VszU1N/t9/cFWArSJeBygIZaSiXUZirukgGK/KlvzhLulKjxJ6fLLacJLFkJEhncrS0 + JYRLS2yI1IUHSHngAyl8ckcy7l2XQYvp6rCz9zuzU5N/98WLF/+O///HTwB++0uyuZnpvzNg6bpRHRUo + 1eGPpTjgjgx1d3014nR8d35m+m9vrK/9TXrl88RA0M/Pzf5f+6yWLxpSYqQTgDZlJYk1J0UG4PmHakpk + CMf+ijwZLMsVZ1GmDFbmyUBZjgyU58pASY70F2WJIydVenKSxZIeJ6akKGmLD5P6qKdSFx0kpQgXsu99 + LS5z591hR8/3qRD86sBPAH77CzACeHig/7+vjgsHENNlsDBd+jMTxZIQIZ2Q6m0JYVIT/EBGTO13Uq5+ + Jhm3v5Qhc+fNAVv3J/3dppuZgfcl+8ktyYeUL0OMXxsXKm0p0ZD6cdILsDuLc8RZmCX23BRx5KZKfykA + X5onrooCGSqnFYIY8mWwFGQAIugvyJBekEEvXmvLThIz1QHIoC78iZSBkBgquMwddwa7TVeYN/j222// + p/7/Rz8B+O1PaUuLi//HfkvXo/H6cllorZXZxgqZri+VMXjqgewUsQPEdnh0M4ihMuCuNEYESX3EUykL + vCelT+9JZWiAlATcl6IndyXvAWL4hzck4/4NSbhxRVJufyUpt77Qa+Whj6QxNlSaYabkGEj+ROnOAhnk + Z0pvQab0FeXAslQduBAyjCJUGC7PlyESQnG2OBFKOPJSpSMxQlpigqUG98u/f00y73xJIrg91G3+Ynp8 + 7O/5icBPAH77FXZ8dPTvuwb6/2ldWqKMN5TJXHutrNrbZbWrSVY7G2UJRLDYUiPTkO0T8NC2uEixJkSL + LTlWWqNDpD0+QjpgrfHh0gZANkCeM2avDnmkxFAZ8lgKHt+WLAA0A546DWoh8donEvflh5Jw7TPJuXdN + ih7dluqwJ5D3wdICculIihFbVjIIIV0GCPqSXIA/T4bKEDKAAKgMnFAFdqgBK8OD5ChpiHwilcH3JJef + cf1zhgY3XDbLhwtzs/+Xf/z//n/KH/2j/1ZufP87cvtHfyC3f/w9ufPj78rH//QfCJ/3hw5+AvidsufP + n/+7Q33O7xaGhyIer5DJhiqZba2RZVOzrNnaZM3aquBfaa+XhWZcb62ThcZKmSjJk1FI95HCTBnKS5N+ + eOE+mBPSvAdg7AYYLVAIFiiFrqRIaYoJggVLXUSAVMFLlwfdl+Int3WpL/fBDUm785UkXP9UIj//qURd + eU9SQQhZt65C1j+Q2vCn0oqQw5weD9mfgpAhW62fCgGfPwB1wJyBPStRzPi8trgQaYzE5wQ9kHImC6E2 + kr7+REI++Yk8fP8HEvjBDyXkwx9K5Kc/UYu/8r4kgIRiv/pQnuI1i3Nz/2c/Afjtt13i/60+i/npaGOt + zLTUA/QNsmrtUluzd8qGwySeQYdsObpld8ihttljljVzi8zWVchye4OSwjzIYB7hwVR1McKDArVheOXh + khwZgnQfIDHkp4ojJ1FsGSCFtBh49TBpigqSRiiEWiiESiiEYq77gxAyQASJAGv0Zz+V4A9+IGEf/ZEk + fvmRpCN0KH58BwTyVFoSosScliC2zBTpPSUAgwT6oAZsqbHSzc9JCMPrn+hyYv796yCUryTmCkD+/vfl + 0Q9/TwL/6DvyFBbykz+QCBBC9CcGIUR99r5c+8PvyO+qGvAD5LfYNjY2/tO0p4Ey2dIIeW+WdYdFNnq7 + ZdNpFfdAj2zDCPzd4T45GB2SvZFBOZgckYMRnLv6ZdNuke0ei2zaTLLZ3SHr5taz8KCpWglhoaFCZuvL + ZbKySKaqimQcsftwYYYM5qVIf26S9MJsGfGQ69GaSKQyqI8MhPR/LJWwgkc3JR1eOw4emWogBEQQCJCG + vvdDSfzsI8m9c02qEE40IezogMqwZidLT26aOPLSQQq4N5QAzZwaLe2JYdIcS6J5LOVP70s+FEfazSsS + 9smP5RHk/70//Gfy4Pv/TAL+6PclCJ8R9N4fSuhHP5JwkMGdP/qerK+t/e/+tMudfgLw26/l2n2/w/GT + GVMnQG8Vz5ATIB+Q/dFBgHxA9nDcHcP56IAcjA3J/rhLCeBwYkSOpsZlfxgEMNQv+64+2envkV3YXr9D + 9gd7ZRfksWXtBCG0y4alVTatbbJqapKFpiqZAxnM1JbIGBTBWHGOjFIZwEv3E7BZSWLPTJQuxO0mALk1 + DsogNkRqoAoqgh9K9v1rknXva0m4+jGUwI8Azh9IwI//QJ7+6LsS+f4PJf3rz6Tk0W2QhxEidCTHSFdq + nK4wdJMAEDK0cbUiORIkEIx7B0stFEFJwB3JhSJIvvqphgIPAf6HIIHHVAU/+a6SQDiUR9THP0ZI8oE8 + ee9HwhWF3xVF4AfMb5kdHBz8h5VpqeKBB98d7peDCZcczozI8ew4bEIOp0bleA7HuXE8N2yAfnpMjsaH + 5dnslBxPT8rJ1IQcTY7JMV+L1xyBIE4mR0EWIAoQyMFIv3j6bLLj7BZ3TxdIoFO2bAglQAYrHQgVastk + sbla5mCT5YUyXJwtI1z3z+OSHpRBfrou6ZnTYqU5LlSJoD46WOoA7oKHN5UMkq99JtGfvycRn/5UQj76 + McAKb/3T70vsp+9Jxu2ruupQG4HQIiZMWkEGrUmR0gaF0ck8BNRAC0ICJiZplSCY4se3JQ/3Tb/xuUTh + nk9/+gfyGGRAtRH2wQ8l8oM/kgiSwCfvgQg+kscf/kRWlpb+Cz8B+O03xhbm5/9PrtYW2QdoDyYB3Jlh + eb4wKc+WpuT58oy8WJ6VF4sz8nx+Wp7NTckJ7MUCri/OynM+BvCfz+N5XHu+NCfP+DqYPo/jCQjkxEsK + hyCXfSqLQaeGCzsDDvFAKXgQYqx1NcuaJhMbZKayRKYZHtRAGVQWggjyZKgwS2zpCWKFWdLiIeGTxJKZ + IJ2pJAQW/QTp6oAmDh/fklxI+ZQbVyT6yvsq10M//JFEfvwTSfjyI8kEGRQ+uiUVoY+kFvK/KR7KgveA + AmiMMQigISoQyuGJVIbc1xxB8ZNbknX7S4n94gN5CnUR+P4PJJgE8973JRTn4SCcyM8+kCgQwdd/8Hty + dHj4H/gJwG+/1jY0MPDPVvt65BCe+mQeoIc9XwDAlwDw1Tl5vbEor9cW1F4tz8vLxTkFNs9PbWVBXq4u + yqu1JXm9tSav1ldgq/JqdVmvv1zB+5bxPpLBHJTC7KQcUUGMDMnuQO+p7TG/YOsCEbTIcmO1zNeUaogw + XVemimAE4cFISR4UQYZW/fUiROiFKnDmc50/TUuCLRnx0hQTgrg+XDrg2esiA6Uq/IkCvQiyPp9K4e7X + knL9M00kJuGYdu+qZD+4jufvqtevCHogVSEPAf4AaQAZ1IMIGBZUhz3S66V4Xd7D65J083MJg/cP/Okf + SvBPDRJgQjIcoUgkFEf45x/K/OzM/81PAH77tbRem/XK7sggpP4YAA9QrwGsqwD0OkC/sSLfutfkm60V + nC/JN5tL+vxrgJyg/2Z9Wb7ZWNHXvd5YlZ/vbsu3nk35dntLvt3Zxvs2cZ81EMK6QQp4zavNVXm9iXPY + i4U5JQIqh+PpCSiLSTkcG5SdXotsaZ6gRRYQEszXlstSc52MlxbIaHGuTFeVIDTIExfIoCc7WfryM2SQ + OYPSXHFBJfQXZmo5cH9xpjgQNpAc+osytUKwB49ZJmxmLQCMOQGGEdURAVqYVAS5nw9VkHnvay0Syrr7 + FcwghxwQR+7jm5JHZfHkpmQ+QFhwH0Ry5yuJQ9gRBsAHfvADTQ6GICxgPiLsI6iOTz+QH/3Dv/9bt1rg + B9BveLxfEBst+4jN6fUp8V+tw8uvA+gbywr8n3m8tr0q3yoJLCoB8Hm+7vXqgkECm3yer9sE6NcAfoMA + SASvNzfkGxx/RkLA8RuPW75xb6q9XFuR51ATPH8GMjieYf5gVPYZEvR0y16/Tdy2Ttk0tchSS72sNNcr + +GdACpOVxaoEGBqMI0wYKS+QkYoCGecyY3WRuEpzcCyWYRACr01ASUxUw/B+vn6iFsfaUry/WEarYBVF + MgRCGcjPlH4QCsMMO8ILTTrGhsHrP5Lce9clHWFDKkKKhJufScrdLyQLBGAQwm3JenRTMu5fk5SbVyTu + q48k8vP3lRRCPnlPQj/5UN77J/9Yfv7zn/9P/ATgt79WG3D0/Gi2s1X2x4cQ48/A4y+ox/9mE2DG8Vt6 + eQD8WzcUwBa9/KJxHfZKQb+kj1+TCLyK4NvNFS95rKgK+Jl7A++HeUAGux4QwA4Uwi4eG8rg9Y5bXkEZ + vMLrX4EYXuLay9UlOZ4a16QiSWAXYQlXDxgSrLTWy3pnky4jrltaZbq6XCYqimWqGkCuKJTZhgoAHcCu + A6hrSAgAfhWOZfkK+GmQxrjmE8rVpusqYJUyU18JUiiVGT6G0uBrhqEySC6uEhzL81VV2NMTpTMhUjpg + NWFPJB+gT77xmRYGpd26osnHvIc3JO/BDckBKWRAPZAIEq99KrFfkgw+BBF8IF//4A/lt6VfgR9Mv4Fe + vzAiRDYdXXI0NSwnC5Ner2/E+AT6a8Tq3xDka4a9RjjwenkG5zjCvkEo8NpLAN8Q/Ijr+Z5vEedTEbxe + XfQqgnWoB3r/DQDfA+DDdjxQBAgR3FtKBK+pGNzr+prXUAGvNtbl2fysvJyfk8OhATkY6gMBOGXLZhJP + L2sKOmTd1Czr5hZZbq+XWQB5DgCea66VhZY63Yuw1NEg81xFaKrCtRp4/RKZrGYSsUjGQBT09OMwXiPY + DVIolVEoiGEAnmGFqyhbhmDDpfkyWJAtfdmp4shIkt7MZOlOiZOupGhpYD2C5gLg+W9/oQVIqdc/l+w7 + V6EUrukxHcTA6sLUG59L8vVPJR5kEPf1p/Ll9/7pb0Vy0A+q3yCbHBv9r13wjjt9VnjYAcTbY/JyBd5/ + DQCGfbMyC8NxcVZeL+EcIcE3CA1ew75ZmpZXJItFGF5HUng5j2vM8iOGfwl7NTctL2GvVwwSoCp4vbmi + KuCbrQ0jFADwv9mC519fR3jgRsgAQ0jwaoNqYVufe7m6Ks+Xl+VkhvmAYTmeGJVD1iCwjmDQIW6HRZY7 + mmQDocGapUMWmmpltbtdVkyten2prU6BTxJY6WrSsuTZhkp9PFNXrkpgkiEB1MJYVSGIoQCkkK+KgVWJ + WqqcnQTAx4szK1nsaQlqXfGR0hIRLI3hT6UjMUoaIgKlPvyJlEP6lzy4LsXw/Nm32YzkC0n7+jNJvvqJ + ZAL8tPSbn0saQgYqhhSQQiIIYcjSeWvY6fju+urqf/abGhb4gfUbYNzd1m8x3ZlpLAP4LXIw4pTnc6Py + YmFCXi4DxKsAOoxe/vX8hLxCHP5qdkKB/2pmVF5OjeDxuLzA8cU03jc7BuBPyLOZEXk5My7Pp0Ak07gX + APtibkpeLXHZz1hBIAkwR/Aasf6rFYYHIAF4/p9BDZAMvt3G+fa2HmmvoA5egQCeLS3i/QvybHpSns+w + toBhwQiIYEgOER5sIzTwIERw91pls8cia3aTbEAhrFvbtfR4DSHCGs5ZZESlsAxS4JHEwMrDMcj6icpC + mWIeAGHCFIhxsqYEHj9Hy5H7WZKcmSAObi7KShFrRqK0xIbpikJzTKg0RAXD+z+WqsD7AP9NyQPo8259 + KYX3b4AIbkkhLPv21xLPzUtffCDJX38sSV9/KEnXP5EUkEAqSCDl1ueSBCKIvPK+PP34x/KD/99/I263 + +3/9m1RN6AfYb0DjzKbUWACgWtzWVjlw9cCjDgL8AC5A/GIBgKW8p/cHYF/NjcurKYB8EuCGvRgblOcj + Azi65DnPYc94RPjwYh6vY5HQcD+uuWDD8gze+tkkbBr3B4koIczjvgtzUA9QDQD1a8h8n33rdqvnVxJw + b3sfMzm4pkTwcm0ZamFZni3MysncjDybnZYj3PsI3/FgfFA8UAU7Q05Yr2z390AdsLgI5rQhzOmWdSgD + liAzZFgECbC4iLsUZ+rKNHcwXVuqioDy31WaK32FGdILBcBtw9b0eE0AdrEMGdaRFKW7F7kdmduZq0Mf + S3XwIylgFSKAnAMSyAEJFN2/KTUhT5QgWFJc/PiWqoA0vCbh6kcS/9WHGg7QUqEMknE99quPJOLzDyTk + 0w/l+g+/J2ya8vrVq3/TTwB++zPb4eHh/9KSnykbXQ2yaW6SHUenHA73ArwA9xw8NiT96wV46cUZlf2v + 6PHh6V9Pw8NPjinoX8DbPofXpZ0M98mxyymHkOFHAD3DiMNhp+xDkh84rXIAAB4PD8jJiEtORg0yeI77 + qE3h8wDeFwDxcx4R41MRvFxmsnFNvtnk6sGWgv+bjTVVA9+sIyxA6PB8eUGeL80jZAEBLM7Jc5ABSYCF + Rfv4XruuftkfGYIhTHANQB04ZGfACRKww6yywWpDriTAVr1EMAMVMAXwT8Drj1YUyhCkP5cLu+H1u0CY + 7QB7B0DfFAdvHx2sJcSsJaiF5K8JN0qECyD587lUSBBf/VDiP/+pJF75QDKvf65lx5VBD6EUgqQhkmTx + SMp0ifGW9iuM+fx92AeS+PWnknSNZIDQ4AYVwRWJRegQivsEfPye/Pd/77+S3d2d/9hPAH77Uyf7ugsA + fgDfbWmRXYDgcMguJ+NDGq+/BHhezk1qDP+Kch0S+xWuv2Ycj/OX46PyamJMjy8B5BckAHjaY4D/oM8u + e5Deuz1m2bZ2yra5TVabamTHxs/Aa4aHQBSDIAEXzge9hmsghme453N8zrM5gwRegnheAtyvV5cV+N9s + rIIIvAoBpPBiBQpgdcVYLgRZPIOKIAFofgBK4xDhxwFDA3zHA3zezmC/Hg9xbQ9kRHVAEnAjPDByBi2a + LJxuqJARxP9DZbniyE+TLnh69ewAa0XIIykNgrSH9y7gXoBHNyQHxopCZvlzHlxTy7z7lWRAyid8+QGk + /vsSd+UnEvs57NOfSAIAnApw59+7rv0NWDhUGczCogdSwXs/uSN5969rWXLS1595ScAgAlriDRADQgSS + QcTnH8oXf/B7uvX4100V+MH2axrzt+akicfeIbuOLpgJwOyRo5F+eT4xrB75GY+Q0Rq7TxqAfz07Jd/M + TAHw9P4jXhIY1ePJ0KAc9cP7Ox2ya7eI29Qum23NstHWJItVZTJTkCuzZYXi6e6SvR6rHOK1R0MDBhEQ + +FAER0P9cghvfQxlcQzgPtecwbRWBr5kiLBkVBi+XmGx0Yq8XkMIsALwgxxeLC/Jc1wnITz3kcCsUY6s + +w/wvQ/wnQ/HDVIgARzgc6kOtvsdsgXCWreZZKmrWeZAABNQAD0FadKUECFVLACCty4hMGFF8NT5T25L + PisGA3AMvCNZIIBMADb9zpeSdhvAv/u1NimhfE8EcBOufSzRAH/kJz+SCFgUtwp/+mOJ+Yxk8L6kAsh5 + 965K4cOvoQ5uSvnj21IGEijFZ7BkOef+VUm//bl36ZAkwFzBJyCGjzWPEIV7RFAVfPgT+cl/9w9kdXn5 + //Czn/3s3/ATgN/esLSgQN1oczDYI4cDAD684MkogOiL0QESAvIFji9GhuXlsEtejUP20/ODHF7yOXjs + 53iORHDiGpKjPkj9nh54e5NsdtDj18tceanMlhTKRE6WDCQniiMuRiaK8mQb5LBrs8g+yOJAiQDAH+pT + dXDo6jOW9yDVj/A5J/w++FxVBiCjF/PeEAGE8GJhAbYoLxYXAXg8BkEQ/MwFPJ839hqwcOiExUO4z/Hk + BIA/BkKYUkXAjUr7VAX47E0QwBoUykRjpVhz06Qant4AOjzxo1uSB0AWwuMXBtyT4sAHUgoVUAliqIDc + 55HtyMrgxQvZS/AhyeAawHpVUtlABB6bsX0clEDsF+9L1Gc/lfCPWQb8YyUDHsM//iOQwY8k6UuSwYeS + A+WQD7AXQUmUB96W4oCbUvD4puTDqCy4YpD0NfMFH2jOIBb3jvnqY4kAGYR+9r4EwwI/+qm890/+kWxu + bPynf12JQz/gfo2MHqGv23LP02uDlyXABhRwz8aGDPCPMyZH7EyQDAHgAPazAagCePeX8JYvAfjXAPxz + gPP50IBefzaI+zidcgDweyyQ0c3NslRVJbPFRdIfnyC2mEgZykyXgfRUnEdJe/BTmakolR2rSXa6QQJU + AyCCwz6SQS9IiSQwIPskAHz+IcKDI5c3RMBnP5sCoEEGx/yeAPLJzCzCBVYIMgFI6T+t3p5lwwQ+CeBQ + wQ+vPzHutVEjUQjjZqPpplrpyc9APB8u5UzaAfC5LNp5cF1LfNlPgA1Ks+58JYXwziwDLnn6QCrDEPPH + BEszewgmReqOQW4Wqol6KuVhT6QU99KW43gfM/uU8Ilff6xbkn1EEEP7/KdeEvgh7AcS/skP8PxPJRFE + kHrtE8kC2HPvfYnPva5zDYoe35ACKIWMW59eIIBoWBQN946FGmDSMAzHkCsfyo0ffk/blP1VqwI/8H6N + En5V6emyYYfnR+xLeU9vf0JA04sD3LRnI5DJgwDcAMANe47zE5DAS5fL8PoA5gm89nFfrxw7e2UPIN7t + 7hZ3V5est7TIYnWlTBTkiysjXbojIqT8+nXJ/PQzKbh9UyofP5CSu7cl66uvZBLqwGPplD0ogb0eGwjA + KXuOHkMVDPTJXh+sv0/21aAucO1gkElFF9TBoByCkI5Guew3qnYwAlBP0NOPQ+oz3h+Wffx7KPWPxo3H + jP1ZRsz+BVMtDeIsyJbWuEjpTIrWHoTtkPvsCsySXsbjeZDxufDi2ZD0aYjBM1is89VHkgK5nn3nayl6 + ck8qwwOkMS5MOlKixZwaJ13pcdKRFiudsLakKM0ZcP8AcwIZLAa6eUVSEb8nQhEkAOAJAK+hDN6TmCtQ + Bp/8kUR89kcS+TnCgyvv4fmPJPnaR3jPJ5J554pkgwgKHl6Tggdfg6jY44ChwReSAGKJwz2VBLjNGaFF + 5BcfSOSXH0ER/FTCPn9fQj77QAI/+1B+8A/+vpycnPwv/ATwO2IcmeWsbxR3Xz/ifJcuw71EfPwS3vI5 + vOkzAOYZ42MAfQ+efK8XntjZZ5BAP+J0BwDvcODcAP4RvbXDBg9uEo+5U9ztrbLSUA+5XyzD6WnSHRUl + rYGBUgrwp36EH+D3fyCPv/c9uf/7/1we/PPfkzv/+J9IwHe/J1MVZbLZ1iQeUycUQbfs2e3ihopwm83i + wbkHhODpwdFmxWMYiGLbYZdtHHdw3AVZ7DDngO+92w8bGBAPiGRHz/FvgUqhMePvQZw/jdjeWZQNwMdI + T2ay9GQkiR1AtafGiBUkYIqPku7kWG1G2g4iqEbcXxl4X0of35HsW4jrr30uSVcA3E8/kOQrH+u1IqiB + mtAnus24kx2FspKkm30MsxLFkgEySI6SloRwKIUgzfDnaZfhK1AEH0vqTWOZL/UmdxySDODNr34AQnhP + Ij+DImC+AMogjisIrBEAESRc+xCK5DMQ0BXJvfulZCMcyAO55N7/WpUKCYbhRiTJBCQQ/vlPAP6fKgmE + ggTCv/hIYvDviPzqM/m9//b/I/v7+/8rPwH8NoN/b+8/GmhsRqw7KnsABeXziykW8MyqPZ+YhAIYl2N4 + +gMHgN/TC1nuADA7ZN9mh4e3yj5AuY/Y3oPYftfShee7AfwO2W5vkc3WJlmurZZ5yHpXWoqYw0Kk+s5t + yf70Uwn//h9I4O9/Rx7+/u/Lkz/4Awn+wQ8l+A9+IE+/+4fy+Pe/K09BDH1ZGbJUXyub7W1QBGbZMplk + o7NTti3dSgYbHZ2yiceb7R2yAVtrb5e1thbD2ptl3dQm6/hO6xaTrJm6YCbZBIFs4PGGXuuUiepysWYm + iROx/WBBpoyU5sooOwLnZchIUZY4MxLEkRov9sRYcSTHizk2UtojQ6UhOEDqQgOlJvix5N3+WrKuXZGk + T9+X6A9+JLEf/UQSv/hQuwUXAthsUModg10gExMtjW3JcUxB2JMULs0JoVId9lBKEc/n3OPuwSvw3l9K + Jiz7/leSeRvK4BpI4fonum+ACiEWCoDhQTSAzHOqhCgAOvrKT5QskvF6kkLqTagDqIDce19rY9R8bY76 + tSoH3od5hhAQScinIIQr7+uuxPjrn0sULPLqZ/LTf/rfCbs4+wngtzDb311WLieTU/CWTsT4E/ICsfKL + iSl5NTEtL8Yn5dko4ml4zkMH43iHgn+nyyzL1TWyUFEpmy3NstlYL6vVVbJSWSHrAOsara4ar6mQJdhM + abEMZqRIW3CgVN2Fl/viisT95CcS+L3vSsAffk+Cf/hDiXoPwPkppOmPfyKhIAIqgnu/9/ty55/9c6kL + D5UZ3HulsUHW21plswNgb22T1YYmWalvQlhRC5Kp1+NiVa0sVIFw8H3mKstlGsQzXVUuk1ATM1VVMl9b + K1NVlQB5oThzMsSWlix9ADpr96cqi2W2tkwWWPZbWyFT5UX43GIZQygwnJspg9lp0peeIrZ4SPgIhAIP + 7klzaJBUPLon+TevStbXn0saPGjsR/DM731foj/+kYYEGTe/kKKHt6QKMT+bk7bGhEpzTLB0APidyVAT + SWEghCjtKVgb/ggkwBwDtxF/oZKehJALy77zheTAsu98Kam3PlNwUw1w9YDgZ94gmoTAbse8hucYPlA9 + UE1k3v4c9/oSBGCEBzxyuEkScw5fvq95gsjP3pMwrkAgPGDCkIlDJYQbV+TrP/qOLC8t/hd/kQlDPxD/ + Gs3ZbbmrzTQgk59DAbyampFXs3MK/mdDiPsHhhT8R70APyT3vs0Gs8pmc5MslJTIaHq69CfEy2RBjswW + 5csMbLa4QKaL8/XaGEAzAg8+kJosHeHBUvvovhTdvCbpVz6V+A/fl4ifQr5+9IGkfPaZpMKSPv5YonAt + +AffV0Vw7zvfkUef4If48LZkhjwRR262zFRUyDyAvFAJMJdXKAlNF5XITHGJTOI4UVgsrqxs6c9IB1hT + xZoYL51xUdIB64QH70qJF0tqgtgh77sh9XtxHCvNk9nqUllprpX1zmbZMLfJWkuDLNeB5GoqZbasSGYr + imWyHCFMfra48O+yx8dIW2iwdEWESn3AI6l8eEdK7lyX4rvXlQgyAJpkKICkzz/E8WN9nMdWYggJqp8+ + 1PChKeopiCBI2uJDFPwdKZEICSKkMfapFAXclAzE9Rnw+OkIATIA+CzI+hzI+lwqApBDBiz1Ftf+P5aU + 61QHn2pNQSKBCxJQUH9hGJOBSQgf0nDPrDufGyEC8wUPrimhZN76XPJINlymxOfFf/0xiABhBsmAqgBE + FnvtE4QGH8ijD38ov/f//XtyfHz87/kJ4DfU1tbW/jN3r0l27CY5cfXL68kJeT01La+gBl7A679wDcsx + YmWCn8Dfg3zeaW2V7RZI65pqmc3NERfA3x0EjwYA1Dy8L1X370pd4GNpDnkqbZD67fDcHWHB0hr8VOqe + PJKiWzck+yo821dXJPNLSNwrOH52RdIB/jRY9E9/AjXwfQkEAQS//1OJuXtD4hFfJwfckzTE2kWQ2kzK + tcZESHNkmDSFh0h96FMA6jHsEUD1RMoDEJc/ZXvux1IT9lRqwwKkITJIB352p8dLb0G69vgfyM+AtE+S + 0cJsWWyslrWOJtk0tcqOzax9BLY62xFudIrb3AW1AcKrrdKQYqG+SibYN4A7/NISpScpTiwxkdKC79GM + kKAm4IFUBz6Qiif3pALfu+wRiOH+TSm6d12t7NFtqQq4LzUggMbwQGkMeyLtVAMIDzoT8R2z48WWmyjN + IIWSR9clHUBMh5RPv/7JqQdnjoBr/jkPoAweXEVIgL8fPDxJIhVEQJBT/icA8FGI9WNAAHEgg/ir72td + QArulQxVwNdnUhkA8Pn3ryoZcPWAyiDvMYgMYULKzc8lGu/3EQF7JEaD2OKvQ8Xd+Fyu/tHvy9Liwt/6 + s05D8oPxr8lGhob+kbunRQ4HbPJixCWvAPpXI6PyYnhEs/snfYbnZ4y/3dYu201NsgWPuFlTJUulJTKd + kSF9TOY9eigFAHPq+/hxQMqH/OAH8OA/kKA//EN5BPn+5J/9PmL9H0rMj34sSR/iRwf5n3P1quR8BUn7 + OUjg888l9ZNPJBHeP/2rL/CjB5EATA1R7OcfKi3eUV6NUUFSE/IY4H6oJbKlT+5qNVx54D1tvVXx9IEm + 21hj3xQVIm1xEdKRECFdSVFiQwzPISKDhZkyUVUoo6X52gxkuqpYlhqq1Ot7rCZxswipF0qHpcD2bvHY + LHrctppls6tdtxOzz+BKZ6O2JZ8oK4AayBBHcgLCgmgQQYS0hgVJPYiqDt+xHt+3Duc8NoKIeGyJAGHC + OmLg8aNDpSsuVCyJEWKKDxNzPAggKVJnGpjTovHvCJCCu/D21z6V3FtGUq/w4TUpAjipAgj+HKoBrzLI + uMUdgwYBGBuHjKRgzJc/hQd/XwkhEdeSNPY31EDaTZDBjY81z2CEBl9L6dNbUhEC8gq6g/PbuHYdochX + ukzJJcTwz9/DPbmigPuDIOKufY5w4ROtKeCw1H/5L//l/9BPAL/GLbv7+pzfn3XA+zva4eVt8gye/jk8 + Pu3Y2SdHPb1y2G2H1++W3Y4u2Uas7a6rl3XG4QD/TFaWjCQmSm9EhNTfv+dN6H1fnn73e/KUMf17zDB/ + rEm9iB/+SBIQ2yeBINI+hhf7AjEswJ/39VXJu34NHvEewB4iHYnR0hobjh99sDQBIAaA4e0BjNb4UGlC + 7NwcHQwLkSZ49NqQR1IHwHNOYAM9KUEVD8AnRqm851BPR06K7s0fK8vTTTtsG76I+H6urlxWWutkra1e + 3BZ4enM7QN+DUAjgH3DKoVYcDsgxlwi5nViXCoe0r4DbbpZt2BLCBZIHewwO5mTIYGYqSAChBr5HE4Ee + FihNoQHSjGMb/n3t/DfCLFxWBPitSbE65qw3PVEcaVBSyVFihkox499rTYlWEjAlIRyICJDie4jXET7k + 3wVAdaTZTV3vL3lySwoJTs34f6XxfNbtK0oE6SwFvgqgc2XgayMP4FMFSXx89QNNAiZd57X3JeM2x6V9 + oSRQEnBLi4vYwLQqlAR7G8R7W4pJECCd/EfXdGUiAuoiEsoiijMVuEIBoopGuBPgLTDaWF//my9fvvy3 + fxkh+EH5V5z0s1mtIWtrK+JZXZR9h1mOe21y4nDgyCW8fjnuAQDMNtlt7YTkbxd3faNs1dbD89fIMsA/ + l5crYykpYg+Bl30EWX/1K0l67z0JBQEEwSI+x4/gwW2JguyNunlVIj7+QGI+eE8ycD3ziy/VwzNz3hYb + YXh4nckXrQ0yOLCzMyEKx1j8+DnqC54Q1+kVrQCIBeA2xUeKBcSgR74mkc8DTKnxunTXl5tutPkCMMer + jGYdk/D0yy21stwB793VKKsdjbLOzsE4euxd8P5dcgACOBjskwOtOhzQfQesNDxkFSNI4GjMpQTB8MDD + XYKWNllurddeg+wxyJCgPz1JLLFh0g4V0hYeJI0hAertm8PxGMTWBYLrRdhgBlGQAHpw7sxOhSWLM4v9 + AxIU/CaGK2kx+DtEghjDpAEkUPn0vm4QYg6h/OkdqQqm6rmPkAdK6NENKQEhFNy/rqsOlPQkgJSvP5GM + mwYhEOwp8P5GePCRQQTXjEpBFhUl3+Dy4SeSffcLyYMaKLj/NdTGdYD/nlSHQl0Fgwie3JTK4DtKCIWP + r2m9AWsMIq9+KKFcffjCyBUY5c0IEa4jfIA6CP7iI/nih9+T2amp/8flQiM/MP8Kl/sqqyple9stOJe9 + HY/sDvXKAWTuka1bDi1WOei2wayyA8m/09wm2wD/ZnWtrFdXy0pVhSyUFMsUYv8BxP5dj59I1bXriOEh + 30EA0R9Bat65KbEP70nio7uS/hAeA3I99/bXuse9AnFvJeLjxmgO4gw1Ou4SyCmxCnwzB3PCE/akc6xX + ou6hd+akiROgdqZBwuO5Xj0m6LWBnHTpw/N92SkyyCx+Yba27hoth0Gas+ffXD08fnONbt8lWJdgG90d + sgnwskfgth2y39qpBODpNin4tciIlYcjRmUhC4q0shAqgMVG+0OsIXDivRZdYuR952rKZAykM4jvYwdh + 2fFvMcHLtwD4tOYwgwA6QQ52kBrBb09N0O5APQD+YF46/j2p0u81ElwnwoF2WCcUQXNUoFQD8LVQPDVa + hHQf4c5DJYHKIIRMsMqnd6UiEOD0HvPZUgxAzLzJ8OArJYUMEsNNI2mYdOPj01BAqwqpEEAQXHmgaVLw + Prz9g2tKBGVQBKUBNwF+hF1QA2UIE0gGJKMSPJd6mzsRP0Vo8CGcwHtacRjNUuTr+H2w7RmLpO5clQFL + 5+0x19A/8NUX+MH5V2Czs7N/ux8y3+PxyP7ePsC/K7vbHvGsryIMsMqeuVP2ujog99thHZro265HzA/w + r5ZXyGplOeL+YpnIgtRNTJCu4GCpun1b8uHRkz/7FP+5X0smwF4Cj1cLyVuF+Lc9LkwqHt9Wq2K8fu86 + vD5iech3jtfmmC7ulyfo6f36IddpA3lp2lVnHB58DEAeLcqRYQB8EEB3AShDsFF427HiPPW8YyW5euTr + fePBKPUXWqplBsCcb6zScWKrnU06PGTV1CobtK4WnTDE+J7gd1tBAC5WGDrUWA257+rXUmNtIgJCOMD5 + DlSCbhnutSqZrLQ3yVJjta4iUAUMwKNT1neDCBjTd8SEShtVAMKBtnCQAFQPpb+D//ZUqID0OOPfz/ex + FgHWAzVjoSJKMHIDLdHMawQC/A/x92VL8fvwyiSBB0oEtbzOXYIBt5UIqkEQZU/gpaEIuGzI3EHO7S80 + w5+NEIHgZuWgqgEtMEKIdo1FRj+V1FsfqxpI+vp9LSXOvM3E4keaJCxGuFHj/fz6yEcI3R4jRAAxRTyS + 6vCHUoHPLQm8K3m68emqJONzYrhUCSJg6/NkEEsyvkvi7S9l0Nz5cMBm/dAP0L/keL+31/mjxcVF2d3d + Vc+/69lR2wMB7Gy5ZXdlRSUti3bcjfWyBXM3Nchmfb2sQTEsl5bKQlGhTGRmSG8spHpIkFSykOeLK5KG + GL4IP+x6ePROxKtdKVHSArlKj862WJSy5qQoSOEnKtet8Pbsi0fQD7DNdm6aFtu48jMA4mzE03kyAZus + KDR6+deXy3R5EaxQZnBtprJAxoqyZLwoW6YrixX0UxVFMgOJPwWbxXs4OHS6ukQWEJ8vUfa31Km3X2lr + MKYHIebfgOd227pgJpX1Oz0gQTYQ7Qf4OVuAew5GBtXbE/T0/qwYpBogAeyDHLhfYhP3YMOQFYQWswgF + JkryQVKZ6tEd7AgMb29iBWCskfBTiwqRHvx9HGlxAHyCDOWnafeggYI0fR8VDVVAb2aiPs9KxO4UjjOL + VBKoAwArIcvp6RuiAqRRieGBmo8AaqAMynFOJVAIJVAEQGreAMcCADOXSUPWE9z/UhWBb+Ug6ep7CBOM + UCDp6vsgh/fxHJTB1x/oKgSXJCuf3tPcQHNsIJTcU/0OdSAAJiybowOh8AK91x6DrB5KOQexMjxB+FAY + cEsK8Z24KzLh5hcggY57fqD+Zcb7Nlvk5saGHAD4+wQ/Pf85Ath1b8sebX1dPJC7m51tst5YJ+sN1bJa + WyUr1RUyW5gv4wB/X3ystAUFSNW9O5KNuD8/8LHYAMSB4iwZKETcXQavXZELD/dUpgHEEY7jYmNM/Ki7 + IIfHS/NVprvys2QYXn20FGCvIIDhucsLAGiAvrYEVgpJbYB/ualaFusqZK4C4AbQ5+ndq3EkwHF9vqFS + i3YI/Gk8P4v3Uu5zLNhsVYmstzfKfH2lrLbWyUZnizb02HHaZKu7S3bYj4BxP0eYweNzytAOwL8/MqAt + wzirkENKD855f25A4uYgen8PiGObPQKsHZpbWMDnkKxG8G9zFYAEOGwEXt6eGgeLBekZXr8bhOjMgPdP + ipQexPkEOfsHDoAIlAyogrhigXMX/q6ceMz32vEaVhC2xoVo7UBd+GP1xA1RTwDEIFUD9Pw1CAdIADXB + zLXAK0Oua0IPxFD65JZ68ZLHN3RbcSlAmc+VBCoEWCYkPHcasuiIS4SJUAaZIINEhghfQCV8+R6I4GPJ + x2sbIh8r4Ovh+Rsjn8AeS2tMgLSCGJqjA5Qg6sLwHULuKUEoIXAyEgihNOgBVMJtSbt91Q/UvwyDt/+P + W5qbxb25Jfu7e2p7BL3XKP/PE8A+lMDB1pbsra3L9vS0rMIjztXXyFQJftA5mdKXkiyWKHig0CBIzyDp + hBd3wdNONVbIZH2pzDdXQArXyUJjpfTB8y02MjueD1lcLGOFmTIMbz/pBTuBT2muwzwB8pmaYgX7XF2Z + LAO4S7i+BFDTONWHxTkkAj633Gqc+55ne+81c7Mst9Xqvejx17tb8ZoaTfqtwvuvIkZfa29Q2b/tsIin + j3sFugF6u3rxbSeHjzoV5IztCXhOG2KHIB4PuUmI51NjAH8/7mGFkujSxqLuHousIpRYaq5RQprksmBB + loYkroIMGchK1nCASoCZ/r6sREh9gJ0DRzTsgeTP4ipArJIAn+/je0AQAyCC4ZJsqAKqgXjpycZ98pKk + M5UTjhESxHDc2GOpDmNexetxQ1lgdE/VAJVAbegj4zzwrj5H713JzD7i9mIm+NiMFKqA8r4IxJDPfQMI + E/LvfaWPuSqQc+dzgP4jSbv2oaQyXLjyY0nHkcpCcw6BtwD0Bwj77uAz7us5SaEZxFQXfl9aoh5JM75n + U/gjnD+RejzfAMJgR2QmMf2A/Qu2iYmJvzc0hB/zzg48vxHv+2wf4N9nHsBDAnCr7W9vA/xuOSQJgDAO + NjZld3VVdpaWZGtmRlaHh2WxzylDTXVi4tIaftgj8NTTBFxbna6Js832Whdj4SpxIZZdrC9Tub5QVy6j + kMSz8NiLIIs5evgGem7E5fCaiwQ7YnXaOu6ziftsmVsQn7MLUatsAVxuSxseN+vRTbkNQqAR1Ozxz+do + jMOZ3efU4C2LYSstDXhdkyb7dgB4GmcEkAR2KPe9G4Ho3TlfkLH90bh3V+DEmBxNjmufAG4LZvcgNgdR + Y5txFgt1m/QzqEL4b2XYMsXVB6gdY2UgC0ogXYa4xwDmKuB5uuHh8bcczE1RMnDB2w8jBOCeAxcAz5xA + b0aihgKDuO4qBgnnpYg9K146E0KlJdrwsgRSI+Q2vX9LXLCqA4YI9eEGGRD8VAVVAD5zBzVhD1We8318 + vhTSvBIAJohLntxQZcB6g4L7BgFwOTDvPjcQfSzpNz7WRGLaNeM8+/ZnUgYFQYVB8Ffg/TU4VgfD44MI + CPTaEIQKkQ8B/gfShMctVA0ggbZYEBi+Q1t8qB+wf5Hxfk9Pz8crK8sa6x/sH8gePT9l/w5lP0Dvcavt + e+D1veCnHdKgBHwksL+2ARJYk52VVZkHQAYqS6QXnnwIXn2srkQmGspltqkS4DeW1dZMTWpziMNHIPnn + 4dUny/I0Nh/LywLAa9R7LzUjrMBxleAl0C0wHD3wqDsOs2xDous5zNNj0vFePHo4/dcMQkDsvm3uELe5 + HWTQoQU8mxrTtyoQGdtv4foyzj1WvNdqRoxv0wTf/pCRwWcjEY3nx9jsgzasj7kN+BhA920TPuGosYUZ + 7Reg04bw3P6ISxuE7LkGoQBssoFQYrWrVVXAIlTHfH2FzFSXyQQIYIwjyEpyZRh/t7EShkOZMs78BoiR + ami4MEuvcbMRcyDDDBty2E4cCgCkwMIlthTXEAGPBxkOFKUjHIiWZnjTeoCslgk4en0cdf5gVIACnfG4 + jwSq1ePflSaQBlUCyUJjdYCxJviuVD+9rQRAIFcCwAV3v5BihAWlADRDh8KHV6EQvoQyuCK5tw3LgzKg + FT+5rmFIJYiiGKRRhddX4T7VXqtHCNAQBvIJvQPQg7SgDJoQDnQgZOlMCJHu9Gg/cP8ijFNi7HZb4tbW + huzv73ILpyb9NPF3GvdvewlgW23fawcEP8hBj14lcLi5KVuzMzLcWCP9Zfgx07PBg89A4s/CW68A7AuU + 5YitOY571dykRDCJH/MUJX5VIWLyfJnCe5kcWwV4tc025PkavPx2D2NwswKco7sO4JU5xWdvwC57fTbZ + RQjCa2z+sd9vjPjagdz22C04dst2dyesS/YRh7OIhyRAQtiydGiRzrYNwO+1a2KPtsNYHzJegc8+gPTs + k+wNMCnHU5OI7YeMHoHjBimwZ8Axm4bMTuO1Y3ICIjianpCDMZADawJIBggVPAO9suXo1rbiG/hOy+1N + OmNgCiQwXgYlwDFjzHXg78CQaLqmxEhS6rFIJkioIAk+P65kkaWEMJiXpklSjjV3IkxwpBsJQ547sxPE + BBXQSgUQ8UQ9PkFIa/ImBJkArEIooGEAQM4QgK+lxyUpNEezYhKxO8DYAFVQx/dr8vCuFvxQFZQpAdxE + iHAN5GA8ZiFQ7u3PoRSugSCuw9vfUdLRzwL4a0A2lQE3QAQ3QQC3oAAAfqiE6iCDDOrwfBtClxZ8966E + YLGkhvvB++e17e3t/6STS3cE8MGBsca/6034XUr6qQHsuz4V4KH398B2lAAOtg0CWOhzyCC896QCHvE1 + bKnTKIFd5hFxN9fXKeOX20gAzaoGpvGDn8d7FpshietLlQiYJNuwdQAk8Op9LKKxqAzfZ3fgMWbaAXTY + PpuFDvdrFd7J5IgcQ4o/4yhweNwDvQYpznN48MMBEkOv7DpIAFzKg0oA8DehDnZx7z3Ke/0MyvU+jefp + 2Y/Y+29uRrv+HM9Ma5egIxzZMIREQNn/DMdDyn/tKjTh7SA0LUd8HiTAfoFH2j1oWMMGJgz3tG9gr7id + Pbr1mEuD802cLFSpy5GzUEwzTG6yNqGuTJUClyuZuJwqL1ACGOUUIaiA4cIMkAbIgAlUqgJ4/iF6f4QL + Q1AMQ1ATXBmwpseo7Dcy7k+gAh7qlCEuFVYRiDASQT1DAYQB1UH34fUDlDB4bGLeACqiGe+vBvgrn9Br + 35EaALcKIPYpgmqqBByrQAqVADbVASV/5VPDGE7UhXFfw0OV+DUAfAOA34DHVCnVUAckoZpg9jC8Li0g + gCaGAXFPxZQU4gfwn2tSz+Tk/2sUHmt3FwA+B/69c8Df9SX/vASw7zHCAV8e4EBVgEdVwN7Guoy2t8gk + 4v0leO0VxNNrXDvnlF0AeMPapjP1CPil9npZAyFQ3q/ifLO7TZNxjMvXGN83G+vvHnh0j5NJNxuAadds + O9fYjycAcgD9+cy4gvtkijYmL+Ft2c9PJwwvzerob+3dh9ew088JwHcw0C9Hw0NyODiga/as3Nu2WrRU + VzsDDRllvB6oAE4GYscfgvkIXp12DHl/whZh8/MAPq5NTRhgB8iPCXy2DJ8a17Ziz6AMSArP5jisZFbb + hXEaMZcEfbkDJg+1NgAEwB4D61AlK22NIIEqVUlUSwuNVbLEBGabkSzligVXKuaqSzRHwgQpFcFIGcKF + 0hwNE8ZBDEMghIH8VF0R6M9N1BDAmsJy51ixZcWLNTNO6gGo1thggDkIQHtohAZhbFZyR8oe31ICYB6A + 1XyNCA3qQozMPYHYABVQi+druXoA0qgm0B9D2oeSRBAaPL4hDQByVcBNkMQNKQEB1CC2p5EcGOsT6LRO + gJrJPsr81njufbgrjbhOciCJVOAeLTHMQTBPcR9KJsgP4j9rvG+326+sra6o3D8FP5f6di+C30cA++dI + YFeJgArAIIB9xP8L8LKuOvxQEc+u2kyy6bQagzIYQ+N8y2mRLcj2LXuXFsCsMYa3tss6woEte6eCnIU1 + HMm93tWEeNyotKO334HM3x+wq8w/GXd5p/O4AK4JEMCE0eYb9mJhRsd8f8OOvqtL8mqRvf8XdIDoy6U5 + ffwMXvho2OXtXDSugCcZEPSHUAj7g/3Gsh2M4GfLb3rsk+lJnQtwgtBGZwTgqI8B7hNIfTYSZXNR5gaO + QRi6B2CC7xvX+7B/IEMEDh494XfQ3EG/rhrwM/cRDmxCiWwh/KBtIhzZZOjT0ag5D+441JAJoRJtsb1O + VzG4esHkKNXSZCVCgYo8GWEhFBOJUAejZXniKspQFTBUwCXCROkBCdhSo6QH4HfkJmljESYA26gIQg1A + 14c9Pk0I+moCyr11ArUAOpfkGC5wK3JTRCDe9xhe+7E3twALfwBl8VA9OEFdB/Ko4T1ADnyesr4lEkTC + rH4YieW+tEY9hsR/Il3JwdKR+FRaAXRzInc6BoFsHuv7OhO4MxPP4XWWRL8C+FPb69ev/+c2xvvM2AP4 + NF+8T9vZOQP+DsHuXfY7NYB9d9sIAVbw4x5sqRczYs4RrqF3NhuA5xIZY2edpden7bK4Rq7Xe626Dq5L + ar0G6Lkevj/aD9nt1KKiXQCeR58UZ3PRg+E+gG3YkPmcEQCP/0q9vWGvCPClOW3r/XJ1UYH/anleR4W/ + 5mgwXHs2yylBs9qujB2AjZl/IBT2AIQ3Zvaennuf6oBAnp5SoBvx/riOD2dXYE4QPpk/mxT0en1FG4i+ + wLVjvO45rh95S4BJOCQEnpNU2I6c5LCvhUFD+u9hARFDkh2HTQuLuMy4hZDErdYFUmxTMqBK2rAYeRDD + WpRIl7saZKahTKZrimWqEkqgIl8Lo0bKcr27F3Mg/zOkL9fIAfTC89tBAt3JEWKGOfKStFCoiz0FEkM1 + JKgKvqcqgCRAoDNOZ9a//MktTdQxTND8Acki/InuYGyJDoI6eKCxO717S2yg1ELmM+nIXEEtVcRTZvlB + DvDurVGQ+SCCpggQSfQTaUdY0RH7FN8jSMxJQdIZ8xjHEL1mSQ8H+IPxfcPEnBYJ7x8i1tQIP6D/NLa1 + tfW/aWtr03ifwKf393l9At9nPvBftl23G7YlbnhYJzwP98gPFGXJDKTqCn6Y2/1WSFnI6BF4tOF+/XGr + h2NVnHdqDmW4lsRqEQ0bc/aorD+edGlRDXfTHas8N8poCRbe52TGK/MRM7+YHlfvz5FfBD+9O/v7v1yY + NUZ/AZDfbK3qhGAd9MHHa14VAO+tswPnJnV8GNfqjR17o7pk9wJS3Re/H7HDEe7H+P0YMv85ZwXCOBvg + xfKien9+3ksQDWcLPJ+fVk/PzsKHQ4MK+hN2QmYCEEqD4Qc3BbEJ6TMQwb7++1wahjDpuNtrkx3YLqsK + udw40KMkqfsNQAYeBwiBw1B6LIaSYtKS8wjtHaoKlhBCaYKwhsusZZoEZEEV6wFIAEOlWeLMTzGqA5Mj + Aa4wMYEAOthHICNOukACLbHBmhtgY5FmHLlCoJt5GBIA9CSBMsp5ZuwD7+qKQQ3VQCC9eIC0RBlLjIzz + 6bXrWcgD4/IdFQBDBYYWTfDubdGPpAUqgdLfnMhtzWH4XvguyaHq+a0pYfiuIKa4ILFmRIoF4LemRoo9 + O06PPVAufmD/Kdb3p6enFew+yX8Z+D7zvAX8PgKYcFjFkp0ovdlcWsrSYZeM6z2Izw/YBnxsEF7QpXH5 + IX7ctAMeAVztnc91cR2y6dKk3CGI4YjFM6PcPDOs22gJFibvmNB7zp79nPM3D288zd798KocGzY3ZQz0 + WJyR1wDgK04U9nr9FyvzOj6c9u3miqEEoABeLs5r339OA2IcrpIdZHOCUEC37E4YJHDijekPxwD6uQVt + B34ED/9qbU3j/hdLHByyjNfhO/CeOihkRu9NkLMTMslrny3NuSuQY8NIAIz7SWywI6/y2O/rlT14/oMB + I5HpqzUgOZL4NOGpCck+tV1VVXgPSNNDtQUlteUwyaqlRScRc1VlobVGFpqrtb5A9zhUF2so4MhKQiiQ + qUVDtlRuoIrU5qTcKt0aFyodeMwWY0wM1iPGZ6/BdgCyCQBsjHmqy4Bl3OoLAihFbM+lvjIoAoYJVd4C + IhYPteC1WtkHz93Igp6wB0oK1SANKoFqqAAW9TSDBDpi6PUDpDMeXp8eHrK+JzNKrAB7TxpBzl2coeLM + SRBberRYQFoOEIAjC5YR6wf2nzDe/2pl5Sze9y3zvQF8j+eUANQg93egFjwA/sbysjiqigH8RMSSaTIK + r7Lcwd1xbZqgO4DXP4ZEP5kGgADSY3hrxuk0JuvUezM5Ru/NeFr774+qEfgEhD4GUXCIyDEHicB00CdJ + APZiZhIEYBDCi5kJNY4We6kDQKkAvKPB1zkReMkYD762qATxesWY+sMkIXMBJA+fPGcugCqAk4JYuXc0 + YbQCP56YUKMSOJ6c1iEhJIBX+Fu8WlzQzP+rVWO+4DMOFME9j9gCnTYxorMQ+G/SXYLcJjzIfMOg0S9g + aMC7c7BHdiH595zG0iXnKBywrBjg1pWNIQfIBO8bZatyKiinkoOSAPMjUFLbJAIog3Vzm6wjLFjV5Gqt + zFQbPQw4g5Dbmpkb4FLhgLdYyAEisOemaA+E1pgQ7ZfQEhtirPmHGkBtTwiVtrgQEASeiw/WKsJK3ypB + oJETUFUAItCiIRBBQ4RRsMMEYTuA3QQyICE0hHKTV4jxPIt6EBpQ9nfGP5V2qAULCKALr3fmxAPgsdKT + HSP9BdzqHCf9CF/6chLFkRknAwUp0oewpRfhjB/kv8RevXr1bwL8KZD+p/E+Pb/K/Hd5f5CAGsC/veUW + N967AnnthNQfY3a5PE/GS7JkBV5mm6O/8OM7pESfGDY8tM78m9bEnBHvuowpvUzS0eaMZJ2O5JoxiOCl + HscMcBPwY0M6/PN4xNhXT4LQ56bGvIQwpuDXGYMAP2cM8t6vl+dU3vs8/uuVRa/n57UFPb6YNZTDc7yP + pELAMiF3PGHE6Ye+Bh5jVDBQBMwJcJ1/ckLje6oDAp5JPy7vMceg4cDstKEscB/19lylUEUzqH8fTiVi + fH8ExaOeHypgjyPGrRydxhWIHjWtY+gzwE/AE/gHOPJ+VEkaNg06VAGoGnAZR04kXmf1ojdpyE1MuoLQ + XCPzUAPj3DNRRSWQL8OlueIqzZbenCTpYVlxQYYxiZhTiDmAJNYgA3ZQqgtjth8KgTsxqRTiQ6AGAqUy + +J7WDpQ8uakrBD4C4ONab/KQJEESaOTOP2bu4f3bQCBcx29S4AcrKdDzd4AQ6P3tKRHizIw3QJ6bAMXC + TkypehzKTwYJJMggjjSSgx/o77DNzc3/rcVi8sb7hxeKe05j/XPnPuDz9YYB/JubsoIfdl+xUY22UFci + S40VsgSvsm3vRCxrP521xzX3ZwA6vSDBT2+sWXkFvVeuM15fNDywzuKD0fvqFF8SAI2KAD96zhEkcJ5x + qW961Bg0MmlM+30Bz/2K96QSUDUwriTwcmHKCAkYAkAJfOMNCV6BGF56lQJXDZ5NG4pCwxAFuxGSHHqb + eGjt/phRx6/hioJ4SMeHaUyvGX+uPuDzQDJMOupyH5OLjP+Hh5T4jrw1CPTaHnhoAtsIeVy65LiL+H4X + 8Ty9P4GvR9j+oF0J8GCoTwHP1+pjnB+PuzQ0UAJgrgCm+xO84YDbblIlwPoK7jJcbq/XpUOdRlxTChIo + krGKAk0QuvB/2pefJk7YQCFIIDUWYYExs6AZJMBuSjWhJIEAaYZCYB8G5gW4Y6/eGyYwJGAREGsBuKZf + FnhXm4/UhUPiRz9VlcDafhYO+Wr8uazHkKALMp8FSYz7TbHBUABh0psWLT3JkeIqgEqht4fndxWlIpTh + JrA0fG/ulkxWMhgqTvcD/W02PDz892dmpi+s718G/xse3wt+N+S+m4k+eP55eJ0+VueV58piXaksN1XK + GmT/FmQmR3Ezc02w+mQ5k3IvmZgjAcwbSTmC/+U8DR56ZU7HgevQEKoAHJ/p6G7E+MyUM3M+bhTxcK1f + VcXUiJLACVTBMyoBhgg8kgw4OZhA5DxBfI7K/CUQDD9nhSsC0zqF+AWJaYbfcVSN+YVjAJEdfI4J8pFh + jc19NQAHBDBLetnUFN5/38lBJk6jww+/I8jhoM94/HJhXtf3dUwYvgfvp/fE9zzEv0GrCenVAXYmO4+8 + uQ8mPFnFyI5Cu94y5l0FPzsLObwzDJ1a5KTnXul/4EuujnBnIZOFCCEQJridFl1KdeNe3LW4TCXA3oOt + tZoT4HEWJDBZXWQsDULNjVYWAFzGPoFeAMoJ0HWnx0gXYn8u77UBkMwNUAWwJbmPABgG+LbsVrMKEJ6e + uwWpCkgOhQ+uAewBiPcf6XIh1/pboo31+xZIfRqvmVIjtKyXtQlsYWZJjpD+vETpg7QnyBXoXrBPVuXJ + aCkcUSXCGAB/rIzzFzL9YD9v7J1ms9lurq+vX5D870r4XfT4XiMBbG3ICn7UvQVZur681Fgmqy3VAH6z + gn8P3ozJK07f1az8jJGcI+hfkwC8GXraa1+MTuMyHLP1XvlOj/+CBEDjCDGCx0sCvO8xJC8JgAqA6//M + CRwBVM8AoBd4zQvve154CYCFP0oAUAEvF3D/mRF9HycSk0CONf72xt5s3tnbo8NDdWIRk3EcH4bHewPG + uLC9XoAXr/F0W2Sro0Pfy+/GOJ7P6cSj6WldIdBQYsRoAaZNQFg6TA8N8HMT0j4Ik6sfqiZcBjEQ7O7u + NpCAoaYOfFWNeO7ImwBkebMRDnDq0sBpMlCXD4fw3QZssgPF4Onn0qrZWCq0dWg4sG7tkDWcL0ABzJME + cOQeDFYWjiKUGwYJ6I5BeH9HbjLCAcTViK0pv5lwY8xvrAyEaFPVhsinmihkKMAx46weZMxP0/5/JANW + DTIXwGXEkHtSGXDTWArU/QUPQSKBYgbwSQDdWTFa08+lyD6QjwWen8k+fv4Avsd4GUubWb+QKhOVuTJR + kScTIIKR0gwZLYHh6Ae+154/f/7vOhy9+fTgh4eHF5b4fpnn9wHf5/UJ/s2VFRmqrpC5mhJZa6uVja4G + cVsAfHirHcSsh05O/e3T5Tqd9uuN31/Ne4FOkBP8XJYD0HmNI8BfcAw4wPKCHp+JPZ0Z6DJAzzVxzu2D + 7TnsOs33GD/y516v/VxzA5wt6FLQ68hwAEof4z4knedagWe8nqT0bMogkSOfN4ViIXHtchyYxSK7Vpue + 8/N27ACQDUC3dUOuW70tvTt1EvFWe6usNTboe1UlgCj4PgU78wRjxlQkzhb0zRvc41xDkAfje3YPopwn + 8Hf4PtYc4PEOnqP334fnZqETwX7o6vUqAKcmAA1SMLz/oXr+fvX6rIug5yf4d/A+FlkxEcjlwU3WDvSY + VAmsWdpkFaSw1NUos82VMgPwcyfmeE2xTMDYPWmgEN4UhMCmInZ4YjticGduotiz4qQNcXkHpLrh9YN0 + tmFjVDDCg3Cp0L6CdyD7b0lpwA0penzN2NkXeMuo8ot8pOXCrCBs5hZkqAMqAlNSmCYGSQQWWCvO+/OT + lAD6AfyhYjY4SdbCpbEK9n5g/ilLpqsLjMcIB0ZK0sVVmOoHvq9HvxM/uJ0dD8D/9vX981L/bZKfG4G2 + ttZlYXxMxvCjZ7PKjc4m8VjbZRs/oD38oHZtbH6J+BTS+YTLZwAiY3bG4T6PTnn/CjHyq5lpTe69gtFD + v8B9T+D9ngE0x/SkOD8CYI65JEZvSwByak9rs7jx+TsAI0ngZMKl8b/mB0gYJB1OFWYl34gxPZhG7/5s + jMuP3rBhakSz5wcDHAiK+NrBIaGQycyLANxb7QB2h9G7n7aBz3Rz1Beur7Y0AvB1stpQK6v19bJcU41j + Hb6TVXY5U9CEe5gR0zvgxQcGId37xG21qoJwW0AgOOdr9wD2XTtkfjdf26PDSXk8HB1UL66A9o4sp3fn + uRH7O5QESAhHLiMZyucPfSHAiBPvx/83vD9rLzxQF9v4f3H3WWXDYQIZgHRAAquWVlk2NSn41RC+zTZW + KAGMIRSgjVcXazjg5AaiAsOcUAOOrASEf+liBxA7UsKlMylUC32aooNBCpFSgxi/KSZY8wBcDqwMuq31 + /xUBRqFQBWL/1gSEDyCCKpgpNUrrC5pjjSXC9tgA3UTUnRoprSAGLu+Z8BkOKA9XSRrCkxyEAvEyXsGu + zIj/C1NkurYIIUyhjJVny3hltiYCf+fBPzAw8Htzc3PGFt7Ds2Tf3t6uEsK7vD6PBvg31bY2N2RhZETm + TBb8QKrg+RsU9Pus0oMdMeHHuvl+h3p+eu4XjMFJAAr8iVMp/wJent795YTxWEHPNXHKbXjFfU7rBTj2 + 6Q2hJhSYpnbZbKqXpYoyWaqulPX2Znhcuya9ns+CPCb4mbgHvDCnCRsE4NLHRwDVEZTDkRLGsMp+AucA + 33kb993uaJXtTkhtU4e4uzoU/GtNTbLW3KQzA9fh4VdbmmWltRXhTpPM4TtM5OfKdFGhLJaXyxIIYLm2 + Vna6u2WzrV3W6ptwH5POF9y22UEeXTpjkLbW2KyzBndAAnyeSmGrs0M/b9fGgaO9Wheg33HUWOEwlkJd + mlA1pD//znYlLyP+95KFJgSNMIGJxd0B9iVgQ5IekIFDm7RugQQ2tTagW9bZcgz/h2v2DhBBoyy018p8 + c5UWbo1xS3EZ4urKQnj/fE0CsrUYNw+xfwBJwJ6TKAPwvNasWGmHVO+MD9UWYs0gAhYM1bL8NzJAC4XY + D6Am7IFBAoF3dAmR/QZZJVgX+kjrCLij0JQWiXsF4/yhUSsA8JuTw6UjNlCLfuwgm97MWHj5bIAeSgDf + YbISsj8vGcDPkZn6Eg0HRkEKv9MKgPG+1WZ9vLGxoV6fsn9vH16fwP8la/yXY36CfxvefxEgXbLgx9Pb + K+utjfBaHXKEH+DxoFNOAKyjgV4F2DF+pOqFGXszI+4F+osxksG4jv9+PjaqRq9/TE8Pz7gPD3/AIRkA + IgHJib80TzfAYuvU/MIaO+RWlcp4bpZMlRRAgTSrVzweM+oCqD4Oeq24FyQzZPsxpPYByOTAapY9eO/D + Hm9BzVCvkWDjFt/melmvr5H1RhybG2SFcwgb6hXQS7U1slzfIEuwhdo6mauslunSMhnPgxxOTJSe6Bgd + G7ZYjfc3NQPI8Ko4cn7gWitIpB2xNkC/3NgoK6oUamWhvEI2WG3ZbYFKgCHUWGtuNoaTghSYWOR0ZCYM + uZpwPGYkDplfYFKVBKDVkdzmPOTwZv9BnKP9WhJtbIZiObRRdMXeg0yYsux6j/UBLBDiHgz8rTahfNbs + Zhi+N5TcUkejLLZBBYAAZhsqZBIh3gQUwHBpng40HfS2EOuDDZfjcXGW9OKc+wXYTKSDNQEJoboRiDv4 + uF+gKuiBDi4teXIbocAdXR0oDbitG4jYQagNoUJ9WICYUqKlgwSi0j9SQ4OOhCBpjHisFYmsBOwECfRl + x4ktJVITgVPw+IN5KTJVVSCjIKXRokyZqimUqep8GS3J1FDgdxL8nL3e6+wt3treUuD/SZJ9b8j+LcO2 + NzdldXRM3E78oGZmZY0juQHKQ6cVwO81inHGAWqaNwYn2J+NIL7m+jjl+CCOtCHE2/2Q5EPwzsPDOhH4 + ZGBAPf02JLYH4HdD4m801OkQ0A3I7LUmSG126WmqkYXqMpkqzpeh9BQZSE8WV066LLfUi4fNN2EeS4fm + IPZsZiWSHUun7FlNsmfuEA9IZYfTd3DO5iDMrm91NQPs8N41FbJUVQHgVsg8vDuPcxXlMgPvPllaKlNl + iIlLSmU4L1+Gc3OlLyVVrFFRUnXnjozkF8pKc4sCn8cVHBeqOPOvHqRRr8QxjffOlUG51NTIYlmFrNTh + 30elQGtu1hDDg1BgC4Sw00t5PyjP51gzMG6MS+d4co5Os1p07Z9e/kiz/k5vjYVLTesAvOXD3A3J3Y/a + c5DVludLrhH6uLmLEipi1QrV44QawN+DScFltiDraJJFkO0Ci4XqK2SiCmFAcbaxZyA/XfqyuGkoDfE4 + m4hkggBSNC/AOQPtALEZAK2PNHoJVAc/hDdHbA8yKNYpxo/VuHGoVhVAgLTEhMDTB+mmo6boJ9oMlGXC + rTg/bfARH6wk0BUfgs9PEGtyhAwXZ8h4Obs2Z8lsfakM5aTIbB17ISAMKM2WCYQHv3PgX1xc/Ft9fX0K + 5tNk3+7eGxt63hb3n3l9o8CH6/xbCB92+rlJZVoOxyfguczwsnYA1ykv8CPT9XZm2SeM4ptnKvkn1PPT + sx/Bm+1C6nIi0CHODxAXHzrhtQfwmDEvvN4ufuCM6zebGgH8OlkGAJcrAZgqgrFM5suLZQYef7wgR4Fv + i4/R8dmN4cFSF40fXGYKfqQcvsnpvk3wxK3wsk2yjlh9qw0AQ7iwydl87bRmzV0s1lUC7CUyh3tzOOd0 + aZFMwSZLEPsWAOiwobxc6c9iJ50c6c+Gt0tLla7YGGkMC5XqkGApeRoIL1gsc/DuCwD1YkODhgizlZWw + Kh00Ol5QIMMZmVAN+fiscpkFGXDS8FID/q3t8Lp19TqOfBPgX2sFGLutsmUxI1xo19CACcattlYNT45d + xtIncx/0/EejRkhAEmbIoGEC1JdvK7Qe2XeQS45cOuXzE9zBOCQ7UA/MDzAnQALgbkwSwIa1U1cIlqGu + lpUIarXnAJcHXeyYXJaPvwmLbjL1cR9CASe8sC0TnjkjFl47RNpig6QDXlulfSTLfZ9q91427Cx+cE3q + 4OU5pZilwe2xRpa/HcA2Qepb0iK1RNiCc5YDc1MQC4W68JhVhzriDMdefJYDocAIZP9gPv7/GQaw10FJ + tszWFmtdCs9/p8Df39//3fn5Oe3a4wP/7jsz/Rfj/bOEn1u2tjYV/O61ZchGxtcA+cycZsIZp/uSdK9n + vct6lPMsxdV6/HFjHZ5r3fBm+/gxu1tbZBu2Y4ang7fbMcObQZJ7EG9vtgCwsM2mBlmrNQaEzAOInAQ8 + VZQnE0W5MlaYrUMx+tKTAMAoqQbwC0MCJTc4QHJCAyQvLEjyw4KlJiFGuhEeDJWwC3CpzNdUAZhs8smm + mpWQ8KU6hZfddadKC2USaoIzAEY4kTcvC/EkftDZGeJIY6dceHmYJSVZOpMSpS0pQeoTY6UiLkaKYUXx + cVIYHy81eM0wAD8D7z6J4wRAPs4ZB6UlkKQF0peaLH3JSTKYno5/B4imBKQGVbCAcGARpLFc14BjgyxT + PcCGs7NlLD1DRjPToRSqlbA8+LtxhUKTqmwWwg1Ro0aegDULXPqkElD5T3Jg7oA5BFZMAvA+MtAJRCAC + bsTaG3aqufu6T7dib9o6ZM3SorZqxvcxtcgiC4U68f26jByB1gjkZ2gz0gFtOZ5ihAYlGdKdHis2WEcc + Q4EQaQbw2UCkPTFCqrXd+F0penhdyrkRKCZIFQB79xH8/fDmnVAO3MTDwh8m/bqTwqU1xig7bo4M1BCj + PZYDUIK0JoAEwFoAVwEIoDwbf/9ckHaKzNQWqzKYKMv93QD+L37xi3/darUGMd6n3D8P/h3G/Hu7b1T2 + sZLP43FfIAAay4K3Ntdla2NNdqdn5Pn0rLxcWDIaVPT1qsR/idj91eS4vJ7h8h0IYtilXv/EW5fPJOA+ + PP2u2azg3kJMvQpvuFJdKRvwyusNkPQA5yp+4BuNiJfrq9XTzxKUhfiPK+Cwjkzph2fnSK6upBipjw2X + kogggP2p5KoFGQYyyIsIlfyocCmIDpei2AgpiY2UCpBBbQqAmwmvDVB3ZmWIOStNLJlpYs3JEGtuJsgi + U8x4rgtga01PlcbUJGkAYGtxrAXwq1KSpDw5Qcpxn/K0FClNS5YyAL4UVpLKY5oeazMzxATF0FtcKP0A + /QDIy1WMY06m2BPjxQ6y6E1MlImSEpmvrgIBAPxQOotN8LBQDNMIMWZAGEOpaToNqfLaNSiGHNlob9F6 + BBYh6ZZhXdpkAnNQC6S07JmdjLR82NgjQCIg+J8R+KxtmBjSnZRHSgiGMT/AjVkMBXYGuFcAJNBj9nr/ + Vt04xPZq7MzE9mxLHSAsdmXuqJX5pkptt86mpBwyws7DDi4Lggj6CtK0xbhFNxEZScGmiACtFmwGcFkL + wJ6BpYFGKMAx5XVQAlQNPfm4T2EyPHyw2NNjpA+PTbiHJSkUJMBGIHh9xBNdbdC2XyAFbvbhtuWh/BT9 + HrP1Jfi7Z2gIwLwAy9J/68HPEUh18CT03JrlPzzr3KPJPoDfs3sm9y8W+Bge/xT4us7v1oy/Z21Fjian + 5JVucJlDLGpVqf96bkZezYIUJia8ib0xjfN12Y5FLPixHnDJsQsxdgukNxNq+HHPcuYfgNMHUE0W5yng + Kb+n6a3hiUcAxkEAniOxnamJYoG3bQKYyyNDpICAD4XHh5fPCw+VvMgwgD1C8qMjpTA2WooT4qQEQCtJ + ipey5EQpI2AJXgC5EsAuA3grM1KlymvVIINqSPvqrCypzj5nOdlSBQ9cDavJzZEaSP9qxPxV3mN1Di0P + r+E1WK5hvtfU4t9Yh3u05OVJB847QSyd+B7diQniSE6GR8K/GQQwW1khM1Vs6cWuPo0acnQGgcw++lgi + v/MdqbxzB89XiNvarbUDLCA6GvEWEg0OgmBd2rSEycET3Sg15FUCRjHQMSQ+cwJMBh5P4P9lfECPxi5M + bmbiDkwQwGi/sV9gwCFu9h60dcmGrd1owQZTErCAAAB+rg4sttXITH2ZlgyPlMDbcgR6VqJOH7LDOIaM + ewd6MkDaLBWOCpROjmljph9yn2v92uWX3X6CjBJg7gUgwNnAsycrTnf29WuxD34rBcniwDm7EtH714cb + ewbYtbgVBGKKfqr1CLoBDSpklH0i6xDCleXIZBXOYb/V4J+Zmfk7LoBvBwDXLP8BvP6+N9nn69YLEvB4 + 5f7OW+L907if4PfG/tskAHbHmRmXV8szst9n1kz/q+lJ+YZbXWcg+0fhgYaNhN8xd69R7tttsgfJv93W + JluM56shv0sQY+fnyjCAaQsPk6Ynj6Xszm0puHdbKuDBGcM3wGrg3atgpQB6EeR9zingwwD4cMmjh4+J + gocH4OFRSxJ8YE+Bd06VinR4z4x0gBoAJmAB4locaxAS1ALMdQBlfX6+1BUYVl9YKA3FRdJYVAzDEedN + 8NANeMyjzxqLaaXSVFym1lhUKg2Q8fW4Xl/McxiOjUV8L6y4WBoKi/U+vHczZH8zPrsN38eJ8xGQAMOE + ARBIb1o6fvDx8GbBknvlikR+/wcS8offl5iPPpTCe/dkCQpho7NDPN0cIW7VMuR9pxP/H33arux4xKiM + 5H4C3S3JLdX0+rrlesjYHTiC1471q5EYWGNAIqAq4CoB9wzseXcPcr/AJpUAQwGAf93apsuD7MfIXABJ + gApgDPKaWXjuvmPrcW4dNqYMxUPCx+nUZGtanDSHQ7aDCDoTwjQhyDl/nDHAQiD28GN7sEZvyzAW/7AN + WX9hmgwDwCO6ypCuZcgcDMNOxmxLVu9tOcYuQHyvNSNO9yuwanGoJEcWWqtllFWB1fm6SvBbC/7e3t6f + LC4vGVt4z4Ff7Zck/C4u8fkUgNu73EfwwzbWoQAg+2fhYSb75WTQBgJwggAm5CVbXNELOfvkGD/Ek34m + 8+yy04EfamurIferKmWF6/VlJTID8I9BdjvgrdsCnkjJrVuSfvUrSb57S1Ie3ZO0xw8kI+ChZD59JFlB + TySL4Iesz40IVeAXIuYviosF4OOkFJ60DPG0Ah5gr6Gn9nrrWnjhhvwCaSgoVAA2AojNAHBrWam0lJZK + a3mZtMHaEYq0I1b3WRuP8MYdlTUXrLOqVq0d52143rAaaa2sxutrLj7Ga9orqtVaK6qkBdZGKz9v+FxY + J4jDkl8onemZkL+RUnD/vuTeuCnJX34pGffuSyXCisa8AilLSEIoUSxrra0GAbDISKsHjZLkAy5xDvQb + y6jMybBikhl/3QuA/5PRvtMNU1weZDigCUSECydTzAkMGy3OvKXDNNYKsGCIfQRYLbhiajVyAB31umOQ + DUU0BGBtABuIFqTrIBInwM8Jyw54Yk4d7ogNFWtyjHTFh+tOQU4cYoafIYA2+WQDEXbwhQfnUFLOJuyI + DxNTYgTAnyvjbGHWVCGjZSxFztKehcZmpBhdLWjC6xs5IRmk0RIZqCsRPZmJqgAmWA1YlYdjntYD/FbG + +zabLWZza1O9vrG+v6/r+741fgX/zu4vXerzEcDp0Qt+j9qG7K6vwjtYAPxuOR60yglixWfMQrNVFX6E + hza7Tvo9gEzd6SL4Ifdra2QVHm4BIFwsKpSFsmIZR4zdA8/dHPBIym7hh/7VlxJ/+6Yk3b8nyQ8fSPLj + h5L69IlkBj+VnLAQyWE8D3lfFBdtgD4pUSrSUtW713q9ei29eUGBeu4mAh0hRkuZAe6OqipYtZhq6qSz + plZMdfVirjesu6FBzXduRfxtqm9QMzc0wRrVuk/Pm6S7sdn7HF9rPG9SaxJLYwseN+M6nq/H6+qajHO+ + nse6ZumsbYDVSxesswZHWEcVjtV4XFWH71wl7WWVIKpKaSmH8VhSIa2lFdJXWSXLCKPWWZVohrLC33rT + ws4/Nt2LoCTAKsn+Xi1X3u+1QRmwVsDYHMS8wDHrAbha4FUGR5D+bJ1m7Bo0BpSy1Zr2DujvUfBvsQ25 + tVOWu5rVlrqalAjm4V3ZiJSj2dhVeKQ4x5sQTJAedhFKitKBpOaEcGnnsl5qlG4d5mpAF867M+OlGqFA + a0ywhgT13uahrBfoYteh5ChxZLGRTKb2JpioKZTx6iItQ3aBGEgwHVABrBpsYHiRFK4zCLgEyc1Cw+U5 + qh6GSjNltrlMVcBv10iuvd2/0dzcooA9OjoywL9nFPewsk89PsIBtUsbei4fL6sAjvlyK/g3ZQfksg87 + WJyGh+lGfG+Toz6rHOMHdsRKPVu37OEHeQAS4PmOuQOSv0G9/iKkNON92kQeZG9CrDQC5MW3b0nqzesS + h2MivF7Ko0eSHhAgGcFBAH04QB8Fbx8jxfTyTMClG4Cvg2dvJNgh2Vvo0eHN2yChO6AyOgH2ToQZ5to6 + L7gbxYa/j625TWwtbWKH9bR1qDnacYQ5Ojr12NvRpeeOzi7p0Wud+tje1o5rOOrrca29S498bMfRhnvZ + YbZWvrZLX29r9T3msc2wlnax4xq/R3dTi5qlsRXk0HLBOmsblRhMNQZBdJC4oDxIYvbqWsTfrRoGbOJ7 + ctlw02wSD+sCbEY5MZOt3IPAVYIDJzcvOXTegZYPc4lwmisCg97iILZg4/BRA/DaN2DA6DJE4GsPRrtJ + NjUZ2CYr7Q2y0FwHz98AFdCgqwGaD2io1BFsHKPOAawDeekq/2nmxEixgAjaY0KMRiFsIUZPD7CbUqPF + nBypFX+WpAjNDdCjcxNRe1yoDnt1ckhJcaYOf3UBzNON5fDohfDuBTJYmqWkwmIja3a81PM+uvQYLr1Z + 7GWYKGPVhdJfkAKyAlE1lP/2gH9qaurvjoyMKKi1uIfJPq3sO5fp9yX8dt4d61+U/mdFP9ss+d3a0Kq/ + PVzbx+ND2NH8OGJ8mxw4TAB8F2L8LtnpbBcPjQUspnZZb6rXtfu5EoC/CDIsJ0tcaYgN4fkbAgM0nk2+ + d0eSHjyQ1McBkv40SLLDAPqoaHj6OClLSpbSVCbqMuHhcyHn8xTwzYivW8vK1UvSs5tqa8Xi9d62pmbp + Ach84O4FQPpMJuk3W2TA0i39sEGrTQZgg7YeHHv0vB+qRa/zCALT6xa7DHbj2G3H+2z6uL8br7VYYXY9 + 9uk9bTji3GwzzGQ9M7NxdJq6xdlFs0hvlwnfy6ymRNNu8pJGl5KGFeRgbQZpNLeDINpUOVgaDOVgroWa + qG6QUfx7mQtYbmqUVaos1gaAfD02m+z1OFQB6CYkp0M7CmnLNN3GjDCN3n7SKBLSHYKn24X7lAz2hhxq + 7NHIMmEtGQbRswvzRnersV24pVrnLC431+ogVIJ/Dsexsnx4/0ydNsyx47QezimEmROjpCMuTJcDG7gn + IDxAuwpzXgBB3gW53xUfqst8jfD+3VANLQgV2EaOwO9FWDHK6j5uRy7Nlqn6UoAfoWRNsfTmpUBJxIot + J1E7B1nh/bkNmasQpsQwfc9oBV4L1fBbQwA9PT2fLC8v6xIfPf8+23adi/lPge+zPyEBnBKBm0nATfFs + Gd6fBMB23kfbbjkmESzOaOnpLufntTaKm2Oymupkrb5KVmELlSUyVZgnY7kAflaadMdHSc3Tx1IIz5/1 + 5DHkfbDkhBLwMVIUGy/lySmQ9fDwWT4PD7AjNmasThnv8+oEuxU/fltzq9eTt4sTQDoDuF2GAGIXgODq + 6ZGRXqcMO50y4uyTUWe/jPYNyEhfn4zhyMc8juBoPG/YcO+ADDtwrbcf5zjy3NEnLodTXL19Z4bHQ7j/ + EK/34GjvVRvu6dPHgzh39eCazSFDIJUhkA6/3ymxgDD6vaThhDk6LeIAUeixwwxyICm0gxDalAyUEBBW + WPF3mKgm+OplpdnYR+DmTkU7lJijVzcUcV8BdyAeeucVGK3MvZ2GmARkGODdKkwCYBhwODagPQMOJzmY + dEQOSRLchITrO9xG3GdBKNCpI9mW4fU5ck37CLbW6GahicoiGUYIYFQEpoEAkgHoGMT+cWJLjhVTXLh0 + RAcr2JvYMASxugkyvw3yn8uEVnjyrkR6/TAQQBRIIxwKIk5GOc0I4HXmpshMc6UMIr6fYGFPbYmMVhfL + AAjBAQ+vg0yTw3RFgH0JrFAOHfHBShhjlfm6KjDTWPGb37IL8X7ClnvrNN4n+He9nn/3/DLfOfBve7Z/ + JfC3fbZ1Zjv4HHb1PSUAEMgxSOAI1042N2R/dka2ITFXO1tlsb5apsuKZCw/WwayUqUvLVkskPstcTFS + FRcrlclJUp2aJjXpGVKfkyfN+QB5YbG0+rx6Jb16DYBerzG3FRLZppK9Xb25s8vw5v1mw5MT6MMA2AgA + SKAbgB6U8f4BmRgYlMnBIZkacsm0a1htZnjk9Nxnvud59Nn44DDMJRNDOMIm8HhiAI9xbQw2PjSE45Ce + 62M8Z3zukNrowJmN8bEST7+XdEAuSh4wO4jDBpIAURhqpAcqww5FYVP14DSZlQh6YQ4oBVtLh1o3Qppe + EMBYGbxvXb1WGrKEmBuMPPjb7Nhsur2YFYDawJRty0Zc3qajbA7aq1uEj8a9qwIsGBru08lJLAg6mhmT + 47lJOZ7ljMJxJYODsSHjNcPsPAxF4OhSRcAhrYYqqJE5KIHp2jLNvrNl2ADMARXQFRch3YnRemyJDNIk + IMuD2QaM/f46Y0NADqGa8GPLMRIAqwkduYkgj0hxwvuPw/v3F6RpQnC2vkwGC9Lh/YtkhIoA5DCI0KAH + CqA7O1aHlHQksF15hIYVDoQBEywLzk+VWZDVb3TLLhPjPYCQXv+0Tfe5DT0kgfPg3yb4vWZ4f/cl8G+p + sQDIRwIeAN/X348KQAlAB3u6dZzXEYjgmESA5482N+VgdVX2lhbFMzsrW+PjsgZAzcPrTtsdMo4f8wgk + 8CAA3I8faB+8dl9bp/Qidu7rNJ1Z15lUH4DUdsFbuuyGDcOrjXm98ziA5AM4wTqpoDXAPTsyqjY/Nn5q + Cz4bn5T58QmZn5iQuXHaxdfwODc6pu+fHh2XGTyeHuU5rxk2jeem9BqOPMe1meFR/Wwep12GjbtGZMJr + 07BJEMgkvuOkkorLIA8vadBICqo2QArDXsVghB7nwogukoHJUAYMGZpaxVlWIWMgz+nyKlmqb5QVkOYq + bMtk0QYl7FJ04GKWf0THlLG9+g4big54uwSzV+AQpX6f9gvYdnRD+kMVcJ/AxIjavpYKjxg2iXtNDKki + YM5gp69b3D1dxnj09nqZq6uQmRp8n5oymawukRH2EizJRQyfBq8fAaCHw+uDAMLhoWONoqC2aHb2DZO2 + iECxJkWLIyMRxqXDaAA7G0oiWRt5ukpYZpyuwJ+oyjcmF+H5IQCfW5CHyqE8SjONJGN8iOYY6sOMJUFr + eqzMtlVrc9BFU+NvJvjHxsb+m3H8aOnpTz0/wO/bx793Lu6/TABuj9cuFfr4mnpsbxt2qga83l87/G5t + wdza3nvfO9TzEPc89uwgFAAZcLDnBpTA2prsrazIziKIYH5etmZmZXNyRtbHp2RlbFIWCEyClJ4XQJgC + ACbhISe9xymAYtrrgU+P9NqwWb7PawQp7QLIAWja4sSkLMF8R58tT07h2rQs4rgwRZuRxelZWZyCTczI + 0uQ0bEqWp6ZlAcd5PDYMpIH3kzwWvOTBx0oefExSOUce82MwHGdGxw0SGRk3iMNFohhXkpgcHlZimAJR + KDFQbfiUA4mA4QZDCCiDISuIwGLkI5hfMPIIXus0ix1hUDdUVH9mtgxDUU0UFOvuxLWOTtnkNmSrRW2j + 2ywbFrOsW0yyYe7yWgesXecEcOrSfF0VPHiVDhqdq0VMXwdvXsP5gpWy2NYoq10c0dah1YHb/XbdN7Dr + chgdhvqt+Jw2WWmr1xmEsyCAmdpy7So8VlagJNCdHiet0SFKAI2hAVoP0MWcgC71waAETCAEFgz1ZBrJ + O24v7ivkfMI07eU3CJnPMWajFTlKCiOVuboDsQcxP9XBYFmWmFOixQSv38qy4qD7YstKEDMJoKVKHDnx + stBZ95u3hZcjudii2xfvn7bp9sb6b8T8Oxdjft3H73lT+r81DNCW3tuqAnaUANzGZB81t472IgkoAcCO + SAIgkQOWHG+syx7UwO7yqngWl2V7YUncc4uyMTsvawDcCoE2MXkKWDWvByZofWBevPwavQZwesGtYKaN + 435TBoCXJ2dxnAGIzz2empOl6TlZmeURj2fmTm0Z32lpZv7s2rT3Nfo6EAMeL3ofL08ZtoD7kzzUlDRm + lDAWJ6bw78IRhLBIdUGVgcdzeDw3OnHOxr3qAaQwTFIYA9GNqCkRMGTwKgJVAxoiOGWw22HkDUgEmmD0 + JhSZK4Aa6EYYZYqJlb5UyOKCIpngTsXSEt1/MJyfB3LIkeFcbppKE2dSovQkwsMiJDPHRkl3Qgw8M6R5 + RLC0RYVKfdBjqX36SGoDH0rlo7tS/uC2lD68JeUwArcrIQpeOQPgLkbsXyfr9k5j3wC3DyMUYHJwqpqN + REuMcKAkD+DNQRyeK2Z4+NqgR1L39CHuf18z/mwmaoG3565BdvfhABIWETm8JOAq4XSiRBmpyDK6+4IQ + XKUZ2vJ7sDhDC4I4s4BTjFkwxIRiZzSnAMfoyDJzWozYcK/x2mLph1KY/00iAMT7/5a9x57GclzfEt+B + N9m355X+PtmvJb5a4edd7vMYgzk8PpBvb8K2TiX/m6HAOWXgBb8SgfuMAPa2z2b7HTIfgM854pBPPEc7 + 4OiwjU2ogQ0QwZrsLK+IZ2lF3ItLsrWwKJvzi7I+tyCrANgqQEdbmZ6BzZ4aAXzZFOReYC57AcnXrgLA + K7MGiFd89yO4efQCnEBfJgHAlufweny+7xqP+pgkMY1z7+uXEMro8/p5OE4Zzy9NeQli2iCMBSoIJRxc + m8C1iVklBkM9QElMeA0qaGF8ShWDEVr41IFPIQxDAQ2rGho/zRuABHqchhqwG2GBJhB11cK38mA1ViU6 + u6WzsFSao+Ol4WmIVN69LxV37kjpzZtScO26FF2/KUVfX5f8L7+S4pvXpeT+bakMeCBNEdxSyxZbcQAJ + 4u2MBHjqeDGlxmr7LrbzqoenrgsPkOrgR1IVxK28nOXH5p0B0h4TBpDCK5fmyRLIYM3cLKsmY7fgHJVA + Q6XMNlbKWGWhuMry9P7NkU+l7MFNKQOhVAfek9rghwr+Lt3oE6HhAMuIWUPAkmKW//azuhDen+2+2P5r + sChVO/8S9Nx0RNJg2MDVgi4oifaop7hfpK40dEJdWNOiNS8xgu8w3177mwH+9fX1v9nZ2XFuff+sbdfe + Odl/vrrP847tvB6PjwDc50IA91vVwHkFoGHA9pZ3vNe2d9Dn9tmYb4/nzBgebBmjvvdJArC9tXXZWVkT + DxTB9tKyuEEGmwvLsEW1dQByQ0nBsDXYKgC5hmtrswunRjCvKlgNwNLW5oznVmeNx6t4j++58+e/zJZ9 + 9wTw+fkkkOVpks20evR5gNOnUOa9uQVD6kOVTEx5lYFBEEteBeJTCCQBDTcmjPBjwUcCY4YqmKWNjHtz + BwYBMCyaOKcCdEUCJEAlQDtdTbD2noYGSgBcTTCBFBAW1ERHS8GNm1J6766UPXog9cFBao1hwdISGaKg + bYOxlz/7+rNE15kHL5qfCQ+aLQ6c97LCLidZzPCcHclR0p4UJc1x4dIUEy6NfD9UQHtCJO4RLWaQhwVH + bgIag6efb6jQUGCxqUZJYKauXOZbarSSj+W5nQmhOhI8/9aXUvX0AdTFTR0XRhXAxB2rB62Q8T4lwGYj + PZDwHOlFJcCcQD9UQE96jBYBMUywwsuzYIgEwKOFyb/UKGli+7AMTgYKV3XQj8/nBqZfe/C7XK5/yBbd + BPjhMST/kXdDz8FZzO+T/Wfr/OeKfDTrb8j+C1V+28by3uV1/7d1+fUlAU9zATrg03Nh5LdhxtTfAxyZ + IKTt4/17UC17SgIbsrtKIkBYACMJbEMZuEEIW1QGi8sXSGFjfkHWETpswE6vkSwWAPgFgntej2u4tja/ + BOM1GB7zuOZ93YaXBEgGa3pP47gKwC8DmHNDgzJptcpYa5sMVFWKLS9HOlISpCU+RhqjwuH5gqU+5KnU + PA2QqoDHUvHkoZQ/hmd9zLHXj6UqNEjq8LrGhFhpy84Uc0mR9NRUS39TozgbGqSnrlZsVVVip+G6o7ZG + n7dX87xWnI2N0t/WJi6zWYbxPUZ7emWs1wnrkzGuGgD8vnzAMJcWlQR6ERI4xAUScCEsGDAboHe2tYu9 + rk7MxUVQAYh/oyJUzlPa6xJcGrx7eqI44K3pse3w9r25KQAQPCqBX5ANmZ4nAwBwf1G29BXhWJwr/ZDv + joJMseemIY6mQkgWC494fzeN1X7ZKVrso7I9BWFIZrKMQRHM1XHwaJGOGJsE+CfxeKgkW5N7bOhR8uCG + lD6+rfMBSh9cl6bYYPXaHTHBKuOpBkgEjO9tyTyPUDVAT8+9AGwLbtexXxG66Yj7C+wZ/D4JUAAB2q68 + LuCOOPK5dTtChsvzdGVC6wD+xb/4F/+jt43D+jVp0X1LW3SzZdeRscRH4Pts91LCz0cAmuX3At9I/G0b + cT9JwNe6m95fE37nvf6ZKrhACueWAz3ecGDnNBewfY4EvIrAB36uFvB5KoFNhAuwvfVN2SURwDxUBCAC + koBhBimQEDZJCEoMK15iMAhgcxEEcMnW1JbOri0seYFukMMmHvMaCYBefcraLa6aSni7TLEA5JboSDED + LGaA2IzzdnjItuCnamws0hkdpkaP2QowNYQFGvGr12oRx1Y9uYfY+LZUPITcvndTiu/gR33/lhTfhfS+ + fV3ybl6V/OtXpeTeLZW8Jfe+lqI7V6UAln/7qmTDC9Ly71yTAsjikoD7Uh4cKBWhIJ6YCGlMTJDG1GRp + hrWlp0h7Rqp0ZKVLO86b4hDj4vs1RrPnHhtuhMPzRYstJc7IpMMj07P3A8BDbN1Vwo0xuTJSXgAJnY3Y + OUu9fh/7HpRQohfJQEk+XpeP8wIlgUEeQQx9VAa56VAHGdKL9/QCSM4CEgfugc9wggQcBB/UgBME0Q+S + ceWnywRAN1lVqENFOHZ8oDgLJBIvLQB5xZPbUg6AckxY+SMODr0rbZwhEBUoTaEPpSOK+YYQ3eOvfQDY + 8isfagDhiS05WluQsyOQJTFck4YkDS4fOkAAbBM2VJar9QVWKAU2JeFns13ZyLu2AxN8vw4ture8Lbq1 + uIey3+v5zxf4XM72+8B/6v3PEYD73NSeC+28Feiet6qA0+XA0zzAme14R37vXTIlAx5VAXgMElAlgPds + IIzYQBiBo4ckoLYu2yQCEIJbDeEBCGFrefVUIWwue23JIIYNHH1G0J8/0qgoVDHguDYzI3NOh0wD+IM1 + pTJQlKHjrZiVnqqAd6qskOnychnJz5XRwnzpT0OsmZwk9oQY6UmMFXsSLCVeerKSxQYz4cetY7AQG7fH + huqylolLW5DGrWEgjrAgscRGSkv4U2kND5IWXKt8dE8Kr3+lSbV2vK4N72uJCoYHDEQczek5iKcjAqQ+ + 7Im2waoNgbIAsdQEgVwCqTTu4/yRXm+KDJKOOHxePKR3PCQ4AN8abXwPSxK+MzyvE566l3IeAB0qzgbw + igC8QpmoLpXxanjh6jLtkjRVU6bPjeA5Vwkn/uB11ez6i79HWQmADzIoJfhzVQn0FRhkMVAAUijidKB8 + PQ4V5YmLRFHAzTncBpxsLOWlxEgvwotBSO5R3YZbZBBAYSbAmAhPHyUVAbe1BRibgVaBCMof39JegewC + XBt0V6oDbmqrrw7t+BOqCsOOe/by/bHGLsFOPM/qQQsz/wnhOimYDUGoFpy5bMpSpBONuTW4BzYKMurP + /TVsCrq6uvqfW602lfFHx4z3j855/r2zHX3eXX07l4p96OW1m6/HON9ST+89et4l9X2ef8t7fMuwj7eQ + wI53AOjbScBIEvoeq1LYMmwXSoBGMvBAEdC2SQTrG+JeXZNtmFttXUlga8UwzhvwKYWtZZLEqhIFjaAn + QWzRlgxbcg1Jf1WJTqxl48hFS4d4Jkdke2JIVnrMstDRIut2drxxiLvXIatdnbIAeT5TUiIT+TkyDA87 + mJ4MKZuCH3CGZq+ZxaYNUA5T+qbFGT/GTO57jxczvLU1MUaG4CXtKSQOSNfUeAVmIzPqsJ5cjtNiRjtd + elgjj9d1Ayj0iBbcj7E0gdEOQBsTd3kebhBNYhR+4FG6rbY3JxXGmD1LpTvPnXkZ6tGHINkJ6vFKgL2K + mfgymQLoZ+qYjKuWqfpKmeJafX2VzDXXygSX6mrKDVLAa2aa62QCKmmitlJcFSVQAoXwmggNCrkej/uD + AFyFeWrDxTSQZ2k+iIAkkCN9OWm648+Of5cDsrw/h5150o1NQmX5+je04N/NOQEVgfekCNJfCQDev/Th + DSl+eFNbhXEzUG3wPWkIf2w0BQVZWhIjdWORHX8DM0jQFBcmZoQN7AFgxWexnkDVQHosPjtWw5KFrjYd + ZtqXl6zvJQGwivDXrkX37NysxvYK/qODs0w/wG8QwP6FSj/dz39urV+lvsr/nUsKYMsgAC8heM7lAmja + 7MO9daoI3lUmvP02FXAuFNg7lwugXSCAX0ECHoQ7PtuGKiABXDRDJWwvrwHosBWfkSBABCtUCMuyPD4m + Tnibjpgn0hH9SDojH4kp6rGYYkLwo4AXh9kzGEOmAti5Ml5RIbPNTbLKtfJeu8xWVchITiYsQ0uXx+Dh + JuFBOCXX6IRbIqMVhSojB4uydMw5u+K6QAo98Ho+yTvCFtnwfKyJHy7MEQeIgAqhJztNxgG40cpieNBc + Y8MMPHV/IUgBRnJgn30206RpnJ0Wr3G7nXE2PXx2qsr3YYButKJYxkB0BDxtlC3NfB4enzNbXy2zDdXa + yXeuuVoWOxplvq1BFtobZa6pTuZhM1zzZ1flJhBgbYUstNTJtJJEFe5dhvsWqfHzlAAKc9WGoQ5cIJsx + kg1VRBH78eO7gYjsCEGsVCT87ozXs5KM0mD8nXpx7Mbfoyniqaob5gCKAPzq0EcA/y2EAWwRfl8rBLk5 + iMeWGCYxHyrAu+DhOQuQ8X57VBCIAKFZ0D2QZ4xWFLYhbOhOjVbi6c9JkSmQ3nBlnk4Q6stP0X0Fw5UF + vx7A/+M//uP/Mbx+wObm5qnkP/BK/jdl/5n8f5MAts/i/fPS/9zjrdPY3/D6W6cEsPl2wG9vnTP3GQls + X1QBPL4rJDgliHMk4COA80RwkQw2VBUYysC4RrDrNZABjY/PyGFNvf9crwX/wWw+ESE98Px2eNAexonc + igpwWuCdzZDNHTHhsDDpioGHjY2Gx00UW3a2rPb2gAxqZZK9BuFRhzJSZASylm2uZuvKNZM921gFD1pu + 1J9XUlYXaVPMScS3Q/iRz4Is5jg4Az++cQB7BICeYedcxs9pidIZHSGTjbUy01KvnpiS2OWNyUc5gRcy + eZhesoRNL7JPAePMhXcv5jUAD9J7BCHMOCT9BIttmqphlTJdx/l9pVq4M9dYo4Ceb66Xlc5mWWpv1OOq + pV3WurtkzdKpuwkXGgH8eryusU6WWptkEWQw38j31co0yGCyslRJZbzKOI4iVKDXdxXgO7NbE0iB4RQV + AJXAIEMEhiAZSUoC3ZqPQPwNNcTrjsxkXXHogidny+/akIfaG7ASsr8aIU5FwH0Nf9gtmHMDasIeautw + zhSgEmDTD0tarDRHcLcgQi4QAL16F1UAVANj/RaQfive68zmhKAMJWpWADpzIP/LczRPMf7rQAAA+79f + WVklboCCwL8Afkr/fWOdf9fXwHNv92yHn3d771mJ7/Y5r89qv7MVAB/4t86BWBt8+khg6+IOwLeHB28u + E3KL8OUVggte30sKu+eIYHfLawT/1q8igs03QoU3jYphTRYHHTJSlSOjxQboxuAhR+GRaMMA8RClMryv + Mx0/zKQ43ZtgSUA8nwDpnpSEH06G9Bbm44deImPwYC54qCEAdgTnE+X58Iyl8Iw1Rjfc9gZ4zBpVBdN1 + pZDSONYWyzB+2NMA8mpbrYzDiw8jTnXBI84CPDPw+CP4LgwTRspLZdlqkmVI01mAdIrAJZAbqmSSoKup + UIUwAuIZAbG4WEqL+/IxE3N8bhLefQZkRMKa43Ibh3e01mkV3yIk/CIB39Uqy52tAHuH2rq1SzbwuVs9 + NtmwW2XDZsFrOBa8FaCvl2UQwHJbo0EeCA/mW2oNAiilhy9WFUCJ35+bCWXD/f74G5MEivMMIoAKYEfg + AYQAvelJCn5zgrH9V5f0mCBE7N0N+c59ALWhRvffqqD7WmPQAACTCJgDIAFoc1B2+QEBVIEkuMe/JoRd + gKOkLYaVhA81wccVAy4btoQ/0Y1EpqQQaQq9rysRTigoNgyZbigDsWYrAQzgO4z8dfcEZIvuocFBlevH + x8de8BvZ/v3D/bOlvlPZv3vWu597+i8l/95M+nllvOec9/dWAXrOd/vdPqsFeOdy4FvNfUoAGhLg8c5b + vP8bhOAlAVUC7oskoElCHnHfnXME8DZifHFRrQAASy1JREFUOA/+zZlpWbS3w2tBvtaWySJ+vGwDvgIv + uwAJPI9YdqYCcXBZsSb6BrIy8ePIhCdALJ+GWDwDUh6x/0htlUzUlckYZDzBO4If0FRJjkzDq3Ndm+vY + K10tsmpq1T3w7ITDLbHaDovDMhDbTiFedls7ZKYcKgIerw9ecAYyfR7fawoAcSbHiTMtVdYBwPWebqOz + DiQ5ZTqP862U5lAHIIIJSPnJWsbl3iMJpx6fA+VA4C9wT35XsyyYWmQJpg06cI9VfMd1WwdAbhZ3r1U2 + eyx4bNLHm/hMN5TOdr9T3E6HbDpsBhGY2mWpuREk0AASwPepYfiAMAAhwHABQxwAvigfXjUd3j0VZJqh + Up82oiSbLWPMPUDpsCuwIy0BBBADko3WLcCmhEhI9FhNpJqTY3QmAON/rgAw/q+BzOfg0BZIePYHqAx+ + oNOC2SK8Dt6+Hh69iQ1ESBShT4x+gmFPVAHw3uwz0Blj7DBkwU8b3tMdF6lkz/6EkzUg01KOBcuTXig1 + FiX9tYHf6ez94eISW3YdXKzs05r+g1Ovf7rBR2X/jprnHeD37J4nALe3s6/HyPB7s/w+sLOox1cHsMV2 + X6fJv7c3BnmbnRYXbXuLjc7lBXz2Rhhw6bGvvFgBz6pDkon33OMD/qZxzbOxeYkIEB5oe7IVWXfhxzxo + k3UAYAvS1sMhnRymwdp3U6esQequtLSADOpluqJKB3mMFBXjhwGPVYy4vr5BJpubZLoNYOpsBAGkymh2 + kgzDe4wXZiqpsN3VSleTtsTeAMB5vtzZoBVvK7otFqCFF5yARPYgFFlsKJeJMoCGa+wkkupimaJHB2BM + EaGy2Noim047AAgS6O7AfduMfvud+J4gAUry2WZajaoNensO4piD3J9v4cTeelliVx4rPLitS1ZhK7jH + qqVN1rvbAepO2eDEX9yfrby2evH3cPbAcO6wAvycDtwvO4P9stEDNWDv1p2cSy0NILU6mUYoMQU1RO/f + n5EOwswQR2oKZH0ivDor9FJBBCzEYWyfrp5/GOB3QW0x8cllSBvIjuA3A5wdCRHwzAjBYJ1xEZDwQfD0 + D9X7syU4pT5XQjpTIrQWgPkAAp+dgrlK0Ai5z3mCOkjk6X2EbpD8UBbcVtyM97HluHYaSmTSN0LLgJue + PsJ3y1Ii4grEcFGmDjAhEQwXZ/31xPs2my2SSTeu7Sv4jw7PlfYaST+jxHdPO/n4dvjt+pb9vF193rrL + TxOBRgig2369R7fPw2/71IH7LAHIsuBTFXBGHG/rDvROEvAYpcbnwb9zSQFcVgbn9xb4wH/eCHqqgx1v + KzIlCS8BGGphA9fWxD03I7uzY7LDqTbsfjs5KUcTk3IwMiq7AwPi4dBNux0/bsS8rW0AVpPM1NThx10D + r0DpbIYHNctcV5fMmwEieyc8NuJb/KBGsxJkAj+aWQBhGfLamJ5r0km67I+/DBVA8JMMWPY6W0WQF2n3 + nKWGSvX605DwPfjRj5fmqZIYQ5xsAQGMFhUCoFYF4wbAy2m8OpIbAF4DISx3tskC5TibbUCKLzBJh3ty + c40m8kA4i51NstFrlnWHBQTQqe9j915+R07z0UGf8P6qAuDpDdDD60MJbEEJ7Az24THsnBpYboeagAqY + hvoYKS6QMRBAd3wMvCm8eUy0dEYBvKEhYoqNBdBTVA04OQyEYRZrAhjns+AoOVaX6kgAXDI1IeZnOMBW + 7lzCbIkOlbqwAKmG3OccQAKcUr+VPQIA3lp4d9YFVATclPInt7TDD0OCZjxXE3RfW4EzGWiOD5H64Hs6 + I5B9A7il2IFYvx33bgp6hO+YpAnYgawUzVMM5qXLeEW+9OX8FQ8HBcD/wyZILALJJ/l93Xv2LyX99rze + X7v47l8s89311vmfrftfJIGL4YDnrUlBXzhA8G9w0If7PAF43rJMeJEALg8KfRcBXE4MXggDztkvJ4Mt + bURyuhtRQwRvmLC5Lgewb4725GdHB/Lzk2P5+fGR2rf4uzxfWZPjuXk5GJ+Q3SEXZO8APGCfrFpBCDZ4 + Q9ewbA0Py0Z/v6z19ctqb69s9jvgvUtltihNxjLjEAKQAIpkDaDzOK0KHreTk3VtunuOk3LYJmsNJDCD + 100gHt7B88uN1TJZwR709KBJmkx05WUqEfQDIN3RUfDWXTqHbwOApcfeosfmmG4QyJrFpPH7QnM9PD/D + DMT3OHIpb74DwHdCOeCz13vMOtBzpaNZVqEc1s0gAQB+G0DfgOznoE/atvbzw/eG/Cf41zgKDWrA7b22 + CxLYtHXrc3ONtTJXVwXAFKkRRK2hQQBUoDQEPpG6J9zI80RM8czys9V3igxxiRBKgM0/2P3HCgXAI5WA + mfIfntmG2J9LniYogjbWTdB7R3NKcKA0A7hdOgMwUpojg0EOIIDHt6X43lWp46xAhAlsHc7lQfYNbES8 + b2L7r9QIbR7CXX+sBOzyEkAHuwwhxOiOj9UmpdaEMP13sUsRczpDf5XLgLOzs3+bLbsIXoKfXp/APyvt + PU8AB17ge0t9vcm/84m/U/B7S3+330ECvq2/58F/pgAMhbC17X5LObD7whTgt3n9N65x+fAtBHC5dPgy + CbyNEN5JEOeShyQEz9qqvDrcl1+8eCY/f/4MxxcAP89fyM/wd3y5tilHswtyMDYpeyNjsgvzQBlsD4+K + B6RwsLwkO1PT4sa1jYEhWeOobnjFlXZ4XMSKk3kpMg3JOA8FsAQpvg2gEUg7/UaHXPbIcxO8III1yPGV + Fkh1xPk7IIo1bolFGDDfUCaDiHsnEB9PMFEGY5xsggSerq4EAXA8t00JwO2kNLfjsUMz9SSApTbG9QwL + mpQMmNVfZlLPbpE1mxkk0KUSfgu2CtVAYG9CVXgGerVBC4mABOAhKTgAeLtVgb8Nyb/GGYl4n4fXQARr + HW0Im7pkEZ8xV4P4v7ICMXMFZDS+b2y0NIEEalgKffeWVjw2RYQA2Kxj4NZd1ktkiR3e1pmZCjAmS3dq + gmb9qQK4T4Br8pbUWOnkWn56vHQlhEtbfKgm9VpjwjQ3wEKplphQJYamKIQCTx9oY1BOCuaqQKtWCQYb + KwAAPbP+3OfPvoFMBJIE2F2Ix3qQRhuUwHARVBeeZ38CtisbLcnW7cR/JeB3OBzvr6yuqKxX8KvXN4Dv + I4B9b3mvL/l3WvRziQAudvd5t+c/7/FPM/0ew7bOVQKe1gO4f3Ws/7Zw4PwmI04e8hUM+ZYHz5cP774D + 6L+KAHxK4JQItrZPlcHuyrK8PtyTnz+D538GAnj2XH5+dCJ//OqV/Ax/45drG3I0PS/7o5OyPz4le5Mz + OtFob3ZejtY35ITbmqEQdiamZGsQSsCJmHjYJRuQ9wuVhTJbmiuL1SWyUl8pyyQAeFt2x90b6lMCoGxn + OMDrm5ZWWe+ol1n8wHYBuA0OF2WisL5cBjKSZawoR3MAXC/nj7AbP/SJyhJIdYvKcnbbJfApx3cRlyug + 4c1XTB2y2N6qimDJ1K6PV7tNAL9FJfsqrq/bSEyU9laEJa2a9PMMOmUH31PNxe/bY0z+BWns9HH/fr+h + Fjj2vNssu1ABG10IPZrqZInLh7UAf0kRwqVqSHuEMempumxaFxQglU/uS+Hta1IdjJgdcT43A+lOQkh+ + bf2VFG8og7Qklfz0/Cz8sabFa5FTFzcP6eNYrdxrY3+AaMb0oUoKLBFuiwtDvP9UGsKfanUk4/9mDgrl + UBEA35ISqS3AOWy0OxUhCtRDG+7BPQA6fQhKgduO6/BdB/KyjDqEvAyZrCvXlaGRv+zRYD/72c/+DZvd + nsiWXb4lPl8DDx8BKAkcegt+Di/V+u+fkcDeufj/Qj9/JYOLS4BnS4I757y++0JJ8NnmoIu7Ac8rgHd1 + Cj5739bFXYbnthb7lMDlpKAvJLgcHpwngp1Ly4a8fj4kuEAO8ODfAvx//M0r9fpUAD87PJZfvHoNJXAs + LxACHE7NKgEcTM3IAYC/PzsH0C/KCZTEqwP8X7BnweS0bIIA1noAvlGEBTaArrpYFkECS/VlslxfIWuI + xd32Th2XxYEZHhzdCAd2INe3e9loA8AzN8sc4suN9kbZYiyPsIF74zm/cLwkF9KzQCarihGDFkovgDFd + XwUP7TSMoBzolz3XIMA5ANAOyK6rX735uhWe3sQcgUWWOls1P7DOUKTHpqCnAuDrmPDb0Ck+ZmPCL1t6 + Dw8qCXgGezV0ocTfwnv28Tkep0EI+nqOJIctgQAWQHhzNeUyVlKI0KVABnNzIOWTpC0qTOq5PyE4UEoe + 3oUsfwTwx+p4NhMATWlvio8CCcQpAbB4iUt+zAfYEQbZGBqkJWh40A1v303iYLUj5Hkj4n96cZZU6x5+ + hAVM+DWGB0p9CLx/aIA0hj6BPVLP30EvzylDUBPs+8+KTAvCi57sJFiyNISwMjBBycUUH601DKyW5FIr + C7Mmqv4SB4MABP9JR2cHJDbj/SO1I2+yz0cAB774/+DgNAG4t3+29r93uva/d3Htn+Df9W38OSMB3yrA + OxXBNsuDd40VAR8RuC96f48XzB7P9ltnBVwkgsuk4Hnjfe8igXflCXYvkcKvVAlQVj8H+P/4Zz+TX7x8 + BQJ4KT8/gQL49lv5GQj3Jbz8MUKAQ3j+I3j6w/lFOVpYgq3Ic7z/9dGxPMO9DxaXERqMQwEMyN74mE6/ + WW2oVAJYa66UtdYa2YA334bH34XnZ798Ds7YI3CZTINnVTXQ3Q4CyJO5sjzx2LpkvatJVlrrZJYFO8V5 + ui+eiTw2yeiHKpiGzPbgHrsA6S4AvzPAnnzsuTcoBxOjcjA+LDsA8QZUghpjd8j/5bYmTe5xSW+Ly3g4 + 33JSMbQbyoRJPsr+ASqKXm31pQSD8IbKwk3Zz5ZgeG67125c5xRm2KalE2FLjXYAmqqpkKH8PF3ztyYn + GJuhGAZABbCxayXCgeYoSvd4eGIAMDlOi62seGznCgE3B2WnAvDxGgrY2R2Yx7QELY8mCRDsJoYA7ASU + HKVemzX95rRoXQ6sC34k1YEP9GgQwGNpYGVgXLAOGuG4MaoPR16q9jLQceQZCWosne4BCbRCSbBEmuZr + UTZW/pdUCDQxMfH3xicmVKL7kn0+739eBRhmgN+nAHQFwDu+63SP/7lNP75BnmerAGdLgKwEvBwCXE7+ + eXyrAqfmOd0peL4+4JcB/52rAG+ZMfCrwK+hgWfnrVWEb1tBuEgAblUAP39+rCHAN/i7fLu3L784eQZF + 8A1CgSMNAY5n5lUFHMLzHzIhSAUAO97YlFd4zYnbI4eLK0oAm32Dsj85AbBYZLmxEmFAEcBfDWlfB3C3 + Ilbu1Aw/R2gdsrcegOrpdejwDJLAtt2EcKFMZooyxW1pVxWw2lgji/CmU4hD2TCTNoMf4QCAwLX/bQ7u + GAHoQSg7zl4lgMPREdkf4dj1cU3QaXIOQHXD2I5rTYt7OjXJt2LGOYiHicNNPCZBbPda1dsT2G7G/b02 + lf9ccdhh/M9jX4/2AeTKAFcDfHUBfLyMsGC+oV5m6upkuKxUBgsLIOOTpSXCIIDKgIdSE/hQqgMegQzY + zYedfOK0zNqWmgQ5Dk+farQBd2Qke8OAeO0JSBVA8JME7Gl4PidV90B0JkRAAYBM4MVpnBNAyV/z1CgK + Ys8A7sJsRfzPvRFcEWAXIbb6sgHkvfnpYs9JkX4m/FKNnZB27VEQBUKK0r0UbATCEuqp2jLpL8z8S9nC + e2NtY129+Qk80Wmmn95fl/0OLxHA4Tnwn+UCfHUAe175f3mcl2fnYi7AIIKLy4JnBOAFp+fdxODLFbwt + xv+TEsHbCMAH8ncRgXEd5mGO4O1FQ29bRjSe25KdlUX55nAXXv9Ifoa/E+P+X7x8qWrgZ/Dur9a35ARy + nwrgELH/wcychgEHMwtyvLIh3+D/6MXOLghgVXbGpsQ9MCz7U5MAn1PW22pkubYUMXIDpH2TbFvbZMfe + hVjZqgM0OUr7cHQIgIJnZbKNSgAEsMYuOPAwXAXYZGKwrkqWq8t1uvFSC8tta0EApTKUlSaLzQ3wxA71 + /ofjI/DC3Uoqe8MGIRyODQPMDoQXRoxPtbBu6ZLl1mZZMxl9/Ninj6Bnkc+6tVNWTW1a8cdkopIGAL0J + j7+tyUa29LYb35VKhtN/YLoK0Ac14OgBqVhlDaHGbEMdlAqLkapkqLBQ1/2Z9W8JD5Gqxw+kMeQpvDHi + 81CO5mYOgPsVmPxj3A9gpxs1A6wVsHnrAqgCeqEKCFiSQw/DAhIBJLtuhIKcZ71AFwuHANym6CDdLckc + AGsEuG+gDtK+0/u6ZpBAZ2KEd9szPovbhHOStfuv9hJgxWDUU3zvMC0c4oDSIZDAGHcl/kUWAr148eLf + 6enpyaQnPVLJf3zm8Y+8BHB45LUz4B+cWwY8OLxUA3Cwd1YLcIkELq7/71woBPK8pTzYc2k50Di63+wZ + cApqz7mioF+eFPxluQLPnygE8BGAsZlo7x1q4K1kALJ97tmSn784MZYAnz+H90c48Pob+QXOv9nelWdz + kPwggAMA/HByVg6moQCmERYgDPgG/0/PQQDHKyDtqTlxD44YuYLRAVlrqVJvvtHZIDs9AL7DJHuQ2fsA + +/HkCLwzW2wPATgOHcflYeKNE3RNzfD4xTKLH9iWpVUWq8tkvqxIJvKzZbmlXqvsmGDjxOPFhlqtUzgY + G1G5T1lOr8wQgN13mQvwOEAA3d0q5XcG+gD6LllqrNds/ypr+7taVAmwloAFSqumFi0Ecjssqhgo/Rkm + MGG4B7BrWAACIAnsuwwC2PVdszI/wMSiWZt/ToMAZmprZLgYIUtOttgYBkAF0POTCNqjwwEwAC0FXhgx + fy9IrZeVgpnGZqu+LOO8O9nYUu3INDYz9eVn6HlPJlt/G7scbZDsVAHcat0WG6YA16rA4Ie6JFjHVYAI + 7h14pNOCzGmxutOTy30cPuqAEjAaiSZq0lETj0mRCn4OIeHUITMeD5Zka08Ellj/hbXsMoOhKcvPS/6j + o4Nz55fNUAMHXjtfA3BWEXjW+OMy+M83/7gIevc7koLnvb8P7N71/bcogLeu8/8KJfBm+7FzU4e3t38p + EfjCgPPnbysjflMFbMrx1ob84tULgP6VSv8//uZb+QXt1WtNCH6z6ZYXixxnPqcEcARFcDS7JCdL6/L6 + 4FBe7R3I8TqnHBl5gMOZWUjyfnjvBlltqJDNzkbZQ0iw5wRgBnpkH/H0CcBPY699jtrahbdlMnATsp+J + QlYBck+AG4phuaFKpoo5GCVTO+6ud3dqrf5wXpZ23N3lWG9IfSoAH/iZCDyaGNWk4Lq5S1t400t7mKkH + Aay0Ncu61vA3qzGbv46QgGTADT+sTdiACmDCb5vvRRjAGgANCZzGqC+GCVz7Jwno/D8Q2yZUxVa3SVUG + 9wewNFhVAMKAgexMgBhxdQy8amSYdMVCWqcQwLG6y9Kelqyen7sF2WzFmZOpicP+HIAdqsCaFCfd7K0A + j9+Xm6Gvs2dQBSQqQVhYLwDrSmQ332jdKszMP4uCygPuGcDXSUJGYpA1A1xK1NZfUAtsEuKAh9flxtR4 + DSOsIAUTuwtx0lBajCYb2Q+gNz/tLyYEcA0N/ePZuTkAcw/gp+Q/vgRyQw1cJIaz5416gLNVAV0KPLcJ + 6HwuYMebDzCk/67XzicCL+UFLoUC5wnA1x3oLCdg1At4zuUJtj3uSysCvkSf51csCXrebttnRPA2wF8m + hN13SP/LYcDeyrJ6f3r8X3Al4OS5Yc9eyM8PEBpABbxYWpOjsWnI60kQAVTA2IwczSzKC8T/L3f25GRt + E4SwJvvj07I/OSUHE0OyB6+/waU/ePFdEMABvKQxOcclz2Ym5GRyVBXAIT0owLMDj7sO8KkCaKiUieJ8 + 2YYs1xl6teUyVVYgk2UIA9qbdB/BaFGuTNWUy97kmBzPTgL0A9q/n8nF3f4+SHQQCzz+Jif9AKgqzyHN + 10AIJIVVLg0SpK0N+NxWPEZYgM9fbK4FyRitu418QI/W/isBcDWAU3456w8hAIllB8qCxLLn6tdVDZZS + r3cZ911pa5LZumqZrigVF2c5wqv3JAJU0ay9Z9Y9CVI+xVjyAwnweW4U6gXAHVACdnh/JwmA72NuIDFO + VwC0YUl2qrYiYzjAMIA1AJ0q/aP1yGXBOl3+C9HlwLrgxwr8tphQbSvOegAmDdkzkOPDuuH9nQC2VceQ + s94gQZODVBQcPsI5hF3J4UoGZg4azUv584Hfbrd/sb65oR6b8f55kPtAT/M9d5kELuYBDrUAaM8XAniX + AX0EsHe6ErBzwS4D/839Ab7zXd105D4n+dU77+ycJgW33NunS3hG2bC3iIilwn+GMOBdRPCrFMD5a3u/ + YnORrhpsrMnJ+qq8Bhl8C0DTvtnclm+3POr9XwPcz+Hxj0emIN8R4/cNI45nwm1Y9hAWHM1DDSyuySFC + BSWAsXGQBLyxwywbrXWybWuX/UHEywO9OjGXwDcUwKgcsfSYY7SHnAgTAB5Tq6w01chSfaUMIx5e62Cs + 3qw1BAv1FTJdWaLenzsKZypLZaq6Qg4mJ+VwGgQw7JIDF4hnaFABz5icS3XbNpsmGglSFvqwl/96J+R/ + R4ustbfoZzAUWAGx8LjGzUre0IC5ABYDsdjITZIaNJYDdwZZJNSj0n8P13adeI6rEQMsmwaR4d5LjbWy + CFtoqJOpynIZys+RoTyGAfG6pZqZfyb9rKlstJGl25V7MlO1Q1Bvdho8O2JxHEkEfSAF5gSUBBgOwFMb + DU2YFIQ6YGUkQGvyJgDbWQ0YFSK17DocGSQtsSwPDpWmqFCEB5D9CVFaMtylY8RiQTipmv236X3TVE1w + yY9LjiSTrqQI3U7cFhes/QSaQ40txX9m8Pf393+H+/cJZMT/8uzZswtApxo4OTl+h/c/B/xL8v+NHYDn + zTfhd2/ntB5AwwFfYtALdl+S8DwBXN4zsO0Do29U2LmRYQZQvQrg/C5Cz8W1/3flB36ZXX7Nn2R14F1h + wOljjixbmpdnKwsA5YQ8n56BzclLAPvF7ALO5+VkHB5/eEIO+l2yZ++XHWsfpC68ai8AxsQfgH84My/7 + E9NaNkwCOICkd7P819YBgDsU/EzQEfxH41AB0yNyjLj9CF57lzUBrAaEt1+C5F+Cxx9OS8J5tWyBQJZb + amWpqRqgqpHlVmOb7mxthczW1+LzEEpMTmj2f29wAB65T1cAtCAI59zH4IH3Z4JwtR0EYDIIYKXV2PjE + zU6rUAKs3qPXZy5gzdyuycA1hBsEP+sBaNtaHdjrXRrs12pB5hlINMwv7FBpcNWh24zvzA1JtfieVTJZ + USYT5aXizErXzL01JUEr/ezerD87EinYQXrsKMw8QDfCAbYk4zLgQH629GVnQJozQZigBUPGEmGa5g66 + 8bdyQBWwloAdkVqZ4IOXp/wnAdSFGYnG9jjmByL1dZxL0BEfLj1snJJnSHor7mvLTNImpfasFK0DaFXy + CNKtxjRuQa4MuKu7C//MBGDj+GUAkuCnEfwXCeBN8B+ebvw52wNAAjgF/vkk4MFFAriwLLh/bsy3rwvw + aSjg3SdwWh68cxomXCAAHxDPhwgKeLeOBvP4dhNuG6XCvg1Fp+v8p1WE7jdI4JetDvwyNfA2FbBz2du/ + LTnI1QP3puzMA+DjQ/DWTjkEkI7gUY8ZXw/Bs/YDXFzjd8ADdiMmttgR79rgTW2yaeuVrd5+2R2dUALY + HRuR/VEAkbX5IIAdEMBev11jf87UO54Y1mXAZ9PDOlOPxLDTZ9P4fwOefYXbdGvKZDwjRRaqy8WN97MW + gCqAU3RVWsNDz3PLMmL5fXzeIUiAcf+eF4zMA+hxoB9gtGjZ7jricy7/rXd1qBH4mxYTzIyQgPF/q8b9 + TAhu2o29BCwg4u6/bS5TQuLrOXMCTqPicLuv17sZqE+JZ9sJouhD2NFjB4mwX0G7zDXWyzRIYAxhQB9U + gG+pz5bC2D1euw07WPrLbD+8OfsGMgdghwLoBTAJSJIA434+r9dzjOtMCLKE2A7isIEMTFAHnUkx0uKd + RdASR/ByC/BTqIBweHMQBD63i4VEkPpteJ2Nrb5KskAgRh0AVxO6OdcgEyFAerwuJXLTUT3CCG4wqgp+ + KGUBd6QU9ucYxz2pgH358uWpAqBdlvznSeDw+OjcDsCzFt8XWn1f6gDk2wx0FgbsXhj57csN+KT/G+XC + l3ICp6sBPvn/lroBnwLwnCoC90Xgel+78xa5/676gD9NOPCuIqHL+YHzQ0q0JmBzTbbHBsTt6NI1+N0e + i+z12gH6Hh2VzYm5HhsA0G2VbTO8u8kKyW4FeOBZTdwvDzk8Oi77M1OQ5KNKAO6uZtmxd8qer/gHnvmI + 6/Pw/M+moAAmXEoAHLLptoIwILvX2hCTw2aK8mS6tFAVAEmB5bVcAaA838D3YwKQo7pYA8DS331VEv06 + vNNHBIz7GQK47Rzv1WWU7gLUfN+aqUtzAhtWs8b6m1aTJgJ9z7NqkOXCLB5y654AJ4Bt8y4nGjsAucSo + BUC47vEmGkkKW/jcdbtNlkAySx3tMtPUIKNVZdJXmAfQQmIzDxAfI13wyKbEaLGQCNKS1JgPGESo4Mji + ZOBMLf7h3AGntxtQD5f9WBCEEKBHCQAGgrDj9awq5JyBVm4X5vxAeO9mxPyU/y2U/iQAhhEgITNChp6c + FO3L2FecBSJIPhtq4u2v2In4nyRC8NeFB0pl0EMpfnxHih/dlrx71/7sBNDndJYTQAS9jwROnoMAnp28 + VQ2cEsDxmRo42w3oJQJfNeA5AvAN/zgrCd57Y1XA1xh099w24bflB86HBkYIsHtxWKjHc44A3kzg7fgS + eb9kNeBXAf6NFYLtP7kR+Bfaj12oEDQGlextrcvmSC/iYcrtSlmF9N5AjOxmhRx33tHg2bZMAEuXSTZg + a50wEgCIYBMkcTAzKScLMxrXewAsru3vIV4m0AnOo9Fhr7mUADhFl7HzDhuM4nPWIJ03utpkpcloLcbq + wA1m55sBfsTpjNE3IM/n4FUpu/dxLwKSgN8dZJjhgqLo1YTgHgiBAKX8N4DNAZxmb/1/twHUbqOUl96f + BMB6ACYK13jdYZM1vI97BXQpkBWADAEQ9xuS36ErBDSqBi4BsuhoA8b7L0FpLLS1ylR9nYxUliPWzwHY + 2Y03WrriYQnc3x+rcT0Lf3q4DMgMPyS/IyNNM/2MxW1sfgo53g/P79By4HglACYBrUoIUA94fQeIpCXe + B/5wBX5DZLCSAEmhKTpcTCAAK9SGGe9jzE8lQfBrpyE2adWagnivmgAB6GoCVEBkoJQF3peChzclG+BP + v/XVnycEsF3f29tXj06wkwCev4S9eH6qBnxEoAVB3h2APgJ4296AgzeKgg4uTP3du0QCZ5WCe+f6BFxu + Fb57LizYPW0W6vGZr5DIc7GluHYWPkcGpzkCr3rwJRD/JEm/P68CeGeI8JYy4T3dMbglntkpWbE0ymJT + CWR4nsxV5mlN/zpi9A3I6I0OxNJd8JYmkgDAQjKwWAHAIXm+NA8PPyoHzJADaLsAhsdh1SQgVcA+YucD + Fu5QptP74xqLgdzdkN5d3BDUDAXQhM9qkeniAt2iS0JYRcy+0upN1HW0GEVAACGlv8bkBOSgQSasLWAi + kJ7ZALhFjytdHbonYEMrA3u0wQcBy2QfNw5xByHLgwl03x6BNXyvVYQPJIctVgh6E4IebhIaNEqEmSjk + /gASDJcZdZUBf5eF9laZb26SyepqcZUUw6tniEmz/4naWJWyX3MBaYkq7ZkIJAEMFObo8h9XAPpyIc9Z + FpyRpM1MeyH3SRRsjspVAktSnJYOU963wsu3MQFIzx8Xjjg9RGoh/5s5k8FLAJ3JCRoGdCOcsIAEmA+w + ewmARUBUBdxs1JEYA0KJkWYQQENsiNSEB0gZFEDu/euScuNzSfz6kz87AUxOTv5XE7OzCqCTc2AnEbwR + FpAATo7VDo8vrhC8URXoKwo6OFcifK4zkG8A6HnbuVwqfG7T0OXpwD5SuFw+fGHlwHM2WMSnDIzwwG2Y + Z/uNcWOGeYnkV5DBuxSA5y2JwLfVDpzfXfjOPQLcNLSxIRuI5Ze7EH+3lmtFn9vcAkCbxAMwHSDmPRgc + 0JHZR5MTcjwJz89mIi7IesTCu1YAH/G3Bx50B96TewB2uROQmXPY/qBDk3/Mnnt6beqBV5n1Z1Ye4N4E + 6JcaamShrlrW2psRAtRrjf0KwL8KAlrpbDekuDblANgdhvz20JwGIFnGS7Aznl9VOd6mJKDen+CnV2d+ + QCW/4ekZFpAAFPx2i6wC1KtMDiJE0G3Cg87TDUj6+TAND5x23UtAsmAREKsNl1tbZL6xQSYqK2UEBDAA + BdCTmQ6gZ8tQUYEqgt6cTAW+rv2z7yLnBBTn63Ig43uSAFcHmCBk0q9XVwWyQQIZuozYmcBNRHEAfpSO + HKPUb02IkjaEFvVRoZDuQdKIYzOea42LUmCbUuLh+UEquek4j9M2Y735GdpyneEEk4Cd3FqcFCUN9P5R + T6U86IEUP70nmXevSgLAH/X5+392AgB4/70pEMAGvM3y6op64efPnysZENgXiADXGRrQlAjeliA87Qbs + LQ46RwTnqwNPcwL753MCF4G/s7v71pbhZ0RwMSl4QQ3s7pzWD7yRH/CcKzA6rRdwv3Xz0C/fSXixndjp + 8FLPeRK42HH4/HbinUvtyM/vGLzYUMToHbCzviE7s9PiHoLHtCMUsHZoRd/x0KA8n5qUF7Nz8mJuHjH9 + rBwPj8i+A97YZpcdq1V2IL89AJWHW3YJHm6wYU8AbgFmpR2PvVZNvBE8lPfsqccddVr118reehV4XCvz + 9dWI+xGakAzgWdctZm3MuUkgA3yM2Qlat7fkl8CkbFfJz05FsKXODhAatwPjvXjfBhUC38f79NqN1l49 + 3dppiP0GV5kfsBmEsE4C4J4Ayv+hgTMCgOrg+Qaeo4Lg67RPYQv+HS1NSgDTtbUyVVMlrtICAD0L4M+X + 4ZIiNU4T6i/k0JBcSH+2C89XGyjIU2JgLsBBNQByoNfv0YRhGkCfiHAiSboSOR8gGh4fHl4tCl6b4A+X + mrCnCAFClQBIBEwEtsaxt2CCEoCduQMmFLOZcMRn4fMYHjBHQAJpTgiXWoQQ1VARJYH3pODJbcl5cENi + v/pYQj75yZ+vDqC5tU08CAOePX+h4OMeAIKDXv/5c0MVkBROVwq8OQI1ryLw1QycrRRcIgCvXVgp2D/r + GHSaHLy0VHiaH9h7c3jIhT0ElNUIDdR77+y8e9nwXGmx1hCcTh/yJRR/9ZLfu+1iV6HzoL/ccvx0G/A5 + wPtWBN4kgMvNRhEacGrQyDA8pVklO5th7PY5QQgABQeymLtlp8uiR7elW7YBUjcr6phcM0NCc+usxtpG + F6ANxPdrFsNrLgPYTPJpR15vR525hmptrc3e/FNsOFpVDkkNUmhuVHm92MXGHt2yjPsvIjRZ5NId5b42 + DDXidwKe2fhF2JIawgCW6trZS9CCc4usscsvqwRh67A19hKg9O82cgW85xq/t91QDtsEPUIZbgDaZPwP + 42eu8PUMNfBvXYSSYROS2Sbj+07W18hIeQlIoEgGQADsbDxcClUAsBP8gwh3BnGu48VADP28np8DAkiH + 589Qj88EojWVsxnYFhyyH+EEPXoD24NFhUgjAN4ABVAPqV8TGixlbA+O6w2RbBQaoKFAB/sKpiboyoHm + Aej1OZMB4LdnkwBwPTURsX+kNMSFSi3uXRHyRAqf3JXcR7ck9fZVifryY3n60Y/+fARgs9vjNtnR9xk8 + /MlzxP+vANJDYXEQl85OlAieKxGQFHxE8OzF8wsJw7epgV9WJ/CG7Z8vFDI6B58lBs8nC3cu5AcIfF9f + gZ1zy4Vvgv/S3oJTwLrfyBt4LuUH/iTA396+pCDOgf/yFKLL8f+7moZcJoDzpiPK2FF4YR5k4FLPScm7 + 2gLp3tIqa81tst7UChkPqd5mFNWscO2+pVFjeF/TzEU25WRdf3MtAF6lRsDzOMkpO+ypB8CMA/S0scoy + GS4v1tFb47WVMk1gQWbPgghmQR7TUAszrSQGyG7YDLzvLD5rFp87S0+M7zLHzkAggQWEA/MgAvYzXDQD + sFABK1AsKzabLHNPP5uHAMTMFxDMTBquan7AqpOQNkkWDC8IfpAHFYYShtUAP3sOkJD4+TNewhqvrZaR + aoQC+LeMVnFiUJl6eQcrBIvyIP3zlRgGSQAghsHiQshyhAysHYAK6OaeAHht1hBYYB2Q/vT4bZyaBODX + RAQZ3hpevyaMTUGDpCL4MaT7Y6kKC1Qv3soyYcj7rtQEvVd3ZrIaCcCeD4PSsOB6WzLuDQKoA/ir8L5y + EEDe4zuSdf+mxF+7IhFffiJPP3nvz0cAPT09H69xDDYBfEL5/0KNiuAYgN90u2UTzx97lYCPCJgofO4N + DXzX31gxuJAcPNcz8NxKwWm14LnOwT4VcH6Z8KIa2DlrK7ZzvobAc27JcOdcLYHv8e7ZdY939Nj2xXDA + 4x1L5sshnBHB9jvA/47lQu8OQZ8auEwCl4eTngLeO8rcd02bip6C/hz4T5uKbnptQzwcKTY9JWvDLlnp + dcgSgdXWJgvNTTo9h734uG7PpTtK+mmO26oolfGKEhktgwzW2Xh5OrBjtLxEgT8ET0mv2F+Y5/WS+QoW + B0HB7Di77XL+HoDEYaUu3NcFkhjA46HyMgNguM9wVYWMAXyTjXUyxf6AIIsZEoQSR7vMISyY9xLCPMEL + sC8C0Iu4puqBhULeUECVBZN9DC2YKGTRj91mlBgz1AAJEPgL8P6zIKTJRigYqJmpxnoZq6uRUXwPfp9h + fk/8u52I9Z2s/EMIoCSAf+8QnnNxC3ERnkdIQI9sAwGYCdYseP7kOGPEOMDfwERfTKjURAWr1cWGSVUE + JXswwB8oxQEPpAhWARDXgiTa2FA0PVHM8P7dJBUcrSz4yUsXaz4UQEE2CCJJWhD/N8VHgABCpCIsQIqD + H0rmg1uSdOuqxFz9VMKufPjnJ4Dx8fH/enp+4ZQAfHZGBC/h6V9oGe7m1paC+ZkC/plWChL8vjzBZSK4 + oAbOhQUHBxeXCy/a3pvVg28JB942U/BikvDciPHLXYd3vMuBp2YAfMcHdt9U4jeShL88PPhl24jfRgBq + vvbhvrZj3vMLDUUB9ndNIjo/iEQ7D0MV7PE6ZxFwMhEHlC4syObUtKwOj8hSX5/MATwzpg6ZhBIYa6gH + IGrEBa/YX1YsvcUAQFmhOAkMANsJ8Dogk+0lxWIvLhFLQZGYS0ulu7JCTHi+s7hI2osKpAvesruyXM3G + 53C9q7BAOvJypAtk0ZmbJabcbOkGmHoYb1eDKCjH62vFRa+M4zgAOgmloAZvPQXFMNfWrApjgfsFTAYR + rHhJQUMDdiOGAljr5goDCABhBXMMnJk43873N8o01M1UQw3+rbVKAEP4jv34vv34t/WDAPlv5b+7pzBH + j318DsTVB/A78nAd/wZzWrJ0wmObs1OkMw3SHF6c3rmBHhoEUA3PX8kW4Yjzq2LwGFYJz10S/ATgfyiF + Tx9JBUKA+oRIaUmJlc6MJDFlp0kXPL2ZqiIHBvBbAX4rvocZiqM1JU4/owbhQzkIIC/gnqTcuSrxN65I + 1NWPlQCCPnv/z0cAANLfGJuYgmd+OwGcJ4IXL1/DM+/LOjwOvfWzc4D3EQGXEU+eP/sThQWnS4bnCMCX + G7igDrwhweUx4mdlxb4uQ2edhg1i2PaGCed7D3jOwoRz4N09F0Zsn1tBOPP+bykmOrXLOxCNcWTq/bfc + b+QCznv/He/1ba/3f1tH4fOy/23nl2cUnrYd5/nqxgXjTEIdTOodWb4xtyRrs/OyPDkrS+PTsjA6LvOj + ozILspgeGoK5ZKJ/QMb7BmTMOSCjjn4ZtjvVhmwOGejukX6TVfottG7pM3VLfxeO7Wbp6+gSR1u79LS2 + iA0qxN7YKLa6eumuqZXu6irps3Z/Pdhj//GA3fZB8r27UhwRJu0ZiK+ZpYfS6AWxDIJkXJUc+lkNENcZ + IUcTOwyzzXij9hkkIfDIpORSZ7uez4M4+NpJvGcCoB/3ev1BkFkfVwLg3ftxPgAjGfRC6jtATH0ghV48 + doDA+B3sICwzYv6u9GTpQEzOY2dSgrQkxEljYhwAHSO1cZFSFR0m5fD6pfT6AH9VXJSUR4bBa8NzQ/6X + hgTqa+pJHAgXWlPipQNeviMjGaTCfzOkP2cRlORJd2G2dIAYWpLwGQnRUoP3lYX+/9s78xi5quyMK4oS + JZlkoiSQTEaZjDKTSEgRWaSEyYREyWhQlGgySYYokQgoIZPJDBGCAQUGgx0MBoNXbGPjDW8Y23in7bax + sdtde3dV7+59q669Xr1XVb0YAwIhbs6579737rv1qtzGNsb4/PGpqqvbZbu7z+9+59xzz32GbQLrv/7x + h9i6xx5kqx55gC1/6Idsyf98/8pPA4ZCoU0Gv8xzlksHge4IsE5gQfBmcX4eBMrMrJ0WYJDj8znuBi54 + +gj0swTlql0YlNuGanHQsryOwFSnCyvbiIZvWiDOGXAAFEUaUPQOHxG7BAZvLCp4rL7h1APkDcXSIfgX + /NRRYt4LSfI19t/jBMRq77mSTJsXWK8G4Bf8HicgQIAAKKVsSQAUk2kBAfsRQZAdm2Dp0QmWHBplU6DJ + gSE20T/Axvr6uUa6e9gQAGGgA9XFzic6WW97gvUAFLqi7aw70sY6Q1HWEYpwKHQGoywBYEi0hlgiEAJI + BAUgQC2t/LX1y5ezjz/++Kf0YTQ4gxK+b1/JZbO/MzYy8sd9XV13JULB/4i3nH3qwIplLLhtq23VecEO + LxXF24ZgdT+I15DtZd37X2ddsKJ3vI4Xn2Khbzes5LsAJmDzwdqHId/HoI7B53Clj/LP7bIBAJ9HCETg + eRDeP7gDZa/GZyEFQL0NegunCq1fB8H8Eju6bjU7unY1O7h6OXtjxQtcBwEIh9ashIAHF/DiEnAB/8f2 + cQBAqgBB3QQQaFq7gjXjzcKb1oITgPQCVv4gBH7w9e3glDazk+A2mrCwCAA4AK5iJziIVxY86jiA5ZAC + PAcO4OF77r5yALz33ns/HwgEFmTAQlbR2s/OeSBQzxnMXXiHbwemszm+lYjP1VbiOVEfaJgaKINFnS1D + q36RUD1g5Nw1cMk+AjlpyD1dWFs4VJqFjMZFQL/X3SPGRe9Zg3xjqQXBmmGhAgA84JX8v1E6oN9N6DiB + lNcNIAgkDPIT0hEkWWZ0kqVHxsAVjLHk4AgXwgA13jfARsAhoAY7e9hAZzfr51DoZP2JDq4eAENvG8Ah + 1s56BBzkY3e4DVxEOzt+8CD+/G+53N/Tjz766Kerlcotk2Njtw/09f5lT7z97ztCwX/vaDn7v68+9QQ7 + uBRybFiBUUdh9W1evYo1r4I8+iVYcSHwTm1YBzZ+PQu8uoUFACSBHSBITxAIrTtetZ8DKAIQ/PhaAF5r + xa5B+PqzWzaxM+BOTsGfPw6rfxO855E1qzgAjqxZwQ6seoGv8IcABAiGY+vXsmPwdx5cvoS9/uxTbDe4 + gAMrwBmsXAbpwQvsKPyZExvXAgDABUD+f27XFtBW1rJzC3sLXEEzAOBNAMARAMVegMjOZxaxbYsWgAN4 + BFb+H/Hcf3hw4A6E5tW85POWUDD4SHs8zoO9wt2A7QBUZ8BdweycAoKL/PUs/NKm8Ggx1gmU3QEJAbW7 + UAWBukugThrmxUKr7IWAM2NAyqeb0NTbi2vrBXImYUlJHZy0QIWDEvDoFtzX8zXbhuqsAekO/LYCG0LA + Z4io5z4B5WZiv0e1QFjKYP9ArSswplAIAHACAgQcAhwEUyzLQTABGucwmBoZZUnQxBDAYGCYa/z8ELgD + SBl6BwEIvWykt48N9fayQXALqIHubhsQCVQXB8VwVy+AIMbGx8b+4GrPsMRA+OCDD34WHcS7Fy9+IZWc + vK0nEf9OvPXcgxueeIy99twzYNdXs+MQnM0QmCcgQE9t3gAWfCOs8JtZ63YIwJ2Yf+/gKQiu/AiFs9u2 + gEXfBHZ9Aw/8oxjgYO+bIIDxEQP58EvL2GEI1CNg7Zsgb2+GNOE46Ais9PuXPwvBv5DtfeFZyOOXsH0Q + zJgqIDjegtz/DNj/U1hU3I6Pr7DjuPKvX8OOb3yZv9fB1S+y3c8vZq8uXsg2LVzAVj78IFvwn/fhQvnr + 8v9+xdd8qR+jLYNV+dbz58//xaFDhyAgqxDsF8AZeB1ATZ1ApAboCnLwi5tMpSHATFEstIN9VmwpShhI + EFTEUBF5uKisFQzNsveYsdNWbNbKBoDhpgamu32opgASAI4TcF5HABQUh6C2FOd9ewXcGoHPNOJ5OAC1 + FmDM454B3io8j5qAc/uQkxpk+dZhCaEAEOAOANxAHkEgripHZcEVZCaSXOnxSa4UpAlTAARUEqAwOTwC + gnQBoTBoawxSB1vDbLR/iI32YRohdH6QjZ7vZ23R6L2f9lV26Bzee/fdX5icmPj9RCh0/95lS2F1XsNX + 2ROg05tEHr4b0oM9uwAGsPKDQzi3dQtf9d+CfL0ZVuMT6wEc8PwkBCjm5k1rMJ9fzd+jGWByAtQMKzoC + 4Biu4PA5LAbuhcDfv3wpD/z9K58Ht/AiAGAVfM06dhJW+xOb17HmjWsg8Ffz13maAH8Hrv6vLV3MXln4 + OFv75GPs6Qd+wL79jT9hCDn1/3fNvnEIg3w+/1VMD0ZgBZiBwK3OaiCYqy0WShUNk03hXnXR4LUBGfDO + uYM66YFTLKy6xULLM3HYqrl6zJZVPx0wvWPIvL0EAAynn6Dk1AEMzQno9t8w5nlWoCi2BOu0BHueSynp + gOci0rzrAvzkBwG/GgGmBPatxPJ2YnABUykFAlMOCOznEghJBwjpcRsK+Dw1ZoNBCgExMTQMgBh1Xlu7 + YmVN3v9pC/9+o1j8rXgg8MPmV15msb27ee4fgpX/HO5WbNsMthycwcYN7BToDOT/LVwbIVffxIt2pzeu + A5v+Mqzg8DV8Bbd1ElZ0DH6EgFzJj6xdxfZB8O+BtGTX80+zfVjRhxQAIYA6AgF/CJzCQXAS+PwIOIP9 + 4DJeX7aEbX9uEYufe/vJeChwfyaT/jq6HL//k0O5a/mNQ3sVb4/fjfcE8LMAIgWoarsFdnpwwZMeFMEJ + oCPIwArEW4iVgJeNRSoI1NkDjhPQ24p1GJQtAQCxc1C2vHcR+MDAAQEEvQ2AktNYZBcQi540wA8I8uyA + CwefukHeCwBn5a8zO8CoOzrMbSU2uYq2GoBApgRGBj92n6u3E0sI5NENTKW5JAhcGNjKgaQ7kEDITExy + GKAQBCjpFqYADtnkFDvV1IQ/5y9ez+DX9S64gsHz5+9sP3f2qRYAQOLQfpY4/AZrx/4APDW4/w0W3beH + tb62nbVAOnAG0oSzoBZ8hHShBYBxZvtWrrchVXgLIIAu4NiGtewoBP5hEBYD9yx7nm0DC78D8vidYOd3 + L3uW7VgCOf3ip9jO5xazPegSVi9ju1cuhaBfzODf83hHLPKvE+Njt2N97lLQ/NQJOj4+fnswGFyRyWZ5 + j4C++k/P+ruCcmWaTcIvF8IAA3tG7BzIOoE+laiqnDqUMPDcSKQBQO00VGcQqIeP/GBQcyxZwIEPGs27 + tl+HgJseyOahglYzMJyBovzzBbv5yBkYqs0UVF+vOz/Q73UIcgtAYGkwkB83dAIcBDkFBF5xGICyAgZ5 + DQgqDDgQQCmRNuDH+Of7Eh1sKpm87bMU/H43YA0PDt7R3tr60H4I3ODunSwMziC4axsE/GYAAAT9DniE + wMd+BgRCC+4QgPDjk5tf4Xn7UVj1D6xaxvYsf55rO27fLVrA1j32CKzmZx9vb235cSISuq+zLXp3V1vs + e31dnXcNnu+7M5vJfK1UMr6M/47LdUnX7ZuGh4mwkzAWjfH0ADsHp4UD4A1EPu4AX8eCYhp+8abAcvK7 + B2ZdR1B/50DrLhSpQUWBgT6JWB1Iqg4j8e0jMO0jx/rdBSXHHRS1NEFAwDl2bNQEv15ItL/ecIJdlQoB + FQCWeI6P/Lnf5xQYWHUcgesMpGp3C2xHYKsAKQI6ggJPFTIcArkUgkDKdQgZBIEKBa4kfH2GJcEF4NDZ + z3Lw+xUU4XfoN4b6+7/ZEQnf09567uH1C5/gW3wHwJ4fgNUaH3Gv/wDu90PA48oebzm7EFfvRCT8b92J + xHcQKFisw50LdND1LPyV6rp/wzD9GBgY+GY0Gtlp4nhqrS7g11iEqQE+ZuGXciKZ5JV3df6gPHegFwxr + R5O5KYJ66tCRVdtH4F5Y6gLA6S9wziEYvE+g5NkxcINavduwFgoi0P3Sh2LRFwCe4BfBPh/VuAGRGnAQ + 1KkV8MJgzu4c5FuN2EEIKkCKVgAnUMy4EMDgLwo3kEulhNAVpDzKgM3PCuHKn+Nfn2ahQOuTN1LwN9KH + H374MxjIUrhaS+Hnrte/6zP1TYLg+RKkB08MDAw6wa/3EaiFQgQBPubhlzmZmuLNRVVRKOSNRaJg6De0 + 1O8IsrOFaGlpgaXsIJj6fEL9JKKcWCRSCBHwvjJsMJRKhuMEuGsoGjUpg/P14vOmePQEP7wmX7fwuQj0 + MjxXH+cFBQECS9YKBBzUcwbysahCQMhODVx3kAc4oLgTmBIgEM85AMQjfk0R0sPd27fVVKxJn3MAqM1F + HWD9jjUd4wNEOQBE/4AKAPW8weyFd/jWIdYI8DQi1gAkCOqlB15H4DedqOI9fGSJy0vL7sWlzgWmHAJi + YKlpaE1FtacS3Y8Nz6PuEuTneBrhSS3cgFcDX38NZamBr31s1nMNBcMGQMF+7ldUlDMHuCMQ15UjCHgQ + Z2wguO4AIWCrIIAgIYDigEDHAO+TiETw53ArBehNCgC1aJhOpX4Pi4bZnFs0lEE/7VsnuACreZUlYUXB + WkFFHEBS6wR+6cH0dB0YCADYbkC5uEQ2GJmm9ySiZdbeaIypQUm4g5JZ3xF4oGBc8uvqBb6F7kMDQNlH + 8nU18Ou5BL+iIg9+sbXoOIKMcAWZnMcdYICrDiGvuAIp/POjQ8NscGDgzyg4CQAeYWElHAo9EAgGwRFg + 4Pq3GsvnCIlKdYZlIGeVE4tkqzEGej0Q6PMK9YKhDgCZFlj6PQbaASR9kCkPYP6aXStQ0wonvWgAC+dr + tWCXAFAhIJ9batCrX6uAwA8AeuFQpghGrlgDANsR2HJg4KQI+JgXEMg5LgGF75FPp1k0HP4+BSYBoG63 + IRYNh4aG/jQWje0uimEkM3LXACGgQYHvHAAweMFwclIUDGVjUdXTWCQB0bhgiLUBtz5gqfUBuXsg5xOY + 3nsN7MD1qSOUSjWvmaapOAbTuSVZvy3Z+XOlBg6gZH9eBrt8NDUI+MrPDQgQqG3I6kQid1S5GFeunE7k + AIBUASVdgvwzoUBgxfUsiBEAbjDBL/Vv4nXkPb19fBtRLxw6HzspwpxdMIT0AIeV4Eiy+XYY6tuIlja8 + 1CkcajcbmWW7LmD5bCuqwW7WdQiG79f6vYdc+ecr1S3or/ulEB5HoBYga+oDdnpQyiktyTnZjZh3ji/L + OQb4Hieb3sQj4b9EQUkA+ERFw87Ozr/D8wclq+ycSJSHkHQw8CPJ5QqkBlN8jiF2J/LGoun6jUXOzoEH + BgIA6sQijzMwvQ7BJ3jNkvqx4bkKXZ1l4HULJY8bUN/LDmK/wLb4YxkcCOpyIOEpJip1B3370QaAIe4o + KDqpgDq3UD3LwP8cAK67s4PhEV4KSALAFRcNs9ns1wKBwKLhkVFeK7DlhYAKBl4wTGOHYYpDYUYJer2f + wM8R4E5BRZ1kXFblBYBHTipQ8txz4J5U9AOGvwPAry1BgJtO0PoHvnytbNqajzvwqymYjSAgBqSUirW3 + GyEY1PML+B7pZJJ1d3X9DQUjAeCqCoL2l9vb2v/l9OnTfACJGvwSACoInIJhcoqvsvIk4rwaiypqjcDd + OeCpghr04tpzHQayiKgHdS0EStrnTV9nIR2AvtL7rf6X4wb8IKADgQc736r0tijrl5vge2EaEA4GH6ZA + JABc09bM0dHRPwoGQ2vw/AEWBKszXgDgI84vkC7BLhhO8evC/QqGNecOHBCUxc3H7n2HdnpgFw5xQpGl + FA7LdZ2BZc8ycJ67Qa4HvPdz7msWPoqAdwJfvL/tCrTP+QChERwagcC7PSkcgmF4gh8hFQ2FtlLRjwDw + qQmC9NdCodADkUiUW/8qv7Js1hG6ABSHwgwWDEs8NcjlaycW2SCYa7h7YHcYVhUQyMKhJVIESxlyailw + aJA6cJW8wa64CF01ANBekzIVYMzHGegpgt+jAwLD7VyU7x0JtH6iyT4kAsBVcQW9vb3fikaju/HUHtYJ + qiL4JQjc9GCOHxPGVuNUOs0DWx9dph5J9ksP9DHnNWPMtBFm6sUn6ARsyUC3LgGHxuLAMf3BoDsRyzQv + K2Wo14egtipX4L2HB/sZujIKQALAdS8aFgqF3w4Ggz/p7x9gZZwwxNMBFwR4DJm7Agh6XM2nsGA4NcWD + xL730K0T2FuIMj2Y9TQV+V2Eqt585Awt8dyC5KYK5bIbwOrjZQuD2gcKdYHh4xb8gKCv/O5z92ur5QrL + ZdIs3t7+zxR8BIDPlHDAQzwe/6fDR9/kl4CUq9NO8KsgqAIccNsQx5tjeoBdfvaQEjnDcLZOq7F3jqGU + 2nLsuRBVGVyiX5KqB+yloID2/lLAqPd5v+BvVDdQV3/19Qq4mJKBzT6ti673ZB/SPABwszZlyKEloWBw + 5fjEJAT9NHcEKgTwUdYNcIbhVDrF7FRi2rOF6HUEM87hJL/0QI45r5QrTm+Bs5vAAYFBiqu36bXzDhy8 + TUluQJue1/xAUi7XB4GfA3AC26zdXfCDA9p+1JEDb9AJP3IAN44gYH8lHA7/KByK8EtN8AIUVLkyrTiC + WV5MLJTsI8mZXJYPIJFzDH1HnM9M16YHomhYrShzDSEYbSAoDqGiB3nZN7AbBbcODr/3uHQq4aqRQ8CV + H61/RyyC26tfpt8rAsANJ1y1+np7/zocjmydSqVhVa56QFDmLmGaFxMNWBWT4AjcEef+BUM9PfAeRKp4 + bkKSKYL66PeankbojkB/bT41gHkXFxEilrdwiME/DUAbHxlh/f39d9LvEgHghlehUPhKKBj6cWdXF78J + uayAAMFgO4Np3nyEbgBbjbGibzsCfxC424hV91iyCHInVagDBG/g18JAXd3r1Qqc9MIHBLWvlee924D/ + 5nw2y2KRyP30u0MA+FwJaySJROIfDh08xG9A5q5AAEA+txuMpnl9AEFgiA5DGfDqlel6P4HfwJKqSBF0 + GOggqFTUoNdXf8vXCfjBwt81NH4f+V7478ETiKFA61Iq+hEAPtdFw7GxsT/EoSXDQ8N8aIgEgAoFrBfk + 8kWWwsEYWDAUAJB1Ah0EddMDHQCei1PLCgRqU4JKRQauNa90oVE9wXUOZU/jkupMmo4cYhcvXvxF+j0h + ANwUwuPJkXDkB5Ai8GPHJgS/WamFAW4z4jZiDpyDfRLRhoC9c3Ch4RxDv94CDgWtz8B1BRU+Vs1TSGwo + y7dm4H3dcuTdbrQElMosEW9j6XT6d+n3ggBw0+n999//uZ7unm+Hw+EtuJWIpwxNy5bqCPBjLBbiGYWK + KBjKoFfrBPW6DGuajHwhUK5xB7UwqNStE3hTh/rFRLny479lcnyU9fb2fIt+FwgAN73ETMOftMcTLI+d + cWUbAhwIZTlcpMLnF6YBBDhJaHZ22rkg1Q8E9R2BLBpKEFQb1AlEwdBnpffL7+dTF8C/O5/P4gm/h+hn + TwAgaUVD3ml4+DBLJlM8PSgBAKTs7UQIoEIeHEGGTw7G1EA6Ardg+E6dLsNpZwKyAwO+Ipc1MFSdVd8d + aOLnCCoNtxa9YLD434GHlKKh4Cace08/8xsQANf6jkCSXTQcGhy8A1zBsp6+PlYsWcwQEDDFDAGsCxSM + It85wGvC5ZQiGwSXUTD0NBr57xyocwz8ego805Ets27Fv1Kx2OmTzfj3/yr9nG9QAOgDOEmNdaXAxOPJ + sWjs3lOnz9j2H9ODSpXfPYBXq9t1gjI4ghzL5QrONelSWDAEZyEKhtqwEp+tRE+q4NQHLNsJVPQGozLv + DXDrCGXfGoB8v/6+HoYt1PR7QSkA6RN0Gvb39/95KBRcOzQ0wucammVv0RCf4yDTHKQIGHjeLcQ5AIF3 + 90B1BNPTtY6gKtOBS/QU1DYXuYCQRb9UcpIl4vF/pJ8lAeCmk3RNV+uQC26dhbBo2N7OJxJh4JecgqHd + YYhXoKHwyLBaI8CTiAgCdAVqeuDZPZiuem5OvpzWY9UNyOAvApDwhB/9LhAASFe5aNiR6PhuLNa2d3Ry + khl4nFbUCiwxcqxgGCyby/GBongkeUbZQpQg8PQT+NUKtB4CfeuwXt0A/wzWANoi4V1U9CMAkK5h0dCe + aRhY0tHZyfLFonNxqQQBthqnc1lWKBr8rgO7RuBuIXIQvIPXps3W7Sdwg9uddlzWmorc4LfhEDx3FmcN + fIl+TgQAUp3UQD6/GgVWvB4tEg7/94mTJ/idBkVTXEcmgIAnEe06QYEX/WRqgAVCBwTz7ClwW40rYhvR + Xfmn4flQfx8bHBz8Bv2sCQCkTwCHKwEC1ht6urvvCoWCG/r6z7MszuDHewGsilMwRDeAdQKs7vNW4zm5 + hTjHQaDXCfTUQAa7CwSZLkyzVCrJ2qLRe+lnSQAgXef0IJlM3hYKBh/FoSWZTA5cQJmVTPtAEvYV5A2D + OwJ0Cq4jmGXv4I5BHUfge95APC8CbEKBwAu0ZUwAIF3FFAF7Cq5kJwGnF8Xj8e+1xdreGBwZYYVSicOA + Fw/BEaBDyOPFHJZdMJxT+glkeqCeRXDcgAIEnC3YFovuoxN+BADSNVrREQIIhHcvXvzCJ3kPrMjj7cnB + QODF9kSCpfACTtlXAK6gWDJ40RBbjfnospkZp8NQFg25nPkE9qUo2AQUaG0Bl5H5Ov2sCACka+wOrsaN + OTiDLxqN3XfkzSY2MjEJrsDkdxxgL4FhWnw+AbYcY51AH1uGtQIsHuLreM1XLBJkCBb6+RAASDdYizK6 + gr6+vr8KhULru3t6WQYv6DQtcSLRshuLcjk+qKQkRoiXzBLLZrOsr7eXRULBTfl8/qv0vSUAkG5w2UXD + 0KOB1lY2kUyxomEyA1wBpgWDw0Oss6uThcNB1tx8DB83d3V2/i1ev07fOwIA6XOkSqVya1ssdk8sEtvV + Dav86Pg46+ruYc0nTrBQKLAm3t5+N6QCX6TvFQGA9DkW1hrwFF80HP6vYCDwTDAYeDrRkfgurfoEABKJ + RAAg3YjCuYT0fSARAAgCBAPStQEAjRAjkcgBkEgkAgDp83AugUQAIJFIBAASiUQAIJFIBAASiUQAIJFI + BAASiUQAIJFIBAASiUQAIJFIBAASiUQAIJFIBAASiUQAIJFIBAASiUQAIJFIBAASiUQAIJFIBAASiUQA + IJFIBAASiUQAIJFIBAASiUQAIJFIBAASiUQAIJFIBAASiUQAIJFIBAASiUQAIJFIBAASiUQAIJFIBAAS + iUQAIJFIBAASiUTfBBKJAEAikQgAJBKJAEAikQgAJBKJAEAikQgAJBKJAEAikQgAJBKJAEAikQgAJBKJ + AEAikQgAJBKJAEAikT7r+n/L5C5CQ2Ej+QAAAABJRU5ErkJggg== + + + \ No newline at end of file diff --git a/AmtPtpControlPanel/Program.cs b/AmtPtpControlPanel/Program.cs new file mode 100644 index 0000000..24a9390 --- /dev/null +++ b/AmtPtpControlPanel/Program.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace AmtPtpControlPanel +{ + static class Program + { + /// + /// The main entry point for the application. + /// + [STAThread] + static void Main() + { + Application.EnableVisualStyles(); + Application.SetCompatibleTextRenderingDefault(false); + Application.Run(new Main()); + } + } +} diff --git a/AmtPtpControlPanel/Properties/AssemblyInfo.cs b/AmtPtpControlPanel/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..acd6192 --- /dev/null +++ b/AmtPtpControlPanel/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("AmtPtpControlPanel")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("AmtPtpControlPanel")] +[assembly: AssemblyCopyright("Copyright © 2024")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("ee1c7a99-9593-4d6a-889b-d75eea2de6ab")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/AmtPtpControlPanel/Properties/Resources.Designer.cs b/AmtPtpControlPanel/Properties/Resources.Designer.cs new file mode 100644 index 0000000..6d5de9a --- /dev/null +++ b/AmtPtpControlPanel/Properties/Resources.Designer.cs @@ -0,0 +1,73 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace AmtPtpControlPanel.Properties { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("AmtPtpControlPanel.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized resource of type System.Drawing.Icon similar to (Icon). + /// + internal static System.Drawing.Icon Icon1 { + get { + object obj = ResourceManager.GetObject("Icon1", resourceCulture); + return ((System.Drawing.Icon)(obj)); + } + } + } +} diff --git a/AmtPtpControlPanel/Properties/Resources.resx b/AmtPtpControlPanel/Properties/Resources.resx new file mode 100644 index 0000000..fddd1ea --- /dev/null +++ b/AmtPtpControlPanel/Properties/Resources.resx @@ -0,0 +1,124 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + ..\Resources\Icon1.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + \ No newline at end of file diff --git a/AmtPtpControlPanel/Properties/Settings.Designer.cs b/AmtPtpControlPanel/Properties/Settings.Designer.cs new file mode 100644 index 0000000..9c994a3 --- /dev/null +++ b/AmtPtpControlPanel/Properties/Settings.Designer.cs @@ -0,0 +1,29 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + + +namespace AmtPtpControlPanel.Properties +{ + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase + { + + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); + + public static Settings Default + { + get + { + return defaultInstance; + } + } + } +} diff --git a/AmtPtpControlPanel/Properties/Settings.settings b/AmtPtpControlPanel/Properties/Settings.settings new file mode 100644 index 0000000..abf36c5 --- /dev/null +++ b/AmtPtpControlPanel/Properties/Settings.settings @@ -0,0 +1,7 @@ + + + + + + + diff --git a/AmtPtpControlPanel/Resources/Icon1.ico b/AmtPtpControlPanel/Resources/Icon1.ico new file mode 100644 index 0000000000000000000000000000000000000000..02db645eef535a4596dbe52e3be65b405a9df706 GIT binary patch literal 52117 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 literal 0 HcmV?d00001 diff --git a/AmtPtpControlPanel/app.manifest b/AmtPtpControlPanel/app.manifest new file mode 100644 index 0000000..b2fa678 --- /dev/null +++ b/AmtPtpControlPanel/app.manifest @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + true + true + + + + + + + + diff --git a/AmtPtpDeviceUsbUm/AmtPtpDevice.wprp b/AmtPtpDeviceUsbUm/AmtPtpDevice.wprp new file mode 100644 index 0000000..3bc7788 --- /dev/null +++ b/AmtPtpDeviceUsbUm/AmtPtpDevice.wprp @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/AmtPtpDeviceUsbUm/Device.c b/AmtPtpDeviceUsbUm/Device.c new file mode 100644 index 0000000..7db588b --- /dev/null +++ b/AmtPtpDeviceUsbUm/Device.c @@ -0,0 +1,1017 @@ +// Device.c: Device handling events for driver. + +#include +#include "device.tmh" + +_IRQL_requires_(PASSIVE_LEVEL) +static const struct BCM5974_CONFIG* +AmtPtpGetDeviceConfig( + _In_ USB_DEVICE_DESCRIPTOR deviceInfo +) +{ + USHORT id = deviceInfo.idProduct; + const struct BCM5974_CONFIG *cfg; + + for (cfg = Bcm5974ConfigTable; cfg->ansi; ++cfg) { + if (cfg->ansi == id || cfg->iso == id || cfg->jis == id) { + return cfg; + } + } + + return NULL; +} + +_IRQL_requires_(PASSIVE_LEVEL) +NTSTATUS +AmtPtpCreateDevice( + _In_ WDFDRIVER Driver, + _Inout_ PWDFDEVICE_INIT DeviceInit +) +{ + WDF_PNPPOWER_EVENT_CALLBACKS pnpPowerCallbacks; + WDF_DEVICE_PNP_CAPABILITIES pnpCaps; + WDF_OBJECT_ATTRIBUTES deviceAttributes; + PDEVICE_CONTEXT deviceContext; + WDFDEVICE device; + NTSTATUS status; + + UNREFERENCED_PARAMETER(Driver); + PAGED_CODE(); + + TraceEvents( + TRACE_LEVEL_INFORMATION, + TRACE_DRIVER, + "%!FUNC! Entry" + ); + + // Initialize Power Callback + WDF_PNPPOWER_EVENT_CALLBACKS_INIT(&pnpPowerCallbacks); + + // Initialize PNP power event callbacks + pnpPowerCallbacks.EvtDevicePrepareHardware = AmtPtpEvtDevicePrepareHardware; + pnpPowerCallbacks.EvtDeviceD0Entry = AmtPtpEvtDeviceD0Entry; + pnpPowerCallbacks.EvtDeviceD0Exit = AmtPtpEvtDeviceD0Exit; + WdfDeviceInitSetPnpPowerEventCallbacks(DeviceInit, &pnpPowerCallbacks); + + // Create WDF device object + WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&deviceAttributes, DEVICE_CONTEXT); + + status = WdfDeviceCreate( + &DeviceInit, + &deviceAttributes, + &device + ); + + if (!NT_SUCCESS(status)) { + TraceEvents(TRACE_LEVEL_ERROR, TRACE_DRIVER, + "%!FUNC! WdfDeviceCreate failed with Status code %!STATUS!", status); + return status; + } + + // + // Get a pointer to the device context structure that we just associated + // with the device object. We define this structure in the device.h + // header file. DeviceGetContext is an inline function generated by + // using the WDF_DECLARE_CONTEXT_TYPE_WITH_NAME macro in device.h. + // This function will do the type checking and return the device context. + // If you pass a wrong object handle it will return NULL and assert if + // run under framework verifier mode. + // + deviceContext = DeviceGetContext(device); + + // + // Tell the framework to set the SurpriseRemovalOK in the DeviceCaps so + // that you don't get the popup in usermode + // when you surprise remove the device. + // + WDF_DEVICE_PNP_CAPABILITIES_INIT(&pnpCaps); + pnpCaps.SurpriseRemovalOK = WdfTrue; + WdfDeviceSetPnpCapabilities( + device, + &pnpCaps + ); + + // + // Create a device interface so that applications can find and talk + // to us. + // + status = WdfDeviceCreateDeviceInterface( + device, + &GUID_DEVINTERFACE_AmtPtpDevice, + NULL // ReferenceString + ); + + if (NT_SUCCESS(status)) { + // + // Initialize the I/O Package and any Queues + // + status = AmtPtpDeviceQueueInitialize(device); + } + + TraceEvents( + TRACE_LEVEL_INFORMATION, + TRACE_DRIVER, + "%!FUNC! Exit" + ); + + return status; +} + +NTSTATUS +AmtPtpEvtDevicePrepareHardware( + _In_ WDFDEVICE Device, + _In_ WDFCMRESLIST ResourceList, + _In_ WDFCMRESLIST ResourceListTranslated +) +{ + + NTSTATUS status; + PDEVICE_CONTEXT pDeviceContext; + ULONG waitWakeEnable; + WDF_USB_DEVICE_INFORMATION deviceInfo; + + waitWakeEnable = FALSE; + + UNREFERENCED_PARAMETER(ResourceList); + UNREFERENCED_PARAMETER(ResourceListTranslated); + PAGED_CODE(); + + TraceEvents( + TRACE_LEVEL_INFORMATION, + TRACE_DRIVER, + "%!FUNC! Entry" + ); + + status = STATUS_SUCCESS; + pDeviceContext = DeviceGetContext(Device); + + if (pDeviceContext->UsbDevice == NULL) { + status = WdfUsbTargetDeviceCreate(Device, + WDF_NO_OBJECT_ATTRIBUTES, + &pDeviceContext->UsbDevice + ); + + if (!NT_SUCCESS(status)) { + TraceEvents( + TRACE_LEVEL_ERROR, + TRACE_DRIVER, + "%!FUNC! WdfUsbTargetDeviceCreate failed with Status code %!STATUS!", + status + ); + return status; + } + } + + // Retrieve device information + WdfUsbTargetDeviceGetDeviceDescriptor( + pDeviceContext->UsbDevice, + &pDeviceContext->DeviceDescriptor + ); + + if (NT_SUCCESS(status)) { + // Get correct configuration from conf store + pDeviceContext->DeviceInfo = AmtPtpGetDeviceConfig(pDeviceContext->DeviceDescriptor); + if (pDeviceContext->DeviceInfo == NULL) { + status = STATUS_INVALID_DEVICE_STATE; + TraceEvents( + TRACE_LEVEL_ERROR, + TRACE_DEVICE, + "%!FUNC! failed because device is not found in registry." + ); + TraceLoggingWrite( + g_hAmtPtpDeviceTraceProvider, + EVENT_DEVICE_IDENTIFICATION, + TraceLoggingString("AmtPtpEvtDevicePrepareHardware", "Routine"), + TraceLoggingUInt16(pDeviceContext->DeviceDescriptor.idProduct, "idProduct"), + TraceLoggingUInt16(pDeviceContext->DeviceDescriptor.idVendor, "idVendor"), + TraceLoggingString(EVENT_DEVICE_ID_SUBTYPE_NOTFOUND, EVENT_DRIVER_FUNC_SUBTYPE) + ); + return status; + } + } + + // + // Retrieve USBD version information, port driver capabilites and device + // capabilites such as speed, power, etc. + // + WDF_USB_DEVICE_INFORMATION_INIT(&deviceInfo); + status = WdfUsbTargetDeviceRetrieveInformation( + pDeviceContext->UsbDevice, + &deviceInfo + ); + + if (NT_SUCCESS(status)) { + TraceEvents( + TRACE_LEVEL_INFORMATION, + TRACE_DEVICE, + "%!FUNC! IsDeviceHighSpeed: %s", + (deviceInfo.Traits & WDF_USB_DEVICE_TRAIT_AT_HIGH_SPEED) ? "TRUE" : "FALSE"); + + TraceEvents( + TRACE_LEVEL_INFORMATION, + TRACE_DEVICE, + "%!FUNC! IsDeviceSelfPowered: %s", + (deviceInfo.Traits & WDF_USB_DEVICE_TRAIT_SELF_POWERED) ? "TRUE" : "FALSE"); + + waitWakeEnable = deviceInfo.Traits & + WDF_USB_DEVICE_TRAIT_REMOTE_WAKE_CAPABLE; + + TraceEvents( + TRACE_LEVEL_INFORMATION, + TRACE_DEVICE, + "%!FUNC! IsDeviceRemoteWakeable: %s", + waitWakeEnable ? "TRUE" : "FALSE"); + + // + // Save these for use later. + // + pDeviceContext->UsbDeviceTraits = deviceInfo.Traits; + } else { + pDeviceContext->UsbDeviceTraits = 0; + } + + // Select interface to use + status = SelectInterruptInterface(Device); + if (!NT_SUCCESS(status)) { + TraceEvents(TRACE_LEVEL_ERROR, TRACE_DEVICE, "%!FUNC! SelectInterruptInterface failed with %!STATUS!", status); + return status; + } + + // Set up interrupt + status = AmtPtpConfigContReaderForInterruptEndPoint(pDeviceContext); + if (!NT_SUCCESS(status)) { + TraceEvents(TRACE_LEVEL_ERROR, TRACE_DEVICE, "%!FUNC! AmtPtpConfigContReaderForInterruptEndPoint failed with %!STATUS!", status); + return status; + } + + // Set default settings + pDeviceContext->IsButtonReportOn = TRUE; + pDeviceContext->IsSurfaceReportOn = TRUE; + + TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DRIVER, "%!FUNC! Exit"); + return status; +} + +_IRQL_requires_(PASSIVE_LEVEL) +NTSTATUS +AmtPtpGetWellspringMode( + _In_ PDEVICE_CONTEXT DeviceContext, + _Out_ BOOL* IsWellspringModeOn +) +{ + + NTSTATUS status; + WDF_USB_CONTROL_SETUP_PACKET setupPacket; + WDF_MEMORY_DESCRIPTOR memoryDescriptor; + ULONG cbTransferred; + WDFMEMORY bufHandle = NULL; + unsigned char* buffer; + + status = STATUS_SUCCESS; + + TraceEvents( + TRACE_LEVEL_INFORMATION, + TRACE_DRIVER, + "%!FUNC! Entry" + ); + + // Type 3 does not need a mode switch. + if (DeviceContext->DeviceInfo->tp_type == TYPE3) { + *IsWellspringModeOn = TRUE; + return STATUS_SUCCESS; + } + + status = WdfMemoryCreate( + WDF_NO_OBJECT_ATTRIBUTES, + PagedPool, + POOL_TAG_PTP_CONTROL, + DeviceContext->DeviceInfo->um_size, + &bufHandle, + &buffer + ); + + if (!NT_SUCCESS(status)) { + goto cleanup; + } + + RtlZeroMemory( + buffer, + DeviceContext->DeviceInfo->um_size + ); + + WDF_MEMORY_DESCRIPTOR_INIT_BUFFER( + &memoryDescriptor, + buffer, + sizeof(DeviceContext->DeviceInfo->um_size) + ); + + WDF_USB_CONTROL_SETUP_PACKET_INIT( + &setupPacket, + BmRequestDeviceToHost, + BmRequestToInterface, + BCM5974_WELLSPRING_MODE_READ_REQUEST_ID, + (USHORT)DeviceContext->DeviceInfo->um_req_val, + (USHORT)DeviceContext->DeviceInfo->um_req_idx + ); + + // Set stuffs right + setupPacket.Packet.bm.Request.Type = BmRequestClass; + + status = WdfUsbTargetDeviceSendControlTransferSynchronously( + DeviceContext->UsbDevice, + WDF_NO_HANDLE, + NULL, + &setupPacket, + &memoryDescriptor, + &cbTransferred + ); + + // Behavior mismatch: Actual device does not transfer bytes as expected (in length) + // So we do not check um_size as a temporary workaround. + if (!NT_SUCCESS(status)) { + TraceEvents( + TRACE_LEVEL_ERROR, + TRACE_DEVICE, + "%!FUNC! WdfUsbTargetDeviceSendControlTransferSynchronously (Read) failed with %!STATUS!, cbTransferred = %llu, um_size = %d", + status, + cbTransferred, + DeviceContext->DeviceInfo->um_size + ); + goto cleanup; + } + + // Check mode switch + unsigned char wellspringBit = buffer[DeviceContext->DeviceInfo->um_switch_idx]; + *IsWellspringModeOn = wellspringBit == DeviceContext->DeviceInfo->um_switch_on ? TRUE : FALSE; + +cleanup: + TraceEvents( + TRACE_LEVEL_INFORMATION, + TRACE_DRIVER, + "%!FUNC! Exit" + ); + + WdfObjectDelete(bufHandle); + return status; + +} + +_IRQL_requires_(PASSIVE_LEVEL) +ULONG ReadSettingValue( + _In_ PWCHAR SettingName, + _In_ ULONG DefaultValue +) +{ + WDFKEY key = NULL; + NTSTATUS status; + ULONG value = DefaultValue; + UNICODE_STRING valueName; + + TraceEvents( + TRACE_LEVEL_INFORMATION, + TRACE_DRIVER, + "%!FUNC! Entry (%ws)", + SettingName + ); + + status = WdfDriverOpenParametersRegistryKey( + WdfGetDriver(), + KEY_READ, + WDF_NO_OBJECT_ATTRIBUTES, + &key); + + if (!NT_SUCCESS(status)) + { + key = NULL; + TraceEvents( + TRACE_LEVEL_ERROR, + TRACE_DEVICE, + "%!FUNC! WdfDriverOpenParametersRegistryKey failed with %!STATUS!", + status + ); + goto cleanup; + } + + RtlInitUnicodeString(&valueName, SettingName); + + status = WdfRegistryQueryULong(key, &valueName, &value); + if (!NT_SUCCESS(status)) + { + value = DefaultValue; + TraceEvents( + TRACE_LEVEL_ERROR, + TRACE_DEVICE, + "%!FUNC! WdfRegistryQueryULong failed with %!STATUS!", + status + ); + goto cleanup; + } + + TraceEvents( + TRACE_LEVEL_INFORMATION, + TRACE_DRIVER, + "%!FUNC! read value of %ws is %u", + SettingName, + value + ); + +cleanup: + if (key != NULL) + { + WdfRegistryClose(key); + } + + TraceEvents( + TRACE_LEVEL_INFORMATION, + TRACE_DRIVER, + "%!FUNC! Exit" + ); + + return value; +} + +_IRQL_requires_(PASSIVE_LEVEL) +NTSTATUS +AmtPtpSetWellspringMode( + _In_ PDEVICE_CONTEXT DeviceContext, + _In_ BOOL IsWellspringModeOn +) +{ + + NTSTATUS status; + WDF_USB_CONTROL_SETUP_PACKET setupPacket; + WDF_MEMORY_DESCRIPTOR memoryDescriptor; + ULONG cbTransferred; + WDFMEMORY bufHandle = NULL; + unsigned char* buffer; + + if (IsWellspringModeOn) + { + ULONG feedbackClick = ReadSettingValue(L"FeedbackClick", 0x08081E); + ULONG feedbackRelease = ReadSettingValue(L"FeedbackRelease", 0x020218); + + AmtPtpSetHapticFeedback(DeviceContext, feedbackClick, feedbackRelease); + } + + TraceEvents( + TRACE_LEVEL_INFORMATION, + TRACE_DRIVER, + "%!FUNC! Entry" + ); + + // Type 3 does not need a mode switch. + // However, turn mode on or off as requested. + if (DeviceContext->DeviceInfo->tp_type == TYPE3) { + DeviceContext->IsWellspringModeOn = IsWellspringModeOn; + return STATUS_SUCCESS; + } + + status = WdfMemoryCreate( + WDF_NO_OBJECT_ATTRIBUTES, + PagedPool, + POOL_TAG_PTP_CONTROL, + DeviceContext->DeviceInfo->um_size, + &bufHandle, + &buffer + ); + + if (!NT_SUCCESS(status)) { + goto cleanup; + } + + RtlZeroMemory( + buffer, + DeviceContext->DeviceInfo->um_size + ); + + WDF_MEMORY_DESCRIPTOR_INIT_BUFFER( + &memoryDescriptor, + buffer, + sizeof(DeviceContext->DeviceInfo->um_size) + ); + + WDF_USB_CONTROL_SETUP_PACKET_INIT( + &setupPacket, + BmRequestDeviceToHost, + BmRequestToInterface, + BCM5974_WELLSPRING_MODE_READ_REQUEST_ID, + (USHORT) DeviceContext->DeviceInfo->um_req_val, + (USHORT) DeviceContext->DeviceInfo->um_req_idx + ); + + // Set stuffs right + setupPacket.Packet.bm.Request.Type = BmRequestClass; + + status = WdfUsbTargetDeviceSendControlTransferSynchronously( + DeviceContext->UsbDevice, + WDF_NO_HANDLE, + NULL, + &setupPacket, + &memoryDescriptor, + &cbTransferred + ); + + // Behavior mismatch: Actual device does not transfer bytes as expected (in length) + // So we do not check um_size as a temporary workaround. + if (!NT_SUCCESS(status)) { + TraceEvents( + TRACE_LEVEL_ERROR, + TRACE_DEVICE, + "%!FUNC! WdfUsbTargetDeviceSendControlTransferSynchronously (Read) failed with %!STATUS!, cbTransferred = %llu, um_size = %d", + status, + cbTransferred, + DeviceContext->DeviceInfo->um_size + ); + goto cleanup; + } + + // Apply the mode switch + buffer[DeviceContext->DeviceInfo->um_switch_idx] = IsWellspringModeOn ? + (unsigned char) DeviceContext->DeviceInfo->um_switch_on : + (unsigned char) DeviceContext->DeviceInfo->um_switch_off; + + // Write configuration + WDF_USB_CONTROL_SETUP_PACKET_INIT( + &setupPacket, + BmRequestHostToDevice, + BmRequestToInterface, + BCM5974_WELLSPRING_MODE_WRITE_REQUEST_ID, + (USHORT) DeviceContext->DeviceInfo->um_req_val, + (USHORT) DeviceContext->DeviceInfo->um_req_idx + ); + + // Set stuffs right + setupPacket.Packet.bm.Request.Type = BmRequestClass; + + status = WdfUsbTargetDeviceSendControlTransferSynchronously( + DeviceContext->UsbDevice, + WDF_NO_HANDLE, + NULL, + &setupPacket, + &memoryDescriptor, + &cbTransferred + ); + + if (!NT_SUCCESS(status)) { + TraceEvents( + TRACE_LEVEL_ERROR, + TRACE_DEVICE, + "%!FUNC! WdfUsbTargetDeviceSendControlTransferSynchronously (Write) failed with %!STATUS!", + status + ); + goto cleanup; + } + + // Set status + DeviceContext->IsWellspringModeOn = IsWellspringModeOn; + +cleanup: + TraceEvents( + TRACE_LEVEL_INFORMATION, + TRACE_DRIVER, + "%!FUNC! Exit" + ); + + WdfObjectDelete(bufHandle); + return status; + +} + +_IRQL_requires_(PASSIVE_LEVEL) +NTSTATUS +AmtPtpSetHapticFeedback( // --> Based on: https://github.com/dos1/Linux-Magic-Trackpad-2-Driver/blob/master/linux/drivers/hid/hid-magicmouse.c + _In_ PDEVICE_CONTEXT DeviceContext, + _In_ ULONG FeedbackClick, + _In_ ULONG FeedbackRelease +) +{ + NTSTATUS status = STATUS_UNSUCCESSFUL; + WDFMEMORY bufHandle = NULL; + PBYTE buffer; + WDF_MEMORY_DESCRIPTOR memoryDescriptor; + WDF_USB_CONTROL_SETUP_PACKET setupPacket; + ULONG cbTransferred; + CONST BYTE mt2Click[] = { 0x22, 0x01, 0x00, 0x78, 0x02, 0x00, 0x24, 0x30, 0x06, 0x01, 0x00, 0x18, 0x48, 0x13 }; + CONST BYTE mt2Release[] = { 0x23, 0x01, 0x00, 0x78, 0x02, 0x00, 0x24, 0x30, 0x06, 0x01, 0x00, 0x18, 0x48, 0x13 }; // WARNING: sizeof(mt2Release) MUST BE == TO sizeof(mt2Click)! + + TraceEvents( + TRACE_LEVEL_INFORMATION, + TRACE_DRIVER, + "%!FUNC! Entry" + ); + + status = WdfMemoryCreate( + WDF_NO_OBJECT_ATTRIBUTES, + PagedPool, + POOL_TAG_PTP_CONTROL, + sizeof(mt2Click), + &bufHandle, + &buffer + ); + + if (!NT_SUCCESS(status)) { + goto cleanup; + } + + RtlCopyMemory( + buffer, + mt2Click, + sizeof(mt2Click) + ); + + buffer[2] = (BYTE)(FeedbackClick >> 0); + buffer[5] = (BYTE)(FeedbackClick >> 8); + buffer[10] = (BYTE)(FeedbackClick >> 16); + + WDF_MEMORY_DESCRIPTOR_INIT_BUFFER( + &memoryDescriptor, + buffer, + sizeof(mt2Click) + ); + + WDF_USB_CONTROL_SETUP_PACKET_INIT( + &setupPacket, + BmRequestHostToDevice, + BmRequestToInterface, + 9, + 0x0322, + 2 + ); + + setupPacket.Packet.bm.Request.Type = BmRequestClass; + + status = WdfUsbTargetDeviceSendControlTransferSynchronously( + DeviceContext->UsbDevice, + WDF_NO_HANDLE, + NULL, + &setupPacket, + &memoryDescriptor, + &cbTransferred + ); + + if (!NT_SUCCESS(status)) { + TraceEvents( + TRACE_LEVEL_ERROR, + TRACE_DEVICE, + "%!FUNC! WdfUsbTargetDeviceSendControlTransferSynchronously (Write) failed with %!STATUS!", + status + ); + goto cleanup; + } + + RtlCopyMemory( + buffer, + mt2Release, + sizeof(mt2Release) + ); + + buffer[2] = (BYTE)(FeedbackRelease >> 0); + buffer[5] = (BYTE)(FeedbackRelease >> 8); + buffer[10] = (BYTE)(FeedbackRelease >> 16); + + WDF_USB_CONTROL_SETUP_PACKET_INIT( + &setupPacket, + BmRequestHostToDevice, + BmRequestToInterface, + 9, + 0x0323, + 2 + ); + + setupPacket.Packet.bm.Request.Type = BmRequestClass; + + status = WdfUsbTargetDeviceSendControlTransferSynchronously( + DeviceContext->UsbDevice, + WDF_NO_HANDLE, + NULL, + &setupPacket, + &memoryDescriptor, + &cbTransferred + ); + + if (!NT_SUCCESS(status)) { + TraceEvents( + TRACE_LEVEL_ERROR, + TRACE_DEVICE, + "%!FUNC! WdfUsbTargetDeviceSendControlTransferSynchronously (Write) failed with %!STATUS!", + status + ); + goto cleanup; + } + +cleanup: + TraceEvents( + TRACE_LEVEL_INFORMATION, + TRACE_DRIVER, + "%!FUNC! Exit" + ); + + WdfObjectDelete(bufHandle); + return status; +} + +NTSTATUS +AmtPtpEvtDeviceD0Entry( + _In_ WDFDEVICE Device, + _In_ WDF_POWER_DEVICE_STATE PreviousState +) +{ + PDEVICE_CONTEXT pDeviceContext; + NTSTATUS status; + BOOLEAN isTargetStarted; + + pDeviceContext = DeviceGetContext(Device); + isTargetStarted = FALSE; + + TraceEvents( + TRACE_LEVEL_INFORMATION, + TRACE_DRIVER, + "%!FUNC! -->AmtPtpDeviceEvtDeviceD0Entry - coming from %s", + DbgDevicePowerString(PreviousState) + ); + + // Check wellspring mode + if (pDeviceContext->IsButtonReportOn || pDeviceContext->IsWellspringModeOn) { + TraceEvents( + TRACE_LEVEL_INFORMATION, + TRACE_DRIVER, + "%!FUNC! <--AmtPtpDeviceEvtDeviceD0Entry - Start Wellspring Mode" + ); + + status = AmtPtpSetWellspringMode( + pDeviceContext, + TRUE + ); + + if (!NT_SUCCESS(status)) { + TraceEvents( + TRACE_LEVEL_WARNING, + TRACE_DRIVER, + "%!FUNC! <--AmtPtpDeviceEvtDeviceD0Entry - Start Wellspring Mode failed with %!STATUS!", + status + ); + } + } + + // Get current time counter + QueryPerformanceCounter( + &pDeviceContext->PerfCounter + ); + + // + // Since continuous reader is configured for this interrupt-pipe, we must explicitly start + // the I/O target to get the framework to post read requests. + // + status = WdfIoTargetStart(WdfUsbTargetPipeGetIoTarget(pDeviceContext->InterruptPipe)); + if (!NT_SUCCESS(status)) { + TraceEvents( + TRACE_LEVEL_ERROR, + TRACE_DRIVER, + "%!FUNC! <--AmtPtpDeviceEvtDeviceD0Entry - Failed to start interrupt pipe %!STATUS!", + status + ); + goto End; + } + + isTargetStarted = TRUE; + +End: + + if (!NT_SUCCESS(status)) { + // + // Failure in D0Entry will lead to device being removed. So let us stop the continuous + // reader in preparation for the ensuing remove. + // + if (isTargetStarted) { + WdfIoTargetStop( + WdfUsbTargetPipeGetIoTarget(pDeviceContext->InterruptPipe), + WdfIoTargetCancelSentIo + ); + } + } + + TraceEvents( + TRACE_LEVEL_INFORMATION, + TRACE_DRIVER, + "%!FUNC! <--AmtPtpDeviceEvtDeviceD0Entry" + ); + + return status; +} + +NTSTATUS +AmtPtpEvtDeviceD0Exit( + _In_ WDFDEVICE Device, + _In_ WDF_POWER_DEVICE_STATE TargetState +) +{ + PDEVICE_CONTEXT pDeviceContext; + NTSTATUS status; + + PAGED_CODE(); + status = STATUS_SUCCESS; + + TraceEvents( + TRACE_LEVEL_INFORMATION, + TRACE_DRIVER, + "%!FUNC! -->AmtPtpDeviceEvtDeviceD0Exit - moving to %s", + DbgDevicePowerString(TargetState) + ); + + pDeviceContext = DeviceGetContext(Device); + + // Stop IO Pipe. + WdfIoTargetStop(WdfUsbTargetPipeGetIoTarget( + pDeviceContext->InterruptPipe), + WdfIoTargetCancelSentIo + ); + + // Cancel Wellspring mode. + TraceEvents( + TRACE_LEVEL_INFORMATION, + TRACE_DRIVER, + "%!FUNC! -->AmtPtpDeviceEvtDeviceD0Exit - Cancel Wellspring Mode" + ); + + status = AmtPtpSetWellspringMode( + pDeviceContext, + FALSE + ); + + if (!NT_SUCCESS(status)) { + TraceEvents( + TRACE_LEVEL_WARNING, + TRACE_DRIVER, + "%!FUNC! -->AmtPtpDeviceEvtDeviceD0Exit - Cancel Wellspring Mode failed with %!STATUS!", + status + ); + } + + TraceEvents( + TRACE_LEVEL_INFORMATION, + TRACE_DRIVER, + "%!FUNC! <--AmtPtpDeviceEvtDeviceD0Exit" + ); + + return status; +} + +_IRQL_requires_(PASSIVE_LEVEL) +NTSTATUS +SelectInterruptInterface( + _In_ WDFDEVICE Device +) +{ + WDF_USB_DEVICE_SELECT_CONFIG_PARAMS configParams; + NTSTATUS status = STATUS_SUCCESS; + PDEVICE_CONTEXT pDeviceContext; + WDFUSBPIPE pipe; + WDF_USB_PIPE_INFORMATION pipeInfo; + UCHAR index; + UCHAR numberConfiguredPipes; + WDFUSBINTERFACE usbInterface; + + PAGED_CODE(); + + pDeviceContext = DeviceGetContext(Device); + WDF_USB_DEVICE_SELECT_CONFIG_PARAMS_INIT_SINGLE_INTERFACE(&configParams); + usbInterface = WdfUsbTargetDeviceGetInterface( + pDeviceContext->UsbDevice, + 0 + ); + + if (NULL == usbInterface) { + status = STATUS_UNSUCCESSFUL; + TraceEvents( + TRACE_LEVEL_ERROR, + TRACE_DEVICE, + "%!FUNC! WdfUsbTargetDeviceGetInterface 0 failed %!STATUS!", + status + ); + return status; + } + + configParams.Types.SingleInterface.ConfiguredUsbInterface = usbInterface; + configParams.Types.SingleInterface.NumberConfiguredPipes = WdfUsbInterfaceGetNumConfiguredPipes(usbInterface); + + pDeviceContext->UsbInterface = configParams.Types.SingleInterface.ConfiguredUsbInterface; + numberConfiguredPipes = configParams.Types.SingleInterface.NumberConfiguredPipes; + + // + // Get pipe handles + // + for (index = 0; index < numberConfiguredPipes; index++) { + + WDF_USB_PIPE_INFORMATION_INIT(&pipeInfo); + + pipe = WdfUsbInterfaceGetConfiguredPipe( + pDeviceContext->UsbInterface, + index, //PipeIndex, + &pipeInfo + ); + + // + // Tell the framework that it's okay to read less than + // MaximumPacketSize + // + WdfUsbTargetPipeSetNoMaximumPacketSizeCheck(pipe); + + if (WdfUsbPipeTypeInterrupt == pipeInfo.PipeType) { + pDeviceContext->InterruptPipe = pipe; + break; + } + + } + + // + // If we didn't find interrupt pipe, fail the start. + // + if (!pDeviceContext->InterruptPipe) { + status = STATUS_INVALID_DEVICE_STATE; + TraceEvents( + TRACE_LEVEL_ERROR, + TRACE_DEVICE, + "%!FUNC! Device is not configured properly %!STATUS!", + status + ); + + return status; + } + + return status; +} + +_IRQL_requires_(PASSIVE_LEVEL) +PCHAR +DbgDevicePowerString( + _In_ WDF_POWER_DEVICE_STATE Type +) +{ + switch (Type) + { + case WdfPowerDeviceInvalid: + return "WdfPowerDeviceInvalid"; + case WdfPowerDeviceD0: + return "WdfPowerDeviceD0"; + case WdfPowerDeviceD1: + return "WdfPowerDeviceD1"; + case WdfPowerDeviceD2: + return "WdfPowerDeviceD2"; + case WdfPowerDeviceD3: + return "WdfPowerDeviceD3"; + case WdfPowerDeviceD3Final: + return "WdfPowerDeviceD3Final"; + case WdfPowerDevicePrepareForHibernation: + return "WdfPowerDevicePrepareForHibernation"; + case WdfPowerDeviceMaximum: + return "WdfPowerDeviceMaximum"; + default: + return "UnKnown Device Power State"; + } +} + +_IRQL_requires_(PASSIVE_LEVEL) +NTSTATUS +AmtPtpEmergResetDevice( + _In_ PDEVICE_CONTEXT DeviceContext +) +{ + + NTSTATUS status; + TraceEvents( + TRACE_LEVEL_INFORMATION, + TRACE_DRIVER, + "%!FUNC! Entry"); + + status = AmtPtpSetWellspringMode( + DeviceContext, + FALSE); + + if (!NT_SUCCESS(status)) { + TraceEvents( + TRACE_LEVEL_ERROR, + TRACE_DEVICE, + "%!FUNC! AmtPtpSetWellspringMode failed with %!STATUS!", + status); + } + + status = AmtPtpSetWellspringMode( + DeviceContext, + TRUE); + + if (!NT_SUCCESS(status)) { + TraceEvents( + TRACE_LEVEL_ERROR, + TRACE_DEVICE, + "%!FUNC! AmtPtpSetWellspringMode failed with %!STATUS!", + status); + } + + TraceEvents( + TRACE_LEVEL_INFORMATION, + TRACE_DRIVER, + "%!FUNC! Exit"); + + return status; + +} diff --git a/AmtPtpDeviceUsbUm/Driver.c b/AmtPtpDeviceUsbUm/Driver.c new file mode 100644 index 0000000..5ec007c --- /dev/null +++ b/AmtPtpDeviceUsbUm/Driver.c @@ -0,0 +1,158 @@ +// Driver.c: This file contains the driver entry points and callbacks. + +#include +#include "driver.tmh" + +// Declares Windows 10 TraceLogger provider here. +TRACELOGGING_DEFINE_PROVIDER( + g_hAmtPtpDeviceTraceProvider, + "AmtPtpDeviceTraceProvider", + (0x871b1e2d, 0xcc5a, 0x4ade, 0xb7, 0x4e, 0x6c, 0xf1, 0x0, 0x4e, 0xf1, 0x49)); + +NTSTATUS +DriverEntry( + _In_ PDRIVER_OBJECT DriverObject, + _In_ PUNICODE_STRING RegistryPath +) +{ + WDF_DRIVER_CONFIG config; + NTSTATUS status; + WDF_OBJECT_ATTRIBUTES attributes; + + // Initialize tracing + DriverTraceInit( + DriverObject, + RegistryPath + ); + + TraceEvents( + TRACE_LEVEL_INFORMATION, + TRACE_DRIVER, + "%!FUNC! Entry" + ); + + // + // Register a cleanup callback so that we can call WPP_CLEANUP when + // the framework driver object is deleted during driver unload. + // + WDF_OBJECT_ATTRIBUTES_INIT(&attributes); + attributes.EvtCleanupCallback = AmtPtpDeviceEvtDriverContextCleanup; + + WDF_DRIVER_CONFIG_INIT(&config, + AmtPtpDeviceEvtDeviceAdd + ); + + status = WdfDriverCreate(DriverObject, + RegistryPath, + &attributes, + &config, + WDF_NO_HANDLE + ); + + if (!NT_SUCCESS(status)) { + TraceEvents( + TRACE_LEVEL_ERROR, + TRACE_DRIVER, + "%!FUNC! WdfDriverCreate failed %!STATUS!", + status + ); + TraceLoggingWrite( + g_hAmtPtpDeviceTraceProvider, + EVENT_DRIVER_FUNCTIONAL, + TraceLoggingString("DriverEntry", "Routine"), + TraceLoggingString("WdfDriverCreate", "Context"), + TraceLoggingInt32(status, "Status"), + TraceLoggingString(EVENT_DRIVER_FUNC_SUBTYPE_CRITFAIL, EVENT_DRIVER_FUNC_SUBTYPE) + ); + DriverTraceCleanup(DriverObject); + return status; + } + + TraceEvents( + TRACE_LEVEL_INFORMATION, + TRACE_DRIVER, + "%!FUNC! Exit" + ); + + return status; +} + +VOID +DriverTraceInit( + _In_ PDRIVER_OBJECT DriverObject, + _In_ PUNICODE_STRING RegistryPath +) +{ + // Initialize WPP Tracing + WPP_INIT_TRACING( + DriverObject, + RegistryPath + ); + + // Initialize TraceLogger Tracing + TraceLoggingRegister(g_hAmtPtpDeviceTraceProvider); +} + +VOID +DriverTraceCleanup( + _In_ WDFOBJECT DriverObject +) +{ + UNREFERENCED_PARAMETER(DriverObject); + + TraceLoggingUnregister(g_hAmtPtpDeviceTraceProvider); + // This actually directly calls WppCleanupUm() in UMDF drivers + WPP_CLEANUP(WdfDriverWdmGetDriverObject((WDFDRIVER) Driver)); +} + +NTSTATUS +AmtPtpDeviceEvtDeviceAdd( + _In_ WDFDRIVER Driver, + _Inout_ PWDFDEVICE_INIT DeviceInit +) +{ + NTSTATUS status = STATUS_SUCCESS; + + TraceEvents( + TRACE_LEVEL_INFORMATION, + TRACE_DRIVER, + "%!FUNC! Entry" + ); + + TraceEvents( + TRACE_LEVEL_INFORMATION, + TRACE_DRIVER, + "%!FUNC! Set FDO driver filter" + ); + + WdfFdoInitSetFilter(DeviceInit); + + status = AmtPtpCreateDevice( + Driver, + DeviceInit + ); + + TraceEvents( + TRACE_LEVEL_INFORMATION, + TRACE_DRIVER, + "%!FUNC! Exit" + ); + return status; +} + +VOID +AmtPtpDeviceEvtDriverContextCleanup( + _In_ WDFOBJECT DriverObject +) +{ + TraceEvents( + TRACE_LEVEL_INFORMATION, + TRACE_DRIVER, + "%!FUNC! Entry" + ); + + // + // Stop WPP Tracing + // + DriverTraceCleanup(DriverObject); +} diff --git a/AmtPtpDeviceUsbUm/Hid.c b/AmtPtpDeviceUsbUm/Hid.c new file mode 100644 index 0000000..e5c9d36 --- /dev/null +++ b/AmtPtpDeviceUsbUm/Hid.c @@ -0,0 +1,1130 @@ +// Hid.c: HID-related routine + +#include +#include +#include "Hid.tmh" + +_IRQL_requires_(PASSIVE_LEVEL) +NTSTATUS +AmtPtpGetHidDescriptor( + _In_ WDFDEVICE Device, + _In_ WDFREQUEST Request +) +{ + + NTSTATUS status = STATUS_SUCCESS; + PDEVICE_CONTEXT pContext = DeviceGetContext(Device); + size_t szHidDescriptor = 0; + WDFMEMORY RequestMemory; + PHID_DESCRIPTOR pSelectedHidDescriptor = NULL; + + TraceEvents( + TRACE_LEVEL_INFORMATION, + TRACE_DRIVER, + "%!FUNC! Entry" + ); + + status = WdfRequestRetrieveOutputMemory( + Request, + &RequestMemory + ); + + if (!NT_SUCCESS(status)) { + TraceEvents( + TRACE_LEVEL_ERROR, + TRACE_DRIVER, + "%!FUNC! WdfRequestRetrieveOutputBuffer failed with %!STATUS!", + status + ); + return status; + } + + switch (pContext->DeviceDescriptor.idProduct) { + case USB_DEVICE_ID_APPLE_WELLSPRING3_ANSI: + case USB_DEVICE_ID_APPLE_WELLSPRING3_ISO: + case USB_DEVICE_ID_APPLE_WELLSPRING3_JIS: + { + TraceEvents( + TRACE_LEVEL_INFORMATION, + TRACE_DRIVER, + "%!FUNC! Request HID Report Descriptor for MacBook Family, Wellspring 3 Series" + ); + + szHidDescriptor = AmtPtp3DefaultHidDescriptor.bLength; + pSelectedHidDescriptor = &AmtPtp3DefaultHidDescriptor; + break; + } + case USB_DEVICE_ID_APPLE_WELLSPRING5_ANSI: + case USB_DEVICE_ID_APPLE_WELLSPRING5_ISO: + case USB_DEVICE_ID_APPLE_WELLSPRING5_JIS: + case USB_DEVICE_ID_APPLE_WELLSPRING5A_ANSI: + case USB_DEVICE_ID_APPLE_WELLSPRING5A_ISO: + case USB_DEVICE_ID_APPLE_WELLSPRING5A_JIS: + { + TraceEvents( + TRACE_LEVEL_INFORMATION, + TRACE_DRIVER, + "%!FUNC! Request HID Report Descriptor for MacBook Family, Wellspring 5/5A Series" + ); + + szHidDescriptor = AmtPtp5DefaultHidDescriptor.bLength; + pSelectedHidDescriptor = &AmtPtp5DefaultHidDescriptor; + break; + } + case USB_DEVICE_ID_APPLE_WELLSPRING6_ANSI: + case USB_DEVICE_ID_APPLE_WELLSPRING6_ISO: + case USB_DEVICE_ID_APPLE_WELLSPRING6_JIS: + case USB_DEVICE_ID_APPLE_WELLSPRING6A_ANSI: + case USB_DEVICE_ID_APPLE_WELLSPRING6A_ISO: + case USB_DEVICE_ID_APPLE_WELLSPRING6A_JIS: + { + TraceEvents( + TRACE_LEVEL_INFORMATION, + TRACE_DRIVER, + "%!FUNC! Request HID Report Descriptor for MacBook Family, Wellspring 6/6A Series" + ); + + szHidDescriptor = AmtPtp6DefaultHidDescriptor.bLength; + pSelectedHidDescriptor = &AmtPtp6DefaultHidDescriptor; + break; + } + case USB_DEVICE_ID_APPLE_WELLSPRING7_ANSI: + case USB_DEVICE_ID_APPLE_WELLSPRING7_ISO: + case USB_DEVICE_ID_APPLE_WELLSPRING7_JIS: + case USB_DEVICE_ID_APPLE_WELLSPRING7A_ANSI: + case USB_DEVICE_ID_APPLE_WELLSPRING7A_ISO: + case USB_DEVICE_ID_APPLE_WELLSPRING7A_JIS: + { + TraceEvents( + TRACE_LEVEL_INFORMATION, + TRACE_DRIVER, + "%!FUNC! Request HID Report Descriptor for MacBook Family, Wellspring 7/7A Series" + ); + + szHidDescriptor = AmtPtp7aDefaultHidDescriptor.bLength; + pSelectedHidDescriptor = &AmtPtp7aDefaultHidDescriptor; + break; + } + case USB_DEVICE_ID_APPLE_WELLSPRING8_ANSI: + case USB_DEVICE_ID_APPLE_WELLSPRING8_ISO: + case USB_DEVICE_ID_APPLE_WELLSPRING8_JIS: + case USB_DEVICE_ID_APPLE_WELLSPRING9_JIS: + case USB_DEVICE_ID_APPLE_WELLSPRING9_ANSI: + case USB_DEVICE_ID_APPLE_WELLSPRING9_ISO: + { + TraceEvents( + TRACE_LEVEL_INFORMATION, + TRACE_DRIVER, + "%!FUNC! Request HID Report Descriptor for MacBook Family, Wellspring 8 Series" + ); + + szHidDescriptor = AmtPtp8DefaultHidDescriptor.bLength; + pSelectedHidDescriptor = &AmtPtp8DefaultHidDescriptor; + break; + } + case USB_DEVICE_ID_APPLE_MAGICTRACKPAD2: + { + TraceEvents( + TRACE_LEVEL_INFORMATION, + TRACE_DRIVER, + "%!FUNC! Request HID Report Descriptor for Apple Magic Trackpad 2 Family" + ); + + szHidDescriptor = AmtPtpMt2DefaultHidDescriptor.bLength; + pSelectedHidDescriptor = &AmtPtpMt2DefaultHidDescriptor; + break; + } + } + + if (pSelectedHidDescriptor != NULL && szHidDescriptor > 0) { + status = WdfMemoryCopyFromBuffer( + RequestMemory, + 0, + (PVOID) pSelectedHidDescriptor, + szHidDescriptor + ); + + if (!NT_SUCCESS(status)) { + TraceEvents( + TRACE_LEVEL_ERROR, + TRACE_DRIVER, + "%!FUNC! WdfMemoryCopyFromBuffer failed with %!STATUS!", + status + ); + goto exit; + } + + WdfRequestSetInformation( + Request, + szHidDescriptor + ); + } + else { + TraceEvents( + TRACE_LEVEL_WARNING, + TRACE_DRIVER, + "%!FUNC! Device HID registry is not found" + ); + TraceLoggingWrite( + g_hAmtPtpDeviceTraceProvider, + EVENT_DEVICE_IDENTIFICATION, + TraceLoggingString("AmtPtpGetHidDescriptor", "Routine"), + TraceLoggingUInt16(pContext->DeviceDescriptor.idProduct, "idProduct"), + TraceLoggingString(EVENT_DEVICE_ID_SUBTYPE_HIDREG_NOTFOUND, EVENT_DRIVER_FUNC_SUBTYPE) + ); + status = STATUS_INVALID_DEVICE_STATE; + goto exit; + } + +exit: + TraceEvents( + TRACE_LEVEL_INFORMATION, + TRACE_DRIVER, + "%!FUNC! Exit" + ); + return status; +} + +_IRQL_requires_(PASSIVE_LEVEL) +NTSTATUS +AmtPtpGetDeviceAttribs( + _In_ WDFDEVICE Device, + _In_ WDFREQUEST Request +) +{ + + NTSTATUS status = STATUS_SUCCESS; + PDEVICE_CONTEXT pContext = DeviceGetContext(Device); + PHID_DEVICE_ATTRIBUTES pDeviceAttributes = NULL; + + TraceEvents( + TRACE_LEVEL_INFORMATION, + TRACE_DRIVER, + "%!FUNC! Entry" + ); + + status = WdfRequestRetrieveOutputBuffer( + Request, + sizeof(HID_DEVICE_ATTRIBUTES), + &pDeviceAttributes, + NULL + ); + + if (!NT_SUCCESS(status)) { + TraceEvents( + TRACE_LEVEL_ERROR, + TRACE_DRIVER, + "%!FUNC! WdfRequestRetrieveOutputBuffer failed with %!STATUS!", + status + ); + goto exit; + } + + pDeviceAttributes->Size = sizeof(HID_DEVICE_ATTRIBUTES); + pDeviceAttributes->ProductID = pContext->DeviceDescriptor.idProduct; + // Okay here's one thing: we cannot report the real ID here, otherwise there's will be some great conflict with the USB/BT driver. + // Therefore Vendor ID is changed to a hardcoded number + pDeviceAttributes->VendorID = DEVICE_VID; + pDeviceAttributes->VersionNumber = DEVICE_VERSION; + + WdfRequestSetInformation( + Request, + sizeof(HID_DEVICE_ATTRIBUTES) + ); + +exit: + TraceEvents( + TRACE_LEVEL_INFORMATION, + TRACE_DRIVER, + "%!FUNC! Exit" + ); + + return status; +} + +_IRQL_requires_(PASSIVE_LEVEL) +NTSTATUS +AmtPtpGetReportDescriptor( + _In_ WDFDEVICE Device, + _In_ WDFREQUEST Request +) +{ + + NTSTATUS status = STATUS_SUCCESS; + PDEVICE_CONTEXT pContext = DeviceGetContext(Device); + size_t szHidDescriptor = 0; + WDFMEMORY RequestMemory; + PHID_REPORT_DESCRIPTOR pSelectedHidDescriptor = NULL; + + TraceEvents( + TRACE_LEVEL_INFORMATION, + TRACE_DRIVER, + "%!FUNC! Entry" + ); + + status = WdfRequestRetrieveOutputMemory( + Request, + &RequestMemory + ); + + if (!NT_SUCCESS(status)) { + TraceEvents( + TRACE_LEVEL_ERROR, + TRACE_DRIVER, + "%!FUNC! WdfRequestRetrieveOutputBuffer failed with %!STATUS!", + status + ); + goto exit; + } + + switch (pContext->DeviceDescriptor.idProduct) { + case USB_DEVICE_ID_APPLE_WELLSPRING3_ANSI: + case USB_DEVICE_ID_APPLE_WELLSPRING3_ISO: + case USB_DEVICE_ID_APPLE_WELLSPRING3_JIS: + { + szHidDescriptor = AmtPtp3DefaultHidDescriptor.DescriptorList[0].wReportLength; + pSelectedHidDescriptor = AmtPtp3ReportDescriptor; + break; + } + case USB_DEVICE_ID_APPLE_WELLSPRING5_ANSI: + case USB_DEVICE_ID_APPLE_WELLSPRING5_ISO: + case USB_DEVICE_ID_APPLE_WELLSPRING5_JIS: + case USB_DEVICE_ID_APPLE_WELLSPRING5A_ANSI: + case USB_DEVICE_ID_APPLE_WELLSPRING5A_ISO: + case USB_DEVICE_ID_APPLE_WELLSPRING5A_JIS: + { + szHidDescriptor = AmtPtp5DefaultHidDescriptor.DescriptorList[0].wReportLength; + pSelectedHidDescriptor = AmtPtp5ReportDescriptor; + break; + } + case USB_DEVICE_ID_APPLE_WELLSPRING6_ANSI: + case USB_DEVICE_ID_APPLE_WELLSPRING6_ISO: + case USB_DEVICE_ID_APPLE_WELLSPRING6_JIS: + case USB_DEVICE_ID_APPLE_WELLSPRING6A_ANSI: + case USB_DEVICE_ID_APPLE_WELLSPRING6A_ISO: + case USB_DEVICE_ID_APPLE_WELLSPRING6A_JIS: + { + szHidDescriptor = AmtPtp6DefaultHidDescriptor.DescriptorList[0].wReportLength; + pSelectedHidDescriptor = AmtPtp6ReportDescriptor; + break; + } + case USB_DEVICE_ID_APPLE_WELLSPRING7_ANSI: + case USB_DEVICE_ID_APPLE_WELLSPRING7_ISO: + case USB_DEVICE_ID_APPLE_WELLSPRING7_JIS: + case USB_DEVICE_ID_APPLE_WELLSPRING7A_ANSI: + case USB_DEVICE_ID_APPLE_WELLSPRING7A_ISO: + case USB_DEVICE_ID_APPLE_WELLSPRING7A_JIS: + { + szHidDescriptor = AmtPtp7aDefaultHidDescriptor.DescriptorList[0].wReportLength; + pSelectedHidDescriptor = AmtPtp7aReportDescriptor; + break; + } + case USB_DEVICE_ID_APPLE_WELLSPRING8_ANSI: + case USB_DEVICE_ID_APPLE_WELLSPRING8_ISO: + case USB_DEVICE_ID_APPLE_WELLSPRING8_JIS: + case USB_DEVICE_ID_APPLE_WELLSPRING9_JIS: + case USB_DEVICE_ID_APPLE_WELLSPRING9_ANSI: + case USB_DEVICE_ID_APPLE_WELLSPRING9_ISO: + { + szHidDescriptor = AmtPtp8DefaultHidDescriptor.DescriptorList[0].wReportLength; + pSelectedHidDescriptor = AmtPtp8ReportDescriptor; + break; + } + case USB_DEVICE_ID_APPLE_MAGICTRACKPAD2: + { + szHidDescriptor = AmtPtpMt2DefaultHidDescriptor.DescriptorList[0].wReportLength; + pSelectedHidDescriptor = AmtPtpMt2ReportDescriptor; + break; + } + } + + if (pSelectedHidDescriptor != NULL && szHidDescriptor > 0) { + status = WdfMemoryCopyFromBuffer( + RequestMemory, + 0, + (PVOID) pSelectedHidDescriptor, + szHidDescriptor + ); + + if (!NT_SUCCESS(status)) { + TraceEvents( + TRACE_LEVEL_ERROR, + TRACE_DRIVER, + "%!FUNC! WdfMemoryCopyFromBuffer failed with %!STATUS!", + status + ); + return status; + } + + WdfRequestSetInformation( + Request, + szHidDescriptor + ); + } + else if (szHidDescriptor == 0) { + status = STATUS_INVALID_DEVICE_STATE; + TraceEvents( + TRACE_LEVEL_WARNING, + TRACE_DRIVER, + "%!FUNC! Device HID report length is zero" + ); + goto exit; + } + else { + TraceEvents( + TRACE_LEVEL_WARNING, + TRACE_DRIVER, + "%!FUNC! Device HID registry is not found" + ); + TraceLoggingWrite( + g_hAmtPtpDeviceTraceProvider, + EVENT_DEVICE_IDENTIFICATION, + TraceLoggingString("AmtPtpGetReportDescriptor", "Routine"), + TraceLoggingUInt16(pContext->DeviceDescriptor.idProduct, "idProduct"), + TraceLoggingString(EVENT_DEVICE_ID_SUBTYPE_HIDREG_NOTFOUND, EVENT_DRIVER_FUNC_SUBTYPE) + ); + status = STATUS_INVALID_DEVICE_STATE; + goto exit; + } + +exit: + TraceEvents( + TRACE_LEVEL_INFORMATION, + TRACE_DRIVER, + "%!FUNC! Exit" + ); + return status; +} + +_IRQL_requires_(PASSIVE_LEVEL) +NTSTATUS +AmtPtpGetStrings( + _In_ WDFDEVICE Device, + _In_ WDFREQUEST Request +) +{ + + NTSTATUS status = STATUS_SUCCESS; + PDEVICE_CONTEXT pContext = DeviceGetContext(Device); + void *pStringBuffer = NULL; + WDFMEMORY memHandle; + USHORT wcharCount; + size_t actualSize; + UCHAR strIndex; + + ULONG inputValue; + WDFMEMORY inputMemory; + size_t inputBufferLength; + PVOID inputBuffer; + ULONG languageId, stringId; + + TraceEvents( + TRACE_LEVEL_INFORMATION, + TRACE_DRIVER, + "%!FUNC! Entry" + ); + + status = WdfRequestRetrieveInputMemory( + Request, + &inputMemory + ); + + if (!NT_SUCCESS(status)) { + TraceEvents( + TRACE_LEVEL_INFORMATION, + TRACE_DRIVER, + "%!FUNC! WdfRequestRetrieveInputMemory failed with status %!STATUS!", + status + ); + return status; + } + + inputBuffer = WdfMemoryGetBuffer( + inputMemory, + &inputBufferLength + ); + + // + // make sure buffer is big enough. + // + if (inputBufferLength < sizeof(ULONG)) { + status = STATUS_INVALID_BUFFER_SIZE; + TraceEvents( + TRACE_LEVEL_INFORMATION, + TRACE_DRIVER, + "%!FUNC! GetStringId: invalid input buffer. size %d, expect %d", + (int)inputBufferLength, + (int) sizeof(ULONG) + ); + return status; + } + + inputValue = (*(PULONG)inputBuffer); + stringId = (inputValue & 0x0ffff); + languageId = (inputValue >> 16); + + // Get actual string from USB device + switch (stringId) + { + case HID_STRING_ID_IMANUFACTURER: + strIndex = pContext->DeviceDescriptor.iManufacturer; + break; + case HID_STRING_ID_IPRODUCT: + strIndex = pContext->DeviceDescriptor.iProduct; + break; + case HID_STRING_ID_ISERIALNUMBER: + strIndex = pContext->DeviceDescriptor.iSerialNumber; + break; + default: + TraceEvents( + TRACE_LEVEL_WARNING, + TRACE_DRIVER, + "%!FUNC! gets invalid string type" + ); + return status; + } + + status = WdfUsbTargetDeviceAllocAndQueryString( + pContext->UsbDevice, + WDF_NO_OBJECT_ATTRIBUTES, + &memHandle, + &wcharCount, + strIndex, + (USHORT) languageId + ); + + if (!NT_SUCCESS(status)) { + TraceEvents( + TRACE_LEVEL_ERROR, + TRACE_DRIVER, "%!FUNC! WdfUsbTargetDeviceAllocAndQueryString failed with %!STATUS!", + status + ); + return status; + } + + status = WdfRequestRetrieveOutputBuffer( + Request, + wcharCount * sizeof(WCHAR), + &pStringBuffer, + &actualSize + ); + + if (!NT_SUCCESS(status)) { + TraceEvents( + TRACE_LEVEL_ERROR, + TRACE_DRIVER, + "%!FUNC! WdfMemoryCopyFromBuffer failed with %!STATUS!", + status + ); + return status; + } + + WdfMemoryCopyToBuffer( + memHandle, + 0, + &pStringBuffer, + actualSize + ); + + WdfRequestSetInformation( + Request, + actualSize + ); + + TraceEvents( + TRACE_LEVEL_INFORMATION, + TRACE_DRIVER, + "%!FUNC! Exit" + ); + return status; + +} + +_IRQL_requires_(PASSIVE_LEVEL) +NTSTATUS +RequestGetHidXferPacketToReadFromDevice( + _In_ WDFREQUEST Request, + _Out_ HID_XFER_PACKET *Packet +) +{ + + // + // Driver need to write to the output buffer (so that App can read from it) + // + // Report Buffer: Output Buffer + // Report Id : Input Buffer + // + + NTSTATUS status; + WDFMEMORY inputMemory; + WDFMEMORY outputMemory; + size_t inputBufferLength; + size_t outputBufferLength; + PVOID inputBuffer; + PVOID outputBuffer; + + // + // Get report Id from input buffer + // + status = WdfRequestRetrieveInputMemory( + Request, + &inputMemory + ); + + if (!NT_SUCCESS(status)) { + TraceEvents( + TRACE_LEVEL_ERROR, + TRACE_DRIVER, + "%!FUNC! WdfRequestRetrieveInputMemory failed with %!STATUS!", + status + ); + return status; + } + inputBuffer = WdfMemoryGetBuffer( + inputMemory, + &inputBufferLength + ); + + if (inputBufferLength < sizeof(UCHAR)) { + status = STATUS_INVALID_BUFFER_SIZE; + TraceEvents( + TRACE_LEVEL_ERROR, + TRACE_DRIVER, + "%!FUNC! WdfRequestRetrieveInputMemory: invalid input buffer. size %d, expect %d", + (int) inputBufferLength, + (int) sizeof(UCHAR) + ); + return status; + } + + Packet->reportId = *(PUCHAR) inputBuffer; + + // + // Get report buffer from output buffer + // + status = WdfRequestRetrieveOutputMemory( + Request, + &outputMemory + ); + + if (!NT_SUCCESS(status)) { + TraceEvents( + TRACE_LEVEL_ERROR, + TRACE_DRIVER, + "%!FUNC! WdfRequestRetrieveOutputMemory failed with %!STATUS!", + status + ); + return status; + } + + outputBuffer = WdfMemoryGetBuffer( + outputMemory, + &outputBufferLength + ); + + Packet->reportBuffer = (PUCHAR) outputBuffer; + Packet->reportBufferLen = (ULONG) outputBufferLength; + + return status; + +} + +_IRQL_requires_(PASSIVE_LEVEL) +NTSTATUS +RequestGetHidXferPacketToWriteToDevice( + _In_ WDFREQUEST Request, + _Out_ HID_XFER_PACKET *Packet +) +{ + + // + // Driver need to read from the input buffer (which was written by App) + // + // Report Buffer: Input Buffer + // Report Id : Output Buffer Length + // + // Note that the report id is not stored inside the output buffer, as the + // driver has no read-access right to the output buffer, and trying to read + // from the buffer will cause an access violation error. + // + // The workaround is to store the report id in the OutputBufferLength field, + // to which the driver does have read-access right. + // + + NTSTATUS status; + WDFMEMORY inputMemory; + WDFMEMORY outputMemory; + size_t inputBufferLength; + size_t outputBufferLength; + PVOID inputBuffer; + + // + // Get report Id from output buffer length + // + status = WdfRequestRetrieveOutputMemory( + Request, + &outputMemory + ); + if (!NT_SUCCESS(status)) { + TraceEvents( + TRACE_LEVEL_ERROR, + TRACE_DRIVER, + "%!FUNC! WdfRequestRetrieveOutputMemory failed with %!STATUS!", + status + ); + return status; + } + WdfMemoryGetBuffer( + outputMemory, + &outputBufferLength + ); + Packet->reportId = (UCHAR) outputBufferLength; + + // + // Get report buffer from input buffer + // + status = WdfRequestRetrieveInputMemory( + Request, + &inputMemory + ); + if (!NT_SUCCESS(status)) { + TraceEvents( + TRACE_LEVEL_ERROR, + TRACE_DRIVER, + "%!FUNC! WdfRequestRetrieveInputMemory failed with %!STATUS!", + status + ); + return status; + } + inputBuffer = WdfMemoryGetBuffer( + inputMemory, + &inputBufferLength + ); + + Packet->reportBuffer = (PUCHAR) inputBuffer; + Packet->reportBufferLen = (ULONG) inputBufferLength; + + return status; + +} + +_IRQL_requires_(PASSIVE_LEVEL) +NTSTATUS +AmtPtpReportFeatures( + _In_ WDFDEVICE Device, + _In_ WDFREQUEST Request +) +{ + + NTSTATUS status; + PDEVICE_CONTEXT deviceContext; + HID_XFER_PACKET packet; + size_t reportSize; + + TraceEvents( + TRACE_LEVEL_INFORMATION, + TRACE_DRIVER, + "%!FUNC! Entry" + ); + + status = STATUS_SUCCESS; + deviceContext = DeviceGetContext(Device); + + status = RequestGetHidXferPacketToReadFromDevice( + Request, + &packet + ); + + if (!NT_SUCCESS(status)) { + TraceEvents( + TRACE_LEVEL_ERROR, + TRACE_DRIVER, + "%!FUNC! RequestGetHidXferPacketToReadFromDevice failed with status %!STATUS!", + status + ); + goto exit; + } + + switch (packet.reportId) + { + case REPORTID_DEVICE_CAPS: + { + TraceEvents( + TRACE_LEVEL_INFORMATION, + TRACE_DRIVER, + "%!FUNC! Report REPORTID_DEVICE_CAPS is requested" + ); + + // Size sanity check + reportSize = sizeof(PTP_DEVICE_CAPS_FEATURE_REPORT); + if (packet.reportBufferLen < reportSize) { + status = STATUS_INVALID_BUFFER_SIZE; + TraceEvents( + TRACE_LEVEL_ERROR, + TRACE_DRIVER, + "%!FUNC! Report buffer is too small" + ); + goto exit; + } + + PPTP_DEVICE_CAPS_FEATURE_REPORT capsReport = (PPTP_DEVICE_CAPS_FEATURE_REPORT) packet.reportBuffer; + + capsReport->MaximumContactPoints = PTP_MAX_CONTACT_POINTS; + capsReport->ButtonType = PTP_BUTTON_TYPE_CLICK_PAD; + capsReport->ReportID = REPORTID_DEVICE_CAPS; + + TraceEvents( + TRACE_LEVEL_INFORMATION, + TRACE_DRIVER, + "%!FUNC! Report REPORTID_DEVICE_CAPS has maximum contact points of %d", + capsReport->MaximumContactPoints + ); + TraceEvents( + TRACE_LEVEL_INFORMATION, + TRACE_DRIVER, + "%!FUNC! Report REPORTID_DEVICE_CAPS has touchpad type %d", + capsReport->ButtonType + ); + TraceEvents( + TRACE_LEVEL_INFORMATION, + TRACE_DRIVER, + "%!FUNC! Report REPORTID_DEVICE_CAPS is fulfilled" + ); + + WdfRequestSetInformation( + Request, + reportSize + ); + break; + } + case REPORTID_PTPHQA: + { + TraceEvents( + TRACE_LEVEL_INFORMATION, + TRACE_DRIVER, + "%!FUNC! Report REPORTID_PTPHQA is requested" + ); + + // Size sanity check + reportSize = sizeof(PTP_DEVICE_HQA_CERTIFICATION_REPORT); + if (packet.reportBufferLen < reportSize) { + status = STATUS_INVALID_BUFFER_SIZE; + TraceEvents( + TRACE_LEVEL_ERROR, + TRACE_DRIVER, + "%!FUNC! Report buffer is too small." + ); + goto exit; + } + + PPTP_DEVICE_HQA_CERTIFICATION_REPORT certReport = (PPTP_DEVICE_HQA_CERTIFICATION_REPORT) packet.reportBuffer; + + *certReport->CertificationBlob = DEFAULT_PTP_HQA_BLOB; + certReport->ReportID = REPORTID_PTPHQA; + + TraceEvents( + TRACE_LEVEL_INFORMATION, + TRACE_DRIVER, + "%!FUNC! Report REPORTID_PTPHQA is fulfilled" + ); + + WdfRequestSetInformation( + Request, + reportSize + ); + break; + } + case REPORTID_UMAPP_CONF: + { + TraceEvents( + TRACE_LEVEL_INFORMATION, + TRACE_DRIVER, + "%!FUNC! Report REPORTID_UMAPP_CONF is requested" + ); + + // Size sanity check + reportSize = sizeof(PTP_USERMODEAPP_CONF_REPORT); + if (packet.reportBufferLen < reportSize) { + status = STATUS_INVALID_BUFFER_SIZE; + TraceEvents( + TRACE_LEVEL_ERROR, + TRACE_DRIVER, + "%!FUNC! Report buffer is too small." + ); + goto exit; + } + + PPTP_USERMODEAPP_CONF_REPORT confReport = (PPTP_USERMODEAPP_CONF_REPORT)packet.reportBuffer; + + confReport->ReportID = REPORTID_UMAPP_CONF; + confReport->MultipleContactSizeQualificationLevel = deviceContext->MuContactSizeQualLevel; + confReport->SingleContactSizeQualificationLevel = deviceContext->SgContactSizeQualLevel; + confReport->PressureQualificationLevel = deviceContext->PressureQualLevel; + + TraceEvents( + TRACE_LEVEL_INFORMATION, + TRACE_DRIVER, + "%!FUNC! Report REPORTID_UMAPP_CONF is fulfilled" + ); + + WdfRequestSetInformation( + Request, + reportSize + ); + break; + } + default: + TraceEvents( + TRACE_LEVEL_INFORMATION, + TRACE_DRIVER, + "%!FUNC! Unsupported type %d is requested", + packet.reportId + ); + + status = STATUS_NOT_SUPPORTED; + goto exit; + } +exit: + TraceEvents( + TRACE_LEVEL_INFORMATION, + TRACE_DRIVER, + "%!FUNC! Exit" + ); + return status; + +} + +_IRQL_requires_(PASSIVE_LEVEL) +NTSTATUS +AmtPtpSetFeatures( + _In_ WDFDEVICE Device, + _In_ WDFREQUEST Request +) +{ + + NTSTATUS status; + HID_XFER_PACKET packet; + PDEVICE_CONTEXT deviceContext; + + TraceEvents( + TRACE_LEVEL_INFORMATION, + TRACE_DRIVER, + "%!FUNC! Entry" + ); + + status = STATUS_SUCCESS; + deviceContext = DeviceGetContext(Device); + + status = RequestGetHidXferPacketToWriteToDevice( + Request, + &packet + ); + + if (!NT_SUCCESS(status)) { + TraceEvents( + TRACE_LEVEL_ERROR, + TRACE_DRIVER, + "%!FUNC! RequestGetHidXferPacketToWriteToDevice failed with status %!STATUS!", + status + ); + goto exit; + } + + switch (packet.reportId) + { + case REPORTID_REPORTMODE: + { + TraceEvents( + TRACE_LEVEL_INFORMATION, + TRACE_DRIVER, + "%!FUNC! Report REPORTID_REPORTMODE is requested" + ); + + PPTP_DEVICE_INPUT_MODE_REPORT devInputMode = (PPTP_DEVICE_INPUT_MODE_REPORT) packet.reportBuffer; + + // Get current WellSpring mode + BOOL bWellspringMode = FALSE; + status = AmtPtpGetWellspringMode( + deviceContext, + &bWellspringMode + ); + + if (!NT_SUCCESS(status)) { + + TraceEvents( + TRACE_LEVEL_ERROR, + TRACE_DRIVER, + "%!FUNC! -> AmtPtpGetWellspringMode failed with status %!STATUS!", + status + ); + goto exit; + + } + + switch (devInputMode->Mode) + { + case PTP_COLLECTION_MOUSE: + { + + TraceEvents( + TRACE_LEVEL_INFORMATION, + TRACE_DRIVER, + "%!FUNC! Report REPORTID_REPORTMODE requested Mouse Input" + ); + + if (bWellspringMode) { + + status = AmtPtpSetWellspringMode( + deviceContext, + FALSE + ); + + if (!NT_SUCCESS(status)) { + TraceEvents( + TRACE_LEVEL_ERROR, + TRACE_DRIVER, + "%!FUNC! -> AmtPtpSetWellspringMode failed with status %!STATUS!", + status + ); + goto exit; + } + + } + + break; + + } + case PTP_COLLECTION_WINDOWS: + { + + TraceEvents( + TRACE_LEVEL_INFORMATION, + TRACE_DRIVER, + "%!FUNC! Report REPORTID_REPORTMODE requested Windows PTP Input" + ); + + if (!bWellspringMode) { + + status = AmtPtpSetWellspringMode( + deviceContext, + TRUE + ); + + if (!NT_SUCCESS(status)) { + TraceEvents( + TRACE_LEVEL_ERROR, + TRACE_DRIVER, + "%!FUNC! -> AmtPtpSetWellspringMode failed with status %!STATUS!", + status + ); + goto exit; + } + + } + + break; + + } + } + + WdfRequestSetInformation( + Request, + sizeof(PTP_DEVICE_INPUT_MODE_REPORT) + ); + + TraceEvents( + TRACE_LEVEL_INFORMATION, + TRACE_DRIVER, + "%!FUNC! Report REPORTID_REPORTMODE is fulfilled" + ); + break; + } + case REPORTID_FUNCSWITCH: + { + TraceEvents( + TRACE_LEVEL_INFORMATION, + TRACE_DRIVER, + "%!FUNC! Report REPORTID_FUNCSWITCH is requested" + ); + PPTP_DEVICE_SELECTIVE_REPORT_MODE_REPORT secInput = (PPTP_DEVICE_SELECTIVE_REPORT_MODE_REPORT) packet.reportBuffer; + + deviceContext->IsButtonReportOn = secInput->ButtonReport; + deviceContext->IsSurfaceReportOn = secInput->SurfaceReport; + + TraceEvents( + TRACE_LEVEL_INFORMATION, + TRACE_DRIVER, + "%!FUNC! Report REPORTID_FUNCSWITCH requested Button = %d, Surface = %d", + secInput->ButtonReport, + secInput->SurfaceReport + ); + + WdfRequestSetInformation( + Request, + sizeof(PTP_DEVICE_SELECTIVE_REPORT_MODE_REPORT) + ); + + TraceEvents( + TRACE_LEVEL_INFORMATION, + TRACE_DRIVER, + "%!FUNC! Report REPORTID_FUNCSWITCH is fulfilled" + ); + break; + } + case REPORTID_UMAPP_CONF: + { + TraceEvents( + TRACE_LEVEL_INFORMATION, + TRACE_DRIVER, + "%!FUNC! Report REPORTID_UMAPP_CONF is requested" + ); + PPTP_USERMODEAPP_CONF_REPORT umConfInput = (PPTP_USERMODEAPP_CONF_REPORT) packet.reportBuffer; + + // Set value + deviceContext->SgContactSizeQualLevel = umConfInput->SingleContactSizeQualificationLevel; + deviceContext->MuContactSizeQualLevel = umConfInput->MultipleContactSizeQualificationLevel; + deviceContext->PressureQualLevel = umConfInput->PressureQualificationLevel; + + TraceEvents( + TRACE_LEVEL_INFORMATION, + TRACE_DRIVER, + "%!FUNC! Report REPORTID_UMAPP_CONF requested PressureQual = %d, SgSize = %d, MuSize = %d", + umConfInput->PressureQualificationLevel, + umConfInput->SingleContactSizeQualificationLevel, + umConfInput->MultipleContactSizeQualificationLevel + ); + + // Report back + WdfRequestSetInformation( + Request, + sizeof(PTP_USERMODEAPP_CONF_REPORT) + ); + + TraceEvents( + TRACE_LEVEL_INFORMATION, + TRACE_DRIVER, + "%!FUNC! Report REPORTID_UMAPP_CONF is fulfilled" + ); + + break; + } + default: + TraceEvents( + TRACE_LEVEL_INFORMATION, + TRACE_DRIVER, + "%!FUNC! Unsupported type %d is requested", + packet.reportId + ); + status = STATUS_NOT_SUPPORTED; + goto exit; + } + +exit: + TraceEvents( + TRACE_LEVEL_INFORMATION, + TRACE_DRIVER, + "%!FUNC! Exit" + ); + return STATUS_SUCCESS; + +} \ No newline at end of file diff --git a/AmtPtpDeviceUsbUm/InputInterrupt.c b/AmtPtpDeviceUsbUm/InputInterrupt.c new file mode 100644 index 0000000..b302d43 --- /dev/null +++ b/AmtPtpDeviceUsbUm/InputInterrupt.c @@ -0,0 +1,650 @@ +// InputInterrupt.c: Handles device input event + +#include +#include "InputInterrupt.tmh" + +_IRQL_requires_(PASSIVE_LEVEL) +NTSTATUS +AmtPtpConfigContReaderForInterruptEndPoint( + _In_ PDEVICE_CONTEXT DeviceContext +) +{ + + WDF_USB_CONTINUOUS_READER_CONFIG contReaderConfig; + NTSTATUS status; + size_t transferLength = 0; + + TraceEvents( + TRACE_LEVEL_INFORMATION, + TRACE_DRIVER, + "%!FUNC! Entry" + ); + + switch (DeviceContext->DeviceInfo->tp_type) + { + case TYPE1: + transferLength = HEADER_TYPE1 + FSIZE_TYPE1 * MAX_FINGERS; + break; + case TYPE2: + transferLength = HEADER_TYPE2 + FSIZE_TYPE2 * MAX_FINGERS; + break; + case TYPE3: + transferLength = HEADER_TYPE3 + FSIZE_TYPE3 * MAX_FINGERS; + break; + case TYPE4: + transferLength = HEADER_TYPE4 + FSIZE_TYPE4 * MAX_FINGERS; + break; + case TYPE5: + transferLength = HEADER_TYPE5 + FSIZE_TYPE5 * MAX_FINGERS; + break; + } + + if (transferLength <= 0) { + status = STATUS_UNKNOWN_REVISION; + return status; + } + + WDF_USB_CONTINUOUS_READER_CONFIG_INIT( + &contReaderConfig, + AmtPtpEvtUsbInterruptPipeReadComplete, + DeviceContext, // Context + transferLength // Calculate transferred length by device information + ); + + contReaderConfig.EvtUsbTargetPipeReadersFailed = AmtPtpEvtUsbInterruptReadersFailed; + + // Remember to turn it on in D0 entry + status = WdfUsbTargetPipeConfigContinuousReader( + DeviceContext->InterruptPipe, + &contReaderConfig + ); + + if (!NT_SUCCESS(status)) { + TraceEvents( + TRACE_LEVEL_ERROR, + TRACE_DRIVER, + "%!FUNC! AmtPtpConfigContReaderForInterruptEndPoint failed with Status code %!STATUS!", + status + ); + return status; + } + + TraceEvents( + TRACE_LEVEL_INFORMATION, + TRACE_DRIVER, + "%!FUNC! Exit" + ); + + return STATUS_SUCCESS; + +} + +_IRQL_requires_(PASSIVE_LEVEL) +VOID +AmtPtpEvtUsbInterruptPipeReadComplete( + _In_ WDFUSBPIPE Pipe, + _In_ WDFMEMORY Buffer, + _In_ size_t NumBytesTransferred, + _In_ WDFCONTEXT Context +) +{ + UNREFERENCED_PARAMETER(Pipe); + + WDFDEVICE device; + PDEVICE_CONTEXT pDeviceContext = Context; + UCHAR* pBuffer = NULL; + NTSTATUS status; + + TraceEvents( + TRACE_LEVEL_INFORMATION, + TRACE_DRIVER, + "%!FUNC! Entry" + ); + + device = WdfObjectContextGetObject(pDeviceContext); + size_t headerSize = (unsigned int) pDeviceContext->DeviceInfo->tp_header; + size_t fingerprintSize = (unsigned int) pDeviceContext->DeviceInfo->tp_fsize; + + if (NumBytesTransferred < headerSize || (NumBytesTransferred - headerSize) % fingerprintSize != 0) { + + TraceEvents( + TRACE_LEVEL_INFORMATION, + TRACE_DRIVER, + "%!FUNC! Malformed input received. Length = %llu. Attempt to reset device.", + NumBytesTransferred + ); + + status = AmtPtpEmergResetDevice(pDeviceContext); + if (!NT_SUCCESS(status)) { + + TraceEvents( + TRACE_LEVEL_INFORMATION, + TRACE_DRIVER, + "%!FUNC! AmtPtpEmergResetDevice failed with %!STATUS!", + status + ); + + } + + return; + } + + if (!pDeviceContext->IsWellspringModeOn) { + + TraceEvents( + TRACE_LEVEL_WARNING, + TRACE_DRIVER, + "%!FUNC! Routine is called without enabling Wellspring mode" + ); + + return; + } + + // Dispatch USB Interrupt routine by device family + switch (pDeviceContext->DeviceInfo->tp_type) { + case TYPE1: + { + TraceEvents( + TRACE_LEVEL_WARNING, + TRACE_DRIVER, + "%!FUNC! Mode not yet supported" + ); + break; + } + // Universal routine handler + case TYPE2: + case TYPE3: + case TYPE4: + { + pBuffer = WdfMemoryGetBuffer( + Buffer, + NULL + ); + + status = AmtPtpServiceTouchInputInterrupt( + pDeviceContext, + pBuffer, + NumBytesTransferred + ); + + if (!NT_SUCCESS(status)) { + TraceEvents( + TRACE_LEVEL_WARNING, + TRACE_DRIVER, + "%!FUNC! AmtPtpServiceTouchInputInterrupt failed with %!STATUS!", + status + ); + } + break; + } + // Magic Trackpad 2 + case TYPE5: + { + pBuffer = WdfMemoryGetBuffer( + Buffer, + NULL + ); + status = AmtPtpServiceTouchInputInterruptType5( + pDeviceContext, + pBuffer, + NumBytesTransferred + ); + + if (!NT_SUCCESS(status)) { + TraceEvents( + TRACE_LEVEL_WARNING, + TRACE_DRIVER, + "%!FUNC! AmtPtpServiceTouchInputInterrupt5 failed with %!STATUS!", + status + ); + } + break; + } + } + + TraceEvents( + TRACE_LEVEL_INFORMATION, + TRACE_DRIVER, + "%!FUNC! Exit" + ); + +} + +_IRQL_requires_(PASSIVE_LEVEL) +BOOLEAN +AmtPtpEvtUsbInterruptReadersFailed( + _In_ WDFUSBPIPE Pipe, + _In_ NTSTATUS Status, + _In_ USBD_STATUS UsbdStatus +) +{ + UNREFERENCED_PARAMETER(Pipe); + UNREFERENCED_PARAMETER(UsbdStatus); + UNREFERENCED_PARAMETER(Status); + + return TRUE; +} + +_IRQL_requires_(PASSIVE_LEVEL) +NTSTATUS +AmtPtpServiceTouchInputInterrupt( + _In_ PDEVICE_CONTEXT DeviceContext, + _In_ UCHAR* Buffer, + _In_ size_t NumBytesTransferred +) +{ + NTSTATUS Status; + WDFREQUEST Request; + WDFMEMORY RequestMemory; + PTP_REPORT PtpReport; + LARGE_INTEGER CurrentPerfCounter; + LONGLONG PerfCounterDelta; + + const struct TRACKPAD_FINGER *f; + + TraceEvents( + TRACE_LEVEL_INFORMATION, + TRACE_DRIVER, + "%!FUNC! Entry" + ); + + size_t raw_n, i = 0; + size_t headerSize = (unsigned int) DeviceContext->DeviceInfo->tp_header; + size_t fingerprintSize = (unsigned int) DeviceContext->DeviceInfo->tp_fsize; + USHORT x = 0, y = 0; + + Status = STATUS_SUCCESS; + PtpReport.ReportID = REPORTID_MULTITOUCH; + PtpReport.IsButtonClicked = 0; + + // Retrieve next PTP touchpad request. + Status = WdfIoQueueRetrieveNextRequest( + DeviceContext->InputQueue, + &Request + ); + + if (!NT_SUCCESS(Status)) { + TraceEvents( + TRACE_LEVEL_INFORMATION, + TRACE_DRIVER, + "%!FUNC! No pending PTP request. Interrupt disposed" + ); + goto exit; + } + + QueryPerformanceCounter( + &CurrentPerfCounter + ); + + // Scan time is in 100us + PerfCounterDelta = (CurrentPerfCounter.QuadPart - DeviceContext->PerfCounter.QuadPart) / 100; + // Only two bytes allocated + if (PerfCounterDelta > 0xFF) + { + PerfCounterDelta = 0xFF; + } + + PtpReport.ScanTime = (USHORT) PerfCounterDelta; + + // Allocate output memory. + Status = WdfRequestRetrieveOutputMemory( + Request, + &RequestMemory + ); + + if (!NT_SUCCESS(Status)) { + TraceEvents( + TRACE_LEVEL_ERROR, + TRACE_DRIVER, + "%!FUNC! WdfRequestRetrieveOutputMemory failed with %!STATUS!", + Status + ); + goto exit; + } + + // Type 2 touchpad surface report + if (DeviceContext->IsSurfaceReportOn) { + // Handles trackpad surface report here. + raw_n = (NumBytesTransferred - headerSize) / fingerprintSize; + if (raw_n >= PTP_MAX_CONTACT_POINTS) raw_n = PTP_MAX_CONTACT_POINTS; + PtpReport.ContactCount = (UCHAR) raw_n; + +#ifdef INPUT_CONTENT_TRACE + TraceEvents( + TRACE_LEVEL_INFORMATION, + TRACE_DRIVER, + "%!FUNC! with %llu points.", + raw_n + ); +#endif + + // Fingers + for (i = 0; i < raw_n; i++) { + + UCHAR *f_base = Buffer + headerSize + DeviceContext->DeviceInfo->tp_delta; + f = (const struct TRACKPAD_FINGER*) (f_base + i * fingerprintSize); + + // Translate X and Y + x = (AmtRawToInteger(f->abs_x) - DeviceContext->DeviceInfo->x.min) > 0 ? + ((USHORT)(AmtRawToInteger(f->abs_x) - DeviceContext->DeviceInfo->x.min)) : 0; + y = (DeviceContext->DeviceInfo->y.max - AmtRawToInteger(f->abs_y)) > 0 ? + ((USHORT)(DeviceContext->DeviceInfo->y.max - AmtRawToInteger(f->abs_y))) : 0; + + // Defuzz functions remain the same + // TODO: Implement defuzz later + PtpReport.Contacts[i].ContactID = (UCHAR) i; + PtpReport.Contacts[i].X = x; + PtpReport.Contacts[i].Y = y; + PtpReport.Contacts[i].TipSwitch = (AmtRawToInteger(f->touch_major) << 1) >= 200; + PtpReport.Contacts[i].Confidence = (AmtRawToInteger(f->touch_minor) << 1) > 0; + +#ifdef INPUT_CONTENT_TRACE + TraceEvents( + TRACE_LEVEL_INFORMATION, + TRACE_INPUT, + "%!FUNC!: Point %llu, X = %d, Y = %d, TipSwitch = %d, Confidence = %d, tMajor = %d, tMinor = %d, origin = %d, PTP Origin = %d", + i, + PtpReport.Contacts[i].X, + PtpReport.Contacts[i].Y, + PtpReport.Contacts[i].TipSwitch, + PtpReport.Contacts[i].Confidence, + AmtRawToInteger(f->touch_major) << 1, + AmtRawToInteger(f->touch_minor) << 1, + AmtRawToInteger(f->origin), + (UCHAR) i + ); +#endif + } + } + + // Type 2 touchpad contains integrated trackpad buttons + if (DeviceContext->IsButtonReportOn) { + // Handles trackpad button input here. + if (Buffer[DeviceContext->DeviceInfo->tp_button]) { + PtpReport.IsButtonClicked = TRUE; + } + } + + // Compose final report and write it back + Status = WdfMemoryCopyFromBuffer( + RequestMemory, + 0, + (PVOID) &PtpReport, + sizeof(PTP_REPORT) + ); + + if (!NT_SUCCESS(Status)) { + TraceEvents( + TRACE_LEVEL_ERROR, + TRACE_DRIVER, + "%!FUNC! WdfMemoryCopyFromBuffer failed with %!STATUS!", + Status + ); + goto exit; + } + + // Set result + WdfRequestSetInformation( + Request, + sizeof(PTP_REPORT) + ); + + // Set completion flag + WdfRequestComplete( + Request, + Status + ); + +exit: + TraceEvents( + TRACE_LEVEL_INFORMATION, + TRACE_DRIVER, + "%!FUNC! Exit" + ); + return Status; + +} + +_IRQL_requires_(PASSIVE_LEVEL) +NTSTATUS +AmtPtpServiceTouchInputInterruptType5( + _In_ PDEVICE_CONTEXT DeviceContext, + _In_ UCHAR* Buffer, + _In_ size_t NumBytesTransferred +) +{ + + NTSTATUS Status; + WDFREQUEST Request; + WDFMEMORY RequestMemory; + PTP_REPORT PtpReport; + + const struct TRACKPAD_FINGER_TYPE5* f; + const struct TRACKPAD_REPORT_TYPE5* mt_report; + const struct TRACKPAD_COMBINED_REPORT_TYPE5* full_report; + + TraceEvents( + TRACE_LEVEL_INFORMATION, + TRACE_DRIVER, + "%!FUNC! Entry" + ); + + Status = STATUS_SUCCESS; + PtpReport.ReportID = REPORTID_MULTITOUCH; + PtpReport.IsButtonClicked = 0; + + UINT timestamp; + INT x, y = 0; + size_t raw_n, i = 0; + + Status = WdfIoQueueRetrieveNextRequest( + DeviceContext->InputQueue, + &Request + ); + + if (!NT_SUCCESS(Status)) { + TraceEvents( + TRACE_LEVEL_INFORMATION, + TRACE_DRIVER, + "%!FUNC! No pending PTP request. Interrupt disposed" + ); + goto exit; + } + + Status = WdfRequestRetrieveOutputMemory( + Request, + &RequestMemory + ); + if (!NT_SUCCESS(Status)) { + TraceEvents( + TRACE_LEVEL_ERROR, + TRACE_DRIVER, + "%!FUNC! WdfRequestRetrieveOutputBuffer failed with %!STATUS!", + Status + ); + goto exit; + } + + full_report = (const struct TRACKPAD_COMBINED_REPORT_TYPE5 *) Buffer; + mt_report = &full_report->MTReport; + + timestamp = (mt_report->TimestampHigh << 5) | mt_report->TimestampLow; + PtpReport.ScanTime = (USHORT) timestamp * 10; + PtpReport.IsButtonClicked = (UCHAR) mt_report->Button; + + if (!DeviceContext->PrevPtpReportAuxAndSettingsInited) + { + DeviceContext->PrevPtpReportAuxAndSettingsInited = TRUE; + DeviceContext->PrevPtpReportAux1.Id = (UINT32)-1; + DeviceContext->PrevPtpReportAux2.Id = (UINT32)-1; + DeviceContext->ButtonDisabled = ReadSettingValue(L"ButtonDisabled", 0) ? TRUE : FALSE; + DeviceContext->StopPressure = ReadSettingValue(L"StopPressure", 0); + DeviceContext->StopSize = ReadSettingValue(L"StopSize", 0xffffffff); + DeviceContext->IgnoreButtonFinger = ReadSettingValue(L"IgnoreButtonFinger", 1) ? TRUE : FALSE; + DeviceContext->IgnoreNearFingers = ReadSettingValue(L"IgnoreNearFingers", 1) ? TRUE : FALSE; + DeviceContext->PalmRejection = ReadSettingValue(L"PalmRejection", 0) ? TRUE : FALSE; + } + + if (DeviceContext->ButtonDisabled) + PtpReport.IsButtonClicked = 0; + + // Type 5 finger report + if (DeviceContext->IsSurfaceReportOn) { + raw_n = (NumBytesTransferred - sizeof(struct TRACKPAD_REPORT_TYPE5)) / sizeof(struct TRACKPAD_FINGER_TYPE5); + if (raw_n >= PTP_MAX_CONTACT_POINTS) raw_n = PTP_MAX_CONTACT_POINTS; + PtpReport.ContactCount = (UCHAR)raw_n; + +#ifdef INPUT_CONTENT_TRACE + TraceEvents( + TRACE_LEVEL_INFORMATION, + TRACE_DRIVER, + "%!FUNC! with %llu points.", + raw_n + ); +#endif + + // Fingers to array + for (i = 0; i < raw_n; i++) { + f = &mt_report->Fingers[i]; + + // Sign extend + x = (SHORT) (f->AbsoluteX << 3) >> 3; + y = -(SHORT) (f->AbsoluteY << 3) >> 3; + + x = (x - DeviceContext->DeviceInfo->x.min) > 0 ? (x - DeviceContext->DeviceInfo->x.min) : 0; + y = (y - DeviceContext->DeviceInfo->y.min) > 0 ? (y - DeviceContext->DeviceInfo->y.min) : 0; + + PtpReport.Contacts[i].ContactID = f->Id; + // 0x1 = Transition between states + // 0x2 = Floating finger + // 0x4 = Contact/Valid + // I've gotten 0x6 if I press on the trackpad and then keep my finger close + // Note: These values come from my MBP9,2. These also are valid on my MT2 + PtpReport.Contacts[i].TipSwitch = (f->State & 0x4) && (DeviceContext->IgnoreNearFingers == FALSE ? TRUE : !(f->State & 0x2)); + + // The Microsoft spec says reject any input larger than 25mm. This is not ideal + // for Magic Trackpad 2 - so we raised the threshold a bit higher. + // Or maybe I used the wrong unit? IDK + // BOOL valid_size = (AmtRawToInteger(f->TouchMinor) << 1) < 345 && + // (AmtRawToInteger(f->TouchMinor) << 1) < 345; + + // 1 = thumb, 2 = index, etc etc + // 6 = palm on MT2, 7 = palm on my MBP9,2 (why are these different?) + // BOOL valid_finger = f->Finger != 6; + PtpReport.Contacts[i].Confidence = DeviceContext->PalmRejection == FALSE ? TRUE : f->Finger != 6; // valid_size && valid_finger; + + PPTP_REPORT_AUX prev_contact = NULL; + if ( + (DeviceContext->IgnoreButtonFinger == FALSE ? TRUE : (!DeviceContext->PrevIsButtonClicked || !PtpReport.IsButtonClicked)) && + (DeviceContext->StopPressure == 0xffffffff ? TRUE : f->Pressure > DeviceContext->StopPressure) && + (DeviceContext->StopSize == 0xffffffff ? TRUE : f->Size > DeviceContext->StopSize) + ) + { + PPTP_REPORT_AUX contact; + + if (DeviceContext->PrevPtpReportAux1.Id == f->Id) + contact = &DeviceContext->PrevPtpReportAux1; + else if (DeviceContext->PrevPtpReportAux2.Id == f->Id) + contact = &DeviceContext->PrevPtpReportAux2; + else if (!DeviceContext->PrevPtpReportAux1.TipSwitch) + contact = &DeviceContext->PrevPtpReportAux1; + else + contact = &DeviceContext->PrevPtpReportAux2; + + contact->X = (USHORT)x; + contact->Y = (USHORT)y; + contact->Id = f->Id; + contact->TipSwitch = PtpReport.Contacts[i].TipSwitch; + } + else + { + size_t j; + for (j = 0; j < 2; j++) + { + PPTP_REPORT_AUX contact = !j ? &DeviceContext->PrevPtpReportAux1 : &DeviceContext->PrevPtpReportAux2; + + if (contact->Id == f->Id) + { + contact->TipSwitch = PtpReport.Contacts[i].TipSwitch; + + if (contact->TipSwitch) + prev_contact = contact; + else + contact->Id = (UINT32)-1; + } + } + } + PtpReport.Contacts[i].X = prev_contact ? prev_contact->X : (USHORT)x; + PtpReport.Contacts[i].Y = prev_contact ? prev_contact->Y : (USHORT)y; + +//#ifdef INPUT_CONTENT_TRACE + TraceEvents( + TRACE_LEVEL_INFORMATION, + TRACE_INPUT, + "%!FUNC!: Point %llu, ContactID = %lu, X = %d, Y = %d, TipSwitch = %d, Confidence = %d, tMajor = %d, tMinor = %d, finger type = %d, rotate = %d, pressure = %d, size = %d", + i, + PtpReport.Contacts[i].ContactID, + PtpReport.Contacts[i].X, + PtpReport.Contacts[i].Y, + PtpReport.Contacts[i].TipSwitch, + PtpReport.Contacts[i].Confidence, + AmtRawToInteger(f->TouchMajor) << 1, + AmtRawToInteger(f->TouchMinor) << 1, + f->Finger, + f->Orientation, + f->Pressure, + f->Size + ); +//#endif + } + } + + DeviceContext->PrevIsButtonClicked = PtpReport.IsButtonClicked; + + // Write output + Status = WdfMemoryCopyFromBuffer( + RequestMemory, + 0, + (PVOID) &PtpReport, + sizeof(PTP_REPORT) + ); + + if (!NT_SUCCESS(Status)) { + TraceEvents( + TRACE_LEVEL_ERROR, + TRACE_DRIVER, + "%!FUNC! WdfMemoryCopyFromBuffer failed with %!STATUS!", + Status + ); + goto exit; + } + + // Set result + WdfRequestSetInformation( + Request, + sizeof(PTP_REPORT) + ); + + // Set completion flag + WdfRequestComplete( + Request, + Status + ); + +exit: + TraceEvents( + TRACE_LEVEL_INFORMATION, + TRACE_DRIVER, + "%!FUNC! Exit" + ); + return Status; + +} + +// Helper function for numberic operation +static inline INT AmtRawToInteger( + _In_ USHORT x +) +{ + return (signed short) x; +} diff --git a/AmtPtpDeviceUsbUm/MagicTrackpad2PtpDevice.sln b/AmtPtpDeviceUsbUm/MagicTrackpad2PtpDevice.sln new file mode 100644 index 0000000..9deb276 --- /dev/null +++ b/AmtPtpDeviceUsbUm/MagicTrackpad2PtpDevice.sln @@ -0,0 +1,43 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.33801.447 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "AmtPtpDeviceUsbUm", "MagicTrackpad2PtpDevice.vcxproj", "{87EFA31B-25EB-4944-A30A-300171BFFF57}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|ARM64 = Debug|ARM64 + Debug|x64 = Debug|x64 + Release|ARM64 = Release|ARM64 + Release|x64 = Release|x64 + ReleaseSigned|ARM64 = ReleaseSigned|ARM64 + ReleaseSigned|x64 = ReleaseSigned|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {87EFA31B-25EB-4944-A30A-300171BFFF57}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {87EFA31B-25EB-4944-A30A-300171BFFF57}.Debug|ARM64.Build.0 = Debug|ARM64 + {87EFA31B-25EB-4944-A30A-300171BFFF57}.Debug|ARM64.Deploy.0 = Debug|ARM64 + {87EFA31B-25EB-4944-A30A-300171BFFF57}.Debug|x64.ActiveCfg = Debug|x64 + {87EFA31B-25EB-4944-A30A-300171BFFF57}.Debug|x64.Build.0 = Debug|x64 + {87EFA31B-25EB-4944-A30A-300171BFFF57}.Debug|x64.Deploy.0 = Debug|x64 + {87EFA31B-25EB-4944-A30A-300171BFFF57}.Release|ARM64.ActiveCfg = Release|ARM64 + {87EFA31B-25EB-4944-A30A-300171BFFF57}.Release|ARM64.Build.0 = Release|ARM64 + {87EFA31B-25EB-4944-A30A-300171BFFF57}.Release|ARM64.Deploy.0 = Release|ARM64 + {87EFA31B-25EB-4944-A30A-300171BFFF57}.Release|x64.ActiveCfg = Release|x64 + {87EFA31B-25EB-4944-A30A-300171BFFF57}.Release|x64.Build.0 = Release|x64 + {87EFA31B-25EB-4944-A30A-300171BFFF57}.Release|x64.Deploy.0 = Release|x64 + {87EFA31B-25EB-4944-A30A-300171BFFF57}.ReleaseSigned|ARM64.ActiveCfg = ReleaseSigned|ARM64 + {87EFA31B-25EB-4944-A30A-300171BFFF57}.ReleaseSigned|ARM64.Build.0 = ReleaseSigned|ARM64 + {87EFA31B-25EB-4944-A30A-300171BFFF57}.ReleaseSigned|ARM64.Deploy.0 = ReleaseSigned|ARM64 + {87EFA31B-25EB-4944-A30A-300171BFFF57}.ReleaseSigned|x64.ActiveCfg = ReleaseSigned|x64 + {87EFA31B-25EB-4944-A30A-300171BFFF57}.ReleaseSigned|x64.Build.0 = ReleaseSigned|x64 + {87EFA31B-25EB-4944-A30A-300171BFFF57}.ReleaseSigned|x64.Deploy.0 = ReleaseSigned|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {9DB74DD3-FF04-422C-A220-CD209446BCA8} + EndGlobalSection +EndGlobal diff --git a/AmtPtpDeviceUsbUm/MagicTrackpad2PtpDevice.vcxproj b/AmtPtpDeviceUsbUm/MagicTrackpad2PtpDevice.vcxproj new file mode 100644 index 0000000..fad6741 --- /dev/null +++ b/AmtPtpDeviceUsbUm/MagicTrackpad2PtpDevice.vcxproj @@ -0,0 +1,240 @@ + + + + + ReleaseSigned + ARM64 + + + ReleaseSigned + x64 + + + Debug + x64 + + + Release + x64 + + + Debug + ARM64 + + + Release + ARM64 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {87EFA31B-25EB-4944-A30A-300171BFFF57} + {9181db3b-298d-4e39-a572-55bca4e4ac89} + v4.5 + 12.0 + Debug + Win32 + MagicTrackpad2PtpDevice + AmtPtpDeviceUsbUm + + + + $(LatestTargetPlatformVersion) + + + 10.0.19041.0 + + + + Windows10 + true + WindowsUserModeDriver10.0 + DynamicLibrary + 2 + Universal + + + Windows10 + false + WindowsUserModeDriver10.0 + DynamicLibrary + 2 + Universal + + + Windows10 + false + WindowsUserModeDriver10.0 + DynamicLibrary + 2 + Universal + + + Windows10 + true + WindowsUserModeDriver10.0 + DynamicLibrary + 2 + Universal + + + Windows10 + false + WindowsUserModeDriver10.0 + DynamicLibrary + 2 + Universal + + + Windows10 + false + WindowsUserModeDriver10.0 + DynamicLibrary + 2 + Universal + + + + + + + + + + + DbgengRemoteDebugger + $(SolutionDir)build\$(ProjectName)\$(Platform)\$(ConfigurationName)\ + $(DDK_INC_PATH);$(SolutionDir)intermediate\$(Platform)\$(ConfigurationName)\;$(ProjectDir)include;$(IncludePath) + $(SolutionDir)intermediate\$(ProjectName)\$(Platform)\$(ConfigurationName)\ + http://timestamp.digicert.com + + + DbgengRemoteDebugger + $(SolutionDir)build\$(ProjectName)\$(Platform)\$(ConfigurationName)\ + $(DDK_INC_PATH);$(SolutionDir)intermediate\$(Platform)\$(ConfigurationName)\;$(ProjectDir)include;$(IncludePath) + $(SolutionDir)intermediate\$(ProjectName)\$(Platform)\$(ConfigurationName)\ + http://timestamp.digicert.com + + + DbgengRemoteDebugger + $(SolutionDir)build\$(ProjectName)\$(Platform)\$(ConfigurationName)\ + $(DDK_INC_PATH);$(SolutionDir)intermediate\$(Platform)\$(ConfigurationName)\;$(ProjectDir)include;$(IncludePath) + $(SolutionDir)intermediate\$(ProjectName)\$(Platform)\$(ConfigurationName)\ + + + DbgengRemoteDebugger + $(SolutionDir)build\$(ProjectName)\$(Platform)\$(ConfigurationName)\ + $(DDK_INC_PATH);$(SolutionDir)intermediate\$(Platform)\$(ConfigurationName)\;$(ProjectDir)include;$(IncludePath) + $(SolutionDir)intermediate\$(ProjectName)\$(Platform)\$(ConfigurationName)\ + http://timestamp.digicert.com + + + DbgengRemoteDebugger + $(SolutionDir)build\$(ProjectName)\$(Platform)\$(ConfigurationName)\ + $(DDK_INC_PATH);$(SolutionDir)intermediate\$(Platform)\$(ConfigurationName)\;$(ProjectDir)include;$(IncludePath) + $(SolutionDir)intermediate\$(ProjectName)\$(Platform)\$(ConfigurationName)\ + http://timestamp.digicert.com + + + DbgengRemoteDebugger + $(SolutionDir)build\$(ProjectName)\$(Platform)\$(ConfigurationName)\ + $(DDK_INC_PATH);$(SolutionDir)intermediate\$(Platform)\$(ConfigurationName)\;$(ProjectDir)include;$(IncludePath) + $(SolutionDir)intermediate\$(ProjectName)\$(Platform)\$(ConfigurationName)\ + + + + http://timestamp.digicert.com + $(ProductionCertPath) + ProductionSign + Off + + + + true + true + include\trace.h + + + sha256 + + + + + true + true + include\trace.h + + + sha256 + + + + + true + true + include\trace.h + + + sha256 + + + + + true + true + include\trace.h + + + + + true + true + include\trace.h + + + + + true + true + include\trace.h + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/AmtPtpDeviceUsbUm/MagicTrackpad2PtpDevice.vcxproj.filters b/AmtPtpDeviceUsbUm/MagicTrackpad2PtpDevice.vcxproj.filters new file mode 100644 index 0000000..a17aa32 --- /dev/null +++ b/AmtPtpDeviceUsbUm/MagicTrackpad2PtpDevice.vcxproj.filters @@ -0,0 +1,101 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + {8E41214B-6785-4CFE-B992-037D68949A14} + inf;inv;inx;mof;mc; + + + {d3b4239b-c0e3-4d91-a6ee-730df7a15240} + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Device Specific Metadata Files + + + Device Specific Metadata Files + + + Device Specific Metadata Files + + + Device Specific Metadata Files + + + Device Specific Metadata Files + + + Device Specific Metadata Files + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + + + Resource Files + + + + + Resource Files + + + \ No newline at end of file diff --git a/AmtPtpDeviceUsbUm/Queue.c b/AmtPtpDeviceUsbUm/Queue.c new file mode 100644 index 0000000..f5ae8ff --- /dev/null +++ b/AmtPtpDeviceUsbUm/Queue.c @@ -0,0 +1,302 @@ +// Queue.c: This file contains the queue entry points and callbacks. + +#include +#include "queue.tmh" + +NTSTATUS +AmtPtpDeviceQueueInitialize( + _In_ WDFDEVICE Device +) +{ + + WDFQUEUE queue; + NTSTATUS status; + WDF_IO_QUEUE_CONFIG queueConfig; + PDEVICE_CONTEXT deviceContext; + + deviceContext = DeviceGetContext(Device); + + // + // Configure a default queue so that requests that are not + // configure-fowarded using WdfDeviceConfigureRequestDispatching to goto + // other queues get dispatched here. + // + WDF_IO_QUEUE_CONFIG_INIT_DEFAULT_QUEUE( + &queueConfig, + WdfIoQueueDispatchParallel + ); + + queueConfig.EvtIoDeviceControl = AmtPtpDeviceEvtIoDeviceControl; + queueConfig.EvtIoStop = AmtPtpDeviceEvtIoStop; + + status = WdfIoQueueCreate( + Device, + &queueConfig, + WDF_NO_OBJECT_ATTRIBUTES, + &queue + ); + + if (!NT_SUCCESS(status)) { + TraceEvents( + TRACE_LEVEL_ERROR, + TRACE_QUEUE, + "%!FUNC! WdfIoQueueCreate (Primary) failed %!STATUS!", + status + ); + return status; + } + + // + // Create secondary queues for touch and mouse read requests. + // + + WDF_IO_QUEUE_CONFIG_INIT(&queueConfig, WdfIoQueueDispatchManual); + queueConfig.PowerManaged = WdfFalse; + + status = WdfIoQueueCreate( + Device, + &queueConfig, + WDF_NO_OBJECT_ATTRIBUTES, + &deviceContext->InputQueue + ); + + if (!NT_SUCCESS(status)) { + TraceEvents( + TRACE_LEVEL_ERROR, + TRACE_QUEUE, + "%!FUNC! WdfIoQueueCreate (Input) failed %!STATUS!", + status + ); + return status; + } + + return status; + +} + +_IRQL_requires_(PASSIVE_LEVEL) +PCHAR +DbgIoControlGetString( + _In_ ULONG IoControlCode +) +{ + + switch (IoControlCode) + { + case IOCTL_HID_GET_DEVICE_DESCRIPTOR: + return "IOCTL_HID_GET_DEVICE_DESCRIPTOR"; + case IOCTL_HID_GET_DEVICE_ATTRIBUTES: + return "IOCTL_HID_GET_DEVICE_ATTRIBUTES"; + case IOCTL_HID_GET_REPORT_DESCRIPTOR: + return "IOCTL_HID_GET_REPORT_DESCRIPTOR"; + case IOCTL_HID_GET_STRING: + return "IOCTL_HID_GET_STRING"; + case IOCTL_HID_READ_REPORT: + return "IOCTL_HID_READ_REPORT"; + case IOCTL_HID_WRITE_REPORT: + return "IOCTL_HID_WRITE_REPORT"; + case IOCTL_UMDF_HID_GET_INPUT_REPORT: + return "IOCTL_UMDF_HID_GET_INPUT_REPORT"; + case IOCTL_UMDF_HID_SET_OUTPUT_REPORT: + return "IOCTL_UMDF_HID_SET_OUTPUT_REPORT"; + case IOCTL_UMDF_HID_GET_FEATURE: + return "IOCTL_UMDF_HID_GET_FEATURE"; + case IOCTL_UMDF_HID_SET_FEATURE: + return "IOCTL_UMDF_HID_SET_FEATURE"; + case IOCTL_HID_ACTIVATE_DEVICE: + return "IOCTL_HID_ACTIVATE_DEVICE"; + case IOCTL_HID_DEACTIVATE_DEVICE: + return "IOCTL_HID_DEACTIVATE_DEVICE"; + case IOCTL_HID_SEND_IDLE_NOTIFICATION_REQUEST: + return "IOCTL_HID_SEND_IDLE_NOTIFICATION_REQUEST"; + default: + return "IOCTL_UNKNOWN"; + } + +} + +VOID +AmtPtpDeviceEvtIoDeviceControl( + _In_ WDFQUEUE Queue, + _In_ WDFREQUEST Request, + _In_ size_t OutputBufferLength, + _In_ size_t InputBufferLength, + _In_ ULONG IoControlCode +) +{ + + NTSTATUS status = STATUS_SUCCESS; + WDFDEVICE device = WdfIoQueueGetDevice(Queue); + BOOLEAN requestPending = FALSE; + + TraceEvents( + TRACE_LEVEL_INFORMATION, + TRACE_QUEUE, + "%!FUNC!: Queue 0x%p, Request 0x%p OutputBufferLength %d InputBufferLength %d IoControlCode %d", + Queue, + Request, + (int) OutputBufferLength, + (int) InputBufferLength, + IoControlCode + ); + + switch (IoControlCode) + { + case IOCTL_HID_GET_DEVICE_DESCRIPTOR: + status = AmtPtpGetHidDescriptor( + device, + Request + ); + break; + case IOCTL_HID_GET_DEVICE_ATTRIBUTES: + status = AmtPtpGetDeviceAttribs( + device, + Request + ); + break; + case IOCTL_HID_GET_REPORT_DESCRIPTOR: + status = AmtPtpGetReportDescriptor( + device, + Request + ); + break; + case IOCTL_HID_GET_STRING: + status = AmtPtpGetStrings( + device, + Request + ); + break; + case IOCTL_HID_READ_REPORT: + status = AmtPtpDispatchReadReportRequests( + device, + Request, + &requestPending + ); + break; + case IOCTL_UMDF_HID_GET_FEATURE: + status = AmtPtpReportFeatures( + device, + Request + ); + break; + case IOCTL_UMDF_HID_SET_FEATURE: + status = AmtPtpSetFeatures( + device, + Request + ); + break; + case IOCTL_HID_WRITE_REPORT: + case IOCTL_UMDF_HID_SET_OUTPUT_REPORT: + case IOCTL_UMDF_HID_GET_INPUT_REPORT: + case IOCTL_HID_ACTIVATE_DEVICE: + case IOCTL_HID_DEACTIVATE_DEVICE: + case IOCTL_HID_SEND_IDLE_NOTIFICATION_REQUEST: + default: + status = STATUS_NOT_SUPPORTED; + TraceEvents( + TRACE_LEVEL_WARNING, + TRACE_QUEUE, + "%!FUNC!: %s is not yet implemented", + DbgIoControlGetString(IoControlCode) + ); + break; + } + + if (requestPending != TRUE) { + WdfRequestComplete( + Request, + status + ); + } + + return; +} + +VOID +AmtPtpDeviceEvtIoStop( + _In_ WDFQUEUE Queue, + _In_ WDFREQUEST Request, + _In_ ULONG ActionFlags +) +{ + + TraceEvents( + TRACE_LEVEL_INFORMATION, + TRACE_QUEUE, + "%!FUNC! Queue 0x%p, Request 0x%p ActionFlags %d", + Queue, + Request, + ActionFlags + ); + + // + // In most cases, the EvtIoStop callback function completes, cancels, or postpones + // further processing of the I/O request. + // + // Typically, the driver uses the following rules: + // + // - If the driver owns the I/O request, it either postpones further processing + // of the request and calls WdfRequestStopAcknowledge, or it calls WdfRequestComplete + // with a completion status value of STATUS_SUCCESS or STATUS_CANCELLED. + // + // The driver must call WdfRequestComplete only once, to either complete or cancel + // the request. To ensure that another thread does not call WdfRequestComplete + // for the same request, the EvtIoStop callback must synchronize with the driver's + // other event callback functions, for instance by using interlocked operations. + // + // - If the driver has forwarded the I/O request to an I/O target, it either calls + // WdfRequestCancelSentRequest to attempt to cancel the request, or it postpones + // further processing of the request and calls WdfRequestStopAcknowledge. + // + // A driver might choose to take no action in EvtIoStop for requests that are + // guaranteed to complete in a small amount of time. For example, the driver might + // take no action for requests that are completed in one of the driver’s request handlers. + // + + return; + +} + +NTSTATUS +AmtPtpDispatchReadReportRequests( + _In_ WDFDEVICE Device, + _In_ WDFREQUEST Request, + _Out_ BOOLEAN *Pending +) +{ + + NTSTATUS status; + PDEVICE_CONTEXT devContext; + + status = STATUS_SUCCESS; + devContext = DeviceGetContext(Device); + + status = WdfRequestForwardToIoQueue( + Request, + devContext->InputQueue + ); + + + if (!NT_SUCCESS(status)) { + TraceEvents( + TRACE_LEVEL_ERROR, + TRACE_DRIVER, + "%!FUNC! WdfRequestForwardToIoQueue failed with %!STATUS!", + status + ); + return status; + } else { + TraceEvents( + TRACE_LEVEL_INFORMATION, + TRACE_DRIVER, + "%!FUNC! A report has been forwarded to input queue" + ); + } + + if (NULL != Pending) { + *Pending = TRUE; + } + + return status; + +} \ No newline at end of file diff --git a/AmtPtpDeviceUsbUm/Resource.rc b/AmtPtpDeviceUsbUm/Resource.rc new file mode 100644 index 0000000000000000000000000000000000000000..6c681213763d1a4b4ccdd9d48d6e7f792727e602 GIT binary patch literal 4792 zcmdUzT~As;5QgX0CjAemcvI5`@#CUiqKGj?G4jziX+o?DnpiY|)+YV!ZJ&3Sg*}`D zje0|~S$5B!nVs2pK6Va&ey`iQC3a-jc5VZ^wIRK0Bi7oi4ej2}?1ra3dq+#GXP4He z4_F^DUa&S`6yH6wbLI(8+U?rzpefpGdxeyBH?FeU=j{#D7ml`&;V;^i6&$rVR~=o~ z@oDd}omj(~*0PEnTHQK!YWu8Km>*lszOd5Z_sDixsWA`orNi>Ipj{XfI{=qijJu$8 zYu`El2y}!)lT&}@_mETXaZcf}&-x~P)*}D5!4HS8=S$u7ewZ(Yg{B+0Ov&+VePV+4( zKX0SaN4P8hT}`nzM2>QXZ;JW6{E_uK;kR5FBGx8o z7siD9Z-gau>-HSNXZDR8ayf}0QL5`sJ$0USz^sg?)$ldFN&DOiG!>{j(45dZP#@Ao z9nkb+0g5}Cq$N=5b~$(1>zcc)@3c|XaE_&|LR0X9| z3^}n*8`d?dw;hIUP&;L&O6C|WE@ecfRdAU zf}A?|w#etrC-Me~RpcsvH>j@4{C-4E$sTQsNUAw0$`9SieXLt!AOC5=Bh6PPx=(lV zJ8Vq&FY%Wl$StNXMfAG=B{jyTj&^?MZHGX~w#OXdEKO>}6SPaq4{sajybDe-O^`Vo z4~mpJs0xh&a;NcDMK5{(A@=X$1-`0X0f*T={93$^G%nc=eVd34d0ce)+$-3$j3zC2 zrULq?UQ}H5?I&J!#?vJl$Ttee_Zi`FJSO(ZQT8GoB`LjUI8TM0;u_z3c-Z^tQQvYF zAE4P0TKn&UY$ot$pQlqg+JY{Mi6W6vBp%ci4O1&*Pbf>No*FRf%V!q1GANGln-Rzg zg$MA{H%}3*Z)l75u0hvxyUI3qSVeicNA?@R=k1bxqdITT)m`&#rf4s@_+KQyK9(oT z>ukZAdWwDHC1+HYmj#v8M>*)dsP}2*Q+*=|H=ifGZ=}67t;_TLV_G*!Li#Bk z_ia!dOfa2|_-wqVb#20{NF8Dq)$nmdDZ~0);49{7PLDO3>Lx_u8b{B|byvpPNIS2w za`>Wexoj;K@3UO + +#define AAPL_WELLSPRING_3_PTP_FINGER_COLLECTION_1 \ + BEGIN_COLLECTION, 0x02, /* Begin Collection: Logical */ \ + /* Begin a byte */ \ + LOGICAL_MAXIMUM, 0x01, /* Logical Maximum: 1 */ \ + USAGE, 0x47, /* Usage: Confidence */ \ + USAGE, 0x42, /* Usage: Tip switch */ \ + REPORT_COUNT, 0x02, /* Report Count: 2 */ \ + REPORT_SIZE, 0x01, /* Report Size: 1 */ \ + INPUT, 0x02, /* Input: (Data, Var, Abs) */ \ + REPORT_SIZE, 0x01, /* Report Size: 1 */ \ + REPORT_COUNT, 0x06, /* Report Count: 6 */ \ + INPUT, 0x03, /* Input: (Const, Var, Abs) */ \ + /* End of a byte */ \ + /* Begin of 4 bytes */ \ + REPORT_COUNT, 0x01, /* Report Count: 1 */ \ + REPORT_SIZE, 0x20, /* Report Size: 0x20 (4 bytes) */ \ + LOGICAL_MAXIMUM_3, 0xff, 0xff, 0xff, 0xff, /* Logical Maximum: 0xffffffff */ \ + USAGE, 0x51, /* Usage: Contract Identifier */ \ + INPUT, 0x02, /* Input: (Data, Var, Abs) */ \ + /* End of 4 bytes */ \ + /* Begin of 4 bytes */ \ + /* Size is hard-coded at this moment */ \ + /* This hard-coded size is designed for MacBookAir 7,2 */ \ + USAGE_PAGE, 0x01, /* Usage Page: Generic Desktop */ \ + LOGICAL_MAXIMUM_2, 0x9a, 0x25, /* Logical Maximum: 9626 (See defintion) */ \ + REPORT_SIZE, 0x10, /* Report Size: 0x10 (2 bytes) */ \ + UNIT_EXPONENT, 0x0e, /* Unit exponent: -2 */ \ + UNIT, 0x11, /* Unit: SI Length (cm) */ \ + USAGE, 0x30, /* Usage: X */ \ + PHYSICAL_MAXIMUM_2, 0x1a, 0x04, /* Physical Maximum: 1050 (See Apple Spec) */ \ + REPORT_COUNT, 0x01, /* Report count: 1 */ \ + INPUT, 0x02, /* Input: (Data, Var, Abs) */ \ + PHYSICAL_MAXIMUM_2, 0xf8, 0x02, /* Physical Maximum: 760 (See Apple Spec) */ \ + LOGICAL_MAXIMUM_2, 0x77, 0x1a, /* Logical Maximum: 6775 (See definition) */ \ + USAGE, 0x31, /* Usage: Y */ \ + INPUT, 0x02, /* Input: (Data, Var, Abs) */ \ + PHYSICAL_MAXIMUM, 0x00, /* Physical Maximum: 0 */ \ + UNIT_EXPONENT, 0x00, /* Unit exponent: 0 */ \ + UNIT, 0x00, /* Unit: None */ \ + /* End of 4 bytes */ \ + END_COLLECTION /* End Collection */ \ + +#define AAPL_WELLSPRING_3_PTP_FINGER_COLLECTION_2 \ + BEGIN_COLLECTION, 0x02, /* Begin Collection: Logical */ \ + /* Begin a byte */ \ + LOGICAL_MAXIMUM, 0x01, /* Logical Maximum: 1 */ \ + USAGE, 0x47, /* Usage: Confidence */ \ + USAGE, 0x42, /* Usage: Tip switch */ \ + REPORT_COUNT, 0x02, /* Report Count: 2 */ \ + REPORT_SIZE, 0x01, /* Report Size: 1 */ \ + INPUT, 0x02, /* Input: (Data, Var, Abs) */ \ + REPORT_SIZE, 0x01, /* Report Size: 1 */ \ + REPORT_COUNT, 0x06, /* Report Count: 6 */ \ + INPUT, 0x03, /* Input: (Const, Var, Abs) */ \ + /* End of a byte */ \ + /* Begin of 4 bytes */ \ + REPORT_COUNT, 0x01, /* Report Count: 1 */ \ + REPORT_SIZE, 0x20, /* Report Size: 0x20 (4 bytes) */ \ + LOGICAL_MAXIMUM_3, 0xff, 0xff, 0xff, 0xff, /* Logical Maximum: 0xffffffff */ \ + USAGE, 0x51, /* Usage: Contract Identifier */ \ + INPUT, 0x02, /* Input: (Data, Var, Abs) */ \ + /* End of 4 bytes */ \ + /* Begin of 4 bytes */ \ + /* Size is hard-coded at this moment */ \ + USAGE_PAGE, 0x01, /* Usage Page: Generic Desktop */ \ + LOGICAL_MAXIMUM_2, 0x9a, 0x25, /* Logical Maximum: 9626 (See defintion) */ \ + REPORT_SIZE, 0x10, /* Report Size: 0x10 (2 bytes) */ \ + UNIT_EXPONENT, 0x0e, /* Unit exponent: -2 */ \ + UNIT, 0x11, /* Unit: SI Length (cm) */ \ + USAGE, 0x30, /* Usage: X */ \ + PHYSICAL_MAXIMUM_2, 0x1a, 0x04, /* Physical Maximum: 1050 (See Apple Spec) */ \ + REPORT_COUNT, 0x01, /* Report count: 1 */ \ + INPUT, 0x02, /* Input: (Data, Var, Abs) */ \ + PHYSICAL_MAXIMUM_2, 0xf8, 0x02, /* Physical Maximum: 760 (See Apple Spec) */ \ + LOGICAL_MAXIMUM_2, 0x77, 0x1a, /* Logical Maximum: 6775 (See definition) */ \ + USAGE, 0x31, /* Usage: Y */ \ + INPUT, 0x02, /* Input: (Data, Var, Abs) */ \ + /* End of 4 bytes */ \ + END_COLLECTION /* End Collection */ \ + +#define AAPL_WELLSPRING_3_PTP_TLC \ + USAGE_PAGE, 0x0d, /* Usage Page: Digitizer */ \ + USAGE, 0x05, /* Usage: Touch Pad */ \ + BEGIN_COLLECTION, 0x01, /* Begin Collection: Application */ \ + REPORT_ID, REPORTID_MULTITOUCH, /* Report ID: Multi-touch */ \ + USAGE, 0x22, /* Usage: Finger */ \ + AAPL_WELLSPRING_3_PTP_FINGER_COLLECTION_1, /* 1 */ \ + USAGE_PAGE, 0x0d, /* Usage Page: Digitizer */ \ + USAGE, 0x22, /* Usage: Finger */ \ + AAPL_WELLSPRING_3_PTP_FINGER_COLLECTION_1, /* 2 */ \ + USAGE_PAGE, 0x0d, /* Usage Page: Digitizer */ \ + USAGE, 0x22, /* Usage: Finger */ \ + AAPL_WELLSPRING_3_PTP_FINGER_COLLECTION_2, /* 3 */ \ + USAGE_PAGE, 0x0d, /* Usage Page: Digitizer */ \ + USAGE, 0x22, /* Usage: Finger */ \ + AAPL_WELLSPRING_3_PTP_FINGER_COLLECTION_1, /* 4 */ \ + USAGE_PAGE, 0x0d, /* Usage Page: Digitizer */ \ + USAGE, 0x22, /* Usage: Finger */ \ + AAPL_WELLSPRING_3_PTP_FINGER_COLLECTION_2, /* 5 */ \ + USAGE_PAGE, 0x0d, /* Usage Page: Digitizer */ \ + UNIT_EXPONENT, 0x0c, /* Unit exponent: -4 */ \ + UNIT_2, 0x01, 0x10, /* Time: Second */ \ + PHYSICAL_MAXIMUM_3, 0xff, 0xff, 0x00, 0x00, \ + LOGICAL_MAXIMUM_3, 0xff, 0xff, 0x00, 0x00, \ + USAGE, 0x56, /* Usage: Scan Time */ \ + INPUT, 0x02, /* Input: (Data, Var, Abs) */ \ + USAGE, 0x54, /* Usage: Contact Count */ \ + LOGICAL_MAXIMUM, 0x7f, \ + REPORT_SIZE, 0x08, \ + INPUT, 0x02, /* Input: (Data, Var, Abs) */ \ + USAGE_PAGE, 0x09, /* Usage Page: Button */ \ + USAGE, 0x01, /* Button 1 */ \ + LOGICAL_MAXIMUM, 0x01, \ + REPORT_SIZE, 0x01, \ + INPUT, 0x02, /* Input: (Data, Var, Abs) */ \ + REPORT_COUNT, 0x07, \ + INPUT, 0x03, /* Input: (Const, Var, Abs) */ \ + USAGE_PAGE, 0x0d, /* Usage Page: Digitizer */ \ + REPORT_ID, REPORTID_DEVICE_CAPS, \ + USAGE, 0x55, /* Usage: Maximum Contacts */ \ + USAGE, 0x59, /* Usage: Touchpad Button Type*/ \ + LOGICAL_MINIMUM, 0x00, \ + LOGICAL_MAXIMUM_2, 0xff, 0x00, \ + REPORT_SIZE, 0x08, \ + REPORT_COUNT, 0x02, \ + FEATURE, 0x02, \ + USAGE_PAGE_1, 0x00, 0xff, \ + REPORT_ID, REPORTID_PTPHQA, \ + USAGE, 0xc5, \ + LOGICAL_MINIMUM, 0x00, \ + LOGICAL_MAXIMUM_2, 0xff, 0x00, \ + REPORT_SIZE, 0x08, \ + REPORT_COUNT_2, 0x00, 0x01, \ + FEATURE, 0x02, \ + END_COLLECTION /* End Collection */ diff --git a/AmtPtpDeviceUsbUm/include/DeviceFamily/Wellspring5.h b/AmtPtpDeviceUsbUm/include/DeviceFamily/Wellspring5.h new file mode 100644 index 0000000..8e9a191 --- /dev/null +++ b/AmtPtpDeviceUsbUm/include/DeviceFamily/Wellspring5.h @@ -0,0 +1,139 @@ +#pragma once + +#include + +#define AAPL_WELLSPRING_5_PTP_FINGER_COLLECTION_1 \ + BEGIN_COLLECTION, 0x02, /* Begin Collection: Logical */ \ + /* Begin a byte */ \ + LOGICAL_MAXIMUM, 0x01, /* Logical Maximum: 1 */ \ + USAGE, 0x47, /* Usage: Confidence */ \ + USAGE, 0x42, /* Usage: Tip switch */ \ + REPORT_COUNT, 0x02, /* Report Count: 2 */ \ + REPORT_SIZE, 0x01, /* Report Size: 1 */ \ + INPUT, 0x02, /* Input: (Data, Var, Abs) */ \ + REPORT_SIZE, 0x01, /* Report Size: 1 */ \ + REPORT_COUNT, 0x06, /* Report Count: 6 */ \ + INPUT, 0x03, /* Input: (Const, Var, Abs) */ \ + /* End of a byte */ \ + /* Begin of 4 bytes */ \ + REPORT_COUNT, 0x01, /* Report Count: 1 */ \ + REPORT_SIZE, 0x20, /* Report Size: 0x20 (4 bytes) */ \ + LOGICAL_MAXIMUM_3, 0xff, 0xff, 0xff, 0xff, /* Logical Maximum: 0xffffffff */ \ + USAGE, 0x51, /* Usage: Contract Identifier */ \ + INPUT, 0x02, /* Input: (Data, Var, Abs) */ \ + /* End of 4 bytes */ \ + /* Begin of 4 bytes */ \ + /* Size is hard-coded at this moment */ \ + /* This hard-coded size is designed for MacBookAir 7,2 */ \ + USAGE_PAGE, 0x01, /* Usage Page: Generic Desktop */ \ + LOGICAL_MAXIMUM_2, 0xf9, 0x24, /* Logical Maximum: 9465 (See defintion) */ \ + REPORT_SIZE, 0x10, /* Report Size: 0x10 (2 bytes) */ \ + UNIT_EXPONENT, 0x0e, /* Unit exponent: -2 */ \ + UNIT, 0x11, /* Unit: SI Length (cm) */ \ + USAGE, 0x30, /* Usage: X */ \ + PHYSICAL_MAXIMUM_2, 0x1a, 0x04, /* Physical Maximum: 1050 (See Apple Spec) */ \ + REPORT_COUNT, 0x01, /* Report count: 1 */ \ + INPUT, 0x02, /* Input: (Data, Var, Abs) */ \ + PHYSICAL_MAXIMUM_2, 0x2a, 0x03, /* Physical Maximum: 810 (See Apple Spec) */ \ + LOGICAL_MAXIMUM_2, 0x4f, 0x1a, /* Logical Maximum: 6735 (See definition) */ \ + USAGE, 0x31, /* Usage: Y */ \ + INPUT, 0x02, /* Input: (Data, Var, Abs) */ \ + PHYSICAL_MAXIMUM, 0x00, /* Physical Maximum: 0 */ \ + UNIT_EXPONENT, 0x00, /* Unit exponent: 0 */ \ + UNIT, 0x00, /* Unit: None */ \ + /* End of 4 bytes */ \ + END_COLLECTION /* End Collection */ \ + +#define AAPL_WELLSPRING_5_PTP_FINGER_COLLECTION_2 \ + BEGIN_COLLECTION, 0x02, /* Begin Collection: Logical */ \ + /* Begin a byte */ \ + LOGICAL_MAXIMUM, 0x01, /* Logical Maximum: 1 */ \ + USAGE, 0x47, /* Usage: Confidence */ \ + USAGE, 0x42, /* Usage: Tip switch */ \ + REPORT_COUNT, 0x02, /* Report Count: 2 */ \ + REPORT_SIZE, 0x01, /* Report Size: 1 */ \ + INPUT, 0x02, /* Input: (Data, Var, Abs) */ \ + REPORT_SIZE, 0x01, /* Report Size: 1 */ \ + REPORT_COUNT, 0x06, /* Report Count: 6 */ \ + INPUT, 0x03, /* Input: (Const, Var, Abs) */ \ + /* End of a byte */ \ + /* Begin of 4 bytes */ \ + REPORT_COUNT, 0x01, /* Report Count: 1 */ \ + REPORT_SIZE, 0x20, /* Report Size: 0x20 (4 bytes) */ \ + LOGICAL_MAXIMUM_3, 0xff, 0xff, 0xff, 0xff, /* Logical Maximum: 0xffffffff */ \ + USAGE, 0x51, /* Usage: Contract Identifier */ \ + INPUT, 0x02, /* Input: (Data, Var, Abs) */ \ + /* End of 4 bytes */ \ + /* Begin of 4 bytes */ \ + /* Size is hard-coded at this moment */ \ + USAGE_PAGE, 0x01, /* Usage Page: Generic Desktop */ \ + LOGICAL_MAXIMUM_2, 0xf9, 0x24, /* Logical Maximum: 9465 (See defintion) */ \ + REPORT_SIZE, 0x10, /* Report Size: 0x10 (2 bytes) */ \ + UNIT_EXPONENT, 0x0e, /* Unit exponent: -2 */ \ + UNIT, 0x11, /* Unit: SI Length (cm) */ \ + USAGE, 0x30, /* Usage: X */ \ + PHYSICAL_MAXIMUM_2, 0x1a, 0x04, /* Physical Maximum: 1050 (See Apple Spec) */ \ + REPORT_COUNT, 0x01, /* Report count: 1 */ \ + INPUT, 0x02, /* Input: (Data, Var, Abs) */ \ + PHYSICAL_MAXIMUM_2, 0x2a, 0x03, /* Physical Maximum: 810 (See Apple Spec) */ \ + LOGICAL_MAXIMUM_2, 0x4f, 0x1a, /* Logical Maximum: 6735 (See definition) */ \ + USAGE, 0x31, /* Usage: Y */ \ + INPUT, 0x02, /* Input: (Data, Var, Abs) */ \ + /* End of 4 bytes */ \ + END_COLLECTION /* End Collection */ \ + +#define AAPL_WELLSPRING_5_PTP_TLC \ + USAGE_PAGE, 0x0d, /* Usage Page: Digitizer */ \ + USAGE, 0x05, /* Usage: Touch Pad */ \ + BEGIN_COLLECTION, 0x01, /* Begin Collection: Application */ \ + REPORT_ID, REPORTID_MULTITOUCH, /* Report ID: Multi-touch */ \ + USAGE, 0x22, /* Usage: Finger */ \ + AAPL_WELLSPRING_5_PTP_FINGER_COLLECTION_1, /* 1 */ \ + USAGE_PAGE, 0x0d, /* Usage Page: Digitizer */ \ + USAGE, 0x22, /* Usage: Finger */ \ + AAPL_WELLSPRING_5_PTP_FINGER_COLLECTION_1, /* 2 */ \ + USAGE_PAGE, 0x0d, /* Usage Page: Digitizer */ \ + USAGE, 0x22, /* Usage: Finger */ \ + AAPL_WELLSPRING_5_PTP_FINGER_COLLECTION_2, /* 3 */ \ + USAGE_PAGE, 0x0d, /* Usage Page: Digitizer */ \ + USAGE, 0x22, /* Usage: Finger */ \ + AAPL_WELLSPRING_5_PTP_FINGER_COLLECTION_1, /* 4 */ \ + USAGE_PAGE, 0x0d, /* Usage Page: Digitizer */ \ + USAGE, 0x22, /* Usage: Finger */ \ + AAPL_WELLSPRING_5_PTP_FINGER_COLLECTION_2, /* 5 */ \ + USAGE_PAGE, 0x0d, /* Usage Page: Digitizer */ \ + UNIT_EXPONENT, 0x0c, /* Unit exponent: -4 */ \ + UNIT_2, 0x01, 0x10, /* Time: Second */ \ + PHYSICAL_MAXIMUM_3, 0xff, 0xff, 0x00, 0x00, \ + LOGICAL_MAXIMUM_3, 0xff, 0xff, 0x00, 0x00, \ + USAGE, 0x56, /* Usage: Scan Time */ \ + INPUT, 0x02, /* Input: (Data, Var, Abs) */ \ + USAGE, 0x54, /* Usage: Contact Count */ \ + LOGICAL_MAXIMUM, 0x7f, \ + REPORT_SIZE, 0x08, \ + INPUT, 0x02, /* Input: (Data, Var, Abs) */ \ + USAGE_PAGE, 0x09, /* Usage Page: Button */ \ + USAGE, 0x01, /* Button 1 */ \ + LOGICAL_MAXIMUM, 0x01, \ + REPORT_SIZE, 0x01, \ + INPUT, 0x02, /* Input: (Data, Var, Abs) */ \ + REPORT_COUNT, 0x07, \ + INPUT, 0x03, /* Input: (Const, Var, Abs) */ \ + USAGE_PAGE, 0x0d, /* Usage Page: Digitizer */ \ + REPORT_ID, REPORTID_DEVICE_CAPS, \ + USAGE, 0x55, /* Usage: Maximum Contacts */ \ + USAGE, 0x59, /* Usage: Touchpad Button Type*/ \ + LOGICAL_MINIMUM, 0x00, \ + LOGICAL_MAXIMUM_2, 0xff, 0x00, \ + REPORT_SIZE, 0x08, \ + REPORT_COUNT, 0x02, \ + FEATURE, 0x02, \ + USAGE_PAGE_1, 0x00, 0xff, \ + REPORT_ID, REPORTID_PTPHQA, \ + USAGE, 0xc5, \ + LOGICAL_MINIMUM, 0x00, \ + LOGICAL_MAXIMUM_2, 0xff, 0x00, \ + REPORT_SIZE, 0x08, \ + REPORT_COUNT_2, 0x00, 0x01, \ + FEATURE, 0x02, \ + END_COLLECTION /* End Collection */ diff --git a/AmtPtpDeviceUsbUm/include/DeviceFamily/Wellspring6.h b/AmtPtpDeviceUsbUm/include/DeviceFamily/Wellspring6.h new file mode 100644 index 0000000..578e688 --- /dev/null +++ b/AmtPtpDeviceUsbUm/include/DeviceFamily/Wellspring6.h @@ -0,0 +1,139 @@ +#pragma once + +#include + +#define AAPL_WELLSPRING_6_PTP_FINGER_COLLECTION_1 \ + BEGIN_COLLECTION, 0x02, /* Begin Collection: Logical */ \ + /* Begin a byte */ \ + LOGICAL_MAXIMUM, 0x01, /* Logical Maximum: 1 */ \ + USAGE, 0x47, /* Usage: Confidence */ \ + USAGE, 0x42, /* Usage: Tip switch */ \ + REPORT_COUNT, 0x02, /* Report Count: 2 */ \ + REPORT_SIZE, 0x01, /* Report Size: 1 */ \ + INPUT, 0x02, /* Input: (Data, Var, Abs) */ \ + REPORT_SIZE, 0x01, /* Report Size: 1 */ \ + REPORT_COUNT, 0x06, /* Report Count: 6 */ \ + INPUT, 0x03, /* Input: (Const, Var, Abs) */ \ + /* End of a byte */ \ + /* Begin of 4 bytes */ \ + REPORT_COUNT, 0x01, /* Report Count: 1 */ \ + REPORT_SIZE, 0x20, /* Report Size: 0x20 (4 bytes) */ \ + LOGICAL_MAXIMUM_3, 0xff, 0xff, 0xff, 0xff, /* Logical Maximum: 0xffffffff */ \ + USAGE, 0x51, /* Usage: Contract Identifier */ \ + INPUT, 0x02, /* Input: (Data, Var, Abs) */ \ + /* End of 4 bytes */ \ + /* Begin of 4 bytes */ \ + /* Size is hard-coded at this moment */ \ + /* This hard-coded size is designed for MacBookAir 7,2 */ \ + USAGE_PAGE, 0x01, /* Usage Page: Generic Desktop */ \ + LOGICAL_MAXIMUM_2, 0x20, 0x26, /* Logical Maximum: 9760 (See defintion) */ \ + REPORT_SIZE, 0x10, /* Report Size: 0x10 (2 bytes) */ \ + UNIT_EXPONENT, 0x0e, /* Unit exponent: -2 */ \ + UNIT, 0x11, /* Unit: SI Length (cm) */ \ + USAGE, 0x30, /* Usage: X */ \ + PHYSICAL_MAXIMUM_2, 0x2b, 0x04, /* Physical Maximum: 1067 (See Apple Spec) */ \ + REPORT_COUNT, 0x01, /* Report count: 1 */ \ + INPUT, 0x02, /* Input: (Data, Var, Abs) */ \ + PHYSICAL_MAXIMUM_2, 0xfa, 0x02, /* Physical Maximum: 762 (See Apple Spec) */ \ + LOGICAL_MAXIMUM_2, 0x5e, 0x1a, /* Logical Maximum: 6750 (See definition) */ \ + USAGE, 0x31, /* Usage: Y */ \ + INPUT, 0x02, /* Input: (Data, Var, Abs) */ \ + PHYSICAL_MAXIMUM, 0x00, /* Physical Maximum: 0 */ \ + UNIT_EXPONENT, 0x00, /* Unit exponent: 0 */ \ + UNIT, 0x00, /* Unit: None */ \ + /* End of 4 bytes */ \ + END_COLLECTION /* End Collection */ \ + +#define AAPL_WELLSPRING_6_PTP_FINGER_COLLECTION_2 \ + BEGIN_COLLECTION, 0x02, /* Begin Collection: Logical */ \ + /* Begin a byte */ \ + LOGICAL_MAXIMUM, 0x01, /* Logical Maximum: 1 */ \ + USAGE, 0x47, /* Usage: Confidence */ \ + USAGE, 0x42, /* Usage: Tip switch */ \ + REPORT_COUNT, 0x02, /* Report Count: 2 */ \ + REPORT_SIZE, 0x01, /* Report Size: 1 */ \ + INPUT, 0x02, /* Input: (Data, Var, Abs) */ \ + REPORT_SIZE, 0x01, /* Report Size: 1 */ \ + REPORT_COUNT, 0x06, /* Report Count: 6 */ \ + INPUT, 0x03, /* Input: (Const, Var, Abs) */ \ + /* End of a byte */ \ + /* Begin of 4 bytes */ \ + REPORT_COUNT, 0x01, /* Report Count: 1 */ \ + REPORT_SIZE, 0x20, /* Report Size: 0x20 (4 bytes) */ \ + LOGICAL_MAXIMUM_3, 0xff, 0xff, 0xff, 0xff, /* Logical Maximum: 0xffffffff */ \ + USAGE, 0x51, /* Usage: Contract Identifier */ \ + INPUT, 0x02, /* Input: (Data, Var, Abs) */ \ + /* End of 4 bytes */ \ + /* Begin of 4 bytes */ \ + /* Size is hard-coded at this moment */ \ + USAGE_PAGE, 0x01, /* Usage Page: Generic Desktop */ \ + LOGICAL_MAXIMUM_2, 0x20, 0x26, /* Logical Maximum: 9760 (See defintion) */ \ + REPORT_SIZE, 0x10, /* Report Size: 0x10 (2 bytes) */ \ + UNIT_EXPONENT, 0x0e, /* Unit exponent: -2 */ \ + UNIT, 0x11, /* Unit: SI Length (cm) */ \ + USAGE, 0x30, /* Usage: X */ \ + PHYSICAL_MAXIMUM_2, 0x2b, 0x04, /* Physical Maximum: 1067 (See Apple Spec) */ \ + REPORT_COUNT, 0x01, /* Report count: 1 */ \ + INPUT, 0x02, /* Input: (Data, Var, Abs) */ \ + PHYSICAL_MAXIMUM_2, 0xfa, 0x02, /* Physical Maximum: 762 (See Apple Spec) */ \ + LOGICAL_MAXIMUM_2, 0x5e, 0x1a, /* Logical Maximum: 6750 (See definition) */ \ + USAGE, 0x31, /* Usage: Y */ \ + INPUT, 0x02, /* Input: (Data, Var, Abs) */ \ + /* End of 4 bytes */ \ + END_COLLECTION /* End Collection */ \ + +#define AAPL_WELLSPRING_6_PTP_TLC \ + USAGE_PAGE, 0x0d, /* Usage Page: Digitizer */ \ + USAGE, 0x05, /* Usage: Touch Pad */ \ + BEGIN_COLLECTION, 0x01, /* Begin Collection: Application */ \ + REPORT_ID, REPORTID_MULTITOUCH, /* Report ID: Multi-touch */ \ + USAGE, 0x22, /* Usage: Finger */ \ + AAPL_WELLSPRING_6_PTP_FINGER_COLLECTION_1, /* 1 */ \ + USAGE_PAGE, 0x0d, /* Usage Page: Digitizer */ \ + USAGE, 0x22, /* Usage: Finger */ \ + AAPL_WELLSPRING_6_PTP_FINGER_COLLECTION_1, /* 2 */ \ + USAGE_PAGE, 0x0d, /* Usage Page: Digitizer */ \ + USAGE, 0x22, /* Usage: Finger */ \ + AAPL_WELLSPRING_6_PTP_FINGER_COLLECTION_2, /* 3 */ \ + USAGE_PAGE, 0x0d, /* Usage Page: Digitizer */ \ + USAGE, 0x22, /* Usage: Finger */ \ + AAPL_WELLSPRING_6_PTP_FINGER_COLLECTION_1, /* 4 */ \ + USAGE_PAGE, 0x0d, /* Usage Page: Digitizer */ \ + USAGE, 0x22, /* Usage: Finger */ \ + AAPL_WELLSPRING_6_PTP_FINGER_COLLECTION_2, /* 5 */ \ + USAGE_PAGE, 0x0d, /* Usage Page: Digitizer */ \ + UNIT_EXPONENT, 0x0c, /* Unit exponent: -4 */ \ + UNIT_2, 0x01, 0x10, /* Time: Second */ \ + PHYSICAL_MAXIMUM_3, 0xff, 0xff, 0x00, 0x00, \ + LOGICAL_MAXIMUM_3, 0xff, 0xff, 0x00, 0x00, \ + USAGE, 0x56, /* Usage: Scan Time */ \ + INPUT, 0x02, /* Input: (Data, Var, Abs) */ \ + USAGE, 0x54, /* Usage: Contact Count */ \ + LOGICAL_MAXIMUM, 0x7f, \ + REPORT_SIZE, 0x08, \ + INPUT, 0x02, /* Input: (Data, Var, Abs) */ \ + USAGE_PAGE, 0x09, /* Usage Page: Button */ \ + USAGE, 0x01, /* Button 1 */ \ + LOGICAL_MAXIMUM, 0x01, \ + REPORT_SIZE, 0x01, \ + INPUT, 0x02, /* Input: (Data, Var, Abs) */ \ + REPORT_COUNT, 0x07, \ + INPUT, 0x03, /* Input: (Const, Var, Abs) */ \ + USAGE_PAGE, 0x0d, /* Usage Page: Digitizer */ \ + REPORT_ID, REPORTID_DEVICE_CAPS, \ + USAGE, 0x55, /* Usage: Maximum Contacts */ \ + USAGE, 0x59, /* Usage: Touchpad Button Type*/ \ + LOGICAL_MINIMUM, 0x00, \ + LOGICAL_MAXIMUM_2, 0xff, 0x00, \ + REPORT_SIZE, 0x08, \ + REPORT_COUNT, 0x02, \ + FEATURE, 0x02, \ + USAGE_PAGE_1, 0x00, 0xff, \ + REPORT_ID, REPORTID_PTPHQA, \ + USAGE, 0xc5, \ + LOGICAL_MINIMUM, 0x00, \ + LOGICAL_MAXIMUM_2, 0xff, 0x00, \ + REPORT_SIZE, 0x08, \ + REPORT_COUNT_2, 0x00, 0x01, \ + FEATURE, 0x02, \ + END_COLLECTION /* End Collection */ \ No newline at end of file diff --git a/AmtPtpDeviceUsbUm/include/DeviceFamily/Wellspring7A.h b/AmtPtpDeviceUsbUm/include/DeviceFamily/Wellspring7A.h new file mode 100644 index 0000000..fb43230 --- /dev/null +++ b/AmtPtpDeviceUsbUm/include/DeviceFamily/Wellspring7A.h @@ -0,0 +1,139 @@ +#pragma once + +#include + +#define AAPL_WELLSPRING_7A_PTP_FINGER_COLLECTION_1 \ + BEGIN_COLLECTION, 0x02, /* Begin Collection: Logical */ \ + /* Begin a byte */ \ + LOGICAL_MAXIMUM, 0x01, /* Logical Maximum: 1 */ \ + USAGE, 0x47, /* Usage: Confidence */ \ + USAGE, 0x42, /* Usage: Tip switch */ \ + REPORT_COUNT, 0x02, /* Report Count: 2 */ \ + REPORT_SIZE, 0x01, /* Report Size: 1 */ \ + INPUT, 0x02, /* Input: (Data, Var, Abs) */ \ + REPORT_SIZE, 0x01, /* Report Size: 1 */ \ + REPORT_COUNT, 0x06, /* Report Count: 6 */ \ + INPUT, 0x03, /* Input: (Const, Var, Abs) */ \ + /* End of a byte */ \ + /* Begin of 4 bytes */ \ + REPORT_COUNT, 0x01, /* Report Count: 1 */ \ + REPORT_SIZE, 0x20, /* Report Size: 0x20 (4 bytes) */ \ + LOGICAL_MAXIMUM_3, 0xff, 0xff, 0xff, 0xff, /* Logical Maximum: 0xffffffff */ \ + USAGE, 0x51, /* Usage: Contract Identifier */ \ + INPUT, 0x02, /* Input: (Data, Var, Abs) */ \ + /* End of 4 bytes */ \ + /* Begin of 4 bytes */ \ + /* Size is hard-coded at this moment */ \ + /* This hard-coded size is designed for MacBookPro 11,1 */ \ + USAGE_PAGE, 0x01, /* Usage Page: Generic Desktop */ \ + LOGICAL_MAXIMUM_2, 0x2e, 0x27, /* Logical Maximum: 10030 (See defintion) */ \ + REPORT_SIZE, 0x10, /* Report Size: 0x10 (2 bytes) */ \ + UNIT_EXPONENT, 0x0e, /* Unit exponent: -2 */ \ + UNIT, 0x11, /* Unit: SI Length (cm) */ \ + USAGE, 0x30, /* Usage: X */ \ + PHYSICAL_MAXIMUM_2, 0x28, 0x04, /* Physical Maximum: 1064 (See Apple Spec) */ \ + REPORT_COUNT, 0x01, /* Report count: 1 */ \ + INPUT, 0x02, /* Input: (Data, Var, Abs) */ \ + PHYSICAL_MAXIMUM_2, 0x05, 0x03, /* Physical Maximum: 773 (See Apple Spec) */ \ + LOGICAL_MAXIMUM_2, 0xe0, 0x1a, /* Logical Maximum: 6880 (See definition) */ \ + USAGE, 0x31, /* Usage: Y */ \ + INPUT, 0x02, /* Input: (Data, Var, Abs) */ \ + PHYSICAL_MAXIMUM, 0x00, /* Physical Maximum: 0 */ \ + UNIT_EXPONENT, 0x00, /* Unit exponent: 0 */ \ + UNIT, 0x00, /* Unit: None */ \ + /* End of 4 bytes */ \ + END_COLLECTION /* End Collection */ \ + +#define AAPL_WELLSPRING_7A_PTP_FINGER_COLLECTION_2 \ + BEGIN_COLLECTION, 0x02, /* Begin Collection: Logical */ \ + /* Begin a byte */ \ + LOGICAL_MAXIMUM, 0x01, /* Logical Maximum: 1 */ \ + USAGE, 0x47, /* Usage: Confidence */ \ + USAGE, 0x42, /* Usage: Tip switch */ \ + REPORT_COUNT, 0x02, /* Report Count: 2 */ \ + REPORT_SIZE, 0x01, /* Report Size: 1 */ \ + INPUT, 0x02, /* Input: (Data, Var, Abs) */ \ + REPORT_SIZE, 0x01, /* Report Size: 1 */ \ + REPORT_COUNT, 0x06, /* Report Count: 6 */ \ + INPUT, 0x03, /* Input: (Const, Var, Abs) */ \ + /* End of a byte */ \ + /* Begin of 4 bytes */ \ + REPORT_COUNT, 0x01, /* Report Count: 1 */ \ + REPORT_SIZE, 0x20, /* Report Size: 0x20 (4 bytes) */ \ + LOGICAL_MAXIMUM_3, 0xff, 0xff, 0xff, 0xff, /* Logical Maximum: 0xffffffff */ \ + USAGE, 0x51, /* Usage: Contract Identifier */ \ + INPUT, 0x02, /* Input: (Data, Var, Abs) */ \ + /* End of 4 bytes */ \ + /* Begin of 4 bytes */ \ + /* Size is hard-coded at this moment */ \ + USAGE_PAGE, 0x01, /* Usage Page: Generic Desktop */ \ + LOGICAL_MAXIMUM_2, 0x2e, 0x27, /* Logical Maximum: 10030 (See defintion) */ \ + REPORT_SIZE, 0x10, /* Report Size: 0x10 (2 bytes) */ \ + UNIT_EXPONENT, 0x0e, /* Unit exponent: -2 */ \ + UNIT, 0x11, /* Unit: SI Length (cm) */ \ + USAGE, 0x30, /* Usage: X */ \ + PHYSICAL_MAXIMUM_2, 0x28, 0x04, /* Physical Maximum: 1064 (See Apple Spec) */ \ + REPORT_COUNT, 0x01, /* Report count: 1 */ \ + INPUT, 0x02, /* Input: (Data, Var, Abs) */ \ + PHYSICAL_MAXIMUM_2, 0x05, 0x03, /* Physical Maximum: 773 (See Apple Spec) */ \ + LOGICAL_MAXIMUM_2, 0xe0, 0x1a, /* Logical Maximum: 6880 (See definition) */ \ + USAGE, 0x31, /* Usage: Y */ \ + INPUT, 0x02, /* Input: (Data, Var, Abs) */ \ + /* End of 4 bytes */ \ + END_COLLECTION /* End Collection */ \ + +#define AAPL_WELLSPRING_7A_PTP_TLC \ + USAGE_PAGE, 0x0d, /* Usage Page: Digitizer */ \ + USAGE, 0x05, /* Usage: Touch Pad */ \ + BEGIN_COLLECTION, 0x01, /* Begin Collection: Application */ \ + REPORT_ID, REPORTID_MULTITOUCH, /* Report ID: Multi-touch */ \ + USAGE, 0x22, /* Usage: Finger */ \ + AAPL_WELLSPRING_7A_PTP_FINGER_COLLECTION_1, /* 1 */ \ + USAGE_PAGE, 0x0d, /* Usage Page: Digitizer */ \ + USAGE, 0x22, /* Usage: Finger */ \ + AAPL_WELLSPRING_7A_PTP_FINGER_COLLECTION_1, /* 2 */ \ + USAGE_PAGE, 0x0d, /* Usage Page: Digitizer */ \ + USAGE, 0x22, /* Usage: Finger */ \ + AAPL_WELLSPRING_7A_PTP_FINGER_COLLECTION_2, /* 3 */ \ + USAGE_PAGE, 0x0d, /* Usage Page: Digitizer */ \ + USAGE, 0x22, /* Usage: Finger */ \ + AAPL_WELLSPRING_7A_PTP_FINGER_COLLECTION_1, /* 4 */ \ + USAGE_PAGE, 0x0d, /* Usage Page: Digitizer */ \ + USAGE, 0x22, /* Usage: Finger */ \ + AAPL_WELLSPRING_7A_PTP_FINGER_COLLECTION_2, /* 5 */ \ + USAGE_PAGE, 0x0d, /* Usage Page: Digitizer */ \ + UNIT_EXPONENT, 0x0c, /* Unit exponent: -4 */ \ + UNIT_2, 0x01, 0x10, /* Time: Second */ \ + PHYSICAL_MAXIMUM_3, 0xff, 0xff, 0x00, 0x00, \ + LOGICAL_MAXIMUM_3, 0xff, 0xff, 0x00, 0x00, \ + USAGE, 0x56, /* Usage: Scan Time */ \ + INPUT, 0x02, /* Input: (Data, Var, Abs) */ \ + USAGE, 0x54, /* Usage: Contact Count */ \ + LOGICAL_MAXIMUM, 0x7f, \ + REPORT_SIZE, 0x08, \ + INPUT, 0x02, /* Input: (Data, Var, Abs) */ \ + USAGE_PAGE, 0x09, /* Usage Page: Button */ \ + USAGE, 0x01, /* Button 1 */ \ + LOGICAL_MAXIMUM, 0x01, \ + REPORT_SIZE, 0x01, \ + INPUT, 0x02, /* Input: (Data, Var, Abs) */ \ + REPORT_COUNT, 0x07, \ + INPUT, 0x03, /* Input: (Const, Var, Abs) */ \ + USAGE_PAGE, 0x0d, /* Usage Page: Digitizer */ \ + REPORT_ID, REPORTID_DEVICE_CAPS, \ + USAGE, 0x55, /* Usage: Maximum Contacts */ \ + USAGE, 0x59, /* Usage: Touchpad Button Type*/ \ + LOGICAL_MINIMUM, 0x00, \ + LOGICAL_MAXIMUM_2, 0xff, 0x00, \ + REPORT_SIZE, 0x08, \ + REPORT_COUNT, 0x02, \ + FEATURE, 0x02, \ + USAGE_PAGE_1, 0x00, 0xff, \ + REPORT_ID, REPORTID_PTPHQA, \ + USAGE, 0xc5, \ + LOGICAL_MINIMUM, 0x00, \ + LOGICAL_MAXIMUM_2, 0xff, 0x00, \ + REPORT_SIZE, 0x08, \ + REPORT_COUNT_2, 0x00, 0x01, \ + FEATURE, 0x02, \ + END_COLLECTION /* End Collection */ diff --git a/AmtPtpDeviceUsbUm/include/DeviceFamily/Wellspring8.h b/AmtPtpDeviceUsbUm/include/DeviceFamily/Wellspring8.h new file mode 100644 index 0000000..9c6f897 --- /dev/null +++ b/AmtPtpDeviceUsbUm/include/DeviceFamily/Wellspring8.h @@ -0,0 +1,139 @@ +#pragma once + +#include + +#define AAPL_WELLSPRING_8_PTP_FINGER_COLLECTION_1 \ + BEGIN_COLLECTION, 0x02, /* Begin Collection: Logical */ \ + /* Begin a byte */ \ + LOGICAL_MAXIMUM, 0x01, /* Logical Maximum: 1 */ \ + USAGE, 0x47, /* Usage: Confidence */ \ + USAGE, 0x42, /* Usage: Tip switch */ \ + REPORT_COUNT, 0x02, /* Report Count: 2 */ \ + REPORT_SIZE, 0x01, /* Report Size: 1 */ \ + INPUT, 0x02, /* Input: (Data, Var, Abs) */ \ + REPORT_SIZE, 0x01, /* Report Size: 1 */ \ + REPORT_COUNT, 0x06, /* Report Count: 6 */ \ + INPUT, 0x03, /* Input: (Const, Var, Abs) */ \ + /* End of a byte */ \ + /* Begin of 4 bytes */ \ + REPORT_COUNT, 0x01, /* Report Count: 1 */ \ + REPORT_SIZE, 0x20, /* Report Size: 0x20 (4 bytes) */ \ + LOGICAL_MAXIMUM_3, 0xff, 0xff, 0xff, 0xff, /* Logical Maximum: 0xffffffff */ \ + USAGE, 0x51, /* Usage: Contract Identifier */ \ + INPUT, 0x02, /* Input: (Data, Var, Abs) */ \ + /* End of 4 bytes */ \ + /* Begin of 4 bytes */ \ + /* Size is hard-coded at this moment */ \ + /* This hard-coded size is designed for MacBookAir 7,2 */ \ + USAGE_PAGE, 0x01, /* Usage Page: Generic Desktop */ \ + LOGICAL_MAXIMUM_2, 0x20, 0x26, /* Logical Maximum: 9760 (See defintion) */ \ + REPORT_SIZE, 0x10, /* Report Size: 0x10 (2 bytes) */ \ + UNIT_EXPONENT, 0x0e, /* Unit exponent: -2 */ \ + UNIT, 0x11, /* Unit: SI Length (cm) */ \ + USAGE, 0x30, /* Usage: X */ \ + PHYSICAL_MAXIMUM_2, 0x15, 0x04, /* Physical Maximum: 1045 (See Apple Spec) */ \ + REPORT_COUNT, 0x01, /* Report count: 1 */ \ + INPUT, 0x02, /* Input: (Data, Var, Abs) */ \ + PHYSICAL_MAXIMUM_2, 0xee, 0x02, /* Physical Maximum: 750 (See Apple Spec) */ \ + LOGICAL_MAXIMUM_2, 0x5e, 0x1a, /* Logical Maximum: 6750 (See definition) */ \ + USAGE, 0x31, /* Usage: Y */ \ + INPUT, 0x02, /* Input: (Data, Var, Abs) */ \ + PHYSICAL_MAXIMUM, 0x00, /* Physical Maximum: 0 */ \ + UNIT_EXPONENT, 0x00, /* Unit exponent: 0 */ \ + UNIT, 0x00, /* Unit: None */ \ + /* End of 4 bytes */ \ + END_COLLECTION /* End Collection */ \ + +#define AAPL_WELLSPRING_8_PTP_FINGER_COLLECTION_2 \ + BEGIN_COLLECTION, 0x02, /* Begin Collection: Logical */ \ + /* Begin a byte */ \ + LOGICAL_MAXIMUM, 0x01, /* Logical Maximum: 1 */ \ + USAGE, 0x47, /* Usage: Confidence */ \ + USAGE, 0x42, /* Usage: Tip switch */ \ + REPORT_COUNT, 0x02, /* Report Count: 2 */ \ + REPORT_SIZE, 0x01, /* Report Size: 1 */ \ + INPUT, 0x02, /* Input: (Data, Var, Abs) */ \ + REPORT_SIZE, 0x01, /* Report Size: 1 */ \ + REPORT_COUNT, 0x06, /* Report Count: 6 */ \ + INPUT, 0x03, /* Input: (Const, Var, Abs) */ \ + /* End of a byte */ \ + /* Begin of 4 bytes */ \ + REPORT_COUNT, 0x01, /* Report Count: 1 */ \ + REPORT_SIZE, 0x20, /* Report Size: 0x20 (4 bytes) */ \ + LOGICAL_MAXIMUM_3, 0xff, 0xff, 0xff, 0xff, /* Logical Maximum: 0xffffffff */ \ + USAGE, 0x51, /* Usage: Contract Identifier */ \ + INPUT, 0x02, /* Input: (Data, Var, Abs) */ \ + /* End of 4 bytes */ \ + /* Begin of 4 bytes */ \ + /* Size is hard-coded at this moment */ \ + USAGE_PAGE, 0x01, /* Usage Page: Generic Desktop */ \ + LOGICAL_MAXIMUM_2, 0x20, 0x26, /* Logical Maximum: 9760 (See defintion) */ \ + REPORT_SIZE, 0x10, /* Report Size: 0x10 (2 bytes) */ \ + UNIT_EXPONENT, 0x0e, /* Unit exponent: -2 */ \ + UNIT, 0x11, /* Unit: SI Length (cm) */ \ + USAGE, 0x30, /* Usage: X */ \ + PHYSICAL_MAXIMUM_2, 0x15, 0x04, /* Physical Maximum: 1045 (See Apple Spec) */ \ + REPORT_COUNT, 0x01, /* Report count: 1 */ \ + INPUT, 0x02, /* Input: (Data, Var, Abs) */ \ + PHYSICAL_MAXIMUM_2, 0xee, 0x02, /* Physical Maximum: 750 (See Apple Spec) */ \ + LOGICAL_MAXIMUM_2, 0x5e, 0x1a, /* Logical Maximum: 6750 (See definition) */ \ + USAGE, 0x31, /* Usage: Y */ \ + INPUT, 0x02, /* Input: (Data, Var, Abs) */ \ + /* End of 4 bytes */ \ + END_COLLECTION /* End Collection */ \ + +#define AAPL_WELLSPRING_8_PTP_TLC \ + USAGE_PAGE, 0x0d, /* Usage Page: Digitizer */ \ + USAGE, 0x05, /* Usage: Touch Pad */ \ + BEGIN_COLLECTION, 0x01, /* Begin Collection: Application */ \ + REPORT_ID, REPORTID_MULTITOUCH, /* Report ID: Multi-touch */ \ + USAGE, 0x22, /* Usage: Finger */ \ + AAPL_WELLSPRING_8_PTP_FINGER_COLLECTION_1, /* 1 */ \ + USAGE_PAGE, 0x0d, /* Usage Page: Digitizer */ \ + USAGE, 0x22, /* Usage: Finger */ \ + AAPL_WELLSPRING_8_PTP_FINGER_COLLECTION_1, /* 2 */ \ + USAGE_PAGE, 0x0d, /* Usage Page: Digitizer */ \ + USAGE, 0x22, /* Usage: Finger */ \ + AAPL_WELLSPRING_8_PTP_FINGER_COLLECTION_2, /* 3 */ \ + USAGE_PAGE, 0x0d, /* Usage Page: Digitizer */ \ + USAGE, 0x22, /* Usage: Finger */ \ + AAPL_WELLSPRING_8_PTP_FINGER_COLLECTION_1, /* 4 */ \ + USAGE_PAGE, 0x0d, /* Usage Page: Digitizer */ \ + USAGE, 0x22, /* Usage: Finger */ \ + AAPL_WELLSPRING_8_PTP_FINGER_COLLECTION_2, /* 5 */ \ + USAGE_PAGE, 0x0d, /* Usage Page: Digitizer */ \ + UNIT_EXPONENT, 0x0c, /* Unit exponent: -4 */ \ + UNIT_2, 0x01, 0x10, /* Time: Second */ \ + PHYSICAL_MAXIMUM_3, 0xff, 0xff, 0x00, 0x00, \ + LOGICAL_MAXIMUM_3, 0xff, 0xff, 0x00, 0x00, \ + USAGE, 0x56, /* Usage: Scan Time */ \ + INPUT, 0x02, /* Input: (Data, Var, Abs) */ \ + USAGE, 0x54, /* Usage: Contact Count */ \ + LOGICAL_MAXIMUM, 0x7f, \ + REPORT_SIZE, 0x08, \ + INPUT, 0x02, /* Input: (Data, Var, Abs) */ \ + USAGE_PAGE, 0x09, /* Usage Page: Button */ \ + USAGE, 0x01, /* Button 1 */ \ + LOGICAL_MAXIMUM, 0x01, \ + REPORT_SIZE, 0x01, \ + INPUT, 0x02, /* Input: (Data, Var, Abs) */ \ + REPORT_COUNT, 0x07, \ + INPUT, 0x03, /* Input: (Const, Var, Abs) */ \ + USAGE_PAGE, 0x0d, /* Usage Page: Digitizer */ \ + REPORT_ID, REPORTID_DEVICE_CAPS, \ + USAGE, 0x55, /* Usage: Maximum Contacts */ \ + USAGE, 0x59, /* Usage: Touchpad Button Type*/ \ + LOGICAL_MINIMUM, 0x00, \ + LOGICAL_MAXIMUM_2, 0xff, 0x00, \ + REPORT_SIZE, 0x08, \ + REPORT_COUNT, 0x02, \ + FEATURE, 0x02, \ + USAGE_PAGE_1, 0x00, 0xff, \ + REPORT_ID, REPORTID_PTPHQA, \ + USAGE, 0xc5, \ + LOGICAL_MINIMUM, 0x00, \ + LOGICAL_MAXIMUM_2, 0xff, 0x00, \ + REPORT_SIZE, 0x08, \ + REPORT_COUNT_2, 0x00, 0x01, \ + FEATURE, 0x02, \ + END_COLLECTION /* End Collection */ diff --git a/AmtPtpDeviceUsbUm/include/DeviceFamily/WellspringMt2.h b/AmtPtpDeviceUsbUm/include/DeviceFamily/WellspringMt2.h new file mode 100644 index 0000000..215a1ce --- /dev/null +++ b/AmtPtpDeviceUsbUm/include/DeviceFamily/WellspringMt2.h @@ -0,0 +1,138 @@ +#pragma once + +#include + +#define AAPL_MAGIC_TRACKPAD2_PTP_FINGER_COLLECTION_1 \ + BEGIN_COLLECTION, 0x02, /* Begin Collection: Logical */ \ + /* Begin a byte */ \ + LOGICAL_MAXIMUM, 0x01, /* Logical Maximum: 1 */ \ + USAGE, 0x47, /* Usage: Confidence */ \ + USAGE, 0x42, /* Usage: Tip switch */ \ + REPORT_COUNT, 0x02, /* Report Count: 2 */ \ + REPORT_SIZE, 0x01, /* Report Size: 1 */ \ + INPUT, 0x02, /* Input: (Data, Var, Abs) */ \ + REPORT_SIZE, 0x01, /* Report Size: 1 */ \ + REPORT_COUNT, 0x06, /* Report Count: 6 */ \ + INPUT, 0x03, /* Input: (Const, Var, Abs) */ \ + /* End of a byte */ \ + /* Begin of 4 bytes */ \ + REPORT_COUNT, 0x01, /* Report Count: 1 */ \ + REPORT_SIZE, 0x20, /* Report Size: 0x20 (4 bytes) */ \ + LOGICAL_MAXIMUM_3, 0xff, 0xff, 0xff, 0xff, /* Logical Maximum: 0xffffffff */ \ + USAGE, 0x51, /* Usage: Contract Identifier */ \ + INPUT, 0x02, /* Input: (Data, Var, Abs) */ \ + /* End of 4 bytes */ \ + /* Begin of 4 bytes */ \ + /* Size is hard-coded at this moment */ \ + USAGE_PAGE, 0x01, /* Usage Page: Generic Desktop */ \ + LOGICAL_MAXIMUM_2, 0xbc, 0x1d, /* Logical Maximum: 7612 (See defintion) */ \ + REPORT_SIZE, 0x10, /* Report Size: 0x10 (2 bytes) */ \ + UNIT_EXPONENT, 0x0e, /* Unit exponent: -2 */ \ + UNIT, 0x11, /* Unit: SI Length (cm) */ \ + USAGE, 0x30, /* Usage: X */ \ + PHYSICAL_MAXIMUM_2, 0x40, 0x06, /* Physical Maximum: 1600 (See Apple Spec) */ \ + REPORT_COUNT, 0x01, /* Report count: 1 */ \ + INPUT, 0x02, /* Input: (Data, Var, Abs) */ \ + PHYSICAL_MAXIMUM_2, 0x7d, 0x04, /* Physical Maximum: 1149 (See Apple Spec) */ \ + LOGICAL_MAXIMUM_2, 0xc9, 0x13, /* Logical Maximum: 5065 (See definition) */ \ + USAGE, 0x31, /* Usage: Y */ \ + INPUT, 0x02, /* Input: (Data, Var, Abs) */ \ + PHYSICAL_MAXIMUM, 0x00, /* Physical Maximum: 0 */ \ + UNIT_EXPONENT, 0x00, /* Unit exponent: 0 */ \ + UNIT, 0x00, /* Unit: None */ \ + /* End of 4 bytes */ \ + END_COLLECTION /* End Collection */ \ + +#define AAPL_MAGIC_TRACKPAD2_PTP_FINGER_COLLECTION_2 \ + BEGIN_COLLECTION, 0x02, /* Begin Collection: Logical */ \ + /* Begin a byte */ \ + LOGICAL_MAXIMUM, 0x01, /* Logical Maximum: 1 */ \ + USAGE, 0x47, /* Usage: Confidence */ \ + USAGE, 0x42, /* Usage: Tip switch */ \ + REPORT_COUNT, 0x02, /* Report Count: 2 */ \ + REPORT_SIZE, 0x01, /* Report Size: 1 */ \ + INPUT, 0x02, /* Input: (Data, Var, Abs) */ \ + REPORT_SIZE, 0x01, /* Report Size: 1 */ \ + REPORT_COUNT, 0x06, /* Report Count: 6 */ \ + INPUT, 0x03, /* Input: (Const, Var, Abs) */ \ + /* End of a byte */ \ + /* Begin of 4 bytes */ \ + REPORT_COUNT, 0x01, /* Report Count: 1 */ \ + REPORT_SIZE, 0x20, /* Report Size: 0x20 (4 bytes) */ \ + LOGICAL_MAXIMUM_3, 0xff, 0xff, 0xff, 0xff, /* Logical Maximum: 0xffffffff */ \ + USAGE, 0x51, /* Usage: Contract Identifier */ \ + INPUT, 0x02, /* Input: (Data, Var, Abs) */ \ + /* End of 4 bytes */ \ + /* Begin of 4 bytes */ \ + /* Size is hard-coded at this moment */ \ + USAGE_PAGE, 0x01, /* Usage Page: Generic Desktop */ \ + LOGICAL_MAXIMUM_2, 0xbc, 0x1d, /* Logical Maximum: 7612 (See defintion) */ \ + REPORT_SIZE, 0x10, /* Report Size: 0x10 (2 bytes) */ \ + UNIT_EXPONENT, 0x0e, /* Unit exponent: -2 */ \ + UNIT, 0x11, /* Unit: SI Length (cm) */ \ + USAGE, 0x30, /* Usage: X */ \ + PHYSICAL_MAXIMUM_2, 0x40, 0x06, /* Physical Maximum: 1600 (See Apple Spec) */ \ + REPORT_COUNT, 0x01, /* Report count: 1 */ \ + INPUT, 0x02, /* Input: (Data, Var, Abs) */ \ + PHYSICAL_MAXIMUM_2, 0x7d, 0x04, /* Physical Maximum: 1149 (See Apple Spec) */ \ + LOGICAL_MAXIMUM_2, 0xc9, 0x13, /* Logical Maximum: 5065 (See definition) */ \ + USAGE, 0x31, /* Usage: Y */ \ + INPUT, 0x02, /* Input: (Data, Var, Abs) */ \ + /* End of 4 bytes */ \ + END_COLLECTION /* End Collection */ \ + +#define AAPL_MAGIC_TRACKPAD2_PTP_TLC \ + USAGE_PAGE, 0x0d, /* Usage Page: Digitizer */ \ + USAGE, 0x05, /* Usage: Touch Pad */ \ + BEGIN_COLLECTION, 0x01, /* Begin Collection: Application */ \ + REPORT_ID, REPORTID_MULTITOUCH, /* Report ID: Multi-touch */ \ + USAGE, 0x22, /* Usage: Finger */ \ + AAPL_MAGIC_TRACKPAD2_PTP_FINGER_COLLECTION_1, /* 1 */ \ + USAGE_PAGE, 0x0d, /* Usage Page: Digitizer */ \ + USAGE, 0x22, /* Usage: Finger */ \ + AAPL_MAGIC_TRACKPAD2_PTP_FINGER_COLLECTION_1, /* 2 */ \ + USAGE_PAGE, 0x0d, /* Usage Page: Digitizer */ \ + USAGE, 0x22, /* Usage: Finger */ \ + AAPL_MAGIC_TRACKPAD2_PTP_FINGER_COLLECTION_2, /* 3 */ \ + USAGE_PAGE, 0x0d, /* Usage Page: Digitizer */ \ + USAGE, 0x22, /* Usage: Finger */ \ + AAPL_MAGIC_TRACKPAD2_PTP_FINGER_COLLECTION_1, /* 4 */ \ + USAGE_PAGE, 0x0d, /* Usage Page: Digitizer */ \ + USAGE, 0x22, /* Usage: Finger */ \ + AAPL_MAGIC_TRACKPAD2_PTP_FINGER_COLLECTION_2, /* 5 */ \ + USAGE_PAGE, 0x0d, /* Usage Page: Digitizer */ \ + UNIT_EXPONENT, 0x0c, /* Unit exponent: -4 */ \ + UNIT_2, 0x01, 0x10, /* Time: Second */ \ + PHYSICAL_MAXIMUM_3, 0xff, 0xff, 0x00, 0x00, \ + LOGICAL_MAXIMUM_3, 0xff, 0xff, 0x00, 0x00, \ + USAGE, 0x56, /* Usage: Scan Time */ \ + INPUT, 0x02, /* Input: (Data, Var, Abs) */ \ + USAGE, 0x54, /* Usage: Contact Count */ \ + LOGICAL_MAXIMUM, 0x7f, \ + REPORT_SIZE, 0x08, \ + INPUT, 0x02, /* Input: (Data, Var, Abs) */ \ + USAGE_PAGE, 0x09, /* Usage Page: Button */ \ + USAGE, 0x01, /* Button 1 */ \ + LOGICAL_MAXIMUM, 0x01, \ + REPORT_SIZE, 0x01, \ + INPUT, 0x02, /* Input: (Data, Var, Abs) */ \ + REPORT_COUNT, 0x07, \ + INPUT, 0x03, /* Input: (Const, Var, Abs) */ \ + USAGE_PAGE, 0x0d, /* Usage Page: Digitizer */ \ + REPORT_ID, REPORTID_DEVICE_CAPS, \ + USAGE, 0x55, /* Usage: Maximum Contacts */ \ + USAGE, 0x59, /* Usage: Touchpad Button Type*/ \ + LOGICAL_MINIMUM, 0x00, \ + LOGICAL_MAXIMUM_2, 0xff, 0x00, \ + REPORT_SIZE, 0x08, \ + REPORT_COUNT, 0x02, \ + FEATURE, 0x02, \ + USAGE_PAGE_1, 0x00, 0xff, \ + REPORT_ID, REPORTID_PTPHQA, \ + USAGE, 0xc5, \ + LOGICAL_MINIMUM, 0x00, \ + LOGICAL_MAXIMUM_2, 0xff, 0x00, \ + REPORT_SIZE, 0x08, \ + REPORT_COUNT_2, 0x00, 0x01, \ + FEATURE, 0x02, \ + END_COLLECTION /* End Collection */ diff --git a/AmtPtpDeviceUsbUm/include/Driver.h b/AmtPtpDeviceUsbUm/include/Driver.h new file mode 100644 index 0000000..511a4a0 --- /dev/null +++ b/AmtPtpDeviceUsbUm/include/Driver.h @@ -0,0 +1,46 @@ +// Driver.h: Driver definitions + +#include +#include +#include +#include +#include +#include + +// ModernTrace is for runtime debugging +// Trace is WPP-based, development debugging +#include +#include + +#include +#include +#include +#include + +EXTERN_C_START + +// +// WDFDRIVER Events +// + +DRIVER_INITIALIZE DriverEntry; +EVT_WDF_DRIVER_DEVICE_ADD AmtPtpDeviceEvtDeviceAdd; +EVT_WDF_OBJECT_CONTEXT_CLEANUP AmtPtpDeviceEvtDriverContextCleanup; + + +// +// Driver initialization routines +// + +VOID +DriverTraceInit( + _In_ PDRIVER_OBJECT DriverObject, + _In_ PUNICODE_STRING RegistryPath +); + +VOID +DriverTraceCleanup( + _In_ WDFOBJECT DriverObject +); + +EXTERN_C_END diff --git a/AmtPtpDeviceUsbUm/include/Hid.h b/AmtPtpDeviceUsbUm/include/Hid.h new file mode 100644 index 0000000..bd50b60 --- /dev/null +++ b/AmtPtpDeviceUsbUm/include/Hid.h @@ -0,0 +1,169 @@ +// Hid.h: Device-related HID definitions +#pragma once + +#include + +#include +#include + +// Device family metadata +#include "DeviceFamily/Wellspring3.h" +#include "DeviceFamily/Wellspring5.h" +#include "DeviceFamily/Wellspring6.h" +#include "DeviceFamily/Wellspring7A.h" +#include "DeviceFamily/Wellspring8.h" +#include "DeviceFamily/WellspringMt2.h" + +typedef UCHAR HID_REPORT_DESCRIPTOR, *PHID_REPORT_DESCRIPTOR; + +#define DEVICE_VERSION 0x01 +#define DEVICE_VID 0x8910 + +#define AAPL_PTP_USERMODE_CONFIGURATION_APP_TLC \ + USAGE_PAGE_1, 0x00, 0xff, /* Usage Page: Vendor defined */ \ + USAGE, 0x01, /* Usage: Vendor Usage 0x01 */ \ + BEGIN_COLLECTION, 0x01, /* Begin Collection: Application */ \ + REPORT_ID, REPORTID_UMAPP_CONF, /* Report ID: User-mode Application configuration */ \ + USAGE, 0x01, /* Usage: Vendor Usage 0x01 */ \ + LOGICAL_MINIMUM, 0x00, /* Logical Minimum 0 */ \ + LOGICAL_MAXIMUM_2, 0xff, 0x00, /* Logical Maximum 255 */ \ + REPORT_SIZE, 0x08, /* Report Size: 8 */ \ + REPORT_COUNT, 0x03, /* Report Count: 3 */ \ + FEATURE, 0x02, /* Feature: (Data, Var, Abs) */ \ + END_COLLECTION + +#define AAPL_PTP_WINDOWS_CONFIGURATION_TLC \ + USAGE_PAGE, 0x0d, /* Usage Page: Digitizer */ \ + USAGE, 0x0e, /* Usage: Configuration */ \ + BEGIN_COLLECTION, 0x01, /* Begin Collection: Application */ \ + REPORT_ID, REPORTID_REPORTMODE, /* Report ID: Mode Selection */ \ + USAGE, 0x22, /* Usage: Finger */ \ + BEGIN_COLLECTION, 0x02, /* Begin Collection: Logical */ \ + USAGE, 0x52, /* Usage: Input Mode */ \ + LOGICAL_MINIMUM, 0x00, /* Logical Minumum: 0 finger */ \ + LOGICAL_MAXIMUM, MAX_FINGERS, /* Logical Maximum: MAX_TOUCH_COUNT fingers */ \ + REPORT_SIZE, 0x08, /* Report Size: 0x08 */ \ + REPORT_COUNT, 0x01, /* Report Count: 0x01 */ \ + FEATURE, 0x02, /* Feature: (Data, Var, Abs) */ \ + END_COLLECTION, /* End Collection */ \ + BEGIN_COLLECTION, 0x00, /* Begin Collection: Physical */ \ + REPORT_ID, REPORTID_FUNCSWITCH, /* Report ID: Function Switch */ \ + USAGE, BUTTON_SWITCH, /* Usage: Button Switch */ \ + USAGE, SURFACE_SWITCH, /* Usage: Surface Switch */ \ + REPORT_SIZE, 0x01, /* Report Size: 0x01 */ \ + REPORT_COUNT, 0x02, /* Report Count: 0x02 */ \ + LOGICAL_MAXIMUM, 0x01, /* Logical Maximum: 0x01 */ \ + FEATURE, 0x02, /* Feature: (Data, Var, Abs) */ \ + REPORT_COUNT, 0x06, /* Report Count: 0x06 */ \ + FEATURE, 0x03, /* Feature: (Const, Var, Abs) */ \ + END_COLLECTION, /* End Collection */ \ + END_COLLECTION /* End Collection */ + +#define DEFAULT_PTP_HQA_BLOB \ + 0xfc, 0x28, 0xfe, 0x84, 0x40, 0xcb, 0x9a, 0x87, \ + 0x0d, 0xbe, 0x57, 0x3c, 0xb6, 0x70, 0x09, 0x88, \ + 0x07, 0x97, 0x2d, 0x2b, 0xe3, 0x38, 0x34, 0xb6, \ + 0x6c, 0xed, 0xb0, 0xf7, 0xe5, 0x9c, 0xf6, 0xc2, \ + 0x2e, 0x84, 0x1b, 0xe8, 0xb4, 0x51, 0x78, 0x43, \ + 0x1f, 0x28, 0x4b, 0x7c, 0x2d, 0x53, 0xaf, 0xfc, \ + 0x47, 0x70, 0x1b, 0x59, 0x6f, 0x74, 0x43, 0xc4, \ + 0xf3, 0x47, 0x18, 0x53, 0x1a, 0xa2, 0xa1, 0x71, \ + 0xc7, 0x95, 0x0e, 0x31, 0x55, 0x21, 0xd3, 0xb5, \ + 0x1e, 0xe9, 0x0c, 0xba, 0xec, 0xb8, 0x89, 0x19, \ + 0x3e, 0xb3, 0xaf, 0x75, 0x81, 0x9d, 0x53, 0xb9, \ + 0x41, 0x57, 0xf4, 0x6d, 0x39, 0x25, 0x29, 0x7c, \ + 0x87, 0xd9, 0xb4, 0x98, 0x45, 0x7d, 0xa7, 0x26, \ + 0x9c, 0x65, 0x3b, 0x85, 0x68, 0x89, 0xd7, 0x3b, \ + 0xbd, 0xff, 0x14, 0x67, 0xf2, 0x2b, 0xf0, 0x2a, \ + 0x41, 0x54, 0xf0, 0xfd, 0x2c, 0x66, 0x7c, 0xf8, \ + 0xc0, 0x8f, 0x33, 0x13, 0x03, 0xf1, 0xd3, 0xc1, \ + 0x0b, 0x89, 0xd9, 0x1b, 0x62, 0xcd, 0x51, 0xb7, \ + 0x80, 0xb8, 0xaf, 0x3a, 0x10, 0xc1, 0x8a, 0x5b, \ + 0xe8, 0x8a, 0x56, 0xf0, 0x8c, 0xaa, 0xfa, 0x35, \ + 0xe9, 0x42, 0xc4, 0xd8, 0x55, 0xc3, 0x38, 0xcc, \ + 0x2b, 0x53, 0x5c, 0x69, 0x52, 0xd5, 0xc8, 0x73, \ + 0x02, 0x38, 0x7c, 0x73, 0xb6, 0x41, 0xe7, 0xff, \ + 0x05, 0xd8, 0x2b, 0x79, 0x9a, 0xe2, 0x34, 0x60, \ + 0x8f, 0xa3, 0x32, 0x1f, 0x09, 0x78, 0x62, 0xbc, \ + 0x80, 0xe3, 0x0f, 0xbd, 0x65, 0x20, 0x08, 0x13, \ + 0xc1, 0xe2, 0xee, 0x53, 0x2d, 0x86, 0x7e, 0xa7, \ + 0x5a, 0xc5, 0xd3, 0x7d, 0x98, 0xbe, 0x31, 0x48, \ + 0x1f, 0xfb, 0xda, 0xaf, 0xa2, 0xa8, 0x6a, 0x89, \ + 0xd6, 0xbf, 0xf2, 0xd3, 0x32, 0x2a, 0x9a, 0xe4, \ + 0xcf, 0x17, 0xb7, 0xb8, 0xf4, 0xe1, 0x33, 0x08, \ + 0x24, 0x8b, 0xc4, 0x43, 0xa5, 0xe5, 0x24, 0xc2 + +#define PTP_MAX_CONTACT_POINTS 5 +#define PTP_BUTTON_TYPE_CLICK_PAD 0 +#define PTP_BUTTON_TYPE_PRESSURE_PAD 1 + +#define PTP_COLLECTION_MOUSE 0 +#define PTP_COLLECTION_WINDOWS 3 + +#define PTP_CONTACT_CONFIDENCE_BIT 1 +#define PTP_CONTACT_TIPSWITCH_BIT 2 + +typedef struct _HID_AAPL_MOUSE_REPORT { + struct { + UCHAR bButtons; + UCHAR wXData; + UCHAR wYData; + UINT Padding; + } InputReport; +} HID_AAPL_MOUSE_REPORT, *PHID_AAPL_MOUSE_REPORT; + +typedef struct _HID_INPUT_REPORT { + UCHAR ReportID; + HID_AAPL_MOUSE_REPORT MouseReport; +} HID_INPUT_REPORT, *PHID_INPUT_REPORT; + +typedef struct _PTP_DEVICE_CAPS_FEATURE_REPORT { + UCHAR ReportID; + UCHAR MaximumContactPoints; + UCHAR ButtonType; +} PTP_DEVICE_CAPS_FEATURE_REPORT, *PPTP_DEVICE_CAPS_FEATURE_REPORT; + +typedef struct _PTP_DEVICE_HQA_CERTIFICATION_REPORT { + UCHAR ReportID; + UCHAR CertificationBlob[256]; +} PTP_DEVICE_HQA_CERTIFICATION_REPORT, *PPTP_DEVICE_HQA_CERTIFICATION_REPORT; + +typedef struct _PTP_DEVICE_INPUT_MODE_REPORT { + UCHAR ReportID; + UCHAR Mode; +} PTP_DEVICE_INPUT_MODE_REPORT, *PPTP_DEVICE_INPUT_MODE_REPORT; + +#pragma pack(1) +typedef struct _PTP_DEVICE_SELECTIVE_REPORT_MODE_REPORT { + UCHAR ReportID; + UCHAR ButtonReport : 1; + UCHAR SurfaceReport : 1; + UCHAR Padding : 6; +} PTP_DEVICE_SELECTIVE_REPORT_MODE_REPORT, *PPTP_DEVICE_SELECTIVE_REPORT_MODE_REPORT; +#pragma pack() + +#pragma pack(1) +typedef struct _PTP_CONTACT { + UCHAR Confidence : 1; + UCHAR TipSwitch : 1; + UCHAR Padding : 6; + ULONG ContactID; + USHORT X; + USHORT Y; +} PTP_CONTACT, *PPTP_CONTACT; +#pragma pack() + +typedef struct _PTP_REPORT { + UCHAR ReportID; + PTP_CONTACT Contacts[5]; + USHORT ScanTime; + UCHAR ContactCount; + UCHAR IsButtonClicked; +} PTP_REPORT, *PPTP_REPORT; + +typedef struct _PTP_USERMODEAPP_CONF_REPORT { + UCHAR ReportID; + UCHAR PressureQualificationLevel; + UCHAR SingleContactSizeQualificationLevel; + UCHAR MultipleContactSizeQualificationLevel; +} PTP_USERMODEAPP_CONF_REPORT, *PPTP_USERMODEAPP_CONF_REPORT; diff --git a/AmtPtpDeviceUsbUm/include/HidCommon.h b/AmtPtpDeviceUsbUm/include/HidCommon.h new file mode 100644 index 0000000..08d91dc --- /dev/null +++ b/AmtPtpDeviceUsbUm/include/HidCommon.h @@ -0,0 +1,39 @@ +#pragma once + +#define REPORTID_STANDARDMOUSE 0x02 +#define REPORTID_MULTITOUCH 0x05 +#define REPORTID_REPORTMODE 0x04 +#define REPORTID_PTPHQA 0x08 +#define REPORTID_FUNCSWITCH 0x06 +#define REPORTID_DEVICE_CAPS 0x07 +#define REPORTID_UMAPP_CONF 0x09 + +#define BUTTON_SWITCH 0x57 +#define SURFACE_SWITCH 0x58 + +#define USAGE_PAGE 0x05 +#define USAGE_PAGE_1 0x06 +#define USAGE 0x09 +#define USAGE_MINIMUM 0x19 +#define USAGE_MAXIMUM 0x29 +#define LOGICAL_MINIMUM 0x15 +#define LOGICAL_MAXIMUM 0x25 +#define LOGICAL_MAXIMUM_2 0x26 +#define LOGICAL_MAXIMUM_3 0x27 +#define PHYSICAL_MINIMUM 0x35 +#define PHYSICAL_MAXIMUM 0x45 +#define PHYSICAL_MAXIMUM_2 0x46 +#define PHYSICAL_MAXIMUM_3 0x47 +#define UNIT_EXPONENT 0x55 +#define UNIT 0x65 +#define UNIT_2 0x66 + +#define REPORT_ID 0x85 +#define REPORT_COUNT 0x95 +#define REPORT_COUNT_2 0x96 +#define REPORT_SIZE 0x75 +#define INPUT 0x81 +#define FEATURE 0xb1 + +#define BEGIN_COLLECTION 0xa1 +#define END_COLLECTION 0xc0 diff --git a/AmtPtpDeviceUsbUm/include/ModernTrace.h b/AmtPtpDeviceUsbUm/include/ModernTrace.h new file mode 100644 index 0000000..1a34c5f --- /dev/null +++ b/AmtPtpDeviceUsbUm/include/ModernTrace.h @@ -0,0 +1,28 @@ +#pragma once +// This is the new Windows 10 trace logger provider +#include + +EXTERN_C_START + +// +// Declare TraceLogger Handler +// TraceLogger GUID {871B1E2D-CC5A-4ADE-B74E-6CF1004EF149} +// Do not confuse with WPP tracing +// +TRACELOGGING_DECLARE_PROVIDER(g_hAmtPtpDeviceTraceProvider); + +EXTERN_C_END + +// +// Defines a set of events to use +// + +#define EVENT_DRIVER_FUNCTIONAL "DriverFunctionalEvent" +#define EVENT_DEVICE_IDENTIFICATION "DeviceIdentificationEvent" +#define EVENT_DEVICE_USBOPERATION "DeviceUsbOperationEvent" +#define EVENT_INPUT_DIAGNOSTICS "InputDiagnosticsEvent" + +#define EVENT_DRIVER_FUNC_SUBTYPE "Subtype" +#define EVENT_DRIVER_FUNC_SUBTYPE_CRITFAIL "CriticalFailure" +#define EVENT_DEVICE_ID_SUBTYPE_NOTFOUND "DeviceNotFoundInRegistry" +#define EVENT_DEVICE_ID_SUBTYPE_HIDREG_NOTFOUND "DeviceDescriptorNotFoundInRegistry" diff --git a/AmtPtpDeviceUsbUm/include/Queue.h b/AmtPtpDeviceUsbUm/include/Queue.h new file mode 100644 index 0000000..4d5cd67 --- /dev/null +++ b/AmtPtpDeviceUsbUm/include/Queue.h @@ -0,0 +1,41 @@ +// Queue.h: This file contains the queue definitions. + +EXTERN_C_START + +// +// This is the context that can be placed per queue +// and would contain per queue information. +// +typedef struct _QUEUE_CONTEXT { + + ULONG placeholder; + +} QUEUE_CONTEXT, *PQUEUE_CONTEXT; + +WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(QUEUE_CONTEXT, QueueGetContext) + +_IRQL_requires_(PASSIVE_LEVEL) +PCHAR +DbgIoControlGetString( + _In_ ULONG IoControlCode +); + +NTSTATUS +AmtPtpDeviceQueueInitialize( + _In_ WDFDEVICE Device +); + +NTSTATUS +AmtPtpDispatchReadReportRequests( + _In_ WDFDEVICE Device, + _In_ WDFREQUEST Request, + _Out_ BOOLEAN *Pending +); + +// +// Events from the IoQueue object +// +EVT_WDF_IO_QUEUE_IO_DEVICE_CONTROL AmtPtpDeviceEvtIoDeviceControl; +EVT_WDF_IO_QUEUE_IO_STOP AmtPtpDeviceEvtIoStop; + +EXTERN_C_END diff --git a/AmtPtpDeviceUsbUm/include/StaticHidRegistry.h b/AmtPtpDeviceUsbUm/include/StaticHidRegistry.h new file mode 100644 index 0000000..1ffb41d --- /dev/null +++ b/AmtPtpDeviceUsbUm/include/StaticHidRegistry.h @@ -0,0 +1,114 @@ +#pragma once + +#ifndef _AAPL_HID_DESCRIPTOR_H_ +#define _AAPL_HID_DESCRIPTOR_H_ + +HID_REPORT_DESCRIPTOR AmtPtp3ReportDescriptor[] = { + AAPL_WELLSPRING_3_PTP_TLC, + AAPL_PTP_WINDOWS_CONFIGURATION_TLC, + AAPL_PTP_USERMODE_CONFIGURATION_APP_TLC +}; + +HID_REPORT_DESCRIPTOR AmtPtp5ReportDescriptor[] = { + AAPL_WELLSPRING_5_PTP_TLC, + AAPL_PTP_WINDOWS_CONFIGURATION_TLC, + AAPL_PTP_USERMODE_CONFIGURATION_APP_TLC +}; + +HID_REPORT_DESCRIPTOR AmtPtp6ReportDescriptor[] = { + AAPL_WELLSPRING_6_PTP_TLC, + AAPL_PTP_WINDOWS_CONFIGURATION_TLC, + AAPL_PTP_USERMODE_CONFIGURATION_APP_TLC +}; + +HID_REPORT_DESCRIPTOR AmtPtp7aReportDescriptor[] = { + AAPL_WELLSPRING_7A_PTP_TLC, + AAPL_PTP_WINDOWS_CONFIGURATION_TLC, + AAPL_PTP_USERMODE_CONFIGURATION_APP_TLC +}; + +HID_REPORT_DESCRIPTOR AmtPtp8ReportDescriptor[] = { + AAPL_WELLSPRING_8_PTP_TLC, + AAPL_PTP_WINDOWS_CONFIGURATION_TLC, + AAPL_PTP_USERMODE_CONFIGURATION_APP_TLC +}; + +HID_REPORT_DESCRIPTOR AmtPtpMt2ReportDescriptor[] = { + AAPL_MAGIC_TRACKPAD2_PTP_TLC, + AAPL_PTP_WINDOWS_CONFIGURATION_TLC, + AAPL_PTP_USERMODE_CONFIGURATION_APP_TLC +}; + +HID_DESCRIPTOR AmtPtp3DefaultHidDescriptor = { + 0x09, // bLength + 0x21, // bDescriptorType + 0x0100, // bcdHID + 0x00, // bCountryCode + 0x01, // bNumDescriptors + { + 0x22, // bDescriptorType + sizeof(AmtPtp3ReportDescriptor) // bDescriptorLength + }, +}; + +HID_DESCRIPTOR AmtPtp5DefaultHidDescriptor = { + 0x09, // bLength + 0x21, // bDescriptorType + 0x0100, // bcdHID + 0x00, // bCountryCode + 0x01, // bNumDescriptors + { + 0x22, // bDescriptorType + sizeof(AmtPtp5ReportDescriptor) // bDescriptorLength + }, +}; + +HID_DESCRIPTOR AmtPtp6DefaultHidDescriptor = { + 0x09, // bLength + 0x21, // bDescriptorType + 0x0100, // bcdHID + 0x00, // bCountryCode + 0x01, // bNumDescriptors + { + 0x22, // bDescriptorType + sizeof(AmtPtp6ReportDescriptor) // bDescriptorLength + }, +}; + +HID_DESCRIPTOR AmtPtp7aDefaultHidDescriptor = { + 0x09, // bLength + 0x21, // bDescriptorType + 0x0100, // bcdHID + 0x00, // bCountryCode + 0x01, // bNumDescriptors + { + 0x22, // bDescriptorType + sizeof(AmtPtp7aReportDescriptor) // bDescriptorLength + }, +}; + +HID_DESCRIPTOR AmtPtp8DefaultHidDescriptor = { + 0x09, // bLength + 0x21, // bDescriptorType + 0x0100, // bcdHID + 0x00, // bCountryCode + 0x01, // bNumDescriptors + { + 0x22, // bDescriptorType + sizeof(AmtPtp8ReportDescriptor) // bDescriptorLength + }, +}; + +HID_DESCRIPTOR AmtPtpMt2DefaultHidDescriptor = { + 0x09, // bLength + 0x21, // bDescriptorType + 0x0100, // bcdHID + 0x00, // bCountryCode + 0x01, // bNumDescriptors + { + 0x22, // bDescriptorType + sizeof(AmtPtpMt2ReportDescriptor) // bDescriptorLength + }, +}; + +#endif diff --git a/AmtPtpDeviceUsbUm/include/Trace.h b/AmtPtpDeviceUsbUm/include/Trace.h new file mode 100644 index 0000000..b82137f --- /dev/null +++ b/AmtPtpDeviceUsbUm/include/Trace.h @@ -0,0 +1,48 @@ +// Trace.h: Local type definitions for tracing + +// +// Device Interface GUID +// 4a5064e5-7d39-41d1-a0e4-81097edce967 +// +DEFINE_GUID(GUID_DEVINTERFACE_AmtPtpDevice, + 0x4a5064e5, 0x7d39, 0x41d1, 0xa0, 0xe4, 0x81, 0x09, 0x7e, 0xdc, 0xe9, 0x67); + +// +// Define the tracing flags. +// +// Tracing GUID - efc3ce99-43ff-4b59-afe4-c856e1afd8b0 +// + +#define WPP_CONTROL_GUIDS \ + WPP_DEFINE_CONTROL_GUID( \ + AmtPtpDriverTraceGuid, (efc3ce99,43ff,4b59,afe4,c856e1afd8b0), \ + \ + WPP_DEFINE_BIT(AMTPTPDRIVER_ALL_INFO) \ + WPP_DEFINE_BIT(TRACE_DRIVER) \ + WPP_DEFINE_BIT(TRACE_DEVICE) \ + WPP_DEFINE_BIT(TRACE_QUEUE) \ + WPP_DEFINE_BIT(TRACE_INPUT) \ + ) + +#define WPP_FLAG_LEVEL_LOGGER(flag, level) \ + WPP_LEVEL_LOGGER(flag) + +#define WPP_FLAG_LEVEL_ENABLED(flag, level) \ + (WPP_LEVEL_ENABLED(flag) && \ + WPP_CONTROL(WPP_BIT_ ## flag).Level >= level) + +#define WPP_LEVEL_FLAGS_LOGGER(lvl,flags) \ + WPP_LEVEL_LOGGER(flags) + +#define WPP_LEVEL_FLAGS_ENABLED(lvl, flags) \ + (WPP_LEVEL_ENABLED(flags) && WPP_CONTROL(WPP_BIT_ ## flags).Level >= lvl) + +// +// This comment block is scanned by the trace preprocessor to define our +// Trace function. +// +// begin_wpp config +// FUNC Trace{FLAG=MYDRIVER_ALL_INFO}(LEVEL, MSG, ...); +// FUNC TraceEvents(LEVEL, FLAGS, MSG, ...); +// end_wpp +// \ No newline at end of file diff --git a/AmtPtpDeviceUsbUm/include/resource.h b/AmtPtpDeviceUsbUm/include/resource.h new file mode 100644 index 0000000..f214f6b --- /dev/null +++ b/AmtPtpDeviceUsbUm/include/resource.h @@ -0,0 +1,16 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by Resource.rc + +#define IDS_APP_TITLE 103 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 101 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/README.md b/README.md new file mode 100644 index 0000000..41297e3 --- /dev/null +++ b/README.md @@ -0,0 +1,29 @@ +This is the Precision Touchpad driver for the Magic Trackpad 2 that I personally use on my PCs. It is based entirely on the excellent [imbushuo driver](https://github.com/imbushuo/mac-precision-touchpad) and solves a number of problems explained in the "Context" section below. This is an image of the Control Panel: (each option is an additional feature built on top of the "vanilla" imbushuo driver) + +![Control Panel](https://raw.githubusercontent.com/vitoplantamura/MagicTrackpad2ForWindows/master/assets/ControlPanel.png) + +# Context + +In terms of hardware, the Magic Trackpad 2 is the best external touchpad you can buy (not just for macOS), by far. In terms of software, specifically for Windows, AFAIK there are 4 options to use it: Trackpad++, Magic Utilities, the excellent [open source driver by imbushuo](https://github.com/imbushuo/mac-precision-touchpad) and the [official 2021 Apple driver](https://github.com/lc700x/MagicTrackPad2_Windows_Precision_Drivers). In my opinion the two options that offer the best feeling, experience and stability (using the MT2 via USB at least) are the last two (imbushuo and Apple drivers) which coincidentally are extremely similar according to my tests (in terms of "feeling", and they are the only 2 that present the MT2 as a Precision Touchpad to Windows). Unfortunately these two solutions present different pointer precision problems. One problem is that of "near field fingers", i.e. the trackpad registers movements and clicks even without physically touching the trackpad surface, at a distance of even one millimeter from the surface. Another issue (determined by the first) is the accuracy of the pointer when you lift your finger from the trackpad. Furthermore, AFAIK, with both the imbushuo driver and the Apple one, it is not possible to adjust the behavior of the haptic feedback. + +A few months ago, I discovered [this excellent PR](https://github.com/imbushuo/mac-precision-touchpad/pull/533) of [1Revenger1](https://github.com/1Revenger1) to the imbushuo repo (which unfortunately hasn't been updated for 3 years). This PR solves the "near field fingers" problem. It also removes the QueryPerformanceCounter call in the interrupt function, instead setting the timestamp of the reports to the value returned by the Magic Trackpad itself (this may seem secondary but it is important, since using the MT2 in conditions of heavy PC load can determine returning inaccurate timestamps due to delayed thread scheduling). This PR convinced me that it might be worth investing some time in trying to solve all the other problems that made using MT2 more uncomfortable on PC than on macOS: I added the Control Panel, the ability to control the MT2's haptic feedback and other pointer precision options which I personally found useful. + +I'm really happy with the result: the feeling of the MT2 is identical to that of the touchpad of my laptop and very similar to that of the MT2 when used in macOS (pointer acceleration is different, but this is not determined by the driver). + +**Additional Credits**: The haptic feedback control messages sent by the driver to the MT2 in this project are based on the excellent reverse engineering work of [dos1](https://github.com/dos1) ([here](https://github.com/mwyborski/Linux-Magic-Trackpad-2-Driver/issues/28#issuecomment-451625504)). + +**License**: This project has the same license as the imbushuo project, on which it is entirely based. + +# Installation + +**NOTE**: Only for the MT2 when connected via USB! Bluetooth not supported. ARM64 not supported (at the moment). + +1) Connect the MT2 to the PC via USB and first install the imbushuo driver: download [this file](https://github.com/imbushuo/mac-precision-touchpad/releases/download/2105-3979/Drivers-amd64-ReleaseMSSigned.zip), unzip it, right-click on the INF file and click "Install". + +2) Download the two files of this project from the Releases of this repo (save them in the same directory), start the Control Panel and click on "Install Driver". + +# How the Installation Works + +The imbushuo MT2 USB driver is a UMDF driver. Windows Driver Signature Enforcement does not block the loading of self-signed UMDF drivers. This unfortunately does not apply to KMDF drivers such as the imbushuo bluetooth driver for the MT2 and this is the reason why this project only supports the USB version of the driver. On a personal note I prefer to use the MT2 via USB: the MT2 can be switched between different computers without problems, no worries about the battery, the driver cannot bluescreen the PC and the USB version of the imbushuo driver in particular has proven to be very stable over the years. + +When you click on "Install Driver" in the Control Panel, all MT2s connected to the system are disabled and then the AmtPtpDeviceUsbUm.dll file is updated directly in the Windows Driver Store. The owner and ACL of the file are modified to allow copying, and are restored to their original state at the end of the procedure. At the end of the copy, the MT2 is re-enabled. This procedure is not supported by Microsoft, but is commonly used during driver development and does not put system security at risk (as long as the file you are copying is trusted). diff --git a/assets/ControlPanel.png b/assets/ControlPanel.png new file mode 100644 index 0000000000000000000000000000000000000000..fb8270b32ba4aafb87201c0b27a21eeaff5e4fea GIT binary patch literal 35382 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�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