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

SteamInputActionEvent_t incorrectly interprets data in struct #640

Open
marcozee03 opened this issue Aug 21, 2024 · 1 comment
Open

SteamInputActionEvent_t incorrectly interprets data in struct #640

marcozee03 opened this issue Aug 21, 2024 · 1 comment

Comments

@marcozee03
Copy link

brief description

Trying to work with action event callbacks I was having problems with receiving seemingly nonsensical data in SteamInputActionEvent_t. using the pointer the callback gives you by investigating the actual byte data that I was receiving and reinterpreted manually I found the data was in fact being lost somewhere in translation

crude testing script

I was using godot so it uses godot to print to console. change GD.Print() to whatever you prefer for logging

    protected SteamInputActionEventCallbackPointer actionCallback;
    private void init()
    {
        actionCallback = OnActionEvent;
        SteamInput.EnableActionEventCallbacks(actionCallback);
    }
    unsafe void OnActionEvent(IntPtr actionEvent)
    {
        SteamInputActionEvent_t actual = *(SteamInputActionEvent_t*)actionEvent;
        SteamInputActionEvent_t coerced = coerceActionEventPointer(actionEvent);
        GD.Print("Actual(correct) Hex ");
        printActionEventHexData(&actual);
        GD.Print("Coerced(incorrect) Hex");
        printActionEventHexData(&coerced);
        GD.Print("Actual Event(incorrect data interpretation)");
        printActionEventData(actual);
        GD.Print("Coerced Event(correct data interpretation)");
        printActionEventData(coerced);

    }

    unsafe void printActionEventHexData(SteamInputActionEvent_t* actionEvent)
    {
        SteamInputActionEvent_t action = *actionEvent;
        int size = 0;
        switch (action.eEventType)
        {
            case ESteamInputActionEventType.ESteamInputActionEventType_DigitalAction:
                size = 22;
                break;
            case ESteamInputActionEventType.ESteamInputActionEventType_AnalogAction:
                size = 33;
                break;
        }
        byte* bytes = (byte*)actionEvent;
        StringBuilder build = new StringBuilder();
        build.Append('[');
        for (int i = 0; i < size; i++)
        {
            build.Append(Convert.ToHexString([bytes[i]]));
            if (i is 7 or 11 or 19 || size == 22 & i == 20)
            {
                build.Append("] [");
            }
            else if (size == 33 && i is 23 or 27 or 31)
            {
                build.Append("] [");
            }
            else if (i == 32 || size == 22 && i == 21)
            {
                build.Append(']');
            }
            else
            {
                build.Append('-');
            }
        }
        GD.Print(build.ToString());
    }
    unsafe SteamInputActionEvent_t coerceActionEventPointer(IntPtr actionEvent)
    {
        byte* ptr = (byte*)actionEvent;
        InputHandle_t controller = new InputHandle_t(*(ulong*)ptr);
        ESteamInputActionEventType eType = *(ESteamInputActionEventType*)(ptr + 8);
        ulong* handle = (ulong*)(((byte*)ptr) + 12);
        switch (eType)
        {
            case ESteamInputActionEventType.ESteamInputActionEventType_DigitalAction:
                InputDigitalActionHandle_t digitalHandle = new InputDigitalActionHandle_t(handle[0]);
                InputDigitalActionData_t digitalData = new InputDigitalActionData_t()
                {
                    bState = ptr[20],
                    bActive = ptr[21]
                };
                return new SteamInputActionEvent_t()
                {
                    controllerHandle = controller,
                    eEventType = eType,
                    m_val = new SteamInputActionEvent_t.OptionValue()
                    {
                        digitalAction = new SteamInputActionEvent_t.DigitalAction_t()
                        {
                            actionHandle = digitalHandle,
                            digitalActionData = digitalData
                        }
                    }
                };
            case ESteamInputActionEventType.ESteamInputActionEventType_AnalogAction:
                InputAnalogActionHandle_t analogHandle = new InputAnalogActionHandle_t(handle[0]);
                EInputSourceMode eMode = *(EInputSourceMode*)(ptr + 20);
                float x = *(float*)(ptr + 24);
                float y = *(float*)(ptr + 28);
                byte bActive = ptr[32];

                InputAnalogActionData_t analogData = new InputAnalogActionData_t()
                {
                    eMode = eMode,
                    x = x,
                    y = y,
                    bActive = bActive
                };
                return new SteamInputActionEvent_t
                {
                    controllerHandle = controller,
                    eEventType = eType,
                    m_val = new SteamInputActionEvent_t.OptionValue
                    {
                        analogAction = new SteamInputActionEvent_t.AnalogAction_t()
                        {
                            actionHandle = analogHandle,
                            analogActionData = analogData
                        }
                    }
                };
        }
        throw new Exception("Etype not valid" + eType);
    }
    unsafe void printActionEventData(SteamInputActionEvent_t action)
    {
        StringBuilder builder = new StringBuilder();

        builder.AppendLine("\tControllerHandle:" + action.controllerHandle);
        builder.AppendLine("\tEventType:" + action.eEventType);
        switch (action.eEventType)
        {
            case ESteamInputActionEventType.ESteamInputActionEventType_DigitalAction:
                builder.AppendLine("\tDigitalActionHandle:" + action.m_val.digitalAction.actionHandle.m_InputDigitalActionHandle);
                builder.AppendLine($"\tBState:{action.m_val.digitalAction.digitalActionData.bState} BActive: {action.m_val.digitalAction.digitalActionData.bActive}");
                break;
            case ESteamInputActionEventType.ESteamInputActionEventType_AnalogAction:
                var actionData = action.m_val.analogAction.analogActionData;
                builder.AppendLine("\tAnalogActionHandle: " + action.m_val.analogAction.actionHandle.m_InputAnalogActionHandle);
                builder.AppendLine("\tEmode:" + actionData.eMode);
                builder.AppendLine($"\tStickInput({actionData.x},{actionData.y})) BActive:{actionData.bActive}");
                break;
        }
        GD.Print(builder);
    }

