?login_element?

Subversion Repositories NedoOS

Rev

Blame | Last modification | View Log | Download

  1. /*
  2.  
  3.   SjASMPlus Z80 Cross Compiler - modified - SAVECPCSNA extension
  4.  
  5.   Copyright (c) 2006 Sjoerd Mastijn (original SW)
  6.  
  7.   This software is provided 'as-is', without any express or implied warranty.
  8.   In no event will the authors be held liable for any damages arising from the
  9.   use of this software.
  10.  
  11.   Permission is granted to anyone to use this software for any purpose,
  12.   including commercial applications, and to alter it and redistribute it freely,
  13.   subject to the following restrictions:
  14.  
  15.   1. The origin of this software must not be misrepresented; you must not claim
  16.          that you wrote the original software. If you use this software in a product,
  17.          an acknowledgment in the product documentation would be appreciated but is
  18.          not required.
  19.  
  20.   2. Altered source versions must be plainly marked as such, and must not be
  21.          misrepresented as being the original software.
  22.  
  23.   3. This notice may not be removed or altered from any source distribution.
  24.  
  25. */
  26.  
  27. // io_cpc.cpp
  28.  
  29. #include "sjdefs.h"
  30. #include "io_cpc_ldrs.h"
  31.  
  32. //
  33. // Amstrad CPC snapshot file saving (SNA)
  34. //
  35.  
  36. namespace
  37. {
  38.         // report error and close the file
  39.         static int writeError(const char* fname, FILE*& fileToClose) {
  40.                 Error("[SAVECPCSNA] Write error (disk full?)", fname, IF_FIRST);
  41.                 fclose(fileToClose);
  42.                 return 0;
  43.         }
  44.  
  45.         static bool isCPC6128() {
  46.                 return strcmp(DeviceID, "AMSTRADCPC464");
  47.         }
  48.  
  49.         static word getCPCMemoryDepth() {
  50.                 return Device->PagesCount * 0x10;
  51.         }
  52. }
  53.  
  54. static int SaveSNA_CPC(const char* fname, word start) {
  55.         // for Lua
  56.         if (!DeviceID) {
  57.                 Error("SAVECPCSNA only allowed in real device emulation mode (See DEVICE)"); return 0;
  58.         }
  59.         else if (!IsAmstradCPCDevice(DeviceID)) {
  60.                 Error("[SAVECPCSNA] Device must be AMSTRADCPC464 or AMSTRADCPC6128."); return 0;
  61.         }
  62.  
  63.         FILE* ff;
  64.         if (!FOPEN_ISOK(ff, fname, "wb")) {
  65.                 Error("[SAVECPCSNA] Error opening file for write", fname);
  66.                 return 0;
  67.         }
  68.  
  69.         // format:  http://cpctech.cpc-live.com/docs/snapshot.html
  70.         constexpr int SNA_HEADER_SIZE = 256;
  71.         const char magic[8] = { 'M', 'V', ' ', '-', ' ', 'S', 'N', 'A' };
  72.         // basic rom initialized pens
  73.         const byte ga_pens[17] = { 0x04, 0x0A, 0x13, 0x0C, 0x0B, 0x14, 0x15, 0x0D, 0x06, 0x1E, 0x1F, 0x07, 0x12, 0x19, 0x04, 0x17, 0x04 };
  74.         // crtc set to standard mode 1 screen @ $C000
  75.         const byte crtc_defaults[18] = {
  76.                 0x3F,           // h total
  77.                 0x28,           // h displayed
  78.                 0x2E,           // h sync pos
  79.                 0x8E,           // h/v sync widths
  80.                 0x26,           // v total (height)
  81.                 0x00,           // v adjust
  82.                 0x19,           // v displayed (height)
  83.                 0x1E,           // v sync pos
  84.                 0x00,           // interlace & skew
  85.                 0x07,           // max raster
  86.                 0x00, 0x00, // cursor start
  87.                 0x30, 0x00, // display (xxPPSSOO) -> 0xC000 based screen
  88.                 0x00, 0x00, // cursor addr
  89.                 0x00, 0x00  // light pen
  90.         };
  91.         // psg defaults as initialized by ROM
  92.         const byte psg_defaults[16] = { 0x0E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
  93.  
  94.         // init header
  95.         byte snbuf[SNA_HEADER_SIZE];
  96.         memset(snbuf, 0, SNA_HEADER_SIZE);
  97.         // copy over the magic marker
  98.         memcpy(snbuf, magic, sizeof(magic));
  99.         snbuf[0x10] = 2; // v2 file format
  100.  
  101.         // v1 format fields
  102.         snbuf[0x1B] = 0; snbuf[0x1C] = 0; // ensure interrupts are disabled
  103.         snbuf[0x21] = 0xF0; snbuf[0x22] = 0xBF; // set the sp to $BFF0
  104.         snbuf[0x23] = start & 0xFF; snbuf[0x24] = start >> 8; // pc set to start addr
  105.         snbuf[0x25] = 1; // im = 1
  106.         // set the pens to the defaults
  107.         memcpy(snbuf + 0x2F, ga_pens, sizeof(ga_pens));
  108.         // multi-config (RMR: 100I ULVM)
  109.         snbuf[0x40] = 0b1000'01'01; // Upper ROM paged out, Lower ROM paged in, Mode 1
  110.         // RAM config (MMR see https://www.grimware.org/doku.php/documentations/devices/gatearray#mmr)
  111.         snbuf[0x41] = 0; // default RAM paging
  112.         // set the crtc registers to the default values
  113.         snbuf[0x42] = 0x0D;     // selected crtc reg
  114.         memcpy(snbuf + 0x43, crtc_defaults, sizeof(crtc_defaults));
  115.         // PPI
  116.         snbuf[0x59] = 0x82;     // PPI control port default
  117.         // Set the PSG registers to sensible defaults
  118.         memcpy(snbuf + 0x5B, psg_defaults, sizeof(psg_defaults));
  119.  
  120.         word memdepth = getCPCMemoryDepth();
  121.         snbuf[0x6B] = memdepth & 0xFF;
  122.         snbuf[0x6C] = memdepth >> 8;
  123.  
  124.         // v2 format fields
  125.         snbuf[0x6D] = isCPC6128() ? 2 : 0;      // machine type (0 = 464, 1 = 664, 2 = 6128)
  126.  
  127.         if (fwrite(snbuf, 1, SNA_HEADER_SIZE, ff) != SNA_HEADER_SIZE) {
  128.                 return writeError(fname, ff);
  129.         }
  130.  
  131.         // Write the pages out in order
  132.         for (int page = 0; page < Device->PagesCount; ++page) {
  133.                 if ((aint)fwrite(Device->GetPage(page)->RAM, 1, Device->GetPage(page)->Size, ff) != Device->GetPage(page)->Size) {
  134.                         return writeError(fname, ff);
  135.                 }
  136.         }
  137.  
  138.         fclose(ff);
  139.         return 1;
  140. }
  141.  
  142. void dirSAVECPCSNA() {
  143.         if (pass != LASTPASS) {
  144.                 SkipToEol(lp);
  145.                 return;
  146.         }
  147.         std::unique_ptr<char[]> fnaam(GetOutputFileName(lp));
  148.         int start = StartAddress;
  149.         if (anyComma(lp)) {
  150.                 aint val;
  151.                 if (ParseExpression(lp, val)) {
  152.                         if (0 <= start) Warning("[SAVECPCSNA] Start address was also defined by END, SAVECPCSNA argument used instead");
  153.                         if (0 <= val) {
  154.                                 start = val;
  155.                         }
  156.                         else {
  157.                                 Error("[SAVECPCSNA] Negative values are not allowed", bp, SUPPRESS); return;
  158.                         }
  159.                 }
  160.                 else {
  161.                         return;
  162.                 }
  163.         }
  164.         if (start < 0) {
  165.                 Error("[SAVECPCSNA] No start address defined", bp, SUPPRESS); return;
  166.         }
  167.  
  168.         if (!SaveSNA_CPC(fnaam.get(), start))
  169.                 Error("[SAVECPCSNA] Error writing file (Disk full?)", bp, IF_FIRST);
  170. }
  171.  
  172. //
  173. // Amstrad CPC tape file saving (CDT)
  174. //
  175.  
  176. enum ECDTHeadlessFormat { AMSTRAD, SPECTRUM };
  177.  
  178. namespace CDTUtil {
  179.  
  180.         static constexpr word DefaultPause = 1000;
  181. //      static constexpr byte BlockTypeReg = 0x0A;                      //unused currently, TODO ask Oli about it?
  182.         static constexpr byte BlockTypeHeader = 0x2C;
  183.         static constexpr byte BlockTypeData = 0x16;
  184.  
  185.         static constexpr byte FileTypeBASIC = (0 << 1);
  186.         static constexpr byte FileTypeBINARY = (1 << 1);
  187. //      static constexpr byte FileTypeSCREEN = (2 << 1);        //unused currently, TODO ask Oli about it?
  188.  
  189.         /* CRC polynomial: X^16+X^12+X^5+1 */
  190.         static unsigned int crcupdate(word c, byte b) {
  191.                 constexpr unsigned int poly = 4129;
  192.                 unsigned int aux = c ^ (b << 8);
  193.                 for (aint i = 0; i < 8; i++) {
  194.                         if (aux & 0x8000) {
  195.                                 aux = (aux << 1) ^ poly;
  196.                         }
  197.                         else {
  198.                                 aux <<= 1;
  199.                         }
  200.                 }
  201.                 return aux;
  202.         }
  203.  
  204.         static void writeChunkedData(const char* fname, const byte* buf, const aint buflen, word pauseAfter, byte sync) {
  205.                 constexpr aint chunkLen = 256;
  206.  
  207.                 const aint chunkCount = (buflen + 255) >> 8;
  208.                 const aint dataLen = (chunkCount * chunkLen)    // data size (in chunks)
  209.                         + (chunkCount * 2)      // crcs
  210.                         + 5; // sync byte + trailer
  211.  
  212.                 std::unique_ptr<byte[]> chunkedData(new byte[dataLen]);
  213.                 byte* wptr = chunkedData.get(); // write ptr
  214.                 const byte* rptr = buf; // read ptr
  215.                 // build the buffer
  216.                 *(wptr++) = sync; // sync pattern
  217.  
  218.                 aint remaining = buflen;
  219.        
  220.                 // N chunks of 256 bytes with a 2 byte checksum at the end
  221.                 for (aint i = 0; i < chunkCount; ++i) {        
  222.                         if (remaining < chunkLen) {
  223.                                 memcpy(wptr, rptr, remaining);
  224.                                 memset(wptr + remaining, 0, chunkLen - remaining);
  225.                                 rptr += remaining;
  226.                                 remaining = 0;
  227.                         }
  228.                         else {
  229.                                 memcpy(wptr, rptr, chunkLen);
  230.                                 rptr += chunkLen;
  231.                                 remaining -= chunkLen;
  232.                         }
  233.  
  234.                         unsigned int check = 0xFFFF;
  235.                         for (aint n = 0; n < chunkLen; ++n) {
  236.                                 check = crcupdate(check, wptr[n]);
  237.                         }
  238.  
  239.                         wptr += chunkLen;
  240.                         // append block crc
  241.                         check ^= 0xFFFF;
  242.                         *(wptr++) = check >> 8;
  243.                         *(wptr++) = check & 0xFF;
  244.                 }
  245.  
  246.                 // 4 trailer bytes of 0xFF
  247.                 const byte trailer[] = { 0xFF, 0xFF, 0xFF, 0xFF };
  248.                 memcpy(wptr, trailer, sizeof(trailer));
  249.  
  250.                 // save block
  251.                 STZXTurboBlock turbo;
  252.                 turbo.PilotPulseLen = 0x091A;
  253.                 turbo.FirstSyncLen = 0x048D;
  254.                 turbo.SecondSyncLen = 0x048D;
  255.                 turbo.ZeroBitLen = 0x048D;
  256.                 turbo.OneBitLen = 0x091A;
  257.                 turbo.PilotToneLen = 0x1000;
  258.                 turbo.LastByteUsedBits = 0x08;
  259.                 turbo.PauseAfterMs = pauseAfter;
  260.  
  261.                 TZX_AppendTurboBlock(fname, chunkedData.get(), dataLen, turbo);
  262.         }
  263.  
  264.         static void writeTapeFile(const char* fname, const char* tfname, byte fileType, const byte* buf, aint buflen, word memaddr, word startaddr, word pause) {
  265.                 constexpr aint blocksize = 2048;
  266.                 constexpr aint headerlen = 64;
  267.  
  268.                 byte hbuf[headerlen];
  269.                 memset(hbuf, 0, headerlen);
  270.                 /*
  271.                    0   16  Filename       Name of the file, padded with nulls
  272.                   16    1  Block number   The first block is 1, numbers are consecutive
  273.                   17    1  Last block     A non-zero value means that this is the last block of a file
  274.                   18    1  File type      A value recording  the type of the file
  275.                   19    2  Data length    The number of data bytes in the data record
  276.                   21    2  Data location  Where the data was written from originally
  277.                   23    1  First block    A non-zero value means that this is the first block of a file
  278.                   24    2  Logical length This is the total length of the file in bytes
  279.                   26    2  Entry address  The execution address for machine code programs
  280.                 */
  281.                
  282.                 // ensure name is <= 16
  283.                 aint tapefname_len = strlen(tfname);
  284.                 if (tapefname_len > 16) {
  285.                         tapefname_len = 16;
  286.                 }
  287.  
  288.                 // copy tape file name (16 bytes)
  289.                 memcpy(hbuf, tfname, tapefname_len);
  290.  
  291.                 // init header
  292.                 hbuf[16] = 1; // block 1
  293.                 hbuf[18] = fileType;
  294.                 hbuf[24] = buflen & 0xFF;       // logical len (size of whole file)
  295.                 hbuf[25] = buflen >> 8;
  296.                 hbuf[26] = startaddr & 0xFF;
  297.                 hbuf[27] = startaddr >> 8; // entry addr
  298.  
  299.                 word memloc = memaddr;
  300.                 aint remaining = buflen;
  301.                 byte block = 1;
  302.                 const byte* rptr = buf;
  303.  
  304.                 // split the file into blocks of up to 2048 bytes, each with a header and a payload
  305.                 while (remaining) {
  306.                         hbuf[16] = block;       // write block num
  307.                         hbuf[21] = memloc & 0xFF; // where's this block going in memory?
  308.                         hbuf[22] = memloc >> 8;
  309.                         hbuf[23] = block == 1 ? 0xFF : 0x00;    // first block flag
  310.  
  311.                         aint dlen = remaining;
  312.                         if (remaining > blocksize) {
  313.                                 dlen = blocksize;
  314.                                 hbuf[17] = 0x00;        // more blocks to come
  315.                         }
  316.                         else {
  317.                                 hbuf[17] = 0xFF;        // last block
  318.                         }
  319.  
  320.                         // write data len (size of block)
  321.                         hbuf[19] = dlen & 0xFF;
  322.                         hbuf[20] = dlen >> 8;
  323.  
  324.                         writeChunkedData(fname, hbuf, headerlen, 10, BlockTypeHeader); // header
  325.                         writeChunkedData(fname, rptr, dlen, pause, BlockTypeData);      // data
  326.                         rptr += dlen;
  327.                         memloc += dlen;
  328.                         remaining -= dlen;
  329.                         ++block;
  330.                 }
  331.         }
  332.  
  333.         static void writeBASICLoader(const char* fname, byte screenMode, const byte* palette) {
  334.                 constexpr byte mode_values[] = { 0x0E, 0x0F, 0x10 };
  335.                 byte border = 0;
  336.                 border = *palette;
  337.                 // Border is always the first entry in the palette
  338.                 ++palette;
  339.  
  340.                 // BASIC number encoding format for the palette entries
  341.                 byte p[32];
  342.                 for (aint i = 0, n = 0; i < 16; ++i, n+=2) {
  343.                         p[n] = (palette[i] / 10) + '0';
  344.                         p[n+1] = (palette[i] % 10) + '0';
  345.                 }
  346.  
  347.                 const word callad = SaveCDT_AmstradCPC464_ORG;
  348.                 const word himem = callad - 1; // himem is one byte lower than the program we load
  349.  
  350.                 // BASIC loader to set the screen mode, border color, palette, and then load the asm loader and execute it
  351.                 const byte basic[] = {
  352.                         // Line format <Line Len (int16)> <line no (int16)> <tokens...> <0x00>
  353.                         //
  354.                         //              10              MODE            N                                               :               CLS  :     BORDER               N
  355.                         15, 00, 10, 00, 0xAD, 0x20, mode_values[screenMode], 0x01, 0x8A, 0x01, 0x82, 0x20, 0x19, border, 0x00,
  356.                         //              20              DATA [16 bytes of int8]
  357.                         54, 00, 20, 00, 0x8C, 0x20,
  358.                                 p[0], p[1], ',', p[2], p[3], ',', p[4], p[5], ',', p[6], p[7], ',',
  359.                                 p[8], p[9], ',', p[10], p[11], ',', p[12], p[13], ',', p[14], p[15], ',',
  360.                                 p[16], p[17], ',', p[18], p[19], ',', p[20], p[21], ',', p[22], p[23], ',',
  361.                                 p[24], p[25], ',', p[26], p[27], ',', p[28], p[29], ',', p[30], p[31], 0x00,
  362.                         //              30              FOR                     i                                               =         0                       TO              15
  363.                         18, 00, 30, 00, 0x9E, 0x20, 0x0D, 0x00, 0x00, 0xE9, 0xEF, 0x0E, 0x20, 0xEC, 0x20, 0x19, 0x0F, 0x00,
  364.                         //              40              READ            v                                               :         INK                   i                                         ,           v                       ,                             v
  365.                         30, 00, 40, 00, 0xC3, 0x20, 0x0D, 0x00, 0x00, 0xF6, 0x01, 0xA2, 0x20, 0x0D, 0x00, 0x00, 0xE9, 0x2C, 0x20, 0x0D, 0x00, 0x00, 0xF6, 0x2C, 0x20, 0x0D, 0x00, 0x00, 0xF6, 0x00,
  366.                         //              50              NEXT            i
  367.                         11, 00, 50, 00, 0xB0, 0x20, 0x0D, 0x00, 0x00, 0xE9, 0x00,
  368.                         //              60              MEMORY          &NNNN                     :             LOAD
  369.                         20, 00, 60, 00, 0xAA, 0x20, 0x1C, himem & 0xFF, himem >> 8, 0x01,       0xA8, 0x20, 0x22, '!', 'c', 'o', 'd', 'e', 0x22, 0x00,
  370.                         //              70              CALL
  371.                         10, 00, 70, 00, 0x83, 0x20, 0x1C, callad & 0xFF, callad >> 8, 0x00,
  372.                         // EOF
  373.                         00, 00
  374.                 };
  375.                 constexpr aint basiclen = sizeof(basic);
  376.                 writeTapeFile(fname, "LOADER", FileTypeBASIC, basic, basiclen, 0x0170, 0x0000, DefaultPause);
  377.         }
  378.  
  379.         static void writeUserProgram(const char* fname, const char* tapefname, const byte* buf, aint buflen, word baseAddr, word startAddr) {
  380.                 writeTapeFile(fname, tapefname, FileTypeBINARY, buf, buflen, baseAddr, startAddr, DefaultPause);
  381.         }
  382.  
  383.         static bool hasScreen() {
  384.                 const CDevicePage* page = Device->GetPage(3);
  385.                 const byte* ptr = page->RAM;
  386.                 for (int i = 0; i < page->Size; ++i) {
  387.                         if (*ptr != 0) {
  388.                                 return true;
  389.                         }
  390.                         ++ptr;
  391.                 }
  392.                 return false;
  393.         }
  394.  
  395.         static aint calcRAMStart(const byte* ram, aint ramlen) {
  396.                 for (int i = 0; i < ramlen; ++i) {
  397.                         if (ram[i]) return i;
  398.                 }
  399.                 return 0x10000;
  400.         }
  401.  
  402.         static aint calcRAMLength(const byte* ram, aint ramlen) {
  403.                 while (ramlen--) {
  404.                         if (ram[ramlen]) return 1 + ramlen;
  405.                 }
  406.                 return 0x0000;
  407.         }
  408.  
  409.         static std::unique_ptr<byte[]> getContigRAM(aint startAddr, aint length) {
  410.                 assert(0 <= startAddr && 1 <= length && (startAddr+length) <= 0x10000);
  411.                 std::unique_ptr<byte[]> data(new byte[length]);
  412.                 byte* bptr = data.get();
  413.                 // copy the currently mapped device memory into new continuous buffer
  414.                 while (0 < length) {
  415.                         const int slotId = Device->GetSlotOfA16(startAddr);
  416.                         assert(-1 != slotId);
  417.                         CDeviceSlot* const S = Device->GetSlot(slotId);
  418.                         const aint offset = startAddr - S->Address;
  419.                         const aint slotLength = std::min(S->Size - offset, length);
  420.                         memcpy(bptr, S->Page->RAM+offset, slotLength);
  421.                         bptr += slotLength;
  422.                         startAddr += slotLength;
  423.                         length -= slotLength;
  424.                 }
  425.                 assert(0 == length);
  426.                 return data;
  427.         }
  428.  
  429. /* //unused currently, TODO ask Oli about it?
  430.         static constexpr byte basicToHWColor[] = {
  431.                 0x54, 0x44, 0x55, 0x5C, 0x58, 0x5D,     0x4C, 0x45,
  432.                 0x4D, 0x56,     0x46, 0x57,     0x5E, 0x40,     0x5F, 0x4E,
  433.                 0x47, 0x4F,     0x52, 0x42,     0x53, 0x5A,     0x59, 0x5B,
  434.                 0x4A, 0x43,     0x4B, 0x41,     0x48, 0x49,     0x50, 0x51,
  435.         };
  436. */
  437. }
  438.  
  439. static void createCDTDump464(const char* fname, aint startAddr, byte screenMode, const byte* palette) {
  440.  
  441.         byte* ramptr;
  442.         aint ram_size = 0xC000; // 3 x 16K pages (eg: excl screen)
  443.         std::unique_ptr<byte[]> ram(new byte[ram_size]);
  444.         ramptr = ram.get();
  445.         memcpy(ramptr + 0x0000, Device->GetPage(0)->RAM, 0x4000);
  446.         memcpy(ramptr + 0x4000, Device->GetPage(1)->RAM, 0x4000);
  447.         memcpy(ramptr + 0x8000, Device->GetPage(2)->RAM, 0x4000);
  448.  
  449.         if (screenMode > 2) {
  450.                 screenMode = 0xFF; // Turn mode & palette select off
  451.         }
  452.         bool hasScreen = CDTUtil::hasScreen();
  453.         aint ramBase = CDTUtil::calcRAMStart(ramptr, ram_size);
  454.         aint ramEnd = CDTUtil::calcRAMLength(ramptr, ram_size);
  455.  
  456.         if (0x10000 == ramBase || 0x0000 == ramEnd) {
  457.                 Error("[SAVECDT] Could not determine the start and end of the program", nullptr, SUPPRESS); return;
  458.         }
  459.  
  460.         aint ramUsed = ramEnd - ramBase;
  461.  
  462.         if (startAddr < 0) {
  463.                 startAddr = ramBase;
  464.         }
  465.  
  466.         // construct the asm loader
  467.         byte loader[SaveCDT_AmstradCPC464_Len];
  468.         memcpy(loader, SaveCDT_AmstradCPC464, SaveCDT_AmstradCPC464_Len);
  469.  
  470.         // loader settings
  471.         loader[SaveCDT_AmstradCPC464_Settings + 0x0] = hasScreen;
  472.         loader[SaveCDT_AmstradCPC464_Settings + 0x1] = startAddr & 0xFF;
  473.         loader[SaveCDT_AmstradCPC464_Settings + 0x2] = startAddr >> 8;
  474.  
  475.         loader[SaveCDT_AmstradCPC464_Settings + 0x3] = ramBase & 0xFF;
  476.         loader[SaveCDT_AmstradCPC464_Settings + 0x4] = ramBase >> 8;
  477.  
  478.         loader[SaveCDT_AmstradCPC464_Settings + 0x5] = ramUsed & 0xFF;
  479.         loader[SaveCDT_AmstradCPC464_Settings + 0x6] = ramUsed >> 8;
  480.  
  481.         // Create an empty file with a 2s pause to start with
  482.         TZX_CreateEmpty(fname);
  483.         TZX_AppendPauseBlock(fname, 2000);
  484.  
  485.         // append a CPC basic loader which will run the asm loader
  486.         CDTUtil::writeBASICLoader(fname, screenMode, palette);
  487.  
  488.         // append the asm loader program
  489.         CDTUtil::writeUserProgram(fname, "CODE", loader, SaveCDT_AmstradCPC464_Len, SaveCDT_AmstradCPC464_ORG, SaveCDT_AmstradCPC464_ORG);
  490.  
  491.         // append screen if we have one
  492.         if (hasScreen) {
  493.                 CDTUtil::writeChunkedData(fname, Device->GetPage(3)->RAM, Device->GetPage(3)->Size, CDTUtil::DefaultPause, CDTUtil::BlockTypeData);
  494.         }
  495.         // finally write the main code
  496.         CDTUtil::writeChunkedData(fname, ramptr + ramBase, ramUsed, CDTUtil::DefaultPause, CDTUtil::BlockTypeData);
  497. }
  498.  
  499. static void createCDTDump6128(const char* fname, aint startAddr, byte screenMode, const byte* palette) {
  500.  
  501.         byte* ramptr;
  502.         aint ram_size = 0xC000; // 3 x 16K pages (eg: excl screen)
  503.         std::unique_ptr<byte[]> ram(new byte[ram_size]);
  504.         ramptr = ram.get();
  505.         memcpy(ramptr + 0x0000, Device->GetPage(0)->RAM, 0x4000);
  506.         memcpy(ramptr + 0x4000, Device->GetPage(1)->RAM, 0x4000);
  507.         memcpy(ramptr + 0x8000, Device->GetPage(2)->RAM, 0x4000);
  508.  
  509.         if (screenMode > 2) {
  510.                 screenMode = 0xFF; // Turn mode & palette select off
  511.         }
  512.         bool hasScreen = CDTUtil::hasScreen();
  513.         aint ramBase = CDTUtil::calcRAMStart(ramptr, ram_size);
  514.         aint ramEnd = CDTUtil::calcRAMLength(ramptr, ram_size);
  515.  
  516.         if (0x10000 == ramBase || 0x0000 == ramEnd) {
  517.                 Error("[SAVECDT] Could not determine the start and end of the program", nullptr, SUPPRESS); return;
  518.         }
  519.  
  520.         aint ramUsed = ramEnd - ramBase;
  521.  
  522.         if (startAddr < 0) {
  523.                 startAddr = ramBase;
  524.         }
  525.  
  526.         // the max possible length for the loader
  527.         constexpr aint max_loader_len = SaveCDT_AmstradCPC6128_Len + (SaveCDT_AmstradCPC6128_PageEntrySize * 4);
  528.         // construct the asm loader
  529.         byte loader[max_loader_len];
  530.         memcpy(loader, SaveCDT_AmstradCPC6128, SaveCDT_AmstradCPC6128_Len);
  531.         aint loader_actual_len = SaveCDT_AmstradCPC6128_Len;
  532.  
  533.         // loader settings
  534.         loader[SaveCDT_AmstradCPC6128_Settings + 0x0] = hasScreen;
  535.         loader[SaveCDT_AmstradCPC6128_Settings + 0x1] = startAddr & 0xFF;
  536.         loader[SaveCDT_AmstradCPC6128_Settings + 0x2] = startAddr >> 8;
  537.  
  538.         loader[SaveCDT_AmstradCPC6128_Settings + 0x3] = ramBase & 0xFF;
  539.         loader[SaveCDT_AmstradCPC6128_Settings + 0x4] = ramBase >> 8;
  540.  
  541.         loader[SaveCDT_AmstradCPC6128_Settings + 0x5] = ramUsed & 0xFF;
  542.         loader[SaveCDT_AmstradCPC6128_Settings + 0x6] = ramUsed >> 8;
  543.  
  544.         byte* loader_pages = loader + SaveCDT_AmstradCPC6128_Pages;
  545.         byte* loader_entries = loader_pages + 1;
  546.  
  547.         unsigned char* pages_ram[4];
  548.         aint pages_len[4];
  549.         aint pages_start[4];
  550.         aint count = 0;
  551.  
  552.         // Ignore the lower 64K at this time!
  553.         for (aint i = 4; i < Device->PagesCount; i++) {
  554.                 {
  555.                         // Calc start and end of this block
  556.                         aint base = CDTUtil::calcRAMStart(Device->GetPage(i)->RAM, 0x4000);
  557.                         aint length = CDTUtil::calcRAMLength(Device->GetPage(i)->RAM, 0x4000);
  558.  
  559.                         if (0x10000 == base || 0 == length) {
  560.                                 continue;
  561.                         }
  562.  
  563.                         loader_entries[0] = i;  // configuration (4-7)
  564.                         loader_entries[1] = base & 0xFF;
  565.                         loader_entries[2] = (base >> 8) & 0xFF;
  566.                         loader_entries[3] = length & 0xFF;
  567.                         loader_entries[4] = (length >> 8) & 0xFF;
  568.  
  569.                         loader_actual_len += SaveCDT_AmstradCPC6128_PageEntrySize;
  570.                         loader_entries += SaveCDT_AmstradCPC6128_PageEntrySize;
  571.  
  572.                         pages_ram[count] = Device->GetPage(i)->RAM;
  573.                         pages_start[count] = base;
  574.                         pages_len[count] = length;
  575.  
  576.                         ++count;
  577.                 }
  578.         }
  579.  
  580.         loader_pages[0] = count;
  581.  
  582.         // Create an empty file with a 2s pause to start with
  583.         TZX_CreateEmpty(fname);
  584.         TZX_AppendPauseBlock(fname, 2000);
  585.  
  586.         // append a CPC basic loader which will run the asm loader
  587.         CDTUtil::writeBASICLoader(fname, screenMode, palette);
  588.  
  589.         // append the asm loader program
  590.         CDTUtil::writeUserProgram(fname, "CODE", loader, loader_actual_len, SaveCDT_AmstradCPC6128_ORG, SaveCDT_AmstradCPC6128_ORG);
  591.  
  592.         // append screen if we have one
  593.         if (hasScreen) {
  594.                 CDTUtil::writeChunkedData(fname, Device->GetPage(3)->RAM, Device->GetPage(3)->Size, CDTUtil::DefaultPause, CDTUtil::BlockTypeData);
  595.         }
  596.  
  597.         // Write each of the pages
  598.         for (aint i = 0; i < count; ++i) {
  599.                 CDTUtil::writeChunkedData(fname, pages_ram[i] + pages_start[i], pages_len[i], CDTUtil::DefaultPause, CDTUtil::BlockTypeData);
  600.         }
  601.  
  602.         // Now drop the rest of the low 64k
  603.         CDTUtil::writeChunkedData(fname, ramptr + ramBase, ramUsed, CDTUtil::DefaultPause, CDTUtil::BlockTypeData);
  604. }
  605.  
  606. static void SaveCDT_SnapshotWithPalette(const char* fname, aint startAddr, byte screenMode, const byte* palette) {
  607.         isCPC6128() ?
  608.                 createCDTDump6128(fname, startAddr, screenMode, palette) :
  609.                 createCDTDump464(fname, startAddr, screenMode, palette);
  610. }
  611.  
  612. static void SaveCDT_Snapshot(const char* fname, aint startAddr) {
  613.         // Default mode after loading from BASIC
  614.         constexpr byte mode = 1;
  615.         // Default ROM palette
  616.         constexpr byte palette[] = {
  617.                  1,
  618.                  1, 24, 20,  6, 26,  0,  2,  8,
  619.                 10, 12, 14, 16, 18, 22, 24, 16,
  620.         };
  621.         SaveCDT_SnapshotWithPalette(fname, startAddr, mode, palette);
  622. }
  623.  
  624. static void SaveCDT_BASIC(const char* fname, const char* tfname, aint startAddr, aint length) {
  625.         std::unique_ptr<byte[]> data(CDTUtil::getContigRAM(startAddr, length));
  626.  
  627.         CDTUtil::writeTapeFile(fname, tfname, CDTUtil::FileTypeBASIC, data.get(), length, startAddr, 0x0000, CDTUtil::DefaultPause);
  628. }
  629.  
  630. static void SaveCDT_Code(const char* fname, const char* tfname, aint startAddr, aint length, aint entryAddr) {
  631.         if (entryAddr < 0) entryAddr = startAddr;
  632.  
  633.         std::unique_ptr<byte[]> data(CDTUtil::getContigRAM(startAddr, length));
  634.         CDTUtil::writeTapeFile(fname, tfname, CDTUtil::FileTypeBINARY, data.get(), length, startAddr, entryAddr, CDTUtil::DefaultPause);
  635. }
  636.  
  637. static void SaveCDT_Headless(const char* fname, aint startAddr, aint length, byte sync, ECDTHeadlessFormat format) {
  638.         assert(ECDTHeadlessFormat::AMSTRAD == format || ECDTHeadlessFormat::SPECTRUM == format);
  639.         std::unique_ptr<byte[]> data(CDTUtil::getContigRAM(startAddr, length));
  640.  
  641.         if (format == ECDTHeadlessFormat::AMSTRAD) CDTUtil::writeChunkedData(fname, data.get(), length, CDTUtil::DefaultPause, sync);
  642.         else TZX_AppendStandardBlock(fname, data.get(), length, CDTUtil::DefaultPause, sync);
  643. }
  644.  
  645. typedef void (*savecdt_command_t)(const char*);
  646.  
  647. // Creates a CDT tape file of a full memory snapshot, with loader
  648. static void dirSAVECDTFull(const char* cdtname) {
  649.         constexpr const char* argerr = "[SAVECDT] Invalid args. SAVECDT FULL <cdtname>[,<startaddr>[,<screenmode>[,<border>[,<ink0>...<ink15>]]]]";
  650.  
  651.         aint args[] = {
  652.                 StartAddress,
  653.                 0xFF, 0, // mode, border
  654.                 0, 0, 0, 0, 0, 0, 0, 0, // palette
  655.                 0, 0, 0, 0, 0, 0, 0, 0,
  656.         };
  657.  
  658.         bool opt[] = {
  659.                 false// this is used only when comma was parsed after cdtname => not optional then
  660.                 true, true,
  661.                 true, true, true, true, true, true, true, true,
  662.                 true, true, true, true, true, true, true, true,
  663.         };
  664.  
  665.         if (anyComma(lp) && !getIntArguments<19>(lp, args, opt)) {
  666.                 Error(argerr, lp, SUPPRESS); return;
  667.         }
  668.  
  669.         if (args[1] != 0xFF) {
  670.                 byte palette[17];
  671.                 for (aint i = 0; i < 17; ++i) {
  672.                         palette[i] = args[2 + i];
  673.                 }
  674.  
  675.                 SaveCDT_SnapshotWithPalette(cdtname, args[0], args[1], palette);
  676.         }
  677.         else {
  678.                 SaveCDT_Snapshot(cdtname, args[0]);
  679.         }
  680. }
  681.  
  682. static void dirSAVECDTEmpty(const char* cdtname) {
  683.         // EMPTY <cdtname>
  684.         TZX_CreateEmpty(cdtname);
  685. }
  686.  
  687. static void dirSAVECDTBasic(const char* cdtname) {
  688.         constexpr const char* argerr = "[SAVECDT] Invalid args. SAVECDT BASIC <cdtname>,<name>,<start>,<length>";
  689.  
  690.         if (!anyComma(lp)) {
  691.                 Error(argerr, lp, SUPPRESS); return;
  692.         }
  693.  
  694.         std::unique_ptr<char[]> tfname(GetFileName(lp));       
  695.         if (!anyComma(lp)) {
  696.                 Error(argerr, lp, SUPPRESS); return;
  697.         }
  698.  
  699.         aint args[] = { /*0:start*/ 0, /*1:length*/ 0 };
  700.         bool opt[] = { false, false };
  701.         if (!getIntArguments<2>(lp, args, opt) || args[0] < 0 || args[1] < 1 || 0x10000 <= args[1] || 0x10000 < (args[0]+args[1])) {
  702.                 Error(argerr, lp, SUPPRESS); return;
  703.         }
  704.  
  705.         SaveCDT_BASIC(cdtname, tfname.get(), args[0], args[1]);
  706. }
  707.  
  708. static void dirSAVECDTCode(const char* cdtname) {
  709.         constexpr const char* argerr = "[SAVECDT] Invalid args. SAVECDT CODE <cdtname>,<name>,<start>,<length>[,<customstartaddress>]";
  710.  
  711.         if (!anyComma(lp)) {
  712.                 Error(argerr, lp, SUPPRESS); return;
  713.         }
  714.  
  715.         std::unique_ptr<char[]> tfname(GetFileName(lp));
  716.         if (!anyComma(lp)) {
  717.                 Error(argerr, lp, SUPPRESS); return;
  718.         }
  719.  
  720.         aint args[] = { /*0:start*/ 0, /*1:length*/ 0, /*2:customStart*/ -1 };
  721.         bool opt[] = { false, false, true };
  722.         if (!getIntArguments<3>(lp, args, opt) || args[0] < 0 || args[1] < 1 || 0x10000 <= args[1] || 0x10000 < (args[0]+args[1])) {
  723.                 Error(argerr, lp, SUPPRESS); return;
  724.         }
  725.  
  726.         SaveCDT_Code(cdtname, tfname.get(), args[0], args[1], args[2]);
  727. }
  728.  
  729. static void dirSAVECDTHeadless(const char* cdtname) {
  730.         constexpr const char* argerr = "[SAVECDT] Invalid args. SAVECDT HEADLESS <cdtname>,<start>,<length>[,<sync>[,<format>]]";
  731.  
  732.         if (!anyComma(lp)) {
  733.                 Error(argerr, lp, SUPPRESS); return;
  734.         }
  735.  
  736.         aint args[] = { /*0:start*/ 0, /*1:length*/ 0, /*2:sync*/ CDTUtil::BlockTypeData, /*3:format*/ 0 };
  737.         bool opt[] = { false, false, true, true };
  738.         if (!getIntArguments<4>(lp, args, opt) || args[0] < 0 || args[1] < 1 || 0x10000 <= args[1] || 0x10000 < (args[0]+args[1])) {
  739.                 Error(argerr, lp, SUPPRESS); return;
  740.         }
  741.  
  742.         ECDTHeadlessFormat format;
  743.         switch (args[3]) {
  744.         case 0:
  745.                 format = ECDTHeadlessFormat::AMSTRAD;
  746.                 break;
  747.         case 1:
  748.                 format = ECDTHeadlessFormat::SPECTRUM;
  749.                 break;
  750.         default:
  751.                 Error("[SAVECDT HEADLESS] invalid format flag. Expected 0 (AMSTRAD) or 1 (SPECTRUM).", NULL, SUPPRESS); return;
  752.         }
  753.  
  754.         SaveCDT_Headless(cdtname, args[0], args[1], args[2], format);
  755. }
  756.  
  757. static void cdtParseFnameAndExecuteCmd(savecdt_command_t command_fn) {
  758.         std::unique_ptr<char[]> cdtname(GetOutputFileName(lp));
  759.         if (cdtname[0]) command_fn(cdtname.get());
  760.         else Error("[SAVECDT] CDT file name is empty", bp, SUPPRESS);
  761. }
  762.  
  763. void dirSAVECDT() {
  764.         if (pass != LASTPASS) {
  765.                 SkipToEol(lp);
  766.                 return;
  767.         }
  768.         if (!IsAmstradCPCDevice(DeviceID)) {
  769.                 Error("[SAVECDT] is allowed only in AMSTRADCPC464 or AMSTRADCPC6128 device mode", NULL, SUPPRESS);
  770.                 return;
  771.         }
  772.         SkipBlanks(lp);
  773.         if (cmphstr(lp, "full")) cdtParseFnameAndExecuteCmd(dirSAVECDTFull);
  774.         else if (cmphstr(lp, "empty")) cdtParseFnameAndExecuteCmd(dirSAVECDTEmpty);
  775.         else if (cmphstr(lp, "basic")) cdtParseFnameAndExecuteCmd(dirSAVECDTBasic);
  776.         else if (cmphstr(lp, "code")) cdtParseFnameAndExecuteCmd(dirSAVECDTCode);
  777.         else if (cmphstr(lp, "headless")) cdtParseFnameAndExecuteCmd(dirSAVECDTHeadless);
  778.         else Error("[SAVECDT] unknown command (commands: FULL, EMPTY, BASIC, CODE, HEADLESS)", lp, SUPPRESS);  
  779. }
  780.  
  781. // eof io_cpc.cpp
  782.