Login

Subversion Repositories NedoOS

Rev

Rev 539 | Blame | Compare with Previous | Last modification | View Log | Download | RSS feed

/*

  SjASMPlus Z80 Cross Compiler

  Copyright (c) 2004-2006 Aprisobal

  This software is provided 'as-is', without any express or implied warranty.
  In no event will the authors be held liable for any damages arising from the
  use of this software.

  Permission is granted to anyone to use this software for any purpose,
  including commercial applications, and to alter it and redistribute it freely,
  subject to the following restrictions:

  1. The origin of this software must not be misrepresented; you must not claim
         that you wrote the original software. If you use this software in a product,
         an acknowledgment in the product documentation would be appreciated but is
         not required.

  2. Altered source versions must be plainly marked as such, and must not be
         misrepresented as being the original software.

  3. This notice may not be removed or altered from any source distribution.

*/


// devices.cpp

#include "sjdefs.h"

bool IsZXSpectrumDevice(const char *name) {
        if (nullptr == name) return false;
        if (strcmp(name, "ZXSPECTRUM48") &&
                strcmp(name, "ZXSPECTRUM128") &&
                strcmp(name, "ZXSPECTRUM256") &&
                strcmp(name, "ZXSPECTRUM512") &&
                strcmp(name, "ZXSPECTRUM1024") &&
                strcmp(name, "ZXSPECTRUM2048") &&
                strcmp(name, "ZXSPECTRUM4096") &&
                strcmp(name, "ZXSPECTRUM8192"))
        {
                return false;
        }
        return true;
}

bool IsAmstradCPCDevice(const char* name) {
        if (nullptr == name) return false;
        if (strcmp(name, "AMSTRADCPC464") &&
                strcmp(name, "AMSTRADCPC6128"))
        {
                return false;
        }
        return true;
}

static void initRegularSlotDevice(CDevice* const device, const int32_t slotSize, const int32_t slotCount,
                                                                  const int pageCount, const int* const initialPages) {
        for (int32_t slotAddress = 0; slotAddress < slotSize * slotCount; slotAddress += slotSize) {
                device->AddSlot(slotAddress, slotSize);
        }
        device->Memory = new byte[slotSize * pageCount]();
        for (byte* memPtr = device->Memory; memPtr < device->Memory + slotSize * pageCount; memPtr += slotSize) {
                device->AddPage(memPtr, slotSize);
        }
        for (int i = 0; i < device->SlotsCount; ++i) {
                CDeviceSlot* slot = device->GetSlot(i);
                slot->Page = device->GetPage(initialPages[i]);
                slot->InitialPage = slot->Page->Number;
        }
        device->SetSlot(device->SlotsCount - 1);
}

static void initZxLikeDevice(
        CDevice* const device, int32_t slotSize, int pageCount, const int* const initialPages, aint ramtop)
{
        initRegularSlotDevice(device, slotSize, 0x10000/slotSize, pageCount, initialPages);
        device->ZxRamTop = (0x5D00 <= ramtop && ramtop <= 0xFFFF) ? ramtop : ZX_RAMTOP_DEFAULT;

        // set memory to state like if (for snapshot saving):
                // CLEAR ZxRamTop (0x5D5B default) : LOAD "bin" CODE : ... USR start_adr
        aint adr;
        // ULA attributes: INK 0 : PAPER 7 : FLASH 0 : BRIGTH 0
        for (adr = 0x5800; adr < 0x5B00; ++adr) device->Poke(adr, 7*8);

        // set UDG data at ZX_UDG_ADR (0xFF58)
        adr = ZX_UDG_ADR;
        for (byte value : ZX_UDG_DATA) device->Poke(adr++, value);

        // ZX SYSVARS at 0x5C00
        adr = ZX_SYSVARS_ADR;
        for (byte value : ZX_SYSVARS_DATA) device->Poke(adr++, value);
        // set RAMTOP sysvar
        device->Poke(ZX_SYSVARS_ADR+0xB2, device->ZxRamTop & 0xFF);
        device->Poke(ZX_SYSVARS_ADR+0xB3, (device->ZxRamTop>>8) & 0xFF);

        // set STACK data (without the "start" address)
        adr = device->ZxRamTop + 1 - sizeof(ZX_STACK_DATA);
        // set ERRSP sysvar to beginning of the fake stack
        device->Poke(ZX_SYSVARS_ADR+0x3D, adr & 0xFF);
        device->Poke(ZX_SYSVARS_ADR+0x3E, (adr>>8) & 0xFF);
        for (byte value : ZX_STACK_DATA) device->Poke(adr++, value);
}

