#include "core/gb/gbSGB.h"

#include <cstdlib>
#include <cstring>

#include "core/base/file_util.h"
#include "core/base/port.h"
#include "core/base/system.h"
#include "core/gb/gb.h"
#include "core/gb/gbGlobals.h"

extern uint8_t* g_pix;
extern bool speedup;
extern bool gbSgbResetFlag;

#define GBSGB_NONE 0
#define GBSGB_RESET 1
#define GBSGB_PACKET_TRANSMIT 2

uint8_t* gbSgbBorderChar = NULL;
uint8_t* gbSgbBorder = NULL;

int gbSgbCGBSupport = 0;
int gbSgbMask = 0;
int gbSgbPacketState = GBSGB_NONE;
int gbSgbBit = 0;
int gbSgbPacketTimeout = 0;
int GBSGB_PACKET_TIMEOUT = 66666;
uint8_t gbSgbPacket[16 * 7];
int gbSgbPacketNBits = 0;
int gbSgbPacketByte = 0;
int gbSgbPacketNumber = 0;
int gbSgbMultiplayer = 0;
int gbSgbFourPlayers = 0;
uint8_t gbSgbNextController = 0x0f;
uint8_t gbSgbReadingController = 0;
uint16_t gbSgbSCPPalette[4 * 512];
uint8_t gbSgbATF[20 * 18];
uint8_t gbSgbATFList[45 * 20 * 18];
uint8_t gbSgbScreenBuffer[4160];

inline void gbSgbDraw24Bit(uint8_t* p, uint16_t v)
{
    memcpy(p, &systemColorMap32[v], 3);
}

inline void gbSgbDraw32Bit(uint32_t* p, uint16_t v)
{
    *p = systemColorMap32[v];
}

inline void gbSgbDraw16Bit(uint16_t* p, uint16_t v)
{
    *p = systemColorMap16[v];
}

inline void gbSgbDraw8Bit(uint8_t* p, uint16_t v)
{
    *p = systemColorMap8[v];
}

void gbSgbReset()
{
    gbSgbPacketTimeout = 0;
    gbSgbCGBSupport = 0;
    gbSgbMask = 0;
    gbSgbPacketState = GBSGB_NONE;
    gbSgbBit = 0;
    gbSgbPacketNBits = 0;
    gbSgbPacketNumber = 0;
    gbSgbMultiplayer = 0;
    gbSgbFourPlayers = 0;
    gbSgbNextController = 0x0f;
    gbSgbReadingController = 0;

    memset(gbSgbSCPPalette, 0, 512 * 4 * sizeof(uint16_t));
    memset(gbSgbATF, 0, 20 * 18);
    memset(gbSgbATFList, 0, 45 * 20 * 18);
    memset(gbSgbPacket, 0, 16 * 7);
    memset(gbSgbBorderChar, 0, 32 * 256);
    memset(gbSgbBorder, 0, 2048);

    int i;
    for (i = 1; i < 2048; i += 2) {
        gbSgbBorder[i] = 1 << 2;
    }

    for (i = 0; i < 32; i++) {
        gbPalette[i * 4] = (0x1f) | (0x1f << 5) | (0x1f << 10);
        gbPalette[i * 4 + 1] = (0x15) | (0x15 << 5) | (0x15 << 10);
        gbPalette[i * 4 + 2] = (0x0c) | (0x0c << 5) | (0x0c << 10);
        gbPalette[i * 4 + 3] = 0;
    }
}

void gbSgbInit()
{
    gbSgbBorderChar = (uint8_t*)malloc(32 * 256);
    gbSgbBorder = (uint8_t*)malloc(2048);

    gbSgbReset();
}

void gbSgbShutdown()
{
    if (gbSgbBorderChar != NULL) {
        free(gbSgbBorderChar);
        gbSgbBorderChar = NULL;
    }

    if (gbSgbBorder != NULL) {
        free(gbSgbBorder);
        gbSgbBorder = NULL;
    }
}

