Skip to content

Commit c9cd6ad

Browse files
committed
twinhook: extract imgui_impl_win32 and remove XInput dependency #26
deps: shuffle dependency wrapper projects into another directory
1 parent 397d505 commit c9cd6ad

File tree

12 files changed

+390
-65
lines changed

12 files changed

+390
-65
lines changed

twinhook/gfx/imgui_impl_win32.cpp

Lines changed: 276 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,276 @@
1+
// dear imgui: Platform Binding for Windows (standard windows API for 32 and 64 bits applications)
2+
// This needs to be used along with a Renderer (e.g. DirectX11, OpenGL3, Vulkan..)
3+
4+
// Implemented features:
5+
// [X] Platform: Clipboard support (for Win32 this is actually part of core imgui)
6+
// [X] Platform: Mouse cursor shape and visibility. Disable with 'io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange'.
7+
// [X] Platform: Keyboard arrays indexed using VK_* Virtual Key Codes, e.g. ImGui::IsKeyPressed(VK_SPACE).
8+
// [X] Platform: Gamepad support. Enabled with 'io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad'.
9+
10+
#include "imgui.h"
11+
#include "imgui_impl_win32.h"
12+
#ifndef WIN32_LEAN_AND_MEAN
13+
#define WIN32_LEAN_AND_MEAN
14+
#endif
15+
#include <windows.h>
16+
17+
// CHANGELOG
18+
// (minor and older changes stripped away, please see git history for details)
19+
// 2019-05-11: Inputs: Don't filter value from WM_CHAR before calling AddInputCharacter().
20+
// 2019-01-17: Misc: Using GetForegroundWindow()+IsChild() instead of GetActiveWindow() to be compatible with windows created in a different thread or parent.
21+
// 2019-01-17: Inputs: Added support for mouse buttons 4 and 5 via WM_XBUTTON* messages.
22+
// 2019-01-15: Inputs: Added support for XInput gamepads (if ImGuiConfigFlags_NavEnableGamepad is set by user application).
23+
// 2018-11-30: Misc: Setting up io.BackendPlatformName so it can be displayed in the About Window.
24+
// 2018-06-29: Inputs: Added support for the ImGuiMouseCursor_Hand cursor.
25+
// 2018-06-10: Inputs: Fixed handling of mouse wheel messages to support fine position messages (typically sent by track-pads).
26+
// 2018-06-08: Misc: Extracted imgui_impl_win32.cpp/.h away from the old combined DX9/DX10/DX11/DX12 examples.
27+
// 2018-03-20: Misc: Setup io.BackendFlags ImGuiBackendFlags_HasMouseCursors and ImGuiBackendFlags_HasSetMousePos flags + honor ImGuiConfigFlags_NoMouseCursorChange flag.
28+
// 2018-02-20: Inputs: Added support for mouse cursors (ImGui::GetMouseCursor() value and WM_SETCURSOR message handling).
29+
// 2018-02-06: Inputs: Added mapping for ImGuiKey_Space.
30+
// 2018-02-06: Inputs: Honoring the io.WantSetMousePos by repositioning the mouse (when using navigation and ImGuiConfigFlags_NavMoveMouse is set).
31+
// 2018-02-06: Misc: Removed call to ImGui::Shutdown() which is not available from 1.60 WIP, user needs to call CreateContext/DestroyContext themselves.
32+
// 2018-01-20: Inputs: Added Horizontal Mouse Wheel support.
33+
// 2018-01-08: Inputs: Added mapping for ImGuiKey_Insert.
34+
// 2018-01-05: Inputs: Added WM_LBUTTONDBLCLK double-click handlers for window classes with the CS_DBLCLKS flag.
35+
// 2017-10-23: Inputs: Added WM_SYSKEYDOWN / WM_SYSKEYUP handlers so e.g. the VK_MENU key can be read.
36+
// 2017-10-23: Inputs: Using Win32 ::SetCapture/::GetCapture() to retrieve mouse positions outside the client area when dragging.
37+
// 2016-11-12: Inputs: Only call Win32 ::SetCursor(NULL) when io.MouseDrawCursor is set.
38+
39+
// Win32 Data
40+
static HWND g_hWnd = 0;
41+
static INT64 g_Time = 0;
42+
static INT64 g_TicksPerSecond = 0;
43+
static ImGuiMouseCursor g_LastMouseCursor = ImGuiMouseCursor_COUNT;
44+
static bool g_HasGamepad = false;
45+
static bool g_WantUpdateHasGamepad = true;
46+
47+
// Functions
48+
bool ImGui_ImplWin32_Init(void* hwnd)
49+
{
50+
if (!::QueryPerformanceFrequency((LARGE_INTEGER *)&g_TicksPerSecond))
51+
return false;
52+
if (!::QueryPerformanceCounter((LARGE_INTEGER *)&g_Time))
53+
return false;
54+
55+
// Setup back-end capabilities flags
56+
g_hWnd = (HWND)hwnd;
57+
ImGuiIO& io = ImGui::GetIO();
58+
io.BackendFlags |= ImGuiBackendFlags_HasMouseCursors; // We can honor GetMouseCursor() values (optional)
59+
io.BackendFlags |= ImGuiBackendFlags_HasSetMousePos; // We can honor io.WantSetMousePos requests (optional, rarely used)
60+
io.BackendPlatformName = "imgui_impl_win32";
61+
io.ImeWindowHandle = hwnd;
62+
63+
// Keyboard mapping. ImGui will use those indices to peek into the io.KeysDown[] array that we will update during the application lifetime.
64+
io.KeyMap[ImGuiKey_Tab] = VK_TAB;
65+
io.KeyMap[ImGuiKey_LeftArrow] = VK_LEFT;
66+
io.KeyMap[ImGuiKey_RightArrow] = VK_RIGHT;
67+
io.KeyMap[ImGuiKey_UpArrow] = VK_UP;
68+
io.KeyMap[ImGuiKey_DownArrow] = VK_DOWN;
69+
io.KeyMap[ImGuiKey_PageUp] = VK_PRIOR;
70+
io.KeyMap[ImGuiKey_PageDown] = VK_NEXT;
71+
io.KeyMap[ImGuiKey_Home] = VK_HOME;
72+
io.KeyMap[ImGuiKey_End] = VK_END;
73+
io.KeyMap[ImGuiKey_Insert] = VK_INSERT;
74+
io.KeyMap[ImGuiKey_Delete] = VK_DELETE;
75+
io.KeyMap[ImGuiKey_Backspace] = VK_BACK;
76+
io.KeyMap[ImGuiKey_Space] = VK_SPACE;
77+
io.KeyMap[ImGuiKey_Enter] = VK_RETURN;
78+
io.KeyMap[ImGuiKey_Escape] = VK_ESCAPE;
79+
io.KeyMap[ImGuiKey_A] = 'A';
80+
io.KeyMap[ImGuiKey_C] = 'C';
81+
io.KeyMap[ImGuiKey_V] = 'V';
82+
io.KeyMap[ImGuiKey_X] = 'X';
83+
io.KeyMap[ImGuiKey_Y] = 'Y';
84+
io.KeyMap[ImGuiKey_Z] = 'Z';
85+
86+
return true;
87+
}
88+
89+
void ImGui_ImplWin32_Shutdown()
90+
{
91+
g_hWnd = (HWND)0;
92+
}
93+
94+
static bool ImGui_ImplWin32_UpdateMouseCursor()
95+
{
96+
ImGuiIO& io = ImGui::GetIO();
97+
if (io.ConfigFlags & ImGuiConfigFlags_NoMouseCursorChange)
98+
return false;
99+
100+
ImGuiMouseCursor imgui_cursor = ImGui::GetMouseCursor();
101+
if (imgui_cursor == ImGuiMouseCursor_None || io.MouseDrawCursor)
102+
{
103+
// Hide OS mouse cursor if imgui is drawing it or if it wants no cursor
104+
::SetCursor(NULL);
105+
}
106+
else
107+
{
108+
// Show OS mouse cursor
109+
LPTSTR win32_cursor = IDC_ARROW;
110+
switch (imgui_cursor)
111+
{
112+
case ImGuiMouseCursor_Arrow: win32_cursor = IDC_ARROW; break;
113+
case ImGuiMouseCursor_TextInput: win32_cursor = IDC_IBEAM; break;
114+
case ImGuiMouseCursor_ResizeAll: win32_cursor = IDC_SIZEALL; break;
115+
case ImGuiMouseCursor_ResizeEW: win32_cursor = IDC_SIZEWE; break;
116+
case ImGuiMouseCursor_ResizeNS: win32_cursor = IDC_SIZENS; break;
117+
case ImGuiMouseCursor_ResizeNESW: win32_cursor = IDC_SIZENESW; break;
118+
case ImGuiMouseCursor_ResizeNWSE: win32_cursor = IDC_SIZENWSE; break;
119+
case ImGuiMouseCursor_Hand: win32_cursor = IDC_HAND; break;
120+
}
121+
::SetCursor(::LoadCursor(NULL, win32_cursor));
122+
}
123+
return true;
124+
}
125+
126+
static void ImGui_ImplWin32_UpdateMousePos()
127+
{
128+
ImGuiIO& io = ImGui::GetIO();
129+
130+
// Set OS mouse position if requested (rarely used, only when ImGuiConfigFlags_NavEnableSetMousePos is enabled by user)
131+
if (io.WantSetMousePos)
132+
{
133+
POINT pos = { (int)io.MousePos.x, (int)io.MousePos.y };
134+
::ClientToScreen(g_hWnd, &pos);
135+
::SetCursorPos(pos.x, pos.y);
136+
}
137+
138+
// Set mouse position
139+
io.MousePos = ImVec2(-FLT_MAX, -FLT_MAX);
140+
POINT pos;
141+
if (HWND active_window = ::GetForegroundWindow())
142+
if (active_window == g_hWnd || ::IsChild(active_window, g_hWnd))
143+
if (::GetCursorPos(&pos) && ::ScreenToClient(g_hWnd, &pos))
144+
io.MousePos = ImVec2((float)pos.x, (float)pos.y);
145+
}
146+
147+
// Gamepad navigation mapping
148+
static void ImGui_ImplWin32_UpdateGamepads()
149+
{
150+
ImGuiIO& io = ImGui::GetIO();
151+
IM_ASSERT(!(io.ConfigFlags & ImGuiConfigFlags_NavEnableGamepad));
152+
}
153+
154+
void ImGui_ImplWin32_NewFrame()
155+
{
156+
ImGuiIO& io = ImGui::GetIO();
157+
IM_ASSERT(io.Fonts->IsBuilt() && "Font atlas not built! It is generally built by the renderer back-end. Missing call to renderer _NewFrame() function? e.g. ImGui_ImplOpenGL3_NewFrame().");
158+
159+
// Setup display size (every frame to accommodate for window resizing)
160+
RECT rect;
161+
::GetClientRect(g_hWnd, &rect);
162+
io.DisplaySize = ImVec2((float)(rect.right - rect.left), (float)(rect.bottom - rect.top));
163+
164+
// Setup time step
165+
INT64 current_time;
166+
::QueryPerformanceCounter((LARGE_INTEGER *)&current_time);
167+
io.DeltaTime = (float)(current_time - g_Time) / g_TicksPerSecond;
168+
g_Time = current_time;
169+
170+
// Read keyboard modifiers inputs
171+
io.KeyCtrl = (::GetKeyState(VK_CONTROL) & 0x8000) != 0;
172+
io.KeyShift = (::GetKeyState(VK_SHIFT) & 0x8000) != 0;
173+
io.KeyAlt = (::GetKeyState(VK_MENU) & 0x8000) != 0;
174+
io.KeySuper = false;
175+
// io.KeysDown[], io.MousePos, io.MouseDown[], io.MouseWheel: filled by the WndProc handler below.
176+
177+
// Update OS mouse position
178+
ImGui_ImplWin32_UpdateMousePos();
179+
180+
// Update OS mouse cursor with the cursor requested by imgui
181+
ImGuiMouseCursor mouse_cursor = io.MouseDrawCursor ? ImGuiMouseCursor_None : ImGui::GetMouseCursor();
182+
if (g_LastMouseCursor != mouse_cursor)
183+
{
184+
g_LastMouseCursor = mouse_cursor;
185+
ImGui_ImplWin32_UpdateMouseCursor();
186+
}
187+
188+
// Update game controllers (if enabled and available)
189+
ImGui_ImplWin32_UpdateGamepads();
190+
}
191+
192+
// Allow compilation with old Windows SDK. MinGW doesn't have default _WIN32_WINNT/WINVER versions.
193+
#ifndef WM_MOUSEHWHEEL
194+
#define WM_MOUSEHWHEEL 0x020E
195+
#endif
196+
#ifndef DBT_DEVNODES_CHANGED
197+
#define DBT_DEVNODES_CHANGED 0x0007
198+
#endif
199+
200+
// Process Win32 mouse/keyboard inputs.
201+
// You can read the io.WantCaptureMouse, io.WantCaptureKeyboard flags to tell if dear imgui wants to use your inputs.
202+
// - When io.WantCaptureMouse is true, do not dispatch mouse input data to your main application.
203+
// - When io.WantCaptureKeyboard is true, do not dispatch keyboard input data to your main application.
204+
// Generally you may always pass all inputs to dear imgui, and hide them from your application based on those two flags.
205+
// PS: In this Win32 handler, we use the capture API (GetCapture/SetCapture/ReleaseCapture) to be able to read mouse coordinates when dragging mouse outside of our window bounds.
206+
// PS: We treat DBLCLK messages as regular mouse down messages, so this code will work on windows classes that have the CS_DBLCLKS flag set. Our own example app code doesn't set this flag.
207+
IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
208+
{
209+
if (ImGui::GetCurrentContext() == NULL)
210+
return 0;
211+
212+
ImGuiIO& io = ImGui::GetIO();
213+
switch (msg)
214+
{
215+
case WM_LBUTTONDOWN: case WM_LBUTTONDBLCLK:
216+
case WM_RBUTTONDOWN: case WM_RBUTTONDBLCLK:
217+
case WM_MBUTTONDOWN: case WM_MBUTTONDBLCLK:
218+
case WM_XBUTTONDOWN: case WM_XBUTTONDBLCLK:
219+
{
220+
int button = 0;
221+
if (msg == WM_LBUTTONDOWN || msg == WM_LBUTTONDBLCLK) { button = 0; }
222+
if (msg == WM_RBUTTONDOWN || msg == WM_RBUTTONDBLCLK) { button = 1; }
223+
if (msg == WM_MBUTTONDOWN || msg == WM_MBUTTONDBLCLK) { button = 2; }
224+
if (msg == WM_XBUTTONDOWN || msg == WM_XBUTTONDBLCLK) { button = (GET_XBUTTON_WPARAM(wParam) == XBUTTON1) ? 3 : 4; }
225+
if (!ImGui::IsAnyMouseDown() && ::GetCapture() == NULL)
226+
::SetCapture(hwnd);
227+
io.MouseDown[button] = true;
228+
return 0;
229+
}
230+
case WM_LBUTTONUP:
231+
case WM_RBUTTONUP:
232+
case WM_MBUTTONUP:
233+
case WM_XBUTTONUP:
234+
{
235+
int button = 0;
236+
if (msg == WM_LBUTTONUP) { button = 0; }
237+
if (msg == WM_RBUTTONUP) { button = 1; }
238+
if (msg == WM_MBUTTONUP) { button = 2; }
239+
if (msg == WM_XBUTTONUP) { button = (GET_XBUTTON_WPARAM(wParam) == XBUTTON1) ? 3 : 4; }
240+
io.MouseDown[button] = false;
241+
if (!ImGui::IsAnyMouseDown() && ::GetCapture() == hwnd)
242+
::ReleaseCapture();
243+
return 0;
244+
}
245+
case WM_MOUSEWHEEL:
246+
io.MouseWheel += (float)GET_WHEEL_DELTA_WPARAM(wParam) / (float)WHEEL_DELTA;
247+
return 0;
248+
case WM_MOUSEHWHEEL:
249+
io.MouseWheelH += (float)GET_WHEEL_DELTA_WPARAM(wParam) / (float)WHEEL_DELTA;
250+
return 0;
251+
case WM_KEYDOWN:
252+
case WM_SYSKEYDOWN:
253+
if (wParam < 256)
254+
io.KeysDown[wParam] = 1;
255+
return 0;
256+
case WM_KEYUP:
257+
case WM_SYSKEYUP:
258+
if (wParam < 256)
259+
io.KeysDown[wParam] = 0;
260+
return 0;
261+
case WM_CHAR:
262+
// You can also use ToAscii()+GetKeyboardState() to retrieve characters.
263+
io.AddInputCharacter((unsigned int)wParam);
264+
return 0;
265+
case WM_SETCURSOR:
266+
if (LOWORD(lParam) == HTCLIENT && ImGui_ImplWin32_UpdateMouseCursor())
267+
return 1;
268+
return 0;
269+
case WM_DEVICECHANGE:
270+
if ((UINT)wParam == DBT_DEVNODES_CHANGED)
271+
g_WantUpdateHasGamepad = true;
272+
return 0;
273+
}
274+
return 0;
275+
}
276+

