├── demo.bmp ├── icon.bmp ├── screenshot.h ├── tools ├── Makefile ├── ripwinfont.cpp ├── decode_ne.cpp ├── convert_wgp.cpp └── decode_mov.cpp ├── decoder.h ├── random.h ├── scaler.h ├── str.h ├── util.h ├── random.cpp ├── mixer.h ├── fs.h ├── util.cpp ├── file.h ├── Makefile ├── win16.cpp ├── parser_dlg.cpp ├── intern.h ├── README.TXT ├── screenshot.cpp ├── systemstub.h ├── str.cpp ├── avi_player.h ├── decoder.cpp ├── mixer_sdl.cpp ├── main.cpp ├── menu.cpp ├── file.cpp ├── bag.cpp ├── saveload.cpp ├── fs.cpp ├── main_libretro.cpp ├── avi_player.cpp ├── mixer_soft.cpp ├── dialogue.cpp ├── resource.cpp ├── game.h └── parser_scn.cpp /demo.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cyxx/bermuda/HEAD/demo.bmp -------------------------------------------------------------------------------- /icon.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cyxx/bermuda/HEAD/icon.bmp -------------------------------------------------------------------------------- /screenshot.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef SCREENSHOT_H__ 3 | #define SCREENSHOT_H__ 4 | 5 | #include 6 | 7 | void saveTGA(const char *filename, const uint8_t *rgb, int w, int h); 8 | 9 | #endif 10 | -------------------------------------------------------------------------------- /tools/Makefile: -------------------------------------------------------------------------------- 1 | 2 | all: convert_wgp decode_mov decode_ne 3 | 4 | convert_wgp: convert_wgp.o 5 | $(CXX) -o $@ $^ -lz 6 | 7 | decode_mov: decode_mov.o 8 | $(CXX) -o $@ $^ 9 | 10 | decode_ne: decode_ne.o 11 | $(CXX) -o $@ $^ 12 | 13 | clean: 14 | rm -f *.o 15 | -------------------------------------------------------------------------------- /decoder.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Bermuda Syndrome engine rewrite 3 | * Copyright (C) 2007-2011 Gregory Montoir 4 | */ 5 | 6 | #ifndef DECODER_H__ 7 | #define DECODER_H__ 8 | 9 | #include "intern.h" 10 | 11 | extern int decodeLzss(const uint8_t *src, uint8_t *dst); 12 | extern int decodeZlib(const uint8_t *src, uint8_t *dst); 13 | 14 | #endif // DECODER_H__ 15 | -------------------------------------------------------------------------------- /random.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Bermuda Syndrome engine rewrite 3 | * Copyright (C) 2007-2011 Gregory Montoir 4 | */ 5 | 6 | #ifndef RANDOM_H__ 7 | #define RANDOM_H__ 8 | 9 | #include "intern.h" 10 | 11 | struct RandomGenerator { 12 | RandomGenerator(); 13 | 14 | void setSeed(uint16_t seed); 15 | uint16_t getNumber(); 16 | 17 | uint32_t _randomSeed; 18 | }; 19 | 20 | #endif // RANDOM_H__ 21 | -------------------------------------------------------------------------------- /scaler.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef SCALER_H__ 3 | #define SCALER_H__ 4 | 5 | #include 6 | 7 | #define SCALER_TAG 1 8 | 9 | struct Scaler { 10 | int tag; 11 | const char *name; 12 | int factorMin, factorMax; 13 | void (*scale32)(int factor, const uint32_t *src, uint32_t *dst, int w, int h); 14 | }; 15 | 16 | extern "C" { 17 | const Scaler *getScaler(); 18 | } 19 | 20 | #endif 21 | -------------------------------------------------------------------------------- /str.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Bermuda Syndrome engine rewrite 3 | * Copyright (C) 2007-2011 Gregory Montoir 4 | */ 5 | 6 | #ifndef STR_H__ 7 | #define STR_H__ 8 | 9 | #include "intern.h" 10 | 11 | extern void stringToLowerCase(char *p); 12 | extern void stringToUpperCase(char *p); 13 | extern char *stringTrimRight(char *p); 14 | extern char *stringTrimLeft(char *p); 15 | extern char *stringNextToken(char **p); 16 | extern char *stringNextTokenEOL(char **p); 17 | extern void stringStripComments(char *p); 18 | extern bool stringEndsWith(const char *p, const char *suf); 19 | 20 | #endif // STR_H__ 21 | -------------------------------------------------------------------------------- /util.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Bermuda Syndrome engine rewrite 3 | * Copyright (C) 2007-2011 Gregory Montoir 4 | */ 5 | 6 | #ifndef UTIL_H__ 7 | #define UTIL_H__ 8 | 9 | #include "intern.h" 10 | 11 | enum { 12 | DBG_INFO = 1 << 0, 13 | DBG_GAME = 1 << 1, 14 | DBG_OPCODES = 1 << 2, 15 | DBG_RES = 1 << 3, 16 | DBG_DIALOGUE = 1 << 4, 17 | DBG_MIXER = 1 << 5, 18 | DBG_WIN16 = 1 << 6 19 | }; 20 | 21 | extern uint16_t g_debugMask; 22 | 23 | extern void debug(uint16_t cm, const char *msg, ...); 24 | extern void error(const char *msg, ...); 25 | extern void warning(const char *msg, ...); 26 | 27 | #endif // UTIL_H__ 28 | -------------------------------------------------------------------------------- /random.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Bermuda Syndrome engine rewrite 3 | * Copyright (C) 2007-2011 Gregory Montoir 4 | */ 5 | 6 | #include 7 | #include "random.h" 8 | 9 | RandomGenerator::RandomGenerator() { 10 | uint16_t seed = time(0); 11 | setSeed(seed); 12 | } 13 | 14 | void RandomGenerator::setSeed(uint16_t seed) { 15 | _randomSeed = seed; 16 | } 17 | 18 | // Borland C random generator 19 | uint16_t RandomGenerator::getNumber() { 20 | const uint16_t randomSeedLo = _randomSeed & 0xFFFF; 21 | const uint16_t randomSeedHi = _randomSeed >> 16; 22 | uint16_t rnd = 0x15A * randomSeedLo; 23 | if (randomSeedHi != 0) { 24 | rnd += 0x4E35 * randomSeedHi; 25 | } 26 | _randomSeed = (rnd << 16) | ((0x4E35 * randomSeedLo) & 0xFFFF); 27 | ++_randomSeed; 28 | return _randomSeed & 0x7FFF; 29 | } 30 | -------------------------------------------------------------------------------- /mixer.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Bermuda Syndrome engine rewrite 3 | * Copyright (C) 2007-2011 Gregory Montoir 4 | */ 5 | 6 | #ifndef MIXER_H__ 7 | #define MIXER_H__ 8 | 9 | #include "intern.h" 10 | 11 | struct File; 12 | struct SystemStub; 13 | 14 | struct Mixer { 15 | static const int kDefaultSoundId = -1; 16 | 17 | Mixer() {} 18 | virtual ~Mixer() {} 19 | 20 | virtual void open() = 0; 21 | virtual void close() = 0; 22 | 23 | virtual void playSound(File *f, int *id) = 0; 24 | virtual void playMusic(File *f, int *id) = 0; 25 | virtual bool isSoundPlaying(int id) = 0; 26 | virtual void stopSound(int id) = 0; 27 | virtual void stopAll() = 0; 28 | 29 | virtual void setMusicMix(void *param, void (*mix)(void *, uint8_t *, int)) = 0; 30 | }; 31 | 32 | Mixer *Mixer_SDL_create(SystemStub *); 33 | Mixer *Mixer_Software_create(SystemStub *); 34 | 35 | #endif // MIXER_H__ 36 | -------------------------------------------------------------------------------- /fs.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Bermuda Syndrome engine rewrite 3 | * Copyright (C) 2007-2011 Gregory Montoir 4 | */ 5 | 6 | #ifndef FS_H__ 7 | #define FS_H__ 8 | 9 | #include "intern.h" 10 | 11 | struct File; 12 | struct FileSystem_impl; 13 | 14 | struct FileSystem { 15 | FileSystem(const char *dataPath); 16 | ~FileSystem(); 17 | 18 | File *openFile(const char *path, bool errorIfNotFound = true); 19 | void closeFile(File *f); 20 | 21 | bool existFile(const char *path); 22 | 23 | FileSystem_impl *_impl; 24 | bool _romfs; 25 | }; 26 | 27 | struct FileHolder { 28 | FileHolder(FileSystem &fs, const char *path, bool errorIfNotFound = true) 29 | : _fs(fs) { 30 | _fp = _fs.openFile(path, errorIfNotFound); 31 | } 32 | 33 | ~FileHolder() { 34 | _fs.closeFile(_fp); 35 | _fp = 0; 36 | } 37 | 38 | File *operator->() { 39 | return _fp; 40 | } 41 | 42 | FileSystem &_fs; 43 | File *_fp; 44 | }; 45 | 46 | #endif // FS_H__ 47 | -------------------------------------------------------------------------------- /util.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Bermuda Syndrome engine rewrite 3 | * Copyright (C) 2007-2011 Gregory Montoir 4 | */ 5 | 6 | #include 7 | #include "util.h" 8 | 9 | uint16_t g_debugMask; 10 | 11 | void debug(uint16_t cm, const char *msg, ...) { 12 | char buf[1024]; 13 | if (cm & g_debugMask) { 14 | va_list va; 15 | va_start(va, msg); 16 | vsprintf(buf, msg, va); 17 | va_end(va); 18 | printf("%s\n", buf); 19 | fflush(stdout); 20 | } 21 | } 22 | 23 | void error(const char *msg, ...) { 24 | char buf[1024]; 25 | va_list va; 26 | va_start(va, msg); 27 | vsprintf(buf, msg, va); 28 | va_end(va); 29 | fprintf(stderr, "ERROR: %s!\n", buf); 30 | fflush(stderr); 31 | exit(-1); 32 | } 33 | 34 | void warning(const char *msg, ...) { 35 | char buf[1024]; 36 | va_list va; 37 | va_start(va, msg); 38 | vsprintf(buf, msg, va); 39 | va_end(va); 40 | fprintf(stderr, "WARNING: %s!\n", buf); 41 | fflush(stderr); 42 | } 43 | -------------------------------------------------------------------------------- /file.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Bermuda Syndrome engine rewrite 3 | * Copyright (C) 2007-2011 Gregory Montoir 4 | */ 5 | 6 | #ifndef FILE_H__ 7 | #define FILE_H__ 8 | 9 | #include "intern.h" 10 | 11 | struct File_impl; 12 | 13 | struct File { 14 | File(); 15 | File(uint32_t offset, uint32_t size); 16 | File(const uint8_t *ptr, uint32_t len); 17 | File(uint8_t *ptr, uint32_t len); 18 | ~File(); 19 | 20 | bool open(const char *path, const char *mode = "rb"); 21 | void close(); 22 | bool ioErr() const; 23 | uint32_t size(); 24 | uint32_t tell(); 25 | void seek(int offs, int origin = SEEK_SET); 26 | uint32_t read(void *ptr, uint32_t len); 27 | uint8_t readByte(); 28 | uint16_t readUint16LE(); 29 | uint32_t readUint32LE(); 30 | uint32_t readUint32BE(); 31 | void write(void *ptr, uint32_t size); 32 | void writeByte(uint8_t b); 33 | void writeUint16LE(uint16_t n); 34 | void writeUint32LE(uint32_t n); 35 | 36 | char *_path; 37 | File_impl *_impl; 38 | }; 39 | 40 | #endif // FILE_H__ 41 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | # List of possible defines : 3 | # BERMUDA_WIN32 : enable windows directory browsing code 4 | # BERMUDA_POSIX : enable unix/posix directory browsing code 5 | # BERMUDA_VORBIS : enable playback of digital soundtracks (22 khz mono .ogg files) 6 | 7 | #DEFINES = -DBERMUDA_WIN32 -DBERMUDA_VORBIS 8 | DEFINES = -DBERMUDA_POSIX -DBERMUDA_VORBIS -DBERMUDA_ZLIB 9 | VORBIS_LIBS = -lvorbisfile -lvorbis -logg 10 | 11 | SDL_CFLAGS = `sdl2-config --cflags` 12 | SDL_LIBS = `sdl2-config --libs` -lSDL2_mixer 13 | 14 | CXXFLAGS = -g -O -Wall $(SDL_CFLAGS) $(DEFINES) 15 | 16 | OBJDIR = obj 17 | 18 | SRCS = avi_player.cpp bag.cpp decoder.cpp dialogue.cpp file.cpp fs.cpp game.cpp \ 19 | main.cpp menu.cpp mixer_sdl.cpp mixer_soft.cpp opcodes.cpp parser_dlg.cpp parser_scn.cpp \ 20 | random.cpp resource.cpp saveload.cpp screenshot.cpp staticres.cpp str.cpp systemstub_sdl.cpp \ 21 | util.cpp win16.cpp 22 | 23 | OBJS = $(SRCS:.cpp=.o) 24 | DEPS = $(SRCS:.cpp=.d) 25 | 26 | all: $(OBJDIR) bs 27 | 28 | bs: $(addprefix $(OBJDIR)/, $(OBJS)) 29 | $(CXX) $(LDFLAGS) -o $@ $^ $(SDL_LIBS) $(VORBIS_LIBS) -lz 30 | 31 | $(OBJDIR): 32 | mkdir $(OBJDIR) 33 | 34 | $(OBJDIR)/%.o: %.cpp 35 | $(CXX) $(CXXFLAGS) -MMD -c $< -o $@ 36 | 37 | clean: 38 | rm -f $(OBJDIR)/*.o $(OBJDIR)/*.d 39 | 40 | -include $(addprefix $(OBJDIR)/, $(DEPS)) 41 | -------------------------------------------------------------------------------- /win16.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Bermuda Syndrome engine rewrite 3 | * Copyright (C) 2007-2011 Gregory Montoir 4 | */ 5 | 6 | #include "file.h" 7 | #include "game.h" 8 | #include "mixer.h" 9 | #include "systemstub.h" 10 | 11 | int Game::win16_sndPlaySound(int op, void *data) { 12 | debug(DBG_WIN16, "win16_sndPlaySound() %d", op); 13 | switch (op) { 14 | case 22: 15 | if (!_mixer->isSoundPlaying(_mixerSoundId)) { 16 | return 1; 17 | } 18 | break; 19 | case 3: { 20 | const char *fileName = (const char *)data; 21 | File *f = _fs.openFile(fileName, false); 22 | if (f) { 23 | _mixer->playSound(f, &_mixerSoundId); 24 | _fs.closeFile(f); 25 | } else { 26 | warning("Unable to open wav file '%s'", fileName); 27 | } 28 | } 29 | break; 30 | case 6: 31 | case 7: 32 | _mixer->stopSound(_mixerSoundId); 33 | break; 34 | default: 35 | warning("Unhandled op %d in win16_sndPlaySound", op); 36 | break; 37 | } 38 | return 0; 39 | } 40 | 41 | void Game::win16_stretchBits(SceneBitmap *bits, int srcHeight, int srcWidth, int srcY, int srcX, int dstHeight, int dstWidth, int dstY, int dstX) { 42 | debug(DBG_WIN16, "win16_stretchBits() %d,%d %d,%d", srcX, srcY, srcWidth, srcHeight); 43 | assert(srcWidth == dstWidth && srcHeight == dstHeight); 44 | const uint8_t *src = bits->bits + srcY * bits->pitch + srcX; 45 | if (dstX >= kGameScreenWidth) { 46 | return; 47 | } else if (dstX + dstWidth > kGameScreenWidth) { 48 | dstWidth = kGameScreenWidth - dstX; 49 | } 50 | if (dstY >= kGameScreenHeight) { 51 | return; 52 | } else if (dstY + dstHeight > kGameScreenHeight) { 53 | dstHeight = kGameScreenHeight - dstY; 54 | } 55 | _stub->copyRect(dstX, dstY, dstWidth, dstHeight, src, bits->pitch); 56 | } 57 | -------------------------------------------------------------------------------- /parser_dlg.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Bermuda Syndrome engine rewrite 3 | * Copyright (C) 2007-2011 Gregory Montoir 4 | */ 5 | 6 | #include "fs.h" 7 | #include "file.h" 8 | #include "game.h" 9 | #include "str.h" 10 | 11 | enum ParserToken { 12 | kParserTokenGoto, 13 | kParserTokenEnd, 14 | kParserTokenThen, 15 | kParserTokenEOS, 16 | kParserTokenUnknown 17 | }; 18 | 19 | static ParserToken _currentToken; 20 | 21 | static ParserToken getNextToken(char **s) { 22 | char *token = stringNextToken(s); 23 | if (!token || !*token) { 24 | return kParserTokenEOS; 25 | } else if (strcmp(token, "Goto") == 0) { 26 | return kParserTokenGoto; 27 | } else if (strcmp(token, "End") == 0) { 28 | return kParserTokenEnd; 29 | } else if (strcmp(token, "->") == 0) { 30 | return kParserTokenThen; 31 | } else { 32 | return kParserTokenUnknown; 33 | } 34 | } 35 | 36 | void Game::parseDLG() { 37 | _dialogueChoiceSize = 0; 38 | for (char *s = _dialogueDescriptionBuffer; s; ) { 39 | assert(_dialogueChoiceSize < NUM_DIALOG_ENTRIES); 40 | DialogueChoice *dc = &_dialogueChoiceData[_dialogueChoiceSize]; 41 | dc->id = stringNextToken(&s); 42 | if (!s) { 43 | break; 44 | } 45 | _currentToken = getNextToken(&s); 46 | switch (_currentToken) { 47 | case kParserTokenGoto: 48 | dc->gotoFlag = true; 49 | break; 50 | case kParserTokenEnd: 51 | dc->gotoFlag = false; 52 | break; 53 | default: 54 | error("Unexpected token %d", _currentToken); 55 | break; 56 | } 57 | dc->nextId = stringNextToken(&s); 58 | dc->speechSoundFile = stringNextToken(&s); 59 | _currentToken = getNextToken(&s); 60 | if (_currentToken != kParserTokenThen) { 61 | error("Unexpected token %d", _currentToken); 62 | } 63 | dc->text = stringNextTokenEOL(&s); 64 | ++_dialogueChoiceSize; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /intern.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Bermuda Syndrome engine rewrite 3 | * Copyright (C) 2007-2011 Gregory Montoir 4 | */ 5 | 6 | #ifndef INTERN_H__ 7 | #define INTERN_H__ 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #include "util.h" 17 | 18 | #ifndef ARRAYSIZE 19 | #define ARRAYSIZE(a) (sizeof(a)/sizeof(a[0])) 20 | #endif 21 | 22 | struct Point { 23 | int x, y; 24 | }; 25 | 26 | struct Rect { 27 | int x, y; 28 | int w, h; 29 | }; 30 | 31 | inline uint16_t READ_LE_UINT16(const void *ptr) { 32 | const uint8_t *b = (const uint8_t *)ptr; 33 | return (b[1] << 8) | b[0]; 34 | } 35 | 36 | inline uint32_t READ_LE_UINT32(const void *ptr) { 37 | const uint8_t *b = (const uint8_t *)ptr; 38 | return (b[3] << 24) | (b[2] << 16) | (b[1] << 8) | b[0]; 39 | } 40 | 41 | inline uint16_t READ_BE_UINT16(const void *ptr) { 42 | const uint8_t *b = (const uint8_t *)ptr; 43 | return (b[0] << 8) | b[1]; 44 | } 45 | 46 | inline uint32_t READ_BE_UINT32(const void *ptr) { 47 | const uint8_t *b = (const uint8_t *)ptr; 48 | return (b[0] << 24) | (b[1] << 16) | (b[2] << 8) | b[3]; 49 | } 50 | 51 | #ifndef MIN 52 | template 53 | inline T MIN(T v1, T v2) { 54 | return (v1 < v2) ? v1 : v2; 55 | } 56 | #endif 57 | 58 | #ifndef MAX 59 | template 60 | inline T MAX(T v1, T v2) { 61 | return (v1 > v2) ? v1 : v2; 62 | } 63 | #endif 64 | 65 | template 66 | inline T ABS(T t) { 67 | return (t < 0) ? -t : t; 68 | } 69 | 70 | template 71 | inline void SWAP(T &a, T &b) { 72 | T tmp = a; a = b; b = tmp; 73 | } 74 | 75 | template 76 | inline T CLIP(T t, T tmin, T tmax) { 77 | if (t < tmin) { 78 | return tmin; 79 | } else if (t > tmax) { 80 | return tmax; 81 | } else { 82 | return t; 83 | } 84 | } 85 | 86 | #endif // INTERN_H__ 87 | -------------------------------------------------------------------------------- /README.TXT: -------------------------------------------------------------------------------- 1 | 2 | Bermuda Syndrome README 3 | Release version: 0.1.7 4 | ------------------------------------------------------------------------------- 5 | 6 | 7 | About: 8 | ------ 9 | 10 | This program is a re-implementation of the engine used in the game Bermuda 11 | Syndrome made by Century Interactive and published by BMG in 1995. The original 12 | data files are required (retail or demo versions). 13 | 14 | This rewrite is based on the reversing of the original Windows executable. More 15 | informations about the game can be found at [1] and [2]. 16 | 17 | 18 | Running: 19 | -------- 20 | 21 | By default, the engine will try to load the game data files from the 'DATA' 22 | directory. The savestates are saved in the current directory. If you have the 23 | CD game soundtrack, you can rip the tracks to 22 Khz stereo Vorbis .ogg files. 24 | 25 | The paths can be changed using command line switches : 26 | 27 | Usage: bs [OPTIONS]... 28 | --datapath=PATH Path to data files (default 'DATA') 29 | --savepath=PATH Path to save files (default '.') 30 | --musicpath=MUSIC Path to music files (default 'MUSIC') 31 | --fullscreen Fullscreen display 32 | --widescreen=MODE Widescreen mode ('default', '4:3' or '16:9') 33 | 34 | Game hotkeys : 35 | 36 | Arrow Keys move Jack 37 | Tab display bag 38 | Ctrl display status bar 39 | Shift/Space use weapon 40 | Enter skip dialogue speech 41 | 42 | Other hotkeys: 43 | 44 | C capture screenshot as .tga 45 | S save game state 46 | L load game state 47 | + increase game state slot 48 | - decrease game state slot 49 | F enable fast mode 50 | W toggle fullscreen/windowed display 51 | 52 | For the Korean version, you need the 'BT16.TBM' file that is installed with 53 | Windows 3.x. The file is 330328 bytes long and should be copied in the same 54 | directory as the 'bs' executable. 55 | 56 | 57 | Contact: 58 | -------- 59 | 60 | Gregory Montoir, cyx@users.sourceforge.net 61 | 62 | 63 | URLs: 64 | ----- 65 | 66 | [1] http://www.mobygames.com/game/bermuda-syndrome 67 | [2] http://www.reakktor.com/about 68 | -------------------------------------------------------------------------------- /screenshot.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "screenshot.h" 3 | #include "file.h" 4 | 5 | static void TO_LE16(uint8_t *dst, uint16_t value) { 6 | for (int i = 0; i < 2; ++i) { 7 | dst[i] = value & 255; 8 | value >>= 8; 9 | } 10 | } 11 | 12 | #define kTgaImageTypeUncompressedTrueColor 2 13 | #define kTgaImageTypeRunLengthEncodedTrueColor 10 14 | #define kTgaDirectionTop (1 << 5) 15 | 16 | static const int TGA_HEADER_SIZE = 18; 17 | 18 | void saveTGA(const char *filename, const uint8_t *rgba, int w, int h) { 19 | 20 | static const uint8_t kImageType = kTgaImageTypeRunLengthEncodedTrueColor; 21 | uint8_t buffer[TGA_HEADER_SIZE]; 22 | buffer[0] = 0; // ID Length 23 | buffer[1] = 0; // ColorMap Type 24 | buffer[2] = kImageType; 25 | TO_LE16(buffer + 3, 0); // ColorMap Start 26 | TO_LE16(buffer + 5, 0); // ColorMap Length 27 | buffer[7] = 0; // ColorMap Bits 28 | TO_LE16(buffer + 8, 0); // X-origin 29 | TO_LE16(buffer + 10, 0); // Y-origin 30 | TO_LE16(buffer + 12, w); // Image Width 31 | TO_LE16(buffer + 14, h); // Image Height 32 | buffer[16] = 24; // Pixel Depth 33 | buffer[17] = kTgaDirectionTop; // Descriptor 34 | 35 | File f; 36 | if (f.open(filename, "wb")) { 37 | f.write(buffer, sizeof(buffer)); 38 | if (kImageType == kTgaImageTypeUncompressedTrueColor) { 39 | for (int i = 0; i < w * h; ++i) { 40 | f.writeByte(rgba[0]); 41 | f.writeByte(rgba[1]); 42 | f.writeByte(rgba[2]); 43 | rgba += 4; 44 | } 45 | } else { 46 | assert(kImageType == kTgaImageTypeRunLengthEncodedTrueColor); 47 | int prevColor = rgba[2] + (rgba[1] << 8) + (rgba[0] << 16); rgba += 4; 48 | int count = 0; 49 | for (int i = 1; i < w * h; ++i) { 50 | int color = rgba[2] + (rgba[1] << 8) + (rgba[0] << 16); rgba += 4; 51 | if (prevColor == color && count < 127) { 52 | ++count; 53 | continue; 54 | } 55 | f.writeByte(count | 0x80); 56 | f.writeByte((prevColor >> 16) & 255); 57 | f.writeByte((prevColor >> 8) & 255); 58 | f.writeByte( prevColor & 255); 59 | count = 0; 60 | prevColor = color; 61 | } 62 | if (count != 0) { 63 | f.writeByte(count | 0x80); 64 | f.writeByte((prevColor >> 16) & 255); 65 | f.writeByte((prevColor >> 8) & 255); 66 | f.writeByte( prevColor & 255); 67 | } 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /systemstub.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Bermuda Syndrome engine rewrite 3 | * Copyright (C) 2007-2011 Gregory Montoir 4 | */ 5 | 6 | #ifndef SYSTEMSTUB_H__ 7 | #define SYSTEMSTUB_H__ 8 | 9 | #include "intern.h" 10 | 11 | struct Mixer; 12 | 13 | struct PlayerInput { 14 | enum { 15 | DIR_UP = 1 << 0, 16 | DIR_DOWN = 1 << 1, 17 | DIR_LEFT = 1 << 2, 18 | DIR_RIGHT = 1 << 3 19 | }; 20 | uint8_t dirMask; 21 | bool enter; // use, current action 22 | bool space; // take gun, shoot 23 | bool shift; // put back gun, run 24 | bool ctrl; // toggle status window 25 | bool tab; // toggle bag window 26 | bool escape; // quit 27 | bool leftMouseButton, rightMouseButton; 28 | int mouseX, mouseY; 29 | bool save; 30 | bool load; 31 | int stateSlot; 32 | bool fastMode; 33 | }; 34 | 35 | enum { 36 | SCREEN_MODE_DEFAULT, 37 | SCREEN_MODE_4_3, 38 | SCREEN_MODE_16_9 39 | }; 40 | 41 | struct SystemStub { 42 | typedef void (*AudioCallback)(void *param, uint8_t *buf, int len); 43 | 44 | bool _quit; 45 | PlayerInput _pi; 46 | 47 | virtual ~SystemStub() {} 48 | 49 | virtual void init(const char *title, int w, int h, bool fullscreen, int screenMode) = 0; 50 | virtual void destroy() = 0; 51 | 52 | virtual void setIcon(const uint8_t *data, int size) = 0; 53 | virtual void showCursor(bool show) = 0; 54 | 55 | virtual void setPalette(const uint8_t *pal, int n) = 0; 56 | virtual void fillRect(int x, int y, int w, int h, uint8_t color) = 0; 57 | virtual void copyRect(int x, int y, int w, int h, const uint8_t *buf, int pitch, bool transparent = false) = 0; 58 | virtual void darkenRect(int x, int y, int w, int h) = 0; 59 | virtual void copyRectWidescreen(int w, int h, const uint8_t *buf, int pitch) = 0; 60 | virtual void clearWidescreen() = 0; 61 | virtual void updateScreen() = 0; 62 | 63 | virtual void setYUV(bool flag, int w, int h) = 0; 64 | virtual uint8_t *lockYUV(int *pitch) = 0; 65 | virtual void unlockYUV() = 0; 66 | 67 | virtual void processEvents() = 0; 68 | virtual void sleep(int duration) = 0; 69 | virtual uint32_t getTimeStamp() = 0; 70 | 71 | virtual void lockAudio() = 0; 72 | virtual void unlockAudio() = 0; 73 | virtual void startAudio(AudioCallback callback, void *param) = 0; 74 | virtual void stopAudio() = 0; 75 | virtual int getOutputSampleRate() = 0; 76 | 77 | virtual Mixer *getMixer() = 0; 78 | }; 79 | 80 | extern SystemStub *SystemStub_SDL_create(); 81 | 82 | #endif // SYSTEMSTUB_H__ 83 | -------------------------------------------------------------------------------- /str.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Bermuda Syndrome engine rewrite 3 | * Copyright (C) 2007-2011 Gregory Montoir 4 | */ 5 | 6 | #include "str.h" 7 | 8 | void stringToLowerCase(char *p) { 9 | for (; *p; ++p) { 10 | if (*p >= 'A' && *p <= 'Z') { 11 | *p += 'a' - 'A'; 12 | } 13 | } 14 | } 15 | 16 | void stringToUpperCase(char *p) { 17 | for (; *p; ++p) { 18 | if (*p >= 'a' && *p <= 'z') { 19 | *p += 'A' - 'a'; 20 | } 21 | } 22 | } 23 | 24 | static bool isIgnorableChar(char c) { 25 | return (c == 0x60); // C1_05.SCN 26 | } 27 | 28 | static bool isWhitespace(char c) { 29 | return (c == ' ' || c == '\t' || c == '\r' || c == '\n' || isIgnorableChar(c)); 30 | } 31 | 32 | char *stringTrimRight(char *p) { 33 | char *s = p; 34 | int len = strlen(p); 35 | if (len != 0) { 36 | p += len - 1; 37 | while (p >= s && isWhitespace(*p)) { 38 | *p = 0; 39 | --p; 40 | } 41 | } 42 | return s; 43 | } 44 | 45 | char *stringTrimLeft(char *p) { 46 | while (*p && isWhitespace(*p)) { 47 | *p = 0; 48 | ++p; 49 | } 50 | return p; 51 | } 52 | 53 | char *stringNextToken(char **p) { 54 | char *token = stringTrimLeft(*p); 55 | char *end = token; 56 | if (token) { 57 | if (*token == '"') { 58 | ++token; 59 | while (*end && *end != '"') { 60 | ++end; 61 | } 62 | } else { 63 | while (*end && !isWhitespace(*end)) { 64 | ++end; 65 | } 66 | } 67 | if (*end) { 68 | *end = 0; 69 | *p = end + 1; 70 | } else { 71 | *p = 0; 72 | } 73 | } 74 | return token; 75 | } 76 | 77 | char *stringNextTokenEOL(char **p) { 78 | char *token = stringTrimLeft(*p); 79 | if (token) { 80 | char *buf = strstr(token, "\r\n"); 81 | if (buf) { 82 | *buf = 0; 83 | *p = &buf[2]; 84 | } else { 85 | *p = 0; 86 | } 87 | } 88 | return token; 89 | } 90 | 91 | void stringStripComments(char *p) { 92 | for (char *str = p; *str; ++str) { 93 | if (*str == '\t') { 94 | *str = ' '; 95 | } 96 | } 97 | while (true) { 98 | char *cmt = strstr(p, "//"); 99 | if (!cmt) { 100 | break; 101 | } 102 | const char *eol = strstr(cmt, "\r\n"); 103 | while (*cmt && cmt != eol) { 104 | *cmt++ = ' '; 105 | } 106 | } 107 | char *cmt = p; 108 | while (true) { 109 | while (*cmt && isWhitespace(*cmt)) { 110 | ++cmt; 111 | } 112 | if (!*cmt) { 113 | break; 114 | } 115 | char *eol = strstr(cmt, "\r\n"); 116 | if (cmt[0] == ';' || cmt[0] == '/') { 117 | while (*cmt && cmt != eol) { 118 | *cmt++ = ' '; 119 | } 120 | } 121 | if (!eol) { 122 | break; 123 | } 124 | cmt = &eol[2]; 125 | } 126 | } 127 | 128 | bool stringEndsWith(const char *p, const char *suf) { 129 | const int offs = strlen(p) - strlen(suf); 130 | return (offs >= 0) && (strcmp(p + offs, suf) == 0); 131 | } 132 | -------------------------------------------------------------------------------- /avi_player.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Bermuda Syndrome engine rewrite 3 | * Copyright (C) 2007-2011 Gregory Montoir 4 | */ 5 | 6 | #ifndef AVI_PLAYER_H__ 7 | #define AVI_PLAYER_H__ 8 | 9 | #include "intern.h" 10 | 11 | enum AVI_ChunkType { 12 | kChunkAudioType, 13 | kChunkVideoType 14 | }; 15 | 16 | struct AVI_Chunk { 17 | AVI_ChunkType type; 18 | uint8_t *data; 19 | int dataSize; 20 | }; 21 | 22 | struct File; 23 | 24 | struct AVI_Demuxer { 25 | enum { 26 | kSizeOfChunk_avih = 56, 27 | kSizeOfChunk_strh = 56, 28 | kSizeOfChunk_waveformat = 16, 29 | kSizeOfChunk_bitmapinfo = 40 30 | }; 31 | 32 | bool open(File *f); 33 | void close(); 34 | 35 | bool readHeader(); 36 | bool readHeader_avih(); 37 | bool readHeader_strh(); 38 | bool readHeader_strf_auds(); 39 | bool readHeader_strf_vids(); 40 | 41 | bool readNextChunk(AVI_Chunk &chunk); 42 | 43 | int _frames; 44 | int _width, _height; 45 | int _streams; 46 | int _frameRate; 47 | 48 | File *_f; 49 | int _recordsListSize; 50 | uint8_t *_chunkData; 51 | int _chunkDataSize; 52 | int _audioBufferSize; 53 | int _videoBufferSize; 54 | }; 55 | 56 | struct Mixer; 57 | struct SystemStub; 58 | 59 | struct Cinepak_YUV_Vector { 60 | uint8_t y[4]; 61 | uint8_t u, v; 62 | }; 63 | 64 | enum { 65 | kCinepakV1 = 0, 66 | kCinepakV4 = 1 67 | }; 68 | 69 | struct Cinepak_Decoder { 70 | enum { 71 | MAX_STRIPS = 2, 72 | MAX_VECTORS = 256 73 | }; 74 | 75 | uint8_t readByte() { 76 | return *_data++; 77 | } 78 | 79 | uint16_t readWord() { 80 | uint16_t value = READ_BE_UINT16(_data); _data += 2; 81 | return value; 82 | } 83 | 84 | uint32_t readLong() { 85 | uint32_t value = READ_BE_UINT32(_data); _data += 4; 86 | return value; 87 | } 88 | 89 | void decodeFrameV4(Cinepak_YUV_Vector *v0, Cinepak_YUV_Vector *v1, Cinepak_YUV_Vector *v2, Cinepak_YUV_Vector *v3); 90 | void decodeFrameV1(Cinepak_YUV_Vector *v); 91 | void decodeVector(Cinepak_YUV_Vector *v); 92 | void decode(const uint8_t *data, int dataSize); 93 | 94 | const uint8_t *_data; 95 | Cinepak_YUV_Vector _vectors[2][MAX_STRIPS][MAX_VECTORS]; 96 | int _w, _h; 97 | int _xPos, _yPos, _yMax; 98 | 99 | uint8_t *_yuvFrame; 100 | int _yuvPitch; 101 | }; 102 | 103 | struct AVI_SoundBufferQueue { 104 | uint8_t *buffer; 105 | int size; 106 | int offset; 107 | AVI_SoundBufferQueue *next; 108 | }; 109 | 110 | struct AVI_Player { 111 | enum { 112 | kDefaultFrameWidth = 320, 113 | kDefaultFrameHeight = 200, 114 | kSoundPreloadSize = 4, 115 | }; 116 | 117 | AVI_Player(Mixer *mixer, SystemStub *stub); 118 | ~AVI_Player(); 119 | 120 | void play(File *f); 121 | void readNextFrame(); 122 | void decodeAudioChunk(AVI_Chunk &c); 123 | void decodeVideoChunk(AVI_Chunk &c); 124 | void mix(int16_t *buf, int samples); 125 | static void mixCallback(void *param, uint8_t *buf, int len); 126 | 127 | AVI_Demuxer _demux; 128 | AVI_SoundBufferQueue *_soundQueue, *_soundTailQueue; 129 | int _soundQueuePreloadSize; 130 | Cinepak_Decoder _cinepak; 131 | Mixer *_mixer; 132 | SystemStub *_stub; 133 | }; 134 | 135 | #endif // AVI_PLAYER_H__ 136 | -------------------------------------------------------------------------------- /tools/ripwinfont.cpp: -------------------------------------------------------------------------------- 1 | 2 | // g++ ripwinfont.cpp -lgdi32 3 | #include 4 | #include 5 | #include 6 | 7 | static LOGFONT hSystemLogFont; 8 | static HFONT hSystemFont; 9 | static int gridW = 16; 10 | static int gridH = 16; 11 | 12 | static void loadSystemFont() { 13 | HGDIOBJ h = GetStockObject(SYSTEM_FONT); 14 | GetObject(h, sizeof(hSystemLogFont), &hSystemLogFont); 15 | hSystemFont = CreateFontIndirect(&hSystemLogFont); 16 | printf("hSystemFont %p w %d h %d\n", hSystemFont, hSystemLogFont.lfWidth, hSystemLogFont.lfHeight); 17 | } 18 | 19 | static void ripChar(int chr, int *dst, bool display) { 20 | HDC hDC = CreateCompatibleDC(NULL); 21 | HBITMAP hBitmap = CreateCompatibleBitmap(hDC, gridW, gridH); 22 | HGDIOBJ hPrevBitmap = SelectObject(hDC, hBitmap); 23 | HGDIOBJ hPrevFont = SelectObject(hDC, hSystemFont); 24 | 25 | for (int yPos = 0; yPos < gridH; ++yPos) { 26 | for (int xPos = 0; xPos < gridW; ++xPos) { 27 | SetPixel(hDC, xPos, yPos, RGB(0, 0, 0)); 28 | } 29 | } 30 | SetBkMode(hDC, TRANSPARENT); 31 | SetTextColor(hDC, RGB(255, 255, 255)); 32 | char c = chr & 0xFF; 33 | TextOut(hDC, 0, 0, &c, 1); 34 | if (display) { 35 | for (int yPos = 0; yPos < gridH; ++yPos) { 36 | for (int xPos = 0; xPos < gridW; ++xPos) { 37 | COLORREF c = GetPixel(hDC, xPos, yPos); 38 | if (c == 0) { 39 | printf("0"); 40 | } else { 41 | printf("1"); 42 | } 43 | } 44 | printf("\n"); 45 | } 46 | } 47 | for (int yPos = 0; yPos < gridH; ++yPos) { 48 | int bitMap = 0; 49 | for (int xPos = 0; xPos < gridW; ++xPos) { 50 | COLORREF c = GetPixel(hDC, xPos, yPos); 51 | if (c != 0) { 52 | bitMap |= 1 << xPos; 53 | } 54 | } 55 | dst[yPos] = bitMap; 56 | } 57 | 58 | SelectObject(hDC, hPrevFont); 59 | SelectObject(hDC, hPrevBitmap); 60 | DeleteObject(hBitmap); 61 | DeleteDC(hDC); 62 | } 63 | 64 | static void getCharPitch(int *src, int *p) { 65 | int maxWidth = 0; 66 | for (int yPos = 0; yPos < gridH; ++yPos) { 67 | int w = 0; 68 | for (int xPos = 0; xPos < gridW; ++xPos) { 69 | if (src[yPos] & (1 << xPos)) { 70 | w = xPos; 71 | } 72 | } 73 | if (w != 0) { 74 | ++w; 75 | if (w > maxWidth) { 76 | maxWidth = w; 77 | } 78 | } 79 | } 80 | *p = maxWidth; 81 | } 82 | 83 | static void dumpFont(int *src, int *src2) { 84 | printf("const uint16 Game::_fontData[] = {"); 85 | for (int i = 0; i < gridH * 256; ++i) { 86 | if ((i % 10) == 0) { 87 | printf("\n\t"); 88 | } 89 | printf("0x%04X, ", src[i]); 90 | } 91 | printf("\n};\n"); 92 | printf("const uint8 Game::_fontCharWidth[] = {"); 93 | for (int i = 0; i < 256; ++i) { 94 | if ((i % 16) == 0) { 95 | printf("\n\t"); 96 | } 97 | printf("0x%02X, ", src2[i]); 98 | } 99 | printf("\n};\n"); 100 | } 101 | 102 | int main(int argc, char *argv[]) { 103 | printf("sizeof(HFONT) %d sizeof(LOGFONT) %d\n", sizeof(HFONT), sizeof(LOGFONT)); 104 | loadSystemFont(); 105 | int *fontData = (int *)malloc(sizeof(int) * gridH * 256); 106 | for (int i = 0; i < 256; ++i) { 107 | ripChar(i, &fontData[gridH * i], false); 108 | } 109 | int *fontCharWidth = (int *)malloc(sizeof(int) * 256); 110 | for (int i = 0; i < 256; ++i) { 111 | getCharPitch(&fontData[gridH * i], &fontCharWidth[i]); 112 | } 113 | fontCharWidth[0x20] = 4; 114 | dumpFont(fontData, fontCharWidth); 115 | // ripChar('m', &fontData[0], true); 116 | // printf("w %d\n", fontCharWidth['m']); 117 | return 0; 118 | } 119 | -------------------------------------------------------------------------------- /decoder.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Bermuda Syndrome engine rewrite 3 | * Copyright (C) 2007-2011 Gregory Montoir 4 | */ 5 | 6 | #include "decoder.h" 7 | #ifdef BERMUDA_ZLIB 8 | #define ZLIB_CONST 9 | #include 10 | #endif 11 | 12 | struct BitStream { 13 | const uint8_t *_src; 14 | uint16_t _bits; 15 | int _len; 16 | 17 | void reset(const uint8_t *src) { 18 | _src = src; 19 | _bits = READ_LE_UINT16(_src); _src += 2; 20 | _len = 16; 21 | } 22 | 23 | bool getNextBit() { 24 | const bool bit = (_bits & (1 << (16 - _len))) != 0; 25 | --_len; 26 | if (_len == 0) { 27 | _bits = READ_LE_UINT16(_src); _src += 2; 28 | _len = 16; 29 | } 30 | return bit; 31 | } 32 | 33 | uint8_t getNextByte() { 34 | uint8_t b = *_src++; 35 | return b; 36 | } 37 | 38 | uint16_t getNextWord() { 39 | uint16_t w = READ_LE_UINT16(_src); _src += 2; 40 | return w; 41 | } 42 | }; 43 | 44 | static const bool kCheckCompressedData = true; 45 | 46 | int decodeLzss(const uint8_t *src, uint8_t *dst) { 47 | BitStream stream; 48 | int outputSize = READ_LE_UINT32(src); src += 4; 49 | int inputSize = READ_LE_UINT32(src); src += 4; 50 | for (const uint8_t *compressedData = src; inputSize != 0; compressedData += 0x1000) { 51 | int decodeSize = inputSize; 52 | if (decodeSize > 256) { 53 | decodeSize = 256; 54 | } 55 | inputSize -= decodeSize; 56 | if (kCheckCompressedData) { 57 | src = compressedData; 58 | const uint16_t crc = READ_LE_UINT16(src); src += 2; 59 | uint16_t sum = 0; 60 | for (int i = 0; i < decodeSize * 8 - 1; ++i) { 61 | sum = ((sum & 1) << 15) | (sum >> 1); 62 | sum ^= READ_LE_UINT16(src); src += 2; 63 | } 64 | if (sum != crc) { 65 | error("Invalid checksum, expected 0x%X got 0x%X", crc, sum); 66 | } 67 | } 68 | src = compressedData + 2; 69 | stream.reset(src); 70 | while (1) { 71 | if (stream.getNextBit()) { 72 | *dst++ = stream.getNextByte(); 73 | continue; 74 | } 75 | uint16_t size = 0; 76 | uint16_t offset = 0; 77 | if (stream.getNextBit()) { 78 | uint16_t code = stream.getNextWord(); 79 | offset = 0xE000 | ((code >> 3) & 0x1F00) | (code & 0xFF); 80 | if (code & 0x700) { 81 | size = ((code >> 8) & 7) + 2; 82 | } else { 83 | code = stream.getNextByte(); 84 | if (code == 0) { 85 | return outputSize; 86 | } else if (code == 1) { 87 | continue; 88 | } else if (code == 2) { 89 | break; 90 | } else { 91 | size = code + 1; 92 | } 93 | } 94 | } else { 95 | for (int i = 0; i < 2; ++i) { 96 | size <<= 1; 97 | if (stream.getNextBit()) { 98 | size |= 1; 99 | } 100 | } 101 | size += 2; 102 | offset = 0xFF00 | stream.getNextByte(); 103 | } 104 | while (size--) { 105 | *dst = *(dst + (int16_t)offset); 106 | ++dst; 107 | } 108 | } 109 | } 110 | return outputSize; 111 | } 112 | 113 | int decodeZlib(const uint8_t *src, uint8_t *dst) { 114 | #ifdef BERMUDA_ZLIB 115 | z_stream s; 116 | memset(&s, 0, sizeof(s)); 117 | int ret = inflateInit(&s); 118 | if (ret == Z_OK) { 119 | s.next_in = src + 8; 120 | s.avail_in = READ_LE_UINT32(src); 121 | s.next_out = dst; 122 | s.avail_out = READ_LE_UINT32(src + 4); 123 | if (inflate(&s, Z_NO_FLUSH) == Z_STREAM_END) { 124 | ret = s.total_out; 125 | } else { 126 | ret = 0; 127 | } 128 | inflateEnd(&s); 129 | return ret; 130 | } 131 | #else 132 | warning("No decoder for zlib data"); 133 | #endif 134 | return 0; 135 | } 136 | -------------------------------------------------------------------------------- /mixer_sdl.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #define MIX_INIT_FLUIDSYNTH MIX_INIT_MID // renamed with SDL2_mixer >= 2.0.2 4 | #include 5 | #include "file.h" 6 | #include "mixer.h" 7 | #include "util.h" 8 | 9 | struct MixerSDL: Mixer { 10 | 11 | static const int kMixFreq = 22050; 12 | static const int kMixBufSize = 4096; 13 | static const int kChannels = 4; 14 | 15 | bool _isOpen; 16 | Mix_Chunk *_sounds[kChannels]; 17 | Mix_Music *_music; 18 | uint8_t *_musicBuf; 19 | 20 | MixerSDL() 21 | : _isOpen(false), _music(0), _musicBuf(0) { 22 | memset(_sounds, 0, sizeof(_sounds)); 23 | } 24 | 25 | virtual ~MixerSDL() { 26 | } 27 | 28 | virtual void open() { 29 | assert(!_isOpen); 30 | Mix_Init(MIX_INIT_OGG | MIX_INIT_MID); 31 | if (Mix_OpenAudio(kMixFreq, AUDIO_S16SYS, 2, kMixBufSize) < 0) { 32 | warning("Mix_OpenAudio failed: %s", Mix_GetError()); 33 | } 34 | Mix_AllocateChannels(kChannels); 35 | Mix_VolumeMusic(MIX_MAX_VOLUME / 2); 36 | _isOpen = true; 37 | } 38 | virtual void close() { 39 | assert(_isOpen); 40 | stopAll(); 41 | Mix_CloseAudio(); 42 | Mix_Quit(); 43 | _isOpen = false; 44 | } 45 | 46 | virtual void playSound(File *f, int *id) { 47 | debug(DBG_MIXER, "MixerSDL::playSound() path '%s'", f->_path); 48 | Mix_Chunk *chunk = Mix_LoadWAV(f->_path); 49 | if (chunk) { 50 | const int ch = Mix_PlayChannel(-1, chunk, 0); 51 | if (ch >= 0 && ch < kChannels) { 52 | if (_sounds[ch]) { 53 | Mix_FreeChunk(_sounds[ch]); 54 | } 55 | _sounds[ch] = chunk; 56 | } else { 57 | warning("Sound playing on channel %d (max %d)", ch, kChannels); 58 | } 59 | *id = ch; 60 | } else { 61 | *id = -1; 62 | } 63 | } 64 | virtual void playMusic(File *f, int *id) { 65 | debug(DBG_MIXER, "MixerSDL::playSoundMusic() path '%s'", f->_path); 66 | stopMusic(); 67 | const char *ext = strrchr(f->_path, '.'); 68 | if (ext) { 69 | if (strcasecmp(ext, ".ogg") != 0 && strcasecmp(ext, ".mid") != 0) { 70 | loadMusic(f); 71 | *id = -1; 72 | return; 73 | } 74 | } 75 | playMusic(f->_path); 76 | *id = -1; 77 | } 78 | virtual bool isSoundPlaying(int id) { 79 | return Mix_Playing(id) != 0; 80 | } 81 | virtual void stopSound(int id) { 82 | debug(DBG_MIXER, "MixerSDL::stopSound()"); 83 | Mix_HaltChannel(id); 84 | if (id >= 0 && id < kChannels && _sounds[id]) { 85 | Mix_FreeChunk(_sounds[id]); 86 | _sounds[id] = 0; 87 | } 88 | } 89 | 90 | void loadMusic(File *f) { 91 | _musicBuf = (uint8_t *)malloc(f->size()); 92 | if (_musicBuf) { 93 | const int size = f->read(_musicBuf, f->size()); 94 | SDL_RWops *rw = SDL_RWFromConstMem(_musicBuf, size); 95 | if (rw) { 96 | _music = Mix_LoadMUSType_RW(rw, MUS_MID, 1); 97 | if (_music) { 98 | Mix_PlayMusic(_music, 0); 99 | } else { 100 | warning("Failed to load music, %s", Mix_GetError()); 101 | } 102 | } 103 | } 104 | } 105 | 106 | void playMusic(const char *path) { 107 | _music = Mix_LoadMUS(path); 108 | if (_music) { 109 | Mix_PlayMusic(_music, 0); 110 | } else { 111 | warning("Failed to load music '%s', %s", path, Mix_GetError()); 112 | } 113 | } 114 | void stopMusic() { 115 | Mix_HaltMusic(); 116 | if (_music) { 117 | Mix_FreeMusic(_music); 118 | _music = 0; 119 | } 120 | if (_musicBuf) { 121 | free(_musicBuf); 122 | _musicBuf = 0; 123 | } 124 | } 125 | virtual void setMusicMix(void *param, void (*mix)(void *, uint8_t *, int)) { 126 | #ifndef __EMSCRIPTEN__ 127 | if (mix) { 128 | Mix_HookMusic(mix, param); 129 | } else { 130 | Mix_HookMusic(0, 0); 131 | } 132 | #endif 133 | } 134 | 135 | virtual void stopAll() { 136 | debug(DBG_MIXER, "MixerSDL::stopAll()"); 137 | Mix_HaltChannel(-1); 138 | stopMusic(); 139 | } 140 | }; 141 | 142 | Mixer *Mixer_SDL_create(SystemStub *stub) { 143 | return new MixerSDL(); 144 | } 145 | -------------------------------------------------------------------------------- /main.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Bermuda Syndrome engine rewrite 3 | * Copyright (C) 2007-2011 Gregory Montoir 4 | */ 5 | 6 | #include 7 | #include 8 | #ifdef __EMSCRIPTEN__ 9 | #include 10 | #endif 11 | #include "game.h" 12 | #include "systemstub.h" 13 | 14 | static const char *USAGE = 15 | "Bermuda Syndrome\n" 16 | "Usage: bs [OPTIONS]...\n" 17 | " --datapath=PATH Path to data files (default 'DATA')\n" 18 | " --savepath=PATH Path to save files (default '.')\n" 19 | " --musicpath=PATH Path to music files (default 'MUSIC')\n" 20 | " --fullscreen Fullscreen display\n" 21 | " --widescreen=MODE Widescreen mode ('default', '4:3' or '16:9')\n"; 22 | 23 | static Game *g_game; 24 | static SystemStub *g_stub; 25 | 26 | static void init(const char *dataPath, const char *savePath, const char *musicPath, bool fullscreen, int screenMode) { 27 | g_stub = SystemStub_SDL_create(); 28 | g_game = new Game(g_stub, dataPath ? dataPath : "DATA", savePath ? savePath : ".", musicPath ? musicPath : "MUSIC"); 29 | g_game->init(fullscreen, screenMode); 30 | } 31 | 32 | static void fini() { 33 | g_game->fini(); 34 | delete g_game; 35 | delete g_stub; 36 | g_stub = 0; 37 | } 38 | 39 | #ifdef __EMSCRIPTEN__ 40 | static void mainLoop() { 41 | if (!g_stub->_quit) { 42 | g_game->mainLoop(); 43 | } 44 | } 45 | #endif 46 | 47 | #undef main 48 | int main(int argc, char *argv[]) { 49 | char *dataPath = 0; 50 | char *savePath = 0; 51 | char *musicPath = 0; 52 | bool fullscreen = false; 53 | int screenMode = SCREEN_MODE_DEFAULT; 54 | if (argc == 2) { 55 | // data path as the only command line argument 56 | struct stat st; 57 | if (stat(argv[1], &st) == 0 && (S_ISDIR(st.st_mode) || S_ISREG(st.st_mode))) { 58 | dataPath = strdup(argv[1]); 59 | } 60 | } 61 | while (1) { 62 | static struct option options[] = { 63 | { "datapath", required_argument, 0, 1 }, 64 | { "savepath", required_argument, 0, 2 }, 65 | { "musicpath", required_argument, 0, 3 }, 66 | { "fullscreen", no_argument, 0, 4 }, 67 | { "widescreen", required_argument, 0, 5 }, 68 | { "help", no_argument, 0, 0 }, 69 | { 0, 0, 0, 0 } 70 | }; 71 | int index; 72 | const int c = getopt_long(argc, argv, "", options, &index); 73 | if (c == -1) { 74 | break; 75 | } 76 | switch (c) { 77 | case 1: 78 | dataPath = strdup(optarg); 79 | break; 80 | case 2: 81 | savePath = strdup(optarg); 82 | break; 83 | case 3: 84 | musicPath = strdup(optarg); 85 | break; 86 | case 4: 87 | fullscreen = true; 88 | break; 89 | case 5: { 90 | static const struct { 91 | const char *name; 92 | int mode; 93 | } modes[] = { 94 | { "default", SCREEN_MODE_DEFAULT }, 95 | { "4:3", SCREEN_MODE_4_3 }, 96 | { "16:9", SCREEN_MODE_16_9 }, 97 | { 0, -1 } 98 | }; 99 | for (int i = 0; modes[i].name; ++i) { 100 | if (strcmp(modes[i].name, optarg) == 0) { 101 | screenMode = modes[i].mode; 102 | break; 103 | } 104 | } 105 | } 106 | break; 107 | default: 108 | fprintf(stdout, "%s", USAGE); 109 | return 0; 110 | } 111 | } 112 | g_debugMask = DBG_INFO; // | DBG_GAME | DBG_OPCODES | DBG_DIALOGUE; 113 | init(dataPath, savePath, musicPath, fullscreen, screenMode); 114 | #ifdef __EMSCRIPTEN__ 115 | emscripten_set_main_loop(mainLoop, kCycleDelay, 0); 116 | #else 117 | uint32_t lastFrameTimeStamp = g_stub->getTimeStamp(); 118 | while (!g_stub->_quit) { 119 | g_game->mainLoop(); 120 | const uint32_t end = lastFrameTimeStamp + kCycleDelay; 121 | do { 122 | g_stub->sleep(10); 123 | g_stub->processEvents(); 124 | } while (!g_stub->_pi.fastMode && g_stub->getTimeStamp() < end); 125 | lastFrameTimeStamp = g_stub->getTimeStamp(); 126 | } 127 | fini(); 128 | #endif 129 | free(dataPath); 130 | free(savePath); 131 | free(musicPath); 132 | return 0; 133 | } 134 | -------------------------------------------------------------------------------- /menu.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "game.h" 3 | #include "systemstub.h" 4 | 5 | void Game::initMenu(int num) { 6 | _menuOption = -1; 7 | _menuHighlight = -1; 8 | _menuObjectCount = _sceneObjectsCount; 9 | _menuObjectMotion = _sceneObjectMotionsCount; 10 | _menuObjectFrames = _sceneObjectFramesCount; 11 | const int animationsCount = _animationsCount; 12 | const int state = _loadDataState; 13 | char name[32]; 14 | snprintf(name, sizeof(name), "..\\menu\\menu%d.wgp", num); 15 | loadWGP(name); 16 | snprintf(name, sizeof(name), "..\\menu\\menu%d.mov", num); 17 | loadMOV(name); 18 | _loadDataState = state; 19 | assert(_menuObjectMotion + 1 == _sceneObjectMotionsCount); 20 | assert(animationsCount + 1 == _animationsCount); 21 | _stub->setPalette(_bitmapBuffer0 + kOffsetBitmapPalette, 256); 22 | _stub->copyRectWidescreen(kGameScreenWidth, kGameScreenHeight, _bitmapBuffer1.bits, _bitmapBuffer1.pitch); 23 | _stub->showCursor(true); 24 | } 25 | 26 | void Game::finiMenu() { 27 | if (_currentSceneWgp[0]) { 28 | --_animationsCount; 29 | _sceneObjectsCount = _menuObjectCount; 30 | _sceneObjectMotionsCount = _menuObjectMotion; 31 | _sceneObjectFramesCount = _menuObjectFrames; 32 | const int state = _loadDataState; 33 | loadWGP(_currentSceneWgp); 34 | _loadDataState = state; 35 | _stub->setPalette(_bitmapBuffer0 + kOffsetBitmapPalette, 256); 36 | _stub->copyRectWidescreen(kGameScreenWidth, kGameScreenHeight, _bitmapBuffer1.bits, _bitmapBuffer1.pitch); 37 | } 38 | _stub->showCursor(false); 39 | } 40 | 41 | void Game::handleMenu() { 42 | if (_menuHighlight != -1) { 43 | SceneObjectFrame *sof = &_sceneObjectFramesTable[_sceneObjectMotionsTable[_menuObjectMotion].firstFrameIndex + _menuHighlight]; 44 | const int yPos = _bitmapBuffer1.h + 1 - sof->hdr.yPos - sof->hdr.h; 45 | copyBufferToBuffer(sof->hdr.xPos, yPos, sof->hdr.w, sof->hdr.h, &_bitmapBuffer3, &_bitmapBuffer1); 46 | } 47 | const int xCursor = _stub->_pi.mouseX; 48 | const int yCursor = _stub->_pi.mouseY; 49 | for (int frame = 0; frame < _sceneObjectMotionsTable[_menuObjectMotion].count; ++frame) { 50 | SceneObjectFrame *sof = &_sceneObjectFramesTable[_sceneObjectMotionsTable[_menuObjectMotion].firstFrameIndex + frame]; 51 | if (xCursor >= sof->hdr.xPos && xCursor < sof->hdr.xPos + sof->hdr.w && yCursor >= sof->hdr.yPos && yCursor < sof->hdr.yPos + sof->hdr.h) { 52 | _menuHighlight = frame; 53 | if (_stub->_pi.leftMouseButton) { 54 | _stub->_pi.leftMouseButton = false; 55 | _menuOption = frame; 56 | } 57 | } 58 | } 59 | 60 | if (_state == kStateMenu1) { 61 | // vertical layout 62 | if (_stub->_pi.dirMask & PlayerInput::DIR_UP) { 63 | _stub->_pi.dirMask &= ~PlayerInput::DIR_UP; 64 | --_menuHighlight; 65 | if (_menuHighlight < 0) { 66 | _menuHighlight = 0; 67 | } 68 | } 69 | if (_stub->_pi.dirMask & PlayerInput::DIR_DOWN) { 70 | _stub->_pi.dirMask &= ~PlayerInput::DIR_DOWN; 71 | ++_menuHighlight; 72 | if (_menuHighlight >= _sceneObjectMotionsTable[_menuObjectMotion].count) { 73 | _menuHighlight = _sceneObjectMotionsTable[_menuObjectMotion].count - 1; 74 | } 75 | } 76 | } else if (_state == kStateMenu2) { 77 | // horizontal layout 78 | if (_stub->_pi.dirMask & PlayerInput::DIR_LEFT) { 79 | _stub->_pi.dirMask &= ~PlayerInput::DIR_LEFT; 80 | --_menuHighlight; 81 | if (_menuHighlight < 0) { 82 | _menuHighlight = 0; 83 | } 84 | } 85 | if (_stub->_pi.dirMask & PlayerInput::DIR_RIGHT) { 86 | _stub->_pi.dirMask &= ~PlayerInput::DIR_RIGHT; 87 | ++_menuHighlight; 88 | if (_menuHighlight >= _sceneObjectMotionsTable[_menuObjectMotion].count) { 89 | _menuHighlight = _sceneObjectMotionsTable[_menuObjectMotion].count - 1; 90 | } 91 | } 92 | } 93 | 94 | if (_stub->_pi.enter) { 95 | _stub->_pi.enter = false; 96 | _menuOption = _menuHighlight; 97 | } 98 | 99 | if (_menuHighlight != -1) { 100 | SceneObjectFrame *sof = &_sceneObjectFramesTable[_sceneObjectMotionsTable[_menuObjectMotion].firstFrameIndex + _menuHighlight]; 101 | sof->decode(sof->data, _tempDecodeBuffer); 102 | const int yPos = _bitmapBuffer1.h + 1 - sof->hdr.yPos - sof->hdr.h; 103 | drawObject(sof->hdr.xPos, yPos, _tempDecodeBuffer, &_bitmapBuffer1); 104 | } 105 | 106 | _stub->copyRect(0, 0, kGameScreenWidth, kGameScreenHeight, _bitmapBuffer1.bits, _bitmapBuffer1.pitch); 107 | } 108 | -------------------------------------------------------------------------------- /tools/decode_ne.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | static void TO_LE16(uint8_t *dst, uint16_t value) { 8 | for (int i = 0; i < 2; ++i) { 9 | dst[i] = value & 255; value >>= 8; 10 | } 11 | } 12 | 13 | static void TO_LE32(uint8_t *dst, uint32_t value) { 14 | for (int i = 0; i < 4; ++i) { 15 | dst[i] = value & 255; value >>= 8; 16 | } 17 | } 18 | 19 | static bool freadTag(FILE *fp, const char *tag) { 20 | assert(tag); 21 | while (*tag) { 22 | if (fgetc(fp) != *tag) { 23 | return false; 24 | } 25 | ++tag; 26 | } 27 | return true; 28 | } 29 | 30 | static uint16_t freadUint16LE(FILE *fp) { 31 | uint16_t val = fgetc(fp); 32 | return (fgetc(fp) << 8) + val; 33 | } 34 | 35 | enum { 36 | kResTypeCursor = 0x8001, 37 | kResTypeBitmap = 0x8002, 38 | kResTypeIcon = 0x8003, 39 | kResTypeMenu = 0x8004, 40 | kResTypeDialog = 0x8005, 41 | kResTypeString = 0x8006, 42 | kResTypeGroupFont = 0x8007, 43 | kResTypeFont = 0x8008, 44 | kResTypeData = 0x800A, 45 | kResTypeGroupCursor = 0x800C, 46 | kResTypeGroupIcon = 0x800E, 47 | kResTypeVersion = 0x8010, 48 | }; 49 | 50 | const char *resourceTypeName(int type) { 51 | switch (type) { 52 | case kResTypeCursor: 53 | return "cursor"; 54 | case kResTypeBitmap: 55 | return "bitmap"; 56 | case kResTypeIcon: 57 | return "icon"; 58 | case kResTypeMenu: 59 | return "menu"; 60 | case kResTypeString: 61 | return "string"; 62 | case kResTypeData: 63 | return "data"; 64 | case kResTypeGroupCursor: 65 | return "group_cursor"; 66 | case kResTypeGroupIcon: 67 | return "group_icon"; 68 | default: 69 | return ""; 70 | } 71 | } 72 | 73 | struct ResourceEntry { 74 | uint32_t offset; 75 | uint32_t size; 76 | uint16_t flags; 77 | uint16_t id; 78 | }; 79 | 80 | #define BITMAPFILEHEADER_SIZE 14 81 | #define BITMAPINFOHEADER_SIZE 40 82 | 83 | static void dumpResource(FILE *fp, int type, int index, const ResourceEntry *re) { 84 | fprintf(stdout, "Resource size %d flags 0x%x id 0x%x type 0x%x\n", re->size, re->flags, re->id, type); 85 | FILE *out; 86 | 87 | char name[32]; 88 | snprintf(name, sizeof(name), "%s%d", resourceTypeName(type), index); 89 | 90 | const int pos = ftell(fp); 91 | fseek(fp, re->offset, SEEK_SET); 92 | 93 | uint8_t *buf = (uint8_t *)malloc(re->size); 94 | if (buf) { 95 | fread(buf, 1, re->size, fp); 96 | 97 | switch (type) { 98 | case kResTypeIcon: 99 | out = fopen(name, "wb"); 100 | if (out) { 101 | // icons are stored with DIB header 102 | uint8_t header[BITMAPFILEHEADER_SIZE]; 103 | 104 | header[0] = 'B'; header[1] = 'M'; 105 | TO_LE32(header + 2, BITMAPFILEHEADER_SIZE + re->size); 106 | TO_LE16(header + 6, 0); 107 | TO_LE16(header + 8, 0); 108 | TO_LE32(header + 10, BITMAPFILEHEADER_SIZE + BITMAPINFOHEADER_SIZE + 16 * 4); 109 | 110 | fwrite(header, 1, sizeof(header), out); 111 | 112 | // Fixup dimensions to 32x32, the lower 32 rows contain garbage 113 | if (buf[0] == 40 && buf[4] == 32 && buf[8] == 64) { 114 | fprintf(stdout, "Fixing up dimensions to 32x32\n"); 115 | buf[8] = 32; 116 | } 117 | 118 | fwrite(buf, 1, re->size, out); 119 | fclose(out); 120 | } 121 | break; 122 | case kResTypeData: 123 | out = fopen(name, "wb"); 124 | if (out) { 125 | fwrite(buf, 1, re->size, out); 126 | fclose(out); 127 | } 128 | break; 129 | } 130 | free(buf); 131 | } 132 | fseek(fp, pos, SEEK_SET); 133 | } 134 | 135 | static void decodeResourceTable(FILE *fp) { 136 | const int align = 1 << freadUint16LE(fp); 137 | int type = freadUint16LE(fp); 138 | while (type != 0) { 139 | assert((type & 0x8000) != 0); 140 | 141 | // DW Number of resources for this type 142 | int count = freadUint16LE(fp); 143 | fprintf(stdout, "Resource type 0x%x '%s' count %d\n", type, resourceTypeName(type), count); 144 | 145 | // DD Reserved. 146 | fseek(fp, 4, SEEK_CUR); 147 | 148 | for (int i = 0; i < count; ++i) { 149 | ResourceEntry re; 150 | 151 | // DW File offset to the contents of the resource data 152 | re.offset = freadUint16LE(fp) * align; 153 | 154 | // DW Length of resource in the file 155 | re.size = freadUint16LE(fp) * align; 156 | 157 | // DW Flag word. 158 | re.flags = freadUint16LE(fp); 159 | 160 | // DW Resource ID 161 | re.id = freadUint16LE(fp); 162 | 163 | // DD Reserved. 164 | fseek(fp, 4, SEEK_CUR); 165 | 166 | dumpResource(fp, type, i, &re); 167 | } 168 | 169 | type = freadUint16LE(fp); 170 | } 171 | } 172 | 173 | int main(int argc, char *argv[]) { 174 | if (argc == 2) { 175 | FILE *fp = fopen(argv[1], "rb"); 176 | if (fp) { 177 | if (!freadTag(fp, "MZ")) { 178 | fprintf(stdout, "Failed to read MZ tag\n"); 179 | } else { 180 | fseek(fp, 60, SEEK_SET); 181 | uint16_t executableOffset = freadUint16LE(fp); 182 | fseek(fp, executableOffset, SEEK_SET); 183 | if (!freadTag(fp, "NE")) { 184 | fprintf(stdout, "Failed to read NE tag\n"); 185 | } else { 186 | fseek(fp, executableOffset + 36, SEEK_SET); 187 | uint16_t resourceOffset = freadUint16LE(fp); 188 | if (resourceOffset != 0) { 189 | fseek(fp, executableOffset + resourceOffset, SEEK_SET); 190 | decodeResourceTable(fp); 191 | } 192 | } 193 | } 194 | fclose(fp); 195 | } 196 | } 197 | return 0; 198 | } 199 | -------------------------------------------------------------------------------- /file.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Bermuda Syndrome engine rewrite 3 | * Copyright (C) 2007-2011 Gregory Montoir 4 | */ 5 | 6 | #include "file.h" 7 | 8 | struct File_impl { 9 | bool _ioErr; 10 | File_impl() : _ioErr(false) {} 11 | virtual ~File_impl() {} 12 | virtual bool open(const char *path, const char *mode) { return false; } 13 | virtual void close() {} 14 | virtual uint32_t size() = 0; 15 | virtual uint32_t tell() = 0; 16 | virtual void seek(int offs, int origin) = 0; 17 | virtual uint32_t read(void *ptr, uint32_t len) = 0; 18 | virtual uint32_t write(void *ptr, uint32_t len) = 0; 19 | }; 20 | 21 | struct StdioFile : File_impl { 22 | FILE *_fp; 23 | uint32_t _offset, _size; 24 | StdioFile() : _fp(0), _offset(0), _size(0) {} 25 | StdioFile(uint32_t offset, uint32_t size) : _fp(0), _offset(offset), _size(size) {} 26 | bool open(const char *path, const char *mode) { 27 | _ioErr = false; 28 | _fp = fopen(path, mode); 29 | if (_fp != 0) { 30 | if (_offset != 0) { 31 | fseek(_fp, _offset, SEEK_SET); 32 | } 33 | return true; 34 | } 35 | return false; 36 | } 37 | void close() { 38 | if (_fp) { 39 | fclose(_fp); 40 | _fp = 0; 41 | } 42 | } 43 | uint32_t size() { 44 | if (_size != 0) { 45 | return _size; 46 | } 47 | if (_fp) { 48 | int pos = ftell(_fp); 49 | fseek(_fp, 0, SEEK_END); 50 | _size = ftell(_fp); 51 | fseek(_fp, pos, SEEK_SET); 52 | } 53 | return _size; 54 | } 55 | uint32_t tell() { 56 | uint32_t pos = 0; 57 | if (_fp) { 58 | pos = ftell(_fp) - _offset; 59 | } 60 | return pos; 61 | } 62 | void seek(int offs, int origin) { 63 | if (_fp) { 64 | fseek(_fp, _offset + offs, origin); 65 | } 66 | } 67 | uint32_t read(void *ptr, uint32_t len) { 68 | uint32_t r = 0; 69 | if (_fp) { 70 | r = fread(ptr, 1, len, _fp); 71 | if (r != len) { 72 | _ioErr = true; 73 | } 74 | } 75 | return r; 76 | } 77 | uint32_t write(void *ptr, uint32_t len) { 78 | uint32_t r = 0; 79 | if (_fp) { 80 | r = fwrite(ptr, 1, len, _fp); 81 | if (r != len) { 82 | _ioErr = true; 83 | } 84 | } 85 | return r; 86 | } 87 | }; 88 | 89 | struct ReadMemoryFile: File_impl { 90 | const uint8_t *_ptr; 91 | uint32_t _offset, _len; 92 | ReadMemoryFile(const uint8_t *ptr, uint32_t len) 93 | : _ptr(ptr), _offset(0), _len(len) { 94 | } 95 | uint32_t size() { 96 | return _len; 97 | } 98 | uint32_t tell() { 99 | return _offset; 100 | } 101 | void seek(int offs, int origin) { 102 | assert(origin == SEEK_SET); 103 | _offset = offs; 104 | } 105 | uint32_t read(void *ptr, uint32_t len) { 106 | int count = len; 107 | if (_offset + count > _len) { 108 | count = _len - _offset; 109 | _ioErr = true; 110 | } 111 | if (count != 0) { 112 | memcpy(ptr, _ptr + _offset, count); 113 | _offset += count; 114 | } 115 | return count; 116 | } 117 | uint32_t write(void *ptr, uint32_t len) { 118 | assert(0); 119 | return 0; 120 | } 121 | }; 122 | 123 | struct WriteMemoryFile: File_impl { 124 | uint8_t *_ptr; 125 | uint32_t _offset, _len; 126 | WriteMemoryFile(uint8_t *ptr, uint32_t len) 127 | : _ptr(ptr), _offset(0), _len(len) { 128 | } 129 | uint32_t size() { 130 | return _len; 131 | } 132 | uint32_t tell() { 133 | return _offset; 134 | } 135 | void seek(int offs, int origin) { 136 | assert(origin == SEEK_SET); 137 | _offset = offs; 138 | } 139 | uint32_t read(void *ptr, uint32_t len) { 140 | assert(0); 141 | return 0; 142 | } 143 | uint32_t write(void *ptr, uint32_t len) { 144 | int count = len; 145 | if (_offset + count > _len) { 146 | count = _len - _offset; 147 | _ioErr = true; 148 | } 149 | if (count != 0) { 150 | memcpy(_ptr + _offset, ptr, count); 151 | _offset += count; 152 | } 153 | return count; 154 | } 155 | }; 156 | 157 | File::File() 158 | : _path(0) { 159 | _impl = new StdioFile; 160 | } 161 | 162 | File::File(uint32_t offset, uint32_t size) 163 | : _path(0) { 164 | _impl = new StdioFile(offset, size); 165 | } 166 | 167 | File::File(const uint8_t *ptr, uint32_t len) 168 | : _path(0) { 169 | _impl = new ReadMemoryFile(ptr, len); 170 | } 171 | 172 | File::File(uint8_t *ptr, uint32_t len) 173 | : _path(0) { 174 | _impl = new WriteMemoryFile(ptr, len); 175 | } 176 | 177 | File::~File() { 178 | free(_path); 179 | _impl->close(); 180 | delete _impl; 181 | } 182 | 183 | bool File::open(const char *path, const char *mode) { 184 | _impl->close(); 185 | free(_path); 186 | _path = strdup(path); 187 | return _impl->open(path, mode); 188 | } 189 | 190 | void File::close() { 191 | _impl->close(); 192 | } 193 | 194 | bool File::ioErr() const { 195 | return _impl->_ioErr; 196 | } 197 | 198 | uint32_t File::size() { 199 | return _impl->size(); 200 | } 201 | 202 | uint32_t File::tell() { 203 | return _impl->tell(); 204 | } 205 | 206 | void File::seek(int offs, int origin) { 207 | _impl->seek(offs, origin); 208 | } 209 | 210 | uint32_t File::read(void *ptr, uint32_t len) { 211 | return _impl->read(ptr, len); 212 | } 213 | 214 | uint8_t File::readByte() { 215 | uint8_t b; 216 | read(&b, 1); 217 | return b; 218 | } 219 | 220 | uint16_t File::readUint16LE() { 221 | uint8_t b[2]; 222 | read(b, sizeof(b)); 223 | return READ_LE_UINT16(b); 224 | } 225 | 226 | uint32_t File::readUint32LE() { 227 | uint8_t b[4]; 228 | read(b, sizeof(b)); 229 | return READ_LE_UINT32(b); 230 | } 231 | 232 | uint32_t File::readUint32BE() { 233 | uint8_t b[4]; 234 | read(b, sizeof(b)); 235 | return READ_BE_UINT32(b); 236 | } 237 | 238 | void File::write(void *ptr, uint32_t len) { 239 | _impl->write(ptr, len); 240 | } 241 | 242 | void File::writeByte(uint8_t b) { 243 | write(&b, 1); 244 | } 245 | 246 | void File::writeUint16LE(uint16_t n) { 247 | writeByte(n & 0xFF); 248 | writeByte(n >> 8); 249 | } 250 | 251 | void File::writeUint32LE(uint32_t n) { 252 | writeUint16LE(n & 0xFFFF); 253 | writeUint16LE(n >> 16); 254 | } 255 | -------------------------------------------------------------------------------- /bag.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Bermuda Syndrome engine rewrite 3 | * Copyright (C) 2007-2011 Gregory Montoir 4 | */ 5 | 6 | #include "game.h" 7 | #include "systemstub.h" 8 | 9 | void Game::handleBagMenu() { 10 | 11 | const int xPosWnd = (kGameScreenWidth - (_bagBackgroundImage.w + 1)) / 2; 12 | const int yPosWnd = (kGameScreenHeight - (_bagBackgroundImage.h + 1)) / 4; 13 | 14 | if (_stub->_pi.leftMouseButton) { 15 | _stub->_pi.leftMouseButton = false; 16 | int xPos = _stub->_pi.mouseX - xPosWnd; 17 | int yPos = _stub->_pi.mouseY - yPosWnd; 18 | 19 | // action area 20 | for (int i = 0; i < 3; ++i) { 21 | if (xPos >= i * 45 + 118 && xPos < i * 45 + 160) { 22 | if (yPos >= 15 && yPos < 55) { 23 | if (i != _currentBagAction) { 24 | _currentBagAction = i; 25 | } 26 | } 27 | } 28 | } 29 | // bag object area 30 | for (int i = 0; i < MIN(_bagObjectsCount, 4); ++i) { 31 | if (xPos >= i * 32 + 261 && xPos < i * 32 + 293) { 32 | if (yPos >= 4 && yPos < 44) { 33 | if (i != _currentBagObject || _currentBagAction != 3) { 34 | _currentBagObject = i; 35 | _currentBagAction = 3; // kActionUseObject 36 | } 37 | } 38 | } 39 | } 40 | // weapons area 41 | if (xPos >= 22 && xPos < getBitmapWidth(_weaponIconImageTable[0]) + 22) { 42 | if (yPos >= 22 && yPos < getBitmapHeight(_weaponIconImageTable[0]) + 22) { 43 | if (_varsTable[1] == 1 && _varsTable[2] != 0) { // switch to gun 44 | _varsTable[2] = 1; 45 | _varsTable[1] = 2; 46 | } 47 | } 48 | } 49 | if (xPos >= 22 && xPos < getBitmapWidth(_swordIconImage) + 22) { 50 | if (yPos >= 37 && yPos < getBitmapHeight(_swordIconImage) + 37) { 51 | if (_varsTable[2] == 1 && _varsTable[1] != 0) { // switch to ? 52 | _varsTable[2] = 2; 53 | _varsTable[1] = 1; 54 | } 55 | } 56 | } 57 | } 58 | 59 | if (_stub->_pi.tab) { 60 | _stub->_pi.tab = false; 61 | _nextState = kStateGame; 62 | return; 63 | } 64 | if (_stub->_pi.enter) { 65 | _stub->_pi.enter = false; 66 | _nextState = kStateGame; 67 | return; 68 | } 69 | 70 | if (_stub->_pi.dirMask & PlayerInput::DIR_LEFT) { 71 | _stub->_pi.dirMask &= ~PlayerInput::DIR_LEFT; 72 | if (_currentBagAction != 0) { 73 | if (_currentBagAction != 3 || _currentBagObject <= 0) { 74 | --_currentBagAction; 75 | } else { 76 | --_currentBagObject; 77 | } 78 | } 79 | } 80 | 81 | if (_stub->_pi.dirMask & PlayerInput::DIR_RIGHT) { 82 | _stub->_pi.dirMask &= ~PlayerInput::DIR_RIGHT; 83 | if (_currentBagAction == 3) { 84 | if (_bagObjectsCount - 1 > _currentBagObject && _currentBagObject != -1) { 85 | ++_currentBagObject; 86 | } 87 | } else { 88 | ++_currentBagAction; 89 | } 90 | } 91 | 92 | if (_stub->_pi.dirMask & PlayerInput::DIR_DOWN) { 93 | _stub->_pi.dirMask &= ~PlayerInput::DIR_DOWN; 94 | if (_varsTable[2] == 1 && _varsTable[1] != 0) { 95 | _varsTable[2] = 2; 96 | _varsTable[1] = 1; 97 | } 98 | } 99 | 100 | if (_stub->_pi.dirMask & PlayerInput::DIR_UP) { 101 | _stub->_pi.dirMask &= ~PlayerInput::DIR_UP; 102 | if (_varsTable[1] == 1 && _varsTable[2] != 0) { 103 | _varsTable[2] = 1; 104 | _varsTable[1] = 2; 105 | } 106 | } 107 | 108 | drawBagMenu(xPosWnd, yPosWnd); 109 | 110 | ++_lifeBarCurrentFrame; 111 | if (_lifeBarCurrentFrame >= 12) { 112 | _lifeBarCurrentFrame = 0; 113 | } 114 | if (_currentBagObject >= 0 && _currentBagObject < 4 && _currentBagAction == 3 && _bagObjectsCount != 0) { 115 | ++_bagObjectAreaBlinkCounter; 116 | if (_bagObjectAreaBlinkCounter >= 10) { 117 | _bagObjectAreaBlinkCounter = 0; 118 | } 119 | } 120 | if (_varsTable[1] == 1) { 121 | ++_bagWeaponAreaBlinkCounter; 122 | if (_bagWeaponAreaBlinkCounter >= 10) { 123 | _bagWeaponAreaBlinkCounter = 0; 124 | } 125 | } else if (_varsTable[2] == 1) { 126 | ++_bagWeaponAreaBlinkCounter; 127 | if (_bagWeaponAreaBlinkCounter >= 10) { 128 | _bagWeaponAreaBlinkCounter = 0; 129 | } 130 | } 131 | } 132 | 133 | void Game::drawBagMenu(int xPosWnd, int yPosWnd) { 134 | static const struct { 135 | int x, y; 136 | } _bagPosItems[] = { 137 | { 121, 17 }, 138 | { 164, 26 }, 139 | { 222, 16 } 140 | }; 141 | for (int i = 0; i < 4; ++i) { 142 | int y = _bagBackgroundImage.h - 32 - getBitmapHeight(_bagObjectAreaBlinkImageTable[0]); 143 | drawObject((_isDemo ? 247 : 269) + i * 32, y, _bagObjectAreaBlinkImageTable[0], &_bagBackgroundImage); 144 | } 145 | 146 | // make a copy for drawing (will be restored at the end) 147 | SceneBitmap backup = _bagBackgroundImage; 148 | int size = (backup.w + 1) * (backup.h + 1); 149 | backup.bits = (uint8_t *)malloc(size); 150 | if (!backup.bits) { 151 | return; 152 | } 153 | memcpy(backup.bits, _bagBackgroundImage.bits, size); 154 | 155 | if (_varsTable[2] != 0) { 156 | int index = MIN(13 - _varsTable[4], 13); 157 | uint8_t *p = _weaponIconImageTable[index]; 158 | int y = _bagBackgroundImage.h - (_isDemo ? 19 : 21) - getBitmapHeight(p); 159 | drawObject(22, y, p, &_bagBackgroundImage); 160 | if (_varsTable[3] < 5) { 161 | index = _varsTable[4] <= 0 ? 0 : 1; 162 | p = _ammoIconImageTable[index][_varsTable[3]]; 163 | y = _bagBackgroundImage.h - (_isDemo ? 29 : 31) - getBitmapHeight(p); 164 | drawObject(33, y, p, &_bagBackgroundImage); 165 | } 166 | } 167 | if (!_isDemo && _varsTable[1] != 0) { 168 | int y = _bagBackgroundImage.h - 36 - getBitmapHeight(_swordIconImage); 169 | drawObject(22, y, _swordIconImage, &_bagBackgroundImage); 170 | } 171 | int index = (_varsTable[0] >= 10) ? 10 : _varsTable[0]; 172 | uint8_t *lifeBarFrame = _lifeBarImageTable[index][_lifeBarCurrentFrame]; 173 | drawObject(_isDemo ? 23 : 314, _bagBackgroundImage.h - (_isDemo ? 52 : 51) - getBitmapHeight(lifeBarFrame), lifeBarFrame, &_bagBackgroundImage); 174 | if (_currentBagAction < 3) { 175 | uint8_t *p = _bermudaSprDataTable[_currentBagAction]; 176 | int y = _bagBackgroundImage.h - _bagPosItems[_currentBagAction].y - getBitmapHeight(p); 177 | int x = _bagPosItems[_currentBagAction].x; 178 | if (_isDemo) { 179 | x -= 22; 180 | } 181 | drawObject(x, y, p, &_bagBackgroundImage); 182 | } 183 | for (int i = 0; i < MIN(_bagObjectsCount, 4); ++i) { 184 | uint8_t *bagObjectData = _bagObjectsTable[i].data; 185 | int x = (_isDemo ? 239 : 261) + i * 32 + (32 - getBitmapWidth(bagObjectData)) / 2; 186 | int y = _bagBackgroundImage.h - 3 - (40 - getBitmapHeight(bagObjectData)) / 2 - getBitmapHeight(bagObjectData); 187 | drawObject(x, y, bagObjectData, &_bagBackgroundImage); 188 | } 189 | if (_currentBagObject >= 0 && _currentBagObject < 4 && _bagObjectsCount != 0) { 190 | uint8_t *p = _currentBagAction == 3 ? _bagObjectAreaBlinkImageTable[_bagObjectAreaBlinkCounter] : _bagObjectAreaBlinkImageTable[0]; 191 | drawObject((_isDemo ? 247 : 269) + _currentBagObject * 32, _bagBackgroundImage.h - 32 - getBitmapHeight(p), p, &_bagBackgroundImage); 192 | } 193 | if (!_isDemo) { 194 | uint8_t *weaponImage1 = _varsTable[1] == 1 ? _bagWeaponAreaBlinkImageTable[_bagWeaponAreaBlinkCounter] : _bagWeaponAreaBlinkImageTable[0]; 195 | drawObject(87, _bagBackgroundImage.h - 38 - getBitmapHeight(weaponImage1), weaponImage1, &_bagBackgroundImage); 196 | uint8_t *weaponImage2 = _varsTable[2] == 1 ? _bagWeaponAreaBlinkImageTable[_bagWeaponAreaBlinkCounter] : _bagWeaponAreaBlinkImageTable[0]; 197 | drawObject(87, _bagBackgroundImage.h - 25 - getBitmapHeight(weaponImage2), weaponImage2, &_bagBackgroundImage); 198 | } 199 | 200 | _stub->copyRect(xPosWnd, yPosWnd, _bagBackgroundImage.w + 1, _bagBackgroundImage.h + 1, _bagBackgroundImage.bits, _bagBackgroundImage.pitch); 201 | 202 | memcpy(_bagBackgroundImage.bits, backup.bits, size); 203 | free(backup.bits); 204 | } 205 | -------------------------------------------------------------------------------- /saveload.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Bermuda Syndrome engine rewrite 3 | * Copyright (C) 2007-2011 Gregory Montoir 4 | */ 5 | 6 | #include "file.h" 7 | #include "game.h" 8 | 9 | enum SaveLoadMode { 10 | kSaveMode, 11 | kLoadMode 12 | }; 13 | 14 | static File *_saveOrLoadStream; 15 | static SaveLoadMode _saveOrLoadMode; 16 | 17 | static void saveByte(uint8_t b) { 18 | _saveOrLoadStream->writeByte(b); 19 | } 20 | 21 | static uint8_t loadByte() { 22 | return _saveOrLoadStream->readByte(); 23 | } 24 | 25 | static void saveInt16(int i) { 26 | _saveOrLoadStream->writeUint16LE(i); 27 | } 28 | 29 | static int loadInt16() { 30 | return _saveOrLoadStream->readUint16LE(); 31 | } 32 | 33 | static void saveInt32(int i) { 34 | _saveOrLoadStream->writeUint32LE(i); 35 | } 36 | 37 | static int loadInt32() { 38 | return _saveOrLoadStream->readUint32LE(); 39 | } 40 | 41 | static void saveStr(void *s, int len) { 42 | _saveOrLoadStream->write(s, len); 43 | } 44 | 45 | static void loadStr(void *s, int len) { 46 | _saveOrLoadStream->read(s, len); 47 | } 48 | 49 | static void saveOrLoadByte(uint8_t &b) { 50 | switch (_saveOrLoadMode) { 51 | case kSaveMode: 52 | saveByte(b); 53 | break; 54 | case kLoadMode: 55 | b = loadByte(); 56 | break; 57 | } 58 | } 59 | 60 | static void saveOrLoadInt16(int16_t &i) { 61 | switch (_saveOrLoadMode) { 62 | case kSaveMode: 63 | saveInt16(i); 64 | break; 65 | case kLoadMode: 66 | i = loadInt16(); 67 | break; 68 | } 69 | } 70 | 71 | static void saveOrLoadStr(char *s, int16_t len = -1) { 72 | bool countTermChar = (len == -2); // deal with original buggy file format... 73 | bool storeLen = (len < 0); 74 | if (storeLen) { 75 | len = strlen(s); 76 | if (countTermChar) { 77 | ++len; 78 | } 79 | saveOrLoadInt16(len); 80 | } 81 | switch (_saveOrLoadMode) { 82 | case kSaveMode: 83 | saveStr(s, len); 84 | break; 85 | case kLoadMode: 86 | loadStr(s, len); 87 | if (storeLen) { 88 | s[len] = 0; 89 | } 90 | break; 91 | } 92 | } 93 | 94 | static void saveOrLoad_sceneObject(SceneObject &so) { 95 | saveOrLoadInt16(so.xInit); 96 | saveOrLoadInt16(so.yInit); 97 | saveOrLoadInt16(so.x); 98 | saveOrLoadInt16(so.y); 99 | saveOrLoadInt16(so.xPrev); 100 | saveOrLoadInt16(so.yPrev); 101 | saveOrLoadInt16(so.zInit); 102 | saveOrLoadInt16(so.zPrev); 103 | saveOrLoadInt16(so.z); 104 | saveOrLoadInt16(so.frameNum); 105 | saveOrLoadInt16(so.frameNumPrev); 106 | saveOrLoadInt16(so.flipInit); 107 | saveOrLoadInt16(so.flip); 108 | saveOrLoadInt16(so.flipPrev); 109 | saveOrLoadInt16(so.motionNum); 110 | saveOrLoadInt16(so.motionInit); 111 | saveOrLoadInt16(so.motionNum1); 112 | saveOrLoadInt16(so.motionNum2); 113 | saveOrLoadInt16(so.motionFrameNum); 114 | saveOrLoadInt16(so.mode); 115 | saveOrLoadInt16(so.modeRndMul); 116 | saveOrLoadInt16(so.statePrev); 117 | saveOrLoadInt16(so.state); 118 | saveOrLoadStr(so.name, 20); 119 | saveOrLoadStr(so.className, 20); 120 | for (int j = 0; j < 10; ++j) { 121 | saveOrLoadInt16(so.varsTable[j]); 122 | } 123 | } 124 | 125 | static void saveOrLoad_box(Box &b) { 126 | saveOrLoadInt16(b.x1); 127 | saveOrLoadInt16(b.x2); 128 | saveOrLoadInt16(b.y1); 129 | saveOrLoadInt16(b.y2); 130 | saveOrLoadByte(b.state); 131 | saveOrLoadInt16(b.z); 132 | saveOrLoadInt16(b.startColor); 133 | saveOrLoadInt16(b.endColor); 134 | } 135 | 136 | static void saveOrLoad_sceneObjectStatus(SceneObjectStatus &sos) { 137 | saveOrLoadInt16(sos.x); 138 | saveOrLoadInt16(sos.y); 139 | saveOrLoadInt16(sos.z); 140 | saveOrLoadInt16(sos.motionNum); 141 | saveOrLoadInt16(sos.frameNum); 142 | saveOrLoadInt16(sos.flip); 143 | } 144 | 145 | // original save/load data format 146 | static void save_bagObjects(BagObject *bo, int count) { 147 | int totalSize = 0; 148 | for (int i = 0; i < count; ++i) { 149 | totalSize += bo[i].dataSize; 150 | } 151 | assert(totalSize < 0xFFFF); 152 | saveInt16(totalSize); 153 | for (int i = 0; i < count; ++i) { 154 | saveStr(bo[i].data, bo[i].dataSize); 155 | } 156 | saveInt16(count); 157 | int offset = 0; 158 | for (int i = 0; i < count; ++i) { 159 | saveInt16(offset); 160 | saveStr(bo[i].name, 20); 161 | offset += bo[i].dataSize; 162 | } 163 | } 164 | 165 | static void load_bagObjects(BagObject *bo, int &count) { 166 | uint16_t totalSize = loadInt16(); 167 | uint8_t *bagData = (uint8_t *)malloc(totalSize); 168 | if (bagData) { 169 | loadStr(bagData, totalSize); 170 | count = loadInt16(); 171 | assert(count <= Game::NUM_BAG_OBJECTS); 172 | uint16_t bagObjectsOffset[Game::NUM_BAG_OBJECTS + 1]; 173 | for (int i = 0; i < count; ++i) { 174 | bagObjectsOffset[i] = loadInt16(); 175 | loadStr(bo[i].name, 20); 176 | } 177 | for (int i = 0; i < count; ++i) { 178 | int dataSize = (i == count - 1) ? totalSize : bagObjectsOffset[i + 1]; 179 | dataSize -= bagObjectsOffset[i]; 180 | bo[i].data = (uint8_t *)malloc(dataSize); 181 | if (bo[i].data) { 182 | memcpy(bo[i].data, bagData + bagObjectsOffset[i], dataSize); 183 | bo[i].dataSize = dataSize; 184 | } 185 | } 186 | free(bagData); 187 | } 188 | } 189 | 190 | void Game::saveState(File *f, int slot) { 191 | _saveOrLoadStream = f; 192 | _saveOrLoadMode = kSaveMode; 193 | 194 | saveInt16(NUM_VARS); 195 | for (int i = 0; i < NUM_VARS; ++i) { 196 | saveInt16(_defaultVarsTable[i]); 197 | } 198 | assert(strchr(_currentSceneScn, '\\') == 0); 199 | saveOrLoadStr(_currentSceneScn, -2); 200 | saveInt16(_sceneObjectsCount); 201 | for (int i = 0; i < _sceneObjectsCount; ++i) { 202 | saveOrLoad_sceneObject(_sceneObjectsTable[i]); 203 | } 204 | saveInt16(NUM_BOXES); 205 | for (int i = 0; i < NUM_BOXES; ++i) { 206 | saveInt16(_boxesCountTable[i]); 207 | } 208 | saveInt16(NUM_BOXES * 10); 209 | for (int i = 0; i < NUM_BOXES; ++i) { 210 | for (int j = 0; j < 10; ++j) { 211 | saveOrLoad_box(_boxesTable[i][j]); 212 | } 213 | } 214 | saveInt16(NUM_VARS); 215 | for (int i = 0; i < NUM_VARS; ++i) { 216 | saveInt16(_varsTable[i]); 217 | } 218 | saveInt16(NUM_SCENE_OBJECT_STATUS); 219 | for (int i = 0; i < NUM_SCENE_OBJECT_STATUS; ++i) { 220 | saveOrLoad_sceneObjectStatus(_sceneObjectStatusTable[i]); 221 | } 222 | saveInt16(_bagPosX); 223 | saveInt16(_bagPosY); 224 | saveInt16(_currentBagObject); 225 | save_bagObjects(_bagObjectsTable, _bagObjectsCount); 226 | saveInt16(_currentBagAction); 227 | saveInt32(_musicTrack); 228 | saveInt32(_musicTrack); // original saved it twice... 229 | saveOrLoadStr(_musicName); 230 | debug(DBG_INFO, "Saved state to slot %d", slot); 231 | } 232 | 233 | void Game::loadState(File *f, int slot, bool switchScene) { 234 | _saveOrLoadStream = f; 235 | _saveOrLoadMode = kLoadMode; 236 | 237 | stopMusic(); 238 | 239 | int n = loadInt16(); 240 | assert(n <= NUM_VARS); 241 | for (int i = 0; i < n; ++i) { 242 | _varsTable[i] = loadInt16(); 243 | } 244 | saveOrLoadStr(_tempTextBuffer, -2); 245 | if (switchScene) { 246 | _switchScene = true; 247 | return; 248 | } 249 | _sceneObjectsCount = loadInt16(); 250 | for (int i = 0; i < _sceneObjectsCount; ++i) { 251 | saveOrLoad_sceneObject(_sceneObjectsTable[i]); 252 | } 253 | n = loadInt16(); 254 | assert(n <= NUM_BOXES); 255 | for (int i = 0; i < n; ++i) { 256 | _boxesCountTable[i] = loadInt16(); 257 | } 258 | n = loadInt16(); 259 | assert((n % 10) == 0 && n <= NUM_BOXES * 10); 260 | for (int i = 0; i < n / 10; ++i) { 261 | for (int j = 0; j < 10; ++j) { 262 | saveOrLoad_box(_boxesTable[i][j]); 263 | } 264 | } 265 | n = loadInt16(); 266 | assert(n <= NUM_VARS); 267 | for (int i = 0; i < n; ++i) { 268 | _varsTable[i] = loadInt16(); 269 | } 270 | n = loadInt16(); 271 | memset(_sceneObjectStatusTable, 0, sizeof(_sceneObjectStatusTable)); 272 | assert(n <= NUM_SCENE_OBJECT_STATUS); 273 | for (int i = 0; i < n; ++i) { 274 | saveOrLoad_sceneObjectStatus(_sceneObjectStatusTable[i]); 275 | } 276 | _bagPosX = loadInt16(); 277 | _bagPosY = loadInt16(); 278 | _currentBagObject = loadInt16(); 279 | _previousBagObject = _currentBagObject; 280 | for (int i = 0; i < _bagObjectsCount; ++i) { 281 | free(_bagObjectsTable[i].data); 282 | } 283 | memset(_bagObjectsTable, 0, sizeof(_bagObjectsTable)); 284 | load_bagObjects(_bagObjectsTable, _bagObjectsCount); 285 | _currentBagAction = loadInt16(); 286 | loadInt32(); 287 | // demo .SAV files do not persist any music state 288 | if (slot == kDemoSavSlot) { 289 | debug(DBG_INFO, "Loaded state from .SAV scene '%s'", _tempTextBuffer); 290 | return; 291 | } 292 | _musicTrack = loadInt32(); 293 | saveOrLoadStr(_musicName); 294 | debug(DBG_INFO, "Loaded state from slot %d scene '%s'", slot, _tempTextBuffer); 295 | } 296 | -------------------------------------------------------------------------------- /fs.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Bermuda Syndrome engine rewrite 3 | * Copyright (C) 2007-2011 Gregory Montoir 4 | */ 5 | 6 | #ifdef BERMUDA_WIN32 7 | #define WIN32_LEAN_AND_MEAN 8 | #include 9 | #endif 10 | #ifdef BERMUDA_POSIX 11 | #include 12 | #include 13 | #endif 14 | #include 15 | #include 16 | #include 17 | #include "file.h" 18 | #include "fs.h" 19 | #include "str.h" 20 | 21 | struct FileSystem_impl { 22 | FileSystem_impl() : 23 | _rootDir(0), _fileList(0), _fileCount(0), _filePathSkipLen(0) { 24 | } 25 | 26 | virtual ~FileSystem_impl() { 27 | free(_rootDir); 28 | for (int i = 0; i < _fileCount; ++i) { 29 | free(_fileList[i]); 30 | } 31 | free(_fileList); 32 | } 33 | 34 | void setDataDirectory(const char *dir) { 35 | _rootDir = strdup(dir); 36 | _filePathSkipLen = strlen(dir) + 1; 37 | buildFileListFromDirectory(dir); 38 | } 39 | 40 | int findFileIndex(const char *file) const { 41 | for (int i = 0; i < _fileCount; ++i) { 42 | if (strcasecmp(_fileList[i], file) == 0) { 43 | return i; 44 | } 45 | } 46 | return -1; 47 | } 48 | 49 | virtual const char *findFilePath(const char *file) const { 50 | const int i = findFileIndex(file); 51 | return (i < 0) ? 0 : _fileList[i]; 52 | } 53 | 54 | virtual bool exists(const char *filePath) const { 55 | return findFilePath(filePath) != 0; 56 | } 57 | 58 | virtual void buildFileListFromDirectory(const char *dir) { 59 | } 60 | 61 | void addFileToList(const char *filePath) { 62 | _fileList = (char **)realloc(_fileList, (_fileCount + 1) * sizeof(char *)); 63 | if (_fileList) { 64 | _fileList[_fileCount] = strdup(filePath + _filePathSkipLen); 65 | ++_fileCount; 66 | } 67 | } 68 | 69 | static FileSystem_impl *create(); 70 | 71 | char *_rootDir; 72 | char **_fileList; 73 | int _fileCount; 74 | int _filePathSkipLen; 75 | 76 | }; 77 | 78 | #ifdef BERMUDA_WIN32 79 | struct FileSystem_Win32 : FileSystem_impl { 80 | void buildFileListFromDirectory(const char *dir) { 81 | WIN32_FIND_DATA findData; 82 | char searchPath[MAX_PATH]; 83 | snprintf(searchPath, sizeof(searchPath), "%s/*", dir); 84 | HANDLE h = FindFirstFile(searchPath, &findData); 85 | if (h) { 86 | do { 87 | char filePath[MAX_PATH]; 88 | snprintf(filePath, sizeof(filePath), "%s/%s", dir, findData.cFileName); 89 | if (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { 90 | if (strcmp(findData.cFileName, "..") == 0 || strcmp(findData.cFileName, ".") == 0) { 91 | continue; 92 | } 93 | buildFileListFromDirectory(filePath); 94 | } else { 95 | addFileToList(filePath); 96 | } 97 | } while (FindNextFile(h, &findData)); 98 | FindClose(h); 99 | } 100 | } 101 | }; 102 | FileSystem_impl *FileSystem_impl::create() { return new FileSystem_Win32; } 103 | #endif 104 | 105 | #ifdef BERMUDA_POSIX 106 | struct FileSystem_POSIX : FileSystem_impl { 107 | void buildFileListFromDirectory(const char *dir) { 108 | DIR *d = opendir(dir); 109 | if (d) { 110 | dirent *de; 111 | while ((de = readdir(d)) != NULL) { 112 | if (de->d_name[0] == '.') { 113 | continue; 114 | } 115 | char filePath[MAXPATHLEN]; 116 | snprintf(filePath, sizeof(filePath), "%s/%s", dir, de->d_name); 117 | struct stat st; 118 | if (stat(filePath, &st) == 0) { 119 | if (S_ISDIR(st.st_mode)) { 120 | buildFileListFromDirectory(filePath); 121 | } else { 122 | addFileToList(filePath); 123 | } 124 | } 125 | } 126 | closedir(d); 127 | } 128 | } 129 | }; 130 | FileSystem_impl *FileSystem_impl::create() { return new FileSystem_POSIX; } 131 | #endif 132 | 133 | #ifdef __EMSCRIPTEN__ 134 | struct FileSystem_Emscripten : FileSystem_impl { 135 | const char *findFilePath(const char *file) const { 136 | return file; 137 | } 138 | bool exists(const char *filePath) const { 139 | char path[MAXPATHLEN]; 140 | snprintf(path, sizeof(path), "%s/%s", _rootDir, filePath); 141 | return File().open(path); 142 | } 143 | }; 144 | FileSystem_impl *FileSystem_impl::create() { return new FileSystem_Emscripten; } 145 | #endif 146 | 147 | struct FileSystem_romfs: FileSystem_impl { 148 | static const int kHeaderSize = 16; // signature (8), size (4), checksum (4) 149 | static const int kMaxEntries = 2048; 150 | 151 | struct Entry { 152 | uint32_t offset; 153 | uint32_t size; 154 | }; 155 | 156 | FileSystem_romfs(const char *filePath) { 157 | _filePath = strdup(filePath); 158 | _f.open(filePath); 159 | _f.seek(kHeaderSize); 160 | const int len = readString(0); 161 | const int align = (len + 15) & ~15; 162 | _f.seek(kHeaderSize + align); 163 | readTOC(""); 164 | } 165 | 166 | ~FileSystem_romfs() { 167 | free(_filePath); 168 | } 169 | 170 | uint32_t readLong() { 171 | const uint32_t num = _f.readUint32BE(); 172 | return num; 173 | } 174 | 175 | int readString(char *s) { 176 | int len = 0; 177 | while (1) { 178 | const char c = _f.readByte(); 179 | if (s) { 180 | *s++ = c; 181 | } 182 | if (c == 0) { 183 | break; 184 | } 185 | ++len; 186 | } 187 | return len; 188 | } 189 | 190 | void readTOC(const char *dirPath, int level = 0) { 191 | int pos; 192 | do { 193 | const uint32_t nextOffset = readLong(); 194 | const uint32_t specInfo = readLong(); 195 | const uint32_t dataSize = readLong(); 196 | readLong(); 197 | char name[32]; 198 | readString(name); 199 | char path[MAXPATHLEN]; 200 | snprintf(path, sizeof(path), "%s/%s", dirPath, name); 201 | switch (nextOffset & 7) { 202 | case 1: 203 | if (name[0] != '.') { 204 | _f.seek(specInfo); 205 | readTOC(path, level + 1); 206 | } 207 | break; 208 | case 2: 209 | addFileToList(&path[1]); 210 | assert(_fileCount <= (int)ARRAYSIZE(_fileEntries)); 211 | _fileEntries[_fileCount - 1].offset = (_f.tell() + 15) & ~15; 212 | _fileEntries[_fileCount - 1].size = dataSize; 213 | break; 214 | } 215 | pos = nextOffset & ~15; 216 | _f.seek(pos); 217 | } while (pos != 0); 218 | } 219 | 220 | const Entry *findFileEntry(const char *path) const { 221 | const int i = findFileIndex(path); 222 | return (i < 0) ? 0 : &_fileEntries[i]; 223 | } 224 | 225 | char *_filePath; 226 | File _f; 227 | Entry _fileEntries[kMaxEntries]; 228 | }; 229 | 230 | FileSystem::FileSystem(const char *dataPath) 231 | : _impl(0), _romfs(false) { 232 | struct stat st; 233 | if (stat(dataPath, &st) == 0 && S_ISREG(st.st_mode)) { 234 | File f; 235 | if (f.open(dataPath)) { 236 | char sig[8]; 237 | if (f.read(sig, sizeof(sig)) == sizeof(sig) && memcmp(sig, "-rom1fs-", 8) == 0) { 238 | _romfs = true; 239 | _impl = new FileSystem_romfs(dataPath); 240 | return; 241 | } 242 | } 243 | } 244 | _impl = FileSystem_impl::create(); 245 | _impl->setDataDirectory(dataPath); 246 | } 247 | 248 | FileSystem::~FileSystem() { 249 | delete _impl; 250 | } 251 | 252 | static char *fixPath(const char *src) { 253 | char *path = (char *)malloc(strlen(src) + 4 + 1); 254 | if (path) { 255 | char *dst = path; 256 | if (strncmp(src, "..", 2) == 0) { 257 | src += 3; 258 | } else { 259 | strcpy(dst, "SCN/"); 260 | dst += 4; 261 | } 262 | do { 263 | if (*src == '\\') { 264 | *dst = '/'; 265 | } else { 266 | *dst = *src; 267 | } 268 | ++dst; 269 | } while (*src++); 270 | #ifdef __EMSCRIPTEN__ 271 | stringToUpperCase(path); 272 | #endif 273 | } 274 | return path; 275 | } 276 | 277 | File *FileSystem::openFile(const char *path, bool errorIfNotFound) { 278 | File *f = 0; 279 | char *fixedPath = fixPath(path); 280 | if (fixedPath) { 281 | if (_romfs) { 282 | const FileSystem_romfs::Entry *e = ((FileSystem_romfs *)_impl)->findFileEntry(fixedPath); 283 | if (e) { 284 | f = new File(e->offset, e->size); 285 | if (!f->open(((FileSystem_romfs *)_impl)->_filePath, "rb")) { 286 | delete f; 287 | f = 0; 288 | } 289 | } 290 | } else { 291 | const char *filePath = _impl->findFilePath(fixedPath); 292 | if (filePath) { 293 | f = new File; 294 | char fileSystemPath[MAXPATHLEN]; 295 | snprintf(fileSystemPath, sizeof(fileSystemPath), "%s/%s", _impl->_rootDir, filePath); 296 | if (!f->open(fileSystemPath, "rb")) { 297 | delete f; 298 | f = 0; 299 | } 300 | } 301 | } 302 | free(fixedPath); 303 | } 304 | if (errorIfNotFound && !f) { 305 | error("Unable to open '%s'", path); 306 | } 307 | return f; 308 | } 309 | 310 | void FileSystem::closeFile(File *f) { 311 | if (f) { 312 | f->close(); 313 | delete f; 314 | } 315 | } 316 | 317 | bool FileSystem::existFile(const char *path) { 318 | bool exists = false; 319 | char *fixedPath = fixPath(path); 320 | if (fixedPath) { 321 | exists = _impl->exists(fixedPath); 322 | free(fixedPath); 323 | } 324 | return exists; 325 | } 326 | -------------------------------------------------------------------------------- /tools/convert_wgp.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #define ZLIB_CONST 8 | #include 9 | 10 | static uint16_t READ_LE_UINT16(const void *ptr) { 11 | const uint8_t *b = (const uint8_t *)ptr; 12 | return (b[1] << 8) | b[0]; 13 | } 14 | 15 | static uint32_t READ_LE_UINT32(const void *ptr) { 16 | const uint8_t *b = (const uint8_t *)ptr; 17 | return (b[3] << 24) | (b[2] << 16) | (b[1] << 8) | b[0]; 18 | } 19 | 20 | struct BitStream { 21 | const uint8_t *_src; 22 | uint16_t _bits; 23 | int _len; 24 | 25 | void reset(const uint8_t *src) { 26 | _src = src; 27 | _bits = READ_LE_UINT16(_src); _src += 2; 28 | _len = 16; 29 | } 30 | 31 | bool getNextBit() { 32 | const bool bit = (_bits & (1 << (16 - _len))) != 0; 33 | --_len; 34 | if (_len == 0) { 35 | _bits = READ_LE_UINT16(_src); _src += 2; 36 | _len = 16; 37 | } 38 | return bit; 39 | } 40 | 41 | uint8_t getNextByte() { 42 | uint8_t b = *_src++; 43 | return b; 44 | } 45 | 46 | uint16_t getNextWord() { 47 | uint16_t w = READ_LE_UINT16(_src); _src += 2; 48 | return w; 49 | } 50 | }; 51 | 52 | static int decodeLzss(const uint8_t *src, uint8_t *dst) { 53 | BitStream stream; 54 | int outputSize = READ_LE_UINT32(src); src += 4; 55 | int inputSize = READ_LE_UINT32(src); src += 4; 56 | for (const uint8_t *compressedData = src; inputSize != 0; compressedData += 0x1000) { 57 | int decodeSize = inputSize; 58 | if (decodeSize > 256) { 59 | decodeSize = 256; 60 | } 61 | inputSize -= decodeSize; 62 | if (1) { 63 | src = compressedData; 64 | const uint16_t crc = READ_LE_UINT16(src); src += 2; 65 | uint16_t sum = 0; 66 | for (int i = 0; i < decodeSize * 8 - 1; ++i) { 67 | sum = ((sum & 1) << 15) | (sum >> 1); 68 | sum ^= READ_LE_UINT16(src); src += 2; 69 | } 70 | if (sum != crc) { 71 | fprintf(stderr, "Invalid checksum, expected 0x%X got 0x%X\n", crc, sum); 72 | } 73 | } 74 | src = compressedData + 2; 75 | stream.reset(src); 76 | while (1) { 77 | if (stream.getNextBit()) { 78 | *dst++ = stream.getNextByte(); 79 | continue; 80 | } 81 | uint16_t size = 0; 82 | uint16_t offset = 0; 83 | if (stream.getNextBit()) { 84 | uint16_t code = stream.getNextWord(); 85 | offset = 0xE000 | ((code >> 3) & 0x1F00) | (code & 0xFF); 86 | if (code & 0x700) { 87 | size = ((code >> 8) & 7) + 2; 88 | } else { 89 | code = stream.getNextByte(); 90 | if (code == 0) { 91 | return outputSize; 92 | } else if (code == 1) { 93 | continue; 94 | } else if (code == 2) { 95 | break; 96 | } else { 97 | size = code + 1; 98 | } 99 | } 100 | } else { 101 | for (int i = 0; i < 2; ++i) { 102 | size <<= 1; 103 | if (stream.getNextBit()) { 104 | size |= 1; 105 | } 106 | } 107 | size += 2; 108 | offset = 0xFF00 | stream.getNextByte(); 109 | } 110 | while (size--) { 111 | *dst = *(dst + (int16_t)offset); 112 | ++dst; 113 | } 114 | } 115 | } 116 | return outputSize; 117 | } 118 | 119 | static const int kBitmapSize = 40 + 256 * 4 + 640 * 480 + 256 * 4; 120 | 121 | static uint8_t _bitmapBuffer[kBitmapSize]; 122 | static uint8_t _tempBuffer[kBitmapSize]; 123 | 124 | static uint16_t freadUint16LE(FILE *fp) { 125 | uint8_t buf[2]; 126 | fread(buf, 1, sizeof(buf), fp); 127 | return READ_LE_UINT16(buf); 128 | } 129 | 130 | static uint32_t freadUint32LE(FILE *fp) { 131 | uint8_t buf[4]; 132 | fread(buf, 1, sizeof(buf), fp); 133 | return READ_LE_UINT32(buf); 134 | } 135 | 136 | static int decodeWGP(FILE *fp) { 137 | const uint16_t tag = freadUint16LE(fp); 138 | if (tag != 0x5057) { 139 | fprintf(stderr, "Invalid tag '0x%04x'\n", tag); 140 | return 0; 141 | } 142 | fseek(fp, 0, SEEK_END); 143 | int dataSize = ftell(fp); 144 | fseek(fp, 2, SEEK_SET); 145 | int len = 0; 146 | while (dataSize > 0) { 147 | const int sz = freadUint16LE(fp); 148 | dataSize -= 2; 149 | if (feof(fp)) { 150 | break; 151 | } 152 | if (sz != 0) { 153 | assert(sz < sizeof(_tempBuffer)); 154 | fread(_tempBuffer, 1, sz, fp); 155 | const int decodedSize = decodeLzss(_tempBuffer, _bitmapBuffer + len); 156 | len += decodedSize; 157 | dataSize -= sz; 158 | } 159 | } 160 | return len; 161 | } 162 | 163 | static int decodeBMP(FILE *fp) { 164 | const uint16_t tag = freadUint16LE(fp); 165 | if (tag != 0x4D42) { 166 | fprintf(stderr, "Invalid tag '0x%04x'\n", tag); 167 | return 0; 168 | } 169 | const uint32_t size = freadUint32LE(fp); 170 | fseek(fp, 14, SEEK_SET); 171 | int count = fread(_bitmapBuffer, 1, 40 + 4 * 256, fp); 172 | if (count != 40 + 4 * 256) { 173 | fprintf(stdout, "fread() return %d\n", count); 174 | return 0; 175 | } 176 | const int x2 = READ_LE_UINT32(_bitmapBuffer + 4) - 1; 177 | const int y2 = READ_LE_UINT32(_bitmapBuffer + 8) - 1; 178 | fprintf(stdout, "BMP %d,%d size %d\n", x2, y2, size); 179 | _bitmapBuffer[count++] = x2 & 255; 180 | _bitmapBuffer[count++] = x2 >> 8; 181 | _bitmapBuffer[count++] = y2 & 255; 182 | _bitmapBuffer[count++] = y2 >> 8; 183 | int count2 = fread(_bitmapBuffer + count, 1, size - 14 - count, fp); 184 | return count + count2; 185 | } 186 | 187 | enum { 188 | kSprBufferRead, 189 | kSprBufferDecoded, 190 | kSprBufferDeflated, 191 | }; 192 | 193 | static uint8_t _sprBuffer[3][0x10000]; 194 | 195 | static const int kMaxSprFrames = 1024; 196 | 197 | static struct { 198 | uint8_t *data; 199 | uint16_t dataSize; 200 | uint8_t dim[10]; /* 5x uint16_t */ 201 | } _sprFrame[kMaxSprFrames]; 202 | 203 | static const int kMaxSpr = 256; 204 | 205 | static int _sprCount[kMaxSpr]; 206 | 207 | static int decodeSPR(FILE *fp) { 208 | const uint16_t tag = freadUint16LE(fp); 209 | if (tag != 0x3553) { 210 | fprintf(stderr, "Invalid tag '0x%04x'\n", tag); 211 | return 0; 212 | } 213 | int spr = 0; 214 | int offset = 0; 215 | while (1) { 216 | const int count = freadUint16LE(fp); 217 | if (count == 0) { 218 | break; 219 | } 220 | assert(spr < kMaxSpr); 221 | _sprCount[spr] = count; 222 | ++spr; 223 | assert(offset + count < kMaxSprFrames); 224 | for (int i = 0; i < count; ++i) { 225 | const int len = freadUint16LE(fp); 226 | fread(_sprBuffer[kSprBufferRead], 1, len, fp); 227 | const int decodedSize = decodeLzss(_sprBuffer[kSprBufferRead], _sprBuffer[kSprBufferDecoded]); 228 | assert(decodedSize < 0x10000); 229 | uint8_t *p = (uint8_t *)malloc(decodedSize); 230 | if (p) { 231 | memcpy(p, _sprBuffer[kSprBufferDecoded], decodedSize); 232 | _sprFrame[offset + i].data = p; 233 | _sprFrame[offset + i].dataSize = decodedSize; 234 | } 235 | fread(_sprFrame[offset + i].dim, 1, 5 * 2, fp); 236 | const uint8_t *hdr = _sprFrame[offset + i].dim; 237 | const int w = READ_LE_UINT16(hdr + 2); 238 | const int h = READ_LE_UINT16(hdr + 4); 239 | // fprintf(stdout, "SPR %d/%d size %d,%d dim %d,%d\n", i, count, len, decodedSize, w, h); 240 | } 241 | offset += count; 242 | } 243 | return 0; 244 | } 245 | 246 | static int deflate(const uint8_t *in, int len, uint8_t *out, int outSize) { 247 | int ret; 248 | z_stream s; 249 | 250 | memset(&s, 0, sizeof(s)); 251 | ret = deflateInit(&s, Z_DEFAULT_COMPRESSION); 252 | if (ret != Z_OK) { 253 | fprintf(stderr, "deflateInit() ret %d\n", ret); 254 | return -1; 255 | } 256 | s.avail_in = len; 257 | s.next_in = in; 258 | s.avail_out = outSize; 259 | s.next_out = out; 260 | ret = deflate(&s, Z_FINISH); 261 | if (ret != Z_STREAM_END) { 262 | fprintf(stderr, "deflate() ret %d\n", ret); 263 | return -1; 264 | } 265 | deflateEnd(&s); 266 | return s.total_out; 267 | } 268 | 269 | static void fwriteUint16LE(FILE *fp, uint16_t n) { 270 | fputc(n & 0xFF, fp); 271 | fputc(n >> 8, fp); 272 | } 273 | 274 | static void fwriteUint32LE(FILE *fp, uint32_t n) { 275 | fputc(n & 0xFF, fp); 276 | fputc(n >> 8, fp); 277 | fputc(n >> 16, fp); 278 | fputc(n >> 24, fp); 279 | } 280 | 281 | static void writeSPR(const char *path) { 282 | FILE *fp = fopen(path, "wb"); 283 | if (fp) { 284 | fwriteUint16LE(fp, 0x355A); // '5Z' 285 | int offset = 0; 286 | for (int spr = 0; _sprCount[spr] != 0; ++spr) { 287 | fwriteUint16LE(fp, _sprCount[spr]); 288 | for (int i = 0; i < _sprCount[spr]; ++i) { 289 | if (0) { /* uncompressed */ 290 | fwriteUint16LE(fp, _sprFrame[offset + i].dataSize); 291 | fwrite(_sprFrame[offset + i].data, 1, _sprFrame[offset + i].dataSize, fp); 292 | } else { /* zlib */ 293 | const int size = deflate(_sprFrame[offset + i].data, _sprFrame[offset + i].dataSize, _sprBuffer[kSprBufferDeflated], 0x10000); 294 | assert(size + 8 < 0x10000); 295 | fwriteUint16LE(fp, size + 8); 296 | fwriteUint32LE(fp, size); 297 | fwriteUint32LE(fp, _sprFrame[offset + i].dataSize); 298 | fwrite(_sprBuffer[kSprBufferDeflated], 1, size, fp); 299 | } 300 | fwrite(_sprFrame[offset + i].dim, 1, 5 * 2, fp); 301 | } 302 | offset += _sprCount[spr]; 303 | } 304 | fwriteUint16LE(fp, 0); 305 | fclose(fp); 306 | } 307 | } 308 | 309 | static void writeWGP(const char *path, uint8_t *buf, int size, bool saveAsBitmap = false) { 310 | FILE *fp = fopen(path, "wb"); 311 | if (fp) { 312 | if (saveAsBitmap) { 313 | // bmp_file_header_t 314 | fwriteUint16LE(fp, 0x4D42); // type 315 | fwriteUint32LE(fp, 14 + size - 4); // size 316 | fwriteUint16LE(fp, 0); // reserved1 317 | fwriteUint16LE(fp, 0); // reserved2 318 | fwriteUint32LE(fp, 14 + 40 + 4 * 256); // off_bits 319 | // bmp_info_header_t and palette 320 | static const int len = 40 + 4 * 256; 321 | fwrite(buf, 1, len, fp); 322 | const int w = READ_LE_UINT16(buf + len); 323 | const int h = READ_LE_UINT16(buf + len + 2); 324 | fprintf(stdout, "write '%s' size %d dim %d,%d\n", path, size, w, h); 325 | // bitmap_bits 326 | fwrite(buf + len + 4, 1, size - len - 4, fp); 327 | } else { 328 | fwriteUint16LE(fp, 0x505A); // 'PZ' 329 | const int compressedSize = deflate(buf, size, _tempBuffer, sizeof(_tempBuffer)); 330 | fwriteUint32LE(fp, compressedSize); 331 | fwriteUint32LE(fp, size); 332 | fwrite(_tempBuffer, 1, compressedSize, fp); 333 | } 334 | fclose(fp); 335 | } 336 | } 337 | 338 | int main(int argc, char *argv[]) { 339 | if (argc == 3) { 340 | FILE *fp = fopen(argv[1], "rb"); 341 | if (fp) { 342 | const char *ext = strrchr(argv[1], '.'); 343 | if (ext) { 344 | if (strcasecmp(ext + 1, "WGP") == 0) { 345 | const int bitmapSize = decodeWGP(fp); 346 | writeWGP(argv[2], _bitmapBuffer, bitmapSize); 347 | } else if (strcasecmp(ext + 1, "BMP") == 0) { 348 | const int bitmapSize = decodeBMP(fp); 349 | writeWGP(argv[2], _bitmapBuffer, bitmapSize); 350 | } else if (strcasecmp(ext + 1, "SPR") == 0) { 351 | decodeSPR(fp); 352 | writeSPR(argv[2]); 353 | } 354 | } 355 | fclose(fp); 356 | } 357 | } 358 | return 0; 359 | } 360 | -------------------------------------------------------------------------------- /main_libretro.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | 4 | #include "file.h" 5 | #include "game.h" 6 | #include "mixer.h" 7 | #include "systemstub.h" 8 | 9 | static const char *kName = "Bermuda Syndrome"; 10 | static const char *kVersion = "0.1.7"; 11 | 12 | static const int kAudioHz = 22050; 13 | static const int kFps = 20; 14 | 15 | static const int kMaxSaveStateSize = 128 * 1024; 16 | 17 | static Game *g_game; 18 | static char *g_dataPath; 19 | 20 | // S16 stereo samples for each frame 21 | static int16_t _audioBuffer[kAudioHz / kFps * 2]; 22 | 23 | static int _controllerDevicePort0 = RETRO_DEVICE_KEYBOARD; 24 | 25 | struct SystemStub_libretro: SystemStub { 26 | 27 | uint32_t _palette[256]; 28 | uint32_t *_offscreenBuffer; 29 | int _w, _h; 30 | Mixer *_mixer; 31 | AudioCallback _audioProc; 32 | void *_audioData; 33 | 34 | SystemStub_libretro() { 35 | _mixer = Mixer_Software_create(this); 36 | _audioProc = 0; 37 | } 38 | 39 | ~SystemStub_libretro() { 40 | delete _mixer; 41 | _mixer = 0; 42 | } 43 | 44 | virtual void init(const char *title, int w, int h, bool fullscreen, int screenMode) { 45 | _offscreenBuffer = (uint32_t *)malloc(w * h * sizeof(uint32_t)); 46 | _w = w; 47 | _h = h; 48 | _mixer->open(); 49 | } 50 | virtual void destroy() { 51 | _mixer->close(); 52 | free(_offscreenBuffer); 53 | _offscreenBuffer = 0; 54 | delete _mixer; 55 | _mixer = 0; 56 | } 57 | 58 | virtual void setIcon(const uint8_t *data, int size) { 59 | } 60 | virtual void showCursor(bool show) { 61 | } 62 | 63 | virtual void setPalette(const uint8_t *pal, int n) { 64 | for (int i = 0; i < n; ++i) { 65 | _palette[i] = (pal[2] << 16) | (pal[1] << 8) | pal[0]; 66 | pal += 4; 67 | } 68 | } 69 | virtual void fillRect(int x, int y, int w, int h, uint8_t color) { 70 | assert(x >= 0 && x + w <= _w && y >= 0 && y + h <= _h); 71 | uint32_t *dst = _offscreenBuffer + y * _w + x; 72 | const uint32_t rgb = _palette[color]; 73 | for (int j = 0; j < h; ++j) { 74 | for (int i = 0; i < w; ++i) { 75 | dst[i] = rgb; 76 | } 77 | dst += w; 78 | } 79 | } 80 | virtual void copyRect(int x, int y, int w, int h, const uint8_t *buf, int pitch, bool transparent = false) { 81 | assert(x >= 0 && x + w <= _w && y >= 0 && y + h <= _h); 82 | uint32_t *dst = _offscreenBuffer + y * _w + x; 83 | buf += (h - 1) * pitch; 84 | for (int j = 0; j < h; ++j) { 85 | for (int i = 0; i < w; ++i) { 86 | if (!transparent || buf[i] != 0) { 87 | dst[i] = _palette[buf[i]]; 88 | } 89 | } 90 | dst += _w; 91 | buf -= pitch; 92 | } 93 | } 94 | virtual void darkenRect(int x, int y, int w, int h) { 95 | assert(x >= 0 && x + w <= _w && y >= 0 && y + h <= _h); 96 | const uint32_t redBlueMask = 0xFF00FF; 97 | const uint32_t greenMask = 0xFF00; 98 | uint32_t *dst = _offscreenBuffer + y * _w + x; 99 | for (int j = 0; j < h; ++j) { 100 | for (int i = 0; i < w; ++i) { 101 | uint32_t color = ((dst[i] & redBlueMask) >> 1) & redBlueMask; 102 | color |= ((dst[i] & greenMask) >> 1) & greenMask; 103 | dst[i] = color; 104 | } 105 | dst += w; 106 | } 107 | } 108 | 109 | virtual void copyRectWidescreen(int w, int h, const uint8_t *buf, int pitch) { 110 | } 111 | virtual void clearWidescreen() { 112 | } 113 | virtual void updateScreen() { 114 | } 115 | 116 | virtual void setYUV(bool flag, int w, int h) { 117 | } 118 | virtual uint8_t *lockYUV(int *pitch) { 119 | return 0; 120 | } 121 | virtual void unlockYUV() { 122 | } 123 | 124 | virtual void processEvents() { 125 | } 126 | virtual void sleep(int duration) { 127 | } 128 | virtual uint32_t getTimeStamp() { 129 | return 0; 130 | } 131 | 132 | virtual void lockAudio() { 133 | } 134 | virtual void unlockAudio() { 135 | } 136 | virtual void startAudio(AudioCallback callback, void *param) { 137 | _audioProc = callback; 138 | _audioData = param; 139 | } 140 | virtual void stopAudio() { 141 | _audioProc = 0; 142 | } 143 | virtual int getOutputSampleRate() { 144 | return kAudioHz; 145 | } 146 | 147 | virtual Mixer *getMixer() { 148 | return _mixer; 149 | } 150 | } g_stub; 151 | 152 | static retro_pixel_format _pixelFormat = RETRO_PIXEL_FORMAT_XRGB8888; 153 | 154 | struct retro_input_descriptor _inputDescriptors[] = { 155 | { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_UP, "Up" }, 156 | { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_DOWN, "Down" }, 157 | { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_LEFT, "Left" }, 158 | { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_RIGHT, "Right" }, 159 | { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_A, "Use / Action" }, // enter 160 | { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_B, "Take Gun / Shoot" }, // space 161 | { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_X, "Holster Gun / Run" }, // shift 162 | { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_Y, "Inventory" }, // tab 163 | { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_SELECT, "Status" }, // ctrl 164 | { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_START, "Menu" }, // escape 165 | { 0 } 166 | }; 167 | 168 | static struct retro_variable _vars[] = { 169 | { "no_hit", "Disable life countdown when hit; true|false" }, 170 | { 0, 0 } 171 | }; 172 | 173 | static retro_log_printf_t log_cb; 174 | static retro_video_refresh_t video_cb; 175 | static retro_input_poll_t input_poll_cb; 176 | static retro_input_state_t input_state_cb; 177 | static retro_environment_t environ_cb; 178 | static retro_audio_sample_t audio_cb; 179 | static retro_audio_sample_batch_t audio_batch_cb; 180 | 181 | void retro_set_environment(retro_environment_t cb) { 182 | environ_cb = cb; 183 | } 184 | 185 | void retro_set_video_refresh(retro_video_refresh_t cb) { 186 | video_cb = cb; 187 | } 188 | 189 | void retro_set_audio_sample(retro_audio_sample_t cb) { 190 | audio_cb = cb; 191 | } 192 | 193 | void retro_set_audio_sample_batch(retro_audio_sample_batch_t cb) { 194 | audio_batch_cb = cb; 195 | } 196 | 197 | void retro_set_input_poll(retro_input_poll_t cb) { 198 | input_poll_cb = cb; 199 | } 200 | 201 | void retro_set_input_state(retro_input_state_t cb) { 202 | input_state_cb = cb; 203 | } 204 | 205 | void retro_get_system_info(struct retro_system_info *info) { 206 | memset(info, 0, sizeof(*info)); 207 | info->library_name = kName; 208 | info->library_version = kVersion; 209 | info->need_fullpath = true; 210 | } 211 | 212 | void retro_get_system_av_info(struct retro_system_av_info *info) { 213 | memset(info, 0, sizeof(*info)); 214 | info->timing.fps = kFps; 215 | info->timing.sample_rate = kAudioHz; 216 | info->geometry.base_width = kGameScreenWidth; 217 | info->geometry.base_height = kGameScreenHeight; 218 | info->geometry.max_width = kGameScreenWidth; 219 | info->geometry.max_height = kGameScreenHeight; 220 | info->geometry.aspect_ratio = 4.0f / 3.0f; 221 | } 222 | 223 | void retro_set_controller_port_device(unsigned port, unsigned device) { 224 | if (port == 0) { 225 | _controllerDevicePort0 = device; 226 | } 227 | } 228 | 229 | size_t retro_serialize_size() { 230 | return kMaxSaveStateSize; 231 | } 232 | 233 | bool retro_serialize(void *data, size_t size) { 234 | if (g_game->_state == kStateGame) { 235 | File f((uint8_t *)data, size); 236 | g_game->saveState(&f, 0); 237 | return !f.ioErr(); 238 | } 239 | return false; 240 | } 241 | 242 | bool retro_unserialize(const void *data, size_t size) { 243 | if (g_game->_state == kStateGame) { 244 | File f((const uint8_t *)data, size); 245 | g_game->loadState(&f, 0, false); 246 | return !f.ioErr(); 247 | } 248 | return false; 249 | } 250 | 251 | void retro_cheat_reset() { 252 | } 253 | 254 | void retro_cheat_set(unsigned index, bool enabled, const char *code) { 255 | } 256 | 257 | bool retro_load_game(const struct retro_game_info *info) { 258 | 259 | environ_cb(RETRO_ENVIRONMENT_SET_INPUT_DESCRIPTORS, _inputDescriptors); 260 | environ_cb(RETRO_ENVIRONMENT_SET_VARIABLES, _vars); 261 | environ_cb(RETRO_ENVIRONMENT_SET_PIXEL_FORMAT, &_pixelFormat); 262 | 263 | g_debugMask = DBG_INFO; // | DBG_GAME | DBG_OPCODES | DBG_DIALOGUE; 264 | 265 | g_dataPath = strdup(info->path); 266 | const char *savePath = "."; 267 | g_game = new Game(&g_stub, g_dataPath, savePath, g_dataPath); 268 | g_game->init(false, SCREEN_MODE_DEFAULT); 269 | 270 | return true; 271 | } 272 | 273 | bool retro_load_game_special(unsigned game_type, const struct retro_game_info *info, size_t num_info) { 274 | return false; 275 | } 276 | 277 | void retro_unload_game() { 278 | if (g_game) { 279 | delete g_game; 280 | g_game = 0; 281 | } 282 | } 283 | 284 | unsigned retro_get_region() { 285 | return RETRO_REGION_PAL; 286 | } 287 | 288 | unsigned retro_api_version() { 289 | return RETRO_API_VERSION; 290 | } 291 | 292 | void *retro_get_memory_data(unsigned id) { 293 | return 0; 294 | } 295 | 296 | size_t retro_get_memory_size(unsigned id) { 297 | return 0; 298 | } 299 | 300 | void retro_init() { 301 | struct retro_log_callback log; 302 | if (environ_cb(RETRO_ENVIRONMENT_GET_LOG_INTERFACE, &log)) { 303 | log_cb = log.log; 304 | } 305 | } 306 | 307 | void retro_deinit() { 308 | } 309 | 310 | void retro_reset() { 311 | g_game->restart(); 312 | } 313 | 314 | static const struct { 315 | uint8_t player; 316 | uint8_t joypad; 317 | uint16_t keyboard; 318 | } _directionMapping[] = { 319 | { PlayerInput::DIR_UP, RETRO_DEVICE_ID_JOYPAD_UP, RETROK_UP }, 320 | { PlayerInput::DIR_DOWN, RETRO_DEVICE_ID_JOYPAD_DOWN, RETROK_DOWN }, 321 | { PlayerInput::DIR_LEFT, RETRO_DEVICE_ID_JOYPAD_LEFT, RETROK_LEFT }, 322 | { PlayerInput::DIR_RIGHT, RETRO_DEVICE_ID_JOYPAD_RIGHT, RETROK_RIGHT }, 323 | { 0, 0, 0 } 324 | }; 325 | 326 | static struct { 327 | bool *player; 328 | uint8_t joypad; 329 | uint16_t keyboard; 330 | } _buttonMapping[] = { 331 | { 0, RETRO_DEVICE_ID_JOYPAD_A, RETROK_RETURN }, 332 | { 0, RETRO_DEVICE_ID_JOYPAD_B, RETROK_SPACE }, 333 | { 0, RETRO_DEVICE_ID_JOYPAD_X, RETROK_LSHIFT }, 334 | { 0, RETRO_DEVICE_ID_JOYPAD_Y, RETROK_TAB }, 335 | { 0, RETRO_DEVICE_ID_JOYPAD_SELECT, RETROK_LCTRL }, 336 | { 0, RETRO_DEVICE_ID_JOYPAD_START, RETROK_BACKSPACE }, 337 | { 0 } 338 | }; 339 | 340 | static void updateInput(PlayerInput &pi) { 341 | _buttonMapping[0].player = &g_stub._pi.enter; 342 | _buttonMapping[1].player = &g_stub._pi.space; 343 | _buttonMapping[2].player = &g_stub._pi.shift; 344 | _buttonMapping[3].player = &g_stub._pi.tab; 345 | _buttonMapping[4].player = &g_stub._pi.ctrl; 346 | _buttonMapping[5].player = &g_stub._pi.escape; 347 | for (int i = 0; _buttonMapping[i].player; ++i) { 348 | if (_controllerDevicePort0 == RETRO_DEVICE_JOYPAD) { 349 | *_buttonMapping[i].player = input_state_cb(0, RETRO_DEVICE_JOYPAD, 0, _buttonMapping[i].joypad) != 0; 350 | } else { 351 | *_buttonMapping[i].player = input_state_cb(0, RETRO_DEVICE_KEYBOARD, 0, _buttonMapping[i].keyboard) != 0; 352 | } 353 | } 354 | for (int i = 0; _directionMapping[i].player; ++i) { 355 | if (_controllerDevicePort0 == RETRO_DEVICE_JOYPAD) { 356 | if (input_state_cb(0, RETRO_DEVICE_JOYPAD, 0, _directionMapping[i].joypad)) { 357 | pi.dirMask |= _directionMapping[i].player; 358 | } else { 359 | pi.dirMask &= ~_directionMapping[i].player; 360 | } 361 | } else { 362 | if (input_state_cb(0, RETRO_DEVICE_KEYBOARD, 0, _directionMapping[i].keyboard)) { 363 | pi.dirMask |= _directionMapping[i].player; 364 | } else { 365 | pi.dirMask &= ~_directionMapping[i].player; 366 | } 367 | } 368 | } 369 | } 370 | 371 | void retro_run() { 372 | bool updated = false; 373 | if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE_UPDATE, &updated) && updated) { 374 | retro_variable var; 375 | var.key = _vars[0].key; // 'no_hit' 376 | if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { 377 | if (strcmp(var.value, "true") == 0) { 378 | g_game->_cheats |= kCheatNoHit; 379 | } else { 380 | g_game->_cheats &= ~kCheatNoHit; 381 | } 382 | } 383 | } 384 | updateInput(g_stub._pi); 385 | g_game->mainLoop(); 386 | video_cb(g_stub._offscreenBuffer, g_stub._w, g_stub._h, g_stub._w * sizeof(uint32_t)); 387 | g_stub._audioProc(g_stub._audioData, (uint8_t *)_audioBuffer, sizeof(_audioBuffer)); 388 | audio_batch_cb(_audioBuffer, sizeof(_audioBuffer) / sizeof(int16_t)); 389 | } 390 | -------------------------------------------------------------------------------- /avi_player.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Bermuda Syndrome engine rewrite 3 | * Copyright (C) 2007-2011 Gregory Montoir 4 | */ 5 | 6 | #include "avi_player.h" 7 | #include "file.h" 8 | #include "mixer.h" 9 | #include "systemstub.h" 10 | 11 | bool AVI_Demuxer::open(File *f) { 12 | _f = f; 13 | _recordsListSize = 0; 14 | _chunkData = 0; 15 | _chunkDataSize = 0; 16 | return _f != 0 && readHeader(); 17 | } 18 | 19 | void AVI_Demuxer::close() { 20 | _f = 0; 21 | free(_chunkData); 22 | _chunkData = 0; 23 | } 24 | 25 | bool AVI_Demuxer::readHeader_avih() { 26 | uint8_t hdr[kSizeOfChunk_avih]; 27 | _f->read(hdr, kSizeOfChunk_avih); 28 | _frameRate = 1000000 / READ_LE_UINT32(hdr); 29 | _frames = READ_LE_UINT32(hdr + 16); 30 | _streams = READ_LE_UINT32(hdr + 24); 31 | _width = READ_LE_UINT32(hdr + 32); 32 | _height = READ_LE_UINT32(hdr + 36); 33 | return _streams == 2 && _width == AVI_Player::kDefaultFrameWidth && _height == AVI_Player::kDefaultFrameHeight; 34 | } 35 | 36 | bool AVI_Demuxer::readHeader_strh() { 37 | uint8_t hdr[kSizeOfChunk_strh]; 38 | _f->read(hdr, kSizeOfChunk_strh); 39 | if (memcmp(hdr, "auds", 4) == 0 && READ_LE_UINT32(hdr + 4) == 0) { 40 | _audioBufferSize = READ_LE_UINT32(hdr + 36); 41 | return true; 42 | } 43 | if (memcmp(hdr, "vids", 4) == 0 && memcmp(hdr + 4, "cvid", 4) == 0) { 44 | _videoBufferSize = READ_LE_UINT32(hdr + 36); 45 | return true; 46 | } 47 | return false; 48 | } 49 | 50 | bool AVI_Demuxer::readHeader_strf_auds() { 51 | uint8_t hdr[kSizeOfChunk_waveformat]; 52 | _f->read(hdr, kSizeOfChunk_waveformat); 53 | int formatTag = READ_LE_UINT16(hdr); 54 | int channels = READ_LE_UINT16(hdr + 2); 55 | int sampleRate = READ_LE_UINT32(hdr + 4); 56 | int bitsPerSample = READ_LE_UINT16(hdr + 14); 57 | return formatTag == 1 && channels == 1 && sampleRate == 44100 && bitsPerSample == 8; 58 | } 59 | 60 | bool AVI_Demuxer::readHeader_strf_vids() { 61 | uint8_t hdr[kSizeOfChunk_bitmapinfo]; 62 | _f->read(hdr, kSizeOfChunk_bitmapinfo); 63 | int width = READ_LE_UINT32(hdr + 4); 64 | int height = READ_LE_UINT32(hdr + 8); 65 | int planes = READ_LE_UINT16(hdr + 12); 66 | int bitDepth = READ_LE_UINT16(hdr + 14); 67 | return width == _width && height == _height && planes == 1 && bitDepth == 24; 68 | } 69 | 70 | bool AVI_Demuxer::readHeader() { 71 | _frames = 0; 72 | _width = _height = 0; 73 | _streams = 0; 74 | _frameRate = 0; 75 | 76 | char tag[4]; 77 | _f->seek(8); // skip RIFF header 78 | _f->read(tag, 4); 79 | if (memcmp(tag, "AVI ", 4) == 0) { 80 | bool readHdrLoop = true; 81 | while (readHdrLoop) { 82 | _f->read(tag, 4); 83 | int len = _f->readUint32LE(); 84 | assert((len & 1) == 0); 85 | if (memcmp(tag, "LIST", 4) == 0) { 86 | _f->read(tag, 4); 87 | if (memcmp(tag, "movi", 4) == 0) { 88 | _chunkDataSize = MAX(_videoBufferSize, _audioBufferSize); 89 | _chunkData = (uint8_t *)malloc(_chunkDataSize); 90 | if (!_chunkData) { 91 | warning("Unable to allocate %d bytes", _chunkDataSize); 92 | return false; 93 | } 94 | return true; 95 | } 96 | } else if (memcmp(tag, "avih", 4) == 0 && len == kSizeOfChunk_avih) { 97 | readHdrLoop = readHeader_avih(); 98 | } else if (memcmp(tag, "strh", 4) == 0 && len == kSizeOfChunk_strh) { 99 | readHdrLoop = readHeader_strh(); 100 | } else if (memcmp(tag, "strf", 4) == 0 && len == kSizeOfChunk_waveformat) { 101 | readHdrLoop = readHeader_strf_auds(); 102 | } else if (memcmp(tag, "strf", 4) == 0 && len == kSizeOfChunk_bitmapinfo) { 103 | readHdrLoop = readHeader_strf_vids(); 104 | } else { 105 | _f->seek(len, SEEK_CUR); 106 | } 107 | } 108 | } 109 | return false; 110 | } 111 | 112 | bool AVI_Demuxer::readNextChunk(AVI_Chunk &chunk) { 113 | char tag[12]; 114 | assert(_recordsListSize >= 0); 115 | if (_recordsListSize == 0) { 116 | _f->read(tag, 12); // 'LIST', size, 'rec ' 117 | _recordsListSize = READ_LE_UINT32(tag + 4) - 4; 118 | } 119 | _f->read(tag, 8); 120 | int len = READ_LE_UINT32(tag + 4); 121 | len = (len + 1) & ~1; 122 | _recordsListSize -= len + 8; 123 | if (tag[2] == 'w' && tag[3] == 'b') { 124 | chunk.type = kChunkAudioType; 125 | } else if (tag[2] == 'd' && tag[3] == 'c') { 126 | chunk.type = kChunkVideoType; 127 | } else { 128 | _f->seek(len, SEEK_CUR); 129 | return false; 130 | } 131 | assert(len <= _chunkDataSize); 132 | _f->read(_chunkData, len); 133 | chunk.data = _chunkData; 134 | chunk.dataSize = len; 135 | return true; 136 | } 137 | 138 | static void SET_YUV_V4(uint8_t *dst, uint8_t y1, uint8_t y2, uint8_t u, uint8_t v) { 139 | dst[0] = u; 140 | dst[1] = y1; 141 | dst[2] = v; 142 | dst[3] = y2; 143 | } 144 | 145 | void Cinepak_Decoder::decodeFrameV4(Cinepak_YUV_Vector *v0, Cinepak_YUV_Vector *v1, Cinepak_YUV_Vector *v2, Cinepak_YUV_Vector *v3) { 146 | uint8_t *p = _yuvFrame + _yPos * _yuvPitch + _xPos * 2; 147 | 148 | SET_YUV_V4(&p[0], v0->y[0], v0->y[1], v0->u, v0->v); 149 | SET_YUV_V4(&p[4], v1->y[0], v1->y[1], v1->u, v1->v); 150 | p += _yuvPitch; 151 | SET_YUV_V4(&p[0], v0->y[2], v0->y[3], v0->u, v0->v); 152 | SET_YUV_V4(&p[4], v1->y[2], v1->y[3], v1->u, v1->v); 153 | p += _yuvPitch; 154 | SET_YUV_V4(&p[0], v2->y[0], v2->y[1], v2->u, v2->v); 155 | SET_YUV_V4(&p[4], v3->y[0], v3->y[1], v3->u, v3->v); 156 | p += _yuvPitch; 157 | SET_YUV_V4(&p[0], v2->y[2], v2->y[3], v2->u, v2->v); 158 | SET_YUV_V4(&p[4], v3->y[2], v3->y[3], v3->u, v3->v); 159 | } 160 | 161 | static void SET_YUV_V1(uint8_t *dst, uint8_t y, uint8_t u, uint8_t v) { 162 | dst[0] = u; 163 | dst[1] = y; 164 | dst[2] = v; 165 | dst[3] = y; 166 | } 167 | 168 | void Cinepak_Decoder::decodeFrameV1(Cinepak_YUV_Vector *v) { 169 | uint8_t *p = _yuvFrame + _yPos * _yuvPitch + _xPos * 2; 170 | 171 | SET_YUV_V1(&p[0], v->y[0], v->u, v->v); 172 | SET_YUV_V1(&p[4], v->y[1], v->u, v->v); 173 | p += _yuvPitch; 174 | SET_YUV_V1(&p[0], v->y[0], v->u, v->v); 175 | SET_YUV_V1(&p[4], v->y[1], v->u, v->v); 176 | p += _yuvPitch; 177 | SET_YUV_V1(&p[0], v->y[2], v->u, v->v); 178 | SET_YUV_V1(&p[4], v->y[3], v->u, v->v); 179 | p += _yuvPitch; 180 | SET_YUV_V1(&p[0], v->y[2], v->u, v->v); 181 | SET_YUV_V1(&p[4], v->y[3], v->u, v->v); 182 | } 183 | 184 | void Cinepak_Decoder::decodeVector(Cinepak_YUV_Vector *v) { 185 | for (int i = 0; i < 4; ++i) { 186 | v->y[i] = readByte(); 187 | } 188 | v->u = 128 + readByte(); 189 | v->v = 128 + readByte(); 190 | } 191 | 192 | void Cinepak_Decoder::decode(const uint8_t *data, int dataSize) { 193 | _data = data; 194 | 195 | const uint8_t flags = readByte(); 196 | _data += 3; 197 | _w = readWord(); 198 | _h = readWord(); 199 | const int strips = readWord(); 200 | assert(_w == AVI_Player::kDefaultFrameWidth && _h == AVI_Player::kDefaultFrameHeight && strips == MAX_STRIPS); 201 | 202 | _xPos = _yPos = 0; 203 | int yMax = 0; 204 | 205 | for (int strip = 0; strip < strips; ++strip) { 206 | if (strip != 0 && (flags & 1) == 0) { 207 | memcpy(&_vectors[kCinepakV1][strip][0], &_vectors[kCinepakV1][strip - 1][0], sizeof(Cinepak_YUV_Vector) * MAX_VECTORS); 208 | memcpy(&_vectors[kCinepakV4][strip][0], &_vectors[kCinepakV4][strip - 1][0], sizeof(Cinepak_YUV_Vector) * MAX_VECTORS); 209 | } 210 | 211 | readWord(); 212 | int size = readWord(); 213 | readWord(); 214 | readWord(); 215 | const int stripHeight = readWord(); 216 | readWord(); 217 | 218 | size -= 12; 219 | _xPos = 0; 220 | yMax += stripHeight; 221 | int v, i; 222 | while (size > 0) { 223 | int chunkType = readWord(); 224 | int chunkSize = readWord(); 225 | size -= chunkSize; 226 | chunkSize -= 4; 227 | 228 | switch (chunkType) { 229 | case 0x2000: 230 | case 0x2200: 231 | v = (chunkType == 0x2200) ? kCinepakV1 : kCinepakV4; 232 | for (i = 0; i < chunkSize / 6; ++i) { 233 | decodeVector(&_vectors[v][strip][i]); 234 | } 235 | chunkSize = 0; 236 | break; 237 | case 0x2100: 238 | case 0x2300: 239 | v = (chunkType == 0x2300) ? kCinepakV1 : kCinepakV4; 240 | i = 0; 241 | while (chunkSize > 0) { 242 | const uint32_t mask = readLong(); 243 | chunkSize -= 4; 244 | for (int bit = 0; bit < 32; ++bit) { 245 | if (mask & (1 << (31 - bit))) { 246 | decodeVector(&_vectors[v][strip][i]); 247 | chunkSize -= 6; 248 | } 249 | ++i; 250 | } 251 | } 252 | break; 253 | case 0x3000: 254 | while (chunkSize > 0 && _yPos < yMax) { 255 | uint32_t mask = readLong(); 256 | chunkSize -= 4; 257 | for (int bit = 0; bit < 32 && _yPos < yMax; ++bit) { 258 | if (mask & (1 << (31 - bit))) { 259 | Cinepak_YUV_Vector *v0 = &_vectors[kCinepakV4][strip][readByte()]; 260 | Cinepak_YUV_Vector *v1 = &_vectors[kCinepakV4][strip][readByte()]; 261 | Cinepak_YUV_Vector *v2 = &_vectors[kCinepakV4][strip][readByte()]; 262 | Cinepak_YUV_Vector *v3 = &_vectors[kCinepakV4][strip][readByte()]; 263 | chunkSize -= 4; 264 | decodeFrameV4(v0, v1, v2, v3); 265 | } else { 266 | Cinepak_YUV_Vector *v0 = &_vectors[kCinepakV1][strip][readByte()]; 267 | --chunkSize; 268 | decodeFrameV1(v0); 269 | } 270 | _xPos += 4; 271 | if (_xPos >= _w) { 272 | _xPos = 0; 273 | _yPos += 4; 274 | } 275 | } 276 | } 277 | break; 278 | case 0x3100: 279 | while (chunkSize > 0 && _yPos < yMax) { 280 | uint32_t mask = readLong(); 281 | chunkSize -= 4; 282 | for (int bit = 0; bit < 32 && chunkSize >= 0 && _yPos < yMax; ) { 283 | if (mask & (1 << (31 - bit))) { 284 | ++bit; 285 | if (bit == 32) { 286 | assert(chunkSize >= 4); 287 | mask = readLong(); 288 | chunkSize -= 4; 289 | bit = 0; 290 | } 291 | if (mask & (1 << (31 - bit))) { 292 | Cinepak_YUV_Vector *v0 = &_vectors[kCinepakV4][strip][readByte()]; 293 | Cinepak_YUV_Vector *v1 = &_vectors[kCinepakV4][strip][readByte()]; 294 | Cinepak_YUV_Vector *v2 = &_vectors[kCinepakV4][strip][readByte()]; 295 | Cinepak_YUV_Vector *v3 = &_vectors[kCinepakV4][strip][readByte()]; 296 | chunkSize -= 4; 297 | decodeFrameV4(v0, v1, v2, v3); 298 | } else { 299 | Cinepak_YUV_Vector *v0 = &_vectors[kCinepakV1][strip][readByte()]; 300 | --chunkSize; 301 | decodeFrameV1(v0); 302 | } 303 | } 304 | ++bit; 305 | _xPos += 4; 306 | if (_xPos >= _w) { 307 | _xPos = 0; 308 | _yPos += 4; 309 | } 310 | } 311 | } 312 | break; 313 | case 0x3200: 314 | while (chunkSize > 0 && _yPos < yMax) { 315 | Cinepak_YUV_Vector *v0 = &_vectors[kCinepakV1][strip][readByte()]; 316 | --chunkSize; 317 | decodeFrameV1(v0); 318 | _xPos += 4; 319 | if (_xPos >= _w) { 320 | _xPos = 0; 321 | _yPos += 4; 322 | } 323 | } 324 | break; 325 | } 326 | _data += chunkSize; 327 | } 328 | } 329 | } 330 | 331 | AVI_Player::AVI_Player(Mixer *mixer, SystemStub *stub) 332 | : _soundQueue(0), _soundTailQueue(0), _mixer(mixer), _stub(stub) { 333 | } 334 | 335 | AVI_Player::~AVI_Player() { 336 | while (_soundQueue) { 337 | AVI_SoundBufferQueue *next = _soundQueue->next; 338 | free(_soundQueue->buffer); 339 | free(_soundQueue); 340 | _soundQueue = next; 341 | } 342 | } 343 | 344 | void AVI_Player::play(File *f) { 345 | _soundQueue = 0; 346 | _soundQueuePreloadSize = 0; 347 | if (_demux.open(f)) { 348 | _stub->setYUV(true, _demux._width, _demux._height); 349 | _mixer->setMusicMix(this, AVI_Player::mixCallback); 350 | for (int i = 0; i < _demux._frames; ++i) { 351 | uint32_t nextFrameTimeStamp = _stub->getTimeStamp() + 1000 / _demux._frameRate; 352 | _stub->processEvents(); 353 | if (_stub->_quit || _stub->_pi.enter) { 354 | _stub->_pi.enter = false; 355 | break; 356 | } 357 | AVI_Chunk chunk; 358 | while (_demux.readNextChunk(chunk)) { 359 | switch (chunk.type) { 360 | case kChunkAudioType: 361 | decodeAudioChunk(chunk); 362 | break; 363 | case kChunkVideoType: 364 | decodeVideoChunk(chunk); 365 | break; 366 | } 367 | } 368 | int diff = nextFrameTimeStamp - _stub->getTimeStamp(); 369 | if (diff > 0) { 370 | _stub->sleep(diff); 371 | } 372 | } 373 | _mixer->setMusicMix(0, 0); 374 | _stub->setYUV(false, 0, 0); 375 | _demux.close(); 376 | } 377 | } 378 | 379 | void AVI_Player::decodeAudioChunk(AVI_Chunk &c) { 380 | AVI_SoundBufferQueue *sbq = (AVI_SoundBufferQueue *)malloc(sizeof(AVI_SoundBufferQueue)); 381 | if (sbq) { 382 | sbq->buffer = (uint8_t *)malloc(c.dataSize); 383 | if (sbq->buffer) { 384 | memcpy(sbq->buffer, c.data, c.dataSize); 385 | sbq->size = c.dataSize; 386 | sbq->offset = 0; 387 | sbq->next = 0; 388 | } else { 389 | free(sbq); 390 | sbq = 0; 391 | } 392 | } 393 | _stub->lockAudio(); 394 | if (sbq) { 395 | if (!_soundQueue) { 396 | _soundQueue = sbq; 397 | } else { 398 | AVI_SoundBufferQueue *p = _soundTailQueue; 399 | assert(!p->next); 400 | p->next = sbq; 401 | } 402 | _soundTailQueue = sbq; 403 | if (_soundQueuePreloadSize < kSoundPreloadSize) { 404 | ++_soundQueuePreloadSize; 405 | } 406 | } 407 | _stub->unlockAudio(); 408 | } 409 | 410 | void AVI_Player::decodeVideoChunk(AVI_Chunk &c) { 411 | _cinepak._yuvFrame = _stub->lockYUV(&_cinepak._yuvPitch); 412 | if (_cinepak._yuvFrame) { 413 | _cinepak.decode(c.data, c.dataSize); 414 | } 415 | _stub->unlockYUV(); 416 | } 417 | 418 | void AVI_Player::mix(int16_t *buf, int samples) { 419 | if (_soundQueuePreloadSize < kSoundPreloadSize) { 420 | return; 421 | } 422 | while (_soundQueue && samples > 0) { 423 | int sample = (_soundQueue->buffer[_soundQueue->offset] << 8) ^ 0x8000; 424 | *buf++ = (int16_t)sample; 425 | *buf++ = (int16_t)sample; 426 | _soundQueue->offset += 2; // skip every second sample (44Khz stream vs 22Khz mixer) 427 | if (_soundQueue->offset >= _soundQueue->size) { 428 | AVI_SoundBufferQueue *next = _soundQueue->next; 429 | free(_soundQueue->buffer); 430 | free(_soundQueue); 431 | _soundQueue = next; 432 | } 433 | --samples; 434 | } 435 | if (!_soundQueue) { 436 | _soundTailQueue = 0; 437 | } 438 | if (samples > 0) { 439 | warning("AVI_Player::mix() soundQueue underrun %d", samples); 440 | } 441 | } 442 | 443 | void AVI_Player::mixCallback(void *param, uint8_t *buf, int len) { 444 | memset(buf, 0, len); 445 | assert((len & 3) == 0); 446 | ((AVI_Player *)param)->mix((int16_t *)buf, len / 4); 447 | } 448 | -------------------------------------------------------------------------------- /mixer_soft.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Bermuda Syndrome engine rewrite 3 | * Copyright (C) 2007-2011 Gregory Montoir 4 | */ 5 | 6 | #include "file.h" 7 | #include "mixer.h" 8 | #include "systemstub.h" 9 | #ifdef BERMUDA_VORBIS 10 | #include 11 | #endif 12 | #ifdef BERMUDA_STB_VORBIS 13 | #include "stb_vorbis.c" 14 | #endif 15 | 16 | static const int _fracStepBits = 8; 17 | static const int _sfxVolume = 256; 18 | static const int _musicVolume = 192; 19 | 20 | struct LockAudioStack { 21 | LockAudioStack(SystemStub *stub) : _stub(stub) { 22 | _stub->lockAudio(); 23 | } 24 | ~LockAudioStack() { 25 | _stub->unlockAudio(); 26 | } 27 | SystemStub *_stub; 28 | }; 29 | 30 | static void mixSample(int16_t &dst, int sample, int volume) { 31 | int pcm = dst + ((sample * volume) >> 8); 32 | if (pcm < -32768) { 33 | pcm = -32768; 34 | } else if (pcm > 32767) { 35 | pcm = 32767; 36 | } 37 | dst = (int16_t)pcm; 38 | } 39 | 40 | struct MixerChannel { 41 | virtual ~MixerChannel() {} 42 | virtual bool load(File *f, int mixerSampleRate) = 0; 43 | virtual int read(int16_t *dst, int samples) = 0; 44 | int id; 45 | }; 46 | 47 | struct MixerChannel_Wav : MixerChannel { 48 | MixerChannel_Wav() 49 | : _buf(0), _bufSize(0), _bufReadOffset(0), _bufReadStep(0) { 50 | } 51 | 52 | virtual ~MixerChannel_Wav() { 53 | if (_buf) { 54 | free(_buf); 55 | _buf = 0; 56 | } 57 | } 58 | 59 | virtual bool load(File *f, int mixerSampleRate) { 60 | char buf[8]; 61 | f->seek(8); // skip RIFF header 62 | f->read(buf, 8); 63 | if (memcmp(buf, "WAVEfmt ", 8) == 0) { 64 | f->readUint32LE(); // fmtLength 65 | int compression = f->readUint16LE(); 66 | int channels = f->readUint16LE(); 67 | int sampleRate = f->readUint32LE(); 68 | f->readUint32LE(); // averageBytesPerSec 69 | f->readUint16LE(); // blockAlign 70 | _bitsPerSample = f->readUint16LE(); 71 | if (compression != 1 || 72 | (channels != 1 && channels != 2) || 73 | (sampleRate != 11025 && sampleRate != 22050 && sampleRate != 44100) || 74 | (_bitsPerSample != 8 && _bitsPerSample != 16)) { 75 | warning("Unhandled wav/pcm format compression %d channels %d rate %d bits %d", compression, channels, sampleRate, _bitsPerSample); 76 | return false; 77 | } 78 | _stereo = (channels == 2); 79 | _bufReadStep = (sampleRate << _fracStepBits) / mixerSampleRate; 80 | f->read(buf, 4); 81 | if (memcmp(buf, "data", 4) == 0) { 82 | _bufSize = f->readUint32LE(); 83 | _buf = (uint8_t *)malloc(_bufSize); 84 | if (_buf) { 85 | f->read(_buf, _bufSize); 86 | return true; 87 | } 88 | } 89 | } 90 | return false; 91 | } 92 | 93 | bool readSample(int16_t &sample) { 94 | switch (_bitsPerSample) { 95 | case 8: 96 | if ((_bufReadOffset >> _fracStepBits) >= _bufSize) { // end of buffer 97 | return false; 98 | } 99 | sample = (_buf[_bufReadOffset >> _fracStepBits] << 8) ^ 0x8000; 100 | break; 101 | case 16: 102 | if ((_bufReadOffset >> _fracStepBits) * 2 >= _bufSize) { // end of buffer 103 | return false; 104 | } 105 | sample = READ_LE_UINT16(&_buf[(_bufReadOffset >> _fracStepBits) * 2]); 106 | break; 107 | } 108 | _bufReadOffset += _bufReadStep; 109 | return true; 110 | } 111 | 112 | virtual int read(int16_t *dst, int samples) { 113 | for (int i = 0; i < samples; ++i) { 114 | int16_t sampleL = 0, sampleR; 115 | if (!readSample(sampleL)) { 116 | return i; 117 | } 118 | sampleR = sampleL; 119 | if (_stereo && !readSample(sampleR)) { 120 | return i; 121 | } 122 | mixSample(*dst++, sampleL, _sfxVolume); 123 | mixSample(*dst++, sampleR, _sfxVolume); 124 | } 125 | return samples; 126 | } 127 | 128 | uint8_t *_buf; 129 | int _bufSize; 130 | int _bufReadOffset; 131 | int _bufReadStep; 132 | int _bitsPerSample; 133 | bool _stereo; 134 | }; 135 | 136 | #ifdef BERMUDA_VORBIS 137 | static size_t file_vorbis_read_helper(void *ptr, size_t size, size_t nmemb, void *datasource) { 138 | if (size != 0 && nmemb != 0) { 139 | int n = ((File *)datasource)->read(ptr, size * nmemb); 140 | if (n > 0) { 141 | return n / size; 142 | } 143 | } 144 | return 0; 145 | } 146 | 147 | static int file_vorbis_seek_helper(void *datasource, ogg_int64_t offset, int whence) { 148 | ((File *)datasource)->seek(offset, whence); 149 | return 0; 150 | } 151 | 152 | static int file_vorbis_close_helper(void *datasource) { 153 | ((File *)datasource)->close(); 154 | delete ((File *)datasource); 155 | return 0; 156 | } 157 | 158 | static long file_vorbis_tell_helper(void *datasource) { 159 | return ((File *)datasource)->tell(); 160 | } 161 | 162 | struct MixerChannel_Vorbis : MixerChannel { 163 | MixerChannel_Vorbis() 164 | : _loop(true), _open(false), _readBuf(0), _readBufSize(0) { 165 | } 166 | 167 | virtual ~MixerChannel_Vorbis() { 168 | if (_open) { 169 | ov_clear(&_ovf); 170 | } 171 | free(_readBuf); 172 | } 173 | 174 | virtual bool load(File *f, int mixerSampleRate) { 175 | ov_callbacks ovcb; 176 | ovcb.read_func = file_vorbis_read_helper; 177 | ovcb.seek_func = file_vorbis_seek_helper; 178 | ovcb.close_func = file_vorbis_close_helper; 179 | ovcb.tell_func = file_vorbis_tell_helper; 180 | if (ov_open_callbacks(f, &_ovf, 0, 0, ovcb) < 0) { 181 | warning("Invalid .ogg file"); 182 | return false; 183 | } 184 | _open = true; 185 | vorbis_info *vi = ov_info(&_ovf, -1); 186 | if (vi->channels != 2 || vi->rate != mixerSampleRate) { 187 | warning("Unhandled ogg/pcm format ch %d rate %d", vi->channels, vi->rate); 188 | return false; 189 | } 190 | return true; 191 | } 192 | 193 | virtual int read(int16_t *dst, int samples) { 194 | int dstSize = samples * sizeof(int16_t) * 2; 195 | if (dstSize > _readBufSize) { 196 | _readBufSize = dstSize; 197 | free(_readBuf); 198 | _readBuf = (char *)malloc(_readBufSize); 199 | if (!_readBuf) { 200 | return 0; 201 | } 202 | } 203 | int readSize = 0; 204 | while (dstSize > 0) { 205 | int len = ov_read(&_ovf, _readBuf, dstSize, 0, 2, 1, 0); 206 | if (len < 0) { 207 | // error in decoder 208 | return 0; 209 | } 210 | if (len == 0) { 211 | if (_loop) { 212 | ov_raw_seek(&_ovf, 0); 213 | continue; 214 | } 215 | break; 216 | } 217 | // mix pcm data 218 | for (unsigned int i = 0; i < len / sizeof(int16_t); ++i) { 219 | const int16_t sample = (int16_t)READ_LE_UINT16(&_readBuf[i * 2]); 220 | mixSample(dst[readSize + i], sample, _musicVolume); 221 | } 222 | readSize += len / sizeof(int16_t); 223 | dstSize -= len; 224 | } 225 | return readSize; 226 | } 227 | 228 | OggVorbis_File _ovf; 229 | bool _loop; 230 | bool _open; 231 | char *_readBuf; 232 | int _readBufSize; 233 | }; 234 | #endif 235 | 236 | #ifdef BERMUDA_STB_VORBIS 237 | struct MixerChannel_StbVorbis : MixerChannel { 238 | MixerChannel_StbVorbis() 239 | : _v(0), _f(0) { 240 | } 241 | ~MixerChannel_StbVorbis() { 242 | if (_v) { 243 | stb_vorbis_close(_v); 244 | _v = 0; 245 | } 246 | } 247 | virtual bool load(File *f, int mixerSampleRate) { 248 | _count = f->read(_buffer, sizeof(_buffer)); 249 | if (_count > 0) { 250 | int bytes = 0; 251 | int error = 0; 252 | _v = stb_vorbis_open_pushdata(_buffer, _count, &bytes, &error, 0); 253 | if (_v) { 254 | _offset = bytes; 255 | stb_vorbis_info info = stb_vorbis_get_info(_v); 256 | if (info.channels != 2 || info.sample_rate != mixerSampleRate) { 257 | warning("Unhandled ogg/pcm format ch %d rate %d", info.channels, info.sample_rate); 258 | return false; 259 | } 260 | _f = f; 261 | _decodedSamplesLen = 0; 262 | return true; 263 | } 264 | } 265 | return false; 266 | } 267 | virtual int read(int16_t *dst, int samples) { 268 | int total = 0; 269 | if (_decodedSamplesLen != 0) { 270 | const int len = MIN(_decodedSamplesLen, samples); 271 | for (int i = 0; i < len; ++i) { 272 | mixSample(*dst++, _decodedSamples[0][i], _musicVolume); 273 | mixSample(*dst++, _decodedSamples[1][i], _musicVolume); 274 | } 275 | total += len; 276 | _decodedSamplesLen -= len; 277 | } 278 | while (total < samples) { 279 | int channels = 0; 280 | float **outputs; 281 | int count; 282 | int bytes = stb_vorbis_decode_frame_pushdata(_v, _buffer + _offset, _count - _offset, &channels, &outputs, &count); 283 | if (bytes == 0) { 284 | if (_offset != _count) { 285 | memmove(_buffer, _buffer + _offset, _count - _offset); 286 | _offset = _count - _offset; 287 | } else { 288 | _offset = 0; 289 | } 290 | _count = sizeof(_buffer) - _offset; 291 | bytes = _f->read(_buffer + _offset, _count); 292 | if (bytes < 0) { 293 | break; 294 | } 295 | if (bytes == 0) { 296 | // rewind 297 | _f->seek(0, SEEK_SET); 298 | _count = _f->read(_buffer, sizeof(_buffer)); 299 | stb_vorbis_flush_pushdata(_v); 300 | } else { 301 | _count = _offset + bytes; 302 | } 303 | _offset = 0; 304 | continue; 305 | } 306 | _offset += bytes; 307 | if (channels == 2) { 308 | const int remain = samples - total; 309 | const int len = MIN(count, remain); 310 | for (int i = 0; i < len; ++i) { 311 | mixSample(*dst++, int(outputs[0][i] * 32768 + .5), _musicVolume); 312 | mixSample(*dst++, int(outputs[1][i] * 32768 + .5), _musicVolume); 313 | } 314 | if (count > remain) { 315 | _decodedSamplesLen = count - remain; 316 | assert(_decodedSamplesLen < 1024); 317 | for (int i = 0; i < _decodedSamplesLen; ++i) { 318 | _decodedSamples[0][i] = int(outputs[0][len + i] * 32768 + .5); 319 | _decodedSamples[1][i] = int(outputs[1][len + i] * 32768 + .5); 320 | } 321 | total = samples; 322 | break; 323 | } 324 | } else { 325 | warning("Invalid decoded data channels %d count %d", channels, count); 326 | } 327 | total += count; 328 | } 329 | return total; 330 | } 331 | 332 | uint8_t _buffer[8192]; 333 | int16_t _decodedSamples[2][1024]; 334 | int _decodedSamplesLen; 335 | uint32_t _offset, _count; 336 | stb_vorbis *_v; 337 | File *_f; 338 | }; 339 | #endif 340 | 341 | struct MixerSoftware: Mixer { 342 | static const int kMaxChannels = 4; 343 | 344 | SystemStub *_stub; 345 | int _channelIdSeed; 346 | bool _open; 347 | MixerChannel *_channels[kMaxChannels]; 348 | 349 | MixerSoftware(SystemStub *stub) 350 | : _stub(stub), _channelIdSeed(0), _open(false) { 351 | memset(_channels, 0, sizeof(_channels)); 352 | } 353 | 354 | virtual ~MixerSoftware() { 355 | for (int i = 0; i < kMaxChannels; ++i) { 356 | if (_channels[i]) { 357 | delete _channels[i]; 358 | } 359 | } 360 | } 361 | 362 | virtual void open() { 363 | if (!_open) { 364 | _stub->startAudio(MixerSoftware::mixCallback, this); 365 | _open = true; 366 | } 367 | } 368 | 369 | virtual void close() { 370 | if (_open) { 371 | _stub->stopAudio(); 372 | _open = false; 373 | } 374 | } 375 | 376 | void startSound(File *f, int *id, MixerChannel *mc) { 377 | if (mc->load(f, _stub->getOutputSampleRate()) && bindChannel(mc, id)) { 378 | return; 379 | } 380 | *id = kDefaultSoundId; 381 | delete mc; 382 | } 383 | 384 | virtual void playSound(File *f, int *id) { 385 | debug(DBG_MIXER, "Mixer::playSound()"); 386 | LockAudioStack las(_stub); 387 | startSound(f, id, new MixerChannel_Wav); 388 | } 389 | 390 | virtual void playMusic(File *f, int *id) { 391 | debug(DBG_MIXER, "Mixer::playMusic()"); 392 | #ifdef BERMUDA_VORBIS 393 | LockAudioStack las(_stub); 394 | startSound(f, id, new MixerChannel_Vorbis); 395 | #endif 396 | #ifdef BERMUDA_STB_VORBIS 397 | LockAudioStack las(_stub); 398 | startSound(f, id, new MixerChannel_StbVorbis); 399 | #endif 400 | } 401 | 402 | virtual bool isSoundPlaying(int id) { 403 | debug(DBG_MIXER, "Mixer::isSoundPlaying() 0x%X", id); 404 | if (id == kDefaultSoundId) { 405 | return false; 406 | } 407 | LockAudioStack las(_stub); 408 | const int channel = getChannelFromSoundId(id); 409 | assert(channel >= 0 && channel < kMaxChannels); 410 | MixerChannel *mc = _channels[channel]; 411 | return (mc && mc->id == id); 412 | } 413 | 414 | virtual void stopSound(int id) { 415 | debug(DBG_MIXER, "Mixer::stopSound() 0x%X", id); 416 | if (id == kDefaultSoundId) { 417 | return; 418 | } 419 | LockAudioStack las(_stub); 420 | const int channel = getChannelFromSoundId(id); 421 | assert(channel >= 0 && channel < kMaxChannels); 422 | MixerChannel *mc = _channels[channel]; 423 | if (mc && mc->id == id) { 424 | delete mc; 425 | _channels[channel] = 0; 426 | } 427 | } 428 | 429 | virtual void stopAll() { 430 | debug(DBG_MIXER, "Mixer::stopAll()"); 431 | LockAudioStack las(_stub); 432 | for (int i = 0; i < kMaxChannels; ++i) { 433 | if (_channels[i]) { 434 | delete _channels[i]; 435 | _channels[i] = 0; 436 | } 437 | } 438 | } 439 | 440 | virtual void setMusicMix(void *param, void (*mix)(void *, uint8_t *, int)) { 441 | _stub->stopAudio(); 442 | if (mix) { 443 | _stub->startAudio(mix, param); 444 | } else { 445 | _stub->startAudio(mixCallback, this); 446 | } 447 | } 448 | 449 | void mix(int16_t *buf, int len) { 450 | assert((len & 1) == 0); 451 | memset(buf, 0, len * sizeof(int16_t)); 452 | for (int i = 0; i < kMaxChannels; ++i) { 453 | MixerChannel *mc = _channels[i]; 454 | if (mc) { 455 | if (mc->read(buf, len / 2) <= 0) { 456 | delete mc; 457 | _channels[i] = 0; 458 | } 459 | } 460 | } 461 | } 462 | 463 | static void mixCallback(void *param, uint8_t *buf, int len) { 464 | assert((len & 1) == 0); 465 | ((MixerSoftware *)param)->mix((int16_t *)buf, len / 2); 466 | } 467 | 468 | int generateSoundId(int channel) { 469 | ++_channelIdSeed; 470 | _channelIdSeed &= 0xFFFF; 471 | assert(channel >= 0 && channel < 16); 472 | return (_channelIdSeed << 4) | channel; 473 | } 474 | 475 | int getChannelFromSoundId(int id) { 476 | return id & 15; 477 | } 478 | 479 | bool bindChannel(MixerChannel *mc, int *id) { 480 | for (int i = 0; i < kMaxChannels; ++i) { 481 | if (!_channels[i]) { 482 | _channels[i] = mc; 483 | *id = mc->id = generateSoundId(i); 484 | return true; 485 | } 486 | } 487 | return false; 488 | } 489 | 490 | void unbindChannel(int channel) { 491 | assert(channel >= 0 && channel < kMaxChannels); 492 | if (_channels[channel]) { 493 | delete _channels[channel]; 494 | _channels[channel] = 0; 495 | } 496 | } 497 | }; 498 | 499 | Mixer *Mixer_Software_create(SystemStub *stub) { 500 | return new MixerSoftware(stub); 501 | } 502 | -------------------------------------------------------------------------------- /dialogue.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Bermuda Syndrome engine rewrite 3 | * Copyright (C) 2007-2011 Gregory Montoir 4 | */ 5 | 6 | #include "decoder.h" 7 | #include "file.h" 8 | #include "game.h" 9 | #include "str.h" 10 | #include "systemstub.h" 11 | 12 | static const Rect _dialogueBackgroundRect = { 33, 165, 574, 150 }; 13 | static const int _selectedDialogueChoiceColor = 0xCAF6FF; 14 | static const int _unselectedDialogueChoiceColor = 0x6DC6FA; 15 | 16 | static void drawChar(uint8_t *dst, int dstPitch, const uint16_t *fontData, int c, uint8_t color) { 17 | int offset = c * 16; 18 | for (int i = 0; i < 16; ++i) { 19 | uint16_t chr = fontData[offset + i]; 20 | for (int b = 0; b < 16; ++b) { 21 | if (chr & (1 << b)) { 22 | dst[-i * dstPitch + b] = color; 23 | } 24 | } 25 | } 26 | } 27 | 28 | static int getHangulGlyphOffset(int codeLo, int codeHi) { 29 | int offset, num; 30 | if (codeHi == 0x20) { 31 | return codeLo * 7; 32 | } else if (codeLo <= 0xAC) { 33 | offset = 128 * 7; 34 | num = codeLo - 0xA1; 35 | } else if (codeLo <= 0xC8) { 36 | offset = 1256 * 7; 37 | num = codeLo - 0xB0; 38 | } else { 39 | offset = 3606 * 7; 40 | num = codeLo - 0xCA; 41 | } 42 | return offset + (num * 94 + (codeHi - 0xA1)) * 7; 43 | } 44 | 45 | static void drawHangulGlyph(uint8_t *dst, int dstPitch, const uint8_t *fontData, uint32_t fontLutOffset, int c, uint8_t color) { 46 | if (!fontData) return; 47 | int offset = getHangulGlyphOffset(c >> 8, c & 0xFF); 48 | 49 | const uint8_t *p = fontData + fontLutOffset + offset; 50 | const int bufferIndex = READ_LE_UINT16(p); 51 | const int bufferOffset = READ_LE_UINT16(p + 2); 52 | 53 | p = fontData + 38 + bufferIndex * 16; 54 | offset = READ_LE_UINT32(p); 55 | assert(bufferOffset < READ_LE_UINT16(p + 4)); 56 | p = fontData + offset + bufferOffset; 57 | 58 | for (int y = 0; y < 16; ++y, p += 2) { 59 | const int bits = p[0] * 256 + p[1]; 60 | for (int x = 0; x < 16; ++x) { 61 | if ((bits & (1 << (15 - x))) == 0) { 62 | dst[-y * dstPitch + x] = color; 63 | } 64 | } 65 | } 66 | } 67 | 68 | static uint8_t findBestMatchingColor(const uint8_t *src, int color) { 69 | uint8_t bestColor = 0; 70 | int bestSum = -1; 71 | int r = color & 0xFF; 72 | int g = (color >> 8) & 0xFF; 73 | int b = (color >> 16) & 0xFF; 74 | for (int i = 0; i < 256; ++i) { 75 | int dr = r - src[2]; 76 | int dg = g - src[1]; 77 | int db = b - src[0]; 78 | int sum = dr * dr + dg * dg + db * db; 79 | if (bestSum == -1 || sum < bestSum) { 80 | bestSum = sum; 81 | bestColor = i; 82 | } 83 | src += 4; 84 | } 85 | return bestColor; 86 | } 87 | 88 | void Game::redrawDialogueTexts() { 89 | debug(DBG_DIALOGUE, "Game::redrawDialogueTexts()"); 90 | char *substringOffset[NUM_DIALOG_CHOICES][8]; 91 | for (int i = 0; i < _dialogueChoiceCounter; ++i) { 92 | char *lastWord = 0; 93 | int lastStringLen = 0; 94 | int stringLen = 0; 95 | int substringCount = 0; 96 | for (char *p = _dialogueChoiceText[i]; *p; ++p) { 97 | int chr = (uint8_t)*p; 98 | if (_textCp949 && (chr & 0x80) != 0) { 99 | ++p; 100 | stringLen += 16; 101 | } else { 102 | stringLen += _fontCharWidth[chr] + 1; 103 | } 104 | if (stringLen > _dialogueTextRect.w) { 105 | assert(substringCount < 8); 106 | substringOffset[i][substringCount] = lastWord; 107 | ++substringCount; 108 | stringLen -= lastStringLen; 109 | } 110 | if (chr == ' ') { 111 | lastWord = p; 112 | lastStringLen = stringLen; 113 | } 114 | } 115 | } 116 | 117 | uint8_t *textBuffer = (uint8_t *)malloc(_dialogueTextRect.w * _dialogueTextRect.h); 118 | if (!textBuffer) { 119 | return; 120 | } 121 | 122 | memset(textBuffer, 0, _dialogueTextRect.w * _dialogueTextRect.h); 123 | int y = 0; 124 | for (int i = 0; i < _dialogueChoiceCounter; ++i) { 125 | int color = (_dialogueSpeechIndex == i) ? _selectedDialogueChoiceColor : _unselectedDialogueChoiceColor; 126 | uint8_t choiceColor = findBestMatchingColor(_bitmapBuffer0 + kOffsetBitmapPalette, color); 127 | int x = 0; 128 | int substring = 0; 129 | for (char *p = _dialogueChoiceText[i]; *p; ++p) { 130 | if (p == substringOffset[i][substring]) { 131 | ++substring; 132 | y += 16; 133 | x = 0; 134 | } else { 135 | if (y + 16 >= _dialogueTextRect.h) { 136 | break; 137 | } 138 | int chr = (uint8_t)*p; 139 | if (_textCp949 && (chr & 0x80) != 0) { 140 | ++p; 141 | chr = (chr << 8) | (uint8_t)*p; 142 | drawHangulGlyph(textBuffer + (_dialogueTextRect.h - 1 - y) * _dialogueTextRect.w + x, _dialogueTextRect.w, _hangulFontData, _hangulFontLutOffset, chr, choiceColor); 143 | x += 16; 144 | } else { 145 | drawChar(textBuffer + (_dialogueTextRect.h - 1 - y) * _dialogueTextRect.w + x, _dialogueTextRect.w, _fontData, chr, choiceColor); 146 | x += _fontCharWidth[chr] + 1; 147 | } 148 | } 149 | } 150 | y += 16; 151 | } 152 | _stub->copyRect(_dialogueTextRect.x, _dialogueTextRect.y, _dialogueTextRect.w, _dialogueTextRect.h, textBuffer, _dialogueTextRect.w, true); 153 | free(textBuffer); 154 | } 155 | 156 | void Game::initDialogue() { 157 | playMusic("..\\midi\\sadialog.mid"); 158 | for (int spr = 0; spr < 3; ++spr) { 159 | for (int i = 0; i < 105; ++i) { 160 | _dialogueSpriteDataTable[spr][i] = 0; 161 | } 162 | } 163 | 164 | _dialogueFrameSpriteData = loadFile("..\\wgp\\frame.spr"); 165 | loadDialogueSprite(0); 166 | loadDialogueSprite(1); 167 | loadDialogueSprite(2); 168 | loadDialogueData(_scriptDialogFileName); 169 | 170 | _stub->_pi.dirMask = 0; 171 | _stub->_pi.escape = false; 172 | _stub->_pi.enter = false; 173 | } 174 | 175 | void Game::handleDialogue() { 176 | debug(DBG_DIALOGUE, "Game::handleDialogue()"); 177 | 178 | if (_stub->_pi.dirMask & PlayerInput::DIR_DOWN) { 179 | _stub->_pi.dirMask &= ~PlayerInput::DIR_DOWN; 180 | if (_dialogueChoiceSelected == 0 && _dialogueSpeechIndex < _dialogueChoiceCounter - 1) { 181 | ++_dialogueSpeechIndex; 182 | } 183 | } 184 | if (_stub->_pi.dirMask & PlayerInput::DIR_UP) { 185 | _stub->_pi.dirMask &= ~PlayerInput::DIR_UP; 186 | if (_dialogueChoiceSelected == 0 && _dialogueSpeechIndex > 0) { 187 | --_dialogueSpeechIndex; 188 | } 189 | } 190 | if (_stub->_pi.escape) { 191 | _stub->_pi.escape = false; 192 | _nextState = kStateGame; 193 | return; 194 | } 195 | if (_stub->_pi.enter) { 196 | _stub->_pi.enter = false; 197 | if (_dialogueChoiceCounter > 1 && _dialogueChoiceSelected == 0) { 198 | win16_sndPlaySound(3, _dialogueChoiceSpeechSoundFile[_dialogueSpeechIndex]); 199 | _dialogueChoiceSelected = 1; 200 | } else { 201 | win16_sndPlaySound(6); 202 | } 203 | } 204 | if (_dialogueChoiceSelected != 0 && win16_sndPlaySound(22)) { 205 | _dialogueChoiceSelected = 0; 206 | strcpy(_tempTextBuffer, _dialogueChoiceNextId[_dialogueSpeechIndex]); 207 | if (_dialogueChoiceGotoFlag[_dialogueSpeechIndex]) { 208 | setupDialog(_dialogueChoiceNextId[_dialogueSpeechIndex]); 209 | if (_dialogueChoiceCounter == 0) { 210 | _nextState = kStateGame; 211 | return; 212 | } 213 | } else { 214 | int n = atoi(_tempTextBuffer); 215 | if (n == 100) { // special scene, Jack kisses Natalia 216 | _dialogueSpriteIndex = 2; 217 | } else { 218 | _dialogueEndedFlag = 1; 219 | _lastDialogueEndedId = atoi(_tempTextBuffer); 220 | return; 221 | } 222 | } 223 | } 224 | 225 | redrawDialogueBackground(); 226 | redrawDialogueSprite(_dialogueSpriteIndex); 227 | if (_dialogueSpriteIndex == 2) { 228 | if (_dialogueSpriteCurrentFrameTable[2] == 0) { 229 | _dialogueEndedFlag = 1; 230 | return; 231 | } 232 | } else { 233 | redrawDialogueTexts(); 234 | } 235 | } 236 | 237 | void Game::finiDialogue() { 238 | unloadDialogueData(); 239 | if (_dialogueFrameSpriteData) { 240 | free(_dialogueFrameSpriteData); 241 | _dialogueFrameSpriteData = 0; 242 | } 243 | for (int spr = 0; spr < 3; ++spr) { 244 | for (int i = 0; i < 105; ++i) { 245 | free(_dialogueSpriteDataTable[spr][i]); 246 | _dialogueSpriteDataTable[spr][i] = 0; 247 | } 248 | } 249 | playMusic(_musicName); 250 | memset(_keysPressed, 0, sizeof(_keysPressed)); 251 | } 252 | 253 | void Game::unloadDialogueData() { 254 | debug(DBG_DIALOGUE, "Game::unloadDialogueData() %d", _loadDialogueDataState); 255 | if (_loadDialogueDataState == 2) { 256 | free(_dialogueDescriptionBuffer); 257 | _dialogueDescriptionBuffer = 0; 258 | _loadDialogueDataState = 1; 259 | } 260 | } 261 | 262 | void Game::setupDialog(const char *dialogId) { 263 | debug(DBG_DIALOGUE, "Game::setupDialog('%s')", dialogId); 264 | if (dialogId[0] == 'J' || dialogId[0] == 'j') { 265 | _dialogueSpriteIndex = 0; 266 | } else { 267 | _dialogueSpriteIndex = 1; 268 | } 269 | _dialogueSpeechIndex = 0; 270 | _dialogueChoiceCounter = 0; 271 | for (int i = 0; i < _dialogueChoiceSize; ++i) { 272 | if (strcmp(_dialogueChoiceData[i].id, dialogId) == 0) { 273 | assert(_dialogueChoiceCounter < NUM_DIALOG_CHOICES); 274 | _dialogueChoiceGotoFlag[_dialogueChoiceCounter] = _dialogueChoiceData[i].gotoFlag; 275 | _dialogueChoiceNextId[_dialogueChoiceCounter] = _dialogueChoiceData[i].nextId; 276 | _dialogueChoiceSpeechSoundFile[_dialogueChoiceCounter] = _dialogueChoiceData[i].speechSoundFile; 277 | _dialogueChoiceText[_dialogueChoiceCounter] = _dialogueChoiceData[i].text; 278 | debug(DBG_DIALOGUE, "Add dialog choice goto %d nextId %s speechSoundFile %s text \"%s\"", 279 | _dialogueChoiceGotoFlag[_dialogueChoiceCounter], 280 | _dialogueChoiceNextId[_dialogueChoiceCounter], 281 | _dialogueChoiceSpeechSoundFile[_dialogueChoiceCounter], 282 | _dialogueChoiceText[_dialogueChoiceCounter]); 283 | ++_dialogueChoiceCounter; 284 | } 285 | } 286 | // only one choice, play the speech directly 287 | if (_dialogueChoiceCounter == 1) { 288 | win16_sndPlaySound(3, _dialogueChoiceSpeechSoundFile[_dialogueSpeechIndex]); 289 | _dialogueChoiceSelected = 1; 290 | } 291 | } 292 | 293 | void Game::loadDialogueSprite(int spr) { 294 | debug(DBG_DIALOGUE, "Game::loadDialogueSprite(%d)", spr); 295 | const char *spriteFile = "..\\wgp\\kiss.spr"; 296 | switch (spr) { 297 | case 0: 298 | spriteFile = _scriptDialogSprite1; 299 | break; 300 | case 1: 301 | spriteFile = _scriptDialogSprite2; 302 | break; 303 | } 304 | FileHolder fp(_fs, spriteFile); 305 | int tag = fp->readUint16LE(); 306 | if (tag != 0x3553) { 307 | error("Invalid spr format %X", tag); 308 | } 309 | int count = fp->readUint16LE(); 310 | assert(count <= 105); 311 | for (int i = 0; i < count; ++i) { 312 | int size = fp->readUint16LE(); 313 | _dialogueSpriteDataTable[spr][i] = (uint8_t *)malloc(size + 10); 314 | if (_dialogueSpriteDataTable[spr][i]) { 315 | fp->read(_dialogueSpriteDataTable[spr][i], size + 10); 316 | } 317 | } 318 | _dialogueSpriteFrameCountTable[spr] = count; 319 | _dialogueSpriteCurrentFrameTable[spr] = 0; 320 | } 321 | 322 | void Game::loadDialogueData(const char *filename) { 323 | debug(DBG_DIALOGUE, "Game::loadDialogueData(%s)", filename); 324 | if (_loadDialogueDataState == 0) { 325 | _loadDialogueDataState = 1; 326 | } else { 327 | unloadDialogueData(); 328 | } 329 | FileHolder fp(_fs, filename); 330 | _dialogueDescriptionSize = fp->size(); 331 | _dialogueDescriptionBuffer = (char *)malloc(_dialogueDescriptionSize + 1); 332 | if (_dialogueDescriptionBuffer) { 333 | _loadDialogueDataState = 2; 334 | fp->read(_dialogueDescriptionBuffer, _dialogueDescriptionSize); 335 | _dialogueDescriptionBuffer[_dialogueDescriptionSize] = 0; 336 | stringStripComments(_dialogueDescriptionBuffer); 337 | parseDLG(); 338 | setupDialog(_scriptDialogId); 339 | } 340 | } 341 | 342 | void Game::redrawDialogueSprite(int num) { 343 | debug(DBG_DIALOGUE, "Game::redrawDialogueSprite(%d)", num); 344 | decodeLzss(_dialogueFrameSpriteData + 2, _tempDecodeBuffer); 345 | uint8_t *spriteBitmap = _tempDecodeBuffer + 25000; 346 | decodeLzss(_dialogueSpriteDataTable[num][_dialogueSpriteCurrentFrameTable[num]], spriteBitmap); 347 | int sprY = getBitmapHeight(_tempDecodeBuffer) - getBitmapHeight(spriteBitmap); 348 | int sprX = getBitmapWidth(_tempDecodeBuffer) - getBitmapWidth(spriteBitmap); 349 | int frameX = 0; 350 | switch (num) { 351 | case 0: 352 | frameX = _dialogueBackgroundRect.x + 13; 353 | _dialogueTextRect.x = frameX + getBitmapWidth(_tempDecodeBuffer) + 10; 354 | break; 355 | case 1: 356 | frameX = _dialogueBackgroundRect.x + _dialogueBackgroundRect.w - getBitmapWidth(_tempDecodeBuffer) - 13; 357 | _dialogueTextRect.x = _dialogueBackgroundRect.x + 10; 358 | break; 359 | case 2: 360 | frameX = _dialogueBackgroundRect.x + (_dialogueBackgroundRect.w - getBitmapWidth(_tempDecodeBuffer)) / 2; 361 | _dialogueTextRect.x = _dialogueBackgroundRect.x; 362 | break; 363 | } 364 | _dialogueTextRect.w = _dialogueBackgroundRect.w - 10 - getBitmapWidth(_tempDecodeBuffer) - 13; 365 | 366 | int frameY = _dialogueBackgroundRect.y + (_dialogueBackgroundRect.h - getBitmapHeight(_tempDecodeBuffer)) / 2; 367 | _dialogueTextRect.y = _dialogueBackgroundRect.y + 10; 368 | _dialogueTextRect.h = _dialogueBackgroundRect.h - 20; 369 | 370 | _stub->copyRect(frameX, frameY, getBitmapWidth(_tempDecodeBuffer), getBitmapHeight(_tempDecodeBuffer), getBitmapData(_tempDecodeBuffer), getBitmapWidth(_tempDecodeBuffer)); 371 | frameX += sprX / 2; 372 | frameY += sprY / 2; 373 | _stub->copyRect(frameX, frameY, getBitmapWidth(spriteBitmap), getBitmapHeight(spriteBitmap), getBitmapData(spriteBitmap), getBitmapWidth(spriteBitmap)); 374 | ++_dialogueSpriteCurrentFrameTable[num]; 375 | if (_dialogueSpriteCurrentFrameTable[num] >= _dialogueSpriteFrameCountTable[num]) { 376 | _dialogueSpriteCurrentFrameTable[num] = 0; 377 | } 378 | } 379 | 380 | void Game::redrawDialogueBackground() { 381 | debug(DBG_DIALOGUE, "Game::redrawDialogueBackground()"); 382 | sortObjects(); 383 | int previousObject = -1; 384 | for (int i = 0; i < _sceneObjectsCount; ++i) { 385 | SceneObject *so = _sortedSceneObjectsTable[i]; 386 | if (so->statePrev == 1 || so->statePrev == 2) { 387 | if (previousObject >= 0) { 388 | redrawObjectBoxes(previousObject, i); 389 | } 390 | previousObject = i; 391 | decodeLzss(_sceneObjectFramesTable[so->frameNumPrev].data, _tempDecodeBuffer); 392 | SceneObjectFrame *sof = &_sceneObjectFramesTable[so->frameNumPrev]; 393 | if (so->flipPrev == 2) { 394 | int y = _bitmapBuffer1.h + 1 - so->yPrev - sof->hdr.h; 395 | drawObjectVerticalFlip(so->xPrev, y, _tempDecodeBuffer, &_bitmapBuffer1); 396 | } else { 397 | int y = _bitmapBuffer1.h + 1 - so->yPrev - sof->hdr.h; 398 | drawObject(so->xPrev, y, _tempDecodeBuffer, &_bitmapBuffer1); 399 | } 400 | } 401 | } 402 | if (previousObject >= 0) { 403 | redrawObjectBoxes(previousObject, previousObject); 404 | } 405 | 406 | const uint8_t *src = _bitmapBuffer1.bits + _dialogueBackgroundRect.y * _bitmapBuffer1.pitch + _dialogueBackgroundRect.x; 407 | _stub->copyRect(_dialogueBackgroundRect.x, _dialogueBackgroundRect.y, _dialogueBackgroundRect.w, _dialogueBackgroundRect.h, src, _bitmapBuffer1.pitch); 408 | _stub->darkenRect(_dialogueBackgroundRect.x, _dialogueBackgroundRect.y, _dialogueBackgroundRect.w, _dialogueBackgroundRect.h); 409 | 410 | for (int i = 0; i < _sceneObjectsCount; ++i) { 411 | SceneObject *so = _sortedSceneObjectsTable[i]; 412 | if (so->statePrev == 1) { 413 | SceneObjectFrame *sof = &_sceneObjectFramesTable[so->frameNumPrev]; 414 | int y = _bitmapBuffer1.h + 1 - so->yPrev - sof->hdr.h; 415 | copyBufferToBuffer(so->xPrev, y, sof->hdr.w, sof->hdr.h, &_bitmapBuffer3, &_bitmapBuffer1); 416 | } 417 | } 418 | } 419 | -------------------------------------------------------------------------------- /resource.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Bermuda Syndrome engine rewrite 3 | * Copyright (C) 2007-2011 Gregory Montoir 4 | */ 5 | 6 | #include "decoder.h" 7 | #include "file.h" 8 | #include "fs.h" 9 | #include "game.h" 10 | #include "str.h" 11 | 12 | static const bool kDumpObjectScript = false; 13 | 14 | void Game::allocateTables() { 15 | _tempDecodeBuffer = (uint8_t *)malloc(65535); 16 | if (!_tempDecodeBuffer) { 17 | error("Unable to allocate temporary decode buffer (65535 bytes)"); 18 | } 19 | _bitmapBuffer0 = (uint8_t *)malloc(kBitmapBufferDefaultSize); 20 | if (!_bitmapBuffer0) { 21 | error("Unable to allocate bitmap buffer 0 (%d bytes)", kBitmapBufferDefaultSize); 22 | } 23 | _bitmapBuffer2 = (uint8_t *)malloc(kBitmapBufferDefaultSize); 24 | if (!_bitmapBuffer2) { 25 | error("Unable to allocate bitmap buffer 2 (%d bytes)", kBitmapBufferDefaultSize); 26 | } 27 | } 28 | 29 | void Game::deallocateTables() { 30 | if (_bitmapBuffer0) { 31 | free(_bitmapBuffer0); 32 | _bitmapBuffer0 = 0; 33 | } 34 | if (_bitmapBuffer2) { 35 | free(_bitmapBuffer2); 36 | _bitmapBuffer2 = 0; 37 | } 38 | if (_tempDecodeBuffer) { 39 | free(_tempDecodeBuffer); 40 | _tempDecodeBuffer = 0; 41 | } 42 | } 43 | 44 | void Game::loadCommonSprites() { 45 | if (_isDemo) { 46 | _bermudaOvrData = 0; 47 | } else { 48 | _bermudaOvrData = loadFile("..\\bermuda.ovr"); 49 | } 50 | 51 | loadWGP("..\\bermuda.wgp"); 52 | _bagBackgroundImage = _bitmapBuffer1; 53 | int bagBitmapSize = _bagBackgroundImage.pitch * (_bagBackgroundImage.h + 1); 54 | _bagBackgroundImage.bits = (uint8_t *)malloc(bagBitmapSize); 55 | if (!_bagBackgroundImage.bits) { 56 | error("Unable to allocate bag bitmap buffer (%d bytes)", bagBitmapSize); 57 | } 58 | memcpy(_bagBackgroundImage.bits, _bitmapBuffer1.bits, bagBitmapSize); 59 | 60 | loadFile("..\\bermuda.spr", _tempDecodeBuffer); 61 | int decodedSize = READ_LE_UINT32(_tempDecodeBuffer + 2); 62 | _bermudaSprData = (uint8_t *)malloc(decodedSize); 63 | if (!_bermudaSprData) { 64 | error("Unable to allocate bermuda.spr buffer (%d bytes)", decodedSize); 65 | } 66 | decodeLzss(_tempDecodeBuffer + 2, _bermudaSprData); 67 | _bermudaSprDataTable[0] = _bermudaSprData; 68 | for (int i = 1; i < 3; ++i) { 69 | _bermudaSprDataTable[i] = _bermudaSprDataTable[i - 1] + getBitmapSize(_bermudaSprDataTable[i - 1]); 70 | } 71 | for (int i = 0; i < 11; ++i) { 72 | if (i == 0) { 73 | _lifeBarImageTable[i][0] = _bermudaSprDataTable[2] + getBitmapSize(_bermudaSprDataTable[2]); 74 | } else { 75 | _lifeBarImageTable[i][0] = _lifeBarImageTable[i - 1][11] + getBitmapSize(_lifeBarImageTable[i - 1][11]); 76 | } 77 | for (int j = 1; j < 12; ++j) { 78 | _lifeBarImageTable[i][j] = _lifeBarImageTable[i][j - 1] + getBitmapSize(_lifeBarImageTable[i][j - 1]); 79 | } 80 | } 81 | _bagObjectAreaBlinkImageTable[0] = _lifeBarImageTable[10][11] + getBitmapSize(_lifeBarImageTable[10][11]); 82 | for (int i = 1; i < 10; ++i) { 83 | _bagObjectAreaBlinkImageTable[i] = _bagObjectAreaBlinkImageTable[i - 1] + getBitmapSize(_bagObjectAreaBlinkImageTable[i - 1]); 84 | } 85 | _weaponIconImageTable[0] = _bagObjectAreaBlinkImageTable[9] + getBitmapSize(_bagObjectAreaBlinkImageTable[9]); 86 | for (int i = 1; i < 14; ++i) { 87 | _weaponIconImageTable[i] = _weaponIconImageTable[i - 1] + getBitmapSize(_weaponIconImageTable[i - 1]); 88 | } 89 | for (int i = 0; i < 2; ++i) { 90 | if (i == 0) { 91 | _ammoIconImageTable[i][0] = _weaponIconImageTable[13] + getBitmapSize(_weaponIconImageTable[13]); 92 | } else { 93 | _ammoIconImageTable[i][0] = _ammoIconImageTable[i - 1][4] + getBitmapSize(_ammoIconImageTable[i - 1][4]); 94 | } 95 | for (int j = 1; j < 5; ++j) { 96 | _ammoIconImageTable[i][j] = _ammoIconImageTable[i][j - 1] + getBitmapSize(_ammoIconImageTable[i][j - 1]); 97 | } 98 | } 99 | if (_isDemo) { 100 | _swordIconImage = 0; 101 | _iconBackgroundImage = _ammoIconImageTable[1][4] + getBitmapSize(_ammoIconImageTable[1][4]); 102 | _lifeBarImage = _iconBackgroundImage + getBitmapSize(_iconBackgroundImage); 103 | memset(_bagWeaponAreaBlinkImageTable, 0, sizeof(_bagWeaponAreaBlinkImageTable)); 104 | } else { 105 | _swordIconImage = _ammoIconImageTable[1][4] + getBitmapSize(_ammoIconImageTable[1][4]); 106 | _iconBackgroundImage = _swordIconImage + getBitmapSize(_swordIconImage); 107 | _lifeBarImage = _iconBackgroundImage + getBitmapSize(_iconBackgroundImage); 108 | _bagWeaponAreaBlinkImageTable[0] = _lifeBarImage + getBitmapSize(_lifeBarImage); 109 | for (int i = 1; i < 10; ++i) { 110 | _bagWeaponAreaBlinkImageTable[i] = _bagWeaponAreaBlinkImageTable[i - 1] + getBitmapSize(_bagWeaponAreaBlinkImageTable[i - 1]); 111 | } 112 | } 113 | } 114 | 115 | void Game::unloadCommonSprites() { 116 | if (_bermudaOvrData) { 117 | free(_bermudaOvrData); 118 | _bermudaOvrData = 0; 119 | } 120 | if (_bagBackgroundImage.bits) { 121 | free(_bagBackgroundImage.bits); 122 | _bagBackgroundImage.bits = 0; 123 | } 124 | if (_bermudaSprData) { 125 | free(_bermudaSprData); 126 | _bermudaSprData = 0; 127 | } 128 | } 129 | 130 | uint8_t *Game::loadFile(const char *fileName, uint8_t *dst, uint32_t *dstSize) { 131 | debug(DBG_RES, "Game::loadFile('%s')", fileName); 132 | FileHolder fp(_fs, fileName); 133 | uint32_t fileSize = fp->size(); 134 | if (!dst) { 135 | dst = (uint8_t *)malloc(fileSize); 136 | if (!dst) { 137 | error("Unable to allocate buffer for file loading (%d bytes)", fileSize); 138 | } 139 | } 140 | if (dstSize) { 141 | *dstSize = fileSize; 142 | } 143 | fp->read(dst, fileSize); 144 | return dst; 145 | } 146 | 147 | void Game::loadWGP(const char *fileName) { 148 | debug(DBG_RES, "Game::loadWGP('%s')", fileName); 149 | FileHolder fp(_fs, fileName); 150 | int offs = kOffsetBitmapBits; 151 | int len = 0; 152 | int tag = fp->readUint16LE(); 153 | if (tag == 0x4D42) { // _10.SCN (uncompressed .bmp) 154 | len = fp->readUint32LE() - 14; 155 | fp->seek(8, SEEK_CUR); 156 | fp->read(_bitmapBuffer0, len); 157 | } else if (tag == 0x5057) { 158 | len = 0; 159 | int dataSize = fp->size(); 160 | do { 161 | const int sz = fp->readUint16LE(); 162 | if (fp->ioErr()) { 163 | break; 164 | } else if (sz != 0) { 165 | fp->read(_bitmapBuffer2, sz); 166 | const int decodedSize = decodeLzss(_bitmapBuffer2, _bitmapBuffer0 + len); 167 | len += decodedSize; 168 | dataSize -= sz; 169 | } 170 | dataSize -= 2; 171 | } while (dataSize > 0); 172 | offs += 4; 173 | len += 4; 174 | } else if (tag == 0x505A) { 175 | const int sz = fp->size() - 2; 176 | fp->read(_bitmapBuffer2, sz); 177 | len = decodeZlib(_bitmapBuffer2, _bitmapBuffer0); 178 | } else { 179 | error("Invalid wgp format %X", tag); 180 | } 181 | _loadDataState = 1; 182 | 183 | _bitmapBuffer1.w = READ_LE_UINT32(_bitmapBuffer0 + 4) - 1; 184 | _bitmapBuffer1.h = READ_LE_UINT32(_bitmapBuffer0 + 8) - 1; 185 | _bitmapBuffer1.pitch = (READ_LE_UINT32(_bitmapBuffer0 + 4) + 3) & ~3; 186 | _bitmapBuffer1.bits = _bitmapBuffer2; 187 | _bitmapBuffer3 = _bitmapBuffer1; 188 | _bitmapBuffer3.bits = _bitmapBuffer0 + offs; 189 | memcpy(_bitmapBuffer1.bits, _bitmapBuffer3.bits, len - offs); 190 | } 191 | 192 | void Game::loadSPR(const char *fileName, SceneAnimation *sa) { 193 | debug(DBG_RES, "Game::loadSPR('%s')", fileName); 194 | FileHolder fp(_fs, fileName); 195 | int (*decode)(const uint8_t *, uint8_t *) = 0; 196 | const int tag = fp->readUint16LE(); 197 | if (tag == 0x3553) { 198 | decode = decodeLzss; 199 | } else if (tag == 0x355A) { 200 | decode = decodeZlib; 201 | } else { 202 | error("Invalid spr format %X", tag); 203 | } 204 | sa->motionsCount = 0; 205 | sa->firstMotionIndex = _sceneObjectMotionsCount; 206 | while (1) { 207 | int num = fp->readUint16LE(); 208 | if (num == 0) { 209 | break; 210 | } 211 | assert(_sceneObjectMotionsCount < NUM_SCENE_MOTIONS); 212 | SceneObjectMotion *motion = &_sceneObjectMotionsTable[_sceneObjectMotionsCount]; 213 | motion->firstFrameIndex = _sceneObjectFramesCount; 214 | motion->count = num; 215 | motion->animNum = _animationsCount; 216 | assert(_sceneObjectFramesCount + num <= NUM_SCENE_OBJECT_FRAMES); 217 | for (int i = 0; i < num; ++i) { 218 | int len = fp->readUint16LE(); 219 | SceneObjectFrame *frame = &_sceneObjectFramesTable[_sceneObjectFramesCount]; 220 | frame->data = (uint8_t *)malloc(len); 221 | if (!frame->data) { 222 | error("Unable to allocate %d bytes", len); 223 | } 224 | fp->read(frame->data, len); 225 | frame->hdr.num = fp->readUint16LE(); 226 | frame->hdr.w = fp->readUint16LE(); 227 | frame->hdr.h = fp->readUint16LE(); 228 | frame->hdr.xPos = fp->readUint16LE(); 229 | frame->hdr.yPos = fp->readUint16LE(); 230 | frame->decode = decode; 231 | ++_sceneObjectFramesCount; 232 | } 233 | ++sa->motionsCount; 234 | ++_sceneObjectMotionsCount; 235 | } 236 | } 237 | 238 | static void dumpObjectScript(SceneAnimation *sa, const char *dirPath, const char *fileName) { 239 | const char *name = strrchr(fileName, '\\'); 240 | if (name) { 241 | char filePath[512]; 242 | snprintf(filePath, sizeof(filePath), "%s/dumps/%s.script", dirPath, name + 1); 243 | File f; 244 | if (f.open(filePath, "wb")) { 245 | f.write(sa->scriptData, sa->scriptSize); 246 | f.close(); 247 | } 248 | } 249 | } 250 | 251 | void Game::loadMOV(const char *fileName) { 252 | debug(DBG_RES, "Game::loadMOV('%s')", fileName); 253 | FileHolder fp(_fs, fileName); 254 | int tag = fp->readUint16LE(); 255 | if (tag != 0x354D) { 256 | error("Invalid mov format %X", tag); 257 | } 258 | assert(_animationsCount < NUM_SCENE_ANIMATIONS); 259 | SceneAnimation *sa = &_animationsTable[_animationsCount]; 260 | 261 | char sprName[128]; 262 | int len = fp->readUint16LE(); 263 | fp->read(sprName, len); 264 | 265 | char wgpName[128]; 266 | len = fp->readUint16LE(); 267 | fp->read(wgpName, len); 268 | 269 | int speed = fp->readUint16LE(); // anim speed in ms 270 | debug(DBG_RES, "speed %d ms", speed); 271 | 272 | if (_loadDataState == 0) { 273 | loadWGP(wgpName); 274 | strcpy(_currentSceneWgp, wgpName); 275 | _sceneObjectFramesCount = 0; 276 | _sceneObjectMotionsCount = 0; 277 | _sceneObjectsCount = 0; 278 | for (int i = 0; i < NUM_SCENE_OBJECTS; ++i) { 279 | memset(_sceneObjectsTable[i].varsTable, 0, sizeof(_sceneObjectsTable[i].varsTable)); 280 | } 281 | } 282 | sa->unk26 = 0; 283 | sa->objectsCount = 0; 284 | sa->firstObjectIndex = _sceneObjectsCount; 285 | sa->soundBuffersCount = 0; 286 | sa->firstSoundBufferIndex = _soundBuffersCount; 287 | while (1) { 288 | int type = fp->readUint16LE(); 289 | switch (type) { 290 | case 1: 291 | while (1) { 292 | len = fp->readUint16LE(); 293 | if (len == 0) { 294 | break; 295 | } 296 | assert(_soundBuffersCount < NUM_SOUND_BUFFERS); 297 | fp->read(_soundBuffersTable[_soundBuffersCount].filename, len); 298 | ++_soundBuffersCount; 299 | ++sa->soundBuffersCount; 300 | } 301 | break; 302 | case 2: 303 | while (1) { 304 | int index = fp->readUint16LE(); 305 | if (index == 0) { 306 | break; 307 | } 308 | --index; 309 | Box *box = derefBox(index, _boxesCountTable[index]); 310 | box->state = fp->readByte(); 311 | box->x1 = fp->readUint16LE(); 312 | box->y1 = fp->readUint16LE(); 313 | box->x2 = fp->readUint16LE(); 314 | box->y2 = fp->readUint16LE(); 315 | ++_boxesCountTable[index]; 316 | } 317 | break; 318 | case 3: { 319 | assert(_sceneObjectsCount < NUM_SCENE_OBJECTS); 320 | SceneObject *so = &_sceneObjectsTable[_sceneObjectsCount]; 321 | len = fp->readUint16LE(); 322 | fp->read(so->name, len); 323 | for (int i = 0; i < _sceneObjectsCount; ++i) { 324 | if (strcmp(_sceneObjectsTable[i].name, so->name) == 0) { 325 | error("Duplicate object name %s", so->name); 326 | } 327 | } 328 | so->motionFrameNum = 0; 329 | so->motionNum = 0, 330 | so->flipInit = 0; 331 | so->zInit = 0; 332 | so->yInit = 0; 333 | so->xInit = 0; 334 | so->mode = 0; 335 | so->statePrev = 0; 336 | so->state = 0; 337 | so->motionInit = _sceneObjectMotionsCount; 338 | while (1) { 339 | int initType = fp->readUint16LE(); 340 | if (initType == 0) { 341 | break; 342 | } 343 | switch (initType) { 344 | case 2000: 345 | len = fp->readUint16LE(); 346 | fp->read(so->className, len); 347 | break; 348 | case 3000: 349 | so->mode = fp->readUint16LE(); 350 | if (so->mode == 2) { 351 | so->modeRndMul = fp->readUint16LE(); 352 | } 353 | break; 354 | case 3500: 355 | so->xInit = fp->readUint16LE(); 356 | so->yInit = fp->readUint16LE(); 357 | break; 358 | case 4000: 359 | so->zInit = fp->readUint16LE(); 360 | break; 361 | case 4500: 362 | so->flipInit = fp->readUint16LE(); 363 | break; 364 | case 5000: 365 | so->motionFrameNum = fp->readUint16LE(); 366 | --so->motionFrameNum; 367 | break; 368 | case 5500: 369 | so->motionNum = fp->readUint16LE(); 370 | --so->motionNum; 371 | break; 372 | case 6000: { 373 | int var = fp->readUint16LE(); 374 | assert(var >= 0 && var < 10); 375 | so->varsTable[var] = fp->readUint16LE(); 376 | } 377 | break; 378 | default: 379 | assert(0); 380 | break; 381 | } 382 | } 383 | ++_sceneObjectsCount; 384 | ++sa->objectsCount; 385 | } 386 | break; 387 | case 4: 388 | sa->scriptSize = fp->readUint16LE(); 389 | if (sa->scriptSize != 0) { 390 | sa->scriptData = (uint8_t *)malloc(sa->scriptSize); 391 | fp->read(sa->scriptData, sa->scriptSize); 392 | if (kDumpObjectScript) { 393 | dumpObjectScript(sa, _savePath, fileName); 394 | } 395 | } 396 | break; 397 | case 5: 398 | sa->unk26 = fp->readUint16LE(); 399 | break; 400 | default: 401 | assert(0); 402 | } 403 | if (type == 4) { 404 | break; 405 | } 406 | } 407 | 408 | char filePath[128]; 409 | strcpy(filePath, fileName); 410 | char *p = strrchr(filePath, '\\'); 411 | if (p) { 412 | strcpy(p + 1, sprName); 413 | } else { 414 | strcpy(filePath, sprName); 415 | } 416 | 417 | loadSPR(filePath, sa); 418 | 419 | strcpy(sa->name, fileName); 420 | stringToLowerCase(sa->name); 421 | 422 | ++_animationsCount; 423 | 424 | // for (int i = sa->firstSoundBufferIndex; i < _soundBuffersCount; ++i) { 425 | // SoundBuffer *sb = &_soundBuffersTable[i]; 426 | // sb->buffer = loadFile(sb->filename); 427 | // } 428 | 429 | _loadDataState = 2; 430 | // _skipUpdateScreen = false; 431 | } 432 | 433 | void Game::loadKBR(const char *fileName) { 434 | _keyboardReplaySize = 0; 435 | _keyboardReplayOffset = 0; 436 | free(_keyboardReplayData); 437 | _keyboardReplayData = 0; 438 | 439 | char name[128]; 440 | strcpy(name, fileName); 441 | char *p = strstr(name, ".SAV"); 442 | if (p) { 443 | strcpy(p, ".KBR"); 444 | FileHolder fh(_fs, name, false); 445 | if (fh._fp) { 446 | _keyboardReplaySize = fh->size(); 447 | if (_keyboardReplaySize & 127) { 448 | warning("Unexpected keyboard buffer size %d", _keyboardReplaySize); 449 | } 450 | _keyboardReplayData = (uint8_t *)malloc(_keyboardReplaySize); 451 | if (_keyboardReplayData) { 452 | fh->read(_keyboardReplayData, _keyboardReplaySize); 453 | debug(DBG_RES, "Loading keyboard replay buffer size %d", _keyboardReplaySize); 454 | } 455 | } else { 456 | warning("Unable to open '%s' for reading", name); 457 | } 458 | } 459 | } 460 | 461 | void Game::loadTBM() { 462 | static const char *name = "BT16.TBM"; 463 | File f; 464 | if (f.open(name)) { 465 | const uint32_t dataSize = f.size(); 466 | uint8_t *p = (uint8_t *)malloc(dataSize); 467 | if (!p) { 468 | warning("Unable to allocate %d bytes for '%s'", dataSize, name); 469 | return; 470 | } 471 | f.read(p, dataSize); 472 | if (memcmp(p, "TTBITMAP VARIABLE-PITCH HANGEUL", 31) != 0) { 473 | warning("Unexpected signature for '%s'", name); 474 | free(p); 475 | return; 476 | } 477 | const int glyphW = READ_LE_UINT16(p + 32); 478 | const int glyphH = READ_LE_UINT16(p + 34); 479 | if (glyphW != 16 || glyphH != 16) { 480 | warning("Unexpected glyph size %d,%d", glyphW, glyphH); 481 | free(p); 482 | return; 483 | } 484 | const int count = READ_LE_UINT16(p + 36); 485 | _hangulFontLutOffset = 38 + count * 16; 486 | _hangulFontData = p; 487 | } else { 488 | warning("Unable to open Hangul font '%s'", name); 489 | _hangulFontData = 0; 490 | } 491 | } 492 | -------------------------------------------------------------------------------- /game.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Bermuda Syndrome engine rewrite 3 | * Copyright (C) 2007-2011 Gregory Montoir 4 | */ 5 | 6 | #ifndef GAME_H__ 7 | #define GAME_H__ 8 | 9 | #include "intern.h" 10 | #include "random.h" 11 | #include "fs.h" 12 | 13 | struct SceneBitmap { 14 | uint16_t w; // x2 15 | uint16_t h; // y2 16 | uint16_t pitch; 17 | uint8_t *bits; 18 | }; 19 | 20 | struct SceneAnimation { 21 | char name[20]; 22 | int16_t firstMotionIndex; 23 | int16_t motionsCount; 24 | int16_t firstObjectIndex; 25 | int16_t objectsCount; 26 | int16_t firstSoundBufferIndex; 27 | int16_t soundBuffersCount; 28 | uint8_t *scriptData; 29 | uint16_t scriptSize; 30 | int16_t unk26; 31 | }; 32 | 33 | struct SceneObject { 34 | int16_t xInit; 35 | int16_t yInit; 36 | int16_t x; 37 | int16_t y; 38 | int16_t xPrev; 39 | int16_t yPrev; 40 | int16_t zInit; 41 | int16_t zPrev; 42 | int16_t z; 43 | int16_t frameNum; 44 | int16_t frameNumPrev; 45 | int16_t flipInit; 46 | int16_t flip; 47 | int16_t flipPrev; 48 | int16_t motionNum; // motionStart 49 | int16_t motionInit; 50 | int16_t motionNum1; // motionNumPrev 51 | int16_t motionNum2; // motionNum 52 | int16_t motionFrameNum; // motionIdle ? 53 | int16_t mode; 54 | int16_t modeRndMul; 55 | int16_t statePrev; 56 | int16_t state; 57 | char name[20]; 58 | char className[20]; 59 | int16_t varsTable[10]; 60 | }; 61 | 62 | struct SoundBuffer { 63 | char filename[40]; 64 | // uint8_t *buffer; // original preload the sound 65 | }; 66 | 67 | struct SceneObjectMotion { 68 | int16_t firstFrameIndex; 69 | int16_t count; 70 | int16_t animNum; 71 | }; 72 | 73 | struct Box { 74 | int16_t x1; 75 | int16_t x2; 76 | int16_t y1; 77 | int16_t y2; 78 | uint8_t state; 79 | int16_t z; 80 | int16_t startColor; 81 | int16_t endColor; 82 | }; 83 | 84 | struct SceneObjectFrameHeader { 85 | int16_t num; // 0 86 | int16_t w; // 2 87 | int16_t h; // 4 88 | int16_t xPos; // 6 89 | int16_t yPos; // 8 90 | }; 91 | 92 | struct SceneObjectFrame { 93 | uint8_t *data; 94 | SceneObjectFrameHeader hdr; 95 | int (*decode)(const uint8_t *, uint8_t *); 96 | }; 97 | 98 | struct BagObject { 99 | char name[20]; 100 | uint8_t *data; 101 | uint32_t dataSize; // not in the original 102 | }; 103 | 104 | struct NextScene { 105 | int16_t num; 106 | char name[20]; 107 | }; 108 | 109 | struct SceneObjectStatus { 110 | int16_t x; 111 | int16_t y; 112 | int16_t z; 113 | int16_t motionNum; 114 | int16_t frameNum; 115 | int16_t flip; 116 | }; 117 | 118 | struct Script { 119 | uint8_t *data; 120 | int dataOffset; 121 | int testDataOffset; 122 | int currentObjectNum; 123 | bool objectFound; 124 | int testObjectNum; 125 | int nextScene; 126 | int statementNum; 127 | 128 | int16_t fetchNextWord() { 129 | int16_t word = READ_LE_UINT16(data + dataOffset); 130 | dataOffset += 2; 131 | return word; 132 | } 133 | 134 | const char *fetchNextString() { 135 | int len = fetchNextWord(); 136 | assert(len >= 1); 137 | const char *str = (const char *)(data + dataOffset); 138 | dataOffset += len; 139 | assert(len >= 1 && str[len - 1] == '\0'); 140 | return str; 141 | } 142 | 143 | const char *getString() { 144 | return (const char *)(data + dataOffset); 145 | } 146 | }; 147 | 148 | struct DialogueChoice { 149 | char *id; 150 | bool gotoFlag; 151 | char *nextId; 152 | char *speechSoundFile; 153 | char *text; 154 | }; 155 | 156 | struct Game; 157 | 158 | enum { 159 | kActionTake = 0, 160 | kActionTalk = 1, 161 | kActionGirl = 2, 162 | kActionUseObject = 3, 163 | }; 164 | 165 | enum { 166 | kObjectFlipY = 1, 167 | kObjectFlipX = 2 168 | }; 169 | 170 | enum { 171 | kVarHasSword = 1, 172 | kVarHasGun = 2, 173 | kVarAmmo = 3 174 | }; 175 | 176 | enum { 177 | kLeftMouseButton = 1 << 0, 178 | kRightMouseButton = 1 << 1 179 | }; 180 | 181 | enum { 182 | kStateInit, 183 | kStateGame, 184 | kStateBag, 185 | kStateDialogue, 186 | kStateBitmap, 187 | kStateMenu1, 188 | kStateMenu2, 189 | }; 190 | 191 | enum { 192 | // menu1 193 | kMenuOptionNewGame = 0, 194 | kMenuOptionLoadGame, 195 | kMenuOptionSaveGame, 196 | kMenuOptionQuitGame, 197 | // menu2 198 | kMenuOptionExitGame = 0, 199 | kMenuOptionReturnGame, 200 | }; 201 | 202 | enum { 203 | kCycleDelay = 50, 204 | kGameScreenWidth = 640, 205 | kGameScreenHeight = 480, 206 | kDemoSavSlot = -1, 207 | kOffsetBitmapInfo = 0, 208 | kOffsetBitmapPalette = kOffsetBitmapInfo + 40, 209 | kOffsetBitmapBits = kOffsetBitmapPalette + 256 * 4, 210 | kBitmapBufferDefaultSize = 40 + 256 * 4 + 640 * 480 + 256 * 4 211 | }; 212 | 213 | enum { 214 | kCheatNoHit = 1 << 0 215 | }; 216 | 217 | static inline int getBitmapWidth(const uint8_t *p) { 218 | return READ_LE_UINT16(p) + 1; 219 | } 220 | 221 | static inline int getBitmapHeight(const uint8_t *p) { 222 | return READ_LE_UINT16(p + 2) + 1; 223 | } 224 | 225 | static inline int getBitmapSize(const uint8_t *p) { 226 | int sz = 4 + getBitmapWidth(p) * getBitmapHeight(p); 227 | return sz; 228 | } 229 | 230 | static inline const uint8_t *getBitmapData(const uint8_t *p) { 231 | return p + 4; 232 | } 233 | 234 | static inline bool boxInRect(Box *box, int rect_x1, int rect_x2, int rect_y1, int rect_y2) { 235 | assert(box->x1 <= box->x2 && box->y1 <= box->y2); 236 | const int xmin = MIN(rect_x1, rect_x2); 237 | const int xmax = MAX(rect_x1, rect_x2); 238 | const int ymin = MIN(rect_y1, rect_y2); 239 | const int ymax = MAX(rect_y1, rect_y2); 240 | return box->x1 <= xmax && box->x2 >= xmin && box->y1 <= ymax && box->y2 >= ymin; 241 | } 242 | 243 | struct Mixer; 244 | struct SystemStub; 245 | 246 | struct Game { 247 | enum { 248 | NUM_VARS = 310, 249 | NUM_BOXES = 20, 250 | NUM_SCENE_OBJECT_FRAMES = 3000, 251 | NUM_SCENE_MOTIONS = 300, 252 | NUM_SOUND_BUFFERS = 80, 253 | NUM_SCENE_OBJECTS = 50, 254 | NUM_SCENE_ANIMATIONS = 50, 255 | NUM_BAG_OBJECTS = 20, 256 | NUM_NEXT_SCENES = 20, 257 | NUM_SCENE_OBJECT_STATUS = 200, 258 | NUM_DIALOG_CHOICES = 10, 259 | NUM_DIALOG_ENTRIES = 40 260 | }; 261 | 262 | Game(SystemStub *stub, const char *dataPath, const char *savePath, const char *musicPath); 263 | ~Game(); 264 | 265 | SceneObject *derefSceneObject(int i) { 266 | assert(i >= 0 && i < _sceneObjectsCount); 267 | return &_sceneObjectsTable[i]; 268 | } 269 | 270 | Box *derefBox(int i, int j) { 271 | assert(i >= 0 && i < NUM_BOXES && j >= 0 && j < 10); 272 | return &_boxesTable[i][j]; 273 | } 274 | 275 | SceneObjectStatus *derefSceneObjectStatus(int i) { 276 | assert(i >= 0 && i < NUM_SCENE_OBJECT_STATUS); 277 | return &_sceneObjectStatusTable[i]; 278 | } 279 | 280 | SceneObjectFrame *derefSceneObjectFrame(int i) { 281 | assert(i >= 0 && i < _sceneObjectFramesCount); 282 | return &_sceneObjectFramesTable[i]; 283 | } 284 | 285 | // bag.cpp 286 | void handleBagMenu(); 287 | void drawBagMenu(int xPosWnd, int yPosWnd); 288 | 289 | // dialogue.cpp 290 | void unloadDialogueData(); 291 | void setupDialog(const char *code); 292 | void loadDialogueSprite(int spr); 293 | void loadDialogueData(const char *filename); 294 | void redrawDialogueSprite(int num); 295 | void redrawDialogueBackground(); 296 | void redrawDialogueTexts(); 297 | void initDialogue(); 298 | void finiDialogue(); 299 | void handleDialogue(); 300 | 301 | // game.cpp 302 | void detectTextCp949(); 303 | void detectVersion(); 304 | void restart(); 305 | void init(bool fullscreen, int screenMode); 306 | void fini(); 307 | void mainLoop(); 308 | void updateMouseButtonsPressed(); 309 | void updateKeysPressedTable(); 310 | void clearSceneData(int anim); 311 | void reinitializeObject(int object); 312 | void updateObjects(); 313 | void runObjectsScript(); 314 | int findBagObjectByName(const char *objectName) const; 315 | int getObjectTranslateXPos(int object, int dx1, int div, int dx2); 316 | int getObjectTranslateYPos(int object, int dy1, int div, int dy2); 317 | int findObjectByName(int currentObjectNum, int defaultObjectNum, bool *objectFlag); 318 | void sortObjects(); 319 | void copyBufferToBuffer(int x, int y, int w, int h, SceneBitmap *src, SceneBitmap *dst); 320 | void drawBox(int x, int y, int w, int h, SceneBitmap *src, SceneBitmap *dst, int startColor, int endColor); 321 | void drawObject(int x, int y, const uint8_t *src, SceneBitmap *dst); 322 | void drawObjectVerticalFlip(int x, int y, const uint8_t *src, SceneBitmap *dst); 323 | void redrawObjectBoxes(int previousObject, int currentObject); 324 | void redrawObjects(); 325 | void playVideo(const char *name); 326 | void displayTitleBitmap(); 327 | void stopMusic(); 328 | void playMusic(const char *name); 329 | void changeObjectMotionFrame(int object, int object2, int useObject2, int count1, int count2, int useDx, int dx, int useDy, int dy); 330 | int16_t getObjectTransformXPos(int object); 331 | int16_t getObjectTransformYPos(int object); 332 | bool comparePrevObjectTransformXPos(int object, bool fetchCmp = true, int cmpX = -1); 333 | bool compareObjectTransformXPos(int object, bool fetchCmp = true, int cmpX = -1); 334 | bool comparePrevObjectTransformYPos(int object, bool fetchCmp = true, int cmpY = -1); 335 | bool compareObjectTransformYPos(int object, bool fetchCmp = true, int cmpY = -1); 336 | void setupObjectPos(int object, int object2, int useObject2, int useData, int type1, int type2); 337 | bool intersectsBox(int num, int index, int x1, int y1, int x2, int y2); 338 | 339 | // menu.cpp 340 | void initMenu(int num); 341 | void finiMenu(); 342 | void handleMenu(); 343 | 344 | // opcodes.cpp 345 | bool executeConditionOpcode(int num); 346 | void executeOperatorOpcode(int num); 347 | void evalExpr(int16_t *val); 348 | bool testExpr(int16_t val); 349 | bool cop_true(); 350 | bool cop_isInRandomRange(); 351 | bool cop_isKeyPressed(); 352 | bool cop_isKeyNotPressed(); 353 | bool cop_testMouseButtons(); 354 | bool cop_isObjectInScene(); 355 | bool cop_testObjectPrevState(); 356 | bool cop_testObjectState(); 357 | bool cop_isObjectInRect(); 358 | bool cop_testPrevObjectTransformXPos(); 359 | bool cop_testObjectTransformXPos(); 360 | bool cop_testPrevObjectTransformYPos(); 361 | bool cop_testObjectTransformYPos(); 362 | bool cop_testObjectPrevFlip(); 363 | bool cop_testObjectFlip(); 364 | bool cop_testObjectPrevFrameNum(); 365 | bool cop_testObjectFrameNum(); 366 | bool cop_testPrevMotionNum(); 367 | bool cop_testMotionNum(); 368 | bool cop_testObjectVar(); 369 | bool cop_testObjectAndObjectXPos(); 370 | bool cop_testObjectAndObjectYPos(); 371 | bool cop_testObjectMotionYPos(); 372 | bool cop_testVar(); 373 | bool cop_isCurrentBagAction(); 374 | bool cop_isObjectInBox(); 375 | bool cop_isObjectNotInBox(); 376 | bool cop_isObjectNotIntersectingBox(); 377 | bool cop_isCurrentBagObject(); 378 | bool cop_isLifeBarDisplayed(); 379 | bool cop_isLifeBarNotDisplayed(); 380 | bool cop_testLastDialogue(); 381 | bool cop_isNextScene(); 382 | void oop_initializeObject(); 383 | void oop_evalCurrentObjectX(); 384 | void oop_evalCurrentObjectY(); 385 | void oop_evalObjectX(); 386 | void oop_evalObjectY(); 387 | void oop_evalObjectZ(); 388 | void oop_setObjectFlip(); 389 | void oop_adjustObjectPos_vv0000(); 390 | void oop_adjustObjectPos_vv1v00(); 391 | void oop_adjustObjectPos_vv1v1v(); 392 | void oop_setupObjectPos_121(); 393 | void oop_setupObjectPos_122(); 394 | void oop_setupObjectPos_123(); 395 | void oop_adjustObjectPos_1v0000(); 396 | void oop_adjustObjectPos_1v1v1v(); 397 | void oop_setupObjectPos_021(); 398 | void oop_setupObjectPos_022(); 399 | void oop_setupObjectPos_023(); 400 | void oop_evalObjectVar(); 401 | void oop_translateObjectXPos(); 402 | void oop_translateObjectYPos(); 403 | void oop_setObjectMode(); 404 | void oop_setObjectInitPos(); 405 | void oop_setObjectTransformInitPos(); 406 | void oop_evalObjectXInit(); 407 | void oop_evalObjectYInit(); 408 | void oop_evalObjectZInit(); 409 | void oop_setObjectFlipInit(); 410 | void oop_setObjectCel(); 411 | void oop_resetObjectCel(); 412 | void oop_evalVar(); 413 | void oop_getSceneNumberInVar(); 414 | void oop_disableBox(); 415 | void oop_enableBox(); 416 | void oop_evalBoxesXPos(); 417 | void oop_evalBoxesYPos(); 418 | void oop_setBoxToObject(); 419 | void oop_clipBoxes(); 420 | void oop_saveObjectStatus(); 421 | void oop_addObjectToBag(); 422 | void oop_removeObjectFromBag(); 423 | void oop_playSoundLowerEqualPriority(); 424 | void oop_playSoundLowerPriority(); 425 | void oop_startDialogue(); 426 | void oop_switchSceneClearBoxes(); 427 | void oop_switchSceneCopyBoxes(); 428 | 429 | // parser_scn.cpp 430 | void parseSCN(const char *fileName); 431 | 432 | // parser_dlg.cpp 433 | void parseDLG(); 434 | 435 | // resource.cpp 436 | void allocateTables(); 437 | void deallocateTables(); 438 | void loadCommonSprites(); 439 | void unloadCommonSprites(); 440 | uint8_t *loadFile(const char *fileName, uint8_t *dst = 0, uint32_t *dstSize = 0); 441 | void loadWGP(const char *fileName); 442 | void loadSPR(const char *fileName, SceneAnimation *sa); 443 | void loadMOV(const char *fileName); 444 | void loadKBR(const char *fileName); 445 | void loadTBM(); 446 | 447 | // saveload.cpp 448 | void saveState(File *f, int slot); 449 | void loadState(File *f, int slot, bool switchScene); 450 | 451 | // win16.cpp (temporary helpers) 452 | int win16_sndPlaySound(int op, void *data = 0); 453 | void win16_stretchBits(SceneBitmap *bits, int srcHeight, int srcWidth, int srcY, int srcX, int dstHeight, int dstWidth, int dstY, int dstX); 454 | 455 | int _nextState, _state; 456 | bool _textCp949; 457 | bool _isDemo; 458 | const char *_startupScene; 459 | FileSystem _fs; 460 | RandomGenerator _rnd; 461 | SystemStub *_stub; 462 | Mixer *_mixer; 463 | const char *_dataPath; 464 | const char *_savePath; 465 | const char *_musicPath; 466 | uint32_t _cheats; 467 | int _stateSlot; 468 | int _mixerSoundId; 469 | int _mixerMusicId; 470 | int _menuObjectCount; 471 | int _menuObjectMotion; 472 | int _menuObjectFrames; 473 | int _menuOption; 474 | int _menuHighlight; 475 | 476 | uint8_t *_bitmapBuffer0; 477 | SceneBitmap _bitmapBuffer1; 478 | uint8_t *_bitmapBuffer2; 479 | SceneBitmap _bitmapBuffer3; 480 | uint8_t *_bermudaOvrData; 481 | uint8_t *_tempDecodeBuffer; 482 | SceneBitmap _bagBackgroundImage; 483 | uint8_t *_bermudaSprData; 484 | uint8_t *_bermudaSprDataTable[3]; 485 | uint32_t _keyboardReplaySize; 486 | uint32_t _keyboardReplayOffset; 487 | uint8_t *_keyboardReplayData; 488 | 489 | uint8_t *_hangulFontData; 490 | uint32_t _hangulFontLutOffset; 491 | 492 | // inventory 493 | uint8_t *_lifeBarImageTable[11][12]; 494 | uint8_t *_weaponIconImageTable[14]; 495 | uint8_t *_ammoIconImageTable[2][5]; 496 | uint8_t *_swordIconImage; 497 | uint8_t *_iconBackgroundImage; 498 | uint8_t *_lifeBarImage; 499 | uint8_t *_bagObjectAreaBlinkImageTable[10]; 500 | int _bagObjectAreaBlinkCounter; 501 | uint8_t *_bagWeaponAreaBlinkImageTable[10]; 502 | int _bagWeaponAreaBlinkCounter; 503 | int _lifeBarCurrentFrame; 504 | int _bagObjectW, _bagObjectH; 505 | 506 | // dialogue 507 | int _lastDialogueEndedId; 508 | int _dialogueEndedFlag; 509 | int _dialogueSpriteIndex; 510 | int _dialogueSpriteFrameCountTable[3]; 511 | int _dialogueSpriteCurrentFrameTable[3]; 512 | int _dialogueChoiceSelected; 513 | uint8_t *_dialogueSpriteDataTable[3][105]; 514 | int _dialogueSpeechIndex; 515 | int _loadDialogueDataState; 516 | int _dialogueDescriptionSize; 517 | char *_dialogueDescriptionBuffer; 518 | DialogueChoice _dialogueChoiceData[NUM_DIALOG_ENTRIES]; 519 | int _dialogueChoiceSize; 520 | bool _dialogueChoiceGotoFlag[NUM_DIALOG_CHOICES]; 521 | char *_dialogueChoiceText[NUM_DIALOG_CHOICES]; 522 | char *_dialogueChoiceSpeechSoundFile[NUM_DIALOG_CHOICES]; 523 | char *_dialogueChoiceNextId[NUM_DIALOG_CHOICES]; 524 | int _dialogueChoiceCounter; 525 | uint8_t *_dialogueFrameSpriteData; 526 | Rect _dialogueTextRect; 527 | 528 | // logic 529 | int _sceneDescriptionSize; 530 | char *_sceneDescriptionBuffer; 531 | Script _objectScript; 532 | int16_t _defaultVarsTable[NUM_VARS]; 533 | int16_t _varsTable[NUM_VARS]; 534 | const char *_scriptDialogId; 535 | const char *_scriptDialogFileName; 536 | const char *_scriptDialogSprite1; 537 | const char *_scriptDialogSprite2; 538 | bool _switchScene; 539 | bool _startDialogue; 540 | bool _loadState; 541 | bool _clearSceneData; 542 | bool _gameOver; 543 | int _loadDataState; 544 | int _currentBagAction; 545 | int _previousBagAction; 546 | int _currentBagObject; 547 | int _previousBagObject; 548 | bool _workaroundRaftFlySceneBug; 549 | int _currentPlayingSoundPriority; 550 | bool _lifeBarDisplayed; 551 | bool _lifeBarDisplayed2; 552 | uint8_t _keysPressed[128]; 553 | int _mouseButtonsPressed; 554 | int _musicTrack; 555 | char _musicName[40]; 556 | int _sceneNumber; 557 | char _currentSceneWgp[128]; 558 | char _tempTextBuffer[128]; 559 | char _currentSceneScn[128]; 560 | char _currentSceneSav[128]; // non interactive parts of the demo relies on savestates 561 | int _bagPosX, _bagPosY; 562 | SceneObject *_sortedSceneObjectsTable[NUM_SCENE_OBJECTS]; 563 | SceneObject _sceneObjectsTable[NUM_SCENE_OBJECTS]; 564 | int _sceneObjectsCount; 565 | SceneAnimation _animationsTable[NUM_SCENE_ANIMATIONS]; 566 | int _animationsCount; 567 | SoundBuffer _soundBuffersTable[NUM_SOUND_BUFFERS]; 568 | int _soundBuffersCount; 569 | Box _boxesTable[NUM_BOXES][10]; 570 | int _boxesCountTable[NUM_BOXES]; 571 | SceneObjectFrame _sceneObjectFramesTable[NUM_SCENE_OBJECT_FRAMES]; 572 | int _sceneObjectFramesCount; 573 | BagObject _bagObjectsTable[NUM_BAG_OBJECTS]; 574 | int _bagObjectsCount; 575 | SceneObjectMotion _sceneObjectMotionsTable[NUM_SCENE_MOTIONS]; 576 | int _sceneObjectMotionsCount; 577 | NextScene _nextScenesTable[NUM_NEXT_SCENES]; 578 | int _sceneConditionsCount; 579 | SceneObjectStatus _sceneObjectStatusTable[NUM_SCENE_OBJECT_STATUS]; 580 | int _sceneObjectStatusCount; 581 | 582 | static const uint16_t _fontData[]; 583 | static const uint8_t _fontCharWidth[]; 584 | static const uint8_t _bermudaIconBmpData[]; 585 | static const int _bermudaIconBmpSize; 586 | static const uint8_t _bermudaDemoBmpData[]; 587 | static const int _bermudaDemoBmpSize; 588 | }; 589 | 590 | #endif // GAME_H__ 591 | -------------------------------------------------------------------------------- /tools/decode_mov.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | typedef unsigned char uint8; 8 | typedef short int16; 9 | typedef unsigned short uint16; 10 | 11 | static uint8 *objectScriptData; 12 | static int objectScriptOffset; 13 | static int objectScriptSize; 14 | 15 | static int getFileSize(FILE *fp) { 16 | int pos = ftell(fp); 17 | fseek(fp, 0, SEEK_END); 18 | int sz = ftell(fp); 19 | fseek(fp, 0, SEEK_SET); 20 | return sz; 21 | } 22 | 23 | static int16 readNextScriptInt() { 24 | assert(objectScriptOffset < objectScriptSize); 25 | uint16 num = objectScriptData[objectScriptOffset] | (objectScriptData[objectScriptOffset + 1] << 8); 26 | objectScriptOffset += 2; 27 | return (int16)num; 28 | } 29 | 30 | static const char *readNextScriptObjectName() { 31 | const char *name = ""; 32 | int len = readNextScriptInt(); 33 | assert(len >= 0); 34 | if (len == 0) { 35 | name = ""; 36 | } else { 37 | name = (const char *)(objectScriptData + objectScriptOffset); 38 | } 39 | objectScriptOffset += len; 40 | return name; 41 | } 42 | 43 | static const char *readNextScriptEval(const char *dst) { 44 | static char evalBuffer[256]; 45 | int op = readNextScriptInt(); 46 | int val = readNextScriptInt(); 47 | switch (op) { 48 | case 0: 49 | snprintf(evalBuffer, sizeof(evalBuffer), "%s = %d", dst, val); 50 | break; 51 | case 1: 52 | snprintf(evalBuffer, sizeof(evalBuffer), "%s += %d", dst, val); 53 | break; 54 | case 2: 55 | snprintf(evalBuffer, sizeof(evalBuffer), "%s -= %d", dst, val); 56 | break; 57 | case 3: 58 | snprintf(evalBuffer, sizeof(evalBuffer), "%s *= %d", dst, val); 59 | break; 60 | case 4: 61 | snprintf(evalBuffer, sizeof(evalBuffer), "%s /= %d", dst, val); 62 | break; 63 | default: 64 | printf("Invalid eval operator %d", op); 65 | exit(0); 66 | } 67 | return evalBuffer; 68 | } 69 | 70 | static const char *readNextScriptTest(const char *src) { 71 | static char testBuffer[256]; 72 | int op = readNextScriptInt(); 73 | if (op == -1) { 74 | char orTestBuffer[64]; 75 | int num = readNextScriptInt(); 76 | for (int i = 0; i < num; ++i) { 77 | int cmp1 = readNextScriptInt(); 78 | int cmp2 = readNextScriptInt(); 79 | assert(cmp1 <= cmp2); 80 | snprintf(orTestBuffer,sizeof(orTestBuffer), "%d <= %s <= %d", cmp1, src, cmp2); 81 | if (i == 0) { 82 | strcpy(testBuffer, orTestBuffer); 83 | } else { 84 | strcat(testBuffer, " || "); 85 | strcat(testBuffer, orTestBuffer); 86 | } 87 | } 88 | } else { 89 | int cmp = readNextScriptInt(); 90 | switch (op) { 91 | case 0: 92 | snprintf(testBuffer, sizeof(testBuffer), "%d == %s", cmp, src); 93 | break; 94 | case 1: 95 | snprintf(testBuffer, sizeof(testBuffer), "%d != %s", cmp, src); 96 | break; 97 | case 2: 98 | snprintf(testBuffer, sizeof(testBuffer), "%d > %s", cmp, src); 99 | break; 100 | case 3: 101 | snprintf(testBuffer, sizeof(testBuffer), "%d < %s", cmp, src); 102 | break; 103 | case 4: 104 | snprintf(testBuffer, sizeof(testBuffer), "%d >= %s", cmp, src); 105 | break; 106 | case 5: 107 | snprintf(testBuffer, sizeof(testBuffer), "%d <= %s", cmp, src); 108 | break; 109 | default: 110 | printf("Invalid test operator %d\n", op); 111 | exit(0); 112 | } 113 | } 114 | return testBuffer; 115 | } 116 | 117 | static void getScaledCoord(int *mul, int *div, int *d) { 118 | *mul = readNextScriptInt(); 119 | *div = readNextScriptInt(); 120 | *d = readNextScriptInt(); 121 | } 122 | 123 | static const char *getKeyIdentifier(int key) { 124 | static char keyId[20]; 125 | switch (key) { 126 | case 13: 127 | strcpy(keyId, "VK_ENTER"); 128 | break; 129 | case 16: 130 | strcpy(keyId, "VK_SHIFT"); 131 | break; 132 | case 32: 133 | strcpy(keyId, "VK_SPACE"); 134 | break; 135 | case 37: 136 | strcpy(keyId, "VK_LEFT"); 137 | break; 138 | case 38: 139 | strcpy(keyId, "VK_UP"); 140 | break; 141 | case 39: 142 | strcpy(keyId, "VK_RIGHT"); 143 | break; 144 | case 40: 145 | strcpy(keyId, "VK_DOWN"); 146 | break; 147 | default: 148 | snprintf(keyId, sizeof(keyId), "%d", key); 149 | break; 150 | } 151 | return keyId; 152 | } 153 | 154 | static void decodeObjectScript() { 155 | int statementCount = 0; 156 | objectScriptOffset = 0; 157 | while (objectScriptOffset < objectScriptSize) { 158 | int endOffset = readNextScriptInt(); 159 | int op; 160 | 161 | // conditions 162 | // printf("if ( // statement %d, offset 0x%04X/0x%04X\n", statementCount, objectScriptOffset, objectScriptSize); 163 | printf("SCRIPT %d OFFSET 0x%X\n", statementCount, objectScriptOffset); 164 | printf("\tCONDITIONS SEQUENCE\n"); 165 | while ((op = readNextScriptInt()) != 0) { 166 | printf("\t\t"); 167 | switch (op) { 168 | case 10: { 169 | printf("TRUE"); 170 | } 171 | break; 172 | case 100: { 173 | int rnd = readNextScriptInt(); 174 | printf("IS_IN_RANDOM_RANGE(rnd=%d)", rnd); 175 | } 176 | break; 177 | case 500: { 178 | int key = readNextScriptInt(); 179 | printf("IS_KEY_PRESSED(key=%s)", getKeyIdentifier(key)); 180 | } 181 | break; 182 | case 510: { 183 | int key = readNextScriptInt(); 184 | printf("IS_KEY_NOT_PRESSED(key=%s)", getKeyIdentifier(key)); 185 | } 186 | break; 187 | case 3000: { 188 | const char *name = readNextScriptObjectName(); 189 | int state = readNextScriptInt(); 190 | printf("object['%s'].state == %d", name, state); 191 | } 192 | break; 193 | case 3050: { 194 | const char *name = readNextScriptObjectName(); 195 | int x1 = readNextScriptInt(); 196 | int y1 = readNextScriptInt(); 197 | int x2 = readNextScriptInt(); 198 | int y2 = readNextScriptInt(); 199 | printf("IS_OBJECT_IN_RECT(object['%s'], x1=%d, y1=%d, x2=%d, y2=%d)", name, x1, y1, x2, y2); 200 | } 201 | break; 202 | case 3100: { 203 | const char *name = readNextScriptObjectName(); 204 | objectScriptOffset += 14; // XXX 205 | printf("cop_testPrevObjectTransformXPos(object['%s'])", name); 206 | } 207 | break; 208 | case 3105: { 209 | const char *name = readNextScriptObjectName(); 210 | objectScriptOffset += 14; // XXX 211 | printf("IS_OBJECT_X_IN_AREA(object['%s'])", name); 212 | } 213 | break; 214 | case 3110: { 215 | const char *name = readNextScriptObjectName(); 216 | objectScriptOffset += 14; // XXX 217 | printf("IS_OBJECT_X_IN_AREA(object['%s'])", name); 218 | } 219 | break; 220 | case 3300: { 221 | const char *name = readNextScriptObjectName(); 222 | int value = readNextScriptInt(); 223 | printf("object['%s'].unk1A == %d", name, value); 224 | } 225 | break; 226 | case 3310: { 227 | const char *name = readNextScriptObjectName(); 228 | int value = readNextScriptInt(); 229 | printf("object['%s'].unk18 == %d", name, value); 230 | } 231 | break; 232 | case 3400: { 233 | const char *name = readNextScriptObjectName(); 234 | char data[100]; 235 | snprintf(data, sizeof(data), "GET_FIRST_ANIM_FRAME(object['%s'])", name); 236 | const char *test = readNextScriptTest(data); 237 | printf("%s", test); 238 | } 239 | break; 240 | case 3500: { 241 | const char *name = readNextScriptObjectName(); 242 | char data[100]; 243 | snprintf(data, sizeof(data), "GET_CURRENT_ANIM_FRAME(object['%s'])", name); 244 | const char *test = readNextScriptTest(data); 245 | printf("%s", test); 246 | } 247 | break; 248 | case 3510: { 249 | const char *name = readNextScriptObjectName(); 250 | char data[100]; 251 | snprintf(data, sizeof(data), "GET_PREVIOUS_ANIM_FRAME(object['%s'])", name); 252 | const char *test = readNextScriptTest(data); 253 | printf("%s", test); 254 | } 255 | break; 256 | case 3600: { 257 | int var = readNextScriptInt(); 258 | const char *name = readNextScriptObjectName(); 259 | char data[100]; 260 | snprintf(data, sizeof(data), "object['%s'].varsTable[%d]", name, var); 261 | const char *test = readNextScriptTest(data); 262 | printf("%s", test); 263 | } 264 | break; 265 | case 3700: { 266 | const char *name1 = readNextScriptObjectName(); 267 | objectScriptOffset += 12; // XXX 268 | const char *name2 = readNextScriptObjectName(); 269 | objectScriptOffset += 12; // XXX 270 | printf("UNK_3700"); 271 | } 272 | break; 273 | case 3710: { 274 | readNextScriptObjectName(); 275 | objectScriptOffset += 12; // XXX 276 | readNextScriptObjectName(); // XXX 277 | objectScriptOffset += 12; // XXX 278 | printf("UNK_3710"); 279 | } 280 | break; 281 | case 6000: { 282 | int var = readNextScriptInt(); 283 | char data[100]; 284 | snprintf(data, sizeof(data), "varsTable[%d]", var); 285 | const char *test = readNextScriptTest(data); 286 | printf("%s", test); 287 | } 288 | break; 289 | case 6500: { 290 | int num = readNextScriptInt(); 291 | printf("IS_CURRENT_BAG_ACTION == %d", num); 292 | } 293 | break; 294 | case 7000: { 295 | int box = readNextScriptInt(); 296 | const char *name = readNextScriptObjectName(); 297 | objectScriptOffset += 6 * 4; 298 | printf("IS_IN_BOX(box=%d, object['%s'])", box, name); 299 | } 300 | break; 301 | case 7500: { 302 | int box = readNextScriptInt(); 303 | const char *name = readNextScriptObjectName(); 304 | objectScriptOffset += 6 * 4; 305 | printf("IS_IN_BOX_2(box=%d, object['%s'])", box, name); 306 | } 307 | break; 308 | case 10000: { 309 | int len = readNextScriptInt(); 310 | const char *name = (const char *)(objectScriptData + objectScriptOffset); 311 | objectScriptOffset += len; 312 | printf("IS_CURRENT_BAG_OBJECT == %s", name); 313 | } 314 | break; 315 | case 25000: { 316 | const char *test = readNextScriptTest("_lastDialogueEndedId"); 317 | printf("%s", test); 318 | } 319 | break; 320 | case 30000: { 321 | int num = readNextScriptInt(); 322 | printf("IS_NEXT_SCENE(scene=%d)", num); 323 | } 324 | break; 325 | default: 326 | printf("Unhandled condition %d\n", op); 327 | exit(0); 328 | } 329 | printf("\n"); 330 | } 331 | 332 | // operators 333 | printf("\tOPERATORS SEQUENCE\n"); 334 | while (objectScriptOffset < endOffset) { 335 | op = readNextScriptInt(); 336 | printf("\t\t"); 337 | switch (op) { 338 | case 100: { 339 | printf("END_OF_OBJECT_SCRIPT"); 340 | } 341 | break; 342 | case 3000: { 343 | const char *name = readNextScriptObjectName(); 344 | int val = readNextScriptInt(); 345 | printf("INIT_OBJECT(object['%s'], mode=%d)", name, val); 346 | } 347 | break; 348 | case 3100: { 349 | objectScriptOffset += 6; 350 | const char *eval = readNextScriptEval("CURRENT_OBJECT.xPosCur2"); 351 | printf("%s", eval); 352 | } 353 | break; 354 | case 3110: { 355 | objectScriptOffset += 6; 356 | const char *eval = readNextScriptEval("CURRENT_OBJECT.yPosCur2"); 357 | printf("%s", eval); 358 | } 359 | break; 360 | case 3300: { 361 | const char *name = readNextScriptObjectName(); 362 | int val = readNextScriptInt(); 363 | printf("object['%s'].unk18 = %d", name, val); 364 | } 365 | break; 366 | case 3400: { 367 | const char *name = readNextScriptObjectName(); 368 | int a0 = readNextScriptInt(); 369 | int a2 = readNextScriptInt(); 370 | printf("ADJUST_POS(object['%s'], %d, %d, 0, 0, 0, 0)", name, a2, a0); 371 | } 372 | break; 373 | case 3430: { 374 | const char *name = readNextScriptObjectName(); 375 | int a0 = readNextScriptInt(); 376 | int a2 = readNextScriptInt(); 377 | int a4 = readNextScriptInt(); 378 | int a6 = readNextScriptInt(); 379 | printf("ADJUST_POS(object['%s'], %d, %d, 1, %d, 1, %d)", name, a2, a0, a4, a6); 380 | } 381 | break; 382 | case 3440: { 383 | const char *name = readNextScriptObjectName(); 384 | printf("SETUP_OBJECT_POS(object['%s'], 1, 2, 1)", name); 385 | objectScriptOffset += 16; 386 | } 387 | break; 388 | case 3480: { 389 | const char *name = readNextScriptObjectName(); 390 | printf("SETUP_OBJECT_POS(object['%s'], 1, 2, 3)", name); 391 | objectScriptOffset += 20; 392 | } 393 | break; 394 | case 3500: { 395 | const char *name = readNextScriptObjectName(); 396 | int a0 = readNextScriptInt(); 397 | printf("ADJUST_POS(object['%s'], 1, %d, 0, 0, 0, 0)", name, a0); 398 | } 399 | break; 400 | case 3530: { 401 | const char *name = readNextScriptObjectName(); 402 | int a0 = readNextScriptInt(); 403 | int a2 = readNextScriptInt(); 404 | int a4 = readNextScriptInt(); 405 | printf("ADJUST_POS(object['%s'], 1, %d, 1, %d, 1, %d)", name, a0, a2, a4); 406 | } 407 | break; 408 | case 3540: { 409 | const char *name = readNextScriptObjectName(); 410 | printf("SETUP_OBJECT_POS(object['%s'], 0, 2, 1)", name); 411 | objectScriptOffset += 14; 412 | } 413 | break; 414 | case 3560: { 415 | const char *name = readNextScriptObjectName(); 416 | printf("SETUP_OBJECT_POS(object['%s'], 0, 2, 2)", name); 417 | objectScriptOffset += 26; 418 | } 419 | break; 420 | case 4000: { 421 | int var = readNextScriptInt(); 422 | const char *name = readNextScriptObjectName(); 423 | char data[100]; 424 | snprintf(data, sizeof(data), "object['%s'].varsTable[%d]", name, var); 425 | const char *eval = readNextScriptEval(data); 426 | printf("%s", eval); 427 | } 428 | break; 429 | case 4100: { 430 | const char *name = readNextScriptObjectName(); 431 | int var1 = readNextScriptInt(); 432 | int var2 = readNextScriptInt(); 433 | int var3 = readNextScriptInt(); 434 | int var4 = readNextScriptInt(); 435 | printf("TRANSLATE_X_POS(object['%s'], %d, %d, %d, %d)", name, var1, var2, var3, var4); 436 | } 437 | break; 438 | case 4200: { 439 | const char *name = readNextScriptObjectName(); 440 | int var1 = readNextScriptInt(); 441 | int var2 = readNextScriptInt(); 442 | int var3 = readNextScriptInt(); 443 | int var4 = readNextScriptInt(); 444 | printf("TRANSLATE_Y_POS(object['%s'], %d, %d, %d, %d)", name, var1, var2, var3, var4); 445 | } 446 | break; 447 | case 5000: { 448 | const char *name = readNextScriptObjectName(); 449 | int mode = readNextScriptInt(); 450 | printf("object['%s'].mode = %d", name, mode); 451 | if (mode == 2) { 452 | int rnd = readNextScriptInt(); 453 | printf(" rnd = %d", rnd); 454 | } 455 | } 456 | break; 457 | case 5100: { 458 | const char *name = readNextScriptObjectName(); 459 | int x = readNextScriptInt(); 460 | int y = readNextScriptInt(); 461 | printf("object['%s'] { .xOrigin = %d, .yOrigin = %d }", name, x, y); 462 | } 463 | break; 464 | case 5110: { 465 | const char *name = readNextScriptObjectName(); 466 | objectScriptOffset += 12; // XXX 467 | printf("object['%s'].transform { .xOrigin, .yOrigin }", name); 468 | } 469 | break; 470 | case 5112: { 471 | const char *name = readNextScriptObjectName(); 472 | char data[100]; 473 | snprintf(data, sizeof(data), "object['%s'].xOrigin", name); 474 | const char *eval = readNextScriptEval(data); 475 | printf("%s", eval); 476 | } 477 | break; 478 | case 5114: { 479 | const char *name = readNextScriptObjectName(); 480 | char data[100]; 481 | snprintf(data, sizeof(data), "object['%s'].yOrigin", name); 482 | const char *eval = readNextScriptEval(data); 483 | printf("%s", eval); 484 | } 485 | break; 486 | case 5500: { 487 | const char *name = readNextScriptObjectName(); 488 | int val = readNextScriptInt(); 489 | printf("object['%s'].motionNum = %d", name, val); 490 | } 491 | break; 492 | case 6000: { 493 | int var = readNextScriptInt(); 494 | char data[100]; 495 | snprintf(data, sizeof(data), "varsTable[%d]", var); 496 | const char *eval = readNextScriptEval(data); 497 | printf("%s", eval); 498 | } 499 | break; 500 | case 6100: { 501 | int var = readNextScriptInt(); 502 | printf("varsTable[%d] = SCENE_NUMBER", var); 503 | } 504 | break; 505 | case 7000: { 506 | int box = readNextScriptInt(); 507 | int num = readNextScriptInt(); 508 | printf("Box[%d][%d].disable", box, num); 509 | } 510 | break; 511 | case 7010: { 512 | int box = readNextScriptInt(); 513 | int num = readNextScriptInt(); 514 | printf("Box[%d][%d].enable", box, num); 515 | } 516 | break; 517 | case 7100: { 518 | const char *eval = readNextScriptEval("BOXES.X1,BOXES.X2"); 519 | printf("%s", eval); 520 | } 521 | break; 522 | case 7110: { 523 | const char *eval = readNextScriptEval("BOXES.Y1,BOXES.Y2"); 524 | printf("%s", eval); 525 | } 526 | break; 527 | case 7200: { 528 | int box = readNextScriptInt(); 529 | int num = readNextScriptInt(); 530 | const char *name = readNextScriptObjectName(); 531 | char data[100]; 532 | snprintf(data, sizeof(data), "object['%s']", name); 533 | int x1mul, x1div, x1d; 534 | getScaledCoord(&x1mul, &x1div, &x1d); 535 | int x2mul, x2div, x2d; 536 | getScaledCoord(&x2mul, &x2div, &x2d); 537 | int y1mul, y1div, y1d; 538 | getScaledCoord(&y1mul, &y1div, &y1d); 539 | int y2mul, y2div, y2d; 540 | getScaledCoord(&y2mul, &y2div, &y2d); 541 | printf("set_box_from_object ( [%d][%d], object['%s'], sx1=%d,%d,%d, sy1=%d,%d,%d, sx2=%d,%d,%d, sy2=%d,%d,%d )", box, num, name, x1mul, x1div, x1d, y1mul, y1div, y1d, x2mul, x2div, x2d, y2mul, y2div, y2d); 542 | } 543 | break; 544 | case 7300: { 545 | int x1 = readNextScriptInt(); 546 | int y1 = readNextScriptInt(); 547 | int x2 = readNextScriptInt(); 548 | int y2 = readNextScriptInt(); 549 | printf("EXTEND_BOXES(x1=%d, y1=%d, x2=%d, y2=%d)", x1, y1, x2, y2); 550 | } 551 | break; 552 | case 8000: { 553 | int index = readNextScriptInt(); 554 | printf("save_object_status ( index=%d )", index); 555 | } 556 | break; 557 | case 20000: { 558 | int num = readNextScriptInt(); 559 | int priority = readNextScriptInt(); 560 | printf("PLAY_SOUND_LOWER_EQUAL_PRIORITY(num=%d, priority=%d)", num, priority); 561 | } 562 | break; 563 | case 25000: { 564 | const char *str[4]; 565 | for (int i = 0; i < 4; ++i) { 566 | int len = readNextScriptInt(); 567 | str[i] = (const char *)(objectScriptData + objectScriptOffset); 568 | objectScriptOffset += len; 569 | } 570 | printf("START_TALK '%s'", str[0]); 571 | } 572 | break; 573 | case 30000: { 574 | int num = readNextScriptInt(); 575 | printf("SWITCH_TO_SCENE(scene=%d)", num); 576 | } 577 | break; 578 | case 30010: { 579 | int num = readNextScriptInt(); 580 | printf("SWITCH_TO_SCENE_COPY_BOXES(scene=%d)", num); 581 | } 582 | break; 583 | default: 584 | printf("Unhandled operator %d\n", op); 585 | exit(0); 586 | } 587 | printf("\n"); 588 | } 589 | printf("\n"); 590 | 591 | assert(objectScriptOffset == endOffset); 592 | ++statementCount; 593 | } 594 | } 595 | 596 | int main(int argc, char *argv[]) { 597 | if (argc == 2) { 598 | FILE *fp = fopen(argv[1], "rb"); 599 | if (fp) { 600 | objectScriptSize = getFileSize(fp); 601 | objectScriptData = (uint8 *)malloc(objectScriptSize); 602 | if (objectScriptData) { 603 | fread(objectScriptData, objectScriptSize, 1, fp); 604 | decodeObjectScript(); 605 | free(objectScriptData); 606 | objectScriptData = 0; 607 | } 608 | fclose(fp); 609 | } 610 | } 611 | return 0; 612 | } 613 | -------------------------------------------------------------------------------- /parser_scn.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Bermuda Syndrome engine rewrite 3 | * Copyright (C) 2007-2011 Gregory Montoir 4 | */ 5 | 6 | #include "file.h" 7 | #include "fs.h" 8 | #include "game.h" 9 | #include "str.h" 10 | 11 | enum ParserState { 12 | kParserStateDef = 0, 13 | kParserStateMovies = 1, 14 | kParserStateBag = 2, 15 | kParserStateScene = 3, 16 | kParserStateObject = 4, 17 | kParserStateNewObject = 5, 18 | kParserStateBox = 6 19 | }; 20 | 21 | enum ParserToken { 22 | kParserTokenGlobalMemory, 23 | kParserTokenSceneNumber, 24 | kParserTokenEq, 25 | kParserTokenNotEq, 26 | kParserTokenLower, 27 | kParserTokenLowerEq, 28 | kParserTokenGreater, 29 | kParserTokenGreaterEq, 30 | kParserTokenThen, 31 | kParserTokenEnd, 32 | kParserTokenMovies, 33 | kParserTokenScene, 34 | kParserTokenScreen, 35 | kParserTokenMidi, 36 | kParserTokenNULL, 37 | kParserTokenObject, 38 | kParserTokenIfNewObject, 39 | kParserTokenBag, 40 | kParserTokenBox, 41 | kParserTokenBagEnd, 42 | kParserTokenMoviesEnd, 43 | kParserTokenSceneEnd, 44 | kParserTokenObjectEnd, 45 | kParserTokenBoxEnd, 46 | kParserTokenClass, 47 | kParserTokenMemory, 48 | kParserTokenCoord, 49 | kParserTokenAddCoordX, 50 | kParserTokenAddCoordY, 51 | kParserTokenCoordX, 52 | kParserTokenCoordY, 53 | kParserTokenDepth, 54 | kParserTokenMove, 55 | kParserTokenCel, 56 | kParserTokenMirror, 57 | kParserTokenNo, 58 | kParserTokenX, 59 | kParserTokenY, 60 | kParserTokenXY, 61 | kParserTokenInit, 62 | kParserTokenNoInit, 63 | kParserTokenSimple, 64 | kParserTokenRandom, 65 | kParserTokenPut, 66 | kParserTokenLoadStatus, 67 | kParserTokenDisable, 68 | kParserTokenEnable, 69 | kParserTokenMix, 70 | kParserTokenEOS, 71 | kParserTokenUnknown 72 | }; 73 | 74 | static int _currentState; 75 | static bool _stopParsing; 76 | static ParserToken _currentToken; 77 | static const char *_currentTokenStr; 78 | static SceneObject *_currentSceneObject; 79 | 80 | static ParserToken getNextToken(char **s) { 81 | _currentTokenStr = stringNextToken(s); 82 | if (!_currentTokenStr || !*_currentTokenStr) { 83 | return kParserTokenEOS; 84 | } else if (strcmp(_currentTokenStr, "GlobalMemory") == 0) { 85 | return kParserTokenGlobalMemory; 86 | } else if (strcmp(_currentTokenStr, "SceneNumber") == 0) { 87 | return kParserTokenSceneNumber; 88 | } else if (strcmp(_currentTokenStr, "==") == 0) { 89 | return kParserTokenEq; 90 | } else if (strcmp(_currentTokenStr, "!=") == 0) { 91 | return kParserTokenNotEq; 92 | } else if (strcmp(_currentTokenStr, "<") == 0) { 93 | return kParserTokenLower; 94 | } else if (strcmp(_currentTokenStr, "<=") == 0) { 95 | return kParserTokenLowerEq; 96 | } else if (strcmp(_currentTokenStr, ">") == 0) { 97 | return kParserTokenGreater; 98 | } else if (strcmp(_currentTokenStr, ">=") == 0) { 99 | return kParserTokenGreaterEq; 100 | } else if (strcmp(_currentTokenStr, "->") == 0) { 101 | return kParserTokenThen; 102 | } else if (strcmp(_currentTokenStr, "End") == 0) { 103 | return kParserTokenEnd; 104 | } else if (strcmp(_currentTokenStr, "Movies") == 0) { 105 | return kParserTokenMovies; 106 | } else if (strcmp(_currentTokenStr, "Scene") == 0) { 107 | return kParserTokenScene; 108 | } else if (strcmp(_currentTokenStr, "Screen") == 0) { 109 | return kParserTokenScreen; 110 | } else if (strcmp(_currentTokenStr, "Midi") == 0) { 111 | return kParserTokenMidi; 112 | } else if (strcmp(_currentTokenStr, "NULL") == 0) { 113 | return kParserTokenNULL; 114 | } else if (strcmp(_currentTokenStr, "Object:") == 0) { 115 | return kParserTokenObject; 116 | } else if (strcmp(_currentTokenStr, "IfNewObject:") == 0) { 117 | return kParserTokenIfNewObject; 118 | } else if (strcmp(_currentTokenStr, "Bag") == 0) { 119 | return kParserTokenBag; 120 | } else if (strcmp(_currentTokenStr, "Box") == 0) { 121 | return kParserTokenBox; 122 | } else if (strcmp(_currentTokenStr, "BagEnd") == 0) { 123 | return kParserTokenBagEnd; 124 | } else if (strcmp(_currentTokenStr, "MoviesEnd") == 0) { 125 | return kParserTokenMoviesEnd; 126 | } else if (strcmp(_currentTokenStr, "SceneEnd") == 0) { 127 | return kParserTokenSceneEnd; 128 | } else if (strcmp(_currentTokenStr, "ObjectEnd") == 0) { 129 | return kParserTokenObjectEnd; 130 | } else if (strcmp(_currentTokenStr, "BoxEnd") == 0) { 131 | return kParserTokenBoxEnd; 132 | } else if (strcmp(_currentTokenStr, "Class") == 0) { 133 | return kParserTokenClass; 134 | } else if (strcmp(_currentTokenStr, "Memory") == 0) { 135 | return kParserTokenMemory; 136 | } else if (strcmp(_currentTokenStr, "Coord") == 0) { 137 | return kParserTokenCoord; 138 | } else if (strcmp(_currentTokenStr, "AddCoordX") == 0) { 139 | return kParserTokenAddCoordX; 140 | } else if (strcmp(_currentTokenStr, "AddCoordY") == 0) { 141 | return kParserTokenAddCoordY; 142 | } else if (strcmp(_currentTokenStr, "CoordX") == 0) { 143 | return kParserTokenCoordX; 144 | } else if (strcmp(_currentTokenStr, "CoordY") == 0) { 145 | return kParserTokenCoordY; 146 | } else if (strcmp(_currentTokenStr, "Depth") == 0) { 147 | return kParserTokenDepth; 148 | } else if (strcmp(_currentTokenStr, "Move") == 0) { 149 | return kParserTokenMove; 150 | } else if (strcmp(_currentTokenStr, "Cel") == 0) { 151 | return kParserTokenCel; 152 | } else if (strcmp(_currentTokenStr, "Mirror") == 0) { 153 | return kParserTokenMirror; 154 | } else if (strcmp(_currentTokenStr, "No") == 0) { 155 | return kParserTokenNo; 156 | } else if (strcmp(_currentTokenStr, "X") == 0) { 157 | return kParserTokenX; 158 | } else if (strcmp(_currentTokenStr, "Y") == 0) { 159 | return kParserTokenY; 160 | } else if (strcmp(_currentTokenStr, "XY") == 0) { 161 | return kParserTokenXY; 162 | } else if (strcmp(_currentTokenStr, "Init") == 0) { 163 | return kParserTokenInit; 164 | } else if (strcmp(_currentTokenStr, "NoInit") == 0) { 165 | return kParserTokenNoInit; 166 | } else if (strcmp(_currentTokenStr, "Simple") == 0) { 167 | return kParserTokenSimple; 168 | } else if (strcmp(_currentTokenStr, "Random") == 0) { 169 | return kParserTokenRandom; 170 | } else if (strcmp(_currentTokenStr, "Put") == 0) { 171 | return kParserTokenPut; 172 | } else if (strcmp(_currentTokenStr, "LoadStatus") == 0) { 173 | return kParserTokenLoadStatus; 174 | } else if (strcmp(_currentTokenStr, "Disable") == 0) { 175 | return kParserTokenDisable; 176 | } else if (strcmp(_currentTokenStr, "Enable") == 0) { 177 | return kParserTokenEnable; 178 | } else if (strcmp(_currentTokenStr, "Mix") == 0) { 179 | return kParserTokenMix; 180 | } else if (strcmp(_currentTokenStr, "ScenenNumber") == 0) { // C1_07.SCN 181 | return kParserTokenSceneNumber; 182 | } else { 183 | return kParserTokenUnknown; 184 | } 185 | } 186 | 187 | static void getToken_Int(int *i) { 188 | if (_currentTokenStr) { 189 | errno = 0; 190 | *i = strtol(_currentTokenStr, 0, 0); 191 | if (errno == 0) { 192 | return; 193 | } 194 | } 195 | error("Parse error for integer '%s'", !_currentTokenStr ? "" : _currentTokenStr); 196 | } 197 | 198 | static void getNextToken_Int(char **s, int *i) { 199 | _currentTokenStr = stringNextToken(s); 200 | getToken_Int(i); 201 | } 202 | 203 | static void getNextToken_ArrayIndex(char **s, int *i) { 204 | _currentTokenStr = stringNextToken(s); 205 | if (_currentTokenStr && _currentTokenStr[0] == '[') { 206 | errno = 0; 207 | *i = strtol(&_currentTokenStr[1], 0, 0); 208 | if (errno == 0) { 209 | *i -= 1; 210 | return; 211 | } 212 | } 213 | error("Parse error for array index '%s'", !_currentTokenStr ? "" : _currentTokenStr); 214 | } 215 | 216 | static void getNextToken_Coord(char **s, int16_t *i, int16_t *j) { 217 | _currentTokenStr = stringTrimLeft(*s); 218 | if (_currentTokenStr && _currentTokenStr[0] == '(') { 219 | errno = 0; 220 | *i = strtol(&_currentTokenStr[1], 0, 0); 221 | if (errno == 0) { 222 | const char *subTokenStr = strchr(&_currentTokenStr[1], ','); 223 | if (subTokenStr) { 224 | errno = 0; 225 | *j = strtol(&subTokenStr[1], 0, 0); 226 | if (errno == 0) { 227 | const char *endTokenStr = strchr(&subTokenStr[1], ')'); 228 | if (endTokenStr) { 229 | *s = (char *)&endTokenStr[1]; 230 | return; 231 | } 232 | } 233 | } 234 | } 235 | } 236 | error("Parse error for coord '%s'", !_currentTokenStr ? "" : _currentTokenStr); 237 | } 238 | 239 | static bool compareOp(int value1, int value2, int op) { 240 | bool compareResult = false; 241 | switch (op) { 242 | case kParserTokenEq: 243 | compareResult = (value1 == value2); 244 | break; 245 | case kParserTokenNotEq: 246 | compareResult = (value1 != value2); 247 | break; 248 | case kParserTokenLower: 249 | compareResult = (value1 < value2); 250 | break; 251 | case kParserTokenLowerEq: 252 | compareResult = (value1 <= value2); 253 | break; 254 | case kParserTokenGreater: 255 | compareResult = (value1 > value2); 256 | break; 257 | case kParserTokenGreaterEq: 258 | compareResult = (value1 >= value2); 259 | break; 260 | default: 261 | error("Invalid compare operator %d", op); 262 | break; 263 | } 264 | return compareResult; 265 | } 266 | 267 | static bool parseToken_GlobalMemory(char **s, Game *g) { 268 | int var, testValue, op; 269 | 270 | getNextToken_ArrayIndex(s, &var); 271 | op = getNextToken(s); 272 | _currentToken = getNextToken(s); 273 | if (_currentToken == kParserTokenSceneNumber) { 274 | testValue = g->_sceneNumber; 275 | } else { 276 | getToken_Int(&testValue); 277 | } 278 | assert(var >= 0 && var < Game::NUM_VARS); 279 | return compareOp(g->_varsTable[var], testValue, op); 280 | } 281 | 282 | static void parseToken_Screen(char **s, Game *g) { 283 | for (int i = 0; i < 10; ++i) { 284 | g->_boxesCountTable[i] = 0; 285 | } 286 | _currentTokenStr = stringNextToken(s); 287 | g->loadWGP(_currentTokenStr); 288 | strcpy(g->_currentSceneWgp, _currentTokenStr); 289 | g->_loadDataState = (g->_sceneObjectsCount == 0) ? 1 : 2; 290 | } 291 | 292 | static void parseToken_Midi(char **s, Game *g) { 293 | g->_musicTrack = 0; 294 | _currentToken = getNextToken(s); 295 | if (_currentToken == kParserTokenNULL) { 296 | g->stopMusic(); 297 | } else { 298 | if (strcmp(_currentTokenStr, g->_musicName) != 0) { 299 | g->playMusic(_currentTokenStr); 300 | } 301 | strcpy(g->_musicName, _currentTokenStr); 302 | getNextToken_Int(s, &g->_musicTrack); 303 | } 304 | } 305 | 306 | static void parseToken_Object(char **s, Game *g) { 307 | _currentTokenStr = stringNextToken(s); 308 | _currentSceneObject = 0; 309 | for (int i = 0; i < g->_sceneObjectsCount; ++i) { 310 | if (strcasecmp(g->_sceneObjectsTable[i].name, _currentTokenStr) == 0) { 311 | _currentSceneObject = &g->_sceneObjectsTable[i]; 312 | break; 313 | } 314 | } 315 | if (_currentState == kParserStateObject) { 316 | if (_currentSceneObject) { 317 | memset(_currentSceneObject->varsTable, 0, sizeof(_currentSceneObject->varsTable)); 318 | _currentSceneObject->state = 0; 319 | // FIXME: fixes _08.SCN TELE05.SCN transition + IARD obj 320 | _currentSceneObject->statePrev = 0; 321 | } 322 | } 323 | } 324 | 325 | static void parseToken_Bag(char **s, Game *g) { 326 | getNextToken_Int(s, &g->_bagPosX); 327 | getNextToken_Int(s, &g->_bagPosY); 328 | } 329 | 330 | static void parse_Object(char **s, Game *g) { 331 | int var, index, value; 332 | SceneObjectStatus *sos; 333 | 334 | if (!_currentSceneObject) { 335 | stringNextTokenEOL(s); 336 | return; 337 | } 338 | switch (_currentToken) { 339 | case kParserTokenClass: 340 | _currentTokenStr = stringNextToken(s); 341 | strcpy(_currentSceneObject->className, _currentTokenStr); 342 | break; 343 | case kParserTokenMemory: 344 | getNextToken_ArrayIndex(s, &var); 345 | getNextToken_Int(s, &value); 346 | assert(var >= 0 && var < 10); 347 | _currentSceneObject->varsTable[var] = value; 348 | break; 349 | case kParserTokenCoord: 350 | getNextToken_Coord(s, &_currentSceneObject->xInit, &_currentSceneObject->yInit); 351 | break; 352 | case kParserTokenAddCoordX: 353 | getNextToken_Int(s, &value); 354 | _currentSceneObject->xInit += value; 355 | break; 356 | case kParserTokenAddCoordY: 357 | getNextToken_Int(s, &value); 358 | _currentSceneObject->yInit += value; 359 | break; 360 | case kParserTokenCoordX: 361 | getNextToken_Int(s, &value); 362 | _currentSceneObject->xInit = value; 363 | break; 364 | case kParserTokenCoordY: 365 | getNextToken_Int(s, &value); 366 | _currentSceneObject->yInit = value; 367 | break; 368 | case kParserTokenDepth: 369 | getNextToken_Int(s, &value); 370 | _currentSceneObject->zInit = value; 371 | break; 372 | case kParserTokenMove: 373 | getNextToken_Int(s, &value); 374 | _currentSceneObject->motionNum = value - 1; 375 | _currentSceneObject->motionFrameNum = 0; 376 | break; 377 | case kParserTokenCel: 378 | getNextToken_Int(s, &value); 379 | _currentSceneObject->motionNum = value - 1; 380 | getNextToken_Int(s, &value); 381 | _currentSceneObject->motionFrameNum = value - 1; 382 | break; 383 | case kParserTokenMirror: 384 | _currentToken = getNextToken(s); 385 | switch (_currentToken) { 386 | case kParserTokenNo: 387 | _currentSceneObject->flipInit = 0; 388 | break; 389 | case kParserTokenX: 390 | _currentSceneObject->flipInit = 1; 391 | break; 392 | case kParserTokenY: 393 | _currentSceneObject->flipInit = 2; 394 | break; 395 | case kParserTokenXY: 396 | _currentSceneObject->flipInit = 3; 397 | break; 398 | default: 399 | error("Unexpected mirror mode %s", _currentTokenStr); 400 | break; 401 | } 402 | break; 403 | case kParserTokenInit: 404 | _currentToken = getNextToken(s); 405 | switch (_currentToken) { 406 | case kParserTokenNoInit: 407 | _currentSceneObject->mode = 0; 408 | break; 409 | case kParserTokenSimple: 410 | _currentSceneObject->mode = 1; 411 | break; 412 | case kParserTokenRandom: 413 | _currentSceneObject->mode = 2; 414 | getNextToken_Int(s, &value); 415 | _currentSceneObject->modeRndMul = value; 416 | break; 417 | case kParserTokenPut: 418 | _currentSceneObject->mode = 3; 419 | break; 420 | default: 421 | error("Unexpected init mode %s", _currentTokenStr); 422 | break; 423 | } 424 | break; 425 | case kParserTokenLoadStatus: 426 | getNextToken_ArrayIndex(s, &index); 427 | sos = g->derefSceneObjectStatus(index); 428 | _currentSceneObject->xInit = sos->x; 429 | _currentSceneObject->yInit = sos->y; 430 | _currentSceneObject->zInit = sos->z; 431 | _currentSceneObject->motionNum = sos->motionNum; 432 | _currentSceneObject->motionFrameNum = sos->frameNum; 433 | _currentSceneObject->flipInit = sos->flip; 434 | break; 435 | default: 436 | error("Unexpected token %d state %d", _currentToken, _currentState); 437 | break; 438 | } 439 | } 440 | 441 | static void parse_BagObject(char **s, Game *g) { 442 | if (g->findBagObjectByName(_currentTokenStr) == -1) { 443 | assert(g->_bagObjectsCount < Game::NUM_BAG_OBJECTS); 444 | BagObject *bo = &g->_bagObjectsTable[g->_bagObjectsCount]; 445 | strcpy(bo->name, _currentTokenStr); 446 | _currentTokenStr = stringNextToken(s); 447 | bo->data = g->loadFile(_currentTokenStr, 0, &bo->dataSize); 448 | ++g->_bagObjectsCount; 449 | } else { 450 | stringNextTokenEOL(s); 451 | } 452 | } 453 | 454 | static void parse_SceneCondition(char **s, Game *g) { 455 | int num; 456 | 457 | getToken_Int(&num); 458 | assert(g->_sceneConditionsCount < Game::NUM_NEXT_SCENES); 459 | NextScene *ns = &g->_nextScenesTable[g->_sceneConditionsCount]; 460 | ns->num = num; 461 | _currentTokenStr = stringNextToken(s); 462 | strcpy(ns->name, _currentTokenStr); 463 | stringToUpperCase(ns->name); 464 | ++g->_sceneConditionsCount; 465 | } 466 | 467 | static void parse_BoxDescription(char **s, Game *g) { 468 | int box, value; 469 | 470 | getToken_Int(&box); 471 | --box; 472 | // 10 Mix -1 192 64 (582,332) (640,480) 473 | Box *b = g->derefBox(box, g->_boxesCountTable[box]); 474 | _currentToken = getNextToken(s); 475 | switch (_currentToken) { 476 | case kParserTokenDisable: 477 | b->state = 0; 478 | break; 479 | case kParserTokenEnable: 480 | b->state = 1; 481 | break; 482 | case kParserTokenMix: 483 | b->state = 2; 484 | getNextToken_Int(s, &value); 485 | b->z = value; 486 | getNextToken_Int(s, &value); 487 | b->startColor = value; 488 | getNextToken_Int(s, &value); 489 | b->endColor = value; 490 | assert(b->startColor + b->endColor <= 256); // invalid box mix color 491 | break; 492 | default: 493 | error("Unexpected token %d state %d", _currentToken, _currentState); 494 | break; 495 | } 496 | getNextToken_Coord(s, &b->x1, &b->y1); 497 | getNextToken_Coord(s, &b->x2, &b->y2); 498 | ++g->_boxesCountTable[box]; 499 | } 500 | 501 | void Game::parseSCN(const char *fileName) { 502 | debug(DBG_GAME, "Game::parseSCN()"); 503 | 504 | FileHolder fp(_fs, fileName); 505 | _sceneDescriptionSize = fp->size(); 506 | _sceneDescriptionBuffer = (char *)malloc(_sceneDescriptionSize + 1); 507 | if (!_sceneDescriptionBuffer) { 508 | error("Unable to allocate %d bytes", _sceneDescriptionSize + 1); 509 | } 510 | fp->read(_sceneDescriptionBuffer, _sceneDescriptionSize); 511 | _sceneDescriptionBuffer[_sceneDescriptionSize] = 0; 512 | stringStripComments(_sceneDescriptionBuffer); 513 | 514 | int anim = 0; 515 | bool loadMovData = false; 516 | _sceneNumber = 0; 517 | _currentSceneObject = 0; 518 | memcpy(_defaultVarsTable, _varsTable, sizeof(_varsTable)); 519 | 520 | _currentState = kParserStateDef; 521 | _stopParsing = false; 522 | for (char *s = _sceneDescriptionBuffer; !_stopParsing && s; ) { 523 | bool didTest = false; 524 | bool compareTest = true; 525 | while ((_currentToken = getNextToken(&s)) == kParserTokenGlobalMemory) { 526 | compareTest = compareTest && parseToken_GlobalMemory(&s, this); 527 | didTest = true; 528 | } 529 | if (didTest) { 530 | if (!compareTest) { 531 | // condition statement is false, skip to next line 532 | stringNextTokenEOL(&s); 533 | continue; 534 | } 535 | if (_currentToken != kParserTokenThen) { 536 | error("Unexpected token %d", _currentToken); 537 | } 538 | _currentToken = getNextToken(&s); 539 | } 540 | if (_currentState != kParserStateDef) { 541 | switch (_currentState) { 542 | case kParserStateMovies: 543 | if (_currentToken == kParserTokenMoviesEnd) { 544 | if (!loadMovData) { 545 | clearSceneData(anim - 1); 546 | } 547 | _currentState = kParserStateDef; 548 | } else { 549 | if (!loadMovData) { 550 | if (anim < _animationsCount && strcasecmp(_animationsTable[anim].name, _currentTokenStr) == 0) { 551 | ++anim; 552 | } else { 553 | if (_animationsCount != 0) { 554 | clearSceneData(anim - 1); 555 | } 556 | _loadDataState = 1; 557 | loadMovData = true; 558 | } 559 | } 560 | if (loadMovData) { 561 | loadMOV(_currentTokenStr); 562 | } 563 | } 564 | break; 565 | case kParserStateBag: 566 | if (_currentToken == kParserTokenBagEnd) { 567 | _currentState = kParserStateDef; 568 | } else { 569 | parse_BagObject(&s, this); 570 | } 571 | break; 572 | case kParserStateScene: 573 | if (_currentToken == kParserTokenSceneEnd) { 574 | _currentState = kParserStateDef; 575 | } else { 576 | parse_SceneCondition(&s, this); 577 | } 578 | break; 579 | case kParserStateObject: 580 | if (_currentToken == kParserTokenObjectEnd) { 581 | _currentState = kParserStateDef; 582 | } else { 583 | parse_Object(&s, this); 584 | } 585 | break; 586 | case kParserStateNewObject: 587 | if (_currentToken == kParserTokenObjectEnd) { 588 | _currentState = kParserStateDef; 589 | } else { 590 | if (_currentSceneObject) { 591 | if (_sceneObjectMotionsTable[_currentSceneObject->motionInit].animNum < anim) { 592 | _currentSceneObject = 0; 593 | } 594 | } 595 | parse_Object(&s, this); 596 | } 597 | break; 598 | case kParserStateBox: 599 | if (_currentToken == kParserTokenBoxEnd) { 600 | _currentState = kParserStateDef; 601 | } else { 602 | parse_BoxDescription(&s, this); 603 | } 604 | break; 605 | } 606 | } else { 607 | switch (_currentToken) { 608 | case kParserTokenEnd: 609 | _stopParsing = true; 610 | break; 611 | case kParserTokenMovies: 612 | _currentState = kParserStateMovies; 613 | break; 614 | case kParserTokenScene: 615 | _currentState = kParserStateScene; 616 | break; 617 | case kParserTokenScreen: 618 | parseToken_Screen(&s, this); 619 | break; 620 | case kParserTokenSceneNumber: 621 | getNextToken_Int(&s, &_sceneNumber); 622 | break; 623 | case kParserTokenMidi: 624 | parseToken_Midi(&s, this); 625 | break; 626 | case kParserTokenObject: 627 | _currentState = kParserStateObject; 628 | parseToken_Object(&s, this); 629 | break; 630 | case kParserTokenIfNewObject: 631 | _currentState = kParserStateNewObject; 632 | parseToken_Object(&s, this); 633 | break; 634 | case kParserTokenBag: 635 | _currentState = kParserStateBag; 636 | parseToken_Bag(&s, this); 637 | break; 638 | case kParserTokenBox: 639 | _currentState = kParserStateBox; 640 | break; 641 | default: 642 | error("Unexpected token %d state %d", _currentToken, _currentState); 643 | break; 644 | } 645 | } 646 | } 647 | 648 | free(_sceneDescriptionBuffer); 649 | _sceneDescriptionBuffer = 0; 650 | _sceneDescriptionSize = 0; 651 | } 652 | --------------------------------------------------------------------------------