/* ScummVM - Graphic Adventure Engine * * ScummVM is the legal property of its developers, whose names * are too numerous to list here. Please refer to the COPYRIGHT * file distributed with this source distribution. * * 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. * */ // Disable symbol overrides so that we can use system headers. #define FORBIDDEN_SYMBOL_ALLOW_ALL // HACK to allow building with the SDL backend on MinGW // see bug #1800764 "TOOLS: MinGW tools building broken" #ifdef main #undef main #endif // main #include #include #include #include "common/language.h" #include "common/rect.h" #include "create_access_dat.h" #include "amazon_resources.h" #include "martian_resources.h" /** * Format of the access.dat file that will be created: * 4 Bytes - Magic string 'SVMA' to identify valid data file * 2 bytes - Version number * 2 Bytes - Number of different games data in the data file * Series of index entries identifying each game: * 1 byte - Game type: 1 = Amazon, 2 = Martian Memorandum, 3 = Noctropolis * 1 byte - disc type: 0 = Floppy, 1 = CD, 2 = Common data shared across * all variations of the given game * 1 byte - Is Demo: 0 = Full game, 1 = Demo * 1 byte - Language (Common::Language) * 4 bytes - File offset for the data for the game */ File outputFile; void writeHeader(int numExecutables); void writeAmazonCommonData(); void writeMartianCommonData(int argc, char *argv[]); bool processExecutable(int idx, const char *name); void NORETURN_PRE error(const char *s, ...) { printf("%s\n", s); exit(1); } int main(int argc, char *argv[]) { const uint NUM_COMMON_ENTRIES = 2; if (argc < 3) { printf("Format: %s output_filename executable1 [executable2 ..]\n", argv[0]); exit(0); } // Create the new data file for output outputFile.open(argv[1], kFileWriteMode); writeHeader(argc - 2 + NUM_COMMON_ENTRIES); // Write out entries containing common data for the games writeAmazonCommonData(); writeMartianCommonData(argc, argv); // Iterate through processing each specified executable outputFile.seek(0, SEEK_END); for (int idx = 2; idx < argc; ++idx) { if (!processExecutable(idx - 2 + NUM_COMMON_ENTRIES, argv[idx])) break; } // Close the output file outputFile.close(); } void writeHeader(int numExecutables) { // Write out magic string const char *MAGIC_STR = "SVMA"; outputFile.write(MAGIC_STR, 4); // Write out version number outputFile.writeWord(VERSION_NUMBER); // Write out the number of entries the data file will contain outputFile.writeWord(numExecutables); // Write out padding for index entries that will be written outputFile.writeByte(0, 8 * numExecutables); } void writeAmazonCommonData() { // Write out the header entry outputFile.seek(8); outputFile.writeByte(1); // Amazon outputFile.writeByte(2); // Common data outputFile.writeByte(0); outputFile.writeByte(0); outputFile.writeLong(outputFile.size()); // Write out cursor list outputFile.seek(0, SEEK_END); outputFile.writeWord(AMAZON_NUM_CURSORS); for (uint idx = 0; idx < AMAZON_NUM_CURSORS; ++idx) { outputFile.writeWord(Amazon::CURSOR_SIZES[idx]); outputFile.write(Amazon::CURSORS[idx], Amazon::CURSOR_SIZES[idx]); } // Write out font data outputFile.writeWord(Amazon::FONT2_INDEX_SIZE); for (uint idx = 0; idx < Amazon::FONT2_INDEX_SIZE; ++idx) outputFile.writeWord(Amazon::FONT2_INDEX[idx]); outputFile.writeWord(Amazon::FONT2_DATA_SIZE); outputFile.write(Amazon::FONT2_DATA, Amazon::FONT2_DATA_SIZE); outputFile.writeWord(Amazon::FONT6x6_INDEX_SIZE); for (uint idx = 0; idx < Amazon::FONT6x6_INDEX_SIZE; ++idx) outputFile.writeWord(Amazon::FONT6x6_INDEX[idx]); outputFile.writeWord(Amazon::FONT6x6_DATA_SIZE); outputFile.write(Amazon::FONT2_DATA, Amazon::FONT6x6_DATA_SIZE); } void writeMartianCommonData(int argc, char *argv[]) { // Write out the header entry outputFile.seek(16); outputFile.writeByte(2); // Martian outputFile.writeByte(2); // Common data outputFile.writeByte(0); outputFile.writeByte(0); outputFile.writeLong(outputFile.size()); // Write out cursor list outputFile.seek(0, SEEK_END); outputFile.writeWord(MARTIAN_NUM_CURSORS); for (uint idx = 0; idx < MARTIAN_NUM_CURSORS; ++idx) { outputFile.writeWord(Martian::CURSOR_SIZES[idx]); outputFile.write(Martian::CURSORS[idx], Martian::CURSOR_SIZES[idx]); } // Check for the presence of a Martian Memorandum executable for (int idx = 2; idx < argc; ++idx) { File exeFile; if (!exeFile.open(argv[idx])) continue; // Total up the first 256 bytes of the executable as a simplified checksum uint fileChecksum = 0; for (int i = 0; i < 256; ++i) fileChecksum += exeFile.readByte(); if (fileChecksum == 10454) { // Write out font data const int DATA_SEGMENT = 0x9600; #define FONT_COUNT 119 const int FONT_WIDTHS[2] = { 0x47E6, 0x4C9C }; const int FONT_CHAR_OFFSETS[2] = { 0x46F8, 0x4BAE }; const uint FONT_DATA_SIZE[2] = { 849, 907 }; int dataOffset; for (int fontNum = 0; fontNum < 2; ++fontNum) { // Write out sizes outputFile.writeWord(FONT_COUNT); outputFile.writeWord(FONT_DATA_SIZE[fontNum]); // Write out character widths exeFile.seek(DATA_SEGMENT + FONT_WIDTHS[fontNum]); outputFile.write(exeFile, FONT_COUNT); // Write out character offsets uint offsets[FONT_COUNT]; exeFile.seek(DATA_SEGMENT + FONT_CHAR_OFFSETS[fontNum]); for (int i = 0; i < FONT_COUNT; ++i) { offsets[i] = exeFile.readWord(); if (i == 0) dataOffset = offsets[0]; offsets[i] -= dataOffset; assert(offsets[i] < FONT_DATA_SIZE[fontNum]); outputFile.writeWord(offsets[i]); } // Write out character data exeFile.seek(DATA_SEGMENT + dataOffset); outputFile.write(exeFile, FONT_DATA_SIZE[fontNum]); } return; } } // No executable found, so store 0 size fonts outputFile.writeWord(0); outputFile.writeWord(0); outputFile.writeWord(0); outputFile.writeWord(0); } bool processExecutable(int exeIdx, const char *name) { uint dataSegmentOffset; uint filenamesOffset, numFilenames; uint charsStart, charsEnd; uint roomsStart, roomsEnd, numRooms; uint travelPosOffset; const char *const *roomDescs; const byte *deathScreens; const char *const *deathText; uint numDeaths; uint numItems; const char *const *itemNames; const int *comboTable; byte gameId = 0, discType = 0, demoType = 0; byte language = Common::EN_ANY; // Open up the file for access File exeFile; if (!exeFile.open(name)) { printf("Could not open file - %s\n", name); return false; } // Total up the first 256 bytes of the executable as a simplified // means of identifying the different executables we support uint fileChecksum = 0; for (int idx = 0; idx < 256; ++idx) fileChecksum += exeFile.readByte(); switch (fileChecksum) { case 11899: // Amazon English floppy gameId = 1; dataSegmentOffset = 0xC8C0; filenamesOffset = dataSegmentOffset + 0x3628; numFilenames = 100; charsStart = dataSegmentOffset + 0x4234; charsEnd = dataSegmentOffset + 0x49c6; roomsStart = dataSegmentOffset + 0x35a8; roomsEnd = dataSegmentOffset + 0x4234; travelPosOffset = dataSegmentOffset + 0x5ff7; numRooms = 64; roomDescs = &Amazon::ROOM_DESCR[0]; deathScreens = Amazon::DEATH_SCREENS_ENG; deathText = &Amazon::DEATH_TEXT_ENG[0]; numDeaths = sizeof(Amazon::DEATH_SCREENS_ENG); numItems = 85; itemNames = &Amazon::INVENTORY_NAMES_ENG[0]; comboTable = &Amazon::COMBO_TABLE[0][0]; break; case 12360: // Amazon CD English gameId = 1; discType = 1; dataSegmentOffset = 0xd370; filenamesOffset = dataSegmentOffset + 0x3EA0; numFilenames = 116; charsStart = dataSegmentOffset + 0x4BDC; charsEnd = dataSegmentOffset + 0x5AF4; roomsStart = dataSegmentOffset + 0x3E20; roomsEnd = dataSegmentOffset + 0x4BDC; travelPosOffset = dataSegmentOffset + 0x7125; numRooms = 64; roomDescs = &Amazon::ROOM_DESCR[0]; deathScreens = Amazon::DEATH_SCREENS_ENG; deathText = &Amazon::DEATH_TEXT_ENG[0]; numDeaths = sizeof(Amazon::DEATH_SCREENS_ENG); numItems = 85; itemNames = &Amazon::INVENTORY_NAMES_ENG[0]; comboTable = &Amazon::COMBO_TABLE[0][0]; break; case 11748: // Amazon English Demo gameId = 1; discType = 0; demoType = 1; dataSegmentOffset = 0xa2a0; filenamesOffset = dataSegmentOffset + 0x242C; numFilenames = 100; charsStart = dataSegmentOffset + 0x2F1A; charsEnd = dataSegmentOffset + 0x34FB; roomsStart = dataSegmentOffset + 0x23AC; roomsEnd = dataSegmentOffset + 0x2F1A; travelPosOffset = dataSegmentOffset + 0x494E; numRooms = 64; roomDescs = &Amazon::ROOM_DESCR[0]; deathScreens = Amazon::DEATH_SCREENS_ENG; deathText = &Amazon::DEATH_TEXT_ENG[0]; numDeaths = sizeof(Amazon::DEATH_SCREENS_ENG); numItems = 85; itemNames = &Amazon::INVENTORY_NAMES_ENG[0]; comboTable = &Amazon::COMBO_TABLE[0][0]; break; case 1224: // Martian Memorandum English packed printf("Martian Memorandum provided that's packed with EXEPACK.\n"); printf("It needs to be first unpacked before it can be used with this tool.\n"); return false; case 10454: // Martian Memorandum English decompressed gameId = 2; dataSegmentOffset = 0x9600; filenamesOffset = dataSegmentOffset + 0x373A; numFilenames = 80; charsStart = dataSegmentOffset + 0x40F2; charsEnd = dataSegmentOffset + 0x46F8; roomsStart = dataSegmentOffset + 0x36DA; roomsEnd = dataSegmentOffset + 0x40F2; travelPosOffset = dataSegmentOffset + 0x58E9; numRooms = 48; roomDescs = &Martian::ROOM_DESCR[0]; deathScreens = Martian::DEATH_SCREENS_ENG; deathText = &Martian::DEATH_TEXT_ENG[0]; numDeaths = sizeof(Martian::DEATH_SCREENS_ENG); numItems = 55; itemNames = &Martian::INVENTORY_NAMES_ENG[0]; comboTable = nullptr; break; default: printf("Unknown game executable specified - %s\n", name); exeFile.close(); return false; } // Write out header entry uint outputOffset = outputFile.size(); outputFile.seek(8 + exeIdx * 8); outputFile.writeByte(gameId); outputFile.writeByte(discType); outputFile.writeByte(demoType); outputFile.writeByte(language); outputFile.writeLong(outputOffset); outputFile.seek(0, SEEK_END); // Write out list of AP filenames outputFile.writeWord(numFilenames); for (uint idx = 0; idx < numFilenames; ++idx) { exeFile.seek(filenamesOffset + idx * 2); uint nameOffset = exeFile.readWord(); exeFile.seek(dataSegmentOffset + nameOffset); outputFile.writeString(exeFile); } // Write out the character list exeFile.seek(charsStart); Common::Array charOffsets; charOffsets.push_back(exeFile.readWord()); assert((dataSegmentOffset + charOffsets[0] - exeFile.pos()) < 512); while (exeFile.pos() < (dataSegmentOffset + charOffsets[0])) charOffsets.push_back(exeFile.readWord()); outputFile.writeWord(charOffsets.size()); charOffsets.push_back(charsEnd); for (uint idx = 0; idx < charOffsets.size() - 1; ++idx) { if (charOffsets[idx] == 0) { outputFile.writeWord(0); } else { uint nextOffset = 0xffff; for (uint idx2 = 0; idx2 < charOffsets.size(); ++idx2) { if (charOffsets[idx2] && charOffsets[idx2] > charOffsets[idx] && charOffsets[idx2] < nextOffset) nextOffset = charOffsets[idx2]; } uint size = nextOffset - charOffsets[idx]; exeFile.seek(dataSegmentOffset + charOffsets[idx]); outputFile.writeWord(size); outputFile.write(exeFile, size); } } // Write out the room data Common::Array roomOffsets; Common::Array travelPos; exeFile.seek(roomsStart); for (uint idx = 0; idx < numRooms; ++idx) roomOffsets.push_back(exeFile.readWord()); roomOffsets.push_back(roomsEnd); exeFile.seek(travelPosOffset); for (uint idx = 0; idx < numRooms; ++idx) { int16 xp = (int16)exeFile.readWord(); int16 yp = (int16)exeFile.readWord(); travelPos.push_back(Common::Point(xp, yp)); } outputFile.writeWord(numRooms); for (uint idx = 0; idx < numRooms; ++idx) { uint dataSize = 0; if (roomOffsets[idx] == 0) { dataSize = 0; } else { // Find the offset of the next higher entry that's non-zero uint offset = 0; for (uint idx2 = idx + 1; !offset; ++idx2) offset = roomOffsets[idx2]; dataSize = offset - roomOffsets[idx]; exeFile.seek(dataSegmentOffset + roomOffsets[idx]); } // Write out the room description (used only by the debugger) outputFile.writeString(roomDescs[idx]); // Write out travel position outputFile.writeWord((uint16)travelPos[idx].x); outputFile.writeWord((uint16)travelPos[idx].y); // Write out the data for the room outputFile.writeWord(dataSize); if (dataSize > 0) outputFile.write(exeFile, dataSize); } // Write out the deaths list outputFile.writeWord(numDeaths); for (uint idx = 0; idx < numDeaths; ++idx) { // Write out the screen number and text outputFile.writeByte(deathScreens[idx]); outputFile.writeString(deathText[idx]); } // Write out inventory data outputFile.writeWord(numItems); for (uint idx = 0; idx < numItems; ++idx) { outputFile.writeString(itemNames[idx]); if (comboTable == nullptr) { for (uint cIdx = 0; cIdx < 4; ++cIdx) outputFile.writeWord(0); } else { for (uint cIdx = 0; cIdx < 4; ++cIdx, ++comboTable) outputFile.writeWord((uint16)*comboTable); } } // Write out game specific strings and other data if (gameId == 1) { // Write out miscellaneous strings outputFile.writeString(Amazon::NO_HELP_MESSAGE_ENG); outputFile.writeString(Amazon::NO_HINTS_MESSAGE_ENG); outputFile.writeString(Amazon::RIVER_HIT1_ENG); outputFile.writeString(Amazon::RIVER_HIT2_ENG); outputFile.writeString(Amazon::BAR_MESSAGE_ENG); for (int idx = 0; idx < 3; ++idx) outputFile.writeString(Amazon::HELPLVLTXT_ENG[idx]); for (int idx = 0; idx < 9; ++idx) outputFile.writeString(Amazon::IQLABELS_ENG[idx]); outputFile.writeString(Amazon::CANT_GET_THERE_ENG); } // Do final padding to the next paragraph boundary if ((outputFile.size() % 16) != 0) outputFile.writeByte(0, 16 - (outputFile.size() % 16)); // Close the executable and signal that it was processed successfully exeFile.close(); return true; }