diff --git a/bonus/docs/IF_XML_metadata.tex b/bonus/docs/IF_XML_metadata.tex index 779caadd..7063f19d 100644 --- a/bonus/docs/IF_XML_metadata.tex +++ b/bonus/docs/IF_XML_metadata.tex @@ -198,13 +198,25 @@ \subsubsection{Glulx identification} \subsubsection{MD5 identification} -Any story format may have an MD5 section. This is a hexadecimal representation of the +Any story format may have an MD5 section instead of a format-specific identification. +This is a hexadecimal representation of the MD5 checksum of a story file. In the case of a Blorb file, this is the MD5 checksum of the executable portion of the file (the Z-Code or Glulx code file that it contains) rather than the entire file. There are several utilities available for generating MD5 checksums, -for example the 'md5' utility that comes with OpenSSL. Any story may have -an section, although the format-specific identification should be used for preference -as it will be easier to write for a story author. +for example the 'md5' utility that comes with OpenSSL. + +A story format that has a format-specific means of identification will probably want to +provide both an MD5 and the format-specific ID tags. + +\subsubsection{Resource identification} + +A 'story' might be considered as spread across several files. Typically, IF systems that do +this distribute files into a 'game' file containing the actual story data, and some kind of +'resources' file containing additional information such as images and sounds. With modern +graphical operating systems, it is desirable that a game resource can be associated with the +game that created it. To enable this, this specification provides several extra identification +formats that identify resources. The contents of the resources module can be used to find +the actual resources associated with a game. \subsection{The metadata tags} diff --git a/src/ifmetabase.c b/src/ifmetabase.c index 3afb26cd..3786d5b6 100644 --- a/src/ifmetabase.c +++ b/src/ifmetabase.c @@ -15,6 +15,8 @@ /* Concrete data structure definitions */ +typedef struct IFMDRecord IFMDRecord; + typedef struct IFMetabaseIndexEntry { IFMDKey key; int entryNumber; @@ -27,7 +29,7 @@ struct IFMetabase { int numModules; int exclusive; /* If 0, modules indicates the modules that are INCLUDED in this metabase, if 1, those that are excluded. Excluded modules are not represented in entries from this metabase */ - IFMDEntry* entries; /* Unordered array of entries */ + IFMDRecord* entries; /* Unordered array of records */ int numEntries; /* Number of entries */ int readOnly; /* If 1, it's an error to do anything that alters this metabase or an entry in it */ @@ -40,22 +42,31 @@ struct IFMDKey { /* Format of the game */ enum IFMDFormat format; - /* ID for specific game types */ + enum { + IFMDKeyZCode, + + IFMDKeyMD5, + IFMDKeyURI + } type; + + /* ID for specific key types */ union { struct { - int hasZCodeID; - unsigned char serial[6]; int release; int checksum; } zcode; + + struct { + unsigned char md5[16]; + } md5; + + struct { + char* uri; + } uri; } specific; - - /* General ID */ - int hasMD5; - unsigned char md5[16]; }; typedef struct IFMDField IFMDField; @@ -74,7 +85,14 @@ struct IFMDField { }; struct IFMDEntry { - IFMetabase belongsTo; /* The metabase this entry belongs to */ + IFMetabase metabase; /* Metabase this entry is from */ + int record; /* Record this entry refers to */ + + IFMDEntry previous; /* Entry in a previous metabase */ +}; + +struct IFMDRecord { + IFMetabase belongsTo; /* The metabase this record belongs to */ IFMDEntry parent; /* If this entry also exists in a parent metabase, contains a pointer to this entry */ IFMDKey* keys; /* The keys that refer to this entry */ @@ -503,6 +521,12 @@ void metabase_add_filter(IFMetabase metabase, const char* module_name) { metabase->modules[storage_pos] = module_namespace; } + +/* Marks a metabase as read-only */ +void metabase_set_readonly(IFMetabase metabase, int isReadOnly) { + metabase->readOnly = isReadOnly!=0; +} + /* Returns 1 if a module is filtered from a particular metabase */ int metabase_is_filtered(IFMetabase metabase, const char* module_name) { const int* module_namespace; @@ -529,29 +553,161 @@ int metabase_is_filtered(IFMetabase metabase, const char* module_name) { return !found; } -/* Describing stories */ +/* Describing stories/story resources */ /* Creates a reference to a story with a specific type and MD5 */ -extern IFMDKey metabase_story_with_md5(enum IFMDFormat format, const char* md5); +IFMDKey metabase_story_with_md5(enum IFMDFormat format, const char* md5) { + IFMDKey res; + int x; + + res = metabase_alloc(sizeof(struct IFMDKey)); + + res->type = IFMDKeyMD5; + res->format = format; -/* Creates a reference to a story with a z-code identification. md5 can be NULL if unknown */ -extern IFMDKey metabase_story_with_zcode(const char* serial, unsigned int release, unsigned int checksum, const char* md5); + for (x=0; x<16; x++) { + res->specific.md5.md5[x] = md5[x]; + } + + return res; +} + +/* Creates a reference to a story existing at a specific URI */ +IFMDKey metabase_story_with_uri(enum IFMDFormat format, const char* uri) { + IFMDKey res; + + res = metabase_alloc(sizeof(struct IFMDKey)); + + res->type = IFMDKeyURI; + res->format = format; + + res->specific.uri.uri = metabase_alloc(sizeof(char)*(strlen(uri)+1)); + strcpy(res->specific.uri.uri, uri); + + return res; +} + +/* Creates a reference to a story with a z-code identification. */ +IFMDKey metabase_story_with_zcode(const char* serial, unsigned int release, unsigned int checksum) { + IFMDKey res; + int x; + + res = metabase_alloc(sizeof(struct IFMDKey)); + + res->type = IFMDKeyURI; + res->format = IFFormat_ZCode; + + for (x=0; x<6; x++) { + res->specific.zcode.serial[x] = serial[x]; + } + res->specific.zcode.release = release; + res->specific.zcode.checksum = checksum; + + return res; +} /* Compares two IFMDKeys */ -extern int metabase_compare_keys(IFMDKey key1, IFMDKey key2); +int metabase_compare_keys(IFMDKey key1, IFMDKey key2) { + int x; + + /* See if key types differ */ + if (key1->type < key2->type) { + return -1; + } else if (key1->type > key2->type) { + return 1; + } + + /* Compare based on the data the key represents */ + switch (key1->type) { + case IFMDKeyMD5: + for (x=0; x<16; x++) { + char k1 = key1->specific.md5.md5[x]; + char k2 = key2->specific.md5.md5[x]; + + if (k1 < k2) { + return -1; + } else if (k1 > k2) { + return 1; + } + } + break; + + case IFMDKeyZCode: + if (key1->specific.zcode.release < key2->specific.zcode.release) { + return -1; + } else if (key1->specific.zcode.release > key2->specific.zcode.release) { + return 1; + } else if (key1->specific.zcode.checksum < key2->specific.zcode.checksum) { + return -1; + } else if (key1->specific.zcode.checksum > key2->specific.zcode.checksum) { + return 1; + } + + for (x=0; x<6; x++) { + unsigned char k1 = key1->specific.zcode.serial[x]; + unsigned char k2 = key2->specific.zcode.serial[x]; + + if (k1 < k2) { + return -1; + } else if (k1 > k2) { + return 1; + } + } + break; + + case IFMDKeyURI: + break; + + default: + metabase_error(IFMDE_InvalidMDKey, "A key of an unknown type was passed to metabase_compare_keys"); + } + + return 0; +} + +/* Gets the format associated with a key */ +enum IFMDFormat metabase_format_for_key(IFMDKey key) { + return key->format; +} /* Storing metadata */ -/* - * Metadata strings are in null-terminated UCS-4. Fields can use '.' to indicate structure (eg foo.bar to indicate - * Data), and '@' to indicate attributes (eg foo@bar for ). - * - * Data fields should be otherwise unstructured. Not all XML structure can be represented: this is deliberate. The - * metabase is XML-like, not actual XML. - */ +/* Compares a IFMetabaseIndexEntry to a IFMDKey */ +static int key_index_to_key_compare(const void* a_indexEntry, const void* b_mdKey) { + const struct IFMetabaseIndexEntry* entry; + const struct IFMDKey* key; + + entry = a_indexEntry; + key = b_mdKey; + + return metabase_compare_keys(entry->key, (IFMDKey)key); +} + +/* Makes a copy of an entry (used to copy entries out of the metabase) */ +void metabase_copy_entry(IFMDEntry dest, IFMDEntry src) { +} /* Gets an entry for a specific key (entries may be created if they don't exist yet) */ -extern IFMDEntry metabase_entry_for_key(IFMetabase metabase, IFMDKey key); +IFMDEntry metabase_entry_for_key(IFMetabase metabase, IFMDKey key) { + IFMDEntry result = NULL; + IFMDEntry thisResult = NULL; + int entryNumber = 0; + + /* Search for the entry in the entry index */ + if (metabase->keyIndex != NULL) { + entryNumber = binary_search((void**)metabase->keyIndex, + key, + metabase->numKeys, + key_index_to_key_compare); + } + + /* See if we've found */ + + return NULL; +} + +/* Given a key with an unknown (or uncertain) format and an entry indexed by that key, retrieves the format (which MAY still be unknown, but really shouldn't be) */ +extern enum IFMDFormat metabase_format_for_entry_key(IFMDEntry entry, IFMDKey unknownKey); /* Associates an additional key with an entry */ extern void metabase_add_key(IFMDEntry entry, IFMDKey newKey); diff --git a/src/ifmetabase.h b/src/ifmetabase.h index 4f561673..e00230db 100644 --- a/src/ifmetabase.h +++ b/src/ifmetabase.h @@ -31,7 +31,8 @@ enum IFMDError { IFMDE_FailedToAllocateMemory, IFMDE_NamespaceAlreadyInUse, IFMDE_ModuleNameAlreadyInUse, - IFMDE_NULLReference + IFMDE_NULLReference, + IFMDE_InvalidMDKey }; extern void metabase_error(enum IFMDError errorCode, const char* simple_description, ...); @@ -93,15 +94,19 @@ extern void metabase_filter(IFMetabase metabase, enum IFMDFilter filter_style); */ extern void metabase_add_filter(IFMetabase metabase, const char* module_name); +/* Marks a metabase as read-only */ +extern void metabase_set_readonly(IFMetabase metabase, int isReadOnly); + /* Describing stories */ /* A key to an entry in the metabase */ typedef struct IFMDKey* IFMDKey; -/* Types of story */ +/* Types of story/resource */ enum IFMDFormat { IFFormat_Unknown = 0x0, + /* Story types */ IFFormat_ZCode, IFFormat_Glulx, @@ -112,7 +117,20 @@ enum IFMDFormat { IFFormat_Level9, IFFormat_AGT, IFFormat_MagScrolls, - IFFormat_AdvSys + IFFormat_AdvSys, + + /* Resource types */ + IFFormat_BlorbResourcesOnly = 0x100, /* Blorb file containing only resources */ + IFFormat_BlorbAndZCode, /* Blorb file containing resources and a ZCode file */ + IFFormat_BlorbAndGlulx, /* Blorb file containing resources and a Glulx file */ + IFFormat_BlorbAndUnknown, /* Blorb file containing resources and some other executable file */ + + IFFormat_Image, /* A single image file */ + IFFormat_Sound, /* A single sound file */ + IFFormat_GameData, /* A file containing miscellaneous game data (maybe some aggregate format other than Blorb) */ + IFFormat_GameFeelie, /* A file containing 'feelies' for a game, for example a PDF documentation file */ + + IFFormat_SaveGame = 0x200 /* A file containing a save game for this story */ }; /* For z-code games with an unknown checksum */ @@ -121,12 +139,21 @@ enum IFMDFormat { /* Creates a reference to a story with a specific type and MD5 */ extern IFMDKey metabase_story_with_md5(enum IFMDFormat format, const char* md5); +/* Creates a reference to a story existing at a specific URI */ +extern IFMDKey metabase_story_with_uri(enum IFMDFormat format, const char* uri); + /* Creates a reference to a story with a z-code identification. md5 can be NULL if unknown */ -extern IFMDKey metabase_story_with_zcode(const char* serial, unsigned int release, unsigned int checksum, const char* md5); +extern IFMDKey metabase_story_with_zcode(const char* serial, unsigned int release, unsigned int checksum); /* Compares two IFMDKeys */ extern int metabase_compare_keys(IFMDKey key1, IFMDKey key2); +/* Gets the format associated with a key */ +extern enum IFMDFormat metabase_format_for_key(IFMDKey key); + +/* Destroys a key */ +extern void metabase_destroy_key(IFMDKey oldKey); + /* Storing metadata */ /* @@ -142,6 +169,9 @@ typedef struct IFMDEntry* IFMDEntry; /* Gets an entry for a specific key (entries may be created if they don't exist yet) */ extern IFMDEntry metabase_entry_for_key(IFMetabase metabase, IFMDKey key); +/* Given a key with an unknown (or uncertain) format and an entry indexed by that key, retrieves the format (which MAY still be unknown, but really shouldn't be) */ +extern enum IFMDFormat metabase_format_for_entry_key(IFMDEntry entry, IFMDKey unknownKey); + /* Associates an additional key with an entry */ extern void metabase_add_key(IFMDEntry entry, IFMDKey newKey); @@ -175,4 +205,7 @@ extern unsigned char* metabase_get_data(IFMDEntry entry, const char* module, con /* Retrieve all the data from a specific field */ extern unsigned char** metabase_get_all_data(IFMDEntry entry, const char* module, const char* field, int* count_out); +/* Destroys an entry object */ +extern void metabase_destroy_entry(IFMDEntry entry); + #endif diff --git a/src/macos/macreadme.rtf b/src/macos/macreadme.rtf index 378f8276..f6acdd68 100644 --- a/src/macos/macreadme.rtf +++ b/src/macos/macreadme.rtf @@ -1,8 +1,7 @@ -{\rtf1\mac\ansicpg10000\cocoartf824\cocoasubrtf100 +{\rtf1\mac\ansicpg10000\cocoartf824\cocoasubrtf110 {\fonttbl\f0\fswiss\fcharset77 Helvetica-Bold;\f1\fswiss\fcharset77 Helvetica;\f2\fmodern\fcharset77 Courier; } {\colortbl;\red255\green255\blue255;} -\vieww9000\viewh9000\viewkind0 \pard\tx1440\tx2880\tx4320\tx5760\tx7200\qc \f0\b\fs48 \cf0 \ul \ulc0 Zoom for Mac OS X @@ -45,7 +44,7 @@ The Mac OS X version of Zoom is by far the most feature-complete version of Zoom - Dynamic window resizing and font changing\ - Speech output\ - Quickdraw or Quartz rendering (the Quartz rendering is somewhat experimental, but looks a lot better than Quickdraw...)\ - - The finder can be used to launch both games and savegame files\ + - The finder can be used to launch both games and saved game files\ \ Some of these features need to be switched on - see the preferences window within Zoom to do this.\ \ diff --git a/src/zoomCocoa/Skein/ZoomSkeinLayout.h b/src/zoomCocoa/Skein/ZoomSkeinLayout.h index 4bc6754b..1d9f061d 100644 --- a/src/zoomCocoa/Skein/ZoomSkeinLayout.h +++ b/src/zoomCocoa/Skein/ZoomSkeinLayout.h @@ -38,6 +38,7 @@ - (void) setRootItem: (ZoomSkeinItem*) item; - (void) setActiveItem: (ZoomSkeinItem*) item; - (void) setSelectedItem: (ZoomSkeinItem*) item; +- (void) highlightSkeinLine: (ZoomSkeinItem*) itemOnLine; - (ZoomSkeinItem*) rootItem; - (ZoomSkeinItem*) activeItem; @@ -47,7 +48,6 @@ - (void) layoutSkein; // Getting layout data - - (int) levels; - (NSArray*) itemsOnLevel: (int) level; - (NSArray*) dataForLevel: (int) level; diff --git a/src/zoomCocoa/Skein/ZoomSkeinView.h b/src/zoomCocoa/Skein/ZoomSkeinView.h index 43326e27..d8e19832 100644 --- a/src/zoomCocoa/Skein/ZoomSkeinView.h +++ b/src/zoomCocoa/Skein/ZoomSkeinView.h @@ -101,4 +101,8 @@ extern NSString* ZoomSkeinItemPboardType; // The transcript - (void) transcriptToPoint: (ZoomSkeinItem*) point; +// Various types of possible error +- (void) cantDeleteActiveBranch; // User attempted to delete an item on the active skein branch (which can't be done) +- (void) cantEditRootItem; // User attemptted to edit the root skein item + @end diff --git a/src/zoomCocoa/Skein/ZoomSkeinView.m b/src/zoomCocoa/Skein/ZoomSkeinView.m index 0eb7a4fa..0e6cf708 100644 --- a/src/zoomCocoa/Skein/ZoomSkeinView.m +++ b/src/zoomCocoa/Skein/ZoomSkeinView.m @@ -782,8 +782,12 @@ - (void) deleteButtonClicked: (NSEvent*) event ZoomSkeinItem* parent = [skein activeItem]; while (parent != nil) { if (parent == skeinItem) { - // Can't delete an item that's the parent of the active item - NSBeep(); // Maybe need some better feedback + if (![delegate respondsToSelector: @selector(cantDeleteActiveBranch)]) { + // Can't delete an item that's the parent of the active item + NSBeep(); + } else { + [delegate cantDeleteActiveBranch]; + } return; } @@ -938,7 +942,12 @@ - (void) editItem: (ZoomSkeinItem*) skeinItem if ([skeinItem parent] == nil) { // Can't edit the root item - NSBeep(); + if (![delegate respondsToSelector: @selector(cantEditRootItem)]) { + NSBeep(); + } else { + [delegate cantEditRootItem]; + } + return; } diff --git a/src/zoomCocoa/ZoomSavePreview.m b/src/zoomCocoa/ZoomSavePreview.m index 04b854ec..00573acb 100644 --- a/src/zoomCocoa/ZoomSavePreview.m +++ b/src/zoomCocoa/ZoomSavePreview.m @@ -221,7 +221,7 @@ - (IBAction) deleteSavegame: (id) sender { NSBeginAlertSheet(@"Are you sure?", @"Keep", @"Delete", nil, nil, self, @selector(confirmDelete:returnCode:contextInfo:), nil, nil, - @"Are you sure you want to delete this savegame?"); + @"Are you sure you want to delete this saved game?"); return; } @@ -254,24 +254,24 @@ - (void) confirmDelete:(NSWindow *)sheet returnCode:(int)returnCode contextInfo: if (![[NSFileManager defaultManager] fileExistsAtPath: saveQut isDirectory: &isDir]) { - genuine = NO; reason = reason?reason:@"Contents do not look like a savegame"; + genuine = NO; reason = reason?reason:@"Contents do not look like a saved game"; } if (isDir) { - genuine = NO; reason = reason?reason:@"Contents do not look like a savegame"; + genuine = NO; reason = reason?reason:@"Contents do not look like a saved game"; } if (![[NSFileManager defaultManager] fileExistsAtPath: zPreview isDirectory: &isDir]) { - genuine = NO; reason = reason?reason:@"Contents do not look like a savegame"; + genuine = NO; reason = reason?reason:@"Contents do not look like a saved game"; } if (isDir) { - genuine = NO; reason = reason?reason:@"Contents do not look like a savegame"; + genuine = NO; reason = reason?reason:@"Contents do not look like a saved game"; } if (![[NSFileManager defaultManager] fileExistsAtPath: status isDirectory: &isDir]) { - genuine = NO; reason = reason?reason:@"Contents do not look like a savegame"; + genuine = NO; reason = reason?reason:@"Contents do not look like a saved game"; } if (isDir) { - genuine = NO; reason = reason?reason:@"Contents do not look like a savegame"; + genuine = NO; reason = reason?reason:@"Contents do not look like a saved game"; } // Report a problem if not genuine diff --git a/src/zoomCocoa/ZoomView.m b/src/zoomCocoa/ZoomView.m index c7aa215e..ecc5f25f 100644 --- a/src/zoomCocoa/ZoomView.m +++ b/src/zoomCocoa/ZoomView.m @@ -1731,7 +1731,7 @@ - (void) setupPanel: (NSSavePanel*) panel } typeCode = 'IFZS'; if (supportsMessage) { - [panel setMessage: [NSString stringWithFormat: @"%@ savegame (quetzal) file", saveOpen]]; + [panel setMessage: [NSString stringWithFormat: @"%@ saved game (quetzal) file", saveOpen]]; [panel setAllowedFileTypes: [NSArray arrayWithObjects: usePackage?@"zoomSave":@"qut", nil]]; } break; diff --git a/src/zoomCocoa/ZoomiFictionController.m b/src/zoomCocoa/ZoomiFictionController.m index 26cc460c..f64845ee 100644 --- a/src/zoomCocoa/ZoomiFictionController.m +++ b/src/zoomCocoa/ZoomiFictionController.m @@ -264,7 +264,7 @@ - (void) windowDidLoad { [commentView setDelegate: self]; [collapseView addSubview: previewView - withTitle: @"Savegames"]; + withTitle: @"Saved games"]; [collapseView addSubview: teaserView withTitle: @"Teaser"]; [collapseView addSubview: commentView