mirror of
https://github.com/autc04/Retro68.git
synced 2025-02-20 02:29:11 +00:00
LaunchAPPL: more cleanup, config file support
This commit is contained in:
parent
c3f16cd873
commit
687fd2a779
@ -16,6 +16,7 @@ if(APPLE)
|
|||||||
Carbon.h Carbon.cc)
|
Carbon.h Carbon.cc)
|
||||||
endif()
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
add_definitions(-DRETRO68_PREFIX="${CMAKE_INSTALL_PREFIX}")
|
||||||
|
|
||||||
add_executable(LaunchAPPL
|
add_executable(LaunchAPPL
|
||||||
LaunchAPPL.cc
|
LaunchAPPL.cc
|
||||||
|
@ -26,7 +26,15 @@ ExecutorLauncher::~ExecutorLauncher()
|
|||||||
|
|
||||||
bool ExecutorLauncher::Go(int timeout)
|
bool ExecutorLauncher::Go(int timeout)
|
||||||
{
|
{
|
||||||
return ChildProcess(options["executor-path"].as<std::string>(), { appPath.string() }, timeout) == 0;
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -34,12 +42,18 @@ void Executor::GetOptions(options_description &desc)
|
|||||||
{
|
{
|
||||||
desc.add_options()
|
desc.add_options()
|
||||||
("executor-path", po::value<std::string>()->default_value("executor"),"path to executor")
|
("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)
|
bool Executor::CheckOptions(variables_map &options)
|
||||||
{
|
{
|
||||||
return options.count("executor-path") != 0;
|
if(options.count("executor-path") == 0)
|
||||||
|
return false;
|
||||||
|
return CheckExecutable(options["executor-path"].as<std::string>());
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<Launcher> Executor::MakeLauncher(variables_map &options)
|
std::unique_ptr<Launcher> Executor::MakeLauncher(variables_map &options)
|
||||||
|
@ -24,38 +24,95 @@ using std::string;
|
|||||||
using std::vector;
|
using std::vector;
|
||||||
|
|
||||||
static po::options_description desc;
|
static po::options_description desc;
|
||||||
po::variables_map options;
|
static po::variables_map options;
|
||||||
|
static vector<string> configFiles;
|
||||||
|
static vector<LaunchMethod*> launchMethods;
|
||||||
|
|
||||||
|
static void RegisterLaunchMethods()
|
||||||
|
{
|
||||||
|
launchMethods = {
|
||||||
|
#if defined(__APPLE__) && defined(__powerpc)
|
||||||
|
new Classic(),
|
||||||
|
#endif
|
||||||
|
#ifdef HAS_LAUNCHCFMAPP
|
||||||
|
new Carbon(),
|
||||||
|
#endif
|
||||||
|
new Executor(), new MiniVMac()
|
||||||
|
// #### Add new `LaunchMethod`s here.
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
static void usage()
|
static void usage()
|
||||||
{
|
{
|
||||||
std::cerr << "Usage: " << "LaunchAPPL [options] appl-file\n";
|
std::cerr << "Usage: " << "LaunchAPPL [options] appl-file\n";
|
||||||
std::cerr << desc << std::endl;
|
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";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
int main(int argc, char *argv[])
|
int main(int argc, char *argv[])
|
||||||
{
|
{
|
||||||
std::vector<LaunchMethod*> methods = {
|
RegisterLaunchMethods();
|
||||||
#if defined(__APPLE__) && defined(__powerpc)
|
configFiles = { string(getenv("HOME")) + "/.LaunchAPPL.cfg", RETRO68_PREFIX "/LaunchAPPL.cfg"};
|
||||||
new Classic(),
|
|
||||||
#endif
|
|
||||||
#ifdef HAS_LAUNCHCFMAPP
|
|
||||||
new Carbon(),
|
|
||||||
#endif
|
|
||||||
new Executor(), new MiniVMac()
|
|
||||||
};
|
|
||||||
desc.add_options()
|
desc.add_options()
|
||||||
("help,h", "show this help message")
|
("help,h", "show this help message")
|
||||||
|
;
|
||||||
|
po::options_description configdesc;
|
||||||
|
configdesc.add_options()
|
||||||
("emulator,e", po::value<std::string>(), "what emulator/environment to use")
|
("emulator,e", po::value<std::string>(), "what emulator/environment to use")
|
||||||
;
|
;
|
||||||
for(LaunchMethod *lm : methods)
|
for(LaunchMethod *lm : launchMethods)
|
||||||
lm->GetOptions(desc);
|
lm->GetOptions(configdesc);
|
||||||
|
desc.add(configdesc);
|
||||||
|
|
||||||
desc.add_options()
|
desc.add_options()
|
||||||
("timeout,t", po::value<int>(),"abort after timeout")
|
("timeout,t", po::value<int>(),"abort after timeout")
|
||||||
("timeout-ok","timeout counts as success")
|
|
||||||
("logfile", po::value<std::string>(), "read log file")
|
|
||||||
;
|
;
|
||||||
po::options_description hidden, alldesc;
|
po::options_description hidden, alldesc;
|
||||||
hidden.add_options()
|
hidden.add_options()
|
||||||
@ -79,6 +136,26 @@ int main(int argc, char *argv[])
|
|||||||
return 1;
|
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);
|
po::notify(options);
|
||||||
|
|
||||||
if(options.count("help") || !options.count("application") || !options.count("emulator"))
|
if(options.count("help") || !options.count("application") || !options.count("emulator"))
|
||||||
@ -88,7 +165,7 @@ int main(int argc, char *argv[])
|
|||||||
}
|
}
|
||||||
|
|
||||||
LaunchMethod *method = NULL;
|
LaunchMethod *method = NULL;
|
||||||
for(LaunchMethod *lm : methods)
|
for(LaunchMethod *lm : launchMethods)
|
||||||
{
|
{
|
||||||
if(lm->GetName() == options["emulator"].as<string>())
|
if(lm->GetName() == options["emulator"].as<string>())
|
||||||
{
|
{
|
||||||
@ -98,13 +175,14 @@ int main(int argc, char *argv[])
|
|||||||
}
|
}
|
||||||
if(!method)
|
if(!method)
|
||||||
{
|
{
|
||||||
std::cerr << "ERROR: unknown emulator/environment.\n";
|
std::cerr << "ERROR: unknown emulator/environment: " << options["emulator"].as<string>() << "\n";
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!method->CheckOptions(options))
|
if(!method->CheckOptions(options))
|
||||||
{
|
{
|
||||||
std::cerr << "Missing configuration.\n";
|
std::cerr << "Missing configuration.\n";
|
||||||
|
usage();
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
69
LaunchAPPL/LaunchAPPL.cfg.example
Normal file
69
LaunchAPPL/LaunchAPPL.cfg.example
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
#
|
||||||
|
# Example configuration file for LaunchAPPL
|
||||||
|
# Copy this file to $HOME/.LaunchAPPL.cfg and modify to your taste
|
||||||
|
#
|
||||||
|
|
||||||
|
# ########### Classic Environment
|
||||||
|
|
||||||
|
# If you are on a PowerPC Mac running Tiger (10.4),
|
||||||
|
# uncomment the following to use the Classic Environment:
|
||||||
|
|
||||||
|
# emulator = classic
|
||||||
|
|
||||||
|
|
||||||
|
# ########### Carbon on Mac OS X (native PowerPC or Rosetta)
|
||||||
|
|
||||||
|
# If you are on any Mac running Snow Leopard (10.6) or earlier
|
||||||
|
# and you are developing Carbon applications, use the following:
|
||||||
|
|
||||||
|
# emulator = carbon
|
||||||
|
|
||||||
|
|
||||||
|
# ########### Mini vMac (old 68K Macs)
|
||||||
|
|
||||||
|
# To use Mini vMac with LaunchAPPL, you need to supply the ROM file,
|
||||||
|
# a system disk image, and a download of autoquit from the minivmac web
|
||||||
|
# site, currently at
|
||||||
|
# http://www.gryphel.com/c/minivmac/extras/autoquit/index.html
|
||||||
|
# LaunchAPPL does not currently support MultiFinder or System 7.
|
||||||
|
|
||||||
|
# Fill in the information below and uncomment the lines:
|
||||||
|
|
||||||
|
# emulator = minivmac
|
||||||
|
|
||||||
|
# The directory containing vMac.ROM
|
||||||
|
# All other paths relevant to Mini vMac are relative to this directory.
|
||||||
|
# minivmac-dir = /path/to/directory/with/vMac.ROM/
|
||||||
|
|
||||||
|
# First, we need Mini vMac itself:
|
||||||
|
# minivmac-path = ./Mini vMac
|
||||||
|
|
||||||
|
# Next, a system disk image (System 6 or earlier)
|
||||||
|
# system-image = ./System.dsk
|
||||||
|
|
||||||
|
# And finally, autoquit:
|
||||||
|
# autoquit-image = ./autoquit-1.1.1.dsk
|
||||||
|
|
||||||
|
# ########### Executor (68K only)
|
||||||
|
|
||||||
|
# No ROM files needed - an opensource reimplementation of classic Mac OS.
|
||||||
|
|
||||||
|
# emulator = executor
|
||||||
|
|
||||||
|
# If Executor is in your PATH and the SystemFolder environment variable
|
||||||
|
# is already set up, nothing else is required.
|
||||||
|
|
||||||
|
# in case it's somewhere else:
|
||||||
|
# executor-path = /usr/local/bin/executor
|
||||||
|
|
||||||
|
# Path to the Executor system folder:
|
||||||
|
# executor-system-folder = /path/to/ExecutorVolume/System Folder
|
||||||
|
|
||||||
|
# Pass more options to Executor.
|
||||||
|
# Note that each "word" needs to be specified separately:
|
||||||
|
# executor-option = -size # emulated screen size
|
||||||
|
# executor-option = 1600x1200
|
||||||
|
|
||||||
|
# executor-option = -appearance # uncommenting these two lines
|
||||||
|
# executor-option = windows # is seriously not recommended.
|
||||||
|
|
@ -1,4 +1,8 @@
|
|||||||
#include "LaunchMethod.h"
|
#include "LaunchMethod.h"
|
||||||
|
#include <boost/filesystem.hpp>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
namespace fs = boost::filesystem;
|
||||||
|
|
||||||
LaunchMethod::LaunchMethod()
|
LaunchMethod::LaunchMethod()
|
||||||
{
|
{
|
||||||
@ -18,3 +22,39 @@ bool LaunchMethod::CheckOptions(boost::program_options::variables_map &options)
|
|||||||
{
|
{
|
||||||
return true;
|
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;
|
||||||
|
}
|
||||||
|
@ -23,6 +23,9 @@ public:
|
|||||||
virtual bool CheckOptions(variables_map& options);
|
virtual bool CheckOptions(variables_map& options);
|
||||||
|
|
||||||
virtual std::unique_ptr<Launcher> MakeLauncher(variables_map& options) = 0;
|
virtual std::unique_ptr<Launcher> MakeLauncher(variables_map& options) = 0;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
bool CheckExecutable(std::string program);
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // LAUNCHMETHOD_H
|
#endif // LAUNCHMETHOD_H
|
||||||
|
@ -20,11 +20,13 @@ class MiniVMacLauncher : public Launcher
|
|||||||
{
|
{
|
||||||
fs::path imagePath;
|
fs::path imagePath;
|
||||||
fs::path systemImage;
|
fs::path systemImage;
|
||||||
|
fs::path vmacDir;
|
||||||
|
fs::path vmacPath;
|
||||||
|
|
||||||
hfsvol *sysvol;
|
hfsvol *sysvol;
|
||||||
hfsvol *vol;
|
hfsvol *vol;
|
||||||
|
|
||||||
void CopySystemFile(const char* fn, const char *dstfn = NULL);
|
void CopySystemFile(const std::string& fn, bool required);
|
||||||
public:
|
public:
|
||||||
MiniVMacLauncher(po::variables_map& options);
|
MiniVMacLauncher(po::variables_map& options);
|
||||||
virtual ~MiniVMacLauncher();
|
virtual ~MiniVMacLauncher();
|
||||||
@ -38,9 +40,20 @@ MiniVMacLauncher::MiniVMacLauncher(po::variables_map &options)
|
|||||||
: Launcher(options, ResourceFile::Format::percent_appledouble),
|
: Launcher(options, ResourceFile::Format::percent_appledouble),
|
||||||
sysvol(NULL), vol(NULL)
|
sysvol(NULL), vol(NULL)
|
||||||
{
|
{
|
||||||
imagePath = fs::absolute(tempDir / "image.dsk");
|
imagePath = tempDir / "image.dsk";
|
||||||
systemImage = fs::absolute(options["system-image"].as<std::string>());
|
vmacDir = fs::absolute( options["minivmac-dir"].as<std::string>() );
|
||||||
fs::path autoquitImage = fs::absolute(options["autoquit-image"].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;
|
int size = 5000*1024;
|
||||||
@ -49,10 +62,6 @@ MiniVMacLauncher::MiniVMacLauncher(po::variables_map &options)
|
|||||||
hfs_format(imagePath.string().c_str(), 0, 0, "SysAndApp", 0, NULL);
|
hfs_format(imagePath.string().c_str(), 0, 0, "SysAndApp", 0, NULL);
|
||||||
|
|
||||||
{
|
{
|
||||||
std::vector<unsigned char> bootblock1(1024);
|
|
||||||
|
|
||||||
fs::ifstream(systemImage).read((char*) bootblock1.data(), 1024);
|
|
||||||
|
|
||||||
bootblock1[0x1A] = 8;
|
bootblock1[0x1A] = 8;
|
||||||
memcpy(&bootblock1[0x1B],"AutoQuit", 8);
|
memcpy(&bootblock1[0x1B],"AutoQuit", 8);
|
||||||
bootblock1[0x5A] = 3;
|
bootblock1[0x5A] = 3;
|
||||||
@ -79,9 +88,8 @@ MiniVMacLauncher::MiniVMacLauncher(po::variables_map &options)
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
CopySystemFile("System");
|
CopySystemFile(systemFileName, true);
|
||||||
CopySystemFile("Finder");
|
CopySystemFile("MacsBug", false);
|
||||||
CopySystemFile("MacsBug");
|
|
||||||
|
|
||||||
{
|
{
|
||||||
std::ostringstream rsrcOut;
|
std::ostringstream rsrcOut;
|
||||||
@ -99,8 +107,10 @@ MiniVMacLauncher::MiniVMacLauncher(po::variables_map &options)
|
|||||||
|
|
||||||
hfs_umount(sysvol);
|
hfs_umount(sysvol);
|
||||||
sysvol = hfs_mount(autoquitImage.string().c_str(),0, HFS_MODE_RDONLY);
|
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);
|
assert(sysvol);
|
||||||
CopySystemFile("AutoQuit");
|
CopySystemFile("AutoQuit", true);
|
||||||
|
|
||||||
{
|
{
|
||||||
hfsfile *file = hfs_create(vol, "out", "TEXT", "MPS ");
|
hfsfile *file = hfs_create(vol, "out", "TEXT", "MPS ");
|
||||||
@ -120,15 +130,15 @@ MiniVMacLauncher::~MiniVMacLauncher()
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void MiniVMacLauncher::CopySystemFile(const char *fn, const char *dstfn)
|
void MiniVMacLauncher::CopySystemFile(const std::string &fn, bool required)
|
||||||
{
|
{
|
||||||
if(!dstfn)
|
|
||||||
dstfn = fn;
|
|
||||||
hfsdirent fileent;
|
hfsdirent fileent;
|
||||||
if(hfs_stat(sysvol, fn, &fileent) < 0)
|
if(hfs_stat(sysvol, fn.c_str(), &fileent) < 0)
|
||||||
return;
|
{
|
||||||
hfsfile *in = hfs_open(sysvol, fn);
|
throw std::runtime_error(string("File ") + fn + " not found in disk image");
|
||||||
hfsfile *out = hfs_create(vol, dstfn, fileent.u.file.type,fileent.u.file.creator);
|
}
|
||||||
|
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));
|
std::vector<uint8_t> buffer(std::max(fileent.u.file.dsize, fileent.u.file.rsize));
|
||||||
hfs_setfork(in, 0);
|
hfs_setfork(in, 0);
|
||||||
@ -160,8 +170,6 @@ void MiniVMacLauncher::DumpOutput()
|
|||||||
vol = hfs_mount(imagePath.string().c_str(), 0, HFS_MODE_RDONLY);
|
vol = hfs_mount(imagePath.string().c_str(), 0, HFS_MODE_RDONLY);
|
||||||
hfsdirent fileent;
|
hfsdirent fileent;
|
||||||
int statres = hfs_stat(vol, "out", &fileent);
|
int statres = hfs_stat(vol, "out", &fileent);
|
||||||
std::cerr << "stat: " << statres << "\n";
|
|
||||||
std::cerr << "out: " << fileent.u.file.dsize << " bytes\n";
|
|
||||||
|
|
||||||
hfsfile *out = hfs_open(vol, "out");
|
hfsfile *out = hfs_open(vol, "out");
|
||||||
if(!out)
|
if(!out)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user