diff --git a/BitInfo.txt b/BitInfo.txt
new file mode 100644
index 0000000..ab0de5d
--- /dev/null
+++ b/BitInfo.txt
@@ -0,0 +1,58 @@
+BIT ARRAY
+00719780
+
+INGAME:
+00 - NULL
+01 - digital throttle
+02 - digital reverse
+03 - digital left
+04 - digital right
+05 - ebrake
+06 - NOS
+07 - shift up
+08 - shift down
+09 - look back
+10 - change cam
+11 - P2 start / debug camera spawn car / 5 key and Q key
+12 - pause
+13 - unk
+14 - reset car
+15 - unk
+FE CONTROLS
+16 - up
+17 - down
+18 - left
+19 - right
+20 - accept
+21 - ? start?
+22 - H key (HELP?)
+23 - back
+24 - C key - select?
+25 - L2
+26 - R2
+27 - left shoulder
+28 - right shoulder
+29 - car orbit up
+30 - car orbit left
+31 - car orbit down
+from 00719784 ( counting up bits with 32 offset )
+32 - car orbit right
+// Player 2???
+33 - HOME key
+34 - END key
+35 - P key?
+36 - quit game / Q key
+37 - R key
+38 - debug camera up
+39 - debug camera down
+40 - d.c. left
+41 - d.c. right
+42 - U key
+43 - Y key
+44 - Z key
+45 - colon
+46 - quote " key
+47 - T key / 4 ingame - debug camera turbo
+48 - 6 key - debug camera super turbo
+50 - O key
+51 - activate debug camera
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..4a97a60
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2022 Lovro Pleše
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/NFSU_ConsoleButtonHashes.h b/NFSU_ConsoleButtonHashes.h
new file mode 100644
index 0000000..a105893
--- /dev/null
+++ b/NFSU_ConsoleButtonHashes.h
@@ -0,0 +1,28 @@
+#pragma once
+
+#define XBOXA_HASH 0x0408D061
+#define XBOXB_HASH 0x0408D062
+#define XBOXX_HASH 0x0408D078
+#define XBOXY_HASH 0x0408D079
+#define XBOXBACK_HASH 0x594DAA91
+#define XBOXSTART_HASH 0x8441012E
+#define XBOXLT_HASH 0x8522DE40
+
+#define PSCIRCLE_HASH 0xF6D884F4
+#define PSCROSS_HASH 0xE8780F8C
+#define PSL2_HASH 0x001B30E0
+#define PSOPTIONS_HASH 0x7E1FA26E
+#define PSSHARE_HASH 0xE993E015
+#define PSSQUARE_HASH 0x1CBD7073
+#define PSTRIANGLE_HASH 0x5F176458
+
+#define PC_TUTORIAL 0xA35C52C5
+#define PC_X 0x001AEF69
+#define PC_PERFORMANCE 0x04BDFDA3
+#define PC_HELP 0xC51A8C5A
+#define PC_DELETE 0x6C9A44A4
+#define PC_CREATEROOM 0x4073AF02
+#define PC_CUSTOM 0x6B6AB9EC
+#define PC_BACK 0xC5172FE2
+#define PC_QUIT 0xC51FBF74
+
diff --git a/NFSU_JoyBitInfo.h b/NFSU_JoyBitInfo.h
new file mode 100644
index 0000000..288d280
--- /dev/null
+++ b/NFSU_JoyBitInfo.h
@@ -0,0 +1,56 @@
+#pragma once
+
+#define NFSUJOY_NULL 0
+#define NFSUJOY_DIGITAL_THROTTLE 1
+#define NFSUJOY_DIGITAL_REVERSE 2
+#define NFSUJOY_DIGITAL_LEFT 3
+#define NFSUJOY_DIGITAL_RIGHT 4
+#define NFSUJOY_EBRAKE 5
+#define NFSUJOY_NOS 6
+#define NFSUJOY_SHIFTUP 7
+#define NFSUJOY_SHIFTDOWN 8
+#define NFSUJOY_LOOKBACK 9
+#define NFSUJOY_CHANGECAM 10
+#define NFSUJOY_BIT11 11
+#define NFSUJOY_PAUSE 12
+#define NFSUJOY_BIT13 13
+#define NFSUJOY_RESETCAR 14
+#define NFSUJOY_BIT15 15
+#define NFSUJOY_FE_UP 16
+#define NFSUJOY_FE_DOWN 17
+#define NFSUJOY_FE_LEFT 18
+#define NFSUJOY_FE_RIGHT 19
+#define NFSUJOY_FE_ACCEPT 20
+#define NFSUJOY_FE_BIT21 21
+#define NFSUJOY_FE_BIT22 22
+#define NFSUJOY_FE_BACK 23
+#define NFSUJOY_FE_BIT24 24
+#define NFSUJOY_FE_L2 25
+#define NFSUJOY_FE_R2 26
+#define NFSUJOY_FE_L1 27
+#define NFSUJOY_FE_R1 28
+#define NFSUJOY_FE_ORBIT_UP 29
+#define NFSUJOY_FE_ORBIT_LEFT 30
+#define NFSUJOY_FE_ORBIT_DOWN 31
+// second bits start from zero
+#define NFSUJOY_FE_ORBIT_RIGHT 0
+#define NFSUJOY_BIT33 1
+#define NFSUJOY_BIT34 2
+#define NFSUJOY_BIT35 3
+#define NFSUJOY_BIT36 4
+#define NFSUJOY_BIT37 5
+#define NFSUJOY_DEBUGCAM_LOOKUP 6
+#define NFSUJOY_DEBUGCAM_LOOKDOWN 7
+#define NFSUJOY_DEBUGCAM_LOOKLEFT 8
+#define NFSUJOY_DEBUGCAM_LOOKRIGHT 9
+#define NFSUJOY_DEBUGCAM_MOVEDOWN 10
+#define NFSUJOY_DEBUGCAM_MOVEFORWARD 11
+#define NFSUJOY_DEBUGCAM_MOVEBACKWARD 12
+#define NFSUJOY_DEBUGCAM_MOVELEFT 13
+#define NFSUJOY_DEBUGCAM_MOVERIGHT 14
+#define NFSUJOY_DEBUGCAM_TURBO 15
+#define NFSUJOY_DEBUGCAM_SUPERTURBO 16
+#define NFSUJOY_DEBUGCAM_SLOW 17
+#define NFSUJOY_DEBUGCAM_MOVEUP 18
+#define NFSUJOY_DEBUGCAM_ACTIVATE 19
+// other bits unknown...
\ No newline at end of file
diff --git a/NFSU_XtendedInput.filters b/NFSU_XtendedInput.filters
new file mode 100644
index 0000000..95c6df3
--- /dev/null
+++ b/NFSU_XtendedInput.filters
@@ -0,0 +1,33 @@
+
+
+
+
+ {4FC737F1-C7A5-4376-A066-2A32D752A2FF}
+ cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx
+
+
+ {93995380-89BD-4b04-88EB-625FBE52EBFB}
+ h;hh;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
+
+
+
+
+ Header Files
+
+
+ Header Files
+
+
+
+
+ Source Files
+
+
+ Source Files
+
+
+
\ No newline at end of file
diff --git a/NFSU_XtendedInput.ini b/NFSU_XtendedInput.ini
new file mode 100644
index 0000000..c08546e
--- /dev/null
+++ b/NFSU_XtendedInput.ini
@@ -0,0 +1,3 @@
+[Icons]
+ControllerIconMode = 0 // 0 = Xbox, 1 = PlayStation 4 -- MAKE SURE TO DISABLE WIDESCREEN FIX'S BUTTON ICONS FIRST (they're dymamically changing!)
+FirstControlDevice = 0 // 0 = Keyboard, 1 = Controller -- for setting the first state
diff --git a/NFSU_XtendedInput.sln b/NFSU_XtendedInput.sln
new file mode 100644
index 0000000..0eca694
--- /dev/null
+++ b/NFSU_XtendedInput.sln
@@ -0,0 +1,22 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio 14
+VisualStudioVersion = 14.0.25420.1
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "DebugOpts", "NFSU_XtendedInput.vcxproj", "{3C558AD9-5F9C-4A14-8F07-800F46C132C7}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|x86 = Debug|x86
+ Release|x86 = Release|x86
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {3C558AD9-5F9C-4A14-8F07-800F46C132C7}.Debug|x86.ActiveCfg = Debug|Win32
+ {3C558AD9-5F9C-4A14-8F07-800F46C132C7}.Debug|x86.Build.0 = Debug|Win32
+ {3C558AD9-5F9C-4A14-8F07-800F46C132C7}.Release|x86.ActiveCfg = Release|Win32
+ {3C558AD9-5F9C-4A14-8F07-800F46C132C7}.Release|x86.Build.0 = Release|Win32
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+EndGlobal
diff --git a/NFSU_XtendedInput.sln.bak b/NFSU_XtendedInput.sln.bak
new file mode 100644
index 0000000..eae6243
--- /dev/null
+++ b/NFSU_XtendedInput.sln.bak
@@ -0,0 +1,22 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio 14
+VisualStudioVersion = 14.0.25420.1
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "DebugOpts", "ConsoleAttachment.vcxproj", "{3C558AD9-5F9C-4A14-8F07-800F46C132C7}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|x86 = Debug|x86
+ Release|x86 = Release|x86
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {3C558AD9-5F9C-4A14-8F07-800F46C132C7}.Debug|x86.ActiveCfg = Debug|Win32
+ {3C558AD9-5F9C-4A14-8F07-800F46C132C7}.Debug|x86.Build.0 = Debug|Win32
+ {3C558AD9-5F9C-4A14-8F07-800F46C132C7}.Release|x86.ActiveCfg = Release|Win32
+ {3C558AD9-5F9C-4A14-8F07-800F46C132C7}.Release|x86.Build.0 = Release|Win32
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+EndGlobal
diff --git a/NFSU_XtendedInput.vcxproj b/NFSU_XtendedInput.vcxproj
new file mode 100644
index 0000000..1c4e9aa
--- /dev/null
+++ b/NFSU_XtendedInput.vcxproj
@@ -0,0 +1,113 @@
+
+
+
+
+ Debug
+ Win32
+
+
+ Release
+ Win32
+
+
+
+ {3C558AD9-5F9C-4A14-8F07-800F46C132C7}
+ Win32Proj
+ QRHiddenOptions
+ NFSU_XtendedInput
+ 10.0
+
+
+
+ DynamicLibrary
+ true
+ v143
+ MultiByte
+
+
+ DynamicLibrary
+ false
+ v143
+ true
+ MultiByte
+
+
+
+
+
+
+
+
+
+
+
+
+ true
+ .asi
+ $(ProjectName)
+
+
+ false
+ .asi
+ $(ProjectName)
+
+
+
+ Use
+ Level3
+ Disabled
+ WIN32;_DEBUG;_WINDOWS;_USRDLL;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)
+
+
+ Windows
+ true
+
+
+
+
+ Level4
+ Use
+ MaxSpeed
+ true
+ true
+ WIN32;NDEBUG;_WINDOWS;_USRDLL;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)
+ MultiThreadedDLL
+
+
+ Windows
+ true
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+ false
+
+
+ false
+
+
+
+
+ Create
+ Create
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..0814c1a
--- /dev/null
+++ b/README.md
@@ -0,0 +1,99 @@
+# NFS Underground - Xtended Input
+
+This is an plugin for NFS Underground which brings XInput support to the game.
+
+Currently work in progress, but fully usable and playable!
+
+Planned to bring to other NFS games in the future!
+
+## Features
+
+- Automatic texture and text swapping for button icons - dynamically detects when you use either a keyboard or a controller (Disable Widescreen Fix's button icons to see the effect)
+
+- Choice between Xbox and PS4 icons (from WS Fix)
+
+## Button mappings
+
+They are currently fixed and can't be changed without recompiling the code, but it should suit most, if not all players.
+
+### Ingame
+
+- Throttle: Right Trigger
+
+- Brake: Left Trigger
+
+- Steering: Left Stick X axis
+
+- Digital steering: D-Pad Left & Right
+
+- E-Brake: A button
+
+- NOS: B button
+
+- Look back: X button
+
+- Change camera: Y button
+
+- Shift up/down: Right Stick Y axis up/down
+
+- Pause: Menu/Start
+
+- Quit game: Windows/Back button
+
+### FrontEnd
+
+- Navigation: D-Pad & Left Analog Stick
+
+- Accept: A button
+
+- Back: B button
+
+- Left shoulder & Right shoulder buttons: mapped to their respective equivalents
+
+- L2: Left Trigger
+
+- R2: Right Trigger
+
+- Quit game: Windows/Back button
+
+Some extra notes about FrontEnd: PC version has had some changes to console navigation and complicated some things
+
+- Vinyl color changing: X button
+
+This button is NOT marked anywhere on the screen because the menu has to normally be invoked by the mouse.
+
+### Debug camera
+
+Currently does NOT support analog inputs. You can only use digital inputs. You must use a controller on the second port to invoke it and use it.
+
+- Activation: Windows/Back
+
+- Move forward: A button
+
+- Move backward: Y button
+
+- Move left: X button
+
+- Move right: B button
+
+- Move up: Left Shoulder
+
+- Move down: Left Trigger
+
+- Look up/down/left/right: D-Pad
+
+- Turbo: Right Shoulder
+
+- Super Turbo: Right Trigger
+
+## TODO list:
+
+- Check the top of the source file, but to sum it up - lower level and more direct access to the game would be nice!
+
+- Currently KILLS Direct Input, beware
+
+# Credits
+
+- ThirteenAG & AeroWidescreen - for the great button icons!
+
+- LINK/2012 - injector
diff --git a/UG_ConsoleButtons/AAED1FC1.ini b/UG_ConsoleButtons/AAED1FC1.ini
new file mode 100644
index 0000000..66ff2a4
--- /dev/null
+++ b/UG_ConsoleButtons/AAED1FC1.ini
@@ -0,0 +1,426 @@
+[TPK]
+Name = CONSOLEBUTTONS
+Version = 4
+Filename = ConsoleButtons
+FilenameHash = 0xAAED1FC1
+Animations = 0
+
+[0408D061]
+File = UG_ConsoleButtons\AAED1FC1\XBOXA.dds
+Name = XBOXA
+ClassNameHash = 0x1A93CF
+ShiftWidth = 5
+ShiftHeight = 5
+ImageCompressionType = 0x24
+PaletteCompressionType = 0x0
+NumPaletteEntries = 0
+TilableUV = 0
+BiasLevel = 0
+RenderingOrder = 5
+ScrollType = 0
+UsedFlag = 0
+ApplyAlphaSorting = 0
+AlphaUsageType = 2
+AlphaBlendType = 1
+Flags = 0x0
+MipmapBiasType = 0
+ScrollTimeStep = 0
+ScrollSpeedS = 0
+ScrollSpeedT = 0
+OffsetS = 0
+OffsetT = 256
+ScaleS = 256
+ScaleT = 0
+PixelFormatUnk1 = 0x1
+PixelFormatUnk2 = 0x5
+PixelFormatUnk3 = 0x6
+
+[0408D062]
+File = UG_ConsoleButtons\AAED1FC1\XBOXB.dds
+Name = XBOXB
+ClassNameHash = 0x1A93CF
+ShiftWidth = 5
+ShiftHeight = 5
+ImageCompressionType = 0x24
+PaletteCompressionType = 0x0
+NumPaletteEntries = 0
+TilableUV = 0
+BiasLevel = 0
+RenderingOrder = 5
+ScrollType = 0
+UsedFlag = 0
+ApplyAlphaSorting = 0
+AlphaUsageType = 2
+AlphaBlendType = 1
+Flags = 0x0
+MipmapBiasType = 0
+ScrollTimeStep = 0
+ScrollSpeedS = 0
+ScrollSpeedT = 0
+OffsetS = 0
+OffsetT = 256
+ScaleS = 256
+ScaleT = 0
+PixelFormatUnk1 = 0x1
+PixelFormatUnk2 = 0x5
+PixelFormatUnk3 = 0x6
+
+[0408D078]
+File = UG_ConsoleButtons\AAED1FC1\XBOXX.dds
+Name = XBOXX
+ClassNameHash = 0x1A93CF
+ShiftWidth = 5
+ShiftHeight = 5
+ImageCompressionType = 0x24
+PaletteCompressionType = 0x0
+NumPaletteEntries = 0
+TilableUV = 0
+BiasLevel = 0
+RenderingOrder = 5
+ScrollType = 0
+UsedFlag = 0
+ApplyAlphaSorting = 0
+AlphaUsageType = 2
+AlphaBlendType = 1
+Flags = 0x0
+MipmapBiasType = 0
+ScrollTimeStep = 0
+ScrollSpeedS = 0
+ScrollSpeedT = 0
+OffsetS = 0
+OffsetT = 256
+ScaleS = 256
+ScaleT = 0
+PixelFormatUnk1 = 0x1
+PixelFormatUnk2 = 0x5
+PixelFormatUnk3 = 0x6
+
+[0408D079]
+File = UG_ConsoleButtons\AAED1FC1\XBOXY.dds
+Name = XBOXY
+ClassNameHash = 0x1A93CF
+ShiftWidth = 5
+ShiftHeight = 5
+ImageCompressionType = 0x24
+PaletteCompressionType = 0x0
+NumPaletteEntries = 0
+TilableUV = 0
+BiasLevel = 0
+RenderingOrder = 5
+ScrollType = 0
+UsedFlag = 0
+ApplyAlphaSorting = 0
+AlphaUsageType = 2
+AlphaBlendType = 1
+Flags = 0x0
+MipmapBiasType = 0
+ScrollTimeStep = 0
+ScrollSpeedS = 0
+ScrollSpeedT = 0
+OffsetS = 0
+OffsetT = 256
+ScaleS = 256
+ScaleT = 0
+PixelFormatUnk1 = 0x1
+PixelFormatUnk2 = 0x5
+PixelFormatUnk3 = 0x6
+
+[594DAA91]
+File = UG_ConsoleButtons\AAED1FC1\XBOXBACK.dds
+Name = XBOXBACK
+ClassNameHash = 0x1A93CF
+ShiftWidth = 5
+ShiftHeight = 5
+ImageCompressionType = 0x24
+PaletteCompressionType = 0x0
+NumPaletteEntries = 0
+TilableUV = 0
+BiasLevel = 0
+RenderingOrder = 5
+ScrollType = 0
+UsedFlag = 0
+ApplyAlphaSorting = 0
+AlphaUsageType = 2
+AlphaBlendType = 1
+Flags = 0x0
+MipmapBiasType = 0
+ScrollTimeStep = 0
+ScrollSpeedS = 0
+ScrollSpeedT = 0
+OffsetS = 0
+OffsetT = 256
+ScaleS = 256
+ScaleT = 0
+PixelFormatUnk1 = 0x1
+PixelFormatUnk2 = 0x5
+PixelFormatUnk3 = 0x6
+
+[8441012E]
+File = UG_ConsoleButtons\AAED1FC1\XBOXSTART.dds
+Name = XBOXSTART
+ClassNameHash = 0x1A93CF
+ShiftWidth = 5
+ShiftHeight = 5
+ImageCompressionType = 0x24
+PaletteCompressionType = 0x0
+NumPaletteEntries = 0
+TilableUV = 0
+BiasLevel = 0
+RenderingOrder = 5
+ScrollType = 0
+UsedFlag = 0
+ApplyAlphaSorting = 0
+AlphaUsageType = 2
+AlphaBlendType = 1
+Flags = 0x0
+MipmapBiasType = 0
+ScrollTimeStep = 0
+ScrollSpeedS = 0
+ScrollSpeedT = 0
+OffsetS = 0
+OffsetT = 256
+ScaleS = 256
+ScaleT = 0
+PixelFormatUnk1 = 0x1
+PixelFormatUnk2 = 0x5
+PixelFormatUnk3 = 0x6
+
+[8522DE40]
+File = UG_ConsoleButtons\AAED1FC1\XBOXLT.dds
+Name = XBOXLT
+ClassNameHash = 0x1A93CF
+ShiftWidth = 5
+ShiftHeight = 5
+ImageCompressionType = 0x24
+PaletteCompressionType = 0x0
+NumPaletteEntries = 0
+TilableUV = 0
+BiasLevel = 0
+RenderingOrder = 5
+ScrollType = 0
+UsedFlag = 0
+ApplyAlphaSorting = 0
+AlphaUsageType = 2
+AlphaBlendType = 1
+Flags = 0x0
+MipmapBiasType = 0
+ScrollTimeStep = 0
+ScrollSpeedS = 0
+ScrollSpeedT = 0
+OffsetS = 0
+OffsetT = 256
+ScaleS = 256
+ScaleT = 0
+PixelFormatUnk1 = 0x1
+PixelFormatUnk2 = 0x5
+PixelFormatUnk3 = 0x6
+
+[F6D884F4]
+File = UG_ConsoleButtons\AAED1FC1\PSCIRCLE.dds
+Name = PSCIRCLE
+ClassNameHash = 0x1A93CF
+ShiftWidth = 5
+ShiftHeight = 5
+ImageCompressionType = 0x24
+PaletteCompressionType = 0x0
+NumPaletteEntries = 0
+TilableUV = 0
+BiasLevel = 0
+RenderingOrder = 5
+ScrollType = 0
+UsedFlag = 0
+ApplyAlphaSorting = 0
+AlphaUsageType = 2
+AlphaBlendType = 1
+Flags = 0x0
+MipmapBiasType = 0
+ScrollTimeStep = 0
+ScrollSpeedS = 0
+ScrollSpeedT = 0
+OffsetS = 0
+OffsetT = 256
+ScaleS = 256
+ScaleT = 0
+PixelFormatUnk1 = 0x1
+PixelFormatUnk2 = 0x5
+PixelFormatUnk3 = 0x6
+
+[E8780F8C]
+File = UG_ConsoleButtons\AAED1FC1\PSCROSS.dds
+Name = PSCROSS
+ClassNameHash = 0x1A93CF
+ShiftWidth = 5
+ShiftHeight = 5
+ImageCompressionType = 0x24
+PaletteCompressionType = 0x0
+NumPaletteEntries = 0
+TilableUV = 0
+BiasLevel = 0
+RenderingOrder = 5
+ScrollType = 0
+UsedFlag = 0
+ApplyAlphaSorting = 0
+AlphaUsageType = 2
+AlphaBlendType = 1
+Flags = 0x0
+MipmapBiasType = 0
+ScrollTimeStep = 0
+ScrollSpeedS = 0
+ScrollSpeedT = 0
+OffsetS = 0
+OffsetT = 256
+ScaleS = 256
+ScaleT = 0
+PixelFormatUnk1 = 0x1
+PixelFormatUnk2 = 0x5
+PixelFormatUnk3 = 0x6
+
+[001B30E0]
+File = UG_ConsoleButtons\AAED1FC1\PSL2.dds
+Name = PSL2
+ClassNameHash = 0x1A93CF
+ShiftWidth = 5
+ShiftHeight = 5
+ImageCompressionType = 0x24
+PaletteCompressionType = 0x0
+NumPaletteEntries = 0
+TilableUV = 0
+BiasLevel = 0
+RenderingOrder = 5
+ScrollType = 0
+UsedFlag = 0
+ApplyAlphaSorting = 0
+AlphaUsageType = 2
+AlphaBlendType = 1
+Flags = 0x0
+MipmapBiasType = 0
+ScrollTimeStep = 0
+ScrollSpeedS = 0
+ScrollSpeedT = 0
+OffsetS = 0
+OffsetT = 256
+ScaleS = 256
+ScaleT = 0
+PixelFormatUnk1 = 0x1
+PixelFormatUnk2 = 0x5
+PixelFormatUnk3 = 0x6
+
+[7E1FA26E]
+File = UG_ConsoleButtons\AAED1FC1\PSOPTIONS.dds
+Name = PSOPTIONS
+ClassNameHash = 0x1A93CF
+ShiftWidth = 5
+ShiftHeight = 5
+ImageCompressionType = 0x24
+PaletteCompressionType = 0x0
+NumPaletteEntries = 0
+TilableUV = 0
+BiasLevel = 0
+RenderingOrder = 5
+ScrollType = 0
+UsedFlag = 0
+ApplyAlphaSorting = 0
+AlphaUsageType = 2
+AlphaBlendType = 1
+Flags = 0x0
+MipmapBiasType = 0
+ScrollTimeStep = 0
+ScrollSpeedS = 0
+ScrollSpeedT = 0
+OffsetS = 0
+OffsetT = 256
+ScaleS = 256
+ScaleT = 0
+PixelFormatUnk1 = 0x1
+PixelFormatUnk2 = 0x5
+PixelFormatUnk3 = 0x6
+
+[E993E015]
+File = UG_ConsoleButtons\AAED1FC1\PSSHARE.dds
+Name = PSSHARE
+ClassNameHash = 0x1A93CF
+ShiftWidth = 5
+ShiftHeight = 5
+ImageCompressionType = 0x24
+PaletteCompressionType = 0x0
+NumPaletteEntries = 0
+TilableUV = 0
+BiasLevel = 0
+RenderingOrder = 5
+ScrollType = 0
+UsedFlag = 0
+ApplyAlphaSorting = 0
+AlphaUsageType = 2
+AlphaBlendType = 1
+Flags = 0x0
+MipmapBiasType = 0
+ScrollTimeStep = 0
+ScrollSpeedS = 0
+ScrollSpeedT = 0
+OffsetS = 0
+OffsetT = 256
+ScaleS = 256
+ScaleT = 0
+PixelFormatUnk1 = 0x1
+PixelFormatUnk2 = 0x5
+PixelFormatUnk3 = 0x6
+
+[1CBD7073]
+File = UG_ConsoleButtons\AAED1FC1\PSSQUARE.dds
+Name = PSSQUARE
+ClassNameHash = 0x1A93CF
+ShiftWidth = 5
+ShiftHeight = 5
+ImageCompressionType = 0x24
+PaletteCompressionType = 0x0
+NumPaletteEntries = 0
+TilableUV = 0
+BiasLevel = 0
+RenderingOrder = 5
+ScrollType = 0
+UsedFlag = 0
+ApplyAlphaSorting = 0
+AlphaUsageType = 2
+AlphaBlendType = 1
+Flags = 0x0
+MipmapBiasType = 0
+ScrollTimeStep = 0
+ScrollSpeedS = 0
+ScrollSpeedT = 0
+OffsetS = 0
+OffsetT = 256
+ScaleS = 256
+ScaleT = 0
+PixelFormatUnk1 = 0x1
+PixelFormatUnk2 = 0x5
+PixelFormatUnk3 = 0x6
+
+[5F176458]
+File = UG_ConsoleButtons\AAED1FC1\PSTRIANGLE.dds
+Name = PSTRIANGLE
+ClassNameHash = 0x1A93CF
+ShiftWidth = 5
+ShiftHeight = 5
+ImageCompressionType = 0x24
+PaletteCompressionType = 0x0
+NumPaletteEntries = 0
+TilableUV = 0
+BiasLevel = 0
+RenderingOrder = 5
+ScrollType = 0
+UsedFlag = 0
+ApplyAlphaSorting = 0
+AlphaUsageType = 2
+AlphaBlendType = 1
+Flags = 0x0
+MipmapBiasType = 0
+ScrollTimeStep = 0
+ScrollSpeedS = 0
+ScrollSpeedT = 0
+OffsetS = 0
+OffsetT = 256
+ScaleS = 256
+ScaleT = 0
+PixelFormatUnk1 = 0x1
+PixelFormatUnk2 = 0x5
+PixelFormatUnk3 = 0x6
diff --git a/UG_ConsoleButtons/AAED1FC1/PSCIRCLE.dds b/UG_ConsoleButtons/AAED1FC1/PSCIRCLE.dds
new file mode 100644
index 0000000..cfb19c3
Binary files /dev/null and b/UG_ConsoleButtons/AAED1FC1/PSCIRCLE.dds differ
diff --git a/UG_ConsoleButtons/AAED1FC1/PSCROSS.dds b/UG_ConsoleButtons/AAED1FC1/PSCROSS.dds
new file mode 100644
index 0000000..71b066e
Binary files /dev/null and b/UG_ConsoleButtons/AAED1FC1/PSCROSS.dds differ
diff --git a/UG_ConsoleButtons/AAED1FC1/PSL2.dds b/UG_ConsoleButtons/AAED1FC1/PSL2.dds
new file mode 100644
index 0000000..f57815a
Binary files /dev/null and b/UG_ConsoleButtons/AAED1FC1/PSL2.dds differ
diff --git a/UG_ConsoleButtons/AAED1FC1/PSOPTIONS.dds b/UG_ConsoleButtons/AAED1FC1/PSOPTIONS.dds
new file mode 100644
index 0000000..8837e2e
Binary files /dev/null and b/UG_ConsoleButtons/AAED1FC1/PSOPTIONS.dds differ
diff --git a/UG_ConsoleButtons/AAED1FC1/PSSHARE.dds b/UG_ConsoleButtons/AAED1FC1/PSSHARE.dds
new file mode 100644
index 0000000..1cc4aaa
Binary files /dev/null and b/UG_ConsoleButtons/AAED1FC1/PSSHARE.dds differ
diff --git a/UG_ConsoleButtons/AAED1FC1/PSSQUARE.dds b/UG_ConsoleButtons/AAED1FC1/PSSQUARE.dds
new file mode 100644
index 0000000..887ba30
Binary files /dev/null and b/UG_ConsoleButtons/AAED1FC1/PSSQUARE.dds differ
diff --git a/UG_ConsoleButtons/AAED1FC1/PSTRIANGLE.dds b/UG_ConsoleButtons/AAED1FC1/PSTRIANGLE.dds
new file mode 100644
index 0000000..4cb9e1a
Binary files /dev/null and b/UG_ConsoleButtons/AAED1FC1/PSTRIANGLE.dds differ
diff --git a/UG_ConsoleButtons/AAED1FC1/XBOXA.dds b/UG_ConsoleButtons/AAED1FC1/XBOXA.dds
new file mode 100644
index 0000000..d276eee
Binary files /dev/null and b/UG_ConsoleButtons/AAED1FC1/XBOXA.dds differ
diff --git a/UG_ConsoleButtons/AAED1FC1/XBOXB.dds b/UG_ConsoleButtons/AAED1FC1/XBOXB.dds
new file mode 100644
index 0000000..2912ee2
Binary files /dev/null and b/UG_ConsoleButtons/AAED1FC1/XBOXB.dds differ
diff --git a/UG_ConsoleButtons/AAED1FC1/XBOXBACK.dds b/UG_ConsoleButtons/AAED1FC1/XBOXBACK.dds
new file mode 100644
index 0000000..41cb5f5
Binary files /dev/null and b/UG_ConsoleButtons/AAED1FC1/XBOXBACK.dds differ
diff --git a/UG_ConsoleButtons/AAED1FC1/XBOXLT.dds b/UG_ConsoleButtons/AAED1FC1/XBOXLT.dds
new file mode 100644
index 0000000..f30de57
Binary files /dev/null and b/UG_ConsoleButtons/AAED1FC1/XBOXLT.dds differ
diff --git a/UG_ConsoleButtons/AAED1FC1/XBOXSTART.dds b/UG_ConsoleButtons/AAED1FC1/XBOXSTART.dds
new file mode 100644
index 0000000..6796016
Binary files /dev/null and b/UG_ConsoleButtons/AAED1FC1/XBOXSTART.dds differ
diff --git a/UG_ConsoleButtons/AAED1FC1/XBOXX.dds b/UG_ConsoleButtons/AAED1FC1/XBOXX.dds
new file mode 100644
index 0000000..b250b72
Binary files /dev/null and b/UG_ConsoleButtons/AAED1FC1/XBOXX.dds differ
diff --git a/UG_ConsoleButtons/AAED1FC1/XBOXY.dds b/UG_ConsoleButtons/AAED1FC1/XBOXY.dds
new file mode 100644
index 0000000..2005389
Binary files /dev/null and b/UG_ConsoleButtons/AAED1FC1/XBOXY.dds differ
diff --git a/dllmain.cpp b/dllmain.cpp
new file mode 100644
index 0000000..3218294
--- /dev/null
+++ b/dllmain.cpp
@@ -0,0 +1,767 @@
+// NFS Underground - Xtended Input plugin
+// Bringing native XInput to NFS
+// by Xan/Tenjoin
+
+// TODO: hook input scanners instead -- they're a better and more accurate target than the joypad input buffer
+// TODO: bring rumble/vibration function
+// TODO: remapping?
+// TODO: bring back P2 controls if possible
+// TODO: kill DInput enough so that it doesn't detect XInput controllers but still detects wheels
+// TODO: properly restore console FrontEnd objects (help messages, controller icons) -- partially done, HELP menu still needs to be restored
+
+#include "stdafx.h"
+#include "stdio.h"
+#include
+#include
+#include "NFSU_JoyBitInfo.h"
+#include "NFSU_ConsoleButtonHashes.h"
+#include "includes\injector\injector.hpp"
+#include "includes\IniReader.h"
+
+#if (_WIN32_WINNT >= 0x0602 /*_WIN32_WINNT_WIN8*/)
+#include
+#pragma comment(lib,"xinput.lib")
+#else
+#include
+#pragma comment(lib,"xinput9_1_0.lib")
+#endif
+
+#define MAX_CONTROLLERS 4 // XInput handles up to 4 controllers
+#define INPUT_DEADZONE ( 0.24f * FLOAT(0x7FFF) ) // Default to 24% of the +/- 32767 range. This is a reasonable default value but can be altered if needed.
+
+#define TRIGGER_ACTIVATION_THRESHOLD 0x20
+#define SHIFT_ANALOG_THRESHOLD 0x5000
+#define FEUPDOWN_ANALOG_THRESHOLD 0x3FFF
+
+#define JOYBUTTONS1_ADDR 0x00719780
+#define JOYBUTTONS2_ADDR 0x00719784
+#define THROTTLE_AXIS_ADDR 0x00719788
+#define BRAKE_AXIS_ADDR 0x00719789
+#define STEER_AXIS_ADDR 0x0071978A
+#define FE_WINDOWS_KEY_CODE_ADDR 0x007363B1
+#define GAMEFLOWMANAGER_STATUS_ADDR 0x0077A920
+#define CURRENT_MENUPKG_ADDR 0x72CDD0
+#define FEMOUSECURSOR_BUTTONPRESS_ADDR 0x007064B0
+#define FEMOUSECURSOR_CARORBIT_X_ADDR 0x007064A4
+#define FEMOUSECURSOR_CARORBIT_Y_ADDR 0x007064A8
+
+bool bCarOrbitState = 0;
+bool bCarOrbitOldState = 0;
+float CarOrbitDivisor = 0.5;
+
+bool bLoadedConsoleButtonTex = false;
+char LastFEngPackage[128];
+char CurrentSplashText[64];
+
+#define LASTCONTROLLED_KB 0
+#define LASTCONTROLLED_CONTROLLER 1
+unsigned int LastControlledDevice = 0; // 0 = keyboard, 1 = controller
+unsigned int LastControlledDeviceOldState = 0;
+
+#define CONTROLLERICON_XBOXONE 0
+#define CONTROLLERICON_PS4 1
+unsigned int ControllerIconMode = 0; // 0 = Xbox (One and later only for now), 1 = PlayStation (4 only now) -- planned to add: Nintendo (Wii/U Classic Controller, Switch), PS3/PS2, Xbox 360
+
+// for triggering the over-zelaous inputs once in a tick...
+WORD bQuitButtonOldState = 0;
+WORD bZButtonOldState = 0;
+WORD bXButtonOldState = 0;
+
+struct CONTROLLER_STATE
+{
+ XINPUT_STATE state;
+ bool bConnected;
+}g_Controllers[MAX_CONTROLLERS];
+
+enum ResourceFileType
+{
+ RESOURCE_FILE_NONE = 0,
+ RESOURCE_FILE_GLOBAL = 1,
+ RESOURCE_FILE_FRONTEND = 2,
+ RESOURCE_FILE_INGAME = 3,
+ RESOURCE_FILE_TRACK = 4,
+ RESOURCE_FILE_NIS = 5,
+ RESOURCE_FILE_CAR = 6,
+ RESOURCE_FILE_LANGUAGE = 7,
+ RESOURCE_FILE_REPLAY = 8,
+};
+
+void*(*CreateResourceFile)(char* filename, int ResType, int unk1, int unk2, int unk3) = (void*(*)(char*, int, int, int, int))0x004482F0;
+void(*ServiceResourceLoading)() = (void(*)())0x004483C0;
+unsigned int(*GetTextureInfo)(unsigned int hash, int unk, int unk2) = (unsigned int(*)(unsigned int, int, int))0x005461C0;
+
+unsigned int GameWndProcAddr = 0;
+LRESULT(WINAPI* GameWndProc)(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
+
+LRESULT WINAPI CustomWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
+{
+ if (msg == WM_KEYDOWN)
+ LastControlledDevice = LASTCONTROLLED_KB;
+
+ return GameWndProc(hWnd, msg, wParam, lParam);
+}
+
+unsigned int ResourceFile_BeginLoading_Addr = 0x00448110;
+void __stdcall ResourceFile_BeginLoading(void* ResourceFile, void* unk1, int unk2)
+{
+ _asm
+ {
+ mov edx, ResourceFile
+ mov ecx, unk2
+ mov eax, unk1
+ call ResourceFile_BeginLoading_Addr
+ }
+}
+
+void __stdcall LoadResourceFile(char* filename, int ResType, int unk1, void* unk2, int unk3, int unk4, int unk5)
+{
+ ResourceFile_BeginLoading(CreateResourceFile(filename, ResType, unk1, unk4, unk5), unk2, unk3);
+}
+
+unsigned int FEHashUpper_Addr = 0x004FD230;
+unsigned int __stdcall FEHashUpper(const char* instr)
+{
+ unsigned int result = 0;
+ _asm
+ {
+ mov edx, instr
+ call FEHashUpper_Addr
+ mov result, eax
+ }
+ return result;
+}
+
+// entrypoint: 0x004B0CB9
+unsigned int FECarOrbitCave_Exit1 = 0x004B0CC1;
+unsigned int FECarOrbitCave_Exit2 = 0x4B0D37;
+static bool bOrbitingWithRightStick = 0;
+void __declspec(naked) FECarOrbitCave()
+{
+ // using assembly here because MSVC insists on using the ebp register...
+ _asm
+ {
+ mov al, ds:FEMOUSECURSOR_BUTTONPRESS_ADDR
+ mov cl, bOrbitingWithRightStick
+ or al, cl
+ mov bCarOrbitState, al
+ }
+
+ if (bCarOrbitState != bCarOrbitOldState)
+ {
+ // on the change to false, for precisely 1 tick allow the variables to be updated
+ _asm
+ {
+ mov al, bCarOrbitState
+ mov bCarOrbitOldState, al
+ jmp FECarOrbitCave_Exit1
+ }
+ }
+
+ if (bCarOrbitState)
+ _asm jmp FECarOrbitCave_Exit1
+ _asm jmp FECarOrbitCave_Exit2
+}
+
+// texture & control stuff
+unsigned int FEPkgMgr_FindPackage_Addr = 0x004F65D0;
+unsigned int FEPackageManager_FindPackage_Addr = 0x004F3F90;
+unsigned int FEngFindObject_Addr = 0x004FFB70;
+unsigned int FEngSendMessageToPackage_Addr = 0x004C96C0;
+unsigned int FEPrintf_Addr = 0x004F68A0;
+#pragma runtime_checks( "", off )
+int __stdcall FEPrintf(void* FEString, char* format)
+{
+ _asm
+ {
+ mov eax, FEString
+ push format
+ call FEPrintf_Addr
+ }
+}
+#pragma runtime_checks( "", restore )
+void __stdcall FEngSendMessageToPackage(unsigned int msg, char* dest)
+{
+ _asm
+ {
+ push msg
+ mov eax, dest
+ call FEngSendMessageToPackage_Addr
+ add esp, 4
+ }
+}
+
+unsigned int __stdcall FEPkgMgr_FindPackage(const char* pkgname)
+{
+ unsigned int result = 0;
+ _asm
+ {
+ mov eax, pkgname
+ call FEPkgMgr_FindPackage_Addr
+ mov result, eax
+ }
+ return result;
+}
+
+unsigned int __stdcall FEPackageManager_FindPackage(const char* pkgname)
+{
+ unsigned int result = 0;
+ _asm
+ {
+ push 0x00746104
+ mov eax, pkgname
+ call FEPackageManager_FindPackage_Addr
+ mov result, eax
+ }
+ return result;
+}
+
+unsigned int __stdcall _FEngFindObject(const char* pkg, int hash)
+{
+ unsigned int result = 0;
+ _asm
+ {
+ mov ecx, hash
+ mov edx, pkg
+ call FEngFindObject_Addr
+ mov result, eax
+ }
+ return result;
+}
+
+unsigned int __stdcall FEngFindObject(const char* pkg, int hash)
+{
+ char* pkgname = (char*)FEPkgMgr_FindPackage(pkg);
+ if (!pkgname)
+ return 0;
+ else
+ return _FEngFindObject(pkgname, hash);
+}
+
+unsigned int __stdcall FEngSetTextureHash(unsigned int FEImage, int hash)
+{
+ int v2;
+
+ if (FEImage)
+ {
+ if (*(int*)(FEImage + 36) != hash)
+ {
+ v2 = *(int*)(FEImage + 28);
+ *(int*)(FEImage + 36) = hash;
+ *(int*)(FEImage + 28) = v2 | 0x400000;
+ }
+ }
+ return FEImage;
+}
+
+int __stdcall FEngFindString(const char* pkgname, int hash)
+{
+ int result; // r3
+
+ result = FEngFindObject(pkgname, hash);
+ if (!result || *(int*)(result + 24) != 2)
+ result = 0;
+ return result;
+}
+
+int __stdcall FEngFindImage(const char* pkgname, int hash)
+{
+ int result;
+
+ result = FEngFindObject(pkgname, hash);
+ if (!result || *(int*)(result + 24) != 1)
+ result = 0;
+ return result;
+}
+
+void SnoopLastFEPackage(char* format, char* pkg)
+{
+ strcpy(LastFEngPackage, pkg);
+}
+
+void SetPCFEButtons(char* pkgname)
+{
+ int FEImage = 0;
+ if (FEImage = FEngFindImage(pkgname, 0x06749436)) // DeleteProfile_hotkey
+ FEngSetTextureHash(FEImage, PC_DELETE);
+ if (FEImage = FEngFindImage(pkgname, 0xA7615A5D)) // DeletePersona_hotkey
+ FEngSetTextureHash(FEImage, PC_DELETE);
+ if (FEImage = FEngFindImage(pkgname, 0x6E1D75B7)) // Default_hotkey
+ FEngSetTextureHash(FEImage, PC_CREATEROOM);
+ if (FEImage = FEngFindImage(pkgname, 0x83A65176)) // Stock_hotkey
+ FEngSetTextureHash(FEImage, PC_CREATEROOM);
+ if (FEImage = FEngFindImage(pkgname, 0xC8754FA4)) // Performance_hotkey
+ FEngSetTextureHash(FEImage, PC_PERFORMANCE);
+ if (FEImage = FEngFindImage(pkgname, 0xA16A1877)) // Changecolor_hotkey -- during car color changer
+ FEngSetTextureHash(FEImage, PC_X);
+ if (FEImage = FEngFindImage(pkgname, 0xBDD6AEEA)) // DecalColor_hotkey
+ FEngSetTextureHash(FEImage, PC_CREATEROOM);
+ if (FEImage = FEngFindImage(pkgname, 0x548F2015)) // Quit_hotkey
+ FEngSetTextureHash(FEImage, PC_QUIT);
+ if (FEImage = FEngFindImage(pkgname, 0x55DAFA35)) // customize_hotkey
+ FEngSetTextureHash(FEImage, PC_CUSTOM);
+ if (FEImage = FEngFindImage(pkgname, 0x571A8D51)) // Next_hotkey -- the "accept" button
+ FEngSetTextureHash(FEImage, PC_X);
+ if (FEImage = FEngFindImage(pkgname, 0x2A1208C3)) // Back_hotkey
+ FEngSetTextureHash(FEImage, PC_BACK);
+ if (FEImage = FEngFindImage(pkgname, 0x6340B325)) // PC_tutorial_01 (tutorial key)
+ FEngSetTextureHash(FEImage, PC_TUTORIAL);
+ if (FEImage = FEngFindImage(pkgname, 0x6340B326)) // PC_tutorial_02 (tutorial key)
+ FEngSetTextureHash(FEImage, PC_TUTORIAL);
+
+ // set splash screen text
+ if (FEHashUpper(pkgname) == 0x2729D8C3) // if we're in LS_Splash_PC.fng
+ {
+ wcstombs(CurrentSplashText, *(wchar_t**)(FEngFindString("LS_Splash_PC.fng", 0x13CF446D) + 0x60), 0x800);
+ if (FEHashUpper(CurrentSplashText) != 0xE7126192) // "Press enter to continue" -- TODO: multi-lingual support...
+ FEPrintf((void*)FEngFindString("LS_Splash_PC.fng", 0x13CF446D), "Press enter to continue"); // mouse_click text object
+ }
+}
+
+void SetPCIGButtons(char* pkgname)
+{
+ int FEImage = 0;
+ if (FEImage = FEngFindImage(pkgname, 0x571A8D51)) // Next_hotkey -- the "accept" button
+ FEngSetTextureHash(FEImage, PC_X);
+ if (FEImage = FEngFindImage(pkgname, 0x2A1208C3)) // Back_hotkey
+ FEngSetTextureHash(FEImage, PC_BACK);
+ if (FEImage = FEngFindImage(pkgname, 0x548F2015)) // Quit_hotkey
+ FEngSetTextureHash(FEImage, PC_QUIT);
+}
+
+void SetXboxFEButtons(char* pkgname)
+{
+ int FEImage = 0;
+ if (FEImage = FEngFindImage(pkgname, 0x06749436)) // DeleteProfile_hotkey
+ FEngSetTextureHash(FEImage, XBOXY_HASH);
+ if (FEImage = FEngFindImage(pkgname, 0xA7615A5D)) // DeletePersona_hotkey
+ FEngSetTextureHash(FEImage, XBOXY_HASH);
+ if (FEImage = FEngFindImage(pkgname, 0x6E1D75B7)) // Default_hotkey
+ FEngSetTextureHash(FEImage, XBOXY_HASH);
+ if (FEImage = FEngFindImage(pkgname, 0x83A65176)) // Stock_hotkey
+ FEngSetTextureHash(FEImage, XBOXY_HASH);
+ //if (FEImage = FEngFindImage(pkgname, 0xAD7303C0)) // CreateGame_hotkey
+ // FEngSetTextureHash(FEImage, XBOXY_HASH);
+ if (FEImage = FEngFindImage(pkgname, 0xC8754FA4)) // Performance_hotkey
+ FEngSetTextureHash(FEImage, XBOXX_HASH);
+ if (FEImage = FEngFindImage(pkgname, 0xA16A1877)) // Changecolor_hotkey -- during car color changer
+ FEngSetTextureHash(FEImage, XBOXA_HASH);
+ if (FEImage = FEngFindImage(pkgname, 0xBDD6AEEA)) // DecalColor_hotkey
+ FEngSetTextureHash(FEImage, XBOXY_HASH);
+ if (FEImage = FEngFindImage(pkgname, 0x548F2015)) // Quit_hotkey
+ FEngSetTextureHash(FEImage, XBOXBACK_HASH);
+ if (FEImage = FEngFindImage(pkgname, 0x55DAFA35)) // customize_hotkey
+ FEngSetTextureHash(FEImage, XBOXX_HASH);
+ if (FEImage = FEngFindImage(pkgname, 0x571A8D51)) // Next_hotkey -- the "accept" button
+ FEngSetTextureHash(FEImage, XBOXA_HASH);
+ if (FEImage = FEngFindImage(pkgname, 0x2A1208C3)) // Back_hotkey
+ FEngSetTextureHash(FEImage, XBOXB_HASH);
+ if (FEImage = FEngFindImage(pkgname, 0x6340B325)) // PC_tutorial_01 (tutorial key)
+ FEngSetTextureHash(FEImage, XBOXLT_HASH);
+ if (FEImage = FEngFindImage(pkgname, 0x6340B326)) // PC_tutorial_02 (tutorial key)
+ FEngSetTextureHash(FEImage, XBOXLT_HASH);
+
+
+
+ // set splash screen text
+ if (FEHashUpper(pkgname) == 0x2729D8C3) // if we're in LS_Splash_PC.fng
+ {
+ wcstombs(CurrentSplashText, *(wchar_t**)(FEngFindString("LS_Splash_PC.fng", 0x13CF446D) + 0x60), 0x800);
+ if (FEHashUpper(CurrentSplashText) != 0x544518FD) // "Press MENU button"
+ FEPrintf((void*)FEngFindString("LS_Splash_PC.fng", 0x13CF446D), "Press MENU/A button"); // mouse_click text object
+ }
+}
+
+void SetXboxIGButtons(char* pkgname)
+{
+ int FEImage = 0;
+ if (FEImage = FEngFindImage(pkgname, 0x571A8D51)) // Next_hotkey -- the "accept" button
+ FEngSetTextureHash(FEImage, XBOXA_HASH);
+ if (FEImage = FEngFindImage(pkgname, 0x2A1208C3)) // Back_hotkey
+ FEngSetTextureHash(FEImage, XBOXB_HASH);
+ if (FEImage = FEngFindImage(pkgname, 0x548F2015)) // Quit_hotkey
+ FEngSetTextureHash(FEImage, XBOXBACK_HASH);
+}
+
+void SetPlayStationFEButtons(char* pkgname)
+{
+ int FEImage = 0;
+ if (FEImage = FEngFindImage(pkgname, 0x06749436)) // DeleteProfile_hotkey
+ FEngSetTextureHash(FEImage, PSTRIANGLE_HASH);
+ if (FEImage = FEngFindImage(pkgname, 0xA7615A5D)) // DeletePersona_hotkey
+ FEngSetTextureHash(FEImage, PSTRIANGLE_HASH);
+ if (FEImage = FEngFindImage(pkgname, 0x6E1D75B7)) // Default_hotkey
+ FEngSetTextureHash(FEImage, PSTRIANGLE_HASH);
+ if (FEImage = FEngFindImage(pkgname, 0x83A65176)) // Stock_hotkey
+ FEngSetTextureHash(FEImage, PSTRIANGLE_HASH);
+ //if (FEImage = FEngFindImage(pkgname, 0xAD7303C0)) // CreateGame_hotkey
+ // FEngSetTextureHash(FEImage, PSTRIANGLE_HASH);
+ if (FEImage = FEngFindImage(pkgname, 0xC8754FA4)) // Performance_hotkey
+ FEngSetTextureHash(FEImage, PSSQUARE_HASH);
+ if (FEImage = FEngFindImage(pkgname, 0xA16A1877)) // Changecolor_hotkey -- during car color changer
+ FEngSetTextureHash(FEImage, PSCROSS_HASH);
+ if (FEImage = FEngFindImage(pkgname, 0xBDD6AEEA)) // DecalColor_hotkey
+ FEngSetTextureHash(FEImage, PSTRIANGLE_HASH);
+ if (FEImage = FEngFindImage(pkgname, 0x548F2015)) // Quit_hotkey
+ FEngSetTextureHash(FEImage, PSSHARE_HASH);
+ if (FEImage = FEngFindImage(pkgname, 0x55DAFA35)) // customize_hotkey
+ FEngSetTextureHash(FEImage, PSSQUARE_HASH);
+ if (FEImage = FEngFindImage(pkgname, 0x571A8D51)) // Next_hotkey -- the "accept" button
+ FEngSetTextureHash(FEImage, PSCROSS_HASH);
+ if (FEImage = FEngFindImage(pkgname, 0x2A1208C3)) // Back_hotkey
+ FEngSetTextureHash(FEImage, PSCIRCLE_HASH);
+ if (FEImage = FEngFindImage(pkgname, 0x6340B325)) // PC_tutorial_01 (tutorial key)
+ FEngSetTextureHash(FEImage, PSL2_HASH);
+ if (FEImage = FEngFindImage(pkgname, 0x6340B326)) // PC_tutorial_02 (tutorial key)
+ FEngSetTextureHash(FEImage, PSL2_HASH);
+
+ // set splash screen text
+ if (FEHashUpper(pkgname) == 0x2729D8C3) // if we're in LS_Splash_PC.fng
+ {
+ wcstombs(CurrentSplashText, *(wchar_t**)(FEngFindString("LS_Splash_PC.fng", 0x13CF446D) + 0x60), 0x800);
+ if (FEHashUpper(CurrentSplashText) != 0x0BACC2D4) // "Press OPTIONS button"
+ FEPrintf((void*)FEngFindString("LS_Splash_PC.fng", 0x13CF446D), "Press OPTIONS/X button"); // mouse_click text object
+ }
+}
+
+void SetPlayStationIGButtons(char* pkgname)
+{
+ int FEImage = 0;
+ if (FEImage = FEngFindImage(pkgname, 0x571A8D51)) // Next_hotkey -- the "accept" button
+ FEngSetTextureHash(FEImage, PSCROSS_HASH);
+ if (FEImage = FEngFindImage(pkgname, 0x2A1208C3)) // Back_hotkey
+ FEngSetTextureHash(FEImage, PSCIRCLE_HASH);
+ if (FEImage = FEngFindImage(pkgname, 0x548F2015)) // Quit_hotkey
+ FEngSetTextureHash(FEImage, PSSHARE_HASH);
+}
+
+void __stdcall SetControllerFEng(char* pkgname)
+{
+ if (!bLoadedConsoleButtonTex)
+ {
+ LoadResourceFile("GLOBAL\\UG_ConsoleButtons.tpk", RESOURCE_FILE_NONE, 0, NULL, 0, 0, 0);
+ ServiceResourceLoading();
+ bLoadedConsoleButtonTex = true;
+ }
+
+ if (*(int*)GAMEFLOWMANAGER_STATUS_ADDR == 3)
+ {
+ if (LastControlledDevice == LASTCONTROLLED_CONTROLLER)
+ {
+ switch (ControllerIconMode)
+ {
+ case CONTROLLERICON_PS4:
+ SetPlayStationFEButtons(pkgname);
+ break;
+ case CONTROLLERICON_XBOXONE:
+ default:
+ SetXboxFEButtons(pkgname);
+ break;
+ }
+ }
+ if (LastControlledDevice == LASTCONTROLLED_KB)
+ SetPCFEButtons(pkgname);
+
+ }
+ if (*(int*)GAMEFLOWMANAGER_STATUS_ADDR == 6)
+ {
+ if (LastControlledDevice == LASTCONTROLLED_CONTROLLER)
+ {
+ switch (ControllerIconMode)
+ {
+ case CONTROLLERICON_PS4:
+ SetPlayStationIGButtons(pkgname);
+ break;
+ case CONTROLLERICON_XBOXONE:
+ default:
+ SetXboxIGButtons(pkgname);
+ break;
+ }
+ }
+ if (LastControlledDevice == LASTCONTROLLED_KB)
+ SetPCIGButtons(pkgname);
+ }
+}
+
+// entrypoint: 0x004F7C08
+unsigned int FEngGlobalCaveExit = 0x004F7C0E;
+unsigned int FEngGlobalCaveEBP = 0;
+unsigned int FEngGlobalCaveESI = 0;
+char* CurrentFEngPackage = NULL;
+void __declspec(naked) FEngGlobalCave()
+{
+ _asm mov FEngGlobalCaveEBP, ebp
+ _asm mov FEngGlobalCaveESI, esi
+ CurrentFEngPackage = *(char**)(FEngGlobalCaveESI + 0xC);
+ SetControllerFEng(CurrentFEngPackage);
+ _asm
+ {
+ mov esi, FEngGlobalCaveESI
+ mov ebp, FEngGlobalCaveEBP
+ cmp ebp, 0xC519BFC3
+ jmp FEngGlobalCaveExit
+ }
+}
+
+void DummyFunc()
+{
+ return;
+}
+
+HRESULT UpdateControllerState()
+{
+ DWORD dwResult;
+
+ dwResult = XInputGetState(0, &g_Controllers[0].state);
+
+ if (dwResult == ERROR_SUCCESS)
+ g_Controllers[0].bConnected = true;
+ else
+ g_Controllers[0].bConnected = false;
+
+ dwResult = XInputGetState(1, &g_Controllers[1].state);
+
+ if (dwResult == ERROR_SUCCESS)
+ g_Controllers[1].bConnected = true;
+ else
+ g_Controllers[1].bConnected = false;
+
+ return S_OK;
+}
+
+void ReadXInput()
+{
+ std::bitset<32> b1(*(int*)JOYBUTTONS1_ADDR);
+ std::bitset<32> b2(*(int*)JOYBUTTONS2_ADDR);
+ bool bLastWasKeyboard = false;
+
+ if (g_Controllers[0].bConnected)
+ {
+ WORD wButtons = g_Controllers[0].state.Gamepad.wButtons;
+
+ // Zero value if thumbsticks are within the dead zone
+ if ((g_Controllers[0].state.Gamepad.sThumbLX < INPUT_DEADZONE &&
+ g_Controllers[0].state.Gamepad.sThumbLX > -INPUT_DEADZONE) &&
+ (g_Controllers[0].state.Gamepad.sThumbLY < INPUT_DEADZONE &&
+ g_Controllers[0].state.Gamepad.sThumbLY > -INPUT_DEADZONE))
+ {
+ g_Controllers[0].state.Gamepad.sThumbLX = 0;
+ g_Controllers[0].state.Gamepad.sThumbLY = 0;
+ }
+
+ if ((g_Controllers[0].state.Gamepad.sThumbRX < INPUT_DEADZONE &&
+ g_Controllers[0].state.Gamepad.sThumbRX > -INPUT_DEADZONE) &&
+ (g_Controllers[0].state.Gamepad.sThumbRY < INPUT_DEADZONE &&
+ g_Controllers[0].state.Gamepad.sThumbRY > -INPUT_DEADZONE))
+ {
+ g_Controllers[0].state.Gamepad.sThumbRX = 0;
+ g_Controllers[0].state.Gamepad.sThumbRY = 0;
+ }
+
+ if (wButtons || g_Controllers[0].state.Gamepad.sThumbLX || g_Controllers[0].state.Gamepad.sThumbLY || g_Controllers[0].state.Gamepad.sThumbRX || g_Controllers[0].state.Gamepad.sThumbRY || g_Controllers[0].state.Gamepad.bRightTrigger || g_Controllers[0].state.Gamepad.bLeftTrigger)
+ LastControlledDevice = LASTCONTROLLED_CONTROLLER;
+
+ *(unsigned char*)THROTTLE_AXIS_ADDR = g_Controllers[0].state.Gamepad.bRightTrigger;
+ *(unsigned char*)BRAKE_AXIS_ADDR = g_Controllers[0].state.Gamepad.bLeftTrigger;
+ *(char*)STEER_AXIS_ADDR = (char)(((float)(g_Controllers[0].state.Gamepad.sThumbLX) / (float)(0x7FFF)) * (float)(0x7F)) + 0x80;
+
+ b1[NFSUJOY_DIGITAL_LEFT] = b1[NFSUJOY_DIGITAL_LEFT] ? !(wButtons & XINPUT_GAMEPAD_DPAD_LEFT) : b1[NFSUJOY_DIGITAL_LEFT];
+ b1[NFSUJOY_DIGITAL_RIGHT] = b1[NFSUJOY_DIGITAL_RIGHT] ? !(wButtons & XINPUT_GAMEPAD_DPAD_RIGHT) : b1[NFSUJOY_DIGITAL_RIGHT];
+
+ b1[NFSUJOY_PAUSE] = b1[NFSUJOY_PAUSE] ? !(wButtons & XINPUT_GAMEPAD_START) : b1[NFSUJOY_PAUSE];
+ b1[NFSUJOY_NOS] = b1[NFSUJOY_NOS] ? !(wButtons & XINPUT_GAMEPAD_B) : b1[NFSUJOY_NOS];
+ b1[NFSUJOY_EBRAKE] = b1[NFSUJOY_EBRAKE] ? !(wButtons & XINPUT_GAMEPAD_A) : b1[NFSUJOY_EBRAKE];
+ b1[NFSUJOY_RESETCAR] = b1[NFSUJOY_RESETCAR] ? !(wButtons & XINPUT_GAMEPAD_BACK) : b1[NFSUJOY_RESETCAR];
+ b1[NFSUJOY_LOOKBACK] = b1[NFSUJOY_LOOKBACK] ? !(wButtons & XINPUT_GAMEPAD_X) : b1[NFSUJOY_LOOKBACK];
+ b1[NFSUJOY_CHANGECAM] = b1[NFSUJOY_CHANGECAM] ? !(wButtons & XINPUT_GAMEPAD_Y) : b1[NFSUJOY_CHANGECAM];
+ b1[NFSUJOY_SHIFTUP] = b1[NFSUJOY_SHIFTUP] ? !(g_Controllers[0].state.Gamepad.sThumbRY > SHIFT_ANALOG_THRESHOLD) : b1[NFSUJOY_SHIFTUP];
+ b1[NFSUJOY_SHIFTDOWN] = b1[NFSUJOY_SHIFTDOWN] ? !(g_Controllers[0].state.Gamepad.sThumbRY < -SHIFT_ANALOG_THRESHOLD) : b1[NFSUJOY_SHIFTDOWN];
+
+ b1[NFSUJOY_FE_UP] = b1[NFSUJOY_FE_UP] ? !(wButtons & XINPUT_GAMEPAD_DPAD_UP) : b1[NFSUJOY_FE_UP];
+ b1[NFSUJOY_FE_DOWN] = b1[NFSUJOY_FE_DOWN] ? !(wButtons & XINPUT_GAMEPAD_DPAD_DOWN) : b1[NFSUJOY_FE_DOWN];
+ b1[NFSUJOY_FE_UP] = b1[NFSUJOY_FE_UP] ? !(g_Controllers[0].state.Gamepad.sThumbLY > FEUPDOWN_ANALOG_THRESHOLD) : b1[NFSUJOY_FE_UP];
+ b1[NFSUJOY_FE_DOWN] = b1[NFSUJOY_FE_DOWN] ? !(g_Controllers[0].state.Gamepad.sThumbLY < -FEUPDOWN_ANALOG_THRESHOLD) : b1[NFSUJOY_FE_DOWN];
+
+ b1[NFSUJOY_FE_LEFT] = b1[NFSUJOY_FE_LEFT] ? !(wButtons & XINPUT_GAMEPAD_DPAD_LEFT) : b1[NFSUJOY_FE_LEFT];
+ b1[NFSUJOY_FE_RIGHT] = b1[NFSUJOY_FE_RIGHT] ? !(wButtons & XINPUT_GAMEPAD_DPAD_RIGHT) : b1[NFSUJOY_FE_RIGHT];
+ b1[NFSUJOY_FE_ACCEPT] = b1[NFSUJOY_FE_ACCEPT] ? !(wButtons & XINPUT_GAMEPAD_A) : b1[NFSUJOY_FE_ACCEPT];
+ b1[NFSUJOY_FE_BACK] = b1[NFSUJOY_FE_BACK] ? !(wButtons & XINPUT_GAMEPAD_B) : b1[NFSUJOY_FE_BACK];
+ b1[NFSUJOY_FE_BIT24] = b1[NFSUJOY_FE_BIT24] ? !(wButtons & XINPUT_GAMEPAD_X) : b1[NFSUJOY_FE_BIT24];
+ b1[NFSUJOY_FE_BIT22] = b1[NFSUJOY_FE_BIT22] ? !(wButtons & XINPUT_GAMEPAD_Y) : b1[NFSUJOY_FE_BIT22];
+ b1[NFSUJOY_FE_L1] = b1[NFSUJOY_FE_L1] ? !(wButtons & XINPUT_GAMEPAD_LEFT_SHOULDER) : b1[NFSUJOY_FE_L1];
+ b1[NFSUJOY_FE_R1] = b1[NFSUJOY_FE_R1] ? !(wButtons & XINPUT_GAMEPAD_RIGHT_SHOULDER) : b1[NFSUJOY_FE_R1];
+ b1[NFSUJOY_FE_L2] = b1[NFSUJOY_FE_L2] ? !(g_Controllers[0].state.Gamepad.bLeftTrigger > TRIGGER_ACTIVATION_THRESHOLD) : b1[NFSUJOY_FE_L2];
+ b1[NFSUJOY_FE_R2] = b1[NFSUJOY_FE_R2] ? !(g_Controllers[0].state.Gamepad.bRightTrigger > TRIGGER_ACTIVATION_THRESHOLD) : b1[NFSUJOY_FE_R2];
+ // splash screen start button
+ if (FEHashUpper(LastFEngPackage) == 0x2729D8C3)
+ b1[NFSUJOY_FE_ACCEPT] = b1[NFSUJOY_FE_ACCEPT] ? !(wButtons & XINPUT_GAMEPAD_START) : b1[NFSUJOY_FE_ACCEPT];
+
+ // car orbiting
+ if ((g_Controllers[0].state.Gamepad.sThumbRX != 0) || (g_Controllers[0].state.Gamepad.sThumbRY != 0))
+ {
+ bOrbitingWithRightStick = true;
+ if (g_Controllers[0].state.Gamepad.sThumbRX != 0)
+ *(int*)FEMOUSECURSOR_CARORBIT_X_ADDR = -g_Controllers[0].state.Gamepad.sThumbRX;
+ if (g_Controllers[0].state.Gamepad.sThumbRY != 0)
+ *(int*)FEMOUSECURSOR_CARORBIT_Y_ADDR = g_Controllers[0].state.Gamepad.sThumbRY;
+ CarOrbitDivisor = 0.000031f;
+ }
+ else
+ {
+ bOrbitingWithRightStick = false;
+ CarOrbitDivisor = 0.5;
+ if (!*(bool*)FEMOUSECURSOR_BUTTONPRESS_ADDR)
+ {
+ *(int*)FEMOUSECURSOR_CARORBIT_X_ADDR = 0;
+ *(int*)FEMOUSECURSOR_CARORBIT_Y_ADDR = 0;
+ }
+ }
+ if (*(bool*)FEMOUSECURSOR_BUTTONPRESS_ADDR)
+ CarOrbitDivisor = 0.5;
+
+ // tester
+ //b2[NFSUJOY_BIT33] = !(wButtons & XINPUT_GAMEPAD_DPAD_UP);
+
+ // TODO: these are a lil' buggy
+ if (*(int*)GAMEFLOWMANAGER_STATUS_ADDR == 3)
+ {
+ if ((wButtons & XINPUT_GAMEPAD_X) != bXButtonOldState)
+ {
+ if (wButtons & XINPUT_GAMEPAD_X) // trigger once only on button down state
+ {
+ if (FEHashUpper((char*)CURRENT_MENUPKG_ADDR) == 0x1F549740) // MU_GaragePerformanceCategory.fng
+ *(char*)FE_WINDOWS_KEY_CODE_ADDR = 'P'; // performance stats
+ else if (FEHashUpper((char*)CURRENT_MENUPKG_ADDR) == 0xF53C6787) // MU_GarageVinylLayerV2.fng -- call the color picker when X is pressed!!!
+ FEngSendMessageToPackage(0xC519BFC0, "MU_GarageVinylLayerV2.fng");
+ else
+ *(char*)FE_WINDOWS_KEY_CODE_ADDR = 'C'; // customize car
+ }
+ bXButtonOldState = wButtons & XINPUT_GAMEPAD_X;
+ }
+
+ if ((wButtons & XINPUT_GAMEPAD_Y))
+ *(char*)FE_WINDOWS_KEY_CODE_ADDR = 'D'; // delete profile
+
+ if (((wButtons & XINPUT_GAMEPAD_Y) != bZButtonOldState) && (FEHashUpper((char*)CURRENT_MENUPKG_ADDR) != 0xFD6DFFB3)) // except in MU_UG_NewOrLoad_PC2.fng
+ {
+ if ((wButtons & XINPUT_GAMEPAD_Y)) // trigger once only on button down state
+ *(char*)FE_WINDOWS_KEY_CODE_ADDR = 'Z'; // reset to default, decal color, etc...
+ bZButtonOldState = (wButtons & XINPUT_GAMEPAD_Y);
+ }
+
+ //if ((wButtons & XINPUT_GAMEPAD_B))
+ // *(char*)FE_WINDOWS_KEY_CODE_ADDR = 0x1B; // escape
+
+ if ((g_Controllers[0].state.Gamepad.bLeftTrigger > TRIGGER_ACTIVATION_THRESHOLD))
+ *(char*)FE_WINDOWS_KEY_CODE_ADDR = 'T'; // tutorial
+ }
+ if ((wButtons & XINPUT_GAMEPAD_BACK) != bQuitButtonOldState)
+ {
+ if ((wButtons & XINPUT_GAMEPAD_BACK)) // trigger once only on button down state
+ *(char*)FE_WINDOWS_KEY_CODE_ADDR = 'Q';
+ bQuitButtonOldState = (wButtons & XINPUT_GAMEPAD_BACK);
+ }
+ }
+ // controller 2 - debug camera and other stuff
+ if (g_Controllers[1].bConnected)
+ {
+ WORD wButtons = g_Controllers[1].state.Gamepad.wButtons;
+ b2[NFSUJOY_DEBUGCAM_ACTIVATE] = b2[NFSUJOY_DEBUGCAM_ACTIVATE] ? !(wButtons & XINPUT_GAMEPAD_BACK) : b2[NFSUJOY_DEBUGCAM_ACTIVATE];
+ b2[NFSUJOY_DEBUGCAM_MOVEFORWARD] = b2[NFSUJOY_DEBUGCAM_MOVEFORWARD] ? !(wButtons & XINPUT_GAMEPAD_A) : b2[NFSUJOY_DEBUGCAM_MOVEFORWARD];
+ b2[NFSUJOY_DEBUGCAM_MOVEBACKWARD] = b2[NFSUJOY_DEBUGCAM_MOVEBACKWARD] ? !(wButtons & XINPUT_GAMEPAD_Y) : b2[NFSUJOY_DEBUGCAM_MOVEBACKWARD];
+ b2[NFSUJOY_DEBUGCAM_MOVELEFT] = b2[NFSUJOY_DEBUGCAM_MOVELEFT] ? !(wButtons & XINPUT_GAMEPAD_X) : b2[NFSUJOY_DEBUGCAM_MOVELEFT];
+ b2[NFSUJOY_DEBUGCAM_MOVERIGHT] = b2[NFSUJOY_DEBUGCAM_MOVERIGHT] ? !(wButtons & XINPUT_GAMEPAD_B) : b2[NFSUJOY_DEBUGCAM_MOVERIGHT];
+ b2[NFSUJOY_DEBUGCAM_MOVEUP] = b2[NFSUJOY_DEBUGCAM_MOVEUP] ? !(wButtons & XINPUT_GAMEPAD_LEFT_SHOULDER) : b2[NFSUJOY_DEBUGCAM_MOVEUP];
+ b2[NFSUJOY_DEBUGCAM_MOVEDOWN] = b2[NFSUJOY_DEBUGCAM_MOVEDOWN] ? !(g_Controllers[1].state.Gamepad.bLeftTrigger > TRIGGER_ACTIVATION_THRESHOLD) : b2[NFSUJOY_DEBUGCAM_MOVEDOWN];
+ b2[NFSUJOY_DEBUGCAM_LOOKUP] = b2[NFSUJOY_DEBUGCAM_LOOKUP] ? !(wButtons & XINPUT_GAMEPAD_DPAD_UP) : b2[NFSUJOY_DEBUGCAM_LOOKUP];
+ b2[NFSUJOY_DEBUGCAM_LOOKDOWN] = b2[NFSUJOY_DEBUGCAM_LOOKDOWN] ? !(wButtons & XINPUT_GAMEPAD_DPAD_DOWN) : b2[NFSUJOY_DEBUGCAM_LOOKDOWN];
+ b2[NFSUJOY_DEBUGCAM_LOOKLEFT] = b2[NFSUJOY_DEBUGCAM_LOOKLEFT] ? !(wButtons & XINPUT_GAMEPAD_DPAD_LEFT) : b2[NFSUJOY_DEBUGCAM_LOOKLEFT];
+ b2[NFSUJOY_DEBUGCAM_LOOKRIGHT] = b2[NFSUJOY_DEBUGCAM_LOOKRIGHT] ? !(wButtons & XINPUT_GAMEPAD_DPAD_RIGHT) : b2[NFSUJOY_DEBUGCAM_LOOKRIGHT];
+ b2[NFSUJOY_DEBUGCAM_TURBO] = b2[NFSUJOY_DEBUGCAM_TURBO] ? !(wButtons & XINPUT_GAMEPAD_RIGHT_SHOULDER) : b2[NFSUJOY_DEBUGCAM_TURBO];
+ b2[NFSUJOY_DEBUGCAM_SUPERTURBO] = b2[NFSUJOY_DEBUGCAM_SUPERTURBO] ? !(g_Controllers[1].state.Gamepad.bRightTrigger > TRIGGER_ACTIVATION_THRESHOLD) : b2[NFSUJOY_DEBUGCAM_SUPERTURBO];
+ }
+
+ // write bits back
+ *(int*)JOYBUTTONS1_ADDR = b1.to_ulong();
+ *(int*)JOYBUTTONS2_ADDR = b2.to_ulong();
+}
+
+void __stdcall ReadControllerData()
+{
+ UpdateControllerState();
+ ReadXInput();
+}
+
+// entrypoint: 0x004065AD
+unsigned int JoyEventCaveExit = 0x004065B6;
+unsigned int SavedObj = 0;
+void __declspec(naked) JoyEventCave()
+{
+ _asm mov SavedObj, esi
+
+ ReadControllerData();
+
+ _asm
+ {
+ mov esi, SavedObj
+ mov eax, [esi+8]
+ mov edi, ds:JOYBUTTONS1_ADDR
+ jmp JoyEventCaveExit
+ }
+}
+
+void InitConfig()
+{
+ CIniReader inireader("");
+
+ ControllerIconMode = inireader.ReadInteger("Icons", "ControllerIconMode", 0);
+ LastControlledDevice = inireader.ReadInteger("Icons", "FirstControlDevice", 0);
+}
+
+int Init()
+{
+ // kill DInput8 joypad reading & event generation -- TODO: try to kill dinput from ever being created if possible
+ injector::MakeCALL(0x0040A7B5, DummyFunc, true);
+ injector::MakeCALL(0x00401921, DummyFunc, true);
+ injector::MakeCALL(0x0040164C, DummyFunc, true);
+ injector::MakeJMP(0x004071B8, 0x004075D1, true);
+ // hook the reading for XInput
+ injector::MakeJMP(0x004065AD, JoyEventCave, true);
+ // hook for car orbiting with right stick (hooking mouse inputs) + bugfix (adding a slight delay to give time for the actual vars to be read and updated)
+ injector::MakeJMP(0x004B0CB9, FECarOrbitCave, true);
+ injector::WriteMemory(0x004B0CE7, (int)&CarOrbitDivisor, true);
+ injector::WriteMemory(0x004B0D17, (int)&CarOrbitDivisor, true);
+ // hook FEng globally in FEPkgMgr_SendMessageToPackage
+ injector::MakeJMP(0x004F7C08, FEngGlobalCave, true);
+ // snoop last activated FEng package
+ injector::MakeCALL(0x004F3BC3, SnoopLastFEPackage, true);
+
+ // this kills DInput enumeration COMPLETELY -- even the keyboard
+ //injector::MakeJMP(0x00405695, 0x004056AA, true);
+ // kill DInput8 enumeration for joypads only
+ injector::MakeJMP(0x00418E4B, 0x00418E68, true);
+
+ // dereference the current WndProc from the game executable and write to the function pointer (to maximize compatibility)
+ GameWndProcAddr = *(unsigned int*)0x4088FC;
+ GameWndProc = (LRESULT(WINAPI*)(HWND, UINT, WPARAM, LPARAM))GameWndProcAddr;
+ injector::WriteMemory(0x4088FC, (unsigned int)&CustomWndProc, true);
+
+ // Init state
+ ZeroMemory(g_Controllers, sizeof(CONTROLLER_STATE) * MAX_CONTROLLERS);
+
+ InitConfig();
+
+ return 0;
+}
+
+
+BOOL APIENTRY DllMain(HMODULE /*hModule*/, DWORD reason, LPVOID /*lpReserved*/)
+{
+ if (reason == DLL_PROCESS_ATTACH)
+ {
+ freopen("CON", "w", stdout);
+ freopen("CON", "w", stderr);
+ Init();
+ }
+ return TRUE;
+}
+
diff --git a/includes/IniReader.h b/includes/IniReader.h
new file mode 100644
index 0000000..1ca2d4d
--- /dev/null
+++ b/includes/IniReader.h
@@ -0,0 +1,103 @@
+#ifndef INIREADER_H
+#define INIREADER_H
+
+#include "stdafx.h"
+#include
+#include
+using namespace std;
+#pragma warning(disable:4996)
+EXTERN_C IMAGE_DOS_HEADER __ImageBase;
+
+class CIniReader
+{
+public:
+ CIniReader(char* szFileName)
+ {
+ char moduleName[MAX_PATH];
+ char dllPath[MAX_PATH];
+ char iniName[MAX_PATH];
+ char* tempPointer;
+
+ GetModuleFileName((HINSTANCE)&__ImageBase, moduleName, MAX_PATH);
+ tempPointer = strrchr(moduleName, '.');
+ *tempPointer = '\0';
+ tempPointer = strrchr(moduleName, '\\');
+ strncpy(iniName, tempPointer + 1, 255);
+ strcat(iniName, ".ini");
+ strncpy(dllPath, moduleName, (tempPointer - moduleName + 1));
+ dllPath[tempPointer - moduleName + 1] = '\0';
+ if (strcmp(szFileName, "") == 0)
+ {
+ strcat(dllPath, iniName);
+ }
+ else {
+ strcat(dllPath, szFileName);
+ }
+
+ memset(m_szFileName, 0x00, 255);
+ memcpy(m_szFileName, dllPath, strlen(dllPath));
+ }
+ int ReadInteger(char* szSection, char* szKey, int iDefaultValue)
+ {
+ int iResult = GetPrivateProfileInt(szSection, szKey, iDefaultValue, m_szFileName);
+ return iResult;
+ }
+ float ReadFloat(char* szSection, char* szKey, float fltDefaultValue)
+ {
+ char szResult[255];
+ char szDefault[255];
+ float fltResult;
+ _snprintf(szDefault, 255, "%f", fltDefaultValue);
+ GetPrivateProfileString(szSection, szKey, szDefault, szResult, 255, m_szFileName);
+ fltResult = (float)atof(szResult);
+ return fltResult;
+ }
+ bool ReadBoolean(char* szSection, char* szKey, bool bolDefaultValue)
+ {
+ char szResult[255];
+ char szDefault[255];
+ bool bolResult;
+ _snprintf(szDefault, 255, "%s", bolDefaultValue ? "True" : "False");
+ GetPrivateProfileString(szSection, szKey, szDefault, szResult, 255, m_szFileName);
+ bolResult = (strcmp(szResult, "True") == 0 ||
+ strcmp(szResult, "true") == 0) ? true : false;
+ return bolResult;
+ }
+ char* ReadString(char* szSection, char* szKey, const char* szDefaultValue)
+ {
+ char* szResult = new char[255];
+ memset(szResult, 0x00, 255);
+ GetPrivateProfileString(szSection, szKey,
+ szDefaultValue, szResult, 255, m_szFileName);
+ return szResult;
+ }
+ void WriteInteger(char* szSection, char* szKey, int iValue)
+ {
+ char szValue[255];
+ _snprintf(szValue, 255, "%s%d", " ", iValue);
+ WritePrivateProfileString(szSection, szKey, szValue, m_szFileName);
+ }
+ void WriteFloat(char* szSection, char* szKey, float fltValue)
+ {
+ char szValue[255];
+ _snprintf(szValue, 255, "%s%f", " ", fltValue);
+ WritePrivateProfileString(szSection, szKey, szValue, m_szFileName);
+ }
+ void WriteBoolean(char* szSection, char* szKey, bool bolValue)
+ {
+ char szValue[255];
+ _snprintf(szValue, 255, "%s%s", " ", bolValue ? "True" : "False");
+ WritePrivateProfileString(szSection, szKey, szValue, m_szFileName);
+ }
+ void WriteString(char* szSection, char* szKey, char* szValue)
+ {
+ WritePrivateProfileString(szSection, szKey, szValue, m_szFileName);
+ }
+ char* GetIniPath()
+ {
+ return m_szFileName;
+ }
+private:
+ char m_szFileName[MAX_PATH];
+};
+#endif//INIREADER_H
\ No newline at end of file
diff --git a/includes/injector/LICENSE b/includes/injector/LICENSE
new file mode 100644
index 0000000..65960fb
--- /dev/null
+++ b/includes/injector/LICENSE
@@ -0,0 +1,21 @@
+Copyright (C) 2012-2014 LINK/2012
+
+This software is provided 'as-is', without any express or implied
+warranty. In no event will the authors be held liable for any damages
+arising from the use of this software.
+
+Permission is granted to anyone to use this software for any purpose,
+including commercial applications, and to alter it and redistribute it
+freely, subject to the following restrictions:
+
+ 1. The origin of this software must not be misrepresented; you must not
+ claim that you wrote the original software. If you use this software
+ in a product, an acknowledgment in the product documentation would be
+ appreciated but is not required.
+
+ 2. Altered source versions must be plainly marked as such, and must not be
+ misrepresented as being the original software.
+
+ 3. This notice may not be removed or altered from any source
+ distribution.
+
diff --git a/includes/injector/assembly.hpp b/includes/injector/assembly.hpp
new file mode 100644
index 0000000..9854a34
--- /dev/null
+++ b/includes/injector/assembly.hpp
@@ -0,0 +1,179 @@
+/*
+ * Injectors - Useful Assembly Stuff
+ *
+ * Copyright (C) 2012-2014 LINK/2012
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ *
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ *
+ * 3. This notice may not be removed or altered from any source
+ * distribution.
+ *
+ */
+#pragma once
+
+// This header is very restrict about compiler and architecture
+#ifndef _MSC_VER // MSVC is much more flexible when we're talking about inline assembly
+#error Cannot use this header in another compiler other than MSVC
+#endif
+#ifndef _M_IX86
+#error Supported only in x86
+#endif
+
+//
+#include
+#include "injector.hpp"
+
+namespace injector
+{
+ struct reg_pack
+ {
+ // The ordering is very important, don't change
+ // The first field is the last to be pushed and first to be poped
+
+ // PUSHFD / POPFD
+ uint32_t ef;
+
+ // PUSHAD/POPAD -- must be the lastest fields (because of esp)
+ union
+ {
+ uint32_t arr[8];
+ struct { uint32_t edi, esi, ebp, esp, ebx, edx, ecx, eax; };
+ };
+
+ enum reg_name {
+ reg_edi, reg_esi, reg_ebp, reg_esp, reg_ebx, reg_edx, reg_ecx, reg_eax
+ };
+
+ enum ef_flag {
+ carry_flag = 0, parity_flag = 2, adjust_flag = 4, zero_flag = 6, sign_flag = 7,
+ direction_flag = 10, overflow_flag = 11
+ };
+
+ uint32_t& operator[](size_t i)
+ { return this->arr[i]; }
+ const uint32_t& operator[](size_t i) const
+ { return this->arr[i]; }
+
+ template // bit starts from 0, use ef_flag enum
+ bool flag()
+ {
+ return (this->ef & (1 << bit)) != 0;
+ }
+
+ bool jnb()
+ {
+ return flag() == false;
+ }
+ };
+
+ // Lowest level stuff (actual assembly) goes on the following namespace
+ // PRIVATE! Skip this, not interesting for you.
+ namespace injector_asm
+ {
+ // Wrapper functor, so the assembly can use some templating
+ template
+ struct wrapper
+ {
+ static void call(reg_pack* regs)
+ {
+ T fun; fun(*regs);
+ }
+ };
+
+ // Constructs a reg_pack and calls the wrapper functor
+ template // where W is of type wrapper
+ inline void __declspec(naked) make_reg_pack_and_call()
+ {
+ _asm
+ {
+ // Construct the reg_pack structure on the stack
+ pushad // Pushes general purposes registers to reg_pack
+ add [esp+12], 4 // Add 4 to reg_pack::esp 'cuz of our return pointer, let it be as before this func is called
+ pushfd // Pushes EFLAGS to reg_pack
+
+ // Call wrapper sending reg_pack as parameter
+ push esp
+ call W::call
+ add esp, 4
+
+ // Destructs the reg_pack from the stack
+ sub [esp+12+4], 4 // Fix reg_pack::esp before popping it (doesn't make a difference though) (+4 because eflags)
+ popfd // Warning: Do not use any instruction that changes EFLAGS after this (-> sub affects EF!! <-)
+ popad
+
+ // Back to normal flow
+ ret
+ }
+ }
+ };
+
+
+ /*
+ * MakeInline
+ * Makes inline assembly (but not assembly, an actual functor of type FuncT) at address
+ */
+ template
+ void MakeInline(memory_pointer_tr at)
+ {
+ typedef injector_asm::wrapper functor;
+ if(false) functor::call(nullptr); // To instantiate the template, if not done _asm will fail
+ MakeCALL(at, injector_asm::make_reg_pack_and_call);
+ }
+
+ /*
+ * MakeInline
+ * Same as above, but it NOPs everything between at and end (exclusive), then performs MakeInline
+ */
+ template
+ void MakeInline(memory_pointer_tr at, memory_pointer_tr end)
+ {
+ MakeRangedNOP(at, end);
+ MakeInline(at);
+ }
+
+ /*
+ * MakeInline
+ * Same as above, but (at,end) are template parameters.
+ * On this case the functor can be passed as argument since there will be one func instance for each at,end not just for each FuncT
+ */
+ template
+ void MakeInline(FuncT func)
+ {
+ static std::unique_ptr static_func;
+ static_func.reset(new FuncT(std::move(func)));
+
+ // Encapsulates the call to static_func
+ struct Caps
+ {
+ void operator()(reg_pack& regs)
+ { (*static_func)(regs); }
+ };
+
+ // Does the actual MakeInline
+ return MakeInline(lazy_pointer::get(), lazy_pointer::get());
+ }
+
+ /*
+ * MakeInline
+ * Same as above, but (end) is calculated by the length of a call instruction
+ */
+ template
+ void MakeInline(FuncT func)
+ {
+ return MakeInline(func);
+ }
+};
diff --git a/includes/injector/calling.hpp b/includes/injector/calling.hpp
new file mode 100644
index 0000000..ebf3bdf
--- /dev/null
+++ b/includes/injector/calling.hpp
@@ -0,0 +1,126 @@
+/*
+ * Injectors - Function Calls Using Variadic Templates
+ *
+ * Copyright (C) 2014 LINK/2012
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ *
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ *
+ * 3. This notice may not be removed or altered from any source
+ * distribution.
+ *
+ */
+#pragma once
+#include "injector.hpp"
+#include
+
+#if __cplusplus >= 201103L || _MSC_VER >= 1800 // MSVC 2013
+#else
+#error "This feature is not supported on this compiler"
+#endif
+
+namespace injector
+{
+ template
+ struct cstd;
+
+ template
+ struct cstd
+ {
+ // Call function at @p returning @Ret with args @Args
+ static Ret call(memory_pointer_tr p, Args... a)
+ {
+ auto fn = (Ret(*)(Args...)) p.get();
+ return fn(std::forward(a)...);
+ }
+
+ template // Uses lazy pointer
+ static Ret call(Args... a)
+ {
+ return call(lazy_pointer::get(), std::forward(a)...);
+ }
+ };
+
+ template
+ struct stdcall;
+
+ template
+ struct stdcall
+ {
+ // Call function at @p returning @Ret with args @Args
+ static Ret call(memory_pointer_tr p, Args... a)
+ {
+ auto fn = (Ret(__stdcall *)(Args...)) p.get();
+ return fn(std::forward(a)...);
+ }
+
+ template // Uses lazy pointer
+ static Ret call(Args... a)
+ {
+ return call(lazy_pointer::get(), std::forward(a)...);
+ }
+ };
+
+ template
+ struct fastcall;
+
+ template
+ struct fastcall
+ {
+ // Call function at @p returning @Ret with args @Args
+ static Ret call(memory_pointer_tr p, Args... a)
+ {
+ auto fn = (Ret(__fastcall *)(Args...)) p.get();;
+ return fn(std::forward(a)...);
+ }
+
+ template // Uses lazy pointer
+ static Ret call(Args... a)
+ {
+ return call(lazy_pointer::get(), std::forward(a)...);
+ }
+ };
+
+ template
+ struct thiscall;
+
+ template
+ struct thiscall
+ {
+ // Call function at @p returning @Ret with args @Args
+ static Ret call(memory_pointer_tr p, Args... a)
+ {
+ auto fn = (Ret(__thiscall *)(Args...)) p.get();
+ return fn(std::forward(a)...);
+ }
+
+ // Call function at the index @i from the vtable of the object @a[0]
+ template
+ static Ret vtbl(Args... a)
+ {
+ auto obj = raw_ptr(std::get<0>(std::forward_as_tuple(a...)));
+ auto p = raw_ptr( (*obj.template get()) [i] );
+ return call(p, std::forward(a)...);
+ }
+
+ template // Uses lazy pointer
+ static Ret call(Args... a)
+ {
+ return call(lazy_pointer::get(), std::forward(a)...);
+ }
+ };
+}
+
diff --git a/includes/injector/gvm/gvm.hpp b/includes/injector/gvm/gvm.hpp
new file mode 100644
index 0000000..a957929
--- /dev/null
+++ b/includes/injector/gvm/gvm.hpp
@@ -0,0 +1,232 @@
+/*
+ * Injectors - Base Header
+ *
+ * Copyright (C) 2012-2014 LINK/2012
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ *
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ *
+ * 3. This notice may not be removed or altered from any source
+ * distribution.
+ *
+ */
+#pragma once
+#include
+#include
+#include
+
+namespace injector
+{
+
+#if 1 // GVM and Address Translator, Not very interesting for the users, so skip reading those...
+
+/*
+ * game_version_manager
+ * Detects the game, the game version and the game region
+ * This assumes the executable is decrypted, so, Silent's ASI Loader is recommended.
+ */
+#ifndef INJECTOR_OWN_GVM
+#ifndef INJECTOR_GVM_DUMMY
+class game_version_manager
+{
+ public:
+ // Set this if you would like that MessagesBox contain PluginName as caption
+ const char* PluginName;
+
+ private:
+ char game, region, major, minor, majorRevision, minorRevision, cracker, steam;
+
+ public:
+ game_version_manager()
+ {
+ #ifdef INJECTOR_GVM_PLUGIN_NAME
+ PluginName = INJECTOR_GVM_PLUGIN_NAME;
+ #else
+ PluginName = "LODLights.asi";
+ #endif
+
+ this->Clear();
+ }
+
+
+ // Clear any information about game version
+ void Clear()
+ {
+ game = region = major = minor = cracker = steam = 0;
+ }
+
+ // Checks if I don't know the game we are attached to
+ bool IsUnknown() { return game == 0; }
+ // Checks if this is the steam version
+ bool IsSteam() { return steam != 0; }
+ // Gets the game we are attached to (0, '3', 'V', 'S', 'I', 'E')
+ char GetGame() { return game; }
+ // Gets the region from the game we are attached to (0, 'U', 'E');
+ char GetRegion() { return region; }
+ // Get major and minor version of the game (e.g. [major = 1, minor = 0] = 1.0)
+ int GetMajorVersion() { return major; }
+ int GetMinorVersion() { return minor; }
+ int GetMajorRevisionVersion() { return majorRevision; }
+ int GetMinorRevisionVersion() { return minorRevision; }
+
+ bool IsHoodlum() { return cracker == 'H'; }
+
+ // Region conditions
+ bool IsUS() { return region == 'U'; }
+ bool IsEU() { return region == 'E'; }
+
+ // Game Conditions
+ bool IsIII() { return game == '3'; }
+ bool IsVC () { return game == 'V'; }
+ bool IsSA () { return game == 'S'; }
+ bool IsIV () { return game == 'I'; }
+ bool IsEFLC(){ return game == 'E'; }
+
+ // Detects game, region and version; returns false if could not detect it
+ bool Detect();
+
+ // Gets the game version as text, the buffer must contain at least 32 bytes of space.
+ char* GetVersionText(char* buffer)
+ {
+ if(this->IsUnknown())
+ {
+ strcpy(buffer, "UNKNOWN GAME");
+ return buffer;
+ }
+
+ const char* g = this->IsIII() ? "III" : this->IsVC() ? "VC" : this->IsSA() ? "SA" : this->IsIV() ? "IV" : this->IsEFLC() ? "EFLC" : "UNK";
+ const char* r = this->IsUS()? "US" : this->IsEU()? "EURO" : "UNK_REGION";
+ const char* s = this->IsSteam()? "Steam" : "";
+ if (this->IsIII() || this->IsVC() || this->IsSA())
+ sprintf(buffer, "GTA %s %d.%d %s%s", g, major, minor, r, s);
+ else
+ sprintf(buffer, "GTA %s %d.%d.%d.%d %s%s", g, major, minor, majorRevision, minorRevision, r, s);
+ return buffer;
+ }
+
+
+ public:
+ // Raises a error saying that you could not detect the game version
+ void RaiseCouldNotDetect()
+ {
+ MessageBoxA(0,
+ "Could not detect the game version\nContact the mod creator!",
+ PluginName, MB_ICONERROR
+ );
+ }
+
+ // Raises a error saying that the exe version is incompatible (and output the exe name)
+ void RaiseIncompatibleVersion()
+ {
+ char buf[128], v[32];
+ sprintf(buf,
+ "An incompatible exe version has been detected! (%s)\nContact the mod creator!",
+ GetVersionText(v)
+ );
+ MessageBoxA(0, buf, PluginName, MB_ICONERROR);
+ }
+};
+#else // INJECTOR_GVM_DUMMY
+class game_version_manager
+{
+ public:
+ bool Detect() { return true; }
+};
+#endif // INJECTOR_GVM_DUMMY
+#endif // INJECTOR_OWN_GVM
+
+
+/*
+ * address_manager
+ * Address translator from 1.0 executables to other executables offsets
+ * Inherits from game_version_manager ;)
+ */
+class address_manager : public game_version_manager
+{
+ private:
+ address_manager()
+ {
+ this->Detect();
+ }
+
+ // You could implement your translator for the address your plugin uses
+ // If not implemented, the translator won't translate anything, just return the samething as before
+ #ifdef INJECTOR_GVM_HAS_TRANSLATOR
+ void* translator(void* p);
+ #else
+ void* translator(void* p) { return p; }
+ #endif
+
+ public:
+ // Translates address p to the running executable pointer
+ void* translate(void* p)
+ {
+ return translator(p);
+ }
+
+
+ public:
+ // Address manager singleton
+ static address_manager& singleton()
+ {
+ static address_manager m;
+ return m;
+ }
+
+ // Static version of translate()
+ static void* translate_address(void* p)
+ {
+ return singleton().translate(p);
+ }
+
+ //
+ static void set_name(const char* modname)
+ {
+ singleton().PluginName = modname;
+ }
+
+ public:
+ // Functors for memory translation:
+
+ // Translates aslr translator
+ struct fn_mem_translator_aslr
+ {
+ void* operator()(void* p) const
+ {
+ static uintptr_t module = (uintptr_t)GetModuleHandle(NULL);
+ return (void*)((uintptr_t)(p)-(0x400000 - module));
+ }
+ };
+
+ // Translates nothing translator
+ struct fn_mem_translator_nop
+ {
+ void* operator()(void* p) const
+ { return p; }
+ };
+
+ // Real translator
+ struct fn_mem_translator
+ {
+ void* operator()(void* p) const
+ { return translate_address(p); }
+ };
+};
+
+#endif // #if 1
+
+
+}
\ No newline at end of file
diff --git a/includes/injector/gvm/translator.hpp b/includes/injector/gvm/translator.hpp
new file mode 100644
index 0000000..c6ac430
--- /dev/null
+++ b/includes/injector/gvm/translator.hpp
@@ -0,0 +1,203 @@
+/*
+ * Injectors - Address Translation Management
+ *
+ * Copyright (C) 2014 LINK/2012
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ *
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ *
+ * 3. This notice may not be removed or altered from any source
+ * distribution.
+ *
+ */
+#pragma once
+
+#if !defined(INJECTOR_GVM_HAS_TRANSLATOR)
+#error Missing INJECTOR_GVM_HAS_TRANSLATOR on compiler definitions
+#endif
+
+/*
+ * This is a quick solution for address translations if you're too lazy to implement a proper address_manager::translator by yourself
+ * So, just call address_translator_manager::singleton().translate(p) from your address_manager::translator and that's it.
+ * It'll translate addresses based on 'address_translator' objects, when one gets constructed it turns into a possible translator.
+ * At the constructor of your derived 'address_translator' make the map object to have [addr_to_translate] = translated_addr;
+ * There's also the virtual method 'fallback' that will get called when the translation wasn't possible, you can do some fallback stuff here
+ * (such as return the pointer as is or output a error message)
+ */
+
+#include "../injector.hpp"
+#include
+#include