2016-05-21 13:01:57 +00:00
|
|
|
//
|
|
|
|
// UIImage+DiskImageIcon.m
|
|
|
|
// Mini vMac
|
|
|
|
//
|
|
|
|
// Created by Jesús A. Álvarez on 21/05/2016.
|
2017-09-23 13:41:55 +00:00
|
|
|
// Copyright © 2016-2017 namedfork. All rights reserved.
|
2016-05-21 13:01:57 +00:00
|
|
|
//
|
|
|
|
|
|
|
|
#import "UIImage+DiskImageIcon.h"
|
|
|
|
#import "libhfs.h"
|
|
|
|
#import "res.h"
|
|
|
|
#import "mfs.h"
|
2016-05-25 17:50:54 +00:00
|
|
|
#import "AppDelegate.h"
|
2016-07-02 21:06:09 +00:00
|
|
|
#import <sys/xattr.h>
|
|
|
|
|
|
|
|
NSString *DidUpdateIconForDiskImageNotification = @"didUpdateIconForDiskImage";
|
|
|
|
static const char kDiskImageIconAttributeName[] = "net.namedfork.DiskImageIcon";
|
2016-05-21 13:01:57 +00:00
|
|
|
|
|
|
|
#define kDiskImageHasDC42Header 1 << 0
|
|
|
|
#define RSHORT(base, offset) ntohs(*((short *)((base) + (offset))))
|
|
|
|
#define RLONG(base, offset) ntohl(*((long *)((base) + (offset))))
|
|
|
|
#define RCSTR(base, offset) ((char *)((base) + (offset)))
|
|
|
|
|
|
|
|
@interface DiskImageIconReader : NSObject
|
|
|
|
|
|
|
|
- (UIImage *)iconForDiskImage:(NSString *)path;
|
|
|
|
|
|
|
|
@end
|
|
|
|
|
|
|
|
@implementation UIImage (DiskImageIcon)
|
|
|
|
|
2016-07-02 21:06:09 +00:00
|
|
|
+ (void)_didEjectDisk:(NSNotification*)notification {
|
|
|
|
NSString *path = [notification.userInfo[@"path"] stringByStandardizingPath];
|
|
|
|
[self loadIconForDiskImageAndNotify:path];
|
|
|
|
}
|
|
|
|
|
|
|
|
+ (UIImage *)imageWithIconForDiskImage:(NSString *)path {
|
2016-05-25 17:50:54 +00:00
|
|
|
static dispatch_once_t onceToken;
|
|
|
|
dispatch_once(&onceToken, ^{
|
2016-05-28 11:01:13 +00:00
|
|
|
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_didEjectDisk:) name:[AppDelegate sharedEmulator].ejectDiskNotification object:nil];
|
2016-05-25 17:50:54 +00:00
|
|
|
});
|
2016-07-02 21:06:09 +00:00
|
|
|
|
|
|
|
// check attribute
|
|
|
|
ssize_t attrLen = getxattr(path.fileSystemRepresentation, kDiskImageIconAttributeName, NULL, 0, 0, 0);
|
|
|
|
if (attrLen == -1) {
|
|
|
|
[self loadIconForDiskImageAndNotify:path];
|
|
|
|
return nil;
|
|
|
|
}
|
|
|
|
|
|
|
|
// read data
|
|
|
|
void *attrData = malloc(attrLen);
|
|
|
|
getxattr(path.fileSystemRepresentation, kDiskImageIconAttributeName, attrData, attrLen, 0, 0);
|
|
|
|
return [UIImage imageWithData:[NSData dataWithBytesNoCopy:attrData length:attrLen freeWhenDone:YES]];
|
2016-05-25 17:50:54 +00:00
|
|
|
}
|
|
|
|
|
2016-07-02 21:06:09 +00:00
|
|
|
+ (void)loadIconForDiskImageAndNotify:(NSString *)path {
|
|
|
|
if ([NSThread isMainThread]) {
|
2016-07-15 19:00:51 +00:00
|
|
|
long queue = NSFoundationVersionNumber >= NSFoundationVersionNumber_iOS_8_0 ? QOS_CLASS_USER_INITIATED : DISPATCH_QUEUE_PRIORITY_LOW;
|
|
|
|
dispatch_async(dispatch_get_global_queue(queue, 0), ^{
|
2016-07-02 21:06:09 +00:00
|
|
|
[self loadIconForDiskImageAndNotify:path];
|
|
|
|
});
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// get current value
|
|
|
|
ssize_t attrLen = getxattr(path.fileSystemRepresentation, kDiskImageIconAttributeName, NULL, 0, 0, 0);
|
|
|
|
NSData *previousData = nil;
|
|
|
|
if (attrLen != -1) {
|
|
|
|
void *attrData = malloc(attrLen);
|
|
|
|
getxattr(path.fileSystemRepresentation, kDiskImageIconAttributeName, attrData, attrLen, 0, 0);
|
|
|
|
previousData = [NSData dataWithBytesNoCopy:attrData length:attrLen freeWhenDone:YES];
|
|
|
|
}
|
|
|
|
|
|
|
|
// load new icon
|
|
|
|
UIImage *icon = [[DiskImageIconReader new] iconForDiskImage:path];
|
|
|
|
NSData *newData = UIImagePNGRepresentation(icon);
|
|
|
|
if (![newData isEqualToData:previousData]) {
|
|
|
|
// save new icon and notify
|
|
|
|
setxattr(path.fileSystemRepresentation, kDiskImageIconAttributeName, newData.bytes, newData.length, 0, 0);
|
|
|
|
NSNotification *newIconNotification = [[NSNotification alloc] initWithName:DidUpdateIconForDiskImageNotification object:path userInfo:icon ? @{@"icon": icon} : nil];
|
|
|
|
[[NSNotificationCenter defaultCenter] performSelectorOnMainThread:@selector(postNotification:) withObject:newIconNotification waitUntilDone:NO];
|
2016-05-25 17:50:54 +00:00
|
|
|
}
|
2016-05-21 13:01:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
@end
|
|
|
|
|
|
|
|
// Mac OS 1 bit palette
|
|
|
|
static uint32_t ctb1[2] = {0xFFFFFF, 0x000000};
|
|
|
|
|
|
|
|
// Mac OS 4 bit palette
|
|
|
|
static uint32_t ctb4[16] = {
|
|
|
|
0xFFFFFF, 0xFFFF00, 0xFF6600, 0xDD0000, 0xFF0099, 0x330099, 0x0000DD, 0x0099FF,
|
|
|
|
0x00BB00, 0x006600, 0x663300, 0x996633, 0xCCCCCC, 0x888888, 0x444444, 0x000000};
|
|
|
|
// Mac OS 8 bit palette
|
|
|
|
static uint32_t ctb8[256] = {
|
|
|
|
0xFFFFFF, 0xFFFFCC, 0xFFFF99, 0xFFFF66, 0xFFFF33, 0xFFFF00, 0xFFCCFF, 0xFFCCCC,
|
|
|
|
0xFFCC99, 0xFFCC66, 0xFFCC33, 0xFFCC00, 0xFF99FF, 0xFF99CC, 0xFF9999, 0xFF9966,
|
|
|
|
0xFF9933, 0xFF9900, 0xFF66FF, 0xFF66CC, 0xFF6699, 0xFF6666, 0xFF6633, 0xFF6600,
|
|
|
|
0xFF33FF, 0xFF33CC, 0xFF3399, 0xFF3366, 0xFF3333, 0xFF3300, 0xFF00FF, 0xFF00CC,
|
|
|
|
0xFF0099, 0xFF0066, 0xFF0033, 0xFF0000, 0xCCFFFF, 0xCCFFCC, 0xCCFF99, 0xCCFF66,
|
|
|
|
0xCCFF33, 0xCCFF00, 0xCCCCFF, 0xCCCCCC, 0xCCCC99, 0xCCCC66, 0xCCCC33, 0xCCCC00,
|
|
|
|
0xCC99FF, 0xCC99CC, 0xCC9999, 0xCC9966, 0xCC9933, 0xCC9900, 0xCC66FF, 0xCC66CC,
|
|
|
|
0xCC6699, 0xCC6666, 0xCC6633, 0xCC6600, 0xCC33FF, 0xCC33CC, 0xCC3399, 0xCC3366,
|
|
|
|
0xCC3333, 0xCC3300, 0xCC00FF, 0xCC00CC, 0xCC0099, 0xCC0066, 0xCC0033, 0xCC0000,
|
|
|
|
0x99FFFF, 0x99FFCC, 0x99FF99, 0x99FF66, 0x99FF33, 0x99FF00, 0x99CCFF, 0x99CCCC,
|
|
|
|
0x99CC99, 0x99CC66, 0x99CC33, 0x99CC00, 0x9999FF, 0x9999CC, 0x999999, 0x999966,
|
|
|
|
0x999933, 0x999900, 0x9966FF, 0x9966CC, 0x996699, 0x996666, 0x996633, 0x996600,
|
|
|
|
0x9933FF, 0x9933CC, 0x993399, 0x993366, 0x993333, 0x993300, 0x9900FF, 0x9900CC,
|
|
|
|
0x990099, 0x990066, 0x990033, 0x990000, 0x66FFFF, 0x66FFCC, 0x66FF99, 0x66FF66,
|
|
|
|
0x66FF33, 0x66FF00, 0x66CCFF, 0x66CCCC, 0x66CC99, 0x66CC66, 0x66CC33, 0x66CC00,
|
|
|
|
0x6699FF, 0x6699CC, 0x669999, 0x669966, 0x669933, 0x669900, 0x6666FF, 0x6666CC,
|
|
|
|
0x666699, 0x666666, 0x666633, 0x666600, 0x6633FF, 0x6633CC, 0x663399, 0x663366,
|
|
|
|
0x663333, 0x663300, 0x6600FF, 0x6600CC, 0x660099, 0x660066, 0x660033, 0x660000,
|
|
|
|
0x33FFFF, 0x33FFCC, 0x33FF99, 0x33FF66, 0x33FF33, 0x33FF00, 0x33CCFF, 0x33CCCC,
|
|
|
|
0x33CC99, 0x33CC66, 0x33CC33, 0x33CC00, 0x3399FF, 0x3399CC, 0x339999, 0x339966,
|
|
|
|
0x339933, 0x339900, 0x3366FF, 0x3366CC, 0x336699, 0x336666, 0x336633, 0x336600,
|
|
|
|
0x3333FF, 0x3333CC, 0x333399, 0x333366, 0x333333, 0x333300, 0x3300FF, 0x3300CC,
|
|
|
|
0x330099, 0x330066, 0x330033, 0x330000, 0x00FFFF, 0x00FFCC, 0x00FF99, 0x00FF66,
|
|
|
|
0x00FF33, 0x00FF00, 0x00CCFF, 0x00CCCC, 0x00CC99, 0x00CC66, 0x00CC33, 0x00CC00,
|
|
|
|
0x0099FF, 0x0099CC, 0x009999, 0x009966, 0x009933, 0x009900, 0x0066FF, 0x0066CC,
|
|
|
|
0x006699, 0x006666, 0x006633, 0x006600, 0x0033FF, 0x0033CC, 0x003399, 0x003366,
|
|
|
|
0x003333, 0x003300, 0x0000FF, 0x0000CC, 0x000099, 0x000066, 0x000033, 0xEE0000,
|
|
|
|
0xDD0000, 0xBB0000, 0xAA0000, 0x880000, 0x770000, 0x550000, 0x440000, 0x220000,
|
|
|
|
0x110000, 0x00EE00, 0x00DD00, 0x00BB00, 0x00AA00, 0x008800, 0x007700, 0x005500,
|
|
|
|
0x004400, 0x002200, 0x001100, 0x0000EE, 0x0000DD, 0x0000BB, 0x0000AA, 0x000088,
|
|
|
|
0x000077, 0x000055, 0x000044, 0x000022, 0x000011, 0xEEEEEE, 0xDDDDDD, 0xBBBBBB,
|
|
|
|
0xAAAAAA, 0x888888, 0x777777, 0x555555, 0x444444, 0x222222, 0x111111, 0x000000};
|
|
|
|
|
2016-05-21 13:43:19 +00:00
|
|
|
static uint8_t maskReplacement[][128] = {
|
|
|
|
{ 0x1D, 0xC7, 0xFC, 0x00, 0x3F, 0xE7, 0xFC, 0x00, 0x7F, 0xF6, 0xAC, 0x00,
|
|
|
|
0x3F, 0xF7, 0x5C, 0x00, 0x17, 0xC6, 0x0C, 0x00, 0x00, 0x02, 0xAB, 0xFE,
|
|
|
|
0x0F, 0xFF, 0x5B, 0xFE, 0x0F, 0xF9, 0xFB, 0xAA, 0x0F, 0x5D, 0xFE, 0x02,
|
|
|
|
0x0A, 0xA8, 0x01, 0xAC, 0x04, 0x00, 0x00, 0x5C, 0x02, 0xBA, 0xAA, 0x2C,
|
|
|
|
0x03, 0xD5, 0x54, 0x5C, 0x02, 0xBA, 0xAA, 0x2C, 0x7E, 0x17, 0x5C, 0x5C,
|
|
|
|
0x6A, 0xBA, 0xAA, 0x2C, 0x75, 0x55, 0x54, 0x3C, 0x7A, 0x6A, 0xEA, 0x20,
|
|
|
|
0x70, 0x95, 0xB5, 0x60, 0x1A, 0x2A, 0xAA, 0xE0, 0x1D, 0x95, 0x15, 0x60,
|
|
|
|
0x1A, 0xAB, 0x1A, 0xE0, 0x1D, 0x95, 0x15, 0x60, 0x1A, 0xAB, 0x1A, 0xE0,
|
|
|
|
0x1D, 0x95, 0x15, 0xE0, 0x3E, 0xAB, 0x1B, 0x70, 0x30, 0x45, 0x10, 0x38,
|
|
|
|
0x20, 0x08, 0x08, 0x3C, 0x68, 0x03, 0xF8, 0x16, 0xC0, 0x00, 0x00, 0x02,
|
|
|
|
0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFF, 0xFF, 0xFE
|
|
|
|
}, {
|
|
|
|
0x1D, 0xC7, 0xFC, 0x00, 0x3F, 0xE7, 0xFC, 0x00, 0x7F, 0xF7, 0xFC, 0x00,
|
|
|
|
0x3F, 0xF7, 0xFC, 0x00, 0x17, 0xC7, 0xFC, 0x00, 0x00, 0x03, 0xFB, 0xFE,
|
|
|
|
0x0F, 0xFF, 0xFF, 0xFE, 0x0F, 0xFF, 0xFF, 0xFE, 0x0F, 0xFF, 0xFF, 0xFE,
|
|
|
|
0x0F, 0xFF, 0xFF, 0xFC, 0x07, 0xFF, 0xFF, 0xFC, 0x03, 0xFF, 0xFF, 0xFC,
|
|
|
|
0x03, 0xFF, 0xFF, 0xFC, 0x03, 0xFF, 0xFF, 0xFC, 0x7F, 0xFF, 0xFF, 0xFC,
|
|
|
|
0x7F, 0xFF, 0xFF, 0xFC, 0x7F, 0xFF, 0xFF, 0xFC, 0x7F, 0xFF, 0xFF, 0xE0,
|
|
|
|
0x7F, 0xFF, 0xFF, 0xE0, 0x1F, 0xFF, 0xFF, 0xE0, 0x1F, 0xFF, 0xFF, 0xE0,
|
|
|
|
0x1F, 0xFF, 0xFF, 0xE0, 0x1F, 0xFF, 0xFF, 0xE0, 0x1F, 0xFF, 0xFF, 0xE0,
|
|
|
|
0x1F, 0xFF, 0xFF, 0xE0, 0x3F, 0xFF, 0xFF, 0xF0, 0x3F, 0xFF, 0xFF, 0xF8,
|
|
|
|
0x3F, 0xFF, 0xFF, 0xFC, 0x7F, 0xFF, 0xFF, 0xFE, 0xFF, 0xFF, 0xFF, 0xFE,
|
|
|
|
0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFF, 0xFF, 0xFE
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2016-05-21 13:01:57 +00:00
|
|
|
@implementation DiskImageIconReader
|
|
|
|
|
|
|
|
- (UIImage *)iconForDiskImage:(NSString *)path {
|
|
|
|
// determine format and offset of disk image
|
|
|
|
NSFileHandle *fh = [NSFileHandle fileHandleForReadingAtPath:path];
|
|
|
|
if (fh == nil) {
|
|
|
|
return nil;
|
|
|
|
}
|
|
|
|
[fh seekToFileOffset:1024];
|
|
|
|
NSData *checkHeader = [fh readDataOfLength:128];
|
|
|
|
[fh closeFile];
|
2016-06-06 18:47:25 +00:00
|
|
|
if (checkHeader == nil || checkHeader.length != 128) {
|
|
|
|
return nil;
|
|
|
|
}
|
2016-05-21 13:01:57 +00:00
|
|
|
const unsigned char *chb = [checkHeader bytes];
|
|
|
|
|
|
|
|
// determine type from header
|
|
|
|
if ((chb[0] == 0x42) && (chb[1] == 0x44)) {
|
|
|
|
/* hfs */
|
|
|
|
return [self iconForHFSDiskImage:path options:0];
|
|
|
|
} else if ((chb[0] == 0xD2) && (chb[1] == 0xD7)) {
|
|
|
|
/* mfs */
|
|
|
|
return [self iconForMFSDiskImage:path options:0];
|
|
|
|
} else if ((chb[84] == 0x42) && (chb[85] == 0x44)) {
|
|
|
|
/* hfs, dc42 header */
|
|
|
|
return [self iconForHFSDiskImage:path options:kDiskImageHasDC42Header];
|
|
|
|
} else if ((chb[84] == 0xD2) && (chb[85] == 0xD7)) {
|
|
|
|
/* mfs, dc42 header */
|
|
|
|
return [self iconForMFSDiskImage:path options:kDiskImageHasDC42Header];
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil;
|
|
|
|
}
|
|
|
|
|
|
|
|
#pragma mark - MFS
|
|
|
|
|
|
|
|
- (UIImage *)iconForMFSDiskImage:(NSString *)path options:(int)options {
|
|
|
|
// open disk image
|
|
|
|
size_t offset = (options & kDiskImageHasDC42Header) ? 84 : 0;
|
|
|
|
MFSVolume *vol = mfs_vopen([path fileSystemRepresentation], (size_t)offset, 0);
|
|
|
|
if (vol == NULL) {
|
|
|
|
NSLog(@"Can't open MFS volume at %@", path);
|
|
|
|
return nil;
|
|
|
|
}
|
|
|
|
NSString *volName = [NSString stringWithCString:vol->name encoding:NSMacOSRomanStringEncoding];
|
|
|
|
NSString *volComment;
|
|
|
|
char *const volCommentBytes = mfs_comment(vol, NULL);
|
|
|
|
if (volCommentBytes) {
|
|
|
|
volComment = [NSString stringWithCString:volCommentBytes encoding:NSMacOSRomanStringEncoding];
|
|
|
|
free(volCommentBytes);
|
|
|
|
}
|
|
|
|
|
|
|
|
// find applications
|
|
|
|
MFSDirectoryRecord *rec;
|
|
|
|
NSMutableArray *apps = [NSMutableArray arrayWithCapacity:5];
|
|
|
|
for (int i = 0; vol->directory[i]; i++) {
|
|
|
|
rec = vol->directory[i];
|
|
|
|
if (ntohl(rec->flUsrWds.type) != 'APPL') {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
[apps addObject:[NSNumber numberWithInt:i]];
|
|
|
|
}
|
|
|
|
|
|
|
|
// if there's more than one app, find one that looks matching
|
|
|
|
if ([apps count] == 0) {
|
|
|
|
return nil;
|
|
|
|
} else if ([apps count] > 1) {
|
|
|
|
rec = NULL;
|
|
|
|
for (NSNumber *num in apps) {
|
|
|
|
rec = vol->directory[[num intValue]];
|
|
|
|
NSString *appName = [[NSString alloc] initWithCString:rec->flCName encoding:NSMacOSRomanStringEncoding];
|
|
|
|
if (![self chooseApp:appName inVolume:volName hint:volComment]) {
|
|
|
|
rec = NULL;
|
|
|
|
}
|
|
|
|
if (rec) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (rec == NULL) {
|
|
|
|
return nil;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
rec = vol->directory[[[apps objectAtIndex:0] intValue]];
|
|
|
|
}
|
|
|
|
|
|
|
|
// open resource fork
|
|
|
|
MFSFork *rsrcFork = mfs_fkopen(vol, rec, kMFSForkRsrc, 0);
|
|
|
|
RFILE *rfile = res_open_funcs(rsrcFork, mfs_fkseek, mfs_fkread);
|
|
|
|
|
|
|
|
// get icon
|
2016-05-26 19:33:09 +00:00
|
|
|
UIImage *icon = [self appIconForResourceFile:rfile creator:ntohl(rec->flUsrWds.creator)];
|
|
|
|
|
2016-05-21 13:01:57 +00:00
|
|
|
// close stuff
|
|
|
|
res_close(rfile);
|
|
|
|
mfs_fkclose(rsrcFork);
|
|
|
|
mfs_vclose(vol);
|
|
|
|
|
|
|
|
return icon;
|
|
|
|
}
|
|
|
|
|
|
|
|
#pragma mark - HFS
|
|
|
|
|
|
|
|
- (UIImage *)iconForHFSDiskImage:(NSString *)path options:(int)options {
|
|
|
|
// open disk image
|
|
|
|
int mountFlags = HFS_MODE_RDONLY;
|
|
|
|
if (options & kDiskImageHasDC42Header) {
|
|
|
|
mountFlags |= HFS_OPT_DC42HEADER;
|
|
|
|
}
|
|
|
|
hfsvol *vol = hfs_mount([path fileSystemRepresentation], 0, mountFlags);
|
|
|
|
if (vol == NULL) {
|
|
|
|
NSLog(@"Can't open HFS volume at %@ with flags %x", path, mountFlags);
|
|
|
|
return nil;
|
|
|
|
}
|
|
|
|
|
|
|
|
// try volume icon
|
|
|
|
UIImage *volumeIcon = [self iconFromHFSVolumeIcon:vol];
|
|
|
|
if (volumeIcon) {
|
|
|
|
hfs_umount(vol);
|
|
|
|
return volumeIcon;
|
|
|
|
}
|
|
|
|
|
|
|
|
// find best application
|
|
|
|
UIImage *icon = nil;
|
|
|
|
NSString *appPath = [self findAppInHFSVolume:vol];
|
|
|
|
hfsfile *hfile = NULL;
|
|
|
|
RFILE *rfile = NULL;
|
|
|
|
if (appPath == nil) {
|
|
|
|
hfs_umount(vol);
|
|
|
|
return nil;
|
|
|
|
}
|
|
|
|
|
|
|
|
// open resource fork
|
|
|
|
hfile = hfs_open(vol, [appPath cStringUsingEncoding:NSMacOSRomanStringEncoding]);
|
|
|
|
if (hfile == NULL) {
|
|
|
|
hfs_umount(vol);
|
|
|
|
return nil;
|
|
|
|
}
|
|
|
|
hfs_setfork(hfile, 1);
|
|
|
|
rfile = res_open_funcs(hfile, (res_seek_func)hfs_seek, (res_read_func)hfs_read);
|
|
|
|
if (rfile == NULL) {
|
|
|
|
hfs_close(hfile);
|
|
|
|
hfs_umount(vol);
|
|
|
|
return nil;
|
|
|
|
}
|
|
|
|
|
|
|
|
// get icon
|
|
|
|
hfsdirent ent;
|
|
|
|
if (hfs_stat(vol, [appPath cStringUsingEncoding:NSMacOSRomanStringEncoding], &ent)) {
|
|
|
|
res_close(rfile);
|
|
|
|
hfs_close(hfile);
|
|
|
|
hfs_umount(vol);
|
|
|
|
return nil;
|
|
|
|
}
|
2016-05-26 19:33:09 +00:00
|
|
|
icon = [self appIconForResourceFile:rfile creator:ntohl(*(uint32_t *)ent.u.file.creator)];
|
|
|
|
|
2016-05-21 13:01:57 +00:00
|
|
|
// close stuff
|
|
|
|
res_close(rfile);
|
|
|
|
hfs_close(hfile);
|
|
|
|
hfs_umount(vol);
|
|
|
|
return icon;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (NSString *)findAppInHFSVolume:(hfsvol *)vol {
|
|
|
|
// get disk name
|
|
|
|
hfsvolent volEnt;
|
|
|
|
hfs_vstat(vol, &volEnt);
|
|
|
|
NSString *volName = [NSString stringWithCString:volEnt.name encoding:NSMacOSRomanStringEncoding];
|
|
|
|
NSString *volComment = [self commentForHFSVolume:vol];
|
|
|
|
|
|
|
|
// find apps
|
|
|
|
NSMutableArray *apps = [[NSMutableArray alloc] initWithCapacity:5];
|
|
|
|
[self findApps:apps inDirectory:HFS_CNID_ROOTDIR ofHFSVolume:vol skipFolder:volEnt.blessed];
|
|
|
|
|
|
|
|
// decide which one to use
|
|
|
|
NSString *myApp = nil;
|
|
|
|
NSString *appName = nil;
|
|
|
|
if ([apps count] == 1) {
|
|
|
|
myApp = [apps objectAtIndex:0];
|
|
|
|
} else if ([apps count] > 1) {
|
|
|
|
for (NSString *appPath in apps) {
|
|
|
|
// choose an app
|
|
|
|
appName = [appPath componentsSeparatedByString:@":"].lastObject;
|
|
|
|
if (![self chooseApp:appName inVolume:volName hint:volComment]) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
myApp = appPath;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return myApp;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)findApps:(NSMutableArray *)apps inDirectory:(unsigned long)cnid ofHFSVolume:(hfsvol *)vol skipFolder:(unsigned long)skipCNID {
|
|
|
|
if (hfs_setcwd(vol, cnid)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
hfsdir *dir = hfs_opendir(vol, ":");
|
|
|
|
if (dir == NULL) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
hfsdirent ent;
|
|
|
|
while (hfs_readdir(dir, &ent) == 0) {
|
|
|
|
if (ent.flags & HFS_ISDIR && ent.cnid != skipCNID) {
|
|
|
|
[self findApps:apps inDirectory:ent.cnid ofHFSVolume:vol skipFolder:skipCNID];
|
|
|
|
} else if (ntohl(*(uint32_t *)ent.u.file.type) == 'APPL') {
|
|
|
|
// Found an app
|
|
|
|
[apps addObject:[self pathToDirEntry:&ent ofHFSVolume:vol]];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
hfs_closedir(dir);
|
|
|
|
}
|
|
|
|
|
|
|
|
- (NSString *)pathToDirEntry:(const hfsdirent *)ent ofHFSVolume:(hfsvol *)vol {
|
|
|
|
NSMutableString *path = [NSMutableString stringWithCString:ent->name encoding:NSMacOSRomanStringEncoding];
|
|
|
|
NSString *entName;
|
|
|
|
char name[HFS_MAX_FLEN + 1];
|
|
|
|
unsigned long cnid = ent->parid;
|
|
|
|
while (cnid != HFS_CNID_ROOTPAR) {
|
|
|
|
if (hfs_dirinfo(vol, &cnid, name)) {
|
|
|
|
return nil;
|
|
|
|
}
|
|
|
|
entName = [[NSString alloc] initWithCString:name encoding:NSMacOSRomanStringEncoding];
|
|
|
|
[path insertString:@":" atIndex:0];
|
|
|
|
[path insertString:entName atIndex:0];
|
|
|
|
}
|
|
|
|
return path;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (NSString *)commentForHFSVolume:(hfsvol *)vol {
|
|
|
|
hfsvolent vent;
|
|
|
|
hfsdirent dent;
|
|
|
|
NSString *comment = nil;
|
|
|
|
|
|
|
|
// get comment ID
|
|
|
|
if (hfs_vstat(vol, &vent) || hfs_stat(vol, ":", &dent)) {
|
|
|
|
return nil;
|
|
|
|
}
|
|
|
|
unsigned short cmtID = dent.fdcomment;
|
|
|
|
|
|
|
|
// open desktop
|
|
|
|
hfsfile *hfile = NULL;
|
|
|
|
RFILE *rfile = NULL;
|
|
|
|
hfs_chdir(vol, vent.name);
|
|
|
|
hfile = hfs_open(vol, "Desktop");
|
|
|
|
if (hfile == NULL) {
|
|
|
|
return nil;
|
|
|
|
}
|
|
|
|
hfs_setfork(hfile, 1);
|
|
|
|
rfile = res_open_funcs(hfile, (res_seek_func)hfs_seek, (res_read_func)hfs_read);
|
|
|
|
if (rfile == NULL) {
|
|
|
|
hfs_close(hfile);
|
|
|
|
return nil;
|
|
|
|
}
|
|
|
|
|
|
|
|
// read resource
|
|
|
|
unsigned char cmtLen;
|
|
|
|
size_t readBytes;
|
|
|
|
res_read(rfile, 'FCMT', cmtID, &cmtLen, 0, 1, &readBytes, NULL);
|
|
|
|
if (readBytes == 0) {
|
|
|
|
res_close(rfile);
|
|
|
|
hfs_close(hfile);
|
|
|
|
return nil;
|
|
|
|
}
|
|
|
|
char cmtBytes[256];
|
|
|
|
res_read(rfile, 'FCMT', cmtID, cmtBytes, 1, cmtLen, &readBytes, NULL);
|
|
|
|
cmtBytes[cmtLen] = '\0';
|
|
|
|
comment = [NSString stringWithCString:cmtBytes encoding:NSMacOSRomanStringEncoding];
|
|
|
|
|
|
|
|
// close
|
|
|
|
res_close(rfile);
|
|
|
|
hfs_close(hfile);
|
|
|
|
return comment;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (UIImage *)iconFromHFSVolumeIcon:(hfsvol *)vol {
|
|
|
|
UIImage *icon = nil;
|
|
|
|
hfsvolent vent;
|
|
|
|
if (hfs_vstat(vol, &vent)) {
|
|
|
|
return nil;
|
|
|
|
}
|
|
|
|
|
|
|
|
// open icon file
|
|
|
|
hfs_chdir(vol, vent.name);
|
|
|
|
hfsfile *hfile = NULL;
|
|
|
|
RFILE *rfile = NULL;
|
|
|
|
hfile = hfs_open(vol, "Icon\x0D");
|
|
|
|
if (hfile == NULL) {
|
|
|
|
res_close(rfile);
|
|
|
|
return nil;
|
|
|
|
}
|
|
|
|
hfs_setfork(hfile, 1);
|
|
|
|
rfile = res_open_funcs(hfile, (res_seek_func)hfs_seek, (res_read_func)hfs_read);
|
|
|
|
if (rfile == NULL) {
|
|
|
|
if (hfile) {
|
|
|
|
hfs_close(hfile);
|
|
|
|
}
|
|
|
|
return nil;
|
|
|
|
}
|
2016-05-26 19:33:09 +00:00
|
|
|
|
2016-05-21 13:01:57 +00:00
|
|
|
// read icon family
|
|
|
|
NSDictionary *iconFamily = [self iconFamilyID:-16455 inResourceFile:rfile];
|
2016-05-26 19:33:09 +00:00
|
|
|
icon = [self iconImageFromFamily:iconFamily];
|
2016-05-21 13:01:57 +00:00
|
|
|
|
|
|
|
res_close(rfile);
|
|
|
|
if (hfile) {
|
|
|
|
hfs_close(hfile);
|
|
|
|
}
|
|
|
|
return icon;
|
|
|
|
}
|
|
|
|
|
|
|
|
#pragma mark - App Selection
|
|
|
|
|
|
|
|
- (BOOL)chooseApp:(NSString *)appName inVolume:(NSString *)volName hint:(NSString *)hint {
|
|
|
|
return ([appName hasPrefix:volName] ||
|
|
|
|
[volName hasPrefix:appName] ||
|
|
|
|
[volName isEqualToString:appName] ||
|
|
|
|
[appName isEqualToString:hint]);
|
|
|
|
}
|
|
|
|
|
|
|
|
#pragma mark - Resource Access
|
|
|
|
|
2016-05-26 19:33:09 +00:00
|
|
|
- (UIImage*)appIconForResourceFile:(RFILE *)rfile creator:(OSType)creator {
|
2016-05-21 13:01:57 +00:00
|
|
|
// load bundle
|
|
|
|
size_t numBundles;
|
|
|
|
ResAttr *bundles = res_list(rfile, 'BNDL', NULL, 0, 0, &numBundles, NULL);
|
|
|
|
void *bundle = NULL;
|
|
|
|
if (numBundles == 0 || bundles == NULL) {
|
|
|
|
return nil;
|
|
|
|
}
|
|
|
|
for (int i = 0; i < numBundles; i++) {
|
|
|
|
bundle = res_read(rfile, 'BNDL', bundles[i].ID, NULL, 0, 0, NULL, NULL);
|
|
|
|
if (bundle == NULL || ntohl(*(OSType *)bundle) == creator) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
free(bundle);
|
|
|
|
bundle = NULL;
|
|
|
|
}
|
|
|
|
free(bundles);
|
|
|
|
if (bundle == NULL) {
|
|
|
|
return nil;
|
|
|
|
}
|
|
|
|
|
|
|
|
// read bundle
|
2016-05-26 19:33:09 +00:00
|
|
|
NSInteger iconID = [self iconFamilyIDForType:'APPL' inBundle:bundle inResourceFile:rfile];
|
2016-05-21 13:01:57 +00:00
|
|
|
free(bundle);
|
|
|
|
if (iconID == NSNotFound) {
|
|
|
|
return nil;
|
|
|
|
}
|
|
|
|
|
|
|
|
// read icon family
|
|
|
|
NSDictionary *iconFamily = [self iconFamilyID:iconID inResourceFile:rfile];
|
|
|
|
|
|
|
|
// create image
|
|
|
|
return [self iconImageFromFamily:iconFamily];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (NSDictionary *)iconFamilyID:(int16_t)famID inResourceFile:(RFILE *)rfile {
|
|
|
|
NSMutableDictionary *iconFamily = [NSMutableDictionary dictionaryWithCapacity:6];
|
|
|
|
NSData *iconData, *maskData;
|
|
|
|
void *iconRsrc;
|
|
|
|
size_t resSize;
|
|
|
|
|
|
|
|
// separate resources
|
|
|
|
const uint32_t iconResourceTypes[] = {'ICN#', 'icl4', 'icl8', 'ics#', 'ics4', 'ics8', 0};
|
|
|
|
for (int i = 0; iconResourceTypes[i]; i++) {
|
|
|
|
iconRsrc = res_read(rfile, iconResourceTypes[i], famID, NULL, 0, 0, &resSize, NULL);
|
|
|
|
if (iconRsrc == NULL) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
[iconFamily setObject:[NSData dataWithBytes:iconRsrc length:resSize] forKey:[NSString stringWithFormat:@"%c%c%c%c", TYPECHARS(iconResourceTypes[i])]];
|
|
|
|
free(iconRsrc);
|
|
|
|
}
|
|
|
|
|
|
|
|
// mask pseudo-resources
|
|
|
|
if ((iconData = [iconFamily objectForKey:@"ICN#"])) {
|
|
|
|
maskData = [iconData subdataWithRange:NSMakeRange(0x80, 0x80)];
|
|
|
|
[iconFamily setObject:maskData forKey:@"IMK#"];
|
|
|
|
}
|
|
|
|
if ((iconData = [iconFamily objectForKey:@"ics#"])) {
|
|
|
|
maskData = [iconData subdataWithRange:NSMakeRange(0x20, 0x20)];
|
|
|
|
[iconFamily setObject:maskData forKey:@"imk#"];
|
|
|
|
}
|
|
|
|
|
|
|
|
return iconFamily;
|
|
|
|
}
|
|
|
|
|
2016-05-26 19:33:09 +00:00
|
|
|
- (NSInteger)iconFamilyIDForType:(OSType)type inBundle:(void *)bndl inResourceFile:(RFILE *)rfile {
|
2016-05-21 13:01:57 +00:00
|
|
|
short numIconFamilies = RSHORT(bndl, 0x0C) + 1;
|
|
|
|
short *iconFamily = (short *)(bndl + 0x0E);
|
|
|
|
short numFileRefs = RSHORT(bndl, (numIconFamilies * 4) + 0x12) + 1;
|
|
|
|
short *fileRef = (short *)(bndl + (numIconFamilies * 4) + 0x14);
|
|
|
|
|
|
|
|
// find FREF for APPL type
|
|
|
|
short localIconID;
|
|
|
|
void *FREF = NULL;
|
|
|
|
for (int i = 0; i < 2 * numFileRefs; i += 2) {
|
|
|
|
FREF = res_read(rfile, 'FREF', (int)ntohs(fileRef[i + 1]), NULL, 0, 0, NULL, NULL);
|
|
|
|
if (FREF == NULL || RLONG(FREF, 0) == 'APPL') {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
free(FREF);
|
|
|
|
FREF = NULL;
|
|
|
|
}
|
|
|
|
if (FREF == NULL) {
|
|
|
|
return NSNotFound;
|
|
|
|
}
|
|
|
|
|
|
|
|
// read FREF
|
|
|
|
localIconID = RSHORT(FREF, 4);
|
|
|
|
free(FREF);
|
|
|
|
|
|
|
|
// find resource ID for local ID
|
|
|
|
for (int i = 0; i < 2 * numIconFamilies; i += 2) {
|
|
|
|
if (ntohs(iconFamily[i]) == localIconID) {
|
|
|
|
return (int)ntohs(iconFamily[i + 1]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return NSNotFound;
|
|
|
|
}
|
|
|
|
|
2016-05-26 19:33:09 +00:00
|
|
|
- (UIImage*)iconImageFromFamily:(NSDictionary *)iconFamily {
|
2016-05-21 13:01:57 +00:00
|
|
|
NSData *iconData, *iconMask;
|
|
|
|
if ((iconMask = [iconFamily objectForKey:@"IMK#"])) {
|
|
|
|
// has large mask, find best large icon
|
|
|
|
if ((iconData = [iconFamily objectForKey:@"icl8"])) {
|
|
|
|
return [self iconImageWithData:iconData mask:iconMask size:32 depth:8];
|
|
|
|
} else if ((iconData = [iconFamily objectForKey:@"icl4"])) {
|
|
|
|
return [self iconImageWithData:iconData mask:iconMask size:32 depth:4];
|
|
|
|
} else {
|
|
|
|
iconData = [iconFamily objectForKey:@"ICN#"];
|
|
|
|
}
|
|
|
|
return [self iconImageWithData:iconData mask:iconMask size:32 depth:1];
|
|
|
|
} else if ((iconMask = [iconFamily objectForKey:@"imk#"])) {
|
|
|
|
// has small mask, find best small icon
|
|
|
|
if ((iconData = [iconFamily objectForKey:@"ics8"])) {
|
|
|
|
return [self iconImageWithData:iconData mask:iconMask size:32 depth:8];
|
|
|
|
} else if ((iconData = [iconFamily objectForKey:@"ics4"])) {
|
|
|
|
return [self iconImageWithData:iconData mask:iconMask size:32 depth:4];
|
|
|
|
} else {
|
|
|
|
iconData = [iconFamily objectForKey:@"ics#"];
|
|
|
|
}
|
|
|
|
return [self iconImageWithData:iconData mask:iconMask size:32 depth:1];
|
|
|
|
}
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2016-05-26 19:33:09 +00:00
|
|
|
- (UIImage*)iconImageWithData:(NSData *)iconData mask:(NSData *)iconMask size:(int)size depth:(int)depth {
|
2016-05-21 13:01:57 +00:00
|
|
|
if (iconData == nil || iconMask == nil) {
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
// convert to ARGB
|
|
|
|
#define _iSETPIXELRGB(px, py, sa, srgb) \
|
|
|
|
data[(4 * (px + (py * size))) + 0] = sa; \
|
|
|
|
data[(4 * (px + (py * size))) + 1] = ((srgb >> 16) & 0xFF); \
|
|
|
|
data[(4 * (px + (py * size))) + 2] = ((srgb >> 8) & 0xFF); \
|
|
|
|
data[(4 * (px + (py * size))) + 3] = (srgb & 0xFF)
|
|
|
|
|
|
|
|
CFMutableDataRef pixels = CFDataCreateMutable(kCFAllocatorDefault, 4 * size * size);
|
|
|
|
CFDataSetLength(pixels, 4 * size * size);
|
|
|
|
unsigned char *data = CFDataGetMutableBytePtr(pixels);
|
|
|
|
const unsigned char *pixelData = [iconData bytes];
|
|
|
|
const unsigned char *maskData = [iconMask bytes];
|
2016-05-21 13:43:19 +00:00
|
|
|
|
|
|
|
if (size == 32 && iconMask.length == 128) {
|
|
|
|
for (int i=0; i < sizeof(maskReplacement) / sizeof(maskReplacement[0]); i+=2) {
|
|
|
|
if (memcmp(maskData, maskReplacement[i], 128) == 0) {
|
|
|
|
maskData = maskReplacement[i+1];
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-05-21 13:01:57 +00:00
|
|
|
int m, mxy, pxy, rgb;
|
|
|
|
if (pixels == NULL) {
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
switch (depth) {
|
|
|
|
case 1:
|
|
|
|
// 1-bit
|
|
|
|
for (int y = 0; y < size; y++) {
|
|
|
|
for (int x = 0; x < size; x++) {
|
|
|
|
mxy = pxy = (y * (size / 8)) + (x / 8);
|
|
|
|
m = ((maskData[mxy] >> (7 - (x % 8))) & 0x01) ? 0xFF : 0x00;
|
|
|
|
rgb = ctb1[((pixelData[pxy] >> (7 - (x % 8))) & 0x01)];
|
|
|
|
_iSETPIXELRGB(x, y, m, rgb);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 4:
|
|
|
|
// 4-bit
|
|
|
|
for (int y = 0; y < size; y++) {
|
|
|
|
for (int x = 0; x < size; x++) {
|
|
|
|
mxy = (y * (size / 8)) + (x / 8);
|
|
|
|
pxy = (y * (size / 2)) + (x / 2);
|
|
|
|
m = ((maskData[mxy] >> (7 - (x % 8))) & 0x01) ? 0xFF : 0x00;
|
|
|
|
rgb = ctb4[(pixelData[pxy] >> 4 * (1 - x % 2)) & 0x0F];
|
|
|
|
_iSETPIXELRGB(x, y, m, rgb);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 8:
|
|
|
|
// 8-bit
|
|
|
|
for (int y = 0; y < size; y++) {
|
|
|
|
for (int x = 0; x < size; x++) {
|
|
|
|
mxy = (y * (size / 8)) + (x / 8);
|
|
|
|
pxy = (y * size) + x;
|
|
|
|
m = ((maskData[mxy] >> (7 - (x % 8))) & 0x01) ? 0xFF : 0x00;
|
|
|
|
rgb = ctb8[pixelData[pxy]];
|
|
|
|
_iSETPIXELRGB(x, y, m, rgb);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
// create image
|
|
|
|
CGDataProviderRef provider = CGDataProviderCreateWithCFData(pixels);
|
|
|
|
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
|
2016-05-26 19:33:09 +00:00
|
|
|
CGImageRef cgImage = CGImageCreate(size, size, 8, 32, size * 4, colorSpace, kCGImageAlphaFirst | kCGBitmapByteOrder32Big, provider, NULL, false, kCGRenderingIntentDefault);
|
2016-05-21 13:01:57 +00:00
|
|
|
CGDataProviderRelease(provider);
|
|
|
|
CGColorSpaceRelease(colorSpace);
|
|
|
|
CFRelease(pixels);
|
2016-05-26 19:33:09 +00:00
|
|
|
UIImage *image = [UIImage imageWithCGImage:cgImage];
|
|
|
|
CGImageRelease(cgImage);
|
2016-05-21 13:01:57 +00:00
|
|
|
return image;
|
|
|
|
}
|
|
|
|
|
2017-09-23 13:41:55 +00:00
|
|
|
@end
|