#include #include #include #include #include #include #include #ifdef _WIN32 #include #include #include "dirent/dirent.h" #ifdef max #undef max #endif #ifdef min #undef min #endif #else #include #include #include #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 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 segmentsOut; int lastPage = int((Memory::getSizeRAM() - 1) >>8); for(int j=1; j<=lastPage; j++) { uint8_t hiAddr = uint8_t(j); std::vector segmentsIn; for(int i=0; i 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 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 _configRoms; std::string _currentGame = ""; INIReader _configIniReader; INIReader _highScoresIniReader; std::map _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 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 counts; std::vector addresses; std::vector endianness; std::vector> 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(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& 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& 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& 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& 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(sdata._counts[j], 0x00)); for(uint16_t i=0; i.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 .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 _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 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 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= ' '; 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= 0x8000 for(int i=0; i= RAM_UPPER_START) { setMemoryModel64k(); break; } } for(int j=0; j