void gbSgbFillScreen(uint16_t color)
{
    switch (systemColorDepth) {
    case 8: {
        for (int y = 0; y < 144; y++) {
#ifdef __LIBRETRO__
            int yLine = (y + gbBorderRowSkip) * gbBorderLineSkip + gbBorderColumnSkip;
#else
            int yLine = (y + gbBorderRowSkip + 1) * (gbBorderLineSkip + 2) + gbBorderColumnSkip;
#endif
            uint8_t* dest = (uint8_t*)g_pix + yLine;
            for (int x = 0; x < 160; x++)
                gbSgbDraw8Bit(dest++, color);
        }
    } break;
    case 16: {
        for (int y = 0; y < 144; y++) {
#ifdef __LIBRETRO__
            int yLine = (y + gbBorderRowSkip) * gbBorderLineSkip + gbBorderColumnSkip;
#else
            int yLine = (y + gbBorderRowSkip + 1) * (gbBorderLineSkip + 2) + gbBorderColumnSkip;
#endif
            uint16_t* dest = (uint16_t*)g_pix + yLine;
            for (int x = 0; x < 160; x++)
                gbSgbDraw16Bit(dest++, color);
        }
    } break;
    case 24: {
        for (int y = 0; y < 144; y++) {
            int yLine = (y + gbBorderRowSkip) * gbBorderLineSkip + gbBorderColumnSkip;
            uint8_t* dest = (uint8_t*)g_pix + yLine * 3;
            for (int x = 0; x < 160; x++) {
                gbSgbDraw24Bit(dest, color);
                dest += 3;
            }
        }
    } break;
    case 32: {
        for (int y = 0; y < 144; y++) {
#ifdef __LIBRETRO__
            int yLine = (y + gbBorderRowSkip) * gbBorderLineSkip + gbBorderColumnSkip;
#else
            int yLine = (y + gbBorderRowSkip + 1) * (gbBorderLineSkip + 1) + gbBorderColumnSkip;
#endif
            uint32_t* dest = (uint32_t*)g_pix + yLine;
            for (int x = 0; x < 160; x++) {
                gbSgbDraw32Bit(dest++, color);
            }
        }
    } break;
    }
}

#define getmem(x) gbMemoryMap[(x) >> 12][(x)&0xfff]

void gbSgbRenderScreenToBuffer()
{
    uint16_t mapAddress = 0x9800;

    if (register_LCDC & 0x08)
        mapAddress = 0x9c00;

    uint16_t patternAddress = 0x8800;

    int flag = 1;

    if (register_LCDC & 0x10) {
        patternAddress = 0x8000;
        flag = 0;
    }

    uint8_t* toAddress = gbSgbScreenBuffer;

    for (int i = 0; i < 13; i++) {
        for (int j = 0; j < 20; j++) {
            int tile = getmem(mapAddress);
            mapAddress++;

            if (flag) {
                if (tile > 127)
                    tile -= 128;
                else
                    tile += 128;
            }
            for (int k = 0; k < 16; k++)
                *toAddress++ = getmem(patternAddress + tile * 16 + k);
        }
        mapAddress += 12;
    }
}