static void DeviceZXSpectrum48(CDevice **dev, CDevice *parent, aint ramtop) {
        *dev = new CDevice("ZXSPECTRUM48", parent);
        const int initialPages[] = {0, 1, 2, 3};
        initZxLikeDevice(*dev, 0x4000, 4, initialPages, ramtop);
}

const static int initialPagesZx128[] = {7, 5, 2, 0};

static void DeviceZXSpectrum128(CDevice **dev, CDevice *parent, aint ramtop) {
        *dev = new CDevice("ZXSPECTRUM128", parent);
        initZxLikeDevice(*dev, 0x4000, 8, initialPagesZx128, ramtop);
}

static void DeviceZXSpectrum256(CDevice **dev, CDevice *parent, aint ramtop) {
        *dev = new CDevice("ZXSPECTRUM256", parent);
        initZxLikeDevice(*dev, 0x4000, 16, initialPagesZx128, ramtop);
}

static void DeviceZXSpectrum512(CDevice **dev, CDevice *parent, aint ramtop) {
        *dev = new CDevice("ZXSPECTRUM512", parent);
        initZxLikeDevice(*dev, 0x4000, 32, initialPagesZx128, ramtop);
}

static void DeviceZXSpectrum1024(CDevice **dev, CDevice *parent, aint ramtop) {
        *dev = new CDevice("ZXSPECTRUM1024", parent);
        initZxLikeDevice(*dev, 0x4000, 64, initialPagesZx128, ramtop);
}

static void DeviceZXSpectrum2048(CDevice **dev, CDevice *parent, aint ramtop) {
        *dev = new CDevice("ZXSPECTRUM2048", parent);
        initZxLikeDevice(*dev, 0x4000, 128, initialPagesZx128, ramtop);
}

static void DeviceZXSpectrum4096(CDevice **dev, CDevice *parent, aint ramtop) {
        *dev = new CDevice("ZXSPECTRUM4096", parent);
        initZxLikeDevice(*dev, 0x4000, 256, initialPagesZx128, ramtop);
}

static void DeviceZXSpectrum8192(CDevice **dev, CDevice *parent, aint ramtop) {
        *dev = new CDevice("ZXSPECTRUM8192", parent);
        initZxLikeDevice(*dev, 0x4000, 512, initialPagesZx128, ramtop);
}

static void DeviceZxSpectrumNext(CDevice **dev, CDevice *parent, aint ramtop) {
        if (ramtop) WarningById(W_NO_RAMTOP);
        if (Options::IsI8080) Error("Can't use ZXN device while in i8080 assembling mode", line, FATAL);
        if (Options::IsLR35902) Error("Can't use ZXN device while in Sharp LR35902 assembling mode", line, FATAL);
        *dev = new CDevice("ZXSPECTRUMNEXT", parent);
        const int initialPages[] = {14, 15, 10, 11, 4, 5, 0, 1};        // basically same as ZX128, but 8k
        initRegularSlotDevice(*dev, 0x2000, 8, 224, initialPages);
        // auto-enable ZX Next instruction extensions
        if (0 == Options::syx.IsNextEnabled) {
                Options::syx.IsNextEnabled = 1;
                Z80::InitNextExtensions();              // add the special opcodes here (they were not added)
                                // this is a bit late, but it should work as well as `--zxnext` option I believe
        }
}

static void DeviceNoSlot64k(CDevice **dev, CDevice *parent, aint ramtop) {
        if (ramtop) WarningById(W_NO_RAMTOP);
        *dev = new CDevice("NOSLOT64K", parent);
        const int initialPages[] = { 0 };
        initRegularSlotDevice(*dev, 0x10000, 1, 32, initialPages);      // 32*64kiB = 2MiB
}

static void DeviceAmstradCPC464(CDevice** dev, CDevice* parent, aint ramtop) {
        if (ramtop) WarningById(W_NO_RAMTOP);
        *dev = new CDevice("AMSTRADCPC464", parent);
        const int initialPages[] = { 0, 1, 2, 3 };
        initRegularSlotDevice(*dev, 0x4000, 4, 4, initialPages);
}

