/* ScummVM - Scumm Interpreter * Copyright (C) 2005-2006 The ScummVM project * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * $URL$ * $Id$ * * This is a utility for extracting needed resource data from the Lure of the Temptress * lure.exe file into a new file lure.dat - this file is required for the ScummVM * Lure of the Temptress module to work properly * * TODO: * Some of the field values, such as hotspot tick proc offsets, will vary with * different language versions. These will need to be remapped to a language independant * value once I've fully implemented the English version. * Some areas of the data segment are still to be decoded */ #include #include #include #include "create_lure_dat.h" File lure_exe; uint16 animOffsets[MAX_NUM_ANIM_RECORDS]; int animIndex = 0; uint16 actionOffsets[MAX_NUM_ACTION_RECORDS]; int actionIndex = 0; void add_anim_record(uint16 offset) { for (int ctr = 0; ctr < animIndex; ++ctr) if (animOffsets[ctr] == offset) return; if (animIndex == MAX_NUM_ANIM_RECORDS) { printf("Animation record offset table size exceeded\n"); exit(1); } animOffsets[animIndex++] = offset; } void add_action_list(uint16 offset) { for (int ctr = 0; ctr < actionIndex; ++ctr) if (actionOffsets[ctr] == offset) return; if (actionIndex == MAX_NUM_ACTION_RECORDS) { printf("Action record offset table size exceeded\n"); exit(1); } actionOffsets[actionIndex++] = offset; } void read_basic_palette(byte *&data, uint16 &totalSize) { totalSize = PALETTE_SIZE; lure_exe.seek(PALETTE_OFFSET); data = (byte *) malloc(PALETTE_SIZE); lure_exe.read(data, totalSize); } #define ALT_PALETTE_1 0x1757 #define ALT_PALETTE_1_SIZE 180 #define ALT_PALETTE_2 0x180B #define ALT_PALETTE_2_SIZE 24 void read_replacement_palette(byte *&data, uint16 &totalSize) { totalSize = ALT_PALETTE_1_SIZE + ALT_PALETTE_2_SIZE; data = (byte *) malloc(totalSize); lure_exe.seek(DATA_SEGMENT + ALT_PALETTE_1); lure_exe.read(data, ALT_PALETTE_1_SIZE); lure_exe.seek(DATA_SEGMENT + ALT_PALETTE_2); lure_exe.read(data + ALT_PALETTE_1_SIZE, ALT_PALETTE_2_SIZE); } void read_dialog_data(byte *&data, uint16 &totalSize) { totalSize = DIALOG_SIZE; lure_exe.seek(DIALOG_OFFSET); data = (byte *) malloc(DIALOG_SIZE); lure_exe.read(data, totalSize); } void read_talk_dialog_data(byte *&data, uint16 &totalSize) { totalSize = TALK_DIALOG_SIZE; lure_exe.seek(TALK_DIALOG_OFFSET, SEEK_SET); data = (byte *) malloc(TALK_DIALOG_SIZE); lure_exe.read(data, totalSize); } void read_room_data(byte *&data, uint16 &totalSize) { data = (byte *) malloc(MAX_DATA_SIZE); memset(data, 0, MAX_DATA_SIZE); uint16 *offsetPtr = (uint16 *) data; uint16 offset = (ROOM_NUM_ENTRIES + 1) * sizeof(uint16); uint16 pixelOffset; RoomResource buffer; int outputIndex = 0; RoomHeaderEntry headerEntry; RoomRectIn bounds; for (int index = 0; index < ROOM_NUM_ENTRIES; ++index) { lure_exe.seek(DATA_SEGMENT + ROOM_TABLE + index * 9); lure_exe.read(&headerEntry, sizeof(RoomHeaderEntry)); if ((FROM_LE_16(headerEntry.offset) != 0) && (FROM_LE_16(headerEntry.offset) != 0xffff) && (FROM_LE_16(headerEntry.roomNumber) != 0xc09) && (FROM_LE_16(headerEntry.roomNumber) != 0)) { // Store offset of room entry *offsetPtr++ = TO_LE_16(offset); // Copy over basic room details lure_exe.seek(DATA_SEGMENT + headerEntry.offset); lure_exe.read(&buffer, sizeof(RoomResource)); RoomResourceOutput *rec = (RoomResourceOutput *) (data + offset); rec->hdrFlags = headerEntry.hdrFlags; rec->actions = FROM_LE_32(buffer.actions); rec->roomNumber = headerEntry.roomNumber; rec->descId = headerEntry.descId; rec->numLayers = buffer.numLayers; memcpy(rec->layers, buffer.layers, 8); rec->sequenceOffset = buffer.sequenceOffset; rec->clippingXStart = TO_LE_16(FROM_LE_16(buffer.clippingXStart) - 0x80); rec->clippingXEnd = (FROM_LE_16(buffer.clippingXEnd) == 0) ? 0 : TO_LE_16(FROM_LE_16(buffer.clippingXEnd) - 0x80); rec->numExits = 0; offset += sizeof(RoomResourceOutput); // Copy over room exits for (;;) { RoomResourceExit1 *p = (RoomResourceExit1 *) (data + offset); lure_exe.read(p, sizeof(RoomResourceExit1)); if (FROM_LE_16(p->xs) == 0xffff) break; rec->numExits = TO_LE_16(FROM_LE_16(rec->numExits) + 1); p->xs = TO_LE_16(FROM_LE_16(p->xs) - 0x80); p->ys = TO_LE_16(FROM_LE_16(p->ys) - 0x80); p->xe = TO_LE_16(FROM_LE_16(p->xe) - 0x80); p->ye = TO_LE_16(FROM_LE_16(p->ye) - 0x80); offset += sizeof(RoomResourceExit1); RoomResourceExit2 *p2 = (RoomResourceExit2 *) (data + offset); if (FROM_LE_16(p->sequenceOffset) == 0xffff) { lure_exe.read(p2, sizeof(RoomResourceExit2)); p2->newRoomX = TO_LE_16(FROM_LE_16(p2->newRoomX) - 0x80); p2->newRoomY = TO_LE_16(FROM_LE_16(p2->newRoomY) - 0x80); } else { p2->newRoom = 0; p2->direction = 0; p2->newRoomX = 0; p2->newRoomY = 0; } offset += sizeof(RoomResourceExit2); } // Handle the random destination walk bounds for the room lure_exe.seek(DATA_SEGMENT + WALK_AREAS_OFFSET + buffer.walkBoundsIndex * sizeof(RoomRectIn)); lure_exe.read(&bounds, sizeof(RoomRectIn)); rec->walkBounds.xs = TO_LE_16(FROM_LE_16(bounds.xs) - 0x80); rec->walkBounds.xe = TO_LE_16(FROM_LE_16(bounds.xe) - 0x80); rec->walkBounds.ys = TO_LE_16(FROM_LE_16(bounds.ys) - 0x80); rec->walkBounds.ye = TO_LE_16(FROM_LE_16(bounds.ye) - 0x80); // If the room has a default pixel blocks list, add the references if (buffer.pixelListOffset != 0) { lure_exe.seek(DATA_SEGMENT + FROM_LE_16(buffer.pixelListOffset)); pixelOffset = lure_exe.readWord(); while (pixelOffset != 0) { add_anim_record(pixelOffset); pixelOffset = lure_exe.readWord(); } } } } WRITE_LE_UINT16(offsetPtr, 0xffff); totalSize = offset; } void read_hotspot_data(byte *&data, uint16 &totalSize) { uint16 offsets[4] = {0x5d98, 0x5eb8, 0x623e, 0x63b1}; uint16 startId[4] = {0x3e8, 0x408, 0x2710, 0x7530}; int walkNumEntries = 0; int walkCtr; int numEntries; HotspotWalkToRecord rec; HotspotWalkToRecord *walkList; HotspotHeaderEntry entryHeader; HotspotResource entry; uint16 dataSize; HotspotResourceOutput *r; CurrentActionInput action; // Allocate enough space for output hotspot list data = (byte *) malloc(MAX_HOTSPOTS * sizeof(HotspotResourceOutput)); // Determine number of hotspot walk to entries lure_exe.seek(DATA_SEGMENT + HOTSPOT_WALK_TO_OFFSET); do { ++walkNumEntries; lure_exe.read(&rec, sizeof(HotspotWalkToRecord)); } while (TO_LE_16(rec.hotspotId) != 0); --walkNumEntries; dataSize = walkNumEntries * sizeof(HotspotWalkToRecord); walkList = (HotspotWalkToRecord *) malloc(dataSize); lure_exe.seek(DATA_SEGMENT + HOTSPOT_WALK_TO_OFFSET); lure_exe.read(walkList, sizeof(HotspotWalkToRecord) * walkNumEntries); // Main code for creating the hotspot list r = (HotspotResourceOutput *) data; numEntries = 0; for (int tableNum = 0; tableNum < 4; ++tableNum) { uint16 hotspotIndex = 0; for (;;) { lure_exe.seek(DATA_SEGMENT + offsets[tableNum] + hotspotIndex * 9); lure_exe.read(&entryHeader, sizeof(HotspotHeaderEntry)); if (FROM_LE_16(entryHeader.offset) == 0xffff) break; memset(r, 0, sizeof(HotspotResourceOutput)); r->hotspotId = TO_LE_16(startId[tableNum] + hotspotIndex); r->nameId = entryHeader.resourceId; r->descId = entryHeader.descId; //TO_LE_16(FROM_LE_16(entryHeader.descId) & 0x1fff); r->descId2 = entryHeader.descId2; //TO_LE_16(FROM_LE_16(entryHeader.descId2) & 0x1fff); r->hdrFlags = entryHeader.hdrFlags; // Get the hotspot data lure_exe.seek(DATA_SEGMENT + entryHeader.offset); lure_exe.read(&entry, sizeof(HotspotResource)); r->actions = entry.actions; r->roomNumber = entry.roomNumber; r->startX = TO_LE_16(FROM_LE_16(entry.startX) - 0x80); r->startY = TO_LE_16(FROM_LE_16(entry.startY) - 0x80); r->width = entry.width; r->height = entry.height; r->widthCopy = entry.widthCopy; r->heightCopy = entry.heightCopy; r->yCorrection = entry.yCorrection; r->talkX = entry.talkX; r->talkY = entry.talkY; r->characterMode = entry.characterMode; r->delayCtr = entry.delayCtr; r->tickSequenceOffset = entry.tickSequenceOffset; r->layer = entry.layer; r->scriptLoadFlag = entry.scriptLoadFlag; r->loadOffset = entry.loadOffset; r->colourOffset = entry.colourOffset; r->sequenceOffset = entry.sequenceOffset; r->tickProcOffset = entry.tickProcOffset; r->flags = entry.flags; // Find the walk-to coordinates for the hotspot walkCtr = 0; while ((walkCtr < walkNumEntries) && (FROM_LE_16(walkList[walkCtr].hotspotId) != FROM_LE_16(r->hotspotId))) ++walkCtr; if (walkCtr == walkNumEntries) { r->walkX = 0; r->walkY = 0; } else { r->walkX = TO_LE_16(FROM_LE_16(walkList[walkCtr].x) - 0x80); uint16 y = FROM_LE_16(walkList[walkCtr].y); r->walkY = TO_LE_16((y & 0x8000) | (uint16) ((int16) (y & 0x7fff) - 0x80)); } // Use the offset of the animation data as a dummy Id for the data r->animRecordId = entry.animOffset; r->tickTimeout = entry.tickTimeout; add_anim_record(FROM_LE_16(entry.animOffset)); // Add in the actions offset table r->actionsOffset = entry.actionsOffset; if (FROM_LE_16(entry.actionsOffset) != 0) add_action_list(FROM_LE_16(entry.actionsOffset)); if (FROM_LE_16(r->hotspotId) >= 0x408) { // Hotspot is not an NPC r->npcSchedule = 0; } else { // Check for an NPC schedule lure_exe.seek(DATA_SEGMENT + entryHeader.offset + 0x63); lure_exe.read(&action, sizeof(CurrentActionInput)); if (action.action != 2) r->npcSchedule = 0; else { r->npcSchedule = get_sequence_index(FROM_LE_16(action.dataOffset)); } } ++hotspotIndex; ++r; ++numEntries; if (numEntries == MAX_HOTSPOTS) { printf("Ran out of stack spaces for hotspot copying\n"); exit(1); } } } r->hotspotId = TO_LE_16(0xffff); totalSize = numEntries * sizeof(HotspotResourceOutput) + 2; // Dispose of hotspot walk-to co-ordinate list free(walkList); } void read_hotspot_override_data(byte *&data, uint16 &totalSize) { lure_exe.seek(DATA_SEGMENT + HOTSPOT_OVERRIDE_OFFSET); int numOverrides = 0; HotspotOverride rec; // Determine number of hotspot overrides do { ++numOverrides; lure_exe.read(&rec, sizeof(HotspotOverride)); } while (FROM_LE_16(rec.hotspotId) != 0); --numOverrides; // Prepare output data and read in all entries at once totalSize = numOverrides * sizeof(HotspotOverride) + 2; data = (byte *) malloc(totalSize); lure_exe.seek(DATA_SEGMENT + HOTSPOT_OVERRIDE_OFFSET); lure_exe.read(data, totalSize - 2); WRITE_LE_UINT16(data + totalSize - 2, 0xffff); // Post-process the coordinates HotspotOverride *p = (HotspotOverride *) data; for (int overrideCtr = 0; overrideCtr < numOverrides; ++overrideCtr, ++p) { p->xs = TO_LE_16(FROM_LE_16(p->xs) - 0x80); p->xe = TO_LE_16(FROM_LE_16(p->xe) - 0x80); p->ys = TO_LE_16(FROM_LE_16(p->ys) - 0x80); p->ye = TO_LE_16(FROM_LE_16(p->ye) - 0x80); } } void read_room_exits(byte *&data, uint16 &totalSize) { RoomExitHotspotRecord rec; uint16 offsets[NUM_ROOM_EXITS]; uint16 numEntries[NUM_ROOM_EXITS]; int roomCtr; totalSize = (NUM_ROOM_EXITS + 1) * sizeof(uint16); lure_exe.seek(DATA_SEGMENT + ROOM_EXITS_OFFSET); for (roomCtr = 0; roomCtr < NUM_ROOM_EXITS; ++roomCtr) offsets[roomCtr] = lure_exe.readWord(); // First loop to find total of room exit records there are for (roomCtr = 0; roomCtr < NUM_ROOM_EXITS; ++roomCtr) { numEntries[roomCtr] = 0; if (offsets[roomCtr] == 0) continue; // Get number of exits for the room lure_exe.seek(DATA_SEGMENT + offsets[roomCtr]); lure_exe.read(&rec, sizeof(RoomExitHotspotRecord)); while (FROM_LE_16(rec.xs) != 0) { totalSize += sizeof(RoomExitHotspotOutputRecord); numEntries[roomCtr]++; lure_exe.read(&rec, sizeof(RoomExitHotspotRecord)); } totalSize += sizeof(uint16); // save room for room list end flag } // Alloacte the total needed space data = (byte *) malloc(totalSize); uint16 *offset = (uint16 *) data; uint16 destIndex = (NUM_ROOM_EXITS + 1) * sizeof(uint16); uint16 entryCtr; // Loop to build up the result table for (roomCtr = 0; roomCtr < NUM_ROOM_EXITS; ++roomCtr) { if (offsets[roomCtr] == 0) { *offset++ = 0; // No entries } else { // Read in the entries for the room *offset++ = TO_LE_16(destIndex); RoomExitHotspotOutputRecord *destP = (RoomExitHotspotOutputRecord *) (data + destIndex); lure_exe.seek(DATA_SEGMENT + offsets[roomCtr]); for (entryCtr = 0; entryCtr < numEntries[roomCtr]; ++entryCtr, ++destP) { lure_exe.read(&rec, sizeof(RoomExitHotspotRecord)); // Copy over the record destP->xs = TO_LE_16(FROM_LE_16(rec.xs) - 0x80); destP->xe = TO_LE_16(FROM_LE_16(rec.xe) - 0x80); destP->ys = TO_LE_16(FROM_LE_16(rec.ys) - 0x80); destP->ye = TO_LE_16(FROM_LE_16(rec.ye) - 0x80); destP->hotspotId = rec.hotspotId; destP->cursorNum = rec.cursorNum; destP->destRoomNumber = rec.destRoomNumber; } destIndex += numEntries[roomCtr] * sizeof(RoomExitHotspotOutputRecord); WRITE_LE_UINT16(data + destIndex, 0xffff); destIndex += sizeof(uint16); } } WRITE_LE_UINT16(offset, 0xffff); } void read_room_exit_joins(byte *&data, uint16 &totalSize) { RoomExitHotspotJoinRecord rec, *p; lure_exe.seek(DATA_SEGMENT + ROOM_EXIT_JOINS_OFFSET); int numRecords = 0; uint32 unused; lure_exe.seek(DATA_SEGMENT + ROOM_EXIT_JOINS_OFFSET); do { lure_exe.read(&rec, sizeof(RoomExitHotspotJoinRecord)); lure_exe.read(&unused, sizeof(uint32)); ++numRecords; } while (FROM_LE_16(rec.hotspot1Id) != 0); --numRecords; // Allocate the data and read in all the records totalSize = (numRecords * sizeof(RoomExitHotspotJoinRecord)) + 2; data = (byte *) malloc(totalSize); lure_exe.seek(DATA_SEGMENT + ROOM_EXIT_JOINS_OFFSET); p = (RoomExitHotspotJoinRecord *) data; for (int recordCtr = 0; recordCtr < numRecords; ++recordCtr) { lure_exe.read(p, sizeof(RoomExitHotspotJoinRecord)); lure_exe.read(&unused, sizeof(uint32)); ++p; } WRITE_LE_UINT16(p, 0xffff); } // This next method reads in the animation and movement data. At the moment I // figure out which animations have valid movement record sets by finding // animations whose four direction offsets are near each other. There's // probably a better method than this, but it'll do for now void read_anim_data(byte *&data, uint16 &totalSize) { // Add special pixel records add_anim_record(0x5c95); // Get the animation data records AnimRecord inRec; MovementRecord move; MovementRecord *destMove; uint16 offset, moveOffset; uint16 startOffset; int ctr, dirCtr; int movementSize = 0; bool *includeAnim = (bool *) malloc(animIndex); // Loop to figure out the total number of movement records there are for (ctr = 0; ctr < animIndex; ++ctr) { lure_exe.seek(DATA_SEGMENT + animOffsets[ctr]); lure_exe.read(&inRec, sizeof(AnimRecord)); if ((FROM_LE_16(inRec.leftOffset) < 0x5000) || (FROM_LE_16(inRec.rightOffset) < 0x5000) || (abs(FROM_LE_16(inRec.leftOffset)-FROM_LE_16(inRec.rightOffset)) > 0x800) || (abs(FROM_LE_16(inRec.rightOffset)-FROM_LE_16(inRec.upOffset)) > 0x800) || (abs(FROM_LE_16(inRec.upOffset)-FROM_LE_16(inRec.downOffset)) > 0x800)) { // Animation doesn't have valid movement data includeAnim[ctr] = false; } else { includeAnim[ctr] = true; for (dirCtr=0; dirCtr<4; ++dirCtr) { switch (dirCtr) { case 0: offset = FROM_LE_16(inRec.leftOffset); break; case 1: offset = FROM_LE_16(inRec.rightOffset); break; case 2: offset = FROM_LE_16(inRec.upOffset); break; default: offset = FROM_LE_16(inRec.downOffset); } if (offset != 0) { lure_exe.seek(DATA_SEGMENT + offset); lure_exe.read(&move, sizeof(MovementRecord)); while (FROM_LE_16(move.frameNumber) != 0xffff) { movementSize += sizeof(MovementRecord); lure_exe.read(&move, sizeof(MovementRecord)); } movementSize += 2; } } } } totalSize = animIndex * sizeof(AnimRecordOutput) + 2 + movementSize; AnimRecordOutput *rec = (AnimRecordOutput *) malloc(totalSize); data = (byte *) rec; moveOffset = animIndex * sizeof(AnimRecordOutput) + 2; // Loop to get in the animation records for (ctr = 0; ctr < animIndex; ++ctr, ++rec) { lure_exe.seek(DATA_SEGMENT + animOffsets[ctr]); lure_exe.read(&inRec, sizeof(AnimRecord)); rec->animRecordId = animOffsets[ctr]; rec->animId = inRec.animId; rec->flags = TO_LE_16(inRec.flags); rec->leftOffset = 0; rec->rightOffset = 0; rec->upOffset = 0; rec->downOffset = 0; rec->upFrame = inRec.upFrame; rec->downFrame = inRec.downFrame; rec->leftFrame = inRec.leftFrame; rec->rightFrame = inRec.rightFrame; if (includeAnim[ctr]) { // Loop to get movement records uint16 *inDirs[4] = {&inRec.leftOffset, &inRec.rightOffset, &inRec.upOffset, &inRec.downOffset}; uint16 *outDirs[4] = {&rec->leftOffset, &rec->rightOffset, &rec->upOffset, &rec->downOffset}; for (dirCtr=0; dirCtr<4; ++dirCtr) { offset = READ_LE_UINT16(inDirs[dirCtr]); if (offset == 0) { startOffset = 0; } else { startOffset = moveOffset; lure_exe.seek(DATA_SEGMENT + offset); lure_exe.read(&move, sizeof(MovementRecord)); destMove = (MovementRecord *) (data + moveOffset); while (FROM_LE_16(move.frameNumber) != 0xffff) { destMove->frameNumber = move.frameNumber; destMove->xChange = move.xChange; destMove->yChange = move.yChange; moveOffset += sizeof(MovementRecord); ++destMove; lure_exe.read(&move, sizeof(MovementRecord)); } destMove->frameNumber = TO_LE_16(0xffff); moveOffset += 2; } WRITE_LE_UINT16(outDirs[dirCtr], startOffset); } } } rec->animRecordId = TO_LE_16(0xffff); delete includeAnim; } void read_script_data(byte *&data, uint16 &totalSize) { lure_exe.seek(SCRIPT_SEGMENT); totalSize = SCRIPT_SEGMENT_SIZE; data = (byte *) malloc(totalSize); lure_exe.read(data, totalSize); } void read_script2_data(byte *&data, uint16 &totalSize) { lure_exe.seek(SCRIPT2_SEGMENT); totalSize = SCRIPT2_SEGMENT_SIZE; data = (byte *) malloc(totalSize); lure_exe.read(data, totalSize); } void read_hotspot_script_offsets(byte *&data, uint16 &totalSize) { lure_exe.seek(DATA_SEGMENT + HOTSPOT_SCRIPT_LIST); totalSize = HOTSPOT_SCRIPT_SIZE; data = (byte *) malloc(totalSize); lure_exe.read(data, totalSize); } void read_messages_segment(byte *&data, uint16 &totalSize) { lure_exe.seek(MESSAGES_SEGMENT); totalSize = MESSAGES_SEGMENT_SIZE; data = (byte *) malloc(totalSize); lure_exe.read(data, totalSize); } // Reads in the list of actions used void read_actions_list(byte *&data, uint16 &totalSize) { // Allocate enough space for output action list data = (byte *) malloc(MAX_DATA_SIZE); HotspotActionsRecord *header = (HotspotActionsRecord *) data; uint16 offset = actionIndex * sizeof(HotspotActionsRecord) + sizeof(uint16); for (int ctr = 0; ctr < actionIndex; ++ctr) { header->recordId = actionOffsets[ctr]; header->offset = offset; ++header; lure_exe.seek(DATA_SEGMENT + actionOffsets[ctr]); uint16 *numItems = (uint16 *) (data + offset); lure_exe.read(numItems, sizeof(uint16)); offset += 2; if (READ_UINT16(numItems) > 0) { lure_exe.read(data + offset, READ_UINT16(numItems) * 3); offset += READ_UINT16(numItems) * 3; } } header->recordId = TO_LE_16(0xffff); } // Reads in the talk data #define TALK_OFFSET 0x505c #define TALK_NUM_ENTRIES 28 #define MAX_TALK_LISTS 300 uint16 talkOffsets[MAX_TALK_LISTS]; int talkOffsetIndex = 0; void add_talk_offset(uint16 offset) { for (int ctr = 0; ctr < talkOffsetIndex; ++ctr) if (talkOffsets[ctr] == offset) return; if (talkOffsetIndex == MAX_TALK_LISTS) { printf("Exceeded maximum talk offset list size\n"); exit(1); } talkOffsets[talkOffsetIndex++] = offset; } struct TalkEntry { uint16 hotspotId; uint16 offset; }; void read_talk_headers(byte *&data, uint16 &totalSize) { TalkEntry entries[TALK_NUM_ENTRIES]; uint16 sortedOffsets[TALK_NUM_ENTRIES+1]; int entryCtr, subentryCtr; lure_exe.seek(DATA_SEGMENT + TALK_OFFSET); lure_exe.read(&entries[0], sizeof(TalkEntry) * TALK_NUM_ENTRIES); // Sort the entry offsets into a list - this is used to figure out each entry's size int currVal, prevVal = 0; for (entryCtr = 0; entryCtr < TALK_NUM_ENTRIES; ++entryCtr) { currVal = 0xffff; for (subentryCtr = 0; subentryCtr < TALK_NUM_ENTRIES; ++subentryCtr) { if ((FROM_LE_16(entries[subentryCtr].offset) < currVal) && (FROM_LE_16(entries[subentryCtr].offset) > prevVal)) currVal = FROM_LE_16(entries[subentryCtr].offset); } if (currVal == 0xffff) break; sortedOffsets[entryCtr] = currVal; prevVal = currVal; } sortedOffsets[entryCtr] = 0x5540; // end for end record data = (byte *) malloc(MAX_DATA_SIZE); TalkEntry *entry = (TalkEntry *) data; uint16 offset = TALK_NUM_ENTRIES * sizeof(TalkEntry) + sizeof(uint16); for (int entryCtr = 0; entryCtr < TALK_NUM_ENTRIES; ++entryCtr) { entry->hotspotId = entries[entryCtr].hotspotId; entry->offset = TO_LE_16(offset); ++entry; // Find the following offset in a sorted list int startOffset = FROM_LE_16(entries[entryCtr].offset); int nextOffset = 0; for (subentryCtr = 0; subentryCtr < TALK_NUM_ENTRIES; ++subentryCtr) { if (sortedOffsets[subentryCtr] == startOffset) { nextOffset = sortedOffsets[subentryCtr+1]; break; } } if (nextOffset == 0) exit(1); // Read in line entries into the data lure_exe.seek(DATA_SEGMENT + startOffset); int size = nextOffset - startOffset; uint16 *talkOffset = (uint16 *) (data + offset); lure_exe.read(talkOffset, size); while (size > 0) { if (READ_UINT16(talkOffset) != 0) add_talk_offset(READ_UINT16(talkOffset)); size -= sizeof(uint16); offset += sizeof(uint16); talkOffset++; } WRITE_LE_UINT16(talkOffset, 0xffff); offset += 2; } add_talk_offset(0xffff); entry->hotspotId = TO_LE_16(0xffff); totalSize = offset + 2; } // Reads in the contents of the previously loaded talk lists struct TalkRecord { uint16 recordId; uint16 listOffset; uint16 responsesOffset; }; uint16 giveTalkIds[6] = {0xCF5E, 0xCF14, 0xCF90, 0xCFAA, 0xCFD0, 0xCFF6}; void read_talk_data(byte *&data, uint16 &totalSize) { uint16 responseOffset; int talkCtr, subentryCtr; uint16 size; for (talkCtr = 0; talkCtr < 6; ++talkCtr) add_talk_offset(giveTalkIds[talkCtr]); data = (byte *) malloc(MAX_DATA_SIZE); TalkRecord *header = (TalkRecord *) data; uint16 offset = talkOffsetIndex * sizeof(TalkRecord) + sizeof(uint16); uint16 *sortedList = (uint16 *) malloc((talkOffsetIndex+1) * sizeof(uint16)); memset(sortedList, 0, (talkOffsetIndex+1) * sizeof(uint16)); // Sort the entry offsets into a list - this is used to figure out each entry's size int currVal, prevVal = 0; for (talkCtr = 0; talkCtr < talkOffsetIndex; ++talkCtr) { currVal = 0xffff; for (subentryCtr = 0; subentryCtr < talkOffsetIndex; ++subentryCtr) { if ((talkOffsets[subentryCtr] < currVal) && (talkOffsets[subentryCtr] > prevVal)) currVal = talkOffsets[subentryCtr]; } if (currVal == 0xffff) break; sortedList[talkCtr] = currVal; prevVal = currVal; } sortedList[talkCtr] = 0xf010; int numTalks = talkCtr; // Loop through the talk list for (talkCtr = 0; talkCtr < numTalks; ++talkCtr) { uint16 startOffset = sortedList[talkCtr]; uint16 nextOffset = sortedList[talkCtr+1]; header->recordId = startOffset; header->listOffset = offset; lure_exe.seek(DATA_SEGMENT + startOffset); responseOffset = lure_exe.readWord(); startOffset += 2; // Special handling for entry at 0d930h if (responseOffset == 0x8000) continue; // Calculate talk data size - if response is within record range, // use simple calculation size. Otherwise, read in full data until // end of record if ((responseOffset < startOffset) || (responseOffset >= nextOffset)) size = nextOffset - startOffset; else size = responseOffset - startOffset; if ((size % 6) == 2) size -= 2; if ((size % 6) != 0) { printf("Failure reading talk data\n"); exit(1); } // Read in the list of talk entries lure_exe.read(data + offset, size); offset += size; memset(data + offset, 0xff, 2); offset += 2; // Handle the response data header->responsesOffset = offset; // Scan through the list of record offsets and find the offset of // the following record. This is done because although the talk // records and responses are normally sequential, it can also // point into another record's talk responses nextOffset = 0; for (subentryCtr = 0; subentryCtr < numTalks; ++subentryCtr) { if ((responseOffset >= sortedList[subentryCtr]) && (responseOffset < sortedList[subentryCtr+1])) { // Found a record nextOffset = sortedList[subentryCtr+1]; break; } } if (nextOffset < responseOffset) { printf("Failure reading talk data\n"); exit(1); } size = nextOffset - responseOffset; if ((size % 6) != 0) size -= (size % 6); if ((size % 6) != 0) { printf("Failure reading talk data\n"); exit(1); } lure_exe.read(data + offset, size); offset += size; WRITE_LE_UINT16(data + offset, 0xffff); offset += 2; ++header; } header->recordId = TO_LE_16(0xffff); totalSize = offset; free(sortedList); } void read_room_pathfinding_data(byte *&data, uint16 &totalSize) { lure_exe.seek(DATA_SEGMENT + PATHFIND_OFFSET); totalSize = PATHFIND_SIZE; data = (byte *) malloc(totalSize); lure_exe.read(data, totalSize); } void read_room_exit_coordinate_data(byte *&data, uint16 &totalSize) { // Read in the exit coordinates list int roomNum, entryNum; totalSize = EXIT_COORDINATES_NUM_ROOMS * sizeof(RoomExitCoordinateEntryResource) + 2; data = (byte *) malloc(totalSize); lure_exe.seek(DATA_SEGMENT + EXIT_COORDINATES_OFFSET); lure_exe.read(data, totalSize - 2); WRITE_LE_UINT16(data + totalSize - 2, 0xffff); // Post process the list to adjust data RoomExitCoordinateEntryResource *rec = (RoomExitCoordinateEntryResource *) data; for (roomNum = 0; roomNum < EXIT_COORDINATES_NUM_ROOMS; ++roomNum, ++rec) { for (entryNum = 0; entryNum < ROOM_EXIT_COORDINATES_NUM_ENTRIES; ++entryNum) { if ((rec->entries[entryNum].x != 0) || (rec->entries[entryNum].y != 0)) { rec->entries[entryNum].x = TO_LE_16(FROM_LE_16(rec->entries[entryNum].x) - 0x80); uint16 tempY = FROM_LE_16(rec->entries[entryNum].y); rec->entries[entryNum].y = TO_LE_16( ((tempY & 0xfff) - 0x80) | (tempY & 0xf000)); } } for (entryNum = 0; entryNum < ROOM_EXIT_COORDINATES_ENTRY_NUM_ROOMS; ++entryNum) { rec->roomIndex[entryNum] = TO_LE_16(FROM_LE_16(rec->roomIndex[entryNum]) / 6); } } } void read_room_exit_hotspots_data(byte *&data, uint16 &totalSize) { totalSize = 0; data = (byte *) malloc(MAX_DATA_SIZE); RoomExitIndexedHotspotResource *rec = (RoomExitIndexedHotspotResource *) data; lure_exe.seek(DATA_SEGMENT + EXIT_HOTSPOTS_OFFSET); lure_exe.read(rec, sizeof(RoomExitIndexedHotspotResource)); while (FROM_LE_16(rec->roomNumber) != 0) { ++rec; totalSize += sizeof(RoomExitIndexedHotspotResource); lure_exe.read(rec, sizeof(RoomExitIndexedHotspotResource)); } WRITE_LE_UINT16(rec, 0xffff); totalSize += sizeof(uint16); } void getEntry(uint8 entryIndex, uint16 &resourceId, byte *&data, uint16 &size) { resourceId = 0x3f01 + entryIndex; switch (entryIndex) { case 0: // Copy the default palette to file read_basic_palette(data, size); break; case 1: // Copy the replacement palette fragments to file read_replacement_palette(data, size); break; case 2: // Copy the dialog segment data into the new vga file read_dialog_data(data, size); break; case 3: // Copy the talk dialog segment data into the new vga file read_talk_dialog_data(data, size); break; case 4: // Get the room info data read_room_data(data, size); break; case 5: // Get the action sequence set for NPC characters read_action_sequence(data, size); break; case 6: // Get the hotspot info data read_hotspot_data(data, size); break; case 7: // Get the hotspot override info data read_hotspot_override_data(data, size); break; case 8: // Get the list of room exits read_room_exits(data, size); break; case 9: // Get the list of room exit joins read_room_exit_joins(data, size); break; case 10: // Get the hotspot animation record data read_anim_data(data, size); break; case 11: // Get the script segment data read_script_data(data, size); break; case 12: // Get the second script segment data read_script2_data(data, size); break; case 13: // Get a list of hotspot script offsets read_hotspot_script_offsets(data, size); break; case 14: // Get the messages segment read_messages_segment(data, size); break; case 15: // Get the actions list read_actions_list(data, size); break; case 16: // Get the talk header information read_talk_headers(data, size); break; case 17: // Get the talk data read_talk_data(data, size); break; case 18: // Get the pathfinding data read_room_pathfinding_data(data, size); break; case 19: // Get the room exit coordinate list read_room_exit_coordinate_data(data, size); break; case 20: // Read the room exit hotspot list read_room_exit_hotspots_data(data, size); break; default: data = NULL; size = 0; resourceId = 0xffff; break; } } void createFile(const char *outFilename) { FileEntry rec; uint32 startOffset, numBytes; uint16 resourceId; uint16 resourceSize; byte *resourceData; bool resourceFlag; byte tempBuffer[32]; File f; f.open(outFilename, kFileWriteMode); memset(tempBuffer, 0, 32); // Write header f.write("heywow", 6); f.writeWord(0); startOffset = 0x600; resourceFlag = true; for (int resIndex=0; resIndex<0xBE; ++resIndex) { resourceData = NULL; // Get next data entry if (resourceFlag) // Get resource details getEntry(resIndex, resourceId, resourceData, resourceSize); // Write out the next header entry f.seek(8 + resIndex * 8); if (resourceSize == 0) { // Unused entry memset(&rec, 0xff, sizeof(FileEntry)); resourceFlag = false; } else { rec.id = TO_LE_16(resourceId); rec.offset = TO_LE_16(startOffset >> 5); rec.sizeExtension = (uint8) ((resourceSize >> 16) & 0xff); rec.size = TO_LE_16(resourceSize & 0xffff); rec.unused = 0xff; } f.write(&rec, sizeof(FileEntry)); // Write out the resource if (resourceFlag) { f.seek(startOffset); f.write(resourceData, resourceSize); startOffset += resourceSize; free(resourceData); // Free the data block // Write out enough bytes to move to the next 32 byte boundary numBytes = 0x20 * ((startOffset + 0x1f) / 0x20) - startOffset; if (numBytes != 0) { f.write(tempBuffer, numBytes); startOffset += numBytes; } } } // Store file version in final file slot f.seek(0xBF * 8); FileEntry fileVersion; memset(&fileVersion, 0xff, sizeof(FileEntry)); fileVersion.unused = VERSION_MAJOR; fileVersion.sizeExtension = VERSION_MINOR; f.write(&fileVersion, sizeof(FileEntry)); f.close(); } // validate_executable // Validates that the correct executable is being used to generate the // resource file. Eventually the resource file creator will need to work // with the other language executables, but for now just make #define NUM_BYTES_VALIDATE 1024 #define FILE_CHECKSUM 64880 void validate_executable() { uint32 sumTotal = 0; byte buffer[NUM_BYTES_VALIDATE]; lure_exe.read(buffer, NUM_BYTES_VALIDATE); for (int ctr = 0; ctr < NUM_BYTES_VALIDATE; ++ctr) sumTotal += buffer[ctr]; if (sumTotal != FILE_CHECKSUM) { printf("Lure executable not correct English version\n"); exit(1); } } int main(int argc, char *argv[]) { const char *inFilename = (argc >= 2) ? argv[1] : "c:\\games\\lure\\lure.exe"; const char *outFilename = (argc == 3) ? argv[2] : "c:\\games\\lure\\lure.dat"; if (!lure_exe.open(inFilename)) { if (argc == 1) printf("Format: %s input_exe_filename output_filename\n"); else printf("Could not open file: %s\n", inFilename); } else { validate_executable(); createFile(outFilename); lure_exe.close(); } }