Skip to content
This repository was archived by the owner on Sep 17, 2024. It is now read-only.

Commit f8d2695

Browse files
committed
Improve debugger automation script
1 parent 83227d8 commit f8d2695

File tree

5 files changed

+136
-15
lines changed

5 files changed

+136
-15
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
# Launch.vs.json isn't user-specific in this repo
66
!.vs/launch.vs.json
7+
!.vs/launch_with_automation.ps1
78

89
# Artifacts
910
bin/*

.vs/launch.vs.json

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,12 @@
1212
"currentDir": "${env.GAME_ROOT_DIRECTORY}",
1313
"exe": "${env.ComSpec}",
1414
"args": [
15-
"/C \"${env.GAME_ROOT_DIRECTORY}/Cyberpunk2077.exe\" && taskkill /F /IM VsDebugConsole.exe"
15+
"/C \"powershell.exe -WindowStyle Hidden -nologo -ExecutionPolicy Bypass -File ^\"${workspaceRoot}/.vs/launch_with_automation.ps1^\" Launch\""
1616
],
1717
"environment": [
1818
{
19-
"name": "VS_DEBUGGER_REQUEST",
20-
"value": "${env.VS_DEBUGGER_REQUEST}"
21-
},
22-
{
23-
"name": "VS_DEBUGGER_PROC",
24-
"value": "${env.VS_DEBUGGER_PROC}"
19+
"name": "GAME_DEBUGGER_CMDLINE",
20+
"value": "${env.GAME_DEBUGGER_CMDLINE}"
2521
}
2622
]
2723
}

.vs/launch_with_automation.ps1

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
param ($Action, $VSProcessId, $ChildProcessId)
2+
3+
# See end of file
4+
5+
Add-Type -Language Csharp -TypeDefinition @"
6+
using System;
7+
using System.Runtime.InteropServices;
8+
using System.Runtime.InteropServices.ComTypes;
9+
10+
namespace VSAutomationHelper
11+
{
12+
public class Main
13+
{
14+
[DllImport("ole32.dll")]
15+
private static extern int CreateBindCtx(uint reserved, out IBindCtx ppbc);
16+
17+
public static object FindDTEInstanceByProcessId(int processId)
18+
{
19+
IBindCtx bindContext = null;
20+
IRunningObjectTable objectTable = null;
21+
IEnumMoniker enumMonikers = null;
22+
23+
try
24+
{
25+
Marshal.ThrowExceptionForHR(CreateBindCtx(0, out bindContext));
26+
bindContext.GetRunningObjectTable(out objectTable);
27+
objectTable.EnumRunning(out enumMonikers);
28+
29+
IMoniker[] moniker = new IMoniker[1];
30+
IntPtr numberFetched = IntPtr.Zero;
31+
32+
while (enumMonikers.Next(1, moniker, numberFetched) == 0)
33+
{
34+
var runningObjectMoniker = moniker[0];
35+
36+
if (runningObjectMoniker == null)
37+
continue;
38+
39+
try
40+
{
41+
string name = null;
42+
runningObjectMoniker.GetDisplayName(bindContext, null, out name);
43+
44+
if (name.StartsWith("!VisualStudio.DTE.")) // Consistent from 2010~2022
45+
{
46+
int idIndex = name.IndexOf(':');
47+
48+
if (idIndex != -1 && int.Parse(name.Substring(idIndex + 1)) == processId)
49+
{
50+
object runningObject = null;
51+
Marshal.ThrowExceptionForHR(objectTable.GetObject(runningObjectMoniker, out runningObject));
52+
53+
return runningObject;
54+
}
55+
}
56+
}
57+
catch (UnauthorizedAccessException)
58+
{
59+
// Inaccessible due to permissions
60+
}
61+
}
62+
}
63+
finally
64+
{
65+
if (enumMonikers != null)
66+
Marshal.ReleaseComObject(enumMonikers);
67+
68+
if (objectTable != null)
69+
Marshal.ReleaseComObject(objectTable);
70+
71+
if (bindContext != null)
72+
Marshal.ReleaseComObject(bindContext);
73+
}
74+
75+
return null;
76+
}
77+
}
78+
}
79+
"@
80+
81+
#
82+
# .\script.ps1 Launch Callback from Visual Studio to launch the game
83+
# .\script.ps1 Attach Callback from the game to attach Visual Studio's debugger
84+
#
85+
# GAME_DEBUGGER_CMDLINE Env var holding a command line expression that launches the game
86+
# GAME_DEBUGGER_REQUEST Env var holding a command line expression that runs when the game is ready for debugger attach
87+
# GAME_DEBUGGER_PROC Env var holding kernel32.dll's CreateProcessA import function string to prevent AV false positives
88+
#
89+
if ($Action -eq 'Launch') {
90+
# Visual Studio doesn't necessarily launch the debugee. Keep chasing the parent PID until we find it.
91+
$parentVSProcessId = $PID
92+
93+
while ($true) {
94+
$wmiProcess = Get-WmiObject Win32_Process -Filter ProcessId=$parentVSProcessId
95+
96+
if ($wmiProcess.ProcessName.ToLower() -eq "devenv.exe") {
97+
break;
98+
}
99+
100+
$parentVSProcessId = $wmiProcess.ParentProcessId
101+
}
102+
103+
# Start process with special environment vars set
104+
$localPSPath = (Get-Process -Id $PID | Get-Item).FullName
105+
$localScriptPath = $MyInvocation.MyCommand.Path
106+
107+
$env:GAME_DEBUGGER_REQUEST = '"' + $localPSPath + '" -ExecutionPolicy Bypass -File "' + $localScriptPath + '" Attach ' + $parentVSProcessId + ' '
108+
$env:GAME_DEBUGGER_PROC = 'kernel32!CreateProcessA'
109+
110+
& $env:GAME_DEBUGGER_CMDLINE
111+
}
112+
elseif ($Action -eq 'Attach') {
113+
# Callback from game DLL side. Tell Visual Studio to attach to its process.
114+
$automationDTE = [VSAutomationHelper.Main]::FindDTEInstanceByProcessId($VSProcessId)
115+
116+
foreach ($process in $automationDTE.Debugger.LocalProcesses) {
117+
if ($process.ProcessID -eq $ChildProcessId) {
118+
$process.Attach()
119+
break;
120+
}
121+
}
122+
}

CMakeUserEnvVars.json

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,8 @@
55
"name": "config-envvars",
66
"hidden": true,
77
"environment": {
8-
"___GAME_ROOT_DIRECTORY": "C:/Program Files (x86)/Steam/steamapps/common/Cyberpunk 2077/bin/x64",
9-
"VS_DEBUGGER_REQUEST": "vsjitdebugger.exe -p ",
10-
"VS_DEBUGGER_PROC": "kernel32!CreateProcessA"
8+
"___GAME_ROOT_DIRECTORY": "C:/Program Files (x86)/Steam/steamapps/common/Horizon Forbidden West Complete Edition",
9+
"GAME_DEBUGGER_CMDLINE": "$env{GAME_ROOT_DIRECTORY}/HorizonForbiddenWest.exe"
1110
}
1211
}
1312
]

source/wrapper_dt/dllmain.cpp

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,20 +5,23 @@ BOOL WINAPI RawDllMain(HINSTANCE hInstDLL, DWORD fdwReason, LPVOID lpvReserved)
55
{
66
if (fdwReason == DLL_PROCESS_ATTACH)
77
{
8-
// Start vsjitdebugger.exe if a debugger isn't already attached. VS_DEBUGGER_REQUEST determines the
9-
// command line and VS_DEBUGGER_PROC is used to hide the CreateProcessA IAT entry.
10-
if (char cmd[256] = {}, proc[256] = {}; !IsDebuggerPresent() &&
11-
GetEnvironmentVariableA("VS_DEBUGGER_REQUEST", cmd, ARRAYSIZE(cmd)) > 0 &&
12-
GetEnvironmentVariableA("VS_DEBUGGER_PROC", proc, ARRAYSIZE(proc)) > 0)
8+
// Start vsjitdebugger.exe if a debugger isn't already attached. GAME_DEBUGGER_REQUEST determines the
9+
// command line and GAME_DEBUGGER_PROC is used to hide the CreateProcessA IAT entry.
10+
if (char cmd[512] = {}, proc[512] = {}; !IsDebuggerPresent() &&
11+
GetEnvironmentVariableA("GAME_DEBUGGER_REQUEST", cmd, ARRAYSIZE(cmd)) > 0 &&
12+
GetEnvironmentVariableA("GAME_DEBUGGER_PROC", proc, ARRAYSIZE(proc)) > 0)
1313
{
1414
std::to_chars(cmd + strlen(cmd), std::end(cmd), GetCurrentProcessId());
1515
auto moduleName = proc;
1616
auto importName = strchr(proc, '!') + 1;
1717
importName[-1] = '\0';
1818

1919
PROCESS_INFORMATION pi = {};
20+
2021
STARTUPINFOA si = {};
2122
si.cb = sizeof(si);
23+
si.dwFlags = STARTF_USESHOWWINDOW;
24+
si.wShowWindow = SW_HIDE;
2225

2326
auto c = reinterpret_cast<decltype(&CreateProcessA)>(GetProcAddress(GetModuleHandleA(moduleName), importName));
2427
c(nullptr, cmd, nullptr, nullptr, false, 0, nullptr, nullptr, &si, &pi);

0 commit comments

Comments
 (0)