├── bin └── .gitkeep ├── AUTHORS ├── share └── another-world │ ├── another-world.png │ └── README.md ├── .gitignore ├── src ├── main.h ├── input.h ├── bytekiller.h ├── sound.h ├── music.h ├── mixer.h ├── backend.h ├── input.cc ├── audio.cc ├── logger.h ├── audio.h ├── config.h ├── file.h ├── video.h ├── resources.h ├── data.h ├── vm.h ├── intern.cc ├── bytekiller.cc ├── engine.cc ├── logger.cc ├── sound.cc ├── engine.h ├── mixer.cc ├── resources.cc ├── main.cc ├── file.cc ├── music.cc ├── intern.h └── data.cc ├── Makefile.linux ├── Makefile.wasm ├── README.md └── COPYING /bin/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Grégory Montoir 2 | Fabien Sanglard 3 | Olivier Poncet 4 | -------------------------------------------------------------------------------- /share/another-world/another-world.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ponceto/another-world-interpreter/HEAD/share/another-world/another-world.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.bin 2 | *.a 3 | *.o 4 | bin/another-world.bin 5 | bin/another-world.html 6 | bin/another-world.js 7 | bin/another-world.wasm 8 | bin/another-world.data 9 | share/another-world/BANK01 10 | share/another-world/BANK02 11 | share/another-world/BANK03 12 | share/another-world/BANK04 13 | share/another-world/BANK05 14 | share/another-world/BANK06 15 | share/another-world/BANK07 16 | share/another-world/BANK08 17 | share/another-world/BANK09 18 | share/another-world/BANK0A 19 | share/another-world/BANK0B 20 | share/another-world/BANK0C 21 | share/another-world/BANK0D 22 | share/another-world/MEMLIST.BIN 23 | -------------------------------------------------------------------------------- /share/another-world/README.md: -------------------------------------------------------------------------------- 1 | # ANOTHER WORLD INTERPRETER 2 | 3 | This directory should contain the original game data files. 4 | 5 | ## LIST OF REQUIRED FILES 6 | 7 | Simply drop the original game data files into this directory. 8 | 9 | The required files are: 10 | 11 | - `BANK01` 12 | - `BANK02` 13 | - `BANK03` 14 | - `BANK04` 15 | - `BANK05` 16 | - `BANK06` 17 | - `BANK07` 18 | - `BANK08` 19 | - `BANK09` 20 | - `BANK0A` 21 | - `BANK0B` 22 | - `BANK0C` 23 | - `BANK0D` 24 | - `MEMLIST.BIN` 25 | 26 | ## DISCLAIMER 27 | 28 | The original game data files are protected under the copyrights of their authors and are not distributed with this project. 29 | 30 | ``` 31 | Please do not distribute the original data game files with this project. 32 | ``` 33 | 34 | -------------------------------------------------------------------------------- /src/main.h: -------------------------------------------------------------------------------- 1 | /* 2 | * main.h - Copyright (c) 2004-2025 3 | * 4 | * Gregory Montoir, Fabien Sanglard, Olivier Poncet 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 2 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | #ifndef __AW_MAIN_H__ 20 | #define __AW_MAIN_H__ 21 | 22 | // --------------------------------------------------------------------------- 23 | // main 24 | // --------------------------------------------------------------------------- 25 | 26 | extern int main(int argc, char* argv[]); 27 | 28 | // --------------------------------------------------------------------------- 29 | // End-Of-File 30 | // --------------------------------------------------------------------------- 31 | 32 | #endif /* __AW_MAIN_H__ */ 33 | -------------------------------------------------------------------------------- /src/input.h: -------------------------------------------------------------------------------- 1 | /* 2 | * input.h - Copyright (c) 2004-2025 3 | * 4 | * Gregory Montoir, Fabien Sanglard, Olivier Poncet 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 2 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | #ifndef __AW_INPUT_H__ 20 | #define __AW_INPUT_H__ 21 | 22 | #include "intern.h" 23 | 24 | // --------------------------------------------------------------------------- 25 | // Input 26 | // --------------------------------------------------------------------------- 27 | 28 | class Input final 29 | : public SubSystem 30 | { 31 | public: // public interface 32 | Input(Engine& engine); 33 | 34 | Input(Input&&) = delete; 35 | 36 | Input(const Input&) = delete; 37 | 38 | Input& operator=(Input&&) = delete; 39 | 40 | Input& operator=(const Input&) = delete; 41 | 42 | virtual ~Input() = default; 43 | 44 | virtual auto start() -> void override final; 45 | 46 | virtual auto reset() -> void override final; 47 | 48 | virtual auto stop() -> void override final; 49 | 50 | public: // public input interface 51 | auto getControls() -> Controls& 52 | { 53 | return _controls; 54 | } 55 | 56 | auto getControls() const -> const Controls& 57 | { 58 | return _controls; 59 | } 60 | 61 | private: // private data 62 | Controls _controls; 63 | }; 64 | 65 | // --------------------------------------------------------------------------- 66 | // End-Of-File 67 | // --------------------------------------------------------------------------- 68 | 69 | #endif /* __AW_INPUT_H__ */ 70 | -------------------------------------------------------------------------------- /src/bytekiller.h: -------------------------------------------------------------------------------- 1 | /* 2 | * bytekiller.h - Copyright (c) 2004-2025 3 | * 4 | * Gregory Montoir, Fabien Sanglard, Olivier Poncet 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 2 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | #ifndef __AW_BYTEKILLER_H__ 20 | #define __AW_BYTEKILLER_H__ 21 | 22 | // --------------------------------------------------------------------------- 23 | // ByteKiller 24 | // --------------------------------------------------------------------------- 25 | 26 | class ByteKiller 27 | { 28 | public: // public interface 29 | ByteKiller(uint8_t* buffer, uint32_t packedSize, uint32_t unpackedSize); 30 | 31 | ByteKiller(ByteKiller&&) = delete; 32 | 33 | ByteKiller(const ByteKiller&) = delete; 34 | 35 | ByteKiller& operator=(ByteKiller&&) = delete; 36 | 37 | ByteKiller& operator=(const ByteKiller&) = delete; 38 | 39 | ~ByteKiller() = default; 40 | 41 | auto unpack() -> bool; 42 | 43 | private: // private interface 44 | auto fetchLong() -> uint32_t; 45 | 46 | auto writeByte(uint8_t byte) -> void; 47 | 48 | auto getBit() -> uint32_t; 49 | 50 | auto getBits(uint32_t bits) -> uint32_t; 51 | 52 | auto unpackBytes(uint32_t count) -> void; 53 | 54 | auto unpackBytes(uint32_t offset, uint32_t count) -> void; 55 | 56 | private: // private data 57 | uint8_t* _buffer; 58 | uint8_t* _srcptr; 59 | uint8_t* _dstptr; 60 | int32_t _length; 61 | uint32_t _check; 62 | uint32_t _chunk; 63 | }; 64 | 65 | // --------------------------------------------------------------------------- 66 | // End-Of-File 67 | // --------------------------------------------------------------------------- 68 | 69 | #endif /* __AW_BYTEKILLER_H__ */ 70 | -------------------------------------------------------------------------------- /src/sound.h: -------------------------------------------------------------------------------- 1 | /* 2 | * sound.h - Copyright (c) 2004-2025 3 | * 4 | * Gregory Montoir, Fabien Sanglard, Olivier Poncet 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 2 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | #ifndef __AW_SOUND_H__ 20 | #define __AW_SOUND_H__ 21 | 22 | #include "intern.h" 23 | 24 | // --------------------------------------------------------------------------- 25 | // Sound 26 | // --------------------------------------------------------------------------- 27 | 28 | class Sound final 29 | : public SubSystem 30 | { 31 | public: // public interface 32 | Sound(Engine& engine, Audio& audio); 33 | 34 | Sound(Sound&&) = delete; 35 | 36 | Sound(const Sound&) = delete; 37 | 38 | Sound& operator=(Sound&&) = delete; 39 | 40 | Sound& operator=(const Sound&) = delete; 41 | 42 | virtual ~Sound() = default; 43 | 44 | virtual auto start() -> void override final; 45 | 46 | virtual auto reset() -> void override final; 47 | 48 | virtual auto stop() -> void override final; 49 | 50 | public: // public sound interface 51 | auto playSound(uint16_t sound_id, uint8_t channel, uint8_t volume, uint8_t pitch) -> void; 52 | 53 | auto stopSound() -> void; 54 | 55 | private: // private interface 56 | auto startTimer() -> void; 57 | 58 | auto resetTimer() -> void; 59 | 60 | auto stopTimer() -> void; 61 | 62 | auto processTimer() -> uint32_t; 63 | 64 | private: // private data 65 | Audio& _audio; 66 | AudioSample _samples[4]; 67 | int _timer; 68 | }; 69 | 70 | // --------------------------------------------------------------------------- 71 | // End-Of-File 72 | // --------------------------------------------------------------------------- 73 | 74 | #endif /* __AW_SOUND_H__ */ 75 | -------------------------------------------------------------------------------- /src/music.h: -------------------------------------------------------------------------------- 1 | /* 2 | * music.h - Copyright (c) 2004-2025 3 | * 4 | * Gregory Montoir, Fabien Sanglard, Olivier Poncet 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 2 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | #ifndef __AW_MUSIC_H__ 20 | #define __AW_MUSIC_H__ 21 | 22 | #include "intern.h" 23 | 24 | // --------------------------------------------------------------------------- 25 | // Music 26 | // --------------------------------------------------------------------------- 27 | 28 | class Music final 29 | : public SubSystem 30 | { 31 | public: // public interface 32 | Music(Engine& engine, Audio& audio); 33 | 34 | Music(Music&&) = delete; 35 | 36 | Music(const Music&) = delete; 37 | 38 | Music& operator=(Music&&) = delete; 39 | 40 | Music& operator=(const Music&) = delete; 41 | 42 | virtual ~Music() = default; 43 | 44 | virtual auto start() -> void override final; 45 | 46 | virtual auto reset() -> void override final; 47 | 48 | virtual auto stop() -> void override final; 49 | 50 | public: // public music interface 51 | auto playMusic(uint16_t music_id, uint8_t index, uint16_t ticks) -> void; 52 | 53 | auto stopMusic() -> void; 54 | 55 | private: // private interface 56 | auto startTimer() -> void; 57 | 58 | auto resetTimer() -> void; 59 | 60 | auto stopTimer() -> void; 61 | 62 | auto processTimer() -> uint32_t; 63 | 64 | auto playMusicUnlocked(uint16_t id, uint8_t index, uint16_t ticks) -> void; 65 | 66 | auto stopMusicUnlocked() -> void; 67 | 68 | auto processPattern(uint8_t channel, Data& data) -> void; 69 | 70 | private: // private data 71 | Audio& _audio; 72 | MusicModule _module; 73 | int _timer; 74 | }; 75 | 76 | // --------------------------------------------------------------------------- 77 | // End-Of-File 78 | // --------------------------------------------------------------------------- 79 | 80 | #endif /* __AW_MUSIC_H__ */ 81 | -------------------------------------------------------------------------------- /src/mixer.h: -------------------------------------------------------------------------------- 1 | /* 2 | * mixer.h - Copyright (c) 2004-2025 3 | * 4 | * Gregory Montoir, Fabien Sanglard, Olivier Poncet 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 2 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | #ifndef __AW_MIXER_H__ 20 | #define __AW_MIXER_H__ 21 | 22 | #include "intern.h" 23 | 24 | // --------------------------------------------------------------------------- 25 | // Mixer 26 | // --------------------------------------------------------------------------- 27 | 28 | class Mixer final 29 | : public SubSystem 30 | { 31 | public: // public interface 32 | Mixer(Engine& engine, Audio& audio); 33 | 34 | Mixer(Mixer&&) = delete; 35 | 36 | Mixer(const Mixer&) = delete; 37 | 38 | Mixer& operator=(Mixer&&) = delete; 39 | 40 | Mixer& operator=(const Mixer&) = delete; 41 | 42 | virtual ~Mixer() = default; 43 | 44 | virtual auto start() -> void override final; 45 | 46 | virtual auto reset() -> void override final; 47 | 48 | virtual auto stop() -> void override final; 49 | 50 | public: // public mixer interface 51 | auto playAllChannels() -> void; 52 | 53 | auto stopAllChannels() -> void; 54 | 55 | auto playChannel(uint8_t channel, const AudioSample& sample) -> void; 56 | 57 | auto stopChannel(uint8_t channel) -> void; 58 | 59 | auto setChannelVolume(uint8_t channel, uint8_t volume) -> void; 60 | 61 | private: // private interface 62 | auto startAudio() -> void; 63 | 64 | auto resetAudio() -> void; 65 | 66 | auto stopAudio() -> void; 67 | 68 | auto processAudio(float* buffer, int length) -> void; 69 | 70 | private: // private data 71 | Audio& _audio; 72 | AudioChannel _channels[4]; 73 | uint32_t _samplerate; 74 | }; 75 | 76 | // --------------------------------------------------------------------------- 77 | // End-Of-File 78 | // --------------------------------------------------------------------------- 79 | 80 | #endif /* __AW_MIXER_H__ */ 81 | -------------------------------------------------------------------------------- /src/backend.h: -------------------------------------------------------------------------------- 1 | /* 2 | * backend.h - Copyright (c) 2004-2025 3 | * 4 | * Gregory Montoir, Fabien Sanglard, Olivier Poncet 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 2 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | #ifndef __AW_BACKEND_H__ 20 | #define __AW_BACKEND_H__ 21 | 22 | #include "intern.h" 23 | 24 | // --------------------------------------------------------------------------- 25 | // Backend 26 | // --------------------------------------------------------------------------- 27 | 28 | class Backend 29 | : public SubSystem 30 | { 31 | public: // public interface 32 | Backend(Engine& engine); 33 | 34 | Backend(Backend&&) = delete; 35 | 36 | Backend(const Backend&) = delete; 37 | 38 | Backend& operator=(Backend&&) = delete; 39 | 40 | Backend& operator=(const Backend&) = delete; 41 | 42 | virtual ~Backend() = default; 43 | 44 | public: // public backend interface 45 | static auto create(Engine& engine) -> Backend*; 46 | 47 | virtual auto getTicks() -> uint32_t = 0; 48 | 49 | virtual auto sleepFor(uint32_t delay) -> void = 0; 50 | 51 | virtual auto sleepUntil(uint32_t ticks) -> void = 0; 52 | 53 | virtual auto processEvents(Controls& controls) -> void = 0; 54 | 55 | virtual auto updateScreen(const Page& page, const Page& page0, const Page& page1, const Page& page2, const Page& page3, const Palette& palette) -> void = 0; 56 | 57 | virtual auto startAudio(AudioCallback callback, void* userdata) -> void = 0; 58 | 59 | virtual auto stopAudio() -> void = 0; 60 | 61 | virtual auto getAudioSampleRate() -> uint32_t = 0; 62 | 63 | virtual auto addTimer(uint32_t delay, TimerCallback callback, void* data) -> int = 0; 64 | 65 | virtual auto removeTimer(int timerId) -> void = 0; 66 | }; 67 | 68 | // --------------------------------------------------------------------------- 69 | // End-Of-File 70 | // --------------------------------------------------------------------------- 71 | 72 | #endif /* __AW_BACKEND_H__ */ 73 | -------------------------------------------------------------------------------- /src/input.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * input.cc - Copyright (c) 2004-2025 3 | * 4 | * Gregory Montoir, Fabien Sanglard, Olivier Poncet 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 2 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | #ifdef HAVE_CONFIG_H 20 | #include "config.h" 21 | #endif 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include "logger.h" 41 | #include "engine.h" 42 | #include "input.h" 43 | 44 | // --------------------------------------------------------------------------- 45 | // some useful macros 46 | // --------------------------------------------------------------------------- 47 | 48 | #ifndef NDEBUG 49 | #define LOG_DEBUG(format, ...) log_debug(SYS_INPUT, format, ##__VA_ARGS__) 50 | #else 51 | #define LOG_DEBUG(format, ...) do {} while(0) 52 | #endif 53 | 54 | // --------------------------------------------------------------------------- 55 | // Input 56 | // --------------------------------------------------------------------------- 57 | 58 | Input::Input(Engine& engine) 59 | : SubSystem(engine, "Input") 60 | , _controls() 61 | { 62 | } 63 | 64 | auto Input::start() -> void 65 | { 66 | LOG_DEBUG("starting..."); 67 | LOG_DEBUG("started!"); 68 | } 69 | 70 | auto Input::reset() -> void 71 | { 72 | LOG_DEBUG("resetting..."); 73 | _controls = Controls(); 74 | LOG_DEBUG("reset!"); 75 | } 76 | 77 | auto Input::stop() -> void 78 | { 79 | LOG_DEBUG("stopping..."); 80 | LOG_DEBUG("stopped!"); 81 | } 82 | 83 | // --------------------------------------------------------------------------- 84 | // End-Of-File 85 | // --------------------------------------------------------------------------- 86 | -------------------------------------------------------------------------------- /src/audio.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * audio.cc - Copyright (c) 2004-2025 3 | * 4 | * Gregory Montoir, Fabien Sanglard, Olivier Poncet 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 2 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | #ifdef HAVE_CONFIG_H 20 | #include "config.h" 21 | #endif 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include "logger.h" 41 | #include "engine.h" 42 | #include "audio.h" 43 | 44 | // --------------------------------------------------------------------------- 45 | // some useful macros 46 | // --------------------------------------------------------------------------- 47 | 48 | #ifndef NDEBUG 49 | #define LOG_DEBUG(format, ...) log_debug(SYS_AUDIO, format, ##__VA_ARGS__) 50 | #else 51 | #define LOG_DEBUG(format, ...) do {} while(0) 52 | #endif 53 | 54 | // --------------------------------------------------------------------------- 55 | // Audio 56 | // --------------------------------------------------------------------------- 57 | 58 | Audio::Audio(Engine& engine) 59 | : SubSystem(engine, "Audio") 60 | , _mixer(engine, *this) 61 | , _sound(engine, *this) 62 | , _music(engine, *this) 63 | { 64 | } 65 | 66 | auto Audio::start() -> void 67 | { 68 | LOG_DEBUG("starting..."); 69 | _mixer.start(); 70 | _sound.start(); 71 | _music.start(); 72 | LOG_DEBUG("started!"); 73 | } 74 | 75 | auto Audio::reset() -> void 76 | { 77 | LOG_DEBUG("resetting..."); 78 | _mixer.reset(); 79 | _sound.reset(); 80 | _music.reset(); 81 | LOG_DEBUG("reset!"); 82 | } 83 | 84 | auto Audio::stop() -> void 85 | { 86 | LOG_DEBUG("stopping..."); 87 | _music.stop(); 88 | _sound.stop(); 89 | _mixer.stop(); 90 | LOG_DEBUG("stopped!"); 91 | } 92 | 93 | // --------------------------------------------------------------------------- 94 | // End-Of-File 95 | // --------------------------------------------------------------------------- 96 | -------------------------------------------------------------------------------- /src/logger.h: -------------------------------------------------------------------------------- 1 | /* 2 | * logger.h - Copyright (c) 2004-2025 3 | * 4 | * Gregory Montoir, Fabien Sanglard, Olivier Poncet 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 2 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | #ifndef __AW_LOGGER_H__ 20 | #define __AW_LOGGER_H__ 21 | 22 | // --------------------------------------------------------------------------- 23 | // Panic 24 | // --------------------------------------------------------------------------- 25 | 26 | class Panic 27 | { 28 | public: // public interface 29 | Panic() = default; 30 | 31 | virtual ~Panic() = default; 32 | 33 | virtual auto operator()() const -> void; 34 | }; 35 | 36 | // --------------------------------------------------------------------------- 37 | // debug masks 38 | // --------------------------------------------------------------------------- 39 | 40 | enum 41 | { 42 | LOG_DEBUG = (1u << 0), 43 | LOG_PRINT = (1u << 1), 44 | LOG_ALERT = (1u << 2), 45 | LOG_ERROR = (1u << 3), 46 | LOG_FATAL = (1u << 4), 47 | SYS_ENGINE = (1u << 5), 48 | SYS_BACKEND = (1u << 6), 49 | SYS_RESOURCES = (1u << 7), 50 | SYS_VIDEO = (1u << 8), 51 | SYS_AUDIO = (1u << 9), 52 | SYS_MIXER = (1u << 10), 53 | SYS_SOUND = (1u << 11), 54 | SYS_MUSIC = (1u << 12), 55 | SYS_INPUT = (1u << 13), 56 | SYS_VM = (1u << 14), 57 | }; 58 | 59 | // --------------------------------------------------------------------------- 60 | // debug mask 61 | // --------------------------------------------------------------------------- 62 | 63 | extern FILE* g_logger_out; 64 | extern FILE* g_logger_err; 65 | extern uint32_t g_logger_mask; 66 | 67 | // --------------------------------------------------------------------------- 68 | // logging functions 69 | // --------------------------------------------------------------------------- 70 | 71 | extern auto log_debug(uint32_t mask, const char* format, ...) -> void; 72 | 73 | extern auto log_debug(const char* format, ...) -> void; 74 | 75 | extern auto log_print(const char* format, ...) -> void; 76 | 77 | extern auto log_alert(const char* format, ...) -> void; 78 | 79 | extern auto log_error(const char* format, ...) -> void; 80 | 81 | extern auto log_fatal(const char* format, ...) -> void; 82 | 83 | // --------------------------------------------------------------------------- 84 | // End-Of-File 85 | // --------------------------------------------------------------------------- 86 | 87 | #endif /* __AW_LOGGER_H__ */ 88 | -------------------------------------------------------------------------------- /src/audio.h: -------------------------------------------------------------------------------- 1 | /* 2 | * audio.h - Copyright (c) 2004-2025 3 | * 4 | * Gregory Montoir, Fabien Sanglard, Olivier Poncet 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 2 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | #ifndef __AW_AUDIO_H__ 20 | #define __AW_AUDIO_H__ 21 | 22 | #include "intern.h" 23 | #include "mixer.h" 24 | #include "sound.h" 25 | #include "music.h" 26 | 27 | // --------------------------------------------------------------------------- 28 | // Audio 29 | // --------------------------------------------------------------------------- 30 | 31 | class Audio final 32 | : public SubSystem 33 | { 34 | public: // public interface 35 | Audio(Engine& engine); 36 | 37 | Audio(Audio&&) = delete; 38 | 39 | Audio(const Audio&) = delete; 40 | 41 | Audio& operator=(Audio&&) = delete; 42 | 43 | Audio& operator=(const Audio&) = delete; 44 | 45 | virtual ~Audio() = default; 46 | 47 | virtual auto start() -> void override final; 48 | 49 | virtual auto reset() -> void override final; 50 | 51 | virtual auto stop() -> void override final; 52 | 53 | public: // public audio interface 54 | auto playAllChannels() -> void 55 | { 56 | return _mixer.playAllChannels(); 57 | } 58 | 59 | auto stopAllChannels() -> void 60 | { 61 | return _mixer.stopAllChannels(); 62 | } 63 | 64 | auto playChannel(uint8_t channel, const AudioSample& sample) -> void 65 | { 66 | return _mixer.playChannel(channel, sample); 67 | } 68 | 69 | auto stopChannel(uint8_t channel) -> void 70 | { 71 | return _mixer.stopChannel(channel); 72 | } 73 | 74 | auto setChannelVolume(uint8_t channel, uint8_t volume) -> void 75 | { 76 | return _mixer.setChannelVolume(channel, volume); 77 | } 78 | 79 | auto playSound(uint16_t id, uint8_t channel, uint8_t volume, uint8_t frequency) -> void 80 | { 81 | return _sound.playSound(id, channel, volume, frequency); 82 | } 83 | 84 | auto playMusic(uint16_t id, uint8_t position, uint16_t delay) -> void 85 | { 86 | return _music.playMusic(id, position, delay); 87 | } 88 | 89 | private: // private data 90 | Mixer _mixer; 91 | Sound _sound; 92 | Music _music; 93 | }; 94 | 95 | // --------------------------------------------------------------------------- 96 | // End-Of-File 97 | // --------------------------------------------------------------------------- 98 | 99 | #endif /* __AW_AUDIO_H__ */ 100 | -------------------------------------------------------------------------------- /src/config.h: -------------------------------------------------------------------------------- 1 | /* 2 | * config.h - Copyright (c) 2004-2025 3 | * 4 | * Gregory Montoir, Fabien Sanglard, Olivier Poncet 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 2 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | #ifndef __AW_CONFIG_H__ 20 | #define __AW_CONFIG_H__ 21 | 22 | // --------------------------------------------------------------------------- 23 | // force no debug when compiling with emscripten 24 | // --------------------------------------------------------------------------- 25 | 26 | #ifdef __EMSCRIPTEN__ 27 | #ifndef NDEBUG 28 | #define NDEBUG 29 | #endif 30 | #endif 31 | 32 | // --------------------------------------------------------------------------- 33 | // force no debug 34 | // --------------------------------------------------------------------------- 35 | 36 | #if 0 37 | #ifndef NDEBUG 38 | #define NDEBUG 39 | #endif 40 | #endif 41 | 42 | // --------------------------------------------------------------------------- 43 | // Another World / Out Of This World 44 | // --------------------------------------------------------------------------- 45 | 46 | #if 0 47 | #ifndef OUT_OF_THIS_WORLD 48 | #define OUT_OF_THIS_WORLD 49 | #endif 50 | #endif 51 | 52 | // --------------------------------------------------------------------------- 53 | // skip protection screen 54 | // --------------------------------------------------------------------------- 55 | 56 | #if 1 57 | #ifndef SKIP_GAME_PART0 58 | #define SKIP_GAME_PART0 59 | #endif 60 | #endif 61 | 62 | // --------------------------------------------------------------------------- 63 | // bypass protection 64 | // --------------------------------------------------------------------------- 65 | 66 | #if 1 67 | #ifndef BYPASS_PROTECTION 68 | #define BYPASS_PROTECTION 69 | #endif 70 | #endif 71 | 72 | // --------------------------------------------------------------------------- 73 | // preload resources 74 | // --------------------------------------------------------------------------- 75 | 76 | #if 1 77 | #ifndef PRELOAD_RESOURCES 78 | #define PRELOAD_RESOURCES 79 | #endif 80 | #endif 81 | 82 | // --------------------------------------------------------------------------- 83 | // audio sample rate 84 | // --------------------------------------------------------------------------- 85 | 86 | #if 1 87 | #ifndef AUDIO_SAMPLE_RATE 88 | #define AUDIO_SAMPLE_RATE 44100 89 | #endif 90 | #endif 91 | 92 | // --------------------------------------------------------------------------- 93 | // End-Of-File 94 | // --------------------------------------------------------------------------- 95 | 96 | #endif /* __AW_CONFIG_H__ */ 97 | -------------------------------------------------------------------------------- /src/file.h: -------------------------------------------------------------------------------- 1 | /* 2 | * file.h - Copyright (c) 2004-2025 3 | * 4 | * Gregory Montoir, Fabien Sanglard, Olivier Poncet 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 2 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | #ifndef __AW_FILE_H__ 20 | #define __AW_FILE_H__ 21 | 22 | // --------------------------------------------------------------------------- 23 | // forward declarations 24 | // --------------------------------------------------------------------------- 25 | 26 | class File; 27 | class FileImpl; 28 | class FileNullImpl; 29 | class FileStdioImpl; 30 | class FileZlibImpl; 31 | 32 | // --------------------------------------------------------------------------- 33 | // File 34 | // --------------------------------------------------------------------------- 35 | 36 | class File 37 | { 38 | public: // public interface 39 | File(const std::string& impl); 40 | 41 | File(File&&) = delete; 42 | 43 | File(const File&) = delete; 44 | 45 | File& operator=(File&&) = delete; 46 | 47 | File& operator=(const File&) = delete; 48 | 49 | virtual ~File() = default; 50 | 51 | auto open(const std::string& path, const std::string& mode) -> bool; 52 | 53 | auto close() -> bool; 54 | 55 | auto seek(int32_t offset) -> bool; 56 | 57 | auto read(void* buffer, uint32_t length) -> bool; 58 | 59 | auto write(void* buffer, uint32_t length) -> bool; 60 | 61 | auto ioOk() const -> bool; 62 | 63 | auto ioErr() const -> bool; 64 | 65 | private: // private data 66 | std::unique_ptr _impl; 67 | }; 68 | 69 | // --------------------------------------------------------------------------- 70 | // FileImpl 71 | // --------------------------------------------------------------------------- 72 | 73 | class FileImpl 74 | { 75 | public: // public interface 76 | FileImpl() = default; 77 | 78 | FileImpl(FileImpl&&) = delete; 79 | 80 | FileImpl(const FileImpl&) = delete; 81 | 82 | FileImpl& operator=(FileImpl&&) = delete; 83 | 84 | FileImpl& operator=(const FileImpl&) = delete; 85 | 86 | virtual ~FileImpl() = default; 87 | 88 | virtual auto open(const std::string& path, const std::string& mode) -> bool = 0; 89 | 90 | virtual auto close()-> bool = 0; 91 | 92 | virtual auto seek(int32_t offset) -> bool = 0; 93 | 94 | virtual auto read(void* buffer, uint32_t length) -> bool = 0; 95 | 96 | virtual auto write(void* buffer, uint32_t length) -> bool = 0; 97 | 98 | auto ioOk() const -> bool 99 | { 100 | return _fail == false; 101 | } 102 | 103 | auto ioErr() const -> bool 104 | { 105 | return _fail != false; 106 | } 107 | 108 | protected: // protected data 109 | bool _fail = false; 110 | }; 111 | 112 | // --------------------------------------------------------------------------- 113 | // End-Of-File 114 | // --------------------------------------------------------------------------- 115 | 116 | #endif /* __AW_FILE_H__ */ 117 | -------------------------------------------------------------------------------- /src/video.h: -------------------------------------------------------------------------------- 1 | /* 2 | * video.h - Copyright (c) 2004-2025 3 | * 4 | * Gregory Montoir, Fabien Sanglard, Olivier Poncet 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 2 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | #ifndef __AW_VIDEO_H__ 20 | #define __AW_VIDEO_H__ 21 | 22 | #include "intern.h" 23 | 24 | // --------------------------------------------------------------------------- 25 | // Video 26 | // --------------------------------------------------------------------------- 27 | 28 | class Video final 29 | : public SubSystem 30 | { 31 | public: // public interface 32 | Video(Engine& engine); 33 | 34 | Video(Video&&) = delete; 35 | 36 | Video(const Video&) = delete; 37 | 38 | Video& operator=(Video&&) = delete; 39 | 40 | Video& operator=(const Video&) = delete; 41 | 42 | virtual ~Video() = default; 43 | 44 | virtual auto start() -> void override final; 45 | 46 | virtual auto reset() -> void override final; 47 | 48 | virtual auto stop() -> void override final; 49 | 50 | public: // public video interface 51 | auto setPalettes(const uint8_t* palettes, uint8_t mode) -> void; 52 | 53 | auto selectPalette(uint8_t palette) -> void; 54 | 55 | auto selectPage(uint8_t dst) -> void; 56 | 57 | auto fillPage(uint8_t dst, uint8_t col) -> void; 58 | 59 | auto copyPage(uint8_t dst, uint8_t src, int16_t vscroll) -> void; 60 | 61 | auto showPage(uint8_t src) -> void; 62 | 63 | auto drawBitmap(const uint8_t* buffer) -> void; 64 | 65 | auto drawString(uint16_t id, uint16_t x, uint16_t y, uint8_t color) -> void; 66 | 67 | auto drawPolygons(const uint8_t* buffer, uint16_t offset, const Point& position, uint16_t zoom) -> void; 68 | 69 | private: // private interface 70 | static constexpr uint8_t VIDEO_PAGE0 = 0x00; 71 | static constexpr uint8_t VIDEO_PAGE1 = 0x01; 72 | static constexpr uint8_t VIDEO_PAGE2 = 0x02; 73 | static constexpr uint8_t VIDEO_PAGE3 = 0x03; 74 | static constexpr uint8_t VIDEO_PAGEV = 0xfe; 75 | static constexpr uint8_t VIDEO_PAGEI = 0xff; 76 | 77 | static constexpr int16_t PAGE_W = 320; // page width 78 | static constexpr int16_t PAGE_H = 200; // page height 79 | static constexpr int16_t XMIN = 0; // x-min 80 | static constexpr int16_t YMIN = 0; // y-min 81 | static constexpr int16_t XMAX = 319; // x-max 82 | static constexpr int16_t YMAX = 199; // y-max 83 | static constexpr int16_t BPP = 4; // bits per pixel 84 | static constexpr int16_t PPB = 2; // pixels per byte 85 | static constexpr int16_t BPL = 160; // bytes per line 86 | 87 | auto getPage(uint8_t page) -> Page*; 88 | 89 | auto renderString(const char* string, uint16_t x, uint16_t y, uint8_t color) -> void; 90 | 91 | auto renderPolygon(const Polygon& polygon, const Point& position, uint16_t zoom, uint16_t color) -> void; 92 | 93 | auto renderPolygons(const uint8_t* buffer, uint32_t offset, const Point& position, uint16_t zoom, uint8_t color) -> void; 94 | 95 | private: // private data 96 | Page _pages[4]; 97 | Palette _palettes[32]; 98 | Page* _page0; 99 | Page* _page1; 100 | Page* _page2; 101 | Palette* _palette; 102 | Polygon _polygon; 103 | uint16_t _interpolate[0x400]; 104 | }; 105 | 106 | // --------------------------------------------------------------------------- 107 | // End-Of-File 108 | // --------------------------------------------------------------------------- 109 | 110 | #endif /* __AW_VIDEO_H__ */ 111 | -------------------------------------------------------------------------------- /src/resources.h: -------------------------------------------------------------------------------- 1 | /* 2 | * resources.h - Copyright (c) 2004-2025 3 | * 4 | * Gregory Montoir, Fabien Sanglard, Olivier Poncet 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 2 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | #ifndef __AW_RESOURCES_H__ 20 | #define __AW_RESOURCES_H__ 21 | 22 | #include "intern.h" 23 | #include "data.h" 24 | 25 | // --------------------------------------------------------------------------- 26 | // Resources 27 | // --------------------------------------------------------------------------- 28 | 29 | class Resources final 30 | : public SubSystem 31 | { 32 | public: // public interface 33 | Resources(Engine& engine); 34 | 35 | Resources(Resources&&) = delete; 36 | 37 | Resources(const Resources&) = delete; 38 | 39 | Resources& operator=(Resources&&) = delete; 40 | 41 | Resources& operator=(const Resources&) = delete; 42 | 43 | virtual ~Resources() = default; 44 | 45 | virtual auto start() -> void override final; 46 | 47 | virtual auto reset() -> void override final; 48 | 49 | virtual auto stop() -> void override final; 50 | 51 | public: // public resources interface 52 | auto loadPart(uint16_t partId) -> void; 53 | 54 | auto loadResource(uint16_t resourceId) -> void; 55 | 56 | auto getString(uint16_t stringId) -> const String*; 57 | 58 | auto getResource(uint16_t resourceId) -> Resource* 59 | { 60 | if(resourceId < _resourcesCount) { 61 | return &_resourcesArray[resourceId]; 62 | } 63 | return nullptr; 64 | } 65 | 66 | auto getCurPartId() const -> uint16_t 67 | { 68 | return _curPartId; 69 | } 70 | 71 | auto getReqPartId() const -> uint16_t 72 | { 73 | return _reqPartId; 74 | } 75 | 76 | auto requestPartId(uint16_t partId) -> void 77 | { 78 | _reqPartId = partId; 79 | } 80 | 81 | auto getPalettesData() const -> const uint8_t* 82 | { 83 | return _segPalettes; 84 | } 85 | 86 | auto getByteCodeData() const -> const uint8_t* 87 | { 88 | return _segByteCode; 89 | } 90 | 91 | auto getPolygon1Data() const -> const uint8_t* 92 | { 93 | return _segPolygon1; 94 | } 95 | 96 | auto getPolygon2Data() const -> const uint8_t* 97 | { 98 | return _segPolygon2; 99 | } 100 | 101 | auto getPolygonData(int index) -> const uint8_t* 102 | { 103 | switch(index) { 104 | case 1: 105 | return _segPolygon1; 106 | case 2: 107 | return _segPolygon2; 108 | default: 109 | break; 110 | } 111 | return nullptr; 112 | } 113 | 114 | private: // private interface 115 | static constexpr uint32_t BLOCK_COUNT = 1792; 116 | static constexpr uint32_t BLOCK_SIZE = 1024; 117 | static constexpr uint32_t TOTAL_SIZE = (BLOCK_COUNT * BLOCK_SIZE); 118 | 119 | auto loadMemList() -> void; 120 | 121 | auto loadResources() -> void; 122 | 123 | auto invalidateAll() -> void; 124 | 125 | private: // private data 126 | uint8_t _buffer[TOTAL_SIZE]; 127 | uint8_t* _bufptr; 128 | uint8_t* _bufend; 129 | uint8_t _dictionaryId; 130 | Resource _resourcesArray[256]; 131 | uint16_t _resourcesCount; 132 | uint16_t _curPartId; 133 | uint16_t _reqPartId; 134 | uint8_t* _segPalettes; 135 | uint8_t* _segByteCode; 136 | uint8_t* _segPolygon1; 137 | uint8_t* _segPolygon2; 138 | }; 139 | 140 | // --------------------------------------------------------------------------- 141 | // End-Of-File 142 | // --------------------------------------------------------------------------- 143 | 144 | #endif /* __AW_RESOURCES_H__ */ 145 | -------------------------------------------------------------------------------- /Makefile.linux: -------------------------------------------------------------------------------- 1 | # 2 | # Makefile.linux - Copyright (c) 2024-2025 - Olivier Poncet 3 | # 4 | # This program is free software: you can redistribute it and/or modify 5 | # it under the terms of the GNU General Public License as published by 6 | # the Free Software Foundation, either version 2 of the License, or 7 | # (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with this program. If not, see . 16 | # 17 | 18 | # ---------------------------------------------------------------------------- 19 | # global environment 20 | # ---------------------------------------------------------------------------- 21 | 22 | TOPDIR = $(CURDIR) 23 | OPTLEVEL = -O2 -g 24 | WARNINGS = -Wall 25 | EXTRAS = -pthread -fstack-protector-strong 26 | CC = gcc 27 | CFLAGS = -std=c99 $(OPTLEVEL) $(WARNINGS) $(EXTRAS) 28 | CXX = g++ 29 | CXXFLAGS = -std=c++14 $(OPTLEVEL) $(WARNINGS) $(EXTRAS) 30 | CPP = cpp 31 | CPPFLAGS = -I. -I$(TOPDIR)/src -D_DEFAULT_SOURCE -D_FORTIFY_SOURCE=2 -DHAVE_CONFIG_H 32 | LD = g++ 33 | LDFLAGS = -O2 -L. 34 | CP = cp 35 | CPFLAGS = -f 36 | RM = rm 37 | RMFLAGS = -f 38 | 39 | # ---------------------------------------------------------------------------- 40 | # default rules 41 | # ---------------------------------------------------------------------------- 42 | 43 | .c.o: 44 | $(CC) -c $(CFLAGS) $(CPPFLAGS) -o $@ $< 45 | 46 | .cc.o: 47 | $(CXX) -c $(CXXFLAGS) $(CPPFLAGS) -o $@ $< 48 | 49 | # ---------------------------------------------------------------------------- 50 | # global targets 51 | # ---------------------------------------------------------------------------- 52 | 53 | all: build 54 | 55 | build: build_another_world 56 | @echo "=== $@ ok ===" 57 | 58 | clean: clean_another_world 59 | @echo "=== $@ ok ===" 60 | 61 | # ---------------------------------------------------------------------------- 62 | # Another World 63 | # ---------------------------------------------------------------------------- 64 | 65 | another_world_PROGRAM = bin/another-world.bin 66 | 67 | another_world_SOURCES = \ 68 | src/main.cc \ 69 | src/logger.cc \ 70 | src/bytekiller.cc \ 71 | src/file.cc \ 72 | src/intern.cc \ 73 | src/data.cc \ 74 | src/engine.cc \ 75 | src/backend.cc \ 76 | src/resources.cc \ 77 | src/video.cc \ 78 | src/audio.cc \ 79 | src/mixer.cc \ 80 | src/sound.cc \ 81 | src/music.cc \ 82 | src/input.cc \ 83 | src/vm.cc \ 84 | $(NULL) 85 | 86 | another_world_HEADERS = \ 87 | src/main.h \ 88 | src/logger.h \ 89 | src/bytekiller.h \ 90 | src/file.h \ 91 | src/intern.h \ 92 | src/data.h \ 93 | src/engine.h \ 94 | src/backend.h \ 95 | src/resources.h \ 96 | src/video.h \ 97 | src/audio.h \ 98 | src/mixer.h \ 99 | src/sound.h \ 100 | src/music.h \ 101 | src/input.h \ 102 | src/vm.h \ 103 | $(NULL) 104 | 105 | another_world_OBJECTS = \ 106 | src/main.o \ 107 | src/logger.o \ 108 | src/bytekiller.o \ 109 | src/file.o \ 110 | src/intern.o \ 111 | src/data.o \ 112 | src/engine.o \ 113 | src/backend.o \ 114 | src/resources.o \ 115 | src/video.o \ 116 | src/audio.o \ 117 | src/mixer.o \ 118 | src/sound.o \ 119 | src/music.o \ 120 | src/input.o \ 121 | src/vm.o \ 122 | $(NULL) 123 | 124 | another_world_LDFLAGS = \ 125 | $(NULL) 126 | 127 | another_world_LDADD = \ 128 | -lSDL2 \ 129 | -lz \ 130 | $(NULL) 131 | 132 | another_world_CLEANFILES = \ 133 | bin/another-world.bin \ 134 | bin/another-world.data \ 135 | bin/another-world.html \ 136 | bin/another-world.wasm \ 137 | bin/another-world.js \ 138 | $(NULL) 139 | 140 | # ---------------------------------------------------------------------------- 141 | # build Another World 142 | # ---------------------------------------------------------------------------- 143 | 144 | build_another_world: $(another_world_PROGRAM) 145 | 146 | $(another_world_PROGRAM): $(another_world_OBJECTS) 147 | $(LD) $(LDFLAGS) $(another_world_LDFLAGS) -o $(another_world_PROGRAM) $(another_world_OBJECTS) $(another_world_LDADD) 148 | 149 | # ---------------------------------------------------------------------------- 150 | # clean Another World 151 | # ---------------------------------------------------------------------------- 152 | 153 | clean_another_world: 154 | $(RM) $(RMFLAGS) $(another_world_OBJECTS) $(another_world_PROGRAM) $(another_world_CLEANFILES) 155 | 156 | # ---------------------------------------------------------------------------- 157 | # End-Of-File 158 | # ---------------------------------------------------------------------------- 159 | -------------------------------------------------------------------------------- /src/data.h: -------------------------------------------------------------------------------- 1 | /* 2 | * data.h - Copyright (c) 2004-2025 3 | * 4 | * Gregory Montoir, Fabien Sanglard, Olivier Poncet 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 2 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | #ifndef __AW_DATA_H__ 20 | #define __AW_DATA_H__ 21 | 22 | // --------------------------------------------------------------------------- 23 | // String 24 | // --------------------------------------------------------------------------- 25 | 26 | struct String 27 | { 28 | uint16_t id = 0xffff; 29 | const char* value = nullptr; 30 | }; 31 | 32 | // --------------------------------------------------------------------------- 33 | // Dictionary 34 | // --------------------------------------------------------------------------- 35 | 36 | struct Dictionary 37 | { 38 | enum 39 | { 40 | DIC_DEFAULT = 0, 41 | DIC_ENGLISH = 1, 42 | DIC_FRENCH = 2, 43 | }; 44 | 45 | static const String dataEN[141]; 46 | static const String dataFR[141]; 47 | }; 48 | 49 | // --------------------------------------------------------------------------- 50 | // ResourceState 51 | // --------------------------------------------------------------------------- 52 | 53 | enum ResourceState 54 | { 55 | RS_NOT_NEEDED = 0x00, 56 | RS_NEEDED = 0x01, 57 | RS_LOADED = 0x02, 58 | RS_END = 0xff, 59 | }; 60 | 61 | // --------------------------------------------------------------------------- 62 | // ResourceType 63 | // --------------------------------------------------------------------------- 64 | 65 | enum ResourceType 66 | { 67 | RT_SOUND = 0x00, 68 | RT_MUSIC = 0x01, 69 | RT_BITMAP = 0x02, 70 | RT_PALETTE = 0x03, 71 | RT_BYTECODE = 0x04, 72 | RT_POLYGON1 = 0x05, 73 | RT_POLYGON2 = 0x06, 74 | RT_END = 0xff, 75 | }; 76 | 77 | // --------------------------------------------------------------------------- 78 | // Resource 79 | // --------------------------------------------------------------------------- 80 | 81 | struct Resource 82 | { 83 | uint16_t id = 0; 84 | uint8_t state = 0xff; // offset 0x00: state 85 | uint8_t type = 0xff; // offset 0x01: type 86 | uint16_t unused1 = 0; // offset 0x02: unused 87 | uint16_t unused2 = 0; // offset 0x04: unused 88 | uint8_t unused3 = 0; // offset 0x06: unused 89 | uint8_t bank_id = 0; // offset 0x07: bank id 90 | uint32_t bank_offset = 0; // offset 0x08: bank offset 91 | uint16_t unused4 = 0; // offset 0x0c: unused 92 | uint16_t packed_size = 0; // offset 0x0e: packed size 93 | uint16_t unused5 = 0; // offset 0x10: unused 94 | uint16_t unpacked_size = 0; // offset 0x12: unpacked size 95 | uint8_t* data = nullptr; 96 | }; 97 | 98 | // --------------------------------------------------------------------------- 99 | // ResourceStats 100 | // --------------------------------------------------------------------------- 101 | 102 | struct ResourceStats 103 | { 104 | uint32_t count = 0; 105 | uint32_t packed = 0; 106 | uint32_t unpacked = 0; 107 | }; 108 | 109 | // --------------------------------------------------------------------------- 110 | // MemList 111 | // --------------------------------------------------------------------------- 112 | 113 | class MemList 114 | { 115 | public: // public interface 116 | MemList(const std::string& dataDir, const std::string& dumpDir); 117 | 118 | MemList(MemList&&) = delete; 119 | 120 | MemList(const MemList&) = delete; 121 | 122 | MemList& operator=(MemList&&) = delete; 123 | 124 | MemList& operator=(const MemList&) = delete; 125 | 126 | virtual ~MemList() = default; 127 | 128 | auto loadMemList(Resource* resources) -> bool; 129 | 130 | auto dumpMemList(Resource* resources) -> bool; 131 | 132 | auto loadResource(Resource& resource) -> bool; 133 | 134 | auto dumpResource(const Resource& resource) -> void; 135 | 136 | private: // private data 137 | const std::string _dataDir; 138 | const std::string _dumpDir; 139 | }; 140 | 141 | // --------------------------------------------------------------------------- 142 | // End-Of-File 143 | // --------------------------------------------------------------------------- 144 | 145 | #endif /* __AW_DATA_H__ */ 146 | -------------------------------------------------------------------------------- /Makefile.wasm: -------------------------------------------------------------------------------- 1 | # 2 | # Makefile.wasm - Copyright (c) 2024-2025 - Olivier Poncet 3 | # 4 | # This program is free software: you can redistribute it and/or modify 5 | # it under the terms of the GNU General Public License as published by 6 | # the Free Software Foundation, either version 2 of the License, or 7 | # (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with this program. If not, see . 16 | # 17 | 18 | # ---------------------------------------------------------------------------- 19 | # global environment 20 | # ---------------------------------------------------------------------------- 21 | 22 | TOPDIR = $(CURDIR) 23 | OPTLEVEL = -O2 24 | WARNINGS = -Wall 25 | EXTRAS = -sUSE_SDL=2 -sUSE_SDL_IMAGE=2 -sUSE_ZLIB=1 26 | CC = emcc 27 | CFLAGS = -std=c99 $(OPTLEVEL) $(WARNINGS) $(EXTRAS) 28 | CXX = em++ 29 | CXXFLAGS = -std=c++14 $(OPTLEVEL) $(WARNINGS) $(EXTRAS) 30 | CPP = emcpp 31 | CPPFLAGS = -I. -I$(TOPDIR)/src -D_DEFAULT_SOURCE -D_FORTIFY_SOURCE=2 -DHAVE_CONFIG_H 32 | LD = em++ 33 | LDFLAGS = -L. 34 | CP = cp 35 | CPFLAGS = -f 36 | RM = rm 37 | RMFLAGS = -f 38 | 39 | # ---------------------------------------------------------------------------- 40 | # default rules 41 | # ---------------------------------------------------------------------------- 42 | 43 | .c.o: 44 | $(CC) -c $(CFLAGS) $(CPPFLAGS) -o $@ $< 45 | 46 | .cc.o: 47 | $(CXX) -c $(CXXFLAGS) $(CPPFLAGS) -o $@ $< 48 | 49 | # ---------------------------------------------------------------------------- 50 | # global targets 51 | # ---------------------------------------------------------------------------- 52 | 53 | all: build 54 | 55 | build: build_another_world 56 | @echo "=== $@ ok ===" 57 | 58 | clean: clean_another_world 59 | @echo "=== $@ ok ===" 60 | 61 | # ---------------------------------------------------------------------------- 62 | # Another World 63 | # ---------------------------------------------------------------------------- 64 | 65 | another_world_PROGRAM = bin/another-world.html 66 | 67 | another_world_SOURCES = \ 68 | src/main.cc \ 69 | src/logger.cc \ 70 | src/bytekiller.cc \ 71 | src/file.cc \ 72 | src/intern.cc \ 73 | src/data.cc \ 74 | src/engine.cc \ 75 | src/backend.cc \ 76 | src/resources.cc \ 77 | src/video.cc \ 78 | src/audio.cc \ 79 | src/mixer.cc \ 80 | src/sound.cc \ 81 | src/music.cc \ 82 | src/input.cc \ 83 | src/vm.cc \ 84 | $(NULL) 85 | 86 | another_world_HEADERS = \ 87 | src/main.h \ 88 | src/logger.h \ 89 | src/bytekiller.h \ 90 | src/file.h \ 91 | src/intern.h \ 92 | src/data.h \ 93 | src/engine.h \ 94 | src/backend.h \ 95 | src/resources.h \ 96 | src/video.h \ 97 | src/audio.h \ 98 | src/mixer.h \ 99 | src/sound.h \ 100 | src/music.h \ 101 | src/input.h \ 102 | src/vm.h \ 103 | $(NULL) 104 | 105 | another_world_OBJECTS = \ 106 | src/main.o \ 107 | src/logger.o \ 108 | src/bytekiller.o \ 109 | src/file.o \ 110 | src/intern.o \ 111 | src/data.o \ 112 | src/engine.o \ 113 | src/backend.o \ 114 | src/resources.o \ 115 | src/video.o \ 116 | src/audio.o \ 117 | src/mixer.o \ 118 | src/sound.o \ 119 | src/music.o \ 120 | src/input.o \ 121 | src/vm.o \ 122 | $(NULL) 123 | 124 | another_world_LDFLAGS = \ 125 | --use-preload-plugins \ 126 | --preload-file share/another-world/BANK01 \ 127 | --preload-file share/another-world/BANK02 \ 128 | --preload-file share/another-world/BANK03 \ 129 | --preload-file share/another-world/BANK04 \ 130 | --preload-file share/another-world/BANK05 \ 131 | --preload-file share/another-world/BANK06 \ 132 | --preload-file share/another-world/BANK07 \ 133 | --preload-file share/another-world/BANK08 \ 134 | --preload-file share/another-world/BANK09 \ 135 | --preload-file share/another-world/BANK0A \ 136 | --preload-file share/another-world/BANK0B \ 137 | --preload-file share/another-world/BANK0C \ 138 | --preload-file share/another-world/BANK0D \ 139 | --preload-file share/another-world/MEMLIST.BIN \ 140 | $(NULL) 141 | 142 | another_world_LDADD = \ 143 | -lSDL2 \ 144 | -lz \ 145 | $(NULL) 146 | 147 | another_world_CLEANFILES = \ 148 | bin/another-world.bin \ 149 | bin/another-world.data \ 150 | bin/another-world.html \ 151 | bin/another-world.wasm \ 152 | bin/another-world.js \ 153 | $(NULL) 154 | 155 | # ---------------------------------------------------------------------------- 156 | # build Another World 157 | # ---------------------------------------------------------------------------- 158 | 159 | build_another_world: $(another_world_PROGRAM) 160 | 161 | $(another_world_PROGRAM): $(another_world_OBJECTS) 162 | $(LD) $(LDFLAGS) $(another_world_LDFLAGS) -o $(another_world_PROGRAM) $(another_world_OBJECTS) $(another_world_LDADD) 163 | 164 | # ---------------------------------------------------------------------------- 165 | # clean Another World 166 | # ---------------------------------------------------------------------------- 167 | 168 | clean_another_world: 169 | $(RM) $(RMFLAGS) $(another_world_OBJECTS) $(another_world_PROGRAM) $(another_world_CLEANFILES) 170 | 171 | # ---------------------------------------------------------------------------- 172 | # End-Of-File 173 | # ---------------------------------------------------------------------------- 174 | -------------------------------------------------------------------------------- /src/vm.h: -------------------------------------------------------------------------------- 1 | /* 2 | * vm.h - Copyright (c) 2004-2025 3 | * 4 | * Gregory Montoir, Fabien Sanglard, Olivier Poncet 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 2 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | #ifndef __AW_VIRTUALMACHINE_H__ 20 | #define __AW_VIRTUALMACHINE_H__ 21 | 22 | #include "intern.h" 23 | 24 | // --------------------------------------------------------------------------- 25 | // Variables 26 | // --------------------------------------------------------------------------- 27 | 28 | enum Variables 29 | { 30 | VM_VARIABLE_RANDOM_SEED = 0x3c, 31 | VM_VARIABLE_INPUT_KEY = 0xda, 32 | VM_VARIABLE_HERO_POS_UP_DOWN = 0xe5, 33 | VM_VARIABLE_MUSIC_MARK = 0xf4, 34 | VM_VARIABLE_SCROLL_Y = 0xf9, 35 | VM_VARIABLE_HERO_ACTION = 0xfa, 36 | VM_VARIABLE_HERO_POS_JUMP_DOWN = 0xfb, 37 | VM_VARIABLE_HERO_POS_LEFT_RIGHT = 0xfc, 38 | VM_VARIABLE_HERO_POS_MASK = 0xfd, 39 | VM_VARIABLE_HERO_ACTION_POS_MASK = 0xfe, 40 | VM_VARIABLE_PAUSE_SLICES = 0xff 41 | }; 42 | 43 | // --------------------------------------------------------------------------- 44 | // VirtualMachine 45 | // --------------------------------------------------------------------------- 46 | 47 | class VirtualMachine final 48 | : public SubSystem 49 | { 50 | public: // public interface 51 | VirtualMachine(Engine& engine); 52 | 53 | VirtualMachine(VirtualMachine&&) = delete; 54 | 55 | VirtualMachine(const VirtualMachine&) = delete; 56 | 57 | VirtualMachine& operator=(VirtualMachine&&) = delete; 58 | 59 | VirtualMachine& operator=(const VirtualMachine&) = delete; 60 | 61 | virtual ~VirtualMachine() = default; 62 | 63 | virtual auto start() -> void override final; 64 | 65 | virtual auto reset() -> void override final; 66 | 67 | virtual auto stop() -> void override final; 68 | 69 | public: // public vm interface 70 | auto run(Controls& controls) -> void; 71 | 72 | auto setByteCode(const uint8_t* bytecode) -> void; 73 | 74 | auto getRegister(uint8_t index) const -> uint16_t 75 | { 76 | return _registers[index].u; 77 | } 78 | 79 | auto setRegister(uint8_t index, uint16_t value) 80 | { 81 | _registers[index].u = value; 82 | } 83 | 84 | private: // private interface 85 | struct Thread 86 | { 87 | uint32_t thread_id = 0; 88 | uint16_t current_pc = 0; 89 | uint16_t requested_pc = 0; 90 | uint8_t current_state = 0; 91 | uint8_t requested_state = 0; 92 | uint8_t opcode = 0x3f; 93 | bool yield = false; 94 | }; 95 | 96 | struct Register 97 | { 98 | union { 99 | int16_t s; 100 | uint16_t u; 101 | }; 102 | }; 103 | 104 | struct Stack 105 | { 106 | uint32_t array[256]; 107 | uint32_t index; 108 | }; 109 | 110 | auto op_setr(Thread& thread) -> void; 111 | 112 | auto op_seti(Thread& thread) -> void; 113 | 114 | auto op_addr(Thread& thread) -> void; 115 | 116 | auto op_addi(Thread& thread) -> void; 117 | 118 | auto op_subr(Thread& thread) -> void; 119 | 120 | auto op_andi(Thread& thread) -> void; 121 | 122 | auto op_iori(Thread& thread) -> void; 123 | 124 | auto op_lsli(Thread& thread) -> void; 125 | 126 | auto op_lsri(Thread& thread) -> void; 127 | 128 | auto op_jump(Thread& thread) -> void; 129 | 130 | auto op_cjmp(Thread& thread) -> void; 131 | 132 | auto op_dbra(Thread& thread) -> void; 133 | 134 | auto op_call(Thread& thread) -> void; 135 | 136 | auto op_ret(Thread& thread) -> void; 137 | 138 | auto op_start(Thread& thread) -> void; 139 | 140 | auto op_reset(Thread& thread) -> void; 141 | 142 | auto op_yield(Thread& thread) -> void; 143 | 144 | auto op_halt(Thread& thread) -> void; 145 | 146 | auto op_load(Thread& thread) -> void; 147 | 148 | auto op_fade(Thread& thread) -> void; 149 | 150 | auto op_page(Thread& thread) -> void; 151 | 152 | auto op_fill(Thread& thread) -> void; 153 | 154 | auto op_copy(Thread& thread) -> void; 155 | 156 | auto op_show(Thread& thread) -> void; 157 | 158 | auto op_print(Thread& thread) -> void; 159 | 160 | auto op_sound(Thread& thread) -> void; 161 | 162 | auto op_music(Thread& thread) -> void; 163 | 164 | auto op_poly1(Thread& thread) -> void; 165 | 166 | auto op_poly2(Thread& thread) -> void; 167 | 168 | auto op_invalid(Thread& thread) -> void; 169 | 170 | private: // private data 171 | ByteCode _bytecode; 172 | Thread _threads[64]; 173 | Register _registers[256]; 174 | Stack _stack; 175 | }; 176 | 177 | // --------------------------------------------------------------------------- 178 | // End-Of-File 179 | // --------------------------------------------------------------------------- 180 | 181 | #endif /* __AW_VIRTUALMACHINE_H__ */ 182 | -------------------------------------------------------------------------------- /src/intern.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * intern.cc - Copyright (c) 2004-2025 3 | * 4 | * Gregory Montoir, Fabien Sanglard, Olivier Poncet 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 2 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | #ifdef HAVE_CONFIG_H 20 | #include "config.h" 21 | #endif 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include "logger.h" 41 | #include "intern.h" 42 | 43 | // --------------------------------------------------------------------------- 44 | // some useful macros 45 | // --------------------------------------------------------------------------- 46 | 47 | #ifndef NDEBUG 48 | #define LOG_DEBUG(format, ...) log_debug(SYS_ENGINE, format, ##__VA_ARGS__) 49 | #else 50 | #define LOG_DEBUG(format, ...) do {} while(0) 51 | #endif 52 | 53 | // --------------------------------------------------------------------------- 54 | // Paula::frequencyTable (formula is: PaulaFrequency / (2 * period)) 55 | // --------------------------------------------------------------------------- 56 | 57 | const uint16_t Paula::frequencyTable[] = { 58 | static_cast(Paula::Carrier / 1076), 59 | static_cast(Paula::Carrier / 1016), 60 | static_cast(Paula::Carrier / 960), 61 | static_cast(Paula::Carrier / 906), 62 | static_cast(Paula::Carrier / 856), 63 | static_cast(Paula::Carrier / 808), 64 | static_cast(Paula::Carrier / 762), 65 | static_cast(Paula::Carrier / 720), 66 | static_cast(Paula::Carrier / 678), 67 | static_cast(Paula::Carrier / 640), 68 | static_cast(Paula::Carrier / 604), 69 | static_cast(Paula::Carrier / 570), 70 | static_cast(Paula::Carrier / 538), 71 | static_cast(Paula::Carrier / 508), 72 | static_cast(Paula::Carrier / 480), 73 | static_cast(Paula::Carrier / 453), 74 | static_cast(Paula::Carrier / 428), 75 | static_cast(Paula::Carrier / 404), 76 | static_cast(Paula::Carrier / 381), 77 | static_cast(Paula::Carrier / 360), 78 | static_cast(Paula::Carrier / 339), 79 | static_cast(Paula::Carrier / 320), 80 | static_cast(Paula::Carrier / 302), 81 | static_cast(Paula::Carrier / 285), 82 | static_cast(Paula::Carrier / 269), 83 | static_cast(Paula::Carrier / 254), 84 | static_cast(Paula::Carrier / 240), 85 | static_cast(Paula::Carrier / 226), 86 | static_cast(Paula::Carrier / 214), 87 | static_cast(Paula::Carrier / 202), 88 | static_cast(Paula::Carrier / 190), 89 | static_cast(Paula::Carrier / 180), 90 | static_cast(Paula::Carrier / 170), 91 | static_cast(Paula::Carrier / 160), 92 | static_cast(Paula::Carrier / 151), 93 | static_cast(Paula::Carrier / 143), 94 | static_cast(Paula::Carrier / 135), 95 | static_cast(Paula::Carrier / 127), 96 | static_cast(Paula::Carrier / 120), 97 | static_cast(Paula::Carrier / 113), 98 | }; 99 | 100 | // --------------------------------------------------------------------------- 101 | // GameParts 102 | // --------------------------------------------------------------------------- 103 | 104 | const GamePart GameParts::data[GAME_NUM_PARTS] = { 105 | { "Protection" , 0x14, 0x15, 0x16, 0x00 }, 106 | { "Introduction", 0x17, 0x18, 0x19, 0x00 }, 107 | { "Water" , 0x1a, 0x1b, 0x1c, 0x11 }, 108 | { "Jail" , 0x1d, 0x1e, 0x1f, 0x11 }, 109 | { "Cite" , 0x20, 0x21, 0x22, 0x11 }, 110 | { "Arena" , 0x23, 0x24, 0x25, 0x00 }, 111 | { "Luxe" , 0x26, 0x27, 0x28, 0x11 }, 112 | { "Final" , 0x29, 0x2a, 0x2b, 0x11 }, 113 | { "Password" , 0x7d, 0x7e, 0x7f, 0x00 }, 114 | { "Password" , 0x7d, 0x7e, 0x7f, 0x00 }, 115 | }; 116 | 117 | // --------------------------------------------------------------------------- 118 | // SubSystem 119 | // --------------------------------------------------------------------------- 120 | 121 | SubSystem::SubSystem(Engine& engine, const std::string& subsystem) 122 | : _subsystem(subsystem) 123 | , _engine(engine) 124 | , _mutex() 125 | , _currTicks(0) 126 | , _prevTicks(0) 127 | , _nextTicks(0) 128 | { 129 | LOG_DEBUG("creating %s", _subsystem.c_str()); 130 | } 131 | 132 | SubSystem::~SubSystem() 133 | { 134 | LOG_DEBUG("destroyed %s", _subsystem.c_str()); 135 | } 136 | 137 | // --------------------------------------------------------------------------- 138 | // End-Of-File 139 | // --------------------------------------------------------------------------- 140 | -------------------------------------------------------------------------------- /src/bytekiller.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * bytekiller.cc - Copyright (c) 2004-2025 3 | * 4 | * Gregory Montoir, Fabien Sanglard, Olivier Poncet 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 2 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | #ifdef HAVE_CONFIG_H 20 | #include "config.h" 21 | #endif 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include "logger.h" 41 | #include "bytekiller.h" 42 | 43 | // --------------------------------------------------------------------------- 44 | // ByteKiller 45 | // --------------------------------------------------------------------------- 46 | 47 | ByteKiller::ByteKiller(uint8_t* buffer, uint32_t packedSize, uint32_t unpackedSize) 48 | : _buffer(buffer) 49 | , _srcptr(_buffer + packedSize - 1) 50 | , _dstptr(_buffer + unpackedSize - 1) 51 | , _length(0) 52 | , _check(0) 53 | , _chunk(0) 54 | { 55 | _length = fetchLong(); 56 | _check = fetchLong(); 57 | _chunk = fetchLong(); 58 | _check = _check ^_chunk; 59 | } 60 | 61 | auto ByteKiller::unpack() -> bool 62 | { 63 | if(_length != 0) { 64 | do { 65 | uint32_t code = getBit(); 66 | uint32_t count = 0; 67 | uint32_t offset = 0; 68 | if(code == 0) { 69 | code = (code << 1) | getBits(1); 70 | } 71 | else { 72 | code = (code << 2) | getBits(2); 73 | } 74 | switch(code) { 75 | case 0x00: // 0b00ccc 76 | count = getBits(3) + 1; 77 | unpackBytes(count); 78 | break; 79 | case 0x07: // 0b111cccccccc 80 | count = getBits(8) + 9; 81 | unpackBytes(count); 82 | break; 83 | case 0x01: // 0b01oooooooo 84 | count = 2; 85 | offset = getBits(8); 86 | unpackBytes(offset, count); 87 | break; 88 | case 0x04: // 0b100ooooooooo 89 | count = 3; 90 | offset = getBits(9); 91 | unpackBytes(offset, count); 92 | break; 93 | case 0x05: // 0b101oooooooooo 94 | count = 4; 95 | offset = getBits(10); 96 | unpackBytes(offset, count); 97 | break; 98 | case 0x06: // 0b110ccccccccoooooooooooo 99 | count = getBits(8) + 1; 100 | offset = getBits(12); 101 | unpackBytes(offset, count); 102 | break; 103 | default: 104 | log_error("ByteKiller: unhandled code"); 105 | return false; 106 | } 107 | } while(_length != 0); 108 | } 109 | else { 110 | log_error("ByteKiller: already unpacked"); 111 | return false; 112 | } 113 | if(_check != 0) { 114 | log_error("ByteKiller: CRC error while unpacking"); 115 | return false; 116 | } 117 | return true; 118 | } 119 | 120 | auto ByteKiller::fetchLong() -> uint32_t 121 | { 122 | uint32_t l = 0; 123 | if(_srcptr != nullptr) { 124 | assert(((_srcptr - 4) + 1) >= _buffer); 125 | l |= (static_cast(*_srcptr--) << 0); 126 | l |= (static_cast(*_srcptr--) << 8); 127 | l |= (static_cast(*_srcptr--) << 16); 128 | l |= (static_cast(*_srcptr--) << 24); 129 | } 130 | return l; 131 | } 132 | 133 | auto ByteKiller::writeByte(uint8_t byte) -> void 134 | { 135 | if(_dstptr != nullptr) { 136 | assert((((_dstptr - 1) + 1) >= _buffer)); 137 | *_dstptr-- = byte; 138 | --_length; 139 | } 140 | } 141 | 142 | auto ByteKiller::getBit() -> uint32_t 143 | { 144 | constexpr uint32_t msb = (1 << 31); 145 | constexpr uint32_t lsb = (1 << 0); 146 | uint32_t bit = _chunk & lsb; 147 | 148 | if((_chunk >>= 1) == 0) { 149 | _chunk = fetchLong(); 150 | _check = _check ^_chunk; 151 | bit = _chunk & lsb; 152 | _chunk = (_chunk >> 1) | msb; 153 | } 154 | return bit; 155 | } 156 | 157 | auto ByteKiller::getBits(uint32_t count) -> uint32_t 158 | { 159 | uint32_t bits = 0; 160 | 161 | while(count != 0) { 162 | bits = (bits << 1) | getBit(); 163 | --count; 164 | } 165 | return bits; 166 | } 167 | 168 | auto ByteKiller::unpackBytes(uint32_t count) -> void 169 | { 170 | while(count != 0) { 171 | writeByte(getBits(8)); 172 | --count; 173 | } 174 | } 175 | 176 | auto ByteKiller::unpackBytes(uint32_t offset, uint32_t count) -> void 177 | { 178 | while(count != 0) { 179 | writeByte(_dstptr[offset]); 180 | --count; 181 | } 182 | } 183 | 184 | // --------------------------------------------------------------------------- 185 | // End-Of-File 186 | // --------------------------------------------------------------------------- 187 | -------------------------------------------------------------------------------- /src/engine.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * engine.cc - Copyright (c) 2004-2025 3 | * 4 | * Gregory Montoir, Fabien Sanglard, Olivier Poncet 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 2 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | #ifdef HAVE_CONFIG_H 20 | #include "config.h" 21 | #endif 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #ifdef __EMSCRIPTEN__ 41 | #include 42 | #endif 43 | #include "logger.h" 44 | #include "engine.h" 45 | 46 | // --------------------------------------------------------------------------- 47 | // some useful macros 48 | // --------------------------------------------------------------------------- 49 | 50 | #ifndef NDEBUG 51 | #define LOG_DEBUG(format, ...) log_debug(SYS_ENGINE, format, ##__VA_ARGS__) 52 | #else 53 | #define LOG_DEBUG(format, ...) do {} while(0) 54 | #endif 55 | 56 | // --------------------------------------------------------------------------- 57 | // Engine::Emscripten 58 | // --------------------------------------------------------------------------- 59 | 60 | struct Engine::Emscripten 61 | { 62 | #ifdef __EMSCRIPTEN__ 63 | static auto startup(Engine* engine) -> void 64 | { 65 | engine->start(); 66 | engine->reset(); 67 | LOG_DEBUG("running..."); 68 | ::emscripten_set_main_loop_arg(reinterpret_cast(mainloop), engine, 0, 1); 69 | }; 70 | 71 | static auto shutdown(Engine* engine) -> void 72 | { 73 | LOG_DEBUG("stopped!"); 74 | engine->stop(); 75 | engine = (delete engine, nullptr); 76 | ::emscripten_cancel_main_loop(); 77 | ::emscripten_force_exit(EXIT_SUCCESS); 78 | }; 79 | 80 | static auto mainloop(Engine* engine) -> void 81 | { 82 | if(engine->isRunning()) { 83 | engine->processEvents(); 84 | engine->processVirtualMachine(); 85 | } 86 | if(engine->isStopped()) { 87 | shutdown(engine); 88 | } 89 | if(engine->getReqPartId() != 0) { 90 | engine->initPart(0); 91 | } 92 | }; 93 | #endif 94 | }; 95 | 96 | // --------------------------------------------------------------------------- 97 | // Engine 98 | // --------------------------------------------------------------------------- 99 | 100 | Engine::Engine(const std::string& datadir, const std::string& dumpdir) 101 | : SubSystem(*this, "Engine") 102 | , _datadir(datadir) 103 | , _dumpdir(dumpdir) 104 | , _palmode(0) 105 | , _backend(Backend::create(*this)) 106 | , _resources(*this) 107 | , _video(*this) 108 | , _audio(*this) 109 | , _input(*this) 110 | , _vm(*this) 111 | { 112 | } 113 | 114 | auto Engine::main() -> void 115 | { 116 | auto run = [&]() -> void 117 | { 118 | #ifdef __EMSCRIPTEN__ 119 | Emscripten::startup(this); 120 | #else 121 | start(); 122 | reset(); 123 | LOG_DEBUG("running..."); 124 | while(true) { 125 | if(isRunning()) { 126 | processEvents(); 127 | processVirtualMachine(); 128 | } 129 | if(isStopped()) { 130 | break; 131 | } 132 | if(getReqPartId() != 0) { 133 | initPart(0); 134 | } 135 | sleepUntil(_vm.getNextTicks()); 136 | } 137 | LOG_DEBUG("stopped!"); 138 | stop(); 139 | #endif 140 | }; 141 | 142 | return run(); 143 | } 144 | 145 | auto Engine::start() -> void 146 | { 147 | LOG_DEBUG("starting..."); 148 | _backend->start(); 149 | _resources.start(); 150 | _video.start(); 151 | _audio.start(); 152 | _input.start(); 153 | _vm.start(); 154 | LOG_DEBUG("started!"); 155 | } 156 | 157 | auto Engine::reset() -> void 158 | { 159 | LOG_DEBUG("resetting..."); 160 | _backend->reset(); 161 | _resources.reset(); 162 | _video.reset(); 163 | _audio.reset(); 164 | _input.reset(); 165 | _vm.reset(); 166 | #ifdef SKIP_GAME_PART0 167 | initPart(GAME_PART1); 168 | #else 169 | initPart(GAME_PART0); 170 | #endif 171 | LOG_DEBUG("reset!"); 172 | } 173 | 174 | auto Engine::stop() -> void 175 | { 176 | LOG_DEBUG("stopping..."); 177 | _vm.stop(); 178 | _input.stop(); 179 | _audio.stop(); 180 | _video.stop(); 181 | _resources.stop(); 182 | _backend->stop(); 183 | LOG_DEBUG("stopped!"); 184 | } 185 | 186 | auto Engine::initPart(uint16_t id) -> void 187 | { 188 | _video.reset(); 189 | _audio.reset(); 190 | _input.reset(); 191 | _resources.loadPart(id); 192 | _video.setPalettes(_resources.getPalettesData(), _palmode); 193 | _vm.setByteCode(_resources.getByteCodeData()); 194 | } 195 | 196 | auto Engine::switchPalettes() -> void 197 | { 198 | _palmode = (_palmode + 1) % 5; 199 | _video.setPalettes(_resources.getPalettesData(), _palmode); 200 | } 201 | 202 | // --------------------------------------------------------------------------- 203 | // End-Of-File 204 | // --------------------------------------------------------------------------- 205 | -------------------------------------------------------------------------------- /src/logger.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * logger.cc - Copyright (c) 2004-2025 3 | * 4 | * Gregory Montoir, Fabien Sanglard, Olivier Poncet 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 2 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | #ifdef HAVE_CONFIG_H 20 | #include "config.h" 21 | #endif 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include "logger.h" 41 | 42 | // --------------------------------------------------------------------------- 43 | // Panic 44 | // --------------------------------------------------------------------------- 45 | 46 | auto Panic::operator()() const -> void 47 | { 48 | std::cerr << "*** Engine panic ***" << std::endl; 49 | } 50 | 51 | // --------------------------------------------------------------------------- 52 | // debug mask 53 | // --------------------------------------------------------------------------- 54 | 55 | FILE* g_logger_out = stdout; 56 | FILE* g_logger_err = stderr; 57 | uint32_t g_logger_mask = 0u 58 | | LOG_DEBUG 59 | | LOG_PRINT 60 | | LOG_ALERT 61 | | LOG_ERROR 62 | | LOG_FATAL 63 | ; 64 | 65 | // --------------------------------------------------------------------------- 66 | // logging functions 67 | // --------------------------------------------------------------------------- 68 | 69 | auto log_debug(uint32_t mask, const char* format, ...) -> void 70 | { 71 | auto get_domain = [&]() -> const char* 72 | { 73 | if(mask & SYS_ENGINE) { return "ENGINE"; }; 74 | if(mask & SYS_BACKEND) { return "BACKEND"; }; 75 | if(mask & SYS_RESOURCES) { return "RESRCS"; }; 76 | if(mask & SYS_VIDEO) { return "VIDEO "; }; 77 | if(mask & SYS_AUDIO) { return "AUDIO "; }; 78 | if(mask & SYS_MIXER) { return "MIXER "; }; 79 | if(mask & SYS_SOUND) { return "SOUND "; }; 80 | if(mask & SYS_MUSIC) { return "MUSIC "; }; 81 | if(mask & SYS_INPUT) { return "INPUT "; }; 82 | if(mask & SYS_VM) { return "VM "; }; 83 | return "UNKNOWN"; 84 | }; 85 | 86 | if((g_logger_mask & LOG_DEBUG) != 0) { 87 | if((g_logger_mask & mask) != 0) { 88 | va_list args; 89 | va_start(args, format); 90 | (void) ::fputc('D', g_logger_out); 91 | (void) ::fputc('\t', g_logger_out); 92 | (void) ::fputs(get_domain(), g_logger_out); 93 | (void) ::fputc('\t', g_logger_out); 94 | (void) ::vfprintf(g_logger_out, format, args); 95 | (void) ::fputc('\n', g_logger_out); 96 | (void) ::fflush(g_logger_out); 97 | va_end(args); 98 | } 99 | } 100 | } 101 | 102 | auto log_debug(const char* format, ...) -> void 103 | { 104 | if((g_logger_mask & LOG_DEBUG) != 0) { 105 | va_list args; 106 | va_start(args, format); 107 | (void) ::fputc('D', g_logger_out); 108 | (void) ::fputc('\t', g_logger_out); 109 | (void) ::vfprintf(g_logger_out, format, args); 110 | (void) ::fputc('\n', g_logger_out); 111 | (void) ::fflush(g_logger_out); 112 | va_end(args); 113 | } 114 | } 115 | 116 | auto log_print(const char* format, ...) -> void 117 | { 118 | if((g_logger_mask & LOG_PRINT) != 0) { 119 | va_list args; 120 | va_start(args, format); 121 | (void) ::fputc('I', g_logger_out); 122 | (void) ::fputc('\t', g_logger_out); 123 | (void) ::vfprintf(g_logger_out, format, args); 124 | (void) ::fputc('\n', g_logger_out); 125 | (void) ::fflush(g_logger_out); 126 | va_end(args); 127 | } 128 | } 129 | 130 | auto log_alert(const char* format, ...) -> void 131 | { 132 | if((g_logger_mask & LOG_ALERT) != 0) { 133 | va_list args; 134 | va_start(args, format); 135 | (void) ::fputc('W', g_logger_err); 136 | (void) ::fputc('\t', g_logger_err); 137 | (void) ::vfprintf(g_logger_err, format, args); 138 | (void) ::fputc('\n', g_logger_err); 139 | (void) ::fflush(g_logger_err); 140 | va_end(args); 141 | } 142 | } 143 | 144 | auto log_error(const char* format, ...) -> void 145 | { 146 | if((g_logger_mask & LOG_ERROR) != 0) { 147 | va_list args; 148 | va_start(args, format); 149 | (void) ::fputc('E', g_logger_err); 150 | (void) ::fputc('\t', g_logger_err); 151 | (void) ::vfprintf(g_logger_err, format, args); 152 | (void) ::fputc('\n', g_logger_err); 153 | (void) ::fflush(g_logger_err); 154 | va_end(args); 155 | } 156 | } 157 | 158 | auto log_fatal(const char* format, ...) -> void 159 | { 160 | if((g_logger_mask & LOG_FATAL) != 0) { 161 | va_list args; 162 | va_start(args, format); 163 | (void) ::fputc('F', g_logger_err); 164 | (void) ::fputc('\t', g_logger_err); 165 | (void) ::vfprintf(g_logger_err, format, args); 166 | (void) ::fputc('\n', g_logger_err); 167 | (void) ::fflush(g_logger_err); 168 | va_end(args); 169 | } 170 | throw Panic(); 171 | } 172 | 173 | // --------------------------------------------------------------------------- 174 | // End-Of-File 175 | // --------------------------------------------------------------------------- 176 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ANOTHER WORLD INTERPRETER 2 | 3 | This is another « Another World » Bytecode Interpreter written in C++ and built with [SDL](https://www.libsdl.org/), targeting Linux, BSD and [WASM](https://en.wikipedia.org/wiki/WebAssembly) with [Emscripten](https://emscripten.org/). 4 | 5 | This is a fork of Fabien Sanglard's project, itself a fork of Gregory Montoir's original implementation, almost completely rewritten with a cleaner architecture and better code readability in mind, primarily for educational purposes. 6 | 7 | ![Another World](share/another-world/another-world.png) 8 | 9 | You can play Another World directly in your browser by clicking [here](https://www.emaxilde.net/assets/games/another-world/another-world.html). 10 | 11 | ## COMPILE AND RUN FOR LINUX 12 | 13 | ### Install the dependencies 14 | 15 | In order to build the Linux port, you must install the build toolchain, the [SDL2](https://www.libsdl.org/) and the [zlib](https://www.zlib.net/) libraries. 16 | 17 | Under Debian and derivatives (Ubuntu, Mint, ...): 18 | 19 | ``` 20 | apt-get install build-essential libsdl2-dev zlib1g-dev 21 | ``` 22 | 23 | ### Build the project 24 | 25 | ``` 26 | make -f Makefile.linux 27 | ``` 28 | 29 | or 30 | 31 | ``` 32 | ./build.sh linux 33 | ``` 34 | 35 | ### Install the data files 36 | 37 | The original game data files must be copied into the [share/another-world](share/another-world) directory. 38 | 39 | ### Run the project 40 | 41 | ``` 42 | ./bin/another-world.bin 43 | ``` 44 | 45 | ## COMPILE AND RUN FOR WASM 46 | 47 | ### Install the dependencies 48 | 49 | In order to build the WASM port, You must install the [Emscripten](https://emscripten.org/) toolchain. 50 | 51 | - [Download and install](https://emscripten.org/docs/getting_started/downloads.html) 52 | 53 | ### Build the project 54 | 55 | ``` 56 | make -f Makefile.wasm 57 | ``` 58 | 59 | or 60 | 61 | ``` 62 | ./build.sh wasm 63 | ``` 64 | 65 | ### Install the data files 66 | 67 | The original game data files must be copied into the [share/another-world](share/another-world) directory. 68 | 69 | ### Run the project 70 | 71 | ``` 72 | emrun ./bin/another-world.html 73 | ``` 74 | 75 | ## COMMAND-LINE OPTIONS 76 | 77 | ``` 78 | Usage: another-world.bin [OPTIONS...] 79 | 80 | Options: 81 | 82 | -h, --help display this help and exit 83 | 84 | --datadir=PATH directory where data files are stored 85 | --dumpdir=PATH directory where dump files are stored 86 | 87 | --quiet quiet mode 88 | --debug debug mode 89 | --debug-all debug all subsystems 90 | --debug-engine debug the engine subsystem 91 | --debug-backend debug the backend subsystem 92 | --debug-resources debug the resources subsystem 93 | --debug-video debug the video subsystem 94 | --debug-audio debug the audio subsystem 95 | --debug-mixer debug the mixer subsystem 96 | --debug-sound debug the sound subsystem 97 | --debug-music debug the music subsystem 98 | --debug-input debug the input subsystem 99 | --debug-vm debug the vm subsystem 100 | 101 | ``` 102 | 103 | ## GAME CONTROLS 104 | 105 | - `Up`, `Down`, `Left`, `Right` move 106 | - `Space` run or fire 107 | - `Tab` or `Tab` change the window size 108 | - `0` ... `9` jump at a specific game part 109 | - `c` enter a code to jump at a specific level 110 | - `p` pause the game 111 | - `r` reset the game 112 | - `m` change the display mode (standard/CRT/pages/palette/...) 113 | - `v` change the video mode (RGB/RGB-ALT/VGA/EGA/CGA) 114 | - `Escape` exit the game 115 | 116 | ## RESOURCES 117 | 118 | Eric Chahi's website with some interesting informations: 119 | 120 | - https://www.anotherworld.fr/another_world.htm 121 | 122 | Grégory Montoir's reimplementation of the game engine: 123 | 124 | - https://github.com/cyxx/rawgl 125 | 126 | Fabien Sanglard's fork cleaned up with legibility and readability in mind: 127 | 128 | - https://github.com/fabiensanglard/Another-World-Bytecode-Interpreter 129 | 130 | You can also find below some great articles written by Fabien Sanglard about the architecture and ports of `Another World`: 131 | 132 | - [Another World Code Review](https://fabiensanglard.net/anotherWorld_code_review/) 133 | - [The polygons of Another World](https://fabiensanglard.net/another_world_polygons/) 134 | - [The polygons of Another World: Amiga](https://fabiensanglard.net/another_world_polygons_amiga500/) 135 | - [The polygons of Another World: Atari ST](https://fabiensanglard.net/another_world_polygons_atariST/) 136 | - [The polygons of Another World: IBM PC](https://fabiensanglard.net/another_world_polygons_PC_DOS/) 137 | - [The polygons of Another World: Sega Genesis](https://fabiensanglard.net/another_world_polygons_Genesis/) 138 | - [The polygons of Another World: SNES](https://fabiensanglard.net/another_world_polygons_SNES/) 139 | - [The polygons of Another World: GBA](https://fabiensanglard.net/another_world_polygons_GBA/) 140 | - [The polygons of Another World: Jaguar](https://fabiensanglard.net/another_world_polygons_Jaguar/) 141 | 142 | ## CREDITS 143 | 144 | Thanks to: 145 | 146 | - Eric Chahi, obviously, who made this fantastic game and released it in 1991. 147 | - Gregory Montoir, who reversed the original virtual machine and wrote a full modern implementation. 148 | - Fabien Sanglard, who improved the reimplementation and wrote blog posts about this clever architecture. 149 | 150 | ## LICENSE 151 | 152 | The source code is released under the terms of the GNU General Public License 2.0. 153 | 154 | ``` 155 | This program is free software: you can redistribute it and/or modify 156 | it under the terms of the GNU General Public License as published by 157 | the Free Software Foundation, either version 2 of the License, or 158 | (at your option) any later version. 159 | 160 | This program is distributed in the hope that it will be useful, 161 | but WITHOUT ANY WARRANTY; without even the implied warranty of 162 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 163 | GNU General Public License for more details. 164 | 165 | You should have received a copy of the GNU General Public License 166 | along with this program. If not, see 167 | ``` 168 | 169 | ## DISCLAIMER 170 | 171 | The original game data files are protected under the copyrights of their authors and are not distributed with this project. 172 | 173 | ``` 174 | Please do not distribute the original data game files with this project. 175 | ``` 176 | 177 | -------------------------------------------------------------------------------- /src/sound.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * sound.cc - Copyright (c) 2004-2025 3 | * 4 | * Gregory Montoir, Fabien Sanglard, Olivier Poncet 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 2 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | #ifdef HAVE_CONFIG_H 20 | #include "config.h" 21 | #endif 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include "logger.h" 41 | #include "engine.h" 42 | #include "sound.h" 43 | 44 | // --------------------------------------------------------------------------- 45 | // some useful macros 46 | // --------------------------------------------------------------------------- 47 | 48 | #ifndef NDEBUG 49 | #define LOG_DEBUG(format, ...) log_debug(SYS_SOUND, format, ##__VA_ARGS__) 50 | #else 51 | #define LOG_DEBUG(format, ...) do {} while(0) 52 | #endif 53 | 54 | // --------------------------------------------------------------------------- 55 | // Sound 56 | // --------------------------------------------------------------------------- 57 | 58 | Sound::Sound(Engine& engine, Audio& audio) 59 | : SubSystem(engine, "Sound") 60 | , _audio(audio) 61 | , _samples() 62 | , _timer(0) 63 | { 64 | } 65 | 66 | auto Sound::start() -> void 67 | { 68 | LOG_DEBUG("starting..."); 69 | stopSound(); 70 | startTimer(); 71 | LOG_DEBUG("started!"); 72 | } 73 | 74 | auto Sound::reset() -> void 75 | { 76 | LOG_DEBUG("resetting..."); 77 | stopSound(); 78 | resetTimer(); 79 | LOG_DEBUG("reset!"); 80 | } 81 | 82 | auto Sound::stop() -> void 83 | { 84 | LOG_DEBUG("stopping..."); 85 | stopSound(); 86 | stopTimer(); 87 | LOG_DEBUG("stopped!"); 88 | } 89 | 90 | auto Sound::playSound(uint16_t sound_id, uint8_t channel, uint8_t volume, uint8_t pitch) -> void 91 | { 92 | const LockGuard lock(_mutex); 93 | 94 | LOG_DEBUG("play sound [sound_id: 0x%02x, channel: %d, volume: %d, pitch: %d]", sound_id, channel, volume, pitch); 95 | 96 | auto get_frequency = [](uint8_t pitch) -> uint16_t 97 | { 98 | if(pitch >= countof(Paula::frequencyTable)) { 99 | pitch = 0; 100 | } 101 | return Paula::frequencyTable[pitch]; 102 | }; 103 | 104 | auto get_volume = [](uint8_t volume) -> uint8_t 105 | { 106 | if(volume > 0x3f) { 107 | volume = 0x3f; 108 | } 109 | return volume; 110 | }; 111 | 112 | auto play_sound = [&](Resource* resource) -> void 113 | { 114 | if(resource == nullptr) { 115 | log_alert("resource not found [sound_id: 0x%02x]", sound_id); 116 | } 117 | else if(resource->type != RT_SOUND) { 118 | log_alert("resource is invalid [sound_id: 0x%02x]", sound_id); 119 | } 120 | else if(resource->state != RS_LOADED) { 121 | log_alert("resource not loaded [sound_id: 0x%02x]", sound_id); 122 | } 123 | else { 124 | Data data(resource->data); 125 | const uint32_t data_len = static_cast(data.fetchWordBE()) * 2; 126 | const uint32_t loop_len = static_cast(data.fetchWordBE()) * 2; 127 | const uint16_t unused1 = data.fetchWordBE(); 128 | const uint16_t unused2 = data.fetchWordBE(); 129 | const uint8_t* data_ptr = data.get(); 130 | auto& sample(_samples[channel & 3]); 131 | sample.sample_id = sound_id; 132 | sample.frequency = get_frequency(pitch); 133 | sample.volume = get_volume(volume); 134 | sample.data_ptr = data_ptr; 135 | sample.data_len = data_len; 136 | sample.loop_pos = 0; 137 | sample.loop_len = 0; 138 | sample.unused1 = unused1; 139 | sample.unused2 = unused2; 140 | if(loop_len != 0) { 141 | sample.data_len += loop_len; 142 | sample.loop_pos += data_len; 143 | sample.loop_len += loop_len; 144 | } 145 | } 146 | }; 147 | 148 | return play_sound(_engine.getResource(sound_id)); 149 | } 150 | 151 | auto Sound::stopSound() -> void 152 | { 153 | const LockGuard lock(_mutex); 154 | 155 | for(auto& sample : _samples) { 156 | sample = AudioSample(); 157 | } 158 | } 159 | 160 | auto Sound::startTimer() -> void 161 | { 162 | const LockGuard lock(_mutex); 163 | 164 | auto callback = +[](uint32_t interval, void* data) -> uint32_t 165 | { 166 | return reinterpret_cast(data)->processTimer(); 167 | }; 168 | 169 | if(_timer == 0) { 170 | _currTicks = _engine.getTicks(); 171 | _nextTicks = _currTicks + 20; 172 | _prevTicks = 0; 173 | _timer = _engine.addTimer(callback, (_nextTicks - _prevTicks), this); 174 | } 175 | } 176 | 177 | auto Sound::resetTimer() -> void 178 | { 179 | const LockGuard lock(_mutex); 180 | 181 | if(_timer != 0) { 182 | _currTicks = _engine.getTicks(); 183 | _nextTicks = _currTicks + 20; 184 | _prevTicks = 0; 185 | } 186 | } 187 | 188 | auto Sound::stopTimer() -> void 189 | { 190 | const LockGuard lock(_mutex); 191 | 192 | if(_timer != 0) { 193 | _currTicks = 0; 194 | _nextTicks = 0; 195 | _prevTicks = 0; 196 | _timer = (_engine.removeTimer(_timer), 0); 197 | } 198 | } 199 | 200 | auto Sound::processTimer() -> uint32_t 201 | { 202 | const LockGuard lock(_mutex); 203 | 204 | auto is_ready = [&]() -> bool 205 | { 206 | _currTicks = _engine.getTicks(); 207 | if(_engine.isStopped() || _engine.isPaused()) { 208 | _nextTicks = _currTicks + 100; 209 | return false; 210 | } 211 | if(_currTicks >= _nextTicks) { 212 | _prevTicks = _nextTicks; 213 | return true; 214 | } 215 | return false; 216 | }; 217 | 218 | auto process = [&]() -> void 219 | { 220 | int channel = 0; 221 | for(auto& sample : _samples) { 222 | if(sample.sample_id != 0xffff) { 223 | if(sample.volume != 0) { 224 | _audio.playChannel(channel, sample); 225 | } 226 | else { 227 | _audio.stopChannel(channel); 228 | } 229 | sample = AudioSample(); 230 | } 231 | ++channel; 232 | } 233 | }; 234 | 235 | auto schedule = [&]() -> void 236 | { 237 | const uint32_t delay = 20; 238 | 239 | _currTicks = _engine.getTicks(); 240 | _nextTicks = _prevTicks + delay; 241 | 242 | if(_nextTicks <= _currTicks) { 243 | _nextTicks = _currTicks + 1; 244 | } 245 | }; 246 | 247 | auto delay = [&]() -> uint32_t 248 | { 249 | return _nextTicks - _currTicks; 250 | }; 251 | 252 | if(is_ready()) { 253 | process(); 254 | schedule(); 255 | } 256 | return delay(); 257 | } 258 | 259 | // --------------------------------------------------------------------------- 260 | // End-Of-File 261 | // --------------------------------------------------------------------------- 262 | -------------------------------------------------------------------------------- /src/engine.h: -------------------------------------------------------------------------------- 1 | /* 2 | * engine.h - Copyright (c) 2004-2025 3 | * 4 | * Gregory Montoir, Fabien Sanglard, Olivier Poncet 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 2 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | #ifndef __AW_ENGINE_H__ 20 | #define __AW_ENGINE_H__ 21 | 22 | #include "intern.h" 23 | #include "backend.h" 24 | #include "resources.h" 25 | #include "video.h" 26 | #include "audio.h" 27 | #include "input.h" 28 | #include "vm.h" 29 | 30 | // --------------------------------------------------------------------------- 31 | // Engine 32 | // --------------------------------------------------------------------------- 33 | 34 | class Engine final 35 | : public SubSystem 36 | { 37 | public: // public interface 38 | Engine(const std::string& datadir, const std::string& dumpdir); 39 | 40 | Engine(Engine&&) = delete; 41 | 42 | Engine(const Engine&) = delete; 43 | 44 | Engine& operator=(Engine&&) = delete; 45 | 46 | Engine& operator=(const Engine&) = delete; 47 | 48 | virtual ~Engine() = default; 49 | 50 | virtual auto start() -> void override final; 51 | 52 | virtual auto reset() -> void override final; 53 | 54 | virtual auto stop() -> void override final; 55 | 56 | public: // public interface 57 | auto main() -> void; 58 | 59 | auto getDataDir() const -> const std::string& 60 | { 61 | return _datadir; 62 | } 63 | 64 | auto getDumpDir() const -> const std::string& 65 | { 66 | return _dumpdir; 67 | } 68 | 69 | auto initPart(uint16_t id) -> void; 70 | 71 | auto switchPalettes() -> void; 72 | 73 | public: // public backend interface 74 | auto getTicks() -> uint32_t 75 | { 76 | return _backend->getTicks(); 77 | } 78 | 79 | auto sleepFor(uint32_t delay) -> void 80 | { 81 | return _backend->sleepFor(delay); 82 | } 83 | 84 | auto sleepUntil(uint32_t ticks) -> void 85 | { 86 | return _backend->sleepUntil(ticks); 87 | } 88 | 89 | auto processEvents() -> void 90 | { 91 | return _backend->processEvents(_input.getControls()); 92 | } 93 | 94 | auto updateScreen(const Page& page, const Page& page0, const Page& page1, const Page& page2, const Page& page3, const Palette& palette) -> void 95 | { 96 | return _backend->updateScreen(page, page0, page1, page2, page3, palette); 97 | } 98 | 99 | auto startAudio(AudioCallback callback, void* data) -> void 100 | { 101 | return _backend->startAudio(callback, data); 102 | } 103 | 104 | auto stopAudio() -> void 105 | { 106 | return _backend->stopAudio(); 107 | } 108 | 109 | auto getAudioSampleRate() -> uint32_t 110 | { 111 | return _backend->getAudioSampleRate(); 112 | } 113 | 114 | auto addTimer(TimerCallback callback, uint32_t delay, void* data) -> int 115 | { 116 | return _backend->addTimer(delay, callback, data); 117 | } 118 | 119 | auto removeTimer(int timer) -> void 120 | { 121 | return _backend->removeTimer(timer); 122 | } 123 | 124 | public: // public resource interface 125 | auto getResource(uint16_t resourceId) -> Resource* 126 | { 127 | return _resources.getResource(resourceId); 128 | } 129 | 130 | auto loadResource(uint16_t resourceId) -> void 131 | { 132 | return _resources.loadResource(resourceId); 133 | } 134 | 135 | auto getCurPartId() const -> uint16_t 136 | { 137 | return _resources.getCurPartId(); 138 | } 139 | 140 | auto getReqPartId() const -> uint16_t 141 | { 142 | return _resources.getReqPartId(); 143 | } 144 | 145 | auto requestPartId(uint16_t partId) -> void 146 | { 147 | return _resources.requestPartId(partId); 148 | } 149 | 150 | auto getPolygonData(int index) -> const uint8_t* 151 | { 152 | return _resources.getPolygonData(index); 153 | } 154 | 155 | auto getString(uint16_t id) -> const char* 156 | { 157 | const String* string = _resources.getString(id); 158 | 159 | if(string != nullptr) { 160 | return string->value; 161 | } 162 | return nullptr; 163 | } 164 | 165 | public: // public video interface 166 | auto selectPalette(uint8_t palette) -> void 167 | { 168 | return _video.selectPalette(palette); 169 | } 170 | 171 | auto selectPage(uint8_t dst) -> void 172 | { 173 | return _video.selectPage(dst); 174 | } 175 | 176 | auto fillPage(uint8_t dst, uint8_t col) -> void 177 | { 178 | return _video.fillPage(dst, col); 179 | } 180 | 181 | auto drawBitmap(const uint8_t* bitmap) -> void 182 | { 183 | return _video.drawBitmap(bitmap); 184 | } 185 | 186 | auto copyPage(uint8_t dst, uint8_t src, int16_t vscroll) -> void 187 | { 188 | return _video.copyPage(dst, src, vscroll); 189 | } 190 | 191 | auto showPage(uint8_t src) -> void 192 | { 193 | return _video.showPage(src); 194 | } 195 | 196 | auto drawString(uint16_t id, uint16_t x, uint16_t y, uint8_t color) -> void 197 | { 198 | return _video.drawString(id, x, y, color); 199 | } 200 | 201 | auto drawPolygons(const uint8_t* buffer, uint16_t offset, const Point& point, uint16_t zoom) -> void 202 | { 203 | return _video.drawPolygons(buffer, offset, point, zoom); 204 | } 205 | 206 | public: // public audio interface 207 | auto playSound(uint16_t id, uint8_t channel, uint8_t volume, uint8_t frequency) -> void 208 | { 209 | return _audio.playSound(id, channel, volume, frequency); 210 | } 211 | 212 | auto playMusic(uint16_t id, uint8_t position, uint16_t delay) -> void 213 | { 214 | return _audio.playMusic(id, position, delay); 215 | } 216 | 217 | public: // public input interface 218 | auto getControls() -> Controls& 219 | { 220 | return _input.getControls(); 221 | } 222 | 223 | auto getControls() const -> const Controls& 224 | { 225 | return _input.getControls(); 226 | } 227 | 228 | auto isRunning() const -> bool 229 | { 230 | const Controls& controls(_input.getControls()); 231 | 232 | return controls.quit == false; 233 | } 234 | 235 | auto isStopped() const -> bool 236 | { 237 | const Controls& controls(_input.getControls()); 238 | 239 | return controls.quit != false; 240 | } 241 | 242 | auto isPaused() const -> bool 243 | { 244 | const Controls& controls(_input.getControls()); 245 | 246 | return controls.pause != false; 247 | } 248 | 249 | public: // public vm interface 250 | auto setMusicMark(uint16_t value) -> void 251 | { 252 | return _vm.setRegister(VM_VARIABLE_MUSIC_MARK, value); 253 | } 254 | 255 | auto processVirtualMachine() -> void 256 | { 257 | return _vm.run(_input.getControls()); 258 | } 259 | 260 | private: // private interface 261 | struct Emscripten; 262 | 263 | private: // private data 264 | const std::string _datadir; 265 | const std::string _dumpdir; 266 | uint8_t _palmode; 267 | const BackendPtr _backend; 268 | Resources _resources; 269 | Video _video; 270 | Audio _audio; 271 | Input _input; 272 | VirtualMachine _vm; 273 | }; 274 | 275 | // --------------------------------------------------------------------------- 276 | // End-Of-File 277 | // --------------------------------------------------------------------------- 278 | 279 | #endif /* __AW_ENGINE_H__ */ 280 | -------------------------------------------------------------------------------- /src/mixer.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * mixer.cc - Copyright (c) 2004-2025 3 | * 4 | * Gregory Montoir, Fabien Sanglard, Olivier Poncet 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 2 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | #ifdef HAVE_CONFIG_H 20 | #include "config.h" 21 | #endif 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include "logger.h" 41 | #include "engine.h" 42 | #include "mixer.h" 43 | 44 | // --------------------------------------------------------------------------- 45 | // some useful macros 46 | // --------------------------------------------------------------------------- 47 | 48 | #ifndef NDEBUG 49 | #define LOG_DEBUG(format, ...) log_debug(SYS_MIXER, format, ##__VA_ARGS__) 50 | #else 51 | #define LOG_DEBUG(format, ...) do {} while(0) 52 | #endif 53 | 54 | // --------------------------------------------------------------------------- 55 | // Mixer 56 | // --------------------------------------------------------------------------- 57 | 58 | Mixer::Mixer(Engine& engine, Audio& audio) 59 | : SubSystem(engine, "Mixer") 60 | , _audio(audio) 61 | , _channels() 62 | , _samplerate(engine.getAudioSampleRate()) 63 | { 64 | int count = 0; 65 | for(auto& channel : _channels) { 66 | channel.channel_id = count++; 67 | } 68 | static_cast(_audio); // avoid unused value warning 69 | } 70 | 71 | auto Mixer::start() -> void 72 | { 73 | LOG_DEBUG("starting..."); 74 | stopAllChannels(); 75 | startAudio(); 76 | LOG_DEBUG("started!"); 77 | } 78 | 79 | auto Mixer::reset() -> void 80 | { 81 | LOG_DEBUG("resetting..."); 82 | stopAllChannels(); 83 | resetAudio(); 84 | LOG_DEBUG("reset!"); 85 | } 86 | 87 | auto Mixer::stop() -> void 88 | { 89 | LOG_DEBUG("stopping..."); 90 | stopAllChannels(); 91 | stopAudio(); 92 | LOG_DEBUG("stopped!"); 93 | } 94 | 95 | auto Mixer::playAllChannels() -> void 96 | { 97 | const LockGuard lock(_mutex); 98 | 99 | for(auto& channel : _channels) { 100 | LOG_DEBUG("play channel [channel: %d]", channel.channel_id); 101 | channel.active = true; 102 | } 103 | } 104 | 105 | auto Mixer::stopAllChannels() -> void 106 | { 107 | const LockGuard lock(_mutex); 108 | 109 | for(auto& channel : _channels) { 110 | LOG_DEBUG("stop channel [channel: %d]", channel.channel_id); 111 | channel.active = false; 112 | } 113 | } 114 | 115 | auto Mixer::playChannel(uint8_t index, const AudioSample& sample) -> void 116 | { 117 | const LockGuard lock(_mutex); 118 | 119 | if(index < countof(_channels)) { 120 | AudioChannel& channel(_channels[index]); 121 | LOG_DEBUG("play channel [channel: %d, sample: 0x%02x, frequency: %d, volume: %d]", channel.channel_id, sample.sample_id, sample.frequency, sample.volume); 122 | channel.active = true; 123 | channel.volume = sample.volume; 124 | channel.sample_id = sample.sample_id; 125 | channel.data_ptr = sample.data_ptr; 126 | channel.data_len = static_cast(sample.data_len); 127 | channel.data_pos = static_cast(0); 128 | channel.data_inc = (static_cast(sample.frequency) << 8) / _samplerate; 129 | channel.loop_pos = static_cast(sample.loop_pos); 130 | channel.loop_len = static_cast(sample.loop_len); 131 | } 132 | } 133 | 134 | auto Mixer::stopChannel(uint8_t index) -> void 135 | { 136 | const LockGuard lock(_mutex); 137 | 138 | if(index < countof(_channels)) { 139 | AudioChannel& channel = _channels[index]; 140 | LOG_DEBUG("stop channel [channel: %d]", channel.channel_id); 141 | channel.active = false; 142 | } 143 | } 144 | 145 | auto Mixer::setChannelVolume(uint8_t index, uint8_t volume) -> void 146 | { 147 | const LockGuard lock(_mutex); 148 | 149 | if(index < countof(_channels)) { 150 | AudioChannel& channel = _channels[index]; 151 | LOG_DEBUG("set channel volume [channel: %d, volume: %d]", channel.channel_id); 152 | channel.volume = volume; 153 | } 154 | } 155 | 156 | auto Mixer::startAudio() -> void 157 | { 158 | const LockGuard lock(_mutex); 159 | 160 | auto callback = +[](void* data, uint8_t* buffer, int length) -> void 161 | { 162 | static_cast(::memset(buffer, 0, length)); 163 | 164 | reinterpret_cast(data)->processAudio(reinterpret_cast(buffer), (length / sizeof(float))); 165 | }; 166 | 167 | _engine.startAudio(callback, this); 168 | } 169 | 170 | auto Mixer::resetAudio() -> void 171 | { 172 | const LockGuard lock(_mutex); 173 | 174 | // _engine.resetAudio(); 175 | } 176 | 177 | auto Mixer::stopAudio() -> void 178 | { 179 | const LockGuard lock(_mutex); 180 | 181 | _engine.stopAudio(); 182 | } 183 | 184 | auto Mixer::processAudio(float* buffer, int length) -> void 185 | { 186 | const LockGuard lock(_mutex); 187 | 188 | auto add_clamp = [](const float a, const float b) -> float 189 | { 190 | float value = a + b; 191 | if(value < -1.0f) value = -1.0f; 192 | if(value > +1.0f) value = +1.0f; 193 | return value; 194 | }; 195 | 196 | auto mix_one_channel = [&](AudioChannel& channel) -> void 197 | { 198 | float* bufptr = buffer; 199 | auto const volume = channel.volume; 200 | auto const data_ptr = channel.data_ptr; 201 | auto const data_end = channel.data_len; 202 | auto curr_pos = channel.data_pos; 203 | auto next_pos = channel.data_pos; 204 | auto const data_inc = channel.data_inc; 205 | auto const loop_pos = channel.loop_pos; 206 | auto const loop_len = channel.loop_len; 207 | auto const loop_end = loop_pos + loop_len; 208 | for(int count = length; count != 0; --count) { 209 | curr_pos = next_pos; 210 | next_pos += data_inc; 211 | uint32_t p1 = curr_pos >> 8; 212 | uint32_t p2 = p1 + 1; 213 | if(loop_len == 0) { 214 | if(p2 >= data_end) { 215 | channel.active = false; 216 | next_pos = 0; 217 | break; 218 | } 219 | } 220 | else { 221 | if(p2 >= loop_end) { 222 | p2 = loop_pos; 223 | next_pos = (loop_pos << 8); 224 | } 225 | } 226 | const int32_t ic = (curr_pos & 0xff); // retrieve reminder 227 | const int32_t i1 = (0xff - ic); // compute 1st factor 228 | const int32_t i2 = (0x00 + ic); // compute 2nd factor 229 | const int32_t s1 = static_cast(data_ptr[p1]); // 1st sample 230 | const int32_t s2 = static_cast(data_ptr[p2]); // 2nd sample 231 | const int32_t s3 = ((s1 * i1) + (s2 * i2)) >> 8; // interpolate samples 232 | const float sample = static_cast(s3 * volume) / (63.0f * 128.0f * 4.0f); 233 | *bufptr = add_clamp(*bufptr, sample); 234 | ++bufptr; 235 | } 236 | channel.data_pos = next_pos; 237 | }; 238 | 239 | auto mix_all_channels = [&]() -> void 240 | { 241 | if(_engine.isPaused()) { 242 | return; 243 | } 244 | for(auto& channel : _channels) { 245 | if((channel.active != 0) && (channel.data_ptr != nullptr)) { 246 | mix_one_channel(channel); 247 | } 248 | } 249 | }; 250 | 251 | return mix_all_channels(); 252 | } 253 | 254 | // --------------------------------------------------------------------------- 255 | // End-Of-File 256 | // --------------------------------------------------------------------------- 257 | -------------------------------------------------------------------------------- /src/resources.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * resources.cc - Copyright (c) 2004-2025 3 | * 4 | * Gregory Montoir, Fabien Sanglard, Olivier Poncet 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 2 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | #ifdef HAVE_CONFIG_H 20 | #include "config.h" 21 | #endif 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include "logger.h" 41 | #include "engine.h" 42 | #include "file.h" 43 | #include "resources.h" 44 | 45 | // --------------------------------------------------------------------------- 46 | // some useful macros 47 | // --------------------------------------------------------------------------- 48 | 49 | #ifndef NDEBUG 50 | #define LOG_DEBUG(format, ...) log_debug(SYS_RESOURCES, format, ##__VA_ARGS__) 51 | #else 52 | #define LOG_DEBUG(format, ...) do {} while(0) 53 | #endif 54 | 55 | // --------------------------------------------------------------------------- 56 | // Resources 57 | // --------------------------------------------------------------------------- 58 | 59 | Resources::Resources(Engine& engine) 60 | : SubSystem(engine, "Resources") 61 | , _buffer() 62 | , _bufptr(_buffer) 63 | , _bufend(_buffer + countof(_buffer)) 64 | , _dictionaryId(Dictionary::DIC_DEFAULT) 65 | , _resourcesArray() 66 | , _resourcesCount(0) 67 | , _curPartId(0) 68 | , _reqPartId(0) 69 | , _segPalettes(nullptr) 70 | , _segByteCode(nullptr) 71 | , _segPolygon1(nullptr) 72 | , _segPolygon2(nullptr) 73 | { 74 | } 75 | 76 | auto Resources::start() -> void 77 | { 78 | LOG_DEBUG("starting..."); 79 | LOG_DEBUG("buffer size is %luK", (sizeof(_buffer) / 1024)); 80 | loadMemList(); 81 | LOG_DEBUG("started!"); 82 | } 83 | 84 | auto Resources::reset() -> void 85 | { 86 | LOG_DEBUG("resetting..."); 87 | LOG_DEBUG("reset!"); 88 | } 89 | 90 | auto Resources::stop() -> void 91 | { 92 | LOG_DEBUG("stopping..."); 93 | invalidateAll(); 94 | LOG_DEBUG("stopped!"); 95 | } 96 | 97 | auto Resources::loadPart(uint16_t partId) -> void 98 | { 99 | const GamePart* part = nullptr; 100 | 101 | if(partId == 0) { 102 | if((partId = _reqPartId) != 0) { 103 | _reqPartId = 0; 104 | } 105 | else { 106 | return; 107 | } 108 | } 109 | if((partId >= GAME_PART_FIRST) && (partId <= GAME_PART_LAST)) { 110 | part = &GameParts::data[partId - GAME_PART_FIRST]; 111 | LOG_DEBUG("load part [id: 0x%04x, name: '%s']", partId, part->name); 112 | _curPartId = partId; 113 | _reqPartId = 0; 114 | } 115 | else { 116 | log_error("cannot load invalid part <0x%04x>", partId); 117 | } 118 | #ifndef PRELOAD_RESOURCES 119 | invalidateAll(); 120 | #endif 121 | const uint8_t palettesId = part->palettes; 122 | const uint8_t bytecodeId = part->bytecode; 123 | const uint8_t polygon1Id = part->polygon1; 124 | const uint8_t polygon2Id = part->polygon2; 125 | 126 | if(palettesId != 0x00) { 127 | loadResource(palettesId); 128 | } 129 | if(bytecodeId != 0x00) { 130 | loadResource(bytecodeId); 131 | } 132 | if(polygon1Id != 0x00) { 133 | loadResource(polygon1Id); 134 | } 135 | if(polygon2Id != 0x00) { 136 | loadResource(polygon2Id); 137 | } 138 | 139 | _segPalettes = _resourcesArray[palettesId].data; 140 | _segByteCode = _resourcesArray[bytecodeId].data; 141 | _segPolygon1 = _resourcesArray[polygon1Id].data; 142 | _segPolygon2 = _resourcesArray[polygon2Id].data; 143 | } 144 | 145 | auto Resources::loadResource(uint16_t resourceId) -> void 146 | { 147 | if(resourceId >= countof(_resourcesArray)) { 148 | LOG_DEBUG("load resource [part: 0x%04x]", resourceId); 149 | _reqPartId = resourceId; 150 | } 151 | else { 152 | Resource& resource = _resourcesArray[resourceId]; 153 | if(resource.state != RS_LOADED) { 154 | resource.state = RS_NEEDED; 155 | loadResources(); 156 | } 157 | if(resource.state == RS_LOADED) { 158 | if(resource.type == RT_BITMAP) { 159 | _engine.drawBitmap(resource.data); 160 | } 161 | } 162 | } 163 | } 164 | 165 | auto Resources::getString(uint16_t stringId) -> const String* 166 | { 167 | auto get_string_en = [&]() -> const String* 168 | { 169 | for(auto& string : Dictionary::dataEN) { 170 | if(string.id == 0xffff) { 171 | break; 172 | } 173 | if(stringId == string.id) { 174 | return &string; 175 | } 176 | } 177 | return nullptr; 178 | }; 179 | 180 | auto get_string_fr = [&]() -> const String* 181 | { 182 | for(auto& string : Dictionary::dataFR) { 183 | if(string.id == 0xffff) { 184 | break; 185 | } 186 | if(stringId == string.id) { 187 | return &string; 188 | } 189 | } 190 | return nullptr; 191 | }; 192 | 193 | auto get_string = [&]() -> const String* 194 | { 195 | switch(_dictionaryId) { 196 | case Dictionary::DIC_DEFAULT: 197 | case Dictionary::DIC_ENGLISH: 198 | return get_string_en(); 199 | case Dictionary::DIC_FRENCH: 200 | return get_string_fr(); 201 | default: 202 | break; 203 | } 204 | return nullptr; 205 | }; 206 | 207 | return get_string(); 208 | } 209 | 210 | auto Resources::loadMemList() -> void 211 | { 212 | LOG_DEBUG("load memlist"); 213 | 214 | MemList memlist(_engine.getDataDir(), _engine.getDumpDir()); 215 | if(memlist.loadMemList(_resourcesArray) != false) { 216 | _bufptr = _buffer; 217 | _resourcesCount = 0; 218 | for(auto& resource : _resourcesArray) { 219 | if(resource.type == RT_END) { 220 | break; 221 | } 222 | ++_resourcesCount; 223 | #ifndef PRELOAD_RESOURCES 224 | resource.state = RS_NOT_NEEDED; 225 | #else 226 | resource.state = RS_NEEDED; 227 | #endif 228 | resource.data = nullptr; 229 | } 230 | loadResources(); 231 | } 232 | else { 233 | log_fatal("error while loading memlist"); 234 | } 235 | } 236 | 237 | auto Resources::loadResources() -> void 238 | { 239 | MemList memlist(_engine.getDataDir(), _engine.getDumpDir()); 240 | for(auto& resource : _resourcesArray) { 241 | if(resource.type == RT_END) { 242 | break; 243 | } 244 | if(resource.state == RS_NEEDED) { 245 | LOG_DEBUG("load resource [resource: 0x%02x, bank: 0x%02x]", resource.id, resource.bank_id); 246 | resource.data = _bufptr; 247 | _bufptr += ((resource.unpacked_size + 1) + 3) & ~3; 248 | if(_bufptr > _bufend) { 249 | log_fatal("cannot load resource, not enough memory"); 250 | } 251 | if(memlist.loadResource(resource) != false) { 252 | resource.state = RS_LOADED; 253 | memlist.dumpResource(resource); 254 | } 255 | else { 256 | log_fatal("error while loading resource"); 257 | } 258 | } 259 | } 260 | LOG_DEBUG("resources memory [consumed: %luK, remaining: %luK]", ((_bufptr - _buffer) / 1024), ((_bufend - _bufptr) / 1024)); 261 | } 262 | 263 | auto Resources::invalidateAll() -> void 264 | { 265 | for(auto& resource : _resourcesArray) { 266 | if(resource.type == RT_END) { 267 | break; 268 | } 269 | LOG_DEBUG("invalidate resource [resource: 0x%02x]", resource.id); 270 | resource.state = RS_NOT_NEEDED; 271 | resource.data = nullptr; 272 | } 273 | _bufptr = _buffer; 274 | } 275 | 276 | // --------------------------------------------------------------------------- 277 | // End-Of-File 278 | // --------------------------------------------------------------------------- 279 | -------------------------------------------------------------------------------- /src/main.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * main.cc - Copyright (c) 2004-2025 3 | * 4 | * Gregory Montoir, Fabien Sanglard, Olivier Poncet 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 2 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | #ifdef HAVE_CONFIG_H 20 | #include "config.h" 21 | #endif 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include "logger.h" 41 | #include "engine.h" 42 | #include "main.h" 43 | 44 | // --------------------------------------------------------------------------- 45 | // ::globals 46 | // --------------------------------------------------------------------------- 47 | 48 | namespace { 49 | 50 | std::string g_aw_program = "another-world"; 51 | std::string g_aw_datadir = "share/another-world"; 52 | std::string g_aw_dumpdir = ""; 53 | bool g_aw_usage = false; 54 | 55 | } 56 | 57 | // --------------------------------------------------------------------------- 58 | // ::CommandLine 59 | // --------------------------------------------------------------------------- 60 | 61 | namespace { 62 | 63 | struct CommandLine 64 | { 65 | static auto program(const std::string& arg) -> bool 66 | { 67 | const char* str = arg.c_str(); 68 | const char* sep = ::strrchr(str, '/'); 69 | if(sep != nullptr) { 70 | g_aw_program = (sep + 1); 71 | } 72 | else { 73 | g_aw_program = arg; 74 | } 75 | return true; 76 | } 77 | 78 | static auto option(const std::string& arg) -> bool 79 | { 80 | const char* str = arg.c_str(); 81 | const char* equ = ::strchr(str, '='); 82 | if(equ == nullptr) { 83 | if((arg == "-h") || (arg == "--help")) { 84 | g_aw_usage = true; 85 | return true; 86 | } 87 | if(arg == "--quiet") { 88 | g_logger_mask &= ~LOG_DEBUG; 89 | g_logger_mask &= ~LOG_PRINT; 90 | g_logger_mask &= ~LOG_ALERT; 91 | g_logger_mask &= ~LOG_ERROR; 92 | g_logger_mask &= ~LOG_FATAL; 93 | return true; 94 | } 95 | if(arg == "--debug") { 96 | g_logger_mask |= LOG_DEBUG; 97 | return true; 98 | } 99 | if(arg == "--debug-all") { 100 | g_logger_mask |= LOG_DEBUG; 101 | g_logger_mask |= SYS_ENGINE; 102 | g_logger_mask |= SYS_BACKEND; 103 | g_logger_mask |= SYS_RESOURCES; 104 | g_logger_mask |= SYS_VIDEO; 105 | g_logger_mask |= SYS_AUDIO; 106 | g_logger_mask |= SYS_MIXER; 107 | g_logger_mask |= SYS_SOUND; 108 | g_logger_mask |= SYS_MUSIC; 109 | g_logger_mask |= SYS_INPUT; 110 | g_logger_mask |= SYS_VM; 111 | return true; 112 | } 113 | if(arg == "--debug-engine") { 114 | g_logger_mask |= LOG_DEBUG; 115 | g_logger_mask |= SYS_ENGINE; 116 | return true; 117 | } 118 | if(arg == "--debug-backend") { 119 | g_logger_mask |= LOG_DEBUG; 120 | g_logger_mask |= SYS_BACKEND; 121 | return true; 122 | } 123 | if(arg == "--debug-resources") { 124 | g_logger_mask |= LOG_DEBUG; 125 | g_logger_mask |= SYS_RESOURCES; 126 | return true; 127 | } 128 | if(arg == "--debug-video") { 129 | g_logger_mask |= LOG_DEBUG; 130 | g_logger_mask |= SYS_VIDEO; 131 | return true; 132 | } 133 | if(arg == "--debug-audio") { 134 | g_logger_mask |= LOG_DEBUG; 135 | g_logger_mask |= SYS_AUDIO; 136 | return true; 137 | } 138 | if(arg == "--debug-mixer") { 139 | g_logger_mask |= LOG_DEBUG; 140 | g_logger_mask |= SYS_MIXER; 141 | return true; 142 | } 143 | if(arg == "--debug-sound") { 144 | g_logger_mask |= LOG_DEBUG; 145 | g_logger_mask |= SYS_SOUND; 146 | return true; 147 | } 148 | if(arg == "--debug-music") { 149 | g_logger_mask |= LOG_DEBUG; 150 | g_logger_mask |= SYS_MUSIC; 151 | return true; 152 | } 153 | if(arg == "--debug-input") { 154 | g_logger_mask |= LOG_DEBUG; 155 | g_logger_mask |= SYS_INPUT; 156 | return true; 157 | } 158 | if(arg == "--debug-vm") { 159 | g_logger_mask |= LOG_DEBUG; 160 | g_logger_mask |= SYS_VM; 161 | return true; 162 | } 163 | } 164 | else { 165 | const size_t len = (equ - str); 166 | if(arg.compare(0, len, "--datadir") == 0) { 167 | g_aw_datadir = (equ + 1); 168 | return true; 169 | } 170 | if(arg.compare(0, len, "--dumpdir") == 0) { 171 | g_aw_dumpdir = (equ + 1); 172 | return true; 173 | } 174 | } 175 | return false; 176 | } 177 | 178 | static auto usage() -> void 179 | { 180 | std::cout << "Usage: " << g_aw_program << " [OPTIONS...]" << std::endl; 181 | std::cout << "" << std::endl; 182 | std::cout << "Options:" << std::endl; 183 | std::cout << "" << std::endl; 184 | std::cout << " -h, --help display this help and exit" << std::endl; 185 | std::cout << "" << std::endl; 186 | std::cout << " --datadir=PATH directory where data files are stored" << std::endl; 187 | std::cout << " --dumpdir=PATH directory where dump files are stored" << std::endl; 188 | std::cout << "" << std::endl; 189 | std::cout << " --quiet quiet mode" << std::endl; 190 | std::cout << " --debug debug mode" << std::endl; 191 | std::cout << " --debug-all debug all subsystems" << std::endl; 192 | std::cout << " --debug-engine debug the engine subsystem" << std::endl; 193 | std::cout << " --debug-backend debug the backend subsystem" << std::endl; 194 | std::cout << " --debug-resources debug the resources subsystem" << std::endl; 195 | std::cout << " --debug-video debug the video subsystem" << std::endl; 196 | std::cout << " --debug-audio debug the audio subsystem" << std::endl; 197 | std::cout << " --debug-mixer debug the mixer subsystem" << std::endl; 198 | std::cout << " --debug-sound debug the sound subsystem" << std::endl; 199 | std::cout << " --debug-music debug the music subsystem" << std::endl; 200 | std::cout << " --debug-input debug the input subsystem" << std::endl; 201 | std::cout << " --debug-vm debug the vm subsystem" << std::endl; 202 | std::cout << "" << std::endl; 203 | } 204 | }; 205 | 206 | } 207 | 208 | // --------------------------------------------------------------------------- 209 | // ::Program 210 | // --------------------------------------------------------------------------- 211 | 212 | namespace { 213 | 214 | struct Program 215 | { 216 | static auto main() -> int 217 | { 218 | EnginePtr engine; 219 | 220 | try { 221 | engine = std::make_unique(g_aw_datadir, g_aw_dumpdir); 222 | engine->main(); 223 | } 224 | catch(const Panic& panic) { 225 | panic(); 226 | return EXIT_FAILURE; 227 | } 228 | catch(...) { 229 | std::cerr << "unhandled exception" << std::endl; 230 | return EXIT_FAILURE; 231 | } 232 | return EXIT_SUCCESS; 233 | } 234 | }; 235 | 236 | } 237 | 238 | // --------------------------------------------------------------------------- 239 | // main 240 | // --------------------------------------------------------------------------- 241 | 242 | int main(int argc, char* argv[]) 243 | { 244 | #ifdef __EMSCRIPTEN__ 245 | g_logger_mask &= ~LOG_DEBUG; 246 | g_logger_mask &= ~LOG_PRINT; 247 | g_logger_mask &= ~LOG_ALERT; 248 | g_logger_mask &= ~LOG_ERROR; 249 | g_logger_mask &= ~LOG_FATAL; 250 | #else 251 | g_logger_mask &= ~LOG_DEBUG; 252 | #endif 253 | for(int argi = 0; argi < argc; ++argi) { 254 | const std::string arg(argv[argi]); 255 | if(argi == 0) { 256 | if(CommandLine::program(arg) == false) { 257 | std::cerr << "Error: invalid argument " << '<' << arg << '>' << std::endl; 258 | return EXIT_FAILURE; 259 | } 260 | } 261 | else { 262 | if(CommandLine::option(arg) == false) { 263 | std::cerr << "Error: invalid argument " << '<' << arg << '>' << std::endl; 264 | return EXIT_FAILURE; 265 | } 266 | } 267 | if(g_aw_usage != false) { 268 | CommandLine::usage(); 269 | return EXIT_SUCCESS; 270 | } 271 | #ifdef NDEBUG 272 | if(g_logger_mask & LOG_DEBUG) { 273 | log_alert("debug mode was requested but the program was built in release mode"); 274 | log_alert("no debugging information will be generated"); 275 | } 276 | #endif 277 | } 278 | return Program::main(); 279 | } 280 | 281 | // --------------------------------------------------------------------------- 282 | // End-Of-File 283 | // --------------------------------------------------------------------------- 284 | -------------------------------------------------------------------------------- /src/file.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * file.cc - Copyright (c) 2004-2025 3 | * 4 | * Gregory Montoir, Fabien Sanglard, Olivier Poncet 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 2 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | #ifdef HAVE_CONFIG_H 20 | #include "config.h" 21 | #endif 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include "logger.h" 42 | #include "file.h" 43 | 44 | // --------------------------------------------------------------------------- 45 | // FileNullImpl 46 | // --------------------------------------------------------------------------- 47 | 48 | class FileNullImpl final 49 | : public FileImpl 50 | { 51 | public: // public interface 52 | FileNullImpl(); 53 | 54 | FileNullImpl(FileNullImpl&&) = delete; 55 | 56 | FileNullImpl(const FileNullImpl&) = delete; 57 | 58 | FileNullImpl& operator=(FileNullImpl&&) = delete; 59 | 60 | FileNullImpl& operator=(const FileNullImpl&) = delete; 61 | 62 | virtual ~FileNullImpl() = default; 63 | 64 | virtual auto open(const std::string& path, const std::string& mode) -> bool override final; 65 | 66 | virtual auto close()-> bool override final; 67 | 68 | virtual auto seek(int32_t offset) -> bool override final; 69 | 70 | virtual auto read(void* buffer, uint32_t length) -> bool override final; 71 | 72 | virtual auto write(void* buffer, uint32_t length) -> bool override final; 73 | }; 74 | 75 | // --------------------------------------------------------------------------- 76 | // FileStdioImpl 77 | // --------------------------------------------------------------------------- 78 | 79 | class FileStdioImpl final 80 | : public FileImpl 81 | { 82 | public: // public interface 83 | FileStdioImpl(); 84 | 85 | FileStdioImpl(FileStdioImpl&&) = delete; 86 | 87 | FileStdioImpl(const FileStdioImpl&) = delete; 88 | 89 | FileStdioImpl& operator=(FileStdioImpl&&) = delete; 90 | 91 | FileStdioImpl& operator=(const FileStdioImpl&) = delete; 92 | 93 | virtual ~FileStdioImpl(); 94 | 95 | virtual auto open(const std::string& path, const std::string& mode) -> bool override final; 96 | 97 | virtual auto close()-> bool override final; 98 | 99 | virtual auto seek(int32_t offset) -> bool override final; 100 | 101 | virtual auto read(void* buffer, uint32_t length) -> bool override final; 102 | 103 | virtual auto write(void* buffer, uint32_t length) -> bool override final; 104 | 105 | private: // private data 106 | FILE* _file; 107 | }; 108 | 109 | // --------------------------------------------------------------------------- 110 | // FileZlibImpl 111 | // --------------------------------------------------------------------------- 112 | 113 | class FileZlibImpl final 114 | : public FileImpl 115 | { 116 | public: // public interface 117 | FileZlibImpl(); 118 | 119 | FileZlibImpl(FileZlibImpl&&) = delete; 120 | 121 | FileZlibImpl(const FileZlibImpl&) = delete; 122 | 123 | FileZlibImpl& operator=(FileZlibImpl&&) = delete; 124 | 125 | FileZlibImpl& operator=(const FileZlibImpl&) = delete; 126 | 127 | virtual ~FileZlibImpl(); 128 | 129 | virtual auto open(const std::string& path, const std::string& mode) -> bool override final; 130 | 131 | virtual auto close()-> bool override final; 132 | 133 | virtual auto seek(int32_t offset) -> bool override final; 134 | 135 | virtual auto read(void* buffer, uint32_t length) -> bool override final; 136 | 137 | virtual auto write(void* buffer, uint32_t length) -> bool override final; 138 | 139 | private: // private data 140 | gzFile _file; 141 | }; 142 | 143 | // --------------------------------------------------------------------------- 144 | // File 145 | // --------------------------------------------------------------------------- 146 | 147 | File::File(const std::string& impl) 148 | : _impl(nullptr) 149 | { 150 | if(!_impl && (impl == "stdio")) { 151 | _impl = std::make_unique(); 152 | } 153 | if(!_impl && (impl == "zlib")) { 154 | _impl = std::make_unique(); 155 | } 156 | if(!_impl) { 157 | _impl = std::make_unique(); 158 | } 159 | } 160 | 161 | auto File::open(const std::string& path, const std::string& mode) -> bool 162 | { 163 | const bool status = _impl->open(path, mode); 164 | 165 | if(status == false) { 166 | log_error("error while opening file '%s'", path.c_str()); 167 | } 168 | return status; 169 | } 170 | 171 | auto File::close() -> bool 172 | { 173 | const bool status = _impl->close(); 174 | 175 | if(status == false) { 176 | log_error("error while closing file"); 177 | } 178 | return status; 179 | } 180 | 181 | auto File::seek(int32_t offset) -> bool 182 | { 183 | const bool status = _impl->seek(offset); 184 | 185 | if(status == false) { 186 | log_error("error while seeking %d bytes in file", offset); 187 | } 188 | return status; 189 | } 190 | 191 | auto File::read(void* buffer, uint32_t length) -> bool 192 | { 193 | const bool status = _impl->read(buffer, length); 194 | 195 | if(status == false) { 196 | log_error("error while reading %u bytes from file", length); 197 | } 198 | return status; 199 | } 200 | 201 | auto File::write(void* buffer, uint32_t length) -> bool 202 | { 203 | const bool status = _impl->write(buffer, length); 204 | 205 | if(status == false) { 206 | log_error("error while writing %u bytes into file", length); 207 | } 208 | return status; 209 | } 210 | 211 | auto File::ioOk() const -> bool 212 | { 213 | return _impl->ioOk(); 214 | } 215 | 216 | auto File::ioErr() const -> bool 217 | { 218 | return _impl->ioErr(); 219 | } 220 | 221 | // --------------------------------------------------------------------------- 222 | // FileNullImpl 223 | // --------------------------------------------------------------------------- 224 | 225 | FileNullImpl::FileNullImpl() 226 | : FileImpl() 227 | { 228 | } 229 | 230 | auto FileNullImpl::open(const std::string& path, const std::string& mode) -> bool 231 | { 232 | _fail = true; 233 | 234 | return false; 235 | } 236 | 237 | auto FileNullImpl::close() -> bool 238 | { 239 | _fail = true; 240 | 241 | return false; 242 | } 243 | 244 | auto FileNullImpl::seek(int32_t offset) -> bool 245 | { 246 | _fail = true; 247 | 248 | return false; 249 | } 250 | 251 | auto FileNullImpl::read(void* buffer, uint32_t length) -> bool 252 | { 253 | _fail = true; 254 | 255 | return false; 256 | } 257 | 258 | auto FileNullImpl::write(void* buffer, uint32_t length) -> bool 259 | { 260 | _fail = true; 261 | 262 | return false; 263 | } 264 | 265 | // --------------------------------------------------------------------------- 266 | // FileStdioImpl 267 | // --------------------------------------------------------------------------- 268 | 269 | FileStdioImpl::FileStdioImpl() 270 | : FileImpl() 271 | , _file(nullptr) 272 | { 273 | } 274 | 275 | FileStdioImpl::~FileStdioImpl() 276 | { 277 | static_cast(close()); 278 | } 279 | 280 | auto FileStdioImpl::open(const std::string& path, const std::string& mode) -> bool 281 | { 282 | if(_file != nullptr) { 283 | _file = (::fclose(_file), nullptr); 284 | _fail = false; 285 | } 286 | if(_file == nullptr) { 287 | _file = ::fopen(path.c_str(), mode.c_str()); 288 | if(_file == nullptr) { 289 | _fail = true; 290 | } 291 | else { 292 | _fail = false; 293 | } 294 | } 295 | return _fail == false; 296 | } 297 | 298 | auto FileStdioImpl::close() -> bool 299 | { 300 | if(_file != nullptr) { 301 | _file = (::fclose(_file), nullptr); 302 | _fail = false; 303 | } 304 | else { 305 | _fail = false; 306 | } 307 | return _fail == false; 308 | } 309 | 310 | auto FileStdioImpl::seek(int32_t offset) -> bool 311 | { 312 | if(_file != nullptr) { 313 | const int rc = ::fseek(_file, offset, SEEK_SET); 314 | if(rc != 0) { 315 | _fail = true; 316 | } 317 | else { 318 | _fail = false; 319 | } 320 | } 321 | else { 322 | _fail = true; 323 | } 324 | return _fail == false; 325 | } 326 | 327 | auto FileStdioImpl::read(void* buffer, uint32_t length) -> bool 328 | { 329 | if(_file != nullptr) { 330 | const auto rc = ::fread(buffer, 1, length, _file); 331 | if(static_cast(rc) != length) { 332 | _fail = true; 333 | } 334 | else { 335 | _fail = false; 336 | } 337 | } 338 | else { 339 | _fail = true; 340 | } 341 | return _fail == false; 342 | } 343 | 344 | auto FileStdioImpl::write(void* buffer, uint32_t length) -> bool 345 | { 346 | if(_file != nullptr) { 347 | const auto rc = ::fwrite(buffer, 1, length, _file); 348 | if(static_cast(rc) != length) { 349 | _fail = true; 350 | } 351 | else { 352 | _fail = false; 353 | } 354 | } 355 | else { 356 | _fail = true; 357 | } 358 | return _fail == false; 359 | } 360 | 361 | // --------------------------------------------------------------------------- 362 | // FileZlibImpl 363 | // --------------------------------------------------------------------------- 364 | 365 | FileZlibImpl::FileZlibImpl() 366 | : FileImpl() 367 | , _file(nullptr) 368 | { 369 | } 370 | 371 | FileZlibImpl::~FileZlibImpl() 372 | { 373 | static_cast(close()); 374 | } 375 | 376 | auto FileZlibImpl::open(const std::string& path, const std::string& mode) -> bool 377 | { 378 | if(_file != nullptr) { 379 | _file = (::gzclose(_file), nullptr); 380 | _fail = false; 381 | } 382 | if(_file == nullptr) { 383 | _file = ::gzopen(path.c_str(), mode.c_str()); 384 | if(_file == nullptr) { 385 | _fail = true; 386 | } 387 | else { 388 | _fail = false; 389 | } 390 | } 391 | return _fail == false; 392 | } 393 | 394 | auto FileZlibImpl::close() -> bool 395 | { 396 | if(_file != nullptr) { 397 | _file = (::gzclose(_file), nullptr); 398 | _fail = false; 399 | } 400 | else { 401 | _fail = false; 402 | } 403 | return _fail == false; 404 | } 405 | 406 | auto FileZlibImpl::seek(int32_t offset) -> bool 407 | { 408 | if(_file != nullptr) { 409 | const int rc = ::gzseek(_file, offset, SEEK_SET); 410 | if(rc != 0) { 411 | _fail = true; 412 | } 413 | else { 414 | _fail = false; 415 | } 416 | } 417 | else { 418 | _fail = true; 419 | } 420 | return _fail == false; 421 | } 422 | 423 | auto FileZlibImpl::read(void* buffer, uint32_t length) -> bool 424 | { 425 | if(_file != nullptr) { 426 | const auto rc = ::gzread(_file, buffer, length); 427 | if(static_cast(rc) != length) { 428 | _fail = true; 429 | } 430 | else { 431 | _fail = false; 432 | } 433 | } 434 | else { 435 | _fail = true; 436 | } 437 | return _fail == false; 438 | } 439 | 440 | auto FileZlibImpl::write(void* buffer, uint32_t length) -> bool 441 | { 442 | if(_file != nullptr) { 443 | const auto rc = ::gzwrite(_file, buffer, length); 444 | if(static_cast(rc) != length) { 445 | _fail = true; 446 | } 447 | else { 448 | _fail = false; 449 | } 450 | } 451 | else { 452 | _fail = true; 453 | } 454 | return _fail == false; 455 | } 456 | 457 | // --------------------------------------------------------------------------- 458 | // End-Of-File 459 | // --------------------------------------------------------------------------- 460 | -------------------------------------------------------------------------------- /src/music.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * music.cc - Copyright (c) 2004-2025 3 | * 4 | * Gregory Montoir, Fabien Sanglard, Olivier Poncet 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 2 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | #ifdef HAVE_CONFIG_H 20 | #include "config.h" 21 | #endif 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include "logger.h" 41 | #include "engine.h" 42 | #include "music.h" 43 | 44 | // --------------------------------------------------------------------------- 45 | // some useful macros 46 | // --------------------------------------------------------------------------- 47 | 48 | #ifndef NDEBUG 49 | #define LOG_DEBUG(format, ...) log_debug(SYS_MUSIC, format, ##__VA_ARGS__) 50 | #else 51 | #define LOG_DEBUG(format, ...) do {} while(0) 52 | #endif 53 | 54 | // --------------------------------------------------------------------------- 55 | // Music 56 | // --------------------------------------------------------------------------- 57 | 58 | Music::Music(Engine& engine, Audio& audio) 59 | : SubSystem(engine, "Music") 60 | , _audio(audio) 61 | , _module() 62 | , _timer(0) 63 | { 64 | } 65 | 66 | auto Music::start() -> void 67 | { 68 | LOG_DEBUG("starting..."); 69 | stopMusic(); 70 | startTimer(); 71 | LOG_DEBUG("started!"); 72 | } 73 | 74 | auto Music::reset() -> void 75 | { 76 | LOG_DEBUG("resetting..."); 77 | stopMusic(); 78 | resetTimer(); 79 | LOG_DEBUG("reset!"); 80 | } 81 | 82 | auto Music::stop() -> void 83 | { 84 | LOG_DEBUG("stopping..."); 85 | stopMusic(); 86 | stopTimer(); 87 | LOG_DEBUG("stopped!"); 88 | } 89 | 90 | auto Music::playMusic(uint16_t music_id, uint8_t index, uint16_t ticks) -> void 91 | { 92 | const LockGuard lock(_mutex); 93 | 94 | if(music_id == 0x0000) { 95 | music_id = 0xffff; 96 | } 97 | if(music_id != 0xffff) { 98 | stopMusicUnlocked(); 99 | playMusicUnlocked(music_id, index, ticks); 100 | } 101 | else if((index != 0) || (ticks != 0)) { 102 | if(index != 0) { 103 | _module.seq_index = index; 104 | } 105 | if(ticks != 0) { 106 | _module.music_ticks = ticks; 107 | } 108 | } 109 | else { 110 | stopMusicUnlocked(); 111 | } 112 | } 113 | 114 | auto Music::stopMusic() -> void 115 | { 116 | const LockGuard lock(_mutex); 117 | 118 | stopMusicUnlocked(); 119 | } 120 | 121 | auto Music::startTimer() -> void 122 | { 123 | const LockGuard lock(_mutex); 124 | 125 | auto callback = +[](uint32_t interval, void* data) -> uint32_t 126 | { 127 | return reinterpret_cast(data)->processTimer(); 128 | }; 129 | 130 | if(_timer == 0) { 131 | _currTicks = _engine.getTicks(); 132 | _nextTicks = _currTicks + 20; 133 | _prevTicks = 0; 134 | _timer = _engine.addTimer(callback, (_nextTicks - _prevTicks), this); 135 | } 136 | } 137 | 138 | auto Music::resetTimer() -> void 139 | { 140 | const LockGuard lock(_mutex); 141 | 142 | if(_timer != 0) { 143 | _currTicks = _engine.getTicks(); 144 | _nextTicks = _currTicks + 20; 145 | _prevTicks = 0; 146 | } 147 | } 148 | 149 | auto Music::stopTimer() -> void 150 | { 151 | const LockGuard lock(_mutex); 152 | 153 | if(_timer != 0) { 154 | _currTicks = 0; 155 | _nextTicks = 0; 156 | _prevTicks = 0; 157 | _timer = (_engine.removeTimer(_timer), 0); 158 | } 159 | } 160 | 161 | auto Music::processTimer() -> uint32_t 162 | { 163 | const LockGuard lock(_mutex); 164 | 165 | auto is_ready = [&]() -> bool 166 | { 167 | _currTicks = _engine.getTicks(); 168 | if(_engine.isStopped() || _engine.isPaused()) { 169 | _nextTicks = _currTicks + 100; 170 | return false; 171 | } 172 | if(_module.music_id == 0xffff) { 173 | _nextTicks = _currTicks + 100; 174 | return false; 175 | } 176 | if(_currTicks >= _nextTicks) { 177 | _prevTicks = _nextTicks; 178 | return true; 179 | } 180 | return false; 181 | }; 182 | 183 | auto process = [&]() -> void 184 | { 185 | if(_module.seq_index < _module.seq_count) { 186 | const uint8_t sequence = _module.seq_table[_module.seq_index]; 187 | LOG_DEBUG("process music [music_id: 0x%02x, sequence: 0x%02x, position: 0x%04x]", _module.music_id, sequence, _module.data_pos); 188 | Data data(_module.data_ptr + _module.data_pos + sequence * 1024); 189 | for(uint8_t channel = 0; channel < 4; ++channel) { 190 | processPattern(channel, data); 191 | } 192 | _module.data_pos += data.offset(); 193 | if(_module.data_pos >= 1024) { 194 | _module.data_pos = 0; 195 | const uint8_t seq_index = _module.seq_index + 1; 196 | const uint8_t seq_count = _module.seq_count; 197 | if(seq_index >= seq_count) { 198 | LOG_DEBUG("music is over [music_id: 0x%02x]", _module.music_id); 199 | stopMusicUnlocked(); 200 | } 201 | else { 202 | _module.seq_index = seq_index; 203 | } 204 | } 205 | } 206 | else { 207 | LOG_DEBUG("music is over [music_id: 0x%02x]", _module.music_id); 208 | stopMusicUnlocked(); 209 | } 210 | }; 211 | 212 | auto schedule = [&]() -> void 213 | { 214 | constexpr uint32_t hfreq = 15625; 215 | constexpr uint32_t bpm = 125; 216 | const uint32_t ticks = (_module.music_ticks != 0 ? _module.music_ticks : hfreq); 217 | const uint32_t delay = ((ticks * bpm) / hfreq); 218 | 219 | _currTicks = _engine.getTicks(); 220 | _nextTicks = _prevTicks + delay; 221 | 222 | if(_nextTicks <= _currTicks) { 223 | _nextTicks = _currTicks + 1; 224 | } 225 | }; 226 | 227 | auto delay = [&]() -> uint32_t 228 | { 229 | return _nextTicks - _currTicks; 230 | }; 231 | 232 | if(is_ready()) { 233 | process(); 234 | schedule(); 235 | } 236 | return delay(); 237 | } 238 | 239 | auto Music::playMusicUnlocked(uint16_t music_id, uint8_t index, uint16_t ticks) -> void 240 | { 241 | LOG_DEBUG("play music [music_id: 0x%02x, index: %d, ticks: %d]", music_id, index, ticks); 242 | 243 | auto get_frequency = [](uint16_t period) -> uint16_t 244 | { 245 | if(period < 55) { 246 | return 65535; 247 | } 248 | return static_cast(Paula::Carrier / period); 249 | }; 250 | 251 | auto get_volume = [](uint8_t volume) -> uint8_t 252 | { 253 | if(volume > 0x3f) { 254 | volume = 0x3f; 255 | } 256 | return volume; 257 | }; 258 | 259 | auto load_module = [&](Data& data) -> void 260 | { 261 | _module = MusicModule(); 262 | _module.music_id = music_id; 263 | _module.music_ticks = data.seek(0x00).fetchWordBE(); 264 | _module.data_ptr = data.seek(0xc0).get(); 265 | _module.data_pos = 0; 266 | _module.seq_index = index; 267 | _module.seq_count = data.seek(0x3e).fetchWordBE(); 268 | if(ticks != 0) { 269 | _module.music_ticks = ticks; 270 | } 271 | }; 272 | 273 | auto load_samples = [&](Data& data) -> void 274 | { 275 | data.seek(0x02); 276 | for(auto& sample : _module.samples) { 277 | const uint16_t sample_id = data.fetchWordBE(); 278 | const uint16_t volume = data.fetchWordBE(); 279 | if(sample_id != 0) { 280 | LOG_DEBUG("load sample [sound_id: 0x%02x, volume: %d]", sample_id, volume); 281 | Resource* resource = _engine.getResource(sample_id); 282 | if(resource == nullptr) { 283 | log_alert("resource not found [sound_id: 0x%02x]", sample_id); 284 | } 285 | else if(resource->type != RT_SOUND) { 286 | log_alert("resource is invalid [sound_id: 0x%02x]", sample_id); 287 | } 288 | else if(resource->state != RS_LOADED) { 289 | log_alert("resource not loaded [sound_id: 0x%02x]", sample_id); 290 | } 291 | else { 292 | Data data(resource->data); 293 | const uint32_t data_len = static_cast(data.fetchWordBE()) * 2; 294 | const uint32_t loop_len = static_cast(data.fetchWordBE()) * 2; 295 | const uint16_t unused1 = data.fetchWordBE(); 296 | const uint16_t unused2 = data.fetchWordBE(); 297 | const uint8_t* data_ptr = data.get(); 298 | sample.sample_id = sample_id; 299 | sample.frequency = get_frequency(109); 300 | sample.volume = get_volume(volume); 301 | sample.data_ptr = data_ptr; 302 | sample.data_len = data_len; 303 | sample.loop_pos = 0; 304 | sample.loop_len = 0; 305 | sample.unused1 = unused1; 306 | sample.unused2 = unused2; 307 | if(loop_len != 0) { 308 | sample.data_len += loop_len; 309 | sample.loop_pos += data_len; 310 | sample.loop_len += loop_len; 311 | } 312 | } 313 | } 314 | } 315 | }; 316 | 317 | auto load_seq_table = [&](Data& data) -> void 318 | { 319 | data.seek(0x40); 320 | for(auto& sequence : _module.seq_table) { 321 | sequence = data.fetchByte(); 322 | } 323 | }; 324 | 325 | auto play_music = [&](Resource* resource) -> void 326 | { 327 | if(resource == nullptr) { 328 | log_alert("resource not found [music_id: 0x%02x]", music_id); 329 | } 330 | else if(resource->type != RT_MUSIC) { 331 | log_alert("resource is invalid [music_id: 0x%02x]", music_id); 332 | } 333 | else if(resource->state != RS_LOADED) { 334 | log_alert("resource not loaded [music_id: 0x%02x]", music_id); 335 | } 336 | else { 337 | Data data(resource->data); 338 | load_module(data); 339 | load_samples(data); 340 | load_seq_table(data); 341 | } 342 | }; 343 | 344 | return play_music(_engine.getResource(music_id)); 345 | } 346 | 347 | auto Music::stopMusicUnlocked() -> void 348 | { 349 | LOG_DEBUG("stop music [music_id: 0x%02x]", _module.music_id); 350 | 351 | auto stop_music = [&]() -> void 352 | { 353 | if(_module.music_id != 0xffff) { 354 | _audio.stopAllChannels(); 355 | _module = MusicModule(); 356 | } 357 | }; 358 | 359 | return stop_music(); 360 | } 361 | 362 | auto Music::processPattern(uint8_t channel, Data& data) -> void 363 | { 364 | auto get_frequency = [](uint16_t period) -> uint16_t 365 | { 366 | if(period < 55) { 367 | return 65535; 368 | } 369 | return static_cast(Paula::Carrier / period); 370 | }; 371 | 372 | auto get_volume = [](int16_t volume) -> uint8_t 373 | { 374 | if(volume < 0x00) { 375 | volume = 0x00; 376 | } 377 | if(volume > 0x3f) { 378 | volume = 0x3f; 379 | } 380 | return static_cast(volume); 381 | }; 382 | 383 | MusicPattern pattern; 384 | pattern.word1 = data.fetchWordBE(); 385 | pattern.word2 = data.fetchWordBE(); 386 | if(pattern.word1 == 0x0000) { 387 | return; 388 | } 389 | else if(pattern.word1 == 0xfffd) { 390 | _engine.setMusicMark(pattern.word2); 391 | } 392 | else if(pattern.word1 == 0xfffe) { 393 | _audio.stopChannel(channel); 394 | } 395 | else { 396 | const uint16_t period_value = ((pattern.word1 & 0x0fff) >> 0); 397 | const uint8_t sample_index = ((pattern.word2 & 0xf000) >> 12); 398 | const uint8_t effect_index = ((pattern.word2 & 0x0f00) >> 8); 399 | const uint8_t effect_value = ((pattern.word2 & 0x00ff) >> 0); 400 | if(sample_index != 0) { 401 | auto sample(_module.samples[sample_index - 1]); 402 | int16_t volume = sample.volume; 403 | switch(effect_index) { 404 | case 0x0: // no effect 405 | break; 406 | case 0x5: // volume up 407 | volume += effect_value; 408 | break; 409 | case 0x6: // volume down 410 | volume -= effect_value; 411 | break; 412 | default: 413 | LOG_DEBUG("unsupported effect $%x", effect_index); 414 | break; 415 | } 416 | sample.frequency = get_frequency(period_value); 417 | sample.volume = get_volume(volume); 418 | _audio.playChannel(channel, sample); 419 | } 420 | } 421 | } 422 | 423 | // --------------------------------------------------------------------------- 424 | // End-Of-File 425 | // --------------------------------------------------------------------------- 426 | -------------------------------------------------------------------------------- /src/intern.h: -------------------------------------------------------------------------------- 1 | /* 2 | * intern.h - Copyright (c) 2004-2025 3 | * 4 | * Gregory Montoir, Fabien Sanglard, Olivier Poncet 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 2 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | #ifndef __AW_INTERN_H__ 20 | #define __AW_INTERN_H__ 21 | 22 | // --------------------------------------------------------------------------- 23 | // countof 24 | // --------------------------------------------------------------------------- 25 | 26 | #ifndef countof 27 | #define countof(array) (sizeof(array) / sizeof(array[0])) 28 | #endif 29 | 30 | // --------------------------------------------------------------------------- 31 | // forward declarations 32 | // --------------------------------------------------------------------------- 33 | 34 | class Engine; 35 | class Backend; 36 | class Resources; 37 | class Video; 38 | class Audio; 39 | class Mixer; 40 | class Sound; 41 | class Music; 42 | class Input; 43 | class VirtualMachine; 44 | 45 | // --------------------------------------------------------------------------- 46 | // some aliases 47 | // --------------------------------------------------------------------------- 48 | 49 | using Mutex = std::mutex; 50 | using LockGuard = std::lock_guard; 51 | using AudioCallback = void (*)(void* data, uint8_t* buffer, int length); 52 | using TimerCallback = uint32_t (*)(uint32_t delay, void* param); 53 | using EnginePtr = std::unique_ptr; 54 | using BackendPtr = std::unique_ptr; 55 | 56 | // --------------------------------------------------------------------------- 57 | // Library 58 | // --------------------------------------------------------------------------- 59 | 60 | class Library 61 | { 62 | public: // public interface 63 | Library() = default; 64 | 65 | Library(Library&&) = delete; 66 | 67 | Library(const Library&) = delete; 68 | 69 | Library& operator=(Library&&) = delete; 70 | 71 | Library& operator=(const Library&) = delete; 72 | 73 | virtual ~Library() = default; 74 | }; 75 | 76 | // --------------------------------------------------------------------------- 77 | // SubSystem 78 | // --------------------------------------------------------------------------- 79 | 80 | class SubSystem 81 | { 82 | public: // public interface 83 | SubSystem(Engine& engine, const std::string& subsystem); 84 | 85 | SubSystem(SubSystem&&) = delete; 86 | 87 | SubSystem(const SubSystem&) = delete; 88 | 89 | SubSystem& operator=(SubSystem&&) = delete; 90 | 91 | SubSystem& operator=(const SubSystem&) = delete; 92 | 93 | virtual ~SubSystem(); 94 | 95 | virtual auto start() -> void = 0; 96 | 97 | virtual auto reset() -> void = 0; 98 | 99 | virtual auto stop() -> void = 0; 100 | 101 | auto getCurrTicks() -> uint32_t 102 | { 103 | return _currTicks; 104 | } 105 | 106 | auto getPrevTicks() -> uint32_t 107 | { 108 | return _prevTicks; 109 | } 110 | 111 | auto getNextTicks() -> uint32_t 112 | { 113 | return _nextTicks; 114 | } 115 | 116 | protected: // protected data 117 | const std::string _subsystem; 118 | Engine& _engine; 119 | Mutex _mutex; 120 | uint32_t _currTicks; 121 | uint32_t _prevTicks; 122 | uint32_t _nextTicks; 123 | }; 124 | 125 | // --------------------------------------------------------------------------- 126 | // Data 127 | // --------------------------------------------------------------------------- 128 | 129 | class Data 130 | { 131 | public: // public interface 132 | Data() 133 | : Data(nullptr, 0) 134 | { 135 | } 136 | 137 | Data(const uint8_t* buffer) 138 | : Data(buffer, 0) 139 | { 140 | } 141 | 142 | Data(const uint8_t* buffer, uint32_t offset) 143 | : _buffer(buffer) 144 | , _bufptr(buffer) 145 | { 146 | if(_bufptr != nullptr) { 147 | _bufptr += offset; 148 | } 149 | } 150 | 151 | Data(Data&&) = delete; 152 | 153 | Data(const Data&) = default; 154 | 155 | Data& operator=(Data&&) = delete; 156 | 157 | Data& operator=(const Data&) = default; 158 | 159 | ~Data() = default; 160 | 161 | auto get() const -> const uint8_t* 162 | { 163 | return _bufptr; 164 | } 165 | 166 | auto reset() -> void 167 | { 168 | _buffer = _bufptr = nullptr; 169 | } 170 | 171 | auto reset(const uint8_t* buffer) -> void 172 | { 173 | _buffer = _bufptr = buffer; 174 | } 175 | 176 | auto reset(const uint8_t* buffer, uint32_t offset) -> void 177 | { 178 | _buffer = _bufptr = buffer; 179 | if(_bufptr != nullptr) { 180 | _bufptr += offset; 181 | } 182 | } 183 | 184 | auto offset() -> uint32_t 185 | { 186 | return (_bufptr - _buffer); 187 | } 188 | 189 | auto seek(uint32_t offset) -> Data& 190 | { 191 | if(_buffer != nullptr) { 192 | _bufptr = _buffer + offset; 193 | } 194 | return *this; 195 | } 196 | 197 | auto advance(uint32_t offset) -> Data& 198 | { 199 | if(_bufptr != nullptr) { 200 | _bufptr += offset; 201 | } 202 | return *this; 203 | } 204 | 205 | auto rewind(uint32_t offset) -> Data& 206 | { 207 | if(_bufptr != nullptr) { 208 | _bufptr -= offset; 209 | } 210 | return *this; 211 | } 212 | 213 | auto fetchByte() -> uint8_t 214 | { 215 | uint8_t b = 0; 216 | if(_bufptr != nullptr) { 217 | b |= (static_cast(*_bufptr++) << 0); 218 | } 219 | return b; 220 | } 221 | 222 | auto fetchWordBE() -> uint16_t 223 | { 224 | uint16_t w = 0; 225 | if(_bufptr != nullptr) { 226 | w |= (static_cast(*_bufptr++) << 8); 227 | w |= (static_cast(*_bufptr++) << 0); 228 | } 229 | return w; 230 | } 231 | 232 | auto fetchWordLE() -> uint16_t 233 | { 234 | uint16_t w = 0; 235 | if(_bufptr != nullptr) { 236 | w |= (static_cast(*_bufptr++) << 0); 237 | w |= (static_cast(*_bufptr++) << 8); 238 | } 239 | return w; 240 | } 241 | 242 | auto fetchLongBE() -> uint32_t 243 | { 244 | uint32_t l = 0; 245 | if(_bufptr != nullptr) { 246 | l |= (static_cast(*_bufptr++) << 24); 247 | l |= (static_cast(*_bufptr++) << 16); 248 | l |= (static_cast(*_bufptr++) << 8); 249 | l |= (static_cast(*_bufptr++) << 0); 250 | } 251 | return l; 252 | } 253 | 254 | auto fetchLongLE() -> uint32_t 255 | { 256 | uint32_t l = 0; 257 | if(_bufptr != nullptr) { 258 | l |= (static_cast(*_bufptr++) << 0); 259 | l |= (static_cast(*_bufptr++) << 8); 260 | l |= (static_cast(*_bufptr++) << 16); 261 | l |= (static_cast(*_bufptr++) << 24); 262 | } 263 | return l; 264 | } 265 | 266 | private: // private data 267 | const uint8_t* _buffer; 268 | const uint8_t* _bufptr; 269 | }; 270 | 271 | // --------------------------------------------------------------------------- 272 | // ByteCode 273 | // --------------------------------------------------------------------------- 274 | 275 | class ByteCode 276 | { 277 | public: // public interface 278 | ByteCode() 279 | : ByteCode(nullptr, 0) 280 | { 281 | } 282 | 283 | ByteCode(const uint8_t* buffer) 284 | : ByteCode(buffer, 0) 285 | { 286 | } 287 | 288 | ByteCode(const uint8_t* buffer, uint32_t offset) 289 | : _buffer(buffer) 290 | , _bufptr(buffer) 291 | { 292 | if(_bufptr != nullptr) { 293 | _bufptr += offset; 294 | } 295 | } 296 | 297 | ByteCode(ByteCode&&) = delete; 298 | 299 | ByteCode(const ByteCode&) = default; 300 | 301 | ByteCode& operator=(ByteCode&&) = delete; 302 | 303 | ByteCode& operator=(const ByteCode&) = default; 304 | 305 | ~ByteCode() = default; 306 | 307 | auto get() const -> const uint8_t* 308 | { 309 | return _bufptr; 310 | } 311 | 312 | auto reset() -> void 313 | { 314 | _buffer = _bufptr = nullptr; 315 | } 316 | 317 | auto reset(const uint8_t* buffer) -> void 318 | { 319 | _buffer = _bufptr = buffer; 320 | } 321 | 322 | auto offset() -> uint32_t 323 | { 324 | return (_bufptr - _buffer); 325 | } 326 | 327 | auto seek(uint32_t offset) -> void 328 | { 329 | if(_buffer != nullptr) { 330 | _bufptr = _buffer + offset; 331 | } 332 | } 333 | 334 | auto fetchByte() -> uint8_t 335 | { 336 | uint8_t b = 0; 337 | if(_bufptr != nullptr) { 338 | b |= (static_cast(*_bufptr++) << 0); 339 | } 340 | return b; 341 | } 342 | 343 | auto fetchWord() -> uint16_t 344 | { 345 | uint16_t w = 0; 346 | if(_bufptr != nullptr) { 347 | w |= (static_cast(*_bufptr++) << 8); 348 | w |= (static_cast(*_bufptr++) << 0); 349 | } 350 | return w; 351 | } 352 | 353 | auto fetchLong() -> uint32_t 354 | { 355 | uint32_t l = 0; 356 | if(_bufptr != nullptr) { 357 | l |= (static_cast(*_bufptr++) << 24); 358 | l |= (static_cast(*_bufptr++) << 16); 359 | l |= (static_cast(*_bufptr++) << 8); 360 | l |= (static_cast(*_bufptr++) << 0); 361 | } 362 | return l; 363 | } 364 | 365 | private: // private data 366 | const uint8_t* _buffer; 367 | const uint8_t* _bufptr; 368 | }; 369 | 370 | // --------------------------------------------------------------------------- 371 | // Point 372 | // --------------------------------------------------------------------------- 373 | 374 | struct Point 375 | { 376 | Point() 377 | : Point(0, 0) 378 | { 379 | } 380 | 381 | Point(const int16_t px, const int16_t py) 382 | : x(px) 383 | , y(py) 384 | { 385 | } 386 | 387 | int16_t x; 388 | int16_t y; 389 | }; 390 | 391 | // --------------------------------------------------------------------------- 392 | // Polygon 393 | // --------------------------------------------------------------------------- 394 | 395 | struct Polygon 396 | { 397 | Polygon() 398 | : bbw(0) 399 | , bbh(0) 400 | , count(0) 401 | , points() 402 | { 403 | } 404 | 405 | uint16_t bbw; 406 | uint16_t bbh; 407 | uint8_t count; 408 | Point points[50]; 409 | }; 410 | 411 | // --------------------------------------------------------------------------- 412 | // Color3u8 413 | // --------------------------------------------------------------------------- 414 | 415 | struct Color3u8 416 | { 417 | uint8_t r = 0; 418 | uint8_t g = 0; 419 | uint8_t b = 0; 420 | }; 421 | 422 | // --------------------------------------------------------------------------- 423 | // Palette 424 | // --------------------------------------------------------------------------- 425 | 426 | struct Palette 427 | { 428 | uint8_t id = 0; 429 | Color3u8 data[16]; 430 | }; 431 | 432 | // --------------------------------------------------------------------------- 433 | // Page 434 | // --------------------------------------------------------------------------- 435 | 436 | struct Page 437 | { 438 | uint8_t id = 0; 439 | uint8_t data[((320 / 2) * 200)]; 440 | }; 441 | 442 | // --------------------------------------------------------------------------- 443 | // AudioChannel 444 | // --------------------------------------------------------------------------- 445 | 446 | struct AudioChannel 447 | { 448 | uint8_t channel_id = 0xff; 449 | uint8_t active = 0; 450 | uint8_t volume = 0; 451 | uint16_t sample_id = 0xffff; 452 | const uint8_t* data_ptr = nullptr; 453 | uint32_t data_len = 0; 454 | uint32_t data_pos = 0; 455 | uint32_t data_inc = 0; 456 | uint32_t loop_pos = 0; 457 | uint32_t loop_len = 0; 458 | }; 459 | 460 | // --------------------------------------------------------------------------- 461 | // AudioSample 462 | // --------------------------------------------------------------------------- 463 | 464 | struct AudioSample 465 | { 466 | uint16_t sample_id = 0xffff; 467 | uint16_t frequency = 0; 468 | uint8_t volume = 0; 469 | const uint8_t* data_ptr = nullptr; 470 | uint32_t data_len = 0; 471 | uint32_t loop_pos = 0; 472 | uint32_t loop_len = 0; 473 | uint16_t unused1 = 0; 474 | uint16_t unused2 = 0; 475 | }; 476 | 477 | // --------------------------------------------------------------------------- 478 | // MusicPattern 479 | // --------------------------------------------------------------------------- 480 | 481 | struct MusicPattern 482 | { 483 | uint16_t word1 = 0; 484 | uint16_t word2 = 0; 485 | }; 486 | 487 | // --------------------------------------------------------------------------- 488 | // MusicModule 489 | // --------------------------------------------------------------------------- 490 | 491 | struct MusicModule 492 | { 493 | uint16_t music_id = 0xffff; 494 | uint16_t music_ticks = 0; 495 | const uint8_t* data_ptr = nullptr; 496 | uint32_t data_pos = 0; 497 | uint8_t seq_index = 0; 498 | uint8_t seq_count = 0; 499 | uint8_t seq_table[0x80] = {0}; 500 | AudioSample samples[15]; 501 | }; 502 | 503 | // --------------------------------------------------------------------------- 504 | // Controls 505 | // --------------------------------------------------------------------------- 506 | 507 | struct Controls 508 | { 509 | static constexpr uint16_t DPAD_RIGHT = (1 << 0); 510 | static constexpr uint16_t DPAD_LEFT = (1 << 1); 511 | static constexpr uint16_t DPAD_DOWN = (1 << 2); 512 | static constexpr uint16_t DPAD_UP = (1 << 3); 513 | static constexpr uint16_t DPAD_BUTTON = (1 << 7); 514 | 515 | uint16_t mask = 0; 516 | int16_t horz = 0; 517 | int16_t vert = 0; 518 | int16_t btns = 0; 519 | uint8_t input = 0; 520 | bool quit = false; 521 | bool pause = false; 522 | }; 523 | 524 | // --------------------------------------------------------------------------- 525 | // Paula 526 | // --------------------------------------------------------------------------- 527 | 528 | struct Paula 529 | { 530 | static constexpr uint32_t Frequency = 7159090; 531 | static constexpr uint32_t Carrier = Frequency / 2; 532 | 533 | static const uint16_t frequencyTable[40]; 534 | }; 535 | 536 | // --------------------------------------------------------------------------- 537 | // GameParts 538 | // --------------------------------------------------------------------------- 539 | 540 | enum GamePartId 541 | { 542 | GAME_PART_FIRST = 0x3e80, 543 | GAME_PART0 = 0x3e80, 544 | GAME_PART1 = 0x3e81, 545 | GAME_PART2 = 0x3e82, 546 | GAME_PART3 = 0x3e83, 547 | GAME_PART4 = 0x3e84, 548 | GAME_PART5 = 0x3e85, 549 | GAME_PART6 = 0x3e86, 550 | GAME_PART7 = 0x3e87, 551 | GAME_PART8 = 0x3e88, 552 | GAME_PART9 = 0x3e89, 553 | GAME_PART_LAST = 0x3e89, 554 | GAME_NUM_PARTS = 10, 555 | }; 556 | 557 | struct GamePart 558 | { 559 | const char* name = nullptr; 560 | uint16_t palettes = 0; 561 | uint16_t bytecode = 0; 562 | uint16_t polygon1 = 0; 563 | uint16_t polygon2 = 0; 564 | }; 565 | 566 | struct GameParts 567 | { 568 | static const GamePart data[GAME_NUM_PARTS]; 569 | }; 570 | 571 | // --------------------------------------------------------------------------- 572 | // End-Of-File 573 | // --------------------------------------------------------------------------- 574 | 575 | #endif /* __AW_INTERN_H__ */ 576 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | , 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | -------------------------------------------------------------------------------- /src/data.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * data.cc - Copyright (c) 2004-2025 3 | * 4 | * Gregory Montoir, Fabien Sanglard, Olivier Poncet 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 2 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | #ifdef HAVE_CONFIG_H 20 | #include "config.h" 21 | #endif 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include "logger.h" 41 | #include "intern.h" 42 | #include "bytekiller.h" 43 | #include "file.h" 44 | #include "data.h" 45 | 46 | // --------------------------------------------------------------------------- 47 | // some useful macros 48 | // --------------------------------------------------------------------------- 49 | 50 | #ifndef NDEBUG 51 | #define LOG_DEBUG(format, ...) log_debug(SYS_RESOURCES, format, ##__VA_ARGS__) 52 | #else 53 | #define LOG_DEBUG(format, ...) do {} while(0) 54 | #endif 55 | 56 | // --------------------------------------------------------------------------- 57 | // Dictionary 58 | // --------------------------------------------------------------------------- 59 | 60 | const String Dictionary::dataEN[] = { 61 | { 0x0001, "P E A N U T 3000" }, 62 | { 0x0002, "Copyright } 1990 Peanut Computer, Inc.\nAll rights reserved.\n\nCDOS Version 5.01" }, 63 | { 0x0003, "2" }, 64 | { 0x0004, "3" }, 65 | { 0x0005, "." }, 66 | { 0x0006, "A" }, 67 | { 0x0007, "@" }, 68 | { 0x0008, "PEANUT 3000" }, 69 | { 0x000a, "R" }, 70 | { 0x000b, "U" }, 71 | { 0x000c, "N" }, 72 | { 0x000d, "P" }, 73 | { 0x000e, "R" }, 74 | { 0x000f, "O" }, 75 | { 0x0010, "J" }, 76 | { 0x0011, "E" }, 77 | { 0x0012, "C" }, 78 | { 0x0013, "T" }, 79 | { 0x0014, "Shield 9A.5f Ok" }, 80 | { 0x0015, "Flux % 5.0177 Ok" }, 81 | { 0x0016, "CDI Vector ok" }, 82 | { 0x0017, " %%%ddd ok" }, 83 | { 0x0018, "Race-Track ok" }, 84 | { 0x0019, "SYNCHROTRON" }, 85 | { 0x001a, "E: 23%\ng: .005\n\nRK: 77.2L\n\nopt: g+\n\n Shield:\n1: OFF\n2: ON\n3: ON\n\nP~: 1\n" }, 86 | { 0x001b, "ON" }, 87 | { 0x001c, "-" }, 88 | { 0x0021, "|" }, 89 | { 0x0022, "--- Theoretical study ---" }, 90 | { 0x0023, " THE EXPERIMENT WILL BEGIN IN SECONDS" }, 91 | { 0x0024, " 20" }, 92 | { 0x0025, " 19" }, 93 | { 0x0026, " 18" }, 94 | { 0x0027, " 4" }, 95 | { 0x0028, " 3" }, 96 | { 0x0029, " 2" }, 97 | { 0x002a, " 1" }, 98 | { 0x002b, " 0" }, 99 | { 0x002c, "L E T ' S G O" }, 100 | { 0x0031, "- Phase 0:\nINJECTION of particles\ninto synchrotron" }, 101 | { 0x0032, "- Phase 1:\nParticle ACCELERATION." }, 102 | { 0x0033, "- Phase 2:\nEJECTION of particles\non the shield." }, 103 | { 0x0034, "A N A L Y S I S" }, 104 | { 0x0035, "- RESULT:\nProbability of creating:\n ANTIMATTER: 91.V %\n NEUTRINO 27: 0.04 %\n NEUTRINO 424: 18 %\n" }, 105 | { 0x0036, " Practical verification Y/N ?" }, 106 | { 0x0037, "SURE ?" }, 107 | { 0x0038, "MODIFICATION OF PARAMETERS\nRELATING TO PARTICLE\nACCELERATOR (SYNCHROTRON)." }, 108 | { 0x0039, " RUN EXPERIMENT ?" }, 109 | { 0x003c, "t---t" }, 110 | { 0x003d, "000 ~" }, 111 | { 0x003e, ".20x14dd" }, 112 | { 0x003f, "gj5r5r" }, 113 | { 0x0040, "tilgor 25%" }, 114 | { 0x0041, "12% 33% checked" }, 115 | { 0x0042, "D=4.2158005584" }, 116 | { 0x0043, "d=10.00001" }, 117 | { 0x0044, "+" }, 118 | { 0x0045, "*" }, 119 | { 0x0046, "% 304" }, 120 | { 0x0047, "gurgle 21" }, 121 | { 0x0048, "{{{{" }, 122 | { 0x0049, "Delphine Software" }, 123 | { 0x004a, "By Eric Chahi" }, 124 | { 0x004b, " 5" }, 125 | { 0x004c, " 17" }, 126 | { 0x012c, "0" }, 127 | { 0x012d, "1" }, 128 | { 0x012e, "2" }, 129 | { 0x012f, "3" }, 130 | { 0x0130, "4" }, 131 | { 0x0131, "5" }, 132 | { 0x0132, "6" }, 133 | { 0x0133, "7" }, 134 | { 0x0134, "8" }, 135 | { 0x0135, "9" }, 136 | { 0x0136, "A" }, 137 | { 0x0137, "B" }, 138 | { 0x0138, "C" }, 139 | { 0x0139, "D" }, 140 | { 0x013a, "E" }, 141 | { 0x013b, "F" }, 142 | { 0x013c, " ACCESS CODE:" }, 143 | { 0x013d, "PRESS BUTTON OR RETURN TO CONTINUE" }, 144 | { 0x013e, " ENTER ACCESS CODE" }, 145 | { 0x013f, " INVALID PASSWORD !" }, 146 | { 0x0140, "ANNULER" }, 147 | { 0x0141, " INSERT DISK ?\n\n\n\n\n\n\n\n\nPRESS ANY KEY TO CONTINUE" }, 148 | { 0x0142, " SELECT SYMBOLS CORRESPONDING TO\n THE POSITION\n ON THE CODE WHEEL" }, 149 | { 0x0143, " LOADING..." }, 150 | { 0x0144, " ERROR" }, 151 | { 0x015e, "LDKD" }, 152 | { 0x015f, "HTDC" }, 153 | { 0x0160, "CLLD" }, 154 | { 0x0161, "FXLC" }, 155 | { 0x0162, "KRFK" }, 156 | { 0x0163, "XDDJ" }, 157 | { 0x0164, "LBKG" }, 158 | { 0x0165, "KLFB" }, 159 | { 0x0166, "TTCT" }, 160 | { 0x0167, "DDRX" }, 161 | { 0x0168, "TBHK" }, 162 | { 0x0169, "BRTD" }, 163 | { 0x016a, "CKJL" }, 164 | { 0x016b, "LFCK" }, 165 | { 0x016c, "BFLX" }, 166 | { 0x016d, "XJRT" }, 167 | { 0x016e, "HRTB" }, 168 | { 0x016f, "HBHK" }, 169 | { 0x0170, "JCGB" }, 170 | { 0x0171, "HHFL" }, 171 | { 0x0172, "TFBB" }, 172 | { 0x0173, "TXHF" }, 173 | { 0x0174, "JHJL" }, 174 | { 0x0181, " BY" }, 175 | { 0x0182, "ERIC CHAHI" }, 176 | { 0x0183, " MUSIC AND SOUND EFFECTS" }, 177 | { 0x0184, " " }, 178 | { 0x0185, "JEAN-FRANCOIS FREITAS" }, 179 | { 0x0186, "IBM PC VERSION" }, 180 | { 0x0187, " BY" }, 181 | { 0x0188, " DANIEL MORAIS" }, 182 | { 0x018b, " THEN PRESS FIRE" }, 183 | { 0x018c, " PUT THE PADDLE ON THE UPPER LEFT CORNER" }, 184 | { 0x018d, "PUT THE PADDLE IN CENTRAL POSITION" }, 185 | { 0x018e, "PUT THE PADDLE ON THE LOWER RIGHT CORNER" }, 186 | { 0x0258, " Designed by ..... Eric Chahi" }, 187 | { 0x0259, " Programmed by...... Eric Chahi" }, 188 | { 0x025a, " Artwork ......... Eric Chahi" }, 189 | { 0x025b, "Music by ........ Jean-francois Freitas" }, 190 | { 0x025c, " Sound effects" }, 191 | { 0x025d, " Jean-Francois Freitas\n Eric Chahi" }, 192 | { 0x0263, " Thanks To" }, 193 | { 0x0264, " Jesus Martinez\n\n Daniel Morais\n\n Frederic Savoir\n\n Cecile Chahi\n\n Philippe Delamarre\n\n Philippe Ulrich\n\nSebastien Berthet\n\nPierre Gousseau" }, 194 | { 0x0265, "Now Go Out Of This World" }, 195 | { 0x0190, "Good evening professor." }, 196 | { 0x0191, "I see you have driven here in your\nFerrari." }, 197 | { 0x0192, "IDENTIFICATION" }, 198 | { 0x0193, "Monsieur est en parfaite sante." }, 199 | { 0x0194, "Y\n" }, 200 | { 0x0193, "AU BOULOT !!!\n" }, 201 | { 0xffff, "" } 202 | }; 203 | 204 | const String Dictionary::dataFR[] = { 205 | { 0x0001, "P E A N U T 3000" }, 206 | { 0x0002, "Copyright } 1990 Peanut Computer, Inc.\nAll rights reserved.\n\nCDOS Version 5.01" }, 207 | { 0x0003, "2" }, 208 | { 0x0004, "3" }, 209 | { 0x0005, "." }, 210 | { 0x0006, "A" }, 211 | { 0x0007, "@" }, 212 | { 0x0008, "PEANUT 3000" }, 213 | { 0x000a, "R" }, 214 | { 0x000b, "U" }, 215 | { 0x000c, "N" }, 216 | { 0x000d, "P" }, 217 | { 0x000e, "R" }, 218 | { 0x000f, "O" }, 219 | { 0x0010, "J" }, 220 | { 0x0011, "E" }, 221 | { 0x0012, "C" }, 222 | { 0x0013, "T" }, 223 | { 0x0014, "Shield 9A.5f Ok" }, 224 | { 0x0015, "Flux % 5.0177 Ok" }, 225 | { 0x0016, "CDI Vector ok" }, 226 | { 0x0017, " %%%ddd ok" }, 227 | { 0x0018, "Race-Track ok" }, 228 | { 0x0019, "SYNCHROTRON" }, 229 | { 0x001a, "E: 23%\ng: .005\n\nRK: 77.2L\n\nopt: g+\n\n Shield:\n1: OFF\n2: ON\n3: ON\n\nP~: 1\n" }, 230 | { 0x001b, "ON" }, 231 | { 0x001c, "-" }, 232 | { 0x0021, "|" }, 233 | { 0x0022, "--- Etude theorique ---" }, 234 | { 0x0023, " L'EXPERIENCE DEBUTERA DANS SECONDES." }, 235 | { 0x0024, "20" }, 236 | { 0x0025, "19" }, 237 | { 0x0026, "18" }, 238 | { 0x0027, "4" }, 239 | { 0x0028, "3" }, 240 | { 0x0029, "2" }, 241 | { 0x002a, "1" }, 242 | { 0x002b, "0" }, 243 | { 0x002c, "L E T ' S G O" }, 244 | { 0x0031, "- Phase 0:\nINJECTION des particules\ndans le synchrotron" }, 245 | { 0x0032, "- Phase 1:\nACCELERATION des particules." }, 246 | { 0x0033, "- Phase 2:\nEJECTION des particules\nsur le bouclier." }, 247 | { 0x0034, "A N A L Y S E" }, 248 | { 0x0035, "- RESULTAT:\nProbabilites de creer de:\n ANTI-MATIERE: 91.V %\n NEUTRINO 27: 0.04 %\n NEUTRINO 424: 18 %\n" }, 249 | { 0x0036, "Verification par la pratique O/N ?" }, 250 | { 0x0037, "SUR ?" }, 251 | { 0x0038, "MODIFICATION DES PARAMETRES\nRELATIFS A L'ACCELERATEUR\nDE PARTICULES (SYNCHROTRON)." }, 252 | { 0x0039, "SIMULATION DE L'EXPERIENCE ?" }, 253 | { 0x003c, "t---t" }, 254 | { 0x003d, "000 ~" }, 255 | { 0x003e, ".20x14dd" }, 256 | { 0x003f, "gj5r5r" }, 257 | { 0x0040, "tilgor 25%" }, 258 | { 0x0041, "12% 33% checked" }, 259 | { 0x0042, "D=4.2158005584" }, 260 | { 0x0043, "d=10.00001" }, 261 | { 0x0044, "+" }, 262 | { 0x0045, "*" }, 263 | { 0x0046, "% 304" }, 264 | { 0x0047, "gurgle 21" }, 265 | { 0x0048, "{{{{" }, 266 | { 0x0049, "Delphine Software" }, 267 | { 0x004a, "By Eric Chahi" }, 268 | { 0x004b, "5" }, 269 | { 0x004c, "17" }, 270 | { 0x012c, "0" }, 271 | { 0x012d, "1" }, 272 | { 0x012e, "2" }, 273 | { 0x012f, "3" }, 274 | { 0x0130, "4" }, 275 | { 0x0131, "5" }, 276 | { 0x0132, "6" }, 277 | { 0x0133, "7" }, 278 | { 0x0134, "8" }, 279 | { 0x0135, "9" }, 280 | { 0x0136, "A" }, 281 | { 0x0137, "B" }, 282 | { 0x0138, "C" }, 283 | { 0x0139, "D" }, 284 | { 0x013a, "E" }, 285 | { 0x013b, "F" }, 286 | { 0x013c, " CODE D'ACCES:" }, 287 | { 0x013d, "PRESSEZ LE BOUTON POUR CONTINUER" }, 288 | { 0x013e, " ENTRER LE CODE D'ACCES" }, 289 | { 0x013f, "MOT DE PASSE INVALIDE !" }, 290 | { 0x0140, "ANNULER" }, 291 | { 0x0141, " INSEREZ LA DISQUETTE ?\n\n\n\n\n\n\n\n\nPRESSEZ UNE TOUCHE POUR CONTINUER" }, 292 | { 0x0142, "SELECTIONNER LES SYMBOLES CORRESPONDANTS\nA LA POSITION\nDE LA ROUE DE PROTECTION" }, 293 | { 0x0143, "CHARGEMENT..." }, 294 | { 0x0144, " ERREUR" }, 295 | { 0x015e, "LDKD" }, 296 | { 0x015f, "HTDC" }, 297 | { 0x0160, "CLLD" }, 298 | { 0x0161, "FXLC" }, 299 | { 0x0162, "KRFK" }, 300 | { 0x0163, "XDDJ" }, 301 | { 0x0164, "LBKG" }, 302 | { 0x0165, "KLFB" }, 303 | { 0x0166, "TTCT" }, 304 | { 0x0167, "DDRX" }, 305 | { 0x0168, "TBHK" }, 306 | { 0x0169, "BRTD" }, 307 | { 0x016a, "CKJL" }, 308 | { 0x016b, "LFCK" }, 309 | { 0x016c, "BFLX" }, 310 | { 0x016d, "XJRT" }, 311 | { 0x016e, "HRTB" }, 312 | { 0x016f, "HBHK" }, 313 | { 0x0170, "JCGB" }, 314 | { 0x0171, "HHFL" }, 315 | { 0x0172, "TFBB" }, 316 | { 0x0173, "TXHF" }, 317 | { 0x0174, "JHJL" }, 318 | { 0x0181, "PAR" }, 319 | { 0x0182, "ERIC CHAHI" }, 320 | { 0x0183, " MUSIQUES ET BRUITAGES" }, 321 | { 0x0184, "DE" }, 322 | { 0x0185, "JEAN-FRANCOIS FREITAS" }, 323 | { 0x0186, "VERSION IBM PC" }, 324 | { 0x0187, " PAR" }, 325 | { 0x0188, " DANIEL MORAIS" }, 326 | { 0x018b, "PUIS PRESSER LE BOUTON" }, 327 | { 0x018c, "POSITIONNER LE JOYSTICK EN HAUT A GAUCHE" }, 328 | { 0x018d, " POSITIONNER LE JOYSTICK AU CENTRE" }, 329 | { 0x018e, " POSITIONNER LE JOYSTICK EN BAS A DROITE" }, 330 | { 0x0258, " Conception ..... Eric Chahi" }, 331 | { 0x0259, " Programmation ..... Eric Chahi" }, 332 | { 0x025a, " Graphismes ....... Eric Chahi" }, 333 | { 0x025b, "Musique de ...... Jean-francois Freitas" }, 334 | { 0x025c, " Bruitages" }, 335 | { 0x025d, " Jean-Francois Freitas\n Eric Chahi" }, 336 | { 0x0263, " Merci a" }, 337 | { 0x0264, " Jesus Martinez\n\n Daniel Morais\n\n Frederic Savoir\n\n Cecile Chahi\n\n Philippe Delamarre\n\n Philippe Ulrich\n\nSebastien Berthet\n\nPierre Gousseau" }, 338 | { 0x0265, "Now Go Back To Another Earth" }, 339 | { 0x0190, "Bonsoir professeur." }, 340 | { 0x0191, "Je vois que Monsieur a pris\nsa Ferrari." }, 341 | { 0x0192, "IDENTIFICATION" }, 342 | { 0x0193, "Monsieur est en parfaite sante." }, 343 | { 0x0194, "O\n" }, 344 | { 0x0193, "AU BOULOT !!!\n" }, 345 | { 0xffff, "" } 346 | }; 347 | 348 | // --------------------------------------------------------------------------- 349 | // MemList 350 | // --------------------------------------------------------------------------- 351 | 352 | MemList::MemList(const std::string& dataDir, const std::string& dumpDir) 353 | : _dataDir(dataDir) 354 | , _dumpDir(dumpDir) 355 | { 356 | } 357 | 358 | auto MemList::loadMemList(Resource* resources) -> bool 359 | { 360 | char path[PATH_MAX] = ""; 361 | 362 | if(_dataDir.empty() == false) { 363 | (void) ::snprintf(path, sizeof(path), "%s/MEMLIST.BIN", _dataDir.c_str()); 364 | } 365 | if(path[0] != '\0') { 366 | File file("stdio"); 367 | if(file.open(path, "rb") != false) { 368 | if(resources != nullptr) { 369 | auto* resource = resources; 370 | uint16_t index = 0; 371 | uint8_t buffer[32]; 372 | while(true) { 373 | if(file.read(buffer, 20) != false) { 374 | Data data(buffer); 375 | resource->id = index++; 376 | resource->state = data.fetchByte(); 377 | resource->type = data.fetchByte(); 378 | resource->unused1 = data.fetchWordBE(); 379 | resource->unused2 = data.fetchWordBE(); 380 | resource->unused3 = data.fetchByte(); 381 | resource->bank_id = data.fetchByte(); 382 | resource->bank_offset = data.fetchLongBE(); 383 | resource->unused4 = data.fetchWordBE(); 384 | resource->packed_size = data.fetchWordBE(); 385 | resource->unused5 = data.fetchWordBE(); 386 | resource->unpacked_size = data.fetchWordBE(); 387 | resource->data = nullptr; 388 | if(resource->type != RT_END) { 389 | ++resource; 390 | } 391 | else { 392 | break; 393 | } 394 | } 395 | else { 396 | return false; 397 | } 398 | } 399 | dumpMemList(resources); 400 | } 401 | return true; 402 | } 403 | } 404 | return false; 405 | } 406 | 407 | auto MemList::dumpMemList(Resource* resources) -> bool 408 | { 409 | #ifndef NDEBUG 410 | ResourceStats total; 411 | ResourceStats sound; 412 | ResourceStats music; 413 | ResourceStats bitmap; 414 | ResourceStats palette; 415 | ResourceStats bytecode; 416 | ResourceStats polygon1; 417 | ResourceStats polygon2; 418 | ResourceStats unknown; 419 | 420 | auto percent = [](uint32_t count, uint32_t total) -> float 421 | { 422 | if(total != 0) { 423 | return 100.0f * (0.0f + (static_cast(count) / static_cast(total))); 424 | } 425 | return 0.0f; 426 | }; 427 | 428 | auto gain = [](uint32_t count, uint32_t total) -> float 429 | { 430 | if(total != 0) { 431 | return 100.0f * (1.0f - (static_cast(count) / static_cast(total))); 432 | } 433 | return 0.0f; 434 | }; 435 | 436 | auto log_one_resource = [&](const char* type, const Resource& resource) -> void 437 | { 438 | LOG_DEBUG("| 0x%02x | %-8s | %7d bytes | %7d bytes | %6.2f%% |", resource.id, type, resource.packed_size, resource.unpacked_size, gain(resource.packed_size, resource.unpacked_size)); 439 | }; 440 | 441 | auto log_all_resources = [&]() -> void 442 | { 443 | LOG_DEBUG("+------+----------+---------------+---------------+---------+"); 444 | LOG_DEBUG("| id | type | packed-size | unpacked-size | gain %% |"); 445 | LOG_DEBUG("+------+----------+---------------+---------------+---------+"); 446 | auto* resource = resources; 447 | while(resource->type != RT_END) { 448 | const char* type = nullptr; 449 | total.count += 1; 450 | total.packed += resource->packed_size; 451 | total.unpacked += resource->unpacked_size; 452 | switch(resource->type) { 453 | case RT_SOUND: 454 | type = "SOUND"; 455 | sound.count += 1; 456 | sound.packed += resource->packed_size; 457 | sound.unpacked += resource->unpacked_size; 458 | break; 459 | case RT_MUSIC: 460 | type = "MUSIC"; 461 | music.count += 1; 462 | music.packed += resource->packed_size; 463 | music.unpacked += resource->unpacked_size; 464 | break; 465 | case RT_BITMAP: 466 | type = "BITMAP"; 467 | bitmap.count += 1; 468 | bitmap.packed += resource->packed_size; 469 | bitmap.unpacked += resource->unpacked_size; 470 | break; 471 | case RT_PALETTE: 472 | type = "PALETTE"; 473 | palette.count += 1; 474 | palette.packed += resource->packed_size; 475 | palette.unpacked += resource->unpacked_size; 476 | break; 477 | case RT_BYTECODE: 478 | type = "BYTECODE"; 479 | bytecode.count += 1; 480 | bytecode.packed += resource->packed_size; 481 | bytecode.unpacked += resource->unpacked_size; 482 | break; 483 | case RT_POLYGON1: 484 | type = "POLYGON1"; 485 | polygon1.count += 1; 486 | polygon1.packed += resource->packed_size; 487 | polygon1.unpacked += resource->unpacked_size; 488 | break; 489 | case RT_POLYGON2: 490 | type = "POLYGON2"; 491 | polygon2.count += 1; 492 | polygon2.packed += resource->packed_size; 493 | polygon2.unpacked += resource->unpacked_size; 494 | break; 495 | default: 496 | type = "UNKNOWN"; 497 | unknown.count += 1; 498 | unknown.packed += resource->packed_size; 499 | unknown.unpacked += resource->unpacked_size; 500 | break; 501 | } 502 | log_one_resource(type, *resource); 503 | ++resource; 504 | } 505 | LOG_DEBUG("+------+----------+---------------+---------------+---------+"); 506 | }; 507 | 508 | auto log_one_stats = [&](const char* type, const ResourceStats& stats) -> void 509 | { 510 | LOG_DEBUG("| %-8s | %5d | %7d bytes | %7d bytes | %6.2f%% | %6.2f%% |", type, stats.count, stats.packed, stats.unpacked, gain(stats.packed, stats.unpacked), percent(stats.unpacked, total.unpacked)); 511 | }; 512 | 513 | auto log_all_stats = [&]() -> void 514 | { 515 | LOG_DEBUG("+----------+-------+---------------+---------------+---------+---------+"); 516 | LOG_DEBUG("| type | count | packed-size | unpacked-size | gain %% | total %% |"); 517 | LOG_DEBUG("+----------+-------+---------------+---------------+---------+---------+"); 518 | log_one_stats("SOUND" , sound ); 519 | log_one_stats("MUSIC" , music ); 520 | log_one_stats("BITMAP" , bitmap ); 521 | log_one_stats("PALETTE" , palette ); 522 | log_one_stats("BYTECODE", bytecode); 523 | log_one_stats("POLYGON1", polygon1); 524 | log_one_stats("POLYGON2", polygon2); 525 | log_one_stats("UNKNOWN" , unknown ); 526 | log_one_stats("TOTAL" , total ); 527 | LOG_DEBUG("+----------+-------+---------------+---------------+---------+---------+"); 528 | }; 529 | 530 | if(resources != nullptr) { 531 | log_all_resources(); 532 | log_all_stats(); 533 | } 534 | #endif 535 | return true; 536 | } 537 | 538 | auto MemList::loadResource(Resource& resource) -> bool 539 | { 540 | char path[PATH_MAX] = ""; 541 | 542 | if(_dataDir.empty() == false) { 543 | (void) ::snprintf(path, sizeof(path), "%s/BANK%02X", _dataDir.c_str(), resource.bank_id); 544 | } 545 | if(path[0] != '\0') { 546 | File file("stdio"); 547 | if(file.open(path, "rb") != false) { 548 | if(file.seek(resource.bank_offset) == false) { 549 | return false; 550 | } 551 | if(file.read(resource.data, resource.packed_size) == false) { 552 | return false; 553 | } 554 | if(resource.packed_size != resource.unpacked_size) { 555 | ByteKiller bytekiller(resource.data, resource.packed_size, resource.unpacked_size); 556 | 557 | return bytekiller.unpack(); 558 | } 559 | return true; 560 | } 561 | } 562 | return false; 563 | } 564 | 565 | auto MemList::dumpResource(const Resource& resource) -> void 566 | { 567 | char path[PATH_MAX] = ""; 568 | 569 | if(resource.data == nullptr) { 570 | return; 571 | } 572 | if(_dumpDir.empty() == false) { 573 | const char* type = nullptr; 574 | switch(resource.type) { 575 | case RT_SOUND: 576 | type = "sound"; 577 | break; 578 | case RT_MUSIC: 579 | type = "music"; 580 | break; 581 | case RT_BITMAP: 582 | type = "bitmap"; 583 | break; 584 | case RT_PALETTE: 585 | type = "palette"; 586 | break; 587 | case RT_BYTECODE: 588 | type = "bytecode"; 589 | break; 590 | case RT_POLYGON1: 591 | type = "polygon1"; 592 | break; 593 | case RT_POLYGON2: 594 | type = "polygon2"; 595 | break; 596 | default: 597 | type = "unknown"; 598 | break; 599 | } 600 | (void) ::snprintf(path, sizeof(path), "%s/%02x_%s.data", _dumpDir.c_str(), resource.id, type); 601 | } 602 | if(path[0] != '\0') { 603 | File file("stdio"); 604 | if(file.open(path, "wb") != false) { 605 | file.write(resource.data, resource.unpacked_size); 606 | } 607 | } 608 | } 609 | 610 | // --------------------------------------------------------------------------- 611 | // End-Of-File 612 | // --------------------------------------------------------------------------- 613 | --------------------------------------------------------------------------------