void gbSgbDrawBorderTile(int x, int y, int tile, int attr)
{
#ifdef __LIBRETRO__
    uint16_t* dest = (uint16_t*)g_pix + (y * 256) + x;
    uint32_t* dest32 = (uint32_t*)g_pix + (y * 256) + x;
#else
    uint16_t* dest = (uint16_t*)g_pix + ((y + 1) * (256 + 2)) + x;
    uint32_t* dest32 = (uint32_t*)g_pix + ((y + 1) * (256 + 1)) + x;
#endif
    uint8_t* dest8 = (uint8_t*)g_pix + ((y * 256) + x) * 3;
    uint8_t* dest8b = (uint8_t*)g_pix + ((y * 256) + x);
    uint8_t* tileAddress = &gbSgbBorderChar[tile * 32];
    uint8_t* tileAddress2 = &gbSgbBorderChar[tile * 32 + 16];

    uint8_t l = 8;

    uint8_t palette = ((attr >> 2) & 7);

    if (palette < 4)
        palette += 4;

    palette *= 16;

    uint8_t xx = 0;
    uint8_t yy = 0;

    int flipX = attr & 0x40;
    int flipY = attr & 0x80;

    while (l > 0) {
        uint8_t mask = 0x80;
        uint8_t a = *tileAddress++;
        uint8_t b = *tileAddress++;
        uint8_t c = *tileAddress2++;
        uint8_t d = *tileAddress2++;

        uint8_t yyy;
        if (!flipY)
            yyy = yy;
        else
            yyy = 7 - yy;

        while (mask > 0) {

            uint8_t color = 0;
            if (a & mask)
                color++;
            if (b & mask)
                color += 2;
            if (c & mask)
                color += 4;
            if (d & mask)
                color += 8;

            if (color || (y + yy < 40 || y + yy >= 184) || (x + xx < 48 || x + xx >= 208)) {
                uint8_t xxx;

                if (!flipX)
                    xxx = xx;
                else
                    xxx = 7 - xx;

                uint16_t cc;
                if (color) {
                    cc = gbPalette[palette + color];
                } else {
                    cc = gbPalette[0];
                }

                switch (systemColorDepth) {
                case 8:
#ifdef __LIBRETRO__
                    gbSgbDraw8Bit(dest8b + yyy * 256 + xxx, cc);
#else
                    gbSgbDraw8Bit(dest8b + yyy * (256 + 2) + xxx, cc);
#endif
                case 16:
#ifdef __LIBRETRO__
                    gbSgbDraw16Bit(dest + yyy * 256 + xxx, cc);
#else
                    gbSgbDraw16Bit(dest + yyy * (256 + 2) + xxx, cc);
#endif
                    break;
                case 24:
                    gbSgbDraw24Bit(dest8 + (yyy * 256 + xxx) * 3, cc);
                    break;
                case 32:
#ifdef __LIBRETRO__
                    gbSgbDraw32Bit(dest32 + yyy * 256 + xxx, cc);
#else
                    gbSgbDraw32Bit(dest32 + yyy * (256 + 1) + xxx, cc);
#endif
                    break;
                }
            }

            mask >>= 1;

            xx++;
        }
        yy++;
        xx = 0;
        l--;
        mask = 0x80;
    }
}

void gbSgbRenderBorder()
{
    if (gbBorderOn) {
        uint8_t* fromAddress = gbSgbBorder;

        for (uint8_t y = 0; y < 28; y++) {
            for (uint8_t x = 0; x < 32; x++) {
                uint8_t tile = *fromAddress++;
                uint8_t attr = *fromAddress++;

                gbSgbDrawBorderTile(x * 8, y * 8, tile, attr);
            }
        }
    }
}

void gbSgbPicture()
{
    gbSgbRenderScreenToBuffer();

    memcpy(gbSgbBorder, gbSgbScreenBuffer, 2048);

    uint16_t* paletteAddr = (uint16_t*)&gbSgbScreenBuffer[2048];

    for (int i = 64; i < 128; i++) {
        gbPalette[i] = READ16LE(paletteAddr++);
    }

    gbSgbCGBSupport |= 4;

    if (gbBorderAutomatic && !gbBorderOn && gbSgbCGBSupport > 4) {
        gbBorderOn = true;
        systemGbBorderOn();
    }

    if (gbBorderOn && !gbSgbMask)
        gbSgbRenderBorder();

    if (gbSgbMode && gbCgbMode && gbSgbCGBSupport > 4) {
        gbSgbCGBSupport = 0;
        gbSgbMode = false;
        gbSgbMask = 0;
        gbSgbRenderBorder();
        gbReset();
    }

    if (gbSgbCGBSupport > 4)
        gbSgbCGBSupport = 0;
}

