From 687fd2a779a24950c7f3420162248b298f99b825 Mon Sep 17 00:00:00 2001 From: Wolfgang Thaller Date: Tue, 3 Oct 2017 18:44:06 +0200 Subject: [PATCH] LaunchAPPL: more cleanup, config file support --- LaunchAPPL/CMakeLists.txt | 1 + LaunchAPPL/Executor.cc | 18 ++++- LaunchAPPL/LaunchAPPL.cc | 110 +++++++++++++++++++++++++----- LaunchAPPL/LaunchAPPL.cfg.example | 69 +++++++++++++++++++ LaunchAPPL/LaunchMethod.cc | 40 +++++++++++ LaunchAPPL/LaunchMethod.h | 3 + LaunchAPPL/MiniVMac.cc | 50 ++++++++------ 7 files changed, 252 insertions(+), 39 deletions(-) create mode 100644 LaunchAPPL/LaunchAPPL.cfg.example diff --git a/LaunchAPPL/CMakeLists.txt b/LaunchAPPL/CMakeLists.txt index 566513fdb2..30e846322d 100644 --- a/LaunchAPPL/CMakeLists.txt +++ b/LaunchAPPL/CMakeLists.txt @@ -16,6 +16,7 @@ if(APPLE) Carbon.h Carbon.cc) endif() endif() +add_definitions(-DRETRO68_PREFIX="${CMAKE_INSTALL_PREFIX}") add_executable(LaunchAPPL LaunchAPPL.cc diff --git a/LaunchAPPL/Executor.cc b/LaunchAPPL/Executor.cc index 8c9b7d4cc2..42277e587c 100644 --- a/LaunchAPPL/Executor.cc +++ b/LaunchAPPL/Executor.cc @@ -26,7 +26,15 @@ ExecutorLauncher::~ExecutorLauncher() bool ExecutorLauncher::Go(int timeout) { - return ChildProcess(options["executor-path"].as(), { appPath.string() }, timeout) == 0; + std::vector args; + if(options.count("executor-option")) + args = options["executor-option"].as>(); + args.push_back(appPath.string()); + + if(options.count("executor-system-folder")) + setenv("SystemFolder", options["executor-system-folder"].as().c_str(), true); + + return ChildProcess(options["executor-path"].as(), args, timeout) == 0; } @@ -34,12 +42,18 @@ void Executor::GetOptions(options_description &desc) { desc.add_options() ("executor-path", po::value()->default_value("executor"),"path to executor") + ("executor-system-folder", po::value(), + "system folder for executor (overrides SystemFolder environment variable)") + ("executor-option", po::value>(), + "pass an option to executor") ; } bool Executor::CheckOptions(variables_map &options) { - return options.count("executor-path") != 0; + if(options.count("executor-path") == 0) + return false; + return CheckExecutable(options["executor-path"].as()); } std::unique_ptr Executor::MakeLauncher(variables_map &options) diff --git a/LaunchAPPL/LaunchAPPL.cc b/LaunchAPPL/LaunchAPPL.cc index 322d3c654d..38f391b9d6 100644 --- a/LaunchAPPL/LaunchAPPL.cc +++ b/LaunchAPPL/LaunchAPPL.cc @@ -24,38 +24,95 @@ using std::string; using std::vector; static po::options_description desc; -po::variables_map options; +static po::variables_map options; +static vector configFiles; +static vector launchMethods; + +static void RegisterLaunchMethods() +{ + launchMethods = { +#if defined(__APPLE__) && defined(__powerpc) + new Classic(), +#endif +#ifdef HAS_LAUNCHCFMAPP + new Carbon(), +#endif + new Executor(), new MiniVMac() + // #### Add new `LaunchMethod`s here. + }; +} static void usage() { std::cerr << "Usage: " << "LaunchAPPL [options] appl-file\n"; std::cerr << desc << std::endl; + std::cerr << "Defaults are read from:\n"; + for(string str : configFiles) + { + std::cerr << "\t" << str; + if(!std::ifstream(str)) + std::cerr << " (not found)"; + std::cerr << std::endl; + } + std::cerr << std::endl; + + vector configuredMethods, unconfiguredMethods; + for(LaunchMethod *method : launchMethods) + (method->CheckOptions(options) ? configuredMethods : unconfiguredMethods) + .push_back(method->GetName()); + + if(!configuredMethods.empty()) + { + std::cerr << "Available emulators/environments:\n"; + for(string m : configuredMethods) + std::cerr << "\t" << m << std::endl; + } + if(!unconfiguredMethods.empty()) + { + std::cerr << "Emulators/environments needing more configuration:\n"; + for(string m : unconfiguredMethods) + std::cerr << "\t" << m << std::endl; + } + + if(options.count("emulator")) + { + string e = options["emulator"].as(); + std::cerr << "\nChosen emulator/environment: " << e; + if(std::find(configuredMethods.begin(), configuredMethods.end(), e) + != configuredMethods.end()) + std::cerr << "\n"; + else if(std::find(unconfiguredMethods.begin(), unconfiguredMethods.end(), e) + != unconfiguredMethods.end()) + std::cerr << " (needs more configuration)\n"; + else + std::cerr << " (UNKNOWN)\n"; + } + else + { + std::cerr << "\nNo emulator/environment chosen (-e)\n"; + } } int main(int argc, char *argv[]) { - std::vector methods = { -#if defined(__APPLE__) && defined(__powerpc) - new Classic(), -#endif -#ifdef HAS_LAUNCHCFMAPP - new Carbon(), -#endif - new Executor(), new MiniVMac() - }; + RegisterLaunchMethods(); + configFiles = { string(getenv("HOME")) + "/.LaunchAPPL.cfg", RETRO68_PREFIX "/LaunchAPPL.cfg"}; + desc.add_options() ("help,h", "show this help message") + ; + po::options_description configdesc; + configdesc.add_options() ("emulator,e", po::value(), "what emulator/environment to use") ; - for(LaunchMethod *lm : methods) - lm->GetOptions(desc); + for(LaunchMethod *lm : launchMethods) + lm->GetOptions(configdesc); + desc.add(configdesc); desc.add_options() ("timeout,t", po::value(),"abort after timeout") - ("timeout-ok","timeout counts as success") - ("logfile", po::value(), "read log file") ; po::options_description hidden, alldesc; hidden.add_options() @@ -79,6 +136,26 @@ int main(int argc, char *argv[]) return 1; } + for(string configFileName : configFiles) + { + try + { + std::ifstream cfg(configFileName); + if(cfg) + { + auto parsed = po::parse_config_file(cfg,configdesc,false); + + po::store(parsed, options); + } + } + catch(po::error& e) + { + std::cerr << "CONFIG FILE ERROR: " << e.what() << std::endl << std::endl; + usage(); + return 1; + } + } + po::notify(options); if(options.count("help") || !options.count("application") || !options.count("emulator")) @@ -88,7 +165,7 @@ int main(int argc, char *argv[]) } LaunchMethod *method = NULL; - for(LaunchMethod *lm : methods) + for(LaunchMethod *lm : launchMethods) { if(lm->GetName() == options["emulator"].as()) { @@ -98,13 +175,14 @@ int main(int argc, char *argv[]) } if(!method) { - std::cerr << "ERROR: unknown emulator/environment.\n"; + std::cerr << "ERROR: unknown emulator/environment: " << options["emulator"].as() << "\n"; return 1; } if(!method->CheckOptions(options)) { std::cerr << "Missing configuration.\n"; + usage(); return 1; } diff --git a/LaunchAPPL/LaunchAPPL.cfg.example b/LaunchAPPL/LaunchAPPL.cfg.example new file mode 100644 index 0000000000..5bb7e0780a --- /dev/null +++ b/LaunchAPPL/LaunchAPPL.cfg.example @@ -0,0 +1,69 @@ +# +# Example configuration file for LaunchAPPL +# Copy this file to $HOME/.LaunchAPPL.cfg and modify to your taste +# + +# ########### Classic Environment + + # If you are on a PowerPC Mac running Tiger (10.4), + # uncomment the following to use the Classic Environment: + +# emulator = classic + + +# ########### Carbon on Mac OS X (native PowerPC or Rosetta) + + # If you are on any Mac running Snow Leopard (10.6) or earlier + # and you are developing Carbon applications, use the following: + +# emulator = carbon + + +# ########### Mini vMac (old 68K Macs) + + # To use Mini vMac with LaunchAPPL, you need to supply the ROM file, + # a system disk image, and a download of autoquit from the minivmac web + # site, currently at + # http://www.gryphel.com/c/minivmac/extras/autoquit/index.html + # LaunchAPPL does not currently support MultiFinder or System 7. + +# Fill in the information below and uncomment the lines: + +# emulator = minivmac + + # The directory containing vMac.ROM + # All other paths relevant to Mini vMac are relative to this directory. +# minivmac-dir = /path/to/directory/with/vMac.ROM/ + + # First, we need Mini vMac itself: +# minivmac-path = ./Mini vMac + + # Next, a system disk image (System 6 or earlier) +# system-image = ./System.dsk + + # And finally, autoquit: +# autoquit-image = ./autoquit-1.1.1.dsk + +# ########### Executor (68K only) + + # No ROM files needed - an opensource reimplementation of classic Mac OS. + +# emulator = executor + + # If Executor is in your PATH and the SystemFolder environment variable + # is already set up, nothing else is required. + + # in case it's somewhere else: +# executor-path = /usr/local/bin/executor + + # Path to the Executor system folder: +# executor-system-folder = /path/to/ExecutorVolume/System Folder + + # Pass more options to Executor. + # Note that each "word" needs to be specified separately: +# executor-option = -size # emulated screen size +# executor-option = 1600x1200 + +# executor-option = -appearance # uncommenting these two lines +# executor-option = windows # is seriously not recommended. + diff --git a/LaunchAPPL/LaunchMethod.cc b/LaunchAPPL/LaunchMethod.cc index 34560d31db..b79928552c 100644 --- a/LaunchAPPL/LaunchMethod.cc +++ b/LaunchAPPL/LaunchMethod.cc @@ -1,4 +1,8 @@ #include "LaunchMethod.h" +#include +#include + +namespace fs = boost::filesystem; LaunchMethod::LaunchMethod() { @@ -18,3 +22,39 @@ bool LaunchMethod::CheckOptions(boost::program_options::variables_map &options) { return true; } + +bool LaunchMethod::CheckExecutable(std::string program) +{ + if(access(program.c_str(), X_OK) == 0) + return true; + if(program.find("/") != std::string::npos) + return false; + const char *PATH = getenv("PATH"); + + if(PATH) + { + bool endFound = false; + do + { + const char *end = strchr(PATH, ':'); + if(!end) + { + end = strchr(PATH, '\0'); + endFound = true; + } + std::string pathElement(PATH, end); + + if(pathElement == "") + pathElement = "."; + + fs::path f = fs::path(pathElement) / program; + + if(access(f.string().c_str(), X_OK) == 0) + return true; + + PATH = end + 1; + } while(!endFound); + } + + return false; +} diff --git a/LaunchAPPL/LaunchMethod.h b/LaunchAPPL/LaunchMethod.h index ab208db8e0..5fa1eb53f9 100644 --- a/LaunchAPPL/LaunchMethod.h +++ b/LaunchAPPL/LaunchMethod.h @@ -23,6 +23,9 @@ public: virtual bool CheckOptions(variables_map& options); virtual std::unique_ptr MakeLauncher(variables_map& options) = 0; + +protected: + bool CheckExecutable(std::string program); }; #endif // LAUNCHMETHOD_H diff --git a/LaunchAPPL/MiniVMac.cc b/LaunchAPPL/MiniVMac.cc index ac42ed035d..15eb341dbb 100644 --- a/LaunchAPPL/MiniVMac.cc +++ b/LaunchAPPL/MiniVMac.cc @@ -20,11 +20,13 @@ class MiniVMacLauncher : public Launcher { fs::path imagePath; fs::path systemImage; + fs::path vmacDir; + fs::path vmacPath; hfsvol *sysvol; hfsvol *vol; - void CopySystemFile(const char* fn, const char *dstfn = NULL); + void CopySystemFile(const std::string& fn, bool required); public: MiniVMacLauncher(po::variables_map& options); virtual ~MiniVMacLauncher(); @@ -38,9 +40,20 @@ MiniVMacLauncher::MiniVMacLauncher(po::variables_map &options) : Launcher(options, ResourceFile::Format::percent_appledouble), sysvol(NULL), vol(NULL) { - imagePath = fs::absolute(tempDir / "image.dsk"); - systemImage = fs::absolute(options["system-image"].as()); - fs::path autoquitImage = fs::absolute(options["autoquit-image"].as()); + imagePath = tempDir / "image.dsk"; + vmacDir = fs::absolute( options["minivmac-dir"].as() ); + vmacPath = fs::absolute( options["minivmac-path"].as(), vmacDir ); + + systemImage = fs::absolute(options["system-image"].as(), vmacDir); + fs::path autoquitImage = fs::absolute(options["autoquit-image"].as(), vmacDir); + + std::vector bootblock1(1024); + fs::ifstream(systemImage).read((char*) bootblock1.data(), 1024); + + if(bootblock1[0] != 'L' || bootblock1[1] != 'K' || bootblock1[0xA] > 15) + throw std::runtime_error("Not a bootable Mac disk image: " + systemImage.string()); + + string systemFileName(bootblock1.begin() + 0xB, bootblock1.begin() + 0xB + bootblock1[0xA]); int size = 5000*1024; @@ -49,10 +62,6 @@ MiniVMacLauncher::MiniVMacLauncher(po::variables_map &options) hfs_format(imagePath.string().c_str(), 0, 0, "SysAndApp", 0, NULL); { - std::vector bootblock1(1024); - - fs::ifstream(systemImage).read((char*) bootblock1.data(), 1024); - bootblock1[0x1A] = 8; memcpy(&bootblock1[0x1B],"AutoQuit", 8); bootblock1[0x5A] = 3; @@ -79,9 +88,8 @@ MiniVMacLauncher::MiniVMacLauncher(po::variables_map &options) - CopySystemFile("System"); - CopySystemFile("Finder"); - CopySystemFile("MacsBug"); + CopySystemFile(systemFileName, true); + CopySystemFile("MacsBug", false); { std::ostringstream rsrcOut; @@ -99,8 +107,10 @@ MiniVMacLauncher::MiniVMacLauncher(po::variables_map &options) hfs_umount(sysvol); sysvol = hfs_mount(autoquitImage.string().c_str(),0, HFS_MODE_RDONLY); + if(!sysvol) + throw std::runtime_error("Cannot open disk image: " + autoquitImage.string()); assert(sysvol); - CopySystemFile("AutoQuit"); + CopySystemFile("AutoQuit", true); { hfsfile *file = hfs_create(vol, "out", "TEXT", "MPS "); @@ -120,15 +130,15 @@ MiniVMacLauncher::~MiniVMacLauncher() } -void MiniVMacLauncher::CopySystemFile(const char *fn, const char *dstfn) +void MiniVMacLauncher::CopySystemFile(const std::string &fn, bool required) { - if(!dstfn) - dstfn = fn; hfsdirent fileent; - if(hfs_stat(sysvol, fn, &fileent) < 0) - return; - hfsfile *in = hfs_open(sysvol, fn); - hfsfile *out = hfs_create(vol, dstfn, fileent.u.file.type,fileent.u.file.creator); + if(hfs_stat(sysvol, fn.c_str(), &fileent) < 0) + { + throw std::runtime_error(string("File ") + fn + " not found in disk image"); + } + hfsfile *in = hfs_open(sysvol, fn.c_str()); + hfsfile *out = hfs_create(vol, fn.c_str(), fileent.u.file.type,fileent.u.file.creator); std::vector buffer(std::max(fileent.u.file.dsize, fileent.u.file.rsize)); hfs_setfork(in, 0); @@ -160,8 +170,6 @@ void MiniVMacLauncher::DumpOutput() vol = hfs_mount(imagePath.string().c_str(), 0, HFS_MODE_RDONLY); hfsdirent fileent; int statres = hfs_stat(vol, "out", &fileent); - std::cerr << "stat: " << statres << "\n"; - std::cerr << "out: " << fileent.u.file.dsize << " bytes\n"; hfsfile *out = hfs_open(vol, "out"); if(!out)