Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ jobs:
shell: cmd
run: |
cl /EHsc /std:c++20 /permissive- /I. /DUNICODE /D_UNICODE /GS /sdl ^
cli_args_debugger.cpp log_manager.cpp seh_wrapper.cpp qrcodegen.cpp ^
cli_args_debugger.cpp log_manager.cpp path_info.cpp seh_wrapper.cpp qrcodegen.cpp ^
/Fe:build\cloud-streaming-args-debugger.exe ^
/Fo:obj\ ^
/link d3d11.lib d3dcompiler.lib d2d1.lib dwrite.lib ole32.lib avrt.lib user32.lib shell32.lib gdi32.lib propsys.lib winmm.lib psapi.lib
Expand Down
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ add_executable(cloud-streaming-args-debugger
WIN32 # Specify that the application uses WinMain instead of main
cli_args_debugger.cpp
log_manager.cpp
path_info.cpp
seh_wrapper.cpp
qrcodegen.cpp # Include QR code generator
)
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ Check out a preview of the application:

# Compile with MSVC
cl /EHsc /std:c++20 /permissive- /I. /DUNICODE /D_UNICODE ^
cli_args_debugger.cpp log_manager.cpp seh_wrapper.cpp qrcodegen.cpp ^
cli_args_debugger.cpp log_manager.cpp path_info.cpp seh_wrapper.cpp qrcodegen.cpp ^
/Fe:build/ArgumentDebugger.exe ^
/Fo:build/ ^
/link d3d11.lib d3dcompiler.lib d2d1.lib dwrite.lib ole32.lib avrt.lib user32.lib shell32.lib gdi32.lib propsys.lib
Expand Down
126 changes: 4 additions & 122 deletions cli_args_debugger.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,9 @@ using qrcodegen::QrCode;
// seh_wrapper) can link against the same instance.
#include "log_manager.hpp"

// Path/env inspection (executable path, OS version, Wine/Proton, etc.)
#include "path_info.hpp"

// Use Microsoft::WRL::ComPtr for COM object management
using Microsoft::WRL::ComPtr;

Expand Down Expand Up @@ -1738,128 +1741,7 @@ void ArgumentDebuggerWindow::ReadData()
// Helper method to calculate and cache path information once
void ArgumentDebuggerWindow::CalculatePathInfo()
{
cached_path_items_.clear();

// Display full and relative path where the application is running
wchar_t exePath[MAX_PATH] = L"\0";
GetModuleFileNameW(nullptr, exePath, MAX_PATH);
std::wstring fullPath = exePath;

// Get current working directory for relative path
wchar_t currentDir[MAX_PATH] = L"\0";
GetCurrentDirectoryW(MAX_PATH, currentDir);
std::wstring currentDirStr = currentDir;

// Extract just the executable name
std::wstring exeName = fullPath;
size_t lastSlash = exeName.find_last_of(L"\\");
if (lastSlash != std::wstring::npos)
{
exeName = exeName.substr(lastSlash + 1);
}

// Extract directory from full path
std::wstring exeDir = fullPath;
if (lastSlash != std::wstring::npos)
{
exeDir = exeDir.substr(0, lastSlash);
}

// Get TEMP directory
wchar_t tempPath[MAX_PATH] = L"\0";
GetTempPathW(MAX_PATH, tempPath);
std::wstring tempDir = tempPath;
if (!tempDir.empty() && tempDir.back() == L'\\')
{
tempDir.pop_back(); // Remove trailing slash
}

// Get Windows directory
wchar_t winPath[MAX_PATH] = L"\0";
GetWindowsDirectoryW(winPath, MAX_PATH);
std::wstring winDir = winPath;

// Get System directory
wchar_t sysPath[MAX_PATH] = L"\0";
GetSystemDirectoryW(sysPath, MAX_PATH);
std::wstring sysDir = sysPath;

// Get command line
std::wstring cmdLine = GetCommandLineW();

// Get OS version
std::wstring osVersion = L"Unknown";
typedef NTSTATUS(WINAPI * RtlGetVersionPtr)(PRTL_OSVERSIONINFOW);
HMODULE hNtdll = GetModuleHandleW(L"ntdll.dll");
if (hNtdll)
{
RtlGetVersionPtr RtlGetVersion = (RtlGetVersionPtr)GetProcAddress(hNtdll, "RtlGetVersion");
if (RtlGetVersion)
{
RTL_OSVERSIONINFOW osvi = {0};
osvi.dwOSVersionInfoSize = sizeof(osvi);
if (RtlGetVersion(&osvi) == 0)
{
osVersion = L"Windows " + std::to_wstring(osvi.dwMajorVersion) + L"." +
std::to_wstring(osvi.dwMinorVersion) + L" (Build " + std::to_wstring(osvi.dwBuildNumber) +
L")";
}
}
}

// Check for Wine/Proton (simplified version)
std::wstring wineVersion = L"Not detected";
HMODULE hNtdllCheck = GetModuleHandleW(L"ntdll.dll");
if (hNtdllCheck && GetProcAddress(hNtdllCheck, "wine_get_version"))
{
typedef const char* (*wine_get_version_func)(void);
wine_get_version_func wine_get_version =
(wine_get_version_func)GetProcAddress(hNtdllCheck, "wine_get_version");
if (wine_get_version)
{
const char* version = wine_get_version();
if (version)
{
int size_needed = MultiByteToWideChar(CP_UTF8, 0, version, -1, NULL, 0);
std::wstring wversion(size_needed - 1, 0);
MultiByteToWideChar(CP_UTF8, 0, version, -1, &wversion[0], size_needed);
wineVersion = L"Wine " + wversion;

// Quick Proton check
wchar_t envBuf[1024] = {0};
if (GetEnvironmentVariableW(L"PROTON_VERSION", envBuf, 1024) > 0)
{
wineVersion = L"Proton " + std::wstring(envBuf) + L" (Wine " + wversion + L")";
}
}
}
}

// Get save path
std::wstring savePath = L"Not available";
PWSTR appdata_path = nullptr;
HRESULT hr = SHGetKnownFolderPath(FOLDERID_RoamingAppData, 0, nullptr, &appdata_path);
if (SUCCEEDED(hr))
{
savePath = appdata_path;
CoTaskMemFree(appdata_path);
savePath += L"\\CloudStreamingArgsDebugger\\saved_data.txt";
}

// Store all path information
cached_path_items_ = {
{L"OS Version: ", osVersion},
{L"Wine/Proton: ", wineVersion},
{L"Executable name: ", exeName},
{L"Full path: ", fullPath},
{L"Executable directory: ", exeDir},
{L"Current directory: ", currentDirStr},
{L"Command line: ", cmdLine},
{L"Save file path: ", savePath},
{L"TEMP directory: ", tempDir},
{L"Windows directory: ", winDir},
{L"System directory: ", sysDir}
};
cached_path_items_ = path_info::Collect();
}