twinhook/gfx/imgui_impl_win32.h

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// dear imgui: Platform Binding for Windows (standard windows API for 32 and 64 bits applications)
2+
// This needs to be used along with a Renderer (e.g. DirectX11, OpenGL3, Vulkan..)
3+
4+
// Implemented features:
5+
// [X] Platform: Clipboard support (for Win32 this is actually part of core imgui)
6+
// [X] Platform: Mouse cursor shape and visibility. Disable with 'io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange'.
7+
// [X] Platform: Keyboard arrays indexed using VK_* Virtual Key Codes, e.g. ImGui::IsKeyPressed(VK_SPACE).
8+
// [X] Platform: Gamepad support. Enabled with 'io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad'.
9+
10+
#pragma once
11+
12+
IMGUI_IMPL_API bool ImGui_ImplWin32_Init(void* hwnd);
13+
IMGUI_IMPL_API void ImGui_ImplWin32_Shutdown();
14+
IMGUI_IMPL_API void ImGui_ImplWin32_NewFrame();
15+
16+
// Handler for Win32 messages, update mouse/keyboard data.
17+
// You may or not need this for your implementation, but it can serve as reference for handling inputs.
18+
// Intentionally commented out to avoid dragging dependencies on <windows.h> types. You can COPY this line into your .cpp code instead.
19+
/*
20+
IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
21+
*/

