Resource Editing in Mac OS Cocoa Objective-C

Making my own helper app for Nova

I'm currently working on a Cocoa Objective-C app that solves a feature many have requested for EV Nova ... namely, a warehouse to store your ship temporarily. I've created an app that extracts the outfit and ship ids from a pilot log, and I would like to make this app able to edit a resource fork (I've already made the template in MC, and I would like it to directly edit the hex code). I don't even need to separate the resources, just to load the big fork file. However, there's no Obj-C system methods to load a resource fork, and the Carbon functions create "Handles" when they load resource forks, which I have no idea how to implement in Obj-C. Please help, as I don't want to throw out my work on the text extraction ...

I don't think that the pilot resources have been cracked enough to modify them.

I haven't touched resource forks in a programming project for years (EV plugin work aside), and good riddance. I'm can't say for certain, but you're going to have a hard time doing this Cocoa - Carbon is probably you're only choice. Classic Mac programming relied on handles (pointers to pointers) so that the system would be free to move memory around in the background to stave-off fragmentation.

Manipulating resources meant obtaining a handle from the Toolbox, locking it, doing stuff with the resource data and unlocking the handle again.

Memory fragmentation isn't an issue under OSX, so neither are handles. Interestingly, the best modern-ish program I've found for dealing with classic resources is REALbasic.

This post has been edited by tycho61uk : 08 May 2006 - 07:44 PM

- Cocoa has no knowledge or resources or the resource fork; you'll have to use Carbon functions to manage the resources themselves
- handles are "just" pointers to pointers to data, you can access the raw data this way (however, do NOT attempt to create handles with a simple pointer to a pointer to memory you allocated)
- If you just need to access the resource fork as a whole bunch of data, use the corresponding Carbon function, or perhaps (if you have a POSIX path to the file) acces TheFile/..namedfork/rsrc
- Remember that it will work only on OSX, if you use Cocoa.

This post has been edited by Zacha Pedro : 09 May 2006 - 12:07 PM

@zacha-pedro, on May 9 2006, 12:06 PM, said in Resource Editing in Mac OS Cocoa Objective-C:

- If you just need to access the resource fork as a whole bunch of data, use the corresponding Carbon function, or perhaps (if you have a POSIX path to the file) acces TheFile/..namedfork/rsrc

Wait... so, can I use the following code?
I'm bundling a plugin in the app, and I just want to edit the CompBitSet field in it.

NSBundle *theBundle = (NSBundle bundleForClass:(self class));
NSString *thePath = (theBundle pathForResource:@"PluginTemplate" ofType:nil);  //see note in EDIT:
NSString *includedResourceForkPath = (thePath stringByAppendingString:@"/..namedfork/rsrc");  //two periods!?
NSFileWrapper *theResourceFork = ((NSFileWrapper alloc) initWithPath:includedResourceForkPath)
NSData *theData = (theResourceFork regularFileContents);

Then, I would edit the data using Cocoa calls, do similar code in reverse (on an empty file outside the bundle), and there I would have a valid plugin at that external file?

EDIT: Should the specified type of PluginTemplate be nil or Npďf? Also, an unrelated question, when passing read/write permissions to the Carbon Resource Manager, how should they be passed (i.e. r/w = 1, read-only = 0, or vice-versa).

BTW, I'll release the final app as open-source. 🆒

This post has been edited by mushu : 09 May 2006 - 09:51 PM

I think the best way to do it is to use the Carbon Resource Manager calls. They're actually pretty straightforward, and it doesn't take much work to extract the raw data to an NSData, from which you can do all your editing using standard cocoa calls. When you've modified the data, it's also not that hard to lump it back into the resource fork.

I found the most annoying part of the whole process to be generating the FSSpec to pass to the legacy FSpOpenResFile() call. 😛

Code I'm using to load a res file, given an NSString with the file path. This will open it with Read/Write permissions. (As given by the fsRdWrPerm constant used in the FSpOpenResFile() call):

int LoadResFork(NSString* path) {
	
	char filepath(256);
	strcpy(filepath, (path cString));
	FSSpec ResFileSpec;
	FSRef ResFileRef;
	OSErr err;
	err = FSPathMakeRef((const UInt8*)filepath, &ResFileRef, false);
	OSErr SpecErr;
	SpecErr = FSGetCatalogInfo(&ResFileRef, kFSCatInfoNone, NULL, NULL, &ResFileSpec, NULL);
	short Ref;
	Ref = FSpOpenResFile(&ResFileSpec, fsRdWrPerm);
	OSErr ResErr = ResError();
	if (ResErr < 0) return ResErr;
	else return Ref;
}

To then load a resource of, say, type desc with id 128 into an NSData you'd use:

UseResFile(refNum);
Handle theResHandle = Get1Resource(TYPE_DESC, 128); // Where TYPE_DESC is a constant defined to equal the dësc ResType, which happens to be 1687253859.

NSData *theResourceData;

theResourceData = ((NSData alloc) initWithBytes:*theResHandle size:GetMaxResourceSize(theResHandle));

Once this code is in there, you can just pull out chunks of data you want to use by making an NSRange, and using the getBytes:range: method on the NSData object.

To save data, just set up the NSData to contain the data you want to dump back to a resource and use
(where theData is the NSData object):

Handle h = Get1Resource(TYPE_DESC, 128);
SetHandleSize(h, (theData length));
(theData getBytes:*h);
ChangedResource(h);
WriteResource(h);
UpdateResFile(filRefNum);

Keep in mind to call ResError() after any resource manager calls, and make sure it returns noErr before continuing.

Why bother with FSSpecs, Andcarne? Don't you know about:
SInt16 FSOpenResFile (
const FSRef *ref,
SInt8 permission
);

Second, how is the caller of LoadResFork supposed to know whether the return value is an error code or an actual RefNum?

TYPE_DESC could be written as 'dësc', on condition that the source files are encoded as MacRoman, so as it may not be the case it's best to write it as 0x64917364, at least in the header in the way you define TYPE_DESC, as it will be easier to check if you know ASCII.

Even if that may seem not to be much use of memory (and there is no risk of arbitrarily leaking memory), you should ReleaseResource the resource when you're finished loading it in the NSData and after saving it from the NSData. Also, you should ALWAYS lock handles before passing around the master pointer (*h), because the function that uses it may (and, in Cocoa, likely will) allocate memory, which may in turn cuase the memory to be moved and the copy of the master pointer that you passed will no longer point to the appropriate memory.

@zacha-pedro, on May 13 2006, 02:08 AM, said in Resource Editing in Mac OS Cocoa Objective-C:

Why bother with FSSpecs, Andcarne? Don't you know about:
SInt16 FSOpenResFile (
const FSRef *ref,
SInt8 permission
);

Huh, somehow I totally managed to miss that in the reference documents.

Quote

Second, how is the caller of LoadResFork supposed to know whether the return value is an error code or an actual RefNum?

Error codes are negative values, whereas a valid RefNum will be positive.

Quote

TYPE_DESC could be written as 'dësc', on condition that the source files are encoded as MacRoman, so as it may not be the case it's best to write it as 0x64917364, at least in the header in the way you define TYPE_DESC, as it will be easier to check if you know ASCII.

The reason I used my value was because I was having trouble finding a MacRoman character table. It was easy enough just to have my app spew out a value for each resource type it found in the file, with the corresponding text representation (minus the characters with umlaüts over them).

Quote

Even if that may seem not to be much use of memory (and there is no risk of arbitrarily leaking memory), you should ReleaseResource the resource when you're finished loading it in the NSData and after saving it from the NSData. Also, you should ALWAYS lock handles before passing around the master pointer (*h), because the function that uses it may (and, in Cocoa, likely will) allocate memory, which may in turn cuase the memory to be moved and the copy of the master pointer that you passed will no longer point to the appropriate memory.

Oops, forgot to copy the ReleaseResource line over from my code. As for locking the handles, this project of mine has been a bit of a learning experience (my first from-scratch programming project, actually) and I have little knowledge of Handles. Thanks for the tip.

@andcarne, on May 13 2006, 06:07 PM, said in Resource Editing in Mac OS Cocoa Objective-C:

Huh, somehow I totally managed to miss that in the reference documents.

Perhaps it's because it is at the end in miscellaneous functions (as are many functions added for CarbonLib and OSX), though it has been corrected in later versions of the reference documents.

@andcarne, on May 13 2006, 06:07 PM, said in Resource Editing in Mac OS Cocoa Objective-C:

Error codes are negative values, whereas a valid RefNum will be positive.

Doesn't surprise me that RefNums are positive, however there are valid, positive error codes (not just our fatal friends 1: bus error, 2: adress error, etc...) At any rate, but this is personal, I don't find it a good design.

@andcarne, on May 13 2006, 06:07 PM, said in Resource Editing in Mac OS Cocoa Objective-C:

The reason I used my value was because I was having trouble finding a MacRoman character table. It was easy enough just to have my app spew out a value for each resource type it found in the file, with the corresponding text representation (minus the characters with umlaüts over them).

Come on, just take just about any hex editor (including ResEdit's) and type the letter in the right column, and it will give you the hex value in the other. Quick and easy, works the other way too.

@andcarne, on May 13 2006, 06:07 PM, said in Resource Editing in Mac OS Cocoa Objective-C:

Oops, forgot to copy the ReleaseResource line over from my code. As for locking the handles, this project of mine has been a bit of a learning experience (my first from-scratch programming project, actually) and I have little knowledge of Handles. Thanks for the tip.

As long as you're running in OSX, you can get away with not locking handles since they are never moved anyway (wouldn't be of much use with the large address space and memory) to compact memory, though of course it is foolish to count on this as they can still be moved by SetHandleSize (because there may not be enough free memory after the memory block where it is right now, so it needs to be put in another place), and of course everything will explode on you as soon as you try backporting to OS9 (something that you should keep in mind when doing stuff to go with Nova: you might need to support OS9). Otherwise, not much to say about handles other than: don't fake them.

Andcarne and Zacha Pedro ++ guys 🙂

Good show!

Thanks so much! This is actually my first time coding in Cocoa or Carbon (I know, an ambitious project for the first time, but why not?), so I really appreciate your help.

P.S. By the way, does anyone know if the NSScanner instance method (theScanner scanString:@"..." intoString:nil) moves the scanLocation past the string? Documentation says yes, but testing it (OS 10.4.6) doesn't move the scanLocation at all. I was able to work around it by calling scanUpToString, then adding the length of the string into scanLocation. Any thoughts why this is happening?

EDIT: I'll just lock my handles, period. (lame.pun)I don't think I could handle it any other way.(/lame.pun)

This post has been edited by mushu : 14 May 2006 - 03:02 PM

@rmx256, on May 8 2006, 08:20 PM, said in Resource Editing in Mac OS Cocoa Objective-C:

I don't think that the pilot resources have been cracked enough to modify them.

Not true. Unfortunatly, I can't find them, but there was this huge suite of mini-apps that would just do something to your file, it wasn't a plugin or anything. Like you went into a folder for, say, Carbon Fiber (armor), and you would open one of the Carbon Fiber applications, say "Carbon Fiber 60", select your pilot file, and you would have 60 layers of Carbon Fiber. If you had 4 before, you would not have 64, you would have 60. It wasn't complicated, it didn't look to see what was there, but it definitly existed with the purpose of going in and directly editing pilot files.

@andcarne, on May 13 2006, 01:57 AM, said in Resource Editing in Mac OS Cocoa Objective-C:

To save data, just set up the NSData to contain the data you want to dump back to a resource and use
(where theData is the NSData object):

Handle h = Get1Resource(TYPE_DESC, 128);
SetHandleSize(h, (theData length));
(theData getBytes:*h);
ChangedResource(h);
WriteResource(h);
UpdateResFile(filRefNum);

Keep in mind to call ResError() after any resource manager calls, and make sure it returns noErr before continuing.

According to the Resource Manager Reference, UpdateResFile automatically writes changed resources, so you don't need WriteResource, correct me if I'm wrong.

EDIT: And CloseResFile should call both of them, so you can kill 3 birds with one stone.

This post has been edited by mushu : 14 May 2006 - 07:03 PM

Yep, didn't catch that one, provided you call ChangedResource() before, of course (as it is in the code example). However, JustInCase™ your app crashes if for nothing else, it would be best to call UpdateResFile after having made a change to the handle, before only calling CloseResFile at the end.

A progress update:

I've almost finished the entire implementation. Just need to do the method to make a STR# for the ship name. How exactly is the STR# resource structured? I can't seem to find any documentation on it.

P.S. Is it legal to include images and audio from the Nova Files in my application bundle? I'm using the Phoenix Bay as my application icon and the Nova beeps as my success or failure audio feedback. Anyone can download and extract these resources without paying for the program, so does anyone know if this is allowed?

I don't know if you would win a case in court if some big company got mad at you, went mad, and set the RIAA on your ass, but I think if you ask permission, Ambrosia may let you.

@mushu, on May 17 2006, 06:04 PM, said in Resource Editing in Mac OS Cocoa Objective-C:

A progress update:

I've almost finished the entire implementation. Just need to do the method to make a STR# for the ship name. How exactly is the STR# resource structured? I can't seem to find any documentation on it.

(DLNG) number of strings + ( (PSTR) string1, (PSTR) string2, ... )

That is, the STR# resource begins by a 32-bit integer indicating how many strings are there, followed by a list of Pascal strings (that is, a byte containing the length followed by the actual chars); the next Pascal string begins where the previous ends, so you cannot directly access the Nth Pascal string, you have to walk and count them for this.