American spelling, aligned declarations. Resources a directory lists but the game's files lack count as absent, as with Sierra's interpreter.
This commit is contained in:
parent
850181d8a9
commit
65c380555f
3 changed files with 89 additions and 45 deletions
1
agi.h
1
agi.h
|
|
@ -464,6 +464,7 @@ bool agiQuirk(AgiQuirkE quirk);
|
|||
void agiResClose(AgiGameT *game);
|
||||
uint8_t *agiResLoad(const AgiGameT *game, AgiResTypeE type, uint16_t index, uint16_t *outLength);
|
||||
bool agiResOpen(AgiGameT *game);
|
||||
bool agiResPresent(const AgiGameT *game, AgiResTypeE type, uint16_t index);
|
||||
uint16_t agiWordMatch(const AgiGameT *game, const char *text, uint8_t *outLen);
|
||||
|
||||
|
||||
|
|
|
|||
67
agiRes.c
67
agiRes.c
|
|
@ -64,6 +64,7 @@ static uint16_t detectVersion(void);
|
|||
static void loadDirectory(AgiGameT *game, const char *fileName, AgiResTypeE type);
|
||||
static uint8_t *loadFile(const char *name, uint16_t maxBytes, uint16_t *outLength);
|
||||
static bool loadObjects(AgiGameT *game);
|
||||
static bool resHeaderRead(const AgiGameT *game, AgiResTypeE type, uint16_t index, uint8_t *header);
|
||||
|
||||
|
||||
// ----- Tables -----
|
||||
|
|
@ -237,6 +238,38 @@ static bool loadObjects(AgiGameT *game) {
|
|||
}
|
||||
|
||||
|
||||
// Seek to resource index's header in its VOL file and read it: false when
|
||||
// the number is past its directory or unused, or its entry names a VOL file
|
||||
// the game lacks or a place in one that holds no header for that volume
|
||||
// (12 34, then the entry's own volume). A directory can list a resource its
|
||||
// VOL files do not hold -- King's Quest 1's sounds 34 to 37 point past the
|
||||
// end of VOL.2, Space Quest 1's sound 0 into a VOL.15 -- which Sierra's
|
||||
// interpreter, reading a resource only when a script loads it, meets only
|
||||
// then.
|
||||
static bool resHeaderRead(const AgiGameT *game, AgiResTypeE type, uint16_t index, uint8_t *header) {
|
||||
const AgiResEntryT *entry;
|
||||
FILE *fp;
|
||||
long offset;
|
||||
|
||||
if (index >= game->resCount[type]) {
|
||||
return false;
|
||||
}
|
||||
entry = &game->resDir[type][index];
|
||||
if (entry->volume >= AGI_MAX_VOLUMES) {
|
||||
return false;
|
||||
}
|
||||
fp = game->volFiles[entry->volume];
|
||||
if (fp == NULL) {
|
||||
return false;
|
||||
}
|
||||
offset = ((long)entry->offsetHigh << DIR_OFFSET_HIGH_SHIFT) | (long)entry->offsetLow;
|
||||
if (fseek(fp, offset, SEEK_SET) != 0 || fread(header, 1, VOL_HEADER_BYTES, fp) != VOL_HEADER_BYTES) {
|
||||
return false;
|
||||
}
|
||||
return header[0] == VOL_SIG_0 && header[1] == VOL_SIG_1 && (header[2] & VOL_VOLUME_MASK) == entry->volume;
|
||||
}
|
||||
|
||||
|
||||
// ----- Public API (alphabetical) -----
|
||||
|
||||
// Undo Sierra's XOR with "Avis Durgan" (OBJECT, and LOGIC messages from
|
||||
|
|
@ -297,38 +330,19 @@ void agiResClose(AgiGameT *game) {
|
|||
|
||||
|
||||
uint8_t *agiResLoad(const AgiGameT *game, AgiResTypeE type, uint16_t index, uint16_t *outLength) {
|
||||
const AgiResEntryT *entry;
|
||||
FILE *fp;
|
||||
uint8_t header[VOL_HEADER_BYTES];
|
||||
uint8_t *payload;
|
||||
uint16_t length;
|
||||
long offset;
|
||||
|
||||
if (index >= game->resCount[type]) {
|
||||
return NULL;
|
||||
}
|
||||
entry = &game->resDir[type][index];
|
||||
if (entry->volume >= AGI_MAX_VOLUMES) {
|
||||
return NULL;
|
||||
}
|
||||
fp = game->volFiles[entry->volume];
|
||||
if (fp == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
offset = ((long)entry->offsetHigh << DIR_OFFSET_HIGH_SHIFT) | (long)entry->offsetLow;
|
||||
if (fseek(fp, offset, SEEK_SET) != 0) {
|
||||
return NULL;
|
||||
}
|
||||
if (fread(header, 1, VOL_HEADER_BYTES, fp) != VOL_HEADER_BYTES) {
|
||||
return NULL;
|
||||
}
|
||||
if (header[0] != VOL_SIG_0 || header[1] != VOL_SIG_1) {
|
||||
if (!resHeaderRead(game, type, index, header)) {
|
||||
return NULL;
|
||||
}
|
||||
// v3 resources are LZW-compressed and flag it in the volume byte.
|
||||
if ((header[2] & VOL_COMPRESSED) != 0u || (header[2] & VOL_VOLUME_MASK) != entry->volume) {
|
||||
if ((header[2] & VOL_COMPRESSED) != 0u) {
|
||||
return NULL;
|
||||
}
|
||||
fp = game->volFiles[game->resDir[type][index].volume];
|
||||
length = (uint16_t)(header[3] | ((uint16_t)header[4] << 8));
|
||||
if (length == 0u) {
|
||||
return NULL;
|
||||
|
|
@ -389,6 +403,15 @@ bool agiResOpen(AgiGameT *game) {
|
|||
}
|
||||
|
||||
|
||||
// Whether resource index of type is in the game's files: its entry points at
|
||||
// a resource header in its VOL file (resHeaderRead).
|
||||
bool agiResPresent(const AgiGameT *game, AgiResTypeE type, uint16_t index) {
|
||||
uint8_t header[VOL_HEADER_BYTES];
|
||||
|
||||
return resHeaderRead(game, type, index, header);
|
||||
}
|
||||
|
||||
|
||||
// Find the longest dictionary entry that starts `text` and ends at a word
|
||||
// boundary (the end of text or a space). Entries can hold several words
|
||||
// ("pick up"). text is lower case with single spaces. Returns the word's
|
||||
|
|
|
|||
|
|
@ -5,7 +5,10 @@
|
|||
// hashes, a view's loop and cel counts with a hash of its decoded cels, a
|
||||
// logic's code and message counts, a sound's note counts -- for
|
||||
// scripts/test-agi.sh to compare, and a summary line on stderr. Exits 1
|
||||
// when a resource is malformed.
|
||||
// when a resource is malformed. A directory entry with no resource in the
|
||||
// game's files (agiResPresent) is listed as absent, not malformed: Sierra's
|
||||
// interpreter reads a resource only when a script loads it, and shipped
|
||||
// games list some they do not hold (King's Quest 1's sounds 34 to 37).
|
||||
//
|
||||
// Run on the host (BLANK port) from a folder holding DATA/.
|
||||
|
||||
|
|
@ -29,6 +32,10 @@
|
|||
#define MAX_CEL_PIXELS (255u * 255u)
|
||||
|
||||
|
||||
// What the lines call each resource type, by AgiResTypeE.
|
||||
static const char *const kTypeNames[AGI_RES_COUNT] = { "logic", "picture", "view", "sound" };
|
||||
|
||||
|
||||
// ----- Prototypes -----
|
||||
|
||||
static uint32_t fnv(uint32_t hash, const uint8_t *p, uint32_t length);
|
||||
|
|
@ -185,6 +192,8 @@ int main(void) {
|
|||
uint8_t *pixels;
|
||||
uint16_t n;
|
||||
uint16_t counts[AGI_RES_COUNT];
|
||||
uint16_t absent;
|
||||
uint8_t t;
|
||||
bool ok;
|
||||
|
||||
memset(&cfg, 0, sizeof(cfg));
|
||||
|
|
@ -209,26 +218,37 @@ int main(void) {
|
|||
return 1;
|
||||
}
|
||||
memset(counts, 0, sizeof(counts));
|
||||
absent = 0u;
|
||||
ok = true;
|
||||
for (n = 0u; n < AGI_MAX_RESOURCES; n++) {
|
||||
if (n < gAgi->game.resCount[AGI_RES_LOGIC] && gAgi->game.resDir[AGI_RES_LOGIC][n].volume < AGI_MAX_VOLUMES) {
|
||||
for (t = 0u; t < AGI_RES_COUNT; t++) {
|
||||
if (n >= gAgi->game.resCount[t] || gAgi->game.resDir[t][n].volume >= AGI_MAX_VOLUMES) {
|
||||
continue;
|
||||
}
|
||||
if (!agiResPresent(&gAgi->game, (AgiResTypeE)t, n)) {
|
||||
fprintf(stderr, "%s %u: listed, but not in the game's files\n", kTypeNames[t], (unsigned)n);
|
||||
printf("%s %u absent\n", kTypeNames[t], (unsigned)n);
|
||||
absent++;
|
||||
continue;
|
||||
}
|
||||
switch ((AgiResTypeE)t) {
|
||||
case AGI_RES_LOGIC:
|
||||
ok = testLogic(n) && ok;
|
||||
counts[AGI_RES_LOGIC]++;
|
||||
}
|
||||
if (n < gAgi->game.resCount[AGI_RES_PIC] && gAgi->game.resDir[AGI_RES_PIC][n].volume < AGI_MAX_VOLUMES) {
|
||||
break;
|
||||
case AGI_RES_PIC:
|
||||
ok = testPicture(n, visual, priority) && ok;
|
||||
counts[AGI_RES_PIC]++;
|
||||
}
|
||||
if (n < gAgi->game.resCount[AGI_RES_VIEW] && gAgi->game.resDir[AGI_RES_VIEW][n].volume < AGI_MAX_VOLUMES) {
|
||||
break;
|
||||
case AGI_RES_VIEW:
|
||||
ok = testView(n, pixels) && ok;
|
||||
counts[AGI_RES_VIEW]++;
|
||||
}
|
||||
if (n < gAgi->game.resCount[AGI_RES_SOUND] && gAgi->game.resDir[AGI_RES_SOUND][n].volume < AGI_MAX_VOLUMES) {
|
||||
break;
|
||||
case AGI_RES_SOUND:
|
||||
default:
|
||||
ok = testSound(n) && ok;
|
||||
counts[AGI_RES_SOUND]++;
|
||||
break;
|
||||
}
|
||||
counts[t]++;
|
||||
}
|
||||
}
|
||||
fprintf(stderr, "%u logics, %u pictures, %u views, %u sounds%s\n", (unsigned)counts[AGI_RES_LOGIC], (unsigned)counts[AGI_RES_PIC],
|
||||
(unsigned)counts[AGI_RES_VIEW], (unsigned)counts[AGI_RES_SOUND], ok ? "" : " -- MALFORMED (see above)");
|
||||
fprintf(stderr, "%u logics, %u pictures, %u views, %u sounds, %u absent%s\n", (unsigned)counts[AGI_RES_LOGIC], (unsigned)counts[AGI_RES_PIC], (unsigned)counts[AGI_RES_VIEW], (unsigned)counts[AGI_RES_SOUND], (unsigned)absent, ok ? "" : " -- MALFORMED (see above)");
|
||||
return ok ? 0 : 1;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue