diff --git a/src/format.h b/src/format.h index bcc5644..9ba4912 100644 --- a/src/format.h +++ b/src/format.h @@ -2499,16 +2499,6 @@ namespace TR { } inv; } extra; - struct TPAL { - uint8 data[3]; - }; - - uint32 tpalCount; - TPAL *tpal; - - int32 spalCount; - uint16 *spal; - uint32 tsubCount; uint8 *tsub; @@ -2971,8 +2961,6 @@ namespace TR { delete[] soundOffsets; delete[] soundSize; - delete[] spal; - delete[] tpal; delete[] tsub; } @@ -3032,17 +3020,12 @@ namespace TR { break; case CHUNK("ROOMTPAL") : { ASSERTV(stream.readBE32() == 0x00000003); - tpalCount = stream.readBE32(); - tpal = new TPAL[tpalCount]; - stream.raw(tpal, sizeof(TPAL) * tpalCount); + stream.seek(stream.readBE32() * 3); break; } case CHUNK("ROOMSPAL") : { ASSERTV(stream.readLE32() == 0x02000000); - spalCount = stream.readBE32(); - spal = new uint16[spalCount]; - for (int i = 0; i < spalCount; i++) - spal[i] = stream.readBE16(); + stream.seek(stream.readBE32() * 2); break; } case CHUNK("ROOMDATA") : @@ -3411,8 +3394,6 @@ namespace TR { case CHUNK("ROOMEND ") : ASSERTV(stream.readBE32() == 0x00000000); ASSERTV(stream.readBE32() == 0x00000000); - LOG("tpalCount = %d\n", tpalCount); - LOG("spalCount = %d\n", spalCount); prepare(); break; // SAD diff --git a/src/gameflow.h b/src/gameflow.h index b33a87d..cf37976 100644 --- a/src/gameflow.h +++ b/src/gameflow.h @@ -290,8 +290,8 @@ namespace TR { // TITLE case 585648 : // PSX JAP case 508614 : version = VER_TR1_PSX; - //case 320412 : // PC JAP case 5148 : // SAT + case 320412 : // PC JAP case 334874 : case 316138 : case 316460 : return LVL_TR1_TITLE; @@ -305,7 +305,7 @@ namespace TR { // LEVEL1 case 1667568 : // PSX JAP case 1448896 : version = VER_TR1_PSX; - case 497656 : + case 497656 : // SAT case 2540906 : // PC JAP case 2533312 : case 2533634 : return LVL_TR1_1; @@ -313,96 +313,114 @@ namespace TR { case 2873406 : isDemoLevel = true; return LVL_TR1_2; case 1766352 : // PSX JAP case 1535734 : version = VER_TR1_PSX; + case 532250 : // SAT case 2880722 : // PC JAP case 2873128 : case 2873450 : return LVL_TR1_2; // LEVEL3A case 1876896 : // PSX JAP case 1630560 : version = VER_TR1_PSX; + case 547782 : // SAT case 2942002 : // PC JAP case 2934408 : case 2934730 : return LVL_TR1_3A; // LEVEL3B case 1510414 : // PSX JAP case 1506614 : version = VER_TR1_PSX; + case 310960 : // SAT case 2745530 : // PC JAP case 2737936 : case 2738258 : return LVL_TR1_3B; // CUT1 case 722402 : version = VER_TR1_PSX; + case 142116 : // SAT case 599840 : return LVL_TR1_CUT_1; // LEVEL4 case 1624130 : // PSX JAP case 1621970 : version = VER_TR1_PSX; + case 440612 : // SAT case 3038144 : // PC JAP case 3030550 : case 3030872 : return LVL_TR1_4; // LEVEL5 case 1588102 : // PSX JAP case 1585942 : version = VER_TR1_PSX; + case 389996 : // SAT case 2725812 : // PC JAP case 2718218 : case 2718540 : return LVL_TR1_5; // LEVEL6 case 1710624 : // PSX JAP case 1708464 : version = VER_TR1_PSX; + case 573506 : // SAT case 3147184 : // PC JAP case 3139590 : case 3074376 : return LVL_TR1_6; // LEVEL7A case 1698824 : // PSX JAP case 1696664 : version = VER_TR1_PSX; + case 581416 : // SAT case 2824884 : // PC JAP case 2817290 : case 2817612 : return LVL_TR1_7A; // LEVEL7B case 1735434 : // PSX JAP case 1733274 : version = VER_TR1_PSX; + case 596416 : // SAT case 3603912 : // PC JAP case 3388774 : case 3389096 : return LVL_TR1_7B; // CUT2 case 542960 : version = VER_TR1_PSX; + case 70860 : // SAT case 354320 : return LVL_TR1_CUT_2; // LEVEL8A case 1565494 : // PSX JAP case 1563356 : version = VER_TR1_PSX; + case 592188 : // SAT case 2887836 : // PC JAP case 2880242 : case 2880564 : return LVL_TR1_8A; // LEVEL8B case 1567790 : // PSX JAP case 1565630 : version = VER_TR1_PSX; + case 599928 : // SAT case 2894028 : // PC JAP case 2886434 : case 2886756 : return LVL_TR1_8B; // LEVEL8C case 1621520 : // PSX JAP case 1619360 : version = VER_TR1_PSX; + case 536950 : // SAT case 3072066 : // PC JAP case 3105128 : case 3105450 : return LVL_TR1_8C; // LEVEL10A case 1680146 : // PSX JAP case 1678018 : version = VER_TR1_PSX; + case 569856 : // SAT case 3270372 : // PC JAP case 3223816 : case 3224138 : return LVL_TR1_10A; // CUT3 case 636660 : version = VER_TR1_PSX; + case 210134 : // SAT case 512104 : return LVL_TR1_CUT_3; // LEVEL10B case 1688908 : // PSX JAP case 1686748 : version = VER_TR1_PSX; + case 525646 : // SAT case 3101614 : // PC JAP case 3094342 : case 3094020 : return LVL_TR1_10B; // CUT4 case 940398 : version = VER_TR1_PSX; + case 167188 : // SAT case 879582 : return LVL_TR1_CUT_4; // LEVEL10C case 1816438 : // PSX JAP case 1814278 : version = VER_TR1_PSX; + case 418170 : // SAT case 3533814 : // PC JAP case 3531702 : case 3532024 : return LVL_TR1_10C; @@ -1095,6 +1113,7 @@ namespace TR { if (version & VER_TR1) { CHECK_FILE("FMV/CORELOGO.FMV"); CHECK_FILE("FMV/CORE.RPL"); + CHECK_FILE("FMV/CORELOGO.CPK"); CHECK_FILE("video/1/CORELOGO.FMV"); CHECK_FILE("video/1/CORE.RPL"); } @@ -1119,59 +1138,77 @@ namespace TR { const char* getGameVideo(LevelID id) { switch (id) { // TR1 - case LVL_TR1_TITLE : + case LVL_TR1_TITLE : CHECK_FILE("FMV/CAFE.FMV"); CHECK_FILE("FMV/CAFE.RPL"); + CHECK_FILE("FMV/CAFE.CPK"); CHECK_FILE("video/1/CAFE.FMV"); CHECK_FILE("video/1/CAFE.RPL"); + CHECK_FILE("video/1/CAFE.CPK"); break; - case LVL_TR1_GYM : + case LVL_TR1_GYM : CHECK_FILE("FMV/MANSION.FMV"); CHECK_FILE("FMV/MANSION.RPL"); + CHECK_FILE("FMV/MANSION.CPK"); CHECK_FILE("video/1/MANSION.FMV"); CHECK_FILE("video/1/MANSION.RPL"); + CHECK_FILE("video/1/MANSION.CPK"); break; - case LVL_TR1_1 : + case LVL_TR1_1 : CHECK_FILE("FMV/SNOW.FMV"); CHECK_FILE("FMV/SNOW.RPL"); + CHECK_FILE("FMV/SNOW.CPK"); CHECK_FILE("video/1/SNOW.FMV"); CHECK_FILE("video/1/SNOW.RPL"); + CHECK_FILE("video/1/SNOW.CPK"); break; - case LVL_TR1_4 : + case LVL_TR1_4 : CHECK_FILE("FMV/LIFT.FMV"); CHECK_FILE("FMV/LIFT.RPL"); + CHECK_FILE("FMV/LIFT.CPK"); CHECK_FILE("video/1/LIFT.FMV"); CHECK_FILE("video/1/LIFT.RPL"); + CHECK_FILE("video/1/LIFT.CPK"); break; - case LVL_TR1_8A : + case LVL_TR1_8A : CHECK_FILE("FMV/VISION.FMV"); CHECK_FILE("FMV/VISION.RPL"); + CHECK_FILE("FMV/VISION.CPK"); CHECK_FILE("video/1/VISION.FMV"); CHECK_FILE("video/1/VISION.RPL"); + CHECK_FILE("video/1/VISION.CPK"); break; - case LVL_TR1_10A : + case LVL_TR1_10A : CHECK_FILE("FMV/CANYON.FMV"); CHECK_FILE("FMV/CANYON.RPL"); + CHECK_FILE("FMV/CANYON.CPK"); CHECK_FILE("video/1/CANYON.FMV"); CHECK_FILE("video/1/CANYON.RPL"); + CHECK_FILE("video/1/CANYON.CPK"); break; - case LVL_TR1_10B : + case LVL_TR1_10B : CHECK_FILE("FMV/PYRAMID.FMV"); CHECK_FILE("FMV/PYRAMID.RPL"); + CHECK_FILE("FMV/PYRAMID.CPK"); CHECK_FILE("video/1/PYRAMID.FMV"); CHECK_FILE("video/1/PYRAMID.RPL"); + CHECK_FILE("video/1/PYRAMID.CPK"); break; - case LVL_TR1_CUT_4 : + case LVL_TR1_CUT_4 : CHECK_FILE("FMV/PRISON.FMV"); CHECK_FILE("FMV/PRISON.RPL"); + CHECK_FILE("FMV/PRISON.CPK"); CHECK_FILE("video/1/PRISON.FMV"); CHECK_FILE("video/1/PRISON.RPL"); + CHECK_FILE("video/1/PRISON.CPK"); break; - case LVL_TR1_EGYPT : + case LVL_TR1_EGYPT : CHECK_FILE("FMV/END.FMV"); CHECK_FILE("FMV/END.RPL"); + CHECK_FILE("FMV/END.CPK"); CHECK_FILE("video/1/END.FMV"); CHECK_FILE("video/1/END.RPL"); + CHECK_FILE("video/1/END.CPK"); break; // TR2 case LVL_TR2_TITLE : diff --git a/src/utils.h b/src/utils.h index 6bdcd29..905ef15 100644 --- a/src/utils.h +++ b/src/utils.h @@ -1650,17 +1650,21 @@ struct Array { clear(); } + void reserve(int capacity) { + this->capacity = capacity; + if (items) + items = (T*)realloc(items, capacity * sizeof(T)); + else + items = (T*)malloc(capacity * sizeof(T)); + } + int push(const T &item) { if (!items) items = (T*)malloc(capacity * sizeof(T)); - if (length == capacity) { - capacity += capacity + capacity / 2; - if (items) - items = (T*)realloc(items, capacity * sizeof(T)); - else - items = (T*)malloc(capacity * sizeof(T)); - } + if (length == capacity) + reserve(capacity + capacity / 2); + items[length] = item; return length++; } @@ -1681,6 +1685,12 @@ struct Array { items[i] = items[i + 1]; } + void resize(int length) { + if (capacity < length) + reserve(length); + this->length = length; + } + void clear() { length = 0; free(items); diff --git a/src/video.h b/src/video.h index 96b0380..d3c779a 100644 --- a/src/video.h +++ b/src/video.h @@ -1131,6 +1131,156 @@ struct Video { } }; +// based on https://wiki.multimedia.cx/index.php/Sega_FILM + struct Cinepak : Decoder { + + struct Chunk { + int offset; + int size; + uint32 info[2]; + } *chunks; + + int chunksCount; + int audioChunkIndex; + int audioChunkPos; + Array audioChunkFrames; + + int videoChunkIndex; + int videoChunkPos; + Array videoChunkData; + + Cinepak(Stream *stream) : Decoder(stream), chunks(NULL), audioChunkIndex(-1), audioChunkPos(0), videoChunkIndex(-1), videoChunkPos(0) { + ASSERTV(stream->readLE32() == FOURCC("FILM")); + int sampleOffset = stream->readBE32(); + stream->seek(4); // skip version 1.06 + stream->seek(4); // skip reserved + ASSERTV(stream->readLE32() == FOURCC("FDSC")); + ASSERTV(stream->readBE32() == 32); + ASSERTV(stream->readLE32() == FOURCC("cvid")); + height = stream->readBE32(); + width = stream->readBE32(); + ASSERTV(stream->read() == 24); + channels = stream->read(); + ASSERT(channels == 2); + ASSERTV(stream->read() == 16); + ASSERTV(stream->read() == 0); + freq = stream->readBE16(); + ASSERT(freq == 22254); + + stream->seek(6); + ASSERTV(stream->readLE32() == FOURCC("STAB")); + stream->seek(4); // skip STAB length + fps = stream->readBE32() / 2; + chunksCount = stream->readBE32(); + chunks = new Chunk[chunksCount]; + for (int i = 0; i < chunksCount; i++) { + Chunk &c = chunks[i]; + c.offset = stream->readBE32() + sampleOffset; + c.size = stream->readBE32(); + c.info[0] = stream->readBE32(); + c.info[1] = stream->readBE32(); + } + } + + virtual ~Cinepak() { + delete[] chunks; + } + + virtual bool decodeVideo(Color32 *pixels) { + if (audioChunkIndex >= chunksCount) + return false; + /* + // TODO: sega cinepak film decoder + // get next audio chunk + if (videoChunkPos >= videoChunkData.length) { + videoChunkPos = 0; + + while (++videoChunkIndex < chunksCount) { + if (chunks[videoChunkIndex].info[0] != 0xFFFFFFFF || chunks[videoChunkIndex].info[1] != 1) + break; + } + + if (videoChunkIndex >= chunksCount) + return true; + + const Chunk &chunk = chunks[videoChunkIndex]; + + { + OS_LOCK(Sound::lock); + stream->setPos(chunk.offset); + videoChunkData.resize(chunk.size); + stream->raw(videoChunkData.items, videoChunkData.length); + } + } + + // TODO: decode + Stream data(NULL, videoChunkData.items + videoChunkPos, videoChunkData.length - videoChunkPos); + union FrameHeader { + struct { uint32 flags:8, size:24; }; + uint32 value; + } hdr; + + hdr.value = data.readBE32(); + ASSERT(hdr.size <= videoChunkData.length - videoChunkPos); + videoChunkPos += hdr.size; + */ + for (int y = 0; y < height; y++) + for (int x = 0; x < width; x++) { + Color32 c; + c.r = c.g = c.b = x ^ y; + c.a = 255; + pixels[y * width + x] = c; + } + + return true; + } + + virtual int decode(Sound::Frame *frames, int count) { + if (audioChunkIndex >= chunksCount) { + memset(frames, 0, count); + return count; + } + + // get next audio chunk + if (audioChunkPos >= audioChunkFrames.length) { + audioChunkPos = 0; + + while (++audioChunkIndex < chunksCount) { + if (chunks[audioChunkIndex].info[0] == 0xFFFFFFFF) + break; + } + + if (audioChunkIndex >= chunksCount) { + memset(frames, 0, count); + return count; + } + + const Chunk &chunk = chunks[audioChunkIndex]; + + audioChunkFrames.resize(chunk.size / sizeof(Sound::Frame)); + + stream->setPos(chunk.offset); + // read LEFT channel samples + for (int i = 0; i < audioChunkFrames.length; i++) + audioChunkFrames[i].L = stream->readBE16(); + // read RIGHT channel samples + for (int i = 0; i < audioChunkFrames.length; i++) + audioChunkFrames[i].R = stream->readBE16(); + } + + for (int i = 0; i < count; i += 2) { + frames[i + 0] = audioChunkFrames[audioChunkPos]; + frames[i + 1] = audioChunkFrames[audioChunkPos++]; + + if (audioChunkPos >= audioChunkFrames.length) + return i + 2; + } + + return count; + } + }; + + Decoder *decoder; Texture *frameTex[2]; Color32 *frameData; @@ -1144,11 +1294,15 @@ struct Video { if (!stream) return; - uint32 magic; - stream->read(magic); + uint32 magic = stream->readLE32(); stream->seek(-4); - if (magic == 0x6F4D5241) + float pitch = 1.0f; + + if (magic == FOURCC("FILM")) { + decoder = new Cinepak(stream); + pitch = decoder->freq / 22050.0f; // 22254 / 22050 = 1.00925 + } else if (magic == FOURCC("ARMo")) decoder = new Escape(stream); else decoder = new STR(stream); @@ -1160,6 +1314,7 @@ struct Video { frameTex[i] = new Texture(decoder->width, decoder->height, FMT_RGBA, 0, frameData); sample = Sound::play(decoder); + sample->pitch = pitch; step = 1.0f / decoder->fps; stepTimer = step;