2012-04-21 00:04:34 +00:00
|
|
|
/*
|
|
|
|
epple2
|
2015-07-29 01:14:58 +00:00
|
|
|
Copyright (C) 2008 by Christopher A. Mosher <cmosher01@gmail.com>
|
2012-04-21 00:04:34 +00:00
|
|
|
|
|
|
|
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"
|
|
|
|
|
2022-12-06 03:19:24 +00:00
|
|
|
#include "E2wxApp.h"
|
2022-12-10 02:49:59 +00:00
|
|
|
#include "e2filesystem.h"
|
2022-11-03 22:26:07 +00:00
|
|
|
#include "apple2.h"
|
2012-04-21 00:04:34 +00:00
|
|
|
#include "memory.h"
|
2019-02-02 21:08:32 +00:00
|
|
|
#include "memoryrandomaccess.h"
|
2012-04-21 00:04:34 +00:00
|
|
|
#include "slots.h"
|
|
|
|
#include "diskcontroller.h"
|
|
|
|
#include "languagecard.h"
|
|
|
|
#include "firmwarecard.h"
|
|
|
|
#include "standardout.h"
|
|
|
|
#include "standardin.h"
|
|
|
|
#include "clockcard.h"
|
2019-01-21 05:12:47 +00:00
|
|
|
#include "cassettein.h"
|
|
|
|
#include "cassetteout.h"
|
2018-12-29 05:24:16 +00:00
|
|
|
#include "tinyfiledialogs.h"
|
2012-04-21 00:04:34 +00:00
|
|
|
|
2022-12-06 03:19:24 +00:00
|
|
|
#include <wx/config.h>
|
|
|
|
#include <wx/string.h>
|
|
|
|
|
2022-12-10 02:49:59 +00:00
|
|
|
#include <boost/log/trivial.hpp>
|
|
|
|
|
2012-04-21 00:04:34 +00:00
|
|
|
#include <iostream>
|
|
|
|
#include <istream>
|
|
|
|
#include <fstream>
|
|
|
|
#include <sstream>
|
2022-12-06 23:00:53 +00:00
|
|
|
#include <filesystem>
|
2012-04-21 00:04:34 +00:00
|
|
|
#include <string>
|
|
|
|
#include <stdexcept>
|
2019-02-03 04:16:49 +00:00
|
|
|
#include <cctype>
|
|
|
|
|
2012-04-21 00:04:34 +00:00
|
|
|
|
2019-02-02 21:08:32 +00:00
|
|
|
|
2022-12-10 02:49:59 +00:00
|
|
|
static const wxString DEFAULT_CONFIG_NAME{"epple2"};
|
|
|
|
|
|
|
|
|
|
|
|
|
2019-02-02 21:08:32 +00:00
|
|
|
#define K 1024u
|
|
|
|
|
|
|
|
static std::uint16_t memory_block_size(const std::string &block_size) {
|
|
|
|
if (block_size == "4K") {
|
|
|
|
return 4u*K;
|
|
|
|
}
|
|
|
|
if (block_size == "16K") {
|
|
|
|
return 16u*K;
|
|
|
|
}
|
|
|
|
throw ConfigException("invalid RAM strapping block size (must be 4K or 16K)");
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
2012-04-21 00:04:34 +00:00
|
|
|
|
2022-12-10 02:49:59 +00:00
|
|
|
Config::Config(const std::filesystem::path& f, bool p): file_path {f}, prefs_only {p} {
|
2012-04-21 00:04:34 +00:00
|
|
|
}
|
|
|
|
|
2022-12-10 02:49:59 +00:00
|
|
|
Config::~Config() {
|
2012-04-21 00:04:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static void strip_comment(std::string& str)
|
|
|
|
{
|
2019-04-13 04:34:25 +00:00
|
|
|
const size_t comment = str.find('#');
|
|
|
|
if (comment < std::string::npos)
|
|
|
|
{
|
|
|
|
str.erase(comment);
|
|
|
|
}
|
2012-04-21 00:04:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static void trim(std::string& str)
|
|
|
|
{
|
2019-04-13 04:34:25 +00:00
|
|
|
{
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
2012-04-21 00:04:34 +00:00
|
|
|
}
|
|
|
|
|
2019-04-13 04:34:25 +00:00
|
|
|
|
|
|
|
|
2022-12-10 02:49:59 +00:00
|
|
|
/*
|
|
|
|
* Searches for config file with the given name in the preferences areas.
|
|
|
|
* The name must not be a filesystem path.
|
|
|
|
* The actual config files themselves have ".conf" extension.
|
|
|
|
* The preferences areas are searched in this order:
|
|
|
|
* user area
|
|
|
|
* built-in area
|
|
|
|
*
|
|
|
|
* If successful, returns an open stream, caller is responsible for deleting it
|
|
|
|
* Otherwise, returns null
|
|
|
|
*/
|
|
|
|
std::ifstream *Config::openFilePref(const wxString& s_name) {
|
|
|
|
std::ifstream *ret = nullptr;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
BOOST_LOG_TRIVIAL(info) << "config file name was specified as: " << s_name;
|
|
|
|
std::filesystem::path path_name = path_from_string(s_name);
|
|
|
|
if (path_name.has_parent_path()) {
|
|
|
|
BOOST_LOG_TRIVIAL(error) << "invalid name for config file (paths are not allowed): " << path_name.c_str();
|
|
|
|
return ret;
|
2019-04-13 04:34:25 +00:00
|
|
|
}
|
2022-12-06 03:19:24 +00:00
|
|
|
|
2022-12-10 02:49:59 +00:00
|
|
|
if (path_name.empty()) {
|
|
|
|
BOOST_LOG_TRIVIAL(error) << "invalid: empty name for config file";
|
|
|
|
return ret;
|
|
|
|
}
|
2022-12-06 03:19:24 +00:00
|
|
|
|
2022-12-10 02:49:59 +00:00
|
|
|
// required file extension for any file to be recognized as a config file
|
|
|
|
path_name = path_from_string(s_name+".conf");
|
2022-12-06 03:19:24 +00:00
|
|
|
|
2022-12-10 02:49:59 +00:00
|
|
|
|
|
|
|
|
|
|
|
std::filesystem::path path;
|
|
|
|
|
|
|
|
path = valid_input_file(path_name, wxGetApp().GetConfigDir());
|
|
|
|
if (!path.empty()) {
|
|
|
|
ret = new std::ifstream(path);
|
|
|
|
if (ret->is_open()) {
|
|
|
|
return ret; // Found specified config file in user area of preferences
|
2022-12-06 03:19:24 +00:00
|
|
|
}
|
2022-12-10 02:49:59 +00:00
|
|
|
delete ret;
|
|
|
|
ret = nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
path = valid_input_file(path_name, wxGetApp().GetResDir());
|
|
|
|
if (!path.empty()) {
|
|
|
|
ret = new std::ifstream(path);
|
|
|
|
if (ret->is_open()) {
|
|
|
|
return ret; // Found specified config file in built-in area of preferences
|
2022-12-06 03:19:24 +00:00
|
|
|
}
|
2022-12-10 02:49:59 +00:00
|
|
|
delete ret;
|
|
|
|
ret = nullptr;
|
2022-12-06 03:19:24 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
2022-12-10 02:49:59 +00:00
|
|
|
return ret;
|
|
|
|
}
|
2022-12-06 03:19:24 +00:00
|
|
|
|
2022-12-10 02:49:59 +00:00
|
|
|
std::ifstream *Config::openFileExternal(const std::filesystem::path& path) {
|
|
|
|
std::ifstream *ret = nullptr;
|
|
|
|
|
|
|
|
const std::filesystem::path p = valid_input_file(path);
|
|
|
|
if (!p.empty()) {
|
|
|
|
ret = new std::ifstream(p);
|
|
|
|
if (ret->is_open()) {
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
delete ret;
|
|
|
|
ret = nullptr;
|
2019-04-13 04:34:25 +00:00
|
|
|
}
|
2022-12-10 02:49:59 +00:00
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static const std::array rs_path_legacy{
|
|
|
|
"./etc/epple2/epple2.conf",
|
|
|
|
ETCDIR "/epple2/epple2.conf",
|
|
|
|
"/etc/epple2/epple2.conf",
|
|
|
|
"/etc/epple2.conf",
|
|
|
|
"./epple2.conf",
|
|
|
|
};
|
|
|
|
|
|
|
|
std::ifstream *Config::openFileLegacy() {
|
|
|
|
std::ifstream *ret = nullptr;
|
|
|
|
|
|
|
|
for (const auto &s_path_legacy : rs_path_legacy) {
|
|
|
|
if ((ret = openFileExternal(std::filesystem::path{s_path_legacy})) != nullptr) {
|
|
|
|
return ret;
|
|
|
|
}
|
2019-04-13 04:34:25 +00:00
|
|
|
}
|
2022-12-10 02:49:59 +00:00
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Algorithm to locate and open the configuration file, as specified by
|
|
|
|
* the user, either on command line, or via preferences, allowing for
|
|
|
|
* backward compatibility with legacy file locations.
|
|
|
|
*/
|
|
|
|
std::ifstream *Config::openFile() {
|
|
|
|
std::ifstream *ret = nullptr;
|
|
|
|
|
|
|
|
if (this->file_path.empty()) {
|
|
|
|
wxString cname{};
|
|
|
|
const bool stored_prefs_found = wxConfigBase::Get()->Read("/ActivePreferences/name", &cname, DEFAULT_CONFIG_NAME);
|
|
|
|
|
|
|
|
if (stored_prefs_found) {
|
|
|
|
ret = openFilePref(cname);
|
|
|
|
if (ret != nullptr) {
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!this->prefs_only) {
|
|
|
|
ret = openFileLegacy();
|
|
|
|
if (ret != nullptr) {
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (!this->prefs_only) {
|
|
|
|
ret = openFileLegacy();
|
|
|
|
if (ret != nullptr) {
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
ret = openFilePref(cname);
|
|
|
|
if (ret != nullptr) {
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (!this->prefs_only) {
|
|
|
|
ret = openFileExternal(this->file_path);
|
|
|
|
if (ret != nullptr) {
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
ret = openFilePref(this->file_path.c_str());
|
|
|
|
if (ret != nullptr) {
|
|
|
|
return ret;
|
|
|
|
}
|
2019-04-13 04:34:25 +00:00
|
|
|
}
|
2022-12-10 02:49:59 +00:00
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void Config::parse(MemoryRandomAccess& ram, Memory& rom, Slots& slts, int& revision, ScreenImage& gui, CassetteIn& cassetteIn, CassetteOut& cassetteOut, Apple2* apple2) {
|
|
|
|
std::ifstream *p_ifstream_config = openFile();
|
|
|
|
|
|
|
|
if (p_ifstream_config == nullptr) {
|
2022-11-01 05:40:32 +00:00
|
|
|
std::cerr << "Cannot find config file. Running without any RAM, ROM, or cards." << std::endl;
|
2019-04-13 04:34:25 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::string line;
|
2022-12-10 02:49:59 +00:00
|
|
|
std::getline(*p_ifstream_config, line);
|
|
|
|
while (!p_ifstream_config->eof()) {
|
2019-04-13 04:34:25 +00:00
|
|
|
strip_comment(line);
|
|
|
|
trim(line);
|
2022-12-10 02:49:59 +00:00
|
|
|
if (!line.empty()) {
|
|
|
|
// TODO "parseLine" will become Command::execute, or similar
|
|
|
|
parseLine(line, ram, rom, slts, revision, gui, cassetteIn, cassetteOut, apple2);
|
2019-04-13 04:34:25 +00:00
|
|
|
}
|
2022-12-10 02:49:59 +00:00
|
|
|
std::getline(*p_ifstream_config, line);
|
2019-04-13 04:34:25 +00:00
|
|
|
}
|
2022-12-10 02:49:59 +00:00
|
|
|
|
|
|
|
delete p_ifstream_config;
|
2012-04-21 00:04:34 +00:00
|
|
|
|
2019-04-13 04:34:25 +00:00
|
|
|
// TODO: make sure there is no more than ONE stdin and/or ONE stdout card
|
2012-04-21 00:04:34 +00:00
|
|
|
}
|
2022-12-08 07:05:30 +00:00
|
|
|
|
2022-12-10 02:49:59 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2022-11-03 22:26:07 +00:00
|
|
|
void Config::parseLine(const std::string& line, MemoryRandomAccess& ram, Memory& rom, Slots& slts, int& revision, ScreenImage& gui, CassetteIn& cassetteIn, CassetteOut& cassetteOut, Apple2* apple2)
|
2012-04-21 00:04:34 +00:00
|
|
|
{
|
2019-04-13 04:34:25 +00:00
|
|
|
try
|
|
|
|
{
|
2022-11-03 22:26:07 +00:00
|
|
|
tryParseLine(line,ram,rom,slts,revision,gui,cassetteIn,cassetteOut,apple2);
|
2019-04-13 04:34:25 +00:00
|
|
|
}
|
|
|
|
catch (const ConfigException& err)
|
|
|
|
{
|
|
|
|
std::cerr << err.msg.c_str() << std::endl;
|
|
|
|
}
|
2012-04-21 00:04:34 +00:00
|
|
|
}
|
|
|
|
|
2019-02-03 04:16:49 +00:00
|
|
|
static std::string filter_row(const std::string &row) {
|
|
|
|
if (row.length() != 1) {
|
|
|
|
return "";
|
|
|
|
}
|
|
|
|
return std::string(1, static_cast<char>(std::toupper(row[0])));
|
|
|
|
}
|
|
|
|
|
2022-11-03 22:26:07 +00:00
|
|
|
void Config::tryParseLine(const std::string& line, MemoryRandomAccess& ram, Memory& rom, Slots& slts, int& revision, ScreenImage& gui, CassetteIn& cassetteIn, CassetteOut& cassetteOut, Apple2* apple2)
|
2012-04-21 00:04:34 +00:00
|
|
|
{
|
2019-04-13 04:34:25 +00:00
|
|
|
std::istringstream tok(line);
|
|
|
|
|
|
|
|
std::string cmd;
|
|
|
|
tok >> cmd;
|
|
|
|
if (cmd == "slot")
|
|
|
|
{
|
|
|
|
int slot;
|
|
|
|
std::string sCardType;
|
|
|
|
tok >> slot >> sCardType;
|
|
|
|
|
2020-04-18 16:19:01 +00:00
|
|
|
insertCard(sCardType,slot,slts,gui,tok);
|
2019-04-13 04:34:25 +00:00
|
|
|
}
|
2019-02-02 21:08:32 +00:00
|
|
|
else if (cmd == "motherboard") {
|
|
|
|
std::string op;
|
|
|
|
tok >> op;
|
|
|
|
if (op == "ram") {
|
|
|
|
/* ram ROW BIT0 [BIT1 [... [BIT7]]]
|
|
|
|
* ram e -
|
|
|
|
* ram d 4096 MK4096 4K
|
|
|
|
* ram c 16K 4116 MK4116 MM5290 16K 16K 16K 16K
|
|
|
|
*/
|
|
|
|
std::string row;
|
|
|
|
tok >> row;
|
2019-02-03 04:16:49 +00:00
|
|
|
row = filter_row(row);
|
2019-02-02 21:08:32 +00:00
|
|
|
if (row != "C" && row != "D" && row != "E") {
|
|
|
|
throw ConfigException("expected row to be C, D, or E");
|
|
|
|
}
|
2019-02-07 05:45:54 +00:00
|
|
|
|
2019-02-02 21:08:32 +00:00
|
|
|
std::string chip_model;
|
|
|
|
tok >> chip_model;
|
|
|
|
for (std::uint_fast8_t bit = 0u; bit < 8u; ++bit) {
|
2019-02-07 05:45:54 +00:00
|
|
|
ram.insert_chip(row, MemoryChip::instance(chip_model), bit);
|
|
|
|
|
2019-02-02 21:08:32 +00:00
|
|
|
std::string chip_model_optional;
|
|
|
|
tok >> chip_model_optional;
|
|
|
|
if (chip_model_optional.length()) {
|
|
|
|
chip_model = chip_model_optional;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else if (op == "strap") {
|
|
|
|
/* strap ROM K start-addr
|
2019-02-07 05:45:54 +00:00
|
|
|
* strap e 4K 5000
|
|
|
|
* strap d 4K 4000
|
|
|
|
* strap c 16K 0000
|
2019-02-02 21:08:32 +00:00
|
|
|
*/
|
|
|
|
std::string row;
|
|
|
|
tok >> row;
|
2019-02-03 04:16:49 +00:00
|
|
|
row = filter_row(row);
|
2019-02-02 21:08:32 +00:00
|
|
|
if (row != "C" && row != "D" && row != "E") {
|
|
|
|
throw ConfigException("expected row to be C, D, or E");
|
|
|
|
}
|
|
|
|
|
|
|
|
std::string block_size;
|
|
|
|
tok >> block_size;
|
|
|
|
std::uint16_t siz = memory_block_size(block_size);
|
|
|
|
unsigned short base(0);
|
|
|
|
tok >> std::hex >> base;
|
|
|
|
// TODO validate siz/base combination
|
|
|
|
ram.strap_to(row, base, siz);
|
|
|
|
} else {
|
|
|
|
throw ConfigException("error at \"motherboard\"; expected \"ram\" or \"strap\"");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (cmd == "import")
|
2019-04-13 04:34:25 +00:00
|
|
|
{
|
|
|
|
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);
|
2022-12-06 03:19:24 +00:00
|
|
|
std::ifstream *memfile = new std::ifstream(file.c_str(),std::ios::binary);
|
|
|
|
if (!memfile->is_open())
|
2019-04-13 04:34:25 +00:00
|
|
|
{
|
2022-12-06 03:19:24 +00:00
|
|
|
std::filesystem::path f = wxGetApp().GetResDir();
|
|
|
|
f /= file;
|
|
|
|
memfile = new std::ifstream(f,std::ios::binary);
|
|
|
|
if (!memfile->is_open())
|
|
|
|
{
|
|
|
|
throw ConfigException("cannot open file "+file);
|
|
|
|
}
|
2019-04-13 04:34:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (slot < 0) // motherboard
|
|
|
|
{
|
|
|
|
if (romtype == "rom")
|
|
|
|
{
|
2022-12-06 03:19:24 +00:00
|
|
|
rom.load(base,*memfile);
|
2019-04-13 04:34:25 +00:00
|
|
|
}
|
|
|
|
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")
|
2022-12-06 03:19:24 +00:00
|
|
|
card->loadRom(base,*memfile);
|
2019-04-13 04:34:25 +00:00
|
|
|
else if (romtype == "rom7")
|
2022-12-06 03:19:24 +00:00
|
|
|
card->loadSeventhRom(base,*memfile);
|
2019-04-13 04:34:25 +00:00
|
|
|
else if (romtype == "rombank")
|
2022-12-06 03:19:24 +00:00
|
|
|
card->loadBankRom(base,*memfile);
|
2019-04-13 04:34:25 +00:00
|
|
|
else
|
|
|
|
throw ConfigException("error at \""+romtype+"\"; expected rom, rom7, or rombank");
|
|
|
|
}
|
2022-12-06 03:19:24 +00:00
|
|
|
memfile->close();
|
2019-04-13 04:34:25 +00:00
|
|
|
}
|
|
|
|
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")
|
|
|
|
{
|
2018-12-29 05:24:16 +00:00
|
|
|
std::string fn_optional;
|
|
|
|
std::getline(tok,fn_optional);
|
|
|
|
trim(fn_optional);
|
|
|
|
if (fn_optional.length() == 0) {
|
2019-01-13 04:30:34 +00:00
|
|
|
gui.exitFullScreen();
|
2018-12-29 05:24:16 +00:00
|
|
|
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) {
|
2022-12-06 03:19:24 +00:00
|
|
|
// TODO check if file exists, if not then check resources
|
2018-12-29 05:24:16 +00:00
|
|
|
loadDisk(slts,slot,drive,fn_optional);
|
|
|
|
}
|
|
|
|
}
|
2019-04-13 04:34:25 +00:00
|
|
|
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")
|
|
|
|
{
|
2019-01-21 05:12:47 +00:00
|
|
|
cassetteIn.rewind();
|
2019-04-13 04:34:25 +00:00
|
|
|
}
|
2019-01-22 02:37:22 +00:00
|
|
|
else if (cas == "tone")
|
|
|
|
{
|
|
|
|
cassetteIn.tone();
|
|
|
|
}
|
2019-01-21 05:12:47 +00:00
|
|
|
else if (cas == "blank")
|
2019-04-13 04:34:25 +00:00
|
|
|
{
|
|
|
|
std::string fcas;
|
|
|
|
std::getline(tok,fcas);
|
|
|
|
trim(fcas);
|
2019-01-21 05:12:47 +00:00
|
|
|
if (!fcas.empty()) {
|
|
|
|
cassetteOut.blank(fcas);
|
|
|
|
}
|
2019-04-13 04:34:25 +00:00
|
|
|
}
|
|
|
|
else if (cas == "load")
|
|
|
|
{
|
2019-01-21 05:12:47 +00:00
|
|
|
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);
|
|
|
|
}
|
2019-04-13 04:34:25 +00:00
|
|
|
}
|
2019-01-21 05:12:47 +00:00
|
|
|
else if (cas == "eject")
|
2019-04-13 04:34:25 +00:00
|
|
|
{
|
2019-01-21 05:12:47 +00:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
2019-04-13 04:34:25 +00:00
|
|
|
else if (cas == "save")
|
|
|
|
{
|
2019-01-21 05:12:47 +00:00
|
|
|
cassetteOut.save();
|
2019-04-13 04:34:25 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
throw ConfigException("error: unknown cassette command: "+cas);
|
|
|
|
}
|
|
|
|
}
|
2022-11-03 22:26:07 +00:00
|
|
|
else if (cmd == "cpu")
|
|
|
|
{
|
|
|
|
std::string cpu;
|
|
|
|
tok >> cpu;
|
|
|
|
if (apple2 != NULL) {
|
|
|
|
if (cpu == "epple2") {
|
|
|
|
apple2->useEpple2Cpu();
|
|
|
|
} else if (cpu == "visual6502") {
|
|
|
|
apple2->useVisual6502Cpu();
|
|
|
|
} else {
|
|
|
|
throw ConfigException("invalid value for cpu command: "+cpu);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-04-13 04:34:25 +00:00
|
|
|
else
|
|
|
|
{
|
|
|
|
throw ConfigException("Invalid command: "+cmd);
|
|
|
|
}
|
2022-11-03 22:26:07 +00:00
|
|
|
|
|
|
|
if (apple2 != NULL) {
|
|
|
|
apple2->useEpple2Cpu(); // set default CPU
|
|
|
|
}
|
2012-04-21 00:04:34 +00:00
|
|
|
}
|
|
|
|
|
2022-12-10 02:49:59 +00:00
|
|
|
|
|
|
|
|
|
|
|
unsigned char Config::disk_mask(0);
|
|
|
|
|
|
|
|
|
|
|
|
|
2012-04-21 00:04:34 +00:00
|
|
|
void Config::loadDisk(Slots& slts, int slot, int drive, const std::string& fnib)
|
|
|
|
{
|
2019-04-13 04:34:25 +00:00
|
|
|
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);
|
2012-04-21 00:04:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void Config::unloadDisk(Slots& slts, int slot, int drive)
|
|
|
|
{
|
2019-04-13 04:34:25 +00:00
|
|
|
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);
|
2012-04-21 00:04:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void Config::saveDisk(Slots& slts, int slot, int drive)
|
|
|
|
{
|
2019-04-13 04:34:25 +00:00
|
|
|
if (drive < 1 || 2 < drive)
|
|
|
|
{
|
|
|
|
throw ConfigException("Invalid drive; must be 1 or 2");
|
|
|
|
}
|
2018-12-29 05:24:16 +00:00
|
|
|
slts.get(slot)->save(drive-1);
|
2012-04-21 00:04:34 +00:00
|
|
|
}
|
|
|
|
|
2020-04-18 16:19:01 +00:00
|
|
|
void Config::insertCard(const std::string& cardType, int slot, Slots& slts, ScreenImage& gui, std::istringstream& tok)
|
2012-04-21 00:04:34 +00:00
|
|
|
{
|
2019-04-13 04:34:25 +00:00
|
|
|
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);
|
|
|
|
}
|
2018-12-18 13:46:46 +00:00
|
|
|
else if (cardType == "disk") // 16-sector LSS ROM
|
2019-04-13 04:34:25 +00:00
|
|
|
{
|
2020-04-18 16:19:01 +00:00
|
|
|
double random_ones_rate(0.3); // WOZ spec v2.0: 30%
|
|
|
|
tok >> random_ones_rate;
|
|
|
|
std::cerr << "MC3470: rate of 1 bits during random bit generation: " << random_ones_rate << std::endl;
|
|
|
|
card = new DiskController(gui,slot,false,random_ones_rate);
|
2019-04-13 04:34:25 +00:00
|
|
|
disk_mask |= (1 << slot);
|
|
|
|
}
|
2018-12-18 13:46:46 +00:00
|
|
|
else if (cardType == "disk13") // 13-sector LSS ROM
|
|
|
|
{
|
2020-04-18 16:19:01 +00:00
|
|
|
double random_ones_rate(0.3); // WOZ spec v2.0: 30%
|
|
|
|
tok >> random_ones_rate;
|
|
|
|
std::cerr << "MC3470: rate of 1 bits during random bit generation: " << random_ones_rate << std::endl;
|
|
|
|
card = new DiskController(gui,slot,true,random_ones_rate);
|
2018-12-18 13:46:46 +00:00
|
|
|
disk_mask |= (1 << slot);
|
|
|
|
}
|
|
|
|
else if (cardType == "clock")
|
2019-04-13 04:34:25 +00:00
|
|
|
{
|
|
|
|
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);
|
2012-04-21 00:04:34 +00:00
|
|
|
}
|