Epple-II/src/configep2.cpp

465 lines
10 KiB
C++

/*
epple2
Copyright (C) 2008 by Christopher A. Mosher <cmosher01@gmail.com>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY, without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "configep2.h"
#include "memory.h"
#include "slots.h"
#include "diskcontroller.h"
#include "languagecard.h"
#include "firmwarecard.h"
#include "standardout.h"
#include "standardin.h"
#include "clockcard.h"
#include "cassettein.h"
#include "cassetteout.h"
#include "tinyfiledialogs.h"
#include <iostream>
#include <istream>
#include <fstream>
#include <sstream>
#include <string>
#include <stdexcept>
unsigned char Config::disk_mask(0);
Config::Config(const std::string& file_path):
file_path(file_path)
{
}
Config::~Config()
{
}
static void strip_comment(std::string& str)
{
const size_t comment = str.find('#');
if (comment < std::string::npos)
{
str.erase(comment);
}
}
static void trim(std::string& str)
{
{
const size_t p = str.find_first_not_of(" \t");
if (p < std::string::npos)
{
str.erase(0,p);
}
}
{
const size_t p = str.find_last_not_of(" \t");
if (p+1 < std::string::npos)
{
str.erase(p+1);
}
}
}
void Config::parse(Memory& ram, Memory& rom, Slots& slts, int& revision, ScreenImage& gui, CassetteIn& cassetteIn, CassetteOut& cassetteOut)
{
std::ifstream* pConfig;
std::string path(this->file_path);
if (!path.empty())
{
pConfig = new std::ifstream(path.c_str());
if (!pConfig->is_open())
{
std::stringstream ss;
ss << "Cannot open config file " << this->file_path.c_str();
throw std::runtime_error(ss.str());
}
}
if (path.empty())
{
/*
On Windows, the default directory will be
C:\Program Files\Epple2 if they start the
program from the Start Menu; therefore
etc/epple2/epple2.conf would be
C:\Program Files\epple2\etc\epple2\epple2.conf
On Linux... the current directory could be
anything, so this probably won't find it (unless
the current directory is /).
*/
path = "etc/epple2/epple2.conf";
pConfig = new std::ifstream(path.c_str());
if (!pConfig->is_open())
path.clear();
}
if (path.empty())
{
/*
This is primarily for Linux. If configured for
a PREFIX of "/usr/local", then this would be
/usr/local/etc/epple2/epple2.conf
*/
path = ETCDIR "/epple2/epple2.conf";
pConfig = new std::ifstream(path.c_str());
if (!pConfig->is_open())
path.clear();
}
if (path.empty())
{
/*
Last effort to find it (most likely will
only work on Linux).
*/
path = "/etc/epple2/epple2.conf";
pConfig = new std::ifstream(path.c_str());
if (!pConfig->is_open())
path.clear();
}
if (path.empty())
{
std::cerr << "Cannot open config file /etc/epple2/epple2.conf" << std::endl;
return;
}
std::string line;
std::getline(*pConfig,line);
while (!pConfig->eof())
{
strip_comment(line);
trim(line);
if (!line.empty())
{
parseLine(line,ram,rom,slts,revision,gui,cassetteIn,cassetteOut);
}
std::getline(*pConfig,line);
}
pConfig->close();
delete pConfig;
// TODO: make sure there is no more than ONE stdin and/or ONE stdout card
}
void Config::parseLine(const std::string& line, Memory& ram, Memory& rom, Slots& slts, int& revision, ScreenImage& gui, CassetteIn& cassetteIn, CassetteOut& cassetteOut)
{
try
{
tryParseLine(line,ram,rom,slts,revision,gui,cassetteIn,cassetteOut);
}
catch (const ConfigException& err)
{
std::cerr << err.msg.c_str() << std::endl;
}
}
void Config::tryParseLine(const std::string& line, Memory& ram, Memory& rom, Slots& slts, int& revision, ScreenImage& gui, CassetteIn& cassetteIn, CassetteOut& cassetteOut)
{
std::istringstream tok(line);
std::string cmd;
tok >> cmd;
if (cmd == "slot")
{
int slot;
std::string sCardType;
tok >> slot >> sCardType;
insertCard(sCardType,slot,slts,gui);
}
else if (cmd == "import")
{
std::string sm;
tok >> sm;
int slot(-1);
if (sm == "slot")
{
tok >> slot;
}
else if (sm != "motherboard")
{
throw ConfigException("error at \""+sm+"\"; expected \"slot #\" or \"motherboard\"");
}
std::string romtype;
tok >> romtype;
unsigned short base(0);
tok >> std::hex >> base;
std::string file;
std::getline(tok,file);
trim(file);
std::ifstream memfile(file.c_str(),std::ios::binary);
if (!memfile.is_open())
{
throw ConfigException("cannot open file "+file);
}
if (slot < 0) // motherboard
{
if (romtype == "rom")
{
rom.load(base,memfile);
}
else if (romtype == "ram")
{
ram.load(base,memfile);
}
else
{
throw ConfigException("error at \""+romtype+"\"; expected rom or ram");
}
}
else
{
if (8 <= slot)
{
throw ConfigException("invalid slot number");
}
Card* card = slts.get(slot);
if (romtype == "rom")
card->loadRom(base,memfile);
else if (romtype == "rom7")
card->loadSeventhRom(base,memfile);
else if (romtype == "rombank")
card->loadBankRom(base,memfile);
else
throw ConfigException("error at \""+romtype+"\"; expected rom, rom7, or rombank");
}
memfile.close();
}
else if (cmd == "load" || cmd == "save" || cmd == "unload")
{
std::string slotk;
tok >> slotk;
if (slotk != "slot")
{
throw ConfigException("error at \""+slotk+"\"; expected \"slot\"");
}
int slot(-1);
tok >> slot;
std::string drivek;
tok >> drivek;
if (drivek != "drive")
{
throw ConfigException("error at \""+drivek+"\"; expected \"drive\"");
}
int drive(-1);
tok >> drive;
if (cmd == "load")
{
std::string fn_optional;
std::getline(tok,fn_optional);
trim(fn_optional);
if (fn_optional.length() == 0) {
gui.exitFullScreen();
char const *ft[1] = { "*.woz" };
char const *fn = tinyfd_openFileDialog("Load floppy", "", 1, ft, "WOZ 2.0 disk images", 0);
if (fn) {
fn_optional = std::string(fn);
}
}
if (fn_optional.length() > 0) {
loadDisk(slts,slot,drive,fn_optional);
}
}
else if (cmd == "unload")
{
unloadDisk(slts,slot,drive);
}
else if (cmd == "save")
{
saveDisk(slts,slot,drive);
}
}
else if (cmd == "revision")
{
tok >> std::hex >> revision;
}
else if (cmd == "cassette")
{
std::string cas;
tok >> cas;
if (cas == "rewind")
{
cassetteIn.rewind();
}
else if (cas == "tone")
{
cassetteIn.tone();
}
else if (cas == "blank")
{
std::string fcas;
std::getline(tok,fcas);
trim(fcas);
if (!fcas.empty()) {
cassetteOut.blank(fcas);
}
}
else if (cas == "load")
{
std::string fn_optional;
std::getline(tok,fn_optional);
trim(fn_optional);
if (fn_optional.length() == 0) {
gui.exitFullScreen();
char const *ft[1] = { "*.wav" };
char const *fn = tinyfd_openFileDialog("Load cassette audio", "", 1, ft, "WAVE cassette images", 0);
if (fn) {
fn_optional = std::string(fn);
}
}
if (fn_optional.length() > 0) {
cassetteIn.load(fn_optional);
}
}
else if (cas == "eject")
{
std::string eject;
tok >> eject;
if (eject == "in") {
cassetteIn.eject();
} else if (eject == "out") {
cassetteOut.eject();
} else {
throw ConfigException("error: unknown cassette to eject: "+eject);
}
}
else if (cas == "save")
{
cassetteOut.save();
}
else
{
throw ConfigException("error: unknown cassette command: "+cas);
}
}
else
{
throw ConfigException("Invalid command: "+cmd);
}
}
void Config::loadDisk(Slots& slts, int slot, int drive, const std::string& fnib)
{
if (drive < 1 || 2 < drive)
{
throw ConfigException("Invalid drive; must be 1 or 2");
}
// TODO if file doesn't exist, name still gets displayed, and there's no error message
Card* card = slts.get(slot);
if (!(disk_mask & (1 << slot)))
{
std::cerr << "Slot " << slot << " doesn't have a disk controller card" << std::endl;
return;
}
DiskController* controller = (DiskController*)card;
controller->loadDisk(drive-1,fnib);
}
void Config::unloadDisk(Slots& slts, int slot, int drive)
{
if (drive < 1 || 2 < drive)
{
throw ConfigException("Invalid drive; must be 1 or 2");
}
Card* card = slts.get(slot);
if (!(disk_mask & (1 << slot)))
{
std::cerr << "Slot " << slot << " doesn't have a disk controller card" << std::endl;
return;
}
DiskController* controller = (DiskController*)card;
controller->unloadDisk(drive-1);
}
void Config::saveDisk(Slots& slts, int slot, int drive)
{
if (drive < 1 || 2 < drive)
{
throw ConfigException("Invalid drive; must be 1 or 2");
}
slts.get(slot)->save(drive-1);
}
void Config::insertCard(const std::string& cardType, int slot, Slots& slts, ScreenImage& gui)
{
if (slot < 0 || 8 <= slot)
{
throw ConfigException("Invalid slot number");
}
Card* card;
disk_mask &= ~(1 << slot);
if (cardType == "language")
{
card = new LanguageCard(gui,slot);
}
else if (cardType == "firmware")
{
card = new FirmwareCard(gui,slot);
}
else if (cardType == "disk") // 16-sector LSS ROM
{
card = new DiskController(gui,slot,false);
disk_mask |= (1 << slot);
}
else if (cardType == "disk13") // 13-sector LSS ROM
{
card = new DiskController(gui,slot,true);
disk_mask |= (1 << slot);
}
else if (cardType == "clock")
{
card = new ClockCard();
}
else if (cardType == "stdout")
{
card = new StandardOut();
}
else if (cardType == "stdin")
{
card = new StandardIn();
}
else if (cardType == "empty")
{
card = 0;
}
else
{
throw ConfigException("Invalid card type: "+cardType);
}
if (card)
slts.set(slot,card);
else
slts.remove(slot);
}