ramapcsx2 / gbs-control

GNU General Public License v3.0
793 stars 111 forks source link

Preset slot system confuses integers and characters, breaking slots 'H' and higher #422

Closed nyanpasu64 closed 1 year ago

nyanpasu64 commented 1 year ago

This bug report includes multiple symptoms caused by the same central type confusion over uopt->presetSlot. All issues were confirmed to occur on master c4babdd49da0a7d299b3e246d89fb9bb9a5efaaa.

After factory reset, all settings are grayed out

Upon first boot, or after a factory reset or SPIFFS format, all toggle switches on the Settings page are turned off (gray). Clicking on them still works (affects VGA output and appears in the developer console), but does not change the web UI switches. This bug persists until you click a slot on the Presets panel.

Looking in Network debugger, I see malformed status messages consisting of #1 with characters 2-5 missing. Why does this happen?

void loadDefaultUserOptions()
{
    uopt->presetPreference = Output960P;    // #1
    uopt->enableFrameTimeLock = 0; // permanently adjust frame timing to avoid glitch vertical bar. does not work on all displays!
    uopt->presetSlot = 0;          //
    ...
}

void updateWebSocketData()
{
    if (rto->webServerEnabled && rto->webServerStarted) {
        if (webSocket.connectedClients() > 0) {

            char toSend[7] = {0};
            ...
            toSend[2] = (char)uopt->presetSlot;

            // '@' = 0x40, used for "byte is present" detection; 0x80 not in ascii table
            toSend[3] = '@';
            toSend[4] = '@';
            toSend[5] = '@';
            ...

            // send ping and stats
            if (ESP.getFreeHeap() > 14000) {
                webSocket.broadcastTXT(toSend);
            } else {
                webSocket.disconnect();
            }
        }
    }
}

After a factory reset, uopt->presetSlot is initialized to 0, gets written to toSend[2], and webSocket.broadcastTXT(toSend); treats it as a null terminator.

This can be fixed easily by passing length 6 as the second argument of webSocket.broadcastTXT(toSend), to avoid truncating the message. The null byte survives being sent through WebSockets and processed by the browser. Now that you're passing length explicitly and webSocket isn't calling strlen(), you can also turn toSend into a 6-character array without a null terminator (I never was a fan of null terminated strings anyway).

Mixing up ASCII characters and 8-bit integer indexes

If you create 8 or more preset slots, picking the 8th or above slot (technically 'H' or above) does not work:

Why does this happen?

Types

struct userOptions
{
    ...
    uint8_t presetSlot;

Additionally, saveUserPrefs() writes f.write(uopt->presetSlot); to char 2 of "/preferencesv2.txt".

Control flow

Most of the code currently treats uopt->presetSlot as an ASCII character:

Other code treats uopt->presetSlot as a number or array index:

Other code is internally confused, even within a single function:

Some code ignores presetSlot altogether:

Solutions