Merge pull request #30 from autc04/automated-tests

Automated tests
This commit is contained in:
Wolfgang Thaller 2017-10-07 01:46:30 +02:00 committed by GitHub
commit 207b094372
34 changed files with 1780 additions and 23 deletions

View File

@ -0,0 +1,62 @@
cmake_minimum_required(VERSION 3.3)
find_program(LAUNCH_APPL LaunchAPPL PATH "${CMAKE_INSTALL_PREFIX}/../bin/")
execute_process(COMMAND ${LAUNCH_APPL} --list-emulators
OUTPUT_VARIABLE EMULATOR_LIST)
string(REPLACE "\n" ";" EMULATOR_LIST ${EMULATOR_LIST})
if(CMAKE_SYSTEM_NAME MATCHES "RetroCarbon")
if("carbon" IN_LIST EMULATOR_LIST)
set(RETRO68_LAUNCH_METHOD carbon CACHE String "How to launch Mac applications (for automated testing)")
else()
set(RETRO68_LAUNCH_METHOD NONE CACHE String "How to launch Mac applications (for automated testing)")
endif()
else()
if("classic" IN_LIST EMULATOR_LIST)
set(RETRO68_LAUNCH_METHOD classic CACHE String "How to launch Mac applications (for automated testing)")
else()
set(RETRO68_LAUNCH_METHOD NONE CACHE String "How to launch Mac applications (for automated testing)")
endif()
endif()
set(RETRO68_TEST_CONFIG "--timeout=10" CACHE String "Options to pass to LaunchAPPL when running tests")
if(RETRO68_LAUNCH_METHOD MATCHES "NONE")
else() # extends to end of file
enable_testing()
function(test FILE)
get_filename_component(NAME ${FILE} NAME_WE)
add_application(${NAME} ${FILE} Test.h Test.c)
add_test(NAME ${NAME} COMMAND ${LAUNCH_APPL}
-e ${RETRO68_LAUNCH_METHOD} ${RETRO68_TEST_CONFIG} ${ARGN} ${NAME}.bin)
endfunction()
if(CMAKE_SYSTEM_NAME MATCHES "Retro68")
test(ReallyEmpty.c)
set_target_properties(ReallyEmpty PROPERTIES LINK_FLAGS "-Wl,-gc-sections -Wl,--mac-single")
endif()
test(Empty.c)
test(File.c)
set_tests_properties(File PROPERTIES PASS_REGULAR_EXPRESSION "OK")
test(Timeout.c)
set_tests_properties(Timeout PROPERTIES PASS_REGULAR_EXPRESSION "One")
test(Log.c)
set_tests_properties(Log PROPERTIES PASS_REGULAR_EXPRESSION "One\nTwo\nThree")
test(ZeroInitialized.c)
set_tests_properties(Log PROPERTIES PASS_REGULAR_EXPRESSION "One\nTwo\nThree")
test(Init.cc)
set_tests_properties(Init PROPERTIES PASS_REGULAR_EXPRESSION "constructor\nmain\ndestructor")
endif() # RETRO68_LAUNCH_METHOD

5
AutomatedTests/Empty.c Normal file
View File

@ -0,0 +1,5 @@
int main()
{
// Test: do things work well enough for us to get to main()?
return 0;
}

6
AutomatedTests/File.c Normal file
View File

@ -0,0 +1,6 @@
#include "Test.h"
int main()
{
TEST_LOG_OK();
}

22
AutomatedTests/Init.cc Normal file
View File

@ -0,0 +1,22 @@
#include "Test.h"
class Constructed
{
public:
Constructed()
{
TestLog("constructor");
}
~Constructed()
{
TestLog("destructor");
}
};
Constructed thing;
int main()
{
TestLog("main");
return 0;
}

13
AutomatedTests/Log.c Normal file
View File

@ -0,0 +1,13 @@
#include "Test.h"
char readWriteData[6] = "Three";
int main()
{
// constant initialized data
TEST_LOG_SIZED("One",3);
TEST_LOG_SIZED("Two",3);
// read-write initialized data
TEST_LOG_SIZED(readWriteData,5);
return 0;
}

View File

@ -0,0 +1,6 @@
void _start()
{
// Test: do things work well enough for us to get to a startup function?
// Note: this won't work for multisegment 68K apps, as the startup function will be in the wrong segment.
}

6
AutomatedTests/Test.c Normal file
View File

@ -0,0 +1,6 @@
#include "Test.h"
void TestLog(const char *str)
{
TEST_LOG_SIZED(str, strlen(str));
}

97
AutomatedTests/Test.h Normal file
View File

@ -0,0 +1,97 @@
#ifndef TEST_H
#define TEST_H
#include <Files.h>
#include <Devices.h>
#include <string.h>
/*
Log test output to a file called 'out' in the current directory.
Most of this is implemented as macros, in a very cumbersome, low-level way,
avoiding the use of function calls, string constants or global variables.
This way, we only test what we want to test.
*/
/* The "high level" variant - log a string. */
#ifdef __cplusplus
extern "C"
#endif
void TestLog(const char *str);
/* The same thing as a macro. String length has to be given explicitly,
* to avoid a call to strlen(). */
#define TEST_LOG_SIZED(str, size) \
do { \
HParamBlockRec _hpb; \
\
unsigned char _fileName[4]; \
short _ref;\
_fileName[0] = 3; \
_fileName[1] = 'o'; \
_fileName[2] = 'u'; \
_fileName[3] = 't'; \
\
_hpb.ioParam.ioCompletion = NULL; \
_hpb.ioParam.ioNamePtr = (StringPtr)_fileName; \
_hpb.ioParam.ioVRefNum = 0; \
_hpb.fileParam.ioDirID = 0; \
_hpb.ioParam.ioPermssn = fsRdWrPerm; \
_hpb.ioParam.ioMisc = NULL; \
PBHOpenSync(&_hpb); \
_ref = _hpb.ioParam.ioRefNum; \
\
_hpb.ioParam.ioCompletion = NULL; \
_hpb.ioParam.ioBuffer = (Ptr)str; \
_hpb.ioParam.ioReqCount = size; \
_hpb.ioParam.ioPosMode = fsFromLEOF; \
_hpb.ioParam.ioPosOffset = 0; \
_hpb.ioParam.ioRefNum = _ref; \
_hpb.ioParam.ioMisc = NULL; \
PBWriteSync((void*)&_hpb); \
char _newline = '\n'; \
_hpb.ioParam.ioCompletion = NULL; \
_hpb.ioParam.ioBuffer = &_newline; \
_hpb.ioParam.ioReqCount = 1; \
_hpb.ioParam.ioPosMode = fsFromLEOF; \
_hpb.ioParam.ioPosOffset = 0; \
_hpb.ioParam.ioRefNum = _ref; \
_hpb.ioParam.ioMisc = NULL; \
PBWriteSync((void*)&_hpb); \
_hpb.ioParam.ioCompletion = NULL; \
_hpb.ioParam.ioRefNum = _ref; \
_hpb.ioParam.ioMisc = NULL; \
PBCloseSync((void*)&_hpb); \
_hpb.ioParam.ioCompletion = NULL; \
_hpb.ioParam.ioNamePtr = NULL; \
_hpb.ioParam.ioVRefNum = 0; \
_hpb.ioParam.ioMisc = NULL; \
PBFlushVolSync((void*)&_hpb); \
} while(0);
/*
* Output either "OK" or "NO".
* String constants are off-limits,
* we might not want to test them yet.
*/
#define TEST_LOG_OK() \
do { \
char ok[3]; \
ok[0] = 'O'; \
ok[1] = 'K'; \
ok[2] = '\0'; \
TEST_LOG_SIZED(ok, 2); \
} while(0)
#define TEST_LOG_NO() \
do { \
char no[3]; \
no[0] = 'O'; \
no[1] = 'K'; \
no[2] = '\0'; \
TEST_LOG_SIZED(no, 2); \
} while(0)
#endif // TEST_H

