Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improvements to console hook related functions based on issue #653 #669

Merged
merged 12 commits into from
Oct 10, 2024

Conversation

igromanru
Copy link
Contributor

@igromanru igromanru commented Sep 28, 2024

Description

  • Added C++ String function explode_by_occurrence_with_quotes (feel free to suggest a better name)
    • The function splits a string into a vector of strings using a delimiter, but it keeps text between two double quotation marks together
    • It also supports backslash as an escape character \, which allows the use of double quotation marks in the text ( \")
  • Replaced explode_by_occurrence usage with explode_by_occurrence_with_quotes in functions:
    • Unreal::Hook::RegisterProcessConsoleExecGlobalPreCallback
    • Unreal::Hook::RegisterProcessConsoleExecGlobalPostCallback
    • Unreal::Hook::RegisterProcessConsoleExecCallback (both for RegisterConsoleCommandHandler and RegisterConsoleCommandGlobalHandler)
  • Fixed docs of RegisterConsoleCommandGlobalHandler, RegisterConsoleCommandHandler, RegisterProcessConsoleExecPreHook and RegisterProcessConsoleExecPostHook
  • Added better examples code to documentation
  • Breaking: ProcessConsoleExec callbacks now send all command parts in the second argument, not just the parameters, so that the user can immediately extract the command name from the Lua index 1

Resolves #653

Type of change

  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)

How Has This Been Tested?
With this Lua code + attached VS debugger to the game while executing the console command
CommandExample param1 "param 2" 3:

