LaunchAPPL: Fix two macOS Mini vMac launching bugs

On all macOS versions, Mini vMac would launch behind other applications
because it was being launched by running the executable within the
application bundle directly. The solution is to let the OS launch the
app normally by using the open utility. This eliminates the 30 lines of
code that laboriously determined the executable name.

On macOS 10.12 and later, Mini vMac could not find the ROM or disk
files. These files were being placed in the same directory as the Mini
vMac application, but a new macOS security measure called Gatekeeper
Path Randomization or App Translocation prevents that from working
anymore. See https://www.gryphel.com/c/minivmac/osx_note.html. The
solution is to place the ROM and disk files within the mnvm_dat
directory in the Contents directory in the application bundle.

The code that copies Mini vMac into the temporary directory had to be
moved earlier so that when the ROM file is symlinked and the disk image
is created the mnvm_dat directory into which they go will be there.

Since more than one method needed to know whether Mini vMac was an app
bundle, a Boolean instance var was added on macOS to indicate that.
This commit is contained in:
Ryan Schmidt 2022-07-31 10:44:15 -05:00
parent 75631f17ae
commit 4a7b698ab4

View File

@ -70,6 +70,9 @@ class MiniVMacLauncher : public Launcher
fs::path systemImage;
fs::path vmacDir;
fs::path vmacPath;
#ifdef __APPLE__
bool vmacIsAppBundle;
#endif
hfsvol *sysvol;
hfsvol *vol;
@ -164,10 +167,23 @@ 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 );
fs::path dataDir;
#ifdef __APPLE__
vmacIsAppBundle = vmacPath.extension().string() == ".app";
if(vmacIsAppBundle)
{
dataDir = tempDir / "minivmac.app" / "Contents" / "mnvm_dat";
}
else
#endif
{
dataDir = tempDir;
}
imagePath = dataDir / "disk1.dsk";
systemImage = fs::absolute(options["system-image"].as<std::string>(), vmacDir);
systemImage = ConvertImage(systemImage);
@ -196,9 +212,36 @@ MiniVMacLauncher::MiniVMacLauncher(po::variables_map &options)
}
fs::path autoquitImage = fs::absolute(options[optionsKey].as<std::string>(), vmacDir);
autoquitImage = ConvertImage(autoquitImage);
/*
Copy over the entire Mini vMac program.
Mini vMac looks for ROM (vMac.ROM) and disk images (disk1.dsk)
in the directory next to its binary or in the case of the Mac
version in the mnvm_dat directory in the Contents directory in
the app bundle.
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.
*/
fs::path vmacCopy;
#ifdef __APPLE__
if(vmacIsAppBundle)
{
vmacCopy = tempDir / "minivmac.app";
copyDirectoryRecursively(vmacPath, vmacCopy);
vmacPath = vmacCopy;
boost::filesystem::create_directories(dataDir);
}
else
#endif
{
vmacCopy = tempDir / "minivmac";
fs::copy(vmacPath, vmacCopy);
vmacPath = vmacCopy;
}
int size = 5000*1024;
fs::ofstream(imagePath, std::ios::binary | std::ios::trunc).seekp(size-1).put(0);
@ -275,85 +318,22 @@ MiniVMacLauncher::MiniVMacLauncher(po::variables_map &options)
fs::create_symlink(
romFile,
tempDir / romFile.filename() );
dataDir / 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.
// 2. The user is using a version of Mini vMac that is not emulating
// a Macintosh Plus and has named the ROM file accordingly.
// To be on the safe side, provide both the user-specified name and
// the standard vMac.ROM.
fs::create_symlink(
romFile,
tempDir / "vMac.ROM" );
}
/*
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";
dataDir / "vMac.ROM" );
}
}
@ -445,6 +425,10 @@ uint16_t MiniVMacLauncher::GetSystemVersion(const std::string& systemFileName)
bool MiniVMacLauncher::Go(int timeout)
{
fs::current_path(tempDir);
#ifdef __APPLE__
if(vmacIsAppBundle)
return ChildProcess("open", {"-nWa", vmacPath.string()}, timeout) == 0;
#endif
return ChildProcess(vmacPath.string(), {}, timeout) == 0;
}