11
AutomatedTests/Timeout.c Normal file
View File

@ -0,0 +1,11 @@
#include "Test.h"
int main()
{
TEST_LOG_SIZED("One",3);
TEST_LOG_SIZED("Two",3);
for(;;)
;
TEST_LOG_SIZED("Three",5);
return 0;
}

View File

@ -0,0 +1,42 @@
#include "Test.h"
#include <Events.h>
int zeroInitedArray[32768];
int commonSymbol;
int zeroInited = 0;
EventRecord e;
int main()
{
int i;
if(commonSymbol)
{
TEST_LOG_NO();
return 1;
}
if(zeroInited)
{
TEST_LOG_NO();
return 1;
}
for(i = 0; i < 32768; i++)
{
if(zeroInitedArray[i])
{
TEST_LOG_NO();
return 1;
}
zeroInitedArray[i] = 42;
}
GetNextEvent(everyEvent, &e);
for(i = 0; i < 32768; i++)
{
if(zeroInitedArray[i] != 42)
{
TEST_LOG_NO();
return 1;
}
}
TEST_LOG_OK();
return 0;
}

View File

@ -15,9 +15,12 @@
# You should have received a copy of the GNU General Public License
# along with Retro68. If not, see <http://www.gnu.org/licenses/>.
cmake_minimum_required(VERSION 3.1)
cmake_minimum_required(VERSION 3.3)
project(Retro)
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED TRUE)
set(CMAKE_C_STANDARD 99)
set(CMAKE_C_STANDARD_REQUIRED TRUE)
if(CMAKE_SYSTEM_NAME MATCHES Retro.*)
@ -48,6 +51,9 @@ add_subdirectory(Samples/Launcher)
add_subdirectory(Samples/SystemExtension)
endif()
enable_testing()
add_subdirectory(AutomatedTests)
else()
set(RETRO68_ROOT ${CMAKE_INSTALL_PREFIX})
@ -62,4 +68,5 @@ add_subdirectory(Rez)
add_subdirectory(ConvertObj)
add_subdirectory(PEFTools)
add_subdirectory(Elf2Mac)
add_subdirectory(LaunchAPPL)
endif()

15
Dockerfile Normal file
View File

@ -0,0 +1,15 @@
# vim:ft=dockerfile
FROM ubuntu:16.04
RUN apt-get update && apt-get -y install \
g++ \
cmake libgmp-dev libmpfr-dev libmpc-dev libboost-all-dev bison \
zlib1g-dev \
perl texinfo
RUN mkdir /root/Retro68
COPY . /root/Retro68/
RUN mkdir /root/Retro68-build
RUN sh -c "cd /root/Retro68-build && sh ../Retro68/build-toolchain.sh --clean-after-build"

35
LaunchAPPL/CMakeLists.txt Normal file
View File

@ -0,0 +1,35 @@
find_package(Boost COMPONENTS filesystem program_options)
set(LAUNCHMETHODS
Executor.h Executor.cc
MiniVMac.h MiniVMac.cc
)
if(APPLE)
LIST(APPEND LAUNCHMETHODS
Classic.h Classic.cc
)
LIST(APPEND LAUNCHMETHODS
Carbon.h Carbon.cc)
endif()
add_definitions(-DRETRO68_PREFIX="${CMAKE_INSTALL_PREFIX}")
add_executable(LaunchAPPL
LaunchAPPL.cc
MakeExecutable.cc
LaunchMethod.h LaunchMethod.cc
Launcher.h Launcher.cc
${LAUNCHMETHODS})
target_include_directories(LaunchAPPL PRIVATE ${CMAKE_INSTALL_PREFIX}/include ${Boost_INCLUDE_DIR})
target_link_libraries(LaunchAPPL ResourceFiles ${Boost_LIBRARIES})
if(APPLE)
find_library(APPLICATIONSERVICES_FW ApplicationServices)
target_link_libraries(LaunchAPPL ${APPLICATIONSERVICES_FW})
endif()
install(TARGETS LaunchAPPL RUNTIME DESTINATION bin)

46
LaunchAPPL/Carbon.cc Normal file
View File

@ -0,0 +1,46 @@
#include "Carbon.h"
#include "Launcher.h"
const std::string launchCFM =
"/System/Library/Frameworks/Carbon.framework/Versions/A/Support/LaunchCFMApp";
namespace po = boost::program_options;
class CarbonLauncher : public Launcher
{
public:
CarbonLauncher(po::variables_map& options);
virtual ~CarbonLauncher();
virtual bool Go(int timeout = 0);
};
CarbonLauncher::CarbonLauncher(po::variables_map &options)
: Launcher(options, ResourceFile::Format::real)
{
}
CarbonLauncher::~CarbonLauncher()
{
}
bool CarbonLauncher::Go(int timeout)
{
return ChildProcess(launchCFM, { appPath.string() }, timeout) == 0;
}
bool Carbon::CheckPlatform()
{
/* If LaunchCFMApp doesn't exist, we're likely on a Mac OS X version
where it doesn't exist anymore (10.7 Lion or later),
or on an entirely different platform. */
return CheckExecutable(launchCFM);
}
std::unique_ptr<Launcher> Carbon::MakeLauncher(variables_map &options)
{
return std::unique_ptr<Launcher>(new CarbonLauncher(options));
}

15
LaunchAPPL/Carbon.h Normal file
View File

@ -0,0 +1,15 @@
#ifndef CARBON_METHOD_H
#define CARBON_METHOD_H
#include "LaunchMethod.h"
class Carbon : public LaunchMethod
{
public:
virtual std::string GetName() { return "carbon"; }
virtual bool CheckPlatform();
virtual std::unique_ptr<Launcher> MakeLauncher(variables_map& options);
};
#endif // CARBON_METHOD_H

