1770 lines
		
	
	
		
			63 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			1770 lines
		
	
	
		
			63 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
#include <stdint.h>
 | 
						|
#include <stdio.h>
 | 
						|
#include <stdlib.h>
 | 
						|
#include <string>
 | 
						|
#include <fstream>
 | 
						|
#include <algorithm>
 | 
						|
#include <string.h>
 | 
						|
 | 
						|
#ifdef _WIN32
 | 
						|
#include <windows.h>
 | 
						|
#include <direct.h>
 | 
						|
#include "dirent/dirent.h"
 | 
						|
#ifdef max
 | 
						|
#undef max
 | 
						|
#endif
 | 
						|
#ifdef min
 | 
						|
#undef min
 | 
						|
#endif
 | 
						|
#else
 | 
						|
#include <limits.h>
 | 
						|
#include <unistd.h>
 | 
						|
#include <dirent.h>
 | 
						|
#endif
 | 
						|
 | 
						|
#ifndef STAND_ALONE
 | 
						|
#include "editor.h"
 | 
						|
#include "timing.h"
 | 
						|
#include "graphics.h"
 | 
						|
#include "inih/INIReader.h"
 | 
						|
#include "rs232/rs232.h"
 | 
						|
#include "spi.h"
 | 
						|
#endif
 | 
						|
 | 
						|
#include "memory.h"
 | 
						|
#include "cpu.h"
 | 
						|
#include "audio.h"
 | 
						|
#include "loader.h"
 | 
						|
#include "expression.h"
 | 
						|
#include "assembler.h"
 | 
						|
#include "compiler.h"
 | 
						|
#include "keywords.h"
 | 
						|
#include "validater.h"
 | 
						|
 | 
						|
 | 
						|
#define DEFAULT_COM_BAUD_RATE 115200
 | 
						|
#define DEFAULT_COM_PORT      0
 | 
						|
#define DEFAULT_GIGA_TIMEOUT  5.0
 | 
						|
#define MAX_GT1_SIZE          (1<<16)
 | 
						|
 | 
						|
 | 
						|