static void DeviceAmstradCPC6128(CDevice** dev, CDevice* parent, aint ramtop) {
        if (ramtop) WarningById(W_NO_RAMTOP);
        *dev = new CDevice("AMSTRADCPC6128", parent);
        const int initialPages[] = { 0, 1, 2, 3 };
        initRegularSlotDevice(*dev, 0x4000, 4, 8, initialPages);
}

static bool SetUserDefinedDevice(const char* id, CDevice** dev, CDevice* parent, aint ramtop) {
        auto findIt = std::find_if(
                DefDevices.begin(), DefDevices.end(),
                [&](const CDeviceDef* el) { return 0 == strcasecmp(id, el->getID()); }
        );
        if (DefDevices.end() == findIt) return false;   // not found
        const CDeviceDef & def = **findIt;
        if (ramtop) WarningById(W_NO_RAMTOP);
        *dev = new CDevice(def.getID(), parent);
        initRegularSlotDevice(*dev, def.SlotSize, def.SlotsCount, def.PagesCount, def.initialPages);
        return true;
}

bool SetDevice(const char *const_id, const aint ramtop) {
        CDevice** dev;
        CDevice* parent = nullptr;
        char* id = const_cast<char*>(const_id);         //TODO cmphstr for both const/nonconst variants?
                // ^ argument is const because of lua bindings

        if (!id || cmphstr(id, "none")) {
                DeviceID = nullptr;
                Device = nullptr;
                return true;
        }

        if (!DeviceID || strcmp(DeviceID, id)) {        // different device than current, change to it
                DeviceID = nullptr;
                dev = &Devices;
                // search for device
                while (*dev) {
                        parent = *dev;
                        if (!strcmp(parent->ID, id)) break;
                        dev = &(parent->Next);
                }
                if (nullptr == (*dev)) {        // device not found
                        if (cmphstr(id, "zxspectrum48")) {                      // must be lowercase to catch both cases
                                DeviceZXSpectrum48(dev, parent, ramtop);
                        } else if (cmphstr(id, "zxspectrum128")) {
                                DeviceZXSpectrum128(dev, parent, ramtop);
                        } else if (cmphstr(id, "zxspectrum256")) {
                                DeviceZXSpectrum256(dev, parent, ramtop);
                        } else if (cmphstr(id, "zxspectrum512")) {
                                DeviceZXSpectrum512(dev, parent, ramtop);
                        } else if (cmphstr(id, "zxspectrum1024")) {
                                DeviceZXSpectrum1024(dev, parent, ramtop);
                        } else if (cmphstr(id, "zxspectrum2048")) {
                                DeviceZXSpectrum2048(dev, parent, ramtop);
                        } else if (cmphstr(id, "zxspectrum4096")) {
                                DeviceZXSpectrum4096(dev, parent, ramtop);
                        } else if (cmphstr(id, "zxspectrum8192")) {
                                DeviceZXSpectrum8192(dev, parent, ramtop);
                        } else if (cmphstr(id, "zxspectrumnext")) {
                                DeviceZxSpectrumNext(dev, parent, ramtop);
                        } else if (cmphstr(id, "noslot64k")) {
                                DeviceNoSlot64k(dev, parent, ramtop);
                        } else if (cmphstr(id, "amstradcpc464")) {
                                DeviceAmstradCPC464(dev, parent, ramtop);
                        } else if (cmphstr(id, "amstradcpc6128")) {
                                DeviceAmstradCPC6128(dev, parent, ramtop);
                        } else if (!SetUserDefinedDevice(id, dev, parent, ramtop)) {
                                return false;
                        }
                }
                // set up the found/new device
                Device = (*dev);
                DeviceID = Device->ID;
                Device->CheckPage(CDevice::CHECK_RESET);
        }
        if (ramtop && Device->ZxRamTop && ramtop != Device->ZxRamTop) {
                WarningById(W_DEV_RAMTOP);
        }
        if (IsSldExportActive()) {
                // SLD tracing data are being exported, export the device data
                int pageSize = Device->GetCurrentSlot()->Size;
                int pageCount = Device->PagesCount;
                int slotsCount = Device->SlotsCount;
                char buf[LINEMAX];
                snprintf(buf, LINEMAX, "pages.size:%d,pages.count:%d,slots.count:%d",
                        pageSize, pageCount, slotsCount
                );
                for (int slotI = 0; slotI < slotsCount; ++slotI) {
                        size_t bufLen = strlen(buf);
                        char* bufAppend = buf + bufLen;
                        snprintf(bufAppend, LINEMAX-bufLen,
                                                (0 == slotI) ? ",slots.adr:%d" : ",%d",
                                                Device->GetSlot(slotI)->Address);
                }
                // pagesize
                WriteToSldFile(-1,-1,'Z',buf);
        }
        return true;
}