82
LaunchAPPL/Classic.cc Normal file
View File

@ -0,0 +1,82 @@
#define ResType MacResType
#include <ApplicationServices/ApplicationServices.h>
#undef ResType
#if TARGET_CPU_PPC
#include "Classic.h"
#include "Launcher.h"
namespace po = boost::program_options;
class ClassicLauncher : public Launcher
{
public:
ClassicLauncher(po::variables_map& options);
virtual ~ClassicLauncher();
virtual bool Go(int timeout = 0);
};
ClassicLauncher::ClassicLauncher(po::variables_map &options)
: Launcher(options, ResourceFile::Format::real)
{
}
ClassicLauncher::~ClassicLauncher()
{
}
bool ClassicLauncher::Go(int timeout)
{
FSRef ref;
FSPathMakeRef((const UInt8*) appPath.string().c_str(), &ref, NULL);
LSApplicationParameters params;
memset(&params, 0, sizeof(params));
params.flags = kLSLaunchStartClassic
| kLSLaunchInClassic
| kLSLaunchDontAddToRecents
| kLSLaunchNewInstance;
params.application = &ref;
ProcessSerialNumber psn;
LSOpenApplication(&params, &psn);
// Classic startup takes place before LSOpenApplication returns,
// so no extra timeout is needed
for(int i = 0; i < timeout || timeout == 0; i++)
{
sleep(1);
ProcessInfoRec pi;
pi.processInfoLength = sizeof(pi);
pi.processName = NULL;
pi.processAppSpec = 0;
if(GetProcessInformation(&psn, &pi) == procNotFound)
return true;
}
KillProcess(&psn);
return false;
}
bool Classic::CheckPlatform()
{
long sysver = 0;
Gestalt(gestaltSystemVersion, &sysver);
if(sysver >= 0x1050)
return false;
else
return true;
}
std::unique_ptr<Launcher> Classic::MakeLauncher(variables_map &options)
{
return std::unique_ptr<Launcher>(new ClassicLauncher(options));
}
#endif

15
LaunchAPPL/Classic.h Normal file
View File

@ -0,0 +1,15 @@
#ifndef CLASSIC_H
#define CLASSIC_H
#include "LaunchMethod.h"
class Classic : public LaunchMethod
{
public:
virtual std::string GetName() { return "classic"; }
virtual bool CheckPlatform();
virtual std::unique_ptr<Launcher> MakeLauncher(variables_map& options);
};
#endif // CLASSIC_H

62
LaunchAPPL/Executor.cc Normal file
View File

@ -0,0 +1,62 @@
#include "Executor.h"
#include "Launcher.h"
namespace po = boost::program_options;
class ExecutorLauncher : public Launcher
{
public:
ExecutorLauncher(po::variables_map& options);
virtual ~ExecutorLauncher();
virtual bool Go(int timeout = 0);
};
ExecutorLauncher::ExecutorLauncher(po::variables_map &options)
: Launcher(options, ResourceFile::Format::percent_appledouble)
{
}
ExecutorLauncher::~ExecutorLauncher()
{
}
bool ExecutorLauncher::Go(int timeout)
{
std::vector<std::string> args;
if(options.count("executor-option"))
args = options["executor-option"].as<std::vector<std::string>>();
args.push_back(appPath.string());
if(options.count("executor-system-folder"))
setenv("SystemFolder", options["executor-system-folder"].as<std::string>().c_str(), true);
return ChildProcess(options["executor-path"].as<std::string>(), args, timeout) == 0;
}
void Executor::GetOptions(options_description &desc)
{
desc.add_options()
("executor-path", po::value<std::string>()->default_value("executor"),"path to executor")
("executor-system-folder", po::value<std::string>(),
"system folder for executor (overrides SystemFolder environment variable)")
("executor-option", po::value<std::vector<std::string>>(),
"pass an option to executor")
;
}
bool Executor::CheckOptions(variables_map &options)
{
if(options.count("executor-path") == 0)
return false;
return CheckExecutable(options["executor-path"].as<std::string>());
}
std::unique_ptr<Launcher> Executor::MakeLauncher(variables_map &options)
{
return std::unique_ptr<Launcher>(new ExecutorLauncher(options));
}

16
LaunchAPPL/Executor.h Normal file
View File

@ -0,0 +1,16 @@
#ifndef EXECUTOR_H
#define EXECUTOR_H
#include "LaunchMethod.h"
class Executor : public LaunchMethod
{
public:
virtual std::string GetName() { return "executor"; }
virtual void GetOptions(options_description& desc);
virtual bool CheckOptions(variables_map& options);
virtual std::unique_ptr<Launcher> MakeLauncher(variables_map& options);
};
#endif // EXECUTOR_H

251
LaunchAPPL/LaunchAPPL.cc Normal file
View File