twinhook/twinhook.vcxproj

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -39,13 +39,13 @@
3939
</ImportGroup>
4040
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
4141
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
42-
<Import Project="..\ImGui\imgui.props" />
43-
<Import Project="..\Detours\Detours.props" />
42+
<Import Project="..\vcxproj\ImGui\imgui.props" />
43+
<Import Project="..\vcxproj\Detours\Detours.props" />
4444
</ImportGroup>
4545
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="PropertySheets">
4646
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
47-
<Import Project="..\ImGui\imgui.props" />
48-
<Import Project="..\Detours\Detours.props" />
47+
<Import Project="..\vcxproj\ImGui\imgui.props" />
48+
<Import Project="..\vcxproj\Detours\Detours.props" />
4949
</ImportGroup>
5050
<PropertyGroup Label="UserMacros" />
5151
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
@@ -149,6 +149,7 @@
149149
<ClCompile Include="control\th06_player.cpp" />
150150
<ClCompile Include="control\th11_player.cpp" />
151151
<ClCompile Include="control\th_player.cpp" />
152+
<ClCompile Include="gfx\imgui_impl_win32.cpp" />
152153
<ClCompile Include="model\aabb.cpp" />
153154
<ClCompile Include="model\circle.cpp" />
154155
<ClCompile Include="model\entity.cpp" />
@@ -193,6 +194,7 @@
193194
<ClInclude Include="config\th_registry.h" />
194195
<ClInclude Include="control\th06_player.h" />
195196
<ClInclude Include="control\th11_player.h" />
197+
<ClInclude Include="gfx\imgui_impl_win32.h" />
196198
<ClInclude Include="model\aabb.h" />
197199
<ClInclude Include="model\circle.h" />
198200
<ClInclude Include="model\entity.h" />
@@ -241,10 +243,10 @@
241243
<ClInclude Include="util\vec2.h" />
242244
</ItemGroup>
243245
<ItemGroup>
244-
<ProjectReference Include="..\Detours\Detours.vcxproj">
246+
<ProjectReference Include="..\vcxproj\Detours\Detours.vcxproj">
245247
<Project>{66ec5a9d-6965-4557-87c8-821a089bb8c8}</Project>
246248
</ProjectReference>
247-
<ProjectReference Include="..\ImGui\ImGui.vcxproj">
249+
<ProjectReference Include="..\vcxproj\ImGui\ImGui.vcxproj">
248250
<Project>{df399789-7f8c-404a-beff-b93671197597}</Project>
249251
</ProjectReference>
250252
</ItemGroup>