const char* DEVICE_NONE_ID = "NONE";

const char* GetDeviceName() {
        return DeviceID ? DeviceID : DEVICE_NONE_ID;
}

std::vector<CDeviceDef*> DefDevices;

CDeviceDef::CDeviceDef(const char* name, aint slot_size, aint page_count)
        : SlotSize(slot_size), SlotsCount((0x10000 + slot_size - 1) / slot_size), PagesCount(page_count) {
        assert(name);
        ID = STRDUP(name);
}

CDeviceDef::~CDeviceDef() {
        free(ID);
}

CDevice::CDevice(const char *name, CDevice *parent)
        : Next(nullptr), SlotsCount(0), PagesCount(0), Memory(nullptr), ZxRamTop(0), CurrentSlot(0),
        previousSlotI(0), previousSlotOpt(CDeviceSlot::ESlotOptions::O_NONE), limitExceeded(false) {
        ID = STRDUP(name);
        if (parent) parent->Next = this;
        for (auto & slot : Slots) slot = nullptr;
        for (auto & page : Pages) page = nullptr;
}

CDevice::~CDevice() {
        for (auto & slot : Slots) if (slot) delete slot;
        for (auto & page : Pages) if (page) delete page;
        if (Memory) delete[] Memory;
        if (Next) delete Next;
        free(ID);
}

void CDevice::AddSlot(int32_t adr, int32_t size) {
        if (CDeviceDef::MAX_SLOT_N == SlotsCount) ErrorInt("Can't add more slots, already at max", CDeviceDef::MAX_SLOT_N, FATAL);
        Slots[SlotsCount++] = new CDeviceSlot(adr, size);
}

void CDevice::AddPage(byte* memory, int32_t size) {
        if (CDeviceDef::MAX_PAGE_N == PagesCount) ErrorInt("Can't add more pages, already at max", CDeviceDef::MAX_PAGE_N, FATAL);
        Pages[PagesCount] = new CDevicePage(memory, size, PagesCount);
        PagesCount++;
}

CDeviceSlot* CDevice::GetSlot(int num) {
        if (Slots[num]) return Slots[num];
        Error("Wrong slot number", lp);
        return Slots[0];
}

CDevicePage* CDevice::GetPage(int num) {
        if (Pages[num]) return Pages[num];
        Error("Wrong page number", lp);
        return Pages[0];
}

// returns slot belonging to the Z80-address (16bit)
int CDevice::GetSlotOfA16(int32_t address) {
        for (int i=SlotsCount; i--; ) {
                CDeviceSlot* const S = Slots[i];
                if (address < S->Address) continue;
                if (S->Address + S->Size <= address) return -1;         // outside of slot
                return i;
        }
        return -1;
}

// returns currently mapped page for the Z80-address (16bit)
int CDevice::GetPageOfA16(int32_t address) {
        int slotNum = GetSlotOfA16(address);
        if (-1 == slotNum) return -1;
        if (nullptr == Slots[slotNum]->Page) return -1;
        return Slots[slotNum]->Page->Number;
}

