From 9fe40ec699fe415f683df99252904a365a842d37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=88=B0=E9=98=9F=E7=9A=84=E5=81=B6=E5=83=8F-=E5=B2=9B?= =?UTF-8?q?=E9=A3=8E=E9=85=B1?= Date: Tue, 21 Apr 2026 13:56:32 +0800 Subject: [PATCH 1/4] Refactor CLI argument parsing to use `--` delimiter MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace `--args="..."` flag with standard `--` delimiter for passing arguments to the target executable. This follows conventional CLI patterns where arguments after `--` are forwarded directly to the spawned process rather than being parsed by Syringe. Changes: - Update usage message to reflect new `--` syntax - Modify `parse_command_line` to recognize `--` as the delimiter - Append all arguments after `--` to the game arguments string - Remove deprecated `--args=` flag handling Signed-off-by: 舰队的偶像-岛风酱 --- Main.cpp | 2 +- README.md | 4 ++-- Support.h | 26 +++++++++++++++++--------- 3 files changed, 20 insertions(+), 12 deletions(-) diff --git a/Main.cpp b/Main.cpp index 27bc1dc..740c364 100644 --- a/Main.cpp +++ b/Main.cpp @@ -84,7 +84,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..7bc723e 100644 --- a/Support.h +++ b/Support.h @@ -28,8 +28,6 @@ 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; @@ -45,6 +43,8 @@ inline auto parse_command_line(const std::vector& arguments) // First non-flag argument becomes executable name bool exe_found = false; + bool exe_arguments = false; + for (const auto& arg : arguments) { // executable name: first argument not starting with '-' @@ -55,22 +55,30 @@ inline auto parse_command_line(const std::vector& arguments) continue; } - // game arguments: --args="blob" - if (arg.starts_with(ARGS_FLAG)) + if (arg == "--") { - // extract after --args= - std::string blob = arg.substr(ARGS_FLAG.size()); - ret.game_arguments = blob; + exe_arguments = true; continue; } - // Syringe arguments - ret.syringe_arguments.push_back(arg); + if (exe_arguments) + { + // game arguments + ret.game_arguments += " "; + ret.game_arguments += arg; + } + else + { + // Syringe arguments + ret.syringe_arguments.push_back(arg); + } } if (!exe_found || ret.executable_name.empty()) throw invalid_command_arguments{}; + ret.game_arguments = ret.game_arguments.substr(1); + return ret; } From 075782de784dc9ad0984c6762ec0867adb0c53d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=88=B0=E9=98=9F=E7=9A=84=E5=81=B6=E5=83=8F-=E5=B2=9B?= =?UTF-8?q?=E9=A3=8E=E9=85=B1?= Date: Tue, 21 Apr 2026 13:56:33 +0800 Subject: [PATCH 2/4] Add support for quoted arguments in CLI parsing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Handle quoted strings in command-line arguments to properly parse paths with spaces. Both executable names and injection DLL paths can now be wrapped in double quotes. Changes: - Strip surrounding quotes from executable name if present - Parse `-i="path with spaces.dll"` format by removing quotes - Preserve argument integrity when paths contain spaces Signed-off-by: 舰队的偶像-岛风酱 --- Support.h | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/Support.h b/Support.h index 7bc723e..c7e4dc7 100644 --- a/Support.h +++ b/Support.h @@ -44,14 +44,18 @@ inline auto parse_command_line(const std::vector& arguments) bool exe_found = false; bool exe_arguments = false; - + for (const auto& arg : arguments) { // executable name: first argument not starting with '-' if (!exe_found && !arg.starts_with("-")) { exe_found = true; - ret.executable_name = arg; + if (arg.starts_with('"') && arg.ends_with('"')) + ret.executable_name = arg.substr(1, arg.length() - 2); + else + ret.executable_name = arg; + continue; } @@ -70,7 +74,10 @@ inline auto parse_command_line(const std::vector& arguments) else { // 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); } } From 61870d0f0048d1e96ff4238bfcb4c346f0c04469 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=88=B0=E9=98=9F=E7=9A=84=E5=81=B6=E5=83=8F-=E5=B2=9B?= =?UTF-8?q?=E9=A3=8E=E9=85=B1?= Date: Tue, 21 Apr 2026 13:56:33 +0800 Subject: [PATCH 3/4] Implement raw command-line parsing for `--` delimiter MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Parse the raw command line string to properly detect the `--` separator before argument splitting. This ensures that quoted arguments containing the `--` delimiter are handled correctly and not mistakenly treated as separators. The previous approach used `CommandLineToArgvW` which splits arguments before parsing, making it impossible to distinguish between a quoted `--` in an argument and the actual separator. Changes: - Add `FindSeparator()` to locate `--` in raw command line with proper quote and escape handling - Modify `GetArguments()` to split raw command line at separator - Remove `--` handling from `parse_command_line()` since it's now done earlier - Return separate `syringe_args` and `game_args` from `GetArguments()` Signed-off-by: 舰队的偶像-岛风酱 --- Main.cpp | 75 ++++++++++++++++++++++++++++++++++++++++++++++++------- Support.h | 27 +++----------------- 2 files changed, 70 insertions(+), 32 deletions(-) diff --git a/Main.cpp b/Main.cpp index 740c364..c6e5153 100644 --- a/Main.cpp +++ b/Main.cpp @@ -8,11 +8,61 @@ #include #include -std::vector GetArguments() +struct Arguments { + std::vector syringe_args; + std::string game_args; +}; + +size_t FindSeparator(const std::wstring& cmdLine) +{ + bool inQuotes = false; + size_t len = cmdLine.length(); + for (size_t i = 0; i < len; ++i) + { + if (cmdLine[i] == L'\\' && i + 1 < len && cmdLine[i + 1] == L'"') + { + i += 1; + continue; + } + + if (cmdLine[i] == L'"') + { + inQuotes = !inQuotes; + continue; + } + + if (!inQuotes && cmdLine[i] == L' ') + { + if (i + 3 < len && cmdLine[i + 1] == L'-' && cmdLine[i + 2] == L'-' && cmdLine[i + 3] == L' ') + { + return i; + } + } + } + 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 +75,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 +96,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 +119,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 +141,7 @@ int Run(const std::vector& arguments) { MessageBoxA( nullptr, "Syringe cannot be run like that.\n\n" - "Usage:\nSyringe.exe [-i= ...] [-- ]", + "Usage:\nSyringe.exe [-i= ...] [-- ]", VersionString, MB_OK | MB_ICONINFORMATION); Log::WriteLine( diff --git a/Support.h b/Support.h index c7e4dc7..56b36dc 100644 --- a/Support.h +++ b/Support.h @@ -32,7 +32,6 @@ inline auto parse_command_line(const std::vector& arguments) { std::vector syringe_arguments; std::string executable_name; - std::string game_arguments; }; if (arguments.empty()) @@ -43,8 +42,6 @@ inline auto parse_command_line(const std::vector& arguments) // First non-flag argument becomes executable name bool exe_found = false; - bool exe_arguments = false; - for (const auto& arg : arguments) { // executable name: first argument not starting with '-' @@ -59,32 +56,16 @@ inline auto parse_command_line(const std::vector& arguments) continue; } - if (arg == "--") - { - exe_arguments = true; - continue; - } - - if (exe_arguments) - { - // game arguments - ret.game_arguments += " "; - ret.game_arguments += arg; - } + // Syringe arguments + if (arg.starts_with("-i=\"") && arg.ends_with('"')) + ret.syringe_arguments.push_back("-i=" + arg.substr(4, arg.length() - 5)); else - { - // Syringe arguments - 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); - } + ret.syringe_arguments.push_back(arg); } if (!exe_found || ret.executable_name.empty()) throw invalid_command_arguments{}; - ret.game_arguments = ret.game_arguments.substr(1); return ret; } From 56ce8ddb19c5bfd526777e7ceccde3627a5c3769 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=88=B0=E9=98=9F=E7=9A=84=E5=81=B6=E5=83=8F-=E5=B2=9B?= =?UTF-8?q?=E9=A3=8E=E9=85=B1!?= Date: Wed, 22 Apr 2026 08:19:37 +0800 Subject: [PATCH 4/4] fix: handle windows quote rules in separator parsing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: 舰队的偶像-岛风酱! --- Main.cpp | 64 ++++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 51 insertions(+), 13 deletions(-) diff --git a/Main.cpp b/Main.cpp index c6e5153..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" @@ -14,32 +14,70 @@ struct Arguments 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; - size_t len = cmdLine.length(); + const size_t len = cmdLine.length(); + for (size_t i = 0; i < len; ++i) { - if (cmdLine[i] == L'\\' && i + 1 < len && cmdLine[i + 1] == L'"') - { - i += 1; - continue; - } - + // ------------------------------------------------------------ + // Handle quote with Windows backslash rules + // ------------------------------------------------------------ if (cmdLine[i] == L'"') { - inQuotes = !inQuotes; + // 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; } - - if (!inQuotes && cmdLine[i] == L' ') + + // ------------------------------------------------------------ + // Only check for separator outside quotes + // ------------------------------------------------------------ + if (!inQuotes && iswspace(cmdLine[i])) { - if (i + 3 < len && cmdLine[i + 1] == L'-' && cmdLine[i + 2] == L'-' && cmdLine[i + 3] == L' ') + // Skip whitespace to find next token start + size_t j = i; + while (j < len && iswspace(cmdLine[j])) { - return i; + 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; }