void ArgumentDebuggerWindow::PlayTelephoneBeeps() // Name kept for compatibility
Expand Down
191 changes: 191 additions & 0 deletions path_info.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
#ifndef UNICODE
#define UNICODE
#define _UNICODE
#endif

// clang-format off
// <windows.h> must come first; <knownfolders.h> and <winternl.h> rely on
// EXTERN_C / GUID being already defined.
#include <windows.h>
#include <knownfolders.h>
#include <shlobj.h>
#include <winternl.h>
// clang-format on

#include "path_info.hpp"

#pragma comment(lib, "shell32")
#pragma comment(lib, "ole32")

namespace
{

// Generic retry pattern for Win32 "fill-a-buffer" APIs that either write
// `len < size` on success or report `ERROR_INSUFFICIENT_BUFFER` when the
// buffer is too small. Avoids MAX_PATH truncation silently.
template <typename Fill> std::wstring FillWithGrowingBuffer(Fill fill)
{
std::wstring buf;
buf.resize(MAX_PATH);
for (int attempt = 0; attempt < 6; ++attempt)
{
SetLastError(ERROR_SUCCESS);
// `&buf[0]` yields `wchar_t*` in both C++17 and C++20; `buf.data()`
// would only do so in C++20. Tests compile with C++17.
DWORD len = fill(&buf[0], static_cast<DWORD>(buf.size()));
if (len == 0)
return {};
if (len < buf.size())
{
buf.resize(len);
return buf;
}
if (GetLastError() != ERROR_INSUFFICIENT_BUFFER)
{
buf.resize(len);
return buf;
}
buf.resize(buf.size() * 2);
}
return {};
}

// For APIs whose first form takes (size=0, buf=nullptr) and returns the
// required character count including the terminator.
template <typename Query> std::wstring FillWithQueriedSize(Query query)
{
DWORD needed = query(0, nullptr);
if (!needed)
return {};
std::wstring buf(needed, L'\0');
DWORD got = query(needed, &buf[0]);
if (got == 0 || got > needed)
return {};
buf.resize(got);
return buf;
}

} // namespace