void CDevice::CheckPage(const ECheckPageLevel level) {
        // fake DISP address gets auto-wrapped FFFF->0 (with warning only)
        // only with "emit" mode, labels may get the value 0x10000 before the address gets truncated
        if (DISP_NONE != PseudoORG && CHECK_NO_EMIT != level && 0x10000 <= CurAddress) {
                if (LASTPASS == pass) {
                        char buf[64];
                        SPRINTF1(buf, 64, "RAM limit exceeded 0x%X by DISP", (unsigned int)CurAddress);
                        Warning(buf);
                }
                CurAddress &= 0xFFFF;
        }
        // check the emit address for bytecode
        const int realAddr = DISP_NONE != PseudoORG ? adrdisp : CurAddress;
        // quicker check to avoid scanning whole slots array every byte
        if (CHECK_RESET != level
                && Slots[previousSlotI]->Address <= realAddr
                && realAddr < Slots[previousSlotI]->Address + Slots[previousSlotI]->Size) return;
        for (int i=SlotsCount; i--; ) {
                CDeviceSlot* const S = Slots[i];
                if (realAddr < S->Address) continue;
                Page = S->Page;
                MemoryPointer = Page->RAM + (realAddr - S->Address);
                if (CHECK_RESET == level) {
                        previousSlotOpt = S->Option;
                        previousSlotI = i;
                        limitExceeded = false;
                        return;
                }
                // if still in the same slot and within boundaries, we are done
                if (i == previousSlotI && realAddr < S->Address + S->Size) return;
                // crossing into other slot, check options for special functionality of old slot
                if (S->Address + S->Size <= realAddr) MemoryPointer = nullptr; // you're not writing there
                switch (previousSlotOpt) {
                        case CDeviceSlot::O_ERROR:
                                if (LASTPASS == pass && CHECK_EMIT == level && !limitExceeded) {
                                        ErrorInt("Write outside of memory slot", realAddr, SUPPRESS);
                                        limitExceeded = true;
                                }
                                break;
                        case CDeviceSlot::O_WARNING:
                                if (LASTPASS == pass && CHECK_EMIT == level && !limitExceeded) {
                                        Warning("Write outside of memory slot");
                                        limitExceeded = true;
                                }
                                break;
                        case CDeviceSlot::O_NEXT:
                        {
                                CDeviceSlot* const prevS = Slots[previousSlotI];
                                const int nextPageN = prevS->Page->Number + 1;
                                if (PagesCount <= nextPageN) {
                                        ErrorInt("No more memory pages to map next one into slot", previousSlotI, SUPPRESS);
                                        // disable the option on the overflowing slot
                                        prevS->Option = CDeviceSlot::O_NONE;
                                        break;          // continue into next slot, don't wrap any more
                                }
                                if (realAddr != (prevS->Address + prevS->Size)) {       // should be equal
                                        ErrorInt("Write beyond memory slot in wrap-around slot catched too late by",
                                                                realAddr - prevS->Address - prevS->Size, FATAL);
                                        break;
                                }
                                prevS->Page = Pages[nextPageN];         // map next page into the guarded slot
                                Page = prevS->Page;
                                if (DISP_NONE != PseudoORG) adrdisp -= prevS->Size;
                                else CurAddress -= prevS->Size;
                                MemoryPointer = Page->RAM;
                                return;         // preserve current option status
                        }
                        default:
                                if (LASTPASS == pass && CHECK_EMIT == level && !limitExceeded && !MemoryPointer) {
                                        ErrorInt("Write outside of device memory at", realAddr, SUPPRESS);
                                        limitExceeded = true;
                                }
                                break;
                }
                // refresh check slot settings
                limitExceeded &= (previousSlotI == i);  // reset limit flag if slot changed (in non check_reset mode)
                previousSlotI = i;
                previousSlotOpt = S->Option;
                return;
        }
        Error("CheckPage(..): please, contact the author of this program.", nullptr, FATAL);
}

bool CDevice::SetSlot(int slotNumber) {
        if (slotNumber < 0 || SlotsCount <= slotNumber || nullptr == Slots[slotNumber]) return false;
        CurrentSlot = slotNumber;
        return true;
}

CDeviceSlot* CDevice::GetCurrentSlot() {
        return GetSlot(CurrentSlot);
}

// Calling this with (PagesCount, 0) will return total memory size
int32_t CDevice::GetMemoryOffset(int page, int32_t offset) const {
        if (!Pages[0]) return 0;
        return offset + page * Pages[0]->Size;
}

void CDevice::Poke(aint z80adr, byte value) {
        // write byte into device memory with current page-mapping
        const int adrPage = GetPageOfA16(z80adr);
        if (-1 == adrPage) return;              // silently ignore invalid address
        CDevicePage* page = GetPage(adrPage);
        page->RAM[z80adr & (page->Size-1)] = value;
}

aint CDevice::SlotNumberFromPreciseAddress(aint address) {
        if (address < SlotsCount) return address;               // seems to be slot number, not address
        // check if the address (input value) does exactly match start-address of some slot
        int slotNum = GetSlotOfA16(address);
        if (-1 == slotNum) return address;                              // does not belong to any slot
        if (address != GetSlot(slotNum)->Address) return address;               // not exact match
        return slotNum;                                                                 // return address converted into slot number
}

