mirror of
https://github.com/autc04/Retro68.git
synced 2025-02-25 22:29:38 +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
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with Retro68. If not, see <http://www.gnu.org/licenses/>.
|
# 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)
|
project(Retro)
|
||||||
set(CMAKE_CXX_STANDARD 11)
|
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.*)
|
if(CMAKE_SYSTEM_NAME MATCHES Retro.*)
|
||||||
|
|
||||||
@ -48,6 +51,9 @@ add_subdirectory(Samples/Launcher)
|
|||||||
add_subdirectory(Samples/SystemExtension)
|
add_subdirectory(Samples/SystemExtension)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
enable_testing()
|
||||||
|
add_subdirectory(AutomatedTests)
|
||||||
|
|
||||||
else()
|
else()
|
||||||
|
|
||||||
set(RETRO68_ROOT ${CMAKE_INSTALL_PREFIX})
|
set(RETRO68_ROOT ${CMAKE_INSTALL_PREFIX})
|
||||||
@ -62,4 +68,5 @@ add_subdirectory(Rez)
|
|||||||
add_subdirectory(ConvertObj)
|
add_subdirectory(ConvertObj)
|
||||||
add_subdirectory(PEFTools)
|
add_subdirectory(PEFTools)
|
||||||
add_subdirectory(Elf2Mac)
|
add_subdirectory(Elf2Mac)
|
||||||
|
add_subdirectory(LaunchAPPL)
|
||||||
endif()
|
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
|
../Retro68/build-toolchain.bash
|
||||||
|
|
||||||
The toolchain will be installed in the "toolchain" directory inside
|
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
|
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:
|
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
|
### 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`,
|
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.
|
so you can also `cd` to one of these and run `make` separately if you've made changes.
|
||||||
|
|
||||||
|
|
||||||
Sample programs
|
Sample programs
|
||||||
---------------
|
---------------
|
||||||
|
|
||||||
@ -113,7 +113,8 @@ Sample programs are built in several formats:
|
|||||||
|
|
||||||
Look under `Retro68-build/build-target/` (68K),
|
Look under `Retro68-build/build-target/` (68K),
|
||||||
`Retro68-build/build-target-ppc/` (PowerPC Classic) and
|
`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
|
Components
|
||||||
----------
|
----------
|
||||||
@ -126,7 +127,7 @@ Third Party Components:
|
|||||||
- binutils 2.28
|
- binutils 2.28
|
||||||
- gcc 6.3.0
|
- gcc 6.3.0
|
||||||
- newlib 2.10.1 (inside the gcc directory)
|
- newlib 2.10.1 (inside the gcc directory)
|
||||||
- elf2flt (from the ucLinux project's CVS)
|
- libelf from elfutils-0.170
|
||||||
- hfsutils 3.2.6
|
- hfsutils 3.2.6
|
||||||
|
|
||||||
Retro68-Specific Components:
|
Retro68-Specific Components:
|
||||||
@ -134,6 +135,7 @@ Retro68-Specific Components:
|
|||||||
- Rez
|
- Rez
|
||||||
- PEFTools (MakePEF and MakeImport)
|
- PEFTools (MakePEF and MakeImport)
|
||||||
- MakeAPPL
|
- MakeAPPL
|
||||||
|
- LaunchAPPL
|
||||||
- libretro
|
- libretro
|
||||||
- TestApps - a few tiny test programs
|
- TestApps - a few tiny test programs
|
||||||
- Sample Programs: Raytracer, HelloWorld, Launcher, Dialog
|
- Sample Programs: Raytracer, HelloWorld, Launcher, Dialog
|
||||||
@ -145,6 +147,7 @@ Two new target platforms:
|
|||||||
- `powerpc-apple-macos`, based on the `powerpc-ibm-aix` target
|
- `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 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
|
### gcc
|
||||||
|
|
||||||
@ -169,10 +172,12 @@ PowerPC specific:
|
|||||||
Standard C library. Currently unmodified. The missing platform-dependent
|
Standard C library. Currently unmodified. The missing platform-dependent
|
||||||
bits haven't been added, instead they are found in 'libretro'.
|
bits haven't been added, instead they are found in 'libretro'.
|
||||||
|
|
||||||
### elf2flt
|
### libelf
|
||||||
|
|
||||||
Converts from ELF to a much simpler binary format.
|
A library for convenient access to ELF files, taken from the elfutils-0.170
|
||||||
Minor patch: provide symbols around .init and .fini sections
|
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:
|
### 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
|
containing textual resource descriptions and compiles them to binary
|
||||||
resource files.
|
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
|
### ConvertObj
|
||||||
|
|
||||||
Reads a MPW 68K Object file (`*.o`) and converts it to input for the
|
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.
|
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
|
### Sample Program: Hello World
|
||||||
|
|
||||||
The binary is in Retro68-build/build-target/Samples/HelloWorld/.
|
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
|
most other parts. Some parts are licensed GPL2+ or with more
|
||||||
liberal licenses. Check the copyright notices in the individual
|
liberal licenses. Check the copyright notices in the individual
|
||||||
files.
|
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
|
||||||
|
@ -38,8 +38,9 @@ int main(int argc, char *argv[])
|
|||||||
("creator,c", "print creator code")
|
("creator,c", "print creator code")
|
||||||
("all,a", "print all info")
|
("all,a", "print all info")
|
||||||
("format,f", "print format")
|
("format,f", "print format")
|
||||||
("count,n", "print number of resources")
|
("count,n", "print number of resources")
|
||||||
("filename,l", "echo input file name")
|
("size,s", "show data fork size")
|
||||||
|
("filename,l", "echo input file name")
|
||||||
("set-format,F", po::value<string>(), "resource fork format)")
|
("set-format,F", po::value<string>(), "resource fork format)")
|
||||||
;
|
;
|
||||||
po::options_description hidden, alldesc;
|
po::options_description hidden, alldesc;
|
||||||
@ -82,11 +83,12 @@ int main(int argc, char *argv[])
|
|||||||
bool showCreator = options.count("creator") != 0;
|
bool showCreator = options.count("creator") != 0;
|
||||||
bool showFormat = options.count("format") != 0;
|
bool showFormat = options.count("format") != 0;
|
||||||
bool showCount = options.count("count") != 0;
|
bool showCount = options.count("count") != 0;
|
||||||
|
bool showSize = options.count("size") != 0;
|
||||||
|
|
||||||
ResourceFile::Format format = ResourceFile::Format::autodetect;
|
ResourceFile::Format format = ResourceFile::Format::autodetect;
|
||||||
|
|
||||||
if(options.count("all"))
|
if(options.count("all"))
|
||||||
showType = showCreator = showFormat = showCount = true;
|
showType = showCreator = showFormat = showCount = showSize = true;
|
||||||
|
|
||||||
if(options.count("set-format"))
|
if(options.count("set-format"))
|
||||||
{
|
{
|
||||||
@ -121,6 +123,8 @@ int main(int argc, char *argv[])
|
|||||||
out << " " << reverseFormats[rsrcFile.format];
|
out << " " << reverseFormats[rsrcFile.format];
|
||||||
if(showCount)
|
if(showCount)
|
||||||
out << " " << rsrcFile.resources.resources.size();
|
out << " " << rsrcFile.resources.resources.size();
|
||||||
|
if(showSize)
|
||||||
|
out << " " << rsrcFile.data.size();
|
||||||
|
|
||||||
string str = out.str();
|
string str = out.str();
|
||||||
if(str.size())
|
if(str.size())
|
||||||
|
@ -70,7 +70,9 @@ static void writeMacBinary(std::ostream& out, std::string filename,
|
|||||||
out.seekp(128);
|
out.seekp(128);
|
||||||
out << data;
|
out << data;
|
||||||
std::streampos dataend = out.tellp();
|
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);
|
rsrc.writeFork(out);
|
||||||
|
|
||||||
std::streampos rsrcend = out.tellp();
|
std::streampos rsrcend = out.tellp();
|
||||||
@ -217,11 +219,14 @@ bool ResourceFile::read()
|
|||||||
case Format::basilisk:
|
case Format::basilisk:
|
||||||
{
|
{
|
||||||
fs::ifstream dataIn(path);
|
fs::ifstream dataIn(path);
|
||||||
|
if(!dataIn)
|
||||||
|
return false;
|
||||||
data = std::string(std::istreambuf_iterator<char>(dataIn),
|
data = std::string(std::istreambuf_iterator<char>(dataIn),
|
||||||
std::istreambuf_iterator<char>());
|
std::istreambuf_iterator<char>());
|
||||||
|
|
||||||
fs::ifstream rsrcIn(path.parent_path() / ".rsrc" / path.filename());
|
fs::ifstream rsrcIn(path.parent_path() / ".rsrc" / path.filename());
|
||||||
resources = Resources(rsrcIn);
|
if(rsrcIn)
|
||||||
|
resources = Resources(rsrcIn);
|
||||||
fs::ifstream finfIn(path.parent_path() / ".finf" / path.filename());
|
fs::ifstream finfIn(path.parent_path() / ".finf" / path.filename());
|
||||||
if(finfIn)
|
if(finfIn)
|
||||||
{
|
{
|
||||||
@ -234,10 +239,13 @@ bool ResourceFile::read()
|
|||||||
case Format::real:
|
case Format::real:
|
||||||
{
|
{
|
||||||
fs::ifstream dataIn(path);
|
fs::ifstream dataIn(path);
|
||||||
|
if(!dataIn)
|
||||||
|
return false;
|
||||||
data = std::string(std::istreambuf_iterator<char>(dataIn),
|
data = std::string(std::istreambuf_iterator<char>(dataIn),
|
||||||
std::istreambuf_iterator<char>());
|
std::istreambuf_iterator<char>());
|
||||||
fs::ifstream rsrcIn(path / "..namedfork" / "rsrc");
|
fs::ifstream rsrcIn(path / "..namedfork" / "rsrc");
|
||||||
resources = Resources(rsrcIn);
|
if(rsrcIn)
|
||||||
|
resources = Resources(rsrcIn);
|
||||||
|
|
||||||
char finf[32];
|
char finf[32];
|
||||||
int n = getxattr(path.c_str(), XATTR_FINDERINFO_NAME,
|
int n = getxattr(path.c_str(), XATTR_FINDERINFO_NAME,
|
||||||
@ -265,13 +273,16 @@ bool ResourceFile::read()
|
|||||||
in.seekg(26 + i * 12);
|
in.seekg(26 + i * 12);
|
||||||
int what = longword(in);
|
int what = longword(in);
|
||||||
int off = longword(in);
|
int off = longword(in);
|
||||||
//int len = longword(in);
|
int len = longword(in);
|
||||||
in.seekg(off);
|
in.seekg(off);
|
||||||
switch(what)
|
switch(what)
|
||||||
{
|
{
|
||||||
case 1:
|
case 1:
|
||||||
// ###
|
{
|
||||||
// FIXME: read data fork
|
std::vector<char> buf(len);
|
||||||
|
in.read(buf.data(), len);
|
||||||
|
data = std::string(buf.begin(), buf.end());
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case 2:
|
case 2:
|
||||||
resources = Resources(in);
|
resources = Resources(in);
|
||||||
@ -342,7 +353,11 @@ bool ResourceFile::read()
|
|||||||
unsigned short crc = CalculateCRC(0,header,124);
|
unsigned short crc = CalculateCRC(0,header,124);
|
||||||
if(word(in) != crc)
|
if(word(in) != crc)
|
||||||
return false;
|
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);
|
in.seekg(128 + datasize);
|
||||||
resources = Resources(in);
|
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);
|
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);
|
hfsvol *vol = hfs_mount(pathstring.c_str(), 0, HFS_MODE_RDWR);
|
||||||
|
if(!vol)
|
||||||
|
return false;
|
||||||
//hfs_setvol(vol, )
|
//hfs_setvol(vol, )
|
||||||
hfsfile *file = hfs_create(vol, (path.stem().string().substr(0,31)).c_str(),
|
hfsfile *file = hfs_create(vol, (path.stem().string().substr(0,31)).c_str(),
|
||||||
((std::string)type).c_str(), ((std::string)creator).c_str());
|
((std::string)type).c_str(), ((std::string)creator).c_str());
|
||||||
@ -510,3 +527,24 @@ bool ResourceFile::write()
|
|||||||
return true;
|
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 read();
|
||||||
bool write();
|
bool write();
|
||||||
|
|
||||||
|
static bool hasPlainDataFork(Format f);
|
||||||
|
bool hasPlainDataFork();
|
||||||
|
|
||||||
std::string pathstring;
|
std::string pathstring;
|
||||||
Format format;
|
Format format;
|
||||||
ResType type;
|
ResType type;
|
||||||
|
@ -79,7 +79,9 @@ int main(int argc, const char *argv[])
|
|||||||
|
|
||||||
po::notify(options);
|
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();
|
usage();
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -42,6 +42,8 @@ function usage()
|
|||||||
echo " --no-ppc disable classic PowerPC CFM support"
|
echo " --no-ppc disable classic PowerPC CFM support"
|
||||||
echo " --no-carbon disable Carbon CFM support"
|
echo " --no-carbon disable Carbon CFM support"
|
||||||
echo " --clean-after-build remove intermediate build files right after building"
|
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"
|
echo " --help show this help message"
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -64,7 +66,11 @@ for ARG in $*; do
|
|||||||
CLEAN_AFTER_BUILD=true
|
CLEAN_AFTER_BUILD=true
|
||||||
;;
|
;;
|
||||||
--host-cxx-compiler=*)
|
--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)
|
--help)
|
||||||
usage
|
usage
|
||||||
|
Loading…
x
Reference in New Issue
Block a user