namespace path_info
{

std::wstring ExecutablePath()
{
return FillWithGrowingBuffer([](wchar_t* p, DWORD n) { return GetModuleFileNameW(nullptr, p, n); });
}

std::wstring CurrentWorkingDirectory()
{
return FillWithQueriedSize([](DWORD n, wchar_t* p) { return GetCurrentDirectoryW(n, p); });
}

std::wstring TempDirectory()
{
std::wstring path = FillWithQueriedSize([](DWORD n, wchar_t* p) { return GetTempPathW(n, p); });
if (!path.empty() && path.back() == L'\\')
path.pop_back();
return path;
}

std::wstring WindowsDirectory()
{
return FillWithQueriedSize([](DWORD n, wchar_t* p) { return ::GetWindowsDirectoryW(p, n); });
}

std::wstring SystemDirectory()
{
return FillWithQueriedSize([](DWORD n, wchar_t* p) { return ::GetSystemDirectoryW(p, n); });
}

std::wstring OsVersionString()
{
using RtlGetVersionPtr = NTSTATUS(WINAPI*)(PRTL_OSVERSIONINFOW);
// GetModuleHandleW returns a cached handle — do not call FreeLibrary on it.
HMODULE hNtdll = GetModuleHandleW(L"ntdll.dll");
if (!hNtdll)
return L"Unknown";

auto rtlGetVersion = reinterpret_cast<RtlGetVersionPtr>(GetProcAddress(hNtdll, "RtlGetVersion"));
if (!rtlGetVersion)
return L"Unknown";

RTL_OSVERSIONINFOW osvi{};
osvi.dwOSVersionInfoSize = sizeof(osvi);
if (rtlGetVersion(&osvi) != 0)
return L"Unknown";

return L"Windows " + std::to_wstring(osvi.dwMajorVersion) + L"." + std::to_wstring(osvi.dwMinorVersion) +
L" (Build " + std::to_wstring(osvi.dwBuildNumber) + L")";
}

std::wstring WineOrProtonVersion()
{
using wine_get_version_func = const char* (*)(void);
HMODULE hNtdll = GetModuleHandleW(L"ntdll.dll");
if (!hNtdll)
return L"Not detected";

auto wineGetVersion = reinterpret_cast<wine_get_version_func>(GetProcAddress(hNtdll, "wine_get_version"));
if (!wineGetVersion)
return L"Not detected";

const char* version = wineGetVersion();
if (!version)
return L"Not detected";

int size_needed = MultiByteToWideChar(CP_UTF8, 0, version, -1, nullptr, 0);
if (size_needed <= 1)
return L"Not detected";

std::wstring wversion(size_needed - 1, L'\0');
MultiByteToWideChar(CP_UTF8, 0, version, -1, &wversion[0], size_needed);

wchar_t protonBuf[1024]{};
if (GetEnvironmentVariableW(L"PROTON_VERSION", protonBuf, 1024) > 0)
return L"Proton " + std::wstring(protonBuf) + L" (Wine " + wversion + L")";
return L"Wine " + wversion;
}

std::wstring SaveFilePath()
{
PWSTR appdata_path = nullptr;
HRESULT hr = SHGetKnownFolderPath(FOLDERID_RoamingAppData, 0, nullptr, &appdata_path);
if (FAILED(hr))
return L"Not available";

std::wstring path = appdata_path;
CoTaskMemFree(appdata_path);
path += L"\\CloudStreamingArgsDebugger\\saved_data.txt";
return path;
}

std::vector<PathItem> Collect()
{
const std::wstring fullPath = ExecutablePath();

std::wstring exeName = fullPath;
std::wstring exeDir = fullPath;
const size_t lastSlash = fullPath.find_last_of(L'\\');
if (lastSlash != std::wstring::npos)
{
exeName.erase(0, lastSlash + 1);
exeDir.erase(lastSlash);
}

return {
{L"OS Version: ", OsVersionString()},
{L"Wine/Proton: ", WineOrProtonVersion()},
{L"Executable name: ", exeName},
{L"Full path: ", fullPath},
{L"Executable directory: ", exeDir},
{L"Current directory: ", CurrentWorkingDirectory()},
{L"Command line: ", GetCommandLineW() ? std::wstring(GetCommandLineW()) : std::wstring()},
{L"Save file path: ", SaveFilePath()},
{L"TEMP directory: ", TempDirectory()},
{L"Windows directory: ", WindowsDirectory()},
{L"System directory: ", SystemDirectory()},
};
}

} // namespace path_info
32 changes: 32 additions & 0 deletions path_info.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#pragma once

#include <string>
#include <utility>
#include <vector>

// `label` keeps a trailing space so the UI can draw the value immediately
// after it. Using a plain pair keeps this module drop-in compatible with the
// previous `cached_path_items_` field, which already had this type.
using PathItem = std::pair<std::wstring, std::wstring>;

// The namespace guards against Windows.h macros such as `GetWindowsDirectory`
// and `GetSystemDirectory` rewriting these names into their W/A variants.
namespace path_info
{

// Collect executable path, working dir, OS version, Wine/Proton marker, TEMP,
// Windows, System and save-file paths. All Win32 calls are sized from their
// own return values, so paths longer than MAX_PATH are not silently truncated.
std::vector<PathItem> Collect();

// Helpers exposed for unit tests and for callers that need only one value.
std::wstring ExecutablePath();
std::wstring CurrentWorkingDirectory();
std::wstring TempDirectory();
std::wstring WindowsDirectory();
std::wstring SystemDirectory();
std::wstring OsVersionString();
std::wstring WineOrProtonVersion();
std::wstring SaveFilePath();

} // namespace path_info
1 change: 1 addition & 0 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ set(PARENT_SOURCES
../qrcodegen.cpp
../cli_args_debugger.cpp
../log_manager.cpp
../path_info.cpp
../seh_wrapper.cpp
)

Expand Down
Loading
Loading