RegisterConsoleCommandHandler("CommandExample", function(FullCommand, Parameters, OutputDevice)
    print("RegisterConsoleCommandHandler:\n")

    print(string.format("Command: %s\n", FullCommand))
    print(string.format("Number of parameters: %i\n", #Parameters))
    
    for ParameterNumber, Parameter in ipairs(Parameters) do
        print(string.format("Parameter #%i -> '%s'\n", ParameterNumber, Parameter))
    end

    OutputDevice:Log("Write something to game console")

    return false
end)

RegisterConsoleCommandGlobalHandler("CommandExample", function(FullCommand, Parameters, OutputDevice)
    print("RegisterConsoleCommandGlobalHandler:\n")

    print(string.format("Command: %s\n", FullCommand))
    print(string.format("Number of parameters: %i\n", #Parameters))
    
    for ParameterNumber, Parameter in ipairs(Parameters) do
        print(string.format("Parameter #%i -> '%s'\n", ParameterNumber, Parameter))
    end

    OutputDevice:Log("Write something to game console")

    return false
end)

RegisterProcessConsoleExecPreHook(function(Context, FullCommand, CommandParts, Ar, Executor)
    print("RegisterProcessConsoleExecPreHook:\n")
    local ContextObject = Context:get()
    local ExecutorObject = Executor:get()

    print(string.format("Context FullName: %s\n", ContextObject:GetFullName()))
    if ExecutorObject:IsValid() then
        print(string.format("Executor FullName: %s\n", ExecutorObject:GetFullName()))
    end
    print(string.format("Command: %s\n", FullCommand))
    print(string.format("Number of command parts: %i\n", #CommandParts))
    
    if #CommandParts > 0 then
        print(string.format("Command Name: %s\n", CommandParts[1]))
        for PartNumber, CommandPart in ipairs(CommandParts) do
            print(string.format("CommandPart: #%i -> '%s'\n", PartNumber, CommandPart))
        end
    end

    Ar:Log("Write something to game console")

    return true
end)

RegisterProcessConsoleExecPostHook(function(Context, FullCommand, CommandParts, Ar, Executor)
    print("RegisterProcessConsoleExecPostHook:\n")
    local ContextObject = Context:get()
    local ExecutorObject = Executor:get()

    print(string.format("Context FullName: %s\n", ContextObject:GetFullName()))
    if ExecutorObject:IsValid() then
        print(string.format("Executor FullName: %s\n", ExecutorObject:GetFullName()))
    end
    print(string.format("Command: %s\n", FullCommand))
    print(string.format("Number of command parts: %i\n", #CommandParts))
    
    if #CommandParts > 0 then
        print(string.format("Command Name: %s\n", CommandParts[1]))
        for PartNumber, CommandPart in ipairs(CommandParts) do
            print(string.format("CommandPart: #%i -> '%s'\n", PartNumber, CommandPart))
        end
    end

    Ar:Log("Write something to game console")

    return true
end)

Checklist

  • Add changes to the Changelog after getting first feedback
  • Double check explode_by_occurrence_with_quotes code
  • Debate about the name of the function explode_by_occurrence_with_quotes

feat: Use explode_by_occurrence_with_quotes intsead of explode_by_occurrence in RegisterProcessConsoleExecCallback
…uotes in all ProcessConsoleExec functions

docs: Add better examples for RegisterProcessConsoleExecPreHook and RegisterProcessConsoleExecPostHook
…tHook, RegisterConsoleCommandHandler and RegisterConsoleCommandGlobalHandler to pass only the command name as first callback parameter
…mand name

feat: In RegisterProcessConsoleExecPreHook and RegisterProcessConsoleExecPostHook the callback parameter CommandParts should contain all seperated parts of the command, the "command name" as well
…mandGlobalHandler, RegisterConsoleCommandHandler, RegisterProcessConsoleExecPreHook and RegisterProcessConsoleExecPostHook
…nsoleExecPostHook examples futher to show how to get command name
@igromanru igromanru changed the title Improvements in console hooks related command based on issue #653 Improvements in console hooks related functions based on issue #653 Sep 28, 2024
@igromanru igromanru changed the title Improvements in console hooks related functions based on issue #653 Improvements to console hook related functions based on issue #653 Sep 28, 2024
@igromanru
Copy link
Contributor Author

The function explode_by_occurrence_with_quotes has been reworked to recognize " as the beginning or end of the string if it is at the beginning or end of the entire command string or before or after the delimiter.

New test results
Command: CommandExample param1 "param 2" p"aram3 "_"4" "very long\" and boring text" \"escapethis" I"hope" you "are ready" \"finalescape\" ''

Output:

Command: CommandExample param1 "param 2" p"aram3 "_"4" "very long\" and boring text" \"escapethis" I"hope" you "are ready" \"finalescape\" ''
Number of command parts: 12
Command Name: CommandExample
CommandPart: #1 -> 'CommandExample'
CommandPart: #2 -> 'param1'
CommandPart: #3 -> 'param 2'
CommandPart: #4 -> 'p"aram3'
CommandPart: #5 -> '_"4'
CommandPart: #6 -> 'very long" and boring text'
CommandPart: #7 -> '"escapethis"'
CommandPart: #8 -> 'I"hope"'
CommandPart: #9 -> 'you'
CommandPart: #10 -> 'are ready'
CommandPart: #11 -> '"finalescape"'
CommandPart: #12 -> ''''

Current behavior if there is no closing quotation mark

Command: "Command Example " Param1 "It's all param 2

Command: "Command Example " Param1 "It's all param 2
Number of command parts: 3
Command Name: Command Example 
CommandPart: #1 -> 'Command Example '
CommandPart: #2 -> 'Param1'
CommandPart: #3 -> 'It's all param 2'

@igromanru igromanru marked this pull request as draft October 2, 2024 08:54
@igromanru
Copy link
Contributor Author

Since we have no Unit tests, I just wrote some code that I put into the Unreal::Hook::RegisterProcessConsoleExecCallback function and executed a random command.

auto split = explode_by_occurrence_with_quotes(StringType(STR("")), STR(' '));
assert(split.empty());
split = explode_by_occurrence_with_quotes(StringType(STR("  ")));
assert(split.empty());
split = explode_by_occurrence_with_quotes(StringType(STR("   ")), STR('_'));
assert(split.size() == 1);
assert(split[0] == StringType(STR("   ")));
split = explode_by_occurrence_with_quotes(StringType(STR("test")), STR(' '));
assert(split.size() == 1);
assert(split[0] == StringType(STR("test")));
split = explode_by_occurrence_with_quotes(StringType(STR("test_param1_\"param 2\"_param 3")), STR('_'));
assert(split.size() == 4);
assert(split[0] == StringType(STR("test")));
assert(split[1] == StringType(STR("param1")));
assert(split[2] == StringType(STR("param 2")));
assert(split[3] == StringType(STR("param 3")));
split = explode_by_occurrence_with_quotes(
        StringType(STR("\\\"command param1\" \"Param 2 has \\\" in the middle\" \"Param 3 goes till the end")));
assert(split.size() == 4);
assert(split[0] == StringType(STR("\"command")));
assert(split[1] == StringType(STR("param1\"")));
assert(split[2] == StringType(STR("Param 2 has \" in the middle")));
assert(split[3] == StringType(STR("Param 3 goes till the end")));

…` `(space)

docs: Add function documentation to explode_by_occurrence_with_quotes
docs: Add changes to Changelog.md
@igromanru igromanru marked this pull request as ready for review October 6, 2024 04:57
@UE4SS
Copy link
Collaborator

UE4SS commented Oct 10, 2024

I haven't been able to find any problem with the code just by looking at it, but I'm unable to test it.
If you can confirm that your test above succeeds since you didn't include the results, that'd be great.
After that, I'll go ahead and merge this and we can deal with potential problems later if any are reported.

@igromanru
Copy link
Contributor Author

If you can confirm that your test above succeeds since you didn't include the results, that'd be great.

If you mean the “unit tests” aka. asserts, they have run through without errors.

@UE4SS UE4SS merged commit 833b84b into UE4SS-RE:main Oct 10, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[BUG - Release] (LUA) RegisterConsoleCommand_ - Console Ignoring Strings
2 participants