namespace Loader
 | 
						|
{
 | 
						|
    bool _hostIsBigEndian = false;
 | 
						|
 | 
						|
    std::string _exePath = ".";
 | 
						|
    std::string _cwdPath = ".";
 | 
						|
    std::string _filePath = ".";
 | 
						|
 | 
						|
 | 
						|
    const std::string& getExePath(void) {return _exePath;}
 | 
						|
    const std::string& getCwdPath(void) {return _cwdPath;}
 | 
						|
    const std::string& getFilePath(void) {return _filePath;}
 | 
						|
    void setFilePath(const std::string& filePath) {_filePath = filePath;}
 | 
						|
 | 
						|
#ifdef _WIN32
 | 
						|
    char* getcwd(char* dst, int size)
 | 
						|
    {
 | 
						|
        return _getcwd(dst, size);
 | 
						|
    }
 | 
						|
    int chdir(const char* path)
 | 
						|
    {
 | 
						|
        return _chdir(path);
 | 
						|
    }
 | 
						|
    std::string getExeDir(void)
 | 
						|
    {
 | 
						|
        char dir[MAX_PATH] = {0};
 | 
						|
        GetModuleFileName(NULL, dir, MAX_PATH);
 | 
						|
        std::string path = dir;
 | 
						|
        size_t slash = path.find_last_of("\\/");
 | 
						|
        path = (slash != std::string::npos) ? path.substr(0, slash) : ".";
 | 
						|
        Expression::replaceText(path, "\\", "/");
 | 
						|
        return path;
 | 
						|
    }
 | 
						|
#else
 | 
						|
    std::string getExeDir(void)
 | 
						|
    {
 | 
						|
        char dir[PATH_MAX];
 | 
						|
        ssize_t result = readlink("/proc/self/exe", dir, PATH_MAX);
 | 
						|
        std::string path = (result > 0) ? dir : ".";
 | 
						|
        size_t slash = path.find_last_of("\\/");
 | 
						|
        path = (slash != std::string::npos) ? path.substr(0, slash) : ".";
 | 
						|
        Expression::replaceText(path, "\\", "/");
 | 
						|
        return path;
 | 
						|
    }
 | 
						|
#endif
 | 
						|
 | 
						|
    bool loadGt1File(const std::string& filename, Gt1File& gt1File)
 | 
						|
    {
 | 
						|
        std::ifstream infile(filename, std::ios::binary | std::ios::in);
 | 
						|
        if(!infile.is_open())
 | 
						|
        {
 | 
						|
            fprintf(stderr, "Loader::loadGt1File() : failed to open '%s'\n", filename.c_str());
 | 
						|
            return false;
 | 
						|
        }
 | 
						|
 | 
						|
        int segmentCount = 1;
 | 
						|
        for(;;)
 | 
						|
        {
 | 
						|
            // Read segment header
 | 
						|
            Gt1Segment segment;
 | 
						|
            infile.read((char *)&segment._hiAddress, SEGMENT_HEADER_SIZE);
 | 
						|
            if(infile.eof() || infile.bad() || infile.fail())
 | 
						|
            {
 | 
						|
                fprintf(stderr, "Loader::loadGt1File() : bad header in segment %d of '%s'\n", segmentCount, filename.c_str());
 | 
						|
                return false;
 | 
						|
            }
 | 
						|
 | 
						|
            // Finished
 | 
						|
            if(segment._hiAddress == 0x00  &&  infile.peek() == EOF)
 | 
						|
            {
 | 
						|
                // Segment header aligns with Gt1File terminator, hiStart and loStart
 | 
						|
                gt1File._hiStart = segment._loAddress;
 | 
						|
                gt1File._loStart = segment._segmentSize;
 | 
						|
                break;
 | 
						|
            }
 | 
						|
 | 
						|
            // Read segment
 | 
						|
            int segmentSize = (segment._segmentSize == 0) ? SEGMENT_SIZE : segment._segmentSize;
 | 
						|
            segment._dataBytes.resize(segmentSize);
 | 
						|
            infile.read((char *)&segment._dataBytes[0], segmentSize);
 | 
						|
            if(infile.eof() || infile.bad() || infile.fail())
 | 
						|
            {
 | 
						|
                fprintf(stderr, "Loader::loadGt1File() : bad segment %d in '%s'\n", segmentCount, filename.c_str());
 | 
						|
                return false;
 | 
						|
            }
 | 
						|
 | 
						|
            gt1File._segments.push_back(segment);
 | 
						|
            segmentCount++;
 | 
						|
        }
 | 
						|
 | 
						|
        return true;
 | 
						|
    }
 | 
						|
 | 
						|
    bool saveGt1File(const std::string& filepath, Gt1File& gt1File, std::string& filename)
 | 
						|
    {
 | 
						|
        if(gt1File._segments.size() == 0)
 | 
						|
        {
 | 
						|
            fprintf(stderr, "Loader::saveGt1File() : zero segments, not saving.\n");
 | 
						|
            return false;
 | 
						|
        }
 | 
						|
 | 
						|
        size_t dot = filepath.rfind('.');
 | 
						|
        filename = (dot != std::string::npos) ? filepath.substr(0, dot) + ".gt1" : filepath + ".gt1";
 | 
						|
 | 
						|
        std::ofstream outfile(filename, std::ios::binary | std::ios::out);
 | 
						|
        if(!outfile.is_open())
 | 
						|
        {
 | 
						|
            fprintf(stderr, "Loader::saveGt1File() : failed to open '%s'\n", filename.c_str());
 | 
						|
            return false;
 | 
						|
        }
 | 
						|
 | 
						|
        // Sort segments from lowest address to highest address
 | 
						|
        std::sort(gt1File._segments.begin(), gt1File._segments.end(), [](const Gt1Segment& segmentA, const Gt1Segment& segmentB)
 | 
						|
        {
 | 
						|
            uint16_t addressA = (segmentA._hiAddress <<8) | segmentA._loAddress;
 | 
						|
            uint16_t addressB = (segmentB._hiAddress <<8) | segmentB._loAddress;
 | 
						|
            return (addressA < addressB);
 | 
						|
        });
 | 
						|
 | 
						|
        // Merge page 0 segments together
 | 
						|
        Gt1Segment page0;
 | 
						|
        int page0Segments = 0;
 | 
						|
        for(int i=0; i<int(gt1File._segments.size()); i++) if(gt1File._segments[i]._hiAddress == 0x00) page0Segments++;
 | 
						|
        if(page0Segments > 1)
 | 
						|
        {
 | 
						|
            uint8_t start = gt1File._segments[0]._loAddress;
 | 
						|
            uint8_t end = gt1File._segments[page0Segments-1]._loAddress + uint8_t(gt1File._segments[page0Segments-1]._dataBytes.size()) - 1;
 | 
						|
 | 
						|
            // Reserve space taking into account ONE_CONST_ADDRESS
 | 
						|
            page0._loAddress = start;
 | 
						|
            page0._segmentSize = end - start + 1;
 | 
						|
            page0._dataBytes.resize(end - start + 1, 0x00);
 | 
						|
            if(start <= ONE_CONST_ADDRESS && end >= ONE_CONST_ADDRESS) page0._dataBytes[ONE_CONST_ADDRESS-start] = 0x01;
 | 
						|
 | 
						|
            // Copy page 0 segments
 | 
						|
            fprintf(stderr, "\n* Merging %d page 0 segments\n", page0Segments);
 | 
						|
            for(int i=0; i<page0Segments; i++)
 | 
						|
            {
 | 
						|
                int j = 0;
 | 
						|
                int seg = gt1File._segments[i]._loAddress - start;
 | 
						|
                if(page0._dataBytes.size() < seg + gt1File._segments[i]._dataBytes.size())
 | 
						|
                {
 | 
						|
                    fprintf(stderr, "* Can't Merge: start: 0x%0x  end: 0x%02x  size: 0x%02x\n", gt1File._segments[0]._loAddress, 
 | 
						|
                                                                                                gt1File._segments[0]._loAddress + uint8_t(gt1File._segments[0]._dataBytes.size()) - 1, 
 | 
						|
                                                                                                uint8_t(gt1File._segments[0]._dataBytes.size()));
 | 
						|
                    return false;
 | 
						|
                }
 | 
						|
 | 
						|
                for(int k=seg; k<seg+int(gt1File._segments[i]._dataBytes.size()); k++)
 | 
						|
                {
 | 
						|
                    page0._dataBytes[k] = gt1File._segments[i]._dataBytes[j++];
 | 
						|
                }
 | 
						|
                fprintf(stderr, "* Segment: %03d  start: 0x%0x  end: 0x%02x  size: 0x%02x\n", i, gt1File._segments[i]._loAddress, 
 | 
						|
                                                                                                 gt1File._segments[i]._loAddress + uint8_t(gt1File._segments[i]._dataBytes.size()) - 1, 
 | 
						|
                                                                                                 uint8_t(gt1File._segments[i]._dataBytes.size()));
 | 
						|
            }
 | 
						|
 | 
						|
            // Erase old page 0 segments
 | 
						|
            for(int i=0; i<page0Segments; i++) gt1File._segments.erase(gt1File._segments.begin());
 | 
						|
 | 
						|
            // Insert merged page 0 segment
 | 
						|
            gt1File._segments.insert(gt1File._segments.begin(), page0);
 | 
						|
            fprintf(stderr, "* Merged:       start: 0x%0x  end: 0x%02x  size: 0x%02x\n", gt1File._segments[0]._loAddress, 
 | 
						|
                                                                                         gt1File._segments[0]._loAddress + uint8_t(gt1File._segments[0]._dataBytes.size()) - 1, 
 | 
						|
                                                                                         uint8_t(gt1File._segments[0]._dataBytes.size()));
 | 
						|
        }
 | 
						|
 | 
						|
#if 1
 | 
						|
        // Merge non page 0 segments together
 | 
						|
        std::vector<Gt1Segment> segmentsOut;
 | 
						|
        int lastPage = int((Memory::getSizeRAM() - 1) >>8);
 | 
						|
        for(int j=1; j<=lastPage; j++)
 | 
						|
        {
 | 
						|
            uint8_t hiAddr = uint8_t(j);
 | 
						|
            std::vector<Gt1Segment> segmentsIn;
 | 
						|
            for(int i=0; i<int(gt1File._segments.size()); i++)
 | 
						|
            {
 | 
						|
                if(gt1File._segments[i]._hiAddress == hiAddr) segmentsIn.push_back(gt1File._segments[i]);
 | 
						|
            }
 | 
						|
 | 
						|
            if(segmentsIn.size())
 | 
						|
            {
 | 
						|
                // Sort segmentsIn from lowest low byte address to highest low byte address
 | 
						|
                std::sort(segmentsIn.begin(), segmentsIn.end(), [](const Gt1Segment& segmentA, const Gt1Segment& segmentB)
 | 
						|
                {
 | 
						|
                    return (segmentA._loAddress < segmentB._loAddress);
 | 
						|
                });
 | 
						|
 | 
						|
                bool lastSegFractured = false;
 | 
						|
                Gt1Segment segment = {false, hiAddr, segmentsIn[0]._loAddress, uint8_t(segmentsIn[0]._dataBytes.size()), segmentsIn[0]._dataBytes};
 | 
						|
                for(int i=0; i<int(segmentsIn.size()); i++)
 | 
						|
                {
 | 
						|
                    if(segment._dataBytes.size() == 0)
 | 
						|
                    {
 | 
						|
                        lastSegFractured = false;
 | 
						|
                        segment._loAddress = segmentsIn[i]._loAddress;
 | 
						|
                        segment._dataBytes = segmentsIn[i]._dataBytes;
 | 
						|
                        segment._segmentSize = uint8_t(segmentsIn[i]._dataBytes.size());
 | 
						|
                    }
 | 
						|
 | 
						|
                    if(i<int(segmentsIn.size()-1)  &&  segmentsIn[i]._loAddress + segmentsIn[i]._dataBytes.size()  ==  segmentsIn[i + 1]._loAddress)
 | 
						|
                    {
 | 
						|
                        segment._dataBytes.insert(segment._dataBytes.end(), segmentsIn[i + 1]._dataBytes.begin(), segmentsIn[i + 1]._dataBytes.end());
 | 
						|
 | 
						|
                        uint16_t segmentSize = segment._segmentSize + segmentsIn[i + 1]._segmentSize;
 | 
						|
                        if(segmentSize > SEGMENT_SIZE)
 | 
						|
                        {
 | 
						|
                            fprintf(stderr, "Loader::saveGt1File() : total segment size:%d > %d in segment %d\n", segment._segmentSize, SEGMENT_SIZE, i);
 | 
						|
                            return false;
 | 
						|
                        }
 | 
						|
 | 
						|
                        segment._segmentSize += segmentsIn[i + 1]._segmentSize;
 | 
						|
                        if(segmentSize == SEGMENT_SIZE) segment._segmentSize = 0;
 | 
						|
                    }
 | 
						|
                    else
 | 
						|
                    {
 | 
						|
                        lastSegFractured = true;
 | 
						|
                        segmentsOut.push_back(segment);
 | 
						|
                        segment._dataBytes.clear();
 | 
						|
                    }
 | 
						|
                }
 | 
						|
                if(!lastSegFractured) segmentsOut.push_back(segment);
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        // Erase pages 1 to N and insert new pages, (page 0 exists and has already been merged into first segment)
 | 
						|
        if(gt1File._segments[0]._hiAddress == 0x00)
 | 
						|
        {
 | 
						|
            gt1File._segments.erase(gt1File._segments.begin() + 1, gt1File._segments.end());
 | 
						|
        }
 | 
						|
        // Erase all pages and insert new pages
 | 
						|
        else
 | 
						|
        {
 | 
						|
            gt1File._segments.clear();
 | 
						|
        }
 | 
						|
        gt1File._segments.insert(gt1File._segments.end(), segmentsOut.begin(), segmentsOut.end());
 | 
						|
#endif
 | 
						|
 | 
						|
        // Output
 | 
						|
        for(int i=0; i<int(gt1File._segments.size()); i++)
 | 
						|
        {
 | 
						|
            // Write header
 | 
						|
            outfile.write((char *)>1File._segments[i]._hiAddress, SEGMENT_HEADER_SIZE);
 | 
						|
            if(outfile.bad() || outfile.fail())
 | 
						|
            {
 | 
						|
                fprintf(stderr, "Loader::saveGt1File() : write error in header of segment %d\n", i);
 | 
						|
                return false;
 | 
						|
            }
 | 
						|
 | 
						|
            // Write segment
 | 
						|
            int segmentSize = (gt1File._segments[i]._segmentSize == 0) ? SEGMENT_SIZE : gt1File._segments[i]._segmentSize;
 | 
						|
            outfile.write((char *)>1File._segments[i]._dataBytes[0], segmentSize);
 | 
						|
            if(outfile.bad() || outfile.fail())
 | 
						|
            {
 | 
						|
                fprintf(stderr, "Loader::saveGt1File() : bad segment %d in '%s'\n", i, filename.c_str());
 | 
						|
                return false;
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        // Write trailer
 | 
						|
        outfile.write((char *)>1File._terminator, GT1FILE_TRAILER_SIZE);
 | 
						|
        if(outfile.bad() || outfile.fail())
 | 
						|
        {
 | 
						|
            fprintf(stderr, "Loader::saveGt1File() : write error in trailer of '%s'\n", filename.c_str());
 | 
						|
            return false;
 | 
						|
        }
 | 
						|
 | 
						|
        return true;
 | 
						|
    }
 | 
						|
 | 
						|
    uint16_t printGt1Stats(const std::string& filename, const Gt1File& gt1File, bool isGbasFile)
 | 
						|
    {
 | 
						|
        size_t nameSuffix = filename.find_last_of(".");
 | 
						|
        std::string output = filename.substr(0, nameSuffix) + ".gt1";
 | 
						|
 | 
						|
        // Header
 | 
						|
        uint16_t totalSize = 0;
 | 
						|
        for(int i=0; i<int(gt1File._segments.size()); i++)
 | 
						|
        {
 | 
						|
            // Don't count page 0 RAM usage or segments outside of current RAM size
 | 
						|
            if(gt1File._segments[i]._hiAddress)
 | 
						|
            {
 | 
						|
                uint16_t address = gt1File._segments[i]._loAddress + (gt1File._segments[i]._hiAddress <<8);
 | 
						|
                uint16_t segmentSize = (gt1File._segments[i]._segmentSize == 0) ? SEGMENT_SIZE : gt1File._segments[i]._segmentSize;
 | 
						|
                //if((address + segmentSize - 1) < Memory::getSizeRAM()  &&  !Memory::isVideoRAM(address)) totalSize += segmentSize;
 | 
						|
                if((address + segmentSize - 1) < Memory::getSizeRAM()) totalSize += segmentSize;
 | 
						|
                if((address & SEGMENT_MASK) + segmentSize > SEGMENT_SIZE)
 | 
						|
                {
 | 
						|
                    fprintf(stderr, "\nLoader::printGt1Stats() : Page overflow : segment %d : address 0x%04x : segmentSize %3d\n", i, address, segmentSize);
 | 
						|
                    return 0;
 | 
						|
                }
 | 
						|
            }
 | 
						|
        }
 | 
						|
        uint16_t startAddress = gt1File._loStart + (gt1File._hiStart <<8);
 | 
						|
        fprintf(stderr, "\n*******************************************************\n");
 | 
						|
        fprintf(stderr, "*                     Creating .gt1                    \n");
 | 
						|
        fprintf(stderr, "*******************************************************\n");
 | 
						|
        fprintf(stderr, "* %-20s : 0x%04x  : %5d bytes                          \n", output.c_str(), startAddress, totalSize);
 | 
						|
#if 1
 | 
						|
        fprintf(stderr, "*******************************************************\n");
 | 
						|
        fprintf(stderr, "*   Segment   :  Type  : Address :    Size             \n");
 | 
						|
        fprintf(stderr, "*******************************************************\n");
 | 
						|
 | 
						|
        // Segments
 | 
						|
        int contiguousSegments = 0;
 | 
						|
        int startContiguousSegment = 0;
 | 
						|
        uint16_t startContiguousAddress = 0x0000;
 | 
						|
        for(int i=0; i<int(gt1File._segments.size()); i++)
 | 
						|
        {
 | 
						|
            uint16_t address = gt1File._segments[i]._loAddress + (gt1File._segments[i]._hiAddress <<8);
 | 
						|
            uint16_t segmentSize = (gt1File._segments[i]._segmentSize == 0) ? SEGMENT_SIZE : gt1File._segments[i]._segmentSize;
 | 
						|
            std::string memory = "RAM";
 | 
						|
            if(gt1File._segments[i]._isRomAddress)
 | 
						|
            {
 | 
						|
                memory = "ROM";
 | 
						|
                if(gt1File._segments.size() == 1)
 | 
						|
                {
 | 
						|
                    fprintf(stderr, "*    %4d     :  %s   : 0x%04x  : %5d bytes\n", i, memory.c_str(), address, totalSize);
 | 
						|
                    fprintf(stderr, "*******************************************************\n");
 | 
						|
                    return totalSize;
 | 
						|
                }
 | 
						|
                totalSize -= segmentSize;
 | 
						|
            }
 | 
						|
            else if(segmentSize != int(gt1File._segments[i]._dataBytes.size()))
 | 
						|
            {
 | 
						|
                fprintf(stderr, "Segment %4d : %s 0x%04x : segmentSize %3d != dataBytes.size() %3d\n", i, memory.c_str(), address, segmentSize, int(gt1File._segments[i]._dataBytes.size()));
 | 
						|
                return 0;
 | 
						|
            }
 | 
						|
 | 
						|
            // New contiguous segment
 | 
						|
            if(segmentSize == SEGMENT_SIZE)
 | 
						|
            {
 | 
						|
                if(contiguousSegments == 0)
 | 
						|
                {
 | 
						|
                    startContiguousSegment = i;
 | 
						|
                    startContiguousAddress = address;
 | 
						|
                }
 | 
						|
                contiguousSegments++;
 | 
						|
            }
 | 
						|
            else
 | 
						|
            {
 | 
						|
                // Normal segment < 256 bytes
 | 
						|
                if(contiguousSegments == 0)
 | 
						|
                {
 | 
						|
                    fprintf(stderr, "*    %4d     :  %s   : 0x%04x  : %5d bytes\n", i, memory.c_str(), address, segmentSize);
 | 
						|
                }
 | 
						|
                // Contiguous segment < 256 bytes
 | 
						|
                else
 | 
						|
                {
 | 
						|
                    fprintf(stderr, "*    %4d     :  %s   : 0x%04x  : %5d bytes (%dx%d)\n", startContiguousSegment, memory.c_str(), startContiguousAddress, contiguousSegments*SEGMENT_SIZE, contiguousSegments, SEGMENT_SIZE);
 | 
						|
                    fprintf(stderr, "*    %4d     :  %s   : 0x%04x  : %5d bytes\n", i, memory.c_str(), address, segmentSize);
 | 
						|
                    contiguousSegments = 0;
 | 
						|
                }
 | 
						|
            }
 | 
						|
        }
 | 
						|
#endif
 | 
						|
        if(!isGbasFile) Memory::setSizeFreeRAM(Memory::getBaseFreeRAM() - totalSize); 
 | 
						|
 | 
						|
        int memSize = (Memory::getSizeRAM() == RAM_SIZE_LO) ? 32 : 64;
 | 
						|
        fprintf(stderr, "*******************************************************\n");
 | 
						|
        fprintf(stderr, "* Free RAM on %dK Gigatron       : %5d bytes           \n", memSize, Memory::getSizeFreeRAM());
 | 
						|
        fprintf(stderr, "*******************************************************\n");
 | 
						|
 | 
						|
        return totalSize;
 | 
						|
    }
 | 
						|
 | 
						|
 | 
						|
#ifndef STAND_ALONE
 | 
						|
    enum LoaderState {FirstByte=0, MsgLength, LowAddress, HighAddress, Message, LastByte, ResetIN, NumLoaderStates};
 | 
						|
    enum FrameState {Resync=0, Frame, Execute, NumFrameStates};
 | 
						|
 | 
						|
 | 
						|
    bool _disableUploads = false;
 | 
						|
 | 
						|
    int _gt1UploadSize = 0;
 | 
						|
    char _gt1Buffer[MAX_GT1_SIZE];
 | 
						|
 | 
						|
    int _numComPorts = 0;
 | 
						|
    int _currentComPort = -1;
 | 
						|
    bool _enableComPort = false;
 | 
						|
 | 
						|
    std::string _configComPort;
 | 
						|
    int _configBaudRate = DEFAULT_COM_BAUD_RATE;
 | 
						|
    double _configTimeOut = DEFAULT_GIGA_TIMEOUT;
 | 
						|
    
 | 
						|
    std::string _configGclBuild = ".";
 | 
						|
    bool _configGclBuildFound = false;
 | 
						|
 | 
						|
    bool _autoSet64k = true;
 | 
						|
 | 
						|
    std::vector<ConfigRom> _configRoms;
 | 
						|
 | 
						|
    std::string _currentGame = "";
 | 
						|
 | 
						|
    INIReader _configIniReader;
 | 
						|
    INIReader _highScoresIniReader;
 | 
						|
    std::map<std::string, SaveData> _saveData;
 | 
						|
 | 
						|
    SDL_Thread* _uploadThread = nullptr;
 | 
						|
 | 
						|
 | 
						|
    void shutdown(void)
 | 
						|
    {
 | 
						|
        if(_uploadThread) SDL_WaitThread(_uploadThread, nullptr);
 | 
						|
        closeComPort();
 | 
						|
    }
 | 
						|
 | 
						|
    bool getKeyAsString(INIReader& iniReader, const std::string& sectionString, const std::string& iniKey, const std::string& defaultKey, std::string& result, bool upperCase=true)
 | 
						|
    {
 | 
						|
        result = iniReader.Get(sectionString, iniKey, defaultKey);
 | 
						|
        if(result == defaultKey) return false;
 | 
						|
        if(upperCase) Expression::strToUpper(result);
 | 
						|
        return true;
 | 
						|
    }
 | 
						|
#endif
 | 
						|
 | 
						|
    void initialise(void)
 | 
						|
    {
 | 
						|
        // Current working directory
 | 
						|
        char cwdPath[FILENAME_MAX];
 | 
						|
        if(!getcwd(cwdPath, FILENAME_MAX)) strcpy(cwdPath, ".");
 | 
						|
        _cwdPath = std::string(cwdPath);
 | 
						|
        Expression::replaceText(_cwdPath, "\\", "/");
 | 
						|
        _exePath = getExeDir();
 | 
						|
 | 
						|
        if(Cpu::getHostEndianness() == Cpu::BigEndian) _hostIsBigEndian = true;
 | 
						|
 | 
						|
#ifndef STAND_ALONE
 | 
						|
        _enableComPort = false;
 | 
						|
        _currentComPort = -1;
 | 
						|
 | 
						|
        _numComPorts = comEnumerate();
 | 
						|
        if(_numComPorts == 0) fprintf(stderr, "Loader::initialise() : no COM ports found.\n");
 | 
						|
 | 
						|
        // Loader config
 | 
						|
        std::string configPath = _exePath + "/" + LOADER_CONFIG_INI;
 | 
						|
        //fprintf(stderr, "%s\n", configPath.c_str());
 | 
						|
        INIReader iniReader(configPath);
 | 
						|
        _configIniReader = iniReader;
 | 
						|
        if(_configIniReader.ParseError() == 0)
 | 
						|
        {
 | 
						|
            // Parse Loader Keys
 | 
						|
            enum Section {Comms, ROMS, RAM, Unk = -1 };
 | 
						|
            std::map<std::string, Section> section;
 | 
						|
            section["Comms"] = Comms;
 | 
						|
            section["ROMS"]  = ROMS;
 | 
						|
            section["RAM"]   = RAM;
 | 
						|
 | 
						|
            for(auto sectionString : _configIniReader.Sections())
 | 
						|
            {
 | 
						|
                std::string result;
 | 
						|
                Section sec = Unk;
 | 
						|
 | 
						|
                if(section.find(sectionString) != section.end())
 | 
						|
                    sec = section[sectionString];
 | 
						|
 | 
						|
                switch(sec)
 | 
						|
                {
 | 
						|
                    case Comms:
 | 
						|
                    {
 | 
						|
                        // Enable comms
 | 
						|
                        getKeyAsString(_configIniReader, sectionString, "Enable", "0", result, false);
 | 
						|
                        _enableComPort = strtol(result.c_str(), nullptr, 10);
 | 
						|
 | 
						|
                        // Baud rate
 | 
						|
                         getKeyAsString(_configIniReader, sectionString, "BaudRate", "115200", result);   
 | 
						|
                        _configBaudRate = strtol(result.c_str(), nullptr, 10);
 | 
						|
 
 | 
						|
                        // Com port
 | 
						|
                        char *endPtr;
 | 
						|
                        getKeyAsString(_configIniReader, sectionString, "ComPort", "0", result);
 | 
						|
                        _currentComPort = strtol(result.c_str(), &endPtr, 10);
 | 
						|
                        if((endPtr - &result[0]) != int(result.size()))
 | 
						|
                        {
 | 
						|
                            _currentComPort = comFindPort(result.c_str());
 | 
						|
                            if(_currentComPort < 0)
 | 
						|
                            {
 | 
						|
                                fprintf(stderr, "Loader::initialise() : Couldn't find COM Port '%s' in INI file '%s'\n", result.c_str(), LOADER_CONFIG_INI);
 | 
						|
                            }
 | 
						|
                        }
 | 
						|
                        _configComPort = result;
 | 
						|
 | 
						|
                        // Time out
 | 
						|
                        getKeyAsString(_configIniReader, sectionString, "TimeOut", "5.0", result);
 | 
						|
                        _configTimeOut = strtod(result.c_str(), nullptr);
 | 
						|
 | 
						|
                        // GCL tools build path
 | 
						|
                        _configGclBuildFound = getKeyAsString(_configIniReader, sectionString, "GclBuild", ".", result, false);
 | 
						|
                        _configGclBuild = result;
 | 
						|
                    }
 | 
						|
                    break;
 | 
						|
 | 
						|
                    case ROMS:
 | 
						|
                    {
 | 
						|
                        for(int index=0; ; index++)
 | 
						|
                        {
 | 
						|
                            ConfigRom configRom;
 | 
						|
 | 
						|
                            // ROM name
 | 
						|
                            std::string romName = "RomName" + std::to_string(index);
 | 
						|
                            if(!getKeyAsString(_configIniReader, sectionString, romName, "", result, false)) break;
 | 
						|
                            configRom._name = result;
 | 
						|
 | 
						|
                            // ROM type
 | 
						|
                            std::string romVer = "RomType" + std::to_string(index);
 | 
						|
                            if(!getKeyAsString(_configIniReader, sectionString, romVer, "", result)) break;
 | 
						|
                            configRom._type = uint8_t(std::stoul(result, nullptr, 16));
 | 
						|
 | 
						|
                            _configRoms.push_back(configRom);
 | 
						|
                        }
 | 
						|
                    }
 | 
						|
                    break;
 | 
						|
 | 
						|
                    case RAM:
 | 
						|
                    {
 | 
						|
                        // Enable auto 64K
 | 
						|
                        getKeyAsString(_configIniReader, sectionString, "AutoSet64k", "1", result, false);
 | 
						|
                        _autoSet64k = strtol(result.c_str(), nullptr, 10);
 | 
						|
                    }
 | 
						|
                    break;
 | 
						|
 | 
						|
                    default:
 | 
						|
                    {
 | 
						|
                        // Delegate to other ini handlers.
 | 
						|
                        if (! Spi::config(_configIniReader, sectionString))
 | 
						|
                            fprintf(stderr, "Loader::initialise() : INI file '%s' has bad Sections : reverting to default values.\n", LOADER_CONFIG_INI);
 | 
						|
                    }
 | 
						|
                    break;
 | 
						|
 | 
						|
                }
 | 
						|
            }
 | 
						|
        }
 | 
						|
        else
 | 
						|
        {
 | 
						|
            fprintf(stderr, "Loader::initialise() : couldn't find loader configuration INI file '%s' : reverting to default values.\n", LOADER_CONFIG_INI);
 | 
						|
        }
 | 
						|
 | 
						|
        // High score config
 | 
						|
        INIReader highScoresIniReader(_exePath + "/" + HIGH_SCORES_INI);
 | 
						|
        _highScoresIniReader = highScoresIniReader;
 | 
						|
        if(_highScoresIniReader.ParseError() == 0)
 | 
						|
        {
 | 
						|
            // Parse high scores INI file
 | 
						|
            for(auto game : _highScoresIniReader.Sections())
 | 
						|
            {
 | 
						|
                std::vector<uint16_t> counts;
 | 
						|
                std::vector<uint16_t> addresses;
 | 
						|
                std::vector<Endianness> endianness;
 | 
						|
                std::vector<std::vector<uint8_t>> data;
 | 
						|
 | 
						|
                int updateRate = uint16_t(_highScoresIniReader.GetReal(game, "updateRate", VSYNC_RATE));
 | 
						|
 | 
						|
                for(int index=0; ; index++)
 | 
						|
                {
 | 
						|
                    std::string count = "count" + std::to_string(index);
 | 
						|
                    std::string address = "address" + std::to_string(index);
 | 
						|
                    std::string endian = "endian" + std::to_string(index);
 | 
						|
                    if(_highScoresIniReader.Get(game, count, "") == "") break;
 | 
						|
                    if(_highScoresIniReader.Get(game, address, "") == "") break;
 | 
						|
                    endian = _highScoresIniReader.Get(game, endian, "little");
 | 
						|
                    counts.push_back(uint16_t(_highScoresIniReader.GetReal(game, count, -1)));
 | 
						|
                    addresses.push_back(uint16_t(_highScoresIniReader.GetReal(game, address, -1)));
 | 
						|
                    endianness.push_back((endian == "little") ? Little : Big);
 | 
						|
                    data.push_back(std::vector<uint8_t>(counts.back(), 0x00));
 | 
						|
                }
 | 
						|
 | 
						|
                SaveData saveData = {true, updateRate, game, counts, addresses, endianness, data};
 | 
						|
                _saveData[game] = saveData;
 | 
						|
            }
 | 
						|
        }
 | 
						|
        else
 | 
						|
        {
 | 
						|
            fprintf(stderr, "Loader::initialise() : couldn't load high scores INI file '%s' : high scores are disabled.\n", HIGH_SCORES_INI);
 | 
						|
        }
 | 
						|
#endif
 | 
						|
    }
 | 
						|
 | 
						|
 | 
						|
#ifndef STAND_ALONE
 | 
						|
    const std::string& getConfigComPort(void) {return _configComPort;}
 | 
						|
 | 
						|
    const std::string& getCurrentGame(void) {return _currentGame;}
 | 
						|
    void setCurrentGame(const std::string& currentGame) {_currentGame = currentGame;}
 | 
						|
 | 
						|
    int getConfigRomsSize(void) {return int(_configRoms.size());}
 | 
						|
    ConfigRom* getConfigRom(int index)
 | 
						|
    {
 | 
						|
        if(_configRoms.size() == 0  ||  index >= int(_configRoms.size())) return nullptr;
 | 
						|
 | 
						|
        return &_configRoms[index];
 | 
						|
    }
 | 
						|
 | 
						|
    int matchFileSystemName(const std::string& path, const std::string& match, std::vector<std::string>& names)
 | 
						|
    {
 | 
						|
        DIR *dir;
 | 
						|
        struct dirent *ent;
 | 
						|
 | 
						|
        names.clear();
 | 
						|
 | 
						|
        if((dir = opendir(path.c_str())) != NULL)
 | 
						|
        {
 | 
						|
            while((ent = readdir(dir)) != NULL)
 | 
						|
            {
 | 
						|
                std::string name = std::string(ent->d_name);
 | 
						|
                if(name.find(match) != std::string::npos) names.push_back(path + name);   
 | 
						|
            }
 | 
						|
            closedir (dir);
 | 
						|
        }
 | 
						|
 | 
						|
        return int(names.size());
 | 
						|
    }
 | 
						|
 | 
						|
    bool openComPort(void)
 | 
						|
    {
 | 
						|
        if(!_enableComPort)
 | 
						|
        {
 | 
						|
            _currentComPort = -1;
 | 
						|
            return false;
 | 
						|
        }
 | 
						|
 | 
						|
        if(_numComPorts == 0)
 | 
						|
        {
 | 
						|
            _numComPorts = comEnumerate();
 | 
						|
            if(_numComPorts == 0)
 | 
						|
            {
 | 
						|
                fprintf(stderr, "Loader::openComPort() : no COM ports found\n");
 | 
						|
                return false;
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        if(_currentComPort < 0)
 | 
						|
        {
 | 
						|
            _numComPorts = 0;
 | 
						|
            fprintf(stderr, "Loader::openComPort() : couldn't open COM port '%s'\n", _configComPort.c_str());
 | 
						|
            return false;
 | 
						|
        } 
 | 
						|
        else if(comOpen(_currentComPort, _configBaudRate) == 0)
 | 
						|
        {
 | 
						|
            _numComPorts = 0;
 | 
						|
            fprintf(stderr, "Loader::openComPort() : couldn't open COM port '%s'\n", comGetPortName(_currentComPort));
 | 
						|
            return false;
 | 
						|
        } 
 | 
						|
 | 
						|
        return true;
 | 
						|
    }
 | 
						|
 | 
						|
    void closeComPort(void)
 | 
						|
    {
 | 
						|
        comClose(_currentComPort);
 | 
						|
        _currentComPort = -1;
 | 
						|
    }
 | 
						|
 | 
						|
    bool checkComPort(void)
 | 
						|
    {
 | 
						|
        if(!_enableComPort)
 | 
						|
        {
 | 
						|
            fprintf(stderr, "Loader::checkComPort() : Comms in '%s' disabled\n", LOADER_CONFIG_INI);
 | 
						|
            return false;
 | 
						|
        }
 | 
						|
 | 
						|
        if(_currentComPort < 0)
 | 
						|
        {
 | 
						|
            fprintf(stderr, "Loader::checkComPort() : Invalid COM port '%s'\n", _configComPort.c_str());
 | 
						|
            return false;
 | 
						|
        }
 | 
						|
 | 
						|
        return true;
 | 
						|
    }
 | 
						|
 | 
						|
 | 
						|
    bool readCharGiga(char* chr)
 | 
						|
    {
 | 
						|
        if(!checkComPort()) return false;
 | 
						|
 | 
						|
        return (comRead(_currentComPort, chr, 1) == 1);
 | 
						|
    }
 | 
						|
 | 
						|
    bool sendCharGiga(char chr)
 | 
						|
    {
 | 
						|
        if(!checkComPort()) return false;
 | 
						|
 | 
						|
        return (comWrite(_currentComPort, &chr, 1) == 1);
 | 
						|
    }
 | 
						|
 | 
						|
    bool readLineGiga(std::string& line)
 | 
						|
    {
 | 
						|
        if(!checkComPort()) return false;
 | 
						|
 | 
						|
        line.clear();
 | 
						|
        char buffer = 0;
 | 
						|
        uint64_t prevFrameCounter = SDL_GetPerformanceCounter();
 | 
						|
 | 
						|
        while(buffer != '\n')
 | 
						|
        {
 | 
						|
            if(comRead(_currentComPort, &buffer, 1))
 | 
						|
            {
 | 
						|
                if((buffer >= 32  &&  buffer <= 126)  ||  buffer == '\n') line.push_back(buffer);
 | 
						|
            }
 | 
						|
            double frameTime = double(SDL_GetPerformanceCounter() - prevFrameCounter) / double(SDL_GetPerformanceFrequency());
 | 
						|
            if(frameTime > _configTimeOut) return false;
 | 
						|
        }
 | 
						|
 | 
						|
        // Replace '\n'
 | 
						|
        line.back() = 0;
 | 
						|
 | 
						|
        //fprintf(stderr, "%s\n", line.c_str());
 | 
						|
 | 
						|
        return true;
 | 
						|
    }
 | 
						|
 | 
						|
    bool readLineGiga(std::vector<std::string>& text)
 | 
						|
    {
 | 
						|
        if(!checkComPort()) return false;
 | 
						|
 | 
						|
        std::string line;
 | 
						|
        if(!readLineGiga(line))
 | 
						|
        {
 | 
						|
            fprintf(stderr, "Loader::readLineGiga() : timed out on COM port : %s\n", comGetPortName(_currentComPort));
 | 
						|
            return false;
 | 
						|
        }
 | 
						|
 | 
						|
        text.push_back(line);
 | 
						|
 | 
						|
        return true;
 | 
						|
    }
 | 
						|
 | 
						|
    bool readUntilPromptGiga(std::vector<std::string>& text)
 | 
						|
    {
 | 
						|
        if(!checkComPort()) return false;
 | 
						|
 | 
						|
        std::string line;
 | 
						|
        do
 | 
						|
        {
 | 
						|
            if(!readLineGiga(line))
 | 
						|
            {
 | 
						|
                fprintf(stderr, "Loader::readUntilPromptGiga() : timed out on COM port : %s\n", comGetPortName(_currentComPort));
 | 
						|
                return false;
 | 
						|
            }
 | 
						|
 | 
						|
            if(size_t e = line.find('!') != std::string::npos)
 | 
						|
            {
 | 
						|
                fprintf(stderr, "Loader::readUntilPromptGiga() : Arduino Error : %s\n", &line[e]);
 | 
						|
                return false;
 | 
						|
            }
 | 
						|
 | 
						|
            text.push_back(line);
 | 
						|
        }
 | 
						|
        while(line.find("?") == std::string::npos);
 | 
						|
 | 
						|
        return true;
 | 
						|
    }
 | 
						|
 | 
						|
    bool waitForPromptGiga(std::string& line)
 | 
						|
    {
 | 
						|
        if(!checkComPort()) return false;
 | 
						|
 | 
						|
        do
 | 
						|
        {
 | 
						|
            if(!readLineGiga(line))
 | 
						|
            {
 | 
						|
                fprintf(stderr, "Loader::waitForPromptGiga() : timed out on COM port : %s\n", comGetPortName(_currentComPort));
 | 
						|
                return false;
 | 
						|
            }
 | 
						|
 | 
						|
            //fprintf(stderr, "Loader::waitForPromptGiga() : %s\n", line.c_str());
 | 
						|
 | 
						|
            if(size_t e = line.find('!') != std::string::npos)
 | 
						|
            {
 | 
						|
                fprintf(stderr, "Loader::waitForPromptGiga() : Arduino Error : %s\n", &line[e]);
 | 
						|
                return false;
 | 
						|
            }
 | 
						|
        }
 | 
						|
        while(line.find("?") == std::string::npos);
 | 
						|
 | 
						|
        return true;
 | 
						|
    }
 | 
						|
 | 
						|
    bool sendCommandToGiga(char cmd, std::string& line, bool wait)
 | 
						|
    {
 | 
						|
        if(!checkComPort()) return false;
 | 
						|
 | 
						|
        char command[2] = {cmd, '\n'};
 | 
						|
        comWrite(_currentComPort, command, 2);
 | 
						|
 | 
						|
        // Wait for ready prompt
 | 
						|
        if(wait)
 | 
						|
        {
 | 
						|
            if(!waitForPromptGiga(line)) return false;
 | 
						|
        }
 | 
						|
 | 
						|
        return true;
 | 
						|
    }
 | 
						|
 | 
						|
    bool sendCommandToGiga(char cmd, bool wait)
 | 
						|
    {
 | 
						|
        if(!checkComPort()) return false;
 | 
						|
 | 
						|
        std::string line;
 | 
						|
        return sendCommandToGiga(cmd, line, wait);
 | 
						|
    }
 | 
						|
 | 
						|
    bool sendCommandToGiga(const std::string& cmd, std::vector<std::string>& text)
 | 
						|
    {
 | 
						|
        if(!checkComPort()) return false;
 | 
						|
 | 
						|
        comWrite(_currentComPort, cmd.c_str(), cmd.size());
 | 
						|
        return readUntilPromptGiga(text);
 | 
						|
    }
 | 
						|
 | 
						|
 | 
						|
    int uploadToGigaThread(void* userData)
 | 
						|
    {
 | 
						|
        if(!checkComPort())
 | 
						|
        {
 | 
						|
            _uploadThread = nullptr;
 | 
						|
            return -1;
 | 
						|
        }
 | 
						|
 | 
						|
        Graphics::enableUploadBar(true);
 | 
						|
 | 
						|
        std::string line;
 | 
						|
        if(!sendCommandToGiga('R', line, true)) return -1;
 | 
						|
        if(!sendCommandToGiga('L', line, true)) return -1;
 | 
						|
        if(!sendCommandToGiga('U', line, true)) return -1;
 | 
						|
 | 
						|
        int gt1Size = *((int*)userData);
 | 
						|
 | 
						|
        int index = 0;
 | 
						|
        while(std::isdigit((unsigned char)line[0]))
 | 
						|
        {
 | 
						|
            int n = strtol(line.c_str(), nullptr, 10);
 | 
						|
            comWrite(_currentComPort, &_gt1Buffer[index], n);
 | 
						|
            index += n;
 | 
						|
 | 
						|
            if(!waitForPromptGiga(line))
 | 
						|
            {
 | 
						|
                Graphics::enableUploadBar(false);
 | 
						|
                //fprintf(stderr, "\n");
 | 
						|
                return -1;
 | 
						|
            }
 | 
						|
 | 
						|
            float upload = float(index) / float(gt1Size);
 | 
						|
            Graphics::updateUploadBar(upload);
 | 
						|
            //fprintf(stderr, "Loader::uploadToGiga() : Uploading...%3d%%\r", int(upload * 100.0f));
 | 
						|
        }
 | 
						|
 | 
						|
        Graphics::enableUploadBar(false);
 | 
						|
        //fprintf(stderr, "\n");
 | 
						|
 | 
						|
        _uploadThread = nullptr;
 | 
						|
 | 
						|
        return 0;
 | 
						|
    }
 | 
						|
 | 
						|
    void uploadToGiga(const std::string& filepath, const std::string& filename)
 | 
						|
    {
 | 
						|
        if(!checkComPort()) return;
 | 
						|
 | 
						|
        // An upload is already in progress
 | 
						|
        if(Graphics::getUploadBarEnabled()) return;
 | 
						|
 | 
						|
        std::ifstream gt1file(filepath, std::ios::binary | std::ios::in);
 | 
						|
        if(!gt1file.is_open())
 | 
						|
        {
 | 
						|
            fprintf(stderr, "Loader::uploadToGiga() : failed to open '%s'\n", filepath.c_str());
 | 
						|
            return;
 | 
						|
        }
 | 
						|
 | 
						|
        gt1file.read(_gt1Buffer, MAX_GT1_SIZE);
 | 
						|
        if(gt1file.bad())
 | 
						|
        {
 | 
						|
            fprintf(stderr, "Loader::uploadToGiga() : failed to read GT1 file '%s'\n", filepath.c_str());
 | 
						|
            return;
 | 
						|
        }
 | 
						|
 | 
						|
        Graphics::setUploadFilename(filename);
 | 
						|
 | 
						|
        _gt1UploadSize = int(gt1file.gcount());
 | 
						|
        _uploadThread = SDL_CreateThread(uploadToGigaThread, "gtemuAT67::Loader::uploadToGiga()", (void*)&_gt1UploadSize);
 | 
						|
    }
 | 
						|
 | 
						|
    void disableUploads(bool disable)
 | 
						|
    {
 | 
						|
        _disableUploads = disable;
 | 
						|
    }
 | 
						|
 | 
						|
    bool loadDataFile(SaveData& saveData)
 | 
						|
    {
 | 
						|
        SaveData sdata = saveData;
 | 
						|
        std::string filename = sdata._filename + ".dat";
 | 
						|
        std::ifstream infile(_exePath + "/" + filename, std::ios::binary | std::ios::in);
 | 
						|
        if(!infile.is_open())
 | 
						|
        {
 | 
						|
            fprintf(stderr, "Loader::loadDataFile() : failed to open '%s'\n", filename.c_str());
 | 
						|
            return false;
 | 
						|
        }
 | 
						|
 | 
						|
        // Load counts
 | 
						|
        uint16_t numCounts = 0;
 | 
						|
        infile.read((char *)&numCounts, 2);
 | 
						|
        if(infile.eof() || infile.bad() || infile.fail())
 | 
						|
        {
 | 
						|
            fprintf(stderr, "Loader::loadDataFile() : read error in number of counts in '%s'\n", filename.c_str());
 | 
						|
            return false;
 | 
						|
        }
 | 
						|
        if(_hostIsBigEndian) Cpu::swapEndianness(numCounts);
 | 
						|
        for(int i=0; i<numCounts; i++)
 | 
						|
        {
 | 
						|
            uint16_t count;
 | 
						|
            infile.read((char *)&count, 2);
 | 
						|
            if(infile.bad() || infile.fail())
 | 
						|
            {
 | 
						|
                fprintf(stderr, "Loader::loadDataFile() : read error in counts of '%s'\n", filename.c_str());
 | 
						|
                return false;
 | 
						|
            }
 | 
						|
            if(_hostIsBigEndian) Cpu::swapEndianness(count);
 | 
						|
            sdata._counts[i] = count;
 | 
						|
        }         
 | 
						|
 | 
						|
        // Load addresses
 | 
						|
        uint16_t numAddresses = 0;
 | 
						|
        infile.read((char *)&numAddresses, 2);
 | 
						|
        if(infile.eof() || infile.bad() || infile.fail())
 | 
						|
        {
 | 
						|
            fprintf(stderr, "Loader::loadDataFile() : read error in number of addresses in '%s'\n", filename.c_str());
 | 
						|
            return false;
 | 
						|
        }
 | 
						|
        if(_hostIsBigEndian) Cpu::swapEndianness(numAddresses);
 | 
						|
        for(int i=0; i<numAddresses; i++)
 | 
						|
        {
 | 
						|
            uint16_t address;
 | 
						|
            infile.read((char *)&address, 2);
 | 
						|
            if(infile.bad() || infile.fail())
 | 
						|
            {
 | 
						|
                fprintf(stderr, "Loader::loadDataFile() : read error in addresses of '%s'\n", filename.c_str());
 | 
						|
                return false;
 | 
						|
            }
 | 
						|
            if(_hostIsBigEndian) Cpu::swapEndianness(address);
 | 
						|
            sdata._addresses[i] = address;
 | 
						|
        }         
 | 
						|
 | 
						|
        if(sdata._counts.size() == 0  ||  sdata._counts.size() != sdata._addresses.size())
 | 
						|
        {
 | 
						|
            fprintf(stderr, "Loader::loadDataFile() : save data is corrupt : saveData._counts.size() = %d : saveData._addresses.size() = %d\n", int(sdata._counts.size()), int(sdata._addresses.size()));
 | 
						|
            return false;
 | 
						|
        }
 | 
						|
 | 
						|
        // load data
 | 
						|
        for(int j=0; j<int(sdata._addresses.size()); j++)
 | 
						|
        {
 | 
						|
            //sdata._data.push_back(std::vector<uint8_t>(sdata._counts[j], 0x00));
 | 
						|
            for(uint16_t i=0; i<sdata._counts[j]; i++)
 | 
						|
            {
 | 
						|
                uint8_t data;
 | 
						|
                infile.read((char *)&data, 1);
 | 
						|
                if(infile.bad() || infile.fail())
 | 
						|
                {
 | 
						|
                    fprintf(stderr, "Loader::loadDataFile() : read error in data of '%s'\n", filename.c_str());
 | 
						|
                    return false;
 | 
						|
                }
 | 
						|
                sdata._data[j][i] = data;
 | 
						|
                Cpu::setRAM(sdata._addresses[j] + i, data);
 | 
						|
            }
 | 
						|
        }
 | 
						|
        sdata._initialised = true;
 | 
						|
 | 
						|
        saveData = sdata;
 | 
						|
 | 
						|
        return true;
 | 
						|
    }
 | 
						|
 | 
						|
    // Only for emulation
 | 
						|
    bool saveDataFile(SaveData& saveData)
 | 
						|
    {
 | 
						|
        std::string filename = saveData._filename + ".dat";
 | 
						|
        std::ofstream outfile(_exePath + "/" + filename, std::ios::binary | std::ios::out);
 | 
						|
        if(!outfile.is_open())
 | 
						|
        {
 | 
						|
            fprintf(stderr, "Loader::saveDataFile() : failed to open '%s'\n", filename.c_str());
 | 
						|
            return false;
 | 
						|
        }
 | 
						|
 | 
						|
        if(saveData._counts.size() == 0  ||  saveData._counts.size() != saveData._addresses.size())
 | 
						|
        {
 | 
						|
            fprintf(stderr, "Loader::saveDataFile() : save data is corrupt : saveData._counts.size() = %d : saveData._addresses.size() = %d\n", int(saveData._counts.size()), int(saveData._addresses.size()));
 | 
						|
            return false;
 | 
						|
        }
 | 
						|
 | 
						|
        // Save counts
 | 
						|
        uint16_t numCounts = uint16_t(saveData._counts.size());
 | 
						|
        if(_hostIsBigEndian) Cpu::swapEndianness(numCounts);
 | 
						|
        outfile.write((char *)&numCounts, 2);
 | 
						|
        if(_hostIsBigEndian) Cpu::swapEndianness(numCounts);
 | 
						|
        if(outfile.bad() || outfile.fail())
 | 
						|
        {
 | 
						|
            fprintf(stderr, "Loader::saveDataFile() : write error in number of counts of '%s'\n", filename.c_str());
 | 
						|
            return false;
 | 
						|
        }
 | 
						|
        for(int i=0; i<numCounts; i++)
 | 
						|
        {
 | 
						|
            if(_hostIsBigEndian) Cpu::swapEndianness(saveData._counts[i]);
 | 
						|
            outfile.write((char *)&saveData._counts[i], 2);
 | 
						|
            if(_hostIsBigEndian) Cpu::swapEndianness(saveData._counts[i]);
 | 
						|
            if(outfile.bad() || outfile.fail())
 | 
						|
            {
 | 
						|
                fprintf(stderr, "Loader::saveDataFile() : write error in counts of '%s'\n", filename.c_str());
 | 
						|
                return false;
 | 
						|
            }
 | 
						|
        }         
 | 
						|
 | 
						|
        // Save addresses
 | 
						|
        uint16_t numAddresses = uint16_t(saveData._addresses.size());
 | 
						|
        if(_hostIsBigEndian) Cpu::swapEndianness(numAddresses);
 | 
						|
        outfile.write((char *)&numAddresses, 2);
 | 
						|
        if(_hostIsBigEndian) Cpu::swapEndianness(numAddresses);
 | 
						|
        if(outfile.bad() || outfile.fail())
 | 
						|
        {
 | 
						|
            fprintf(stderr, "Loader::saveDataFile() : write error in number of addresses of '%s'\n", filename.c_str());
 | 
						|
            return false;
 | 
						|
        }
 | 
						|
        for(int i=0; i<numAddresses; i++)
 | 
						|
        {
 | 
						|
            if(_hostIsBigEndian) Cpu::swapEndianness(saveData._addresses[i]);
 | 
						|
            outfile.write((char *)&saveData._addresses[i], 2);
 | 
						|
            if(_hostIsBigEndian) Cpu::swapEndianness(saveData._addresses[i]);
 | 
						|
            if(outfile.bad() || outfile.fail())
 | 
						|
            {
 | 
						|
                fprintf(stderr, "Loader::saveDataFile() : write error in addresses of '%s'\n", filename.c_str());
 | 
						|
                return false;
 | 
						|
            }
 | 
						|
        }         
 | 
						|
 | 
						|
        // Check data has been initialised
 | 
						|
        for(int j=0; j<int(saveData._addresses.size()); j++)
 | 
						|
        {
 | 
						|
            if(saveData._data.size() != saveData._addresses.size())
 | 
						|
            {
 | 
						|
                fprintf(stderr, "Loader::saveDataFile() : data has not been initialised or loaded, nothing to save for '%s'\n", filename.c_str());
 | 
						|
                return false;
 | 
						|
            }
 | 
						|
            for(int i=0; i<saveData._counts[j]; i++)
 | 
						|
            {
 | 
						|
                if(saveData._data[j].size() != saveData._counts[j]) 
 | 
						|
                {
 | 
						|
                    fprintf(stderr, "Loader::saveDataFile() : data has not been initialised or loaded, nothing to save for '%s'\n", filename.c_str());
 | 
						|
                    return false;
 | 
						|
                }
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        // Save data
 | 
						|
        for(int j=0; j<int(saveData._addresses.size()); j++)
 | 
						|
        {
 | 
						|
            for(int i=0; i<saveData._counts[j]; i++)
 | 
						|
            {
 | 
						|
                uint8_t data = saveData._data[j][i];
 | 
						|
                outfile.write((char *)&data, 1);
 | 
						|
                if(outfile.bad() || outfile.fail())
 | 
						|
                {
 | 
						|
                    fprintf(stderr, "Loader::saveDataFile() : write error in data of '%s'\n", filename.c_str());
 | 
						|
                    return false;
 | 
						|
                }
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        return true;
 | 
						|
    }
 | 
						|
 | 
						|
    // Loads high score for current game from a simple <game>.dat file
 | 
						|
    void loadHighScore(void)
 | 
						|
    {
 | 
						|
        if(_saveData.find(_currentGame) == _saveData.end())
 | 
						|
        {
 | 
						|
            //fprintf(stderr, "Loader::loadHighScore() : warning, no game entry defined in '%s' for '%s'\n", HIGH_SCORES_INI, _currentGame.c_str());
 | 
						|
            return;
 | 
						|
        }
 | 
						|
 | 
						|
        if(Loader::loadDataFile(_saveData[_currentGame]))
 | 
						|
        {
 | 
						|
            fprintf(stderr, "Loader::loadHighScore() : loaded high score data successfully for '%s'\n", _currentGame.c_str());
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    // Saves high score for current game to a simple <game>.dat file
 | 
						|
    bool saveHighScore(void)
 | 
						|
    {
 | 
						|
        if(_saveData.find(_currentGame) == _saveData.end())
 | 
						|
        {
 | 
						|
            fprintf(stderr, "Loader::saveHighScore() : error, no game entry defined in '%s' for '%s'\n", HIGH_SCORES_INI, _currentGame.c_str());
 | 
						|
            return false;
 | 
						|
        }
 | 
						|
 | 
						|
        if(Loader::saveDataFile(_saveData[_currentGame]))
 | 
						|
        {
 | 
						|
            //fprintf(stderr, "Loader::saveHighScore() : saved high score data successfully for '%s'\n", _currentGame.c_str());
 | 
						|
            return true;
 | 
						|
        }
 | 
						|
 | 
						|
        fprintf(stderr, "Loader::saveHighScore() : saving high score data failed, for '%s'\n", _currentGame.c_str());
 | 
						|
 | 
						|
        return false;
 | 
						|
    }
 | 
						|
 | 
						|
    // Updates a game's high score, (call this in the vertical blank)
 | 
						|
    void updateHighScore(void)
 | 
						|
    {
 | 
						|
        static int frameCount = 0;
 | 
						|
 | 
						|
        // No entry in high score file defined for this game, so silently exit
 | 
						|
        if(_saveData.find(_currentGame) == _saveData.end()) return;
 | 
						|
        if(!_saveData[_currentGame]._initialised) return;
 | 
						|
 | 
						|
        // Update once every updateRate VBlank ticks, (defaults to VSYNC_RATE, hence once every second)
 | 
						|
        if(frameCount++ < _saveData[_currentGame]._updaterate) return;
 | 
						|
        frameCount = 0;
 | 
						|
 | 
						|
        // Update data, (checks byte by byte and saves if larger, endian order is configurable)
 | 
						|
        for(int j=0; j<int(_saveData[_currentGame]._addresses.size()); j++)
 | 
						|
        {
 | 
						|
            // Defaults to little endian
 | 
						|
            int start = _saveData[_currentGame]._counts[j] - 1, end = -1, step = -1;
 | 
						|
            if(_saveData[_currentGame]._endianness[j] == Big)
 | 
						|
            {
 | 
						|
                start = 0;
 | 
						|
                end = _saveData[_currentGame]._counts[j];
 | 
						|
                step = 1;
 | 
						|
            }
 | 
						|
 | 
						|
            // Loop MSB to LSB or vice versa depending on endianness            
 | 
						|
            while(start != end)
 | 
						|
            {
 | 
						|
                int i = start;
 | 
						|
                uint8_t data = Cpu::getRAM(uint16_t(_saveData[_currentGame]._addresses[j] + i));
 | 
						|
 | 
						|
                // TODO: create a list of INI rules to make this test more flexible
 | 
						|
                if(data < _saveData[_currentGame]._data[j][i]) return;
 | 
						|
                if(_saveData[_currentGame]._data[j][i] == 0  ||  data > _saveData[_currentGame]._data[j][i])
 | 
						|
                {
 | 
						|
                    for(int k=i; k!=end; k+=step)
 | 
						|
                    {
 | 
						|
                        _saveData[_currentGame]._data[j][k] = Cpu::getRAM(uint16_t(_saveData[_currentGame]._addresses[j] + k));
 | 
						|
                    }
 | 
						|
                    saveHighScore();
 | 
						|
                    return;
 | 
						|
                }
 | 
						|
 | 
						|
                start += step;
 | 
						|
            }
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    bool loadGtbFile(const std::string& filepath)
 | 
						|
    {
 | 
						|
        // open .gtb file
 | 
						|
        std::ifstream infile(filepath, std::ios::binary | std::ios::in);
 | 
						|
        if(!infile.is_open())
 | 
						|
        {
 | 
						|
            fprintf(stderr, "Loader::loadGtbFile() : failed to open '%s'\n", filepath.c_str());
 | 
						|
            return false;
 | 
						|
        }
 | 
						|
 | 
						|
        // Read .gtb file
 | 
						|
        std::string line;
 | 
						|
        std::vector<std::string> lines;
 | 
						|
        while(!infile.eof())
 | 
						|
        {
 | 
						|
            std::getline(infile, line);
 | 
						|
            if(!infile.good() && !infile.eof())
 | 
						|
            {
 | 
						|
                fprintf(stderr, "Loader::loadGtbFile() : '%s:%d' : bad line '%s'\n", filepath.c_str(), int(lines.size()+1), line.c_str());
 | 
						|
                return false;
 | 
						|
            }
 | 
						|
 | 
						|
            if(line.size()) lines.push_back(line);
 | 
						|
        }
 | 
						|
        
 | 
						|
        // Delete non numbered lines, (comments etc)
 | 
						|
        for(auto i=lines.begin(); i!=lines.end();)
 | 
						|
        {
 | 
						|
            long lineNumber = strtol(i->c_str(), nullptr, 10);
 | 
						|
            if(lineNumber < 1  ||  lineNumber > 32767)
 | 
						|
            {
 | 
						|
                i = lines.erase(i);
 | 
						|
            }
 | 
						|
            else
 | 
						|
            {
 | 
						|
                ++i;
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
#if 0
 | 
						|
        // Remove trailing comments
 | 
						|
        for(int i=0; i<lines.size(); i++)
 | 
						|
        {
 | 
						|
            size_t pos = lines[i].find('\'');
 | 
						|
            if(pos != string::npos  &&  pos > 2) lines[i] = lines[i].substr(0, pos-1);
 | 
						|
            fprintf(stderr, "Loader::loadGtbFile() : %s\n", lines[i].c_str());
 | 
						|
        }
 | 
						|
#endif
 | 
						|
 | 
						|
        // Load .gtb file into memory
 | 
						|
        uint16_t startAddress = GTB_LINE0_ADDRESS + MAX_GTB_LINE_SIZE;
 | 
						|
        uint16_t endAddress = startAddress;
 | 
						|
        char *endPtr;
 | 
						|
        for(int i=0; i<int(lines.size()); i++)
 | 
						|
        {
 | 
						|
            uint16_t lineNumber = (uint16_t)strtol(lines[i].c_str(), &endPtr, 10);
 | 
						|
            Cpu::setRAM(endAddress + 0, LO_BYTE(lineNumber));
 | 
						|
            Cpu::setRAM(endAddress + 1, HI_BYTE(lineNumber));
 | 
						|
            uint8_t lineStart = uint8_t(endPtr - &lines[i][0]);
 | 
						|
 | 
						|
            // First 2 bytes are int16 line number
 | 
						|
            for(uint8_t j=lineStart; j<(MAX_GTB_LINE_SIZE-2 + lineStart); j++)
 | 
						|
            {
 | 
						|
                uint8_t offset = 2 + j - lineStart;
 | 
						|
                bool validData = offset < MAX_GTB_LINE_SIZE-1  &&  j < lines[i].size()  &&  lines[i][j] >= ' ';
 | 
						|
                uint8_t data = validData ? lines[i][j] : 0;
 | 
						|
                Cpu::setRAM(endAddress + offset, data);
 | 
						|
            }
 | 
						|
            endAddress += MAX_GTB_LINE_SIZE;
 | 
						|
            if(LO_BYTE(endAddress) < LO_BYTE(GTB_LINE0_ADDRESS)) endAddress = HI_MASK(endAddress) | LO_BYTE(GTB_LINE0_ADDRESS);
 | 
						|
        }
 | 
						|
 | 
						|
        uint16_t freeMemory = uint16_t(Memory::getFreeGtbRAM(int(lines.size())));
 | 
						|
        fprintf(stderr, "Loader::loadGtbFile() : start %04x : end %04x : free %d : '%s'\n", startAddress, endAddress, freeMemory, filepath.c_str());
 | 
						|
 | 
						|
        Cpu::setRAM(GTB_LINE0_ADDRESS + 0, LO_BYTE(endAddress));
 | 
						|
        Cpu::setRAM(GTB_LINE0_ADDRESS + 1, HI_BYTE(endAddress));
 | 
						|
        std::string list = "RUN";
 | 
						|
        for(int i=0; i<int(list.size()); i++) Cpu::setRAM(uint16_t(endAddress + 2 + i), list[i]);
 | 
						|
        Cpu::setRAM(uint16_t(endAddress + 2 + uint16_t(list.size())), 0);
 | 
						|
 | 
						|
        return true;
 | 
						|
    }
 | 
						|
 | 
						|
    void setMemoryModel64k(void)
 | 
						|
    {
 | 
						|
        if (Memory::getSizeRAM() < RAM_SIZE_HI)
 | 
						|
        {
 | 
						|
            Memory::setSizeRAM(RAM_SIZE_HI);
 | 
						|
            Cpu::setSizeRAM(Memory::getSizeRAM());
 | 
						|
            Cpu::setRAM(0x0001, 0x00); // inform system that RAM is 64k
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    void uploadToEmulatorRAM(const Gt1File& gt1File)
 | 
						|
    {
 | 
						|
        // Set 64k memory model based on any segment address being >= 0x8000
 | 
						|
        for(int i=0; i<int(gt1File._segments.size()); i++)
 | 
						|
        {
 | 
						|
            if(gt1File._segments[i]._loAddress + (gt1File._segments[i]._hiAddress <<8) >= RAM_UPPER_START)
 | 
						|
            {
 | 
						|
                setMemoryModel64k();
 | 
						|
                break;
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        for(int j=0; j<int(gt1File._segments.size()); j++)
 | 
						|
        {
 | 
						|
            // Ignore if address will not fit in current RAM
 | 
						|
            uint16_t address = gt1File._segments[j]._loAddress + (gt1File._segments[j]._hiAddress <<8);
 | 
						|
            if((address + int(gt1File._segments[j]._dataBytes.size()) - 1) < Memory::getSizeRAM())
 | 
						|
            {
 | 
						|
                for(int i=0; i<int(gt1File._segments[j]._dataBytes.size()); i++)
 | 
						|
                {
 | 
						|
                    Cpu::setRAM(uint16_t(address + i), gt1File._segments[j]._dataBytes[i]);
 | 
						|
                }
 | 
						|
            }
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    // Uploads directly into the emulator's RAM/ROM or to real Gigatron hardware if available
 | 
						|
    void uploadDirect(UploadTarget uploadTarget)
 | 
						|
    {
 | 
						|
        Gt1File gt1File;
 | 
						|
 | 
						|
        bool gt1FileBuilt = false;
 | 
						|
        bool isGtbFile = false;
 | 
						|
        bool isGt1File = false;
 | 
						|
        bool isGbasFile = false;
 | 
						|
        bool hasRomCode = false;
 | 
						|
        bool hasRamCode = false;
 | 
						|
 | 
						|
        uint16_t execAddress = USER_CODE_START;
 | 
						|
        std::string gtbFilepath;
 | 
						|
 | 
						|
        if(Editor::getCurrentFileEntryName())
 | 
						|
        {
 | 
						|
            _filePath = Editor::getBrowserPath() + *Editor::getCurrentFileEntryName();
 | 
						|
        }
 | 
						|
 | 
						|
        std::string filepath = _filePath;
 | 
						|
        size_t slash = filepath.find_last_of("\\/");
 | 
						|
        std::string filename = filepath.substr(slash + 1);
 | 
						|
 | 
						|
        Expression::replaceText(filepath, "\\", "/");
 | 
						|
 | 
						|
        size_t nameSuffix = filename.find_last_of(".");
 | 
						|
        size_t pathSuffix = filepath.find_last_of(".");
 | 
						|
        if(nameSuffix == std::string::npos  ||  pathSuffix == std::string::npos)
 | 
						|
        {
 | 
						|
            fprintf(stderr, "\nLoader::uploadDirect() : invalid filepath '%s' or filename '%s'\n", filepath.c_str(), filename.c_str());
 | 
						|
            return;
 | 
						|
        }
 | 
						|
 | 
						|
        // Set 64k memory model based on .INI file or filename
 | 
						|
        if(_autoSet64k  &&  Expression::strUpper(filename).find("64K") != std::string::npos)
 | 
						|
        {
 | 
						|
            setMemoryModel64k();
 | 
						|
        }
 | 
						|
 | 
						|
        if(uploadTarget == Emulator)
 | 
						|
        {
 | 
						|
            Audio::restoreWaveTables();
 | 
						|
            Audio::initialiseChannels();
 | 
						|
        }
 | 
						|
 | 
						|
        // Compile gbas to gasm
 | 
						|
        if(filename.find(".gbas") != filename.npos)
 | 
						|
        {
 | 
						|
            Cpu::enable6BitSound(Cpu::ROMv5a, false);
 | 
						|
 | 
						|
            std::string output = filepath.substr(0, pathSuffix) + ".gasm";
 | 
						|
            if(!Compiler::compile(filepath, output))
 | 
						|
            {
 | 
						|
                //Memory::printFreeRamList(Memory::SizeDescending);
 | 
						|
                return;
 | 
						|
            }
 | 
						|
 | 
						|
            // Create gasm name and path
 | 
						|
            filename = filename.substr(0, nameSuffix) + ".gasm";
 | 
						|
            filepath = filepath.substr(0, pathSuffix) + ".gasm";
 | 
						|
            isGbasFile = true;
 | 
						|
        }
 | 
						|
        // Load to gtb and launch TinyBasic
 | 
						|
        else if(_configGclBuildFound  &&  filename.find(".gtb") != filename.npos)
 | 
						|
        {
 | 
						|
            gtbFilepath = filepath;
 | 
						|
            filename = "TinyBASIC_v3.gt1";
 | 
						|
            filepath = _configGclBuild + "/Apps/TinyBASIC/" + filename;
 | 
						|
            isGtbFile = true;
 | 
						|
        }
 | 
						|
        // Compile gcl to gt1
 | 
						|
        else if(_configGclBuildFound  &&  filename.find(".gcl") != filename.npos)
 | 
						|
        {
 | 
						|
            // Create compile gcl string
 | 
						|
            if(chdir(_configGclBuild.c_str()))
 | 
						|
            {
 | 
						|
                fprintf(stderr, "\nLoader::uploadDirect() : failed to change directory to '%s' : can't build %s\n", _configGclBuild.c_str(), filename.c_str());
 | 
						|
                return;
 | 
						|
            }
 | 
						|
 | 
						|
            // Prepend CWD to relative paths
 | 
						|
            if(filepath.find(":") == std::string::npos  &&  filepath[0] != '/')
 | 
						|
            {
 | 
						|
                filepath = _cwdPath + "/" + filepath;
 | 
						|
                pathSuffix = filepath.find_last_of(".");
 | 
						|
            }
 | 
						|
 | 
						|
            slash = filepath.find_last_of("\\/");
 | 
						|
            std::string gclPath = (slash != std::string::npos) ? filepath.substr(0, slash) : "./";
 | 
						|
            std::string command = "python3 -B Core/compilegcl.py -s interface.json \"" + filepath + "\" \"" + gclPath + "\"";
 | 
						|
 | 
						|
            //fprintf(stderr, "\nLoader::uploadDirect() : %s : %s : %s : %s\n", filepath.c_str(), command.c_str(), _cwdPath.c_str(), _exePath.c_str());
 | 
						|
 | 
						|
            // Create gt1 name and path
 | 
						|
            filename = filename.substr(0, nameSuffix) + ".gt1";
 | 
						|
            filepath = filepath.substr(0, pathSuffix) + ".gt1";
 | 
						|
 | 
						|
            // Build gcl
 | 
						|
            int gt1FileDeleted = remove(filepath.c_str());
 | 
						|
            fprintf(stderr, "\n");
 | 
						|
            (void)!system(command.c_str());
 | 
						|
 | 
						|
            // Check for gt1
 | 
						|
            std::ifstream infile(filepath, std::ios::binary | std::ios::in);
 | 
						|
            if(!infile.is_open())
 | 
						|
            {
 | 
						|
                fprintf(stderr, "\nLoader::uploadDirect() : failed to compile '%s'\n", filepath.c_str());
 | 
						|
                filename = "";
 | 
						|
                if(gt1FileDeleted == 0) Editor::browseDirectory();
 | 
						|
            }
 | 
						|
            else
 | 
						|
            {
 | 
						|
                gt1FileBuilt = true;
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        // Upload gt1
 | 
						|
        if(filename.find(".gt1") != filename.npos)
 | 
						|
        {
 | 
						|
            Assembler::clearAssembler(true);
 | 
						|
 | 
						|
            if(!loadGt1File(filepath, gt1File)) return;
 | 
						|
            execAddress = gt1File._loStart + (gt1File._hiStart <<8);
 | 
						|
            Editor::setLoadBaseAddress(execAddress);
 | 
						|
 | 
						|
            // Changes memory model if needed then uploads to emulator
 | 
						|
            if(uploadTarget == Emulator)
 | 
						|
            {
 | 
						|
                uploadToEmulatorRAM(gt1File);
 | 
						|
            }
 | 
						|
 | 
						|
            isGt1File = true;
 | 
						|
            hasRamCode = true;
 | 
						|
            hasRomCode = false;
 | 
						|
            _disableUploads = false;
 | 
						|
        }
 | 
						|
        // Upload vCPU assembly code
 | 
						|
        else if(filename.find(".gasm") != filename.npos  ||  filename.find(".vasm") != filename.npos  ||  filename.find(".s") != filename.npos  ||  filename.find(".asm") != filename.npos)
 | 
						|
        {
 | 
						|
            uint16_t address = (isGbasFile) ? Compiler::getUserCodeStart() : DEFAULT_EXEC_ADDRESS;
 | 
						|
            if(!Assembler::assemble(filepath, address, isGbasFile)) return;
 | 
						|
            if(isGbasFile  &&  !Validater::checkRuntimeVersion()) return;
 | 
						|
 | 
						|
            // Found a breakpoint in source code
 | 
						|
            if(Editor::getVpcBreakPointsSize())
 | 
						|
            {
 | 
						|
                Editor::startDebugger();
 | 
						|
                Editor::runToBreakpoint();
 | 
						|
                Editor::setEditorMode(Editor::Dasm);
 | 
						|
            }
 | 
						|
 | 
						|
            execAddress = (isGbasFile) ? Compiler::getUserCodeStart() : Assembler::getStartAddress();
 | 
						|
            uint16_t customAddress = execAddress;
 | 
						|
            Editor::setLoadBaseAddress(execAddress);
 | 
						|
            address = execAddress;
 | 
						|
 | 
						|
            // Save to gt1 format
 | 
						|
            gt1File._loStart = LO_BYTE(address);
 | 
						|
            gt1File._hiStart = HI_BYTE(address);
 | 
						|
            Gt1Segment gt1Segment;
 | 
						|
            gt1Segment._loAddress = LO_BYTE(address);
 | 
						|
            gt1Segment._hiAddress = HI_BYTE(address);
 | 
						|
 | 
						|
            // Generate gt1File
 | 
						|
            Assembler::ByteCode byteCode;
 | 
						|
            while(!Assembler::getNextAssembledByte(byteCode))
 | 
						|
            {
 | 
						|
                (byteCode._isRomAddress) ? hasRomCode = true : hasRamCode = true;
 | 
						|
 | 
						|
                // Custom address
 | 
						|
                if(byteCode._isCustomAddress)
 | 
						|
                {
 | 
						|
                    if(gt1Segment._dataBytes.size())
 | 
						|
                    {
 | 
						|
                        // Previous segment
 | 
						|
                        gt1Segment._segmentSize = uint8_t(gt1Segment._dataBytes.size());
 | 
						|
                        gt1File._segments.push_back(gt1Segment);
 | 
						|
                        gt1Segment._dataBytes.clear();
 | 
						|
                    }
 | 
						|
 | 
						|
                    address = byteCode._address;
 | 
						|
                    customAddress = address;
 | 
						|
                    gt1Segment._isRomAddress = byteCode._isRomAddress;
 | 
						|
                    gt1Segment._loAddress = LO_BYTE(address);
 | 
						|
                    gt1Segment._hiAddress = HI_BYTE(address);
 | 
						|
                }
 | 
						|
 | 
						|
                // Upload any ROM code to emulator ROM
 | 
						|
                if(byteCode._isRomAddress  &&  !_disableUploads  &&  uploadTarget == Emulator)
 | 
						|
                {
 | 
						|
                    Cpu::setROM(customAddress, address++, byteCode._data);
 | 
						|
                }
 | 
						|
 | 
						|
                gt1Segment._dataBytes.push_back(byteCode._data);
 | 
						|
            }
 | 
						|
 | 
						|
            // Last segment
 | 
						|
            if(gt1Segment._dataBytes.size())
 | 
						|
            {
 | 
						|
                gt1Segment._segmentSize = uint8_t(gt1Segment._dataBytes.size());
 | 
						|
                gt1File._segments.push_back(gt1Segment);
 | 
						|
            }
 | 
						|
 | 
						|
            // Changes memory model if needed then uploads to emulator RAM
 | 
						|
            if(uploadTarget == Emulator)
 | 
						|
            {
 | 
						|
                uploadToEmulatorRAM(gt1File);
 | 
						|
            }
 | 
						|
 | 
						|
            // Don't save gt1 file for any asm files that contain native rom code
 | 
						|
            std::string gt1FileName;
 | 
						|
            if(!hasRomCode)
 | 
						|
            {
 | 
						|
                if(!saveGt1File(filepath, gt1File, gt1FileName))
 | 
						|
                {
 | 
						|
                    Cpu::reset();
 | 
						|
                    return;
 | 
						|
                }
 | 
						|
            }
 | 
						|
 | 
						|
            gt1FileBuilt = true;
 | 
						|
        }
 | 
						|
        // Invalid file
 | 
						|
        else
 | 
						|
        {
 | 
						|
            fprintf(stderr, "Loader::upload() : invalid file or file does not exist '%s'\n", filename.c_str());
 | 
						|
            return;
 | 
						|
        }
 | 
						|
 | 
						|
        if(uploadTarget == Emulator) fprintf(stderr, "\nTarget : Emulator\n");
 | 
						|
        else if(uploadTarget == Hardware) fprintf(stderr, "\nTarget : Gigatron\n");
 | 
						|
 | 
						|
        // BASIC calculates the correct value of free RAM as part of the compilation
 | 
						|
        printGt1Stats(filename, gt1File, isGbasFile);
 | 
						|
 | 
						|
        if(uploadTarget == Emulator)
 | 
						|
        {
 | 
						|
            size_t i = filename.find('.');
 | 
						|
            _currentGame = (i != std::string::npos) ? filename.substr(0, i) : filename;
 | 
						|
            loadHighScore();
 | 
						|
 | 
						|
            // Load .gtb file into memory and launch TinyBasic, (TinyBasic has already been loaded)
 | 
						|
            if(isGtbFile  &&  gtbFilepath.size())
 | 
						|
            {
 | 
						|
                loadGtbFile(gtbFilepath);
 | 
						|
            }
 | 
						|
 | 
						|
            // Reset single step watch address to video line counter
 | 
						|
            Editor::setSingleStepAddress(FRAME_COUNT_ADDRESS);
 | 
						|
 | 
						|
            // Execute code
 | 
						|
            if(!_disableUploads  &&  hasRamCode)
 | 
						|
            {
 | 
						|
                // vPC
 | 
						|
                Cpu::setRAM(0x0016, LO_BYTE(execAddress-2));
 | 
						|
                Cpu::setRAM(0x0017, HI_BYTE(execAddress));
 | 
						|
 | 
						|
                // vLR
 | 
						|
                Cpu::setRAM(0x001a, LO_BYTE(execAddress-2));
 | 
						|
                Cpu::setRAM(0x001b, HI_BYTE(execAddress));
 | 
						|
 | 
						|
                // Reset stack and constants
 | 
						|
                Cpu::setRAM(STACK_POINTER, 0x00);
 | 
						|
                Cpu::setRAM(ZERO_CONST_ADDRESS, 0x00);
 | 
						|
                Cpu::setRAM(ONE_CONST_ADDRESS, 0x01);
 | 
						|
 | 
						|
                // Reset VBlank and video top
 | 
						|
                Cpu::setRAM16(VBLANK_PROC, 0x0000);
 | 
						|
                Cpu::setRAM(VIDEO_TOP, 0x00);
 | 
						|
 | 
						|
                // Reset video table and reset single step watch address to video line counter
 | 
						|
                Graphics::resetVTable();
 | 
						|
            }
 | 
						|
        }
 | 
						|
        else if(uploadTarget == Hardware)
 | 
						|
        {
 | 
						|
            if(!isGt1File)
 | 
						|
            {
 | 
						|
                size_t i = filepath.rfind('.');
 | 
						|
                std::string filepathGt1 = (i != std::string::npos) ? filepath.substr(0, i) + ".gt1" : filepath + ".gt1";
 | 
						|
                uploadToGiga(filepathGt1, filename);
 | 
						|
            }
 | 
						|
            else
 | 
						|
            {
 | 
						|
                uploadToGiga(filepath, filename);
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        // Updates browser in case a new gt1 file was created from a gcl file or a gasm file
 | 
						|
        if(gt1FileBuilt) Editor::browseDirectory();
 | 
						|
 | 
						|
        return;
 | 
						|
    }
 | 
						|
 | 
						|
    void sendByte(uint8_t value, uint8_t& checksum)
 | 
						|
    {
 | 
						|
        Cpu::setIN(value);
 | 
						|
        checksum += value;
 | 
						|
    }
 | 
						|
 | 
						|
    bool sendFrame(int vgaY, uint8_t firstByte, uint8_t* message, uint8_t len, uint16_t address, uint8_t& checksum)
 | 
						|
    {
 | 
						|
        static LoaderState loaderState = LoaderState::FirstByte;
 | 
						|
        static uint8_t payload[PAYLOAD_SIZE];
 | 
						|
 | 
						|
        bool sending = true;
 | 
						|
 | 
						|
        switch(loaderState)
 | 
						|
        {
 | 
						|
            case LoaderState::FirstByte: // 8 bits
 | 
						|
            {
 | 
						|
                if(vgaY == VSYNC_START+8)
 | 
						|
                {
 | 
						|
                    for(int i=0; i<len; ++i) payload[i % PAYLOAD_SIZE] = message[i % PAYLOAD_SIZE];
 | 
						|
                    sendByte(firstByte, checksum);
 | 
						|
                    checksum += firstByte << 6;
 | 
						|
                    loaderState = LoaderState::MsgLength;
 | 
						|
                }
 | 
						|
            }
 | 
						|
            break;
 | 
						|
 | 
						|
            case LoaderState::MsgLength: // 6 bits
 | 
						|
            {
 | 
						|
                if(vgaY == VSYNC_START+14)
 | 
						|
                {
 | 
						|
                    sendByte(len, checksum);
 | 
						|
                    loaderState = LoaderState::LowAddress;
 | 
						|
                }
 | 
						|
            }
 | 
						|
            break;
 | 
						|
 | 
						|
            case LoaderState::LowAddress: // 8 bits
 | 
						|
            {
 | 
						|
                if(vgaY == VSYNC_START+22)
 | 
						|
                {
 | 
						|
                    sendByte(LO_BYTE(address), checksum);
 | 
						|
                    loaderState = LoaderState::HighAddress;
 | 
						|
                }
 | 
						|
            }
 | 
						|
            break;
 | 
						|
 | 
						|
            case LoaderState::HighAddress: // 8 bits
 | 
						|
            {
 | 
						|
                if(vgaY == VSYNC_START+30)
 | 
						|
                {
 | 
						|
                    sendByte(HI_BYTE(address), checksum);
 | 
						|
                    loaderState = LoaderState::Message;
 | 
						|
                }
 | 
						|
            }
 | 
						|
            break;
 | 
						|
 | 
						|
            case LoaderState::Message: // 8*PAYLOAD_SIZE bits
 | 
						|
            {
 | 
						|
                static int msgIdx = 0;
 | 
						|
                if(vgaY == VSYNC_START+38+msgIdx*8)
 | 
						|
                {
 | 
						|
                    sendByte(payload[msgIdx], checksum);
 | 
						|
                    if(++msgIdx == PAYLOAD_SIZE)
 | 
						|
                    {
 | 
						|
                        msgIdx = 0;
 | 
						|
                        loaderState = LoaderState::LastByte;
 | 
						|
                    }
 | 
						|
                }
 | 
						|
            }
 | 
						|
            break;
 | 
						|
 | 
						|
            case LoaderState::LastByte: // 8 bits
 | 
						|
            {
 | 
						|
                if(vgaY == VSYNC_START+38+PAYLOAD_SIZE*8)
 | 
						|
                {
 | 
						|
                    uint8_t lastByte = -checksum;
 | 
						|
                    sendByte(lastByte, checksum);
 | 
						|
                    checksum = lastByte;
 | 
						|
                    loaderState = LoaderState::ResetIN;
 | 
						|
                }
 | 
						|
            }
 | 
						|
            break;
 | 
						|
 | 
						|
            case LoaderState::ResetIN:
 | 
						|
            {
 | 
						|
                if(vgaY == VSYNC_START+39+PAYLOAD_SIZE*8)
 | 
						|
                {
 | 
						|
                    Cpu::setIN(0xFF);
 | 
						|
                    loaderState = LoaderState::FirstByte;
 | 
						|
                    sending = false;
 | 
						|
                }
 | 
						|
            }
 | 
						|
            break;
 | 
						|
 | 
						|
            default: break;
 | 
						|
        }
 | 
						|
 | 
						|
        return sending;
 | 
						|
    }
 | 
						|
 | 
						|
    // TODO: fix the Gigatron version of upload so that it can send more than 60 total bytes, (i.e. break up the payload into multiple packets of 60, 1 packet per frame)
 | 
						|
    void upload(int vgaY)
 | 
						|
    {
 | 
						|
        static bool frameUploading = false;
 | 
						|
        static uint8_t payload[RAM_SIZE_HI];
 | 
						|
        static uint8_t payloadSize = 0;
 | 
						|
 | 
						|
        if(frameUploading)
 | 
						|
        {
 | 
						|
            uint16_t execAddress = Editor::getLoadBaseAddress();
 | 
						|
 | 
						|
            frameUploading = true;            
 | 
						|
            static uint8_t checksum = 0;
 | 
						|
            static FrameState frameState = FrameState::Resync;
 | 
						|
            switch(frameState)
 | 
						|
            {
 | 
						|
                case FrameState::Resync:
 | 
						|
                {
 | 
						|
                    if(!sendFrame(vgaY, 0xFF, payload, payloadSize, execAddress, checksum))
 | 
						|
                    {
 | 
						|
                        checksum = 'g'; // loader resets checksum
 | 
						|
                        frameState = FrameState::Frame;
 | 
						|
                    }
 | 
						|
                }
 | 
						|
                break;
 | 
						|
 | 
						|
                case FrameState::Frame:
 | 
						|
                {
 | 
						|
                    if(!sendFrame(vgaY,'L', payload, payloadSize, execAddress, checksum))
 | 
						|
                    {
 | 
						|
                        frameState = FrameState::Execute;
 | 
						|
                    }
 | 
						|
                }
 | 
						|
                break;
 | 
						|
 | 
						|
                case FrameState::Execute:
 | 
						|
                {
 | 
						|
                    if(!sendFrame(vgaY, 'L', payload, 0, execAddress, checksum))
 | 
						|
                    {
 | 
						|
                        checksum = 0;
 | 
						|
                        frameState = FrameState::Resync;
 | 
						|
                        frameUploading = false;
 | 
						|
                    }
 | 
						|
                }
 | 
						|
                break;
 | 
						|
 | 
						|
                default: break;
 | 
						|
            }
 | 
						|
        }
 | 
						|
    }
 | 
						|
#endif
 | 
						|
}
 |