X-Git-Url: https://git.artsoft.org/?a=blobdiff_plain;ds=sidebyside;f=src%2Fgame_sp%2Ffile.c;h=438e76644e9791943153c3b6a1e04d088b4fe46f;hb=5abed77f4575375756d228a2afc749589a25c87a;hp=2b8a1755fbd66d1ad559fe76ad9c86dbf4132b19;hpb=c6d59064aa782b8ad71eddac2797fdc50e4858f9;p=rocksndiamonds.git diff --git a/src/game_sp/file.c b/src/game_sp/file.c index 2b8a1755..438e7664 100644 --- a/src/game_sp/file.c +++ b/src/game_sp/file.c @@ -60,64 +60,9 @@ void setLevelInfoToDefaults_SP() void copyInternalEngineVars_SP() { - char *preceding_playfield_memory[] = - { - "95 89 95 89 95 89 3b 8a 3b 8a 3b 8a 3b 8a 3b 8a", // |......;.;.;.;.;.| - "3b 8a 3b 8a 3b 8a e8 8a e8 8a e8 8a e8 8a e8 8a", // |;.;.;.è.è.è.è.è.| - "e8 8a e8 8a e8 8a b1 8b b1 8b b1 8b b1 8b b1 8b", // |è.è.è.±.±.±.±.±.| - "b1 8b b1 8b b1 8b 85 8c 85 8c 85 8c 85 8c 85 8c", // |±.±.±...........| - "85 8c 85 8c 85 8c 5b 8d 5b 8d 5b 8d 5b 8d 5b 8d", // |......[.[.[.[.[.| - "5b 8d 5b 8d 5b 8d 06 8e 06 8e 06 8e 06 8e 06 8e", // |[.[.[...........| - "06 8e 06 8e 06 8e ac 8e ac 8e ac 8e ac 8e ac 8e", // |......¬.¬.¬.¬.¬.| - "ac 8e ac 8e ac 8e 59 8f 59 8f 59 8f 59 8f 59 8f", // |¬.¬.¬.Y.Y.Y.Y.Y.| - "59 8f 59 8f 59 8f 00 00 70 13 00 00 00 00 e8 17", // |Y.Y.Y...p.....è.| - "00 00 00 00 00 00 69 38 00 00 00 00 00 00 00 00", // |......i8........| - "00 00 00 00 00 00 00 00 d0 86 00 00 b2 34 00 00", // |........Ð...²4..| - "00 00 00 00 00 00 8f 8b 1d 34 00 00 00 00 00 00", // |.........4......| - "00 00 00 00 23 39 09 09 00 0c 00 08 00 58 00 00", // |....#9.......X..| - "00 00 00 25 77 06 7f 00 00 00 01 00 00 00 00 00", // |...%w...........| - "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00", // |................| - "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00", // |................| - "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00", // |................| - "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00", // |................| - "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00", // |................| - "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00", // |................| - "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00", // |................| - "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00", // |................| - "00 00 00 00 00 00 00 00 00 ec 06 26 05 00 00 00", // |.........ì.&....| - "00 00 00 01 00 00 00 00 31 32 33 34 35 36 37 38", // |........12345678| - "39 30 2d 00 08 00 51 57 45 52 54 59 55 49 4f 50", // |90-...QWERTYUIOP| - "00 00 0a 00 41 53 44 46 47 48 4a 4b 4c 00 00 00", // |....ASDFGHJKL...| - "00 00 5a 58 43 56 42 4e 4d 00 00 00 00 00 00 20", // |..ZXCVBNM...... | - "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00", // |................| - "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00", // |................| - "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00", // |................| - "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00", // |................| - "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00", // |................| - "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00", // |................| - "00 00 00 00 00 00 2e 00 1e 00 31 00 14 00 39 00", // |..........1...9.| - "1f 00 14 00 18 00 ff ff 01 00 01 4c 45 56 45 4c", // |......ÿÿ...LEVEL| - "53 2e 44 41 54 00 00 00 00 00 00 00 00 00 00 00", // |S.DAT...........| - "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00", // |................| - "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00", // |................| - "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00", // |................| - "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00", // |................| - "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00", // |................| - "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00", // |................| - "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00", // |................| - - NULL - }; - int preceding_buffer_size = 0; int count; int i, x, y; - for (i = 0; preceding_playfield_memory[i] != NULL; i++) - preceding_buffer_size += 8; /* eight 16-bit integer values */ - - /* needed for engine snapshots */ - game_sp.preceding_buffer_size = preceding_buffer_size; - LInfo = native_sp_level.header; FieldWidth = native_sp_level.width; @@ -127,17 +72,29 @@ void copyInternalEngineVars_SP() FieldMax = (FieldWidth * FieldHeight) + HeaderSize - 1; LevelMax = (FieldWidth * FieldHeight) - 1; +#if 0 /* (add one byte for the level number stored as first byte of demo data) */ FileMax = FieldMax + native_sp_level.demo.length + 1; +#endif +#if 0 PlayField8 = REDIM_1D(sizeof(byte), 0, FileMax); DisPlayField = REDIM_1D(sizeof(byte), 0, FieldMax); - PlayField16 = REDIM_1D(sizeof(int), -preceding_buffer_size, FieldMax); + PlayField16 = REDIM_1D(sizeof(int), -game_sp.preceding_buffer_size, FieldMax); +#endif + + /* initialize preceding playfield buffer */ + for (i = -game_sp.preceding_buffer_size; i < 0; i++) + PlayField16[i] = 0; + + /* initialize preceding playfield buffer */ + for (i = -SP_MAX_PLAYFIELD_WIDTH; i < 0; i++) + PlayField8[i] = 0; count = 0; - for (i = 0; preceding_playfield_memory[i] != NULL; i++) + for (i = 0; game_sp.preceding_buffer[i] != NULL; i++) { - char *s = preceding_playfield_memory[i]; + char *s = game_sp.preceding_buffer[i]; boolean hi_byte = FALSE; /* little endian data => start with low byte */ while (s[0] != '\0' && s[1] != '\0') @@ -149,7 +106,7 @@ void copyInternalEngineVars_SP() if (hi_byte) byte <<= 8; - PlayField16[-preceding_buffer_size + count] |= byte; + PlayField16[-game_sp.preceding_buffer_size + count] |= byte; if (hi_byte) count++; @@ -183,15 +140,21 @@ void copyInternalEngineVars_SP() { DemoAvailable = True; +#if 0 + /* !!! NEVER USED !!! */ PlayField8[FieldMax + 1] = native_sp_level.demo.level_nr; + /* !!! NEVER USED !!! */ for (i = 0; i < native_sp_level.demo.length; i++) PlayField8[FieldMax + 2 + i] = native_sp_level.demo.data[i]; +#endif } +#if 0 AnimationPosTable = REDIM_1D(sizeof(int), 0, LevelMax - 2 * FieldWidth); AnimationSubTable = REDIM_1D(sizeof(byte), 0, LevelMax - 2 * FieldWidth); - TerminalState = REDIM_1D(sizeof(byte), 0, FieldMax + 1 - 1); + TerminalState = REDIM_1D(sizeof(byte), 0, FieldMax); +#endif GravityFlag = LInfo.InitialGravity; FreezeZonks = LInfo.InitialFreezeZonks; @@ -205,7 +168,9 @@ void copyInternalEngineVars_SP() LevelLoaded = True; } -static void LoadNativeLevelFromFileStream_SP(FILE *file, int width, int height, +#if 1 + +static void LoadNativeLevelFromFileStream_SP(File *file, int width, int height, boolean demo_available) { LevelInfoType *header = &native_sp_level.header; @@ -214,13 +179,154 @@ static void LoadNativeLevelFromFileStream_SP(FILE *file, int width, int height, /* for details of the Supaplex level format, see Herman Perk's Supaplex documentation file "SPFIX63.DOC" from his Supaplex "SpeedFix" package */ - native_sp_level.width = width; - native_sp_level.height = height; + native_sp_level.width = MIN(width, SP_MAX_PLAYFIELD_WIDTH); + native_sp_level.height = MIN(height, SP_MAX_PLAYFIELD_HEIGHT); /* read level playfield (width * height == 60 * 24 tiles == 1440 bytes) */ - for (y = 0; y < native_sp_level.height; y++) + /* (MPX levels may have non-standard playfield size -- check max. size) */ + for (y = 0; y < height; y++) + { + for (x = 0; x < width; x++) + { + byte element = getFile8Bit(file); + + if (x < SP_MAX_PLAYFIELD_WIDTH && + y < SP_MAX_PLAYFIELD_HEIGHT) + native_sp_level.playfield[x][y] = element; + } + } + + /* read level header (96 bytes) */ + + ReadUnusedBytesFromFile(file, 4); /* (not used by Supaplex engine) */ + + /* initial gravity: 1 == "on", anything else (0) == "off" */ + header->InitialGravity = getFile8Bit(file); + + /* SpeedFixVersion XOR 0x20 */ + header->Version = getFile8Bit(file); + + /* level title in uppercase letters, padded with dashes ("-") (23 bytes) */ + for (i = 0; i < SP_LEVEL_NAME_LEN; i++) + header->LevelTitle[i] = getFile8Bit(file); + + /* initial "freeze zonks": 2 == "on", anything else (0, 1) == "off" */ + header->InitialFreezeZonks = getFile8Bit(file); + + /* number of infotrons needed; 0 means that Supaplex will count the total + amount of infotrons in the level and use the low byte of that number + (a multiple of 256 infotrons will result in "0 infotrons needed"!) */ + header->InfotronsNeeded = getFile8Bit(file); + + /* number of special ("gravity") port entries below (maximum 10 allowed) */ + header->SpecialPortCount = getFile8Bit(file); + + /* database of properties of up to 10 special ports (6 bytes per port) */ + for (i = 0; i < SP_MAX_SPECIAL_PORTS; i++) + { + SpecialPortType *port = &header->SpecialPort[i]; + + /* high and low byte of the location of a special port; if (x, y) are the + coordinates of a port in the field and (0, 0) is the top-left corner, + the 16 bit value here calculates as 2 * (x + (y * 60)) (this is twice + of what may be expected: Supaplex works with a game field in memory + which is 2 bytes per tile) */ + port->PortLocation = getFile16BitBE(file); /* yes, big endian */ + + /* change gravity: 1 == "turn on", anything else (0) == "turn off" */ + port->Gravity = getFile8Bit(file); + + /* "freeze zonks": 2 == "turn on", anything else (0, 1) == "turn off" */ + port->FreezeZonks = getFile8Bit(file); + + /* "freeze enemies": 1 == "turn on", anything else (0) == "turn off" */ + port->FreezeEnemies = getFile8Bit(file); + + ReadUnusedBytesFromFile(file, 1); /* (not used by Supaplex engine) */ + } + + /* SpeedByte XOR Highbyte(RandomSeed) */ + header->SpeedByte = getFile8Bit(file); + + /* CheckSum XOR SpeedByte */ + header->CheckSumByte = getFile8Bit(file); + + /* random seed used for recorded demos */ + header->DemoRandomSeed = getFile16BitLE(file); /* yes, little endian */ + + /* auto-determine number of infotrons if it was stored as "0" -- see above */ + if (header->InfotronsNeeded == 0) + { for (x = 0; x < native_sp_level.width; x++) - native_sp_level.playfield[x][y] = getFile8Bit(file); + for (y = 0; y < native_sp_level.height; y++) + if (native_sp_level.playfield[x][y] == fiInfotron) + header->InfotronsNeeded++; + + header->InfotronsNeeded &= 0xff; /* only use low byte -- see above */ + } + + /* read raw level header bytes (96 bytes) */ + + seekFile(file, -(SP_HEADER_SIZE), SEEK_CUR); /* rewind file */ + for (i = 0; i < SP_HEADER_SIZE; i++) + native_sp_level.header_raw_bytes[i] = getByteFromFile(file); + + /* also load demo tape, if available (only in single level files) */ + + if (demo_available) + { + int level_nr = getFile8Bit(file); + + level_nr &= 0x7f; /* clear highest bit */ + level_nr = (level_nr < 1 ? 1 : + level_nr > 111 ? 111 : level_nr); + + native_sp_level.demo.level_nr = level_nr; + + for (i = 0; i < SP_MAX_TAPE_LEN && !checkEndOfFile(file); i++) + { + native_sp_level.demo.data[i] = getFile8Bit(file); + + if (native_sp_level.demo.data[i] == 0xff) /* "end of demo" byte */ + { + i++; + + break; + } + } + + native_sp_level.demo.length = i; + native_sp_level.demo.is_available = (native_sp_level.demo.length > 0); + } +} + +#else + +static void LoadNativeLevelFromFileStream_SP(FILE *file, int width, int height, + boolean demo_available) +{ + LevelInfoType *header = &native_sp_level.header; + int i, x, y; + + /* for details of the Supaplex level format, see Herman Perk's Supaplex + documentation file "SPFIX63.DOC" from his Supaplex "SpeedFix" package */ + + native_sp_level.width = MIN(width, SP_MAX_PLAYFIELD_WIDTH); + native_sp_level.height = MIN(height, SP_MAX_PLAYFIELD_HEIGHT); + + /* read level playfield (width * height == 60 * 24 tiles == 1440 bytes) */ + /* (MPX levels may have non-standard playfield size -- check max. size) */ + for (y = 0; y < height; y++) + { + for (x = 0; x < width; x++) + { + byte element = getFile8Bit(file); + + if (x < SP_MAX_PLAYFIELD_WIDTH && + y < SP_MAX_PLAYFIELD_HEIGHT) + native_sp_level.playfield[x][y] = element; + } + } /* read level header (96 bytes) */ @@ -326,7 +432,271 @@ static void LoadNativeLevelFromFileStream_SP(FILE *file, int width, int height, } } -boolean LoadNativeLevel_SP(char *filename, int level_pos) +#endif + +#if 1 + +boolean LoadNativeLevel_SP(char *filename, int level_pos, + boolean level_info_only) +{ + File *file; + int i, l, x, y; + char name_first, name_last; + struct LevelInfo_SP multipart_level; + int multipart_xpos, multipart_ypos; + boolean is_multipart_level; + boolean is_first_part; + boolean reading_multipart_level = FALSE; + boolean use_empty_level = FALSE; + LevelInfoType *header = &native_sp_level.header; + boolean is_single_level_file = (strSuffixLower(filename, ".sp") || + strSuffixLower(filename, ".mpx")); + boolean demo_available = is_single_level_file; + boolean is_mpx_file = strSuffixLower(filename, ".mpx"); + int file_seek_pos = level_pos * SP_STD_LEVEL_SIZE; + int level_width = SP_STD_PLAYFIELD_WIDTH; + int level_height = SP_STD_PLAYFIELD_HEIGHT; + + /* always start with reliable default values */ + setLevelInfoToDefaults_SP(); + copyInternalEngineVars_SP(); + + if (!(file = openFile(filename, MODE_READ))) + { + if (!level_info_only) + Error(ERR_WARN, "cannot open file '%s' -- using empty level", filename); + + return FALSE; + } + + if (is_mpx_file) + { + char mpx_chunk_name[4 + 1]; + int mpx_version; + int mpx_level_count; + LevelDescriptor *mpx_level_desc; + + getFileChunkBE(file, mpx_chunk_name, NULL); + + if (!strEqual(mpx_chunk_name, "MPX ")) + { + Error(ERR_WARN, "cannot find MPX ID in file '%s' -- using empty level", + filename); + + return FALSE; + } + + mpx_version = getFile16BitLE(file); + + if (mpx_version != 1) + { + Error(ERR_WARN, "unknown MPX version in file '%s' -- using empty level", + filename); + + return FALSE; + } + + mpx_level_count = getFile16BitLE(file); + + if (mpx_level_count < 1) + { + Error(ERR_WARN, "no MPX levels found in file '%s' -- using empty level", + filename); + + return FALSE; + } + + if (level_pos >= mpx_level_count) + { + Error(ERR_WARN, "MPX level not found in file '%s' -- using empty level", + filename); + + return FALSE; + } + + mpx_level_desc = checked_calloc(mpx_level_count * sizeof(LevelDescriptor)); + + for (i = 0; i < mpx_level_count; i++) + { + LevelDescriptor *ldesc = &mpx_level_desc[i]; + + ldesc->Width = getFile16BitLE(file); + ldesc->Height = getFile16BitLE(file); + ldesc->OffSet = getFile32BitLE(file); /* starts with 1, not with 0 */ + ldesc->Size = getFile32BitLE(file); + } + + level_width = mpx_level_desc[level_pos].Width; + level_height = mpx_level_desc[level_pos].Height; + + file_seek_pos = mpx_level_desc[level_pos].OffSet - 1; + } + + /* position file stream to the requested level (in case of level package) */ + if (seekFile(file, file_seek_pos, SEEK_SET) != 0) + { + Error(ERR_WARN, "cannot fseek in file '%s' -- using empty level", filename); + + return FALSE; + } + + /* there exist Supaplex level package files with multi-part levels which + can be detected as follows: instead of leading and trailing dashes ('-') + to pad the level name, they have leading and trailing numbers which are + the x and y coordinations of the current part of the multi-part level; + if there are '?' characters instead of numbers on the left or right side + of the level name, the multi-part level consists of only horizontal or + vertical parts */ + + for (l = level_pos; l < SP_NUM_LEVELS_PER_PACKAGE; l++) + { + LoadNativeLevelFromFileStream_SP(file, level_width, level_height, + demo_available); + + /* check if this level is a part of a bigger multi-part level */ + + if (is_single_level_file) + break; + + name_first = header->LevelTitle[0]; + name_last = header->LevelTitle[SP_LEVEL_NAME_LEN - 1]; + + is_multipart_level = + ((name_first == '?' || (name_first >= '0' && name_first <= '9')) && + (name_last == '?' || (name_last >= '0' && name_last <= '9'))); + + is_first_part = + ((name_first == '?' || name_first == '1') && + (name_last == '?' || name_last == '1')); + + if (is_multipart_level) + { + /* correct leading multipart level meta information in level name */ + for (i = 0; + i < SP_LEVEL_NAME_LEN && header->LevelTitle[i] == name_first; + i++) + header->LevelTitle[i] = '-'; + + /* correct trailing multipart level meta information in level name */ + for (i = SP_LEVEL_NAME_LEN - 1; + i >= 0 && header->LevelTitle[i] == name_last; + i--) + header->LevelTitle[i] = '-'; + } + + /* ---------- check for normal single level ---------- */ + + if (!reading_multipart_level && !is_multipart_level) + { + /* the current level is simply a normal single-part level, and we are + not reading a multi-part level yet, so return the level as it is */ + + break; + } + + /* ---------- check for empty level (unused multi-part) ---------- */ + + if (!reading_multipart_level && is_multipart_level && !is_first_part) + { + /* this is a part of a multi-part level, but not the first part + (and we are not already reading parts of a multi-part level); + in this case, use an empty level instead of the single part */ + + use_empty_level = TRUE; + + break; + } + + /* ---------- check for finished multi-part level ---------- */ + + if (reading_multipart_level && + (!is_multipart_level || + !strEqualN(header->LevelTitle, multipart_level.header.LevelTitle, + SP_LEVEL_NAME_LEN))) + { + /* we are already reading parts of a multi-part level, but this level is + either not a multi-part level, or a part of a different multi-part + level; in both cases, the multi-part level seems to be complete */ + + break; + } + + /* ---------- here we have one part of a multi-part level ---------- */ + + reading_multipart_level = TRUE; + + if (is_first_part) /* start with first part of new multi-part level */ + { + /* copy level info structure from first part */ + multipart_level = native_sp_level; + + /* clear playfield of new multi-part level */ + for (x = 0; x < SP_MAX_PLAYFIELD_WIDTH; x++) + for (y = 0; y < SP_MAX_PLAYFIELD_HEIGHT; y++) + multipart_level.playfield[x][y] = fiSpace; + } + + if (name_first == '?') + name_first = '1'; + if (name_last == '?') + name_last = '1'; + + multipart_xpos = (int)(name_first - '0'); + multipart_ypos = (int)(name_last - '0'); + +#if 0 + printf("----------> part (%d/%d) of multi-part level '%s'\n", + multipart_xpos, multipart_ypos, multipart_level.header.LevelTitle); +#endif + + if (multipart_xpos * SP_STD_PLAYFIELD_WIDTH > SP_MAX_PLAYFIELD_WIDTH || + multipart_ypos * SP_STD_PLAYFIELD_HEIGHT > SP_MAX_PLAYFIELD_HEIGHT) + { + Error(ERR_WARN, "multi-part level is too big -- ignoring part of it"); + + break; + } + + multipart_level.width = MAX(multipart_level.width, + multipart_xpos * SP_STD_PLAYFIELD_WIDTH); + multipart_level.height = MAX(multipart_level.height, + multipart_ypos * SP_STD_PLAYFIELD_HEIGHT); + + /* copy level part at the right position of multi-part level */ + for (x = 0; x < SP_STD_PLAYFIELD_WIDTH; x++) + { + for (y = 0; y < SP_STD_PLAYFIELD_HEIGHT; y++) + { + int start_x = (multipart_xpos - 1) * SP_STD_PLAYFIELD_WIDTH; + int start_y = (multipart_ypos - 1) * SP_STD_PLAYFIELD_HEIGHT; + + multipart_level.playfield[start_x + x][start_y + y] = + native_sp_level.playfield[x][y]; + } + } + } + + closeFile(file); + + if (use_empty_level) + { + setLevelInfoToDefaults_SP(); + + Error(ERR_WARN, "single part of multi-part level -- using empty level"); + } + + if (reading_multipart_level) + native_sp_level = multipart_level; + + copyInternalEngineVars_SP(); + + return TRUE; +} + +#else + +boolean LoadNativeLevel_SP(char *filename, int level_pos, + boolean level_info_only) { FILE *file; int i, l, x, y; @@ -352,7 +722,8 @@ boolean LoadNativeLevel_SP(char *filename, int level_pos) if (!(file = fopen(filename, MODE_READ))) { - Error(ERR_WARN, "cannot open file '%s' -- using empty level", filename); + if (!level_info_only) + Error(ERR_WARN, "cannot open file '%s' -- using empty level", filename); return FALSE; } @@ -581,6 +952,8 @@ boolean LoadNativeLevel_SP(char *filename, int level_pos) return TRUE; } +#endif + void SaveNativeLevel_SP(char *filename) { LevelInfoType *header = &native_sp_level.header;