void gbSgbSetPalette(int a, int b, uint16_t* p)
{
    uint16_t bit00 = READ16LE(p++);
    int i;

    for (i = 1; i < 4; i++) {
        gbPalette[a * 4 + i] = READ16LE(p++);
    }

    for (i = 1; i < 4; i++) {
        gbPalette[b * 4 + i] = READ16LE(p++);
    }

    gbPalette[0] = gbPalette[4] = gbPalette[8] = gbPalette[12] = bit00;
    if (gbBorderOn && !gbSgbMask)
        gbSgbRenderBorder();
}

void gbSgbScpPalette()
{
    gbSgbRenderScreenToBuffer();

    uint16_t* fromAddress = (uint16_t*)gbSgbScreenBuffer;

    for (int i = 0; i < 512 * 4; i++) {
        gbSgbSCPPalette[i] = READ16LE(fromAddress++);
    }
}

void gbSgbSetATF(int n)
{
    if (n < 0)
        n = 0;
    if (n > 44)
        n = 44;
    memcpy(gbSgbATF, &gbSgbATFList[n * 20 * 18], 20 * 18);

    if (gbSgbPacket[1] & 0x40) {
        gbSgbMask = 0;
        if (gbBorderOn)
            gbSgbRenderBorder();
    }
}

void gbSgbSetPalette()
{
    uint16_t pal = READ16LE((((uint16_t*)&gbSgbPacket[1]))) & 511;
    memcpy(&gbPalette[0], &gbSgbSCPPalette[pal * 4], 4 * sizeof(uint16_t));

    pal = READ16LE((((uint16_t*)&gbSgbPacket[3]))) & 511;
    memcpy(&gbPalette[4], &gbSgbSCPPalette[pal * 4], 4 * sizeof(uint16_t));

    pal = READ16LE((((uint16_t*)&gbSgbPacket[5]))) & 511;
    memcpy(&gbPalette[8], &gbSgbSCPPalette[pal * 4], 4 * sizeof(uint16_t));

    pal = READ16LE((((uint16_t*)&gbSgbPacket[7]))) & 511;
    memcpy(&gbPalette[12], &gbSgbSCPPalette[pal * 4], 4 * sizeof(uint16_t));

    uint8_t atf = gbSgbPacket[9];

    if (atf & 0x80) {
        gbSgbSetATF(atf & 0x3f);
    }

    if (atf & 0x40) {
        gbSgbMask = 0;
        if (gbBorderOn)
            gbSgbRenderBorder();
    }
}

void gbSgbAttributeBlock()
{
    uint8_t* fromAddress = &gbSgbPacket[1];

    uint8_t nDataSet = *fromAddress++;
    if (nDataSet > 12)
        nDataSet = 12;
    if (nDataSet == 0)
        nDataSet = 1;

    while (nDataSet) {
        uint8_t controlCode = (*fromAddress++) & 7;
        uint8_t paletteDesignation = (*fromAddress++) & 0x3f;
        uint8_t startH = (*fromAddress++) & 0x1f;
        uint8_t startV = (*fromAddress++) & 0x1f;
        uint8_t endH = (*fromAddress++) & 0x1f;
        uint8_t endV = (*fromAddress++) & 0x1f;

        uint8_t* toAddress = gbSgbATF;

        for (uint8_t y = 0; y < 18; y++) {
            for (uint8_t x = 0; x < 20; x++) {
                if (x < startH || y < startV || x > endH || y > endV) {
                    // outside
                    if (controlCode & 0x04)
                        *toAddress = (paletteDesignation >> 4) & 0x03;
                } else if (x > startH && x < endH && y > startV && y < endV) {
                    // inside
                    if (controlCode & 0x01)
                        *toAddress = paletteDesignation & 0x03;
                } else {
                    // surrounding line
                    if (controlCode & 0x02)
                        *toAddress = (paletteDesignation >> 2) & 0x03;
                    else if (controlCode == 0x01)
                        *toAddress = paletteDesignation & 0x03;
                }
                toAddress++;
            }
        }
        nDataSet--;
    }
}

