/* 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 3 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, see . * */ // Disable symbol overrides so that we can use system headers. #define FORBIDDEN_SYMBOL_ALLOW_ALL #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 = new File; 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(); delete outputFile; } 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 = 5; //old 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 12012: // Amazon Spanish floppy language = 23; //old Common::ES_ESP; gameId = 1; dataSegmentOffset = 0xC8C0; filenamesOffset = dataSegmentOffset + 0x3628 + 0x128; numFilenames = 100; charsStart = dataSegmentOffset + 0x4234 + 0x128; charsEnd = dataSegmentOffset + 0x49c6 + 0x128; roomsStart = dataSegmentOffset + 0x35a8 + 0x128; roomsEnd = dataSegmentOffset + 0x4234 + 0x128; travelPosOffset = dataSegmentOffset + 0x5ff7 + 0x128 + 0x2b; 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; }