CDevicePage::CDevicePage(byte* memory, int32_t size, int number)
        : Size(size), Number(number), RAM(memory) {
        if (nullptr == RAM) Error("No memory defined", nullptr, FATAL);
}

CDeviceSlot::CDeviceSlot(int32_t adr, int32_t size)
        : Address(adr), Size(size), Page(nullptr), InitialPage(-1), Option(O_NONE) {
}

CDeviceSlot::~CDeviceSlot() {
}

const unsigned char ZX_SYSVARS_DATA[] = {
        0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00, 0x14, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x01, 0x00, 0x06, 0x00, 0x0b, 0x00, 0x01, 0x00, 0x01, 0x00, 0x06, 0x00, 0x10, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x40, 0x00, 0xff, 0xcc, 0x01, 0x54, 0xff, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0xcb, 0x5c, 0x00, 0x00, 0xb6,
        0x5c, 0xb6, 0x5c, 0xcb, 0x5c, 0x00, 0x00, 0xca, 0x5c, 0xcc, 0x5c, 0xcc, 0x5c, 0xcc, 0x5c, 0x00,
        0x00, 0xce, 0x5c, 0xce, 0x5c, 0xce, 0x5c, 0x00, 0x92, 0x5c, 0x10, 0x02, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x58, 0xff, 0x00, 0x00, 0x21,
        0x5b, 0x00, 0x21, 0x17, 0x00, 0x40, 0xe0, 0x50, 0x21, 0x18, 0x21, 0x17, 0x01, 0x38, 0x00, 0x38,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x57, 0xff, 0xff, 0xff, 0xf4, 0x09, 0xa8, 0x10, 0x4b, 0xf4, 0x09, 0xc4, 0x15, 0x53,
        0x81, 0x0f, 0xc4, 0x15, 0x52, 0xf4, 0x09, 0xc4, 0x15, 0x50, 0x80, 0x80, 0x0d, 0x80, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};

const unsigned char ZX_STACK_DATA[] = {
        0x03, 0x13, 0x00, 0x3e
};

const unsigned char ZX_UDG_DATA[168] = {
        0x00, 0x3c, 0x42, 0x42, 0x7e, 0x42, 0x42, 0x00, 0x00, 0x7c, 0x42, 0x7c, 0x42, 0x42, 0x7c, 0x00,
        0x00, 0x3c, 0x42, 0x40, 0x40, 0x42, 0x3c, 0x00, 0x00, 0x78, 0x44, 0x42, 0x42, 0x44, 0x78, 0x00,
        0x00, 0x7e, 0x40, 0x7c, 0x40, 0x40, 0x7e, 0x00, 0x00, 0x7e, 0x40, 0x7c, 0x40, 0x40, 0x40, 0x00,
        0x00, 0x3c, 0x42, 0x40, 0x4e, 0x42, 0x3c, 0x00, 0x00, 0x42, 0x42, 0x7e, 0x42, 0x42, 0x42, 0x00,
        0x00, 0x3e, 0x08, 0x08, 0x08, 0x08, 0x3e, 0x00, 0x00, 0x02, 0x02, 0x02, 0x42, 0x42, 0x3c, 0x00,
        0x00, 0x44, 0x48, 0x70, 0x48, 0x44, 0x42, 0x00, 0x00, 0x40, 0x40, 0x40, 0x40, 0x40, 0x7e, 0x00,
        0x00, 0x42, 0x66, 0x5a, 0x42, 0x42, 0x42, 0x00, 0x00, 0x42, 0x62, 0x52, 0x4a, 0x46, 0x42, 0x00,
        0x00, 0x3c, 0x42, 0x42, 0x42, 0x42, 0x3c, 0x00, 0x00, 0x7c, 0x42, 0x42, 0x7c, 0x40, 0x40, 0x00,
        0x00, 0x3c, 0x42, 0x42, 0x52, 0x4a, 0x3c, 0x00, 0x00, 0x7c, 0x42, 0x42, 0x7c, 0x44, 0x42, 0x00,
        0x00, 0x3c, 0x40, 0x3c, 0x02, 0x42, 0x3c, 0x00, 0x00, 0xfe, 0x10, 0x10, 0x10, 0x10, 0x10, 0x00,
        0x00, 0x42, 0x42, 0x42, 0x42, 0x42, 0x3c, 0x00
};