void gbSgbSetColumnPalette(uint8_t col, uint8_t p)
{
    //  if(col < 0)
    //    col = 0;
    if (col > 19)
        col = 19;

    p &= 3;

    uint8_t* toAddress = &gbSgbATF[col];

    for (uint8_t y = 0; y < 18; y++) {
        *toAddress = p;
        toAddress += 20;
    }
}

void gbSgbSetRowPalette(uint8_t row, uint8_t p)
{
    //  if(row < 0)
    //    row = 0;
    if (row > 17)
        row = 17;

    p &= 3;

    uint8_t* toAddress = &gbSgbATF[row * 20];

    for (uint8_t x = 0; x < 20; x++) {
        *toAddress++ = p;
    }
}

void gbSgbAttributeDivide()
{
    uint8_t control = gbSgbPacket[1];
    uint8_t coord = gbSgbPacket[2];
    uint8_t colorBR = control & 3;
    uint8_t colorAL = (control >> 2) & 3;
    uint8_t colorOL = (control >> 4) & 3;

    if (control & 0x40) {
        if (coord > 17)
            coord = 17;

        for (uint8_t i = 0; i < 18; i++) {
            if (i < coord)
                gbSgbSetRowPalette(i, colorAL);
            else if (i > coord)
                gbSgbSetRowPalette(i, colorBR);
            else
                gbSgbSetRowPalette(i, colorOL);
        }
    } else {
        if (coord > 19)
            coord = 19;

        for (uint8_t i = 0; i < 20; i++) {
            if (i < coord)
                gbSgbSetColumnPalette(i, colorAL);
            else if (i > coord)
                gbSgbSetColumnPalette(i, colorBR);
            else
                gbSgbSetColumnPalette(i, colorOL);
        }
    }
}

void gbSgbAttributeLine()
{
    uint8_t* fromAddress = &gbSgbPacket[1];

    uint8_t nDataSet = *fromAddress++;

    if (nDataSet > 0x6e)
        nDataSet = 0x6e;

    while (nDataSet) {
        uint8_t line = *fromAddress++;
        uint8_t num = line & 0x1f;
        uint8_t pal = (line >> 5) & 0x03;
        if (line & 0x80) {
            if (num > 17)
                num = 17;
            gbSgbSetRowPalette(num, pal);
        } else {
            if (num > 19)
                num = 19;
            gbSgbSetColumnPalette(num, pal);
        }
        nDataSet--;
    }
}

void gbSgbAttributeCharacter()
{
    uint8_t startH = gbSgbPacket[1] & 0x1f;
    uint8_t startV = gbSgbPacket[2] & 0x1f;
    int nDataSet = READ16LE(((uint16_t*)&gbSgbPacket[3]));
    int style = gbSgbPacket[5] & 1;
    if (startH > 19)
        startH = 19;
    if (startV > 17)
        startV = 17;

    uint8_t s = 6;
    uint8_t* fromAddress = &gbSgbPacket[6];
    uint8_t v = *fromAddress++;

    if (style) {
        while (nDataSet) {
            uint8_t p = (v >> s) & 3;
            gbSgbATF[startV * 20 + startH] = p;
            startV++;
            if (startV == 18) {
                startV = 0;
                startH++;
                if (startH == 20)
                    break;
            }

            if (s)
                s -= 2;
            else {
                s = 6;
                v = *fromAddress++;
                nDataSet--;
            }
        }
    } else {
        while (nDataSet) {
            uint8_t p = (v >> s) & 3;
            gbSgbATF[startV * 20 + startH] = p;
            startH++;
            if (startH == 20) {
                startH = 0;
                startV++;
                if (startV == 18)
                    break;
            }

            if (s)
                s -= 2;
            else {
                s = 6;
                v = *fromAddress++;
                nDataSet--;
            }
        }
    }
}

