mirror of
https://github.com/zydeco/minivmac4ios.git
synced 2024-09-27 12:59:00 +00:00
Add HFSDiskImage class for creating disk images and adding files to them
This commit is contained in:
parent
7060fc5b23
commit
aa64a915fa
@ -153,6 +153,7 @@
|
|||||||
28F6B4C21CF07F5C002D76D0 /* liblibres.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 28F6B4B61CF07F32002D76D0 /* liblibres.a */; };
|
28F6B4C21CF07F5C002D76D0 /* liblibres.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 28F6B4B61CF07F32002D76D0 /* liblibres.a */; };
|
||||||
28F6B4CA1CF1FA7A002D76D0 /* about.plist in Resources */ = {isa = PBXBuildFile; fileRef = 28F6B4C91CF1FA7A002D76D0 /* about.plist */; };
|
28F6B4CA1CF1FA7A002D76D0 /* about.plist in Resources */ = {isa = PBXBuildFile; fileRef = 28F6B4C91CF1FA7A002D76D0 /* about.plist */; };
|
||||||
28F6B4CF1CF77099002D76D0 /* compat.m in Sources */ = {isa = PBXBuildFile; fileRef = 28F6B4CE1CF77099002D76D0 /* compat.m */; };
|
28F6B4CF1CF77099002D76D0 /* compat.m in Sources */ = {isa = PBXBuildFile; fileRef = 28F6B4CE1CF77099002D76D0 /* compat.m */; };
|
||||||
|
CA06D3352BA25DC10019B7B7 /* HFSDiskImage.m in Sources */ = {isa = PBXBuildFile; fileRef = CA06D3332BA25DC00019B7B7 /* HFSDiskImage.m */; };
|
||||||
/* End PBXBuildFile section */
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
/* Begin PBXContainerItemProxy section */
|
/* Begin PBXContainerItemProxy section */
|
||||||
@ -371,6 +372,8 @@
|
|||||||
28F6B4CE1CF77099002D76D0 /* compat.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = compat.m; sourceTree = "<group>"; };
|
28F6B4CE1CF77099002D76D0 /* compat.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = compat.m; sourceTree = "<group>"; };
|
||||||
28F875921D29402B001E99EB /* PlugIn-Capabilities.plist.xml */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = "PlugIn-Capabilities.plist.xml"; sourceTree = "<group>"; };
|
28F875921D29402B001E99EB /* PlugIn-Capabilities.plist.xml */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = "PlugIn-Capabilities.plist.xml"; sourceTree = "<group>"; };
|
||||||
CA06D3322BA255830019B7B7 /* CodeSigning.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = CodeSigning.xcconfig; sourceTree = "<group>"; };
|
CA06D3322BA255830019B7B7 /* CodeSigning.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = CodeSigning.xcconfig; sourceTree = "<group>"; };
|
||||||
|
CA06D3332BA25DC00019B7B7 /* HFSDiskImage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HFSDiskImage.m; sourceTree = "<group>"; };
|
||||||
|
CA06D3342BA25DC00019B7B7 /* HFSDiskImage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HFSDiskImage.h; sourceTree = "<group>"; };
|
||||||
/* End PBXFileReference section */
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
/* Begin PBXFrameworksBuildPhase section */
|
/* Begin PBXFrameworksBuildPhase section */
|
||||||
@ -617,6 +620,8 @@
|
|||||||
28848B611CDE97D600B86C45 /* InsertDiskViewController.m */,
|
28848B611CDE97D600B86C45 /* InsertDiskViewController.m */,
|
||||||
28848B631CDE97E900B86C45 /* SettingsViewController.h */,
|
28848B631CDE97E900B86C45 /* SettingsViewController.h */,
|
||||||
28848B641CDE97E900B86C45 /* SettingsViewController.m */,
|
28848B641CDE97E900B86C45 /* SettingsViewController.m */,
|
||||||
|
CA06D3342BA25DC00019B7B7 /* HFSDiskImage.h */,
|
||||||
|
CA06D3332BA25DC00019B7B7 /* HFSDiskImage.m */,
|
||||||
28F676C91CD15E0B00FC6FA6 /* Main.storyboard */,
|
28F676C91CD15E0B00FC6FA6 /* Main.storyboard */,
|
||||||
28F676CC1CD15E0B00FC6FA6 /* Assets.xcassets */,
|
28F676CC1CD15E0B00FC6FA6 /* Assets.xcassets */,
|
||||||
28BA896E1CE7314500A98104 /* Keyboard */,
|
28BA896E1CE7314500A98104 /* Keyboard */,
|
||||||
@ -1301,6 +1306,7 @@
|
|||||||
28D5A3FD1CD6868F001A33F6 /* TouchScreen.m in Sources */,
|
28D5A3FD1CD6868F001A33F6 /* TouchScreen.m in Sources */,
|
||||||
28F676C51CD15E0B00FC6FA6 /* AppDelegate.m in Sources */,
|
28F676C51CD15E0B00FC6FA6 /* AppDelegate.m in Sources */,
|
||||||
28BA89801CE7315400A98104 /* KBKeyboardView.m in Sources */,
|
28BA89801CE7315400A98104 /* KBKeyboardView.m in Sources */,
|
||||||
|
CA06D3352BA25DC10019B7B7 /* HFSDiskImage.m in Sources */,
|
||||||
28F676C21CD15E0B00FC6FA6 /* main.m in Sources */,
|
28F676C21CD15E0B00FC6FA6 /* main.m in Sources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
42
Mini vMac/HFSDiskImage.h
Normal file
42
Mini vMac/HFSDiskImage.h
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
//
|
||||||
|
// HFSDiskImage.h
|
||||||
|
// Mini vMac
|
||||||
|
//
|
||||||
|
// Created by Lieven Dekeyser on 13/03/2024.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import <Foundation/Foundation.h>
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_BEGIN
|
||||||
|
|
||||||
|
@interface HFSDiskImage : NSObject
|
||||||
|
|
||||||
|
@property (nonatomic, readonly, copy) NSString * path;
|
||||||
|
@property (nonatomic, readonly, getter=isOpen) BOOL open;
|
||||||
|
@property (nonatomic, readonly, getter=isReadOnly) BOOL readOnly;
|
||||||
|
|
||||||
|
+ (nullable HFSDiskImage *)createDiskImageWithName:(NSString *)name size:(size_t)volumeSize atPath:(NSString *)path;
|
||||||
|
|
||||||
|
- (nullable instancetype)initWithPath:(NSString *)path;
|
||||||
|
|
||||||
|
- (instancetype)init __unavailable;
|
||||||
|
+ (instancetype)new __unavailable;
|
||||||
|
|
||||||
|
- (BOOL)openForReading;
|
||||||
|
- (BOOL)openForReadingAndWriting;
|
||||||
|
|
||||||
|
- (BOOL)close;
|
||||||
|
|
||||||
|
- (BOOL)addFile:(NSString *)sourceFile;
|
||||||
|
|
||||||
|
@end // HFSDiskImage
|
||||||
|
|
||||||
|
|
||||||
|
@interface HFSDiskImage (Import)
|
||||||
|
|
||||||
|
+ (nullable HFSDiskImage *)importFileIntoTemporaryDiskImage:(NSString *)sourceFile;
|
||||||
|
|
||||||
|
@end // HFSDiskImage (Import)
|
||||||
|
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_END
|
328
Mini vMac/HFSDiskImage.m
Normal file
328
Mini vMac/HFSDiskImage.m
Normal file
@ -0,0 +1,328 @@
|
|||||||
|
//
|
||||||
|
// HFSDiskImage.m
|
||||||
|
// Mini vMac
|
||||||
|
//
|
||||||
|
// Created by Lieven Dekeyser on 13/03/2024.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import "HFSDiskImage.h"
|
||||||
|
#include "libhfs.h"
|
||||||
|
|
||||||
|
@interface NSString (HFSSafe)
|
||||||
|
- (nullable NSString *)hfsSafeFileName;
|
||||||
|
- (nullable NSString *)hfsSafeVolumeName;
|
||||||
|
@end // NSString (HFSSafe)
|
||||||
|
|
||||||
|
|
||||||
|
@interface HFSDiskImage ()
|
||||||
|
@property (nonatomic, copy) NSString * path;
|
||||||
|
@end // HFSDiskImage()
|
||||||
|
|
||||||
|
@implementation HFSDiskImage
|
||||||
|
{
|
||||||
|
hfsvol * _volume;
|
||||||
|
}
|
||||||
|
|
||||||
|
+ (nullable HFSDiskImage *)createDiskImageWithName:(NSString *)name size:(size_t)volumeSize atPath:(NSString *)path
|
||||||
|
{
|
||||||
|
NSFileManager * fm = [NSFileManager defaultManager];
|
||||||
|
if ([fm fileExistsAtPath:path])
|
||||||
|
{
|
||||||
|
return nil;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int fileDescriptor = open(path.fileSystemRepresentation, O_CREAT | O_TRUNC | O_EXCL | O_WRONLY, 0644);
|
||||||
|
if (fileDescriptor == -1)
|
||||||
|
{
|
||||||
|
return nil;
|
||||||
|
}
|
||||||
|
|
||||||
|
int error = 0;
|
||||||
|
if (ftruncate(fileDescriptor, volumeSize))
|
||||||
|
{
|
||||||
|
error = errno;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
const char * volumeName = [name cStringUsingEncoding:NSMacOSRomanStringEncoding];
|
||||||
|
if (volumeName != nil)
|
||||||
|
{
|
||||||
|
error = hfs_format(path.fileSystemRepresentation, 0, 0, volumeName, 0, NULL);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
error = EINVAL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
close(fileDescriptor);
|
||||||
|
|
||||||
|
if (error != 0)
|
||||||
|
{
|
||||||
|
[fm removeItemAtPath:path error:nil];
|
||||||
|
return nil;
|
||||||
|
}
|
||||||
|
|
||||||
|
return [[self alloc] initWithPath:path];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (nullable instancetype)initWithPath:(NSString *)path
|
||||||
|
{
|
||||||
|
if ((self = [super init]))
|
||||||
|
{
|
||||||
|
_readOnly = NO;
|
||||||
|
_volume = nil;
|
||||||
|
self.path = path;
|
||||||
|
}
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)dealloc
|
||||||
|
{
|
||||||
|
[self close];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (BOOL)isOpen
|
||||||
|
{
|
||||||
|
return _volume != NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (BOOL)openForReading
|
||||||
|
{
|
||||||
|
return [self _openWithMode:HFS_MODE_RDONLY];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (BOOL)openForReadingAndWriting
|
||||||
|
{
|
||||||
|
return [self _openWithMode:HFS_MODE_RDWR];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (BOOL)_openWithMode:(int)mode
|
||||||
|
{
|
||||||
|
if ([self isOpen])
|
||||||
|
{
|
||||||
|
return YES;
|
||||||
|
}
|
||||||
|
|
||||||
|
_volume = hfs_mount(self.path.fileSystemRepresentation, 0, mode);
|
||||||
|
_readOnly = (mode != HFS_MODE_RDWR);
|
||||||
|
|
||||||
|
return [self isOpen];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (BOOL)close
|
||||||
|
{
|
||||||
|
if (_volume)
|
||||||
|
{
|
||||||
|
int error = hfs_umount(_volume);
|
||||||
|
if (error == 0) {
|
||||||
|
_volume = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ![self isOpen];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (BOOL)addFile:(NSString *)sourceFilePath
|
||||||
|
{
|
||||||
|
if (![self isOpen] || [self isReadOnly])
|
||||||
|
{
|
||||||
|
return NO;
|
||||||
|
}
|
||||||
|
|
||||||
|
NSString * fileName = [[sourceFilePath lastPathComponent] hfsSafeFileName];
|
||||||
|
if (fileName == nil)
|
||||||
|
{
|
||||||
|
return NO;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char * targetPath = [[NSString stringWithFormat:@":%@", fileName] cStringUsingEncoding:NSMacOSRomanStringEncoding];
|
||||||
|
if (targetPath == NULL)
|
||||||
|
{
|
||||||
|
return NO;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
FILE * sourceFile = fopen(sourceFilePath.fileSystemRepresentation, "r");
|
||||||
|
if (sourceFile == NULL)
|
||||||
|
{
|
||||||
|
return NO;
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOL error = NO;
|
||||||
|
hfsfile * file = hfs_create(_volume, targetPath, "SIT!", "SITx"); // FIXME: type and creator from extension
|
||||||
|
if (file)
|
||||||
|
{
|
||||||
|
const size_t bufferSize = HFS_BLOCKSZ;
|
||||||
|
uint8_t buffer[bufferSize] = { 0 };
|
||||||
|
|
||||||
|
size_t bytesRead = 0;
|
||||||
|
while ((bytesRead = fread(buffer, 1, bufferSize, sourceFile)) > 0)
|
||||||
|
{
|
||||||
|
unsigned long bytesWritten = hfs_write(file, buffer, bytesRead);
|
||||||
|
if (bytesWritten < bytesRead)
|
||||||
|
{
|
||||||
|
error = YES;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
hfs_close(file);
|
||||||
|
file = NULL;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
error = YES;
|
||||||
|
}
|
||||||
|
|
||||||
|
fclose(sourceFile);
|
||||||
|
sourceFile = NULL;
|
||||||
|
|
||||||
|
return error == NO;
|
||||||
|
}
|
||||||
|
|
||||||
|
@end // HFSDiskImage
|
||||||
|
|
||||||
|
|
||||||
|
@implementation HFSDiskImage (Import)
|
||||||
|
|
||||||
|
+ (HFSDiskImage *)importFileIntoTemporaryDiskImage:(NSString *)sourceFile
|
||||||
|
{
|
||||||
|
NSFileManager * fm = [NSFileManager defaultManager];
|
||||||
|
NSError * error = nil;
|
||||||
|
size_t fileSize = [fm attributesOfItemAtPath:sourceFile error:&error].fileSize;
|
||||||
|
if (fileSize == 0)
|
||||||
|
{
|
||||||
|
return nil;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
NSString * tempFolder = NSTemporaryDirectory();
|
||||||
|
|
||||||
|
NSString * volumeName = [[[sourceFile lastPathComponent] stringByDeletingPathExtension] hfsSafeVolumeName];
|
||||||
|
if (volumeName == nil)
|
||||||
|
{
|
||||||
|
return nil;
|
||||||
|
}
|
||||||
|
NSString * diskImagePath = [tempFolder stringByAppendingPathComponent:[NSString stringWithFormat:@"%@.img", volumeName]];
|
||||||
|
int tries = 1;
|
||||||
|
while ([fm fileExistsAtPath:diskImagePath])
|
||||||
|
{
|
||||||
|
++tries;
|
||||||
|
diskImagePath = [tempFolder stringByAppendingPathComponent:[NSString stringWithFormat:@"%@ %d.img", volumeName, tries]];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
HFSDiskImage * diskImage = [HFSDiskImage createDiskImageWithName:volumeName size:(fileSize + (512*1024)) atPath:diskImagePath];
|
||||||
|
if (diskImage == NULL || ![diskImage openForReadingAndWriting])
|
||||||
|
{
|
||||||
|
return nil;
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOL result = [diskImage addFile:sourceFile];
|
||||||
|
|
||||||
|
[diskImage close];
|
||||||
|
|
||||||
|
if (result)
|
||||||
|
{
|
||||||
|
return diskImage;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return nil;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@end // HFSDiskImage (Import)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@implementation NSString (HFSSafe)
|
||||||
|
|
||||||
|
- (NSString *)macOSRomanSafeStringGettingLength:(NSUInteger *)outLength
|
||||||
|
{
|
||||||
|
NSData * converted = [self dataUsingEncoding:NSMacOSRomanStringEncoding allowLossyConversion:YES];
|
||||||
|
if (converted)
|
||||||
|
{
|
||||||
|
if (outLength)
|
||||||
|
{
|
||||||
|
*outLength = [converted length];
|
||||||
|
}
|
||||||
|
return [[NSString alloc] initWithData:converted encoding:NSMacOSRomanStringEncoding];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return nil;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (NSString *)macOSRomanSafeStringWithMaxLength:(NSUInteger)maxLength
|
||||||
|
{
|
||||||
|
NSData * converted = [self dataUsingEncoding:NSMacOSRomanStringEncoding allowLossyConversion:YES];
|
||||||
|
if (converted)
|
||||||
|
{
|
||||||
|
NSUInteger convertedLength = [converted length];
|
||||||
|
if (convertedLength > maxLength)
|
||||||
|
{
|
||||||
|
converted = [converted subdataWithRange:NSMakeRange(0, maxLength)];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [[NSString alloc] initWithData:converted encoding:NSMacOSRomanStringEncoding];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return nil;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (NSString *)hfsSafeFileName
|
||||||
|
{
|
||||||
|
NSString * noColons = [self stringByReplacingOccurrencesOfString:@":" withString:@"_"];
|
||||||
|
NSUInteger convertedStringLength = 0;
|
||||||
|
NSString * convertedString = [noColons macOSRomanSafeStringGettingLength:&convertedStringLength];
|
||||||
|
if (convertedString)
|
||||||
|
{
|
||||||
|
if (convertedStringLength <= HFS_MAX_FLEN)
|
||||||
|
{
|
||||||
|
return convertedString;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// keep path extension if possible:
|
||||||
|
NSUInteger pathExtensionLength = 0;
|
||||||
|
NSString * pathExtension = [[self pathExtension] macOSRomanSafeStringGettingLength:&pathExtensionLength];
|
||||||
|
if (pathExtension)
|
||||||
|
{
|
||||||
|
NSInteger remainingLength = HFS_MAX_FLEN - pathExtensionLength - 1;
|
||||||
|
if (remainingLength > 2)
|
||||||
|
{
|
||||||
|
NSString * trimmedName = [[self stringByDeletingPathExtension] macOSRomanSafeStringWithMaxLength:remainingLength];
|
||||||
|
return [trimmedName stringByAppendingPathExtension:pathExtension];
|
||||||
|
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return [[self stringByDeletingPathExtension] macOSRomanSafeStringWithMaxLength:HFS_MAX_FLEN];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return [self macOSRomanSafeStringWithMaxLength:HFS_MAX_FLEN];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return nil;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (NSString *)hfsSafeVolumeName
|
||||||
|
{
|
||||||
|
NSString * noColons = [self stringByReplacingOccurrencesOfString:@":" withString:@"_"];
|
||||||
|
return [noColons macOSRomanSafeStringWithMaxLength:HFS_MAX_VLEN];
|
||||||
|
}
|
||||||
|
|
||||||
|
@end // NSString (HFSSafe)
|
Loading…
Reference in New Issue
Block a user