├── FlatOut2 ├── testScript.txt ├── DIDetours.h ├── User32Detours.h ├── DXDetours.h ├── FlatOut2.rc ├── WinSockDetours.h ├── GameDetours.h ├── stdafx.cpp ├── targetver.h ├── resource.h ├── CDirect3D9DevHook.cpp ├── TestScriptRunner.h ├── DIDetours.cpp ├── DXDetours.cpp ├── stdafx.h ├── VirtualClient.h ├── GameDetours.cpp ├── FlatOut2.h ├── VirtualHost.h ├── CDirectInput8Hook.cpp ├── CDirectInput8Hook.h ├── CDIDevice8Hook.cpp ├── Logging.h ├── InstanceSettings.h ├── DetourCallDefs.h ├── TestScriptRunner.cpp ├── CDirect3D9Hook.h ├── CDirect3D9Hook.cpp ├── dllmain.cpp ├── CDIDevice8Hook.h ├── Logging.cpp ├── VirtualIP.h ├── InstanceSettings.cpp ├── FlatOut2.cpp ├── FlatOut2.vcxproj.filters ├── WinSockDetours.cpp ├── User32Detours.cpp ├── VirtualClient.cpp ├── FlatOut2.vcxproj └── VirtualHost.cpp ├── .gitmodules ├── docs ├── images │ └── virtual_network.png └── virtual_net.md ├── Launcher ├── packages.config ├── App.config ├── Properties │ ├── Settings.settings │ ├── Settings.Designer.cs │ ├── AssemblyInfo.cs │ ├── Resources.Designer.cs │ └── Resources.resx ├── Launcher.csproj.user ├── UpdateNotifier.cs ├── Settings.StyleCop ├── Program.cs ├── GamePad.cs ├── Launcher.csproj ├── UpdateNotifier.Designer.cs ├── UpdateNotifier.resx ├── Setup.en.resx ├── FlatOut2.cs ├── AutoUpdate.cs └── Setup.cs ├── version.json ├── Detours.vcxproj.filters ├── README.md ├── FlatOut2.sln ├── .gitignore └── Detours.vcxproj /FlatOut2/testScript.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /FlatOut2/DIDetours.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | void DIDetour(); -------------------------------------------------------------------------------- /FlatOut2/User32Detours.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | void User32Detour(); -------------------------------------------------------------------------------- /FlatOut2/DXDetours.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "CDirect3D9Hook.h" 3 | 4 | void DXDetour(); -------------------------------------------------------------------------------- /FlatOut2/FlatOut2.rc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MorMund/FO2-Splitscreen/HEAD/FlatOut2/FlatOut2.rc -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "Detours"] 2 | path = Detours 3 | url = https://github.com/microsoft/Detours.git 4 | -------------------------------------------------------------------------------- /docs/images/virtual_network.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MorMund/FO2-Splitscreen/HEAD/docs/images/virtual_network.png -------------------------------------------------------------------------------- /FlatOut2/WinSockDetours.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "VirtualIP.h" 3 | #include "InstanceSettings.h" 4 | extern VirtualIP *virtIP; 5 | void WinSockDetour(); -------------------------------------------------------------------------------- /Launcher/packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /FlatOut2/GameDetours.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #define FO2_WinMain (PVOID)0x00520ed0 3 | 4 | #define FO2_PlayIntroAddress (PVOID)0x00520BB0 5 | #define FO2_PlayMovieAddress (PVOID)0x004C8F00 6 | 7 | void DeferredHookDetour(); -------------------------------------------------------------------------------- /Launcher/App.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /Launcher/Properties/Settings.settings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /FlatOut2/stdafx.cpp: -------------------------------------------------------------------------------- 1 | // stdafx.cpp : source file that includes just the standard includes 2 | // FlatOut2.pch will be the pre-compiled header 3 | // stdafx.obj will contain the pre-compiled type information 4 | 5 | #include "stdafx.h" 6 | 7 | // TODO: reference any additional headers you need in STDAFX.H 8 | // and not in this file 9 | -------------------------------------------------------------------------------- /FlatOut2/targetver.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // Including SDKDDKVer.h defines the highest available Windows platform. 4 | 5 | // If you wish to build your application for a previous Windows platform, include WinSDKVer.h and 6 | // set the _WIN32_WINNT macro to the platform you wish to support before including SDKDDKVer.h. 7 | 8 | #include 9 | -------------------------------------------------------------------------------- /version.json: -------------------------------------------------------------------------------- 1 | { 2 | "Version" : "0.1.2", 3 | "Date" : "2019/09/11", 4 | "CacheDuration" : "23:59:59.999", 5 | "Changelog" : [ 6 | "Fixed Steam version of the game being unable to find the hosts game", 7 | "Added support for writing debug info to a log file", 8 | "Added notifications about updates to the mod" 9 | ] 10 | } -------------------------------------------------------------------------------- /FlatOut2/resource.h: -------------------------------------------------------------------------------- 1 | //{{NO_DEPENDENCIES}} 2 | // Microsoft Visual C++ generated include file. 3 | // Used by FlatOut2.rc 4 | 5 | // Next default values for new objects 6 | // 7 | #ifdef APSTUDIO_INVOKED 8 | #ifndef APSTUDIO_READONLY_SYMBOLS 9 | #define _APS_NEXT_RESOURCE_VALUE 101 10 | #define _APS_NEXT_COMMAND_VALUE 40001 11 | #define _APS_NEXT_CONTROL_VALUE 1001 12 | #define _APS_NEXT_SYMED_VALUE 101 13 | #endif 14 | #endif 15 | -------------------------------------------------------------------------------- /FlatOut2/CDirect3D9DevHook.cpp: -------------------------------------------------------------------------------- 1 | #include "stdafx.h" 2 | #include "CDirect3D9DevHook.h" 3 | 4 | CDirect3D9DevHook::CDirect3D9DevHook(IDirect3DDevice9 * ptr) 5 | { 6 | if (ptr == NULL) { 7 | MessageBox(NULL, L"Error in CDirect3D9DevHook::CDirect3D9DevHook. DirectX object pointer was null!", L"DirectX Error", MB_OK | MB_ICONERROR); 8 | throw std::exception("Error in CDirect3D9DevHook::CDirect3D9DevHook. DirectX object pointer was null!"); 9 | } 10 | 11 | m_ptr = ptr; 12 | } 13 | 14 | HRESULT CDirect3D9DevHook::EndScene() 15 | { 16 | return m_ptr->EndScene(); 17 | } 18 | -------------------------------------------------------------------------------- /Launcher/Launcher.csproj.user: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ..\GameDir\ 5 | true 6 | 7 | 8 | ..\GameDir\ 9 | 10 | -------------------------------------------------------------------------------- /FlatOut2/TestScriptRunner.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | typedef void PressKeyCallback(DWORD key); 8 | 9 | #define SR_ERROR 0 10 | #define SR_SUCCESS 1 11 | #define SR_WAIT 2 12 | #define SR_EOF 3 13 | 14 | class TestScriptRunner 15 | { 16 | public: 17 | TestScriptRunner(const wchar_t* scriptFile,PressKeyCallback pkCallback); 18 | ~TestScriptRunner(); 19 | void SetSetting(std::string setting, DWORD value); 20 | DWORD RunOnce(); 21 | private: 22 | std::ifstream m_scriptFile; 23 | int m_lineNum; 24 | ULONGLONG m_waitStart; 25 | int m_waitTime; 26 | std::unordered_map m_settings; 27 | PressKeyCallback* m_pkCallback; 28 | }; 29 | 30 | -------------------------------------------------------------------------------- /FlatOut2/DIDetours.cpp: -------------------------------------------------------------------------------- 1 | #include "stdafx.h" 2 | #include "DIDetours.h" 3 | 4 | PVOID oDIn8Create; 5 | 6 | HRESULT WINAPI MyDirectInput8Create(HINSTANCE hinst, DWORD dwVersion, REFIID riidltf, LPVOID* ppvOut, LPUNKNOWN punkOuter) 7 | { 8 | HRESULT res = ((diCreate)oDIn8Create)(hinst, dwVersion, riidltf, ppvOut, punkOuter); 9 | IDirectInput8* input = static_cast(*ppvOut); 10 | CDirectInput8Hook* directInput = new CDirectInput8Hook(input); 11 | InstanceSettings::GetSettings()->SetDirectInputHook(directInput); 12 | (*ppvOut) = static_cast(directInput); 13 | return res; 14 | } 15 | 16 | void DIDetour() 17 | { 18 | HMODULE diDLL = LoadLibrary(L"Dinput8.dll"); 19 | oDIn8Create = GetProcAddress(diDLL, "DirectInput8Create"); 20 | DetourAttach(&oDIn8Create, MyDirectInput8Create); 21 | } 22 | -------------------------------------------------------------------------------- /Detours.vcxproj.filters: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {15df074d-e69c-45e1-b673-b8817e2cfc6c} 6 | 7 | 8 | {1cc2ac9d-1c2d-4ef9-a817-2de4d63c5187} 9 | 10 | 11 | 12 | 13 | Header Files 14 | 15 | 16 | 17 | 18 | 19 | Source Files 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /FlatOut2/DXDetours.cpp: -------------------------------------------------------------------------------- 1 | #include "stdafx.h" 2 | #include "DXDetours.h" 3 | 4 | PVOID oDX9Create; 5 | CDirect3D9Hook* d3d; 6 | 7 | IDirect3D9* WINAPI myCreateDx9(UINT sdkVer) 8 | { 9 | IDirect3D9* idx = ((dxCreate)oDX9Create)(sdkVer); 10 | if (idx == NULL) 11 | { 12 | WCHAR errMsg[DEFAULT_ERRMSG_BUFFER]; 13 | StringCbPrintf(errMsg, sizeof(errMsg), L"Failed to create DirectX9 object. SDK Version : %d", sdkVer); 14 | MessageBox(NULL, errMsg, L"DirectX Error", MB_OK | MB_ICONERROR); 15 | throw std::exception("Failed to create DirectX9 object."); 16 | } 17 | d3d = new CDirect3D9Hook(idx); 18 | IDirect3D9* proxy = static_cast(d3d); 19 | InstanceSettings::GetSettings()->SetDirect3DHook(d3d); 20 | return proxy; 21 | } 22 | 23 | void DXDetour() 24 | { 25 | HMODULE dxDLL = LoadLibrary(L"d3d9.dll"); 26 | oDX9Create = GetProcAddress(dxDLL, "Direct3DCreate9"); 27 | DetourAttach(&oDX9Create, myCreateDx9); 28 | } -------------------------------------------------------------------------------- /FlatOut2/stdafx.h: -------------------------------------------------------------------------------- 1 | // stdafx.h : include file for standard system include files, 2 | // or project specific include files that are used frequently, but 3 | // are changed infrequently 4 | // 5 | 6 | #pragma once 7 | #include "targetver.h" 8 | #define DIRECTINPUT_VERSION 0x0800 9 | #define DEFAULT_ERRMSG_BUFFER 4096 10 | #define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers 11 | // Windows Header Files: 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | // TODO: reference additional headers your program requires here 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include "Logging.h" 34 | #include "FlatOut2.h" 35 | #include "VirtualClient.h" 36 | #include "VirtualHost.h" 37 | 38 | -------------------------------------------------------------------------------- /Launcher/Properties/Settings.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.42000 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace Launcher.Properties 12 | { 13 | 14 | 15 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 16 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")] 17 | internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase 18 | { 19 | 20 | private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); 21 | 22 | public static Settings Default 23 | { 24 | get 25 | { 26 | return defaultInstance; 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /FlatOut2/VirtualClient.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "stdafx.h" 3 | #include "VirtualIP.h" 4 | 5 | 6 | class VirtualClient : VirtualIP 7 | { 8 | public: 9 | VirtualClient(PVOID* oCalls, int callCount); 10 | ~VirtualClient(); 11 | private: 12 | int HandleVirtNetwork(SocketState** pprecvSock); 13 | public: 14 | // Inherited via VirtualIP 15 | virtual BOOL Init() override; 16 | virtual int RegisterSocket(SOCKET s, const sockaddr* addr) override; 17 | virtual int ReleaseSocket(SOCKET s) override; 18 | virtual int DSendTo(SOCKET s, LPWSABUF lpBuffers, DWORD dwBufferCount, LPDWORD lpNumberOfBytesSent, DWORD dwFlags, const sockaddr * lpTo, int iToLen, LPWSAOVERLAPPED lpOverlapped) override; 19 | virtual int DRecvFrom(SOCKET s, LPWSABUF lpBuffers, DWORD dwBufferCount, LPDWORD lpNumberOfBytesRecvd, LPDWORD lpFlags, sockaddr * lpFrom, LPINT lpFromlen, LPWSAOVERLAPPED lpOverlapped) override; 20 | virtual int DGetOverlappedResult(SOCKET s, LPWSAOVERLAPPED lpOverlapped, LPDWORD lpcbTransfer, BOOL fWait, LPDWORD lpdwFlags) override; 21 | virtual DWORD DWaitForEvents(DWORD cEvents, const WSAEVENT * lphEvents, BOOL fWaitAll, DWORD dwTimeout, BOOL fAlertable) override; 22 | virtual BOOL DWSAResetEvent(WSAEVENT hEvent) override; 23 | }; 24 | 25 | -------------------------------------------------------------------------------- /FlatOut2/GameDetours.cpp: -------------------------------------------------------------------------------- 1 | #include "stdafx.h" 2 | #include "GameDetours.h" 3 | 4 | PVOID oWinMain; 5 | PVOID oFO2PlayMov; 6 | PVOID oFO2PlayIntros; 7 | 8 | void _stdcall MyFO2PlayMovie(UINT unknown1, LPCSTR vidPath, UINT unknown2) 9 | { 10 | ((PlayMovie)oFO2PlayMov)(unknown1, vidPath, unknown2); 11 | } 12 | 13 | void _stdcall MyFO2PlayIntro() 14 | { 15 | return; 16 | } 17 | 18 | void DeferredDetour() 19 | { 20 | // TODO : Use relative addresses. 21 | oFO2PlayIntros = FO2_PlayIntroAddress; 22 | oFO2PlayMov = FO2_PlayMovieAddress; 23 | if (InstanceSettings::GetSettings()->SkipIntros()) 24 | { 25 | DetourAttach(&oFO2PlayIntros, MyFO2PlayIntro); 26 | DetourAttach(&oFO2PlayMov, MyFO2PlayMovie); 27 | } 28 | } 29 | 30 | int MyWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd) { 31 | DetourTransactionBegin(); 32 | DetourUpdateThread(GetCurrentThread()); 33 | DeferredDetour(); 34 | if (DetourTransactionCommit() != NO_ERROR) 35 | { 36 | Logging::getInstance().error("DETOURS", std::string("Detour Error!")); 37 | throw std::exception("Detour Error!"); 38 | } 39 | return ((_WinMain)oWinMain)(hInstance, hPrevInstance, lpCmdLine, nShowCmd); 40 | } 41 | 42 | void DeferredHookDetour() { 43 | oWinMain = FO2_WinMain; 44 | DetourAttach(&oWinMain, MyWinMain); 45 | } -------------------------------------------------------------------------------- /FlatOut2/FlatOut2.h: -------------------------------------------------------------------------------- 1 | // The following ifdef block is the standard way of creating macros which make exporting 2 | // from a DLL simpler. All files within this DLL are compiled with the FLATOUT2_EXPORTS 3 | // symbol defined on the command line. This symbol should not be defined on any project 4 | // that uses this DLL. This way any other project whose source files include this file see 5 | // FLATOUT2_API functions as being imported from a DLL, whereas this DLL sees symbols 6 | // defined with this macro as being exported. 7 | #pragma once 8 | #ifdef FLATOUT2_EXPORTS 9 | #define FLATOUT2_API extern "C" __declspec(dllexport) 10 | #else 11 | #define FLATOUT2_API extern "C" __declspec(dllimport) 12 | #endif 13 | 14 | #define injectDllName "FlatOut2.dll" 15 | #define FO2_AllowAttach FALSE 16 | #define FO2_AllowAttach2 FALSE 17 | #define FO2_MaxClientCount 8 18 | 19 | struct ControllerInfo { 20 | WCHAR deviceName[512]; 21 | WCHAR instanceName[512]; 22 | GUID guid; 23 | }; 24 | 25 | FLATOUT2_API int _stdcall CreateNewInstance(LPWSTR cmdLine); 26 | FLATOUT2_API BOOL _stdcall GetSharedSettingsSize(int* settingsSize, int sizeCount); 27 | FLATOUT2_API BOOL _stdcall GetControllerGuids(ControllerInfo* devices, LPINT deviceCount); 28 | FLATOUT2_API BOOL _stdcall GetResolutions(LPRECT resolutions, LPINT resoCount); 29 | extern HMODULE dllHandle; 30 | 31 | 32 | -------------------------------------------------------------------------------- /Launcher/UpdateNotifier.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | using System.Data; 5 | using System.Drawing; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | using System.Windows.Forms; 10 | 11 | namespace Launcher 12 | { 13 | public partial class UpdateNotifier : Form 14 | { 15 | const string VerCompString = "Current Version : {0} New Version : {1}"; 16 | 17 | private string downloadURI = "https://github.com/DeadlySurprise/FO2-Splitscreen/releases"; 18 | 19 | public UpdateNotifier(string version, string[] changelog, string downloadLinkText, string downloadLinkURI) 20 | { 21 | InitializeComponent(); 22 | VersionCompare.Text = string.Format(VerCompString, AutoUpdate.LauncherVersion, version); 23 | Changelog.Text = changelog.Select(s => "*" + s).Aggregate((a, b) => string.Concat(a, "\n", b)); 24 | DownloadLink.Text = downloadLinkText; 25 | } 26 | 27 | private void DownloadLink_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e) 28 | { 29 | System.Diagnostics.Process.Start(downloadURI); 30 | } 31 | 32 | private void OkButton_Click(object sender, EventArgs e) 33 | { 34 | Close(); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /FlatOut2/VirtualHost.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "stdafx.h" 3 | #include "VirtualIP.h" 4 | 5 | class VirtualHost : VirtualIP 6 | { 7 | public: 8 | VirtualHost(int clientCount, PVOID* oCalls, int callCount); 9 | ~VirtualHost(); 10 | private: 11 | int m_clCount; 12 | void CompletePhysicalReceive(SocketState* state); 13 | void BroadcastVirtual(char* buffer, int buflen, short nport); 14 | void ImmediateVirtualRecv(SocketState* state, LPWSABUF lpBuffers, DWORD dwBufferCount, LPDWORD lpNumberOfBytesRecvd, sockaddr * lpFrom); 15 | int HandleVirtNetwork(SocketState** pprecvSock); 16 | public: 17 | // Inherited via VirtualIP 18 | virtual BOOL Init() override; 19 | virtual int RegisterSocket(SOCKET s, const sockaddr* addr) override; 20 | virtual int ReleaseSocket(SOCKET s) override; 21 | virtual int DSendTo(SOCKET s, LPWSABUF lpBuffers, DWORD dwBufferCount, LPDWORD lpNumberOfBytesSent, DWORD dwFlags, const sockaddr * lpTo, int iToLen, LPWSAOVERLAPPED lpOverlapped) override; 22 | virtual int DRecvFrom(SOCKET s, LPWSABUF lpBuffers, DWORD dwBufferCount, LPDWORD lpNumberOfBytesRecvd, LPDWORD lpFlags, sockaddr * lpFrom, LPINT lpFromlen, LPWSAOVERLAPPED lpOverlapped) override; 23 | virtual int DGetOverlappedResult(SOCKET s, LPWSAOVERLAPPED lpOverlapped, LPDWORD lpcbTransfer, BOOL fWait, LPDWORD lpdwFlags) override; 24 | virtual DWORD DWaitForEvents(DWORD cEvents, const WSAEVENT * lphEvents, BOOL fWaitAll, DWORD dwTimeout, BOOL fAlertable) override; 25 | virtual BOOL DWSAResetEvent(WSAEVENT hEvent) override; 26 | }; 27 | 28 | -------------------------------------------------------------------------------- /Launcher/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("Launcher")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("Launcher")] 13 | [assembly: AssemblyCopyright("Copyright © 2016")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("4760197a-8a5d-4c93-b5a0-532c03f2257a")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("0.1.2.0")] 36 | [assembly: AssemblyFileVersion("0.1.2.0")] 37 | -------------------------------------------------------------------------------- /FlatOut2/CDirectInput8Hook.cpp: -------------------------------------------------------------------------------- 1 | #include "stdafx.h" 2 | #include "CDirectInput8Hook.h" 3 | 4 | struct DIEnumCallback 5 | { 6 | LPDIENUMDEVICESCALLBACKW origCallback; 7 | LPVOID origRef; 8 | }; 9 | 10 | BOOL WINAPI EnumCallback(LPCDIDEVICEINSTANCE lpddi, LPVOID pvRef) 11 | { 12 | DIEnumCallback* orig = (DIEnumCallback*)pvRef; 13 | GUID instanceDev = InstanceSettings::GetSettings()->GetLocalSettings().instanceController; 14 | if (lpddi->guidInstance == instanceDev && GUID_NULL != instanceDev) 15 | { 16 | BOOL ret = orig->origCallback(lpddi, orig->origRef); 17 | return DIENUM_STOP; 18 | } 19 | else 20 | { 21 | return DIENUM_CONTINUE; 22 | } 23 | } 24 | 25 | CDIDevice8Hook * CDirectInput8Hook::GetDevice() 26 | { 27 | return m_dev; 28 | } 29 | 30 | HRESULT CDirectInput8Hook::EnumDevices(DWORD dwDevType, LPDIENUMDEVICESCALLBACKW lpCallback, LPVOID pvRef, DWORD dwFlags) 31 | { 32 | DIEnumCallback* eCallback = new DIEnumCallback(); 33 | eCallback->origCallback = lpCallback; 34 | eCallback->origRef = pvRef; 35 | HRESULT res = m_ptr->EnumDevices(dwDevType, EnumCallback, eCallback, dwFlags); 36 | delete eCallback; 37 | return res; 38 | } 39 | 40 | HRESULT CDirectInput8Hook::CreateDevice(REFGUID rguid, LPDIRECTINPUTDEVICE8W * lplpDirectInputDevice, LPUNKNOWN pUnkOuter) 41 | { 42 | HRESULT res = m_ptr->CreateDevice(rguid, lplpDirectInputDevice, pUnkOuter); 43 | IDirectInputDevice8* inputDev = static_cast(*lplpDirectInputDevice); 44 | m_dev = new CDIDevice8Hook(inputDev); 45 | (*lplpDirectInputDevice) = static_cast(m_dev); 46 | return res; 47 | } 48 | -------------------------------------------------------------------------------- /FlatOut2/CDirectInput8Hook.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "stdafx.h" 3 | #include "CDIDevice8Hook.h" 4 | 5 | BOOL WINAPI EnumCallback(LPCDIDEVICEINSTANCE lpddi, LPVOID pvRef); 6 | 7 | class CDirectInput8Hook : public IDirectInput8 8 | { 9 | private: 10 | IDirectInput8* m_ptr; 11 | CDIDevice8Hook* m_dev; 12 | public: 13 | CDirectInput8Hook(IDirectInput8* ptr) : m_ptr(ptr) 14 | { 15 | } 16 | public: 17 | CDIDevice8Hook* _stdcall GetDevice(); 18 | HRESULT _stdcall EnumDevices(DWORD dwDevType, LPDIENUMDEVICESCALLBACKW lpCallback, LPVOID pvRef, DWORD dwFlags); 19 | HRESULT _stdcall CreateDevice(REFGUID rguid, LPDIRECTINPUTDEVICE8W * lplpDirectInputDevice, LPUNKNOWN pUnkOuter); 20 | COM_METHOD(HRESULT, QueryInterface)(THIS_ REFIID riid, LPVOID * ppvObj) { return m_ptr->QueryInterface(riid, ppvObj); } 21 | COM_METHOD(ULONG, AddRef(THIS)) { return m_ptr->AddRef(); } 22 | COM_METHOD(ULONG, Release(THIS)) { return m_ptr->Release(); } 23 | 24 | COM_METHOD(HRESULT, GetDeviceStatus)(THIS_ REFGUID a) { return m_ptr->GetDeviceStatus(a); } 25 | COM_METHOD(HRESULT, RunControlPanel)(THIS_ HWND a, DWORD b) { return m_ptr->RunControlPanel(a, b); } 26 | COM_METHOD(HRESULT, Initialize)(THIS_ HINSTANCE a, DWORD b) { return m_ptr->Initialize(a, b); } 27 | COM_METHOD(HRESULT, FindDevice)(THIS_ REFGUID a, LPCWSTR b, LPGUID c) { return m_ptr->FindDevice(a, b, c); } 28 | COM_METHOD(HRESULT, EnumDevicesBySemantics)(THIS_ LPCWSTR a, LPDIACTIONFORMATW b, LPDIENUMDEVICESBYSEMANTICSCBW c, LPVOID d, DWORD e) { return m_ptr->EnumDevicesBySemantics(a, b, c, d, e); } 29 | COM_METHOD(HRESULT, ConfigureDevices)(THIS_ LPDICONFIGUREDEVICESCALLBACK a, LPDICONFIGUREDEVICESPARAMSW b, DWORD c, LPVOID d) { return m_ptr->ConfigureDevices(a, b, c, d); } 30 | }; -------------------------------------------------------------------------------- /FlatOut2/CDIDevice8Hook.cpp: -------------------------------------------------------------------------------- 1 | #include "stdafx.h" 2 | #include "CDIDevice8Hook.h" 3 | 4 | BOOL CDIDevice8Hook::IsButtonPress(DWORD button) 5 | { 6 | if (button < 128) 7 | { 8 | return m_currentJoyState.rgbButtons[button] && !m_lastJoyState.rgbButtons[button]; 9 | } 10 | else 11 | { 12 | return false; 13 | } 14 | } 15 | 16 | void CDIDevice8Hook::GetStickDirection(LPINT upDown, LPINT rightLeft) 17 | { 18 | signed short joystickXPos = (signed short)m_currentJoyState.lX; 19 | signed short joystickYPos = (signed short)m_currentJoyState.lY; 20 | if (joystickXPos > XBOX_JOY_THRESHOLD) 21 | { 22 | *rightLeft = 1; 23 | } 24 | else if (joystickXPos < -XBOX_JOY_THRESHOLD) 25 | { 26 | *rightLeft = -1; 27 | } 28 | else if (abs(joystickXPos) < XBOX_JOY_R_THRESHOLD) 29 | { 30 | *rightLeft = 0; 31 | } 32 | else 33 | { 34 | *rightLeft = 2; 35 | } 36 | 37 | if (joystickYPos > XBOX_JOY_THRESHOLD) 38 | { 39 | *upDown = 1; 40 | } 41 | else if (joystickYPos < -XBOX_JOY_THRESHOLD) 42 | { 43 | *upDown = -1; 44 | } 45 | else if (abs(joystickYPos) < XBOX_JOY_R_THRESHOLD) 46 | { 47 | *upDown = 0; 48 | } 49 | else 50 | { 51 | *upDown = 2; 52 | } 53 | } 54 | 55 | HRESULT CDIDevice8Hook::SetCooperativeLevel(HWND hwnd, DWORD dwFlags) 56 | { 57 | HRESULT res = m_ptr->SetCooperativeLevel(hwnd, DISCL_BACKGROUND | DISCL_NONEXCLUSIVE); 58 | return res; 59 | } 60 | 61 | HRESULT CDIDevice8Hook::GetDeviceState(DWORD cbData, LPVOID lpvData) 62 | { 63 | HRESULT res = m_ptr->GetDeviceState(cbData, lpvData); 64 | memcpy_s(&m_lastJoyState, sizeof(m_lastJoyState), &m_currentJoyState, sizeof(m_currentJoyState)); 65 | memcpy_s(&m_currentJoyState, sizeof(m_currentJoyState), lpvData, sizeof(DIJOYSTATE2)); 66 | return res; 67 | } 68 | -------------------------------------------------------------------------------- /FlatOut2/Logging.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | constexpr auto LOGGING_SHAREDBUFFER = L"Local\\FO2_SS_LogShared"; 3 | constexpr auto LOGGING_FILECREATEMUTEX = L"Local\\FO2_SS_LogCreateMutex"; 4 | constexpr auto LOGGING_THREADMUTEX = L"Local\\FO2_SS_LogThreadMutex"; 5 | constexpr auto LOGGING_WRITEMUTEX = L"Local\\FO2_SS_LogWriteMutex"; 6 | constexpr auto LOGGING_SIGNALLOGTHREAD = L"Local\\FO2_SS_LogWriteSignal"; 7 | constexpr auto LOGGING_BUFFERSIZE = 0x1000; 8 | 9 | struct LoggingBuffer { 10 | DWORD writePos = 0; 11 | BYTE buffer[LOGGING_BUFFERSIZE - sizeof(DWORD)]; 12 | }; 13 | 14 | class Logging 15 | { 16 | public: 17 | static Logging& getInstance() { 18 | static Logging instance; 19 | return instance; 20 | } 21 | enum Level : int { Off, Error, Warn, Info, Debug, Trace }; 22 | void error(const char* tag, std::string msg) { log(Level::Error, tag, msg); } 23 | void warn(const char* tag, std::string msg) { log(Level::Warn, tag, msg); } 24 | void info(const char* tag, std::string msg) { log(Level::Info, tag, msg); } 25 | void debug(const char* tag, std::string msg) { log(Level::Debug, tag, msg); } 26 | void trace(const char* tag, std::string msg) { log(Level::Trace, tag, msg); } 27 | private: 28 | Logging(); 29 | void log(Level level, const char* tag, std::string msg); 30 | void writeBuffer(std::string formattedMsg); 31 | static DWORD WINAPI threadEntry(LPVOID p) { return static_cast(p)->threadLoop(); } 32 | DWORD threadLoop(); 33 | LoggingBuffer* m_sharedBuffer; 34 | HANDLE m_sharedBufferMapping; 35 | HANDLE m_createMappingsMutex; 36 | HANDLE m_bufferMutex; 37 | HANDLE m_loggerThreadMutex; 38 | HANDLE m_loggerThreadSignal; 39 | public: 40 | Logging(Logging const&) = delete; 41 | void operator=(Logging const&) = delete; 42 | }; 43 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FlatOut 2 Splitscreen Mod 2 | ![gameplay](http://imgur.com/wNrbZ8I.png) 3 | 4 | ## 1. Installation 5 | 1. Install [Microsoft .NET Framework 4.5.2](https://www.microsoft.com/en-us/download/details.aspx?id=42643). 6 | 2. Download the latest release at [Releases](https://github.com/DeadlySurprise/FO2-Splitscreen/releases). 7 | 3. Extract all files into the FlatOut 2 game folder. (e.g. C:\\Program Files\\FlatOut2\\). 8 | 3a. If you're using a fresh installation of the game, launch it once regularly. (Run FlatOut2.exe) 9 | 4. Run the Launcher.exe 10 | 11 | ## 2. Configuration 12 | ![Launcher configuration](http://i.imgur.com/0BcnTTi.png) 13 | 14 | ### 1. General Tab 15 | * Select number of players 16 | * Set resolution of the game windows 17 | 18 | ### 2. Controllers tab 19 | * Select a controller for each player 20 | 21 | ### 3. In-game 22 | **The game will open multiple times, once for each player. All In-game setup can only be performed by using the keyboard and clicking on the window of the instance you're trying to setup.** 23 | * Go to settings, controller options and select your controller. Afterwards you can change your button bindings. These settings are usually saved between sessions. 24 | * Now you can start a multiplayer session by selecting multiplayer -> LAN in the main menu and selecting Create game with the host instance (The top left game.) 25 | * Afterwards you can join with all other players by selecting multiplayer -> LAN -> Join Game. 26 | **Even though you are playing in LAN mode it is currently not possible to join a splitscreen session from another computer in your network!** 27 | ### 4. Advanced Settings 28 | * Settings can be changed manually by editing `Splitscreen_Settings.xml` and starting the `Launcher.exe` with the command line argument `-noConfig` 29 | 30 | ## 3. Troubleshooting 31 | * If the game repeatedly crashes on startup, try disabling "Skip intro videos" in the launcher. 32 | 33 | ## 5. Documentation 34 | 35 | Documentation about the inner workings of the mod can be found in the [docs](/docs) folder. 36 | -------------------------------------------------------------------------------- /Launcher/Settings.StyleCop: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | False 8 | 9 | 10 | 11 | 12 | False 13 | 14 | 15 | 16 | 17 | False 18 | 19 | 20 | 21 | 22 | False 23 | 24 | 25 | 26 | 27 | False 28 | 29 | 30 | 31 | 32 | False 33 | 34 | 35 | 36 | 37 | False 38 | 39 | 40 | 41 | 42 | True 43 | 44 | 45 | 46 | 47 | 48 | 49 | False 50 | 51 | 52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /Launcher/Program.cs: -------------------------------------------------------------------------------- 1 | namespace Launcher 2 | { 3 | using System; 4 | using System.Diagnostics; 5 | using System.IO; 6 | using System.Linq; 7 | using System.Threading; 8 | using System.Windows.Forms; 9 | 10 | static class Program 11 | { 12 | /// 13 | /// The main entry point for the application. 14 | /// 15 | [STAThread] 16 | static void Main(string[] args) 17 | { 18 | try 19 | { 20 | if (args.Contains("-noConfig")) 21 | { 22 | using (var s = File.OpenRead(Setup.settingsFile)) 23 | { 24 | using (var settings = Settings.LoadSettings(s)) 25 | { 26 | FlatOut2.CreateSplitScreenSession(settings).Wait(); 27 | Application.Exit(); 28 | return; 29 | } 30 | } 31 | } 32 | else 33 | { 34 | Application.EnableVisualStyles(); 35 | Application.SetCompatibleTextRenderingDefault(false); 36 | Application.SetUnhandledExceptionMode(UnhandledExceptionMode.ThrowException); 37 | Application.ThreadException += Application_ThreadException; 38 | Application.Run(new Setup()); 39 | } 40 | } 41 | catch (Exception e) 42 | { 43 | ShowFatalExceptionDialog(e); 44 | 45 | } 46 | } 47 | 48 | private static void Application_ThreadException(object sender, ThreadExceptionEventArgs e) 49 | { 50 | ShowFatalExceptionDialog(e.Exception); 51 | } 52 | 53 | private static void ShowFatalExceptionDialog(Exception e) 54 | { 55 | MessageBox.Show( 56 | $"Unhandled {e.ToString()} in\n" + 57 | $"{e.Source}@{e.TargetSite}\n" + 58 | e.StackTrace, 59 | "Unhandled exception", 60 | MessageBoxButtons.OK, 61 | MessageBoxIcon.Error); 62 | Application.Exit(); 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /FlatOut2/InstanceSettings.h: -------------------------------------------------------------------------------- 1 | #include "stdafx.h" 2 | #include "CDirect3D9Hook.h" 3 | #include "CDirectInput8Hook.h" 4 | 5 | #ifndef INSTANCESETTINGS 6 | #define INSTANCESETTINGS 7 | #define INSTANCESETTINGS_HOSTINST 0 8 | #define INSTANCESETTINGS_MEMNAME L"Local\\FO2_SS_Shared" 9 | #define INSTANCESETTINGS_WAITNAME L"Local\\FO2_SS_Wait" 10 | 11 | struct SET_InstanceSettings 12 | { 13 | int instanceID; 14 | GUID instanceController; 15 | HWND mainHwnd = NULL; 16 | DWORD procID = NULL; 17 | RECT windowPos; 18 | CDirect3D9Hook* d3d = NULL; 19 | CDirectInput8Hook* directInput = NULL; 20 | }; 21 | 22 | struct SET_NetSettings 23 | { 24 | u_short virtHostPort; 25 | u_char virtAddressOffset; 26 | char virtAddressRange[16]; 27 | }; 28 | 29 | struct SET_GlobalSettings 30 | { 31 | int instanceCount; 32 | BOOL useInputEmulation; 33 | BOOL skipIntros; 34 | SET_NetSettings network; 35 | Logging::Level logFileVerbosity; 36 | Logging::Level consoleVerbosity; 37 | GUID controller[FO2_MaxClientCount]; 38 | RECT windowPos[FO2_MaxClientCount]; 39 | }; 40 | 41 | struct SET_SharedSettings 42 | { 43 | u_short virtClientPorts[FO2_MaxClientCount]; 44 | SET_GlobalSettings globals; 45 | SET_InstanceSettings instances[FO2_MaxClientCount]; 46 | }; 47 | 48 | class InstanceSettings 49 | { 50 | public: 51 | ~InstanceSettings(); 52 | public: 53 | static InstanceSettings* InitSettings(int instanceID); 54 | static InstanceSettings* GetSettings(); 55 | SET_InstanceSettings GetLocalSettings(); 56 | SET_NetSettings GetNetworkSettings(); 57 | BOOL IsHostInstance(); 58 | int GetInstanceCount(); 59 | int GetInstanceID(); 60 | int GetInstanceID(u_short clientPortH); 61 | u_short GetInstanceVirtPort(int instanceID); 62 | Logging::Level GetConsoleVerbosity() { return m_settings->globals.consoleVerbosity; } 63 | Logging::Level GetLogFileVerbosity() { return m_settings->globals.logFileVerbosity; } 64 | void SetInstanceVirtPort(u_short clientPortH); 65 | void SetGameWindowHandle(HWND window); 66 | void SetDirect3DHook(CDirect3D9Hook* d3d); 67 | void SetDirectInputHook(CDirectInput8Hook* dinput); 68 | BOOL UseInputEmulation(); 69 | BOOL SkipIntros(); 70 | private: 71 | InstanceSettings(); 72 | private: 73 | static InstanceSettings* m_instanceSettings; 74 | private: 75 | HANDLE m_sharedMem; 76 | SET_SharedSettings* m_settings; 77 | int m_instanceID; 78 | }; 79 | #endif -------------------------------------------------------------------------------- /FlatOut2/DetourCallDefs.h: -------------------------------------------------------------------------------- 1 | #include "stdafx.h" 2 | 3 | #pragma once 4 | typedef IDirect3D9* (WINAPI *dxCreate)(UINT); 5 | typedef HRESULT(WINAPI *diCreate)(HINSTANCE, DWORD, REFIID, LPVOID*, LPUNKNOWN); 6 | typedef HWND(WINAPI* CreateWinExA) 7 | (DWORD dwExStyle, LPCSTR lpClassName, LPCSTR lpWindowName, DWORD dwStyle, int x, 8 | int y, int nWidth, int nHeight, HWND hWndParent, HMENU hMenu, HINSTANCE hInstance, LPVOID lpParam); 9 | typedef BOOL(WINAPI* PeekMessageT)(LPMSG lpMsg, HWND hWnd, UINT wMsgFilterMin, UINT wMsgFilterMax, UINT wRemoveMsg); 10 | typedef HHOOK(WINAPI* SetWinHookExA)(int idHook, HOOKPROC lpfn, HINSTANCE hMod, DWORD dwThreadId); 11 | typedef LRESULT(WINAPI* CallNextHook)(HHOOK hhk, int code, WPARAM wParam, LPARAM lParam); 12 | typedef LRESULT(CALLBACK* KBHookProc)(int code, WPARAM wParam, LPARAM lParam); 13 | typedef int(WSAAPI* sBind)(SOCKET s, const struct sockaddr *name, int namelen); 14 | typedef int(WSAAPI* sClose)(SOCKET s); 15 | typedef int(WSAAPI* WSAStart)(WORD wVersionRequested, LPWSADATA lpWSAData); 16 | typedef int(WSAAPI* WSAClean)(); 17 | typedef DWORD(WSAAPI* WSAWaitMultiple)(DWORD cEvents, const WSAEVENT *lphEvents, BOOL fWaitAll, DWORD dwTimeout, BOOL fAlertable); 18 | typedef BOOL(WSAAPI* OverlappedResult)(SOCKET s, LPWSAOVERLAPPED lpOverlapped, LPDWORD lpcbTransfer, BOOL fWait, LPDWORD lpdwFlags); 19 | typedef int (WSAAPI* RecvFrom)( 20 | SOCKET s, LPWSABUF lpBuffers, DWORD dwBufferCount, LPDWORD lpNumberOfBytesRecvd, LPDWORD lpFlags, 21 | struct sockaddr *lpFrom, LPINT lpFromlen, LPWSAOVERLAPPED lpOverlapped, LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine); 22 | typedef int(WSAAPI* SendTo)( 23 | SOCKET s, LPWSABUF lpBuffers, DWORD dwBufferCount, LPDWORD lpNumberOfBytesSent, DWORD dwFlags, const struct sockaddr *lpTo, 24 | int iToLen, LPWSAOVERLAPPED lpOverlapped, LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine); 25 | typedef u_short(WSAAPI* HTONS)(u_short hostshort); 26 | typedef BOOL(WSAAPI* WSAEReset)(WSAEVENT hEvent); 27 | typedef DWORD(WINAPI* GetAdaptInfo)(PIP_ADAPTER_INFO pAdapterInfo, PULONG pOutBufLen); 28 | typedef int (WSAAPI* hostName)(char* name, int namelen); 29 | typedef struct hostent* FAR(WSAAPI* hostbyname)(const char* name); 30 | typedef int(_stdcall* _WinMain)(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd); 31 | typedef void(_stdcall* PlayMovie)(UINT unknown1, LPCSTR vidPath, UINT unknown2); 32 | typedef void(_stdcall* VoidCall)(); 33 | #define COM_METHOD(TYPE, METHOD) TYPE STDMETHODCALLTYPE METHOD -------------------------------------------------------------------------------- /Launcher/GamePad.cs: -------------------------------------------------------------------------------- 1 | namespace Launcher 2 | { 3 | using System; 4 | 5 | /// 6 | /// An immutable class for storing information about a connected controller. Provides methods for converting from/to strings. 7 | /// 8 | public class GamePad 9 | { 10 | private Guid guid; 11 | private string deviceName; 12 | private string instanceName; 13 | 14 | /// 15 | /// Default constructor that for defining all properties of the controller. 16 | /// 17 | /// The device GUID. 18 | /// The device category name. 19 | /// The device instance name. 20 | public GamePad(Guid guid, string deviceName, string instanceName) 21 | { 22 | this.guid = guid; 23 | this.deviceName = deviceName; 24 | this.instanceName = instanceName; 25 | } 26 | 27 | /// 28 | /// Converts a string of the format "deviceName,instanceName,GUID" to object. 29 | /// 30 | /// A string of the format "deviceName,instanceName,GUID". 31 | /// Return a object converted from the input string. 32 | public static GamePad Parse(string input) 33 | { 34 | string[] args = input.Split(','); 35 | if (args.Length != 3) 36 | { 37 | throw new FormatException(); 38 | } 39 | 40 | Guid g = Guid.Parse(args[2]); 41 | return new GamePad(g, args[0], args[1]); 42 | } 43 | 44 | /// 45 | public override string ToString() 46 | { 47 | return $"{deviceName}:{instanceName}"; 48 | } 49 | 50 | /// 51 | public override int GetHashCode() 52 | { 53 | return guid.GetHashCode(); 54 | } 55 | 56 | /// 57 | public override bool Equals(object obj) 58 | { 59 | if (obj == null) 60 | { 61 | return false; 62 | } 63 | 64 | GamePad pad = obj as GamePad; 65 | if (pad == null) 66 | { 67 | return false; 68 | } 69 | 70 | return pad.guid == guid; 71 | } 72 | 73 | /// 74 | /// Gets the of this controller. 75 | /// 76 | public Guid Guid 77 | { 78 | get 79 | { 80 | return guid; 81 | } 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /Launcher/Properties/Resources.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.42000 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace Launcher.Properties 12 | { 13 | 14 | 15 | /// 16 | /// A strongly-typed resource class, for looking up localized strings, etc. 17 | /// 18 | // This class was auto-generated by the StronglyTypedResourceBuilder 19 | // class via a tool like ResGen or Visual Studio. 20 | // To add or remove a member, edit your .ResX file then rerun ResGen 21 | // with the /str option, or rebuild your VS project. 22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] 23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 25 | internal class Resources 26 | { 27 | 28 | private static global::System.Resources.ResourceManager resourceMan; 29 | 30 | private static global::System.Globalization.CultureInfo resourceCulture; 31 | 32 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] 33 | internal Resources() 34 | { 35 | } 36 | 37 | /// 38 | /// Returns the cached ResourceManager instance used by this class. 39 | /// 40 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 41 | internal static global::System.Resources.ResourceManager ResourceManager 42 | { 43 | get 44 | { 45 | if ((resourceMan == null)) 46 | { 47 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Launcher.Properties.Resources", typeof(Resources).Assembly); 48 | resourceMan = temp; 49 | } 50 | return resourceMan; 51 | } 52 | } 53 | 54 | /// 55 | /// Overrides the current thread's CurrentUICulture property for all 56 | /// resource lookups using this strongly typed resource class. 57 | /// 58 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 59 | internal static global::System.Globalization.CultureInfo Culture 60 | { 61 | get 62 | { 63 | return resourceCulture; 64 | } 65 | set 66 | { 67 | resourceCulture = value; 68 | } 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /FlatOut2/TestScriptRunner.cpp: -------------------------------------------------------------------------------- 1 | #include "stdafx.h" 2 | #include "TestScriptRunner.h" 3 | 4 | 5 | TestScriptRunner::TestScriptRunner(const wchar_t* scriptFile, PressKeyCallback pkCallback) 6 | { 7 | try { 8 | m_scriptFile.open(scriptFile, std::ios::in); 9 | } 10 | catch (std::ios_base::failure& e) { 11 | MessageBoxA(NULL, e.what(), NULL, MB_OK); 12 | } 13 | 14 | m_pkCallback = pkCallback; 15 | m_waitTime = 0; 16 | } 17 | 18 | 19 | TestScriptRunner::~TestScriptRunner() 20 | { 21 | m_scriptFile.close(); 22 | } 23 | 24 | void TestScriptRunner::SetSetting(std::string setting, DWORD value) 25 | { 26 | m_settings[setting] = value; 27 | } 28 | 29 | ULONGLONG GetSysTime() 30 | { 31 | SYSTEMTIME st; 32 | FILETIME ft; 33 | LARGE_INTEGER lit; 34 | GetSystemTime(&st); 35 | SystemTimeToFileTime(&st, &ft); 36 | lit.HighPart = ft.dwHighDateTime; 37 | lit.LowPart = ft.dwLowDateTime; 38 | return lit.QuadPart; 39 | } 40 | 41 | DWORD TestScriptRunner::RunOnce() 42 | { 43 | if (m_waitTime) 44 | { 45 | ULONGLONG ct = GetSysTime() / 10000; 46 | ULONGLONG wt = m_waitStart / 10000; 47 | ULONGLONG timeDeltaMS = ct - wt; 48 | if (m_waitTime > timeDeltaMS) 49 | { 50 | return SR_WAIT; 51 | } 52 | else 53 | { 54 | m_waitTime = 0; 55 | } 56 | } 57 | 58 | int trueIfDepth = 0; 59 | int ifDepth = 0; 60 | while (true) 61 | { 62 | if (m_scriptFile.eof()) 63 | { 64 | return SR_EOF; 65 | } 66 | else if (m_scriptFile.bad() || m_scriptFile.fail()) 67 | { 68 | return SR_ERROR; 69 | } 70 | 71 | std::string str; 72 | std::getline(m_scriptFile, str); 73 | m_lineNum++; 74 | int falseIfDepth = ifDepth - trueIfDepth; 75 | if (falseIfDepth < -1) 76 | { 77 | throw new std::exception(); 78 | } 79 | 80 | if (0 == str.compare("ENDIF")) 81 | { 82 | ifDepth--; 83 | if (0 == falseIfDepth) 84 | { 85 | trueIfDepth--; 86 | } 87 | } 88 | else if ( 0 == str.compare("ELSE")) 89 | { 90 | if (0 < falseIfDepth) 91 | { 92 | trueIfDepth++; 93 | } 94 | else 95 | { 96 | trueIfDepth--; 97 | } 98 | } 99 | else if (falseIfDepth == 0) 100 | { 101 | if (0 == _strnicmp("IF", str.c_str(), 2)) 102 | { 103 | ifDepth++; 104 | std::string exp = str.substr(3, str.npos); 105 | if (m_settings.count(exp) && m_settings[exp]) 106 | { 107 | trueIfDepth++; 108 | } 109 | } 110 | else if (0 == _strnicmp("PRESS", str.c_str(), 5)) 111 | { 112 | std::string exp = str.substr(6, str.npos); 113 | int key = std::stoi(exp); 114 | m_pkCallback(key); 115 | return SR_SUCCESS; 116 | } 117 | else if (0 == _strnicmp("WAIT", str.c_str(), 4)) 118 | { 119 | std::string exp = str.substr(5, str.npos); 120 | int wait = std::stoi(exp); 121 | m_waitTime = wait; 122 | m_waitStart = GetSysTime(); 123 | return SR_WAIT; 124 | } 125 | else if (0 == _strnicmp("PRINT", str.c_str(), 5)) 126 | { 127 | std::string exp = str.substr(6, str.npos); 128 | Logging::getInstance().debug("SCRIPT", exp); 129 | } 130 | } 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /FlatOut2.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.29230.47 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Launcher", "Launcher\Launcher.csproj", "{4760197A-8A5D-4C93-B5A0-532C03F2257A}" 7 | ProjectSection(ProjectDependencies) = postProject 8 | {4AE789A1-99C7-4D40-BB22-096ED992398C} = {4AE789A1-99C7-4D40-BB22-096ED992398C} 9 | EndProjectSection 10 | EndProject 11 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "FlatOut2", "FlatOut2\FlatOut2.vcxproj", "{4AE789A1-99C7-4D40-BB22-096ED992398C}" 12 | ProjectSection(ProjectDependencies) = postProject 13 | {AF2527E9-D3B1-4B89-9E3E-6F8C00593E0C} = {AF2527E9-D3B1-4B89-9E3E-6F8C00593E0C} 14 | EndProjectSection 15 | EndProject 16 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Detours", "Detours.vcxproj", "{AF2527E9-D3B1-4B89-9E3E-6F8C00593E0C}" 17 | EndProject 18 | Global 19 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 20 | Debug|x64 = Debug|x64 21 | Debug|x86 = Debug|x86 22 | Release|x64 = Release|x64 23 | Release|x86 = Release|x86 24 | EndGlobalSection 25 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 26 | {4760197A-8A5D-4C93-B5A0-532C03F2257A}.Debug|x64.ActiveCfg = Debug|Any CPU 27 | {4760197A-8A5D-4C93-B5A0-532C03F2257A}.Debug|x64.Build.0 = Debug|Any CPU 28 | {4760197A-8A5D-4C93-B5A0-532C03F2257A}.Debug|x86.ActiveCfg = Debug|Any CPU 29 | {4760197A-8A5D-4C93-B5A0-532C03F2257A}.Debug|x86.Build.0 = Debug|Any CPU 30 | {4760197A-8A5D-4C93-B5A0-532C03F2257A}.Release|x64.ActiveCfg = Release|Any CPU 31 | {4760197A-8A5D-4C93-B5A0-532C03F2257A}.Release|x64.Build.0 = Release|Any CPU 32 | {4760197A-8A5D-4C93-B5A0-532C03F2257A}.Release|x86.ActiveCfg = Release|Any CPU 33 | {4760197A-8A5D-4C93-B5A0-532C03F2257A}.Release|x86.Build.0 = Release|Any CPU 34 | {4AE789A1-99C7-4D40-BB22-096ED992398C}.Debug|x64.ActiveCfg = Debug|x64 35 | {4AE789A1-99C7-4D40-BB22-096ED992398C}.Debug|x64.Build.0 = Debug|x64 36 | {4AE789A1-99C7-4D40-BB22-096ED992398C}.Debug|x86.ActiveCfg = Debug|Win32 37 | {4AE789A1-99C7-4D40-BB22-096ED992398C}.Debug|x86.Build.0 = Debug|Win32 38 | {4AE789A1-99C7-4D40-BB22-096ED992398C}.Release|x64.ActiveCfg = Release|x64 39 | {4AE789A1-99C7-4D40-BB22-096ED992398C}.Release|x64.Build.0 = Release|x64 40 | {4AE789A1-99C7-4D40-BB22-096ED992398C}.Release|x86.ActiveCfg = Release|Win32 41 | {4AE789A1-99C7-4D40-BB22-096ED992398C}.Release|x86.Build.0 = Release|Win32 42 | {AF2527E9-D3B1-4B89-9E3E-6F8C00593E0C}.Debug|x64.ActiveCfg = Debug|x64 43 | {AF2527E9-D3B1-4B89-9E3E-6F8C00593E0C}.Debug|x64.Build.0 = Debug|x64 44 | {AF2527E9-D3B1-4B89-9E3E-6F8C00593E0C}.Debug|x86.ActiveCfg = Debug|Win32 45 | {AF2527E9-D3B1-4B89-9E3E-6F8C00593E0C}.Debug|x86.Build.0 = Debug|Win32 46 | {AF2527E9-D3B1-4B89-9E3E-6F8C00593E0C}.Release|x64.ActiveCfg = Release|x64 47 | {AF2527E9-D3B1-4B89-9E3E-6F8C00593E0C}.Release|x64.Build.0 = Release|x64 48 | {AF2527E9-D3B1-4B89-9E3E-6F8C00593E0C}.Release|x86.ActiveCfg = Release|Win32 49 | {AF2527E9-D3B1-4B89-9E3E-6F8C00593E0C}.Release|x86.Build.0 = Release|Win32 50 | EndGlobalSection 51 | GlobalSection(SolutionProperties) = preSolution 52 | HideSolutionNode = FALSE 53 | EndGlobalSection 54 | GlobalSection(ExtensibilityGlobals) = postSolution 55 | SolutionGuid = {A40EEB40-FE7A-477C-9A79-EC6ADEC80CCB} 56 | EndGlobalSection 57 | EndGlobal 58 | -------------------------------------------------------------------------------- /FlatOut2/CDirect3D9Hook.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "stdafx.h" 3 | #include "CDirect3D9DevHook.h" 4 | 5 | class CDirect3D9Hook : public IDirect3D9 6 | { 7 | private: 8 | IDirect3D9* m_ptr; 9 | CDirect3D9DevHook* m_pdev; 10 | public: 11 | CDirect3D9Hook(IDirect3D9* ptr); 12 | public: 13 | HRESULT _stdcall CreateDevice(UINT Adapter, D3DDEVTYPE DeviceType, HWND hFocusWindow, DWORD BehaviorFlags, D3DPRESENT_PARAMETERS* pPresentationParameters, IDirect3DDevice9** ppReturnedDeviceInterface); 14 | 15 | /*** IUnknown methods ***/ 16 | COM_METHOD(HRESULT, QueryInterface)(THIS_ REFIID riid, void** ppvObj) { /*TODO*/ return m_ptr->QueryInterface(riid, ppvObj); } 17 | COM_METHOD(ULONG, AddRef)(THIS) { /*TODO*/ return m_ptr->AddRef(); } 18 | COM_METHOD(ULONG, Release)(THIS) { /*TODO*/ return m_ptr->Release(); } 19 | 20 | /*** IDirect3D9 methods ***/ 21 | COM_METHOD(HRESULT, RegisterSoftwareDevice)(THIS_ void* pInitializeFunction) { /*TODO*/ return m_ptr->RegisterSoftwareDevice(pInitializeFunction); } 22 | COM_METHOD(UINT, GetAdapterCount)(THIS) { /*TODO*/ return m_ptr->GetAdapterCount(); } 23 | COM_METHOD(HRESULT, GetAdapterIdentifier)(THIS_ UINT Adapter, DWORD Flags, D3DADAPTER_IDENTIFIER9* pIdentifier) { /*TODO*/ return m_ptr->GetAdapterIdentifier(Adapter, Flags, pIdentifier); } 24 | COM_METHOD(UINT, GetAdapterModeCount)(THIS_ UINT Adapter, D3DFORMAT Format) { /*TODO*/ return m_ptr->GetAdapterModeCount(Adapter, Format); } 25 | COM_METHOD(HRESULT, EnumAdapterModes)(THIS_ UINT Adapter, D3DFORMAT Format, UINT Mode, D3DDISPLAYMODE* pMode) { /*TODO*/ return m_ptr->EnumAdapterModes(Adapter, Format, Mode, pMode); } 26 | COM_METHOD(HRESULT, GetAdapterDisplayMode)(THIS_ UINT Adapter, D3DDISPLAYMODE* pMode) { /*TODO*/ return m_ptr->GetAdapterDisplayMode(Adapter, pMode); } 27 | COM_METHOD(HRESULT, CheckDeviceType)(THIS_ UINT Adapter, D3DDEVTYPE DevType, D3DFORMAT AdapterFormat, D3DFORMAT BackBufferFormat, BOOL bWindowed) { /*TODO*/ return m_ptr->CheckDeviceType(Adapter, DevType, AdapterFormat, BackBufferFormat, bWindowed); } 28 | COM_METHOD(HRESULT, CheckDeviceFormat)(THIS_ UINT Adapter, D3DDEVTYPE DeviceType, D3DFORMAT AdapterFormat, DWORD Usage, D3DRESOURCETYPE RType, D3DFORMAT CheckFormat) { /*TODO*/ return m_ptr->CheckDeviceFormat(Adapter, DeviceType, AdapterFormat, Usage, RType, CheckFormat); } 29 | COM_METHOD(HRESULT, CheckDeviceMultiSampleType)(THIS_ UINT Adapter, D3DDEVTYPE DeviceType, D3DFORMAT SurfaceFormat, BOOL Windowed, D3DMULTISAMPLE_TYPE MultiSampleType, DWORD* pQualityLevels) { /*TODO*/ return m_ptr->CheckDeviceMultiSampleType(Adapter, DeviceType, SurfaceFormat, Windowed, MultiSampleType, pQualityLevels); } 30 | COM_METHOD(HRESULT, CheckDepthStencilMatch)(THIS_ UINT Adapter, D3DDEVTYPE DeviceType, D3DFORMAT AdapterFormat, D3DFORMAT RenderTargetFormat, D3DFORMAT DepthStencilFormat) { /*TODO*/ return m_ptr->CheckDepthStencilMatch(Adapter, DeviceType, AdapterFormat, RenderTargetFormat, DepthStencilFormat); } 31 | COM_METHOD(HRESULT, CheckDeviceFormatConversion)(THIS_ UINT Adapter, D3DDEVTYPE DeviceType, D3DFORMAT SourceFormat, D3DFORMAT TargetFormat) { /*TODO*/ return m_ptr->CheckDeviceFormatConversion(Adapter, DeviceType, SourceFormat, TargetFormat); } 32 | COM_METHOD(HRESULT, GetDeviceCaps)(THIS_ UINT Adapter, D3DDEVTYPE DeviceType, D3DCAPS9* pCaps) { /*TODO*/ return m_ptr->GetDeviceCaps(Adapter, DeviceType, pCaps); } 33 | COM_METHOD(HMONITOR, GetAdapterMonitor)(THIS_ UINT Adapter) { /*TODO*/ return m_ptr->GetAdapterMonitor(Adapter); } 34 | }; 35 | -------------------------------------------------------------------------------- /FlatOut2/CDirect3D9Hook.cpp: -------------------------------------------------------------------------------- 1 | #include "stdafx.h" 2 | #include "CDirect3D9Hook.h" 3 | 4 | CDirect3D9Hook::CDirect3D9Hook(IDirect3D9 * ptr) 5 | { 6 | if (ptr == NULL) { 7 | MessageBox(NULL, L"Error in CDirect3D9Hook::CDirect3D9Hook. DirectX object pointer was null!", L"DirectX Error", MB_OK | MB_ICONERROR); 8 | throw std::exception("Error in CDirect3D9Hook::CDirect3D9Hook. DirectX object pointer was null!"); 9 | } 10 | 11 | m_ptr = ptr; 12 | } 13 | 14 | BOOL WriteErrorLog(const char* filename, HRESULT hr, UINT Adapter, D3DDEVTYPE DeviceType, HWND hFocusWindow, DWORD BehaviorFlags, D3DPRESENT_PARAMETERS * pPresentationParameters, IDirect3DDevice9 ** ppReturnedDeviceInterface) 15 | { 16 | std::ofstream errorLog = std::ofstream(filename, std::ofstream::out | std::ofstream::app); 17 | errorLog 18 | << "Error log CDirect3D9Hook::CreateDevice." << std::endl 19 | << "HRESULT : " << hr << std::endl 20 | << "Adapter : " << Adapter << std::endl 21 | << "DeviceType : " << DeviceType << std::endl 22 | << "hFocusWindow : " << hFocusWindow << std::endl 23 | << "BehaviorFlags : " << BehaviorFlags << std::endl 24 | << "pPresentationParameters : " << pPresentationParameters << std::endl 25 | << "ppReturnedDeviceInterface : " << ppReturnedDeviceInterface << std::endl 26 | << "Presentation Parameters : " << std::endl 27 | << "\tBackBufferWidth : " << pPresentationParameters->BackBufferWidth << std::endl 28 | << "\tBackBufferHeight : " << pPresentationParameters->BackBufferHeight << std::endl 29 | << "\tBackBufferFormat : " << pPresentationParameters->BackBufferFormat << std::endl 30 | << "\tBackBufferCount : " << pPresentationParameters->BackBufferCount << std::endl 31 | << "\tMultiSampleType : " << pPresentationParameters->MultiSampleType << std::endl 32 | << "\tMultiSampleQuality : " << pPresentationParameters->MultiSampleQuality << std::endl 33 | << "\tSwapEffect : " << pPresentationParameters->SwapEffect << std::endl 34 | << "\thDeviceWindow : " << pPresentationParameters->hDeviceWindow << std::endl 35 | << "\tWindowed : " << pPresentationParameters->Windowed << std::endl 36 | << "\tEnableAutoDepthStencil : " << pPresentationParameters->EnableAutoDepthStencil << std::endl 37 | << "\tAutoDepthStencilFormat : " << pPresentationParameters->AutoDepthStencilFormat << std::endl 38 | << "\tFlags : " << pPresentationParameters->Flags << std::endl 39 | << "\tFullScreen_RefreshRateInHz : " << pPresentationParameters->FullScreen_RefreshRateInHz << std::endl; 40 | bool good = errorLog.good(); 41 | errorLog.close(); 42 | return good; 43 | } 44 | 45 | HRESULT CDirect3D9Hook::CreateDevice(UINT Adapter, D3DDEVTYPE DeviceType, HWND hFocusWindow, DWORD BehaviorFlags, D3DPRESENT_PARAMETERS * pPresentationParameters, IDirect3DDevice9 ** ppReturnedDeviceInterface) 46 | { 47 | pPresentationParameters->Windowed = true; 48 | pPresentationParameters->FullScreen_RefreshRateInHz = 0; 49 | HRESULT hr = m_ptr->CreateDevice(Adapter, DeviceType, hFocusWindow, BehaviorFlags, pPresentationParameters, ppReturnedDeviceInterface); 50 | if (hr != D3D_OK) 51 | { 52 | WCHAR errMsg[DEFAULT_ERRMSG_BUFFER]; 53 | const WCHAR* error = 54 | (hr == D3DERR_DEVICELOST ? L"D3DERR_DEVICELOST" : 55 | hr == D3DERR_INVALIDCALL ? L"D3DERR_INVALIDCALL" : 56 | hr == D3DERR_NOTAVAILABLE ? L"D3DERR_NOTAVAILABLE" : 57 | hr == D3DERR_OUTOFVIDEOMEMORY ? L"D3DERR_OUTOFVIDEOMEMORY" : L"D3DERR_UNKNOWN"); 58 | if (WriteErrorLog("d3derror.txt", hr, Adapter, DeviceType, hFocusWindow, BehaviorFlags, pPresentationParameters, ppReturnedDeviceInterface)) 59 | { 60 | StringCbPrintf(errMsg, sizeof(errMsg), L"Failed to create DirectX9 Device : %s(%d). Additional info was written to d3derror.txt.", error, hr); 61 | } 62 | else 63 | { 64 | StringCbPrintf(errMsg, sizeof(errMsg), L"Failed to create DirectX9 Device : %s(%d). Additional info could NOT be written to disk.", error, hr); 65 | } 66 | MessageBox(NULL, errMsg, L"DirectX Error", MB_OK | MB_ICONERROR); 67 | throw new std::exception("Failed to create DirectX9 Device."); 68 | } 69 | m_pdev = new CDirect3D9DevHook(*ppReturnedDeviceInterface); 70 | *ppReturnedDeviceInterface = m_pdev; 71 | return hr; 72 | } 73 | -------------------------------------------------------------------------------- /FlatOut2/dllmain.cpp: -------------------------------------------------------------------------------- 1 | #include "stdafx.h" 2 | #include "DetourCallDefs.h" 3 | #include "User32Detours.h" 4 | #include "WinSockDetours.h" 5 | #include "DXDetours.h" 6 | #include "DIDetours.h" 7 | #include "GameDetours.h" 8 | 9 | void earlyDetour() 10 | { 11 | DetourTransactionBegin(); 12 | DetourUpdateThread(GetCurrentThread()); 13 | User32Detour(); 14 | WinSockDetour(); 15 | DXDetour(); 16 | DIDetour(); 17 | DeferredHookDetour(); 18 | if (DetourTransactionCommit() != NO_ERROR) 19 | { 20 | Logging::getInstance().error("DETOURS", std::string("Detour Error!")); 21 | throw std::exception("Detour Error!"); 22 | } 23 | } 24 | 25 | void GenerateExceptionData(WCHAR* buffer, size_t size, PEXCEPTION_RECORD exception) 26 | { 27 | switch (exception->ExceptionCode) 28 | { 29 | case EXCEPTION_ACCESS_VIOLATION: 30 | if (exception->NumberParameters < 2) 31 | { 32 | StringCbPrintf(buffer, size, L"Access Violation (no info)"); 33 | } 34 | else 35 | { 36 | auto type = exception->ExceptionInformation[0]; 37 | 38 | StringCbPrintf(buffer, size, L"%s ACCESS VIOLATION at 0x%x", 39 | type == 0 ? L"READ" : type == 1 ? L"WRITE" : type == 8 ? L"DEP" : L"UNKNOWN", 40 | exception->ExceptionInformation[1]); 41 | } 42 | break; 43 | default: 44 | StringCbPrintf(buffer, size, L"Error Code : 0x%x", exception->ExceptionRecord->ExceptionCode); 45 | break; 46 | } 47 | } 48 | 49 | LONG WINAPI TerminateHandler(LPEXCEPTION_POINTERS exception) 50 | { 51 | WCHAR buffer[4096]; 52 | WCHAR dllPathBuf[4096]; 53 | WCHAR excepData[1024]; 54 | HMODULE errMod = NULL; 55 | uint32_t errOffset = 0; 56 | if ( 57 | GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, (LPWSTR)exception->ExceptionRecord->ExceptionAddress, &errMod)) 58 | { 59 | GetModuleFileName(errMod, dllPathBuf, sizeof(dllPathBuf)); 60 | auto name = wcsrchr(dllPathBuf, '\\') + 1; 61 | errOffset = (uint32_t)exception->ExceptionRecord->ExceptionAddress - (uint32_t)errMod; 62 | GenerateExceptionData(excepData, sizeof(excepData), exception->ExceptionRecord); 63 | StringCbPrintf(buffer, sizeof(buffer), L"Unhandled exception at %s+0x%x(0x%x)! %s", name, errOffset, exception->ExceptionRecord->ExceptionAddress, excepData); 64 | } 65 | else { 66 | StringCbPrintf(buffer, sizeof(buffer), L"Unhandled exception at 0x%x! Error Code :0x%x", exception->ExceptionRecord->ExceptionAddress, exception->ExceptionRecord->ExceptionCode); 67 | } 68 | MessageBox(NULL, buffer, L"Unhandled Exception", MB_OK | MB_ICONERROR); 69 | return EXCEPTION_EXECUTE_HANDLER; 70 | } 71 | 72 | BOOL APIENTRY DllMain(HMODULE hModule, 73 | DWORD ul_reason_for_call, 74 | LPVOID lpReserved) 75 | { 76 | dllHandle = hModule; 77 | WCHAR modBuf[512]; 78 | SetUnhandledExceptionFilter(TerminateHandler); 79 | if (GetModuleBaseName(GetCurrentProcess(), NULL, modBuf, sizeof(modBuf)) != NULL) 80 | { 81 | if (lstrcmpi(modBuf, L"FlatOut2.exe") != 0) 82 | { 83 | return TRUE; 84 | } 85 | } 86 | else 87 | { 88 | throw new std::exception(); 89 | } 90 | switch (ul_reason_for_call) 91 | { 92 | case DLL_PROCESS_ATTACH: 93 | if (InstanceSettings::GetSettings() == NULL) 94 | { 95 | int nArgs = -1, instanceID = -1, i = 0; 96 | LPWSTR *szArglist; 97 | szArglist = CommandLineToArgvW(GetCommandLineW(), &nArgs); 98 | while (i < nArgs) 99 | { 100 | TCHAR ident = szArglist[i][0]; 101 | if (ident == '-') 102 | { 103 | TCHAR cmd = szArglist[i][1]; 104 | switch (cmd) 105 | { 106 | case 'i': 107 | instanceID = _ttoi(szArglist[++i]); 108 | break; 109 | default: 110 | std::ostringstream msg; 111 | msg << "Unknown launch arg \"" << cmd << "\"."; 112 | Logging::getInstance().error("START", msg.str()); 113 | break; 114 | } 115 | } 116 | i++; 117 | } 118 | LocalFree(szArglist); 119 | InstanceSettings::InitSettings(instanceID); 120 | earlyDetour(); 121 | } 122 | break; 123 | case DLL_THREAD_ATTACH: 124 | case DLL_THREAD_DETACH: 125 | break; 126 | case DLL_PROCESS_DETACH: 127 | break; 128 | } 129 | return TRUE; 130 | } 131 | 132 | -------------------------------------------------------------------------------- /FlatOut2/CDIDevice8Hook.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "stdafx.h" 3 | 4 | #define XBOXBT_A 0 5 | #define XBOXBT_B 1 6 | #define XBOXBT_X 2 7 | #define XBOXBT_Y 3 8 | #define XBOXBT_BACK 6 9 | #define XBOXBT_START 7 10 | 11 | #define XBOX_JOY_THRESHOLD 8000 12 | #define XBOX_JOY_R_THRESHOLD 5000 13 | 14 | class CDIDevice8Hook : public IDirectInputDevice8 15 | { 16 | private: 17 | IDirectInputDevice8* m_ptr; 18 | DIJOYSTATE2 m_currentJoyState; 19 | DIJOYSTATE2 m_lastJoyState; 20 | public: 21 | CDIDevice8Hook(IDirectInputDevice8* ptr) : m_ptr(ptr) {} 22 | public: 23 | BOOL _stdcall IsButtonPress(DWORD button); 24 | void _stdcall GetStickDirection(LPINT upDown, LPINT rightLeft); 25 | HRESULT _stdcall SetCooperativeLevel(HWND hwnd, DWORD dwFlags); 26 | HRESULT _stdcall GetDeviceState(DWORD cbData, LPVOID lpvData); 27 | COM_METHOD(HRESULT, QueryInterface)(THIS_ REFIID riid, LPVOID * ppvObj) { return m_ptr->QueryInterface(riid, ppvObj); } 28 | COM_METHOD(ULONG, AddRef)(THIS) { return m_ptr->AddRef(); } 29 | COM_METHOD(ULONG, Release)(THIS) { return m_ptr->Release(); } 30 | COM_METHOD(HRESULT, GetCapabilities)(THIS_ LPDIDEVCAPS a) { return m_ptr->GetCapabilities(a); } 31 | COM_METHOD(HRESULT, EnumObjects)(THIS_ LPDIENUMDEVICEOBJECTSCALLBACKW a, LPVOID b, DWORD c) { return m_ptr->EnumObjects(a, b, c); } 32 | COM_METHOD(HRESULT, GetProperty)(THIS_ REFGUID a, LPDIPROPHEADER b) { return m_ptr->GetProperty(a, b); } 33 | COM_METHOD(HRESULT, SetProperty)(THIS_ REFGUID a, LPCDIPROPHEADER b) { return m_ptr->SetProperty(a, b); } 34 | COM_METHOD(HRESULT, Acquire)(THIS) { return m_ptr->Acquire(); } 35 | COM_METHOD(HRESULT, Unacquire)(THIS) { return m_ptr->Unacquire(); } 36 | COM_METHOD(HRESULT, GetDeviceData)(THIS_ DWORD a, LPDIDEVICEOBJECTDATA b, LPDWORD c, DWORD d) { return m_ptr->GetDeviceData(a, b, c, d); } 37 | COM_METHOD(HRESULT, SetDataFormat)(THIS_ LPCDIDATAFORMAT a) { return m_ptr->SetDataFormat(a); } 38 | COM_METHOD(HRESULT, SetEventNotification)(THIS_ HANDLE a) { return m_ptr->SetEventNotification(a); } 39 | COM_METHOD(HRESULT, GetObjectInfo)(THIS_ LPDIDEVICEOBJECTINSTANCEW a, DWORD b, DWORD c) { return m_ptr->GetObjectInfo(a, b, c); } 40 | COM_METHOD(HRESULT, GetDeviceInfo)(THIS_ LPDIDEVICEINSTANCEW a) { return m_ptr->GetDeviceInfo(a); } 41 | COM_METHOD(HRESULT, RunControlPanel)(THIS_ HWND a, DWORD b) { return m_ptr->RunControlPanel(a, b); } 42 | COM_METHOD(HRESULT, Initialize)(THIS_ HINSTANCE a, DWORD b, REFGUID c) { return m_ptr->Initialize(a, b, c); } 43 | COM_METHOD(HRESULT, CreateEffect)(THIS_ REFGUID a, LPCDIEFFECT b, LPDIRECTINPUTEFFECT * c, LPUNKNOWN d) { return m_ptr->CreateEffect(a, b, c, d); } 44 | COM_METHOD(HRESULT, EnumEffects)(THIS_ LPDIENUMEFFECTSCALLBACKW a, LPVOID b, DWORD c) { return m_ptr->EnumEffects(a, b, c); } 45 | COM_METHOD(HRESULT, GetEffectInfo)(THIS_ LPDIEFFECTINFOW a, REFGUID b) { return m_ptr->GetEffectInfo(a, b); } 46 | COM_METHOD(HRESULT, GetForceFeedbackState)(THIS_ LPDWORD a) { return m_ptr->GetForceFeedbackState(a); } 47 | COM_METHOD(HRESULT, SendForceFeedbackCommand)(THIS_ DWORD a) { return m_ptr->SendForceFeedbackCommand(a); } 48 | COM_METHOD(HRESULT, EnumCreatedEffectObjects)(THIS_ LPDIENUMCREATEDEFFECTOBJECTSCALLBACK a, LPVOID b, DWORD c) { return m_ptr->EnumCreatedEffectObjects(a, b, c); } 49 | COM_METHOD(HRESULT, Escape)(THIS_ LPDIEFFESCAPE a) { return m_ptr->Escape(a); } 50 | COM_METHOD(HRESULT, Poll)(THIS) { return m_ptr->Poll(); } 51 | COM_METHOD(HRESULT, SendDeviceData)(THIS_ DWORD a, LPCDIDEVICEOBJECTDATA b, LPDWORD c, DWORD d) { return m_ptr->SendDeviceData(a, b, c, d); } 52 | COM_METHOD(HRESULT, EnumEffectsInFile)(THIS_ LPCWSTR a, LPDIENUMEFFECTSINFILECALLBACK b, LPVOID c, DWORD d) { return m_ptr->EnumEffectsInFile(a, b, c, d); } 53 | COM_METHOD(HRESULT, WriteEffectToFile)(THIS_ LPCWSTR a, DWORD b, LPDIFILEEFFECT c, DWORD d) { return m_ptr->WriteEffectToFile(a, b, c, d); } 54 | COM_METHOD(HRESULT, SetActionMap)(THIS_ LPDIACTIONFORMATW a, LPCWSTR b, DWORD c) { return m_ptr->SetActionMap(a, b, c); } 55 | COM_METHOD(HRESULT, GetImageInfo)(THIS_ LPDIDEVICEIMAGEINFOHEADERW a) { return m_ptr->GetImageInfo(a); } 56 | COM_METHOD(HRESULT, BuildActionMap)(THIS_ LPDIACTIONFORMATW a, LPCWSTR b, DWORD c) { return m_ptr->BuildActionMap(a, b, c); } 57 | }; 58 | -------------------------------------------------------------------------------- /FlatOut2/Logging.cpp: -------------------------------------------------------------------------------- 1 | #include "stdafx.h" 2 | #include "Logging.h" 3 | 4 | Logging::Logging() { 5 | if (InstanceSettings::GetSettings()->GetLogFileVerbosity() == Off) { 6 | return; 7 | } 8 | 9 | DWORD waitResult; 10 | m_createMappingsMutex = CreateMutex(NULL, FALSE, LOGGING_FILECREATEMUTEX); 11 | m_bufferMutex = CreateMutex(NULL, FALSE, LOGGING_WRITEMUTEX); 12 | m_loggerThreadSignal = CreateEvent(NULL, TRUE, FALSE, LOGGING_SIGNALLOGTHREAD); 13 | if (!m_createMappingsMutex || !m_bufferMutex || !m_loggerThreadSignal) { 14 | throw std::exception("Failed to create logging mutex."); 15 | } 16 | waitResult = WaitForSingleObject(m_createMappingsMutex, 0); 17 | if (waitResult == WAIT_OBJECT_0) { 18 | // The first process has to create the shared memory 19 | m_sharedBufferMapping = CreateFileMapping( 20 | INVALID_HANDLE_VALUE, 21 | NULL, 22 | PAGE_READWRITE, 23 | 0, 24 | sizeof(LoggingBuffer), 25 | LOGGING_SHAREDBUFFER); 26 | } 27 | else { 28 | m_sharedBufferMapping = OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, LOGGING_SHAREDBUFFER); 29 | } 30 | 31 | if (!m_sharedBufferMapping) { 32 | throw std::exception("Failed to create logging shared memory mapping!"); 33 | } 34 | 35 | m_sharedBuffer = static_cast(MapViewOfFile(m_sharedBufferMapping, FILE_MAP_ALL_ACCESS, 0, 0, sizeof(LoggingBuffer))); 36 | if (!m_sharedBuffer) { 37 | throw std::exception("Failed to map view of logging shared memory!"); 38 | } 39 | 40 | // Create a logging thread for each instance so fail over is ensured if an instance exits 41 | DWORD id = 0; 42 | CreateThread(NULL, 0, threadEntry, this, 0, &id); 43 | } 44 | 45 | void Logging::log(Logging::Level level, const char* tag, std::string msg) { 46 | std::ostringstream formattedMsg; 47 | auto settings = InstanceSettings::GetSettings(); 48 | formattedMsg << '[' << tag << "|" << settings->GetInstanceID() << "] " << msg << std::endl; 49 | if (InstanceSettings::GetSettings()->GetLogFileVerbosity() >= level) { 50 | writeBuffer(formattedMsg.str()); 51 | } 52 | 53 | if (InstanceSettings::GetSettings()->GetConsoleVerbosity() >= level) { 54 | std::cout << formattedMsg.str(); 55 | } 56 | } 57 | 58 | void Logging::writeBuffer(std::string formattedMsg) { 59 | DWORD waitResult; 60 | auto msgBuffer = formattedMsg.c_str(); 61 | int copyPos = 0; 62 | waitResult = WaitForSingleObject(m_bufferMutex, INFINITE); 63 | while (copyPos < formattedMsg.length()) { 64 | SetEvent(m_loggerThreadSignal); 65 | auto availableSpace = sizeof(m_sharedBuffer->buffer) - m_sharedBuffer->writePos; 66 | if (availableSpace > 0) { 67 | auto bytesToWrite = min(availableSpace, formattedMsg.length() - copyPos); 68 | memcpy(m_sharedBuffer->buffer + m_sharedBuffer->writePos, msgBuffer + copyPos, bytesToWrite); 69 | copyPos += bytesToWrite; 70 | m_sharedBuffer->writePos += bytesToWrite; 71 | } 72 | else { 73 | // Release the mutex so the logging thread has a chance to write out the full buffer 74 | ReleaseMutex(m_bufferMutex); 75 | waitResult = WaitForSingleObject(m_bufferMutex, INFINITE); 76 | } 77 | 78 | } 79 | 80 | ReleaseMutex(m_bufferMutex); 81 | 82 | } 83 | 84 | DWORD Logging::threadLoop() { 85 | DWORD waitResult; 86 | m_loggerThreadMutex = CreateMutex(NULL, FALSE, LOGGING_THREADMUTEX); 87 | waitResult = WaitForSingleObject(m_loggerThreadMutex, INFINITE); 88 | HANDLE logFile = CreateFile(L"log.txt", FILE_GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); 89 | for (;;) { 90 | BYTE ioBuffer[sizeof(m_sharedBuffer->buffer)]; 91 | DWORD bytesToWrite; 92 | DWORD bytesWritten = 0; 93 | waitResult = WaitForSingleObject(m_loggerThreadSignal, INFINITE); 94 | waitResult = WaitForSingleObject(m_bufferMutex, INFINITE); 95 | bytesToWrite = m_sharedBuffer->writePos; 96 | memcpy_s(ioBuffer, sizeof(ioBuffer), m_sharedBuffer->buffer, m_sharedBuffer->writePos); 97 | m_sharedBuffer->writePos = 0; 98 | ZeroMemory(m_sharedBuffer->buffer, sizeof(m_sharedBuffer->buffer)); 99 | ResetEvent(m_loggerThreadSignal); 100 | ReleaseMutex(m_bufferMutex); 101 | WriteFile(logFile, ioBuffer, bytesToWrite, &bytesWritten, NULL); 102 | } 103 | } -------------------------------------------------------------------------------- /FlatOut2/VirtualIP.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "stdafx.h" 3 | #include "DetourCallDefs.h" 4 | #include "InstanceSettings.h" 5 | 6 | #define VirtRecv_StateNone 0 7 | #define VirtRecv_StateVirtBuffered 1 8 | #define VirtRecv_StateVirtOverlapped 2 9 | 10 | #define VirtHandleNet_Buffered -1 11 | #define VirtHandleNet_UnknownPort -2 12 | #define VirtHandleNet_Error -3 13 | 14 | #define VirtNet_SubnetMask 0xFFFFFF00 15 | #define VirtNet_HostName "FO2-VirtClient" 16 | 17 | #define oCall_recvFrom 0 18 | #define oCall_sendTo 1 19 | #define oCall_overlapped 2 20 | #define oCall_wbind 3 21 | #define oCall_wclosesock 4 22 | #define oCall_wWaitMult 5 23 | #define oCall_getAdaptInfo 6 24 | #define oCall_resetEvent 7 25 | #define oCall_hostName 8 26 | #define oCall_hostByName 9 27 | #define oCall_Count 10 28 | 29 | #define VirtualBuffer_MaxQueue 64 30 | 31 | #define VirtualLog_MutexName L"Local\\FO2_SS_VirtNetLock" 32 | #define VirtualLog_FileName L"netLog.txt" 33 | 34 | #define FO2Packet_MagicOffset 10 35 | #define FO2Packet_Magic "FO14" 36 | 37 | struct VirtReceiveBuffer { 38 | char buffer[2048]; 39 | WSABUF wsaBuf; 40 | sockaddr_in recvFrom; 41 | int recvFromLen = sizeof(sockaddr_in); 42 | DWORD numBytesRecvd; 43 | }; 44 | 45 | struct VirtIPHeader { 46 | in_addr virtIP; 47 | u_short virtPort; 48 | }; 49 | 50 | struct SocketState { 51 | SOCKET s; 52 | u_short port; 53 | BOOL isReceiving = FALSE; 54 | DWORD virtRecvState = VirtRecv_StateNone; 55 | WSAEVENT overlappedRecvEvent; 56 | LPWSABUF lpRecvBuffers; 57 | LPWSAOVERLAPPED lpRecvOverlapped; 58 | LPDWORD lpNumberOfBytesRecvd; 59 | sockaddr* lpRecvFrom; 60 | LPINT lpRecvFromlen; 61 | std::queue virtRecvBuffers; 62 | char sendBuffer[2048]; 63 | VirtReceiveBuffer physRecvBuffer; 64 | WSABUF virtSendBuffers; 65 | WSAOVERLAPPED virtOverlapped; 66 | }; 67 | 68 | class VirtualIP 69 | { 70 | public: 71 | VirtualIP(BOOL isServer, PVOID* oCalls, int callCount); 72 | ~VirtualIP(); 73 | void SetClientVirtualPort(u_short portH); 74 | virtual BOOL Init(); 75 | virtual int RegisterSocket(SOCKET s, const sockaddr* addr); 76 | virtual int ReleaseSocket(SOCKET s) = 0; 77 | virtual int DSendTo(SOCKET s, LPWSABUF lpBuffers, DWORD dwBufferCount, LPDWORD lpNumberOfBytesSent, 78 | DWORD dwFlags, const struct sockaddr *lpTo, int iToLen, LPWSAOVERLAPPED lpOverlapped) = 0; 79 | virtual int DRecvFrom(SOCKET s, LPWSABUF lpBuffers, DWORD dwBufferCount, LPDWORD lpNumberOfBytesRecvd, 80 | LPDWORD lpFlags, struct sockaddr *lpFrom, LPINT lpFromlen, LPWSAOVERLAPPED lpOverlapped) = 0; 81 | virtual BOOL DGetOverlappedResult(SOCKET s, LPWSAOVERLAPPED lpOverlapped, LPDWORD lpcbTransfer, BOOL fWait, LPDWORD lpdwFlags) = 0; 82 | virtual DWORD DWaitForEvents(DWORD cEvents, const WSAEVENT *lphEvents, BOOL fWaitAll, DWORD dwTimeout, BOOL fAlertable); 83 | virtual DWORD DGetAdaptInfo(PIP_ADAPTER_INFO pAdapterInfo, PULONG pOutBufLen); 84 | virtual BOOL DWSAResetEvent(WSAEVENT hEvent) = 0; 85 | virtual int GetHostName(char* name, int namelen); 86 | virtual struct hostent* FAR GetHostByName(const char* name); 87 | protected: 88 | static const char* tag; 89 | protected: 90 | virtual int HandleVirtNetwork(SocketState** pprecvSock); 91 | protected: 92 | in_addr GetClientVirtAddr(int clientID); 93 | in_addr GetClientVirtAddr(); 94 | int GetClientID(short netOrdPort); 95 | int GetClientID(in_addr virtAddr); 96 | int GetClientID(); 97 | u_short GetClientPhysHPort(int clientID); 98 | int UpdateVirtualSocket(SOCKET s, DWORD cEvents, const WSAEVENT* lphEvents); 99 | void WritePacketInfoToLog(const char* tags, sockaddr_in from, sockaddr_in to, int len); 100 | bool CheckPacketIntegrity(char* buf, int buflen); 101 | private: 102 | int PrepareEvent(SocketState* recvSocket, DWORD cEvents, const WSAEVENT * lphEvents); 103 | bool IsValidClientID(int clientID); 104 | protected: 105 | RecvFrom m_recvFrom; 106 | SendTo m_sendTo; 107 | OverlappedResult m_overlapped; 108 | sBind m_bind; 109 | sClose m_closeSock; 110 | WSAWaitMultiple m_waitMult; 111 | GetAdaptInfo m_getAdaptInfo; 112 | WSAEReset m_reset; 113 | hostName m_hostName; 114 | hostbyname m_hostByName; 115 | BOOL m_isServer; 116 | in_addr m_localAddr; 117 | std::unordered_map m_socketStates; 118 | SET_NetSettings m_settings; 119 | in_addr m_addressRange; 120 | SOCKET m_virtSocket = SOCKET_ERROR; 121 | SocketState m_virtSockState; 122 | std::unordered_map m_ignoredEvents; 123 | private: 124 | std::list m_internalSocketState; 125 | InstanceSettings* m_sharedSettings; 126 | }; 127 | 128 | -------------------------------------------------------------------------------- /FlatOut2/InstanceSettings.cpp: -------------------------------------------------------------------------------- 1 | #include "stdafx.h" 2 | #include "InstanceSettings.h" 3 | 4 | InstanceSettings* InstanceSettings::m_instanceSettings = NULL; 5 | 6 | InstanceSettings::~InstanceSettings() 7 | { 8 | if (m_settings != NULL) 9 | { 10 | UnmapViewOfFile(m_settings); 11 | } 12 | 13 | if (m_sharedMem != NULL) 14 | { 15 | CloseHandle(m_sharedMem); 16 | } 17 | } 18 | 19 | InstanceSettings* InstanceSettings::InitSettings(int instanceID) 20 | { 21 | if (m_instanceSettings != NULL) 22 | { 23 | throw std::exception("Tried to initialize instance settings more than once!"); 24 | } 25 | m_instanceSettings = new InstanceSettings(); 26 | m_instanceSettings->m_sharedMem = OpenFileMapping( 27 | FILE_MAP_ALL_ACCESS, 28 | FALSE, 29 | INSTANCESETTINGS_MEMNAME); 30 | if (m_instanceSettings->m_sharedMem == NULL) 31 | { 32 | int err = GetLastError(); 33 | CloseHandle(m_instanceSettings->m_sharedMem); 34 | throw new std::exception("Could not open settings shared memory!", err); 35 | } 36 | m_instanceSettings->m_settings = (SET_SharedSettings*)MapViewOfFile(m_instanceSettings->m_sharedMem, 37 | FILE_MAP_ALL_ACCESS, 38 | 0, 39 | 0, 40 | sizeof(SET_SharedSettings)); 41 | if (m_instanceSettings->m_settings == NULL) 42 | { 43 | int err = GetLastError(); 44 | throw new std::exception("Could not map settings shared memory!", err); 45 | } 46 | m_instanceSettings->m_instanceID = instanceID; 47 | SET_InstanceSettings* instSet = &m_instanceSettings->m_settings->instances[instanceID]; 48 | instSet->instanceID = instanceID; 49 | instSet->procID = GetCurrentProcessId(); 50 | instSet->instanceController = m_instanceSettings->m_settings->globals.controller[instanceID]; 51 | instSet->windowPos = m_instanceSettings->m_settings->globals.windowPos[instanceID]; 52 | if (m_instanceSettings->m_instanceID == INSTANCESETTINGS_HOSTINST) 53 | { 54 | HANDLE wait = OpenEvent(EVENT_ALL_ACCESS, false, INSTANCESETTINGS_WAITNAME); 55 | if (NULL == wait) 56 | { 57 | std::ostringstream msg; 58 | msg << "Could not open launcher wait handle! Error : " << GetLastError(); 59 | Logging::getInstance().error("SETTINGS", msg.str()); 60 | } 61 | 62 | if (!SetEvent(wait)) 63 | { 64 | std::ostringstream msg; 65 | msg << "Could not set launcher wait event! Error : " << GetLastError(); 66 | Logging::getInstance().error("SETTINGS", msg.str()); 67 | } 68 | 69 | CloseHandle(wait); 70 | } 71 | return m_instanceSettings; 72 | } 73 | 74 | InstanceSettings * InstanceSettings::GetSettings() 75 | { 76 | return m_instanceSettings; 77 | } 78 | 79 | SET_InstanceSettings InstanceSettings::GetLocalSettings() 80 | { 81 | return m_instanceSettings->m_settings->instances[m_instanceID]; 82 | } 83 | 84 | SET_NetSettings InstanceSettings::GetNetworkSettings() 85 | { 86 | return m_settings->globals.network; 87 | } 88 | 89 | BOOL InstanceSettings::IsHostInstance() 90 | { 91 | return (INSTANCESETTINGS_HOSTINST == m_instanceID); 92 | } 93 | 94 | int InstanceSettings::GetInstanceCount() 95 | { 96 | return m_settings->globals.instanceCount; 97 | } 98 | 99 | int InstanceSettings::GetInstanceID() 100 | { 101 | return m_instanceID; 102 | } 103 | 104 | int InstanceSettings::GetInstanceID(u_short clientPortH) 105 | { 106 | if (clientPortH == 0) 107 | { 108 | return -1; 109 | } 110 | 111 | for (int i = 0; i < sizeof(m_settings->virtClientPorts); i++) 112 | { 113 | if (m_settings->virtClientPorts[i] == clientPortH) 114 | { 115 | return i; 116 | } 117 | } 118 | 119 | return -1; 120 | } 121 | 122 | void InstanceSettings::SetInstanceVirtPort(u_short clientPortH) 123 | { 124 | m_settings->virtClientPorts[m_instanceID] = clientPortH; 125 | } 126 | 127 | u_short InstanceSettings::GetInstanceVirtPort(int instanceID) 128 | { 129 | if (instanceID == INSTANCESETTINGS_HOSTINST) 130 | { 131 | return m_settings->globals.network.virtHostPort; 132 | } 133 | else if (FO2_MaxClientCount <= instanceID) 134 | { 135 | throw std::exception("Argument out of Range"); 136 | } 137 | return m_settings->virtClientPorts[instanceID]; 138 | } 139 | 140 | void InstanceSettings::SetGameWindowHandle(HWND window) 141 | { 142 | m_instanceSettings->m_settings->instances[m_instanceID].mainHwnd = window; 143 | } 144 | 145 | void InstanceSettings::SetDirect3DHook(CDirect3D9Hook * d3d) 146 | { 147 | m_instanceSettings->m_settings->instances[m_instanceID].d3d = d3d; 148 | } 149 | 150 | void InstanceSettings::SetDirectInputHook(CDirectInput8Hook * dinput) 151 | { 152 | m_instanceSettings->m_settings->instances[m_instanceID].directInput = dinput; 153 | } 154 | 155 | BOOL InstanceSettings::UseInputEmulation() 156 | { 157 | return m_settings->globals.useInputEmulation; 158 | } 159 | 160 | BOOL InstanceSettings::SkipIntros() 161 | { 162 | return m_settings->globals.skipIntros; 163 | } 164 | 165 | InstanceSettings::InstanceSettings() 166 | { 167 | } 168 | -------------------------------------------------------------------------------- /docs/virtual_net.md: -------------------------------------------------------------------------------- 1 | # Local virtual component 2 | 3 | This chapters requires some familiarity with the [Winsock API](https://docs.microsoft.com/en-us/windows/win32/winsock/getting-started-with-winsock) and its asynchronous variant using [Overlapped IO](https://docs.microsoft.com/en-us/windows/win32/winsock/overlapped-i-o-and-event-objects-2), as well as API hooking/detouring in general. Additionally understanding of general networking (IP-addresses, ports, UDP) is needed. 4 | 5 | ## Overview 6 | 7 | Allowing players to run multiple instances of the game on one machine requires resolving network port conflicts. These conflicts are caused by the two hard coded ports used by FlatOut2. One Port is used to discovery open lobbies in the network using broadcasts. The other port is then used for the actual game messages. 8 | 9 | While these ports can be changed in the game's `options.cfg` file, lobby discovery is only possible if the ports are identical and therefore conflicting. To resolve this port conflict, the mod includes a virtual network component. This component allows each instance of the game to receive its own unique IP address and transparently manages network traffic between the instances. The functionality is somewhat similar to a VPN, but is handled entirely in software and requires no true virtual network devices. 10 | 11 | While the mod was built for FlatOut 2, the networking component doesn't rely on any game specific implementation details and could therefore be used in other games that use UDP networking through the Winsock API. 12 | 13 | ## Implementation 14 | 15 | ### Source files 16 | 17 | Code for the virtual network component can be found in the base class `VirtualIP`, the specialized classes `VirtualHost` and `VirtualClient` for the host instance and other clients respectively. Hooks for the Winsock API are found in `WinSockDetours.cpp`. 18 | 19 | ### Explanation 20 | 21 | The virtual network component is initialized by requesting a UDP socket from the OS with automatic port assignment enabled. The randomly assigned port number is stored in the shared memory accessible by all game instances. Now when the player enters the LAN menu the game requests its regular UDP sockets from the OS using the "hard coded" port numbers. These calls are intercepted by the mod and instead register a virtual socket. To register the socket, the mod creates a struct with all the information required to service the virtual socket such as buffers, IO events and internal state. This struct is stored in a dictionary using the socket descriptor as an index. This allows retrieval of the internal socket struct since all future network IO calls will use this to socket descriptor as a reference. 22 | 23 | ![Virtual Network Graph](images/virtual_network.png) 24 | When the game sends a UDP packet a header is added to the beginning which contains the virtual address and port of the target instance. This modified packet is the sent to the target on the physical port created by the mod. On the receiving side the virtual information is then restored from the header and by looking up the source client id through the sender's port number. The game's regular network handlers are then signaled and receive the packet like if it was sent from a real host. 25 | 26 | When accessing the game's LAN mode, chronologically the game will search for available LAN games by sending a discovery request on the broadcast address (255.255.255.255). Currently, to simplify the implementation, client's broadcast messages are only sent to the instance with the ID of zero which will be referred to as the host instance. If a game is available on the host instance, it will answer the discovery request with information about the available session. This information is sent to the broadcast address as well and in contrast to the clients implementation, the message are distributed to all clients like a physical broadcast. Due to this limitation only the host instance is able to host LAN games. 27 | 28 | ## Limitations and potential improvements 29 | 30 | ### Reliance on host instance 31 | 32 | Only the instance with ID 0 can publish broadcast messages to all other instances. While broadcasts requires individually sending separate packets to each clients port, the overhead caused by this should be negligible. This would allow any instance to host LAN games. 33 | 34 | ### Virtual headers reduce the max packet size 35 | 36 | Since a header is appended to each packet, the maximum packet size usable by the game is limited. Sending such a packet is currently undefined behavior that might crash the mod. During testing with 8 game instances no packet was sent that was large enough to be affected by this limitation. 37 | 38 | ### Allow communications between multiple physical hosts 39 | 40 | Currently the virtual network is restricted to one physical computer. However since regular networking operations are used to achieve the virtualization, it would be possible to route packet between physical hosts as well. These hosts could be in the same LAN or even only via regular internet. However the routing would require distribution of the virtual network configuration through the network instead of relying on shared memory. 41 | -------------------------------------------------------------------------------- /FlatOut2/FlatOut2.cpp: -------------------------------------------------------------------------------- 1 | // FlatOut2.cpp : Defines the exported functions for the DLL application. 2 | // 3 | 4 | #include "stdafx.h" 5 | #include "FlatOut2.h" 6 | #include "InstanceSettings.h" 7 | 8 | HMODULE dllHandle = NULL; 9 | 10 | struct DevicesArray 11 | { 12 | ControllerInfo* devices; 13 | int deviceIndex; 14 | int deviceCount; 15 | }; 16 | 17 | BOOL _stdcall DIEnumDevicesCallback(LPCDIDEVICEINSTANCEW lpddi, LPVOID pvRef) 18 | { 19 | DevicesArray* devs = (DevicesArray*)pvRef; 20 | if (devs->deviceIndex < devs->deviceCount) 21 | { 22 | ControllerInfo* dev = &devs->devices[devs->deviceIndex]; 23 | memcpy_s(dev->deviceName, sizeof(dev->deviceName), lpddi->tszProductName, sizeof(lpddi->tszProductName)); 24 | memcpy_s(dev->instanceName, sizeof(dev->instanceName), lpddi->tszInstanceName, sizeof(lpddi->tszInstanceName)); 25 | dev->guid = lpddi->guidInstance; 26 | } 27 | 28 | devs->deviceIndex++; 29 | return DIENUM_CONTINUE; 30 | } 31 | 32 | FLATOUT2_API int _stdcall CreateNewInstance(LPWSTR cmdLine) 33 | { 34 | std::cout << "Launching FlatOut2 executable.\n"; 35 | SECURITY_ATTRIBUTES saAttr; 36 | saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); 37 | saAttr.bInheritHandle = TRUE; 38 | saAttr.lpSecurityDescriptor = NULL; 39 | 40 | STARTUPINFO suInfo = STARTUPINFO(); 41 | ZeroMemory(&suInfo, sizeof(STARTUPINFO)); 42 | suInfo.cb = sizeof(STARTUPINFO); 43 | suInfo.dwX = CW_USEDEFAULT; 44 | suInfo.dwY = CW_USEDEFAULT; 45 | suInfo.dwXSize = CW_USEDEFAULT; 46 | suInfo.dwYSize = CW_USEDEFAULT; 47 | suInfo.dwFlags |= STARTF_USESTDHANDLES; 48 | 49 | PROCESS_INFORMATION pInf; 50 | if (!CreateProcess( 51 | L"FlatOut2.exe", 52 | cmdLine, 53 | NULL, 54 | NULL, 55 | TRUE, 56 | CREATE_SUSPENDED, 57 | NULL, 58 | NULL, 59 | &suInfo, 60 | &pInf)) 61 | { 62 | std::cout << "Process creation failed. Error :" << GetLastError() << "\n"; 63 | getchar(); 64 | return S_FALSE; 65 | } 66 | 67 | if (FO2_AllowAttach) 68 | { 69 | getchar(); 70 | } 71 | 72 | LPVOID loadLib = (LPVOID)GetProcAddress(GetModuleHandle(L"kernel32.dll"), "LoadLibraryA"); 73 | LPVOID arg = (LPVOID)VirtualAllocEx(pInf.hProcess, NULL, strlen(injectDllName), MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); 74 | if (arg == NULL) 75 | { 76 | std::cout << "Error: the memory could not be allocated inside the chosen process.\n"; 77 | getchar(); 78 | return S_FALSE; 79 | } 80 | 81 | if (WriteProcessMemory(pInf.hProcess, arg, injectDllName, strlen(injectDllName), NULL) == 0) 82 | { 83 | std::cout << "Error: there were no bytes written to the process's address space.\n"; 84 | getchar(); 85 | return S_FALSE; 86 | } 87 | 88 | HANDLE thread = CreateRemoteThread(pInf.hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)loadLib, arg, NULL, NULL); 89 | if (thread == NULL) 90 | { 91 | std::cout << "Create Remote Thread failed. Error : " << GetLastError() << "\n"; 92 | getchar(); 93 | return S_FALSE; 94 | } 95 | 96 | if (ResumeThread(pInf.hThread) == -1) 97 | { 98 | std::cout << "Resume failed. Error : " << GetLastError() << "\n"; 99 | getchar(); 100 | return S_FALSE; 101 | } 102 | 103 | return S_OK; 104 | } 105 | 106 | FLATOUT2_API BOOL _stdcall GetSharedSettingsSize(int* settingsSize, int sizeCount) 107 | { 108 | if (sizeCount != 3) 109 | { 110 | return false; 111 | } 112 | else 113 | { 114 | settingsSize[0] = sizeof(SET_SharedSettings); 115 | settingsSize[1] = sizeof(SET_GlobalSettings); 116 | settingsSize[2] = sizeof(SET_InstanceSettings); 117 | return true; 118 | } 119 | } 120 | 121 | FLATOUT2_API BOOL _stdcall GetControllerGuids(ControllerInfo* devices, LPINT deviceCount) 122 | { 123 | IDirectInput8* di; 124 | HRESULT res = DirectInput8Create(dllHandle, DIRECTINPUT_VERSION, IID_IDirectInput8, (LPVOID*)&di, NULL); 125 | if (res != DI_OK) 126 | { 127 | return FALSE; 128 | } 129 | DevicesArray dev = { devices,0,*deviceCount }; 130 | res = di->EnumDevices(DI8DEVCLASS_GAMECTRL, DIEnumDevicesCallback, &dev, DIEDFL_ALLDEVICES); 131 | if (res != DI_OK) 132 | { 133 | di->Release(); 134 | return FALSE; 135 | } 136 | *deviceCount = dev.deviceIndex; 137 | di->Release(); 138 | return TRUE; 139 | } 140 | 141 | FLATOUT2_API BOOL _stdcall GetResolutions(LPRECT resolutions, LPINT resoCount) 142 | { 143 | IDirect3D9* d3d = Direct3DCreate9(D3D_SDK_VERSION); 144 | if (NULL == d3d) 145 | { 146 | return FALSE; 147 | } 148 | UINT adaptCount = d3d->GetAdapterModeCount(D3DADAPTER_DEFAULT, D3DFMT_X8R8G8B8); 149 | int resoIndex = 0; 150 | for (UINT adaptIndex = 0; adaptIndex < adaptCount; adaptIndex++) 151 | { 152 | D3DDISPLAYMODE mode; 153 | if (d3d->EnumAdapterModes(D3DADAPTER_DEFAULT, D3DFMT_X8R8G8B8, adaptIndex, &mode) != D3D_OK) 154 | { 155 | d3d->Release(); 156 | return FALSE; 157 | } 158 | if ((0 != mode.RefreshRate && 60 != mode.RefreshRate) || D3DFMT_X8R8G8B8 != mode.Format) 159 | { 160 | continue; 161 | } 162 | if (resoIndex < *resoCount) 163 | { 164 | resolutions[resoIndex] = { 0,0,(LONG)mode.Width,(LONG)mode.Height }; 165 | } 166 | resoIndex++; 167 | } 168 | *resoCount = resoIndex; 169 | return TRUE; 170 | } 171 | -------------------------------------------------------------------------------- /Launcher/Launcher.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {4760197A-8A5D-4C93-B5A0-532C03F2257A} 8 | WinExe 9 | Properties 10 | Launcher 11 | Launcher 12 | v4.5.2 13 | 512 14 | true 15 | 16 | 17 | x86 18 | true 19 | full 20 | false 21 | ..\GameDir\ 22 | DEBUG;TRACE 23 | prompt 24 | 4 25 | Off 26 | true 27 | 28 | 29 | x86 30 | pdbonly 31 | true 32 | ..\GameDir\ 33 | TRACE 34 | prompt 35 | 4 36 | true 37 | 38 | 39 | 40 | 41 | ..\packages\Newtonsoft.Json.10.0.3\lib\net45\Newtonsoft.Json.dll 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | False 63 | 64 | 65 | Form 66 | 67 | 68 | Setup.cs 69 | True 70 | 71 | 72 | Form 73 | 74 | 75 | UpdateNotifier.cs 76 | True 77 | 78 | 79 | ResXFileCodeGenerator 80 | Resources.Designer.cs 81 | Designer 82 | 83 | 84 | True 85 | Resources.resx 86 | 87 | 88 | Setup.cs 89 | 90 | 91 | Setup.cs 92 | 93 | 94 | UpdateNotifier.cs 95 | 96 | 97 | 98 | SettingsSingleFileGenerator 99 | Settings.Designer.cs 100 | 101 | 102 | True 103 | Settings.settings 104 | True 105 | 106 | 107 | 108 | 109 | Designer 110 | 111 | 112 | 113 | 114 | 121 | -------------------------------------------------------------------------------- /Launcher/Properties/Resources.resx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | text/microsoft-resx 107 | 108 | 109 | 2.0 110 | 111 | 112 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 113 | 114 | 115 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | -------------------------------------------------------------------------------- /Launcher/UpdateNotifier.Designer.cs: -------------------------------------------------------------------------------- 1 | namespace Launcher 2 | { 3 | partial class UpdateNotifier 4 | { 5 | /// 6 | /// Required designer variable. 7 | /// 8 | private System.ComponentModel.IContainer components = null; 9 | 10 | /// 11 | /// Clean up any resources being used. 12 | /// 13 | /// true if managed resources should be disposed; otherwise, false. 14 | protected override void Dispose(bool disposing) 15 | { 16 | if (disposing && (components != null)) 17 | { 18 | components.Dispose(); 19 | } 20 | base.Dispose(disposing); 21 | } 22 | 23 | #region Windows Form Designer generated code 24 | 25 | /// 26 | /// Required method for Designer support - do not modify 27 | /// the contents of this method with the code editor. 28 | /// 29 | private void InitializeComponent() 30 | { 31 | this.label1 = new System.Windows.Forms.Label(); 32 | this.VersionCompare = new System.Windows.Forms.Label(); 33 | this.Changelog = new System.Windows.Forms.RichTextBox(); 34 | this.label2 = new System.Windows.Forms.Label(); 35 | this.DownloadLink = new System.Windows.Forms.LinkLabel(); 36 | this.OkButton = new System.Windows.Forms.Button(); 37 | this.SuspendLayout(); 38 | // 39 | // label1 40 | // 41 | this.label1.AutoSize = true; 42 | this.label1.Font = new System.Drawing.Font("Microsoft Sans Serif", 15.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); 43 | this.label1.Location = new System.Drawing.Point(27, 9); 44 | this.label1.Name = "label1"; 45 | this.label1.Size = new System.Drawing.Size(231, 25); 46 | this.label1.TabIndex = 0; 47 | this.label1.Text = "New Version available!"; 48 | // 49 | // VersionCompare 50 | // 51 | this.VersionCompare.AutoSize = true; 52 | this.VersionCompare.Location = new System.Drawing.Point(32, 48); 53 | this.VersionCompare.Name = "VersionCompare"; 54 | this.VersionCompare.Size = new System.Drawing.Size(154, 13); 55 | this.VersionCompare.TabIndex = 1; 56 | this.VersionCompare.Text = "Current Version : New Version :"; 57 | // 58 | // Changelog 59 | // 60 | this.Changelog.BackColor = System.Drawing.Color.White; 61 | this.Changelog.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; 62 | this.Changelog.Location = new System.Drawing.Point(32, 89); 63 | this.Changelog.Name = "Changelog"; 64 | this.Changelog.ReadOnly = true; 65 | this.Changelog.Size = new System.Drawing.Size(218, 144); 66 | this.Changelog.TabIndex = 2; 67 | this.Changelog.Text = ""; 68 | // 69 | // label2 70 | // 71 | this.label2.AutoSize = true; 72 | this.label2.Font = new System.Drawing.Font("Microsoft Sans Serif", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); 73 | this.label2.Location = new System.Drawing.Point(32, 70); 74 | this.label2.Name = "label2"; 75 | this.label2.Size = new System.Drawing.Size(80, 16); 76 | this.label2.TabIndex = 3; 77 | this.label2.Text = "Changelog :"; 78 | // 79 | // DownloadLink 80 | // 81 | this.DownloadLink.AutoSize = true; 82 | this.DownloadLink.Location = new System.Drawing.Point(32, 239); 83 | this.DownloadLink.Name = "DownloadLink"; 84 | this.DownloadLink.Size = new System.Drawing.Size(55, 13); 85 | this.DownloadLink.TabIndex = 4; 86 | this.DownloadLink.TabStop = true; 87 | this.DownloadLink.Text = "Download"; 88 | this.DownloadLink.LinkClicked += new System.Windows.Forms.LinkLabelLinkClickedEventHandler(this.DownloadLink_LinkClicked); 89 | // 90 | // OkButton 91 | // 92 | this.OkButton.Location = new System.Drawing.Point(99, 261); 93 | this.OkButton.Name = "OkButton"; 94 | this.OkButton.Size = new System.Drawing.Size(75, 23); 95 | this.OkButton.TabIndex = 5; 96 | this.OkButton.Text = "OK"; 97 | this.OkButton.UseVisualStyleBackColor = true; 98 | this.OkButton.Click += new System.EventHandler(this.OkButton_Click); 99 | // 100 | // UpdateNotifier 101 | // 102 | this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); 103 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; 104 | this.ClientSize = new System.Drawing.Size(284, 296); 105 | this.Controls.Add(this.OkButton); 106 | this.Controls.Add(this.DownloadLink); 107 | this.Controls.Add(this.label2); 108 | this.Controls.Add(this.Changelog); 109 | this.Controls.Add(this.VersionCompare); 110 | this.Controls.Add(this.label1); 111 | this.Name = "UpdateNotifier"; 112 | this.Text = "New Version Available!"; 113 | this.ResumeLayout(false); 114 | this.PerformLayout(); 115 | 116 | } 117 | 118 | #endregion 119 | 120 | private System.Windows.Forms.Label label1; 121 | private System.Windows.Forms.Label VersionCompare; 122 | private System.Windows.Forms.RichTextBox Changelog; 123 | private System.Windows.Forms.Label label2; 124 | private System.Windows.Forms.LinkLabel DownloadLink; 125 | private System.Windows.Forms.Button OkButton; 126 | } 127 | } -------------------------------------------------------------------------------- /FlatOut2/FlatOut2.vcxproj.filters: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF} 6 | cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx 7 | 8 | 9 | {93995380-89BD-4b04-88EB-625FBE52EBFB} 10 | h;hh;hpp;hxx;hm;inl;inc;xsd 11 | 12 | 13 | {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} 14 | rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms 15 | 16 | 17 | {b608554c-b341-41ae-adc2-19faab1877f7} 18 | 19 | 20 | {97d03ab0-0c28-4dc8-aaed-26fe85e57998} 21 | 22 | 23 | {6c6b75f7-83d3-49e7-bb73-ede6ad5a64b0} 24 | 25 | 26 | {dd381751-7251-4661-8640-033bda5fe8de} 27 | 28 | 29 | 30 | 31 | Header Files 32 | 33 | 34 | Header Files 35 | 36 | 37 | Header Files 38 | 39 | 40 | Header Files 41 | 42 | 43 | Header Files 44 | 45 | 46 | Header Files 47 | 48 | 49 | Header Files\InterfaceHooks 50 | 51 | 52 | Header Files\InterfaceHooks 53 | 54 | 55 | Header Files\InterfaceHooks 56 | 57 | 58 | Header Files\InterfaceHooks 59 | 60 | 61 | Header Files 62 | 63 | 64 | Header Files 65 | 66 | 67 | Header Files\Detours 68 | 69 | 70 | Header Files\Detours 71 | 72 | 73 | Header Files\Detours 74 | 75 | 76 | Header Files\Detours 77 | 78 | 79 | Header Files\Detours 80 | 81 | 82 | Header Files\Detours 83 | 84 | 85 | Header Files 86 | 87 | 88 | Header Files 89 | 90 | 91 | 92 | 93 | Source Files 94 | 95 | 96 | Source Files 97 | 98 | 99 | Source Files 100 | 101 | 102 | Source Files 103 | 104 | 105 | Source Files 106 | 107 | 108 | Source Files 109 | 110 | 111 | Source Files\InterfaceHooks 112 | 113 | 114 | Source Files\InterfaceHooks 115 | 116 | 117 | Source Files\InterfaceHooks 118 | 119 | 120 | Source Files\InterfaceHooks 121 | 122 | 123 | Source Files 124 | 125 | 126 | Source Files 127 | 128 | 129 | Source Files\Detours 130 | 131 | 132 | Source Files\Detours 133 | 134 | 135 | Source Files\Detours 136 | 137 | 138 | Source Files\Detours 139 | 140 | 141 | Source Files\Detours 142 | 143 | 144 | Source Files 145 | 146 | 147 | 148 | 149 | Resource Files 150 | 151 | 152 | -------------------------------------------------------------------------------- /Launcher/UpdateNotifier.resx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | -------------------------------------------------------------------------------- /FlatOut2/WinSockDetours.cpp: -------------------------------------------------------------------------------- 1 | #include "stdafx.h" 2 | #include "WinSockDetours.h" 3 | 4 | VirtualIP *virtIP = NULL; 5 | PVOID oBind; 6 | PVOID oRecvFrom; 7 | PVOID oSendTo; 8 | PVOID oOverlappedRes; 9 | PVOID oWSAStart; 10 | PVOID oWSAClean; 11 | PVOID oCloseSocket; 12 | PVOID oWaitMult; 13 | PVOID oWSAEReset; 14 | PVOID oGetAdapterInfo; 15 | PVOID oGetHostName; 16 | PVOID oGetHostByName; 17 | LARGE_INTEGER countFreq; 18 | 19 | int WINAPI MyBind(SOCKET s, const struct sockaddr *name, int namelen) 20 | { 21 | return virtIP->RegisterSocket(s, name); 22 | } 23 | 24 | int WINAPI MyWSARecvFrom(SOCKET s, LPWSABUF lpBuffers, DWORD dwBufferCount, LPDWORD lpNumberOfBytesRecvd, 25 | LPDWORD lpFlags, struct sockaddr *lpFrom, LPINT lpFromlen, LPWSAOVERLAPPED lpOverlapped, LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine) 26 | { 27 | return virtIP->DRecvFrom(s, lpBuffers, dwBufferCount, lpNumberOfBytesRecvd, lpFlags, lpFrom, lpFromlen, lpOverlapped); 28 | } 29 | 30 | int WINAPI MyWSASendTo(SOCKET s, LPWSABUF lpBuffers, DWORD dwBufferCount, LPDWORD lpNumberOfBytesSent, 31 | DWORD dwFlags, const struct sockaddr *lpTo, int iToLen, LPWSAOVERLAPPED lpOverlapped, LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine) 32 | { 33 | return virtIP->DSendTo(s, lpBuffers, dwBufferCount, lpNumberOfBytesSent, dwFlags, lpTo, iToLen, lpOverlapped); 34 | } 35 | 36 | BOOL WSAAPI MyWSAGetOverlappedResult(SOCKET s, LPWSAOVERLAPPED lpOverlapped, LPDWORD lpcbTransfer, BOOL fWait, LPDWORD lpdwFlags) 37 | { 38 | return virtIP->DGetOverlappedResult(s, lpOverlapped, lpcbTransfer, fWait, lpdwFlags); 39 | } 40 | 41 | int WSAAPI MyCloseSocket(SOCKET s) 42 | { 43 | return virtIP->ReleaseSocket(s); 44 | } 45 | 46 | DWORD WINAPI MyGetAdaptInfo(PIP_ADAPTER_INFO pAdapterInfo, PULONG pOutBufLen) 47 | { 48 | return virtIP->DGetAdaptInfo(pAdapterInfo, pOutBufLen); 49 | } 50 | 51 | DWORD WSAAPI MyWaitForMulti(DWORD cEvents, const WSAEVENT *lphEvents, BOOL fWaitAll, DWORD dwTimeout, BOOL fAlertable) 52 | { 53 | LARGE_INTEGER before; 54 | LARGE_INTEGER after; 55 | QueryPerformanceCounter(&before); 56 | DWORD r = virtIP->DWaitForEvents(cEvents, lphEvents, fWaitAll, dwTimeout, fAlertable); 57 | QueryPerformanceCounter(&after); 58 | if (((double)(after.QuadPart - before.QuadPart) / countFreq.QuadPart) > dwTimeout && dwTimeout > 0) 59 | { 60 | std::ostringstream msg; 61 | msg << "WaitForMultipleEvents took longer than dwTimeout : " 62 | << std::setprecision(2) << ((double)(after.QuadPart - before.QuadPart) / countFreq.QuadPart) * 1000 63 | << " ms"; 64 | Logging::getInstance().error("NETWORK", msg.str()); 65 | } 66 | return r; 67 | } 68 | 69 | BOOL WSAAPI MyResetEvent(WSAEVENT hEvent) 70 | { 71 | return virtIP->DWSAResetEvent(hEvent); 72 | } 73 | 74 | int WSAAPI MyCleanup() 75 | { 76 | Logging::getInstance().error("NETWORK", std::string("WinSock clean up")); 77 | return ((WSAClean)oWSAClean)(); 78 | } 79 | 80 | int WSAAPI MyWSAStart(WORD wVersionRequested, LPWSADATA lpWSAData) 81 | { 82 | int r = ((WSAStart)oWSAStart)(wVersionRequested, lpWSAData); 83 | if (virtIP) { 84 | Logging::getInstance().warn("NETWORK", "Game called WinSock WSAStartup more than once."); 85 | return r; 86 | } 87 | 88 | if (r == 0) 89 | { 90 | #if oCall_Count != 10 91 | #error Call defs not updated 92 | #endif 93 | PVOID calls[10]; 94 | calls[oCall_recvFrom] = oRecvFrom; 95 | calls[oCall_sendTo] = oSendTo; 96 | calls[oCall_overlapped] = oOverlappedRes; 97 | calls[oCall_wbind] = oBind; 98 | calls[oCall_wclosesock] = oCloseSocket; 99 | calls[oCall_wWaitMult] = oWaitMult; 100 | calls[oCall_getAdaptInfo] = oGetAdapterInfo; 101 | calls[oCall_resetEvent] = oWSAEReset; 102 | calls[oCall_hostName] = oGetHostName; 103 | calls[oCall_hostByName] = oGetHostByName; 104 | if (InstanceSettings::GetSettings()->IsHostInstance()) 105 | { 106 | VirtualHost* vHost = new VirtualHost(InstanceSettings::GetSettings()->GetInstanceCount(), calls, 8); 107 | if (!vHost->Init()) 108 | { 109 | Logging::getInstance().error("NETWORK", std::string("Virtual network host initialization failure!")); 110 | } 111 | virtIP = (VirtualIP*)vHost; 112 | } 113 | else 114 | { 115 | VirtualClient* virtCl = new VirtualClient(calls, 8); 116 | virtIP = (VirtualIP*)virtCl; 117 | if (!virtCl->Init()) 118 | { 119 | Logging::getInstance().error("NETWORK", std::string("Virtual network client initialization failure!")); 120 | } 121 | } 122 | } 123 | return r; 124 | } 125 | 126 | int WSAAPI myGethostname(char* name, int namelen) 127 | { 128 | return virtIP->GetHostName(name, namelen); 129 | } 130 | 131 | struct hostent* FAR WSAAPI myGethostbyname(const char* name) 132 | { 133 | return virtIP->GetHostByName(name); 134 | } 135 | 136 | void WinSockDetour() 137 | { 138 | QueryPerformanceFrequency(&countFreq); 139 | 140 | HMODULE wsock2 = LoadLibrary(L"Ws2_32.dll"); 141 | HMODULE Iphlpapi = LoadLibrary(L"Iphlpapi.dll"); 142 | 143 | oBind = GetProcAddress(wsock2, "bind"); 144 | oSendTo = GetProcAddress(wsock2, "WSASendTo"); 145 | oRecvFrom = GetProcAddress(wsock2, "WSARecvFrom"); 146 | oOverlappedRes = GetProcAddress(wsock2, "WSAGetOverlappedResult"); 147 | oCloseSocket = GetProcAddress(wsock2, "closesocket"); 148 | oWSAClean = GetProcAddress(wsock2, "WSACleanup"); 149 | oWaitMult = GetProcAddress(wsock2, "WSAWaitForMultipleEvents"); 150 | oWSAStart = GetProcAddress(wsock2, "WSAStartup"); 151 | oWSAEReset = GetProcAddress(wsock2, "WSAResetEvent"); 152 | oGetHostName = GetProcAddress(wsock2, "gethostname"); 153 | oGetHostByName = GetProcAddress(wsock2, "gethostbyname"); 154 | oGetAdapterInfo = GetProcAddress(Iphlpapi, "GetAdaptersInfo"); 155 | 156 | DetourAttach(&oBind, MyBind); 157 | DetourAttach(&oSendTo, MyWSASendTo); 158 | DetourAttach(&oRecvFrom, MyWSARecvFrom); 159 | DetourAttach(&oOverlappedRes, MyWSAGetOverlappedResult); 160 | DetourAttach(&oCloseSocket, MyCloseSocket); 161 | DetourAttach(&oWSAClean, MyCleanup); 162 | DetourAttach(&oWaitMult, MyWaitForMulti); 163 | DetourAttach(&oWSAStart, MyWSAStart); 164 | DetourAttach(&oGetHostName, myGethostname); 165 | DetourAttach(&oGetHostByName, myGethostbyname); 166 | DetourAttach(&oGetAdapterInfo, MyGetAdaptInfo); 167 | DetourAttach(&oWSAEReset, MyResetEvent); 168 | } -------------------------------------------------------------------------------- /Launcher/Setup.en.resx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | 121 | FlatOut2 Splitscreen Launcher 122 | 123 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ### Game directory link 2 | */GameDir 3 | /GameDir 4 | 5 | ## Ignore Visual Studio temporary files, build results, and 6 | ## files generated by popular Visual Studio add-ons. 7 | ## 8 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 9 | 10 | # User-specific files 11 | *.suo 12 | *.user 13 | *.userosscache 14 | *.sln.docstates 15 | 16 | # User-specific files (MonoDevelop/Xamarin Studio) 17 | *.userprefs 18 | 19 | # Build results 20 | [Dd]ebug/ 21 | [Dd]ebugPublic/ 22 | [Rr]elease/ 23 | [Rr]eleases/ 24 | x64/ 25 | x86/ 26 | bld/ 27 | [Bb]in/ 28 | [Oo]bj/ 29 | [Ll]og/ 30 | 31 | # Visual Studio 2015 cache/options directory 32 | .vs/ 33 | # Uncomment if you have tasks that create the project's static files in wwwroot 34 | #wwwroot/ 35 | 36 | # MSTest test Results 37 | [Tt]est[Rr]esult*/ 38 | [Bb]uild[Ll]og.* 39 | 40 | # NUNIT 41 | *.VisualState.xml 42 | TestResult.xml 43 | 44 | # Build Results of an ATL Project 45 | [Dd]ebugPS/ 46 | [Rr]eleasePS/ 47 | dlldata.c 48 | 49 | # Benchmark Results 50 | BenchmarkDotNet.Artifacts/ 51 | 52 | # .NET Core 53 | project.lock.json 54 | project.fragment.lock.json 55 | artifacts/ 56 | **/Properties/launchSettings.json 57 | 58 | *_i.c 59 | *_p.c 60 | *_i.h 61 | *.ilk 62 | *.meta 63 | *.obj 64 | *.pch 65 | *.pdb 66 | *.pgc 67 | *.pgd 68 | *.rsp 69 | *.sbr 70 | *.tlb 71 | *.tli 72 | *.tlh 73 | *.tmp 74 | *.tmp_proj 75 | *.log 76 | *.vspscc 77 | *.vssscc 78 | .builds 79 | *.pidb 80 | *.svclog 81 | *.scc 82 | 83 | # Chutzpah Test files 84 | _Chutzpah* 85 | 86 | # Visual C++ cache files 87 | ipch/ 88 | *.aps 89 | *.ncb 90 | *.opendb 91 | *.opensdf 92 | *.sdf 93 | *.cachefile 94 | *.VC.db 95 | *.VC.VC.opendb 96 | 97 | # Visual Studio profiler 98 | *.psess 99 | *.vsp 100 | *.vspx 101 | *.sap 102 | 103 | # TFS 2012 Local Workspace 104 | $tf/ 105 | 106 | # Guidance Automation Toolkit 107 | *.gpState 108 | 109 | # ReSharper is a .NET coding add-in 110 | _ReSharper*/ 111 | *.[Rr]e[Ss]harper 112 | *.DotSettings.user 113 | 114 | # JustCode is a .NET coding add-in 115 | .JustCode 116 | 117 | # TeamCity is a build add-in 118 | _TeamCity* 119 | 120 | # DotCover is a Code Coverage Tool 121 | *.dotCover 122 | 123 | # Visual Studio code coverage results 124 | *.coverage 125 | *.coveragexml 126 | 127 | # NCrunch 128 | _NCrunch_* 129 | .*crunch*.local.xml 130 | nCrunchTemp_* 131 | 132 | # MightyMoose 133 | *.mm.* 134 | AutoTest.Net/ 135 | 136 | # Web workbench (sass) 137 | .sass-cache/ 138 | 139 | # Installshield output folder 140 | [Ee]xpress/ 141 | 142 | # DocProject is a documentation generator add-in 143 | DocProject/buildhelp/ 144 | DocProject/Help/*.HxT 145 | DocProject/Help/*.HxC 146 | DocProject/Help/*.hhc 147 | DocProject/Help/*.hhk 148 | DocProject/Help/*.hhp 149 | DocProject/Help/Html2 150 | DocProject/Help/html 151 | 152 | # Click-Once directory 153 | publish/ 154 | 155 | # Publish Web Output 156 | *.[Pp]ublish.xml 157 | *.azurePubxml 158 | # TODO: Comment the next line if you want to checkin your web deploy settings 159 | # but database connection strings (with potential passwords) will be unencrypted 160 | *.pubxml 161 | *.publishproj 162 | 163 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 164 | # checkin your Azure Web App publish settings, but sensitive information contained 165 | # in these scripts will be unencrypted 166 | PublishScripts/ 167 | 168 | # NuGet Packages 169 | *.nupkg 170 | # The packages folder can be ignored because of Package Restore 171 | **/packages/* 172 | # except build/, which is used as an MSBuild target. 173 | !**/packages/build/ 174 | # Uncomment if necessary however generally it will be regenerated when needed 175 | #!**/packages/repositories.config 176 | # NuGet v3's project.json files produces more ignorable files 177 | *.nuget.props 178 | *.nuget.targets 179 | 180 | # Microsoft Azure Build Output 181 | csx/ 182 | *.build.csdef 183 | 184 | # Microsoft Azure Emulator 185 | ecf/ 186 | rcf/ 187 | 188 | # Windows Store app package directories and files 189 | AppPackages/ 190 | BundleArtifacts/ 191 | Package.StoreAssociation.xml 192 | _pkginfo.txt 193 | *.appx 194 | 195 | # Visual Studio cache files 196 | # files ending in .cache can be ignored 197 | *.[Cc]ache 198 | # but keep track of directories ending in .cache 199 | !*.[Cc]ache/ 200 | 201 | # Others 202 | ClientBin/ 203 | ~$* 204 | *~ 205 | *.dbmdl 206 | *.dbproj.schemaview 207 | *.jfm 208 | *.pfx 209 | *.publishsettings 210 | orleans.codegen.cs 211 | 212 | # Since there are multiple workflows, uncomment next line to ignore bower_components 213 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 214 | #bower_components/ 215 | 216 | # RIA/Silverlight projects 217 | Generated_Code/ 218 | 219 | # Backup & report files from converting an old project file 220 | # to a newer Visual Studio version. Backup files are not needed, 221 | # because we have git ;-) 222 | _UpgradeReport_Files/ 223 | Backup*/ 224 | UpgradeLog*.XML 225 | UpgradeLog*.htm 226 | 227 | # SQL Server files 228 | *.mdf 229 | *.ldf 230 | *.ndf 231 | 232 | # Business Intelligence projects 233 | *.rdl.data 234 | *.bim.layout 235 | *.bim_*.settings 236 | 237 | # Microsoft Fakes 238 | FakesAssemblies/ 239 | 240 | # GhostDoc plugin setting file 241 | *.GhostDoc.xml 242 | 243 | # Node.js Tools for Visual Studio 244 | .ntvs_analysis.dat 245 | node_modules/ 246 | 247 | # Typescript v1 declaration files 248 | typings/ 249 | 250 | # Visual Studio 6 build log 251 | *.plg 252 | 253 | # Visual Studio 6 workspace options file 254 | *.opt 255 | 256 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 257 | *.vbw 258 | 259 | # Visual Studio LightSwitch build output 260 | **/*.HTMLClient/GeneratedArtifacts 261 | **/*.DesktopClient/GeneratedArtifacts 262 | **/*.DesktopClient/ModelManifest.xml 263 | **/*.Server/GeneratedArtifacts 264 | **/*.Server/ModelManifest.xml 265 | _Pvt_Extensions 266 | 267 | # Paket dependency manager 268 | .paket/paket.exe 269 | paket-files/ 270 | 271 | # FAKE - F# Make 272 | .fake/ 273 | 274 | # JetBrains Rider 275 | .idea/ 276 | *.sln.iml 277 | 278 | # CodeRush 279 | .cr/ 280 | 281 | # Python Tools for Visual Studio (PTVS) 282 | __pycache__/ 283 | *.pyc 284 | 285 | # Cake - Uncomment if you are using it 286 | # tools/** 287 | # !tools/packages.config 288 | 289 | # Tabs Studio 290 | *.tss 291 | 292 | # Telerik's JustMock configuration file 293 | *.jmconfig 294 | 295 | # BizTalk build output 296 | *.btp.cs 297 | *.btm.cs 298 | *.odx.cs 299 | *.xsd.cs 300 | -------------------------------------------------------------------------------- /Launcher/FlatOut2.cs: -------------------------------------------------------------------------------- 1 | namespace Launcher 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Runtime.InteropServices; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | using static Settings; 9 | 10 | /// 11 | /// A class for managing game launches and querying system configuration. Provides a managed interface for methods of the FlatOut.dll. 12 | /// 13 | public class FlatOut2 : IDisposable 14 | { 15 | /// 16 | /// The maximum amount of instances that can run at once. 17 | /// 18 | public const int MaxInstanceCount = 8; 19 | private const int SharedSettingsSizeCount = 3; 20 | private const int DeviceInfoLength = 512; 21 | 22 | private Settings settings; 23 | 24 | private FlatOut2(Settings settings) 25 | { 26 | if (settings == null) 27 | { 28 | throw new ArgumentNullException(); 29 | } 30 | else if (settings.InstanceCount < 1 || settings.InstanceCount > MaxInstanceCount) 31 | { 32 | throw new ArgumentOutOfRangeException(); 33 | } 34 | 35 | this.settings = settings; 36 | } 37 | 38 | /// 39 | /// Asynchronously starts a new split-screen session with the specified settings. 40 | /// 41 | /// The settings used to setup the session. 42 | /// Returns a task that represents the session startup process. 43 | public static Task CreateSplitScreenSession(Settings settings) 44 | { 45 | FlatOut2 fo2 = new FlatOut2(settings); 46 | return Task.Factory.StartNew(() => { StartSplitScreenSession(fo2); }); 47 | } 48 | 49 | /// 50 | /// Asynchronously retrieves a list of connected controllers. 51 | /// 52 | /// Returns a task representing the querying operation. 53 | public static Task> GetGamePads() 54 | { 55 | return Task.Factory.StartNew(() => 56 | { 57 | List pads = new List(); 58 | InitGamePadList(pads); 59 | return pads; 60 | }); 61 | } 62 | 63 | /// 64 | /// Asynchronously retrieves a list of available resolution modes. 65 | /// 66 | /// Returns a task representing the querying operation 67 | public static Task> GetResolutions() 68 | { 69 | return Task.Factory.StartNew(() => 70 | { 71 | List resos = new List(); 72 | InitResolutionsList(resos); 73 | return resos; 74 | }); 75 | } 76 | 77 | [DllImport("FlatOut2.dll", EntryPoint = "CreateNewInstance", ExactSpelling = false, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Unicode)] 78 | private static extern int CreateNewInstance([MarshalAs(UnmanagedType.LPWStr)] string cmdLine); 79 | 80 | [DllImport("FlatOut2.dll", EntryPoint = "GetControllerGuids", ExactSpelling = false, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Unicode)] 81 | private static extern bool GetControllerGuids([Out, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)] ControllerInfo[] controllers, ref int deviceCount); 82 | 83 | [DllImport("FlatOut2.dll", EntryPoint = "GetResolutions", ExactSpelling = false, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Unicode)] 84 | private static extern bool GetResolutions([Out, MarshalAs(UnmanagedType.LPArray)] RECT[] resolutions, ref int resoCount); 85 | 86 | private static void StartSplitScreenSession(FlatOut2 fo2) 87 | { 88 | using (fo2) 89 | { 90 | fo2.StartGame(); 91 | } 92 | } 93 | 94 | private static void InitResolutionsList(List resolutions) 95 | { 96 | if (resolutions == null) 97 | { 98 | throw new ArgumentNullException(); 99 | } 100 | 101 | int resoCount = 20; 102 | int oldCount; 103 | RECT[] resos; 104 | while (true) 105 | { 106 | resos = new RECT[resoCount]; 107 | oldCount = resoCount; 108 | if (!GetResolutions(resos, ref resoCount)) 109 | { 110 | throw new Exception(); 111 | } 112 | else if (oldCount >= resoCount) 113 | { 114 | break; 115 | } 116 | 117 | oldCount = resoCount; 118 | } 119 | 120 | resolutions.Clear(); 121 | foreach (var reso in resos) 122 | { 123 | if (reso.Width != 0 && reso.Height != 0) 124 | { 125 | resolutions.Add(reso); 126 | } 127 | } 128 | } 129 | 130 | private static void InitGamePadList(List gamePads) 131 | { 132 | if (gamePads == null) 133 | { 134 | throw new ArgumentNullException(); 135 | } 136 | 137 | int deviceCount = 10; 138 | int oldCount; 139 | gamePads.Clear(); 140 | ControllerInfo[] deviceBuffer; 141 | while (true) 142 | { 143 | oldCount = deviceCount; 144 | deviceBuffer = new ControllerInfo[deviceCount]; 145 | if (!GetControllerGuids(deviceBuffer, ref deviceCount)) 146 | { 147 | throw new Exception(); 148 | } 149 | else if (oldCount >= deviceCount) 150 | { 151 | break; 152 | } 153 | } 154 | 155 | for (int i = 0; i < deviceCount; i++) 156 | { 157 | var device = deviceBuffer[i]; 158 | gamePads.Add(new GamePad(device.Guid, new string(device.DeviceName), new string(device.InstanceName))); 159 | } 160 | } 161 | 162 | private void StartGame() 163 | { 164 | settings.SetupGameSettings(); 165 | for (int i = 0; i < settings.InstanceCount; i++) 166 | { 167 | CreateNewInstance($"-i {i}"); 168 | } 169 | 170 | settings.WaitForHostInstance(); 171 | } 172 | 173 | public void Dispose() 174 | { 175 | ((IDisposable)settings).Dispose(); 176 | } 177 | 178 | [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] 179 | private struct ControllerInfo 180 | { 181 | [MarshalAs(UnmanagedType.ByValArray, SizeConst = 512)] 182 | public char[] DeviceName; 183 | [MarshalAs(UnmanagedType.ByValArray, SizeConst = 512)] 184 | public char[] InstanceName; 185 | public Guid Guid; 186 | } 187 | } 188 | } 189 | -------------------------------------------------------------------------------- /FlatOut2/User32Detours.cpp: -------------------------------------------------------------------------------- 1 | #include "stdafx.h" 2 | #include "User32Detours.h" 3 | #include "TestScriptRunner.h" 4 | #include "WinSockDetours.h" 5 | #include "GameDetours.h" 6 | 7 | PVOID oCreateWinExA; 8 | PVOID oPeekMessageA; 9 | PVOID oSetWinHookExA; 10 | PVOID oCallNextHookEx; 11 | PVOID kbHook; 12 | 13 | LPWSTR controllerGuid; 14 | 15 | TestScriptRunner* script = NULL; 16 | BOOL runScript = FALSE; 17 | BOOL markKeyDown = FALSE; 18 | 19 | BOOL isInjectedHook = FALSE; 20 | BOOL xboxRJoystickXCentered = TRUE; 21 | BOOL xboxRJoystickYCentered = TRUE; 22 | BOOL disableJoyInput = FALSE; 23 | 24 | std::list lastEmulatedPresses; 25 | 26 | void EmulateKeyPress(DWORD key) 27 | { 28 | isInjectedHook = TRUE; 29 | lastEmulatedPresses.push_back(key); 30 | LPARAM lparam = 0x00000001 | (LPARAM)(MapVirtualKey(key, MAPVK_VK_TO_VSC) << 16); 31 | ((KBHookProc)kbHook)(HC_ACTION, key, lparam); 32 | } 33 | 34 | void EmulateKeyRelease(DWORD key) 35 | { 36 | /*isInjectedHook = TRUE; 37 | LPARAM lparam = 0x00000002 | (LPARAM)(MapVirtualKey(key, MAPVK_VK_TO_VSC) << 16); 38 | ((KBHookProc)kbHook)(HC_ACTION, key, lparam);*/ 39 | } 40 | 41 | void setupScript() 42 | { 43 | script = new TestScriptRunner(L"testScript.txt", EmulateKeyPress); 44 | runScript = TRUE; 45 | } 46 | 47 | BOOL WINAPI MyPeekMessage(LPMSG lpMsg, HWND hWnd, UINT wMsgFilterMin, UINT wMsgFilterMax, UINT wRemoveMsg) 48 | { 49 | SET_InstanceSettings instSet = InstanceSettings::GetSettings()->GetLocalSettings(); 50 | BOOL ret = ((PeekMessageT)oPeekMessageA)(lpMsg, hWnd, wMsgFilterMin, wMsgFilterMax, wRemoveMsg); 51 | if (ret && WM_KEYDOWN == lpMsg->message && VK_F2 == lpMsg->wParam) 52 | { 53 | markKeyDown = TRUE; 54 | Logging::getInstance().info("SCRIPTING", std::string("MARK")); 55 | } 56 | else 57 | { 58 | markKeyDown = FALSE; 59 | } 60 | 61 | if (InstanceSettings::GetSettings()->UseInputEmulation()) 62 | { 63 | if (!ret) 64 | { 65 | if (!lastEmulatedPresses.empty()) 66 | { 67 | for (std::list::iterator key = lastEmulatedPresses.begin(); key != lastEmulatedPresses.end(); ++key) 68 | { 69 | EmulateKeyRelease(*key); 70 | } 71 | 72 | lastEmulatedPresses.clear(); 73 | } 74 | 75 | if (runScript) 76 | { 77 | DWORD r = script->RunOnce(); 78 | switch (r) 79 | { 80 | case SR_SUCCESS: 81 | case SR_WAIT: 82 | break; 83 | case SR_EOF: 84 | runScript = false; 85 | break; 86 | case SR_ERROR: 87 | default: 88 | std::ostringstream msg; 89 | msg << "Unknown script error : " << r; 90 | Logging::getInstance().error("SCRIPTING", msg.str()); 91 | runScript = false; 92 | break; 93 | } 94 | } 95 | 96 | if (instSet.directInput != NULL) 97 | { 98 | CDIDevice8Hook* dev = instSet.directInput->GetDevice(); 99 | if (dev != NULL) 100 | { 101 | if (dev->IsButtonPress(XBOXBT_BACK)) 102 | { 103 | disableJoyInput = !disableJoyInput; 104 | } 105 | 106 | if (!disableJoyInput) 107 | { 108 | if (dev->IsButtonPress(XBOXBT_A)) 109 | { 110 | EmulateKeyPress(VK_RETURN); 111 | } 112 | else if (dev->IsButtonPress(XBOXBT_B)) 113 | { 114 | EmulateKeyPress(VK_ESCAPE); 115 | } 116 | int upDown, rightLeft; 117 | dev->GetStickDirection(&upDown, &rightLeft); 118 | if (rightLeft == 1 && xboxRJoystickXCentered) 119 | { 120 | EmulateKeyPress(VK_RIGHT); 121 | xboxRJoystickXCentered = false; 122 | } 123 | else if (rightLeft == -1 && xboxRJoystickXCentered) 124 | { 125 | EmulateKeyPress(VK_LEFT); 126 | xboxRJoystickXCentered = false; 127 | } 128 | else if (rightLeft == 0) 129 | { 130 | xboxRJoystickXCentered = true; 131 | } 132 | 133 | if (upDown == 1 && xboxRJoystickYCentered) 134 | { 135 | EmulateKeyPress(VK_DOWN); 136 | xboxRJoystickYCentered = false; 137 | } 138 | else if (upDown == -1 && xboxRJoystickYCentered) 139 | { 140 | EmulateKeyPress(VK_UP); 141 | xboxRJoystickYCentered = false; 142 | } 143 | else if (upDown == 0) 144 | { 145 | xboxRJoystickYCentered = true; 146 | } 147 | } 148 | } 149 | } 150 | } 151 | } 152 | 153 | return ret; 154 | } 155 | 156 | LRESULT WINAPI MyCallNextHook(HHOOK hhk, int code, WPARAM wParam, LPARAM lParam) 157 | { 158 | if (isInjectedHook) 159 | { 160 | isInjectedHook = FALSE; 161 | return S_OK; 162 | } 163 | else 164 | { 165 | return ((CallNextHook)oCallNextHookEx)(hhk, code, wParam, lParam); 166 | } 167 | } 168 | 169 | HHOOK WINAPI MySetWindowsHookExA(int idHook, HOOKPROC lpfn, HINSTANCE hMod, DWORD dwThreadId) 170 | { 171 | DWORD currentT = GetCurrentThreadId(); 172 | kbHook = lpfn; 173 | HHOOK ret = ((SetWinHookExA)oSetWinHookExA)(idHook, lpfn, hMod, dwThreadId); 174 | return ret; 175 | } 176 | 177 | HWND WINAPI MyCreateWindowExA( 178 | DWORD dwExStyle, 179 | LPCSTR lpClassName, 180 | LPCSTR lpWindowName, 181 | DWORD dwStyle, 182 | int x, 183 | int y, 184 | int nWidth, 185 | int nHeight, 186 | HWND hWndParent, 187 | HMENU hMenu, 188 | HINSTANCE hInstance, 189 | LPVOID lpParam) 190 | { 191 | SET_InstanceSettings instSet = InstanceSettings::GetSettings()->GetLocalSettings(); 192 | if (FO2_AllowAttach2) 193 | { 194 | Logging::getInstance().debug("DEBUGGER", std::string("Waiting for 5 seconds.")); 195 | Sleep(5000); 196 | } 197 | 198 | if (NULL != script && InstanceSettings::GetSettings()->IsHostInstance()) 199 | { 200 | script->SetSetting(std::string("HOST"), 1); 201 | } 202 | HWND handle = ((CreateWinExA)oCreateWinExA)( 203 | dwExStyle, 204 | lpClassName, 205 | lpWindowName, 206 | dwStyle, 207 | instSet.windowPos.left, 208 | instSet.windowPos.top, 209 | instSet.windowPos.right, 210 | instSet.windowPos.bottom, 211 | hWndParent, 212 | hMenu, 213 | hInstance, 214 | lpParam); 215 | if (strcmp(lpClassName, "BDX9 Render Window") == 0) 216 | { 217 | InstanceSettings::GetSettings()->SetGameWindowHandle(handle); 218 | } 219 | 220 | return handle; 221 | } 222 | 223 | void User32Detour() 224 | { 225 | SET_InstanceSettings instSet = InstanceSettings::GetSettings()->GetLocalSettings(); 226 | if (InstanceSettings::GetSettings()->GetConsoleVerbosity() > Logging::Off) 227 | { 228 | AllocConsole(); 229 | AttachConsole(GetCurrentProcessId()); 230 | FILE* con; 231 | freopen_s(&con, "CON", "w", stdout); 232 | std::ostringstream msg; 233 | msg << "Attached console for instance : " << InstanceSettings::GetSettings()->GetInstanceID() << " process id : " << GetCurrentProcessId(); 234 | Logging::getInstance().debug("DEBUGGER", msg.str()); 235 | } 236 | HMODULE user32DLL = LoadLibrary(L"User32.dll"); 237 | oCreateWinExA = GetProcAddress(user32DLL, "CreateWindowExA"); 238 | oPeekMessageA = GetProcAddress(user32DLL, "PeekMessageA"); 239 | oSetWinHookExA = GetProcAddress(user32DLL, "SetWindowsHookExA"); 240 | oCallNextHookEx = GetProcAddress(user32DLL, "CallNextHookEx"); 241 | DetourAttach(&oCreateWinExA, MyCreateWindowExA); 242 | DetourAttach(&oPeekMessageA, MyPeekMessage); 243 | DetourAttach(&oSetWinHookExA, MySetWindowsHookExA); 244 | DetourAttach(&oCallNextHookEx, MyCallNextHook); 245 | #ifdef _DEBUG 246 | setupScript(); 247 | #endif // DEBUG 248 | } 249 | -------------------------------------------------------------------------------- /Detours.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Release 10 | Win32 11 | 12 | 13 | Debug 14 | x64 15 | 16 | 17 | Release 18 | x64 19 | 20 | 21 | 22 | 16.0 23 | {AF2527E9-D3B1-4B89-9E3E-6F8C00593E0C} 24 | Detours 25 | Win32Proj 26 | 10.0 27 | 28 | 29 | 30 | StaticLibrary 31 | true 32 | v143 33 | MultiByte 34 | 35 | 36 | StaticLibrary 37 | false 38 | v143 39 | true 40 | MultiByte 41 | 42 | 43 | StaticLibrary 44 | true 45 | v143 46 | MultiByte 47 | 48 | 49 | StaticLibrary 50 | false 51 | v143 52 | true 53 | MultiByte 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | $(SolutionDir)\DetoursLib\$(Configuration)\ 75 | $(SolutionDir)\DetoursLib\$(Configuration)\ 76 | 77 | 78 | $(SolutionDir)\DetoursLib\$(Configuration)\ 79 | $(SolutionDir)\DetoursLib\$(Configuration)\ 80 | 81 | 82 | 83 | Level3 84 | Disabled 85 | WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions) 86 | true 87 | true 88 | 89 | 90 | Windows 91 | true 92 | 93 | 94 | 95 | 96 | Level3 97 | Disabled 98 | _DEBUG;_LIB;%(PreprocessorDefinitions) 99 | true 100 | true 101 | 102 | 103 | Windows 104 | true 105 | 106 | 107 | 108 | 109 | Level3 110 | MaxSpeed 111 | WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) 112 | true 113 | true 114 | true 115 | true 116 | 117 | 118 | Windows 119 | true 120 | true 121 | true 122 | 123 | 124 | 125 | 126 | Level3 127 | MaxSpeed 128 | NDEBUG;_LIB;%(PreprocessorDefinitions) 129 | true 130 | true 131 | true 132 | true 133 | 134 | 135 | Windows 136 | true 137 | true 138 | true 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | true 148 | 149 | 150 | 151 | 152 | 153 | -------------------------------------------------------------------------------- /Launcher/AutoUpdate.cs: -------------------------------------------------------------------------------- 1 | namespace Launcher 2 | { 3 | using Newtonsoft.Json; 4 | using System; 5 | using System.Diagnostics; 6 | using System.IO; 7 | using System.Linq; 8 | using System.Net.Http; 9 | using System.Reflection; 10 | using System.Threading.Tasks; 11 | 12 | public static class AutoUpdate 13 | { 14 | private const string versionURI = "https://fo2ssversion.s3.eu-central-1.amazonaws.com/version.json"; 15 | private const string verCacheFile = "FO2SS_CachedVersion.json"; 16 | private const string flatOutDll = "FlatOut2.dll"; 17 | private const int netRetryCount = 3; 18 | private static TimeSpan netRetryWait = new TimeSpan(0, 0, 0, 15, 0); // 15 seconds 19 | 20 | public static event VersionUpdateEvent NewVersion; 21 | public static event VersionUpdateEvent NotifyNewVersion; 22 | 23 | public static void CheckVersion() 24 | { 25 | try 26 | { 27 | Task.Run(() => AsyncCheckVersion()); 28 | } 29 | catch (Exception e) 30 | { 31 | Console.WriteLine("Failed to check version {0}.", e); 32 | } 33 | } 34 | 35 | public static void SetNotifyUpdate(bool notify) 36 | { 37 | var ver = GetVersionCache(); 38 | WriteVersionCache(notify, Version.Parse(ver.Version), ver.ValideUntil); 39 | } 40 | 41 | private static async void AsyncCheckVersion(int retry = 0) 42 | { 43 | var http = new HttpClient 44 | { 45 | BaseAddress = new Uri(versionURI) 46 | }; 47 | 48 | var json = JsonSerializer.Create(); 49 | var cachedVersion = GetCachedVersion(out var notify); 50 | var req = string.Empty; 51 | try 52 | { 53 | req = await http.GetStringAsync(string.Empty); 54 | } 55 | catch (HttpRequestException e) 56 | { 57 | Console.WriteLine("[Updater] Failed to get current version. Caused by"); 58 | Console.WriteLine(e.ToString()); 59 | if (retry < netRetryCount) 60 | { 61 | AsyncCheckVersion(retry + 1); 62 | return; 63 | } 64 | } 65 | 66 | VersionUpdate update = new VersionUpdate(); 67 | try 68 | { 69 | using (JsonTextReader jsonReader = new JsonTextReader(new StringReader(req))) 70 | { 71 | update = json.Deserialize(jsonReader); 72 | } 73 | } 74 | catch (Exception) 75 | { 76 | Console.WriteLine("Failed to read current version info."); 77 | return; 78 | } 79 | 80 | 81 | var currentVer = Version.Parse(update.Version); 82 | var cacheValidTo = DateTime.UtcNow + update.CacheDuration; 83 | if (currentVer == null || cachedVersion >= currentVer) 84 | { 85 | currentVer = cachedVersion; 86 | } 87 | 88 | if (currentVer > LauncherVersion) 89 | { 90 | var args = new VersionUpdateEventArgs(update); 91 | NewVersion?.Invoke(null, args); 92 | 93 | if (notify) 94 | { 95 | NotifyNewVersion?.Invoke(null, args); 96 | notify = false; 97 | } 98 | } 99 | 100 | WriteVersionCache(notify, currentVer, cacheValidTo); 101 | } 102 | 103 | private static void WriteVersionCache(bool notify, Version currentVer, DateTime cacheValidTo) 104 | { 105 | var json = JsonSerializer.Create(); 106 | using (var updatedCache = new StringWriter()) 107 | { 108 | json.Serialize(updatedCache, new VersionCache 109 | { 110 | CheckedOn = DateTime.UtcNow, 111 | ValideUntil = cacheValidTo, 112 | Version = currentVer.ToString(), 113 | Notify = notify 114 | }); 115 | File.WriteAllText(CacheFilePath, updatedCache.ToString()); 116 | } 117 | } 118 | 119 | private static VersionCache GetVersionCache() 120 | { 121 | var json = JsonSerializer.Create(); 122 | using (var jsonReader = new JsonTextReader(File.OpenText(CacheFilePath))) 123 | { 124 | return json.Deserialize(jsonReader); 125 | } 126 | } 127 | 128 | private static Version GetCachedVersion(out bool notify) 129 | { 130 | Version cachedVersion = null; 131 | notify = true; 132 | try 133 | { 134 | if (File.Exists(CacheFilePath)) 135 | { 136 | var verCache = GetVersionCache(); 137 | if(!Version.TryParse(verCache.Version, out cachedVersion)) 138 | { 139 | return null; 140 | } 141 | 142 | notify = verCache.Notify; 143 | 144 | } 145 | } 146 | catch (IOException e) 147 | { 148 | Console.WriteLine("[Updater] Failed to read version cache. Caused by"); 149 | Console.WriteLine(e.ToString()); 150 | } 151 | 152 | return cachedVersion; 153 | } 154 | 155 | public static Version FlatOutDLLVersion 156 | { 157 | get 158 | { 159 | FileVersionInfo dllVersion = null; 160 | try 161 | { 162 | dllVersion = FileVersionInfo.GetVersionInfo(Path.Combine(Directory.GetCurrentDirectory(), flatOutDll)); 163 | // GetVersionInfo doesn't throw if the file is not found, but file name does 164 | var a = dllVersion.FileName; 165 | } 166 | catch (ArgumentException) 167 | { 168 | return new Version(); 169 | } 170 | return Version.Parse(dllVersion.FileVersion ?? "0.0"); 171 | } 172 | } 173 | 174 | public static Version LauncherVersion => Assembly.GetCallingAssembly().GetName().Version; 175 | 176 | private static string CacheFilePath => Path.Combine(Path.GetTempPath(), verCacheFile); 177 | 178 | [JsonObject] 179 | public struct VersionUpdate 180 | { 181 | [JsonProperty] 182 | public string Version { get; set; } 183 | [JsonProperty] 184 | public DateTime ReleaseDate { get; set; } 185 | [JsonProperty] 186 | public TimeSpan CacheDuration { get; set; } 187 | [JsonProperty] 188 | public string[] ChangeLog { get; set; } 189 | } 190 | 191 | public class VersionUpdateEventArgs : EventArgs 192 | { 193 | public VersionUpdate Version { get; private set; } 194 | public VersionUpdateEventArgs(VersionUpdate version) 195 | { 196 | Version = version; 197 | } 198 | } 199 | 200 | public delegate void VersionUpdateEvent(object sender, VersionUpdateEventArgs e); 201 | 202 | [JsonObject] 203 | private struct VersionCache 204 | { 205 | [JsonProperty] 206 | public string Version { get; set; } 207 | [JsonProperty] 208 | public DateTime ValideUntil { get; set; } 209 | [JsonProperty] 210 | public DateTime CheckedOn { get; set; } 211 | [JsonProperty] 212 | public bool Notify { get; set; } 213 | } 214 | } 215 | } 216 | -------------------------------------------------------------------------------- /FlatOut2/VirtualClient.cpp: -------------------------------------------------------------------------------- 1 | #include "stdafx.h" 2 | #include "VirtualClient.h" 3 | 4 | VirtualClient::VirtualClient(PVOID* oCalls, int callCount) 5 | : VirtualIP(FALSE, oCalls, callCount) 6 | { 7 | m_localAddr = GetClientVirtAddr(); 8 | } 9 | 10 | VirtualClient::~VirtualClient() 11 | { 12 | } 13 | 14 | BOOL VirtualClient::Init() 15 | { 16 | if (!VirtualIP::Init()) 17 | return FALSE; 18 | sockaddr_in client; 19 | client.sin_family = AF_INET; 20 | client.sin_port = 0; 21 | client.sin_addr = in4addr_loopback; 22 | if (m_bind(m_virtSocket, (SOCKADDR *)&client, sizeof(client)) == SOCKET_ERROR) 23 | { 24 | std::ostringstream msg; 25 | msg << "Virtual network client socket bind error : " << WSAGetLastError(); 26 | Logging::getInstance().error(tag, msg.str()); 27 | return FALSE; 28 | } 29 | 30 | int namelen = sizeof(client); 31 | if (getsockname(m_virtSocket, (SOCKADDR*)&client, &namelen) == SOCKET_ERROR) 32 | { 33 | std::ostringstream msg; 34 | msg << "Virtual network client socket address retrieval error : " << WSAGetLastError(); 35 | Logging::getInstance().error(tag, msg.str()); 36 | return FALSE; 37 | } 38 | 39 | SetClientVirtualPort(ntohs(client.sin_port)); 40 | return TRUE; 41 | } 42 | 43 | int VirtualClient::RegisterSocket(SOCKET s, const sockaddr* addr) 44 | { 45 | // Flush virtual buffer queue 46 | char buffer[2048]; 47 | WSABUF buf = { 2048,buffer }; 48 | DWORD numBytesRecvd; 49 | DWORD flags = 0; 50 | sockaddr_in from; 51 | int fromLen = sizeof(sockaddr_in); 52 | int r = 0; 53 | WSAOVERLAPPED over; 54 | over.hEvent = m_virtSockState.overlappedRecvEvent; 55 | while (true) 56 | { 57 | r = m_recvFrom(m_virtSocket, &buf, 1, &numBytesRecvd, &flags, (sockaddr*)&from, &fromLen, &over, NULL); 58 | if (0 == r) 59 | { 60 | DWORD trans, flags; 61 | if (!m_overlapped(m_virtSocket, &over, &trans, FALSE, &flags)) 62 | { 63 | std::ostringstream msg; 64 | msg << "Unexpected error while flushing virtual socket (GetOverlappedResult)! Error : " << WSAGetLastError(); 65 | Logging::getInstance().error(tag, msg.str()); 66 | break; 67 | } 68 | else 69 | { 70 | Logging::getInstance().debug(tag, std::string("Flushed virtual packet")); 71 | continue; 72 | } 73 | } 74 | else if (r == SOCKET_ERROR && WSA_IO_PENDING == WSAGetLastError()) 75 | { 76 | DWORD wait = m_waitMult(1, &m_virtSockState.overlappedRecvEvent, false, 1, FALSE); 77 | if (wait == WSA_WAIT_TIMEOUT) 78 | { 79 | if (!CancelIo((HANDLE)m_virtSocket)) 80 | { 81 | std::ostringstream msg; 82 | msg << "Could not cancel peek receive! Error : " << GetLastError(); 83 | Logging::getInstance().error(tag, msg.str()); 84 | } 85 | 86 | break; 87 | } 88 | else if (wait == WSA_WAIT_EVENT_0) 89 | { 90 | DWORD trans, flags; 91 | if (!m_overlapped(m_virtSocket, &over, &trans, FALSE, &flags)) 92 | { 93 | std::ostringstream msg; 94 | msg << "Unexpected error while flushing virtual socket (GetOverlappedResult)! Error : " << WSAGetLastError(); 95 | Logging::getInstance().error(tag, msg.str()); 96 | break; 97 | } 98 | else 99 | { 100 | Logging::getInstance().debug(tag, std::string("Flushed virtual packet")); 101 | continue; 102 | } 103 | } 104 | else 105 | { 106 | std::ostringstream msg; 107 | msg << "Unexpected error while flushing virtual socket (WaitForMultipleEvents)! Error : " << WSAGetLastError(); 108 | Logging::getInstance().error(tag, msg.str()); 109 | break; 110 | } 111 | } 112 | else 113 | { 114 | std::ostringstream msg; 115 | msg << "Unexpected error while flushing virtual socket (RecvFrom)! Error : " << WSAGetLastError(); 116 | Logging::getInstance().error(tag, msg.str()); 117 | break; 118 | } 119 | } 120 | 121 | VirtualIP::RegisterSocket(s, addr); 122 | m_socketStates[s]->s = s; 123 | m_socketStates[s]->virtRecvState = VirtRecv_StateNone; 124 | m_socketStates[s]->port = ((sockaddr_in*)addr)->sin_port; 125 | 126 | return 0; 127 | } 128 | 129 | int VirtualClient::ReleaseSocket(SOCKET s) 130 | { 131 | return VirtualIP::ReleaseSocket(s);; 132 | } 133 | 134 | int VirtualClient::HandleVirtNetwork(SocketState** pprecvSock) 135 | { 136 | int ret = VirtualIP::HandleVirtNetwork(pprecvSock); 137 | if (ret == VirtHandleNet_UnknownPort) 138 | { 139 | Logging::getInstance().error(tag, std::string("Virtual server sending to unknown port!")); 140 | } 141 | 142 | return ret; 143 | } 144 | 145 | int VirtualClient::DSendTo(SOCKET s, LPWSABUF lpBuffers, DWORD dwBufferCount, LPDWORD lpNumberOfBytesSent, DWORD dwFlags, const sockaddr * lpTo, int iToLen, LPWSAOVERLAPPED lpOverlapped) 146 | { 147 | SocketState* state = m_socketStates[s]; 148 | VirtIPHeader header; 149 | if ((((sockaddr_in*)lpTo)->sin_addr.s_addr & 0x00FFFFFF) != 0x0050a8c0 && INADDR_BROADCAST != ((sockaddr_in*)lpTo)->sin_addr.s_addr) 150 | { 151 | CHAR addr[16]; 152 | InetNtopA(AF_INET, &((sockaddr_in*)lpTo)->sin_addr, addr, sizeof(addr)); 153 | std::ostringstream msg; 154 | msg << "Sending to unknown address:" << addr; 155 | Logging::getInstance().error(tag, msg.str()); 156 | } 157 | 158 | header.virtIP = ((sockaddr_in*)lpTo)->sin_addr; 159 | header.virtPort = ((sockaddr_in*)lpTo)->sin_port; 160 | memcpy_s(state->sendBuffer, sizeof(state->sendBuffer), &header, sizeof(header)); 161 | memcpy_s(state->sendBuffer + sizeof(header), sizeof(state->sendBuffer) - sizeof(header), lpBuffers->buf, lpBuffers->len); 162 | DWORD numBytesSend = 0; 163 | state->virtSendBuffers.buf = state->sendBuffer; 164 | state->virtSendBuffers.len = sizeof(header) + lpBuffers->len; 165 | int toClientID; 166 | if (INADDR_BROADCAST == ((sockaddr_in*)lpTo)->sin_addr.s_addr) 167 | { 168 | toClientID = INSTANCESETTINGS_HOSTINST; 169 | } 170 | else 171 | { 172 | toClientID = GetClientID(header.virtIP = ((sockaddr_in*)lpTo)->sin_addr); 173 | } 174 | 175 | sockaddr_in toPhysAddr; 176 | toPhysAddr.sin_family = AF_INET; 177 | toPhysAddr.sin_addr = in4addr_loopback; 178 | toPhysAddr.sin_port = htons(GetClientPhysHPort(toClientID)); 179 | WritePacketInfoToLog("Send,Virt,Imme", sockaddr_in{ AF_INET,state->port,m_localAddr }, *((sockaddr_in*)lpTo), lpBuffers->len); 180 | int r = m_sendTo(m_virtSocket, &state->virtSendBuffers, 1, &numBytesSend, dwFlags, (sockaddr*)&toPhysAddr, sizeof(sockaddr_in), lpOverlapped, NULL); 181 | int err = WSAGetLastError(); 182 | if (r) 183 | { 184 | std::ostringstream msg; 185 | msg << "Sending packet failed with " << r << " and WSALastError " << WSAGetLastError(); 186 | Logging::getInstance().error(tag, msg.str()); 187 | } 188 | return r; 189 | } 190 | 191 | int VirtualClient::DRecvFrom(SOCKET s, LPWSABUF lpBuffers, DWORD dwBufferCount, LPDWORD lpNumberOfBytesRecvd, LPDWORD lpFlags, sockaddr * lpFrom, LPINT lpFromlen, LPWSAOVERLAPPED lpOverlapped) 192 | { 193 | SocketState* state = m_socketStates[s]; 194 | state->lpNumberOfBytesRecvd = lpNumberOfBytesRecvd; 195 | state->lpRecvBuffers = lpBuffers; 196 | state->overlappedRecvEvent = lpOverlapped->hEvent; 197 | state->lpRecvOverlapped = lpOverlapped; 198 | state->lpRecvFrom = lpFrom; 199 | state->lpRecvFromlen = lpFromlen; 200 | WSASetLastError(WSA_IO_PENDING); 201 | return SOCKET_ERROR; 202 | } 203 | 204 | BOOL VirtualClient::DGetOverlappedResult(SOCKET s, LPWSAOVERLAPPED lpOverlapped, LPDWORD lpcbTransfer, BOOL fWait, LPDWORD lpdwFlags) 205 | { 206 | SocketState* state = m_socketStates[s]; 207 | BOOLEAN ret; 208 | if (VirtRecv_StateVirtOverlapped == state->virtRecvState) 209 | { 210 | state->virtRecvState = VirtRecv_StateNone; 211 | VirtReceiveBuffer buf = state->virtRecvBuffers.front(); 212 | state->virtRecvBuffers.pop(); 213 | VirtIPHeader virtHead = *((VirtIPHeader*)buf.buffer); 214 | 215 | // Check if virtual client send broadcast 216 | if (in4addr_broadcast.s_addr == virtHead.virtIP.s_addr) 217 | { 218 | ((sockaddr_in*)state->lpRecvFrom)->sin_addr = in4addr_any; 219 | } 220 | else 221 | { 222 | int id = GetClientID(buf.recvFrom.sin_port); 223 | ((sockaddr_in*)state->lpRecvFrom)->sin_addr = GetClientVirtAddr(id); 224 | } 225 | 226 | ((sockaddr_in*)state->lpRecvFrom)->sin_port = virtHead.virtPort; 227 | ((sockaddr_in*)state->lpRecvFrom)->sin_family = AF_INET; 228 | memcpy_s(state->lpRecvBuffers->buf, state->lpRecvBuffers->len, 229 | buf.buffer + sizeof(VirtIPHeader), sizeof(buf.buffer) - sizeof(VirtIPHeader)); 230 | *lpcbTransfer = buf.numBytesRecvd - sizeof(VirtIPHeader); 231 | CheckPacketIntegrity(state->lpRecvBuffers->buf, *lpcbTransfer); 232 | ret = TRUE; 233 | } 234 | else if (VirtRecv_StateVirtBuffered >= state->virtRecvState) 235 | { 236 | Logging::getInstance().error(tag, std::string("Unexpected virtual receive state!")); 237 | ret = FALSE; 238 | } 239 | else 240 | { 241 | Logging::getInstance().error(tag, std::string("Unexpected virtual receive state! Virtual client can not receive physical packets.")); 242 | ret = FALSE; 243 | } 244 | 245 | WritePacketInfoToLog("Recv,Virt,Asyn", *((sockaddr_in*)state->lpRecvFrom), sockaddr_in{ AF_INET,state->port,m_localAddr }, *lpcbTransfer); 246 | return ret; 247 | } 248 | 249 | DWORD VirtualClient::DWaitForEvents(DWORD cEvents, const WSAEVENT * lphEvents, BOOL fWaitAll, DWORD dwTimeout, BOOL fAlertable) 250 | { 251 | return VirtualIP::DWaitForEvents(cEvents, lphEvents, fWaitAll, dwTimeout, fAlertable); 252 | } 253 | 254 | BOOL VirtualClient::DWSAResetEvent(WSAEVENT hEvent) 255 | { 256 | return m_reset(hEvent); 257 | } 258 | -------------------------------------------------------------------------------- /FlatOut2/FlatOut2.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Release 10 | Win32 11 | 12 | 13 | Debug 14 | x64 15 | 16 | 17 | Release 18 | x64 19 | 20 | 21 | 22 | 15.0 23 | {4AE789A1-99C7-4D40-BB22-096ED992398C} 24 | Win32Proj 25 | FlatOut2 26 | 10.0 27 | 28 | 29 | 30 | DynamicLibrary 31 | true 32 | v143 33 | Unicode 34 | 35 | 36 | DynamicLibrary 37 | false 38 | v143 39 | true 40 | Unicode 41 | 42 | 43 | DynamicLibrary 44 | true 45 | v143 46 | Unicode 47 | 48 | 49 | DynamicLibrary 50 | false 51 | v143 52 | true 53 | Unicode 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | true 75 | $(SolutionDir)Detours\src;$(IncludePath) 76 | $(SolutionDir)\DetoursLib\$(Configuration)\;$(LibraryPath) 77 | ..\GameDir\ 78 | 79 | 80 | true 81 | 82 | 83 | false 84 | $(SolutionDir)Detours\src;$(IncludePath) 85 | $(SolutionDir)\DetoursLib\$(Configuration)\;$(LibraryPath) 86 | ..\GameDir\ 87 | 88 | 89 | false 90 | 91 | 92 | 93 | Use 94 | Level3 95 | Disabled 96 | true 97 | WIN32;_DEBUG;FLATOUT2_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) 98 | true 99 | 100 | 101 | Windows 102 | true 103 | detours.lib;d3d9.lib;dinput8.lib;Ws2_32.lib;%(AdditionalDependencies) 104 | 105 | 106 | 107 | 108 | Use 109 | Level3 110 | Disabled 111 | true 112 | _DEBUG;FLATOUT2_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) 113 | true 114 | 115 | 116 | Windows 117 | true 118 | 119 | 120 | 121 | 122 | Use 123 | Level3 124 | MaxSpeed 125 | true 126 | true 127 | true 128 | WIN32;NDEBUG;FLATOUT2_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) 129 | true 130 | 131 | 132 | Windows 133 | true 134 | true 135 | true 136 | detours.lib;d3d9.lib;dinput8.lib;Ws2_32.lib;%(AdditionalDependencies) 137 | 138 | 139 | 140 | 141 | Use 142 | Level3 143 | MaxSpeed 144 | true 145 | true 146 | true 147 | NDEBUG;FLATOUT2_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) 148 | true 149 | 150 | 151 | Windows 152 | true 153 | true 154 | true 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | Create 193 | Create 194 | Create 195 | Create 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | -------------------------------------------------------------------------------- /FlatOut2/VirtualHost.cpp: -------------------------------------------------------------------------------- 1 | #include "stdafx.h" 2 | #include "VirtualHost.h" 3 | 4 | VirtualHost::VirtualHost(int clientCount, PVOID* oCalls, int callCount) 5 | : VirtualIP(TRUE, oCalls, callCount) 6 | { 7 | m_clCount = clientCount; 8 | m_localAddr = m_addressRange; 9 | m_localAddr.S_un.S_un_b.s_b4 = m_settings.virtAddressOffset; 10 | } 11 | 12 | VirtualHost::~VirtualHost() 13 | { 14 | } 15 | 16 | BOOL VirtualHost::Init() 17 | { 18 | if (!VirtualIP::Init()) 19 | return FALSE; 20 | 21 | sockaddr_in server; 22 | server.sin_family = AF_INET; 23 | server.sin_port = htons(m_settings.virtHostPort); 24 | server.sin_addr = in4addr_loopback; 25 | if (m_bind(m_virtSocket, (SOCKADDR *)&server, sizeof(server)) == SOCKET_ERROR) 26 | { 27 | std::ostringstream msg; 28 | msg << "Virtual network server socket bind error : " << WSAGetLastError(); 29 | Logging::getInstance().error(tag, msg.str()); 30 | return FALSE; 31 | } 32 | 33 | SetClientVirtualPort(m_settings.virtHostPort); 34 | return TRUE; 35 | } 36 | 37 | int VirtualHost::RegisterSocket(SOCKET s, const sockaddr* addr) 38 | { 39 | VirtualIP::RegisterSocket(s, addr); 40 | int r = m_bind(s, addr, sizeof(sockaddr)); 41 | if (r == 0) 42 | { 43 | m_socketStates[s]->s = s; 44 | m_socketStates[s]->virtRecvState = VirtRecv_StateNone; 45 | m_socketStates[s]->port = ((sockaddr_in*)addr)->sin_port; 46 | } 47 | else 48 | { 49 | std::ostringstream msg; 50 | msg << "Register socket failed : " << WSAGetLastError(); 51 | Logging::getInstance().error(tag, msg.str()); 52 | } 53 | 54 | return r; 55 | } 56 | 57 | int VirtualHost::ReleaseSocket(SOCKET s) 58 | { 59 | VirtualIP::ReleaseSocket(s); 60 | return m_closeSock(s); 61 | } 62 | 63 | int VirtualHost::HandleVirtNetwork(SocketState** pprecvSock) 64 | { 65 | int ret = VirtualIP::HandleVirtNetwork(pprecvSock); 66 | if (ret == VirtHandleNet_UnknownPort) 67 | { 68 | Logging::getInstance().error(tag, std::string("Virtual client sending to unknown port!")); 69 | } 70 | 71 | return ret; 72 | } 73 | 74 | int VirtualHost::DSendTo(SOCKET s, LPWSABUF lpBuffers, DWORD dwBufferCount, LPDWORD lpNumberOfBytesSent, DWORD dwFlags, const sockaddr * lpTo, int iToLen, LPWSAOVERLAPPED lpOverlapped) 75 | { 76 | sockaddr_in to = *(sockaddr_in*)lpTo; 77 | // Decides between a broadcast, sending to a virtual client and finally sending to a physical client 78 | if (in4addr_broadcast.s_addr == to.sin_addr.s_addr) // Is broadcast? 79 | { 80 | WritePacketInfoToLog("Send,BrCa,Imme", sockaddr_in{ AF_INET,m_socketStates[s]->port,m_localAddr }, to, lpBuffers->len); 81 | BroadcastVirtual(lpBuffers->buf, lpBuffers->len, to.sin_port); 82 | } 83 | else 84 | { 85 | u_long toNetwork = ntohl(to.sin_addr.s_addr) & VirtNet_SubnetMask; 86 | u_long virtNetwork = ntohl(m_addressRange.s_addr) & VirtNet_SubnetMask; 87 | if (toNetwork == virtNetwork && 88 | to.sin_addr.S_un.S_un_b.s_b4 > m_settings.virtAddressOffset && 89 | to.sin_addr.S_un.S_un_b.s_b4 < (m_clCount + m_settings.virtAddressOffset)) 90 | { 91 | int cl = GetClientID(to.sin_addr); 92 | char virtBuffer[2048]; 93 | DWORD numSent; 94 | sockaddr_in virtClAddr; 95 | virtClAddr.sin_family = AF_INET; 96 | virtClAddr.sin_addr = in4addr_loopback; 97 | virtClAddr.sin_port = htons(GetClientPhysHPort(cl)); 98 | VirtIPHeader virtHead; 99 | virtHead.virtIP = m_localAddr; 100 | virtHead.virtPort = to.sin_port; 101 | memcpy_s(virtBuffer, sizeof(virtBuffer), &virtHead, sizeof(virtHead)); 102 | memcpy_s(virtBuffer + sizeof(virtHead), sizeof(virtBuffer) - sizeof(virtHead), lpBuffers->buf, lpBuffers->len); 103 | WSABUF buf; 104 | buf.buf = virtBuffer; 105 | buf.len = sizeof(virtHead) + lpBuffers->len; 106 | WritePacketInfoToLog("Send,Virt,Imme", sockaddr_in{ AF_INET,m_socketStates[s]->port,m_localAddr }, to, lpBuffers->len); 107 | DWORD r = m_sendTo(m_virtSocket, &buf, 1, &numSent, NULL, (sockaddr*)&virtClAddr, sizeof(virtClAddr), NULL, NULL); 108 | if (r != 0) 109 | { 110 | std::ostringstream msg; 111 | msg << "Virtual send to client " << cl << "failed with " << r << " and WSA error " << WSAGetLastError() << "!"; 112 | Logging::getInstance().error(tag, msg.str()); 113 | } 114 | return r; 115 | } 116 | } 117 | 118 | WritePacketInfoToLog("Send,Phys,Imme", sockaddr_in{ AF_INET,m_socketStates[s]->port,in4addr_loopback }, to, lpBuffers->len); 119 | return m_sendTo(s, lpBuffers, dwBufferCount, lpNumberOfBytesSent, dwFlags, lpTo, iToLen, lpOverlapped, NULL); 120 | } 121 | 122 | int VirtualHost::DRecvFrom(SOCKET s, LPWSABUF lpBuffers, DWORD dwBufferCount, LPDWORD lpNumberOfBytesRecvd, LPDWORD lpFlags, sockaddr * lpFrom, LPINT lpFromlen, LPWSAOVERLAPPED lpOverlapped) 123 | { 124 | SocketState* state = m_socketStates[s]; 125 | // Set current receive buffers 126 | state->lpRecvOverlapped = lpOverlapped; 127 | state->lpRecvBuffers = lpBuffers; 128 | state->lpRecvFrom = lpFrom; 129 | state->lpRecvFromlen = lpFromlen; 130 | state->overlappedRecvEvent = lpOverlapped->hEvent; 131 | // 1. Check if physical socket is currently waiting for an overlapped receive 132 | if (!state->isReceiving) 133 | { 134 | VirtReceiveBuffer *physBuf = &state->physRecvBuffer; 135 | physBuf->wsaBuf.buf = physBuf->buffer; 136 | physBuf->wsaBuf.len = sizeof(physBuf->buffer); 137 | physBuf->recvFromLen = sizeof(sockaddr_in); 138 | int ret = m_recvFrom(s, &physBuf->wsaBuf, 1, &physBuf->numBytesRecvd, lpFlags, (sockaddr*)&physBuf->recvFrom, &physBuf->recvFromLen, lpOverlapped, NULL); 139 | // 1 a) Immediate physical packet was received. 140 | // 1 b) No physical packet was waiting, the call can now handle potential virtual packets. 141 | if (0 == ret) 142 | { 143 | CompletePhysicalReceive(state); 144 | CheckPacketIntegrity(state->lpRecvBuffers->buf, *lpNumberOfBytesRecvd); 145 | WritePacketInfoToLog("Recv,Phys,Imme", *(sockaddr_in*)lpFrom, sockaddr_in{ AF_INET,state->port,m_localAddr }, *lpNumberOfBytesRecvd); 146 | return 0; 147 | } 148 | else 149 | { 150 | int err = WSAGetLastError(); 151 | if (err != WSA_IO_PENDING) 152 | { 153 | std::ostringstream msg; 154 | msg << "ReceiveFrom failed with Error : " << err; 155 | Logging::getInstance().error(tag, msg.str()); 156 | } 157 | 158 | state->isReceiving = TRUE; 159 | } 160 | 161 | } 162 | // 2. a) Check if virtual packets are queued for this socket, if so this socket can receive an immediate packet. 163 | // 2. b) Check if there is a new virtual packet for this socket. 164 | if (0 < state->virtRecvBuffers.size() || WSA_WAIT_EVENT_0 == UpdateVirtualSocket(s, 0, NULL)) 165 | { 166 | ImmediateVirtualRecv(state, lpBuffers, dwBufferCount, lpNumberOfBytesRecvd, lpFrom); 167 | WritePacketInfoToLog("Recv,Virt,Imme", *(sockaddr_in*)lpFrom, sockaddr_in{ AF_INET,state->port,m_localAddr }, *lpNumberOfBytesRecvd); 168 | return 0; 169 | } 170 | 171 | 172 | // There are no new packets for this socket. 173 | WSASetLastError(WSA_IO_PENDING); 174 | return SOCKET_ERROR; 175 | } 176 | 177 | BOOL VirtualHost::DGetOverlappedResult(SOCKET s, LPWSAOVERLAPPED lpOverlapped, LPDWORD lpcbTransfer, BOOL fWait, LPDWORD lpdwFlags) 178 | { 179 | SocketState* state = m_socketStates[s]; 180 | BOOLEAN ret; 181 | if (VirtRecv_StateVirtOverlapped == state->virtRecvState) 182 | { 183 | state->virtRecvState = VirtRecv_StateNone; 184 | VirtReceiveBuffer buf = state->virtRecvBuffers.front(); 185 | state->virtRecvBuffers.pop(); 186 | int virtCLID = GetClientID(buf.recvFrom.sin_port); 187 | VirtIPHeader virtHead = *((VirtIPHeader*)buf.buffer); 188 | 189 | // Check if virtual client send broadcast 190 | if (in4addr_broadcast.s_addr == virtHead.virtIP.s_addr) 191 | { 192 | ((sockaddr_in*)state->lpRecvFrom)->sin_addr = in4addr_any; 193 | } 194 | else 195 | { 196 | ((sockaddr_in*)state->lpRecvFrom)->sin_addr = GetClientVirtAddr(virtCLID); 197 | } 198 | ((sockaddr_in*)state->lpRecvFrom)->sin_port = virtHead.virtPort; 199 | ((sockaddr_in*)state->lpRecvFrom)->sin_family = AF_INET; 200 | memcpy_s(state->lpRecvBuffers->buf, state->lpRecvBuffers->len, 201 | buf.buffer + sizeof(VirtIPHeader), sizeof(buf.buffer) - sizeof(VirtIPHeader)); 202 | *lpcbTransfer = buf.numBytesRecvd - sizeof(VirtIPHeader); 203 | CheckPacketIntegrity(state->lpRecvBuffers->buf, *lpcbTransfer); 204 | WritePacketInfoToLog("Recv,Virt,Asyn", 205 | *((sockaddr_in*)state->lpRecvFrom), 206 | sockaddr_in{ AF_INET,virtHead.virtPort,m_localAddr }, 207 | *lpcbTransfer); 208 | ret = TRUE; 209 | } 210 | else if (state->virtRecvState >= VirtRecv_StateVirtBuffered) 211 | { 212 | Logging::getInstance().error(tag, std::string("Unexpected virtual receive state!")); 213 | ret = FALSE; 214 | } 215 | else // Physical receive result 216 | { 217 | state->isReceiving = FALSE; 218 | ret = m_overlapped(s, lpOverlapped, lpcbTransfer, fWait, lpdwFlags); 219 | if (ret) 220 | { 221 | CompletePhysicalReceive(state); 222 | CheckPacketIntegrity(state->lpRecvBuffers->buf, *lpcbTransfer); 223 | WritePacketInfoToLog("Recv,Phys,Asyn", 224 | *((sockaddr_in*)state->lpRecvFrom), 225 | sockaddr_in{ AF_INET, ((sockaddr_in*)state->lpRecvFrom)->sin_port,m_localAddr }, 226 | *lpcbTransfer); 227 | } 228 | else 229 | { 230 | std::ostringstream msg; 231 | msg << "Overlapped physical receive failed with :" << WSAGetLastError(); 232 | Logging::getInstance().error(tag, msg.str()); 233 | } 234 | } 235 | 236 | return ret; 237 | } 238 | 239 | DWORD VirtualHost::DWaitForEvents(DWORD cEvents, const WSAEVENT * lphEvents, BOOL fWaitAll, DWORD dwTimeout, BOOL fAlertable) 240 | { 241 | return VirtualIP::DWaitForEvents(cEvents, lphEvents, fWaitAll, dwTimeout, fAlertable); 242 | } 243 | 244 | BOOL VirtualHost::DWSAResetEvent(WSAEVENT hEvent) 245 | { 246 | if (m_ignoredEvents[hEvent]) 247 | { 248 | m_ignoredEvents[hEvent] = FALSE; 249 | return TRUE; 250 | } 251 | else 252 | { 253 | return m_reset(hEvent); 254 | } 255 | } 256 | 257 | void VirtualHost::CompletePhysicalReceive(SocketState * state) 258 | { 259 | VirtReceiveBuffer *physBuf = &state->physRecvBuffer; 260 | memcpy_s(state->lpRecvBuffers->buf, state->lpRecvBuffers->len, physBuf->buffer, sizeof(physBuf->buffer)); 261 | *state->lpRecvFrom = *(sockaddr*)&physBuf->recvFrom; 262 | *state->lpRecvFromlen = physBuf->recvFromLen; 263 | 264 | } 265 | 266 | void VirtualHost::BroadcastVirtual(char * buffer, int buflen, short nport) 267 | { 268 | for (int i = 1; i < m_clCount; i++) 269 | { 270 | u_short clPort = GetClientPhysHPort(i); 271 | if (0 == clPort) // Client is not yet listening 272 | { 273 | continue; 274 | } 275 | char virtBuffer[2048]; 276 | DWORD numSent; 277 | sockaddr_in virtClAddr; 278 | virtClAddr.sin_family = AF_INET; 279 | virtClAddr.sin_addr = in4addr_loopback; 280 | virtClAddr.sin_port = htons(clPort); 281 | VirtIPHeader virtHead; 282 | virtHead.virtIP = in4addr_broadcast; 283 | virtHead.virtPort = nport; 284 | memcpy_s(virtBuffer, sizeof(virtBuffer), &virtHead, sizeof(virtHead)); 285 | memcpy_s(virtBuffer + sizeof(virtHead), sizeof(virtBuffer) - sizeof(virtHead), buffer, buflen); 286 | WSABUF buf; 287 | buf.buf = virtBuffer; 288 | buf.len = sizeof(virtHead) + buflen; 289 | DWORD r = m_sendTo(m_virtSocket, &buf, 1, &numSent, NULL, (sockaddr*)&virtClAddr, sizeof(virtClAddr), NULL, NULL); 290 | if (r != 0) 291 | { 292 | std::ostringstream msg; 293 | msg << "Virtual broadcast to client " << i << "failed with " << r << " and WSA error " << WSAGetLastError() << "!"; 294 | Logging::getInstance().error(tag, msg.str()); 295 | } 296 | } 297 | } 298 | 299 | void VirtualHost::ImmediateVirtualRecv(SocketState* state, LPWSABUF lpBuffers, DWORD dwBufferCount, LPDWORD lpNumberOfBytesRecvd, sockaddr * lpFrom) 300 | { 301 | VirtReceiveBuffer buf = state->virtRecvBuffers.front(); 302 | state->virtRecvBuffers.pop(); 303 | int virtCLID = GetClientID(buf.recvFrom.sin_port); 304 | VirtIPHeader virtHead = *((VirtIPHeader*)buf.buffer); 305 | 306 | // Check if virtual client send broadcast 307 | if (in4addr_broadcast.s_addr == virtHead.virtIP.s_addr) 308 | { 309 | ((sockaddr_in*)state->lpRecvFrom)->sin_addr = in4addr_any; 310 | } 311 | else 312 | { 313 | ((sockaddr_in*)state->lpRecvFrom)->sin_addr = GetClientVirtAddr(virtCLID); 314 | } 315 | ((sockaddr_in*)lpFrom)->sin_port = virtHead.virtPort; 316 | ((sockaddr_in*)lpFrom)->sin_family = AF_INET; 317 | memcpy_s(lpBuffers->buf, lpBuffers->len, 318 | buf.buffer + sizeof(VirtIPHeader), sizeof(buf.buffer) - sizeof(VirtIPHeader)); 319 | *lpNumberOfBytesRecvd = buf.numBytesRecvd - sizeof(VirtIPHeader); 320 | CheckPacketIntegrity(lpBuffers->buf, *lpNumberOfBytesRecvd); 321 | } 322 | -------------------------------------------------------------------------------- /Launcher/Setup.cs: -------------------------------------------------------------------------------- 1 | namespace Launcher 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Diagnostics; 6 | using System.IO; 7 | using System.Linq; 8 | using System.Threading; 9 | using System.Threading.Tasks; 10 | using System.Windows.Forms; 11 | using static Settings; 12 | 13 | public partial class Setup : Form 14 | { 15 | public const string settingsFile = "Splitscreen_Settings.xml"; 16 | 17 | private Task gameLaunch = null; 18 | private List resolutions = null; 19 | private List connectedGamedPads = null; 20 | private List availablePads = new List(); 21 | private Settings settings = null; 22 | private bool suppressUpdate = false; 23 | 24 | public Setup() 25 | { 26 | InitializeComponent(); 27 | } 28 | 29 | private void Setup_Load(object sender, EventArgs e) 30 | { 31 | var load = Task.Factory.StartNew(LoadSettings); 32 | load.ContinueWith(t => 33 | { 34 | if (t.Exception != null) 35 | { 36 | throw t.Exception.InnerException; 37 | } 38 | UpdateSettings(this.settings); 39 | EnterConfigTab(); 40 | ConfigTabControl.Enabled = true; 41 | }, 42 | CancellationToken.None, TaskContinuationOptions.None, TaskScheduler.FromCurrentSynchronizationContext()); 43 | } 44 | 45 | private void LoadSettings() 46 | { 47 | ValidateInstallation(); 48 | AutoUpdate.NewVersion += AutoUpdate_NewVersion; 49 | AutoUpdate.NotifyNewVersion += AutoUpdate_NotifyNewVersion; 50 | AutoUpdate.CheckVersion(); 51 | 52 | if (settings != null) 53 | { 54 | throw new InvalidOperationException("Settings can only be loaded once at the start of the application."); 55 | } 56 | 57 | try 58 | { 59 | using (var s = File.OpenRead(settingsFile)) 60 | { 61 | settings = Settings.LoadSettings(s); 62 | } 63 | } 64 | catch (Exception e) 65 | { 66 | Debug.WriteLine($"Loading settings failed with exception : \n{e.ToString()}"); 67 | } 68 | 69 | if (this.settings == null) 70 | { 71 | this.settings = new Settings(1); 72 | } 73 | } 74 | 75 | private void AutoUpdate_NotifyNewVersion(object sender, AutoUpdate.VersionUpdateEventArgs e) 76 | { 77 | Invoke(new Action(() => 78 | { 79 | using ( 80 | var notify = new UpdateNotifier( 81 | e.Version.Version, 82 | e.Version.ChangeLog, 83 | "Download new version", 84 | "https://github.com/DeadlySurprise/FO2-Splitscreen/releases")) 85 | { 86 | notify.ShowDialog(); 87 | } 88 | })); 89 | } 90 | 91 | private void AutoUpdate_NewVersion(object sender, EventArgs e) 92 | { 93 | Invoke(new Action(() => 94 | { 95 | NewVerLink.Enabled = true; 96 | NewVerLink.Visible = true; 97 | })); 98 | } 99 | 100 | private void ValidateInstallation() 101 | { 102 | bool isValid = true; 103 | if (!File.Exists("FlatOut2.exe")) 104 | { 105 | MessageBox.Show( 106 | "FlatOut2 executable not found! Make sure that Launcher.exe and FlatOut2.dll are inside the FlatOut2 game folder.", 107 | "Installation Error!", 108 | MessageBoxButtons.OK, 109 | MessageBoxIcon.Error); 110 | isValid = false; 111 | } 112 | 113 | if (!File.Exists("FlatOut2.dll")) 114 | { 115 | MessageBox.Show( 116 | "FlatOut2.dll not found! Make sure that Launcher.exe and FlatOut2.dll are inside the FlatOut2 game folder.", 117 | "Installation Error!", 118 | MessageBoxButtons.OK, 119 | MessageBoxIcon.Error); 120 | isValid = false; 121 | } 122 | 123 | if (!Directory.Exists("savegame") || !File.Exists("Savegame/options.cfg")) 124 | { 125 | MessageBox.Show( 126 | "Game settings not found! Run FlatOut 2 normally once before using this launcher.", 127 | "Installation Error!", 128 | MessageBoxButtons.OK, 129 | MessageBoxIcon.Error); 130 | isValid = false; 131 | } 132 | 133 | if (AutoUpdate.LauncherVersion != AutoUpdate.FlatOutDLLVersion) 134 | { 135 | MessageBox.Show( 136 | "FlatOut2.dll is out of Date! Please get the newest version from https://github.com/DeadlySurprise/FO2-Splitscreen/releases.", 137 | "Installation Error!", 138 | MessageBoxButtons.OK, 139 | MessageBoxIcon.Error); 140 | isValid = false; 141 | } 142 | 143 | if (!isValid) 144 | { 145 | Application.Exit(); 146 | } 147 | } 148 | 149 | private void ButtonLaunch_Click(object sender, EventArgs e) 150 | { 151 | if (gameLaunch != null) 152 | { 153 | return; 154 | } 155 | 156 | ButtonLaunch.Enabled = false; 157 | RECT selected = new RECT(0, 0, 640, 480); 158 | if (resolutions != null && InputResoSelect.Enabled) 159 | { 160 | selected = resolutions[InputResoSelect.SelectedIndex]; 161 | } 162 | this.settings.SetDefaultWindowPos(Math.Max(selected.Width, 640), Math.Max(selected.Height, 480)); 163 | 164 | gameLaunch = FlatOut2.CreateSplitScreenSession(this.settings); 165 | gameLaunch.ContinueWith(t => 166 | { 167 | using (Stream s = File.Open(settingsFile, FileMode.Create)) 168 | { 169 | this.settings.SaveSettings(s); 170 | } 171 | Application.Exit(); 172 | }, 173 | TaskContinuationOptions.OnlyOnRanToCompletion); 174 | gameLaunch.ContinueWith(t => 175 | { 176 | throw t.Exception.InnerException; 177 | }, 178 | CancellationToken.None, 179 | TaskContinuationOptions.OnlyOnFaulted, 180 | TaskScheduler.FromCurrentSynchronizationContext()); 181 | } 182 | 183 | private void InputInstanceCount_ValueChanged(object sender, EventArgs e) 184 | { 185 | Settings oldSettings = settings; 186 | settings = new Settings((int)InputInstanceCount.Value); 187 | UpdateSettings(oldSettings); 188 | if (oldSettings != null) 189 | { 190 | settings.Dispose(); 191 | } 192 | } 193 | 194 | private void UpdateSettings(Settings oldSettings) 195 | { 196 | InputInstanceCount.Value = settings.InstanceCount; 197 | settings.SkipIntros = oldSettings.SkipIntros; 198 | InputSkipIntros.Checked = settings.SkipIntros; 199 | ConsoleVerbositySelect.SelectedItem = settings.ConsoleVerbosity.ToString(); 200 | LogFileVerbositySelect.SelectedItem = settings.LogFileVerbosity.ToString(); 201 | RECT res = (oldSettings.GetWindowPos().Equals(RECT.Zero) ? new RECT(0, 0, 640, 480) : oldSettings.GetWindowPos()); 202 | settings.SetDefaultWindowPos(res.Width, res.Height); 203 | } 204 | 205 | private void ConfigTabControl_Enter(object sender, EventArgs e) 206 | { 207 | EnterConfigTab(); 208 | } 209 | 210 | private void TabController_Enter(object sender, EventArgs e) 211 | { 212 | ControllerPlayerList.Enabled = false; 213 | ControllerOptionsPanel.Enabled = false; 214 | ControllerBTResetAll.Enabled = false; 215 | Task> getControllers = FlatOut2.GetGamePads(); 216 | getControllers.ContinueWith(UpdateControllerList, CancellationToken.None, TaskContinuationOptions.None, TaskScheduler.FromCurrentSynchronizationContext()); 217 | ControllerPlayerList.Clear(); 218 | for (int player = 0; player < settings.InstanceCount; player++) 219 | { 220 | ControllerPlayerList.Items.Add($"Player {player + 1}"); 221 | } 222 | } 223 | 224 | private void EnterConfigTab() 225 | { 226 | Task> getResolutions = FlatOut2.GetResolutions(); 227 | getResolutions.ContinueWith(UpdateResolutionList, CancellationToken.None, TaskContinuationOptions.None, TaskScheduler.FromCurrentSynchronizationContext()); 228 | } 229 | 230 | private void UpdateResolutionList(Task> getResolutions) 231 | { 232 | if (getResolutions.Exception != null) 233 | { 234 | throw getResolutions.Exception.InnerException; 235 | } 236 | 237 | resolutions = getResolutions.Result; 238 | InputResoSelect.Items.Clear(); 239 | foreach (var reso in resolutions) 240 | { 241 | InputResoSelect.Items.Add($"{reso.Width} x {reso.Height}"); 242 | } 243 | 244 | if (InputResoSelect.SelectedIndex == -1 && resolutions.Count > 0) 245 | { 246 | InputResoSelect.SelectedIndex = 0; 247 | if (!settings.GetWindowPos().Equals(RECT.Zero)) 248 | { 249 | for (int i = 0; i < resolutions.Count; i++) 250 | { 251 | if (settings.GetWindowPos().Equals(resolutions[i])) 252 | { 253 | InputResoSelect.SelectedIndex = i; 254 | } 255 | } 256 | } 257 | } 258 | 259 | InputResoSelect.Enabled = true; 260 | } 261 | 262 | private void UpdateControllerList(Task> getControllers) 263 | { 264 | if (getControllers.Exception != null) 265 | { 266 | throw getControllers.Exception.InnerException; 267 | } 268 | 269 | connectedGamedPads = getControllers.Result; 270 | 271 | InputControllerSelect.Items.Clear(); 272 | foreach (var gamepad in connectedGamedPads) 273 | { 274 | InputControllerSelect.Items.Add(gamepad.ToString()); 275 | } 276 | 277 | ControllerPlayerList.Enabled = true; 278 | ControllerBTResetAll.Enabled = true; 279 | } 280 | 281 | private void ControllerBTResetAll_Click(object sender, EventArgs e) 282 | { 283 | ControllerBTResetAll.Enabled = false; 284 | ControllerOptionsPanel.Enabled = false; 285 | settings.ResetControllerSettings(); 286 | ControllerOptionsPanel.Enabled = true; 287 | ControllerBTResetAll.Enabled = true; 288 | } 289 | 290 | private void ControllerPlayerList_SelectedIndexChanged(object sender, EventArgs e) 291 | { 292 | if (ControllerPlayerList.SelectedIndices.Count > 0) 293 | { 294 | UpdateControllerSelect(); 295 | } 296 | else 297 | { 298 | ControllerOptionsPanel.Enabled = false; 299 | } 300 | } 301 | 302 | private void UpdateControllerSelect() 303 | { 304 | if (suppressUpdate) 305 | { 306 | suppressUpdate = false; 307 | return; 308 | } 309 | 310 | List usedPads = settings.GetUsedControllers(connectedGamedPads); 311 | GamePad instancePad = settings.GetInstanceController(ControllerPlayerList.SelectedIndices[0], connectedGamedPads); 312 | availablePads.Clear(); 313 | InputControllerSelect.Items.Clear(); 314 | InputControllerSelect.Text = string.Empty; 315 | InputControllerSelect.SelectedIndex = -1; 316 | foreach (var pad in connectedGamedPads) 317 | { 318 | if (!usedPads.Contains(pad) || pad.Equals(instancePad)) 319 | { 320 | availablePads.Add(pad); 321 | InputControllerSelect.Items.Add(pad.ToString()); 322 | 323 | if (pad.Equals(instancePad)) 324 | { 325 | suppressUpdate = true; 326 | InputControllerSelect.SelectedIndex = InputControllerSelect.Items.Count - 1; 327 | } 328 | } 329 | } 330 | 331 | ControllerOptionsPanel.Enabled = true; 332 | } 333 | 334 | private void InputControllerSelect_SelectedIndexChanged(object sender, EventArgs e) 335 | { 336 | if (InputControllerSelect.SelectedIndex != -1) 337 | { 338 | settings.SetInstanceController( 339 | ControllerPlayerList.SelectedIndices[0], 340 | availablePads[InputControllerSelect.SelectedIndex]); 341 | UpdateControllerSelect(); 342 | } 343 | } 344 | 345 | private void InputSkipIntros_CheckedChanged(object sender, EventArgs e) 346 | { 347 | settings.SkipIntros = InputSkipIntros.Checked; 348 | } 349 | 350 | private void NewVerLink_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e) 351 | { 352 | System.Diagnostics.Process.Start("https://github.com/DeadlySurprise/FO2-Splitscreen/releases"); 353 | } 354 | 355 | private void LogFileVerbositySelect_SelectedIndexChanged(object sender, EventArgs e) 356 | { 357 | settings.LogFileVerbosity = (LogLevel)Enum.Parse(typeof(LogLevel), LogFileVerbositySelect.SelectedItem.ToString()); 358 | } 359 | 360 | private void ConsoleVerbositySelect_SelectedIndexChanged(object sender, EventArgs e) 361 | { 362 | settings.ConsoleVerbosity = (LogLevel)Enum.Parse(typeof(LogLevel), ConsoleVerbositySelect.SelectedItem.ToString()); 363 | } 364 | } 365 | } 366 | --------------------------------------------------------------------------------