void gbSgbSetATFList()
{
    gbSgbRenderScreenToBuffer();

    uint8_t* fromAddress = gbSgbScreenBuffer;
    uint8_t* toAddress = gbSgbATFList;

    for (int i = 0; i < 45; i++) {
        for (int j = 0; j < 90; j++) {
            uint8_t v = *fromAddress++;
            uint8_t s = 6;
            if (i == 2)
                s = 6;
            for (int k = 0; k < 4; k++) {
                *toAddress++ = (v >> s) & 0x03;
                s -= 2;
            }
        }
    }
}

void gbSgbMaskEnable()
{
    int gbSgbMaskFlag = gbSgbPacket[1] & 3;

    gbSgbMask = gbSgbMaskFlag;

    switch (gbSgbMaskFlag) {
    case 1:
        break;
    case 2:
        gbSgbFillScreen(0x0000);
        //    memset(&gbPalette[0], 0, 128*sizeof(uint16_t));
        break;
    case 3:
        gbSgbFillScreen(gbPalette[0]);
        break;
    }
    if (!gbSgbMask) {
        if (gbBorderOn)
            gbSgbRenderBorder();
    }
}

void gbSgbChrTransfer()
{
    gbSgbRenderScreenToBuffer();

    int address = (gbSgbPacket[1] & 1) * (128 * 32);

    if (gbSgbPacket[1] & 1)
        gbSgbCGBSupport |= 2;
    else
        gbSgbCGBSupport |= 1;

    memcpy(&gbSgbBorderChar[address], gbSgbScreenBuffer, 128 * 32);

    if (gbBorderAutomatic && !gbBorderOn && gbSgbCGBSupport > 4) {
        gbBorderOn = true;
        systemGbBorderOn();
    }

    if (gbBorderOn && !gbSgbMask)
        gbSgbRenderBorder();

    if (gbSgbMode && gbCgbMode && gbSgbCGBSupport == 7) {
        gbSgbCGBSupport = 0;
        gbSgbMode = false;
        gbSgbMask = 0;
        gbSgbRenderBorder();
        gbReset();
    }

    if (gbSgbCGBSupport > 4)
        gbSgbCGBSupport = 0;
}

void gbSgbMultiRequest()
{
    if (gbSgbPacket[1] & 1) {
        gbSgbMultiplayer = 1;
        if (gbSgbPacket[1] & 2)
            gbSgbFourPlayers = 1;
        else
            gbSgbFourPlayers = 0;
        gbSgbNextController = 0x0e;
    } else {
        gbSgbFourPlayers = 0;
        gbSgbMultiplayer = 0;
        gbSgbNextController = 0x0f;
    }
}

void gbSgbCommand()
{
    int command = gbSgbPacket[0] >> 3;
    //  int nPacket = gbSgbPacket[0] & 7;

    switch (command) {
    case 0x00:
        gbSgbSetPalette(0, 1, (uint16_t*)&gbSgbPacket[1]);
        break;
    case 0x01:
        gbSgbSetPalette(2, 3, (uint16_t*)&gbSgbPacket[1]);
        break;
    case 0x02:
        gbSgbSetPalette(0, 3, (uint16_t*)&gbSgbPacket[1]);
        break;
    case 0x03:
        gbSgbSetPalette(1, 2, (uint16_t*)&gbSgbPacket[1]);
        break;
    case 0x04:
        gbSgbAttributeBlock();
        break;
    case 0x05:
        gbSgbAttributeLine();
        break;
    case 0x06:
        gbSgbAttributeDivide();
        break;
    case 0x07:
        gbSgbAttributeCharacter();
        break;
    case 0x0a:
        gbSgbSetPalette();
        break;
    case 0x0b:
        gbSgbScpPalette();
        break;
    case 0x11:
        gbSgbMultiRequest();
        break;
    case 0x13:
        gbSgbChrTransfer();
        break;
    case 0x14:
        gbSgbPicture();
        break;
    case 0x15:
        gbSgbSetATFList();
        break;
    case 0x16:
        gbSgbSetATF(gbSgbPacket[1] & 0x3f);
        break;
    case 0x17:
        gbSgbMaskEnable();
        break;
    }
}