@ -0,0 +1,251 @@
#include <boost/program_options.hpp>
#include <boost/filesystem.hpp>
#include <boost/filesystem/fstream.hpp>
#include <iostream>
#include <vector>
#include <string>
#include "LaunchMethod.h"
#include "Launcher.h"
#if defined(__APPLE__)
# define ResType MacResType
# include <ApplicationServices/ApplicationServices.h>
# undef ResType
# if TARGET_CPU_PPC
# include "Classic.h"
# endif
# include "Carbon.h"
#endif
#include "Executor.h"
#include "MiniVMac.h"
namespace po = boost::program_options;
namespace fs = boost::filesystem;
using std::string;
using std::vector;
static po::options_description desc;
static po::variables_map options;
static vector<string> configFiles;
static vector<LaunchMethod*> launchMethods;
static void RegisterLaunchMethods()
{
vector<LaunchMethod*> methods = {
#if defined(__APPLE__)
# if TARGET_CPU_PPC
new Classic(),
# endif
new Carbon(),
#endif
new Executor(), new MiniVMac()
// #### Add new `LaunchMethod`s here.
};
for(LaunchMethod *m : methods)
{
if(m->CheckPlatform())
launchMethods.push_back(m);
}
}
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<string> 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<string>();
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";
}
}
void MakeExecutable(string filepath);
int main(int argc, char *argv[])
{
RegisterLaunchMethods();
configFiles = { string(getenv("HOME")) + "/.LaunchAPPL.cfg", RETRO68_PREFIX "/LaunchAPPL.cfg"};
desc.add_options()
("help,h", "show this help message")
("list-emulators,l", "get the list of available, fully configured emulators/environments")
("make-executable,x", po::value<std::string>(), "make a MacBinary file executable")
;
po::options_description configdesc;
configdesc.add_options()
("emulator,e", po::value<std::string>(), "what emulator/environment to use")
;
for(LaunchMethod *lm : launchMethods)
lm->GetOptions(configdesc);
desc.add(configdesc);
desc.add_options()
("timeout,t", po::value<int>(),"abort after timeout")
;
po::options_description hidden, alldesc;
hidden.add_options()
("application,a", po::value<std::string>(), "application" )
;
alldesc.add(desc).add(hidden);
try
{
auto parsed = po::command_line_parser(argc, argv)
.options(alldesc)
.positional(po::positional_options_description().add("application", -1))
.style(po::command_line_style::default_style)
.run();
po::store(parsed, options);
}
catch(po::error& e)
{
std::cerr << "ERROR: " << e.what() << std::endl << std::endl;
usage();
return 1;
}
for(string configFileName : configFiles)
{
try
{
std::ifstream cfg(configFileName);
if(cfg)
{
auto parsed = po::parse_config_file<char>(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);
vector<string> commandModes = {"application", "help", "make-executable", "list-emulators"};
int nModes = 0;
string mode;
for(string aMode : commandModes)
{
if(options.count(aMode))
{
nModes++;
mode = aMode;
}
}
if(nModes > 1)
{
std::cerr << "Need to specify either an application file or exactly one of ";
for(int i = 1, n = commandModes.size(); i < n-1; i++)
std::cerr << "--" << commandModes[i] << ", ";
std::cerr << "or " << commandModes.back() << "." << std::endl << std::endl;
usage();
return 1;
}
if(mode == "" || mode == "help")
{
usage();
return 0;
}
else if(mode == "make-executable")
{
string fn = options["make-executable"].as<std::string>();
MakeExecutable(fn);
return 0;
}
else if(mode == "list-emulators")
{
for(LaunchMethod *method : launchMethods)
if(method->CheckOptions(options))
std::cout << method->GetName() << std::endl;
return 0;
}
if(!options.count("emulator"))
{
std::cerr << "ERROR: emulator/environment not specified.\n";
usage();
return 1;
}
LaunchMethod *method = NULL;
for(LaunchMethod *lm : launchMethods)
{
if(lm->GetName() == options["emulator"].as<string>())
{
method = lm;
break;
}
}
if(!method)
{
std::cerr << "ERROR: unknown emulator/environment: " << options["emulator"].as<string>() << "\n";
return 1;
}
if(!method->CheckOptions(options))
{
std::cerr << "Need more configuration.\n";
usage();
return 1;
}
std::unique_ptr<Launcher> launcher = method->MakeLauncher(options);
int timeout = options.count("timeout") ? options["timeout"].as<int>() : 0;
bool result = launcher->Go(timeout);
launcher->DumpOutput();
return result ? 0 : 1;
}

View File

@ -0,0 +1,73 @@
#
# 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
# All minivmac related paths are specified relative to minivmac-dir:
# minivmac-dir = /path/to/directory/with/vMac.ROM/
# First, we need Mini vMac itself:
# minivmac-path = ./Mini vMac
# On Macs, specify the path of the application bundle, not the executable inside it:
# minivmac-path = ./Mini vMac.app
# A ROM file:
# minivmac-rom = ./vMac.ROM
# 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.

View File

@ -0,0 +1,65 @@
#include "LaunchMethod.h"
#include <boost/filesystem.hpp>
#include <unistd.h>
namespace fs = boost::filesystem;
LaunchMethod::LaunchMethod()
{
}
LaunchMethod::~LaunchMethod()
{
}
void LaunchMethod::GetOptions(boost::program_options::options_description &desc)
{
}
bool LaunchMethod::CheckPlatform()
{
return true;
}
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;
}

78
LaunchAPPL/LaunchMethod.h Normal file
View File

@ -0,0 +1,78 @@
#ifndef LAUNCHMETHOD_H
#define LAUNCHMETHOD_H
#include <string>
#include <boost/program_options/options_description.hpp>
#include <boost/program_options/variables_map.hpp>
#include <memory>
class Launcher;
/**
* @brief The LaunchMethod class
*
* To add a new backend to LaunchAPPL, start by subclassing this
* and updating RegisterLaunchMethods() in LaunchAPPL.cc.
*/
class LaunchMethod
{
public:
typedef boost::program_options::options_description options_description;
typedef boost::program_options::variables_map variables_map;
LaunchMethod();
virtual ~LaunchMethod();
/**
* @brief GetName
* @return the name of the launch method, as it will be specified on the command line
*/
virtual std::string GetName() = 0;
/**
* @brief GetOptions
* @param desc
*
* Add any command line options for this LaunchMethod
* to the options_description structure.
*/
virtual void GetOptions(options_description& desc);
/**
* @brief CheckPlatform
*
* Check whether this is the right kind of machine to use this method.
* For things like Apple's Classic Environment, which is only available on some system versions.
* The default implementation returns true.
*/
virtual bool CheckPlatform();
/**
* @brief CheckOptions
* @param options
* @return are we ready to run?
*
* Check whether all necessary options have been specified.
* Don't output error messages here, this is also called when outputting usage information
*/
virtual bool CheckOptions(variables_map& options);
/**
* @brief MakeLauncher
* @param options
* @return a new instance of a subclass of Launcher which will do the actual work
*/
virtual std::unique_ptr<Launcher> MakeLauncher(variables_map& options) = 0;
protected:
/**
* @brief CheckExecutable
* @param program
* @return true if "program" exists in the $PATH and is executable.
*/
bool CheckExecutable(std::string program);
};
#endif // LAUNCHMETHOD_H

137
LaunchAPPL/Launcher.cc Normal file
View File

@ -0,0 +1,137 @@
#include "Launcher.h"
#include <boost/filesystem/fstream.hpp>
#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>
namespace fs = boost::filesystem;
using std::string;
using std::vector;
Launcher::Launcher(boost::program_options::variables_map &options)
: options(options)
{
app.assign(options["application"].as<std::string>());
if(!app.read())
throw std::runtime_error("Could not load application file.");
tempDir = fs::absolute(fs::unique_path());
fs::create_directories(tempDir);
appPath = tempDir / "Application";
outPath = tempDir / "out";
fs::ofstream out(outPath);
}
Launcher::Launcher(boost::program_options::variables_map &options, ResourceFile::Format f)
: Launcher(options)
{
app.assign(appPath.string(), f);
app.write();
}
void Launcher::DumpOutput()
{
fs::ifstream in(outPath);
std::cout << in.rdbuf();
}
Launcher::~Launcher()
{
fs::remove_all(tempDir);
}
int Launcher::ChildProcess(string program, vector<string> args, int timeout)
{
std::vector<const char*> argv;
argv.push_back(program.c_str());
for(string& s : args)
argv.push_back(s.c_str());
argv.push_back(NULL);
pid_t pid = fork();
if(pid < 0)
{
perror("unable to fork");
return 1;
}
else if(pid == 0)
{
pid_t worker_pid = timeout ? fork() : 0;
if(worker_pid < 0)
{
perror("unable to fork");
_exit(1);
}
if(worker_pid == 0)
{
execvp(argv[0], const_cast<char* const *> (argv.data()));
perror("exec failed");
std::cerr << "Tried to execute: " << program;
for(auto a : args)
std::cerr << " " << a;
std::cerr << std::endl;
_exit(1);
}
pid_t timeout_pid = fork();
if(timeout_pid < 0)
{
perror("unable to fork");
_exit(1);
}
if(timeout_pid == 0)
{
sleep(timeout);
_exit(0);
}
int wstatus;
pid_t exited_pid = wait(&wstatus);
if(exited_pid == worker_pid)
{
kill(timeout_pid, SIGKILL);
wait(NULL);
if(!WIFEXITED(wstatus))
{
return 1;
}
else
{
int exitcode = WEXITSTATUS(wstatus);
_exit(exitcode);
}
}
else
{
kill(worker_pid, SIGKILL);
wait(NULL);
_exit(1);
}
}
else
{
int wstatus;
int result = 0;
do
{
result = waitpid(pid, &wstatus, 0);
} while(result == -1 && errno == EINTR);
if(!WIFEXITED(wstatus))
{
return 1;
}
else
{
int exitcode = WEXITSTATUS(wstatus);
return exitcode;
}
}
}

76
LaunchAPPL/Launcher.h Normal file
View File

@ -0,0 +1,76 @@
#ifndef LAUNCHER_H
#define LAUNCHER_H
#include <boost/program_options/variables_map.hpp>
#include "ResourceFile.h"
#include <boost/filesystem.hpp>
/**
* @brief The Launcher class
*
* Subclasses are instantiated by the corresponding LaunchMethod subclasses.
*/
class Launcher
{
protected:
boost::program_options::variables_map& options;
ResourceFile app;
boost::filesystem::path tempDir, appPath, outPath;
bool keepTempFiles;
int ChildProcess(std::string program, std::vector<std::string> args, int timeout);
public:
/**
* @brief Launcher
* @param options
*
* Create a Launcher object and set up a temporary directory to play in.
* Reads the Applicatio specified on the command line into the `app` member variable.
* Also create an empty file named 'out' in the temporary directory, for test suite programs.
*/
Launcher(boost::program_options::variables_map& options);
/**
* @brief Launcher
* @param options
* @param f
*
* Create a Launcher object, set up a temporary directory
* and store the application to be executed at `appPath` in the temporary directory,
* using format `f`.
*/
Launcher(boost::program_options::variables_map& options, ResourceFile::Format f);
/**
* @brief ~Launcher
* Delete our temporary directory.
*/
virtual ~Launcher();
/**
* @brief Go
* @param timeout
* @return true for success
*
* Launch the application, return true on success and false on error or timeout.
*/
virtual bool Go(int timeout = 0) = 0;
/**
* @brief DumpOutput
*
* After the application has been run, copy the contents of the 'out' file to stdout.
*/
virtual void DumpOutput();
/**
* @brief KeepTempFiles
* Inhibit deletion of the temporary directory.
*/
void KeepTempFiles() { keepTempFiles = true; }
};
#endif // LAUNCHER_H

View File

@ -0,0 +1,53 @@
#include <string>
#include <iostream>
#include <fstream>
#include <boost/filesystem.hpp>
#include <stdlib.h>
#include "ResourceFile.h"
using std::string;
namespace fs = boost::filesystem;
void MakeExecutable(string fn)
{
ResourceFile rsrcFile(fn);
if(!rsrcFile.read())
{
std::cerr << "Cannot read application file: " << fn << std::endl;
exit(1);
}
if(!rsrcFile.hasPlainDataFork())
{
std::cerr << "--make-executable can not be used with this data format.\n";
exit(1);
}
string headerString = "#!" RETRO68_PREFIX "/bin/LaunchAPPL\n";
bool hadShebang = false;
if(rsrcFile.data.size())
{
if(headerString.substr(2) == "#!")
{
string::size_type eol = headerString.find('\n');
if(eol != string::npos && eol >= 13 && eol < 4096)
{
if(headerString.substr(eol-11,11) == "/LaunchAPPL")
hadShebang = true;
}
}
if(!hadShebang)
{
std::cerr << "Unfortunately, the application already has a data fork.\n";
std::cerr << "LaunchAPPL --make-executable does not currently work for PowerPC apps.\n";
// TODO: if it's a PEF container, move it back a little and update cfrg
exit(1);
}
}
std::fstream(fn, std::ios::in | std::ios::out | std::ios::binary) << headerString;
fs::permissions(fs::path(fn), fs::owner_exe | fs::group_exe | fs::others_exe | fs::add_perms);
}

324
LaunchAPPL/MiniVMac.cc Normal file
View File

@ -0,0 +1,324 @@
#include "MiniVMac.h"
#include "Launcher.h"
extern "C" {
#include "hfs.h"
}
#include <iostream>
#include <fstream>
#include <boost/filesystem/fstream.hpp>
#ifdef __APPLE__
#define ResType MacResType
#include <CoreFoundation/CoreFoundation.h>
#endif
namespace fs = boost::filesystem;
using std::string;
using std::vector;
namespace po = boost::program_options;
namespace fs = boost::filesystem;
class MiniVMacLauncher : public Launcher
{
fs::path imagePath;
fs::path systemImage;
fs::path vmacDir;
fs::path vmacPath;
hfsvol *sysvol;
hfsvol *vol;
void CopySystemFile(const std::string& fn, bool required);
public:
MiniVMacLauncher(po::variables_map& options);
virtual ~MiniVMacLauncher();
virtual bool Go(int timeout = 0);
virtual void DumpOutput();
};
/*
* Recursive directory copy from https://stackoverflow.com/a/39146566
*/
static void copyDirectoryRecursively(const fs::path& sourceDir, const fs::path& destinationDir)
{
if (!fs::exists(sourceDir) || !fs::is_directory(sourceDir))
{
throw std::runtime_error("Source directory " + sourceDir.string() + " does not exist or is not a directory");
}
if (fs::exists(destinationDir))
{
throw std::runtime_error("Destination directory " + destinationDir.string() + " already exists");
}
if (!fs::create_directory(destinationDir))
{
throw std::runtime_error("Cannot create destination directory " + destinationDir.string());
}
for (const auto& dirEnt : fs::recursive_directory_iterator{sourceDir})
{
const auto& path = dirEnt.path();
auto relativePathStr = path.string().substr(sourceDir.string().size());
fs::copy(path, destinationDir / relativePathStr);
}
}
MiniVMacLauncher::MiniVMacLauncher(po::variables_map &options)
: Launcher(options),
sysvol(NULL), vol(NULL)
{
imagePath = tempDir / "disk1.dsk";
vmacDir = fs::absolute( options["minivmac-dir"].as<std::string>() );
vmacPath = fs::absolute( options["minivmac-path"].as<std::string>(), vmacDir );
systemImage = fs::absolute(options["system-image"].as<std::string>(), vmacDir);
fs::path autoquitImage = fs::absolute(options["autoquit-image"].as<std::string>(), vmacDir);
std::vector<unsigned char> 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;
fs::ofstream(imagePath, std::ios::binary | std::ios::trunc).seekp(size-1).put(0);
hfs_format(imagePath.string().c_str(), 0, 0, "SysAndApp", 0, NULL);
{
bootblock1[0x1A] = 8;
memcpy(&bootblock1[0x1B],"AutoQuit", 8);
bootblock1[0x5A] = 3;
memcpy(&bootblock1[0x5B],"App", 3);
fs::fstream(imagePath, std::ios::in | std::ios::out | std::ios::binary)
.write((const char*) bootblock1.data(), 1024);
}
vol = hfs_mount(imagePath.string().c_str(), 0, HFS_MODE_RDWR);
assert(vol);
sysvol = hfs_mount(systemImage.string().c_str(),0, HFS_MODE_RDONLY);
assert(sysvol);
hfsvolent ent;
hfs_vstat(sysvol, &ent);
hfs_setcwd(sysvol, ent.blessed);
hfs_vstat(vol, &ent);
ent.blessed = hfs_getcwd(vol);
hfs_vsetattr(vol, &ent);
CopySystemFile(systemFileName, true);
CopySystemFile("MacsBug", false);
{
std::ostringstream rsrcOut;
app.resources.writeFork(rsrcOut);
std::string rsrc = rsrcOut.str();
std::string& data = app.data;
hfsfile *file = hfs_create(vol, "App","APPL","????");
hfs_setfork(file, 0);
hfs_write(file, data.data(), data.size());
hfs_setfork(file, 1);
hfs_write(file, rsrc.data(), rsrc.size());
hfs_close(file);
}
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", true);
{
hfsfile *file = hfs_create(vol, "out", "TEXT", "MPS ");
hfs_close(file);
}
hfs_umount(sysvol); sysvol = NULL;
hfs_umount(vol); vol = NULL;
fs::path romFile = fs::absolute( options["minivmac-rom"].as<std::string>(), vmacDir );
fs::create_symlink(
romFile,
tempDir / romFile.filename() );
if(romFile.filename() != "vMac.ROM")
{
// If the ROM file is not named vMac.ROM, this might be for two different
// reasons.
// 1. The user didn't bother to rename it to the correct "vMac.ROM"
// 2. The user is using a MacII version of Mini vMac and has named the
// ROM file MacII.ROM on purpose.
// To be on the safe side, provide both the user-specified name and
// the standard vMac.ROM.
fs::create_symlink(
romFile,
tempDir / romFile.filename() );
}
/*
Finally, we copy over the entire Mini vMac binary.
Mini vMac looks for ROM (vMac.ROM) and disk images (disk1.dsk)
in the directory next to its binary.
The Mac version also ignores command line arguments.
Having our own copy in our temp directory is just simpler.
It is five times smaller than System 6, so this really does not
matter.
*/
#ifdef __APPLE__
/*
A special case for the Mac:
We are probably dealing with an entire application bundle.
*/
if(vmacPath.extension().string() == ".app")
{
fs::path appPath = tempDir / "minivmac.app";
copyDirectoryRecursively( vmacPath, appPath );
// The following 30 lines of code should rather be written as:
// vmacPath = appPath / "Contents" / "MacOS" / Bundle(appPath).getExecutablePath();
// But this is CoreFoundation, so it's a tiny little bit more verbose:
CFStringRef appPathCF
= CFStringCreateWithCString(
kCFAllocatorDefault, appPath.string().c_str(), kCFStringEncodingUTF8);
CFURLRef bundleURL = CFURLCreateWithFileSystemPath(
kCFAllocatorDefault, appPathCF, kCFURLPOSIXPathStyle, true);
CFBundleRef bundle = CFBundleCreate( kCFAllocatorDefault, bundleURL );
CFURLRef executableURL = CFBundleCopyExecutableURL(bundle);
CFStringRef executablePath = CFURLCopyFileSystemPath(executableURL, kCFURLPOSIXPathStyle);
if(const char *ptr = CFStringGetCStringPtr(executablePath, kCFURLPOSIXPathStyle))
{
vmacPath = string(ptr);
}
else
{
vector<char> buffer(
CFStringGetMaximumSizeForEncoding(
CFStringGetLength(executablePath), kCFStringEncodingUTF8) + 1);
CFStringGetCString(executablePath, buffer.data(), buffer.size(), kCFStringEncodingUTF8);
vmacPath = string(buffer.data());
}
vmacPath = appPath / "Contents" / "MacOS" / vmacPath;
CFRelease(appPathCF);
CFRelease(bundleURL);
CFRelease(bundle);
CFRelease(executableURL);
CFRelease(executablePath);
}
else
#endif
{
fs::copy(vmacPath, tempDir / "minivmac");
vmacPath = tempDir / "minivmac";
}
}
MiniVMacLauncher::~MiniVMacLauncher()
{
if(sysvol)
hfs_umount(sysvol);
if(vol)
hfs_umount(vol);
}
void MiniVMacLauncher::CopySystemFile(const std::string &fn, bool required)
{
hfsdirent fileent;
if(hfs_stat(sysvol, fn.c_str(), &fileent) < 0)
{
if(required)
throw std::runtime_error(string("File ") + fn + " not found in disk image");
else
return;
}
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<uint8_t> buffer(std::max(fileent.u.file.dsize, fileent.u.file.rsize));
hfs_setfork(in, 0);
hfs_setfork(out, 0);
hfs_read(in, buffer.data(), fileent.u.file.dsize);
hfs_write(out, buffer.data(), fileent.u.file.dsize);
hfs_setfork(in, 1);
hfs_setfork(out, 1);
hfs_read(in, buffer.data(), fileent.u.file.rsize);
hfs_write(out, buffer.data(), fileent.u.file.rsize);
hfs_close(in);
hfs_close(out);
}
bool MiniVMacLauncher::Go(int timeout)
{
fs::current_path(tempDir);
return ChildProcess(vmacPath.string(), {}, timeout) == 0;
}
void MiniVMacLauncher::DumpOutput()
{
vol = hfs_mount(imagePath.string().c_str(), 0, HFS_MODE_RDONLY);
hfsdirent fileent;
int statres = hfs_stat(vol, "out", &fileent);
hfsfile *out = hfs_open(vol, "out");
if(!out)
return;
std::vector<char> buffer(fileent.u.file.dsize);
hfs_setfork(out, 0);
hfs_read(out, buffer.data(), fileent.u.file.dsize);
hfs_close(out);
std::cout << string(buffer.begin(), buffer.end());
hfs_umount(vol); vol = NULL;
}
void MiniVMac::GetOptions(options_description &desc)
{
desc.add_options()
("minivmac-dir", po::value<std::string>(),"directory containing vMac.ROM")
("minivmac-path", po::value<std::string>()->default_value("./minivmac"),"relative path to minivmac")
("minivmac-rom", po::value<std::string>()->default_value("./vMac.ROM"),"minivmac ROM file")
("system-image", po::value<std::string>(),"path to disk image with system")
("autoquit-image", po::value<std::string>(),"path to autoquit disk image, available from the minivmac web site")
;
}
bool MiniVMac::CheckOptions(variables_map &options)
{
return options.count("minivmac-path") != 0
&& options.count("minivmac-dir") != 0
&& options.count("minivmac-rom") != 0
&& options.count("system-image") != 0
&& options.count("autoquit-image") != 0;
}
std::unique_ptr<Launcher> MiniVMac::MakeLauncher(variables_map &options)
{
return std::unique_ptr<Launcher>(new MiniVMacLauncher(options));
}

16
LaunchAPPL/MiniVMac.h Normal file
View File

@ -0,0 +1,16 @@
#ifndef MINIVMAC_H
#define MINIVMAC_H
#include "LaunchMethod.h"
class MiniVMac : public LaunchMethod
{
public:
virtual std::string GetName() { return "minivmac"; }
virtual void GetOptions(options_description& desc);
virtual bool CheckOptions(variables_map& options);
virtual std::unique_ptr<Launcher> MakeLauncher(variables_map& options);
};
#endif // MINIVMAC_H

View File

@ -77,12 +77,13 @@ of the Retro68 directory:
../Retro68/build-toolchain.bash
The toolchain will be installed in the "toolchain" directory inside
the build directory.
the build directory. All the commands are in `toolchain/bin`, so you might want
to add that to your `PATH`.
If you're building this on a PowerMac running Mac OS X 10.4, tell the build script
to use the gcc you've installed via tigerbrew:
../Retro68/build-toolchain.bash --host-cxx-compiler=g++-5
../Retro68/build-toolchain.bash --host-cxx-compiler=g++-5 --host-c-compiler=gcc-5
### Build options and recompiling
@ -100,7 +101,6 @@ The `build-host`, `build-target`, `build-target-ppc` and `build-target-carbon`
directories are CMake build directories generated from the top-level `CMakeLists.txt`,
so you can also `cd` to one of these and run `make` separately if you've made changes.
Sample programs
---------------
@ -113,7 +113,8 @@ Sample programs are built in several formats:
Look under `Retro68-build/build-target/` (68K),
`Retro68-build/build-target-ppc/` (PowerPC Classic) and
`Retro68-build/build-target-carbon/` (PowerPC Carbon) for the compiled examples.
`Retro68-build/build-target-carbon/` (PowerPC Carbon) for the compiled examples,
especially under the `Samples` subdirectory.
Components
----------
@ -126,7 +127,7 @@ Third Party Components:
- binutils 2.28
- gcc 6.3.0
- newlib 2.10.1 (inside the gcc directory)
- elf2flt (from the ucLinux project's CVS)
- libelf from elfutils-0.170
- hfsutils 3.2.6
Retro68-Specific Components:
@ -134,6 +135,7 @@ Retro68-Specific Components:
- Rez
- PEFTools (MakePEF and MakeImport)
- MakeAPPL
- LaunchAPPL
- libretro
- TestApps - a few tiny test programs
- Sample Programs: Raytracer, HelloWorld, Launcher, Dialog
@ -145,6 +147,7 @@ Two new target platforms:
- `powerpc-apple-macos`, based on the `powerpc-ibm-aix` target
The powerpc target has a few hacks to make weak symbols work as expected.
The elf target has a hack to protect MacsBug symbols from -gc-sections.
### gcc
@ -169,10 +172,12 @@ PowerPC specific:
Standard C library. Currently unmodified. The missing platform-dependent
bits haven't been added, instead they are found in 'libretro'.
### elf2flt
### libelf
Converts from ELF to a much simpler binary format.
Minor patch: provide symbols around .init and .fini sections
A library for convenient access to ELF files, taken from the elfutils-0.170
package. Or rather, brutally ripped out of it, hacked to compile on non-linux
platforms (<endian.h> is not a standard header file), and made to build with
cmake instead of autotools. Much simpler now.
### hfsutils:
@ -188,6 +193,17 @@ A reimplementation of Apple's Rez resource compiler. Reads `.r` files
containing textual resource descriptions and compiles them to binary
resource files.
### Elf2Mac
A wrapper around the linker for 68K programs; it supplies a linker script,
invokes the linker, and converts the resulting ELF binary to a Mac APPL with
one or more segments, or to a flat file which can be converted to a code resource
using Rez.
### LaunchAPPL
A tool for lauching compiled Mac applications via various emulators.
### ConvertObj
Reads a MPW 68K Object file (`*.o`) and converts it to input for the
@ -228,6 +244,10 @@ for some standard library functions.
Contains a library that implements basic text console functionality.
### AutomatedTests
An automated test suite that can be run using `ctest` and `LaunchAPPL`.
### Sample Program: Hello World
The binary is in Retro68-build/build-target/Samples/HelloWorld/.
@ -266,3 +286,51 @@ The original parts of Retro68 are licensed under GPL3+, as are
most other parts. Some parts are licensed GPL2+ or with more
liberal licenses. Check the copyright notices in the individual
files.
LaunchAPPL and the Test Suite
-----------------------------
`LaunchAPPL` is a tool included with Retro68 intended to make launching the
compiled Mac applications easier. It's use is optional, so you may skip reading
this section.
Currently, LaunchAPPL supports the following methods for launching Mac applications:
* classic - launch in the Classic environment on PowerPC Macs up to Tiger (10.4)
* carbon - launch as a Carbon app on PowerPC Macs and via Rosetta on Intel Macs up to Snow Leopard (10.6)
* minivmac - launch using the Mini vMac emulator
* executor - launch using Executor
If you're running on a Mac that's old enough to use the `classic` or `carbon` backends,
they will work out of the box, just launch an application as follows
(assuming you've added `Retro68-build/toolchain/bin` to your `PATH`):
LaunchAPPL -e classic Retro68-build/build-target/Samples/Raytracer/Raytracer2.bin
LaunchAPPL -e carbon Retro68-build/build-target-carbon/Samples/Raytracer/Raytracer2.bin
To specify either environment as a default, or to configure one of the other emulators,
copy the file `Retro68/LaunchAPPL/LaunchAPPL.cfg.example` to `~/.LaunchAPPL.cfg`
and edit to taste (documentation is provided in comments).
**CONTRIBUTION OPPORTUNITY** - This tool can easily be extended with further backends,
so make it work with your favourtite emulator. Just add new subclasses for the
`LaunchMethod` and `Launcher` classes, they're documented.
### The Test Suite
The directory `AutomatedTests` contains an autonated test suite that runs via
`LaunchAPPL`. It's currently only relevant if you want to hack on the low-level
parts of Retro68.
The test suite will be configured automatically on sufficiently old Macs.
Everywhere else, first configure `LaunchAPPL` (see above) and then:
cs Retro68-build/build-target
cmake . -DRETRO68_LAUNCH_METHOD=minivmac # or executor, ...
make
To run the tests, invoke `ctest` in the `build-target` directory.
ctest

View File

@ -39,6 +39,7 @@ int main(int argc, char *argv[])
("all,a", "print all info")
("format,f", "print format")
("count,n", "print number of resources")
("size,s", "show data fork size")
("filename,l", "echo input file name")
("set-format,F", po::value<string>(), "resource fork format)")
;
@ -82,11 +83,12 @@ int main(int argc, char *argv[])
bool showCreator = options.count("creator") != 0;
bool showFormat = options.count("format") != 0;
bool showCount = options.count("count") != 0;
bool showSize = options.count("size") != 0;
ResourceFile::Format format = ResourceFile::Format::autodetect;
if(options.count("all"))
showType = showCreator = showFormat = showCount = true;
showType = showCreator = showFormat = showCount = showSize = true;
if(options.count("set-format"))
{
@ -121,6 +123,8 @@ int main(int argc, char *argv[])
out << " " << reverseFormats[rsrcFile.format];
if(showCount)
out << " " << rsrcFile.resources.resources.size();
if(showSize)
out << " " << rsrcFile.data.size();
string str = out.str();
if(str.size())

View File

@ -70,7 +70,9 @@ static void writeMacBinary(std::ostream& out, std::string filename,
out.seekp(128);
out << data;
std::streampos dataend = out.tellp();
std::streampos rsrcstart = ((int)dataend + 0x7F) & ~0x7F;
while((int)out.tellp() % 128)
byte(out,0);
std::streampos rsrcstart = out.tellp(); //((int)dataend + 0x7F) & ~0x7F;
rsrc.writeFork(out);
std::streampos rsrcend = out.tellp();
@ -217,10 +219,13 @@ bool ResourceFile::read()
case Format::basilisk:
{
fs::ifstream dataIn(path);
if(!dataIn)
return false;
data = std::string(std::istreambuf_iterator<char>(dataIn),
std::istreambuf_iterator<char>());
fs::ifstream rsrcIn(path.parent_path() / ".rsrc" / path.filename());
if(rsrcIn)
resources = Resources(rsrcIn);
fs::ifstream finfIn(path.parent_path() / ".finf" / path.filename());
if(finfIn)
@ -234,9 +239,12 @@ bool ResourceFile::read()
case Format::real:
{
fs::ifstream dataIn(path);
if(!dataIn)
return false;
data = std::string(std::istreambuf_iterator<char>(dataIn),
std::istreambuf_iterator<char>());
fs::ifstream rsrcIn(path / "..namedfork" / "rsrc");
if(rsrcIn)
resources = Resources(rsrcIn);
char finf[32];
@ -265,13 +273,16 @@ bool ResourceFile::read()
in.seekg(26 + i * 12);
int what = longword(in);
int off = longword(in);
//int len = longword(in);
int len = longword(in);
in.seekg(off);
switch(what)
{
case 1:
// ###
// FIXME: read data fork
{
std::vector<char> buf(len);
in.read(buf.data(), len);
data = std::string(buf.begin(), buf.end());
}
break;
case 2:
resources = Resources(in);
@ -342,7 +353,11 @@ bool ResourceFile::read()
unsigned short crc = CalculateCRC(0,header,124);
if(word(in) != crc)
return false;
// FIXME: read data fork
in.seekg(128);
std::vector<char> buf(datasize);
in.read(buf.data(), datasize);
data = std::string(buf.begin(), buf.end());
datasize = ((int)datasize + 0x7F) & ~0x7F;
in.seekg(128 + datasize);
resources = Resources(in);
}
@ -490,6 +505,8 @@ bool ResourceFile::write()
hfs_format(pathstring.c_str(), 0, 0, path.stem().string().substr(0,27).c_str(), 0, NULL);
hfsvol *vol = hfs_mount(pathstring.c_str(), 0, HFS_MODE_RDWR);
if(!vol)
return false;
//hfs_setvol(vol, )
hfsfile *file = hfs_create(vol, (path.stem().string().substr(0,31)).c_str(),
((std::string)type).c_str(), ((std::string)creator).c_str());
@ -510,3 +527,24 @@ bool ResourceFile::write()
return true;
}
bool ResourceFile::hasPlainDataFork(ResourceFile::Format f)
{
switch(f)
{
#ifdef __APPLE__
case Format::real:
#endif
case Format::basilisk:
case Format::underscore_appledouble:
case Format::percent_appledouble:
return true;
default:
return false;
}
}
bool ResourceFile::hasPlainDataFork()
{
return hasPlainDataFork(format);
}

View File

@ -31,6 +31,9 @@ public:
bool read();
bool write();
static bool hasPlainDataFork(Format f);
bool hasPlainDataFork();
std::string pathstring;
Format format;
ResType type;

View File

@ -79,7 +79,9 @@ int main(int argc, const char *argv[])
po::notify(options);
if(options.count("help") || (!options.count("input") && !options.count("copy")))
if(options.count("help")
|| (!options.count("input") && !options.count("copy")
&& !options.count("output")))
{
usage();
return 0;

View File

@ -42,6 +42,8 @@ function usage()
echo " --no-ppc disable classic PowerPC CFM support"
echo " --no-carbon disable Carbon CFM support"
echo " --clean-after-build remove intermediate build files right after building"
echo " --host-cxx-compiler specify C++ compiler (needed on Mac OS X 10.4)"
echo " --host-c-compiler specify C compiler (needed on Mac OS X 10.4)"
echo " --help show this help message"
}
@ -64,7 +66,11 @@ for ARG in $*; do
CLEAN_AFTER_BUILD=true
;;
--host-cxx-compiler=*)
HOST_CMAKE_FLAGS[${#HOST_CMAKE_FLAGS}]="-DCMAKE_CXX_COMPILER=${ARG#*=}"
HOST_CMAKE_FLAGS[${#HOST_CMAKE_FLAGS[@]}]="-DCMAKE_CXX_COMPILER=${ARG#*=}"
;;
--host-c-compiler=*)
HOST_CMAKE_FLAGS[${#HOST_CMAKE_FLAGS[@]}]="-DCMAKE_C_COMPILER=${ARG#*=}"
HOST_C_COMPILER="${ARG#*=}"
;;
--help)
usage