diff --git a/Main.cpp b/Main.cpp index 27bc1dc..0703b1c 100644 --- a/Main.cpp +++ b/Main.cpp @@ -1,4 +1,4 @@ -#include "Log.h" +#include "Log.h" #include "SyringeDebugger.h" #include "Support.h" #include "resource.h" @@ -8,11 +8,99 @@ #include #include -std::vector GetArguments() +struct Arguments { + std::vector syringe_args; + std::string game_args; +}; + +// Find position of the separator token "--" (token-aware). +// Returns the index of the whitespace BEFORE "--", or npos if not found. +// +// This implementation: +// - Tracks quotes using correct Windows rules (backslash counting) +// - Ensures "--" is a standalone token +// - Avoids false matches inside quotes +size_t FindSeparator(const std::wstring& cmdLine) +{ + bool inQuotes = false; + const size_t len = cmdLine.length(); + + for (size_t i = 0; i < len; ++i) + { + // ------------------------------------------------------------ + // Handle quote with Windows backslash rules + // ------------------------------------------------------------ + if (cmdLine[i] == L'"') + { + // Count consecutive backslashes before the quote + size_t backslashCount = 0; + for (size_t j = i; j > 0 && cmdLine[j - 1] == L'\\'; --j) + { + backslashCount++; + } + + // Even => delimiter, Odd => escaped + if ((backslashCount % 2) == 0) + { + inQuotes = !inQuotes; + } + + continue; + } + + // ------------------------------------------------------------ + // Only check for separator outside quotes + // ------------------------------------------------------------ + if (!inQuotes && iswspace(cmdLine[i])) + { + // Skip whitespace to find next token start + size_t j = i; + while (j < len && iswspace(cmdLine[j])) + { + j++; + } + + // Check for "--" + if (j + 1 < len && + cmdLine[j] == L'-' && + cmdLine[j + 1] == L'-') + { + size_t k = j + 2; + + // Ensure it's a standalone token: + // must end or be followed by whitespace + if (k == len || iswspace(cmdLine[k])) + { + return i; // position before separator + } + } + } + } + + return std::wstring::npos; +} + +Arguments GetArguments() +{ + std::wstring wszSyringeArgs; + std::wstring wszGameArgs; + std::wstring lpCmdLine = GetCommandLineW(); + auto separator = FindSeparator(lpCmdLine); + if (separator != std::wstring::npos) + { + wszSyringeArgs = lpCmdLine.substr(0, separator); + wszGameArgs = lpCmdLine.substr(separator + 4); + } + else + { + wszSyringeArgs = lpCmdLine; + wszGameArgs = L""; + } + // Get argc, argv in wide chars int argc = 0; - LPWSTR* argvW = CommandLineToArgvW(GetCommandLineW(), &argc); + LPWSTR* argvW = CommandLineToArgvW(wszSyringeArgs.c_str(), &argc); // Convert to UTF-8. Skip the first argument as it contains the path to Syringe itself std::vector argv(argc - 1); @@ -25,10 +113,17 @@ std::vector GetArguments() LocalFree(argvW); - return argv; + int len = WideCharToMultiByte(CP_UTF8, 0, wszGameArgs.c_str(), -1, nullptr, 0, nullptr, nullptr); + std::string gameArgs = std::string(len - 1, '\0'); + WideCharToMultiByte(CP_UTF8, 0, wszGameArgs.c_str(), -1, gameArgs.data(), len, nullptr, nullptr); + + return { + argv, + gameArgs, + }; } -int Run(const std::vector& arguments) +int Run(const Arguments& arguments) { constexpr auto const VersionString = "SyringeEx " SYRINGEEX_VER_TEXT ", based on Syringe 0.7.2.0"; @@ -39,14 +134,14 @@ int Run(const std::vector& arguments) Log::WriteLine(VersionString); Log::WriteLine("==============="); Log::WriteLine(); - Log::WriteLine("WinMain: arguments = \"%.*s\"", printable(arguments)); + Log::WriteLine("WinMain: arguments = \"%.*s\"", printable(arguments.syringe_args)); auto failure = "Could not load executable."; auto exit_code = ERROR_ERRORS_ENCOUNTERED; try { - auto const command = parse_command_line(arguments); + auto const command = parse_command_line(arguments.syringe_args); Log::WriteLine( "WinMain: Trying to load executable file \"%.*s\"...", @@ -62,10 +157,10 @@ int Run(const std::vector& arguments) Log::WriteLine( "WinMain: SyringeDebugger::Run(\"%.*s\");", - printable(command.game_arguments)); + printable(arguments.game_args)); Log::WriteLine(); - Debugger.Run(command.game_arguments); + Debugger.Run(arguments.game_args); Log::WriteLine("WinMain: SyringeDebugger::Run finished."); Log::WriteLine("WinMain: Exiting on success."); return ERROR_SUCCESS; @@ -84,7 +179,7 @@ int Run(const std::vector& arguments) { MessageBoxA( nullptr, "Syringe cannot be run like that.\n\n" - "Usage:\nSyringe.exe [-i= ...] [--args=\"\"]", + "Usage:\nSyringe.exe [-i= ...] [-- ]", VersionString, MB_OK | MB_ICONINFORMATION); Log::WriteLine( diff --git a/README.md b/README.md index efaa0a7..a0f234f 100644 --- a/README.md +++ b/README.md @@ -30,10 +30,10 @@ syringe.exe game.exe ### Passing Arguments to the Target Executable -Use `--args="..."` to provide arguments for the launched process: +Use `--` as a delimiter to pass arguments to the launched process. All arguments after `--` will be forwarded directly to the target executable: ``` -syringe.exe game.exe --args="-CD. -SPAWN" +syringe.exe game.exe -- -CD. -SPAWN ``` ### DLL Injection Behavior diff --git a/Support.h b/Support.h index e3b699b..56b36dc 100644 --- a/Support.h +++ b/Support.h @@ -28,13 +28,10 @@ inline auto trim(std::string_view string) noexcept inline auto parse_command_line(const std::vector& arguments) { - static constexpr std::string_view ARGS_FLAG = "--args="; - struct argument_set { std::vector syringe_arguments; std::string executable_name; - std::string game_arguments; }; if (arguments.empty()) @@ -51,26 +48,25 @@ inline auto parse_command_line(const std::vector& arguments) if (!exe_found && !arg.starts_with("-")) { exe_found = true; - ret.executable_name = arg; - continue; - } + if (arg.starts_with('"') && arg.ends_with('"')) + ret.executable_name = arg.substr(1, arg.length() - 2); + else + ret.executable_name = arg; - // game arguments: --args="blob" - if (arg.starts_with(ARGS_FLAG)) - { - // extract after --args= - std::string blob = arg.substr(ARGS_FLAG.size()); - ret.game_arguments = blob; continue; } // Syringe arguments - ret.syringe_arguments.push_back(arg); + if (arg.starts_with("-i=\"") && arg.ends_with('"')) + ret.syringe_arguments.push_back("-i=" + arg.substr(4, arg.length() - 5)); + else + ret.syringe_arguments.push_back(arg); } if (!exe_found || ret.executable_name.empty()) throw invalid_command_arguments{}; + return ret; }