void gbSgbResetPacketState()
{
    gbSgbPacketState = GBSGB_NONE;
    gbSgbPacketTimeout = 0;
}

void gbSgbDoBitTransfer(uint8_t value)
{
    value = value & 0x30;
    switch (gbSgbPacketState) {
    case GBSGB_NONE:
        if (value == 0) {
            gbSgbPacketState = GBSGB_RESET;
            gbSgbPacketTimeout = GBSGB_PACKET_TIMEOUT;
        } else if (value == 0x30) {
            if (gbSgbMultiplayer) {
                if ((gbSgbReadingController & 7) == 7) {
                    gbSgbReadingController = 0;
                    if (gbSgbMultiplayer) {
                        gbSgbNextController--;
                        if (gbSgbFourPlayers) {
                            if (gbSgbNextController == 0x0b)
                                gbSgbNextController = 0x0f;
                        } else {
                            if (gbSgbNextController == 0x0d)
                                gbSgbNextController = 0x0f;
                        }
                    }
                } else {
                    gbSgbReadingController &= 3;
                }
            }
            gbSgbPacketTimeout = 0;
        } else {
            if (value == 0x10)
                gbSgbReadingController |= 0x2;
            else if (value == 0x20)
                gbSgbReadingController |= 0x01;
            gbSgbPacketTimeout = 0;
        }
        gbSgbPacketTimeout = 0;
        break;
    case GBSGB_RESET:
        if (value == 0x30) {
            gbSgbPacketState = GBSGB_PACKET_TRANSMIT;
            gbSgbPacketByte = 0;
            gbSgbPacketNBits = 0;
            gbSgbPacketTimeout = GBSGB_PACKET_TIMEOUT;
        } else if (value == 0x00) {
            gbSgbPacketTimeout = GBSGB_PACKET_TIMEOUT;
            gbSgbPacketState = GBSGB_RESET;
        } else {
            gbSgbPacketState = GBSGB_NONE;
            gbSgbPacketTimeout = 0;
        }
        break;
    case GBSGB_PACKET_TRANSMIT:
        if (value == 0) {
            gbSgbPacketState = GBSGB_RESET;
            gbSgbPacketTimeout = 0;
        } else if (value == 0x30) {
            if (gbSgbPacketNBits == 128) {
                gbSgbPacketNBits = 0;
                gbSgbPacketByte = 0;
                gbSgbPacketNumber++;
                gbSgbPacketTimeout = 0;
                if (gbSgbPacketNumber == (gbSgbPacket[0] & 7)) {
                    gbSgbCommand();
                    gbSgbPacketNumber = 0;
                    gbSgbPacketState = GBSGB_NONE;
                    gbSgbPacketTimeout = 0;
                }
            } else {
                if (gbSgbPacketNBits < 128) {
                    gbSgbPacket[gbSgbPacketNumber * 16 + gbSgbPacketByte] >>= 1;
                    gbSgbPacket[gbSgbPacketNumber * 16 + gbSgbPacketByte] |= gbSgbBit;
                    gbSgbPacketNBits++;
                    if (!(gbSgbPacketNBits & 7)) {
                        gbSgbPacketByte++;
                    }
                    gbSgbPacketTimeout = GBSGB_PACKET_TIMEOUT;
                }
            }
        } else {
            if (value == 0x20)
                gbSgbBit = 0x00;
            else
                gbSgbBit = 0x80;
            gbSgbPacketTimeout = GBSGB_PACKET_TIMEOUT;
        }
        gbSgbReadingController = 0;
        break;
    default:
        gbSgbPacketState = GBSGB_NONE;
        gbSgbPacketTimeout = 0;
        break;
    }
}

