mirror of
https://github.com/autc04/Retro68.git
synced 2024-11-28 05:51:04 +00:00
commit
207b094372
62
AutomatedTests/CMakeLists.txt
Normal file
62
AutomatedTests/CMakeLists.txt
Normal 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
5
AutomatedTests/Empty.c
Normal 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
6
AutomatedTests/File.c
Normal file
@ -0,0 +1,6 @@
|
||||
#include "Test.h"
|
||||
|
||||
int main()
|
||||
{
|
||||
TEST_LOG_OK();
|
||||
}
|
22
AutomatedTests/Init.cc
Normal file
22
AutomatedTests/Init.cc
Normal 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
13
AutomatedTests/Log.c
Normal 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;
|
||||
}
|
6
AutomatedTests/ReallyEmpty.c
Normal file
6
AutomatedTests/ReallyEmpty.c
Normal 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
6
AutomatedTests/Test.c
Normal 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
97
AutomatedTests/Test.h
Normal 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
11
AutomatedTests/Timeout.c
Normal 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;
|
||||
}
|
42
AutomatedTests/ZeroInitialized.c
Normal file
42
AutomatedTests/ZeroInitialized.c
Normal 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;
|
||||
}
|
@ -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
15
Dockerfile
Normal 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
35
LaunchAPPL/CMakeLists.txt
Normal 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
46
LaunchAPPL/Carbon.cc
Normal 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
15
LaunchAPPL/Carbon.h
Normal 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
82
LaunchAPPL/Classic.cc
Normal 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(¶ms, 0, sizeof(params));
|
||||
params.flags = kLSLaunchStartClassic
|
||||
| kLSLaunchInClassic
|
||||
| kLSLaunchDontAddToRecents
|
||||
| kLSLaunchNewInstance;
|
||||
params.application = &ref;
|
||||
|
||||
ProcessSerialNumber psn;
|
||||
LSOpenApplication(¶ms, &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
15
LaunchAPPL/Classic.h
Normal 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
62
LaunchAPPL/Executor.cc
Normal 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
16
LaunchAPPL/Executor.h
Normal 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
251
LaunchAPPL/LaunchAPPL.cc
Normal 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;
|
||||
}
|
73
LaunchAPPL/LaunchAPPL.cfg.example
Normal file
73
LaunchAPPL/LaunchAPPL.cfg.example
Normal 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.
|
||||
|
65
LaunchAPPL/LaunchMethod.cc
Normal file
65
LaunchAPPL/LaunchMethod.cc
Normal 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
78
LaunchAPPL/LaunchMethod.h
Normal 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
137
LaunchAPPL/Launcher.cc
Normal 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
76
LaunchAPPL/Launcher.h
Normal 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
|
53
LaunchAPPL/MakeExecutable.cc
Normal file
53
LaunchAPPL/MakeExecutable.cc
Normal 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
324
LaunchAPPL/MiniVMac.cc
Normal 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
16
LaunchAPPL/MiniVMac.h
Normal 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
|
84
README.md
84
README.md
@ -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
|
||||
|
@ -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())
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -31,6 +31,9 @@ public:
|
||||
bool read();
|
||||
bool write();
|
||||
|
||||
static bool hasPlainDataFork(Format f);
|
||||
bool hasPlainDataFork();
|
||||
|
||||
std::string pathstring;
|
||||
Format format;
|
||||
ResType type;
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user