├── .github └── workflows │ ├── build.yml │ └── deploy.yml ├── .gitignore ├── CMakeLists.txt ├── README.md ├── common ├── emsc.html └── shell.html ├── docs ├── 3DO.md ├── Amiga_DOS.md ├── File220.png ├── File221.png ├── File224.png ├── File225.png ├── File226.png ├── File246.png ├── File302.png ├── File306.png ├── WiiU.md ├── amiga_opcodes_histogram.png ├── clf10_hd.png ├── clf10_sd.png ├── copy_protection.png ├── dos-code.png ├── dosbox-unp.png ├── file067.png ├── file068.png ├── file069.png ├── file070.png ├── file072.png ├── file073.png ├── file144.png ├── file145.png ├── fxplayer.s ├── logo-aw.png ├── logo-ootw.png ├── palette-intro-amiga.png ├── protection-amiga-presskit-1.png ├── protection-amiga-presskit-2.png ├── screenshot-code.png ├── screenshot-intro-3do-light.png ├── screenshot-intro-3do.png ├── screenshot-intro-amiga-light.png ├── screenshot-intro-amiga.png └── screenshot-protection.png ├── fips ├── fips-files ├── configs │ ├── metal-osx-vscode-debug.yml │ ├── metal-osx-vscode-release.yml │ ├── metal-osx-xcode-debug.yml │ ├── metal-osx-xcode-release.yml │ ├── wasm-ninja-debug.yml │ └── wasm-ninja-release.yml └── verbs │ └── webpage.py ├── fips.cmd ├── fips.yml ├── libs ├── CMakeLists.txt └── sokol │ ├── CMakeLists.txt │ ├── clock.c │ ├── clock.h │ ├── fs.c │ ├── fs.h │ ├── gfx.c │ ├── gfx.h │ ├── miniz.c │ ├── miniz.h │ ├── shaders.glsl │ ├── sokol.c │ ├── sokol.m │ ├── ui.cc │ ├── ui.h │ ├── ui_display.h │ └── ui_settings.h ├── src ├── CMakeLists.txt ├── game.h ├── raw-ui-impl.cc ├── raw.c └── ui │ ├── raw_dasm.h │ ├── ui_dasm.h │ ├── ui_dbg.h │ ├── ui_game.h │ ├── ui_snapshot.h │ └── ui_util.h └── tools ├── convert_3do ├── Makefile ├── bitmap.cpp ├── bitmap.h ├── cinepak.cpp ├── cinepak.h ├── main.cpp ├── opera.cpp ├── opera.h ├── util.cpp ├── util.h ├── wave.cpp └── wave.h ├── convert_segacd └── convert_segacd.py ├── convert_soundfx ├── Makefile ├── convert.sh └── main.cpp ├── convert_wiiu ├── convert_wiiu_20th.py └── extract_wiiu.py ├── decode_mat ├── Makefile ├── bitmap.c ├── bitmap.h ├── data.h └── decode_mat.c └── disasm ├── Makefile └── disasm.cpp /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | windows: 7 | runs-on: windows-latest 8 | steps: 9 | - uses: actions/checkout@v1 10 | - name: win64-vstudio-debug 11 | run: python3 fips build win64-vstudio-debug 12 | - name: win64-vstudio-release 13 | run: python3 fips build win64-vstudio-release 14 | mac: 15 | runs-on: macos-latest 16 | steps: 17 | - uses: actions/checkout@v1 18 | - name: osx-make-debug 19 | run: python3 fips build osx-make-debug 20 | - name: osx-make-release 21 | run: python3 fips build osx-make-release 22 | linux: 23 | runs-on: ubuntu-latest 24 | steps: 25 | - uses: actions/checkout@v1 26 | - name: prepare 27 | run: | 28 | sudo apt-get update 29 | sudo apt-get install libglu1-mesa-dev mesa-common-dev xorg-dev libasound-dev 30 | - name: linux-make-debug 31 | run: python3 fips build linux-make-debug 32 | - name: linux-make-release 33 | run: python3 fips build linux-make-release 34 | emscripten: 35 | runs-on: ubuntu-latest 36 | steps: 37 | - uses: actions/checkout@v1 38 | - name: install emsdk 39 | run: | 40 | sudo apt-get install ninja-build 41 | python3 fips emsdk install latest 42 | - name: wasm-ninja-debug 43 | run: python3 fips build wasm-ninja-debug 44 | - name: wasm-ninja-release 45 | run: python3 fips build wasm-ninja-release 46 | -------------------------------------------------------------------------------- /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | name: Deploy Webpage 2 | on: workflow_dispatch 3 | 4 | jobs: 5 | build: 6 | runs-on: ubuntu-latest 7 | steps: 8 | - uses: actions/checkout@v2 9 | - uses: seanmiddleditch/gha-setup-ninja@master 10 | - name: prepare 11 | run: | 12 | sudo apt update 13 | sudo apt install webp 14 | python3 fips emsdk install latest 15 | - name: build 16 | run: | 17 | python3 fips set local on 18 | python3 fips webpage build 19 | - name: upload-artifact 20 | uses: actions/upload-artifact@main 21 | with: 22 | name: webpage 23 | 24 | path: fips-files/deploy/raw 25 | retention-days: 1 26 | deploy: 27 | needs: build 28 | runs-on: ubuntu-latest 29 | steps: 30 | - uses: actions/checkout@main 31 | with: 32 | repository: scemino/raw_wp 33 | token: ${{ secrets.GHACTION_PUSH}} 34 | - uses: actions/download-artifact@main 35 | with: 36 | name: webpage 37 | - name: "commit and push" 38 | run: | 39 | git config user.email "none" 40 | git config user.name "GH Action" 41 | git add . 42 | git commit -m "updated (${{ github.run_number }})" 43 | git push 44 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | #>fips 2 | # this area is managed by fips, do not edit 3 | .fips-* 4 | fips-files/build/ 5 | fips-files/deploy/ 6 | *.pyc 7 | .vscode/ 8 | .idea/ 9 | CMakeUserPresets.json 10 | # 2 | 3 | 4 | 5 | 6 | Another World 7 | 32 | 33 | 34 | 35 | 61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /common/shell.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Emscripten-Generated Code 6 | 28 | 29 | 30 | 31 | 61 | {{{ SCRIPT }}} 62 | 63 | 64 | -------------------------------------------------------------------------------- /docs/3DO.md: -------------------------------------------------------------------------------- 1 | 2 | # Another World 3DO Technical Notes 3 | 4 | Another World has been ported on many platforms. The way the game was written (interpreted game logic) clearly helped. 5 | 6 | This document focuses on the 3DO release made by Interplay in 1994. This version was not a straight port. In addition to reworking the assets, the game code was modified. 7 | 8 | - [Assets](#assets) 9 | - [Game Code](#game-code) 10 | - [Game Copy Protection](#game-copy-protection) 11 | - [Bytecode](#bytecode) 12 | - [Resources](#resources) 13 | - [Rendering](#rendering) 14 | - [Introduction Sequence Synchronization](#introduction-sequence-synchronization) 15 | - [Game Passwords](#game-passwords) 16 | 17 | ## Assets 18 | 19 | Type | Amiga/DOS | 3DO 20 | -------- | --------- | --- 21 | Music | 4 channels tracker with raw signed 8bits samples (3 files) | AIFF SDX2 compressed audio (30 files) 22 | Sound | raw signed 8bits mono (103 files) | AIFF signed 16 bits (92 files) 23 | Bitmap | 4 bits depth paletted 320x200 (8 files) | True-color RGB555 320x200 (139 files) 24 | Bytecode | Big-endian (Motorola 68000) | Little-endian (ARMv3) 25 | 26 | ## Game Code 27 | 28 | The game code is split in 9 different sections. 29 | 30 | ``` 31 | 16000.asm - protection screen 32 | 16001_intro.asm 33 | 16002_eau.asm - water 34 | 16003_pri.asm - jail 35 | 16004_cite.asm 36 | 16005_arene.asm 37 | 16006_luxe.asm 38 | 16007_final.asm 39 | 16008.asm - password screen 40 | ``` 41 | 42 | Each section can be used as a starting point by the engine, with _vars[0] set to start at a specific position within that section. 43 | 44 | ![Screenshot Code](screenshot-code.png) 45 | 46 | The password screen bytecode contains series of checks such as below to lookup the code entered. 47 | 48 | ``` 49 | 02E3: jmpIf(VAR(0x07) != 10, @0303) 50 | 02E9: jmpIf(VAR(0x08) != 2, @0303) 51 | 02EF: jmpIf(VAR(0x09) != 9, @0303) 52 | 02F5: jmpIf(VAR(0x0A) != 2, @0303) 53 | 02FB: VAR(0x00) = 10 54 | 02FF: updateResources(res=16002) 55 | ``` 56 | 57 | The 4 variables (var7..varA) hold the index of the letter selected. 58 | 59 | ``` 60 | alphabet = [ 'B', 'C', 'D', 'F', 'G', 'H', '?', '?', 'J', 'K', 'L', 'R', 'T', 'X' ] 61 | ``` 62 | 63 | ## Game Copy Protection 64 | 65 | With the original Amiga/DOS version, the player has to lookup some symbols from a code wheel provided with the game. 66 | 67 | ![Screenshot Code](screenshot-protection.png) 68 | 69 | In addition to checking if the symbols entered are the correct ones, the game code is setting a few variables which are checked during gameplay. 70 | This was probably added to defeat game cracks that would simply bypass the game protection screen. 71 | 72 | The Amiga version bytecode checks 4 variables : 73 | 74 | ``` 75 | 0091: VAR(0xBC) &= 16 76 | 0095: VAR(0xC6) &= 128 77 | 0099: jmpIf(VAR(0xBC) == 0, @00B3) 78 | 009F: jmpIf(VAR(0xC6) == 0, @00B3) 79 | 00A5: jmpIf(VAR(0xF2) != 6000, @00B3) 80 | 00AC: jmpIf(VAR(0xDC) != 33, @00B3) 81 | ``` 82 | 83 | The DOS version bytecode checks 5 variables : 84 | 85 | ``` 86 | 00B7: VAR(0xBC) &= 16 87 | 00BB: VAR(0xF8) = VAR(0xC6) 88 | 00BE: VAR(0xF8) &= 128 89 | 00C2: jmpIf(VAR(0xBC) == 0, @00E9) 90 | 00C8: jmpIf(VAR(0xF8) == 0, @00E9) 91 | 00CE: VAR(0xF8) = VAR(0xE4) 92 | 00D1: VAR(0xF8) &= 60 93 | 00D5: jmpIf(VAR(0xF8) != 20, @00E9) 94 | 00DB: jmpIf(VAR(0xF2) != 4000, @00E9) 95 | 00E2: jmpIf(VAR(0xDC) != 33, @00E9) 96 | ``` 97 | 98 | ## Bytecode 99 | 100 | The game engine is based on a virtual machine with 30 opcodes. 101 | 102 | ### Opcodes 103 | 104 | The game was developed on an Amiga. As the Motorola 68000 CPU was big endian, this is the byte order found in the bytecode on the original Amiga/DOS versions. 105 | Most of the opcodes operand size is 2 bytes, matching the register size of the target 68000 CPU. 106 | 107 | * ALU 108 | 109 | Num | Name | Parameters | Description 110 | --- | ---- | ---------- | --- 111 | 0 | movConst | var: byte, value: word | var := value 112 | 1 | mov | var(dst): byte, var(src): byte | dst := src 113 | 2 | add | var(dst): byte, var(src): byte | dst += src 114 | 3 | addConst | var: byte; value: word | var += value 115 | 19 | sub | var(dst): byte, var(src): byte | var -= src 116 | 20 | and | var: byte, value: word | var &= value 117 | 21 | or | var: byte, value: word | var |= value 118 | 22 | shl | var: byte, count: word | var <<= count 119 | 23 | shr | var: byte, count: word | var >>= count 120 | 121 | * Control flow 122 | 123 | Num | Name | Parameters | Description 124 | --- | ---- | ---------- | --- 125 | 4 | call | offset: word | function call 126 | 5 | ret | | return from function call 127 | 7 | jmp | offset: word | pc = offset 128 | 9 | jmpIfVar | var: byte, offset: word | --var != 0: pc = offset (dbra) 129 | 10 | condJmp | operator: byte, var(operand1): byte, operand2: byte/word, offset: word | cmp op1, op2: pc = offset 130 | 131 | * Coroutines 132 | 133 | Num | Name | Parameters | Description 134 | --- | ---- | ---------- | --- 135 | 8 | installTask | num: byte, offset: word | setup a coroutine, pc = offset 136 | 6 | yieldTask | | pause current coroutine 137 | 12 | changeTasksState | num1: byte, num2: byte, state:byte | change coroutines num1..num2 state 138 | 17 | removeTask | | abort current coroutine 139 | 140 | * Display 141 | 142 | Num | Name | Parameters | Description 143 | --- | ---- | ---------- | --- 144 | 11 | setPalette | num: word | set palette (16 colors) 145 | 13 | selectPage | num: byte | set current drawing page 146 | 14 | fillPage | num: byte, color: byte | fill page with color 147 | 15 | copyPage | num(dst): byte, num(src): byte | copy page content 148 | 16 | updateDisplay | num: byte | present page to screen 149 | 18 | drawString | num: word, x(/8): byte, y(/8): byte, color: byte | draw string 150 | 151 | * Assets 152 | 153 | Num | Name | Parameters | Description 154 | --- | ---- | ---------- | --- 155 | 24 | playSound | num: word, frequency: byte, volume: byte, channel: byte | play sound 156 | 25 | updateResources | num: word | load asset resource or section 157 | 26 | playMusic | num: word, pitch: word, position: byte | play music 158 | 159 | ### 3DO Specific Opcodes 160 | 161 | The 3DO port does not use the same bytecode. The game code was modified, recompiled and saved under the target endianness of the target ARM CPU of the 3DO. 162 | In the process, some opcodes have been added and some others have been optimized for size by using one byte operand when applicable. 163 | 164 | The new shift opcode is a good example : the original bytecode was using a 16bits value to specify the number of bits to shift on the target 16 bits register. 165 | 8 bits were twice as enough. 166 | 167 | Num | Name | Parameters | Description 168 | --- | ---- | -----------| --- 169 | 11 | setPalette | num: byte | 170 | 22 | shiftLeft | var: byte, count: byte | var <<= count 171 | 23 | shiftRight | var: byte, count: byte | var >>= count 172 | 26 | playMusic | num: byte | 173 | 27 | drawString | num: byte, var(x): byte, var(y): byte, color: byte | 174 | 28 | jmpIfVarFalse | var: byte, offset: word | if var == 0: pc = offset 175 | 29 | jmpIfVarTrue | var: byte, offset: word | if var != 0: pc = offset 176 | 30 | printTime | | output running time (via SWI 0x1000E), not referenced in game code 177 | 178 | ## Resources 179 | 180 | As any 3DO game, the data-files are read from an OperaFS CD-ROM. 181 | 182 | Inside the GameData/ directory 183 | * The game data-files are numbered from 1 to 340 (File%d) 184 | * The song files (AIFF-C) are numbered from 1 to 30 (song%d) 185 | * Three cinematics files : Logo.Cine, Spintitle.Cine, ootw2.cine 186 | 187 | The files are stored uncompressed at the exception of the background bitmaps. 188 | The decompression code can be found in the DOOM 3DO source code - [dlzss.s](https://github.com/Olde-Skuul/doom3do/blob/master/lib/burger/dlzss.s) 189 | 190 | ## Rendering 191 | 192 | ### Transparency 193 | 194 | The engine can display semi-transparent shapes such as the car lights in the introduction. 195 | 196 | ![Screenshot Intro Amiga](screenshot-intro-amiga.png) ![Screenshot Intro 3DO](screenshot-intro-3do.png) 197 | 198 | The original Amiga/DOS game used a palette of 16 colors. The semi-transparency is achieved by allocating the upper half of the palette to the transparent colors. 199 | The palette indexes 0 to 7 hold the scene colors, indexes 8 to 15 the blended colors. 200 | 201 | ![Palette Intro Amiga](palette-intro-amiga.png) 202 | 203 | Turning a pixel semi-transparent is simply done by setting to 1 the bit 3 of the palette color index (|= 8). 204 | 205 | This is not directly applicable to the 3DO which renders everything with true-color buffers. 206 | The 3DO engine leverages the console capabilities to render an alpha blended shape. Interestingly, the color of the lights is not yellow in that version. 207 | 208 | ![Screenshot Intro Amiga](screenshot-intro-amiga-light.png) ![Screenshot Intro 3DO](screenshot-intro-3do-light.png) 209 | 210 | ### Background Bitmaps 211 | 212 | While the original Amiga/DOS game relied on polygons for most of its graphics, the game engine supports using a raster bitmap to be used as the background. 213 | In the original game version, this is only used for 8 different screens. 214 | 215 | ![file067](file067.png) ![file068](file068.png) ![file069](file069.png) ![file070](file070.png) 216 | 217 | ![file072](file072.png) ![file073](file073.png) ![file144](file144.png) ![file145](file145.png) 218 | 219 | The 3DO version uses the feature for all the screens of the game. For comparison, the same screens in RGB555. 220 | 221 | ![File246](File246.png) ![File224](File224.png) ![File225](File225.png) ![File226](File226.png) 222 | 223 | ![File221](File221.png) ![File220](File220.png) ![File306](File306.png) ![File302](File302.png) 224 | 225 | ### Drawing Primitives 226 | 227 | The original Amiga/DOS used only one primitive for all its drawing : a quad (4 vertices). 228 | 229 | The 3DO reworked that format, probably to optimize shapes made of single pixels or straight lines. 230 | The upper nibble of the shape color byte specifies the primitive to draw. 231 | 232 | ``` 233 | 0x00 : nested/composite shape 234 | 0x20 : rectangle 235 | 0x40 : pixel 236 | 0xC0 : polygon/quad 237 | ``` 238 | 239 | ## Introduction Sequence Synchronization 240 | 241 | On Amiga/DOS, the introduction is synchronized to the music. 242 | 243 | The music module contains 2 bytes patterns, that are copied to the variable 0xF4. 244 | 245 | ``` 246 | if (pat.note_1 == -3) { 247 | _vars[0xF4] = pat.note_2; 248 | } 249 | ``` 250 | 251 | The condition can be found in the original 68000 SoundFX player [routine](https://github.com/cyxx/rawgl/blob/master/docs/fxplayer.s#L555). 252 | 253 | The bytecode contains checks on this variable to wait and continue. 254 | 255 | ``` 256 | 000B: (00) VAR(0xF4) = 0 257 | 0F66: (0A) jmpIf(VAR(0xF4) == 43, @0F91) 258 | ... 259 | 1399: (0A) jmpIf(VAR(0xF4) == 46, @13AA) 260 | 13AE: (0A) jmpIf(VAR(0xF4) == 47, @13BF) 261 | 13C3: (0A) jmpIf(VAR(0xF4) == 48, @13D4) 262 | 13F6: (0A) jmpIf(VAR(0xF4) != 49, @1406) 263 | ``` 264 | 265 | 266 | On the 3DO, the tracker based music has been replaced with digital tracks. 267 | The synchronization had to be modified. 268 | 269 | That version relies on the variable 0xF7. It holds the total number of VBLs since the game started. 270 | 271 | ``` 272 | nbtrame = readTick() - OldVBL; 273 | if (_vars[0xFF] != 0) { 274 | while (_vars[0xFF] > nbtrame) { 275 | nbtrame = readTick() - OldVBL; 276 | } 277 | } 278 | _vars[0xF7] += nbtrame; 279 | OldVBL += nbtrame; 280 | ``` 281 | 282 | The introduction bytecode has the checks against this variable for the timing. 283 | 284 | ``` 285 | 0068: jmpIf(VAR(0xF7) < 3450, @0067) 286 | 043E: jmpIf(VAR(0xF7) < 7484, @043D) 287 | 046A: jmpIf(VAR(0xF7) < 8602, @0469) 288 | jmpIf(VAR(0xF7) < 9212, @0576) 289 | ... 290 | jmpIf(VAR(0xF7) < 400, @1C40) 291 | ``` 292 | 293 | ## Game Passwords 294 | 295 | Game passwords extracted from the 16008 part bytecode. 296 | 297 | Code | Part | Checkpoint | Notes 298 | ---- | ----- | ---------- | ----- 299 | LDKD | 16002 | 10 | 300 | HTDC | 16003 | 20 | 301 | CLLD | 16004 | 30 | 302 | FXLC | 16004 | 35 | 303 | KRFK | 16004 | 37 | 304 | XDDJ | 16004 | 33 | 305 | LBKG | 16004 | 31 | 306 | KLFB | 16004 | 39 | 307 | TTCT | 16004 | 41 | 308 | DDRX | 16004 | 42 | 309 | TBHK | 16004 | 43 | 310 | BRTD | 16004 | 49 | 311 | CKJL | 16005 | 50 | 312 | LFCK | 16006 | 60 | 313 | BFLX | 16004 | 44 | 314 | XJRT | 16004 | 45 | 315 | HRTB | 16004 | 46 | 316 | HBHK | 16004 | 47 | 317 | JCGB | 16004 | 48 | 318 | HHFL | 16006 | 62 | 319 | TFBB | 16006 | 64 | 320 | TXHF | 16006 | 66 | 321 | KRTD | 16007 | 70 | 322 | BRGR | 16010 | -1 | Stalactites 323 | -------------------------------------------------------------------------------- /docs/Amiga_DOS.md: -------------------------------------------------------------------------------- 1 | 2 | # Another World Amiga/DOS Technical notes 3 | 4 | This document lists differences found in the Amiga and DOS executables and bytecode of Another World. 5 | 6 | - [Game Copy Protection](#game-copy-protection) 7 | - [Variables](#variables) 8 | - [Opcodes Dispatch](#opcodes-dispatch) 9 | - [Data Files](#data-files) 10 | - [Cracks](#cracks) 11 | 12 | ## Game Copy Protection 13 | 14 | To prevent piracy, the game was bundled with a code wheel. 15 | 16 | The player has to lookup the randomized symbols everytime the game starts. 17 | 18 | ![Screenshot Code](screenshot-protection.png) 19 | 20 | After checking the symbols entered are the correct ones, a few variables are set and checked during gameplay. 21 | 22 | This was probably added to defeat game cracks that would simply bypass the game protection screen. 23 | 24 | The Amiga version bytecode checks 4 variables : 25 | 26 | ``` 27 | 0081: VAR(0xBC) &= 16 28 | 0085: VAR(0xF8) = VAR(0xC6) 29 | 0088: VAR(0xF8) &= 128 30 | 008C: jmpIf(VAR(0xBC) == 0, @00A6) 31 | 0092: jmpIf(VAR(0xF8) == 0, @00A6) 32 | 0098: jmpIf(VAR(0xF2) != 6000, @00A6) 33 | 009F: jmpIf(VAR(0xDC) != 33, @00A6) 34 | ``` 35 | 36 | The DOS version bytecode checks 5 variables : 37 | 38 | ``` 39 | 00B7: (14) VAR(0xBC) &= 16 40 | 00BB: (01) VAR(0xF8) = VAR(0xC6) 41 | 00BE: (14) VAR(0xF8) &= 128 42 | 00C2: (0A) jmpIf(VAR(0xBC) == 0, @00E9) 43 | 00C8: (0A) jmpIf(VAR(0xF8) == 0, @00E9) 44 | 00CE: (01) VAR(0xF8) = VAR(0xE4) 45 | 00D1: (14) VAR(0xF8) &= 60 46 | 00D5: (0A) jmpIf(VAR(0xF8) != 20, @00E9) 47 | 00DB: (0A) jmpIf(VAR(0xF2) != 4000, @00E9) 48 | 00E2: (0A) jmpIf(VAR(0xDC) != 33, @00E9) 49 | ``` 50 | 51 | The variables 0xBC, 0xC6 and 0xF2 are set by the bytecode when the symbols match. 52 | 53 | The variables 0xDC and 0xE4 are set by the engine code. 54 | 0xE4 is initialized to 20. 55 | 0xDC is set when the protection part successfully exits. 56 | 57 | ``` 58 | _vars[0xE4] = 20; 59 | ... 60 | if (_part == 16000 && _vars[0x67] == 1) { 61 | _vars[0xDC] = 33; 62 | } 63 | ``` 64 | 65 | While it is possible to use the Amiga datafiles with the DOS executable, the 0xE4 variable prevents the other way around. 66 | 67 | 68 | ## Game Code 69 | 70 | The game code is split in 9 different sections. 71 | 72 | Part | Name | Comment 73 | ----- | ----- | ------- 74 | 16000 | | protection screen 75 | 16001 | intro | 76 | 16002 | eau | 77 | 16003 | pri | 78 | 16004 | cite | 79 | 16005 | arene | 80 | 16006 | luxe | 81 | 16007 | final | 82 | 16008 | | password screen 83 | 84 | Each section can be used as a starting point by the engine, with _vars[0] set to start at a specific position within that section. 85 | 86 | ![DOS Code](dos-code.png) 87 | 88 | The password screen bytecode contains series of checks such as below to lookup the code entered. 89 | 90 | ``` 91 | 08B6: (0A) jmpIf(VAR(0x1E) != 21, @08D8) 92 | 08BC: (0A) jmpIf(VAR(0x1F) != 15, @08D8) 93 | 08C2: (0A) jmpIf(VAR(0x20) != 14, @08D8) 94 | 08C8: (0A) jmpIf(VAR(0x21) != 20, @08D8) 95 | 08CE: (00) VAR(0x00) = 60 96 | 08D2: (19) updateResources(res=16006) 97 | ``` 98 | 99 | ## Variables 100 | 101 | The engine communicates with the game bytecode through special variables. 102 | 103 | ### 0x3C 104 | 105 | The variable 0x3C contains a random value set on engine initialization. 106 | The random seed is only used as part of the copy protection screen to randomize the symbols. 107 | 108 | On Amiga, this variable is initialized with [VHPOSR](http://amiga-dev.wikidot.com/hardware:vhposr). 109 | 110 | ``` 111 | 0B4E movea.l (_vars).l,a0 112 | 0B54 move.w ($DFF006).l,$78(a0) 113 | ``` 114 | 115 | On DOS, the value is read from the BIOS timer. 116 | 117 | ``` 118 | seg000:180D mov ax, 40h 119 | seg000:1810 mov es, ax 120 | seg000:1812 mov di, 6Ch 121 | seg000:1815 mov ax, es:[di] 122 | seg000:1818 xchg ah, al 123 | seg000:181A add al, bl 124 | seg000:181C adc ah, bh 125 | seg000:1820 mov ds:_vars+78h, ax 126 | ``` 127 | 128 | ### 0x54 129 | 130 | The game title was different when commercialized in the United States. 131 | 132 | The "Out of This World" and "Interplay" screens can be toggled by setting bit 7 in the variable 0x54. 133 | 134 | ![Logo Another World](logo-aw.png) ![Logo Out of This World](logo-ootw.png) 135 | ``` 136 | 0084: (0A) jmpIf(VAR(0x54) < 128, @00C4) 137 | 008A: (0E) fillPage(page=255, color=0) 138 | 008D: (0B) setPalette(num=0) 139 | 0090: (19) updateResources(res=18) 140 | 0093: (0D) selectPage(page=0) 141 | ``` 142 | 143 | ### 0xE5 144 | 145 | The Amiga bytecode maps both `jump` and `up` to the same button (0xFB). 146 | 147 | The DOS bytecode relies on the variable 0xE5 for the `jump` action. 148 | 149 | 150 | ## Opcodes Dispatch 151 | 152 | The Amiga executable dispatches the opcodes with a sequence of 'if/else if' conditions. 153 | 154 | ``` 155 | 0162 clr.l d0 156 | 0164 move.b (a0)+,d0 157 | 0166 btst #7,d0 158 | 016A bne.w loc_25C ; DrawShape1 159 | 016E btst #6,d0 160 | 0172 bne.w loc_296 ; DrawShape2 161 | 0176 cmp.b #$A,d0 162 | 017A beq.w loc_418 ; op_condJmp 163 | 017E cmp.b #0,d0 164 | 0182 beq.w loc_344 ; op_movConst 165 | 0186 cmp.b #1,d0 166 | 018A beq.w loc_35E ; op_mov 167 | 018E cmp.b #2,d0 168 | 0192 beq.w loc_37A ; op_add 169 | ... 170 | 0246 cmp.b #$1A,d0 171 | 024A beq.w loc_638 ; op_playMusic 172 | ``` 173 | 174 | The order of these checks roughly corresponds to the frequency of the opcodes found in the game code. 175 | 176 | ![Amiga Opcodes Histogram](amiga_opcodes_histogram.png) 177 | 178 | The DOS executable relies on a jump table. 179 | 180 | ``` 181 | seg000:1ABF xor ah, ah 182 | seg000:1AC1 mov al, es:[di] 183 | seg000:1AC4 inc di 184 | seg000:1AC5 test al, 80h 185 | seg000:1AC7 jnz loc_0_11AE4 ; DrawShape1 186 | seg000:1AC9 test al, 40h 187 | seg000:1ACB jnz loc_0_11B27 ; DrawShape2 188 | seg000:1ACD cmp al, 0 189 | seg000:1ACF jl loc_0_11ADD ; Invalid opcode 190 | seg000:1AD1 cmp al, 1Ah 191 | seg000:1AD3 jg loc_0_11ADD ; Invalid opcode 192 | seg000:1AD5 shl ax, 1 193 | seg000:1AD7 mov bx, ax 194 | seg000:1AD9 jmp ds:_op_table[bx] ; opcodes jump table 195 | ``` 196 | 197 | ## Data Files 198 | 199 | The game data is stored in several `Bank` files. The offsets and sizes for each file are stored in the `memlist.bin` file for the DOS version. 200 | 201 | On Amiga (and Atari ST), the `memlist.bin` file is stored in the executable itself. 202 | 203 | Version | Offset 204 | --------------- | ------ 205 | Amiga (French) | 0x5E7A 206 | Amiga (English) | 0x5EC2 207 | Atari ST | 0x7EF2 208 | 209 | 210 | ## Cracks 211 | 212 | The game copy protection was cracked after the game release. Let's have a closer look at several versions. 213 | 214 | ### DOS 215 | 216 | ``` 217 | $ md5sum ANOTHER.EXE 218 | 4b68a50a1cbb35dcb977932b11aa7b78 ANOTHER.EXE 219 | $ strings ANOTHER.EXE 220 | THIS GAME WAS CRACKED BY TDT 12-04-92 221 | ``` 222 | 223 | The game shows the copy protection screen but accepts any input for the symbols. 224 | 225 | 226 | From the output of the `strings` command, it seems the executable is packed. Let's run it through `unp`. 227 | 228 | ![dosbox unp](dosbox-unp.png) 229 | 230 | With an unpacked executable, we can now generate the disassembly. 231 | 232 | After mapping function names and reviewing the code, the conditional jump opcode implementation looks suspicious. 233 | 234 | ``` 235 | seg000:1B98 op_condJmp_tampered: 236 | seg000:1B98 push cs 237 | seg000:1B99 mov ax, offset loc_15206 238 | seg000:1B9C push ds 239 | seg000:1B9D push ax 240 | seg000:1B9E retf 241 | ``` 242 | 243 | Looking at the implementation of that trampoline function, it patches the bytecode from the offset 0xC4C. 244 | 245 | ``` 246 | seg001:0016 loc_15206: 247 | seg001:0016 cmp di, 0C4Ch ; bytecode offset 248 | seg001:001A jnz short loc_1521D 249 | seg001:001C mov byte ptr es:[di], 81h 250 | seg001:0020 mov word ptr es:[di+3], 0B70Ch 251 | seg001:0026 mov word ptr es:[di+99h], 0ED0Ch 252 | seg001:002D loc_1521D: 253 | seg001:002D mov ax, offset op_condJmp ; jump back to the original opcode implementation 254 | seg001:0030 push ax 255 | seg001:0031 xor ah, ah 256 | seg001:0033 xor ch, ch 257 | seg001:0035 mov al, es:[di] 258 | seg001:0038 retf 259 | ``` 260 | 261 | Let's dump the game bytecode around `0x0C4C` and `0x0CE5`. 262 | 263 | ``` 264 | 0C4B: (0A) jmpIf(VAR(0x29) == VAR(0x1E), @0C66) 265 | 0C51: (0A) jmpIf(VAR(0x29) == VAR(0x1F), @0C66) 266 | 0C57: (0A) jmpIf(VAR(0x29) == VAR(0x20), @0C66) 267 | 0C5D: (0A) jmpIf(VAR(0x29) == VAR(0x21), @0C66) 268 | 0C63: (07) jmp(@0D4F) 269 | ... 270 | 0CE1: (0A) jmpIf(VAR(0x32) < 6, @0D4F) 271 | 0CE7: (0A) jmpIf(VAR(0x64) < 20, @0D4F) 272 | ``` 273 | 274 | The variables 0x1E..0x21 contains the 4 symbols to be entered. 275 | Variables 0x32 and 0x64 hold other counters that must be greater than 6 and 20. 276 | 277 | 278 | Let's have a look at the bytecode patching. 279 | 280 | 281 | The bytes '0x81 0xB7, 0x0C' will change the first condition to accept any symbol. 282 | ``` 283 | 0C4B: (0A) jmpIf(VAR(0x29) != VAR(0x1E), @0CB7) 284 | ... 285 | 0CB7: (04) call(@0A15) 286 | ``` 287 | 288 | The bytes '0xED 0x0C' will patch the symbols counters comparison and exit the protection screen. 289 | ``` 290 | 0CE1: (0A) jmpIf(VAR(0x32) < 6, @0CED) 291 | 0CE7: (0A) jmpIf(VAR(0x64) < 20, @0D4F) 292 | 0CED: (04) call(@0A15) 293 | ``` 294 | 295 | With these in place, the two main protection checks are passing and the game can start. 296 | 297 | ### Amiga WHDLoad 298 | 299 | [WHDLoad](http://www.whdload.de/games/AnotherWorld.html) removes the protection at the installation time. 300 | 301 | The archive contains the source code of the patcher and supports 3 different versions. 302 | 303 | It also works by patching the bytecode. Let's have a look at the French version. 304 | 305 | The patcher works by intercepting the file reads and matches on the file offset. 306 | 307 | ``` 308 | cmp.l #$1B5EE,d1 ; protection load 309 | beq.b .prot 310 | ``` 311 | 312 | This corresponds to the resource #21 313 | 314 | ``` 315 | { 4, 0x1, 0x01B5EE, 0x0D2A, 0x0D2A }, 316 | ``` 317 | 318 | As the file is not compressed, the bytes can be modified in place. 319 | 320 | ``` 321 | ; 0A 80 29 1E 09 6A 0A 80 29 322 | lea $94f(a1),a2 323 | move.b #$07,(a2)+ 324 | move.b #$09,(a2)+ 325 | move.b #$bb,(a2) 326 | ; 0A 01 1B 15 0A 28 14 32 327 | lea $9db(a1),a2 328 | move.b #$07,(a2)+ 329 | move.b #$09,(a2)+ 330 | move.b #$f1,(a2) 331 | ``` 332 | 333 | These instructions replace conditionals with jumps. 334 | 335 | ``` 336 | ; 094F: (0A) jmpIf(VAR(0x29) == VAR(0x1E), @096A) 337 | 094F: (07) jmp(@09BB) 338 | ; 09DB: (0A) jmpIf(VAR(0x1B) != 21, @0A28) 339 | 09DB: (07) jmp(@09F1) 340 | ``` 341 | 342 | With the symbols and counters comparisons removed, the protection screen successfully exits. 343 | 344 | 345 | ### Amiga retro presskit 346 | 347 | The 20th Anniversary edition comes with a few extras, including the original English Amiga version as .adf files. 348 | This is part of the [retro presskit](http://thedigitalounge.com/dl/AnotherWorld-Retro-presskit.zip). 349 | 350 | The filenames clearly indicates the protection has been removed. 351 | 352 | ``` 353 | -rw-a-- 2.0 fat 901120 b- defX 06-Dec-22 15:59 AnotherWorld_DiskA_nologo_noprotec.adf 354 | -rw-a-- 2.0 fat 901120 b- defX 06-Dec-22 15:59 AnotherWorld_DiskB_nologo_noprotec.adf 355 | ``` 356 | 357 | Looking at the date and time modifications, we can see the main executable and the `bank01` have been modified. 358 | 359 | ``` 360 | $ unadf -l /tmp/AnotherWorld_DiskA_nologo_noprotec.adf 361 | 55096 1991/12/30 10:17:30 bank06 362 | 10366 1991/12/30 10:17:33 bank09 363 | 1991/12/30 10:06:25 Trashcan/ 364 | 25 1991/12/30 12:09:35 .info 365 | 52294 1991/12/30 10:17:48 bank0B 366 | 25108 1991/12/30 10:17:53 bank0C 367 | 94862 1991/12/30 10:18:16 bank0D 368 | 1991/12/30 10:10:31 s/ 369 | 251 1991/12/30 12:12:52 readme.txt 370 | 28964 2006/12/17 15:17:20 another 371 | 1166 1991/12/30 10:06:25 Trashcan.info 372 | 244868 2006/12/17 15:18:21 bank01 373 | 226172 1991/12/30 10:17:16 bank02 374 | ``` 375 | 376 | This is interesting because the `bank01` is the file where the protection screen bytecode is stored. 377 | 378 | Running the .adf through an emulator, we can verify the protection is passing if we select any symbols. 379 | It is however failing if less than 3 symbols are selected. 380 | 381 | ![Protection Amiga Presskit Error](protection-amiga-presskit-2.png) 382 | 383 | This hints only the first check of the bytecode has been patched (the symbol comparison) but not the counter. 384 | Let's dump the bytecode around these two checks. 385 | 386 | We can see the bytecode at 0x0A14 has been modified to jump as if a symbol matched. 387 | 388 | ``` 389 | 09FC: (0A) jmpIf(VAR(0x29) == VAR(0x1E), @0A68) 390 | 0A02: (0A) jmpIf(VAR(0x29) == VAR(0x1F), @0A68) 391 | 0A08: (0A) jmpIf(VAR(0x29) == VAR(0x20), @0A68) 392 | 0A0E: (0A) jmpIf(VAR(0x29) == VAR(0x21), @0A68) 393 | 0A14: (07) jmp(@0A68) 394 | ``` 395 | 396 | The second check has however not been patched, which explains the protection fails if no symbol is selected. 397 | 398 | ``` 399 | 0A88: (0A) jmpIf(VAR(0x1B) != 21, @0AD5) 400 | 0A8E: (14) VAR(0x32) &= 31 401 | 0A92: (0A) jmpIf(VAR(0x32) < 3, @0AD5) 402 | 0A98: (0A) jmpIf(VAR(0x64) < 20, @0AD5) 403 | 0A9E: (04) call(@081E) 404 | ``` 405 | -------------------------------------------------------------------------------- /docs/File220.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scemino/rawgl/98022415e10f4b60ec8733163703ef0bb3496522/docs/File220.png -------------------------------------------------------------------------------- /docs/File221.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scemino/rawgl/98022415e10f4b60ec8733163703ef0bb3496522/docs/File221.png -------------------------------------------------------------------------------- /docs/File224.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scemino/rawgl/98022415e10f4b60ec8733163703ef0bb3496522/docs/File224.png -------------------------------------------------------------------------------- /docs/File225.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scemino/rawgl/98022415e10f4b60ec8733163703ef0bb3496522/docs/File225.png -------------------------------------------------------------------------------- /docs/File226.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scemino/rawgl/98022415e10f4b60ec8733163703ef0bb3496522/docs/File226.png -------------------------------------------------------------------------------- /docs/File246.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scemino/rawgl/98022415e10f4b60ec8733163703ef0bb3496522/docs/File246.png -------------------------------------------------------------------------------- /docs/File302.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scemino/rawgl/98022415e10f4b60ec8733163703ef0bb3496522/docs/File302.png -------------------------------------------------------------------------------- /docs/File306.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scemino/rawgl/98022415e10f4b60ec8733163703ef0bb3496522/docs/File306.png -------------------------------------------------------------------------------- /docs/WiiU.md: -------------------------------------------------------------------------------- 1 | 2 | # Another World 20th Anniversary Edition (WiiU) 3 | 4 | Technical notes on the WiiU release. 5 | 6 | ## Resource Bundle 7 | 8 | The content directory of the game contains the music files (.OGG) and one resource bundle (.BIN) 9 | 10 | ``` 11 | content/ 12 | ├── archive.bin 13 | └── game 14 | └── OGG 15 | ├── amb5001.ogg 16 | ... 17 | └── amb5011.ogg 18 | ``` 19 | 20 | The file archive.bin contains the game data files. 21 | 22 | The offset to the entries table and the number of entries are stored at the end of the file as 32 bits 23 | big endian integers (BE32). 24 | 25 | Resource table entries format. 26 | 27 | Field | Type | Details 28 | ----- | ---- | ------- 29 | File name | \0 terminated string | File path 30 | Uncompressed file size | BE32 | File size after decompression 31 | File size | BE32 | File size in the resource bundle 32 | Data offset | BE32 | File data offset in the resource bundle 33 | Flags | BE32 | Either 0 or 1 34 | 35 | Data is compressed with [LZ4](https://github.com/lz4/lz4). 36 | 37 | ## Data Files 38 | 39 | File | Details 40 | ----- | ------- 41 | game/awt/data1728x1080/ | 1080p texture backgrounds 42 | game/bmp/data1280x800/ | 800p texture backgrounds (with a corrupted or incomplete palette ? maybe left-over) 43 | game/dat/*mac | bytecode 44 | game/dat/*mat | polygons (shapes) 45 | game/dat/*pal | RGB444 palette colors (Amiga) 46 | game/txt/*txt | strings ('Good evening professor.') 47 | game/wgz_mixed/*wav | sound files 48 | 49 | The 20th anniversary editions of Another World on consoles use higher resolution graphics than 50 | the Linux/Mac/Windows releases (1080p vs 800p). 51 | 52 | ## Textures 53 | 54 | The .awt (Another World Texture ?) files are the background pictures. 55 | The image pixels are stored as uncompressed RGBA (4 bytes). The file has a 12 bytes footer. 56 | Image dimensions are stored as 16 bits big endian integers (BE16). 57 | 58 | ``` 59 | 08 08 08 08 : RGBA color component depth ? 60 | BE16 : aligned width 61 | BE16 : height 62 | BE16 : width 63 | BE16 : height 64 | ``` 65 | 66 | ## Game Code 67 | 68 | The game bytecode contains two variants, one for the original low-resolution (320x200) and another one for 'HD' (1280x800). 69 | 70 | Name | Part | Files 71 | ------ | ----- | ----- 72 | Intro | 16001 | intro2011(hd).mac/.mat 73 | Eau | 16002 | eau2011(hd).mac/.mat 74 | Prison | 16003 | pri2011(hd).mac/.mat 75 | Cite | 16004 | cite2011(hd).mac/.mat 76 | Arene | 16005 | arene2011(hd).mac/.mat 77 | Luxe | 16006 | luxe2011(hd).mac/.mat 78 | Final | 16007 | final2011(hd).mac/.mat 79 | 80 | The only difference between the 'SD' and 'HD' bytecode files appear to be related to the polygons offsets, as expected. 81 | 82 | ``` 83 | $ diff -u intro2011.mac.asm intro2011hd.mac.asm 84 | ... 85 | -033A: (FA) drawShape(code=0xFA, x=160, y=100); // offset=0xF5F0 (bank1.mat) 86 | +033A: (FB) drawShape(code=0xFB, x=160, y=100); // offset=0xF608 (bank1.mat) 87 | 033E: (18) playSound(res=44, freq=15, vol=63, channel=2) 88 | ... 89 | ``` 90 | 91 | An example of polygons upscale (SD vs HD .mat shape) : 92 | 93 | ![clf10sd](clf10_sd.png) ![clf10hd](clf10_hd.png) 94 | 95 | 96 | ## Bytecode Variables 97 | 98 | The game engine communicates with the game code via special variables. 99 | 100 | Index | Details 101 | ----- | ------- 102 | 0xBF | Game difficulty (0 to 2) 103 | 0xDB | Enable sounds preloading (resource >= 2000) 104 | 0xDE | Enable playback of remastered sounds (set to 0 when running the game in original low resolution) 105 | 106 | 107 | ## Debugging Information 108 | 109 | The .elf executable has a symbol table section. 110 | 111 | ``` 112 | $ rpl2elf AnotherWorld_WiiU.rpx 113 | $ readelf -S AnotherWorld_WiiU.elf 114 | ... 115 | [23] .symtab SYMTAB c0000000 301bf0 02ac90 10 A 24 24411 4 116 | ... 117 | 118 | $ readelf -sW AnotherWorld_WiiU.elf | tr -s' ' | cut -d' ' -f9 | c++filt 119 | ... 120 | CInterpret::LoadGamePart(int, int) 121 | CInterpret::InterpretAll(void) 122 | CInterpret::Interpret(unsigned short) 123 | CInterpret::HandleControl(void) 124 | ... 125 | CDisplay::LoadImage(char const *, unsigned char *, int *, int *) 126 | CDisplay::LoadPal(char const *, unsigned int *) 127 | CDisplay::SetWorkScreen(int) 128 | CDisplay::ComputePalette(void) 129 | CDisplay::SetScreen(int) 130 | CDisplay::FillScreen(int, unsigned char) 131 | CDisplay::CopyScreen(int, int) 132 | CDisplay::LoadImage(int, unsigned char *, int *, int *) 133 | CDisplay::DrawImage(int) 134 | CDisplay::PrintAt(int, unsigned short, unsigned short, unsigned char) 135 | CDisplay::DrawScaledPoint(int, int, unsigned char) 136 | CDisplay::DrawShapeHD(unsigned char *, float, float, unsigned short, float, unsigned char) 137 | CDisplay::DrawHead(int, int, bool) 138 | CDisplay::DrawShape(unsigned char *, int, int, unsigned short, unsigned char) 139 | ``` 140 | -------------------------------------------------------------------------------- /docs/amiga_opcodes_histogram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scemino/rawgl/98022415e10f4b60ec8733163703ef0bb3496522/docs/amiga_opcodes_histogram.png -------------------------------------------------------------------------------- /docs/clf10_hd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scemino/rawgl/98022415e10f4b60ec8733163703ef0bb3496522/docs/clf10_hd.png -------------------------------------------------------------------------------- /docs/clf10_sd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scemino/rawgl/98022415e10f4b60ec8733163703ef0bb3496522/docs/clf10_sd.png -------------------------------------------------------------------------------- /docs/copy_protection.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scemino/rawgl/98022415e10f4b60ec8733163703ef0bb3496522/docs/copy_protection.png -------------------------------------------------------------------------------- /docs/dos-code.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scemino/rawgl/98022415e10f4b60ec8733163703ef0bb3496522/docs/dos-code.png -------------------------------------------------------------------------------- /docs/dosbox-unp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scemino/rawgl/98022415e10f4b60ec8733163703ef0bb3496522/docs/dosbox-unp.png -------------------------------------------------------------------------------- /docs/file067.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scemino/rawgl/98022415e10f4b60ec8733163703ef0bb3496522/docs/file067.png -------------------------------------------------------------------------------- /docs/file068.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scemino/rawgl/98022415e10f4b60ec8733163703ef0bb3496522/docs/file068.png -------------------------------------------------------------------------------- /docs/file069.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scemino/rawgl/98022415e10f4b60ec8733163703ef0bb3496522/docs/file069.png -------------------------------------------------------------------------------- /docs/file070.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scemino/rawgl/98022415e10f4b60ec8733163703ef0bb3496522/docs/file070.png -------------------------------------------------------------------------------- /docs/file072.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scemino/rawgl/98022415e10f4b60ec8733163703ef0bb3496522/docs/file072.png -------------------------------------------------------------------------------- /docs/file073.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scemino/rawgl/98022415e10f4b60ec8733163703ef0bb3496522/docs/file073.png -------------------------------------------------------------------------------- /docs/file144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scemino/rawgl/98022415e10f4b60ec8733163703ef0bb3496522/docs/file144.png -------------------------------------------------------------------------------- /docs/file145.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scemino/rawgl/98022415e10f4b60ec8733163703ef0bb3496522/docs/file145.png -------------------------------------------------------------------------------- /docs/fxplayer.s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scemino/rawgl/98022415e10f4b60ec8733163703ef0bb3496522/docs/fxplayer.s -------------------------------------------------------------------------------- /docs/logo-aw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scemino/rawgl/98022415e10f4b60ec8733163703ef0bb3496522/docs/logo-aw.png -------------------------------------------------------------------------------- /docs/logo-ootw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scemino/rawgl/98022415e10f4b60ec8733163703ef0bb3496522/docs/logo-ootw.png -------------------------------------------------------------------------------- /docs/palette-intro-amiga.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scemino/rawgl/98022415e10f4b60ec8733163703ef0bb3496522/docs/palette-intro-amiga.png -------------------------------------------------------------------------------- /docs/protection-amiga-presskit-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scemino/rawgl/98022415e10f4b60ec8733163703ef0bb3496522/docs/protection-amiga-presskit-1.png -------------------------------------------------------------------------------- /docs/protection-amiga-presskit-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scemino/rawgl/98022415e10f4b60ec8733163703ef0bb3496522/docs/protection-amiga-presskit-2.png -------------------------------------------------------------------------------- /docs/screenshot-code.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scemino/rawgl/98022415e10f4b60ec8733163703ef0bb3496522/docs/screenshot-code.png -------------------------------------------------------------------------------- /docs/screenshot-intro-3do-light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scemino/rawgl/98022415e10f4b60ec8733163703ef0bb3496522/docs/screenshot-intro-3do-light.png -------------------------------------------------------------------------------- /docs/screenshot-intro-3do.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scemino/rawgl/98022415e10f4b60ec8733163703ef0bb3496522/docs/screenshot-intro-3do.png -------------------------------------------------------------------------------- /docs/screenshot-intro-amiga-light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scemino/rawgl/98022415e10f4b60ec8733163703ef0bb3496522/docs/screenshot-intro-amiga-light.png -------------------------------------------------------------------------------- /docs/screenshot-intro-amiga.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scemino/rawgl/98022415e10f4b60ec8733163703ef0bb3496522/docs/screenshot-intro-amiga.png -------------------------------------------------------------------------------- /docs/screenshot-protection.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scemino/rawgl/98022415e10f4b60ec8733163703ef0bb3496522/docs/screenshot-protection.png -------------------------------------------------------------------------------- /fips: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """fips main entry""" 3 | import os 4 | import sys 5 | import subprocess 6 | proj_path = os.path.dirname(os.path.abspath(__file__)) 7 | fips_path = os.path.dirname(proj_path) + '/fips' 8 | if not os.path.isdir(fips_path) : 9 | print("\033[93m=== cloning fips build system to '{}':\033[0m".format(fips_path)) 10 | subprocess.call(['git', 'clone', 'https://github.com/floooh/fips.git', fips_path]) 11 | sys.path.insert(0,fips_path) 12 | try : 13 | from mod import fips 14 | except ImportError : 15 | print("\033[91m[ERROR]\033[0m failed to initialize fips build system in '{}'".format(proj_path)) 16 | sys.exit(10) 17 | fips.run(fips_path, proj_path, sys.argv) 18 | -------------------------------------------------------------------------------- /fips-files/configs/metal-osx-vscode-debug.yml: -------------------------------------------------------------------------------- 1 | --- 2 | platform: osx 3 | generator: Ninja 4 | build_tool: vscode 5 | build_type: Debug 6 | defines: 7 | SOKOL_USE_METAL: ON 8 | 9 | -------------------------------------------------------------------------------- /fips-files/configs/metal-osx-vscode-release.yml: -------------------------------------------------------------------------------- 1 | --- 2 | platform: osx 3 | generator: Ninja 4 | build_tool: vscode 5 | build_type: Release 6 | defines: 7 | SOKOL_USE_METAL: ON 8 | 9 | -------------------------------------------------------------------------------- /fips-files/configs/metal-osx-xcode-debug.yml: -------------------------------------------------------------------------------- 1 | --- 2 | platform: osx 3 | generator: Xcode 4 | build_tool: xcodebuild 5 | build_type: Debug 6 | defines: 7 | SOKOL_USE_METAL: ON 8 | -------------------------------------------------------------------------------- /fips-files/configs/metal-osx-xcode-release.yml: -------------------------------------------------------------------------------- 1 | --- 2 | platform: osx 3 | generator: Xcode 4 | build_tool: xcodebuild 5 | build_type: Release 6 | defines: 7 | SOKOL_USE_METAL: ON 8 | -------------------------------------------------------------------------------- /fips-files/configs/wasm-ninja-debug.yml: -------------------------------------------------------------------------------- 1 | --- 2 | platform: emscripten 3 | generator: Ninja 4 | build_tool: ninja 5 | build_type: Debug 6 | cmake-toolchain: emscripten.toolchain.cmake 7 | defines: 8 | FIPS_EMSCRIPTEN_USE_WASM: ON 9 | FIPS_EMSCRIPTEN_USE_WEBGL2: ON 10 | FIPS_EMSCRIPTEN_MEM_INIT_METHOD: 0 11 | FIPS_EMSCRIPTEN_USE_EMMALLOC: ON 12 | FIPS_EMSCRIPTEN_RELATIVE_SHELL_HTML: "common/shell.html" 13 | -------------------------------------------------------------------------------- /fips-files/configs/wasm-ninja-release.yml: -------------------------------------------------------------------------------- 1 | --- 2 | platform: emscripten 3 | generator: Ninja 4 | build_tool: ninja 5 | build_type: Release 6 | cmake-toolchain: emscripten.toolchain.cmake 7 | defines: 8 | FIPS_EMSCRIPTEN_USE_WASM: ON 9 | FIPS_EMSCRIPTEN_USE_WEBGL2: ON 10 | FIPS_EMSCRIPTEN_MEM_INIT_METHOD: 0 11 | FIPS_EMSCRIPTEN_USE_CLOSURE: ON 12 | FIPS_EMSCRIPTEN_USE_EMMALLOC: ON 13 | FIPS_EMSCRIPTEN_RELATIVE_SHELL_HTML: "common/shell.html" 14 | -------------------------------------------------------------------------------- /fips-files/verbs/webpage.py: -------------------------------------------------------------------------------- 1 | """fips verb to build the webpage""" 2 | 3 | import os 4 | import yaml 5 | import shutil 6 | import subprocess 7 | import glob 8 | from string import Template 9 | 10 | from mod import log, util, project 11 | 12 | BuildConfig = 'wasm-ninja-release' 13 | 14 | systems = [ 15 | 'raw', 'raw-ui' 16 | ] 17 | 18 | #------------------------------------------------------------------------------- 19 | def deploy_webpage(fips_dir, proj_dir, webpage_dir) : 20 | emsc_deploy_dir = util.get_deploy_dir(fips_dir, 'rawgl', BuildConfig) 21 | 22 | # generate emu HTML pages 23 | for system in systems : 24 | log.info('> generate emscripten HTML page: {}'.format(system)) 25 | for ext in ['wasm', 'js'] : 26 | src_path = '{}/{}.{}'.format(emsc_deploy_dir, system, ext) 27 | if os.path.isfile(src_path) : 28 | shutil.copy(src_path, '{}/'.format(webpage_dir)) 29 | with open(proj_dir + '/common/emsc.html', 'r') as f : 30 | templ = Template(f.read()) 31 | html = templ.safe_substitute(name=system, prog=system) 32 | with open('{}/{}.html'.format(webpage_dir, system), 'w') as f : 33 | f.write(html) 34 | 35 | #------------------------------------------------------------------------------- 36 | def build_deploy_webpage(fips_dir, proj_dir, rebuild) : 37 | # if webpage dir exists, clear it first 38 | proj_build_dir = util.get_deploy_root_dir(fips_dir, 'rawgl') 39 | webpage_dir = '{}/raw'.format(proj_build_dir) 40 | if rebuild : 41 | if os.path.isdir(webpage_dir) : 42 | shutil.rmtree(webpage_dir) 43 | if not os.path.isdir(webpage_dir) : 44 | os.makedirs(webpage_dir) 45 | 46 | # compile samples 47 | project.gen(fips_dir, proj_dir, BuildConfig) 48 | project.build(fips_dir, proj_dir, BuildConfig) 49 | 50 | # deploy the webpage 51 | deploy_webpage(fips_dir, proj_dir, webpage_dir) 52 | 53 | log.colored(log.GREEN, 'Generated Samples web page under {}.'.format(webpage_dir)) 54 | 55 | #------------------------------------------------------------------------------- 56 | def serve_webpage(fips_dir, proj_dir) : 57 | proj_build_dir = util.get_deploy_root_dir(fips_dir, 'rawgl') 58 | webpage_dir = '{}/raw'.format(proj_build_dir) 59 | p = util.get_host_platform() 60 | if p == 'osx' : 61 | try : 62 | subprocess.call( 63 | 'http-server -c-1 -g -o'.format(fips_dir), 64 | cwd = webpage_dir, shell=True) 65 | except KeyboardInterrupt : 66 | pass 67 | elif p == 'win': 68 | try: 69 | subprocess.call( 70 | 'http-server -c-1 -g -o'.format(fips_dir), 71 | cwd = webpage_dir, shell=True) 72 | except KeyboardInterrupt: 73 | pass 74 | elif p == 'linux': 75 | try: 76 | subprocess.call( 77 | 'http-server -c-1 -g -o'.format(fips_dir), 78 | cwd = webpage_dir, shell=True) 79 | except KeyboardInterrupt: 80 | pass 81 | 82 | #------------------------------------------------------------------------------- 83 | def run(fips_dir, proj_dir, args) : 84 | if len(args) > 0 : 85 | if args[0] == 'build' : 86 | build_deploy_webpage(fips_dir, proj_dir, False) 87 | elif args[0] == 'rebuild' : 88 | build_deploy_webpage(fips_dir, proj_dir, True) 89 | elif args[0] == 'serve' : 90 | serve_webpage(fips_dir, proj_dir) 91 | else : 92 | log.error("Invalid param '{}', expected 'build' or 'serve'".format(args[0])) 93 | else : 94 | log.error("Param 'build' or 'serve' expected") 95 | 96 | #------------------------------------------------------------------------------- 97 | def help() : 98 | log.info(log.YELLOW + 99 | 'fips webpage build\n' + 100 | 'fips webpage rebuild\n' + 101 | 'fips webpage serve\n' + 102 | log.DEF + 103 | ' build raw webpage') 104 | -------------------------------------------------------------------------------- /fips.cmd: -------------------------------------------------------------------------------- 1 | @python fips %* 2 | 3 | -------------------------------------------------------------------------------- /fips.yml: -------------------------------------------------------------------------------- 1 | # 2 | # raw 3 | # 4 | --- 5 | policies: 6 | no_auto_import: true 7 | imports: 8 | dcimgui: 9 | git: https://github.com/floooh/dcimgui 10 | sokol: 11 | git: https://github.com/floooh/sokol 12 | sokol-tools-bin: 13 | git: https://github.com/floooh/sokol-tools-bin 14 | fips-utils: 15 | git: https://github.com/fips-libs/fips-utils 16 | 17 | -------------------------------------------------------------------------------- /libs/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # select the sokol-gfx backend depending on platform 2 | if(FIPS_EMSCRIPTEN) 3 | add_definitions(-DSOKOL_GLES3) 4 | set(slang "glsl300es") 5 | elseif(FIPS_ANDROID) 6 | add_definitions(-DSOKOL_GLES3) 7 | set(slang "glsl300es") 8 | elseif(FIPS_WINDOWS) 9 | add_definitions(-DSOKOL_D3D11) 10 | set(slang "hlsl4") 11 | elseif(FIPS_OSX) 12 | add_definitions(-DSOKOL_METAL) 13 | if (FIPS_IOS) 14 | set(slang "metal_ios") 15 | else() 16 | set(slang "metal_macos") 17 | endif() 18 | else() 19 | add_definitions(-DSOKOL_GLCORE) 20 | if (FIPS_IOS) 21 | set(slang "glsl300es") 22 | else() 23 | set(slang "glsl410") 24 | endif() 25 | endif() 26 | add_subdirectory(sokol) 27 | -------------------------------------------------------------------------------- /libs/sokol/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | if (FIPS_WINDOWS) 2 | add_definitions(-D_CRT_SECURE_NO_WARNINGS) 3 | endif() 4 | 5 | # the general sokol implementations library (compiled as C, C++ or ObjC depending on platform) 6 | fips_begin_lib(sokol) 7 | fips_files(clock.c clock.h fs.c fs.h gfx.c gfx.h) 8 | sokol_shader(shaders.glsl ${slang}) 9 | if (FIPS_OSX) 10 | fips_files(sokol.m) 11 | fips_frameworks_osx(Foundation) 12 | if (FIPS_IOS) 13 | fips_frameworks_osx(UIKit Metal MetalKit AudioToolbox AVFoundation) 14 | else() 15 | fips_frameworks_osx(Cocoa QuartzCore Metal MetalKit AudioToolbox) 16 | endif() 17 | if (FIPS_MSVC) 18 | target_compile_options(sokol PRIVATE /W4) 19 | endif() 20 | else() 21 | fips_files(sokol.c) 22 | if (FIPS_ANDROID) 23 | fips_libs(GLESv3 EGL OpenSLES android log) 24 | elseif (FIPS_LINUX) 25 | fips_libs(X11 Xcursor Xi GL m dl asound) 26 | endif() 27 | endif() 28 | fips_end_lib() 29 | 30 | # optional UI library (using Dear ImGui) 31 | fips_begin_lib(ui) 32 | fips_files(ui.cc ui.h) 33 | fips_deps(imgui-docking) 34 | fips_end_lib() 35 | 36 | # optional miniz library 37 | fips_begin_lib(miniz) 38 | fips_files(miniz.c miniz.h) 39 | fips_end_lib() 40 | -------------------------------------------------------------------------------- /libs/sokol/clock.c: -------------------------------------------------------------------------------- 1 | #include "sokol_app.h" 2 | #include "clock.h" 3 | #include 4 | 5 | typedef struct { 6 | bool valid; 7 | uint64_t cur_time; 8 | } clock_state_t; 9 | static clock_state_t state; 10 | 11 | void clock_init(void) { 12 | state = (clock_state_t) { 13 | .valid = true, 14 | .cur_time = 0, 15 | }; 16 | } 17 | 18 | uint32_t clock_frame_time(void) { 19 | assert(state.valid); 20 | uint32_t frame_time_us = (uint32_t) (sapp_frame_duration() * 1000000.0); 21 | // prevent death-spiral on host systems that are too slow to emulate 22 | // in real time, or during long frames (e.g. debugging) 23 | if (frame_time_us > 24000) { 24 | frame_time_us = 24000; 25 | } 26 | state.cur_time += frame_time_us; 27 | return frame_time_us; 28 | } 29 | 30 | uint32_t clock_frame_count_60hz(void) { 31 | assert(state.valid); 32 | return (uint32_t) (state.cur_time / 16667); 33 | } 34 | -------------------------------------------------------------------------------- /libs/sokol/clock.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | #ifdef __cplusplus 5 | extern "C" { 6 | #endif 7 | 8 | void clock_init(void); 9 | uint32_t clock_frame_time(void); 10 | uint32_t clock_frame_count_60hz(void); 11 | 12 | #ifdef __cplusplus 13 | } /* extern "C" */ 14 | #endif 15 | -------------------------------------------------------------------------------- /libs/sokol/fs.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | 5 | #if defined(__cplusplus) 6 | extern "C" { 7 | #endif 8 | 9 | // standard loading slots 10 | typedef enum { 11 | FS_CHANNEL_IMAGES = 0, 12 | FS_CHANNEL_SNAPSHOTS, 13 | //--- 14 | FS_CHANNEL_NUM, 15 | } fs_channel_t; 16 | 17 | typedef enum { 18 | FS_RESULT_IDLE, 19 | FS_RESULT_FAILED, 20 | FS_RESULT_PENDING, 21 | FS_RESULT_SUCCESS, 22 | } fs_result_t; 23 | 24 | typedef struct { 25 | size_t snapshot_index; 26 | fs_result_t result; 27 | gfx_range_t data; 28 | } fs_snapshot_response_t; 29 | 30 | typedef void (*fs_snapshot_load_callback_t)(const fs_snapshot_response_t* response); 31 | 32 | void fs_init(void); 33 | void fs_dowork(void); 34 | void fs_reset(fs_channel_t chn); 35 | void fs_load_file_async(fs_channel_t chn, const char* path); 36 | void fs_load_dropped_file_async(fs_channel_t chn); 37 | bool fs_load_base64(fs_channel_t chn, const char* name, const char* payload); 38 | bool fs_save_snapshot(const char* system_name, size_t snapshot_index, gfx_range_t data); 39 | bool fs_load_snapshot_async(const char* system_name, size_t snapshot_index, fs_snapshot_load_callback_t callback); 40 | fs_result_t fs_result(fs_channel_t chn); 41 | bool fs_success(fs_channel_t chn); 42 | bool fs_failed(fs_channel_t chn); 43 | bool fs_pending(fs_channel_t chn); 44 | gfx_range_t fs_data(fs_channel_t chn); 45 | bool fs_ext(fs_channel_t chn, const char* str); 46 | void fs_save_ini(const char* key, const char* payload); 47 | const char* fs_load_ini(const char* key); 48 | void fs_free_ini(const char* payload_or_null); 49 | 50 | #if defined(__cplusplus) 51 | } // extern "C" 52 | #endif 53 | -------------------------------------------------------------------------------- /libs/sokol/gfx.c: -------------------------------------------------------------------------------- 1 | #include "sokol_gfx.h" 2 | #include "sokol_app.h" 3 | #include "sokol_gl.h" 4 | #include "sokol_log.h" 5 | #include "sokol_glue.h" 6 | #include "gfx.h" 7 | #include "shaders.glsl.h" 8 | #include 9 | #include // malloc/free 10 | #include // memset 11 | 12 | #define GFX_DEF(v,def) (v?v:def) 13 | 14 | typedef struct { 15 | bool valid; 16 | gfx_border_t border; 17 | struct { 18 | sg_image img; // framebuffer texture, RGBA8 or R8 if paletted 19 | sg_image pal_img; // optional color palette texture 20 | sg_sampler smp; 21 | gfx_dim_t dim; 22 | } fb; 23 | struct { 24 | gfx_rect_t view; 25 | gfx_dim_t pixel_aspect; 26 | sg_image img; 27 | sg_sampler smp; 28 | sg_buffer vbuf; 29 | sg_pipeline pip; 30 | sg_attachments attachments; 31 | sg_pass_action pass_action; 32 | } offscreen; 33 | struct { 34 | sg_buffer vbuf; 35 | sg_pipeline pip; 36 | sg_pass_action pass_action; 37 | bool portrait; 38 | } display; 39 | int flash_success_count; 40 | int flash_error_count; 41 | sg_image empty_snapshot_texture; 42 | void (*draw_extra_cb)(const gfx_draw_info_t* draw_info); 43 | } gfx_state_t; 44 | static gfx_state_t state; 45 | 46 | static const float gfx_verts[] = { 47 | 0.0f, 0.0f, 0.0f, 0.0f, 48 | 1.0f, 0.0f, 1.0f, 0.0f, 49 | 0.0f, 1.0f, 0.0f, 1.0f, 50 | 1.0f, 1.0f, 1.0f, 1.0f 51 | }; 52 | static const float gfx_verts_rot[] = { 53 | 0.0f, 0.0f, 1.0f, 0.0f, 54 | 1.0f, 0.0f, 1.0f, 1.0f, 55 | 0.0f, 1.0f, 0.0f, 0.0f, 56 | 1.0f, 1.0f, 0.0f, 1.0f 57 | }; 58 | static const float gfx_verts_flipped[] = { 59 | 0.0f, 0.0f, 0.0f, 1.0f, 60 | 1.0f, 0.0f, 1.0f, 1.0f, 61 | 0.0f, 1.0f, 0.0f, 0.0f, 62 | 1.0f, 1.0f, 1.0f, 0.0f 63 | }; 64 | static const float gfx_verts_flipped_rot[] = { 65 | 0.0f, 0.0f, 1.0f, 1.0f, 66 | 1.0f, 0.0f, 1.0f, 0.0f, 67 | 0.0f, 1.0f, 0.0f, 1.0f, 68 | 1.0f, 1.0f, 0.0f, 0.0f 69 | }; 70 | 71 | gfx_dim_t gfx_pixel_aspect(void) { 72 | assert(state.valid); 73 | return state.offscreen.pixel_aspect; 74 | } 75 | 76 | sg_image gfx_create_icon_texture(const uint8_t* packed_pixels, int width, int height, int stride) { 77 | // textures must be 2^n for WebGL 78 | const size_t pixel_data_size = width * height * sizeof(uint32_t); 79 | uint32_t* pixels = malloc(pixel_data_size); 80 | assert(pixels); 81 | memset(pixels, 0, pixel_data_size); 82 | const uint8_t* src = packed_pixels; 83 | uint32_t* dst = pixels; 84 | for (int y = 0; y < height; y++) { 85 | uint8_t bits = 0; 86 | dst = pixels + (y * width); 87 | for (int x = 0; x < width; x++) { 88 | if ((x & 7) == 0) { 89 | bits = *src++; 90 | } 91 | if (bits & 1) { 92 | *dst++ = 0xFFFFFFFF; 93 | } 94 | else { 95 | *dst++ = 0x00FFFFFF; 96 | } 97 | bits >>= 1; 98 | } 99 | } 100 | assert(src == packed_pixels + stride * height); (void)stride; // stride is unused in release mode 101 | assert(dst <= pixels + (width * height)); 102 | sg_image img = sg_make_image(&(sg_image_desc){ 103 | .pixel_format = SG_PIXELFORMAT_RGBA8, 104 | .width = width, 105 | .height = height, 106 | .data.subimage[0][0] = { .ptr=pixels, .size=pixel_data_size } 107 | }); 108 | free(pixels); 109 | return img; 110 | } 111 | 112 | // this function will be called at init time and when the emulator framebuffer size changes 113 | static void gfx_init_images_and_pass(void) { 114 | // destroy previous resources (if exist) 115 | sg_destroy_image(state.fb.img); 116 | sg_destroy_sampler(state.fb.smp); 117 | sg_destroy_image(state.offscreen.img); 118 | sg_destroy_sampler(state.offscreen.smp); 119 | sg_destroy_attachments(state.offscreen.attachments); 120 | 121 | // a texture with the emulator's raw pixel data 122 | assert((state.fb.dim.width > 0) && (state.fb.dim.height > 0)); 123 | state.fb.img = sg_make_image(&(sg_image_desc){ 124 | .width = state.fb.dim.width, 125 | .height = state.fb.dim.height, 126 | .pixel_format = SG_PIXELFORMAT_R8, 127 | .usage = SG_USAGE_STREAM, 128 | }); 129 | 130 | // a sampler for sampling the emulators raw pixel data 131 | state.fb.smp = sg_make_sampler(&(sg_sampler_desc){ 132 | .min_filter = SG_FILTER_NEAREST, 133 | .mag_filter = SG_FILTER_NEAREST, 134 | .wrap_u = SG_WRAP_CLAMP_TO_EDGE, 135 | .wrap_v = SG_WRAP_CLAMP_TO_EDGE 136 | }); 137 | 138 | // 2x-upscaling render target texture, sampler and pass 139 | assert((state.offscreen.view.width > 0) && (state.offscreen.view.height > 0)); 140 | state.offscreen.img = sg_make_image(&(sg_image_desc){ 141 | .render_target = true, 142 | .width = 2 * state.offscreen.view.width, 143 | .height = 2 * state.offscreen.view.height, 144 | .sample_count = 1, 145 | }); 146 | state.offscreen.smp = sg_make_sampler(&(sg_sampler_desc){ 147 | .min_filter = SG_FILTER_LINEAR, 148 | .mag_filter = SG_FILTER_LINEAR, 149 | .wrap_u = SG_WRAP_CLAMP_TO_EDGE, 150 | .wrap_v = SG_WRAP_CLAMP_TO_EDGE, 151 | }); 152 | state.offscreen.attachments = sg_make_attachments(&(sg_attachments_desc){ 153 | .colors[0].image = state.offscreen.img 154 | }); 155 | } 156 | 157 | static const struct { 158 | int width; 159 | int height; 160 | int stride; 161 | uint8_t pixels[32]; 162 | } empty_snapshot_icon = { 163 | .width = 16, 164 | .height = 16, 165 | .stride = 2, 166 | .pixels = { 167 | 0xFF,0xFF, 168 | 0x03,0xC0, 169 | 0x05,0xA0, 170 | 0x09,0x90, 171 | 0x11,0x88, 172 | 0x21,0x84, 173 | 0x41,0x82, 174 | 0x81,0x81, 175 | 0x81,0x81, 176 | 0x41,0x82, 177 | 0x21,0x84, 178 | 0x11,0x88, 179 | 0x09,0x90, 180 | 0x05,0xA0, 181 | 0x03,0xC0, 182 | 0xFF,0xFF, 183 | 184 | } 185 | }; 186 | 187 | void gfx_init(const gfx_desc_t* desc) { 188 | sg_setup(&(sg_desc){ 189 | .buffer_pool_size = 32, 190 | .image_pool_size = 128, 191 | .shader_pool_size = 16, 192 | .pipeline_pool_size = 16, 193 | .attachments_pool_size = 2, 194 | .environment = sglue_environment(), 195 | .logger.func = slog_func, 196 | }); 197 | sgl_setup(&(sgl_desc_t){ 198 | .max_vertices = 16, 199 | .max_commands = 16, 200 | .context_pool_size = 1, 201 | .pipeline_pool_size = 16, 202 | .logger.func = slog_func, 203 | }); 204 | 205 | state.valid = true; 206 | state.display.portrait = desc->display_info.portrait; 207 | state.draw_extra_cb = desc->draw_extra_cb; 208 | state.fb.dim = desc->display_info.frame.dim; 209 | state.offscreen.pixel_aspect.width = GFX_DEF(desc->pixel_aspect.width, 1); 210 | state.offscreen.pixel_aspect.height = GFX_DEF(desc->pixel_aspect.height, 1); 211 | state.offscreen.view = desc->display_info.screen; 212 | 213 | static uint32_t palette_buf[256]; 214 | assert((desc->display_info.palette.size > 0) && (desc->display_info.palette.size <= sizeof(palette_buf))); 215 | memcpy(palette_buf, desc->display_info.palette.ptr, desc->display_info.palette.size); 216 | state.fb.pal_img = sg_make_image(&(sg_image_desc){ 217 | .width = 256, 218 | .height = 1, 219 | .usage = SG_USAGE_STREAM, 220 | .pixel_format = SG_PIXELFORMAT_RGBA8 221 | }); 222 | 223 | state.offscreen.pass_action = (sg_pass_action) { 224 | .colors[0] = { .load_action = SG_LOADACTION_DONTCARE } 225 | }; 226 | state.offscreen.vbuf = sg_make_buffer(&(sg_buffer_desc){ 227 | .data = SG_RANGE(gfx_verts) 228 | }); 229 | 230 | sg_shader shd = sg_make_shader(offscreen_pal_shader_desc(sg_query_backend())); 231 | state.offscreen.pip = sg_make_pipeline(&(sg_pipeline_desc){ 232 | .shader = shd, 233 | .layout = { 234 | .attrs = { 235 | [0].format = SG_VERTEXFORMAT_FLOAT2, 236 | [1].format = SG_VERTEXFORMAT_FLOAT2 237 | } 238 | }, 239 | .primitive_type = SG_PRIMITIVETYPE_TRIANGLE_STRIP, 240 | .depth.pixel_format = SG_PIXELFORMAT_NONE 241 | }); 242 | 243 | state.display.pass_action = (sg_pass_action) { 244 | .colors[0] = { .load_action = SG_LOADACTION_CLEAR, .clear_value = { 0.05f, 0.05f, 0.05f, 1.0f } } 245 | }; 246 | state.display.vbuf = sg_make_buffer(&(sg_buffer_desc){ 247 | .data = { 248 | .ptr = sg_query_features().origin_top_left ? 249 | (state.display.portrait ? gfx_verts_rot : gfx_verts) : 250 | (state.display.portrait ? gfx_verts_flipped_rot : gfx_verts_flipped), 251 | .size = sizeof(gfx_verts) 252 | } 253 | }); 254 | 255 | state.display.pip = sg_make_pipeline(&(sg_pipeline_desc){ 256 | .shader = sg_make_shader(display_shader_desc(sg_query_backend())), 257 | .layout = { 258 | .attrs = { 259 | [0].format = SG_VERTEXFORMAT_FLOAT2, 260 | [1].format = SG_VERTEXFORMAT_FLOAT2 261 | } 262 | }, 263 | .primitive_type = SG_PRIMITIVETYPE_TRIANGLE_STRIP 264 | }); 265 | 266 | // create an icon texture for an empty snapshot 267 | state.empty_snapshot_texture = gfx_create_icon_texture( 268 | empty_snapshot_icon.pixels, 269 | empty_snapshot_icon.width, 270 | empty_snapshot_icon.height, 271 | empty_snapshot_icon.stride); 272 | 273 | // create image and pass resources 274 | gfx_init_images_and_pass(); 275 | } 276 | 277 | /* apply a viewport rectangle to preserve the emulator's aspect ratio, 278 | and for 'portrait' orientations, keep the emulator display at the 279 | top, to make room at the bottom for mobile virtual keyboard 280 | */ 281 | static void apply_viewport(gfx_dim_t canvas, gfx_rect_t view, gfx_dim_t pixel_aspect, gfx_border_t border) { 282 | float cw = (float) (canvas.width - border.left - border.right); 283 | if (cw < 1.0f) { 284 | cw = 1.0f; 285 | } 286 | float ch = (float) (canvas.height - border.top - border.bottom); 287 | if (ch < 1.0f) { 288 | ch = 1.0f; 289 | } 290 | const float canvas_aspect = (float)cw / (float)ch; 291 | const gfx_dim_t aspect = pixel_aspect; 292 | const float emu_aspect = (float)(view.width * aspect.width) / (float)(view.height * aspect.height); 293 | float vp_x, vp_y, vp_w, vp_h; 294 | if (emu_aspect < canvas_aspect) { 295 | vp_y = (float)border.top; 296 | vp_h = ch; 297 | vp_w = (ch * emu_aspect); 298 | vp_x = border.left + (cw - vp_w) / 2; 299 | } 300 | else { 301 | vp_x = (float)border.left; 302 | vp_w = cw; 303 | vp_h = (cw / emu_aspect); 304 | vp_y = (float)border.top; 305 | } 306 | sg_apply_viewportf(vp_x, vp_y, vp_w, vp_h, true); 307 | } 308 | 309 | void gfx_draw(gfx_display_info_t display_info) { 310 | assert(state.valid); 311 | assert((display_info.frame.dim.width > 0) && (display_info.frame.dim.height > 0)); 312 | assert(display_info.frame.buffer.ptr && (display_info.frame.buffer.size > 0)); 313 | assert((display_info.screen.width > 0) && (display_info.screen.height > 0)); 314 | const gfx_dim_t display = { .width = sapp_width(), .height = sapp_height() }; 315 | 316 | state.offscreen.view = display_info.screen; 317 | 318 | // check if emulator framebuffer size has changed, need to create new backing texture 319 | if ((display_info.frame.dim.width != state.fb.dim.width) || (display_info.frame.dim.height != state.fb.dim.height)) { 320 | state.fb.dim = display_info.frame.dim; 321 | gfx_init_images_and_pass(); 322 | } 323 | 324 | // copy emulator pixel data into emulator framebuffer texture 325 | sg_update_image(state.fb.img, &(sg_image_data){ 326 | .subimage[0][0] = { 327 | .ptr = display_info.frame.buffer.ptr, 328 | .size = display_info.frame.buffer.size, 329 | } 330 | }); 331 | 332 | sg_update_image(state.fb.pal_img, &(sg_image_data){ 333 | .subimage[0][0] = { 334 | .ptr = display_info.palette.ptr, 335 | .size = display_info.palette.size, 336 | } 337 | }); 338 | 339 | // upscale the original framebuffer 2x with nearest filtering 340 | sg_begin_pass(&(sg_pass){ 341 | .action = state.offscreen.pass_action, 342 | .attachments = state.offscreen.attachments 343 | }); 344 | sg_apply_pipeline(state.offscreen.pip); 345 | sg_apply_bindings(&(sg_bindings){ 346 | .vertex_buffers[0] = state.offscreen.vbuf, 347 | .images = { 348 | [IMG_fb_tex] = state.fb.img, 349 | [IMG_pal_tex] = state.fb.pal_img, 350 | }, 351 | .samplers[SMP_smp] = state.fb.smp, 352 | }); 353 | const offscreen_vs_params_t vs_params = { 354 | .uv_offset = { 355 | (float)state.offscreen.view.x / (float)state.fb.dim.width, 356 | (float)state.offscreen.view.y / (float)state.fb.dim.height, 357 | }, 358 | .uv_scale = { 359 | (float)state.offscreen.view.width / (float)state.fb.dim.width, 360 | (float)state.offscreen.view.height / (float)state.fb.dim.height 361 | } 362 | }; 363 | sg_apply_uniforms(UB_offscreen_vs_params, &SG_RANGE(vs_params)); 364 | sg_draw(0, 4, 1); 365 | sg_end_pass(); 366 | 367 | // tint the clear color red or green if flash feedback is requested 368 | if (state.flash_error_count > 0) { 369 | state.flash_error_count--; 370 | state.display.pass_action.colors[0].clear_value.r = 0.7f; 371 | } 372 | else if (state.flash_success_count > 0) { 373 | state.flash_success_count--; 374 | state.display.pass_action.colors[0].clear_value.g = 0.7f; 375 | } 376 | else { 377 | state.display.pass_action.colors[0].clear_value.r = 0.05f; 378 | state.display.pass_action.colors[0].clear_value.g = 0.05f; 379 | } 380 | 381 | // draw the final pass with linear filtering 382 | sg_begin_pass(&(sg_pass){ 383 | .action = state.display.pass_action, 384 | .swapchain = sglue_swapchain() 385 | }); 386 | apply_viewport(display, display_info.screen, state.offscreen.pixel_aspect, state.border); 387 | sg_apply_pipeline(state.display.pip); 388 | sg_apply_bindings(&(sg_bindings){ 389 | .vertex_buffers[0] = state.display.vbuf, 390 | .images[IMG_tex] = state.offscreen.img, 391 | .samplers[SMP_smp] = state.offscreen.smp, 392 | }); 393 | sg_draw(0, 4, 1); 394 | sg_apply_viewport(0, 0, display.width, display.height, true); 395 | sgl_draw(); 396 | if (state.draw_extra_cb) { 397 | state.draw_extra_cb(&(gfx_draw_info_t){ 398 | .display_image = state.offscreen.img, 399 | .display_sampler = state.offscreen.smp, 400 | .display_info = display_info, 401 | }); 402 | } 403 | sg_end_pass(); 404 | sg_commit(); 405 | } 406 | 407 | void gfx_shutdown() { 408 | assert(state.valid); 409 | sgl_shutdown(); 410 | sg_shutdown(); 411 | } 412 | 413 | void gfx_flash_success(void) { 414 | assert(state.valid); 415 | state.flash_success_count = 20; 416 | } 417 | 418 | void gfx_flash_error(void) { 419 | assert(state.valid); 420 | state.flash_error_count = 20; 421 | } 422 | -------------------------------------------------------------------------------- /libs/sokol/gfx.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | /* 3 | Common graphics functions for the chips-test example emulators. 4 | 5 | REMINDER: consider using this CRT shader? 6 | 7 | https://github.com/mattiasgustavsson/rebasic/blob/master/source/libs/crtemu.h 8 | */ 9 | #include 10 | #include 11 | #include 12 | #include "sokol_gfx.h" 13 | 14 | #ifdef __cplusplus 15 | extern "C" { 16 | #endif 17 | 18 | typedef struct { 19 | int top, bottom, left, right; 20 | } gfx_border_t; 21 | 22 | typedef struct { 23 | void* ptr; 24 | size_t size; 25 | } gfx_range_t; 26 | 27 | typedef struct { 28 | int width, height; 29 | } gfx_dim_t; 30 | 31 | typedef struct { 32 | int x, y, width, height; 33 | } gfx_rect_t; 34 | 35 | typedef struct { 36 | struct { 37 | gfx_dim_t dim; // framebuffer dimensions in pixels 38 | gfx_range_t buffer; 39 | size_t bytes_per_pixel; // 1 or 4 40 | } frame; 41 | gfx_rect_t screen; 42 | gfx_range_t palette; 43 | bool portrait; 44 | } gfx_display_info_t; 45 | 46 | typedef struct { 47 | sg_image display_image; 48 | sg_sampler display_sampler; 49 | gfx_display_info_t display_info; 50 | } gfx_draw_info_t; 51 | 52 | typedef struct { 53 | gfx_display_info_t display_info; 54 | gfx_dim_t pixel_aspect; // optional pixel aspect ratio, default is 1:1 55 | void (*draw_extra_cb)(const gfx_draw_info_t* draw_info); 56 | } gfx_desc_t; 57 | 58 | void gfx_init(const gfx_desc_t* desc); 59 | void gfx_draw(gfx_display_info_t display_info); 60 | void gfx_shutdown(void); 61 | void gfx_flash_success(void); 62 | void gfx_flash_error(void); 63 | gfx_dim_t gfx_pixel_aspect(void); 64 | sg_image gfx_create_icon_texture(const uint8_t* packed_pixels, int width, int height, int stride); 65 | 66 | #ifdef __cplusplus 67 | } /* extern "C" */ 68 | #endif 69 | -------------------------------------------------------------------------------- /libs/sokol/shaders.glsl: -------------------------------------------------------------------------------- 1 | @vs offscreen_vs 2 | in vec2 in_pos; 3 | in vec2 in_uv; 4 | 5 | layout(binding=0) uniform offscreen_vs_params { 6 | vec2 uv_offset; 7 | vec2 uv_scale; 8 | }; 9 | 10 | out vec2 uv; 11 | void main() { 12 | gl_Position = vec4(in_pos*2.0-1.0, 0.5, 1.0); 13 | uv = (in_uv * uv_scale) + uv_offset; 14 | } 15 | @end 16 | 17 | @fs offscreen_fs 18 | layout(binding=0) uniform texture2D fb_tex; 19 | layout(binding=0) uniform sampler smp; 20 | in vec2 uv; 21 | out vec4 frag_color; 22 | void main() { 23 | frag_color = texture(sampler2D(fb_tex, smp), uv); 24 | } 25 | @end 26 | 27 | // offscreen shader with color palette decoding 28 | @fs offscreen_pal_fs 29 | layout(binding=0) uniform texture2D fb_tex; 30 | layout(binding=1) uniform texture2D pal_tex; 31 | layout(binding=0) uniform sampler smp; 32 | in vec2 uv; 33 | out vec4 frag_color; 34 | void main() { 35 | float pix = texture(sampler2D(fb_tex, smp), uv).x; 36 | frag_color = vec4(texture(sampler2D(pal_tex, smp), vec2(pix,0)).xyz, 1.0); 37 | } 38 | @end 39 | 40 | @vs display_vs 41 | layout(location=0) in vec2 in_pos; 42 | layout(location=1) in vec2 in_uv; 43 | out vec2 uv; 44 | void main() { 45 | gl_Position = vec4(in_pos*2.0-1.0, 0.5, 1.0); 46 | uv = in_uv; 47 | } 48 | @end 49 | 50 | @fs display_fs 51 | layout(binding=0) uniform texture2D tex; 52 | layout(binding=0) uniform sampler smp; 53 | in vec2 uv; 54 | out vec4 frag_color; 55 | 56 | void main() { 57 | frag_color = vec4(texture(sampler2D(tex, smp), uv).xyz, 1.0); 58 | } 59 | @end 60 | 61 | @program offscreen offscreen_vs offscreen_fs 62 | @program offscreen_pal offscreen_vs offscreen_pal_fs 63 | @program display display_vs display_fs 64 | -------------------------------------------------------------------------------- /libs/sokol/sokol.c: -------------------------------------------------------------------------------- 1 | /* sokol implementations need to live in it's own source file, because 2 | on MacOS and iOS the implementation must be compiled as Objective-C, so there 3 | must be a *.m file on MacOS/iOS, and *.c file everywhere else 4 | */ 5 | #define SOKOL_IMPL 6 | /* sokol 3D-API defines are provided by build options */ 7 | #include "sokol_app.h" 8 | #include "sokol_gfx.h" 9 | #include "sokol_time.h" 10 | #include "sokol_audio.h" 11 | #include "sokol_args.h" 12 | #include "sokol_gl.h" 13 | #include "sokol_fetch.h" 14 | #include "sokol_debugtext.h" 15 | #include "sokol_log.h" 16 | #include "sokol_glue.h" 17 | -------------------------------------------------------------------------------- /libs/sokol/sokol.m: -------------------------------------------------------------------------------- 1 | /* sokol implementations need to live in it's own source file, because 2 | on MacOS and iOS the implementation must be compiled as Objective-C, so there 3 | must be a *.m file on MacOS/iOS, and *.c file everywhere else 4 | */ 5 | #define SOKOL_IMPL 6 | /* sokol 3D-API defines are provided by build options */ 7 | #include "sokol_app.h" 8 | #include "sokol_gfx.h" 9 | #include "sokol_time.h" 10 | #include "sokol_audio.h" 11 | #include "sokol_args.h" 12 | #include "sokol_gl.h" 13 | #include "sokol_fetch.h" 14 | #include "sokol_debugtext.h" 15 | #include "sokol_log.h" 16 | #include "sokol_glue.h" 17 | -------------------------------------------------------------------------------- /libs/sokol/ui.cc: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // ui.cc 3 | //------------------------------------------------------------------------------ 4 | #include "ui/ui_util.h" 5 | #include "ui.h" 6 | #include "sokol_gfx.h" 7 | #include "sokol_app.h" 8 | #include "sokol_time.h" 9 | #include "imgui.h" 10 | #include "imgui_internal.h" // ImGui::SettingsHandler 11 | #define SOKOL_IMGUI_IMPL 12 | #include "sokol_imgui.h" 13 | #include "gfx.h" 14 | #include "fs.h" 15 | #include // snprintf 16 | 17 | #define UI_DELETE_STACK_SIZE (32) 18 | 19 | static struct { 20 | ui_draw_t draw_cb; 21 | ui_save_settings_t save_settings_cb; 22 | sg_sampler nearest_sampler; 23 | sg_sampler linear_sampler; 24 | sg_image empty_snapshot_texture; 25 | struct { 26 | sg_image images[UI_DELETE_STACK_SIZE]; 27 | size_t cur_slot; 28 | } delete_stack; 29 | char imgui_ini_key[128]; 30 | ui_settings_t settings; 31 | } state; 32 | 33 | 34 | static const struct { 35 | int width; 36 | int height; 37 | int stride; 38 | uint8_t pixels[32]; 39 | } empty_snapshot_icon = { 40 | .width = 16, 41 | .height = 16, 42 | .stride = 2, 43 | .pixels = { 44 | 0xFF,0xFF, 45 | 0x03,0xC0, 46 | 0x05,0xA0, 47 | 0x09,0x90, 48 | 0x11,0x88, 49 | 0x21,0x84, 50 | 0x41,0x82, 51 | 0x81,0x81, 52 | 0x81,0x81, 53 | 0x41,0x82, 54 | 0x21,0x84, 55 | 0x11,0x88, 56 | 0x09,0x90, 57 | 0x05,0xA0, 58 | 0x03,0xC0, 59 | 0xFF,0xFF, 60 | } 61 | }; 62 | 63 | static void commit_listener(void* user_data); 64 | static void load_imgui_ini(void); 65 | static void handle_save_imgui_ini(void); 66 | static void register_imgui_settings_handler(void); 67 | 68 | void ui_init(const ui_desc_t* desc) { 69 | assert(desc && desc->draw_cb && desc->imgui_ini_key); 70 | 71 | state.save_settings_cb = desc->save_settings_cb; 72 | snprintf(state.imgui_ini_key, sizeof(state.imgui_ini_key), "%s", desc->imgui_ini_key); 73 | 74 | simgui_desc_t simgui_desc = { }; 75 | simgui_setup(&simgui_desc); 76 | register_imgui_settings_handler(); 77 | ImGui::GetIO().ConfigFlags |= ImGuiConfigFlags_DockingEnable; 78 | auto& style = ImGui::GetStyle(); 79 | style.WindowRounding = 0.0f; 80 | style.WindowBorderSize = 1.0f; 81 | style.Alpha = 1.0f; 82 | state.draw_cb = desc->draw_cb; 83 | load_imgui_ini(); 84 | 85 | sg_add_commit_listener({ .func = commit_listener }); 86 | 87 | state.nearest_sampler = sg_make_sampler({ 88 | .min_filter = SG_FILTER_NEAREST, 89 | .mag_filter = SG_FILTER_NEAREST, 90 | .wrap_u = SG_WRAP_CLAMP_TO_EDGE, 91 | .wrap_v = SG_WRAP_CLAMP_TO_EDGE, 92 | }); 93 | 94 | state.linear_sampler = sg_make_sampler({ 95 | .min_filter = SG_FILTER_LINEAR, 96 | .mag_filter = SG_FILTER_LINEAR, 97 | .wrap_u = SG_WRAP_CLAMP_TO_EDGE, 98 | .wrap_v = SG_WRAP_CLAMP_TO_EDGE, 99 | }); 100 | 101 | state.empty_snapshot_texture = gfx_create_icon_texture( 102 | empty_snapshot_icon.pixels, 103 | empty_snapshot_icon.width, 104 | empty_snapshot_icon.height, 105 | empty_snapshot_icon.stride); 106 | } 107 | 108 | const ui_settings_t* ui_settings(void) { 109 | return &state.settings; 110 | } 111 | 112 | void ui_discard(void) { 113 | sg_destroy_sampler(state.nearest_sampler); 114 | sg_destroy_sampler(state.linear_sampler); 115 | sg_remove_commit_listener({ .func = commit_listener }); 116 | simgui_shutdown(); 117 | } 118 | 119 | void ui_draw(const gfx_draw_info_t* gfx_draw_info) { 120 | handle_save_imgui_ini(); 121 | simgui_new_frame({sapp_width(), sapp_height(), sapp_frame_duration(), sapp_dpi_scale() }); 122 | const ImGuiViewport* viewport = ImGui::GetMainViewport(); 123 | const ImGuiID dockspace = ImGui::GetID("main_dockspace"); 124 | ImGui::DockSpaceOverViewport(dockspace, viewport, ImGuiDockNodeFlags_PassthruCentralNode); 125 | if (state.draw_cb) { 126 | ui_draw_info_t ui_draw_info = {}; 127 | if (gfx_draw_info) { 128 | ui_draw_info.display.tex = simgui_imtextureid_with_sampler(gfx_draw_info->display_image, gfx_draw_info->display_sampler); 129 | ui_draw_info.display.dim = gfx_draw_info->display_info.frame.dim; 130 | ui_draw_info.display.screen = gfx_draw_info->display_info.screen; 131 | ui_draw_info.display.pixel_aspect = gfx_pixel_aspect(); 132 | ui_draw_info.display.portrait = gfx_draw_info->display_info.portrait; 133 | ui_draw_info.display.origin_top_left = sg_query_features().origin_top_left; 134 | } 135 | state.draw_cb(&ui_draw_info); 136 | } 137 | simgui_render(); 138 | } 139 | 140 | bool ui_input(const sapp_event* event) { 141 | simgui_handle_event(event); 142 | return ImGui::GetIO().WantCaptureKeyboard; 143 | } 144 | 145 | ui_texture_t ui_create_texture(int w, int h) { 146 | return simgui_imtextureid_with_sampler( 147 | sg_make_image({ 148 | .width = w, 149 | .height = h, 150 | .usage = SG_USAGE_STREAM, 151 | .pixel_format = SG_PIXELFORMAT_RGBA8, 152 | }), 153 | state.nearest_sampler); 154 | } 155 | 156 | void ui_update_texture(ui_texture_t h, void* data, int data_byte_size) { 157 | sg_image img = simgui_image_from_imtextureid(h); 158 | sg_image_data img_data = { }; 159 | img_data.subimage[0][0] = { .ptr = data, .size = (size_t) data_byte_size }; 160 | sg_update_image(img, img_data); 161 | } 162 | 163 | void ui_destroy_texture(ui_texture_t h) { 164 | if (state.delete_stack.cur_slot < UI_DELETE_STACK_SIZE) { 165 | state.delete_stack.images[state.delete_stack.cur_slot++] = simgui_image_from_imtextureid(h); 166 | } 167 | } 168 | 169 | // creates a 2x downscaled screenshot texture of the emulator framebuffer 170 | ui_texture_t ui_create_screenshot_texture(gfx_display_info_t info) { 171 | assert(info.frame.buffer.ptr); 172 | 173 | size_t dst_w = (info.screen.width + 1) >> 1; 174 | size_t dst_h = (info.screen.height + 1) >> 1; 175 | size_t dst_num_bytes = (size_t)(dst_w * dst_h * 4); 176 | uint32_t* dst = (uint32_t*) calloc(1, dst_num_bytes); 177 | 178 | if (info.palette.ptr) { 179 | assert(info.frame.bytes_per_pixel == 1); 180 | const uint8_t* pixels = (uint8_t*) info.frame.buffer.ptr; 181 | const uint32_t* palette = (uint32_t*) info.palette.ptr; 182 | const size_t num_palette_entries = info.palette.size / sizeof(uint32_t); 183 | for (size_t y = 0; y < (size_t)info.screen.height; y++) { 184 | for (size_t x = 0; x < (size_t)info.screen.width; x++) { 185 | uint8_t p = pixels[(y + info.screen.y) * info.frame.dim.width + (x + info.screen.x)]; 186 | assert(p < num_palette_entries); (void)num_palette_entries; 187 | uint32_t c = (palette[p] >> 2) & 0x3F3F3F3F; 188 | size_t dst_x = x >> 1; 189 | size_t dst_y = y >> 1; 190 | if (info.portrait) { 191 | dst[dst_x * dst_h + (dst_h - dst_y - 1)] += c; 192 | } 193 | else { 194 | dst[dst_y * dst_w + dst_x] += c; 195 | } 196 | } 197 | } 198 | } 199 | else { 200 | assert(info.frame.bytes_per_pixel == 4); 201 | const uint32_t* pixels = (uint32_t*) info.frame.buffer.ptr; 202 | for (size_t y = 0; y < (size_t)info.screen.height; y++) { 203 | for (size_t x = 0; x < (size_t)info.screen.width; x++) { 204 | uint32_t c = pixels[(y + info.screen.y) * info.frame.dim.width + (x + info.screen.x)]; 205 | c = (c >> 2) & 0x3F3F3F3F; 206 | size_t dst_x = x >> 1; 207 | size_t dst_y = y >> 1; 208 | if (info.portrait) { 209 | dst[dst_x * dst_h + (dst_h - dst_y - 1)] += c; 210 | } 211 | else { 212 | dst[dst_y * dst_w + dst_x] += c; 213 | } 214 | } 215 | } 216 | } 217 | 218 | sg_image_desc img_desc = { 219 | .width = (int) (info.portrait ? dst_h : dst_w), 220 | .height = (int) (info.portrait ? dst_w : dst_h), 221 | .pixel_format = SG_PIXELFORMAT_RGBA8, 222 | }; 223 | img_desc.data.subimage[0][0] = { .ptr = dst, .size = dst_num_bytes }; 224 | sg_image img = sg_make_image(img_desc); 225 | free(dst); 226 | return simgui_imtextureid_with_sampler(img, state.linear_sampler); 227 | } 228 | 229 | ui_texture_t ui_shared_empty_snapshot_texture(void) { 230 | return simgui_imtextureid_with_sampler(state.empty_snapshot_texture, state.nearest_sampler); 231 | } 232 | 233 | static void commit_listener(void* user_data) { 234 | (void)user_data; 235 | // garbage collect images 236 | for (size_t i = 0; i < state.delete_stack.cur_slot; i++) { 237 | sg_destroy_image(state.delete_stack.images[i]); 238 | } 239 | state.delete_stack.cur_slot = 0; 240 | } 241 | 242 | static void handle_save_imgui_ini(void) { 243 | if (ImGui::GetIO().WantSaveIniSettings) { 244 | ImGui::GetIO().WantSaveIniSettings = false; 245 | fs_save_ini(state.imgui_ini_key, ImGui::SaveIniSettingsToMemory()); 246 | } 247 | } 248 | 249 | static void load_imgui_ini(void) { 250 | const char* payload = fs_load_ini(state.imgui_ini_key); 251 | if (payload) { 252 | ImGui::LoadIniSettingsFromMemory(payload); 253 | fs_free_ini(payload); 254 | } 255 | } 256 | 257 | // ImGui Settings handler implementation 258 | 259 | // clear all settings data 260 | static void imgui_ClearAllFn(ImGuiContext* ctx, ImGuiSettingsHandler* handler) { 261 | (void)ctx; (void)handler; 262 | ui_settings_init(&state.settings); 263 | } 264 | 265 | // read: Called before reading (in registration order) 266 | static void imgui_ReadInitFn(ImGuiContext* ctx, ImGuiSettingsHandler* handler) { 267 | (void)ctx; (void)handler; 268 | ui_settings_init(&state.settings); 269 | } 270 | 271 | // read: Called when entering into a new ini entry e.g. "[Window][Name]" 272 | static void* imgui_ReadOpenFn(ImGuiContext* ctx, ImGuiSettingsHandler* handler, const char* name) { 273 | (void)ctx; (void)handler; 274 | ui_settings_add(&state.settings, name, false); 275 | // NOTE: cannot return nullptr since this means 'no valid ini entry' 276 | return (void*)&state.settings; 277 | } 278 | 279 | // read: Called for every line of text within an ini entry 280 | static void imgui_ReadLineFn(ImGuiContext* ctx, ImGuiSettingsHandler* handler, void* entry, const char* line) { 281 | (void)ctx; (void)handler; (void)entry; 282 | assert(state.settings.num_slots > 0); 283 | int cur_slot_idx = state.settings.num_slots - 1; 284 | int is_open = 0; 285 | if (sscanf(line, "IsOpen=%i", &is_open) == 1) { 286 | state.settings.slots[cur_slot_idx].open = true; 287 | } 288 | } 289 | 290 | // read: Called after reading (in registration order) 291 | static void imgui_ApplyAllFn(ImGuiContext* ctx, ImGuiSettingsHandler* handler) { 292 | (void)ctx; (void)handler; 293 | } 294 | 295 | // write: output all entries into 'out_buf' 296 | static void imgui_WriteAllFn(ImGuiContext* ctx, ImGuiSettingsHandler* handler, ImGuiTextBuffer* buf) { 297 | (void)ctx; (void)handler; 298 | if (state.save_settings_cb) { 299 | ui_settings_t settings; 300 | ui_settings_init(&settings); 301 | state.save_settings_cb(&settings); 302 | buf->reserve(buf->size() + settings.num_slots * 64); 303 | for (int i = 0; i < settings.num_slots; i++) { 304 | const ui_settings_slot_t* slot = &settings.slots[i]; 305 | buf->appendf("[%s][%s]\n", handler->TypeName, slot->window_title.buf); 306 | if (slot->open) { 307 | buf->append("IsOpen=1\n"); 308 | } 309 | } 310 | buf->append("\n"); 311 | } 312 | } 313 | 314 | static void register_imgui_settings_handler(void) { 315 | const char* type_name = "scemino.rawgl"; 316 | ImGuiSettingsHandler ini_handler; 317 | ini_handler.TypeName = type_name;; 318 | ini_handler.TypeHash = ImHashStr(type_name); 319 | ini_handler.ClearAllFn = imgui_ClearAllFn; 320 | ini_handler.ReadInitFn = imgui_ReadInitFn; 321 | ini_handler.ReadOpenFn = imgui_ReadOpenFn; 322 | ini_handler.ReadLineFn = imgui_ReadLineFn; 323 | ini_handler.ApplyAllFn = imgui_ApplyAllFn; 324 | ini_handler.WriteAllFn = imgui_WriteAllFn; 325 | ImGui::AddSettingsHandler(&ini_handler); 326 | } -------------------------------------------------------------------------------- /libs/sokol/ui.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "sokol_app.h" 3 | #include "sokol_gfx.h" 4 | #include "sokol_imgui.h" 5 | #include "gfx.h" 6 | #include "ui_settings.h" 7 | #include "ui_display.h" 8 | 9 | #ifdef __cplusplus 10 | extern "C" { 11 | #endif 12 | 13 | typedef struct { 14 | ui_display_frame_t display; 15 | } ui_draw_info_t; 16 | 17 | typedef void(*ui_draw_t)(const ui_draw_info_t* draw_info); 18 | typedef void (*ui_save_settings_t)(ui_settings_t* settings); 19 | 20 | typedef struct { 21 | ui_draw_t draw_cb; 22 | ui_save_settings_t save_settings_cb; 23 | const char* imgui_ini_key; 24 | } ui_desc_t; 25 | 26 | void ui_init(const ui_desc_t* desc); 27 | const ui_settings_t* ui_settings(void); 28 | void ui_discard(void); 29 | void ui_draw(const gfx_draw_info_t* draw_info); 30 | bool ui_input(const sapp_event* event); 31 | ui_texture_t ui_create_texture(int w, int h); 32 | void ui_update_texture(ui_texture_t h, void* data, int data_byte_size); 33 | void ui_destroy_texture(ui_texture_t h); 34 | ui_texture_t ui_create_screenshot_texture(gfx_display_info_t display_info); 35 | ui_texture_t ui_shared_empty_snapshot_texture(void); 36 | 37 | #ifdef __cplusplus 38 | } /* extern "C" */ 39 | #endif 40 | -------------------------------------------------------------------------------- /libs/sokol/ui_display.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | /*# 3 | # ui_display.h 4 | 5 | Display the emulator framebuffer in a window. 6 | 7 | Do this: 8 | ~~~C 9 | #define GAME_UI_IMPL 10 | ~~~ 11 | before you include this file in *one* C++ file to create the 12 | implementation. 13 | 14 | Optionally provide the following macros with your own implementation 15 | 16 | ~~~C 17 | GAME_ASSERT(c) 18 | ~~~ 19 | your own assert macro (default: assert(c)) 20 | 21 | Include the following headers before including the *implementation*: 22 | - imgui.h 23 | - ui_util.h 24 | - ui_settings.h 25 | 26 | ## zlib/libpng license 27 | 28 | Copyright (c) 2024 Andre Weissflog 29 | This software is provided 'as-is', without any express or implied warranty. 30 | In no event will the authors be held liable for any damages arising from the 31 | use of this software. 32 | Permission is granted to anyone to use this software for any purpose, 33 | including commercial applications, and to alter it and redistribute it 34 | freely, subject to the following restrictions: 35 | 1. The origin of this software must not be misrepresented; you must not 36 | claim that you wrote the original software. If you use this software in a 37 | product, an acknowledgment in the product documentation would be 38 | appreciated but is not required. 39 | 2. Altered source versions must be plainly marked as such, and must not 40 | be misrepresented as being the original software. 41 | 3. This notice may not be removed or altered from any source 42 | distribution. 43 | #*/ 44 | #include 45 | #include 46 | 47 | #ifdef __cplusplus 48 | extern "C" { 49 | #endif 50 | 51 | typedef struct { 52 | const char* title; // window title 53 | int x, y; // initial window position 54 | int w, h; // initial window size of 0 for default size 55 | bool open; // initial open state 56 | } ui_display_desc_t; 57 | 58 | typedef struct { 59 | ui_texture_t tex; 60 | gfx_dim_t dim; // framebuffer width/height 61 | gfx_rect_t screen; // visible area 62 | gfx_dim_t pixel_aspect; // pixel aspect 63 | bool portrait; 64 | bool origin_top_left; 65 | } ui_display_frame_t; 66 | 67 | typedef struct { 68 | const char* title; 69 | float init_x, init_y; 70 | float init_w, init_h; 71 | bool open; 72 | bool last_open; 73 | bool valid; 74 | } ui_display_t; 75 | 76 | void ui_display_init(ui_display_t* win, const ui_display_desc_t* desc); 77 | void ui_display_discard(ui_display_t* win); 78 | void ui_display_draw(ui_display_t* win, const ui_display_frame_t* frame); 79 | void ui_display_save_settings(ui_display_t* win, ui_settings_t* settings); 80 | void ui_display_load_settings(ui_display_t* win, const ui_settings_t* settings); 81 | 82 | #ifdef __cplusplus 83 | } // extern "C" 84 | #endif 85 | 86 | /*-- IMPLEMENTATION (include in C++ source) ----------------------------------*/ 87 | #ifdef GAME_UI_IMPL 88 | #ifndef __cplusplus 89 | #error "implementation must be compiled as C++" 90 | #endif 91 | #include /* memset */ 92 | #ifndef GAME_ASSERT 93 | #include 94 | #define GAME_ASSERT(c) assert(c) 95 | #endif 96 | 97 | typedef struct { 98 | ImVec2 v[4]; 99 | } ui_display_quad_t; 100 | 101 | void ui_display_init(ui_display_t* win, const ui_display_desc_t* desc) { 102 | GAME_ASSERT(win && desc); 103 | GAME_ASSERT(desc->title); 104 | memset(win, 0, sizeof(ui_display_t)); 105 | win->title = desc->title; 106 | win->init_x = (float) desc->x; 107 | win->init_y = (float) desc->y; 108 | win->init_w = (float) ((desc->w == 0) ? 320 + 20 : desc->w); 109 | win->init_h = (float) ((desc->h == 0) ? 256 + 20 : desc->h); 110 | win->open = win->last_open = desc->open; 111 | win->valid = true; 112 | } 113 | 114 | void ui_display_discard(ui_display_t* win) { 115 | GAME_ASSERT(win && win->valid); 116 | win->valid = false; 117 | } 118 | 119 | static ui_display_quad_t ui_display_uv_quad(bool origin_top_left, bool portrait) { 120 | ui_display_quad_t res = {}; 121 | res.v[0] = { 0, 0 }; 122 | res.v[1] = { 1, 0 }; 123 | res.v[2] = { 1, 1 }; 124 | res.v[3] = { 0, 1 }; 125 | if (origin_top_left) { 126 | res.v[0].y = res.v[1].y = 1; 127 | res.v[2].y = res.v[3].y = 0; 128 | } 129 | if (portrait) { 130 | ImVec2 v3 = res.v[3]; 131 | res.v[3] = res.v[2]; 132 | res.v[2] = res.v[1]; 133 | res.v[1] = res.v[0]; 134 | res.v[0] = v3; 135 | } 136 | return res; 137 | } 138 | 139 | static ui_display_quad_t ui_display_pos_quad(ImVec2 dim, ImVec2 aspect) { 140 | const ImVec2 pos = ImGui::GetCursorScreenPos(); 141 | const ImVec2 region = ImGui::GetContentRegionAvail(); 142 | float cw = region.x; 143 | if (cw < 1.0f) { 144 | cw = 1.0f; 145 | } 146 | float ch = region.y; 147 | if (ch < 1.0f) { 148 | ch = 1.0f; 149 | } 150 | const float canvas_aspect = cw / ch; 151 | const float view_aspect = (dim.x * aspect.x) / (dim.y * aspect.y); 152 | float vp_x, vp_y, vp_w, vp_h; 153 | if (view_aspect < canvas_aspect) { 154 | vp_y = pos.y; 155 | vp_h = ch; 156 | vp_w = ch * view_aspect; 157 | vp_x = pos.x + (cw - vp_w) * 0.5f; 158 | } else { 159 | vp_x = pos.x; 160 | vp_w = cw; 161 | vp_h = cw / view_aspect; 162 | vp_y = pos.y + (ch - vp_h) * 0.5f; 163 | } 164 | const float x0 = vp_x; 165 | const float y0 = vp_y; 166 | const float x1 = vp_x + vp_w; 167 | const float y1 = vp_y + vp_h; 168 | ui_display_quad_t res = {}; 169 | res.v[0] = { x0, y0 }; 170 | res.v[1] = { x1, y0 }; 171 | res.v[2] = { x1, y1 }; 172 | res.v[3] = { x0, y1 }; 173 | return res; 174 | } 175 | 176 | void ui_display_draw(ui_display_t* win, const ui_display_frame_t* frame) { 177 | GAME_ASSERT(win && frame && win->valid && win->title); 178 | ui_util_handle_window_open_dirty(&win->open, &win->last_open); 179 | if (!win->open) { 180 | return; 181 | } 182 | const ImVec2 dim = { (float)frame->screen.width, (float)frame->screen.height }; 183 | const ImVec2 pixel_aspect = { (float)frame->pixel_aspect.width, (float)frame->pixel_aspect.height }; 184 | ImGui::SetNextWindowPos({win->init_x, win->init_y}, ImGuiCond_FirstUseEver); 185 | ImGui::SetNextWindowSize({win->init_w, win->init_h}, ImGuiCond_FirstUseEver); 186 | if (ImGui::Begin(win->title, &win->open, ImGuiWindowFlags_HorizontalScrollbar|ImGuiWindowFlags_NoNav)) { 187 | // need to render the image via ImDrawList because we need to specify 4 uv coords 188 | ImDrawList* dl = ImGui::GetWindowDrawList(); 189 | const ui_display_quad_t p = ui_display_pos_quad(dim, pixel_aspect); 190 | const ui_display_quad_t uv = ui_display_uv_quad(frame->origin_top_left, frame->portrait); 191 | dl->AddImageQuad(frame->tex, p.v[0], p.v[1], p.v[2], p.v[3], uv.v[0], uv.v[1], uv.v[2], uv.v[3], 0xFFFFFFFF); 192 | } 193 | ImGui::End(); 194 | } 195 | 196 | void ui_display_save_settings(ui_display_t* win, ui_settings_t* settings) { 197 | GAME_ASSERT(win && settings); 198 | ui_settings_add(settings, win->title, win->open); 199 | } 200 | 201 | void ui_display_load_settings(ui_display_t* win, const ui_settings_t* settings) { 202 | GAME_ASSERT(win && settings); 203 | win->open = ui_settings_isopen(settings, win->title); 204 | } 205 | 206 | #endif // GAME_UI_IMPL 207 | -------------------------------------------------------------------------------- /libs/sokol/ui_settings.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | /*# 3 | # ui_settings.h 4 | 5 | Persistent settings helper. 6 | 7 | Do this: 8 | ~~~C 9 | #define GAME_UI_IMPL 10 | ~~~ 11 | before you include this file in *one* C++ file to create the 12 | implementation. 13 | 14 | Optionally provide the following macros with your own implementation 15 | 16 | ~~~C 17 | CHIPS_ASSERT(c) 18 | ~~~ 19 | your own assert macro (default: assert(c)) 20 | 21 | You need to include the following headers before including the 22 | *implementation*: 23 | 24 | - imgui.h 25 | 26 | ## zlib/libpng license 27 | 28 | Copyright (c) 2024 Andre Weissflog 29 | This software is provided 'as-is', without any express or implied warranty. 30 | In no event will the authors be held liable for any damages arising from the 31 | use of this software. 32 | Permission is granted to anyone to use this software for any purpose, 33 | including commercial applications, and to alter it and redistribute it 34 | freely, subject to the following restrictions: 35 | 1. The origin of this software must not be misrepresented; you must not 36 | claim that you wrote the original software. If you use this software in a 37 | product, an acknowledgment in the product documentation would be 38 | appreciated but is not required. 39 | 2. Altered source versions must be plainly marked as such, and must not 40 | be misrepresented as being the original software. 41 | 3. This notice may not be removed or altered from any source 42 | distribution. 43 | #*/ 44 | #include 45 | #include 46 | #include 47 | 48 | #ifdef __cplusplus 49 | extern "C" { 50 | #endif 51 | 52 | #define UI_SETTINGS_MAX_SLOTS (32) 53 | #define UI_SETTINGS_MAX_STRING_LENGTH (128) 54 | 55 | typedef struct { 56 | char buf[UI_SETTINGS_MAX_STRING_LENGTH]; 57 | } ui_settings_str_t; 58 | 59 | typedef struct { 60 | ui_settings_str_t window_title; 61 | bool open; 62 | } ui_settings_slot_t; 63 | 64 | typedef struct { 65 | int num_slots; 66 | ui_settings_slot_t slots[UI_SETTINGS_MAX_SLOTS]; 67 | } ui_settings_t; 68 | 69 | // initialize settings instance 70 | void ui_settings_init(ui_settings_t* state); 71 | // add a settings item 72 | bool ui_settings_add(ui_settings_t* state, const char* window_title, bool open); 73 | // find slot index, return -1 if not found 74 | int ui_settings_find_slot_index(ui_settings_t* state, const char* window_title); 75 | // check window open settings flag 76 | bool ui_settings_isopen(const ui_settings_t* state, const char* window_title); 77 | 78 | #ifdef __cplusplus 79 | } // extern "C" 80 | #endif 81 | 82 | //-- IMPLEMENTATION ------------------------------------------------------------ 83 | #ifdef GAME_UI_IMPL 84 | #include // snprintf 85 | #ifndef CHIPS_ASSERT 86 | #include 87 | #define CHIPS_ASSERT(c) assert(c) 88 | #endif 89 | 90 | void ui_settings_init(ui_settings_t* state) { 91 | CHIPS_ASSERT(state); 92 | memset(state, 0, sizeof(*state)); 93 | } 94 | 95 | static ui_settings_str_t ui_settings_make_str(const char* str) { 96 | ui_settings_str_t res = {0}; 97 | snprintf(res.buf, sizeof(res.buf), "%s", str); 98 | return res; 99 | } 100 | 101 | bool ui_settings_add(ui_settings_t* state, const char* window_title, bool open) { 102 | CHIPS_ASSERT(state); 103 | CHIPS_ASSERT(window_title); 104 | if (state->num_slots >= UI_SETTINGS_MAX_SLOTS) { 105 | return false; 106 | } 107 | if (strlen(window_title) >= (UI_SETTINGS_MAX_STRING_LENGTH - 1)) { 108 | return false; 109 | } 110 | ui_settings_slot_t* slot = &state->slots[state->num_slots++]; 111 | slot->window_title = ui_settings_make_str(window_title); 112 | slot->open = open; 113 | return true; 114 | } 115 | 116 | int ui_settings_find_slot_index(const ui_settings_t* state, const char* window_title) { 117 | CHIPS_ASSERT(state && window_title); 118 | for (int i = 0; i < state->num_slots; i++) { 119 | const ui_settings_slot_t* slot = &state->slots[i]; 120 | CHIPS_ASSERT(slot->window_title.buf); 121 | if (strcmp(window_title, slot->window_title.buf) == 0) { 122 | return i; 123 | } 124 | } 125 | return -1; 126 | } 127 | 128 | bool ui_settings_isopen(const ui_settings_t* state, const char* window_title) { 129 | CHIPS_ASSERT(state && window_title); 130 | int slot_index = ui_settings_find_slot_index(state, window_title); 131 | if (slot_index != -1) { 132 | return state->slots[slot_index].open; 133 | } 134 | return false; 135 | } 136 | #endif // GAME_UI_IMPL 137 | -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | fips_begin_app(raw windowed) 2 | fips_files(raw.c game.h) 3 | fips_deps(sokol miniz) 4 | fips_end_app() 5 | 6 | fips_begin_app(raw-ui windowed) 7 | fips_files(raw.c game.h raw-ui-impl.cc) 8 | fips_deps(sokol miniz) 9 | fips_deps(ui) 10 | fips_end_app() 11 | target_compile_definitions(raw-ui PRIVATE GAME_USE_UI) -------------------------------------------------------------------------------- /src/raw-ui-impl.cc: -------------------------------------------------------------------------------- 1 | /* 2 | UI implementation for raw.c, this must live in a .cc file. 3 | */ 4 | #include 5 | #include 6 | #include 7 | #include "gfx.h" 8 | #include "game.h" 9 | #include "sokol_time.h" 10 | #define GAME_UI_IMPL 11 | #include "imgui.h" 12 | #include "imgui_internal.h" 13 | #include "ui/ui_util.h" 14 | #include "ui/raw_dasm.h" 15 | #include "ui_settings.h" 16 | #include "ui/ui_dbg.h" 17 | #include "ui/ui_dasm.h" 18 | #include "ui/ui_snapshot.h" 19 | #include "ui_display.h" 20 | #include "ui/ui_game.h" 21 | -------------------------------------------------------------------------------- /src/raw.c: -------------------------------------------------------------------------------- 1 | /* 2 | RAW.c 3 | 4 | Another world rewrite with sokol. 5 | */ 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include "sokol_app.h" 11 | #include "sokol_args.h" 12 | #include "sokol_audio.h" 13 | #include "sokol_gfx.h" 14 | #include "sokol_glue.h" 15 | #include "sokol_log.h" 16 | #include "clock.h" 17 | #include "gfx.h" 18 | #define GAME_IMPL 19 | #include "game.h" 20 | #include "miniz.h" 21 | #include "fs.h" 22 | #if defined(GAME_USE_UI) 23 | #include "ui/ui_util.h" 24 | #include "ui.h" 25 | #include "ui/raw_dasm.h" 26 | #include "ui/ui_dasm.h" 27 | #include "ui/ui_dbg.h" 28 | #include "ui/ui_snapshot.h" 29 | #include "ui_display.h" 30 | #include "ui/ui_game.h" 31 | #endif 32 | 33 | typedef struct { 34 | uint32_t version; 35 | game_t game; 36 | } game_snapshot_t; 37 | 38 | typedef struct { 39 | int part_num; 40 | bool use_ega; 41 | game_lang_t lang; 42 | bool enable_protection; 43 | } game_options_t; 44 | 45 | static struct { 46 | bool ready; 47 | game_data_t data; 48 | game_options_t options; 49 | game_t game; 50 | uint32_t frame_time_us; 51 | #ifdef GAME_USE_UI 52 | ui_game_t ui; 53 | game_snapshot_t snapshots[UI_SNAPSHOT_MAX_SLOTS]; 54 | #endif 55 | } state = {0}; 56 | 57 | #ifdef GAME_USE_UI 58 | static void ui_draw_cb(const ui_draw_info_t* draw_info); 59 | #endif 60 | 61 | // audio-streaming callback 62 | static void push_audio(const float* samples, int num_samples, void* user_data) { 63 | (void)user_data; 64 | saudio_push(samples, num_samples/2); 65 | } 66 | 67 | #if defined(GAME_USE_UI) 68 | static void ui_draw_cb(const ui_draw_info_t* draw_info) { 69 | ui_game_draw(&state.ui, &(ui_game_frame_t){ 70 | .display = draw_info->display, 71 | }); 72 | } 73 | 74 | static void ui_save_settings_cb(ui_settings_t* settings) { 75 | ui_game_save_settings(&state.ui, settings); 76 | } 77 | 78 | static void ui_update_snapshot_screenshot(size_t slot) { 79 | ui_snapshot_screenshot_t screenshot = { 80 | .texture = ui_create_screenshot_texture(game_display_info(&state.snapshots[slot].game)) 81 | }; 82 | ui_snapshot_screenshot_t prev_screenshot = ui_snapshot_set_screenshot(&state.ui.snapshot, slot, screenshot); 83 | if (prev_screenshot.texture) { 84 | ui_destroy_texture(prev_screenshot.texture); 85 | } 86 | } 87 | 88 | static bool ui_load_snapshot(size_t slot) { 89 | bool success = false; 90 | if ((slot < UI_SNAPSHOT_MAX_SLOTS) && (state.ui.snapshot.slots[slot].valid)) { 91 | success = game_load_snapshot(&state.game, state.snapshots[slot].version, &state.snapshots[slot].game); 92 | } 93 | return success; 94 | } 95 | 96 | static void ui_save_snapshot(size_t slot) { 97 | if (slot < UI_SNAPSHOT_MAX_SLOTS) { 98 | state.snapshots[slot].version = game_save_snapshot(&state.game, &state.snapshots[slot].game); 99 | ui_update_snapshot_screenshot(slot); 100 | fs_save_snapshot("raw", slot, (gfx_range_t){ .ptr = &state.snapshots[slot], sizeof(gfx_range_t) }); 101 | } 102 | } 103 | #endif 104 | 105 | static int _to_num(char c) { 106 | if(c > '0' && c <= '9') { 107 | return c - '0'; 108 | } else if(c >= 'a') { 109 | return 10 + c - 'a'; 110 | } else if(c >= 'A') { 111 | return 10 + c - 'A'; 112 | } 113 | return 0; 114 | } 115 | 116 | static void app_init(void) { 117 | clock_init(); 118 | fs_init(); 119 | int part = GAME_PART_INTRO; 120 | game_lang_t lang = GAME_LANG_US; 121 | if (sargs_exists("part")) { 122 | part = atoi(sargs_value("part")); 123 | } 124 | if (sargs_exists("lang")) { 125 | lang = strcmp(sargs_value("lang"), "fr") == 0 ? GAME_LANG_FR : GAME_LANG_US; 126 | } 127 | state.options = (game_options_t){ 128 | .part_num = part, 129 | .lang = lang, 130 | .use_ega = sargs_exists("use_ega"), 131 | .enable_protection = sargs_exists("protec"), 132 | }; 133 | 134 | game_init(&state.game, &(game_desc_t){ 135 | .part_num = state.options.part_num, 136 | .use_ega = state.options.use_ega, 137 | .enable_protection = state.options.enable_protection, 138 | .lang = state.options.lang, 139 | .audio = { 140 | .callback = { .func = push_audio }, 141 | }, 142 | #if defined(GAME_USE_UI) 143 | .debug = ui_game_get_debug(&state.ui) 144 | #endif 145 | }); 146 | gfx_init(&(gfx_desc_t){ 147 | .display_info = game_display_info(&state.game), 148 | #ifdef GAME_USE_UI 149 | .draw_extra_cb = ui_draw, 150 | #endif 151 | }); 152 | saudio_setup(&(saudio_desc){ 153 | .num_channels = 2, 154 | .logger.func = slog_func, 155 | }); 156 | #ifdef GAME_USE_UI 157 | ui_init(&(ui_desc_t){ 158 | .draw_cb = ui_draw_cb, 159 | .save_settings_cb = ui_save_settings_cb, 160 | .imgui_ini_key = "scemino.rawgl", 161 | }); 162 | ui_game_init(&state.ui, &(ui_game_desc_t){ 163 | .game = &state.game, 164 | .dbg_texture = { 165 | .create_cb = ui_create_texture, 166 | .update_cb = ui_update_texture, 167 | .destroy_cb = ui_destroy_texture, 168 | }, 169 | .snapshot = { 170 | .load_cb = ui_load_snapshot, 171 | .save_cb = ui_save_snapshot, 172 | .empty_slot_screenshot = { 173 | .texture = ui_shared_empty_snapshot_texture(), 174 | } 175 | }, 176 | .dbg_keys = { 177 | .cont = { .keycode = simgui_map_keycode(SAPP_KEYCODE_F5), .name = "F5" }, 178 | .stop = { .keycode = simgui_map_keycode(SAPP_KEYCODE_F5), .name = "F5" }, 179 | .step_over = { .keycode = simgui_map_keycode(SAPP_KEYCODE_F6), .name = "F6" }, 180 | .step_into = { .keycode = simgui_map_keycode(SAPP_KEYCODE_F7), .name = "F7" }, 181 | .toggle_breakpoint = { .keycode = simgui_map_keycode(SAPP_KEYCODE_F9), .name = "F9" } 182 | } 183 | }); 184 | ui_game_load_settings(&state.ui, ui_settings()); 185 | #endif 186 | 187 | if (sargs_exists("file")) { 188 | fs_load_file_async(FS_CHANNEL_IMAGES, sargs_value("file")); 189 | } 190 | } 191 | 192 | static void _game_start(void) { 193 | game_init(&state.game, &(game_desc_t){ 194 | .part_num = state.options.part_num, 195 | .use_ega = state.options.use_ega, 196 | .enable_protection = state.options.enable_protection, 197 | .lang = state.options.lang, 198 | .audio = { 199 | .callback = { .func = push_audio }, 200 | }, 201 | #if defined(GAME_USE_UI) 202 | .debug = ui_game_get_debug(&state.ui) 203 | #endif 204 | }); 205 | game_start(&state.game, state.data); 206 | sapp_set_window_title(state.game.title); 207 | } 208 | 209 | int _game_strnicmp(const char* a, const char* b, size_t i) 210 | { 211 | for (;; a++, b++, i--) { 212 | int d = tolower(*a) - tolower(*b); 213 | if(!i) 214 | return 0; 215 | if (d != 0 || !*a) 216 | return d; 217 | } 218 | return 0; 219 | } 220 | 221 | bool _game_load_data(gfx_range_t data) { 222 | memset(&state.data, 0, sizeof(state.data)); 223 | mz_zip_archive archive; 224 | mz_zip_zero_struct(&archive); 225 | mz_zip_reader_init_mem(&archive, data.ptr, data.size, 0); 226 | mz_uint num = mz_zip_reader_get_num_files(&archive); 227 | mz_zip_archive_file_stat stat; 228 | bool result = false; 229 | for(mz_uint i=0; i load_delay_frames) { 256 | 257 | bool load_success = false; 258 | if (fs_ext(FS_CHANNEL_IMAGES, "zip")) { 259 | load_success = _game_load_data(fs_data(FS_CHANNEL_IMAGES)); 260 | } 261 | if (load_success) { 262 | state.ready = true; 263 | _game_start(); 264 | if (clock_frame_count_60hz() > (load_delay_frames + 10)) { 265 | gfx_flash_success(); 266 | } 267 | } 268 | else { 269 | gfx_flash_error(); 270 | } 271 | fs_reset(FS_CHANNEL_IMAGES); 272 | } 273 | } 274 | 275 | static void app_frame(void) { 276 | state.frame_time_us = clock_frame_time(); 277 | gfx_draw(game_display_info(&state.game)); 278 | if(state.ready) { 279 | game_exec(&state.game, state.frame_time_us/1000); 280 | } 281 | handle_file_loading(); 282 | } 283 | 284 | static void app_cleanup(void) { 285 | game_cleanup(&state.game); 286 | #ifdef GAME_USE_UI 287 | ui_game_discard(&state.ui); 288 | ui_discard(); 289 | #endif 290 | saudio_shutdown(); 291 | gfx_shutdown(); 292 | sargs_shutdown(); 293 | } 294 | 295 | void app_input(const sapp_event* event) { 296 | // accept dropped files also when ImGui grabs input 297 | if (event->type == SAPP_EVENTTYPE_FILES_DROPPED) { 298 | fs_load_dropped_file_async(FS_CHANNEL_IMAGES); 299 | } 300 | #ifdef GAME_USE_UI 301 | if (ui_input(event)) { 302 | // input was handled by UI 303 | return; 304 | } 305 | #endif 306 | if(state.ready) { 307 | switch (event->type) { 308 | case SAPP_EVENTTYPE_CHAR: { 309 | int c = (int)event->char_code; 310 | if ((c > 0x20) && (c < 0x7F)) { 311 | game_char_pressed(&state.game, c); 312 | } 313 | } 314 | break; 315 | case SAPP_EVENTTYPE_KEY_DOWN: 316 | case SAPP_EVENTTYPE_KEY_UP: { 317 | game_input_t c; 318 | switch (event->key_code) { 319 | case SAPP_KEYCODE_LEFT: c = GAME_INPUT_LEFT; break; 320 | case SAPP_KEYCODE_RIGHT: c = GAME_INPUT_RIGHT; break; 321 | case SAPP_KEYCODE_DOWN: c = GAME_INPUT_DOWN; break; 322 | case SAPP_KEYCODE_UP: c = GAME_INPUT_UP; break; 323 | case SAPP_KEYCODE_ENTER: c = GAME_INPUT_ACTION; break; 324 | case SAPP_KEYCODE_SPACE: c = GAME_INPUT_ACTION; break; 325 | case SAPP_KEYCODE_ESCAPE: c = GAME_INPUT_BACK; break; 326 | case SAPP_KEYCODE_F: c = GAME_INPUT_BACK; break; 327 | case SAPP_KEYCODE_C: c = GAME_INPUT_CODE; break; 328 | case SAPP_KEYCODE_P: c = GAME_INPUT_PAUSE; break; 329 | default: c = -1; break; 330 | } 331 | if ((int)c != -1) { 332 | if (event->type == SAPP_EVENTTYPE_KEY_DOWN) { 333 | game_key_down(&state.game, c); 334 | } 335 | else { 336 | game_key_up(&state.game, c); 337 | } 338 | } 339 | break; 340 | default: 341 | break; 342 | } 343 | } 344 | } 345 | } 346 | 347 | sapp_desc sokol_main(int argc, char* argv[]) { 348 | sargs_setup(&(sargs_desc){ .argc=argc, .argv=argv }); 349 | return (sapp_desc) { 350 | .init_cb = app_init, 351 | .event_cb = app_input, 352 | .frame_cb = app_frame, 353 | .cleanup_cb = app_cleanup, 354 | .width = 800, 355 | .height = 600, 356 | .window_title = "RAW", 357 | .icon.sokol_default = true, 358 | .enable_dragndrop = true, 359 | .logger.func = slog_func, 360 | }; 361 | } 362 | -------------------------------------------------------------------------------- /src/ui/raw_dasm.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | /*# 3 | # raw-dasm.h 4 | 5 | ## zlib/libpng license 6 | 7 | Copyright (c) 2023 Scemino 8 | This software is provided 'as-is', without any express or implied warranty. 9 | In no event will the authors be held liable for any damages arising from the 10 | use of this software. 11 | Permission is granted to anyone to use this software for any purpose, 12 | including commercial applications, and to alter it and redistribute it 13 | freely, subject to the following restrictions: 14 | 1. The origin of this software must not be misrepresented; you must not 15 | claim that you wrote the original software. If you use this software in a 16 | product, an acknowledgment in the product documentation would be 17 | appreciated but is not required. 18 | 2. Altered source versions must be plainly marked as such, and must not 19 | be misrepresented as being the original software. 20 | 3. This notice may not be removed or altered from any source 21 | distribution. 22 | #*/ 23 | #include 24 | #include 25 | 26 | #ifdef __cplusplus 27 | extern "C" { 28 | #endif 29 | 30 | /* the input callback type */ 31 | typedef uint8_t (*raw_dasm_input_t)(void* user_data); 32 | /* the output callback type */ 33 | typedef void (*raw_dasm_output_t)(char c, void* user_data); 34 | /* the get string callback type */ 35 | typedef const char* (*raw_getstrt_t)(uint16_t id, void* user_data); 36 | 37 | /* disassemble a single Another World instruction into a stream of ASCII characters */ 38 | uint16_t raw_dasm_op(uint16_t pc, raw_dasm_input_t in_cb, raw_dasm_output_t out_cb, raw_getstrt_t get_str_cb, void* user_data); 39 | 40 | #ifdef __cplusplus 41 | } /* extern "C" */ 42 | #endif 43 | 44 | /*-- IMPLEMENTATION ----------------------------------------------------------*/ 45 | #ifdef GAME_UI_IMPL 46 | #ifndef GAME_ASSERT 47 | #include 48 | #define GAME_ASSERT(c) assert(c) 49 | #endif 50 | 51 | /* fetch unsigned 8-bit value and track pc */ 52 | #ifdef _FETCH_U8 53 | #undef _FETCH_U8 54 | #endif 55 | #define _FETCH_U8(v) v=in_cb(user_data);pc++; 56 | /* fetch signed 8-bit value and track pc */ 57 | #ifdef _FETCH_I8 58 | #undef _FETCH_I8 59 | #endif 60 | #define _FETCH_I8(v) v=(int8_t)in_cb(user_data);pc++; 61 | /* fetch unsigned 16-bit value and track pc */ 62 | #ifdef _FETCH_U16 63 | #undef _FETCH_U16 64 | #endif 65 | #define _FETCH_U16(v) v=(in_cb(user_data))<<8;v|=in_cb(user_data);pc+=2; 66 | /* fetch usigned 16-bit value and track pc */ 67 | #ifdef _FETCH_I16 68 | #undef _FETCH_I16 69 | #endif 70 | #define _FETCH_I16(v) v=(int16_t)((in_cb(user_data)<<8)|(in_cb(user_data)));pc+=2; 71 | /* output character */ 72 | #ifdef _CHR 73 | #undef _CHR 74 | #endif 75 | #define _CHR(c) if (out_cb) { out_cb(c,user_data); } 76 | /* output string */ 77 | #ifdef _STR 78 | #undef _STR 79 | #endif 80 | #define _STR(s) _raw_dasm_str(s,out_cb,user_data); 81 | /* output number as unsigned 8-bit string (hex) */ 82 | #ifdef _STR_U8 83 | #undef _STR_U8 84 | #endif 85 | #define _STR_U8(u8) _raw_dasm_u8((uint8_t)(u8),out_cb,user_data); 86 | /* output number number as unsigned 16-bit string (hex) */ 87 | #ifdef _STR_U16 88 | #undef _STR_U16 89 | #endif 90 | #define _STR_U16(u16) _raw_dasm_u16((uint16_t)(u16),out_cb,user_data); 91 | 92 | static const char* _raw_dasm_hex = "0123456789ABCDEF"; 93 | 94 | /* helper function to output string */ 95 | static void _raw_dasm_str(const char* str, raw_dasm_output_t out_cb, void* user_data) { 96 | if (out_cb) { 97 | char c; 98 | while (0 != (c = *str++)) { 99 | out_cb(c, user_data); 100 | } 101 | } 102 | } 103 | 104 | /* helper function to output an unsigned 8-bit value as decimal string */ 105 | static void _raw_dasm_u8(uint8_t val, raw_dasm_output_t out_cb, void* user_data) { 106 | if (out_cb) { 107 | if (val == 0) { 108 | out_cb(_raw_dasm_hex[0], user_data); 109 | } else { 110 | uint8_t div = 100; 111 | bool b = false; 112 | for(int i=0; i<3; i++) { 113 | uint8_t v = (val / div) % 10; 114 | if(b || (v > 0)) { 115 | b = true; 116 | out_cb(_raw_dasm_hex[v & 0xF], user_data); 117 | } 118 | div /= 10; 119 | } 120 | } 121 | } 122 | } 123 | 124 | /* helper function to output an unsigned 16-bit value as hex string */ 125 | static void _raw_dasm_u16(uint16_t val, raw_dasm_output_t out_cb, void* user_data) { 126 | if (out_cb) { 127 | out_cb('$', user_data); 128 | for (int i = 3; i >= 0; i--) { 129 | out_cb(_raw_dasm_hex[(val>>(i*4)) & 0xF], user_data); 130 | } 131 | } 132 | } 133 | 134 | /* main disassembler function */ 135 | uint16_t raw_dasm_op(uint16_t pc, raw_dasm_input_t in_cb, raw_dasm_output_t out_cb, raw_getstrt_t get_str_cb, void* user_data) { 136 | GAME_ASSERT(in_cb); 137 | uint8_t op; 138 | uint8_t u8; uint16_t u16; int16_t i16; 139 | _FETCH_U8(op); 140 | 141 | /* opcode name */ 142 | switch (op) { 143 | case 0x00: _FETCH_U8(u8); _STR("set v"); _STR_U8(u8); _CHR(' '); _FETCH_I16(i16); _STR_U16(i16); break; // mov const 144 | case 0x01: _FETCH_U8(u8); _STR("seti v"); _STR_U8(u8); _STR(" v"); _FETCH_U8(u8); _STR_U8(u8); break; // mov 145 | case 0x02: _FETCH_U8(u8); _STR("addi "); _STR_U8(u8); _STR(" v"); _FETCH_U8(u8); _STR_U8(u8); break; // add 146 | case 0x03: _FETCH_U8(u8); _STR("addi v"); _STR_U8(u8); _CHR(' '); _FETCH_I16(i16); _STR_U16(i16); break; // add const 147 | case 0x04: _FETCH_U16(u16); _STR("jsr "); _STR_U16(u16); break; // call 148 | case 0x05: _STR("return"); break; // ret 149 | case 0x06: _STR("break"); break; // yield task 150 | case 0x07: _FETCH_U16(u16); _STR("jmp "); _STR_U16(u16); break; // jmp 151 | case 0x08: _FETCH_U8(u8); _STR("setvec "); _STR_U8(u8); _CHR(' '); _FETCH_U16(u16); _STR_U16(u16); break; // install task 152 | case 0x09: _FETCH_U8(u8); _STR("dbra v"); _STR_U8(u8); _CHR(' '); _FETCH_U16(u16); _STR_U16(u16); break; // jmpIfVar 153 | case 0x0a: { 154 | uint8_t op, v; 155 | _FETCH_U8(op); _FETCH_U8(v); 156 | _STR("if (v"); _STR_U8(v); 157 | switch (op & 7) { 158 | case 0: _STR(" == "); break; 159 | case 1: _STR(" != "); break; 160 | case 2: _STR(" > "); break; 161 | case 3: _STR(" >= "); break; 162 | case 4: _STR(" < "); break; 163 | case 5: _STR(" <= "); break; 164 | default: _STR("???"); break; 165 | } 166 | if (op & 0x80) { 167 | uint8_t a; _FETCH_U8(a); 168 | _STR("v"); _STR_U8(a); 169 | } else if (op & 0x40) { 170 | _FETCH_I16(i16); _STR_U16(i16); 171 | } else { 172 | uint8_t a; 173 | _FETCH_U8(a); _STR_U8(a); 174 | } 175 | _STR(") jmp "); 176 | _FETCH_I16(i16); _STR_U16(i16); 177 | } break; 178 | case 0x0b: _FETCH_U16(u16); _STR("fade "); _STR_U8(u16>>8); break; // setPalette 179 | case 0x0c: _FETCH_U8(u8); _STR("vec "); _STR_U8(u8); _CHR(','); _FETCH_U8(u8); _STR_U8(u8); _CHR(','); _FETCH_U8(u8); _STR_U8(u8); break; // changeTasksState 180 | case 0x0d: _FETCH_U8(u8); _STR("setws "); _STR_U8(u8); break; // selectPage 181 | case 0x0e: _FETCH_U8(u8); _STR("clr "); _STR_U8(u8); _FETCH_U8(u8); _CHR(' '); _STR_U8(u8); break; // fillPage 182 | case 0x0f: _FETCH_U8(u8); _STR("copy "); _STR_U8(u8); _FETCH_U8(u8); _CHR(' '); _STR_U8(u8); break; // copyPage 183 | case 0x10: _FETCH_U8(u8); _STR("show "); _STR_U8(u8); break; // updateDisplay 184 | case 0x11: _STR("bigend"); break; // removeTask 185 | case 0x12: _FETCH_U16(u16); _STR("text "); _CHR('\"'); _STR(get_str_cb(u16, user_data)); _STR("\" "); _STR_U16(u16); _CHR(' '); _FETCH_U8(u8); _STR_U8(u8); _CHR(' '); _FETCH_U8(u8); _STR_U8(u8); _CHR(' '); _FETCH_U8(u8); _STR_U8(u8); break; // text "text number", x, y, color 186 | case 0x13: _FETCH_U8(u8); _STR("v"); _STR_U8(u8); _STR(" -= "); _FETCH_U8(u8); _STR("v"); _STR_U8(u8); break; // sub 187 | case 0x14: _FETCH_U8(u8); _STR("v"); _STR_U8(u8); _STR(" &= "); _FETCH_U16(u16); _STR_U16(u16); break; // and 188 | case 0x15: _FETCH_U8(u8); _STR("v"); _STR_U8(u8); _STR(" |= "); _FETCH_U16(u16); _STR_U16(u16); break; // or 189 | case 0x16: _FETCH_U8(u8); _STR("v"); _STR_U8(u8); _STR(" <<= "); _FETCH_U16(u16); _STR_U16(u16); break; // shl 190 | case 0x17: _FETCH_U8(u8); _STR("v"); _STR_U8(u8); _STR(" >>= "); _FETCH_U16(u16); _STR_U16(u16); break; // shr 191 | case 0x18: _FETCH_U16(u16); _STR("play "); _STR_U16(u16); _CHR(' '); _FETCH_U8(u8); _STR_U8(u8); _CHR(' '); _FETCH_U8(u8); _STR_U8(u8); _CHR(' '); _FETCH_U8(u8); _STR_U8(u8); break; // playSound 192 | case 0x19: _FETCH_U16(u16); _STR("load "); _STR_U16(u16); break; // updateResource 193 | case 0x1a: _FETCH_U16(u16); _STR("song "); _STR_U16(u16); _CHR(' '); _FETCH_U16(u16); _STR_U16(u16); _CHR(' '); _FETCH_U8(u8); _STR_U8(u8); break; // playMusic 194 | default: 195 | if(op & 0x80) { 196 | uint8_t x, y; 197 | _FETCH_U8(u8); _FETCH_U8(x); _FETCH_U8(y); 198 | const uint16_t off = ((op << 8) | u8) << 1; 199 | _STR("spr "); _STR_U16(off); _CHR(' '); _STR_U8(x); _STR(" "); _STR_U8(y); _CHR(' '); 200 | } else if(op & 0x40) { 201 | uint8_t offset_hi; 202 | _FETCH_U8(offset_hi); _FETCH_U8(u8); 203 | const uint16_t off = ((offset_hi << 8) | u8) << 1; 204 | _STR("spr "); 205 | _STR_U16(off); 206 | _CHR(' '); 207 | if (!(op & 0x20)) { 208 | if (!(op & 0x10)) { 209 | int16_t x; 210 | _FETCH_I16(x); 211 | _STR_U16(x); 212 | } else { 213 | _FETCH_U8(u8); 214 | _STR("v"); _STR_U8(u8); 215 | } 216 | } else { 217 | _FETCH_U8(u8); 218 | int16_t x = u8; 219 | if (op & 0x10) { 220 | x += 0x100; 221 | } 222 | _STR_U16(x); 223 | } 224 | _STR(" "); 225 | _FETCH_U8(u8); 226 | int16_t y = u8; 227 | if (!(op & 8)) { 228 | if (!(op & 4)) { 229 | _FETCH_U8(u8); 230 | y = (y << 8) | u8; 231 | _STR_U16(y); 232 | } else { 233 | _STR("v"); _STR_U8(u8); 234 | } 235 | } else { 236 | _STR_U16(y); 237 | } 238 | _STR(" "); 239 | if (!(op & 2)) { 240 | if (op & 1) { 241 | _FETCH_U8(u8); 242 | _STR("v"); _STR_U8(u8); 243 | } else { 244 | _STR("64"); 245 | } 246 | } else { 247 | if (!(op & 1)) { 248 | _FETCH_U8(u8); 249 | _STR_U8(u8); 250 | } else { 251 | _STR("64"); 252 | } 253 | } 254 | } else { 255 | _STR("???"); 256 | } 257 | } 258 | return pc; 259 | } 260 | 261 | #undef _FETCH_I8 262 | #undef _FETCH_U8 263 | #undef _FETCH_I16 264 | #undef _FETCH_U16 265 | #undef _CHR 266 | #undef _STR 267 | #undef _STR_U8 268 | #undef _STR_U16 269 | #undef A____ 270 | #undef A_IMM 271 | #undef A_ZER 272 | #undef A_ZPX 273 | #undef A_ZPY 274 | #undef A_ABS 275 | #undef A_ABX 276 | #undef A_ABY 277 | #undef A_IDX 278 | #undef A_IDY 279 | #undef A_JMP 280 | #undef A_JSR 281 | #undef A_INV 282 | #undef M___ 283 | #undef M_R_ 284 | #undef M__W 285 | #undef M_RW 286 | #endif /* GAME_UTIL_IMPL */ 287 | -------------------------------------------------------------------------------- /src/ui/ui_dasm.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | /*# 3 | # ui_dasm.h 4 | 5 | Disassembler UI using Dear ImGui. 6 | 7 | Do this: 8 | ~~~C 9 | #define GAME_UI_IMPL 10 | ~~~ 11 | before you include this file in *one* C++ file to create the 12 | implementation. 13 | 14 | Optionally provide the following macros with your own implementation 15 | 16 | ~~~C 17 | GAME_ASSERT(c) 18 | ~~~ 19 | your own assert macro (default: assert(c)) 20 | 21 | You need to include the following headers before including the 22 | *implementation*: 23 | 24 | - imgui.h 25 | - ui_util.h 26 | 27 | All strings provided to ui_dasm_init() must remain alive until 28 | ui_dasm_discard() is called! 29 | 30 | ## zlib/libpng license 31 | 32 | Copyright (c) 2018 Andre Weissflog 33 | This software is provided 'as-is', without any express or implied warranty. 34 | In no event will the authors be held liable for any damages arising from the 35 | use of this software. 36 | Permission is granted to anyone to use this software for any purpose, 37 | including commercial applications, and to alter it and redistribute it 38 | freely, subject to the following restrictions: 39 | 1. The origin of this software must not be misrepresented; you must not 40 | claim that you wrote the original software. If you use this software in a 41 | product, an acknowledgment in the product documentation would be 42 | appreciated but is not required. 43 | 2. Altered source versions must be plainly marked as such, and must not 44 | be misrepresented as being the original software. 45 | 3. This notice may not be removed or altered from any source 46 | distribution. 47 | #*/ 48 | #include 49 | #include 50 | 51 | #ifdef __cplusplus 52 | extern "C" { 53 | #endif 54 | 55 | /* callback for reading a byte from memory */ 56 | typedef uint8_t (*ui_dasm_read_t)(int layer, uint16_t addr, bool* valid, void* user_data); 57 | typedef const char* (*ui_dasm_getstr_t)(uint16_t id, void* user_data); 58 | 59 | #define UI_DASM_MAX_LAYERS (16) 60 | #define UI_DASM_MAX_STRLEN (32*4) 61 | #define UI_DASM_MAX_BINLEN (16) 62 | #define UI_DASM_NUM_LINES (4096) 63 | #define UI_DASM_MAX_STACK (128) 64 | 65 | /* setup parameters for ui_dasm_init() 66 | 67 | NOTE: all strings must be static! 68 | */ 69 | typedef struct { 70 | const char* title; /* window title */ 71 | const char* layers[UI_DASM_MAX_LAYERS]; /* memory system layer names */ 72 | uint16_t start_addr; 73 | ui_dasm_read_t read_cb; 74 | ui_dasm_getstr_t getstr_cb; 75 | void* user_data; 76 | int x, y; /* initial window pos */ 77 | int w, h; /* initial window size or 0 for default size */ 78 | bool open; /* initial open state */ 79 | } ui_dasm_desc_t; 80 | 81 | typedef struct { 82 | const char* title; 83 | ui_dasm_read_t read_cb; 84 | ui_dasm_getstr_t getstr_cb; 85 | int cur_layer; 86 | int num_layers; 87 | const char* layers[UI_DASM_MAX_LAYERS]; 88 | void* user_data; 89 | float init_x, init_y; 90 | float init_w, init_h; 91 | bool open; 92 | bool valid; 93 | uint16_t start_addr; 94 | uint16_t cur_addr; 95 | int str_pos; 96 | char str_buf[UI_DASM_MAX_STRLEN]; 97 | int bin_pos; 98 | uint8_t bin_buf[UI_DASM_MAX_BINLEN]; 99 | int stack_num; 100 | int stack_pos; 101 | uint16_t stack[UI_DASM_MAX_STACK]; 102 | uint16_t highlight_addr; 103 | uint32_t highlight_color; 104 | } ui_dasm_t; 105 | 106 | void ui_dasm_init(ui_dasm_t* win, const ui_dasm_desc_t* desc); 107 | void ui_dasm_discard(ui_dasm_t* win); 108 | void ui_dasm_draw(ui_dasm_t* win); 109 | void ui_dasm_save_settings(ui_dasm_t* win, ui_settings_t* settings); 110 | void ui_dasm_load_settings(ui_dasm_t* ui, const ui_settings_t* settings); 111 | 112 | #ifdef __cplusplus 113 | } /* extern "C" */ 114 | #endif 115 | 116 | /*-- IMPLEMENTATION (include in C++ source) ----------------------------------*/ 117 | #ifdef GAME_UI_IMPL 118 | #ifndef __cplusplus 119 | #error "implementation must be compiled as C++" 120 | #endif 121 | #include /* memset */ 122 | #include /* sscanf, sprintf (ImGui memory editor) */ 123 | #ifndef GAME_ASSERT 124 | #include 125 | #define GAME_ASSERT(c) assert(c) 126 | #endif 127 | 128 | void ui_dasm_init(ui_dasm_t* win, const ui_dasm_desc_t* desc) { 129 | GAME_ASSERT(win && desc); 130 | GAME_ASSERT(desc->title); 131 | memset(win, 0, sizeof(ui_dasm_t)); 132 | win->title = desc->title; 133 | win->read_cb = desc->read_cb; 134 | win->getstr_cb = desc->getstr_cb; 135 | win->start_addr = desc->start_addr; 136 | win->user_data = desc->user_data; 137 | win->init_x = (float) desc->x; 138 | win->init_y = (float) desc->y; 139 | win->init_w = (float) ((desc->w == 0) ? 400 : desc->w); 140 | win->init_h = (float) ((desc->h == 0) ? 256 : desc->h); 141 | win->open = desc->open; 142 | win->highlight_color = 0xFF30FF30; 143 | for (int i = 0; i < UI_DASM_MAX_LAYERS; i++) { 144 | if (desc->layers[i]) { 145 | win->num_layers++; 146 | win->layers[i] = desc->layers[i]; 147 | } 148 | else { 149 | break; 150 | } 151 | } 152 | win->valid = true; 153 | } 154 | 155 | void ui_dasm_discard(ui_dasm_t* win) { 156 | GAME_ASSERT(win && win->valid); 157 | win->valid = false; 158 | } 159 | 160 | /* disassembler callback to fetch the next instruction byte */ 161 | static uint8_t _ui_dasm_in_cb(void* user_data) { 162 | ui_dasm_t* win = (ui_dasm_t*) user_data; 163 | bool valid; 164 | uint8_t val = win->read_cb(win->cur_layer, win->cur_addr++, &valid, win->user_data); 165 | if (!valid) return 0; 166 | if (win->bin_pos < UI_DASM_MAX_BINLEN) { 167 | win->bin_buf[win->bin_pos++] = val; 168 | } 169 | return val; 170 | } 171 | 172 | /* disassembler callback to output a character */ 173 | static void _ui_dasm_out_cb(char c, void* user_data) { 174 | ui_dasm_t* win = (ui_dasm_t*) user_data; 175 | if ((win->str_pos+1) < UI_DASM_MAX_STRLEN) { 176 | win->str_buf[win->str_pos++] = c; 177 | win->str_buf[win->str_pos] = 0; 178 | } 179 | } 180 | 181 | static const char* _ui_dasm_getstr_cb(uint16_t id, void* user_data) { 182 | ui_dasm_t* win = (ui_dasm_t*) user_data; 183 | return win->getstr_cb(id, win->user_data); 184 | } 185 | 186 | /* disassemble the next instruction */ 187 | static void _ui_dasm_disasm(ui_dasm_t* win) { 188 | win->str_pos = 0; 189 | win->bin_pos = 0; 190 | raw_dasm_op(win->cur_addr, _ui_dasm_in_cb, _ui_dasm_out_cb, _ui_dasm_getstr_cb, win); 191 | } 192 | 193 | static bool _ui_dasm_jumptarget(ui_dasm_t* win, uint16_t pc, uint16_t* out_addr) { 194 | (void)pc; 195 | switch (win->bin_buf[0]) { 196 | case 0x04: // jsr 197 | case 0x07: // jmp 198 | *out_addr = win->bin_buf[2] | win->bin_buf[1] << 8; 199 | return true; 200 | case 0x08: // setvec 201 | *out_addr = win->bin_buf[3] | win->bin_buf[2] << 8; 202 | return true; 203 | case 0x09: // if var 204 | *out_addr = win->bin_buf[3] | win->bin_buf[2] << 8; 205 | return true; 206 | case 0x0a: // if exp 207 | const uint8_t op = win->bin_buf[1]; 208 | const uint8_t off = (op & 0x40) ? 5 : 4; 209 | *out_addr = win->bin_buf[off+1] | win->bin_buf[off] << 8; 210 | return true; 211 | } 212 | return false; 213 | } 214 | 215 | /* push an address on the bookmark stack */ 216 | static void _ui_dasm_stack_push(ui_dasm_t* win, uint16_t addr) { 217 | if (win->stack_num < UI_DASM_MAX_STACK) { 218 | /* ignore if the same address is already on top of stack */ 219 | if ((win->stack_num > 0) && (addr == win->stack[win->stack_num-1])) { 220 | return; 221 | } 222 | win->stack_pos = win->stack_num; 223 | win->stack[win->stack_num++] = addr; 224 | } 225 | } 226 | 227 | /* return current address on stack, and set pos to previous */ 228 | static bool _ui_dasm_stack_back(ui_dasm_t* win, uint16_t* addr) { 229 | if (win->stack_num > 0) { 230 | *addr = win->stack[win->stack_pos]; 231 | if (win->stack_pos > 0) { 232 | win->stack_pos--; 233 | } 234 | return true; 235 | } 236 | *addr = 0; 237 | return false; 238 | } 239 | 240 | /* goto to address, op address on stack */ 241 | static void _ui_dasm_goto(ui_dasm_t* win, uint16_t addr) { 242 | win->start_addr = addr; 243 | } 244 | 245 | /* draw the address entry field and layer combo */ 246 | static void _ui_dasm_draw_controls(ui_dasm_t* win) { 247 | win->start_addr = ui_util_input_u16("##addr", win->start_addr); 248 | ImGui::SameLine(); 249 | uint16_t addr = 0; 250 | if (ImGui::ArrowButton("##back", ImGuiDir_Left)) { 251 | if (_ui_dasm_stack_back(win, &addr)) { 252 | _ui_dasm_goto(win, addr); 253 | } 254 | } 255 | if (ImGui::IsItemHovered() && (win->stack_num > 0)) { 256 | ImGui::SetTooltip("Goto %04X", win->stack[win->stack_pos]); 257 | } 258 | ImGui::SameLine(); 259 | ImGui::SameLine(); 260 | ImGui::PushItemWidth(ImGui::GetContentRegionAvail().x); 261 | ImGui::Combo("##layer", &win->cur_layer, win->layers, win->num_layers); 262 | ImGui::PopItemWidth(); 263 | } 264 | 265 | /* draw the disassembly column */ 266 | static void _ui_dasm_draw_disasm(ui_dasm_t* win) { 267 | ImGui::BeginChild("##dasmbox", ImVec2(0, 0), true); 268 | _ui_dasm_draw_controls(win); 269 | 270 | ImGui::BeginChild("##dasm", ImVec2(0, 0), false); 271 | ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0,0)); 272 | ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0,0)); 273 | const float line_height = ImGui::GetTextLineHeight(); 274 | const float glyph_width = ImGui::CalcTextSize("F").x; 275 | const float cell_width = 3 * glyph_width; 276 | ImGuiListClipper clipper; 277 | clipper.Begin(UI_DASM_NUM_LINES, line_height); 278 | clipper.Step(); 279 | 280 | /* skip hidden lines */ 281 | win->cur_addr = win->start_addr; 282 | for (int line_i = 0; (line_i < clipper.DisplayStart) && (line_i < UI_DASM_NUM_LINES); line_i++) { 283 | _ui_dasm_disasm(win); 284 | } 285 | 286 | /* visible items */ 287 | for (int line_i = clipper.DisplayStart; line_i < clipper.DisplayEnd; line_i++) { 288 | const uint16_t op_addr = win->cur_addr; 289 | _ui_dasm_disasm(win); 290 | const int num_bytes = win->bin_pos; 291 | if(!num_bytes) break; 292 | 293 | /* highlight current hovered address */ 294 | bool highlight = false; 295 | if (win->highlight_addr == op_addr) { 296 | ImGui::PushStyleColor(ImGuiCol_Text, win->highlight_color); 297 | highlight = true; 298 | } 299 | 300 | /* address */ 301 | ImGui::Text("%04X: ", op_addr); 302 | ImGui::SameLine(); 303 | 304 | /* instruction bytes */ 305 | const float line_start_x = ImGui::GetCursorPosX(); 306 | for (int n = 0; n < num_bytes; n++) { 307 | ImGui::SameLine(line_start_x + cell_width * n); 308 | ImGui::Text("%02X ", win->bin_buf[n]); 309 | } 310 | 311 | /* disassembled instruction */ 312 | ImGui::SameLine(line_start_x + cell_width*6 + glyph_width*2); 313 | ImGui::Text("%s", win->str_buf); 314 | 315 | if (highlight) { 316 | ImGui::PopStyleColor(); 317 | } 318 | 319 | /* check for jump instruction and draw an arrow */ 320 | uint16_t jump_addr = 0; 321 | if (_ui_dasm_jumptarget(win, win->cur_addr, &jump_addr)) { 322 | ImGui::SameLine(line_start_x + cell_width*8 + glyph_width*2 + glyph_width*20); 323 | ImGui::PushID(line_i); 324 | if (ImGui::ArrowButton("##btn", ImGuiDir_Right)) { 325 | ImGui::SetScrollY(0); 326 | _ui_dasm_goto(win, jump_addr); 327 | _ui_dasm_stack_push(win, op_addr); 328 | } 329 | if (ImGui::IsItemHovered()) { 330 | ImGui::SetTooltip("Goto %04X", jump_addr); 331 | win->highlight_addr = jump_addr; 332 | } 333 | ImGui::PopID(); 334 | } 335 | } 336 | clipper.End(); 337 | ImGui::PopStyleVar(2); 338 | ImGui::EndChild(); 339 | ImGui::EndChild(); 340 | } 341 | 342 | /* draw the stack */ 343 | static void _ui_dasm_draw_stack(ui_dasm_t* win) { 344 | ImGui::BeginChild("##stackbox", ImVec2(72, 0), true); 345 | if (ImGui::Button("Clear")) { 346 | win->stack_num = 0; 347 | } 348 | char buf[5] = { 0 }; 349 | if (ImGui::BeginListBox("##stack", ImVec2(-1,-1))) { 350 | for (int i = 0; i < win->stack_num; i++) { 351 | snprintf(buf, sizeof(buf), "%04X", win->stack[i]); 352 | ImGui::PushID(i); 353 | if (ImGui::Selectable(buf, i == win->stack_pos)) { 354 | win->stack_pos = i; 355 | _ui_dasm_goto(win, win->stack[i]); 356 | } 357 | if (ImGui::IsItemHovered()) { 358 | ImGui::SetTooltip("Goto %04X", win->stack[i]); 359 | win->highlight_addr = win->stack[i]; 360 | } 361 | ImGui::PopID(); 362 | } 363 | ImGui::EndListBox(); 364 | } 365 | ImGui::EndChild(); 366 | } 367 | 368 | void ui_dasm_draw(ui_dasm_t* win) { 369 | GAME_ASSERT(win && win->valid && win->title); 370 | if (!win->open) { 371 | return; 372 | } 373 | ImGui::SetNextWindowPos(ImVec2(win->init_x, win->init_y), ImGuiCond_FirstUseEver); 374 | ImGui::SetNextWindowSize(ImVec2(win->init_w, win->init_h), ImGuiCond_FirstUseEver); 375 | if (ImGui::Begin(win->title, &win->open)) { 376 | _ui_dasm_draw_stack(win); 377 | ImGui::SameLine(); 378 | _ui_dasm_draw_disasm(win); 379 | } 380 | ImGui::End(); 381 | } 382 | 383 | void ui_dasm_save_settings(ui_dasm_t* win, ui_settings_t* settings) { 384 | CHIPS_ASSERT(win && settings); 385 | ui_settings_add(settings, win->title, win->open); 386 | } 387 | 388 | void ui_dasm_load_settings(ui_dasm_t* win, const ui_settings_t* settings) { 389 | CHIPS_ASSERT(win && settings); 390 | win->open = ui_settings_isopen(settings, win->title); 391 | } 392 | #endif /* GAME_UI_IMPL */ 393 | -------------------------------------------------------------------------------- /src/ui/ui_snapshot.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | /*# 3 | # ui_snapshots.h 4 | 5 | Snapshot UI helpers. 6 | 7 | Do this: 8 | ~~~C 9 | #define GAME_UI_IMPL 10 | ~~~ 11 | before you include this file in *one* C++ file to create the 12 | implementation. 13 | 14 | Optionally provide the following macros with your own implementation 15 | 16 | ~~~C 17 | GAME_ASSERT(c) 18 | ~~~ 19 | your own assert macro (default: assert(c)) 20 | 21 | You need to include the following headers before including the 22 | *implementation*: 23 | 24 | - imgui.h 25 | 26 | ## zlib/libpng license 27 | 28 | Copyright (c) 2018 Andre Weissflog 29 | This software is provided 'as-is', without any express or implied warranty. 30 | In no event will the authors be held liable for any damages arising from the 31 | use of this software. 32 | Permission is granted to anyone to use this software for any purpose, 33 | including commercial applications, and to alter it and redistribute it 34 | freely, subject to the following restrictions: 35 | 1. The origin of this software must not be misrepresented; you must not 36 | claim that you wrote the original software. If you use this software in a 37 | product, an acknowledgment in the product documentation would be 38 | appreciated but is not required. 39 | 2. Altered source versions must be plainly marked as such, and must not 40 | be misrepresented as being the original software. 41 | 3. This notice may not be removed or altered from any source 42 | distribution. 43 | #*/ 44 | #include 45 | #include 46 | #include 47 | 48 | #ifdef __cplusplus 49 | extern "C" { 50 | #endif 51 | 52 | #define UI_SNAPSHOT_MAX_SLOTS (8) 53 | 54 | // Dear ImGui compatible texture handle 55 | typedef uint64_t ui_snapshot_texture_t; 56 | // callback function to save snapshot to a numbered slot 57 | typedef void (*ui_snapshot_save_t)(size_t slot_index); 58 | // callback function to load snapshot from numbered slot 59 | typedef bool (*ui_snapshot_load_t)(size_t slot_index); 60 | 61 | // a snapshot screenshot wrapper struct 62 | typedef struct { 63 | ui_snapshot_texture_t texture; 64 | bool portrait; 65 | } ui_snapshot_screenshot_t; 66 | 67 | // a snapshot slot 68 | typedef struct { 69 | bool valid; 70 | ui_snapshot_screenshot_t screenshot; 71 | } ui_snapshot_slot_t; 72 | 73 | // initialization parameters 74 | typedef struct { 75 | ui_snapshot_save_t save_cb; 76 | ui_snapshot_load_t load_cb; 77 | ui_snapshot_screenshot_t empty_slot_screenshot; 78 | } ui_snapshot_desc_t; 79 | 80 | // snapshot system state 81 | typedef struct { 82 | ui_snapshot_save_t save_cb; 83 | ui_snapshot_load_t load_cb; 84 | ui_snapshot_slot_t slots[UI_SNAPSHOT_MAX_SLOTS]; 85 | } ui_snapshot_t; 86 | 87 | // initialize the snapshot instance 88 | void ui_snapshot_init(ui_snapshot_t* state, const ui_snapshot_desc_t* desc); 89 | // inject snap menu UI 90 | void ui_snapshot_menus(ui_snapshot_t* state); 91 | // called from UI when a snapshot should be saved 92 | void ui_snapshot_save_slot(ui_snapshot_t* state, size_t slot_index); 93 | // called from UI when a snapshot should be loaded 94 | bool ui_snapshot_load_slot(ui_snapshot_t* state, size_t slot_index); 95 | // update snapshot info, returns previous slot info (usually called from within save callback) 96 | ui_snapshot_screenshot_t ui_snapshot_set_screenshot(ui_snapshot_t* state, size_t slot_index, ui_snapshot_screenshot_t screenshot); 97 | 98 | #ifdef __cplusplus 99 | } // extern "C" 100 | #endif 101 | 102 | //-- IMPLEMENTATION ------------------------------------------------------------ 103 | #ifdef GAME_UI_IMPL 104 | #include 105 | #ifndef GAME_ASSERT 106 | #include 107 | #define GAME_ASSERT(c) assert(c) 108 | #endif 109 | 110 | void ui_snapshot_init(ui_snapshot_t* state, const ui_snapshot_desc_t* desc) { 111 | GAME_ASSERT(state && desc); 112 | GAME_ASSERT(desc->save_cb && desc->load_cb); 113 | memset(state, 0, sizeof(ui_snapshot_t)); 114 | state->save_cb = desc->save_cb; 115 | state->load_cb = desc->load_cb; 116 | for (size_t i = 0; i < UI_SNAPSHOT_MAX_SLOTS; i++) { 117 | state->slots[i].screenshot = desc->empty_slot_screenshot; 118 | } 119 | } 120 | 121 | static bool ui_snapshot_draw_menu_slot(const char* sel_id, ui_snapshot_screenshot_t screenshot) { 122 | const ImVec2 pos = ImGui::GetCursorPos(); 123 | const ImVec2 size = screenshot.portrait ? ImVec2{ 96.0f, 128.0f } : ImVec2{ 128.0f, 96.0f }; 124 | bool pressed = ImGui::Selectable(sel_id, false, 0, size); 125 | ImGui::SetCursorPos(pos); 126 | ImGui::Image(screenshot.texture, size); 127 | return pressed; 128 | } 129 | 130 | void ui_snapshot_menus(ui_snapshot_t* state) { 131 | GAME_ASSERT(state); 132 | if (ImGui::BeginMenu("Save Snapshot")) { 133 | for (int slot_index = 0; slot_index < UI_SNAPSHOT_MAX_SLOTS; slot_index++) { 134 | const ui_snapshot_screenshot_t screenshot = state->slots[slot_index].screenshot; 135 | ImGui::PushID(slot_index); 136 | if (screenshot.texture) { 137 | if (ui_snapshot_draw_menu_slot("##savesnapshot", screenshot)) { 138 | ui_snapshot_save_slot(state, slot_index); 139 | } 140 | if ((slot_index + 1) & 1) { 141 | ImGui::SameLine(); 142 | } 143 | } 144 | else { 145 | char buf[128]; 146 | snprintf(buf, sizeof(buf), "%sSlot %d\n", state->slots[slot_index].valid ? "* ":"", (int)slot_index); 147 | if (ImGui::MenuItem(buf)) { 148 | ui_snapshot_save_slot(state, slot_index); 149 | } 150 | } 151 | ImGui::PopID(); 152 | } 153 | ImGui::EndMenu(); 154 | } 155 | if (ImGui::BeginMenu("Load Snapshot")) { 156 | for (int slot_index = 0; slot_index < UI_SNAPSHOT_MAX_SLOTS; slot_index++) { 157 | if (state->slots[slot_index].valid) { 158 | const ui_snapshot_screenshot_t screenshot = state->slots[slot_index].screenshot; 159 | ImGui::PushID(slot_index); 160 | if (screenshot.texture) { 161 | if (ui_snapshot_draw_menu_slot("##loadsnapshot", screenshot)) { 162 | ui_snapshot_load_slot(state, slot_index); 163 | } 164 | if ((slot_index + 1) & 1) { 165 | ImGui::SameLine(); 166 | } 167 | } 168 | else { 169 | char buf[128]; 170 | snprintf(buf, sizeof(buf), "Slot %d\n", (int)slot_index); 171 | if (ImGui::MenuItem(buf)) { 172 | ui_snapshot_load_slot(state, slot_index); 173 | } 174 | } 175 | ImGui::PopID(); 176 | } 177 | } 178 | ImGui::EndMenu(); 179 | } 180 | } 181 | 182 | void ui_snapshot_save_slot(ui_snapshot_t* state, size_t slot_index) { 183 | GAME_ASSERT(slot_index < UI_SNAPSHOT_MAX_SLOTS); 184 | state->save_cb(slot_index); 185 | } 186 | 187 | bool ui_snapshot_load_slot(ui_snapshot_t* state, size_t slot_index) { 188 | GAME_ASSERT(state); 189 | GAME_ASSERT(slot_index < UI_SNAPSHOT_MAX_SLOTS); 190 | if (state->slots[slot_index].valid) { 191 | return state->load_cb(slot_index); 192 | } 193 | else { 194 | return false; 195 | } 196 | } 197 | 198 | ui_snapshot_screenshot_t ui_snapshot_set_screenshot(ui_snapshot_t* state, size_t slot_index, ui_snapshot_screenshot_t screenshot) { 199 | GAME_ASSERT(state && screenshot.texture); 200 | GAME_ASSERT(slot_index < UI_SNAPSHOT_MAX_SLOTS); 201 | ui_snapshot_screenshot_t prev_screenshot = {}; 202 | if (state->slots[slot_index].valid) { 203 | prev_screenshot = state->slots[slot_index].screenshot; 204 | } 205 | state->slots[slot_index].valid = true; 206 | state->slots[slot_index].screenshot = screenshot; 207 | return prev_screenshot; 208 | } 209 | #endif -------------------------------------------------------------------------------- /src/ui/ui_util.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | /*# 3 | # ui_util.h 4 | 5 | UI helper functions. Include this header before most other Chips UI headers. 6 | 7 | Do this: 8 | ~~~C 9 | #define GAME_UI_IMPL 10 | ~~~ 11 | before you include this file in *one* C++ file to create the 12 | implementation. 13 | 14 | Optionally provide the following macros with your own implementation 15 | 16 | ~~~C 17 | GAME_ASSERT(c) 18 | ~~~ 19 | your own assert macro (default: assert(c)) 20 | 21 | You need to include the following headers before including the 22 | *implementation*: 23 | 24 | - imgui.h 25 | 26 | ## zlib/libpng license 27 | 28 | Copyright (c) 2018 Andre Weissflog 29 | This software is provided 'as-is', without any express or implied warranty. 30 | In no event will the authors be held liable for any damages arising from the 31 | use of this software. 32 | Permission is granted to anyone to use this software for any purpose, 33 | including commercial applications, and to alter it and redistribute it 34 | freely, subject to the following restrictions: 35 | 1. The origin of this software must not be misrepresented; you must not 36 | claim that you wrote the original software. If you use this software in a 37 | product, an acknowledgment in the product documentation would be 38 | appreciated but is not required. 39 | 2. Altered source versions must be plainly marked as such, and must not 40 | be misrepresented as being the original software. 41 | 3. This notice may not be removed or altered from any source 42 | distribution. 43 | #*/ 44 | #include 45 | #include 46 | 47 | #ifdef __cplusplus 48 | extern "C" { 49 | #endif 50 | 51 | // Dear ImGui compatible texture handle 52 | typedef uint64_t ui_texture_t; 53 | 54 | /* draw an 16-bit hex text input field */ 55 | uint16_t ui_util_input_u16(const char* label, uint16_t val); 56 | /* draw an 8-bit hex text input field */ 57 | uint8_t ui_util_input_u8(const char* label, uint8_t val); 58 | /* draw an 8-bit hex label/value text */ 59 | void ui_util_u8(const char* label, uint8_t val); 60 | /* draw a 16-bit hex label/value text */ 61 | void ui_util_u16(const char* label, uint16_t val); 62 | /* draw an 8-bit binary label/value text */ 63 | void ui_util_b8(const char* label, uint8_t val); 64 | /* draw a 24-bit binary label/value text */ 65 | void ui_util_b24(const char* label, uint32_t val); 66 | /* draw a 32-bit binary label/value text */ 67 | void ui_util_b32(const char* label, uint32_t val); 68 | /* get an ImGui style color (ImGuiCol_*) with overall alpha applied */ 69 | uint32_t ui_util_color(int imgui_color); 70 | /* inject the common options menu */ 71 | void ui_util_options_menu(void); 72 | // check if window open state has changed and call ImGui::MarkIniSettingsDirty() if needed 73 | void ui_util_handle_window_open_dirty(const bool* cur_open, bool* last_open); 74 | 75 | #ifdef __cplusplus 76 | } /* extern "C" */ 77 | #endif 78 | 79 | /*-- IMPLEMENTATION (include in C++ source) ----------------------------------*/ 80 | #ifdef GAME_UI_IMPL 81 | #ifndef __cplusplus 82 | #error "implementation must be compiled as C++" 83 | #endif 84 | #ifdef _MSC_VER 85 | #pragma warning(push) 86 | #pragma warning(disable:4996) /* sscanf */ 87 | #endif 88 | #include /* memset */ 89 | #include /* sscanf */ 90 | #ifndef GAME_ASSERT 91 | #include 92 | #define GAME_ASSERT(c) assert(c) 93 | #endif 94 | 95 | uint16_t ui_util_input_u16(const char* label, uint16_t val) { 96 | char buf[5]; 97 | for (int i = 0; i < 4; i++) { 98 | buf[i] = "0123456789ABCDEF"[val>>((3-i)*4) & 0xF]; 99 | } 100 | buf[4] = 0; 101 | const int flags = ImGuiInputTextFlags_CharsHexadecimal| 102 | ImGuiInputTextFlags_CharsUppercase| 103 | ImGuiInputTextFlags_EnterReturnsTrue; 104 | ImGui::PushItemWidth(38); 105 | if (ImGui::InputText(label, buf, sizeof(buf), flags)) { 106 | int res; 107 | if (sscanf(buf, "%X", &res) == 1) { 108 | val = (uint16_t) res; 109 | } 110 | } 111 | ImGui::PopItemWidth(); 112 | return val; 113 | } 114 | 115 | uint8_t ui_util_input_u8(const char* label, uint8_t val) { 116 | char buf[3]; 117 | for (int i = 0; i < 2; i++) { 118 | buf[i] = "0123456789ABCDEF"[val>>((1-i)*4) & 0xF]; 119 | } 120 | buf[2] = 0; 121 | const int flags = ImGuiInputTextFlags_CharsHexadecimal| 122 | ImGuiInputTextFlags_CharsUppercase| 123 | ImGuiInputTextFlags_EnterReturnsTrue; 124 | ImGui::PushItemWidth(22); 125 | if (ImGui::InputText(label, buf, sizeof(buf), flags)) { 126 | int res; 127 | if (sscanf(buf, "%X", &res) == 1) { 128 | val = (uint8_t) res; 129 | } 130 | } 131 | ImGui::PopItemWidth(); 132 | return val; 133 | } 134 | 135 | void ui_util_u8(const char* label, uint8_t val) { 136 | ImGui::Text("%s", label); ImGui::SameLine(); ImGui::Text("%02X", val); 137 | } 138 | 139 | void ui_util_u16(const char* label, uint16_t val) { 140 | ImGui::Text("%s", label); ImGui::SameLine(); ImGui::Text("%04X", val); 141 | } 142 | 143 | void ui_util_b8(const char* label, uint8_t val) { 144 | char str[9]; 145 | for (int i = 0; i < 8; i++) { 146 | str[i] = (val & (1<<(7-i))) ? '1':'0'; 147 | } 148 | str[8] = 0; 149 | ImGui::Text("%s%s", label, str); 150 | } 151 | 152 | void ui_util_b24(const char* label, uint32_t val) { 153 | char str[25]; 154 | for (int i = 0; i < 24; i++) { 155 | str[i] = (val & (1<<(23-i))) ? '1':'0'; 156 | } 157 | str[24] = 0; 158 | ImGui::Text("%s%s", label, str); 159 | } 160 | 161 | void ui_util_b32(const char* label, uint32_t val) { 162 | char str[33]; 163 | for (int i = 0; i < 32; i++) { 164 | str[i] = (val & (1<<(31-i))) ? '1':'0'; 165 | } 166 | str[32] = 0; 167 | ImGui::Text("%s%s", label, str); 168 | } 169 | 170 | uint32_t ui_util_color(int imgui_color) { 171 | GAME_ASSERT((imgui_color >= 0) && (imgui_color < ImGuiCol_COUNT)); 172 | const ImGuiStyle& style = ImGui::GetStyle(); 173 | ImVec4 c = style.Colors[imgui_color]; 174 | c.w *= style.Alpha; 175 | return ImColor(c); 176 | } 177 | 178 | void ui_util_options_menu(void) { 179 | if (ImGui::BeginMenu("Options")) { 180 | ImGui::SliderFloat("UI Alpha", &ImGui::GetStyle().Alpha, 0.1f, 1.0f); 181 | ImGui::SliderFloat("BG Alpha", &ImGui::GetStyle().Colors[ImGuiCol_WindowBg].w, 0.1f, 1.0f); 182 | static int theme = 0; 183 | if (ImGui::RadioButton("Dark Theme", &theme, 0)) { 184 | ImGui::StyleColorsDark(); 185 | } 186 | if (ImGui::RadioButton("Light Theme", &theme, 1)) { 187 | ImGui::StyleColorsLight(); 188 | } 189 | if (ImGui::RadioButton("Classic Theme", &theme, 2)) { 190 | ImGui::StyleColorsClassic(); 191 | } 192 | ImGui::EndMenu(); 193 | } 194 | ImGui::SameLine(ImGui::GetWindowWidth() - 120); 195 | } 196 | 197 | void ui_util_handle_window_open_dirty(const bool* cur_open, bool* last_open) { 198 | GAME_ASSERT(cur_open && last_open); 199 | if (*cur_open != *last_open) { 200 | *last_open = *cur_open; 201 | ImGui::MarkIniSettingsDirty(); 202 | } 203 | } 204 | 205 | #ifdef _MSC_VER 206 | #pragma warning(pop) 207 | #endif 208 | #endif /* GAME_UI_IMPL */ 209 | -------------------------------------------------------------------------------- /tools/convert_3do/Makefile: -------------------------------------------------------------------------------- 1 | 2 | CXXFLAGS := -Wall -Wpedantic 3 | 4 | convert_3do: bitmap.o cinepak.o main.o opera.o util.o wave.o 5 | $(CXX) -o $@ $^ 6 | 7 | clean: 8 | rm -f *.o convert_3do 9 | -------------------------------------------------------------------------------- /tools/convert_3do/bitmap.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "bitmap.h" 3 | 4 | static void TO_LE16(uint8_t *dst, uint16_t value) { 5 | for (int i = 0; i < 2; ++i) { 6 | dst[i] = value & 255; 7 | value >>= 8; 8 | } 9 | } 10 | 11 | static void TO_LE32(uint8_t *dst, uint32_t value) { 12 | for (int i = 0; i < 4; ++i) { 13 | dst[i] = value & 255; 14 | value >>= 8; 15 | } 16 | } 17 | 18 | static uint32_t TO_ARGB(const uint16_t rgb555) { 19 | const int r = ((rgb555 >> 10) & 31) << 3; 20 | const int g = ((rgb555 >> 5) & 31) << 3; 21 | const int b = ( rgb555 & 31) << 3; 22 | return 0xFF000000 | (r << 16) | (g << 8) | b; 23 | } 24 | 25 | void writeBitmap555(FILE *fp, const uint16_t *src, int w, int h) { 26 | static const int FILE_HEADER_SIZE = 14; 27 | static const int INFO_HEADER_SIZE = 40; 28 | const int alignedWidth = w * sizeof(uint32_t); 29 | const int imageSize = alignedWidth * h; 30 | const int bufferSize = FILE_HEADER_SIZE + INFO_HEADER_SIZE + imageSize; 31 | uint8_t *buffer = (uint8_t *)calloc(bufferSize, 1); 32 | if (buffer) { 33 | int offset = 0; 34 | 35 | // file header 36 | TO_LE16(buffer + offset, 0x4D42); offset += 2; 37 | TO_LE32(buffer + offset, bufferSize); offset += 4; 38 | TO_LE16(buffer + offset, 0); offset += 2; // reserved1 39 | TO_LE16(buffer + offset, 0); offset += 2; // reserved2 40 | TO_LE32(buffer + offset, FILE_HEADER_SIZE + INFO_HEADER_SIZE); offset += 4; 41 | 42 | // info header 43 | TO_LE32(buffer + offset, INFO_HEADER_SIZE); offset += 4; 44 | TO_LE32(buffer + offset, w); offset += 4; 45 | TO_LE32(buffer + offset, h); offset += 4; 46 | TO_LE16(buffer + offset, 1); offset += 2; // planes 47 | TO_LE16(buffer + offset, 32); offset += 2; // bit_count 48 | TO_LE32(buffer + offset, 0); offset += 4; // compression 49 | TO_LE32(buffer + offset, imageSize); offset += 4; // size_image 50 | TO_LE32(buffer + offset, 0); offset += 4; // x_pels_per_meter 51 | TO_LE32(buffer + offset, 0); offset += 4; // y_pels_per_meter 52 | TO_LE32(buffer + offset, 0); offset += 4; // num_colors_used 53 | TO_LE32(buffer + offset, 0); offset += 4; // num_colors_important 54 | 55 | // bitmap data 56 | for (int y = 0; y < h; ++y) { 57 | const uint16_t *p = src + (h - 1 - y) * w; 58 | for (int x = 0; x < w; ++x) { 59 | TO_LE32(buffer + offset + x * 4, TO_ARGB(p[x])); 60 | } 61 | offset += alignedWidth; 62 | } 63 | } 64 | const int count = fwrite(buffer, 1, bufferSize, fp); 65 | if (count != bufferSize) { 66 | fprintf(stderr, "Failed to write %d bytes, ret %d\n", bufferSize, count); 67 | } 68 | free(buffer); 69 | } 70 | -------------------------------------------------------------------------------- /tools/convert_3do/bitmap.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef BITMAP_H__ 3 | #define BITMAP_H__ 4 | 5 | #include "util.h" 6 | 7 | void writeBitmap555(FILE *fp, const uint16_t *src, int w, int h); 8 | 9 | #endif 10 | -------------------------------------------------------------------------------- /tools/convert_3do/cinepak.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | #include 5 | #include "cinepak.h" 6 | 7 | static void copyV4(uint8_t *dst, uint8_t y1, uint8_t y2, uint8_t u, uint8_t v) { 8 | dst[0] = u; 9 | dst[1] = y1; 10 | dst[2] = v; 11 | dst[3] = y2; 12 | } 13 | 14 | void CinepakDecoder::decodeFrameV4(Cinepak_YUV_Vector *v0, Cinepak_YUV_Vector *v1, Cinepak_YUV_Vector *v2, Cinepak_YUV_Vector *v3) { 15 | uint8_t *p = _yuvFrame + _yPos * _yuvPitch + _xPos * 2; 16 | 17 | copyV4(&p[0], v0->y[0], v0->y[1], v0->u, v0->v); 18 | copyV4(&p[4], v1->y[0], v1->y[1], v1->u, v1->v); 19 | p += _yuvPitch; 20 | copyV4(&p[0], v0->y[2], v0->y[3], v0->u, v0->v); 21 | copyV4(&p[4], v1->y[2], v1->y[3], v1->u, v1->v); 22 | p += _yuvPitch; 23 | copyV4(&p[0], v2->y[0], v2->y[1], v2->u, v2->v); 24 | copyV4(&p[4], v3->y[0], v3->y[1], v3->u, v3->v); 25 | p += _yuvPitch; 26 | copyV4(&p[0], v2->y[2], v2->y[3], v2->u, v2->v); 27 | copyV4(&p[4], v3->y[2], v3->y[3], v3->u, v3->v); 28 | } 29 | 30 | static void copyV1(uint8_t *dst, uint8_t y, uint8_t u, uint8_t v) { 31 | dst[0] = u; 32 | dst[1] = y; 33 | dst[2] = v; 34 | dst[3] = y; 35 | } 36 | 37 | void CinepakDecoder::decodeFrameV1(Cinepak_YUV_Vector *v) { 38 | uint8_t *p = _yuvFrame + _yPos * _yuvPitch + _xPos * 2; 39 | 40 | copyV1(&p[0], v->y[0], v->u, v->v); 41 | copyV1(&p[4], v->y[1], v->u, v->v); 42 | p += _yuvPitch; 43 | copyV1(&p[0], v->y[0], v->u, v->v); 44 | copyV1(&p[4], v->y[1], v->u, v->v); 45 | p += _yuvPitch; 46 | copyV1(&p[0], v->y[2], v->u, v->v); 47 | copyV1(&p[4], v->y[3], v->u, v->v); 48 | p += _yuvPitch; 49 | copyV1(&p[0], v->y[2], v->u, v->v); 50 | copyV1(&p[4], v->y[3], v->u, v->v); 51 | } 52 | 53 | void CinepakDecoder::decodeVector(Cinepak_YUV_Vector *v) { 54 | for (int i = 0; i < 4; ++i) { 55 | v->y[i] = readByte(); 56 | } 57 | v->u = 128 + readByte(); 58 | v->v = 128 + readByte(); 59 | } 60 | 61 | void CinepakDecoder::decode(const uint8_t *data, int dataSize) { 62 | _data = data; 63 | 64 | const uint8_t flags = readByte(); 65 | _data += 3; 66 | _w = readWord(); 67 | _h = readWord(); 68 | assert(_w == _yuvW && _h == _yuvH); 69 | const int strips = readWord(); 70 | assert(strips <= kMaxStrips); 71 | 72 | if (memcmp(_data, "\xFE\x00\x00\x06\x00\x00", 6) == 0) { 73 | _data += 6; 74 | } 75 | 76 | _xPos = _yPos = 0; 77 | int yMax = 0; 78 | for (int strip = 0; strip < strips; ++strip) { 79 | if (strip != 0 && (flags & 1) == 0) { 80 | memcpy(&_vectors[kCinepakV1][strip][0], &_vectors[kCinepakV1][strip - 1][0], sizeof(Cinepak_YUV_Vector) * kMaxVectors); 81 | memcpy(&_vectors[kCinepakV4][strip][0], &_vectors[kCinepakV4][strip - 1][0], sizeof(Cinepak_YUV_Vector) * kMaxVectors); 82 | } 83 | 84 | readWord(); 85 | int size = readWord(); 86 | /* const int stripTopY = */ readWord(); 87 | /* const int stripTopX = */ readWord(); 88 | const int stripBottomY = readWord(); 89 | /* const int stripBottomX = */ readWord(); 90 | 91 | size -= 12; 92 | _xPos = 0; 93 | yMax += stripBottomY; 94 | int v, i; 95 | while (size > 0) { 96 | int chunkType = readWord(); 97 | int chunkSize = readWord(); 98 | if (chunkSize > size) { 99 | chunkSize = size; 100 | } 101 | size -= chunkSize; 102 | chunkSize -= 4; 103 | switch (chunkType) { 104 | case 0x2000: 105 | case 0x2200: 106 | v = (chunkType == 0x2200) ? kCinepakV1 : kCinepakV4; 107 | for (i = 0; i < chunkSize / 6; ++i) { 108 | decodeVector(&_vectors[v][strip][i]); 109 | } 110 | chunkSize = 0; 111 | break; 112 | case 0x2100: 113 | case 0x2300: 114 | v = (chunkType == 0x2300) ? kCinepakV1 : kCinepakV4; 115 | i = 0; 116 | while (chunkSize > 0) { 117 | const uint32_t mask = readLong(); 118 | chunkSize -= 4; 119 | for (int bit = 0; bit < 32; ++bit) { 120 | if (mask & (1 << (31 - bit))) { 121 | decodeVector(&_vectors[v][strip][i]); 122 | chunkSize -= 6; 123 | } 124 | ++i; 125 | } 126 | } 127 | break; 128 | case 0x3000: 129 | while (chunkSize > 0 && _yPos < yMax) { 130 | uint32_t mask = readLong(); 131 | chunkSize -= 4; 132 | for (int bit = 0; bit < 32 && _yPos < yMax; ++bit) { 133 | if (mask & (1 << (31 - bit))) { 134 | Cinepak_YUV_Vector *v0 = &_vectors[kCinepakV4][strip][readByte()]; 135 | Cinepak_YUV_Vector *v1 = &_vectors[kCinepakV4][strip][readByte()]; 136 | Cinepak_YUV_Vector *v2 = &_vectors[kCinepakV4][strip][readByte()]; 137 | Cinepak_YUV_Vector *v3 = &_vectors[kCinepakV4][strip][readByte()]; 138 | chunkSize -= 4; 139 | decodeFrameV4(v0, v1, v2, v3); 140 | } else { 141 | Cinepak_YUV_Vector *v0 = &_vectors[kCinepakV1][strip][readByte()]; 142 | --chunkSize; 143 | decodeFrameV1(v0); 144 | } 145 | _xPos += 4; 146 | if (_xPos >= _w) { 147 | _xPos = 0; 148 | _yPos += 4; 149 | } 150 | } 151 | } 152 | break; 153 | case 0x3100: 154 | while (chunkSize > 0 && _yPos < yMax) { 155 | uint32_t mask = readLong(); 156 | chunkSize -= 4; 157 | for (int bit = 0; bit < 32 && chunkSize >= 0 && _yPos < yMax; ) { 158 | if (mask & (1 << (31 - bit))) { 159 | ++bit; 160 | if (bit == 32) { 161 | assert(chunkSize >= 4); 162 | mask = readLong(); 163 | chunkSize -= 4; 164 | bit = 0; 165 | } 166 | if (mask & (1 << (31 - bit))) { 167 | Cinepak_YUV_Vector *v0 = &_vectors[kCinepakV4][strip][readByte()]; 168 | Cinepak_YUV_Vector *v1 = &_vectors[kCinepakV4][strip][readByte()]; 169 | Cinepak_YUV_Vector *v2 = &_vectors[kCinepakV4][strip][readByte()]; 170 | Cinepak_YUV_Vector *v3 = &_vectors[kCinepakV4][strip][readByte()]; 171 | chunkSize -= 4; 172 | decodeFrameV4(v0, v1, v2, v3); 173 | } else { 174 | Cinepak_YUV_Vector *v0 = &_vectors[kCinepakV1][strip][readByte()]; 175 | --chunkSize; 176 | decodeFrameV1(v0); 177 | } 178 | } 179 | ++bit; 180 | _xPos += 4; 181 | if (_xPos >= _w) { 182 | _xPos = 0; 183 | _yPos += 4; 184 | } 185 | } 186 | } 187 | break; 188 | case 0x3200: 189 | while (chunkSize > 0 && _yPos < yMax) { 190 | Cinepak_YUV_Vector *v0 = &_vectors[kCinepakV1][strip][readByte()]; 191 | --chunkSize; 192 | decodeFrameV1(v0); 193 | _xPos += 4; 194 | if (_xPos >= _w) { 195 | _xPos = 0; 196 | _yPos += 4; 197 | } 198 | } 199 | break; 200 | } 201 | _data += chunkSize; 202 | } 203 | } 204 | } 205 | -------------------------------------------------------------------------------- /tools/convert_3do/cinepak.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef CINEPAK_H__ 3 | #define CINEPAK_H__ 4 | 5 | #include 6 | 7 | struct Cinepak_YUV_Vector { 8 | uint8_t y[4]; 9 | uint8_t u, v; 10 | }; 11 | 12 | enum { 13 | kCinepakV1 = 0, 14 | kCinepakV4 = 1 15 | }; 16 | 17 | struct CinepakDecoder { 18 | enum { 19 | kMaxStrips = 2, 20 | kMaxVectors = 2048 21 | }; 22 | 23 | uint8_t readByte() { 24 | return *_data++; 25 | } 26 | 27 | uint16_t readWord() { 28 | uint16_t value = (_data[0] << 8) | _data[1]; 29 | _data += 2; 30 | return value; 31 | } 32 | 33 | uint32_t readLong() { 34 | uint32_t value = (_data[0] << 24) | (_data[1] << 16) | (_data[2] << 8) | _data[3]; 35 | _data += 4; 36 | return value; 37 | } 38 | 39 | void decodeFrameV4(Cinepak_YUV_Vector *v0, Cinepak_YUV_Vector *v1, Cinepak_YUV_Vector *v2, Cinepak_YUV_Vector *v3); 40 | void decodeFrameV1(Cinepak_YUV_Vector *v); 41 | void decodeVector(Cinepak_YUV_Vector *v); 42 | void decode(const uint8_t *data, int dataSize); 43 | 44 | const uint8_t *_data; 45 | Cinepak_YUV_Vector _vectors[2][kMaxStrips][kMaxVectors]; 46 | int _w, _h; 47 | int _xPos, _yPos, _yMax; 48 | 49 | uint8_t *_yuvFrame; 50 | int _yuvPitch; 51 | int _yuvW, _yuvH; 52 | }; 53 | 54 | #endif // CINEPAK_H__ 55 | -------------------------------------------------------------------------------- /tools/convert_3do/opera.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include "opera.h" 4 | 5 | static const char *GAMEDATA_DIRECTORY = "GameData"; 6 | 7 | static void dumpGameData(const char *name, FILE *in, uint32_t offset, uint32_t size) { 8 | fprintf(stdout, "%s at 0x%x size %d bytes\n", name, offset, size); 9 | makeDirectory(GAMEDATA_DIRECTORY); 10 | char path[MAXPATHLEN]; 11 | snprintf(path, sizeof(path), "%s/%s", GAMEDATA_DIRECTORY, name); 12 | FILE *out = fopen(path, "wb"); 13 | if (out) { 14 | uint8_t buf[4096]; 15 | int count; 16 | fseek(in, offset, SEEK_SET); 17 | while ((count = fread(buf, 1, size < sizeof(buf) ? size : sizeof(buf), in)) > 0) { 18 | fwrite(buf, 1, count, out); 19 | size -= count; 20 | } 21 | fclose(out); 22 | } 23 | } 24 | 25 | static const int BLOCK_SIZE = 2048; 26 | 27 | static void readIso(FILE *fp, int block, int flags) { 28 | uint32_t attr = 0; 29 | do { 30 | fseek(fp, block * BLOCK_SIZE + 20, SEEK_SET); 31 | do { 32 | uint8_t buf[72]; 33 | fread(buf, sizeof(buf), 1, fp); 34 | attr = READ_BE_UINT32(buf); 35 | const char *name = (const char *)buf + 32; 36 | const uint32_t count = READ_BE_UINT32(buf + 64); 37 | const uint32_t offset = READ_BE_UINT32(buf + 68); 38 | fseek(fp, count * 4, SEEK_CUR); 39 | switch (attr & 255) { 40 | case 2: 41 | if (flags & 1) { 42 | const int pos = ftell(fp); 43 | dumpGameData(name, fp, offset * BLOCK_SIZE, READ_BE_UINT32(buf + 16)); 44 | fseek(fp, pos, SEEK_SET); 45 | } 46 | break; 47 | case 7: 48 | if (strcmp(name, GAMEDATA_DIRECTORY) == 0) { 49 | readIso(fp, offset, 1); 50 | } 51 | break; 52 | } 53 | } while (attr != 0 && attr < 256); 54 | ++block; 55 | } while ((attr >> 24) == 0x40); 56 | } 57 | 58 | int extractOperaIso(FILE *fp) { 59 | uint8_t buf[128]; 60 | const int count = fread(buf, 1, sizeof(buf), fp); 61 | if (count != sizeof(buf)) { 62 | fprintf(stderr, "Failed to read %d bytes, ret %d\n", (int)sizeof(buf), count); 63 | return -1; 64 | } 65 | if (buf[0] != 1 || memcmp(buf + 40, "CD-ROM", 6) != 0) { 66 | fprintf(stderr, "Unexpected Opera ISO signature\n"); 67 | return -1; 68 | } 69 | const int offset = READ_BE_UINT32(buf + 100); 70 | readIso(fp, offset, 0); 71 | return 0; 72 | } 73 | -------------------------------------------------------------------------------- /tools/convert_3do/opera.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef OPERA_H__ 3 | #define OPERA_H__ 4 | 5 | #include "util.h" 6 | 7 | int extractOperaIso(FILE *fp); 8 | 9 | #endif 10 | -------------------------------------------------------------------------------- /tools/convert_3do/util.cpp: -------------------------------------------------------------------------------- 1 | 2 | #ifdef _WIN32 3 | #define WIN32_LEAN_AND_MEAN 4 | #include 5 | #endif 6 | #include 7 | #include "util.h" 8 | 9 | void stringLower(char *p) { 10 | for (; *p; ++p) { 11 | if (*p >= 'A' && *p <= 'Z') { 12 | *p += 'a' - 'A'; 13 | } 14 | } 15 | } 16 | 17 | uint16_t READ_BE_UINT16(const uint8_t *p) { 18 | return (p[0] << 8) | p[1]; 19 | } 20 | 21 | uint32_t READ_BE_UINT32(const uint8_t *p) { 22 | return (p[0] << 24) | (p[1] << 16) | (p[2] << 8) | p[3]; 23 | } 24 | 25 | void makeDirectory(const char *path) { 26 | #ifdef _WIN32 27 | CreateDirectory(path, NULL); 28 | #else 29 | mkdir(path, S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH); 30 | #endif 31 | } 32 | -------------------------------------------------------------------------------- /tools/convert_3do/util.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef UTIL_H__ 3 | #define UTIL_H__ 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | void stringLower(char *p); 11 | uint16_t READ_BE_UINT16(const uint8_t *p); 12 | uint32_t READ_BE_UINT32(const uint8_t *p); 13 | void makeDirectory(const char *path); 14 | 15 | #endif // UTIL_H__ 16 | -------------------------------------------------------------------------------- /tools/convert_3do/wave.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "wave.h" 3 | 4 | static void TO_LE16(uint8_t *dst, uint16_t value) { 5 | for (int i = 0; i < 2; ++i) { 6 | dst[i] = (value >> (8 * i)) & 255; 7 | } 8 | } 9 | 10 | static void TO_LE32(uint8_t *dst, uint32_t value) { 11 | for (int i = 0; i < 4; ++i) { 12 | dst[i] = (value >> (8 * i)) & 255; 13 | } 14 | } 15 | 16 | static void TO_BE32(uint8_t *dst, uint32_t value) { 17 | for (int i = 0; i < 4; ++i) { 18 | dst[i] = (value >> (8 * (3 - i))) & 255; 19 | } 20 | } 21 | 22 | int writeWav_stereoS16(FILE *fp, const int16_t *samples, int count, int frequency) { 23 | static const int FMT_CHUNK_SIZE = 16; 24 | int bufferSize = 4 + (8 + FMT_CHUNK_SIZE) + (8 + count * sizeof(int16_t)); 25 | uint8_t *buffer = (uint8_t *)calloc(8 + bufferSize, 1); 26 | if (buffer) { 27 | int offset = 0; 28 | 29 | TO_BE32(buffer + offset, 0x52494646); offset += 4; // 'RIFF' 30 | TO_LE32(buffer + offset, bufferSize); offset += 4; 31 | 32 | TO_BE32(buffer + offset, 0x57415645); offset += 4; // 'WAVE' 33 | TO_BE32(buffer + offset, 0x666d7420); offset += 4; // 'fmt ' 34 | TO_LE32(buffer + offset, FMT_CHUNK_SIZE); offset += 4; 35 | TO_LE16(buffer + offset, 1); offset += 2; // audio_format 36 | TO_LE16(buffer + offset, 2); offset += 2; // num_channels 37 | TO_LE32(buffer + offset, frequency); offset += 4; // sample_rate 38 | TO_LE32(buffer + offset, frequency * 2 * sizeof(int16_t)); offset += 4; // byte_rate 39 | TO_LE16(buffer + offset, 2 * sizeof(int16_t)); offset += 2; // block_align 40 | TO_LE16(buffer + offset, 16); offset += 2; // bits_per_sample 41 | 42 | TO_BE32(buffer + offset, 0x64617461); offset += 4; // 'data' 43 | TO_LE32(buffer + offset, count * sizeof(int16_t)); offset += 4; 44 | memcpy(buffer + offset, samples, count * sizeof(int16_t)); 45 | 46 | fwrite(buffer, 1, 8 + bufferSize, fp); 47 | 48 | free(buffer); 49 | } 50 | return 0; 51 | } 52 | -------------------------------------------------------------------------------- /tools/convert_3do/wave.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef WAVE_H__ 3 | #define WAVE_H__ 4 | 5 | #include "util.h" 6 | 7 | int writeWav_stereoS16(FILE *fp, const int16_t *samples, int count, int frequency); 8 | 9 | #endif 10 | -------------------------------------------------------------------------------- /tools/convert_segacd/convert_segacd.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Convert data files from the ISO of Heart of the Alien to the DOS version memlist/bank structure. 4 | 5 | import os 6 | import os.path 7 | import shutil 8 | import struct 9 | import sys 10 | 11 | PARTS = { 12 | 'intro7' : [ 0x17, 0x18, 0x19 ], # 16001 13 | 'eau3' : [ 0x1A, 0x1B, 0x1C ], # 16002 14 | 'pri3' : [ 0x1D, 0x1E, 0x1F ], # 16003 15 | 'cite1' : [ 0x20, 0x21, 0x22 ], # 16004 16 | 'arene2' : [ 0x23, 0x24, 0x25 ], # 16005 17 | 'luxe2' : [ 0x26, 0x27, 0x28 ], # 16006 18 | 'final3' : [ 0x29, 0x2A, 0x2B ], # 16007 19 | 'code' : [ 0x7D, 0x7E, 0x7F ] # 16008 20 | } 21 | 22 | COUNT = 146 23 | 24 | TYPE_SND = 0 25 | TYPE_PAL = 3 26 | TYPE_MAC = 4 27 | TYPE_MAT = 5 28 | TYPE_BNK = 6 # bank2.mat 29 | 30 | class Resource(object): 31 | def __init__(self, rtype, data): 32 | self.rtype = rtype 33 | self.data = data 34 | self.offset = 0 35 | self.size = len(data) 36 | 37 | def convert_snd_amiga(data, length): 38 | assert (length & 1) == 0 39 | size_words = length / 2 40 | s = struct.pack('>H', size_words) # length 41 | s += struct.pack('>H', size_words) # length 42 | # convert sign/magnitude to signed PCM 43 | for b in data: 44 | pcm = ord(b) 45 | if (pcm & 0x80) != 0: 46 | s += chr((-(pcm & 0x7F)) & 0xFF) 47 | else: 48 | s += b 49 | return s 50 | 51 | def convert_snd(filepath, resources): 52 | print filepath 53 | with open(filepath, 'rb') as f: 54 | # sound offsets 55 | offset = struct.unpack('>I', f.read(4))[0] 56 | assert (offset & 3) == 0 57 | count = offset / 4 58 | offsets = [ offset ] 59 | for i in range(1, count): 60 | offset = struct.unpack('>I', f.read(4))[0] 61 | offsets.append(offset) 62 | offsets.append( os.path.getsize(filepath) ) 63 | # sound num table 64 | length = offsets[1] - offsets[0] 65 | f.seek(offsets[0]) 66 | mapping = f.read(length) 67 | first_num = ord(mapping[0]) 68 | last_num = ord(mapping[1]) 69 | length -= 2 70 | count = last_num + 1 - first_num + 2 71 | assert length >= count 72 | for i in range(2, count): 73 | num = ord(mapping[i]) 74 | if num != 0: 75 | sound_num = first_num + i - 2 76 | f.seek(offsets[num]) 77 | header = struct.unpack('>I', f.read(4))[0] 78 | length = offsets[num + 1] - offsets[num] 79 | print 'sound #%d num 0x%02x header %d length %d' % (num, sound_num, header, length) 80 | assert length >= header 81 | resources[ sound_num ] = Resource(TYPE_SND, convert_snd_amiga(f.read(header), length)) 82 | 83 | def convert(dirpath): 84 | resources = [ None for i in range(COUNT) ] 85 | for name, indexes in PARTS.items(): 86 | mac = os.path.join(dirpath, name + '.mac') 87 | resources[ indexes[1] ] = Resource(TYPE_MAC, file(mac).read()) 88 | mat = os.path.join(dirpath, name + '.mat') 89 | resources[ indexes[2] ] = Resource(TYPE_MAT, file(mat).read()) 90 | if name[-1].isdigit(): 91 | snd = os.path.join(dirpath, name[:-1] + '.snd') 92 | if os.path.exists(snd): 93 | convert_snd(snd, resources) 94 | # palettes are possibly stored in make2s.bin (0x5A88, 0x5D28), use external files for now 95 | pal = 'data_%02x_%d' % (indexes[0], TYPE_PAL) 96 | resources[ indexes[0] ] = Resource(TYPE_PAL, file(pal).read()) 97 | # also use an external file for bank2.mat 98 | bnk = 'data_11_%d' % TYPE_BNK 99 | resources[ 0x11 ] = Resource(TYPE_BNK, file(bnk).read()) 100 | # generate bank0f 101 | with open('bank0f', 'wb') as f: 102 | offset = 0 103 | for r in resources: 104 | if r: 105 | r.offset = offset 106 | f.write(r.data) 107 | offset += r.size 108 | # generate memlist.bin 109 | with open('memlist.bin', 'wb') as f: 110 | dummy = '\x00' * 20 111 | for r in resources: 112 | if r: 113 | s = '\x00' # status 114 | s += chr(r.rtype) 115 | s += struct.pack('>I', 0) # pointer 116 | s += '\x00' # rank 117 | s += chr(0xF) # bank 118 | s += struct.pack('>I', r.offset) # data offset 119 | s += struct.pack('>I', r.size) # compressed size 120 | s += struct.pack('>I', r.size) # uncompresssed size 121 | assert len(s) == 20 122 | f.write(s) 123 | else: 124 | f.write(dummy) 125 | f.write('\xff') 126 | 127 | convert(sys.argv[1]) 128 | -------------------------------------------------------------------------------- /tools/convert_soundfx/Makefile: -------------------------------------------------------------------------------- 1 | 2 | convert_soundfx: main.o 3 | $(CXX) -o $@ $^ 4 | 5 | clean: 6 | rm -f *.o convert_soundfx 7 | -------------------------------------------------------------------------------- /tools/convert_soundfx/convert.sh: -------------------------------------------------------------------------------- 1 | set -x 2 | for num in 7 137 138; do 3 | filename=soundfx$num.raw 4 | ./convert_soundfx $num 5 | mv out.raw $filename 6 | sox -r 22050 -e signed -b 8 -c 2 $filename soundfx$num.wav 7 | oggenc soundfx$num.wav 8 | rm -f $filename soundfx$num.wav 9 | done 10 | -------------------------------------------------------------------------------- /tools/convert_soundfx/main.cpp: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * Another World soundfx player 4 | * Copyright (C) 2004-2005 Gregory Montoir (cyx@users.sourceforge.net) 5 | */ 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #define PAULA_FREQ 3579545 // NTSC 15 | //#define PAULA_FREQ 3546897 // PAL 16 | 17 | static const int kHz = 22050; // mixer sampling rate 18 | 19 | static const char *kSndFilePath = "DATA/data_%02x_0"; // sample 20 | static const char *kSfxFilePath = "DATA/data_%02x_1"; // module 21 | 22 | static uint16_t READ_BE_UINT16(const uint8_t *p) { 23 | return p[0] * 256 + p[1]; 24 | } 25 | 26 | struct Buf { 27 | uint8_t *buf; 28 | uint32_t size; 29 | Buf() 30 | : buf(0), size(0) { 31 | } 32 | }; 33 | 34 | static Buf readFile(const char *filename) { 35 | Buf b; 36 | FILE *fp = fopen(filename, "rb"); 37 | if (fp) { 38 | fseek(fp, 0, SEEK_END); 39 | b.size = ftell(fp); 40 | fseek(fp, 0, SEEK_SET); 41 | b.buf = (uint8_t *)malloc(b.size); 42 | if (fread(b.buf, 1, b.size, fp) != b.size) { 43 | fprintf(stderr, "readFile(%s) read error\n", filename); 44 | } 45 | fclose(fp); 46 | } else { 47 | fprintf(stderr, "Failed to open '%s'\n", filename); 48 | } 49 | return b; 50 | } 51 | 52 | struct InstrumentSample { 53 | const uint8_t *data; 54 | int len; 55 | int loopPos; 56 | int loopLen; 57 | }; 58 | 59 | static const uint16_t _freqTable[] = { 60 | 0x0CFF, 0x0DC3, 0x0E91, 0x0F6F, 0x1056, 0x114E, 0x1259, 0x136C, 61 | 0x149F, 0x15D9, 0x1726, 0x1888, 0x19FD, 0x1B86, 0x1D21, 0x1EDE, 62 | 0x20AB, 0x229C, 0x24B3, 0x26D7, 0x293F, 0x2BB2, 0x2E4C, 0x3110, 63 | 0x33FB, 0x370D, 0x3A43, 0x3DDF, 0x4157, 0x4538, 0x4998, 0x4DAE, 64 | 0x5240, 0x5764, 0x5C9A, 0x61C8, 0x6793, 0x6E19, 0x7485, 0x7BBD 65 | }; 66 | 67 | static const int kFracBits = 16; 68 | 69 | struct Player { 70 | enum { 71 | NUM_CHANNELS = 4, 72 | NUM_INSTRUMENTS = 15 73 | }; 74 | 75 | struct SfxInstrument { 76 | uint16_t resId; 77 | uint16_t volume; 78 | Buf buf; 79 | }; 80 | struct SfxModule { 81 | Buf buf; 82 | SfxInstrument samples[NUM_INSTRUMENTS]; 83 | uint8_t curOrder; 84 | uint8_t numOrder; 85 | uint8_t orderTable[0x80]; 86 | }; 87 | struct Pattern { 88 | uint16_t note_1; 89 | uint16_t note_2; 90 | uint16_t sample_start; 91 | uint8_t *sample_buffer; 92 | uint16_t period_value; 93 | uint16_t loopPos; 94 | uint8_t *loopData; 95 | uint16_t loopLen; 96 | uint16_t period_arpeggio; // unused by Another World tracks 97 | uint16_t sample_volume; 98 | }; 99 | struct Channel { 100 | InstrumentSample sample; 101 | uint32_t pos; 102 | uint64_t inc; 103 | int volume; 104 | }; 105 | 106 | uint16_t _delay; 107 | uint16_t _curPos; 108 | uint8_t *_bufData; 109 | SfxModule _sfxMod; 110 | Pattern _patterns[4]; 111 | Channel _channels[NUM_CHANNELS]; 112 | bool _playingMusic; 113 | 114 | void loadSfxModule(int num); 115 | void loadInstruments(const uint8_t *p); 116 | void stop(); 117 | void start(); 118 | void handleEvents(); 119 | void handlePattern(uint8_t channel, uint8_t *&data, Pattern *pat); 120 | 121 | int _rate; 122 | int _samplesLeft; 123 | 124 | void Mix_playChannel(int channel, InstrumentSample *, int freq, int volume); 125 | void Mix_stopChannel(int channel); 126 | void Mix_setChannelVolume(int channel, int volume); 127 | void Mix_stopAll(); 128 | void Mix_doMixChannel(int8_t *buf, int channel); 129 | void Mix_doMix(int8_t *samples, int count); 130 | }; 131 | 132 | void Player::loadSfxModule(int num) { 133 | char path[MAXPATHLEN]; 134 | snprintf(path, sizeof(path), kSfxFilePath, num); 135 | _sfxMod.buf = readFile(path); 136 | if (_sfxMod.buf.buf) { 137 | uint8_t *p = _sfxMod.buf.buf; 138 | _sfxMod.curOrder = 0; 139 | _sfxMod.numOrder = READ_BE_UINT16(p + 0x3E); 140 | fprintf(stdout, "curOrder = 0x%X numOrder = 0x%X\n", _sfxMod.curOrder, _sfxMod.numOrder); 141 | for (int i = 0; i < 0x80; ++i) { 142 | _sfxMod.orderTable[i] = *(p + 0x40 + i); 143 | } 144 | _delay = READ_BE_UINT16(p); 145 | // _delay = 15700; 146 | _delay = _delay * 60 * 1000 / (PAULA_FREQ * 2); 147 | _bufData = p + 0xC0; 148 | fprintf(stdout, "eventDelay = %d\n", _delay); 149 | loadInstruments(p + 2); 150 | stop(); 151 | start(); 152 | } 153 | } 154 | 155 | void Player::loadInstruments(const uint8_t *p) { 156 | memset(_sfxMod.samples, 0, sizeof(_sfxMod.samples)); 157 | for (int i = 0; i < 0xF; ++i) { 158 | SfxInstrument *ins = &_sfxMod.samples[i]; 159 | ins->resId = READ_BE_UINT16(p); p += 2; 160 | if (ins->resId != 0) { 161 | ins->volume = READ_BE_UINT16(p); 162 | char path[MAXPATHLEN]; 163 | snprintf(path, sizeof(path), kSndFilePath, ins->resId); 164 | ins->buf = readFile(path); 165 | if (ins->buf.buf) { 166 | memset(ins->buf.buf + 8, 0, 4); 167 | fprintf(stdout, "Loaded instrument '%s' n=%d volume=%d\n", path, i, ins->volume); 168 | } else { 169 | fprintf(stderr, "Error loading instrument '%s'\n", path); 170 | } 171 | } 172 | p += 2; // volume 173 | } 174 | } 175 | 176 | void Player::stop() { 177 | } 178 | 179 | void Player::start() { 180 | _curPos = 0; 181 | _playingMusic = true; 182 | memset(&_channels, 0, sizeof(_channels)); 183 | _rate = kHz; 184 | _samplesLeft = 0; 185 | } 186 | 187 | void Player::handleEvents() { 188 | uint8_t order = _sfxMod.orderTable[_sfxMod.curOrder]; 189 | uint8_t *patternData = _bufData + _curPos + order * 1024; 190 | memset(_patterns, 0, sizeof(_patterns)); 191 | for (uint8_t ch = 0; ch < 4; ++ch) { 192 | handlePattern(ch, patternData, &_patterns[ch]); 193 | } 194 | _curPos += 4 * 4; 195 | fprintf(stdout, "order = 0x%X curPos = 0x%X\n", order, _curPos); 196 | if (_curPos >= 1024) { 197 | _curPos = 0; 198 | order = _sfxMod.curOrder + 1; 199 | if (order == _sfxMod.numOrder) { 200 | Mix_stopAll(); 201 | order = 0; 202 | _playingMusic = false; 203 | } 204 | _sfxMod.curOrder = order; 205 | } 206 | } 207 | 208 | void Player::handlePattern(uint8_t channel, uint8_t *&data, Pattern *pat) { 209 | pat->note_1 = READ_BE_UINT16(data + 0); 210 | pat->note_2 = READ_BE_UINT16(data + 2); 211 | data += 4; 212 | if (pat->note_1 != 0xFFFD) { 213 | uint16_t sample = (pat->note_2 & 0xF000) >> 12; 214 | if (sample != 0) { 215 | uint8_t *ptr = _sfxMod.samples[sample - 1].buf.buf; 216 | fprintf(stdout, "Preparing sample %d ptr = %p\n", sample, ptr); 217 | if (ptr != 0) { 218 | pat->sample_volume = _sfxMod.samples[sample - 1].volume; 219 | pat->sample_start = 8; 220 | pat->sample_buffer = ptr; 221 | pat->period_value = READ_BE_UINT16(ptr) * 2; 222 | uint16_t loopLen = READ_BE_UINT16(ptr + 2) * 2; 223 | if (loopLen != 0) { 224 | pat->loopPos = pat->period_value; 225 | pat->loopData = ptr; 226 | pat->loopLen = loopLen; 227 | } else { 228 | pat->loopPos = 0; 229 | pat->loopData = 0; 230 | pat->loopLen = 0; 231 | } 232 | int16_t m = pat->sample_volume; 233 | uint8_t effect = pat->note_2 & 0x0F00; 234 | fprintf(stdout, "effect = %d\n", effect); 235 | if (effect == 5) { // volume up 236 | uint8_t volume = (pat->note_2 & 0xFF); 237 | m += volume; 238 | if (m > 0x3F) { 239 | m = 0x3F; 240 | } 241 | } else if (effect == 6) { // volume down 242 | uint8_t volume = (pat->note_2 & 0xFF); 243 | m -= volume; 244 | if (m < 0) { 245 | m = 0; 246 | } 247 | } else if (effect != 0) { 248 | fprintf(stderr, "Unhandled effect %d\n", effect); 249 | } 250 | Mix_setChannelVolume(channel, m); 251 | pat->sample_volume = m; 252 | } 253 | } 254 | } 255 | if (pat->note_1 == 0xFFFD) { // 'PIC' 256 | pat->note_2 = 0; 257 | } else if (pat->note_1 != 0) { 258 | pat->period_arpeggio = pat->note_1; 259 | if (pat->period_arpeggio == 0xFFFE) { 260 | Mix_stopChannel(channel); 261 | } else if (pat->sample_buffer != 0) { 262 | InstrumentSample sample; 263 | memset(&sample, 0, sizeof(sample)); 264 | sample.data = pat->sample_buffer + pat->sample_start; 265 | sample.len = pat->period_value; 266 | sample.loopPos = pat->loopPos; 267 | sample.loopLen = pat->loopLen; 268 | assert(pat->note_1 < 0x1000); 269 | uint16_t freq = PAULA_FREQ / pat->note_1; 270 | fprintf(stdout, "Adding sample indFreq = %d freq = %d dataPtr = %p\n", pat->note_1, freq, sample.data); 271 | Mix_playChannel(channel, &sample, freq, pat->sample_volume); 272 | } 273 | } 274 | } 275 | 276 | void Player::Mix_playChannel(int channel, InstrumentSample *sample, int freq, int volume) { 277 | _channels[channel].sample = *sample; 278 | _channels[channel].pos = 0; 279 | _channels[channel].inc = (freq << kFracBits) / _rate; 280 | _channels[channel].volume = volume; 281 | } 282 | 283 | void Player::Mix_stopChannel(int channel) { 284 | memset(&_channels[channel], 0, sizeof(Channel)); 285 | } 286 | 287 | void Player::Mix_setChannelVolume(int channel, int volume) { 288 | _channels[channel].volume = volume; 289 | } 290 | 291 | void Player::Mix_stopAll() { 292 | memset(_channels, 0, sizeof(_channels)); 293 | } 294 | 295 | void Player::Mix_doMixChannel(int8_t *buf, int channel) { 296 | InstrumentSample *sample = &_channels[channel].sample; 297 | if (sample->data == 0) { 298 | return; 299 | } 300 | const int pos = _channels[channel].pos >> kFracBits; 301 | _channels[channel].pos += _channels[channel].inc; 302 | if (sample->loopLen != 0) { 303 | if (pos == sample->loopPos + sample->len - 1) { 304 | _channels[channel].pos = sample->loopPos << kFracBits; 305 | } 306 | } else { 307 | if (pos == sample->len - 1) { 308 | sample->data = 0; 309 | return; 310 | } 311 | } 312 | int s = (int8_t)sample->data[pos]; 313 | s = *buf + s * _channels[channel].volume / 64; 314 | if (s < -128) { 315 | s = -128; 316 | } else if (s > 127) { 317 | s = 127; 318 | } 319 | *buf = (int8_t)s; 320 | } 321 | 322 | void Player::Mix_doMix(int8_t *buf, int len) { 323 | memset(buf, 0, 2 * len); 324 | if (_delay != 0) { 325 | const int samplesPerTick = _rate / (1000 / _delay); 326 | while (len != 0) { 327 | if (_samplesLeft == 0) { 328 | handleEvents(); 329 | _samplesLeft = samplesPerTick; 330 | } 331 | int count = _samplesLeft; 332 | if (count > len) { 333 | count = len; 334 | } 335 | _samplesLeft -= count; 336 | len -= count; 337 | for (int i = 0; i < count; ++i) { 338 | Mix_doMixChannel(buf, 0); 339 | Mix_doMixChannel(buf, 3); 340 | ++buf; 341 | Mix_doMixChannel(buf, 1); 342 | Mix_doMixChannel(buf, 2); 343 | ++buf; 344 | } 345 | } 346 | } 347 | } 348 | 349 | static void nr_stereo(const int8_t *in, int len, int8_t *out) { 350 | static int prevL = 0; 351 | static int prevR = 0; 352 | for (int i = 0; i < len; ++i) { 353 | const int sL = *in++ >> 1; 354 | *out++ = sL + prevL; 355 | prevL = sL; 356 | const int sR = *in++ >> 1; 357 | *out++ = sR + prevR; 358 | prevR = sR; 359 | } 360 | } 361 | 362 | static void playSoundfx(int num) { 363 | Player p; 364 | p.loadSfxModule(num); 365 | p.start(); 366 | FILE *fp = fopen("out.raw", "wb"); 367 | if (fp) { 368 | while (p._playingMusic) { 369 | static const int kBufSize = 2048; 370 | int8_t inbuf[kBufSize * 2]; 371 | p.Mix_doMix(inbuf, kBufSize); 372 | int8_t nrbuf[kBufSize * 2]; 373 | nr_stereo(inbuf, kBufSize, nrbuf); 374 | fwrite(nrbuf, 1, sizeof(nrbuf), fp); 375 | } 376 | fclose(fp); 377 | } 378 | p.stop(); 379 | } 380 | 381 | int main(int argc, char *argv[]) { 382 | if (argc == 2) { 383 | const int num = atoi(argv[1]); 384 | playSoundfx(num); 385 | } 386 | return 0; 387 | } 388 | -------------------------------------------------------------------------------- /tools/convert_wiiu/convert_wiiu_20th.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Convert data files from the WiiU version of 'Another World 20th Anniversary Edition' 4 | # to the Linux/Mac/Windows releases directory structure. 5 | # 6 | # $ tree -d game/ 7 | # game/ 8 | # |-- BGZ 9 | # | |-- data1152x720 10 | # | |-- data1280x800 11 | # |-- DAT 12 | # |-- OGG 13 | # | |-- original 14 | # |-- PNG 15 | # |-- TXT 16 | # | |-- Linux 17 | # |-- WGZ 18 | # |-- original 19 | 20 | import gzip 21 | import os 22 | import os.path 23 | import shutil 24 | import sys 25 | from PIL import Image 26 | 27 | OUT = 'out/game/' 28 | 29 | PARTS = { 30 | 'intro2011' : [ 0x17, 0x18, 0x19 ], # 16001 31 | 'eau2011' : [ 0x1A, 0x1B, 0x1C ], # 16002 32 | 'pri2011' : [ 0x1D, 0x1E, 0x1F ], # 16003 33 | 'cite2011' : [ 0x20, 0x21, 0x22 ], # 16004 34 | 'arene2011' : [ 0x23, 0x24, 0x25 ], # 16005 35 | 'luxe2011' : [ 0x26, 0x27, 0x28 ], # 16006 36 | 'final2011' : [ 0x29, 0x2A, 0x2B ] # 16007 37 | } 38 | 39 | BITMAP_SIZE = '1728x1080' 40 | 41 | def convert_awt(dir, out): 42 | dir = os.path.join(dir, 'data' + BITMAP_SIZE) 43 | for png in os.listdir(dir): 44 | (name, ext) = os.path.splitext(png) 45 | if ext == '.png': 46 | source = os.path.join(dir, png) 47 | target = os.path.join(out, BITMAP_SIZE + '_' + name + '.bmp') 48 | with Image.open(source) as im: 49 | im.save(target) 50 | source = target 51 | target = os.path.join(out, BITMAP_SIZE + '_' + name + '.bgz') 52 | with gzip.open(target, 'wb') as f: 53 | shutil.copyfileobj(file(source), f) 54 | 55 | def convert_bmp(dir, out): 56 | for bmp in os.listdir(dir): 57 | (name, ext) = os.path.splitext(bmp) 58 | if ext == '.bmp': 59 | if name.startswith('font') or name.startswith('heads'): 60 | name = name.capitalize() 61 | source = os.path.join(dir, bmp) 62 | target = os.path.join(out, name + '.bgz') 63 | with gzip.open(target, 'wb') as f: 64 | shutil.copyfileobj(file(source), f) 65 | 66 | def convert_dat(dir, out): 67 | for dat in os.listdir(dir): 68 | num = -1 69 | if dat == 'bank2.mat': 70 | num = 0x11 71 | else: 72 | (name, ext) = os.path.splitext(dat) 73 | name = name.lower() 74 | if PARTS.has_key(name): 75 | ext = ext.lower() 76 | if ext == '.pal': 77 | num = PARTS[name][0] 78 | elif ext == '.mac': 79 | num = PARTS[name][1] 80 | elif ext == '.mat': 81 | num = PARTS[name][2] 82 | if num != -1: 83 | source = os.path.join(dir, dat) 84 | target = os.path.join(out, 'FILE%03d.DAT' % num) 85 | shutil.copy(source, target) 86 | 87 | 88 | def convert_txt(dir, out): 89 | for txt in os.listdir(dir): 90 | (name, ext) = os.path.splitext(txt) 91 | if ext == '.txt': 92 | source = os.path.join(dir, txt) 93 | target = os.path.join(out, name.upper() + '.txt') 94 | shutil.copy(source, target) 95 | 96 | def convert_wgz(dir, out): 97 | for wav in os.listdir(dir): 98 | (name, ext) = os.path.splitext(wav) 99 | if ext == '.wav': 100 | source = os.path.join(dir, wav) 101 | target = os.path.join(out, name + '.wgz') 102 | with gzip.open(target, 'wb') as f: 103 | shutil.copyfileobj(file(source), f) 104 | 105 | TYPES = { 106 | 'awt': [ convert_awt, 'BGZ/data' + BITMAP_SIZE ], 107 | 'bmp': [ convert_bmp, 'BGZ' ], 108 | 'dat': [ convert_dat, 'DAT' ], 109 | 'txt': [ convert_txt, 'TXT' ], 110 | 'wgz_mixed': [ convert_wgz, 'WGZ' ] 111 | } 112 | 113 | dir = 'game' 114 | if len(sys.argv) > 1: 115 | dir = sys.argv[1] 116 | 117 | for f in os.listdir(dir): 118 | if TYPES.has_key(f): 119 | print "Converting '%s'" % f 120 | out = os.path.join(OUT, TYPES[f][1]) 121 | try: 122 | os.makedirs(out) 123 | except: 124 | pass 125 | TYPES[f][0](os.path.join(dir, f), out) 126 | -------------------------------------------------------------------------------- /tools/convert_wiiu/extract_wiiu.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Extract data files from 'archive.bin' found in the WiiU and PS Vita versions 4 | # of 'Another World 20th Anniversary Edition'. 5 | # 6 | # liblz4.so is required to decompress the game data. 7 | # 8 | 9 | import ctypes 10 | import io 11 | import os 12 | import os.path 13 | import struct 14 | import sys 15 | from PIL import Image 16 | 17 | endian = '>' # LE for Vita, BE for WiiU 18 | rgb = 'RGBA' # RGB for Vita, RGBA for WiiU 19 | 20 | fname = 'content/archive.bin' 21 | if len(sys.argv) > 1: 22 | fname = sys.argv[1] 23 | if len(sys.argv) > 2 and sys.argv[2] == 'vita': 24 | endian = '<' 25 | rgb = 'RGB' 26 | 27 | f = open(fname, 'rb') 28 | f.seek(-8, io.SEEK_END) 29 | pos = struct.unpack(endian + 'I', f.read(4))[0] 30 | count = struct.unpack(endian + 'I', f.read(4))[0] 31 | print '0x%x, %d files' % (pos, count) 32 | 33 | def read_cstr(f): 34 | str = '' 35 | while True: 36 | chr = f.read(1) 37 | if ord(chr) == 0: 38 | break 39 | str += chr 40 | return str 41 | 42 | for i in range(0, count): 43 | f.seek(pos) 44 | 45 | fname = read_cstr(f) 46 | uncompressed = struct.unpack(endian + 'I', f.read(4))[0] 47 | size = struct.unpack(endian + 'I', f.read(4))[0] 48 | offset = struct.unpack(endian + 'I', f.read(4))[0] 49 | flags = struct.unpack(endian + 'I', f.read(4))[0] 50 | assert flags == 0 or flags == 1 51 | 52 | pos = f.tell() 53 | 54 | print "%s %d %d 0x%x 0x%x" % (fname, uncompressed, size, offset, flags) 55 | 56 | fn = os.path.join('dump', fname) 57 | try: 58 | os.makedirs(os.path.dirname(fn)) 59 | except: 60 | pass 61 | 62 | f.seek(offset) 63 | 64 | if uncompressed != size: 65 | liblz4 = ctypes.cdll.LoadLibrary('liblz4.so') 66 | src = f.read(size) 67 | dst = ctypes.create_string_buffer(uncompressed) 68 | ret = liblz4.LZ4_decompress_safe(src, dst, size, uncompressed) 69 | if ret != uncompressed: 70 | print 'LZ4_decompress_safe returned %d, expected %d' % (ret, uncompressed) 71 | continue 72 | o = open(fn, 'wb') 73 | o.write(dst.raw) 74 | o.close() 75 | else: 76 | o = open(fn, 'wb') 77 | o.write(f.read(size)) 78 | o.close() 79 | 80 | # background textures 81 | if fn.endswith('.awt'): 82 | awt = open(fn) 83 | # 12 bytes footer 84 | # 08 08 08 08 LE16(w) LE16(h) LE16(w) LE16(h) 85 | awt.seek(-8, io.SEEK_END) 86 | w = struct.unpack(endian + 'H', awt.read(2))[0] 87 | h = struct.unpack(endian + 'H', awt.read(2))[0] 88 | awt.seek(0) 89 | image = Image.frombytes(rgb, (w, h), awt.read(w * h * len(rgb))) 90 | image.save(os.path.splitext(fn)[0] + '.png') 91 | 92 | # low resolution (original 320x200) background pictures 93 | elif fn.endswith('.bms'): 94 | os.rename(fn, os.path.splitext(fn)[0] + '.bmp') 95 | 96 | # game code (bytecode) 97 | elif fn.endswith('.mac'): 98 | asm = os.path.splitext(fn)[0] + '.asm' 99 | os.system('../disasm/disasm %s > %s' % (fn, asm)) 100 | -------------------------------------------------------------------------------- /tools/decode_mat/Makefile: -------------------------------------------------------------------------------- 1 | 2 | CXXFLAGS += -Wall -Wpedantic 3 | 4 | SRCS = decode_mat.c bitmap.c 5 | 6 | OBJS = $(SRCS:.c=.o) 7 | 8 | decode_mat: $(OBJS) 9 | $(CC) $(LDFLAGS) -o $@ $(OBJS) 10 | 11 | clean: 12 | rm -f $(OBJS) decode_mat 13 | -------------------------------------------------------------------------------- /tools/decode_mat/bitmap.c: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include "bitmap.h" 4 | 5 | static const uint16_t TAG_BM = 0x4D42; 6 | 7 | static void fwriteByte(FILE *fp, uint8_t n) { 8 | fputc(n, fp); 9 | } 10 | 11 | static void fwriteUint16LE(FILE *fp, uint16_t n) { 12 | fwriteByte(fp, n & 0xFF); 13 | fwriteByte(fp, n >> 8); 14 | } 15 | 16 | static void fwriteUint32LE(FILE *fp, uint32_t n) { 17 | fwriteUint16LE(fp, n & 0xFFFF); 18 | fwriteUint16LE(fp, n >> 16); 19 | } 20 | 21 | void WriteFile_BMP_PAL(const char *filename, int w, int h, int pitch, const uint8_t *bits, const uint8_t *pal) { 22 | FILE *fp = fopen(filename, "wb"); 23 | if (fp) { 24 | int alignWidth = (w + 3) & ~3; 25 | int imageSize = alignWidth * h; 26 | 27 | // Write file header 28 | fwriteUint16LE(fp, TAG_BM); 29 | fwriteUint32LE(fp, 14 + 40 + 4 * 256 + imageSize); 30 | fwriteUint16LE(fp, 0); // reserved1 31 | fwriteUint16LE(fp, 0); // reserved2 32 | fwriteUint32LE(fp, 14 + 40 + 4 * 256); 33 | 34 | // Write info header 35 | fwriteUint32LE(fp, 40); 36 | fwriteUint32LE(fp, w); 37 | fwriteUint32LE(fp, h); 38 | fwriteUint16LE(fp, 1); // planes 39 | fwriteUint16LE(fp, 8); // bit_count 40 | fwriteUint32LE(fp, 0); // compression 41 | fwriteUint32LE(fp, imageSize); // size_image 42 | fwriteUint32LE(fp, 0); // x_pels_per_meter 43 | fwriteUint32LE(fp, 0); // y_pels_per_meter 44 | fwriteUint32LE(fp, 0); // num_colors_used 45 | fwriteUint32LE(fp, 0); // num_colors_important 46 | 47 | // Write palette data 48 | for (int i = 0; i < 256; ++i) { 49 | fwriteByte(fp, pal[2]); 50 | fwriteByte(fp, pal[1]); 51 | fwriteByte(fp, pal[0]); 52 | fwriteByte(fp, 0); 53 | pal += 3; 54 | } 55 | 56 | // Write bitmap data 57 | bits += h * pitch; 58 | for (int i = 0; i < h; ++i) { 59 | bits -= pitch; 60 | fwrite(bits, w, 1, fp); 61 | int pad = alignWidth - w; 62 | while (pad--) { 63 | fwriteByte(fp, 0); 64 | } 65 | } 66 | 67 | fclose(fp); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /tools/decode_mat/bitmap.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef BITMAP_H__ 3 | #define BITMAP_H__ 4 | 5 | #include 6 | 7 | void WriteFile_BMP_PAL(const char *filename, int w, int h, int pitch, const uint8_t *bits, const uint8_t *pal); 8 | 9 | #endif // BITMAP_H__ 10 | -------------------------------------------------------------------------------- /tools/decode_mat/decode_mat.c: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "bitmap.h" 9 | #include "data.h" 10 | 11 | static uint8_t palette[16 * 3] = { 12 | 0x22,0x22,0x33,0x33,0x33,0x55,0x33,0x44,0x77,0x55,0xaa,0xff,0x77,0x66,0x66,0xdd,0xbb,0xaa,0x99,0x77,0x77,0xdd,0x99,0x88, 13 | 0x88,0x33,0x00,0x99,0xcc,0xff,0x44,0x44,0x55,0x44,0x55,0x77,0x55,0x66,0x99,0x55,0x77,0xdd,0x00,0x99,0xff,0xbb,0x55,0x00 14 | }; 15 | 16 | enum { 17 | MAX_FILESIZE = 0x10000, 18 | MAX_SHAPES = 4096, 19 | MAX_VERTICES = 2048, 20 | SCREEN_W = 320, 21 | SCREEN_H = 200, 22 | SCALE = 64, 23 | HD_SCALE = 4, // hd.mat files are 1280x800 24 | }; 25 | 26 | static uint8_t _buffer[MAX_FILESIZE]; 27 | 28 | static struct { 29 | uint32_t offset; 30 | char name[7]; 31 | } _shapes[MAX_SHAPES]; 32 | 33 | static uint8_t _screen[SCREEN_W * SCREEN_H]; 34 | 35 | static struct { 36 | int x; 37 | int y; 38 | } _vertices[MAX_VERTICES]; 39 | 40 | static uint16_t readWord(const uint8_t *p) { 41 | return (p[0] << 8) | p[1]; 42 | } 43 | 44 | static void readShapeNames(const uint8_t *buf, uint32_t size) { 45 | int count = 0; 46 | uint32_t offset = 0; 47 | while (offset < size) { 48 | const uint16_t addr = readWord(buf + offset); offset += 2; 49 | assert(count < MAX_SHAPES); 50 | _shapes[count].offset = addr << 1; 51 | memcpy(_shapes[count].name, buf + offset, 6); offset += 6; 52 | ++count; 53 | } 54 | } 55 | 56 | static uint32_t calcStep(int p1_x, int p1_y, int p2_x, int p2_y, uint16_t *dy) { 57 | *dy = p2_y - p1_y; 58 | uint16_t delta = (*dy <= 1) ? 1 : *dy; 59 | return ((p2_x - p1_x) * (0x4000 / delta)) << 2; 60 | } 61 | 62 | static void fillPolygon(const uint8_t *data, int color, int zoom, int x, int y) { 63 | 64 | int w = data[0] * zoom / SCALE; 65 | int h = data[1] * zoom / SCALE; 66 | data += 2; 67 | 68 | int x1 = x - w / 2; 69 | int x2 = x + w / 2; 70 | int y1 = y - h / 2; 71 | int y2 = y + h / 2; 72 | 73 | if (x1 >= SCREEN_W || x2 < 0 || y1 >= SCREEN_H || y2 < 0) 74 | return; 75 | 76 | int count = *data++; 77 | assert((count & 1) == 0); 78 | assert(count <= MAX_VERTICES); 79 | for (int i = 0; i < count; ++i) { 80 | _vertices[i].x = data[0] * zoom / SCALE; 81 | _vertices[i].y = data[1] * zoom / SCALE; 82 | data += 2; 83 | } 84 | 85 | if (color >= 16) { 86 | assert(color == 0x10 || color == 0x11); 87 | color = 15; 88 | } 89 | 90 | int i = 0; 91 | int j = count - 1; 92 | 93 | x2 = x1 + _vertices[i].x; 94 | x1 = x1 + _vertices[j].x; 95 | int scanline = y1; 96 | 97 | ++i; 98 | --j; 99 | 100 | uint32_t cpt1 = x1 << 16; 101 | uint32_t cpt2 = x2 << 16; 102 | 103 | while (1) { 104 | count -= 2; 105 | if (count == 0) { 106 | return; 107 | } 108 | uint16_t h; 109 | uint32_t step1 = calcStep(_vertices[j + 1].x, _vertices[j + 1].y, _vertices[j].x, _vertices[j].y, &h); 110 | uint32_t step2 = calcStep(_vertices[i - 1].x, _vertices[i - 1].y, _vertices[i].x, _vertices[i].y, &h); 111 | 112 | ++i; 113 | --j; 114 | 115 | cpt1 = (cpt1 & 0xFFFF0000) | 0x7FFF; 116 | cpt2 = (cpt2 & 0xFFFF0000) | 0x8000; 117 | 118 | if (h == 0) { 119 | cpt1 += step1; 120 | cpt2 += step2; 121 | } else { 122 | while (h--) { 123 | if (scanline >= 0) { 124 | x1 = (int16_t)(cpt1 >> 16); 125 | x2 = (int16_t)(cpt2 >> 16); 126 | if (x1 > x2) { 127 | const int tmp = x1; 128 | x1 = x2; 129 | x2 = tmp; 130 | } 131 | if (x1 < 0) { 132 | x1 = 0; 133 | } 134 | if (x2 > SCREEN_W - 1) { 135 | x2 = SCREEN_W - 1; 136 | } 137 | const int len = x2 - x1 + 1; 138 | if (len > 0) { 139 | memset(_screen + scanline * SCREEN_W + x1, color, len); 140 | } 141 | } 142 | cpt1 += step1; 143 | cpt2 += step2; 144 | ++scanline; 145 | if (scanline >= SCREEN_H) return; 146 | } 147 | } 148 | } 149 | } 150 | 151 | static void decodeShapeMask(int num, int color, int x, int y) { 152 | static const int COUNT = sizeof(shapes_offset) / sizeof(shapes_offset[0]); 153 | if (num < COUNT) { 154 | const uint8_t *p = shapes_data + shapes_offset[num]; 155 | const int w = *p++; 156 | x -= w / 2; 157 | const int h = *p++; 158 | y -= h / 2; 159 | for (int j = 0; j < h; ++j) { 160 | for (int i = 0; i <= w / 16; ++i) { 161 | const uint16_t mask = readWord(p); p += 2; 162 | for (int b = 0; b < 16; ++b) { 163 | if (mask & (1 << (15 - b))) { 164 | _screen[(y + j) * SCREEN_W + x + i * 16 + b] = color; 165 | } 166 | } 167 | } 168 | } 169 | } 170 | } 171 | 172 | static void dumpShape(const uint8_t *data, const char *name, int color, int zoom, int x, int y); 173 | 174 | static void dumpShapeParts(const uint8_t *data, int zoom, int x, int y) { 175 | int x0 = x - data[0] * zoom / SCALE; 176 | int y0 = y - data[1] * zoom / SCALE; 177 | data += 2; 178 | int count = *data++; 179 | for (int i = 0; i <= count; ++i) { 180 | uint16_t offset = readWord(data); data += 2; 181 | int x1 = x0 + data[0] * zoom / SCALE; 182 | int y1 = y0 + data[1] * zoom / SCALE; 183 | data += 2; 184 | int color = 0xFF; 185 | if (offset & 0x8000) { 186 | color = *data++; 187 | const int num = *data++; 188 | if (color & 0x80) { 189 | decodeShapeMask(num, color & 0x7F, x1, y1); 190 | continue; 191 | } 192 | } 193 | offset <<= 1; 194 | dumpShape(_buffer + offset, 0, color, zoom, x1, y1); 195 | } 196 | } 197 | 198 | static void dumpShape(const uint8_t *data, const char *name, int color, int zoom, int x, int y) { 199 | const int code = *data++; 200 | if (code >= 0xC0) { 201 | if (color & 0x80) { 202 | color = code & 0x3F; 203 | } 204 | fillPolygon(data, color, zoom, x, y); 205 | } else { 206 | assert((code & 0x3F) == 2); 207 | dumpShapeParts(data, zoom, x, y); 208 | } 209 | } 210 | 211 | static int readFile(const char *path) { 212 | int read, size = 0; 213 | FILE *fp; 214 | 215 | fp = fopen(path, "rb"); 216 | if (fp) { 217 | fseek(fp, 0, SEEK_END); 218 | size = ftell(fp); 219 | fseek(fp, 0, SEEK_SET); 220 | assert(size <= MAX_FILESIZE); 221 | read = fread(_buffer, 1, size, fp); 222 | if (read != size) { 223 | fprintf(stderr, "Failed to read %d bytes (%d)\n", size, read); 224 | } 225 | fclose(fp); 226 | } 227 | return size; 228 | } 229 | 230 | int main(int argc, char *argv[]) { 231 | int scale = SCALE; 232 | if (argc == 3) { 233 | if (strcmp(argv[1], "-hd") == 0) { 234 | scale = SCALE / HD_SCALE; 235 | ++argv; 236 | --argc; 237 | } 238 | } 239 | if (argc == 2) { 240 | char path[MAXPATHLEN]; 241 | struct stat st; 242 | if (stat(argv[1], &st) == 0 && S_ISREG(st.st_mode)) { 243 | strcpy(path, argv[1]); 244 | int count = 0; 245 | char *ext = strrchr(path, '.'); 246 | if (ext) { 247 | strcpy(ext, ".nom"); 248 | const int size = readFile(path); 249 | if (size != 0) { 250 | assert((size & 7) == 0); 251 | count = size / 8; 252 | readShapeNames(_buffer, size); 253 | } 254 | } 255 | const int size = readFile(argv[1]); 256 | if (size != 0) { 257 | for (int i = 0; i < count; ++i) { 258 | fprintf(stdout, "Dumping shape '%s'\n", _shapes[i].name); 259 | memset(_screen, 0, sizeof(_screen)); 260 | dumpShape(_buffer + _shapes[i].offset, _shapes[i].name, 0xFF, scale, SCREEN_W / 2, SCREEN_H / 2); 261 | char filename[64]; 262 | snprintf(filename, sizeof(filename), "%s.bmp", _shapes[i].name); 263 | WriteFile_BMP_PAL(filename, SCREEN_W, SCREEN_H, SCREEN_W, _screen, palette); 264 | } 265 | } 266 | } 267 | } 268 | return 0; 269 | } 270 | -------------------------------------------------------------------------------- /tools/disasm/Makefile: -------------------------------------------------------------------------------- 1 | 2 | CXXFLAGS += -g -O -Wall -Wpedantic 3 | 4 | SRCS = disasm.cpp 5 | 6 | OBJS = $(SRCS:.cpp=.o) 7 | DEPS = $(SRCS:.cpp=.d) 8 | 9 | disasm: $(OBJS) 10 | $(CXX) $(LDFLAGS) -o $@ $(OBJS) 11 | 12 | .cpp.o: 13 | $(CXX) $(CXXFLAGS) -MMD -c $< -o $*.o 14 | 15 | clean: 16 | rm -f $(OBJS) $(DEPS) disasm 17 | 18 | -include $(DEPS) 19 | --------------------------------------------------------------------------------