#import "NGSTemplateWindowController.h" #import "NGSElement.h" #import @implementation TemplateWindowController - (id)initWithResource:(id)newResource { return [self initWithResources:newResource, nil]; } - (id)initWithResources:(id)newResource, ... { id currentResource; va_list resourceList; // one instance of your principal class will be created for every resource set the user wants to edit (similar to Windows apps) self = [self initWithWindowNibName:@"TemplateWindow"]; if(!self) return self; resource = [newResource retain]; tmpl = [[NSMutableArray alloc] init]; res = [[NSMutableArray alloc] init]; va_start(resourceList, newResource); [self readTemplate:va_arg(resourceList, id)]; // reads (but doesn't retain) the template for this resource (the TMPL resource with a name equal to the passed resource's type) while(currentResource = va_arg(resourceList, id)) NSLog(@"too many params passed to -initWithResources: %@", [currentResource description]); va_end(resourceList); NSString *warning = [self checkTemplate]; if(warning != nil) { // should display error message here NSLog(warning); return nil; } // load the window from the nib [self window]; return self; } - (void)dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self]; [(id)resource release]; [tmpl release]; [res release]; [super dealloc]; } - (void)windowDidLoad { [super windowDidLoad]; // set the window's title [[self window] setTitle:[resource defaultWindowTitle]]; // parse data using pre-scanned template and create the fields as needed [self readData]; [self createUI]; // insert the resources' data into the text fields [self refreshData:[resource data]]; // we don't want this notification until we have a window! (Only register for notifications on the resource we're editing) [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(resourceDataDidChange:) name:ResourceDataDidChangeNotification object:resource]; // finally, show the window [self showWindow:self]; } - (void)readTemplate:(id )tmplResource { if(tmplResource) { NSString *label, *type; char *currentByte = (char *) [[tmplResource data] bytes]; unsigned long size = [[tmplResource data] length], position = 0; while(position < size) { // check where pointer will be AFTER having loaded this element position += *currentByte +5; if(position >= size) { NSLog(@"Corrupt TMPL resource: not enough data. Dumping remaining resource as hex."); [tmpl addObject:[Element elementOfType:@"HEXD" withLabel:NSLocalizedString(@"Corrupted Template: Hex Dump",nil)]]; break; } // obtain label and type label = [[NSString alloc] initWithBytes:currentByte+1 length:*currentByte encoding:NSMacOSRomanStringEncoding]; currentByte += *currentByte +1; type = [[NSString alloc] initWithBytes:currentByte length:4 encoding:NSMacOSRomanStringEncoding]; currentByte += 4; // add element to array [tmpl addObject:[Element elementOfType:type withLabel:label]]; } } else { // for some reason we got a nill object as the TMPL, do the best we can NSLog(@"Corrupt TMPL resource: no template given. Dumping resource as hex."); [tmpl addObject:[Element elementOfType:@"HEXD" withLabel:NSLocalizedString(@"Corrupted Template: Hex Dump",nil)]]; } } - (NSString *)checkTemplate { NSSet *recognised = [NSSet setWithObjects:nil]; NSMutableSet *fields = [NSMutableSet set]; NSEnumerator *enumerator = [tmpl objectEnumerator]; while(Element *element = [enumerator nextObject]) [fields addObject:[element type]]; // doesn't take into account Pxxx, Cxxx and Hxxx NSSet *unrecognised = [fields copy]; [unrecognised minusSet:recognised]; if([unrecognised count] > 0) return [NSString stringWithFormat:NSLocalizedString(@"The template contains the following unrecognised field types: '%@'",nil), [[unrecognised allObjects] componentsJoinedByString:@"', '"]]; else return nil; } - (void)readData { // tmpl == array of Elements describing the template for this resource // res == array of either Elements or Arrays containing the resource data // this function creates the res instance variable, filling it with data from the resource if there's data available. // unsigned long position = 0; // position (byte offset) in resource I'm currently reading from // char *data = (char *) [[resource data] bytes]; // address of initial byte of resource in memory unsigned long templateCounter = 0; // index into template array of the current template element Element *currentTemplateElement; // current template element NSMutableArray *targetStack = [NSMutableArray arrayWithObject:res]; // stack of arrays (target for addition of new elements) // NSMutableArray *loopStack = [NSMutableArray array]; // stack for 'LSTB' and 'LSTC' elements // NSMutableArray *loopCountStack = [NSMutableArray array]; // stack for counting how many times to loop // when templateCounter >= [tmpl count], loop is only exited if targetStack has more than one target (this handles empty templates) while(templateCounter < [tmpl count] || [targetStack count] > 1) { currentTemplateElement = [tmpl objectAtIndex:templateCounter]; NSLog(@"template = %@", currentTemplateElement); /* unsigned long type = [currentTemplateElement typeAsLong]; switch(type) { case 'BCNT': case 'BZCT': [resourceElement setNumberWithLong:*(unsigned char *)(data + position)]; lim = *(unsigned char *)(data + position) + (type == 'BZCT'? 1:0); position += 1; break; case 'OCNT': case 'ZCNT': [resourceElement setNumberWithLong:*(unsigned short *)(data + position)]; lim = *(unsigned short *)(data + position) + (type == 'ZCNT'? 1:0); position += 2; break; case 'LCNT': case 'LZCT': [resourceElement setNumberWithLong:*(unsigned long *)(data + position)]; lim = *(unsigned long *)(data + position) + (type == 'LZCT'? 1:0); position += 4; break; case 'LSTB': case 'LSTC': [(NSMutableArray *)[targetStack lastObject] addObject:[NSMutableArray array]]; [loopStack addObject:currentTemplateElement]; // append the template loop start object to the array break; default: // [(NSMutableArray *)[targetStack lastObject] addItem:[self createElementForTemplate:currentTemplateElement } */ templateCounter++; } } - (void)parseData { unsigned long position = 0; char *data = (char *) [[resource data] bytes]; // used for nesting of elements, 'target' is current object to append to, targetStack is a FILO stack of mutable array pointers, loopStack is a stack of element indicies to the start of loops, so I can go back to the head of a loop when iterating it NSMutableArray *target = res; NSMutableArray *targetStack = [NSMutableArray arrayWithObject:res]; // NSMutableArray *loopStack = [NSMutableArray array]; // n = current item in TMPL to read, c = loop counter, when exiting loop, go back 'c' items in the template, lim is how many times to loop, obtained from a loop count unsigned long n = 0, c = 0, lim = 0; // creates an array of elements containing the data in whatever format the template dictates // array can then simply be manipulated one element at a time, or flattened to save Element *currentTemplateElement, *resourceElement; // NSEnumerator *enumerator = [tmpl objectEnumerator]; // while(currentTemplateElement = [enumerator nextObject]) while(position < [[resource size] unsignedLongValue]) { unsigned long type; currentTemplateElement = [tmpl objectAtIndex:n]; type = [currentTemplateElement typeAsLong]; resourceElement = [[currentTemplateElement copy] autorelease]; NSLog(@"tmpl element = %@; position = %d", currentTemplateElement, position); n++, c++; switch(type) { /* Alignment */ case 'AWRD': position += position % 2; break; case 'ALNG': position += position % 4; break; /* Fillers */ case 'FBYT': position += 1; break; case 'FWRD': position += 2; break; case 'FLNG': position += 4; break; /* Decimal */ case 'DBYT': [resourceElement setNumberWithLong:*(char *)(data + position)]; position += 1; break; case 'DWRD': [resourceElement setNumberWithLong:*(short *)(data + position)]; position += 2; break; case 'DLNG': [resourceElement setNumberWithLong:*(long *)(data + position)]; position += 4; break; /* Hex */ case 'HBYT': [resourceElement setData:[NSData dataWithBytes:(void *)(data + position) length:1]]; position += 1; break; case 'HWRD': [resourceElement setData:[NSData dataWithBytes:(void *)(data + position) length:2]]; position += 2; break; case 'HLNG': [resourceElement setData:[NSData dataWithBytes:(void *)(data + position) length:4]]; position += 4; break; case 'HEXD': // bug: doesn't check HEXD is the last element [resourceElement setData:[NSData dataWithBytes:(void *)(data + position) length:([[resource size] intValue] - position)]]; position = [[resource size] intValue]; break; /* Strings */ case 'CHAR': [resourceElement setString:[[NSString alloc] initWithData:[NSData dataWithBytes:(void *)(data + position) length:1] encoding:NSMacOSRomanStringEncoding]]; position += 1; break; case 'TNAM': [resourceElement setString:[[NSString alloc] initWithData:[NSData dataWithBytes:(void *)(data + position) length:4] encoding:NSMacOSRomanStringEncoding]]; position += 4; break; case 'PSTR': [resourceElement setString:[[NSString alloc] initWithData:[NSData dataWithBytes:(void *)(data + position + 1) length:*(unsigned char *)(data + position)] encoding:NSMacOSRomanStringEncoding]]; position += *(unsigned char *)(data + position) + 1; break; /* List Counts */ case 'BCNT': case 'BZCT': [resourceElement setNumberWithLong:*(unsigned char *)(data + position)]; lim = *(unsigned char *)(data + position) + (type == 'BZCT'? 1:0); position += 1; break; case 'OCNT': case 'ZCNT': case 'WCNT': case 'WZCT': [resourceElement setNumberWithLong:*(unsigned short *)(data + position)]; lim = *(unsigned short *)(data + position) + (type == 'ZCNT'? 1:0); position += 2; break; case 'LCNT': case 'LZCT': [resourceElement setNumberWithLong:*(unsigned long *)(data + position)]; lim = *(unsigned long *)(data + position) + (type == 'LZCT'? 1:0); position += 4; break; /* List beginning and end */ case 'LSTB': case 'LSTC': [target addObject:resourceElement]; // add list item to current target array // target = [resourceElement subelements]; // change current array to list's sub-elements [targetStack addObject:target]; // append sub-element array to target stack so it can be popped off afterwards resourceElement = nil; // don't add item to it's own sub-elements later! break; case 'LSTE': // bug: if there is a LSTE without a preceeding LSTB or LSTC this will crash [targetStack removeLastObject]; // pop off current target from stack target = [targetStack lastObject]; // set current target to whatever was second from top on the stack resourceElement = nil; // list end items are not needed in a resource array if(n < lim) n -= c; c = 0; break; /* Cxxx, Hxxx or P0xx */ default: // bug: should look for Cxxx, Hxxx or P0xx and complain if it's something else (an unknown type)!! {/* long lengthStr = (type & 0x00FFFFFF) << 8; unsigned long length = strtoul((char *) &lengthStr, nil, 10); char *lengthStr = (type & 0x00FFFFFF) & (3 << 24); unsigned long length; StringToNum(lengthStr, &length); NSLog(@"error, '%@' is unsupported, skipping %d bytes", [resourceElement type], length); resourceElement = nil; // relies on element being previously autoreleased to avoid a leak position += length;*/ } break; } // end template element type switch if(resourceElement) { NSLog(@"adding %@", resourceElement); [target addObject:resourceElement]; } } // end while position < size NSLog([target description]); } - (void)createUI { // iterate through res (the resource element array) creating fields [self enumerateElements:res]; } - (void)enumerateElements:(NSMutableArray *)elements { // iterate through the array of resource elements, creating fields Element *currentResourceElement; NSEnumerator *enumerator = [elements objectEnumerator]; NSLog(@"elements in resource array = %d", [elements count]); while(currentResourceElement = [enumerator nextObject]) { // if element is a container (subelements != nil), iterate inside it first /* if([currentResourceElement subelements]) { // bug: need to indent view right [self enumerateElements:[currentResourceElement subelements]]; // bug: need to remove indentation } else // element is normal */ { /* NSFormCell *newField = [[NSFormCell alloc] initTextCell:[currentResourceElement label]]; [fieldsMatrix addRowWithCells:[NSArray arrayWithObject:[newField autorelease]]]; */ NSLog([currentResourceElement description]); } } } - (void)resourceDataDidChange:(NSNotification *)notification { // ensure it's our resource which got changed (should always be true, we don't register for notifications on other resource objects) if([notification object] == (id)resource) [self refreshData:[resource data]]; } - (void)refreshData:(NSData *)data; { // put data from resource into correct fields } - (id)resource { return resource; } - (NSData *)data { return [resource data]; } @end