twinhook/twinhook.vcxproj.filters

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,9 @@
135135
<ClCompile Include="model\obb.cpp">
136136
<Filter>Source Files</Filter>
137137
</ClCompile>
138+
<ClCompile Include="gfx\imgui_impl_win32.cpp">
139+
<Filter>Source Files</Filter>
140+
</ClCompile>
138141
</ItemGroup>
139142
<ItemGroup>
140143
<ClInclude Include="stdafx.h">
@@ -287,5 +290,8 @@
287290
<ClInclude Include="control\movement.h">
288291
<Filter>Header Files</Filter>
289292
</ClInclude>
293+
<ClInclude Include="gfx\imgui_impl_win32.h">
294+
<Filter>Header Files</Filter>
295+
</ClInclude>
290296
</ItemGroup>
291297
</Project>

twinject.sln

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "twinject", "twinject\twinje
1010
EndProject
1111
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "twinhook", "twinhook\twinhook.vcxproj", "{A47B2FB1-6209-46DC-AA46-17F03164F7D8}"
1212
EndProject
13-
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ImGui", "ImGui\ImGui.vcxproj", "{DF399789-7F8C-404A-BEFF-B93671197597}"
13+
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ImGui", "vcxproj\ImGui\ImGui.vcxproj", "{DF399789-7F8C-404A-BEFF-B93671197597}"
1414
EndProject
15-
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Detours", "Detours\Detours.vcxproj", "{66EC5A9D-6965-4557-87C8-821A089BB8C8}"
15+
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Detours", "vcxproj\Detours\Detours.vcxproj", "{66EC5A9D-6965-4557-87C8-821A089BB8C8}"
1616
EndProject
1717
Global
1818
GlobalSection(SolutionConfigurationPlatforms) = preSolution

0 commit comments

Comments
 (0)