variable_desc gbSgbSaveStruct[] = {
    { &gbSgbMask, sizeof(int) },
    { &gbSgbPacketState, sizeof(int) },
    { &gbSgbBit, sizeof(int) },
    { &gbSgbPacketNBits, sizeof(int) },
    { &gbSgbPacketByte, sizeof(int) },
    { &gbSgbPacketNumber, sizeof(int) },
    { &gbSgbMultiplayer, sizeof(int) },
    { &gbSgbNextController, sizeof(uint8_t) },
    { &gbSgbReadingController, sizeof(uint8_t) },
    { NULL, 0 }
};

variable_desc gbSgbSaveStructV3[] = {
    { &gbSgbMask, sizeof(int) },
    { &gbSgbPacketState, sizeof(int) },
    { &gbSgbBit, sizeof(int) },
    { &gbSgbPacketNBits, sizeof(int) },
    { &gbSgbPacketByte, sizeof(int) },
    { &gbSgbPacketNumber, sizeof(int) },
    { &gbSgbMultiplayer, sizeof(int) },
    { &gbSgbNextController, sizeof(uint8_t) },
    { &gbSgbReadingController, sizeof(uint8_t) },
    { &gbSgbFourPlayers, sizeof(int) },
    { NULL, 0 }
};

#ifdef __LIBRETRO__
void gbSgbSaveGame(uint8_t*& data)
{
    utilWriteDataMem(data, gbSgbSaveStructV3);

    utilWriteMem(data, gbSgbBorder, 2048);
    utilWriteMem(data, gbSgbBorderChar, 32 * 256);

    utilWriteMem(data, gbSgbPacket, 16 * 7);

    utilWriteMem(data, gbSgbSCPPalette, 4 * 512 * sizeof(uint16_t));
    utilWriteMem(data, gbSgbATF, 20 * 18);
    utilWriteMem(data, gbSgbATFList, 45 * 20 * 18);
}

void gbSgbReadGame(const uint8_t*& data)
{
	utilReadDataMem(data, gbSgbSaveStructV3);

	utilReadMem(gbSgbBorder, data, 2048);
    utilReadMem(gbSgbBorderChar, data, 32 * 256);

    utilReadMem(gbSgbPacket, data, 16 * 7);

    utilReadMem(gbSgbSCPPalette, data, 4 * 512 * sizeof(uint16_t));
    utilReadMem(gbSgbATF, data, 20 * 18);
    utilReadMem(gbSgbATFList, data, 45 * 20 * 18);
}

#else // !__LIBRETRO__
void gbSgbSaveGame(gzFile gzFile)
{
    utilWriteData(gzFile, gbSgbSaveStructV3);

    utilGzWrite(gzFile, gbSgbBorder, 2048);
    utilGzWrite(gzFile, gbSgbBorderChar, 32 * 256);

    utilGzWrite(gzFile, gbSgbPacket, 16 * 7);

    utilGzWrite(gzFile, gbSgbSCPPalette, 4 * 512 * sizeof(uint16_t));
    utilGzWrite(gzFile, gbSgbATF, 20 * 18);
    utilGzWrite(gzFile, gbSgbATFList, 45 * 20 * 18);
}

void gbSgbReadGame(gzFile gzFile, int version)
{
    if (version >= 3)
        utilReadData(gzFile, gbSgbSaveStructV3);
    else {
        utilReadData(gzFile, gbSgbSaveStruct);
        gbSgbFourPlayers = 0;
    }

    if (version >= 8) {
        utilGzRead(gzFile, gbSgbBorder, 2048);
        utilGzRead(gzFile, gbSgbBorderChar, 32 * 256);
    }

    utilGzRead(gzFile, gbSgbPacket, 16 * 7);

    utilGzRead(gzFile, gbSgbSCPPalette, 4 * 512 * sizeof(uint16_t));
    utilGzRead(gzFile, gbSgbATF, 20 * 18);
    utilGzRead(gzFile, gbSgbATFList, 45 * 20 * 18);
}
#endif // !__LIBRETRO__