Example Results

using the above functions I compared the actual data I received and the current interpretation with my manual interpretation and how the struct would translate my data and received these results: (values are separated based on native C++ layout) (data is in little-endian)

Analog Example Output

Actual(correct) Hex
[EA-33-39-EB-68-DC-00-00] [01-00-00-00] [01-00-00-00-00-00-00-00] [06-00-00-00] [4C-29-A6-3E] [26-FD-92-BE] [01]
Coerced(incorrect) Hex
[EA-33-39-EB-68-DC-00-00] [01-00-00-00] [00-00-00-00-01-00-00-00] [00-00-00-00] [06-00-00-00] [4C-29-A6-3E] [26]
Actual Event(incorrect data interpretation)
ControllerHandle:242343181104106
EventType:ESteamInputActionEventType_AnalogAction
AnalogActionHandle: 25769803776
Emode:1051076940
StickInput(-0.28708762,1E-45)) BActive:0

Coerced Event(correct data interpretation)
ControllerHandle:242343181104106
EventType:ESteamInputActionEventType_AnalogAction
AnalogActionHandle: 1
Emode:k_EInputSourceMode_JoystickMove
StickInput(0.32453382,-0.28708762)) BActive:1

Digital Example Output

Actual(correct) Hex
[EA-33-39-EB-68-DC-00-00] [00-00-00-00] [02-00-00-00-00-00-00-00] [01] [01]
Coerced(incorrect) Hex
[EA-33-39-EB-68-DC-00-00] [00-00-00-00] [00-00-00-00-02-00-00-00] [00] [00]
Actual Event(incorrect data interpretation)
ControllerHandle:242343181104106
EventType:ESteamInputActionEventType_DigitalAction
DigitalActionHandle:1103806595072
BState:0 BActive: 0

Coerced Event(correct data interpretation)
ControllerHandle:242343181104106
EventType:ESteamInputActionEventType_DigitalAction
DigitalActionHandle:2
BState:1 BActive: 1

Conclusions/Hypothesis

The problem lies with the OptionValue struct and how it organizes data. It looks like the C# struct is larger than the C++ native one as seen by the presumed cut-off float value from how I printed the data. It appears an extra 4 bytes are inserted before the bytes corresponding to the Digital/AnalogActionHandle_t. (in the coerced data bytes 13 - 16 (indexed at 1) should not be there)
As I write I'm realizing this is likely an issue of the enum ESteamInputActionEventType in C++ taking up 4 bytes as it is an int32 derived type while in C# it is derived from a 64 bit integer.

@rlabrecque
Copy link
Owner

I can't investigate much riight now but I wonder if one of the following is needed:

[StructLayout(LayoutKind.Sequential, Pack = Packsize.value)]
[StructLayout(LayoutKind.Sequential, Pack = Packsize.value, Size = 1)]

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

No branches or pull requests

2 participants